2. 盘口快照数据结构解析

盘口快照(Order Book Snapshot)是行情系统在某一时刻对订单簿状态的完整切片。理解它的字段组成,是后续做逐笔事件联动、盘口重建和信号计算的前提。本节围绕三个问题展开:快照里到底有哪些字段、不同市场的快照差异在哪里、以及如何实际获取并解析一份快照。

一、快照的字段组成

一份典型的十档快照,可以拆成四组字段。第一组是时间与标识:交易所时间戳(精确到毫秒或微秒)、本地接收时间戳、合约代码、交易所代码。第二组是买卖十档量价:买一至买十的价格与数量、卖一至卖十的价格与数量,共 40 个数值字段。第三组是衍生指标:最新价、成交量、成交额、持仓量、昨收价、涨跌停价等。第四组是统计与状态:买卖总量、加权平均价、盘口价差等。

这里要特别注意两个约定。其一,价格与数量的对齐关系:买盘价格从高到低排列(买一最高),卖盘价格从低到高排列(卖一最低),档位索引 i 对应第 i 档,价格数组与数量数组必须同下标一一对应,否则重建盘口时会错位。其二,时间戳的参照系:交易所时间戳是撮合系统打上的,本地时间戳是行情网关收到数据时打上的,两者之差反映链路延迟,做逐笔联动时必须明确用哪一个作为事件排序依据,全文保持一致。

十档快照的 40 个量价字段通常以扁平数组形式传输,例如 bids 为 [买一价, 买一量, 买二价, 买二量, ...],解析时按步长 2 拆分,不要误按两个独立数组处理。

二、不同市场的快照差异

股票、期货、数字货币三类市场的快照结构差异明显,直接影响解析代码的写法。

股票市场(以 A 股为例):快照通常包含买卖十档,但价格受涨跌停限制,且存在集合竞价阶段,此时盘口可能只有虚拟撮合价而无真实挂单。字段上常见昨收、开盘、最高、最低、成交量(股)、成交额(元)。部分行情源只提供五档,需要确认档位深度。

期货市场:除十档量价外,必带持仓量和结算价,且合约有到期日,主力合约会切换。期货盘口还常见“开平仓”标志影响逐笔成交方向判断。夜盘与日盘的时间戳跨自然日,解析时不能简单按日期截断。

数字货币市场:档位深度往往远大于十档(常见 20 档甚至全量),数量为币本位小数,价格精度随交易对变化(如 BTC/USDT 价格精度 0.1,某些小币种精度 1e-8)。交易所通常提供 REST 快照 + WebSocket 增量推送的组合,快照自带 lastUpdateId 序列号,用于与增量流对齐。

盘口快照字段结构与三市场差异 盘口快照 Snapshot 某一时刻订单簿完整切片 ① 时间与标识 交易所时间戳 / 本地时间戳 合约代码 / 交易所代码 序列号 lastUpdateId ② 买卖十档量价 买一~买十 价格/数量 卖一~卖十 价格/数量 共 40 个数值字段 ③ 衍生指标 最新价 / 成交量 / 成交额 持仓量 / 昨收 / 涨跌停 结算价(期货) ④ 统计与状态 买卖总量 / 加权均价 盘口价差 / 档位深度 交易状态(竞价/连续) 三市场快照差异对比 股票(A股) • 十档(部分源仅五档) • 涨跌停价约束盘口 • 集合竞价无真实挂单 • 成交量单位:股 • 无持仓量字段 • 昨收/开盘/最高/最低 期货 • 十档 + 持仓量 • 结算价、到期日 • 主力合约会切换 • 开平仓标志影响方向 • 夜盘跨自然日 • 时间戳需处理跨日 数字货币 • 20 档或全量深度 • 数量为币本位小数 • 价格精度随交易对变 • REST 快照 + WS 增量 • lastUpdateId 对齐 • 7×24 无休市

三、快照的获取与样例解析

获取快照有两条常见路径。一是REST 拉取:向行情服务请求一次,得到当前时刻的完整快照,适合初始化盘口。二是WebSocket 订阅:服务端在盘口变化时推送快照或增量,适合实时联动。工程上通常先用 REST 拿一份基准快照,再用 WebSocket 增量流在其上叠加,并用序列号校验连续性。

下面是一份简化的数字货币快照 JSON 样例(示例数据,非实测),字段做了裁剪:

{
  "symbol": "BTCUSDT",
  "lastUpdateId": 1027024,
  "exchangeTs": 1700000000123,
  "localTs":    1700000000158,
  "bids": [["42000.10","1.500"],["42000.00","0.800"],["41999.50","2.300"]],
  "asks": [["42000.50","0.900"],["42001.00","1.200"],["42001.50","0.600"]],
  "lastPrice": "42000.30",
  "volume24h": "15234.78"
}

解析要点:bids 与 asks 是“价格-数量”二元组数组,价格是字符串而非浮点数,必须用高精度小数类型(如 Python 的 Decimal)解析,避免 42000.10 被存成 42000.099999。买一价 42000.10 高于买二价 42000.00,卖一价 42000.50 低于卖二价 42001.00,符合买盘降序、卖盘升序的约定。盘口价差 = 卖一价 − 买一价 = 0.40,中间价 = (42000.10 + 42000.50) / 2 = 42000.30。

再看时间戳:exchangeTs 为 1700000000123,localTs 为 1700000000158,链路延迟约 35 毫秒。做逐笔联动时,若用本地时间排序,需意识到这 35ms 的偏移会让快照与逐笔成交的先后关系产生系统性偏差;更稳妥的做法是以交易所时间戳为主键,本地时间仅用于监控链路质量。

常见错误:把 bids/asks 当成两个独立的价格数组和数量数组分别解析。实际传输多为扁平或二元组结构,务必先确认字段布局,再写拆分逻辑,否则会出现“价格对、数量错位”的隐蔽 bug。

四、解析后的校验清单

一份快照解析完,至少做三项校验再投入使用。第一,档位单调性:买盘价格严格递减、卖盘价格严格递增,出现逆序说明数据损坏或字段错位。第二,价差非负:卖一价应大于等于买一价,若为负说明买卖方向搞反。第三,序列号连续:与上一份快照的 lastUpdateId 比对,若出现跳号,说明中间丢失了增量,需要重新拉取基准快照重建盘口。这三项校验成本很低,却能拦住绝大多数解析层面的错误,是快照进入联动展示前的必要关口。