116 lines
5 KiB
Markdown
116 lines
5 KiB
Markdown
# Modul-03-Market-Data — Betriebsdokumentation
|
|
|
|
> Erstellt: 20.08.2026 (Rain Ocampo) · Status: ✅ In Betrieb (healthy)
|
|
|
|
## Zweck
|
|
Zentrale Marktdatenquelle des 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).
|
|
```
|