"""CRYPTO x MERCATI IB — correlazioni e ANTICIPAZIONI (lead-lag). Obiettivo: la crypto (24/7) anticipa i mercati IB (azioni/bond/oro/credito), o viceversa? Disciplina onesta: i tranelli di timing daily sono enormi (crypto chiude 00:00 UTC, US equity 21:00 UTC -> il lag-0 e' contaminato), quindi (1) allineo i rendimenti sullo STESSO intervallo (compounding crypto sul grid giorni-di-borsa), (2) guardo i lag >=1 giorno, (3) test del segno con hit-rate e split in-sample/OOS, (4) flag multiple-testing. Ipotesi piu' pulita = EFFETTO WEEKEND: la crypto si muove Sab+Dom (azionario chiuso) -> quel movimento e' informazione PRIOR al lunedi'. Predice il gap/intraday del lunedi' azionario? Uso gli OPEN dei parquet eq_ (Monday open noto alle 13:30 UTC, weekend crypto noto alle 00:00 UTC). DATI: cache su disco (BTC/ETH Deribit 1h->1d UTC; ETF IB eq_*). Nessun IB online. """ import sys from pathlib import Path import numpy as np, pandas as pd ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)); sys.path.insert(0, str(ROOT / "scripts" / "research")) from src.data.downloader import load_data import eqlib ETFS = ["SPY", "QQQ", "IWM", "TLT", "GLD", "HYG"] def crypto_daily_close(asset="BTC") -> pd.Series: df = load_data(asset, "1h").set_index("datetime")["close"].astype(float) return df.resample("1D").last().dropna() # close ~00:00 UTC del giorno dopo def _ret(s): return s.pct_change() def _corr_lags(x: pd.Series, y: pd.Series, lags=range(-5, 6)): """corr(x_{t-k}, y_t): k>0 => x ANTICIPA y di k giorni. Allineati sullo stesso grid.""" J = pd.concat({"x": x, "y": y}, axis=1, join="inner").dropna() out = {} for k in lags: out[k] = round(float(J["x"].shift(k).corr(J["y"])), 3) return out def main(): print("=" * 98) print(" CRYPTO x MERCATI IB — correlazioni & anticipazioni (lead-lag)") print("=" * 98) btc = crypto_daily_close("BTC"); eth = crypto_daily_close("ETH") btc_r = _ret(btc); eth_r = _ret(eth) # equity close + grid giorni-di-borsa eq_close = {s: eqlib.load_eq(s)["close"].astype(float) for s in ETFS} eq_open = {s: eqlib.load_eq(s)["open"].astype(float) for s in ETFS} grid = eq_close["SPY"].index grid = grid[grid >= btc.index[0]] # crypto compoundato sul grid giorni-di-borsa (stesso intervallo dell'equity ret) def to_grid(s): cum = (1 + _ret(s)).cumprod() return (cum.reindex(cum.index.union(grid)).ffill().reindex(grid)).pct_change() btc_g = to_grid(btc); eth_g = to_grid(eth) print(f" overlap dal {grid[0].date()} ({len(grid)} giorni di borsa)\n") print(" --- (1) CORRELAZIONE CONTEMPORANEA (stesso intervallo; lag0 contaminato da timing) ---") print(f" {'ETF':5} {'corr BTC':>9} {'corr ETH':>9}") for s in ETFS: er = _ret(eq_close[s]).reindex(grid) cb = round(float(pd.concat([btc_g, er], axis=1).dropna().corr().iloc[0, 1]), 3) ce = round(float(pd.concat([eth_g, er], axis=1).dropna().corr().iloc[0, 1]), 3) print(f" {s:5} {cb:>9} {ce:>9}") print("\n --- (2) LEAD-LAG BTC vs ETF: corr(BTC_{t-k}, ETF_t), k>0 = BTC ANTICIPA ---") print(f" {'ETF':5} " + " ".join(f"k{ k:+d}" for k in range(-3, 4)) + " picco") for s in ETFS: er = _ret(eq_close[s]).reindex(grid) cl = _corr_lags(btc_g, er, range(-3, 4)) peak = max(cl, key=lambda k: abs(cl[k])) row = " ".join(f"{cl[k]:+.2f}" for k in range(-3, 4)) tag = f"k={peak:+d} ({'BTC->ETF' if peak>0 else 'ETF->BTC' if peak<0 else 'contemp'})" print(f" {s:5} {row} {tag}") print("\n --- (3) EFFETTO WEEKEND: crypto Sab+Dom -> lunedi' azionario (anticipazione pulita) ---") # weekend crypto = close(Dom 00:00 lun) / close(Ven) - 1 ; calcolato su crypto daily (calendario) cal = pd.date_range(btc.index[0], btc.index[-1], freq="D", tz="UTC") bc = btc.reindex(cal).ffill() for s in ["SPY", "QQQ", "IWM", "HYG"]: oc = eq_open[s]; cc = eq_close[s] rows = [] for mon in grid: if mon.weekday() != 0: # solo lunedi' continue fri = mon - pd.Timedelta(days=3) if fri not in cc.index: # venerdi' non di borsa (festa) -> salta continue wk = float(bc.get(mon, np.nan) / bc.get(fri + pd.Timedelta(days=0), np.nan) - 1) if fri in bc.index else np.nan # weekend crypto: da venerdi 00:00(close ven) a lunedi 00:00 -> usa bc[fri]..bc[mon] wk = float(bc.loc[mon] / bc.loc[fri] - 1) if (mon in bc.index and fri in bc.index) else np.nan gap = float(oc.loc[mon] / cc.loc[fri] - 1) if (mon in oc.index and fri in cc.index) else np.nan intr = float(cc.loc[mon] / oc.loc[mon] - 1) if mon in oc.index else np.nan rows.append((mon, wk, gap, intr)) D = pd.DataFrame(rows, columns=["mon", "wk", "gap", "intr"]).dropna().set_index("mon") if len(D) < 50: print(f" {s}: pochi dati ({len(D)})"); continue def stat(col): c = float(D["wk"].corr(D[col])) hit = float((np.sign(D["wk"]) == np.sign(D[col])).mean()) return c, hit cg, hg = stat("gap"); ci, hi = stat("intr") # OOS: split 2022 Dh = D[D.index >= pd.Timestamp("2022-01-01", tz="UTC")] cg_o = float(Dh["wk"].corr(Dh["gap"])); ci_o = float(Dh["wk"].corr(Dh["intr"])) print(f" {s}: n={len(D)} | weekend-crypto -> Mon GAP corr {cg:+.2f} hit {hg*100:.0f}% (OOS22+ {cg_o:+.2f}) " f"| Mon INTRADAY corr {ci:+.2f} hit {hi*100:.0f}% (OOS {ci_o:+.2f})") print("\n NB: lag-0/contemporanea contaminata dal timing (crypto chiude 00:00, equity 21:00 UTC).") print(" Il GAP del lunedi' e' il test pulito (weekend crypto = info prior all'apertura).") if __name__ == "__main__": main()