订单簿深度解析:限价单与市价单、买卖盘口、构建与更新机制
做市的核心,说白了就是跟订单簿打交道。你每天盯着屏幕上跳动的数字,其实就是在看订单簿的实时状态。我刚开始做量化的时候,觉得订单簿不就是一堆买单卖单嘛,有什么好研究的?后来在实盘中被狠狠教育了几次,才明白这东西的深度远超想象。
今天我们就来彻底拆解订单簿。从最基础的概念讲起,到背后的构建逻辑,再到更新机制。嗯,这部分内容虽然基础,但如果你理解透了,做市策略的很多坑就能提前避开。
一、限价单与市价单:两种最基本的武器
先问个问题:你下单的时候,到底是在跟谁做对手盘?
答案是:要么你挂单等别人来吃,要么你直接吃掉别人的挂单。这就是限价单和市价单的本质区别。
限价单(Limit Order):你指定一个价格,只有市场价到了这个位置,你的单子才会成交。说白了,你是「守株待兔」的那一方。
- 优点:你能控制成交价格,不会滑点
- 缺点:可能永远成交不了,或者成交很慢
- 做市商最爱:我们就是靠挂限价单赚价差的
市价单(Market Order):你不管价格,直接按当前最优价成交。你是「主动出击」的那一方。
- 优点:几乎立即成交,流动性好
- 缺点:可能产生滑点,尤其在流动性差的时候
- 做市商最怕:市价单就是来吃我们挂单的
核心认知:做市商本质上是在「提供限价单」,而交易者(包括其他做市商)用「市价单」来消耗这些流动性。你赚的钱,就是限价单和市价单之间的价差。
我在项目中遇到过一种情况:某个小币种流动性极差,我挂的限价单深度只有几百美金。结果一个市价单砸下来,直接把我三层挂单全吃了,滑点大得离谱。从那以后,我对流动性差的品种就格外小心。
三、买卖盘口:Bid 和 Ask 的博弈
订单簿的左右两侧,就是买卖双方的战场。
- Bid(买盘):左侧,显示所有想买入的人挂的价格和数量。最高买价叫 Best Bid。
- Ask(卖盘):右侧,显示所有想卖出的人挂的价格和数量。最低卖价叫 Best Ask。
这两个价格之间的差距,就是 Spread(价差)。做市商赚的就是这个。
| 概念 | 含义 | 做市商视角 |
|---|---|---|
| Best Bid | 当前最高买入价 | 我们挂卖单时参考的下限 |
| Best Ask | 当前最低卖出价 | 我们挂买单时参考的上限 |
| Spread | Ask - Bid | 我们的利润空间 |
| Depth | 各价位的挂单总量 | 判断市场承接力 |
你想想看,如果某个币的 Best Bid 是 100,Best Ask 是 100.1,Spread 只有 0.1%。这意味着市场很活跃,流动性好。但如果 Spread 是 5%,那说明这个品种要么没人玩,要么波动极大。
个人经验:我一般会监控 Spread 的实时变化。如果某个品种的 Spread 突然从 0.1% 扩大到 0.5%,我会立刻暂停做市。这通常意味着有大户在撤单,或者市场情绪突变。我曾经因为没注意这个细节,在 ETH 上亏了一笔不小的钱。
三、订单簿的构建:从零到一
订单簿不是天然存在的,它是交易所根据所有挂单实时计算出来的。我们作为做市商,需要理解它的构建逻辑,才能更好地预测市场行为。
构建过程其实很简单:
- 交易所收到每一笔限价单指令(包括价格、数量、方向)
- 按价格优先、时间优先的原则排序
- 买盘从高到低排列,卖盘从低到高排列
- 相同价格的单子,按时间先后排队
举个例子,假设当前订单簿是这样的:
卖盘(Ask):
价格 100.5 数量 200
价格 100.4 数量 150
价格 100.3 数量 100 ← Best Ask
买盘(Bid):
价格 100.2 数量 120 ← Best Bid
价格 100.1 数量 80
价格 100.0 数量 300
这时候 Spread = 100.3 - 100.2 = 0.1。如果你想市价买入,你会先吃掉 100.3 的 100 个,然后继续吃 100.4 的 150 个,以此类推。
关键点:订单簿的深度决定了你的市价单会造成多大的滑点。深度越厚,滑点越小。做市商的任务之一,就是提供足够的深度,让市场更平滑。
四、更新机制:每一毫秒都在变化
订单簿不是静态的。每一秒都有新的限价单进来,也有旧的单子被成交或撤销。交易所会实时推送这些变化。
常见的更新事件有四种:
- 新增订单:有人挂了新的限价单,插入到对应位置
- 撤销订单:有人撤回了未成交的限价单
- 成交:限价单被市价单吃掉,或者两个限价单相遇
- 修改订单:有人改了价格或数量(通常先撤再挂)
我习惯在代码里维护一个本地的订单簿副本。每次收到交易所的增量更新(Incremental Update),就同步修改本地数据。这样做的好处是:
- 不用每次都请求全量快照,节省带宽
- 可以实时计算各种指标,比如订单簿不平衡度
- 能更快地发现异常,比如价格突变
避坑指南:我曾经在维护本地订单簿时,因为没处理好时间戳的先后顺序,导致本地数据和交易所不一致。结果我的报价策略基于错误的数据运行了整整 3 秒,亏了大概 2000 美金。从那以后,我每次收到更新都会先校验序列号,确保没有丢包或乱序。
五、用 SVG 画一张订单簿结构图
光说可能不够直观,我画了一张图,帮你把整个结构串起来。
这张图里,左侧是卖盘,价格从低到高排列。右侧是买盘,价格从高到低排列。中间的 Spread 就是我们的利润空间。你注意看 Best Ask 和 Best Bid 的位置,它们决定了当前市场的最优买卖价格。
六、实战中的订单簿监控
理论讲完了,说说实际怎么做。我一般会在策略里维护一个这样的数据结构:
class OrderBook:
def __init__(self):
self.bids = {} # 价格 -> 数量
self.asks = {} # 价格 -> 数量
self.best_bid = None
self.best_ask = None
self.sequence = 0 # 用于校验更新顺序
def update(self, side, price, size, seq):
if seq <= self.sequence:
return # 丢弃过期更新
self.sequence = seq
if side == 'bid':
if size == 0:
self.bids.pop(price, None)
else:
self.bids[price] = size
self.best_bid = max(self.bids.keys()) if self.bids else None
else:
if size == 0:
self.asks.pop(price, None)
else:
self.asks[price] = size
self.best_ask = min(self.asks.keys()) if self.asks else None
这段代码虽然简单,但核心逻辑都在了。每次收到更新,先检查序列号,防止乱序。然后根据 side 更新对应的字典。size 为 0 表示撤销订单。
小技巧:我习惯用有序字典(比如 Python 的 SortedDict)来维护 bids 和 asks,这样取 best bid/ask 的时间复杂度是 O(1),而不是 O(n)。在高频场景下,这点性能差异可能就是盈亏的分水岭。
好了,订单簿的核心内容就这些。理解了这个基础,后面讲报价生成逻辑的时候,你就能明白为什么我们要盯着订单簿的每一个变化了。