Files
Predictalytics/docs/archiv/UMSETZUNGSPLAN.md
RichardandClaude Opus 5 6975720dc6 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>
2026-08-23 18:46:00 +02:00

237 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 4754). 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 8486). 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** (148153) und **D1/D2** (165173) 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.
>
> Phase 3 (Monetarisierung: Auth, Multi-Tenant, Billing) ist bewusst **nicht** Teil dieses Plans.
---
## 0. Vorab-Bugfund (betrifft Phase 1 direkt)
Beim Review ist aufgefallen, dass `TraderAnalyticsWorker.CalculatePnL` **jeden Trade, der kein `Buy` ist, wie ein `Sell` behandelt** (`else pnl += t.Amount`). Das ist falsch für `Split`, `Merge`, `Redeem`, `AddLiquidity`, `RemoveLiquidity` — das sind keine gewöhnlichen Verkäufe. Dieser Fix gehört zwingend in die neue PnL-Engine (siehe A1/A6), nicht als Extra-Task.
---
## Phase 1 Kernanalytik korrigieren
### A1. Positionsbasierte PnL-Engine
- [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)
- [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
- [x] Buchungslogik je `TradeSide` sauber definieren:
- [x] `Buy`: Shares += Size, AvgCost neu gewichten
- [x] `Sell`: RealizedPnl += Size × (Price AvgCost), Shares = Size
- [x] `Redeem` (Marktauflösung): RealizedPnl += verbleibende Shares × (1 oder 0 je nach Gewinn-Outcome AvgCost), Shares = 0
- [x] `Split` / `Merge`: als neutrale Positionsumwandlung behandeln (kein PnL-Effekt), nicht wie `Sell`
- [x] `AddLiquidity` / `RemoveLiquidity`: getrennt von Trading-PnL betrachten (eigene Kategorie, fließt nicht in "Trading-Skill"-Bewertung ein)
- [x] Unrealisierten PnL für offene Positionen berechnen: `Shares × (MarketOutcome.CurrentPrice AvgCost)`
- [x] Gesamt-PnL = realisiert + unrealisiert (ersetzt `OverallPnL`, `PnL30d/7d/24h` Felder in `TraderAnalytics`)
- [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)
- [x] `CalculatePnL`-Bug aus Abschnitt 0 im Zuge dessen mit erledigen
### A2. Win-Rate korrekt berechnen
- [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
- [x] `WinRate = Anzahl gewonnener Märkte / Anzahl abgeschlossener Märkte` (offene Positionen zählen nicht mit)
- [x] Placeholder `return 0;` in `TraderAnalyticsWorker.CalculateWinRate` ersetzen
### A3. Deep-Dive-Kennzahlen mit echten Werten füllen
- [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.
- [x] Prüfen, ob Polymarkets CLOB-API einen Preishistorie-Endpoint (`/prices-history`) hergibt, den wir zum Backfill nutzen können
- [x] Falls ja: neue Tabelle `MarketOutcomePriceSnapshot` (MarketOutcomeId, Timestamp, Price) einführen, periodisch befüllt (z.B. durch bestehenden `MarketSyncWorker`/`MarketHistoryWorker` erweitern)
- [x] `AvgHoldDurationHours` echt berechnen: gewichtete Haltedauer zwischen Einstieg (Buy-Zeitpunkte, gewichtet nach Größe) und Ausstieg (Sell/Redeem) pro Position
- [x] `EntryQuality`: Einstiegspreis im Vergleich zur nachfolgenden Preisentwicklung (z.B. Perzentil des Einstiegspreises innerhalb eines Zeitfensters danach)
- [x] `ExitQuality`: analog für Ausstiegspreis
- [x] `TimingAccuracy`: z.B. Anteil der Trades, die kurz vor einer für den Trader günstigen Preisbewegung platziert wurden
- [x] Hardcodierte `50`-Neutralwerte in `AnalyticsService.PerformDeepDive` entfernen
### A4. Strategie-Klassifikation verbessern
- [ ] Zusätzliche Signale einbeziehen statt nur "Ø-Größe" und "Hedging-Rate":
- [ ] Verteilung der Haltedauern (kurz/lang, Varianz)
- [ ] Diversität der Marktkategorien
- [ ] Anteil der Trades kurz vor Marktauflösung vs. früh im Marktleben
- [ ] Nutzung gegenläufiger Positionen (Arbitrage-Muster über mehrere Outcomes/Märkte)
- [ ] Trade-Größen-Varianz (regelmäßig gleich große Orders = evtl. automatisiert)
- [ ] Überlegen, ob `StrategyType` als reines Einzel-Enum ausreicht oder ob wir zusätzlich Mehrfach-Signale/Tags parallel speichern (z.B. "primär Hedger, aber auch Whale-Größenordnung") — Enum bleibt als Primär-Tag, Zusatzsignale als eigene Felder/Scores
- [ ] Sicherstellen, dass nicht der Großteil der Trader dauerhaft bei "Unknown" landet (aktuell strukturell der Fall)
### A5. Copytrading-Eignungs-Score (neu)
- [x] Neue Metrik-Dimension definieren, unabhängig vom bestehenden `PriorityScore`:
- [x] **Liquiditäts-Fit**: durchschnittliche Positionsgröße im Verhältnis zur Marktliquidität/zum Volumen zum Handelszeitpunkt (Slippage-Risiko für Nachahmer)
- [x] **Reaktionsfenster**: wie viel Zeit bliebe einem Copytrader realistisch zum Nachziehen (Trader mit Sekunden/Millisekunden-Kadenz sind nicht kopierbar)
- [x] **Frequenz/Konzentration**: sehr hochfrequente/bot-artige Trader senken den Score automatisch
- [x] **Track-Record-Länge & Konsistenz**: mehr abgeschlossene Märkte mit konsistent positivem PnL = höheres Vertrauen
- [x] Kombinierten `CopytradingScore` (0100) berechnen und persistieren (neues Feld auf `TraderScore` oder eigene Entity `TraderCopytradingProfile`)
- [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
- [x] Erledigt sich durch A1, hier nur als Häkchen zur Nachverfolgung
- [x] Kurzer Änderungsvermerk/Commit-Hinweis, damit klar ist, dass dieser Bug bewusst behoben wurde
---
## Phase 2 Robustheit & Wartbarkeit
### B1. EF Core Migrationen einführen
- [x] Aktuellen Ist-Stand des Schemas exakt erfassen (inkl. aller manuellen `ALTER TABLE`-Patches aus `DependencyInjection.cs`) — abgeglichen, Entities/`OnModelCreating` deckten den Patch-Stand bereits vollständig ab
- [x] Erste **Baseline-Migration** erzeugt (`20260701102311_InitialBaseline`, in `src/Predictalytics.Infrastructure/Migrations/`), entspricht exakt dem aktuellen Schema
- [x] `EnsureCreatedAsync` + handgeschriebene `ExecuteIfColumnMissing`/`ALTER`-Helfer aus `DependencyInjection.cs` entfernt, durch `db.Database.MigrateAsync()` ersetzt (Platform-Seed bleibt als `INSERT IGNORE`)
- [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] ~~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] 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)
> 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.
- [ ] Produktiven MySQL-Connection-String aus `appsettings.json` in Umgebungsvariablen/User Secrets verschieben
- [ ] `appsettings.json` im Repo künftig nur Platzhalter/Dev-Default enthalten, damit ein Klon des Repos ohne die echten Zugangsdaten lauffähig bleibt (mit eigener lokaler DB)
- [ ] Keine Passwort-Rotation nötig, solange rein lokale Nutzung
### B3. Scoring-Pipeline entkoppeln
- [x] `PollingWorker` soll **nicht** bei jedem 60-Sekunden-Zyklus alle Trader neu bewerten
- [x] Nur Trader neu bewerten, die seit letzter Berechnung neue Trades bekommen haben (Dirty-Flag oder Vergleich `LastTradesUpdatedAt` vs. `TraderScore.CalculatedAt`)
- [x] N+1-Datenbankzugriffe in `ScoringService.RecalculateAllScoresAsync` durch Batch-Queries ersetzen
- [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)
- [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
- [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
- [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
- [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
- [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
- [x] Neues Testprojekt (z.B. `Predictalytics.Application.Tests`) anlegen — aktuell existiert **kein einziges** Testprojekt
- [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
- [x] Unit-Tests für Win-Rate (A2)
- [x] Unit-Tests für Deep-Dive-Kennzahlen (A3) und Copytrading-Score (A5)
- [x] Diese Tests idealerweise **parallel zu A1A5** schreiben, nicht erst am Ende nachziehen
### B5. Kleine verwandte Aufräumarbeit
- [x] Log-Rotation/Retention der Serilog-Datei-Sinks (`WinFormsHost/logs`) prüfen — wächst potenziell unbegrenzt, ähnliches Prinzip wie die DB-Speicherplatzfrage unten
---
## Datenbank-Speicherplatz-Optimierung
### C0. Kritische Bewertung deiner beiden Ideen (überarbeitet nach Diskussion 2026-07-01)
**Neue Rahmenbedingungen, die die Bewertung ändern:**
- Alle Rohdaten sind jederzeit erneut abrufbar — über Polymarkets `/activity`-Endpoint ([Doku](https://docs.polymarket.com/api-reference/core/get-user-activity), keine sichtbare Limitierung), im Zweifel über die Blockchain selbst. Wir müssen also **nichts "für immer" sichern** — Löschen ist kein unwiderruflicher Datenverlust.
- Analyseziel ist laut Auftrag explizit die **aktuelle** Strategie und aktueller Erfolg/Misserfolg, nicht die Historie von vor Jahren. Wir brauchen also gar keine unbegrenzte Detailtiefe — nur genug, um "aktuelles Verhalten" zu charakterisieren.
- Kern-Einsicht aus A1: Sobald ein Trade in `TraderPosition` (SharesHeld/AvgCost/RealizedPnl) eingerechnet ist, wird die **Rohzeile für die PnL-Fortführung nie wieder gebraucht** — die Position ist bereits die komprimierte Zusammenfassung. Rohdaten braucht es nur noch für die Strategie-/Deep-Dive-Analyse (Haltedauer, Timing, Hedging-Muster), und die soll ohnehin nur das aktuelle Fenster betrachten.
→ Das ersetzt die alte Idee 1 (Archivierung nur inaktiver Trader + Reaktivierungs-Baseline) durch ein einfacheres, einheitliches Prinzip: siehe **C1 Rollierendes Zeitfenster**.
**Idee 2 (Hochfrequenz-Trader alle 10 Min. aggregieren)** bleibt weiterhin sinnvoll — allerdings in überarbeiteter Form, da die ursprüngliche Beschreibung zu grob war:
- "Min/Max/Ø Buy-In" ohne Trennung nach Buy/Sell und nach Markt/Outcome zerstört genau die Information, die die PnL-Engine (A1) braucht.
- Eine feste 10-Minuten-Uhrzeit-Bucket-Grenze für *alle* Trades eines als "Bot" eingestuften Traders würde auch die wenigen möglicherweise bedeutsamen Trades eines Mischtyps mit-aggregieren.
- Relevant bleibt sie, weil auch *innerhalb* des neuen rollierenden Zeitfensters (siehe C1) ein aktiver Bot enorme Mengen an Trades erzeugen kann — das ist jetzt eine Optimierung fürs "heiße" Fenster, nicht mehr fürs Langzeitarchiv.
→ 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")
- [x] Konfigurierbares Retention-Fenster einführen (Default-Vorschlag: 36 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] 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] 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] 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] 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)
### C2. Kompaktierung von Hochfrequenz-Tradern innerhalb des Zeitfensters (überarbeitete "Idee 2")
- [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"
- [x] Mindestlänge für Kompaktierung festlegen (z.B. erst ab 20+ Trades im Burst lohnt sich das)
- [x] Bestehende Bot-Heuristik (`intervals.Average() < 10` in `AnalyticsService`) als Ausgangspunkt wiederverwenden/verallgemeinern statt eine zweite, unabhängige Definition einzuführen
- [x] Aggregat-Datensatz pro Burst: Anzahl Trades, Summe Size, Summe Amount, Min-Preis, Max-Preis, **VWAP** (nicht einfacher Durchschnitt)
- [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`
- [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
- [ ] **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. 3050% Platz auf diesen stark indizierten Spalten und ist schneller
- [ ] **Tabellen-Partitionierung** von `Trades` nach Monat (`ExecutedAt`) prüfen — erlaubt später das Archivieren/Droppen ganzer Partitionen statt teurer zeilenweiser `DELETE`s
- [ ] **Materialitätsschwelle**: Mikro-Trades unterhalb eines Betrags (z.B. < $0.50) unabhängig von Bot-Klassifizierung direkt aggregiert erfassen, da sie für Copytrading/Strategieanalyse ohnehin kaum Aussagekraft haben
- [ ] ~~Kaltarchiv außerhalb der DB~~ — nicht mehr nötig: da Rohdaten jederzeit über die API/Blockchain nachladbar sind (siehe C1), erübrigt sich ein separates Cold-Storage-Export. Nur falls sich die API-Nachladbarkeit später als doch eingeschränkt herausstellt, hier nochmal aufgreifen
- [ ] **InnoDB-Kompression** (`ROW_FORMAT=COMPRESSED`) für die `Trades`-Tabelle als schneller Zwischenschritt prüfen — kein Datenverlust, kombinierbar mit allem anderen
- [ ] `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
- [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.
---
## Entscheidung: Frischer Datenbank-Neustart nach Abschluss?
**Neu bewertet (2026-07-01):** Da wir laut C0/C1 ohnehin nichts an Alt-Historie "für immer" brauchen und alles bei Bedarf über die API/Blockchain nachladbar ist, ist ein sauberer Reset am Ende von Phase 1+2 **risikoarm und klar empfehlenswert** — nicht mehr nur eine Option mit Vorbehalt.
### D1. Kurzer Bestätigungs-Check (geringe Priorität, kein Blocker mehr)
- [ ] Stichprobenartig verifizieren, dass `/activity` für ein bekanntes, lange aktives Wallet tatsächlich vollständig zurückreicht — bestätigt nur die bereits über die [Doku](https://docs.polymarket.com/api-reference/core/get-user-activity) plausibilisierte Annahme, ist kein Show-Stopper mehr für die Entscheidung
### D2. Ablauf des Neustarts
- [ ] Vollständiges Backup/Dump der aktuellen Datenbank sichern (reine Vorsichtsmaßnahme, wird voraussichtlich nicht gebraucht) und einige Wochen aufbewahren
- [ ] EF-Migrationen-Baseline + alle Phase-1/2-Schemaänderungen fertigstellen (B1)
- [ ] Neue leere Datenbank anlegen, Migrationen anwenden
- [ ] Liste bereits bekannter Trader-Wallets aus dem alten Bestand als Startpunkt für die Re-Discovery übernehmen
- [ ] Re-Import über die Worker-Pipeline (mit korrigierter PnL-Logik, direkt im Rahmen des neuen Retention-Fensters aus C1) laufen lassen
- [ ] **Wichtig:** Dieser Schritt kommt ganz am Ende von Phase 1 + 2 — nicht vorher, damit wir nicht zweimal migrieren/importieren müssen
---
## Empfohlene Reihenfolge (Kurzfassung, Stand 2026-07-01)
1. **B1** EF-Migrationen-Baseline einführen (Fundament für alle weiteren Schemaänderungen)
2. **A1 + A6** Positionsbasierte PnL-Engine inkl. TradeSide-Bugfix
3. **A2** Win-Rate
4. **A3** Deep-Dive-Kennzahlen (inkl. Preis-Historie-Voraussetzung klären)
5. **A4** Strategie-Klassifikation verbessern
6. **A5** Copytrading-Eignungs-Score
7. **B4** Tests (idealerweise begleitend zu 2.6., hier als Nachhol-Punkt falls übersprungen)
8. **B3** Scoring-Pipeline entkoppeln/optimieren
9. **C1 + C2 + C3** Speicherplatz-Optimierung: rollierendes Zeitfenster + Burst-Kompaktierung (bewusst erst jetzt, siehe C4)
10. **D** Datenbank-Neustart durchführen (ganz am Ende, jetzt als klar empfohlener Schritt statt offener Entscheidung)
(B2 Secrets-Vorbereitung und B5 Log-Rotation sind risikoarme Nebenpunkte ohne akute Dringlichkeit, da rein lokale Nutzung; können jederzeit zwischendurch erledigt werden, wenn Zeit ist.)