"""VERIFICATORE AVVERSARIALE OVERFIT — EXIT-22 no_sl.

Tesi del sopravvissuto: "rimuovere lo SL intrabar (resta TP+max_bars) MIGLIORA
ret E DD E Sharpe su tutte le 6 sleeve, plateau monotono tight<base<wide<none".

Ipotesi avversaria (da confutare o confermare):
  (A) JITTER: il plateau none>wide>base e' monotono? Aggiungo ponti 3x/4x.
      Se la curva e' monotona e satura (3x~4x~none), il plateau e' robusto;
      se none e' un picco isolato oltre wide, sospetto.
  (B) STABILITA' TEMPORALE: spezzo train (2018-20 vs 21-22) e OOS
      (2023-11..2025-01 vs 2025-01..2026-05). Il guadagno c'e' in OGNI
      finestra o e' concentrato in un solo regime?
  (C) DIPENDENZA HURST (DECISIVO): i segnali in cache hanno hurst_max=0.55
      (loss-guard toglie il regime persistente/trending — proprio dove gli SL
      servono). Rigenero i segnali SENZA hurst (in memoria, cache intatta) e
      ripeto base-vs-none. Se senza guard la policy crolla -> funziona SOLO
      grazie al guard => condizione di validita'.

Esegui: cd /opt/docker/PythagorasGoal && PYTHONPATH=. uv run python \
  scripts/analysis/exit_policies/verify_22_no_sl_overfit.py
"""
import sys
from pathlib import Path

import numpy as np
import pandas as pd

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))

import exit_lab  # noqa: E402
from exit_lab import (ExitPolicy, simulate, load_sleeves, OOS_START_MS,  # noqa: E402
                      CODES, ASSETS, LIVE_PARAMS, _atr14)
from src.data.downloader import load_data            # noqa: E402
from src.live.strategy_loader import load_strategy   # noqa: E402

# import della policy DAL SUO FILE (no copia)
sys.path.insert(0, str(Path(__file__).resolve().parent))
import importlib.util
_spec = importlib.util.spec_from_file_location(
    "policy_22", str(Path(__file__).resolve().parent / "22_no_sl.py"))
_mod = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_mod)
NoSl = _mod.NoSl


# ---- policy ponte: SL a scale arbitrario (per jitter 3x/4x) -----------------
class NoSlScale(ExitPolicy):
    name = "no_sl_scale"

    def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params):
        super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
        scale = params.get("scale", None)
        self.sl = None if scale is None else entry + float(scale) * (sl0 - entry)

    def levels(self, j: int):
        return self.tp0, self.sl, 1.0


def _fmt(r):
    if not r:
        return "  (no trades)"
    return (f"ret{r['ret_pct']:>8.0f}% dd{r['dd_pct']:>6.2f} sh{r['sharpe_t']:>6.2f} "
            f"n{r['trades']:>4} win{r['win_pct']:>5.1f} bars{r['avg_bars']:>5.1f}")


