trading-system-docs/modul-12-backtesting.md

6.1 KiB
Raw Blame History

Modul-12: Backtesting

Status: FREIGEGEBEN · Container Modul-12-Backtesting · Image backtesting:0.1.0

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 — aktuell alpine-Platzhalter (Port 55013). NICHT gestartet.

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).