目 录CONTENT

文章目录

撮合项目部署及学习

懿曲折扇情
2026-03-15 / 0 评论 / 0 点赞 / 2 阅读 / 3,020 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2026-03-15,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。
广告 广告

交易所撮合引擎完整指南

项目推荐:GitBitEx-Spot

GitHub: https://github.com/gitbitex/gitbitex-spot

这是一个完整的加密货币现货交易所实现,包含高性能撮合引擎,使用Go语言编写。

为什么选择这个项目?

完整性 - 包含完整的交易所核心功能(用户、账户、订单、撮合、清算)
高性能 - 基于内存的订单簿,每秒可处理数万笔订单
架构清晰 - 微服务架构,模块分离
生产级别 - 可直接用于生产环境
文档完善 - 有详细的技术文档


一、环境准备

1.1 系统要求

# macOS/Linux
- Go 1.18+
- MySQL 5.7+
- Redis 5.0+
- Kafka (可选)

1.2 安装依赖

安装Go

# macOS
brew install go

# 验证安装
go version
# 输出: go version go1.21.x darwin/amd64

安装MySQL

# macOS
brew install mysql

# 启动MySQL
brew services start mysql

# 设置root密码
mysql -u root
ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;

安装Redis

# macOS
brew install redis

# 启动Redis
brew services start redis

# 验证
redis-cli ping
# 输出: PONG

二、项目部署

2.1 克隆项目

cd ~/code
git clone https://github.com/gitbitex/gitbitex-spot.git
cd gitbitex-spot

2.2 查看项目结构

tree -L 2 -d
gitbitex-spot/
├── cmd/              # 应用入口
│   ├── api/         # API服务
│   ├── matching/    # 撮合引擎 ⭐核心
│   ├── pushing/     # WebSocket推送服务
│   └── worker/      # 后台任务
├── models/          # 数据模型
├── matching/        # 撮合引擎核心逻辑 ⭐⭐⭐
├── service/         # 业务逻辑
├── rest/            # REST API
├── conf/            # 配置文件
└── migrations/      # 数据库迁移

2.3 配置数据库

# 1. 创建数据库
mysql -u root -p
CREATE DATABASE gitbitex DEFAULT CHARACTER SET utf8mb4;
# 2. 导入数据库结构
mysql -u root -p gitbitex < migrations/20210101000000_init.sql

2.4 修改配置文件

编辑 conf/config.toml:

[server]
addr = ":8080"

[mysql]
dataSource = "root:password@tcp(127.0.0.1:3306)/gitbitex?parseTime=true&loc=Local"

[redis]
addr = "127.0.0.1:6379"
password = ""
db = 0

[log]
level = "debug"

2.5 运行服务

# 终端1: 启动撮合引擎
go run cmd/matching/main.go

# 终端2: 启动API服务
go run cmd/api/main.go

# 终端3: 启动WebSocket推送
go run cmd/pushing/main.go

2.6 验证运行

# 测试API
curl http://localhost:8080/api/products

# 预期输出
[
  {
    "id": "BTC-USDT",
    "base_currency": "BTC",
    "quote_currency": "USDT"
  }
]

三、撮合引擎核心逻辑

3.1 订单簿(Order Book)数据结构

// matching/order_book.go

type OrderBook struct {
    ProductId    string
    BuyOrders    *RedBlackTree  // 买单队列(价格从高到低)
    SellOrders   *RedBlackTree  // 卖单队列(价格从低到高)
    Sequence     int64          // 订单序号
    mutex        sync.RWMutex
}

type Order struct {
    Id        string
    Price     decimal.Decimal  // 价格
    Size      decimal.Decimal  // 数量
    Side      string          // "buy" 或 "sell"
    Type      string          // "limit" 或 "market"
    UserId    int64
    Time      time.Time
}

关键特性:

  • 使用红黑树维护买单和卖单队列
  • 买单按价格从高到低排序(最高价优先)
  • 卖单按价格从低到高排序(最低价优先)
  • 相同价格按时间优先(FIFO)

3.2 撮合流程

场景1: 限价买单(Limit Buy Order)

