# source: https://raw.githubusercontent.com/zrowan1/CryptoBuddy/ccb85aad10abf89c941390edc1a137feb7c55b0b/user_data/strategies/AdaptiveMomentumV9.py
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import numpy as np


class Github_zrowan1_CryptoBuddy__AdaptiveMomentumV9__20260404_205200(IStrategy):
    """
    Github_zrowan1_CryptoBuddy__AdaptiveMomentumV9__20260404_205200 — Regime-adaptieve strategie op basis van MomentumRSI V9

    Vier marktregimes:
      - strong_up:  ADX > 28, prijs > EMA200, BTC bullish  → volledige V9 momentum-logica
      - weak_up:    ADX 22-28, prijs > EMA200              → V9 met 75% positiegrootte
      - ranging:    ADX < 22, BB squeeze                   → geen entries (mean-reversion uitgeschakeld)
      - downtrend:  prijs < EMA200, BTC bearish            → geen entries

    Entry-tags:
      - trend_pullback: strong_up regime (100% positie)
      - weak_trend_pullback: weak_up regime (75% positie)

    Exit-logica:
      - Alle trades: StochRSI overbought + ROI + Chandelier Exit (3× ATR)
    """

    INTERFACE_VERSION = 3

    # ROI tabel
    minimal_roi = {
        "0": 0.15,
        "120": 0.08,
        "240": 0.04,
        "480": 0.02,
    }

    # Vangnet stoploss — custom_stoploss overschrijft dynamisch
    stoploss = -0.10

    trailing_stop = False
    use_custom_stoploss = True

    timeframe = "1h"
    startup_candle_count = 350  # 200 EMA warmup + 100 BB-squeeze venster + 50 buffer

    # ATR multiplier voor Chandelier Exit (trend trades)
    atr_multiplier = 3.0

    # Strakke stop voor mean-reversion trades
    MR_STOPLOSS = -0.02

    # Regime hysteresis drempels
    ADX_TRENDING_ENTER = 28
    ADX_TRENDING_EXIT = 22
    REGIME_SMOOTHING_CANDLES = 4
    BB_SQUEEZE_LOOKBACK = 100

    # --- Informative Pairs: BTC macro-filter ("Remora") ---

    def informative_pairs(self):
        return [("BTC/USDT", self.timeframe)]

    # --- Protections ---

    @property
    def protections(self):
        return [
            {
                "method": "MaxDrawdown",
                "lookback_period_candles": 48,
                "trade_back_period_candles": 48,
                "max_allowed_drawdown": 0.15,
                "stop_duration_candles": 24,
            },
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 24,
                "trade_limit": 3,
                "stop_duration_candles": 12,
                "only_per_pair": False,
            },
        ]

    # --- Indicators ---

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # StochRSI (momentum timing)
        rsi = ta.RSI(dataframe, timeperiod=14)
        stochrsi_k, stochrsi_d = ta.STOCH(
            {"high": rsi, "low": rsi, "close": rsi, "open": rsi},
            fastk_period=14,
            slowk_period=3,
            slowk_matype=0,
            slowd_period=3,
            slowd_matype=0,
        )
        dataframe["stochrsi_k"] = stochrsi_k / 100.0
        dataframe["stochrsi_d"] = stochrsi_d / 100.0

        # RSI(14) voor mean-reversion entry
        dataframe["rsi"] = rsi

        # Bollinger Bands
        bb_result = ta.BBANDS(
            dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0
        )
        dataframe["bb_upper"] = bb_result["upperband"]
        dataframe["bb_middle"] = bb_result["middleband"]
        dataframe["bb_lower"] = bb_result["lowerband"]

        # ADX
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)

        # EMA 200
        dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)

        # ATR 14 (voor Chandelier Exit)
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)

        # BTC Remora filter
        if "BTC/USDT_close" not in dataframe.columns:
            if metadata.get("pair") == "BTC/USDT":
                dataframe["btc_close"] = dataframe["close"]
                dataframe["btc_ema200"] = dataframe["ema200"]
            else:
                btc_dataframe = self.dp.get_pair_dataframe(
                    pair="BTC/USDT", timeframe=self.timeframe
                )
                if len(btc_dataframe) > 0:
                    btc_dataframe["btc_ema200"] = ta.EMA(btc_dataframe, timeperiod=200)
                    btc_dataframe = btc_dataframe[["date", "close", "btc_ema200"]].rename(
                        columns={"close": "btc_close"}
                    )
                    dataframe = dataframe.merge(btc_dataframe, on="date", how="left")
                else:
                    dataframe["btc_close"] = np.nan
                    dataframe["btc_ema200"] = np.nan
        else:
            dataframe["btc_close"] = dataframe["BTC/USDT_close"]
            dataframe["btc_ema200"] = ta.EMA(
                dataframe["BTC/USDT_close"], timeperiod=200
            )

        # --- Regime-detectie ---

        # BB-breedte normaliseren (maatstaf voor squeeze)
        dataframe["bb_width"] = (
            (dataframe["bb_upper"] - dataframe["bb_lower"]) / dataframe["bb_middle"]
        )

        # Squeeze-drempel: 25e percentiel van de afgelopen 100 candles
        dataframe["bb_width_pct25"] = (
            dataframe["bb_width"]
            .rolling(self.BB_SQUEEZE_LOOKBACK)
            .quantile(0.25)
        ).bfill()

        # Ruwe regime-classificatie (vectorized, geen state)
        conditions = [
            # Downtrend: prijs én BTC onder EMA200
            (dataframe["close"] < dataframe["ema200"])
            & (dataframe["btc_close"] < dataframe["btc_ema200"]),
            # Sterke uptrend: ADX hoog, prijs én BTC boven EMA200
            (dataframe["adx"] > self.ADX_TRENDING_ENTER)
            & (dataframe["close"] > dataframe["ema200"])
            & (dataframe["btc_close"] > dataframe["btc_ema200"]),
            # Zwakke uptrend: ADX in hysteresis-band, prijs boven EMA200
            (dataframe["adx"] >= self.ADX_TRENDING_EXIT)
            & (dataframe["adx"] <= self.ADX_TRENDING_ENTER)
            & (dataframe["close"] > dataframe["ema200"]),
            # Ranging: ADX laag + BB squeeze
            (dataframe["adx"] < self.ADX_TRENDING_EXIT)
            & (dataframe["bb_width"] < dataframe["bb_width_pct25"]),
        ]
        choices = ["downtrend", "strong_up", "weak_up", "ranging"]
        dataframe["regime_raw"] = np.select(conditions, choices, default="ranging")

        # Hysterese-smoothing: regime moet 4 candles aanhouden (whipsaw-preventie)
        # Rolling mode werkt alleen op numerieke kolommen — map strings naar ints en terug
        regime_to_int = {"downtrend": 0, "strong_up": 1, "weak_up": 2, "ranging": 3}
        int_to_regime = {v: k for k, v in regime_to_int.items()}

        regime_num = dataframe["regime_raw"].map(regime_to_int)
        regime_smooth = (
            regime_num
            .rolling(self.REGIME_SMOOTHING_CANDLES, min_periods=1)
            .apply(lambda x: pd.Series(x).mode()[0], raw=True)
            .fillna(3)
            .astype(int)
        )
        dataframe["regime"] = regime_smooth.map(int_to_regime)

        return dataframe

    # --- Entry ---

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Block A — trend_pullback (strong_up regime)
        dataframe.loc[
            (dataframe["regime"] == "strong_up")
            & (dataframe["close"].shift(1) < dataframe["bb_lower"].shift(1))
            & (dataframe["stochrsi_k"].shift(1) < 0.20)
            & (dataframe["stochrsi_k"] > dataframe["stochrsi_d"])
            & (dataframe["adx"].shift(1) > 25)
            & (dataframe["volume"] > 0),
            ["enter_long", "enter_tag"],
        ] = (1, "trend_pullback")

        # Block B — weak_trend_pullback (weak_up regime)
        # Gereduceerde positiegrootte voor zwakkere trends
        dataframe.loc[
            (dataframe["regime"] == "weak_up")
            & (dataframe["close"].shift(1) < dataframe["bb_lower"].shift(1))
            & (dataframe["stochrsi_k"].shift(1) < 0.20)
            & (dataframe["stochrsi_k"] > dataframe["stochrsi_d"])
            & (dataframe["adx"].shift(1) > 25)
            & (dataframe["volume"] > 0),
            ["enter_long", "enter_tag"],
        ] = (1, "weak_trend_pullback")

        # Block C — ranging/downtrend: geen entries

        return dataframe

    # --- Exit (StochRSI voor trend trades) ---

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe["stochrsi_k"] > 0.80)
            & (dataframe["stochrsi_k"] < dataframe["stochrsi_d"]),
            "exit_long",
        ] = 1
        return dataframe

    # --- Custom Exit: regime-specifieke exits ---

    def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs):
        # Alle exits gebruiken Chandelier Exit via custom_stoploss
        # of de standaard exit criteria uit populate_exit_trend
        return None

    # --- Custom Stake Amount: dynamisch risicopercentage ---

    def custom_stake_amount(self, pair, current_time, current_rate,
                            proposed_stake, min_stake, max_stake,
                            leverage, entry_tag, side, **kwargs):
        equity = self.wallets.get_total_stake_amount()

        if equity < 500:
            risk_pct = 0.22
        elif equity < 750:
            risk_pct = 0.18
        elif equity < 1000:
            risk_pct = 0.15
        elif equity < 1500:
            risk_pct = 0.12
        else:
            risk_pct = 0.10

        # weak_trend_pullback: 75% positie voor gereduceerd risico in zwakke trends
        position_multiplier = 0.75 if entry_tag == "weak_trend_pullback" else 1.0

        stake = equity * risk_pct * position_multiplier
        min_viable = max(min_stake or 1, 5.0)
        return max(min(stake, max_stake), min_viable)

    # --- Custom Stoploss: Chandelier Exit voor alle trend trades ---

    def custom_stoploss(self, pair, trade, current_time, current_rate, current_profit, **kwargs):
        # Alle trades gebruiken Chandelier Exit (3× ATR, trailing van hoogste close)
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if len(dataframe) < 1:
            return self.stoploss

        atr = dataframe["atr"].iat[-1]
        if atr == 0 or np.isnan(atr):
            return self.stoploss

        candles_since_entry = dataframe.loc[dataframe["date"] >= trade.open_date_utc]
        if len(candles_since_entry) > 0:
            highest_close = candles_since_entry["close"].max()
        else:
            highest_close = current_rate

        chandelier_stop = highest_close - (atr * self.atr_multiplier)
        stoploss = (chandelier_stop / current_rate) - 1.0
        return max(stoploss, self.stoploss)
