订单簿基础:限价单与市价单、买卖盘口、深度图、订单簿快照与增量更新

订单簿,说白了就是交易所的「记账本」。它记录着所有交易者挂出来的买卖意愿。我个人习惯把订单簿看作市场的「心电图」——每一笔挂单都是市场情绪的跳动。今天我们就来拆解这个核心数据结构。

一、限价单与市价单:两种最基本的交易指令

先聊聊这两种订单类型。它们构成了订单簿的「输入源」。

限价单(Limit Order):你指定一个价格,只有市场达到这个价格时才会成交。比如你挂一个「买入 1 BTC,价格 30000 USDT」,那只有卖盘价格 ≤ 30000 时才会成交。

市价单(Market Order):你只指定数量,不指定价格。系统会以当前最优价格立即成交。说白了就是「不管多少钱,先买了再说」。

我在项目中遇到过一个问题:有学员问「为什么市价单有时候成交价格比预期差很多?」嗯,这里要注意——当市场流动性不足时,市价单会吃掉多个档位的订单,导致滑点。我曾经在回测中忽略了这个细节,结果实盘时亏损了不少。

核心区别:限价单提供流动性,市价单消耗流动性。做市商靠限价单赚钱,交易者靠市价单快速进出。

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

买卖盘口(Order Book)由两部分组成:

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

你想想看,买盘的最高价和卖盘的最低价之间有个空隙,这就是「价差」(Spread)。价差越小,说明市场流动性越好。

举个例子,假设当前 BTC/USDT 的订单簿如下:

买盘(Bid) 价格 数量 卖盘(Ask) 价格 数量
买1 30000 0.5 卖1 30005 0.3
买2 29995 1.2 卖2 30010 0.8
买3 29990 2.0 卖3 30015 1.5

这里价差是 5 USDT。如果价差突然扩大到 20 USDT,说明市场出现了异常——可能是大新闻发布,也可能是做市商撤单了。

三、深度图:把订单簿「画」出来

深度图(Depth Chart)是订单簿的可视化呈现。它把买卖双方的累计挂单量画成两条曲线。

我个人习惯用深度图来判断「支撑位」和「阻力位」。比如买盘深度很厚(曲线陡峭),说明这个价格有大量买单托底,不容易跌破。反之,卖盘深度厚,说明上方抛压重。

为什么会这样?因为深度图本质上反映了市场参与者的「心理价位」。我曾经在分析某个山寨币时,发现它的买盘深度在 0.5 美元处突然变厚——后来才知道那是项目方在护盘。

实战技巧:当深度图出现「断层」(某个价格区间没有挂单),说明流动性真空。市价单穿过这个区域时会产生剧烈波动。我一般会避开这种品种做高频交易。

四、订单簿快照与增量更新:数据获取的两种方式

做量化交易时,我们需要实时获取订单簿数据。交易所通常提供两种方式:

快照(Snapshot):一次性获取当前订单簿的完整状态。比如请求一次,拿到所有买盘和卖盘的挂单。

增量更新(Incremental Update):只推送发生变化的部分。比如「买1价格从 30000 变为 30001,数量从 0.5 变为 0.3」。

嗯,这里要注意:快照数据量大但频率低,增量更新数据量小但频率高。实际应用中,我们通常先获取一次快照,然后持续接收增量更新来维护本地订单簿。

我曾经在开发一个高频交易系统时,只用了增量更新,结果因为网络丢包导致本地订单簿和交易所不一致——那笔交易亏了 2 个 BTC。从那以后,我每隔 10 秒会重新请求一次快照做「校准」。

避坑指南:增量更新必须保证顺序。如果收到乱序的更新,本地订单簿会「错位」。我曾经因为 WebSocket 重连后没有重新获取快照,导致订单簿数据完全错乱——那真是血的教训。

五、代码实现:用 Python 维护订单簿

下面是一个简单的订单簿维护代码示例。它展示了如何用快照初始化,然后用增量更新保持同步。

class OrderBook:
    def __init__(self):
        self.bids = {}  # 价格 -> 数量
        self.asks = {}
    
    def apply_snapshot(self, snapshot):
        """应用快照,重置订单簿"""
        self.bids = {item[0]: item[1] for item in snapshot['bids']}
        self.asks = {item[0]: item[1] for item in snapshot['asks']}
    
    def apply_update(self, update):
        """应用增量更新"""
        for price, size in update['bids']:
            if size == 0:
                self.bids.pop(price, None)  # 数量为0表示撤单
            else:
                self.bids[price] = size
        for price, size in update['asks']:
            if size == 0:
                self.asks.pop(price, None)
            else:
                self.asks[price] = size
    
    def get_best_bid(self):
        return max(self.bids.keys()) if self.bids else None
    
    def get_best_ask(self):
        return min(self.asks.keys()) if self.asks else None
    
    def get_spread(self):
        best_bid = self.get_best_bid()
        best_ask = self.get_best_ask()
        if best_bid and best_ask:
            return best_ask - best_bid
        return None

这段代码虽然简单,但包含了核心逻辑:用字典存储价格-数量映射,增量更新时判断数量是否为 0 来决定是新增、修改还是删除。

六、知识体系结构图

下面我用一张 SVG 图来总结本章的知识体系。它展示了订单簿的核心概念和它们之间的关系。

订单簿核心知识体系 订单簿 (Order Book) 交易指令 盘口结构 数据获取方式 限价单 市价单 买盘 (Bid) 卖盘 (Ask) 快照 增量更新 提供/消耗流动性 价差 (Spread) 本地维护与校准 深度图 (可视化呈现) 交易指令 盘口结构 数据获取

这张图把订单簿拆解为三个维度:交易指令(输入)、盘口结构(存储)、数据获取(维护)。三者共同构成了订单簿的完整生命周期。

个人建议:初学者可以先从快照入手,理解订单簿的静态结构。等熟悉了再研究增量更新。别一上来就搞实时流处理——我见过太多人把简单问题复杂化了。

好了,订单簿的基础就聊到这里。记住一句话:订单簿是市场的「底层语言」,读懂它,你就能听懂市场在说什么。


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