Backtesting
Kurzzusammenfassung
- Backtesting ist der Robustheits- und Umsetzbarkeitsnachweis, bevor ein Model Candidate in Richtung Portfolio Construction / Promotion weitergegeben wird.
- Die Methodik ist walk-forward (zeitbasiert) und enthält Leakage-Guards, Kosten-/Umsetzbarkeitsmodellierung sowie Regime- und Stress-Tests.
- Alle Resultate werden reproduzierbar als Artefakte und Referenzen in MLflow, Prefect und OpenMetadata abgelegt.
- Das Ergebnis liefert eine klare Entscheidung für den nachgelagerten Gate im Gesamtprozess:
pass/hold/fail(sieheGateway_Acceptance).
Einordnung in die Gesamtarchitektur
Backtesting sitzt im BPMN nach Model Training + HPO und vor Portfolio Construction. Es ist die Stelle, an der ein Model Candidate nicht nur „technisch OK“ ist (Training-QA), sondern unter realistischen Annahmen (PIT-Daten, Execution-Delay, Kosten, Regimes) geprüft wird.
Upstream / Inputs:
- Scheduling:
/docs/orchestration-prefect/scheduling - Ingestion & Validierung (ELT + DQ Gate):
/docs/orchestration-prefect/data-pipeline - Feature Pipeline:
/docs/orchestration-prefect/feature-pipeline - Label Construction (Training Dataset):
/docs/data/label-construction - Model Training + HPO & MLflow Konventionen:
/docs/ml-lifecycle-mlflow/experiments
Downstream / Outputs:
- Portfolio Construction:
/docs/research-risk/portfolio-construction - Entscheidungskriterien / Gate-Logik:
/docs/research-risk/acceptance-criteria - Approval / Review Prozess:
/docs/research-risk/approval-process
BPMN Bezug:
- Haupt-ID:
CallActivity_BacktestValidation - Gate/Entscheidung:
Gateway_Acceptance
Ziel
Business-Ziel: Fehl-Promotions reduzieren (Overfitting, Leakage, nicht umsetzbare Strategien), indem wir den Kandidaten out-of-sample und kosten-/liquiditätsbereinigt prüfen.
Engineering-Ziel: Ein Backtesting-Run ist reproduzierbar und auditierbar:
- Inputs sind über Versionen referenzierbar (
model_candidate_version,backtest_spec_version,dataset_version/ PIT-Market-Data-Versionen). - Outputs sind als Artefakte + Contracts gespeichert (Report, Metriken, Parameter, Logs).
Scope und Abgrenzung
In Scope
- Walk-forward / zeitbasierte Validierung (rolling / expanding)
- Leakage-Guards (PIT-Konsistenz, „no future joins“, Embargo/Purge, Delay-Modell)
- Strategie-Simulation (Signal → Gewichtung → Trades → PnL), inkl.:
- Transaktionskosten & Slippage
- Turnover
- Basis-Constraints (z. B. max position, gross/net exposure)
- Benchmark-Vergleich
- Regime- und Stress-Tests (volatil, illiquid, Kosten-Schock, Feature-Ausfall)
- Evidenz & Artefakt-Logging (MLflow, Prefect, OpenMetadata)
Out of Scope
- Ingestion / Provider Zugriff / DQ-Quarantine-Handling (passiert upstream)
- Purpose/DQ Gates (passieren upstream; Backtesting setzt „approved inputs“ voraus)
- Live Monitoring / Drift / Alerting in Production (siehe Observability/Serving Kapitel)
- Portfolio Construction Optimizer-Feintuning (das ist ein eigener BPMN-Schritt)
Inputs und Contracts
Backtesting ist absichtlich contract-driven: Ein Run muss ohne „Hidden State“ wiederholbar sein.
Input Contract: Model Candidate (aus Model Training + HPO)
Minimal benötigt Backtesting folgende Felder (weitere Felder sind erlaubt, aber nicht zwingend):
model_candidate_version: "mc-2026-02-23_1234_abcd"
registered_model_name: "banana-rocket_alpha_ranker"
model_version: 17
model_uri: "models:/banana-rocket_alpha_ranker/17"
training_dataset_version: "td-2026-02-20_0830_ef01"
training_spec_version: "train-spec-v3"
feature_set_version: "fs-2026-02-20_0800_98aa"
label_spec_version: "label-spec-v2"
dataset_version: "curated-2026-02-20"
run_id: "run-2026-02-23_1105"
pipeline_id: "ml-platform"
pipeline_version: "v1.8.0"
mlflow_run_ref: "mlflow:/experiments/42/runs/abcd1234"
openmetadata_entity_ref: "om:/mlModel/banana-rocket_alpha_ranker@17"
Input Contract: Run Context
Backtesting läuft in derselben Logik wie die vorgelagerten Flows:
run_id: "run-2026-02-23_1105"
trigger_reason: "candidate_ready"
pipeline_id: "ml-platform"
pipeline_version: "v1.8.0"
as_of_date: "2026-02-20" # Referenz-/Snapshot-Datum (z. B. für Universe/Feature-Stand)
universe_id: "CH_EQ_LARGE" # oder analog eurer Universe-Definition
mlflow_experiment_name: "Backtesting/CH_EQ_LARGE"
Input Contract: Backtest Spec
Der Backtest selbst ist über eine versionierte Spezifikation steuerbar. Das ist bewusst pragmatisch gehalten: Ein YAML im Repo ist oft ausreichend.
backtest_spec_version: "bt-spec-v1"
evaluation:
start: "2018-01-01"
end: "2026-01-31"
frequency: "daily"
signal_to_portfolio:
mode: "rank_long_short" # z. B. rank_long_only | rank_long_short | threshold
rebalance: "weekly" # daily | weekly | monthly
top_k: 100
bottom_k: 100
execution:
delay_bdays: 1 # execution delay (entscheidend gegen Look-ahead)
trading_calendar: "SIX"
costs:
model: "simple_tc" # simple_tc | advanced (optional)
bps_per_turnover: 5
slippage_bps: 2
constraints:
max_position_weight: 0.02
max_gross_exposure: 1.0
max_net_exposure: 0.1
benchmarks:
primary: "SMI"
stress_tests:
enabled: true
scenarios:
- "cost_shock_x2"
- "liquidity_haircut"
- "feature_outage_10pct"
metrics:
primary_objective: "IR" # konsistent zur Acceptance Criteria
additional:
- "max_drawdown"
- "turnover"
- "hit_rate"
- "alpha_vs_benchmark"
Datenbasis
Backtesting braucht historisch:
- PIT-Features / Signal-Inputs (indirekt über
feature_set_version/training_dataset_version) - PIT-Market Data (Prices/Returns, Corporate Actions, ggf. FX/Benchmark) aus der Curated Zone
- Diese Inputs sollten ebenfalls versioniert oder mindestens über
dataset_version/ Snapshots referenzierbar sein.
- Diese Inputs sollten ebenfalls versioniert oder mindestens über
Methodik
1) Point-in-Time Konsistenz und Leakage-Guards
Backtesting ist nur so gut wie seine Leakage-Disziplin. Minimal-Guards (pragmatisch, aber effektiv):
- Execution Delay: Signale, die auf t berechnet werden, dürfen frühestens auf t+delay gehandelt werden.
- Feature Availability: Es dürfen nur Features verwendet werden, die zum Entscheidungszeitpunkt verfügbar sind (PIT).
- No Future Joins: Keine rückwirkend korrigierten Stammdaten ohne PIT-Versionierung.
- Split Isolation: Normalisierung/Standardisierung/Feature-Engineering wird pro Fold korrekt angewandt (keine Leakage über die Zeit).
Empfehlung: Leakage-Guards als eigener Prefect-Task mit hartem Fail bei kritischen Findings.
2) Walk-forward (zeitbasierte) Auswertung
Ziel ist nicht „ein schöner Gesamt-IR“, sondern Stabilität über Zeit.
Standard-Setup (MVP):
- Rolling oder Expanding Windows
- 4–12 Folds (je nach Datenverfügbarkeit / Rebalance-Frequenz)
- Pro Fold:
- Training-/Calibration-Fenster (falls nötig)
- Testfenster (OOS) mit PnL/Return, Risk, Turnover, Kosten
Optional (wenn ihr es braucht):
- Purge/Embargo (insbesondere relevant bei überlappenden Label-Horizons)
- Regime-spezifische Auswertung pro Fold (z. B. volatil vs. ruhig)
3) Signal → Portfolio → Trades → PnL
Backtesting in dieser Architektur ist nicht nur Model-Metrik, sondern Strategie-Simulation:
- Signal: Modell-Output (Score/Rank/Probabilities) auf historischer Zeitachse
- Portfolio Mapping: Regelwerk (rank/threshold/optimizer), versioniert über Backtest Spec
- Execution:
- Delay
- Kostenmodell (Turnover-basiert, optional Spread/Slippage)
- Optional: Participation/ADV-Limits (wenn vorhanden)
- Output:
- Daily PnL / Returns
- Exposure- und Turnover-Zeitreihen
- Trade-Listen (mindestens aggregiert)
4) Regime- und Stress-Tests
Regime/Stress müssen nicht „überdimensioniert“ sein – aber gezielt:
Regime-Segmente (MVP):
- Bull / Bear (z. B. Benchmark-Trend)
- High-Vol / Low-Vol (z. B. Rolling Vol)
- Optional: Low-Liquidity (wenn ihr Liquiditätsdaten habt)
Stress-Szenarien (pragmatische Defaults):
- Kosten-Schock (z. B.
bps_per_turnover ×2) - Slippage-Schock
- Feature-Outage (z. B. 10 % Features → Missing/Neutral)
- Execution Delay erhöhen (z. B. +1 Handelstag)
Ziel: Kandidaten, die nur in „happy path“ gut aussehen, sollen vor der Promotion aussortiert werden.
Outputs, Artefakte und Nachweis
Output Contract: Backtest Report
Backtesting produziert eine versionierte Report-Referenz (Artefakt + Metadaten). Ein Beispiel:
backtest_report_version: "btr-2026-02-23_1200_7788"
model_candidate_version: "mc-2026-02-23_1234_abcd"
backtest_spec_version: "bt-spec-v1"
dataset_version: "curated-2026-02-20"
universe_id: "CH_EQ_LARGE"
decision: "pass" # pass | hold | fail
decision_reason: "IR stable across folds; costs within budget; no leakage findings"
summary_metrics:
IR_oos: 0.62
max_drawdown: -0.11
turnover_avg: 0.35
artifacts:
report_uri: "mlflow:/.../artifacts/backtest_report.html"
folds_metrics_uri: "mlflow:/.../artifacts/folds_metrics.parquet"
stress_report_uri: "mlflow:/.../artifacts/stress_tests.json"
trades_uri: "mlflow:/.../artifacts/trades_summary.parquet"
mlflow_run_ref: "mlflow:/experiments/84/runs/efgh5678"
prefect_flow_run_ref: "prefect:/flow-runs/..."
openmetadata_entity_ref: "om:/backtestReport/btr-2026-02-23_1200_7788"
Evidenz in Tools
| Tool | Was muss mindestens gespeichert werden? | Warum? |
|---|---|---|
| MLflow | Run mit Tags (run_id, model_candidate_version, backtest_spec_version, dataset_version), Metriken (OOS + Folds), Artefakte (Report, Folds, Stress), Verweise auf model_uri | Reproduzierbarkeit & Nachvollziehbarkeit |
| Prefect | flow_run_id, Status, Logs, Parameter, Retry-Historie | Operative Transparenz |
| OpenMetadata | Lineage: training_dataset_version → model_candidate_version → backtest_report_version, Owner/Tags, Links zu MLflow-Artefakten | Audit-fähige Herkunft (ohne Overhead) |
Entscheidung: pass / hold / fail
Backtesting liefert die Entscheidungsgrundlage, die im Gesamtprozess in Gateway_Acceptance einfliesst.
- pass: Kandidat erfüllt die Kriterien → darf in Richtung Portfolio Construction / Promotion weiter.
- hold: Grenzfall → gezielte Nacharbeit (z. B. Kostenannahmen, Delay, Regime-Weakness) + erneuter Backtest.
- fail: Verletzung von Hard-Guards (Leakage, Kostenbudget, Drawdown-Limit etc.) → zurück in Iteration.
Die konkreten Schwellenwerte und Gate-Regeln sind zentral definiert unter:
/docs/research-risk/acceptance-criteria
Pragmatischer Hinweis für ein 2–3 Personen-Team:
Ein hold ist kein „Gremium“ – es ist eine dokumentierte, fokussierte Entscheidung: Was muss geändert werden, und welche Evidenz erwarten wir beim Re-Run?
Das kann sauber in einem Issue/PR + MLflow-Link dokumentiert werden.
Operationalisierung in Prefect
Backtesting ist typischerweise event-driven (getriggert durch Model Candidate Ready), nicht primär „cron-basiert“.
Empfohlenes Setup:
- Prefect Flow:
backtesting_validation - Parameter:
model_candidate_version,backtest_spec_version,dataset_version(PIT), optionaluniverse_id - Parallelisierung: pro Fold / pro Stress-Szenario (sofern Compute vorhanden)
- Idempotenz: gleicher Input → gleicher Output (neuer Run nur, wenn bewusst Re-Run)
Backtesting sollte keine upstream Gates wiederholen (Purpose/DQ), sondern die eigene Aufgabe erfüllen: Robustheit & Umsetzbarkeit nachweisen.
BPMN-Kontext
- IDs:
CallActivity_BacktestValidation - Input-Bezug: Kandidatenmodell (
model_uri/model_candidate_version), PIT-Market-Data (Curated/Snapshots), Backtest Spec. - Entscheidungsbezug: Ergebnis speist
Gateway_Acceptance(Regeln siehe/docs/research-risk/acceptance-criteria). - Output-Bezug: Backtest Report (
pass/hold/fail) + Artefakte/Lineage für Review und Approval.