"""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()