3. 订单簿的构建:限价单、市价单、订单簿数据结构
好,咱们进入正题。订单簿这东西,说白了就是交易所的「记账本」。它记录着所有交易者想买、想卖的意愿。我个人习惯把订单簿看作市场的骨架——价格发现、流动性、盘口博弈,全在这上面展开。
你想想看,如果没有订单簿,集合竞价就变成了瞎猜。谁愿意在什么价格买多少,谁愿意在什么价格卖多少,这些信息必须有个地方存着、算着。这个地方,就是订单簿。
3.1 限价单:市场的「钉子」
限价单,英文叫 Limit Order。它的核心就一句话:指定价格,等待成交。
比如我说:「我出 10.00 元买 100 股茅台,低于这个价我不买。」这就是一个买入限价单。反过来,我说:「我 10.50 元卖 200 股,低于这个价我不卖。」这就是卖出限价单。
我在项目中遇到过一个问题:有人觉得限价单就是「挂单等成交」,其实没那么简单。限价单有两个关键属性:
- 价格优先:买价高的先成交,卖价低的先成交。这是铁律。
- 时间优先:同样价格,先挂单的先成交。这是公平。
嗯,这里要注意:限价单不一定能成交。如果你的买价挂得太低,或者卖价挂得太高,可能一天下来纹丝不动。这就是所谓的「流动性提供者」——你挂单,别人来吃。
3.2 市价单:市场的「推土机」
市价单,Market Order。它的逻辑更简单:不管价格,立即成交。
你说:「我现在就要买 1000 股,多少钱都行。」系统就会从最优卖价开始,一路扫过去,直到买够 1000 股为止。
市价单的特点是:
- 成交确定性高:只要盘口有足够的对手盘,几乎瞬间成交。
- 价格不确定性高:如果流动性不足,你可能吃到很差的价位。
说白了,市价单是「流动性消耗者」。你冲进去,把别人的挂单吃掉。我见过不少新手喜欢用市价单,觉得「快」。但遇到大单或者流动性枯竭时,市价单会让你吃大亏。
3.3 订单簿的数据结构
好,现在咱们聊聊怎么在代码里实现订单簿。这玩意儿看着简单,但坑不少。
一个标准的订单簿,通常包含两个「堆」:
- 买盘(Bid):按价格从高到低排列。最高价在最上面。
- 卖盘(Ask):按价格从低到高排列。最低价在最上面。
每个价格档位上,会记录该价格的总委托量。比如:
| 方向 | 价格 | 数量 | 订单数 |
|---|---|---|---|
| 卖 | 10.05 | 500 | 3 |
| 卖 | 10.04 | 200 | 1 |
| 卖 | 10.03 | 800 | 5 |
| 买 | 10.02 | 600 | 4 |
| 买 | 10.01 | 300 | 2 |
| 买 | 10.00 | 1000 | 7 |
这个表格就是订单簿的「快照」。但实际运行时,我们需要一个能动态增删改查的数据结构。
我个人习惯用 红黑树 或 跳表 来实现价格维度的排序。为什么?因为订单簿的核心操作是:
- 插入一个新订单(O(log n))
- 删除一个已成交或撤单的订单(O(log n))
- 查询最优买卖价(O(1))
用数组?不行。插入和删除太慢。用哈希表?也不行。没法按价格排序。红黑树和跳表是工业界的主流选择。
下面是一个简化版的订单簿数据结构,用 Python 示意:
class OrderBook:
def __init__(self):
self.bids = {} # 价格 -> 总量
self.asks = {} # 价格 -> 总量
self.orders = {} # 订单ID -> 订单详情
def add_order(self, order):
# 限价单逻辑
if order.type == 'limit':
if order.side == 'buy':
self.bids[order.price] = self.bids.get(order.price, 0) + order.qty
else:
self.asks[order.price] = self.asks.get(order.price, 0) + order.qty
self.orders[order.id] = order
def remove_order(self, order_id):
order = self.orders.pop(order_id, None)
if order:
if order.side == 'buy':
self.bids[order.price] -= order.qty
if self.bids[order.price] <= 0:
del self.bids[order.price]
else:
self.asks[order.price] -= order.qty
if self.asks[order.price] <= 0:
del self.asks[order.price]
def best_bid(self):
return max(self.bids.keys()) if self.bids else None
def best_ask(self):
return min(self.asks.keys()) if self.asks else None
3.4 订单簿的核心逻辑:撮合
有了订单簿,下一步就是撮合。撮合的逻辑其实就一句话:当买价 ≥ 卖价时,成交。
具体步骤:
- 新订单进来,判断是买还是卖。
- 如果是买单,看最优卖价(最低的 ask)。如果买单价格 ≥ 最优卖价,成交。
- 成交后,更新订单簿。如果买单还有剩余,继续与下一个卖价撮合。
- 直到买单价格 < 最优卖价,或者买单完全成交,停止。
卖单的逻辑对称,反过来就行。
这里有个细节:价格优先 + 时间优先。同一价格档位上,先挂的单先成交。所以订单簿里每个价格档位其实是一个队列(FIFO)。
我在做高频交易系统时,遇到过一个问题:如果两个订单同时到达,谁先谁后?这取决于交易所的撮合引擎。有的交易所按「接收时间」排序,有的按「系统时间戳」。嗯,这里水很深,咱们后面章节再细聊。
3.5 知识体系总览
下面我用一张图来总结本章的核心内容。这张图展示了订单簿的构建、订单类型、以及撮合逻辑之间的关系。
这张图把咱们今天讲的内容串起来了。限价单和市价单是输入,订单簿是存储结构,撮合逻辑是处理引擎,最终输出成交记录和订单簿快照。集合竞价的价格发现,就是在这个框架下完成的。
好了,这一章就到这里。订单簿的构建是后续所有内容的基础。下一章咱们会深入集合竞价的具体流程,看看订单簿在开盘前是怎么「预热」的。