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,53 @@
|
||||
# 2026-06-22 — Crypto × mercati IB: correlazioni e ANTICIPAZIONI (lead-lag)
|
||||
|
||||
## Obiettivo
|
||||
Cercare correlazioni e soprattutto ANTICIPAZIONI tra crypto e mercati IB: un mercato fa capire
|
||||
l'andamento dell'altro? Dati: cache su disco (BTC/ETH Deribit 1h->1d UTC; ETF eq_* con OPEN). Nessun
|
||||
IB online. Disciplina: attenzione ai tranelli di timing daily (crypto chiude 00:00 UTC, US equity
|
||||
21:00 -> lag-0 contaminato), test del segno + OOS + multiple-testing.
|
||||
Script: `crypto_macro_leadlag.py`, `crypto_weekend_signal.py`.
|
||||
|
||||
## (1) Correlazione contemporanea
|
||||
Crypto = asset RISK-ON: corr BTC/ETH ~ **+0.32/0.37** con SPY/QQQ/IWM, **+0.25/0.28** con HYG
|
||||
(credito), **+0.13** GLD, **~-0.02** TLT (bond). Atteso.
|
||||
|
||||
## (2) Lead-lag giornaliero: NIENTE
|
||||
corr(BTC_{t-k}, ETF_t) ha picco a **k=0** (~0.32) e crolla a rumore (±0.05) per |k|>=1. Al daily
|
||||
**nessuno anticipa l'altro** (ne' crypto->equity ne' viceversa). Honest negative.
|
||||
|
||||
## (3) EFFETTO WEEKEND: anticipazione PULITA, significativa, OOS-robusta
|
||||
La crypto si muove Sab+Dom (azionario chiuso) -> quel movimento e' info PRIOR al lunedi'.
|
||||
- **Anticipa il GAP del lunedi'**: corr +0.22/+0.24 (SPY/QQQ/IWM/HYG), hit 59-62%, e **si RAFFORZA
|
||||
OOS (2022+): +0.30/+0.36**. Coerente su 4 ETF (non cherry-pick).
|
||||
- Intraday del lunedi' (open->close) piu' debole ma presente (corr 0.10-0.15, OOS 0.18-0.22).
|
||||
|
||||
### Validazione avversariale
|
||||
- **(A) INCREMENTALE vs venerdi'**: regressione `Mon ~ weekend_crypto + friday_eq`. Coeff weekend
|
||||
crypto significativo ovunque (QQQ gap **t=+4.7**, intr t=+2.9; SPY +4.4/+2.0; IWM +4.7/+2.7);
|
||||
friday_eq NON significativo. -> e' info CRYPTO-SPECIFICA del weekend, non momentum equity.
|
||||
- **(B) TRADABILE** (osservo weekend crypto Dom 24:00, entro Monday OPEN, esco CLOSE, net 4bps):
|
||||
| ETF | hit | Sharpe FULL / IS / OOS22+ | long-flat OOS | ann |
|
||||
|---|---|---|---|---|
|
||||
| QQQ | 60% | 1.46 / 1.61 / 1.33 | **1.91** | ~+9-11% |
|
||||
| SPY | 60% | 0.96 / 0.91 / 1.01 | 1.70 | ~+5% |
|
||||
| IWM | 56% | 0.89 / 0.73 / 1.04 | 1.07 | ~+6% |
|
||||
|
||||
## Verdetto
|
||||
**Trovata UNA anticipazione reale**: il weekend crypto anticipa il lunedi' azionario (massimo su QQQ,
|
||||
risk-on/tech). Significativa (t>4 sul gap), incrementale al venerdi', tradabile net costi, **regge e
|
||||
si rafforza OOS**, coerente su piu' ETF. Meccanismo economico sensato: crypto = proxy 24/7 del
|
||||
risk-sentiment; nel weekend l'equity e' chiuso e lunedi' "recupera" la direzione crypto.
|
||||
|
||||
### Caveat onesti
|
||||
- **Capacita' bassa**: ~52 lunedi'/anno, intraday -> ~+9%/yr sul capitale impiegato il lunedi', non
|
||||
una macchina da compounding. E' un segnale TATTICO, non un cornerstone.
|
||||
- Il GAP (t=4.7) e' piu' forte dell'intraday (t=2.9) ma per catturarlo serve entrare PRIMA del Monday
|
||||
open -> via **futures indice IB (MNQ/MES, aperti la domenica sera)**: enhancement eseguibile da
|
||||
validare (cattura gap+sessione).
|
||||
- Multiple-testing 3 ETF x 2 target: ma TUTTI significativi e coerenti -> effetto ampio, non fortuna.
|
||||
- Niente IB online qui (cache); per il deploy servirebbe il feed crypto live la domenica sera.
|
||||
|
||||
## Prossimo (se si procede)
|
||||
Validare la variante FUTURES (MNQ domenica sera -> cattura il gap del lunedi') e il sizing a basso
|
||||
capitale; eventualmente paper-trade. E' la prima ANTICIPAZIONE cross-mercato trovata: crypto come
|
||||
lead di sentiment sul lunedi' equity.
|
||||
@@ -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()
|
||||
@@ -0,0 +1,97 @@
|
||||
"""WEEKEND CRYPTO -> LUNEDI' AZIONARIO — validazione avversariale dell'anticipazione.
|
||||
|
||||
L'analisi lead-lag ha trovato UNA anticipazione pulita: il movimento crypto del weekend (Sab+Dom,
|
||||
azionario chiuso) anticipa il lunedi' azionario (gap corr ~0.24, OOS piu' forte). Prima di crederci,
|
||||
due test scettici:
|
||||
(A) INCREMENTALE: aggiunge info OLTRE il rendimento del VENERDI'? (o e' solo momentum equity?)
|
||||
regressione Mon ~ weekend_crypto + friday_equity ; il coeff del crypto resta significativo?
|
||||
(B) TRADABILE: segnale eseguibile = osservo weekend crypto (noto Dom 24:00 UTC), entro al Monday
|
||||
OPEN, esco al Monday CLOSE. Net di costi (4 bps RT ETF). Sharpe/hit/OOS vs sempre-long lunedi'.
|
||||
|
||||
DATI: cache su disco (BTC Deribit 1h->1d; ETF eq_* con OPEN). 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
|
||||
|
||||
OOS = pd.Timestamp("2022-01-01", tz="UTC")
|
||||
COST_RT = 0.0004 # 4 bps round-trip ETF (entry open + exit close)
|
||||
|
||||
|
||||
def _sh_weekly(r):
|
||||
r = np.asarray(pd.Series(r).dropna(), float)
|
||||
return float(np.mean(r) / np.std(r) * np.sqrt(52)) if len(r) > 2 and np.std(r) > 0 else 0.0
|
||||
|
||||
|
||||
def build(asset_etf="QQQ"):
|
||||
btc = load_data("BTC", "1h").set_index("datetime")["close"].astype(float).resample("1D").last()
|
||||
cal = pd.date_range(btc.index[0], btc.index[-1], freq="D", tz="UTC")
|
||||
bc = btc.reindex(cal).ffill()
|
||||
oc = eqlib.load_eq(asset_etf)["open"].astype(float)
|
||||
cc = eqlib.load_eq(asset_etf)["close"].astype(float)
|
||||
rows = []
|
||||
for mon in cc.index:
|
||||
if mon.weekday() != 0:
|
||||
continue
|
||||
fri = mon - pd.Timedelta(days=3)
|
||||
thu = mon - pd.Timedelta(days=4)
|
||||
if fri not in cc.index or fri not in bc.index or mon not in bc.index:
|
||||
continue
|
||||
wk = bc.loc[mon] / bc.loc[fri] - 1.0 # weekend crypto (Ven 00:00 -> Lun 00:00)
|
||||
fri_eq = (cc.loc[fri] / cc.loc[thu] - 1.0) if thu in cc.index else np.nan # rendimento venerdi'
|
||||
gap = oc.loc[mon] / cc.loc[fri] - 1.0
|
||||
intr = cc.loc[mon] / oc.loc[mon] - 1.0 # tradabile: open->close lunedi'
|
||||
rows.append((mon, wk, fri_eq, gap, intr))
|
||||
return pd.DataFrame(rows, columns=["mon", "wk", "fri_eq", "gap", "intr"]).dropna().set_index("mon")
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 92)
|
||||
print(" WEEKEND CRYPTO -> LUNEDI' AZIONARIO — validazione avversariale")
|
||||
print("=" * 92)
|
||||
for etf in ("QQQ", "SPY", "IWM"):
|
||||
D = build(etf)
|
||||
print(f"\n ===== {etf} (n={len(D)} lunedi', {D.index[0].date()}..{D.index[-1].date()}) =====")
|
||||
|
||||
# (A) INCREMENTALE vs venerdi' — regressione OLS standardizzata, t-stat su weekend_crypto
|
||||
for tgt in ("gap", "intr"):
|
||||
y = (D[tgt] - D[tgt].mean()) / D[tgt].std()
|
||||
x1 = (D["wk"] - D["wk"].mean()) / D["wk"].std()
|
||||
x2 = (D["fri_eq"] - D["fri_eq"].mean()) / D["fri_eq"].std()
|
||||
X = np.column_stack([np.ones(len(D)), x1.values, x2.values])
|
||||
beta, *_ = np.linalg.lstsq(X, y.values, rcond=None)
|
||||
resid = y.values - X @ beta
|
||||
se = np.sqrt(np.sum(resid**2) / (len(D) - 3) * np.diag(np.linalg.inv(X.T @ X)))
|
||||
t_wk = beta[1] / se[1]; t_fri = beta[2] / se[2]
|
||||
partial = float(pd.Series(resid).corr(x1)) # ~ contributo crypto al netto del resto
|
||||
print(f" [{tgt:4}] beta_weekendCrypto {beta[1]:+.3f} (t={t_wk:+.1f}) | "
|
||||
f"beta_fridayEq {beta[2]:+.3f} (t={t_fri:+.1f}) -> crypto {'INCREMENTALE' if abs(t_wk)>2 else 'non signif.'}")
|
||||
|
||||
# (B) TRADABILE: long lunedi' intraday se weekend crypto > 0, short se < 0 (net costi)
|
||||
sig = np.sign(D["wk"].values)
|
||||
gross = sig * D["intr"].values
|
||||
net = gross - COST_RT
|
||||
D2 = D.assign(net=net)
|
||||
full = D2["net"]; oos = D2[D2.index >= OOS]["net"]; ins = D2[D2.index < OOS]["net"]
|
||||
bh = D["intr"] # baseline: sempre-long lunedi' intraday
|
||||
hit = float((np.sign(gross) > 0).mean()) if False else float((sig == np.sign(D["intr"].values)).mean())
|
||||
print(f" TRADE (long/short Mon intraday su segno weekend-crypto, net {COST_RT*1e4:.0f}bps):")
|
||||
print(f" hit-rate segno {hit*100:.0f}% | Sharpe(sett.) FULL {_sh_weekly(full):.2f} IS {_sh_weekly(ins):.2f} OOS22+ {_sh_weekly(oos):.2f}")
|
||||
print(f" ritorno medio/lun {full.mean()*1e4:+.1f}bps (net) | baseline sempre-long {bh.mean()*1e4:+.1f}bps | "
|
||||
f"ann.~{full.mean()*52*100:+.1f}%")
|
||||
# long-flat (piu' realistico: long se crypto su, altrimenti cash)
|
||||
lf = np.where(D["wk"].values > 0, D["intr"].values, 0.0) - np.where(D["wk"].values > 0, COST_RT, 0.0)
|
||||
lfs = pd.Series(lf, index=D.index)
|
||||
print(f" variante LONG-FLAT (long se crypto su, else cash): Sharpe FULL {_sh_weekly(lfs):.2f} "
|
||||
f"OOS {_sh_weekly(lfs[lfs.index>=OOS]):.2f} ann.~{lfs.mean()*52*100:+.1f}%")
|
||||
|
||||
print("\n NB: ~52 lunedi'/anno -> Sharpe settimanale; OOS = 2022+. Multiple-testing: 3 ETF x 2 target.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user