R9: Echter IbkrBrokerClient über die TWS API

Broker-Adapter gegen TWS/IB Gateway, aktivierbar über IBKRSettings.UseTwsApi;
NullBrokerClient bleibt Default. TradingEnabled bleibt als zweite, unabhängige
Sicherung bestehen – ohne ihn platziert der ExecutionService keine Order.

Aufteilung (src/IBKRTrader.Core/Trading/Ibkr/):
- IbkrMapping      – reine Abbildung Core <-> TWS (Kontrakt, Order, Kurs, Port-
                     und Statusregeln), vollständig unit-getestet
- IbkrConnection   – Socket-Lebenszyklus, Reader-Thread, reqId-Korrelation über
                     TaskCompletionSource
- IbkrBrokerClient – implementiert IBrokerClient, übersetzt Fehler in leere
                     Ergebnisse (Konto 0 lässt die Risikoprüfung alles ablehnen)

Bewusste Entscheidungen:
- Träges Verbinden mit Wiederholung statt Verbindungsaufbau beim Start: TWS ist
  nach einem Neustart minutenlang nicht bereit.
- Port wird gegen den Handelsmodus geprüft; Paper-Modus auf Live-Port (oder
  umgekehrt) lässt den Broker inaktiv, statt auf dem falschen Konto zu handeln.
- MarketDataType Default 4: Paper-Konten ohne Datenabo bekommen sonst keine Kurse.
- Fehlercode 10167 ist ein Statushinweis (verzögerte Daten folgen), kein Fehler.
  Als Fehler behandelt scheiterte jede einzelne Kursabfrage.

Verifiziert gegen Paper-Konto DUR371528: Verbindung, Konto (100.105,50 EUR),
Kurse (AAPL/MSFT/NVDA, verzögert), Fehlerpfade. Orderpfad bis zur Broker-Annahme
per What-If-Order geprüft (Aktie + Option, ohne Ausführung); dabei zugleich die
Optionsberechtigung des Kontos bestätigt. Offen: echte Ausführung (Fill ->
Buchung) und asynchrone Fill-Verfolgung – beides in IBKR-Integration.md notiert.

Doku: TWS-Setup-Checkliste.md (Einstellungen für Neuinstallation) neu,
IBKR-Integration.md / ARCHITECTURE.md / README.md nachgezogen.

