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