trading-system-docs/notes/trading/system-docs/modul-03-market-data.md

121 lines
5 KiB
Markdown

---
type: Trading-Modul-System
_organized: true
---
# Modul-03-Market-Data — Betriebsdokumentation
> Erstellt: 20.08.2026 (Rain Ocampo) · Status: ✅ In Betrieb (healthy)
## Zweck
Zentrale Marktdatenquelle des Modul-Trading-Systems. Pipeline:
`Marktdaten → Validierung → Normalisierung → PostgreSQL → RabbitMQ-Event`
Keine Strategie, keine Orders, keine KI — nur zuverlässige Marktdaten.
## Container
| Attribut | Wert |
|----------|------|
| Name | `Modul-03-Market-Data` |
| Image | `market-data:0.1.0` (lokal gebaut) |
| Port | **55003****NUR intern** (`expose`, nicht öffentlich) |
| Netzwerk | `trading-modules` (bridge) |
| Build-Context | `/opt/trading-modules/modul03-market-data/` |
| Restart | `unless-stopped` |
| Healthcheck | ✅ `healthy` |
## Deployment
```bash
cd /opt/trading-modules
docker compose build modul-03-market-data
docker compose up -d modul-03-market-data
```
## Environment Variables (Compose)
| Variable | Wert (Default) | Zweck |
|----------|---------------|-------|
| `PG_HOST` | `Modul-01-PostgreSQL` | Docker-interner Servicename |
| `PG_PORT` | `5432` | intern (Host: 55432) |
| `PG_USER/PASSWORD/DB` | `trading` | aus `.env`-Defaults |
| `RABBITMQ_HOST` | `Modul-02-RabbitMQ` | Docker-interner Servicename |
| `RABBITMQ_PORT` | `5672` | intern (Host: 55672) |
| `RABBITMQ_USER/PASSWORD/VHOST` | `trading` | **vhost `trading`** |
| `DATA_PROVIDER` | `noop` | `noop` / `demo` / später Broker |
| `LOG_LEVEL` | `INFO` | Strukturiertes Logging |
Keine festen IPs — nur Docker-interne Hostnamen. Creds via Compose env + `${VAR:-default}`.
## Datenbank (Modul-01-PostgreSQL)
Tabelle `public.ohlcv`:
```sql
symbol TEXT, asset_class TEXT, provider TEXT, timeframe TEXT,
ts TIMESTAMPTZ, open/high/low/close DOUBLE PRECISION, volume DOUBLE PRECISION,
created_at TIMESTAMPTZ DEFAULT now(), id BIGSERIAL PRIMARY KEY
```
### Finale Constraints & Indizes (Stand 20.08.2026)
| Index | Typ |
|-------|-----|
| `uq_ohlcv_provider_symbol_tf_ts` | **UNIQUE** `(provider, symbol, timeframe, ts)` |
| `ohlcv_pkey` | UNIQUE `(id)` |
| `idx_ohlcv_symbol_tf` | `(symbol, timeframe)` |
| `idx_ohlcv_symbol_tf_ts` | `(symbol, timeframe, ts DESC)` |
| `idx_ohlcv_provider_symbol_tf_ts` | `(provider, symbol, timeframe, ts DESC)` |
Der Unique-Index inkludiert den **Provider** — langfristig werden mehrere Provider/Broker unterstützt
(gleiche Symbol+Timeframe+Timestamp können von verschiedenen Quellen kommen).
**Migrationen:** `/app/migrations/001_ohlcv.sql` + `002_unique_provider.sql`
(idempotent, löschen nichts; automatisch via `ensure_schema()`/glob angewendet).
## RabbitMQ (Modul-02)
- **Exchange:** `market.data` (topic, durable)
- **vhost:** `trading` (wichtig!)
- **Routing-Keys:**
| Routing-Key | Event-Typ | Wann |
|-------------|-----------|------|
| `market.data.ready` | `MARKET_DATA_READY` | **Batch/Import** — EIN Event pro Batch, `candle_count` + `batch:true` |
| `market.data.candle.closed` | `MARKET_CANDLE_CLOSED` | **Live** — pro abgeschlossener Kerze EIN Event, OHLCV im payload |
- Eventschema v1.0: `event_id, event_type, event_version, timestamp, symbol, asset_class, timeframe, provider` + payload.
## Interne API
| Endpoint | Zweck |
|----------|-------|
| `GET /health` | Liveness (200 immer) + Komponentenstatus im Body |
| `GET /health/ready` | Readiness (503 wenn PG/RabbitMQ down) |
| `POST /ingest` | Kerzen einspeisen (JSON: `{"candles":[...]}`) |
| `GET /prices/{symbol}` | Letzte Kurse |
| `GET /history/{symbol}?timeframe=` | Historische OHLCV |
## Provider-Adapter
`app/providers/providers.py`:
- `DataProvider` (ABC) — abstrakte Schnittstelle `fetch_ohlcv()`, `health()`
- `NoopProvider` — keine Datenquelle konfiguriert (Default)
- `DemoProvider` — synthetische OHLCV-Daten für Tests
- Neue Broker = neue Klasse, umschalten via `DATA_PROVIDER` env → keine harte Anbieter-Kopplung
## Validierung (`app/validation/validator.py`)
- Timestamp gültig (UTC, nicht Zukunft, nicht zu alt/stale)
- OHLC-Werte > 0
- High ≥ Low, High ≥ Open/Close, Low ≤ Open/Close
- Duplikat-Erkennung (Storage + DB-Unique-Index)
## End-to-End-Test (20.08.2026, nach Provider-Constraint-Upgrade) ✅
- **Batch-Pfad:** 4 GOOG-Kerzen → saved:4, **GENAU EIN** `MARKET_DATA_READY` (routing `market.data.ready`, candle_count:4). 3 NVDA → saved:3, EIN Event.
- **Live-Pfad:** 1 AMZN-Candle → `MARKET_CANDLE_CLOSED` (routing `market.data.candle.closed`, OHLCV im payload).
- **Provider-Duplikat:** identische NVDA-Kerze erneut → `duplicate`, kein Insert, kein Event.
- **Port-Sicherheit:** 55003 von außen (`http://187.124.31.123:55003/health`) → **nicht erreichbar**
- Datenbestand final: AAPL 5, GOOG 4, NVDA 3, EURUSD 3, TSLA 4, MSFT 1, AMZN 1.
## Offene Punkte
- Echter Broker-/Datenprovider-Adapter (Interface bereit, noop/demo Defaults)
- Zusätzliche Daten (Bid/Ask/Spread, Ticks, Fundamentaldaten) — vorbereitet
- Consumer für `market.data.ready` / `market.data.candle.closed` (Modul-04 ff.)
---
```
Geändert von: Rain Ocampo
Datum: 20.08.2026
Grund: Modul-03-Dokumentation angelegt (Provider-Constraint, Event-Trennung, Port-Nicht-Exposition).
```