8.3 KiB
| type | _organized |
|---|---|
| Modul | true |
Modul-05-Strategy-Engine — Betriebsdokumentation
Erstellt: 20.08.2026 (Rain Ocampo) · Status: ✅ Freigegeben (E2E bestanden)
Zweck
Strategie-Engine — konsumiert MARKET_REGIME_READY (Modul-04) + OHLCV (Modul-03), berechnet deterministische Handelssignale und persistiert sie.
Pipeline: MARKET_REGIME_READY (Modul-04) → Strategie-Engine (trend_pullback_v1) → PostgreSQL (strategy_signal) → SIGNAL_DETECTED
Deterministische, regelbasierte Engine — bewusst OHNE KI/ML, ohne Ranking, ohne Risk Management, ohne Order-Ausführung.
Container
| Attribut | Wert |
|---|---|
| Name | Modul-05-Strategy-Engine |
| Image | strategy-engine:0.1.0 (lokal gebaut) |
| Port | 55005 — NUR intern (expose, nicht öffentlich) |
| Netzwerk | trading-modules (bridge) |
| Build-Context | /opt/trading-modules/modul05-strategy-engine/ |
| Restart | unless-stopped |
| Healthcheck | ✅ healthy |
Deployment
cd /opt/trading-modules
docker compose build modul-05-strategy-engine
docker compose up -d --no-deps --force-recreate modul-05-strategy-engine
Environment Variables (Compose)
| Variable | Wert (Default) | Zweck |
|---|---|---|
PG_HOST |
Modul-01-PostgreSQL |
Docker-interner Servicename |
PG_PORT |
5432 |
intern |
PG_USER/PASSWORD/DB |
trading |
aus .env-Defaults |
RABBITMQ_HOST |
Modul-02-RabbitMQ |
Docker-interner Servicename |
RABBITMQ_PORT |
5672 |
intern |
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-04 ──market.regime.ready──▶ StrategyConsumer (strategy.input)
│
▼
OHLCV (Modul-03, intern 55003/history/{symbol})
│
▼
StrategyEngine (deterministisch)
trend_pullback_v1 (LONG/SHORT)
│
┌───────────┴───────────┐
▼ ▼
strategy_signal (PG) SIGNAL_DETECTED
(eigene Tabelle) → market.signals / strategy.signal.detected
- Consumer (
app/consumer/consumer.py): bindet Exchangemarket.regime, Routingmarket.regime.ready; durable Queuestrategy.input; manuelles Ack erst nach erfolgreicher Verarbeitung; Reconnect mit Backoff +conn.close();queue_deletebeim Start (entfernt verwaiste/Zombie-Consumer). - MarketData-Client (
app/marketdata/client.py): liest OHLCV über interne FastAPI Modul-03 (http://Modul-03-Market-Data:55003/history/{symbol}); normalisiert Feldts→timestamp. - Engine/Register (
app/engine.py): modular — Strategien via.name/.version/.evaluate()registriert. - Storage (
app/storage/storage.py): idempotent via partiellen Unique-Index. - Publisher (
app/publisher/publisher.py): frische Verbindung je Publish +conn.close()im finally (verhindertConnectionResetErrordurch RabbitMQ-Closed-Verbindungen). - Service (
app/core/service.py): Pipeline Event → OHLCV → Strategie → speichern + publizieren; robuste Payload-Extraktion.
Strategie V1 — trend_pullback_v1 (app/strategies/trend_pullback_v1.py)
Deterministische Pullback-Strategie. Nur abgeschlossene Candles, kein Lookahead-Bias.
LONG-Bedingungen (Regime TREND_UP):
- Close > steigender SMA200 (Trendfilter)
- Pullback: Close < EMA20 (Zug zurück in den Trend)
- Bestätigung: Close > prev Close ODER Break prev High
- Entry = Close; Stop unter Swing-Low; Target = 2R (R:R = 2.0)
SHORT-Bedingungen (Regime TREND_DOWN): spiegelbildlich
- Close < fallender SMA200
- Pullback: Close > EMA20
- Bestätigung: Close < prev Close ODER Break prev Low
- Stop über Swing-High; Target = 2R
Mathematik (vom E2E verifiziert):
- LONG:
Target = Entry + 2 × (Entry - Stop);R:R = 2.0; Stop < Entry - SHORT:
Target = Entry - 2 × (Stop - Entry);R:R = 2.0; Stop > Entry
Zentrale Schwellenwerte (app/config.py, env-overridable):
| Parameter | Default | Bedeutung |
|---|---|---|
lookback |
300 | max. Kerzen zur Berechnung |
min_candles_required |
220 | UNKNOWN, wenn < 220 (SMA200 braucht 200) |
sma_period |
200 | Trendfilter SMA |
ema_period |
20 | Pullback-EMA |
risk_reward |
2.0 | Target-Multiplikator (2R) |
swing_lookback |
10 | Swing-Low/High-Fenster für Stop |
Kein gültiges Setup → kein Event publiziert.
Datenbank (Modul-01-PostgreSQL)
Eigene Tabelle public.strategy_signal (bestehende unangetastet):
signal_id UUID, source_event_id TEXT, correlation_id TEXT,
timestamp TIMESTAMPTZ, symbol TEXT, asset_class TEXT, provider TEXT,
timeframe TEXT, strategy_name TEXT, strategy_version TEXT,
direction TEXT (LONG/SHORT), regime TEXT,
entry DOUBLE PRECISION, stop_loss DOUBLE PRECISION, target DOUBLE PRECISION,
risk_reward DOUBLE PRECISION, setup_metrics JSONB, trigger_reason TEXT
- Unique (partiell):
uq_strategy_signal_srcauf(symbol, timeframe, source_event_id)WHERE source_event_id IS NOT NULL → Idempotenz. - Migration:
migrations/001_strategy_signal.sql(idempotent, löscht nichts).
RabbitMQ (Modul-02)
| Exchange | Typ | Routing | Event |
|---|---|---|---|
market.regime |
topic | market.regime.ready (eingang) |
MARKET_REGIME_READY |
market.signals |
topic | strategy.signal.detected (ausgang) |
SIGNAL_DETECTED |
Interne API
| Endpoint | Zweck |
|---|---|
GET /health |
Liveness (200) + Komponentenstatus im Body |
GET /health/ready |
Readiness (503 wenn PG/RabbitMQ/Modul-03 down) |
End-to-End-Test (20.08.2026, final, nach Publisher-Fixes) ✅
Kette verifiziert: Modul-03 Ingest → market.data.ready → Modul-04 → market.regime.ready → Modul-05 Consumer → trend_pullback_v1 → strategy_signal → SIGNAL_DETECTED.
| Fall | Signal | Entry | Stop | Target | R:R | DB | Event |
|---|---|---|---|---|---|---|---|
| M5LONG (TREND_UP) | LONG | 164.20 | 163.4764 | 165.6473 | 2.00 | ✅ 1 | ✅ 1 |
| M5SHORT (TREND_DOWN) | SHORT | 135.80 | 136.4964 | 134.4073 | 2.00 | ✅ 1 | ✅ 1 |
| M5NOPULL (kein Pullback) | keins | — | — | — | — | ✅ 0 | ✅ 0 |
| M5RANGE (Range) | keins | — | — | — | — | ✅ 0 | ✅ 0 |
Mathematik verifiziert: LONG Target=Entry+2×(Entry-Stop) = 164.20 + 2×0.72364 = 165.6473 ✓; SHORT Target=Entry−2×(Stop−Entry) = 135.80 − 2×0.69636 = 134.4073 ✓.
Idempotenz: identisches source_event_id erneut → kein zweiter DB-Eintrag, kein Doppel-Event (count=1). ✅
RabbitMQ-Reconnect: kontrollierter Neustart → Modul-04+05 verbinden automatisch (Backoff 1s→2s→4s→8s), je exakt 1 Consumer, keine Zombies, keine verlorenen Events. ✅
Logs: keine ConnectionResetError, keine unbehandelten Tracebacks. Health {postgresql:true, rabbitmq:true, market_data_ready:true}. ✅
Bugs behoben während E2E (20.08.2026)
| Bug | Fix |
|---|---|
ConnectionResetError beim Publish |
Publisher: frische Verbindung je Publish + conn.close() (RabbitMQ schließt ungenutzte Verbindung) — gleicher Bug in Modul-03, -04, -05 |
Modul-03 OHLCV-Feld ts |
Normalisierung ts→timestamp im MarketData-Client |
Zombie-Consumer (strategy.input 2–3) |
queue_delete beim Consumer-Start; manuelles Cleanup via rabbitmqctl delete_queue |
| Keine Events nach Recreate | OHLCV/Regime-Daten waren noch in DB (Duplikat) → E2E bereinigt ohlcv+market_regime+strategy_signal |
| M5SHORT ging verloren | Zombie-Consumer verschluckte Event (Round-Robin) → beseitigt |
Offene Punkte
- Downstream-Consumer für
strategy.signal.detected(Modul-06+) - Weitere Strategien über Engine-Register hinzufügbar
Geändert von: Rain Ocampo
Datum: 20.08.2026
Grund: Modul-05-Dokumentation angelegt (Strategy-Engine trend_pullback_v1, strategy_signal-Schema, Events, E2E freigegeben).