# source: https://raw.githubusercontent.com/shamarmoore/TradingEdge/a31c44ecf6d5528f8ea2764ce38d530006589f29/strategies/MeanReverter.py
"""
Github_shamarmoore_TradingEdge__MeanReverter__20260327_024627 – Freqtrade strategy for RANGING regimes.

Buys at the lower Bollinger Band when the market is mean-reverting.
"""
from __future__ import annotations

import pandas as pd

try:
    from freqtrade.strategy import IStrategy
    _FREQTRADE_AVAILABLE = True
except ImportError:
    _FREQTRADE_AVAILABLE = False

    class IStrategy:  # type: ignore[no-redef]
        stoploss: float = -0.03
        minimal_roi: dict = {"0": 0.05}
        timeframe: str = "4h"
        trailing_stop: bool = False
        process_only_new_candles: bool = True
        use_exit_signal: bool = True
        exit_profit_only: bool = False
        can_short: bool = False
        startup_candle_count: int = 200

        def __init__(self, config: dict | None = None):
            self.config = config or {}

        def populate_indicators(self, dataframe, metadata):  # pragma: no cover
            return dataframe

        def populate_entry_trend(self, dataframe, metadata):  # pragma: no cover
            return dataframe

        def populate_exit_trend(self, dataframe, metadata):  # pragma: no cover
            return dataframe


import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from strategies.helpers.indicators import (
    rsi,
    bollinger_bands,
    stochastic,
    volume_sma,
)
from strategies.RegimeDetector import RegimeDetector

# Time-stop: exit after this many candles regardless of profit
TIME_STOP_CANDLES = 12


class Github_shamarmoore_TradingEdge__MeanReverter__20260327_024627(IStrategy):
    """
    Mean-reversion strategy active only in RANGING regime.

    Entry logic (all must be true):
        1. Regime == RANGING
        2. RSI_14 < 30
        3. Price within 1.5 % of lower Bollinger Band
        4. Stochastic %K crosses above %D below 20
        5. Volume increasing on bounce candle
        Filter: 20-day low must not be making new lows.

    Exit logic:
        - Target: middle Bollinger Band (handled via minimal_roi)
        - Hard stop: -3 %
        - Time stop: 12 candles
    """

    stoploss = -0.03
    minimal_roi = {"0": 0.05, "24": 0.03}
    timeframe = "4h"
    trailing_stop = False
    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = False
    can_short = False
    startup_candle_count = 200

    def __init__(self, config: dict | None = None):
        if _FREQTRADE_AVAILABLE:
            super().__init__(config)  # type: ignore[call-arg]
        else:
            self.config = config or {}
        self._regime_detector = RegimeDetector()

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        # Regime
        dataframe = self._regime_detector.add_indicators(dataframe)
        raw_regime = self._regime_detector.detect_regime(dataframe)
        dataframe["regime"] = self._regime_detector.apply_hysteresis(raw_regime, candles=3)

        # Strategy indicators
        dataframe["rsi_14"] = rsi(dataframe["close"], 14)

        bb_upper, bb_middle, bb_lower, bb_width = bollinger_bands(dataframe["close"], period=20)
        dataframe["bb_upper"] = bb_upper
        dataframe["bb_middle"] = bb_middle
        dataframe["bb_lower"] = bb_lower
        dataframe["bb_width"] = bb_width

        stoch_k, stoch_d = stochastic(dataframe["high"], dataframe["low"], dataframe["close"])
        dataframe["stoch_k"] = stoch_k
        dataframe["stoch_d"] = stoch_d
        dataframe["stoch_k_prev"] = stoch_k.shift(1)
        dataframe["stoch_d_prev"] = stoch_d.shift(1)

        dataframe["vol_avg_20"] = volume_sma(dataframe["volume"], 20)
        dataframe["vol_prev"] = dataframe["volume"].shift(1)

        # 20-period low for filter
        dataframe["low_20"] = dataframe["low"].rolling(window=20).min()
        dataframe["low_20_prev"] = dataframe["low_20"].shift(1)

        # Track entry candle for time-stop
        dataframe["candle_idx"] = range(len(dataframe))

        return dataframe

    def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        dataframe["enter_long"] = 0
        dataframe["enter_tag"] = ""

        bb_lower_dist = ((dataframe["close"] - dataframe["bb_lower"]) / dataframe["bb_lower"]).abs()

        # Stochastic %K crosses above %D below 20
        stoch_cross_up = (
            (dataframe["stoch_k"] > dataframe["stoch_d"])
            & (dataframe["stoch_k_prev"] <= dataframe["stoch_d_prev"])
            & (dataframe["stoch_k"] < 20)
        )

        # 20-period low not making new lows (filter)
        no_new_lows = dataframe["low_20"] >= dataframe["low_20_prev"]

        conditions = (
            (dataframe["regime"] == "RANGING")
            & (dataframe["rsi_14"] < 30)
            & (bb_lower_dist <= 0.015)
            & stoch_cross_up
            & (dataframe["volume"] > dataframe["vol_prev"])  # volume increasing
            & no_new_lows
        )

        dataframe.loc[conditions, "enter_long"] = 1
        dataframe.loc[conditions, "enter_tag"] = "mean_reversion_buy"
        return dataframe

    def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        dataframe["exit_long"] = 0
        dataframe["exit_tag"] = ""

        # Exit at middle BB (price reaches middle band)
        at_middle_bb = dataframe["close"] >= dataframe["bb_middle"]

        # Regime change out of RANGING
        regime_change = dataframe["regime"] != "RANGING"

        exit_conditions = at_middle_bb | regime_change
        dataframe.loc[exit_conditions, "exit_long"] = 1
        dataframe.loc[at_middle_bb, "exit_tag"] = "middle_bb_target"
        dataframe.loc[regime_change & ~at_middle_bb, "exit_tag"] = "regime_change"

        return dataframe
