第1章:行情数据接入层设计

行情数据接入层,说白了就是量化交易系统的「耳朵」和「眼睛」。

我做了这么多年量化系统,见过太多团队在行情接入上栽跟头。有的数据延迟高,有的丢包严重,还有的因为协议解析不对直接崩了。嗯,今天我们就来聊聊这块到底该怎么设计。

1.1 交易所协议解析

每家交易所都有自己的「方言」。上交所用STEP协议,深交所用SZSE MDDP,期货交易所用CTP。你想想看,如果系统要同时接入多个市场,协议解析这块就是个硬骨头。

我个人习惯的做法是:抽象一层统一的行情数据模型。不管底层是什么协议,解析完后都转成内部标准格式。

核心思路:协议解析层只做一件事——把二进制流变成结构体。

// 内部统一行情结构体示例
struct MarketData {
    uint64_t timestamp;    // 纳秒级时间戳
    char symbol[32];       // 合约代码
    double last_price;     // 最新价
    uint64_t volume;       // 成交量
    double bid_price[5];   // 买一到买五
    double ask_price[5];   // 卖一到卖五
    uint32_t bid_vol[5];
    uint32_t ask_vol[5];
};

我在项目中遇到过一个问题:某家交易所的行情快照和逐笔成交是分开推送的。如果直接按原始协议处理,很容易出现价格和成交量对不上的情况。后来我们加了个「时间窗口对齐」的逻辑,才彻底解决。

避坑指南:我曾经因为没处理好协议中的「心跳包」,导致系统误判交易所断连,触发了自动切换。后来加了个心跳超时计数器,连续3次无响应才切换。

1.2 行情网关架构

行情网关,你可以把它理解成一个「翻译官+快递员」。它负责从交易所拉数据,翻译成内部格式,再分发给各个业务模块。

我推荐用事件驱动+异步IO的架构。为什么?因为行情数据是流式的,用同步阻塞的方式处理,延迟会高得离谱。

交易所A 交易所B 交易所C 协议解析 STEP/SZSE/CTP 去重聚合 时间戳+序列号 分发 协议解析层 去重聚合层 分发层

上图展示的就是行情网关的经典三层架构。每一层各司其职,互不干扰。

注意:网关层一定要做「熔断保护」。如果某个交易所的行情突然异常(比如数据量暴增),要能自动切断连接,防止拖垮整个系统。

1.3 多数据源聚合与去重

做量化交易的朋友都知道,同一个合约可能从多个数据源收到行情。比如CTP和飞马都推送螺纹钢的数据。这时候就面临两个问题:

  • 去重:同一笔成交,不能重复计算
  • 聚合:不同数据源的数据,要合并成一条完整记录

我常用的去重策略是「时间戳+交易所序列号」双字段校验。为什么不用单一字段?因为不同交易所的时间戳精度不一样,有的到毫秒,有的到微秒。加上序列号就能唯一确定一笔数据。

// 去重逻辑伪代码
bool is_duplicate(MarketData& data) {
    string key = data.symbol + "_" + 
                 to_string(data.timestamp) + "_" + 
                 to_string(data.exchange_seq);
    
    if (seen_keys.find(key) != seen_keys.end()) {
        return true;  // 已处理过
    }
    seen_keys.insert(key);
    return false;
}

经验之谈:我曾经遇到过一个坑——某家交易所的序列号会定期重置。后来我们加了个「时间窗口」机制,只保留最近5秒的序列号缓存,完美解决。

聚合的逻辑相对简单:以最快的数据源为主,用其他数据源做补充。比如A源推送了最新价,B源推送了成交量,那就把两者合并。

1.4 低延迟数据分发

数据解析完了,去重聚合也做了,接下来就是分发给各个业务模块。这里有个关键问题:怎么保证延迟最低?

我推荐用共享内存+无锁队列的方案。为什么不用TCP?因为TCP有协议栈开销,延迟至少多几十微秒。在量化交易里,这几十微秒可能就是几百万的盈亏。

分发方式 延迟(微秒) 适用场景
共享内存+无锁队列 < 1 同机进程间通信
Unix Domain Socket 2-5 同机进程间通信
TCP Socket 10-50 跨机通信
RDMA < 1 跨机通信(需硬件支持)

你看这个表格就明白了。同机通信,共享内存是首选。跨机通信,如果预算够,上RDMA;预算有限,用TCP也凑合。

核心原则:数据分发路径上,每多一次拷贝,就多一份延迟。尽量用「零拷贝」技术。

嗯,说到零拷贝,我记得有一次优化行情分发,把数据从内核态拷贝到用户态这一步就花了3微秒。后来用mmap映射共享内存,直接省掉了拷贝,延迟降到了0.5微秒以下。

最后提一句:分发要支持「订阅模式」。不是所有模块都需要全部行情。比如套利模块只需要价差合约,CTA模块只需要主力合约。按需订阅,能大幅降低系统负载。


专注资料整理