diff --git a/modul-04-market-regime.md b/modul-04-market-regime.md new file mode 100644 index 0000000..3e3ef21 --- /dev/null +++ b/modul-04-market-regime.md @@ -0,0 +1,152 @@ +# 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). +```