Commit Graph
9 Commits
Author SHA1 Message Date
RichardandClaude Opus 5 2ca5bc6553 Reconnect laesst keinen Lesethread mehr zurueck (#504, #505)
Nach jedem Verbindungsaufbau lief ein weiterer IBKR-Lesethread mit: Die
Schleifenbedingung "while (_socket.IsConnected())" ist nach einem Reconnect
wieder wahr, weil dasselbe Socket-Objekt neu verbunden wird. Auf der Test-VM
waren es nach zwei TWS-Neustarts drei Threads gleichzeitig.

Alle warten auf demselben EReaderMonitorSignal, und ein Signal weckt genau
einen. Weckt es den falschen, bleibt die Nachricht in der Warteschlange eines
anderen Readers liegen. Bei laufend wiederholten Push-Daten faellt das nicht
auf, die einmalige Antwort auf reqPositions laeuft in die Frist - der Dienst
sieht gesund aus und ist blind. Zeitueberschreitungen je Tag: 1, 3, 215, 96.

Jeder Lesethread bekommt jetzt eine Generationsnummer. Veraltete beenden sich
beim naechsten Aufwachen und reichen das verbrauchte Signal weiter, damit die
Nachricht des Nachfolgers nicht liegen bleibt.

Dazu ein Zwischenspeicher fuer die Handelszeiten (#505): dieselbe Auskunft
wurde viermal je Stunde und Instrument neu angefragt, obwohl sie sich
hoechstens taeglich aendert. Gueltig bis zum Ende des gemeldeten Zeitraums;
Fehlschlaege werden bewusst nicht gemerkt, sonst bliebe ein Modul nach einer
einzigen Stoerung bis zum Neustart stumm.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:43:09 +02:00
RichardandClaude Opus 5 362207c6f2 Handelszeiten im Core: DebugTrading handelt nicht mehr in die geschlossene Boerse
Das Modul erzeugte ueber das Wochenende Dutzende wartender Orders - alle 15
Minuten eine, alle von IBKR angenommen und fuer Montag 09:00 vorgemerkt, alle
gleichzeitig ausloesend. Am 2026-09-06 waren es 31.

Die Auskunft gehoert in den Core, nicht ins Modul: Handelszeiten braucht jede
Strategie, und sie ist nichts, was ein Modul selbst wissen kann.

- MarketHours.cs: MarketSession, MarketSchedule (IsOpen, NextOpenUtc), der
  Parser TradingHours und der Seam IMarketCalendar. Reine Rechnung, damit die
  Formate ohne Broker pruefbar sind - 20 Tests ueber beide Schreibweisen der
  TWS-API, Feiertage, Zeitraeume ueber Mitternacht und unbekannte Zonen.
- Die Zeiten kommen aus derselben Kontraktabfrage, die der Adapter ohnehin
  macht: contractDetails liefert sie mit, eine eigene Runde waere Verschwendung.
- LiquidHours und nicht TradingHours. Letzteres enthaelt bei US-Werten die vor-
  und nachboersliche Zeit; unsere Orders setzen OutsideRth nicht, wuerden dort
  also gar nicht ausgefuehrt, sondern warten - genau der Zustand, den das hier
  verhindern soll.
- Keine Ersatzzeitzone, wenn TWS eine unbekannte meldet. Mit der falschen Zone
  gerechnete Handelszeiten saehen richtig aus und laegen um Stunden daneben.
- Unbekannt gilt als geschlossen. Wer aus fehlender Auskunft auf einen offenen
  Markt schloesse, handelte bei jeder Stoerung ins Geschlossene.

DebugTrading.OnlyDuringMarketHours, Vorgabe an. Die Pruefung gilt fuer beide
Richtungen: Auch eine Schliessung erzeugte dieselbe wartende Order. Eine
Testposition ein paar Stunden laenger zu halten kostet dagegen nichts - und die
nicht geschlossene Position belegt ihren Platz weiter, sonst eroeffnete die
Planung ueber die eigene Grenze hinaus.

Auf der Testmaschine am Sonntagabend nachgewiesen:
  [20:31:28] Kein Instrument handelbar, naechste Eroeffnung SAP:EUR um
             2026-09-07 07:00 UTC.
07:00 UTC ist 09:00 MEZ - dieselbe Zeit, die IBKR heute frueh im Warntext 399
genannt hat. Zwei unabhaengige Quellen, gleiche Antwort. Seither keine neue
Order; der Lauf dauert zwei statt neunzig Sekunden.

524 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 20:33:45 +02:00
RichardandClaude Opus 5 e7e3970dec Orders, die beim Broker liegen, gelten nicht mehr als gescheitert
Am geschlossenen Markt gemessen: IBKR nimmt eine Order an und merkt sie fuer
die naechste Eroeffnung vor. Dazu schickt es Code 399 - eine Mitteilung, deren
Text mit "Warning" beginnt. Der Adapter las jede Meldung aus dem Fehler-Callback
als Ablehnung, meldete "Order abgelehnt", und das Kapitalmodell gab die Bindung
frei. Auf dem Testkonto lagen dadurch 67 Orders ueber 306 BAS, 140 SAP und 43 ALV
beim Broker, vorgemerkt fuer Montag 09:00, ohne eine einzige Reservierung dahinter.

OrderResult kannte nur "erfolgreich" und "gescheitert". Der haeufigste Fall
passte in keines von beiden. Es gibt jetzt einen dritten Ausgang - Working:
abgesendet, Ausgang offen. Die Trennlinie ist, ob BEWIESEN ist, dass beim Broker
nichts liegt; ohne Beweis bleibt das Kapital gebunden. Eine Bindung, die zu lange
steht, kostet Spielraum und faellt auf. Eine, die zu frueh verschwindet, kostet
Geld und faellt nicht auf.

- 399/404 sind Ordermeldungen, keine Ablehnungen; nur 201/202/203 u. a. beenden
  eine Order beweisbar. Ein Verbindungsabbruch beendet unsere Sicht, nicht die Order.
- IsDeadStatus ist enger als IsTerminalStatus: "Inactive" ist kein Beweis.
- ExecutionService haelt die Bindung bei Working (MarkSubmittedAsync statt
  CancelReservationAsync) und journalisiert Pending/OrderWorking statt Failed.
- Neu lesbar: das Orderbuch des Brokers (reqAllOpenOrders) samt Storno.
  --open-orders stellt Orderbuch und eigene Bindungen gegenueber,
  --cancel-order <ID>|all --confirm nimmt zurueck (Sammelstorno nur im Paper-Modus).
- Der Wiederanlauf-Abgleich lief bisher gegen eine leere Orderliste und haette
  ab jetzt jede gesunde Bindung in Quarantaene gestellt. Er sitzt jetzt im
  ReservationWatchdogWorker, hinter dem Beweis, dass der Broker antwortet.

457 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:27:32 +02:00
RichardandClaude Opus 5 1dac482cc7 DebugTrading: ein Modul, das absichtlich Last erzeugt
Alles, was den Handel absichert - Limits, Deckung, Kapitalpool, Reservierungen,
Abgleich, Sperren - war bisher ausschliesslich durch Unit-Tests mit gemockten
Gegenstellen belegt. Im Betrieb gab es fast keine Trades. Ein Fehler in der
Buchungskette faellt so erst auf, wenn eine echte Strategie scharf geht.

Das Modul eroeffnet kleine Positionen, haelt sie eine gewuerfelte Zeit von
Minuten bis Tagen und schliesst sie selbst. Es soll keine Rendite erzeugen,
sondern Last; die Auswahl ist zufaellig und ausdruecklich nicht renditegetrieben.

Planung und Sicherungen sind reine Rechnung mit einer von aussen gereichten
Zufallsquelle - genau deshalb ist ein Modul, das absichtlich wuerfelt, ueberhaupt
pruefbar. 14 Tests.

Die Sicherung, die nicht verhandelbar ist: Der Worker prueft bei JEDEM Lauf, ob
der Handelsmodus eindeutig Paper ist, und wirft sonst. Nicht nur beim Start -
Einstellungen werden kopiert, wenn eine Instanz umzieht. Geprueft wird gegen den
Rohwert UND den geparsten Modus: TradingSettings.ParsedMode faellt bei einem
unbekannten Wert auf Paper zurueck, und darauf darf sich diese Pruefung nicht
verlassen. Dazu Tagesbudget (in Betriebszeitzone, nicht UTC), Obergrenze offener
Positionen, Grenze je Trade und ein Not-Aus, der das Eroeffnen stoppt und offene
Positionen weiter betreut.

Schliessen hat Vorrang vor Eroeffnen: Sonst koennte eine erreichte Obergrenze
verhindern, dass faellige Positionen je geschlossen werden - die Sperre bliebe
fuer immer.

Beim Bauen hat der Architekturtest aus #474 seinen ersten echten Fang gemacht:
Das Modul griff nach IBrokerClient, um Kurse zu holen. Das verstoesst gegen
Regel 4. Statt den Test aufzuweichen ist eine lesende Kursquelle entstanden
(IQuoteSource) - wer einen Kurs braucht, soll dafuer nicht den Zugang bekommen,
mit dem sich Orders platzieren lassen.

Auf dem Testserver nachgewiesen: Das Modul laeuft im Takt, plant, wird bei
fehlendem Kurs sauber abgelehnt und meldet das - und mit Trading.Mode=Live
verweigert es den Dienst, waehrend unabhaengig davon auch der Broker-Adapter
den Paper-Port gegen den Live-Modus sperrt. Zwei Schichten, die nichts
voneinander wissen.

438 Tests gruen.

Arbeit an #468.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 14:27:58 +02:00
RichardandClaude Opus 5 0feeeba815 Optionskette und Bewertungsgroessen im Core
Der Core kennt jetzt die handelbaren Verfallstage und Strikes eines Basiswerts
(reqSecDefOptParams) und die Bewertungsgroessen eines Kontrakts
(tickOptionComputation). Damit steht der letzte Baustein, den eine
Wheel-Strategie braucht, um ueberhaupt einen Kontrakt auswaehlen zu koennen.

OptionChain ist bewusst nur eine Liste und trifft keine Marktentscheidung. Die
Auswahlhilfen daran sind reine Rechnung - "der Verfall bei etwa 35 Tagen" ist
eine Kalenderfrage, "welches Delta will ich" nicht. Letzteres gehoert ins Modul.

Zwei Eigenheiten der TWS-API sind beim Messen aufgefallen und haben je einen
Fehler gekostet:

1. Greeks kommen NICHT im Snapshot. reqMktData mit snapshot=true liefert Geld-,
   Brief- und letzten Kurs, aber keine tickOptionComputation. Es braucht ein Abo,
   das beim ersten Modell-Tick wieder abbestellt wird.

2. TWS meldet je Basiswert MEHRERE Serien. Fuer AAPL kamen "AAPL" und "2AAPL" -
   letztere eine Sonderserie mit eigenen Ausuebungspreisen. Der erstbeste Satz
   war die falsche: Strike 320 in "2AAPL" gibt es nicht, TWS antwortete mit 200
   ("No security definition has been found"). Die Auswahl nimmt jetzt SMART vor
   allen anderen Boersen und darin die Handelsklasse, die dem Symbol entspricht.

Massgeblich ist ausserdem allein das Modell-Feld (13 bzw. 83 bei verzoegerten
Daten). Die Varianten zu Geld-, Brief- und letztem Kurs fehlen bei illiquiden
Serien und lieferten sonst ein Delta aus einem Kurs, den niemand stellt. Fehlende
Werte meldet TWS als double.MaxValue oder -1; sie werden zu null, nicht zu 0 -
ein Delta von 0 hiesse "reagiert nicht auf den Basiswert".

Gemessen gegen das Paper-Gateway:
  AAPL (USD, SMART/AAPL, x100): 22 Verfallstage, 123 Strikes
  Auswahl wie beim Wheel: AAPL|20261009|P|320 (Kurs 320.01, 34 Tage Laufzeit)
  Deckungsbedarf je Kontrakt: 32.000,00 USD
  IV 24.5 %, Delta -0.471, Gamma 0.0169, Vega 0.392, Theta -0.123 (verzoegert)

Der Schalter --option-chain SYMBOL [--currency X] faehrt genau diesen Ablauf und
bleibt als Pruefwerkzeug. 414 Tests gruen.

Behebt #299.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 13:52:27 +02:00
RichardandClaude Opus 5 9e0d3b229a Kursabfrage nimmt das Instrument statt nur das Symbol
Ohne Waehrung wird jeder Kontrakt als USD gesucht: ein Wert, der nur in
EUR notiert, ist dann "nicht aufloesbar", und die Ursache steht
nirgends. Beim Order-Nachweis gegen MIGA:EUR genau so aufgetreten - die
Kontraktaufloesung fuer die ORDER war bereits waehrungsfaehig, die fuer
den KURS nicht, und damit brach die Kette eine Stufe vorher ab.

Dieselbe Aenderung ist die Voraussetzung fuer Optionskurse (#299): ohne
Kontraktangaben laesst sich eine Option ueberhaupt nicht abfragen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 15:41:26 +02:00
RichardandClaude Opus 5 084cd7ef77 Options-Fundament und Kapitalmodell im Core; Modul OptionsWheel
Build & Test / build (ubuntu-latest) (push) Canceled after 0s
Build & Test / build (windows-latest) (push) Canceled after 0s
Das OptionsWheel setzt auf Dingen auf, die es im Core nicht gab. Diese
Sitzung baut sie.

#298 Optionen als Instrument. InstrumentKind/OptionSpec und ein
druckbarer Instrumentenschluessel ("AAPL|20260918|C|150"); TradeSignal
und OrderRequest tragen ein optionales OptionSpec, ohne das sich der
Aktienpfad unveraendert verhaelt. Position.Notional rechnet ab jetzt mit
der Kontraktgroesse - vorher war jede Option um Faktor 100 zu klein
bewertet und damit jedes Risikolimit wirkungslos, lautlos und in die
gefaehrliche Richtung. Daneben neu BoundCapital: ein Short Put bindet
Strike x Kontraktgroesse, nicht die vereinnahmte Praemie.

#300 sell-to-open. Jeder Auftrag zerfaellt in einen schliessenden und
einen eroeffnenden Teil; der schliessende wird von keinem Limit
verhindert - auch nicht vom Kaufstopp, denn ein Rueckkauf baut eine
Verpflichtung ab. Deckungspruefung im Core, nicht im Modul: Short Call
nur mit freien Aktien, Short Put nur mit reservierbarem Bargeld.

#302-#305 Kapital- und Buchmodell. Buecher mit reservierten Namen und
Eigentumsregeln, Pool mit Caps und Headroom, Margin-Sperre in vier
Schichten, core_reservation mit Lebenszyklus, Frist, Idempotenz ueber
(SignalId, ContractKey) und Quarantaene beim Start.

#309/#310 Modul OptionsWheel: ow_-Schema mit fuenf Tabellen,
Zustandsautomat, StrikeSelector, RollDecider, PremiumMath - vollstaendig
ohne Broker testbar. Der Handelsbetrieb bleibt gesperrt, solange #299
und #294 fehlen; das Modul sagt das ueber GetActivationBlocker, statt
still nichts zu tun.

#293 --verify-order am Daemon: eine Order durch die vollstaendige Kette
(Buch, Risiko, Bindung, Order, Fill, Buchung, Abschluss). Ohne --confirm
ein Trockenlauf; Abbruch ausserhalb von Paper-Modus UND Paper-Port.

Drei Widersprueche in den Konzepten sind dabei entschieden worden:
Schicht B heisst ab jetzt "kein UNGEDECKTER Short" (der Grund der Regel
war der Kredit, nicht der Short), die Hausreserve wird einmal abgezogen
statt zweimal, und der Positionsschluessel ist eine Spalte statt eines
Sechser-Verbunds (MySQL verlangt NOT NULL fuer Schluesselspalten).
Begruendungen stehen in docs/ROADMAP.md.

Enthaelt ausserdem die noch nicht committete Arbeit aus der vorigen
Sitzung: die Fehlerbehebungen aus Epic X (WorkerBase-Intervall,
BackupWorker, --check gegen die Datenbank, CapitolTrades-429, SIGTERM)
samt DatabaseEndpoint, MysqlDumpTool und der Roadmap-Umstellung. Sie lag
im Arbeitsbaum und liess sich nicht abtrennen, weil beide Staende
dieselben Dateien beruehren.

213 -> 369 Tests, Build ohne Warnung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 09:35:38 +02:00
RichardandClaude Opus 5 b96a20788c R10: Lesender Bestandsabgleich (IBrokerPortfolioReader) + Datenlage-Konzepte
Eigener Seam neben IBrokerClient: Wer handelt, braucht ihn nicht; wer die
eigene Buchfuehrung gegen den Broker abstimmt, braucht nur ihn. Zuteilung
und Verfall aendern Positionen ohne Order von uns - ohne Abgleich laeuft
das Managementbuch zwangslaeufig auseinander.

- IBrokerPortfolioReader mit GetPositionsAsync/GetExecutionsAsync;
  implementiert von IbkrBrokerClient und NullBrokerClient (DI registriert
  beide Rollen auf derselben Instanz).
- IbkrConnection: reqAccountUpdates statt reqPositions (nur dieser Weg
  liefert Marktwert und unrealisierten G/V), reqExecutions inkl. Zuordnung
  der verspaetet eintreffenden commissionReport-Callbacks ueber die ExecId.
- BrokerPosition/BrokerExecution als Broker-Wahrheit neben Position;
  IbkrMapping: ParseSide, ParseExecutionTime, FormatExecutionFilterTime
  (UTC wegen TWS-Warnung 2174) - mit Unit-Tests.
- Verifiziert gegen Paper-Konto DUR371528: 2 Positionen, 2 Ausfuehrungen
  inkl. Kommissionen.

Doku: Kapital- und Buchmodell (drei Wahrheiten, Kapitalzuteilung),
KONZEPT-Datenlage-und-Strategien (gemessen, was die API auf diesem Konto
liefert). Options-Wheel: Greeks bei verzoegerten Daten funktionieren
(Feld 83); Earnings-Termine sind ueber die TWS API nicht erreichbar
(Fehler 10358) - Behelf ueber IV-Filter statt Fremddatenquelle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:51:51 +02:00
RichardandClaude Opus 5 80afcd49c1 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>
2026-08-04 17:40:00 +02:00