154/154 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-04 17:40:00 +02:00
co-authored by Claude Opus 5
parent 2a312ca035
commit 80afcd49c1
14 changed files with 1427 additions and 45 deletions
+103 -33
View File
@@ -1,11 +1,13 @@
# IBKR-Anbindung Plan (TWS API via IB Gateway)
# IBKR-Anbindung (TWS API via IB Gateway)
**Entscheidung:** Der `IbkrBrokerClient` (ersetzt `NullBrokerClient`) nutzt die **TWS API** über das
**IB Gateway** native C#-Lib, robusteste Order-Ausführung, region-agnostisch.
**Entscheidung:** Der `IbkrBrokerClient` (Alternative zum `NullBrokerClient`) nutzt die **TWS API**
über das **IB Gateway** native C#-Lib, robusteste Order-Ausführung, region-agnostisch.
> Umsetzung ist **blockiert**, bis der Paper-Zugang + ein laufendes IB Gateway verfügbar sind
> (echte Verifikation von Quotes/Konto/Orders nur gegen den Gateway möglich). Dieses Dokument hält
> Setup und Design fest, damit die Umsetzung dann schnell und korrekt läuft.
> **Stand 2026-07-31:** Der Adapter ist implementiert (`src/IBKRTrader.Core/Trading/Ibkr/`) und wird
> über `IBKRSettings.UseTwsApi` aktiviert. Gegen das Paper-Konto **DUR371528** verifiziert sind
> Verbindung, `GetAccountStateAsync` und `GetQuoteAsync` (verzögerte Kurse, siehe unten) sowie der
> Fehlerpfad bei unbekanntem Symbol und nicht erreichbarer TWS.
> **Noch nicht verifiziert: `PlaceOrderAsync`** dafür ist eine echte Order auf dem Paper-Konto nötig.
## Regionen (US ↔ IE)
Gleiche API, gleiche Gateway-Software, **eine Codebasis**. Unterschied nur: Login/Entity
@@ -13,6 +15,8 @@ Gleiche API, gleiche Gateway-Software, **eine Codebasis**. Unterschied nur: Logi
Paper vs. Live = Konto + Port, kein Code-Unterschied.
## Betrieb (operativ)
- **Setup auf neuem System:** alle nötigen TWS-/Gateway-Einstellungen Schritt für Schritt in der
[TWS-Setup-Checkliste](TWS-Setup-Checkliste.md).
- **IB Gateway** (schlank, statt TWS) muss laufen und eingeloggt sein.
- **Ports:** Paper **4002**, Live **4001** (Auswahl über `TradingSettings.Mode`).
→ passt exakt zu den bestehenden `IBKRSettings` (Host `127.0.0.1`, Port, `ClientId`).
@@ -20,38 +24,104 @@ Paper vs. Live = Konto + Port, kein Code-Unterschied.
Server-Betrieb **IBC/IBController** (Auto-Login + geplanter Neustart).
## Bibliothek
- Empfohlen: offizielle C#-API als NuGet-Mirror **`IB.TWS.CSharpApi`** (keine Abhängigkeiten),
ggf. der vereinfachte Wrapper **`IB.CSharpApiClient`** (mathpaquette) für weniger Callback-Boilerplate.
- NuGet-Allowlist (`NuGet.config`) muss um das Paket ergänzt werden.
Offizielle C#-API als NuGet-Mirror **`IB.TWS.CSharpApi` 9.76.1**, referenziert im Core und in der
NuGet-Allowlist (`NuGet.config`) freigegeben. Das Paket zielt auf .NET Framework, ist aber reiner
managed Code und läuft auf net10 `NU1701` ist in der `.csproj` bewusst unterdrückt.
## Design `IbkrBrokerClient : IBrokerClient`
Die TWS API ist **callback-basiert** (`EClientSocket` sendet, `EWrapper` empfängt Events). Wir kapseln
das hinter unserem bestehenden async-Seam `IBrokerClient`:
- **Verbindung:** `EClientSocket.eConnect(host, port, clientId)` + Reader-Thread; Reconnect-Logik.
- **Korrelation:** je Request eine `reqId`; Antworten via `TaskCompletionSource` in einem
`ConcurrentDictionary<int, TCS>` auflösen. Timeout je Request.
- **`GetQuoteAsync(symbol)`** → Kontrakt (`reqContractDetails`/`reqMatchingSymbols`) → Snapshot-Kurs
(`reqMktData` snapshot bzw. `reqTickByTickData`) → `Quote(Last, Bid, Ask)`.
- **`GetAccountStateAsync()`** → `reqAccountSummary` (NetLiquidation, AvailableFunds).
- **`PlaceOrderAsync(OrderRequest)`** → `Order` bauen (MKT/LMT, Menge, Side) → `placeOrder(orderId, contract, order)`;
Ergebnis über `orderStatus`/`openOrder`/`execDetails`-Callbacks einsammeln → `OrderResult`.
`nextValidId` liefert die Order-IDs.
- **Sicherheit bleibt:** `NullBrokerClient` bleibt Default; `IbkrBrokerClient` wird erst registriert
(via Config-Schalter), wenn verifiziert. Handel zusätzlich weiter durch `TradingEnabled`-Gate geschützt.
## Umsetzung (`src/IBKRTrader.Core/Trading/Ibkr/`)
Die TWS API ist **callback-basiert** (`EClientSocket` sendet, `EWrapper` empfängt Events). Das ist
hinter dem bestehenden async-Seam `IBrokerClient` gekapselt, aufgeteilt in drei Dateien:
| Datei | Aufgabe |
|---|---|
| `IbkrMapping.cs` | Reine Abbildung Core ↔ TWS (Kontrakt, Order, Kurs, Port-/Statusregeln) **unit-getestet** |
| `IbkrConnection.cs` | Socket-Lebenszyklus, Reader-Thread, reqId-Korrelation über `TaskCompletionSource` |
| `IbkrBrokerClient.cs` | Implementiert `IBrokerClient`, übersetzt Fehler in leere Ergebnisse |
**Ablauf je Methode**
- **`GetQuoteAsync`** → `reqContractDetails` (Ergebnis wird gecacht) → `reqMktData` als Snapshot →
`Quote`. Preis-Reihenfolge: letzter Handelspreis, sonst Bid/Ask-Mitte, sonst Schlusskurs.
- **`GetAccountStateAsync`** → `reqAccountSummary` (NetLiquidation, AvailableFunds).
- **`PlaceOrderAsync`** → Kontrakt auflösen → `placeOrder`; das Ergebnis kommt über `orderStatus`,
die Order-IDs liefert `nextValidId`.
**Bewusste Entscheidungen**
- **Träges Verbinden mit Wiederholung statt Verbindungsaufbau beim Start.** TWS ist nach einem
Neustart minutenlang nicht bereit (erst abgelehnte Verbindungen, dann abgebrochene Handshakes);
ein einmaliger Versuch beim Programmstart würde das nicht überleben.
- **Port-Prüfung gegen den Handelsmodus.** Paper-Modus auf einem Live-Port (oder umgekehrt) wird
abgelehnt, der Broker bleibt dann inaktiv sonst würde echtes Geld bewegt, wo ein Test erwartet wird.
- **Fehler werden nie geworfen, sondern zu leeren Ergebnissen.** Konto 0 lässt die Risikoprüfung
jedes Signal ablehnen die sichere Richtung bei nicht erreichbarem Broker.
- **`MarketDataType` standardmäßig 4 (verzögert + Frozen).** Paper-Konten ohne Datenabo bekommen
sonst überhaupt keine Kurse, und der `ExecutionService` überspringt jedes Signal mit „kein Kurs".
- **Zwei Sicherungen bleiben:** `UseTwsApi` entscheidet über den Adapter, `TradingEnabled` über den
Handel. Ohne den zweiten Schalter platziert der `ExecutionService` keine Order.
### Bekannte Grenze: Orders ohne sofortige Ausführung
`IBrokerClient.PlaceOrderAsync` ist synchron gedacht (Ausführung oder Fehlschlag). Eine Order, die
innerhalb von `OrderTimeoutSeconds` keinen Endstatus erreicht etwa eine Limit-Order im Orderbuch
oder eine Market-Order außerhalb der Handelszeiten , wird als Fehlschlag zurückgegeben, **kann bei
IBKR aber weiterhin aktiv sein**. Die Meldung enthält deshalb Order-ID und letzten Status. Sauber
lösen ließe sich das nur mit asynchroner Fill-Verfolgung (Order-Zustand persistieren, `orderStatus`
und `execDetails` dauerhaft mitschreiben) das ändert den Seam und ist eigene Arbeit.
## Marktdaten
Das Paper-Konto hat **kein Marktdaten-Abo**. TWS meldet deshalb bei jeder Kursanfrage den Hinweis
**10167** („nicht abonniert, es werden verzögerte Marktdaten angezeigt") und liefert danach ganz
normal verzögerte Ticks. Das ist ein **Statushinweis, kein Fehler** wird er als Fehler behandelt,
bricht die Kursanfrage ab, bevor die Ticks eintreffen, und jedes Symbol liefert „kein Kurs".
`IbkrMapping.IsInformational` deckt den Code deshalb ausdrücklich ab (mit Test).
Verifiziert am 2026-07-31 gegen DUR371528: AAPL 302,94 / MSFT 476,88 / NVDA 209,81 (verzögert).
## Optionen (Berechtigung verifiziert 2026-08-04)
Das Paper-Konto **DUR371528 ist für den Optionshandel freigeschaltet**. Geprüft ohne jede Ausführung
über eine **What-If-Order** (`Order.WhatIf = true`): IBKR rechnet dabei Margin und Gebühren durch die
komplette Prüfkette inklusive Handelsberechtigung und verwirft die Order anschließend.
| Prüfung | Ergebnis |
|---|---|
| `reqSecDefOptParams` AAPL | 24 Verfallstermine, 127 Strikes (5600), Multiplier 100, TradingClass AAPL |
| Börsen | SMART, CBOE, ISE, EMERALD, NASDAQBX, PHLX, AMEX, MEMX, PSE |
| Kontrakt `AAPL 20260812 C302.5` | aufgelöst, ConId 906106141, LocalSymbol `AAPL 260812C00302500` |
| What-If BUY 1 Kontrakt | **angenommen**, Init-Margin 0 → 589,52 |
| What-If BUY 1 AAPL (Aktie) | **angenommen**, Init-Margin 0 → 87,61, Kommission 1,00 USD |
Fehlte die Berechtigung, hätte IBKR die What-If-Order mit einem Berechtigungsfehler abgelehnt statt
eine Margin zu liefern. Für **Realtime**-Optionskurse wäre zusätzlich ein OPRA-Abo nötig; ohne Abo
kommen verzögerte Daten (siehe Marktdaten unten). Modul-Konzept:
[konzepte/KONZEPT-Modul-OptionsWheel.md](konzepte/KONZEPT-Modul-OptionsWheel.md).
> **What-If als Testwerkzeug:** Damit lässt sich der gesamte Orderpfad bis zur Broker-Annahme prüfen,
> ohne eine Position zu eröffnen. Der Adapter nutzt es nicht produktiv für Vorabprüfungen
> (Margin-Deckung vor einer echten Order) wäre es aber ein naheliegender Ausbau.
### Historie: welcher Gateway?
Aktuell laufen die Marktdaten-Worker über die **Client Portal Web API** (`IBKRGatewayService`).
Optionen (später entscheiden):
Später zu entscheiden:
- Broker (Quotes/Konto/Orders) → TWS API; Marktdaten-Historie **vorerst auf CP Web API belassen**, oder
- Historie ebenfalls auf TWS (`reqHistoricalData`) migrieren → nur noch ein Gateway.
## Testbarkeit
- Reine Logik (Order-/Kontrakt-Mapping, Response-Parsing) → Unit-Tests möglich.
- Verbindung/Quotes/Orders → **manuell gegen das Paper-Gateway**, sobald verfügbar.
## Konfiguration (`settings.json`, Abschnitt `IBKR`)
## To-do bei Gateway-Zugang
1. `IB.TWS.CSharpApi` (+ NuGet-Allowlist) hinzufügen.
2. `IbkrBrokerClient` implementieren (Design oben), hinter Config-Schalter registrieren.
3. `TradingSettings.Mode` → Port 4002/4001; `IBKRSettings.ClientId` nutzen.
4. Gegen Paper verifizieren: Verbindung → Quote → Konto → Test-Order (Paper) → Buchung.
5. IBC für Auto-Login/Neustart einrichten.
| Schlüssel | Default | Bedeutung |
|---|---|---|
| `UseTwsApi` | `false` | Aktiviert den echten Adapter; sonst `NullBrokerClient` |
| `Host` / `Port` / `ClientId` | `127.0.0.1` / `4002` / `1` | Verbindung; Port muss zum Handelsmodus passen |
| `MarketDataType` | `4` | 1 Realtime, 2 Frozen, 3 verzögert, 4 verzögert+Frozen |
| `ConnectTimeoutSeconds` | `15` | Socket-Verbindung und Handshake |
| `RequestTimeoutSeconds` | `15` | Kurs-, Kontrakt- und Kontoantworten |
| `OrderTimeoutSeconds` | `60` | Wartezeit auf den Endstatus einer Order |
## Testbarkeit
- `IbkrMapping` ist vollständig unit-getestet (`tests/.../IbkrMappingTests.cs`): Port-/Modus-Regeln,
Kontrakt- und Order-Aufbau, Kursableitung, Tick- und Statusklassifizierung.
- Verbindung, Kurse und Orders bleiben **manuelle Verifikation gegen das Paper-Gateway** siehe
[TWS-Setup-Checkliste](TWS-Setup-Checkliste.md), Abschnitt Verifikation.
## Offen
1. `PlaceOrderAsync` gegen das Paper-Konto verifizieren (Order → Fill → Buchung).
2. IBC für Auto-Login/Neustart einrichten (Server-Betrieb).
3. Asynchrone Fill-Verfolgung, siehe „Bekannte Grenze" oben.
4. Entscheiden, ob die Marktdaten-Historie von der CP Web API auf `reqHistoricalData` wandert.