# source: https://raw.githubusercontent.com/camster91/crypto-bots/5fd45118d5be1eaadd338e71ca26072dd5437831/strategies/MarketRegimeStrategy.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
import numpy as np
import pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, CategoricalParameter
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib


class Github_camster91_crypto_bots__MarketRegimeStrategy__20260511_032334(IStrategy):
    """
    Market Regime Detection Strategy.

    Automatically detects whether the market is trending or sideways
    and applies the appropriate strategy logic.

    Regime Detection:
    - TRENDING: ADX > 25 AND EMA slope positive AND price above EMA-50
      -> Trend-following: enter on RSI pullback from oversold with MACD confirm
    - SIDEWAYS: ADX < 20 AND price within Bollinger Bands
      -> Mean reversion: enter on Z-score extreme + RSI oversold + volume spike
    - NEUTRAL: ADX 20-25 -> No entries (avoid whipsaws)

    This hybrid approach captures:
    - Bear market alpha via mean reversion (sideways)
    - Bull market profits via trend following
    - Protection in uncertain regimes

    Expected improvement: 2-3x alpha vs single-mode strategies
    """

    INTERFACE_VERSION = 3

    # Regime thresholds
    adx_trend_threshold = IntParameter(22, 35, default=25, space="buy")
    adx_sideways_threshold = IntParameter(12, 22, default=18, space="buy")

    # Trend-following params
    trend_rsi_buy = IntParameter(25, 45, default=35, space="buy")
    trend_rsi_sell = IntParameter(60, 80, default=70, space="sell")
    trend_ema_period = IntParameter(30, 100, default=50, space="buy")

    # Mean reversion params
    mr_zscore_buy = DecimalParameter(-3.0, -1.0, default=-1.8, space="buy", decimals=1)
    mr_zscore_sell = DecimalParameter(0.0, 2.0, default=0.5, space="sell", decimals=1)
    mr_rsi_buy = IntParameter(15, 35, default=25, space="buy")
    mr_rsi_sell = IntParameter(65, 85, default=75, space="sell")
    mr_ma_period = IntParameter(20, 120, default=80, space="buy")
    mr_volume_spike = DecimalParameter(1.5, 4.0, default=2.5, space="buy", decimals=1)

    minimal_roi = {
        "120": 0,
        "80": 0.01,
        "40": 0.025,
        "0": 0.08
    }

    stoploss = -0.10
    trailing_stop = True
    trailing_stop_positive = 0.03
    trailing_stop_positive_offset = 0.05
    trailing_only_offset_is_reached = True

    timeframe = "5m"
    process_only_new_candles = True
    startup_candle_count: int = 200

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # ADX for regime detection
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)

        # EMA slope (difference between current and 5-candle-ago EMA)
        dataframe["ema_trend"] = ta.EMA(dataframe, timeperiod=self.trend_ema_period.value)
        dataframe["ema_slope"] = dataframe["ema_trend"] - dataframe["ema_trend"].shift(5)

        # RSI
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["rsi_prev"] = dataframe["rsi"].shift(1)

        # MACD for trend confirmation
        macd = ta.MACD(dataframe)
        dataframe["macd"] = macd["macd"]
        dataframe["macdsignal"] = macd["macdsignal"]

        # Mean reversion indicators
        dataframe["mr_ma"] = ta.SMA(dataframe, timeperiod=self.mr_ma_period.value)
        dataframe["mr_std"] = dataframe["close"].rolling(window=self.mr_ma_period.value).std()
        dataframe["zscore"] = (dataframe["close"] - dataframe["mr_ma"]) / dataframe["mr_std"]

        # Volume
        dataframe["volume_mean"] = dataframe["volume"].rolling(window=20).mean()

        # Bollinger Bands (for sideways detection)
        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe["bb_lower"] = bollinger["lower"]
        dataframe["bb_upper"] = bollinger["upper"]
        dataframe["bb_mid"] = bollinger["mid"]
        dataframe["bb_width"] = (dataframe["bb_upper"] - dataframe["bb_lower"]) / dataframe["bb_mid"]

        # ATR for stop loss
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)

        # Regime classification
        dataframe["regime_trending"] = (
            (dataframe["adx"] > self.adx_trend_threshold.value) &
            (dataframe["ema_slope"] > 0) &
            (dataframe["close"] > dataframe["ema_trend"])
        ).astype(int)

        dataframe["regime_sideways"] = (
            (dataframe["adx"] < self.adx_sideways_threshold.value) &
            (dataframe["bb_width"] < dataframe["bb_width"].rolling(50).median())
        ).astype(int)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # TREND mode: RSI pullback + MACD bullish
        trend_entry = (
            (dataframe["regime_trending"] == 1) &
            (dataframe["rsi"] > self.trend_rsi_buy.value) &
            (dataframe["rsi_prev"] <= self.trend_rsi_buy.value) &
            (dataframe["macd"] > dataframe["macdsignal"]) &
            (dataframe["volume"] > 0)
        )

        # SIDEWAYS mode: mean reversion on Z-score extreme
        mr_entry = (
            (dataframe["regime_sideways"] == 1) &
            (dataframe["zscore"] < self.mr_zscore_buy.value) &
            (dataframe["rsi"] < self.mr_rsi_buy.value) &
            (dataframe["volume"] > self.mr_volume_spike.value * dataframe["volume_mean"]) &
            (dataframe["mr_std"] > 0) &
            (dataframe["volume"] > 0)
        )

        dataframe.loc[trend_entry | mr_entry, "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # TREND mode: RSI overbought exit
        trend_exit = (
            (dataframe["regime_trending"] == 1) &
            (dataframe["rsi"] > self.trend_rsi_sell.value)
        )

        # SIDEWAYS mode: price returned to mean
        mr_exit = (
            (dataframe["regime_sideways"] == 1) &
            (dataframe["zscore"] > self.mr_zscore_sell.value) &
            (dataframe["rsi"] > self.mr_rsi_sell.value)
        )

        # If regime changed mid-trade: exit safely
        regime_change = (
            (dataframe["regime_trending"] == 0) &
            (dataframe["regime_sideways"] == 0) &
            (dataframe["rsi"] > 60)  # Only exit on strength, not panic
        )

        dataframe.loc[trend_exit | mr_exit | regime_change, "exit_long"] = 1
        return dataframe
