# 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](./umsetzungsplaene/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`, `metrics` und `status: "stopped"` dazu), und §5.2 wie Risiko **R6** stützen die > headless-Lizenzprüfung auf `ILicensePrompt` aus 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](#41-erledigt--der-aktive-bug-ist-behoben) | | 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](#52-lizenz--watchdog--jetzt-über-das-deploymentcenter) | | 6 | Zeitzone | **einmalig bei der Installation** festgelegt, nicht im laufenden Betrieb gewechselt | vereinfacht die Umsetzung, siehe [3.3](#33-anforderung-konfigurierbare-zeitzone-entscheidung-6) | | 7 | Threema | **entfernt** ✅; Nachfolger: **RocketChat + Telegram** | umgesetzt am 06.08.2026, siehe [5.3](#53-threema--entfernt-) | | 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](#7-fahrplan). --- ## 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](#7-fahrplan). | --- ## 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`~~ (lib) | — | — | **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
, 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) und `View.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 erzeugte `ToolStripMenuItem`). Das verstößt gegen die neue Regel und wird zu einem deklarativen `Menu` mit `ItemsSource`-Bindung an eine `ObservableCollection` – Layout im XAML, nur die Daten kommen aus dem Code. Gleiches gilt für die dynamisch angehängte Menüleiste in `ShellUiHost.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` 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.Avalonia` wä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.IImage` - `TradeRowColoring.cs:11-13` – `Color.FromArgb(...)` → eigenes Farbtupel oder `Avalonia.Media.Color` - `WindowMenu.cs` – `Font`, `FontStyle.Bold` - `Models/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`: ```csharp 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:** 1. **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. 2. **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 + Warnung ``` Damit ist die Zeitzone an **einer** Stelle wirksam statt an 23. 3. **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-timezone` für den headless Betrieb) und in `server_settings.xml` festgeschrieben. - 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. 4. **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 Helper `ParseDecimal` statt duplizierter Inline-Logik. Der Helper ist `internal static` und dokumentiert. - `PolymarketClobClient.cs:770, 784, 796, 820, 822, 827` – alle `TryParse` auf `InvariantCulture` (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:** 1. **Anzeige-Kultur beim Start explizit pinnen**: `CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("de-DE")` in `Program.Main`. Damit ist die Darstellung unabhängig von `LANG`. (Sinnvollerweise ebenfalls konfigurierbar, analog zur Zeitzone – dieselbe Begründung: Betrieb im Ausland.) 2. **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=true`** im Publish-Profil, `libicu` auf 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::` 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: 1. **Lizenz:** `Licensing/LicenseGate.cs` und `Ui/LicenseDialog.cs` von `LicenseLabrador.Client` auf `Deploymentcenter.Client` umstellen. Die API unterscheidet sich: `LicenseResult`/`LicenseState`/`GetHardwareId()` → `LicenseValidationResult` (`IsValid`, `Status`, `IsCached`, `Message`) / `HardwareId.GetHardwareId(slug)` / `ValidateAsync(slug, key, serverUrl)`. **`ILicensePrompt` implementieren** 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 in `lib/nuget/` wird ersetzt. 2. **Watchdog:** `WatchdogHeartbeatService` sendet bereits `source`, `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`- und `group`-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.xml` enthält den Watchdog-Token und den Lizenzschlüssel im Klartext (`WatchdogToken`, `LicenseKey`). Beim Umbau auf das Deploymentcenter ist der passende Moment, beide über die `SecretProtection` laufen 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:** 1. **No-Throw-Vertrag.** `SendAsync` wirft 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 eigene `try/catch`-Blöcke — der Code wurde dabei kürzer, nicht länger. **Bindend für die kommenden RocketChat- und Telegram-Implementierungen.** 2. **`NotificationSeverity`** ist 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, kein `netsh` nötig. Unkritisch. - **`MullvadVpnService`**: Der CLI-Aufruf ist portabel (`UseShellExecute = false`, sauber). Auf Linux heißt die CLI `mullvad` (typisch `/usr/bin/mullvad`). **Achtung:** `HealthCheckAsync()` prüft `status.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 Release` des 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 bietet `Avalonia.Headless`, das Muster überträgt sich gut. - **Es gibt keine CI.** Die Portierung ist der richtige Anlass, `dotnet build` + `dotnet test` auf 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 | |---|---|---|---:| | ~~L0~~ | ~~Kultur-Bug~~ | **erledigt** ✅ | ~~0,5 PT~~ | | **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** | | ~~**P3b**~~ | ~~Threema-Ausbau + `INotificationSink`~~ | **erledigt 06.08.2026** ✅ | ~~2 PT~~ | | **P3c** | **Lizenz/Watchdog auf Deploymentcenter umstellen** | **neu** (Entscheidung 5) | **2–3 PT** | | **P4** | PDF-Export: PDFsharp Core + `IFontResolver` + eingebettete Schrift | — | **1–2 PT** | | ~~**P5**~~ | ~~Charts auf LiveCharts2~~ | — | ✅ **erledigt** (`0b8728b`) | | ~~**P6**~~ | ~~UI-Shell: Launcher, Fenstermenü (deklarativ!), `ShellUiHost`, Shutdown-Dialog, Widgets~~ | — | ✅ **erledigt** (`cd59e5c`, `7f0b05e`). Lizenzdialog **bewusst ausgenommen** → kommt mit P3c | | ~~**P7**~~ | ~~Core-Views: Dashboard, Settings, Jobs, Terminal~~ | — | ✅ **erledigt** (`0b8728b`, `4ba8149`, `929fa20`) – PropertyGrid durch kategorisierten `SettingsEditor` ersetzt | | ~~**P8**~~ | ~~Modul-Fenster: CopyTrading, ResolutionFarming, Supervisor, Accounting~~ | — | ✅ **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 | |---|---|---:|---| | ~~**L0**~~ | ~~Kultur-Bug~~ | — | ✅ **erledigt** – Faktor-100-Preisfehler behoben, 10 Regressionstests | | ~~**L0b**~~ | ~~Threema-Ausbau + `INotificationSink`~~ | — | ✅ **erledigt** – 36 Dateien raus, 448 Tests grün, 8 Warnungen weniger | | ~~**L1a**~~ | ~~Tag `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** | ~~Headless-Einstiegspunkt~~ ✅ + systemd + Feldtest im Zielland | 2–4 PT | `--headless` existiert ([`App.Avalonia/Program.cs:41`](../src/PolyTrader.App.Avalonia/Program.cs)). Offen: systemd-Unit + Feldtest. **Trading läuft headless auf Linux.** ⭐ Meilenstein | | ~~**L3**~~ | ~~P5 + P6 + P7 (Charts, Shell, Core-Views)~~ | — | ✅ **erledigt** – Avalonia-Grundgerüst (`cd59e5c`), Dashboard mit LiveCharts2 (`0b8728b`), Settings (`4ba8149`), Terminal (`929fa20`), Dunkelmodus (`487ea44`) | | **L4** | ~~P8 (Modul-Fenster)~~ ✅ + P9 (**CI**) | 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-final` kommt VOR P1.** P1 entkoppelt Core und Module von WinForms — danach ist der alte Stand nicht mehr aus dem Hauptzweig baubar. Erst taggen (und einmal `dotnet publish` archivieren), 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 1. **Accounting-Zeitzone getrennt von der Anwendungszeitzone?** ([3.3](#33-anforderung-konfigurierbare-zeitzone-entscheidung-6)) – Empfehlung ja, verhindert rückwirkend verschobene Buchungsperioden bei einem Standortwechsel. 2. **Anzeige-Kultur ebenfalls bei der Installation festlegbar?** ([4.2](#42-ausgabeformatierung-offen)) – naheliegend, da dieselbe Begründung wie bei der Zeitzone (Auslandsbetrieb). 3. **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 | |---|---|---|---| | ~~R1~~ | Kulturabhängiger Parse, Faktor-100-Preisfehler | ✅ **behoben** | gefixt + 10 Regressionstests unter de-DE | | ~~R4~~ | Lizenz-Hardware-ID ändert sich, in Containern instabil | ✅ **gelöst** | Deploymentcenter HWID v2 mit `DEPLOYMENTCENTER_HWID`-Override und v1→v2-Migration ohne Platzverlust | | ~~R7~~ | 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) | | ~~R11~~ | Doppelpflege beider UIs | ✅ **entfällt** | Variante 2 gewählt (eingefrorener Stand) | | ~~R12~~ | 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`/`interval` passen 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-Migration - `J:\Softwareprojekte\Deploymentcenter\docs\WATCHDOG_INTEGRATION_GUIDE.md` – Ping-API, `X-Agent-Token`, Payload-Schema - `J:\Softwareprojekte\Deploymentcenter\client-dotnet\Deploymentcenter.Client` – SDK-Quellen (`LicenseClient`, `HardwareId`, `ILicensePrompt`, `StateStore`)