第六部分 · 项目实战与工程化 · 第 19 章
实战一:从零做一个指数增强回测
前面讲了很多概念,这一章带你真正走一遍完整的研究流程。目标很具体:选一个指数作为基准,在它的成分股里构造几个简单因子,每月调仓,扣除成本,再和基准比较。做完你会发现,量化项目真正难的地方不是写出买卖信号,而是让数据、时间、交易规则、风险和报告全都对得上——这也是把“懂概念”变成“能动手”的第一道坎。
读完这一章,你会明白
- 指数增强项目的核心是基准、股票池、因子、组合、成本和报告闭环。
- 教学项目要先把时间顺序、交易规则和成本做对,再追求模型复杂度。
本图从“项目目标和边界”走到“写复盘结论”,用于先看第 19 章的关键路径,再回到正文补细节。
1. 项目目标和边界
先把项目边界写清楚。基准选择中证 500 或沪深 300,股票池使用历史当时成分股,调仓频率为每月一次,交易价格用下一个交易日开盘价或成交均价近似,扣除佣金、印花税、过户费和滑点。
这个项目的目标不是寻找最强策略,而是建立研究框架。你要能回答: 策略相对基准有没有超额? 超额来自哪些因子? 换手和成本多高? 最大回撤多深? 哪些年份失效? 结果是否依赖少数参数?
项目边界越清楚,越不容易中途改规则。很多新手回测越做越好看,是因为不断偷偷改假设。先写边界,后面所有改动都要记录。
| 输入 | 第一版字段 | 为什么需要 |
|---|---|---|
| 行情 | date, code, open, close, amount, adj_factor, paused, limit_up, limit_down | 算收益、判断可交易、估计成本 |
| 股票池 | date, code, index_code, index_weight | 保证只在当时指数成分里选股 |
| 行业 | date, code, industry | 行业中性和暴露分析 |
| 因子 | date, code, value_bp, momentum_60, quality_roe | 生成综合分数 |
第一版项目只要这些表就能跑。不要一开始追求字段极多,先让每个字段都能解释、能检查、能追溯。
2. 准备股票池
股票池决定你在哪个市场里选股。指数增强项目应使用历史当时的指数成分,而不是今天的成分回填过去。比如回测 2018 年,就要知道 2018 年每个调仓日中证 500 有哪些成分股。
还要设置可交易过滤。上市未满一定天数的股票可以剔除,ST 股票可以剔除,停牌股票不能买入,涨停股票通常不能假设买入,跌停股票不能假设卖出,成交额太低的股票要小心容量。
过滤规则要同时用于回测和未来模拟盘。如果回测里剔除了流动性差的股票,实盘也要剔除;如果实盘不能买 ST,回测也不能买。否则训练目标和交易现实不一致。
3. 准备价格和复权数据
价格数据至少需要交易日、代码、开盘价、收盘价、成交量、成交额、复权因子、涨停价、跌停价和停牌状态。收益计算可以用复权价格,交易模拟要用真实价格口径。
要检查价格是否异常。是否有 0 价格,是否有重复日期,复权因子是否跳变异常,停牌日成交量是否为 0,涨跌停价格是否和昨收关系合理。数据检查不是可选项,它会挡住很多假收益。
调仓时点也要严谨。如果每月最后一个交易日收盘后生成信号,下一交易日才能买卖。不要用调仓日收盘价算特征,又假设调仓日收盘价成交。
| 时间点 | 动作 | 允许看到什么 |
|---|---|---|
| T 日收盘后 | 计算因子和目标组合 | T 日收盘及以前已公开数据 |
| T+1 开盘 | 按规则提交订单 | 不能改变 T 日已经确定的信号 |
| T+1 盘中 | 模拟成交和未成交 | 涨跌停、停牌、成交额约束 |
| T+1 收盘后 | 更新持仓和净值 | 成交回报、收盘估值、费用 |
这张时间线能挡住最常见的未来函数。只要你想在 T 日收盘价上成交,就必须先问自己信号是不是也用了 T 日收盘价。
4. 构造几个简单因子
教学版可以先用四类因子。第一是估值,比如 PB 或 PE 的倒数。第二是动量,比如过去 60 日收益率。第三是波动,比如过去 60 日日收益标准差的负值。第四是流动性,比如过去 20 日平均成交额或换手率。
每个因子都要处理极端值和缺失值。常见做法是 winsorize 截尾,再做标准化。行业差异明显的因子,可以在行业内部标准化。这样能避免某个行业天然估值低,导致组合长期变成行业押注。
因子方向要统一。分数越高代表越看好。低 PB 如果代表便宜,可以使用 PB 的负值或 BP = 1 / PB。波动越低越好,可以使用负波动。方向不统一,多因子合成会混乱。
value_bp = 1 / pb
momentum_60 = close_adj[t] / close_adj[t-60] - 1
low_vol_60 = -std(daily_return[t-59:t])
quality_roe = net_profit_ttm / equity
这些公式只是教学口径。真实项目里还要处理 PB 为负、上市不满 60 天、财务公告日、行业差异和极端值。但先把第一版公式写出来,读者才知道因子不是一句抽象口号。
5. 合成打分
最简单的多因子合成是等权平均。把每个因子标准化成均值 0、标准差 1,方向统一后相加或求平均,得到综合分数。分数越高,模型越看好。
也可以给不同因子设置权重,比如价值 30%、质量 30%、动量 20%、低波 20%。但初学时不要急着优化权重。权重优化很容易过拟合,尤其当你不断挑历史最好的组合。
合成后要看分数分布。是否有极端值,是否某个因子主导,是否某些行业整体高分,是否缺失覆盖过多。模型输出之前,先检查输入和中间结果。
教学版可以选择综合分数最高的 50 只股票等权持有。等权很简单,但也有问题: 小市值股票和大市值股票权重一样,可能偏离基准较大。
更接近指数增强的做法是加入约束。单只股票不超过 3%,行业权重相对基准不超过一定范围,组合股票数不少于 80 只,现金保留 1% 或 2%,成交额过低的股票降低权重。
先从简单组合开始,再逐步加约束。每加一个约束,都要比较收益、回撤、换手和跟踪误差变化。不要一次性堆很多限制,否则你不知道哪个限制起作用。
candidates = tradable_universe[date]
score = z(value_bp) + z(momentum_60) + z(quality_roe) + z(low_vol_60)
selected = top_n(score, n=50)
target_weight = 0.98 / len(selected)
for code in selected:
target[code] = min(target_weight, 0.03)
这段伪代码故意很简单: 先选前 50,再等权,再保留 2% 现金,再限制单票 3%。它不是最优组合,但足够让第一版回测从分数走到仓位。
6. 模拟调仓
每个调仓日,先读取当时股票池和因子,生成目标组合。然后和当前持仓比较,得到需要买卖的数量。买入普通 A 股通常按 100 股整数取整,卖出还要处理历史持仓产生的零股;ETF、可转债等品种交易单位不完全相同,需要按品种另行建模。资金不足时要按比例缩减。
买入时要检查股票是否停牌、是否涨停、成交额是否足够;卖出时要检查是否停牌、是否跌停、是否有可卖持仓。如果某只股票卖不掉,它会留在组合里,目标组合和真实组合就会不同。
调仓模拟要保存未成交原因。很多回测只记录成功交易,这会掩盖问题。真实市场里,买不到、卖不出、部分成交,都会影响策略。
回测的本质就是这个每天重放的循环。每一步都可能藏坑:数据当时真可见吗?信号偷看未来了吗?成交价真实吗?成本扣了吗?循环里任何一环失真,收益就是假的。
7. 扣除交易成本
成本至少包括佣金、卖出印花税、过户费和滑点。教学版可以设一个统一单边成本,比如买入 0.08%、卖出 0.13%,再做成本敏感性测试。
更细一点,可以让滑点和成交额相关。成交额大的股票滑点低,成交额小的股票滑点高;订单金额占当天成交额比例越高,冲击越大。
成本敏感性很关键。把成本乘以 0、1、2、3 倍分别看结果。如果策略只在 0 成本下赚钱,现实意义很弱。如果成本翻倍后仍有稳定超额,才值得继续研究。
第一版成本公式可以写得朴素: 买入成本 = 买入金额 × 买入成本率,卖出成本 = 卖出金额 × 卖出成本率。比如买入 10000 元,买入成本率 0.08%,成本 8 元;卖出 10000 元,卖出成本率 0.13%,成本 13 元。净收益必须在扣掉这 21 元后再算。
如果要加入冲击,可以把订单金额除以当天成交额作为参与率。参与率越高,滑点越高。教学版不必复杂,但至少要知道大单和小单成本不同。
8. 评价结果
结果评价至少包括净值、基准净值、超额净值、年化收益、最大回撤、波动率、夏普、年化超额、跟踪误差、信息比率、换手率和交易成本。
还要按年份拆解。某个策略总收益不错,可能全部来自 2019 年和 2020 年,之后持续失效。年度表能暴露这个问题。月度收益热力图也能显示收益是否过度集中。
行业和风格暴露也要看。你以为自己做多因子选股,结果组合长期超配某个上涨行业,那超额收益就不全是模型能力。
| 报告模块 | 必须输出 | 看什么问题 |
|---|---|---|
| 收益 | 策略净值、基准净值、超额净值 | 是否真的跑赢基准 |
| 风险 | 最大回撤、波动、夏普、信息比率 | 收益路径是否可承受 |
| 交易 | 换手率、成本、未成交数量 | 纸面收益能否落地 |
| 暴露 | 行业权重、单票集中度、风格倾向 | 是否偷偷押了别的风险 |
报告模板固定后,每次实验都用同一套指标。这样你不会只挑对自己有利的图看,也方便比较不同因子和约束。
9. 写复盘结论
项目最后要写复盘。不要只说策略好或不好,要回答: 哪些因子有贡献? 哪些阶段失效? 成本吃掉多少? 跟踪误差是否可接受? 组合是否过度集中? 结果是否稳健?
还要写下一步。比如加入行业约束,降低换手,替换财务因子数据源,增加样本外,加入风险模型,或者直接放弃这个方向。研究结论要能指导行动。
如果你能把这个项目完整做完,你已经跨过量化入门最重要的一道门。不是因为策略一定赚钱,而是因为你开始用可验证流程替代感觉。
小结
这一章的正文不是让你记住几个孤立名词,而是要把它们放回同一条因果链里。读完后,先用自己的话复述下面几条判断,再顺着“小节回看”检查是否能解释每一步。
- 指数增强项目的核心是基准、股票池、因子、组合、成本和报告闭环。
- 教学项目要先把时间顺序、交易规则和成本做对,再追求模型复杂度。
小节回看
- 1. 项目目标和边界
- 2. 准备股票池
- 3. 准备价格和复权数据
- 4. 构造几个简单因子
- 5. 合成打分
- 6. 模拟调仓
- 7. 扣除交易成本
- 8. 评价结果
- 9. 写复盘结论
自测
为什么指数增强项目要使用历史当时成分股?
为了避免用今天成分回填过去,产生幸存者偏差和成分回填偏差。
多因子合成前为什么要统一方向和标准化?
否则不同因子尺度和方向会混乱,合成分数没有可解释意义。