# source: https://raw.githubusercontent.com/IvPalmer/Master-Trader/f9137953b7c7a06fbcac0c616bf0698329aac0c2/ft_userdata/user_data/strategies/ShortKeltnerV2.py
# directory_url: https://github.com/IvPalmer/Master-Trader/blob/main/ft_userdata/user_data/strategies/
# User: IvPalmer
# Repository: Master-Trader
# --------------------"""
Github_IvPalmer_Master_Trader__ShortKeltnerV2__20261001_225854 — V1 + macro 200-day-MA bear guard (daily informative).

V1 (inverse-Keltner short) was regime-neutral but bled a 36% DD across 2024-2025
because its 1h SMA50/200 gate mis-fired on bull-market pullbacks. Diagnosis
(2026-05-28): BTC was below its 200-DAY MA only ~20-27% of 2024-2025 (bull) but
100% of 2026 (real bear). Gating on the canonical bull/bear line — BTC daily
close < 200d MA — should excise the bull mis-fires while leaving the 2026 bear
edge untouched.

Macro guard: BTC 1h close < daily SMA200 (200-day MA, via 1d informative).
Everything else identical to V1.

Generated 2026-05-28.
"""

import logging

import pandas as pd
import talib.abstract as ta
from freqtrade.strategy import IStrategy, informative
from pandas import DataFrame

logger = logging.getLogger(__name__)


class Github_IvPalmer_Master_Trader__ShortKeltnerV2__20261001_225854(IStrategy):
    INTERFACE_VERSION = 3

    can_short = True
    trading_mode = "futures"
    margin_mode = "isolated"
    timeframe = "1h"

    minimal_roi = {"0": 0.06, "360": 0.04, "720": 0.025, "1440": 0.015, "2160": 0.0}
    stoploss = -0.05
    trailing_stop = False

    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False
    startup_candle_count = 250

    kelt_period = 25
    kelt_atr_mult = 2.5
    vol_multiplier = 1.75
    vol_sma_period = 20
    rsi_overbought = 60
    btc_slope_lookback = 24

    @property
    def protections(self):
        return [
            {"method": "CooldownPeriod", "stop_duration_candles": 4},
            {"method": "StoplossGuard", "lookback_period_candles": 48,
             "trade_limit": 3, "stop_duration_candles": 24, "only_per_pair": False},
            {"method": "MaxDrawdown", "lookback_period_candles": 72,
             "max_allowed_drawdown": 0.12, "stop_duration_candles": 48, "trade_limit": 2},
        ]

    def leverage(self, pair, current_time, current_rate, proposed_leverage,
                 max_leverage, entry_tag, side, **kwargs) -> float:
        return min(2.0, max_leverage)

    @informative("1d", "BTC/USDT:USDT")
    def populate_indicators_btc_1d(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["sma200"] = ta.SMA(dataframe, timeperiod=200)
        return dataframe

    @informative("1h", "BTC/USDT:USDT")
    def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["sma50"] = ta.SMA(dataframe, timeperiod=50)
        dataframe["sma200"] = ta.SMA(dataframe, timeperiod=200)
        dataframe["sma50_slope"] = dataframe["sma50"] - dataframe["sma50"].shift(self.btc_slope_lookback)
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        sma = dataframe["close"].rolling(self.kelt_period).mean()
        atr = _atr(dataframe, self.kelt_period)
        dataframe["kelt_upper"] = sma + self.kelt_atr_mult * atr
        dataframe["vol_sma"] = dataframe["volume"].rolling(self.vol_sma_period).mean()
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)

        dataframe["btc_bear"] = (
            (dataframe["btc_usdt_close_1h"] < dataframe["btc_usdt_sma50_1h"])
            & (dataframe["btc_usdt_close_1h"] < dataframe["btc_usdt_sma200_1h"])
            & (dataframe["btc_usdt_sma50_slope_1h"] < 0)
            & (dataframe["btc_usdt_close_1h"] < dataframe["btc_usdt_sma200_1d"])
        ).astype(int)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        rsi_was_overbought = (
            (dataframe["rsi"].shift(1) > self.rsi_overbought)
            | (dataframe["rsi"].shift(2) > self.rsi_overbought)
        )
        dataframe.loc[
            (
                (dataframe["close"] < dataframe["kelt_upper"])
                & (dataframe["close"].shift(1) >= dataframe["kelt_upper"].shift(1))
                & (dataframe["volume"] > self.vol_multiplier * dataframe["vol_sma"])
                & rsi_was_overbought
                & (dataframe["btc_bear"] == 1)
                & (dataframe["volume"] > 0)
            ),
            ["enter_short", "enter_tag"],
        ] = (1, "kelt_upper_reject")
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (
                    (dataframe["btc_usdt_close_1h"] > dataframe["btc_usdt_sma50_1h"])
                    | (dataframe["rsi"] < 30)
                )
                & (dataframe["volume"] > 0)
            ),
            ["exit_short", "exit_tag"],
        ] = (1, "regime_flip_or_oversold")
        return dataframe

    def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs):
        if not trade.is_short:
            return None
        hours = (current_time - trade.open_date_utc).total_seconds() / 3600
        if hours >= 36:
            return "time_exit_36h"
        return None


def _atr(df: DataFrame, period: int) -> pd.Series:
    high = df["high"]; low = df["low"]; close = df["close"]
    tr = pd.concat([high - low, (high - close.shift(1)).abs(), (low - close.shift(1)).abs()], axis=1).max(axis=1)
    return tr.rolling(period).mean()
