Eine Roadmap statt neun Plandokumente; alte Plaene ins Archiv
Die offenen Punkte lagen ueber neun Dokumente verstreut, teils widersprechend, teils mit Punkten, die laengst umgesetzt waren. ROADMAP.md fuehrt sie zusammen: sechs Stufen, jeder Punkt gegen den Code geprueft. Markierung ueber eine Legende, damit Konzepte nicht mit Aufgaben verwechselt werden: dringend, eingeplant, Backlog, Konzept (durchdacht, aber bewusst nicht eingeplant), liegt beim Nutzer, verworfen. Stufen: 0 Sofort (OpenRouter-Key) - 1 Aufraeumen abschliessen (Merge nach main, Nullable-Warnungen, Startup-Backfill) - 2 Portierung abschliessen (Linux-Erstlauf und Verifikation, dann Deployment/CI) - 3 Analytik schaerfen (Strategie- Klassifikation, Backtest-Harness, Track-Record im Score, Ranking) - 4 Daten- haushalt (SQL des Nutzers) - 5 Ingest skalieren - 6 Monetarisierung vorbereiten. Ein Anhang haelt fest, was geprueft und bewusst NICHT auf die Roadmap kam, damit es nicht versehentlich wieder als Aufgabe auftaucht (Azuro/Limitless, die Marketing-Seiten, Kaltarchiv, EF Core 10). Archiv: FIXPLAN-DONE, FIXPLAN-G-Speicher, FIXPLAN-TODO, FIXPLAN-UI-Ranglisten, UMSETZUNGSPLAN sowie ANALYSE-Linux-Portierung, PLAN-Linux-Portierung, PLAN-Architektur-WebUI-Backend und PLAN-DatenIngest-Skalierung liegen jetzt unter docs/archiv/ mit einer README, die jedes Dokument einordnet. Sie bleiben als Begruendungs- und Detailquelle - die Roadmap nennt jeden Punkt knapp, die Herleitung steht dort. Dabei aufgefallen: die beiden nie gepflegten Plaene (Architektur, DatenIngest) zeigten 40 offene Punkte, von denen die Haelfte umgesetzt war - Read/Control- Split, /api/capabilities, CORS-Whitelist, Egress-Kanaele mit Cooldown und Per-Kanal-Limiter, Ingest-Tiering. Nachgeprueft, abgehakt und mit Statusblock eingeordnet, sonst waere das Archiv selbst eine Fehlerquelle. Was dabei nur teilweise umgesetzt war, ist als Roadmap 5.3 aufgenommen (Health-Statistik je Kanal, multi-homed pruefen, DB-Guard, Plausibilitaets- pruefung), ebenso der nie durchgefuehrte Audit auf versteckte Writes in Read-Endpunkten (6.1). Header-Rotation ist als verworfen markiert statt offen zu bleiben: Verschleierung gegenueber Polymarket riskiert genau den Zugang, auf dem das Projekt aufsetzt. STATUS.md beschreibt jetzt nur noch den Ist-Stand und verweist fuer die offenen Punkte auf die Roadmap, damit nichts doppelt gepflegt wird. CLAUDE.md nennt beide Einstiege. Build gruen, 126 Tests gruen, keine toten Links. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
# Fix- und Datenreparatur-Plan (Stand 2026-07-09, Übergabe an Gemini)
|
||||
|
||||
> **Status 2026-08-23: Teil D und Teil F sind umgesetzt**, nachgeprüft gegen den Code.
|
||||
> Offen sind nur noch drei Detailpunkte, jeweils unten als `- [ ]` stehengeblieben:
|
||||
> der gezielte Nachlade-Schritt in F5 und die zwei Tests aus F6 (Event-Tag-Vererbung,
|
||||
> 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
|
||||
|
||||
Die Engine-Fixes vom 09.07. sind korrekt (Tests grün). Die im WebUI sichtbaren Probleme haben drei andere Ursachen:
|
||||
|
||||
1. Die Buttons der **Trader-Detailseite** nutzen alte, Job-lose Endpoints (die Listen-Buttons nutzen bereits das Job-System).
|
||||
2. Die **abgeleiteten Daten in der DB stammen aus der Bug-Ära** (Snapshots/Positionen wurden von den alten, fehlerhaften
|
||||
Engine-Versionen berechnet). Beispiel aus dem Live-System: `PnL30d = 244,0K` bei `TotalPnL = 158,5K`, weil der
|
||||
Basis-Snapshot `-85,5K` enthält (korrupter Altwert). Kein Code-Fix ändert das — die Daten müssen einmalig repariert werden.
|
||||
3. **Deadlocks + Shutdown-Fehlerkaskaden** in den Workern (unbatchtes Reconciliation-UPDATE, fehlende Cancellation-Behandlung).
|
||||
|
||||
**Ein DB-Reset ist NICHT nötig.** Die Rohdaten (`Trades`) sind größtenteils intakt; Positionen, Analytics, Snapshots und
|
||||
Scores sind abgeleitet und lokal neu berechenbar. Nur Trader, deren Alt-Trades die Retention bereits gelöscht/kompaktiert
|
||||
hat, brauchen einen gezielten API-Re-Import (kleine Teilmenge, siehe Teil B).
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Teil D — Ausbaustufe: Merkmals-Tags & HF-Trader-Tiering (ergänzt 2026-07-11)
|
||||
|
||||
> **Bereits direkt erledigt (nicht Teil dieses Auftrags):** `TotalTrades` wird jetzt von der Engine aus dem
|
||||
> echten Row-Count gesetzt; der Kategorie-Mapper klassifiziert zusätzlich über den Frage-Text und matcht kurze
|
||||
> Tokens nur an Wortgrenzen; `UpdateMarketFields` überschreibt gute Kategorien nicht mehr mit "Other".
|
||||
> Teststand: **32 grün + 1 Skip** — das ist die neue Basis, Assertions unverändert lassen.
|
||||
|
||||
### D3. Trader-Tiering (`IngestMode`) — Umgang mit Ultra-HF-Tradern (RN1, Swisstony)
|
||||
|
||||
**Hintergrund:** Ultra-HF-Trader werden heute schon NICHT vollständig erfasst (PollingWorker: 100 Trades/60 s
|
||||
gegen 300+/min) — das Trade-Replay-PnL ist für diese Klasse bereits falsch und frisst nur Speicher.
|
||||
|
||||
- Enum `IngestMode { Full = 0, Aggregated = 1, SnapshotOnly = 2 }` + Spalte auf `Trader` (Default Full), Migration.
|
||||
- **Klassifizierung** im `TradeHistoryWorker` nach jedem Fetch: Zeitspanne der letzten 500 Trades →
|
||||
Trades/Tag-Schätzung. > 5.000/Tag → SnapshotOnly; > 100/Tag → Aggregated. Hysterese: Rückstufung Richtung
|
||||
Full erst nach 7 Tagen unter der halben Schwelle (kein Flattern).
|
||||
- **SnapshotOnly (Tier C):**
|
||||
- Polling/History-Worker überspringen den Trade-Import komplett.
|
||||
- Stündlich: `GetTraderPositionsAsync` (der ungenutzte `/positions`-Endpoint!) → `TraderPositions` upserten
|
||||
(size→SharesHeld, avgPrice→AvgCost, cashPnl→RealizedPnl); `OverallPnL` aus Positions +
|
||||
`GetLeaderboardAsync`-PnL für die Zeitfenster; `TraderDailySnapshot` weiter schreiben (Equity-Kurve bleibt).
|
||||
- Wöchentliche „Biopsie": einmal 500 Trades via /activity ziehen, NUR durch den `TraderTraitCalculator`
|
||||
schicken, NICHT persistieren.
|
||||
- Engine überspringt Trade-Replay für SnapshotOnly; Estimator/Enrichment überspringen; CopytradingScore = 0
|
||||
mit Trait `not_copyable_hf`.
|
||||
- **Aggregated (Tier B):** Aggregation beim Import statt nachträglicher Kompaktierung: Bucket
|
||||
(TraderId, MarketOutcomeId, Side, Stunde) mit VWAP-Preis, Summen-Size/-Amount, `AggregatedCount`; gespeichert
|
||||
als normale Trade-Zeile mit `PlatformTradeId = "AGG_{traderId}_{outcomeId}_{side}_{yyyyMMddHH}"`, laufende
|
||||
Stunde per Upsert aktualisieren. Average-Cost-Engine bleibt damit verlustfrei.
|
||||
- Danach: `RetentionDays` für Full-Trader auf 180 erhöhen (Config) — die Bots stellen nicht mehr die Masse,
|
||||
und längerer Track-Record nützt genau den kopierbaren Tradern.
|
||||
- **Tests:** Klassifizierungs-Schwellen + Hysterese als pure Funktion; PollingWorker importiert für
|
||||
SnapshotOnly-Trader nichts; Aggregations-Upsert ist idempotent (2× dieselbe Stunde → 1 Zeile, korrekte Summen
|
||||
und `AggregatedCount`).
|
||||
|
||||
### D4. Abnahme Teil D
|
||||
|
||||
1. `dotnet test`: alle bestehenden **32 + 1 Skip** bleiben grün (Assertions unverändert) + die neuen D-Tests.
|
||||
2. RN1/Swisstony stehen nach der Einstufung auf SnapshotOnly: PnL gefüllt (aus /positions/Leaderboard),
|
||||
Traits gesetzt, **keine neuen Trade-Zeilen** mehr in der DB.
|
||||
3. Detailseite zeigt Trait-Chips; Traders-Liste filterbar nach Trait.
|
||||
3b. Detailseite zeigt Median-Win/-Loss-Rendite und Profit Factor; die D2c-Werte sind für Trader mit
|
||||
abgeschlossenen Märkten gefüllt.
|
||||
4. Tägliches DB-Wachstum sichtbar reduziert (DB-Size-Anzeige im WinForms-Statusbar beobachten).
|
||||
|
||||
Reihenfolge: **D1 → D2/D2b/D2c → D3** (D2c ist klein und gehört in denselben Engine-Durchlauf wie die Winrate; bei D3 zuerst Tier C, dann Tier B).
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Teil F — Kategorie-Erkennung strukturell reparieren (ergänzt 2026-07-13)
|
||||
|
||||
> Nutzer meldet: Markt-Kategorien und die Kategorien, in denen sich ein Trader bewegt, stimmen
|
||||
> weiterhin nicht. Ursache **live gegen die Gamma-API verifiziert** — es ist strukturell, nicht der Mapper.
|
||||
|
||||
### F0. Root Cause (verifiziert)
|
||||
|
||||
- Die **`/events`-Liste** (MarketSyncWorker-Pfad) liefert **reichhaltige Tags**
|
||||
(z. B. `['Sports','Soccer','FIFA World Cup',...]`).
|
||||
- Der **`/markets?condition_id=`-Pfad** (On-Demand, `PolymarketProvider.GetMarketAsync`, aufgerufen
|
||||
vom `PollingWorker` für jeden noch unbekannten Markt) liefert **weder `category` noch Event-Tags**
|
||||
(`raw.Events[0].Tags` ist leer, auch mit `include_tag=true`). → Solche Märkte werden nur per
|
||||
Frage-Text klassifiziert und landen sonst auf **`Other`**.
|
||||
- **Genau die vom Trader gehandelten Märkte entstehen überwiegend on-demand** → viele `Other`.
|
||||
- Verschärfend: **geschlossene/aufgelöste** Märkte deckt der aktive Events-Sync nicht laufend ab →
|
||||
historische Märkte (die für die Analyse zählen) bleiben ohne Tags = `Other`.
|
||||
- Zwei Folgefehler: (a) `GetSubcategory` nimmt **`tags[0]`** — das ist oft Müll (`"Ethiopia"`,
|
||||
`"Hide From New"`, `"exchange"`), nicht die Kategorie; (b) die Tag-**Reihenfolge** ist unzuverlässig
|
||||
(bei „Next PM of Ethiopia" steht der kanonische Tag `Politics` **zuletzt**).
|
||||
|
||||
### F1. Mapper: kanonischen Tag zuerst, dann Heuristik
|
||||
|
||||
- [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:
|
||||
`Sports`, `Politics`, `Crypto`, `Business`/`Economy`, `Pop Culture`, `Science`, ...). Mapping-Tabelle
|
||||
Tag→`MarketCategory` (inkl. Synonyme: `Finance`/`Business`→Economy, `Pop Culture`→PopCulture).
|
||||
Erst wenn **kein** kanonischer Tag matcht, die bestehende Keyword-Heuristik auf Frage+Tags anwenden.
|
||||
- [x] **Kategorie über die gesamte Tag-Menge** bestimmen, nie über `tags[0]`.
|
||||
|
||||
### F2. Subcategory: Noise filtern, sinnvoll wählen, leer normalisieren
|
||||
|
||||
- [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?, …).
|
||||
- [x] Subcategory = spezifischster **verbleibender** Tag, der **nicht** die Kategorie selbst ist
|
||||
(bei World-Cup-Tags → `Soccer`, nicht `Sports`). Kein passender → leerer String.
|
||||
- [x] **NULL/`""` einheitlich als `""`** speichern (behebt die doppelten „Sports/-"-Zeilen: heute
|
||||
entstehen zwei Gruppen-Keys aus NULL vs. "").
|
||||
|
||||
### F3. On-Demand-Markt: Tags nachladen statt `Other` zu speichern
|
||||
|
||||
- [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
|
||||
Tags fürs Mapping verwenden. Über den `IRateLimiter` drosseln.
|
||||
- [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.
|
||||
|
||||
### F4. Kategorie aus Event-Tags ableiten + Offline-Backfill (der große Hebel)
|
||||
|
||||
- [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.
|
||||
- [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
|
||||
`RunRecalculateAllTradersAsync` einhängen ODER eigener Dev-Endpoint. Danach `TraderCategoryPerformance`
|
||||
neu rechnen (passiert durch die ohnehin folgende Trader-Neuberechnung).
|
||||
|
||||
### F5. Coverage geschlossener Märkte
|
||||
|
||||
- [ ] Sicherstellen, dass der Events-Sync **geschlossene** Events (mit Tags) ausreichend abdeckt —
|
||||
mindestens für Märkte, die getrackte Trader gehandelt haben (gezielter „fehlende Event-Tags
|
||||
nachladen"-Schritt in der Reconciliation).
|
||||
|
||||
### F6. Tests
|
||||
|
||||
- [x] Mapper: kanonischer Tag gewinnt über Reihenfolge (`"Ethiopia, Elections, ..., Politics"` → Politics;
|
||||
`"Sports, Soccer, ..."` → Sports, Subcategory `Soccer`).
|
||||
- [x] Subcategory: Müll-Tags werden gefiltert; NULL und `""` erzeugen denselben Gruppen-Key
|
||||
(kein Duplikat mehr).
|
||||
- [ ] 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,
|
||||
ohne API-Call.
|
||||
|
||||
### F7. Reihenfolge
|
||||
|
||||
F1+F2 (reiner Mapper, sofort, testbar) → F4-Backfill (heilt Bestand offline) → F3 (On-Demand-Tags für
|
||||
neue Märkte) → F5 (Coverage). F1/F2/F4 bringen den Großteil, ohne nennenswerte API-Last.
|
||||
Reference in New Issue
Block a user