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手就是找死。
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分成三个层级:
- 账户级:整个交易账户的总频率限制
- 策略级:每个策略模块的频率限制
- 股票级:针对单只股票的频率限制
为什么要分三层?因为不同策略的订单频率差异很大。做市策略可能每秒发几十笔,而套利策略可能几秒才发一笔。统一限制会严重影响做市策略的性能。
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;
}
好了,今天的内容就到这里。这四个模块是订单级风控的基石,缺一不可。下节课我们继续讲剩下的几个风控模块,包括订单数量限制、重复订单检测等。