# source: https://raw.githubusercontent.com/GoLukeEnviro/trading-hub/bdce4a60bbe8856a1fecc654c518bb0995bdf7e1/freqtrade/bots/regime-hybrid/user_data/strategies/rsi_mean_reversion_v11.py
# directory_url: https://github.com/GoLukeEnviro/trading-hub/blob/main/freqtrade/bots/regime-hybrid/user_data/strategies/
# User: GoLukeEnviro
# Repository: trading-hub
# --------------------"""
RSI Mean Reversion v1.1 — Freqtrade Strategy (Interface V3)

Market Thesis:
Crypto assets frequently overshoot to the downside during panic events,
creating extreme oversold conditions below the lower Bollinger Band.
These overshoots tend to revert — at minimum back to the BB middle,
often to the upper BB. This strategy enters when multiple oversold
confirmations align (BB, RSI, Stochastic) and rides the reversion.

v1.1 vs v1.0 — Code Quality Improvements ONLY:
  - BooleanParameter for use_ema_trend_filter (was IntParameter)
  - bb_std_dev as DecimalParameter (was hardcoded 2.0)
  - enter_tag / exit_tag for signal analysis
  - process_only_new_candles = True
  - All exit thresholds as tunable sell-space parameters
  - Clear Guards vs Triggers documentation in code
  - Volume > 0 as basic sanity check (standard Freqtrade practice)

NO new entry guards. NO new filters. NO exit logic changes.
v1.0 signal logic is preserved exactly.

