A 股的数据坑是「制度性」的:不是数据质量问题,而是市场规则投射到数据上的必然形状。本篇四个主题,每一个都对应一类反复发生的回测事故。
复权与除权除息:算收益的命门
分红送转让原始价格序列产生跳空,不复权直接算收益会把除息日当成暴跌。三种口径的取舍:
| 口径 | 基准 | 用途 | 陷阱 |
|---|---|---|---|
| 不复权 | — | 与实盘委托价对齐、盘口分析 | 不能跨除权日算收益 |
| 前复权 | 以最新价为锚向前调整 | 画图直观、技术指标 | 历史价格随每次分红整体重算;技术指标的历史值漂移 |
| 后复权 | 以上市首日为锚向后调整 | 收益计算、回测的价格序列 | 价格与实际盘口价脱钩(可用于判断「能不能成交」的只有不复权价) |
回测的黄金组合:收益与信号计算用后复权(稳定、不漂移),成交与限价判断用不复权(真实委托价),除权日单独处理(按分红方案把昨日持仓的股数与成本调整,而不是靠复权价隐含处理——组合层的送转要「股数×2、价格÷2」地显式处理)。
前复权缓存漂移是最隐蔽的事故,算一个具体例子:2026 年 6 月某股每股分红 0.5 元(股价 25 元,2%),前复权把 2020 年以来全部历史价格整体下调 2%——你 5 月缓存的信号值、算好的技术指标、乃至当年的回测曲线,在 6 月全部「悄悄变了」。新旧缓存混用时,回测里出现「幽灵收益」。对策:只缓存后复权 + 复权因子两张表,前复权按需实时计算;任何缓存表记录「生成时间 + 覆盖的除权事件上限」(见因子存储与版本管理 的对账思想)。
停牌:不可交易的建模
A 股停牌曾长期任性(2015 年千股停牌),2018 年后制度收紧但停牌仍常见。回测必须在三层显式建模:
- 信号层:停牌日因子值冻结(ffill)或缺失,该股当日不可买入(挂单也成不了交);
- 组合层:已持仓的停牌股冻结——既不能卖也不能调权重。2015 年股灾中大量基金「停牌股撑住净值」的假象(复牌后补跌)就是这个机制的反面教材;回测里把停牌持仓按冻结处理,净值曲线才会诚实反映复牌补跌。量化其影响:若组合 8% 权重的持仓停牌 2 个月、复牌补跌 30%,回测里「冻结处理」与「假设照常交易」的净值差可达 2.4 个点——足够改变策略评级;
- 成交层:复牌首日波动巨大,成交假设要保守(见数据清洗 的「穿越成交」原则)。
涨跌停与 ST:价格边界的两层含义
涨跌停(主板 ±10%、创业/科创 ±20%、北交所 ±30%、ST ±5%)对回测的三层影响:
- 收益层:涨停价收盘 → 次日惯性(打板策略的制度基础);跌停封死 → 你挂在跌停价的卖单排在几十万手之后,大概率不成交。回测里「跌停日止损成功」是最常见的乐观偏差——正确建模:跌停封死时卖单不成交,顺延次日;
- 可交易层:涨停封死时买单同样无法成交——动量类策略「信号出现但买不进」的容量与收益都要打折。检验方法:统计信号日的涨停占比,>10% 就要认真处理;
- 成本层:接近涨跌停时的 spread 与冲击成本急剧放大,成本模型要对「距涨跌停的距离」分档(见交易成本分解 )。
ST:风险警示股涨跌幅 5%、退市风险高、流动性差。多因子模型的常见处理:直接剔除(注意「剔除」本身要在 PIT 上做——用「当时是否 ST」而不是「现在是否 ST」);或保留但加流动性过滤。小市值策略对 ST 处理最敏感:剔除 ST 后历史收益通常显著下降——这个下降是真实的(你在放弃真实市场里的一类风险收益),要在报告里披露而不是当作噪音(见站内原文小市值策略实战复盘 对此的实战讨论)。
行业分类变更:申万改版的教训
行业因子与中性化都依赖行业分类,而行业分类会改版:申万 2021 年大改(一级 28 → 31 个,大量个股重分类)。用「最新申万分类」回测历史,等于让 2019 年的银行股被分进 2021 年的口径——中性化因子的历史表现系统性失真。
工程方案:行业表做成 PIT 结构(code, industry_sw, effective_from, effective_to),回测截面用「当时生效」的分类;跨口径研究(新旧对比)单独建映射表。中信、证监会、同花顺分类同理,且不同体系的「行业」粒度与逻辑不同(申万偏投资口径、证监会偏监管口径),跨体系混用是行业轮动研究的常见翻车点(见行业与主题
)。
本篇总结成一条工程铁律:A 股数据的每个「特殊处理」(复权、停牌、涨跌停、ST、行业)都应该是回测配置里的显式开关 + 单元测试覆盖,而不是隐藏在数据预处理黑箱里的隐式行为——制度假设显式化,回测才可审计(见回测框架设计 )。