3. 行情数据接入层设计:交易所行情协议解析、多数据源聚合、行情去重与对齐、延迟优化策略
做市系统里,行情接入层是真正的「前线」。
我见过太多团队,策略模型跑得飞起,结果行情一进来就卡住。说白了,行情数据如果接不好,后面全是白搭。今天咱们就聊聊这块怎么设计。
3.1 交易所行情协议解析
每家交易所都有自己的「方言」。上交所用STEP,深交所用SZV2,中金所用FAST,还有CTP、XTP……你得给每套协议配一个翻译官。
核心原则:协议解析层必须独立,不能跟业务逻辑混在一起。
我个人习惯的做法是:
- 每个协议写一个独立的Decoder模块
- Decoder只负责把二进制流变成统一的内存对象
- 统一对象叫
MarketDataPacket,包含时间戳、合约代码、买卖盘口、最新价等字段
// 伪代码示例:统一行情对象
struct MarketDataPacket {
uint64_t timestamp_ns; // 纳秒时间戳
char symbol[32]; // 合约代码
double last_price; // 最新价
double bid_prices[10]; // 买盘价格
double ask_prices[10]; // 卖盘价格
uint32_t bid_volumes[10];// 买盘量
uint32_t ask_volumes[10];// 卖盘量
uint8_t source_id; // 数据源标识
};
嗯,这里要注意:时间戳一定要用纳秒。我在项目中遇到过,有些交易所给的是微秒,有些是毫秒,统一转成纳秒后做对齐会省很多麻烦。
3.2 多数据源聚合
做市商一般不会只接一个数据源。你想想看,万一交易所主线路断了怎么办?所以通常会同时接两路甚至三路行情。
多数据源聚合,说白了就是「谁先到用谁,谁准用谁」。
我的经验:不要搞复杂的投票算法。简单点,主备模式就够了。主数据源正常时就用它,主数据源异常时切到备用。切换逻辑要快,毫秒级。
我曾经踩过一个坑:两个数据源同时推送,我搞了个加权平均。结果行情波动大的时候,加权出来的价格两边都不对,策略直接亏钱。后来改成「主备+心跳检测」,稳多了。
聚合层的设计要点:
- 每个数据源独立线程接收,互不阻塞
- 维护一个数据源状态表,记录延迟、丢包率、序列号连续性
- 状态异常时自动切换,并发出告警
3.3 行情去重与对齐
多数据源必然带来重复数据。同一个tick,可能从A路和B路各来一次。如果不做去重,策略会以为市场变了两次,实际上只变了一次。
去重的关键是什么?
序列号 + 时间戳 + 价格快照。
我建议的做法:
- 每个数据源维护一个最近N条记录的环形缓冲区
- 新数据进来时,用(序列号, 数据源ID)做哈希,查重
- 如果重复,直接丢弃
- 如果不重复,再检查时间戳是否乱序
注意:行情对齐不是简单的时间戳相等。不同数据源的网络延迟不同,同一个tick可能差几毫秒到。对齐时要用「最近邻匹配」——找时间戳最接近的那条,而不是完全相等的那条。
我做过一个测试:用严格相等做对齐,丢包率高达3%。改用最近邻匹配后,丢包率降到0.1%以下。差距非常大。
3.4 延迟优化策略
做市对延迟极其敏感。你比别人慢1微秒,可能就抢不到单了。
延迟优化的核心思路就四个字:减少拷贝。
我常用的优化手段:
| 优化手段 | 说明 | 效果 |
|---|---|---|
| 零拷贝技术 | 网卡数据直接映射到用户态,绕过内核 | 延迟降低40%-60% |
| 内存池 | 预分配行情对象,避免频繁new/delete | 延迟抖动减少80% |
| CPU亲和性 | 行情线程绑定到特定CPU核心 | 缓存命中率提升30% |
| 无锁队列 | 用RingBuffer替代互斥锁 | 吞吐量提升5-10倍 |
举个例子,零拷贝这块。传统做法是:网卡→内核缓冲区→用户态缓冲区→应用。每次拷贝都有开销。用DPDK或者Solarflare的OpenOnload,可以直接把数据从网卡送到应用内存,中间不经过内核。嗯,这个配置起来有点麻烦,但效果是真的好。
一个小技巧:行情线程的优先级要设高,但不能最高。我曾经把行情线程设成实时优先级,结果跟系统关键线程抢CPU,直接把机器搞挂了。设成高优先级但留一点余量,比较稳妥。
3.5 整体架构图
下面这张图是我个人比较推荐的行情接入层架构。你看一眼就明白了。
从下往上看:数据源进来,经过协议解析变成统一格式,再到聚合层做去重和对齐,最后经过延迟优化层送给策略。每一层各司其职,互不干扰。
3.6 避坑指南
最后分享几个我踩过的坑:
- 我曾经以为行情线程越多越好。结果CPU上下文切换开销比处理行情本身还大。后来发现,2-3个行情线程就够了,多了反而坏事。
- 我曾经忽略过内存对齐。行情对象里的字段没按64字节缓存行对齐,导致多核访问时频繁缓存失效。改了对齐后,性能提升了15%。
- 我曾经在行情处理里打了太多日志。一次行情波动,日志直接写爆了磁盘。现在我的做法是:正常行情不记日志,异常情况才记,而且用异步日志。
行情接入层看起来简单,但细节非常多。你想想看,每天几百万笔行情,每一笔都要在微秒级处理完,任何一个环节出问题都会放大。把基础打牢,后面才能跑得快。
一句话总结:行情接入层的设计目标就是「快、稳、准」。快靠零拷贝和无锁队列,稳靠主备切换和心跳检测,准靠去重和对齐。三者缺一不可。
公众号:蓝海资料掘金营,微信deep3321