三、订单流风控:订单频率限制、撤单率监控、自成交预防
订单流风控,说白了就是管住你的手。
做市商不是普通交易者,我们每秒都在跟市场打交道。高频的订单流里,稍有不慎就会出事。我个人习惯把订单流风控拆成三个维度:频率、撤单、自成交。这三件事做好了,系统至少能挡住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万美金。
自成交预防,我常用的方法有三种:
- 订单ID标记法:每个订单带上策略ID和账户ID。在下单前检查对手方是不是自己人。
- 价格交叉检查:如果当前最优卖单是你自己的,那你的买单价格不能高于它。说白了就是“别自己抬自己的价”。
- 延迟撮合:给自己订单加一个极短的“冷却期”,比如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 订单流风控的整体架构
这三个模块不是孤立的。我习惯把它们串成一条流水线:订单进来,先过频率限制,再过自成交检查,最后统计撤单率。任何一个环节卡住,订单都不能发出去。
下面这张图是我自己项目里用的订单流风控架构,你可以参考一下:
你看,这个架构里每个模块都是独立的,但又串联在一起。任何一个模块拒绝,订单都不会发出去。而且每个模块都有独立的日志和告警,方便事后复盘。
核心要点总结:
- 频率限制用滑动窗口,别用固定窗口
- 撤单率要同时监控实时值和峰值
- 自成交预防用三段式ID + 价格交叉检查
- 三个模块串联,任何一个拒绝都不发单
订单流风控,说白了就是给系统装上一道“保险丝”。平时看着多余,但真出问题的时候,它能救你一命。我见过太多团队因为省了这几行代码,最后付出惨痛代价。嗯,希望你别成为下一个。
公众号:蓝海资料掘金营,微信deep3321