"""VERIFICA SLEEVE OPZIONI su QUOTE REALI Deribit — quanto Sharpe sopravvive a bid/ask + skew.

Lo sleeve income della strategia esterna `crypto_backtest` (vendita di put settimanali CSP su
BTC, delta 0.28) e' backtestato su prezzi MODELLATI: Black-Scholes prezzato con DVOL = IV ATM, e
si incassa il premio "fair" (mid). Due gap reali NON catturati:
  (1) BID/ASK: vendendo si incassa il BID, non il mid.
  (2) SKEW: una put OTM (delta 0.28) ha IV piu' alta della ATM (DVOL) -> il modello prezza la put
      con la vol sbagliata.

Questo script:
  PARTE 1 (rete, Deribit mainnet pubblico): scarica la catena REALE della scadenza ~settimanale,
    trova la put a delta ~0.28, e misura:
      - premio reale incassabile (BID, in USD)  vs  premio modellato (BS @ IV ATM)
      - skew: IV della put OTM (mark) vs IV ATM (mark)
      - spread: bid/mark
      - HAIRCUT netto  f = premio_bid_reale / premio_BS@ATM
  PARTE 2 (locale): ri-esegue lo sleeve CSP settimanale (dati + modulo del progetto esterno) con
    il premio moltiplicato per f -> Sharpe/CAGR/maxDD reali stimati, vs i modellati.

NB ONESTO: e' UNO SNAPSHOT (la catena di oggi). Lo spread si allarga nello stress; lo skew varia.
Va ripetuto nel tempo per robustezza. Ma misura direttamente i due gap col mercato vero.

    uv run python scripts/research/options_real_quote_check.py
"""
from __future__ import annotations

import sys
from pathlib import Path

import numpy as np
import pandas as pd

EXT = Path("/home/adriano/crypto_backtest")
sys.path.insert(0, str(EXT))

PUT_DELTA = 0.28
CYCLE_DAYS = 7
ANN = 365


def fetch_real_chain():
    import ccxt
    ex = ccxt.deribit({"enableRateLimit": True})
    ex.load_markets()
    puts = [m for m in ex.markets.values()
            if m.get("option") and m["base"] == "BTC" and m["optionType"] == "put"]
    calls = [m for m in ex.markets.values()
             if m.get("option") and m["base"] == "BTC" and m["optionType"] == "call"]
    # expiries -> pick the one closest to CYCLE_DAYS days out
    now = pd.Timestamp.utcnow().tz_localize(None)
    def exp_dt(m):
        return pd.to_datetime(m["symbol"].split("-")[1], format="%y%m%d")
    exps = sorted(set(exp_dt(m) for m in puts))
    target = now + pd.Timedelta(days=CYCLE_DAYS)
    expiry = min(exps, key=lambda e: abs((e - target).days))
    dte = (expiry - now).days + (expiry - now).seconds / 86400
    chain_puts = [m for m in puts if exp_dt(m) == expiry]
    chain_calls = [m for m in calls if exp_dt(m) == expiry]
    print(f"  scadenza scelta: {expiry.date()}  (DTE ~{dte:.1f}g, target {CYCLE_DAYS}g)  "
          f"strikes put: {len(chain_puts)}")

    def tick(m):
        try:
            t = ex.fetch_ticker(m["symbol"])
            i = t["info"]
            g = i.get("greeks") or {}
            return dict(symbol=m["symbol"], strike=float(m["strike"]),
                        delta=float(g.get("delta", "nan")), mark_iv=float(i.get("mark_iv", "nan")),
                        bid=float(i.get("best_bid_price") or 0), ask=float(i.get("best_ask_price") or 0),
                        mark=float(i.get("mark_price") or 0),
                        S=float(i.get("underlying_price") or i.get("index_price") or 0))
        except Exception:
            return None

    rows = [r for r in (tick(m) for m in chain_puts) if r and np.isfinite(r["delta"])]
    callrows = [r for r in (tick(m) for m in chain_calls) if r and np.isfinite(r["delta"])]
    return expiry, dte, pd.DataFrame(rows), pd.DataFrame(callrows)


