3. 行情数据基础:Level-1 与 Level-2 行情、Tick 数据、Order Book 数据结构
高频回测的起点是行情数据。不同级别的行情数据在粒度、延迟和信息量上存在显著差异,直接影响回测精度与策略逻辑。本章系统梳理 Level-1、Level-2、Tick 数据及 Order Book(订单簿)的核心概念与数据结构,为后续数据清洗与回测引擎搭建奠定基础。
3.1 行情数据层级对比
交易所通常将行情数据分为多个层级,国内 A 股市场以 Level-1 和 Level-2 最为常见。下表从关键维度对比两者的差异:
| 对比维度 | Level-1 行情 | Level-2 行情 |
|---|---|---|
| 快照频率 | 约 3 秒一次 | 约 0.1~0.5 秒一次(逐笔推送) |
| 委托深度 | 5 档买卖盘口 | 10 档买卖盘口(部分市场支持全档) |
| 逐笔成交 | 无 | 包含逐笔成交明细(Tick) |
| 委托队列 | 无 | 前 50 笔委托明细(部分市场) |
| 数据延迟 | 较高(秒级) | 较低(毫秒级) |
| 费用 | 免费或低成本 | 付费(年费数千至数万元) |
| 适用策略 | 日间、低频、统计套利 | 高频、做市、订单簿预测 |
选型建议: 对于高频回测,Level-2 是必要条件。Level-1 数据因快照间隔过长,无法捕捉订单簿的微观变化,容易导致回测结果失真。初学者可从 Level-1 开始理解行情结构,但正式回测应切换到 Level-2。
3.2 Tick 数据:逐笔成交的原子记录
Tick 数据是市场交易的最小可记录单元,每一条 Tick 代表一笔实际发生的成交。在 Level-2 行情中,Tick 数据以逐笔成交明细的形式推送。其典型字段结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
ticker |
str | 股票代码 |
timestamp |
int64 | 成交时间戳(微秒级) |
price |
float64 | 成交价格 |
volume |
int64 | 成交量(股) |
turnover |
float64 | 成交金额(元) |
buy_sell_flag |
int8 | 买卖方向:1=主动买,-1=主动卖,0=未知 |
以下代码展示如何用 Python 加载并预览 Tick 数据:
import pandas as pd
# 模拟加载 Tick 数据(实际可从 CSV/数据库读取)
tick_data = pd.DataFrame({
'ticker': ['000001'] * 5,
'timestamp': [1000000001, 1000000003, 1000000005, 1000000007, 1000000009],
'price': [10.01, 10.02, 10.01, 10.03, 10.02],
'volume': [100, 200, 150, 300, 250],
'turnover': [1001.0, 2004.0, 1501.5, 3009.0, 2505.0],
'buy_sell_flag': [1, -1, 1, -1, 1]
})
print(tick_data.head())
print(f"Tick 数据行数: {len(tick_data)}")
关键注意点:
- 时间戳必须使用微秒或纳秒精度,否则无法对齐高频事件顺序。
- 买卖方向标志(buy_sell_flag)是判断资金流向的基础,需确认交易所的编码规则。
- Tick 数据量极大(单只股票日均数万笔),回测时需考虑内存与 I/O 优化。
3.3 Order Book(订单簿)数据结构
订单簿是交易所撮合引擎维护的实时委托队列快照,包含所有未成交的买单和卖单。Level-2 行情提供 10 档甚至全档订单簿数据。订单簿的核心由 买盘(Bid) 和 卖盘(Ask) 组成,每档包含价格和委托量。
下图展示订单簿的典型分层结构:
订单簿数据在 Python 中通常以嵌套字典或结构化数组表示。以下是一个 10 档订单簿的示例数据结构:
order_book = {
'ticker': '000001',
'timestamp': 1000000010,
'bids': [ # 买盘,按价格从高到低排列
{'price': 10.02, 'volume': 1800},
{'price': 10.01, 'volume': 900},
{'price': 10.00, 'volume': 2500},
# ... 后续档位
],
'asks': [ # 卖盘,按价格从低到高排列
{'price': 10.04, 'volume': 2000},
{'price': 10.05, 'volume': 600},
{'price': 10.06, 'volume': 1500},
# ... 后续档位
]
}
# 计算买卖价差
best_bid = order_book['bids'][0]['price']
best_ask = order_book['asks'][0]['price']
spread = best_ask - best_bid
print(f"最优买价: {best_bid}, 最优卖价: {best_ask}, 价差: {spread:.2f}")
3.4 从 Tick 到订单簿:数据流关系
理解 Tick 数据与订单簿之间的关系是高频回测的核心。Tick 数据是订单簿变化的“结果”,而订单簿是 Tick 数据产生的“环境”。下图展示两者在交易过程中的交互流程:
常见误区: 许多初学者将 Tick 数据直接当作订单簿快照使用,这是错误的。Tick 只记录已成交的订单,而订单簿包含所有未成交的委托。回测中若仅用 Tick 数据重建订单簿,会丢失撤单和未成交委托信息,导致模拟结果严重偏离真实市场。
3.5 数据存储与读取建议
高频行情数据体量巨大,存储格式与读取效率直接影响回测性能。以下是一些实践建议:
- 存储格式: 推荐使用 Parquet 或 HDF5 格式,支持列式压缩与快速过滤,比 CSV 节省 5~10 倍空间。
- 分区策略: 按日期 + 股票代码分区,避免单文件过大,便于并行读取。
- 内存映射: 对于订单簿快照数据,使用 NumPy 的内存映射(memmap)或 Pandas 的
read_parquet(use_mmap=True)减少内存占用。 - 时间索引: 将时间戳设为索引,利用 Pandas 的时间切片加速数据筛选。
# 示例:使用 Parquet 存储 Tick 数据
tick_data.to_parquet('tick_20250101.parquet', index=False, compression='snappy')
# 读取时按时间范围过滤
import pyarrow.parquet as pq
table = pq.read_table('tick_20250101.parquet',
filters=[('timestamp', '>=', 1000000000),
('timestamp', '<=', 1000000100)])
df = table.to_pandas()
print(f"读取 {len(df)} 条 Tick 记录")
本章小结: Level-1 与 Level-2 行情在频率、深度和信息量上存在本质差异;Tick 数据是逐笔成交的原子记录,订单簿是未成交委托的分层快照。两者共同构成高频回测的数据基础。掌握这些数据结构及其在 Python 中的表示方式,是后续构建回测引擎的第一步。