高频tick数据回测中的A B测试技巧 - 第1节
1.1 Tick 数据结构:高频回测的原子单元
Tick 数据是交易所直接发出的逐笔快照,记录每一笔成交或委托的微观细节。在高频回测中,Tick 是时间分辨率最高的数据源,其结构决定了回测的精度与计算开销。
核心字段与含义
一条标准的 Tick 记录通常包含以下字段,不同交易所略有差异:
| 字段名 | 类型 | 说明 |
|---|---|---|
| Timestamp | datetime / int64 | 事件发生时间(纳秒级精度) |
| Symbol | string | 合约代码(如 IF2409) |
| Price | float64 | 成交价格或委托价格 |
| Volume | int64 | 成交量(手) |
| Side | char / int | 买卖方向(B/S 或 1/2) |
| Type | char | 事件类型(T=成交, O=委托, C=撤单) |
| SeqNo | int64 | 交易所流水号(用于排序去重) |
精度陷阱: 不同交易所的 Timestamp 精度不同(毫秒/微秒/纳秒),回测时务必统一对齐,否则会导致订单顺序错乱。
数据粒度对比
高频回测中常混淆“逐笔成交”与“逐笔委托”,两者在信息量和用途上有本质区别:
| 维度 | 逐笔成交 (Trade Tick) | 逐笔委托 (Order Tick) |
|---|---|---|
| 记录内容 | 已撮合成交的订单 | 进入撮合队列的订单(含未成交) |
| 包含撤单 | 否 | 是(撤单也作为一条委托记录) |
| 数据量 | 约 1/3 ~ 1/2 的委托量 | 最大,包含所有挂单与撤单 |
| 回测用途 | 模拟成交、滑点分析 | 模拟订单簿重建、流动性分析 |
| 存储开销 | 较低 | 较高(通常为成交数据的 2~3 倍) |
常见误区: 仅用逐笔成交数据做订单簿回测会导致“看不见的挂单”被忽略,从而低估市场冲击成本。若策略依赖订单簿深度,必须使用逐笔委托数据。
1.2 Tick 数据存储格式:CSV vs Parquet
高频 Tick 数据量巨大(单日可达数千万条),存储格式直接影响回测的 I/O 效率与磁盘占用。以下对比两种主流格式:
| 特性 | CSV | Parquet |
|---|---|---|
| 存储方式 | 纯文本,行式存储 | 二进制,列式存储 |
| 压缩率 | 低(可 gzip 压缩) | 高(内置 snappy/zstd) |
| 读取速度 | 慢(全量解析) | 快(列裁剪、谓词下推) |
| Schema 支持 | 无(需手动定义) | 强(自带类型与元数据) |
| 适用场景 | 小样本分析、调试 | 大规模回测、生产环境 |
实践建议: 在 A/B 测试中,建议使用 Parquet 格式存储原始 Tick 数据,利用列式存储只读取需要的字段(如 Price、Volume),可将 I/O 时间降低 5~10 倍。
Parquet 存储示例(Python)
import pandas as pd
# 读取原始 CSV
df = pd.read_csv('tick_data.csv', parse_dates=['Timestamp'])
# 转换为 Parquet(自动压缩)
df.to_parquet('tick_data.parquet', compression='snappy')
# 读取时只加载 Price 和 Volume 两列
df_sub = pd.read_parquet('tick_data.parquet', columns=['Price', 'Volume'])
1.3 Tick 数据流架构图
下图展示了从交易所原始数据到回测引擎的完整 Tick 数据流,重点突出存储格式与字段映射关系:
1.4 数据质量检查清单
在开始 A/B 测试前,必须对 Tick 数据进行质量校验,避免脏数据污染回测结果:
- 时间戳连续性: 检查是否存在时间倒序或跳跃超过 5 秒的异常间隔。
- 价格合理性: 剔除价格为 0 或超出涨跌停板范围的记录。
- 成交量非负: 逐笔成交的 Volume 必须 > 0,逐笔委托的 Volume 可为 0(撤单)。
- 流水号唯一性: 同一交易所的 SeqNo 应严格递增,无重复。
- 买卖方向完整性: Side 字段不应出现空值或非法字符。
自动化校验脚本: 建议在数据入库前编写 Pandas 校验函数,对上述规则逐条检查,并输出异常记录数。只有通过校验的数据才能进入回测管道。