"""GAMMA SCALPING (long-vol) — "scalping BTC/ETH con copertura in opzioni", valutato onestamente.

Interpretazione rigorosa di "fare scalping con copertura in opzioni" = GAMMA SCALPING:
compri un'opzione (la COPERTURA = long gamma), delta-hedgi il sottostante a cadenza fissa
(lo SCALP), e il P&L netto e' ~ dollar-gamma * (vol realizzata^2 - vol implicita^2). E' lo
SPECCHIO ESATTO del VRP01 (short-vol): VRP01 incassa IV-RV (positivo in media), il gamma
scalping incassa RV-IV (negativo in media, ma CONVESSO -> paga nei crash).

Modello (mirror della struttura VRP per comparabilita'):
  - long STRADDLE ATM, tenor settimanale (7g), strike = spot all'ingresso.
  - IV = DVOL Deribit (data/raw/dvol_*.parquet) — la stessa fonte IV del VRP.
  - delta-hedge GIORNALIERO sui prezzi certificati 1d (BTC/ETH).
  - P&L delta-hedged classico per step: DG_t * [(dS/S)^2 - sigma^2*dt],
    DG_t = dollar-gamma dello straddle = phi(d1)*S/(sigma*sqrt(tau)).
  - fee opzioni Deribit cap 12.5% del premio (come VRP) + fee perp sull'hedge turnover.
  - return-on-notional (pnl/S0), poi VOL-TARGET 20% annuo per apples-to-apples con gli altri sleeve.

Varianti testate:
  NAKED         -> sempre long gamma (baseline: deve perdere il premio in media).
  CHEAP-GATED   -> entra solo quando IV-rank < gate (compri vol a sconto = specchio del gate VRP).
  RICH-SKIP     -> entra sempre tranne quando IV-rank > skip (evita di pagare vol carissima).

Output: metriche standalone + per-anno + SCORING MARGINALE vs TP01 (corr, blend uplift, is_hedge,
has_insample_edge) + il muro di ESEGUIBILITA' a $600 (min size opzioni Deribit).

    uv run python scripts/research/options_gamma_scalp.py

ONESTA': premio MODELLATO su DVOL ATM (skew non esplicito) — stesso caveat del VRP. Long-vol da
modello: meno pericoloso dello short-vol (loss capped al premio), ma resta un LEAD da modello.
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research" / "alt"))

import numpy as np
import pandas as pd
from scipy.stats import norm

from src.data.downloader import load_data
from src.strategies.trend_portfolio import resample_1d
from altlib import marginal_vs_tp01  # type: ignore

RAW = PROJECT_ROOT / "data" / "raw"
SQ365 = np.sqrt(365.25)
DT = 1.0 / 365.25

# fee model (mirror VRP): opzioni Deribit cap 12.5% del premio; perp taker 0.05%/lato sull'hedge.
OPT_FEE_FRAC = 0.125
HEDGE_FEE_SIDE = 0.0005
TENOR_D = 7


def _bs_straddle(S, K, T, sig):
    """Premio BS di uno straddle ATM (call+put), r=0."""
    if T <= 0 or sig <= 0:
        return abs(S - K)
    d1 = (np.log(S / K) + 0.5 * sig ** 2 * T) / (sig * np.sqrt(T))
    d2 = d1 - sig * np.sqrt(T)
    call = S * norm.cdf(d1) - K * norm.cdf(d2)
    put = K * norm.cdf(-d2) - S * norm.cdf(-d1)
    return call + put


def _dvol(asset: str) -> pd.Series:
    dv = pd.read_parquet(RAW / f"dvol_{asset.lower()}.parquet")
    return pd.Series(dv["close"].values.astype(float),
                     index=pd.to_datetime(dv["timestamp"], unit="ms", utc=True)) / 100.0


def _load(asset: str):
    df = resample_1d(load_data(asset, "1h"))
    s = pd.Series(df["close"].values.astype(float), index=pd.to_datetime(df["datetime"]))
    if s.index.tz is None:
        s.index = s.index.tz_localize("UTC")
    J = pd.concat({"px": s, "dvol": _dvol(asset)}, axis=1, join="inner").sort_index().dropna()
    return J["px"].values, J["dvol"].values / 1.0, J.index


def _load_hourly(asset: str):
    df = load_data(asset, "1h")
    s = pd.Series(df["close"].values.astype(float),
                  index=pd.to_datetime(df["timestamp"], unit="ms", utc=True)).sort_index()
    return s


def rv_iv_diagnostic(asset: str):
    """Il CUORE strutturale: vol realizzata (a vari campionamenti) vs vol implicita (DVOL).
    Se RV < IV in media -> il long gamma PERDE gross, prima di ogni fee. E' il VRP, di segno opposto."""
    px, dvf, idx = _load(asset)
    iv = float(np.mean(dvf))
    rdaily = np.diff(np.log(px))
    rv_daily = float(np.std(rdaily) * SQ365)
    h = _load_hourly(asset)
    rhour = np.diff(np.log(h.values))
    rv_hour = float(np.std(rhour) * np.sqrt(365.25 * 24))
    return dict(asset=asset, iv=iv, rv_daily=rv_daily, rv_hour=rv_hour,
                spread_daily=iv - rv_daily, spread_hour=iv - rv_hour)


