C3I FINAL INDIVIDUAL DECISIONS: - modul-12: CONTROLLED ROOT<->CANONICAL RECONCILIATION + PAIRED MIGRATION. Canonical auf aktuellen Root-Stand (inkl. Phase 10e Intrabar/Gap Execution) gebracht, Root unveraendert, atomare Unit, derived_from=Root-ID. - modul-18/19: ROOT-ONLY MIGRATION (kein Canonical-Partner). - vps.md: NICHT migriert/löschen/archivieren, DEFERRED_STUB_CLEANUP dokumentiert. Body byte-genau. IDs stabil. Knowledge schema v1. (Red Queen)
9.9 KiB
| knowledge_schema | id | type | role | representation | state |
|---|---|---|---|---|---|
| 1 | object/302e9929-e186-c930-2406-ad4a8f17c6fd | arch | module | source | current |
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), Spaltenprovider, 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_rdaher korrekt (vorheriger Bug: 31431 → korrekt 2.0).
Eingaben (POST /backtest)
- Symbol, Asset-Klasse, Provider, Timeframe, Start-/Enddatum
- Strategy + Version (
trend_pullback_v1, default1.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+ deterministischerrun_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_exitgeprü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
/health200,/health/ready200; keine öffentlichen Ports.
Compose / Betrieb
- Netzwerk
trading-modules, DB-HostModul-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(Imagebacktesting:0.1.2) — Spezifikation und Parameter:trend-pullback-v1.1-spec.md.
Phase 10e — Intrabar Execution + Gap Execution Realism (23.08.2026)
Versionierung der Gap-/Intrabar-Semantik (KEIN separates gap_execution_version):
- Träger:
intrabar_policy+intrabar_policy_version = "intrabar_policy_v1"imExecutionContext(shared/historical/execution_context.py). Dadurch ist die neue Semantik eindeutig versioniert und ändert den V2-run_hashbei Policy-Wechsel (PESSIMISTIC ≠ OPTIMISTIC → anderer run_hash). - Legacy-Pfad bleibt unangetastet (PESSIMISTIC-Default, identisches Verhalten).
Intrabar-Policy (nur V2/BID_ASK): PESSIMISTIC | OPTIMISTIC | STOP_FIRST |
TARGET_FIRST | TICK_RESOLUTION | UNKNOWN. Fail-closed §18: UNKNOWN/
TICK_RESOLUTION/BOGUS → ValueError/NotImplementedError, kein Auto-Default.
Gap-Semantik (deterministisch, kein Lookahead):
- LONG: Stop/Target/Exit auf BID; SHORT auf ASK.
- Gap-Stop → Fill zum Gap-Open (schlechter, NICHT auf Stop-Level geclampt).
- Target-Gap → Fill zum besseren Gap-Open (nicht geclampt auf Target).
- Intrabar-Target füllt auf
target(nicht low/high); Intrabar-Ambiguity (Stop UND Target in einer Bar) per Policy (PESSIMISTIC→Stop, OPTIMISTIC→Target). - Slippage wird adversial auf den tatsächlichen Fill (Trigger ≠ Market ≠ Final Fill bei Gap + Slippage); Commission auf finalem Fill (rate × qty × fill pro Seite).
- Kein Doppelspread/Slippage/Commission (Vier-Ebenen-Trennung 10b/10c/10d/10e).
Audit (18 Felder, forensisch rekonstruierbar): reason, intrabar_policy,
trigger_price, market_execution_price, exit_fill_price, gap_execution,
gap_open_price, execution_model, price_basis, spread_model, slippage_model,
slippage_value, cost_model, entry_fee, exit_fee, gross_pnl, net_pnl
(verwendete vorhandene Feldnamen, keine künstlichen).
Verifikation 23.08.2026:
- Unit-Suite
test_phase10e_intrabar_gap.py: 40/40 grün (Intrabar, Gap, Slippage nach Gap, Commission auf Final Fill, BID/ASK-Seiten, Doppelzählung, Audit-18, pathologische Fälle, 4 numerische PnL-E2E-Beispiele). - Fixture-E2E (
test_phase10e_fixture_e2e.py): 7/7 grün (A LONG Stop-Gap, B SHORT Stop-Gap, C Intrabar PESSIMISTIC, D Intrabar OPTIMISTIC run_hash-Differenz, E LONG Target-Gap, F SHORT Target-Gap, ENV-RESET); numerische Kette Trigger → Gap-Open → Slippage → Final Fill → Commission → Gross → Net nachgewiesen. - Regression Lauf 1 + Lauf 2: vollständig grün (M12 169 inkl. 10e, Phase5 20,
Phase6 14, Phase7 A–J Legacy-Hash
8a5760ae…, M13 Shared A–J, M13 Compat 5/5). - Legacy Production Smoke 2×: A==B==VORHER (run_hash
bc6e2553…, data_hashd76a7549…, net_pnl 196.586062, 1× LONG target). DB-Wahrheit verifiziert (backtest_run/backtest_trade konsistent). Legacy unverändert durch Phase 10e. - Production Gates M12+M13:
HISTORICAL_DATA_SOURCE/ALLOW_FIXTURE_DATAUNSET. - Deployt:
core/engine.py,core/service.py,api/schemas.py(Container-Hashes aktualisiert);shared/historical/execution_context.py+pricing.pyunverändert.
Geändert
Geändert von: Rain Ocampo
Datum: 23.08.2026
Grund: Modul-12-Doku um Phase 10e (Intrabar Execution + Gap Realism) ergänzt — Versionierung, Intrabar-Policy, Gap-Semantik, Audit-18, Verifikation, Deploy, Smoke, Production Gates.
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.