"""TEST ONESTO finestre NON sovrapposte: crypto[notte presto] -> future indice[notte tardi]. L'idea "monitor Deribit / trade IB" e' valida SOLO se il crypto anticipa il moto SUCCESSIVO del future (non lo stesso intervallo = look-ahead). Qui: entrata a T (ora UTC nella notte, equity cash chiuso): osservato crypto fino a T. segnale = crypto[P 21:00 -> T] (info nota a T) controllo= future[P 21:00 -> T] (il moto PROPRIO del future fino a T) cattura = future[T -> D 13:00] (cio' che catturi entrando a T, fino a ~apertura cash) TEST: il segnale crypto predice la cattura INCREMENTALMENTE al controllo? (crypto vede prima del future?) TRADE: long/short future su sign(crypto[P21:00->T]), cattura future[T->open], net ~2bps. Sharpe/anno/OOS. Confronto col baseline sign(future[P21:00->T]) (momentum proprio del future) per isolare il valore crypto. Dati: data/raw/fut__1h.parquet (UTC) + crypto 1h. sqrt(252). """ import sys, argparse, json 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 CLOSE_H = 21; OPEN_H = 13 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(sym="ES", lead="BTC", h_entry=6): fut = fut_hourly(sym); bc = crypto_hourly(lead) days = pd.date_range(fut.index[0].normalize(), fut.index[-1].normalize(), freq="B", tz="UTC") rows = [] for k in range(1, len(days)): D = days[k]; P = days[k-1] t_pc = P + pd.Timedelta(hours=CLOSE_H) # P close 21:00 t_e = D + pd.Timedelta(hours=h_entry) # entrata T t_o = D + pd.Timedelta(hours=OPEN_H) # ~apertura cash 13:00 fp, fe, fo = at(fut, t_pc), at(fut, t_e), at(fut, t_o) cp, ce = at(bc, t_pc), at(bc, t_e) if not all(np.isfinite(v) and v > 0 for v in (fp, fe, fo, cp, ce)): continue crypto_sig = ce / cp - 1.0 # crypto P21:00 -> T fut_ctrl = fe / fp - 1.0 # future P21:00 -> T (controllo) capture = fo / fe - 1.0 # future T -> open (cio' che catturi) rows.append((D, crypto_sig, fut_ctrl, capture)) if len(rows) < 100: return {"sym": sym, "lead": lead, "h_entry": h_entry, "err": f"n={len(rows)}"} Dd = pd.DataFrame(rows, columns=["d", "csig", "fctrl", "cap"]).set_index("d") 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 # incrementale: cap ~ crypto + future_own_move 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_crypto = float(beta[1] / se[1]) if se[1] > 0 else 0.0 t_futown = float(beta[2] / se[2]) if se[2] > 0 else 0.0 # trade: crypto-signal vs future-own-signal C_crypto = np.sign(x) * y - COST C_futown = np.sign(ctrl) * y - COST m = Dd.index >= OOS yrs = Dd.index.year.values py = {int(yv): round(_sh(C_crypto[yrs == yv]), 2) for yv in sorted(set(yrs)) if (yrs == yv).sum() >= 30} return {"sym": sym, "lead": lead, "h_entry": h_entry, "n": len(Dd), "corr_crypto_cap": round(float(np.corrcoef(x, y)[0, 1]), 3), "t_crypto_incr": round(t_crypto, 2), "t_futown_incr": round(t_futown, 2), "sh_crypto_full": round(_sh(C_crypto), 2), "sh_crypto_oos": round(_sh(C_crypto[m]), 2), "sh_futown_full": round(_sh(C_futown), 2), "ann_crypto_pct": round(float(np.nanmean(C_crypto) * 252 * 100), 1), "per_year": py} def main(): ap = argparse.ArgumentParser() ap.add_argument("--syms", default="ES,NQ,RTY") ap.add_argument("--leads", default="BTC,ETH") ap.add_argument("--entries", default="0,3,6,9") args = ap.parse_args() print("=" * 96) print(" FUTURE OVERNIGHT — crypto[P21:00->T] predice future[T->open]? (finestre NON sovrapposte)") print("=" * 96) print(" cattura = future[T->open]; controllo = future[P21:00->T]; t_crypto_incr = crypto AL NETTO del future. net 2bps\n") best = [] for sym in args.syms.split(","): for lead in args.leads.split(","): for h in [int(x) for x in args.entries.split(",")]: r = run(sym, lead, h) if "err" in r: print(f" {sym}<-{lead} T={h}h: {r['err']}"); continue print(f" {sym}<-{lead} T={h:>2}h UTC: n={r['n']} corr {r['corr_crypto_cap']:+.3f} | " f"t_crypto(incr) {r['t_crypto_incr']:+.1f} t_futOwn {r['t_futown_incr']:+.1f} | " f"trade crypto Sh {r['sh_crypto_full']:+.2f} (OOS {r['sh_crypto_oos']:+.2f}) ann {r['ann_crypto_pct']:+.1f}% | " f"futOwn Sh {r['sh_futown_full']:+.2f}") best.append(r) best.sort(key=lambda r: r.get("sh_crypto_oos", -9), reverse=True) print("\n --- migliori per Sharpe OOS (trade su segnale crypto) ---") for r in best[:5]: print(f" {r['sym']}<-{r['lead']} T={r['h_entry']}h: OOS {r['sh_crypto_oos']} full {r['sh_crypto_full']} " f"t_crypto {r['t_crypto_incr']} per-anno {r['per_year']}") print("\n NB: t_crypto_incr alto E sh_crypto > sh_futOwn => il crypto anticipa il future (idea valida).") print(" t_crypto ~0 o sh_crypto ~ futOwn => e' solo momentum del future, il crypto non aggiunge.") if __name__ == "__main__": main()