行情接口基础:Level-1 vs Level-2 协议

做量化交易,行情数据就是你的眼睛。没有行情,策略就是瞎子。

我个人习惯把行情协议分成两类:Level-1Level-2。说白了,一个是大路货,一个是VIP通道。

Level-1 行情:够用,但不够快

Level-1 是啥?就是你在普通炒股软件上看到的那种行情。3秒刷新一次,只有五档买卖盘,成交明细也是合并后的。

它的数据字段大概长这样:

struct Level1Snapshot {
    char    symbol[8];        // 股票代码
    double  open;             // 开盘价
    double  high;             // 最高价
    double  low;              // 最低价
    double  close;            // 收盘价(昨收)
    double  lastPrice;        // 最新价
    uint64  volume;           // 成交量
    double  amount;           // 成交额
    double  bidPrice[5];      // 买一到买五价格
    double  askPrice[5];      // 卖一到卖五价格
    uint64  bidVolume[5];     // 买一到买五数量
    uint64  askVolume[5];     // 卖一到卖五数量
};

嗯,这里要注意:Level-1 的买卖盘只有五档。对于高频策略来说,这远远不够。你想想看,大单藏在十档、二十档后面,你根本看不见。

我的经验:做日内低频策略,Level-1 完全够用。但如果你做的是 tick 级别的策略,别想了,直接上 Level-2。

Level-2 行情:真正的战场

Level-2 就厉害了。它提供的是 逐笔成交十档行情。数据量大概是 Level-1 的 10 到 20 倍。

我曾经在实盘环境中踩过一个坑:Level-2 的逐笔成交数据,高峰期每秒能产生几千条。如果处理不及时,内存直接爆掉。

Level-2 的核心数据结构:

struct Level2Tick {
    char    symbol[8];        // 股票代码
    uint64  timestamp;        // 精确到微秒
    double  price;            // 成交价格
    uint64  volume;           // 成交量
    char    buySellFlag;      // 'B' 买盘成交, 'S' 卖盘成交
    uint64  bidVolume[10];    // 十档买量
    uint64  askVolume[10];    // 十档卖量
    double  bidPrice[10];     // 十档买价
    double  askPrice[10];     // 十档卖价
};
关键区别:Level-2 的逐笔成交能让你看到每一笔交易的细节。大单拆单、对倒、老鼠仓,都能从逐笔数据里看出来。

TCP vs UDP:怎么选?

行情接入方式,主流就两种:TCP 和 UDP。我见过不少新手在这上面栽跟头。

TCP 接入:稳,但慢

TCP 有重传机制,数据不会丢。但代价是延迟高。对于行情来说,TCP 的延迟通常在 1-5 毫秒。

适合场景:

  • 盘后数据下载
  • 非高频策略
  • 历史回测数据获取

UDP 接入:快,但会丢

UDP 没有重传,延迟可以做到 100 微秒以内。但网络抖动时,数据包可能丢失。

我建议的做法是:

  • 主链路用 UDP 接收实时行情
  • 辅链路用 TCP 做数据补全
  • 本地维护一个序列号,发现跳号就从 TCP 补
注意:UDP 的 MTU 限制是 1500 字节。如果行情包超过这个大小,会被分片。分片包一旦丢失一个,整个包都废了。我曾经因为这个原因,丢了一整天的逐笔数据。

行情快照与增量更新

行情数据有两种更新方式:快照增量。这个设计思路,其实跟数据库的 binlog 有点像。

快照机制

快照就是某一时刻的完整行情数据。比如每 3 秒发一次完整的五档盘口。

优点:简单,直接解析就行。
缺点:数据量大,带宽浪费严重。

增量机制

增量只发变化的部分。比如某只股票买一价格变了,只发这一条变化。

增量更新的数据结构:

struct IncrementalUpdate {
    char    symbol[8];        // 股票代码
    uint64  sequence;         // 序列号,用于排序
    uint8   updateType;       // 0: 新增, 1: 修改, 2: 删除
    uint8   priceLevel;       // 第几档
    double  price;            // 价格
    uint64  volume;           // 数量
};

嗯,这里有个坑:增量更新必须按顺序处理。如果顺序乱了,盘口数据就全乱了。

我的做法:本地维护一个盘口快照,收到增量就更新快照。同时每 30 秒强制拉一次全量快照,用来校验本地数据是否正确。

核心逻辑流程图

下面这张图,是我自己总结的行情接入核心流程。你看一眼就明白了:

行情接入核心流程 交易所行情源 协议选择:TCP / UDP TCP 接入(稳定) UDP 接入(低延迟) 数据解析:快照 / 增量 策略引擎消费

实战中的几个坑

最后,分享几个我踩过的坑:

  1. UDP 端口被防火墙拦截 —— 有一次上线前才发现,运维把 UDP 端口封了。排查了整整一天。
  2. 增量序列号溢出 —— 某些交易所的序列号是 uint32,跑几个月就溢出了。记得做环形缓冲区处理。
  3. 快照和增量时序错乱 —— 先收到增量,后收到快照。这种情况必须丢弃增量,等快照到位后再处理。
一句话总结:行情接入的核心就三件事——选对协议、处理好快照增量、做好异常恢复。这三件事做好了,行情系统就稳了。

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