"""FETCH futures indice ORARI (ES/NQ/RTY) da IB -> data/raw/fut__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" # sym -> exchange (indici US + esteri + commodity + bond per la ricerca cross-mercato oltre SP500) SYMS = {"ES": "CME", "NQ": "CME", "RTY": "CME", "GC": "COMEX", "CL": "NYMEX", "HG": "COMEX", "ZN": "CBOT", "ESTX50": "EUREX", "DAX": "EUREX", "NKD": "CME"} N_CHUNKS = 5 # (non usato: ContFuture non accetta endDateTime -> chiamata singola) 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, exc in SYMS.items(): out = RAW / f"fut_{sym.lower()}_1h.parquet" if out.exists(): print(f" {sym}: gia' su disco -> skip"); continue cf = ContFuture(sym, exchange=exc) 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()