--- type: Trading-Modul-System _organized: true --- # 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). ```