FinTechSep 8, 2026阅读时长8分钟

订单路径内部的交易前风控检查

交易前风控风险管理交易基础设施订单管理系统
图片加载失败

持仓限额、保证金、fat-finger 边界和 kill switch,都必须在订单离开网关之前对每一笔给出答复。本文讲这些检查如何住在订单路径内部而不是旁边:哪些状态放在内存里、哪些做增量重算、重启之后会发生什么。先从约束讲起。

一笔订单到达网关。在它发往交易场所之前,必须有什么来判定这个账户是否被允许发出它。这个判定对每一笔订单都要跑,包括绝大多数完全正常的订单,所以它的成本由全部正常流量承担,而不只由被拒的那些承担。

这是首先要点明的约束。放进订单路径的风控检查,是对下单征收的一道税。工程上的问题不是如何让检查变聪明,而是如何让它小到交易者感觉不到,同时诚实到该拒的订单照样拒。

订单路径里究竟该放什么

热路径只回答一个问题:以我们当前对这个账户的了解,这笔订单现在可以发出去吗。回答这个问题的检查留在里面。回答另一个问题的检查搬到外面。

  • 持仓与敞口限额——成交后在该合约、该分组和该账户上的持仓,与配置好的边界比对
  • 保证金或购买力——在当前保证金模型下,账户是否还容得下这笔订单
  • fat-finger 边界——订单数量、名义金额,以及与参考价之间的价差,赶在交易场所之前抓住手误
  • 合约与账户状态——交易暂停、账户受限、只可平仓、该账户未开通此产品
  • kill switch 状态——一个凌驾于上述一切之上的标志
  • 在交易场所不提供时,自建重复单与自成交防护

其余一切都跑在路径旁边,用同一份状态,不扣住订单。它为热路径所执行的限额提供依据,但不横在交易者与交易场所之间。

  • 组合风险分析——情景推演、压力测试、跨账户的相关性敞口
  • 参数变更时的保证金模型重估,以及任何触及整本账本的重算
  • 监控与模式识别,它需要热路径刻意不携带的历史数据
  • 报表、对账,以及任何要与数据库或外部服务打交道的事情
  • 授信与交易对手审查,它们本质上就按更慢的节奏运行

分界线是一个问题,而不是一个类别。在路径内:这笔订单可以发出去吗。在路径旁:限额应该是多少。任何回答第二个问题、却仍然扣住订单的东西,都是设计错误,无论这项检查多重要。

持仓状态住在哪里,以及数据库为什么不在路径上

交易前检查所需的状态——当前持仓、在途订单、已用与可用保证金、限额配置——就住在做出判定的那个进程的内存里。不是在数据库前面的缓存里,也不是在一次网络调用的后面。就在进程里。

原因不只是速度,尽管一次查询比在本地数组里查一下要贵好几个数量级。原因是正确性。数据库保存的是写入那一刻的持仓。风控检查需要的持仓,要把刚刚发出、尚未成交、尚未确认、也尚未落盘的订单算进去。如果你从存储里读,你比对的就是一段已经被自己的订单流甩在身后的过去。

落到实处,这会像塑造任何低延迟组件那样塑造这个进程:

  • 每个账户只有一个写入者。账户按分片分布到各风控实例上,使账户状态永不发生争用,订单路径上也不加锁
  • 扁平、预分配的结构——按账户与合约 id 索引的定长数组,在会话开始时就解析完成,而不是对每笔订单现拼字符串做哈希查找
  • 判定路径上不做分配、不做 I/O,也没有会阻塞的日志写入;审计记录通过队列交给另一个线程
  • 无需重启即可变更的配置,以整份不可变快照的方式换入,这样检查绝不会读到只更新了一半的限额

数据库是持仓被记录的地方,不是持仓被知晓的地方。

增量重算,而不是全量遍历

完整重算账户的敞口和保证金,要遍历每一个持仓和每一笔在途订单。这个成本随账本规模增长,也就意味着交易最多的客户反而要承受更慢的风控检查。所以热路径不做重算,它只应用增量。

账户上带着一组滚动聚合值——按合约和按分组的净敞口与总敞口、已用保证金、在途名义金额。一笔来单对这些聚合值产生一个小的改动,改动后的值与限额比对,订单据此被接受或拒绝。工作量与订单成正比,而不是与组合成正比。

  • 发单时,按订单的最坏影响在聚合量上做预留,因此两笔在途订单无法同时挤进同一份剩余额度
  • 拒单、撤单或过期时,预留被释放;成交时,预留被已实现的持仓变化替换
  • 部分成交要一步调整其中的双方,而这类引擎的多数缺陷,恰恰就住在这里
  • 轧差与分组规则在加载合约时就已解析,而不是每笔订单解析一次,所以增量只是几次算术运算

