数据获取与解析:从交易所或数据商获取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排查了整整一个下午。

解析流程:从二进制到结构化数据

假设你拿到了交易所的原始二进制流,怎么解析?我画了个流程图,帮你理清思路:

1. 接收二进制流 2. 解析消息头 3. 校验 4. 解析委托队列 5. 提取字段 6. 存入内存队列 7. 供策略调用 注意:校验失败要丢弃整包数据 不要尝试修复,否则时序会乱

你看,整个流程其实不复杂。但每一步都有细节。我重点说说第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,没啥用。其实不然。我在做订单簿重建时发现,委托编号可以用来追踪订单的生命周期。

举个例子:

  • 如果某个委托编号在买一出现,几秒后消失了,说明这个订单被撤单了
  • 如果委托编号不变,但数量减少了,说明部分成交了
  • 如果委托编号变了,说明是新的订单进来了

警告:委托编号是交易所生成的,但不同交易所的编号规则不同。上交所是全局递增的,深交所是按席位分配的。千万别混用,否则你的订单簿重建会出大问题。

数据解析的避坑指南

做数据解析这几年,我踩过的坑能写本书。挑几个典型的说说:

  1. 时间戳对齐:交易所的快照是定时推送的,比如每3秒一次。但网络延迟会导致你收到的时间不整齐。我建议你收到后,按时间戳重新排序,不要依赖接收顺序。
  2. 数据丢失处理:网络偶尔会丢包。如果你发现某个时间戳的序号跳过了,别硬补。直接丢弃,等下一帧。强行插值会让你的策略产生幻觉。
  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%的数据异常。我每次接入新数据源,都会先跑一遍这个脚本。

好了,数据获取与解析这块,核心就是这些。说白了,就是搞清楚数据从哪来、长什么样、怎么拆开。你只要把这三个问题搞明白了,后面的分析就顺了。