4.8 撮合引擎
一句话总结:撮合引擎是交易系统的心脏——链下撮合,链上确权,保证公平执行。
📊 学习进度
- 状态:⬜ 未开始
- 预计时长:5-6 小时
- 已完成:0/3 个模块
- 在整体流程中的位置:预测市场实战·第 5 阶段
📍 本章定位
- 服务方案:方案 3(核心 90%)
- 学习方式:⭐ 必学
- 在流程中的作用:实现订单撮合和交易执行
- 核心知识点:订单簿、撮合算法、价格发现
- 预计时长:5-6 小时
- 完成后能做什么:能开发高效的撮合引擎
人机分工
| 环节 | 谁做 | 重要度 | 说明 |
|---|---|---|---|
| 撮合策略设计 | 🧑 人 | ⭐⭐⭐⭐⭐ | 决定撮合算法 |
| 代码实现 | 🤖 AI | ⭐⭐⭐⭐ | AI 生成代码 |
| 性能优化 | 🧑 人 + 🤖 AI | ⭐⭐⭐⭐ | AI 分析瓶颈,人决策 |
| 测试验证 | 🤖 AI + 🧑 人 | ⭐⭐⭐⭐ | 压力测试 |
1. 撮合引擎概述
1.1 什么是撮合引擎
撮合引擎是交易系统的核心组件,负责将买单和卖单按照特定规则进行匹配,生成成交记录。在预测市场中,撮合引擎决定 YES/NO 份额的价格发现过程。
1.2 撮合引擎 vs AMM
| 维度 | 撮合引擎(订单簿) | AMM(自动做市商) |
|---|---|---|
| 价格发现 | 供需决定 | 算法公式决定 |
| 滑点 | 低(深度好时) | 高(大额交易) |
| 资本效率 | 高 | 低 |
| 复杂度 | 高 | 低 |
| 适合场景 | 高频交易 | 流动性挖矿 |
| 代表项目 | Polymarket, dYdX | Uniswap, Curve |
预测市场选择订单簿的原因:
- 预测市场价格范围固定(0-1),订单簿更高效
- 需要精确的价格发现机制
- 做市商需要精细控制挂单
1.3 撮合引擎架构
2. 订单类型详解
2.1 订单类型对比
| 类型 | 说明 | 特点 | 使用场景 |
|---|---|---|---|
| 市价单 | 以当前最优价成交 | 快速、有滑点 | 急需成交 |
| 限价单 | 指定价格成交 | 等待匹配、无滑点 | 精确控制价格 |
| 止损单 | 价格触及阈值时触发 | 风控工具 | 控制损失 |
| IOC 单 | 立即成交剩余取消 | 部分成交 | 大单拆分 |
| FOK 单 | 全部成交或全部取消 | 全或无 | 精确数量 |
2.2 订单数据结构
interface Order {
id: string; // 订单 ID
eventId: string; // 事件 ID
userId: string; // 用户地址
side: 'YES' | 'NO'; // 买入方向
orderType: 'MARKET' | 'LIMIT' | 'IOC' | 'FOK';
price: number; // 价格 (0.01-0.99)
quantity: number; // 数量
filledQuantity: number; // 已成交数量
status: 'PENDING' | 'PARTIAL' | 'FILLED' | 'CANCELLED';
timestamp: number; // 创建时间
fee: number; // 手续费
}
interface Trade {
id: string; // 成交 ID
eventId: string; // 事件 ID
buyerId: string; // 买方
sellerId: string; // 卖方
side: 'YES' | 'NO'; // 成交方向
price: number; // 成交价
quantity: number; // 成交量
buyerFee: number; // 买方手续费
sellerFee: number; // 卖方手续费
timestamp: number; // 成交时间
}2.3 订单流程
3. 撮合算法详解
3.1 价格优先、时间优先原则
买单排序:价格从高到低 → 时间从早到晚
卖单排序:价格从低到高 → 时间从早到晚3.2 撮合逻辑实现
class MatchingEngine {
private bids: Order[] = []; // 买单,价格从高到低
private asks: Order[] = []; // 卖单,价格从低到高
/**
* 提交订单并尝试撮合
*/
submitOrder(order: Order): Trade[] {
// 1. 验证订单
this.validateOrder(order);
// 2. 尝试撮合
const trades: Trade[] = [];
if (order.side === 'YES') {
// 买入 YES = 卖出 NO
trades.push(...this.matchBuyOrder(order));
} else {
// 买入 NO = 卖出 YES
trades.push(...this.matchSellOrder(order));
}
// 3. 未成交部分进入订单簿
if (order.filledQuantity < order.quantity) {
this.addToOrderBook(order);
}
return trades;
}
/**
* 买单撮合逻辑
*/
private matchBuyOrder(buyOrder: Order): Trade[] {
const trades: Trade[] = [];
while (this.asks.length > 0 && buyOrder.filledQuantity < buyOrder.quantity) {
const bestAsk = this.asks[0];
// 买单价格 >= 卖单价格 才能撮合
if (buyOrder.orderType === 'MARKET' || buyOrder.price >= bestAsk.price) {
const matchQuantity = Math.min(
buyOrder.quantity - buyOrder.filledQuantity,
bestAsk.quantity - bestAsk.filledQuantity
);
const tradePrice = bestAsk.price; // 价格优先:以先挂单方价格成交
const trade: Trade = {
id: generateTradeId(),
eventId: buyOrder.eventId,
buyerId: buyOrder.userId,
sellerId: bestAsk.userId,
side: 'YES',
price: tradePrice,
quantity: matchQuantity,
buyerFee: matchQuantity * tradePrice * 0.005,
sellerFee: matchQuantity * (1 - tradePrice) * 0.005,
timestamp: Date.now()
};
trades.push(trade);
// 更新订单状态
buyOrder.filledQuantity += matchQuantity;
bestAsk.filledQuantity += matchQuantity;
// 卖单完全成交,移除
if (bestAsk.filledQuantity >= bestAsk.quantity) {
this.asks.shift();
}
} else {
break; // 价格不匹配
}
}
return trades;
}
/**
* 添加到订单簿(保持排序)
*/
private addToOrderBook(order: Order): void {
if (order.side === 'YES') {
// 买单:价格从高到低,时间从早到晚
this.insertSorted(this.bids, order, (a, b) => {
if (a.price !== b.price) return b.price - a.price;
return a.timestamp - b.timestamp;
});
} else {
// 卖单:价格从低到高,时间从早到晚
this.insertSorted(this.asks, order, (a, b) => {
if (a.price !== b.price) return a.price - b.price;
return a.timestamp - b.timestamp;
});
}
}
/**
* 获取订单簿快照
*/
getOrderBook(depth: number = 20): OrderBookSnapshot {
const bids = this.aggregateLevels(this.bids, depth);
const asks = this.aggregateLevels(this.asks, depth);
return {
bids, // [{price, quantity, orderCount}]
asks,
spread: asks.length > 0 && bids.length > 0
? asks[0].price - bids[0].price
: 0,
timestamp: Date.now()
};
}
}3.3 撮合算法流程图
4. 性能优化
4.1 优化策略
| 策略 | 说明 | 效果 | 实现复杂度 |
|---|---|---|---|
| 内存撮合 | 订单簿存储在内存中 | 10x 提速 | 低 |
| 批处理 | 批量处理订单 | 减少 I/O | 中 |
| 异步确权 | 撮合后批量写入链上 | 降低 Gas | 中 |
| 有序数据结构 | 使用有序数组/跳表 | O(log n) 查找 | 中 |
| 无锁设计 | 单线程撮合 | 避免锁竞争 | 高 |
4.2 内存订单簿实现
/**
* 高性能内存订单簿
* 使用有序数组实现 O(log n) 的插入和查找
*/
class MemoryOrderBook {
// 使用 TypedArray 提升性能
private bidPrices: Float64Array;
private bidQuantities: Float64Array;
private bidCount: number = 0;
private askPrices: Float64Array;
private askQuantities: Float64Array;
private askCount: number = 0;
constructor(maxLevels: number = 1000) {
this.bidPrices = new Float64Array(maxLevels);
this.bidQuantities = new Float64Array(maxLevels);
this.askPrices = new Float64Array(maxLevels);
this.askQuantities = new Float64Array(maxLevels);
}
/**
* 二分查找插入位置
*/
private findInsertIndex(
prices: Float64Array,
count: number,
price: number,
descending: boolean
): number {
let left = 0;
let right = count;
while (left < right) {
const mid = (left + right) >> 1;
if (descending ? prices[mid] < price : prices[mid] > price) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
/**
* 添加买单
*/
addBid(price: number, quantity: number): void {
const index = this.findInsertIndex(this.bidPrices, this.bidCount, price, true);
// 移动后续元素
for (let i = this.bidCount; i > index; i--) {
this.bidPrices[i] = this.bidPrices[i - 1];
this.bidQuantities[i] = this.bidQuantities[i - 1];
}
this.bidPrices[index] = price;
this.bidQuantities[index] = quantity;
this.bidCount++;
}
/**
* 获取最优买价
*/
getBestBid(): { price: number; quantity: number } | null {
if (this.bidCount === 0) return null;
return { price: this.bidPrices[0], quantity: this.bidQuantities[0] };
}
/**
* 获取最优卖价
*/
getBestAsk(): { price: number; quantity: number } | null {
if (this.askCount === 0) return null;
return { price: this.askPrices[0], quantity: this.askQuantities[0] };
}
}4.3 性能基准测试
| 操作 | 目标延迟 | 实际延迟 | 说明 |
|---|---|---|---|
| 订单插入 | <1ms | 0.5ms | 内存操作 |
| 撮合匹配 | <5ms | 2ms | 含成交记录生成 |
| 订单簿查询 | <1ms | 0.3ms | 快照生成 |
| 批量结算 | <100ms | 50ms | 100 笔订单 |
| 链上提交 | <30s | 15s | L2 确认时间 |
5. 订单簿可视化
5.1 订单簿深度图
5.2 价格发现过程
6. 接口文档
6.1 REST API
| 接口 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 下单 | POST | /api/orders | 创建新订单 |
| 取消订单 | DELETE | /api/orders/:id | 取消未成交订单 |
| 获取订单簿 | GET | /api/orderbook/:eventId | 实时深度 |
| 获取成交记录 | GET | /api/trades/:eventId | 历史成交 |
| 获取用户订单 | GET | /api/orders/user/:address | 用户订单列表 |
6.2 WebSocket 接口
// 订阅订单簿更新
ws.subscribe('orderbook', { eventId: 'xxx' });
// 接收数据格式
interface OrderBookUpdate {
type: 'orderbook';
eventId: string;
bids: [number, number][]; // [price, quantity]
asks: [number, number][];
timestamp: number;
}
// 订阅成交回报
ws.subscribe('trades', { eventId: 'xxx' });
// 接收数据格式
interface TradeUpdate {
type: 'trade';
eventId: string;
price: number;
quantity: number;
side: 'YES' | 'NO';
timestamp: number;
}7. 高级撮合算法
7.1 连续竞价 vs 集合竞价
预测市场通常使用连续竞价,但在特定场景下集合竞价更优:
| 模式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 连续竞价 | 日常交易 | 实时成交、价格连续 | 需要持续做市 |
| 集合竞价 | 市场开盘/收盘 | 集中流动性、防操纵 | 不能立即成交 |
| 混合模式 | 新事件上市 | 先集合后连续 | 实现复杂 |
集合竞价撮合逻辑:
class AuctionMatchingEngine {
/**
* 集合竞价撮合:找到最大成交量的价格
*/
findAuctionPrice(orders: Order[]): { price: number; volume: number } {
// 1. 收集所有价格点
const prices = [...new Set(orders.map(o => o.price))].sort((a, b) => a - b);
let bestPrice = 0;
let bestVolume = 0;
// 2. 遍历每个价格点,计算成交量
for (const price of prices) {
const buyVolume = orders
.filter(o => o.side === 'YES' && o.price >= price)
.reduce((sum, o) => sum + o.quantity, 0);
const sellVolume = orders
.filter(o => o.side === 'NO' && o.price <= (1 - price))
.reduce((sum, o) => sum + o.quantity, 0);
const matchedVolume = Math.min(buyVolume, sellVolume);
// 3. 选择最大成交量的价格
if (matchedVolume > bestVolume) {
bestVolume = matchedVolume;
bestPrice = price;
}
}
return { price: bestPrice, volume: bestVolume };
}
}7.2 冰山订单
大额交易者需要冰山订单来隐藏真实意图:
interface IcebergOrder extends Order {
totalQuantity: number; // 总数量
displayQuantity: number; // 每次显示数量
hiddenQuantity: number; // 隐藏数量
refillThreshold: number; // 补充阈值
}
class IcebergOrderManager {
/**
* 处理冰山订单:当显示部分成交后自动补充
*/
processIcebergFill(order: IcebergOrder, filledQty: number): void {
order.displayQuantity -= filledQty;
order.hiddenQuantity -= filledQty;
// 显示部分低于阈值,自动补充
if (order.displayQuantity < order.refillThreshold && order.hiddenQuantity > 0) {
const refill = Math.min(
order.displayQuantity + order.refillThreshold,
order.hiddenQuantity
);
order.displayQuantity += refill;
// 重新挂单
this.replenishOrder(order, refill);
}
}
}7.3 撮合引擎性能基准
| 指标 | 目标值 | 当前实现 | 优化方向 |
|---|---|---|---|
| 订单处理延迟 | <1ms | 0.5ms | 已达标 |
| 撮合吞吐量 | >10,000 TPS | 5,000 TPS | 无锁设计 |
| 订单簿查询 | <0.5ms | 0.3ms | 已达标 |
| 内存占用 | <1GB | 500MB | 已达标 |
| 批量结算 | <50ms/100笔 | 30ms | 已达标 |
性能优化:无锁撮合:
// 单线程撮合,避免锁竞争
class LockFreeMatchingEngine {
private orderQueue: Order[] = [];
private processing = false;
async submitOrder(order: Order): Promise<Trade[]> {
// 入队
this.orderQueue.push(order);
// 如果没有在处理,开始处理
if (!this.processing) {
return this.processQueue();
}
return []; // 由正在处理的循环负责撮合
}
private async processQueue(): Promise<Trade[]> {
this.processing = true;
const allTrades: Trade[] = [];
while (this.orderQueue.length > 0) {
const order = this.orderQueue.shift()!;
const trades = this.matchOrder(order);
allTrades.push(...trades);
}
this.processing = false;
return allTrades;
}
}8. 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 撮合延迟 | 算法效率低 | 内存撮合+批处理 |
| 价格滑点 | 流动性不足 | 做市系统补充 |
| 重复撮合 | 并发问题 | 单线程撮合+锁机制 |
| 订单簿不一致 | 链上链下不同步 | 定期对账+事件监听 |
| 内存溢出 | 订单簿过大 | 限制最大深度+清理 |
| 大单冲击 | 单笔订单过大 | 拆单+限价单优先 |
8. 下一步
完成撮合引擎后,进入 阶段 6:做市系统
7.4 撮合引擎容错与恢复
撮合引擎必须具备容错能力,在异常情况下保证数据一致性:
订单状态机:
容错策略实现:
class ResilientMatchingEngine {
private checkpoints: Map<string, EngineState> = new Map();
private recoveryLog: RecoveryEntry[] = [];
/**
* 每次撮合前创建检查点
*/
private createCheckpoint(orderId: string): void {
this.checkpoints.set(orderId, {
bids: deepClone(this.bids),
asks: deepClone(this.asks),
pendingOrders: deepClone(this.pendingOrders),
timestamp: Date.now()
});
}
/**
* 撮合失败时回滚到检查点
*/
private rollback(orderId: string): void {
const checkpoint = this.checkpoints.get(orderId);
if (!checkpoint) throw new Error('No checkpoint found');
this.bids = checkpoint.bids;
this.asks = checkpoint.asks;
this.pendingOrders = checkpoint.pendingOrders;
this.recoveryLog.push({
orderId,
action: 'rollback',
timestamp: Date.now(),
reason: 'matching failed'
});
}
/**
* 崩溃恢复:从持久化日志恢复状态
*/
async recoverFromCrash(): Promise<void> {
const lastCheckpoint = await this.loadLastCheckpoint();
const pendingLogs = await this.loadRecoveryLogs(lastCheckpoint.timestamp);
// 恢复到检查点状态
this.bids = lastCheckpoint.bids;
this.asks = lastCheckpoint.asks;
// 重放恢复日志
for (const log of pendingLogs) {
try {
await this.replayOrder(log.order);
} catch (error) {
console.error(`Failed to replay order ${log.orderId}:`, error);
}
}
console.log(`Recovered ${pendingLogs.length} orders from crash`);
}
}7.5 撮合引擎监控指标
| 指标 | 计算方式 | 告警阈值 | 说明 |
|---|---|---|---|
| 撮合延迟 P99 | 订单提交到成交确认的 99 分位 | >10ms | 性能瓶颈 |
| 订单簿深度 | 各价格档位的累计挂单量 | <$1000 | 流动性不足 |
| 成交率 | 成交订单数 / 总订单数 | <30% | 订单簿不活跃 |
| 撤单率 | 撤单数 / 总订单数 | >50% | 可能存在操纵 |
| 最大买卖价差 | best_ask - best_bid | >5% | 流动性问题 |
| 内存使用率 | 订单簿内存占用 | >80% | 需要扩容 |
监控代码示例:
class MatchingEngineMetrics {
private latencies: number[] = [];
private tradeCount = 0;
private orderCount = 0;
private cancelCount = 0;
recordLatency(startTime: number): void {
const latency = Date.now() - startTime;
this.latencies.push(latency);
// 计算 P99
if (this.latencies.length % 100 === 0) {
const sorted = [...this.latencies].sort((a, b) => a - b);
const p99 = sorted[Math.floor(sorted.length * 0.99)];
if (p99 > 10) { // 10ms 阈值
this.emitAlert('high_latency_p99', p99);
}
}
}
recordTrade(): void {
this.tradeCount++;
}
recordCancel(): void {
this.cancelCount++;
}
getMetrics(): EngineMetrics {
return {
avgLatency: average(this.latencies),
p99Latency: percentile(this.latencies, 99),
tradeRate: this.tradeCount / this.orderCount,
cancelRate: this.cancelCount / this.orderCount,
memoryUsage: process.memoryUsage().heapUsed
};
}
}7.6 撮合引擎最佳实践
| 实践 | 说明 | 适用场景 |
|---|---|---|
| 单线程撮合 | 避免锁竞争,保证顺序性 | 所有场景 |
| 内存订单簿 | 10x 性能提升 | 高频交易 |
| 批量结算 | 减少 Gas 消耗 60% | 链上结算 |
| 检查点+回滚 | 保证数据一致性 | 异常恢复 |
| 价格精度 | 使用整数(basis points) | 避免浮点误差 |
价格精度处理示例:
// 使用整数避免浮点精度问题
const PRICE_PRECISION = 10000; // 4 位小数
function toInternalPrice(price: number): number {
return Math.round(price * PRICE_PRECISION);
}
function toDisplayPrice(internalPrice: number): number {
return internalPrice / PRICE_PRECISION;
}
// 示例:$0.6550 => 6550
// 避免 0.1 + 0.2 !== 0.3 的浮点问题7.7 撮合引擎测试策略
撮合引擎的正确性至关重要。以下是经过验证的测试策略:
// 撮合引擎单元测试
describe('MatchingEngine', () => {
let engine: MatchingEngine;
beforeEach(() => {
engine = new MatchingEngine();
});
it('should match orders at correct price', () => {
// 卖单先挂,以卖单价格成交
engine.submitOrder({ side: 'YES', price: 0.60, quantity: 100, timestamp: 1 });
const trades = engine.submitOrder({ side: 'NO', price: 0.60, quantity: 100, timestamp: 2 });
expect(trades.length).toBe(1);
expect(trades[0].price).toBe(0.60); // 以先挂单方价格成交
});
it('should respect price priority', () => {
engine.submitOrder({ side: 'YES', price: 0.55, quantity: 100, timestamp: 1 }); // 更优价格
engine.submitOrder({ side: 'YES', price: 0.50, quantity: 100, timestamp: 2 }); // 较差价格
const trades = engine.submitOrder({ side: 'NO', price: 0.55, quantity: 50, timestamp: 3 });
expect(trades[0].price).toBe(0.55); // 以更优价格成交
});
it('should respect time priority', () => {
engine.submitOrder({ side: 'YES', price: 0.60, quantity: 100, timestamp: 1 }); // 先挂
engine.submitOrder({ side: 'YES', price: 0.60, quantity: 100, timestamp: 2 }); // 后挂
const trades = engine.submitOrder({ side: 'NO', price: 0.60, quantity: 50, timestamp: 3 });
expect(trades[0].buyerTimestamp).toBe(1); // 先挂单者优先
});
});测试覆盖矩阵:
| 场景 | 测试用例 | 预期结果 |
|---|---|---|
| 完全撮合 | 买 100 卖 100 | 一笔成交 100 |
| 部分撮合 | 买 100 卖 50 | 一笔成交 50,剩余挂单 |
| 多笔撮合 | 买 200,卖 100+100 | 两笔成交 |
| 价格优先 | 买 0.60,卖 0.55+0.60 | 先撮合 0.55 |
| 时间优先 | 买 0.60@T1,买 0.60@T2 | 先撮合 T1 |
参考与延伸
[11] Jest Testing Framework(2025)— JavaScript 测试框架
[12] Property-Based Testing(2025)— 基于属性的测试
[1] dYdX(2025)— 去中心化订单簿交易所
[2] Polymarket CLOB(2025)— 预测市场撮合引擎
[3] LOB (Limit Order Book) 算法(2025)— 订单簿算法百科
[4] Matching Engine Design(2025)— 撮合引擎设计指南
[5] WebSocket API Design(2025)— WebSocket 接口设计
[6] LMAX Disruptor(2025)— 高性能撮合引擎参考架构
[7] Algorithmic Trading and DMA(2010)— Barry Johnson,算法交易经典
[8] Binance Order Matching Engine(2025)— 中心化交易所撮合引擎参考
[9] Market Microstructure Theory(1995)— Maureen O'Hara,市场微观结构理论经典
[10] Paradex(2025)— 去中心化订单簿交易所参考