现货交易撮合逻辑怎么做?一篇讲透核心流程
原创声明:本文为原创首发内容。
适合人群:后端工程师、交易系统开发者、微服务架构实践者
关键词:现货交易、撮合引擎、订单簿、价格优先、时间优先、成交回报
阅读收益:看完可独立设计一版可落地的现货撮合主流程
前言
“撮合”本质上就是一句话:
把买单和卖单,按照既定规则配对成交。
但工程实现里,真正难的是:
- 高并发下的顺序一致性
- 成交与资产变更的一致性
- 部分成交、撤单、重复请求等边界处理
这篇文章从第一性原理拆解:规则 -> 数据结构 -> 流程 -> 一致性 -> 工程落地。
1. 先定交易规则(不先定规则,代码一定乱)
现货交易最常见规则是:
- 价格优先
- 时间优先(同价位按先来后到)
- 买卖方向约束
解释:
- 买单想“买便宜”,优先与最低卖价撮合
- 卖单想“卖贵点”,优先与最高买价撮合
1.1 可成交条件
- 买单价格
>=最优卖价,可成交 - 卖单价格
<=最优买价,可成交
1.2 常见订单类型
- 限价单:指定价格上限/下限
- 市价单:按当前盘口最优价格立即成交(需风控保护)
2. 核心数据模型(最小闭环)
建议至少包含这 5 类对象:
Order(订单)OrderBook(订单簿)Trade(成交)Position/Balance(资产与冻结)Event(事件流:下单、成交、撤单)
2.1 订单关键字段
orderIduserIdsymbol(如 BTC/USDT)side(BUY/SELL)type(LIMIT/MARKET)pricequantityfilledQuantitystatus(NEW/PARTIAL/FILLED/CANCELED)createTime
2.2 订单簿结构建议
- 买盘:按价格从高到低
- 卖盘:按价格从低到高
- 每个价位下是 FIFO 队列(时间优先)
Java 常见实现:
TreeMap<Price, Deque<Order>>(买盘可用逆序比较器)
3. 撮合主流程(下单到成交)
3.1 限价买单示例
假设用户下限价买单:买 2 BTC @ 100
当前卖盘:
- 卖1:
99,数量0.8 - 卖2:
100,数量0.7 - 卖3:
101,数量1.5
撮合过程:
- 先吃卖1(99)0.8,剩余 1.2
- 再吃卖2(100)0.7,剩余 0.5
- 卖3价格 101 > 买价 100,不可成交
- 剩余 0.5 挂入买盘(价格 100)
结果:
- 两笔成交回报
- 买单状态
PARTIAL - 剩余数量入簿等待后续撮合
4. 撮合伪代码(可直接映射 Java 实现)
public MatchResult submitOrder(Order taker) {
// 1. 基础校验:交易对、最小下单量、价格精度、风控阈值
validateOrder(taker);
// 2. 资产预检查与冻结(买冻结计价币,卖冻结基础币)
reserveBalance(taker);
// 3. 进入撮合循环
while (taker.hasRemaining()) {
Order maker = orderBook.peekBestOpposite(taker.getSide());
if (maker == null) {
break;
}
// 4. 价格不可成交则退出
if (!priceCrossed(taker, maker)) {
break;
}
// 5. 计算本次成交量
BigDecimal fillQty = taker.remaining().min(maker.remaining());
// 6. 成交价:一般取挂单方(maker)价格
BigDecimal fillPrice = maker.getPrice();
// 7. 生成成交记录并更新双方订单
Trade trade = createTrade(taker, maker, fillQty, fillPrice);
applyFill(taker, maker, trade);
// 8. 若 maker 完成,则从订单簿移除
if (maker.isFilled()) {
orderBook.removeTopOpposite(taker.getSide());
}
// 9. 推送成交事件(异步)
publishTradeEvent(trade);
}
// 10. 若 taker 仍有剩余且是限价单,挂单入簿
if (taker.hasRemaining() && taker.isLimit()) {
orderBook.add(taker);
}
// 11. 结算与解冻差额
settleAndRelease(taker);
// 12. 推送订单状态更新
publishOrderEvent(taker);
return buildResult(taker);
}
5. 资金与一致性(交易系统最容易出事故的地方)
5.1 资产处理建议
- 下单前先冻结
- 每次成交即时扣减冻结并增加对应资产
- 订单结束后解冻未成交部分
5.2 成交与资产更新要么都成功,要么都失败
至少保证以下原子性:
- 成交记录写入
- 订单状态更新
- 账户余额更新
常见做法:
- 撮合核心内存化串行执行
- 通过事件日志/消息队列做异步落库
- 采用幂等键防重复消费
6. 高并发架构建议(实战版)
6.1 核心原则:一个交易对一个串行撮合线程
为什么?
- 同一交易对需要强顺序
- 串行可天然避免锁竞争与乱序成交
可扩展方式:
- 按
symbol分片(BTC/USDT 一条队列,ETH/USDT 一条队列) - 每个分片单线程事件循环
- 多交易对并行扩容
6.2 典型链路
- 网关接单
- 风控预校验
- 投递到撮合分片队列
- 撮合引擎串行撮合
- 生成成交事件
- 账户、订单、K线等消费者异步处理
7. 必须处理的边界场景
- 部分成交:订单状态应为
PARTIAL - 撤单并发:撤单与成交竞争时,需按序判定最终状态
- 重复下单:客户端请求幂等(
clientOrderId) - 市价单滑点:设置保护价或最大成交金额
- 精度问题:数量和价格统一精度与舍入策略
- 宕机恢复:从快照 + 增量日志恢复订单簿
8. 手把手拆分模块(微服务视角)
可以按下面拆:
trade-api:接单、查单、撤单接口risk-service:风控校验(余额、限额、黑白名单)match-engine:撮合核心(内存簿 + 串行事件循环)account-service:冻结、解冻、资产变更market-service:行情、深度、K线audit-service:审计与对账
9. 测试清单(上线前至少覆盖)
- 正常撮合(全成交、部分成交)
- 并发下单顺序一致性
- 撤单竞态
- 宕机恢复一致性
- 重复消息幂等
- 极端行情(瞬时大量订单)
10. 总结
现货撮合并不神秘,核心就三步:
- 定规则(价格优先、时间优先)
- 建结构(双边订单簿 + FIFO)
- 保一致(成交、订单、资产原子更新)
真正决定系统稳定性的,不是“算法多复杂”,而是:
- 是否控制了顺序
- 是否处理了边界
- 是否保证了资金一致性
后续
如果想要了解更多现货交易相关内容,可以关注后续文章内容更新