第三章 订单路由层设计:智能路由算法

订单路由层,说白了就是你的交易系统里那个「派单员」。

我当年刚接触多品种交易系统时,觉得路由不就是把单子发出去吗?后来被现实狠狠教育了一顿。你想想看,一个订单发到交易所A,结果那边流动性不够,单子被拆得七零八落;或者发到交易所B,价格倒是好,但延迟高得离谱。嗯,这时候你就知道路由层有多重要了。

3.1 智能路由的核心算法

智能路由不是简单的「谁便宜找谁」。我个人习惯把它拆成三个维度:价格、时间、流动性。这三个维度缺一不可。

3.1.1 价格优先

价格优先是最基础的逻辑。说白了,哪个交易所的报价最优,就往哪发。

但这里有个坑——我曾经在实盘里遇到过,某个交易所的报价看起来便宜了0.1个tick,结果成交后发现滑点反而更大。为什么?因为那个报价是「影子报价」,根本吃不到量。

注意: 价格优先不能只看最优价,要看「可成交价格区间」。我建议用加权平均价来做判断,而不是简单的最优价。

3.1.2 时间优先

时间优先,很多人理解成「谁先到谁先成交」。其实在路由层,时间优先指的是「路由决策的时效性」。

举个例子:你同时监控三个交易所的订单簿。交易所A的买一价是100.00,交易所B是100.01。如果你花200毫秒做路由决策,等单子发出去,价格可能已经变了。

我自己的做法是:给每个路由决策设定一个「时间预算」。比如50毫秒内必须做出决定,超时就走默认路由。这个默认路由,我建议选流动性最好的那个交易所。

3.1.3 流动性优先

流动性优先,这个是我最看重的。为什么?因为价格再好,吃不到量等于白搭。

我在项目中遇到过这样的情况:某个小交易所的报价比主流交易所便宜了0.2%,但它的订单簿深度只有100手。你一个500手的单子过去,直接就把价格打穿了。最后成交均价反而比主流交易所还贵。

核心思路: 流动性优先不是只看订单簿深度,要看「可吸收量」。我习惯用「订单簿斜率」来衡量——斜率越陡,说明流动性越差。

3.2 订单拆分与聚合

订单拆分和聚合,是路由层的「手术刀」。拆得好,能降低冲击成本;拆不好,反而增加延迟。

3.2.1 订单拆分策略

我常用的拆分策略有三种:

  • 按时间拆分: 把一个大单拆成多个小单,每隔一段时间发一个。适合流动性一般的品种。
  • 按价格区间拆分: 根据订单簿的深度,把单子分配到不同的价格层级。适合流动性好的品种。
  • 按交易所拆分: 把单子分到多个交易所,利用各所的流动性优势。适合跨所套利。

举个例子,一个1000手的买单:

// 伪代码示例
function splitOrder(order, orderBook) {
    let splits = [];
    let remaining = order.quantity; // 1000手
    
    // 按价格区间拆分
    for (let level of orderBook.levels) {
        let take = Math.min(remaining, level.depth);
        splits.push({
            price: level.price,
            quantity: take,
            exchange: level.exchange
        });
        remaining -= take;
        if (remaining <= 0) break;
    }
    
    return splits;
}

这个代码看起来简单,但实际要考虑的东西很多。比如每个交易所的最小交易单位、手续费结构、甚至交易所的故障率。我曾经因为没考虑交易所的故障率,把单子全部分到了同一个交易所,结果那个交易所宕机了...嗯,从那以后我就在拆分逻辑里加了一个「交易所健康度」的权重。

3.2.2 订单聚合策略

聚合和拆分是反过来的。当你有多个小单要发往同一个交易所时,把它们合并成一个单子发出去,能节省网络开销和手续费。

我建议的聚合规则:

  • 相同方向(买/卖)的订单可以聚合
  • 价格差异在0.1%以内的可以聚合
  • 时间窗口在100毫秒内的可以聚合
小技巧: 聚合时要注意「最小交易单位」。比如某个交易所的最小交易单位是10手,那你聚合后的数量必须是10的倍数。我曾经因为没做这个检查,导致聚合后的单子被交易所拒了。

3.3 路由表动态更新

路由表不是写死的。市场在变,交易所的状态在变,你的路由表也得跟着变。

3.3.1 路由表的结构

我习惯用一张表来管理路由信息:

交易所 价格权重 时间权重 流动性权重 健康度 延迟(ms)
交易所A 0.4 0.3 0.3 0.95 10
交易所B 0.3 0.4 0.3 0.88 15
交易所C 0.2 0.3 0.5 0.92 12

这张表里的权重不是固定的。我建议每5分钟重新计算一次,根据最近的市场表现动态调整。

3.3.2 动态更新的触发条件

路由表什么时候更新?我总结了三个触发条件:

  1. 定时更新: 每5分钟全量更新一次。这是兜底策略。
  2. 事件触发: 某个交易所的延迟突然飙升,或者健康度下降,立即更新。
  3. 异常回退: 如果某个交易所连续3次路由失败,自动降低它的权重,甚至暂时剔除。

这里有个细节——更新路由表的时候,不能中断正在进行的路由决策。我自己的做法是用「双缓冲」:一个活跃表,一个备用表。更新时先写备用表,更新完再切换。

3.3.3 路由表的可视化

为了让你更直观地理解路由层的整体逻辑,我画了一张流程图:

智能路由核心流程 订单进入路由层 路由决策引擎 价格优先 | 时间优先 | 流动性优先 订单拆分与聚合 查询路由表 发送到目标交易所 反馈更新 路由表动态更新

这张图展示了路由层的完整流程。注意那个反馈回路——路由决策的结果会反过来影响路由表的更新。这是一个闭环系统。

3.4 实战中的避坑指南

最后,分享几个我在实战中踩过的坑:

  • 不要迷信「最优路由」: 我曾经花了很多精力去追求「理论上的最优路由」,结果发现市场变化太快,等你算出来最优方案,行情已经变了。后来我改用「足够好」策略——只要路由结果在可接受范围内,就立即执行。
  • 注意交易所的「隐藏规则」: 有些交易所对高频交易有额外的限制,比如每秒最多发多少单。我建议在路由表里加上每个交易所的「限流参数」。
  • 做好降级处理: 如果所有交易所都不可用,怎么办?我自己的做法是准备一个「备用路由表」,里面只有流动性最好的那个交易所。虽然可能不是最优,但至少能保证交易不中断。
总结一下: 订单路由层的核心是「在正确的时间,把正确的订单,发到正确的交易所」。价格、时间、流动性三个维度要综合考虑,不能偏废。路由表要动态更新,但更新时不能影响正在进行的路由决策。

嗯,这一章的内容就到这里。路由层设计好了,你的交易系统就成功了一半。剩下的,就是不断根据市场反馈去调优了。


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