API audit: expose 7d/24h windows, remove broken repair-db, add API docs

- 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 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-12 18:34:02 +02:00
co-authored by Claude Fable 5
parent 28e112f128
commit a1fcb4ace5
6 changed files with 264 additions and 28 deletions
+102
View File
@@ -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 9095-¢-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.
+146
View File
@@ -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 0100-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` 01).
**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 0100, 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).
@@ -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<IPlatformProvider> 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).
}
}
@@ -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) =>
{
@@ -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,
@@ -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,