Richards Vorgabe vom 14.09.2026 (#587): Updates nie automatisch installieren,
aber regelmaessig pruefen und darauf hinweisen.
Bisher pruefte der Desktop genau einmal beim Start, das WebUI nur beim
Seitenaufruf, und ab Werk gar nicht (UpdateCheckEnabled=false). Eine
wochenlang laufende oder frisch installierte Anwendung erfuhr nie von einem
neuen Release.
- UpdateCheckEnabled ab Werk true, neu UpdateCheckIntervalHours (6, 1..168)
- UpdateCheckBackgroundService: erste Pruefung nach 30 s, dann im Takt; laeuft
auch ohne WebUI und kopflos; Hinweis im Log einmal je neuer Fassung
- Registrierung ueber AddPolyTraderUpdateCheck, nicht mehr am WebUI
- Desktop-Banner haengt an UpdateStatusService.Checked; "Spaeter" blendet bis
zur naechsten neueren Fassung aus
- WebUI-Banner holt alle 10 Minuten den vorgehaltenen Stand
Auf der Linux-VM geprueft: 0.1.9 ohne Handeinstellung erkennt 0.2.0, ein
Hinweis im Log, Banner erscheint, "Spaeter" blendet aus, Beenden in 0,1 s.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umsetzung von #567.
Entscheidung Richard, 13.09.2026: Geheimnisse verwalten wir ausschliesslich im
Desktop, und selbst dort erst nach zusaetzlicher Passwortabfrage.
Bisher sah und aenderte jeder, der die Desktop-Oberflaeche offen vor sich
hatte:
- Polymarket-Konten mit ApiKey, ApiSecret, ApiPassphrase und PrivateKey
- Master-Key, OpenRouter-Key, Watchdog-Token und Lizenzschluessel
- Mullvad-Account und Polygon-RPC-URL mit Alchemy-Key, offen im Editor
- das WebUI-Token, per "Anzeigen" lesbar
Jetzt gesperrt, bis entsperrt:
- Tab "Polymarket Accounts": Gesperrt stehen die Konten gar nicht im
visuellen Baum. Das Raster bleibt leer, der Editor mit den Private Keys
ebenfalls; nichts ist nur verdeckt.
- Master-Key erzeugen, OpenRouter-, Watchdog- und Lizenzschluessel setzen.
- Mullvad-Account und Polygon-RPC-URL: mit [SensitiveSetting] markiert und im
Editor ausgeblendet.
- WebUI-Zugang: Gesperrt steht das Token nicht einmal maskiert im Feld.
Speichern ist mitgesperrt, weil es den Feldinhalt als Token schreibt;
gesperrt und leer haette es das Token geloescht.
- Frei bleiben Lizenzpruefung und Test-Heartbeat, weil sie keinen Schluessel
zeigen.
Kern im Core (PolyTrader.Core.Security), weil nur dort testbar:
- SettingsPasswordStore: PBKDF2-HMAC-SHA256, 600 000 Iterationen, Salt, eigene
Datei settings-password.json neben master.key, atomar geschrieben und nur fuer
den Besitzer lesbar. Nicht in server_settings.xml, die seit #561 das WebUI
mitschreibt. Ein vorhandenes Passwort wird nie ueberschrieben.
- SettingsLock:
- erstes Festlegen mit Wiederholung und mindestens 10 Zeichen
- zwei freie Fehlversuche, danach 5 s bis 5 min Wartezeit, in der auch das
richtige Passwort nicht geprueft wird
- Sperre nach 5 Minuten ohne geschuetzte Aktion und beim Verlassen der
Einstellungen
- eine unlesbare Passwortdatei entsperrt nie und gilt auch nicht still als
"kein Passwort"
- Protokoll ohne eingegebene Werte
Oberflaeche: PasswordDialog deklarativ in .axaml, Sperr-Leiste ueber den Tabs.
Die PBKDF2-Pruefung dauert etwa eine halbe Sekunde und laeuft nicht im
UI-Thread.
Grenzen:
- Die Sperre schuetzt gegen jemanden an einer entsperrten Desktop-Sitzung. Wer
Dateizugriff hat, liest master.key selbst. Deshalb gibt es kein "Passwort
vergessen": Wer settings-password.json loeschen kann, braucht keins.
- Die Wartezeit nach Fehlversuchen beginnt nach einem Neustart von vorn.
- "Datenbankeinstellungen": Eine Ansicht dafuer gibt es im Desktop nicht; die
Verbindungszeichenfolge steht nur in appsettings.Local.json. Entsteht eine,
gehoert sie hinter dieselbe Sperre.
Tests: 20 fuer Passwortdatei und Sperre (Festlegen, Fehlversuche, Wartezeit,
Leerlauf, unlesbare Datei, Aendern, Iterationszahl aus der Datei, Dateirechte)
und 5 fuer die Markierung der Geheimnisfelder. Die Oberflaeche selbst wird auf
der VM abgenommen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umsetzung von #565.
GET /api/updates liefert die laufende Fassung (BuildInfo), die verfuegbare,
den Hinweis auf ein Sicherheitsupdate und den Changelog. Geprueft wird mit
demselben Aufruf wie beim Start des Desktops (UpdateClient gegen das
Deploymentcenter). Den Lizenzschluessel fuer den Paketabruf liest der Server,
er verlaesst ihn nicht.
Zwischenspeicher: UpdateStatusService haelt das Ergebnis 30 Minuten vor.
"Jetzt pruefen" umgeht das, aber fruehestens nach 60 Sekunden; bis dahin kommt
der vorige Stand mit der Wartezeit zurueck. Gleichzeitige Aufrufe loesen eine
einzige Pruefung aus. Ohne das fragte jeder Seitenaufruf, jedes offene
Browserfenster, das Deploymentcenter. Die Pruefung liegt hinter IUpdateChecker
und ist damit ohne Netz testbar.
Kein Installieren ueber das WebUI, und ein Test haelt fest, dass es keine
solche Route gibt: Die Installation startet den Update-Agent und beendet
danach die Anwendung und mit ihr den Webserver. Richards Vorgabe beschraenkt
das Beenden ueber das WebUI auf das Trading (#563).
Oberflaeche:
- Karte "Version & Updates" in den Einstellungen, mit "Jetzt pruefen".
- Im Dashboard ein Banner wie im Desktop-Launcher, sobald ein Update vorliegt.
Er verweist zur Installation auf den Desktop.
Nicht durch Unit-Tests abgedeckt: DeploymentcenterUpdateChecker selbst, weil
er ueber das Netz geht. Er wird auf der VM gegen das echte Deploymentcenter
geprueft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umsetzung von #564.
Das WebUI zeigte bisher nur den Live-Stream seit dem Prozessstart. GET
/api/logs lieferte den Verlauf im Speicher, die Oberflaeche nutzte ihn nicht.
Der Desktop hat einen Log-Viewer ueber die Tagesdateien. Den gibt es jetzt auch
im WebUI.
- GET /api/logs/files listet die vorhandenen Tage (Logs/yyyy-MM-dd.jsonl),
neueste zuerst. Die .log-Textdateien und fremde Dateien bleiben aussen vor.
- GET /api/logs/file filtert nach Level, Korrelations-ID und Text, alle ohne
Gross-/Kleinschreibung. Hoechstens 20000 Treffer wie im Desktop, dazu
Seitenaufteilung. Unlesbare Zeilen werden uebersprungen und gezaehlt.
- Der Dateiname entsteht aus dem geprueften Datum, nie aus der Eingabe.
"../../etc/passwd" kommt deshalb gar nicht erst in den Pfad; die Tests halten
das fest.
- Gelesen wird mit FileShare.ReadWrite, weil der Logger laufend an die
Tagesdatei anhaengt.
Oberflaeche: "Live" und "Log-Viewer (vergangene Tage)" als Unter-Tabs.
"Alles kopieren" im Live-Tab, "Seite kopieren" im Viewer. navigator.clipboard
gibt es nur in sicheren Kontexten, ueber LAN/VPN laeuft das WebUI aber per
http. Dafuer gibt es einen Rueckweg ueber ein unsichtbares Textfeld, sonst
schluege das Kopieren dort still fehl. Logzeilen werden per textContent
eingesetzt, nicht per innerHTML: Logtexte enthalten, was andere eingegeben
haben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umsetzung von #561.
Entscheidung Richard, 13.09.2026: Alles, was keine Geheimnisse beruehrt, soll
ueber das WebUI gehen; Geheimnisse ausschliesslich im Desktop.
Eine ausdrueckliche Freigabeliste (ServerSettingsCatalog) statt Reflection. Der
Desktop zeigt per Reflection alles, was ServerSettings hat. Uebernaehme das
WebUI das, stuende jedes kuenftige Feld automatisch im Netz, auch ein
Geheimnis.
Bearbeitbar, je Feld mit Pruefung:
- Farbschema und Zeitzone: nach Neustart
- VPN an/aus und VPN-Standort: sofort
- Blockchain-Listener: nach Neustart
- Polymarket-WSS: sofort
- Watchdog an/aus, Source, Instance und Intervall: sofort
"Sofort" und "nach Neustart" sind am Code belegt, nicht am Feldnamen geraten.
Zeitzone und Blockchain-Listener werden nur beim Start gelesen, das Farbschema
haelt der ThemeManager.
Nur im Desktop, Aenderungsversuch 403 und protokolliert:
- Geheimnisse: Mullvad-Account, Polygon-RPC-URL (enthaelt den Alchemy-Key),
Watchdog-Token, Lizenzschluessel
- Watchdog-URL: an diese Adresse geht der Watchdog-Token, wer sie aendert,
bekommt ihn
- Mullvad-CLI-Pfad: dieses Programm wird ausgefuehrt
Von den Geheimnissen zeigt das WebUI nur, ob sie gesetzt sind. OpenRouter- und
Master-Key werden dafuer nicht gelesen.
Gefunden dabei: MullvadVpnService baut die Kommandozeile als eine Zeichenkette
("relay set location <Wert>"). Der VPN-Standort nimmt deshalb nur Mullvad-
Kennungen an (cz, cz-prg). "cz account login 999" wird abgelehnt, ein Test
haelt das fest. Source und Instance gehen als JSON ans Deploymentcenter und
sind auf Kennungszeichen begrenzt.
POST prueft alle Werte zuerst und schreibt dann alles oder nichts, ueber
ServerSettingsFile. Danach zieht er den laufenden Stand nach: das Singleton,
MullvadVpnService.ReloadSettings und WatchdogHeartbeatService.ReloadSettings.
Desktop - bisher schrieb er eine WebUI-Aenderung still zurueck:
Die Einstellungen wurden einmal beim Oeffnen geladen, und "Speichern",
"Watchdog-Token setzen" und "Lizenzschluessel setzen" schrieben jeweils das
ganze alte Objekt zurueck. Jetzt:
- Speichern vergleicht innerhalb der Dateisperre den Dateistand vom Laden und
verweigert, wenn sich die Datei inzwischen geaendert hat.
- Token und Lizenzschluessel schreiben nur noch ihr eigenes Feld.
- Eine unlesbare Datei sperrt das Speichern, statt Vorgaben zu zeigen, die
beim Speichern die Schluessel geloescht haetten.
Die WebUI-Ansicht zeigt je Feld den Wirkungsvermerk und schickt nur, was
geaendert wurde. Der Leak-Test legt seine Kanarienwerte jetzt auch in die
Einstellungsdatei, sonst liefe die neue Route gegen eine leere Datei.
Nicht durch Unit-Tests abgedeckt: die Desktop-Aenderung an SettingsWindow
(Oberflaechenklasse). Sie wird mit der Sichtpruefung auf der VM abgenommen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umsetzung von #560.
Die Ansicht "Moduleinstellungen" zeigte bisher nur an, ob ein Modul laeuft. Jetzt
laesst sich dort wie im Modul-Tab des Desktops festlegen, welche Module beim
naechsten Start geladen werden. POST /api/modules/{name}/enabled nimmt den
gewuenschten Zustand ausdruecklich entgegen ({"enabled": true|false}) und
schaltet nicht einfach um. Zwei Klicks aus zwei Fenstern heben sich damit
nicht auf, und ein wiederholter Aufruf aendert nichts mehr.
Fehlerfaelle:
- Unbekanntes Modul: 404.
- Modul mit Aktivierungs-Blocker: 409.
- Fehlender Zustand: 400. Die Pruefung war zuerst lueckenhaft, der Test hat es
gefunden: Mit "bool Enabled" machte ein leerer Rumpf {} daraus false, und ein
unvollstaendiger Aufruf haette das Modul deaktiviert.
Gemeinsame Quelle statt Kopien:
- KnownModules: Die Modulliste stand viermal im Code (Program, CoreViews,
ModuleViews, hartkodiert im WebUI). Der Start registriert jetzt die eine
Liste.
- ModuleActivationView: Die Statusregeln des Desktops ("Aktiv - stoppt nach
Neustart" usw.) liegen im Core. Desktop und WebUI zeigen dasselbe.
ServerSettingsFile - gefunden, bevor das WebUI in die Datei schreiben durfte:
ServerSettings.Load verschluckt jeden Lesefehler und liefert Vorgaben. "Frisch
lesen, ein Feld aendern, speichern" haette eine gesperrte oder beschaedigte
server_settings.xml durch Vorgaben ersetzt, und mit ihr Lizenzschluessel,
Watchdog-Token, Mullvad-Account und Alchemy-Key. Genau so schrieb auch der
Modul-Schalter des Desktops. ServerSettingsFile:
- liest streng und schreibt bei unlesbarer Datei nichts,
- schreibt atomar ueber eine Nachbardatei,
- erhaelt unter Linux die Dateirechte,
- serialisiert Aenderungen prozessweit.
Der Desktop-Schalter benutzt ihn jetzt ebenfalls.
Tests: Lizenzschluessel, Token und Alchemy-Key bleiben beim Umschalten in der
Datei; eine kaputte Datei bleibt Byte fuer Byte unveraendert und die Anzeige
bietet dann kein Umschalten an; Blocker, 404, 400, Idempotenz; die
Statustabelle; strenges Lesen, atomares Schreiben und Rechte am Dateiobjekt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Behebt #570.
"Trading pausieren" hielt drei Wege nicht an, die Orders am CLOB platzieren:
1. SellLadderService: Eine laufende Verkaufsleiter preiste alle 5 s weiter
nach, storniert und neu platziert. Sie pruefte weder Pause noch Modus. Die
Engine ignoriert bei Pause zwar alle Signale, die bereits laufende Leiter
lief aber ungebremst.
2. TraderMonitorService: Der REST-Pre-Redeem feuert aus der
Positions-Synchronisierung, die bewusst auch bei Pause weiterlaeuft. Er
pruefte nur LiveTradingMode.
3. PolymarketWssClient: Die Pause wurde nur beim Verbindungsaufbau geprueft.
Auf einer bereits offenen Verbindung loesten Kursmeldungen den Auto-Redeem
weiter aus.
Die Ursache ist allen gemeinsam: Jeder Dienst hat seine Sperre selbst
nachgebaut, und jeder hat etwas anderes vergessen. Die Regeln liegen jetzt an
einer Stelle, in TradingGate, und alle drei benutzen sie:
- Verkaufsleiter: Pause oder Modus des Kontotyps "Inaktiv" friert sie ein,
bei "Nur Verkauf" laeuft sie weiter. Das entspricht der Engine.
- Pre-Redeem (REST und WSS): wie bisher nur bei Modus Aktiv, jetzt zusaetzlich
nicht bei Pause.
Eine eingefrorene Leiter storniert nichts und platziert nichts. Das gilt auch
am Floor, wenn dort die ruhende Order fehlt. Die ruhende GTC-Order bleibt am
Buch, genau wie beim Beenden der Anwendung. LastActionAt bleibt unveraendert,
nach der Freigabe geht es an der aktuellen Stufe weiter. Protokolliert wird
einmal beim Einfrieren und einmal beim Fortsetzen, nicht alle 5 s.
CLOB-kritisch, deshalb:
- Vor der Aenderung gesichert unter agentspace/scripts/backups/*.vor-570-*.bak.
- Orderaufbau und Preislogik sind nicht angefasst. Der Diff umfasst eine Sperre
und drei Bedingungen.
- Die Sperre steht vor dem ersten CLOB-Zugriff.
Die Harness von SellLadderServiceTests setzt jetzt Live = Aktiv. Mit dem
Standard Inaktiv waere Floor_replaces_order rot geworden, und Floor_holds_...
waere gruen geblieben, aber aus dem falschen Grund: keine Order, weil
eingefroren, nicht weil die Order noch ruht.
Neu in SellLadderFreezeTests: Pause, Inaktiv, "Nur Verkauf", Floor im
eingefrorenen Zustand, Fortsetzen an der aktuellen Stufe, dazu die
Entscheidungstabellen von TradingGate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umsetzung von #563.
Entscheidung Richard, 13.09.2026: Beenden soll ueber das WebUI gehen, aber
beschraenkt auf das Trading. Ein Ende der ganzen Anwendung naehme den Webserver
mit; danach waere das WebUI nicht mehr erreichbar.
POST /api/dashboard/stop-trading setzt Live- und Demo-Modus auf Inaktiv und
schaltet die Pause ein. Damit halten CopyTrading und ResolutionFarming (#569)
an, wie beim Ende der Anwendung. Offene Orders an der Boerse werden bewusst
nicht storniert: Das tut auch das Beenden der App nicht, und ein Eingriff in
die CLOB-Integration gehoert nicht in einen Notschalter. Der Dialog sagt das
ausdruecklich.
Die Route verlangt {"confirmed": true}. Ein Aufruf ohne Absicht, etwa aus einem
alten Skript, beendet nichts. Der Vorgang wird als Warnung protokolliert, mit
den Modi vorher.
Im Dashboard steht unter der Notfall-Pause jetzt "Trading beenden ...". Der
Dialog ist wie im Desktop 10 Sekunden gesperrt, Abbrechen und Esc gehen
jederzeit. Wieder aufgenommen wird nur ueber die Modus-Schalter. Die Pause
aufzuheben genuegt nicht, das haelt ein Test fest.
Offen bis #570: Laufende Verkaufsleitern und der REST-Pre-Redeem pruefen die
Pause nicht. Der Inaktiv-Modus, den dieser Knopf setzt, sperrt den Pre-Redeem
bereits; die Leitern friert erst #570 ein.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Behebt #562.
Entscheidung Richard, 13.09.2026: Geheimnisse verwalten wir ausschliesslich im
Desktop.
Bis dahin lieferte GET /api/settings/webui das Zugangs-Token im Klartext. Ueber
das WebUI liessen sich ausserdem ein Token setzen, ein neues erzeugen und
LocalOnly abschalten. Wer das Token hatte, konnte das WebUI damit ueber
unverschluesseltes HTTP ins Netz oeffnen, sich den Zugang verlegen oder sich
selbst aussperren.
Jetzt:
- GET /api/settings/webui liefert Enabled, Port, LocalOnly und vom Token nur
hasToken.
- POST /api/settings/webui und /webui/generate-token antworten mit 403 und
protokollieren den Versuch. Sie bleiben als ausdrueckliche Ablehnung stehen:
Ohne eigene Route faengt die Fallback-Route den POST ab und antwortet mit 200,
ein altes Skript hielte das fuer einen Erfolg.
- Die WebUI-Einstellungsansicht zeigt den Zugang nur noch an. Gepflegt wird er
im Desktop unter Einstellungen -> WebUI.
WebUiSecretsTests zaehlt alle GET-Routen selbst auf und prueft jede Antwort
gegen Kanarienwerte. Geprueft werden ApiKey, ApiSecret, ApiPassphrase und
PrivateKey eines Kontos, Mullvad-Account, Alchemy-Key, Watchdog-Token,
Lizenzschluessel und das WebUI-Token. Eine neue Route, die ein Konto oder die
Server-Einstellungen serialisiert, faellt damit auf, ohne dass jemand an diesen
Test denken muss. Gegen die alte Route schlug er fehl ("GET /api/settings/webui
liefert ein Geheimnis aus"), jetzt ist er gruen.
Ausserdem festgehalten: /api/accounts kennt nur GET. Polymarket-Konten bleiben
im WebUI lesbar und nicht aenderbar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Behebt #569.
GlobalTradingPaused, LiveTradingMode und DemoTradingMode wertete bisher allein
CopyTrading aus. Die Farming-Ausfuehrung pruefte nur RfSettings.Enabled und
eroeffnete weiter Positionen, auch wenn "Trading pausieren" gedrueckt war oder
der Demo-Modus auf Inaktiv stand. Der Pause-Knopf hielt damit nur die Haelfte
an.
Echtes Geld war noch nicht betroffen: RF fuehrt nur Demo-Konten aus, die
Live-Ausfuehrung wird als Zielland-Arbeit uebersprungen. Mit der
Live-Anbindung haette eine Pause ohne Wirkung vor echten Positionen gestanden.
Jetzt gelten dieselben Regeln wie im CopyTrading. Die Pause sperrt, und der
Modus des Kontotyps (Demo oder Live) muss auf Aktiv stehen. "Nur Verkauf"
sperrt ebenfalls, denn Farming kauft ausschliesslich. Protokolliert wird nur
der Wechsel zwischen gesperrt und frei, nicht jeder Fuenf-Minuten-Durchlauf.
VERHALTENSAENDERUNG: Demo-Farming eroeffnet nur noch bei Demo-Modus = Aktiv.
Bisher lief es unabhaengig vom Modus, und der steht nach jedem Start auf
Inaktiv.
Bewusst nicht gesperrt ist die Aufloesungsueberwachung. Sie bucht nur Maerkte
ab, die bereits aufgeloest sind, und sendet keine Orders. Hielte man sie an,
wiche der ausgewiesene PnL von der Wirklichkeit ab. Der Scanner schreibt nur
Kandidaten und bleibt ebenfalls aktiv.
Der Schleifenrumpf liegt jetzt als RunCycle() vor und ist damit testbar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Behebt #568.
Keine der Testdateien mit "EndpointsTests" im Namen hat je einen Endpunkt
aufgerufen. CopyTradingAndResolutionFarmingEndpointsTests beruehrte gar keinen
Produktionscode: Die Tests summierten Listen im Test und verglichen das Ergebnis
mit sich selbst. Modules_KnownModules pruefte, ob ein Array seine eigenen
Elemente enthaelt. Settings_UpdateRequest pruefte die Portbegrenzung an einer im
Test nachgebauten Lambda. Solche Tests bleiben gruen, egal was der Endpunkt tut.
Neu startet WebUiTestHost das WebUI in einem Testserver
(Microsoft.AspNetCore.TestHost) und spricht es ueber HTTP an. Damit der Test
die Kette aus dem Betrieb prueft und keine nachgebaute, liegt sie jetzt in
UsePolyTraderWebPipeline(): statische Dateien, Token-Pruefung, alle Routen.
ConfigurePolyTraderWeb ruft genau diese Methode. Ersetzt wird nur, was
ausserhalb des Prozesses liegt - Kestrel durch den Testserver, MySQL durch den
InMemory-Provider.
Die acht CopyTrading/ResolutionFarming-Tests pruefen dieselben Absichten wie
vorher, jetzt an der Antwort der Route. Neu hinzugekommen sind Tests fuer:
- Zugangssperre: ohne Token, mit falschem Token, ohne konfiguriertes Token
- die Modulliste
- Job-Umschalten samt 404
- die Ablehnung einer leeren Supervisor-Frage
Die beiden Tests, die schon echten Code prueften, bleiben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Behebt #534.
Die vier schreibenden Endpunkte verschluckten die Ausnahme des Repositories und
meldeten anschliessend unbedingt Erfolg. Das Verschlucken war unschoen; die
Reihenfolge war gefaehrlich: copyState wurde auch dann geaendert, wenn die
Datenbank abgelehnt hatte. Das Modul handelte danach mit Risikolimits, die
nirgends standen, und nach dem naechsten Neustart konnte niemand mehr
rekonstruieren, womit es gehandelt hatte.
Neu geht der Schreibvorgang zuerst, und der Hauptspeicher wird nur nachgezogen,
wenn er gelang. Sonst 500 mit Klartext und unveraenderter Zustand. Fehlt die
Ablage ganz, ist die Aenderung ebenso rein fluechtig — auch das wird abgelehnt,
mit 503.
Betroffen: CopyTrading-Kontoeinstellungen, ResolutionFarming-Kontoeinstellungen,
Master-Trader anlegen/aendern, Master-Trader loeschen.
Dass alle vier denselben Helfer (PersistenceGuard) benutzen, ist der eigentliche
Punkt: Aus je eigenem try/catch je Endpunkt ist die Ungleichheit ueberhaupt erst
entstanden.
Bewusst anders behandelt bleibt das Archivieren eines Supervisor-Berichts: Die
Antwort des Agenten steht dort bereits und hat Geld gekostet: sie wegen eines
fehlgeschlagenen Archiveintrags zu verwerfen, waere schlimmer als der fehlende
Eintrag, und an diesem Schreibvorgang haengt kein Verhalten des Systems.
Stillschweigend verschwindet er aber nicht mehr, sondern wird protokolliert.
In der Oberflaeche zeigten die Schreibwege bisher nur "HTTP 500" an und
verschenkten damit genau die Begruendung, die der Server jetzt mitliefert.
errorText() liest sie aus der Antwort.
Entscheidung von Richard: ablehnen statt fluechtig uebernehmen. Damit sind
waehrend eines Datenbankausfalls keine Aenderungen an den Einstellungen mehr
moeglich; das Handeln selbst laeuft weiter.
Geprueft auf der Linux-Test-VM mit angehaltener MariaDB: alle drei Schreibwege
antworten mit 500 und nennen den Grund, das Limit bleibt bei 11,25 statt auf
99,99 zu springen, der Phantom-Trader entsteht weder im Hauptspeicher noch in
der Datenbank, jeder Fehlschlag steht im Protokoll. Nach dem Wiederanlauf
speichert es wieder. 556 von 556 Tests bestanden (5 neue).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Behebt #532 und #533.
#532 — Kopfleiste bricht um statt zu scrollen
Mit elf Ansichten brauchte die Leiste 1623 px. Darunter scrollte sie seitlich
weg, ohne dass ein Hinweis darauf sichtbar war: Wer einen rechten Tab anklickte,
verlor "Dashboard" aus dem Blick und fand den Weg zurueck nicht mehr. Statt
overflow-x nun flex-wrap auf Leiste und Kopfzeile. Geprueft von 1920 bis 768 px
— nichts wird mehr abgeschnitten, der Koerper scrollt nie waagerecht.
#533 — Kurse holen, statt EUR still auf null zu setzen
Die Tabelle acc_fx_rates gab es seit A-2, befuellt hat sie nie jemand. Deshalb
war EUR nicht verfuegbar, und der PDF-Export machte daraus wortlos einen Beleg
mit lauter Nullbetraegen.
Neu holt EcbFxRateSource die amtlichen Referenzkurse ueber die EZB-Daten-API
(Reihe EXR/D.USD.EUR.SP00.A, CSV). FxRateSyncService stoesst das beim ersten
Ingest-Lauf des Tages an: erstmalig 400 Tage rueckwirkend, danach nur noch der
Rand. Die Kurse werden gespeichert, nicht bei Bedarf geholt — eine Abrechnung
fuer einen vergangenen Monat muss den Kurs von damals nehmen und beim naechsten
Aufruf dasselbe Ergebnis liefern.
Zwei Fallen, die dabei zu umgehen waren:
- Die Reihe liefert USD je EUR (1 EUR = 1,1652 USD), FxRate.UsdToEur meint die
Gegenrichtung. Wer das verwechselt, bekommt eine Abrechnung, die um rund 35 %
danebenliegt und trotzdem plausibel aussieht. Gespeichert wird der Kehrwert.
- Die CSV-Antwort enthaelt in TITLE_COMPL ein Komma innerhalb von
Anfuehrungszeichen; naives Splitten verschoebe die Spalten.
Die Warnung erscheint, sobald der verwendete Kurs mehr als drei Tage vor dem
Stichtag liegt. Drei Tage, weil genau so gross die planmaessige Wochenendluecke
ist: Der Freitagskurs gilt bis Montag und ist dabei kein Mangel.
Der Beleg bleibt trotzdem erzeugbar — die Warnung steht sichtbar darauf und im
WebUI ueber der Abrechnung. Nur ohne jeden Kurs lehnt der Export jetzt mit 400
ab, statt Nullbetraege auszugeben. PDF und CSV laufen deshalb nicht mehr ueber
window.open: Eine nackte JSON-Fehlermeldung in einem neuen Reiter ist keine
Antwort. downloadExport() holt die Datei, meldet Fehler im Klartext und stoesst
sonst den Download an.
Nebenbei im Testlauf gefunden und behoben: Der manuelle Anstoss aus dem WebUI
und die stuendliche Schleife trafen sich in derselben Sekunde — im Log standen
"280 Kurse uebernommen" und "53 Kurse uebernommen" untereinander, beide hatten
die EZB gefragt. SyncDailyAsync serialisiert jetzt und prueft den Tag ein
zweites Mal innerhalb der Sperre.
Geprueft auf der Linux-Test-VM gegen die echte EZB-API: 280 Kurse vom
06.08.2025 bis 09.09.2026 uebernommen, Umrechnung stimmt (1/1,1652 = 0,858222),
eine Maerz-Abrechnung nimmt den Kurs vom 31.03. und nicht den heutigen, das
Warnbanner erscheint ab dem vierten Tag, der Export verweigert ohne Kurs und
liefert sonst ein gueltiges PDF. 551 von 551 Tests bestanden (18 neue).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das WebGUI konnte bisher Dashboard, Konten, Jobs, Logs und Einstellungen.
Die vier Strategie- und Analysemodule waren nur in der Avalonia-Oberflaeche
erreichbar - wer den Server aus der Ferne bediente, sah sie nicht.
Fuenf neue Endpunktgruppen holen sie herein:
/api/analytics KPIs, Equity-Kurve, PnL je Modul und Tag,
durchsuchbare Trade-Historie mit Seitenlauf
/api/modules/copytrading Master-Trader (CRUD), offene und
geschlossene Trades, Risikolimits je Konto
/api/modules/resolutionfarming Scanner-Kandidaten, Positionen, Historie,
Farming-Parameter je Konto
/api/modules/supervisor Profile, Agentenfragen, Berichtsarchiv,
Signal-Dossiers, Counterfactuals
/api/modules/accounting BWA-Periodenabrechnung, Ledger-Journal,
PDF- und CSV-Export, Ingest-Laeufe
Dazu im Bestand:
- Dashboard bekommt /overview-widgets: Modul-KPIs (heute/7T/30T), auffaellige
Trades der letzten 24 Stunden, Warnungen und Fehler des Tages, den letzten
Supervisor-Bericht.
- Die Kontenliste zeigt 3-Tage-PnL, Winrate, Gesamtergebnis und die
beteiligten Module statt nur Salden.
- Jobs lassen sich ueber /toggle und /state ein- und ausschalten, nicht nur
ausloesen.
Die Oberflaeche bleibt ohne Fremdbibliotheken: alle Diagramme sind Inline-SVG,
kein CDN, kein npm. Alle neuen Pfade liegen unter /api/ und damit hinter der
bestehenden Token-Pruefung.
Zwei Fehler nebenbei behoben:
- AccountingReportService.ResolveCurrency lief in eine NullReferenceException,
wenn der FX-Dienst nicht gesetzt war. Betrifft jeden EUR-Aufruf ohne
geladene EZB-Kurse.
- Ein Master-Trader, dessen Name ein Apostroph enthaelt, hatte einen toten
Loeschen-Knopf: escapeHtml laesst ' unberuehrt, und im onclick-Attribut
beendete das Zeichen das JavaScript-Literal vorzeitig. Laengerer Text mit
Anfuehrungszeichen konnte darueber eigenen Code einschleusen. Neu setzt
jsArg() den Wert als JSON-Literal ein; geprueft mit
O'Brien "quote" <b>bold</b> \ backslash.
McpLightServerTests weicht jetzt aus, wenn HttpListener mangels URL-ACL nicht
binden darf, statt den Lauf scheitern zu lassen.
Geprueft auf der Linux-Test-VM (Ubuntu 26.04, .NET 10): Build ohne Fehler,
533 von 533 Tests bestanden, alle 11 Ansichten und 15 Unteransichten geladen,
alle Endpunkte mit 200 beantwortet, Schreibwege bis in die Datenbank
nachverfolgt, PDF- und CSV-Export gueltig, SSE-Logstrom live.
Offene Befunde aus der Abnahme liegen als #532 (Kopfleiste laeuft unter
1623 px ueber), #533 (EUR-PDF ohne EZB-Kurse liefert Nullbetraege) und #534
(Einstellungen melden Erfolg trotz gescheitertem Speichern) im Bugtracker.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Tests zur Zeitzonen-Umstellung aus dem letzten Commit: der Zeitstempel
eines Logeintrags traegt den Versatz der ANZEIGE-Zone (nicht den des Systems),
und die abgelegte JSONL-Zeile traegt ihn mit. Sie liegen bewusst in
AppTimeZoneTests - dort steht der uebrige Zeitzonen-Zustand, und innerhalb einer
Klasse laufen Tests nacheinander.
Das genuegte aber nicht. Von sechs Laeufen fiel einer um und liess sich danach
nicht wieder einfangen. Die Ursache ist kein einzelner Test, sondern die Bauart:
Mehrere Bausteine halten prozessweiten Zustand, den Tests bewusst umstellen -
AppTimeZone, SecretProtection, die Diagnosezaehler von AccountIdSetJson. xUnit
fuehrt Testklassen standardmaessig nebenlaeufig aus, also sieht eine Klasse
mitten im eigenen Lauf, was eine andere gerade umgestellt hat.
Neu ist das nicht, aber die Zeitzonen-Umstellung hat es verschaerft: Seit die
Logzeitstempel der Anzeige-Zeitzone folgen, haengt jedes LogMessageEventArgs an
AppTimeZone - waehrend AppTimeZoneTests dieselbe Einstellung nach Auckland und
zurueck dreht.
Deshalb laufen die Testklassen jetzt nacheinander, und die Zaehlerpruefungen
arbeiten mit der Differenz statt mit absoluten Werten. Kosten: 4-5 s statt 3 s
Laufzeit. Ein Testlauf, dem man glauben kann, ist das allemal wert - danach
sechs Laeufe in Folge gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Damit mehrere Agenten gleichzeitig Oberflaechen testen koennen, bekommt jedes
Projekt eine feste Displaynummer (Vorgabe Richard):
:99 PolyTrader, :98 Predictalytics, :97 ClawdDotNet, :96 IBKRTrader,
:95 und absteigend fuer kuenftige Projekte.
Zwei Prozesse auf demselben Display sehen die Fenster des jeweils anderen -
Klicks landen im falschen Programm, Aufnahmen zeigen fremde Anwendungen.
Beim Durcharbeiten fielen zwei weitere Kollisionspunkte auf, die zum
Parallelbetrieb dazugehoeren und mit aufgenommen wurden:
- Screenshot- und Logdateien in /tmp brauchen ein Projektpraefix. /tmp/v_1.png
ueberschreiben sich parallel laufende Projekte gegenseitig.
- "xvfb-run -a" darf NICHT mehr verwendet werden: Das -a sucht sich selbst ein
freies Display und beginnt dabei bei :99 - ausgerechnet PolyTraders Nummer.
Stattdessen "xvfb-run -n <nummer>".
Die Warnung ist nicht theoretisch: Auf der VM lag ein verwaister X98-Socket,
entstanden durch genau so einen "xvfb-run -a"-Aufruf beim smoke-ui-Test, als
:99 belegt war. Er blockierte Predictalytics' reservierte Nummer, ohne dass ein
Prozess lief. Entfernt, und die Anleitung erklaert jetzt, wie man verwaiste
Sockets erkennt (ls /tmp/.X11-unix/ gegen pgrep -a Xvfb lesen) und aufraeumt.
Weiter ergaenzt:
- pkill nie ohne Displaynummer - "pkill Xvfb" erschlaegt die Sitzungen aller
anderen Projekte.
- DISPLAY in jedem Skript setzen, sonst landet man auf :0, dem echten Desktop.
- Die Skriptvorlage hat jetzt einen Kopf mit PROJEKT/DISP/FENSTER; das ist
alles, was sich je Projekt unterscheidet. Praktisch gegengetestet.
- Aufraeumabschnitt und Belegungsliste um Displays und Verzeichnisse erweitert.
Nebenbei aufgefallen: Das PolyTrader-Publish lag unter ~/pt-test statt
~/polytrader-test und verletzte damit die eigene Verzeichniskonvention.
Umbenannt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sicherungscommit vor dem Aufraeumen des Repos, damit nachvollziehbar bleibt,
welcher Stand vor der Bereinigung galt. Build gruen, 476 Tests gruen.
Zwei Straenge, die sich ueber .csproj, Program.cs und appsettings.json
ueberschneiden und darum gemeinsam abgelegt werden:
Deploymentcenter-Integration (P3c, Plan D-0 bis D-5 code-seitig fertig):
- Deploymentcenter.Client 2.5.0 als lokales Paket, Source-Mapping erweitert
- DeploymentcenterOptions, DeploymentcenterErrorReporter, LicenseGate/LicenseCli
- WatchdogHeartbeatService auf die Deploymentcenter-API umgestellt
(version, os, checks, metrics, status stopped)
- Security: MasterKeyResolver, SecretRedactor, FilePermissions
- Directory.Build.props mit zentraler Version 0.1.0 (Packager-Versionsdisziplin)
- deploy/: Packager-Vorlage und systemd-Unit; echte Zugangsdaten bleiben
ueber .gitignore aussen vor
- setup.json fuer die Erstinstallation
- UMSETZUNGSPLAN-Deploymentcenter-Integration.md; ANALYSE-Linux-Portierung.md
verweist auf den neuen Plan
Einfenster-Shell (UI-Redesign):
- ShellWindow + ShellNavModel als Seitenleisten-Shell
- WindowMenuBar und WindowMenuModel entfallen
- Modul- und Kernfenster auf die Shell-Einbettung angepasst
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alle vier Core-Fenster sind damit portiert.
TERMINAL:
- Live-Ausgabe als virtualisiertes ItemsControl ueber eine begrenzte Zeilenliste statt
RichTextBox. Damit entfaellt das Auto-Clear der WinForms-Fassung, das bei Erreichen der
Zeichengrenze den GESAMTEN Verlauf verwarf - jetzt werden nur die aeltesten Zeilen
verdraengt (Ringpuffer, 5000 Zeilen), der juengste Verlauf bleibt immer sichtbar.
Die Zeilenzahl steht in der Statuszeile.
- Log-Viewer unveraendert im Funktionsumfang: JSONL-Tagesdateien, Filter nach Datum, Level,
CID und Volltext, Doppelklick uebernimmt die CID (Signal-Kette verfolgen).
ZEITZONE (Befund aus der Linux-Analyse, hier faellig geworden):
Die Terminal-Ansicht rechnete hart gegen die WINDOWS-ID 'W. Europe Standard Time'. Auf Linux
traegt die nur ueber die ICU-Zuordnung und faellt ganz aus, wenn ICU fehlt oder
InvariantGlobalization gesetzt ist - das Fenster haette beim Oeffnen geworfen.
Neu: PolyTraderSharp.Services.AppTimeZone + ServerSettings.ApplicationTimeZoneId
(Default 'Europe/Berlin', IANA-Schreibweise). Aufloesung versucht die ID direkt, dann die
jeweils andere Schreibweise (IANA<->Windows), zuletzt die Systemzeitzone - ein unbekannter
Wert ist damit nie fatal, sondern erzeugt nur eine Warnung. Beide Programm-Einstiege setzen
sie einmalig beim Start; laut Vorgabe wird sie bei der Installation festgelegt und nicht im
laufenden Betrieb gewechselt (Aenderung verschiebt Logdatei-Tagesgrenzen).
8 neue Tests (AppTimeZoneTests) halten fest: IANA- UND Windows-ID liefern denselben
UTC-Versatz (Winter +1, Sommer +2), unbekannte IDs fallen mit Warnung auf die Systemzeitzone
zurueck, leere Angabe = Systemzeitzone, UTC wird korrekt umgerechnet.
Verifiziert: Solution baut, 450 Tests gruen, --smoke-ui gruen (6 Fenster + Editor-Pruefung,
Zeitzone loest als Europe/Berlin auf), App laeuft real, Linux-Publish laeuft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alle sieben Projekte auf net10.0 (App: net10.0-windows). Runtime 10.0.10 ist
installiert, 442 Tests gruen, Linux-Publish aller Nicht-App-Projekte laeuft.
EF Core 8 / Pomelo 8 bleiben bewusst stehen: net10.0 konsumiert net8.0-Bibliotheken
problemlos, und ein Provider-Wechsel hat wegen der Migrations-Implikationen eine
eigene Risikoflaeche - das gehoert in einen separaten, verifizierten Durchgang.
Sicherheitsfund nebenbei: Ab .NET 9 prueft NuGet standardmaessig auch transitive
Pakete. Damit wurde sichtbar, dass Nethereum 6.1.0 Newtonsoft.Json [11.0.2, 14.0.0)
zulaesst und ohne Pinnung auf 11.0.2 aufloest - bekannte Luecke hoher Schwere
(GHSA-5crp-9r3c-p9vr). Die App pinnte laengst 13.0.4, Core und Module nicht.
Jetzt im Core gepinnt, damit jeder Consumer sie bekommt (auch der kuenftige
Linux-Daemon). Danach 0 NU1903-Warnungen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Variante B (Entscheidung Richard): Modul-UI entfernt statt in Zwischenprojekte
ausgelagert. Avalonia ist plattformuebergreifend, die neuen Ansichten kommen spaeter
direkt in die Modul-Projekte zurueck - kein Zwischenschritt, keine Wegwerfarbeit.
- 23 WinForms-Dateien aus den 4 Modulen entfernt (Spezifikation steht in
docs/UI-SPEZIFIKATION-WinForms.md, Originalcode im Tag winforms-final).
- RegisterUi ist jetzt je Modul ein dokumentierter No-Op: View-ID, Titel, Gruppe,
Order und der Tab-Aufbau stehen als XML-Doku drin, damit der Avalonia-Nachbau
die stabilen IDs und die Struktur uebernimmt.
- Alle 4 Modulprojekte + Testprojekt: net8.0 statt net8.0-windows, UseWindowsForms raus.
- P4 vorgezogen (war durch den Testprojekt-Wechsel faellig): PDFsharp-MigraDoc-GDI
-> PDFsharp-MigraDoc (Core-Build). Der Core-Build findet keine Systemschriften,
daher neu Logic/PdfFontResolver.cs: durchsucht die Schriftverzeichnisse des OS nach
Segoe UI/DejaVu/Liberation/Noto/Arial/FreeSans. Keine Schriftdateien im Repo noetig;
fehlt auf Linux alles, kommt eine klare Meldung mit apt-Hinweis statt eines
kryptischen Renderer-Fehlers.
Verifiziert: Core, alle 4 Module und das Testprojekt publishen fuer linux-x64, und
zwar ohne ein einziges Windows-spezifisches Paket in den deps.json. 442 Tests gruen
(inkl. PDF-Rendering) auf net8.0. Windows-App laeuft weiter mit den Core-Fenstern.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- TradeRowColoring liefert jetzt eine TradeRowTint-Kategorie (Loss/SmallWin/BigWin)
statt einer System.Drawing.Color. Die Schwellenlogik bleibt getestet, die konkrete
Farbe legt die UI fest (neu: Ui/TradeRowPalette.cs im CopyTrading-Modul).
Bessere Schichtung und Voraussetzung dafuer, dass das Modul spaeter net8.0 wird.
- TradeRowColoringTests prueft die Kategorie statt der Farbe (gleiche Abdeckung).
- Models/DashboardRow.cs: verwaistes using System.Drawing entfernt.
System.Drawing liegt damit ausschliesslich noch in UI-Ordnern - die Fachlogik in
Core und Modulen ist frei davon.
442 Tests gruen, --smoke-ui gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Core traegt keine WinForms-/System.Drawing-Abhaengigkeit mehr und baut als
plattformneutrales net8.0 (verifiziert: publish -r linux-x64 erfolgreich).
- ModuleView.CreateForm (Func<Form>) -> CreateView (Func<object>): die Shell kennt
ihr Toolkit und castet, der Core nicht. Avalonia kann denselben Contract nutzen.
- ModuleView.Icon (System.Drawing.Image, seit .NET 7 Windows-only) -> IconKey (string).
Aufloesung Schluessel->Bildressource neu in Ui/ViewIcons.cs, ersetzt Program.AssignMenuIcons.
- WindowMenu.cs (reine WinForms-Logik) aus dem Core nach Ui/ verschoben.
- WindowMenuTests entfernt: testet die eingefrorene WinForms-Menuelogik, die das
Testprojekt nach dem Core-Schnitt nicht mehr erreicht. Im Tag winforms-final erhalten;
das Avalonia-Gegenstueck bekommt eigene Tests (P6/P9).
Module und App bleiben vorerst net8.0-windows - ihre UI zieht erst mit der
Avalonia-Portierung um. Windows-App unveraendert lauffaehig (--smoke-ui gruen).
442 Tests gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 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>
Slices W-A/W-B/W-C und L-A aus UMSETZUNGSPLAN-Watchdog-LicenseLabrador-Integration.
Watchdog (Dead-Man's-Switch, externer Server):
- WatchdogHeartbeatService als BackgroundService + DI-Singleton; jeder Sendeversuch
gekapselt, ein Ausfall des Watchdogs beeintraechtigt PolyTrader nie.
- Eigene Implementierung statt Test-Client des Fremdprojekts: TLS-Pruefung bleibt
aktiv, http:// nur fuer localhost (Agent-Token nicht im Klartext ins Netz).
- Status aus dem App-Log abgeleitet (Error mit 5-Minuten-Sticky-Fenster, entprellt),
Lifecycle-Events started/stopping.
- Konfiguration in ServerSettings; Agent-Token [Browsable(false)] mit maskierter
Eingabe + Statusanzeige, bei gesetztem Master-Key verschluesselt (enc:v1:).
Lizenz (LicenseLabrador, Ed25519):
- LicenseGate.RunStartupGate prueft beim Start; bei ungueltiger Lizenz wird die
Modulliste leer gebaut, sodass nur die Core-Shell (Terminal/Einstellungen)
startet. Bewusst kein Environment.Exit - ein Trading-Bot darf nicht mitten im
Lauf hart sterben. TamperSuspected gilt als nicht nutzbar.
- LicenseDialog (partial + .Designer.cs) fuer Start- und Verwalten-Modus, mit
Hardware-ID zum Kopieren; Smoke-UI konstruiert beide Modi headless.
- Master-Key wird jetzt VOR dem Host-Build geladen, da auch der Lizenzschluessel
entschluesselt werden muss; derselbe TerminalLogger wird als Singleton
weitergereicht, damit die Startmeldungen im Terminal-Fenster erscheinen.
Der Lizenz-SDK-Client wird per Cross-Repo-Projektreferenz auf
..\..\LicenseLabrador eingebunden, damit SDK-Fixes ohne Kopie einfliessen.
484 Zeilen Tests fuer den Heartbeat; Suite gruen (438 Tests).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Fenster-Menue: WindowMenu fuellt die oberste MenuStrip mit Top-Level-Eintraegen
nebeneinander (mit Icon) statt Untermenue "Fenster"; ModuleView.Icon zentral in der
App zugewiesen (AssignMenuIcons). Kein miFenster mehr im Launcher-Designer.
- Sicheres Beenden: ShutdownConfirmDialog (10s-Timer sperrt "Jetzt beenden", Abbrechen
jederzeit) via IModuleUiHost.RequestShutdown(). Nur der Launcher (Hauptprozess) bietet
"Beenden"; andere Fenster nur "Fenster schliessen" (kein App-Shutdown, Module laufen
weiter). Launcher-Schliessen-X routet ueber dieselbe Abfrage.
- Modul-Aktivierung (restart-basiert): ServerSettings.DisabledModules, in Program.Main
vor der DI-Registrierung gefiltert -> deaktivierte Module werden nicht geladen.
Dashboard-Tab "Module" zeigt Status + schaltet um (Hinweis: greift nach Neustart).
Optionaler IPolyTraderModule.GetActivationBlocker fuer "nicht aktivierbar"-Info.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Geschlossene Trades: Zeilenfaerbung nach PnL-% (TradeRowColoring, pur+getestet: <0 rot,
0-10% hellgruen, >10% gruen) via DataBindingComplete. Filter-Panel (Designer): Markt (Text),
Master-Trader (Combo, dynamisch), Ergebnis (Alle/Gewinner/Verlierer), Von/Bis (optionale
DateTimePicker mit Checkbox), Zuruecksetzen. Summary zeigt gefiltert/gesamt.
- Neuer Tab 'Offene Trades' (OpenTradesView, designerfaehig): alle offenen Positionen aus dem
Laufzeit-State mit Entry/Aktuell/Wert/Buchgewinn/-% und Status (offen/Exit laeuft), Zeilenfaerbung
nach Buchgewinn-%, Summenzeile. In CopyTradingMainForm zwischen Master-Trader und Geschlossene eingehaengt.
- Master-Trader-Grid: AutoSizeColumnsMode=Fill + FillWeights -> Spaltenbreiten teilen sich immer die
Breite (Fix der 'verbuggten' Breiten); RowHeader war bereits aus.
Tests: +8 (TradeRowColoring-Schwellen). Build 0 Fehler, 396 Tests gruen, --smoke-ui gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ShellUiHost injiziert das gemeinsame 'Fenster'-Menue beim Oeffnen zentral in jedes Fenster
(nur wenn keins eigenes vorhanden ist, z.B. Launcher). Damit erscheint das Menue auf allen
Core- und Modul-Fenstern ohne Achtfach-Designer-Duplikat, und jedes kuenftige Fenster bekommt
es automatisch. Inhaltliche Controls bleiben designerbasiert; das Nav-Menue ist Shell-Chrome.
Tests: +3 (WindowMenu.Populate: Inhalt Launcher/Views/Beenden, aktuelles Fenster fett+angehakt,
offenes angehakt, Klick navigiert). 389 Tests gruen, --smoke-ui gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RF-UI designerfaehig (Richards Vorgabe, letzter code-only-Altbestand):
- ResolutionFarmingMainForm auf partial + .Designer.cs umgestellt (4 Tabs Kandidaten/
Positionen/Historie/Settings, alle Controls im Designer; Verhalten unveraendert).
S-3 Counterfactual ('Was waere aus abgelehnten BUYs geworden?'):
- CounterfactualJob (alle 6h, API-gedrosselt): nimmt Rejected-BUY-Entscheidungen mit
abgelaufenem MarketEndDate aus dem Journal, prueft die Marktaufloesung
(ICounterfactualResolutionSource; live = Adapter um CheckMarketResolutionAsync) und
speichert IsWinner + hypothetischen PnL/Share (CounterfactualMath, pur) nach
sup_counterfactuals (unique je DecisionId -> idempotent). Migration generiert+angewendet.
- Agent-Tool query_counterfactuals + UI-Tab 'Counterfactual' (via Designer).
S-3 Threema-Tagesbericht:
- DailyReportService: taeglich zur konfigurierten Stunde (OPT-IN via
POLYTRADER_SUPERVISOR_DAILY=0-23) laesst der Agent einen 24h-Kurzbericht erstellen
(KPIs je Modul, Fehler, Reject-Haeufungen), sendet via Threema und legt ihn als
sup_report ab. Ohne OpenRouter-Key: stiller Skip mit Log.
Tests: +7 (CounterfactualMath, Job aufgeloest/unaufgeloest/idempotent, Tagesbericht
mit/ohne Key). Build 0 Fehler, 367 Tests gruen, --smoke-ui alle 5 Views gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Behebt den kritischsten Befund (Klartext-Private-Keys in remote-gehosteter MySQL):
- SecretProtection (Core/Security): AES-256-GCM, authenticated. Master-Key AUSSERHALB der DB
(env POLYTRADER_MASTER_KEY, sonst gitignorierte master.key). Format enc:v1:base64(nonce|tag|ct).
Alt-Klartext (ohne Praefix) wird gelesen und beim Speichern verschluesselt (selbstheilend).
Ohne Master-Key: Passthrough + deutliche Startwarnung (kein stiller Sicherheitsverlust).
- EncryptedStringConverter (EF ValueConverter) auf core_accounts.PrivateKey/ApiSecret/ApiPassphrase;
Spalten 256->512 verbreitert (Migration EncryptAccountSecretsWidenColumns, offline generiert).
- Program.cs: Master-Key vor der Hydration laden; nach Start einmalige/idempotente Re-Encryption
vorhandener Klartext-Credentials. Auch in --smoke-ui verdrahtet.
- CoreDbContextFactory nutzt jetzt fixe Server-Version (offline-Migrationsgenerierung, kein DB-Zugriff).
13 neue Krypto-Tests (Round-Trip, Nonce-Frische, Manipulations-/Falscher-Key-Erkennung, Passthrough,
Key-Formate). Build 0 Fehler, 324 Tests gruen, --smoke-ui ok (Warnung ohne Key wie erwartet).
AKTIVIERUNG (im Zielland): POLYTRADER_MASTER_KEY setzen (zufaelliger 32-Byte-Base64-Key, SEPARAT sichern!)
+ Migration anwenden (dotnet ef database update --context CoreDbContext). Danach Alchemy-/Mullvad-Secrets
aus F3 rotieren. WICHTIG: Master-Key-Verlust = Kein Zugriff auf die Keys mehr.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase RF-1 (read-only): Scanner holt bald aufloesende Favoriten von einer
IFarmingMarketSource, bewertet sie und schreibt JEDEN Kandidaten (akzeptiert wie
abgelehnt inkl. Grund) nach rf_candidates. Platziert keine Orders.
- MarketScannerService.Evaluate (pur/statisch, voll getestet): Filterkette mit
Reject-Grund = erster Fehlschlag (Preisband -> Kategorie -> Blacklist ->
Aufloesungsfenster -> Netto-Edge nach Fees). Cluster-Key + Score immer berechnet.
- ScanAccountAsync: Beschaffung -> Bewertung -> Persistenz. BackgroundService-Loop
(12min) ueber Accounts mit Settings; fehlertolerant.
- IFarmingMarketSource + ScannedMarket-DTO trennen die (live-/API-gebundene)
Beschaffung von der Bewertung -> Pipeline ohne echte Gamma/CLOB-API testbar.
- NullFarmingMarketSource als Default: Modul laeuft ohne Live-Anbindung (die im
Zielland registriert wird) und produziert dann korrekt keine Kandidaten.
Hinweis: Zur Laufzeit fragt der Scanner rf_settings ab; bis die Migration angewendet
ist, faengt der try/catch den fehlenden-Tabelle-Fehler ab (nur Log). Migration bewusst
separat anzuwenden.
7 neue Tests (Evaluate-Faelle + Orchestrierung). Build 0 Fehler, 296 Tests gruen, --smoke-ui ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Entities RfCandidate/RfPosition/RfClosedTrade (+ RfSettings aus Slice 1).
- ResolutionFarmingDbContext: Tabellen rf_settings/rf_candidates/rf_positions/
rf_closed_trades. Autoincrement-PKs (Identity) fuer Candidate/ClosedTrade von
Anfang an (Lehre aus dem CopyTrading-TradeId-Problem), zusammengesetzter PK
(AccountId,TokenId) fuer Positions, Indizes + Decimal-Precision.
- 4 Repos (Settings/Candidate/Position/ClosedTrade) mit serverseitigen Aggregaten
(RealizedPnlSince fuer Kill-Switch, CountOpenedSince fuer Tages-Drossel).
- Modul registriert DbContextFactory + Repos.
- Design-Time-Factory nutzt die fest gepinnte Server-Version -> Migration wurde
OHNE DB-Verbindung generiert (kein Zugriff auf die produktive DB). Anwenden per
'dotnet ef database update' bewusst im Zielland/lokal durch den Nutzer.
6 neue EF-InMemory-Repo-Tests. Build 0 Fehler, 289 Tests gruen, --smoke-ui ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vorbereitung fuer das ResolutionFarming-Modul: FeeModel von
PolyTrader.Modules.CopyTrading.Logic nach PolyTrader.Core.Trading verschoben, damit
mehrere Strategiemodule dieselbe Fee-Logik nutzen koennen (Module duerfen nicht
voneinander abhaengen -> geteiltes gehoert in den Core). CopyTrading-Referenzen +
FeeModelTests auf den Core-Namespace umgestellt. Rein mechanisch, verhaltensneutral.
Build 0 Fehler, 244 Tests gruen, --smoke-ui ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verifiziert den kritischen K1a-Fix mit Integrationstests statt nur per Review
(nach dem K3-Fehlplatzierungs-Fund):
- TraderMonitorService._clob -> IClobClient (verhaltensneutral); CleanupStaleOpenOrdersAsync
internal. FakeCopyTradeLogRepository ergaenzt.
- 2 Tests: Stale Order mit aktiver Leiter wird NICHT gecancelt; ohne Leiter WIRD sie
storniert (Kontrast, beweist Nicht-Vacuositaet).
Build 0 Fehler, 244 Tests gruen, --smoke-ui ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Testabdeckung fuer den geldkritischen Engine-Pfad (bisher 0 Tests), da die
Fable-Guards zwischen Services entstehen und Unit-Tests sie nicht fangen:
- Engine._clob -> IClobClient (verhaltensneutral, DI registriert IClobClient bereits);
ProcessAccountOrderAsync internal. FakeMarketRepository/FakeAccountRepository ergaenzt.
- 7 Integrationstests (CopyTradingEngineTests) ueber gemockten CLOB: H3 BUY-Skip bei
ExitPending, Doppel-SELL-Guard, K3 System-Close (TraderId==0) schliesst Fremd-Position,
Fremd-Trader-SELL bleibt abgewiesen, H2 Cleanup schont Leiter (+ Kontrast ohne Leiter).
DABEI ECHTEN BUG GEFANGEN: Der K3-Fix aus Slice 3 sass an der falschen Stelle
(IsAuthorizedSell nach dem Position-Remove, Zeile ~643) – der eigentliche Ownership-Check
ist der fruehe inPortfolio-Lookup (Zeile 437, p.SourceTraderId == signal.TraderId), der
System-Signale schon vorher mit early return abwies. Fix jetzt am richtigen Ort; der
downstream-Check bleibt als Defense-in-depth. Ohne den Engine-Test waere das unentdeckt geblieben.
Build 0 Fehler, 242 Tests gruen, --smoke-ui ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
H4 – Rundungs-Dust-Reject-Schleife:
- SellLogic.RoundToTick (0.001, AwayFromZero, wie CalculateExactOrderAmounts). Die
Leiter platziert Preise gerundet -> usdc = size × Preis geht exakt auf (kein Dust).
- ProcessLadderAsync bricht ab, wenn pos.Size < MinShares (Dust-Rest gehalten,
ExitPending=false), statt endlos Sub-Minimum-Orders zu schicken.
M1 – GlobalPnl-Doppelzaehlung: in beiden Live-Close-Bloecken jetzt NUR innerhalb des
_processedClosures-Dedup-Guards gebucht (API-Lag zaehlte sonst doppelt).
M2 – TokenId in beiden Live-Close-Records gesetzt (sonst greift die Dedup nach Neustart nicht).
M3 (Minimum) – TradeId-Init: serverseitiges GetMaxTradeId() statt Full-Table-Find(_=>true).Max();
Fehlschlag wird laut geloggt statt still geschluckt. (Autoincrement-Migration bewusst als
Follow-up aufgeschoben – Schema-Aenderung an der Trade-Persistenz erst im Zielland live verifizieren.)
M4 – MongoExportParser ProfitTarget-Default 50 -> 9999 (Take-Profit bleibt dormant).
M6 – Fee-Satz (real oder Kategorie-Fallback, FeeModel.ResolveBps) in alle signierten Orders:
Engine-BUY, SELL-Leiter (Start/Step/Floor), PreRedeem (WSS + REST). API-gated, im Zielland verifizieren.
Doku – stale [Description]: ProfitTarget/SellFloorPct als IMPLEMENTIERT markiert.
Tests: +10 (RoundToTick, Dust-Abbruch, ResolveBps). Build 0 Fehler, 233 gruen, --smoke-ui ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>