行情数据基础:Level-1/Level-2行情、Tick数据、Order Book数据结构

高频交易的核心是对市场微观结构的实时建模。理解不同级别的行情数据是构建收益模型的第一步。本章将系统梳理从公开行情到深度订单簿的数据层级、字段含义与Python中的典型数据结构。

一、行情数据层级总览

全球交易所通常提供两种级别的实时行情数据:Level-1(市场摘要)与Level-2(深度行情)。两者的核心差异在于数据粒度与信息含量。

对比维度 Level-1(标准行情) Level-2(深度行情)
数据内容 最新成交价、涨跌幅、最高/最低价、成交量、最佳买一/卖一价与量 多档位买卖挂单(通常10~50档)、逐笔成交、逐笔委托
更新频率 秒级或事件驱动(成交触发) 微秒级,每笔委托变动即推送
信息粒度 聚合快照 全量订单簿快照 + 增量更新
典型用途 趋势跟踪、技术指标计算 订单簿压力分析、流动性建模、微观结构策略
数据成本 较低(多数券商免费提供) 较高(需单独订阅或购买)
关键认知: Level-2 数据是高频策略的“原料”,它揭示了价格形成过程中的供需博弈细节,而不仅仅是价格结果。

二、Tick数据:最细粒度的市场记录

Tick数据是交易所对外发布的最小时间单位数据记录。每一笔成交或委托变动都会产生一个Tick。根据数据来源,Tick可分为两类:

  • 成交Tick(Trade Tick):记录每一笔实际成交的价格、数量、时间戳、成交方向(买方主动/卖方主动)。
  • 报价Tick(Quote Tick):记录订单簿中任意一档挂单价格或数量的变化。

一个典型的成交Tick数据字段如下:

{
  "symbol": "600519.SH",
  "timestamp": 1700000001234567,   // 微秒级Unix时间戳
  "price": 1680.50,
  "volume": 200,
  "turnover": 336100.0,
  "trade_type": "B"                // B=买方主动, S=卖方主动
}
实战注意: 国内A股市场Tick数据的时间戳精度通常为毫秒或微秒,但不同数据源可能存在时钟偏差。在回测中,建议对同一交易所的数据做时间戳对齐处理。

三、Order Book(订单簿)数据结构

订单簿是交易所撮合引擎维护的限价委托队列的实时快照。它按价格从高到低排列所有未成交的买单(Bid)和卖单(Ask)。

3.1 订单簿的层级结构

一个标准的多档订单簿包含以下核心元素:

  • 价格档位(Price Level):每个价格点为一个档位。
  • 委托量(Size):该价格档位上所有未成交委托的总手数。
  • 委托笔数(Order Count):该价格档位上挂单的笔数(部分交易所提供)。
  • 累计量(Cumulative Volume):从最优价到当前档位的累计委托总量。

以下是一个5档订单簿的示例数据:

{
  "bids": [
    [1680.00, 1500, 12],   // [价格, 总量, 笔数]
    [1679.80, 2300, 18],
    [1679.60, 800,  5],
    [1679.40, 3200, 25],
    [1679.20, 1100, 9]
  ],
  "asks": [
    [1680.20, 1800, 14],
    [1680.40, 2100, 16],
    [1680.60, 900,  7],
    [1680.80, 2800, 22],
    [1681.00, 1300, 10]
  ],
  "timestamp": 1700000001234567
}

3.2 订单簿的增量更新机制

高频场景下,交易所不会每次都推送全量订单簿,而是采用增量快照(Snapshot + Incremental Update)模式:

  1. 全量快照(Snapshot):每隔一定时间(如100ms)或连接建立时推送一次完整订单簿。
  2. 增量更新(Update):每次订单簿发生变动时,推送发生变化的档位信息(价格、数量变化、删除或新增)。

增量更新的典型消息格式:

{
  "action": "update",       // add / update / delete
  "side": "bid",
  "price": 1679.80,
  "size": 2500,             // 更新后的新数量
  "order_count": 19
}
常见陷阱: 增量更新可能乱序到达,或出现重复/丢失。生产环境中必须维护一个本地订单簿副本,通过序列号(Sequence Number)校验增量消息的连续性,并在检测到断点后请求全量快照重建。

四、订单簿核心指标与SVG结构图

从订单簿数据中,我们可以计算一系列反映市场微观结构的指标:

  • 买卖价差(Bid-Ask Spread):最优卖价 - 最优买价。
  • 市场深度(Market Depth):各档位累计委托量,反映价格滑点风险。
  • 订单簿斜率(Order Book Slope):价格-累计量曲线的斜率,衡量价格弹性。
  • 订单簿不平衡(Order Book Imbalance):买方总深度 / 卖方总深度,预测短期价格方向。

下图展示了订单簿的完整数据结构与增量更新流程:

