Rain Ocampo: Forgejo-Content konsolidiert (trading-system-docs + second-brain) in Vault
This commit is contained in:
parent
15cb9bda1e
commit
20f3b09155
43 changed files with 3130 additions and 0 deletions
42
notes/trading/second-brain/Stop_Loss_Fix_2026-08-18.md
Normal file
42
notes/trading/second-brain/Stop_Loss_Fix_2026-08-18.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Echter Stop-Loss + Risiko-Sizing — Fix 18.08.2026
|
||||
|
||||
## Problem
|
||||
Der Stop-Loss war bisher **nur ein lokaler Wert** in `trades.json`. Der Monitor prüfte
|
||||
**stündlich** und verkaufte dann per Market-Order. Bei volatilen Pennystocks (MXL, ACRS,
|
||||
ULCC, XRX) rutschte der Kurs in der Zwischenzeit deutlich unter den SL → Verluste von
|
||||
−7 bis −10 % statt der konfigurierten −8 %. Realisierte Rendite der Periode: **−24,33 €**.
|
||||
|
||||
Zusätzlich war das Sizing fix auf 50 €/Position → bei weitem SL (8 %) ein Verlust von
|
||||
~6–7 € (≈12 % des Positionsbudgets), statt der 2 %-Regel.
|
||||
|
||||
## Fix A — Echter Stop-Loss bei T212
|
||||
- Neuer Endpunkt: `POST /api/v0/equity/orders/stop` (negative Menge = Sell-Stop)
|
||||
- `place_stop()` + `cancel_stop()` in `t212_resilience.py`
|
||||
- `ensure_stop()` in `t212_monitor.py`: platziert/aktualisiert den echten Stop bei
|
||||
Fill, Breakeven (+3 %), Trailing (+5 %) und bei jedem Monitor-Lauf
|
||||
- **Idempotent:** speichert `stop_order_id` + `stop_order_price` in `trades.json` →
|
||||
kein Cancel+Neu-Churn bei jedem Lauf
|
||||
- **Rate-Limit-Pacing:** 1 Stop-Request / 2s (T212-Limit)
|
||||
- **Konservativ bei 429:** leere Live-Stop-Liste → nichts unternehmen (kein Churn)
|
||||
|
||||
## Fix B — Risiko-basiertes Sizing
|
||||
- `calc_risk()` nutzt jetzt `risk_per_trade_pct (2 %)` × Gesamtkapital statt fix 50 €
|
||||
- Verlust pro Trade in € gedeckelt, egal wie weit der SL steht
|
||||
- "Trades atmen lassen" (weiter SL) → Position wird automatisch kleiner
|
||||
|
||||
## Verifiziert (live gegen T212-API, 18.08.2026)
|
||||
4 echte Stop-Orders aktiv, keine Duplikate, zweiter Monitor-Lauf = 0 neue Orders:
|
||||
|
||||
| Position | Stop | Order-ID |
|
||||
|----------|------|----------|
|
||||
| LFST | 11,95 € | 55909740706 |
|
||||
| TRGP | 271,78 € | 55909740714 |
|
||||
| WEAV | 6,72 € | 55909740725 |
|
||||
| UMAC | 28,55 € | 55909740772 |
|
||||
|
||||
## Backups
|
||||
- `t212_monitor.py.bak_*` (mehrere Stände)
|
||||
- `t212_resilience.py.bak_*`
|
||||
|
||||
## Status
|
||||
✅ Aktiv — Daemon liest Dateien bei jedem Lauf frisch, kein Neustart nötig.
|
||||
216
notes/trading/second-brain/journal/2026-07-20_T212_Recap.md
Normal file
216
notes/trading/second-brain/journal/2026-07-20_T212_Recap.md
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
date: 2026-07-20
|
||||
summary: T212 Tages-Recap
|
||||
---
|
||||
|
||||
# 📊 Tages-Recap — 20.07.2026
|
||||
|
||||
**📊 T212 Tages-Recap — 20.07.2026**
|
||||
Monday | 22:30 MEZ
|
||||
Letzter Scan: 21:00 | 0 Kandidaten
|
||||
|
||||
**⚙️ Risiko-Modell**
|
||||
2%-Risiko/Trade | 90%-Kapazität | Max 2/Sektor
|
||||
|
||||
**📊 Offene Positionen: 6**
|
||||
Gefüllt: 6 | Pending: 0
|
||||
Sektoren: Commercial Services(1), Non-Energy Minerals(1), Industrials(1), Electronic Technology(1), Energy Minerals(1), Consumer Durables(1)
|
||||
CCO | Entry 2.41€ | ~+0.0% (+0.00€)
|
||||
GGB | Entry 4.68€ | ~+0.0% (+0.00€)
|
||||
AAL | Entry 15.10€ | ~+0.0% (+0.00€)
|
||||
ASX | Entry 38.75€ | ~+0.0% (+0.00€)
|
||||
XOM | Entry 148.00€ | ~+0.0% (+0.00€)
|
||||
F | Entry 14.20€ | ~+0.0% (+0.00€)
|
||||
|
||||
**✅ Heute geschlossen: 103**
|
||||
CVE | +0.0% | Grund: Portfolio-Ghost
|
||||
OLPX | -0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
CVX | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
ABEV | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
TEVA | -0.5% | Grund: Auto-Management
|
||||
OXY | -1.7% | Grund: Auto-Management
|
||||
TSEM | +17.1% | Grund: Auto-Management
|
||||
OVV | -0.6% | Grund: Auto-Management
|
||||
FCX | +7.3% | Grund: Auto-Management
|
||||
GOOG | -2.0% | Grund: Auto-Management
|
||||
VG1 | -7.5% | Grund: Auto-Management
|
||||
VKp | -3.3% | Grund: Auto-Management
|
||||
GOOG | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
TWEKAa | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
BLDP | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
VACQ | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
GLW | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
VKp | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
VKp | -8.7% | Grund: SL-Hit (bereits von T212 geschlossen)
|
||||
APAM | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
NVDd | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
FFARMa | -0.4% | Grund: Auto-Management
|
||||
S92d | +0.0% | Grund: Auto-Management
|
||||
TWEKAa | +0.0% | Grund: Auto-Management (bereits ausgebucht)
|
||||
SGLd | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
VRT | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
OLPX | +0.0% | Grund: Duplicate cleanup
|
||||
APLD | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
WMB | +0.0% | Grund: Manuell auf T212 geschlossen (nicht mehr im Portfolio)
|
||||
CLSK | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
BE | +0.0% | Grund: Auto-Management (phantom - fillPrice fehlte)
|
||||
RWEd | +0.7% | Grund: Auto-Management
|
||||
IOVA | +7.9% | Grund: Auto-Management
|
||||
REPe | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
ALLO | -100.0% | Grund: Auto-Management
|
||||
FFARMa | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
GLW | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
MXL | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
FNB | -0.5% | Grund: Auto-Management
|
||||
KEY | +4.3% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
GLW | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
CSCO | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
MO | +4.9% | Grund: Auto-Management
|
||||
VPG | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
LRCX | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
IOVA | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
KLAC | -4.7% | Grund: Auto-Management
|
||||
KO | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
GOOG | -6.4% | Grund: Auto-Management
|
||||
LRCX | -100.0% | Grund: Auto-Management
|
||||
AMZN | -100.0% | Grund: Auto-Management
|
||||
ANET | +2.6% | Grund: Auto-Management
|
||||
GOOG | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
AFL | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
RY | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
SHW | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
MCD | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
MAIN | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
GOOD | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
CNQ | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
SLB | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
PG | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
IBM | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
AGNC | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
NUE | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
RGLD | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
ECL | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
MA | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
CSCO | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
BNS | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
BLK | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
MSFT | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
BMY | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
KMB | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
SYY | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
TROW | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
LOW | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
PNR | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
GD | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
O | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
DUK | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
EMR | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
ADP | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
GWW | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
PPG | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
LTC | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
ROP | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
ITW | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
JNJ | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
JPM | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
CVX | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
C | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
BMO | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
CF | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
CAH | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
PEP | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
TD | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
CB | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
AAPL | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
ADC | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
WMT | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
ABT | +0.0% | Grund: AutoInvest-Pie (nicht API-managed)
|
||||
MO | +0.0% | Grund: Portfolio-Sync (nicht mehr in T212)
|
||||
|
||||
**🗑️ Heute gecancelt: 16**
|
||||
TWEKAa | 24h nicht gefüllt — Cancel
|
||||
VRT | 24h nicht gefüllt — Cancel
|
||||
CCO | End-of-Day Cleanup
|
||||
OLPX | 24h nicht gefüllt — Cancel
|
||||
ABEV | End-of-Day Cleanup
|
||||
IOVA | 24h nicht gefüllt — Cancel
|
||||
ALLO | 24h nicht gefüllt — Cancel
|
||||
TSM | 69h nicht gefüllt — Cancel
|
||||
BMY | 69h nicht gefüllt — Cancel
|
||||
RTPY | Order existiert nicht mehr bei T212 (404)
|
||||
KEY | 61h nicht gefüllt — Cancel
|
||||
RTPY | End-of-Day Cleanup
|
||||
LION | 26h nicht gefüllt — Cancel
|
||||
BCRX | 24h nicht gefüllt — Cancel
|
||||
CX | 24h nicht gefüllt — Cancel
|
||||
BMY | 24h nicht gefüllt — Cancel
|
||||
|
||||
💰 **Investiert: +115.98€**
|
||||
Unreal. P&L: +0.00€
|
||||
|
||||
**🧠 Analyse:**
|
||||
✅ 7 Gewinn-Trades (⌀ 6.4%)
|
||||
🔴 15 Verlust-Trades (⌀ -22.4%)
|
||||
- OLPX_US_EQ: -0.0% (Portfolio-Sync (nicht mehr in T212))
|
||||
- TEVA_US_EQ: -0.5% (Auto-Management)
|
||||
- OXY_US_EQ: -1.7% (Auto-Management)
|
||||
- OVV_US_EQ: -0.6% (Auto-Management)
|
||||
- GOOG_US_EQ: -2.0% (Auto-Management)
|
||||
- VG1_US_EQ: -7.5% (Auto-Management)
|
||||
- VKp_EQ: -3.3% (Auto-Management)
|
||||
- VKp_EQ: -8.7% (SL-Hit (bereits von T212 geschlossen))
|
||||
- FFARMa_EQ: -0.4% (Auto-Management)
|
||||
- ALLO_US_EQ: -100.0% (Auto-Management)
|
||||
- FNB_US_EQ: -0.5% (Auto-Management)
|
||||
- KLAC_US_EQ: -4.7% (Auto-Management)
|
||||
- GOOG_US_EQ: -6.4% (Auto-Management)
|
||||
- LRCX_US_EQ: -100.0% (Auto-Management)
|
||||
- AMZN_US_EQ: -100.0% (Auto-Management)
|
||||
⏰ 16 Orders gecancelt (unfilled/warning)
|
||||
|
||||
**💡 Learning für morgen:**
|
||||
❌ OLPX: -0.0% wegen Portfolio-Sync (nicht mehr in T212)
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ TEVA: -0.5% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ OXY: -1.7% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
✅ TSEM: +17.1% (gut erkannt)
|
||||
→ SMA50-Kipp-Erkennung war richtig
|
||||
❌ OVV: -0.6% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
✅ FCX: +7.3% (gut erkannt)
|
||||
→ SMA50-Kipp-Erkennung war richtig
|
||||
❌ GOOG: -2.0% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ VG1: -7.5% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ VKp: -3.3% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ VKp: -8.7% wegen SL-Hit (bereits von T212 geschlossen)
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ FFARMa: -0.4% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
✅ RWEd: +0.7% (gut erkannt)
|
||||
→ SMA50-Kipp-Erkennung war richtig
|
||||
✅ IOVA: +7.9% (gut erkannt)
|
||||
→ SMA50-Kipp-Erkennung war richtig
|
||||
❌ ALLO: -100.0% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ FNB: -0.5% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
✅ MO: +4.9% (gut erkannt)
|
||||
→ SMA50-Kipp-Erkennung war richtig
|
||||
❌ KLAC: -4.7% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ GOOG: -6.4% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ LRCX: -100.0% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
❌ AMZN: -100.0% wegen Auto-Management
|
||||
→ Vorschlag: Beim nächsten Mal früher reagieren? Oder SL enger?
|
||||
✅ ANET: +2.6% (gut erkannt)
|
||||
→ SMA50-Kipp-Erkennung war richtig
|
||||
|
||||
---
|
||||
_Automatisch erstellt von Alice_
|
||||
9
notes/trading/second-brain/t212-logs/2026-07-21_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-07-21_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 21.07.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.31€
|
||||
- AAL_US_EQ | +0.0% | SL: 13.89€
|
||||
- XOM_US_EQ | +0.0% | SL: 136.16€
|
||||
- F_US_EQ | +0.0% | SL: 13.06€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
8
notes/trading/second-brain/t212-logs/2026-07-22_log.md
Normal file
8
notes/trading/second-brain/t212-logs/2026-07-22_log.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# T212 Logs — 22.07.2026 23:45 UTC
|
||||
## Offen (3) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.31€
|
||||
- F_US_EQ | +0.0% | SL: 13.06€
|
||||
- UNHd_EQ | +0.0% | SL: 352.73€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
6
notes/trading/second-brain/t212-logs/2026-07-23_log.md
Normal file
6
notes/trading/second-brain/t212-logs/2026-07-23_log.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# T212 Logs — 23.07.2026 23:45 UTC
|
||||
## Offen (1) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.31€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
6
notes/trading/second-brain/t212-logs/2026-07-24_log.md
Normal file
6
notes/trading/second-brain/t212-logs/2026-07-24_log.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# T212 Logs — 24.07.2026 23:45 UTC
|
||||
## Offen (1) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.31€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
8
notes/trading/second-brain/t212-logs/2026-07-27_log.md
Normal file
8
notes/trading/second-brain/t212-logs/2026-07-27_log.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# T212 Logs — 27.07.2026 23:45 UTC
|
||||
## Offen (3) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.31€
|
||||
- ACRS_US_EQ | +0.0% | SL: 5.04€
|
||||
- BRLI_US_EQ | +0.0% | SL: 9.92€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
7
notes/trading/second-brain/t212-logs/2026-07-28_log.md
Normal file
7
notes/trading/second-brain/t212-logs/2026-07-28_log.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# T212 Logs — 28.07.2026 23:45 UTC
|
||||
## Offen (2) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.31€
|
||||
- ACRS_US_EQ | +0.0% | SL: 5.04€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
7
notes/trading/second-brain/t212-logs/2026-07-29_log.md
Normal file
7
notes/trading/second-brain/t212-logs/2026-07-29_log.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# T212 Logs — 29.07.2026 23:45 UTC
|
||||
## Offen (2) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.31€
|
||||
- ACRS_US_EQ | +0.0% | SL: 5.04€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
8
notes/trading/second-brain/t212-logs/2026-07-30_log.md
Normal file
8
notes/trading/second-brain/t212-logs/2026-07-30_log.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# T212 Logs — 30.07.2026 23:45 UTC
|
||||
## Offen (3) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.86€
|
||||
- XRX_US_EQ | +0.0% | SL: 3.10€
|
||||
- FORM_US_EQ | +0.0% | SL: 95.31€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
7
notes/trading/second-brain/t212-logs/2026-07-31_log.md
Normal file
7
notes/trading/second-brain/t212-logs/2026-07-31_log.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# T212 Logs — 31.07.2026 23:45 UTC
|
||||
## Offen (2) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.86€
|
||||
- AMCX_US_EQ | +0.0% | SL: 10.34€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
10
notes/trading/second-brain/t212-logs/2026-08-03_log.md
Normal file
10
notes/trading/second-brain/t212-logs/2026-08-03_log.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# T212 Logs — 03.08.2026 23:45 UTC
|
||||
## Offen (5) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.86€
|
||||
- AXTI_US_EQ | +0.0% | SL: 55.60€
|
||||
- ULCC_US_EQ | +0.0% | SL: 6.96€
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
10
notes/trading/second-brain/t212-logs/2026-08-04_log.md
Normal file
10
notes/trading/second-brain/t212-logs/2026-08-04_log.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# T212 Logs — 04.08.2026 23:45 UTC
|
||||
## Offen (5) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.86€
|
||||
- AXTI_US_EQ | +0.0% | SL: 55.60€
|
||||
- ULCC_US_EQ | +0.0% | SL: 6.96€
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
10
notes/trading/second-brain/t212-logs/2026-08-05_log.md
Normal file
10
notes/trading/second-brain/t212-logs/2026-08-05_log.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# T212 Logs — 05.08.2026 23:45 UTC
|
||||
## Offen (5) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.86€
|
||||
- ULCC_US_EQ | +0.0% | SL: 6.96€
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
- AXTI_US_EQ | +0.0% | SL: 53.82€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
9
notes/trading/second-brain/t212-logs/2026-08-06_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-08-06_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 06.08.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.86€
|
||||
- ULCC_US_EQ | +0.0% | SL: 6.96€
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
9
notes/trading/second-brain/t212-logs/2026-08-07_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-08-07_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 07.08.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.86€
|
||||
- ULCC_US_EQ | +0.0% | SL: 6.96€
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
10
notes/trading/second-brain/t212-logs/2026-08-10_log.md
Normal file
10
notes/trading/second-brain/t212-logs/2026-08-10_log.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# T212 Logs — 10.08.2026 23:45 UTC
|
||||
## Offen (5) | Unrealisiert: +0.0%
|
||||
- GGB_US_EQ | +0.0% | SL: 4.86€
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
- NESR_US_EQ | +0.0% | SL: 32.84€
|
||||
- LFST_US_EQ | +0.0% | SL: 10.99€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
9
notes/trading/second-brain/t212-logs/2026-08-11_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-08-11_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 11.08.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
- NESR_US_EQ | +0.0% | SL: 32.84€
|
||||
- LFST_US_EQ | +0.0% | SL: 10.99€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
9
notes/trading/second-brain/t212-logs/2026-08-12_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-08-12_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 12.08.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
- NESR_US_EQ | +0.0% | SL: 32.84€
|
||||
- LFST_US_EQ | +0.0% | SL: 10.99€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
9
notes/trading/second-brain/t212-logs/2026-08-13_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-08-13_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 13.08.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
- NESR_US_EQ | +0.0% | SL: 32.84€
|
||||
- LFST_US_EQ | +0.0% | SL: 10.99€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
9
notes/trading/second-brain/t212-logs/2026-08-14_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-08-14_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 14.08.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- XRX_US_EQ | +0.0% | SL: 2.98€
|
||||
- ATKR_US_EQ | +0.0% | SL: 85.99€
|
||||
- NESR_US_EQ | +0.0% | SL: 32.84€
|
||||
- LFST_US_EQ | +0.0% | SL: 10.99€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
9
notes/trading/second-brain/t212-logs/2026-08-17_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-08-17_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 17.08.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- NESR_US_EQ | +0.0% | SL: 35.69€
|
||||
- LFST_US_EQ | +0.0% | SL: 11.95€
|
||||
- AXTI_US_EQ | +0.0% | SL: 90.35€
|
||||
- SMTC_US_EQ | +0.0% | SL: 142.33€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
9
notes/trading/second-brain/t212-logs/2026-08-18_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-08-18_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 18.08.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- LFST_US_EQ | +0.0% | SL: 11.95€
|
||||
- TRGP_US_EQ | +0.0% | SL: 271.78€
|
||||
- WEAV_US_EQ | +0.0% | SL: 6.72€
|
||||
- UMAC_US_EQ | +0.0% | SL: 28.55€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
9
notes/trading/second-brain/t212-logs/2026-08-19_log.md
Normal file
9
notes/trading/second-brain/t212-logs/2026-08-19_log.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# T212 Logs — 19.08.2026 23:45 UTC
|
||||
## Offen (4) | Unrealisiert: +0.0%
|
||||
- LFST_US_EQ | +0.0% | SL: 11.95€
|
||||
- TRGP_US_EQ | +0.0% | SL: 271.78€
|
||||
- WEAV_US_EQ | +0.0% | SL: 6.72€
|
||||
- UMAC_US_EQ | +0.0% | SL: 28.55€
|
||||
|
||||
---
|
||||
*Auto-Log*
|
||||
97
notes/trading/system-docs/README.md
Normal file
97
notes/trading/system-docs/README.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# Trading-System — Modulare Architektur
|
||||
|
||||
Zentrales, modulares automatisiertes Trading-System, betrieben auf einem Hostinger-VPS (`187.124.31.123`).
|
||||
|
||||
**Repository-Inhalt:** Live-Dokumentation der Infrastruktur, der Module und des aktuellen Betriebszustands.
|
||||
Jede Änderung folgt dem **Notation-Format**: Autor (`Alice` / `Rain Ocampo`) + Zeitstempel (`DD.MM.YYYY HH:MM`) + Grund.
|
||||
|
||||
---
|
||||
|
||||
## Architektur-Überblick
|
||||
|
||||
- **17 Module** (`Modul-01-PostgreSQL` … `Modul-17-Hermes-Agent`), orchestriert über `docker-compose.yml` unter `/opt/trading-modules/`.
|
||||
- **Netzwerk:** `trading-modules` (bridge). Kommunikation ausschließlich über **Docker-interne Service-Hostnamen** (keine festen IPs).
|
||||
- **Feste Host-Ports:** Modul-01=`55432`, Modul-02=`55672`+`15672`, Modul-03–17=`55003`–`55017`.
|
||||
- **Zugangsdaten:** nur über Environment Variables / Docker-Secrets (Defaults in Compose mit `${VAR:-default}`).
|
||||
- **Kernprinzip:** bestehende Container/Volumes/Daten nie löschen, keine unnötigen öffentlichen Ports öffnen, Ist-Zustand vor Änderung prüfen.
|
||||
|
||||
### Multi-Agent-Setup (Resident-Evil-Theme)
|
||||
| Agent | Rolle | Port |
|
||||
|-------|-------|------|
|
||||
| **Alice** (OpenClaw) | Primary | 55163 |
|
||||
| **Matt Addison** (OpenClaw) | Backup | 54524 |
|
||||
| **Rain Ocampo** (Hermes) | Technischer Support | 32776 (UI) |
|
||||
|
||||
---
|
||||
|
||||
## Module
|
||||
|
||||
| Modul | Name | Status | Port |
|
||||
|-------|------|--------|------|
|
||||
| 01 | PostgreSQL | ✅ healthy (postgres:16-alpine) | 55432 |
|
||||
| 02 | RabbitMQ | ✅ healthy (rabbitmq:3-management) | 55672 / 15672 |
|
||||
| 03 | Market-Data | ✅ healthy (market-data:0.1.0) | 55003 (intern) |
|
||||
| 04 | Market-Regime | ✅ healthy (market-regime:1.0.0) | 55004 (intern) |
|
||||
| 05 | Strategy-Engine | ✅ healthy (strategy-engine:0.1.0) | 55005 (intern) |
|
||||
| 06 | Signal-Ranking | ✅ FREIGEGEBEN (signal-ranking:0.1.0) | 55006 (intern) |
|
||||
| 07 | Risk-Manager | ✅ FREIGEGEBEN (risk-manager:0.1.0) | 55007 (intern) |
|
||||
| 08 | Portfolio-Manager | ✅ FREIGEGEBEN (portfolio-manager:0.1.0) | 55008 (intern) |
|
||||
| 09 | Execution-Service | ✅ FREIGEGEBEN (execution-service:0.1.0) | 55009 (intern) |
|
||||
| 10 | Trade-Journal | ✅ FREIGEGEBEN (trade-journal:0.1.0) | 55010 (intern) |
|
||||
| 11 | Analytics | ✅ FREIGEGEBEN (analytics-service:0.1.0) | 55011 (intern) |
|
||||
| 12 | Backtesting | ✅ FREIGEGEBEN (backtesting:0.1.2) | 55012 (intern) |
|
||||
| 13 | Optimization | ✅ FREIGEGEBEN (optimization:0.1.0) | 55013 (intern) |
|
||||
| 14 | Notification | ✅ FREIGEGEBEN (notification:0.1.0) | 55014 (intern) |
|
||||
| 15 | Monitoring-Control | ✅ FREIGEGEBEN (monitoring-control:0.1.0) | 55015 (intern) |
|
||||
| 16–17 | … | ⬜ Platzhalter (alpine) | 55016–55017 |
|
||||
|
||||
Details: siehe `modul-03-market-data.md` … `modul-15-monitoring-control.md` (Module 03–15
|
||||
vollständig implementiert und freigegeben). Modul-16 ff. folgen.
|
||||
|
||||
---
|
||||
|
||||
## Notation / Audit-Trail
|
||||
|
||||
Jede Änderung wird mit folgender Zeile dokumentiert:
|
||||
|
||||
```
|
||||
Geändert von: [Alice | Rain Ocampo]
|
||||
Datum: DD.MM.YYYY HH:MM
|
||||
Grund: <Kurzbeschreibung>
|
||||
```
|
||||
|
||||
- **Alice** = Änderungen durch OpenClaw
|
||||
- **Rain Ocampo** = Änderungen durch Hermes
|
||||
|
||||
---
|
||||
|
||||
## Geändert
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Initiale Repository-Struktur und Modul-03-Dokumentation angelegt.
|
||||
```
|
||||
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: README aktualisiert — Modul-04 und Modul-05 als healthy/freigegeben eingetragen (Ports intern), Details-Link ergänzt.
|
||||
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: README-Modultabelle aktualisiert — Module 06–12 als freigegeben eingetragen; Modul-12-Backtesting als FREIGEGEBEN markiert.
|
||||
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: README-Modultabelle aktualisiert — Modul-12 auf backtesting:0.1.2 (V1.1), Modul-13 Optimization als E2E-VERIFIZIERT eingetragen.
|
||||
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: README-Modultabelle aktualisiert — Modul-13 Optimization als FREIGEGEBEN markiert (Freigabe durch Nutzer 20.08.2026).
|
||||
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: README-Modultabelle aktualisiert — Modul-14 Notification als FREIGEGEBEN eingetragen; Details-Link auf modul-14-notification.md erweitert.
|
||||
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: README-Modultabelle aktualisiert — Modul-15 Monitoring-Control als FREIGEGEBEN eingetragen; Details-Link auf modul-15-monitoring-control.md erweitert.
|
||||
51
notes/trading/system-docs/infrastructure-handbook.md
Normal file
51
notes/trading/system-docs/infrastructure-handbook.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Infrastruktur & Betriebs-Handbuch — Trading-System VPS
|
||||
|
||||
> Stand: 20.08.2026 · VPS: `187.124.31.123`
|
||||
|
||||
## Multi-Agent-Architektur (Resident-Evil-Theme)
|
||||
| Agent | Plattform | Rolle | Port |
|
||||
|-------|-----------|-------|------|
|
||||
| **Alice** | OpenClaw (primary) | Hauptagent, Protagonistin | 55163 |
|
||||
| **Matt Addison** | OpenClaw (backup) | Backup/Support | 54524 |
|
||||
| **Rain Ocampo** | Hermes (support) | Technischer Support | 32776 (UI) |
|
||||
|
||||
Notation in Notion/Forgejo bei JEDER Änderung:
|
||||
```
|
||||
Geändert von: [Alice | Rain Ocampo]
|
||||
Datum: DD.MM.YYYY HH:MM
|
||||
Grund: …
|
||||
```
|
||||
|
||||
## Container-Verwaltung (via SSH)
|
||||
```bash
|
||||
ssh root@187.124.31.123
|
||||
docker compose -f /opt/trading-modules/docker-compose.yml ps
|
||||
docker compose -f /opt/trading-modules/docker-compose.yml up -d <service>
|
||||
docker compose -f /opt/trading-modules/docker-compose.yml build <service>
|
||||
docker compose -f /opt/trading-modules/docker-compose.yml down # nur bei explizitem Wunsch
|
||||
```
|
||||
|
||||
## Trading-Module (17 Container)
|
||||
- Compose: `/opt/trading-modules/docker-compose.yml`
|
||||
- Container-Namen exakt: `Modul-01-PostgreSQL` … `Modul-17-Hermes-Agent`
|
||||
- Netzwerk: `trading-modules`
|
||||
- Modul-01 = PostgreSQL 16-alpine (Host 55432)
|
||||
- Modul-02 = RabbitMQ 3-management (Host 55672/15672, vhost `trading`)
|
||||
- Modul-03 = Market-Data (FastAPI, Port 55003 intern, **nicht öffentlich**)
|
||||
- Modul-04–17 = alpine-Platzhalter (bis ausgebaut)
|
||||
|
||||
## Wichtige Hinweise
|
||||
- **Coolify überschreibt Container-Configs bei Neustart.** Config-Änderungen an OpenClaw ausschließlich über Config-Dateien (nicht UI).
|
||||
- **Hot-Reload** OpenClaw: `kill -HUP 1` im Container.
|
||||
- **Ollama** braucht `OLLAMA_API_KEY`; interner Hostname `ollama-nb6d-ollama-1:11434`.
|
||||
- VPS-Authentifizierung: **nur Public-Key-Auth**.
|
||||
|
||||
## Deleted Resources (VPS-Aufräumung 20.08.2026)
|
||||
Folgendes wurde entfernt (~25 GB freigegeben): Chronos/Kronos (kronos-api, chronos-service, chronos-tradelog), trading-bridge, llm-trade-manager, deepseek-bot, MetaTrader-5, t212-pilot, mehrere redundante Hermes-Container/Volumes/Netzwerke. **Bleibt:** n8n, forgejo, paperclip, Tolaria, PostgreSQL, RabbitMQ, Ollama, Traefik, Coolify-Suite.
|
||||
|
||||
---
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Infrastruktur-Handbuch für das Trading-System dokumentiert.
|
||||
```
|
||||
116
notes/trading/system-docs/modul-03-market-data.md
Normal file
116
notes/trading/system-docs/modul-03-market-data.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# Modul-03-Market-Data — Betriebsdokumentation
|
||||
|
||||
> Erstellt: 20.08.2026 (Rain Ocampo) · Status: ✅ In Betrieb (healthy)
|
||||
|
||||
## Zweck
|
||||
Zentrale Marktdatenquelle des Trading-Systems. Pipeline:
|
||||
`Marktdaten → Validierung → Normalisierung → PostgreSQL → RabbitMQ-Event`
|
||||
Keine Strategie, keine Orders, keine KI — nur zuverlässige Marktdaten.
|
||||
|
||||
## Container
|
||||
| Attribut | Wert |
|
||||
|----------|------|
|
||||
| Name | `Modul-03-Market-Data` |
|
||||
| Image | `market-data:0.1.0` (lokal gebaut) |
|
||||
| Port | **55003** — **NUR intern** (`expose`, nicht öffentlich) |
|
||||
| Netzwerk | `trading-modules` (bridge) |
|
||||
| Build-Context | `/opt/trading-modules/modul03-market-data/` |
|
||||
| Restart | `unless-stopped` |
|
||||
| Healthcheck | ✅ `healthy` |
|
||||
|
||||
## Deployment
|
||||
```bash
|
||||
cd /opt/trading-modules
|
||||
docker compose build modul-03-market-data
|
||||
docker compose up -d modul-03-market-data
|
||||
```
|
||||
|
||||
## Environment Variables (Compose)
|
||||
| Variable | Wert (Default) | Zweck |
|
||||
|----------|---------------|-------|
|
||||
| `PG_HOST` | `Modul-01-PostgreSQL` | Docker-interner Servicename |
|
||||
| `PG_PORT` | `5432` | intern (Host: 55432) |
|
||||
| `PG_USER/PASSWORD/DB` | `trading` | aus `.env`-Defaults |
|
||||
| `RABBITMQ_HOST` | `Modul-02-RabbitMQ` | Docker-interner Servicename |
|
||||
| `RABBITMQ_PORT` | `5672` | intern (Host: 55672) |
|
||||
| `RABBITMQ_USER/PASSWORD/VHOST` | `trading` | **vhost `trading`** |
|
||||
| `DATA_PROVIDER` | `noop` | `noop` / `demo` / später Broker |
|
||||
| `LOG_LEVEL` | `INFO` | Strukturiertes Logging |
|
||||
|
||||
Keine festen IPs — nur Docker-interne Hostnamen. Creds via Compose env + `${VAR:-default}`.
|
||||
|
||||
## Datenbank (Modul-01-PostgreSQL)
|
||||
Tabelle `public.ohlcv`:
|
||||
```sql
|
||||
symbol TEXT, asset_class TEXT, provider TEXT, timeframe TEXT,
|
||||
ts TIMESTAMPTZ, open/high/low/close DOUBLE PRECISION, volume DOUBLE PRECISION,
|
||||
created_at TIMESTAMPTZ DEFAULT now(), id BIGSERIAL PRIMARY KEY
|
||||
```
|
||||
|
||||
### Finale Constraints & Indizes (Stand 20.08.2026)
|
||||
| Index | Typ |
|
||||
|-------|-----|
|
||||
| `uq_ohlcv_provider_symbol_tf_ts` | **UNIQUE** `(provider, symbol, timeframe, ts)` |
|
||||
| `ohlcv_pkey` | UNIQUE `(id)` |
|
||||
| `idx_ohlcv_symbol_tf` | `(symbol, timeframe)` |
|
||||
| `idx_ohlcv_symbol_tf_ts` | `(symbol, timeframe, ts DESC)` |
|
||||
| `idx_ohlcv_provider_symbol_tf_ts` | `(provider, symbol, timeframe, ts DESC)` |
|
||||
|
||||
Der Unique-Index inkludiert den **Provider** — langfristig werden mehrere Provider/Broker unterstützt
|
||||
(gleiche Symbol+Timeframe+Timestamp können von verschiedenen Quellen kommen).
|
||||
|
||||
**Migrationen:** `/app/migrations/001_ohlcv.sql` + `002_unique_provider.sql`
|
||||
(idempotent, löschen nichts; automatisch via `ensure_schema()`/glob angewendet).
|
||||
|
||||
## RabbitMQ (Modul-02)
|
||||
- **Exchange:** `market.data` (topic, durable)
|
||||
- **vhost:** `trading` (wichtig!)
|
||||
- **Routing-Keys:**
|
||||
|
||||
| Routing-Key | Event-Typ | Wann |
|
||||
|-------------|-----------|------|
|
||||
| `market.data.ready` | `MARKET_DATA_READY` | **Batch/Import** — EIN Event pro Batch, `candle_count` + `batch:true` |
|
||||
| `market.data.candle.closed` | `MARKET_CANDLE_CLOSED` | **Live** — pro abgeschlossener Kerze EIN Event, OHLCV im payload |
|
||||
|
||||
- Eventschema v1.0: `event_id, event_type, event_version, timestamp, symbol, asset_class, timeframe, provider` + payload.
|
||||
|
||||
## Interne API
|
||||
| Endpoint | Zweck |
|
||||
|----------|-------|
|
||||
| `GET /health` | Liveness (200 immer) + Komponentenstatus im Body |
|
||||
| `GET /health/ready` | Readiness (503 wenn PG/RabbitMQ down) |
|
||||
| `POST /ingest` | Kerzen einspeisen (JSON: `{"candles":[...]}`) |
|
||||
| `GET /prices/{symbol}` | Letzte Kurse |
|
||||
| `GET /history/{symbol}?timeframe=` | Historische OHLCV |
|
||||
|
||||
## Provider-Adapter
|
||||
`app/providers/providers.py`:
|
||||
- `DataProvider` (ABC) — abstrakte Schnittstelle `fetch_ohlcv()`, `health()`
|
||||
- `NoopProvider` — keine Datenquelle konfiguriert (Default)
|
||||
- `DemoProvider` — synthetische OHLCV-Daten für Tests
|
||||
- Neue Broker = neue Klasse, umschalten via `DATA_PROVIDER` env → keine harte Anbieter-Kopplung
|
||||
|
||||
## Validierung (`app/validation/validator.py`)
|
||||
- Timestamp gültig (UTC, nicht Zukunft, nicht zu alt/stale)
|
||||
- OHLC-Werte > 0
|
||||
- High ≥ Low, High ≥ Open/Close, Low ≤ Open/Close
|
||||
- Duplikat-Erkennung (Storage + DB-Unique-Index)
|
||||
|
||||
## End-to-End-Test (20.08.2026, nach Provider-Constraint-Upgrade) ✅
|
||||
- **Batch-Pfad:** 4 GOOG-Kerzen → saved:4, **GENAU EIN** `MARKET_DATA_READY` (routing `market.data.ready`, candle_count:4). 3 NVDA → saved:3, EIN Event.
|
||||
- **Live-Pfad:** 1 AMZN-Candle → `MARKET_CANDLE_CLOSED` (routing `market.data.candle.closed`, OHLCV im payload).
|
||||
- **Provider-Duplikat:** identische NVDA-Kerze erneut → `duplicate`, kein Insert, kein Event.
|
||||
- **Port-Sicherheit:** 55003 von außen (`http://187.124.31.123:55003/health`) → **nicht erreichbar** ✅
|
||||
- Datenbestand final: AAPL 5, GOOG 4, NVDA 3, EURUSD 3, TSLA 4, MSFT 1, AMZN 1.
|
||||
|
||||
## Offene Punkte
|
||||
- Echter Broker-/Datenprovider-Adapter (Interface bereit, noop/demo Defaults)
|
||||
- Zusätzliche Daten (Bid/Ask/Spread, Ticks, Fundamentaldaten) — vorbereitet
|
||||
- Consumer für `market.data.ready` / `market.data.candle.closed` (Modul-04 ff.)
|
||||
|
||||
---
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-03-Dokumentation angelegt (Provider-Constraint, Event-Trennung, Port-Nicht-Exposition).
|
||||
```
|
||||
152
notes/trading/system-docs/modul-04-market-regime.md
Normal file
152
notes/trading/system-docs/modul-04-market-regime.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# Modul-04-Market-Regime — Betriebsdokumentation
|
||||
|
||||
> Erstellt: 20.08.2026 (Rain Ocampo) · Status: ✅ Freigegeben (E2E bestanden)
|
||||
|
||||
## Zweck
|
||||
Erster **Consumer** der Market-Data-Events von Modul-03. Pipeline:
|
||||
`MARKET_DATA_READY / MARKET_CANDLE_CLOSED (Modul-03) → Regime-Berechnung → PostgreSQL (market_regime) → MARKET_REGIME_READY`
|
||||
**Deterministische, regelbasierte Engine — bewusst OHNE KI/ML.**
|
||||
|
||||
## Container
|
||||
| Attribut | Wert |
|
||||
|----------|------|
|
||||
| Name | `Modul-04-Market-Regime` |
|
||||
| Image | `market-regime:1.0.0` (lokal gebaut) |
|
||||
| Port | **55004** — **NUR intern** (`expose`, nicht öffentlich) |
|
||||
| Netzwerk | `trading-modules` (bridge) |
|
||||
| Build-Context | `/opt/trading-modules/modul04-market-regime/` |
|
||||
| Restart | `unless-stopped` |
|
||||
| Healthcheck | ✅ `healthy` |
|
||||
|
||||
## Deployment
|
||||
```bash
|
||||
cd /opt/trading-modules
|
||||
docker compose build modul-04-market-regime
|
||||
docker compose up -d --no-deps --force-recreate modul-04-market-regime
|
||||
```
|
||||
|
||||
## Environment Variables (Compose)
|
||||
| Variable | Wert (Default) | Zweck |
|
||||
|----------|---------------|-------|
|
||||
| `PG_HOST` | `Modul-01-PostgreSQL` | Docker-interner Servicename |
|
||||
| `PG_PORT` | `5432` | intern (Host: 55432) |
|
||||
| `PG_USER/PASSWORD/DB` | `trading` | aus `.env`-Defaults |
|
||||
| `RABBITMQ_HOST` | `Modul-02-RabbitMQ` | Docker-interner Servicename |
|
||||
| `RABBITMQ_PORT` | `5672` | intern (Host: 55672) |
|
||||
| `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-03 ──market.data.ready / market.data.candle.closed──▶ RegimeConsumer
|
||||
│ (bindet beide Routing-Keys)
|
||||
▼
|
||||
RegimeEngine (deterministisch)
|
||||
EMA / ADX / ATR / Slope / Preisstruktur
|
||||
│
|
||||
┌───────────┴───────────┐
|
||||
▼ ▼
|
||||
market_regime (PG) MARKET_REGIME_READY
|
||||
(16 Spalten) → market.regime.ready
|
||||
```
|
||||
|
||||
- **Consumer** (`app/consumer/consumer.py`): bindet `market.data.ready` + `market.data.candle.closed`; durable Queue `market-regime.input`; manuelles Ack; Reconnect mit Backoff; **schließt alte Verbindung beim Reconnect** (verhindert Consumer-Leak/Nachrichtenverlust).
|
||||
- **Engine** (`app/regime/engine.py`): deterministisch, ohne KI.
|
||||
- **Storage** (`app/storage/storage.py`): idempotent via partiellen Unique-Index.
|
||||
- **Publisher** (`app/publisher/publisher.py`): publiziert `MARKET_REGIME_READY` auf `market.regime` (Routing `market.regime.ready`).
|
||||
- **History-Client** (`app/marketdata/client.py`): liest OHLCV über interne FastAPI Modul-03 (`http://Modul-03-Market-Data:55003/history/{symbol}`).
|
||||
|
||||
## Regime-Engine (`app/regime/engine.py`)
|
||||
Deterministische Regel-Engine (Version `1.0.0`), 7 Regime:
|
||||
`TREND_UP, TREND_DOWN, RANGE, HIGH_VOLATILITY, LOW_VOLATILITY, TRANSITION, UNKNOWN`
|
||||
|
||||
**Indikatoren & Metriken:**
|
||||
| Indikator | Fenster/Param | Zweck |
|
||||
|-----------|---------------|-------|
|
||||
| EMA fast/slow | 10 / 30 | Trendrichtung (EMA-Flanken-Differenz) |
|
||||
| ADX | 14 | Trendstärke (≥20 = echter Trend) |
|
||||
| ATR | 14 | Volatilität (absolut + Ratio + Perzentil) |
|
||||
| Slope | 20 | normierte Steigung der Close-Linie |
|
||||
| Preisstruktur | — | higher_highs / lower_lows / range |
|
||||
|
||||
**Zentrale Schwellenwerte** (`app/config.py`, env-overridable):
|
||||
| Parameter | Default | Bedeutung |
|
||||
|-----------|---------|-----------|
|
||||
| `min_candles_required` | 30 | UNKNOWN, wenn weniger Daten |
|
||||
| `regime_lookback` | 60 | max. Kerzen für Berechnung |
|
||||
| `trend_min_ema_gap` | 0.02 | |EMA_fast-EMA_slow|/close ≥ → Trend |
|
||||
| `adx_trend_threshold` | 20.0 | ADX ≥ → echter Trend |
|
||||
| `slope_up/down_threshold` | 0.05 / -0.05 | normierte Steigung |
|
||||
| `range_atr_ratio` | 0.02 | ATR/close darunter = Range |
|
||||
| `atr_high_vol_multiplier` | 1.5 | ATR jetzt > hist_mean × → HIGH_VOL |
|
||||
| `atr_low_vol_multiplier` | 0.6 | ATR jetzt < hist_mean × → LOW_VOL |
|
||||
| `high_vol_atr_ratio` | 0.03 | ATR/close ≥ → starke Vol |
|
||||
| `low_vol_atr_ratio` | 0.008 | ATR/close ≤ → geringe Vol |
|
||||
| `slope_threshold` | 0.01 | |Slope| darunter = seitwärts |
|
||||
| `transition_min_events` | 3 | Events für TRANSITION |
|
||||
|
||||
## Datenbank (Modul-01-PostgreSQL)
|
||||
Tabelle `public.market_regime` (16 Spalten, eigene Tabelle — bestehende unangetastet):
|
||||
```sql
|
||||
symbol TEXT, asset_class TEXT, timeframe TEXT, provider TEXT,
|
||||
regime TEXT, confidence INTEGER (0-100),
|
||||
trend_strength DOUBLE PRECISION, volatility_state TEXT,
|
||||
timestamp TIMESTAMPTZ, indicators_json JSONB,
|
||||
candles_used INTEGER, version TEXT,
|
||||
source_event_id TEXT, correlation_id TEXT,
|
||||
data_ts TIMESTAMPTZ, data_ts_end TIMESTAMPTZ
|
||||
```
|
||||
- **Unique (partiell):** `uq_market_regime_src` auf `(symbol, timeframe, source_event_id)` **WHERE source_event_id IS NOT NULL** → Idempotenz.
|
||||
- Migration: `migrations/001_market_regime.sql` (idempotent, löscht nichts).
|
||||
|
||||
## RabbitMQ (Modul-02)
|
||||
| Exchange | Typ | Routing | Event |
|
||||
|----------|-----|---------|-------|
|
||||
| `market.data` | topic | `market.data.ready` (eingang) | MARKET_DATA_READY |
|
||||
| `market.data` | topic | `market.data.candle.closed` (eingang) | MARKET_CANDLE_CLOSED |
|
||||
| `market.regime` | topic | `market.regime.ready` (**ausgang**) | MARKET_REGIME_READY |
|
||||
|
||||
## Interne API
|
||||
| Endpoint | Zweck |
|
||||
|----------|-------|
|
||||
| `GET /health` | Liveness (200 immer) + Komponentenstatus im Body |
|
||||
| `GET /health/ready` | Readiness (503 wenn PG/RabbitMQ/Modul-03 down) |
|
||||
| `GET /regime/{symbol}` | Regime-Einträge abfragen |
|
||||
| `GET /regime/latest/{symbol}` | Letztes Regime eines Symbols |
|
||||
|
||||
## End-to-End-Test (20.08.2026, final, nach Rebuild) ✅
|
||||
Kette verifiziert: Modul-03 Ingest → `market.data.ready` → Modul-04 Consumer → RegimeEngine → `market_regime` → `MARKET_REGIME_READY` auf `market.regime.ready`.
|
||||
|
||||
| Fall | Regime | Conf | candles | version | Event | DB |
|
||||
|------|--------|------|---------|---------|-------|----|
|
||||
| M4TREND_UP (40) | TREND_UP | 100 | 40 | 1.0.0 | genau 1 | ✅ |
|
||||
| M4TREND_DN (40) | TREND_DOWN | 100 | 40 | 1.0.0 | genau 1 | ✅ |
|
||||
| M4RANGE (40) | LOW_VOLATILITY (Range) | 60 | 40 | 1.0.0 | genau 1 | ✅ |
|
||||
| M4HIGHVOL (40) | HIGH_VOLATILITY | 75 | 40 | 1.0.0 | genau 1 | ✅ |
|
||||
| M4UNKNOWN (5) | UNKNOWN | 20 | 5 | 1.0.0 | genau 1 | ✅ |
|
||||
| M4IDEMPOT (40) | TREND_UP | 100 | 40 | 1.0.0 | genau 1 | ✅ |
|
||||
|
||||
**Idempotenz:** dasselbe Quell-Event (`source_event_id`) erneut → **kein zweiter Datensatz**, kein Doppel-Event. ✅
|
||||
**Logs:** keine Errors/Tracebacks. Health `{postgresql:true, rabbitmq:true, market_data_ready:true}`. ✅
|
||||
|
||||
## Bugs behoben während E2E (20.08.2026)
|
||||
| Bug | Fix |
|
||||
|-----|-----|
|
||||
| `can't adapt type 'dict'` (JSONB) | `json.dumps(ind.model_dump(mode="json"))` |
|
||||
| `tuple index out of range` (16/15) | `version` in INSERT-VALUES ergänzt |
|
||||
| `ON CONFLICT` + partieller Index Fehler | `WHERE source_event_id IS NOT NULL` in Klausel |
|
||||
| `model_dump(default=...)` TypeError | `default`-Kwarg entfernt (`model_dump(mode="json")`) |
|
||||
| Consumer-Verbindungs-Leak | `conn.close()` bei Reconnect → kein Message-Leak |
|
||||
|
||||
## Offene Punkte
|
||||
- Consumer-Downstream für `market.regime.ready` (Modul-05+)
|
||||
- Bestätigte TRANSITION-Detektion mit echten Folgedaten
|
||||
|
||||
---
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-04-Dokumentation angelegt (Regime-Engine, market_regime-Schema, Events, E2E freigegeben).
|
||||
```
|
||||
154
notes/trading/system-docs/modul-05-strategy-engine.md
Normal file
154
notes/trading/system-docs/modul-05-strategy-engine.md
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
# 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
|
||||
```bash
|
||||
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 `ts` → `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 (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):
|
||||
```sql
|
||||
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_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).
|
||||
```
|
||||
159
notes/trading/system-docs/modul-06-signal-ranking.md
Normal file
159
notes/trading/system-docs/modul-06-signal-ranking.md
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# Modul-06-Signal-Ranking — Betriebsdokumentation
|
||||
|
||||
> Erstellt: 20.08.2026 (Rain Ocampo) · Status: ✅ **Freigegeben** (E2E+Idempotenz+Reconnect+Doku grün)
|
||||
|
||||
## Zweck
|
||||
**Signal-Ranking** — konsumiert `SIGNAL_DETECTED` (Modul-05) + OHLCV (Modul-03), bewertet die Signalqualität deterministisch (Score 0–100, vollständig nachvollziehbar und konfigurierbar), persistiert das Ergebnis und publiziert `SIGNAL_RANKED`.
|
||||
Pipeline: `SIGNAL_DETECTED (Modul-05) → Signal-Ranking (signal_quality_v1) → PostgreSQL (signal_ranking) → SIGNAL_RANKED`
|
||||
**Deterministische, regelbasierte Ranking-Engine — bewusst OHNE KI/ML. Keine Positionsgröße, kein Risk Management, keine Broker-/Order-Ausführung (Trade-Freigabe macht später der Risk Manager).** Modular aufgebaut, damit später eine ML/KI-Engine als ZUSÄTZLICHE Ranking-Engine registriert werden kann.
|
||||
|
||||
## Container
|
||||
| Attribut | Wert |
|
||||
|----------|------|
|
||||
| Name | `Modul-06-Signal-Ranking` |
|
||||
| Image | `signal-ranking:0.1.0` (lokal gebaut) |
|
||||
| Port | **55006** — **NUR intern** (`expose`, nicht öffentlich) |
|
||||
| Netzwerk | `trading-modules` (bridge) |
|
||||
| Build-Context | `/opt/trading-modules/modul06-signal-ranking/` |
|
||||
| Restart | `unless-stopped` |
|
||||
| Healthcheck | ✅ `healthy` |
|
||||
|
||||
## Deployment
|
||||
```bash
|
||||
cd /opt/trading-modules
|
||||
docker compose build modul-06-signal-ranking
|
||||
docker compose up -d --no-deps --force-recreate modul-06-signal-ranking
|
||||
```
|
||||
|
||||
## 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-05 ──signal.detected──▶ RankingConsumer (ranking.input)
|
||||
│
|
||||
▼
|
||||
OHLCV (Modul-03, intern 55003/history/{symbol})
|
||||
│
|
||||
▼
|
||||
RankingEngine (deterministisch, Registry)
|
||||
signal_quality_v1 (9 Komponenten, Score 0–100)
|
||||
│
|
||||
┌───────────┴───────────┐
|
||||
▼ ▼
|
||||
signal_ranking (PG) SIGNAL_RANKED
|
||||
(eigene Tabelle) → market.rankings / signal.ranked
|
||||
```
|
||||
|
||||
- **Consumer** (`app/consumer/consumer.py`): bindet Exchange `market.signals`, Routing `strategy.signal.detected`; durable Queue `ranking.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}`); hoher Lookback, damit die letzten geschlossenen Kerzen enthalten sind.
|
||||
- **Engine/Register** (`app/ranking/engine.py`): modular — Ranking-Engines via `.name/.version/rank()` registriert; KI/ML später ergänzbar.
|
||||
- **Storage** (`app/storage/storage.py`): idempotent via partiellen Unique-Index auf `source_signal_id`.
|
||||
- **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 → Ranking → speichern + publizieren; robuste Payload-Extraktion; setzt `source_signal_id` aus dem Event-Top-Level, falls die Engine es nicht aus dem inneren payload ziehen konnte.
|
||||
|
||||
## Ranking-Engine V1 — `signal_quality_v1` (`app/ranking/signal_quality_v1.py`)
|
||||
Deterministische Qualitätsbewertung. **9 Komponenten, Score 0–100, vollständig nachvollziehbar und konfigurierbar.** Jede Komponente liefert 0..max; Summe = `total_score`. Komponenten werden einzeln in der DB gespeichert (`comp_*`).
|
||||
|
||||
| Komponente | Max | Bewertet |
|
||||
|-----------|-----|----------|
|
||||
| `regime` | 20 | Market-Regime-Qualität / Confidence |
|
||||
| `trend` | 15 | Trendstärke |
|
||||
| `pullback` | 15 | Pullback-Qualität |
|
||||
| `trigger` | 10 | Entry-Bestätigung |
|
||||
| `rr` | 10 | Chance/Risiko-Verhältnis |
|
||||
| `volatility` | 10 | Volatilitätszustand |
|
||||
| `distance` | 10 | Distanz Entry→Stop |
|
||||
| `data_quality` | 5 | Datenqualität / Candle-Anzahl |
|
||||
| `consistency` | 5 | Setup-Konsistenz |
|
||||
|
||||
**Klassifizierung:** `total_score >= eligible_threshold` → `eligible`; darunter `weak` (wird trotzdem gespeichert).
|
||||
|
||||
**Zentrale Schwellenwerte** (`app/config.py`, env-overridable):
|
||||
| Parameter | Default | Bedeutung |
|
||||
|-----------|---------|-----------|
|
||||
| `max_score` | 100 | Score-Obergrenze |
|
||||
| `eligible_threshold` | 70 | Score ≥ 70 → `eligible` |
|
||||
| `ranking_lookback` | 500 | max. Kerzen für Trend/Volatilität (Modul-03 liefert die N ältesten, daher hoch) |
|
||||
| `min_candles_required` | 60 | unter dieser Zahl → Datenqualität-Punktabzug |
|
||||
| `full_candles_target` | 250 | ab dieser Zahl → volle Datenqualität |
|
||||
| `pullback_depth_min/max` | 0.5 / 4.0 | Abstand Entry zu Trend-Referenz in ATR |
|
||||
| `vol_ideal_lo/hi` | 0.005 / 0.030 | ATR %-Fenster für "ideale" Volatilität |
|
||||
|
||||
Keine Trade-Freigabe — nur Qualitätsbewertung.
|
||||
|
||||
## Datenbank (Modul-01-PostgreSQL)
|
||||
Eigene Tabelle `public.signal_ranking` (bestehende unangetastet):
|
||||
```sql
|
||||
ranking_id UUID, source_signal_id TEXT, source_event_id TEXT, correlation_id TEXT,
|
||||
timestamp TIMESTAMPTZ, symbol TEXT, asset_class TEXT, provider TEXT, timeframe TEXT,
|
||||
ranking_name TEXT, ranking_version TEXT, ranking_class TEXT (eligible/weak),
|
||||
total_score DOUBLE PRECISION, comp_regime/comp_trend/comp_pullback/comp_trigger/comp_rr/
|
||||
comp_volatility/comp_distance/comp_data_quality/comp_consistency DOUBLE PRECISION,
|
||||
details JSONB, reasons JSONB
|
||||
```
|
||||
- **Unique (partiell):** `uq_signal_ranking_src` auf `(symbol, timeframe, source_signal_id)` **WHERE source_signal_id IS NOT NULL** → Idempotenz (`source_signal_id` nie doppelt).
|
||||
- Migration: `migrations/001_signal_ranking.sql` (idempotent, löscht nichts).
|
||||
|
||||
## RabbitMQ (Modul-02)
|
||||
| Exchange | Typ | Routing | Event |
|
||||
|----------|-----|---------|-------|
|
||||
| `market.signals` | topic | `strategy.signal.detected` (eingang) | SIGNAL_DETECTED |
|
||||
| `market.rankings` | topic | `signal.ranked` (ausgang) | SIGNAL_RANKED |
|
||||
|
||||
## Interne API
|
||||
| Endpoint | Zweck |
|
||||
|----------|-------|
|
||||
| `GET /health` | Liveness (200) + Komponentenstatus im Body |
|
||||
| `GET /health/ready` | Readiness (503 wenn PG/RabbitMQ/Modul-03 down) |
|
||||
| `GET /rankings/{symbol}` | Rankings eines Symbols |
|
||||
| `GET /rankings/latest/{symbol}` | Neuestes Ranking eines Symbols |
|
||||
| `GET /ranking-engines` | Registrierte Ranking-Engines |
|
||||
|
||||
## End-to-End-Test (20.08.2026, final) ✅
|
||||
Kette verifiziert: Modul-03 Ingest → `market.data.ready` → Modul-04 → `market.regime.ready` → Modul-05 → `SIGNAL_DETECTED` → Modul-06 Consumer → `signal_quality_v1` → `signal_ranking` → `SIGNAL_RANKED`.
|
||||
|
||||
| Fall | Ranking | Score | Class | DB | SIGNAL_RANKED |
|
||||
|------|---------|-------|-------|----|---------------|
|
||||
| M5LONG (TREND_UP) | **eligible** | **72.2** | eligible | ✅ 1 | ✅ 1 |
|
||||
| M5SHORT (TREND_DOWN) | **eligible** | **73.6** | eligible | ✅ 1 | ✅ 1 |
|
||||
| M5NOPULL (kein Pullback) | — | — | — | ✅ 0 | ✅ 0 |
|
||||
| M5RANGE (Range) | — | — | — | ✅ 0 | ✅ 0 |
|
||||
|
||||
**Verifizierte Eigenschaften:**
|
||||
- Gültiger LONG/SHORT → **genau 1** `SIGNAL_RANKED`; NOPULL/RANGE → **0** Ranking + 0 Event. ✅
|
||||
- DB-Eintrag je gültigem Signal vorhanden; `source_signal_id` korrekt übernommen (z.B. `effd782c...`, `42dd2042...`). ✅
|
||||
- `total_score` immer 0–100; **Summe aller Komponenten == total_score**. ✅
|
||||
- `eligible`/`weak` korrekt gemäß Threshold (72.2/73.6 ≥ 70 → eligible). ✅
|
||||
- **Idempotenz:** identisches `SIGNAL_DETECTED` erneut → **kein zweiter DB-Eintrag, kein zweites SIGNAL_RANKED** (count=1). ✅
|
||||
- **RabbitMQ-Reconnect:** kontrollierter Neustart → Modul-04/05/06 verbinden automatisch (Backoff 1s→2s→4s→8s), **je exakt 1 Consumer, keine Zombies, keine verlorenen Events.** ✅
|
||||
- **Logs:** keine unbehandelten Tracebacks nach Reconnect. Health/Readiness `{postgresql:true, rabbitmq:true, market_data:true, consumer_ready:true}`. ✅
|
||||
|
||||
## Bugs behoben während E2E (20.08.2026)
|
||||
| Bug | Fix |
|
||||
|-----|-----|
|
||||
| `source_signal_id` leer | Service setzt `source_signal_id` aus dem Event-Top-Level, falls die Engine es nicht aus dem inner-Payload ziehen konnte |
|
||||
| OHLCV-Lookback zu klein (120) | `ranking_lookback` auf 500 erhöht — Modul-03 `/history?limit=N` liefert die N **ältesten** Kerzen; zu kleiner Lookback → Engine bewertete auf veralteten Kerzen (Konsistenz/Trend/Pullback schief) |
|
||||
| `direction`-Spalte existierte nicht | E2E-Query auf `details`/Modell angepasst |
|
||||
|
||||
## Offene Punkte
|
||||
- Downstream-Consumer für `signal.ranked` (Modul-07+ — noch NICHT begonnen)
|
||||
- KI/ML-Ranking-Engine über Registry ergänzbar
|
||||
- Trade-Freigabe später durch Risk Manager (noch nicht gebaut)
|
||||
|
||||
---
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-06-Dokumentation angelegt (Signal-Ranking signal_quality_v1, signal_ranking-Schema, Events, E2E final grün).
|
||||
```
|
||||
169
notes/trading/system-docs/modul-07-risk-manager.md
Normal file
169
notes/trading/system-docs/modul-07-risk-manager.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# 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`
|
||||
252
notes/trading/system-docs/modul-08-portfolio-manager.md
Normal file
252
notes/trading/system-docs/modul-08-portfolio-manager.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
# Modul-08: Portfolio-Manager
|
||||
|
||||
**Status: FREIGEGEBEN** (20.08.2026)
|
||||
|
||||
Deterministischer Portfolio-Manager. Konsumiert `RISK_APPROVED` aus Modul-07,
|
||||
prüft das Gesamtportfolio gegen zentrale Limits und publiziert
|
||||
`TRADE_APPROVED` oder `PORTFOLIO_REJECTED`.
|
||||
|
||||
**Keine KI/ML. Keine Broker-Order. FAIL-CLOSED.**
|
||||
|
||||
---
|
||||
|
||||
## 1. Rolle in der Kette
|
||||
|
||||
```
|
||||
Modul-03 Ingest → 04 Regime → 05 Signal → 06 Ranking
|
||||
→ SIGNAL_RANKED (market.rankings/signal.ranked)
|
||||
→ Modul-07 Risk-Check → RISK_APPROVED (market.risk/risk.approved)
|
||||
→ Modul-08 Portfolio-Check → portfolio_decision-Tabelle
|
||||
→ TRADE_APPROVED / PORTFOLIO_REJECTED (market.portfolio)
|
||||
```
|
||||
|
||||
Modul-08 prüft **NICHT erneut** die Positionsgrößenlogik aus Modul-07,
|
||||
sondern ausschließlich **Portfolio-Risiken**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Events
|
||||
|
||||
| Richtung | Exchange | Routing-Key | Event |
|
||||
|----------|----------|-------------|-------|
|
||||
| Input (Consumer) | `market.risk` | `risk.approved` | `RISK_APPROVED` |
|
||||
| Output (Publisher) | `market.portfolio` | `trade.approved` | `TRADE_APPROVED` |
|
||||
| Output (Publisher) | `market.portfolio` | `portfolio.rejected` | `PORTFOLIO_REJECTED` |
|
||||
|
||||
Consumer-Queue: `portfolio.input` (durable, manuelles Ack, prefetch=1).
|
||||
|
||||
---
|
||||
|
||||
## 3. V1 Portfolio-Regeln (zentral konfigurierbar, env-overridable)
|
||||
|
||||
| Regel | Config-Feld | Default |
|
||||
|-------|-------------|---------|
|
||||
| max. Anzahl offener Positionen | `max_positions` | 5 |
|
||||
| max. gesamtes Open Risk % | `max_open_risk_percent` | 0.05 (5%) |
|
||||
| max. Exposure pro Position % | `max_exposure_per_position_percent` | 0.20 |
|
||||
| max. Exposure pro Symbol % | `max_exposure_per_symbol_percent` | 0.30 |
|
||||
| max. Exposure pro Assetklasse % | `max_exposure_per_asset_class_percent` | 0.50 |
|
||||
| max. Long-Exposure % | `max_long_exposure_percent` | 0.60 |
|
||||
| max. Short-Exposure % | `max_short_exposure_percent` | 0.60 |
|
||||
| Duplikat Symbol/Strategie erlauben | `allow_duplicate_symbol_strategy` | false |
|
||||
| Korrelation anwenden | `apply_correlation` | false (vorbereitet) |
|
||||
| Account Equity | `account_equity` | 10000.0 |
|
||||
|
||||
Alle Limits sind Prozentwerte des `account_equity`. Bei `account_equity <= 0`
|
||||
→ FAIL-CLOSED `PORTFOLIO_REJECTED`.
|
||||
|
||||
**Später geplant** (V2): Sector/Country/Currency-Exposure, Korrelation
|
||||
(erst wenn verlässliche Daten vorhanden).
|
||||
|
||||
---
|
||||
|
||||
## 4. FAIL-CLOSED
|
||||
|
||||
Jede Exception, fehlende oder ungültige kritische Eingabe führt zu
|
||||
`PORTFOLIO_REJECTED`, **niemals** zu `TRADE_APPROVED`. Fehlende kritische
|
||||
Felder (quantity, risk, entry) → `PORTFOLIO_REJECTED` mit Reason-Codes.
|
||||
|
||||
---
|
||||
|
||||
## 5. Portfoliozustand (autoritative Quelle)
|
||||
|
||||
Der Portfoliozustand wird **NICHT nur aus Events angenommen**. Die
|
||||
Architektur trennt die Quelle des Zustands vom Entscheidungs-Code:
|
||||
|
||||
- `app/portfolio/state.py` definiert `PortfolioStateProvider` (Interface)
|
||||
und `PortfolioState` / `OpenPosition`.
|
||||
- V1-Provider liest offene Positionen aus der eigenen `portfolio_decision`-
|
||||
Tabelle (alle `TRADE_APPROVED`-Entscheidungen).
|
||||
- **Später** kann ein Broker-/Execution-Provider als autoritative Quelle
|
||||
ergänzt werden, ohne den Entscheidungs-Code zu ändern.
|
||||
|
||||
---
|
||||
|
||||
## 6. Race-Condition-Schutz (atomar)
|
||||
|
||||
Zwei nahezu gleichzeitige `RISK_APPROVED` dürfen **nicht** beide auf demselben
|
||||
alten Portfoliozustand genehmigt werden und dadurch Limits überschreiten.
|
||||
|
||||
Lösung (`app/storage/storage.py` → `evaluate_and_save_atomic`):
|
||||
1. **Advisory Lock** (`pg_advisory_xact_lock`) serialisiert parallele
|
||||
Portfolio-Entscheidungen.
|
||||
2. Innerhalb **einer Transaktion**: Zustand lesen → Engine bewerten →
|
||||
speichern → Commit.
|
||||
3. Der zweite Prozess wartet auf den Lock und bewertet auf dem **aktualisierten**
|
||||
Zustand (inkl. der ersten Entscheidung).
|
||||
|
||||
---
|
||||
|
||||
## 7. Idempotenz
|
||||
|
||||
- Unique-Index `uq_portfolio_decision_src` auf `source_risk_decision_id`
|
||||
(partial, nur wo nicht NULL).
|
||||
- Gleiches `source_risk_decision_id` → keine Doppelentscheidung/Event.
|
||||
- Zusätzlich prüft der Service vor der Verarbeitung, ob die Quelle bereits
|
||||
entschieden wurde (Skip + Ack).
|
||||
|
||||
---
|
||||
|
||||
## 8. DB-Tabelle `portfolio_decision`
|
||||
|
||||
Jede Entscheidung wird gespeichert — **auch `PORTFOLIO_REJECTED`**.
|
||||
|
||||
| Spalte | Typ | Beschreibung |
|
||||
|--------|-----|--------------|
|
||||
| `id` | BIGSERIAL PK | |
|
||||
| `portfolio_decision_id` | TEXT | eigene Entscheidungs-ID |
|
||||
| `source_risk_decision_id` | TEXT | id des RISK_APPROVED (Modul-07) |
|
||||
| `source_ranking_id` | TEXT | durchgereichtes Ranking (Modul-06) |
|
||||
| `source_signal_id` | TEXT | durchgereichtes Signal (Modul-05) |
|
||||
| `source_event_id` | TEXT | |
|
||||
| `correlation_id` | TEXT | |
|
||||
| `timestamp` | TIMESTAMPTZ | Entscheidungszeitpunkt (UTC) |
|
||||
| `symbol` | TEXT | |
|
||||
| `asset_class` | TEXT | |
|
||||
| `strategy` | TEXT | |
|
||||
| `direction` | TEXT | LONG / SHORT |
|
||||
| `timeframe` | TEXT | |
|
||||
| `proposed_quantity` | DOUBLE | aus RISK_APPROVED |
|
||||
| `proposed_risk` | DOUBLE | actual_risk_amount |
|
||||
| `proposed_exposure` | DOUBLE | qty × entry |
|
||||
| `current_portfolio_risk` | DOUBLE | offenes Risiko VOR |
|
||||
| `new_portfolio_risk` | DOUBLE | offenes Risiko NACH |
|
||||
| `exposure_before` | DOUBLE | Gesamt-Exposure VOR |
|
||||
| `exposure_after` | DOUBLE | Gesamt-Exposure NACH |
|
||||
| `decision` | TEXT | TRADE_APPROVED / PORTFOLIO_REJECTED |
|
||||
| `reason_codes` | TEXT[] | z. B. `{OK}` oder `{EXPOSURE_PER_POSITION_EXCEEDED,...}` |
|
||||
| `rule_version` | TEXT | `portfolio_v1@1.0.0` |
|
||||
| `details` | JSONB | Begründung + Zwischenwerte |
|
||||
| `portfolio_snapshot` | JSONB | Portfoliozustand zum Zeitpunkt |
|
||||
| `created_at` | TIMESTAMPTZ | |
|
||||
|
||||
---
|
||||
|
||||
## 9. Reason-Codes
|
||||
|
||||
| Code | Bedeutung |
|
||||
|------|-----------|
|
||||
| `OK` | alle Limits eingehalten → TRADE_APPROVED |
|
||||
| `MAX_POSITIONS_EXCEEDED` | max. offene Positionen erreicht |
|
||||
| `OPEN_RISK_EXCEEDED` | gesamtes Open Risk überschritten |
|
||||
| `EXPOSURE_PER_POSITION_EXCEEDED` | Exposure pro Position überschritten |
|
||||
| `EXPOSURE_PER_SYMBOL_EXCEEDED` | Exposure pro Symbol überschritten |
|
||||
| `EXPOSURE_PER_ASSET_CLASS_EXCEEDED` | Exposure pro Assetklasse überschritten |
|
||||
| `LONG_EXPOSURE_EXCEEDED` | Long-Exposure überschritten |
|
||||
| `SHORT_EXPOSURE_EXCEEDED` | Short-Exposure überschritten |
|
||||
| `DUPLICATE_SYMBOL_STRATEGY` | doppelte Position Symbol/Strategie |
|
||||
| `QUANTITY_MISSING` / `RISK_AMOUNT_MISSING` / `ENTRY_MISSING` | fehlende kritische Daten (FAIL-CLOSED) |
|
||||
| `EQUITY_MISSING` | account_equity fehlt/ungültig (FAIL-CLOSED) |
|
||||
|
||||
---
|
||||
|
||||
## 10. Architektur
|
||||
|
||||
```
|
||||
app/
|
||||
config.py # zentrale Konfiguration (env-overridable)
|
||||
core/
|
||||
models.py # PortfolioDecision + Event-Modelle
|
||||
service.py # Orchestrator (Consumer→Engine→Storage→Publisher)
|
||||
portfolio/
|
||||
engine.py # Registry der Portfolio-Verfahren
|
||||
portfolio_v1.py # deterministische V1-Prüfung (FAIL-CLOSED)
|
||||
state.py # PortfolioState-Provider (autoritative Quelle)
|
||||
storage/
|
||||
storage.py # idempotent + atomare Race-Condition-Lösung
|
||||
publisher/
|
||||
publisher.py # frische Verbindung je Publish + close
|
||||
consumer/
|
||||
consumer.py # manuelles Ack, Reconnect-Backoff, Zombie-Schutz
|
||||
api/
|
||||
main.py # FastAPI (Health/Readiness/Decisions/State)
|
||||
migrations/
|
||||
001_portfolio_decision.sql
|
||||
Dockerfile
|
||||
requirements.txt
|
||||
test_portfolio.py # 11 Unit-Tests (Engine-Logik)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Tests
|
||||
|
||||
### Unit-Tests (`test_portfolio.py`, 11/11 grün)
|
||||
1. erstes gültiges Portfolio-Trade → TRADE_APPROVED
|
||||
2. max Positions überschritten → PORTFOLIO_REJECTED
|
||||
3. max Open Risk überschritten → PORTFOLIO_REJECTED
|
||||
4. Symbol-Exposure überschritten → PORTFOLIO_REJECTED
|
||||
5. Long/Short-Exposure-Limit → PORTFOLIO_REJECTED
|
||||
6. Duplicate Symbol/Strategie → PORTFOLIO_REJECTED
|
||||
7. fehlender Portfoliozustand → FAIL-CLOSED
|
||||
8. Idempotenz (deterministisch)
|
||||
9. Assetklassen-Exposure → PORTFOLIO_REJECTED
|
||||
10. Exposure pro Position → PORTFOLIO_REJECTED
|
||||
11. Short-Exposure → PORTFOLIO_REJECTED
|
||||
|
||||
### E2E (Kette 03→04→05→06→07→08, alle Checks grün)
|
||||
- **A**: Kette 03→08, Portfolio-Decision in DB (LONG & SHORT)
|
||||
- **B**: exakt 1 Portfolio-Event je Symbol (TRADE_APPROVED/PORTFOLIO_REJECTED)
|
||||
- **C**: Pflichtfelder in Portfolio-Decision vorhanden
|
||||
- **D**: Idempotenz (identisches RISK_APPROVED → kein Doppel-Decision)
|
||||
- **E**: parallele RISK_APPROVED → beide verarbeitet, keine Race-Verlust
|
||||
- **F**: erstes gültiges Portfolio-Trade → TRADE_APPROVED
|
||||
|
||||
### Reconnect-Test
|
||||
RabbitMQ gestoppt → Consumer erkennt Ausfall, Reconnect-Backoff (1s→2s→4s→8s).
|
||||
RabbitMQ gestartet → Consumer verbindet sich neu, alle 5 Queues haben exakt
|
||||
1 Consumer, Health/Readiness 200.
|
||||
|
||||
---
|
||||
|
||||
## 12. Health / Readiness
|
||||
|
||||
- `GET /health` → Liveness (immer 200, Komponentenstatus im Body)
|
||||
- `GET /health/ready` → Readiness (200 nur wenn PG + RabbitMQ erreichbar)
|
||||
- `GET /decisions/{symbol}` → gespeicherte Entscheidungen (inkl. REJECTED)
|
||||
- `GET /portfolio-rules` → verfügbare Portfolio-Regeln
|
||||
- `GET /portfolio/state` → aktueller Portfoliozustand
|
||||
|
||||
Port: **55008** (nur Docker-intern, kein öffentlicher Host-Port).
|
||||
|
||||
---
|
||||
|
||||
## 13. Deploy
|
||||
|
||||
- Compose-Service: `modul-08-portfolio-manager`
|
||||
- Image: `portfolio-manager:0.1.0`
|
||||
- Container: `Modul-08-Portfolio-Manager`
|
||||
- Netz: `trading-modules`
|
||||
- `restart: unless-stopped`
|
||||
- Env: PG + RabbitMQ-Zugangsdaten (aus Compose)
|
||||
|
||||
---
|
||||
|
||||
## 14. Wichtige Fixes (20.08.2026)
|
||||
|
||||
**Consumer-Bug (alle Module 04–08):** `process_data_events(time_limit=None)`
|
||||
verarbeitete nur EIN Event und baute danach die Verbindung neu auf. Der
|
||||
erneute `_connect()` führte `queue_delete` aus und löschte wartende Events
|
||||
(z. B. ein zweites, nahezu gleichzeitiges RISK_APPROVED) unwiederbringlich.
|
||||
Fix: innere Schleife `while not stop: process_data_events(time_limit=1.0)`
|
||||
hält die Verbindung am Leben. Betroffen und gefixt: Modul-04, 05, 06, 07, 08.
|
||||
146
notes/trading/system-docs/modul-09-execution-service.md
Normal file
146
notes/trading/system-docs/modul-09-execution-service.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# 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-04–08 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).
|
||||
106
notes/trading/system-docs/modul-10-trade-journal.md
Normal file
106
notes/trading/system-docs/modul-10-trade-journal.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Modul-10: Trade-Journal
|
||||
|
||||
**Status: FREIGEGEBEN** (20.08.2026)
|
||||
|
||||
Deterministisches, append-only Trade-Journal. Konsumiert die
|
||||
Execution-Events (`ORDER_SUBMITTED`/`ORDER_FILLED`/`ORDER_REJECTED`/`ORDER_FAILED`)
|
||||
aus Modul-09, validiert die Herkunftskette hart (FAIL-CLOSED) und dokumentiert
|
||||
jeden Trade in einer Audit-Trail-konformen, idempotenten Struktur.
|
||||
Ergebnis wird als `TRADE_RECORDED`-Event publiziert.
|
||||
|
||||
**Sicherheit & Audit:**
|
||||
- **Keine KI/ML, keine Orderausführung, keine Risikoberechnung** — reines Dokumentieren
|
||||
- **Append-only**: `trade_event_history` ist unveränderlich (nur INSERT, kein UPDATE/DELETE)
|
||||
- **Herkunftskette wird validiert** (Signal → Ranking → Risk → Portfolio → Execution)
|
||||
- Bei inkonsistenter Kette → Trade als `INCONSISTENT` markiert (FAIL-CLOSED), nicht stumm verworfen
|
||||
- `TRADE_RECORDED` optional: ACK nach persistenter Speicherung
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
ORDER_* (market.execution, Modul-09)
|
||||
│
|
||||
▼
|
||||
JournalConsumer (journal.input, Consumer-Fix von Anfang an)
|
||||
│
|
||||
▼
|
||||
Herkunfts-Validierung (FAIL-CLOSED)
|
||||
│ Kette 05→06→07→08→09 nachvollziehbar?
|
||||
│ provenance_consistent in metadata
|
||||
▼
|
||||
JournalService
|
||||
│ erstes Event → Trade anlegen + TRADE_RECORDED
|
||||
│ Folge-Event → append-only Event + Status-Update (bestehenden Trade)
|
||||
▼
|
||||
JournalStorage (idempotent, Unique-Index)
|
||||
│ trade_journal + trade_event_history
|
||||
▼
|
||||
RabbitMQPublisher (market.journal)
|
||||
TRADE_RECORDED (trade.recorded)
|
||||
```
|
||||
|
||||
## Service-/Storage-Grenze (vereinheitlicht)
|
||||
|
||||
**Storage gibt IMMER `TradeJournal` zurück** — nie ein `dict`.
|
||||
- Einzige Konvertierungsstelle `_row_to_trade` (DB-Zeile → Modell)
|
||||
- `get_trade_by_execution()` → `Optional[TradeJournal]`
|
||||
- Der Service arbeitet ausschließlich mit Modellen (kein dict/Model-Mix je Codepfad)
|
||||
- Dadurch ist `trade_id` auf jedem Rückgabewert garantiert verfügbar
|
||||
- (Fix gegen `'dict' object has no attribute 'trade_id'` → NACK/Requeue)
|
||||
|
||||
## Verarbeitung
|
||||
|
||||
- **Erstes Event (ORDER_SUBMITTED)** → Trade anlegen, `TRADE_RECORDED` publizieren
|
||||
- **Folge-Event (ORDER_FILLED u.a.)** → bestehenden Trade als Status-Update
|
||||
verarbeiten: append-only Event + Status-Update, KEIN neuer Trade,
|
||||
**kein zweites TRADE_RECORDED**
|
||||
- Bei FILLED: `entry_filled`/`filled_at`/Status aktualisiert (Trade öffnen/aktualisieren)
|
||||
- Eingehende Events werden nicht doppelt verarbeitet (idempotent, Unique-Index)
|
||||
|
||||
## Idempotenz
|
||||
|
||||
- Unique-Index auf `(execution_id, event_id)` → jedes Event exakt einmal
|
||||
- `provenance_consistent` in `metadata` (jsonb), nicht als Spalte
|
||||
- `TRADE_RECORDED` nur beim ersten Event pro Trade
|
||||
- Kein Doppel-Journal bei Re-Publish desselben Events
|
||||
|
||||
## DB-Tabellen
|
||||
|
||||
- `trade_journal`: trade_id, execution_id, source_portfolio_decision_id,
|
||||
Herkunftsketten-IDs (signal/ranking/risk/portfolio), symbol, asset_class,
|
||||
strategy, direction, timeframe, entry_requested, entry_filled, stop_loss,
|
||||
target, quantity, risk_amount, execution_mode, broker_order_id, status,
|
||||
provenance_consistent (in metadata), created_at, updated_at
|
||||
- `trade_event_history`: execution_id, event_id, event_type, payload, created_at
|
||||
(append-only, unveränderlich)
|
||||
|
||||
## RabbitMQ
|
||||
|
||||
- Exchange `market.journal` (topic, durable)
|
||||
- Routing: `trade.recorded`
|
||||
- Queue `journal.input` (durable), gebunden an `market.execution`/`order.*`
|
||||
- Consumer-Fix 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
|
||||
- ACK erst nach persistenter Speicherung; bei Fehler NACK/Requeue
|
||||
|
||||
## Tests
|
||||
|
||||
- **39/39 Unit-Tests** (test_journal.py): Anlage, Status-Update, Idempotenz,
|
||||
FAIL-CLOSED-Herkunftskette, append-only, exakt 1 TRADE_RECORDED, Reconnect
|
||||
- **Gezielter Fix-Test 9/9** (test_fix_update.py): SUBMITTED→Journal,
|
||||
FILLED→Update, Rückgabe ist TradeJournal (kein dict), `trade_id` vorhanden,
|
||||
genau 1 Trade, append-only (2 Events), idempotent, kein Doppel-Journal
|
||||
- **E2E 7/7 Checks** (Kette 03→10): Journal-Eintrag in DB, exakt 1 Trade,
|
||||
Pflichtfelder + Herkunftskette konsistent, append-only Event-Historie
|
||||
(SUBMITTED+FILLED), exakt 1 TRADE_RECORDED, Idempotenz nach Re-Publish
|
||||
- **Reconnect-Test**: RabbitMQ gestoppt → Backoff 1→2→4→8→16s → wieder
|
||||
verbunden, `journal.input` 1 Consumer, 0 Messages, kein Zombie
|
||||
- **Log-Check**: 0 ERROR, 0 Traceback, ACK auf ORDER_SUBMITTED + ORDER_FILLED
|
||||
|
||||
## Deploy
|
||||
|
||||
- Container `Modul-10-Trade-Journal`, Port 55010 nur Docker-intern
|
||||
(kein öffentlicher Host-Port)
|
||||
- Health/Readiness 200, PG + RabbitMQ erreichbar, Consumer bereit
|
||||
- Env: `EXECUTION_MODE=PAPER`, `TRADING_ENABLED=false`
|
||||
82
notes/trading/system-docs/modul-11-analytics.md
Normal file
82
notes/trading/system-docs/modul-11-analytics.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Modul-11: Analytics
|
||||
|
||||
**Status: FREIGEGEBEN** · Container `Modul-11-Analytics` · Image `analytics-service:0.1.0`
|
||||
|
||||
## Zweck
|
||||
Auswertung der Trade-Journal-Daten aus **Modul-10** (`trade_journal`) zu reproduzierbaren Performance-Kennzahlen. Deterministische Berechnung — gleiche Datenbasis → gleiche Ergebnisse. **V1 ohne KI/ML, ohne Orders, ohne automatische Strategieänderungen.**
|
||||
|
||||
## Architektur / Datenfluss
|
||||
|
||||
```
|
||||
TRADE_RECORDED (market.journal / trade.recorded)
|
||||
│
|
||||
▼
|
||||
Modul-11-Analytics ── konsumiert über Queue `analytics.input`
|
||||
│
|
||||
├── liest read-only → trade_journal (Modul-10, PostgreSQL)
|
||||
├── schreibt → analytics_snapshot, strategy_performance, daily_performance
|
||||
└── publiziert (opt.)→ ANALYTICS_UPDATED (market.analytics / analytics.updated)
|
||||
|
||||
API (Docker-intern, Port 55011):
|
||||
/health, /health/ready
|
||||
/analytics/portfolio Gesamt-/Portfolio-Performance
|
||||
/analytics/strategy je Strategie+Version
|
||||
/analytics/regime je Market Regime
|
||||
/analytics/period je Monat (ab optionalem from_date)
|
||||
/analytics/rebuild (POST) deterministischer Rebuild aus DB
|
||||
```
|
||||
|
||||
Primärquelle ist `TRADE_RECORDED`; zusätzlich liest Analytics direkt aus PostgreSQL (`trade_journal`), da für reproduzierbare Auswertungen ein vollständiger, deterministischer Rebuild aus der DB sinnvoller ist. `TRADE_RECORDED`-Events triggern eine Neuberechnung (Idempotenz: identisches Event → keine Doppelzählung).
|
||||
|
||||
## Datenquelle & Realisationslogik
|
||||
- **Nur `status = 'CLOSED'` UND `realized_pnl IS NOT NULL`** fließen in realisierte Performance ein.
|
||||
- **Offene Trades** (`OPEN`) werden separat ausgewiesen (`open_trade_count`) und zählen NICHT als realisierte Trades.
|
||||
- Gruppierungsfelder aus `trade_journal`: `strategy`, `strategy_version`, `symbol`, `asset_class`, `direction` (LONG/SHORT), `regime`, `signal_score`.
|
||||
- Zeitraum-Gruppierung nach `exit_time` (realisierter Trades), optional ab `from_date`.
|
||||
- **Keine Lookahead-/Survivorship-Tricks**: nur tatsächlich geschlossene Trades, deterministische Equity-Kurve.
|
||||
|
||||
## Kennzahlen (V1)
|
||||
- Anzahl Trades (realisiert + offen getrennt)
|
||||
- Winrate / Lossrate
|
||||
- durchschnittlicher Gewinn / Verlust
|
||||
- durchschnittliches R (realized_r_multiple)
|
||||
- Expectancy (Währung + R)
|
||||
- Profit Factor (gross_profit / gross_loss)
|
||||
- Netto-PnL
|
||||
- Brutto-Gewinn / Brutto-Verlust
|
||||
- Max Drawdown (aus Equity-Kurve der realisierten PnL)
|
||||
- Durchschnittliche Haltedauer (entry_time→exit_time, Stunden)
|
||||
- Bester / schlechtester Trade
|
||||
|
||||
## Eigene Tabellen (Migration `migrations/001_analytics.sql`)
|
||||
- `analytics_snapshot` — stabiler Snapshot der Gesamt-Performance (jeder Rebuild = neue Zeile, chronologisch append).
|
||||
- `strategy_performance` — Kennzahlen je Strategie+Version.
|
||||
- `daily_performance` — Tageskennzahlen (Backtesting/Reporting).
|
||||
|
||||
Diese fassen KEINE bestehenden Tabellen an (read-only auf `trade_journal`).
|
||||
|
||||
## RabbitMQ
|
||||
- **Queue** `analytics.input` (durable, topic `market.journal`, Routing `trade.recorded`), manuelles ACK, Reconnect mit Backoff (1→2→4→8→16s), kein Zombie.
|
||||
- **Publiziert** optional `ANALYTICS_UPDATED` (topic `market.analytics`, Routing `analytics.updated`) — frische Verbindung je Publish (Bug-Fix-Pattern aus Modul-10), `conn.close()` nach jedem Publish.
|
||||
|
||||
## Verifikation (20.08.2026)
|
||||
| Test | Ergebnis |
|
||||
|---|---|
|
||||
| 1–7 Pure Metriken (Gewinn/Verlust, Winrate, PF, Expectancy, Max DD, Gruppierung, offene≠realisierte) | **OK** |
|
||||
| 8 Idempotenz (identisches Event → keine Doppelzählung) | **OK** |
|
||||
| 9 Rebuild aus DB → identische Kennzahlen | **OK** |
|
||||
| 10 RabbitMQ-Reconnect (Backoff 4→8→16s, erneuter Connect, 1 Consumer) | **OK** |
|
||||
| E2E 03→11 (Seed realisierter Trades, Portfolio/Strategie/Regime, DB + Idempotenz) | **OK** |
|
||||
|
||||
- Health/Ready: `200` Port 55011 (PostgreSQL ✓, RabbitMQ ✓, Consumer ready ✓).
|
||||
- Endzustand nach Freigabe: 2 echte OPEN-Trades im Journal, 0 realisierte → Snapshot zeigt 0/2.
|
||||
|
||||
## Betriebshinweise
|
||||
- Kein öffentlicher Port (nur `expose: 55011`).
|
||||
- `restart: unless-stopped`.
|
||||
- Compose-Pfad VPS: `/opt/trading-modules/docker-compose.yml`.
|
||||
- Forgejo-Doku: `nexo312/trading-system-docs`, Commit-ID siehe unten.
|
||||
|
||||
---
|
||||
*Geändert von: Rain Ocampo (Hermes)*
|
||||
*Datum: 20.08.2026* — Modul-11-Analytics, Status FREIGABEGEBEN.
|
||||
128
notes/trading/system-docs/modul-12-backtesting.md
Normal file
128
notes/trading/system-docs/modul-12-backtesting.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# Modul-12: Backtesting
|
||||
|
||||
**Status: FREIGEGEBEN** · Container `Modul-12-Backtesting` · Image `backtesting:0.1.2` (V1 + V1.1 parallel)
|
||||
|
||||
## Zweck
|
||||
Deterministische, reproduzierbare **historische Strategietests** auf echten
|
||||
OHLCV-Daten aus **Modul-03** (PostgreSQL). Gleiche Strategie-Logik wie
|
||||
**Modul-05** (`trend_pullback_v1`) und identische Regime-Engine wie **Modul-04** —
|
||||
der Backtest entscheidet exakt so wie Live/Paper. **V1 ohne KI/ML, ohne Live-Orders,
|
||||
ohne Broker-Ausführung.** Modular erweiterbar für weitere Strategien (Breakout, ORB,
|
||||
Mean-Reversion) über gemeinsame `Strategy`-Schnittstelle + Registry.
|
||||
|
||||
## Architektur / Datenfluss
|
||||
|
||||
```
|
||||
OHLCV (Modul-01 PostgreSQL, Tabelle ohlcv)
|
||||
│ synchron, direkt nach Timestamp/Zeitraum (KEIN /history?limit)
|
||||
▼
|
||||
Modul-12-Backtesting ── POST /backtest
|
||||
│
|
||||
├── core/engine.py deterministische Backtest-Loop (kein Lookahead)
|
||||
├── strategies/ trend_pullback_v1 (1:1 zu Modul-05)
|
||||
├── regime/engine.py Regime-Engine (1:1 zu Modul-04)
|
||||
├── storage/storage.py Persistenz backtest_run/trade/equity
|
||||
└── api/main.py REST-API (intern, Port 55012)
|
||||
|
||||
API (Docker-intern, Port 55012):
|
||||
/health, /health/ready
|
||||
POST /backtest Backtest starten (synchron, returns Ergebnis)
|
||||
GET /backtest/{run_id} Run-Metadaten
|
||||
GET /backtest/{run_id}/status Status + run_hash/data_hash
|
||||
GET /backtest/{run_id}/trades gespeicherte Trades
|
||||
GET /backtest/{run_id}/equity Equity-Kurve
|
||||
GET /backtest alle Runs
|
||||
POST /backtest/{run_id}/rebuild (idempotenter Rebuild aus DB)
|
||||
GET /strategies registrierte Strategien + Versionen
|
||||
```
|
||||
|
||||
Kein RabbitMQ: Backtest ist ein synchroner, request/response-Aufruf. Port `55012`
|
||||
ist **nur intern** (`expose:`), kein öffentliches Port-Mapping.
|
||||
|
||||
## Datenquelle
|
||||
- OHLCV direkt aus PostgreSQL (`ohlcv`), Spalten `provider, symbol, timeframe, ts,
|
||||
open, high, low, close, volume`.
|
||||
- Abfrage **nach Timestamp/Zeitraum** (`start_date`..`end_date`), nicht via
|
||||
`/history?limit=N` — vollständige, deterministische Datenbasis.
|
||||
- Mindestanzahl Candles: `min_candles_required` (default 220), sonst Fehler.
|
||||
|
||||
## Bias-Schutz / Realismus (kein Lookahead)
|
||||
- **Entry am OPEN der Folgewandle** nach dem Signal (nie zum bekannten Signal-Close).
|
||||
- **Regime & Strategie** werden nur auf **abgeschlossenen** Candles (bis Candle i)
|
||||
ausgewertet — keine zukünftige Information.
|
||||
- **Intrabar Stop/Target konservativ**: Stop wird VOR Target geprüft (kein Lookahead
|
||||
durch Intrabar-Reihenfolge). Falls beide in einer Candle getroffen, gewinnt Stop
|
||||
(Worst-Case).
|
||||
- **Gap-Handling**: Überspringt der Open den Stop/Target, Fill zum Gap-Open
|
||||
(realistisch schlechter).
|
||||
- **Gebühren/Spread/Slippage** werden auf Entry UND Exit angewendet (verschlechtern
|
||||
den Fill).
|
||||
- **R-Multiple** bezieht sich auf das **Geldrisiko** `qty * |entry − stop|` (nicht
|
||||
Preisdifferenz) — `expectancy_r` daher korrekt (vorheriger Bug: 31431 → korrekt 2.0).
|
||||
|
||||
## Eingaben (POST /backtest)
|
||||
- Symbol, Asset-Klasse, Provider, Timeframe, Start-/Enddatum
|
||||
- Strategy + Version (`trend_pullback_v1`, default `1.0.0`)
|
||||
- Initiales Kapital, Risk % (Risk-basierte Positionsgröße)
|
||||
- Gebühren (fix + %), Spread (Preispunkte), Slippage (Preispunkte)
|
||||
- Optionale Limits: `max_positions`, `max_qty`
|
||||
|
||||
## Eigene Tabellen (Migration `migrations/001_backtest.sql`)
|
||||
- `backtest_run` — Run-Metadaten: run_id, strategy+version, symbol, timeframe,
|
||||
status (COMPLETED/…), data_hash, run_hash, Parameter (JSON), Datenzeitraum.
|
||||
- `backtest_trade` — einzelne Trades: direction (LONG/SHORT), qty, entry/exit-Preis,
|
||||
entry/exit_ts, signal_ts, reason (target/stop/force_close), regime, gross_pnl,
|
||||
fee, pnl, r (R-Multiple), strategy_version.
|
||||
- `backtest_equity` — Equity-Kurve (Zeitstempel, Equity, offene Positionen).
|
||||
|
||||
Jede Tabelle FK auf `backtest_run(run_id) ON DELETE CASCADE`.
|
||||
|
||||
## Determinismus / Reproduzierbarkeit
|
||||
- Eindeutige `run_id` + deterministischer `run_hash` + `data_hash`.
|
||||
- `run_hash`: Hash über Strategy+Version+Parameter+Datenzeitraum+data_hash.
|
||||
- `data_hash`: Hash über die geladenen OHLCV-Daten.
|
||||
- **Gleiche Daten + gleiche Version + gleiche Parameter → identische Trades, Equity,
|
||||
Kennzahlen und identischer run_hash** (E2E-verifiziert).
|
||||
|
||||
## Tests
|
||||
- `tests/test_backtest.py`: 12/12 grün (deterministisch, LONG/SHORT, fees, slippage,
|
||||
lookahead, stop/target, gaps, unzureichende Daten, Strategy-Version/Parameter,
|
||||
Rebuild-Identität).
|
||||
- `tests/fixtures.py`: deterministische Fixture-Factory.
|
||||
- `tests/m12_fixtures.py`: erzeugt E2E-Fixtures (BT_PULL LONG, BT_PULL_S SHORT) für
|
||||
die VPS-ohlcv-Tabelle.
|
||||
- `tests/determinism_check.py` / `determinism_diff.py`: E2E-Determinismus-Nachweis
|
||||
(identische Trades/Equity/Kennzahlen/Hashes).
|
||||
|
||||
## E2E-Verifikation (VPS, 20.08.2026)
|
||||
- **LONG** (BT_PULL, TREND_UP): 1 Trade, winrate 1.0, expectancy_r 1.94, net_pnl
|
||||
196.59, max_drawdown 0.0001, r=1.94, reason=target.
|
||||
- **SHORT** (BT_PULL_S, TREND_DOWN): 1 Trade, short_count 1, net_pnl 194.96.
|
||||
- `signal_ts 15:00 → entry_ts 16:00` (Einstieg am Open der Folgewandle — Lookahead
|
||||
praktisch verifiziert).
|
||||
- run_hash + data_hash gesetzt (Beispiel: `bc6e2553…` / `d76a7549…`).
|
||||
- **Determinismus**: zwei identische Backtests → identischer run_hash, data_hash,
|
||||
Metrics, Trades (normalisiert), Equity-Curve.
|
||||
- **Stop/Target/Gap**: Engine-`_evaluate_exit` geprüft — Gap-Down unter Stop → Fill
|
||||
zum Gap-Open; Target bei high≥target; Stop vor Target (konservativ); SHORT Gap-Up
|
||||
→ Fill zum Gap-Open.
|
||||
- Health `/health` 200, `/health/ready` 200; keine öffentlichen Ports.
|
||||
|
||||
## Compose / Betrieb
|
||||
- Netzwerk `trading-modules`, DB-Host `Modul-01-PostgreSQL` (Modul-01), Port 55012
|
||||
nur intern (`expose`), `restart: unless-stopped`. Kein öffentliches Port-Mapping.
|
||||
|
||||
## Nächste Module
|
||||
- **Modul-13: Optimization** — Image `optimization:0.1.0`, E2E-verifiziert (12/12 Punkte), **FREIGEGEBEN** (20.08.2026). Details: `modul-13-optimization.md`.
|
||||
- **V1.1-Strategie**: `trend_pullback_v1.1` (Image `backtesting:0.1.2`) — Spezifikation und Parameter: `trend-pullback-v1.1-spec.md`.
|
||||
|
||||
---
|
||||
## Geändert
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-12-Backtesting als FREIGEGEBEN markiert (deployt + E2E verifiziert: deterministisch, kein Lookahead, Stop/Target/Gap, LONG/SHORT, run_hash+data_hash).
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-12-Doku aktualisiert — Image auf backtesting:0.1.2 (V1.1), V1-Kompatmodus bitgenau, Referenz auf V1.1-Spec und Modul-13.
|
||||
129
notes/trading/system-docs/modul-13-optimization.md
Normal file
129
notes/trading/system-docs/modul-13-optimization.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# Modul-13: Optimization
|
||||
|
||||
**Status: FREIGEGEBEN ✅** · Container `Modul-13-Optimization` · Image `optimization:0.1.0`
|
||||
|
||||
## Zweck
|
||||
Deterministische, reproduzierbare **Parameteroptimierung** für die V1.1-Strategie
|
||||
(`trend_pullback_v1.1`, Modul-12). Findet robuste Parameter im erlaubten Suchraum
|
||||
**ohne KI/LLM und ohne Live-Orders** — Ergebnis ist nur **CANDIDATE/RECOMMENDATION**,
|
||||
es gibt keine automatische Parameterübernahme in Modul-05.
|
||||
|
||||
## Kernprinzipien
|
||||
- **Deterministisch**: gleiche Eingaben → gleiche Kandidaten, Reihenfolge, run_hash (kein Zufall außer explizit via Seed).
|
||||
- **Kein Lookahead**: IS/OOS strikt zeitlich getrennt; **OOS wird nie zur Selektion verwendet**.
|
||||
- **IS/OOS strikt**: `is_ratio` (z.B. 0.7) teilt die Daten; OOS ausschließlich zur Validierung.
|
||||
- **Robuste Bereiche > Einzelmax**: best = Kandidat mit bester kombinierter Score (nicht max net_pnl).
|
||||
- **Reject-/Overfit-Logik**: zu wenige Trades, DD-Limit, IS/OOS-Degradation → Kandidat wird `rejected`.
|
||||
- **Parametergrenzen zwingend**: Suchraum begrenzt auf `pullback_band_atr`, `trend_regime_window`, `rr_multiplier`.
|
||||
- **Score-Gewichte**: Expectancy/R 0.30, PF 0.25, Max-Drawdown 0.20, Trades 0.10, Stabilität 0.15.
|
||||
- **Synchron**: Request/Response über API (kein RabbitMQ).
|
||||
|
||||
## Architektur / Datenfluss
|
||||
```
|
||||
OHLCV (Modul-01 PostgreSQL, Tabelle ohlcv)
|
||||
│ synchron, direkt nach Timestamp/Zeitraum
|
||||
▼
|
||||
Modul-13-Optimization ── POST /optimization
|
||||
│
|
||||
├── core/split.py IS/OOS + Walk-Forward-Folds (kein Leakage)
|
||||
├── core/optimizer.py Grid/Random-Suche, ScoreEngine, WF, Reject
|
||||
├── core/scoring.py Multi-Metrik-Score (Gewichte siehe oben)
|
||||
├── storage/storage.py Persistenz optimization_run/candidate/walk_forward
|
||||
├── backtest_client.py synchroner Aufruf Modul-12 (POST /backtest)
|
||||
└── api/main.py REST-API (intern, Port 55013)
|
||||
```
|
||||
|
||||
API (Docker-intern, Port 55013, kein öffentliches Port-Mapping):
|
||||
```
|
||||
GET /health, /health/ready
|
||||
POST /optimization Start (Grid/Random), synchron
|
||||
GET /optimization Liste aller Runs
|
||||
GET /optimization/{id} Run-Metadaten + Status
|
||||
GET /optimization/{id}/candidates Kandidaten + Multi-Metrik-Ranking
|
||||
GET /optimization/{id}/best bester ROBUSTER Kandidat (RECOMMENDATION/NO_RECOMMENDATION)
|
||||
GET /optimization/{id}/walk-forward Walk-Forward-Folds
|
||||
```
|
||||
|
||||
## Suchraum (V1.1 — NUR 3 optimierbare Parameter)
|
||||
| Parameter | Suchraum (E2E) | Zweck |
|
||||
|-----------|----------------|-------|
|
||||
| `pullback_band_atr` | {0.2, 0.5, 0.8, 1.0} | ATR-Band um EMA20 (Pullback-Timing) |
|
||||
| `trend_regime_window` | {60, 200, 300} | Fenster für strategie-internes Trend-Regime |
|
||||
| `rr_multiplier` | {1.0, 2.0, 3.0} | Target = Entry + rr×Risiko |
|
||||
|
||||
Fix (nicht optimiert): `atr_period=14`, `regime_vol_override=false`, `min_risk_reward=1.0`,
|
||||
ADX-/EMA-Perioden (14/20/30), `adx_trend_threshold=20`.
|
||||
|
||||
## Profile
|
||||
| Profil | min_trades_is | min_trades_oos | max_drawdown_limit | max_oos_degradation | Zweck |
|
||||
|--------|---------------|----------------|--------------------|--------------------|-------|
|
||||
| **produktion** (Default) | 3 | 1 | 0.30 | 0.5 | Anti-Overfit, Produktion |
|
||||
| **test** (`profile=test`) | 1 | 1 | — | — | nur Mindest-Trade-Schwellen gesenkt für technischen E2E; sonst identisch |
|
||||
|
||||
Das TEST-PROFIL senkt **ausschließlich** die Mindest-Trade-Schwellen (damit ein
|
||||
Sweep über die kalibrierte synthetische Fixture genügend Kandidaten akzeptiert).
|
||||
Produktive Anti-Overfit-Defaults bleiben unverändert. `profile` wird per API-Feld
|
||||
übergeben; explizite Request-Werte (`min_trades_is/oos`) haben Vorrang.
|
||||
|
||||
## Eigene Tabellen (Migration)
|
||||
- `optimization_run` — Run-Metadaten: optimization_id, strategy+version, symbol, timeframe,
|
||||
optimizer_method, optimizer_version, param_space, seed, status, result (JSON), created/completed_at.
|
||||
- `optimization_candidate` — Kandidaten: rank, params, backtest_run_id_is/oos, is_metrics,
|
||||
oos_metrics, score, score_components, rejected, reject_reasons, flags, stability, robustness.
|
||||
- `walk_forward_result` — WF-Folds: fold, params, is_start/end, oos_start/end, is/oos_metrics,
|
||||
score, backtest_run_id_is/oos.
|
||||
|
||||
## Reject-/Overfit- und Recommendation-Verhalten
|
||||
- Kandidat wird `rejected` wenn: zu wenige Trades (min_trades_is/oos), Drawdown über Limit,
|
||||
OOS-Degradation zu hoch (Overfit), fachlich unbrauchbare Metriken (z.B. PF<=0/kein Trades).
|
||||
- `/best` liefert:
|
||||
- `recommendation=RECOMMENDATION` + `best_candidate` wenn ein fachlich brauchbarer robuster Bester existiert.
|
||||
- `recommendation=NO_RECOMMENDATION` (statt leerem `best_candidate`), wenn das Kandidatenfeld
|
||||
fachlich unbrauchbar ist — eine produktive Empfehlung wird nie aus unbrauchbaren Daten erzeugt.
|
||||
|
||||
## Tests
|
||||
- `tests/test_optimization.py`: 12/12 grün (Grid deterministisch, Random-Seed, IS/OOS-Leak,
|
||||
Walk-Forward korrekt + JSON-serialisierbar, Multi-Metrik-Ranking, Reject/Overfit, Robustheit,
|
||||
Suchraum=3 Params, Reproduzierbarkeit, NO_RECOMMENDATION, TEST-PROFIL).
|
||||
|
||||
## E2E-Verifikation (VPS, 20.08.2026)
|
||||
- **Suchraum-Differenzierung** (kalibrierte Fixture `M13_E2E`, 1314 Bars, alle 3 Params unterscheidbar):
|
||||
- `pullback_band_atr`: 0.2→8 Trades, 0.5→9–10, 0.8/1.0→1 Trade.
|
||||
- `trend_regime_window`: bei pba=0.5: 60→9 Trades vs 200/300→10 Trades.
|
||||
- `rr_multiplier`: 1.0→8–10 Trades (net +828…+1046), 2.0/3.0→1 Trade.
|
||||
- **12/12 E2E-Punkte über API bestätigt** (e2e_full.py):
|
||||
Grid deterministisch, Random-Seed identisch, IS/OOS-Leak ausgeschlossen, WF-Folds zeitlich
|
||||
korrekt + DB-persistiert (3 Folds), Multi-Metrik-Ranking, Reject-Logik (36 Kandidaten,
|
||||
30 rejected / 6 accepted), best=robust + RECOMMENDATION, DB-Run/Candidates vollständig,
|
||||
API list_runs, Reproduzierbarkeit (status COMPLETED), NO_RECOMMENDATION bei nur-1-Trade
|
||||
Konfiguration (produktion-Profil).
|
||||
- **Bugfix C**: Walk-Forward-Fold-Ergebnisse enthalten `datetime`-Timestamps → beim
|
||||
`json.dumps` in `complete_run` nicht serialisierbar (422). Fix: ISO-String-Kopie im
|
||||
Run-Result, datetime bleibt für `storage.insert_wf`. Verifiziert (3 WF-Folds in DB).
|
||||
- Health `/health/ready` → `{"status":"ready","db":"ok"}` (M13 und M12). Keine Tracebacks.
|
||||
- Port 55013 nur intern (`expose`), kein Host-Port-Binding (verifiziert).
|
||||
|
||||
## Freigabe
|
||||
- **20.08.2026: Modul-13-Optimization-Service vom Nutzer FREIGEGEBEN ✅**
|
||||
(nach vollständiger E2E-Verifikation: 12/12 API-Punkte, Suchraum-Differenzierung für alle
|
||||
3 Params, TEST-PROFIL, NO_RECOMMENDATION, Bugfixes A/B/C deployt, Tests 12/12 grün).
|
||||
- Keine weiteren technischen Änderungen an Modul-13.
|
||||
|
||||
## Compose / Betrieb
|
||||
- Netzwerk `trading-modules`, DB-Host `Modul-01-PostgreSQL` (Modul-01), Backtest via
|
||||
`Modul-12-Backtesting` (intern). Port 55013 nur intern (`expose`), `restart: unless-stopped`.
|
||||
|
||||
## Nächste Module
|
||||
- **Modul-14 ff.** — noch Platzhalter (alpine), NICHT gestartet.
|
||||
|
||||
---
|
||||
## Geändert
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-13-Optimization dokumentiert — E2E-verifiziert (12/12 Punkte), Suchraum-Diffusion für alle 3 Params, TEST-PROFIL, NO_RECOMMENDATION, Bugfix C (WF-JSON-datetime).
|
||||
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-13 vom Nutzer FREIGEGEBEN — Status auf FREIGEGEBEN gesetzt, Freigabe-Sektion ergänzt. Keine technischen Änderungen.
|
||||
```
|
||||
142
notes/trading/system-docs/modul-14-notification.md
Normal file
142
notes/trading/system-docs/modul-14-notification.md
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
# Modul-14: Notification-Service
|
||||
|
||||
**Status: FREIGEGEBEN ✅** · Container `Modul-14-Notification` · Image `notification:0.1.0`
|
||||
|
||||
## Zweck
|
||||
Zentrale **Benachrichtigungen für Trading- und Systemereignisse**. Konsumiert relevante
|
||||
Events aus den RabbitMQ-Exchanges der Module 05–10 und liefert kompakte, strukturierte
|
||||
Meldungen — **V1 primär über Telegram** (`TelegramProvider`), provider-neutral angelegt.
|
||||
|
||||
**Restriktionen (Nutzer-Vorgabe):**
|
||||
- **keine KI/ML**, **keine Trading-Entscheidungen**, **keine Orders**.
|
||||
- **FAIL-SAFE:** Ein Notification-Ausfall darf die Trading-Pipeline **NIEMALS blockieren**.
|
||||
|
||||
## Architektur / Datenfluss
|
||||
```
|
||||
RabbitMQ (market.signals/.rankings/.risk/.portfolio/.execution/.journal)
|
||||
│ NotificationConsumer (notification.input)
|
||||
▼
|
||||
Modul-14-Notification ── NotificationService ──> RulesEngine + DedupGuard
|
||||
│ │ Spam-Schutz
|
||||
├── providers/ NotificationProvider-ABC → Telegram / Fake
|
||||
├── rules/ RulesEngine (Regeln) + DedupGuard (Dedup/Cooldown/Rate-Limit)
|
||||
├── storage/ Persistenz notification_log (PostgreSQL, Modul-01)
|
||||
├── core/ formatter (kompakte Nachricht) + service (Retry/Backoff)
|
||||
└── api/main.py REST-API (intern, Port 55014)
|
||||
|
||||
Event → RabbitMQ → Modul-14 → notification_log → Provider (Telegram) → Chat
|
||||
```
|
||||
|
||||
## API (Docker-intern, Port 55014, kein öffentliches Port-Mapping)
|
||||
```
|
||||
GET /health liveness
|
||||
GET /health/ready readiness (postgresql, consumer_ready, provider_configured)
|
||||
GET /notifications/recent letzte Notifications
|
||||
GET /notifications/{id} einzelne Notification
|
||||
POST /notifications/test Test-Sendung (nur konfigurierte Destinationen)
|
||||
```
|
||||
|
||||
## Konsumierte Events & Routing-Keys
|
||||
| Event-Typ | Exchange | Routing-Key | Default-Severity | Default aktiv |
|
||||
|-----------|----------|-------------|------------------|---------------|
|
||||
| `SIGNAL_DETECTED` | `market.signals` | `strategy.signal.detected` | INFO | ❌ (deaktiviert) |
|
||||
| `SIGNAL_RANKED` | `market.rankings` | `signal.ranked` | INFO | ❌ (deaktiviert) |
|
||||
| `RISK_APPROVED` | `market.risk` | `risk.approved` | INFO | ❌ (deaktiviert) |
|
||||
| `RISK_REJECTED` | `market.risk` | `risk.rejected` | WARNING | ❌ (deaktiviert) |
|
||||
| `TRADE_APPROVED` | `market.portfolio` | `trade.approved` | INFO | ✅ |
|
||||
| `PORTFOLIO_REJECTED` | `market.portfolio` | `portfolio.rejected` | WARNING | ✅ |
|
||||
| `ORDER_SUBMITTED` | `market.execution` | `order.submitted` | INFO | ❌ (deaktiviert) |
|
||||
| `ORDER_FILLED` | `market.execution` | `order.filled` | INFO | ✅ |
|
||||
| `ORDER_REJECTED` | `market.execution` | `order.rejected` | CRITICAL | ✅ |
|
||||
| `ORDER_FAILED` | `market.execution` | `order.failed` | CRITICAL | ✅ |
|
||||
| `TRADE_RECORDED` | `market.journal` | `trade.recorded` | INFO | ✅ |
|
||||
|
||||
Die Regeln sind **pro Event-Typ konfigurierbar** (aktiv/inaktiv, Severity, Provider,
|
||||
Destination, Cooldown, Rate-Limit). **Unbekannte Event-Typen werden sicher ignoriert**
|
||||
(kein Crash). V1 sind die rauschigen Events (SIGNAL_*, RISK_*, ORDER_SUBMITTED)
|
||||
standardmäßig deaktiviert.
|
||||
|
||||
## Severity-Hierarchie
|
||||
```
|
||||
INFO < WARNING < CRITICAL
|
||||
```
|
||||
Regel-Severity überschreibt den Event-Standard. V1: alle über Telegram.
|
||||
|
||||
## Spam-Schutz (DedupGuard)
|
||||
Mehrstufig, thread-sicher (Lock):
|
||||
1. **Idempotenz** — gleiche `event_id`/`source_event_id` erzeugt nie eine zweite Notification (DB-unique über `source_event_id` + `event_processed`-Check).
|
||||
2. **Dedup** — identische Meldungen (gleicher Text/Event-Typ) in kurzem Fenster werden zusammengefasst.
|
||||
3. **Cooldown** — pro Event-Typ ein Mindestabstand (`cooldown_seconds`, Default 60s).
|
||||
4. **Rate-Limit** — maximale Notifications pro Minute (`rate_limit_per_minute`, Default 30).
|
||||
5. **Burst-Schutz** — Ansturm vieler Events wird aufs Limit begrenzt.
|
||||
|
||||
## Retry / Dead (FAIL-SAFE)
|
||||
- Provider nicht erreichbar → `RETRY_PENDING` mit **begrenzten Retries** (exponentielles Backoff).
|
||||
- `max_attempts` (Default 5) erreicht → **DEAD** (keine Endlosschleife).
|
||||
- **Provider nicht konfiguriert** (fehlender Token/Chat-ID) → **sofort DEAD** mit
|
||||
`error_code=NOT_CONFIGURED` (keine sinnlosen Retries).
|
||||
- Nach Provider-Recovery werden **neue Events wieder normal gesendet** (Retry betrifft nur die jeweilige Notification).
|
||||
|
||||
## Eigene Tabelle (Migration)
|
||||
`notification_log`:
|
||||
`notification_id` (PK), `source_event_id`, `event_type`, `severity`, `provider`,
|
||||
`destination`, `status` (`SENT`/`FAILED`/`RETRY_PENDING`/`DEAD`), `attempts`,
|
||||
`created_at`, `sent_at`, `error_code`, `error_message`.
|
||||
|
||||
## Sicherheit
|
||||
- **KEINE Secrets in DB/Logs/Doku/Commits.** Telegram-Bot-Token/Chat-ID ausschließlich
|
||||
über Environment (Compose `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`), nie hart kodiert.
|
||||
- `/notifications/test` sendet **nur an konfigurierte Destinationen**.
|
||||
|
||||
## Tests
|
||||
- `tests/test_notification.py`: **14/14 grün** (Event→Notification, Idempotenz, Dedup,
|
||||
Cooldown, Rate-Limit, Telegram-Ausfall blockiert nicht, Retry+Backoff, Max→DEAD,
|
||||
keine Secrets, unbekannter Typ ignoriert, Severity/Regeln, FakeProvider, Reconnect).
|
||||
|
||||
## E2E-Verifikation (VPS, 20.08.2026) — alle 7 Punkte grün
|
||||
1. **Fake-Telegram-E2E** (Modul-14 auf `http://fake-telegram:55000`, Test-Token/Chat-ID
|
||||
nur als ENV): echtes `TRADE_RECORDED` publiziert → **Event → RabbitMQ → Modul-14 →
|
||||
notification_log → Fake-Telegram** komplett durchlaufen. DB: `status=SENT`,
|
||||
`sent_at` gesetzt, `attempts=0`, **genau 1 Zustellung, kein Duplikat**. Fake-Telegram
|
||||
RAW: `{"chat_id": "E2E_TEST_CHAT_114", "text": "TRADE RECORDED | NVDA | LONG | OPEN"}`.
|
||||
2. **NO_CONFIG-Fix**: Container ohne Token → Event → `status=DEAD, attempts=1,
|
||||
error_code=NOT_CONFIGURED` (sofortiges DEAD, keine 5 sinnlosen Retries).
|
||||
3. **Fehlerfall-E2E**: Fake-Telegram gestoppt → Notification `RETRY_PENDING` mit
|
||||
Backoff (attempts 2→4, `NETWORK_ERROR`), **Trading-Pipeline unbeeinflusst**,
|
||||
Max-Retries (`attempts=5`) → `DEAD`. Nach Fake-Telegram-Recovery → neues Event
|
||||
wieder `SENT`.
|
||||
4. **RabbitMQ-Reconnect**: kontrollierter Restart → Modul-14 reconnectet automatisch
|
||||
(Backoff 4s→8s→16s, „Consumer verbunden"), **genau 1 Consumer** an `notification.input`,
|
||||
keine Zombies, keine verlorenen/duplizierten Notifications (nach Reconnect: 1 Zustellung, 0 Duplikate).
|
||||
5. **Spam-/Sicherheitschecks** (praktisch): unbekannter Typ `UNKNOWN_EVENT_XYZ` → ignoriert;
|
||||
gleiche `event_id` 2× → nur 1 Notification (Idempotenz); gleicher Event-Typ 2×
|
||||
→ Cooldown greift; deaktivierter `SIGNAL_DETECTED` → keine Notification. **Keine
|
||||
Tokens/Chat-IDs in Logs/API/Doku/DB** (0 Token-Leaks).
|
||||
6. **Health/Readiness**: `/health` + `/health/ready` → 200
|
||||
(`postgresql=true, consumer_ready=true, provider_configured=…`). Keine
|
||||
Applikations-Tracebacks im Normalbetrieb (nur erwartete Reconnect-Logs beim Test).
|
||||
Port 55014 nur intern (`expose`, kein Host-Port).
|
||||
7. **Forgejo-Doku + Commit-ID** (siehe Freigabe unten), temporäre Test-Credentials
|
||||
entfernt, Remote token-/key-frei.
|
||||
|
||||
## Freigabe
|
||||
- **20.08.2026: Modul-14-Notification-Service vom Nutzer FREIGEGEBEN ✅**
|
||||
(nach vollständiger E2E-Verifikation der Punkte 1–7, alle grün).
|
||||
- Keine weiteren technischen Änderungen an Modul-14.
|
||||
|
||||
## Compose / Betrieb
|
||||
- Netzwerk `trading-modules`, DB-Host `Modul-01-PostgreSQL`, RabbitMQ `Modul-02-RabbitMQ`.
|
||||
- Port 55014 nur intern (`expose`), `restart: unless-stopped`, non-root (uid 1001).
|
||||
- Token/Chat-ID via ENV (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`, `TELEGRAM_API_BASE`).
|
||||
|
||||
## Nächste Module
|
||||
- **Modul-15 ff.** — noch Platzhalter (alpine), NICHT gestartet.
|
||||
|
||||
---
|
||||
## Geändert
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-14-Notification-Service dokumentiert — E2E-verifiziert (Punkte 1–7 grün),
|
||||
API/Events/Severity/Retry-Dead/Spam-Schutz/Fake-E2E dokumentiert, Status FREIGEGEBEN.
|
||||
```
|
||||
331
notes/trading/system-docs/modul-15-m09-anbindung-design.md
Normal file
331
notes/trading/system-docs/modul-15-m09-anbindung-design.md
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
# Design-Vorschlag: Modul-15 → Modul-09 Anbindung (Safety-/Trading-Control)
|
||||
|
||||
**Status: NUR DESIGN-VORSCHLAG — keine Implementierung.**
|
||||
**Datum:** 20.08.2026 · **Autor:** Rain Ocampo (Hermes)
|
||||
**Gilt für:** Modul-09-Execution-Service (FREIGEGEBEN, Order-Pfad) ↔ Modul-15-Monitoring-Control (IMPLEMENTIERT + E2E-verifiziert, **noch nicht freigegeben**)
|
||||
|
||||
> **Scope-Regeln (verbindlich):**
|
||||
> - M09 wird **nicht** verändert, bis dieser Vorschlag vom Nutzer freigegeben wurde.
|
||||
> - M15 bleibt **nicht freigegeben** bis Nutzer-Freigabe.
|
||||
> - M16 wird **nicht** begonnen.
|
||||
> - Dieser Vorschlag ist **Architektur/Pseudoflow**, kein Code.
|
||||
|
||||
---
|
||||
|
||||
## 1. Ziel
|
||||
|
||||
**Modul-15** wird die **autoritative Safety-/Trading-Control-Instanz** für den Order-Durchstich.
|
||||
**Modul-09** darf neue Orders **nur senden, wenn M15 dies eindeutig erlaubt**.
|
||||
|
||||
Kernauftrag: kein OPEN/INCREASE, solange M15 nicht *eindeutig* ENABLED bestätigt. Gleichzeitig darf ein HALT **niemals** Risikoreduktion / Position-Schließen verhindern (Exit-Pfade bleiben immer frei).
|
||||
|
||||
---
|
||||
|
||||
## 2. Ist-Zustand in M09 (Kontext für den Vorschlag)
|
||||
|
||||
Der Order-Pfad in `app/core/service.py::_on_event` ist heute:
|
||||
|
||||
```
|
||||
TRADE_APPROVED (Modul-08)
|
||||
→ 1) Idempotenz (already_processed)
|
||||
→ 2) Harte Validierung validate_trade_approved (FAIL-CLOSED)
|
||||
→ 3) Broker-Bereitschaft validate_broker_ready (FAIL-CLOSED)
|
||||
→ 4) Order atomar in DB anlegen (Advisory Lock + Transaktion, PENDING)
|
||||
→ 5) _submit_and_track → BrokerAdapter.submit_order
|
||||
```
|
||||
|
||||
**Heutiger Kill-Switch:** rein konfigurativ `TRADING_ENABLED=false` (Default, sicher für LIVE).
|
||||
Es existiert **keine** externe Echtzeit-Control-Schnittstelle im Order-Pfad.
|
||||
|
||||
**Einbau-Punkt (Vorschlag):** zwischen Schritt 4 (Order angelegt) und Schritt 5 (Broker-Submit)
|
||||
— d. h. **unmittelbar vor `submit_order`**. Alternativ als zusätzlicher Check in Schritt 2.
|
||||
Empfehlung: als eigener Schritt zwischen 4 und 5, weil dort die Order bereits idempotent in der DB
|
||||
liegt und wir den Final-Check „direkt vor dem Senden" platzieren (Race-Window minimal).
|
||||
|
||||
---
|
||||
|
||||
## 3. Trading-States & Regelwerk
|
||||
|
||||
### 3.1 Globale Trading-States (autoritativ: M15)
|
||||
|
||||
| State | Bedeutung | neue OPEN/INCREASE | REDUCE | CLOSE/CANCEL |
|
||||
|-------|-----------|--------------------|--------|--------------|
|
||||
| `ENABLED` | Alle krit. Dienste gesund, aktuelle Market-Data, Trading erlaubt | **ERLAUBT** | erlaubt | erlaubt |
|
||||
| `PAUSED` | Nicht-krit. Störung (Analytics/Notification), Risiko-umfeld unklar | **VERBOTEN** | **ERLAUBT** | **ERLAUBT** |
|
||||
| `HALTED` | Krit. Ausfall (PG/RMQ/Execution/Market-Data/UNKNOWN) | **VERBOTEN** | **ERLAUBT** | **ERLAUBT** |
|
||||
| `UNKNOWN` / M15 nicht erreichbar / Status veraltet | Unklarheit | **VERBOTEN (FAIL-CLOSED)** | **ERLAUBT** (konservativ) | **ERLAUBT** |
|
||||
|
||||
**Kernregel:** Nur `ENABLED` erlaubt neue Positionen (OPEN) oder Aufstockung (INCREASE).
|
||||
**Alles andere** (PAUSED/HALTED/UNKNOWN/nicht erreichbar/veraltet) blockiert OPEN/INCREASE.
|
||||
**REDUCE/CLOSE** (Risikoreduktion/Position schließen) sind **immer** erlaubt — unabhängig vom State.
|
||||
|
||||
### 3.2 Order-Kategorien (Einordnung)
|
||||
|
||||
| Aktion | Bedeutung | Regel bei PAUSED/HALTED/UNKNOWN |
|
||||
|---|---|---|
|
||||
| `OPEN` | Neue Position eröffnen | **BLOCKIEREN** |
|
||||
| `INCREASE` | Bestehende Position aufstocken | **BLOCKIEREN** (Neue-Position-Logik, Risiko wächst) |
|
||||
| `REDUCE` | Position verkleinern | **ERLAUBT** (Risiko sinkt) |
|
||||
| `CLOSE` | Position schließen | **ERLAUBT** (Risiko eliminieren) |
|
||||
| `CANCEL` | Offene/geplante Order stornieren | **ERLAUBT** (kein neues Risiko) |
|
||||
|
||||
> Begründung INCREASE blockiert: Aufstocken fügt neues Marktrisiko hinzu — bei HALT/Unklarheit
|
||||
> unerlaubt. REDUCE/CLOSE/CANCEL sind per Definition risikoreduzierend → immer frei.
|
||||
|
||||
---
|
||||
|
||||
## 4. Vergleich der Anbindungs-Varianten
|
||||
|
||||
### Variante A — synchroner HTTP-Check vor jeder Order
|
||||
|
||||
M09 ruft vor jedem Submit `GET /control/trading-state` auf M15 (Docker-intern) auf.
|
||||
|
||||
| Kriterium | Bewertung |
|
||||
|---|---|
|
||||
| **Sicherheit** | Sehr hoch — liest immer den frischesten autoritativen Zustand. FAIL-CLOSED bei Timeout. |
|
||||
| **Latenz** | Pro Order +1 Round-Trip (ms-Bereich, Docker-Netz). Bei vielen Orders spürbar, aber Orders sind selten. |
|
||||
| **Ausfallsicherheit** | M15-down → HTTP-Time out → FAIL-CLOSED (blockieren). Sicher, aber **M15 wird Single Point of Failure für OPEN**. |
|
||||
| **Race Conditions** | M15 könnte direkt nach Antwort auf HALTED wechseln → Race trotz Check (siehe §7). Final-Check nur minimal schmaler. |
|
||||
| **Single Point of Failure** | **Ja** — M15 ist für OPEN/INCREASE zwingend. Bei M15-down wird der Execution-Service sonst blind blockiert. |
|
||||
| **Bei RabbitMQ-Ausfall** | HTTP bleibt funktionsfähig (separater Kanal) → Check ok; aber M15 meldet RABBITMQ_UNREACHABLE→HALTED → korrekt blockiert. |
|
||||
| **Bei M15-Ausfall** | Timeout → FAIL-CLOSED → OPEN/INCREASE blockiert (gewünscht). REDUCE/CLOSE müssen **ausgenommen** werden, sonst kann Position nicht geschlossen werden. |
|
||||
| **Komplexität** | Gering — ein HTTP-Call, kein Cache, kein Subscription. |
|
||||
| **Nachvollziehbarkeit/Audit** | M15 protokolliert jede Anfrage; M09 loggt Check + Ergebnis. Gut, aber hochfrequent (jede Order). |
|
||||
|
||||
### Variante B — Event-basierter Status-Cache (publish/subscribe)
|
||||
|
||||
M15 publiziert Statusänderungen auf `market.control`; M09 subscribt und hält ein lokales State-Modell.
|
||||
|
||||
| Kriterium | Bewertung |
|
||||
|---|---|
|
||||
| **Sicherheit** | Mittel — Status nur so frisch wie das letzte Event + Cache-TTL; **riskant ohne TTL/Herzschlag**. |
|
||||
| **Latenz** | Sehr gering — kein HTTP im kritischen Pfad, Cache-Lookp. |
|
||||
| **Ausfallsicherheit** | Schlecht — RabbitMQ-Ausfall ⇒ keine Events mehr; Cache wird **stale**. Ohne TTL würde M09 mit veraltetem ENABLED weiter traden. **Gefahr.** |
|
||||
| **Race Conditions** | Stale-Cache ist die größte Race-Quelle. Event-Reihenfolge/Lag. |
|
||||
| **Single Point of Failure** | RabbitMQ als Event-Bus ist der SPOF für den Status-Transport. |
|
||||
| **Bei RabbitMQ-Ausfall** | Events stoppen → Cache veraltet → **genau das Risiko**, das wir vermeiden wollen. Muss über Cache-TTL + Fail-CLOSED gelöst werden. |
|
||||
| **Bei M15-Ausfall** | Keine neuen Events → Cache veraltet. Ohne TTL gefährlich (weiter OPEN). |
|
||||
| **Komplexität** | Mittel — Subscription, Cache-Update, TTL-Reinigung, RabbitMQ-Reconnect im M09. |
|
||||
| **Nachvollziehbarkeit/Audit** | Gut — Event-Stream ist Append-Log; aber Cache-Echo im M09 ist ein zweites Modell (Konsistenzpflege). |
|
||||
|
||||
### Variante C — Kombination: Event-Cache + synchroner Final-Check (EMPFEHLUNG)
|
||||
|
||||
**Event-Cache als schnelle Vorfilterung**, **synchroner HTTP-Final-Check unmittelbar vor dem
|
||||
Broker-Submit** als harte Bestätigung. Nur wer beide klar "ENABLED" liefert, darf OPEN/INCREASE.
|
||||
|
||||
| Kriterium | Bewertung |
|
||||
|---|---|
|
||||
| **Sicherheit** | **Sehr hoch** — zweistufig. Cache als Bauraus/Performance, HTTP-Final als Autorität. |
|
||||
| **Latenz** | Fast so gering wie B im Common-Case, da Vorfilter die meiste Zeit per Cache durchläuft; **aber** der obligatorische Final-Check ist der Latenzdominante Teil (immer +1 HTTP). |In Praxis Orders selten → akzeptabel. |
|
||||
| **Ausfallsicherheit** | Beste — Cache kann "PAUSED/HALTED/UNKNOWN" sofort blockieren; HTTP- Ausfall ⇒ FAIL-CLOSED. Zwei unabhängige Quellen. |
|
||||
| **Race Conditions** | Der synchronier Final-Check (TTL = kurz, unmittelbar vor dem Submit) minimiert die Chance, dass M15 direkt danach HALTED setzt. Plus `control_version`-Bump (§7) zum harten Ausschluss. |
|
||||
| **Single Point of Failure** | M15 bleibt SPOF für OPEN/INCREASE (unvermeidbar, da autoritativ). REDUCE/CLOSE ausgenommen. |
|
||||
| **Bei RabbitMQ-Ausfall** | Cache veraltet → aber Cache-Logik blockiert bei "veraltet/UNKNOWN"; HTTP-Final ist unabhängig vom Bus → liefert korrekte M15-Sicht (die selbst RMQ-Ausfall als HALTED sieht). |
|
||||
| **Bei M15-Ausfall** | HTTP-Final-Check schlägt fehl → FAIL-CLOSED, OPEN/INCREASE blockiert. REDUCE/CLOSE frei. |
|
||||
| **Komplexität** | Höher (beides). Aber gut beherrschbar; Cache als reiner nicht-kritischer Vorfilter, Autorität liegt klar beim HTTP. |
|
||||
| **Nachvollziehbarkeit/Audit** | Exzellent — Final-Check liefert `state_version` + `timestamp` + `audit_id` pro Order; Cache-Zustand separat auditierbar. |
|
||||
|
||||
### Zusammenfassung der Varianten
|
||||
|
||||
| | A (HTTP) | B (Event-Cache) | C (Kombi) |
|
||||
|---|---|---|---|
|
||||
| Sicherheit | hoch | mittel | **sehr hoch** |
|
||||
| Latenz | +HTTP | **sehr gering** | gering (HTTP-Final) |
|
||||
| Ausfallsicherheit | ok (SPOF M15) | **schwach** (RMQ SPOF) | **beste** |
|
||||
| Race-Risiko | minimiert (kurz) | **hoch (stale)** | **minimiert** |
|
||||
| RMQ-Ausfall | korrekt (HALTED) | **risiko (stale)** | korrekt |
|
||||
| M15-Ausfall | FAIL-CLOSED | **risiko (stale)** | FAIL-CLOSED |
|
||||
| Komplexität | gering | mittel | **höher** |
|
||||
| Audit | gut | zweites Modell | **exzellent** |
|
||||
|
||||
**Empfehlung: Variante C** (Event-Cache als Vorfilter + synchroner Final-Check).
|
||||
|
||||
---
|
||||
|
||||
## 5. Race Condition & Lösung
|
||||
|
||||
**Problem:** M09 prüft `ENABLED`, M15 wechselt sofort danach auf `HALTED` — bevor M09 die Order gesendet hat.
|
||||
|
||||
**Lösung (Kombination):**
|
||||
1. **Final-Check unmittelbar vor `submit_order`** — minimales Zeitfenster (ms) zwischen Check und Send.
|
||||
2. **TTL auf der HTTP-Antwort:** `expires_at` (z. B. `now + 500 ms`). Ist die Antwort beim Senden abgelaufen
|
||||
(Fenster überschritten), NICHT senden → Order nach `FAILED/REJECTED` mit `CONTROL_STATE_STALE` markieren.
|
||||
3. **`state_version`** (Monoton aufsteigend, von M15 je Status-Änderung). M09 übernimmt die im Final-Check
|
||||
gesehene Version. Beim Broker-Submit trägt die Order die `control_version`; die Audit-Kette verbindet
|
||||
genau die Version, die gegolten hat, mit dem Versand.
|
||||
4. **Bestätigtes Einverständnis:** Der Check liefert `trading_status=ENABLED` **und** `state_version` **und**
|
||||
`expires_at`. Nur wenn beides vorliegt und innerhalb der TTL ist, darf OPEN/INCREASE durchgehen.
|
||||
5. **Audit-ID / Correlation:** M09 erzeugt `control_audit_id`, M15 protokolliert die Bestätigung. Damit ist
|
||||
der „wir haben im ENABLED-Zustand gesendet" Moment exakt nachvollziehbar.
|
||||
|
||||
**Kein "Senden dann hoffen":** Ist das Fenster überschritten oder Status unklar → `FAILED` mit
|
||||
`CONTROL_STATE_UNKNOWN/CONTROL_STATE_STALE`, **niemals** blind erneut senden (konsistent zum bestehenden
|
||||
M09-Time-out-Handling: erst Status beim Broker klären, dann entscheiden).
|
||||
|
||||
---
|
||||
|
||||
## 6. API-Vertrag M15 → M09
|
||||
|
||||
### 6.1 `GET /control/trading-state` (von M09 im Final-Check)
|
||||
|
||||
**Request:** keiner (oder optional `?action=OPEN` / `?for_action=OPEN` zur Kontext-Auditierung)
|
||||
|
||||
**Response 200 (ENABLED):**
|
||||
```json
|
||||
{
|
||||
"trading_status": "ENABLED", // ENABLED|PAUSED|HALTED|UNKNOWN
|
||||
"system_status": "HEALTHY", // Kontext
|
||||
"state_version": 42, // monoton, von M15 je Änderung
|
||||
"timestamp": "2026-08-20T15:30:00Z", // Erzeugzeit (UTC, ISO 8601)
|
||||
"expires_at": "2026-08-20T15:30:00.5Z", // TTL-Horizont (Server), z. B. now+500ms
|
||||
"ttl_seconds": 0.5,
|
||||
"audit_id": "ctl-..." // M15-interne Audit-Kennung der Bestätigung
|
||||
}
|
||||
```
|
||||
**Nur** dieses Objekt mit `trading_status=ENABLED` und `expires_at` in der Zukunft gilt als "eindeutige Erlaubnis".
|
||||
|
||||
**Response (blockiert):** gleiches Schema, aber `trading_status` = `PAUSED`/`HALTED`/`UNKNOWN`.
|
||||
|
||||
**Fehler/Timeouts:**
|
||||
- HTTP 503 → M15 selbst nicht Ready/unbekannt → **FAIL-CLOSED** (als HALTED/unbekannt behandeln).
|
||||
- Timeout/5xx/netz → **FAIL-CLOSED**, als `UNKNOWN` behandeln.
|
||||
- Alle nicht-`ENABLED`-Antworten → OPEN/INCREASE **verweigert**.
|
||||
|
||||
### 6.2 Empfehlung der aktuellen API-Form
|
||||
|
||||
M15 hat aktuell `GET /control/trading-state` (liefert `trading_status` + `system_status`). Für M09-Anbindung
|
||||
empfehle ich die obige **Erweiterung** um `state_version`, `timestamp`, `expires_at`, `audit_id` — dies ist ein
|
||||
**additiver API-Vertrag** (kein Breaking Change). Das ist im Vorschlag berücksichtigt; die konkrete
|
||||
Umsetzung erfolgt erst nach Freigabe.
|
||||
|
||||
---
|
||||
|
||||
## 7. Event-Schema für Status-Änderungen (market.control)
|
||||
|
||||
M15 publiziert auf Exchange `market.control`, Routing `trading.state` — **nur bei Status-Änderung** (keine Schleife).
|
||||
|
||||
```json
|
||||
{
|
||||
"event_id": "evt-...",
|
||||
"event_type": "trading.state",
|
||||
"timestamp": "2026-08-20T17:30:00Z",
|
||||
"version": 1,
|
||||
"payload": {
|
||||
"trading_status": "HALTED", // ENABLED|PAUSED|HALTED|UNKNOWN
|
||||
"system_status": "UNHEALTHY",
|
||||
"state_version": 43,
|
||||
"reason_codes": ["POSTGRESQL_UNREACHABLE", "PERSISTENCE_FAILURE"],
|
||||
"changed_at": "2026-08-20T17:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Im Cache (Variante C):**
|
||||
- M09 hält `last_state` (trading_status + state_version + received_at).
|
||||
- **TTL (z. B. 5 s):** ist `received_at` älter als TTL → Cache behandelt als `UNKNOWN/veraltet` → OPEN/INCREASE
|
||||
**blockiert** (auch wenn letztes Event `ENABLED` war).
|
||||
- Ein Event `state_version` älter als bereits gesehen → ignorieren (kein Regress).
|
||||
|
||||
---
|
||||
|
||||
## 8. Verhalten nach Recovery
|
||||
|
||||
- **Kein automatisches RESUME:** M15 nimmt nach Behebung der Ursache Status selbst neu (z. B. HALTED→HEALTHY→ENABLED)
|
||||
**nur durch den regulären Monitoring-Zyklus** (Ursache wirklich erneut geprüft). Es gibt kein verstecktes Auto-Resume.
|
||||
- M09 wartet darauf: erst wenn ein **neues Event mit `trading_status=ENABLED` und neuer `state_version`** oder
|
||||
ein **HTTP-Final-Check mit `ENABLED`** vorliegt, sind OPEN/INCREASE wieder möglich.
|
||||
- Ein `PAUSED→ENABLED`-Übergang verlangt, dass M15 nachweislich die früheren HALT-Gründe entfernt hat
|
||||
(keine Reason-Codes mehr). Dies wird durch die bestehende FAIL-CLOSED-Regel + idempotentes Event sichergestellt.
|
||||
- **Cache-TTL-Disziplin:** Nach einem HALTED/PAUSED-Ereignis bleibt M09 vorsichtig: Es traut dem Cache erst
|
||||
wieder, wenn ein neues `ENABLED`-Event (hohe Version) eingegangen ist — niemals basierend auf Zeitablauf der
|
||||
Sperre, sondern auf **bestätigter Freigabe**.
|
||||
|
||||
---
|
||||
|
||||
## 9. Audit in M09 (zusätzlich zu M15-Audit)
|
||||
|
||||
Für jede Order-Entscheidung (im M09-Storage bzw. `execution_event`):
|
||||
- `control_status`: Ergebnis des Final-Checks (`ENABLED`/`PAUSED`/...)
|
||||
- `control_state_version`: Version, die beim Send gegolten hat
|
||||
- `control_expires_at` / `control_ttl`: TTL-Horizont
|
||||
- `control_audit_id`: vom Final-Check zugeordnet
|
||||
- `control_check_source`: `final_http` (und optional `cache`)
|
||||
|
||||
Damit ist jede Order exakt dem autoritativen Zustand zum Sendzeitpunkt zugeordnet (Audit-Kette vollständig).
|
||||
|
||||
---
|
||||
|
||||
## 10. Zusammenfassung Architektur-Empfehlung
|
||||
|
||||
**Variante C**: Event-Cache (mark-to-control, TTL, state_version) als **nicht-kritischen Vorfilter** im M09 +
|
||||
**synchronier HTTP-Final-Check** (`GET /control/trading-state`, mit `expires_at`+`state_version`+`audit_id`)
|
||||
unmittelbar vor `submit_order`. Beide müssen `ENABLED` und frisch sein; sonst OPEN/INCREASE blockiert
|
||||
(FAIL-CLOSED). REDUCE/CLOSE/CANCEL sind von der Control-Regel ausgenommen und laufen immer.
|
||||
|
||||
- **Zustand, der OPEN/INCREASE erlaubt:** Cache == `ENABLED` (frisch) UND Final-Check == `ENABLED` (frisch).
|
||||
- **Sonst alles:** blockiert OPEN/INCREASE; REDUCE/CLOSE/CANCEL frei.
|
||||
- **FAIL-CLOSED:** M15 nicht erreichbar, Timeout, Cache veraltet, `UNKNOWN` → OPEN/INCREASE verweigert.
|
||||
- **M15 bleibt SPOF nur für OPEN/INCREASE** (unvermeidbar, autoritativ). Exit/Close bleibt immer möglich.
|
||||
|
||||
---
|
||||
|
||||
## 11. Pseudoflow (Order-Durchstich M09)
|
||||
|
||||
```
|
||||
TRADE_APPROVED (Modul-08)
|
||||
│
|
||||
▼
|
||||
1. Kategorisiere Aktion: OPEN | INCREASE | REDUCE | CLOSE | CANCEL
|
||||
│
|
||||
├─ action ∈ {REDUCE, CLOSE, CANCEL} → ERLANG DURCHSTICH (kein Control-Check)
|
||||
│ └─→ weiter zum bestehenden Submit-Pfad
|
||||
│
|
||||
└─ action ∈ {OPEN, INCREASE}
|
||||
│
|
||||
▼
|
||||
2. [Cache-Vorfilter] local_state = cache.get()
|
||||
├─ local_state ist stale (age > TTL) → → zum Final-Check (unten) aber NICHT aus Cache ableiten
|
||||
└─ local_state.trading_status != ENABLED → BLOCKIERT (markiere FAILED/REJECTED, CONTROL_STATE_*)
|
||||
│
|
||||
▼
|
||||
3. [Final-Check] resp = M15 GET /control/trading-state (Time out: 800ms, Retry: 1×)
|
||||
├─ Timeout/5xx → FAIL-CLOSED → BLOCKIERT (FAILED, CONTROL_STATE_UNKNOWN)
|
||||
└─ resp.trading_status != ENABLED → BLOCKIERT (FAILED, CONTROL_STATE_*)
|
||||
└─ resp.expires_at <= now → BLOCKIERT (FAILED, CONTROL_STATE_STALE)
|
||||
▼
|
||||
4. Zustand grün (ENABLED, frisch, version=resp.state_version)
|
||||
→ Order in DB (PENDING, CONTROL_FIELDS: version, audit_id, expires)
|
||||
▼
|
||||
5. unmittelbar danach: broker.submit_order(request) ← nur zwischen 4 und 5 minimales Fenster
|
||||
▼
|
||||
6. Ergebnis verarbeiten (FILLED/REJECTED/FAILED), Audit-Event mit control_* schreiben
|
||||
```
|
||||
|
||||
### Zeit-/Retry-Parameter (Vorschlag)
|
||||
- Final-Check-HTTP-Timeout: **500 ms** (Docker-intern, schnell); Retry: **max 1** sofort.
|
||||
- `expires_at`-TTL (M15): **500 ms** (Server-seitig gesetzt).
|
||||
- Nach Timeout/unklar: **nicht senden**; Order als `FAILED`/`REJECTED` mit `CONTROL_STATE_UNKNOWN/STALE`.
|
||||
- **Kein Auto-Resume**, kein "dann schicken wir eben ohne Check".
|
||||
|
||||
### Warum REDUCE/CLOSE/CANCEL durchlaufen
|
||||
- Ziel des Control ist **Risiko-Management**. Ein HALT darf Risiko niemals einfrieren — im Gegenteil,
|
||||
muss das System bei Problemen schneller in der Lage sein, Positionen abzubauen.
|
||||
- `REDUCE`/`CLOSE`/`CANCEL` senken/eliminieren Risiko → **immer erlaubt**, unabhängig von M15-Status.
|
||||
|
||||
---
|
||||
|
||||
## 12. Offene Punkte für die Nutzer-Freigabe
|
||||
|
||||
1. **API-Vertrag erweitern** (`state_version`, `expires_at`, `ttl_seconds`, `audit_id`) in M15 (additiv).
|
||||
2. **M09-Check-Position:** zwischen Schritt 4 (Order in DB) und 5 (Broker-Submit) — bestätigen.
|
||||
3. **TTL-Wert** (`expires_at`-Fenster) festschreiben (Vorschlag 500 ms).
|
||||
4. **Kategorisierung** der Order-Aktion in M09 (OPEN/INCREASE/REDUCE/CLOSE/CANCEL) ableiten — M09 muss
|
||||
aus dem TRADE_APPROVED die Aktion bestimmen (Vorschlag: über `direction`/Kanten-Art der Entscheidung).
|
||||
5. **M03–M06-Klassifikation** (Market-Data-Pipeline): ob Einzel-Ausfall dieser Module wirklich HALTED
|
||||
auslösen soll, oder nur deren Daten-Staleness — zu finalisieren, beeinflusst WEN oft HALTED greift.
|
||||
6. **SPOF-Akzeptanz:** M15 ist für OPEN/INCREASE zwingend (autoritativ). Exit-Pfad bleibt frei.
|
||||
7. M15 weiterhin **nicht freigeben**, M16 **nicht beginnen** bis Nutzer-Entscheid.
|
||||
|
||||
---
|
||||
|
||||
*Dies ist ein Design-Vorschlag. Es wurde kein Code geändert. M15 nicht freigegeben. M16 nicht begonnen.*
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
# 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).*
|
||||
146
notes/trading/system-docs/modul-15-monitoring-control.md
Normal file
146
notes/trading/system-docs/modul-15-monitoring-control.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Modul-15: Monitoring-Control
|
||||
|
||||
**Status: FREIGEGEBEN** (20.08.2026) · Container `Modul-15-Monitoring-Control` · Image `monitoring-control:0.1.0`
|
||||
|
||||
## Zweck
|
||||
Zentrale **technische Überwachung und Sicherheits-/Kontrollebene** des Trading-Systems.
|
||||
Überwacht die Dienste M03–M14 (Health/Readiness/Erreichbarkeit) plus RabbitMQ-Queues/Consumer
|
||||
und PostgreSQL und leitet daraus deterministisch einen **globalen Trading-Status**
|
||||
(`ENABLED` / `PAUSED` / `HALTED` / `UNKNOWN`) ab.
|
||||
|
||||
**Restriktionen (Nutzer-Vorgabe):**
|
||||
- **Keine KI/ML**, **keine Trading-Entscheidungen**, **keine Brokerorders**, **keine Shell-/Docker-Control-Rechte**.
|
||||
- **FAIL-CLOSED:** Bei unklarem/kritischem Zustand wird Trading sicher blockiert (`HALTED`).
|
||||
- M15 unerreichbar → M09 behandelt den Zustand als `HALTED` (FAIL-CLOSED, siehe M15→M09-Anbindung, FREIGEGEBEN).
|
||||
- **Keine Event-Schleifen:** Events nur bei Status-Änderung, dedupliziert.
|
||||
- Container wird **nicht** automatisch neu gestartet/gekillt — M15 beobachtet und entscheidet Status.
|
||||
- Port 55015 **nur Docker-intern** (`expose`, kein Host-Port).
|
||||
|
||||
## Architektur / Datenfluss
|
||||
```
|
||||
M03–M14 (health/ready) RabbitMQ (Queues) PostgreSQL
|
||||
│ │ │
|
||||
└──────────┬────────────┴──────────────┘
|
||||
▼
|
||||
Modul-15-Monitoring-Control
|
||||
├── probe/ MonitoringProbe (HTTP-health + RMQ passive declare + PG)
|
||||
├── rules/ MonitoringRules (deterministisch, FAIL-CLOSED, versioniert)
|
||||
├── core/ MonitorService (Status-Engine, Single Source of Truth)
|
||||
├── storage/ Persistenz (PostgreSQL: system_health/service_health/control_event)
|
||||
├── publisher/ ControlPublisher (market.control, nur bei Status-Änderung)
|
||||
└── api/main.py REST-API (intern, Port 55015)
|
||||
```
|
||||
Status → `system_health` (append), Services → `service_health` (upsert), Änderungen → `control_event` (append-only Audit).
|
||||
|
||||
## API (Docker-intern, Port 55015, kein öffentliches Port-Mapping)
|
||||
```
|
||||
GET /health liveness
|
||||
GET /health/ready readiness (postgresql)
|
||||
GET /monitoring/status aktueller System-Status + Trading-Status (internes State-Modell)
|
||||
GET /monitoring/services aktueller Zustand aller überwachten Services/Queues
|
||||
GET /control/status Control-Status (manual_override, control_api_enabled)
|
||||
GET /control/trading-state autoritativer Trading-Status für M09
|
||||
POST /control manuelle Control PAUSE/HALT/RESUME (nur wenn CONTROL_API_ENABLED=true)
|
||||
```
|
||||
|
||||
## Deterministische Regeln (Rules-Engine, Version 0.1.0)
|
||||
Präzedenz strikt von oben nach unten (FAIL-CLOSED):
|
||||
1. **UNKNOWN** — PostgreSQL nicht erreichbar → nicht verlässlich → `HALTED`.
|
||||
2. **UNHEALTHY/HALTED** — kritische Infrastruktur (PG/RabbitMQ) oder Execution-Pfad down.
|
||||
3. **DEGRADED/PAUSED** — nicht-kritische Dienste down / stale.
|
||||
4. **HEALTHY/ENABLED** — alles gut.
|
||||
|
||||
| Bedingung | System | Trading | Reason-Code |
|
||||
|---|---|---|---|
|
||||
| PostgreSQL nicht erreichbar | UNHEALTHY | HALTED | `POSTGRESQL_UNREACHABLE` (+ `PERSISTENCE_FAILURE`) |
|
||||
| RabbitMQ nicht erreichbar | UNHEALTHY | HALTED | `RABBITMQ_UNREACHABLE` |
|
||||
| Kritischer Service down (M03–M09) | UNHEALTHY | HALTED | `CRITICAL_SERVICE_DOWN:<id>` |
|
||||
| Market Data stale (> max_age) | UNHEALTHY | HALTED | `MARKET_DATA_STALE` |
|
||||
| Kritische Queue Consumer fehlt | — | HALTED | `NO_CONSUMER_<queue>` |
|
||||
| Kritischer Queue-Backlog ≥1000 | — | HALTED | `QUEUE_BACKLOG_CRIT:<queue>:<n>` |
|
||||
| Nicht-kritische Unhealthy (Analytics/Notification) | DEGRADED | PAUSED | `CRITICAL_SERVICE_DOWN`/`NO_CONSUMER_…` |
|
||||
| Queue-Backlog ≥100 / Warning | DEGRADED | PAUSED | `QUEUE_BACKLOG:<queue>:<n>` |
|
||||
| Alles healthy | HEALTHY | ENABLED | — |
|
||||
|
||||
**FAIL-CLOSED-Hinweis:** `UNKNOWN` Trading-Status wird in der API auf `HALTED` gemappt (nie ENABLED aus unklarem Zustand).
|
||||
|
||||
## Events (market.control Exchange, keine Event-Schleife)
|
||||
Events werden **nur bei Status-Änderung** publiziert (Idempotenz) — nie bei jedem Poll:
|
||||
|
||||
| Event-Typ | Routing-Key | Anlass |
|
||||
|---|---|---|
|
||||
| `system.degraded` | `system.degraded` | System → DEGRADED |
|
||||
| `system.halted` | `system.halted` | System → UNHEALTHY (HALT) |
|
||||
| `system.recovered` | `system.recovered` | System → HEALTHY |
|
||||
| `trading.state` | `trading.state` | Trading-Status-Änderung |
|
||||
| `service.unhealthy` | `service.unhealthy` | Einzelner Service UNHEALTHY |
|
||||
|
||||
## Persistenz / Audit (PostgreSQL Modul-01)
|
||||
- `system_health` — append-only Zeile pro Beobachtung (aktueller Status + reason_codes).
|
||||
- `service_health` — Upsert pro `service_id` (aktueller Zustand jedes Services/Queue).
|
||||
- `control_event` — **append-only Audit-Trail** jeder Status-/Trading-Änderung (`STATE_CHANGE`) und manuellen Control-Aktion (`MANUAL_*`). Idempotent — eine Änderung wird **genau einmal** auditiert.
|
||||
|
||||
## Manuelle Control (PAUSE/HALT/RESUME)
|
||||
- **Nur wenn `CONTROL_API_ENABLED=true`** (Default `false` → manuelle Control über API deaktiviert, nur Regel-getrieben).
|
||||
- **RESUME wird verweigert** (`RESUME_BLOCKED_CRITICAL_CAUSE`), solange eine kritische Ursache besteht.
|
||||
- Jede manuelle Aktion wird append-only in `control_event` auditiert.
|
||||
|
||||
## Sicherheit
|
||||
- **Keine Secrets in DB/Logs/Doku/Commits** — Zugangsdaten ausschließlich via Environment (Compose).
|
||||
- **Kein Docker-Socket**, **keine Shell-/Docker-Control-Rechte** (CMD=`python main.py`).
|
||||
- Port 55015 nur intern (`expose`), `restart: unless-stopped`, non-root (uid 1001).
|
||||
- Env: `POSTGRES_*`, `RABBITMQ_*`, `CONTROL_API_ENABLED`.
|
||||
|
||||
## Compose / Betrieb
|
||||
- Netzwerk `trading-modules`, DB-Host `Modul-01-PostgreSQL`, RabbitMQ `Modul-02-RabbitMQ`.
|
||||
- Env per Compose, Zugangsdaten nur via ENV (`POSTGRES_*`, `RABBITMQ_*`), nie hart kodiert.
|
||||
|
||||
## Unit-Tests
|
||||
- `tests/test_monitoring.py`: **19/19 grün** — deterministische Rules, FAIL-CLOSED (PG/RabbitMQ/Critical), stale, Queue-Consumer, Idempotenz, Audit genau einmal, Recovery, Reconnect, keine Secrets, `state_version`-Inkrementierung.
|
||||
|
||||
## E2E-Verifikation (VPS, 20.08.2026) — 17 Szenarien
|
||||
Alle 17 verifiziert (System-/Trading-Status via Loop, API `/control/trading-state` + `/monitoring/status` konsistent):
|
||||
1. **Normalzustand:** `HEALTHY/ENABLED` — API, State-Modell, Loop identisch.
|
||||
2. **PG-Ausfall:** `HALTED`, `POSTGRESQL_UNREACHABLE`+`PERSISTENCE_FAILURE`, Audit genau einmal.
|
||||
3. **RabbitMQ-Ausfall:** `HALTED`, `RABBITMQ_UNREACHABLE`.
|
||||
4. **Risk-Ausfall:** `HALTED`, `CRITICAL_SERVICE_DOWN:modul-07-risk-manager`+`NO_CONSUMER_risk.input`.
|
||||
5. **Portfolio-Ausfall:** `HALTED`, `CRITICAL_SERVICE_DOWN:modul-08-portfolio-manager`.
|
||||
6. **Execution-Ausfall:** `HALTED`, `CRITICAL_SERVICE_DOWN:modul-09-execution-service`.
|
||||
7. **Analytics-Ausfall:** `DEGRADED` + `PAUSED` (Trading nicht vollständig kill).
|
||||
8. **Notification-Ausfall:** `DEGRADED` + `PAUSED`.
|
||||
9. **Market Data stale:** `HALTED`, `MARKET_DATA_STALE`; Recovery nach frischen Daten.
|
||||
10. **Kritischer Consumer fehlt:** HALT-Severity korrekt (NO_CONSUMER auf kritischer Queue).
|
||||
11. **Queue-Backlog:** Warning/HALT gemäß Schwelle (100/1000, Unit-getestet).
|
||||
12. **Recovery:** Ursache erneut geprüft, kein Auto-RESUME solange Ursache, Status konsistent, kein Event-Spam.
|
||||
13. **Manuelle Controls:** mit `CONTROL_API_ENABLED=false` sauber abgelehnt (`control_api_disabled`); Trading unangetastet.
|
||||
14. **Idempotenz/Event-Flut:** stabiler Zustand → keine wiederholten SYSTEM_HALTED/SERVICE_UNHEALTHY (nur 1 Audit je Änderung).
|
||||
15. **RabbitMQ-Reconnect:** automatisch (alle Recovery-Zyklen), keine Zombies, keine Eventverluste.
|
||||
16. **Audit:** `control_event`/`system_health`/`service_health` vollständig + zeitlich nachvollziehbar.
|
||||
17. **Security:** kein öffentlicher Port (PortBindings `{}`), kein Docker-Socket, keine Secrets im Compose/Logs.
|
||||
|
||||
## Bekannte Design-Hinweise (für separaten M15→M09-Vorschlag)
|
||||
- **M03–M06 (Market-Data-Pipeline) sind in `CRITICAL_SERVICES`** klassifiziert. Ausfall eines einzelnen Daten-Moduls führt so zu `HALTED` (über `CRITICAL_SERVICE_DOWN`), nicht ausschließlich über `MARKET_DATA_STALE`. Das ist fachlich konservativ aber zu klären (nur Data-Pipeline vs. Execution-Pfad).
|
||||
- **DEGRADED → `PAUSED`** (nicht `ENABLED`): bei Analytics/Notification-Ausfall wird Trading vorsichtig gestoppt, nicht vollständig killed. Design-Entscheidung, im Vorschlag benannt.
|
||||
- **PG-Ausfall ⇒ `PERSISTENCE_FAILURE`**: ohne PG kann `control_event`-Audit nicht geschrieben werden (fachlich korrekt FAIL-CLOSED), aber Audit-Lücke im Fehlerfenster.
|
||||
|
||||
## Freigabe
|
||||
- **FREIGEGEBEN** (20.08.2026, Nutzer-Bestätigung). Implementierung + E2E (17 Szenarien) grün.
|
||||
- M09-Anbindung **umgesetzt** (Variante C, Event-Cache + HTTP-Final-Check) — siehe `modul-15-m09-anbindung-implementierung.md`, FREIGEGEBEN.
|
||||
|
||||
## Nächste Module
|
||||
- **Modul-16 ff.** — noch Platzhalter (alpine), NICHT gestartet.
|
||||
|
||||
---
|
||||
## Geändert
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-15-Monitoring-Control dokumentiert — implementiert + E2E-verifiziert (17 Szenarien grün),
|
||||
Rules/API/Events/Audit/Security dokumentiert, NICHT FREIGEGEBEN.
|
||||
```
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-15-Monitoring-Control auf FREIGEGEBEN gesetzt (Nutzer-Bestätigung 20.08.2026).
|
||||
M15→M09-Anbindung (Variante C) als umgesetzt/FREIGEGEBEN vermerkt; Unit-Tests 19/19.
|
||||
```
|
||||
59
notes/trading/system-docs/ports-reference.md
Normal file
59
notes/trading/system-docs/ports-reference.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Ports & Service-Referenz — Trading-System VPS
|
||||
|
||||
> Stand: 20.08.2026 · VPS: `187.124.31.123` · SSH: Public-Key-Auth (nur)
|
||||
|
||||
## Netzwerk & Core-Infrastruktur
|
||||
- **Netzwerk:** `trading-modules` (bridge) — Kommunikation über Docker-interne Service-Hostnamen, keine festen IPs.
|
||||
- **Host:** Hostinger-VPS, 300 GB Disk, ~31 GiB RAM.
|
||||
|
||||
## Feste Port-Zuordnung (Schema `55NNN` nach Modulnummer)
|
||||
| Modul | Container | Service | Host-Port | Öffentlich? |
|
||||
|-------|-----------|---------|-----------|-------------|
|
||||
| 01 | Modul-01-PostgreSQL | PostgreSQL | **keiner (intern, `expose`)** | **nein** (nur intern) |
|
||||
| 02 | Modul-02-RabbitMQ | RabbitMQ AMQP | **keiner (intern, `expose`)** | **nein** (nur intern) |
|
||||
| 02 | Modul-02-RabbitMQ | RabbitMQ Management | **keiner (intern, `expose`)** | **nein** (nur intern) |
|
||||
| 03 | Modul-03-Market-Data | FastAPI | 55003 (intern, `expose`) | **nein** (nur intern) |
|
||||
| 04 | Modul-04-Market-Regime | FastAPI | 55004 (intern, expose) | nein (nur intern) |
|
||||
| 05 | Modul-05-Strategy-Engine | FastAPI | 55005 (intern, expose) | nein (nur intern) |
|
||||
| 06–12 | Modul-06…12 | FastAPI | 55006–55012 (intern, expose) | nein (nur intern) |
|
||||
| 13 | Modul-13-Optimization | FastAPI | 55013 (intern, expose) | nein (nur intern) |
|
||||
| 14–17 | … | — | 55014–55017 | (Platzhalter) |
|
||||
|
||||
> **SECURITY-FIX 20.08.2026:** Modul-01 (PostgreSQL) und Modul-02 (RabbitMQ) haben **keine Host-Port-Bindings** mehr. Die ehemaligen öffentlichen Ports **55432, 55672, 15672 sind geschlossen** und von außen nicht mehr erreichbar (verifiziert). Adminzugriff nur noch per SSH-Tunnel ins `trading-modules`-Netz oder `docker exec`. Alles läuft über `expose:` → nur im internen Docker-Netzwerk.
|
||||
|
||||
## Weitere Dienste (Host)
|
||||
| Dienst | Container | Port |
|
||||
|--------|-----------|------|
|
||||
| Forgejo | forgejo-c4u8yyi1eaz1gepn3pqmr5fb | 3000 (HTTP) / 22222 (SSH) |
|
||||
| Tolaria (Second Brain) | tolaria | 5173 |
|
||||
| OpenClaw Alice | openclaw-suqw-openclaw-1 | 55163 |
|
||||
| OpenClaw Matt | openclaw-3sgu-openclaw-1 | 54524 |
|
||||
| Hermes Rain | hermes-workspace-e6un… | 32776 (UI) |
|
||||
| Ollama | ollama-nb6d-ollama-1 | 11434 (intern) |
|
||||
| n8n | n8n | (Coolify-managed) |
|
||||
| Traefik | traefik | 80/443 |
|
||||
|
||||
## Docker-interne Hostnamen (wichtig für Container-Kommunikation)
|
||||
- PostgreSQL: `Modul-01-PostgreSQL:5432`
|
||||
- RabbitMQ: `Modul-02-RabbitMQ:5672` (vhost `trading`)
|
||||
- Ollama: `ollama-nb6d-ollama-1:11434`
|
||||
|
||||
## Sicherheits-Notizen
|
||||
- **Kein Modul-01/02/03-Port ist öffentlich** — alle nur im Docker-Netzwerk erreichbar (via `expose`, nicht `ports`). Verifiziert: `curl` auf 55432/55672/15672/55003 schlägt von außen fehl, während Modul-03 intern weiterhin PostgreSQL & RabbitMQ erreicht.
|
||||
- Zugangsdaten ausschließlich als Env-Variablen/Secrets, nie im Code.
|
||||
- Öffentliche Ports nur, wo nötig (Admin/Debug/UI).
|
||||
|
||||
---
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: Modul-01/02 von öffentlichen Ports auf interne `expose`-Bindings umgestellt (Security-Fix), Doku aktualisiert.
|
||||
```
|
||||
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: ports-reference aktualisiert — Modul-04 und Modul-05 von Platzhalter auf real (FastAPI, intern expose, nicht öffentlich) eingetragen.
|
||||
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: ports-reference aktualisiert — Modul-06–12 als real (FastAPI, intern) und Modul-13 Optimization (55013 intern) eingetragen.
|
||||
115
notes/trading/system-docs/trend-pullback-v1.1-spec.md
Normal file
115
notes/trading/system-docs/trend-pullback-v1.1-spec.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# trend_pullback_v1.1 — Spezifikation
|
||||
|
||||
> Status: **IMPLEMENTIERT + DEPLOYT** (`backtesting:0.1.2`, Modul-12), V1-Kompatibilitätsmodus bitgenau.
|
||||
> Erstellt: 20.08.2026 (Rain Ocampo) · Aktualisiert: 20.08.2026
|
||||
|
||||
## 1. Motivation
|
||||
|
||||
Die Produktions-Strategie `trend_pullback_v1` (Modul-05/12) ist unter den
|
||||
**unveränderten** produktiven Regime-Schwellen fachlich widersprüchlich
|
||||
(messbar): Ein TREND_UP-Regime verlangt Volatilität, der Pullback
|
||||
`close < EMA20` verlangt ein flaches Plateau. Im 60-Bars-Produktionsfenster
|
||||
(`regime_lookback=60`) schneiden sich `TREND_UP` und `close<EMA20` nur in
|
||||
**1–4 von 1620** Kerzen (m13_evidence.py, Messwerte 20.08.2026). Die
|
||||
Vol-Override (`HIGH_VOL`/`LOW_VOL`) kippt das Regime bei jedem sinnvollen
|
||||
Pullback (>95% der `close<EMA20`-Kerzen werden `LOW_VOL`).
|
||||
|
||||
**V1.1 trennt Richtung und Pullback-Timing.** Richtung wird aus einem
|
||||
strategie-internen, längeren Fenster berechnet (nicht durch Vol-Override
|
||||
kippbar); Volatilität fließt nur noch ins Pullback-Timing (ATR-Band um EMA20)
|
||||
ein.
|
||||
|
||||
## 2. Kernentscheidungen
|
||||
|
||||
1. **V1.1 bleibt zustandslos.**
|
||||
Trendrichtung wird **jede Bar neu** aus dem aktuellen Fenster berechnet.
|
||||
**Kein Sticky-State.** Kein Hidden-State. Begründung: einfacher, transparenter
|
||||
und reproduzierbarer (deterministisch nach Seed/Replay).
|
||||
|
||||
2. **Kein kurzfristiger Vol-Override auf die Richtung.**
|
||||
`regime_vol_override=false` (V1.1-Default): Die Trend-Richtung wird
|
||||
strategie-intern berechnet und **nicht** durch `HIGH_/LOW_VOLATILITY`
|
||||
überschrieben. Volatilität beeinflusst nur das Pullback-Timing (Band).
|
||||
|
||||
3. **Optimierungsraum für V1.1 bewusst klein.** Erste Iteration optimiert
|
||||
ausschließlich `pullback_band_atr`, `trend_regime_window`, `rr_multiplier`.
|
||||
ADX-/EMA-Perioden und `atr_period` bleiben **fix** auf produktiven V1-Werten.
|
||||
|
||||
## 3. Pseudo-Regel (Variante A, lookahead-frei)
|
||||
|
||||
Strategie-intern, jede Bar, aus geordneten `prev_*`-Serien (kein Lookahead,
|
||||
kein versteckter Zustand):
|
||||
|
||||
```
|
||||
# --- Trend-Richtung (Fenster = trend_regime_window) ---
|
||||
ema_fast_l = EMA(prev_closes, ema_fast_period, lookback=trend_regime_window)
|
||||
ema_slow_l = EMA(prev_closes, ema_slow_period, lookback=trend_regime_window)
|
||||
adx_l = ADX(prev_highs, prev_lows, prev_closes, adx_period, lookback=trend_regime_window)
|
||||
|
||||
trend_dir = UP wenn (ema_fast_l > ema_slow_l) UND adx_l >= adx_trend_threshold
|
||||
trend_dir = DOWN wenn (ema_fast_l < ema_slow_l) UND adx_l >= adx_trend_threshold
|
||||
sonst: kein Trade # KEIN atr_ratio-Check für die Richtung
|
||||
|
||||
# --- Pullback-Timing (ATR-Band um EMA20) ---
|
||||
atr_prev = ATR(prev_highs, prev_lows, prev_closes, atr_period)
|
||||
ema20 = EMA20(prev_closes)
|
||||
pullback_limit = ema20 + pullback_band_atr * atr_prev # LONG
|
||||
pullback_limit = ema20 - pullback_band_atr * atr_prev # SHORT
|
||||
|
||||
LONG wenn trend_dir==UP UND last_close>sma200 UND last_close < pullback_limit
|
||||
UND last_close>prev_close UND swing_low<last_close UND rr>=min_risk_reward
|
||||
SHORT wenn trend_dir==DOWN UND last_close<sma200 UND last_close > pullback_limit
|
||||
UND last_close<prev_close UND swing_high>last_close UND rr>=min_risk_reward
|
||||
```
|
||||
|
||||
- `break_high` entfällt (Befund: `prev_high ≥ prev_close` ⇒ `break_high ⇒ close_up`;
|
||||
bleibt als `close_up`/`close_down` erhalten — nicht restriktiv).
|
||||
- Deterministisch: reine Listen-Berechnung, kein Zufall, kein Hidden-State.
|
||||
|
||||
## 4. Parameter-Tabelle
|
||||
|
||||
| Parameter | V1 produktiv (Code) | V1.1 Default | Min | Max | Zweck | V1.1 Optimierbar? |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `pullback_band_atr` | — (strikt `< EMA20`) | **0.35** | 0.0 | 1.0 | ATR-Band um EMA20. `0.0`=strikt V1. | **Ja** |
|
||||
| `atr_period` | 14 | **14** | 14 (fix) | 14 (fix) | ATR-Fenster für Pullback-Band | Nein (fix) |
|
||||
| `trend_regime_window` | 60 (geteilter 60er) | **200** | 60 | 500 | Fenster für strategie-internes Trend-Regime | **Ja** |
|
||||
| `regime_vol_override` | true (geteilt) | **false** | — | — | false=V1.1 strategie-eigene Richtung | Nein (fix) |
|
||||
| `rr_multiplier` | 2.0 | **2.0** | 1.0 | 4.0 | Target = Entry + rr_multiplier×Risiko | **Ja** |
|
||||
| `min_risk_reward` | 1.0 | **1.0** | 1.0 (min) | 3.0 | Mindest-RR; keine Kandidaten mit Chance/Risiko <1 | Nein (fix) |
|
||||
|
||||
**Zusätzliche Trend-Parameter (produktiv, V1.1 fix — NICHT von Modul-13 optimieren):**
|
||||
|
||||
| Parameter | V1 produktiv (Code) | V1.1 | Zweck |
|
||||
|---|---|---|---|
|
||||
| `adx_trend_threshold` | **20.0** (config.py:54) | fix 20.0 | ADX-Schwelle für echten Trend |
|
||||
| `adx_period` | **14** (config.py:50) | fix 14 | ADX-Indikatorfenster |
|
||||
| `ema_fast_period` | **20** (config.py:36) | fix 20 | Fast-EMA (Strategie-Pullback-Basis) |
|
||||
| `ema_slow_period` | **30** (config.py:47) | fix 30 | Slow-EMA (Regime-Flanke) |
|
||||
|
||||
## 5. V1-Kompatibilitätsmodus (bitgenau V1)
|
||||
|
||||
```
|
||||
pullback_band_atr = 0.0
|
||||
regime_vol_override = true
|
||||
trend_regime_window = 60
|
||||
+ alle übrigen V1-Parameter auf produktiven Defaults
|
||||
```
|
||||
⇒ muss V1 **bitgenau** reproduzieren (Rückwärtskompatibilität; Backtest ohne
|
||||
`strategy_params` bleibt fachlich identisch). **E2E-verifiziert**: V1-Kompatmodus
|
||||
LONG+SHORT bitgenau (net 200.0, target r=2.0).
|
||||
|
||||
## 6. Abgrenzung / No-Go
|
||||
|
||||
- **Keine KI/LLM** in V1.1, keine Live-Orders.
|
||||
- Produktions-Regime-Schwellen (`adx_trend_threshold=20`, `trend_min_ema_gap=0.02`,
|
||||
`high_vol_atr_ratio=0.03`, `low_vol_atr_ratio=0.008`) werden **nicht verändert**.
|
||||
- `atr_period`, ADX-/EMA-Perioden, `min_risk_reward` werden in dieser ersten
|
||||
Optimierungsrunde **nicht** über Modul-13 optimiert.
|
||||
|
||||
---
|
||||
## Geändert
|
||||
```
|
||||
Geändert von: Rain Ocampo
|
||||
Datum: 20.08.2026
|
||||
Grund: V1.1-Spec von ENTWURF auf DEPLOYT aktualisiert (backtesting:0.1.2); V1-Kompatmodus bitgenau E2E-verifiziert.
|
||||
```
|
||||
Loading…
Reference in a new issue