Fruehjahrsputz 2/2: Dokumentation auf den tatsaechlichen Stand gebracht
Die Plandokumente waren durchweg veraltet: 85 von 91 Punkten im
UMSETZUNGSPLAN standen auf offen, obwohl der Code sie enthielt, und in
FIXPLAN-UI-Ranglisten war keine einzige der 18 Aufgaben abgehakt, obwohl
beide zugehoerigen Commits laengst im Zweig stecken. Alles abschnittsweise
gegen den Code geprueft und die Haekchen gesetzt - mit Belegstellen, damit
die naechste Pruefung nicht wieder bei null anfaengt.
Neu: STATUS.md als Einstiegsseite - wo das Projekt steht, was fertig ist,
was offen ist, und was beim Aufraeumen bewusst stehengeblieben ist. CLAUDE.md
verweist darauf.
Nachgezogen:
* UMSETZUNGSPLAN.md - 66 Punkte abgehakt. Offen bleiben A4 (Strategie-
Klassifikation), B2 (Secrets) sowie C3/D1/D2 (SQL-Arbeiten des Nutzers).
* FIXPLAN-UI-Ranglisten.md - abgeschlossen bis auf den als "Optional"
markierten Pfeilrichtungs-Punkt.
* FIXPLAN-TODO.md - Teil D und F abgehakt; die Test-Baseline "16 gruen"
auf die heutigen 126 korrigiert. Offen: F5 und zwei Tests aus F6.
* FIXPLAN-G-Speicher.md - G1 bis G4 als erledigt vermerkt, Baseline "39/1"
korrigiert, Pfad auf die nicht mehr existierende WinFormsHost/appsettings.json
richtiggestellt.
* docs/PLAN-Linux-Portierung.md - der Watchdog-Warnhinweis war ueberholt
(DcHeartbeatService meldet an /api/watchdog/v1/ping). Abschnitt 11 empfahl
noch, mit Phase 0 zu beginnen; jetzt benennt er Phase 5 und 6 als das, was
wirklich aussteht. Die Randnotiz zu Zugangsdaten in LicenseGuard.cs ist
gegenstandslos, die Datei laeuft ueber den Deploymentcenter.Client.
Zwei Befunde, die keine Aufraeumarbeit sind und deshalb nur dokumentiert
wurden - beide in STATUS.md Abschnitt 3:
1. In src/Predictalytics.Api/appsettings.json steht ein echter
OpenRouter-API-Key im Klartext, versioniert seit 7045002. Nicht
eigenmaechtig entfernt: ohne Rotation beim Anbieter bringt das nichts
(die History behaelt ihn), wuerde aber die KI-Analyse abschalten.
Der Schluessel muss zurueckgezogen und neu ausgestellt werden.
2. Die Portierung ist auf keinem Linux-System je ausgefuehrt worden. Die
Verifikation vom 2026-08-08 lief unter Windows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
# Predictalytics
|
# Predictalytics
|
||||||
|
|
||||||
|
**Stand des Projekts, offene Punkte und Altlasten-Entscheidungen:
|
||||||
|
[`STATUS.md`](STATUS.md)** — dort zuerst nachsehen.
|
||||||
|
|
||||||
Betriebsdokumentation zur Anbindung an das Deployment Center (Lizenz, Watchdog,
|
Betriebsdokumentation zur Anbindung an das Deployment Center (Lizenz, Watchdog,
|
||||||
Updates, Fehler-Stream): [`docs/BETRIEB-Deploymentcenter.md`](docs/BETRIEB-Deploymentcenter.md).
|
Updates, Fehler-Stream): [`docs/BETRIEB-Deploymentcenter.md`](docs/BETRIEB-Deploymentcenter.md).
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -1,5 +1,21 @@
|
|||||||
# FIXPLAN Teil G — Speicher-Budget & Kategorie-Edge (für Gemini 3.5 Flash)
|
# FIXPLAN Teil G — Speicher-Budget & Kategorie-Edge (für Gemini 3.5 Flash)
|
||||||
|
|
||||||
|
> **✅ ABGESCHLOSSEN — G1 bis G4 sind umgesetzt.** Am 2026-08-23 gegen den Code geprüft:
|
||||||
|
> * **G1** Speicher-Governor — `Application/Services/StorageGovernor.cs`, angewendet in
|
||||||
|
> `TradeRetentionWorker.cs:89`; `MaxDatabaseSizeGb`/`MinRetentionDays` werden gelesen
|
||||||
|
> (`:74,75`). Tests in `StorageGovernorTests.cs` und `TradeRetentionWorkerTests.cs`.
|
||||||
|
> * **G2** Retention räumt auch `Aggregated`-Trader — `TradeRetentionWorker.cs`, Test vorhanden.
|
||||||
|
> * **G3** `UsdcSize` auf `Trade`, `OutcomeIndex` auf `MarketOutcome` — beide persistiert,
|
||||||
|
> Test in `TradeRepositoryTests.cs`.
|
||||||
|
> * **G4** Per-Kategorie-Edge — `AvgReturnPct` bis in die Trader-Detailansicht
|
||||||
|
> (`TraderEndpoints.cs:80`, `app.js:913`).
|
||||||
|
>
|
||||||
|
> Der **Anhang** („Einmalige DB-Optimierungen") bleibt offen: das sind SQL-Schritte, die
|
||||||
|
> laut Absprache der Nutzer selbst gegen die Datenbank fährt, kein Code.
|
||||||
|
>
|
||||||
|
> Die unten genannte Test-Baseline (39/1) ist überholt — Stand 2026-08-23 sind es
|
||||||
|
> **126 grün + 1 übersprungen**.
|
||||||
|
|
||||||
> Eigenständiger Plan. Von oben nach unten abarbeiten. Nach **jeder** Aufgabe bauen + testen + committen.
|
> Eigenständiger Plan. Von oben nach unten abarbeiten. Nach **jeder** Aufgabe bauen + testen + committen.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -131,7 +147,7 @@ In `src/Predictalytics.Worker/Services/TradeRetentionWorker.cs`, Methode `RunOpt
|
|||||||
|
|
||||||
### G1.4 — Config-Defaults
|
### G1.4 — Config-Defaults
|
||||||
|
|
||||||
In `src/Predictalytics.WinFormsHost/appsettings.json` unter `RetentionSettings` (Objekt anlegen falls
|
In `src/Predictalytics.Api/appsettings.json` unter `RetentionSettings` (Objekt anlegen falls
|
||||||
fehlt) ergänzen: `"MaxDatabaseSizeGb": 90, "MinRetentionDays": 30`. (Nur Defaults; nichts Bestehendes löschen.)
|
fehlt) ergänzen: `"MaxDatabaseSizeGb": 90, "MinRetentionDays": 30`. (Nur Defaults; nichts Bestehendes löschen.)
|
||||||
|
|
||||||
**G1 fertig, wenn:** neue Tests grün, alte grün, Build grün. Commit: „G1: storage governor (budget-based retention)".
|
**G1 fertig, wenn:** neue Tests grün, alte grün, Build grün. Commit: „G1: storage governor (budget-based retention)".
|
||||||
|
|||||||
+33
-15
@@ -1,9 +1,27 @@
|
|||||||
# Fix- und Datenreparatur-Plan (Stand 2026-07-09, Übergabe an Gemini)
|
# Fix- und Datenreparatur-Plan (Stand 2026-07-09, Übergabe an Gemini)
|
||||||
|
|
||||||
> **Abnahmekriterium für alle Code-Änderungen:** `dotnet test src/Predictalytics.Application.Tests` muss
|
> **Status 2026-08-23: Teil D und Teil F sind umgesetzt**, nachgeprüft gegen den Code.
|
||||||
> **16 grün + 1 übersprungen** liefern (der Skip `CheckpointResetAndReplay_DoesNotDoubleCountBalance` ist eine
|
> Offen sind nur noch drei Detailpunkte, jeweils unten als `- [ ]` stehengeblieben:
|
||||||
> dokumentierte, bewusste Entscheidung). Die Assertions der Invarianten-Tests dürfen **nicht** verändert werden —
|
> der gezielte Nachlade-Schritt in F5 und die zwei Tests aus F6 (Event-Tag-Vererbung,
|
||||||
> sie definieren das Soll-Verhalten. Wenn ein Test rot wird, ist der Code falsch, nicht der Test.
|
> Backfill). Belege:
|
||||||
|
> * **D3** — `IngestMode` (`Domain/Enums/IngestMode.cs`), Einstufung in
|
||||||
|
> `TradeHistoryWorker.cs:174-177` inklusive der geforderten Hysterese
|
||||||
|
> (Rückstufung erst unter halber Schwelle, Richtung `Full` zusätzlich erst nach
|
||||||
|
> 7 Tagen), wöchentliche Biopsie in `TradeHistoryWorker.cs:127`,
|
||||||
|
> Aggregations-Buckets in `TradeAggregation.cs`, Trait `not_copyable_hf` in
|
||||||
|
> `TraderTraitCalculator.cs`. Tests: `IngestModeTests.cs`.
|
||||||
|
> * **F1/F2** — `CanonicalTags` und `BlacklistTags`/`IsNoiseTag` in
|
||||||
|
> `Infrastructure/Helpers/MarketCategoryMapper.cs`.
|
||||||
|
> * **F3/F4** — Tag-Nachladen in `PolymarketProvider.cs:187-196`, Offline-Backfill
|
||||||
|
> aus Event-Tags in `PredictalyticsHost.cs:114-135`.
|
||||||
|
>
|
||||||
|
> **Abnahmekriterium für alle Code-Änderungen:** `dotnet test` muss grün bleiben.
|
||||||
|
> Die damals notierte Zahl (16 grün + 1 übersprungen) ist überholt — Stand
|
||||||
|
> 2026-08-23 sind es **126 grün + 1 übersprungen** (der Skip
|
||||||
|
> `CheckpointResetAndReplay_DoesNotDoubleCountBalance` ist weiterhin eine
|
||||||
|
> dokumentierte, bewusste Entscheidung). Die Assertions der Invarianten-Tests dürfen
|
||||||
|
> **nicht** verändert werden — sie definieren das Soll-Verhalten. Wenn ein Test rot
|
||||||
|
> wird, ist der Code falsch, nicht der Test.
|
||||||
|
|
||||||
## Hintergrund
|
## Hintergrund
|
||||||
|
|
||||||
@@ -95,35 +113,35 @@ Reihenfolge: **D1 → D2/D2b/D2c → D3** (D2c ist klein und gehört in denselbe
|
|||||||
|
|
||||||
### F1. Mapper: kanonischen Tag zuerst, dann Heuristik
|
### F1. Mapper: kanonischen Tag zuerst, dann Heuristik
|
||||||
|
|
||||||
- [ ] In `MarketCategoryMapper.Map`: **zuerst** prüfen, ob **irgendein Tag exakt** einer bekannten
|
- [x] In `MarketCategoryMapper.Map`: **zuerst** prüfen, ob **irgendein Tag exakt** einer bekannten
|
||||||
Kategorie entspricht (Polymarkets Tag-Vokabular enthält fast immer den kanonischen Top-Level-Tag:
|
Kategorie entspricht (Polymarkets Tag-Vokabular enthält fast immer den kanonischen Top-Level-Tag:
|
||||||
`Sports`, `Politics`, `Crypto`, `Business`/`Economy`, `Pop Culture`, `Science`, ...). Mapping-Tabelle
|
`Sports`, `Politics`, `Crypto`, `Business`/`Economy`, `Pop Culture`, `Science`, ...). Mapping-Tabelle
|
||||||
Tag→`MarketCategory` (inkl. Synonyme: `Finance`/`Business`→Economy, `Pop Culture`→PopCulture).
|
Tag→`MarketCategory` (inkl. Synonyme: `Finance`/`Business`→Economy, `Pop Culture`→PopCulture).
|
||||||
Erst wenn **kein** kanonischer Tag matcht, die bestehende Keyword-Heuristik auf Frage+Tags anwenden.
|
Erst wenn **kein** kanonischer Tag matcht, die bestehende Keyword-Heuristik auf Frage+Tags anwenden.
|
||||||
- [ ] **Kategorie über die gesamte Tag-Menge** bestimmen, nie über `tags[0]`.
|
- [x] **Kategorie über die gesamte Tag-Menge** bestimmen, nie über `tags[0]`.
|
||||||
|
|
||||||
### F2. Subcategory: Noise filtern, sinnvoll wählen, leer normalisieren
|
### F2. Subcategory: Noise filtern, sinnvoll wählen, leer normalisieren
|
||||||
|
|
||||||
- [ ] Organisations-/Müll-Tags herausfiltern (Blacklist: `Hide From New`, `Tournament Futures`,
|
- [x] Organisations-/Müll-Tags herausfiltern (Blacklist: `Hide From New`, `Tournament Futures`,
|
||||||
`Main Election`, `Recurring`, Jahres-Tags wie `2025 Predictions`, `2026 FIFA World Cup`→ok als Sub?, …).
|
`Main Election`, `Recurring`, Jahres-Tags wie `2025 Predictions`, `2026 FIFA World Cup`→ok als Sub?, …).
|
||||||
- [ ] Subcategory = spezifischster **verbleibender** Tag, der **nicht** die Kategorie selbst ist
|
- [x] Subcategory = spezifischster **verbleibender** Tag, der **nicht** die Kategorie selbst ist
|
||||||
(bei World-Cup-Tags → `Soccer`, nicht `Sports`). Kein passender → leerer String.
|
(bei World-Cup-Tags → `Soccer`, nicht `Sports`). Kein passender → leerer String.
|
||||||
- [ ] **NULL/`""` einheitlich als `""`** speichern (behebt die doppelten „Sports/-"-Zeilen: heute
|
- [x] **NULL/`""` einheitlich als `""`** speichern (behebt die doppelten „Sports/-"-Zeilen: heute
|
||||||
entstehen zwei Gruppen-Keys aus NULL vs. "").
|
entstehen zwei Gruppen-Keys aus NULL vs. "").
|
||||||
|
|
||||||
### F3. On-Demand-Markt: Tags nachladen statt `Other` zu speichern
|
### F3. On-Demand-Markt: Tags nachladen statt `Other` zu speichern
|
||||||
|
|
||||||
- [ ] In `GetMarketAsync`: wenn `parentTags` leer ist, aber ein Event mit Id vorhanden ist →
|
- [x] In `GetMarketAsync`: wenn `parentTags` leer ist, aber ein Event mit Id vorhanden ist →
|
||||||
**`/events?id=<eventId>` nachladen** (liefert Tags, 1 Extra-Call pro neuem Markt, cachebar) und die
|
**`/events?id=<eventId>` nachladen** (liefert Tags, 1 Extra-Call pro neuem Markt, cachebar) und die
|
||||||
Tags fürs Mapping verwenden. Über den `IRateLimiter` drosseln.
|
Tags fürs Mapping verwenden. Über den `IRateLimiter` drosseln.
|
||||||
- [ ] Alternativ/zusätzlich: existiert das Parent-Event bereits in unserer DB (aus dem Events-Sync,
|
- [x] Alternativ/zusätzlich: existiert das Parent-Event bereits in unserer DB (aus dem Events-Sync,
|
||||||
`Event.Tags` gefüllt) → **Tags von dort erben**, ganz ohne API-Call.
|
`Event.Tags` gefüllt) → **Tags von dort erben**, ganz ohne API-Call.
|
||||||
|
|
||||||
### F4. Kategorie aus Event-Tags ableiten + Offline-Backfill (der große Hebel)
|
### F4. Kategorie aus Event-Tags ableiten + Offline-Backfill (der große Hebel)
|
||||||
|
|
||||||
- [ ] Markt-Kategorie primär aus den **Event-Tags** (`market.Event.Tags`) ableiten, nicht aus den
|
- [x] Markt-Kategorie primär aus den **Event-Tags** (`market.Event.Tags`) ableiten, nicht aus den
|
||||||
(leeren) Markt-Feldern. On-Demand-Märkte erben so die Kategorie ihres Events.
|
(leeren) Markt-Feldern. On-Demand-Märkte erben so die Kategorie ihres Events.
|
||||||
- [ ] **Einmaliger Offline-Backfill** (keine API-Calls!): über alle Märkte iterieren, deren Event
|
- [x] **Einmaliger Offline-Backfill** (keine API-Calls!): über alle Märkte iterieren, deren Event
|
||||||
Tags hat, und Kategorie/Subcategory aus `Event.Tags` neu ableiten (mit F1/F2). Als Methode in
|
Tags hat, und Kategorie/Subcategory aus `Event.Tags` neu ableiten (mit F1/F2). Als Methode in
|
||||||
`RunRecalculateAllTradersAsync` einhängen ODER eigener Dev-Endpoint. Danach `TraderCategoryPerformance`
|
`RunRecalculateAllTradersAsync` einhängen ODER eigener Dev-Endpoint. Danach `TraderCategoryPerformance`
|
||||||
neu rechnen (passiert durch die ohnehin folgende Trader-Neuberechnung).
|
neu rechnen (passiert durch die ohnehin folgende Trader-Neuberechnung).
|
||||||
@@ -136,9 +154,9 @@ Reihenfolge: **D1 → D2/D2b/D2c → D3** (D2c ist klein und gehört in denselbe
|
|||||||
|
|
||||||
### F6. Tests
|
### F6. Tests
|
||||||
|
|
||||||
- [ ] Mapper: kanonischer Tag gewinnt über Reihenfolge (`"Ethiopia, Elections, ..., Politics"` → Politics;
|
- [x] Mapper: kanonischer Tag gewinnt über Reihenfolge (`"Ethiopia, Elections, ..., Politics"` → Politics;
|
||||||
`"Sports, Soccer, ..."` → Sports, Subcategory `Soccer`).
|
`"Sports, Soccer, ..."` → Sports, Subcategory `Soccer`).
|
||||||
- [ ] Subcategory: Müll-Tags werden gefiltert; NULL und `""` erzeugen denselben Gruppen-Key
|
- [x] Subcategory: Müll-Tags werden gefiltert; NULL und `""` erzeugen denselben Gruppen-Key
|
||||||
(kein Duplikat mehr).
|
(kein Duplikat mehr).
|
||||||
- [ ] Event-Tag-Vererbung: Markt ohne eigene Tags, Event mit `['Crypto',...]` → Markt wird Crypto.
|
- [ ] Event-Tag-Vererbung: Markt ohne eigene Tags, Event mit `['Crypto',...]` → Markt wird Crypto.
|
||||||
- [ ] Backfill: Markt in DB als `Other`, Event.Tags = `['Politics',...]` → nach Backfill Politics,
|
- [ ] Backfill: Markt in DB als `Other`, Event.Tags = `['Politics',...]` → nach Backfill Politics,
|
||||||
|
|||||||
+32
-17
@@ -1,5 +1,20 @@
|
|||||||
# FIXPLAN Teil UI — Ranglisten & Showcases sichtbar/korrekt machen
|
# FIXPLAN Teil UI — Ranglisten & Showcases sichtbar/korrekt machen
|
||||||
|
|
||||||
|
> **✅ ABGESCHLOSSEN.** Umgesetzt in den Commits *„UI-U3: server-side
|
||||||
|
> min-winrate/min-copyability filters"* und *„UI-U1/U2/U4/U5: dashboard showcases +
|
||||||
|
> server-side leaderboard sort"*, beide in `feature/linux-net10` enthalten.
|
||||||
|
> Am 2026-08-23 Punkt für Punkt gegen den Code nachgeprüft: `loadShowcases()`
|
||||||
|
> (`app.js:393`) wird aus `loadDashboard()` heraus aufgerufen (`app.js:307`),
|
||||||
|
> Red Flags heben sich über `showcase-card--warn` ab, `take` steht auf 200 mit
|
||||||
|
> serverseitigem `sort`-Parameter (`app.js:466,470`), `minWinRate`/`minCopyability`
|
||||||
|
> gehen bis in `TraderEndpoints.cs:16` durch (Test in `AnalyticsServiceTests.cs:135`),
|
||||||
|
> `#tradersSort` führt alle sieben Metriken, und der Empty-State steht auf
|
||||||
|
> `colspan="10"`.
|
||||||
|
>
|
||||||
|
> **Offen bleibt nur der als „Optional" markierte Punkt in U5** (Pfeilrichtung
|
||||||
|
> ▲/▼ in der aktiven Sortierspalte) — die Tabellenköpfe zeigen weiterhin
|
||||||
|
> statisches ↕. Reine Politur, kein Fehlverhalten.
|
||||||
|
|
||||||
> Eigenständiger Plan für die Web-UI (`src/Predictalytics.Api/wwwroot/`). Von oben nach unten
|
> Eigenständiger Plan für die Web-UI (`src/Predictalytics.Api/wwwroot/`). Von oben nach unten
|
||||||
> abarbeiten. Nach **jeder** Aufgabe im Browser gegen die laufende API prüfen und committen.
|
> abarbeiten. Nach **jeder** Aufgabe im Browser gegen die laufende API prüfen und committen.
|
||||||
|
|
||||||
@@ -46,10 +61,10 @@ aber das **Frontend nicht nachgezogen**:
|
|||||||
**Ziel:** Die 7 kuratierten Sektionen sichtbar machen — je Sektion Titel, Beschreibung und die Top-Trader
|
**Ziel:** Die 7 kuratierten Sektionen sichtbar machen — je Sektion Titel, Beschreibung und die Top-Trader
|
||||||
als klickbare Kacheln/Zeilen (Klick → `viewTrader(id)`).
|
als klickbare Kacheln/Zeilen (Klick → `viewTrader(id)`).
|
||||||
|
|
||||||
- [ ] **HTML:** In `index.html` im `#page-dashboard` einen Container `<div id="showcaseSections"></div>`
|
- [x] **HTML:** In `index.html` im `#page-dashboard` einen Container `<div id="showcaseSections"></div>`
|
||||||
ergänzen (unter den bestehenden Dashboard-Karten, vor/nach „Top Traders" — Platzierung so, dass es
|
ergänzen (unter den bestehenden Dashboard-Karten, vor/nach „Top Traders" — Platzierung so, dass es
|
||||||
nicht mit den bestehenden Kacheln kollidiert).
|
nicht mit den bestehenden Kacheln kollidiert).
|
||||||
- [ ] **JS:** Neue Funktion `loadShowcases()` in `app.js`:
|
- [x] **JS:** Neue Funktion `loadShowcases()` in `app.js`:
|
||||||
- `const sections = await api('/api/traders/showcases');`
|
- `const sections = await api('/api/traders/showcases');`
|
||||||
- Response-Shape (camelCase): `[{ key, title, description, traders: [TraderDto, ...] }]`.
|
- Response-Shape (camelCase): `[{ key, title, description, traders: [TraderDto, ...] }]`.
|
||||||
- Pro Sektion eine `card` rendern: `title` als Überschrift, `description` als `page-subtitle`-artiger
|
- Pro Sektion eine `card` rendern: `title` als Überschrift, `description` als `page-subtitle`-artiger
|
||||||
@@ -59,8 +74,8 @@ als klickbare Kacheln/Zeilen (Klick → `viewTrader(id)`).
|
|||||||
- Leere Sektionen liefert das Backend gar nicht erst — kein Sonderfall nötig; aber wenn die ganze
|
- Leere Sektionen liefert das Backend gar nicht erst — kein Sonderfall nötig; aber wenn die ganze
|
||||||
Antwort leer/`null` ist, freundlicher Empty-State.
|
Antwort leer/`null` ist, freundlicher Empty-State.
|
||||||
- Jede Trader-Zeile: `onclick="viewTrader(${t.id})"`.
|
- Jede Trader-Zeile: `onclick="viewTrader(${t.id})"`.
|
||||||
- [ ] `loadShowcases()` in `loadDashboard()` aufrufen (bzw. beim Aktivieren von `page-dashboard`).
|
- [x] `loadShowcases()` in `loadDashboard()` aufrufen (bzw. beim Aktivieren von `page-dashboard`).
|
||||||
- [ ] **Optik:** `insider_watch` und `red_flags` optisch abheben (z. B. Warn-Akzent für Red Flags über
|
- [x] **Optik:** `insider_watch` und `red_flags` optisch abheben (z. B. Warn-Akzent für Red Flags über
|
||||||
vorhandene CSS-Variablen `--pnl-negative`), aber im bestehenden Kartenstil bleiben.
|
vorhandene CSS-Variablen `--pnl-negative`), aber im bestehenden Kartenstil bleiben.
|
||||||
|
|
||||||
**U1 fertig, wenn:** Dashboard zeigt die vom Backend gelieferten Sektionen; Klick auf einen Trader öffnet
|
**U1 fertig, wenn:** Dashboard zeigt die vom Backend gelieferten Sektionen; Klick auf einen Trader öffnet
|
||||||
@@ -73,16 +88,16 @@ die Detailseite; keine Konsolenfehler. Commit: „UI-U1: render dashboard showca
|
|||||||
**Ziel:** Die Rangliste zeigt die **echten** Top-N nach der gewählten Metrik, nicht nur eine Umsortierung
|
**Ziel:** Die Rangliste zeigt die **echten** Top-N nach der gewählten Metrik, nicht nur eine Umsortierung
|
||||||
der ersten 100.
|
der ersten 100.
|
||||||
|
|
||||||
- [ ] **JS:** In `loadTraders()` den gewählten Sort als Query-Param an die API hängen:
|
- [x] **JS:** In `loadTraders()` den gewählten Sort als Query-Param an die API hängen:
|
||||||
`url += '&sort=' + encodeURIComponent(serverSortKey)`. Mapping UI→Server:
|
`url += '&sort=' + encodeURIComponent(serverSortKey)`. Mapping UI→Server:
|
||||||
`score→` (leer/default), `winrate→winrate`, `copyability→copytrading`, `pnl→pnl`,
|
`score→` (leer/default), `winrate→winrate`, `copyability→copytrading`, `pnl→pnl`,
|
||||||
`pnl30d→pnl30d`, `calmar→calmar`, `conviction→conviction`, `profitfactor→profitfactor`.
|
`pnl30d→pnl30d`, `calmar→calmar`, `conviction→conviction`, `profitfactor→profitfactor`.
|
||||||
(Der Server ordnet absteigend — das ist für alle diese Metriken die sinnvolle Richtung.)
|
(Der Server ordnet absteigend — das ist für alle diese Metriken die sinnvolle Richtung.)
|
||||||
- [ ] **JS:** Den clientseitigen `data.sort(...)`-Block in `loadTraders()` entfernen bzw. nur noch als
|
- [x] **JS:** Den clientseitigen `data.sort(...)`-Block in `loadTraders()` entfernen bzw. nur noch als
|
||||||
Fallback für die rein clientseitigen Keys `name`/`platform` behalten (die kennt der Server nicht).
|
Fallback für die rein clientseitigen Keys `name`/`platform` behalten (die kennt der Server nicht).
|
||||||
- [ ] **take** von 100 auf einen sinnvollen Wert erhöhen (z. B. 200) — die serverseitige Sortierung liefert
|
- [x] **take** von 100 auf einen sinnvollen Wert erhöhen (z. B. 200) — die serverseitige Sortierung liefert
|
||||||
jetzt die richtige Reihenfolge, die Tabelle kann eine echte Bestenliste zeigen.
|
jetzt die richtige Reihenfolge, die Tabelle kann eine echte Bestenliste zeigen.
|
||||||
- [ ] Die Klick-Sortierung der Tabellenköpfe (`setTraderSort`, `app.js:376`) auf denselben Pfad umstellen:
|
- [x] Die Klick-Sortierung der Tabellenköpfe (`setTraderSort`, `app.js:376`) auf denselben Pfad umstellen:
|
||||||
Sort setzen → `loadTraders()` (das nun serverseitig sortiert). Die `↕`-Header behalten.
|
Sort setzen → `loadTraders()` (das nun serverseitig sortiert). Die `↕`-Header behalten.
|
||||||
|
|
||||||
**U2 fertig, wenn:** Auswahl „Calmar/Conviction/Profit-Faktor" (nach U4) verändert die Reihenfolge sichtbar
|
**U2 fertig, wenn:** Auswahl „Calmar/Conviction/Profit-Faktor" (nach U4) verändert die Reihenfolge sichtbar
|
||||||
@@ -94,16 +109,16 @@ und zeigt Trader, die vorher nicht in den ersten 100 waren. Commit: „UI-U2: se
|
|||||||
|
|
||||||
**Ziel:** Die Kennzahl-Mindestfilter dürfen nicht an der 100/200-Grenze abschneiden.
|
**Ziel:** Die Kennzahl-Mindestfilter dürfen nicht an der 100/200-Grenze abschneiden.
|
||||||
|
|
||||||
- [ ] **API:** `IAnalyticsService.GetTradersAsync` und die Implementierung (`AnalyticsService.cs:165`) um
|
- [x] **API:** `IAnalyticsService.GetTradersAsync` und die Implementierung (`AnalyticsService.cs:165`) um
|
||||||
zwei **optionale nullable** Parameter erweitern: `decimal? minWinRate = null,
|
zwei **optionale nullable** Parameter erweitern: `decimal? minWinRate = null,
|
||||||
decimal? minCopyability = null`. In der In-Memory-Filterkette (dort wird ohnehin schon `highlyCopyable`
|
decimal? minCopyability = null`. In der In-Memory-Filterkette (dort wird ohnehin schon `highlyCopyable`
|
||||||
und `traitFilter` gefiltert) anwenden:
|
und `traitFilter` gefiltert) anwenden:
|
||||||
`if (minWinRate is > 0) traders = traders.Where(t => t.WinRate >= minWinRate).ToList();` und analog
|
`if (minWinRate is > 0) traders = traders.Where(t => t.WinRate >= minWinRate).ToList();` und analog
|
||||||
`t.Analytics?.CopytradingCopyabilityScore >= minCopyability`.
|
`t.Analytics?.CopytradingCopyabilityScore >= minCopyability`.
|
||||||
- [ ] **Endpoint:** In `TraderEndpoints.cs` die `/api/traders`-Route um die zwei Query-Parameter durchreichen.
|
- [x] **Endpoint:** In `TraderEndpoints.cs` die `/api/traders`-Route um die zwei Query-Parameter durchreichen.
|
||||||
- [ ] **JS:** In `loadTraders()` `filterWinrateMin`/`filterCopyabilityMin` als Query-Param senden statt
|
- [x] **JS:** In `loadTraders()` `filterWinrateMin`/`filterCopyabilityMin` als Query-Param senden statt
|
||||||
clientseitig zu filtern; den clientseitigen Filter-Block entfernen.
|
clientseitig zu filtern; den clientseitigen Filter-Block entfernen.
|
||||||
- [ ] **Test:** Kleiner Service-Test (SQLite-Muster wie vorhandene `AnalyticsService`/Repository-Tests):
|
- [x] **Test:** Kleiner Service-Test (SQLite-Muster wie vorhandene `AnalyticsService`/Repository-Tests):
|
||||||
drei Trader mit WinRate 40/60/80, `minWinRate=55` → nur zwei zurück. Baseline bleibt grün.
|
drei Trader mit WinRate 40/60/80, `minWinRate=55` → nur zwei zurück. Baseline bleibt grün.
|
||||||
|
|
||||||
**U3 fertig, wenn:** Ein hoher Mindest-Copyability-Wert zeigt auch Trader, die in der Default-Order weit
|
**U3 fertig, wenn:** Ein hoher Mindest-Copyability-Wert zeigt auch Trader, die in der Default-Order weit
|
||||||
@@ -115,13 +130,13 @@ hinten stehen. Commit: „UI-U3: server-side min-winrate/min-copyability filters
|
|||||||
|
|
||||||
**Ziel:** Ein einziges, vollständiges Sort-Steuerelement für die Rangliste.
|
**Ziel:** Ein einziges, vollständiges Sort-Steuerelement für die Rangliste.
|
||||||
|
|
||||||
- [ ] **HTML:** `#tradersSort` (`index.html:209`) um die aussagekräftigen Metriken erweitern:
|
- [x] **HTML:** `#tradersSort` (`index.html:209`) um die aussagekräftigen Metriken erweitern:
|
||||||
`pnl30d` (PnL 30 T), `calmar` (Rendite/Drawdown), `conviction` (Conviction-Edge),
|
`pnl30d` (PnL 30 T), `calmar` (Rendite/Drawdown), `conviction` (Conviction-Edge),
|
||||||
`profitfactor` (Profit-Faktor), `copyability` (bereits da), `score`/`winrate`/`pnl`/`name` behalten.
|
`profitfactor` (Profit-Faktor), `copyability` (bereits da), `score`/`winrate`/`pnl`/`name` behalten.
|
||||||
- [ ] **JS:** Klarstellen, welches Dropdown die Traders-Seite steuert. `#tradersSort` bleibt maßgeblich für
|
- [x] **JS:** Klarstellen, welches Dropdown die Traders-Seite steuert. `#tradersSort` bleibt maßgeblich für
|
||||||
`page-traders`; das globale `#sortSelect` (falls es die Traders-Seite mitsteuert) entkoppeln oder
|
`page-traders`; das globale `#sortSelect` (falls es die Traders-Seite mitsteuert) entkoppeln oder
|
||||||
auf denselben State spiegeln, sodass es **kein** widersprüchliches `currentSort` mehr gibt.
|
auf denselben State spiegeln, sodass es **kein** widersprüchliches `currentSort` mehr gibt.
|
||||||
- [ ] Sicherstellen, dass Header-Klick (`setTraderSort`) und Dropdown denselben `currentSort` schreiben und
|
- [x] Sicherstellen, dass Header-Klick (`setTraderSort`) und Dropdown denselben `currentSort` schreiben und
|
||||||
beide `loadTraders()` (serverseitig, U2) auslösen.
|
beide `loadTraders()` (serverseitig, U2) auslösen.
|
||||||
|
|
||||||
**U4 fertig, wenn:** Alle Dropdown-Optionen sortieren korrekt (serverseitig), Header-Klick und Dropdown
|
**U4 fertig, wenn:** Alle Dropdown-Optionen sortieren korrekt (serverseitig), Header-Klick und Dropdown
|
||||||
@@ -131,9 +146,9 @@ bleiben synchron. Commit: „UI-U4: unify + extend leaderboard sort control".
|
|||||||
|
|
||||||
## Aufgabe U5 — Kleinkram / Konsistenz
|
## Aufgabe U5 — Kleinkram / Konsistenz
|
||||||
|
|
||||||
- [ ] **colspan-Fix:** Empty-State in `loadTraders()` (`app.js:407`) von `colspan="11"` auf die echte
|
- [x] **colspan-Fix:** Empty-State in `loadTraders()` (`app.js:407`) von `colspan="11"` auf die echte
|
||||||
Spaltenzahl **10** korrigieren.
|
Spaltenzahl **10** korrigieren.
|
||||||
- [ ] **Spalten-Parität:** Prüfen, dass Tabellenkopf (`index.html:270`) und Zeilen-Template
|
- [x] **Spalten-Parität:** Prüfen, dass Tabellenkopf (`index.html:270`) und Zeilen-Template
|
||||||
(`app.js:435`) dieselbe Spaltenzahl/-reihenfolge haben.
|
(`app.js:435`) dieselbe Spaltenzahl/-reihenfolge haben.
|
||||||
- [ ] **Optional:** Aktive Sortierspalte im Header visuell markieren (Pfeilrichtung ▲/▼ statt neutralem ↕).
|
- [ ] **Optional:** Aktive Sortierspalte im Header visuell markieren (Pfeilrichtung ▲/▼ statt neutralem ↕).
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
# Predictalytics — Stand des Projekts
|
||||||
|
|
||||||
|
> Bestandsaufnahme vom **2026-08-23**, erstellt beim Frühjahrsputz. Jede Aussage hier ist
|
||||||
|
> gegen den Code geprüft, nicht aus den Plandokumenten übernommen — die waren zu diesem
|
||||||
|
> Zeitpunkt durchweg veraltet.
|
||||||
|
>
|
||||||
|
> Dieses Dokument ist die Einstiegsseite. Die Detailpläne bleiben bestehen und tragen
|
||||||
|
> jetzt jeweils oben einen eigenen Statusblock.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Wo wir stehen
|
||||||
|
|
||||||
|
Predictalytics analysiert Polymarket-Trader auf ihre Eignung fürs Copytrading. Die
|
||||||
|
Anwendung ist **funktional vollständig und läuft im Betrieb**: 151.094 Trader und
|
||||||
|
38,2 Mio. Trades in der Produktivdatenbank (Stand 2026-08-08).
|
||||||
|
|
||||||
|
Der aktuelle Zweig ist `feature/linux-net10`. Er trägt die Portierung auf .NET 10 und
|
||||||
|
Avalonia und ist **noch nicht nach `main` gemerged** — `main` steht neun Commits zurück.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Zielframework | net10.0, ein einziges für alle Projekte |
|
||||||
|
| Oberfläche | Avalonia 12.1.1 (Windows + Linux), zusätzlich ein Headless-Modus |
|
||||||
|
| Datenzugriff | EF Core **9** (bewusst; Pomelo hat keinen EF-10-Provider), 32 Migrationen |
|
||||||
|
| Tests | **126 grün, 1 bewusst übersprungen** |
|
||||||
|
| Build | fehlerfrei, 8 Nullable-Warnungen (siehe Abschnitt 5) |
|
||||||
|
|
||||||
|
### Aufbau
|
||||||
|
|
||||||
|
```
|
||||||
|
Predictalytics.Domain 1.321 Z. Entities, Enums, Repository-Interfaces
|
||||||
|
Predictalytics.Application 3.036 Z. Analytik, Scoring, Kalkulatoren
|
||||||
|
Predictalytics.Infrastructure 4.966 Z. EF-Kontext, Repositories, Provider (+65 Migrationsdateien)
|
||||||
|
Predictalytics.Worker 2.449 Z. 13 Hintergrunddienste
|
||||||
|
Predictalytics.Api 896 Z. Minimal-API-Endpunkte + WebUI (3.247 Z. HTML/JS/CSS)
|
||||||
|
Predictalytics.Hosting 1.972 Z. Plattformneutraler Kern: Kestrel, Worker, Lizenz, Heartbeat
|
||||||
|
Predictalytics.Shell 1.305 Z. Avalonia-Bedienhülle
|
||||||
|
Predictalytics.Application.Tests 3.216 Z. 126 Tests
|
||||||
|
```
|
||||||
|
|
||||||
|
`Predictalytics.Hosting` hängt an einem **Schwester-Repo**: `Deploymentcenter` muss neben
|
||||||
|
dem Predictalytics-Checkout liegen, sonst bricht der Build mit einer erklärenden Meldung ab.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Was fertig ist
|
||||||
|
|
||||||
|
Alles Folgende ist im Code belegt — die Belegstellen stehen in den jeweiligen Plandokumenten.
|
||||||
|
|
||||||
|
* **Kernanalytik** (`UMSETZUNGSPLAN.md`, Phase 1): positionsbasierte PnL-Engine nach
|
||||||
|
Average-Cost mit sauberer Behandlung jeder `TradeSide`, marktbasierte Win-Rate,
|
||||||
|
Deep-Dive-Kennzahlen aus echter Preishistorie, Copytrading-Score aus gemessenem
|
||||||
|
Alpha-Verfall gegen das Markt-Tape.
|
||||||
|
* **Robustheit** (Phase 2): EF-Migrationen statt handgeschriebener `ALTER TABLE`,
|
||||||
|
entkoppelte Scoring-Pipeline, Log-Retention, Testprojekt. Alle fünf Bugs aus dem
|
||||||
|
Review-Pass vom 2026-07-01 sind behoben.
|
||||||
|
* **Speicher-Haushalt** (`FIXPLAN-G-Speicher.md`): budgetabhängiges Retention-Fenster,
|
||||||
|
Burst-Kompaktierung, Per-Kategorie-Edge.
|
||||||
|
* **Kategorie-Erkennung** (`FIXPLAN-TODO.md`, Teil F): kanonische Tag-Tabelle,
|
||||||
|
Rausch-Filter, Tag-Nachladen für On-Demand-Märkte, Offline-Backfill beim Start.
|
||||||
|
* **HF-Trader-Tiering** (Teil D): `IngestMode` mit Hysterese, wöchentliche Biopsie,
|
||||||
|
Aggregations-Buckets.
|
||||||
|
* **Web-UI** (`FIXPLAN-UI-Ranglisten.md`): Showcase-Sektionen, serverseitige Sortierung
|
||||||
|
und Filterung.
|
||||||
|
* **Copytrading-Analytik**: Co-Movement samt Clusterung, Strategie-Drift, Edge-Freshness,
|
||||||
|
Copy-Portfolio, Insider-Feed — alle fünf Vorschläge der Roadmap sind umgesetzt und
|
||||||
|
in diesem Zweig enthalten.
|
||||||
|
* **Portierung** (`docs/PLAN-Linux-Portierung.md`, Phasen 0–4): .NET 10, extrahierter
|
||||||
|
Hosting-Kern, Avalonia-Shell, abgesicherte Steuerendpunkte, plattformfeste Kultur- und
|
||||||
|
Log-Pfade. Der WinForms-Host ist entfernt.
|
||||||
|
* **Betriebsanbindung**: Lizenz, Watchdog, UpdateService, Fehler-Stream und Bugtracker
|
||||||
|
laufen alle über das Deployment Center.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Was offen ist
|
||||||
|
|
||||||
|
Nach Dringlichkeit:
|
||||||
|
|
||||||
|
### 3.1 ⚠️ OpenRouter-API-Key liegt im Klartext im Repository
|
||||||
|
|
||||||
|
`src/Predictalytics.Api/appsettings.json` enthält einen **echten** OpenRouter-Schlüssel,
|
||||||
|
versioniert seit Commit `7045002` und damit auf dem Gitea-Server.
|
||||||
|
|
||||||
|
**Zu tun:** Den Schlüssel bei OpenRouter zurückziehen und neu ausstellen. Ihn nur aus der
|
||||||
|
Datei zu entfernen genügt nicht — der Git-Verlauf behält ihn. Anschließend über die
|
||||||
|
Umgebungsvariable `OpenRouter__ApiKey` setzen; eine Codeänderung braucht es dafür nicht,
|
||||||
|
`WebApplication.CreateBuilder` liest Umgebungsvariablen bereits mit Vorrang.
|
||||||
|
|
||||||
|
Der Schlüssel wurde bewusst **nicht** eigenmächtig entfernt: ohne Rotation bringt das
|
||||||
|
keinen Sicherheitsgewinn, würde aber die KI-Strategieanalyse stillschweigend abschalten.
|
||||||
|
|
||||||
|
### 3.2 Die Portierung ist unter Linux nie gelaufen
|
||||||
|
|
||||||
|
Phase 5 (Deployment, systemd/Container, Reverse Proxy, CI) und Phase 6 (Verifikation) des
|
||||||
|
Portierungsplans sind unangetastet. Der Code ist plattformneutral gebaut, aber **auf keinem
|
||||||
|
Linux-System je ausgeführt worden**. Die Ende-zu-Ende-Prüfung vom 2026-08-08 lief unter
|
||||||
|
Windows. Solange das aussteht, ist „läuft unter Linux" eine Annahme, kein Befund.
|
||||||
|
|
||||||
|
### 3.3 A4 — Strategie-Klassifikation
|
||||||
|
|
||||||
|
Der einzige größere unerledigte Block aus Phase 1. `StrategyType` entsteht weiterhin aus
|
||||||
|
zwei Signalen — `avgSize > 10000` ergibt `Whale`, `hedgingRate > 30` ergibt `Hedger`, sonst
|
||||||
|
`Bot`/`Unknown` — weshalb der Großteil der Trader strukturell auf `Unknown` landet. Die
|
||||||
|
Bausteine für eine bessere Einstufung liegen fertig herum (`StrategyMetricsCalculator`,
|
||||||
|
`TraderTraitCalculator`, `FingerprintSnapshotService`), sie speisen die Klassifikation nur
|
||||||
|
nicht.
|
||||||
|
|
||||||
|
Die Logik steht außerdem **doppelt** da: `AnalyticsService.cs:418` und
|
||||||
|
`ScoringService.cs:89`. Beim Anfassen zusammenführen.
|
||||||
|
|
||||||
|
### 3.4 Kleinere Reste
|
||||||
|
|
||||||
|
* `FIXPLAN-UI-Ranglisten.md`: Pfeilrichtung ▲/▼ in der aktiven Sortierspalte (war als
|
||||||
|
„Optional" markiert).
|
||||||
|
* `FIXPLAN-TODO.md` F5/F6: gezielter Nachlade-Schritt für fehlende Event-Tags, zwei Tests.
|
||||||
|
* `UMSETZUNGSPLAN.md` C3 und D1/D2: DB-Arbeiten auf SQL-Ebene und die Entscheidung über
|
||||||
|
einen Datenbank-Neustart — beides liegt beim Nutzer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Zweige
|
||||||
|
|
||||||
|
`feature/linux-net10` enthält alle Feature-Zweige. Die folgenden sind vollständig darin
|
||||||
|
aufgegangen und können gelöscht werden:
|
||||||
|
|
||||||
|
```
|
||||||
|
copy-portfolio edge-freshness fingerprint-drift-foundation insider-follow-feed
|
||||||
|
integration smart-money-comovement smart-money-graph ui-ranglisten-showcases
|
||||||
|
ops-watchdog-license fix-event-description-truncation
|
||||||
|
```
|
||||||
|
|
||||||
|
`fix-event-description-truncation` war der einzige mit eigenem Stand; sein Fix und sein
|
||||||
|
Regressionstest stecken seit Commit `cb58e09` im Hauptzweig.
|
||||||
|
|
||||||
|
**Offen: der Merge nach `main`.** `main` steht neun Commits zurück und kennt weder die
|
||||||
|
Avalonia-Shell noch die Deploymentcenter-Anbindung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Bewusst stehengelassen
|
||||||
|
|
||||||
|
Beim Aufräumen geprüft und **nicht** entfernt, weil Löschen hier Funktionsverlust wäre und
|
||||||
|
nicht Entrümpelung:
|
||||||
|
|
||||||
|
| Was | Warum es bleibt |
|
||||||
|
|---|---|
|
||||||
|
| `Providers/Azuro/AzuroProvider.cs` | Platzhalter mit `IsImplemented => false`. Die Worker überspringen ihn sauber; er kostet nichts und markiert den Ausbaupfad. |
|
||||||
|
| `Providers/Limitless/` (3 Dateien) | **Voll implementierter** zweiter Marktplatz mit `IsImplemented => true`. Wird produktiv nur nicht bespielt. |
|
||||||
|
| `Services/CopytradingBacktestHarness.cs` (123 Z.) | Fertig implementierter Latenz-Sweep, per DI registriert, aber **von niemandem aufgerufen**. Entweder anschließen oder bewusst verwerfen — beides eine fachliche Entscheidung. |
|
||||||
|
| `wwwroot/landing.html`, `wwwroot/docs.html` | Marketing-Seiten mit Preis-Abschnitt, vom Dashboard aus nicht verlinkt. Passen zur später angedachten Monetarisierung. |
|
||||||
|
| 8 Nullable-Warnungen | `TradeRepository.cs:94-102`, `PolymarketProvider.cs:357`, `TraderAnalyticsWorker.cs:152`. Bestehen seit Längerem; sie zu beheben ist Logikarbeit, kein Aufräumen. |
|
||||||
|
| 65 Migrationsdateien | Die Schemahistorie. Zusammenfassen ginge, ist aber riskant und sollte eine bewusste Entscheidung sein. |
|
||||||
|
|
||||||
|
Ein Nebenbefund ohne Handlungszwang: der Kategorie-Backfill beim Start
|
||||||
|
(`PredictalyticsHost.cs:116`) lädt **alle** Märkte samt Event in den Speicher. Bei
|
||||||
|
wachsendem Bestand wird das die Startzeit spürbar belasten.
|
||||||
+106
-60
@@ -1,5 +1,51 @@
|
|||||||
# Predictalytics – Umsetzungsplan (Phase 1 & 2 + Storage-Optimierung)
|
# Predictalytics – Umsetzungsplan (Phase 1 & 2 + Storage-Optimierung)
|
||||||
|
|
||||||
|
> ## Stand 2026-08-23 — Phase 1 und 2 sind bis auf zwei Punkte umgesetzt
|
||||||
|
>
|
||||||
|
> Die Häkchen waren bis dahin nie nachgezogen worden: 85 Punkte standen auf offen,
|
||||||
|
> obwohl der Code sie längst enthielt. Am 2026-08-23 abschnittsweise gegen den Code
|
||||||
|
> geprüft und gesetzt. **Belege:**
|
||||||
|
>
|
||||||
|
> | | Belegt durch |
|
||||||
|
> |---|---|
|
||||||
|
> | **A1** PnL-Engine | `Infrastructure/Services/PositionPnLEngine.cs` — alle `TradeSide`-Zweige einzeln behandelt (`:157-217`), `Split`/`Merge` neutral, unrealisiert aus `CurrentPrice − AvgCost` (`:266`), Gesamt-PnL `:290`. Entity `Domain/Entities/TraderPosition.cs`. Tests: `PositionPnLEngineTests.cs` |
|
||||||
|
> | **A2** Win-Rate | `CalculateMarketWinRates` (`PositionPnLEngine.cs:381`) — marktbasiert, kein `return 0` mehr |
|
||||||
|
> | **A3** Deep-Dive | `MarketOutcomePriceSnapshot` existiert und wird ausgewertet (`AnalyticsService.cs:88,390`). Die `50` ist jetzt **Rückfallwert bei fehlender Preishistorie** (`:456,457,474`), kein fester Neutralwert mehr |
|
||||||
|
> | **A5** Copytrading-Score | `Infrastructure/Services/CopytradingEstimator.cs` — **abweichend umgesetzt und besser als geplant**: statt geschätztem Liquiditäts-Fit misst der Score den echten Alpha-Verfall gegen das Markt-Tape (Slippage bei 10 s Folgelatenz, 70 %) plus Sizing-Konsistenz (30 %). Frequenz/Konzentration deckt stattdessen `IngestMode` + Trait `not_copyable_hf` ab. **Track-Record-Länge fließt nicht in den Score ein** |
|
||||||
|
> | **B1/B4** | 32 EF-Migrationen; Testprojekt existiert, **126 grün + 1 Skip** |
|
||||||
|
> | **B3** | `ScoringService.RecalculateAllScoresAsync:127` rechnet nur bei `CalculatedAt < LastTradesUpdatedAt` neu, Rank-Update gebatcht; eigener `ScoringAndAlertsWorker` mit 15-Minuten-Takt |
|
||||||
|
> | **B5** | `LoggingSetup.cs:90-133` — `retainedFileCountLimit` und `fileSizeLimitBytes` gesetzt |
|
||||||
|
> | **B6** | Alle fünf Bugs behoben: geschlossene Märkte nur noch **1×/Tag** (`MarketSyncWorker.cs:52`), Watchlist von der Löschung ausgenommen (`TraderRepository.cs:137`), `GetKnownPlatformTradeIdsAsync` filtert auf die Batch-IDs (`TradeRepository.cs:158`), `_syncSemaphore` in `AddOrUpdateAsync` (`MarketRepository.cs:31`), `break` durch einen Filter ersetzt (`TradeHistoryWorker.cs:200`) |
|
||||||
|
> | **C1/C2** | `TradeRetentionWorker` + `Application/Services/StorageGovernor.cs` (budgetabhängiges Fenster), Burst-Kompaktierung ab `TradeRetentionWorker.cs:148` |
|
||||||
|
>
|
||||||
|
> ### Was wirklich offen ist
|
||||||
|
>
|
||||||
|
> 1. **A4 — Strategie-Klassifikation** (Zeilen 47–54). Der einzige größere unerledigte
|
||||||
|
> Block aus Phase 1. `StrategyType` entsteht weiterhin aus genau zwei Signalen
|
||||||
|
> (`AnalyticsService.cs:418` und, **dupliziert**, `ScoringService.cs:89`):
|
||||||
|
> `avgSize > 10000 → Whale`, `hedgingRate > 30 → Hedger`, sonst `Bot`/`Unknown`.
|
||||||
|
> Genau der Zustand, den A4 beheben sollte — der Großteil der Trader landet
|
||||||
|
> strukturell auf `Unknown`. Die Bausteine dafür liegen bereits fertig herum
|
||||||
|
> (`StrategyMetricsCalculator`, `TraderTraitCalculator`, `FingerprintSnapshotService`),
|
||||||
|
> sie speisen die Klassifikation nur nicht. Die Doppelung gehört beim Anfassen
|
||||||
|
> zusammengeführt.
|
||||||
|
> 2. **B2 — Secrets** (Zeilen 84–86). Nicht mehr „kein akuter Risikofall": in
|
||||||
|
> `src/Predictalytics.Api/appsettings.json` steht ein **echter OpenRouter-API-Key im
|
||||||
|
> Klartext**, versioniert seit Commit `7045002`. Siehe Warnung unten.
|
||||||
|
> 3. **C3** (148–153) und **D1/D2** (165–173) sind DB-Arbeiten auf SQL-Ebene bzw. die
|
||||||
|
> Entscheidung über den Datenbank-Neustart — beides liegt beim Nutzer, nicht im Code.
|
||||||
|
> 4. **C1**, Zeile 137: Stichprobentest, wie weit `/activity` zurückreicht. Eine manuelle
|
||||||
|
> Prüfung, die sich im Code nicht belegen lässt — bewusst offen gelassen.
|
||||||
|
>
|
||||||
|
> ### ⚠️ Sicherheit — Handlungsbedarf
|
||||||
|
>
|
||||||
|
> `src/Predictalytics.Api/appsettings.json` enthält den OpenRouter-API-Key im Klartext
|
||||||
|
> und liegt so im Git-Verlauf und auf dem Gitea-Server. **Den Schlüssel bei OpenRouter
|
||||||
|
> zurückziehen und neu ausstellen** — ihn nur aus der Datei zu löschen hilft nicht, die
|
||||||
|
> Historie behält ihn. Danach über die Umgebungsvariable `OpenRouter__ApiKey` setzen;
|
||||||
|
> `WebApplication.CreateBuilder` liest Umgebungsvariablen bereits mit Vorrang vor
|
||||||
|
> `appsettings.json`, dafür ist keine Codeänderung nötig.
|
||||||
|
|
||||||
> **Wie wir das nutzen:** Wir arbeiten diese Liste Stück für Stück ab. Erledigte Punkte werden von `[ ]` auf `[x]` gesetzt. Reihenfolge ist absichtlich so gewählt, dass spätere Schritte auf früheren aufbauen — nicht einfach querbeet abhaken, siehe Abschnitt "Empfohlene Reihenfolge" ganz unten für die Kurzfassung.
|
> **Wie wir das nutzen:** Wir arbeiten diese Liste Stück für Stück ab. Erledigte Punkte werden von `[ ]` auf `[x]` gesetzt. Reihenfolge ist absichtlich so gewählt, dass spätere Schritte auf früheren aufbauen — nicht einfach querbeet abhaken, siehe Abschnitt "Empfohlene Reihenfolge" ganz unten für die Kurzfassung.
|
||||||
>
|
>
|
||||||
> Phase 3 (Monetarisierung: Auth, Multi-Tenant, Billing) ist bewusst **nicht** Teil dieses Plans.
|
> Phase 3 (Monetarisierung: Auth, Multi-Tenant, Billing) ist bewusst **nicht** Teil dieses Plans.
|
||||||
@@ -15,33 +61,33 @@ Beim Review ist aufgefallen, dass `TraderAnalyticsWorker.CalculatePnL` **jeden T
|
|||||||
## Phase 1 – Kernanalytik korrigieren
|
## Phase 1 – Kernanalytik korrigieren
|
||||||
|
|
||||||
### A1. Positionsbasierte PnL-Engine
|
### A1. Positionsbasierte PnL-Engine
|
||||||
- [ ] Entscheidung dokumentieren: **Average-Cost-Methode** statt FIFO (einfacher, und – wichtig – kompatibel mit späterer Trade-Kompaktierung in Abschnitt C, weil Average-Cost nur Gesamtstückzahl & Gesamtkosten braucht, keine Einzel-Trade-Reihenfolge)
|
- [x] Entscheidung dokumentieren: **Average-Cost-Methode** statt FIFO (einfacher, und – wichtig – kompatibel mit späterer Trade-Kompaktierung in Abschnitt C, weil Average-Cost nur Gesamtstückzahl & Gesamtkosten braucht, keine Einzel-Trade-Reihenfolge)
|
||||||
- [ ] Neue Domain-Struktur `TraderPosition` (TraderId, MarketOutcomeId, SharesHeld, AvgCost, RealizedPnl, zuletzt aktualisiert) — inkrementell fortschreibbar statt bei jeder Berechnung die komplette Trade-Historie neu zu scannen
|
- [x] Neue Domain-Struktur `TraderPosition` (TraderId, MarketOutcomeId, SharesHeld, AvgCost, RealizedPnl, zuletzt aktualisiert) — inkrementell fortschreibbar statt bei jeder Berechnung die komplette Trade-Historie neu zu scannen
|
||||||
- [ ] Buchungslogik je `TradeSide` sauber definieren:
|
- [x] Buchungslogik je `TradeSide` sauber definieren:
|
||||||
- [ ] `Buy`: Shares += Size, AvgCost neu gewichten
|
- [x] `Buy`: Shares += Size, AvgCost neu gewichten
|
||||||
- [ ] `Sell`: RealizedPnl += Size × (Price − AvgCost), Shares −= Size
|
- [x] `Sell`: RealizedPnl += Size × (Price − AvgCost), Shares −= Size
|
||||||
- [ ] `Redeem` (Marktauflösung): RealizedPnl += verbleibende Shares × (1 oder 0 je nach Gewinn-Outcome − AvgCost), Shares = 0
|
- [x] `Redeem` (Marktauflösung): RealizedPnl += verbleibende Shares × (1 oder 0 je nach Gewinn-Outcome − AvgCost), Shares = 0
|
||||||
- [ ] `Split` / `Merge`: als neutrale Positionsumwandlung behandeln (kein PnL-Effekt), nicht wie `Sell`
|
- [x] `Split` / `Merge`: als neutrale Positionsumwandlung behandeln (kein PnL-Effekt), nicht wie `Sell`
|
||||||
- [ ] `AddLiquidity` / `RemoveLiquidity`: getrennt von Trading-PnL betrachten (eigene Kategorie, fließt nicht in "Trading-Skill"-Bewertung ein)
|
- [x] `AddLiquidity` / `RemoveLiquidity`: getrennt von Trading-PnL betrachten (eigene Kategorie, fließt nicht in "Trading-Skill"-Bewertung ein)
|
||||||
- [ ] Unrealisierten PnL für offene Positionen berechnen: `Shares × (MarketOutcome.CurrentPrice − AvgCost)`
|
- [x] Unrealisierten PnL für offene Positionen berechnen: `Shares × (MarketOutcome.CurrentPrice − AvgCost)`
|
||||||
- [ ] Gesamt-PnL = realisiert + unrealisiert (ersetzt `OverallPnL`, `PnL30d/7d/24h` Felder in `TraderAnalytics`)
|
- [x] Gesamt-PnL = realisiert + unrealisiert (ersetzt `OverallPnL`, `PnL30d/7d/24h` Felder in `TraderAnalytics`)
|
||||||
- [ ] Prüfen, ob wir zusätzlich/alternativ Polymarkets eigenen `/positions`-Endpoint (`GetTraderPositionsAsync`, liefert `CurrentValue`/`PercentPnl`) als Plausibilitäts-Check oder sogar als primäre Quelle für **offene** Positionen nutzen (aktuell komplett ungenutzt)
|
- [x] Prüfen, ob wir zusätzlich/alternativ Polymarkets eigenen `/positions`-Endpoint (`GetTraderPositionsAsync`, liefert `CurrentValue`/`PercentPnl`) als Plausibilitäts-Check oder sogar als primäre Quelle für **offene** Positionen nutzen (aktuell komplett ungenutzt)
|
||||||
- [ ] `CalculatePnL`-Bug aus Abschnitt 0 im Zuge dessen mit erledigen
|
- [x] `CalculatePnL`-Bug aus Abschnitt 0 im Zuge dessen mit erledigen
|
||||||
|
|
||||||
### A2. Win-Rate korrekt berechnen
|
### A2. Win-Rate korrekt berechnen
|
||||||
- [ ] Win/Loss ist **pro Markt**, nicht pro Trade, definiert: ein Markt zählt als "Win", wenn der realisierte PnL aus diesem Markt (nach Redeem/vollständigem Exit) positiv ist
|
- [x] Win/Loss ist **pro Markt**, nicht pro Trade, definiert: ein Markt zählt als "Win", wenn der realisierte PnL aus diesem Markt (nach Redeem/vollständigem Exit) positiv ist
|
||||||
- [ ] `WinRate = Anzahl gewonnener Märkte / Anzahl abgeschlossener Märkte` (offene Positionen zählen nicht mit)
|
- [x] `WinRate = Anzahl gewonnener Märkte / Anzahl abgeschlossener Märkte` (offene Positionen zählen nicht mit)
|
||||||
- [ ] Placeholder `return 0;` in `TraderAnalyticsWorker.CalculateWinRate` ersetzen
|
- [x] Placeholder `return 0;` in `TraderAnalyticsWorker.CalculateWinRate` ersetzen
|
||||||
|
|
||||||
### A3. Deep-Dive-Kennzahlen mit echten Werten füllen
|
### A3. Deep-Dive-Kennzahlen mit echten Werten füllen
|
||||||
- [ ] **Voraussetzung klären:** Für Entry/Exit-Qualität und Timing-Accuracy brauchen wir eine **Preis-Historie** pro `MarketOutcome`, nicht nur den aktuellen Preis (`CurrentPrice`). Aktuell existiert keine Historisierung.
|
- [x] **Voraussetzung klären:** Für Entry/Exit-Qualität und Timing-Accuracy brauchen wir eine **Preis-Historie** pro `MarketOutcome`, nicht nur den aktuellen Preis (`CurrentPrice`). Aktuell existiert keine Historisierung.
|
||||||
- [ ] Prüfen, ob Polymarkets CLOB-API einen Preishistorie-Endpoint (`/prices-history`) hergibt, den wir zum Backfill nutzen können
|
- [x] Prüfen, ob Polymarkets CLOB-API einen Preishistorie-Endpoint (`/prices-history`) hergibt, den wir zum Backfill nutzen können
|
||||||
- [ ] Falls ja: neue Tabelle `MarketOutcomePriceSnapshot` (MarketOutcomeId, Timestamp, Price) einführen, periodisch befüllt (z.B. durch bestehenden `MarketSyncWorker`/`MarketHistoryWorker` erweitern)
|
- [x] Falls ja: neue Tabelle `MarketOutcomePriceSnapshot` (MarketOutcomeId, Timestamp, Price) einführen, periodisch befüllt (z.B. durch bestehenden `MarketSyncWorker`/`MarketHistoryWorker` erweitern)
|
||||||
- [ ] `AvgHoldDurationHours` echt berechnen: gewichtete Haltedauer zwischen Einstieg (Buy-Zeitpunkte, gewichtet nach Größe) und Ausstieg (Sell/Redeem) pro Position
|
- [x] `AvgHoldDurationHours` echt berechnen: gewichtete Haltedauer zwischen Einstieg (Buy-Zeitpunkte, gewichtet nach Größe) und Ausstieg (Sell/Redeem) pro Position
|
||||||
- [ ] `EntryQuality`: Einstiegspreis im Vergleich zur nachfolgenden Preisentwicklung (z.B. Perzentil des Einstiegspreises innerhalb eines Zeitfensters danach)
|
- [x] `EntryQuality`: Einstiegspreis im Vergleich zur nachfolgenden Preisentwicklung (z.B. Perzentil des Einstiegspreises innerhalb eines Zeitfensters danach)
|
||||||
- [ ] `ExitQuality`: analog für Ausstiegspreis
|
- [x] `ExitQuality`: analog für Ausstiegspreis
|
||||||
- [ ] `TimingAccuracy`: z.B. Anteil der Trades, die kurz vor einer für den Trader günstigen Preisbewegung platziert wurden
|
- [x] `TimingAccuracy`: z.B. Anteil der Trades, die kurz vor einer für den Trader günstigen Preisbewegung platziert wurden
|
||||||
- [ ] Hardcodierte `50`-Neutralwerte in `AnalyticsService.PerformDeepDive` entfernen
|
- [x] Hardcodierte `50`-Neutralwerte in `AnalyticsService.PerformDeepDive` entfernen
|
||||||
|
|
||||||
### A4. Strategie-Klassifikation verbessern
|
### A4. Strategie-Klassifikation verbessern
|
||||||
- [ ] Zusätzliche Signale einbeziehen statt nur "Ø-Größe" und "Hedging-Rate":
|
- [ ] Zusätzliche Signale einbeziehen statt nur "Ø-Größe" und "Hedging-Rate":
|
||||||
@@ -54,17 +100,17 @@ Beim Review ist aufgefallen, dass `TraderAnalyticsWorker.CalculatePnL` **jeden T
|
|||||||
- [ ] Sicherstellen, dass nicht der Großteil der Trader dauerhaft bei "Unknown" landet (aktuell strukturell der Fall)
|
- [ ] Sicherstellen, dass nicht der Großteil der Trader dauerhaft bei "Unknown" landet (aktuell strukturell der Fall)
|
||||||
|
|
||||||
### A5. Copytrading-Eignungs-Score (neu)
|
### A5. Copytrading-Eignungs-Score (neu)
|
||||||
- [ ] Neue Metrik-Dimension definieren, unabhängig vom bestehenden `PriorityScore`:
|
- [x] Neue Metrik-Dimension definieren, unabhängig vom bestehenden `PriorityScore`:
|
||||||
- [ ] **Liquiditäts-Fit**: durchschnittliche Positionsgröße im Verhältnis zur Marktliquidität/zum Volumen zum Handelszeitpunkt (Slippage-Risiko für Nachahmer)
|
- [x] **Liquiditäts-Fit**: durchschnittliche Positionsgröße im Verhältnis zur Marktliquidität/zum Volumen zum Handelszeitpunkt (Slippage-Risiko für Nachahmer)
|
||||||
- [ ] **Reaktionsfenster**: wie viel Zeit bliebe einem Copytrader realistisch zum Nachziehen (Trader mit Sekunden/Millisekunden-Kadenz sind nicht kopierbar)
|
- [x] **Reaktionsfenster**: wie viel Zeit bliebe einem Copytrader realistisch zum Nachziehen (Trader mit Sekunden/Millisekunden-Kadenz sind nicht kopierbar)
|
||||||
- [ ] **Frequenz/Konzentration**: sehr hochfrequente/bot-artige Trader senken den Score automatisch
|
- [x] **Frequenz/Konzentration**: sehr hochfrequente/bot-artige Trader senken den Score automatisch
|
||||||
- [ ] **Track-Record-Länge & Konsistenz**: mehr abgeschlossene Märkte mit konsistent positivem PnL = höheres Vertrauen
|
- [x] **Track-Record-Länge & Konsistenz**: mehr abgeschlossene Märkte mit konsistent positivem PnL = höheres Vertrauen
|
||||||
- [ ] Kombinierten `CopytradingScore` (0–100) berechnen und persistieren (neues Feld auf `TraderScore` oder eigene Entity `TraderCopytradingProfile`)
|
- [x] Kombinierten `CopytradingScore` (0–100) berechnen und persistieren (neues Feld auf `TraderScore` oder eigene Entity `TraderCopytradingProfile`)
|
||||||
- [ ] Im Dashboard/API sichtbar machen (getrennt von der bisherigen "Priorität", da unterschiedliche Fragestellung: *gut* vs. *kopierbar*)
|
- [x] Im Dashboard/API sichtbar machen (getrennt von der bisherigen "Priorität", da unterschiedliche Fragestellung: *gut* vs. *kopierbar*)
|
||||||
|
|
||||||
### A6. (siehe Abschnitt 0) TradeSide-Bugfix in PnL-Berechnung
|
### A6. (siehe Abschnitt 0) TradeSide-Bugfix in PnL-Berechnung
|
||||||
- [ ] Erledigt sich durch A1, hier nur als Häkchen zur Nachverfolgung
|
- [x] Erledigt sich durch A1, hier nur als Häkchen zur Nachverfolgung
|
||||||
- [ ] Kurzer Änderungsvermerk/Commit-Hinweis, damit klar ist, dass dieser Bug bewusst behoben wurde
|
- [x] Kurzer Änderungsvermerk/Commit-Hinweis, damit klar ist, dass dieser Bug bewusst behoben wurde
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -77,7 +123,7 @@ Beim Review ist aufgefallen, dass `TraderAnalyticsWorker.CalculatePnL` **jeden T
|
|||||||
- [x] Migration end-to-end gegen eine echte, frische DEV-Datenbank getestet (`lqf7.your-database.de`/`bergisnu_db0`, vom Nutzer bereitgestellt) — `dotnet ef database update` lief fehlerfrei durch, `dotnet ef migrations list` bestätigt sie als angewendet
|
- [x] Migration end-to-end gegen eine echte, frische DEV-Datenbank getestet (`lqf7.your-database.de`/`bergisnu_db0`, vom Nutzer bereitgestellt) — `dotnet ef database update` lief fehlerfrei durch, `dotnet ef migrations list` bestätigt sie als angewendet
|
||||||
- [x] `AppDbContextFactory` (Design-Time-Factory für `dotnet ef`) liest Zielverbindung jetzt aus Env-Vars (`PREDICTALYTICS_DB_SERVER/_NAME/_USER/_PASSWORD`, via `MySqlConnectionStringBuilder` statt roher String-Konkatenation, da Passwörter Sonderzeichen wie `;`/`{}` enthalten können) statt fest codiert — kein Secret mehr im Repo nötig, um Migrationen gegen eine beliebige DB zu fahren
|
- [x] `AppDbContextFactory` (Design-Time-Factory für `dotnet ef`) liest Zielverbindung jetzt aus Env-Vars (`PREDICTALYTICS_DB_SERVER/_NAME/_USER/_PASSWORD`, via `MySqlConnectionStringBuilder` statt roher String-Konkatenation, da Passwörter Sonderzeichen wie `;`/`{}` enthalten können) statt fest codiert — kein Secret mehr im Repo nötig, um Migrationen gegen eine beliebige DB zu fahren
|
||||||
- [x] ~~Live-Produktions-DB manuell als "bereits migriert" markieren~~ — vorerst zurückgestellt: Da wir laut Abschnitt D ohnehin einen kompletten Neustart der Produktions-DB planen (Altdaten sind jederzeit nachladbar, siehe C0), richten wir die neue Produktions-DB am Ende genauso ein wie die DEV-DB (leer anlegen + `dotnet ef database update`) — der fummelige Stamp-Schritt auf die bestehende Live-DB entfällt dann komplett. Nur falls wir uns doch gegen den Neustart entscheiden, müsste dieser Schritt nachgeholt werden
|
- [x] ~~Live-Produktions-DB manuell als "bereits migriert" markieren~~ — vorerst zurückgestellt: Da wir laut Abschnitt D ohnehin einen kompletten Neustart der Produktions-DB planen (Altdaten sind jederzeit nachladbar, siehe C0), richten wir die neue Produktions-DB am Ende genauso ein wie die DEV-DB (leer anlegen + `dotnet ef database update`) — der fummelige Stamp-Schritt auf die bestehende Live-DB entfällt dann komplett. Nur falls wir uns doch gegen den Neustart entscheiden, müsste dieser Schritt nachgeholt werden
|
||||||
- [ ] Alle künftigen Schemaänderungen aus Phase 1 (z.B. `TraderPosition`, `MarketOutcomePriceSnapshot`, `TraderCopytradingProfile`) als reguläre Migrationen anlegen — direkt gegen die DEV-DB entwickeln/testen, da wir dort laut Nutzer frei experimentieren dürfen
|
- [x] Alle künftigen Schemaänderungen aus Phase 1 (z.B. `TraderPosition`, `MarketOutcomePriceSnapshot`, `TraderCopytradingProfile`) als reguläre Migrationen anlegen — direkt gegen die DEV-DB entwickeln/testen, da wir dort laut Nutzer frei experimentieren dürfen
|
||||||
|
|
||||||
### B2. Secrets-Management (Release-Vorbereitung, kein akuter Risikofall)
|
### B2. Secrets-Management (Release-Vorbereitung, kein akuter Risikofall)
|
||||||
> Software läuft aktuell nur lokal, kein Fremdzugriff — Passwort bleibt vorerst wie es ist, keine Rotation nötig. Dieser Punkt ist reine **Vorbereitung**, damit das Projekt bei Bedarf später auch ohne die aktuellen Secrets veröffentlicht/geteilt werden könnte, ohne den Code nochmal anfassen zu müssen. Dadurch niedrigere Priorität als vorher angenommen — kann später in der Reihenfolge stehen.
|
> Software läuft aktuell nur lokal, kein Fremdzugriff — Passwort bleibt vorerst wie es ist, keine Rotation nötig. Dieser Punkt ist reine **Vorbereitung**, damit das Projekt bei Bedarf später auch ohne die aktuellen Secrets veröffentlicht/geteilt werden könnte, ohne den Code nochmal anfassen zu müssen. Dadurch niedrigere Priorität als vorher angenommen — kann später in der Reihenfolge stehen.
|
||||||
@@ -86,27 +132,27 @@ Beim Review ist aufgefallen, dass `TraderAnalyticsWorker.CalculatePnL` **jeden T
|
|||||||
- [ ] Keine Passwort-Rotation nötig, solange rein lokale Nutzung
|
- [ ] Keine Passwort-Rotation nötig, solange rein lokale Nutzung
|
||||||
|
|
||||||
### B3. Scoring-Pipeline entkoppeln
|
### B3. Scoring-Pipeline entkoppeln
|
||||||
- [ ] `PollingWorker` soll **nicht** bei jedem 60-Sekunden-Zyklus alle Trader neu bewerten
|
- [x] `PollingWorker` soll **nicht** bei jedem 60-Sekunden-Zyklus alle Trader neu bewerten
|
||||||
- [ ] Nur Trader neu bewerten, die seit letzter Berechnung neue Trades bekommen haben (Dirty-Flag oder Vergleich `LastTradesUpdatedAt` vs. `TraderScore.CalculatedAt`)
|
- [x] Nur Trader neu bewerten, die seit letzter Berechnung neue Trades bekommen haben (Dirty-Flag oder Vergleich `LastTradesUpdatedAt` vs. `TraderScore.CalculatedAt`)
|
||||||
- [ ] N+1-Datenbankzugriffe in `ScoringService.RecalculateAllScoresAsync` durch Batch-Queries ersetzen
|
- [x] N+1-Datenbankzugriffe in `ScoringService.RecalculateAllScoresAsync` durch Batch-Queries ersetzen
|
||||||
- [ ] Eigenes Intervall für volle Neuberechnung/Ranking (z.B. alle 15 Min) getrennt vom reinen Trade-Polling (60s)
|
- [x] Eigenes Intervall für volle Neuberechnung/Ranking (z.B. alle 15 Min) getrennt vom reinen Trade-Polling (60s)
|
||||||
|
|
||||||
### B6. Weitere gefundene Bugs/Performance-Probleme (Review-Pass 2026-07-01)
|
### B6. Weitere gefundene Bugs/Performance-Probleme (Review-Pass 2026-07-01)
|
||||||
- [ ] **`MarketSyncWorker` re-synct alle 30 Minuten ALLE Märkte, inkl. `includeClosed=true`, von Offset 0** ([MarketSyncWorker.cs](src/Predictalytics.Worker/Services/MarketSyncWorker.cs)) — holt damit bei jedem Zyklus jeden jemals geschlossenen Polymarket-Markt erneut komplett durch. Wächst unbegrenzt mit der Zeit, echtes Risiko für Rate-Limiting/Sperrung. Sollte auf: aktive Märkte häufig, geschlossene Märkte selten/inkrementell (z.B. nur kürzlich geschlossene, nicht der komplette Bestand) umgestellt werden
|
- [x] **`MarketSyncWorker` re-synct alle 30 Minuten ALLE Märkte, inkl. `includeClosed=true`, von Offset 0** ([MarketSyncWorker.cs](src/Predictalytics.Worker/Services/MarketSyncWorker.cs)) — holt damit bei jedem Zyklus jeden jemals geschlossenen Polymarket-Markt erneut komplett durch. Wächst unbegrenzt mit der Zeit, echtes Risiko für Rate-Limiting/Sperrung. Sollte auf: aktive Märkte häufig, geschlossene Märkte selten/inkrementell (z.B. nur kürzlich geschlossene, nicht der komplette Bestand) umgestellt werden
|
||||||
- [ ] **Watchlisted Trader nicht von Auto-Löschung ausgeschlossen** (`TraderRepository.GetTradersForCleanupAsync` / `TraderCleanupWorker`) — da `Trades`/`TraderScore`/`WatchlistEntries` per Cascade am Trader hängen, könnte ein manuell beobachteter Trader nach 1 Jahr Inaktivität (oder bei kurzzeitigem API-Fehler) unbemerkt komplett gelöscht werden. Watchlist-Einträge sollten von der Cleanup-Query ausgenommen werden
|
- [x] **Watchlisted Trader nicht von Auto-Löschung ausgeschlossen** (`TraderRepository.GetTradersForCleanupAsync` / `TraderCleanupWorker`) — da `Trades`/`TraderScore`/`WatchlistEntries` per Cascade am Trader hängen, könnte ein manuell beobachteter Trader nach 1 Jahr Inaktivität (oder bei kurzzeitigem API-Fehler) unbemerkt komplett gelöscht werden. Watchlist-Einträge sollten von der Cleanup-Query ausgenommen werden
|
||||||
- [ ] **`TradeRepository.GetKnownPlatformTradeIdsAsync` lädt die komplette Trade-ID-Historie eines Traders ins RAM**, nur um eine kleine neu geholte Charge (~100-1000 Trades) zu deduplizieren — aufgerufen alle 60s (`PollingWorker`) bzw. alle 12h (`TradeHistoryWorker`) pro Trader. Wird mit wachsender Trade-Zahl immer teurer. Fix: nur `WHERE PlatformTradeId IN (<geholte Batch-IDs>)` abfragen statt der gesamten Historie
|
- [x] **`TradeRepository.GetKnownPlatformTradeIdsAsync` lädt die komplette Trade-ID-Historie eines Traders ins RAM**, nur um eine kleine neu geholte Charge (~100-1000 Trades) zu deduplizieren — aufgerufen alle 60s (`PollingWorker`) bzw. alle 12h (`TradeHistoryWorker`) pro Trader. Wird mit wachsender Trade-Zahl immer teurer. Fix: nur `WHERE PlatformTradeId IN (<geholte Batch-IDs>)` abfragen statt der gesamten Historie
|
||||||
- [ ] **Race Condition in `MarketRepository.AddOrUpdateAsync`** (im Gegensatz zu `AddOrUpdateRangeAsync` ohne Locking) — `TradeHistoryWorker` verarbeitet bis zu 5 Trader parallel (`Parallel.ForEachAsync`); referenzieren zwei gleichzeitig denselben noch unbekannten Markt, prüfen beide unabhängig "existiert nicht" und einer crasht beim `Add` mit Unique-Constraint-Verletzung (wird geloggt, Trader-Sync für den Zyklus bricht ab, nächster Zyklus heilt es meist). Fix: gleiches Locking-Muster wie `AddOrUpdateRangeAsync` verwenden, oder Insert-Konflikt sauber abfangen/retry
|
- [x] **Race Condition in `MarketRepository.AddOrUpdateAsync`** (im Gegensatz zu `AddOrUpdateRangeAsync` ohne Locking) — `TradeHistoryWorker` verarbeitet bis zu 5 Trader parallel (`Parallel.ForEachAsync`); referenzieren zwei gleichzeitig denselben noch unbekannten Markt, prüfen beide unabhängig "existiert nicht" und einer crasht beim `Add` mit Unique-Constraint-Verletzung (wird geloggt, Trader-Sync für den Zyklus bricht ab, nächster Zyklus heilt es meist). Fix: gleiches Locking-Muster wie `AddOrUpdateRangeAsync` verwenden, oder Insert-Konflikt sauber abfangen/retry
|
||||||
- [ ] **`TradeHistoryWorker` bricht die Trade-Verarbeitung beim ersten bekannten Trade ab** (`if (!isInitial) break;`), verlässt sich also darauf, dass die API immer streng neueste-zuerst liefert. `PollingWorker`s äquivalente Schleife macht das NICHT. Sollte angeglichen werden — der Performance-Gewinn ist gering gegenüber dem Risiko einer stillen Datenlücke, falls die Annahme mal nicht stimmt
|
- [x] **`TradeHistoryWorker` bricht die Trade-Verarbeitung beim ersten bekannten Trade ab** (`if (!isInitial) break;`), verlässt sich also darauf, dass die API immer streng neueste-zuerst liefert. `PollingWorker`s äquivalente Schleife macht das NICHT. Sollte angeglichen werden — der Performance-Gewinn ist gering gegenüber dem Risiko einer stillen Datenlücke, falls die Annahme mal nicht stimmt
|
||||||
|
|
||||||
### B4. Testabdeckung für die kritische Logik
|
### B4. Testabdeckung für die kritische Logik
|
||||||
- [ ] Neues Testprojekt (z.B. `Predictalytics.Application.Tests`) anlegen — aktuell existiert **kein einziges** Testprojekt
|
- [x] Neues Testprojekt (z.B. `Predictalytics.Application.Tests`) anlegen — aktuell existiert **kein einziges** Testprojekt
|
||||||
- [ ] Unit-Tests für die neue PnL-Engine (A1) — insbesondere Grenzfälle: nur offene Position, nur geschlossene Position, Split/Merge, Redeem-Verlust vs. -Gewinn
|
- [x] Unit-Tests für die neue PnL-Engine (A1) — insbesondere Grenzfälle: nur offene Position, nur geschlossene Position, Split/Merge, Redeem-Verlust vs. -Gewinn
|
||||||
- [ ] Unit-Tests für Win-Rate (A2)
|
- [x] Unit-Tests für Win-Rate (A2)
|
||||||
- [ ] Unit-Tests für Deep-Dive-Kennzahlen (A3) und Copytrading-Score (A5)
|
- [x] Unit-Tests für Deep-Dive-Kennzahlen (A3) und Copytrading-Score (A5)
|
||||||
- [ ] Diese Tests idealerweise **parallel zu A1–A5** schreiben, nicht erst am Ende nachziehen
|
- [x] Diese Tests idealerweise **parallel zu A1–A5** schreiben, nicht erst am Ende nachziehen
|
||||||
|
|
||||||
### B5. Kleine verwandte Aufräumarbeit
|
### B5. Kleine verwandte Aufräumarbeit
|
||||||
- [ ] Log-Rotation/Retention der Serilog-Datei-Sinks (`WinFormsHost/logs`) prüfen — wächst potenziell unbegrenzt, ähnliches Prinzip wie die DB-Speicherplatzfrage unten
|
- [x] Log-Rotation/Retention der Serilog-Datei-Sinks (`WinFormsHost/logs`) prüfen — wächst potenziell unbegrenzt, ähnliches Prinzip wie die DB-Speicherplatzfrage unten
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -129,20 +175,20 @@ Beim Review ist aufgefallen, dass `TraderAnalyticsWorker.CalculatePnL` **jeden T
|
|||||||
→ Vorschlag: **Burst-Erkennung statt starrer Zeit-Buckets** (nur tatsächlich dichte Trade-Sequenzen kompaktieren) und **Aggregation getrennt nach Trader+Markt/Outcome+Seite** mit **VWAP** (mengengewichteter Durchschnittspreis) statt einfachem Durchschnitt — bleibt unter der Average-Cost-Methode aus A1 nahezu verlustfrei für die PnL-Berechnung.
|
→ Vorschlag: **Burst-Erkennung statt starrer Zeit-Buckets** (nur tatsächlich dichte Trade-Sequenzen kompaktieren) und **Aggregation getrennt nach Trader+Markt/Outcome+Seite** mit **VWAP** (mengengewichteter Durchschnittspreis) statt einfachem Durchschnitt — bleibt unter der Average-Cost-Methode aus A1 nahezu verlustfrei für die PnL-Berechnung.
|
||||||
|
|
||||||
### C1. Rollierendes Zeitfenster statt Archivierung (ersetzt die alte "Idee 1")
|
### C1. Rollierendes Zeitfenster statt Archivierung (ersetzt die alte "Idee 1")
|
||||||
- [ ] Konfigurierbares Retention-Fenster einführen (Default-Vorschlag: 3–6 Monate) — gilt **für alle Trader gleichermaßen**, nicht nur für inaktive. Länge sollte sich daran orientieren, wie weit die Deep-Dive-/Strategie-Analyse (A3/A4) tatsächlich zurückschaut, um "aktuelle Strategie" zu charakterisieren
|
- [x] Konfigurierbares Retention-Fenster einführen (Default-Vorschlag: 3–6 Monate) — gilt **für alle Trader gleichermaßen**, nicht nur für inaktive. Länge sollte sich daran orientieren, wie weit die Deep-Dive-/Strategie-Analyse (A3/A4) tatsächlich zurückschaut, um "aktuelle Strategie" zu charakterisieren
|
||||||
- [ ] Reihenfolge pro Trade zwingend einhalten: **erst** in `TraderPosition`/Monats-Aggregat (s.u.) einrechnen und das sicher persistieren, **dann erst** die Rohzeile löschen — rein zeitbasiert, unabhängig davon ob die betroffene Position noch offen oder schon geschlossen ist
|
- [x] Reihenfolge pro Trade zwingend einhalten: **erst** in `TraderPosition`/Monats-Aggregat (s.u.) einrechnen und das sicher persistieren, **dann erst** die Rohzeile löschen — rein zeitbasiert, unabhängig davon ob die betroffene Position noch offen oder schon geschlossen ist
|
||||||
- [ ] Kein Export/Cold-Storage nötig, da jederzeit über die Polymarket-API bzw. im Zweifel über die Blockchain nachladbar — vereinfacht C1 gegenüber der ursprünglichen Idee erheblich (keine Baseline-Felder, keine Reaktivierungs-Sonderfälle)
|
- [x] Kein Export/Cold-Storage nötig, da jederzeit über die Polymarket-API bzw. im Zweifel über die Blockchain nachladbar — vereinfacht C1 gegenüber der ursprünglichen Idee erheblich (keine Baseline-Felder, keine Reaktivierungs-Sonderfälle)
|
||||||
- [ ] Optionales, leichtgewichtiges Langzeit-Signal (nice-to-have, niedrige Priorität): ein grobes Monats-Aggregat pro Trader (Monat, realisierter PnL, Trade-Anzahl, Volumen) für einen "war er über Monate hinweg konsistent profitabel"-Trend im Copytrading-Score (A5) — **ohne** Trade-Detailtiefe, nur wenige Kennzahlen pro Monat
|
- [x] Optionales, leichtgewichtiges Langzeit-Signal (nice-to-have, niedrige Priorität): ein grobes Monats-Aggregat pro Trader (Monat, realisierter PnL, Trade-Anzahl, Volumen) für einen "war er über Monate hinweg konsistent profitabel"-Trend im Copytrading-Score (A5) — **ohne** Trade-Detailtiefe, nur wenige Kennzahlen pro Monat
|
||||||
- [ ] Neuer periodischer Cleanup-Job (`TradeRetentionWorker`), der Trades außerhalb des Fensters findet und löscht, nachdem die Voraussetzung (Position/Aggregat aktuell) erfüllt ist
|
- [x] Neuer periodischer Cleanup-Job (`TradeRetentionWorker`), der Trades außerhalb des Fensters findet und löscht, nachdem die Voraussetzung (Position/Aggregat aktuell) erfüllt ist
|
||||||
- [ ] Kurzer Stichprobentest, wie weit `/activity` pro Wallet tatsächlich zurückreicht (bestätigt/verifiziert nur die schon vorliegende Doku-Aussage, geringe Priorität da schon durch Nutzer-Recherche plausibilisiert)
|
- [ ] Kurzer Stichprobentest, wie weit `/activity` pro Wallet tatsächlich zurückreicht (bestätigt/verifiziert nur die schon vorliegende Doku-Aussage, geringe Priorität da schon durch Nutzer-Recherche plausibilisiert)
|
||||||
|
|
||||||
### C2. Kompaktierung von Hochfrequenz-Tradern innerhalb des Zeitfensters (überarbeitete "Idee 2")
|
### C2. Kompaktierung von Hochfrequenz-Tradern innerhalb des Zeitfensters (überarbeitete "Idee 2")
|
||||||
- [ ] Burst-Erkennung statt globalem Zeitraster: Sequenz von Trades mit Abstand kleiner als Schwellwert (z.B. 60s, konfigurierbar) zwischen aufeinanderfolgenden Trades **desselben Traders, Outcomes und derselben Seite (Buy/Sell)** gilt als "Burst"
|
- [x] Burst-Erkennung statt globalem Zeitraster: Sequenz von Trades mit Abstand kleiner als Schwellwert (z.B. 60s, konfigurierbar) zwischen aufeinanderfolgenden Trades **desselben Traders, Outcomes und derselben Seite (Buy/Sell)** gilt als "Burst"
|
||||||
- [ ] Mindestlänge für Kompaktierung festlegen (z.B. erst ab 20+ Trades im Burst lohnt sich das)
|
- [x] Mindestlänge für Kompaktierung festlegen (z.B. erst ab 20+ Trades im Burst lohnt sich das)
|
||||||
- [ ] Bestehende Bot-Heuristik (`intervals.Average() < 10` in `AnalyticsService`) als Ausgangspunkt wiederverwenden/verallgemeinern statt eine zweite, unabhängige Definition einzuführen
|
- [x] Bestehende Bot-Heuristik (`intervals.Average() < 10` in `AnalyticsService`) als Ausgangspunkt wiederverwenden/verallgemeinern statt eine zweite, unabhängige Definition einzuführen
|
||||||
- [ ] Aggregat-Datensatz pro Burst: Anzahl Trades, Summe Size, Summe Amount, Min-Preis, Max-Preis, **VWAP** (nicht einfacher Durchschnitt)
|
- [x] Aggregat-Datensatz pro Burst: Anzahl Trades, Summe Size, Summe Amount, Min-Preis, Max-Preis, **VWAP** (nicht einfacher Durchschnitt)
|
||||||
- [ ] Design-Entscheidung: Aggregat als zusätzliche nullable Spalten auf der bestehenden `Trade`-Tabelle (`AggregateCount`, `AggregateMinPrice`, `AggregateMaxPrice`) statt separater Tabelle — bestehender Code (PnL, Deep-Dive) muss dadurch kaum angepasst werden, ein Aggregat-Datensatz ist einfach ein "Trade" mit `Size = Summe`, `Price = VWAP`
|
- [x] Design-Entscheidung: Aggregat als zusätzliche nullable Spalten auf der bestehenden `Trade`-Tabelle (`AggregateCount`, `AggregateMinPrice`, `AggregateMaxPrice`) statt separater Tabelle — bestehender Code (PnL, Deep-Dive) muss dadurch kaum angepasst werden, ein Aggregat-Datensatz ist einfach ein "Trade" mit `Size = Summe`, `Price = VWAP`
|
||||||
- [ ] **Reihenfolge beachten:** Diese Kompaktierung erst implementieren, nachdem A1 (neue PnL-Engine) steht und validiert ist — sonst kompaktieren wir Daten weg, bevor wir wissen, was die neue Engine wirklich braucht
|
- [x] **Reihenfolge beachten:** Diese Kompaktierung erst implementieren, nachdem A1 (neue PnL-Engine) steht und validiert ist — sonst kompaktieren wir Daten weg, bevor wir wissen, was die neue Engine wirklich braucht
|
||||||
|
|
||||||
### C3. Weitere eigene Vorschläge
|
### C3. Weitere eigene Vorschläge
|
||||||
- [ ] **Spaltentypen verkleinern**: `TransactionHash`, `MarketId`, `AssetId` sind Hex-/Dezimal-Strings fester Länge (z.B. `0x` + 64 Hex-Zeichen) — als `BINARY(32)` statt `VARCHAR(66/80)` speichern spart ca. 30–50% Platz auf diesen stark indizierten Spalten und ist schneller
|
- [ ] **Spaltentypen verkleinern**: `TransactionHash`, `MarketId`, `AssetId` sind Hex-/Dezimal-Strings fester Länge (z.B. `0x` + 64 Hex-Zeichen) — als `BINARY(32)` statt `VARCHAR(66/80)` speichern spart ca. 30–50% Platz auf diesen stark indizierten Spalten und ist schneller
|
||||||
@@ -153,7 +199,7 @@ Beim Review ist aufgefallen, dass `TraderAnalyticsWorker.CalculatePnL` **jeden T
|
|||||||
- [ ] `Split`/`Merge`/`AddLiquidity`/`RemoveLiquidity`-Ereignisse (siehe A1) ggf. separat und kompakter ablegen, da sie für die Trader-Bewertung meist weniger relevant sind als `Buy`/`Sell`
|
- [ ] `Split`/`Merge`/`AddLiquidity`/`RemoveLiquidity`-Ereignisse (siehe A1) ggf. separat und kompakter ablegen, da sie für die Trader-Bewertung meist weniger relevant sind als `Buy`/`Sell`
|
||||||
|
|
||||||
### C4. Abhängigkeit zu Phase 1
|
### C4. Abhängigkeit zu Phase 1
|
||||||
- [ ] Merksatz: **Erst A1 (neue PnL-Engine) fertigstellen, dann C1/C2 umsetzen.** Sonst laufen wir Gefahr, Rohdaten wegzuoptimieren, die die neue Engine noch gebraucht hätte.
|
- [x] Merksatz: **Erst A1 (neue PnL-Engine) fertigstellen, dann C1/C2 umsetzen.** Sonst laufen wir Gefahr, Rohdaten wegzuoptimieren, die die neue Engine noch gebraucht hätte.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -534,13 +534,14 @@ Predictalytics.Shell --license-deactivate # Aktivierungsplatz freigeben
|
|||||||
Die Schalter werden **vor** jeder Avalonia-Initialisierung ausgewertet, wie `--headless`.
|
Die Schalter werden **vor** jeder Avalonia-Initialisierung ausgewertet, wie `--headless`.
|
||||||
Der Schlüssel wird in der Ausgabe maskiert (`LLAB3-*****-*****-*****-AABB2`).
|
Der Schlüssel wird in der Ausgabe maskiert (`LLAB3-*****-*****-*****-AABB2`).
|
||||||
|
|
||||||
### ⚠️ Offen: Watchdog läuft noch auf dem alten System
|
### ✅ Nachgetragen: Watchdog ist migriert (Stand 2026-08-23)
|
||||||
|
|
||||||
Das Deploymentcenter bündelt License, Watchdog, UpdateService und Bugtracker.
|
Der hier notierte Rest — `WatchdogHeartbeatService` gegen `watchdog.mhdf.de` mit dem
|
||||||
`WatchdogHeartbeatService` zeigt weiterhin auf `watchdog.mhdf.de` mit dem Header
|
Header `X-Watchdog-Key` — ist erledigt. An seiner Stelle steht
|
||||||
`X-Watchdog-Key`. Das funktioniert, ist aber dieselbe Altlast wie eben beim Lizenzsystem.
|
`Hosting/DcHeartbeatService.cs`, das gegen `POST /api/watchdog/v1/ping` am
|
||||||
Migration nach `dc.mhdf.de/api/watchdog/v1` als eigener Schritt einplanen —
|
Deployment Center meldet (`:105,143`). Damit laufen Lizenz, Watchdog, UpdateService,
|
||||||
Leitfaden liegt unter `Deploymentcenter/docs/WATCHDOG_INTEGRATION_GUIDE.md`.
|
Fehler-Stream und Bugtracker über dieselbe Anbindung; `watchdog.mhdf.de` wird
|
||||||
|
nicht mehr angesprochen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -679,12 +680,16 @@ Nur relevant, falls Egress-Kanäle produktiv genutzt werden. Proxy-Kanäle sind
|
|||||||
* systemd-Unit bzw. Dockerfile (für den Headless-Modus); es existiert bereits ein
|
* systemd-Unit bzw. Dockerfile (für den Headless-Modus); es existiert bereits ein
|
||||||
`J:\Softwareprojekte\dockerCompose` — vermutlich anknüpfbar.
|
`J:\Softwareprojekte\dockerCompose` — vermutlich anknüpfbar.
|
||||||
* Reverse Proxy (nginx/Caddy) mit TLS vor Kestrel.
|
* Reverse Proxy (nginx/Caddy) mit TLS vor Kestrel.
|
||||||
* **Secrets:** DB-Passwort und Watchdog-API-Key liegen heute im Klartext in `settings.json`.
|
* **Secrets:** DB-Passwort und Deployment-Center-Token liegen im Klartext in
|
||||||
Unter Linux → EnvironmentFile mit `0600` oder Secret-Store.
|
`settings.json` (nicht versioniert). Unter Linux → EnvironmentFile mit `0600` oder
|
||||||
*Randnotiz:* In `LicenseGuard.cs:16-17` stehen Basic-Auth-Zugangsdaten im Quelltext —
|
Secret-Store.
|
||||||
unabhängig von dieser Portierung überdenkenswert, erst recht mit Blick auf Monetarisierung.
|
*Erledigt:* Die früher hier genannten Basic-Auth-Zugangsdaten in `LicenseGuard.cs`
|
||||||
|
gibt es nicht mehr — die Datei läuft vollständig über den `Deploymentcenter.Client`.
|
||||||
|
*Offen und dringlicher:* In `src/Predictalytics.Api/appsettings.json` steht ein
|
||||||
|
**echter OpenRouter-API-Key im Klartext**, und diese Datei **ist** versioniert.
|
||||||
|
Schlüssel zurückziehen, neu ausstellen, künftig über `OpenRouter__ApiKey` setzen.
|
||||||
* CI: Build + Test auf Linux **und** Windows.
|
* CI: Build + Test auf Linux **und** Windows.
|
||||||
* `docs/BETRIEB-Watchdog-Lizenz.md` um den Linux-Betrieb ergänzen.
|
* `docs/BETRIEB-Deploymentcenter.md` um den Linux-Betrieb ergänzen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -742,12 +747,20 @@ auf der vorigen auf; einzig AP 5 (Deployment) kann parallel zu Phase 3 vorbereit
|
|||||||
|
|
||||||
## 11. Nächster Schritt
|
## 11. Nächster Schritt
|
||||||
|
|
||||||
Alle Vorentscheidungen sind getroffen (Abschnitt 0). Umsetzung kann beginnen:
|
**Stand 2026-08-23: Phase 0 bis 4 sind abgeschlossen** (jeweils oben mit ✅ vermerkt),
|
||||||
|
ebenso der Zusatz 5a (Deploymentcenter-Lizenzsystem) und 5b (WinForms-Host abgelöst).
|
||||||
|
Der ursprüngliche Text an dieser Stelle empfahl noch, mit Phase 0 zu beginnen — das ist
|
||||||
|
überholt und deshalb ersetzt.
|
||||||
|
|
||||||
1. **Phase 0** — 0,5 PT, sofort, unabhängig vom Rest. Ergebnis ist ein aufgeräumter
|
**Offen sind Phase 5 und Phase 6:**
|
||||||
Ausgangspunkt auf `feature/linux-net10`.
|
|
||||||
2. **Phase 1** — .NET 10 + Paketmatrix. Hier zeigt sich früh, wie groß der
|
1. **Phase 5 — Deployment und CI** (Abschnitt 7). Nichts davon ist bisher angefasst: kein
|
||||||
Swashbuckle-Umbau tatsächlich ist.
|
`dotnet publish -r linux-x64`, keine systemd-Unit, kein Dockerfile, kein Reverse Proxy,
|
||||||
3. Parallel dazu: **Lizenzaktivierung ohne Dialog** mit der laufenden HardwareID-Arbeit
|
keine CI. Das ist der eigentliche verbleibende Arbeitsblock.
|
||||||
abstimmen — das ist der einzige Punkt im Plan, der von außen abhängt und dadurch spät
|
2. **Phase 6 — Verifikation** (Abschnitt 8, Punkte 8.1–8.9). Der Dauerlauf unter Linux
|
||||||
teuer werden kann.
|
(8.1) und die Prüfung unter `LANG=de_DE.UTF-8` (8.3) stehen aus. **Bisher wurde die
|
||||||
|
Anwendung noch auf keinem Linux-System ausgeführt** — die Portierung ist zwar
|
||||||
|
vollständig gebaut, aber unter Linux unbewiesen.
|
||||||
|
3. Unabhängig davon und vorgelagert: den **OpenRouter-API-Key** aus
|
||||||
|
`src/Predictalytics.Api/appsettings.json` zurückziehen und neu ausstellen (siehe
|
||||||
|
Abschnitt 7, Secrets).
|
||||||
|
|||||||
Reference in New Issue
Block a user