diff --git a/CLAUDE.md b/CLAUDE.md index 88ce9e3..d7e763e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -338,11 +338,17 @@ loader + `skew_curve()` / `premium_levels()` (aggregati ROBUSTI) + `quote()` cau **Caveat granularità:** cerbero-bite snapshotta una **fetta rotante** (~1 scadenza per ciclo) → ottimo per skew/premi **aggregati**, limitato per il pricing **per-trade** preciso (usare la finestra di staleness). **Pannello regime `market_snapshots`** (anch'esso importato → `data/options/market_snapshots.parquet`, -loader `options_chain.load_market(asset)`): feature REALI pre-calcolate dal 2026-03-26, ~15min, 99-100% -popolate — **spot, dvol, realized_vol_30d, iv_minus_rv (VRP), funding perp/cross, `dealer_net_gamma` -(net-GEX dealer!), `gamma_flip_level`, oi_delta_pct_4h, liquidation_long/short_risk**. È il segnale -ARGO/GEX che il progetto dichiarò non-backtestabile: ora accumula, reale e allineato (usabile come -pannello in regime_lab; lo `spot` elimina il proxy ATM). **Numeri reali misurati (ETH):** skew put 10% OTM = **×1.1** (liquido, spread ~7%, NON ×1.6 +loader `options_chain.load_market(asset)`; merge causale su prezzo via `options_chain.attach_market(df, asset)`): +feature REALI pre-calcolate — **spot, dvol, realized_vol_30d, iv_minus_rv (VRP), funding perp/cross, +`dealer_net_gamma` (net-GEX dealer!), `gamma_flip_level`, oi_delta_pct_4h, liquidation_long/short_risk**. +È il segnale ARGO/GEX che il progetto dichiarò non-backtestabile: ora accumula, reale e allineato (lo +`spot` elimina il proxy ATM). **Copertura reale:** spot/dvol dal 2026-03-26 (sparse/daily fino a fine +aprile), **net-GEX denso orario solo da ~2026-05-01** → ~5-6 settimane, **un singolo regime calmo**: +analisi esplorativa OK ma NIENTE edge validabile ora (i prior GEX/VRP non si replicano su 9 sett.; +`liquidation_risk` costante 'low'). Valore FORWARD: rivalutare quando il pannello attraversa +gamma-flip/liquidazioni/crash con held-out. **NB look-ahead:** usare `attach_market` (merge_asof +causale, NaN prima della copertura), MAI `astype(int64)` su un timestamp datetime (darebbe ns → +match all'ultimo snapshot = leak). **Numeri reali misurati (ETH):** skew put 10% OTM = **×1.1** (liquido, spread ~7%, NON ×1.6 come sembrava da un singolo snapshot a 1g, illiquido); premio reale put 10% OTM ≈ **1.0%/mese** (0.65%/sett), catastrofe 15%+ OTM ≈ 0.33%/mese. **Vincolo strutturale:** gli strike **10% OTM a 24h NON esistono** (Deribit lista solo near-ATM sui tenor corti) → un overlay **per-trade a 24h è infattibile**; l'unica diff --git a/scripts/analysis/options_chain.py b/scripts/analysis/options_chain.py index ad0557d..02850ae 100644 --- a/scripts/analysis/options_chain.py +++ b/scripts/analysis/options_chain.py @@ -68,6 +68,30 @@ def load_market(asset: str | None = None) -> pd.DataFrame: return df.sort_values("ts_ms").reset_index(drop=True) +def attach_market(price_df: pd.DataFrame, asset: str, cols: list[str] | None = None) -> pd.DataFrame: + """merge_asof CAUSALE: ogni barra di price_df (serve colonna 'timestamp' in ms) riceve + l'ultimo market_snapshot con ts <= barra. Pannello pronto per regime_lab/ricerca: + spot, dvol, realized_vol_30d, iv_minus_rv (VRP), funding perp/cross, dealer_net_gamma + (net-GEX), gamma_flip_level, oi_delta_pct_4h, liquidation_long/short_risk. + Ritorna una copia con le colonne pannello (NaN dove non c'e' ancora storia: dal 2026-03-26).""" + m = load_market(asset) + cols = cols or (_MKT_NUM + ["liquidation_long_risk", "liquidation_short_risk"]) + keep = [c for c in cols if c in m.columns] + base = price_df.copy() + # chiave di merge = datetime tz-aware (robusto a timestamp int-ms o datetime; NIENTE astype int64 + # su datetime -> darebbe nanosecondi e matcherebbe tutto all'ultimo snapshot = LOOK-AHEAD). + if np.issubdtype(base["timestamp"].dtype, np.integer): + base["_k"] = pd.to_datetime(base["timestamp"], unit="ms", utc=True) + else: + base["_k"] = pd.to_datetime(base["timestamp"], utc=True) + base["_k"] = base["_k"].astype("datetime64[ns, UTC]") + mk = m[["ts"] + keep].rename(columns={"ts": "_k"}) + mk["_k"] = mk["_k"].astype("datetime64[ns, UTC]") + out = pd.merge_asof(base.sort_values("_k"), mk.sort_values("_k"), + on="_k", direction="backward") + return out.drop(columns="_k") + + class OptionChain: def __init__(self, asset: str): self.asset = asset