1. 回测系统测试方法论:单元测试、集成测试、端到端测试在回测中的角色与分工
做回测系统这些年,我踩过最大的坑是什么?不是策略逻辑写错了,而是测试没做到位。你想想看,一个回测系统里跑着几百万行代码,从数据清洗到订单路由,从撮合引擎到资金核算,任何一个环节出问题,你得到的回测结果都是废纸一张。
所以今天咱们聊聊测试方法论。说白了,就是怎么用单元测试、集成测试、端到端测试这三板斧,把回测系统的质量守住。
1.1 单元测试:守住最小逻辑单元
单元测试,测的是单个函数或模块。比如一个计算夏普比率的函数,一个判断是否触发止损的逻辑,一个K线合并的算法。这些是回测系统最基础的砖块。
我个人习惯,每个核心函数至少写3个测试用例:正常输入、边界值、异常输入。举个例子:
# 测试夏普比率计算函数
def test_sharpe_ratio_normal():
returns = [0.01, 0.02, -0.01, 0.03, 0.005]
result = calculate_sharpe(returns, risk_free=0.02)
assert abs(result - 1.23) < 0.01 # 预期值
def test_sharpe_ratio_empty():
returns = []
result = calculate_sharpe(returns, risk_free=0.02)
assert result == 0.0 # 空序列返回0
def test_sharpe_ratio_constant():
returns = [0.01, 0.01, 0.01]
result = calculate_sharpe(returns, risk_free=0.02)
assert result == float('inf') # 无波动时夏普无穷大
为什么要这么较真?我在项目中遇到过一件事:一个看起来简单的calculate_position_size函数,因为浮点数精度问题,在极端行情下算出了负数仓位。单元测试一跑,立马现原形。要是等到集成测试才发现,定位问题得花半天。
1.2 集成测试:验证模块间的握手
单元测试过了,不代表系统能跑通。集成测试要验证的是模块之间的交互。比如数据模块给策略模块喂数据,策略模块给风控模块发信号,风控模块给订单模块下指令。这些链路一旦断了,回测结果就是错的。
我一般把集成测试分成两类:
- 数据流集成测试:检查数据从原始CSV到内存DataFrame的转换是否正确,时间戳对齐有没有丢数据,复权计算是否准确。
- 信号流集成测试:模拟一个完整的交易信号从生成到执行的路径,看最终成交记录是否合理。
举个例子,我曾经遇到一个经典问题:数据模块返回的OHLCV数据,时间戳是UTC的,但策略模块默认用了本地时间。结果回测时,每天的开盘价都错位了一个小时。集成测试里加一个「数据时间戳一致性检查」,这种问题一抓一个准。
1.3 端到端测试:模拟真实交易场景
端到端测试,说白了就是拿一个完整的策略,跑一遍完整的回测流程,然后跟预期结果对比。这是最接近真实交易的测试方式。
我建议端到端测试用已知结果的场景。比如:
- 一个简单的均线金叉策略,在2018年BTC的行情下,预期收益是多少?
- 一个网格交易策略,在震荡行情下,预期成交次数和盈亏是多少?
- 一个套利策略,在价差固定的情况下,预期无风险收益是多少?
这些场景的结果,你可以用手算或者Excel验证。然后让回测系统跑一遍,看结果是否一致。
我记得有一次,团队用模拟数据做端到端测试,所有指标都完美。结果一上真实数据,回测结果差了30%。查了半天,发现是数据清洗模块把停牌日的价格填充成了前收盘价,导致策略在停牌日「偷偷」交易了。端到端测试用真实数据,这种问题才能暴露。
1.4 三种测试的分工与配合
这三种测试不是互相替代的关系,而是互补的。我画了一张图来说明它们的分工:
从这张图你能看出什么?
- 单元测试是地基,数量最多,跑得最快。它保证每个函数都是对的。
- 集成测试是承重墙,数量适中。它保证模块之间能正常通信。
- 端到端测试是屋顶,数量最少。它保证整个系统能完成一次完整的回测任务。
我建议的配比是:70%单元测试,20%集成测试,10%端到端测试。这个比例不是拍脑袋定的,而是基于我多年的经验。单元测试写得多,后期改代码时心里有底。集成测试写太多,维护成本会爆炸。端到端测试写太多,跑一次要等半天,反而拖慢开发节奏。
1.5 避坑指南:我踩过的三个坑
最后分享几个我亲身踩过的坑,希望能帮你少走弯路。
我曾经用人工合成的完美K线数据做测试,所有测试都通过了。结果一上真实数据,回测结果跟预期差了20%。原因是真实数据里有跳空、有复权、有停牌,这些在人工数据里都没模拟到。后来我强制要求:所有测试数据必须来自真实历史行情,最多做一下裁剪。
回测系统里大量涉及金额计算。浮点数精度问题会导致资金核算差几分钱。别小看这几分钱,在千次交易后,误差会累积到几百块。我的解决方案是:所有金额计算用Decimal类型,或者用整数表示最小单位(比如用「分」代替「元」)。
很多团队写完测试用例就不管了。等代码重构后,测试用例还是老的,跑出来全是红的。我要求团队:每次修改核心逻辑,必须同步更新相关测试用例。测试用例跟代码一样,需要持续维护。
嗯,测试方法论就聊到这里。记住一句话:没有经过充分测试的回测系统,跑出来的结果只是数字,不是结论。下一节咱们聊聊具体怎么搭建测试框架,包括pytest的配置、mock的使用、以及如何做性能测试。到时候我会分享一些实战中的小技巧。