Files
Predictalytics/UMSETZUNGSPLAN.md
T
Richard afb251acfc Initial commit: Predictalytics solution
Clean Architecture .NET 8 solution (Domain/Application/Infrastructure/Api/Worker/WinFormsHost)
for analyzing Polymarket traders for copytrading/strategy-replication candidates.

Includes EF Core InitialBaseline migration and DB secrets removed from source/config
in preparation for version control.
2026-07-01 19:53:29 +02:00

19 KiB
Raw Blame History

Predictalytics Umsetzungsplan (Phase 1 & 2 + Storage-Optimierung)

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

  • 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
  • Buchungslogik je TradeSide sauber definieren:
    • Buy: Shares += Size, AvgCost neu gewichten
    • Sell: RealizedPnl += Size × (Price AvgCost), Shares = Size
    • 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
    • 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)
  • 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)
  • CalculatePnL-Bug aus Abschnitt 0 im Zuge dessen mit erledigen

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
  • WinRate = Anzahl gewonnener Märkte / Anzahl abgeschlossener Märkte (offene Positionen zählen nicht mit)
  • Placeholder return 0; in TraderAnalyticsWorker.CalculateWinRate ersetzen

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.
    • 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)
  • 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)
  • ExitQuality: analog für Ausstiegspreis
  • 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

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)

  • 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)
    • 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
    • Track-Record-Länge & Konsistenz: mehr abgeschlossene Märkte mit konsistent positivem PnL = höheres Vertrauen
  • Kombinierten CopytradingScore (0100) 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)

A6. (siehe Abschnitt 0) TradeSide-Bugfix in PnL-Berechnung

  • Erledigt sich durch A1, hier nur als Häkchen zur Nachverfolgung
  • Kurzer Änderungsvermerk/Commit-Hinweis, damit klar ist, dass dieser Bug bewusst behoben wurde

Phase 2 Robustheit & Wartbarkeit

B1. EF Core Migrationen einführen

  • 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
  • Erste Baseline-Migration erzeugt (20260701102311_InitialBaseline, in src/Predictalytics.Infrastructure/Migrations/), entspricht exakt dem aktuellen Schema
  • EnsureCreatedAsync + handgeschriebene ExecuteIfColumnMissing/ALTER-Helfer aus DependencyInjection.cs entfernt, durch db.Database.MigrateAsync() ersetzt (Platform-Seed bleibt als INSERT IGNORE)
  • Offen (manueller Schritt durch Nutzer): Live-DB per SQL als "bereits migriert" markieren (__EFMigrationsHistory-Tabelle + Insert für 20260701102311_InitialBaseline), da die Tabellen dort schon existieren und nicht per CreateTable neu angelegt werden dürfen. SQL wurde im Chat bereitgestellt, Ausführung liegt beim Nutzer (bewusst nicht automatisch gegen die produktive Remote-DB ausgeführt)
  • Alle künftigen Schemaänderungen aus Phase 1 (z.B. TraderPosition, MarketOutcomePriceSnapshot, TraderCopytradingProfile) als reguläre Migrationen anlegen — dieser Task sollte vor A1 abgeschlossen sein, damit wir dort nicht wieder manuell patchen

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

  • 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)
  • 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)

B4. Testabdeckung für die kritische Logik

  • 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
  • Unit-Tests für Win-Rate (A2)
  • Unit-Tests für Deep-Dive-Kennzahlen (A3) und Copytrading-Score (A5)
  • Diese Tests idealerweise parallel zu A1A5 schreiben, nicht erst am Ende nachziehen

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

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, 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")

  • 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
  • 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)
  • 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
  • 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")

  • 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)
  • 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)
  • 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

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 DELETEs
  • 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

  • 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 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.)