"""LEAD-LAG GENERICO non-sovrapposto: crypto[T-S -> T] predice future[T -> T+H]? (ogni mercato/fuso). Market-agnostic. Per ogni giorno, entrata a ora T (UTC): segnale = crypto nella finestra [T-S, T] (finisce all'entrata), cattura = future nella finestra SUCCESSIVA [T, T+H] (non sovrapposta). Controllo = moto PROPRIO del future [T-S, T] -> isola se il crypto AGGIUNGE (anticipa) oltre il momentum del future. Sweep su T (copre apertura Europa ~07h, USA ~13h, Asia ~00h) e H. TRADE: sign(crypto[T-S,T]) * future[T,T+H] - costo. Sharpe sqrt(252), per-anno, OOS 2024+. Dati: data/raw/fut_*_1h.parquet (UTC) + crypto 1h. Solo cache, nessun IB. """ import sys, glob from pathlib import Path import numpy as np, pandas as pd ROOT = Path(__file__).resolve().parents[2] RAW = ROOT / "data" / "raw" sys.path.insert(0, str(ROOT)); sys.path.insert(0, str(ROOT / "scripts" / "research")) from crypto_lead_harness import crypto_hourly, at OOS = pd.Timestamp("2024-01-01", tz="UTC"); COST = 0.0002 def fut_hourly(sym): d = pd.read_parquet(RAW / f"fut_{sym.lower()}_1h.parquet") return pd.Series(d["close"].astype(float).values, index=pd.to_datetime(d["timestamp"], unit="ms", utc=True)).sort_index() def _sh(r): r = np.asarray(r, float); r = r[np.isfinite(r)] return float(np.mean(r) / np.std(r) * np.sqrt(252)) if len(r) > 5 and np.std(r) > 0 else 0.0 def run(fut, bc, T, S, H): days = pd.date_range(fut.index[0].normalize(), fut.index[-1].normalize(), freq="D", tz="UTC") rows = [] for D in days: te = D + pd.Timedelta(hours=T) ts = te - pd.Timedelta(hours=S) tx = te + pd.Timedelta(hours=H) f0, f1 = at(fut, ts), at(fut, te); f2 = at(fut, tx) c0, c1 = at(bc, ts), at(bc, te) if not all(np.isfinite(v) and v > 0 for v in (f0, f1, f2, c0, c1)): continue rows.append((D, c1/c0 - 1, f1/f0 - 1, f2/f1 - 1)) if len(rows) < 120: return None Dd = pd.DataFrame(rows, columns=["d", "csig", "fctrl", "cap"]).set_index("d") # filtra giorni in cui la cattura e' identicamente 0 (mercato chiuso, prezzo stale) Dd = Dd[Dd["cap"].abs() > 1e-9] if len(Dd) < 120: return None x, ctrl, y = Dd["csig"].values, Dd["fctrl"].values, Dd["cap"].values def z(a): s = a.std(); return (a - a.mean()) / s if s > 0 else a * 0 X = np.column_stack([np.ones(len(y)), z(x), z(ctrl)]) beta, *_ = np.linalg.lstsq(X, z(y), rcond=None) resid = z(y) - X @ beta; dof = max(len(y) - 3, 1) se = np.sqrt(np.sum(resid**2) / dof * np.diag(np.linalg.inv(X.T @ X))) t_c = float(beta[1] / se[1]) if se[1] > 0 else 0.0 C = np.sign(x) * y - COST m = Dd.index >= OOS yrs = Dd.index.year.values py = {int(v): round(_sh(C[yrs == v]), 2) for v in sorted(set(yrs)) if (yrs == v).sum() >= 30} pos = sum(1 for v in py.values() if v > 0) return dict(T=T, H=H, n=len(Dd), t_crypto=round(t_c, 2), sh_full=round(_sh(C), 2), sh_oos=round(_sh(C[m]), 2), ann=round(float(np.nanmean(C) * 252 * 100), 1), years_pos=pos, years_tot=len(py), per_year=py) def main(): syms = sorted(p.name[4:-11].upper() for p in RAW.glob("fut_*_1h.parquet")) print("=" * 98) print(f" LEAD-LAG GENERICO non-sovrapposto — futures: {syms}") print("=" * 98) print(" crypto[T-8h -> T] -> future[T -> T+6h], controllo=moto proprio future. net 2bps, OOS2024+\n") bcs = {l: crypto_hourly(l) for l in ("BTC", "ETH")} winners = [] for sym in syms: fut = fut_hourly(sym) best = None for lead in ("BTC", "ETH"): for T in (0, 4, 8, 12, 16, 20): r = run(fut, bcs[lead], T, 8, 6) if not r: continue r["sym"] = sym; r["lead"] = lead if best is None or r["sh_oos"] > best["sh_oos"]: best = r # raccogli i forti (crypto significativo E robusto) if r["t_crypto"] >= 2.5 and r["sh_oos"] > 0.5 and r["years_pos"] == r["years_tot"]: winners.append(r) if best: print(f" {sym:7} miglior: {best['lead']}->T{best['T']}h: t_crypto {best['t_crypto']:+.1f} " f"Sh full {best['sh_full']:+.2f} OOS {best['sh_oos']:+.2f} ann {best['ann']:+.1f}% " f"{best['years_pos']}/{best['years_tot']}y {best['per_year']}") print("\n --- CANDIDATI FORTI (t_crypto>=2.5, OOS>0.5, tutti gli anni positivi) ---") if not winners: print(" NESSUNO. -> nessuna anticipazione crypto->future robusta oltre il rumore/beta.") else: winners.sort(key=lambda r: r["sh_oos"], reverse=True) for w in winners[:10]: print(f" {w['sym']:7} {w['lead']}->T{w['T']}h H{w['H']}: t_crypto {w['t_crypto']} " f"Sh OOS {w['sh_oos']} full {w['sh_full']} ann {w['ann']}% {w['years_pos']}/{w['years_tot']}y {w['per_year']}") if __name__ == "__main__": main()