我怎么骗了自己两个月

2026-09 研究方法 自我否证
两个月里我立了 27 条假说,全部被自己的数据推翻。

但真正值得写下来的不是那些结论 —— 是我怎么会一次又一次地相信它们。 下面每一条都对应一个我实际踩过的坑,不是「应该注意」的清单。

一、前视偏差:最容易骗到自己的一类

我有两条策略一开始跑出来是强正的。查下去,两条都是代码里的前视。

坑 1:用「该分钟均值」当挂单价

我的分钟网格 MD[i,t] 是第 t 分钟中价的均值。 它包含第 45–60 秒的信息 —— 而我在第 0–45 秒撮合。 决策用到了决策之后的价格。

修正前修正后
随机方向+5.22bp/天−18.48bp/天
信号方向+8.65bp/天−11.80bp/天
正号标的33/450/41

坑 2:信号本身用了未来

fac[t] 是第 t 分钟的因子收益 —— 要到该分钟结束才知道。 而我在该分钟开头下单。改用 fac[t−1] 后, taker 版从 +4.65bp 变成 −2.70bp。

教训:任何按周期聚合的量,只要聚合窗口跨过决策时点,就是前视。
均值尤其危险,因为它看起来无辜 —— 你不会觉得「取个平均」有什么问题。

可操作的检测:前视的特征是「收益随前视量单调变化」。 加一个 --batch-ms N 参数扫 0/5/10/15/30 秒,看结果会不会动。 会动就是有前视。

还有一个高发模式值得单独记:「批量刷新最新状态 + 遍历历史事件」。 任何 refresh_all_latest() 后面跟着 for event in past_events 的代码都要警惕 —— 你在用「现在」的状态去处理「过去」的事件。 修法是把状态更新和事件按发生时间归并成一条流顺序回放。

二、幽灵成交:回测亏钱的头号假来源

我一开始用「价格触及/穿越我的限价 ⇒ 成交」做回测。

后来有了一个干净样本 —— 「他确实挂着单」的那些时刻 —— 拿它校准:

情形发生次数他实际成交的比例
价格穿过他的限价38,2303.2%
价格正好打在他的限价上5,22241.3%

朴素模型造出了 12.45 倍的幽灵成交。而这些幽灵不是中性的 —— 它们系统性有毒:

最有说服力的一个对照:朴素「全触及成交」模型算出来的总体 markout 是 +0.11bp、 逆向 48.5% —— 这正好落在所有复现模拟的水平线上。
换句话说,我用一个错误的成交假设,给自己造出了恰好「看起来合理」的结果。

三、口径:同一个东西的六个版本

「口径」这个词在我的文档里出现了 68 次,是最高频的一个。举两个实例:

费率

同一个「他的费率」,我在不同文档里并存过 0.14 / 0.269 / 0.29bp 三个数。 其中 0.269 出自早期仅 3,881 笔被动 + 265 笔主动的小样本, 后来用 23,135 笔实测是 0.14 —— 而且 p10 = p90 完全一致, 说明是常数费率不是逐笔滑点。小样本那个数大约是真实值的两倍。

我们自己的费率更乱,有 六个版本存活在不同文档里。

markout

同一个策略,两把尺子量出来差 6 倍:

教训:「挂单价 vs 成交价」和「mid vs tape」算出来的 markout 不是同一个量。混用会得出互相矛盾的结论,而你未必会发现。

纪律:口径必须全项目锁定成一个数,并声明权威排序。 口径不锁定的时候,所有 ✅ / ❌ 都是废话。

四、事后挑窗口:最贵的一个错误

我有一张三天的逐日表:09-01 −15.2 / 09-02 −2.2 / 09-03 +14.5(单位 bp)。

然后我把 +14.5 这一天写进了策略规格,作为「策略成型后的水平值」, 并据此得出「每美元名义收益率高于目标地址」的结论。

三天均值约 −1.0bp。
我用一套完全独立的实现复算(更保守的撮合:必须严格穿价;真实 oracle tick; 不假设 oracle 平价出场),五个 1.6 天块得到 −0.04 / +0.03 / +2.43 / +1.98 / +3.63 bp —— 两套实现是一致的,都在零附近。分歧只在解读:我把三天里最好的一天当成了水平值。

同类错误还有一次:我用「已知为正的 3 个窗口」验证一个配置, 3/3 全正,看起来很稳。后来跑完整的 8 个窗口:3/8、中位 −109.3bp。

永远不要只报你挑中的窗口。

五、自证式检验:比不检验更糟

我做过一次「价格审计」,报出 99.66% 命中率,非常漂亮。但那是同义反复 —— 我验的是「他自己的成交价是否落在 1 分钟 K 线的 [low, high] 内」, 而 K 线的 high/low 本来就是从成交里取的极值。99.66% 是数学必然。

真正该验的(我自己算出的 mid、距 mid 距离、markout 基准是否落在真实 K 线内) 一次都没做。

还有一个更隐蔽的变体:用自己写的笔记当「实测值」的出处。 我把自己的 strategy-v1.md 当权威引了五轮, 而那上面的数字本身就是错的 —— 那些「他的实测 RATIOS」其实是我自己写的笔记。

六、统计:不是所有「显著」都算数

样本量差 2 倍时的「两半同号」是弱证据

n 差 2 倍的两个独立正相关样本,本来就有约 3/4 的概率凑出同号。 报两半同号时必须同时给二项 p 值和两半幅度差。

把「币」当独立观测会低估方差

我有一条「卖侧特别强(+2.32bp)」的结论。用币聚类 bootstrap 压了 2,000 次之后, 置信区间 [−4.33, +0.20] —— 含 0。剔掉买侧少于 60 笔的币后从 +2.32 腰斩到 +1.135bp。

比较配置前先算统计功效

我实测了单窗口收益的标准差:488.7bp。反算每组需要的窗口数:

要分辨的差异需要的窗口数
50bp1,568
100bp392
200bp98
400bp24

而我一直只在 3–8 个窗口上做比较 —— 那只能分辨 400bp 以上的差异。

改进:用配对差值代替各自的中位数排名。同一窗口跑两个配置取差值, 市场状况被抵消,标准差从 488.7bp 降到 143.8bp —— 功效提高约 23 倍。

七、把「测不出」说成「测出来了」

这是我最后期才意识到的毛病。当时我写了「六个状态族封板、数据侧封板 —— edge 在数据侧测不出」。

但真相是:L2 真队列根本没采集。 而我的采集脚本第 46–48 行订阅的就是完整的档位数组,只是代码里只取了 levels[0][0] —— 改成 levels[0][:N] 就能落深度。

「需补采基建」和「基建现成」是同一份文件里的两句话。
阴性结果不是结论。找不出来就老实写「来源未定位」, 并给出下一步用哪个可观测输入去测 —— 不要把「我没测」包装成「测不出来」。

八、一些技术性的自杀方式

九、我现在的检查清单

把这些坑压成可执行的动作:


这两个月我最大的收获不是找到了什么,而是知道了自己会怎么骗自己。

上面每一条都不是理论。它们对应的是具体的某一天、某一份跑出漂亮数字的报告, 以及我兴冲冲地把那个数字写进规格、然后被自己的另一份代码推翻的过程。

而自我推翻的频率本身就是样本不足的度量 —— 我在某一轮 13 天里推翻了 4 次。这比任何一条结论都更能说明我现在的阶段。

— 记录于此,明年回看 —