HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 交易所技术完整体系

    • 交易所技术完整体系
    • 交易所技术架构总览
    • 交易基础概念
    • 撮合引擎原理
    • 撮合引擎实现-内存撮合
    • 撮合引擎优化 - 延迟与吞吐
    • 撮合引擎高可用
    • 清算系统设计
    • 风控系统设计
    • 资金管理系统
    • 行情系统设计
    • 去中心化交易所(DEX)设计
    • 合约交易系统
    • 数据库设计与优化
    • 缓存与消息队列
    • 用户系统与KYC
    • 交易所API设计
    • 监控与告警系统
    • 安全防护与攻防
    • 高可用架构设计
    • 压力测试与性能优化
    • 项目实战-完整交易所实现

撮合引擎原理

撮合引擎是交易所的心脏,决定了交易的公平性和效率

📖 章节概述

本章深入讲解撮合引擎的核心原理,包括订单簿结构、撮合算法、价格优先时间优先规则等,为后续实现打下坚实基础。

学习目标

  • 理解撮合引擎的基本概念和作用
  • 掌握订单簿的数据结构设计
  • 理解价格优先、时间优先的撮合规则
  • 掌握限价单和市价单的撮合流程
  • 了解撮合引擎的性能要求

一、撮合引擎基础

1.1 什么是撮合引擎

撮合引擎(Matching Engine)是交易所的核心组件,负责:

功能说明
接收订单处理用户的买卖订单
维护订单簿管理所有未成交订单
执行撮合匹配买卖订单并成交
生成成交记录成交价格、数量、时间
更新账户扣减买方资金、增加卖方资金

1.2 撮合引擎的要求

要求说明目标值
公平性严格按价格优先、时间优先规则100%
高吞吐支持高并发订单处理> 100万QPS
低延迟订单到成交的时间< 1ms
高可用系统稳定性99.99%
准确性撮合结果零错误100%

1.3 撮合模式

模式1:连续撮合(Continuous Matching)

特点:
- 订单实时撮合
- 适用于:股票、数字货币现货交易
- 优点:即时成交
- 缺点:可能产生价格波动

模式2:集合竞价(Call Auction)

特点:
- 收集一段时间的订单,统一撮合
- 适用于:开盘、收盘
- 优点:减少价格波动,发现合理价格
- 缺点:成交延迟高

本手册重点介绍连续撮合模式


二、订单簿(Order Book)

2.1 订单簿的概念

订单簿是所有未成交订单的集合,分为买单簿(Bids)和卖单簿(Asks)。

卖单簿(Asks) - 按价格从低到高排列
┌────────────────────────────────┐
│ 价格     数量     时间          │
├────────────────────────────────┤
│ 101.5    10      14:00:03      │ ← 卖1(最低卖价)
│ 101.6    20      14:00:05      │ ← 卖2
│ 101.8    15      14:00:01      │ ← 卖3
└────────────────────────────────┘

                现价:100.0

┌────────────────────────────────┐
│ 价格     数量     时间          │
├────────────────────────────────┤
│ 100.0    30      14:00:00      │ ← 买1(最高买价)
│ 99.9     25      14:00:02      │ ← 买2
│ 99.8     40      14:00:04      │ ← 买3
└────────────────────────────────┘
买单簿(Bids) - 按价格从高到低排列

2.2 关键概念

概念说明
买1(Best Bid)订单簿中最高的买入价格
卖1(Best Ask)订单簿中最低的卖出价格
价差(Spread)卖1 - 买1,反映市场流动性
深度(Depth)每个价格档位的订单数量
盘口(Book)买卖双方的前N档报价

示例:

卖1: 101.5 (10张)
买1: 100.0 (30张)
价差: 101.5 - 100.0 = 1.5

2.3 订单簿的数据结构

需求分析

操作频率复杂度要求
插入订单极高O(log n)
删除订单(撤单)高O(log n)
查找最优价格极高O(1)
遍历同价格订单高O(k),k是该价格订单数

数据结构选择

type OrderBook struct {
    Symbol string
    Bids   *PriceLevel // 买单簿(红黑树/跳表)
    Asks   *PriceLevel // 卖单簿(红黑树/跳表)
}

// 价格档位
type PriceLevel struct {
    Prices map[decimal.Decimal]*PriceQueue // 价格 -> 订单队列
}

// 同价格订单队列(按时间排序)
type PriceQueue struct {
    Price  decimal.Decimal
    Orders []*Order // 时间优先队列
    Volume decimal.Decimal // 总量
}

// 订单
type Order struct {
    ID        string
    UserID    string
    Symbol    string
    Side      Side // BUY/SELL
    Type      OrderType // LIMIT/MARKET
    Price     decimal.Decimal
    Quantity  decimal.Decimal
    Remaining decimal.Decimal // 剩余数量
    Timestamp time.Time
    Status    OrderStatus // PENDING/PARTIAL/FILLED/CANCELED
}

