545 lines
42 KiB
Markdown
545 lines
42 KiB
Markdown
# Analyse & Umsetzungsrahmen: Linux-Fähigkeit von PolyTrader
|
||
|
||
**Stand:** 06.08.2026 · **Revision 4** (P1 + P3b + P4 umgesetzt – nur noch PolyTrader.App ist Windows-gebunden)
|
||
**Basis:** 7 Projekte, ~24.250 LOC Produktivcode (ohne Tests/EF-Migrationen), 448 Tests
|
||
|
||
---
|
||
|
||
## 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:** leicht gesunken auf **41–61 PT**. PropertyGrid (+5 PT) und LiveCharts2 (+1 PT) stehen gegen den bereits **erledigten** Threema-Ausbau (−2 PT), die vereinfachte Zeitzonen-Anforderung (−1 PT) und den entfallenden Lizenz-Umbau (−2 PT).
|
||
|
||
---
|
||
|
||
## 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 ist der teuerste Einzelpunkt? | Der **`PropertyGrid`-Ersatz** durch handgebaute Steuerelemente (6 Instanzen in 4 Fenstern). |
|
||
| Gesamtaufwand | **~33–51 Personentage offen** (P1, P3b, P4 und der Kultur-Fix sind erledigt) |
|
||
| Wichtigste Erkenntnis | **Für ~15 PT (≈ 25 %) bekommst du 80 % des Nutzens**: Der Trading-Kern läuft headless auf Linux, die WinForms-UI bleibt als eingefrorener Fallback. 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 | **+1 PT** (Entscheidung 8) | **2 PT** |
|
||
| **P6** | UI-Shell: Launcher, Fenstermenü (deklarativ!), `ShellUiHost`, Shutdown-, Lizenz-Dialog, Widgets | — | **5–7 PT** |
|
||
| **P7** | Core-Views: Dashboard, Settings, Jobs, Terminal (inkl. **2 PropertyGrid-Ersätze** + `RichTextBox`) | **+3 PT** (Entscheidung 4) | **11–15 PT** |
|
||
| **P8** | Modul-Fenster: CopyTrading (5), ResolutionFarming, Supervisor, Accounting (inkl. **4 PropertyGrid-Ersätze**, 20 DataGrids) | **+2 PT** (Entscheidung 4) | **14–20 PT** |
|
||
| **P9** | Tests, `--smoke-ui` auf `Avalonia.Headless`, Linux-CI | — | **3–4 PT** |
|
||
| **P10** | Deployment: systemd, TZ, Logrotate, Feldtest im Zielland | **−0,5 PT** (Lizenz-Neubindung entfällt) | **3–4 PT** |
|
||
| **P11** | **WinForms endgültig ausbauen** (nach L4): `Ui/`, Designer-Dateien, `UseWindowsForms`, `WinExe` | **neu** (Entscheidung 3) | **1 PT** |
|
||
| | **Summe (offen)** | | **41–61 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 bleibt
|
||
|
||
**P1–P4 brauchen kein Avalonia.** Sie sind auf Windows entwickelbar und verifizierbar. Zusammen mit einem headless-Einstiegspunkt hast du für **~15 PT von rund 50** den eigentlichen Nutzen:
|
||
|
||
> Der **Bot läuft auf Linux**. Die **WinForms-UI bleibt als eingefrorener Fallback** (Entscheidung 3) und greift über dieselbe MySQL auf dieselben Daten zu.
|
||
|
||
| 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 (Avalonia-Spike) + P2 (Zeitzone/Kultur) + P3 (Mullvad, Dateirechte) + P3c (Deploymentcenter) | 6–9 PT | Zeitzone bei Installation festlegbar; Deploymentcenter angebunden; Linux-Kleinkram erledigt |
|
||
| **L2** | Headless-Einstiegspunkt + systemd + Feldtest im Zielland | 3–5 PT | **Trading läuft headless auf Linux.** ⭐ Meilenstein |
|
||
| **L3** | P5 + P6 + P7 (Charts, Shell, Core-Views) | 18–24 PT | Avalonia-Grundgerüst steht; Dashboard, Settings, Terminal nutzbar |
|
||
| **L4** | P8 + P9 (Modul-Fenster, Tests, CI) | 17–24 PT | WinForms funktional abgelöst |
|
||
| **L5** | P11 – WinForms endgültig aus dem Build entfernen | 1 PT | Keine Altlast mehr; ~4.500 LOC Designer-Code fallen weg |
|
||
|
||
### 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 bei 41–61 PT landet und nicht bei 150: **Zwei Drittel des Codes sind bereits plattformneutral, und die Trennung ist sauber.**
|
||
|
||
---
|
||
|
||
## 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`)
|