假设当前订单簿状态:
卖单(ASK):
  10.2 USDT -> 5 BTC
  10.1 USDT -> 10 BTC
  10.0 USDT -> 20 BTC

买单(BID):
  9.9 USDT -> 15 BTC
  9.8 USDT -> 10 BTC

用户下单:买入 15 BTC @ 10.15 USDT

// 撮合引擎执行步骤
func (book *OrderBook) MatchOrder(order *Order) []*Trade {
    trades := []*Trade{}

    // 1. 如果是买单,遍历卖单队列
    if order.Side == "buy" {
        // 从最低卖价开始匹配
        for sellOrder := book.SellOrders.Min(); sellOrder != nil; {
            // 2. 检查价格是否满足
            if order.Price < sellOrder.Price {
                break  // 买价低于卖价,停止撮合
            }

            // 3. 计算成交量
            tradeSize := Min(order.Size, sellOrder.Size)

            // 4. 生成成交记录
            trade := &Trade{
                ProductId: book.ProductId,
                Price:     sellOrder.Price,  // 以挂单价成交
                Size:      tradeSize,
                MakerOrderId: sellOrder.Id,
                TakerOrderId: order.Id,
                Time:      time.Now(),
            }
            trades = append(trades, trade)

            // 5. 减少订单量
            order.Size -= tradeSize
            sellOrder.Size -= tradeSize

            // 6. 移除已完全成交的订单
            if sellOrder.Size == 0 {
                book.SellOrders.Remove(sellOrder)
            }

            // 7. 如果买单已完全成交,退出
            if order.Size == 0 {
                break
            }

            sellOrder = book.SellOrders.Min()
        }
    }

    // 8. 如果买单还有剩余,加入订单簿
    if order.Size > 0 {
        book.BuyOrders.Insert(order)
    }

    return trades
}

执行结果:

成交1: 10 BTC @ 10.0 USDT (全部成交卖单1)
成交2: 5 BTC @ 10.1 USDT (部分成交卖单2)

用户买单状态: 已完全成交 15 BTC
  - 10 BTC @ 10.0 = 100 USDT
  - 5 BTC @ 10.1 = 50.5 USDT
  - 总计: 150.5 USDT

新订单簿状态:
卖单(ASK):
  10.2 USDT -> 5 BTC
  10.1 USDT -> 5 BTC (剩余)

买单(BID):
  9.9 USDT -> 15 BTC
  9.8 USDT -> 10 BTC

场景2: 市价单(Market Order)

// 市价买单:以最优价格尽快成交
func (book *OrderBook) MatchMarketOrder(order *Order) []*Trade {
    trades := []*Trade{}

    if order.Side == "buy" {
        // 不设价格限制,直接吃掉卖单
        for sellOrder := book.SellOrders.Min(); sellOrder != nil && order.Size > 0; {
            tradeSize := Min(order.Size, sellOrder.Size)

            trade := &Trade{
                Price: sellOrder.Price,  // 以卖单价格成交
                Size:  tradeSize,
            }
            trades = append(trades, trade)

            order.Size -= tradeSize
            sellOrder.Size -= tradeSize

            if sellOrder.Size == 0 {
                book.SellOrders.Remove(sellOrder)
            }

            sellOrder = book.SellOrders.Min()
        }
    }

    return trades
}

3.3 价格优先 + 时间优先规则

// 红黑树比较函数
func CompareOrders(a, b *Order) int {
    // 1. 先比较价格
    priceCompare := a.Price.Cmp(b.Price)
    if priceCompare != 0 {
        return priceCompare
    }

    // 2. 价格相同,比较时间(早下单的优先)
    if a.Time.Before(b.Time) {
        return -1
    } else if a.Time.After(b.Time) {
        return 1
    }

    return 0
}

四、核心代码解读

4.1 撮合引擎主循环

// cmd/matching/main.go

