2. 高频行情数据基础
在进入 Numba 与 Cython 的加速实战之前,必须先建立对高频行情数据本身的准确认知。加速的前提是数据模型清晰:如果连一条逐笔成交的字段语义、盘口快照的更新方式、订单簿的维护规则都不清楚,那么后续无论用多快的编译手段,都只是在错误的结构上做无效优化。本节围绕四类核心对象展开:逐笔成交、盘口快照、订单簿数据结构,以及时间戳与序列化格式。
一、核心原理:三类行情对象与一条数据链路
交易所对外发布的行情,本质上是对“订单簿状态变化”的不同粒度描述。逐笔成交(Trade/Tick)记录的是撮合引擎实际发生的成交事件,通常包含成交价、成交量、成交方向(主动买/主动卖)、成交编号与时间戳。盘口快照(Snapshot/Quote)记录的是某一时刻订单簿前 N 档的买卖报价与挂单量,例如 Level-1 只有一档最优买卖价,Level-2 提供十档甚至更多。订单簿(Order Book)则是把逐笔委托与逐笔成交按价格优先、时间优先规则重建出的完整限价簿结构。
三者关系可以概括为:订单簿是“状态”,逐笔是“事件”,快照是“状态的周期采样”。事件驱动状态变化,快照用于校验与快速初始化。理解这条链路,是后续设计增量更新与批量计算的基础。
二、方法流程:从原始报文到可用结构
高频数据处理通常遵循一条固定流程:接收原始报文 → 解析序列化格式 → 提取字段 → 更新订单簿或生成特征 → 输出到策略或存储。每一步都影响后续加速策略的选择。
- 接收与缓冲:使用环形缓冲区或内存映射文件接收原始字节流,避免频繁分配内存。
- 解析:按协议定义逐字段读取,注意字节序与对齐。二进制协议通常用 struct 或 memoryview 解析。
- 时间戳归一:将交易所时间、接收时间、本地处理时间统一到同一时间基准,便于延迟分析。
- 状态更新:逐笔成交更新最近成交价与成交量;逐笔委托更新订单簿对应档位。
- 快照生成:按固定周期或按事件数触发,输出前 N 档盘口。
关键点:解析阶段应尽量使用零拷贝(memoryview、numpy.frombuffer),把字段提取与结构更新分离,便于后续用 Numba 对纯计算部分加速。
三、案例/场景:逐笔成交与盘口快照的字段设计
下面给出一个典型的逐笔成交与盘口快照字段清单,便于在代码中定义结构化 dtype 或 Cython cdef 结构体。
| 对象 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 逐笔成交 | ts_exchange | int64 | 交易所时间戳(纳秒) |
| 逐笔成交 | price | float64 | 成交价 |
| 逐笔成交 | volume | int64 | 成交量(股/手) |
| 逐笔成交 | side | int8 | 1=主动买,-1=主动卖 |
| 逐笔成交 | seq | int64 | 成交序号,用于去重与排序 |
| 盘口快照 | ts | int64 | 快照时间戳 |
| 盘口快照 | bid_px[10] | float64 | 买十档价格 |
| 盘口快照 | bid_sz[10] | int64 | 买十档数量 |
| 盘口快照 | ask_px[10] | float64 | 卖十档价格 |
| 盘口快照 | ask_sz[10] | int64 | 卖十档数量 |
在 Python 中可以用 numpy 结构化数组定义,在 Cython 中则用 cdef packed struct 保证内存布局紧凑:
import numpy as np
trade_dtype = np.dtype([
('ts_exchange', 'i8'),
('price', 'f8'),
('volume', 'i8'),
('side', 'i1'),
('seq', 'i8'),
])
snapshot_dtype = np.dtype([
('ts', 'i8'),
('bid_px', 'f8', (10,)),
('bid_sz', 'i8', (10,)),
('ask_px', 'f8', (10,)),
('ask_sz', 'i8', (10,)),
])
四、误区/复盘:时间戳与序列化常见陷阱
误区一:混用不同精度的时间戳。交易所可能用纳秒,网关用微秒,本地用毫秒。直接相减会得到错误延迟。必须统一到同一单位,并明确基准。
误区二:忽略字节序与对齐。二进制协议若未按 packed 布局解析,字段偏移会错位,导致价格与数量完全错乱。
误区三:用文本格式做高频解析。JSON/CSV 解析开销大且不稳定,仅适合调试。生产环境应使用二进制协议。
误区四:订单簿更新未处理撤单与改单。只处理成交会导致簿状态漂移,必须按委托编号维护队列。
复盘建议:先用小样本逐条打印解析结果,与交易所官方回放工具比对;确认字段无误后,再引入 Numba/Cython 加速。加速应作用于“已正确的逻辑”,而不是用来掩盖数据模型错误。