From 2ff8ecaf528c512a3a0fa0411857a11ae0b307fe Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sun, 2 Aug 2026 20:54:50 +0800 Subject: [PATCH] =?UTF-8?q?perf(metrics):=20rolling=20alpha/beta=20?= =?UTF-8?q?=E5=90=91=E9=87=8F=E5=8C=96(O(n=C2=B2)=E2=86=92O(n),=E6=B2=BB?= =?UTF-8?q?=E5=88=86=E9=92=9F=E7=BA=A7=E5=8D=A1=E6=AD=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compute_metrics rolling alpha/beta 原 for 循环每点 iloc[:i+1].cov/var = O(n²)。日线 111 天没事,分钟级 daily_df(万行)卡死(cta_c287f4b5 卡 5min CPU88%)。改 pandas expanding 向量化(cov/var/mean O(n)),数值与原循环一致(beta/alpha diff<1e-15,NaN 位置匹配)。pytest 6 绿。触发:b2c41c7 降级让 compute_metrics 首次在 benchmark 有数据时真跑(此前 benchmark 空被 skip),暴露 rolling O(n²) 性能 bug。 --- sanguo_backtest/metrics.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/sanguo_backtest/metrics.py b/sanguo_backtest/metrics.py index c3a54d4..cd26a3b 100644 --- a/sanguo_backtest/metrics.py +++ b/sanguo_backtest/metrics.py @@ -69,15 +69,13 @@ def compute_metrics( equity = empyrical.cum_returns(s) bench_curve = empyrical.cum_returns(b) # rolling alpha/beta (expanding window 用于画图,口径由 scalars 保证) - roll_beta = pd.Series(index=s.index, dtype=float) - roll_alpha = pd.Series(index=s.index, dtype=float) - for i in range(len(s)): - sub = aligned.iloc[: i + 1] - if len(sub) >= 2 and sub["b"].var() > 0: - beta = sub["s"].cov(sub["b"]) / sub["b"].var() - alpha = sub["s"].mean() - beta * sub["b"].mean() - roll_beta.iloc[i] = beta - roll_alpha.iloc[i] = alpha * period + # 向量化 expanding (O(n)) 替代原 for 循环 (O(n²))——分钟级 daily_df(万行)原循环卡死 + # (cta_c287f4b5 卡 5min CPU88% 根因)。expanding.cov/.var/.mean 数值与原循环一致 + # (ddof=1);var_b<=0(含单点 NaN)处 beta=NaN,与原 len>=2 且 var>0 守卫等价。 + cov_sb = aligned["s"].expanding().cov(aligned["b"]) + var_b = aligned["b"].expanding().var() + roll_beta = cov_sb / var_b.where(var_b > 0) + roll_alpha = (aligned["s"].expanding().mean() - roll_beta * aligned["b"].expanding().mean()) * period # drawdown: 从峰值回落 (值 <= 0) cummax = equity.cummax() drawdown = (equity - cummax) / cummax