# source: https://raw.githubusercontent.com/assinscreedFC/trading_strategie/e0f7e7799c2db0bef63083f4ac226cb31f5e8aa5/freqtrade/strategies/RegimeSwitcherLite.py
# ══════════════════════════════════════════════════════════════
# anis solidscale - Elite Spot Trading Suite
# STRATEGIE : Github_assinscreedFC_trading_strategie__RegimeSwitcherLite__20260328_231733
# CATEGORIE : Meta — Switching automatique par regime de marche
# ══════════════════════════════════════════════════════════════
#
# ARCHITECTURE :
# 1. Detection regime sur BTC/USDT 1d (ADX + EMA direction)
#    - Bull  : ADX > 25 + EMA montante  → ChoppinessBreakout logic
#    - Bear  : ADX > 25 + EMA descendante → SuperTrendADX logic
#    - Stable: ADX < 20                   → DCA logic
#    - Transition: 20 <= ADX <= 25        → NO TRADE
#
# 2. Sous-logiques inlinees (0 params hyperopt, anti-overfitting)
#    - Bull  : Choppiness < 38 + breakout 20 + EMA50 filter
#    - Bear  : SuperTrend(ATR11, mult3.0) + ADX14>25 + EMA200
#    - Stable: DCA interval=30 bougies (achat regulier)
#
# 3. Gestion du risque adaptative par regime
#    - Bull  : stake=20, stoploss=-8%
#    - Bear  : stake=10, stoploss=-4%
#    - Stable: stake=15, stoploss=-6%
#
# Tous les params sont FIXES — valides par tournament Phase 2.
# ══════════════════════════════════════════════════════════════

import sys
from pathlib import Path
from typing import List, Optional, Tuple

import numpy as np
from pandas import DataFrame

from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, merge_informative_pair

sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
from utils.indicators import CommonIndicators
from utils.logging_utils import TradeLogger
from utils.telegram_notifier import TelegramNotifier


def _calc_supertrend(close, high, low, atr, mult):
    """SuperTrend vectorise."""
    n = len(close)
    hl2 = (high + low) / 2
    ub = hl2 + mult * atr
    lb = hl2 - mult * atr

    st_upper = np.full(n, np.nan)
    st_lower = np.full(n, np.nan)
    direction = np.ones(n, dtype=int)

    st_lower[0] = lb[0]
    st_upper[0] = ub[0]

    for i in range(1, n):
        st_lower[i] = lb[i] if (lb[i] > st_lower[i - 1] or close[i - 1] < st_lower[i - 1]) else st_lower[i - 1]
        st_upper[i] = ub[i] if (ub[i] < st_upper[i - 1] or close[i - 1] > st_upper[i - 1]) else st_upper[i - 1]

        if direction[i - 1] == 1:
            direction[i] = -1 if close[i] < st_lower[i] else 1
        else:
            direction[i] = 1 if close[i] > st_upper[i] else -1

    return direction


