Skip to content

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, dYdXUniswap, Curve

预测市场选择订单簿的原因

  • 预测市场价格范围固定(0-1),订单簿更高效
  • 需要精确的价格发现机制
  • 做市商需要精细控制挂单

1.3 撮合引擎架构


2. 订单类型详解

2.1 订单类型对比

类型说明特点使用场景
市价单以当前最优价成交快速、有滑点急需成交
限价单指定价格成交等待匹配、无滑点精确控制价格
止损单价格触及阈值时触发风控工具控制损失
IOC 单立即成交剩余取消部分成交大单拆分
FOK 单全部成交或全部取消全或无精确数量

2.2 订单数据结构

typescript
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 撮合逻辑实现

typescript
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 内存订单簿实现

typescript
/**
 * 高性能内存订单簿
 * 使用有序数组实现 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 性能基准测试

操作目标延迟实际延迟说明
订单插入<1ms0.5ms内存操作
撮合匹配<5ms2ms含成交记录生成
订单簿查询<1ms0.3ms快照生成
批量结算<100ms50ms100 笔订单
链上提交<30s15sL2 确认时间

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 接口

typescript
// 订阅订单簿更新
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 集合竞价

预测市场通常使用连续竞价,但在特定场景下集合竞价更优:

模式适用场景优点缺点
连续竞价日常交易实时成交、价格连续需要持续做市
集合竞价市场开盘/收盘集中流动性、防操纵不能立即成交
混合模式新事件上市先集合后连续实现复杂

集合竞价撮合逻辑

typescript
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 冰山订单

大额交易者需要冰山订单来隐藏真实意图:

typescript
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 撮合引擎性能基准

指标目标值当前实现优化方向
订单处理延迟<1ms0.5ms已达标
撮合吞吐量>10,000 TPS5,000 TPS无锁设计
订单簿查询<0.5ms0.3ms已达标
内存占用<1GB500MB已达标
批量结算<50ms/100笔30ms已达标

性能优化:无锁撮合

typescript
// 单线程撮合,避免锁竞争
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 撮合引擎容错与恢复

撮合引擎必须具备容错能力,在异常情况下保证数据一致性:

订单状态机

容错策略实现

typescript
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%需要扩容

监控代码示例

typescript
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)避免浮点误差

价格精度处理示例

typescript
// 使用整数避免浮点精度问题
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 撮合引擎测试策略

撮合引擎的正确性至关重要。以下是经过验证的测试策略:

typescript
// 撮合引擎单元测试
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)— 去中心化订单簿交易所参考

OPC 超级个体实战指南