func main() {
    // 1. 初始化订单簿
    books := make(map[string]*OrderBook)
    books["BTC-USDT"] = NewOrderBook("BTC-USDT")

    // 2. 启动订单处理队列
    orderChannel := make(chan *Order, 10000)

    // 3. 撮合引擎主循环
    go func() {
        for order := range orderChannel {
            book := books[order.ProductId]

            // 执行撮合
            trades := book.MatchOrder(order)

            // 发布成交事件
            for _, trade := range trades {
                PublishTrade(trade)
            }

            // 更新订单簿快照
            PublishOrderBook(book)
        }
    }()

    // 4. 从数据库加载未完成订单
    LoadPendingOrders(books)

    // 5. 启动API服务接收新订单
    StartAPIServer(orderChannel)
}

4.2 订单处理流程图

用户下单
    ↓
API接收订单
    ↓
风控检查(余额、限额)
    ↓
写入数据库
    ↓
发送到撮合队列
    ↓
撮合引擎处理
    ↓
生成成交记录
    ↓
更新账户余额
    ↓
WebSocket推送给用户
    ↓
完成

4.3 关键文件说明

文件 功能
matching/engine.go 撮合引擎主逻辑 ⭐⭐⭐
matching/order_book.go 订单簿数据结构 ⭐⭐⭐
matching/red_black_tree.go 红黑树实现 ⭐⭐
service/order_service.go 订单业务逻辑 ⭐⭐
service/account_service.go 账户余额管理 ⭐⭐
rest/order_api.go 订单API接口 ⭐

五、测试撮合引擎

5.1 创建测试脚本

# test_matching.sh

#!/bin/bash

# 1. 创建两个测试账户
curl -X POST http://localhost:8080/api/users \
  -d '{"email":"buyer@test.com","password":"123456"}'

curl -X POST http://localhost:8080/api/users \
  -d '{"email":"seller@test.com","password":"123456"}'

# 2. 充值
# 买家充值 10000 USDT
curl -X POST http://localhost:8080/api/accounts/deposit \
  -H "Authorization: Bearer buyer_token" \
  -d '{"currency":"USDT","amount":"10000"}'

# 卖家充值 10 BTC
curl -X POST http://localhost:8080/api/accounts/deposit \
  -H "Authorization: Bearer seller_token" \
  -d '{"currency":"BTC","amount":"10"}'

# 3. 卖家挂卖单
curl -X POST http://localhost:8080/api/orders \
  -H "Authorization: Bearer seller_token" \
  -d '{
    "product_id":"BTC-USDT",
    "side":"sell",
    "type":"limit",
    "price":"50000",
    "size":"1"
  }'

# 4. 买家下买单(触发撮合)
curl -X POST http://localhost:8080/api/orders \
  -H "Authorization: Bearer buyer_token" \
  -d '{
    "product_id":"BTC-USDT",
    "side":"buy",
    "type":"limit",
    "price":"50000",
    "size":"1"
  }'

# 5. 查看成交记录
curl http://localhost:8080/api/trades?product_id=BTC-USDT

5.2 预期输出

{
  "trades": [
    {
      "id": "1",
      "product_id": "BTC-USDT",
      "price": "50000",
      "size": "1",
      "maker_order_id": "order_1",
      "taker_order_id": "order_2",
      "time": "2025-03-15T10:00:00Z"
    }
  ]
}

六、性能优化要点

6.1 内存订单簿

// 所有订单存储在内存中,避免数据库I/O
type InMemoryOrderBook struct {
    orders map[string]*Order  // O(1) 查找
    buyTree *RedBlackTree     // O(log n) 插入/删除
    sellTree *RedBlackTree    // O(log n) 插入/删除
}

优势:

  • 查询订单:O(1)
  • 插入订单:O(log n)
  • 撮合:O(log n)

6.2 无锁队列

// 使用channel实现无锁消息队列
orderQueue := make(chan *Order, 100000)

// 单线程处理,避免锁竞争
for order := range orderQueue {
    ProcessOrder(order)
}

6.3 批量更新数据库

// 内存中处理撮合,批量写入数据库
batchTrades := []*Trade{}

for {
    select {
    case trade := <-tradeChannel:
        batchTrades = append(batchTrades, trade)

        // 每100条或每1秒批量写入
        if len(batchTrades) >= 100 || timeout {
            db.BatchInsert(batchTrades)
            batchTrades = batchTrades[:0]
        }
    }
}

