trading-system-docs/notes/trading/system-docs/modul-05-strategy-engine.md

8.3 KiB
Raw Blame History

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 55005NUR 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 Exchange market.regime, Routing market.regime.ready; durable Queue strategy.input; manuelles Ack erst nach erfolgreicher Verarbeitung; Reconnect mit Backoff + conn.close(); queue_delete beim 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 Feld tstimestamp.
  • 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 (verhindert ConnectionResetError durch 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):

  1. Close > steigender SMA200 (Trendfilter)
  2. Pullback: Close < EMA20 (Zug zurück in den Trend)
  3. Bestätigung: Close > prev Close ODER Break prev High
  4. Entry = Close; Stop unter Swing-Low; Target = 2R (R:R = 2.0)

SHORT-Bedingungen (Regime TREND_DOWN): spiegelbildlich

  1. Close < fallender SMA200
  2. Pullback: Close > EMA20
  3. Bestätigung: Close < prev Close ODER Break prev Low
  4. 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_src auf (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_v1strategy_signalSIGNAL_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=Entry2×(StopEntry) = 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 tstimestamp im MarketData-Client
Zombie-Consumer (strategy.input 23) 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).