# source: https://raw.githubusercontent.com/vaskosmihaylov/nfi-custom-strategies/91c1f22df4c5b0ac540cf09a447fe0dd78d9d5cd/user_data/strategies/DevDsl2Approx/DevDsl2Approx.py
import logging
from datetime import datetime

import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np
import pandas as pd
import talib.abstract as ta
import technical.indicators as ftt
from freqtrade.persistence import Trade
from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy, stoploss_from_open
from pandas import DataFrame, Series

logger = logging.getLogger(__name__)


def vwap_bands(dataframe: DataFrame, window: int = 20, num_of_std: float = 1.0):
    df = dataframe.copy()
    df["vwap"] = qtpylib.rolling_vwap(df, window=window)
    rolling_std = df["vwap"].rolling(window=window).std()
    df["vwap_low"] = df["vwap"] - (rolling_std * num_of_std)
    df["vwap_high"] = df["vwap"] + (rolling_std * num_of_std)
    return df["vwap_low"], df["vwap"], df["vwap_high"]


def vwma(series: Series, volume: Series, length: int) -> Series:
    vol_sum = volume.rolling(length).sum()
    return (series * volume).rolling(length).sum() / vol_sum.replace(0, np.nan)


def smma(series: Series, period: int) -> Series:
    result = pd.Series(np.nan, index=series.index, dtype=float)
    if len(series) < period:
        return result

    result.iloc[period - 1] = series.iloc[:period].mean()
    for idx in range(period, len(series)):
        result.iloc[idx] = ((result.iloc[idx - 1] * (period - 1)) + series.iloc[idx]) / period
    return result


def dsl_rsi_level(rsi: Series, length: int) -> Series:
    upper = pd.Series(np.nan, index=rsi.index, dtype=float)
    lower = pd.Series(np.nan, index=rsi.index, dtype=float)
    alpha = 2.0 / (length + 1.0)
    upper_prev = 50.0
    lower_prev = 50.0

    for idx, value in enumerate(rsi.fillna(50.0)):
        if value > 50.0:
            upper_prev = upper_prev + alpha * (value - upper_prev)
        if value < 50.0:
            lower_prev = lower_prev + alpha * (value - lower_prev)
        upper.iloc[idx] = upper_prev
        lower.iloc[idx] = lower_prev

    return upper - lower


