
用 Java 搭建一个最简化的撮合交易算法
在交易市场中,撮合交易算法是每一笔订单成交的核心机制。每当买卖双方提交订单时,系统会根据价格优先、时间优先的规…
在交易市场中,撮合交易算法是每一笔订单成交的核心机制。每当买卖双方提交订单时,系统会根据价格优先、时间优先的规则自动匹配成交。对于老股民来说,这些规则很熟悉,但从开发者角度来看,设计一个高效、可复用的撮合引擎仍然是一项挑战。本文从技术实现的角度出发,讲解如何用 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 属性,就能被路由到对应的交易对引擎实例,系统内部保持隔离。
实例演示
假设有如下订单输入撮合引擎:
方向价格数量buy2082.341sell2087.62buy2087.81buy2085.015sell2088.023sell2087.606buy2081.117buy2086.03buy2088.331sell2086.542sell2086.555buy2086.553
撮合完成后,买卖盘会自动更新,市场最新成交价也会随之改变,系统的内部状态完全可复现。每一笔成交都严格遵循价格优先、时间优先原则,整个撮合流程清晰高效。