research(cross-market): futures overnight non-sovrapposto -> edge ~0 su SP500, soffio su small-cap
Test ONESTO dell'idea "monitor Deribit/trade IB": entra mid-notte sul future indice, cattura il moto SUCCESSIVO (finestre non sovrapposte, no look-ahead). Dati: ES/NQ/RTY orari da IB (fut_*_1h, ~3y). RISULTATO: ES (S&P500) nessun edge (Sharpe ~0/neg, t_crypto 0-1.5); NQ momentum del future non crypto; RTY (small-cap) unico con t_crypto incrementale 2.0-2.7 e crypto che aggiunge oltre il moto proprio del future, ma Sharpe 0.4-0.5, 24 config (multiple-testing), 2.3y, per-anno incoerente (2026 negativo). VERDETTO: l'idea NON da' edge tradabile, men che meno su SP500. Il forte crypto<->equity e' co-movimento contemporaneo (risk-beta), non anticipazione: imposta una finestra causale non-sovrapposta e svanisce. Il "Sharpe 5" del gap era look-ahead. RTY -> forward-monitor al piu'. Coerente col soffitto del progetto. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
"""FETCH futures indice ORARI (ES/NQ/RTY) da IB -> data/raw/fut_<sym>_1h.parquet (UTC).
|
||||
|
||||
Per il test onesto dell'idea "monitor Deribit / trade IB": serve il path INTRADAY del future indice
|
||||
(che si trada di notte) per misurare finestre overnight NON sovrapposte col segnale crypto.
|
||||
ContFuture orario, in chunk da 1 anno (IB limita le durate intraday). Convertito in UTC.
|
||||
Resumable (salta i parquet gia' scritti). Per RETURNS/lead-lag il back-adjust del ContFuture e' ok
|
||||
(i ritorni infra-contratto sono preservati; i gap di roll ~4/anno sono trascurabili).
|
||||
|
||||
uv run --with ib_async python scripts/research/fetch_ib_futures.py
|
||||
"""
|
||||
import sys, time
|
||||
from pathlib import Path
|
||||
import numpy as np, pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
RAW = ROOT / "data" / "raw"
|
||||
SYMS = ["ES", "NQ", "RTY"]
|
||||
N_CHUNKS = 5 # ~5 anni se disponibili
|
||||
|
||||
|
||||
def main():
|
||||
from ib_async import IB, ContFuture
|
||||
ib = IB()
|
||||
try:
|
||||
ib.connect("127.0.0.1", 4002, clientId=144, timeout=15)
|
||||
except Exception as e:
|
||||
print(f"[CONNESSIONE FALLITA] {repr(e)[:100]}"); sys.exit(1)
|
||||
print(f" acct {ib.managedAccounts()} | fetch futures orari -> data/raw/fut_*")
|
||||
for sym in SYMS:
|
||||
out = RAW / f"fut_{sym.lower()}_1h.parquet"
|
||||
if out.exists():
|
||||
print(f" {sym}: gia' su disco -> skip"); continue
|
||||
cf = ContFuture(sym, exchange="CME")
|
||||
try:
|
||||
ib.qualifyContracts(cf)
|
||||
except Exception as e:
|
||||
print(f" {sym}: qualify ERR {repr(e)[:60]}"); continue
|
||||
# ContFuture NON accetta endDateTime (Error 10339) -> chiamata singola, durata massima (~3y orari)
|
||||
try:
|
||||
b = ib.reqHistoricalData(cf, endDateTime="", durationStr="4 Y", barSizeSetting="1 hour",
|
||||
whatToShow="TRADES", useRTH=False, formatDate=1, timeout=150)
|
||||
except Exception as e:
|
||||
print(f" {sym}: ERR {repr(e)[:60]}"); continue
|
||||
if not b:
|
||||
print(f" {sym}: VUOTO"); continue
|
||||
D = pd.DataFrame([(pd.Timestamp(x.date), x.open, x.high, x.low, x.close, x.volume) for x in b],
|
||||
columns=["ts", "open", "high", "low", "close", "volume"]).drop_duplicates("ts").sort_values("ts").reset_index(drop=True)
|
||||
# -> UTC ms (robusto alla risoluzione us/ns: naive-UTC -> datetime64[ms] -> int64)
|
||||
ts = pd.to_datetime(D["ts"], utc=True).dt.tz_convert("UTC").dt.tz_localize(None)
|
||||
D["timestamp"] = ts.values.astype("datetime64[ms]").astype("int64")
|
||||
D = D.drop(columns=["ts"])
|
||||
D.to_parquet(out)
|
||||
u = pd.to_datetime(D["timestamp"], unit="ms", utc=True)
|
||||
print(f" {sym}: SCRITTO {len(D)} barre {u.iloc[0]} .. {u.iloc[-1]} -> {out.name}")
|
||||
time.sleep(1.5)
|
||||
ib.disconnect()
|
||||
print(" done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user