3. 数据格式与协议解析:Level2行情数据常用格式与协议结构
Level2行情数据在传输和存储过程中,为了兼顾效率、可读性和跨平台兼容性,主要采用三种数据格式:二进制、Protobuf 和 JSON。每种格式在协议结构上均遵循“消息头 + 消息体 + 校验和”的通用框架。本章将深入解析这三种格式的差异、协议结构以及关键字段含义,为后续的标签校准实操打下坚实基础。
3.1 三种数据格式对比与选型
不同的数据格式在性能、可读性和开发成本上各有优劣。下表从多个维度对三种格式进行对比,帮助你在实际项目中做出合理选择。
| 对比维度 | 二进制格式 | Protobuf | JSON |
|---|---|---|---|
| 编码方式 | 原始字节流,无压缩 | 二进制序列化,自带压缩 | 文本字符串,UTF-8 |
| 数据体积 | 最小(固定长度字段) | 较小(可变长度编码) | 最大(冗余标签和引号) |
| 解析速度 | 极快(直接内存映射) | 快(预编译Schema) | 慢(字符串解析) |
| 可读性 | 差(需对照协议文档) | 中(需.proto文件辅助) | 好(人类可直接阅读) |
| 跨语言支持 | 需手动处理字节序 | 官方支持多语言 | 几乎所有语言原生支持 |
| Schema约束 | 无(依赖固定偏移) | 强(字段编号+类型) | 弱(可动态扩展) |
| 典型场景 | 交易所原始快照、极速行情 | 券商内部推送、量化平台 | 调试、日志、Web API |
3.2 通用协议结构解析
无论采用哪种数据格式,Level2行情数据包都遵循一个三层协议结构:消息头(Header)、消息体(Body) 和 校验和(Checksum)。理解这一结构是正确解析数据的前提。
3.2.1 消息头(Header)
消息头是数据包的“身份证”,通常为固定长度(如32字节或64字节)。解析器必须先读取并解析消息头,才能正确理解后续的消息体。消息头包含以下核心字段:
- 消息类型(MsgType):标识该数据包是逐笔成交、快照、委托队列还是指数行情。
- 消息体长度(BodyLength):指明消息体的字节数,用于从流中切分出完整的消息体。
- 时间戳(Timestamp):交易所生成该数据的时间,精度通常为微秒或纳秒。
- 会话ID(SessionID):标识数据来源的会话通道,用于多路复用。
- 序列号(SeqNo):该数据包在会话中的递增序号,用于检测丢包和重排序。
3.2.2 消息体(Body)
消息体承载具体的行情数据,其内部字段排列方式取决于数据格式:
- 二进制格式:字段按固定偏移排列,无字段名,解析时需按协议文档逐字节读取。
- Protobuf:字段以Tag-Length-Value(TLV)方式编码,通过.proto文件定义字段编号和类型。
- JSON:字段以键值对形式组织,字段名即为键,可读性最高但体积最大。
3.2.3 校验和(Checksum)
校验和用于验证数据包在传输过程中是否被篡改或损坏。常见的校验算法包括:
- CRC32:循环冗余校验,检测能力强,常用于二进制和Protobuf格式。
- MD5:消息摘要算法,安全性较高,但计算开销较大。
- XOR校验:简单异或,计算速度快,但检测能力较弱。
3.3 常见字段含义详解
无论数据格式如何,Level2行情数据中都有一些核心字段是共通的。理解这些字段的精确含义,是进行标签校准的基础。
| 字段名称 | 数据类型 | 含义说明 | 校准注意事项 |
|---|---|---|---|
| 时间戳 | int64 / string | 交易所生成该笔数据的UTC时间,精度通常为微秒(如 09:30:01.123456) | 校准时间轴时,需注意时区转换和时钟同步偏差 |
| 价格 | int64 / float | 成交价或买卖报价,单位为元,通常保留4位小数(如 12.3456) | 注意价格精度和最小变动单位(Tick Size),避免浮点误差 |
| 数量 | int64 | 成交量或委托量,单位为股或手(1手=100股) | 区分“股”与“手”,部分交易所按股计算,部分按手计算 |
| 买卖方向 | int8 / string | 标识交易方向:'B' 或 1 表示买方主动,'S' 或 2 表示卖方主动 | 方向错误会导致资金流向计算完全相反,是校准的重点 |
| 证券代码 | string | 股票代码,如 "600519.SH" 或 "000001.SZ" | 注意代码格式统一,避免因前缀缺失导致匹配失败 |
| 委托序号 | int64 | 交易所为每笔委托生成的唯一序号,用于追踪订单生命周期 | 可用于关联逐笔成交与委托队列,校验数据一致性 |
3.4 三种格式的协议结构对比
虽然都遵循“Header + Body + Checksum”的框架,但三种格式在具体实现上存在显著差异。下面的SVG图展示了三种格式在协议结构上的不同侧重点。
3.5 代码示例:解析二进制格式的消息头
以下是一个使用Python解析二进制格式消息头的示例。假设消息头固定为32字节,包含消息类型(2字节)、消息体长度(4字节)、时间戳(8字节)和会话ID(8字节)。
import struct
from datetime import datetime
def parse_binary_header(data: bytes) -> dict:
"""
解析二进制格式的消息头(32字节固定长度)
字段顺序:MsgType(2) + BodyLength(4) + Timestamp(8) + SessionID(8) + Reserved(10)
"""
if len(data) < 32:
raise ValueError("消息头长度不足32字节")
# 使用struct模块按大端序解析
msg_type, body_length, timestamp_ns, session_id = struct.unpack('!H I Q Q', data[:22])
# 将纳秒时间戳转换为可读时间
readable_time = datetime.fromtimestamp(timestamp_ns / 1_000_000_000)
header = {
'msg_type': msg_type,
'body_length': body_length,
'timestamp': readable_time,
'session_id': session_id,
'raw_timestamp_ns': timestamp_ns
}
return header
# 示例用法
if __name__ == '__main__':
# 模拟32字节消息头(实际数据应从网络或文件读取)
mock_header = struct.pack('!H I Q Q', 1, 256, 1700000000123456, 1001)
mock_header += b'\x00' * 10 # 填充保留字段
result = parse_binary_header(mock_header)
print(f"消息类型: {result['msg_type']}")
print(f"消息体长度: {result['body_length']} 字节")
print(f"时间戳: {result['timestamp']}")
print(f"会话ID: {result['session_id']}")
3.6 本章小结
本章详细介绍了Level2行情数据的三种常用格式(二进制、Protobuf、JSON)及其协议结构(消息头、消息体、校验和)。通过对比表格和SVG图,你可以清晰地看到不同格式在性能、可读性和解析复杂度上的差异。同时,对时间戳、价格、数量、买卖方向等核心字段的深入理解,为后续的标签校准工作提供了必要的知识储备。在实际校准过程中,应根据数据来源和场景选择合适的解析方式,并始终关注校验和与字节序等关键细节。