# source: https://raw.githubusercontent.com/remiotore/freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/LS.py
"""
Freqtrade strategy equivalent to the TradingView Pine Script (BinHV27_combined).
Replicates the same parameters, indicators, entry/exit logic, dynamic stop-loss, etc.

You must enable "can_short = True" in order to allow short entries, and configure
Freqtrade for margin or futures accordingly.

Disclaimer: The code is provided as an illustrative example; you may need to
adjust it to your environment or for minor syntax changes if using newer versions
of freqtrade/pandas_ta.
"""

import logging
from typing import Dict, List, Optional, Any
import pandas as pd
import numpy as np

# Freqtrade imports
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import (IntParameter, DecimalParameter, BooleanParameter)
from freqtrade.persistence import Trade
from freqtrade.exchange import timeframe_to_minutes

# Third-party libraries
import talib.abstract as ta

logger = logging.getLogger(__name__)

#################################################################
# Helper functions
#################################################################

def linreg(series: pd.Series, period: int = 2) -> pd.Series:
    """
    Mimics TradingView's ta.linreg(series, period=2, offset=0).
    In TradingView, linreg(...) = linear regression of 'period' bars with 0 offset.
    For an exact TSF/linreg(2,0), the slope typically uses 1…period sample.
    The formula for TSF (Time Series Forecast) at bar i is:
        TSF(i) = a0 + a1 * (period-1)
    where a1 is slope, a0 intercept.
    
    A simpler approach for small periods:
      - We can use a direct slope calculation between current bar and bar N bars ago
        or
      - Use a standard least-squares polynomial fit on the last `period` points.
    
    Because period=2 is very short, the slope is basically (series - series.shift(1)).
    We'll do a minimal approach that tries to replicate tv's linreg(…,2).
    """
    # For period=2 specifically, it's basically last 2 bars slope-based forecast:
    # slope = (val1 - val0)/1, intercept = val1 - slope*1
    # TSF(current) = intercept + slope*(period-1) = intercept + slope
    # which effectively equals val1 for offset=0. 
    # However, to emulate typical "linreg" with a 'least squares' approach for period=2:
    
    # Using a standard formula for "least squares" on 2 bars is almost the same as the last known price.
    # We'll implement a generic approach for any period using polyfit:
    linreg_vals = series.rolling(period).apply(
        lambda x: np.polyval(np.polyfit(range(period), x.values, 1), period-1),
        raw=False
    )
    return linreg_vals


def hlc3(df: pd.DataFrame) -> pd.Series:
    """Return typical price (H+L+C)/3 for each row."""
    return (df['high'] + df['low'] + df['close']) / 3.0


#################################################################
# Main Strategy Class
#################################################################

