路由策略引擎:智能路由算法的核心设计

各位同学,今天我们来聊聊路由策略引擎。说白了,这就是订单路由系统的「大脑」。

我在做第一版路由引擎时,犯过一个低级错误——把所有逻辑写死在代码里。结果呢?每次改策略都要重新部署,运维同学差点拿键盘砸我。嗯,从那以后,我彻底转向了策略可配置、可热加载的架构。

这一章,我会把智能路由算法的三种核心模式、策略热加载的实现套路,以及路由规则DSL的设计思路,一次性讲透。

一、智能路由算法的三种模式

路由算法不是玄学,它本质上是一个「打分排序」的过程。每个目标市场,我们给它算一个综合分,然后选最高的那个。

常见的打分维度有三个:价格、流动性、成本。我习惯把它们叫做「铁三角」。

1. 价格优先算法

这个最简单,也最直接。谁的报价最优,单子就往哪送。

举个例子:

// 伪代码:价格优先路由
function routeByPrice(order) {
    let bestMarket = null;
    let bestPrice = order.side === 'BUY' ? Infinity : -Infinity;
    
    for (market in availableMarkets) {
        let currentPrice = market.getQuote(order.symbol);
        // 买盘选最低价,卖盘选最高价
        if (order.side === 'BUY' && currentPrice < bestPrice) {
            bestPrice = currentPrice;
            bestMarket = market;
        }
    }
    return bestMarket;
}

但这里有个坑。我曾经遇到过一个场景:某市场报价确实最优,但深度极差,100手的单子进去,成交价直接滑了3个tick。所以纯价格优先,在实盘中往往不够用。

2. 流动性优先算法

流动性优先,说白了就是「谁家池子深,我就去哪」。这个在机构订单中特别重要。

我一般用「订单簿深度」作为核心指标。具体做法是:

  • 计算目标价位附近(比如±5档)的累计挂单量
  • 评估订单的「可成交比例」——即在不产生明显滑点的情况下,能成交多少
  • 给深度好的市场加权加分

关键指标:流动性评分 = 累计深度 / 订单规模

评分越高,说明这个市场越能「吃下」你的单子。

我记得有一次做美股路由,某家交易所报价比别家好0.01美元,但深度只有对手的十分之一。我们选了流动性优先,结果大单成交后,实际成交均价反而比价格优先策略低了0.03美元。这就是「看起来便宜,实际更贵」的典型案例。

3. 成本优先算法

成本优先,考虑的是「到手净值」。包括:

  • 交易手续费(maker/taker费率)
  • 结算费用
  • 汇率转换成本(如果是跨境市场)
  • 潜在的滑点成本

我习惯把成本模型做成一个可配置的公式:

总成本 = 显性成本(手续费+税费) + 隐性成本(滑点预估+延迟成本)

你想想看,有些市场虽然手续费低,但网络延迟高,行情慢半拍,实际成交价往往吃亏。所以成本优先,一定要把隐性成本算进去。

二、策略热加载与动态配置

这一块,是我踩坑最多的部分。

早期系统,每次改路由策略都要重启服务。重启意味着断连、重连、丢单。有一次因为策略调整,导致30秒内所有订单堆积,差点把上游交易所的限频打爆。

从那以后,我定了一个铁律:路由策略必须支持热加载,零停机切换。

实现方案:配置中心 + 策略工厂

我的做法是这样的:

  1. 所有路由策略的配置参数,统一放在配置中心(比如etcd或Nacos)
  2. 系统启动时,从配置中心拉取最新策略
  3. 配置变更时,通过监听机制实时推送
  4. 策略工厂根据新配置,动态创建新的路由实例
  5. 新旧实例平滑切换,使用「双缓冲」模式

小技巧:热加载时,不要立即销毁旧实例。让旧实例继续处理已经在途的订单,新订单走新实例。等旧实例的待处理队列清空后,再优雅关闭。这个模式我称之为「优雅过渡」。

代码层面,核心逻辑大概长这样:

// 策略工厂:支持热加载
class RoutingStrategyFactory {
    private Map<String, RoutingStrategy> strategyCache;
    
    public void reloadStrategy(String market, String configJson) {
        // 解析新配置
        RoutingConfig newConfig = parseConfig(configJson);
        // 创建新策略实例
        RoutingStrategy newStrategy = createStrategy(newConfig);
        // 放入缓存,但不立即切换
        strategyCache.put(market + "_new", newStrategy);
        // 触发切换信号
        triggerSwitch(market);
    }
    
    private void triggerSwitch(String market) {
        // 原子切换:将新策略标记为活跃
        // 旧策略继续处理在途订单
        // 等旧策略的引用计数归零后,自动回收
    }
}

三、路由规则DSL设计

DSL,领域特定语言。说白了,就是让业务人员也能看懂、能修改的路由规则。

我见过太多系统,路由规则写在XML里,又臭又长。后来我设计了一套简洁的DSL,用类似自然语言的方式描述规则。

DSL语法示例

// 路由规则DSL示例
rule "主力合约路由" {
    when
        order.symbol == "BTC-USDT" 
        && order.quantity > 10
    then
        route to "Binance" priority 0.6
        route to "OKX" priority 0.3
        route to "Bybit" priority 0.1
}

rule "小单成本优先" {
    when
        order.quantity <= 1
    then
        route by "cost" 
        select top 1
}

这个DSL的设计原则有三个:

  • 可读性优先:业务人员一看就懂,不需要懂编程
  • 可组合:多个规则可以叠加,按优先级执行
  • 可测试:每条规则都可以单独做单元测试

注意:DSL解析器一定要做沙箱隔离。我曾经见过有人通过DSL注入了恶意代码,导致路由引擎崩溃。解析时,只允许访问预定义的函数和变量,禁止执行任意代码。

核心知识体系图

下面这张图,是我梳理的路由策略引擎整体架构。你可以把它当作本章的「地图」:

路由策略引擎核心架构 订单输入 路由策略引擎 价格优先 | 流动性优先 | 成本优先 DSL规则匹配器 目标市场选择 订单执行与监控 配置中心 热加载支持 DSL规则仓库 版本化管理 图例 输入/输出 核心引擎 规则匹配

总结一下

路由策略引擎,核心就三件事:

  • 算法选型:价格、流动性、成本,三者之间做权衡。没有银弹,只有最适合你业务场景的组合。
  • 热加载能力:这是生产系统的底线。不能因为改策略就停机,那是不可接受的。
  • DSL设计:让规则变得可读、可配、可测试。把「懂业务的人」和「懂技术的人」之间的鸿沟填平。

我个人习惯,在搭建路由引擎时,会先画一张类似上面的架构图。把每个模块的职责、数据流向、配置来源都标清楚。这样写代码时,思路会清晰很多。

下一节,我们会深入DSL解析器的实现细节。不过在那之前,建议你先动手画一下自己系统的路由策略架构图。嗯,画图的过程,往往比写代码更能发现问题。