一、订单簿:撮合引擎的“心脏”

做量化交易这些年,我见过不少团队在撮合引擎上栽跟头。说白了,订单簿就是整个系统的核心数据结构。你想想看,所有买卖订单进来,都得在这里排队、匹配、成交。它要是设计不好,整个系统就别想跑得快。

我个人习惯把订单簿理解成一个“双向队列”。买盘(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模型 流动性机制 订单簿实现 • 红黑树 / 跳表 • 价格档位管理 • 订单队列(FIFO) • 并发安全控制 • 快速查询/删除 匹配规则 • 价格优先:高价买/低价卖 • 时间优先:先到先得 • 单调递增序列号 • 纳秒级时间戳 • 公平性保障 角色区分 • Maker:挂单提供流动性 • Taker:吃单消耗流动性 • 动态身份判断 • 手续费差异化 • 部分成交场景 四个核心概念共同构成暗池撮合引擎的基础框架

这张图把四个核心概念串起来了。订单簿是基础,价格-时间优先是规则,Maker/Taker是角色,流动性是结果。它们互相影响,缺一不可。

我的建议:刚开始做撮合引擎,别急着上复杂功能。先把订单簿和匹配逻辑跑通。我见过太多团队一上来就想做智能路由、冰山订单,结果基础没打好,后面全是坑。

好了,这一章的内容就到这里。记住:订单簿是骨架,匹配规则是灵魂,Maker/Taker是血肉,流动性是血液。把这四个概念吃透了,后面的章节你会轻松很多。

专注资料整理