高频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 数据流,重点突出存储格式与字段映射关系:

Tick 数据流架构 Tick 数据流架构 交易所撮合引擎 逐笔成交 (Trade) 逐笔委托 (Order) CSV 文件 Parquet 文件 高频回测引擎 关键说明: • 逐笔成交数据量小,适合 CSV 快速预览;逐笔委托数据量大,推荐 Parquet 列式存储。 • 回测引擎通过列裁剪只读取必要字段(如 Price、Volume),大幅降低 I/O 延迟。

1.4 数据质量检查清单

在开始 A/B 测试前,必须对 Tick 数据进行质量校验,避免脏数据污染回测结果:

  • 时间戳连续性: 检查是否存在时间倒序或跳跃超过 5 秒的异常间隔。
  • 价格合理性: 剔除价格为 0 或超出涨跌停板范围的记录。
  • 成交量非负: 逐笔成交的 Volume 必须 > 0,逐笔委托的 Volume 可为 0(撤单)。
  • 流水号唯一性: 同一交易所的 SeqNo 应严格递增,无重复。
  • 买卖方向完整性: Side 字段不应出现空值或非法字符。
自动化校验脚本: 建议在数据入库前编写 Pandas 校验函数,对上述规则逐条检查,并输出异常记录数。只有通过校验的数据才能进入回测管道。