六个 Bug 修复实战:沙盘推演引擎的数学纠正

本文最后更新于 2026年8月4日 凌晨

沙盘推演引擎(Sandtable)上线后跑了两个月,黄金价格的多模型融合分析结果看起来挺合理——直到我打算认真审计它的数学层。

不审不知道,一审头皮麻。

四个推理引擎(贝叶斯、决策树、蒙特卡洛、因果链)里,六个数学 Bug 同时在跑。有些是 off-by-one,有些是概率没归一化,有些是时间单位跨层不一致。六个 Bug 各自的偏差方向不同,恰好相互抵消,让最终输出看起来”正常”——这正是最危险的情况。

以下是六次修复的完整记录。

Bug 1:百分位计算的 off-by-one

位置:蒙特卡洛模拟 → 百分位统计

1
2
3
4
5
# 修复前
idx = p / 100 * len(sorted_arr) # 索引从 0 开始,len 做分母会越界

# 修复后
idx = p / 100 * (len(sorted_arr) - 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
2
3
4
5
6
7
# 修复前(简化示意)
for evidence in evidences:
posterior = prior * likelihood ** strength # strength 导致顺序依赖

# 修复后
for evidence in evidences:
posterior = prior * likelihood # 纯贝叶斯更新,顺序无关

strength 参数本来是为了给”强证据”更高权重,但每次更新时用它指数缩放似然比,导致先处理的证据实际影响力更大——先来先权重,违反了贝叶斯推断的顺序无关性公理。

影响:同样一组证据,按不同顺序输入,得出不同的后验概率。最大偏差可达 8-15 个百分点。

修复:删除 strength 参数对似然的指数缩放,改用在证据选择阶段做权重过滤(只有置信度达标的证据才进入贝叶斯管道),进入后一律平等更新。

Bug 3:决策树概率未归一化

位置:决策树分析器 → 子节点概率求和

决策树的每个分支节点下,子节点的概率之和应该精确等于 1.0。但代码在构建树时直接累加外部输入的概率,没有做归一化处理:

1
2
3
4
5
6
7
8
# 修复前
for child in children:
total_prob = sum(child.probability for child in children)
# 没有归一化,total_prob 可能 != 1.0

# 修复后
for child in children:
child.probability = child.probability / total_prob # 强制归一化

触发场景:当用户(或上游模型)输入的分支概率之和为 0.95 或 1.05 时——因为浮点精度或人为估算误差——决策树会计算出一条”不可能路径”的概率,或者低估某些路径。

影响:多分支决策树中,某些路径的预期收益计算偏差在 3-8% 之间。最坏情况:四个分支各自输入 0.25,浮点误差累积到 0.9999999,归一化后一条路径被吃掉。

发现方式:在批量测试中对比 sum(all_path_probabilities) 是否等于 1.0,发现多数情况不等于 1。

Bug 4:蒙特卡洛时间单位不一致

位置:蒙特卡洛模拟 → 几何布朗运动公式

GBM 的标准公式是:

1
dS = μ·S·dt + σ·S·ε·√dt

代码中 dt 被硬编码为 1,但波动率 σ 的输入单位是”年化波动率”,而漂移率 μ 是”日收益率”:

1
2
3
4
5
6
7
8
# 修复前
drift = (mu - 0.5 * sigma**2) * 1 # dt=1,但 sigma 是年化值
diffusion = sigma * math.sqrt(1) * dw # 年化 sigma 直接当日的用

# 修复后
dt = 1 / 252 # 交易日
drift = (mu - 0.5 * sigma**2) * dt
diffusion = sigma * math.sqrt(dt) * dw

影响:年化波动率 20% 的资产,在 dt=1(即一天=一年)的模拟中,单日波动被放大 √252 ≈ 15.9 倍。模拟出的价格路径几乎全部发散,500 次模拟中 95% 的路径在 30 天后超出合理范围。

最诡异的是——因为所有路径都发散,上涨概率反而看起来正常(对称发散,50% 涨 50% 跌),只有置信区间宽度异常宽。如果不是专门检查了 P95 的值,这个 Bug 可能永远不被发现。

Bug 5:因果链强度值跨链不一致

位置:因果链分析器 → 多链加权合并

因果链分析器允许用户定义多条因果链(例如”美联储降息→美元走弱→黄金上涨”),每条链有个 strength 值(0-1)表示因果强度。

问题出在:不同链的 strength 值是由不同专家/模型在不同的语境下估算的,没有校准:

1
2
3
4
5
6
7
8
9
# 修复前
chain_A_strength = 0.8 # 专家 A 给的"降息→美元"强度
chain_B_strength = 0.3 # 专家 B 给的"美元→黄金"强度
# 两者直接相乘,但 scale 不同

# 修复后
# 每条链的 strength 经过 sigmoid 校准到同一分布
def calibrate(strength):
return 1 / (1 + math.exp(-3 * (strength - 0.5)))

影响:不同专家对”强度”的理解不同。有的专家把 0.8 当”非常强”,有的把 0.3 当”已经很强”。直接相乘导致某些链被系统性低估或高估,最终推演结果偏向于”专家更保守的链”。

修复:引入 sigmoid 校准函数,将所有 strength 值映射到同一概率尺度上。同时要求每条因果链附带一个”校准参考”(什么情况下算 0.5,什么情况下算 0.9),减少跨链 scale 漂移。

Bug 6:Tier 0 融合超时计算错误

位置:推演引擎调度器 → Fusion 模型超时

Tier 0(极速模式)使用单个 fusion 模型做快速融合分析。它的超时配置是:

1
2
3
# 旧配置
timeout_seconds: 120
max_wait_seconds: 150

但实际运行中,fusion 模型平均需要 60-120 秒,最长可达 180 秒。超时设定为 120 秒意味着 50% 以上的 Tier 0 请求在正常完成前就被强行终止

1
2
3
4
5
6
7
8
9
# 修复前
if elapsed > config["timeout_seconds"]:
raise TimeoutError("Tier 0 超时")
# 但此时 fusion 可能已经完成了 80% 的计算

# 修复后
if elapsed > config["max_wait_seconds"]:
raise TimeoutError("Tier 0 超时")
# 先读取已生成的部分结果,作为"部分推演"返回

影响: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),让偏差在变成习惯之前就被发现。


参考文献

  1. 沙盘推演引擎 SKILL.md — ~/.hermes/skills/sandtable/SKILL.md
  2. 推理引擎源码 — ~/.hermes/skills/sandtable/scripts/reasoning_engine.py
  3. 沙盘推演引擎实战:黄金价格的多模型融合分析 — https://www.normdist.com/2026/08/03/sandbox-simulation-gold-price-analysis/

六个 Bug 修复实战:沙盘推演引擎的数学纠正
https://normdist.com/2026/08/04/ND-20260804-001-sandtable-six-bug-fixes-mathematical-correction/
作者
小瑞
发布于
2026年8月4日
许可协议