研究摘要
部分图表为演示数据
回测最常见的问题不是公式算错,而是用了当时拿不到的信息、做了当时做不成的交易,或者从许多次尝试里挑出了最好看的一次。本文按研究流水线的顺序,说明我们在每一环如何防范这些偏差:时点数据与不含幸存者偏差的股票池,只用已到期标签的滚动训练,次日开盘执行并计入涨跌停、停牌、T+1、手数与分项费用的成交模拟,同规则随机孪生对照与多重检验校正,以及上线前的影子盘。文中的市场统计来自 A 股实际数据,费用计算器按现行规则计算,其余图表为演示数据。
- 训练方式
- 滚动 · 按季度
- 成交假设
- 次日开盘
- 交易费用
- 分项 · 按日期
- 上线之前
- 影子盘核对
回测为什么会失真
一条漂亮的回测曲线,最常见的问题不是公式算错,而是下面四类偏差中的某一类:
- 前视偏差:用了当时还拿不到的信息,例如按报告期而不是公告日使用财报,或者在收盘后才知道的价格上成交;
- 幸存者偏差:股票池里只有今天仍在交易的股票,那些退市、被并购的公司从历史中消失了;
- 选择偏差:尝试了许多版本,只报告其中最好看的一个(参见多重检验);
- 不可执行的交易:在涨停价买入、在跌停价卖出、忽略停牌与 T+1、低估费用,或者按远超市场容量的规模成交。
我们的原则可以归结为一句话:回测中的每一个数字,都应当能由一个站在那一天、只掌握那一天信息的人重现出来,他下的每一笔单也都应当成交得了。下文按研究流水线的顺序,说明每一环如何落实这条原则。
- 01
数据
当时知道什么?
- 时点财务
- 含退市股票
- 公司行为
- 02
特征
收盘时能算出什么?
- 收盘前可得
- 截面标准化
- 行业与市值
- 03
标签
要预测的是什么?
- 未来相对收益
- 到期才可用
- 04
训练
模型见过答案吗?
- 滚动窗口
- 按季度重训
- 样本外拼接
- 05
组合
预测如何变成持仓?
- 权重上限
- 换手预算
- 成本门槛
- 06
成交
这些单能成交吗?
- 次日开盘
- 涨跌停与停牌
- T+1 与手数
- 分项费用
- 07
评估
收益从哪里来?
- 随机孪生对照
- 多重检验
- 稳健性
- 08
影子盘
上线后是否一致?
- 封存决策
- 同一套代码
- 逐日核对
具体的模型、特征与组合规则不在本文讨论范围之内;这里关心的是它们被检验的方式。
数据与标签:只用当时知道的信息
数据
研究使用的每一份数据都带有“何时可知”的时间戳。行情在当日收盘后可用;财务报表按公告日生效,从公告之后的第一个交易日起才进入计算,事后更正的版本不会被写回历史(参见基本面数据的时点)。行业分类同样按当时的归属,而不是今天的归属。
股票池按每一天的上市状态构建,包含之后退市的公司,只在决策当日判断是否可交易:上市满约半年、不是风险警示股、当日未停牌。价格收益使用复权价计算,现金分红与送转股则在成交模拟中按实际发生的日期处理。
研究在冻结的数据快照上进行。实时数据会被修订、补录,同一段历史在不同日期读出的结果可能不同;固定快照之后,任何一个结果都可以逐位复现,修订带来的差异也可以单独度量。
特征与标签
特征只用决策日收盘为止的数据计算,并在每个截面上标准化,使不同量纲的变量可以放在一起(参见标准化)。
标签是模型要预测的对象:未来一段时间(例如 20 个交易日)的收益,扣除行业、规模等共同因素之后的相对部分。它有一个容易被忽视的性质:第 日样本的标签,要到 日收盘才完全确定。在第 日训练模型时,最近 个交易日的样本还没有答案,它们必须被排除在训练集之外,否则模型就在用未来的收益学习。
滚动训练:模型从没见过它要预测的时期
我们按时间滚动训练与检验。每个季度初 ,只用此前若干年内、标签已经到期的样本重新训练模型:
其中 为训练窗口, 为预测期。训练好的模型只给接下来这个季度的股票打分;到了下一个季度初,窗口向前滚动,再训练一次。把每个季度的样本外分数按时间拼接起来,就得到一条连续的样本外分数序列。
滚动训练与样本外打分
训练窗口
预测期
- 训练样本
- 2017-01 至 2019-12
- 剔除:标签未到期
- 最后 20 个交易日
- 样本外打分
- 2020-01 至 2020-03
- 已拼接的样本外
- 17 个季度
后续的一切,包括组合构建、成交模拟与统计检验,都只使用这条拼接起来的样本外分数。任何一个日期的分数,都来自一个在该日期之前训练完成、从未见过该日期之后任何标签的模型。
与之配套的是一条纪律:模型的结构与超参数在看到样本外结果之前就固定下来。如果看了结果再回头调整,调整本身就成了一次新的尝试,需要重新登记(见第五节)。
成交模拟:每一笔单都要成交得了
组合规则把每日的分数变成目标持仓,其中包含单只股票的权重上限、每日的换手预算,以及只有预期收益足以覆盖交易成本时才调仓的约束。目标持仓随后交给成交模拟器。模拟器逐日、逐只股票地维护现金、持仓与在途资金,每一笔订单都要经过下面的检查。
一笔订单从决策到可卖出
第 t 日
- 15:00收盘。当日行情与盘后公告入库
- 收盘后计算特征与分数,只用到 t 日收盘为止的数据
- 收盘后生成目标持仓,按 t 日收盘价折算为股数,形成订单
第 t+1 日
- 09:30开盘。按开盘价加滑点成交
- 逐笔停牌、开盘即封板、超出参与率上限、不足一手或现金不足的部分不成交
- 收盘当日买入的股份不可卖出;未成交部分留待下一次决策
第 t+2 日起
- 开盘t+1 日买入的股份可以卖出
- 此后现金分红在派息日到账,送转股在上市日后才可卖出
时点与成交价
决策使用第 日收盘为止的信息,订单按第 日收盘价折算为股数,在第 日开盘成交,成交价为开盘价加上单边 10 个基点的滑点。收盘价只在收盘之后才知道,用它成交等于提前看到了价格。
停牌、涨跌停与 T+1
- 停牌:当日停牌的股票不能成交,持仓按最后一个收盘价估值;
- 涨跌停:开盘即在涨停价的股票不能买入,开盘即在跌停价的股票不能卖出,未成交的部分在下一次决策时重新检查。涨跌停价取交易所每日公布的价格,主板、创业板、科创板、风险警示股与新股的不同规则因此自动生效;
- T+1:当日买入的股份最早在下一个交易日卖出;送转股在上市日之后才可卖出。
这些规则在大多数日子里影响很小,却在最关键的日子里起决定作用。
每天有多少股票无法按计划成交
统计口径
全天停牌的股票占比
- 跌停最多(2020-02-03)
- 83%
- 涨停最多(2015-07-06)
- 40%
- 停牌最多(2015-07-09)
- 52%
- 交易日
- 2,852
2015 年 7 月 8 日,沪深两市 2,779 只股票中有 1,361 只开盘即跌停,约占一半;次日全天停牌的股票达到 1,438 只,同样超过一半。2020 年 2 月 3 日,3,767 只股票中有 3,127 只开盘即跌停,占 83%。2024 年 10 月 8 日则是另一个方向:1,820 只股票开盘即涨停。在这些日子里,“按收盘价调仓”的回测会卖出实际上卖不掉的股票、买入实际上买不到的股票。停牌的影响在 2019 年停复牌新规实施之后大幅下降:2015 至 2018 年平均每天有 150 到 390 只股票全天停牌,此后降到每天 10 只左右。
手数、参与率与资金
买入按 100 股的整数倍下单(科创板不少于 200 股,超出部分可按 1 股递增),只有清仓时才允许卖出零股。每只股票每天的成交量不超过其过去 20 个交易日日均成交量的 1%,超出的部分留到之后的交易日;这个上限只使用决策时已知的成交量,而不是当天实际的成交量。卖单先于买单执行,买入不超过当时可用的现金,并保留少量现金缓冲。
费用与税
费用逐笔、逐项计算,并按成交日期适用当时的规则:
| 项目 | 方向 | 计费方式 |
|---|---|---|
| 佣金 | 买卖双向 | 成交金额的万分之三(含交易所经手费与证管费),单笔最低 5 元 |
| 印花税 | 仅卖出 | 2023 年 8 月 28 日之前 0.1%,此后 0.05% |
| 过户费 | 买卖双向 | 2022 年 4 月 29 日之前 0.002%,此后 0.001% |
| 滑点 | 买卖双向 | 单边 10 个基点;稳健性检验中加倍到 20 个基点 |
| 红利税 | 卖出时补扣 | 持股 1 个月以内 20%,1 个月至 1 年 10%,超过 1 年免征 |
最低佣金会让小额订单的成本成倍上升。一笔 2 千元的订单,佣金就要 25 个基点;账户越小、持仓越分散,这部分成本越显著。图五可以按不同的金额、方向与日期计算单边成本。
一笔订单的单边成本
方向
成交日期
假设:佣金为成交金额的万分之三(含交易所经手费与证管费),单笔最低 5 元;印花税仅对卖出征收,2023 年 8 月 28 日起由 0.1% 降为 0.05%;过户费双向收取,2022 年 4 月 29 日起由 0.002% 降为 0.001%;滑点按单边 10 个基点计。本笔过户费 ¥0.10。
- 佣金(含规费)
- ¥5.00 · 5.0 bp
- 印花税
- ¥5.00 · 5.0 bp
- 滑点
- ¥10 · 10.0 bp
- 合计(含过户费)
- ¥20.1 · 20.1 bp
分红、退市与容量
现金分红在除息日计入应收、在派息日到账,红利税在卖出时按持有期补扣。持有期间退市的股票,按零回收处理,这是一个保守的假设。基准情形不计现金的利息收入,利息的贡献单独报告。
同一组目标持仓会在 50 万、500 万与 2000 万元三档资金下分别重放。小账户受最低佣金与整手限制的影响更大,大账户则更多地受参与率上限约束;此外,我们按每一笔模拟成交的金额,用平方根冲击模型(参见冲击成本与容量)另行估计冲击成本并从收益中扣除。只有在目标资金规模下仍然成立的结果,才被当作结果。
评估:收益从哪里来
收益、波动、夏普、回撤、换手与 IC 等指标的定义见《从 IC 到夏普比率》。本节讨论一个更根本的问题:一段收益里,有多少来自选股的信息,有多少只是承担了某种风格暴露。
同规则随机孪生
A 股的横截面收益有很大一部分来自风格:小市值、某些行业、高换手或低换手。一个偏向小盘股的组合,在小盘股占优的年份里可以大幅跑赢指数,但这部分收益并不需要任何选股能力。与指数比较,混淆了两者。
我们为每个策略构造“随机孪生”:在每一个截面上,把模型的分数在“行业 × 市值 × 流动性”分层内部随机打乱,再用完全相同的组合规则与成交模拟重放。孪生与策略有同样的行业、市值与流动性暴露,同样的换手与交易约束,唯一的区别是孪生的排序里没有信息。重复若干个随机种子,把孪生的平均表现作为对照:
衡量的才是选股信息的贡献。
策略、指数与 200 个随机孪生
横轴:年化收益;纵轴:孪生个数。孪生的收益由风格暴露与随机波动决定,演示用。
- 相对指数的超额
- +11.2%
- 相对孪生的增量
- +4.0%
- 孪生的离散(标准差)
- 2.9%
- 高于策略的孪生
- 15 / 200
统计检验与多重检验
收益序列存在自相关,简单的 检验会高估显著性。我们用块自助法(block bootstrap)对日度超额收益重抽样,得到 的置信区间与单侧 值。每一轮研究只登记少数几个假设作为一个检验族,在族内做 Holm 校正;跨轮次则记录全部试验的次数,计算紧缩夏普比率与回测过拟合概率。
稳健性检验
一个结果要被接受,除了整体显著,还需要同时通过一组稳健性检验。它们回答的都是同一个问题:这个结果是不是依赖于某个特定的时期、参数或假设。
| 检验 | 做法 | 回答的问题 |
|---|---|---|
| 子区间 | 把样本分成三段,至少两段为正 | 是否只来自一段行情 |
| 季度胜率 | 至少 55% 的季度相对孪生为正 | 优势是否持续 |
| 成本加倍 | 滑点加倍到单边 20 个基点 | 是否依赖乐观的成本 |
| 路径扰动 | 起始资金改动千分之一至千分之二 | 是否依赖某条偶然的成交路径 |
| 规则阈值 | 调仓门槛上下调整 | 是否依赖某个特定参数 |
| 去掉任一年 | 依次剔除每一个年份 | 是否依赖单一年份 |
| 冲击成本 | 按模拟成交估计平方根冲击并扣除 | 规模上去之后是否还在 |
| 更早的区间 | 在设计阶段未使用的年份上重放 | 是否只在研究样本内成立 |
| 资金规模 | 50 万、500 万、2000 万元分别重放 | 小账户与目标规模下是否成立 |
预注册与试验台账
检验标准本身也可能被结果牵着走。因此每一轮研究都在看到结果之前,写下要检验的假设、比较的对象、通过的标准与停止的规则;所有运行,包括失败的与中途放弃的,都记入试验台账;任何偏离事先计划的地方都要记录原因。结果出来之后,标准不再改动。
这样做的代价是显而易见的:大多数登记过的想法,都在这一步被否决。但也只有这样,留下来的结果才值得相信。
影子盘:上线前的最后一道关
通过全部检验的策略,并不直接投入资金,而是先以影子盘的形式运行:用生产环境的同一套代码、实时的数据,每个交易日收盘后生成当天的决策。决策在下一个交易日开盘之前封存,记录生成时间与内容的校验值,此后的任何改动都会被识别并记录在案;随后用同一个成交模拟器逐日重放,得到影子盘的净值。
影子盘回答的是研究回测回答不了的问题:生产数据与研究快照之间的差异有多大,代码在真实的时间压力下是否按预期运行,每天的决策是否与研究环境给出的一致。上线之前,影子盘的决策要能与研究环境对同一天的重算逐项对上,个别差异要能追溯到具体的数据修订;只有在足够多的交易日里运行无误之后,才会考虑投入资金。
边界:回测仍然做不到的事
即便做到以上各点,回测仍然只是对真实交易的近似:
- 排队与成交顺序:在涨跌停价附近,真实能否成交取决于排队的先后,模拟器只能保守地认定不成交;
- 日内过程:开盘价附近的真实成交价格与数量,并不总能用一个价格加固定滑点描述;
- 冲击与拥挤:冲击模型基于经验规律,同类策略同时交易时的拥挤效应难以从历史中估计;
- 市场结构的变化:交易规则、费率与投资者结构都在变化,过去的规律未必延续。
因此我们在不确定的地方一律选择保守的假设,并用影子盘、以及之后的小规模实盘,去核对模拟与现实之间的差距。
| 环节 | 常见偏差 | 我们的做法 |
|---|---|---|
| 数据 | 前视、幸存者偏差 | 按公告日生效的时点数据;包含退市股票;冻结快照 |
| 标签与训练 | 用未到期的标签训练 | 只用已到期的标签;按季度滚动重训;拼接样本外分数 |
| 成交 | 不可执行的交易 | 次日开盘成交;停牌、涨跌停、T+1、手数、参与率上限 |
| 费用 | 低估成本 | 佣金、印花税、过户费、滑点、红利税逐笔按日期计算 |
| 评估 | 把风格当作能力 | 同规则随机孪生对照;块自助法;Holm 校正 |
| 选择 | 只报告最好的一次 | 预注册;试验台账;紧缩夏普与过拟合概率 |
| 上线 | 研究与生产不一致 | 影子盘;决策封存;与研究环境逐项核对 |
参考文献
- Harvey, C. R., & Liu, Y. (2015). Backtesting. The Journal of Portfolio Management, 42(1), 13–28.
- Bailey, D. H., Borwein, J. M., López de Prado, M., & Zhu, Q. J. (2017). The Probability of Backtest Overfitting. Journal of Computational Finance, 20(4), 39–69.
- López de Prado, M. (2018). Advances in Financial Machine Learning. Wiley.
- White, H. (2000). A Reality Check for Data Snooping. Econometrica, 68(5), 1097–1126.
- Holm, S. (1979). A Simple Sequentially Rejective Multiple Test Procedure. Scandinavian Journal of Statistics, 6(2), 65–70.
- Künsch, H. R. (1989). The Jackknife and the Bootstrap for General Stationary Observations. The Annals of Statistics, 17(3), 1217–1241.
- Almgren, R., Thum, C., Hauptmann, E., & Li, H. (2005). Direct Estimation of Equity Market Impact. Risk, 18(7), 58–62.
- Nosek, B. A., Ebersole, C. R., DeHaven, A. C., & Mellor, D. T. (2018). The Preregistration Revolution. Proceedings of the National Academy of Sciences, 115(11), 2600–2606.