一、订单簿:撮合引擎的“心脏”
做量化交易这些年,我见过不少团队在撮合引擎上栽跟头。说白了,订单簿就是整个系统的核心数据结构。你想想看,所有买卖订单进来,都得在这里排队、匹配、成交。它要是设计不好,整个系统就别想跑得快。
我个人习惯把订单簿理解成一个“双向队列”。买盘(Bid)和卖盘(Ask)各占一边。每个价格点上,都挂着一堆订单。嗯,这里要注意:订单簿不是简单的数组,它需要支持高频的插入、删除、查询操作。
1.1 订单簿的数据结构选型
我在项目中遇到过最头疼的问题,就是订单簿的并发访问。多个交易员同时下单,数据竞争是家常便饭。所以选对数据结构,比什么都重要。
常见的实现方式有几种:
- 红黑树:C++里std::map的底层实现。插入、删除、查找都是O(log n)。适合价格档位多的情况。
- 跳表(Skip List):实现简单,并发友好。我在一个低延迟项目里用过,效果不错。
- 数组+链表:价格档位固定时,用数组索引价格,链表挂订单。速度极快,但灵活性差。
核心要点:订单簿必须支持“价格优先、时间优先”的快速匹配。数据结构的选择直接影响撮合速度。
给你看一段我常用的订单簿核心结构(Python伪代码):
class OrderBook:
def __init__(self):
self.bids = SortedDict() # 买盘,价格降序
self.asks = SortedDict() # 卖盘,价格升序
self.orders = {} # 订单ID到订单的映射
def add_order(self, order):
# 插入订单到对应价格队列
price_level = self._get_price_level(order.side, order.price)
price_level.append(order)
self.orders[order.id] = order
def match(self):
# 撮合逻辑:检查买卖盘能否成交
while self.bids and self.asks:
best_bid = self.bids.peekitem(0)
best_ask = self.asks.peekitem(0)
if best_bid[0] < best_ask[0]:
break # 无法成交
# 执行撮合...
二、价格-时间优先原则:公平的“排队规则”
这个原则,说白了就是“谁出价好谁先走,出价一样谁先来”。听起来简单,但实现起来有不少坑。
2.1 价格优先
买盘:出价高的优先。卖盘:出价低的优先。这个好理解,市场里谁都想占便宜嘛。
举个例子:
- 买盘A:出价100.5元,挂单时间09:30:01
- 买盘B:出价100.3元,挂单时间09:30:00
虽然B来得早,但A出价更高,所以A先成交。这就是价格优先。
2.2 时间优先
当价格相同时,先挂单的先成交。这个逻辑在代码里就是队列(FIFO)。
避坑指南:我曾经在时间戳精度上吃过亏。如果只精确到毫秒,同一毫秒内进来的订单,顺序可能乱掉。建议用单调递增的序列号,或者纳秒级时间戳。
实现时间优先,我一般用双向链表:
class PriceLevel:
def __init__(self):
self.head = None # 最早的订单
self.tail = None # 最新的订单
def append(self, order):
# 新订单加到尾部
if self.tail:
self.tail.next = order
order.prev = self.tail
self.tail = order
if not self.head:
self.head = order
def pop_head(self):
# 取出最早的订单(成交)
order = self.head
if order:
self.head = order.next
if self.head:
self.head.prev = None
return order
三、Maker与Taker模型:谁在“做市”,谁在“吃单”
这个模型是暗池交易的核心机制之一。我刚开始做的时候,也搞混过这两个角色。其实很简单:
- Maker(挂单者):挂出限价单,不立即成交,为市场提供流动性。
- Taker(吃单者):下市价单或立即成交的限价单,消耗流动性。
为什么要把它们分开?因为交易所通常会给Maker返佣,给Taker收更高的手续费。目的是鼓励大家挂单,增加市场深度。
关键点:在暗池里,Maker和Taker的身份是动态变化的。一个订单可能部分作为Maker,部分作为Taker。
判断逻辑是这样的:
def classify_order(order, order_book):
if order.type == 'market':
return 'taker' # 市价单一定是吃单
if order.side == 'buy':
# 买单价格 >= 最低卖价 → 吃单
if order_book.asks and order.price >= order_book.asks.min_price():
return 'taker'
else:
# 卖单价格 <= 最高买价 → 吃单
if order_book.bids and order.price <= order_book.bids.max_price():
return 'taker'
return 'maker' # 否则就是挂单
四、流动性提供与消耗机制
流动性,说白了就是市场里有没有足够的对手盘。你想想看,一个深度好的市场,大单进来价格波动很小。深度差的市场,一单就能把价格打穿。
4.1 流动性提供
Maker订单就是流动性的“供应商”。他们挂单在订单簿上,等着别人来成交。交易所为了吸引Maker,通常会:
- 降低手续费,甚至返佣
- 提供更快的撤单速度
- 在暗池里隐藏部分订单信息
4.2 流动性消耗
Taker订单是流动性的“消费者”。他们想要立即成交,所以愿意支付更高的手续费。在暗池里,Taker的订单会直接和订单簿上的Maker订单匹配。
注意:暗池和公开交易所不同。暗池里的大单可能被拆分成多个小单,分批成交。这是为了防止市场冲击。我见过一个项目,因为没处理好拆分逻辑,导致大单成交后价格剧烈波动,被套利机器人盯上了。
4.3 流动性指标
衡量流动性好坏,我常用这几个指标:
| 指标 | 说明 | 我的经验值 |
|---|---|---|
| 买卖价差(Spread) | 最低卖价 - 最高买价 | 小于0.1%算健康 |
| 订单簿深度 | 各价格档位的挂单量 | 至少5档有订单 |
| 成交时间 | 订单从挂单到成交的平均时间 | 暗池里通常几秒到几分钟 |
五、知识体系总览
说了这么多,我画了一张图帮你理清思路。这张图展示了本章的核心知识点和它们之间的关系:
这张图把四个核心概念串起来了。订单簿是基础,价格-时间优先是规则,Maker/Taker是角色,流动性是结果。它们互相影响,缺一不可。
我的建议:刚开始做撮合引擎,别急着上复杂功能。先把订单簿和匹配逻辑跑通。我见过太多团队一上来就想做智能路由、冰山订单,结果基础没打好,后面全是坑。
好了,这一章的内容就到这里。记住:订单簿是骨架,匹配规则是灵魂,Maker/Taker是血肉,流动性是血液。把这四个概念吃透了,后面的章节你会轻松很多。