Files
PythagorasGoal/scripts/research/fetch_ib_futures.py
T
Adriano Dal Pastro c4bc336a53 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>
2026-06-23 11:51:15 +00:00

62 lines
2.8 KiB
Python

"""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()