交易所撮合引擎完整指南
项目推荐: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周:进阶扩展
- 实现分布式撮合
- 添加风控系统
- 集成行情推送
- 完成生产级部署
十、参考资源
官方文档
- GitHub项目: https://github.com/gitbitex/gitbitex-spot
- Go语言官方: https://golang.org/doc/
学习资料
- 《交易系统设计》: 深入理解订单簿和撮合原理
- 红黑树算法: https://en.wikipedia.org/wiki/Red–black_tree
- 限价订单簿: https://en.wikipedia.org/wiki/Order_book
社区
- Go中文社区: https://gocn.vip/
- 量化交易论坛: https://www.ricequant.com/
十一、总结
撮合引擎是交易所的核心,关键点:
- 数据结构 - 红黑树保证价格优先、时间优先
- 性能优化 - 内存订单簿 + 无锁队列
- 可靠性 - 事务保证 + 资金冻结
- 扩展性 - 分片 + 异步处理
通过学习这个项目,你将掌握:
✅ 订单簿原理
✅ 高性能数据结构应用
✅ Go语言微服务开发
✅ 交易所核心架构设计
开始动手实践吧!🚀
评论区