"""Lesende Probe gegen das IBKR-Paper-Konto ueber ein laufendes IB Gateway: verbindet, liest, trennt. Koordinator-Werkzeug, nicht fuers Repo. Pascal am 2026-09-24: Zugang zum IBKR-Paper-Konto vorbereiten, Gateway als Container in WSL (ghcr.io/gnzsnz/ib-gateway, wie DockerizedIBGatewayConfig in NautilusTrader 1.231.0), erster Test heute nach 22:00. Diese Probe handelt nie: Das Gateway laeuft mit READ_ONLY_API=yes, und sie ruft keine Order-Methode. Entscheidungsliste (Frage -> Herkunft der Antwort): 1. Erreicht die Probe das Gateway? Quelle: EClient.connect/isConnected (nautilus-ibapi 10.45.1), Port 4004 = Paper aussen (gateway.py:43-45 in Nautilus 1.231.0). 2. Welches Konto ist angemeldet? Quelle: managedAccounts-Rueckruf. Ausgegeben: die ersten zwei Zeichen und die Laenge (Paper-Konten beginnen im Doku-Beispiel mit "DU"; das ist ein Beispiel, keine Zusage). 3. Welche Kontoart, welche Waehrung? Quelle: reqAccountSummary mit den Tags AccountType, Currency; Betraege (NetLiquidation, TotalCashValue) nur als "vorhanden". 4. Wie viele Positionen? Quelle: reqPositions -> positionEnd; nur die Zahl. 5. Kommt ein verzoegerter Kurs? Quelle: reqMarketDataType(4, DELAYED_FROZEN; ohne Abo laut Nautilus-Doku) und reqMktData(AAPL, SMART, USD, Snapshot). Ausgegeben: welche Kursart kam (Tick-Typ), nie der Kurs. 6. Welche Fehler meldet das Gateway? Quelle: error-Rueckruf; Code und gekuerzter Text (ohne Zahlen ueber vier Stellen). Eine fehlende Antwort nach ZEITLIMIT_S ist ein Befund, kein Schweigen. Exit 0 alles beantwortet, 1 mindestens ein Befund, 3 keine Verbindung, 2 abgebrochen. Aufruf (WSL, eigenes Venv): ~/venvs/ibkr-probe/bin/python ibkr_probe.py [--port 4004] [--client-id 991] """ import argparse import re import sys import threading import time ZEITLIMIT_S = 30 NUR_LESEN_TAGS = "AccountType,Currency,NetLiquidation,TotalCashValue" BETRAGS_TAGS = {"NetLiquidation", "TotalCashValue"} def maskiere(text: str) -> str: return re.sub(r"\d{5,}|\d+\.\d+", "[x]", str(text)) def main(argv=None) -> int: zerleger = argparse.ArgumentParser(allow_abbrev=False) zerleger.add_argument("--host", default="127.0.0.1") zerleger.add_argument("--port", type=int, default=4004) zerleger.add_argument("--client-id", type=int, default=991) argumente = zerleger.parse_args(argv) from ibapi.client import EClient from ibapi.contract import Contract from ibapi.wrapper import EWrapper class Probe(EWrapper, EClient): def __init__(self): EWrapper.__init__(self) EClient.__init__(self, self) self.ereignisse = {name: threading.Event() for name in ("konto", "zusammenfassung", "positionen", "kurs", "zeit")} self.konten = "" self.zusammenfassung = {} self.positionen = 0 self.kurs_arten = set() self.fehler = [] self.zeit = None def managedAccounts(self, accountsList): self.konten = accountsList self.ereignisse["konto"].set() def currentTime(self, zeit): self.zeit = zeit self.ereignisse["zeit"].set() def accountSummary(self, reqId, account, tag, value, currency): self.zusammenfassung[tag] = "vorhanden" if tag in BETRAGS_TAGS else value def accountSummaryEnd(self, reqId): self.ereignisse["zusammenfassung"].set() def position(self, account, contract, position, avgCost): self.positionen += 1 def positionEnd(self): self.ereignisse["positionen"].set() def tickPrice(self, reqId, tickType, price, attrib): if price and price > 0: self.kurs_arten.add(tickType) def tickSnapshotEnd(self, reqId): self.ereignisse["kurs"].set() def error(self, reqId, *rest): # Die Signatur wechselt zwischen ibapi-Versionen: nautilus-ibapi 10.45.1 schickt vor dem # Code einen Zeitstempel in Millisekunden (gemessen am 2026-09-24: 1790261211141 vor 502). # Fehlercodes sind klein; der Zeitstempel ist es nicht. werte = [r for r in rest if isinstance(r, (int, str))] code = next((w for w in werte if isinstance(w, int) and 0 < w < 100000), None) text = next((w for w in werte if isinstance(w, str) and len(w) > 3), "") self.fehler.append((reqId, code, maskiere(text)[:160])) probe = Probe() befunde = [] try: probe.connect(argumente.host, argumente.port, argumente.client_id) faden = threading.Thread(target=probe.run, daemon=True) faden.start() beginn = time.time() while not probe.isConnected() and time.time() - beginn < 10: time.sleep(0.2) if not probe.isConnected(): print(f"BEFUND: keine Verbindung zu {argumente.host}:{argumente.port}") for f in probe.fehler[:5]: print(f" fehler {f}") return 3 print(f"verbunden mit {argumente.host}:{argumente.port}, Serverversion {probe.serverVersion()}") probe.reqCurrentTime() probe.reqAccountSummary(9001, "All", NUR_LESEN_TAGS) probe.reqPositions() probe.reqMarketDataType(4) aapl = Contract() aapl.symbol, aapl.secType, aapl.exchange, aapl.currency = "AAPL", "STK", "SMART", "USD" probe.reqMktData(9002, aapl, "", True, False, []) for name, ereignis in probe.ereignisse.items(): if not ereignis.wait(ZEITLIMIT_S): befunde.append(f"keine Antwort auf {name} in {ZEITLIMIT_S} s") konten = [k for k in probe.konten.split(",") if k] print(f"konten: {len(konten)}; " + ", ".join(f"{k[:2]}… ({len(k)} Zeichen)" for k in konten)) print(f"serverzeit: {'da' if probe.zeit else 'fehlt'}") print(f"kontoart: {probe.zusammenfassung.get('AccountType')}, waehrung: {probe.zusammenfassung.get('Currency')}, " f"nettoliquidation: {probe.zusammenfassung.get('NetLiquidation', 'fehlt')}, bargeld: {probe.zusammenfassung.get('TotalCashValue', 'fehlt')}") print(f"positionen: {probe.positionen}") print(f"kursarten AAPL (Tick-Typen mit Preis): {sorted(probe.kurs_arten) or 'keine'}") for f in probe.fehler: print(f"gateway meldet: reqId {f[0]}, code {f[1]}: {f[2]}") if not konten: befunde.append("kein Konto gemeldet") if not probe.kurs_arten: befunde.append("kein verzoegerter Kurs fuer AAPL") probe.cancelAccountSummary(9001) probe.cancelPositions() except Exception as ausnahme: print(f"BEFUND: Probe abgebrochen: {type(ausnahme).__name__}: {maskiere(ausnahme)[:200]}") return 2 finally: if probe.isConnected(): probe.disconnect() for befund in befunde: print(f"BEFUND: {befund}") print(f"ERGEBNIS: {len(befunde)} Befund(e)") return 1 if befunde else 0 if __name__ == "__main__": sys.exit(main())