"""Validazione dell'edge del credit-spread di cerbero-bite sui PREZZI REALI.

cerbero-bite (container accanto) vende credit spread su ETH (bull-put primario,
short delta ~0.18, DTE 18, PT 50% / stop 2.5x credito / delta-breach 0.30 / vol-stop
+10 DVOL / time-stop 7 DTE). Domanda: l'edge regge su un CICLO ETH completo, o e'
profittevole solo nei campioni calmi?

Tre analisi (riprendibili):
  1) entry_economics()  -> economia d'ingresso REALE dalla chain (data/options/eth_chain.parquet):
     credit/width effettivo a delta 0.18 dai bid/ask veri, eleggibilita' sotto i gate liquidita'.
  2) tail_model_free()  -> esito terminale dai prezzi ETH reali (2018-2026), cw reale 0.106,
     NESSUN modello opzioni (niente errore BS): win-rate, EV, frequenza max-loss.
  3) managed_backtest() -> lifecycle CON management; mark con skew calibrato sulle IV reali.

ESITO (2026-06-09):
  - cw reale a delta 0.18 = 0.106 (short ~9.4% OTM, NON 18%), max-loss/credito = 8.4x, eleggibilita' 65%.
  - hold-to-expiry @0.106: EV -1.0 crediti/trade, 7/9 anni NEGATIVI, max-loss 17.8% delle volte.
  - managed (skew): EV -0.02 cr/trade, win-rate 37% (delta-breach esce sul 62% dei trade a piccola perdita).
  - VERDETTO: NON edge robusto su ciclo completo. Il "+0.48%/mese" era artefatto di finestra calma
    (mag-giu 2026, no crash). Premium-selling a skew negativo: vince nei campioni calmi, restituisce
    tutto (o piu') nei crash. Tune "Profilo B" (vendere a 9.4% OTM) PEGGIORA la frequenza di max-loss.
    Coda CONCENTRATA col fade ETH di PythagorasGoal (stesso crash colpisce entrambi).

TODO APERTO (per nail-are l'EV managed esatto): la calibrazione non e' ancora perfetta
(mark mid+skew da cw 0.228 vs 0.106 reale -> sovrastima il credito ~2x). Manca: modellare
bid/ask reale incrociato sulle 2 gambe + griglia strike reale (entrambi nella chain) cosi'
l'entry cw scende a 0.106 e l'EV managed diventa esatto. Allora chiudere il sì/no definitivo.

    uv run python scripts/analysis/cerbero_bite_credit_spread.py
"""
from __future__ import annotations
import sys, math, collections
from pathlib import Path
import numpy as np, pandas as pd

PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.options_chain import OptionChain, load_market
from scripts.analysis.explore_lab import get_df
from scripts.analysis.option_overlay_lab import bs_put, _ncdf, dvol_for

SHORT_OTM, LONG_OTM, DTE = 0.094, 0.134, 17        # da chain reale (delta 0.18, width 4%)
CW_REAL = 0.106


def entry_economics():
    oc = OptionChain("ETH"); ch = oc.df
    mk = load_market("ETH")[["ts_ms", "spot"]].dropna().sort_values("ts_ms")
    p = ch[ch["option_type"] == "P"].copy()
    p = pd.merge_asof(p.sort_values("ts_ms"), mk, on="ts_ms", direction="backward")
    cand = p[(p["tenor_d"] >= 14) & (p["tenor_d"] <= 21)].dropna(subset=["delta", "bid", "ask", "strike", "spot"])
    rows = []
    for (ts, exp), g in cand.groupby(["timestamp", "expiry"]):
        spot = g["spot"].iloc[0]
        sc = g[(g["delta"] <= -0.12) & (g["delta"] >= -0.22)]
        if sc.empty: continue
        short = sc.iloc[(sc["delta"] + 0.18).abs().argmin()]
        Ks = short["strike"]; longc = g[g["strike"] < Ks]
        if longc.empty: continue
        longp = longc.iloc[(longc["strike"] - (Ks - spot * 0.04)).abs().argmin()]
        W = Ks - longp["strike"]
        if W <= 0: continue
        credit = short["bid"] - longp["ask"]
        def ok(o):
            sp = (o["ask"] - o["bid"]) / ((o["ask"] + o["bid"]) / 2) if (o["ask"] + o["bid"]) > 0 else 9
            return (o["open_interest"] or 0) >= 100 and sp <= 0.15 and o["bid"] > 0
        cw = credit / (W / spot)
        rows.append(dict(cw=cw, credit=credit, elig=ok(short) and ok(longp) and cw >= 0.08 and credit > 0,
                         short_otm=(spot - Ks) / spot, delta=short["delta"]))
    r = pd.DataFrame(rows)
    print(f"[ENTRY] {len(r)} spread | eleggibili {r['elig'].mean()*100:.0f}% | cw mediano {r['cw'].median():.3f} "
          f"| short OTM {r['short_otm'].median()*100:.1f}% | max-loss/credito {((1-r['cw'].median())/r['cw'].median()):.1f}x")