def gamma_scalp_hourly(asset: str, mode: str = "naked",
                       gate_cheap: float = 0.30) -> pd.Series:
    """Gamma scalp a rehedge ORARIO (la 'vera' frequenza di scalping): cattura la RV intraday,
    ma paga 24x la fee di hedge. Tenor 7g = 168 step orari. IV costante nel giorno (DVOL)."""
    h = _load_hourly(asset)
    dv = _dvol(asset).reindex(h.index.normalize(), method="ffill")
    dv.index = h.index
    J = pd.concat({"px": h, "dvol": dv}, axis=1).dropna()
    px = J["px"].values; dvf = J["dvol"].values; idx = J.index
    n = len(px); STEPS = TENOR_D * 24; dt = 1.0 / (365.25 * 24)
    # IV-rank causale su DVOL giornaliero per il gate
    daily_iv = _dvol(asset)
    rets = {}
    i = 24 * 60
    while i + STEPS < n:
        S0 = px[i]; sig = dvf[i]; K = S0
        if mode == "cheap":
            day = idx[i].normalize()
            hist = daily_iv[daily_iv.index < day]
            if len(hist) >= 60 and float((hist < sig).mean()) > gate_cheap:
                rets[idx[i + STEPS]] = 0.0; i += STEPS; continue
        T = TENOR_D / 365.25
        premium = _bs_straddle(S0, K, T, sig)
        gamma_pnl = 0.0; hedge_fee = 0.0; prev_delta = None
        for t in range(1, STEPS + 1):
            tau = max((STEPS - (t - 1)) * dt, dt)
            Sp = px[i + t - 1]; Sn = px[i + t]
            r = Sn / Sp - 1.0
            d1 = (np.log(Sp / K) + 0.5 * sig ** 2 * tau) / (sig * np.sqrt(tau))
            dollar_gamma = norm.pdf(d1) * Sp / (sig * np.sqrt(tau))
            gamma_pnl += dollar_gamma * (r * r - sig * sig * dt)
            delta = 2.0 * norm.cdf(d1) - 1.0
            if prev_delta is not None:
                hedge_fee += HEDGE_FEE_SIDE * abs(delta - prev_delta) * Sp
            prev_delta = delta
        pnl = gamma_pnl - OPT_FEE_FRAC * premium - hedge_fee
        rets[idx[i + STEPS]] = pnl / S0
        i += STEPS
    out = pd.Series(rets)
    out.index = out.index.normalize()
    return out


