# source: https://raw.githubusercontent.com/IvPalmer/Master-Trader/c7955a15d4bfe82e567bc2bd315a372000a442fd/ft_userdata/user_data/strategies/BearRegimeShortV1.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__BearRegimeShortV1__20260923_012354 — Regime-gated short, 6-bar confirmed bear + failed rally + volume flush.

Hypothesis (2026-04-20):
  Shorts fail in crypto because fixed-threshold signals fire during the 77% of time
  BTC is NOT in confirmed bear. Solution: dormant 77% of time, only fire on strict
  regime confirmation + failed bounce + capitulation volume.

Signal (futures SHORT):
  - REGIME GATE (BTC 1h): close < SMA50 AND close < SMA200 AND 20d-mom < 0,
    confirmed by 6 consecutive bars (smooth out flicker).
  - ANCHOR (pair 1h): RSI touched >=50 in prior 3 bars AND rolled over to <48
    now (failed rally rejection).
  - CONFIRM: volume > 1.5x 20-SMA (capitulation flush), close < SMA50 (pair),
    ADX > 20 (trend has teeth).
  - ANTI-SQUEEZE: F&G >= 15 (not in "max fear" capitulation zone where squeezes fire).

Exit:
  - EXIT SIGNAL: BTC 1h close > SMA50 (regime flip) OR pair RSI < 30 (oversold bounce risk).
  - ROI: aggressive — crypto crashes are fast, take profits.
  - SL: -5% hard, no trailing.
  - Hard time exit: 36h.

Sizing: $100 stake / $200 wallet, 2x leverage, max 2 concurrent.

Known enemies this design attacks:
  - FundingShortV1 (killed): fired on z-score alone, 52% DD from squeeze-fires.
  - BearCrashShortV1 (killed): bear regime too loose (RSI<45 alone), entered grinds.

Generated 2026-04-20 for short-side research.
"""

import logging
from datetime import datetime
from pathlib import Path

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

logger = logging.getLogger(__name__)


class Github_IvPalmer_Master_Trader__BearRegimeShortV1__20260923_012354(IStrategy):
    INTERFACE_VERSION = 3

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

    # Aggressive ROI — crashes are fast
    minimal_roi = {
        "0":    0.06,   # 6% immediate take
        "360":  0.04,   # 4% after 6h
        "720":  0.025,  # 2.5% after 12h
        "1440": 0.015,  # 1.5% after 24h
        "2160": 0.0,    # break-even at 36h
    }

    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  # 200 + buffer for 20d mom

    @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: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag: str | None,
                 side: str, **kwargs) -> float:
        return min(2.0, max_leverage)

    # -- BTC informative --

    @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["mom20d"] = dataframe["close"].pct_change(20 * 24)
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        # BTC making fresh 7d low = accelerating bear (filters sideways bear-flicker)
        dataframe["low_7d"] = dataframe["low"].rolling(168).min().shift(1)
        dataframe["btc_fresh_low"] = (dataframe["close"] <= dataframe["low_7d"] * 1.02).astype(int)
        return dataframe

    # -- Indicators --

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        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["sma50"] = ta.SMA(dataframe, timeperiod=50)
        dataframe["sma200"] = ta.SMA(dataframe, timeperiod=200)
        dataframe["vol_sma20"] = dataframe["volume"].rolling(20).mean()

        # Donchian low: new 5-day low breakout (momentum short)
        dataframe["donch_low_120"] = dataframe["low"].rolling(120).min().shift(1)

        # BTC confirmed bear regime (6-bar smoothing)
        btc_bear_raw = (
            (dataframe["btc_usdt_close_1h"] < dataframe["btc_usdt_sma50_1h"])
            & (dataframe["btc_usdt_close_1h"] < dataframe["btc_usdt_sma200_1h"])
            & (dataframe["btc_usdt_mom20d_1h"] < 0)
        ).astype(int)
        dataframe["btc_bear_confirmed"] = (
            btc_bear_raw.rolling(6).sum() >= 6
        ).astype(int)

        # Failed rally: RSI was >=50 in prior 3 bars but now <48
        rsi_prior_high = (
            (dataframe["rsi"].shift(1) >= 50)
            | (dataframe["rsi"].shift(2) >= 50)
            | (dataframe["rsi"].shift(3) >= 50)
        )
        dataframe["failed_rally"] = (
            rsi_prior_high & (dataframe["rsi"] < 48) & (dataframe["rsi"] >= 30)
        ).astype(int)

        return dataframe

    # -- Entry --

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # PRIMARY SIGNAL: Momentum-short in confirmed bear
        #   Require pair to break below 5-day low AND BTC confirmed bear
        #   (Turtle-style short breakout gated by regime)
        dataframe.loc[
            (
                # Regime: confirmed bear (6-bar BTC confirmation)
                (dataframe["btc_bear_confirmed"] == 1)
                # BTC also making fresh 7d low (accelerating, not flickering)
                & (dataframe["btc_usdt_btc_fresh_low_1h"] == 1)
                # Momentum: pair breaks 5-day low (true downtrend acceleration)
                & (dataframe["close"] < dataframe["donch_low_120"])
                # Pair below its SMA50 (trend alignment)
                & (dataframe["close"] < dataframe["sma50"])
                # Volume expansion (not dying breakout)
                & (dataframe["volume"] > 1.2 * dataframe["vol_sma20"])
                # Trend teeth
                & (dataframe["adx"] > 20)
                & (dataframe["minus_di"] > dataframe["plus_di"])
                # Anti-squeeze: BTC not at panic low (prevents capitulation-bounce fires)
                & (dataframe["btc_usdt_rsi_1h"] > 25)
                & (dataframe["volume"] > 0)
            ),
            ["enter_short", "enter_tag"],
        ] = (1, "bear_5d_low_break")

        return dataframe

    # -- Exit --

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                # Regime flip: BTC back above SMA50
                (dataframe["btc_usdt_close_1h"] > dataframe["btc_usdt_sma50_1h"])
                # OR pair oversold (bounce risk)
                | (dataframe["rsi"] < 28)
                # OR bulls retook DI
                | (
                    (dataframe["plus_di"] > dataframe["minus_di"])
                    & (dataframe["plus_di"].shift(1) > dataframe["minus_di"].shift(1))
                )
            )
            & (dataframe["volume"] > 0),
            ["exit_short", "exit_tag"],
        ] = (1, "regime_flip_or_oversold")

        return dataframe

    # -- Time exit --

    def custom_exit(self, pair: str, 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"
        if hours >= 24 and current_profit > 0.01:
            return "time_profit_24h"
        return None
