trading-system-docs/notes/trading/system-docs/modul-15-m09-anbindung-implementierung.md

156 lines
6 KiB
Markdown

---
type: Trading-Modul-System
_organized: true
---
# Modul-15 → Modul-09 Control-Anbindung — Implementierung & Live-E2E
**Status: FREIGEGEBEN / PRODUKTIV VERIFIZIERT** (20.08.2026, Nutzer-Bestätigung).
**Datum:** 20.08.2026 · **Autor:** Rain Ocampo (Hermes)
**Gilt für:** Modul-09-Execution-Service ↔ Modul-15-Monitoring-Control
> **Freigabe-Hinweis:** Diese Doku dokumentiert den Implementierungs- und E2E-Stand.
> Die **finale Freigabe** (M15/M09 als FREIGEGEBEN markieren) erfolgte am **20.08.2026**
> durch Nutzer-Bestätigung. M16 wird **nicht** begonnen.
---
## 1. Umgesetzte Architektur (Variante C)
**Event-Cache als nicht-kritischer Vorfilter + synchroner HTTP-Final-Check als Autorität.**
- **M15** = autoritative Safety-/Trading-Control-Instanz.
- **M09** sendet neue Orders (OPEN/INCREASE) **nur**, wenn M15 eindeutig `ENABLED` + frisch bestätigt.
- **REDUCE/CLOSE/CANCEL** (Risikoabbau) sind **immer** erlaubt — unabhängig vom M15-Status.
### Kernregel
| State | OPEN/INCREASE | REDUCE/CLOSE/CANCEL |
|-------|---------------|---------------------|
| `ENABLED` | **ERLAUBT** | erlaubt |
| `PAUSED` | **VERBOTEN** | erlaubt |
| `HALTED` | **VERBOTEN** | erlaubt |
| `UNKNOWN` / M15-down / stale | **VERBOTEN (FAIL-CLOSED)** | erlaubt |
---
## 2. API-Vertrag M15 → M09 (additiv, kein Breaking Change)
### `GET /control/trading-state`
```json
{
"trading_status": "ENABLED", // ENABLED|PAUSED|HALTED|UNKNOWN
"system_status": "HEALTHY",
"state_version": 3, // monoton, je Status-Änderung inkrementiert
"timestamp": "2026-08-20T15:30:00Z",
"expires_at": "2026-08-20T15:30:00.5Z", // TTL-Horizont (now + 500ms)
"ttl_seconds": 0.5,
"audit_id": "ctl-..." // M15-interne Audit-Kennung
}
```
Nur `trading_status=ENABLED` **und** `expires_at` in der Zukunft = eindeutige Erlaubnis.
Timeout/5xx/503 → **FAIL-CLOSED** (als UNKNOWN behandeln).
### Event-Kanal (Event-Cache)
- Exchange `market.control`, Routing-Key `trading.state` (nur bei Status-Änderung).
- M09-Consumer `ControlCacheConsumer` speist den Event-Cache-Vorfilter.
- Cache-TTL: 5 s. Stale-Cache → als UNKNOWN behandeln → OPEN/INCREASE blockiert.
---
## 3. M09-Implementierung
### Neue Dateien
- `app/control/client.py`**ControlClient** (Event-Cache-Vorfilter + HTTP-Final-Check, FAIL-CLOSED)
- `app/control/__init__.py`
- `app/consumer/control_consumer.py`**ControlCacheConsumer** (`market.control`/`trading.state`)
### Geänderte Dateien
- `app/core/service.py` — Control-Check zwischen Schritt 4 (Order anlegen) und Schritt 5 (`_submit_and_track`); `_apply_control_audit`; `action_type`-Ableitung; Consumer-Start/Stop
- `app/core/models.py``ExecutionOrder` + Audit-Felder
- `app/config.py` — M15-URL/Timeout/TTL/Cache-TTL + Control-Exchange/Routing-Key
- `app/storage/storage.py` — INSERT/UPDATE um control_*-Felder; `update_control_audit`
- `migrations/001_execution.sql` — Audit-Spalten (idempotent via `ADD COLUMN IF NOT EXISTS`)
### 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".
---
## 4. Live-E2E-Ergebnisse (VPS, 20.08.2026)
Alle Szenarien auf dem VPS (187.124.31.123) live ausgeführt. **Alle grün.**
| # | Szenario | Ergebnis |
|---|----------|----------|
| S1 | **ENABLED-Pfad**: OPEN-Order durch, Auditfelder in DB | **9/9 PASS** |
| S2 | **HALTED-Pfad**: M03 gestoppt → OPEN blockiert, kein Broker-Send, reason `CONTROL_HALTED` | **9/9 PASS** |
| S3 | **PAUSED-Pfad**: nicht-krit. Service down → OPEN blockiert, reason `CONTROL_PAUSED` | **4/4 PASS** |
| S4 | **Risikoabbau bei HALTED**: REDUCE/CLOSE/CANCEL erlaubt (bypass) | **4/4 PASS** |
| S5 | **M15-down**: OPEN FAIL-CLOSED (`CONTROL_UNREACHABLE`), REDUCE/CLOSE/CANCEL erlaubt | **7/7 PASS** |
| S6 | **Cache-vs-Final-Check**: HTTP HALTED gewinnt über Cache | **2/2 PASS** |
| S7 | **Race/TTL**: Doppel-Publish → keine Doppelorder (Idempotenz) | **2/2 PASS** |
| S8 | **Event-Cache**: 1 Consumer, 0 Backlog, Reconnect nach RabbitMQ-Restart | **verifiziert** |
| S9 | **Recovery**: nach Behebung wieder ENABLED, OPEN erlaubt | **3/3 PASS** |
| S10 | **Health/Security**: health/ready 200, keine öffentl. Ports, keine Secrets, keine echten Tracebacks | **verifiziert** |
| S11 | **Regression**: bestehender M09-Paper-E2E (Kette 03→09) | **ALLE CHECKS BESTANDEN** |
### S1-Detail (ENABLED)
```
control_status=ENABLED
control_state_version=1
control_expires_at=1787241066.98
control_audit_id=ctl-2073194e651040db
control_check_source=http
action_type=OPEN
broker_order_id=PAPER-EXEC-... (Broker-Send erfolgt)
status=FILLED
```
### S2-Detail (HALTED)
```
control_status=HALTED
control_state_version=2
control_audit_id=ctl-b2cd6303f8754328
control_check_source=http
action_type=OPEN
broker_order_id=None (KEIN Broker-Send)
reason_codes=['CONTROL_HALTED']
status=FAILED
```
### S5-Detail (M15-down)
```
reason_codes=['CONTROL_UNREACHABLE']
broker_order_id=None (FAIL-CLOSED, kein Broker-Send)
REDUCE/CLOSE/CANCEL → control_check_source=bypass (erlaubt)
```
---
## 5. Verifikation Health/Security
- M09 + M15 `health` und `health/ready`**200**.
- Keine öffentlichen Ports (nur Docker-intern via `expose:`).
- Keine Secrets in Logs/API/DB.
- Tracebacks in Logs: ausschließlich pika `AMQPConnectionError` während des
kontrollierten RabbitMQ-Restarts (erwartete Reconnect-Logs, keine echten Fehler).
---
## 6. Freigabe-Status
- **FREIGEGEBEN / PRODUKTIV VERIFIZIERT** (20.08.2026, Nutzer-Bestätigung).
- M15/M09-Control-Anbindung ist **final freigegeben** — keine weiteren technischen Änderungen an M09/M15.
- M16: **nicht beginnen**.
- Credential-Cleanup: temporäre Forgejo-Tokens/Deploy-Keys → count=0 (siehe Git-Commit).
---
*Implementierung + Live-E2E abgeschlossen. Finale Freigabe erteilt (20.08.2026).*