Sicherungscommit vor dem Aufraeumen des Repos, damit nachvollziehbar bleibt, welcher Stand vor der Bereinigung galt. Build gruen, 476 Tests gruen. Zwei Straenge, die sich ueber .csproj, Program.cs und appsettings.json ueberschneiden und darum gemeinsam abgelegt werden: Deploymentcenter-Integration (P3c, Plan D-0 bis D-5 code-seitig fertig): - Deploymentcenter.Client 2.5.0 als lokales Paket, Source-Mapping erweitert - DeploymentcenterOptions, DeploymentcenterErrorReporter, LicenseGate/LicenseCli - WatchdogHeartbeatService auf die Deploymentcenter-API umgestellt (version, os, checks, metrics, status stopped) - Security: MasterKeyResolver, SecretRedactor, FilePermissions - Directory.Build.props mit zentraler Version 0.1.0 (Packager-Versionsdisziplin) - deploy/: Packager-Vorlage und systemd-Unit; echte Zugangsdaten bleiben ueber .gitignore aussen vor - setup.json fuer die Erstinstallation - UMSETZUNGSPLAN-Deploymentcenter-Integration.md; ANALYSE-Linux-Portierung.md verweist auf den neuen Plan Einfenster-Shell (UI-Redesign): - ShellWindow + ShellNavModel als Seitenleisten-Shell - WindowMenuBar und WindowMenuModel entfallen - Modul- und Kernfenster auf die Shell-Einbettung angepasst Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
45 KiB
Analyse & Umsetzungsrahmen: Linux-Fähigkeit von PolyTrader
Stand: 13.08.2026 · Revision 5 (Avalonia-Portierung P5–P8 durch; offen sind Deploymentcenter, CI, systemd/Feldtest, WinForms-Ausbau)
Basis: 8 Projekte auf net10.0 (PolyTrader.App als net10.0-windows noch als Fallback), 450 Tests
Als Nächstes: die Integration des überarbeiteten Deploymentcenters (P3c). Sie ersetzt Lizenzverwaltung, Watchdog und Error-Reporting grundlegend. Alles, was in diesem Dokument zu LicenseLabrador oder zum Watchdog-Heartbeat steht, ist damit Übergangszustand — dort nichts mehr ausbauen.
P3c hat seit dem 14.08.2026 einen eigenen Plan: UMSETZUNGSPLAN-Deploymentcenter-Integration.md — er ist ab jetzt maßgeblich und korrigiert zwei Aussagen dieses Dokuments: §5.2 unterschätzt die Watchdog-Umstellung („nur Header und URL"; tatsächlich kommen
version,os,checks,metricsundstatus: "stopped"dazu), und §5.2 wie Risiko R6 stützen die headless-Lizenzprüfung aufILicensePromptaus dem SDK — diese Schnittstelle wird dort jedoch nirgends benutzt und trägt nichts. Der Umfang liegt bei 4–7 PT statt 2–4 PT, weil Error-Reporting, Auslieferung und Erstinstallation hier noch nicht enthalten waren.
Entscheidungen (06.08.2026)
| # | Thema | Entscheidung | Auswirkung |
|---|---|---|---|
| 1 | Kultur-Bug | Sofort gefixt ✅ | Erledigt, siehe Abschnitt 4.1 |
| 2 | Designer-Regel | Neue Formulierung übernommen: Layout deklarativ in .axaml, nicht zur Laufzeit im Code |
ersetzt die WinForms-Designer-Regel |
| 3 | Windows-UI | Variante B: Modul-UI entfernt ✅, Core-UI folgt; Fallback ist der Tag winforms-final |
umgesetzt 06.08.2026 |
| 4 | PropertyGrid |
wird durch normale Steuerelemente ersetzt | +5 PT ggü. Fremdbibliothek, dafür bessere UX |
| 5 | Lizenz/Watchdog | laufen jetzt über das Deploymentcenter, nicht mehr über Einzeldienste | Linux-Blocker entfällt, siehe 5.2 |
| 6 | Zeitzone | einmalig bei der Installation festgelegt, nicht im laufenden Betrieb gewechselt | vereinfacht die Umsetzung, siehe 3.3 |
| 7 | Threema | entfernt ✅; Nachfolger: RocketChat + Telegram | umgesetzt am 06.08.2026, siehe 5.3 |
| 8 | Charts | LiveCharts2 bestätigt (nach Klarstellung, dass die Integration nicht komplex ist) | +1 PT ggü. ScottPlot.Avalonia |
Netto-Effekt auf den Gesamtaufwand: damals auf 41–61 PT geschätzt. Überholt — Stand 13.08.2026 sind noch 7–13 PT offen, weil die Avalonia-Portierung (P5–P8) inzwischen durch ist; siehe Abschnitt 7.
0. Kurzfassung
| Frage | Antwort |
|---|---|
| Ist es machbar? | Ja, ohne architektonische Sackgassen. |
| Was ist der Löwenanteil? | Die UI. ~8.300 LOC (34 % des Produktivcodes) hängen an WinForms. |
| Was ist überraschend gut? | Die gesamte Trading-Kernlogik ist bereits portabel: kein einziger DllImport, keine Registry, keine WMI, keine DPAPI, keine SpecialFolder, saubere Path.Combine-Nutzung. Nethereum, Pomelo/EF, HttpClient, ClientWebSocket, AES-GCM laufen unverändert. |
| Was war der teuerste Einzelpunkt? | Der PropertyGrid-Ersatz — erledigt, ersetzt durch den kategorisierten SettingsEditor. |
| Gesamtaufwand | ~7–13 Personentage offen (Stand 13.08.2026). Die UI-Portierung ist inhaltlich durch (P5–P8); offen sind nur noch P3c Deploymentcenter, CI, systemd/Feldtest, der WinForms-Ausbau und die Abnahme A5. |
| Wichtigste Erkenntnis | Galt: „Für ~15 PT bekommst du 80 % des Nutzens." Eingetreten und überholt — die UI wurde nicht als Fallback eingefroren, sondern vollständig nach Avalonia portiert. Der Rückfallstand liegt im Tag winforms-final. Siehe Abschnitt 7. |
1. Bestandsaufnahme
1.1 Projektstruktur und Zielframeworks
| Projekt | TargetFramework | UseWindowsForms |
Warum WinForms? |
|---|---|---|---|
PolyTrader.App |
net8.0-windows7.0 (WinExe) |
ja | Shell, Launcher, 4 Core-Views |
PolyTrader.Core |
net8.0-windows |
ja | nur UI-Contract (ModuleUi.cs, WindowMenu.cs) |
Modules.CopyTrading |
net8.0-windows |
ja | 5 Views + TradeRowColoring (System.Drawing.Color) |
Modules.ResolutionFarming |
net8.0-windows |
ja | 1 Fenster |
Modules.Supervisor |
net8.0-windows |
ja | 1 Fenster |
Modules.Accounting |
net8.0-windows |
ja | 1 Fenster |
PolyTrader.Tests |
net8.0-windows |
ja | nur weil die Referenzen -windows sind |
IcgSoftware.Threema.CoreMsgApi |
— | — | entfernt am 06.08.2026 (Entscheidung 7) |
Alle verbleibenden Projekte sind -windows – aber die Bindung ist dünn. Core und Module brauchen WinForms ausschließlich für den UI-Contract; die Services darunter sind plattformneutral.
1.2 Wo sitzt der Windows-Code?
WinForms/System.Drawing berührt 41 Dateien – davon:
├─ 33 reine UI-Dateien (Ui/-Ordner) → müssen ohnehin neu
└─ 5 „Ausreißer" außerhalb der Ui-Ordner → das ist die eigentliche Kopplung
├─ src/PolyTrader.Core/Modularity/ModuleUi.cs (Func<Form>, Image)
├─ src/PolyTrader.Core/Modularity/WindowMenu.cs (MenuStrip, Font)
├─ src/…CopyTrading/Logic/TradeRowColoring.cs (Color)
├─ Models/DashboardRow.cs (using System.Drawing)
└─ Program.cs (ApplicationConfiguration.Initialize)
Nur 5 Dateien stehen zwischen „Core/Module sind plattformneutral" und dem Ist-Zustand.
1.3 Code-Verteilung
| Kategorie | LOC | Anteil |
|---|---|---|
| UI (Designer + Code-Behind), App + Module | ~8.300 | 34 % |
| davon Designer-generiert (entfällt ersatzlos) | ~4.500 | 19 % |
| Services / Logik / Persistenz / Modelle | ~15.950 | 66 % |
| Tests (448 Testfälle, 48 Dateien) | ~5.300 | — |
| EF-Migrationen (unverändert übernehmbar) | 6.891 | — |
Nur 1 von 48 Testdateien ist UI-abhängig (WindowMenuTests.cs).
2. Die UI-Portierung
2.1 Control-Inventar und die neue Layout-Regel
16 Fenster/UserControls, ~8.300 LOC. Aus den Designer-Dateien:
| WinForms-Control | Anzahl | Avalonia-Ersatz | Bewertung |
|---|---|---|---|
DataGridViewTextBoxColumn |
69 | DataGridTextColumn |
mechanisch, viel Fleißarbeit |
Label |
49 | TextBlock |
trivial |
ToolStripButton |
37 | Button in StackPanel |
Layout neu |
TabPage / TabControl |
22 / 7 | TabItem / TabControl |
trivial |
DataGridView |
20 | DataGrid |
siehe unten |
ToolStrip |
16 | StackPanel/DockPanel |
Layout neu |
Panel / Button |
16 / 16 | direkt | trivial |
TextBox / ComboBox |
9 / 9 | direkt | trivial |
PropertyGrid |
6 | handgebaute Formulare (Entscheidung 4) | teuerster Punkt, siehe 2.2 |
DateTimePicker |
5 | CalendarDatePicker + TimePicker (getrennt!) |
kleine Nacharbeit |
GroupBox |
4 | HeaderedContentControl/Border |
trivial |
RichTextBox |
3 | kein Äquivalent, siehe 2.3 | mittel |
PictureBox |
3 | CartesianChart (LiveCharts2) |
siehe 2.4 |
SplitContainer |
2 | GridSplitter |
Layout neu |
MenuStrip / StatusStrip |
1 / 1 | Menu / DockPanel |
ToolStripItemAlignment.Right gibt es nicht → DockPanel |
Entwarnung bei den DataGrids: Die Nutzung ist erfreulich schlicht – 25× DataSource-Binding, 35× DefaultCellStyle, 8× AutoGenerateColumns, nur 4 SelectionChanged, 3 CellContentClick, 1 CellDoubleClick. Kein Virtual Mode, kein Custom Painting, keine CellFormatting. Direkt auf ItemsSource + Binding StringFormat abbildbar.
Die Zeilenfärbung (ClosedTradesView.cs, OpenTradesView.cs über DataBindingComplete → row.DefaultCellStyle.BackColor) wird zu einem IValueConverter auf DataGrid.LoadingRow – sauberer als heute.
📐 Die neue Layout-Regel (Entscheidung 2)
Die bisherige Vorgabe „alle WinForms-Controls designerfähig (partial +
.Designer.cs), nie code-only" wird ersetzt durch:„Jede View besteht aus
View.axaml(vollständiges Layout, deklarativ) undView.axaml.cs(nur Verdrahtung und Datenlogik). Steuerelemente und Layout werden NICHT zur Laufzeit im Code erzeugt."Nutzen bleibt identisch: Layout ist vollständig inspizierbar, diffbar und an einer Stelle. Ein WYSIWYG-Designer existiert in Avalonia nicht – stattdessen der XAML-Previewer (VS-/Rider-Extension) mit Live-Vorschau beim Tippen.
Praktische Konsequenz für die Portierung:
WindowMenu.Populate()baut die Menüleiste heute komplett zur Laufzeit auf (menu.Items.Clear()+ dynamisch erzeugteToolStripMenuItem). Das verstößt gegen die neue Regel und wird zu einem deklarativenMenumitItemsSource-Bindung an eineObservableCollection<WindowMenuEntry>– Layout im XAML, nur die Daten kommen aus dem Code. Gleiches gilt für die dynamisch angehängte Menüleiste inShellUiHost.AttachWindowMenu().
2.2 PropertyGrid → normale Steuerelemente (Entscheidung 4)
Betroffen sind 4 Fenster mit 6 Grid-Instanzen:
| Datei | Instanz | Gebundenes Objekt |
|---|---|---|
Ui/Views/SettingsView.Designer.cs:21,33 |
propertyGrid, pgAccount |
ServerSettings, Account-Daten |
…CopyTrading/Ui/AccountSettingsView.Designer.cs:25 |
pgSettings |
CopyTradingAccountSettings |
…CopyTrading/Ui/MasterTradersView.Designer.cs:41 |
pgDetail |
TrackedTrader |
…ResolutionFarming/Ui/ResolutionFarmingMainForm.Designer.cs:38 |
pgSettings |
Farming-Settings |
Vorarbeit, die sich jetzt auszahlt: Die Modelle tragen bereits [Description]-Attribute als Inline-Erklärungen (CopyTradingAccountSettings.cs:11, TrackedTrader.cs:10) und [Browsable(false)] zum Ausblenden von Geheimnissen (ServerSettings.cs:107,146 – Watchdog-Token und Lizenzschlüssel). Diese Attribute sind die Spezifikation der neuen Formulare – Beschriftung, Tooltip und Sichtbarkeit jedes Feldes stehen schon im Code. Das reduziert die Denkarbeit auf Layout und Validierung.
Was die handgebauten Formulare besser können als das PropertyGrid:
- Eingabevalidierung vor dem Speichern statt erst beim Commit der Zelle (bei Risiko-Limits und Preisgrenzen relevant)
- Einheiten direkt am Feld (USDC, %, bps, Minuten) statt im Beschreibungstext
- Fachliche Gruppierung statt alphabetischer Reflection-Reihenfolge
- Abhängige Felder ausgrauen (z. B. Ladder-Parameter nur bei aktivierter Sell-Ladder)
Aufwand: ~8–10 PT statt ~2 PT mit Fremdbibliothek. Der Aufschlag ist bewusst und kauft dauerhafte Wartbarkeit ohne Fremdabhängigkeit.
2.3 RichTextBox → Terminal-Fenster
TerminalView (378 + 195 LOC) und SupervisorMainForm (Chat) nutzen RichTextBox mit Farbausgabe pro Log-Zeile. Avalonia hat kein RichTextBox.
Empfehlung: ItemsControl über eine ObservableCollection<LogLine> mit Farb-Converter – virtualisiert, deklarativ (passt zur neuen Layout-Regel), und schneller als heute. TerminalView.cs:186 implementiert bereits ein Auto-Clear bei RAM-Limit; der Performance-Druck ist bekannt, ein virtualisiertes ItemsControl löst ihn an der Wurzel.
2.4 Charts: ScottPlot → LiveCharts2 (Entscheidung 8)
Nur 3 Charts, alle in DashboardView.RenderCharts() (Equity-Kurve, PnL je Modul, PnL je Tag). Die Auswertungslogik liegt bereits pur in TradeAnalytics und bleibt komplett unverändert.
Der heutige Weg (plot.GetImage(w,h).GetImageBytes() → System.Drawing.Bitmap → PictureBox, DashboardView.cs:207-227) entfällt und wird zu drei deklarativen CartesianChart-Elementen im XAML – was der neuen Layout-Regel ohnehin besser entspricht als das heutige Rendern im Code.
Zur Einordnung deiner Sorge: Der Unterschied ist kleiner als befürchtet.
ScottPlot.Avaloniawäre ~1 PT günstiger gewesen, nicht mehr – bei drei Charts fällt der Integrationsvorteil kaum ins Gewicht. LiveCharts2 bringt dafür Animationen, Tooltips und Zoom out of the box, die du bei ScottPlot nachbauen müsstest. Die Entscheidung für LiveCharts2 ist bei dieser Chart-Anzahl gut begründet – du zahlst dafür keinen nennenswerten Aufpreis.
2.5 System.Drawing.Common muss raus
Seit .NET 7 ist System.Drawing.Common Windows-only und wirft auf Linux PlatformNotSupportedException. Betroffen:
ModuleUi.cs:32–System.Drawing.Image? Icon→Avalonia.Media.IImageTradeRowColoring.cs:11-13–Color.FromArgb(...)→ eigenes Farbtupel oderAvalonia.Media.ColorWindowMenu.cs–Font,FontStyle.BoldModels/DashboardRow.cs–using System.Drawing(vermutlich verwaist, prüfen)DashboardView.cs:222-225– entfällt mit LiveCharts2
TradeRowColoringTests.cs testet die Farben direkt → zieht mit (klein).
2.6 PDF-Export: PDFsharp-MigraDoc-GDI → Core-Build
Modules.Accounting referenziert PDFsharp-MigraDoc-GDI 6.2.4 – der GDI+-Build ist Windows-only. Ersatz: PDFsharp-MigraDoc (Core-Build), API-kompatibel.
Aber: Der Core-Build findet auf Linux keine Systemschriften. PdfExporter.cs:34 setzt style.Font.Name = "Segoe UI" – existiert auf Linux nicht. Nötig ist ein eigener IFontResolver plus eine als EmbeddedResource mitgelieferte Schrift (DejaVu Sans oder Open Sans, beide frei lizenziert). Einziger nennenswerter Umbau am Accounting-Modul.
AccountingReportTests.cs:131 rendert PDFs headless im Test → zieht mit.
3. Zeitzonen ⏰
3.1 Der harte Punkt
Ui/Views/TerminalView.cs:150:
TimeZoneInfo berlinTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
Eine Windows-Zeitzonen-ID. Seit .NET 6 mappt FindSystemTimeZoneById Windows-IDs auf Linux über ICU – funktioniert also meistens. Es bricht, sobald InvariantGlobalization=true gesetzt ist oder die Distro kein vollständiges ICU mitbringt (Alpine ohne icu-libs). Dann fliegt beim Öffnen des Terminal-Fensters eine TimeZoneNotFoundException.
Mit Entscheidung 6 verschwindet diese Stelle ohnehin – die Zeitzone kommt künftig aus der Konfiguration, nicht mehr hartcodiert.
3.2 Die Now/UtcNow-Mischung
122× DateTime.UtcNow gegen 23× DateTime.Now. Konsistent, solange der Rechner auf Europe/Berlin steht. Linux-Server laufen praktisch immer auf UTC. Betroffene Stellen:
| Stelle | Auswirkung bei Server-TZ = UTC |
|---|---|
TerminalLogger.cs:23 – Timestamp = DateTime.Now |
Log-Zeitstempel 1–2 h versetzt |
TerminalLogger.cs:122, LauncherWidgetsPanel.cs:112 – Logdatei {DateTime.Now:yyyy-MM-dd}.jsonl |
Tagesgrenze der Logdateien verschiebt sich |
DossierService.cs:81, SupervisorTools.cs:144 – suchen Logdateien per Datum |
müssen dieselbe Konvention nutzen, sonst leere Dossiers |
DailyReportService.cs:61 |
Tagesbericht schneidet an anderer Stelle |
AccountingMainForm.cs:57 – Periodenvorbelegung |
steuerlich relevant – Monats-/Quartalsgrenzen |
PdfExporter.cs:55 – „Erstellt: …" |
Belegdatum im Steuer-PDF |
MasterTraderAnalyticsJob.cs |
mischt Now (Anzeige) und UtcNow (Cutoffs) – funktioniert, ist aber verwirrend |
3.3 Anforderung: konfigurierbare Zeitzone (Entscheidung 6)
Ziel (präzisiert): Die Zeitzone wird einmalig bei der Installation festgelegt — wie in einem Setup — und im laufenden Betrieb normalerweise nicht mehr gewechselt.
Vorgeschlagene Umsetzung:
-
Neue Einstellung
ServerSettings.ApplicationTimeZoneId(Default"Europe/Berlin"). IANA-IDs verwenden – .NET akzeptiert sie seit Version 6 auf beiden Plattformen, Windows-IDs dagegen nur eingeschränkt auf Linux. -
Zentraler Zugriffspunkt statt verstreuter
DateTime.Now-Aufrufe:IAppClock ├─ DateTime UtcNow → für alle Domänen-/Persistenzwerte ├─ DateTime Local → für Anzeige und Logdatei-Namen └─ TimeZoneInfo TimeZone → aufgelöst aus ApplicationTimeZoneId, mit Fallback auf UTC + WarnungDamit ist die Zeitzone an einer Stelle wirksam statt an 23.
-
Festlegung bei der Erstinstallation, danach schreibgeschützt-mit-Bestätigung:
- Beim ersten Start ohne gesetzte Zeitzone wird sie abgefragt (UI-Dialog bzw. CLI-Schalter
--set-timezonefür den headless Betrieb) und inserver_settings.xmlfestgeschrieben. - Eine spätere Änderung ist möglich, aber bewusst unbequem: Bestätigungsdialog mit Klartext-Folgen („Logdatei-Tagesgrenzen verschieben sich um X Stunden. Bereits geschriebene Logs und Buchungen bleiben unverändert und sind danach in der neuen Zeitzone zu lesen.") und Wirksamkeit erst nach Neustart.
- Protokolleintrag mit alter und neuer Zeitzone, damit die Verschiebung später in den Logs nachvollziehbar ist.
Weil der Regelfall die einmalige Festlegung ist, entfällt jede Komplexität für einen Wechsel zur Laufzeit — kein Neu-Verdrahten laufender Jobs, kein Umschalten offener Fenster. Das ist der eigentliche Gewinn dieser Entscheidung.
- Beim ersten Start ohne gesetzte Zeitzone wird sie abgefragt (UI-Dialog bzw. CLI-Schalter
-
Zusätzlich
TZ-Umgebungsvariable in der systemd-Unit setzen, damit auch die Ausgaben außerhalb der Anwendung (Logrotate, Cron, Systemlogs) zusammenpassen.
Offene fachliche Frage: Soll die Zeitzone im Accounting mitwandern oder dort fix auf der steuerlich maßgeblichen Zeitzone stehen bleiben? Ein Wechsel des Betriebsstandorts ändert nicht zwingend den steuerlichen Bezugsrahmen. Empfehlung: zwei getrennte Einstellungen – ApplicationTimeZoneId (Anzeige, Logs, Jobs) und AccountingTimeZoneId (Buchungsperioden, Steuerbelege), Default identisch. Kostet fast nichts, verhindert aber, dass ein Standortwechsel rückwirkend die Buchungsperioden verschiebt.
Aufwand: ~2 PT (Einstellung, IAppClock, 23 Aufrufstellen umziehen, Erstinstallations-Abfrage, Tests). Durch die Beschränkung auf die Installationszeit ~1 PT günstiger als in Revision 2 angesetzt.
3.4 MySQL
Pomelo speichert DateTime als DATETIME ohne Zeitzoneninformation. Bestehende Daten ändern sich beim Plattformwechsel nicht – aber die Interpretation neuer Werte ändert sich. Prüfen: SELECT @@global.time_zone, @@session.time_zone; auf dem Ziel-Server. Bei gemischt geschriebenen Daten entstehen im Accounting stumme Lücken/Überlappungen an der Tagesgrenze. Der IAppClock aus 3.3 löst das, indem er die Persistenz konsequent auf UTC festlegt.
4. String- und Zahlenformatierung
4.1 ERLEDIGT – der aktive Bug ist behoben ✅
TraderMonitorService.cs parste API-Preise kulturabhängig. Empirisch verifiziert:
de-DE "0.53" -> 53 "1234.5" -> 12345
Invariant "0.53" -> 0,53 "1234.5" -> 1234,5
Ein Faktor-100-Fehler im Einstandspreis der Master-Trader-Auswertung.
Behoben am 06.08.2026:
TraderMonitorService.cs:1276-1280– nutzt jetzt den bereits vorhandenen, korrekten HelperParseDecimalstatt duplizierter Inline-Logik. Der Helper istinternal staticund dokumentiert.PolymarketClobClient.cs:770, 784, 796, 820, 822, 827– alleTryParseaufInvariantCulture(Regex-Gruppen sind zwar ziffernrein und waren damit faktisch sicher, aber die Klasse Fehler soll nirgends mehr im Code stehen).MasterTraderAnalyticsJob.cs:140– Unix-Timestamp-String invariant.- Neuer Regressionstest
tests/PolyTrader.Tests/ApiNumberParsingTests.cs– 10 Fälle, die unter erzwungener de-DE-Kultur laufen und fehlschlagen, sobald jemand die Invariant-Angabe entfernt.
Verifikation: Build fehlerfrei, 448 Tests grün (438 vorher + 10 neue).
4.2 Ausgabeformatierung (offen)
35× DefaultCellStyle.Format, 19× ToString("…"), dazu zahlreiche :N2/:F2/:F3 in Log- und UI-Strings – alle nach CultureInfo.CurrentCulture.
Auf Windows ist das de-DE. Auf Linux ist es LANG/LC_ALL – unter systemd oft gar nicht gesetzt → .NET fällt auf Invariant zurück → aus 1.234,56 USDC wird 1,234.56 USDC. Kein Datenfehler, aber sofort sichtbar.
Empfehlung:
- Anzeige-Kultur beim Start explizit pinnen:
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("de-DE")inProgram.Main. Damit ist die Darstellung unabhängig vonLANG. (Sinnvollerweise ebenfalls konfigurierbar, analog zur Zeitzone – dieselbe Begründung: Betrieb im Ausland.) - Globalisierungs-Analyzer aktivieren (
CA1304,CA1305,CA1307,CA1310). Sie hätten den Bug aus 4.1 gefunden.
4.3 Weitere Kulturthemen
- ICU vs. NLS: .NET nutzt seit Version 5 auf beiden Plattformen ICU – Sortier- und Vergleichssemantik bleibt gleich. Voraussetzung: kein
InvariantGlobalization=trueim Publish-Profil,libicuauf dem Zielsystem (Debian/Ubuntu Standard, Alpine nachinstallieren). - String-Vergleiche: durchgängig
StringComparison.OrdinalIgnoreCase– vorbildlich, keine Nacharbeit.
5. Weitere plattformspezifische Punkte
5.1 Dateisystem – erfreulich sauber
| Prüfpunkt | Befund |
|---|---|
Path.Combine |
17 von 17 korrekt, durchgängig über AppContext.BaseDirectory |
Environment.SpecialFolder |
0 Treffer |
| Harte Windows-Pfade | genau 1: ServerSettings.cs:73 – MullvadCliPath (konfigurierbar, nur der Default ist Windows) |
DllImport / LibraryImport |
0 Treffer |
| Registry / WMI / DPAPI / EventLog | 0 Treffer im eigenen Code |
Groß-/Kleinschreibung: Linux ist case-sensitive. Kritisch: server_settings.xml, appsettings.Local.json, master.key, openrouter.key, Logs/, favicon.ico, die 24 PNGs in Resources/. Im Code konsistent – trotzdem beim ersten Linux-Build gezielt prüfen, ebenso die \-Pfade in den .csproj (MSBuild normalisiert sie, aber es ist eine bekannte Stolperstelle).
Dateirechte: master.key und openrouter.key liegen als Klartext neben der Anwendung (SettingsView.cs:301,356, Program.cs:393, OpenRouterClient.cs:46). Auf Linux erzeugt File.WriteAllText mit Standard-umask 644 – world-readable. Nach jedem Schreiben File.SetUnixFileMode(path, UserRead | UserWrite) (.NET 7+). Ergänzt Befund F1 des Sicherheitskonzepts.
Das Deploymentcenter macht es beim Lizenz-Cache bereits richtig (
chmod 600 state.dat, siehe LICENSE_INTEGRATION_GUIDE Abschnitt 3) – dieselbe Konvention für die eigenen Schlüsseldateien übernehmen.
Zeilenenden: Environment.NewLine wechselt von \r\n auf \n. CsvExporter.cs nutzt bereits explizites '\n' – korrekt.
5.2 Lizenz & Watchdog – jetzt über das Deploymentcenter (Entscheidung 5)
Das war in Revision 1 ein Blocker (R4) – er ist gelöst. Aus J:\Softwareprojekte\Deploymentcenter\docs:
| Thema | Alt (LicenseLabrador.Client 1.0.0) |
Neu (Deploymentcenter, HWID v2) |
|---|---|---|
| Hardware-ID | Windows: Registry MachineGuid; Linux: /etc/machine-id → plattformabhängig verschieden |
2:<plattform>:<sha256> mit dokumentierter Quellen-Priorisierung je Plattform |
| Container | jede Instanz eine neue ID → Lizenz invalidiert bei jedem Neustart | DEPLOYMENTCENTER_HWID-Override gewinnt plattformweit |
| Rechnername im Hash | — | bewusst nicht enthalten, Umbenennen kostet keinen Aktivierungsplatz |
| Migration | — | Client sendet hardware_id (v2) und legacy_hardware_id (v1); Server zieht die Aktivierung lautlos um, ohne zusätzlichen Platz |
| Headless | modaler Dialog blockiert den Start | CLI-Schalter --license-status, --license-set-key, --license-deactivate plus ILicensePrompt/ConsoleLicensePrompt als UI-Abstraktion |
| Cache | — | LLS2-Envelope, AES-256-GCM, HKDF aus HWID+Slug, Linux 0600, kein Klartext-Rückfall |
| SDK | netstandard2.0 |
Deploymentcenter.Client, Multi-Targeting netstandard2.0;net8.0 |
Damit entfallen zwei Risiken vollständig: die Neubindung der Lizenz beim Plattformwechsel (Migration v1→v2 ist serverseitig gelöst) und der Container-Neustart-Effekt.
Verbleibende Arbeit – ein Umbau, kein Blocker:
- Lizenz:
Licensing/LicenseGate.csundUi/LicenseDialog.csvonLicenseLabrador.ClientaufDeploymentcenter.Clientumstellen. Die API unterscheidet sich:LicenseResult/LicenseState/GetHardwareId()→LicenseValidationResult(IsValid,Status,IsCached,Message) /HardwareId.GetHardwareId(slug)/ValidateAsync(slug, key, serverUrl).ILicensePromptimplementieren statt den Dialog direkt aufzurufen – das ist genau die Naht, die den headless-Betrieb und später die Avalonia-UI trägt. Das lokale NuGet-Paket inlib/nuget/wird ersetzt. - Watchdog:
WatchdogHeartbeatServicesendet bereitssource,instance,interval– das Payload-Schema passt schon. Zu ändern sind nur Header (X-Watchdog-Key→X-Agent-Token) und Ziel-URL (watchdog.mhdf.de→dc.mhdf.de/api/watchdog/v1/ping). Ergänzen:os- undgroup-Feld, sowie der im Guide empfohlene Abschluss-Ping beim geordneten Herunterfahren.
Aufwand: ~2–3 PT. Empfehlung: in Stufe L1 mitnehmen, nicht später – der headless-Betrieb (L2) hängt an ILicensePrompt.
Sicherheitshinweis am Rande:
server_settings.xmlenthält den Watchdog-Token und den Lizenzschlüssel im Klartext (WatchdogToken,LicenseKey). Beim Umbau auf das Deploymentcenter ist der passende Moment, beide über dieSecretProtectionlaufen zu lassen oder auf Umgebungsvariablen umzustellen – passend zur bereits offenen Auflage, die Watchdog-Secrets zu rotieren.
5.3 Threema – entfernt ✅ (Entscheidung 7)
Am 06.08.2026 umgesetzt. Nachfolgekanäle werden RocketChat und Telegram.
Was entfernt wurde:
| Entfernt | Umfang |
|---|---|
src/PolyTrader.Core/Services/ThreemaService.cs |
1 Datei (BackgroundService + Webhook-Listener) |
libs/Threema-MsgApi-Net-Core (vendorte Fremdbibliothek) |
35 Dateien, aus Solution und Git entfernt (History bleibt) |
Projektreferenz in PolyTrader.Core.csproj |
1 |
Threema-Block in ServerSettings (7 Properties, [Category("Threema Notifications")]) |
36 Zeilen |
DI-Registrierungen in Program.cs (Singleton + HostedService + View-Injektion) |
3 |
ThreemaService-Verdrahtung in SettingsView |
3 Stellen |
Was an die Stelle getreten ist — INotificationSink:
PolyTrader.Core/Notifications/
├─ INotificationSink.cs Schnittstelle + NotificationSeverity (Info/Warning/Critical)
└─ LogNotificationSink.cs Übergangs-Implementierung: schreibt ins Terminal-Log
Die drei Fachdienste kennen jetzt nur noch die Schnittstelle:
| Dienst | Meldung | Severity |
|---|---|---|
MasterTraderAnalyticsJob |
Auto-Pause eines Master-Traders | Warning |
SellLadderService |
SELL-Floor ohne Fill erreicht, Position wird gehalten | Critical |
DailyReportService |
täglicher Supervisor-Kurzbericht | Info |
Zwei Entwurfsentscheidungen, die sich später auszahlen:
- No-Throw-Vertrag.
SendAsyncwirft laut Schnittstellen-Doku niemals — Kanalfehler werden intern abgefangen und protokolliert. Das ist genau die Best-Practice aus dem Watchdog-Guide („Fange Netzwerkfehler stets stumm ab, damit der Ausfall des Monitoring-Servers niemals den Hauptanwendungsfluss unterbricht"). Die Aufrufer verzichten deshalb bewusst auf eigenetry/catch-Blöcke — der Code wurde dabei kürzer, nicht länger. Bindend für die kommenden RocketChat- und Telegram-Implementierungen. NotificationSeverityist von Anfang an dabei, obwohl die Log-Implementierung sie kaum nutzt. Bei zwei Kanälen (RocketChat für alles, Telegram als Fallback für Kritisches — oder umgekehrt) ist die Einstufung die natürliche Routing-Information. Sie nachträglich einzuziehen hätte alle Aufrufstellen erneut angefasst.
Nebeneffekte — vier Linux-Themen sind mit weggefallen:
| Entfallen | War |
|---|---|
libsodium 1.0.16 (Baujahr 2018) |
Risiko R7: .so gegen alte glibc gelinkt, Ladefehler auf Ubuntu 24.04 möglich |
Microsoft.Win32.Registry (E2EHelper.cs, #if CoreWinOnly) |
die einzige Registry-Nutzung im gesamten Build |
HttpListener auf http://*:{port}/ (Webhook) |
Port-Bindung + netsh-Fehlermeldung mit Windows-Semantik |
System.Web.HttpUtility |
— |
Verifikation: Clean-Build fehlerfrei, 448 Tests grün, Compiler-Warnungen von 17 auf 9 gesunken (die 8 Warnungen der Threema-Bibliothek — 4× SYSLIB0014, 3× CA1416, 1× CS0472 — sind weg).
Testverbesserung nebenbei: Die drei Tests, die bisher new ThreemaService(logger, new JobManager()) als Wegwerf-Instanz injizierten, nutzen jetzt FakeNotificationSink — damit ist prüfbar, ob und mit welcher Severity gemeldet wurde. Vorher war das nicht beobachtbar.
Offen für später: RocketChatNotificationSink und TelegramNotificationSink implementieren und in Program.cs registrieren — eine Klasse je Kanal, keine Änderung an der Fachlogik. Bei mehreren aktiven Kanälen bietet sich ein CompositeNotificationSink an (leitet an alle weiter, jeder Kanal fängt seine Fehler selbst ab).
5.4 Verbleibende Netzwerkdienste
HttpListener– nach dem Threema-Ausbau nur noch der MCP-Light-Server (127.0.0.1:{port}/mcp). Läuft auf Linux über die managed Implementierung, keinnetshnötig. Unkritisch.MullvadVpnService: Der CLI-Aufruf ist portabel (UseShellExecute = false, sauber). Auf Linux heißt die CLImullvad(typisch/usr/bin/mullvad). Achtung:HealthCheckAsync()prüftstatus.Contains("Connected")– der Wortlaut der Linux-CLI weicht ab. Muss gegen die echte CLI verifiziert werden, sonst reconnectet der Dienst dauerhaft ins Leere und meldet nach 3 Fehlversuchen „unrecoverable".- Nethereum 6.1.0, Pomelo/EF Core 8,
HttpClient,ClientWebSocket,System.Text.Json, AES-GCM (OpenSSL): plattformneutral, keine Anpassung nötig. Das ist der gesamte Trading-Pfad.
5.5 Windows-UI als Fallback: Variante 2 (Entscheidung 3)
Heute laufen Generic Host und Application.Run im selben Prozess; darunter 19 BackgroundService-Instanzen. Auf Linux gibt es kein „WinExe" – eine Avalonia-App braucht X11/Wayland, ein Trading-Server hat typischerweise kein Display.
Entschieden: Variante 2 – eingefrorener Stand. Die WinForms-UI bleibt auf dem heutigen Stand und wird nicht weiterentwickelt; später verschwindet sie vollständig aus dem Build, damit keine Altlast mitgeschleppt wird.
Was das konkret heißt:
| Einfrieren | Vor Beginn von L3 einen Git-Tag setzen (Vorschlag: winforms-final) und im Repo dokumentieren. Ab da keine Feature-Arbeit mehr an Ui/ und den Ui/-Ordnern der Module. |
| Fallback im Ernstfall | Tag auschecken, bauen, starten. Greift auf dieselbe MySQL zu — Anzeige und Einstellungen bleiben voll brauchbar. |
| Weiterentwicklung | Läuft unverändert weiter — nur eben im Trading-Kern, der ab L2 headless auf Linux läuft. Eingefroren ist die Oberfläche, nicht die Funktion. |
| Endgültiger Ausbau | Nach Abschluss von L4: Ui/-Ordner, PolyTrader.App als WinExe, alle .Designer.cs/.resx, UseWindowsForms, favicon.ico-Einbindung und ApplicationConfiguration.Initialize() entfernen. ~4.500 LOC generierter Designer-Code fallen ersatzlos weg. |
Wichtig für die Reihenfolge: Damit der eingefrorene Stand überhaupt lauffähig bleibt, darf P1 die WinForms-UI nicht kaputtmachen. Beim Entkoppeln von Core und Modulen (Zielframework net8.0, System.Drawing raus) muss der WinForms-Zweig bis zum Tag baubar bleiben — praktisch heisst das: erst taggen, dann P1 durchziehen. Sonst ist der Fallback genau in dem Moment weg, in dem man ihn braucht.
Konkreter Vorschlag zur Absicherung: Vor P1 einmal
dotnet publish -c Releasedes heutigen Stands erzeugen und das Ergebnis zusammen mit dem Tag archivieren. Ein fertig gebautes Verzeichnis ist im Ernstfall schneller einsatzbereit als ein Checkout, der erst wieder kompiliert werden muss — und beweist nebenbei, dass der eingefrorene Stand tatsächlich läuft.
Unabhängig davon nötig: headless-fähiger Einstiegspunkt für den Linux-Betrieb, systemd-Unit mit Restart=always und Environment=TZ=…, Master-Key über EnvironmentFile mit chmod 600, Logrotate für Logs/*.jsonl.
5.6 Tests und CI
- Testprojekt auf
net8.0(fällt mit den Referenzen). WindowMenuTests.cs– einziger UI-Test, neu schreiben (~0,5 PT).TradeRowColoringTests.cs– zieht mit dem Farbtyp-Wechsel mit (~0,2 PT).AccountingReportTests.cs:131– braucht den neuen FontResolver.--smoke-ui(Program.cs:500-570): konstruiert alle Views headless und hat laut Kommentar bereits Designer-Regressionen gefangen. Unbedingt erhalten – Avalonia bietetAvalonia.Headless, das Muster überträgt sich gut.- Es gibt keine CI. Die Portierung ist der richtige Anlass,
dotnet build+dotnet testauf einem Linux-Runner einzurichten – sonst schleicht sich die Windows-Abhängigkeit unbemerkt zurück.
6. Aufwandsschätzung (Revision 2)
| # | Paket | Änderung ggü. Rev. 1 | Aufwand |
|---|---|---|---|
| erledigt ✅ | |||
| P0 | Zielbild, Avalonia-Spike an einer echten View, Layout-Konventionen festzurren | — | 2–3 PT |
| P1 | Core/Module von WinForms entkoppeln, TargetFramework auf net8.0, System.Drawing raus |
— | 2–3 PT |
| P2 | Kultur & Zeit: IAppClock, Zeitzone bei Installation festlegen, Kultur pinnen, Analyzer |
−1 PT (Entscheidung 6 vereinfacht) | 2–3 PT |
| P3 | Plattform-Kleinkram: Mullvad-Pfad + Status-Parsing, Unix-Dateirechte | −0,5 PT (Threema entfällt) | 1 PT |
INotificationSink |
erledigt 06.08.2026 ✅ | ||
| P3c | Lizenz/Watchdog auf Deploymentcenter umstellen | neu (Entscheidung 5) | 2–3 PT |
| P4 | PDF-Export: PDFsharp Core + IFontResolver + eingebettete Schrift |
— | 1–2 PT |
| — | ✅ erledigt (0b8728b) |
||
ShellUiHost, Shutdown-Dialog, Widgets |
— | ✅ erledigt (cd59e5c, 7f0b05e). Lizenzdialog bewusst ausgenommen → kommt mit P3c |
|
| — | ✅ erledigt (0b8728b, 4ba8149, 929fa20) – PropertyGrid durch kategorisierten SettingsEditor ersetzt |
||
| — | ✅ erledigt (669676a, 7f0b05e) |
||
| P9 | Tests ✅ (450 grün), --smoke-ui auf Avalonia.Headless, Linux-CI fehlt vollständig |
— | 2–3 PT |
| P10 | Deployment: systemd, TZ ✅, Logrotate, Feldtest im Zielland | — | 2–3 PT |
| P11 | WinForms endgültig ausbauen (nach L4): Ui/, Designer-Dateien, UseWindowsForms, WinExe |
— | 1 PT |
| P3c | Deploymentcenter: Lizenzverwaltung, Watchdog, Error-Reporting neu | ⬅️ als Nächstes | 2–4 PT |
| Summe (offen) | 7–13 PT |
Personentage entwickelnder Arbeit. Mit Agentenunterstützung sinkt die Kalenderzeit deutlich; der Review- und Feldtest-Anteil (P9, P10) bleibt weitgehend gleich – bei einer Anwendung, die echtes Geld bewegt, ist das die richtige Reihenfolge.
7. Fahrplan
Die zentrale Erkenntnis — eingetreten, dann überholt
Ursprünglich galt: P1–P4 brauchen kein Avalonia, und mit einem headless-Einstiegspunkt hätte man für ~15 PT von rund 50 den eigentlichen Nutzen — Bot auf Linux, WinForms-UI als eingefrorener Fallback.
So ist es nicht gekommen, und das ist gut so. Die Portierung lief weiter, bis die Oberfläche
vollständig unter Avalonia stand. Der Fallback wurde damit vom Dauerzustand zur reinen
Rückversicherung im Tag winforms-final. Der aktuelle Engpass ist nicht mehr die UI, sondern die
Betriebsreife: Deploymentcenter, CI, systemd und der Feldtest im Zielland.
| Stufe | Inhalt | Aufwand | Ergebnis |
|---|---|---|---|
| — | ✅ erledigt – Faktor-100-Preisfehler behoben, 10 Regressionstests | ||
INotificationSink |
— | ✅ erledigt – 36 Dateien raus, 448 Tests grün, 8 Warnungen weniger | |
winforms-final + P1 (Core/Module entkoppeln) + P4 (PDF) |
— | ✅ erledigt – Core, 4 Module und Testprojekt sind net8.0; alle sechs publishen für linux-x64 ohne Windows-Pakete; 442 Tests grün |
|
| L1b | P0 ✅ + P2 ✅ + P3 (Mullvad, Dateirechte) + P3c (Deploymentcenter) | 2–4 PT | P0/P2 erledigt (Spike wurde zur vollen Portierung; Zeitzone konfigurierbar). Offen: P3c — steht als Nächstes an und ersetzt Lizenzverwaltung, Watchdog und Error-Reporting grundlegend. |
| L2 | 2–4 PT | --headless existiert (App.Avalonia/Program.cs:41). Offen: systemd-Unit + Feldtest. Trading läuft headless auf Linux. ⭐ Meilenstein |
|
| — | ✅ erledigt – Avalonia-Grundgerüst (cd59e5c), Dashboard mit LiveCharts2 (0b8728b), Settings (4ba8149), Terminal (929fa20), Dunkelmodus (487ea44) |
||
| L4 | 2–4 PT | Modul-Fenster portiert (669676a), Restlücken A1–A4 geschlossen (7f0b05e), 450 Tests grün. Offen: es gibt keinerlei CI-Konfiguration – weder .github/workflows noch .gitea/workflows. |
|
| L5 | P11 – WinForms endgültig aus dem Build entfernen | 1 PT | Erst nach Abnahme (A5). Keine Altlast mehr; ~4.500 LOC Designer-Code fallen weg, die 23 Build-Warnungen verschwinden mit |
Reihenfolge-Begründung
- Der Tag
winforms-finalkommt VOR P1. P1 entkoppelt Core und Module von WinForms — danach ist der alte Stand nicht mehr aus dem Hauptzweig baubar. Erst taggen (und einmaldotnet publisharchivieren), dann umbauen. Sonst ist der Fallback genau dann weg, wenn man ihn braucht. - P3c (Deploymentcenter) gehört nach L1, nicht später – der headless-Betrieb in L2 hängt an der
ILicensePrompt-Abstraktion. Ohne sie blockiert der Lizenzdialog den Start auf einem Server ohne Display. - P2 vor L2 – die Zeitzone muss feststehen, bevor der erste Linux-Server Logs und Buchungen schreibt. Nachträglich umzustellen bedeutet gemischte Daten an der Tagesgrenze.
Noch offene Punkte
- Accounting-Zeitzone getrennt von der Anwendungszeitzone? (3.3) – Empfehlung ja, verhindert rückwirkend verschobene Buchungsperioden bei einem Standortwechsel.
- Anzeige-Kultur ebenfalls bei der Installation festlegbar? (4.2) – naheliegend, da dieselbe Begründung wie bei der Zeitzone (Auslandsbetrieb).
- RocketChat und Telegram: Wann sollen die echten
INotificationSink-Implementierungen kommen? Bis dahin landen Auto-Pause- und Eskalationsmeldungen nur im Terminal-Log. Für den unbeaufsichtigten Linux-Betrieb (L2) wäre mindestens ein echter Kanal wichtig — sonst bemerkst du eine Auto-Pause erst beim nächsten Blick ins Log.
8. Risikoliste (Revision 3)
| # | Risiko | Status | Wirkung / Gegenmaßnahme |
|---|---|---|---|
| Kulturabhängiger Parse, Faktor-100-Preisfehler | ✅ behoben | gefixt + 10 Regressionstests unter de-DE | |
| Lizenz-Hardware-ID ändert sich, in Containern instabil | ✅ gelöst | Deploymentcenter HWID v2 mit DEPLOYMENTCENTER_HWID-Override und v1→v2-Migration ohne Platzverlust |
|
| libsodium 1.0.16 gegen moderne glibc | ✅ entfallen | mit dem Threema-Ausbau am 06.08.2026 | |
| R2 | Kein WYSIWYG-Designer; Layout-Regel ist neu | offen | Im Spike (P0) an einer echten View erproben, bevor 16 Views folgen |
| R3 | PropertyGrid-Ersatz durch handgebaute Formulare |
offen | Teuerster Posten (~10 PT). [Description]-Attribute liefern die Spezifikation |
| R5 | Zeitzonen-Semantikwechsel trifft Logs, Tagesberichte, Accounting-Perioden | offen | P2 vor L2; IAppClock; Wirksamkeit erst nach Neustart |
| R6 | Headless-Server ohne Display | offen | ILicensePrompt/ConsoleLicensePrompt aus dem Deploymentcenter-SDK; headless-Einstiegspunkt in L2 |
| R8 | Anzeigeformate kippen auf Invariant (LANG nicht gesetzt) |
offen | Kultur in Program.Main pinnen |
| R9 | Keine CI – Windows-Abhängigkeiten schleichen zurück | offen | Linux-Build in Gitea Actions ab L1 |
| R10 | Mullvad-CLI meldet auf Linux anderen Statustext | offen | Gegen die echte CLI im Zielland verifizieren (5.4) |
| Doppelpflege beider UIs | ✅ entfällt | Variante 2 gewählt (eingefrorener Stand) | |
| Benachrichtigungen fallen still | ✅ entschärft | INotificationSink eingezogen; Meldungen landen im Terminal-Log |
|
| R13 | Kein echter Benachrichtigungskanal bis RocketChat/Telegram da sind | offen | Für den unbeaufsichtigten Betrieb (L2) mindestens einen Kanal implementieren — sonst bleibt eine Auto-Pause bis zum nächsten Log-Blick unbemerkt |
| R14 | Fallback-Stand nicht mehr baubar, weil P1 vor dem Tag lief | offen | Tag winforms-final + archiviertes dotnet publish vor P1 (5.5) |
9. Was nicht angepasst werden muss
- Der gesamte Trading-Pfad:
PolymarketClobClient(EIP-712 über Nethereum),PolymarketApiService,PolymarketWssClient,AlchemyWssClient,CopyTradingEngine,SellLadderService,TraderMonitorService - Persistenz: EF Core 8 + Pomelo/MySQL, alle 15 Migrationen,
EncryptedStringConverter - Sicherheit:
SecretProtection(AES-256-GCM läuft auf Linux über OpenSSL), Master-Key-Ableitung - Alle Analytics:
TradeAnalytics,DossierBuilder, Supervisor-Agent, OpenRouter-Client, MCP-Light-Server - Accounting-Logik: Ingest, Klassifikation, BWA, CSV-Export (nur der PDF-Export braucht den FontResolver)
- Watchdog-Payload-Schema (
source/instance/intervalpassen bereits zur Deploymentcenter-API), Modularitäts-Infrastruktur - 438 von 448 Tests
Das ist der Grund, warum die Schätzung trotz 24.000 LOC damals bei 41–61 PT landete und nicht bei 150: Zwei Drittel des Codes waren bereits plattformneutral, und die Trennung war sauber. Rückblickend hat sich das bestätigt — die Portierung lief schneller als veranschlagt.
10. Anhang: verifizierte Fundstellen
Behoben am 06.08.2026
| Thema | Datei:Zeile |
|---|---|
| Kulturabhängiger Preis-Parse (Faktor 100) | …CopyTrading/Services/TraderMonitorService.cs:1276-1280 → nutzt jetzt ParseDecimal |
ParseDecimal testbar + dokumentiert |
…CopyTrading/Services/TraderMonitorService.cs:1223-1234 |
| Kulturabhängige Parses (Regex-Gruppen) | …Core/Services/PolymarketClobClient.cs:770, 784, 796, 820, 822, 827 |
| Kulturabhängiger Unix-Timestamp | …CopyTrading/Services/MasterTraderAnalyticsJob.cs:140 |
| Neuer Regressionstest (10 Fälle, de-DE) | tests/PolyTrader.Tests/ApiNumberParsingTests.cs |
| Threema vollständig entfernt | ThreemaService.cs, libs/Threema-MsgApi-Net-Core/ (35 Dateien), ServerSettings (7 Properties), Program.cs, SettingsView.cs, PolyTrader.Core.csproj, PolyTraderSharp.sln |
| Neutraler Benachrichtigungsausgang | src/PolyTrader.Core/Notifications/INotificationSink.cs, LogNotificationSink.cs |
Fachdienste auf INotificationSink umgestellt |
MasterTraderAnalyticsJob.cs, SellLadderService.cs, DailyReportService.cs |
| Testdouble mit Assertion-Möglichkeit | tests/PolyTrader.Tests/Fakes/FakeNotificationSink.cs |
Offen
| Thema | Datei:Zeile |
|---|---|
| Windows-Zeitzonen-ID hartcodiert | Ui/Views/TerminalView.cs:150 |
| Harter Windows-Pfad (Mullvad) | src/PolyTrader.Core/Models/ServerSettings.cs:73 |
| Mullvad-Statusprüfung (Wortlaut) | src/PolyTrader.Core/Services/MullvadVpnService.cs:HealthCheckAsync |
| UI-Contract mit WinForms-Typen | src/PolyTrader.Core/Modularity/ModuleUi.cs:32, 36 |
| Menü wird zur Laufzeit gebaut (neue Layout-Regel) | src/PolyTrader.Core/Modularity/WindowMenu.cs:Populate, Ui/ShellUiHost.cs:AttachWindowMenu |
System.Drawing.Color in Modul-Logik |
…CopyTrading/Logic/TradeRowColoring.cs:11-13 |
ScottPlot → System.Drawing.Bitmap |
Ui/Views/DashboardView.cs:207-227 |
| Windows-only Schrift im PDF | …Accounting/Logic/PdfExporter.cs:34 |
| Schlüsseldateien ohne Rechteschutz | Ui/Views/SettingsView.cs:301, 356; Program.cs:393 |
| Headless-Smoke-Test (erhaltenswert) | Program.cs:500-570 |
| Lizenz-Gate mit modalem Dialog | Licensing/LicenseGate.cs, Program.cs:98 |
| Secrets im Klartext | server_settings.xml (WatchdogToken, LicenseKey) |
Referenzen
J:\Softwareprojekte\Deploymentcenter\docs\LICENSE_INTEGRATION_GUIDE.md– Hardware-ID v2,LLS2-Cache, CLI-Schalter, v1→v2-MigrationJ:\Softwareprojekte\Deploymentcenter\docs\WATCHDOG_INTEGRATION_GUIDE.md– Ping-API,X-Agent-Token, Payload-SchemaJ:\Softwareprojekte\Deploymentcenter\client-dotnet\Deploymentcenter.Client– SDK-Quellen (LicenseClient,HardwareId,ILicensePrompt,StateStore)