- .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]>
158 lines
7.2 KiB
Python
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())
|