# Plan: Daten-Ingest skalieren (Rate-Limits, Egress-Kanäle, Blockchain) > Stand: 2026-07-12 · Status: **Richtung bestätigt** (Nutzer: beide Egress-Wege umschaltbar > umsetzen, sofern Aufwand vertretbar). Kein Code in diesem Schritt. > Problem: Polymarket-Rate-Limits bremsen den Datenimport teils extrem aus. ## 0. Ist-Zustand (verifiziert) - `RateLimiterService` = **globaler Singleton-Token-Bucket**, feste Delays je Endpoint-Gruppe (Gamma ≈28/s, Data ≈18/s, Clob ≈66/s). - `PolymarketApiClient` = Singleton mit 3 benannten HttpClients, **ohne** Proxy/IP-Konfig → ein einziger Ausgangs-IP, ein globales Budget. Das ist die echte Bremse. **Zwei Hebel:** Nachfrage senken (Abschnitt 1, größter Gewinn) und Angebot erhöhen (Abschnitt 2, Egress-Kanäle). Der Nutzer möchte in Abschnitt 2 **beide Egress-Arten (eigene IPs UND Proxys) umschaltbar** — und das ist billig, weil beide **derselbe HttpClient-Seam** sind (Abschnitt 2). --- ## 1. Nachfrage senken — größter Hebel (zuerst) ### 1a. Historische Massendaten aus Blockchain/Subgraph statt REST Polymarket-Trades sind On-Chain-Events auf Polygon. Für **Backfill/Deep-Resync** (größter REST-Verbraucher) ist `/activity` die falsche Quelle. - [ ] The-Graph-/Goldsky-Subgraph für Polymarket evaluieren: ein GraphQL-Call → tausende Fills eines Wallets, statt seitenweiser rate-limitierter REST-Abrufe. - [ ] Neue `IHistoricalTradeSource` neben dem REST-Provider; DeepResync (FIXPLAN A6) zieht Bulk hierüber. **Effekt:** nimmt die teuerste Last komplett von der REST-API. ### 1b. Markt-Stammdaten aggressiv cachen - [ ] Metadaten (Frage, Kategorie, Outcomes, `ConditionId`↔`TokenId`) einmal ziehen, lange cachen; nur Volume/Liquidity/Auflösung periodisch aktualisieren. Geschlossene Märkte nie voll re-syncen. ### 1c. Ingest-Tiering (FIXPLAN D3) - [ ] Ultra-HF-Trader im `SnapshotOnly`-Modus erzeugen null Trade-Calls (PnL aus `/positions` + Leaderboard, wöchentliche Biopsie). Entfernt genau die Wallets, die das Budget auffressen. ### 1d. Redundanz vermeiden - [ ] Worker-übergreifender Kurzzeit-Cache „gerade geholt", damit nicht mehrere Worker denselben Markt/Trader kurz hintereinander abrufen. --- ## 2. Angebot erhöhen: umschaltbare **Egress-Kanäle** (eigene IPs UND Proxys — eine Mechanik) **Die Schlüssel-Einsicht:** Ob eine Anfrage über eine **eigene Quell-IP** oder über einen **Proxy** rausgeht, ist im HttpClient nur eine andere Konfiguration desselben `SocketsHttpHandler`. Deshalb wird **nicht** zweimal gebaut, sondern **ein** Konzept: der **Egress-Kanal**. Umschalten = Konfiguration, nicht Code. Damit bekommt der Nutzer „beides, umschaltbar" zu geringen Kosten. ### 2a. Abstraktion `EgressChannel` Ein Kanal ist genau eine Ausgangsroute, per Config als einer von zwei Typen definiert: ```jsonc "Egress": { "Channels": [ { "id": "ip-a", "type": "SourceIp", "value": "203.0.113.10" }, // eigene IP { "id": "ip-b", "type": "SourceIp", "value": "203.0.113.11" }, { "id": "prox-1", "type": "Proxy", "value": "http://user:pass@proxy.example:8080" } ] } ``` - [ ] Pro Kanal **ein** `SocketsHttpHandler`: - `SourceIp` → `ConnectCallback`, Socket vor Connect an die lokale IP binden (`socket.Bind(new IPEndPoint(ip, 0))`). - `Proxy` → `handler.Proxy = new WebProxy(url); handler.UseProxy = true;`. - [ ] `IEgressPool` verteilt Requests round-robin/least-loaded über die aktiven Kanäle. Leere/❑ Kanalliste = heutiges Verhalten (ein Default-Ausgang). - [ ] Umschalten „nur eigene IPs" ↔ „nur Proxys" ↔ „Mix" = Config ändern, kein Deploy-Umbau. ### 2b. Rate-Limiter **pro Kanal** (Generalisierung des globalen Limiters) - [ ] Limiter-Schlüssel wird `{platform}-{endpointGroup}-{channelId}`. Jeder Kanal hält sein eigenes Budget → N Kanäle ≈ N× Durchsatz, jeder Kanal bleibt unter dem Per-Route-Limit. - [ ] 429 sperrt **nur den betroffenen Kanal** kurz, nicht alle. ### 2c. Betrieb - [ ] Bei eigenen IPs prüfen: sind es echte getrennte Egress-IPs (multi-homed), nicht NAT hinter einer. - [ ] Health/Statistik je Kanal (Erfolg, 429-Rate, Latenz), damit tote Proxys automatisch pausiert werden. **Aufwand:** moderat und **einmalig** — durch die gemeinsame Abstraktion kostet „beides umschaltbar" kaum mehr als „nur IPs". --- ## 3. Proxys & Header-Rotation — Einordnung (korrigiert) **Klarstellung (Korrektur einer früheren Fassung):** Predictalytics betreibt **kein Trading und keine Wallet** — es ist reine Analysesoftware, die ausschließlich **öffentliche** Marktdaten liest. Das frühere „Ban gefährdet die Trading-Wallet"-Argument gehört zu **PolyTrader** (getrenntes Projekt) und trifft hier **nicht** zu. Damit ist die Proxy-Wahl eine reine Engineering-Entscheidung des Nutzers — freie Proxys eingeschlossen. Was real bleibt (ehrliche Hinweise, keine Blocker — Entscheidung liegt beim Nutzer): - **ToS-Grauzone:** Rate-Limit-Umgehung per Routen-/Header-Rotation widerspricht vermutlich Polymarkets Nutzungsbedingungen. Realistische Konsequenz **hier**: einzelne IPs/Proxys werden geblockt und müssen ersetzt werden — mehr nicht (kein Kapital, keine Wallet betroffen). - **Datenintegrität (der eigentlich relevante Punkt):** Ein kaputter/bösartiger (v. a. gratis) Proxy kann Antworten **verfälschen** → korrupte Analyse („garbage in, garbage out"). Da die gesamte Auswertung darauf aufbaut, lohnt sich eine **Plausibilitätsprüfung** der Antworten (Feldtypen, Wertebereiche, z. B. Preise ∈ [0,1]) und — wo Genauigkeit zählt — das Bevorzugen kontrollierter Proxys/eigener IPs. Rein informativ, kein Zwang. - **DB-Verbindung NIE über Proxy** (ausdrücklicher Nutzer-Wille): Die MySQL-Verbindung läuft **immer direkt**. Der Egress-Pool gilt **ausschließlich** für ausgehende Polymarket-HTTP-Calls, niemals für die DB. ### 3a. Header-Rotation (separat aktivierbar, standardmäßig AUS) Auf Wunsch integriert, bewusst opt-in: - [ ] Config `Egress.HeaderRotation.Enabled` — **Default `false`**. Bei `false` wird ein einziger, konsistenter Standard-Header-Satz gesendet (heutiges Verhalten). - [ ] Rotiert **kohärente Header-SETS**, nicht nur den User-Agent isoliert: je Eintrag ein zusammenpassendes Bündel (`User-Agent` + `Accept` + `Accept-Language` + `Sec-CH-UA`…), damit die Kombination realistisch bleibt — ein moderner UA mit widersprüchlichen Accept-Headern fällt eher auf als gar keine Rotation. Set-Pool aus Config ladbar. - [ ] Auswahl pro Kanal **oder** pro Request (konfigurierbar). - [ ] Greift nur für Polymarket-Read-Calls; unabhängig vom Kanaltyp (IP oder Proxy) nutzbar. --- ## 4. Reihenfolge 1. **1b + 1d** (Caching/Dedup) — sofort, klein, spürbar. 2. **2a–2c** (Egress-Kanäle + Per-Kanal-Limiter) — moderat, liefert „eigene IPs UND Proxys umschaltbar". 3. **1c** (Tiering) — hängt an FIXPLAN D3. 4. **1a** (Blockchain/Subgraph-Bulk) — größter struktureller Hebel; eigener Rechercheschritt (welcher Subgraph deckt Fills sauber ab?), dann als `IHistoricalTradeSource`. ## 5. Tests / Abnahme - [ ] Per-Kanal-Limiter: unabhängige Budgets (kein globales Blocken); 429 sperrt nur einen Kanal. - [ ] Egress-Binding: Smoke gegen einen Dienst, der die Quell-IP zurückgibt → Round-Robin nutzt wirklich verschiedene IPs; Proxy-Kanal geht über den Proxy. - [ ] Kanal-Health: toter Proxy wird automatisch pausiert, Pool weicht aus. - [ ] Blockchain-Quelle: Stichproben-Abgleich Bulk-Historie ↔ REST-`/activity` eines Wallets, bevor sie produktiv wird. - [ ] **Header-Rotation:** bei `Enabled=false` wird genau ein konsistenter Standard-Satz gesendet (kein Rotieren); bei `Enabled=true` stammt jeder gesendete Header-Satz **unverändert** aus dem Pool (kohärent, keine zusammengewürfelten Felder). - [ ] **DB-Guard:** die MySQL-Verbindung nutzt nie den Egress-Pool/Proxy (Regressionsschutz). - [ ] **Response-Plausibilität:** grob unplausible Provider-Antworten (z. B. Preis außerhalb [0,1]) werden erkannt und verworfen statt in die DB zu wandern.