4、订单级风控(一):价格偏离度检查、最小/最大手数限制、订单频率限制(Rate Limit)、自成交预防

各位同学,今天我们正式进入订单级风控的实战环节。说实话,这部分内容是我在搭建高频交易系统时,踩坑最多的地方。你想想看,订单是交易系统最末端的执行单元,一旦出问题,轻则亏钱,重则被交易所处罚。所以,订单级风控必须做到「滴水不漏」。

我个人习惯把订单级风控分成四个核心模块:价格偏离度检查手数限制频率限制自成交预防。今天我们先讲前两个,剩下的下节课继续。

4.1 价格偏离度检查

什么叫价格偏离度?说白了,就是防止你的订单价格「飞了」。比如当前市价是100块,你一不小心输了个1000块的买单,那后果不堪设想。

我在项目中遇到过这样的真实案例:某次系统升级后,一个参数配置错误,导致一个算法交易模块的限价单价格直接用了昨收价的10倍。幸好价格偏离度检查在最后一刻拦截了,否则那一单就能亏掉整个部门的季度利润。

核心逻辑: 订单价格与参考价格之间的偏差,必须落在预设的阈值范围内。

参考价格怎么选?我一般用以下几种:

  • 最新成交价:最实时,但容易被异常成交带偏
  • 买一/卖一价:反映当前最优报价,适合做市策略
  • 前收盘价:开盘阶段常用,但盘中偏差会很大
  • VWAP(成交量加权均价):适合大单拆单场景

嗯,这里要注意:不同策略应该使用不同的参考价格。比如高频做市商,我建议用买一/卖一价;而套利策略,用最新成交价更合适。

阈值怎么设?我分享一个经验公式:

// 价格偏离度检查伪代码
double maxDeviation = basePrice * deviationRate;  // deviationRate 通常 0.001 ~ 0.05
if (Math.abs(orderPrice - referencePrice) > maxDeviation) {
    rejectOrder("价格偏离度超限");
}

对于港股市场,我个人习惯把偏离度阈值设在0.5%~2%之间。为什么?因为港股波动大,太严了容易误杀,太松了又没意义。你可以根据历史回测来调整这个参数。

小技巧: 我建议把价格偏离度检查放在风控链的第一位。因为它的计算量最小,执行最快,能快速过滤掉明显异常的订单。

4.2 最小/最大手数限制

这个就更好理解了。港股每手股数不固定,有的股票1手=100股,有的1手=1000股,甚至还有1手=1股的。所以手数限制必须动态适配。

我曾经见过一个新手交易员,把某只1手=5000股的股票,下了100手的买单。结果你猜怎么着?50万股直接砸进去,把盘口打穿了三个价位。这就是没有最大手数限制的后果。

手数限制通常分两层:

限制类型 说明 示例
全局手数限制 所有订单统一的最大/最小手数 最小1手,最大10000手
股票级手数限制 针对特定股票的特殊限制 腾讯控股最大5000手

实现上,我建议用配置表来管理:

// 手数限制配置示例
{
  "global": {
    "minLot": 1,
    "maxLot": 10000
  },
  "stockSpecific": {
    "00700.HK": { "maxLot": 5000 },
    "09988.HK": { "maxLot": 3000 }
  }
}

为什么要有股票级限制?因为不同股票的流动性差异巨大。腾讯一天成交几十亿,你下5000手没问题;但有些仙股一天才成交几百手,你下5000手就是找死。

避坑指南: 我曾经遇到过一个问题——最小手数检查没做。结果系统生成了0手的订单,交易所直接返回错误码,导致整个订单队列卡死。所以,最小手数一定要检查,哪怕只是1手。

4.3 订单频率限制(Rate Limit)

Rate Limit,说白了就是「别发太快」。交易所对订单频率有严格限制,港股联交所的规则是:每个交易席位每秒最多发送50笔订单。超过这个限制,轻则被限流,重则被暂停交易权限。

你想想看,高频交易系统一秒钟能产生几百笔订单,如果不做频率限制,分分钟被交易所封杀。我见过一个团队,因为Rate Limit没做好,整个交易席位被交易所禁用了30分钟,那损失...嗯,不提了。

频率限制的实现方式,我推荐用令牌桶算法

// 令牌桶算法实现Rate Limit
class TokenBucket {
    private int capacity;      // 桶容量
    private int tokens;        // 当前令牌数
    private long lastRefill;   // 上次补充时间
    private int refillRate;    // 每秒补充速率

    public boolean tryAcquire() {
        refill();
        if (tokens > 0) {
            tokens--;
            return true;  // 允许发单
        }
        return false;     // 拒绝发单
    }

    private void refill() {
        long now = System.currentTimeMillis();
        long elapsed = now - lastRefill;
        int newTokens = (int)(elapsed * refillRate / 1000);
        tokens = Math.min(capacity, tokens + newTokens);
        lastRefill = now;
    }
}

我个人习惯把Rate Limit分成三个层级:

  • 账户级:整个交易账户的总频率限制
  • 策略级:每个策略模块的频率限制
  • 股票级:针对单只股票的频率限制

为什么要分三层?因为不同策略的订单频率差异很大。做市策略可能每秒发几十笔,而套利策略可能几秒才发一笔。统一限制会严重影响做市策略的性能。

经验之谈: 我建议把Rate Limit的阈值设为交易所限制的80%。比如交易所允许每秒50笔,你就设40笔。留出20%的缓冲,防止突发流量导致超限。

4.4 自成交预防

自成交,就是自己的买单和卖单互相成交了。这在交易所看来是「市场操纵行为」,轻则警告,重则罚款。港股联交所对自成交的处罚力度很大,我听说有机构因为自成交被罚了几百万港币。

自成交预防的核心思路就一句话:自己的订单之间不能互相撮合

实现方式主要有两种:

方式 原理 优缺点
订单标记法 给每个订单打上机构ID,交易所撮合时跳过同ID订单 简单可靠,但需要交易所支持
本地拦截法 在发单前检查是否与已有订单反向 灵活可控,但实现复杂

港股市场支持订单标记法,我强烈建议使用这个方式。但如果你做的是跨市场套利,可能需要本地拦截法作为补充。

本地拦截法的核心逻辑:

// 自成交预防 - 本地拦截
boolean checkSelfTrade(Order newOrder) {
    for (Order existingOrder : activeOrders) {
        // 检查是否同方向
        if (newOrder.side == existingOrder.side) continue;
        
        // 检查价格是否交叉
        if (newOrder.side == BUY && newOrder.price >= existingOrder.price) {
            return true;  // 可能自成交
        }
        if (newOrder.side == SELL && newOrder.price <= existingOrder.price) {
            return true;  // 可能自成交
        }
    }
    return false;
}
避坑指南: 我曾经在自成交预防上犯过一个低级错误——只检查了限价单,没检查市价单。结果市价单直接和已有的限价单成交了。所以,所有订单类型都要检查,包括市价单、冰山单等。

好了,今天的内容就到这里。这四个模块是订单级风控的基石,缺一不可。下节课我们继续讲剩下的几个风控模块,包括订单数量限制、重复订单检测等。


专注资料整理