trading-system-docs/modul-04-market-regime.md

152 lines
7.6 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.

# Modul-04-Market-Regime — Betriebsdokumentation
> Erstellt: 20.08.2026 (Rain Ocampo) · Status: ✅ Freigegeben (E2E bestanden)
## Zweck
Erster **Consumer** der Market-Data-Events von Modul-03. Pipeline:
`MARKET_DATA_READY / MARKET_CANDLE_CLOSED (Modul-03) → Regime-Berechnung → PostgreSQL (market_regime) → MARKET_REGIME_READY`
**Deterministische, regelbasierte Engine — bewusst OHNE KI/ML.**
## Container
| Attribut | Wert |
|----------|------|
| Name | `Modul-04-Market-Regime` |
| Image | `market-regime:1.0.0` (lokal gebaut) |
| Port | **55004****NUR intern** (`expose`, nicht öffentlich) |
| Netzwerk | `trading-modules` (bridge) |
| Build-Context | `/opt/trading-modules/modul04-market-regime/` |
| Restart | `unless-stopped` |
| Healthcheck | ✅ `healthy` |
## Deployment
```bash
cd /opt/trading-modules
docker compose build modul-04-market-regime
docker compose up -d --no-deps --force-recreate modul-04-market-regime
```
## 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`** |
| `LOG_LEVEL` | `INFO` | Strukturiertes Logging |
Keine festen IPs — nur Docker-interne Hostnamen. Creds via Compose env + Defaults.
## Architektur
```
Modul-03 ──market.data.ready / market.data.candle.closed──▶ RegimeConsumer
│ (bindet beide Routing-Keys)
RegimeEngine (deterministisch)
EMA / ADX / ATR / Slope / Preisstruktur
┌───────────┴───────────┐
▼ ▼
market_regime (PG) MARKET_REGIME_READY
(16 Spalten) → market.regime.ready
```
- **Consumer** (`app/consumer/consumer.py`): bindet `market.data.ready` + `market.data.candle.closed`; durable Queue `market-regime.input`; manuelles Ack; Reconnect mit Backoff; **schließt alte Verbindung beim Reconnect** (verhindert Consumer-Leak/Nachrichtenverlust).
- **Engine** (`app/regime/engine.py`): deterministisch, ohne KI.
- **Storage** (`app/storage/storage.py`): idempotent via partiellen Unique-Index.
- **Publisher** (`app/publisher/publisher.py`): publiziert `MARKET_REGIME_READY` auf `market.regime` (Routing `market.regime.ready`).
- **History-Client** (`app/marketdata/client.py`): liest OHLCV über interne FastAPI Modul-03 (`http://Modul-03-Market-Data:55003/history/{symbol}`).
## Regime-Engine (`app/regime/engine.py`)
Deterministische Regel-Engine (Version `1.0.0`), 7 Regime:
`TREND_UP, TREND_DOWN, RANGE, HIGH_VOLATILITY, LOW_VOLATILITY, TRANSITION, UNKNOWN`
**Indikatoren & Metriken:**
| Indikator | Fenster/Param | Zweck |
|-----------|---------------|-------|
| EMA fast/slow | 10 / 30 | Trendrichtung (EMA-Flanken-Differenz) |
| ADX | 14 | Trendstärke (≥20 = echter Trend) |
| ATR | 14 | Volatilität (absolut + Ratio + Perzentil) |
| Slope | 20 | normierte Steigung der Close-Linie |
| Preisstruktur | — | higher_highs / lower_lows / range |
**Zentrale Schwellenwerte** (`app/config.py`, env-overridable):
| Parameter | Default | Bedeutung |
|-----------|---------|-----------|
| `min_candles_required` | 30 | UNKNOWN, wenn weniger Daten |
| `regime_lookback` | 60 | max. Kerzen für Berechnung |
| `trend_min_ema_gap` | 0.02 | |EMA_fast-EMA_slow|/close ≥ → Trend |
| `adx_trend_threshold` | 20.0 | ADX ≥ → echter Trend |
| `slope_up/down_threshold` | 0.05 / -0.05 | normierte Steigung |
| `range_atr_ratio` | 0.02 | ATR/close darunter = Range |
| `atr_high_vol_multiplier` | 1.5 | ATR jetzt > hist_mean × → HIGH_VOL |
| `atr_low_vol_multiplier` | 0.6 | ATR jetzt < hist_mean × LOW_VOL |
| `high_vol_atr_ratio` | 0.03 | ATR/close starke Vol |
| `low_vol_atr_ratio` | 0.008 | ATR/close geringe Vol |
| `slope_threshold` | 0.01 | |Slope| darunter = seitwärts |
| `transition_min_events` | 3 | Events für TRANSITION |
## Datenbank (Modul-01-PostgreSQL)
Tabelle `public.market_regime` (16 Spalten, eigene Tabelle bestehende unangetastet):
```sql
symbol TEXT, asset_class TEXT, timeframe TEXT, provider TEXT,
regime TEXT, confidence INTEGER (0-100),
trend_strength DOUBLE PRECISION, volatility_state TEXT,
timestamp TIMESTAMPTZ, indicators_json JSONB,
candles_used INTEGER, version TEXT,
source_event_id TEXT, correlation_id TEXT,
data_ts TIMESTAMPTZ, data_ts_end TIMESTAMPTZ
```
- **Unique (partiell):** `uq_market_regime_src` auf `(symbol, timeframe, source_event_id)` **WHERE source_event_id IS NOT NULL** Idempotenz.
- Migration: `migrations/001_market_regime.sql` (idempotent, löscht nichts).
## RabbitMQ (Modul-02)
| Exchange | Typ | Routing | Event |
|----------|-----|---------|-------|
| `market.data` | topic | `market.data.ready` (eingang) | MARKET_DATA_READY |
| `market.data` | topic | `market.data.candle.closed` (eingang) | MARKET_CANDLE_CLOSED |
| `market.regime` | topic | `market.regime.ready` (**ausgang**) | MARKET_REGIME_READY |
## Interne API
| Endpoint | Zweck |
|----------|-------|
| `GET /health` | Liveness (200 immer) + Komponentenstatus im Body |
| `GET /health/ready` | Readiness (503 wenn PG/RabbitMQ/Modul-03 down) |
| `GET /regime/{symbol}` | Regime-Einträge abfragen |
| `GET /regime/latest/{symbol}` | Letztes Regime eines Symbols |
## End-to-End-Test (20.08.2026, final, nach Rebuild) ✅
Kette verifiziert: Modul-03 Ingest `market.data.ready` Modul-04 Consumer RegimeEngine `market_regime` `MARKET_REGIME_READY` auf `market.regime.ready`.
| Fall | Regime | Conf | candles | version | Event | DB |
|------|--------|------|---------|---------|-------|----|
| M4TREND_UP (40) | TREND_UP | 100 | 40 | 1.0.0 | genau 1 | |
| M4TREND_DN (40) | TREND_DOWN | 100 | 40 | 1.0.0 | genau 1 | |
| M4RANGE (40) | LOW_VOLATILITY (Range) | 60 | 40 | 1.0.0 | genau 1 | |
| M4HIGHVOL (40) | HIGH_VOLATILITY | 75 | 40 | 1.0.0 | genau 1 | |
| M4UNKNOWN (5) | UNKNOWN | 20 | 5 | 1.0.0 | genau 1 | |
| M4IDEMPOT (40) | TREND_UP | 100 | 40 | 1.0.0 | genau 1 | |
**Idempotenz:** dasselbe Quell-Event (`source_event_id`) erneut **kein zweiter Datensatz**, kein Doppel-Event.
**Logs:** keine Errors/Tracebacks. Health `{postgresql:true, rabbitmq:true, market_data_ready:true}`.
## Bugs behoben während E2E (20.08.2026)
| Bug | Fix |
|-----|-----|
| `can't adapt type 'dict'` (JSONB) | `json.dumps(ind.model_dump(mode="json"))` |
| `tuple index out of range` (16/15) | `version` in INSERT-VALUES ergänzt |
| `ON CONFLICT` + partieller Index Fehler | `WHERE source_event_id IS NOT NULL` in Klausel |
| `model_dump(default=...)` TypeError | `default`-Kwarg entfernt (`model_dump(mode="json")`) |
| Consumer-Verbindungs-Leak | `conn.close()` bei Reconnect kein Message-Leak |
## Offene Punkte
- Consumer-Downstream für `market.regime.ready` (Modul-05+)
- Bestätigte TRANSITION-Detektion mit echten Folgedaten
---
```
Geändert von: Rain Ocampo
Datum: 20.08.2026
Grund: Modul-04-Dokumentation angelegt (Regime-Engine, market_regime-Schema, Events, E2E freigegeben).
```