class Github_vaskosmihaylov_nfi_custom_strategies__DevDsl2Approx__20260513_073522(IStrategy):
    """
    A public-fingerprint reconstruction of `Dev_dsl2` tuned for local Bybit futures backtesting.

    The strategy keeps the original indicator family but now runs as a balanced long/short
    5m futures strategy so it can be evaluated against the available 2026 Bybit USDT dataset.
    """

    INTERFACE_VERSION = 3

    timeframe = "5m"
    can_short = True
    startup_candle_count = 240
    process_only_new_candles = True

    minimal_roi = {"0": 100.0}
    stoploss = -0.99
    trailing_stop = False
    use_custom_stoploss = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    order_types = {
        "entry": "market",
        "exit": "market",
        "stoploss": "market",
        "stoploss_on_exchange": False,
    }

    BUY_DEFAULTS = {
        "bb_factor": 0.992,
        "vwap_factor": 0.998,
        "rsi_14_max": 34,
        "rsi_slow_max": 42,
        "rsi_84_max": 58,
        "dc_percent_max": 0.32,
        "bbdelta_close_min": 0.011,
        "close_delta_min": 0.006,
        "dsl_delta_min": 0.2,
        "goodloss_max": 0.06,
        "donchian_room_min": 0.010,
        "volume_factor": 0.80,
        "smma_length": 14,
        "dc_length": 20,
        "trend_relax_factor": 0.975,
        "donchian_smma_factor": 0.97,
    }

    SELL_DEFAULTS = {
        "rsi_exit_min": 62,
        "fastk_exit_min": 78,
        "high_offset": 1.012,
        "vwap_factor": 1.002,
        "profit_fade_min": 0.022,
        "trend_fail_min": 0.012,
        "band_exhaustion_rsi_mfi_rmi_min": 60,
    }

    SHORT_DEFAULTS = {
        "bb_factor": 1.000,
        "vwap_factor": 1.000,
        "rsi_14_min": 70,
        "rsi_slow_min": 56,
        "rsi_84_min": 50,
        "dc_percent_min": 0.85,
        "dsl_delta_max": 0.0,
        "goodrise_max": 0.12,
        "donchian_room_min": 0.010,
        "volume_factor": 0.80,
        "trend_relax_factor": 0.995,
        "donchian_smma_factor": 1.01,
        "cover_rsi_max": 38,
        "cover_fastk_max": 22,
        "low_offset": 0.988,
        "cover_vwap_factor": 0.998,
        "band_compression_rsi_mfi_rmi_max": 40,
    }

    RISK_DEFAULTS = {
        "hard_stoploss": -0.08,
        "profit_floor_1": 0.016,
        "profit_floor_2": 0.06,
        "stop_1": 0.012,
        "stop_2": 0.04,
        "time_stop_minutes": 16 * 60,
        "time_stop_profit": 0.002,
    }

    buy_bb_factor = DecimalParameter(
        0.985,
        0.999,
        default=BUY_DEFAULTS["bb_factor"],
        decimals=3,
        space="buy",
        optimize=True,
    )
    buy_vwap_factor = DecimalParameter(
        0.990,
        1.000,
        default=BUY_DEFAULTS["vwap_factor"],
        decimals=3,
        space="buy",
        optimize=True,
    )
    buy_rsi_14_max = IntParameter(24, 40, default=BUY_DEFAULTS["rsi_14_max"], space="buy", optimize=True)
    buy_rsi_slow_max = IntParameter(30, 50, default=BUY_DEFAULTS["rsi_slow_max"], space="buy", optimize=True)
    buy_rsi_84_max = IntParameter(45, 70, default=BUY_DEFAULTS["rsi_84_max"], space="buy", optimize=True)
    buy_dc_percent_max = DecimalParameter(
        0.12,
        0.45,
        default=BUY_DEFAULTS["dc_percent_max"],
        decimals=2,
        space="buy",
        optimize=True,
    )
    buy_bbdelta_close_min = DecimalParameter(
        0.004,
        0.03,
        default=BUY_DEFAULTS["bbdelta_close_min"],
        decimals=3,
        space="buy",
        optimize=True,
    )
    buy_close_delta_min = DecimalParameter(
        0.001,
        0.02,
        default=BUY_DEFAULTS["close_delta_min"],
        decimals=3,
        space="buy",
        optimize=True,
    )
    buy_dsl_delta_min = DecimalParameter(
        -2.0,
        5.0,
        default=BUY_DEFAULTS["dsl_delta_min"],
        decimals=1,
        space="buy",
        optimize=True,
    )
    buy_goodloss_max = DecimalParameter(
        0.02,
        0.12,
        default=BUY_DEFAULTS["goodloss_max"],
        decimals=2,
        space="buy",
        optimize=True,
    )
    buy_donchian_room_min = DecimalParameter(
        0.004,
        0.04,
        default=BUY_DEFAULTS["donchian_room_min"],
        decimals=3,
        space="buy",
        optimize=True,
    )
    short_bb_factor = DecimalParameter(
        1.001,
        1.03,
        default=SHORT_DEFAULTS["bb_factor"],
        decimals=3,
        space="sell",
        optimize=True,
    )
    short_vwap_factor = DecimalParameter(
        1.000,
        1.02,
        default=SHORT_DEFAULTS["vwap_factor"],
        decimals=3,
        space="sell",
        optimize=True,
    )
    short_rsi_14_min = IntParameter(55, 86, default=SHORT_DEFAULTS["rsi_14_min"], space="sell", optimize=True)
    short_rsi_slow_min = IntParameter(45, 80, default=SHORT_DEFAULTS["rsi_slow_min"], space="sell", optimize=True)
    short_rsi_84_min = IntParameter(40, 75, default=SHORT_DEFAULTS["rsi_84_min"], space="sell", optimize=True)
    short_dc_percent_min = DecimalParameter(
        0.55,
        0.92,
        default=SHORT_DEFAULTS["dc_percent_min"],
        decimals=2,
        space="sell",
        optimize=True,
    )
    short_dsl_delta_max = DecimalParameter(
        -5.0,
        2.0,
        default=SHORT_DEFAULTS["dsl_delta_max"],
        decimals=1,
        space="sell",
        optimize=True,
    )
    short_goodrise_max = DecimalParameter(
        0.03,
        0.14,
        default=SHORT_DEFAULTS["goodrise_max"],
        decimals=2,
        space="sell",
        optimize=True,
    )
    short_donchian_room_min = DecimalParameter(
        0.004,
        0.04,
        default=SHORT_DEFAULTS["donchian_room_min"],
        decimals=3,
        space="sell",
        optimize=True,
    )

    sell_rsi_exit_min = IntParameter(55, 78, default=SELL_DEFAULTS["rsi_exit_min"], space="sell", optimize=True)
    sell_fastk_exit_min = IntParameter(68, 95, default=SELL_DEFAULTS["fastk_exit_min"], space="sell", optimize=True)
    sell_high_offset = DecimalParameter(
        1.004,
        1.03,
        default=SELL_DEFAULTS["high_offset"],
        decimals=3,
        space="sell",
        optimize=True,
    )
    sell_vwap_factor = DecimalParameter(
        1.000,
        1.02,
        default=SELL_DEFAULTS["vwap_factor"],
        decimals=3,
        space="sell",
        optimize=True,
    )

    plot_config = {
        "main_plot": {
            "vwap_low": {"color": "#7a8ca5"},
            "vwap_upperband": {"color": "#d17b5f"},
            "dc_upper": {"color": "#47a447"},
            "dc_lower": {"color": "#c95f5f"},
            "tenkan_sen": {"color": "#f2b134"},
            "high_offset_sma": {"color": "#b06ab3"},
            "low_offset_sma": {"color": "#3a7f55"},
        },
        "subplots": {
            "Momentum": {
                "rsi_14": {"color": "#4b8bbe"},
                "rsi_slow": {"color": "#306998"},
                "dsl_lvl": {"color": "#ff7f0e"},
            },
            "StochRSI": {
                "fastk_rsi": {"color": "#2ca02c"},
                "fastd_rsi": {"color": "#d62728"},
            },
        },
    }

    def version(self) -> str:
        return "2026.04.23-devdsl2-bybit-v2"

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        heikinashi = qtpylib.heikinashi(dataframe)
        dataframe["ha_open"] = heikinashi["open"]
        dataframe["ha_close"] = heikinashi["close"]
        dataframe["ha_high"] = heikinashi["high"]
        dataframe["ha_low"] = heikinashi["low"]

        dataframe["volume_mean_4"] = dataframe["volume"].rolling(4).mean().shift(1)
        dataframe["sma_15"] = ta.SMA(dataframe, timeperiod=15)
        dataframe["high_offset_sma"] = dataframe["sma_15"] * self.sell_high_offset.value
        dataframe["low_offset_sma"] = dataframe["sma_15"] * self.SHORT_DEFAULTS["low_offset"]
        dataframe["vwma"] = vwma(dataframe["close"], dataframe["volume"], 20)
        dataframe["smma_smma_length_value"] = smma(dataframe["close"], self.BUY_DEFAULTS["smma_length"])

        dataframe["trend_close_5m"] = dataframe["close"]
        dataframe["trend_close_15m"] = ta.EMA(dataframe["close"], timeperiod=3)
        dataframe["trend_close_30m"] = ta.EMA(dataframe["close"], timeperiod=6)
        dataframe["trend_close_1h"] = ta.EMA(dataframe["close"], timeperiod=12)
        dataframe["trend_close_2h"] = ta.EMA(dataframe["close"], timeperiod=24)
        dataframe["trend_close_4h"] = ta.EMA(dataframe["close"], timeperiod=48)
        dataframe["trend_close_6h"] = ta.EMA(dataframe["close"], timeperiod=72)
        dataframe["trend_open_6h"] = ta.EMA(dataframe["open"], timeperiod=72)

        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe["bb_lowerband2"] = bollinger["lower"]
        dataframe["bb_middleband2"] = bollinger["mid"]
        dataframe["bb_upperband2"] = bollinger["upper"]
        dataframe["bbdelta"] = (dataframe["bb_middleband2"] - dataframe["bb_lowerband2"]).abs()
        dataframe["close_delta2"] = (dataframe["close"] - dataframe["close"].shift()).abs()
        dataframe["lower_far"] = (dataframe["bb_lowerband2"] - dataframe["close"]) / dataframe["close"]
        dataframe["upper_near"] = (dataframe["bb_upperband2"] - dataframe["close"]) / dataframe["close"]
        dataframe["upper_far"] = (dataframe["close"] - dataframe["bb_upperband2"]) / dataframe["close"]
        dataframe["lower_near"] = (dataframe["close"] - dataframe["bb_lowerband2"]) / dataframe["close"]

        vwap_low, vwap_mid, vwap_high = vwap_bands(dataframe, 20, 1)
        dataframe["vwap_low"] = vwap_low
        dataframe["vwap_high"] = vwap_high
        dataframe["vwap_upperband"] = vwap_high
        dataframe["vwap_middleband"] = vwap_mid

        dc_length = self.BUY_DEFAULTS["dc_length"]
        dataframe["dc_upper"] = dataframe["high"].shift(1).rolling(dc_length).max()
        dataframe["dc_lower"] = dataframe["low"].shift(1).rolling(dc_length).min()
        dataframe["dc_middle"] = (dataframe["dc_upper"] + dataframe["dc_lower"]) / 2.0
        dc_range = (dataframe["dc_upper"] - dataframe["dc_lower"]).replace(0, np.nan)
        dataframe["dc_percent"] = (dataframe["close"] - dataframe["dc_lower"]) / dc_range
        dataframe["dc_chf"] = (dataframe["close"] - dataframe["dc_middle"]) / dataframe["dc_middle"].replace(0, np.nan)

        dataframe["rsi_14"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["rsi_slow"] = ta.RSI(dataframe, timeperiod=20)
        dataframe["rsi_84"] = ta.RSI(dataframe, timeperiod=84)
        dataframe["mfi"] = ta.MFI(dataframe, timeperiod=14)

        rsi_min = dataframe["rsi_14"].rolling(14).min()
        rsi_max = dataframe["rsi_14"].rolling(14).max()
        stoch_rsi = 100 * (dataframe["rsi_14"] - rsi_min) / (rsi_max - rsi_min).replace(0, np.nan)
        dataframe["fastk_rsi"] = stoch_rsi.rolling(3).mean()
        dataframe["fastd_rsi"] = dataframe["fastk_rsi"].rolling(3).mean()

        rmi_source = dataframe["close"] - dataframe["close"].shift(4)
        dataframe["rmi"] = ta.RSI(rmi_source.fillna(0), timeperiod=14)
        dataframe["rsi_mfi_rmi_length"] = (dataframe["rsi_14"] + dataframe["mfi"] + dataframe["rmi"]) / 3.0

        momentum = dataframe["close"] - dataframe["close"].shift(5)
        positive_mom = momentum.clip(lower=0)
        negative_mom = (-momentum).clip(lower=0)
        pmom = positive_mom.rolling(3).mean()
        nmom = negative_mom.rolling(3).mean()
        dataframe["positive_mom_pmom_nmom"] = ((positive_mom > negative_mom) & (pmom > nmom)).astype(int)
        dataframe["positive_pmom_nmom_prev"] = (
            (pmom.shift(1) > nmom.shift(1)) & (pmom.shift(2) > nmom.shift(2))
        ).astype(int)
        dataframe["negative_mom_pmom_nmom"] = ((negative_mom > positive_mom) & (nmom > pmom)).astype(int)
        dataframe["negative_pmom_nmom_prev"] = (
            (nmom.shift(1) > pmom.shift(1)) & (nmom.shift(2) > pmom.shift(2))
        ).astype(int)

        ichimoku = ftt.ichimoku(
            dataframe,
            conversion_line_period=20,
            base_line_periods=60,
            laggin_span=120,
            displacement=30,
        )
        dataframe["tenkan_sen"] = ichimoku["tenkan_sen"]
        dataframe["cloud_red"] = ichimoku["cloud_red"].astype(int)

        dataframe["dsl_lvl"] = dsl_rsi_level(dataframe["rsi_14"], 7)
        dataframe["strike_strike"] = np.sign(dataframe["close"].diff()).rolling(3).sum()
        dataframe["averimpet_mma_length_value"] = (
            (dataframe["close"] - dataframe["smma_smma_length_value"]).abs()
            / dataframe["smma_smma_length_value"].replace(0, np.nan)
        )

        rsi_mean = dataframe["rsi_14"].rolling(20).mean()
        rsi_std = dataframe["rsi_14"].rolling(20).std()
        dataframe["dev_ma_period_rsi_period_stdev_multiplier"] = rsi_mean + (rsi_std * 1.6)
        dataframe["disp_up_ma_period_rsi_period_stdev_multiplier_dispersion"] = (
            rsi_std / rsi_mean.abs().replace(0, np.nan)
        )

        dataframe["goodloss"] = ((dataframe["trend_open_6h"] - dataframe["close"]) / dataframe["close"]).clip(lower=0)
        dataframe["goodrise"] = ((dataframe["close"] - dataframe["trend_open_6h"]) / dataframe["close"]).clip(lower=0)
        dataframe["dsl_delta"] = dataframe["dsl_lvl"] - dataframe["dsl_lvl"].shift(1)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["enter_long"] = 0
        dataframe["enter_short"] = 0
        dataframe["enter_tag"] = ""

        general_guard = (
            (dataframe["volume"] > 0)
            & (dataframe["volume"] > dataframe["volume_mean_4"] * self.BUY_DEFAULTS["volume_factor"])
            & (dataframe["goodloss"] < self.buy_goodloss_max.value)
            & (dataframe["upper_near"] > self.buy_donchian_room_min.value)
        )

        trend_guard = (
            (dataframe["trend_close_6h"] > dataframe["trend_open_6h"] * self.BUY_DEFAULTS["trend_relax_factor"])
            | ((dataframe["close"] > dataframe["tenkan_sen"]) & (dataframe["cloud_red"] == 0))
        )
        bear_guard = (
            (
                (dataframe["trend_close_6h"] < dataframe["trend_open_6h"] * self.SHORT_DEFAULTS["trend_relax_factor"])
                & (dataframe["close"] < dataframe["trend_open_6h"])
            )
            | ((dataframe["close"] < dataframe["tenkan_sen"]) & (dataframe["cloud_red"] == 1))
        )

        deep_reversion = (
            (dataframe["close"] < dataframe["vwap_low"] * self.buy_vwap_factor.value)
            & (dataframe["close"] < dataframe["bb_lowerband2"] * self.buy_bb_factor.value)
            & ((dataframe["bbdelta"] / dataframe["close"]) > self.buy_bbdelta_close_min.value)
            & ((dataframe["close_delta2"] / dataframe["close"]) > self.buy_close_delta_min.value)
            & (dataframe["rsi_14"] < self.buy_rsi_14_max.value)
            & (dataframe["rsi_slow"] < self.buy_rsi_slow_max.value)
            & (dataframe["rsi_84"] < self.buy_rsi_84_max.value)
            & (dataframe["dc_percent"] < self.buy_dc_percent_max.value)
            & (dataframe["fastk_rsi"] > dataframe["fastd_rsi"])
            & (dataframe["dsl_delta"] > self.buy_dsl_delta_min.value)
            & (dataframe["positive_mom_pmom_nmom"] == 1)
        )

        donchian_reclaim = (
            (dataframe["close"] <= dataframe["dc_lower"] * 1.01)
            & (dataframe["dc_chf"] < -0.01)
            & (dataframe["lower_far"] < -0.005)
            & (dataframe["fastk_rsi"] > dataframe["fastd_rsi"])
            & (dataframe["positive_pmom_nmom_prev"] == 1)
            & (dataframe["strike_strike"] <= 0)
            & (dataframe["dsl_delta"] > 0)
            & (
                dataframe["close"]
                > dataframe["smma_smma_length_value"] * self.BUY_DEFAULTS["donchian_smma_factor"]
            )
        )
        short_general_guard = (
            (dataframe["volume"] > 0)
            & (dataframe["volume"] > dataframe["volume_mean_4"] * self.SHORT_DEFAULTS["volume_factor"])
            & (dataframe["goodrise"] < self.short_goodrise_max.value)
        )
        short_reversion = (
            (dataframe["close"] > dataframe["vwap_high"] * self.short_vwap_factor.value)
            & (dataframe["close"] > dataframe["bb_upperband2"] * self.short_bb_factor.value)
            & (dataframe["upper_far"] > 0)
            & (dataframe["rsi_14"] > self.short_rsi_14_min.value)
            & (dataframe["rsi_slow"] > self.short_rsi_slow_min.value)
            & (dataframe["rsi_84"] > self.short_rsi_84_min.value)
            & (dataframe["dc_percent"] > self.short_dc_percent_min.value)
            & (dataframe["fastk_rsi"] < dataframe["fastd_rsi"])
            & (dataframe["dsl_delta"] < self.short_dsl_delta_max.value)
            & (dataframe["close"] > dataframe["trend_close_30m"])
        )
        donchian_short = (
            (dataframe["close"] >= dataframe["dc_upper"] * 0.99)
            & (dataframe["dc_chf"] > 0.01)
            & (dataframe["upper_far"] > 0.005)
            & (dataframe["fastk_rsi"] < dataframe["fastd_rsi"])
            & (dataframe["strike_strike"] >= 0)
            & (dataframe["dsl_delta"] < 0)
            & (
                dataframe["close"]
                > dataframe["smma_smma_length_value"] * self.SHORT_DEFAULTS["donchian_smma_factor"]
            )
        )
        entry_dsl_vwap = general_guard & trend_guard & deep_reversion
        entry_donchian = general_guard & donchian_reclaim
        entry_short_reversion = short_general_guard & bear_guard & short_reversion
        entry_short_donchian = short_general_guard & bear_guard & donchian_short

        dataframe.loc[entry_dsl_vwap, ["enter_long", "enter_tag"]] = (1, "dsl_vwap_reclaim")
        dataframe.loc[entry_donchian, ["enter_long", "enter_tag"]] = (1, "donchian_dsl_reclaim")
        dataframe.loc[entry_short_reversion, ["enter_short", "enter_tag"]] = (1, "dsl_vwap_short")
        dataframe.loc[entry_short_donchian, ["enter_short", "enter_tag"]] = (1, "donchian_dsl_short")

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["exit_long"] = 0
        dataframe["exit_short"] = 0
        dataframe["exit_tag"] = ""

        vwap_extension = (
            (dataframe["close"] > dataframe["vwap_upperband"] * self.sell_vwap_factor.value)
            & (dataframe["rsi_14"] > self.sell_rsi_exit_min.value)
            & (dataframe["fastk_rsi"] > self.sell_fastk_exit_min.value)
        )

        band_exhaustion = (
            (dataframe["close"] > dataframe["high_offset_sma"])
            & (dataframe["close"] > dataframe["bb_upperband2"])
            & (dataframe["fastk_rsi"] < dataframe["fastd_rsi"])
            & (dataframe["rsi_mfi_rmi_length"] > self.SELL_DEFAULTS["band_exhaustion_rsi_mfi_rmi_min"])
        )

        donchian_take = (
            (dataframe["close"] > dataframe["dc_upper"])
            & (dataframe["rsi_14"] > self.sell_rsi_exit_min.value)
            & (dataframe["dsl_lvl"] < dataframe["dsl_lvl"].shift(1))
        )
        vwap_cover = (
            (dataframe["close"] < dataframe["vwap_low"] * self.SHORT_DEFAULTS["cover_vwap_factor"])
            & (dataframe["rsi_14"] < self.SHORT_DEFAULTS["cover_rsi_max"])
            & (dataframe["fastk_rsi"] < self.SHORT_DEFAULTS["cover_fastk_max"])
            & (dataframe["dsl_lvl"] > dataframe["dsl_lvl"].shift(1))
        )
        band_compression = (
            (dataframe["close"] < dataframe["low_offset_sma"])
            & (dataframe["close"] < dataframe["bb_lowerband2"])
            & (dataframe["fastk_rsi"] > dataframe["fastd_rsi"])
            & (dataframe["dc_percent"] < 0.2)
            & (dataframe["rsi_mfi_rmi_length"] < self.SHORT_DEFAULTS["band_compression_rsi_mfi_rmi_max"])
        )
        donchian_cover = (
            (dataframe["close"] < dataframe["dc_lower"])
            & (dataframe["rsi_14"] < self.SHORT_DEFAULTS["cover_rsi_max"])
            & (dataframe["dsl_lvl"] > dataframe["dsl_lvl"].shift(1))
        )

        dataframe.loc[vwap_extension, ["exit_long", "exit_tag"]] = (1, "vwap_extension")
        dataframe.loc[band_exhaustion, ["exit_long", "exit_tag"]] = (1, "band_exhaustion")
        dataframe.loc[donchian_take, ["exit_long", "exit_tag"]] = (1, "donchian_take")
        dataframe.loc[vwap_cover, ["exit_short", "exit_tag"]] = (1, "vwap_cover")
        dataframe.loc[band_compression, ["exit_short", "exit_tag"]] = (1, "band_compression")
        dataframe.loc[donchian_cover, ["exit_short", "exit_tag"]] = (1, "donchian_cover")

        return dataframe

    def custom_stoploss(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> float:
        hard_stoploss = self.RISK_DEFAULTS["hard_stoploss"]
        profit_floor_1 = self.RISK_DEFAULTS["profit_floor_1"]
        profit_floor_2 = self.RISK_DEFAULTS["profit_floor_2"]
        stop_1 = self.RISK_DEFAULTS["stop_1"]
        stop_2 = self.RISK_DEFAULTS["stop_2"]

        if current_profit > profit_floor_2:
            stop_profit = stop_2 + (current_profit - profit_floor_2)
        elif current_profit > profit_floor_1:
            stop_profit = stop_1 + (
                (current_profit - profit_floor_1)
                * (stop_2 - stop_1)
                / (profit_floor_2 - profit_floor_1)
            )
        else:
            stop_profit = hard_stoploss

        if stop_profit >= current_profit:
            return -0.99

        return stoploss_from_open(
            stop_profit,
            current_profit,
            is_short=trade.is_short,
            leverage=trade.leverage,
        )

    def custom_exit(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ):
        if self.dp is None:
            return None

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe.empty:
            return None

        last_candle = dataframe.iloc[-1]
        previous_candle = dataframe.iloc[-2] if len(dataframe) > 1 else last_candle
        held_minutes = (current_time - trade.open_date_utc).total_seconds() / 60.0

        if trade.is_short:
            if (
                current_profit > self.SELL_DEFAULTS["trend_fail_min"]
                and last_candle["cloud_red"] == 0
                and last_candle["close"] > last_candle["trend_open_6h"]
            ):
                return "trend_fail_short"

            if (
                current_profit > self.SELL_DEFAULTS["profit_fade_min"]
                and last_candle["dsl_lvl"] > previous_candle["dsl_lvl"]
            ):
                return "profit_fade_short"

            if (
                held_minutes > self.RISK_DEFAULTS["time_stop_minutes"]
                and current_profit > self.RISK_DEFAULTS["time_stop_profit"]
                and last_candle["close"] > last_candle["trend_close_6h"]
            ):
                return "time_stop_short"
        else:
            if (
                current_profit > self.SELL_DEFAULTS["trend_fail_min"]
                and last_candle["cloud_red"] == 1
                and last_candle["close"] < last_candle["trend_open_6h"]
            ):
                return "trend_fail"

            if (
                current_profit > self.SELL_DEFAULTS["profit_fade_min"]
                and last_candle["dsl_lvl"] < previous_candle["dsl_lvl"]
            ):
                return "profit_fade"

            if (
                held_minutes > self.RISK_DEFAULTS["time_stop_minutes"]
                and current_profit > self.RISK_DEFAULTS["time_stop_profit"]
                and last_candle["close"] < last_candle["trend_close_6h"]
            ):
                return "time_stop"

        return None