为什么用红黑树/跳表?

数据结构插入删除查找最值优点缺点
数组O(n)O(n)O(1)简单插入删除慢
哈希表O(1)O(1)O(n)快无序,找最值慢
红黑树O(log n)O(log n)O(log n)平衡实现复杂
跳表O(log n)O(log n)O(log n)简单空间消耗大

实践选择:

  • Go语言:自己实现跳表(标准库无红黑树)
  • Java:TreeMap(红黑树)
  • C++:std::map(红黑树)

⚖️ 三、撮合规则

3.1 价格优先原则

买单:价格高的优先成交 卖单:价格低的优先成交

买单簿:
100.0 (最优) ← 优先成交
99.9
99.8

卖单簿:
101.5 (最优) ← 优先成交
101.6
101.8

3.2 时间优先原则

同价格:先到的订单优先成交

买单簿(价格100.0):
订单A: 14:00:00 ← 优先成交
订单B: 14:00:05
订单C: 14:00:10

3.3 撮合条件

成交条件:买价 >= 卖价

买单: 价格100.0, 数量10
卖单: 价格99.5,  数量5

成交价格: 99.5 (卖单价格,因为卖单在前)
成交数量: 5
买单剩余: 5 (继续留在订单簿)

🔄 四、撮合流程

4.1 限价单撮合

场景1:买单 vs 卖单簿

订单簿状态:
卖单:
  101.5 -> [订单1: 10张]
  101.6 -> [订单2: 20张]

买1: 100.0

新买单:
  价格: 102.0, 数量: 15张

撮合流程:
1. 检查买价(102.0) >= 卖1(101.5)  可成交
2. 取出卖单簿最优价格(101.5)的订单1(10张)
3. 成交10张 @ 101.5 (价格取卖单价格)
4. 买单剩余5张,继续撮合
5. 检查买价(102.0) >= 卖1(101.6)  可成交
6. 取出卖单簿次优价格(101.6)的订单2(20张)
7. 成交5张 @ 101.6
8. 订单2剩余15张,留在卖单簿
9. 买单完全成交,结束

结果:
- 成交1: 10张 @ 101.5
- 成交2: 5张 @ 101.6
- 卖单簿更新:
    101.6 -> [订单2: 15张]

场景2:买单部分成交后挂单

订单簿状态:
卖单:
  101.5 -> [订单1: 5张]

新买单:
  价格: 102.0, 数量: 10张

撮合流程:
1. 成交5张 @ 101.5
2. 买单剩余5张
3. 检查买价(102.0) >= 卖1(无)  无法继续成交
4. 买单剩余5张挂到买单簿
   100.0 -> [新买单: 5张]

结果:
- 成交: 5张 @ 101.5
- 挂单: 5张 @ 102.0

4.2 市价单撮合

市价单以最优价格立即成交,不考虑价格,直到成交完成或订单簿耗尽。

订单簿状态:
卖单:
  101.5 -> [订单1: 10张]
  101.6 -> [订单2: 20张]
  101.8 -> [订单3: 15张]

新市价买单:
  数量: 35张

撮合流程:
1. 取卖1(101.5), 成交10张 @ 101.5
2. 取卖1(101.6), 成交20张 @ 101.6
3. 取卖1(101.8), 成交5张 @ 101.8
4. 市价单完全成交

结果:
- 成交1: 10张 @ 101.5
- 成交2: 20张 @ 101.6
- 成交3: 5张 @ 101.8
- 平均成交价: (10×101.5 + 20×101.6 + 5×101.8) / 35 = 101.59

市价单风险:

  • 如果订单簿深度不足,可能以很差的价格成交
  • 因此实盘通常限制市价单数量或设置价格保护

五、撮合算法伪代码

5.1 限价买单撮合

def matchLimitBuyOrder(buyOrder):
    """
    撮合限价买单
    """
    while buyOrder.remaining > 0:
        # 1. 获取卖单簿最优价格
        bestAsk = askBook.getBestPrice()
        if bestAsk is None or buyOrder.price < bestAsk.price:
            # 无法成交,挂到买单簿
            bidBook.addOrder(buyOrder)
            break

        # 2. 获取该价格的订单队列(时间优先)
        askQueue = askBook.getQueue(bestAsk.price)

        # 3. 逐个撮合
        while buyOrder.remaining > 0 and len(askQueue) > 0:
            sellOrder = askQueue[0] # 时间最早的卖单

            # 计算成交数量
            matchQty = min(buyOrder.remaining, sellOrder.remaining)

            # 生成成交
            trade = Trade(
                price=sellOrder.price, # 价格取卖单价格
                quantity=matchQty,
                buyOrderID=buyOrder.id,
                sellOrderID=sellOrder.id,
                timestamp=now()
            )
            trades.append(trade)

            # 更新订单剩余数量
            buyOrder.remaining -= matchQty
            sellOrder.remaining -= matchQty

            # 如果卖单完全成交,从队列移除
            if sellOrder.remaining == 0:
                askQueue.pop(0)
                sellOrder.status = FILLED

        # 如果该价格的卖单耗尽,移除该价格档位
        if len(askQueue) == 0:
            askBook.removePrice(bestAsk.price)

    # 更新买单状态
    if buyOrder.remaining == 0:
        buyOrder.status = FILLED
    elif buyOrder.remaining < buyOrder.quantity:
        buyOrder.status = PARTIAL

