订单簿基础:限价单与市价单、买卖盘口、深度图、订单簿快照与增量更新
订单簿,说白了就是交易所的「记账本」。它记录着所有交易者挂出来的买卖意愿。我个人习惯把订单簿看作市场的「心电图」——每一笔挂单都是市场情绪的跳动。今天我们就来拆解这个核心数据结构。
一、限价单与市价单:两种最基本的交易指令
先聊聊这两种订单类型。它们构成了订单簿的「输入源」。
限价单(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 图来总结本章的知识体系。它展示了订单簿的核心概念和它们之间的关系。
这张图把订单簿拆解为三个维度:交易指令(输入)、盘口结构(存储)、数据获取(维护)。三者共同构成了订单簿的完整生命周期。
个人建议:初学者可以先从快照入手,理解订单簿的静态结构。等熟悉了再研究增量更新。别一上来就搞实时流处理——我见过太多人把简单问题复杂化了。
好了,订单簿的基础就聊到这里。记住一句话:订单簿是市场的「底层语言」,读懂它,你就能听懂市场在说什么。
公众号:蓝海资料掘金营,微信deep3321