Commit Graph
9 Commits
Author SHA1 Message Date
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 c176b05ea1 L6: IBKRTrader.App.Avalonia -> IBKRTrader.App; letzte WinForms-Spuren raus
Build & Test / build (ubuntu-latest) (push) Canceled after 0s
Build & Test / build (windows-latest) (push) Canceled after 0s
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>
2026-08-07 21:16:48 +02:00
RichardandClaude Opus 5 9c7e19149c L2: Kopfloser Linux-Dienst - der Trading-Kern laeuft ohne Oberflaeche
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>
2026-08-06 23:09:33 +02:00
RichardandClaude Opus 5 8b9b993d1d L1b/2: Kultur und Dateisystem plattformunabhaengig machen
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>
2026-08-06 22:58:10 +02:00
RichardandClaude Opus 5 d8273c3a1e L1b/1: Betriebszeitzone statt Rechnerzeit - EU- und US-Instanzen sauber trennen
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>
2026-08-06 22:52:23 +02:00
Richard 19dac18050 @
R4: Worker von WorkerEngine auf IHostedService (Generic Host)

- WorkerBase implementiert IHostedService (StartAsync/StopAsync mit CancellationToken,
  eigener CTS, Guard auf Info.Active); IWorker auf Metadaten + TriggerAsync reduziert
- Worker via AddHostedService registriert (Core + Modul); AppHost.Start() startet sie,
  AppHost.StopAsync() stoppt sie
- WorkerEngine auf leichte Registry reduziert (WorkerInfos fuer UI + TriggerWorkerAsync)
- LauncherForm startet/stoppt keine Worker mehr (Host-getrieben)
- Hosting.Abstractions im Core; Tests angepasst -> 43/43 gruen; smoke-ui + App-Start ok

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-28 18:39:14 +02:00
Richard 475c0b41cb @
R3 Slice 4: IBKR-Marktdaten auf EF; Dapper vollstaendig entfernt

- 3 IBKR-Entities (IBKRInstrument/IBKRMarketBar/IBKRExternalIdentifier) im CoreDbContext
  (core_ibkr_instruments/_market_data/_external_identifiers); Migration AddIbkr
- IBKRMarketDataRepository von Dapper auf EF (IDbContextFactory<CoreDbContext>);
  Cross-Modul-Query (ct_trade) via Database.SqlQueryRaw
- DatabaseService, IBKRMigrations, Dapper-Package entfernt; Laufzeit-Migrationen komplett weg
  (Schema extern via dotnet ef database update)
- Tests: +4 IBKRMarketDataRepository (EF-InMemory) -> 43/43 gruen; Build + smoke-ui + App-Start ok

R3 abgeschlossen: Persistenz vollstaendig auf EF Core.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-28 18:29:00 +02:00
Richard 6c8e36dc3c @
R3 Slice 3: CongressTrading-Modul + Worker-Log + core_settings auf EF Core

- Modul: CongressTradingDbContext (ct_congressMember/ct_trade) + Design-Time-Factory;
  CongressRepository von Dapper auf EF; CongressTrade.DetailsFetched ergaenzt; Dapper entfernt
- Core: CoreSettingsService (core_settings via EF); WorkerBase-Log auf EF (core_worker_log);
  7 Worker-Ctors DatabaseService -> IDbContextFactory<CoreDbContext>
- CoreMigrations + CongressMigrations (Dapper) entfernt; core_-Schema nun rein EF
- EF-Migration InitialCongressTrading; AddCorePersistence-Fallback fuer leeren Connection-String
  (App startet ohne DB); Connection aus appsettings.Local.json (gitignored)
- Tests: +4 CongressRepository (EF-InMemory) -> 39/39 gruen; Build + smoke-ui + App-Start ok

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-28 11:34:42 +02:00
Richard c20538c8d8 @
R1: Umbau auf Multi-Projekt-Struktur (Vorbild PolytraderSharp)

Kurskorrektur: Vorbild ist PolytraderSharp (C#), nicht die veraltete Python-Version.
Reiner Strukturumbau, Verhalten unveraendert.

- src/IBKRTrader.Core (classlib): Core-Code + UI-Contract (ModuleFormBase, WindowManager)
- src/IBKRTrader.Modules.CongressTrading (classlib, referenziert nur Core)
- Root: IBKRTrader.App (WinExe) referenziert Core + Modul
- tests/IBKRTrader.Tests: Referenzen auf Core + Modul
- Neue .slnx; ungenutztes HtmlAgilityPack entfernt
- docs/ARCHITECTURE.md: korrigierter Ziel-Stand + R-Phasenplan
- Build + 38/38 Tests gruen; App startet

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-27 20:26:25 +02:00