订单簿数据结构:Level 2 数据解析,Bid/Ask 队列

做市商的核心工作,说白了就是跟订单簿打交道。你每天盯着屏幕上跳动的数字,其实就是在看买卖双方的博弈。Level 2 数据,就是这场博弈的「底牌」——虽然看不到对手是谁,但能看到他们的挂单意图。

我个人习惯把订单簿比作一个「排队系统」。买方排一队,卖方排一队。谁的价格好,谁就排在前面。这个比喻虽然简单,但能帮你理解后面所有的逻辑。

什么是 Level 2 数据?

很多新手一开始只盯着「买一」「卖一」看,也就是所谓的 Level 1 数据。但做市商要是只看这个,那跟裸奔没什么区别。

Level 2 数据,展示的是订单簿的完整深度。它告诉你:

  • 在每一个价格档位上,有多少人在等着买
  • 在每一个价格档位上,有多少人在等着卖
  • 这些订单是限价单还是市价单(部分交易所会区分)

举个例子,BTC/USDT 的订单簿可能是这样的:

价格 Bid 数量 Ask 数量 价格
30000 5.2 BTC 3.1 BTC 30001
29999 8.7 BTC 4.5 BTC 30002
29998 12.3 BTC 6.8 BTC 30003

你看,左边是买单队列(Bid),右边是卖单队列(Ask)。每个价格档位上的挂单量,就是所谓的「深度」。

核心要点:Level 2 数据让你看到「流动性分布」。哪个价格区间挂单最多,哪里就是多空争夺的「战场」。

Bid/Ask 队列的结构

队列的本质,就是一个按价格排序的列表。买单从高到低排,卖单从低到高排。为什么?因为买方当然希望用更低的价格买,但为了成交,他必须出价更高才能排到前面。反过来,卖方希望卖得更贵,但为了成交,他必须降价。

嗯,这里要注意:队列里的每个「档位」,其实是一个聚合数据。交易所会把同一价格的所有订单合并成一个「档位」。你看到的 5.2 BTC,可能是 10 个订单的总和,也可能是 1 个大户的挂单。

我在项目中遇到过这样的情况:某个价格档位突然出现巨量挂单,看起来像是「支撑位」。结果一查,是某个机构在拆单挂单,故意制造假象。所以,只看总量是不够的,还要看订单的「微观结构」。

如何解析 Level 2 数据?

不同的交易所,数据格式略有不同。但核心逻辑是一样的。我们以 Binance 的 WebSocket 数据为例:

{
  "e": "depthUpdate",   // 事件类型
  "E": 123456789,       // 事件时间
  "s": "BTCUSDT",       // 交易对
  "U": 157,             // 第一个更新ID
  "u": 160,             // 最后一个更新ID
  "b": [                // 买单队列
    ["30000", "5.2"],   // [价格, 数量]
    ["29999", "8.7"]
  ],
  "a": [                // 卖单队列
    ["30001", "3.1"],
    ["30002", "4.5"]
  ]
}

解析的时候,我建议你注意两点:

  1. 增量更新 vs 全量快照:大部分交易所会先给你一个全量快照,然后推送增量更新。增量更新只告诉你「哪个价格变了,变成多少」。你需要自己维护一个本地的订单簿副本。
  2. 更新ID的连续性:每个更新都有一个 ID。如果发现 ID 不连续,说明你漏掉了数据。这时候必须重新请求全量快照,否则你的本地订单簿会「漂移」。
我曾经踩过的坑:有一次,我的策略在回测时表现完美,但实盘时频繁亏损。排查了三天,发现是本地订单簿跟交易所不同步了。原因就是网络抖动丢了一个更新包,而我还在傻傻地累加增量。从那以后,我每 100 次更新就强制做一次全量校验。

队列的「微观特征」

做市商不仅要看队列的「静态分布」,还要看它的「动态变化」。我个人总结了几个关键指标:

  • 队列斜率:从买一到买五,挂单量的变化趋势。如果斜率很陡,说明流动性集中在某个价格附近。
  • 队列厚度:某个价格区间内的总挂单量。厚度越大,价格越难突破。
  • 队列更新频率:如果某个档位的挂单量频繁变化,说明有人在「试盘」或者「撤单钓鱼」。

