# source: https://raw.githubusercontent.com/kaorii-ako/tradingview/f68fe63ff7478da43693ee64d37d08ebdb907841/freqtrade/user_data/strategies/MomentumBreakout.py
# Github_kaorii_ako_tradingview__MomentumBreakout__20260607_033947.py — freqtrade strategy
# Translated from momentum_breakout_strategy.pine (Qullamaggie · Martin Luk · Ariel)
# Paper trading via: https://github.com/freqtrade/freqtrade
#
# Logic:
#   Entry  : close > N-bar consolidation high + volume surge + above EMA50 + ADR ok
#   Stop   : low of entry bar (never rises to original; EMA20 trail takes over)
#   Trail  : max(entry_stop, EMA20) — rises but never falls
#   Target : entry + (entry - stop) * R_target  (default 2R)

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from pandas import DataFrame
import pandas_ta as ta
import numpy as np


class Github_kaorii_ako_tradingview__MomentumBreakout__20260607_033947(IStrategy):
    # ── Strategy metadata ─────────────────────────────────────────────────
    INTERFACE_VERSION = 3
    timeframe = "4h"
    can_short = False

    # ── Risk / sizing (mirrors Pine defaults) ─────────────────────────────
    # Position sizing is handled by freqtrade config (stake_amount)
    stoploss = -0.08          # fallback hard stop; custom_stoploss overrides
    trailing_stop = False     # we do custom trailing via EMA20
    use_custom_stoploss = True
    startup_candle_count = 200

    # ── Hyperopt parameters (mirror Pine inputs) ───────────────────────────
    lookback    = IntParameter(10, 40, default=20, space="buy")
    vol_mult    = DecimalParameter(1.2, 3.0, default=1.5, decimals=1, space="buy")
    vol_len     = IntParameter(10, 40, default=20, space="buy")
    adr_min     = DecimalParameter(1.0, 10.0, default=3.0, decimals=1, space="buy")
    ema_fast    = IntParameter(5, 20, default=10, space="buy")
    ema_slow    = IntParameter(10, 40, default=20, space="buy", optimize=False)
    ema_trend   = IntParameter(30, 100, default=50, space="buy", optimize=False)
    r_target    = DecimalParameter(1.0, 5.0, default=2.0, decimals=1, space="sell")

    # ── Custom stoploss state ──────────────────────────────────────────────
    # Stores {pair: {"entry_stop": float, "entry_px": float}}
    _custom_info: dict = {}

    # ── Indicators ────────────────────────────────────────────────────────
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        lb  = self.lookback.value
        vl  = self.vol_len.value
        ef  = self.ema_fast.value
        es  = self.ema_slow.value
        et  = self.ema_trend.value

        dataframe["ema_fast"]  = ta.ema(dataframe["close"], length=ef)
        dataframe["ema_slow"]  = ta.ema(dataframe["close"], length=es)
        dataframe["ema_trend"] = ta.ema(dataframe["close"], length=et)
        dataframe["vol_avg"]   = ta.sma(dataframe["volume"], length=vl)

        # ADR: 14-bar SMA of (high-low) / close × 100
        dataframe["adr"] = (
            ta.sma(dataframe["high"] - dataframe["low"], length=14)
            / ta.sma(dataframe["close"], length=14)
            * 100
        )

        # Consolidation high: highest high of previous N bars (shifted by 1)
        dataframe["prev_high"] = (
            dataframe["high"].rolling(lb).max().shift(1)
        )

        return dataframe

    # ── Entry signal ──────────────────────────────────────────────────────
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        vm  = self.vol_mult.value
        adm = self.adr_min.value

        breakout   = dataframe["close"] > dataframe["prev_high"]
        high_vol   = dataframe["volume"] > dataframe["vol_avg"] * vm
        in_uptrend = dataframe["close"] > dataframe["ema_trend"]
        adr_ok     = dataframe["adr"] >= adm

        dataframe.loc[
            breakout & high_vol & in_uptrend & adr_ok,
            ["enter_long", "enter_tag"]
        ] = [1, "momentum_breakout"]

        return dataframe

    # ── Exit signal (R-target; EMA20 trail handled by custom_stoploss) ────
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Exits are handled by custom_stoploss (EMA20 trail) and R-target below
        dataframe["exit_long"] = 0
        return dataframe

    # ── Custom stoploss: entry-bar-low → EMA20 trailing ──────────────────
    def custom_stoploss(
        self,
        pair: str,
        trade,
        current_time,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> float:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe is None or dataframe.empty:
            return self.stoploss

        last = dataframe.iloc[-1]
        info = self._custom_info.get(pair)

        # First call for this trade: record entry stop = low of entry bar
        if info is None or info.get("trade_id") != trade.id:
            entry_candle = dataframe[dataframe["date"] <= trade.open_date_utc]
            if entry_candle.empty:
                return self.stoploss
            entry_bar = entry_candle.iloc[-1]
            self._custom_info[pair] = {
                "trade_id":   trade.id,
                "entry_stop": entry_bar["low"],
                "entry_px":   trade.open_rate,
            }
            info = self._custom_info[pair]

        # Trail: max(original entry_stop, current EMA20)
        trail_stop = max(info["entry_stop"], last["ema_slow"])

        # Convert absolute price to relative stoploss fraction freqtrade expects
        stoploss_value = (trail_stop / current_rate) - 1.0
        return max(stoploss_value, -0.99)   # never exceed -99%

    # ── Custom exit: take profit at R-target ──────────────────────────────
    def custom_exit(
        self,
        pair: str,
        trade,
        current_time,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ):
        info = self._custom_info.get(pair)
        if info is None:
            return None

        entry_px   = info["entry_px"]
        entry_stop = info["entry_stop"]
        risk_per_r = entry_px - entry_stop
        target     = entry_px + risk_per_r * self.r_target.value

        if current_rate >= target:
            return f"r_target_{self.r_target.value}R"

        return None