class Github_remiotore_freqtrade__LS__20260111_210550(IStrategy):
    """
    Converted from Pine Script:
    - Strategy name: "BinHV27_combined"
    - Emulates all parameters, logic, dynamic stoploss, custom exit.
    """

    ####################################################
    # --- Standard Freqtrade parameters
    ####################################################
    INTERFACE_VERSION = 3

    # This example uses 1h as base timeframe. 
    # Adjust if your Pine Script used a different base resolution.
    timeframe = '1h'

    # For multi-timeframe analysis, we fetch 4h data via informative pair.
    informative_timeframe = '4h'

    # Enable short for margin/futures mode. 
    can_short = True

    # Minimal ROI and stoploss just as placeholders. We'll override with custom stoploss.
    minimal_roi = {"0": 100}  # effectively no static ROI exit
    stoploss = -1  # we rely on custom_stoploss.

    # If you want to allow custom exit, set to True
    use_custom_stoploss = True
    use_custom_exit = True

    ####################################################
    # --- Pine Script Inputs as Freqtrade parameters ---
    ####################################################

    # 1) Buy parameters (long)
    entry_long_adx1 = IntParameter(10, 100, default=25, space="buy", optimize=False)
    entry_long_emarsi1 = IntParameter(10, 100, default=20, space="buy", optimize=False)
    entry_long_adx2 = IntParameter(20, 100, default=30, space="buy", optimize=False)
    entry_long_emarsi2 = IntParameter(20, 100, default=20, space="buy", optimize=False)
    entry_long_adx3 = IntParameter(10, 100, default=35, space="buy", optimize=False)
    entry_long_emarsi3 = IntParameter(10, 100, default=20, space="buy", optimize=False)
    entry_long_adx4 = IntParameter(20, 100, default=30, space="buy", optimize=False)
    entry_long_emarsi4 = IntParameter(20, 100, default=25, space="buy", optimize=False)

    # 2) Buy parameters (short)
    entry_short_adx1 = IntParameter(10, 100, default=62, space="buy", optimize=False)
    entry_short_emarsi1 = IntParameter(10, 100, default=29, space="buy", optimize=False)
    entry_short_adx2 = IntParameter(20, 100, default=29, space="buy", optimize=False)
    entry_short_emarsi2 = IntParameter(20, 100, default=30, space="buy", optimize=False)
    entry_short_adx3 = IntParameter(10, 100, default=33, space="buy", optimize=False)
    entry_short_emarsi3 = IntParameter(10, 100, default=22, space="buy", optimize=False)
    entry_short_adx4 = IntParameter(20, 100, default=88, space="buy", optimize=False)
    entry_short_emarsi4 = IntParameter(20, 100, default=57, space="buy", optimize=False)

    # 3) Dynamic stop parameters (long)
    pHSL_long = DecimalParameter(-0.99, -0.04, default=-0.25, space="sell", optimize=False)
    pPF_1_long = DecimalParameter(0.008, 0.1, default=0.012, space="sell", optimize=False)
    pSL_1_long = DecimalParameter(0.008, 0.1, default=0.01, space="sell", optimize=False)
    pPF_2_long = DecimalParameter(0.04, 0.2, default=0.05, space="sell", optimize=False)
    pSL_2_long = DecimalParameter(0.04, 0.2, default=0.04, space="sell", optimize=False)

    # 4) Dynamic stop parameters (short)
    pHSL_short = DecimalParameter(-0.99, -0.04, default=-0.863, space="sell", optimize=False)
    pPF_1_short = DecimalParameter(0.008, 0.1, default=0.018, space="sell", optimize=False)
    pSL_1_short = DecimalParameter(0.008, 0.1, default=0.015, space="sell", optimize=False)
    pPF_2_short = DecimalParameter(0.04, 0.2, default=0.197, space="sell", optimize=False)
    pSL_2_short = DecimalParameter(0.04, 0.2, default=0.157, space="sell", optimize=False)

    # 5) Exit parameters (long)
    exit_long_emarsi1 = IntParameter(10, 100, default=75, space="sell", optimize=False)
    exit_long_adx2    = IntParameter(10, 100, default=30, space="sell", optimize=False)
    exit_long_emarsi2 = IntParameter(20, 100, default=80, space="sell", optimize=False)
    exit_long_emarsi3 = IntParameter(20, 100, default=75, space="sell", optimize=False)

    # 6) Exit parameters (short)
    exit_short_emarsi1 = IntParameter(10, 100, default=30, space="sell", optimize=False)
    exit_short_adx2    = IntParameter(10, 100, default=21, space="sell", optimize=False)
    exit_short_emarsi2 = IntParameter(20, 100, default=71, space="sell", optimize=False)
    exit_short_emarsi3 = IntParameter(20, 100, default=72, space="sell", optimize=False)

    # 7) Exit switches
    exit_long_1  = BooleanParameter(default=True,  space="sell", optimize=False)
    exit_long_2  = BooleanParameter(default=True,  space="sell", optimize=False)
    exit_long_3  = BooleanParameter(default=True,  space="sell", optimize=False)
    exit_long_4  = BooleanParameter(default=True,  space="sell", optimize=False)
    exit_long_5  = BooleanParameter(default=True,  space="sell", optimize=False)

    exit_short_1 = BooleanParameter(default=False, space="sell", optimize=False)
    exit_short_2 = BooleanParameter(default=True,  space="sell", optimize=False)
    exit_short_3 = BooleanParameter(default=True,  space="sell", optimize=False)
    exit_short_4 = BooleanParameter(default=True,  space="sell", optimize=False)
    exit_short_5 = BooleanParameter(default=False, space="sell", optimize=False)

    # 8) Leverage param (informational only)
    leverage_num = IntParameter(1, 5, default=1, space="buy", optimize=False)

    ###############################################################
    # Informative pairs: We fetch 4h data for multi-timeframe logic
    ###############################################################
    def informative_pairs(self) -> List[tuple]:
        """
        Define additional (pair, timeframe) combinations to fetch for analysis.
        We'll request the same pair at the 4h timeframe.
        """
        pairs = []
        # We want to fetch the base pair at 4h
        pairs.append((self.dp.current_pair, self.informative_timeframe))
        return pairs

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Populate indicators for the 1h (base) timeframe.
        We'll also merge the 4h indicators (linreg-based) after computing them
        in a separate method or by using the informative pair logic.
        """
        # -------------------------------------------------------------
        # 1) Standard indicators on 1h (base timeframe)
        # -------------------------------------------------------------
        # RSI(5)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=5)
        # EMA of RSI(5)
        dataframe['emarsi'] = ta.EMA(dataframe['rsi'], timeperiod=5)

        # DMI(14) => ADX, plusDI, minusDI
        adx_all = ta.DX(dataframe, timeperiod=14)
        # ta.DX returns ADX alone. We also need +DI, -DI from a custom approach:
        # We'll replicate them quickly:
        # plusDI = 100 * (EMA( Max( (+DM,0) ), 14 ) / ATR(14))
        # minusDI = 100 * (EMA( Max( (-DM,0) ), 14 ) / ATR(14))
        # or we can do a quick approach from pandas_ta. 
        # If you prefer pure TA-Lib, you'd do:
        #   +DI = ta.PLUS_DI(dataframe, 14)
        #   -DI = ta.MINUS_DI(dataframe, 14)
        # but let's do that:

        dataframe['plus_di'] = ta.PLUS_DI(dataframe, timeperiod=14)
        dataframe['minus_di'] = ta.MINUS_DI(dataframe, timeperiod=14)
        dataframe['adx'] = adx_all

        # -DIEMA(25) / +DIEMA(5)
        dataframe['minus_di_ema25'] = ta.EMA(dataframe['minus_di'], timeperiod=25)
        dataframe['plus_di_ema5']   = ta.EMA(dataframe['plus_di'],   timeperiod=5)

        # EMA(60), EMA(120), SMA(120), SMA(240)
        dataframe['ema60']  = ta.EMA(dataframe, timeperiod=60)
        dataframe['ema120'] = ta.EMA(dataframe, timeperiod=120)
        dataframe['sma120'] = ta.SMA(dataframe, timeperiod=120)
        dataframe['sma240'] = ta.SMA(dataframe, timeperiod=240)

        # bigup / bigdown logic
        # bigup = (fastsma > slowsma) and ((fastsma - slowsma) > close/300)
        dataframe['fastsma'] = dataframe['sma120']
        dataframe['slowsma'] = dataframe['sma240']
        dataframe['bigup'] = (
            (dataframe['fastsma'] > dataframe['slowsma']) &
            ((dataframe['fastsma'] - dataframe['slowsma']) > (dataframe['close'] / 300))
        )
        dataframe['bigdown'] = ~dataframe['bigup']

        # "trend" for difference and checks
        dataframe['trend'] = dataframe['fastsma'] - dataframe['slowsma']
        # preparechangetrend = trend > trend[1]
        dataframe['trend_prev'] = dataframe['trend'].shift(1)
        dataframe['trend_prev2'] = dataframe['trend'].shift(2)
        dataframe['preparechangetrend'] = dataframe['trend'] > dataframe['trend_prev']
        dataframe['preparechangetrendconfirm'] = (
            dataframe['preparechangetrend'] &
            (dataframe['trend_prev'] > dataframe['trend_prev2'])
        )
        # continueup = (slowsma>slowsma[1]) and (slowsma[1]>slowsma[2])
        dataframe['slowsma_prev'] = dataframe['slowsma'].shift(1)
        dataframe['slowsma_prev2'] = dataframe['slowsma'].shift(2)
        dataframe['continueup'] = (
            (dataframe['slowsma'] > dataframe['slowsma_prev']) &
            (dataframe['slowsma_prev'] > dataframe['slowsma_prev2'])
        )
        # delta = fastsma - fastsma[1]
        dataframe['fastsma_prev'] = dataframe['fastsma'].shift(1)
        dataframe['delta'] = dataframe['fastsma'] - dataframe['fastsma_prev']
        dataframe['delta_prev'] = dataframe['delta'].shift(1)
        dataframe['slowingdown'] = dataframe['delta'] < dataframe['delta_prev']

        # Bollinger (20,2) for reference (not directly used in entry/exit, but keep it)
        bb_basis = ta.SMA(hlc3(dataframe), timeperiod=20)
        bb_std = 2.0 * pd.Series.rolling(hlc3(dataframe), window=20).std()
        dataframe['bb_upperband']  = bb_basis + bb_std
        dataframe['bb_middleband'] = bb_basis
        dataframe['bb_lowerband']  = bb_basis - bb_std

        # -------------------------------------------------------------
        # 2) Fetch and merge 4h informative data
        # -------------------------------------------------------------
        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.informative_timeframe)

        # We compute h_close4H, h_high4H, h_low4H => then do hlc3_4h => then linreg(2)
        informative['h_close4H'] = informative['close']
        informative['h_high4H']  = informative['high']
        informative['h_low4H']   = informative['low']
        informative['hlc3_4h']   = hlc3(informative)
        # replicate ta.linreg(hlc3_4h, 2, 0)
        informative['tsf_4h'] = linreg(informative['hlc3_4h'], period=2)

        # Only keep the columns we need, and rename them for clarity
        informative = informative[['date', 'hlc3_4h', 'tsf_4h']]

        # Merge with base timeframe
        dataframe = dataframe.merge(
            informative,
            on='date',
            how='left',
            suffixes=('', '_4h')
        )

        # Now the allow_long, allow_short logic:
        # allow_long = (tsf_4h / hlc3_4h) > 1.01
        # allow_short = (tsf_4h / hlc3_4h) < 0.99
        dataframe['allow_long'] = (
            (dataframe['tsf_4h'] / dataframe['hlc3_4h']) > 1.01
        )
        dataframe['allow_short'] = (
            (dataframe['tsf_4h'] / dataframe['hlc3_4h']) < 0.99
        )

        return dataframe

    def populate_buy_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Populate buy (long) and sell (short) signals.
        Because Freqtrade "buy" = open a long position in spot mode,
        and "sell" = close a long. To handle short, we must do a
        separate approach or rely on custom signals (or "entry_tag" approach).
        However, with "can_short = True", we can define "buy" for short if we set
        a signal = is_short: True. We'll do that below in populate_sell_trend or custom logic.
        """

        # --- LONG ENTRY LOGIC (mirror Pine Script) ---
        # Condition: enterLong = (long_entry_1 OR long_entry_2 OR long_entry_3 OR long_entry_4)

        # For readability, define a few aliases:
        df = dataframe
        # Shorter references to parameters:
        a1 = self.entry_long_adx1.value
        e1 = self.entry_long_emarsi1.value
        a2 = self.entry_long_adx2.value
        e2 = self.entry_long_emarsi2.value
        a3 = self.entry_long_adx3.value
        e3 = self.entry_long_emarsi3.value
        a4 = self.entry_long_adx4.value
        e4 = self.entry_long_emarsi4.value

        # Each sub-condition:
        long_entry_1 = (
            (df['allow_long']) &
            (df['slowsma'] > 0) &
            (df['close'] < df['ema120']) &
            (df['close'] < df['ema60']) &
            (df['minus_di'] > df['minus_di_ema25']) &
            (df['rsi'] >= df['rsi'].shift(1)) &
            (~df['preparechangetrend']) &
            (~df['continueup']) &
            (df['adx'] > a1) &
            (df['bigdown']) &
            (df['emarsi'] <= e1)
        )

        long_entry_2 = (
            (df['allow_long']) &
            (df['slowsma'] > 0) &
            (df['close'] < df['ema120']) &
            (df['close'] < df['ema60']) &
            (df['minus_di'] > df['minus_di_ema25']) &
            (df['rsi'] >= df['rsi'].shift(1)) &
            (~df['preparechangetrend']) &
            (df['continueup']) &
            (df['adx'] > a2) &
            (df['bigdown']) &
            (df['emarsi'] <= e2)
        )

        long_entry_3 = (
            (df['allow_long']) &
            (df['slowsma'] > 0) &
            (df['close'] < df['ema120']) &
            (df['close'] < df['ema60']) &
            (df['minus_di'] > df['minus_di_ema25']) &
            (df['rsi'] >= df['rsi'].shift(1)) &
            (~df['continueup']) &
            (df['adx'] > a3) &
            (df['bigup']) &
            (df['emarsi'] <= e3)
        )

        long_entry_4 = (
            (df['allow_long']) &
            (df['slowsma'] > 0) &
            (df['close'] < df['ema120']) &
            (df['close'] < df['ema60']) &
            (df['minus_di'] > df['minus_di_ema25']) &
            (df['rsi'] >= df['rsi'].shift(1)) &
            (df['continueup']) &
            (df['adx'] > a4) &
            (df['bigup']) &
            (df['emarsi'] <= e4)
        )

        df.loc[
            (long_entry_1 | long_entry_2 | long_entry_3 | long_entry_4),
            ['buy','buy_tag']
        ] = (1, 'enter_long')

        return df

    def populate_sell_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        This is used for short entries if `can_short = True`.
        In freqtrade, "sell" can close a long or open a short if "is_short" is set to True.
        We'll place the short-entry logic here by setting `df['sell'] = 1` with `is_short = True`.
        That instructs freqtrade to open a short position.
        However, remember that the standard "sell" also closes a long if there's an open long. 
        Freqtrade’s logic is a bit simpler if you use custom signals (entry_tag approach).
        We'll do the simpler approach: We instruct freqtrade to open short positions by
        returning an 'entry_tag' for short. 
        """

        df = dataframe
        # Shorter references:
        a1 = self.entry_short_adx1.value
        e1 = self.entry_short_emarsi1.value
        a2 = self.entry_short_adx2.value
        e2 = self.entry_short_emarsi2.value
        a3 = self.entry_short_adx3.value
        e3 = self.entry_short_emarsi3.value
        a4 = self.entry_short_adx4.value
        e4 = self.entry_short_emarsi4.value

        short_entry_1 = (
            (df['allow_short']) &
            (df['slowsma'] > 0) &
            (df['close'] < df['ema120']) &
            (df['close'] < df['ema60']) &
            (df['minus_di'] > df['minus_di_ema25']) &
            (df['rsi'] >= df['rsi'].shift(1)) &
            (~df['preparechangetrend']) &
            (~df['continueup']) &
            (df['adx'] > a1) &
            (df['bigdown']) &
            (df['emarsi'] <= e1)
        )

        short_entry_2 = (
            (df['allow_short']) &
            (df['slowsma'] > 0) &
            (df['close'] < df['ema120']) &
            (df['close'] < df['ema60']) &
            (df['minus_di'] > df['minus_di_ema25']) &
            (df['rsi'] >= df['rsi'].shift(1)) &
            (~df['preparechangetrend']) &
            (df['continueup']) &
            (df['adx'] > a2) &
            (df['bigdown']) &
            (df['emarsi'] <= e2)
        )

        short_entry_3 = (
            (df['allow_short']) &
            (df['slowsma'] > 0) &
            (df['close'] < df['ema120']) &
            (df['close'] < df['ema60']) &
            (df['minus_di'] > df['minus_di_ema25']) &
            (df['rsi'] >= df['rsi'].shift(1)) &
            (~df['continueup']) &
            (df['adx'] > a3) &
            (df['bigup']) &
            (df['emarsi'] <= e3)
        )

        short_entry_4 = (
            (df['allow_short']) &
            (df['slowsma'] > 0) &
            (df['close'] < df['ema120']) &
            (df['close'] < df['ema60']) &
            (df['minus_di'] > df['minus_di_ema25']) &
            (df['rsi'] >= df['rsi'].shift(1)) &
            (df['continueup']) &
            (df['adx'] > a4) &
            (df['bigup']) &
            (df['emarsi'] <= e4)
        )

        # Combine short signals:
        short_signal = (short_entry_1 | short_entry_2 | short_entry_3 | short_entry_4)

        # In freqtrade, to open short from "sell" side, we do:
        df.loc[short_signal, ['sell', 'sell_tag', 'is_short']] = (1, 'enter_short', True)

        # Otherwise, do nothing:
        return df

    ######################################################
    # Custom Stoploss to replicate the dynamic stops
    ######################################################
    def custom_stoploss(self, pair: str, trade: Trade, current_time: pd.Timestamp,
                        current_rate: float, current_profit: float, **kwargs) -> float:
        """
        Replicates the dynamic stoploss logic from the Pine Script:
          - pHSL
          - pPF_1 / pSL_1
          - pPF_2 / pSL_2
          - linear interpolation if current_profit is between pPF_1 and pPF_2
          - For short, everything is reversed in terms of direction
        Returns the stoploss (e.g. -0.10 for 10% stop).
        If we want no stop (or very large) in certain conditions, return 1 to disable or a big negative.
        """
        # Distinguish if this trade is short or long:
        is_short = trade.is_short

        if not is_short:
            # Long position
            hsl  = self.pHSL_long.value   # Hard stop
            pf_1 = self.pPF_1_long.value
            sl_1 = self.pSL_1_long.value
            pf_2 = self.pPF_2_long.value
            sl_2 = self.pSL_2_long.value

            if current_profit < pf_1:
                return abs(hsl)
            elif current_profit > pf_2:
                # stop = sl_2 + (current_profit - pf_2)
                return sl_2 + (current_profit - pf_2)
            else:
                # linear interpolation
                pct = (current_profit - pf_1) / (pf_2 - pf_1)
                return sl_1 + pct*(sl_2 - sl_1)

        else:
            # Short position
            hsl  = self.pHSL_short.value
            pf_1 = self.pPF_1_short.value
            sl_1 = self.pSL_1_short.value
            pf_2 = self.pPF_2_short.value
            sl_2 = self.pSL_2_short.value

            if current_profit < pf_1:
                return abs(hsl)
            elif current_profit > pf_2:
                return sl_2 + (current_profit - pf_2)
            else:
                pct = (current_profit - pf_1) / (pf_2 - pf_1)
                return sl_1 + pct*(sl_2 - sl_1)

    ######################################################
    # Custom Exit to replicate all “exit_*” conditions
    ######################################################
    def custom_exit(self, pair: str, trade: Trade, current_time: pd.Timestamp,
                    current_rate: float, current_profit: float, **kwargs) -> Optional[str]:
        """
        Checks the additional exit conditions from the Pine Script:
        exit_long_1/2/3/4/5 and exit_short_1/2/3/4/5.  
        
        Return a non-empty string (exit signal name) to close the position.
        Return None to keep the position open.
        """

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe is None or dataframe.empty:
            return None

        # We need the last row that matches current_time exactly (or the nearest index)
        # Because of possible candle alignment issues, let's do a safer approach:
        # find the candle in 'dataframe' that is <= current_time
        row = dataframe.loc[dataframe['date'] == current_time]
        if row.empty:
            # If no exact match, find last index less than current_time
            row = dataframe.loc[dataframe['date'] < current_time]
            if row.empty:
                return None
            row = row.iloc[-1]  # last available
        else:
            row = row.iloc[-1]

        is_short = trade.is_short

        # Gather booleans from strategy
        preparechangetrendconfirm = bool(row['preparechangetrendconfirm'])
        continueup = bool(row['continueup'])
        slowingdown = bool(row['slowingdown'])
        bigdown = bool(row['bigdown'])
        bigup = bool(row['bigup'])

        # (minusdi < plusdi)
        minus_di = row['minus_di']
        plus_di  = row['plus_di']

        # Also read the parameter toggles
        el1 = self.exit_long_1.value
        el2 = self.exit_long_2.value
        el3 = self.exit_long_3.value
        el4 = self.exit_long_4.value
        el5 = self.exit_long_5.value

        es1 = self.exit_short_1.value
        es2 = self.exit_short_2.value
        es3 = self.exit_short_3.value
        es4 = self.exit_short_4.value
        es5 = self.exit_short_5.value

        # Additional columns we need for conditions:
        close = row['close']
        ema60 = row['ema60']
        ema120 = row['ema120']
        slowsma = row['slowsma']
        adxVal = row['adx']
        emarsi = row['emarsi']

        # -----------
        # Multi-Exit logic for LONG
        # -----------
        if not is_short:
            # "longPos" exit conditions:
            exit_cond_long_1 = (
                el1 and
                (not preparechangetrendconfirm) and
                (not continueup) and
                ((close > ema60) or (close > ema120)) and
                (ema120 > 0) and
                bigdown
            )

            exit_cond_long_2 = (
                el2 and
                (not preparechangetrendconfirm) and
                (not continueup) and
                (close > ema120) and
                (ema120 > 0) and
                ((emarsi > self.exit_long_emarsi1.value) or (close > slowsma)) and
                bigdown
            )

            exit_cond_long_3 = (
                el3 and
                (not preparechangetrendconfirm) and
                (close > ema120) and
                (ema120 > 0) and
                (adxVal > self.exit_long_adx2.value) and
                (emarsi >= self.exit_long_emarsi2.value) and
                bigup
            )

            exit_cond_long_4 = (
                el4 and
                preparechangetrendconfirm and
                (not continueup) and
                slowingdown and
                (emarsi >= self.exit_long_emarsi3.value) and
                (slowsma > 0)
            )

            exit_cond_long_5 = (
                el5 and
                preparechangetrendconfirm and
                (minus_di < plus_di) and
                (close > ema60) and
                (slowsma > 0)
            )

            exit_long = (
                exit_cond_long_1 or
                exit_cond_long_2 or
                exit_cond_long_3 or
                exit_cond_long_4 or
                exit_cond_long_5
            )

            if exit_long:
                return "CustomExitLong"

        else:
            # -----------
            # Multi-Exit logic for SHORT
            # -----------
            exit_cond_short_1 = (
                es1 and
                (not preparechangetrendconfirm) and
                (not continueup) and
                ((close > ema60) or (close > ema120)) and
                (ema120 > 0) and
                bigdown
            )

            exit_cond_short_2 = (
                es2 and
                (not preparechangetrendconfirm) and
                (not continueup) and
                (close > ema120) and
                (ema120 > 0) and
                ((emarsi > self.exit_short_emarsi1.value) or (close > slowsma)) and
                bigdown
            )

            exit_cond_short_3 = (
                es3 and
                (not preparechangetrendconfirm) and
                (close > ema120) and
                (ema120 > 0) and
                (adxVal > self.exit_short_adx2.value) and
                (emarsi >= self.exit_short_emarsi2.value) and
                bigup
            )

            exit_cond_short_4 = (
                es4 and
                preparechangetrendconfirm and
                (not continueup) and
                slowingdown and
                (emarsi >= self.exit_short_emarsi3.value) and
                (slowsma > 0)
            )

            exit_cond_short_5 = (
                es5 and
                preparechangetrendconfirm and
                (minus_di < plus_di) and
                (close > ema60) and
                (slowsma > 0)
            )

            exit_short = (
                exit_cond_short_1 or
                exit_cond_short_2 or
                exit_cond_short_3 or
                exit_cond_short_4 or
                exit_cond_short_5
            )

            if exit_short:
                return "CustomExitShort"

        # If no exit triggered:
        return None