行情数据源接入:交易所数据源与接入协议

做行情系统,第一步就是搞定数据源。这活儿看着简单,其实坑不少。我这些年接过的交易所少说也有十几个,沪深、期货、外盘,每家脾气都不一样。今天咱们就聊聊这个。

一、交易所数据源分类

先分个类。你想想看,全球交易所那么多,但数据源类型其实就三大类:

  • 沪深交易所:上交所、深交所,还有北交所。A股市场的主战场。
  • 期货交易所:中金所、上期所、大商所、郑商所。商品和金融期货。
  • 外盘交易所:NYSE、NASDAQ、CME、LSE 等等。全球市场。

每家交易所的数据格式、协议、延迟要求都不一样。嗯,这里要注意:千万别想着用一个统一方案搞定所有。我在项目中见过有人非要搞"万能接入层",结果每个交易所都得单独适配,反而更麻烦。

核心原则:每个交易所单独适配,但上层统一抽象。接入层做适配器模式,上层行情引擎只认标准化的内部数据结构。

二、数据格式:L1/L2快照与逐笔成交

行情数据格式,说白了就两种:快照逐笔

2.1 L1/L2快照

L1快照就是五档行情。买一卖一,最新价,成交量这些。沪深交易所的L1快照是3秒一张。说实话,做量化策略的话,L1基本不够用。

L2快照就丰富多了。十档甚至全档位行情,还有逐笔成交、逐笔委托。深交所的L2是每秒2张快照,上交所的L2是每秒3-5张。我建议做高频策略的团队,至少上L2。

举个例子,深交所L2快照的数据结构大概长这样:

// 深交所L2快照结构(简化版)
struct L2Snapshot {
    uint32_t    security_id;      // 证券代码
    uint64_t    timestamp;        // 时间戳(纳秒)
    double      last_price;       // 最新价
    uint64_t    volume;           // 成交量
    double      amount;           // 成交额
    // 十档买卖
    double      bid_prices[10];   // 买价
    uint64_t    bid_volumes[10];  // 买量
    double      ask_prices[10];   // 卖价
    uint64_t    ask_volumes[10];  // 卖量
    // 其他统计
    double      open_interest;    // 持仓量(期货用)
    double      pre_close;        // 昨收
};

个人经验:我遇到过L2快照的时间戳精度问题。有些交易所给的是毫秒,有些是微秒,还有纳秒的。接入时一定要确认清楚,否则回放数据时时间线会乱。

2.2 逐笔成交

逐笔成交,就是每一笔真实的成交记录。谁买了多少,谁卖了多少,什么价格成交的。这是最高精度的数据。

沪深L2的逐笔成交,上交所叫"逐笔成交",深交所叫"逐笔成交明细"。两者格式略有不同,但核心字段差不多:

// 逐笔成交结构
struct TradeRecord {
    uint64_t    trade_id;         // 成交编号
    uint64_t    timestamp;        // 成交时间
    double      price;            // 成交价格
    uint64_t    volume;           // 成交量
    uint8_t     buy_order_type;   // 买单类型
    uint8_t     sell_order_type;  // 卖单类型
    uint64_t    buy_order_id;     // 买单编号
    uint64_t    sell_order_id;    // 卖单编号
};

逐笔成交的数据量很大。一个活跃股票一天可能产生几万笔成交。全市场几千只股票,一天下来数据量轻松上GB。所以存储和传输都要考虑压缩。

避坑指南:我曾经在接入上交所L2逐笔成交时,发现它的成交编号不是全局递增的。每个交易日从0开始,而且不同股票独立编号。如果要做全市场合并排序,必须用时间戳+股票代码做联合主键。

三、接入协议:FIX与二进制

协议这块,说白了就是交易所怎么把数据发给你。主流就两种:FIX协议二进制协议

3.1 FIX协议

FIX(Financial Information eXchange)是金融行业的标准协议。文本格式,可读性好。外盘交易所基本都用FIX。

FIX消息长这样:

8=FIX.4.4|9=178|35=W|49=EXCHANGE|56=CLIENT|34=2|52=20240101-12:00:00.000|
55=600519.SH|268=2|269=0|270=185.50|271=1000|269=1|270=185.60|271=500|10=123|

每个字段用竖线分隔,字段编号+值。看着乱,但解析起来其实不难。我建议用现成的FIX引擎,别自己手写解析器。开源的有QuickFIX,商业的有OnixS,都挺成熟。

FIX的优点是标准化,缺点是。文本解析开销大,带宽占用也高。做高频交易的话,FIX基本不够用。

3.2 二进制协议

二进制协议,就是交易所自定义的二进制格式。沪深交易所的L2行情都是二进制协议。上交所叫"MDGW协议",深交所叫"STP协议"。

二进制协议的特点:

  • 紧凑:字段按固定偏移排列,没有分隔符
  • 高效:直接内存映射,零拷贝解析
  • 低延迟:微秒级解析延迟

举个例子,上交所MDGW协议的快照消息头:

// MDGW消息头(16字节)
struct MDGWHeader {
    uint16_t    msg_type;     // 消息类型
    uint16_t    msg_len;      // 消息长度
    uint32_t    seq_no;       // 序列号
    uint64_t    timestamp;    // 时间戳
};

解析二进制协议,我个人的习惯是:用结构体直接映射。C/C++里用#pragma pack(1)对齐,Java里用Unsafe或DirectBuffer。这样解析延迟基本可以忽略。

性能对比:

协议类型解析延迟带宽占用可读性
FIX文本~10微秒
二进制~0.1微秒

四、接入架构设计

说了这么多,咱们看看整体架构。我画了一张图,帮你理清思路:

行情数据源接入架构 数据源层 沪深交易所 期货交易所 外盘交易所 其他数据源 协议适配层 FIX协议解析 二进制协议解析 协议转换 数据校验 数据标准化层 统一快照格式 统一逐笔格式 时间戳归一化 上层应用(行情引擎、存储、分发) 实时计算 数据存储 行情分发 监控告警

这张图展示了行情接入的完整链路。从数据源到协议适配,再到数据标准化,最后给上层应用使用。每一层都有它的职责,千万别跳层。我见过有人直接在协议解析层做业务逻辑,结果后面换交易所时,代码改得想哭。

五、实战建议

最后,给几个实战中的建议:

  1. 先做数据校验:接入任何新数据源,先跑一周的校验程序。检查时间戳连续性、价格合理性、成交量一致性。我曾经在接入某期货交易所时,发现它的成交量字段在特定条件下会溢出,还好提前发现了。
  2. 做好重连机制:交易所的行情网关偶尔会断。TCP断连、心跳超时、序列号跳跃,都要处理。我建议用状态机管理连接状态,别用简单的if-else。
  3. 关注延迟指标:从交易所网关到你的行情引擎,端到端延迟要监控。正常应该在1-5毫秒。如果超过10毫秒,就要排查了。网络、协议解析、CPU调度,都可能成为瓶颈。
  4. 数据备份不能省:原始行情数据要全量备份。回测、复盘、排查问题,都靠它。我习惯用压缩格式存储,一天的数据量大概在50-200GB,看市场活跃度。

小技巧:接入新交易所时,先用模拟数据测试。很多交易所提供测试环境,但测试数据往往太"干净"。我建议自己构造一些边界情况:价格突变、成交量暴增、时间戳回退等。这样上线后心里有底。

好了,数据源接入这块就聊到这儿。记住一句话:接入是基础,稳定是关键。数据源搞不定,后面所有东西都是空中楼阁。


公众号:蓝海资料掘金营,微信deep3321