5.2 市价买单撮合

def matchMarketBuyOrder(buyOrder):
    """
    撮合市价买单(以任意价格成交)
    """
    while buyOrder.remaining > 0:
        # 1. 获取卖单簿最优价格
        bestAsk = askBook.getBestPrice()
        if bestAsk is None:
            # 订单簿耗尽,市价单无法完全成交
            buyOrder.status = PARTIAL
            break

        # 2. 获取该价格的订单队列
        askQueue = askBook.getQueue(bestAsk.price)

        # 3. 逐个撮合(同限价单)
        while buyOrder.remaining > 0 and len(askQueue) > 0:
            sellOrder = askQueue[0]

            matchQty = min(buyOrder.remaining, sellOrder.remaining)

            trade = Trade(
                price=sellOrder.price,
                quantity=matchQty,
                buyOrderID=buyOrder.id,
                sellOrderID=sellOrder.id,
                timestamp=now()
            )
            trades.append(trade)

            buyOrder.remaining -= matchQty
            sellOrder.remaining -= matchQty

            if sellOrder.remaining == 0:
                askQueue.pop(0)
                sellOrder.status = FILLED

        if len(askQueue) == 0:
            askBook.removePrice(bestAsk.price)

    # 市价单必须完全成交或部分成交,不挂单
    if buyOrder.remaining == 0:
        buyOrder.status = FILLED

六、撮合示例

6.1 完整撮合流程示例

初始状态:
订单簿为空

步骤1:用户A挂限价卖单
  卖单1: 价格101.5, 数量10

  订单簿:
  卖单: 101.5 -> [卖单1: 10]

步骤2:用户B挂限价卖单
  卖单2: 价格101.6, 数量20

  订单簿:
  卖单: 101.5 -> [卖单1: 10]
        101.6 -> [卖单2: 20]

步骤3:用户C挂限价买单
  买单1: 价格100.0, 数量30

  检查: 100.0 < 101.5,无法成交,挂单

  订单簿:
  卖单: 101.5 -> [卖单1: 10]
        101.6 -> [卖单2: 20]
  买单: 100.0 -> [买单1: 30]

步骤4:用户D下限价买单
  买单2: 价格102.0, 数量15

  检查: 102.0 >= 101.5,可成交

  成交1: 10 @ 101.5 (买单2 vs 卖单1)
  买单2剩余5

  检查: 102.0 >= 101.6,可成交

  成交2: 5 @ 101.6 (买单2 vs 卖单2)
  买单2完全成交
  卖单2剩余15

  订单簿:
  卖单: 101.6 -> [卖单2: 15]
  买单: 100.0 -> [买单1: 30]

步骤5:用户E下市价卖单
  卖单3: 数量20(市价)

  检查: 买1 = 100.0,取买单簿

  成交3: 20 @ 100.0 (卖单3 vs 买单1)
  买单1剩余10
  卖单3完全成交

  订单簿:
  卖单: 101.6 -> [卖单2: 15]
  买单: 100.0 -> [买单1: 10]

七、性能优化

7.1 内存优化

1. 对象池:复用Order、Trade对象,减少GC
2. 预分配:订单簿预分配容量
3. 批量处理:批量生成成交记录

7.2 并发优化

1. 单线程撮合:避免锁竞争
2. 无锁队列:订单输入使用无锁队列
3. LMAX Disruptor:环形缓冲区

7.3 算法优化

1. 跳表:O(log n)查找,简单高效
2. 价格缓存:缓存最优买卖价
3. 增量更新:只推送变化的深度数据

📖 八、总结

核心要点

  1. 订单簿:用红黑树/跳表维护价格优先队列
  2. 撮合规则:价格优先 > 时间优先
  3. 限价单:价格不满足则挂单
  4. 市价单:立即成交,不挂单
  5. 性能:单线程 + 无锁 + 高效数据结构

关键数据结构

OrderBook
│
├── Bids (跳表/红黑树)
│   ├── 价格100.0 -> [订单1, 订单2, ...]
│   ├── 价格99.9  -> [订单3, ...]
│   └── ...
│
└── Asks (跳表/红黑树)
    ├── 价格101.5 -> [订单4, 订单5, ...]
    ├── 价格101.6 -> [订单6, ...]
    └── ...

Prev
交易基础概念
Next
撮合引擎实现-内存撮合