# source: https://raw.githubusercontent.com/LoLoSenPai/freqtrade-hl-bot/11a5d3238922a0376470e2096cb27b52c83df4e6/user_data/strategies/BtcBearRallyFadeV1.py
from datetime import datetime

from pandas import DataFrame
import talib.abstract as ta

from freqtrade.strategy import DecimalParameter, IStrategy, IntParameter, informative


class Github_LoLoSenPai_freqtrade_hl_bot__BtcBearRallyFadeV1__20260510_172513(IStrategy):
    """Research strategy: short BTC bear-market rallies into EMA resistance."""

    INTERFACE_VERSION = 3

    can_short = True
    timeframe = "15m"
    startup_candle_count = 420
    process_only_new_candles = True

    position_adjustment_enable = False
    max_entry_position_adjustment = 0

    minimal_roi = {
        "0": 0.045,
        "150": 0.024,
        "420": 0.0,
    }

    stoploss = -0.052
    trailing_stop = True
    trailing_stop_positive = 0.012
    trailing_stop_positive_offset = 0.030
    trailing_only_offset_is_reached = True

    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    rsi_min = IntParameter(34, 46, default=39, space="buy", optimize=False)
    rsi_max = IntParameter(50, 66, default=61, space="buy", optimize=False)
    daily_rsi_min = IntParameter(26, 40, default=31, space="buy", optimize=False)
    daily_rsi_max = IntParameter(44, 62, default=56, space="buy", optimize=False)
    adx_min = IntParameter(14, 30, default=18, space="buy", optimize=False)
    volume_factor = DecimalParameter(0.3, 1.5, default=0.55, decimals=2, space="buy", optimize=False)
    rally_buffer = DecimalParameter(0.000, 0.012, default=0.004, decimals=3, space="buy", optimize=False)
    max_extension_atr = DecimalParameter(0.8, 3.0, default=1.6, decimals=1, space="buy", optimize=False)
    ema_exit_buffer = DecimalParameter(0.000, 0.012, default=0.004, decimals=3, space="sell", optimize=False)
    profit_take_rsi = IntParameter(18, 34, default=27, space="sell", optimize=False)

    @property
    def protections(self) -> list[dict]:
        return [
            {"method": "CooldownPeriod", "stop_duration_candles": 3},
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 72,
                "trade_limit": 2,
                "stop_duration_candles": 16,
                "required_profit": 0.0,
                "only_per_pair": False,
                "only_per_side": True,
            },
            {
                "method": "MaxDrawdown",
                "calculation_mode": "equity",
                "lookback_period_candles": 144,
                "trade_limit": 8,
                "stop_duration_candles": 24,
                "max_allowed_drawdown": 0.08,
            },
        ]

    @staticmethod
    def _is_btc_pair(pair: str) -> bool:
        return pair.upper().startswith("BTC/")

    @informative("1h")
    def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ema_20"] = ta.EMA(dataframe, timeperiod=20)
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
        dataframe["plus_di"] = ta.PLUS_DI(dataframe, timeperiod=14)
        dataframe["minus_di"] = ta.MINUS_DI(dataframe, timeperiod=14)
        dataframe["ema_50_slope"] = dataframe["ema_50"] - dataframe["ema_50"].shift(6)
        return dataframe

    @informative("4h")
    def populate_indicators_4h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ema_20"] = ta.EMA(dataframe, timeperiod=20)
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
        dataframe["plus_di"] = ta.PLUS_DI(dataframe, timeperiod=14)
        dataframe["minus_di"] = ta.MINUS_DI(dataframe, timeperiod=14)
        dataframe["ema_50_slope"] = dataframe["ema_50"] - dataframe["ema_50"].shift(4)
        return dataframe

    @informative("1d")
    def populate_indicators_1d(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
        dataframe["plus_di"] = ta.PLUS_DI(dataframe, timeperiod=14)
        dataframe["minus_di"] = ta.MINUS_DI(dataframe, timeperiod=14)
        dataframe["ema_50_slope"] = dataframe["ema_50"] - dataframe["ema_50"].shift(5)
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ema_8"] = ta.EMA(dataframe, timeperiod=8)
        dataframe["ema_20"] = ta.EMA(dataframe, timeperiod=20)
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
        dataframe["plus_di"] = ta.PLUS_DI(dataframe, timeperiod=14)
        dataframe["minus_di"] = ta.MINUS_DI(dataframe, timeperiod=14)
        dataframe["volume_mean_20"] = dataframe["volume"].rolling(20, min_periods=20).mean()
        dataframe["range_atr"] = (dataframe["high"] - dataframe["low"]) / dataframe["atr"]
        dataframe["ema50_extension_atr"] = (dataframe["ema_50"] - dataframe["close"]) / dataframe["atr"]
        dataframe["ema_50_slope"] = dataframe["ema_50"] - dataframe["ema_50"].shift(6)
        return dataframe

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

        if not self._is_btc_pair(metadata["pair"]):
            return dataframe

        volume_ok = (
            (dataframe["volume"] > 0)
            & (dataframe["volume_mean_20"] > 0)
            & (dataframe["volume"] >= dataframe["volume_mean_20"] * self.volume_factor.value)
        )

        daily_risk_off = (
            (dataframe["close_1d"] < dataframe["ema_50_1d"])
            & (dataframe["ema_50_slope_1d"] < 0)
            & (dataframe["rsi_1d"] > self.daily_rsi_min.value)
            & (dataframe["rsi_1d"] < self.daily_rsi_max.value)
        )

        higher_tf_bear = (
            daily_risk_off
            & (dataframe["close_4h"] < dataframe["ema_200_4h"])
            & (dataframe["close_4h"] < dataframe["ema_50_4h"])
            & (dataframe["ema_50_slope_4h"] < 0)
            & (dataframe["minus_di_4h"] > dataframe["plus_di_4h"])
            & (dataframe["close_1h"] < dataframe["ema_200_1h"])
        )

        rally_into_resistance = (
            (dataframe["high"] >= dataframe["ema_20_1h"] * (1 - self.rally_buffer.value))
            | (dataframe["high"] >= dataframe["ema_50_1h"] * (1 - self.rally_buffer.value))
        )

        rejection = (
            (dataframe["close"] < dataframe["open"])
            & (dataframe["close"] < dataframe["ema_8"])
            & (dataframe["close"] < dataframe["ema_20"])
            & (dataframe["close"] < dataframe["close"].shift(1))
        )

        not_late = (
            (dataframe["ema50_extension_atr"] < self.max_extension_atr.value)
            & (dataframe["range_atr"] < 2.8)
            & (dataframe["rsi"] > self.rsi_min.value)
            & (dataframe["rsi"] < self.rsi_max.value)
        )

        short_conditions = (
            volume_ok
            & higher_tf_bear
            & rally_into_resistance
            & rejection
            & not_late
            & (dataframe["close"] < dataframe["ema_200"])
            & (dataframe["ema_50"] < dataframe["ema_200"])
            & (dataframe["adx"] > self.adx_min.value)
            & (dataframe["minus_di"] > dataframe["plus_di"])
        )

        dataframe.loc[short_conditions, ["enter_short", "enter_tag"]] = (1, "btc_bear_rally_fade")
        return dataframe

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

    def custom_exit(
        self,
        pair: str,
        trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> str | bool | None:
        if not self.dp:
            return None
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe.empty or not trade.is_short:
            return None

        candle = dataframe.iloc[-1]
        if current_profit > 0.02 and candle["rsi"] < self.profit_take_rsi.value:
            return "fade_rsi_profit_take"
        if current_profit > 0.012 and current_rate > candle["ema_20"]:
            return "fade_profit_ema20_reclaim"
        if current_rate > candle["ema_50"] * (1 + self.ema_exit_buffer.value):
            return "fade_ema50_reclaim"
        if candle["close_1h"] > candle["ema_50_1h"] and current_profit < 0.01:
            return "fade_1h_reclaim"
        return None

    def leverage(
        self,
        pair: str,
        current_time: datetime,
        current_rate: float,
        proposed_leverage: float,
        max_leverage: float,
        entry_tag: str | None,
        side: str,
        **kwargs,
    ) -> float:
        return 1.0
