# 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). ```