"""ONESTA' DI TIMING: il 'Sharpe 5' del crypto->overnight equity e' look-ahead? Test: separare la versione OVERLAP (segnale e ritorno coprono le stesse ore = look-ahead) dalla versione TRADABILE (segnale finisce all'ENTRATA, catturo solo il moto SUCCESSIVO). segnale crypto = BTC [P 21:00 -> D 13:00 UTC] (noto solo a D 13:00, poco prima dell'open 13:30) - OVERLAP capture = gap = open[D]/close[P]-1 [P 21:00 -> D 13:30] <-- sovrappone il segnale - TRADABILE capture = intra = close[D]/open[D]-1 [D 13:30 -> D 21:00] <-- DOPO l'entrata, pulito Se OVERLAP >> TRADABILE, il numero alto e' artefatto: all'open il gap e' gia' avvenuto, non lo catturi. Annualizzazione CORRETTA sqrt(252) (giornaliero), non sqrt(52). """ 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")) import eqlib from crypto_lead_harness import crypto_hourly, at, OPEN_H, CLOSE_H OOS = pd.Timestamp("2022-01-01", tz="UTC"); COST = 0.0002 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 build(target="SPY", lead="BTC"): bc = crypto_hourly(lead) oc = eqlib.load_eq(target)["open"].astype(float); cc = eqlib.load_eq(target)["close"].astype(float) idx = cc.index; rows = [] for j in range(1, len(idx)): D = idx[j]; P = idx[j-1] c1 = at(bc, D.normalize() + pd.Timedelta(hours=OPEN_H)) # crypto a D 13:00 (entrata) c0 = at(bc, P.normalize() + pd.Timedelta(hours=CLOSE_H)) # crypto a P 21:00 if not (np.isfinite(c1) and np.isfinite(c0) and c0 > 0): continue crypto = c1 / c0 - 1.0 gap = oc[D] / cc[P] - 1.0 # OVERLAP col segnale intra = cc[D] / oc[D] - 1.0 # DOPO l'entrata (tradabile) rows.append((D, crypto, gap, intra)) return pd.DataFrame(rows, columns=["d", "crypto", "gap", "intra"]).set_index("d") def main(): print("=" * 90) print(" TIMING ONESTO: OVERLAP (look-ahead) vs TRADABILE — crypto overnight -> equity") print("=" * 90) print(" segnale noto a D13:00. OVERLAP=gap [copre il segnale]. TRADABILE=intraday [dopo entrata]. net 2bps, sqrt(252)\n") for tgt in ("SPY", "QQQ", "IWM"): D = build(tgt, "BTC") for name, col in (("OVERLAP gap (look-ahead)", "gap"), ("TRADABILE intraday", "intra")): C = np.sign(D["crypto"]) * D[col] - COST m = D.index >= OOS print(f" {tgt} {name:26}: Sharpe long/short {_sh(C):.2f} (OOS {_sh(C[m]):.2f}) ann {np.nanmean(C)*252*100:+.1f}%") print() print(" --- VERDETTO ---") D = build("QQQ", "BTC") cg = _sh(np.sign(D["crypto"]) * D["gap"] - COST) ci = _sh(np.sign(D["crypto"]) * D["intra"] - COST) print(f" QQQ: gap(overlap) Sharpe {cg:.2f} vs intraday(tradabile) Sharpe {ci:.2f}") print(f" -> il moto che il crypto 'predice' avviene NELLA finestra del segnale (overnight), non dopo.") print(f" All'entrata (D13:00, pre-open) il gap e' gia' realizzato -> NON catturabile con l'ETF.") print(f" Cio' che resta da catturare (intraday) ha Sharpe ~{ci:.1f}: l'edge tradabile e' quello.") print(f" Per catturare PARTE dell'overnight servirebbe entrare a META' notte via FUTURES IB e") print(f" testare se il crypto [P21:00->T] predice il future [T->open] (finestre NON sovrapposte):") print(f" richiede dati intraday dei futures indice (ES/NQ), che NON abbiamo in cache -> data step.") if __name__ == "__main__": main()