Files
PythagorasGoal/scripts/games/opt_calibrate.py
T
Adriano Dal Pastro 8d69a0cef5 feat(games): sessioni 2-3 Blind Traders (opzioni/session/grid) + gate PORT06 e tooling reset
- Gioco GRID TRADERS (sessione 3, regola STRATEGIA_GRIGLIA.md): grid_engine
  (backtest causale fee-aware della griglia geometrica), grid_brief (digest
  anonimo per dimensionare la griglia), grid_arena (torneo 100 agenti);
  diario docs/diary/2026-06-10-grid-traders-game3.md
- Gioco OPZIONI: options_engine (BS + skew fittato + DVOL storica),
  options_arena, opt_calibrate (superficie premi REALE da cerbero-bite)
- Gioco SESSION: session_engine/session_arena (pattern orari intraday)
- arena: vincolo GAME_NO_LIVE=1 (vieta pairs e fade zscore/breakout/momentum
  gia' live, coercizione a trend/ma_cross) + normalize del candidato PRIMA
  della valutazione nel hill-climb
- Gate: grid_game_gate (griglia ETH vincitrice vs PORT06, mark-to-market),
  pairs30m_gate (ETH/BTC 30m ridondante col 15m gia' deployato?)
- reset_flatten: flatten one-shot del conto testnet per il reset portafoglio
- .gitignore: data/portfolio_paper_stats/ (stato runtime sleeve paper-only)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 09:49:17 +00:00

81 lines
3.4 KiB
Python

"""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)