回测基础概念:回测流程、标签生成逻辑与常见口径
回测(Backtesting)是量化策略研发的核心验证环节,其本质是用历史数据模拟策略执行过程,评估策略的收益、风险与稳定性。标签(Label)则是回测中用于衡量策略表现的基本单元——每一次交易、每一个持仓周期都会产生一个标签,标签的口径定义直接决定了回测结果的可靠性与可复现性。
2.1 回测标准流程
一个完整的回测流程通常包含以下五个阶段,每个阶段都依赖明确的输入与输出,形成闭环迭代:
| 阶段 | 核心任务 | 关键输出 |
|---|---|---|
| 1. 数据准备 | 清洗、对齐多源行情数据(Tick / 1min / 日线),处理缺失值与复权 | 标准化 OHLCV 数据集 |
| 2. 信号生成 | 基于因子或规则计算买卖信号,确定开仓/平仓时机 | 信号时间戳序列 |
| 3. 交易模拟 | 按信号执行虚拟下单,考虑滑点、手续费、涨跌停限制 | 成交明细表(Trade Log) |
| 4. 标签生成 | 根据持仓期或事件窗口,为每笔交易计算收益、风险等标签 | 标签数据集(Label Table) |
| 5. 绩效归因 | 汇总标签统计指标(夏普、最大回撤、胜率),进行分组对比 | 回测报告 |
关键认知: 标签生成是连接“交易模拟”与“绩效归因”的桥梁。口径不一致的标签会导致归因结果失真,甚至掩盖策略的真实缺陷。
2.2 标签生成逻辑
标签生成的核心是将交易记录转化为可统计的指标样本。每笔交易至少包含以下字段:
- 交易 ID:唯一标识一次开平仓周期。
- 开仓时间 / 平仓时间:决定持仓时长与事件窗口。
- 开仓价格 / 平仓价格:用于计算收益率。
- 持仓方向:多头(Long)或空头(Short)。
- 成交量:用于计算盈亏金额。
标签计算的基本公式(以多头为例):
收益率 = (平仓价格 - 开仓价格) / 开仓价格
盈亏金额 = 收益率 × 开仓市值
持仓期 = 平仓时间 - 开仓时间(通常以交易日或自然日计)
实践建议: 在生成标签时,务必保留原始交易 ID 与时间戳,以便后续进行口径校准与数据溯源。不要只存储聚合后的统计值。
2.3 常见标签口径
标签口径定义了“如何计算一个标签的数值”。同一笔交易,使用不同口径会得到不同的标签值。以下是三种最常用的口径:
| 口径类型 | 定义 | 适用场景 | 注意事项 |
|---|---|---|---|
| 持仓期口径 | 以开仓到平仓的完整区间计算收益 | 中长线趋势策略、日频以上策略 | 需处理隔夜跳空、分红除权 |
| 事件窗口口径 | 以事件发生前后固定窗口(如 T+1 ~ T+5)计算收益 | 事件驱动策略、因子 IC 测试 | 窗口长度需统一,避免未来信息 |
| 滚动窗口口径 | 以固定时间长度(如 20 个交易日)滚动计算收益 | 动量策略、时序因子计算 | 窗口重叠会导致样本非独立 |
2.4 持仓期口径 vs 事件窗口口径
这两种口径是回测中最容易混淆的概念,它们的核心差异在于时间锚点:
- 持仓期口径:锚点是实际交易,标签反映的是策略真实持仓的盈亏。
- 事件窗口口径:锚点是事件发生时刻,标签反映的是事件发生后市场的平均反应。
举例说明:
策略在 2024-01-05 开仓买入股票 A,2024-01-12 平仓卖出。
- 持仓期口径标签:收益率 = (平仓价 - 开仓价) / 开仓价,持仓期 = 7 天。
- 事件窗口口径标签:以 2024-01-05 为事件日,计算 T+1 到 T+5 的累计收益率,
即使策略在 T+3 已经平仓,窗口仍计算到 T+5。
常见陷阱: 使用事件窗口口径时,如果窗口内包含平仓后的数据,会导致标签包含“未来信息”,使回测结果虚高。务必在生成标签时截断窗口至实际持仓结束。
2.5 口径校准的必要性
在实际量化团队中,不同研究员可能使用不同的口径生成标签,导致以下问题:
- 绩效不可比:A 研究员用持仓期口径计算夏普 1.5,B 研究员用事件窗口口径计算夏普 2.0,无法直接对比。
- 因子失效误判:因子 IC 测试使用事件窗口口径,但策略实盘使用持仓期口径,导致因子在回测中有效、实盘中失效。
- 数据管道混乱:标签生成逻辑分散在多个脚本中,口径定义不统一,维护成本高。
解决方案: 建立统一的标签口径规范,并通过 Kafka 消息管道将标签生成逻辑标准化、可追溯。这正是本课程后续章节的核心内容——用 Kafka 集成实现标签口径的实时校准与版本管理。
2.6 标签口径的元数据管理
为了支持口径校准,每个标签应携带以下元数据:
| 元数据字段 | 说明 | 示例值 |
|---|---|---|
label_version |
标签口径版本号 | v2.1.0 |
window_type |
口径类型 | holding_period / event_window |
window_length |
窗口长度(若适用) | 5(交易日) |
generated_at |
标签生成时间戳 | 2024-06-15T10:30:00Z |
source_trade_id |
来源交易 ID | trade_20240601_001 |
通过元数据,团队可以快速回溯某个标签的计算方式,并在口径变更时进行批量重算与对比。
工程落地: 在 Kafka 消息中,将标签元数据放在消息头(Header)中,标签数值放在消息体(Value)中。这样消费者可以根据 Header 中的版本号决定是否处理该消息,实现口径的灰度切换。