六个 Bug 修复实战:沙盘推演引擎的数学纠正
本文最后更新于 2026年8月4日 凌晨
沙盘推演引擎(Sandtable)上线后跑了两个月,黄金价格的多模型融合分析结果看起来挺合理——直到我打算认真审计它的数学层。
不审不知道,一审头皮麻。
四个推理引擎(贝叶斯、决策树、蒙特卡洛、因果链)里,六个数学 Bug 同时在跑。有些是 off-by-one,有些是概率没归一化,有些是时间单位跨层不一致。六个 Bug 各自的偏差方向不同,恰好相互抵消,让最终输出看起来”正常”——这正是最危险的情况。
以下是六次修复的完整记录。
Bug 1:百分位计算的 off-by-one
位置:蒙特卡洛模拟 → 百分位统计
1 | |
典型的围栏柱错误(Fencepost Error)。百分位 p 的理论位置是 p/100 * (N-1),不是 p/100 * N。N=100 条路径时,P95 的索引应该是 94(第 95 个元素),但旧代码算出 95——越界到第 96 个元素,对应 P96 左右。
影响:所有百分位区间都向右偏移了 1 个位置。P5~P95 的置信区间宽度被低估了约 1-2%,导致尾部风险看起来比实际小。
发现方式:人工审查时发现 percentile(sorted_arr, 100) 会报索引错误,100% 对应的索引应该是 N-1,不是 N。
Bug 2:贝叶斯更新的顺序敏感性
位置:贝叶斯推断引擎 → 多证据后验计算
贝叶斯定理说后验概率与证据到达顺序无关,但旧代码在每次更新时掺入了 confidence_strength 权重:
1 | |
strength 参数本来是为了给”强证据”更高权重,但每次更新时用它指数缩放似然比,导致先处理的证据实际影响力更大——先来先权重,违反了贝叶斯推断的顺序无关性公理。
影响:同样一组证据,按不同顺序输入,得出不同的后验概率。最大偏差可达 8-15 个百分点。
修复:删除 strength 参数对似然的指数缩放,改用在证据选择阶段做权重过滤(只有置信度达标的证据才进入贝叶斯管道),进入后一律平等更新。
Bug 3:决策树概率未归一化
位置:决策树分析器 → 子节点概率求和
决策树的每个分支节点下,子节点的概率之和应该精确等于 1.0。但代码在构建树时直接累加外部输入的概率,没有做归一化处理:
1 | |
触发场景:当用户(或上游模型)输入的分支概率之和为 0.95 或 1.05 时——因为浮点精度或人为估算误差——决策树会计算出一条”不可能路径”的概率,或者低估某些路径。
影响:多分支决策树中,某些路径的预期收益计算偏差在 3-8% 之间。最坏情况:四个分支各自输入 0.25,浮点误差累积到 0.9999999,归一化后一条路径被吃掉。
发现方式:在批量测试中对比 sum(all_path_probabilities) 是否等于 1.0,发现多数情况不等于 1。
Bug 4:蒙特卡洛时间单位不一致
位置:蒙特卡洛模拟 → 几何布朗运动公式
GBM 的标准公式是:
1 | |
代码中 dt 被硬编码为 1,但波动率 σ 的输入单位是”年化波动率”,而漂移率 μ 是”日收益率”:
1 | |
影响:年化波动率 20% 的资产,在 dt=1(即一天=一年)的模拟中,单日波动被放大 √252 ≈ 15.9 倍。模拟出的价格路径几乎全部发散,500 次模拟中 95% 的路径在 30 天后超出合理范围。
最诡异的是——因为所有路径都发散,上涨概率反而看起来正常(对称发散,50% 涨 50% 跌),只有置信区间宽度异常宽。如果不是专门检查了 P95 的值,这个 Bug 可能永远不被发现。
Bug 5:因果链强度值跨链不一致
位置:因果链分析器 → 多链加权合并
因果链分析器允许用户定义多条因果链(例如”美联储降息→美元走弱→黄金上涨”),每条链有个 strength 值(0-1)表示因果强度。
问题出在:不同链的 strength 值是由不同专家/模型在不同的语境下估算的,没有校准:
1 | |
影响:不同专家对”强度”的理解不同。有的专家把 0.8 当”非常强”,有的把 0.3 当”已经很强”。直接相乘导致某些链被系统性低估或高估,最终推演结果偏向于”专家更保守的链”。
修复:引入 sigmoid 校准函数,将所有 strength 值映射到同一概率尺度上。同时要求每条因果链附带一个”校准参考”(什么情况下算 0.5,什么情况下算 0.9),减少跨链 scale 漂移。
Bug 6:Tier 0 融合超时计算错误
位置:推演引擎调度器 → Fusion 模型超时
Tier 0(极速模式)使用单个 fusion 模型做快速融合分析。它的超时配置是:
1 | |
但实际运行中,fusion 模型平均需要 60-120 秒,最长可达 180 秒。超时设定为 120 秒意味着 50% 以上的 Tier 0 请求在正常完成前就被强行终止。
1 | |
影响:Tier 0 的失败率高达 60%,导致主代理频繁降级到 Tier 1(5 模型,耗时 30-60 秒),反而比直接等 fusion 更慢。而且因为超时后没有任何返回,主代理不知道”部分结果”的存在,直接重试,浪费更多 token。
修复:将超时从 120 秒提高到 180 秒,并在超时后读取已生成的部分结果,返回”部分推演(partial)”状态,让上层可以决定是接受还是重试。
六个 Bug 的共同特征
回头看,这六个 Bug 有一个共同模式:没有在数学层做闭环验证。
- 百分位做完没有对极端值(P0/P100)做边界测试
- 贝叶斯更新做完没有验证”顺序无关性”
- 决策树建完没有断言
sum(prob) == 1.0 - 蒙特卡洛跑完没有检查置信区间是否合理
- 因果链强度没有做 cross-chain 校准
- 超时配置没有分析实际运行分布
沙盘推演引擎的推理层是”看起来合理”的典型——输入输出都对,中间数学层隐蔽地跑偏了半年。修复方式也很一致:在每层输出加一个不变性断言(invariant assertion),让偏差在变成习惯之前就被发现。
参考文献
- 沙盘推演引擎 SKILL.md —
~/.hermes/skills/sandtable/SKILL.md - 推理引擎源码 —
~/.hermes/skills/sandtable/scripts/reasoning_engine.py - 沙盘推演引擎实战:黄金价格的多模型融合分析 — https://www.normdist.com/2026/08/03/sandbox-simulation-gold-price-analysis/