def gamma_scalp_asset(asset: str, mode: str = "naked",
                      gate_cheap: float = 0.30, skip_rich: float = 0.90) -> pd.Series:
    """Rendimenti settimanali (return-on-notional) di una catena di long-straddle delta-hedged,
    lumpati sul giorno di scadenza. Causale: IV/strike/gate da dati <= entry; payoff sul path."""
    px, dvf, idx = _load(asset)
    n = len(px)
    rets = {}
    i = 60
    while i + TENOR_D < n:
        S0 = px[i]; sig = dvf[i]; K = S0
        skip = False
        if i >= 60 and mode in ("cheap", "rich"):
            ivr = float((dvf[:i] < dvf[i]).mean())          # IV-rank espandente causale
            if mode == "cheap" and ivr > gate_cheap:        # compra gamma solo se vol a SCONTO
                skip = True
            if mode == "rich" and ivr > skip_rich:          # evita di pagare vol carissima
                skip = True
        if skip:
            rets[idx[i + TENOR_D]] = 0.0; i += TENOR_D; continue
        T = TENOR_D / 365.25
        premium = _bs_straddle(S0, K, T, sig)
        gamma_pnl = 0.0; hedge_fee = 0.0; prev_delta = None
        for t in range(1, TENOR_D + 1):
            tau = (TENOR_D - (t - 1)) / 365.25               # tempo residuo a inizio step
            Sp = px[i + t - 1]; Sn = px[i + t]
            r = Sn / Sp - 1.0
            d1 = (np.log(Sp / K) + 0.5 * sig ** 2 * tau) / (sig * np.sqrt(tau))
            dollar_gamma = norm.pdf(d1) * Sp / (sig * np.sqrt(tau))   # DG straddle = phi(d1)*S/(sig*sqrt(tau))
            gamma_pnl += dollar_gamma * (r * r - sig * sig * DT)
            delta = 2.0 * norm.cdf(d1) - 1.0                 # delta straddle ATM
            if prev_delta is not None:
                hedge_fee += HEDGE_FEE_SIDE * abs(delta - prev_delta) * Sp
            prev_delta = delta
        opt_fee = OPT_FEE_FRAC * premium
        pnl = gamma_pnl - opt_fee - hedge_fee
        rets[idx[i + TENOR_D]] = pnl / S0                    # return-on-notional
        i += TENOR_D
    return pd.Series(rets)


def to_daily_voltgt(weekly_btc: pd.Series, weekly_eth: pd.Series, tgt_vol: float = 0.20) -> pd.Series:
    """Book 50/50 BTC+ETH su griglia giornaliera, poi scalato a tgt_vol annuo (apples-to-apples
    con gli altri sleeve, tutti vol-targeted ~20%)."""
    wk = pd.concat({"B": weekly_btc, "E": weekly_eth}, axis=1, join="inner").mean(axis=1).sort_index()
    if wk.empty:
        return wk
    days = pd.date_range(wk.index.min().normalize(), wk.index.max().normalize(), freq="1D", tz="UTC")
    daily = pd.Series(0.0, index=days)
    daily.loc[wk.index.normalize()] = wk.values
    sd = daily.std()
    if sd > 0:
        daily = daily * (tgt_vol / (sd * SQ365))
    return daily


def _metrics(daily: pd.Series) -> dict:
    r = daily.values
    sh = float(np.mean(r) / np.std(r) * SQ365) if np.std(r) > 0 else 0.0
    eq = np.cumprod(1.0 + np.clip(r, -0.99, None))
    pk = np.maximum.accumulate(eq)
    dd = float(np.max((pk - eq) / pk))
    yrs = (daily.index[-1] - daily.index[0]).days / 365.25
    cagr = eq[-1] ** (1 / yrs) - 1 if yrs > 0 and eq[-1] > 0 else -1.0
    s = pd.Series(eq, index=daily.index)
    yearly = {}
    for y, g in s.groupby(s.index.year):
        if len(g) > 1:
            v = g.values; p = np.maximum.accumulate(v)
            yearly[int(y)] = (float(g.iloc[-1] / g.iloc[0] - 1), float(np.max((p - v) / p)))
    return dict(sharpe=sh, dd=dd, cagr=cagr, yearly=yearly)


