"""Ricerca v2 — nuove strategie oltre MR01, stessa metodologia fee-aware OOS.

Lezioni ereditate (vedi strategy_research.py / oos_validation.py):
  - mean-reversion ha edge, continuation/trend NO (i breakout rientrano)
  - fee = vincolo di prim'ordine -> default Deribit 0.10% RT, poche operazioni meglio
  - ingresso ESEGUIBILE a close[i] (mai look-ahead con direzione da barra i)
  - ogni numero NETTO dopo fee+leva, su finestra held-out (OOS=ultimo 30%) + per anno

Nuovi candidati (tutti fade/mean-reversion con ingresso onesto):
  MR02 donchian_fade  - fade rottura canale Donchian (opposto del trend che muore)
  MR03 keltner_fade   - fade canale Keltner (ATR), TP alla EMA media
  MR04 zscore_revert  - fade deviazione z-score estrema, TP alla media
  MR05 boll_fade_adx  - Bollinger fade con filtro regime ADX (solo mercato laterale)

Engine identico a strategy_research.simulate (ingresso close[i], exit TP/SL intrabar
high/low o time-limit, non-overlap, capitale composto).
"""
from __future__ import annotations

import sys
from pathlib import Path

import numpy as np
import pandas as pd

PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))

# riusa engine, dati e indicatori gia' validati
from scripts.analysis.strategy_research import (
    FEE_RT, LEV, POS, OOS_FRAC, get_df, atr, rsi, simulate,
)


# --------------------------- indicatori extra ---------------------------
def ema(x: np.ndarray, n: int) -> np.ndarray:
    return pd.Series(x).ewm(span=n, adjust=False).mean().values


def adx(df: pd.DataFrame, n: int = 14) -> np.ndarray:
    """Average Directional Index: misura la forza del trend (alto=trend, basso=range)."""
    h, l, c = df["high"].values, df["low"].values, df["close"].values
    up = h - np.roll(h, 1)
    dn = np.roll(l, 1) - l
    up[0] = dn[0] = 0.0
    plus_dm = np.where((up > dn) & (up > 0), up, 0.0)
    minus_dm = np.where((dn > up) & (dn > 0), dn, 0.0)
    pc = np.roll(c, 1); pc[0] = c[0]
    tr = np.maximum(h - l, np.maximum(np.abs(h - pc), np.abs(l - pc)))
    atr_n = pd.Series(tr).ewm(alpha=1/n, adjust=False).mean().values
    pdi = 100 * pd.Series(plus_dm).ewm(alpha=1/n, adjust=False).mean().values / np.where(atr_n == 0, np.nan, atr_n)
    mdi = 100 * pd.Series(minus_dm).ewm(alpha=1/n, adjust=False).mean().values / np.where(atr_n == 0, np.nan, atr_n)
    dx = 100 * np.abs(pdi - mdi) / np.where((pdi + mdi) == 0, np.nan, pdi + mdi)
    return pd.Series(dx).ewm(alpha=1/n, adjust=False).mean().values


# --------------------------- strategie nuove ---------------------------
def donchian_fade(df, n=20, sl_atr=2.0, max_bars=24):
    """MR02 — fade rottura canale Donchian: rompe sopra max-N => short verso il mid.

    Coerente con 'i breakout rientrano': l'opposto di donchian_trend (che fallisce).
    Ingresso a close[i] sulla barra che chiude oltre il canale precedente.
    TP al centro del canale, SL = sl_atr*ATR oltre l'estremo.
    """
    h, l, c = df["high"].values, df["low"].values, df["close"].values
    hh = pd.Series(h).rolling(n).max().shift(1).values
    ll = pd.Series(l).rolling(n).min().shift(1).values
    a = atr(df, 14)
    ents = []
    for i in range(n + 14, len(c)):
        if np.isnan(hh[i]) or np.isnan(a[i]):
            continue
        mid = (hh[i] + ll[i]) / 2.0
        if c[i] > hh[i] and c[i - 1] <= hh[i - 1]:          # rottura rialzista => fade short
            ents.append({"i": i, "d": -1, "tp": mid, "sl": c[i] + sl_atr * a[i], "max_bars": max_bars})
        elif c[i] < ll[i] and c[i - 1] >= ll[i - 1]:        # rottura ribassista => fade long
            ents.append({"i": i, "d": 1, "tp": mid, "sl": c[i] - sl_atr * a[i], "max_bars": max_bars})
    return ents