你想想看,如果一个价格档位上的挂单量突然从 10 BTC 变成 0.1 BTC,然后又变回 10 BTC,这正常吗?大概率是有人在「挂单诱多」或者「挂单诱空」。做市商要是跟着这种假信号走,很容易被割韭菜。

用 Python 构建本地订单簿

下面是一个简单的实现思路。我习惯用字典来存储订单簿,价格作为 key,数量作为 value:

class OrderBook:
    def __init__(self):
        self.bids = {}  # 买单队列
        self.asks = {}  # 卖单队列
    
    def apply_snapshot(self, snapshot):
        """应用全量快照"""
        self.bids = {float(price): float(qty) 
                     for price, qty in snapshot['b']}
        self.asks = {float(price): float(qty) 
                     for price, qty in snapshot['a']}
    
    def apply_update(self, update):
        """应用增量更新"""
        for price, qty in update['b']:
            price = float(price)
            qty = float(qty)
            if qty == 0:
                # 数量为0,说明这个档位被撤单了
                self.bids.pop(price, None)
            else:
                self.bids[price] = qty
        
        for price, qty in update['a']:
            price = float(price)
            qty = float(qty)
            if qty == 0:
                self.asks.pop(price, None)
            else:
                self.asks[price] = qty
    
    def get_top_n(self, n=5):
        """获取前N档深度"""
        sorted_bids = sorted(self.bids.items(), 
                             reverse=True)[:n]
        sorted_asks = sorted(self.asks.items())[:n]
        return sorted_bids, sorted_asks

这段代码看起来简单,但实际生产环境中要考虑的事情很多。比如:

  • 数据延迟:WebSocket 推送的数据可能有几十毫秒的延迟
  • 内存管理:如果交易对很多,订单簿会占用大量内存
  • 并发安全:多个线程同时更新订单簿时,要加锁
一个小技巧:我习惯用 sortedcontainers 库来维护有序的订单簿。它的 SortedDict 可以自动按 key 排序,比每次手动排序快得多。尤其是在高频场景下,这个优化能省下不少时间。

订单簿的「可视化」

光看数字是不够的。我建议你把订单簿画出来,直观感受一下市场的「呼吸」。下面这张图展示了典型的订单簿结构:

订单簿深度结构图 Bid 队列(买单) 买一: 30000 5.2 BTC 买二: 29999 8.7 BTC 买三: 29998 12.3 BTC 买四: 29997 6.1 BTC 买五: 29996 3.5 BTC Ask 队列(卖单) 卖一: 30001 3.1 BTC 卖二: 30002 4.5 BTC 卖三: 30003 6.8 BTC 卖四: 30004 2.2 BTC 卖五: 30005 1.8 BTC 价差 = 1 USDT 价格从高到低排列,宽度代表挂单量大小

这张图里,每个档位的「宽度」代表了挂单量的大小。你看买一只有 5.2 BTC,但买三有 12.3 BTC。这说明在 29998 这个价位上,有更强的支撑。如果价格跌到 29998,可能会遇到大量买单「接盘」。

反过来看卖单,卖三有 6.8 BTC,是卖单里最厚的。这说明 30003 是一个重要的「阻力位」。做市商在做策略时,这些位置就是天然的「止损点」和「止盈点」。

总结一下

Level 2 数据解析,说白了就是两件事:

  • 把交易所推送的原始数据,转换成你本地能用的订单簿结构
  • 从订单簿的「静态分布」和「动态变化」中,提取出有交易价值的信号

我个人觉得,做市商的核心竞争力不在于算法有多复杂,而在于对订单簿的理解有多深。你能从队列的细微变化中读出市场的「情绪」,那你就能比别人早一步行动。

嗯,今天就先聊到这里。记住,订单簿是你的「战场地图」,别只看地图上的「山川河流」,还要看「风吹草动」。


专注资料整理