记录一次回测脚本 P0 修复与交叉验证(2026-10-09)
记录一次回测脚本 P0 修复与交叉验证(2026-10-09)
今天最关键的判断:回测里报出的 p=0.049 根本不能算显著,多重检验一校正直接回到 0.1975。
上午还顺手发了那篇耐克市值蒸发的知乎商业稿,下午就一头扎进 C:\Users\Administrator\Documents\craw_app1_wz\defi_dashboard。原以为只是修 rolling_signal_backtest.py 的两个 P0,结果后来越挖越发现,回测和线上压根不是一个玩法。
一、早上先做了一遍博客只读核查
没什么大动作,但结论很明确:本地 D:/blog 是 Hexo 源码工程,本身不是 git 仓库;真正部署的是 [email protected]:wz-git-hub/wz-git-hub.github.io,仓库里只有构建产物,没有 source/。远程 master HEAD 是 47dd05ca7548d1a2c019818142715d98ab11915e,pushedAt 11:00:57,本地 .deploy_git 与远程逐字节一致,44 个 tracked 文件一个不多一个不少。唯一异常是 source/about/index.md 的 mtime 在 11:38,晚于最后部署,但逐行核对内容已经在线上,判定为空改动。
接着查云盘书库融合:用户想内嵌 E:\yunpan_hub 的 Flask 书库到博客。Flask 正在跑,PID 21300,932 本书。响应没设 X-Frame-Options / CSP,技术上允许 iframe。但 Flask 全部 API 无鉴权——/api/book/<id> 的 DELETE 能直接删书,/api/aliyun/token 能改 refresh_token。而且 HTTPS 页面 iframe 嵌 http:// 会被混合内容拦截。没有自有服务器,cpolar 免费版没穿透,xEdge 免费版也不含公网穿透。这个事只能先挂起。
二、回测第一版“修复”其实没修到点子上
下午开始看 rolling_signal_backtest.py。最初那版只改了两处:把 GAP_H 从 36 调到 48,新增一个 price_basis_deviation()。看似在解决重叠采样,但 build() 里仍然把每个独立信号拆成 BTC/ETH/SOL 三笔币种交易,再用币种笔做二项检验。
这等于把三个高度相关的币种当成三个独立样本。我用只读脚本复算了一遍:gap=48 时真实独立信号只有 30–31 个,币种笔却有 89–92 笔;gap=36 时独立信号 39–41 个,币种笔 113–119 笔。样本量被人为灌水,p 值被系统性地压低。15:37 那份交叉验证报告把这个定义为 P0-1,结论是“FAIL”。
三、真正修 P0 时我只改了一个文件
用户给的规矩很死:只改 rolling_signal_backtest.py,先备份,不重启进程、不下单、不 git commit。我照做,备份成 rolling_signal_backtest.py.bak-p0fix-20261009。
核心改动有两个。P0-1:simulate() 删掉 testnet 的 px 入参,入场价改为 mainnet 1h 已收盘 bar 的 close;SL/TP 从入场 bar 的下一根开始扫,48h 超时用第 48 根 bar 的 close 平仓。这样入场、出场、风控全部同源,不再拿 testnet 价格去对 mainnet K 线。P0-2:统计单位从币种笔改成独立信号,每信号聚合 BTC/ETH/SOL 等权组合净收益,不再把三币拆开数。
改完跑 python rolling_signal_backtest.py --live --gap 48。报告里 risk_on + score>=0.30 配置:30 个信号,净胜率 66.7%,净 EV +0.643%,p=0.049。全量不筛:31 个信号,61.3%,+0.476%,p=0.141。修前版本里那种 90 多个样本的假象消失了。
四、独立复算一致,但多重检验直接打脸
交叉验证时我做了完整复算,没 import 生产脚本,直接从 smart_money_oos.db + _candle_cache.json 重建信号级收益序列。四组配置的信号数、净胜率、净 EV、p 值全部逐项吻合,说明修复的数值本身没水分。
但接下来算多重检验才看到问题。risk_on 的原始二项 p=0.0494,看起来擦边显著。做 Bonferroni/Holm/BH 校正之后,p 变成 0.1975,直接回到不显著。再换 t-test,p=0.0627;bootstrap p=0.0563;permutation p=0.0627。全都 >0.05。二项检验对比 50% 胜率,对这个 EV 目标策略本身就是误导——胜率 66.7% 是 20 胜 10 负,但负的那几笔幅度大,把优势吃掉了。而且 risk_on 组 lag-1 序列相关 +0.277,不是独立样本。
前视检查倒是干干净净:t0 落在整点小时之间,入场 bar 取前一根已收盘 close,扫描从下一根 bar 开始。逐信号 pnl 差异为 0,因为没有任何信号在边界 bar 上同时触发 SL 和 TP。超时从入场 bar 起算 48h,实际从 t0 起算约 46h,差 1–2h,属于已声明的粒度误差,不是前视。
五、更麻烦的是回测和线上根本不一个样
本来以为修完 P0 就能交差,结果排查生产环境时又发现三处不一致。
第一,回测假设 BTC/ETH/SOL 三币等权全开,但线上 MAX_CORRELATED_SAME_SIDE=1,BTC 和 ETH 在 CORRELATION_GROUPS 里同属 majors,同方向只能开一个。三币等权是回测里不存在的仓位结构。第二,sentiment 信号没有 validation 标记,生产走的是验证仓 VALIDATION_SIZE_USD,$30 或 $80,不是回测那种固定 1/3 仓位。第三,入场价来源不同:回测用 mainnet 1h close,线上走 _get_mid_price 再限价单 taker 成交,真实成交价是 fill_price,可能穿过价差偏离。
还有一个细节:5m/1m K 线 API 可达,但要拉 30 个 signal 横跨 07-28 到 10-07 的 5m 数据,大约得 20448 根/币,单次 API 上限可能不够。这个只能留给下一步。
遗留
rolling_signal_backtest.py 的 P0-1/P0-2 从代码层面是修掉了,但报告还不能直接用于上线决策:多重检验校正后不显著,生产仓位结构和回测假设对不上。下一步要修的是三件事:把多重检验校正写进报告主口径;回测模拟对齐生产 MAX_CORRELATED_SAME_SIDE 和验证仓 sizing;云盘书库 Flask 还没加鉴权,公网穿透之前必须先加 nginx Basic Auth 或 token。