Eine Roadmap statt fuenfzehn Plandokumente; Altbestand ins Archiv

Der Status des Projekts stand verstreut in elf Umsetzungsplaenen, drei
Konzepten, der Linux-Analyse und dem Projektstand - teils widersprechend, teils
wochenlang veraltet. Ab jetzt gibt es genau eine Statusquelle.

docs/ROADMAP.md (neu):
- Alle Vorhaben in vier Stufen A bis D, plus technische Schuld und Verlauf.
  Die Stufen sind eine Reihenfolge, keine Termine: jede schafft die
  Voraussetzung fuer die naechste.
- Statuszeichen: erledigt / offen / blockiert (mit Ursache) / bewusst
  zurueckgestellt / Idee, nicht beschlossen. Damit ist das, was wir NICHT bauen
  wollen, sichtbar vorgehalten statt unauffindbar in einem Plan zu schlummern.
- Inhaltlich getragen, nicht nur verlinkt: je Vorhaben Ziel, Phasen,
  Akzeptanzkriterien, offene Entscheidungen und Leitplanken aus den Quelldokumenten.
- Sichtbar gemacht, was vorher zwischen den Dokumenten verborgen lag:
  CopyTrading Phase 1 ist der Engpass der gesamten Roadmap (MarketMaking und
  BundleArbitrage haben harte Voraussetzungen darauf), und die Sniper-Metriken
  aus Phase 3.2 sind ein Spezialfall des StrategieDrift-Fingerprints - zusammen
  bauen statt doppelt.

Archiv (docs/archiv/):
- 15 Dokumente verschoben (11 Umsetzungsplaene, 3 Konzepte, ANALYSE-Linux-Portierung).
  Sie bleiben die Bauanleitungen mit Code-Bezuegen, Risikotabellen und
  Begruendungen - eingefroren ist nur ihr Status.
- archiv/README.md ordnet jedes Dokument seinem Roadmap-Punkt zu.

Verweise nachgezogen - der eigentliche Aufwand:
- 25 Markdown-Links repariert. 15 davon verschiebungsbedingt (eine Ebene
  tiefer), der Rest war schon vorher falsch: die Ideensammlung verlinkte
  Quellcode relativ zum Repo-Wurzelverzeichnis statt zu docs/.
- 12 Dateien ausserhalb von docs/ verwiesen in Kommentaren auf die Plaene
  (csproj, props, setup.json, sechs Quelldateien) - alle auf archiv/ umgebogen.
- Verweise auf Dateien, die der Fruehjahrsputz geloescht hat (Ui/,
  Program.cs, WindowMenuBar), zu Klartext entschaerft statt tote Links zu lassen.
- Gegenprobe: 85 Links geprueft, 0 kaputt. Build gruen, 476 Tests gruen.

PROJEKTSTAND.md entdoppelt: Abschnitt "Offen" verweist jetzt auf die Roadmap.
Arbeitsteilung ist damit klar - Projektstand sagt was IST, Roadmap was KOMMT.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-23 18:56:50 +02:00
co-authored by Claude Opus 5
parent 5507db3e32
commit 6218a04fe4
33 changed files with 569 additions and 129 deletions
+564
View File
@@ -0,0 +1,564 @@
# Analyse & Umsetzungsrahmen: Linux-Fähigkeit von PolyTrader
**Stand:** 22.08.2026 · **Revision 6** (Avalonia-Portierung P5P8 durch, Deploymentcenter D-0D-5 code-seitig durch, **WinForms ausgebaut**; offen sind CI, systemd/Feldtest und die Abnahmen)
**Basis:** 7 Projekte auf `net10.0`, **alle plattformneutral** — kein `net10.0-windows` mehr im Repo. 476 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 **47 PT** statt 24 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 **4161 PT** geschätzt. **Überholt — Stand 13.08.2026 sind noch 713 PT offen**, weil die Avalonia-Portierung (P5P8) inzwischen durch ist; siehe [Abschnitt 7](#7-fahrplan).
---
## 0. Kurzfassung
| Frage | Antwort |
|---|---|
| Ist es machbar? | Ja, ohne architektonische Sackgassen. |
| Was ist der Löwenanteil? | War die UI: ~8.300 LOC (34 % des Produktivcodes) hingen an WinForms. **Seit dem 22.08.2026 ist davon nichts mehr im Repo** — portiert nach Avalonia, Rest gelöscht. |
| 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 | **~49 Personentage offen** (Stand 22.08.2026). Die UI-Portierung ist inhaltlich durch (P5P8), das Deploymentcenter code-seitig (D-0D-5), der WinForms-Ausbau vollzogen. Offen sind noch **CI** (es gibt bis heute keinerlei Konfiguration), **systemd/Feldtest** sowie die Abnahmen **A5** und die Live-Abnahme des Deploymentcenters. |
| 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<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) 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<WindowMenuEntry>` 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:** ~810 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.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 12 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:<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:
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:** ~23 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** | ✅ **Vollzogen am 22.08.2026.** `Ui/`, `PolyTrader.App.csproj`, alle `.Designer.cs`/`.resx`, `UseWindowsForms`, `WinExe`, `favicon.ico` und `ApplicationConfiguration.Initialize()` sind entfernt; das Projekt ist aus `PolyTraderSharp.sln` genommen. Die PNG-Symbole sind **nicht** mitgelöscht, sondern nach `src/PolyTrader.App.Avalonia/Assets/` gewandert (der `avares://`-Pfad blieb gleich). |
**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 | — | **23 PT** |
| **P1** | Core/Module von WinForms entkoppeln, `TargetFramework` auf `net8.0`, `System.Drawing` raus | — | **23 PT** |
| **P2** | Kultur & Zeit: `IAppClock`, Zeitzone bei Installation festlegen, Kultur pinnen, Analyzer | **1 PT** (Entscheidung 6 vereinfacht) | **23 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) | **23 PT** |
| **P4** | PDF-Export: PDFsharp Core + `IFontResolver` + eingebettete Schrift | — | **12 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** | — | **23 PT** |
| **P10** | Deployment: systemd, TZ ✅, Logrotate, Feldtest im Zielland | — | **23 PT** |
| ~~**P11**~~ | ~~**WinForms endgültig ausbauen**: `Ui/`, Designer-Dateien, `UseWindowsForms`, `WinExe`~~ | — | ✅ **erledigt** (22.08.2026) |
| **P3c** | **Deploymentcenter**: Lizenzverwaltung, Watchdog, Error-Reporting neu — D-0 bis D-5 code-seitig erledigt, **Live-Abnahme offen** | — | **Rest: Abnahme** |
| | **Summe (offen)** | | **49 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: **P1P4 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)** | 24 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 | 24 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**) | 24 PT | Modul-Fenster portiert (`669676a`), Restlücken A1A4 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~~ | — | ✅ **erledigt am 22.08.2026** (Frühjahrsputz). Vorgezogen vor die A5-Abnahme, weil der Rückfall-Tag `winforms-final` auf dem Server gesichert ist und A5 auch gegen einen Worktree dieses Tags geprüft werden kann. Ergebnis: 91 Dateien und ~4.500 LOC Designer-Code weg, Build-Warnungen von 23 auf 15 gesunken, `LicenseLabrador.Client` mit ausgebaut (D-6). |
### 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 4161 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`)