一、订单簿基础:限价单与市价单、买卖盘口、深度图解读、订单簿数据结构

做量化交易这些年,我见过太多人一上来就研究各种复杂策略,结果连订单簿都没搞明白。说实话,订单簿就是市场的「心电图」,看不懂它,你就是在闭着眼睛交易。

这一章,咱们把订单簿的底裤扒干净。从最基础的单子类型,到盘口数据怎么读,再到深度图背后的博弈逻辑,最后我会给你看订单簿在内存里到底长什么样。

1.1 限价单 vs 市价单:两种「下单姿势」

先问个问题:你想买比特币,现在卖一价是30000 USDT。你该怎么办?

两种选择:

  • 市价单:不管价格,直接吃掉卖一的所有单子。成交价就是30000,但可能滑到30001、30002。
  • 限价单:你挂一个29990的买单,等着别人来砸给你。成交价固定,但可能等很久,甚至永远成交不了。

我个人习惯把市价单叫「急性子单」,限价单叫「守株待兔单」。你想想看,做高频策略时,市价单是抢跑用的;做做市策略时,限价单才是你的武器。

核心区别一句话:市价单保证成交,不保证价格;限价单保证价格,不保证成交。

我在项目中遇到过一件事:有个学员用市价单做套利,结果遇到流动性枯竭,一笔单子滑了0.3%。嗯,0.3%在套利里基本等于白干。从那以后,我建议所有做套利的朋友,至少用「限价单+冰山委托」的组合。

1.2 买卖盘口:订单簿的「骨架」

打开任何一个交易所的深度图,你会看到左右两列数字。左边是买盘(Bid),右边是卖盘(Ask)。

举个例子,BTC/USDT的盘口可能是这样的:

买价 (Bid) 买量 卖价 (Ask) 卖量
30000 1.5 BTC 30005 2.0 BTC
29995 3.0 BTC 30010 1.8 BTC
29990 5.0 BTC 30015 4.0 BTC

这里有个关键概念叫「买卖价差」(Spread)。就是卖一价减去买一价,30005 - 30000 = 5 USDT。价差越小,说明市场流动性越好。我记得2017年比特币刚火的时候,价差经常几十美元,现在主流交易所基本稳定在1-2美元。

避坑指南:我曾经以为价差越小越好,直到有一次在某个小交易所做套利,价差只有0.1 USDT,但一成交才发现,手续费吃掉了一半利润。所以看价差时,一定要把手续费算进去。

1.3 深度图解读:订单簿的「肌肉」

深度图就是把所有买单和卖单按价格累加起来,画成两条曲线。左边是买盘累积,右边是卖盘累积。

怎么看深度图?我教你三个要点:

  1. 看斜率:曲线越陡,说明该价格附近堆积的单子越多,支撑或阻力越强。
  2. 看缺口:如果某个价格区间突然没有单子,那就是「流动性真空区」。价格一旦穿过这里,会像自由落体一样。
  3. 看深度:比如你想买100个BTC,看深度图就知道会不会把价格打穿。

为什么会这样?说白了,深度图就是市场参与者的「心理防线」。每个挂单背后都是一个活生生的人,或者一个算法。你想想看,当价格逼近某个密集挂单区时,挂单的人会不会撤单?这就是博弈。

下面是我手绘的一张订单簿结构图,帮你理清思路:

订单簿核心结构 买盘 (Bid Side) 卖盘 (Ask Side) 价格 ↓ 数量 30000 1.5 BTC 29995 3.0 BTC 29990 5.0 BTC 29985 8.0 BTC ... ... 价格 ↑ 数量 30005 2.0 BTC 30010 1.8 BTC 30015 4.0 BTC 30020 6.0 BTC ... ... 价差 (Spread) 5 USDT 买一价: 30000 | 卖一价: 30005 | 最新价: 30002

1.4 订单簿数据结构:内存里的「战场」

好了,现在咱们聊聊代码层面的事。订单簿在内存里到底怎么存的?

最经典的数据结构是「价格-数量映射表」,说白了就是一个有序字典。买盘按价格降序排列,卖盘按价格升序排列。

我一般用Python的sortedcontainers库来实现,或者直接用dictlist手动排序。给你看个简化版:

class OrderBook:
    def __init__(self):
        self.bids = {}  # 买盘: {价格: 数量}
        self.asks = {}  # 卖盘: {价格: 数量}
        self.bid_prices = []  # 排序后的买价列表
        self.ask_prices = []  # 排序后的卖价列表
    
    def update(self, side, price, size):
        """更新订单簿"""
        if side == 'bid':
            if size == 0:
                # 数量为0表示撤单
                self.bids.pop(price, None)
            else:
                self.bids[price] = size
            # 重新排序
            self.bid_prices = sorted(self.bids.keys(), reverse=True)
        else:
            if size == 0:
                self.asks.pop(price, None)
            else:
                self.asks[price] = size
            self.ask_prices = sorted(self.asks.keys())
    
    def get_top_of_book(self):
        """获取最优买卖价"""
        best_bid = self.bid_prices[0] if self.bid_prices else None
        best_ask = self.ask_prices[0] if self.ask_prices else None
        return best_bid, best_ask

注意:实际生产环境中,订单簿更新频率极高(毫秒级)。我曾经用纯Python实现,结果每秒处理5000次更新就卡死了。后来改用C++或者用Redis的有序集合(ZSET),才扛住高频场景。

这里有个细节:交易所推送的订单簿数据,通常有两种格式:

  • 增量更新:只推送变化的部分(推荐,带宽小)
  • 全量快照:每隔一段时间推送整个订单簿(用于校验)

我个人习惯的做法是:维护一个本地订单簿,用增量更新实时修改,每隔10秒用全量快照做一次「对账」。嗯,这招帮我避免过好几次数据错乱的问题。

1.5 订单簿不平衡度:量化交易的「圣杯」?

终于聊到核心了。订单簿不平衡度(Order Book Imbalance),说白了就是看买盘和卖盘谁更强。

最简单的公式:

imbalance = (bid_volume - ask_volume) / (bid_volume + ask_volume)

这个值在-1到1之间。正数表示买盘强,负数表示卖盘强。但说实话,只看总量太粗糙了。我一般会看不同价格深度的不平衡:

  • 近端不平衡:只看买一卖一附近(反应短期博弈)
  • 远端不平衡:看买五卖五甚至更深(反应中长期意图)

举个例子,如果近端买盘强但远端卖盘更强,说明什么?说明有人在近端拉高诱多,但远端已经挂好了卖单准备砸盘。这种「背离」信号,我用来做反转策略效果不错。

我的经验:不平衡度不是万能的。2019年我靠这个指标赚了不少,但2020年3月12日那天,市场暴跌时订单簿瞬间失效——因为所有人都撤单了,流动性直接消失。所以记住:任何指标都要结合市场环境看。

好了,这一章的内容就这些。订单簿是量化交易的「地基」,地基不稳,上面盖什么楼都得塌。下一章咱们会深入讲不平衡度的各种变体,以及怎么用它构建实盘策略。


公众号:蓝海资料掘金营,微信deep3321