1. 订单簿基础:限价单与市价单、买卖盘口、订单簿数据结构

做量化交易这些年,我见过不少新手一上来就盯着K线图猛看,却忽略了最底层的东西——订单簿。说实话,订单簿才是市场最真实的脉搏。K线是二手加工品,订单簿才是原始数据流。今天我们就从最基础的东西聊起。

1.1 两种最基础的订单类型

先说说限价单和市价单。这两个概念看似简单,但我在项目中见过太多人把它们用错了。

限价单(Limit Order):你指定一个价格,只有市场达到这个价格时才会成交。说白了,就是「我只愿意在这个价位买/卖,贵了我不买,便宜了我不卖」。

市价单(Market Order):你不管价格,直接按当前最优价格成交。说白了就是「我现在就要成交,价格无所谓」。

嗯,这里要注意一个关键区别:限价单提供流动性,市价单消耗流动性。这个区别在后面的课程里会反复出现。

核心要点:限价单 = 挂单等待,市价单 = 立即吃掉。两者共同构成了订单簿的「供给」和「需求」两端。

1.2 买卖盘口:Bid 与 Ask

订单簿的核心结构其实很简单,就两个队列:

  • 买盘(Bid):所有想买入的人挂的限价单,按价格从高到低排列
  • 卖盘(Ask):所有想卖出的人挂的限价单,按价格从低到高排列

为什么买盘从高到低?你想想看,谁出价高谁就先成交,这是很自然的逻辑。卖盘反过来,谁卖得便宜谁先成交。

我刚开始做高频策略时,犯过一个低级错误——把买卖盘的方向搞反了。结果策略一直在反向交易,亏了不少手续费才反应过来。从那以后,我每次写订单簿相关的代码,第一件事就是确认 Bid 和 Ask 的方向。

1.3 订单簿的数据结构

在实际开发中,订单簿的数据结构怎么设计?我个人的习惯是用两个有序字典(OrderedDict)或者红黑树来实现。为什么?因为订单簿的核心操作就三个:

  1. 新增订单:插入到对应价格层级
  2. 撤销订单:从对应价格层级删除
  3. 成交:从最优价格层级移除

这三个操作都需要 O(log n) 或 O(1) 的时间复杂度。用数组?不行,插入删除太慢。用哈希表?也不行,没法按价格排序。

下面是我常用的一个简化版订单簿实现:

class OrderBook:
    def __init__(self):
        self.bids = {}  # 价格 -> 数量
        self.asks = {}  # 价格 -> 数量
        self.bid_prices = []  # 排序后的买价列表
        self.ask_prices = []  # 排序后的卖价列表
    
    def update_bid(self, price, volume):
        if volume == 0:
            # 撤销订单
            if price in self.bids:
                del self.bids[price]
                self.bid_prices.remove(price)
        else:
            # 新增或更新
            self.bids[price] = volume
            if price not in self.bid_prices:
                self.bid_prices.append(price)
                self.bid_prices.sort(reverse=True)  # 从高到低
    
    def update_ask(self, price, volume):
        if volume == 0:
            if price in self.asks:
                del self.asks[price]
                self.ask_prices.remove(price)
        else:
            self.asks[price] = volume
            if price not in self.ask_prices:
                self.ask_prices.append(price)
                self.ask_prices.sort()  # 从低到高
    
    @property
    def best_bid(self):
        return self.bid_prices[0] if self.bid_prices else None
    
    @property
    def best_ask(self):
        return self.ask_prices[0] if self.ask_prices else None
    
    @property
    def spread(self):
        if self.best_bid and self.best_ask:
            return self.best_ask - self.best_bid
        return None

实战经验:这个实现只适合教学和回测。实盘交易中,我建议用 sortedcontainers 库的 SortedDict,或者直接用 Redis 的有序集合。我在一个低延迟项目中试过纯 Python 实现,结果订单簿更新一次要 0.5 毫秒,完全扛不住高频场景。

