记录一次只读交叉验证,我把 funding_collector 的两个坑挖出来了(2026-10-18)
记录一次只读交叉验证,我把 funding_collector 的两个坑挖出来了(2026-10-18)
只读一天,我才明白哈希锁能冻住策略,却冻不住一个没有测试的采集器。
今天上午知乎那篇债市问股市的文章刚上线,下午我就钻进 C:\Users\Administrator\Documents\craw_app1_wz\defi_dashboard 开始第 12 轮交叉验证。说好只读不改,结果还是挖出两个确证 bug,而且其中一个藏在最不容易被发现的地方。
一、为什么今天要“只读不改”
这一轮叫 xverify12,工作目录没变。第 11 轮报了 3 处确证 bug 加 2 处我说错,全修了。用户又做了四件事:新增 funding_collector.py 注册进 watchdog.py、新增 validation/fetch_dvol_history.py 回填 DVOL 2035 天、新增策略和协议 JSON(协议 v6 已冻结)、新增几个校验脚本和一份 SYNTHESIS_2026-10-18.md。前提假设不变:用户这一轮的错误率不低于之前,上一轮的数字全对而机制全错。 所以我只做独立复核,不碰一行代码。
二、n_coins 对不上,币集口径先炸了
复核项 1 是 funding_collector 的数据正确性,最高优先。第一个反常信号是 n_coins: 23,但代码里 INSTRUMENTS 只有 20 个。CSV 有 23 列,多出来的是 INJ、OP、SUI 三个币。
继续对比历史 funding_panel.json 和新采集器 INSTRUMENTS,发现两者都是 20 币,但币集完全不同:历史含 INJ/OP/SUI,不含 ONDO/TAO/ZEC;新采集器含 ONDO/TAO/ZEC,不含 INJ/OP/SUI。合并后 CSV 自然就变成 23 列。实测确认:INJ/OP/SUI 最后非空停在 2026-10-15,之后新采集器不再更新它们,会静默死亡,费率永远停在 10-15;ONDO/TAO/ZEC 则只有 22-34 天数据。
这是一个确证 bug,但方向跟用户担心的相反。 用户问的是“历史是均值还是逐币”,担心口径是均值 vs 逐币的错位。核实下来两者都是逐币,口径一致,真正错位的是币集。表一对就知道:历史独有 INJ/OP/SUI,新采集器独有 ONDO/TAO/ZEC,合并成 23 币,三列停更、三列半新,数据卫生问题直接埋进去了。
三、combine_first 旧值优先:一个不可检测的静默 bug
复核项 1.1 是 combine_first 语义。代码里 old.combine_first(daily) 在第 89 行,我实测 old=[1.0] 遇 new=[2.0],返回的是 1.0,丢掉正确的 2.0。旧值优先,坐实了。
更麻烦的是,这个 bug 不可检测。天数不变,值只差一个费率量级(1e-5),没有任何测试或断言会报警。合并逻辑方向对了一半——历史确实必须保留,因为 OKX 单次只回 100 条;但“旧优先”在重叠日上赌的是“旧值永远正确”,这个假设不成立。注入验证里去掉 combine_first 会从 501 天变成 34 天,历史丢失,但那是事后看日志才能发现;改成新数据优先,天数都是 504,完全无法区分。重叠日正是最新数据,一旦旧文件某天值是错的,新的正确值会被永久丢弃,且无人察觉。
这个 bug 我定性为确证,因为它违反直觉又能静默生效。 用户在设计合并时大概率没意识到重叠日的旧值可能比自己以为的更不可信。
四、协议锁能抓谁,不能抓谁
复核过程中我一直留意协议锁的边界。source_sha256_of_strategy 哈希的是整个策略源码模块,所以注入 #3(rank 方向取反)和 #4(rolling 改 centered)只要改 strategies_funding_regime.py 一行,整个 sha256 就变,run_validation 的 run 命令会拒绝运行。这个锁是健壮的。
但 funding_collector.py 是生产采集器,不在协议锁内。它的正确性完全依赖“没有测试”。注入验证的结果很直白:注入 #1(去掉 combine_first)和 #2(新数据优先)没有任何锁和测试能抓;注入 #5(删掉 dvol_ic_test.py 里的 rv_z30)无锁抓不到;注入 #6(watchdog args 改单 token)无测试抓不到。协议 SHA256 锁只冻策略,不冻采集器。 今天挖出的两个坑全在采集器侧,恰好说明这一层防护是空的。
五、几个误报,和 t=4.17 的幻觉
也有几处用户担心但实际没问题的地方。三源相关性我实测 Spearman:DVOL 与资金费率 -0.526,DVOL 与已实现波动率 +0.433,资金费率与已实现波动率 -0.133。结论是“部分独立”,不是完全独立,但也不是同一信息。DVOL 是年化隐含波动率百分比,单位与币种无关,所以“BTC 计价 vs USD 计价”是个伪问题,用户 3.2 的担忧是误报。regime_flag 用 rolling(L).mean() 默认右对齐,无前视;rank 方向取费率最高的一批做空,和注释一致;watchdog 的 --args="--loop --interval 1800" 多 token 形式在 Windows shell 下解析正确。
最值得记一笔的是 dvol_resid_z30 的 t 值。SYNTHESIS 里说“2024-012024-10 t=+4.17, p<0.0001”,我实测全留出段 992 天最强 h=10 的 t 只有 +2.88。子区间 2024-0110 确实能复现出 h=5 的 +4.11、h=10 的 +4.86。这说明 t=4.17 是事后在那个窗口里发现的,全期根本不显著。SYNTHESIS 自己承认“每一段显著,之后消失”,但它往“市场状态变了”上解释,更简单的解释是多重检验和数据窥探。这条我标记为需要进一步验证,不在今天两个确证 bug 之列。
六、遗留与下一步
今天挖出的两个确证 bug 都还没修:币集不一致导致 CSV 里 3 币停更、3 币半新;combine_first 旧值优先在重叠日静默丢正确值。协议锁管不到采集器,目前没有任何自动测试能拦住这两个问题。INJ/OP/SUI 的静默死亡不影响协议策略(协议只用 10 币),但数据卫生问题迟早要解决。下一步准备给 funding_collector.py 补一组针对 combine_first 和币集对齐的测试,至少在合并前加一个币集一致性断言。dvol_ic_test 的 family_size=28 把 rv_z30 基线也算进去会虚增 family,但它只是探索脚本不写入协议,先记着。今天只读,明天再动手改。