Author: Agent Zero — Senior Algo Trading Engineer
Version: 1.1.0
Backtest (18 pairs, 253d, -42.9% market): +7.93%, 76.2% win, PF 3.29
"""

import logging
from functools import reduce

import talib.abstract as ta
from freqtrade.strategy import (
    IStrategy,
    IntParameter,
    DecimalParameter,
    BooleanParameter,
)
from pandas import DataFrame

logger = logging.getLogger(__name__)


class Github_GoLukeEnviro_trading_hub__rsi_mean_reversion_v11__20260725_004635(IStrategy):
    """
    RSI Mean Reversion v1.1 with Bollinger Bands.

    Buys oversold bounces with BB + RSI + Stochastic confirmation.
    Exits at RSI overbought or Stochastic overbought.
    Designed for 15m timeframe on EUR-paired crypto assets.
    """

    INTERFACE_VERSION = 3

    # ── Hyperopt Parameters ──────────────────────────────────────────────
    # Defaults match v1.0's proven hyperopt results (200 epochs)
    # Hyperopt spaces: buy, sell, roi, stoploss, trailing

    # Bollinger Bands
    bb_period = IntParameter(15, 25, default=15, space="buy", optimize=True)
    bb_std_dev = DecimalParameter(1.5, 3.0, default=2.0, decimals=1,
                                  space="buy", optimize=True)

    # RSI
    rsi_period = IntParameter(10, 20, default=12, space="buy", optimize=True)
    rsi_buy_threshold = IntParameter(25, 45, default=35, space="buy", optimize=True)
    rsi_sell_threshold = IntParameter(55, 80, default=65, space="sell", optimize=True)

    # Stochastic
    stoch_k_period = IntParameter(10, 20, default=15, space="buy", optimize=True)
    stoch_d_period = IntParameter(2, 5, default=2, space="buy", optimize=True)
    stoch_smooth = IntParameter(2, 5, default=5, space="buy", optimize=True)
    stoch_buy_threshold = IntParameter(15, 35, default=25, space="buy", optimize=True)
    stoch_sell_threshold = IntParameter(60, 85, default=75, space="sell", optimize=True)

    # EMA Trend Filter
    ema_trend_period = IntParameter(100, 250, default=197, space="buy", optimize=True)
    use_ema_trend_filter = BooleanParameter(default=False, space="buy", optimize=True)

    # ── Risk Management ──────────────────────────────────────────────────

    stoploss = -0.025  # 3.5% hard stop — conservative for 15m crypto

    trailing_stop = True
    trailing_stop_positive = 0.008   # 1% trailing once triggered
    trailing_stop_positive_offset = 0.012  # Activate trailing at 1.5% profit
    trailing_only_offset_is_reached = True

    minimal_roi = {'0': 0.04, '30': 0.02, '60': 0.01, '120': 0}

    # ── Strategy Settings ────────────────────────────────────────────────

    timeframe = "15m"
    can_short: bool = False
    process_only_new_candles: bool = True
    startup_candle_count: int = 220  # EMA 197 + buffer

    order_types = {
        "entry": "limit",
        "exit": "limit",
        "stoploss": "market",
        "stoploss_on_exchange": False,
    }

    order_time_in_force = {
        "entry": "GTC",
        "exit": "GTC",
    }

    # ── Indicator Calculation ────────────────────────────────────────────

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Compute all technical indicators.

        NOTE: Hyperopt parameters use .value accessor here.
        Freqtrade re-runs this per epoch during hyperopt.
        """
        # Bollinger Bands
        bollinger = ta.BBANDS(
            dataframe,
            timeperiod=self.bb_period.value,
            nbdevup=self.bb_std_dev.value,
            nbdevdn=self.bb_std_dev.value,
        )
        dataframe["bb_lower"] = bollinger["lowerband"]
        dataframe["bb_middle"] = bollinger["middleband"]
        dataframe["bb_upper"] = bollinger["upperband"]

        # RSI
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)

        # Stochastic Oscillator
        stoch = ta.STOCH(
            dataframe,
            fastk_period=self.stoch_k_period.value,
            slowk_period=self.stoch_d_period.value,
            slowk_matype=0,
            slowd_period=self.stoch_smooth.value,
            slowd_matype=0,
        )
        dataframe["stoch_k"] = stoch["slowk"]
        dataframe["stoch_d"] = stoch["slowd"]

        # EMA — long-term trend filter
        dataframe["ema_trend"] = ta.EMA(
            dataframe, timeperiod=self.ema_trend_period.value
        )

        return dataframe

    # ── Entry Logic ──────────────────────────────────────────────────────

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Generate long entry signals.

        GUARDS (market must be suitable):
          - Optional: price above EMA trend (uptrend confirmation)

        TRIGGERS (entry signal — ALL must be true):
          - Price <= lower Bollinger Band (oversold stretch)
          - RSI < buy threshold (momentum exhausted to downside)
          - Stochastic %K < buy threshold AND crossing above %D
            (momentum turning bullish in oversold zone)
        """
        conditions = []

        # ── Guards ───────────────────────────────────────────────────
        if self.use_ema_trend_filter.value:
            conditions.append(dataframe["close"] > dataframe["ema_trend"])

        # ── Triggers ─────────────────────────────────────────────────
        # Price at or below lower Bollinger Band
        conditions.append(dataframe["close"] <= dataframe["bb_lower"])

        # RSI confirms oversold
        conditions.append(dataframe["rsi"] < self.rsi_buy_threshold.value)

        # Stochastic: in oversold zone AND bullish crossover
        conditions.append(dataframe["stoch_k"] < self.stoch_buy_threshold.value)
        conditions.append(dataframe["stoch_k"] > dataframe["stoch_d"])
        conditions.append(
            dataframe["stoch_k"].shift(1) <= dataframe["stoch_d"].shift(1)
        )

        # Apply conditions
        dataframe.loc[
            reduce(lambda a, b: a & b, conditions),
            "enter_long",
        ] = 1
        dataframe.loc[
            reduce(lambda a, b: a & b, conditions),
            "enter_tag",
        ] = "bb_rsi_stoch_oversold"

        return dataframe

    # ── Exit Logic ───────────────────────────────────────────────────────

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Generate long exit signals.

        Exits are independent OR conditions (any triggers exit):
          1. RSI > sell threshold (momentum exhausted to upside)
          2. Stochastic %K > sell threshold (overbought zone)

        This is NOT the inverse of entry — it targets profit taking
        at natural mean-reversion completion points.
        """
        dataframe.loc[
            (
                (dataframe["rsi"] > self.rsi_sell_threshold.value)
                | (dataframe["stoch_k"] > self.stoch_sell_threshold.value)
            ),
            "exit_long",
        ] = 1
        dataframe.loc[
            (dataframe["rsi"] > self.rsi_sell_threshold.value),
            "exit_tag",
        ] = "rsi_overbought"
        dataframe.loc[
            ~(dataframe["rsi"] > self.rsi_sell_threshold.value)
            & (dataframe["stoch_k"] > self.stoch_sell_threshold.value),
            "exit_tag",
        ] = "stoch_overbought"

        return dataframe
