路由策略引擎:智能路由算法的核心设计
各位同学,今天我们来聊聊路由策略引擎。说白了,这就是订单路由系统的「大脑」。
我在做第一版路由引擎时,犯过一个低级错误——把所有逻辑写死在代码里。结果呢?每次改策略都要重新部署,运维同学差点拿键盘砸我。嗯,从那以后,我彻底转向了策略可配置、可热加载的架构。
这一章,我会把智能路由算法的三种核心模式、策略热加载的实现套路,以及路由规则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秒内所有订单堆积,差点把上游交易所的限频打爆。
从那以后,我定了一个铁律:路由策略必须支持热加载,零停机切换。
实现方案:配置中心 + 策略工厂
我的做法是这样的:
- 所有路由策略的配置参数,统一放在配置中心(比如etcd或Nacos)
- 系统启动时,从配置中心拉取最新策略
- 配置变更时,通过监听机制实时推送
- 策略工厂根据新配置,动态创建新的路由实例
- 新旧实例平滑切换,使用「双缓冲」模式
小技巧:热加载时,不要立即销毁旧实例。让旧实例继续处理已经在途的订单,新订单走新实例。等旧实例的待处理队列清空后,再优雅关闭。这个模式我称之为「优雅过渡」。
代码层面,核心逻辑大概长这样:
// 策略工厂:支持热加载
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解析器的实现细节。不过在那之前,建议你先动手画一下自己系统的路由策略架构图。嗯,画图的过程,往往比写代码更能发现问题。