3. 行情数据基础

高频回测的起点是行情数据。不同级别的行情数据在粒度、延迟和信息量上存在显著差异,直接影响回测精度与策略逻辑。本章系统梳理 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) 组成,每档包含价格和委托量。

下图展示订单簿的典型分层结构:

订单簿(Order Book)分层结构 卖盘(Ask) Ask[5] 价格: 10.08 量: 1200 Ask[4] 价格: 10.07 量: 800 Ask[3] 价格: 10.06 量: 1500 Ask[2] 价格: 10.05 量: 600 Ask[1] 价格: 10.04 量: 2000 Spread(价差): 0.02 买盘(Bid) Bid[1] 价格: 10.02 量: 1800 Bid[2] 价格: 10.01 量: 900 Bid[3] 价格: 10.00 量: 2500

订单簿数据在 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 数据是订单簿状态变化的产物,回测引擎需同时维护订单簿快照与 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 中的表示方式,是后续构建回测引擎的第一步。