157 lines
7.6 KiB
Markdown
157 lines
7.6 KiB
Markdown
---
|
||
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).
|
||
```
|