# =============================================================================
# (A) JITTER: ponti di scala 1x(base) 1.5 2 3 4 none
# =============================================================================
def test_jitter(data):
    print("\n" + "=" * 78)
    print("(A) JITTER PLATEAU — scala SL: base(1x) 1.5x 2x 3x 4x none  [OOS]")
    print("=" * 78)
    scales = [("base", {"scale": 1.0}), ("1.5x", {"scale": 1.5}),
              ("2x(wide)", {"scale": 2.0}), ("3x", {"scale": 3.0}),
              ("4x", {"scale": 4.0}), ("none", {"scale": None})]
    monotonic_fail = 0
    for (code, asset), sleeve in data.items():
        key = f"{code.split('_')[0]} {asset}"
        print(f"\n{key}  [OOS 2023-11 ->]")
        rets, dds, shs = [], [], []
        for tag, g in scales:
            r = simulate(NoSlScale, sleeve, g, start_ms=OOS_START_MS)
            rets.append(r.get("ret_pct", 0)); dds.append(r.get("dd_pct", 0))
            shs.append(r.get("sharpe_t", 0))
            print(f"   {tag:<10}{_fmt(r)}")
        # plateau monotono atteso: ret crescente, dd decrescente, sh crescente
        ret_mono = all(rets[i] <= rets[i + 1] + 1e-6 for i in range(len(rets) - 1))
        sh_mono = all(shs[i] <= shs[i + 1] + 1e-6 for i in range(len(shs) - 1))
        # saturazione: none vs 4x ravvicinati (plateau "piatto" in cima)?
        sat = abs(rets[-1] - rets[-2]) / (abs(rets[-1]) + 1e-9) * 100
        print(f"   -> ret monotono crescente: {ret_mono} | sharpe monotono: {sh_mono}"
              f" | gap none-vs-4x: {sat:.1f}%")
        if not (ret_mono and sh_mono):
            monotonic_fail += 1
    print(f"\n  Sleeve con plateau NON monotono: {monotonic_fail}/6")
    return monotonic_fail