class Github_assinscreedFC_trading_strategie__RegimeSwitcherLite__20260328_231733(IStrategy):
    INTERFACE_VERSION = 3
    can_short = False
    timeframe = "4h"
    startup_candle_count = 250  # EMA200 + marge

    # ROI conservateur (compromis entre les 3 regimes)
    minimal_roi = {"0": 0.10, "240": 0.05, "720": 0.03, "1440": 0.01}
    stoploss = -0.06  # Default, override par custom_stoploss
    trailing_stop = True
    trailing_stop_positive = 0.02
    trailing_stop_positive_offset = 0.04
    trailing_only_offset_is_reached = True

    # ── 0 params hyperopt — tout fixe ──

    # Regime detection (BTC 1d)
    REGIME_ADX_PERIOD = 14
    REGIME_EMA_PERIOD = 50
    REGIME_ADX_TREND = 25
    REGIME_ADX_RANGE = 20
    REGIME_EMA_LOOKBACK = 5

    # Bull logic (ChoppinessBreakout)
    BULL_CHOP_PERIOD = 14
    BULL_CHOP_THRESHOLD = 38
    BULL_BREAKOUT_PERIOD = 20
    BULL_EMA_FILTER = 50
    BULL_CHOP_EXIT = 70

    # Bear logic (SuperTrendADX)
    BEAR_ATR_PERIOD = 11
    BEAR_ATR_MULT = 3.0
    BEAR_ADX_PERIOD = 14
    BEAR_ADX_THRESHOLD = 25
    BEAR_EMA_PERIOD = 200
    BEAR_ADX_EXIT = 24

    # Stable logic (DCA)
    STABLE_DCA_INTERVAL = 30

    _logger = None
    _notifier = None

    def __getstate__(self):
        state = self.__dict__.copy()
        state["_logger"] = None
        state["_notifier"] = None
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)

    def _init_utils(self) -> None:
        if self._logger is None:
            self._logger = TradeLogger(strategy_name="Github_assinscreedFC_trading_strategie__RegimeSwitcherLite__20260328_231733")
            self._notifier = TelegramNotifier()

    def informative_pairs(self) -> List[Tuple[str, str]]:
        return [("BTC/USDT", "1d")]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        self._init_utils()

        # ── 1. Regime detection via BTC/USDT 1d ──
        btc_1d = self.dp.get_pair_dataframe("BTC/USDT", "1d")
        btc_1d = CommonIndicators.add_adx(btc_1d, period=self.REGIME_ADX_PERIOD)
        btc_1d = CommonIndicators.add_ema(btc_1d, period=self.REGIME_EMA_PERIOD)

        adx_col = f"adx_{self.REGIME_ADX_PERIOD}"
        ema_col = f"ema_{self.REGIME_EMA_PERIOD}"
        lb = self.REGIME_EMA_LOOKBACK

        ema_rising = btc_1d[ema_col] > btc_1d[ema_col].shift(lb)
        ema_falling = btc_1d[ema_col] < btc_1d[ema_col].shift(lb)

        btc_1d["regime"] = "transition"
        btc_1d.loc[(btc_1d[adx_col] > self.REGIME_ADX_TREND) & ema_rising, "regime"] = "bull"
        btc_1d.loc[(btc_1d[adx_col] > self.REGIME_ADX_TREND) & ema_falling, "regime"] = "bear"
        btc_1d.loc[btc_1d[adx_col] < self.REGIME_ADX_RANGE, "regime"] = "stable"

        dataframe = merge_informative_pair(dataframe, btc_1d, "4h", "1d", ffill=True)

        # ── 2. Bull indicators (Choppiness Breakout) ──
        dataframe = CommonIndicators.add_choppiness(dataframe, period=self.BULL_CHOP_PERIOD)
        dataframe = CommonIndicators.add_breakout_levels(dataframe, period=self.BULL_BREAKOUT_PERIOD)
        dataframe = CommonIndicators.add_ema(dataframe, period=self.BULL_EMA_FILTER)
        dataframe = CommonIndicators.add_volume_sma(dataframe, period=20)

        # ── 3. Bear indicators (SuperTrend + ADX) ──
        dataframe = CommonIndicators.add_atr(dataframe, period=self.BEAR_ATR_PERIOD)
        dataframe = CommonIndicators.add_adx(dataframe, period=self.BEAR_ADX_PERIOD)
        dataframe = CommonIndicators.add_ema(dataframe, period=self.BEAR_EMA_PERIOD)

        atr_vals = dataframe[f"atr_{self.BEAR_ATR_PERIOD}"].values
        dataframe["st_direction"] = _calc_supertrend(
            dataframe["close"].values, dataframe["high"].values,
            dataframe["low"].values, atr_vals, self.BEAR_ATR_MULT
        )

        # ── 4. Stable indicators (DCA) ──
        dataframe["candle_index"] = np.arange(len(dataframe))

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        regime = dataframe["regime_1d"]

        # Bull: Choppiness Breakout
        bull_entry = (
            (regime == "bull")
            & (dataframe[f"choppiness_{self.BULL_CHOP_PERIOD}"] < self.BULL_CHOP_THRESHOLD)
            & (dataframe["close"] > dataframe[f"breakout_high_{self.BULL_BREAKOUT_PERIOD}"].shift(1))
            & (dataframe["volume"] > dataframe["volume_sma_20"])
            & (dataframe["close"] > dataframe[f"ema_{self.BULL_EMA_FILTER}"])
        )

        # Bear: SuperTrend + ADX
        bear_entry = (
            (regime == "bear")
            & (dataframe["st_direction"] == 1)
            & (dataframe[f"adx_{self.BEAR_ADX_PERIOD}"] > self.BEAR_ADX_THRESHOLD)
            & (dataframe["close"] > dataframe[f"ema_{self.BEAR_EMA_PERIOD}"])
        )

        # Stable: DCA (buy every N candles)
        stable_entry = (
            (regime == "stable")
            & (dataframe["candle_index"] % self.STABLE_DCA_INTERVAL == 0)
        )

        dataframe.loc[
            (bull_entry | bear_entry | stable_entry) & (dataframe["volume"] > 0),
            "enter_long"
        ] = 1

        # Tag pour tracking
        dataframe.loc[bull_entry, "enter_tag"] = "bull_chop_breakout"
        dataframe.loc[bear_entry, "enter_tag"] = "bear_supertrend"
        dataframe.loc[stable_entry, "enter_tag"] = "stable_dca"

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        regime = dataframe["regime_1d"]

        # Bull exit: choppiness monte OU breakdown
        bull_exit = (
            (regime == "bull")
            & (
                (dataframe[f"choppiness_{self.BULL_CHOP_PERIOD}"] > self.BULL_CHOP_EXIT)
                | (dataframe["close"] < dataframe[f"breakout_low_{self.BULL_BREAKOUT_PERIOD}"].shift(1))
            )
        )

        # Bear exit: SuperTrend flip OU ADX faiblit
        bear_exit = (
            (regime == "bear")
            & (
                (dataframe["st_direction"] == -1)
                | (dataframe[f"adx_{self.BEAR_ADX_PERIOD}"] < self.BEAR_ADX_EXIT)
            )
        )

        # Stable exit: pas de signal actif, trailing stop gere
        # Mais on exit si le regime change vers bear
        regime_change_exit = (
            (regime == "bear")
            & (dataframe["regime_1d"].shift(1) != "bear")
        )

        dataframe.loc[bull_exit | bear_exit | regime_change_exit, "exit_long"] = 1
        return dataframe

    def confirm_trade_entry(
        self, pair: str, order_type: str, amount: float,
        rate: float, time_in_force: str, current_time, entry_tag: Optional[str],
        side: str, **kwargs
    ) -> bool:
        """Rejeter si regime == transition ou regime vient de changer."""
        df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if df.empty:
            return False

        last = df.iloc[-1]
        regime = last.get("regime_1d", "transition")

        # Pas de trade en transition
        if regime == "transition":
            return False

        # Pas de trade si regime a change dans les 2 dernieres bougies
        if len(df) >= 3:
            prev_regimes = df["regime_1d"].iloc[-3:]
            if prev_regimes.nunique() > 1:
                return False

        return True

    def custom_stake_amount(
        self, pair: str, current_time, current_rate: float,
        proposed_stake: float, min_stake: Optional[float],
        max_stake: float, leverage: float, entry_tag: Optional[str],
        side: str, **kwargs
    ) -> float:
        """Stake adapte par regime."""
        df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if df.empty:
            return proposed_stake

        regime = df.iloc[-1].get("regime_1d", "stable")

        if regime == "bull":
            return 20.0
        elif regime == "bear":
            return 10.0
        else:  # stable
            return 15.0

    def custom_stoploss(
        self, pair: str, trade: Trade, current_time,
        current_rate: float, current_profit: float, after_fill: bool,
        **kwargs
    ) -> float:
        """Stoploss adapte par regime."""
        df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if df.empty:
            return self.stoploss

        regime = df.iloc[-1].get("regime_1d", "stable")

        if regime == "bull":
            return -0.08
        elif regime == "bear":
            return -0.04
        else:  # stable
            return -0.06
