Eine Roadmap statt neun Plandokumente; alte Plaene ins Archiv
Die offenen Punkte lagen ueber neun Dokumente verstreut, teils widersprechend, teils mit Punkten, die laengst umgesetzt waren. ROADMAP.md fuehrt sie zusammen: sechs Stufen, jeder Punkt gegen den Code geprueft. Markierung ueber eine Legende, damit Konzepte nicht mit Aufgaben verwechselt werden: dringend, eingeplant, Backlog, Konzept (durchdacht, aber bewusst nicht eingeplant), liegt beim Nutzer, verworfen. Stufen: 0 Sofort (OpenRouter-Key) - 1 Aufraeumen abschliessen (Merge nach main, Nullable-Warnungen, Startup-Backfill) - 2 Portierung abschliessen (Linux-Erstlauf und Verifikation, dann Deployment/CI) - 3 Analytik schaerfen (Strategie- Klassifikation, Backtest-Harness, Track-Record im Score, Ranking) - 4 Daten- haushalt (SQL des Nutzers) - 5 Ingest skalieren - 6 Monetarisierung vorbereiten. Ein Anhang haelt fest, was geprueft und bewusst NICHT auf die Roadmap kam, damit es nicht versehentlich wieder als Aufgabe auftaucht (Azuro/Limitless, die Marketing-Seiten, Kaltarchiv, EF Core 10). Archiv: FIXPLAN-DONE, FIXPLAN-G-Speicher, FIXPLAN-TODO, FIXPLAN-UI-Ranglisten, UMSETZUNGSPLAN sowie ANALYSE-Linux-Portierung, PLAN-Linux-Portierung, PLAN-Architektur-WebUI-Backend und PLAN-DatenIngest-Skalierung liegen jetzt unter docs/archiv/ mit einer README, die jedes Dokument einordnet. Sie bleiben als Begruendungs- und Detailquelle - die Roadmap nennt jeden Punkt knapp, die Herleitung steht dort. Dabei aufgefallen: die beiden nie gepflegten Plaene (Architektur, DatenIngest) zeigten 40 offene Punkte, von denen die Haelfte umgesetzt war - Read/Control- Split, /api/capabilities, CORS-Whitelist, Egress-Kanaele mit Cooldown und Per-Kanal-Limiter, Ingest-Tiering. Nachgeprueft, abgehakt und mit Statusblock eingeordnet, sonst waere das Archiv selbst eine Fehlerquelle. Was dabei nur teilweise umgesetzt war, ist als Roadmap 5.3 aufgenommen (Health-Statistik je Kanal, multi-homed pruefen, DB-Guard, Plausibilitaets- pruefung), ebenso der nie durchgefuehrte Audit auf versteckte Writes in Read-Endpunkten (6.1). Header-Rotation ist als verworfen markiert statt offen zu bleiben: Verschleierung gegenueber Polymarket riskiert genau den Zugang, auf dem das Projekt aufsetzt. STATUS.md beschreibt jetzt nur noch den Ist-Stand und verweist fuer die offenen Punkte auf die Roadmap, damit nichts doppelt gepflegt wird. CLAUDE.md nennt beide Einstiege. Build gruen, 126 Tests gruen, keine toten Links. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
# Plan: Daten-Ingest skalieren (Rate-Limits, Egress-Kanäle, Blockchain)
|
||||
|
||||
> **Archiviert am 2026-08-23** — offene Punkte siehe [`ROADMAP.md`](../../ROADMAP.md),
|
||||
> Stufe 5. Dieses Dokument wird nicht mehr gepflegt.
|
||||
>
|
||||
> **Erledigt und hier nachträglich abgehakt:**
|
||||
> * **1c Ingest-Tiering** — `IngestMode` mit `SnapshotOnly`/`Aggregated`, siehe
|
||||
> [`FIXPLAN-TODO.md`](FIXPLAN-TODO.md) Teil D.
|
||||
> * **1d Redundanz** — `IMemoryCache` im `PolymarketApiClient`.
|
||||
> * **2a/2b Egress-Kanäle** — `Infrastructure/Services/EgressPoolService.cs` und
|
||||
> `EgressPoolHandler.cs`: Round-Robin über die aktiven Kanäle, Umschalten rein per
|
||||
> Konfiguration, Cooldown nach drei Fehlschlägen in Folge (`:152`), sodass ein 429 nur
|
||||
> den betroffenen Kanal trifft. Der Limiter-Schlüssel enthält den Kanal
|
||||
> (`RateLimiterService.cs:36`). Tests: `EgressPoolTests.cs`.
|
||||
>
|
||||
> **Teilweise:** Health-Statistik je Kanal (Erfolgsquote, 429-Rate, Latenz) fehlt — es gibt
|
||||
> nur Fehlerzähler und Cooldown. Tote Kanäle werden also pausiert, aber nicht ausgewertet.
|
||||
>
|
||||
> **Offen:** 1a (Blockchain/Subgraph als Bulk-Quelle) und 1b (Markt-Stammdaten-Cache) sind
|
||||
> Roadmap 5.1 und 5.2. **Abschnitt 3a (Header-Rotation) ist bewusst verworfen** — siehe
|
||||
> Roadmap 5.3: Verschleierung gegenüber Polymarket riskiert genau den Zugang, auf dem das
|
||||
> Projekt aufsetzt.
|
||||
|
||||
> 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)
|
||||
- [x] 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
|
||||
- [x] 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" }
|
||||
]
|
||||
}
|
||||
```
|
||||
- [x] 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;`.
|
||||
- [x] `IEgressPool` verteilt Requests round-robin/least-loaded über die aktiven Kanäle.
|
||||
Leere/❑ Kanalliste = heutiges Verhalten (ein Default-Ausgang).
|
||||
- [x] Umschalten „nur eigene IPs" ↔ „nur Proxys" ↔ „Mix" = Config ändern, kein Deploy-Umbau.
|
||||
|
||||
### 2b. Rate-Limiter **pro Kanal** (Generalisierung des globalen Limiters)
|
||||
- [x] 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.
|
||||
- [x] 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
|
||||
- [x] Per-Kanal-Limiter: unabhängige Budgets (kein globales Blocken); 429 sperrt nur einen Kanal.
|
||||
- [x] Egress-Binding: Smoke gegen einen Dienst, der die Quell-IP zurückgibt → Round-Robin nutzt
|
||||
wirklich verschiedene IPs; Proxy-Kanal geht über den Proxy.
|
||||
- [x] 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.
|
||||
Reference in New Issue
Block a user