第三章:订单簿与报价引擎

做市系统的核心,说白了就两件事:看订单簿发报价

订单簿是市场的镜子,报价引擎是我们的手。这两者配合得好,你就能在市场上游刃有余。配合不好?嗯,我见过不少团队,订单簿解析慢了10微秒,整个策略就崩了。

3.1 订单簿数据结构

订单簿长什么样?其实就是一个价格-数量的映射表。买盘叫Bid,卖盘叫Ask。每个价格档位上,挂着对应的委托量。

我个人习惯用红黑树跳表来实现。为什么?因为订单簿需要频繁的插入、删除和范围查询。数组太慢,哈希表又没法做范围遍历。

核心数据结构:

  • Bid侧:按价格降序排列(价高者优先)
  • Ask侧:按价格升序排列(价低者优先)
  • 每个Level:价格 + 总量 + 订单数

来看一个简化版的订单簿结构:

// 订单簿层级
struct Level {
    double price;      // 价格
    double volume;     // 总量
    int    orderCount; // 订单数
};

// 订单簿
struct OrderBook {
    map<double, Level, greater<double>> bids; // 买盘
    map<double, Level> asks;                   // 卖盘
    double lastUpdateTime;
};

我在项目中遇到过一个问题:直接用浮点数做Key。结果呢?价格精度导致两个本该合并的Level被分开了。后来我改用整数化价格,比如把1.2345存成12345,乘以最小价格精度。这个坑,大家一定要避开。

3.2 买卖报价生成逻辑

有了订单簿,怎么生成报价?

最基础的做法是:取买一和卖一的中间价,然后加减一个Spread。

但实际没那么简单。你想想看,如果市场深度很差,买一和卖一之间差了好几个Tick,你直接取中间价,很可能挂出去就被吃掉。

我的经验:报价生成要考虑三个维度:

  1. 市场深度:深度越浅,Spread要越大
  2. 波动率:波动越大,报价要更保守
  3. 库存状态:库存偏多,Bid要压低;库存偏少,Ask要抬高

我曾经做过一个策略,只盯着中间价报价。结果遇到一次闪电波动,库存瞬间被打穿。后来我加了一个动态Spread调整模块,根据实时波动率自动扩缩Spread,才稳住了。

报价生成的核心逻辑:

// 报价生成
Quote generateQuote(OrderBook& book, Inventory& inv) {
    double midPrice = (book.bestBid() + book.bestAsk()) / 2.0;
    double spread = calcDynamicSpread(book, inv);
    
    double bidPrice = midPrice - spread / 2.0;
    double askPrice = midPrice + spread / 2.0;
    
    // 库存偏移
    double inventorySkew = inv.skew() * 0.1;
    bidPrice += inventorySkew;
    askPrice += inventorySkew;
    
    return Quote(bidPrice, askPrice);
}

3.3 报价更新频率与策略

报价更新频率,这是个老生常谈的问题。

更新太快,系统压力大,还可能被交易所限频。更新太慢,报价跟不上市场,容易被套利者盯上。

我个人建议:事件驱动 + 定时刷新

  • 事件驱动:订单簿有显著变化时(比如价格变动超过N个Tick),立即更新
  • 定时刷新:即使没有事件,每隔X毫秒也刷新一次,防止报价过期

注意:不同交易所的限频规则差异很大。有的允许每秒100次,有的只允许10次。我曾经因为没注意限频,被某交易所封了API半小时。那半小时,库存敞口完全暴露,亏了不少。

更新策略上,我常用这几种:

策略名称 适用场景 更新频率
激进型 高流动性、低波动 每次Tick都更新
稳健型 中等流动性 每3-5个Tick更新一次
保守型 低流动性、高波动 固定间隔(如100ms)

为什么会这样区分?说白了,流动性好的市场,你报价稍微慢一点,就被别人抢走了。流动性差的市场,你报价太频繁,反而容易暴露意图,被对手盯上。

下面这张图展示了订单簿、报价引擎和风控模块之间的协作关系:

订单簿与报价引擎协作流程 交易所行情 Tick数据流 订单簿引擎 Bid/Ask Level管理 报价引擎 Spread/价格生成 发单 风控模块 库存/敞口/限频检查 风控信号 报价审核 库存管理 持仓/盈亏/风险敞口 图例说明: 外部数据源 核心处理模块 输出模块 风控模块 辅助模块 异步信号传递

嗯,这里要注意一点:报价更新不是越快越好。我见过有人把更新频率设到1ms一次,结果系统CPU跑满,订单簿解析都延迟了。最后报价反而更慢。

我的建议是:先根据交易所限频算出上限,然后留30%的余量。比如限频100次/秒,你就设70次/秒。这样既保证响应速度,又不会触发风控。

总结一下本章核心:

  • 订单簿用红黑树或跳表实现,价格用整数化处理
  • 报价生成要考虑深度、波动率和库存三个维度
  • 更新策略采用事件驱动+定时刷新,频率要留余量

订单簿和报价引擎,是做市系统的心脏。心脏跳得好,整个系统才能稳定运行。下一章我们会聊到风控模块,到时候你会看到,报价引擎和风控是怎么配合的。


公众号:蓝海资料掘金营,微信deep3321