def tail_model_free():
    df = get_df("ETH", "1h"); c = df["close"].values; n = len(c)
    ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True); H = DTE * 24
    res = []
    for i in range(200, n - H - 1, 24 * 2):
        S0 = c[i]; Ks = S0 * (1 - SHORT_OTM); Kl = S0 * (1 - LONG_OTM); W = Ks - Kl
        Sx = c[i + H]; intr = min(max(Ks - Sx, 0.0), W); credit = CW_REAL * W
        res.append((ts.iloc[i].year, 1 - intr / credit, Sx < Kl))
    R = pd.DataFrame(res, columns=["y", "pnl", "maxloss"]); P = R["pnl"].values
    print(f"[TAIL model-free @cw0.106] win {(P>0).mean()*100:.0f}% | EV {P.mean():+.2f}cr | max-loss {R['maxloss'].mean()*100:.0f}% "
          f"| anni neg {(R.groupby('y')['pnl'].mean()<0).sum()}/{R['y'].nunique()}")


def _skew_fit():
    oc = OptionChain("ETH"); ch = oc.df
    mk = load_market("ETH")[["ts_ms", "spot"]].dropna().sort_values("ts_ms")
    p = ch[ch["option_type"] == "P"].copy()
    p = pd.merge_asof(p.sort_values("ts_ms"), mk, on="ts_ms", direction="backward")
    p = p.dropna(subset=["iv", "strike", "spot", "delta", "tenor_d"])
    p = p[(p["tenor_d"] >= 7) & (p["tenor_d"] <= 35) & (p["iv"] > 0)]
    p["dd"] = (p["delta"] + 0.5).abs()
    atm = p.sort_values("dd").groupby("timestamp")["iv"].first()
    p["atm_iv"] = p["timestamp"].map(atm); p = p.dropna(subset=["atm_iv"])
    p["k"] = np.log(p["strike"] / p["spot"]); p["ratio"] = p["iv"] / p["atm_iv"]
    p = p[(p["k"] > -0.35) & (p["k"] < 0.15) & (p["ratio"] > 0.5) & (p["ratio"] < 3)]
    coef, *_ = np.linalg.lstsq(np.c_[p["k"], p["k"]**2], p["ratio"] - 1.0, rcond=None)
    return coef  # a, b


def managed_backtest():
    a, b = _skew_fit()
    def ivol(S, K, atm):
        k = math.log(K / S); return max(atm * (1 + a * k + b * k * k), 0.05)
    def put_delta(S, K, T, sig):
        if T <= 0 or sig <= 0: return -1.0 if S < K else 0.0
        return _ncdf((math.log(S / K) + 0.5 * sig * sig * T) / (sig * math.sqrt(T))) - 1.0
    def mark(S, Ks, Kl, T, atm):
        return bs_put(S, Ks, T, ivol(S, Ks, atm)) - bs_put(S, Kl, T, ivol(S, Kl, atm))
    df = get_df("ETH", "1h"); c = df["close"].values; n = len(c)
    ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True); dvol = dvol_for(df, "ETH")
    H = DTE * 24; STEP = 6; cw = []; tr = []
    for i in range(200, n - H - 1, 24 * 2):
        S0 = c[i]; atm0 = dvol[i] if not np.isnan(dvol[i]) else 0.6
        Ks = S0 * (1 - SHORT_OTM); Kl = S0 * (1 - LONG_OTM); W = Ks - Kl
        credit = mark(S0, Ks, Kl, DTE / 365.0, atm0)
        if credit <= 0: continue
        cw.append(credit / W); pnl = why = None
        for k in range(STEP, H + 1, STEP):
            j = i + k; Trem = max((H - k) / (24 * 365.0), 1e-6); Sj = c[j]
            atmj = dvol[j] if not np.isnan(dvol[j]) else atm0; mk = mark(Sj, Ks, Kl, Trem, atmj)
            if mk <= 0.5 * credit: pnl, why = 1 - mk / credit, "PT"; break
            if mk >= 2.5 * credit: pnl, why = 1 - mk / credit, "stop"; break
            if put_delta(Sj, Ks, Trem, ivol(Sj, Ks, atmj)) <= -0.30: pnl, why = 1 - mk / credit, "delta"; break
            if atmj - atm0 >= 0.10: pnl, why = 1 - mk / credit, "vol"; break
            if k >= (DTE - 7) * 24: pnl, why = 1 - mk / credit, "time"; break
        if pnl is None:
            Sx = c[i + H]; intr = min(max(Ks - Sx, 0), W); pnl, why = 1 - intr / credit, "expiry"
        tr.append((ts.iloc[i].year, pnl, why))
    P = np.array([t[1] for t in tr])
    print(f"[MANAGED skew] cw@entry {np.median(cw):.3f} (vs 0.106 reale: sovrastima ~2x, EV vero <=) | "
          f"win {(P>0).mean()*100:.0f}% | EV {P.mean():+.3f}cr | worst {P.min():.1f} | "
          f"uscite {dict(collections.Counter(t[2] for t in tr))}")
    R = pd.DataFrame({"y": [t[0] for t in tr], "p": P})
    print(f"           2021+: EV {R[R.y>=2021]['p'].mean():+.3f}cr/trade")


if __name__ == "__main__":
    entry_economics()
    tail_model_free()
    managed_backtest()
