# source: https://raw.githubusercontent.com/zevrichards/freqtrade-strategies/9dc9ef238325c4d29fa0b8e64346db96fe96343c/MeanReversion_4h.py
# directory_url: https://github.com/zevrichards/freqtrade-strategies/blob/main//
# User: zevrichards
# Repository: freqtrade-strategies
# --------------------# MeanReversion.py - BEST VERSION
# Bear: -5.3%, 7.77% drawdown (Oct 2025 - Jun 2026, 18 pairs, 4h)
# Bull: parked — no profitable bull version found yet
#
# KEY DECISIONS:
#   - RANGING-only regime — confirmed that BULL entries cause bull losses
#   - ADX floor/ceiling enforces genuine sideways condition (not trending)
#   - Price within 15% of 200 EMA — avoids structural breakdowns
#   - use_exit_signal = False — ROI and stoploss only, exit signal was a major loss source
#   - Stoploss -5% — tight, because if it's not bouncing quickly it's not ranging
#   - 4h timeframe — one candle = 4 hours, enough time for a real oversold bounce

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

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from pandas import DataFrame
import pandas_ta as pta
from regime_filter import add_regime_indicators, get_regime, RANGING


class Github_zevrichards_freqtrade_strategies__MeanReversion_4h__20260820_234001(IStrategy):

    minimal_roi = {"0": 0.03, "60": 0.02, "180": 0.01, "360": 0.005}
    stoploss = -0.05
    timeframe = "4h"
    trailing_stop = False
    use_exit_signal = False
    startup_candle_count: int = 210

    bb_window     = IntParameter(15, 30, default=20, space="buy")
    bb_std        = DecimalParameter(1.5, 2.5, default=2.0, decimals=1, space="buy")
    rsi_oversold  = IntParameter(25, 40, default=32, space="buy")
    adx_min       = IntParameter(10, 20, default=12, space="buy")
    adx_max       = IntParameter(20, 30, default=25, space="buy")
    ema_proximity = DecimalParameter(0.05, 0.20, default=0.15, decimals=2, space="buy")

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = add_regime_indicators(dataframe)
        for window in self.bb_window.range:
            for std in self.bb_std.range:
                bb = pta.bbands(dataframe["close"], length=window, std=float(std))
                if bb is not None:
                    dataframe[f"bb_lower_{window}_{std}"] = bb[f"BBL_{window}_{float(std)}"]
                    dataframe[f"bb_mid_{window}_{std}"]   = bb[f"BBM_{window}_{float(std)}"]
        dataframe["rsi"]       = pta.rsi(dataframe["close"], length=14)
        dataframe["volume_ma"] = pta.sma(dataframe["volume"], length=20)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        w, std = self.bb_window.value, self.bb_std.value
        lower  = f"bb_lower_{w}_{std}"
        regime = get_regime(dataframe)

        near_ema   = dataframe["regime_price_vs_200"].abs() < (self.ema_proximity.value * 100)
        adx_ranging = (
            (dataframe["regime_adx"] > self.adx_min.value) &
            (dataframe["regime_adx"] < self.adx_max.value)
        )

        dataframe.loc[
            (
                (regime == RANGING) &
                near_ema &
                adx_ranging &
                (dataframe["close"] <= dataframe[lower]) &
                (dataframe["rsi"] < self.rsi_oversold.value) &
                (dataframe["volume"] > dataframe["volume_ma"]) &
                (dataframe["volume"] > 0)
            ),
            "enter_long",
        ] = 1
        return dataframe

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