Files
claude-regelwerk/.vault-tools/trading-plattform-pruefung/ibkr_probe.py
T
Pascal MichelandClaude Opus 5 ea278dc24f Automatischer Push: 46 Datei(en) geändert
- .claude/agents/planer.md
- .claude/skills/plan-executer-pruefung/SKILL.md
- .claude/workflows/plankritik.js
- .claude/workflows/planung.js
- .claude/workflows/pruefung.js
- .claude/workflows/verbrauch.js
- .claude/workflows/welle-mit-pruefung.js
- .vault-tools/plan-kopie/LIESMICH.md
- .vault-tools/plan-kopie/plan_zeichen.py
- .vault-tools/plan-kopie/rot_nachweis.py
- .vault-tools/trading-plattform-pruefung/LIESMICH.md
- .vault-tools/trading-plattform-pruefung/abendkontrolle.py
- … und 34 weitere

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-25 04:00:02 +02:00

158 lines
7.2 KiB
Python

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