# =============================================================================
# (B) STABILITA' TEMPORALE: sotto-finestre train e OOS
# =============================================================================
def test_temporal(data):
    print("\n" + "=" * 78)
    print("(B) STABILITA' TEMPORALE — base vs none in 4 sotto-finestre")
    print("=" * 78)
    ms = lambda s: int(pd.Timestamp(s, tz="UTC").value // 1e6)
    wins = [
        ("TR 2018-2020", None, ms("2021-01-01")),
        ("TR 2021-2022", ms("2021-01-01"), OOS_START_MS),
        ("OOS 23-11..25-01", OOS_START_MS, ms("2025-01-01")),
        ("OOS 25-01..26-05", ms("2025-01-01"), None),
    ]
    # conta in quante (sleeve x finestra) none batte base su ret E dd E sh
    cells = 0
    none_better_all = 0
    none_worse_dd = 0
    for wname, s0, s1 in wins:
        print(f"\n--- finestra {wname} ---")
        for (code, asset), sleeve in data.items():
            key = f"{code.split('_')[0]} {asset}"
            b = simulate(ExitPolicy, sleeve, {}, start_ms=s0, end_ms=s1)
            nn = simulate(NoSl, sleeve, {"mode": "none"}, start_ms=s0, end_ms=s1)
            if not b or not nn:
                print(f"   {key:<10} (campione vuoto)")
                continue
            cells += 1
            d_ret = nn["ret_pct"] - b["ret_pct"]
            d_dd = nn["dd_pct"] - b["dd_pct"]   # <0 = none meglio (dd minore)
            d_sh = nn["sharpe_t"] - b["sharpe_t"]
            allbetter = d_ret > 0 and d_dd < 0 and d_sh > 0
            none_better_all += allbetter
            none_worse_dd += d_dd > 1e-6
            flag = "OK" if allbetter else ("dd+" if d_dd > 0 else "ret-" if d_ret <= 0 else "sh-")
            print(f"   {key:<10} base ret{b['ret_pct']:>7.0f} dd{b['dd_pct']:>5.1f} "
                  f"sh{b['sharpe_t']:>5.2f} | none ret{nn['ret_pct']:>7.0f} "
                  f"dd{nn['dd_pct']:>5.1f} sh{nn['sharpe_t']:>5.2f} | "
                  f"dRet{d_ret:>+7.0f} dDD{d_dd:>+5.1f} dSh{d_sh:>+5.2f} [{flag}]")
    print(f"\n  none meglio su (ret&dd&sh) in {none_better_all}/{cells} celle "
          f"sleeve x finestra | none PEGGIORA il DD in {none_worse_dd}/{cells}")
    return none_better_all, none_worse_dd, cells


# =============================================================================
# (C) DECISIVO: segnali SENZA hurst (in memoria, cache intatta)
# =============================================================================
def build_sleeves_no_hurst():
    """Rigenera i segnali con hurst_max=None (loss-guard OFF). NON tocca la
    cache su disco: ritorna un dict identico in forma a load_sleeves()."""
    params = dict(LIVE_PARAMS)
    params["hurst_max"] = None
    out = {}
    for code in CODES:
        strat = load_strategy(code)
        for asset in ASSETS:
            df = load_data(asset, "1h")
            ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
            sigs = strat.generate_signals(df, ts, **params)
            h = df["high"].values.astype(float)
            l = df["low"].values.astype(float)
            c = df["close"].values.astype(float)
            out[(code, asset)] = {
                "signals": [(int(s.idx), int(s.direction), float(s.metadata["tp"]),
                             float(s.metadata["sl"]), int(s.metadata["max_bars"]))
                            for s in sigs],
                "open": df["open"].values.astype(float),
                "high": h, "low": l, "close": c,
                "ts_ms": df["timestamp"].values.astype(np.int64),
                "atr14": _atr14(h, l, c),
            }
    return out


def test_hurst_dependency(data_guard):
    print("\n" + "=" * 78)
    print("(C) DECISIVO — DIPENDENZA DAL FILTRO HURST")
    print("    rigenero segnali con hurst_max=None (guard OFF), confronto base vs none")
    print("=" * 78)
    data_noh = build_sleeves_no_hurst()
    # quanto cambia il numero di segnali (il guard quanti ne toglieva?)
    print("\nConteggio segnali (guard ON cache vs guard OFF):")
    for k in data_guard:
        ng = len(data_guard[k]["signals"])
        nh = len(data_noh[k]["signals"])
        print(f"   {k[0].split('_')[0]} {k[1]:<4} guard ON {ng:>5}  OFF {nh:>5}  "
              f"(+{nh - ng} segnali tossici reintrodotti)")

    for label, ms0 in [("FULL", None), ("OOS", OOS_START_MS)]:
        print(f"\n--- {label} (guard OFF) base vs none ---")
        none_better_all = none_worse_dd = none_worse_ret = cells = 0
        for (code, asset), sleeve in data_noh.items():
            key = f"{code.split('_')[0]} {asset}"
            b = simulate(ExitPolicy, sleeve, {}, start_ms=ms0)
            nn = simulate(NoSl, sleeve, {"mode": "none"}, start_ms=ms0)
            if not b or not nn:
                continue
            cells += 1
            d_ret = nn["ret_pct"] - b["ret_pct"]
            d_dd = nn["dd_pct"] - b["dd_pct"]
            d_sh = nn["sharpe_t"] - b["sharpe_t"]
            none_better_all += (d_ret > 0 and d_dd < 0 and d_sh > 0)
            none_worse_dd += d_dd > 1e-6
            none_worse_ret += d_ret <= 0
            flag = "OK" if (d_ret > 0 and d_dd < 0 and d_sh > 0) else \
                   ("dd+" if d_dd > 0 else "ret-" if d_ret <= 0 else "sh-")
            print(f"   {key:<10} base ret{b['ret_pct']:>8.0f} dd{b['dd_pct']:>6.1f} "
                  f"sh{b['sharpe_t']:>5.2f} | none ret{nn['ret_pct']:>8.0f} "
                  f"dd{nn['dd_pct']:>6.1f} sh{nn['sharpe_t']:>5.2f} | "
                  f"dRet{d_ret:>+8.0f} dDD{d_dd:>+6.1f} dSh{d_sh:>+5.2f} [{flag}]")
        print(f"   => guard OFF {label}: none meglio (ret&dd&sh) {none_better_all}/{cells}"
              f" | none PEGGIORA dd {none_worse_dd}/{cells} | PEGGIORA ret {none_worse_ret}/{cells}")
    return data_noh


if __name__ == "__main__":
    data = load_sleeves()
    mono_fail = test_jitter(data)
    nb_all, nb_dd, cells = test_temporal(data)
    test_hurst_dependency(data)
