From a1fcb4ace5572e96c1ca321482ae1393e2d7cc7d Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 12 Jul 2026 18:34:02 +0200 Subject: [PATCH] API audit: expose 7d/24h windows, remove broken repair-db, add API docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TraderDetailDto now exposes PnL7d/WinRate7d/PnL24h/WinRate24h and CurrentBalance — the engine has computed these all along but the API never delivered them. - Removed POST /api/dev/repair-db: its raw SQL referenced non-existent columns/tables (Trades.Type/Payout, Traders.LastPositionsUpdatedAt, table "Jobs") and would have deleted ALL TraderPositions including pruned-history conserves. The supported repair path is the WinForms "Recalculate All Traders" action. - Swagger tags for Jobs and Dev groups; full endpoint reference in docs/API.md (kept generic — external consumers like PolyTrader adapt to our API, not vice versa). - FIXPLAN Teil E: master-selection gap analysis as generic extensions (profile endpoint, out-of-sample window metrics, price-band profile with per-band win rate, stop-loss ratio, copyability aggregates with category fees, correlation endpoint, martingale trait). Co-Authored-By: Claude Fable 5 --- FIXPLAN-2026-07-09.md | 102 ++++++++++++ docs/API.md | 146 ++++++++++++++++++ .../Endpoints/DevEndpoints.cs | 33 +--- .../Endpoints/JobEndpoints.cs | 2 +- .../DTOs/TraderDto.cs | 5 + .../Services/AnalyticsService.cs | 4 +- 6 files changed, 264 insertions(+), 28 deletions(-) create mode 100644 docs/API.md diff --git a/FIXPLAN-2026-07-09.md b/FIXPLAN-2026-07-09.md index b653bbb..17bfdd8 100644 --- a/FIXPLAN-2026-07-09.md +++ b/FIXPLAN-2026-07-09.md @@ -506,3 +506,105 @@ gegen 300+/min) — das Trade-Replay-PnL ist für diese Klasse bereits falsch un 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 E — Master-Auswahl-Metriken & generischer Profil-Endpoint (ergänzt 2026-07-11) + +> Hintergrund: Ein externer Konsument (PolyTrader-Copytrading) braucht eine belastbare +> Master-Trader-Auswahl (Prüfplan liegt in `PREDICTALYTICS-PRUEFPLAN-Master-Auswahl.md` im +> PolyTraderSharp-Repo). **Bewusste Architektur-Entscheidung: Die API bleibt generisch.** +> Kein PolyTrader-spezifisches Format, kein „HOLDER/STOPLOSS"-Vokabular in der API — +> wir exponieren neutrale Metriken, der Konsument mappt selbst. +> Abgleich: M2/M8 existieren (D2c/CategoryPerformances), M3/M9 teilweise als Traits (D2), +> M1/M5/M6 teilweise; **komplett neu sind M4, M7, Out-of-Sample-Fenster, Kopierbarkeits- +> Aggregate und die Korrelations-Sicht** — das ist Teil E. + +### E1. Generischer Profil-Endpoint `GET /api/traders/{id}/profile` + +Ein Aufruf liefert das vollständige Analyse-Profil (statt 4+ Einzel-Calls): +Stammdaten + alle Analytics-Felder + Traits (D2) + Rendite-Metriken (D2c) + +Fenster-Blöcke (E2) + Fingerprint-Verteilungen (E3) + Kopierbarkeits-Aggregate (E5). +Nur persistierte Werte ausliefern (kein On-the-fly-Rechnen wie im Deep-Dive) — +der Endpoint muss schnell und pollbar sein. In `docs/API.md` dokumentieren. + +### E2. Fenster-Metriken als Struktur + Out-of-Sample-Vergleich + +**Problem:** Die 24h/7d/30d-Felder sind Einzelspalten; der Prüfplan braucht zwei frei +definierte Vergleichsfenster (z. B. Tag −180…−60 vs. −60…heute), um Glücks-Wallets +auszusortieren (nur wer in BEIDEN Fenstern liefert, ist ein Kandidat). + +**Fix:** +- Neue Tabelle `TraderWindowMetrics`: TraderId, WindowStart, WindowEnd, ClosedMarkets, + WinRate, AvgReturnPct (M1: Ø realisierte Rendite je Markt), MedianWinReturnPct, + MedianLossReturnPct, ProfitFactor, ComputedAt. Unique (TraderId, WindowStart, WindowEnd). +- Berechnung im Analytics-Lauf für zwei konfigurierbare Fenster + (`AnalysisWindows`-Sektion in appsettings, Default: −180…−60 und −60…0 Tage). + Wiederverwendet die D2c-Logik mit Zeitfilter auf den Markt-Abschlusszeitpunkt. +- Im Profil (E1) als `windows[]`-Array. **Achtung Retention:** Fenster A reicht weiter + zurück als 90 Tage — Berechnung muss mit fehlender Historie ehrlich umgehen + (`closedMarkets` klein → Konsument sieht die dünne Stichprobe). Nach der + Retention-Verlängerung auf 180 Tage (D3) wird Fenster A tragfähig. + +### E3. Fingerprint-Verteilungen (persistiert, im Profil) + +Im Analytics-Lauf berechnen und als JSON-Spalte(n) auf `TraderAnalytics` oder eigene +Tabelle persistieren: +- **Preisband-Profil (M7):** Einsatz-Anteil je 10-¢-Einstiegspreisband über Buys, + **plus realisierte Winrate je Band** (macht Glück von System unterscheidbar: + „kauft 90–95-¢-Shares, gewinnt 97 %" = +Edge sichtbar pro Band). +- **Haltedauer:** Median (nicht nur Ø) Stunden Kauf→Exit/Auflösung (M6). +- **Positionsgrößen:** P50/P90-Amount (M9-Basis). +- Trade-Frequenz je Woche (aus Trades30d ableitbar, im Profil ausgeben). + +### E4. Exit-Verhalten klassifizieren (M4) — generisch als Trait + Kennzahl + +- Kennzahl `stop_loss_ratio`: Anteil der Sells, die nach einem Preisrückgang von + ≥ 10 % unter den Einstands-AvgCost erfolgen (Sell-Preis ≤ 0,9 × AvgCost), + bezogen auf alle geschlossenen Positionen. Braucht KEINE Preis-Historie — + Sell-Preis vs. AvgCost der Position reicht als v1-Näherung. +- Traits: `sells_at_loss` (Ratio > 0,15) ergänzt das vorhandene `holds_to_resolution`. + Ein Konsument bildet daraus selbst HOLDER (`holds_to_resolution` ∧ ¬`sells_at_loss`), + STOPLOSS, MIXED. + +### E5. Kopierbarkeits-Aggregate (im Profil) + +- `medianMarketVolumeUsd`: Median des `Market.Volume` (bzw. Volume24h) der vom Trader + gehandelten Märkte — handelt er in Kleinstmärkten, bewegt der Kopierer den Preis. +- `medianPostFillDriftPct`: Median-Preisänderung nach seinen Buys (aus vorhandenem + `TradeContext.PriceAfter1m`/`FollowerFillPrice60s`, nur enriched Trades; Anzahl + der Datenpunkte mit ausgeben). +- `netEdgeAfterFeesPct`: `AvgReturnPct` (E2, Fenster B) minus kategorie-gewichteter + Taker-Fee (aus `Market.FeeRateBps` — wird bereits erfasst!) minus konfigurierbarem + Spread-Aufschlag (`CopyCostSettings:SpreadPct`, Default 2,0). Generisch als + „Netto-Edge nach Kopierkosten" benannt. + +### E6. Korrelations-/Portfolio-Sicht + +- Neuer Endpoint `GET /api/traders/correlation?ids=1,2,3` (oder `?top=20`): + paarweise Jaccard-Ähnlichkeit über die gehandelten ConditionIds (Fenster B) + + Kategorie-Mix-Cosinus. Antwort: Matrix + je Paar die Overlap-Zahl. +- Kein „Portfolio-Empfehlungs"-Endpoint in v1 — die Auswahl-Logik (max. 2 je + Kategorie etc.) gehört zum Konsumenten. Wir liefern die Korrelationsdaten. + +### E7. Martingale-Erkennung (M9) als Trait + +`martingale_pattern`: mittleres Verhältnis Einsatz(nach verlorenem Markt) / +Einsatz(nach gewonnenem Markt) über die Sequenz der abgeschlossenen Märkte; +Trait ab Verhältnis ≥ 1,5 bei ≥ 20 Märkten. (Einsatz = investiertes Kapital je +Markt aus D2c.) + +### E8. Abnahme Teil E + +1. Bestehende Tests grün (Assertions unverändert) + neue Tests: Fenster-Metriken + (bekannte Markt-Menge, zwei Fenster → korrekte Werte je Fenster), Preisband- + Winrate (Band-Zuordnung + Ränder 0,895/0,90), `stop_loss_ratio` + (Positiv-/Negativfall), Jaccard-Berechnung, Martingale (steigende Einsätze nach + Losses → Trait; konstante → kein Trait). +2. `GET /api/traders/{id}/profile` liefert für einen analysierten Trader alle Blöcke + gefüllt; Antwortzeit < 200 ms (nur persistierte Daten). +3. `docs/API.md` um Profile-/Correlation-Endpoint ergänzt. +4. Kein PolyTrader-spezifisches Vokabular in API/DTOs. + +Reihenfolge: E1+E3 zuerst (Profil mit vorhandenen + Fingerprint-Daten), dann E2 +(Fenster), E4/E5/E7 (Kennzahlen), E6 zuletzt. Teil E setzt D1/D2/D2c voraus. diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..102dc35 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,146 @@ +# Predictalytics REST API — Referenz + +> Stand: 2026-07-11 · Interne API ohne Authentifizierung (nur lokal betreiben!). +> Interaktive Doku: **`/swagger`** auf dem laufenden Server (Standalone-API und +> WinForms-Embedded-Server registrieren identische Endpoints über +> `ApiConfiguration.MapPredictalyticsEndpoints()` — dort ist die einzige Stelle, +> an der neue Endpoints registriert werden dürfen). + +Konventionen: JSON mit camelCase-Feldnamen · Zeiten in UTC (ISO 8601) · +Prozente auf 0–100-Skala · Beträge in USD · `WinRate` ist marktbasiert +(gewonnene abgeschlossene Märkte / abgeschlossene Märkte, nicht pro Trade). + +--- + +## Traders + +### `GET /api/traders` +Trader-Liste, sortiert nach CombinedScore, dann TotalPnl. + +| Parameter | Typ | Default | Beschreibung | +|---|---|---|---| +| `skip` / `take` | int | 0 / 50 | Paging | +| `platform` | string | All | `Polymarket`, `Limitless`, … | +| `highlyCopyable` | bool | false | nur `CopytradingScore ≥ 60` | + +Antwort: Array von **TraderDto** — `id`, `platform`, `platformUserId` (Wallet), +`displayName`, `tier`, `strategy`, `combinedScore`, `copytradingScore` (kombinierter +Estimator-Score), `copytradingQualityScore` (LCB-Edge), `copytradingCopyabilityScore` +(Alpha-Decay/Sizing), `winRate`, `totalPnl`, `totalTrades`, `trades30d`, `pnL30d`, +`winRate30d`, `estimatedBankroll`, `isOnWatchlist`, `isSuspectedBot`, `lastPolledAt`. + +*Geplant (FIXPLAN D2):* `trait`-Parameter (mehrfach = UND) und `traits`-Feld im DTO. + +### `GET /api/traders/{id}` +**TraderDetailDto** — alles aus TraderDto plus: `notes`, `manualPriorityOverride`, +Zeitfenster `pnL7d`/`winRate7d`/`pnL24h`/`winRate24h`, `currentBalance` +(kumulierter Cashflow seit Tracking-Beginn), Teil-Scores `activityScore`/ +`qualityScore`/`volumeScore`/`timingScore`, `rank`, `createdAt`, +`aiStrategySummary`, `recentTrades` (letzte 50, **TradeDto**), +`categoryPerformances` (je Kategorie: `category`, `totalVolume`, `totalPnL`, +`totalTrades` = abgeschlossene Märkte, `winningTrades`, `winRate` 0–1). + +**TradeDto:** `id`, `traderId`, `traderName`, `platform`, `dbMarketId`, `marketId` +(ConditionId), `marketQuestion`, `outcome`, `side` (`Buy`/`Sell`/`Redeem`/`Split`/ +`Merge`/…), `price`, `size`, `amount`, `executedAt`. + +### `GET /api/traders/{id}/deep-dive` +On-the-fly-Analyse über die letzten 500 Trades (lädt bei Bedarf Preis-Historie nach — +langsamster Endpoint). Felder: `classifiedStrategy`, `isSuspectedBot`, +`avgHoldDurationHours`, `avgPositionSizeUsd`, `marketsTraded`, `hedgingFrequency`, +`timingAccuracy`, `entryQuality`, `exitQuality` (je 0–100, 50 = neutral), +`botIndicators` (string[]), `summary`, `recentTrades` (100). + +### `GET /api/traders/{id}/positions` +Offene und realisierte Positionen. Je **TraderPositionDto**: `marketId`, +`marketName`, `category`, `outcomeToken`, `sharesHeld`, `avgCost`, `realizedPnl`, +`unrealizedPnl`, `currentPrice`, `lastTradeExecutedAt`. + +### `POST /api/traders?platform=Polymarket&wallet=0x…` +Trader manuell anlegen (Import + Discovery-Flag). Antwort: Trader-Id. + +### `POST /api/traders/{id}/priority?score=…` +Manuellen Prioritäts-Override setzen (ohne `score`: löschen). + +### `POST /api/traders/{id}/watchlist` · `DELETE /api/traders/{id}/watchlist` +Watchlist-Zugehörigkeit setzen/entfernen. Watchlist-Trader sind von Retention/ +Cleanup ausgenommen und werden bevorzugt synchronisiert/enriched. + +### `POST /api/traders/{id}/ai-analysis?manual=true|false` +LLM-Strategie-Analyse anstoßen (synchron; `manual=true` nutzt das teurere Modell). +Antwort: `{ summary }`. Persistiert `aiStrategySummary`, `strategy`, `isSuspectedBot`. + +--- + +## Markets + +### `GET /api/markets` +Aktive Märkte. Parameter: `skip`/`take`, `platform`, `category` +(z. B. `Sports`, `Politics`, `Crypto`), `query` (Textsuche in Frage/ConditionId). +Je **MarketDto**: `id`, `platform`, `question`, `volume`, `liquidity`, `endDate`, +`isResolved`. + +### `GET /api/markets/{id}` +**MarketDetailDto**: zusätzlich `platformMarketId` (ConditionId), `description`, +`category`, `subcategory`, `resolutionOutcome`, `imageUrl`, `botActivityScore`/ +`uniqueTradersCount`/`averageTradeSize` (⚠ derzeit immer 0 — MarketAnalytics wird +noch nicht berechnet), `outcomes` (`name`, `price`), `recentTrades` (50). + +--- + +## Watchlist + +### `GET /api/watchlist` +Alle Einträge: `traderId`, `displayName`, `platformUserId`, `platform`, `totalPnl`, +`winRate`, `copytradingScore`, `label`, `notes`, `createdAt`. + +--- + +## Jobs (asynchrone Hintergrund-Aufträge) + +Jobs werden von den Workern abgearbeitet (Worker müssen laufen!). Status: +`Pending` → `InProgress` → `Completed`/`Failed`. + +| Endpoint | Wirkung | +|---|---| +| `GET /api/jobs?skip&take` | Liste, neueste zuerst: `id`, `jobType`, `status`, `traderId`, `traderName`, `createdAt`, `startedAt`, `completedAt`, `errorMessage` | +| `POST /api/jobs/sync/{traderId}` | Trade-Historie synchronisieren (HistorySync) | +| `POST /api/jobs/analyze/{traderId}` | PnL-Engine + Copytrading-Estimator (TraderAnalysis) | +| `POST /api/jobs/deep-resync/{traderId}` | Komplette Historie paginiert neu importieren; löscht vorher COMPACT-Aggregate und alle Positionen des Traders | +| `POST /api/jobs/deep-resync-pruned` | DeepResync-Jobs für alle Trader mit gepruneten Positionen/COMPACT-Trades enqueuen | +| `POST /api/jobs/analyze-backlog?take=50` | TraderAnalysis-Jobs für unanalysierte Trader enqueuen (nie analysiert zuerst, dann nach TotalTrades) | + +--- + +## Dashboard, Suche, Alerts, Sonstiges + +| Endpoint | Beschreibung | +|---|---| +| `GET /api/dashboard` | Kennzahlen-Übersicht: `totalTraders`, `activeTraders24h`, `totalTrades`, `volume24h`, `unreadAlerts`, `watchlistCount`, `topTraders` (Top 5 nach PnL7d), `largestTrades` (24h), `recentAlerts`, `platformBreakdown` | +| `GET /api/search?q=…` | Sucht Trader (DisplayName **und** Wallet-Adresse) und Märkte; Antwort `{ traders, markets }` | +| `GET /api/alerts?count&unreadOnly` | Alert-Liste | +| `PUT /api/alerts/{id}/read` | Alert als gelesen markieren | +| `GET /api/health` | `{ status, timestamp }` | + +## Dev (Diagnose) + +| Endpoint | Beschreibung | +|---|---| +| `GET /api/dev/verify-positions/{traderId}` | Vergleicht unsere `TraderPositions` mit Polymarkets `/positions`-API; Antwort: `mismatches[]`, `match`, `dbPositionsCount`, `apiPositionsCount` | + +> Ein früherer `POST /api/dev/repair-db` wurde entfernt (fehlerhaftes SQL, +> destruktiv). Reparatur läuft über den WinForms-Menüpunkt **Development → +> Recalculate All Traders**. + +--- + +## Geplante Erweiterungen (FIXPLAN Teil D/E — für API-Konsumenten relevant) + +- `GET /api/traits` + `trait`-Filter auf `/api/traders`; `traits`-Liste in den Trader-DTOs (D2). +- Markt-Rendite-Metriken im Detail-DTO: `medianWinReturnPct`, `avgWinReturnPct`, + `medianLossReturnPct`, `avgLossReturnPct`, `profitFactor` (D2c). +- `GET /api/traders/{id}/profile` — vollständiges Analyse-Profil eines Traders in + einem Aufruf (Kennzahlen + Zeitfenster + Traits + Fingerprint-Verteilungen + + Kopierbarkeits-Aggregate), gedacht für externe Konsumenten wie PolyTrader (Teil E). +- Out-of-Sample-Fenster (A/B), Preisband-Profil, Stop-Loss-Klassifikation, + Portfolio-/Korrelations-Sicht (Teil E). diff --git a/src/Predictalytics.Api/Endpoints/DevEndpoints.cs b/src/Predictalytics.Api/Endpoints/DevEndpoints.cs index 47ffe35..b279774 100644 --- a/src/Predictalytics.Api/Endpoints/DevEndpoints.cs +++ b/src/Predictalytics.Api/Endpoints/DevEndpoints.cs @@ -13,7 +13,7 @@ public static class DevEndpoints { public static void MapDevEndpoints(this IEndpointRouteBuilder routes) { - var group = routes.MapGroup("/api/dev"); + var group = routes.MapGroup("/api/dev").WithTags("Dev"); group.MapGet("/verify-positions/{traderId:int}", async (int traderId, AppDbContext db, IEnumerable providers, CancellationToken ct) => { @@ -68,30 +68,11 @@ public static class DevEndpoints }); }); - group.MapPost("/repair-db", async (AppDbContext db, CancellationToken ct) => - { - await db.Database.ExecuteSqlRawAsync(@" - -- 1. Fake PnL Trades löschen - DELETE FROM Trades WHERE Type = 4 AND Payout = 0 AND Size = 0; - - -- 2. Positionen löschen, da sie durch Sync neu aufgebaut werden - DELETE FROM TraderPositions; - - -- 3. Sync State von Tradern zurücksetzen (DeepResync forcieren) - UPDATE Traders SET - IsInitialImportComplete = 0, - LastTradesUpdatedAt = NULL, - LastPositionsUpdatedAt = NULL, - TotalPnl = 0, - WinRate = 0, - TotalTrades = 0, - EstimatedBankroll = 0; - - -- 4. Jobs abbrechen - UPDATE Jobs SET Status = 5 WHERE Status IN (1, 2); - ", ct); - - return Results.Ok("DB repaired. DeepResync needed."); - }); + // NOTE: a former POST /repair-db endpoint was removed here on purpose: + // its raw SQL referenced non-existent columns/tables (Trades.Type/Payout, + // Traders.LastPositionsUpdatedAt, table "Jobs") and would have deleted ALL + // TraderPositions including the pruned-history conserves. The supported + // repair path is the "Recalculate All Traders" action in the WinForms + // Development menu (EmbeddedWebServer.RunRecalculateAllTradersAsync). } } diff --git a/src/Predictalytics.Api/Endpoints/JobEndpoints.cs b/src/Predictalytics.Api/Endpoints/JobEndpoints.cs index 7a063cf..5fd4327 100644 --- a/src/Predictalytics.Api/Endpoints/JobEndpoints.cs +++ b/src/Predictalytics.Api/Endpoints/JobEndpoints.cs @@ -11,7 +11,7 @@ public static class JobEndpoints { public static void MapJobEndpoints(this IEndpointRouteBuilder routes) { - var group = routes.MapGroup("/api/jobs"); + var group = routes.MapGroup("/api/jobs").WithTags("Jobs"); group.MapGet("/", async (IJobRepository repo, int skip = 0, int take = 50, CancellationToken ct = default) => { diff --git a/src/Predictalytics.Application/DTOs/TraderDto.cs b/src/Predictalytics.Application/DTOs/TraderDto.cs index 5440b06..d1ab44c 100644 --- a/src/Predictalytics.Application/DTOs/TraderDto.cs +++ b/src/Predictalytics.Application/DTOs/TraderDto.cs @@ -41,7 +41,12 @@ public record TraderDetailDto( int Trades30d, decimal PnL30d, decimal WinRate30d, + decimal PnL7d, + decimal WinRate7d, + decimal PnL24h, + decimal WinRate24h, decimal EstimatedBankroll, + decimal CurrentBalance, decimal ActivityScore, decimal QualityScore, decimal VolumeScore, diff --git a/src/Predictalytics.Application/Services/AnalyticsService.cs b/src/Predictalytics.Application/Services/AnalyticsService.cs index 89ff1c1..2aa6ce0 100644 --- a/src/Predictalytics.Application/Services/AnalyticsService.cs +++ b/src/Predictalytics.Application/Services/AnalyticsService.cs @@ -236,7 +236,9 @@ public class AnalyticsService : IAnalyticsService return new TraderDetailDto(trader.Id, trader.Platform.ToString(), trader.PlatformUserId, trader.DisplayName, trader.Notes, trader.Tier.ToString(), trader.Strategy.ToString(), trader.IsSuspectedBot, trader.ManualPriorityOverride, trader.WinRate, trader.TotalPnl, trader.TotalTrades, - a?.Trades30d ?? 0, a?.PnL30d ?? 0, a?.WinRate30d ?? 0, a?.EstimatedBankroll ?? 0, + a?.Trades30d ?? 0, a?.PnL30d ?? 0, a?.WinRate30d ?? 0, + a?.PnL7d ?? 0, a?.WinRate7d ?? 0, a?.PnL24h ?? 0, a?.WinRate24h ?? 0, + a?.EstimatedBankroll ?? 0, a?.CurrentBalance ?? 0, s?.ActivityScore ?? 0, s?.QualityScore ?? 0, s?.VolumeScore ?? 0, s?.TimingScore ?? 0, s?.CombinedScore ?? 0, a?.CopytradingScore ?? 0, a?.CopytradingQualityScore ?? 0, a?.CopytradingCopyabilityScore ?? 0, s?.Rank ?? 0, wl != null, trader.CreatedAt, trader.LastPolledAt, trader.AiStrategySummary,