def bs_put(S, K, T, sigma):
    from scipy.stats import norm
    if T <= 0 or sigma <= 0:
        return max(0.0, K - S)
    d1 = (np.log(S / K) + 0.5 * sigma ** 2 * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    return K * norm.cdf(-d2) - S * norm.cdf(-d1)


def measure_haircut(dte, puts, calls):
    S = puts["S"].iloc[0]
    T = dte / ANN
    # ATM IV: option with |delta| closest to 0.5 (use calls+puts mark_iv near ATM)
    allo = pd.concat([puts.assign(typ="P"), calls.assign(typ="C")], ignore_index=True)
    atm = allo.iloc[(allo["delta"].abs() - 0.5).abs().argsort()[:4]]
    atm_iv = atm["mark_iv"].mean() / 100.0
    # delta-0.28 put (delta negative)
    p = puts.iloc[(puts["delta"] - (-PUT_DELTA)).abs().argsort()[:1]].iloc[0]
    K = p["strike"]
    put_iv = p["mark_iv"] / 100.0
    # premiums in USD (Deribit option price is in BTC)
    bid_usd = p["bid"] * S
    mark_usd = p["mark"] * S
    ask_usd = p["ask"] * S
    bs_atm_usd = bs_put(S, K, T, atm_iv)        # cio' che il backtest assume (DVOL=ATM, incassa mid)
    bs_skew_usd = bs_put(S, K, T, put_iv)        # BS alla vol REALE della put (isola lo skew)

    print("\n  --- MISURA SU QUOTE REALI (snapshot) ---")
    print(f"  underlying S = {S:,.0f}  strike(delta~-0.28) K = {K:,.0f}  ({(1-K/S)*100:.1f}% OTM)  delta {p['delta']:.3f}")
    print(f"  IV ATM (DVOL-equivalente) = {atm_iv*100:.1f}%   IV put OTM (mark) = {put_iv*100:.1f}%   "
          f"skew +{(put_iv-atm_iv)*100:.1f} pt")
    print(f"  premio put (USD):  BID {bid_usd:,.1f}   mark {mark_usd:,.1f}   ask {ask_usd:,.1f}")
    print(f"  spread bid/mark = {(p['bid']/p['mark']) if p['mark']>0 else float('nan'):.3f}   "
          f"(ask-bid)/mark = {((p['ask']-p['bid'])/p['mark']) if p['mark']>0 else float('nan'):.3f}")
    print(f"  modellato dal backtest  BS@IV-ATM = {bs_atm_usd:,.1f} USD   (BS@IV-put-reale = {bs_skew_usd:,.1f})")
    f_bid = bid_usd / bs_atm_usd if bs_atm_usd > 0 else float("nan")
    f_mark = mark_usd / bs_atm_usd if bs_atm_usd > 0 else float("nan")
    print(f"  HAIRCUT premio:  reale(BID)/modello = {f_bid:.3f}   |  mark/modello = {f_mark:.3f}")
    print(f"  -> lo skew ALZA il premio lordo (+{(bs_skew_usd/bs_atm_usd-1)*100:.0f}% vs ATM), ma il "
          f"BID/ask lo riporta a {f_bid*100:.0f}% del modello.")
    return f_bid


def csp_sleeve_haircut(f):
    """Ri-esegue lo sleeve CSP settimanale (dati+modulo esterni) con premio * f."""
    import options_deribit as od
    px = pd.read_csv(EXT / "data/BTCUSDT.csv", parse_dates=["date"]).set_index("date")["close"]
    dvol = pd.read_csv(EXT / "data/DVOL_BTC.csv", parse_dates=["date"]).set_index("date")["close"]
    iv = od.build_iv(px, "BTC", dvol)
    d0 = dvol.index[0]
    px, iv = px[px.index >= d0], iv[iv.index >= d0]

    def sim(prem_mult, m=0.63):
        idx = px.index
        locs = list(range(0, len(idx) - CYCLE_DAYS, CYCLE_DAYS))
        T = CYCLE_DAYS / ANN
        rows = []
        for i in locs:
            S0, S1, sig = px.iloc[i], px.iloc[i + CYCLE_DAYS], iv.iloc[i]
            if not (np.isfinite(S0) and np.isfinite(S1) and np.isfinite(sig)):
                continue
            Kp = od.strike_for_delta(S0, T, sig, PUT_DELTA, call=False)
            pp = od.bs_price(S0, Kp, T, sig, call=False) * prem_mult   # <-- haircut sul premio
            fee = od.option_fee(S0, pp) + (od.SETTLE_FEE * S0 if S1 < Kp else 0)
            pnl = pp - max(Kp - S1, 0.0) - fee
            rows.append((idx[i + CYCLE_DAYS], m * pnl / S0))
        s = pd.Series({d: r for d, r in rows}).sort_index()
        return s

    def met(s, name):
        eq = (1 + s).cumprod()
        cpy = ANN / CYCLE_DAYS
        yrs = len(s) / cpy
        cagr = eq.iloc[-1] ** (1 / yrs) - 1 if eq.iloc[-1] > 0 else -1
        sh = s.mean() / s.std() * np.sqrt(cpy)
        dd = (eq / eq.cummax() - 1).min()
        print(f"  {name:<34s} CAGR {cagr*100:>+6.1f}%  Sharpe {sh:>5.2f}  maxDD {dd*100:>6.1f}%  win {(s>0).mean()*100:>3.0f}%")
        return sh

    print("\n  --- RI-ESECUZIONE SLEEVE CSP con HAIRCUT REALE (m=0.63, hold-to-expiry) ---")
    print(f"  finestra {px.index[0].date()} -> {px.index[-1].date()}  (DVOL reale)")
    sh_model = met(sim(1.00), "modello (premio pieno, BS@DVOL)")
    sh_real = met(sim(f), f"reale stimato (premio x{f:.2f} = BID)")
    # sensitivity
    for ff in (0.85, 0.70, 0.55):
        met(sim(ff), f"sensitivity premio x{ff:.2f}")
    print(f"\n  => con haircut reale f={f:.2f}: Sharpe sleeve {sh_model:.2f} -> {sh_real:.2f}")
    return sh_model, sh_real


def main():
    print("=" * 92)
    print("# VERIFICA SLEEVE OPZIONI su QUOTE REALI DERIBIT — quanto Sharpe sopravvive")
    print("=" * 92)
    try:
        expiry, dte, puts, calls = fetch_real_chain()
        f = measure_haircut(dte, puts, calls)
    except Exception as e:
        print(f"  [rete] impossibile scaricare la catena reale ({type(e).__name__}: {e})")
        print("  uso haircut di letteratura f=0.70 (spread+skew tipici su put OTM settimanali)")
        f = 0.70
    f = float(np.clip(f, 0.3, 1.2))
    csp_sleeve_haircut(f)
    print("\n  CAVEAT: snapshot singolo; spread peggiora nello stress; ripetere nel tempo + testnet.")


if __name__ == "__main__":
    main()
