三、订单流风控:订单频率限制、撤单率监控、自成交预防

订单流风控,说白了就是管住你的手。

做市商不是普通交易者,我们每秒都在跟市场打交道。高频的订单流里,稍有不慎就会出事。我个人习惯把订单流风控拆成三个维度:频率撤单自成交。这三件事做好了,系统至少能挡住80%的异常交易风险。

3.1 订单频率限制:别让系统“喷”出去

你有没有想过,一个bug能让你的系统在1秒内发出几千笔订单?我遇到过。有一次,我们的定价引擎因为一个浮点数溢出,瞬间往交易所狂轰了2000多笔订单。要不是频率限制挡着,那天就得亏掉半年的利润。

频率限制的核心逻辑很简单:控制单位时间内的订单数量。但具体怎么限,有讲究。

常见的频率限制策略:

  • 全局限流:整个系统每秒最多发N笔订单。适合防止系统级异常。
  • 品种级限流:每个交易对每秒最多发M笔。防止某个品种被恶意刷单。
  • 方向级限流:买单和卖单分别计数。避免单边过度暴露。

我建议用滑动窗口算法来实现频率限制。为什么?因为固定窗口会有“毛刺”问题——比如你设每秒100笔,那在第1秒的最后1毫秒和第2秒的第1毫秒各发100笔,实际是200毫秒内发了200笔,交易所照样会警告你。

// 滑动窗口频率限制(伪代码)
class RateLimiter {
    windowSize = 1000  // 1秒窗口
    maxRequests = 100  // 最大100笔
    timestamps = []    // 时间戳队列

    func allow() -> bool {
        now = currentTimeMillis()
        // 移除窗口外的旧时间戳
        while timestamps.length > 0 
              and timestamps[0] < now - windowSize {
            timestamps.shift()
        }
        // 检查是否超限
        if timestamps.length >= maxRequests {
            return false  // 拒绝
        }
        timestamps.push(now)
        return true  // 允许
    }
}

避坑指南:我曾经把频率限制设得太死,结果行情剧烈波动时,系统因为限流错过了最佳报价窗口,反而亏了钱。频率限制不是越小越好,要根据你的策略吞吐量和交易所规则来调。一般建议留20%-30%的余量。

3.2 撤单率监控:高撤单率是“红灯”

做市商本质上是在“挂单-撤单”的循环里赚钱。但撤单率太高,交易所会找你麻烦。我记得某头部交易所的规则是:如果某个交易对的撤单率连续5分钟超过90%,直接限制你的交易权限。

撤单率监控,我一般分两个层面:

  • 实时撤单率:过去1分钟内的撤单数量 / 订单总数。用于即时预警。
  • 累计撤单率:当天或当小时的撤单率。用于合规报告。

为什么会撤单?说白了就是你的报价被市场“打穿”了,或者你发现价格算错了。但如果你发现撤单率突然飙升,别急着调参数——先查查是不是行情源出了问题。

警告:撤单率监控不能只看平均值。我曾经遇到过一个情况:某个品种的撤单率平均只有60%,但每10秒内会有一波集中撤单,峰值达到95%。交易所的监控系统抓的就是这种“尖峰”。所以,建议同时监控滑动窗口内的峰值撤单率

监控指标 窗口大小 预警阈值 处置动作
实时撤单率 1分钟 > 85% 降低该品种报价频率
峰值撤单率 10秒 > 95% 暂停该品种做市
累计撤单率 1小时 > 80% 人工介入检查策略

3.3 自成交预防:别自己跟自己打架

自成交,就是你的买单吃掉了你自己的卖单。听起来很蠢对吧?但在高频交易里,这事太容易发生了。尤其是多策略、多账户并行的时候,A策略挂卖单,B策略觉得价格合适就吃了——嗯,你亏了手续费,还白忙活一场。

更严重的是,交易所把自成交视为市场操纵行为。我见过有团队因为自成交被交易所罚了50万美金。

自成交预防,我常用的方法有三种:

  1. 订单ID标记法:每个订单带上策略ID和账户ID。在下单前检查对手方是不是自己人。
  2. 价格交叉检查:如果当前最优卖单是你自己的,那你的买单价格不能高于它。说白了就是“别自己抬自己的价”。
  3. 延迟撮合:给自己订单加一个极短的“冷却期”,比如5毫秒内不允许同策略的订单互相成交。
// 自成交预防 - 价格交叉检查示例
class SelfTradePrevention {
    myOrders = {}  // 存储自己的活跃订单

    func canPlaceBuyOrder(price, strategyId) -> bool {
        // 检查所有自己的卖单
        for order in myOrders {
            if order.side == SELL 
               and order.price <= price
               and order.strategyId == strategyId {
                return false  // 会跟自己成交,拒绝
            }
        }
        return true
    }
}

个人经验:我曾经用“订单ID标记法”时踩过一个坑——不同策略的订单ID前缀一样,结果自成交预防没生效。后来我改成“策略ID + 账户ID + 随机数”的三段式ID,才彻底解决。细节决定成败啊。

3.4 订单流风控的整体架构

这三个模块不是孤立的。我习惯把它们串成一条流水线:订单进来,先过频率限制,再过自成交检查,最后统计撤单率。任何一个环节卡住,订单都不能发出去。

下面这张图是我自己项目里用的订单流风控架构,你可以参考一下:

订单流风控架构图 原始订单 模块1:频率限制 滑动窗口算法 | 全局+品种+方向 模块2:自成交预防 ID标记 | 价格交叉检查 | 延迟撮合 模块3:撤单率监控 实时撤单率 | 峰值撤单率 | 累计撤单率 通过 → 发往交易所 拒绝 日志+告警 拒绝 告警

你看,这个架构里每个模块都是独立的,但又串联在一起。任何一个模块拒绝,订单都不会发出去。而且每个模块都有独立的日志和告警,方便事后复盘。

核心要点总结:

  • 频率限制用滑动窗口,别用固定窗口
  • 撤单率要同时监控实时值和峰值
  • 自成交预防用三段式ID + 价格交叉检查
  • 三个模块串联,任何一个拒绝都不发单

订单流风控,说白了就是给系统装上一道“保险丝”。平时看着多余,但真出问题的时候,它能救你一命。我见过太多团队因为省了这几行代码,最后付出惨痛代价。嗯,希望你别成为下一个。


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