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:
Adriano Dal Pastro
2026-06-22 21:55:01 +00:00
parent 67850b0dd8
commit d2d535cf6a
3 changed files with 270 additions and 0 deletions
+97
View File
@@ -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()