# source: https://raw.githubusercontent.com/shatianming5/Agent_market/8dc940a702171d651cabe59aea11f778550390e4/workspace/strategies/momentum_breakout.py
"""Momentum Breakout Strategy.

Hypothesis: When price breaks above the Donchian channel high with increasing
volume and positive momentum (ADX > 20), it signals a trend continuation.
Exit when momentum fades or price drops below EMA.
"""
from __future__ import annotations

import sys
from pathlib import Path

import numpy as np
from pandas import DataFrame

_ROOT = Path(__file__).resolve().parents[2]
if str(_ROOT / "src") not in sys.path:
    sys.path.insert(0, str(_ROOT / "src"))
    sys.path.insert(0, str(_ROOT))

from freqtrade.strategy import IStrategy


class Github_shatianming5_Agent_market__momentum_breakout__20260330_190736(IStrategy):
    timeframe = "1h"
    minimal_roi = {"0": 0.15, "180": 0.05, "480": 0.02}
    stoploss = -0.035
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.025
    use_exit_signal = True
    process_only_new_candles = True
    startup_candle_count: int = 50
    can_short = False

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Donchian Channel
        dataframe["dc_high_20"] = dataframe["high"].rolling(20).max()
        dataframe["dc_low_20"] = dataframe["low"].rolling(20).min()
        dataframe["dc_mid"] = (dataframe["dc_high_20"] + dataframe["dc_low_20"]) / 2

        # EMA
        dataframe["ema_12"] = dataframe["close"].ewm(span=12).mean()
        dataframe["ema_26"] = dataframe["close"].ewm(span=26).mean()

        # MACD-like momentum
        dataframe["momentum"] = dataframe["ema_12"] - dataframe["ema_26"]
        dataframe["momentum_signal"] = dataframe["momentum"].ewm(span=9).mean()

        # Volume
        dataframe["vol_sma_20"] = dataframe["volume"].rolling(20).mean()
        dataframe["vol_ratio"] = dataframe["volume"] / (dataframe["vol_sma_20"] + 1e-10)

        # ADX approximation (using directional movement)
        high_diff = dataframe["high"].diff()
        low_diff = -dataframe["low"].diff()
        plus_dm = high_diff.where((high_diff > low_diff) & (high_diff > 0), 0.0)
        minus_dm = low_diff.where((low_diff > high_diff) & (low_diff > 0), 0.0)
        tr = np.maximum(
            dataframe["high"] - dataframe["low"],
            np.maximum(
                abs(dataframe["high"] - dataframe["close"].shift(1)),
                abs(dataframe["low"] - dataframe["close"].shift(1)),
            ),
        )
        atr_14 = tr.rolling(14).mean()
        plus_di = 100 * plus_dm.rolling(14).mean() / (atr_14 + 1e-10)
        minus_di = 100 * minus_dm.rolling(14).mean() / (atr_14 + 1e-10)
        dx = 100 * abs(plus_di - minus_di) / (plus_di + minus_di + 1e-10)
        dataframe["adx"] = dx.rolling(14).mean()

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe["volume"] > 0)
            & (dataframe["close"] > dataframe["dc_high_20"].shift(1))  # breakout
            & (dataframe["adx"] > 20)  # trending
            & (dataframe["momentum"] > dataframe["momentum_signal"])  # momentum positive
            & (dataframe["vol_ratio"] > 1.0),  # above-average volume
            ["enter_long", "enter_tag"],
        ] = (1, "momentum_breakout")
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe["volume"] > 0)
            & (
                (dataframe["close"] < dataframe["ema_26"])  # below slow EMA
                | (dataframe["momentum"] < dataframe["momentum_signal"])  # momentum fading
            ),
            "exit_long",
        ] = 1
        return dataframe
