# 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<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:** ~8–10 PT statt ~2 PT mit Fremdbibliothek. Der Aufschlag ist bewusst und kauft dauerhafte Wartbarkeit ohne Fremdabhängigkeit.

### 2.3 `RichTextBox` → Terminal-Fenster

`TerminalView` (378 + 195 LOC) und `SupervisorMainForm` (Chat) nutzen `RichTextBox` mit **Farbausgabe pro Log-Zeile**. Avalonia hat kein `RichTextBox`.

**Empfehlung:** `ItemsControl` über eine `ObservableCollection<LogLine>` mit Farb-Converter – virtualisiert, deklarativ (passt zur neuen Layout-Regel), und schneller als heute. `TerminalView.cs:186` implementiert bereits ein Auto-Clear bei RAM-Limit; der Performance-Druck ist bekannt, ein virtualisiertes `ItemsControl` löst ihn an der Wurzel.

### 2.4 Charts: ScottPlot → LiveCharts2 (Entscheidung 8)

Nur **3 Charts**, alle in `DashboardView.RenderCharts()` (Equity-Kurve, PnL je Modul, PnL je Tag). Die Auswertungslogik liegt bereits pur in `TradeAnalytics` und bleibt **komplett unverändert**.

Der heutige Weg (`plot.GetImage(w,h).GetImageBytes()` → `System.Drawing.Bitmap` → `PictureBox`, `DashboardView.cs:207-227`) entfällt und wird zu drei deklarativen `CartesianChart`-Elementen im XAML – was der neuen Layout-Regel ohnehin besser entspricht als das heutige Rendern im Code.

> **Zur Einordnung deiner Sorge:** Der Unterschied ist kleiner als befürchtet. `ScottPlot.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:<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:** ~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`)
