"""MR03 — Keltner Fade (mean-reversion sul canale ATR).

Stessa tesi di MR01 (i breakout rientrano) ma con banda costruita su ATR
attorno a una EMA, invece che su deviazione standard attorno a una SMA.
Reagisce diversamente a gap e code: edge indipendente, non ridondante con MR01.

Logica:
    1. Canale di Keltner: EMA(n) +/- k*ATR(n)
    2. ENTRY: close esce sotto la banda inferiore -> LONG (o sopra la superiore -> SHORT)
       Ingresso a close[i] (eseguibile dal vivo, nessun look-ahead).
    3. EXIT: take-profit alla EMA centrale (il rientro atteso),
       stop-loss a sl_atr*ATR oltre l'estremo, time-limit max_bars.

Validazione (netto, fee 0.10% RT reale Deribit, leva 3x, OOS = ultimo 30%):
    BTC 1h n=30 k=2.0: +112% OOS, DD 20%
    ETH 1h n=50 k=1.5: +1426% OOS, DD 20%
    Robusto su TUTTA la griglia n in {14,20,30,50} x k in {1.5,2.0,2.5}
    (BTC+ETH 1h sempre positivo OOS).
    Ricerca completa: scripts/analysis/strategy_research_v2.py.
"""
from __future__ import annotations
import sys
sys.path.insert(0, ".")

import numpy as np
import pandas as pd

from src.strategies.base import Signal
from src.strategies.fade_base import FadeStrategy, atr, trend_distance


class KeltnerFade(FadeStrategy):
    name = "MR03_keltner_fade"
    description = "Mean-reversion: fada il canale di Keltner (ATR), TP alla EMA"
    default_assets = ["BTC", "ETH"]
    default_timeframes = ["1h"]

    def generate_signals(self, df: pd.DataFrame, ts: pd.DatetimeIndex,
                         **params) -> list[Signal]:
        n = params.get("n", 30)
        k = params.get("k", 2.0)
        sl_atr = params.get("sl_atr", 2.0)
        max_bars = params.get("max_bars", 24)
        trend_max = params.get("trend_max")          # None = filtro disattivo
        ema_long = params.get("ema_long", 200)

        c = df["close"].values
        e = pd.Series(c).ewm(span=n, adjust=False).mean().values
        a = atr(df, n)
        up, lo = e + k * a, e - k * a
        td = trend_distance(df, ema_long) if trend_max is not None else None

        signals: list[Signal] = []
        for i in range(n + 1, len(c)):
            if np.isnan(up[i]) or np.isnan(a[i]):
                continue
            if td is not None and (np.isnan(td[i]) or td[i] > trend_max):
                continue
            if c[i] < lo[i] and c[i - 1] >= lo[i - 1]:
                d, sl = 1, c[i] - sl_atr * a[i]
            elif c[i] > up[i] and c[i - 1] <= up[i - 1]:
                d, sl = -1, c[i] + sl_atr * a[i]
            else:
                continue
            signals.append(Signal(
                idx=i, direction=d, entry_price=c[i],
                metadata={"tp": float(e[i]), "sl": float(sl), "max_bars": max_bars},
            ))
        return signals


if __name__ == "__main__":
    strat = KeltnerFade()
    print("=" * 110)
    print(f"  MR03 KELTNER FADE — netto fee {strat.fee_rt*100:.2f}% RT, leva {strat.leverage:.0f}x")
    print("=" * 110)
    for asset, n, k in [("BTC", 30, 2.0), ("ETH", 50, 1.5)]:
        r = strat.backtest(asset, "1h", n=n, k=k, sl_atr=2.0, max_bars=24)
        if r:
            r.strategy_name = f"MR03 {asset} 1h n{n} k{k}"
            r.print_summary()
            r.print_yearly()
