Files
PolyTraderSharp/docs/archiv/konzepte/KONZEPT-Multi-Plattform.md
T
RichardandClaude Opus 5 05979406c7 Konzept Mehrere Handelsplattformen: Polymarket US, spaeter Kalshi (#622-#629)
Ein Kunde mit US-Wohnsitz soll ueber Polymarket US handeln - eine eigene,
CFTC-regulierte Boerse, an der die heutige CLOB-Anbindung nicht verwendbar ist.
Das Konzept macht Plattform zu einem Begriff im Core, laesst Module ihre
Plattformen erklaeren und haelt Strategievarianten je Plattform im Modul.

Begruendungen zu #622-#629 in docs/ROADMAP.md, Bauanleitung in
docs/archiv/konzepte/KONZEPT-Multi-Plattform.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 10:54:00 +02:00

20 KiB
Raw Blame History

Konzept: Mehrere Handelsplattformen (Polymarket US, später Kalshi)

Angelegt am 16.09.2026. Planungsstand und Reihenfolge stehen im Deploymentcenter (Items #622–#629), nicht hier. Diese Datei ist die Bauanleitung und die Begründung.


1. Anlass und Ziel

Ein Kunde mit Wohnsitz in den USA soll PolyTrader nutzen. Er darf polymarket.com nicht verwenden (US-IPs gesperrt), wohl aber Polymarket US — eine eigene, CFTC-regulierte Börse (QCX LLC als Designated Contract Market, QC Clearing LLC als Clearingstelle) mit KYC. Der Kunde kann KYC machen.

Polymarket US ist technisch ein anderer Handelsplatz, nicht eine Variante desselben. Keine Zeile der heutigen CLOB-Anbindung ist dort verwendbar (siehe Abschnitt 2). Daraus folgen drei Ziele:

  1. Ein zweiter Plattform-Adapter für Polymarket US.
  2. Plattform als Konzept im Core, sodass ein dritter Handelsplatz (Kalshi) ein weiterer Adapter ist und kein weiterer Umbau.
  3. Module erklären, welche Plattformen sie bedienen können — sichtbar in Desktop und WebUI und wirksam im Handelspfad. Strategien dürfen sich je Plattform unterscheiden.

2. Was Polymarket US anders macht

Quelle: offizielle Doku https://docs.polymarket.us (Stand 16.09.2026). Vor dem Bau erneut prüfen — die Plattform ist jung und ändert sich (Gebühren wurden am 16.09.2026 angehoben).

Polymarket Global (heute) Polymarket US
Endpunkte CLOB, Gamma, Data-API getrennt api.polymarket.us (authentifiziert), gateway.polymarket.us (öffentlich)
Echtzeit CLOB-WSS, Alchemy/Polygon wss://api.polymarket.us/v1/ws/private (Orders, Positionen, Saldo), /v1/ws/markets (Buch, Trades; max. 10 Märkte je Verbindung)
Anmeldung L1 EIP-712 mit Wallet-Key → L2 HMAC (apiKey/secret/passphrase) Key-ID + Ed25519-Secret; Header X-PM-Access-Key, X-PM-Timestamp (ms, ±30 s), X-PM-Signature = Base64(Ed25519(timestamp + method + path))
Konto Wallet / Proxy-Wallet, kein KYC KYC-Person (iOS-App), Schlüssel unter polymarket.us/developer, Secret wird nur einmal angezeigt
Markt-Identität conditionId + tokenId je Ausgang marketSlug; Ausgang über intent bzw. outcomeSide YES/NO
Order signierte Order (EIP-712), Preis als Zahl, FOK/GTC/GTD POST /v1/orders: intent (BUY_LONG, SELL_LONG, BUY_SHORT, SELL_SHORT), price {value, currency}, quantity oder cashOrderQty, TIF DAY/GTC/GTD/IOC/FOK, participateDontInitiate (Post-only), manualOrderIndicator, synchronousExecution
Weitere Order-Wege — Modify, Preview, Close-Position, Sammel-Orders, Cancel-All
Geld USDC auf Polygon USD per Karte, ACH, Wire
Abwicklung On-Chain-Redeem nötig (#146) automatisch zu 1 / 0, kein Redeem
Gebühr Taker in bps je Kategorie (FeeModel) Θ · C · p · (1−p); Taker Θ = 0,0695 (ab 16.09.2026), Maker-Rabatt Θ = −0,0125; Banker's Rounding auf Cent
Limits eigene je Endpunkt 20 Anfragen/s je Schlüssel bzw. je IP; 429 → Backoff. „Global Rate Limit Exceeded" nach 5 s ist Latenzschutz, kein Limit
Testumgebung — Retail-API: keine dokumentierte Sandbox. Preprod nur für die Exchange-API (OAuth), Zugang über onboarding@polymarket.us
Liquidität, Marktangebot globaler Pool, voll eigener US-Pool, kleineres Angebot

Zwei Folgen, die das Design bestimmen:

  • Die Gebührenformel ist eine andere Form, nicht nur ein anderer Satz. Ein „Satz in bps" reicht als Modell nicht. Nahe 0,95 ist die US-Gebühr winzig, bei 0,50 am höchsten.
  • Fremde Wallets sind nicht sichtbar. Alles, was CopyTrading heute beobachtet (Polygon-Logs, Data-API-Activity), gibt es für den US-Pool nicht.

3. Ist-Zustand im Code

PolyTrader ist modular (Core + Module), aber Polymarket Global ist überall stillschweigend vorausgesetzt:

Stelle Bindung
IClobClient tokenId, isNegRisk, actualFeeBps, Decimals-Overrides — CLOB-Begriffe im Vertrag
PolymarketClobClient, PolymarketApiService direkt von Modulen aufgelöst; Registrierung von IClobClient sogar im CopyTrading-Modul
AccountState WalletAddress, ApiPassphrase, PrivateKey — Global-Zugangsdaten als feste Felder
Position, core_positions Primärschlüssel (AccountId, IsDemo, TokenId)
MarketData, core_markets ConditionId, ClobTokenIds, NegRisk
FeeModel statisch, bps je Kategorie
Streaming/AlchemyWssClient, BlockchainWss Polygon
CopyTrading 10 Dateien gegen CLOB/API/Chain
ResolutionFarming hat schon IFarmingMarketSource, IMarketResolutionSource — gutes Vorbild
Accounting hat schon IActivitySource, ITransferSource, IBalanceAnchorSource — ebenso
Supervisor nur CounterfactualJob liest über die API

TokenId kommt in 73 Dateien vor. Ein Umbau in einem Schritt ist ausgeschlossen.


4. Leitentscheidungen

  1. Die Plattform gehört zum Konto, nicht zur Installation. Eine Installation kann Konten mehrerer Plattformen führen. Ob der Netzweg das zulässt (US-Standort gegen US-IP-Sperre von polymarket.com), ist eine Betriebsfrage (#622), keine Architekturfrage.
  2. Der Global-CLOB-Code wird umhüllt, nicht umgebaut. Signierung, Betragsrechnung und Orderablauf von PolymarketClobClient bleiben Zeile für Zeile, wie sie sind. Der Global-Adapter übersetzt nur zwischen neutralem Vertrag und bestehendem Client. Das ist die direkte Anwendung von .agents/rules/clob.md.
  3. Module sprechen gegen Plattform-Verträge, nicht gegen Polymarket*-Klassen. Das Muster existiert schon in ResolutionFarming und Accounting und wird verallgemeinert.
  4. Logik fragt nach Fähigkeiten, nicht nach Namen. if (caps.PublicTraderActivity) statt if (platform == PolymarketGlobal). Ein neuer Handelsplatz bringt seine Fähigkeiten mit, und bestehende Module entscheiden richtig, ohne ihn zu kennen.
  5. Module erklären ihre Plattformen ausdrücklich. Fähigkeiten allein reichen nicht: Dass eine Plattform alles Nötige hat, heißt nicht, dass die Strategie dort geprüft ist. Die Erklärung ist eine Freigabe, die Fähigkeiten sind eine Plausibilitätsprüfung.
  6. Strategievarianten leben im Modul, als Profil je Plattform — kein „ResolutionFarmingUS"-Modul. Die fachliche Idee bleibt eine; Gebühren, Schwellen und Marktquelle unterscheiden sich.
  7. Global bleibt die Vorgabe. Jede neue Spalte bekommt polymarket-global als Standardwert. Bestehende Installationen merken nach der Migration nichts.

5. Architektur

5.1 Plattform und Fähigkeiten

namespace PolyTrader.Core.Platforms;

/// Stabile Kennung, wird in der Datenbank gespeichert – nie umbenennen.
public readonly record struct PlatformId(string Value)
{
    public static readonly PlatformId PolymarketGlobal = new("polymarket-global");
    public static readonly PlatformId PolymarketUs     = new("polymarket-us");
    public static readonly PlatformId Kalshi           = new("kalshi");
}

public sealed record PlatformCapabilities(
    bool PublicTraderActivity,     // fremde Trades beobachtbar (CopyTrading)
    bool OnChainSettlement,        // Redeem nötig, Chain-Transfers als Buchungsquelle
    bool PostOnlyOrders,
    bool OrderModify,              // Preis ändern ohne Cancel+Neu
    bool MakerRebate,
    bool FractionalQuantity,
    bool NativeDemoEnvironment,    // echte Testumgebung der Plattform
    IReadOnlySet<TimeInForce> TimeInForce,
    string SettlementCurrency);    // "USDC", "USD"

5.2 Verträge

Neutral formuliert, je Plattform einmal implementiert. Aufgelöst über eine Registry, nie direkt.

Vertrag Aufgabe Global-Adapter hüllt US-Adapter spricht
IMarketCatalog Märkte suchen, nach Enddatum/Kategorie filtern, Auflösungsstatus Gamma-API, MarketSyncService gateway Markets/Events/Settlement
IOrderBookSource Buch, BBO CLOB /book gateway Book/BBO
IOrderGateway Platzieren, Ändern, Canceln, offene Orders PolymarketClobClient POST/…/v1/orders
IPortfolioSource Positionen, Saldo, Aktivität Data-API, Chain Portfolio, Account-Balances
IMarketStream / IAccountStream Echtzeit CLOB-WSS, Alchemy /v1/ws/markets, /v1/ws/private
IFeeModel Gebühr für (Preis, Menge, Rolle) heutiges FeeModel Θ·C·p·(1−p)
ISettlementHandler Was nach Auflösung zu tun ist Redeem (#146) nichts, nur Abgleich
ITraderActivitySource fremde Trades Polygon, Data-API nicht vorhanden

Stand nach #623 (18.09.2026): Gebaut sind IMarketCatalog, IOrderBookSource, IOrderGateway, IPortfolioSource, IFeeModel, gebündelt in IPlatformAdapter (src/PolyTrader.Core/Platforms/). Die Streams, ISettlementHandler und ITraderActivitySource entstehen mit ihrem ersten zweiten Nutzer, nicht auf Vorrat: Streams mit #625, Abwicklung mit #146 bzw. #625, Trader-Aktivität mit #628. Die Registry löst den ganzen Adapter auf (registry.ForAccount(account).Orders), nicht einzelne Verträge per Typ.

public interface IPlatformRegistry
{
    IReadOnlyList<PlatformId> Available { get; }
    PlatformCapabilities Capabilities(PlatformId p);
    T Get<T>(PlatformId p) where T : class;      // wirft, wenn Plattform den Vertrag nicht hat
    bool TryGet<T>(PlatformId p, out T? service) where T : class;
}

IClobClient bleibt bestehen, bis CopyTrading umgestellt ist; IOrderGateway für Global delegiert darauf. Es gibt keinen Tag, an dem beides gleichzeitig umgeschrieben wird.

5.3 Instrument und Order

/// Ein handelbarer Ausgang auf einer Plattform. Key ist innerhalb der Plattform eindeutig.
/// Global:  Key = tokenId,            MarketKey = conditionId
/// US:      Key = "{marketSlug}:YES", MarketKey = marketSlug
/// Kalshi:  Key = "{ticker}:YES",     MarketKey = ticker
/// Gleichheit nur über (Platform, Key).
public sealed record InstrumentRef(PlatformId Platform, string Key, string MarketKey = "", string Outcome = "");

public sealed record OrderRequest(
    InstrumentRef Instrument,
    OrderSide Side,                 // Buy / Sell  (US: → intent BUY_LONG usw.)
    decimal LimitPrice,             // immer Wahrscheinlichkeit 0..1 – Adapter rechnet um (Kalshi: Cent)
    decimal? Quantity,              // Kontrakte/Shares
    decimal? NotionalUsd,           // alternativ Betrag
    TimeInForce Tif,
    bool PostOnly,
    string OriginModule,            // für die Plattform-Sperre (5.6) und das Journal
    string CorrelationId);

Plattformspezifisches (Global: negRisk, Tick-Size, Decimals; US: manualOrderIndicator, synchronousExecution) bleibt im Adapter. Der US-Adapter setzt manualOrderIndicator fest auf AUTOMATIC — ein Bot, der sich als manuell ausgibt, ist ein Compliance-Fehler, keine Einstellung.

5.4 Konten und Zugangsdaten

  • core_accounts.platform (Vorgabe polymarket-global).

  • Zugangsdaten je Plattform, verschlüsselt wie F1 (EncryptedStringConverter):

    • Global: wie heute (Wallet, Key, Secret, Passphrase, PrivateKey).
    • US: Key-ID in ApiKey, Ed25519-Secret in ApiSecret. WalletAddress, PrivateKey, PayoutAddress leer.
    • Kalshi: Key-ID + privater Schlüssel.

    Die bestehenden Felder reichen für US und Kalshi. Eine eigene Tabelle je Plattform wäre sauberer, kostet aber eine zweite Migration der F1-Verschlüsselung. Entscheidung: vorhandene Felder wiederverwenden, die Kontenansicht zeigt je Plattform nur die passenden Felder mit passender Beschriftung. Neu prüfen, falls eine Plattform mehr als vier Geheimnisse braucht.

  • Die Passwortsperre vor Konten und Geheimnissen (#567) gilt unverändert; Geheimnisse bleiben Desktop-only (WebUI-Parität).

5.5 Datenbank

Tabelle Änderung
core_accounts + platform
core_positions keine Änderung (Entscheidung in #623): Eine Position gehört zu einem Konto, das Konto zu genau einer Plattform. (AccountId, IsDemo, TokenId) bleibt eindeutig, solange TokenId den plattformeindeutigen InstrumentRef.Key hält. Eine eigene Spalte wäre eine zweite Wahrheit, die auseinanderlaufen kann
core_markets + platform; Schlüssel (Platform, Id)
Modultabellen mit Token-Bezug (ct_, rf_, acc_) + platform mit Vorgabe, je Modul im jeweiligen Schritt

Position.TokenId bleibt, wie es ist, und hält InstrumentRef.Key. Die 73 Fundstellen ziehen schrittweise um, statt in einem Commit.

Die lesenden Methoden von IMarketRepository (GetById, FindByTokenId, GetActive) liefern seit #623 nur Global-Märkte — ihre Aufrufer arbeiten mit Gamma-IDs und Token-IDs.

Migration AddPlatformColumns: Es gibt keine automatischen Migrationen beim Start. Vor dem ersten Start einer Fassung mit #623 muss auf dem Server dotnet ef database update --project src/PolyTrader.Core laufen, sonst scheitert jede Abfrage an der fehlenden Spalte Platform.

5.6 Module erklären ihre Plattformen („Auslöser")

Erweiterung von IPolyTraderModule, abwärtskompatibel über Default-Implementierungen:

/// Plattformen, für die dieses Modul geprüft und freigegeben ist.
/// Vorgabe: nur Polymarket Global – ein Modul, das nichts erklärt, verhält sich wie heute.
IReadOnlySet<PlatformId> SupportedPlatforms => new HashSet<PlatformId> { PlatformId.PolymarketGlobal };

/// Grund, warum das Modul eine erklärte Plattform in dieser Installation trotzdem nicht bedienen kann
/// (fehlende Fähigkeit, fehlender Schlüssel). null = bedienbar.
string? GetPlatformBlocker(PlatformId platform, PlatformCapabilities caps) => null;

Eine Stelle für die Regeln. Wie ModuleActivationView seit #560 liegt die Auswertung im Core (ModulePlatformView), und Desktop und WebUI zeigen dasselbe:

Wo Was man sieht
Modul-Tab (Desktop, WebUI) je Modul Plattform-Kennzeichen: ✓ bedienbar, ⚠ erklärt aber blockiert (mit Grund), — nicht erklärt
Kontenansicht je Konto die Module, die es bedienen
Dashboard-Warnung aktives Konto, das kein aktives Modul bedient; aktives Modul ohne passendes Konto

Wirksam an zwei Stellen:

  1. Zuteilung: Ein Modul bekommt nur Konten, deren Plattform es erklärt und nicht blockiert. Module filtern nicht selbst — sie fragen den Core nach „meinen Konten".
  2. Handelspfad: Die Registry reicht jedem Modul ein IOrderGateway, das OriginModule prüft und eine Order für eine nicht erklärte Plattform ablehnt und ins Journal schreibt. Das ist die zweite Sicherung nach dem Muster von TradingGate (#570): Eine Sperre, die nur in der Oberfläche existiert, hält keinen Programmierfehler auf.

Erste Erklärungen:

Modul Global US Begründung
CopyTrading ✓ — braucht PublicTraderActivity; plattformübergreifend siehe #628
ResolutionFarming ✓ ✓ (ab #626) nur eigene Marktdaten nötig, US-Gebühr nahe 1 sehr klein
Accounting ✓ ✓ (ab #627) Quellen je Plattform
Supervisor ✓ ✓ liest Journal und Dossiers, handelt nicht; CounterfactualJob über IMarketCatalog

5.7 Strategieprofile je Plattform

Ein Modul, das mehrere Plattformen erklärt, hält Einstellungen je Plattform (rf_settings.platform). Das Profil legt fest:

  • Gebührenmodell (aus der Registry, nie hart kodiert) — betrifft jeden Netto-Edge-Check.
  • Schwellen (Einstiegspreis, Mindest-Edge, Mindestliquidität). Bei getrenntem, kleinerem US-Pool sind Global-Werte nicht übertragbar; Startwerte stammen aus der Messphase.
  • Order-Stil: auf US lohnt Post-only wegen des Maker-Rabatts; auf Global zahlt der Maker 0.
  • Abwicklung: Global mit Redeem, US ohne.

Die fachliche Logik (Kandidatenbewertung, Sizing, Risikogrenzen) bleibt gemeinsam und testbar ohne Plattform.

5.8 Demo-Modus

Demo bleibt eine Eigenschaft des Kontos (IsDemo). Demo-Fills rechnen gegen das Buch der Plattform des Kontos, mit deren Gebührenmodell. Da die US-Retail-API keine Sandbox hat, ist Demo gegen das echte US-Buch der einzige Weg zur Messphase — und damit Pflicht vor dem Livegang.


6. Umsetzungsschritte

Item Schritt Voraussetzung Ergebnis
#622 PL-0 Voraussetzungen US-Kunde — KYC, API-Schlüssel, Serverstandort, Rolle geklärt
#623 PL-1 Plattform-Fundament — Verträge, Registry, Global-Adapter, DB-Spalten; Verhalten unverändert
#624 PL-2 Plattform-Erklärung der Module #623 Anzeige + Zuteilung + Sperre im Gateway
#625 PL-3 Adapter Polymarket US #623; für echte Orders #622 erst lesend, dann Demo, dann Orders
#626 PL-4 ResolutionFarming für US #624, #625 Profil je Plattform, Messphase
#627 PL-5 Accounting für US #625 US-Quellen statt Data-API/Chain
#628 PL-6 CopyTrading plattformübergreifend #625 + Entscheidung nicht beschlossen
#629 PL-7 Adapter Kalshi #623 + konkreter Bedarf nicht beschlossen

Akzeptanzkriterien

PL-1

  • Alle bestehenden Tests grün, ohne Anpassung ihrer Erwartungen.
  • Diff an PolymarketClobClient.cs ist leer. Backup trotzdem vor Beginn (Regel).
  • Migration auf einer Kopie der Server-Datenbank: Bestandspositionen haben danach platform = polymarket-global und denselben OutcomeKey wie vorher TokenId.
  • Neue Tests: Registry löst je Plattform auf; Global-Adapter reicht Aufrufe 1:1 an den Client durch.

PL-2

  • Ein Modul ohne Erklärung verhält sich exakt wie heute.
  • Test: Order über das Gateway mit nicht erklärter Plattform → abgelehnt, Journal-Eintrag, keine HTTP-Anfrage.
  • Desktop und WebUI zeigen für dieselbe Konfiguration dieselben Kennzeichen (ein gemeinsamer Test gegen ModulePlatformView).

PL-3

  • Signatur gegen die Beispielwerte der Doku geprüft (Einheitentest mit festem Schlüssel und Zeitstempel).
  • Lesend gegen Produktion: Märkte, Buch, Positionen, Saldo des Kundenkontos.
  • 429 → Backoff; „Global Rate Limit Exceeded" → kein Drosseln (siehe Abschnitt 2).
  • Secret nie im Log, nie im WebUI, verschlüsselt at-rest.
  • Erste echte Order erst nach PL-4-Messphase und mit Freigabe durch Richard.

PL-4

  • Netto-Edge-Check nutzt das US-Gebührenmodell (Test mit p = 0,50 und p = 0,97).
  • Demo-Messphase mit definiertem Zeitraum und Auswertung, bevor Live freigegeben wird.

7. Ausblick Kalshi

Kalshi ist der Prüfstein für PL-1: Lässt sich der Adapter bauen, ohne ein Modul anzufassen, trägt die Abstraktion. Bekannte Unterschiede (vor dem Bau gegen die aktuelle Kalshi-Doku prüfen): eigene Anmeldung mit signierten Anfragen und privatem Schlüssel, Märkte über Ticker, Preise in Cent, eigene Gebührenformel, eigene Demo-Umgebung. Genau deshalb ist LimitPrice im Vertrag eine Wahrscheinlichkeit 0..1 und die Umrechnung Sache des Adapters.


8. Offene Fragen

Frage Wer Item
Gibt es für die Retail-API eine Testumgebung? Anfrage an Polymarket US #622
Wo läuft die Installation des US-Kunden, und darf sie zugleich Global-Konten führen? Richard #622
Rechtliche Einordnung unserer Rolle bei automatisiertem Handel auf einem fremden KYC-Konto Richard / Rechtsberatung #622
Lohnt CopyTrading mit Global-Signal und US-Ausführung trotz anderer Kurse? Entscheidung #628
Reihenfolge gegenüber Themenbereich A (Abnahme, #621 Betriebsfreigabe) Richard —