feat(options): attach_market() panel helper (regime panel, causal, no look-ahead)

attach_market(df, asset): merge_asof causale del pannello market_snapshots
(spot/VRP/funding/net-GEX/gamma-flip/liquidation) sul prezzo, pronto per
regime_lab. Fix look-ahead: merge su datetime tz-aware (ns-aligned), MAI
astype(int64) su datetime (darebbe ns -> match all'ultimo snapshot = leak).
Copertura reale documentata: net-GEX denso solo da ~2026-05-01 (~5-6 sett,
singolo regime) -> infrastruttura pronta, edge validabile solo forward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-09 07:24:44 +00:00
parent df8bfbceaa
commit c997bce00e
2 changed files with 35 additions and 5 deletions
+24
View File
@@ -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