09c8bb7de8
Esteso il test crypto-lead a ZN(bond), ESTX50/DAX(Europa), NKD(Nikkei) via futures orari IB (commodity GC/CL/HG bloccate da subscription). Test non-sovrapposto crypto[T-8h->T]->future[T->T+6h]. ES/NQ/RTY niente (gia'); ZN negativo; NKD debole (~overnight drift). ESTX50/DAX SEMBRANO fortissimi (t_crypto 7.8, Sharpe 2.5, 3/3 anni) MA e' artefatto di confine UTC: picco a coltello a T=00:00, morto a T=1h; GAP di 1h uccide l'effetto (Sharpe 2.45->-0.52); tutto l'edge nella singola barra 00:00->01:00 (Sh +2.93) vs ora dopo (-1.02). Firma esatta di day_boundary_robust (CLAUDE.md). VERDETTO: nessuna anticipazione crypto->mercato sfruttabile, ne' SP500 ne' altro. Sempre co-movimento contemporaneo (risk-beta) o artefatto di confine. Resta valido solo il diversificatore TP01+GTAA. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
65 lines
3.1 KiB
Python
65 lines
3.1 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"
|
|
# 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()
|