回测防坑体检
> 一份回测可以跑得很漂亮,同时完全是假的。
> 未来函数、过拟合、幸存者偏差、被你悄悄剔掉的那 0.59% 跌停样本,共同特征是
> 让它好看,让你亏钱,而你在代码里看不出来。
>
> 本工具只有一条原理——把未来砍掉,看历史会不会变。变了,就是坑。
一分钟看懂怎么用
| 你手上有什么 | 跑什么 | 多久 | 得到什么 |
|---|---|---|---|
| 回测代码 | 静态巡检 | 30 秒 | 已知形态的坑,逐条列出来 |
| 回测代码 + 收益序列 | 裁剪法 | 几分钟 | 有没有未来函数(唯一能全自动抓出来的一项) |
| 回测代码 + 生产策略脚本 | 口径对账 | 1 分钟 | 你测的是不是那个策略 |
| 以上齐全 | 完整体检 | 十几分钟 | 14 项 PASS/FAIL 清单 + 修复建议 + 报告 |
不需要联网,不需要行情接口,不需要任何金融数据 MCP。有代码就能查。
一句话分工:本工具查代码(回测是不是真的),「炒股策略可信度检验」查结论(就算真的,样本够不够撑起这句话)。
0. 先读这一段(定位与免责)
这不是选股工具,不产生任何买卖信号。
它只做一件事:在你把真钱押上去之前,告诉你这份回测有多少是假的。
三个必须先接受的判断:
- 回测是筛子,不是保证书。 通过体检只代表「没有明显的错」,不代表「能赚钱」。
- 大部分漂亮回测死在检查环节,不是死在市场。 我实测过一份 75 条规则的选股体系,能忠实移植进回测的只有 18.7%,50.7% 属于重度漂移——代码能跑、不报错、数字很干净,但它测的不是那个策略。
- 本工具能抓到「已知形态」的错,抓不到「你想不到」的错。 所以它降低风险,不消除风险。
如果用户想要的是「帮我找个能赚钱的策略」,请直接说明:这件事我做不到,任何人说能做到都是在骗你。 然后推荐他去看本 skill 能提供的三样东西——诚实的测量、明确的边界、可查证的状态。
1. 什么时候用 / 什么时候别用
该用:
- 刚写完一份回测,数字很好看(尤其夏普 > 1.5、年化 > 30%),想在上真钱前过一遍
- 拿到别人的策略/指标/公众号方案,想快速判断可信度
- 自己的策略实盘一直跑输回测,想定位是哪一层出了问题
- 要给一份策略写报告,需要一段可查证的「已验证到什么程度」
别用:
- 还没写回测(先去写,别拿体检表当回测替代品)
- 想要参数寻优(这是过拟合的加速器,本工具反对)
- 只有一两个月的样本(几乎所有项都会判「数据不足」,先攒样本)
2. 四段漏斗(先定位,再体检)
快筛 → 细查 → 模拟 → 实盘
| 阶段 | 允许跳过什么 | 不许声称什么 |
|---|---|---|
| 快筛 | 可跳过部分检查,先判有没有潜力 | 不许说「策略有效」 |
| 细查 | 13 条逐条过,一条不许跳 | 不许说「可以上实盘」 |
| 模拟 | paper trading 跑几个月,让隐藏问题自己浮出来 | 不许说「已证明」 |
| 实盘 | 小仓位,运行后反复研究、回填修正 | — |
每份报告必须带一行元数据:验证阶段 = 快筛 / 已验证样本外区间 = ____。
这样「这个策略回测过」就变成了可查证的状态,而不是一句笼统的自信。
体检时用 --stage quick|detail|paper|live 声明当前阶段。
3. 13 项体检表(速查)
工具会自动判定前 13 项 + 补充第 14 项,状态为 PASS / FAIL / MANUAL。
动手之前先做第 0 步 —— 口径对账。它过不了,后面 14 项全白做。
| # | 层级 | 检查项 | 自动判? |
|---|---|---|---|
| 0 | 前置 | 回测规则 = 生产脚本?(rule_parity.py 对数字,实跑对逻辑) |
自动(v1.3.0 新增) |
| 1 | 数据 | 复权用乘数法,不是减法 | 半自动(扫代码) |
| 2 | 数据 | 幸存者偏差已声明(股票池含不含退市股) | 需 --universe |
| 3 | 数据 | 日收益 >4σ 先查脏数据 | 自动 |
| 4 | 度量 | 只看夏普 + 最大回撤,不看年化收益 | 自动 |
| 5 | 度量 | 年化系数用 242,不是 252 | 自动 |
| 6 | 度量 | 无风险利率扣除规则正确 | 需 --rf |
| 7 | 偏差 | PnL 必须乘 lag1 仓位 | 半自动(扫代码) |
| 8 | 偏差 | 裁剪法验未来函数 | 自动(核心) |
| 9 | 过拟合 | 参数 ≤ 5,数据点 ≥ 参数 × 252 | 自动 |
| 10 | 过拟合 | 样本外 ≥ 1/3,按时序切 | 需 --oos-frac |
| 11 | 过拟合 | 敏感性测试,微调就崩 = 过拟合 | 需 --sensitive |
| 12 | 成本 | 成本按换手率逐日扣,不为零 | 自动 |
| 13 | 验证 | 最终检验 = 模拟交易 | 需 --stage |
| 14 | 补充 | 夏普过 t 检验(我方补充,原书没有) | 自动 |
逐条详解与 A 股适配见 references/13条铁律.md。做体检前必读第 8 条、第 12 条、第 14 条。
4. 核心:裁剪法(第 8 条,唯一能自动抓未来函数的检查)
原理
未来函数的本质是:某一天的决策用到了那一天还不可能知道的信息。
那么——把未来的数据砍掉,必然改变那一天的输出。
全量跑 A = run(全部数据)
截断跑 B = run(只保留 <= D 的数据)
A 只留 ≤ D 的行,与 B 逐行比对
完全一致 → D 这个截断点上不存在未来函数
有任何差异 → 存在未来函数,或跨日状态泄漏
★★ 关键:两种实现模式,效果天差地别
原理简单,但「怎么砍掉数据」决定了检测是否真的有效。这是本工具最重要的一条工程结论,来自我自己的实测踩坑。
| 模式 A 契约式 | 模式 B 文件切片式 ★推荐 | |
|---|---|---|
| 怎么砍 | 把 end_date 传给策略,靠策略自觉不去读未来数据 |
工具直接把数据文件临时替换成「只含 ≤D 行」的版本,在干净子进程里重新 import 策略 |
| 策略要不要改 | 要,必须实现 end_date 契约 |
一行都不用改 |
| 能不能被绕过 | 能——策略直接读全局数组就绕过了 | 不能——它读的就是被切过的数据 |
| 实测结论 | 会给出假阴性 | 可靠 |
我实测过:一个用「明天收益率」筛股的坏策略,在契约式下 5/5 截断点全部判「一致通过」。
原因很简单:end_date 只过滤了输出日期,数据可见性一点没动——策略照样读到完整的未来。
所以:能用模式 B 就一定用模式 B。 模式 A 只在「数据源不是单一 CSV、无法切片」时才退而求其次,且必须知道它的结论不可靠。
模式 C:面板切片式(.npz 面板 + meta.json)★ 量化引擎最常见形态
很多回测引擎不是读 CSV,而是读「OHLCV 二维面板 + 独立 meta」:
panel.npz O/H/L/C/V 等 (N 只 × T 日) 数组
meta.json {dates:[...], codes:[...], names:{...}}
工具会把面板沿日期轴截断,同时把 meta 的 dates 同步切短,再跑子进程。两个文件都在 finally 里还原。
python scripts/truncation_test.py --module my_engine.py --entry generate \
--data-file panel_hist.npz --panel-meta meta_hist.json --date-axis 1
--date-axis:日期在哪一维。(股票×日期)布局是1(默认)。- 必填
--panel-meta:面板和 meta 必须同步切,否则数组长度与日期错位。 - 会先备份两个文件、最后必定还原;但切 200MB 面板会写盘,跑之前自己再备一份。
★★ --settle-lag:输出含收益时必须设
如果策略输出里含收益,而收益要 T+2 才结算(或有跌停顺延),那截断到 D 时,D 和 D-1 的信号天然算不出收益。
这会在裁剪法里表现为「全量有、截断无」的假分歧。
正确做法不是去改策略,而是把比较截止日往前推:
# 结算需要 2 天、最大顺延 5 天 → lag = 7
--settle-lag 7
判断方法:先 --settle-lag 0 跑一遍,看分歧是否全部落在截断日当天与前一天。
是 → 边界效应,加 lag;不是(更早的日期也在缺、或出现「凭空多出」/「值差」)→ 真问题,继续查。
模式 B 的用法(推荐)
python scripts/truncation_test.py \
--module my_strategy.py \
--data-file prices.csv \
--data-date-col date
工具会:备份 prices.csv → 逐点写入截断版 → 子进程重跑 → 比对 → finally 恢复原文件(即使中途报错也会恢复)。
对策略的唯一要求:从 --data-file 那个文件读行情,或者从环境变量 BT_AUDIT_DATA 读路径(工具会自动设置)。
模式 A 的用法(退路)
python scripts/truncation_test.py --module my_strategy.py --entry generate
要求策略暴露:
def generate(end_date=None):
"""
end_date : 'YYYY-MM-DD' 或 None
非 None 时,只允许使用交易日 <= end_date 的数据
"""
工具额外做契约检查:generate(D) 的输出里不许有 date > D 的行。这能抓「完全无视契约」的策略,但抓不到「遵守了日期、却偷读全局数组」的。
三个实操要点
- 单点通过不代表整体清白。 默认 11 个等距截断点,都必须在实际出过信号的日期上(工具已自动处理,早期没信号的日期不会浪费一个检查位)。
- 截断日别贴样本末尾,每个点后至少留 5 个交易日间隔。
- 两种分歧特征指向不同病因:
- 「全量有、截断无」 → 策略依赖了「下一日才存在」的数据(典型:明日收益率、次日开盘价)
- 「截断凭空多出」 → 策略用未来数据改了基准(典型:整段样本最高价、全样本分位数)
★ 它抓不到什么(诚实边界)
| 盲区 | 为什么抓不到 | 怎么抓 |
|---|---|---|
| 未来信息以字面量写死在代码里 | 没走数据通道,切片不影响输出 | 人工代码审查。看到像股票代码的魔法常量、神秘数字列表就该警觉;查 git diff 看谁加的 |
| 策略从第二个未被切片的文件读未来信息 | 只切了 --data-file 那一个 |
把该文件也纳入切片,或改成单一数据入口 |
| 未来信息来自网络请求 / 数据库实时查询 | 同属第二条通道 | 同上 |
这条边界不是缺陷,是原理决定的。任何宣称「能抓所有未来函数」的检测都在撒谎。
scripts/_verify.py 会把这四种情况(含假阴性用例)跑成验收表,你可以自己验:
python scripts/_verify.py
它会输出一张 4 行对照表,其中 bad_hardcoded(写死的未来赢家)预期就是「通过」——用来演示边界,不是 bug。
5. 完整体检流程
第零步:口径对账(v1.3.0 新增,一分钟)
python scripts/rule_parity.py --prod 生产脚本.py --bt 回测规则.py
拿生产脚本里的每一个数值阈值,去回测代码里找。找不到就是两套口径。
为什么它排在最前面:口径不对时,其余 14 项全能通过 —— 因为它们检查的是「这段代码内部自不自洽」,而口径漂移是「这段代码和那个策略不是一回事」,两者正交。
真实代价:某策略回测缺一个 vr5 >= 2.2332 放量门,可结算命中 8,692 笔虚增成 45,314 笔,alpha 从 +0.2754% 被稀释成 +0.0894%(只剩 1/3),t 值 3.432 掉到 2.548 —— 一个能过 Bonferroni 的结论,被这个漏洞拖成「不显著」。裁剪法当时 4/4 零偏差照样通过。
判定分层:
| 生产侧 | 回测里找不到 | 处理 |
|---|---|---|
策略常量(VR5_MIN = 2.2332) |
FAIL | 必须补齐,重跑,别在原结果上做结论 |
工程常量(NBARS / VR5_SOFT) |
SKIP | 数据规模、观察池、并发数,天然不必进回测 |
内联字面量(*1.2 / <0.02) |
WARN | 可能换了等价写法,人工核 |
> ⚠️ 它只比数字,不比逻辑。 两端数字都在、但一边写成 V > vma5*1.2、另一边写成 V > vma5*0.8333(语义已崩),它看不出来。数字对齐只是及格线,必须再实跑一次 parity:同一天、同规则,比对命中票集合与指标值,重合率 < 90% 不予采信。
> ⚠️ 注释能骗过朴素实现。 工具比对的是剥离注释与字符串之后的代码文本。首版用全文正则时,把门槛偷改成 9.9999 而注释里仍留着 2.2332,它照样报 PASS —— 这个坑实测踩过,别再用全文搜索。
第一步:静态巡检(30 秒)
python scripts/audit.py --code my_strategy.py
扫三类已知危险写法:
- 未来函数:负向
shift(-、iloc[i+1]、center=True、bfill、np.roll负偏移、无窗口cummax/expanding、对整段序列直接.max() - lag1 缺失:收益直接乘当期仓位
- 成本缺失:
cost = 0、完全没有成本变量
命中 HIGH 级会直接判对应铁律 FAIL。
> ⚠️ 静态扫描只能发现已知形态。 它命中不代表一定错(可能是注释或误报),它没命中更不代表没问题。能证明没有未来函数的是裁剪法,不是它。
第二步:裁剪法(核心,几分钟)
见第 4 节。把结果存成 JSON。
第三步:完整体检 + 出报告
python scripts/audit.py \
--code my_strategy.py \
--signals 信号.csv \
--params 3 \
--oos-frac 0.33 \
--cost-bp 20 \
--rf 0.02 \
--n-per-year 242 \
--universe current \
--sensitive stable \
--stage detail \
--truncation-json trunc.json \
--md 体检报告.md
输出:13+1 行体检表 + 实测指标(夏普 / t 值 / 最大回撤 / 年化)+ 一句话结论 + Markdown 报告。
信号表格式:至少 date、code 两列,有 ret 列则自动算夏普、回撤、4σ 异常点。
6. 最常见的一批病(都是真实踩过的)
按出现频率排,这七种占了绝大多数:
① 口径漂移:回测的根本不是那个策略
症状:代码能跑、不报错、数字干净,但测的和实际执行的不是一回事。
四种形态:
| 形态 | 例子 |
|---|---|
| 「全部必中」被写成「≥N」 | 生产要求 4 个条件同时满足,回测写成「满足任意 N 个」——命中数差 100 倍 |
| 「N 选 1」被写成「1 条」 | 生产是「5 条中取最强 1 条」,回测写成「满足第 1 条」 |
| 时序反向 | 生产是「先跌后涨」,回测写成「先涨后跌」——交集为 0,代码不报错,最隐蔽 |
| 核心字段用代理未标注 | 换手率用 vr20 代替、市值用 pos60 代替,但报告里仍写「换手率」 |
铁律:回测规则必须逐字移植生产脚本;不许为「能跑起来」自行简化;文档与代码冲突时以生产脚本为准。
新增规则先进 parity 测试,重合率 < 90% 不许进回测。
v1.3.0 起这一步有工具了:python scripts/rule_parity.py --prod 生产.py --bt 回测.py
(此前只写了要求没给工具,只能人工逐条对账 —— 代价就是漏掉了上面那个 vr5 门槛,见第 5 节第零步。)
② 把跌停样本剔掉(打板类专吃这个)
症状:卖出日跌停的样本直接剔除,不带走。
后果:这类样本占比可能只有 0.59%,但剔除它会把「必亏」洗成「可重仓」——实测某条规则从 −0.85% 变成 +0.55%,另一条从 −1.38% 变成 +0.98%。符号翻转,不改变任何其他设置。
正确做法:必须顺延卖出(≤5 个交易日),禁止剔除样本。
③ 用整段极值而不是「至今极值」
week_high = high[week_days].max() # ❌ 含未来日
week_high = np.fmax.accumulate(...) # ✅ 只取本周至今
实测:这条 bug 曾造出一个「+3.55%、t=13.66」的假发现,修正后是 −0.16%。
④ shift() 对 bool 数组补 NaN,NaN 被当 True
np.full_like(A, nan) # ❌ bool 数组里 nan → True
np.zeros_like(A, bool) # ✅ 补 False
后果:所有「历史上 N 天内出现过 X」的判断,在序列开头凭空多出 N 天「全市场命中」。实测因此生出 13,391 个假事件。
⑤ 分组键跨年撞号
key = year * 100 + isoweek # ❌ 2024-12-30 与 2024-01-01 混进同一周
wk_start = (dts - to_timedelta(dts.dayofweek)).normalize() # ✅
⑥ 用「年化收益率」代替夏普做判断
年化收益可被杠杆任意放大。10 倍杠杆把 5% 做成 50%,数字漂亮,风险也 10 倍。只看夏普 + 最大回撤。
⑦ 忽略夏普的统计显著性
这是最普遍、也最不容易被接受的一条:
| 真实夏普 | 达到 95% 显著所需样本 |
|---|---|
| 0.25 | 63.4 年 |
| 0.50 | 17.3 年 |
| 1.00 | 5.8 年 |
| 2.00 | 2.9 年 |
低夏普策略在统计上永远无法被证明。 一份 3 年样本、夏普 0.8 的回测,它和「什么都没有」在统计上无法区分。这不是策略不好,是样本不够。
「3 日持有 + 涨 10%」这类短线口径,单次交易的 t 值只有 0.111,比显著门槛低 17.6 倍。单次盈亏不含任何关于策略优劣的信息。
⑧ 8 位紧凑日期被当成纳秒时间戳(工具自己也踩过)
pd.read_csv 在 pandas 3.0 返回 StringDtype,pd.to_datetime 会把 '20190403' 当纳秒时间戳,
结果全部变成 1970-01-01,样本塌缩成 1 天,而且不抛异常。
后果:夏普、t 值、回撤全错,报「样本只有 1 天」。或更糟——裁剪法把上千个交易日压成同一天,
比对时 key 全撞在一起,可能给出假阴性。
v1.2.0 起 audit.py 与 truncation_test.py 都加了 _date_to_iso():先判断是否 >90% 为 8 位纯数字,
是则用 format="%Y%m%d" 显式解析。
自查:跑完体检先看「样本期数」是否等于你预期的交易日数。差一个数量级就是踩了这个坑。
7. 拿真实 skill 开刀:适配层怎么写
现有选股 skill 基本都是逐只 HTTP 拉数据的扫描器,没有一个可以被 --data-file 切片的数据文件。所以必须先写一层适配,规则本体不动。
两条铁律
- 规则函数原样 import,一个字都不许改。 例:隔日法的
judge()直接从scan_today.pyimport,只重写「取数 + 循环 + 阶段流程」。 - 阶段流程也要照搬。 隔日法是两阶段的:先用不复权价粗筛,再对候选做前复权复核。只跑其中一种,测的就不是那个策略。
标准做法
① 把行情落成单一长表 CSV(date, code, open_raw, close_raw, open_qfq, close_qfq, vol)
两套价都要有 —— 因为生产是两阶段的,缺一套就只能测半个策略。
② 适配层 generate(end_date=None):
for 每只股票:
for 每个交易日 t:
judge(c_raw[:t+1], v[:t+1], o_raw[:t+1]) # 阶段一
若过:judge(c_qfq[:t+1], v[:t+1], o_qfq[:t+1]) # 阶段二
两者都过才算命中
③ 只传最近 260 根(与生产 NBARS 一致)
judge 最长回看是 vma60s[-20],需要 v[-79:],260 根足够,
且与传全历史结果完全一致 —— 但速度快几十倍。
④ 跑 parity:用生产自己的 fetch_kline + truncate + qfq_close 跑 HTTP 数据,
与适配层的本地面板结果逐项对比。**重合率 < 90% 不许进回测。**
⑤ 跑裁剪法:--data-file 指向 ①的那个 CSV
性能参考
judge() 单次约 246 µs。500 只 × 1800 天 × 2 阶段 ≈ 180 万次 ≈ 7 分钟/轮。
所以实操要把样本压到 200~300 只 × 700~900 天(约 40 秒/轮),信号量仍在 200 笔上下,足够做裁剪法比对。
信号密度参考:224 只子样本 → 204 个信号 / 171 个交易日。但平均每日仅 1.19 只,84.7% 的交易日只有 1 只票。这时「组合日收益」退化成「单笔收益」,夏普与 t 值稳定性很差 —— 子样本数字不能拿去肯定或否定策略,它只用来验证链路和统计检验能不能跑。
8. 输出规范
一句话结论(必须放在最前面)
三档,不许模糊:
| 情况 | 话术 |
|---|---|
| 0 项 FAIL、0 项未验证 | 「全部通过。可以进入模拟交易阶段验证。」 |
| 0 项 FAIL、有未验证 | 「无硬伤,但 N 项未验证。补齐后才能谈「策略有效」。」 |
| 1–3 项 FAIL | 「不通过:N 项硬伤(第 x、y 项),修完重跑。」 |
| ≥4 项 FAIL | 「不通过:N 项硬伤。当前回测结论不可采信,先修问题再看数字。」 |
硬性要求
未验证不许写成通过。 空格子就是空格子,不能默认填绿。- 任何「命中率 XX%」必须同时给样本量,并标注
n<50 探索期 / 50–200 观察期 / ≥200 可下结论。 - 报告末尾必须写边界:静态扫描只抓已知形态;通过体检 ≠ 能赚钱。
- 用户要求出报告时,按用户既定铁律交付 MD + 自包含 HTML 双文件(同目录同名、0 外链、浅色、涨红 #c62828 跌绿 #2e7d32、@media print、表格横向滚动)。
9. 与其它 skill 的关系
本 skill 不选股、不给信号,只审别人的结果。
| 被审对象 | 审什么 |
|---|---|
| 短线 / 抄底 / 野渡技术 / 一夜持股法 | 回测代码是否忠实移植生产脚本(病①)、跌停是否顺延(病②)、夏普是否显著(病⑦) |
| 隔日选股法 | 裁剪法零偏差已验证;重点看成本敏感性与样本外 |
| 任何外来策略 | 先跑静态巡检 + 裁剪法,两项都过再谈细节 |
炒股策略可信度检验(evidence-grade,配套) |
代码审完之后再判结论:样本量配不配得上「策略有效」这句话 |
审别的 skill 时的第一条永远是:代码里的规则,和生产脚本是不是同一个东西? 这条过不了,后面 12 条全白做。
10. 版权与出处
- 方法论来源:Ernest P. Chan《量化交易从入门到精通:如何构建你的算法交易系统》第 3 章,姜军略 译,人民邮电出版社,ISBN 978-7-115-56103-9。
- 本文档性质:方法论转述 + A 股制度适配 + 可执行检查动作,非原文转录。引用时请注明来源,不得大段复制原书文字。
- 我方原创补充:第 14 条夏普 t 检验、A 股 242 交易日口径、T+1 合规持有期(T 选→T+1 买→T+2 卖)、涨跌停顺延规则、单笔成本生死线 10,204 元、以及第 6 节全部真实踩坑案例——原书均未涉及。
11. 工具清单
| 文件 | 作用 |
|---|---|
scripts/rule_parity.py |
口径对账(铁律 ①)。拿生产脚本的策略常量去回测代码里找,剥离注释后比对;注释里留着旧数字骗不过。只比数字,逻辑等价仍需实跑 parity |
scripts/truncation_test.py |
裁剪法自动检验(铁律 8)。三模式:契约式 / 文件切片式 / npz 面板切片式;--settle-lag 可排除结算窗口造成的假分歧 |
scripts/audit.py |
13+1 项体检主程序,静态巡检 + 动态指标 + Markdown 报告 |
scripts/_verify.py |
工具自检,4 个已知好/坏用例跑验收表,证明工具有效且边界明确 |
scripts/_selftest.py |
自检用的伪行情生成器 + 4 个测试策略 |
scripts/_run_entry.py |
内部运行器(文件切片式用子进程保证干净 import),不面向用户 |
references/13条铁律.md |
逐条详解与 A 股适配,做体检前必读 |
依赖:Python 3 + pandas + numpy。无需联网,无需任何行情 MCP。
装完先自检(30 秒)
cd scripts
python _selftest.py # 造假行情
python _verify.py # 跑验收表
预期输出:
用例 真相 预期 实际 判定
good 清白策略 通过 通过 符合
bad_future_ret 用明日收益率 不通过 不通过 符合
bad_future_peak 用整段最高价 不通过 不通过 符合
bad_hardcoded 写死的未来赢家 通过 通过 符合
第 4 行「通过」是故意的——它是已知盲区,用来提醒你边界在哪。什么时候第 4 行变成「不通过」了,说明有人把工具改坏了(或者加了静态常量扫描)。
如果前 3 行有任何一个不符预期,这个工具就不可用,别拿它的结论去做决策。
实测记录(v1.0.1)
首次真实数据审计对象 = 野渡隔日选股法。产物在 E:/workfwb/_audit/:
| 文件 | 内容 |
|---|---|
_build_audit_data.py |
面板 → 可切片长表(含复权前后两套价,带列序校验) |
_audit_geri.py |
适配层,judge() 从生产脚本原样 import |
_parity_geri.py |
口径一致性校验 |
回测体检_野渡隔日选股法.md/.html |
完整体检报告 |
结果:口径 parity 5/5 = 100%(命中票的 dist25/vr5 精确到 6 位小数一致)、裁剪法 5/5 零偏差、3 项硬伤(参数 9 个超上限、幸存者偏差、夏普 t=0.90 不显著)。
这次实测还抓出本工具自己的两个 bug,已修(v1.0.0 → v1.0.1):
1. 静态巡区分大小写 → 漏报 COST = 0.0020、qfq_close。已改 re.IGNORECASE 并支持「名字在前、数值在后」写法。
2. 4σ 检查把「单票集中度」误报成「脏数据」→ 平均每日信号 < 1.5 只时改判「未验证」并说明原因。