Zum Hauptinhalt springen

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 (siehe Gateway_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.

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

ToolWas muss mindestens gespeichert werden?Warum?
MLflowRun mit Tags (run_id, model_candidate_version, backtest_spec_version, dataset_version), Metriken (OOS + Folds), Artefakte (Report, Folds, Stress), Verweise auf model_uriReproduzierbarkeit & Nachvollziehbarkeit
Prefectflow_run_id, Status, Logs, Parameter, Retry-HistorieOperative Transparenz
OpenMetadataLineage: training_dataset_versionmodel_candidate_versionbacktest_report_version, Owner/Tags, Links zu MLflow-ArtefaktenAudit-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), optional universe_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

Diagramm wird geladen …
  • 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.