4、信号处理层:信号生成与过滤、信号优先级队列、信号合并与冲突解决、信号延迟控制
信号处理层,说白了就是交易系统的「大脑皮层」。
策略层算出了买卖信号,但能不能用、什么时候用、多个信号打架怎么办——这些脏活累活,全得靠这一层来搞定。我见过太多团队,策略写得漂漂亮亮,一到信号处理就翻车。嗯,这里面的坑,我踩过不少。
4.1 信号生成与过滤
信号生成不是简单的「金叉买入、死叉卖出」。真正的生产环境里,信号源可能来自十几个策略模块,每个模块每秒能吐出上千条信号。
信号过滤,我习惯分三步走:
- 基础校验:信号格式对不对?时间戳是否合法?价格是否在合理区间?
- 逻辑过滤:比如同一品种在10ms内连续出现同向信号,只保留第一个。
- 风控过滤:当前仓位是否允许开仓?账户资金够不够?
核心原则:过滤要快,但不能漏。我一般把基础校验放在信号生成线程里做,逻辑过滤和风控过滤交给独立的过滤管道。
举个例子,我曾经遇到过一个高频策略,每秒生成2000+信号。如果不做过滤,下游的订单执行系统直接被打爆。后来我们加了一层「信号去重+时间窗口聚合」,吞吐量降到了200条/秒,但有效信号一个没丢。
4.2 信号优先级队列
信号不是平等的。有些信号来自主力策略,有些只是辅助参考。怎么排队?
我常用的优先级策略:
| 优先级 | 信号来源 | 处理策略 |
|---|---|---|
| P0 | 风控信号、止损信号 | 立即执行,不排队 |
| P1 | 主力策略信号 | 进入高优队列,延迟<1ms |
| P2 | 辅助策略信号 | 进入普通队列,延迟<10ms |
| P3 | 统计/回测信号 | 低优队列,可丢弃 |
这里有个细节:优先级队列不能阻塞。P0信号来了,如果P1队列正在处理,必须能抢占。我一般用「多级队列+线程池隔离」来实现——每个优先级有自己的线程池,P0用独立线程,永远不排队。
避坑指南:我曾经把P0和P1放在同一个线程池里,结果有一次P1信号暴增,P0信号被堵了2ms。2ms啊,在高频交易里足够让一笔止损单变成废单。从那以后,P0永远独占一个线程。
4.3 信号合并与冲突解决
多个策略对同一个品种发出相反信号,怎么办?
你想想看,A策略说「买入」,B策略说「卖出」,系统听谁的?
我总结了三层冲突解决机制:
- 时间优先:谁先到听谁的。适合同级别策略。
- 权重优先:每个策略有历史胜率权重,权重高的说了算。
- 投票机制:多个策略投票,少数服从多数。适合信号源较多的场景。
实际项目中,我更喜欢「权重优先+时间戳兜底」的组合。举个例子,策略A胜率65%,策略B胜率55%,两者冲突时默认听A的。但如果A的信号比B晚了100ms以上,说明市场已经变了,这时候听B的。
注意:信号合并不是简单的「取平均」。我见过有人把多个策略的买卖信号做加权平均,结果搞出一个「半买半卖」的尴尬信号。记住,交易信号是离散的,要么买、要么卖、要么不动。
4.4 信号延迟控制
延迟是量化交易的天敌。但「零延迟」是不存在的,我们只能控制延迟在可接受范围内。
延迟控制的核心手段:
- 零拷贝传输:信号在内存中传递,不走网络。我用过共享内存+无锁队列,延迟能压到微秒级。
- 批量处理:把多个信号打包成一批,减少上下文切换。但要注意,批量不能太大,否则单个信号延迟会飙升。
- 延迟预算:给每个处理环节分配固定的时间预算。比如信号过滤最多花50μs,优先级排队最多花20μs。
我个人习惯在系统启动时做一次「延迟热力图」,把每个环节的延迟分布画出来。哪里是瓶颈,一目了然。
一个真实案例:有一次我发现信号从生成到执行平均延迟2ms,但偶尔会跳到50ms。排查后发现是垃圾回收(GC)导致的。解决方案?把信号处理线程设为「实时优先级」,并禁用GC。嗯,C++在这时候就比Java香多了。
4.5 整体架构图
下面这张图,是我个人比较推荐的信号处理层架构。它把上面说的四个模块串在了一起:
这张图里,信号从顶部流入,经过四层处理后,从底部输出到订单执行层。每一层都有明确的职责和边界。我习惯在每一层之间加一个「环形缓冲区」,这样即使某一层暂时卡住,也不会影响上下游。
一个小技巧:在每层处理完后,打一个时间戳。这样出了问题,直接看时间戳就能定位到是哪一层拖了后腿。我管这叫「信号护照」——每个信号带着自己的处理记录走完全程。
好了,信号处理层就聊到这儿。下一层我们讲订单执行层,那才是真正「扣动扳机」的地方。