Files
Predictalytics/docs/PLAN-DatenIngest-Skalierung.md
T

138 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. **2a2c** (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.