55c337144e
L'idea (segnale crypto overnight -> trade indice IB) sembrava Sharpe 3.6-5.9 ma e' look-ahead: la finestra segnale crypto [P21:00->D13:00] e il gap equity [Pclose->Dopen] coprono le stesse ore. All'entrata (D13:00, pre-open) il gap e' gia' avvenuto -> non catturabile con l'ETF. Decomposizione (net 2bps, sqrt252): OVERLAP gap Sh ~3.6-4.0 (artefatto) vs TRADABILE intraday Sh -0.03..0.25 (reale, ~0, muore a costi). Conferma/rafforza "non deployabile" del workflow. Resta possibile solo la versione futures mid-overnight (finestre non sovrapposte) -> serve dato intraday ES/NQ, non in cache. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
72 lines
3.6 KiB
Python
72 lines
3.6 KiB
Python
"""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()
|