def keltner_fade(df, n=20, k=2.0, sl_atr=2.0, max_bars=24):
    """MR03 — fade canale Keltner (EMA +/- k*ATR), TP alla EMA media.

    Come Bollinger ma banda basata su ATR (volatilita' di range) invece che std:
    reagisce diversamente ai gap. Ingresso quando close esce dalla banda.
    """
    c = df["close"].values
    e = ema(c, n)
    a = atr(df, n)
    up, lo = e + k * a, e - k * a
    ents = []
    for i in range(n + 1, len(c)):
        if np.isnan(up[i]) or np.isnan(a[i]):
            continue
        if c[i] < lo[i] and c[i - 1] >= lo[i - 1]:
            ents.append({"i": i, "d": 1, "tp": e[i], "sl": c[i] - sl_atr * a[i], "max_bars": max_bars})
        elif c[i] > up[i] and c[i - 1] <= up[i - 1]:
            ents.append({"i": i, "d": -1, "tp": e[i], "sl": c[i] + sl_atr * a[i], "max_bars": max_bars})
    return ents


def zscore_revert(df, n=50, z=2.0, sl_atr=2.5, max_bars=24):
    """MR04 — fade deviazione z-score estrema dalla media, TP alla media.

    z = (close-ma)/std. Entra quando |z| supera la soglia (close fuori); chiude
    quando torna alla media. Banda di Bollinger riparametrizzata in z (equivalente
    a k=z) ma con SL piu' largo e finestra lunga: poche operazioni, alta selettivita'.
    """
    c = df["close"].values
    ma = pd.Series(c).rolling(n).mean().values
    sd = pd.Series(c).rolling(n).std().values
    a = atr(df, 14)
    ents = []
    for i in range(n + 14, len(c)):
        if np.isnan(ma[i]) or sd[i] == 0 or np.isnan(a[i]):
            continue
        zi = (c[i] - ma[i]) / sd[i]
        zp = (c[i - 1] - ma[i - 1]) / sd[i - 1] if sd[i - 1] else 0.0
        if zi <= -z and zp > -z:
            ents.append({"i": i, "d": 1, "tp": ma[i], "sl": c[i] - sl_atr * a[i], "max_bars": max_bars})
        elif zi >= z and zp < z:
            ents.append({"i": i, "d": -1, "tp": ma[i], "sl": c[i] + sl_atr * a[i], "max_bars": max_bars})
    return ents


def boll_fade_adx(df, n=50, k=2.5, sl_atr=2.0, max_bars=24, adx_max=25.0):
    """MR05 — Bollinger fade SOLO in regime laterale (ADX < adx_max).

    Il fade soffre quando c'e' trend forte (il prezzo continua oltre la banda).
    Filtro ADX: opera solo quando la forza del trend e' bassa -> meno trade, edge piu' pulito.
    """
    c = df["close"].values
    ma = pd.Series(c).rolling(n).mean().values
    sd = pd.Series(c).rolling(n).std().values
    a = atr(df, 14)
    ax = adx(df, 14)
    up, lo = ma + k * sd, ma - k * sd
    ents = []
    for i in range(n + 14, len(c)):
        if np.isnan(up[i]) or np.isnan(a[i]) or np.isnan(ax[i]):
            continue
        if ax[i] >= adx_max:                 # trend forte: niente fade
            continue
        if c[i] < lo[i] and c[i - 1] >= lo[i - 1]:
            ents.append({"i": i, "d": 1, "tp": ma[i], "sl": c[i] - sl_atr * a[i], "max_bars": max_bars})
        elif c[i] > up[i] and c[i - 1] <= up[i - 1]:
            ents.append({"i": i, "d": -1, "tp": ma[i], "sl": c[i] + sl_atr * a[i], "max_bars": max_bars})
    return ents


