# source: https://raw.githubusercontent.com/jcd1991/market-behavior-lab/c97d86a58bda5f2439d3b6fc17465a80ba0f091f/user_data/strategies/BreakoutEnsembleSpot.py
# directory_url: https://github.com/jcd1991/market-behavior-lab/blob/main/user_data/strategies/
# User: jcd1991
# Repository: market-behavior-lab
# --------------------"""Long-only multi-horizon Donchian breakout research lane for spot."""

from __future__ import annotations

import numpy as np
import pandas as pd
import talib.abstract as ta
from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy
from pandas import DataFrame


class Github_jcd1991_market_behavior_lab__BreakoutEnsembleSpot__20261007_192529(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = "4h"
    can_short = False
    process_only_new_candles = True
    startup_candle_count = 220
    stoploss = -0.14
    minimal_roi = {"0": 100.0}

    buy_bes_votes = IntParameter(1, 4, default=3, space="buy", optimize=True)
    buy_bes_atr_max = DecimalParameter(0.03, 0.40, default=0.20, decimals=3, space="buy", optimize=True)
    buy_bes_breakout_buffer = DecimalParameter(0.0, 0.05, default=0.002, decimals=3, space="buy", optimize=True)
    sell_bes_ema = IntParameter(8, 96, default=24, space="sell", optimize=True)

    HORIZONS = (12, 24, 48, 96)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        buffer = float(self.buy_bes_breakout_buffer.value)
        for horizon in self.HORIZONS:
            dataframe[f"bes_upper_{horizon}"] = dataframe["high"].rolling(horizon, min_periods=horizon).max().shift(1)
        dataframe["bes_atr_pct"] = ta.ATR(dataframe, timeperiod=14) / dataframe["close"].replace(0, np.nan)
        dataframe["bes_ema"] = ta.EMA(dataframe, timeperiod=int(self.sell_bes_ema.value))
        upper = dataframe[[f"bes_upper_{horizon}" for horizon in self.HORIZONS]]
        dataframe["bes_votes"] = (dataframe["close"].to_numpy()[:, None] > upper.to_numpy() * (1.0 + buffer)).sum(axis=1)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        valid = dataframe["bes_atr_pct"].le(float(self.buy_bes_atr_max.value))
        dataframe["enter_long"] = (valid & dataframe["bes_votes"].ge(int(self.buy_bes_votes.value))).astype(int)
        dataframe["enter_tag"] = np.where(dataframe["enter_long"], "multi_horizon_breakout", "")
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["exit_long"] = dataframe["close"].lt(dataframe["bes_ema"]).astype(int)
        dataframe["exit_tag"] = "ensemble_trend_exit"
        return dataframe
