第三章:行情数据接入层

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

我做了这么多年债券做市系统,最深的体会就是:行情接不好,后面全是白搭。你策略再牛,模型再准,拿到的数据是错的或者慢了,那交易结果就是灾难。

3.1 交易所与银行间市场:两个世界的协议

国内债券市场,主要分两大块:交易所市场和银行间市场。这两个地方的行情协议,完全是两套逻辑。

交易所市场(上交所、深交所)走的是标准的证券行情协议。我记得早期用的是Step协议,后来逐步升级到STEP、MBO(Market By Order)这类。说白了,就是逐笔成交和快照数据。

银行间市场(CFETS)就复杂多了。它走的是外汇交易中心的专有协议,比如FX系列、CSTP。我当年第一次对接银行间行情时,差点被它的报文结构搞崩溃——字段嵌套深,而且有些字段是可选的,解析起来特别麻烦。

核心差异对比:

维度 交易所 银行间
协议类型 Step/STEP/MBO FX/CSTP
数据频率 毫秒级快照+逐笔 秒级报价+成交
字段复杂度 中等 高(嵌套多)
接入方式 TCP直连/组播 专线+API

3.2 多源数据融合:别让数据打架

你想想看,一个债券可能在交易所、银行间、甚至场外都有报价。这些报价时间不同、价格不同、量也不同。怎么融合?

我个人习惯的做法是:时间戳对齐 + 优先级策略

举个例子,某只国债在交易所最新成交价是99.50,银行间报价是99.52。这时候不能简单取平均,因为两个市场的流动性不同。我一般会设定一个「可信度权重」——银行间的大额报价权重高,交易所的逐笔成交权重高。

避坑指南:我曾经遇到过一个问题:两个源的数据时间戳差了1秒,结果融合出来的价格剧烈抖动。后来我加了一个「时间窗口对齐」的逻辑——只融合时间差在100毫秒以内的数据。嗯,这个细节很重要。

3.3 行情数据缓存与分发:别让下游饿死

行情数据来了,不能直接丢给策略层。为什么?因为策略层处理不过来。一个活跃的债券,每秒可能有几百笔行情变化。策略层如果每笔都处理,CPU直接拉满。

所以,我们需要一个缓存层。我常用的方案是:

  • 本地内存缓存:用ConcurrentHashMap或者Caffeine,存最近N笔行情
  • 环形缓冲区:适合高频场景,避免GC压力
  • Redis发布订阅:适合多进程/多机器分发

分发的时候,要注意「推拉结合」。推模式(Push)适合实时性要求高的场景,比如做市策略;拉模式(Pull)适合分析型应用,比如风控系统。

注意:缓存不能无限大。我见过有人把一天的所有行情都缓存下来,结果内存爆了。建议设置过期时间,比如只保留最近5分钟的逐笔数据。

3.4 行情质量监控:数据坏了要能发现

行情数据出问题,是迟早的事。网络抖动、源端故障、协议解析异常……我经历过太多次了。

监控什么?我总结了几点:

  1. 数据完整性:每个时间窗口内,行情条数是否在合理范围?突然少了,可能是丢包了。
  2. 数据时效性:最新行情的时间戳和当前系统时间差了多少?超过500毫秒就要报警。
  3. 数据合理性:价格突变超过5%?量突然放大100倍?这些很可能是异常数据。
  4. 源端健康度:每个数据源的连接状态、延迟、重连次数。

我曾经遇到过一个经典问题:银行间行情源突然停了10秒,但系统没报警,因为缓存里还有旧数据。结果策略用了10秒前的价格去报价,亏了不少。从那以后,我加了一个「缓存新鲜度」的监控——如果缓存数据超过1秒没更新,直接标记为不可用。

3.5 整体架构图

下面这张图,是我个人比较推荐的一种行情接入层架构。它把上面说的几个模块串起来了。

行情数据接入层架构 交易所行情源 银行间行情源 场外/第三方源 协议解析模块 Step解析 | FX解析 | CSTP解析 | 自定义协议 多源数据融合 时间戳对齐 | 优先级策略 | 去重 | 异常过滤 缓存与分发 内存缓存 | 环形缓冲区 | Redis Pub/Sub | 推拉结合 行情质量监控:完整性 | 时效性 | 合理性 | 源端健康度

3.6 代码示例:一个简单的行情解析器

下面是一个简化版的Step协议解析器片段。实际生产环境要复杂得多,但核心逻辑就是这样。

public class StepQuoteParser {
    
    // 解析Step协议的快照数据
    public Quote parseSnapshot(byte[] rawData) {
        Quote quote = new Quote();
        
        // 跳过协议头
        int offset = 8;
        
        // 解析证券代码
        byte[] secCodeBytes = new byte[8];
        System.arraycopy(rawData, offset, secCodeBytes, 0, 8);
        quote.setSecurityCode(new String(secCodeBytes).trim());
        offset += 8;
        
        // 解析最新价(定点数,4字节)
        int priceInt = ByteBuffer.wrap(rawData, offset, 4).getInt();
        quote.setLastPrice(priceInt / 10000.0);  // 假设精度是万分位
        offset += 4;
        
        // 解析成交量(4字节)
        int volume = ByteBuffer.wrap(rawData, offset, 4).getInt();
        quote.setVolume(volume);
        
        // 设置时间戳
        quote.setTimestamp(System.currentTimeMillis());
        
        return quote;
    }
}

个人经验:解析行情时,一定要注意字节序(Big-Endian vs Little-Endian)。交易所和银行间用的可能不一样。我当年就因为这个bug,排查了整整一个下午。

3.7 总结

行情数据接入层,是整个做市系统的地基。地基不稳,楼盖得再高也没用。

核心就四点:协议解析要准、多源融合要稳、缓存分发要快、质量监控要全。把这四点做好了,后面的策略层才能安心干活。

蓝海数据掘金营,专注资料整理