1.4 订单簿的层级结构

一个完整的订单簿,通常包含多个价格层级。每个层级上可能有多个订单,但价格相同。举个例子:

层级 买盘价格 买盘数量 卖盘价格 卖盘数量
1 100.00 500 100.10 300
2 99.90 800 100.20 600
3 99.80 1200 100.30 900
4 99.70 400 100.40 200
5 99.60 600 100.50 1000

这个表格里,最优买价是 100.00,最优卖价是 100.10,价差(spread)是 0.10。如果你下了一个市价买单,会先吃掉 100.10 的 300 股,如果还不够,继续吃 100.20 的 600 股,以此类推。

避坑指南:我曾经在回测中忽略了「部分成交」的情况。市价单可能只成交一部分,剩下的部分会变成限价单挂在订单簿上。这个细节如果不处理,回测结果会和实盘差很多。

1.5 订单簿的视觉化

为了让你更直观地理解订单簿的结构,我画了一张图:

订单簿微观结构示意图 买盘 (Bid) 卖盘 (Ask) 层级1: 100.00 500 层级2: 99.90 800 层级3: 99.80 1,200 层级4: 99.70 400 层级5: 99.60 600 层级1: 100.10 300 层级2: 100.20 600 层级3: 100.30 900 层级4: 100.40 200 层级5: 100.50 1,000 价差 = 0.10 价格从高到低排列(买盘) / 价格从低到高排列(卖盘) 数字表示该价格层级上的总挂单数量

这张图展示了一个典型的订单簿结构。左边是买盘,价格从高到低排列;右边是卖盘,价格从低到高排列。中间的价差就是市场流动性的直接体现。

1.6 订单簿的实时更新机制

实盘中的订单簿是动态变化的。每一秒都有成千上万的订单进来、撤销、成交。我处理过的一个高频数据源,每秒推送超过 10 万条订单簿更新消息。

订单簿的更新消息通常包含以下几种类型:

  • 新增:一个新的限价单进入订单簿
  • 撤销:一个已有的限价单被取消
  • 成交:一个限价单被市价单吃掉(部分或全部)
  • 修改:一个限价单的价格或数量被修改(有些交易所不支持)

我个人习惯用「快照 + 增量更新」的方式来维护订单簿。先获取一个完整的订单簿快照,然后根据后续的增量消息逐条更新。这样做的好处是,即使某条增量消息丢失了,也能通过重新获取快照来恢复。

关键点:订单簿的深度(Depth)指的是每个价格层级上的挂单总量。深度越厚,说明这个价格附近的流动性越好。我经常用「深度图」来判断市场的支撑位和阻力位。

1.7 常见的数据源与格式

不同交易所的订单簿数据格式略有不同,但核心结构大同小异。以币安为例,它的订单簿数据长这样:

{
  "lastUpdateId": 1027024,
  "bids": [
    ["4.00000000", "431.00000000"],  // [价格, 数量]
    ["3.90000000", "152.00000000"],
    ["3.80000000", "100.00000000"]
  ],
  "asks": [
    ["4.20000000", "245.00000000"],
    ["4.30000000", "320.00000000"],
    ["4.40000000", "150.00000000"]
  ]
}

这个格式很直观。bids 数组里每个元素是 [价格, 数量],按价格从高到低排列。asks 同理,按价格从低到高排列。

小技巧:处理订单簿数据时,我建议把价格和数量都转成浮点数或 Decimal 类型。字符串比较在 Python 里是按字典序的,100.0 和 99.9 比较会出错。我曾经因为这个 bug 排查了整整一个下午。

好了,订单簿的基础知识就讲到这里。这些概念虽然简单,但它们是后续所有流动性信号和微观结构分析的基础。下一节我们会深入讨论如何从订单簿中提取有用的交易信号。


公众号:蓝海数据掘金营,微信deep3321