def rsi2_fade(df, rsi_n=2, lo=10, hi=90, ma_n=200, tp_atr=2.0, sl_atr=3.0, max_bars=24):
    """MR06 — Connors RSI(2) pullback in direzione del trend, TP/SL in ATR.

    Meccanismo distinto da MR01/MR03: non usa bande di prezzo ma l'oscillatore
    RSI(2), che satura su micro-estremi. Filtro di trend con SMA lunga:
      - close SOPRA la SMA (uptrend) + RSI(2) < lo (dip) -> long, target rimbalzo
      - close SOTTO la SMA (downtrend) + RSI(2) > hi (pop) -> short
    TP = tp_atr*ATR a favore, SL = sl_atr*ATR contro. Compra il ritracciamento
    nel trend, non il contro-trend.
    """
    c = df["close"].values
    r = rsi(c, rsi_n)
    ma = pd.Series(c).rolling(ma_n).mean().values
    a = atr(df, 14)
    ents = []
    for i in range(ma_n + 14, len(c)):
        if np.isnan(r[i]) or np.isnan(ma[i]) or np.isnan(a[i]):
            continue
        if r[i] < lo and c[i] > ma[i]:            # dip in uptrend -> long
            ents.append({"i": i, "d": 1, "tp": c[i] + tp_atr * a[i], "sl": c[i] - sl_atr * a[i], "max_bars": max_bars})
        elif r[i] > hi and c[i] < ma[i]:          # pop in downtrend -> short
            ents.append({"i": i, "d": -1, "tp": c[i] - tp_atr * a[i], "sl": c[i] + sl_atr * a[i], "max_bars": max_bars})
    return ents


def return_reversal(df, n=50, k=3.5, tp_atr=2.0, sl_atr=1.5, max_bars=24):
    """MR07 — fade movimento di barra estremo (return reversal).

    Misura il rendimento dell'ultima barra in unita' di deviazione standard rolling
    dei rendimenti. Se |ret| > k*sigma, fada nella direzione opposta; TP/SL in ATR.
    Meccanismo distinto: usa la volatilita' dei RENDIMENTI, non i livelli di prezzo.
    Config robusta (k=3.5, tp=2ATR, sl=1.5ATR): positivo full+OOS BTC e ETH 1h,
    DD piu' contenuto (BTC 25% / ETH 46%).
    """
    c = df["close"].values
    ret = np.zeros_like(c)
    ret[1:] = np.diff(c) / c[:-1]
    sig = pd.Series(ret).rolling(n).std().values
    a = atr(df, 14)
    ents = []
    for i in range(n + 14, len(c)):
        if np.isnan(sig[i]) or sig[i] == 0 or np.isnan(a[i]):
            continue
        z = ret[i] / sig[i]
        if z <= -k:                                # crollo di barra -> fade long
            ents.append({"i": i, "d": 1, "tp": c[i] + tp_atr * a[i], "sl": c[i] - sl_atr * a[i], "max_bars": max_bars})
        elif z >= k:                               # spike di barra -> fade short
            ents.append({"i": i, "d": -1, "tp": c[i] - tp_atr * a[i], "sl": c[i] + sl_atr * a[i], "max_bars": max_bars})
    return ents


