Docs: Konzepte/Plaene in docs/ mit Typ-Unterordnern buendeln

Root aufgeraeumt: alle Konzept-/Plan-/Fach-Dokumente nach docs/ verschoben,
organisiert nach Typ (wie fuer ein separates Docs-Repo vorgeschlagen, aber bewusst
in diesem Repo, damit Plan->umsetzende-Commits nachvollziehbar bleiben):
- docs/konzepte/       (KONZEPT-*)
- docs/umsetzungsplaene/ (UMSETZUNGSPLAN-*)
- docs/ideen/          (fruehe Ideen, Platzhalter)
- docs/pruefplaene/    (PRUEFPLAN-*)
- docs/steuer/         (Steuer-/Buchhaltungs-Doks, z.B. US-CPA-Fragebogen)
- docs/README.md       (Index/Konventionen)

Getrackte Plaene als Rename verschoben (History erhalten); zuvor untracked Konzept-/
Plan-Dateien jetzt versioniert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-14 10:15:08 +02:00
co-authored by Claude Opus 4.8
parent 1c3a364df2
commit c5f0b1d188
15 changed files with 1004 additions and 0 deletions
@@ -0,0 +1,126 @@
# Umsetzungsplan: AI-Bewertung der Auflösequalität (C-S4)
> Stand: 2026-07-11
> Ziel: Ein LLM (via OpenRouter) bewertet je Markt das RESOLUTION-Risiko (subjektive
> Auflösequellen, Regeltext-Fallen, UMA-Dispute-Muster). Die Bewertung wird beim
> Markt-Import in der DB gespeichert und dient als Entry-Gate — zuerst im
> ResolutionFarming, perspektivisch auch im Copytrading.
> Einordnung: Core-Baustein (beide Module profitieren), kein eigenes Strategiemodul.
---
## 1. Abgrenzung (wichtig für die Umsetzung)
Das LLM prognostiziert NICHT den Markt-Ausgang. Es beantwortet ausschließlich:
**„Wie sauber/objektiv wird dieser Markt aufgelöst werden?"** — Input ist der
Regeltext, nicht das Weltgeschehen. Das hält die Aufgabe eng, billig und testbar.
## 2. Architektur
### 2.1 Core-Service `IMarketRiskRater` (PolyTrader.Core)
```csharp
public interface IMarketRiskRater
{
/// Liefert die (ggf. gecachte) Bewertung; null wenn (noch) keine vorliegt.
Task<MarketRiskRating?> GetOrRateAsync(MarketData market, CancellationToken ct);
}
public class MarketRiskRating // Tabelle core_market_risk_ratings
{
public string ConditionId { get; set; } // PK — Regeltexte ändern sich nicht → dauerhaft cachebar
public int Score { get; set; } // 0100 (100 = völlig objektiv auflösbar)
public string Flags { get; set; } // CSV: SUBJECTIVE_SOURCE, DEADLINE_AMBIGUITY,
// MENTIONS_TYPE, DISPUTE_PATTERN, MISSING_RULES
public string Reason { get; set; } // Einzeiler-Begründung des Modells
public string Model { get; set; } // verwendetes Modell (Nachvollziehbarkeit)
public DateTime RatedAt { get; set; }
}
```
### 2.2 `OpenRouterClient` (Core, dünn)
- HTTP-Client gegen https://openrouter.ai/api/v1/chat/completions, API-Key aus
appsettings (`OpenRouter:ApiKey`, gitignoriert wie andere Secrets).
- **Modellwahl:** günstiges Modell reicht (Haiku-Klasse, z. B.
`anthropic/claude-haiku-4.5` via OpenRouter; Modell-ID als Setting, nicht
hartkodieren). Kosten je Markt: Bruchteile eines Cents; mit Cache je ConditionId
einmalig pro Markt.
- **Structured Output:** Antwort als JSON erzwingen (Schema im Prompt + JSON-Mode);
bei Parse-Fehler 1 Retry, danach „kein Rating" (fail-closed, siehe 3.3).
- Timeout kurz (~15 s), Fehler loggen, NIE den aufrufenden Scanner blockieren.
### 2.3 Prompt (Kern, bei Umsetzung feinjustieren)
Input je Markt: `Question`, `Description`/Resolution-Kriterien (Gamma-API liefert
den Regeltext am Markt-Objekt — Feld bei Umsetzung verifizieren), Kategorie, EndDate.
Bewertungsauftrag an das Modell (sinngemäß):
1. Gibt es eine EINDEUTIGE, öffentlich prüfbare Auflösequelle (offizielles
Endergebnis, behördliche Zahl, On-Chain-Fakt)?
2. Sind Randfälle geregelt (Verschiebung, Abbruch, Unentschieden, Definitionsfragen
wie „offiziell angekündigt")?
3. Ähnelt der Markt bekannten Streit-Mustern (Mentions-/„sagt X"-Märkte, vage
Deadlines, Definitions-Ambiguität, mehrdeutige Quellen)?
Output: `{ "score": 0-100, "flags": [...], "reason": "…" }`.
## 3. Integration
### 3.1 Wann wird bewertet?
Beim Markt-Import bzw. beim ersten Kontakt: Der **RF-Scanner** ruft
`GetOrRateAsync` für jeden Kandidaten auf, der alle billigen Filter passiert hat
(NACH Preisband/Kategorie/Fenster, VOR dem Accept — kein LLM-Call für offensichtliche
Rejects). Cache macht Wiederholungs-Scans kostenlos.
### 3.2 Entry-Gate im ResolutionFarming
Neue `RfSettings`:
- `MinResolutionScore` (Default 70): Kandidaten mit Score darunter → Reject mit
Grund `"AI-Resolution-Score {score} < {min}: {reason}"` (landet wie alle Rejects
in rf_candidates → die Kalibrierung kann später prüfen, ob das Gate Geld spart!).
- `RequireRating` (Default true): fail-closed-Schalter (siehe 3.3).
### 3.3 Fail-Closed-Regeln (Sicherheitskern)
1. Kein Rating verfügbar (API down, Parse-Fehler, kein Regeltext) → Kandidat gilt
als riskant und wird abgelehnt — AUSSER die Kategorie steht auf einer
Objektiv-Whitelist (Default: Sports-Endergebnisse), dann Durchlass mit Log.
2. Das Rating **ersetzt die harte Blacklist nicht**: bekannte Giftmuster
(Mentions-Märkte etc.) bleiben in `BlacklistCsv` hart geblockt. Das LLM ist die
zweite Verteidigungslinie für den Long Tail, nicht die erste.
3. **Post-Mortem-Pflicht:** Jeder real erlebte Dispute → Muster in Blacklist/Prompt
aufnehmen. Dafür Flag-Feld in rf_closed_trades (`ResolutionDisputed`) vorsehen.
### 3.4 Copytrading (zweiter Schritt, optional)
Gleicher Service: Vor einem BUY den Score prüfen; unter Schwelle → Trade
verwerfen mit TradeReasoning-Log. Als Per-Account-Setting (`MinResolutionScore`,
0 = aus), Default zunächst AUS, um das Copy-Verhalten nicht zu verändern, bis der
Rater validiert ist.
## 4. Validierung VOR dem Scharfschalten (Pflicht-Phase)
1. **Retrospektiv-Test:** ~1520 öffentlich bekannte, umstrittene UMA-Resolutions
(Recherche-Aufgabe: bekannte Dispute-Fälle) + ~30 unstrittig aufgelöste
Vergleichsmärkte durch den Rater schicken. Messen: erwischt er die Streitfälle
(niedriger Score), ohne die sauberen zu blockieren?
2. **Akzeptanz:** ≥ 80 % der Streitfälle unter der Schwelle, ≤ 10 % der sauberen
fälschlich blockiert. Sonst Prompt/Schwelle iterieren.
3. Ergebnisse als Testdaten einfrieren (Golden-File-Test, läuft ohne API gegen
gespeicherte Antworten — kein LLM-Call in der CI).
## 5. Phasen
| Phase | Inhalt | Akzeptanz |
|---|---|---|
| AR-1 | OpenRouterClient + IMarketRiskRater + Tabelle/Migration + Unit-Tests (Parsing, Fail-Closed) | Build grün; Rater liefert für einen Beispiel-Regeltext strukturiertes Rating |
| AR-2 | Retrospektiv-Validierung (4.) | Akzeptanzquoten erreicht, dokumentiert |
| AR-3 | RF-Integration (3.1/3.2/3.3) + UI-Spalte (Score in Kandidaten-Tab) | Rejects mit AI-Grund sichtbar; Kalibrierung kann Gate-Wirkung auswerten |
| AR-4 | Copytrading-Integration (3.4), Default aus | Setting vorhanden, dokumentiert |
## 6. Leitplanken
- API-Key nie committen; Kosten-Deckel (max. Ratings/Tag als Setting, Default 500).
- Der Rater beeinflusst NIE Exits, nur Entries (keine Panik-Verkäufe durch LLM).
- Score/Reason immer mitloggen — Entscheidungen müssen im Log nachvollziehbar sein.
@@ -0,0 +1,117 @@
# Umsetzungsplan: On-Chain-Auto-Redeem (modulweise schaltbar)
> Stand: 2026-07-11
> Ziel: Gewonnene Positionen automatisch on-chain einlösen (Shares → USDC), als
> **Core-Baustein** mit **Aktivierung je Modul**. Richards Anforderung: In Testphasen
> neuer Module soll Auto-Redeem gezielt AUS bleiben können, um die Performance der
> Entwicklung manuell nachvollziehen zu können — ohne dass andere, etablierte Module
> ihren Automatismus verlieren.
> ⚠️ Höchste clob.md-Kritikalität: On-Chain-Signing mit echten Private Keys.
> Jede Phase: Backup/Commit vorher, Testmarkt/Kleinstbetrag zuerst.
---
## 1. Architektur: Queue statt Direktaufruf
Module redeemen nie selbst. Sie melden einlösbare Positionen in eine zentrale
Queue; ein Core-Worker arbeitet sie ab — nur für Module, deren Auto-Redeem aktiv ist.
```
Modul (RF-Monitor, CT-Sync, später DD) PolyTrader.Core
│ erkennt „Position gewonnen & aufgelöst" │
├── IRedeemQueue.Enqueue(RedeemRequest) ─────────────▶│ Tabelle core_redeem_queue
│ (immer! unabhängig vom Schalter) │
│ ▼
│ OnChainRedeemWorker (BackgroundService)
│ • nur Requests von Modulen mit AutoRedeemEnabled
│ • Rest bleibt als "Manual" sichtbar (UI-Liste)
▼ • CTF redeemPositions / NegRiskAdapter
UI je Modul: Pending-Redeems-Ansicht • Verifikation über USDC-Balance-Delta
```
**Warum immer enqueuen:** Auch bei deaktiviertem Auto-Redeem entsteht so eine
vollständige, je Modul gefilterte „zum Redeem bereit"-Liste (Dashboard/UI) — genau
die Übersicht, die Richard in Testphasen für die manuelle Betreuung will. Der
Schalter entscheidet nur, ob der Worker sie abarbeitet.
### 1.1 Datenmodell (`core_redeem_queue`)
| Feld | Inhalt |
|---|---|
| Id (auto) | PK |
| ModuleName | "ResolutionFarming" / "CopyTrading" / … |
| AccountId, TokenId, ConditionId | Ziel der Einlösung (ConditionId zwingend — für den Contract-Call) |
| IsNegRisk | Adapter-Wahl |
| SizeShares, ExpectedUsd | erwartete Auszahlung (Shares × 1.00) |
| Status | Pending / Processing / Done / Failed / Manual |
| Attempts, LastError, EnqueuedAt, CompletedAt, TxHash | Betrieb/Nachvollziehbarkeit |
### 1.2 Schalter
- **Je Modul:** `core_module_settings` (oder appsettings-Sektion) —
`AutoRedeem:ResolutionFarming = false`, `AutoRedeem:CopyTrading = false`
(Default IMMER aus; bewusstes Einschalten je Modul).
- **Global-Not-Aus:** `AutoRedeemGlobalEnabled` (analog GlobalTradingPaused,
Quickbar-Toggle) — schlägt alle Modul-Schalter.
- UI: Schalter je Modul in dessen Settings-Tab + Anzeige im Core-Dashboard,
welche Module aktiv sind.
## 2. Der On-Chain-Teil (`OnChainCtfService`, Core)
1. **Bibliothek:** Nethereum (bereits als Abhängigkeit im Projekt für EIP-712-Signing
vorhanden) — Contract-Calls über die bestehende Alchemy-RPC-Anbindung (Polygon).
2. **Aufrufe:** Standard-Markt: ConditionalTokens `redeemPositions(collateral,
parentCollectionId=0x0, conditionId, indexSets)`; NegRisk-Markt: über den
NegRisk-Adapter. **Contract-Adressen + ABI + indexSets-Ermittlung bei Umsetzung
zwingend aus https://docs.polymarket.com (Developer/CTF) verifizieren — nicht aus
dem Gedächtnis kodieren.** Die USDC-/CTF-Adressen als Konstanten mit Quellenangabe.
3. **Gas:** Wallet braucht POL. Vor jedem Call Balance-Check; unter Schwelle
(Setting, z. B. 0.5 POL) → Request auf `Manual` + Threema-Warnung „POL nachfüllen".
Gas-Preis: Standard-Estimation, Cap als Setting.
4. **Verifikation = Wahrheit:** Ein Redeem gilt erst als `Done`, wenn (a) die Tx
bestätigt ist UND (b) das USDC-Balance-Delta ≈ ExpectedUsd (Toleranz) gemessen
wurde. Sonst `Failed` mit Fehlertext.
5. **Retry:** max. 3 Versuche mit Backoff (1/10/60 min), danach `Manual` + Threema.
Idempotenz beachten: vor jedem Versuch prüfen, ob die Position on-chain überhaupt
noch einlösbar ist (bereits redeemte Shares → als Done werten, nicht als Fehler).
## 3. Modul-Integration
### 3.1 ResolutionFarming (erster Nutzer)
`FarmingResolutionMonitorService` setzt heute `RedeemStatus = "Pending"` am
RfClosedTrade. Ergänzung: beim Schließen eines Gewinners → `IRedeemQueue.Enqueue`.
Worker-Callback (oder Status-Poll) aktualisiert `RedeemStatus` (Pending → Redeemed/
Manual/Failed) → sichtbar im Historie-Tab. **Wichtig für Richards Testphasen-
Anforderung:** Der realisierte PnL ist bereits bei Resolution gebucht; der Redeem
ändert nur die Kapitalverfügbarkeit. Die Performance-Auswertung bleibt also mit und
ohne Auto-Redeem identisch — nur die Bankroll-Rotation unterscheidet sich.
### 3.2 CopyTrading (zweiter Nutzer)
Im `TraderMonitorService` existiert die Stelle bereits: der auskommentierte
Python-Redeem-Block im Resolution-Fallback („bereit für manuellen Redeem")
— dort `Enqueue` statt Kommentar. Gleicher PnL-Hinweis wie oben.
### 3.3 Künftige Module
Contract: Ein Modul, das Positionen bis Resolution hält, ruft bei „gewonnen &
aufgelöst" genau einmal `Enqueue` und liest optional den Status zurück. Mehr nicht.
## 4. Phasen & Akzeptanzkriterien
| Phase | Inhalt | Akzeptanz |
|---|---|---|
| RD-1 | Queue-Tabelle + IRedeemQueue + Modul-Schalter + UI-Pending-Liste (noch KEIN On-Chain-Code) | Module enqueuen; Liste zeigt je Modul „bereit zum Redeem"; Schalter sichtbar; 100 % ohne Live-API testbar |
| RD-2 | `OnChainCtfService` gegen Polygon: zuerst READ-only (Balance, Einlösbarkeits-Check) | Balance-/Zustandsabfragen stimmen gegen Polygonscan |
| RD-3 | Erster echter Redeem: EIN Testmarkt, Kleinstbetrag, manuell getriggert (Button an der Pending-Liste) | Tx bestätigt, USDC-Delta verifiziert, Status Done |
| RD-4 | Worker-Automatik scharf für RF (Schalter an), CT folgt nach Beobachtung | 1 Woche fehlerfreier Betrieb, Failed-Quote < 5 %, POL-Warnung getestet |
## 5. Leitplanken
1. `.agents/rules/clob.md` gilt verschärft: Private-Key-Nutzung außerhalb des
erprobten Order-Signing-Pfads. Jede Phase einzeln committen; RD-3 nie
überspringen.
2. Der Worker fasst NUR Queue-Einträge an — er scannt nie selbst Positionen
(klare Verantwortung: Module erkennen, Core löst ein).
3. Alle Beträge/TxHashes loggen; Threema-Tageszusammenfassung „X Redeems, Y USDC
freigesetzt, Z manuell offen".
4. Manuelle Redeems (über die Website) müssen erkannt werden: Einlösbarkeits-Check
in 2.5 markiert extern eingelöste Einträge als Done statt Failed.
@@ -0,0 +1,314 @@
# Umsetzungsplan: Copytrading-Modul — Rentabilitäts-Verbesserungen
> Stand: 2026-07-06
> Ziel: Bekannte Verlustquellen im Copytrading-Modul beseitigen und die
> Rentabilität durch datengetriebene Trader-Auswahl, echte Fill-Daten und
> besseres SELL-Handling steigern.
> Reihenfolge: **Dieser Plan zuerst.** Phase 1 (Marktdaten-Fundament) ist
> Voraussetzung für die Strategiemodule MarketMaking und BundleArbitrage.
---
## 0. Kontext & Hintergrund (für die Umsetzung ohne Vorwissen)
Das Copytrading-Modul (`src/PolyTrader.Modules.CopyTrading/`) kopiert Trades von
Master-Tradern auf Polymarket. Signalkette:
1. `AlchemyWebsocketService` erkennt On-Chain-Events der Master-Wallets (WSS).
2. `TraderMonitorService.TriggerFastBlockchainPoll()` parst die Transaktion direkt
(Fast Track, ~34 s hinter dem Master) oder fällt auf Data-API-Polling zurück.
3. Signale (`CopySignal`) laufen über einen Channel in die `CopyTradingEngine`.
4. Die Engine prüft Risiko-Limits (`CopyTradingAccountSettings`: PerMarketLimit,
PerMasterLimit, Zeitfenster-Limits, MaxBuyPrice) und platziert CLOB-Orders
über `PolymarketClobClient` (Core).
**Historischer Kontext (wichtig!):** Eine Verlustanalyse im April 2026
(`agentspace/prompts/AnalyzingOvernightTradingLosses.md`) hat als Hauptursache
für Overnight-Verluste identifiziert, dass der Bot SELLs der Master mit
Market-Orders ins leergeräumte Orderbuch kopiert und so zur „Exit-Liquidity"
wird (Beispiel: Entry 0.51, Master-Exit 0.99, unser Exit 0.49). Der damalige
Fix (GTD-Limit-Sells) ist **im aktuellen Modul-Code nicht mehr vorhanden**
vermutlich bei der Modularisierung verloren gegangen.
**Seit März 2026 erhebt Polymarket Taker-Fees** (Sports ~0,75 %, Politik/Finanzen
~1,0 %, Krypto ~1,8 %, am 50-¢-Preis am höchsten, Richtung 1 ¢/99 ¢ abnehmend;
Maker zahlen nichts und erhalten Rebates). Der Bot ist heute fast immer Taker.
Quelle: https://docs.polymarket.com/trading/fees — **bei Umsetzung aktuellen
Stand verifizieren.**
### Leitplanken (gelten für alle Phasen)
1. `.agents/rules/clob.md` beachten: Änderungen an der CLOB-Integration sind
hochkritisch. Vor jeder Änderung Backup/Commit der alten Version, jede
Änderung mehrfach prüfen.
2. Jede Phase lässt die App baubar und lauffähig zurück (Debug-Build grün).
3. Entscheidungslogik als testbare, pure Funktionen extrahieren und in
`PolyTrader.Tests` (xUnit, existiert bereits) abdecken.
4. Kein Livegang einer Phase ohne mehrtägige Beobachtung auf dem Server.
---
## Phase 0 — Sofortmaßnahmen: Blutung stoppen
### 0.1 🔴 SELL-Exit-Liquidity-Regression beheben (höchste Priorität)
**Befund:** In `CopyTradingEngine.cs` (Live-SELL-Pfad, aktuell ~Zeile 694735)
werden SELLs als `"MARKET"` mit Fallback-Limit `0.01m` gesendet:
```csharp
decimal sellLimit = 0.01m; // Market Order Fallback Limit
...
var result = await _clob.PlaceOrderAsync(account, signal.TokenId, signal.Side,
expectedUsdc, sellLimit, "MARKET", _state.DebugOrderPayloadLog, isNegRisk);
```
Das ist exakt das Verhalten, das die April-Verluste verursacht hat.
**Ziel-Design: Eskalationsleiter statt Market-Order**
1. Referenzpreis = `signal.Price` (Exit-Preis des Masters).
2. Erste Order: GTD-Limit.
- HF-Trader (`trader.Category == "HF"`): `signal.Price - 0.005m`.
- Sonst: `signal.Price * (1 - settings.MaxPriceDifference / 100m)`.
3. Neuer Setting-Wert `SellFloorPct` in `CopyTradingAccountSettings`
(Default z. B. 15 %): absolute Untergrenze = `signal.Price * (1 - SellFloorPct/100)`.
4. Hintergrund-Loop (Erweiterung von `CleanupStaleOpenOrdersAsync` in
`TraderMonitorService` oder eigener Loop): Order nach T Sekunden ohne Fill
(HF: ~20 s, sonst: ~120 s) canceln und eine Stufe tiefer neu platzieren
(Schrittweite z. B. 2 ¢ oder 3 % relativ), bis zum Floor.
5. Floor erreicht und kein Fill → Position halten, **Threema-Benachrichtigung**
senden (`ThreemaService` im Core existiert) und Position als „ExitPending"
markieren.
6. Position darf **nicht mehr optimistisch** aus `account.OpenPositions`
entfernt werden. Stattdessen Flag `ExitPending` (neues Property auf
`Position` oder Tracking-Dictionary im `CopyTradingState`), damit Limits
weiterhin korrekt rechnen und kein Doppel-SELL entsteht. Entfernen erst,
wenn der Fill über Sync/User-Channel (Phase 1) bestätigt ist.
**Preis-/Stufenlogik als pure statische Funktion** implementieren (z. B.
`SellLadder.NextPrice(referencePrice, step, floor, attempt)`) und mit
Unit-Tests abdecken.
**Akzeptanzkriterien:**
- Kein Code-Pfad sendet mehr `"MARKET"`-SELLs mit 0.01-Limit.
- Unit-Tests für Ladder-Preise (HF/normal, Floor-Clamping, 0.01/0.99-Grenzen).
- Log zeigt pro SELL: Referenzpreis, gewähltes Limit, Stufe.
### 0.2 Fee-Modell einführen
1. Fee-Rate je Markt beschaffen: Die CLOB-/Gamma-API liefert Fee-Informationen
am Markt-Objekt (Feldname bei Umsetzung anhand
https://docs.polymarket.com/trading/fees verifizieren, z. B. `fee_rate_bps`).
Fallback: statische Kategorie-Tabelle (Sports 0.75 %, Politics/Finance 1.0 %,
Crypto 1.8 %, Geopolitics 0 %).
2. `MarketData` (Core) um `TakerFeeBps` erweitern (EF-Migration Core),
Befüllung über `MarketSyncService` bzw. beim Markt-Fetch.
3. Risk-Check in `CopyTradingEngine`: erwartete Fee vom verfügbaren Edge
abziehen; Mikro-Trades, deren Fee den erwartbaren Gewinn frisst, verwerfen
(Logging mit Begründung wie bei den bestehenden Checks).
4. PnL-Berechnung (Demo **und** Live-Anzeige) um Fees korrigieren.
**Akzeptanz:** Fee erscheint im TradeReasoning-Log jedes BUY; Demo-PnL weist
Fees aus.
### 0.3 `ProfitTarget` implementieren oder entfernen
`CopyTradingAccountSettings.ProfitTarget` (Default 50.0) existiert in Settings,
DB und UI, wird aber **nirgends ausgewertet** (toter Knopf).
**Empfehlung: implementieren** als optionaler Take-Profit:
- Semantik: `0` = deaktiviert; sonst Prozent-Gewinnschwelle.
- Prüfung im 30-s-Live-Sync (`PollLiveAccountsAsync`): wenn
`CurrentPrice >= EntryPrice * (1 + ProfitTarget/100)` → Verkauf über die
Eskalationsleiter aus 0.1 (Startlimit = CurrentPrice), ExitReason
`"Profit Target"`.
- Zusammenspiel mit `PreRedeemLimit` beachten (beide können feuern —
PreRedeem hat Vorrang, da näher an 1.00).
### 0.4 Kleinere Konsistenz-Fixes
1. **20-Sekunden-Spam-Blockade** (`PendingOrderTimestamps`-Check am Anfang des
SELL-Pfads): blockiert aktuell auch legitime SELLs, wenn der Master < 20 s
nach dem Kauf aussteigt. Fix: Blockade nur für gleichgerichtete Orders
(BUY nach BUY), SELL nach BUY zulassen.
2. **`_state.GlobalPnl`**: wird in `PollLiveAccountsAsync`-Close-Pfaden addiert,
in `PollClosedAccountsAsync` nicht → Anzeige driftet. Vereinheitlichen.
3. **Demo-`ClosedTrade` ohne `TokenId`**: Im Demo-SELL-Pfad wird `TokenId` nicht
gesetzt (Preload von `_processedClosures` filtert auf `TokenId`). Setzen.
---
## Phase 1 — Marktdaten-Fundament (Core-Infrastruktur)
> Diese Phase gehört in **PolyTrader.Core** (`src/PolyTrader.Core/Streaming/`),
> nicht ins Modul — MarketMaking- und BundleArbitrage-Modul (separate Pläne)
> setzen sie voraus.
### 1.1 CLOB User-Channel (echte Fills in Echtzeit)
Polymarket bietet einen authentifizierten WSS-User-Channel, der Order-Events
(Platzierung, Teil-/Voll-Fill, Cancel) der eigenen Accounts pusht.
Endpoint/Protokoll bei Umsetzung verifizieren:
https://docs.polymarket.com (CLOB WSS, `user` channel; Auth via API-Key/
Secret/Passphrase — liegen je Account in `AccountState`).
Neuer Core-Service `ClobUserChannelService : BackgroundService`:
- Verbindet pro Live-Account, Auto-Reconnect mit Backoff (Muster von
`AlchemyWssClient` übernehmen).
- Publiziert Fill-Events intern (Event oder Channel), z. B.
`record OrderFillEvent(int AccountId, string TokenId, string OrderId, string Side, decimal Price, decimal Size, DateTime Ts)`.
Konsumenten im Copytrading-Modul:
- `Position.EntryPrice`/`Size` mit **echten Fill-Daten** aktualisieren
(heute: Limit-Preis als EntryPrice, Korrektur erst im 30-s-REST-Sync).
- SELL-Eskalationsleiter (Phase 0.1): Fill-Bestätigung beendet die Leiter.
- Neue Tabelle `ct_fill_log` (EF-Migration im Modul): SignalPrice, OrderPrice,
FillPrice, Latenz (Signal→Fill in ms), TraderId, AccountId, TokenId, Side.
→ Grundlage für Slippage-Statistik in Phase 3.
### 1.2 CLOB Market-Channel (Orderbücher live)
Neuer Core-Service `ClobMarketDataService`:
- Abonniert den öffentlichen `market`-Channel für eine dynamische Token-Liste
(Subscribe/Unsubscribe zur Laufzeit).
- Hält `OrderBookCache` (Best-Bid/Ask, Tiefe der obersten N Level, Timestamp).
- Interface für Konsumenten: `IOrderBookProvider.TryGetBook(tokenId, maxAgeMs)`.
- REST-Fallback `GET /book` über `PolymarketClobClient`, wenn kein Stream aktiv.
**Hinweis:** Im Modul existiert bereits ein `PolymarketWssClient` (Auto-Redeem).
Nicht verschieben/umbauen (Regression-Risiko), sondern den neuen Core-Service
parallel aufbauen; spätere Konsolidierung als separater Schritt.
### 1.3 Pre-Trade-Orderbuch-Check in der Engine
Vor jedem Live-BUY in `CopyTradingEngine.ProcessAccountOrderAsync`:
1. Buch holen (`IOrderBookProvider`, Fallback REST, Timeout ~150 ms —
bei Timeout Verhalten wie heute, nicht blockieren).
2. Checks (neue Settings in `CopyTradingAccountSettings`):
- `MaxSpreadPct` (Default z. B. 5 %): Spread größer → Skip mit Log.
- Tiefen-Check: liegt an unserem Limit-Preis genug Ask-Size für
`exactShares`? Wenn nein → Skip („Sniping-Verdacht: Liquidität bereits
konsumiert") statt teuer ins dünne Buch zu laufen.
**Akzeptanz Phase 1:** Fill-Log füllt sich mit echten Fills; TradeReasoning
zeigt Spread/Tiefe-Entscheidungen; kein messbarer Latenz-Nachteil im Hot-Path
(> 200 ms Zusatz wäre Regression).
---
## Phase 2 — SELL-Verfeinerung: Proportionalität
Heute (Proportionalitätsfilter in `CopyTradingEngine`, SELL-Pre-Flight):
verkauft der Master < 30 % seines Bestands → ignorieren; ≥ 30 % → **wir
verkaufen alles**. Information über gestaffelte Exits geht verloren.
**Ziel:** Verkaufsquote spiegeln.
1. Beim Öffnen einer Position den Master-Bestand zum Einstiegszeitpunkt
festhalten (`MasterSharesAtEntry`, im `CopyTradingState.MasterTraderPositions`
bzw. auf der Position persistieren).
2. Bei SELL-Signal: `sellRatio = signal.Size / masterSharesVorVerkauf` (wie
heute berechnet). Statt Voll-Exit: `sharesToSell = ourShares * sellRatio`.
3. Untergrenzen beachten: bleibt danach < Polymarket-Minimum (56 Shares) übrig
→ Voll-Exit statt Rest-Dust.
4. Kleiner Teilverkauf (< 10 %) weiterhin ignorieren (Rauschen von Day-Tradern),
Schwelle konfigurierbar (`MinSellRatioPct`).
5. Verkauf läuft immer über die Eskalationsleiter aus Phase 0.1.
Akzeptanz: Unit-Tests für die Ratio-Logik inkl. Dust-Grenzen; Logs zeigen
„Teilverkauf x % gespiegelt".
---
## Phase 3 — Trader-Intelligence (Auswahl automatisieren)
> Beim Copytrading entscheidet die Master-Auswahl über den Großteil des
> Ergebnisses. Diese Phase macht sie messbar und selbstkorrigierend.
### 3.1 Copy-PnL-Score („Kopierbarkeit")
Der `MasterTraderAnalyticsJob` misst heute den PnL des **Masters**. Relevanter
ist, was **wir** mit ihm verdient haben — inkl. unserer Slippage und Fees.
1. Neue Kennzahlen je Master aus `ct_`-Closed-Trades (`ICopyTradeLogRepository`,
Filter `SourceTraderId`, letzte 30 Tage):
- `CopyPnl30d`, `CopyProfitFactor` (Bruttogewinn/Bruttoverlust),
`CopyAvgPnlPerTrade`, `CopyTradeCount30d`.
- `AvgSlippagePct` aus `ct_fill_log` (Phase 1.1): Ø(FillPriceSignalPrice)/SignalPrice.
2. Felder auf `TrackedTrader` ergänzen (+ EF-Migration `mod_copytrading_trackers`),
Berechnung im `MasterTraderAnalyticsJob`, Anzeige in `MastersTradersView`.
3. **Achtung Metrik-Falle:** Winrate allein ist irreführend (Favoriten-Käufer
haben 95 % Winrate und können trotzdem negativ sein). Profit-Faktor und
Ø-PnL/Trade als primäre Sortierung in der UI.
### 3.2 Sniper-/Verhaltens-Metriken in den Analytics-Job
Portierung der Logik aus `analyze_snipers.py` (liegt im Projektroot) nach C#
in den `MasterTraderAnalyticsJob`:
1. Data-API-Activity je Master über volle 3 Tage paginieren (das Skript zeigt
das Pagination-Muster; API-Limit je Request beachten).
2. Kennzahlen: `MedianHoldMinutes`, `SellWithin5MinPct` (Anteil SELLs < 5 min
nach zugehörigem BUY), `SellCount3d`.
3. Schwellen (konfigurierbar): `SellWithin5MinPct > 50 %` → Master als Sniper
flaggen: Warn-Status in UI + Threema-Hinweis. Optional Auto-Pause (siehe 3.3).
### 3.3 Automatischer Kill-Switch je Master
Neue Modul-Settings (global, z. B. in `CopyTradingState` + Persistenz):
`AutoPauseEnabled`, `AutoPauseMinTrades` (z. B. 10), `AutoPauseDrawdownUsd`
oder `-Pct`.
Regel im Analytics-Job (läuft 2×/Tag — zusätzlich stündlicher Light-Check
sinnvoll): Copy-PnL der letzten N Trades unter Schwelle → `IsActive = false`,
`Reasoning` mit Begründung + Zeitstempel befüllen, Threema-Notification.
Reaktivierung bewusst nur manuell.
**Akzeptanz Phase 3:** UI zeigt Copy-Score-Spalten; ein simulierter
Verlust-Master wird automatisch pausiert (Test mit Demo-Daten).
---
## Phase 4 — Maker-Mode & Demo-Realismus
### 4.1 Maker-Einstieg für langsame Master
Für Master mit Haltedauern von Stunden/Tagen (SwissTony/RN1-Typ) ist der
3-Sekunden-Taker-Fill unnötig teuer (Fees + Spread). Neues Verhalten
(Flag je Trader, z. B. `Category == "HOLDER"` oder eigenes Bool `MakerEntry`):
1. BUY als GTC-Limit **auf** Best-Bid (oder Mid 1 Tick) statt über dem Ask.
2. Kein Fill nach T Minuten (konfigurierbar, z. B. 10) und Signal-Markt noch
im Preisband → auf Taker-Verhalten eskalieren oder verwerfen (Setting).
3. Fees: Maker zahlt 0 und sammelt ggf. Rebates — im Fee-Modell (0.2) abbilden.
### 4.2 Demo-Modus realistisch machen
Demo füllt heute zum Signalpreis ohne Slippage/Fees → Demo-Ergebnisse sind
systematisch geschönt und als Validierung neuer Master unbrauchbar.
Fill-Modell im Demo-Pfad der Engine:
`FillPreis = Signalpreis + halber Spread (aus IOrderBookProvider, Fallback
+1 ¢) `, Fee der Marktkategorie abziehen, beides im `ClosedTrade` ausweisen.
**Akzeptanz:** Demo- und Live-PnL desselben Masters weichen über 2 Wochen um
< 20 % relativ ab (grobe Plausibilität statt heutiger Systematik-Lücke).
---
## Offene Entscheidungen (vor Umsetzung mit Richard klären)
1. `SellFloorPct`-Default und Stufen-Timing der Eskalationsleiter (0.1).
2. `ProfitTarget`: implementieren (Empfehlung) oder Feld entfernen?
3. Auto-Pause: nur benachrichtigen oder hart deaktivieren? (Empfehlung: hart,
nachts passiert sonst genau das Falsche.)
4. Maker-Mode: als Trader-Flag oder automatisch aus `MedianHoldMinutes`
ableiten? (Empfehlung: automatisch ab z. B. Median > 60 min, manuell
überschreibbar.)
## Reihenfolge & Abhängigkeiten
```
Phase 0 (sofort, unabhängig)
└── Phase 1 (Core-Infra; parallel zu 0 möglich, Livegang nach 0)
├── Phase 2 (braucht 0.1-Leiter)
├── Phase 3 (braucht 1.1-Fill-Log für Slippage; Rest unabhängig)
└── Phase 4 (braucht 1.2-Orderbuch)
```
@@ -0,0 +1,163 @@
# Umsetzungsplan: Fable-Code-Review-Fixes (Copytrading)
> Basis: Fable-5-Review nach Umsetzung des Rentabilitätsplans (Stand 2026-07-08).
> Ausgangslage: 207 Tests grün, Build/Smoke grün. Die Logic/-Klassen sind laut Review
> sauber; die Lücken liegen im **Zusammenspiel** von SELL-Leiter, Engine und den
> Hintergrund-Services (TraderMonitorService).
## Fortschritt
-**Slice 0** IClobClient-Seam + FakeClobClient (verhaltensneutral).
-**Slice 1** K1/H2/H1: atomarer Claim, Cleanup+Engine schonen Leitern, Floor-Robustheit. 8 Tests.
-**Slice 2** K2: Startup-Reconciliation (GetOpenOrders ohne assetId = alle). 3 Tests.
-**Slice 3** K3 (System-SELL vom Ownership-Check ausgenommen + Resolved-Cache) + M5 (Demo-Score-Anzeige, stündl. Auto-Pause). 5 Tests.
-**Slice 4** H4 (RoundToTick + Dust-Abbruch), M1 (GlobalPnl im Guard), M2 (TokenId), M3-min (serverseitiges Max + lauter Fehlschlag), M4 (Parser 9999), M6 (Fees in Orders), Doku. 10 Tests.
-**Slice 5** H3: BUY-Skip während ExitPending (Entscheidung A).
-**Slice 6** SnapshotService entfernt, Demo-Balance/PnL-Reconciliation, Settings-Validierung (IsLadderConfigInverted + Load-Warnung). 3 Tests.
**Stand: 244 Tests grün, Build/Smoke grün.**
### Nachgelagerte Testabdeckung (nach dem K3-Fund)
- **Engine-Integrationstests** (`CopyTradingEngineTests`, gemockter CLOB): H3 BUY-Skip, Doppel-SELL-Guard, K3 System-Close, Fremd-Trader-Reject, H2 Cleanup-schont-Leiter (+Kontrast). Engine `_clob``IClobClient`, `ProcessAccountOrderAsync` internal.
- **⚠️ K3-Korrektur:** Der Slice-3-Fix sass am falschen Ort (downstream ~Z.643). Der echte Ownership-Check ist der frühe `inPortfolio`-Lookup (~Z.437, `p.SourceTraderId == signal.TraderId`), der System-Signale schon vorher mit early return abwies. Jetzt am richtigen Ort via `IsAuthorizedSell` **vom Engine-Test aufgedeckt**.
- **K1a-Test** (`TraderMonitorServiceTests`): Cleanup cancelt Leiter-Order nicht (aktive Leiter) bzw. cancelt sie ohne Leiter. `_clob``IClobClient`, `CleanupStaleOpenOrdersAsync` internal.
**Alle 3 kritischen + 4 hohen Bugs sind jetzt durch Tests abgesichert** (K1a/K1b/K2/K3/H1/H2/H3/H4).
### Bewusst aufgeschobene Follow-ups (Live-Verifikation/Risiko)
- **M3 Autoincrement-Migration**: `TradeId` auf DB-Autoincrement umstellen Schema-Änderung an der Trade-Persistenz, erst im Zielland live verifizieren. (M3-Minimum ist umgesetzt.)
- **PersistenceService-Dedup-Zeitfenster**: `Exists(AccountId,TokenId)` blockt legit Re-Entries; robuster Fix (z.B. OpenedAt-basiert) braucht Live-Daten Duplikat-Schutz nicht unverifiziert brechen.
- **Perf**: `UpsertLive`-Dirty-Check (Schreib-Amplifikation) und Leiter-Parallelität laut Fable bei aktueller Größe unkritisch.
- **M6/K2**: fee-signierte Orders bzw. `/data/orders` ohne asset_id sind API-gated → im Zielland verifizieren.
## Arbeitsgrundsätze (für jeden Slice)
1. **`.agents/rules/clob.md`:** vor jedem CLOB-nahen Slice ein Commit als Rollback-Punkt;
Preis-/Zustandslogik pur in `SellLogic`/`CopyTradingRisk` + neue Tests; Service-Interaktionen
(Cleanup überspringt Leiter etc.) mit kleinem **Integrationstest über gemockten CLOB-Client**.
2. Nach jedem Slice: `dotnet build` + `dotnet test` (alle grün) + `--smoke-ui` grün, dann commit+push.
3. Ein Slice = eine kohärente Einheit = ein Commit. Reihenfolge unten folgt Fables Empfehlung.
---
## Slice 0 (Prereq): Testbarkeit — `IClobClient`-Interface
**Warum zuerst:** K1/H2/K2 brauchen Integrationstests mit gemocktem CLOB. `PolymarketClobClient`
ist heute eine konkrete Klasse ohne Interface → nicht mockbar.
- Interface `IClobClient` (Core) mit den von Leiter/Reconciliation genutzten Methoden:
`PlaceOrderAsync`, `CancelOrderAsync`, `GetOpenOrdersAsync`, `CancelConflictingOrdersAsync`.
- `PolymarketClobClient : IClobClient`. DI zusätzlich `IClobClient → PolymarketClobClient`.
- `SellLadderService`/Reconciliation gegen `IClobClient` typisieren (Engine kann vorerst konkret bleiben).
- **Verhaltensneutral, keine Logikänderung.** Ermöglicht `FakeClobClient` im Testprojekt.
- Tests: keine neuen fachlichen; Build grün genügt.
---
## Slice 1: „Wer darf Leiter-Orders anfassen" (H1 + K1 + H2) 🔴🟠
Kernthema: Leiter-Order darf nur von der Leiter angefasst/gecancelt werden.
- **H1 — Atomarer Claim:** In `SellLadderService.StartLadderAsync` als ERSTES
`if (!_copyState.ExitLadders.TryAdd(key, placeholder)) return false;` → macht ALLE Aufrufer
(Engine-SELL + ProfitTarget) idempotent. Bei Fehlschlag der Order den Key wieder entfernen.
- **K1 — Cleanup überspringt Leitern:** In `TraderMonitorService.CleanupStaleOpenOrdersAsync`
Keys mit `_copyState.ExitLadders.ContainsKey(key)` überspringen (`continue`).
- **K1 — Floor-Robustheit:** In `SellLadderService.ProcessLadderAsync` am Floor NICHT dauerhaft
früh zurückkehren, sondern periodisch via `GetOpenOrdersAsync` prüfen, ob die Floor-Order noch
ruht; wenn nicht → am Floor neu platzieren (+ `PendingOrderTimestamps` refreshen).
- **H2 — Engine-Cancel schont Leiter:** Den Pre-Signal-`CancelConflictingOrdersAsync`-Aufruf der
Engine überspringen, wenn `_copyState.ExitLadders.ContainsKey(key)` (oder hinter den
ExitPending-Check verschieben).
- Tests: Integrationstest (FakeClob) — Cleanup cancelt KEINE Leiter-Order; zwei parallele
StartLadder-Aufrufe → nur eine Leiter; Floor-Order weg → Leiter platziert neu. Pure: ggf.
Floor-Recheck-Entscheidung.
---
## Slice 2: Neustart-Reconciliation (K2) 🔴
Ruhende GTC-Leiter-/Maker-Orders überleben Neustarts, der Verwaltungszustand nicht.
- Beim Modul-Start je **Live-Account** alle offenen CLOB-Orders via `GetOpenOrdersAsync` abrufen und
pauschal canceln (deterministisch; die Engine entscheidet danach sauber neu). Kein Leiter-Rebuild.
- Ort: eigener Startup-Schritt im Modul (z. B. in `TraderMonitorService`-Warmup oder als kurzer
`IHostedService`), NACH der State-Hydration, VOR dem ersten Signal-Processing.
- Umfangreiches Logging (welche Orders gecancelt).
- Tests: Integrationstest (FakeClob) — für jeden offenen Order-Eintrag wird Cancel gerufen.
---
## Slice 3: Demo-Resolution + Demo-Score (K3 + M5) 🔴🟡
Sonst ist die Demo-Validierungsphase (auf der die Zielland-Strategie beruht) wertlos.
- **K3 — System-Signale (TraderId==0) vom Ownership-Check ausnehmen:** In der Engine SELL-Pre-Flight
(`p.SourceTraderId == signal.TraderId`) den Fall `signal.TraderId == 0` zulassen (System-Close bei
Marktauflösung). Zusätzlich „bereits als resolved erkannt"-Cache, damit ein Markt nur einmal
verarbeitet wird (verhindert 30-s-Loop-Spam + API-Last).
- **M5 — Demo-Score & schnellerer Auto-Pause:** Copy-Score getrennt für Demo (Anzeige/Validierung)
und Live (Pausieren) berechnen; der Kill-Switch filtert weiterhin `!IsDemo`, aber die Demo-Kennzahlen
füllen die Spalten. Zusätzlich stündlicher Light-Check nur für die Pause-Regel (statt nur alle 12 h).
- Tests: Ownership-Ausnahme (Engine), Resolved-Cache (pure). Demo/Live-Score-Trennung ist Job-Logik.
---
## Slice 4: Kleine, klar umrissene Fixes (H4 + M1 + M2 + M3 + M4 + M6 + Doku) 🟠🟡🟢
Jeweils klein und abgegrenzt — in einem oder zwei Commits.
- **H4 — Dust-Reject-Schleife:** (a) Leiter-Preis vor der USDC-Berechnung auf Tick runden
(`Math.Round(next, 3)`, zentral in `SellLogic`); (b) Abbruch in `ProcessLadderAsync`:
`pos.Size < CopyTradingRisk.MinShares` → Leiter beenden, `ExitPending=false`, Dust loggen.
Pure Tests für Rundung + Abbruch.
- **M1 — GlobalPnl-Doppelzählung:** In `TraderMonitorService` (~Z.910 und ~Z.961) das
`GlobalPnl += realizedPnl` INNERHALB des `_processedClosures`-Guards buchen (wie in
`PollClosedAccountsAsync` bereits korrekt).
- **M2 — TokenId in Live-Close-Records:** In beiden Live-Close-Records (~Z.922-940 und ~Z.972-990)
`TokenId = removedPos.TokenId` setzen (der 0.4-Fix erwischte nur den Demo-Pfad).
- **M3 — TradeId robust:** `ClosedTrade.TradeId` auf DB-Autoincrement (`ValueGeneratedOnAdd`)
umstellen + Code-Vergabe (`GetNextTradeId`) entfernen + Migration. Eliminiert die stille
PK-Kollisions-Fehlerklasse und den teuren Full-Table-`Max()`-Startup in `Program.cs`.
(Alternative/Minimum: serverseitiges `Max()` + lauter Fehlschlag statt `catch {}`.)
- **M4 — Parser-Default:** `MongoExportParser` ProfitTarget-Fallback `50m → 9999m` (sonst schaltet
ein erneuter `--migrate-json`-Lauf Take-Profit unbeabsichtigt scharf). Pure Test.
- **M6 — Fee in signierte Orders:** An den Callsites (Engine-BUY, Leiter, PreRedeem)
`actualFeeBps` aus `FeeModel`/`MarketData.TakerFeeBps` an `PlaceOrderAsync` durchreichen.
Verifikation im Zielland, aber die Verdrahtung jetzt.
- **Doku — Stale [Description]:** `SellFloorPct` ist verdrahtet (nicht „Phase 0.1 offen");
`ProfitTarget`-Text nicht mehr „folgt in Phase 0.3". Texte aktualisieren (Richard verlässt sich drauf).
---
## Slice 5: H3 — BUY während ExitPending 🟠
Re-buyt der Master, während unsere Leiter verkauft, kauft die Engine normal zu → die Leiter verkauft
danach `pos.Size` inkl. neuer Shares zum alten Floor.
**ENTSCHEIDUNG (Richard, 2026-07-08): Variante A — BUYs skippen, solange `ExitPending`.**
Während des Ausstiegs keine Zukäufe; die Leiter verkauft die Position sauber zu Ende.
- Umsetzung: In der Engine BUY-Pre-Flight früh prüfen —
`if (account.OpenPositions.TryGetValue(signal.TokenId, out var p) && p.ExitPending) { log + return; }`.
(Spiegelt die bestehende Double-Sell-Guard-Logik, nur für den BUY-Pfad.)
- Umfangreiches Logging (verworfener BUY während aktivem Exit inkl. TokenId/TraderId).
- Tests: Engine-BUY-Pfad überspringt, solange `ExitPending`; nach Leiter-Ende (ExitPending=false)
wird ein neuer BUY wieder normal ausgeführt.
---
## Slice 6: Rest nach Gelegenheit (🟢 Perf/Doku)
- **PersistenceService-Dedup:** `Exists(AccountId, TokenId)` blockt legitime Re-Entries (HF-Alltag) →
Dedup-Schlüssel um Zeitfenster ergänzen. (Copy-Score untererfasst sonst.)
- **Demo-Balance vs. PnL:** Balance sollte `exitUsd ExitFee` gutschreiben (und der BUY die Entry-Fee
abziehen), damit Σ(Balance-Änderungen) = Σ(PnL). Aktuell driftet es um die Fees.
- **Settings-Validierung:** `MaxPriceDifference% > SellFloorPct` → Leiter startet unter dem Floor
(sofortige „Floor erreicht"-Notification). UI-Warnung/Validierung.
- **Perf — Schreib-Amplifikation:** `PollLiveAccountsAsync` `UpsertLive` je Position alle 30 s →
Dirty-Check (nur bei Änderung) oder Batch.
- **Perf — Leitern seriell:** `ProcessLadderAsync` pro Tick seriell → begrenzte Parallelität + Timeout.
- **Totcode:** `services/SnapshotService.cs` entfernen (nirgends registriert) oder bewusst reaktivieren.
---
## Empfohlene Reihenfolge (Fable)
`Slice 0` (Test-Infra) → `Slice 1` (K1+H2+H1) → `Slice 2` (K2) → `Slice 3` (K3+M5) →
`Slice 4` (H4/M1/M2/M3/M4/M6/Doku) → `Slice 5` (H3, nach Entscheidung) → `Slice 6` (Rest).
**Als korrekt bestätigt (nicht anfassen):** Logic/-Klassen sauber/verhaltenstreu; ExitPending
EF-ignoriert; TotalFees gemappt; closed_trades-Indizes vorhanden; SellLadderService als
Singleton+Hosted (eine Instanz); Copy-Score-Find serverseitig; SELL-Spam-Blockade seitensensitiv.
@@ -0,0 +1,187 @@
# Umsetzungsplan: Modul „BundleArbitrage" (Intra-Market- & NegRisk-Arbitrage)
> Stand: 2026-07-06
> Ziel: Neues Strategiemodul, das Preissummen-Anomalien innerhalb von
> Polymarket erkennt und handelt: YES + NO < $1.00 (binäre Märkte) und
> Summen-Verletzungen in NegRisk-Multi-Outcome-Märkten.
> Reihenfolge: Nach/parallel zu MarketMaking — nutzt dieselbe Orderbuch-
> Infrastruktur. **Harte Voraussetzung:** Phase 1 (Marktdaten-Fundament) aus
> `UMSETZUNGSPLAN-CopyTrading-Verbesserungen.md`.
> **Wichtig:** Dieses Modul startet bewusst als reines Mess-Modul
> (Detection-only). Ob Execution gebaut wird, entscheidet die Messphase.
---
## 0. Strategie-Hintergrund & ehrliche Einordnung
**Mechanik:**
- **Binär:** Kostet YES + NO zusammen < $1.00 (beide zum Ask kaufbar),
ist der Kauf beider Seiten ein garantierter Gewinn: Das Paar zahlt bei
Resolution sicher $1.00 aus — oder kann on-chain sofort zu $1.00 USDC
zusammengelegt werden (CTF `mergePositions`).
- **NegRisk (Multi-Outcome, genau ein Gewinner):** Summe aller YES-Asks < $1.00
→ alle YES kaufen (eines zahlt aus). Komplementär: Überteuerte Summen über
die NO-Seite bzw. NegRisk-Konvertierungen handeln.
**Ehrliche Einordnung (Stand 2026):** Auf den großen Märkten ist das ein
HFT-Spiel — Fenster von Sekunden, dominiert von spezialisierten Bots; die
Taker-Fees seit März 2026 haben viele kleine Anomalien zusätzlich unprofitabel
gemacht. **Die Chance liegt im Long Tail** (kleine/neue Märkte, auf die die
großen Bots nicht schauen) und als **Beifang** der ohnehin laufenden
Orderbuch-Streams des MarketMaking-Moduls. Deshalb: erst messen, dann bauen.
**Fee-Beachtung:** Als Taker fallen je Leg Fees an (kategorieabhängig,
01,8 %). Ein Bundle mit 2 ¢ Brutto-Marge kann nach Fees negativ sein.
Die Profitrechnung muss Fees je Leg von Anfang an enthalten. Maker-seitige
Ausführung (ein Leg ruht als Limit) ist fee-frei, aber nicht atomar.
---
## 1. Architektur-Einbettung
Neues Projekt `src/PolyTrader.Modules.BundleArbitrage/` als `IPolyTraderModule`
(`Name = "BundleArbitrage"`, `DbPrefix = "ba_"`), Registrierung in `Program.cs`.
**Eigener Polymarket-Account** (gleiche Begründung wie in den anderen
Modul-Plänen; kann sich in v1 den Account mit MarketMaking teilen, sofern
die Inventar-Buchführung getrennt bleibt — Empfehlung: eigener Account,
sobald Execution live geht).
### Persistenz
| Tabelle | Inhalt |
|---|---|
| `ba_opportunities` | Jede erkannte Anomalie: Zeitpunkt, Markt/Event, Legs mit Preisen & ausführbarer Size, Brutto-/Netto-Marge (nach Fees), Lebensdauer (wann verschwunden) |
| `ba_executions` | Ausgeführte Bundles: Legs, Fills, Slippage, Ergebnis |
| `ba_settings` | Schwellen, Size-Limits, Modus (Detect/Execute) |
Die Lebensdauer-Messung („wie lange war die Anomalie ausführbar?") ist der
wichtigste Datenpunkt der Messphase — sie entscheidet, ob unsere
Ausführungslatenz überhaupt konkurrenzfähig ist.
---
## 2. Komponenten
### 2.1 `ArbScannerService : BackgroundService` — Detection
Zwei Datenpfade:
1. **Hot Set (WSS):** Für die vom `ClobMarketDataService` (Core) ohnehin
gestreamten Bücher (MarketMaking-Märkte + Top-Volumen-Märkte) wird bei
jedem Book-Update die Summenprüfung getriggert (< 1 ms, pure Funktion).
2. **Long-Tail-Sweep (REST):** Zyklischer Scan über aktive Märkte
(Gamma-API-Liste, dann CLOB `GET /book` bzw. Batch-Preis-Endpoints —
verfügbare Batch-Endpoints bei Umsetzung in der Doku prüfen).
Rate-Limits respektieren (Batching + Delays wie im
`TraderMonitorService`-Muster); Sweep-Frequenz Setting (z. B. alle 60 s
für 500 Märkte, priorisiert nach Volumen/Neuheit).
**Prüf-Logik (pure, getestete Klasse `BundleMath`):**
- Binär: `bestAskYes + bestAskNo + FeeYes + FeeNo < 1.00 MinMarginPct`.
Ausführbare Size = min(AskSize beider Seiten), ggf. über mehrere Book-Level
kumuliert (Level-2-Sweep-Rechnung).
- NegRisk: `Σ bestAskYes_i + Σ Fees < 1.00 MinMarginPct` über alle Outcomes
eines NegRisk-Events (Event-Gruppierung über Gamma-API; `NegRisk`-Flag
existiert bereits in `MarketData`).
- Jede erkannte Anomalie → `ba_opportunities`; bei Verschwinden (nächstes
Update unterschreitet Schwelle) Lebensdauer nachtragen.
### 2.2 Mess-Auswertung (Phase BA-1, entscheidungsrelevant)
Report (UI-Tab + wöchentlicher Threema-Report):
- Anomalien/Tag nach Marge-Bucket (0,51 %, 12 %, > 2 % netto).
- Verteilung ausführbare Size und Lebensdauer.
- Erwarteter Monatsertrag bei angenommener Erfolgsquote X % =
Σ(Netto-Marge × min(Size, unser Limit)) über gefangene Fenster.
**Go/No-Go-Kriterium für Execution:** erwarteter Ertrag > Entwicklungs- und
Kapitalkosten; realistisch fangbare Fenster (Lebensdauer > unsere Latenz,
konservativ ≥ 23 s).
### 2.3 `ArbExecutionService` — nur nach Go-Entscheidung
1. **Beide Legs gleichzeitig** als IOC-artige Orders senden (CLOB-Ordertypen
FOK/FAK bei Umsetzung in der Doku verifizieren; `PolymarketClobClient`
ggf. erweitern). Preis = erkannter Ask + kleiner Puffer, Size = min-Leg.
2. **Single-Leg-Risiko** (ein Leg füllt, das andere nicht) ist das
Kernproblem — Behandlungsreihenfolge:
a) Sofortiger Retry des offenen Legs (bis Preis `1.00 Fees MinMargin/2`).
b) Kein Fill → offenes Leg als GTC-Maker-Order zum Break-even-Preis stellen.
c) Timeout (Setting, z. B. 10 min) → Leg über Eskalationsleiter abbauen
(Muster aus Copytrading-Plan Phase 0.1) und Verlust in `ba_executions`
verbuchen. `MaxSingleLegLossUsd`-Tageslimit als Kill-Switch.
3. Size-Limits: `MaxUsdPerBundle` (Start 1025), `MaxOpenBundles`,
Tagesbudget.
4. `.agents/rules/clob.md` beachten — jede CLOB-Client-Erweiterung mit
Backup/Commit und Mehrfach-Review.
### 2.4 Kapital-Recycling: CTF `mergePositions` (Phase BA-4)
Ohne Merge bindet jedes Bundle Kapital bis zur Resolution (bei kurzlaufenden
Märkten oft akzeptabel — Priorisierung im Scanner auf EndDate < 7 Tage
umgeht das Problem anfangs).
On-Chain-Merge: YES + NO gleicher Size → $1.00 USDC sofort, via
ConditionalTokens `mergePositions(...)`; NegRisk-Sets über den
NegRisk-Adapter. Implementierung teilt sich Infrastruktur mit dem
Auto-Redeem des ResolutionFarming-Moduls (Phase RF-4) — **gemeinsamen
Core-Baustein `OnChainCtfService` bauen**, nicht zweimal implementieren.
Contract-Adressen/ABI aus https://docs.polymarket.com (Developer/CTF)
verifizieren; Gas (POL) -Handling und Balance-Warnung wie im RF-Plan.
### 2.5 UI
- Tab „Live-Anomalien": aktuelle Opportunities mit Netto-Marge/Size.
- Tab „Messung": Statistik-Report aus 2.2.
- Tab „Executions": Bundles, Single-Leg-Vorfälle, PnL.
- Tab „Settings": Schwellen, Modus-Schalter Detect/Execute (Default: Detect).
---
## 3. Phasen & Akzeptanzkriterien
### Phase BA-1: Detection-only (24 Wochen Messung)
- Scanner (Hot Set + Long-Tail-Sweep), `BundleMath` mit Unit-Tests
(inkl. Fee-Rechnung, Level-2-Kumulation, NegRisk-Summen),
`ba_opportunities`-Logging, Mess-Report.
- Akzeptanz: App baut & läuft; Report nach 2 Wochen vollständig;
dokumentierte Go/No-Go-Empfehlung.
### Phase BA-2: Execution klein (nur bei Go)
- IOC-Doppel-Leg, Single-Leg-Behandlung, Size-Limits, Kill-Switch.
- Zunächst nur binäre Märkte (NegRisk-Execution ist komplexer → BA-3).
- Akzeptanz: ≥ 20 Bundles ausgeführt; Single-Leg-Quote < 20 %;
Netto-PnL nach Fees > 0.
### Phase BA-3: NegRisk-Execution
- Multi-Leg-Bundles (N Outcomes), strengere Size-/Slippage-Grenzen
(mehr Legs = mehr Single-Leg-Risiko).
### Phase BA-4: `OnChainCtfService` (Merge) — Kapital-Recycling
- Gemeinsam mit ResolutionFarming RF-4 (Redeem) als ein Core-Baustein.
- Testmarkt/Kleinstbetrag zuerst; Akzeptanz: Bundle → USDC ohne manuellen
Eingriff, USDC-Delta verifiziert.
---
## 4. Risiken & Gegenmaßnahmen
| Risiko | Gegenmaßnahme |
|---|---|
| Anomalien existieren, sind aber in < 1 s weg | Messphase BA-1 entscheidet VOR Entwicklungsaufwand für Execution |
| Single-Leg-Exposure | IOC-Orders, Retry-Kaskade, Tages-Verlustlimit, kleine Bundles |
| Fees fressen Marge | Netto-Rechnung inkl. Fees je Leg von Anfang an; `MinMarginPct` konservativ (Start ≥ 1 %) |
| Rate-Limits durch Long-Tail-Sweep | Batching, Priorisierung, Sweep-Frequenz drosseln; API-Fehlerquote überwachen |
| Stale-Book-Falsch-Signale | Max-Age-Check auf Book-Daten (`TryGetBook(maxAgeMs)`); Anomalie erst nach 2 aufeinanderfolgenden Bestätigungen |
| On-Chain-Merge-Fehler | Separater Baustein, Testmarkt, clob.md-Regeln, Balance-Verifikation |
## 5. Offene Entscheidungen
1. `MinMarginPct` (netto, nach Fees) für Detection-Logging (Empfehlung 0,5 %)
vs. Execution (Empfehlung ≥ 1 %).
2. Long-Tail-Sweep-Umfang (alle aktiven Märkte vs. Top-N + Neue) — abhängig
von beobachteten Rate-Limits.
3. Account-Frage: mit MarketMaking teilen oder eigener (Empfehlung: eigener,
sobald BA-2 startet).
4. Priorität von BA-4 (Merge): Bei Fokus auf kurzlaufende Märkte zunächst
verzichtbar — Kapitalbindung von Tagen ist bei kleinen Größen tragbar.
@@ -0,0 +1,200 @@
# Umsetzungsplan: Modul „MarketMaking" (Liquidity Rewards + Spread)
> Stand: 2026-07-06
> Ziel: Neues Strategiemodul, das beidseitige Limit-Orders in belohnungs-
> berechtigten Polymarket-Märkten stellt und drei Ertragsquellen kombiniert:
> tägliche Liquidity Rewards (USDC), Maker-Rebates und den Spread selbst.
> Reihenfolge: Nach ResolutionFarming. **Harte Voraussetzung:** Phase 1
> (Marktdaten-Fundament: `ClobMarketDataService`, `ClobUserChannelService`)
> aus `UMSETZUNGSPLAN-CopyTrading-Verbesserungen.md`.
---
## 0. Strategie-Hintergrund
Polymarket zahlt täglich (00:00 UTC) USDC-Rewards an Wallets, die kompetitive
Resting-Limit-Orders in berechtigten Märkten stellen. Der Reward-Pool liegt
2026 bei > $5 M/Monat (Sport-Peaks ~$8 M). Die Formel belohnt: Nähe zum
Midpoint (innerhalb eines markt-spezifischen Max-Spreads), Ordergröße
(Mindestgröße je Markt) und beidseitige Tiefe (einseitige Orders scoren
reduziert). Seit den Taker-Fees (März 2026) gibt es zusätzlich ein
**Maker-Rebate-Programm** (Anteil der Taker-Fees wird täglich an Maker
ausgeschüttet). Maker zahlen selbst keine Fees.
**Referenzen (bei Umsetzung Formel/Parameter aktuell verifizieren):**
- https://docs.polymarket.com/market-makers/liquidity-rewards
- https://docs.polymarket.com/trading/fees (Maker-Rebates)
- Reward-Parameter je Markt (Max-Spread, Min-Size, Tages-Pool) kommen aus der
Gamma-/CLOB-API am Markt-Objekt.
**Warum dieses Modul strategisch wertvoll ist:** Es ist die einzige Strategie,
bei der wir nicht gegen schnellere Bots um denselben Trade konkurrieren —
Anwesenheit wird bezahlt. Ertrag ist stetig statt direktional.
**Hauptrisiko: Adverse Selection.** Unsere Quotes werden bevorzugt dann
gefüllt, wenn jemand mit besserer Information (News-Bot, Live-Sport-Feed)
gegen uns handelt. Gegenmaßnahmen: Marktauswahl (ruhige, langlaufende Märkte;
anfangs KEINE Live-Sport- und KEINE Krypto-Kurzfrist-Märkte), Inventar-Limits,
Volatilitäts-Pause.
---
## 1. Architektur-Einbettung
Neues Projekt `src/PolyTrader.Modules.MarketMaking/` als `IPolyTraderModule`
(`Name = "MarketMaking"`, `DbPrefix = "mm_"`), Registrierung in `Program.cs`.
**Eigener Polymarket-Account zwingend** (gleiche Begründung wie im
ResolutionFarming-Plan, hier noch kritischer: Der Copytrading-
`TraderMonitorService` würde MM-Inventar als Positionen adoptieren und der
Copytrading-`CancelConflictingOrdersAsync`-Mechanismus würde unsere
Resting-Quotes canceln!).
### Persistenz
| Tabelle | Inhalt |
|---|---|
| `mm_settings` | Globale + je-Markt-Settings (Size, Spread-Ziel, Limits) |
| `mm_markets` | Kuratierte/gescorte Märkte (Reward-Parameter, Status) |
| `mm_quotes_log` | Quote-Historie (Preis, Size, Dauer, Cancel-Grund) — für Reward-Optimierung |
| `mm_fills` | Fills mit Seite, Preis, Inventar danach |
| `mm_daily_pnl` | Tagesabrechnung: Rewards, Rebates, Spread-PnL, Inventar-PnL |
---
## 2. Komponenten
### 2.1 `MarketSelectorJob` — Marktauswahl & Scoring
Täglich + manuell triggerbar:
1. Reward-berechtigte Märkte über Gamma-/CLOB-API listen (Felder: Reward-Pool/
Rate, `rewardsMaxSpread`, `rewardsMinSize` — Feldnamen verifizieren).
2. Score je Markt: `erwarteter Reward pro gequoteter $ ÷ Risiko-Proxy`.
- Reward-Schätzung: Tages-Pool des Markts ÷ beobachtete konkurrierende
Maker-Liquidität innerhalb des Max-Spreads (aus Orderbuch-Snapshots).
- Risiko-Proxy: realisierte Midpoint-Volatilität (Stddev der Mid-Bewegungen
über 24 h aus `ClobMarketDataService`-Daten), Zeit bis Resolution
(je näher, desto gefährlicher), Kategorie.
3. Harte Ausschlüsse (erste Ausbaustufe): Live-Sport (in-play), Krypto-
Kurzfrist-Märkte (15 min/1 h), Märkte < 7 Tage vor EndDate, Midpoint
außerhalb 0.100.90 (Extrempreise = asymmetrisches Inventarrisiko).
4. Output: Ranking in `mm_markets` + UI; Betreiber aktiviert Märkte manuell
(Whitelist-Prinzip — der Bot wählt in v1 nicht selbst).
### 2.2 `QuotingEngine : BackgroundService` — Kern des Moduls
Je aktivem Markt eine Quote-State-Machine:
1. **Zielquote:** Bid und Ask symmetrisch um den Midpoint, Abstand
`QuoteSpreadTicks` (Setting), immer **innerhalb** des Reward-Max-Spreads;
Size ≥ Reward-Min-Size (Setting `QuoteSizeUsd`, initial klein).
2. **Requote-Trigger:** Midpoint-Bewegung > Schwelle (z. B. 1 Tick), eigene
Order gefüllt, Reward-Fenster verletzt. Requote = Cancel + neue Order über
`PolymarketClobClient`.
3. **Churn-Begrenzung:** Mindest-Ruhezeit zwischen Requotes (z. B. 35 s),
Hysterese (nicht bei jedem Tick nachziehen) — API-Rate-Limits und
Order-Spam vermeiden.
4. **Fill-Verarbeitung:** über `ClobUserChannelService` (Echtzeit). Nach Fill:
Inventar aktualisieren, Gegenquote anpassen (siehe 2.3).
5. Alle Quotes/Cancels in `mm_quotes_log` (Grundlage für Optimierung).
Die Preis-/Requote-Logik als **pure, getestete Klasse** (`QuoteCalculator`)
implementieren — Input: Book-Snapshot, Inventar, Settings; Output: Ziel-Quotes.
Unit-Tests in `PolyTrader.Tests` (das ist die kritischste Logik des Moduls).
### 2.3 `InventoryManager` — Risikosteuerung
1. Inventar je Markt = Netto-Shares (YES-äquivalent) × Preis.
2. **Skew:** Bei wachsendem Inventar Quotes asymmetrisch verschieben
(Kaufseite weiter weg, Verkaufsseite näher/attraktiver), Faktor
proportional zu `Inventar / MaxInventoryUsd`.
3. **Limits (Settings je Markt + global):**
- `MaxInventoryUsd` je Markt (Default klein, z. B. 50).
- `MaxTotalInventoryUsd` über alle Märkte.
- Bei Limit-Bruch: Quoting nur noch auf der abbauenden Seite
(„Reduce-Only-Modus") bis Inventar < 50 % des Limits.
4. **Exit vor Resolution:** Ab `ExitHoursBeforeEnd` (Default 48 h) Reduce-Only,
ab 24 h aktiver Abbau (Maker-seitig, notfalls Taker mit Verlust-Deckel).
5. **Volatilitäts-Pause:** Midpoint-Sprung > X % in Y Sekunden → alle Quotes
des Markts canceln, Cooldown Z Minuten (News-Schutz). Global-Kill-Switch
analog `GlobalTradingPaused`.
### 2.4 `RewardTracker`
1. Tägliche Reward-/Rebate-Eingänge erkennen (USDC-Transfers auf die Wallet
via Data-API/Alchemy) und `mm_daily_pnl` zuordnen.
2. Tagesabrechnung: `Rewards + Rebates + SpreadPnL + InventarPnL(mark-to-mid)
Verluste = Netto`. Threema-Tagesreport.
3. Kennzahl je Markt: **Reward-ROI pro gequoteter $** → Feedback in den
`MarketSelectorJob` (schlechte Märkte deaktivieren).
### 2.5 UI
- Tab „Märkte": Kandidaten-Ranking, aktiv/inaktiv-Toggle, Reward-Parameter.
- Tab „Live": aktuelle Quotes, Inventar je Markt (Ampel), letzte Fills.
- Tab „Abrechnung": `mm_daily_pnl`-Historie, Reward-ROI je Markt.
- Tab „Settings": PropertyGrid.
---
## 3. Phasen & Akzeptanzkriterien
### Phase MM-1: Fundament-Verifikation + Selector (read-only)
- Voraussetzung prüfen: `ClobMarketDataService`/`ClobUserChannelService`
laufen stabil (mehrtägiger Soak-Test, Reconnect-Verhalten).
- `MarketSelectorJob` + UI-Ranking, keine Orders.
- Akzeptanz: Ranking plausibel; Orderbuch-Daten für Top-Märkte lückenlos
über 72 h (Basis für Volatilitäts-Proxy).
### Phase MM-2: Paper-Quoting (Messung Adverse Selection)
- QuotingEngine läuft vollständig, sendet aber **keine** Orders; simulierte
Fills: Quote gilt als gefüllt, wenn der Marktpreis durch unser Quote-Level
handelt (aus Market-Channel-Trades ableitbar).
- 2 Wochen laufen lassen. Messen: simulierter Spread-PnL, Inventarverläufe,
Wie oft wären wir „überfahren" worden (Fill unmittelbar vor großer
Gegenbewegung)?
- Akzeptanz/Go-Kriterium: simuliertes Inventar bleibt innerhalb der Limits;
Spread-PnL ≥ 0 (Rewards kommen on top und sind der eigentliche Ertrag).
- **Hinweis:** Rewards selbst lassen sich nicht simulieren — sie erfordern
echte Resting-Orders. Paper-Phase misst nur die Risikoseite.
### Phase MM-3: Live auf 12 ruhigen Märkten
- Eigener Account, kleines Kapital (z. B. 300500 USDC), `QuoteSizeUsd`
knapp über Reward-Min-Size, 12 langlaufende Politik-/Geopolitik-Märkte.
- Akzeptanz nach 24 Wochen: tägliche Rewards fließen nachweislich
(`mm_daily_pnl`); Netto (Rewards + Spread Inventarverluste) > 0;
keine Order-Leichen (Cancel-Fehler) im CLOB.
### Phase MM-4: Skalierung + Skew-Feintuning
- Mehr Märkte (Selector-getrieben), Inventar-Skew-Parameter aus Fill-Daten
optimieren, Size je Markt anhand Reward-ROI erhöhen.
### Phase MM-5 (optional): Reward-Optimierung
- Order-Laddering (mehrere Level innerhalb des Max-Spreads), dynamische
Spread-Wahl abhängig von Konkurrenz-Liquidität, Teilnahme an
Sponsor-/Sonder-Reward-Programmen (z. B. Sport-Events pre-game).
---
## 4. Risiken & Gegenmaßnahmen
| Risiko | Gegenmaßnahme |
|---|---|
| Adverse Selection durch News-/Latenz-Bots | Marktauswahl (keine Live-Events), Volatilitäts-Pause, kleine Size |
| Inventar läuft in Resolution | Exit-Regeln ab 48 h/24 h vor EndDate (2.3) |
| Order-Churn → Rate-Limits/Sperren | Requote-Hysterese, Mindest-Ruhezeit, Monitoring der API-Fehlerquote |
| Reward-Regeländerungen | Parameter täglich aus API lesen, nichts hartkodieren |
| WSS-Ausfall → blinde Quotes | Watchdog: keine Book-Updates > N s → alle Quotes canceln (Fail-Safe) |
| Konflikt mit Copytrading | Eigener Account (Abschnitt 1) |
Der Fail-Safe „bei Datenverlust alles canceln" ist Pflicht ab MM-3 und muss
getestet werden (WSS künstlich trennen).
## 5. Offene Entscheidungen
1. Startmärkte (Empfehlung: 12 langlaufende Politik-/Geopolitik-Märkte mit
mittlerem Volumen — genug Reward-Pool, wenig Newsflow).
2. `QuoteSizeUsd`/Kapital für MM-3.
3. Beidseitig quoten von Anfang an (voller Reward-Score) oder zunächst
einseitig konservativ? (Empfehlung: beidseitig, dafür kleine Size —
einseitig scored schlechter und halbiert den Lerneffekt.)
@@ -0,0 +1,212 @@
# Umsetzungsplan: Modul „ResolutionFarming" (Favoriten nahe Auflösung)
> Stand: 2026-07-06
> Ziel: Neues Strategiemodul, das systematisch unterbewertete Favoriten
> (~9098 ¢) in bald auflösenden Märkten kauft, bis zur Resolution hält und
> automatisch redeemt.
> Reihenfolge: **Erstes neues Strategiemodul** (geringster Infrastrukturbedarf,
> validiert die Modul-Architektur über Copytrading hinaus).
> Voraussetzung: Phase 0 + 0.2 (Fee-Modell) aus
> `UMSETZUNGSPLAN-CopyTrading-Verbesserungen.md`. Phase 1 (Orderbuch) ist
> hilfreich, aber nicht zwingend für den Start.
---
## 0. Strategie-Hintergrund (Warum das funktioniert)
Auswertungen der Polymarket-Handelsdaten zeigen ein **Favorite-Longshot-
Reversal**: Outcomes mit hoher Wahrscheinlichkeit sind systematisch
*unterbewertet* (Retail überschätzt Longshots und drückt damit den Favoriten-
Preis). Ein 95-¢-Favorit gewinnt im Schnitt öfter als in 95 % der Fälle.
25 % Marge in 2448 h ergibt hohe annualisierte Renditen — **sofern das
Tail-Risiko diszipliniert gemanagt wird**: Ein verlorener 95-¢-Trade
vernichtet ~19 gewonnene. Das Risikomodell IST die Strategie.
Interner Kontext: Die profitabelsten kopierten Master (Typ „SwissTony"/„RN1")
machen genau das — hunderte BUYs, nie SELLs, Auflösung abwarten. Dieses Modul
internalisiert die Strategie und eliminiert die Copy-Latenz und die
Fremdbestimmung der Marktauswahl.
**Fees (seit März 2026):** Taker-Fees je Kategorie (Sports ~0,75 %, Politik
~1,0 %, Krypto ~1,8 %, Geopolitik 0 %) — bei 25 % Brutto-Marge ist die
Kategorie-Wahl entscheidend. Maker-Einstieg (Limit ins Buch) zahlt 0 Fees.
---
## 1. Architektur-Einbettung
Neues Projekt `src/PolyTrader.Modules.ResolutionFarming/` (Class Library,
net8.0-windows), Registrierung als `IPolyTraderModule` analog
`CopyTradingModule` (`Name = "ResolutionFarming"`, `DbPrefix = "rf_"`),
Einbindung in `Program.cs` der App.
### ⚠️ Grundsatzentscheidung: Eigener Polymarket-Account je Strategiemodul
**Dringende Empfehlung:** Das Modul handelt über einen **eigenen Account**
(Multi-Account-Support existiert im Core / `AccountState`).
Begründung: Der `TraderMonitorService` des Copytrading-Moduls synct **alle**
Wallet-Positionen eines Accounts in `account.OpenPositions`, würde
ResolutionFarming-Positionen „adoptieren" (Master-Zuordnungs-Fallbacks),
in seine Limits (PerMarket/PerMaster/Zeitfenster) einrechnen und ggf.
Auto-Redeem-/Cleanup-Logik darauf anwenden. Saubere Trennung über getrennte
Wallets vermeidet diese gesamte Konfliktklasse zur Laufzeit **und**
buchhalterisch (PnL je Strategie sauber messbar).
In der UI/Settings des Moduls: Zuordnung `AccountId ↔ Modul` mit Warnung,
wenn derselbe Account auch im Copytrading aktiv ist.
### Persistenz (EF Core / Pomelo / MySQL, eigener DbContext analog `CopyTradingDbContext`)
| Tabelle | Inhalt |
|---|---|
| `rf_settings` | Modul-Settings je Account (Preisband, Budgets, Limits) |
| `rf_candidates` | Scanner-Ergebnisse (Markt, Preis, Score, Filtergründe) — auch abgelehnte, für spätere Kalibrierung |
| `rf_positions` | Offene Farming-Positionen (TokenId, Entry, Size, EndDate, ClusterKey, Status) |
| `rf_closed_trades` | Abgeschlossene Trades inkl. Fees, Redeem-Infos |
Zusätzlich schreibt das Modul in den generischen Core-Trade-Log
(modulübergreifendes Dashboard).
---
## 2. Komponenten
### 2.1 `MarketScannerJob : BackgroundService`
Alle 1015 Minuten (JobManager-Registrierung wie `MasterTraderAnalyticsJob`,
manuell triggerbar):
1. Gamma-API: aktive Märkte mit `endDate < now + MaxHoursToResolution`
(Default 48 h), nicht closed. Bestehenden `PolymarketApiService` erweitern
(Query-Parameter für endDate-Fenster; Endpoint-Details bei Umsetzung aus
https://docs.polymarket.com verifizieren).
2. Je Markt den Favoriten bestimmen (Outcome mit höchstem Preis). Preisquelle:
CLOB Midpoint/Book (REST `GET /book` bzw. `IOrderBookProvider`, falls
Phase 1 des Copytrading-Plans schon umgesetzt).
3. Filterkette (jeder Reject wird mit Grund in `rf_candidates` geloggt):
- Preisband: `MinPrice ≤ ask ≤ MaxPrice` (Default 0.900.98).
- Liquidität: Ask-Tiefe am Zielpreis ≥ geplante Ordergröße × Faktor;
zusätzlich Markt-Volumen/Liquiditätsfelder der Gamma-API als Grobfilter.
- Kategorie-Whitelist (Default: Sports, Geopolitik, Politik; **Krypto
ausschließen** — 1,8 % Fee frisst die Marge; keine 15-Min-/Stunden-Märkte).
- Netto-Edge-Check: `(1 ask) Fee(ask, Kategorie) ≥ MinEdgePct`
(Default z. B. 1,5 %).
- Blacklist-Mechanismus (Slugs/Tags), z. B. für Marktarten mit
Resolution-Streitigkeiten (UMA-Disputes).
4. Kandidaten mit Score in `rf_candidates` schreiben; Anzeige in der Modul-UI.
### 2.2 Risiko-Engine (pure, testbare Klasse `FarmingRiskEngine`)
Settings je Account (`rf_settings`):
| Setting | Default | Bedeutung |
|---|---|---|
| `MaxPerMarketUsd` | 25 | Max. Einsatz je Markt |
| `MaxPerClusterPct` | 10 % | Max. Anteil der Bankroll je **Ereignis-Cluster** |
| `MaxTotalExposurePct` | 60 % | Max. Gesamteinsatz in offenen Positionen |
| `MaxNewPositionsPerDay` | 20 | Drosselung |
| `MinEdgePct` | 1.5 % | Netto-Edge nach Fees |
| `DailyLossKillSwitchUsd` | konfig. | Tagesverlust → Modul pausiert + Threema |
**Cluster-Definition (kritisch!):** 20 Fußballspiele desselben Spieltags sind
keine 20 unabhängigen Wetten. ClusterKey ableiten aus Event-/Series-Slug der
Gamma-API (z. B. Liga+Datum, Turnier, Wahl-Event). Korrelierte Favoriten
(z. B. „Kandidat X gewinnt" + „Partei von X gewinnt") teilen einen Cluster.
Erste Version: Heuristik über Event-Slug; Verfeinerung später.
### 2.3 `FarmingExecutionService`
1. Einstieg **Maker-first**: GTC-Limit auf Best-Bid bzw. Mid 1 Tick
(0 Fees). Kein Fill nach T Minuten (Default 15) → Entscheidung per Setting:
Taker-Fill (wenn Netto-Edge auch mit Fee noch ≥ MinEdge) oder verwerfen.
2. Order-Verwaltung über `PolymarketClobClient` (Core), Tracking analog
`PendingOrderTimestamps`-Muster des Copytrading-Moduls.
3. Positionen in `rf_positions` + Core-Positions-Sync gegen die Wallet
(Data-API `positions` — Muster aus `TraderMonitorService.PollLiveAccountsAsync`
übernehmen, aber schlanker: kein Master-Mapping nötig).
### 2.4 `ResolutionMonitorJob` + `AutoRedeemService`
1. Monitor: prüft offene `rf_positions` gegen Resolution
(`PolymarketApiService.CheckMarketResolutionAsync` existiert bereits).
2. **On-Chain-Auto-Redeem** (heute im gesamten Projekt nur manuell — dieser
Baustein nützt auch dem Copytrading):
- Gewinner-Shares einlösen via ConditionalTokens-Contract auf Polygon
(`redeemPositions(...)`); für NegRisk-Märkte über den NegRisk-Adapter.
**Contract-Adressen und Aufrufparameter bei Umsetzung zwingend aus der
offiziellen Doku verifizieren** (https://docs.polymarket.com,
Developer-Sektion CTF/NegRisk).
- Implementierung: Nethereum-Paket ODER Raw-RPC über die vorhandene
Alchemy-Anbindung; Signing mit dem Account-PrivateKey (liegt in
`AccountState`).
- Gas: Wallet braucht POL; Balance-Check + Threema-Warnung bei Unterdeckung.
- Retry mit Backoff; Erfolg = USDC-Balance-Delta verifiziert.
- `.agents/rules/clob.md` gilt hier besonders: On-Chain-Signing ist
hochkritisch — zuerst mit Kleinstbetrag auf einem Testmarkt verifizieren.
3. Übergangslösung bis 2.4 fertig: PreRedeem-artiger Verkauf (GTC-Limit 0.99+)
oder manueller Redeem — das Modul funktioniert auch ohne On-Chain-Teil,
bindet dann nur Kapital länger.
### 2.5 UI (`RegisterUi`, ein Fenster mit Tabs analog `CopyTradingMainForm`)
- Tab „Kandidaten": aktueller Scan mit Filtergründen (auch Rejects).
- Tab „Positionen": offene Farming-Positionen, Cluster-Auslastung, Countdown.
- Tab „Historie/Statistik": Winrate je Preisband (Kalibrierung!), Netto-PnL,
Fees, Redeem-Status.
- Tab „Settings": PropertyGrid auf `rf_settings` (Muster `AccountSettingsView`).
---
## 3. Phasen & Akzeptanzkriterien
### Phase RF-1: Modul-Skelett + Scanner (read-only)
- Projekt, Modul-Registrierung, DbContext + Migration, Scanner-Job, UI-Tab
„Kandidaten". **Keine Order-Platzierung.**
- Akzeptanz: App baut & startet mit Modul; Scanner liefert plausible
Kandidaten; Rejects nachvollziehbar geloggt; 1 Woche Kandidaten-Sammlung.
### Phase RF-2: Demo-Betrieb (4 Wochen)
- Execution im Demo-Modus (realistisches Fill-Modell: Ask-Preis + Fee,
siehe Copytrading-Plan Phase 4.2). Risiko-Engine aktiv.
- Akzeptanz/Go-Kriterium für Live: Kalibrierungstabelle zeigt
`realisierte Winrate je Preisband > Preisband-Mitte` und
Netto-Edge nach Fees > 0 über ≥ 100 Demo-Trades.
### Phase RF-3: Live klein
- Eigener Account, kleines Budget (z. B. 200500 USDC), `MaxPerMarketUsd` 510.
- Kill-Switch + Threema-Reporting (Tageszusammenfassung) aktiv.
- Akzeptanz: 2 Wochen Live ohne Ausführungsfehler; Live-Ergebnis im Rahmen
der Demo-Erwartung.
### Phase RF-4: Auto-Redeem on-chain
- Wie 2.4; zuerst Testmarkt/Kleinstbetrag, dann aktivieren.
- Akzeptanz: Gewinner-Position wird ohne manuellen Eingriff zu USDC.
### Phase RF-5: Kalibrierung & Skalierung
- Scoring von Heuristik auf Daten umstellen: historische Winrate je
Preisband × Kategorie aus `rf_candidates`/`rf_closed_trades` (+ optional
öffentliche Polymarket-Historien-Datensätze) → nur Bänder/Kategorien mit
nachgewiesenem Edge handeln. Budget stufenweise erhöhen.
---
## 4. Risiken & Gegenmaßnahmen
| Risiko | Gegenmaßnahme |
|---|---|
| Tail-Event (Favorit verliert) | Cluster-Limits, MaxPerMarket, Diversifikation über Kategorien |
| Korrelierte Cluster falsch geschnitten | Konservative Cluster-Heuristik, Review der Cluster in der UI |
| Resolution-Disputes (UMA) | Blacklist strittiger Marktarten; nur klare, objektiv auflösbare Märkte |
| Fee-Änderungen | Fee je Trade persistieren, MinEdge-Check dynamisch |
| Konflikt mit Copytrading-Sync | Eigener Account (Abschnitt 1) |
| On-Chain-Redeem-Fehler | Separate Phase, Testmarkt zuerst, Balance-Verifikation, clob.md-Regeln |
## 5. Offene Entscheidungen
1. Eigener Account: neuer Polymarket-Account nötig — wer legt ihn an, wie viel
Startkapital?
2. Preisband-Default (0.900.98) und `MinEdgePct` — mit Demo-Daten validieren.
3. Nethereum vs. Raw-RPC für On-Chain-Calls (Empfehlung: Nethereum, weniger
Fehlerfläche beim ABI-Encoding).
4. Taker-Fallback beim Einstieg erlauben oder strikt Maker-only?
@@ -0,0 +1,361 @@
# Umsetzungsplan: Modularisierung PolyTraderSharp
> Stand: 2026-07-01
> Ziel: Umbau des monolithischen WinForms-Copytraders in ein modulares System
> mit einem schlanken **Core** und unabhängigen **Modulen**. Erstes Modul: **Copytrading**.
---
## 1. Leitprinzipien
1. **Core kennt keine Module.** Der Core stellt nur Basis-Infrastruktur bereit
(Host, DB/Persistenz, Settings, Jobs, Logging, API-Clients, Benachrichtigungen,
Modul-Contract). Er hat **keine** Referenz auf irgendein Modul.
2. **Module hängen nicht voneinander ab.** Jedes Modul referenziert nur den Core.
Ein Modul kennt kein anderes Modul. Dies wird durch getrennte Projekte
**zur Compile-Zeit erzwungen**.
3. **Jede Phase lässt die App lauffähig und baubar zurück.** Kein „Big Bang".
Nach jeder Phase: Debug-Build grün, App startet, Copytrading funktioniert.
4. **WinForms bleibt.** Die GUI-Anforderung ist fix. Module tragen ihre eigenen
UI-Tabs zur Shell bei.
5. **Sicherheit vor Geschwindigkeit beim Refactoring.** CLOB-Integration ist
hochkritisch (siehe `.agents/rules/clob.md`) bei Berührung besonders sorgfältig,
jede Änderung mehrfach prüfen. Rollback jederzeit über Git möglich.
---
## 2. Zielarchitektur
### 2.1 Solution-Struktur (Multi-Projekt)
```
PolyTraderSharp.sln
├── PolyTrader.Core (Class Library, net8.0-windows)
│ • Generic Host / Bootstrap-Infrastruktur
│ • Persistenz: Repository-Interfaces + Implementierung (EF Core)
│ • Settings (appsettings.json + IOptions) + Core-Settings-Sektion
│ • JobManager, Logging (TerminalLogger / ILogger-Sink)
│ • Polymarket-Infrastruktur: PolymarketApiService, PolymarketClobClient,
│ PolymarketWssClient, AlchemyWebsocketService
│ • Querschnitt: MullvadVpnService, ThreemaService
│ • Eigene Trading-Accounts (AccountState) — die Konten, mit denen WIR traden
│ • Generischer Trade-Log (modulübergreifend auswertbar)
│ • Gesamt-Dashboard (Overview über alle Module)
│ • Core-State (generisch): MarketCache, globale Betriebsschalter
│ • IPolyTraderModule-Contract + Modul-Registry
├── PolyTrader.Modules.CopyTrading (Class Library, net8.0-windows)
│ • TraderMonitorService (Signalquelle)
│ • CopyTradingEngine (Ausführung)
│ • MasterTraderAnalyticsJob, TraderAnalyticsJob
│ • Models: TrackedTrader (kopierte Master-Trader), CopySignal,
│ CopyTradeRecord, TraderAnalyticsResult, MasterTraderHistoryRecord
│ • Copytrading-State: Traders (Master), MasterTraderPositions,
│ PendingOrderTimestamps, TraderAnalyticsCache
│ • Channels: CopySignal, ClosedTrade
│ • Eigener Copytrading-Trade-Log (Detail-Auswertung kopierter Trades,
│ zusätzlich zum generischen Core-Log)
│ • Eigene UI-Tabs (Master/Slave-Verwaltung, Modul-Analyse, Closed Trades)
│ • Eigene Modul-Settings-Sektion
│ • CopyTradingModule : IPolyTraderModule
├── PolyTrader.App (WinForms .exe, net8.0-windows)
│ • Program.cs: Host-Bootstrap, lädt Core + registrierte Module
│ • Shell-Form (frm_main reduziert auf Rahmen: Terminal, Jobs, Settings-Tab)
│ • Referenziert Core + alle aktiven Module
└── PolyTrader.Tests (xUnit, optional — spätere Phase)
• Risk-/Entscheidungslogik des Copytrading-Moduls
```
### 2.2 Modul-Contract (Entwurf)
```csharp
public interface IPolyTraderModule
{
string Name { get; } // "CopyTrading"
string DbPrefix { get; } // Namespace für DB-Objekte, z.B. "ct_"
void RegisterServices(IServiceCollection services, IConfiguration config);
void RegisterUi(IModuleUiHost uiHost); // Modul hängt seine Tabs ein
Task StartAsync(CancellationToken ct); // läuft NACH Core-Hydration
Task StopAsync(CancellationToken ct);
}
```
- **Discovery:** Die App registriert Module explizit in `Program.cs`
(`services.AddPolyTraderModule<CopyTradingModule>()`). Kein Runtime-Assembly-Scanning
(bewusst einfach gehalten; kann später zum Plugin-System ausgebaut werden).
- **Feature-/Lizenz-Gating:** `IPolyTraderModule` ist die natürliche Schnittstelle,
um Module später per Lizenz zu aktivieren/deaktivieren (vgl. `lizenssystem.md`).
### 2.3 State-Aufteilung
`TradingState` wird zerlegt:
| Feld | Ziel |
|------|------|
| `MarketCache` | **Core** (generischer Markt-Cache) |
| `GlobalTradingPaused`, `LiveTradingMode`, `DemoTradingMode` | **Core** (globale Betriebsschalter) |
| `Accounts` (unsere eigenen Trading-Accounts, `AccountState`) | **Core** — die Konten, mit denen WIR traden; modulübergreifend nutzbar |
| `Traders` (kopierte Master-Trader, `TrackedTrader`) | **CopyTrading-Modul** |
| `MasterTraderPositions`, `PendingOrderTimestamps`, `TraderAnalyticsCache`, `TotalCopyTrades`, `GlobalPnl` | **CopyTrading-Modul** |
> Entschieden (2026-07-01): Eigene Trading-Accounts liegen im **Core** (auch künftige
> Module handeln über dieselben Konten). Die **kopierten** Master-Trader (`TrackedTrader`)
> sind ein Copytrading-Konzept und liegen im **Modul**.
### 2.4 Trade-Logging (zweistufig)
Zwei unabhängige, parallel geführte Logs:
1. **Generischer Core-Trade-Log** (`TradeRecord` + `ITradeLogRepository`):
modulneutrale Felder (ModulName, AccountId, Markt, Side, Entry/Exit, PnL, Zeiten,
ExitReason). Ermöglicht die **modulübergreifende** Gesamtauswertung. Jedes Modul,
das Trades ausführt, schreibt hier einen Eintrag.
2. **Copytrading-spezifischer Log** (`CopyTradeRecord`, im Modul): erweitert die
generischen Felder um Copytrading-Details (`SourceTraderId`, `SourceTraderName`,
Master-Adresse, Signal-Herkunft) für die **detaillierte** Copytrading-Analyse.
Beim Schließen eines kopierten Trades schreibt das Modul **beides**: einen generischen
Eintrag in den Core-Log und einen Detaileintrag in seinen eigenen Log.
### 2.5 Dashboard & Analyse
- **Core-Gesamt-Dashboard:** Overview über alle Module (aggregierte PnL, Kontostände,
offene Positionen, grobe Kennzahlen je Modul) — gespeist aus dem generischen Core-Log.
- **Modul-Analyse:** Jedes Modul liefert seine eigene Detailansicht (Copytrading:
Trader-Winrates, kopierte Trades, Master-Performance) — gespeist aus dem Modul-Log.
### 2.6 Settings
- **Core-Settings-Sektion:** globale/Infrastruktur-Einstellungen (DB, VPN, Threema,
Betriebsschalter).
- **Modul-Settings-Sektion:** jedes Modul trägt seine eigene Sektion zum Settings-Tab bei
(analog zu den UI-Tabs), registriert über den `IPolyTraderModule`-Contract.
- **API-Keys sind Modul-Settings:** Alchemy-/Polymarket-WSS-Keys wandern von der globalen
`ServerSettings` in die jeweilige Modul-Settings-Sektion (siehe 2.7).
### 2.7 Streaming / WebSocket-Architektur *(Entscheidung 2026-07-01)*
**Prinzip:** Der **Core stellt die WSS-Verbindungs-Klasse als wiederverwendbare Fähigkeit**
bereit — **keinen** geteilten Singleton-Stream. Jedes **Modul erzeugt seine eigene Instanz**
mit **eigenem API-Key und eigenem Filter**.
**Begründung:** Blockchain-/WSS-Streams werden modulspezifisch **gefiltert** (sonst viel zu
umfangreich). Ein einzelner, Core-gesteuerter Stream, auf den mehrere Module gleichzeitig
zugreifen, wäre für jedes einzelne Modul mit nutzlosen Informationen geflutet.
**Aufteilung des heutigen `AlchemyWebsocketService`:**
- **Core** (`PolyTrader.Core.Streaming`): Verbindungs-Mechanik — `ClientWebSocket`, `eth_subscribe`,
Empfangs-Loop, Decode, Reconnect/429-Backoff, Health. Parametrisiert über eine
`BlockchainWssSubscription` (Contract, Topics, Adress-Filter) + RPC-URL/Key. Als **Factory**
(`IBlockchainWssClientFactory.Create()`), damit jedes Modul eine eigene Instanz bekommt.
- **Modul** (`CopyTrading`): `CopyTradingBlockchainListener : BackgroundService`, der eine
Core-WSS-Instanz mit dem Copytrading-Filter (Wallets der getrackten Master-Trader) + dem
Modul-eigenen Alchemy-Key betreibt, den gefilterten Substream konsumiert und selbst reagiert
(TraderMonitor-Poll, Re-Subscribe bei Trader-Listen-Änderung).
Analog für den Polymarket-User/Market-WSS (`PolymarketWssClient` → Core-Verbindungsklasse +
Modul-Listener). `IsAlchemyHealthy` (heute im Core-State) wird zum Health-Signal der jeweiligen
Modul-Instanz.
---
## 3. Persistenz-Strategie
- **Zielrichtung: Wechsel auf MySQL** via **EF Core + Pomelo.EntityFrameworkCore.MySql**,
gekapselt hinter Repository-Interfaces im Core.
- **Begründung:** DB liegt off-hot-path (Live-Pfad ist RAM-only) → kein Performance-Nachteil.
Gewinn: saubere relationale Tabellen statt Collection-per-Account + Shim, ACID,
EF-Migrations, Standard-Backups.
- **Risikoarm durch Reihenfolge:** Zuerst Repository-Abstraktion einziehen (Phase 3),
MySQL-Umstieg als eigene späte Phase (Phase 6). Die Modularisierung ist davon
entkoppelt und nicht blockiert.
- **Aufräumen:** LiteDB-Paket, `data.db` und `MongoDbLiteDBShim` entfallen nach der Migration.
- **ORM: Entity Framework Core** (entschieden) — Migrations + wenig Boilerplate.
---
## 4. Phasenplan
> Jede Phase endet mit grünem Debug-Build + lauffähiger App + Git-Commit.
### Phase 0 — Fundament: Versionskontrolle & Aufräumen *(ABGESCHLOSSEN 2026-07-01)*
- [x] `git init` (Branch `main`), `.gitignore` (bin/, obj/, .vs/, *.user, *.db, server_settings.xml, agentspace/antigravity/, .claude/settings.local.json).
- [x] Alle 11 `.bak*`-Dateien entfernt (per `-f` im Baseline-Commit `475d396` archiviert, danach entfernt → rekonstruierbar).
- [x] Tote Stubs entfernt: `services/database.cs`, `services/settings.cs`, `polymarket/*.cs`.
- [x] Threema-Lib unter `libs/` vendored (nested `.git` entfernt).
- [x] Baseline-Commit `475d396` + Cleanup-Commit `f76ad73`; Debug-Build 0 Fehler verifiziert.
### Phase 1 — Multi-Projekt-Gerüst anlegen *(ABGESCHLOSSEN 2026-07-01, Commit `4f130ff`)*
- [x] Drei Projekte: `PolyTrader.App` (umbenanntes WinForms-Projekt, Root),
`src/PolyTrader.Core`, `src/PolyTrader.Modules.CopyTrading` (net8.0-windows).
- [x] Referenzen: App → Core + CopyTrading; CopyTrading → Core; Core → nichts.
- [x] App-csproj: `src\**` vom Globbing ausgeschlossen (keine Glob-Kollision);
RootNamespace auf `PolyTraderSharp` gepinnt (schützt .resx/Namespaces).
- [x] Threema-Lib-Referenz bleibt im App-Projekt (wandert in Phase 4 zu Bedarf in Core).
- [x] **Ergebnis:** Solution-Build 0 Fehler, Code liegt weiterhin im App-Projekt.
- [ ] *Offen für spätere Phasen:* NuGet-Pakete beim Code-Umzug auf Core/Modul verteilen.
### Phase 2 — Konfiguration externalisieren *(ABGESCHLOSSEN 2026-07-01, Commit `e312fbb`)*
- [x] `appsettings.json` eingeführt (Mongo-Connection + DB-Name), Copy-to-Output.
- [x] `DatabaseOptions` im Core, via `IOptions<T>` gebunden; hart codierte Strings
aus `Program.cs` entfernt.
- [x] Startup-Cleanup-Hack aus `Main()` entfernt und gekapselt nach Host-Build über
die konfigurierte DB neu verankert.
- [ ] *Offen (bewusst später):* Alchemy-Key / Mullvad-Account / Threema bleiben vorerst
im GUI-editierbaren `server_settings.xml` (kein Konflikt mit Settings-Tab).
### Phase 3 — Persistenz-Abstraktion (DB noch Mongo) *(IN ARBEIT)*
- [x] **3a** (`8b3264f`): Core-Modelle `AccountState`/`Position`/`MarketData` in den Core
verschoben (Namespace `PolyTraderSharp.Models` beibehalten), MongoDB.Driver-Paket im Core.
- [x] **3b** (`ed5d6e3`): Repository-Interfaces + Mongo-Implementierungen im Core
(`IAccountRepository`, `IMarketRepository`, `IPositionRepository`), `AddCorePersistence()`.
- [x] **3c** (`7197f9b`): Unkritische Call-Sites migriert (MarketSyncService, PolymarketWssClient).
- [x] **3d — Hot-Path** (`a0e367e`, `0c6fc6a`): TraderMonitorService + CopyTradingEngine
auf `IPositionRepository`/`IMarketRepository`/`IAccountRepository` umgestellt.
`_db` aus CopyTradingEngine komplett entfernt; Verhalten unverändert.
- [ ] **frm_main-UI:** Account/Market/Demo-Position-Zugriffe → wird zusammen mit der
UI-Zerlegung in Phase 5 migriert (vermeidet Wegwerf-Arbeit).
- [ ] *Offen für Phase 5:* generisches `ITradeLogRepository` (Core) + `ICopyTradeLogRepository`
+ `ITraderRepository` (Modul), sobald `ClosedTrade`/`TrackedTrader` ins Modul wandern.
- [ ] **Ergebnis (Ziel):** Kein direkter DB-Zugriff mehr außerhalb der Repository-Schicht.
### Phase 4 — Core herauslösen *(IN ARBEIT)*
- [x] **4.1** (`9039af8`): TerminalLogger, JobManager, JobStatusRow → Core;
toten Stub `logging.cs` gelöscht.
- [x] **4.2** (`eebe992`): PolymarketClobClient → Core (+ Nethereum.Web3), reines Verschieben.
- [x] **4.3** (`8c126cd`): ServerSettings → Core.
- [x] **4.4** (`a1ce3fc`): `IPolyTraderModule`-Contract im Core (UI-Teil auf Phase 5 vertagt).
- [x] **4.5** (`c88eac5`): Startup-Reihenfolge-Fix — `StartupHydrationService` (IHostedService,
als erster registriert) hydriert Accounts/Trader vor den Trading-Services;
`frm_main.LoadDatabaseAndState` entfernt.
- [x] **4.6a** (`9c068b4`): CopySignal + PolymarketApiService → Core.
- [x] **4.7** (`0ffc041`): MullvadVpnService + ThreemaService (+ Threema-Lib-Ref) → Core;
toter Stub `mullvad.cs` gelöscht.
- [ ] **BLOCKIERT durch TradingState-Split (→ Phase 5):** MarketSyncService,
AlchemyWebsocketService, PolymarketWssClient, SnapshotService nutzen `TradingState`
(MarketCache/Accounts/globale Flags). Sie können erst nach dem Split in den Core.
- [x] **Ergebnis:** Core baut eigenständig und enthält jetzt: Modelle (Account/Position/
Market/CopySignal/JobStatusRow/ServerSettings), Repository-Schicht, Config, Logging,
JobManager, CLOB-Client, API-Service, Mullvad, Threema, IPolyTraderModule.
**Stand nach Phase 4:** Der Core ist substanziell und eigenständig. Was noch in der App liegt:
Modul-Services (TraderMonitor, CopyTradingEngine, Analytics-Jobs), die TradingState-abhängige
Infra (MarketSync, Alchemy, WSS, Snapshot), PersistenceService, StartupHydrationService,
der Shim, `TradingState`, `frm_main` und die Modul-Modelle (TrackedTrader, TraderAnalyticsResult,
MasterTraderHistoryRecord, ClosedTrade, DashboardRow). Der **TradingState-Split** ist der
Dreh- und Angelpunkt für Phase 5.
> **Entscheidung (2026-07-01):** `CopySignal` wird ein **Core**-Typ (generisches Markt-Trade-
> Signal). Das entkoppelt die Polymarket-Infrastruktur sauber in den Core. Der Channel/Workflow
> bleibt Copytrading. Umbenennung zu `TradeSignal` optional/später.
### Phase 5 — CopyTrading-Modul herauslösen *(IN ARBEIT)*
- [x] **5.1** (`55050a1`): Modul-Modelle (TrackedTrader, TraderAnalyticsResult,
MasterTraderHistoryRecord) ins Modul verschoben.
- [x] **5.2** (`f8d395b`): **TradingState-Split** — Core `TradingState` (globale Schalter,
Accounts, MarketCache, GlobalPnl) vs. `CopyTradingState` im Modul (Traders,
MasterTraderPositions, TraderAnalyticsCache, TotalCopyTrades, PendingOrderTimestamps,
SixSharesMinimum); 10 Konsumenten umgestellt. Modulgrenze auf State-Ebene gezogen.
- [x] **5.3a** (`f5e7eaf`): MarketSyncService → Core (nur Core-State).
- [x] **5.3-WSS 1/2** (`3be75c0`): Core-WSS-Verbindungsklasse extrahiert
(`IBlockchainWssClient` + Factory + Modelle in `PolyTrader.Core.Streaming`);
`AlchemyWebsocketService` nutzt sie via Factory. Verhaltensneutral.
- [x] **ClosedTrade-Migration** (`88fc982`): `ClosedTrade` → Modul, `ICopyTradeLogRepository`
(+ Mongo-Impl) im Modul; `closed_trades`-Zugriffe der Services (TraderMonitor,
Persistence, WSS, TraderAnalytics) auf das Repo umgestellt. TraderMonitor nutzt kein
`_db` mehr. frm_main/Program.cs bleiben auf `_db`.
- [ ] **5.3b (jetzt unblockiert):** Modul-Services physisch ins Modul-Projekt verschieben:
TraderMonitorService, CopyTradingEngine, TraderAnalyticsJob, MasterTraderAnalyticsJob.
(Vorher: reststehende `PolyTraderSharp.Extensions`-Usings in CopyTradingEngine entfernen.)
- [ ] **5.3-WSS 2/2:** `CopyTradingBlockchainListener` → Modul (eigener Key + Filter);
API-Keys → Modul-Settings. Analog `PolymarketWssClient`.
- **UI-Trennung (Launcher-Modell, entschieden 2026-07-01):** Hauptfenster = schlanke
Startleiste; jede Ansicht öffnet als eigenständiges Fenster. Module liefern designbare
`UserControl`s via `IPolyTraderModule.RegisterUi` / `IModuleUiHost`. `frm_main` bleibt
übergangsweise als „Legacy-UI" per Button erreichbar, bis alle Views extrahiert sind.
- [x] UI-Contract im Core (`IModuleUiHost`, `ModuleView`, `RegisterUi`) — `26dd68a`.
- [x] Proof-of-Pattern: `LauncherForm` + `ViewHostForm` + `ShellUiHost` + erste View
`TerminalView` (designbar); App startet Launcher — `3503bbb`.
- [ ] Restliche Views view-für-view extrahieren:
**App/Core:** Jobs, Server Settings, Dashboard (Overview), License, Accounts (Slave),
Offene Positionen. **Modul:** Master-Traders, Top/Flop-Analytics, Geschlossene Trades.
- [ ] `frm_main` (Legacy) entfernen, sobald alle Views raus sind.
- [ ] `CopyTradingModule : IPolyTraderModule` implementieren (Services + UI-Tabs +
Settings-Sektion + Modul-Log + Start/Stop).
- [ ] Dualen Trade-Log verdrahten: beim Schließen kopierter Trades in Core-Log **und**
Copytrading-Log schreiben.
- [ ] Latenten Collection-Namensbug beheben (`traders` vs. `trackers`).
- [ ] **Ergebnis:** Copytrading ist ein eigenständiges, entfernbares Modul.
### Phase 6 — MySQL-Migration
- [ ] EF Core + Pomelo einrichten; relationales Schema modellieren
(u.a. `positions` mit `account_id` statt Collection-per-Account;
`trader_accounts` Join-Tabelle für `AssignedAccountIds`).
- [ ] Zweite Repository-Implementierung (MySQL) hinter den bestehenden Interfaces.
- [ ] Einmaliges Migrationsskript Mongo → MySQL (agentspace/scripts).
- [ ] Umschalten per Konfiguration; Mongo/LiteDB/Shim + `data.db` entfernen.
### Phase 7 — Nacharbeiten *(optional, später zu priorisieren)*
- [ ] Test-Projekt: Risk-/Entscheidungslogik als reine Funktionen extrahieren & testen.
- [ ] God-Methoden splitten (`PollLiveAccountsAsync`, `ProcessAccountOrderAsync`);
duplizierte Closed-Trade-Erzeugung zentralisieren.
- [ ] Leere `catch {}` durch gezieltes Logging ersetzen.
- [ ] Secrets-Verschlüsselung (DPAPI) für PrivateKey/ApiSecret/ApiPassphrase.
- [ ] TerminalLogger auf `Microsoft.Extensions.Logging` + UI-Sink umstellen.
---
## 5. Datei-→-Ziel-Zuordnung (Referenz)
| Aktuell | Ziel |
|---------|------|
| `Program.cs` | PolyTrader.App |
| `frm_main.*` | PolyTrader.App (Shell) + Copytrading-Tabs → Modul |
| `frm_analytics.*` | PolyTrader.Modules.CopyTrading |
| `TradingState.cs` | aufgeteilt: Core + Modul |
| `services/PolymarketApiService.cs` | Core |
| `services/PolymarketClobClient.cs` | Core |
| `services/PolymarketWssClient.cs` | Core |
| `services/AlchemyWebsocketService.cs` | Core |
| `services/MullvadVpnService.cs`, `mullvad.cs` | Core |
| `services/ThreemaService.cs` | Core |
| `services/JobManager.cs`, `TerminalLogger.cs`, `logging.cs` | Core |
| `services/PersistenceService.cs` | Core (generischer Trade-Log-Writer); Copytrading-Detail-Writer → Modul |
| `services/MarketSyncService.cs`, `SnapshotService.cs` | Core |
| `Extensions/MongoDbLiteDBShim.cs` | Core (temporär), entfällt in Phase 6 |
| `services/CopyTradingEngine.cs` | Modul |
| `services/TraderMonitorService.cs` | Modul |
| `services/MasterTraderAnalyticsJob.cs`, `TraderAnalyticsJob.cs` | Modul |
| `Models/AccountState.cs`, `Position.cs`, `MarketData.cs`, `ServerSettings.cs`, `JobStatusRow.cs`, `DashboardRow.cs` | Core |
| `Models/ClosedTrade.cs` | aufgeteilt: generischer `TradeRecord` → Core, `CopyTradeRecord` (mit SourceTrader-Feldern) → Modul |
| `Models/TrackedTrader.cs`, `CopySignal.cs`, `TraderAnalyticsResult.cs`, `MasterTraderHistoryRecord.cs` | Modul |
| `services/database.cs`, `settings.cs`, `polymarket/*.cs` | löschen (Phase 0) |
| `*.bak*` | löschen (Phase 0) |
---
## 6. Getroffene Entscheidungen (2026-07-01)
1. **Eigene Trading-Accounts → Core**, **kopierte Master-Trader → Copytrading-Modul.**
2. **Zweistufiges Trade-Logging:** generischer Core-Log (modulübergreifend) **und**
zusätzlicher Copytrading-Detail-Log im Modul (siehe 2.4).
3. **ORM: Entity Framework Core.**
4. **Dashboard:** Core liefert Gesamt-Overview über alle Module; Module liefern
eigene Detail-Analysen (siehe 2.5).
5. **Settings:** getrennte Core- und Modul-Settings-Sektionen (siehe 2.6).
---
## 7. Risiken & Gegenmaßnahmen
- **CLOB-Regression:** Höchstes Risiko. Gegenmaßnahme: CLOB-Client möglichst unverändert
in den Core verschieben (nur Namespace/Referenzen), keine Logikänderung in der
Umstrukturierungsphase.
- **Startup-Race weiterhin aktiv, bis Phase 4:** Bis der Startup-Fix greift, bleibt das
bestehende Verhalten kein neues Risiko, aber früh angehen.
- **Datenmigration (Phase 6):** Server läuft produktiv. Migration mit Read-Only-Export +
Verifikation vor Umschaltung; Rollback-Pfad (Mongo bleibt bis Verifikation bestehen).
@@ -0,0 +1,98 @@
# Umsetzungsplan: Strategie-Drift-Erkennung für Master-Trader (B-S2)
> Stand: 2026-07-11
> Ziel: Verhaltens-Änderungen eines Masters erkennen, BEVOR sie sich im Copy-PnL
> niederschlagen. Die bestehende Auto-Pause (Copy-PnL-basiert) ist ein nachlaufender
> Indikator — bei 95-¢-Tradern sieht man den Schaden erst nach mehreren Verlusten.
> Verhalten läuft dem PnL voraus: Ein Wetter-Bot, der plötzlich Politik-Longshots
> kauft, hat die Strategie gewechselt, lange bevor die Verluste messbar sind.
> Modul: PolyTrader.Modules.CopyTrading (baut auf vorhandenem MasterTraderAnalyticsJob auf).
---
## 1. Der Verhaltens-Fingerprint
Je Master werden zwei Fenster verglichen: **Referenz** (30 Tage bzw. die von
Predictalytics gelieferte Baseline) vs. **aktuell** (7 Tage). Datenquelle: die
Activity-/History-Daten, die der `MasterTraderAnalyticsJob` bereits lädt
(`mod_copytrading_mt_history` + Data-API-Activity; für Preisband/Größe die
Activity-Items — Felder existieren in den bereits geparsten JSONs).
Fingerprint-Metriken (pure Klasse `Logic/TraderFingerprint.cs`, voll unit-getestet):
| Metrik | Definition | Drift-Beispiel |
|---|---|---|
| `TradesPerWeek` | Trade-Frequenz | Bot-Betreiber wechselt von 40 auf 400/Woche |
| `CategoryMix` | Einsatz-Anteil je Kategorie (Vektor) | Wetter-Bot kauft plötzlich Politik |
| `PriceBandMix` | Einsatz-Anteil je Einstiegs-Preisband (10-¢-Bänder) | Favoriten-Halter kauft Longshots |
| `MedianHoldHours` | Median Haltedauer (Kauf→Close/Resolution) | Halter wird Day-Trader |
| `SellRatio` | Anteil aktiv verkaufter Positionen | „Stur-Halter" beginnt zu verkaufen |
| `SizeP90Rel` | 90. Perzentil Positionsgröße relativ zur Referenz | Martingale-/Tilt-Muster |
### Drift-Score
Pro Metrik eine normierte Abweichung (für Anteils-Vektoren: L1-Distanz / 2 → 0..1;
für Skalare: `|akt ref| / max(ref, ε)` gekappt auf 1). Gesamt:
```
DriftScore = gewichtete Summe (Default-Gewichte: CategoryMix 0.3, PriceBandMix 0.25,
SellRatio 0.2, TradesPerWeek 0.1, MedianHoldHours 0.1, SizeP90Rel 0.05)
```
Schwellen (global in `CopyTradingState`, per PropertyGrid änderbar, mit
[Description]): `DriftWarnScore` (Default 0.25) und `DriftPauseScore` (Default 0.5).
**Mindeststichprobe:** unter 10 Trades im 7-Tage-Fenster keine Bewertung (Rauschen).
## 2. Aktionen bei Drift
| Stufe | Bedingung | Aktion |
|---|---|---|
| Beobachten | Score < Warn | nichts; Score in UI-Spalte sichtbar |
| **Warnen** | Warn ≤ Score < Pause | Threema-Meldung mit den 2 größten Abweichungen („Kategorie-Mix: Wetter 90→40 %, Politik 0→45 %"); Master in UI gelb |
| **Neu-Trades pausieren** | Score ≥ Pause UND `AutoPauseEnabled` | NEUE BUYs dieses Masters aussetzen (`DriftPaused`-Flag auf TrackedTrader, Engine-Check im BUY-Pfad analog ExitPending); offene Positionen + SELL-Handling laufen normal weiter; Threema; Reaktivierung manuell |
Bewusst: Drift pausiert nur **Neu-Käufe** — es verkauft nichts. Bestehende
Positionen sind Sache der normalen Exit-Mechanik (Halter: Resolution).
## 3. Umsetzung
### Slice D-1: Pure Logik + Persistenz
- `Logic/TraderFingerprint.cs`: `Compute(IEnumerable<TradeObservation>)`
Fingerprint; `Drift(reference, current)` → Score + Top-Abweichungen. Unit-Tests
(Vektor-Distanzen, Mindeststichprobe, Rand: leere Referenz).
- `TrackedTrader`: Felder `FingerprintBaselineJson` (Referenz, von Predictalytics
importierbar ODER selbst aus 30 Tagen berechnet), `DriftScore`, `DriftPaused`,
`DriftDetail` (Kurztext) + Migration.
- `MasterTraderHistoryRecord` erweitern um die dafür nötigen Felder (EntryPrice-Band,
Kategorie, Size, Haltedauer), sofern noch nicht vorhanden — beim History-Download
mit befüllen (Daten sind in den API-Antworten enthalten).
### Slice D-2: Job-Integration
- Im `MasterTraderAnalyticsJob` nach dem History-Download: Fingerprint aktuell (7 T)
vs. Referenz (30 T bzw. BaselineJson) → Score, Aktionen gemäß Tabelle.
- **Frequenz:** Der Job läuft 12-stündlich — für Drift zu träge. Leichten
Stunden-Tick ergänzen (nur Fingerprint-Neuberechnung aus bereits geladenen
History-Daten, KEINE zusätzlichen API-Calls; die 12-h-Läufe aktualisieren die
Rohdaten).
- Referenz-Handhabung: Baseline wird NICHT automatisch nachgezogen, solange eine
Warnung/Pause aktiv ist (sonst „lernt" die Referenz die Drift). Nach manueller
Entwarnung: Baseline auf aktuelles 30-T-Fenster zurücksetzen (Button in UI).
### Slice D-3: Engine + UI
- Engine-BUY-Pfad: `DriftPaused`-Check (analog `IsActive`), TradeReasoning-Log.
- `MastersTradersView`: Spalten DriftScore (mit Ampelfarbe) + DriftDetail;
Kontextmenü „Drift entwarnen + Baseline zurücksetzen".
## 4. Akzeptanzkriterien
1. Unit-Tests: konstruierte Drift-Szenarien (Kategorie-Wechsel, Frequenz-Explosion,
Longshot-Umstieg) erzeugen erwartete Scores; stabile Master bleiben < Warn.
2. Simulierter Kategorie-Wechsel in Testdaten führt zu `DriftPaused` + Engine
verweigert Neu-BUY mit nachvollziehbarem Log.
3. Kein zusätzlicher Data-API-Traffic durch den Stunden-Tick (nur DB/RAM).
4. Threema-Meldungen enthalten die konkreten Top-Abweichungen, nicht nur den Score.
## 5. Abgrenzung
- Ersetzt NICHT die PnL-Auto-Pause (Phase 3.3, bleibt) — Drift ist das Frühwarnsystem,
PnL-Pause das Sicherheitsnetz.
- Sniper-Metriken (Plan Phase 3.2, Median-Haltezeit via Activity-Pagination) sind ein
Spezialfall dieses Fingerprints — bei Umsetzung zusammenlegen statt doppelt bauen.