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:
+103
-33
@@ -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 (5–600), 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.
|
||||
|
||||
Reference in New Issue
Block a user