
2010年5月6日华尔街的闪电崩盘发生之后美国证监会的调查人员花了将近五个月时间才定位到灾难的触发点。最终的调查报告里有一段描述让所有写代码的人看完都会后背发凉引发连锁崩盘的那个算法交易程序在设计的时候只用了一年的历史数据做回测。那一年是2009年。如果你还有印象的话2009年是金融危机之后全球央行集体放水、资产价格单边上涨的一年。在那个数据集里任何做多策略都会显得聪明绝顶。那个算法从来没有在2008年的数据上跑过。它不知道当市场流动性突然枯竭、买卖价差拉大到平时的一百倍时它的下单逻辑会发生什么。它的开发者可能也没有想过要给它做这个测试。也许他们想过但觉得2008年那种事情不会再来一次了。2010年5月6日下午两点三十二分2008年的幽灵回来了。只用了不到五分钟它就把道琼斯指数打掉了将近一千点。你电脑里那个花了两个周末写出来的交易策略用的是哪一年的数据做回测模拟注入把你那个“绝世策略”扔进历史最烂的年份里单元测试的核心思想用一句话就能说清楚把你写的函数从真实系统里隔离出来给它一组你提前准备好的输入数据检查它的输出是否和你预期的一致。这组输入数据里不仅要有正常情况下的典型输入还必须包含边界值——空列表、负数、超大数值、长度为零的字符串——以及所有你能想到的异常情况。你之所以花时间写这些测试用例是因为你知道生产环境里的真实输入不会只在你设计好的那条路径上规规矩矩地走。你的交易策略就是一个函数。它的输入是市场数据——价格序列、成交量、财务报表、宏观指标它的输出是买卖信号——什么时候买、买多少、什么时候卖。在你把这个函数部署到实盘、绑上你的银行卡之前你要做的事情跟你上线任何一个新服务之前做的事情应该完全一样把它扔进一组你精心挑选的历史数据里看它能不能活下来。第一步把你设想的交易策略用自然语言或者伪代码写下来。必须精确到没有任何歧义的程度。不是“当股票低估时买入”而是“当市盈率低于过去五年历史分位数的20%以下且ROE大于15%且资产负债率低于行业均值时买入目标仓位为目标总仓位的5%”。如果你连把自己的策略用这种精度写下来都做不到那说明你其实没有策略你只有一种模糊的“我觉得应该这样操作”的冲动。这种冲动不应该被赋予管理你血汗钱的权限。第二步划定你的测试数据集。这是大多数工程师在回测时犯下致命错误的地方。他们用最近三年或者最近五年的数据回测发现策略表现不错就觉得自己找到了圣杯。但最近五年可能恰好是一轮完整的牛市周期或者是一个波动率持续下降的平稳期。你的策略在这些数据上表现好可能只是因为它恰好适合这一种市场状态而不是因为它真的能盈利。你应该用什么数据来测你应该用你能找到的最糟糕的数据来测。你要故意挑选历史上那些臭名昭著的暴跌年份把你心爱的策略扔进去看它能不能不全军覆没。2008年的全球金融危机——A股那年跌了超过65%。2015年下半年的A股股灾——从六月中旬到八月底两次断崖式下跌中间夹着一次让无数人重仓杀回然后再次被埋的反弹。2018年全年去杠杆阴跌——上证指数几乎从头跌到尾没有一次像样的反弹所有试图抄底的人全部被埋。2020年3月的全球熔断——连巴菲特都说活了八十九年没见过这种场面。2022年中概股的流动性崩塌——从高点回撤超过70%过程中每次你以为已经跌到地下室了下面又出现了一层地下车库。把你那个年化收益百分之四十的策略用这些年份的数据跑一遍。不要挑数据不要用你早已知道结果的样本做“优化”。原封不动地跑。如果在2008年的数据上你的策略亏掉了百分之六十那这个策略就不能被部署到实盘不管它在2019年到2021年的牛市里表现得有多好。一个在极端行情中会被打穿的系统迟早会被极端行情打穿。你上线之前不给它做压力测试市场会替你做的。市场做的测试费用从你的账户里扣。Mock数据别只测牛市你的策略在熊市里长什么样在软件测试里mock是一种模拟外部依赖的技术。你要测的模块依赖一个外部API但你不想在每次跑测试的时候真的去调那个API——太慢、太贵、而且那个API可能本身不稳定会导致你的测试偶发失败。于是你写一个假的API它的响应格式和真的一样但返回的数据是你提前预设好的。这样你就能精确地控制你的测试模块所接收到的每一个输入变量。在投资策略的回测里你需要mock的数据是整个宏观环境。大多数工程师回测的时候只mock了一种宏观环境温和增长、流动性充裕、波动率适中、趋势向上的正常市场。这就像你测试一个服务的时候只测了它在200 QPS、所有下游服务都正常的情况。真到了生产环境一旦某个下游挂了或者流量突然飙到2000 QPS你的服务就直接OOM了。你应该至少mock以下四种完全不同的宏观环境每一种都把你的策略扔进去跑一遍。第一种单边下跌熊市。用2018年的A股数据。这个环境的特点是几乎没有持续性反弹任何追涨行为都会被次日低开埋掉估值体系持续收缩便宜了还能更便宜市场情绪从怀疑到绝望逐步加深交易量持续萎缩。你的策略在这个环境里是空仓等待还是会反复被假突破骗进去然后止损第二种剧烈波动猴市。用2015年下半年的A股数据。这个环境的特点是日内波动巨大单日涨跌幅动辄超过百分之五趋势转向极快今天是千股跌停明天就可能是千股涨停流动性的突然消失和突然涌入可以在同一个星期内交替出现。你的策略能承受这种级别的波动率吗你的止损单在这种波幅下会被频繁触发到你把所有子弹都打光吗第三种流动性枯竭。用2020年3月全球熔断期间的数据。这个环境的特点是所有风险资产同时下跌相关性趋向于一避险资产可能也在跌因为所有人都在抢现金买卖价差急剧拉大你的市价单可能会成交在你完全想象不到的价格上。你的策略在这个时候是持有现金不动还是在疯狂地补仓买入你认为被错杀的资产第四种长期横盘震荡。用2016年到2017年上半年的大盘蓝筹慢牛叠加中小创横盘的数据。这个环境的特点是大盘指数可能没怎么跌但你持仓的板块就是一直在一个箱体里来回震荡不上不下。你的策略在这个环境里会不会频繁发出假信号让你在一次又一次的窄幅波动里被交易成本和滑点慢慢放血而死把这四种mock数据全部跑完之后如果你的策略在四种环境里的期望收益都是正的或者至少只在其中一种环境里出现可控的亏损那么这个策略值得进入蓝绿部署的沙箱。如果你的策略在四种环境里的三种都亏得一塌糊涂只在牛市环境里赚钱那这个策略就不是一个可部署的系统它是一个需要牛市才能续命的赌注。你不能把家庭财富押在一个需要永远好运气的赌注上。覆盖率报告你的策略能解释多少种市场状态在软件工程里测试覆盖率是一个衡量你测试套件质量的硬指标。如果你的单元测试只覆盖了核心业务逻辑的百分之三十另外百分之七十的代码路径从来没有在任何测试中被执行过那么你的系统在生产环境中触发那百分之七十路径的时候会发生什么没有人知道。通常不是什么好事。你的交易策略也需要一份覆盖率报告。你不一定需要用花哨的工具来生成它一张纸一支笔就够了。回答以下五个问题第一你的策略在趋势上涨的市场里表现如何这是你大概率测过的场景覆盖率百分之百。第二你的策略在趋势下跌的市场里表现如何如果你从没测过这个场景覆盖率从这一条开始往下掉。第三你的策略在横盘震荡的市场里表现如何连续几个月没有趋势你的策略是耐心空仓还是反复试探第四你的策略在市场风格切换的时候表现如何从成长股行情突然切换到价值股行情从大盘股行情突然切换到小盘股行情你的策略是能自动适应还是只在某一种风格下有效第五你的策略在极端事件冲击的时候表现如何一天之内大盘跌百分之七你的策略是在暴跌之前发出了减仓信号还是在暴跌之中发出了恐慌信号还是暴跌之后才告诉你“现在应该止损了”这五个问题每一个都对应着一条必须被覆盖的代码路径。如果你的策略只能覆盖第一条——只能在上涨市场里赚钱——那你的策略覆盖率就是百分之二十。一个测试覆盖率只有百分之二十的系统你绝对不会允许它上线。那一个只能解释百分之二十市场状态的交易策略你为什么敢把真金白银交给它测试覆盖率不够的策略不是没有被发现的宝石。是还需要继续迭代的半成品。它应该留在你的本地开发环境里继续重构而不是被部署到绑着你银行卡的生产环境里去处理真实的用户请求。你每一次把半成品策略推向实盘都等于你作为运维工程师眼睁睁看着一个测试覆盖率远低于标准的构建包被强制上线还心存侥幸地希望它不会把你半夜叫起来处理事故。这一节没有给你新的交易技巧。它给了你一个在你和你的血汗钱之间的验证层。以后每当你产生一个“我好像找到了一个不错的策略”的念头时不要直接打开交易软件下单。先做这一节里的三件事把你的策略写成一个输入输出明确的函数用你精心挑选的最烂的历史数据去跑它用至少四种不同的mock环境去测它最后用那五个问题评估它的覆盖率。三件事做完能通过所有测试再把它放进第三章第九节的蓝绿部署沙箱里用小资金试。大多数时候你会发现那些让你半夜兴奋得睡不着的策略灵感连2008年的数据都跑不过去。这不是你能力的失败。这是单元测试在正常工作。在下一节我们要把这些策略代码、日志记录、测试用例、以及你过去几年的所有投资决策全部纳入一个统一的版本控制系统里去。你的投资体系不是一篇文章不是一套静态的规则清单。它是一个软件项目它有版本号有变更记录有分支策略也必须有回滚能力。当你下一轮熊市里手忙脚乱的时候你不需要重新思考一切。你只需要回滚到上一个稳定版本。