数据获取与解析:从交易所或数据商获取Level-2快照数据
做量化交易的朋友都知道,Level-2数据是咱们分析市场微观结构的核心原料。没有它,你看到的行情就像隔着毛玻璃看比赛——只能看到比分,看不到球员怎么跑位的。
今天我就带大家走一遍,从数据源到内存解析的完整流程。嗯,这里要注意,不同数据商的接口差异还挺大的,但核心逻辑是相通的。
数据源的选择:交易所 vs 数据商
先说说数据从哪来。目前国内主要有两条路:
- 交易所直连:上交所、深交所都提供Level-2行情接口。优点是数据最原始、延迟最低。缺点嘛,你得有券商牌照或者机构席位,个人基本别想。
- 数据商中转:像万得、聚宽、米筐这些,他们把交易所数据加工后再分发。个人开发者用这个最实际,我早期做策略时就是接的聚宽。
核心区别:交易所给的是二进制流,数据商给的是JSON或CSV。前者解析难度大,但信息完整;后者方便,但可能有几毫秒的延迟。
我个人习惯用数据商接口做回测,用交易所直连做实盘。为什么?回测不差那几毫秒,实盘就不一样了,你想想看,高频场景下1毫秒可能就是盈亏的分水岭。
Level-2快照的数据结构
拿到数据后,第一件事就是搞清楚它长什么样。一个典型的Level-2快照,包含以下核心字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| 时间戳 | int64 | 精确到微秒,用于对齐 |
| 股票代码 | string | 如"600519" |
| 买一到买十价格 | float[10] | 10档买入价格 |
| 买一到买十数量 | int[10] | 对应档位的委托股数 |
| 卖一到卖十价格 | float[10] | 10档卖出价格 |
| 卖一到卖十数量 | int[10] | 对应档位的委托股数 |
| 委托编号 | string | 交易所生成的唯一标识 |
这里有个坑,我踩过。委托编号在交易所原始数据里是64位整数,但有些数据商会转成字符串。如果你直接当整数处理,解析时会报错。我曾经因为这个bug排查了整整一个下午。
解析流程:从二进制到结构化数据
假设你拿到了交易所的原始二进制流,怎么解析?我画了个流程图,帮你理清思路:
你看,整个流程其实不复杂。但每一步都有细节。我重点说说第4步——解析委托队列。
委托队列的字段解析
委托队列里最核心的三个字段:价格、数量、委托编号。它们是怎么编码的?
小技巧:上交所的Level-2数据中,价格字段用4字节整数表示,单位是"元×10000"。比如12.3456元,存的是123456。解析时除以10000就行。
下面是一个Python解析示例,我实际项目里就是这么写的:
import struct
def parse_order_book(data):
"""
解析委托队列快照
data: 二进制数据流
"""
# 跳过消息头(前20字节)
offset = 20
# 解析10档买卖盘
bids = [] # 买单
asks = [] # 卖单
for i in range(10):
# 价格:4字节整数,单位0.0001元
price = struct.unpack('>i', data[offset:offset+4])[0] / 10000.0
offset += 4
# 数量:4字节整数,单位股
volume = struct.unpack('>i', data[offset:offset+4])[0]
offset += 4
# 委托编号:8字节整数
order_id = struct.unpack('>Q', data[offset:offset+8])[0]
offset += 8
bids.append({
'price': price,
'volume': volume,
'order_id': order_id
})
# 卖单同理,略
return bids, asks
这段代码看着简单,但有个细节要注意:字节序。上交所用的是大端序(big-endian),深交所也是。但有些数据商转成小端序再发给你。我建议你拿到数据后,先打印前几个字节看看,确认字节序对不对。
委托编号的妙用
很多人觉得委托编号就是个ID,没啥用。其实不然。我在做订单簿重建时发现,委托编号可以用来追踪订单的生命周期。
举个例子:
- 如果某个委托编号在买一出现,几秒后消失了,说明这个订单被撤单了
- 如果委托编号不变,但数量减少了,说明部分成交了
- 如果委托编号变了,说明是新的订单进来了
警告:委托编号是交易所生成的,但不同交易所的编号规则不同。上交所是全局递增的,深交所是按席位分配的。千万别混用,否则你的订单簿重建会出大问题。
数据解析的避坑指南
做数据解析这几年,我踩过的坑能写本书。挑几个典型的说说:
- 时间戳对齐:交易所的快照是定时推送的,比如每3秒一次。但网络延迟会导致你收到的时间不整齐。我建议你收到后,按时间戳重新排序,不要依赖接收顺序。
- 数据丢失处理:网络偶尔会丢包。如果你发现某个时间戳的序号跳过了,别硬补。直接丢弃,等下一帧。强行插值会让你的策略产生幻觉。
- 内存管理:Level-2数据量很大,一天下来几个GB很正常。我习惯用环形缓冲区,只保留最近1000帧。老数据直接覆盖,别舍不得。
嗯,说到内存管理,我记得有一次实盘时,因为没做内存清理,程序跑了3小时后直接OOM了。当时正在跑一个高频策略,损失惨重。从那以后,我每次写数据解析代码,第一件事就是加内存监控。
实战:用Python快速验证数据
拿到数据后,我建议你先做一步快速验证,别急着上策略。写个简单的脚本,打印前几帧看看:
def validate_snapshot(snapshot):
"""快速验证快照数据是否合理"""
# 检查价格是否为正数
for bid in snapshot['bids']:
if bid['price'] <= 0:
return False
# 检查买卖价差是否合理
best_bid = snapshot['bids'][0]['price']
best_ask = snapshot['asks'][0]['price']
spread = best_ask - best_bid
if spread < 0:
print(f"异常:卖价比买价还低!{best_bid} vs {best_ask}")
return False
if spread > 0.1: # 价差超过1毛,可能数据有问题
print(f"警告:价差过大 {spread}")
return True
这个验证函数虽然简单,但能帮你过滤掉90%的数据异常。我每次接入新数据源,都会先跑一遍这个脚本。
好了,数据获取与解析这块,核心就是这些。说白了,就是搞清楚数据从哪来、长什么样、怎么拆开。你只要把这三个问题搞明白了,后面的分析就顺了。