行情接入层:WebSocket vs FIX协议,如何对接交易所行情源
行情接入,是高频系统的第一道关卡。说白了,你策略再牛,拿不到数据就是白搭。我见过太多团队,模拟盘跑得飞起,一上实盘就崩——十有八九是行情接入这块没处理好。
今天咱们就聊聊,怎么选协议、怎么对接、怎么保证数据不丢不坏。
一、WebSocket vs FIX协议:选哪个?
先给结论:没有绝对的好坏,只有适不适合你的场景。
| 对比维度 | WebSocket | FIX协议 |
|---|---|---|
| 传输层 | 基于TCP,全双工 | 基于TCP,会话式 |
| 消息格式 | JSON/Protobuf,轻量 | 标签=值,固定格式 |
| 延迟 | 较低(毫秒级) | 更低(微秒级) |
| 复杂度 | 简单,上手快 | 复杂,需要FIX引擎 |
| 适用场景 | 模拟盘、中小团队 | 实盘、机构级高频 |
我个人习惯是:模拟盘用WebSocket,实盘必须上FIX。为什么?
WebSocket的好处是简单。你写个Python脚本,连上交易所的WS接口,几行代码就能收行情。我在项目中遇到过,一个实习生半天就能搭起来。但问题也在这里——它太简单了,缺乏很多实盘需要的机制。
FIX协议呢?它是个老牌金融协议,从90年代用到现在。它定义了完整的会话层:登录、心跳、序列号、重传机制。说白了,它知道「这条消息你收到了没」,如果丢了还能补。
二、如何对接交易所行情源
不管用哪种协议,对接流程都差不多。我把它拆成三步:
- 获取接入信息:交易所会给你一个URL(WS)或IP+端口(FIX),还有API Key/Secret。
- 建立连接:WS直接连,FIX需要先发送Logon消息。
- 订阅行情:告诉交易所你要哪些品种、哪些深度。
嗯,这里要注意:实盘环境下,连接建立后一定要做心跳检测。我曾经吃过这个亏——连接断了半小时,系统完全没发现,策略还在那傻等行情。
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()
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);
}
};
三、数据校验与补全机制
行情数据到了,不代表就能直接用。你想想看,网络抖动、交易所限流、机器负载,任何一个环节出问题,数据就可能出问题。
我总结了一套「三层校验法」:
- 第一层:格式校验——字段是否完整、类型是否正确、时间戳是否合理。
- 第二层:逻辑校验——买卖价差不能为负、最新价不能偏离太远、成交量不能突变。
- 第三层:时序校验——消息序列号是否连续、时间戳是否递增。
举个例子,我曾经遇到一个情况:交易所偶尔会发出一条卖价比买价还低的行情。这在逻辑上是不可能的。如果直接喂给策略,策略会以为有套利机会,疯狂下单。嗯,这就是逻辑校验的重要性。
数据补全策略
数据丢了怎么办?补!但怎么补,有讲究:
| 丢失类型 | 补全策略 | 适用场景 |
|---|---|---|
| 单条Tick丢失 | 用上一条Tick填充 | 非关键行情,容忍度较高 |
| 连续多条丢失 | 请求交易所重传 | FIX协议支持,WS需要额外接口 |
| 深度数据丢失 | 请求快照+增量恢复 | 做市策略必须保证深度完整 |
四、整体架构图
下面这张图,是我做高频系统时常用的行情接入架构。你仔细看看,每个环节都有它的作用:
从这张图你能看到,行情数据从交易所出来,经过协议层、校验层、补全层,最后进入缓存,才交给策略使用。每一步都不能省。
五、总结
行情接入这块,说白了就三件事:选对协议、做好校验、保证补全。模拟盘你可以偷懒,实盘千万别。
我个人建议的顺序是:先用WebSocket快速验证策略逻辑,等要上实盘了,再切换到FIX协议。切换的时候,注意把校验和补全逻辑一起带上,别漏了。
最后说一句:行情数据是高频系统的血液,血液不干净,整个系统都会出问题。别在这块省钱省力,不值得。