CANDIDATES = {
    "MR02 donch_fade n20":  (donchian_fade, dict(n=20, sl_atr=2.0, max_bars=24)),
    "MR02 donch_fade n50":  (donchian_fade, dict(n=50, sl_atr=2.0, max_bars=24)),
    "MR03 kelt_fade k2":    (keltner_fade, dict(n=20, k=2.0, sl_atr=2.0, max_bars=24)),
    "MR03 kelt_fade k2.5":  (keltner_fade, dict(n=20, k=2.5, sl_atr=2.0, max_bars=24)),
    "MR04 zscore z2 n50":   (zscore_revert, dict(n=50, z=2.0, sl_atr=2.5, max_bars=24)),
    "MR04 zscore z2.5 n50": (zscore_revert, dict(n=50, z=2.5, sl_atr=2.5, max_bars=24)),
    "MR05 boll_adx n50":    (boll_fade_adx, dict(n=50, k=2.5, sl_atr=2.0, max_bars=24, adx_max=25)),
    "MR05 boll_adx n20":    (boll_fade_adx, dict(n=20, k=2.5, sl_atr=2.0, max_bars=24, adx_max=25)),
    "MR06 rsi2 10/90":      (rsi2_fade, dict(rsi_n=2, lo=10, hi=90, ma_n=200, tp_atr=2.0, sl_atr=3.0, max_bars=24)),
    "MR06 rsi2 5/95":       (rsi2_fade, dict(rsi_n=2, lo=5, hi=95, ma_n=200, tp_atr=2.0, sl_atr=3.0, max_bars=24)),
    "MR07 retrev k3.5":     (return_reversal, dict(n=50, k=3.5, tp_atr=2.0, sl_atr=1.5, max_bars=24)),
    "MR07 retrev k3.0":     (return_reversal, dict(n=50, k=3.0, tp_atr=2.0, sl_atr=1.5, max_bars=24)),
}


def table():
    print("=" * 122)
    print(f"  RICERCA v2 — NETTO dopo fee {FEE_RT*100:.2f}% RT | leva {LEV:.0f}x | pos {POS*100:.0f}% "
          f"| OOS = ultimo {int(OOS_FRAC*100)}%")
    print("=" * 122)
    print(f"  {'Strategia':<22s}{'Asset':>5s}{'TF':>5s}{'Trd':>6s}{'Tr/yr':>7s}{'Win%':>7s}"
          f"{'Ret%FULL':>10s}{'Ret%OOS':>10s}{'DD%':>7s}{'Exp%':>7s}{'AnniPos':>9s}")
    print("  " + "-" * 118)
    for label, (fn, params) in CANDIDATES.items():
        for asset in ["BTC", "ETH"]:
            for tf in ["1h", "4h"]:
                df = get_df(asset, tf)
                ents = fn(df, **params)
                full = simulate(ents, df)
                split = int(len(df) * (1 - OOS_FRAC))
                oos = simulate([e for e in ents if e["i"] >= split], df)
                yrs = full["yearly"]
                pos_yrs = sum(1 for v in yrs.values() if v > 0)
                tr_yr = full["trades"] / max(len(yrs), 1)
                robust = oos["ret"] > 0 and full["ret"] > 0 and pos_yrs >= max(len(yrs) - 1, 1)
                flag = "  <<<" if robust else ""
                print(f"  {label:<22s}{asset:>5s}{tf:>5s}{full['trades']:>6d}{tr_yr:>7.0f}{full['win']:>7.1f}"
                      f"{full['ret']:>+10.1f}{oos['ret']:>+10.1f}{full['dd']:>7.1f}{full['exposure']:>7.1f}"
                      f"{f'{pos_yrs}/{len(yrs)}':>9s}{flag}")
        print("  " + "-" * 118)
    print("  <<< = positivo full+OOS e robusto (quasi tutti gli anni positivi).")


