框架

绩效指标交叉验证(回测/perf_audit/)

约 90s(import quantstats 26s + portfolio.load_daily 39s 是主要开销)。

回测 · 资产·机制 · 回测/perf_audit/README.md · 6062 B

绩效指标交叉验证(回测/perf_audit/)

用途:用第三方实现 quantstats 复算我们对外的全部绩效指标,证明手写实现没有系统性错误。

为什么需要它:本仓库因为手写绩效指标已经踩过两个坑——

① 年化因子硬编码 252(周/月频夏普与波动被高估 √(252/ppy) 倍);

② 「1/N 同波动缩放」的夏普变化差点让"ERC > 1/N"这条结论站不住(夏普尺度不变)。

一、目录内容

文件说明
crosscheck_quantstats.py审计脚本(真实调用两个引擎的 performance() / metrics(),不自己复刻)
sns_stub.pyquantstats 缺失可选依赖(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%,须记录定义来源两边公式不同(见报告 §三)
卡玛 · 年化不作判据,只输出对照口径分歧应记录而非判错

四、已知口径(引用本目录数字前必读)

  1. 年化一律以 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"的同源问题:不是算错,是口径没定。

  1. 索提诺用的是本仓库口径(x[x<0].std()),与 quantstats(负收益 RMS ÷ 总观测数)不同,数值系统性偏低。引用时必须注明定义。
  2. 被比对的 net 序列口径:engine_v1 首位补 0(忠实于 run_backtest 的 nan_to_num),engine_multi 与 quantstats 用 pct_change().dropna()。本脚本刻意把同一序列喂给两侧,以便区分"公式差异"与"输入定义差异"。
  3. 卡玛(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%。

五、红线

  • 研究层脚本:不写 数据/、不改任何生产脚本、不 import scripts/ 生产链。
  • 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 数)与 quantstats returns.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)。