七、进阶扩展

7.1 增加订单类型

止损单(Stop-Loss)

type StopOrder struct {
    Order
    StopPrice decimal.Decimal  // 触发价格
    Triggered bool
}

// 价格达到触发条件时,转换为市价单
func CheckStopOrders(currentPrice decimal.Decimal) {
    for _, stopOrder := range stopOrders {
        if !stopOrder.Triggered {
            if (stopOrder.Side == "sell" && currentPrice <= stopOrder.StopPrice) ||
               (stopOrder.Side == "buy" && currentPrice >= stopOrder.StopPrice) {
                marketOrder := ConvertToMarketOrder(stopOrder)
                MatchOrder(marketOrder)
                stopOrder.Triggered = true
            }
        }
    }
}

冰山订单(Iceberg Order)

type IcebergOrder struct {
    Order
    VisibleSize decimal.Decimal  // 显示数量
    HiddenSize  decimal.Decimal  // 隐藏数量
}

// 只在订单簿中显示部分数量
func (book *OrderBook) AddIcebergOrder(order *IcebergOrder) {
    visibleOrder := Order{
        Size: order.VisibleSize,
        // ... 其他字段
    }
    book.AddOrder(&visibleOrder)

    // 成交后,自动补充下一批可见数量
}

7.2 分布式撮合引擎

// 按交易对分片
func GetMatchingEngine(productId string) *Engine {
    shard := Hash(productId) % NumShards
    return engines[shard]
}

// 每个分片独立运行
for i := 0; i < NumShards; i++ {
    go func(shardId int) {
        engine := engines[shardId]
        engine.Run()
    }(i)
}

八、常见问题

Q1: 为什么使用红黑树而不是普通队列?

A: 需要频繁插入/删除并保持有序:

  • 普通队列:插入O(1),但无法按价格排序
  • 排序数组:查询O(log n),但插入/删除O(n)
  • 红黑树:插入/删除/查询都是O(log n),最优解

Q2: 如何防止双花(Double Spend)?

A: 下单前冻结资金:

func PlaceOrder(userId int64, order *Order) error {
    // 1. 计算所需资金
    requiredAmount := order.Price * order.Size

    // 2. 冻结资金(原子操作)
    err := FreezeBalance(userId, order.QuoteCurrency, requiredAmount)
    if err != nil {
        return errors.New("余额不足")
    }

    // 3. 提交订单到撮合引擎
    SubmitOrder(order)

    return nil
}

Q3: 如何保证撮合顺序的公平性?

A: 使用序列号(Sequence Number):

type Order struct {
    Sequence int64  // 全局递增序列号
    // ...
}

// 撮合引擎按序列号顺序处理
func (engine *Engine) ProcessOrders() {
    for {
        order := GetNextOrderBySequence()
        Match(order)
    }
}

九、学习路线图

第1周:基础理解

  • 阅读本文档
  • 搭建本地环境
  • 运行项目
  • 测试下单和撮合

第2周:核心逻辑

  • 阅读 matching/order_book.go
  • 理解红黑树实现
  • 手动追踪撮合流程
  • 修改代码添加日志

第3周:实战练习

  • 实现止损单功能
  • 添加订单簿快照功能
  • 实现K线数据生成
  • 性能测试和优化

第4周:进阶扩展

  • 实现分布式撮合
  • 添加风控系统
  • 集成行情推送
  • 完成生产级部署

十、参考资源

官方文档

学习资料

社区


十一、总结

撮合引擎是交易所的核心,关键点:

  1. 数据结构 - 红黑树保证价格优先、时间优先
  2. 性能优化 - 内存订单簿 + 无锁队列
  3. 可靠性 - 事务保证 + 资金冻结
  4. 扩展性 - 分片 + 异步处理

通过学习这个项目,你将掌握:
✅ 订单簿原理
✅ 高性能数据结构应用
✅ Go语言微服务开发
✅ 交易所核心架构设计

开始动手实践吧!🚀

0

评论区