"""Calibra una superficie premi REALE dalla catena cerbero-bite -> data/games/opt_calib_*.json. Per ETH e BTC, dalla chain reale (OptionChain): premio mediano (ask, %spot), spread bid/ask mediano, e IV mediana per (moneyness OTM x tenor). Piu' DVOL medio della finestra (per scalare i premi sulla storia). + gate liquidita': max OTM con bid>0 frequente. Cosi' il motore del gioco prezza con NUMERI REALI invece del Black-Scholes sintetico. uv run python -m scripts.games.opt_calibrate """ from __future__ import annotations import json 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)) from scripts.analysis.options_chain import OptionChain OUT = PROJECT_ROOT / "data" / "games" # griglie: OTM firmato (put<0, call>0) e tenor in giorni OTM_GRID = [-0.25, -0.20, -0.15, -0.10, -0.07, -0.05, -0.03, 0.0, 0.03, 0.05, 0.07, 0.10, 0.15, 0.20, 0.25] TEN_GRID = [7, 14, 21, 30, 45] def calibrate(asset: str): oc = OptionChain(asset) d = oc.df.copy() spot = oc._spot_proxy() d["spot"] = d["timestamp"].map(spot) d = d.dropna(subset=["spot", "ask", "bid", "iv"]) d = d[d["ask"] > 0] d["otm"] = d["strike"] / d["spot"] - 1.0 # firmato: <0 put OTM, >0 call OTM d["prem_pct"] = d["ask"] * 100.0 # ask in coin -> %notional d["spread"] = (d["ask"] - d["bid"]) / ((d["ask"] + d["bid"]) / 2) d["sellable"] = (d["bid"] > 0).astype(float) # superficie: per ciascun (tipo, otm_bin, tenor_bin) -> mediane surf = {"P": {}, "C": {}} for typ in ("P", "C"): dt = d[d["option_type"] == typ] for ten in TEN_GRID: tlo, thi = ten * 0.6, ten * 1.6 dtt = dt[(dt["tenor_d"] >= tlo) & (dt["tenor_d"] <= thi)] for otm in OTM_GRID: # banda moneyness +-1.5% attorno al target band = dtt[(dtt["otm"] >= otm - 0.02) & (dtt["otm"] <= otm + 0.02)] if len(band) < 5: continue surf[typ][f"{otm:+.2f}|{ten}"] = dict( prem=round(float(band["prem_pct"].median()), 4), spread=round(float(band["spread"].median()), 4), iv=round(float(band["iv"].median()), 4), sellable=round(float(band["sellable"].mean()), 3), n=int(len(band))) dvol_avg = float(np.nanmedian(d["iv"][d["otm"].abs() < 0.03])) # ~ATM IV medio # gate liquidita': OTM piu' profondo (put) con bid>0 nel >=50% dei casi puts = d[d["option_type"] == "P"] deep = puts[puts["otm"] <= -0.10] out = {"asset": asset, "dvol_chain": round(dvol_avg, 4), "surface": surf, "otm_grid": OTM_GRID, "ten_grid": TEN_GRID, "window": [str(oc.df["ts"].min())[:10], str(oc.df["ts"].max())[:10]]} (OUT / f"opt_calib_{asset.lower()}.json").write_text(json.dumps(out)) npts = len(surf["P"]) + len(surf["C"]) print(f"{asset}: {npts} punti superficie | ATM IV ~{dvol_avg:.2f} | finestra {out['window']}") # stampa qualche premio reale put per sanity for key in ["-0.05|14", "-0.10|14", "-0.15|30", "-0.20|45"]: v = surf["P"].get(key) if v: print(f" put {key:>9}: prem {v['prem']:.2f}% spread {v['spread']*100:.0f}% " f"iv {v['iv']:.0f}% sellable {v['sellable']*100:.0f}% (n={v['n']})") if __name__ == "__main__": for a in ("BTC", "ETH"): calibrate(a)