def main():
    print("=" * 92)
    print("  GAMMA SCALPING (long-vol) — scalping BTC/ETH con copertura in opzioni")
    print("  Modello: long straddle ATM 7g, delta-hedge 1d, P&L = DG*(RV^2 - IV^2). Mirror del VRP01.")
    print("=" * 92)

    print("\n  DIAGNOSTICA STRUTTURALE — vol implicita (DVOL) vs realizzata (il segno dell'edge):")
    for a in ("BTC", "ETH"):
        d = rv_iv_diagnostic(a)
        print(f"    {a}: IV {d['iv']*100:5.1f}%  |  RV_1d {d['rv_daily']*100:5.1f}% "
              f"(IV-RV {d['spread_daily']*100:+.1f}pp)  RV_1h {d['rv_hour']*100:5.1f}% "
              f"(IV-RV {d['spread_hour']*100:+.1f}pp)")
    print("    -> IV-RV > 0 = il mercato PAGA per essere short-vol (VRP). Il long gamma e' su questo lato"
          ", al ROVESCIO: paga il premio. RV_1h>RV_1d -> rehedge orario cattura piu' RV (test sotto).")

    variants = {
        "NAKED (sempre long gamma)":            dict(mode="naked"),
        "CHEAP-GATED (IV-rank<0.30 = vol scont)": dict(mode="cheap", gate_cheap=0.30),
        "CHEAP-GATED (IV-rank<0.50)":           dict(mode="cheap", gate_cheap=0.50),
        "RICH-SKIP (no entry se IV-rank>0.90)":  dict(mode="rich", skip_rich=0.90),
    }

    series = {}
    for label, kw in variants.items():
        wB = gamma_scalp_asset("BTC", **kw)
        wE = gamma_scalp_asset("ETH", **kw)
        daily = to_daily_voltgt(wB, wE)
        series[label] = daily
        m = _metrics(daily)
        ntr = int((wB != 0).sum() + (wE != 0).sum())
        print(f"\n--- {label} ---")
        print(f"  standalone (vol-tgt 20%): Sharpe {m['sharpe']:+.2f}  CAGR {m['cagr']*100:+.1f}%  "
              f"maxDD {m['dd']*100:.1f}%  trade {ntr}")
        ys = "  ".join(f"{y}:{p*100:+.0f}%" for y, (p, d) in sorted(m['yearly'].items()))
        print(f"  per-anno PnL: {ys}")

    print("\n" + "=" * 92)
    print("  REHEDGE ORARIO (la 'vera' frequenza di scalping: cattura RV intraday, paga 24x hedge fee)")
    print("=" * 92)
    for label, kw in (("NAKED orario", dict(mode="naked")),
                      ("CHEAP-GATED orario (IV-rank<0.30)", dict(mode="cheap", gate_cheap=0.30))):
        wB = gamma_scalp_hourly("BTC", **kw); wE = gamma_scalp_hourly("ETH", **kw)
        daily = to_daily_voltgt(wB, wE)
        m = _metrics(daily)
        ys = "  ".join(f"{y}:{p*100:+.0f}%" for y, (p, d) in sorted(m['yearly'].items()))
        print(f"\n--- {label} ---")
        print(f"  Sharpe {m['sharpe']:+.2f}  CAGR {m['cagr']*100:+.1f}%  maxDD {m['dd']*100:.1f}%")
        print(f"  per-anno PnL: {ys}")

    print("\n" + "=" * 92)
    print("  SCORING MARGINALE vs TP01 (lo standard del progetto: un nuovo sleeve si giudica QUI)")
    print("=" * 92)
    for label, daily in series.items():
        if daily.std() == 0:
            print(f"\n[{label}] flat — skip"); continue
        m = marginal_vs_tp01(daily)
        b25 = m.get("blends", {}).get("w25", {})
        print(f"\n[{label}]")
        print(f"  verdict={m.get('marginal_verdict')}  corr->TP01 full={m.get('corr_full')} "
              f"hold={m.get('corr_hold')}  is_hedge={m.get('is_hedge')}  "
              f"has_insample_edge={m.get('has_insample_edge')} (cand IS Sharpe {m.get('cand_insample_sharpe')})")
        print(f"  cand Sharpe full={m.get('cand_full_sharpe')} hold={m.get('cand_hold_sharpe')}  |  "
              f"blend25 full {b25.get('full')} (upl {b25.get('uplift_full')})  "
              f"hold {b25.get('hold')} (upl {b25.get('uplift_hold')})  DD {b25.get('dd')}")
        print(f"  hedge-check: uplift TP01-up {m.get('uplift_tp01_up')} / TP01-down {m.get('uplift_tp01_down')}  "
              f"yearly-corr {m.get('hedge_yearly_corr')}")

    print("\n" + "=" * 92)
    print("  ESEGUIBILITA' a ~$600 (Deribit options min size)")
    print("=" * 92)
    pxB = _load("BTC")[0][-1]; pxE = _load("ETH")[0][-1]
    for a, p, csz, minc in (("BTC", pxB, 1.0, 0.1), ("ETH", pxE, 1.0, 0.1)):
        notion = p * csz * minc
        print(f"  {a}: spot ${p:,.0f} | contratto {csz} {a}, min {minc} {a} -> notional minimo "
              f"${notion:,.0f}  ({'OK' if notion < 600 else 'OLTRE i $600 -> NON eseguibile'})")


if __name__ == "__main__":
    main()
