research(cross-market): crypto x mercati IB -> trovata ANTICIPAZIONE weekend-crypto -> lunedi' equity
Goal: cercare correlazioni/anticipazioni crypto<->IB. Dati cache (BTC/ETH Deribit 1h->1d; ETF eq_*).
(1) Correlazione contemporanea: crypto = risk-on (BTC ~0.32 SPY/QQQ/IWM, 0.25 HYG, 0.13 GLD, ~0 TLT).
(2) Lead-lag GIORNALIERO: NIENTE (picco k=0, rumore a |k|>=1) -> nessuno anticipa l'altro al daily.
(3) EFFETTO WEEKEND (anticipazione pulita): crypto Sab+Dom (equity chiuso) anticipa il lunedi'.
GAP lunedi' corr +0.22/0.24 (SPY/QQQ/IWM/HYG), hit 59-62%, si RAFFORZA OOS22+ (+0.30/0.36).
Validazione avversariale:
(A) INCREMENTALE vs venerdi': beta weekend-crypto significativo (QQQ gap t=+4.7, intr +2.9; SPY
+4.4/+2.0; IWM +4.7/+2.7), friday_eq NON signif. -> info crypto-specifica, non momentum equity.
(B) TRADABILE (entro Mon open, esco close, net 4bps): QQQ hit 60%, Sharpe 1.46 (OOS 1.33), long-flat
OOS 1.91 ~+9%/yr; SPY/IWM piu' deboli ma OOS positivi.
VERDETTO: prima anticipazione cross-mercato reale. Crypto = proxy 24/7 risk-sentiment; lunedi' equity
recupera la direzione del weekend. Caveat: capacita' bassa (~52 lun/anno), tattico non cornerstone;
gap catturabile via futures IB (MNQ domenica sera) da validare. Coerente su 3 ETF (no cherry-pick).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user