trading-system-docs/modul-07-risk-manager.md

6.4 KiB
Raw Blame History

Modul-07: Risk-Manager

Status: Freigegeben (20.08.2026) Version: 0.1.0 Port (intern): 55007 Netzwerk: trading-modules-trading-modules


Zweck

Deterministischer Risk-Manager in der Trading-Kette. Konsumiert SIGNAL_RANKED (aus Modul-06), führt eine harte, deterministische Risikoprüfung durch, berechnet die Positionsgröße und publiziert RISK_APPROVED oder RISK_REJECTED.

Prinzipien:

  • KEINE KI/ML — rein deterministische Regeln.
  • KEINE Broker-Order — nur Risiko-Entscheidung + Positionsgröße.
  • FAIL-CLOSED — Fehler oder fehlende Daten führen niemals zu APPROVED, sondern immer zu REJECTED.

Architektur

SIGNAL_RANKED (market.rankings / signal.ranked)
      │
      ▼
┌─────────────────────────────────────────────┐
│ Modul-07 Risk-Manager                       │
│  Consumer (risk.input)                      │
│   → RiskEngine (Registry)                   │
│      → risk_v1 (deterministische Prüfung)   │
│   → Storage (risk_decision, idempotent)     │
│   → Publisher (market.risk)                 │
└─────────────────────────────────────────────┘
      │
      ├── RISK_APPROVED (risk.approved)
      └── RISK_REJECTED (risk.rejected)

Komponenten

  • app/consumer/consumer.py — RabbitMQ-Consumer SIGNAL_RANKED, manuelles ACK, Zombie-Schutz (queue_delete beim Start), Reconnect-Backoff 1s→2s→4s→8s.
  • app/risk/engine.py — Registry für Risiko-Verfahren (V1, zukünftig V2).
  • app/risk/risk_v1.py — deterministische Kernlogik + Positionsgrößen-Mathematik.
  • app/instruments/registry.py — instrument-agnostische Werte (Contract Size, Lot Size, Tick Size/Value, Min/Max Quantity, Quantity Step, Währung/FX).
  • app/storage/storage.py — idempotente Persistenz (unique auf source_ranking_id).
  • app/publisher/publisher.py — frische Verbindung je Publish + conn.close() im finally (RabbitMQ-Bug-Fix aus Modul-03/04/05/06).
  • app/api/main.py — FastAPI, Port 55007 intern, /health, /health/ready.

V1-Risikoregeln (zentral konfigurierbar in config.py, env-overridable)

Regel Default Beschreibung
account_equity 10000 Account-Equity
risk_percent 0.005 max. Risiko pro Trade (0,5 %)
min_score 70 Mindest-Ranking-Score
min_ranking_class eligible Mindest-Ranking-Klasse
max_positions 5 max. Anzahl Positionen (nur bei vorhandenen Daten)
max_open_risk 0.02 max. gesamtes offenes Risiko (nur bei vorhandenen Daten)
daily_loss_limit 0.03 tägliches Verlustlimit (nur bei vorhandenen Daten)
max_drawdown 0.20 maximales Drawdown-Limit (nur bei vorhandenen Daten)

Hinweis: Globale Portfolio-Limits (offene Positionen, Tagesverlust, Drawdown) werden nur geprüft, soweit Daten vorhanden sind. Die eigentliche Portfolio-Logik gehört zu Modul-08. Fehlende Portfolio-Daten sind kein REJECT-Grund.

Positionsgröße

risk_amount   = equity × risk_percent
risk_per_unit = abs(entry  stop)
raw_size      = risk_amount / risk_per_unit
quantity      = floor(raw_size / quantity_step) × quantity_step   # ABRUNDEN

Abrunden auf quantity_step (nie aufrunden), damit das erlaubte Risiko nie überschritten wird (FAIL-CLOSED-Sicherheit).


Output (RiskDecision)

Nachvollziehbar gespeichert in risk_decision: risk_decision_id, source_ranking_id, symbol, asset_class, direction, entry, stop_loss, target, equity, risk_percent, max_risk_amount, calculated_position_size, actual_risk_amount, decision (APPROVED/REJECTED), reason_codes, rule_version, timestamp.


RabbitMQ

Rolle Exchange Routing Key Queue
Consumer market.rankings signal.ranked risk.input
Publisher market.risk risk.approved
Publisher market.risk risk.rejected
  • Manuelles ACK erst nach erfolgreicher Verarbeitung.
  • Idempotenz: gleiches source_ranking_id → keine Doppelentscheidung/Event.
  • Reconnect ohne Zombies (Backoff 1s→2s→4s→8s, queue_delete beim Start).

Tests

Unit-Tests (test_risk.py) — 10/10 grün

  1. gültiger LONG → APPROVED + korrekte Größe
  2. gültiger SHORT → APPROVED
  3. Score unter Threshold → REJECTED
  4. falscher Stop → REJECTED
  5. fehlende/ungültige Daten → REJECTED
  6. Positionsgröße/Risiko mathematisch korrekt
  7. Quantity-Step/Min/Max korrekt
  8. Daily-Loss/Drawdown-Limit → REJECTED
  9. Idempotenz
  10. RabbitMQ-Reconnect

E2E (03→04→05→06→07) — ALLE CHECKS BESTANDEN

  • A: Kette 03→07, Decision in DB (LONG & SHORT) ✓
  • B: exakt 1 Risiko-Event je Symbol (APPROVED/REJECTED) ✓
  • C: Pflichtfelder in Decision vorhanden ✓
  • D: identisches Ranking → kein Doppel-Decision ✓
  • E: Score<70 / falscher Stop / fehlende Daten → REJECTED + source_ranking_id ✓

Mathematik-Verifikation (aus DB)

Symbol Direction Entry Stop equity risk% max_risk qty actual_risk
M5LONG LONG 164.2 163.47636 10000 0.005 50 69 49.93
M5SHORT SHORT 135.8 136.49636 10000 0.005 50 71 49.44

Beide actual_risk_amount ≤ max_risk_amount

Reconnect-Test

RabbitMQ kontrolliert neu gestartet → alle 4 Queues (market-regime.input, strategy.input, ranking.input, risk.input) mit exakt 1 Consumer, keine Zombies, keine verlorenen Events, risk_decision stabil.


Health/Readiness

  • /health{"status":"ok", "postgresql":true, "rabbitmq":true, "consumer_ready":true}
  • /health/ready → 200
  • Container: Modul-07-Risk-Manager (healthy)

Technischer offener Punkt: Modul-03 /history?limit=N

Modul-03 /history?limit=N liefert aktuell die N ältesten Candles (aufsteigend). Das ist langfristig zu korrigieren (sollte die N neuesten Candles liefern). Der Workaround in Modul-06 (Lookback 500) ist nicht als API-Vertrag zu behandeln — er ist ein temporärer Workaround, kein garantiertes Verhalten.


Deployment

  • Compose: /opt/trading-modules/docker-compose.yml (Block modul-07-risk-manager)
  • Nur expose: 55007 (kein öffentlicher Port)
  • restart: unless-stopped
  • Image: risk-manager:0.1.0