fix(backtest): max drawdown now correctly measures peak-to-trough percentage

Previous formula was: max(absolute_drawdown) / initial_capital, which
exceeds 100% when the portfolio grows then drops (e.g. from 600k to 300k
on a 100k initial = 300% drawdown, which is nonsensical).

Fixed to use drawdown_pct (drawdown / peak) which is always in [0, 1].
This correctly measures the maximum percentage drop from the highest
equity peak, matching the standard financial definition.

Also added regression test: test_max_drawdown_never_exceeds_100_pct.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
GitHub
2026-06-09 19:00:11 +08:00
co-authored by Claude Opus 4.8
parent 5550702620
commit 46298e68d7
3 changed files with 37 additions and 6 deletions
+1 -1
View File
@@ -289,7 +289,7 @@ class BacktestEngine:
BacktestResult with empty DataFrames
"""
perf = PerformanceAnalyzer(
pd.DataFrame(columns=["total", "drawdown"]),
pd.DataFrame(columns=["total", "drawdown", "drawdown_pct"]),
pd.DataFrame(columns=["direction", "pnl", "rejected"]),
).compute()
+3 -3
View File
@@ -94,9 +94,9 @@ class PerformanceAnalyzer:
n = len(daily_ret)
annual_return = (1 + total_return) ** (self.ANNUAL_DAYS / n) - 1
# 3. 最大回撤
max_drawdown_value = np.max(drawdown)
max_drawdown = max_drawdown_value / total[0] if total[0] != 0 else 0
# 3. 最大回撤(从峰值的最大跌幅百分比,0~1 之间)
drawdown_pct = self._equity_curve["drawdown_pct"].to_numpy()
max_drawdown = float(np.max(drawdown_pct))
# 4. 最大回撤持续时间
max_dd_duration = self._compute_max_dd_duration(total, drawdown)