
在交易市场中,撮合交易算法是每一笔订单成交的核心机制。每当买卖双方提交订单时,系统会根据价格优先、时间优先的规则自动匹配成交。对于老股民来说,这些规则很熟悉,但从开发者角度来看,设计一个高效、可复用的撮合引擎仍然是一项挑战。本文从技术实现的角度出发,讲解如何用 Java 构建一个简化的撮合交易算法。
买卖盘的设计思路
撮合引擎的核心是 订单簿(OrderBook),它维护两条队列:买盘和卖盘。买盘按价格从高到低排序,确保报价最高的买单优先成交;卖盘按价格从低到高排序,确保报价最低的卖单优先成交。
在交易市场中,买1价格永远不会高于等于卖1价格,否则说明系统漏掉了应成交的订单。对于多个相同价格的订单,则按时间顺序成交——在我们的实现中,用全局唯一的 sequenceId 来保证排序和处理顺序,而不是直接用创建时间。
很多人会想到用 List<Order> 来存储订单,但在高频交易环境中,插入和删除订单的成本太高(O(N)),效率难以保证。更好的方案是用平衡树结构,比如 Java 的 TreeMap,可以让插入、删除和查找操作的时间复杂度保持在 O(logN)。
| public record OrderKey(long sequenceId, BigDecimal price) { } |
由于买盘和卖盘的排序规则不同,需要为 TreeMap 提供两个不同的比较器:
| private static final Comparator<OrderKey> SORT_SELL = (o1, o2) -> { int cmp = o1.price().compareTo(o2.price()); // 价格低在前 return cmp == 0 ? Long.compare(o1.sequenceId(), o2.sequenceId()) : cmp; }; private static final Comparator<OrderKey> SORT_BUY = (o1, o2) -> { int cmp = o2.price().compareTo(o1.price()); // 价格高在前 return cmp == 0 ? Long.compare(o1.sequenceId(), o2.sequenceId()) : cmp; }; |
有了比较器后,OrderBook 的实现就非常直接:
| public class OrderBook { public final Direction direction; public final TreeMap<OrderKey, OrderEntity> book; public OrderBook(Direction direction) { this.direction = direction; this.book = new TreeMap<>(direction == Direction.BUY ? SORT_BUY : SORT_SELL); } public OrderEntity getFirst() { return this.book.isEmpty() ? null : this.book.firstEntry().getValue(); } public boolean remove(OrderEntity order) { return this.book.remove(new OrderKey(order.sequenceId, order.price)) != null; } public boolean add(OrderEntity order) { return this.book.put(new OrderKey(order.sequenceId, order.price), order) == null; } } |
小提示:Java 中比较 BigDecimal 的值一定要用 compareTo(),不要用 equals(),否则 1.2 和 1.20 会被判定不相等。
撮合引擎的核心逻辑
有了买卖盘,就可以实现撮合引擎。核心数据结构如下:
| public class MatchEngine { public final OrderBook buyBook = new OrderBook(Direction.BUY); public final OrderBook sellBook = new OrderBook(Direction.SELL); public BigDecimal marketPrice = BigDecimal.ZERO; private long sequenceId; public MatchResult processOrder(long sequenceId, OrderEntity order) { switch (order.direction) { case BUY: return processOrder(order, this.sellBook, this.buyBook); case SELL: return processOrder(order, this.buyBook, this.sellBook); default: throw new IllegalArgumentException(“Invalid direction.”); } } } |
处理订单时,如果是买单,就尝试与卖盘匹配;如果是卖单,则尝试与买盘匹配。新订单称为 Taker,已经挂在盘上的订单称为 Maker。Taker 如果未完全成交,则转为 Maker 挂在订单簿上。
| MatchResult processOrder(OrderEntity takerOrder, OrderBook makerBook, OrderBook anotherBook) { long ts = takerOrder.createdAt; MatchResult matchResult = new MatchResult(takerOrder); BigDecimal takerUnfilledQuantity = takerOrder.quantity; for (;;) { OrderEntity makerOrder = makerBook.getFirst(); if (makerOrder == null) break; if ((takerOrder.direction == Direction.BUY && takerOrder.price.compareTo(makerOrder.price) < 0) || (takerOrder.direction == Direction.SELL && takerOrder.price.compareTo(makerOrder.price) > 0)) { break; } this.marketPrice = makerOrder.price; BigDecimal matchedQuantity = takerUnfilledQuantity.min(makerOrder.unfilledQuantity); matchResult.add(makerOrder.price, matchedQuantity, makerOrder); takerUnfilledQuantity = takerUnfilledQuantity.subtract(matchedQuantity); BigDecimal makerUnfilledQuantity = makerOrder.unfilledQuantity.subtract(matchedQuantity); if (makerUnfilledQuantity.signum() == 0) { makerOrder.updateOrder(makerUnfilledQuantity, OrderStatus.FULLY_FILLED, ts); makerBook.remove(makerOrder); } else { makerOrder.updateOrder(makerUnfilledQuantity, OrderStatus.PARTIAL_FILLED, ts); } if (takerUnfilledQuantity.signum() == 0) { takerOrder.updateOrder(takerUnfilledQuantity, OrderStatus.FULLY_FILLED, ts); break; } } if (takerUnfilledQuantity.signum() > 0) { takerOrder.updateOrder(takerUnfilledQuantity, takerUnfilledQuantity.compareTo(takerOrder.quantity) == 0 ? OrderStatus.PENDING : OrderStatus.PARTIAL_FILLED, ts); anotherBook.add(takerOrder); } return matchResult; } |
MatchResult 会记录本次 Taker 订单及所有撮合记录,方便清算系统进行结算。
支持多交易对
一个引擎实例只能处理一个交易对。如果想在同一个系统中支持多个交易对,可以用一个引擎组来管理:
| class MatchEngineGroup { Map<Long, MatchEngine> engines = new HashMap<>(); public MatchResult processOrder(long sequenceId, OrderEntity order) { Long symbolId = order.symbolId; MatchEngine engine = engines.get(symbolId); if (engine == null) { engine = new MatchEngine(); engines.put(symbolId, engine); } return engine.processOrder(sequenceId, order); } } |
每个订单加上 symbolId 属性,就能被路由到对应的交易对引擎实例,系统内部保持隔离。
实例演示
假设有如下订单输入撮合引擎:
| 方向 | 价格 | 数量 |
| buy | 2082.34 | 1 |
| sell | 2087.6 | 2 |
| buy | 2087.8 | 1 |
| buy | 2085.01 | 5 |
| sell | 2088.02 | 3 |
| sell | 2087.60 | 6 |
| buy | 2081.11 | 7 |
| buy | 2086.0 | 3 |
| buy | 2088.33 | 1 |
| sell | 2086.54 | 2 |
| sell | 2086.55 | 5 |
| buy | 2086.55 | 3 |
撮合完成后,买卖盘会自动更新,市场最新成交价也会随之改变,系统的内部状态完全可复现。每一笔成交都严格遵循价格优先、时间优先原则,整个撮合流程清晰高效。