完整重算依然存在——按计划执行、在保证金参数变更时执行,以及作为对增量结果的周期性自检。它跑在路径之外、跑在副本上,其结果要么被换入,要么被作为差异上报。悄悄偏离真实状态的增量状态,比根本不做检查更糟,所以这项比对不是可选项。

在我们搭建的风控路径上实测,交易前检查本身在 <1 ms 内完成。这个数字覆盖的是基于内存状态的判定,而不是订单从客户端到交易场所再返回的完整行程。

kill switch 是一条独立路径

kill switch 恰恰是在事情已经出错时才会用到。这就排除了把它建在可能本身正出问题的那套机制之上。它是一条独立的路径,有自己的规则。

  • 它是一个原子标志,在检查最开头、在触碰持仓状态、保证金或合约数据之前读取——所以即便那些数据陈旧、缺失或损坏,它照样有效
  • 它由几个相互独立的触发源置位:操作员动作、自动化条件,以及风控状态所依赖的行情或成交回报流中断
  • 它以关闭态失败。若风控进程无法确认自己持有有效状态,网关就表现得像开关已被启用
  • 它有作用范围——整家公司、某个交易台、某个账户、某个策略——因为只能一刀切停全部的开关,用起来总是太晚
  • 启用它只需一个动作加一次确认,而不是一次配置发布;解除则是审慎的,并且始终留痕

拦住新订单是容易的那一半。更难的一半,是开关对已经挂在交易场所的订单做什么:在发单路径被禁用的同时,仍要能撤回报价、撤销在途订单。那条撤单路径值得单独测试,因为用到它的是最糟的一天,而不是平常的一天。

重启,以及状态如何回来

内存状态是持久记录的派生视图。这正是重启得以幸存的原因。每一个改变风控状态的事件——接受一笔订单、释放一笔预留、应用一次成交、变更一项限额、启用开关——都在被下游处理之前,追加写入本机的日志。

  • 启动时,进程回放日志重建聚合量,然后就持仓和在途订单,与交易场所及清算的 drop copy 做对账
  • 对账完成之前,账户不开放交易。一个还在弄清持仓就接受订单的风控引擎,不是风控引擎
  • 回放出来的状态与交易场所的视图不一致时,该账户被停下并触发告警;绝不通过悄悄采信其中一方来化解
  • 热备节点跟随同一份日志,因此故障切换恢复的是热状态而不是冷回放;备节点靠定期真正升主来验证,而不是停留在理论上

于是恢复时间取决于日志长度和 drop copy 是否可用,而不取决于账本规模;而每一个未知项的失败模式都一样:拒绝让这个账户交易。

这套设计不能给你什么

这些实打实的局限值得直说,因为它们决定这套架构究竟合不合适:

  • 检查的正确性,上限就是成交回报流的正确性。若 drop copy 或成交回报滞后,敞口就会被低估,正确的应对是降级到保守限额或启用开关,而不是继续在陈旧状态上交易
  • 真正不可加的组合保证金模型,难以做增量计算。可行的做法是:路径上用保守的增量上界,路径之外跑完整模型;代价是有些订单会被拒,而完整模型本会放行
  • 这个开关防的是你自己的订单流,不是市场。它无法阻止跳空,也无法阻止你已持有仓位上的滑点
  • 进程内状态意味着风控引擎与订单网关共命运。这买来了延迟,代价是失去分别扩容的能力
  • 按账户做单写者分片,让跨账户限额更难实现,全公司范围的检查需要一层更慢的聚合,而它自带数据陈旧问题
  • 比起基于数据库的检查,它的运维工作更多:日志、对账、备机升主演练。如果下单环节对延迟不敏感,这份复杂度不值得买

amBrain 为经纪商和自营交易公司搭建这类交易前风控路径,热路径用 Rust 编写。团队自 2019 年起在亚美尼亚埃里温做交易基础设施。

需要我们帮你落地吗?

我们的工程团队专注于FinTech方向的解决方案。聊聊怎么把你的项目做出来。

相关文章

图片加载失败
FinTech
Sep 8, 2026阅读需 9 分钟

用 Rust 设计 matching engine:没有 GC 停顿的 price-time priority

阅读全文
图片加载失败
FinTech
Apr 27, 2026阅读时长18分钟

2026交易基础设施报告:哈萨克斯坦、乌兹别克斯坦、亚美尼亚、格鲁吉亚

阅读全文
图片加载失败
FinTech
Mar 14, 2026阅读时长12分钟

毫秒为何重要:交易平台延迟简明指南

阅读全文