# source: https://raw.githubusercontent.com/zevrichards/freqtrade-strategies/9dc9ef238325c4d29fa0b8e64346db96fe96343c/BinHV45_Persistence_5m.py
# directory_url: https://github.com/zevrichards/freqtrade-strategies/blob/main//
# User: zevrichards
# Repository: freqtrade-strategies
# --------------------# Github_zevrichards_freqtrade_strategies__BinHV45_Persistence_5m__20260820_234001.py - BACKTEST ONLY
# Tests: FIX 1 ONLY — 30m trend persistence (3-candle lookback)
# No bounce confirmation candle
# Parameters: -3% stoploss, +2% ROI

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

from freqtrade.strategy import IStrategy, informative
from pandas import DataFrame
import pandas_ta as pta
import pandas as pd


class Github_zevrichards_freqtrade_strategies__BinHV45_Persistence_5m__20260820_234001(IStrategy):

    minimal_roi = {"0": 0.02}
    stoploss = -0.03
    timeframe = "5m"
    startup_candle_count: int = 250

    @property
    def protections(self):
        return [
            {"method": "CooldownPeriod", "stop_duration_candles": 12},
            {"method": "StoplossGuard", "lookback_period_candles": 24,
             "trade_limit": 2, "stop_duration_candles": 48, "only_per_pair": True}
        ]

    @informative('1h')
    def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ema_50"]  = pta.ema(dataframe["close"], length=50)
        dataframe["ema_200"] = pta.ema(dataframe["close"], length=200)
        dataframe["rsi"]     = pta.rsi(dataframe["close"], length=14)
        adx = pta.adx(dataframe["high"], dataframe["low"], dataframe["close"], length=14)
        dataframe["adx"] = adx["ADX_14"] if adx is not None and "ADX_14" in adx.columns else 0
        dataframe["is_bear"] = (
            (dataframe["close"] < dataframe["ema_200"]) &
            (dataframe["ema_50"] < dataframe["ema_200"]) &
            (dataframe["rsi"] < 50) &
            (dataframe["adx"] > 20)
        ).astype(int)
        return dataframe

    @informative('30m')
    def populate_indicators_30m(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ema_20"] = pta.ema(dataframe["close"], length=20)
        dataframe["ema_50"] = pta.ema(dataframe["close"], length=50)
        dataframe["rsi"]    = pta.rsi(dataframe["close"], length=14)
        dataframe["in_downtrend"] = (
            (dataframe["close"] < dataframe["ema_20"]) &
            (dataframe["ema_20"] < dataframe["ema_50"]) &
            (dataframe["rsi"] < 45)
        ).astype(int)
        # FIX 1 ONLY: 3-candle persistence
        dataframe["was_stable"] = (
            (dataframe["in_downtrend"] == 0) &
            (dataframe["in_downtrend"].shift(1) == 0) &
            (dataframe["in_downtrend"].shift(2) == 0)
        ).astype(int)
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        bb40 = pta.bbands(dataframe["close"], length=40, std=2.0)
        if bb40 is not None:
            dataframe["bb40_lower"] = bb40["BBL_40_2.0"]
            dataframe["bb40_mid"]   = bb40["BBM_40_2.0"]
            dataframe["bb40_delta"] = (bb40["BBM_40_2.0"] - bb40["BBL_40_2.0"]).abs()
        dataframe["closedelta"]       = (dataframe["close"] - dataframe["close"].shift(1)).abs()
        dataframe["tail"]             = (dataframe["close"] - dataframe["low"]).abs()
        dataframe["volume_mean_slow"] = dataframe["volume"].rolling(window=30).mean()
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        not_bear   = dataframe["1h_is_bear"] == 0 if "1h_is_bear" in dataframe.columns \
                     else pd.Series(True, index=dataframe.index)
        was_stable = dataframe["30m_was_stable"] == 1 if "30m_was_stable" in dataframe.columns \
                     else pd.Series(True, index=dataframe.index)

        # Original BinHV45 entry — NO bounce confirmation
        dataframe.loc[
            (
                not_bear & was_stable &
                (dataframe["bb40_delta"] > 0) &
                (dataframe["bb40_lower"].shift(1) > 0) &
                (dataframe["bb40_delta"] > dataframe["close"] * 0.007) &
                (dataframe["closedelta"] > dataframe["close"] * 0.0175) &
                (dataframe["tail"] < dataframe["bb40_delta"] * 0.25) &
                (dataframe["close"] < dataframe["bb40_lower"].shift(1)) &
                (dataframe["close"] <= dataframe["close"].shift(1)) &
                (dataframe["volume"] < dataframe["volume_mean_slow"].shift(1) * 20) &
                (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
