订单簿结构:限价单与市价单、订单簿的构建、买卖盘口深度

做市商的核心战场在哪里?

就在订单簿上。你想想看,我们每天跟价格打交道,本质上就是在跟订单簿里的每一笔挂单博弈。我个人习惯把订单簿比作一个「战场沙盘」——谁在哪个价位埋伏了多少兵力,一目了然。

限价单与市价单:两种最基本的武器

先聊聊这两种订单类型。说实话,很多新手搞不清它们的本质区别。

限价单(Limit Order),说白了就是你指定一个价格,挂在那里等对手来吃。比如我挂一个「买入 BTC,价格 30000 USDT」,那只有市场价格跌到 30000 或以下时才会成交。它的特点是:价格确定,成交不确定

我在项目中遇到过一个问题:某次做市策略里,我挂了大量限价买单,结果市场快速下跌,我的单子全被吃掉,但价格还在往下砸。嗯,这就是限价单的风险——你成了别人的「接盘侠」。

市价单(Market Order)就简单粗暴多了。你不管价格,直接按当前最优价成交。比如「买入 1 BTC,市价」,系统会立刻吃掉卖一、卖二、卖三……直到买够为止。它的特点是:成交确定,价格不确定

核心区别一句话:

  • 限价单 = 我要这个价,爱卖不卖
  • 市价单 = 我要马上成交,贵点也行

做市商主要用限价单来提供流动性,用市价单来调整仓位。我建议你记住这个原则:做市靠限价,对冲靠市价

订单簿的构建:从零开始搭一个

订单簿长什么样?其实就是两个列表:

  • 买单队列(Bids):从高到低排列,价格高的优先
  • 卖单队列(Asks):从低到高排列,价格低的优先

每个价格点上,都挂着一堆订单。价格相同的话,先到先得。

我曾经踩过一个坑:刚开始写订单簿时,我用 Python 的 dict 来存每个价位的订单列表。结果发现,当价格精度很高时(比如 0.01 的 tick size),dict 的 key 会膨胀得特别快,内存直接炸了。后来我改用 sortedcontainers 库里的 SortedDict,才解决了这个问题。

下面是一个简化版的订单簿构建代码:

from sortedcontainers import SortedDict
from collections import deque
import time

class OrderBook:
    def __init__(self):
        # 买单:价格降序排列
        self.bids = SortedDict(lambda x: -x)
        # 卖单:价格升序排列
        self.asks = SortedDict()
        # 每个价格点的订单队列
        self.orders = {}
    
    def add_order(self, order):
        """添加一个限价单"""
        price = order['price']
        side = order['side']
        
        # 选择对应的订单簿
        book = self.bids if side == 'buy' else self.asks
        
        # 如果这个价格还没有队列,新建一个
        if price not in book:
            book[price] = deque()
            self.orders[price] = []
        
        # 记录订单信息
        order_id = f"{side}_{price}_{time.time_ns()}"
        order['id'] = order_id
        book[price].append(order)
        self.orders[price].append(order)
        
        return order_id
    
    def get_top_of_book(self):
        """获取最优买卖价"""
        best_bid = next(iter(self.bids.items())) if self.bids else None
        best_ask = next(iter(self.asks.items())) if self.asks else None
        return best_bid, best_ask
    
    def get_depth(self, levels=5):
        """获取指定深度的盘口"""
        bid_depth = []
        ask_depth = []
        
        for price, queue in self.bids.items():
            if len(bid_depth) >= levels:
                break
            total_qty = sum(o['qty'] for o in queue)
            bid_depth.append((price, total_qty))
        
        for price, queue in self.asks.items():
            if len(ask_depth) >= levels:
                break
            total_qty = sum(o['qty'] for o in queue)
            ask_depth.append((price, total_qty))
        
        return bid_depth, ask_depth

小技巧:实际生产环境中,订单簿的更新频率极高(微秒级)。用 Python 的 deque 做队列,插入和删除都是 O(1) 的,比 list 快很多。我一般还会加一个 order_id 到订单对象的映射,方便快速撤单。

买卖盘口深度:看懂市场的「厚度」

盘口深度,说白了就是每个价位上到底有多少单子。它告诉你两件事:

  1. 流动性好不好——深度越厚,大单对价格的冲击越小
  2. 支撑和阻力在哪——买单密集区是支撑,卖单密集区是阻力

我习惯用「深度图」来直观感受市场情绪。举个例子:

价格 (USDT) 买单数量 (BTC) 卖单数量 (BTC)
30050 12.5
30040 8.3
30030 5.1
30020 3.2
30010 7.8
30000 15.6

看到没?30000 这个价位有 15.6 BTC 的买单撑着,而 30030 以上卖单开始变多。这说明什么?市场在 30000 附近有强支撑,但往上突破 30030 需要吃掉不少卖单。

做市商怎么利用这个信息?

  • 在深度厚的地方挂单——不容易被吃掉,还能赚点手续费返还
  • 在深度薄的地方小心——一个大单就能把价格打穿,你的止损可能来不及
  • 观察深度变化——如果买单深度突然减少,说明有人在撤单,可能要跌

注意:盘口深度是会骗人的。有些大单是「冰山订单」,只显示一部分。还有些是「虚假挂单」,挂上去又撤掉,就是为了引诱你进场。我吃过这个亏——看到卖一有 100 BTC 压着,以为涨不上去,结果人家一撤单,价格直接飞了。

最后说一句,订单簿是动态的。每一毫秒都在变化。你看到的深度,只是那一瞬间的快照。真正的高手,看的是深度变化的趋势,而不是绝对值。

嗯,这一章就到这里。下一章我们聊聊「价差与库存管理」,那是做市商真正赚钱的核心。