14522262e6
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
81 lines
3.4 KiB
Python
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)
|