"""IB EQUITIES/ETF DATA PROBE — certifica cosa il paper IB dà per la ricerca su azioni/ETF. Gemello equity di certify_feed.py: PRIMA il dato (cosa c'è, quanto indietro, aggiustato per dividendi/split?, cosa costa), POI la strategia. Disciplina v2.0.0. Universo candidato per la prima ricerca equity (cross-sectional momentum / trend, l'edge "noioso e robusto" più plausibile in un mercato efficiente): * 11 SPDR settoriali (XLK..XLC) — universo canonico del momentum cross-section settoriale; * ETF broad / macro (SPY QQQ IWM TLT GLD HYG) — per trend e risk-on/off; * 2 azioni (AAPL MSFT) per tarare profondità/qualità. Per ogni simbolo: profondità storica daily con whatToShow=ADJUSTED_LAST (split+dividendi, OBBLIGATORIO per un backtest equity onesto) e TRADES (raw), + flag se scatta errore di subscription market-data. uv run --with ib_async python scripts/research/ib_equities_probe.py """ import argparse, sys SECTORS = ["XLK", "XLF", "XLE", "XLV", "XLI", "XLP", "XLY", "XLU", "XLB", "XLRE", "XLC"] BROAD = ["SPY", "QQQ", "IWM", "TLT", "GLD", "HYG"] STOCKS = ["AAPL", "MSFT"] def main(): ap = argparse.ArgumentParser() ap.add_argument("--host", default="127.0.0.1") ap.add_argument("--port", type=int, default=4002) ap.add_argument("--client-id", type=int, default=88) ap.add_argument("--years", default="20 Y", help="durata storica da richiedere") args = ap.parse_args() try: from ib_async import IB, Stock except Exception: print("ib_async non importabile. Esegui con: uv run --with ib_async python ...") sys.exit(2) ib = IB() try: ib.connect(args.host, args.port, clientId=args.client_id, timeout=15) except Exception as e: print(f"[CONNESSIONE FALLITA] {args.host}:{args.port} -> {repr(e)[:140]}") sys.exit(1) print("=" * 96) print(f" IB EQUITIES/ETF PROBE — {args.host}:{args.port} | acct {ib.managedAccounts()} | depth req {args.years}") print("=" * 96) universe = [("SECTOR", s) for s in SECTORS] + [("BROAD", s) for s in BROAD] + [("STOCK", s) for s in STOCKS] print(f" {'sym':6} {'tipo':7} {'ADJUSTED_LAST':>26} {'TRADES':>22} note") rows = [] for cat, sym in universe: con = Stock(sym, "SMART", "USD") try: cds = ib.reqContractDetails(con) if not cds: print(f" {sym:6} {cat:7} {'-- no contract --':>26}") continue except Exception as e: print(f" {sym:6} {cat:7} ERR resolve {repr(e)[:40]}") continue def hist(what): try: b = ib.reqHistoricalData(con, endDateTime="", durationStr=args.years, barSizeSetting="1 day", whatToShow=what, useRTH=True, formatDate=1, timeout=45) if not b: return "0 barre", None return f"{len(b)}b {b[0].date}..{b[-1].date}", b except Exception as e: return f"ERR {repr(e)[:30]}", None adj_s, adj_b = hist("ADJUSTED_LAST") trd_s, _ = hist("TRADES") note = "" if "ERR" in adj_s or "0 barre" in adj_s: note = "subscription? prova delayed" print(f" {sym:6} {cat:7} {adj_s:>26} {trd_s:>22} {note}") if adj_b: rows.append((sym, len(adj_b), str(adj_b[0].date), str(adj_b[-1].date))) print("-" * 96) if rows: depth = min(r[1] for r in rows); start = max(r[2] for r in rows) print(f" CERTIFICABILI (ADJUSTED_LAST): {len(rows)}/{len(universe)} | profondità comune ~{depth}b | start comune {start}") print(f" -> per un backtest cross-sectional servono date allineate: lo start comune e' il limite.") else: print(" NESSUN simbolo ha reso storia ADJUSTED — probabile mancanza market-data subscription.") print(" Ripiego: whatToShow='TRADES' (raw, non adj) o dati 'delayed' / fonte esterna certificabile.") ib.disconnect() if __name__ == "__main__": main()