花拾录
← 返回知识库

出现"不可能的数值"时先怀疑代码:公式、循环变量、键与日历四类错误

编程语言导入2026/09/220 阅读0 评论

当脚本算出一个"物理上不可能"的数字——比如收益率 −100%、t 值 −26——你会先怀疑什么?数据脏了?还是代码写错了?这篇文章的核心观点很短:先怀疑代码。

现象

回测收益率里出现了 −100% 这种不可能值,以及荒谬到离谱的统计量:

最大回撤: -1.00   (-100%,意味着本金全部归零)
t-statistic: -26

−100% 意味着"持仓价值归零",而这只标的明明还在正常交易;t = −26 则说明检验结果"极端显著地反向"——这些都是不该出现的数值。它们共同的特征是:从业务常识上说不通。

根因

这类"不可能的数值"几乎总是来自四类代码错误。逐个说清楚:

第一类:公式多减了一次 1。 把涨跌幅公式写成了多减一次的形式:

# 正确
ret = (p1 / p2 - 1) * 100

# 错误:多减了一个 1
ret = (p1 / p2 - 1 - 1) * 100

当 p1/p2 接近 1 时,这个多减的 1 会让收益率直接逼近 −100%——那个"不可能值"就是这么来的。

第二类:循环变量复用。 两层循环里,内层循环不小心用了外层残留的循环变量,或者反过来:

for i in range(len(prices)):
    for j in range(len(volumes)):
        # 错误:本意用 j,却写成了 i
        total += prices[i] * volumes[i]

第二个循环里用了上一个循环残留的变量,结果每次都在用同一个值,统计量自然错得离谱。给循环变量起互不冲突、有含义的名字(比如 i_price / j_vol),能有效避免。

第三类:键错位。 一侧按 A 键分组、对照组按 B 键分组,两边窗口没对齐。比如信号组按"信号日"对齐,对照组却按"自然日"对齐,两个窗口错开几天,算出来的差值毫无意义。

第四类:日历混用。 把很久以前的日期带进了当前窗口——比如某处用了"上一个交易日"的逻辑,但没判断节假日,于是一个跨假期的窗口把几天甚至几周前的数据也算了进来。

解决

针对四类问题,逐个修正:

# 1) 修正公式(去掉多余的一次减 1)
ret = (p1 / p2 - 1) * 100

# 2) 循环变量改为语义化的独立命名
for idx_sig, sig in enumerate(signals):
    for idx_ref, ref in enumerate(references):
        total += sig.price * ref.volume

# 3) 统一键的口径:两侧都按"交易日"对齐
sig_by_day = signals.groupby("trade_date")
ref_by_day = references.groupby("trade_date")

# 4) 用统一的交易日历有效集再统计
valid_days = calendar.intersection(set(sig_by_day.groups))

除了修代码,还要建立一个数值合理性断言的习惯,把这些"不可能的数值"拦在报告生成之前:

assert df["ret"].min() > -0.21, "收益率超出单日理论下限"
assert df["ret"].max() < 0.21,  "收益率超出单日理论上限"
assert abs(t_stat) < 20,        "t 值异常,检查样本独立性或键对齐"

延伸与预防

一句话:出现"不可能的数值"时,先怀疑代码,而不是先怀疑数据。 数据脏了通常是"局部离谱",而"整批结果都违背常识"往往是代码的系统性错误。

几个可操作的预防措施:

  • 给关键指标设合理区间断言,越界即报错,别让错误值流到报告里;
  • 循环变量语义化命名,两层以上循环尤其要注意;
  • 两侧对比的键必须显式统一,宁可在代码里多写一行对齐逻辑,也不要有"默认对齐"的心照不宣;
  • 日历用统一的有效交易日集,所有窗口裁剪都基于它。

这类 bug 的特点是"数学上合法、业务上荒谬"。养成对荒谬值的敏感度,并且第一时间往代码里找原因,能省下大量在数据上瞎找的时间。

评论(0)

  • 还没有评论,来抢沙发~

相关文章