2、数据源接入:WebSocket协议基础、交易所API鉴权、订阅行情频道、心跳维持机制
好,咱们进入第二章。这一章讲的是怎么把订单簿数据真正「接进来」。说白了,就是让你的程序跟交易所服务器「对上话」。
我刚开始做量化交易那会儿,用的还是轮询——每隔几秒发一次HTTP请求。结果呢?行情慢了半拍不说,还被交易所封了IP。后来才明白,实时行情必须走WebSocket。这玩意儿就像一根专线,服务器有数据就主动推给你,不用你一遍遍去问。
2.1 WebSocket协议基础
WebSocket是什么?简单说,它让客户端和服务器之间建立一条「全双工」的通道。你想想看,HTTP是「你问一句,我答一句」,而WebSocket是「随时说话,随时听」。
握手过程其实很简单:客户端发一个HTTP Upgrade请求,服务器同意后,连接就升级成了WebSocket。之后的数据交换就是二进制帧了,开销极小。
核心要点:
- WebSocket使用 ws:// 或 wss://(加密)协议
- 连接建立后,双方可以随时发送消息
- 消息格式通常是JSON或二进制
- 心跳机制是必须的,否则连接会被断开
我在项目中遇到过一个问题:用公共WebSocket库连币安,结果连上几秒就断了。查了半天,原来是没发心跳。嗯,这个坑后面会细说。
2.2 交易所API鉴权
鉴权,说白了就是证明「你是谁」。交易所的公开行情不需要鉴权,但如果你想订阅私有频道(比如你的订单状态),就得带上身份信息。
常见的鉴权方式有两种:
| 方式 | 说明 | 适用场景 |
|---|---|---|
| API Key + Secret | 用密钥对请求签名 | REST API、WebSocket登录 |
| Token | 先登录获取Token,后续请求携带 | 部分交易所的WebSocket |
以币安为例,它的WebSocket鉴权流程是这样的:
# 伪代码示例
import time
import hmac
import hashlib
api_key = "你的API_KEY"
secret_key = "你的SECRET_KEY"
# 构造签名
timestamp = int(time.time() * 1000)
payload = f"timestamp={timestamp}"
signature = hmac.new(
secret_key.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
# 发送登录请求
login_msg = {
"method": "LOGIN",
"params": {
"apiKey": api_key,
"signature": signature,
"timestamp": timestamp
},
"id": 1
}
避坑指南:我曾经把API Key直接硬编码在代码里,结果不小心提交到了GitHub。几分钟后,账户里的币就被转走了。所以,密钥一定要用环境变量或配置文件管理,千万别写死在代码里。
2.3 订阅行情频道
连上WebSocket之后,下一步就是告诉交易所:「我要看哪些数据」。每个交易所的频道命名规则不一样,但逻辑大同小异。
以币安的订单簿深度频道为例:
# 订阅深度频道
subscribe_msg = {
"method": "SUBSCRIBE",
"params": [
"btcusdt@depth20@100ms" # 20档深度,100ms更新一次
],
"id": 2
}
# 发送订阅请求
ws.send(json.dumps(subscribe_msg))
常见的频道类型:
- 深度频道(depth):订单簿的买卖盘口数据
- 成交频道(trade):最新的成交记录
- K线频道(kline):蜡烛图数据
- Ticker频道:24小时统计信息
我个人习惯是先订阅深度频道,因为订单簿是核心。其他频道可以按需添加,但别一次订阅太多,否则带宽和CPU都扛不住。
2.4 心跳维持机制
这是最容易踩坑的地方。交易所为了节省资源,会定期检查连接是否还活着。如果你不发心跳,连接就会被断开。
心跳机制一般有两种:
- Ping/Pong:客户端发Ping,服务器回Pong
- 自定义心跳:交易所定义的心跳消息格式
以币安为例,它的心跳是每3分钟发一次Ping帧:
import asyncio
import websockets
async def heartbeat(ws):
while True:
await asyncio.sleep(180) # 3分钟
try:
# 发送Ping帧
await ws.ping()
print("心跳发送成功")
except Exception as e:
print(f"心跳失败: {e}")
break
# 在主循环中启动心跳任务
async def main():
async with websockets.connect("wss://stream.binance.com:9443/ws") as ws:
# 启动心跳任务
asyncio.create_task(heartbeat(ws))
# 处理消息
async for message in ws:
# 处理行情数据
pass
注意:我曾经遇到过一个问题:心跳线程和消息处理线程共用同一个连接,结果心跳发出去后,消息处理卡住了。后来改用异步方式,才彻底解决。记住,心跳和消息处理要解耦。
2.5 整体流程梳理
说了这么多,咱们把整个流程串起来。下面这张图展示了从连接到数据接收的完整链路:
嗯,这张图把整个流程讲清楚了。从建立连接开始,到鉴权、订阅、接收数据,中间还有心跳维持和异常重连。说白了,这就是一个「活着」的连接该有的样子。
2.6 实战中的几个坑
最后,分享几个我踩过的坑:
- 连接数限制:有些交易所限制单个IP的连接数,别开太多线程去连
- 数据积压:如果处理速度跟不上接收速度,内存会爆。建议用队列缓冲
- 断线重连:一定要实现自动重连,而且要有退避策略(比如第一次等1秒,第二次等2秒)
- 日志记录:所有连接状态变化都要打日志,方便排查问题
我的习惯:每次启动程序前,先手动测试一下WebSocket连接是否正常。用wscat这个命令行工具,几秒钟就能验证。别等到程序跑起来才发现连不上,那会儿就尴尬了。
好了,这一章的内容就到这里。数据源接入是订单簿系统的「地基」,地基打牢了,后面的数据处理和可视化才能稳。下一章咱们聊聊数据解析和清洗——毕竟,交易所发来的原始数据,可不是直接能用的。