订单簿数据结构与增量更新流程 全量快照 (Snapshot) • 价格档位列表 • 每档委托总量 • 每档委托笔数 • 时间戳 • 序列号 (Seq No.) 推送频率: 100ms~1s 增量更新 (Update) • 操作类型 (add/update/delete) • 买卖方向 (bid/ask) • 价格 • 更新后数量 • 序列号 (Seq No.) 推送频率: 每笔委托变动 本地订单簿维护引擎 1. 接收全量快照 → 初始化 2. 校验增量序列号连续性 3. 应用增量更新到本地副本 4. 检测断点 → 请求重传快照 订单簿衍生指标 • 买卖价差 (Spread) = Ask[0] - Bid[0] • 市场深度 (Depth) = Σ 各档委托量 • 订单簿不平衡 (OBI) = BidDepth / AskDepth • 价格冲击成本估算

五、Python中的订单簿数据结构实现

在Python中,高频订单簿通常使用有序字典(OrderedDict)或红黑树(如bintrees库)来维护价格档位的快速插入、删除和查询。以下是一个轻量级订单簿类的核心实现:

from collections import OrderedDict
from typing import Dict, List, Tuple

class OrderBook:
    def __init__(self):
        self.bids: Dict[float, int] = OrderedDict()  # 价格 → 总量
        self.asks: Dict[float, int] = OrderedDict()
        self.sequence: int = 0

    def apply_snapshot(self, bids: List[Tuple], asks: List[Tuple], seq: int):
        """全量快照初始化"""
        self.bids.clear()
        self.asks.clear()
        for price, size, _ in bids:
            if size > 0:
                self.bids[price] = size
        for price, size, _ in asks:
            if size > 0:
                self.asks[price] = size
        self.sequence = seq

    def apply_update(self, side: str, price: float, size: int, seq: int):
        """增量更新"""
        if seq <= self.sequence:
            return  # 丢弃过期消息
        book = self.bids if side == 'bid' else self.asks
        if size == 0:
            book.pop(price, None)  # 删除档位
        else:
            book[price] = size     # 新增或更新
        self.sequence = seq

    def get_top_n(self, n: int = 5) -> Tuple[List, List]:
        """获取前N档买卖盘口"""
        sorted_bids = sorted(self.bids.items(), reverse=True)[:n]
        sorted_asks = sorted(self.asks.items())[:n]
        return sorted_bids, sorted_asks

    def spread(self) -> float:
        """当前买卖价差"""
        if not self.bids or not self.asks:
            return float('inf')
        best_bid = max(self.bids.keys())
        best_ask = min(self.asks.keys())
        return best_ask - best_bid
性能提示: 在实盘高频场景中,建议使用 sortedcontainers 库的 SortedDict 或 C 扩展实现的红黑树,以应对每秒数万次的订单簿更新。纯 Python 的 OrderedDict 在档位数超过 50 档时可能成为瓶颈。

六、Level-2行情与Tick数据的关联

理解两者关系对构建特征工程至关重要:

  • 成交Tick 是订单簿中吃单(Market Order)的结果,它会消耗订单簿中对应档位的委托量。
  • 报价Tick 是订单簿中挂单(Limit Order)的变动,包括新增、撤销和部分成交后的数量修改。
  • 通过对比成交Tick的方向与订单簿变化,可以识别主动买卖压力,这是高频收益模型的核心输入之一。

下图展示了成交Tick与订单簿之间的联动关系:

成交Tick与订单簿联动关系 订单簿 (Order Book) Ask: 1680.20 (1800) Ask: 1680.00 (1500) Bid: 1679.80 (2300) Bid: 1679.60 (800) ... 成交Tick (Trade Tick) 价格: 1680.00 数量: 500 方向: 卖方主动 (S) 时间: 10:00:00.123456 吃单消耗 卖方主动成交 → 消耗Bid档位 → 订单簿买一档数量减少

七、数据质量与实战避坑

高频数据常见问题:
  • 时间戳抖动:同一毫秒内多条消息的时间戳可能相同,需使用序列号排序。
  • 数据缺失:网络抖动可能导致部分增量更新丢失,必须实现断点重连与快照重建逻辑。
  • 交易所规则差异:不同交易所对Level-2数据的档位数量、更新频率、字段定义存在差异,需仔细阅读API文档。
  • 回测中的未来信息:使用Tick数据回测时,务必确保每个Bar的OHLC计算只使用该Bar结束前已产生的Tick,避免引入未来数据。

八、小结

本章建立了高频行情数据的基础认知框架:

  • Level-1 提供聚合快照,适合低频策略;Level-2 提供深度订单簿,是高频建模的基石。
  • Tick数据 是市场最细粒度的记录,分为成交Tick和报价Tick。
  • 订单簿 通过全量快照+增量更新的方式维护,本地引擎需处理乱序和断点问题。
  • Python中可使用 OrderedDict 或 SortedDict 实现轻量级订单簿,生产环境需关注性能。