绩效指标交叉验证(回测/perf_audit/)
约 90s(import quantstats 26s + portfolio.load_daily 39s 是主要开销)。
绩效指标交叉验证(回测/perf_audit/)
用途:用第三方实现 quantstats 复算我们对外的全部绩效指标,证明手写实现没有系统性错误。
为什么需要它:本仓库因为手写绩效指标已经踩过两个坑——
① 年化因子硬编码 252(周/月频夏普与波动被高估
√(252/ppy)倍);② 「1/N 同波动缩放」的夏普变化差点让"ERC > 1/N"这条结论站不住(夏普尺度不变)。
一、目录内容
| 文件 | 说明 |
|---|---|
crosscheck_quantstats.py | 审计脚本(真实调用两个引擎的 performance() / metrics(),不自己复刻) |
sns_stub.py | quantstats 缺失可选依赖(seaborn / yfinance / tabulate)的 no-op 替身 |
crosscheck_report.md | 人读报告(结论 / 阈值 / 索提诺定义差异 / 年化口径分歧)→ 回测/perf_audit/crosscheck_report |
crosscheck_results.csv / .json | 机器可核的逐项对照(含 meta:版本、阈值、口径分歧、抽样对拍) |
二、运行
cd "回测/perf_audit"
PYTHONPATH=/var/minis/mounts/量化策略/quantstats-main python3 crosscheck_quantstats.py
PYTHONPATH=... python3 crosscheck_quantstats.py --report-only # 秒级,只重写报告约 90s(import quantstats 26s + portfolio.load_daily 39s 是主要开销)。 iSH 上接近 2 分钟静默 kill 阈值——必要时按曲线组拆短进程。
三、判定分层(原「差异 > 1e-6 即报错」会必然误报,已废弃)
| 指标 | 判据 | 理由 |
|---|---|---|
| 波动率 · 最大回撤 | 绝对差 ≤ 1e-9(engine_v1 因输出 round(4) 放宽到 5e-5) | 定义相同 → 真正的一致性检验 |
| 夏普 | 相对差 < 0.5% | 可能 rf / ddof 差异 |
| 索提诺 | 相对差 < 5%,须记录定义来源 | 两边公式不同(见报告 §三) |
| 卡玛 · 年化 | 不作判据,只输出对照 | 口径分歧应记录而非判错 |
四、已知口径(引用本目录数字前必读)
- 年化一律以
bar 数 / ppy为准(=engine_v1= quantstats 的years = returns.count()/periods)。
✔ 口径已统一(2026-09-27,v5.3.2):engine_multi::metrics() 的 years 由日历跨度 (idx[-1]-idx[0]).days/365.25 改为 len(eq)/ppy,相对差 0.36% → 0.05%(见 §四、§六 v1.2)。 统一前 engine_multi 系统性高估年化约 0.4%(相对)、卡玛同比例。 混用正是"年化因子硬编码 252"的同源问题:不是算错,是口径没定。
- 索提诺用的是本仓库口径(
x[x<0].std()),与 quantstats(负收益 RMS ÷ 总观测数)不同,数值系统性偏低。引用时必须注明定义。 - 被比对的
net序列口径:engine_v1首位补 0(忠实于run_backtest的nan_to_num),engine_multi与 quantstats 用pct_change().dropna()。本脚本刻意把同一序列喂给两侧,以便区分"公式差异"与"输入定义差异"。 - 卡玛(v1.1 起可用):v1.0 脚本调用
qs.stats.calmar(r)漏传periods(默认 252)→ 非日频序列的 quantstats 卡玛被高估约 +82%(周)/ +98%(月);日频不受影响。已修(periods=ppy),修复后engine_v1相对差 0.08–0.16%(round(4)量化)、engine_multi/portfolio 周重建稳定 0.36%(日历跨度口径)→ 与 §四 的年化口径分歧同源、互为印证。 ⚠ v1.0 旧 CSV 的卡玛列不得引用。
✔ v5.3.2 后:年化口径统一(见第 1 条)使 engine_multi 的卡玛列随之一并对齐,multi vs qs 同降至 0.05%。
五、红线
- 研究层脚本:不写
数据/、不改任何生产脚本、不 importscripts/生产链。 - quantstats 只在本目录使用;生产链一律不 import(见 数据库说明 §〇·五·一 的分层规则)。
- 本目录产出的数字不得直接与生产管线数字混用(口径不同)。
六、修订记录
v1.2(2026-09-27,P3-③ 口径同步 / v5.3.2)
- 把 v1.1 的拍板落地:
回测/engine_multi/multiasset_backtest.py::metrics()的years由
日历跨度 (eq.index[-1]-eq.index[0]).days/365.25 改为 len(eq)/ppy(复用 infer_ppy) → 与 engine_v1 / quantstats 同源。仅一处年化公式,信号/权重/净值路径/成本全未动。
- 实测证据:本目录 §四 表
multi vs qs由 0.36% → 0.05%;engine_multi的 cagr 列
0.1143/0.1059/… → 0.1138/0.1054/…,与 engine_v1 逐位一致(见 crosscheck_report.md §四 / §七 v1.2)。
- 残留 0.05% =
len(eq)(bar 数)与 quantstatsreturns.count()(bar 数 − 1)之差 → 定义细节,非口径分歧。 - 影响:FAIL 项仍为 7(全部索提诺,定义不同);
engine_multi五张表已全量复算并同步 README,
所有排序与结论未变。
v1.1(2026-09-27,监工复核修复)
- 修复:
crosscheck_quantstats.py的qs.stats.calmar(r)→qs.stats.calmar(r, periods=ppy)(漏传 periods 会退化为默认 252)。 - 发现者:监工独立复核(非本目录作者);证据特征:误差随
252/ppy单调放大(周 +82% / 月 +98%)、日频不受影响。 - 影响:判定与结论零影响(卡玛不作判据,FAIL 仍为 7 项索提诺);仅使卡玛列从"不可引用"变为"可用",并反向印证年化口径分歧。
- 快照:
/var/minis/shared/backup/DUCKERRY_perfaudit_fix_20260927/(pre/ + md5_before/after.txt)。