Im Logverzeichnis der Test-VM lagen neben 2026-09-03.jsonl auch 1405-06-12.jsonl
und 2569-09-03.jsonl - dieselben Tage im Sonnen-Hidschra- und im buddhistischen
Kalender. Eine interpolierte Zeichenkette formatiert mit CurrentCulture, und die
bringt ihren Kalender mit. Betroffen waren beide Schreibpfade des LoggingService
und die Lesestelle im DossierService, die denselben Namen wieder zusammensetzt.
CA1305 greift hier nicht: die Regel sieht string.Format und ToString, nicht die
interpolierte Form. Die .editorconfig deckt diesen Fall also nicht ab.
FormattableString.Invariant an allen drei Stellen, dazu ein Regressionstest ueber
fa-IR, th-TH und de-DE - mit Gegenbeweis, dass die naive Form tatsaechlich
abweicht, damit der Test nicht eines Tages unbemerkt trivial wird.
Ausserdem: Verweise auf die Bugtracker-Nummern richtiggestellt (488/489).
461 Tests gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
Vier Stufen je Position (frei, nur schliessen, nur aufbauen, gesperrt) und je
Instrument ein Handelsverbot samt harter Bestandsobergrenze. Beides gilt fuer
Module; der manuelle Handel in TWS bleibt unberuehrt.
Die Pruefung sitzt im RiskService, nicht im Modul: Eine Sperre, die ein Modul
selbst pruefen muesste, ist keine Sperre, sondern eine Bitte. Sie laeuft NACH der
fachlichen Bewertung, weil erst dort feststeht, welcher Teil eines Auftrags
eroeffnet und welcher schliesst - genau daran haengen die Stufen.
Drei Entscheidungen, die im Betrieb zaehlen:
"Gesperrt" verhindert auch den Ausgang. Eine Position, die aus Risikogruenden
geschlossen werden muesste, bleibt liegen, bis jemand die Sperre loest. Das ist
gewollt und steht als eigener Test da, damit es niemand fuer einen Fehler haelt.
Ein Handelsverbot sperrt das Eroeffnen, nicht das Schliessen. Ein nachtraeglich
gesetztes Verbot darf keine Position einsperren, die man gerade loswerden will;
wer auch das will, nimmt die Positionssperre.
Die harte Stueckzahl zaehlt den Bestand ueber ALLE Buecher, MANUAL eingeschlossen,
und Short-Positionen mit ihrer Groesse statt ihrem Vorzeichen: "hoechstens 100"
meint die Groesse der Verpflichtung. Sie ersetzt das prozentuale
Konzentrationslimit nicht - der Prozentsatz waechst mit dem Konto, die Stueckzahl
ist eine Aussage ueber ein bestimmtes Papier. Es zieht die engere Grenze.
TradingRuleService haelt die Regeln im Speicher, weil sie bei jeder Risikopruefung
gebraucht werden, sich aber selten aendern. Laesst sich die Liste NICHT laden,
gilt das nicht als "keine Sperren": der Dienst meldet es und sperrt, bis sie
wieder lesbar ist. Eine Sperre, die bei einem Datenbankfehler verschwindet, waere
keine. Ein unlesbarer Stufenwert wird ebenso als schaerfste Stufe gelesen.
Auf dem Testserver angewendet (Tabellen core_position_lock und
core_instrument_rule) und der Dienst damit neu gestartet - keine Ladefehler, der
Handel bleibt frei. Zur Probe stehen dort die beiden Beispiele des Betreibers:
WIRECARD gesperrt, NVDA hoechstens 100 Stueck.
425 Tests gruen, 11 davon neu.
Arbeit an #485 und #486; die Oberflaeche fehlt noch, die Durchsetzung steht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Zwei Befunde aus dem ersten Dauerbetrieb auf dem Testserver.
#487: Der Fix zu #389 hatte nur den Core auf DatabaseEndpoint umgestellt. Alle
vier Modulkontexte lasen die Konfiguration weiter selbst und landeten auf dem
Platzhalter aus EffectiveConnectionString, sobald die Datenbank nur in
settings.json steht - "Access denied for user 'root'@'localhost'", waehrend der
Core im selben Prozess sauber arbeitete. Gefunden 30 Sekunden nach dem ersten
Start des Dienstes; kein Test und kein --check hatte es gesehen, weil --check
nur die Core-Verbindung oeffnet.
Die Kontexte loesen DatabaseEndpoint jetzt ueber die IServiceProvider-Ueberladung
von AddDbContextFactory auf. Der Platzhalter meldet sich ausserdem nicht mehr als
root an, sondern als "KEINE-DATENBANK-KONFIGURIERT" - die alte Meldung sah nach
einem Rechteproblem aus, obwohl gar keine Verbindung konfiguriert war.
#396 traegt NICHT. Die Pruefung auf der VM zeigt: Das Signal kommt an, der
Handler laeuft (Logzeile steht), aber der Prozess bleibt stehen. Zwei Ursachen
sind dabei aufgedeckt und behoben worden, eine dritte ist offen:
1. Ueber Avalonia.Application.Current war die Lebensdauer aus dem Signal-Thread
nicht erreichbar - der Handler lief ins Leere. Sie wird jetzt im Hauptthread
erzeugt (SetupWithLifetime + lifetime.Start) und dem Handler mitgegeben.
2. Die Sicherheitsabfrage der Shell fing das Schliessen ab und wartete auf einen
Klick, den bei einem Signal niemand macht. Ein Signal ist keine Frage; der
Handler uebergeht die Abfrage jetzt ausdruecklich.
3. OFFEN: Der Dispatcher-Rueckruf wird nicht ausgefuehrt - weder die Erfolgs-
noch die Fehlerzeile erscheint, obwohl beide protokolliert wuerden. Der
UI-Thread nimmt die Nachricht offenbar nicht an.
Die Messpunkte bleiben im Code: ohne sie war von aussen nicht zu unterscheiden,
ob das Signal ankommt, ob die Lebensdauer fehlt oder ob der Rueckruf haengt.
404 Tests gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Regel 3 verlangt, dass die Buchzuordnung serverseitig gesetzt wird und
TradeSignal.SourceModule nicht vertrauenswuerdig ist. Bisher war nur der
unbekannte Name abgefangen; ein Modul konnte weiterhin das Kuerzel eines
ANDEREN registrierten Moduls eintragen und dessen Cap, dessen Konzentration und
dessen GuV belasten.
Jedes Modul bekommt jetzt einen an sein Buch gebundenen IModuleExecution-Zugang
statt des gemeinsamen IExecutionService. Der Zugang ueberschreibt die Herkunft
mit seinem eigenen Buch - was im Signal steht, spielt keine Rolle mehr.
Der Zugang steht ausdruecklich NICHT im Container: was dort steht, kann jedes
Modul aufloesen, auch den Zugang eines anderen. Der Host uebergibt stattdessen
eine Fabrik beim Registrieren (neue Ueberladung von IModule.RegisterServices mit
Vorgabe, damit ein nicht handelndes Modul nichts davon wissen muss), und das
Modul reicht den erzeugten Zugang direkt an seine Strategie.
Regel 4 - Module bekommen den Broker nicht per DI - war eingehalten, aber nicht
erzwungen. Jetzt zwei Tests ueber alle vier Modul-Assemblies: der eine prueft
per Reflection Konstruktorparameter, Felder und Eigenschaften auf IBrokerClient,
IBrokerPortfolioReader und IExecutionService; der andere durchsucht die
Modulquellen nach dem zweiten Weg, dem Griff in den Container. Beide Gegenproben
mit einem absichtlichen Regelbruch gefahren: jeder Test schlaegt bei genau
seinem Weg an.
Getrennte Container je Modul waeren der harte Weg, aendern aber die
Modularchitektur. Der Fall, um den es geht, ist ein NEUES Modul, das den Core
versehentlich umgeht - und den faengt der Test beim Uebersetzen.
404 Tests gruen, auf Windows und auf der Test-VM.
Behebt #474, damit ist auch #302 vollstaendig.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Vorhaben des Betreibers, beide mit demselben Kern: die Durchsetzung
gehoert in den Core, die Oberflaeche ist nur die Bedienung. Eine Sperre, die
ein Modul selbst pruefen muesste, ist keine Sperre.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zuteilung und Verfall aendern Positionen ohne eine Order von uns - es gibt kein
Ereignis, an das sich ein Abgleich haengen koennte. Nach einem Verfallstag
fuehren wir Kontrakte, die es nicht mehr gibt; nach einer Zuteilung liegen
Aktien im Konto, die in keinem Buch stehen. Beides verschiebt still die
Deckungslage: Ein Covered Call, dessen Aktien in Wahrheit abgerufen wurden, ist
ab dann ein nackter Call.
PositionReconciler ist reine Rechnung und deutet die Differenz, statt nur Mengen
anzugleichen. Verschwindet ein geschriebener Put und tauchen zugleich Aktien des
Basiswerts auf, gehoeren diese Aktien dem Buch, das den Put geschrieben hat -
landeten sie in UNASSIGNED, verloere ein Wheel-Modul genau den Bestand, aus dem
es den naechsten Call schreiben muss. Der Einstand ist dabei der Strike, denn zu
genau dem Preis wurde geliefert. Passt die Aktienbewegung nicht zur Groesse der
Option, gilt sie NICHT als Zuteilung.
Verfall wird nur angenommen, wenn der Verfallstag erreicht ist. Ein Kontrakt,
der vorher verschwindet, ist eine Abweichung und wird als solche gemeldet - von
Hand geschlossen ist kein Verfall.
Die gefaehrlichste Stelle ist die leere Antwort: Die Leseschnittstelle liefert
bei gestoerter Verbindung eine leere Liste, die von einem leeren Konto nicht zu
unterscheiden ist. Solange wir selbst Positionen fuehren, gilt sie deshalb als
Stoerung. Ohne diese Sperre setzte ein Verbindungsabbruch jede Position auf null.
Gehandelt wird nie. Der Abgleich bucht ausschliesslich nach.
BrokerPosition traegt jetzt das Instrument (IbkrMapping.ToInstrument); ein
Kontrakt, den unser Modell nicht kennt - Future, Devise, Fonds -, liefert null
und wird uebersprungen statt als Aktie gedeutet.
Gemessen auf dem Testserver gegen das Paper-Konto: vier Altlasten erkannt
(GOOG, MIGA:EUR, die SAP-ADR aus dem Fehllauf und zwei ungebuchte SAP:EUR),
alle an UNASSIGNED gebucht, das Buch DIAG unangetastet. Gegenprobe danach:
"Bestand stimmt ueberein (5 Positionen)". 402 Tests gruen.
Erledigt den offenen Teil von #300, Kern von #306.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Solange jedes Instrument in USD notierte, war die Frage gegenstandslos. Mit
waehrungsfaehigen Instrumenten ist sie es nicht mehr: CapitalService summierte
BoundCapital ueber alle Positionen unabhaengig von deren Waehrung und verglich
das Ergebnis mit NetLiquidation in Kontobasiswaehrung. Dasselbe galt fuer die
Konzentration je Basiswert und fuer die offenen Reservierungen, die von einer
Groesse in Basiswaehrung abgezogen wurden.
Auf dem Testkonto ist die Basis EUR und USD steht bei 0,8611: jede USD-Position
ging bisher rund 16 Prozent zu hoch in das verteilbare Kapital ein. Die Richtung
des Fehlers wechselt mit dem Kurs - bei einer starken Fremdwaehrung wird die
Auslastung unterschaetzt und ein Buch darf mehr aufmachen als es soll.
Die Kurse kommen aus dem Ledger des Brokers ($LEDGER:ALL, Tag ExchangeRate),
nicht aus einer eigenen Quelle. Das ist Absicht: NetLiquidation und
TotalCashValue sind mit genau diesen Kursen gebildet. Mit einem fremden Kurs
verglichen wir den Positionswert gegen eine Kontogroesse, die anders gerechnet
wurde. Fuer die Buchhaltung gilt das Gegenteil - dort ist der amtliche
Tageskurs der richtige, das ist ein anderer Zweck (#321).
Fehlt ein Kurs, wird nicht geraten: der Snapshot meldet FxComplete=false, der
Positionswert gilt als Untergrenze, und der Kauf wird angehalten. Schliessen
bleibt moeglich. Eine Reservierung ohne Kurs wird dagegen roh angesetzt statt
uebersprungen - sie zu ignorieren hiesse, gebundenes Geld als frei zu fuehren.
Gemessen am Paper-Gateway: "Konto: Umrechnungskurse EUR 1, USD 0.8611
(Basis EUR)". 388 Tests gruen.
Behebt #480.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Maengel, sichtbar geworden beim Pruefen der vier Tabellen nach dem
Order-Nachweis (#293).
core_trade_history.Symbol war varchar(20) und traegt seit der
Optionserweiterung den Instrumentenschluessel statt des Ticker-Symbols.
"MSFT|20260918|P|302.5" sind 21 Zeichen - gekuerzt auf
"MSFT|20260918|P|302." liest sich das als Strike 302. Ein still
verfaelschter Eintrag in der Handelshistorie ist schlimmer als gar
keiner. Jetzt 64 wie core_position.ContractKey.
MarkSubmittedAsync rief niemand auf: im synchronen Pfad gibt es keinen
beobachtbaren Zeitpunkt zwischen Absenden und Endstatus. BrokerOrderId
blieb dadurch leer, und der Abgleich beim Neustart haette keine Bindung
einer Order zuordnen koennen - genau die Aufgabe, fuer die die Spalte da
ist. Die ID wird jetzt beim Abschluss festgehalten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Beim ersten erfolgreichen Order-Nachweis (#293) gegen das Paper-Konto
lief die Order auf die falsche Notierung: geprueft, bepreist und
reserviert wurde SAP:EUR (XETRA, 188,74 EUR), ausgefuehrt wurde die
SAP-ADR in New York zu 216,68 USD. Das USD-Guthaben rutschte dadurch von
-3.751 auf -4.185 - also genau der Margin-Kredit, den Schicht C
verhindern soll, aufgenommen an der Sicherung vorbei.
Ursache: ExecutionService baute die OrderRequest aus Einzelfeldern und
setzte Currency nicht; die Eigenschaft fiel still auf USD zurueck. Es
gab keine Fehlermeldung, und die Buecher wiesen anschliessend eine
EUR-Position aus, die es beim Broker nie gab.
Behebung nicht durch Nachtragen der Zuweisung, sondern baulich:
OrderRequest.For(instrument, ...) uebertraegt die Identitaet
vollstaendig. Wer die Felder einzeln setzt, baut ein zweites, halb
ausgefuelltes Instrument - das darf im Orderpfad nicht mehr moeglich
sein.
Zwei Regressionstests, die genau diesen Fall fangen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Die Waehrung gehoert zur Identitaet eines Instruments und nicht zu
seiner Beschreibung: derselbe Ticker kann an mehreren Plaetzen in
verschiedenen Waehrungen notieren, und ein Kauf darf nur aus Bargeld
derselben Waehrung finanziert werden (Kapitalmodell §4 Schicht C). Zwei
Notierungen als eine Position zu fuehren, wuerde Bestand und Deckung
vermischen.
USD bleibt im Instrumentenschluessel IMPLIZIT ("AAPL", nicht
"AAPL:USD"). Andernfalls waere jede Bestandszeile ab sofort ein anderes
Instrument als vorher und aus einer Position wuerden zwei. Trennzeichen
ist der Doppelpunkt und nicht der Punkt - Ticker enthalten Punkte
(BRK.B).
Neu: Currencies (kanonische Schreibweise an allen vier Stellen, die sie
gleich haben muessen), Currency an OptionSpec, InstrumentRef,
TradeSignal, OrderRequest und core_position. CapitalService nimmt die
Waehrung ab jetzt aus dem Instrument statt vom Aufrufer - wer sie
uebergeben koennte, koennte die Deckungspruefung gegen den falschen Topf
laufen lassen. --verify-order kennt --currency.
Anlass: das Paper-Konto haelt EUR 91.832 und USD -3.751. Ein
USD-Nachweis waere ein USD-Kredit gewesen und wurde von Schicht C
zu Recht abgelehnt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Beim ersten echten Order-Nachweis gegen das Paper-Konto (#293) lehnte
Schicht C jeden Kauf mit "Waehrungsdeckung [USD]: benoetigt 344,
verfuegbar 0" ab, obwohl das Konto Bargeld hat.
Ursache: das nackte "$LEDGER" liefert nur die Zusammenfassung in
Kontobasiswaehrung, und die traegt als Waehrungskennung "BASE" - also
genau den Wert, den die Auswertung ueberspringen muss, damit dasselbe
Geld nicht zweimal zaehlt. Die Aufschluesselung blieb dadurch leer, und
eine leere Aufschluesselung heisst fuer Schicht C bewusst "Deckung
unbekannt, also ablehnen". Mit ":ALL" kommen die Bestaende je Waehrung.
Dazu eine Logzeile mit dem, was der Broker gemeldet hat. Ohne sie ist
eine Ablehnung durch Schicht C von aussen nicht von einem echten
Geldmangel zu unterscheiden - man sieht "verfuegbar 0" und weiss nicht,
ob kein Geld da ist oder keine Auskunft kam. Genau diese Stunde hat der
Fund gekostet.
Gemessen am Paper-Konto DUR371528: EUR 91.832,22 / USD -3.751,01.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Der offene Stand lag ueber sieben Konzepte, zwei Referenzdokumente und die
Phasen-Checkliste der Architektur verteilt. Dieselbe Aufgabe stand teils
doppelt unter zwei Namen - die asynchrone Fill-Verfolgung etwa als "bekannte
Grenze" in IBKR-Integration.md und zugleich als W-2 im OptionsWheel-Konzept.
Wer wissen wollte, was als Naechstes ansteht, musste alles neun lesen.
docs/ROADMAP.md fuehrt das zusammen:
- Fuenf Stufen in Abhaengigkeitsreihenfolge, von "Fundament schliessen" bis
zum OptionsWheel, dazu vier laufende Bahnen (Auslieferung, Accounting,
Supervisor, technische Schulden).
- Jede Zeile traegt ihre Herkunft (W-2, P5, Kapitalmodell 5, ...), damit die
Herleitung im Archiv auffindbar bleibt.
- Eigene Abschnitte fuer Zurueckgestelltes und Verworfenes. Zurueckgestellte
Ideen nennen ausdruecklich, WAS sie wieder aktuell macht; verworfene nennen
den Grund, damit sie nicht in sechs Monaten erneut vorgeschlagen werden.
- Erledigtes bleibt als Zeile mit Datum stehen statt zu verschwinden.
Archiv (git erkennt alle sieben als Umbenennung, Historie bleibt):
docs/konzepte/* -> docs/archiv/
docs/Kapital-und-Buchmodell.md -> docs/archiv/
Jedes archivierte Dokument bekommt oben einen Vermerk, warum es erhalten bleibt
und wo der lebende Stand steht. Inhaltlich ist keines veraendert.
Weiter gepflegt werden ARCHITECTURE.md (Aufbau + Phasen-Historie),
IBKR-Integration.md (Adapter-Design und Grenzen) und die TWS-Setup-Checkliste -
das sind Referenzen, keine Planung. Ihre eigenen Offen-Listen verweisen jetzt
mit Roadmap-Kennung dorthin, statt einen zweiten Stand zu fuehren.
Alle 63 relativen Markdown-Links geprueft, keiner tot. Fuenf Pfadverweise in
Code, csproj und systemd-Unit mitgezogen; die Unit zeigte auf die
Portierungsanalyse, die ausdruecklich den Stand VOR dem Umbau beschreibt - jetzt
auf ARCHITECTURE.md. Build 0 Warnungen, 198/198 Tests gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alles Entfernte war nachweislich ohne Aufrufer. Build, 198/198 Tests, Smoke-UI
und Daemon-Prueflauf sind vor und nach jedem Schritt gruen.
Code:
- AIModelService: Platzhalter, der immer 0.5 lieferte. Im DI registriert,
aber nie irgendwo injiziert. Ordner Core/AI faellt mit weg.
- CtApiWrapper + CtMeta: JSON-Modelle fuer einen {data,meta}-Umschlag, den
CapitolTrades nicht mehr liefert. Der Scraper deserialisiert seit laengerem
direkt List<CtTrade>.
- IBKRGatewayService: DisconnectAsync, InitBrokerageSessionAsync und
SearchStocksBySymbolAsync. Der Dienst selbst bleibt - er versorgt
Instrument-Sync, Kurshistorie und den Watchdog-Heartbeat.
- Je eine Methode ohne Aufrufer: BudgetService.GetAvailableBudgetAsync,
TradeHistoryService.GetRecentTradesAsync, CongressRepository.
GetAllTradeIdsAsync und .ResetHistoryImportAsync, IbkrMapping.DefaultPortFor,
SecretProtection.IsEncrypted.
- CongressRepository bekam damit einen LoggingService injiziert, den es nicht
mehr benutzt - Abhaengigkeit samt Konstruktorparameter raus.
Ressourcen:
- 17 Symbole der WinForms-Oberflaeche entfernt. Das Wildcard-Muster im csproj
nahm sie in die Binaerdatei auf, ViewIcons.cs bildet aber nur sieben
Schluessel ab. Resources/ enthaelt jetzt genau die sieben.
NuGet-Allowlist:
- Dapper und HtmlAgilityPack sind seit R3 bzw. R1 aus dem Projekt raus,
Microsoft.WindowsDesktop.* seit L5. Muster entfernt.
- MySqlConnector und Newtonsoft.Json stehen NUR transitiv in den
Projektdateien und wurden zuerst mitentfernt - ein Restore in einen leeren
Paket-Ordner scheiterte darauf mit NU1100. Beide wieder aufgenommen, jetzt
mit Begruendung, damit der naechste Aufraeumlauf nicht dieselbe Falle tritt.
Dokumente an den tatsaechlichen Stand angeglichen:
- ARCHITECTURE: R2 fuehrte die Umstellung auf IHostedService als offen, obwohl
R4 sie erledigt hat. L6 und die Deploymentcenter-Phase fehlten ganz.
- DC-Konzept: Schritte 0-8 standen auf "dieser Durchlauf", sind aber umgesetzt.
Jetzt je Schritt der wirkliche Stand - inklusive der beiden Halbfertigen:
Update-PRUEFUNG laeuft, das Anwenden hat keinen Aufrufer; die
Release-Pipeline steht, ist aber nie gelaufen. P5 ist eingetreten.
- Accounting und Supervisor trugen keinen Umsetzungsvermerk, obwohl beide
Module gebaut sind. Vermerk nach dem Muster des Linux-Konzepts ergaenzt,
mit dem, was jeweils offen bleibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bindet Lizenz, Watchdog, Fehler-Stream, UpdateService und Erstinstallation an
das Deploymentcenter 2.5.1 an. Einbauort ist IBKRTrader.Hosting - den Host
teilen sich Shell und Daemon.
Projekt-Befunde aus dem Konzept vorab bereinigt:
P1 Echte DB-Zugangsdaten als Vorgabewerte in AppSettings -> Platzhalter.
Das alte Passwort steht weiterhin in der Git-Historie und ist als
kompromittiert zu behandeln (Rotation ist Nutzer-Aktion).
P2 AppPaths fiel unter Windows auf /etc/ibkrtrader zurueck, was .NET zu
C:\etc\ibkrtrader aufloest. Jetzt %ProgramData%\IBKRTrader.
P3 Globale Ausnahmebehandler (AppDomain / TaskScheduler) - vorher gab es
keinen Logeintrag, wenn der Prozess unbehandelt wegbrach.
P4 Version einmal zentral in Directory.Build.props statt zweimal hartkodiert.
Bewusste Abweichungen vom DC-Leitfaden, beide fuer ein handelndes System:
- Lizenz-Urteil fuehrt zum Sperrbetrieb (TradingEnabled=false) statt zu
Environment.Exit(1). Keine neuen Einstiege, aber Risiko-, Exit- und
Buchhaltungslogik laufen weiter.
- exitCurrentApp bleibt immer false; der Aufrufer beendet geordnet.
Das SDK haengt als Cross-Repo-ProjectReference am Schwester-Repo
Deploymentcenter (Interim, siehe Konzept 2.2). Damit ist P5 offen: die
Gitea-CI checkt das Schwester-Repo nicht aus und wird rot, bis der Bezug auf
ein NuGet-Paket umgestellt ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ResolveExecutionTime liefert neben dem UTC-Zeitpunkt jetzt die Herkunft der
verwendeten Zeitzone (gemeldet / angenommen / unbekannt / unlesbar). Bisher war
im Nachhinein nicht unterscheidbar, ob ein Buchungszeitpunkt von TWS stammte
oder eine Annahme war - genau der Fehler, der beim Umzug zwischen EU- und
US-Host lautlos entsteht.
IbkrConnection schreibt beim Verbinden einmalig Betriebszeitzone, Systemzeitzone
und den Versatz zur TWS-Serverzeit ins Log; ab 5 s Abweichung gilt die Uhr des
Hosts als verstellt.
TWS-Setup-Checkliste um den Linux-Abschnitt ergaenzt (Betrieb und Umgebung
unterscheiden sich, das Protokoll nicht).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Suffix ".Avalonia" gab es nur, weil daneben ein WinForms-IBKRTrader.App
stand. Das ist seit L5 weg, also faellt auch das Suffix. Git erkennt alle
Dateien als Umbenennung; Assembly, Wurzel-Namensraum und die
avares://-Ressourcen-URI sind mitgezogen.
Nebeneffekt der Umbenennung: die global::Avalonia-Qualifizierungen entfallen.
Sie waren noetig, weil der Namensraum IBKRTrader.App.Avalonia das
Avalonia-Paket verdeckt hat - ein Ueberbleibsel genau der Namensgebung, die
jetzt weg ist.
Inhaltlich falsch gewordene Aussagen berichtigt - das waren die eigentlichen
Ueberbleibsel, nicht die Kommentare:
- .agents/rules/grundregeln.md schrieb weiterhin "C# .NET 10 WinForms",
RichTextBox-Logging, LauncherForm und PropertyGrid vor. Das ist die Regel,
nach der kuenftig gearbeitet wird - sie haette die Portierung Stueck fuer
Stueck rueckgaengig gemacht. Jetzt: Avalonia, keine Plattform-Suffixe, die
11er-Pinnung mit Begruendung, dazu die beiden Regeln, die uns in L1b am
meisten gekostet haben (UTC persistieren + AppTimeZone statt DateTime.Now;
jede Formatierung mit ausdruecklichem IFormatProvider).
- Core: LogEntry ("wird in RichTextBox geschrieben"), IWorker/WorkerEngine/
WorkerInfo ("DataGridView-Zeile"/"-Binding"), ModuleView ("die
WinForms-Shell castet auf Form").
- Doku: ARCHITECTURE (Modul-Ui-Ordner, "designbare Forms mit Initialize"),
KONZEPT-Modul-Accounting ("UI (WinForms, ein Fenster mit Tabs)").
BEWUSST STEHEN GEBLIEBEN sind die Kommentare, die WinForms nur als
Begruendung nennen - warum LoggingService ein Ereignis hat statt einer
RichTextBox, warum ModuleView Func<object> liefert, warum es benannte
Record-Zeilentypen gibt, warum die Einstellungsmaske aus Attributen entsteht.
Das ist die Herleitung des heutigen Entwurfs; ohne sie sieht spaeter jede
dieser Stellen nach Umstaendlichkeit ohne Grund aus.
KONZEPT-Linux-Portierung.md bekommt einen Statusvermerk: umgesetzt, die
Pfadangaben im Fundstellenverzeichnis beziehen sich auf den alten Aufbau.
Zwei Abweichungen von der Schaetzung sind dort festgehalten - der geringere
Aufwand dank der PolytraderSharp-Vorlage, und dass die dort empfohlene
InvariantGlobalization ein Fehler gewesen waere.
Verifiziert: Build 0 Fehler/0 Warnungen, 193 Tests gruen, Smoke-UI
konstruiert alle 7 Ansichten + Launcher + Dialog, Daemon-Prueflauf OK,
publish -r linux-x64 fuer beide Einstiegspunkte fehlerfrei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Avalonia-Shell hatte in L4 Funktionsgleichheit erreicht, damit ist die
WinForms-Fassung ueberfluessig. Entfernt: IBKRTrader.App.csproj, Program.cs,
LauncherForm.cs, UI/ (14 Dateien) und Properties/Resources.* - zusammen rund
1.900 Zeilen. Rueckfallpunkt bleibt der Tag winforms-final.
Resources/ mit den 24 PNG-Symbolen bleibt: die Avalonia-Shell bindet sie
ueber AvaloniaResource ein und nutzt dieselben Symbolschluessel.
Kein Projekt traegt mehr eine Plattformbindung. Verifiziert: alle acht
Projekte bauen einzeln fuer linux-x64, und die gesamte Projektmappe baut
ohne Fehler und ohne Warnung.
Die CI kann damit vereinfacht werden: der Behelf aus L4 - unter Linux die
Projekte einzeln statt der Mappe zu bauen, weil IBKRTrader.App dort nicht
baubar war - entfaellt. Beide Plattformen bauen und testen jetzt dieselbe
Mappe.
Doku nachgezogen: README (Architektur-Uebersicht, Start- und Pruefbefehle),
ARCHITECTURE (Ziel-Architektur auf den Ist-Stand, neuer Abschnitt L0-L5 in
der Phasen-Checkliste, AppTimeZone ergaenzt). Der Hinweis "UI-Contract liegt
im Core" war nach L1a falsch - WindowMenu ist dort raus, der Contract ist
jetzt toolkit-neutral.
Verifiziert: Build 0 Fehler/0 Warnungen, 193 Tests gruen, Smoke-UI
konstruiert alle 7 Ansichten + Launcher + Dialog, Daemon-Prueflauf OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die drei Modul-Fenster sind portiert; die Avalonia-Shell zeigt jetzt
dieselben sieben Ansichten wie die WinForms-Shell.
- CongressTradingWindow: Kennzahlen als Kacheln, manueller Scrape-Ausloeser,
offene Positionen des Moduls.
- SupervisorWindow: Analyse mit Live-Anzeige der Tool-Aufrufe, Dossier-
Browser mit Splitter, Berichte, Hinweise. Der Hinweistext nennt jetzt die
Betriebszeitzone, weil die Tagesbericht-Stunde in ihr gilt und nicht in
der Zeitzone des Rechners.
- AccountingWindow: gemeinsame Filterleiste, Uebersicht/BWA, Ledger, Steuer
(Platzhalter), Export und Abruf/Status.
Ersetzte WinForms-Bausteine:
- SaveFileDialog -> IStorageProvider.SaveFilePickerAsync (asynchron, anderer
Zuschnitt). Betrifft die drei Exporte des Accounting-Moduls.
- MessageBox bei Fehlern -> Statuszeile im Fenster. Ein modaler Dialog fuer
einen fehlgeschlagenen Ladevorgang war ohnehin zu laut.
- Anonyme Typen an DataGridView.DataSource -> benannte Records. Kompilierte
Bindings pruefen die Spalten dadurch beim Kompilieren; ein Tippfehler in
einer Spaltendefinition (Binding="{Binding Profile}}") ist beim Build
aufgefallen statt im Betrieb.
Accounting-Betraege werden beim Laden gegen InvariantCulture formatiert, das
Delta eines Ingest-Laufs ist nullable behandelt (ohne Saldo-Anker gibt es
keins) - vorher waere dort eine NullReferenceException moeglich gewesen.
CI-Matrix (Gitea Actions, Actions-kompatibel): ubuntu-latest und
windows-latest. Sie ist der Waechter gegen Rueckfaelle - ein neues
DateTime.Now, ein ToString ohne Formatanbieter oder eine WinForms-Referenz
im Core faellt auf dem Entwicklungsrechner nicht auf, hier schon. Der
Linux-Lauf baut bewusst NICHT die ganze Mappe, weil IBKRTrader.App
(net10.0-windows) dort nicht baubar ist; die Aufzaehlung entfaellt, sobald
die WinForms-Shell aus dem Build genommen wird.
Der Smoke-UI-Lauf ist Teil der CI - mit Avalonia braucht er kein
Anzeigegeraet mehr (SetupWithoutStarting). Mit WinForms war das auf einem
Build-Server nicht moeglich.
Verifiziert: Build 0 Fehler/0 Warnungen, 193 Tests gruen, Smoke-UI
konstruiert alle 7 Ansichten + Launcher + Dialog, publish -r linux-x64
liefert 32 MB mit ELF-Launcher. Die WinForms-Shell ist unveraendert
lauffaehig.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Neues Projekt IBKRTrader.App.Avalonia (net10.0, plattformneutral) mit
Launcher, Fenster-Menue, Beenden-Abfrage und den vier Core-Ansichten
Dashboard, Workers, Logs und Settings. Nutzt denselben AppHostBuilder wie
der Daemon; ShellServices ergaenzt nur, was ohne Oberflaeche nicht existiert.
Versionen bewusst auf der 11er-Linie: Avalonia 11.3.19, DataGrid 11.3.13.
LiveCharts2 2.0.5 ist gegen Avalonia 11 gebaut und bricht unter 12
(Gestures.PinchEvent gibt es dort nicht mehr). Diagramme kommen mit den
neuen Modulen - bis LiveCharts2 Avalonia 12 unterstuetzt, darf hier nicht
angehoben werden. Erfahrung aus PolytraderSharp, im csproj vermerkt.
Ersatz fuer WinForms-Bausteine ohne Gegenstueck:
- PropertyGrid -> SettingsModelBuilder erzeugt die Maske aus den bereits
vorhandenen Category-/DisplayName-/Description-Attributen von AppSettings.
Eine neue Einstellung erscheint damit automatisch, ohne dass jemand die
Oberflaeche anfasst - genau der Vorteil des PropertyGrid. Kennwortfelder
(DB-Passwort, Flex-Token) werden verdeckt. Gemessen: 11 Abschnitte,
41 Felder.
- MessageBox.Show -> ShutdownConfirmWindow (Avalonia bringt keinen
Meldungsdialog mit). Schliessen ueber das X zaehlt als Abbruch, damit ein
versehentlicher Klick nie den Handelsbetrieb stoppt.
- RichTextBox mit SelectionColor -> eingefaerbte Elemente je Logzeile, mit
Filter, Auto-Scroll und Zeilenbegrenzung (im Dauerbetrieb waere die Liste
sonst unbegrenzt gewachsen).
- ToolStrip/StatusStrip -> zentrale Stilklassen in App.axaml (toolbar,
statusbar, kpi, section). Die neuen Module setzen darauf auf.
Gemeinsame Bausteine bewusst jetzt schon zentral, weil die neuen Module
direkt in Avalonia entwickelt werden sollen.
Kompilierte Bindings sind aktiv (x:DataType je Datenkontext) - Tippfehler in
Bindings fallen damit beim Kompilieren auf statt erst zur Laufzeit. Dafuer
brauchte es benannte Record-Zeilentypen statt der anonymen Typen, die
DataGridView.DataSource frueher bekommen hat.
--smoke-ui laeuft jetzt ueber SetupWithoutStarting, also OHNE Anzeigegeraet
und ohne laufende Dienste. Das war mit WinForms nicht moeglich und macht die
Konstruktionspruefung erstmals CI-tauglich. Der Host wird dabei bewusst nicht
gestartet, sonst liefen Worker und Broker-Verbindungen gegen die echten
Endpunkte an.
AVLN3001 unterdrueckt: die Fenster haben absichtlich keinen parameterlosen
Konstruktor. Einer wuerde sie ohne ihre Dienste konstruierbar machen und
genau den Fehler verdecken, den die Konstruktionspruefung finden soll.
Verifiziert: Build 0 Fehler/0 Warnungen, 193 Tests gruen, Avalonia-Smoke-UI
konstruiert alle 6 Fenster, publish -r linux-x64 liefert 32 MB mit
ELF-Launcher samt libSkiaSharp.so/libHarfBuzzSharp.so und ohne
Windows-Abhaengigkeiten. Die WinForms-Shell ist unveraendert lauffaehig.
Offen fuer L4: die drei Modul-Fenster (ModuleViews ist noch leer).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Meilenstein: Trading, Worker, Accounting, Supervisor, REST und MCP laufen
als systemd-Dienst auf Linux. Nur noch IBKRTrader.App ist Windows-gebunden.
Neu IBKRTrader.Hosting: die komplette Host-Zusammenstellung, aus der
WinForms-Program.cs herausgezogen. Bewusst ein eigenes Projekt - laege sie
weiter in der Shell, muesste sie fuer den Daemon dupliziert werden, und
beide wuerden auseinanderlaufen. Ein Modul, das nur in einer Variante
registriert ist, faellt sonst erst im Betrieb auf. RunStartupChecks buendelt
die Startschritte in fester Reihenfolge (Zeitzone vor dem ersten Logeintrag,
Master-Key vor jeder Entschluesselung).
Neu IBKRTrader.Daemon: kopfloser Einstiegspunkt mit SIGTERM-Behandlung,
--check (Trockenlauf: Host bauen, Startpruefungen, keine Boersenverbindung -
fuer Deployment und CI) und --db-version.
AppPaths: Logs, Backups, settings.json und master.key lagen neben der
Binaerdatei. Unter /opt hat der Dienstbenutzer dort keinen Schreibzugriff -
der Dienst waere beim ersten Logeintrag gescheitert. Aufloesung jetzt:
Umgebungsvariable, sonst Binaerverzeichnis WENN beschreibbar (haelt Windows
und die Entwicklung unveraendert), sonst FHS. Der Schreibtest ist ein echter
Schreibversuch - Attributpruefung traegt nicht, weil unter Linux
Besitzer/Gruppe/Modus und unter Windows die ACL entscheiden.
Rechtepruefung fuer master.key/openrouter.key auf Unix: Windows-ACLs
uebertragen sich beim Kopieren auf einen Linux-Host nicht, eine key-Datei mit
644 waere dort fuer jeden lokalen Nutzer lesbar. Nur eine Warnung.
InvariantGlobalization ausdruecklich auf false. Der naheliegende Griff zum
schlanken Image waere hier ein Eigentor gewesen und haette genau die zwei
Punkte aus L1b lautlos rueckgaengig gemacht: ohne ICU faellt die Aufloesung
von Windows-Zeitzonen-IDs aus (Rueckfall auf UTC im Container) und
CultureInfo.GetCultureInfo("de-DE") liefert die invariante Kultur, womit die
PDF-Betraege wieder das Format wechseln.
systemd: Unit mit Type=notify plus UseSystemd() im Host - ohne sd_notify
haette systemctl start bis zum Timeout gehangen. Unit mit Absicherung
(ProtectSystem=strict, ReadWritePaths), deploy/README.md mit Voraussetzungen,
Verzeichnissen, Zeitzonen-Festlegung und dem, was der Daemon NICHT loest
(IB Gateway headless bleibt eine eigene Baustelle).
Verifiziert: 193 Tests gruen (+5), Build 0 Fehler/0 Warnungen, --smoke-ui
konstruiert alle 7 Fenster, --check laeuft durch, und publish -r linux-x64
liefert 11 MB mit ELF-Launcher, ohne eine einzige Windows-Abhaengigkeit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kultur - Ausgaben und Parsen haengen nicht mehr am Host:
- PdfExporter formatierte Betraege mit ToString("N2") ohne Formatanbieter,
also CurrentCulture. Auf dem deutschen Desktop "1.234,56", in einem
Container mit LANG=C "1,234.56" - dieselbe Zahl, fuer einen Leser eine
andere. Fuer ein ausdruecklich pruefbares Dokument jetzt fest de-DE.
- CapitolTradesScraper.ParseDate nutzte DateOnly.TryParse ohne
Formatanbieter. Das ist nicht theoretisch: gemessen wurde aus
"2026-08-04" unter th-TH das Jahr 1483 (buddhistischer Kalender), unter
fa-IR das Jahr 2647 (persischer Kalender), unter ar-SA schlug das Parsen
ganz fehl. de-DE und en-US kommen mit ISO klar - genau deshalb faellt so
etwas auf dem Entwicklungsrechner nie auf. Jetzt TryParseExact mit
InvariantCulture; ein Formatwechsel der Quelle faellt damit auf, statt
still ein falsches Datum zu erzeugen. Regressionstest ueber vier Kulturen.
- IBKRGatewayService baute den Query-Parameter mit .ToString().ToLower()
(Tuerkisch-I) - jetzt fest "true"/"false".
PDF-Schriften: PDFsharp 6 loest auf Nicht-Windows-Plattformen nichts von
selbst auf, "Segoe UI" gibt es dort nicht - der Export waere zur Laufzeit
gescheitert. Neuer DocumentFontResolver: unter Windows bleibt die Plattform
zustaendig (unveraenderte Optik), auf Linux wird eine freie Systemschrift
gesucht (DejaVu/Liberation/Noto/FreeSans). Bewusst keine Schrift im Repo -
das erspart eine Lizenzfrage; fehlt sie, nennt die Fehlermeldung das zu
installierende Paket.
BackupWorker:
- Suchte "mysqldump.exe" in C:\Program Files\... und splittete PATH mit ';'.
Auf Linux ist das Trennzeichen ':' - der gesamte PATH waere als ein
Eintrag gelesen worden. Jetzt Path.PathSeparator, plattformabhaengige
Suchpfade und zusaetzlich "mariadb-dump" (MariaDB hat mysqldump ab 10.5
umbenannt).
- Das DB-Passwort stand als Kommandozeilenargument im Prozessbaum. Unter
Linux ist /proc/<pid>/cmdline fuer jeden lokalen Nutzer lesbar - das waere
eine neue Offenlegung gewesen, die es unter Windows so nicht gab. Jetzt
ueber MYSQL_PWD, nur an den Kindprozess vererbt. Argumente einzeln statt
als Zeichenkette (kein Quoting-Problem bei Pfaden mit Leerzeichen).
Verifiziert: 188 Tests gruen (+5), Build 0 Fehler/0 Warnungen, Core + 3
Module + Tests bauen fuer linux-x64, --smoke-ui konstruiert alle 7 Fenster.
Offen aus L1b und nach L2 verschoben: IAppPaths (Logs/Backups/settings.json/
master.key liegen neben der Binaerdatei; unter /opt ist das nicht schreibbar).
Gehoert zum Daemon, wo die Pfade tatsaechlich gebraucht werden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wir betreiben Instanzen in zwei Regionen. Bisher hing jede Ortszeit an der
Zeitzone des Rechners (DateTime.Now, DateTimeKind.Local): derselbe Code
haette auf einem Windows-Desktop mit Europe/Berlin und in einem
Linux-Container mit UTC lautlos unterschiedliche Werte geliefert - ohne
Fehler, nur um Stunden verschoben, mitten in Buchungszeitstempeln.
AppTimeZone (Core/Time): Betriebszeitzone der Instanz, einmalig aus
Trading.ApplicationTimeZoneId gesetzt, IANA- und Windows-Schreibweise tragen
beide, unbekannter Wert weicht auf die Systemzone aus und warnt. Wird laut
Festlegung vor den ersten Trades gesetzt und danach nie gewechselt - ein
Wechsel verschoebe rueckwirkend alle Tagesgrenzen. Persistenz bleibt UTC,
damit die Daten beider Instanzen vergleichbar sind.
IbkrMapping.ParseExecutionTime verwirft die von TWS gemeldete Zeitzone nicht
mehr, sondern rechnet gegen sie nach UTC; ohne Zonenangabe gilt die
Betriebszeitzone. Das ist der Kern: eine NYSE-Ausfuehrung darf nicht mit
demselben nackten Zeitwert in die Buecher wie eine an der Eurex.
Rueckgabe ist jetzt immer Kind=Utc.
DailyReportService.NextRun -> NextRunUtc(nowUtc, hour, zone): der Bericht
laeuft zu einer festen ORTSZEIT. Sommerzeitumstellung wird behandelt - bei
der uebersprungenen Stunde weicht er aus, statt den Tag ausfallen zu lassen.
LoggingService fuehrt Anzeigezeit und UTC getrennt: Dateinamen und Anzeige in
Ortszeit (Tagesgrenzen gehoeren zur Instanz, der Supervisor liest die
JSONL-Dateien ueber diese Namen), das ts-Feld im JSONL in UTC. Beides musste
getrennt werden, weil die umgerechnete Ortszeit Kind=Unspecified traegt und
ein ToUniversalTime() darauf sie als Zeit des HOSTS gedeutet haette.
Verbleibende DateTime.Now in Worker-Zeitplaenen und Statuszeilen ebenfalls
auf AppTimeZone.Now umgestellt.
Verifiziert: 183 Tests gruen (+20), darunter EU/US-Versatz, Winter-/Sommerzeit,
unbekannte Zone und die uebersprungene Stunde bei der Zeitumstellung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Core, alle drei Module und das Testprojekt tragen keinen UI-Code mehr und
bauen fuer linux-x64. Nur noch IBKRTrader.App ist Windows-gebunden.
UI-Contract toolkit-neutral (Vorbild: PolytraderSharp):
- ModuleView.CreateForm (Func<Form>) -> CreateView (Func<object>)
- ModuleView.Icon (System.Drawing.Image) -> IconKey (string).
System.Drawing.Common ist seit .NET 7 Windows-only und wirft auf Linux.
- WindowMenu.cs war reine WinForms-Umsetzung -> in die Shell verschoben.
LoggingService haelt keine RichTextBox mehr, sondern meldet Eintraege ueber
event EntryWritten. Einfaerbung und UI-Thread-Wechsel liegen jetzt im
LogPanelController der Shell. Nebenbei: ToUpper() -> ToUpperInvariant()
(tr-TR haette aus "info" ein "İNFO" gemacht) und \r\n -> Environment.NewLine.
Die drei Modul-Fenster liegen jetzt unter UI/Views/Modules/; RegisterUi der
Module ist bewusst leer, die Shell registriert sie zentral ueber
UI/ModuleViews.cs (nur fuer tatsaechlich geladene Module). ViewIcons loest
IconKey gegen die PNG-Ressourcen auf - dieselben Schluessel bekommt spaeter
die Avalonia-Shell.
UiConstructionTests entfernt: die Konstruktionspruefung deckt --smoke-ui ab,
das Testprojekt braucht dafuer keine UI-Referenz mehr. Der Test
RegisterUi_RegistersMainView haelt jetzt das Gegenteil fest - das Modul darf
nichts registrieren, sonst waere es wieder toolkit-gebunden.
Verifiziert: Build 0 Fehler/0 Warnungen, 163 Tests gruen, --smoke-ui
konstruiert alle 7 Fenster, und Core + 3 Module + Tests bauen fuer linux-x64.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Pakete hatten kein passendes packageSourceMapping-Muster; mit
<clear/> und Allowlist bedeutet das NU1100. Auf dem Entwicklungsrechner
unsichtbar, weil alle drei laengst im globalen Cache liegen - ein frischer
Klon (und damit jeder Linux-Host) konnte nicht wiederherstellen.
- PDFsharp* : gar kein Muster vorhanden
- Microsoft.EntityFrameworkCore : der Glob "…EntityFrameworkCore.*" matcht
das Basispaket ohne Suffix nicht
- Microsoft.CodeAnalysis.* : transitiv ueber EntityFrameworkCore.Design
Verifiziert: dotnet restore der Projektmappe gegen einen leeren
--packages-Ordner stellt jetzt alle sechs Projekte wieder her.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bestandsaufnahme gegen b96a207, kein Code geaendert. Kernbefunde:
- Der nicht-UI-Teil ist portabel: kein DllImport, keine Registry, kein WMI,
kein DPAPI, keine Windows-Zeitzonen-IDs. WinForms-Kopplung im Core sind
genau 3 Dateien, je Modul genau eine Form-Datei.
- Daraus die Empfehlung: erst headless (~6,5 PT), dann Avalonia (~12 PT).
LiveCharts2 ist Feature-Neubau, kein Migrationsposten (kein Diagramm im
Bestand).
- Kritisch: ParseExecutionTime verwirft die Zeitzone (Kind=Unspecified) und
DailyReportService nutzt DateTimeKind.Local - auf einem UTC-Host liefert
derselbe Code lautlos andere Werte.
- NuGet.config ist bereits heute kaputt: PDFsharp-MigraDoc,
Microsoft.EntityFrameworkCore (Glob matcht nicht ohne Suffix) und
Microsoft.CodeAnalysis.CSharp.Workspaces fehlen im packageSourceMapping.
Gegen leeren Paket-Ordner verifiziert (NU1100).
- Weiter: mysqldump.exe + PATH-Split(';') im BackupWorker, "Segoe UI" und
kulturabhaengige Betragsformatierung im PdfExporter, Schreibzugriff neben
der Binaerdatei (FHS), Fundstellenverzeichnis mit Datei+Zeile im Anhang.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
R3-Vorbereitung: --db-version-Diagnose + EF-Strategie festgelegt
- Program: --db-version gibt die DB-Serverversion aus (fuer EF-ServerVersion-Pin)
- Bestaetigt: MariaDB 11.8.6 -> Pin wie PolytraderSharp (MariaDbServerVersion 11.8.6)
- docs/ARCHITECTURE.md: R3-Strategie fixiert (EF-Migrationen extern, Verbindung aus appsettings.Local.json)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
.NET WinForms-Anwendung (Core, Modules/CongressTrading, UI).
Enthaelt .gitignore und settings.example.json als Konfigurationsvorlage.
Echte settings.json mit Zugangsdaten ist bewusst ausgeschlossen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>