行情接入层:WebSocket vs FIX协议,如何对接交易所行情源

行情接入,是高频系统的第一道关卡。说白了,你策略再牛,拿不到数据就是白搭。我见过太多团队,模拟盘跑得飞起,一上实盘就崩——十有八九是行情接入这块没处理好。

今天咱们就聊聊,怎么选协议、怎么对接、怎么保证数据不丢不坏。

一、WebSocket vs FIX协议:选哪个?

先给结论:没有绝对的好坏,只有适不适合你的场景

对比维度 WebSocket FIX协议
传输层 基于TCP,全双工 基于TCP,会话式
消息格式 JSON/Protobuf,轻量 标签=值,固定格式
延迟 较低(毫秒级) 更低(微秒级)
复杂度 简单,上手快 复杂,需要FIX引擎
适用场景 模拟盘、中小团队 实盘、机构级高频

我个人习惯是:模拟盘用WebSocket,实盘必须上FIX。为什么?

WebSocket的好处是简单。你写个Python脚本,连上交易所的WS接口,几行代码就能收行情。我在项目中遇到过,一个实习生半天就能搭起来。但问题也在这里——它太简单了,缺乏很多实盘需要的机制。

FIX协议呢?它是个老牌金融协议,从90年代用到现在。它定义了完整的会话层:登录、心跳、序列号、重传机制。说白了,它知道「这条消息你收到了没」,如果丢了还能补。

核心区别一句话:WebSocket只管「发出去」,FIX保证「你收到了」。

二、如何对接交易所行情源

不管用哪种协议,对接流程都差不多。我把它拆成三步:

  1. 获取接入信息:交易所会给你一个URL(WS)或IP+端口(FIX),还有API Key/Secret。
  2. 建立连接:WS直接连,FIX需要先发送Logon消息。
  3. 订阅行情:告诉交易所你要哪些品种、哪些深度。

嗯,这里要注意:实盘环境下,连接建立后一定要做心跳检测。我曾经吃过这个亏——连接断了半小时,系统完全没发现,策略还在那傻等行情。

WebSocket对接示例(Python)

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    # 处理行情数据
    process_tick(data)

def on_error(ws, error):
    print(f"连接异常: {error}")
    # 自动重连逻辑
    reconnect()

def on_close(ws, close_status_code, close_msg):
    print("连接关闭,准备重连...")
    reconnect()

def on_open(ws):
    # 订阅BTC/USDT的深度行情
    sub_msg = {
        "op": "subscribe",
        "args": ["depth.BTC-USDT"]
    }
    ws.send(json.dumps(sub_msg))

ws = websocket.WebSocketApp(
    "wss://api.exchange.com/ws",
    on_open=on_open,
    on_message=on_message,
    on_error=on_error,
    on_close=on_close
)
ws.run_forever()
避坑指南:我曾经在模拟盘用单线程跑WS,一切正常。上实盘后,行情一密集,回调函数里处理不过来,直接卡死。后来改成多线程+队列,才解决问题。

FIX协议对接示例(C++)

#include <quickfix/Application.h>
#include <quickfix/MessageCracker.h>

class MarketDataClient : public FIX::Application,
                         public FIX::MessageCracker {
public:
    void onCreate(const FIX::SessionID&) override {}
    
    void onLogon(const FIX::SessionID& sessionID) override {
        std::cout << "登录成功: " << sessionID << std::endl;
        // 订阅行情
        subscribeMarketData("BTC/USDT");
    }
    
    void onLogout(const FIX::SessionID& sessionID) override {
        std::cout << "登出,准备重连..." << std::endl;
        reconnect();
    }
    
    void onMessage(const FIX44::MarketDataSnapshotFullRefresh& msg,
                   const FIX::SessionID&) override {
        // 处理行情快照
        processSnapshot(msg);
    }
    
private:
    void subscribeMarketData(const std::string& symbol) {
        FIX44::MarketDataRequest request;
        request.set(FIX::MDReqID("1"));
        request.set(FIX::SubscriptionRequestType('1')); // 快照+增量
        request.set(FIX::MarketDepth(5)); // 5档深度
        
        FIX44::MDFullGrp& mdGroup = 
            request.createGroup(FIX44::MDFullGrp::FIELD);
        mdGroup.set(FIX::MDEntryType('0')); // 买单
        request.addGroup(mdGroup);
        
        FIX::Session::sendToTarget(request);
    }
};
注意:FIX协议的序列号管理很关键。如果序列号不连续,说明丢了消息,必须发起重传请求。我见过有人忽略这个,结果策略用的行情数据是残缺的,亏了不少。

三、数据校验与补全机制

行情数据到了,不代表就能直接用。你想想看,网络抖动、交易所限流、机器负载,任何一个环节出问题,数据就可能出问题。

我总结了一套「三层校验法」:

  • 第一层:格式校验——字段是否完整、类型是否正确、时间戳是否合理。
  • 第二层:逻辑校验——买卖价差不能为负、最新价不能偏离太远、成交量不能突变。
  • 第三层:时序校验——消息序列号是否连续、时间戳是否递增。

举个例子,我曾经遇到一个情况:交易所偶尔会发出一条卖价比买价还低的行情。这在逻辑上是不可能的。如果直接喂给策略,策略会以为有套利机会,疯狂下单。嗯,这就是逻辑校验的重要性。

数据补全策略

数据丢了怎么办?补!但怎么补,有讲究:

丢失类型 补全策略 适用场景
单条Tick丢失 用上一条Tick填充 非关键行情,容忍度较高
连续多条丢失 请求交易所重传 FIX协议支持,WS需要额外接口
深度数据丢失 请求快照+增量恢复 做市策略必须保证深度完整
我的经验:补全只是临时方案,根本解决方法是保证连接质量。实盘环境一定要用专线,别省那点钱。我曾经在模拟盘用公网跑,一天丢几百条数据,补全逻辑写得再完美也没用。

四、整体架构图

下面这张图,是我做高频系统时常用的行情接入架构。你仔细看看,每个环节都有它的作用:

行情接入层架构图 交易所行情源 WebSocket / FIX协议 连接管理 + 心跳 数据校验层 格式 → 逻辑 → 时序 数据补全机制 重传 / 填充 / 快照恢复 行情缓存(Ring Buffer) 无锁队列,低延迟 策略执行引擎 重连/重订阅 关键指标 • 延迟:< 1ms • 吞吐:> 10万tick/s • 可用性:99.99%

从这张图你能看到,行情数据从交易所出来,经过协议层、校验层、补全层,最后进入缓存,才交给策略使用。每一步都不能省。

一个小技巧:缓存层我建议用Ring Buffer(环形缓冲区),配合无锁队列。这样能保证在极端行情下,写入和读取都不会阻塞。我在实盘系统里就是这么做的,效果很好。

五、总结

行情接入这块,说白了就三件事:选对协议、做好校验、保证补全。模拟盘你可以偷懒,实盘千万别。

我个人建议的顺序是:先用WebSocket快速验证策略逻辑,等要上实盘了,再切换到FIX协议。切换的时候,注意把校验和补全逻辑一起带上,别漏了。

最后说一句:行情数据是高频系统的血液,血液不干净,整个系统都会出问题。别在这块省钱省力,不值得。


专注资料整理