def deep_dive():
    """Robustezza dei 3 candidati promossi: fee sweep + griglia parametri OOS."""
    split_of = lambda df: int(len(df) * (1 - OOS_FRAC))
    fees = [0.0, 0.0005, 0.001, 0.002]

    print("\n" + "#" * 122)
    print("  APPROFONDIMENTO MR02 / MR03 / MR05 — robustezza fee + griglia (deve restare positivo)")
    print("#" * 122)

    # --- MR02 Donchian Fade ---
    print(f"\n  [MR02 donchian_fade] SENSIBILITA' FEE — Ret% FULL/OOS (n=20)")
    print(f"  {'Asset/TF':<10s}" + "".join(f"{f'{f*100:.2f}%RT':>22s}" for f in fees))
    print(f"  {'':<10s}" + "".join(f"{'full':>11s}{'oos':>11s}" for _ in fees))
    for a, tf in [("BTC", "1h"), ("ETH", "1h"), ("BTC", "4h"), ("ETH", "4h")]:
        df = get_df(a, tf); sp = split_of(df)
        ents = donchian_fade(df, n=20, sl_atr=2.0, max_bars=24)
        oents = [e for e in ents if e["i"] >= sp]
        cells = "".join(f"{simulate(ents, df, fee_rt=f)['ret']:>+11.0f}{simulate(oents, df, fee_rt=f)['ret']:>+11.0f}" for f in fees)
        print(f"  {a+' '+tf:<10s}{cells}")
    print(f"\n  [MR02] GRIGLIA n x sl_atr — Ret%OOS(DD%) | fee {FEE_RT*100:.2f}% RT")
    for a in ["BTC", "ETH"]:
        df = get_df(a, "1h"); sp = split_of(df)
        print(f"\n  {a} 1h     " + "".join(f"{f'sl={s}':>16s}" for s in [1.5, 2.0, 3.0]))
        for n in [10, 20, 30, 50]:
            cells = ""
            for s in [1.5, 2.0, 3.0]:
                r = simulate([e for e in donchian_fade(df, n=n, sl_atr=s, max_bars=24) if e["i"] >= sp], df)
                cell = "%+.0f(%.0f)" % (r["ret"], r["dd"])
                cells += f"{cell:>16s}"
            print(f"   n={n:<4d}{cells}")

    # --- MR03 Keltner Fade ---
    print(f"\n  [MR03 keltner_fade] GRIGLIA n x k — Ret%OOS(DD%) | fee {FEE_RT*100:.2f}% RT")
    for a in ["BTC", "ETH"]:
        df = get_df(a, "1h"); sp = split_of(df)
        print(f"\n  {a} 1h     " + "".join(f"{f'k={k}':>16s}" for k in [1.5, 2.0, 2.5]))
        for n in [14, 20, 30, 50]:
            cells = ""
            for k in [1.5, 2.0, 2.5]:
                r = simulate([e for e in keltner_fade(df, n=n, k=k, sl_atr=2.0, max_bars=24) if e["i"] >= sp], df)
                cell = "%+.0f(%.0f)" % (r["ret"], r["dd"])
                cells += f"{cell:>16s}"
            print(f"   n={n:<4d}{cells}")

    # --- MR05 Bollinger Fade + ADX ---
    print(f"\n  [MR05 boll_fade_adx] GRIGLIA n x adx_max — Ret%OOS(DD%) | fee {FEE_RT*100:.2f}% RT")
    for a in ["BTC", "ETH"]:
        df = get_df(a, "1h"); sp = split_of(df)
        print(f"\n  {a} 1h     " + "".join(f"{f'adx<{x}':>16s}" for x in [20, 25, 30]))
        for n in [20, 30, 50]:
            cells = ""
            for x in [20, 25, 30]:
                r = simulate([e for e in boll_fade_adx(df, n=n, k=2.5, sl_atr=2.0, max_bars=24, adx_max=x) if e["i"] >= sp], df)
                cell = "%+.0f(%.0f)" % (r["ret"], r["dd"])
                cells += f"{cell:>16s}"
            print(f"   n={n:<4d}{cells}")


if __name__ == "__main__":
    table()
    deep_dive()
