最近整理一段旧回测代码时,我发现同一份收盘价数据,换了 pandas 版本后,收益率序列里多出了一批 NaN。最初以为是行情文件损坏,沿着日期查下去才发现,这些位置大多对应停牌或缺失报价
旧代码只有一句
ret = close.pct_change()
问题出在缺失值怎样处理。较早版本的 pandas,pct_change 的 fill_method 默认曾是 "pad",相当于先把上一个有效价格向前填充,再计算相对变化。这样一来,缺失当天常会得到 0,看上去收益率很完整。当前官方文档中,fill_method 为 None;pct_change 直接依据原始序列计算,缺失值会保留下来。
这不是简单的“新版本算错了”。0 和 NaN 表达的是两件不同的事:0 表示确认价格没有变化,NaN 表示这一期没有足够信息。停牌期间把价格前向填充,在按自然日绘制净值时可能合理;但在估计波动率、相关性或训练模型时,大量人为的 0 会压低风险,看起来反而比真实情况更平稳。
我后来把处理方式从函数默认值里拆了出来。需要交易日收益率时,先保留缺失:
ret_raw = close.pct_change(fill_method=None)
如果业务上明确要把短缺口视为价格不变,就先限制填充长度,再计算:
ret_filled = close.ffill(limit=1).pct_change(fill_method=None)
这里的 limit=1 不是通用答案,只是把规则写明。连续缺失两天以上仍会留下 NaN,避免一只长期停牌股票被填成一长串零收益。
这次排错让我重新检查了回测环境:依赖版本要锁定,缺失值策略要写进配置,单元测试里也要放入连续缺口。收益率公式很简单,真正影响结果的,常常是公式执行前那一步数据处理。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。