trading-system-docs/notes/trading/system-docs/modul-09-execution-service.md

151 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
type: Modul
_organized: true
---
# Modul-09: Execution-Service
**Status: FREIGEGEBEN** (20.08.2026)
Deterministischer Execution-Service. Konsumiert `TRADE_APPROVED` aus Modul-08,
validiert die Order hart (FAIL-CLOSED), übergibt sie an einen Broker-Adapter
(V1: PaperBrokerAdapter) und überwacht den Status. Ergebnis wird idempotent
in eigenen DB-Tabellen gespeichert und als RabbitMQ-Events publiziert.
**Sicherheit: KEIN unkontrolliertes Live-Trading.**
- Default `EXECUTION_MODE=PAPER` (bzw. DRY_RUN)
- `TRADING_ENABLED=false` als sicherer Default für LIVE
- LIVE nur über explizite Konfiguration aktivierbar, FAIL-CLOSED
- Bei Timeout nach Order-Senden NIE blind erneut senden — erst über
`broker_order_id`/`client_order_id` bzw. Brokerstatus klären
## Architektur
```
TRADE_APPROVED (market.portfolio/trade.approved, Modul-08)
ExecutionConsumer (execution.input, Consumer-Fix von Anfang an)
Order-Validierung (hart, FAIL-CLOSED)
│ source_portfolio_decision_id gültig, decision=APPROVED,
│ symbol/direction/quantity vorhanden, quantity>0,
│ entry/stop/target plausibel, Kette nachvollziehbar,
│ kein bereits ausgeführter identischer Auftrag, Kill-Switch
BrokerAdapter-Interface (modular, austauschbar)
│ V1: PaperBrokerAdapter (realistischer Lifecycle)
ExecutionStorage (idempotent, Advisory Lock + Transaktion)
│ execution_order + execution_event
RabbitMQPublisher (market.execution)
ORDER_SUBMITTED / ORDER_FILLED / ORDER_REJECTED / ORDER_FAILED
```
## BrokerAdapter-Interface
- `BrokerAdapter` (app/broker/base.py): `name()`, `is_configured()`,
`submit_order()`, `get_order_status()`
- `PaperBrokerAdapter` (app/broker/paper.py): V1, simuliert realistischen
Lifecycle (SUBMITTED → FILLED, Partial Fill, Reject, Timeout) für Tests
- `BrokerRegistry` (app/broker/registry.py): wählt Adapter anhand
`DEFAULT_BROKER` (V1: paper). Echte Broker-Adapter später austauschbar,
keine Brokerlogik im Core-Code.
## Idempotenz
- Unique-Index `uq_execution_order_src` auf `source_portfolio_decision_id`
→ gleiches TRADE_APPROVED erzeugt NIE eine Doppelorder
- Unique-Index `uq_execution_order_client` auf `client_order_id`
- Client Order ID deterministisch aus Execution-ID abgeleitet
(`EXEC-<execution_id[:32]>`)
- Advisory Lock + Transaktion: parallele identische Events → exakt eine
Execution (Race-Condition-Schutz, wie Modul-08)
- ACK eines TRADE_APPROVED erst, wenn die Order dauerhaft in der DB gespeichert ist
## Order-Status
`PENDING → SUBMITTED → PARTIALLY_FILLED → FILLED`
sowie `REJECTED`, `CANCELLED`, `FAILED`.
## DB-Tabellen
- `execution_order`: execution_id, source_portfolio_decision_id, broker, mode,
symbol, asset_class, direction, quantity, requested_price, stop_loss, target,
broker_order_id, client_order_id, status, filled_quantity, avg_fill_price,
timestamps, error/reason_codes, execution_version
- `execution_event`: append-only Log der Status-Übergänge
## RabbitMQ
- Exchange `market.execution` (topic, durable)
- Routing: `order.submitted`, `order.filled`, `order.rejected`, `order.failed`
- Queue `execution.input` (durable), gebunden an `market.portfolio`/`trade.approved`
- Consumer-Fix aus Modul-0408 von Anfang an: dauerhafte Verbindung,
`process_data_events(time_limit=1.0)` in innerer Schleife, KEIN queue_delete
bei normalen Reconnects, Backoff 1s→2s→4s→8s→16s→30s
## Tests
- **21/21 Unit-Tests** (test_execution.py): gültige Order → PAPER FILLED,
Idempotenz, ungültige Quantity → REJECTED, Kill-Switch, fehlende
Broker-Credentials → FAIL-CLOSED, Broker-Reject, Timeout → keine blinde
Doppelorder, Partial Fill, Reconnect-Backoff, parallele identische Events
- **E2E 6/6 Checks** (Kette 03→04→05→06→07→08→09): Execution-Order in DB,
exakt 1 Paper-Order, Pflichtfelder, Idempotenz, ORDER_SUBMITTED+ORDER_FILLED,
gültiger Trade → PAPER FILLED
- **Reconnect-Test**: RabbitMQ gestoppt → Backoff → neu verbunden, 1 Consumer,
kein queue_delete bei normalen Reconnects
## Deploy
- Container `Modul-09-Execution-Service`, Port 55009 nur Docker-intern
(kein öffentlicher Host-Port)
- Health/Readiness 200, PG + RabbitMQ erreichbar, Consumer bereit
- Env: `EXECUTION_MODE=PAPER`, `TRADING_ENABLED=false`, `DEFAULT_BROKER=paper`
## Geändert
```
Geändert von: Rain Ocampo
Datum: 20.08.2026
Grund: Modul-09-Doku um final freigegebenen Control-Gate-Status (M15→M09, Variante C) ergänzt.
Kernregel, Einbau-Punkt, Audit-Felder, Parameter, Live-E2E-Ergebnisse dokumentiert.
```
## Control-Gate (M15→M09, FREIGEGEBEN 20.08.2026)
**Status: FREIGEGEBEN / PRODUKTIV VERIFIZIERT** — Variante C (Event-Cache-Vorfilter + synchroner HTTP-Final-Check).
M15 (`Modul-15-Monitoring-Control`) ist die **autoritative Safety-/Trading-Control-Instanz**.
M09 sendet neue Orders (OPEN/INCREASE) **nur**, wenn M15 eindeutig `ENABLED` + frisch bestätigt.
### Kernregel
| State | OPEN/INCREASE | REDUCE/CLOSE/CANCEL |
|-------|---------------|---------------------|
| `ENABLED` | **ERLAUBT** | erlaubt |
| `PAUSED` | **VERBOTEN** | erlaubt |
| `HALTED` | **VERBOTEN** | erlaubt |
| `UNKNOWN` / M15-down / stale | **VERBOTEN (FAIL-CLOSED)** | erlaubt |
### Einbau-Punkt
Control-Check zwischen Schritt 4 (Order atomar in DB anlegen) und Schritt 5 (`_submit_and_track`).
Bei OPEN/INCREASE ohne Erlaubnis → Order als `FAILED`/`REJECTED` speichern, **nicht** senden.
`TRADING_ENABLED`-Flag bleibt unverändert (blockiert nur LIVE, nicht PAPER).
### Audit-Felder je Order
`action_type`, `control_status`, `control_state_version`, `control_expires_at`,
`control_audit_id`, `control_check_source` (`http`|`cache`|`bypass`|`none`).
### Parameter
- Final-Check-HTTP-Timeout: **500 ms**, Retry: **max 1** sofort.
- `expires_at`-TTL (M15): **500 ms**; Cache-TTL (M09): **5 s**.
- Kein Auto-Resume; kein "Senden ohne Check".
### Live-E2E (VPS, 20.08.2026) — alle Szenarien grün
S1 ENABLED 9/9 · S2 HALTED 9/9 · S3 PAUSED 4/4 · S4 Risikoabbau 4/4 · S5 M15-down 7/7 ·
S6 Cache-vs-Final 2/2 · S7 Race/TTL 2/2 · S8 Event-Cache verifiziert · S9 Recovery 3/3 ·
S10 Health/Security verifiziert · S11 Regression bestanden.
Details: siehe `modul-15-m09-anbindung-implementierung.md` (FREIGEGEBEN).