对于交易所系统来说,行情数据的稳定性和实时性直接影响前端展示、撮合逻辑、风控预警和用户体验。下面以 Infoway API的 WebSocket 实时行情 API 为例,总结在对接过程中你必须注意的技术细节和常见坑位,避免踩雷。
一、订阅消息格式一定要确认清楚
问题点: 有些行情 API 使用二进制协议,有些是 JSON 格式;订阅结构不规范或字段拼错会导致服务端无响应,且不会报错。
解决方法:
-
仔细阅读 API 文档中协议号(如
code: 10004
)的说明。 -
trace
字段可以使用uuid.uuid4().hex
动态生成,方便后续问题排查。 -
示例中的
type: 1
表示 1 分钟 K 线,这种数字枚举一定要与文档对齐,不能硬写。
二、务必实现 Ping 保活机制
问题点: 很多 WebSocket 服务端会在客户端长时间不发送消息时自动断开连接。
解决方法:
-
按照接口要求定时发送
ping
消息,例如每 30 秒一次(参考示例中的code: 10010
)。 -
推荐用
asyncio.create_task()
启动异步心跳协程,保证不断线。
三、消息接收循环要做好容错处理
问题点: 实时消息中断或连接重置是常态,不能因一次掉线就崩整个系统。
解决方法:
-
对
websockets.exceptions.ConnectionClosed
做异常捕获,日志记录后重连。 -
可以设计为守护进程,支持自动重连及状态恢复。
四、数据字段不要想当然
问题点: 很多开发者以为 "c"
是“开盘价”但其实它是“收盘价”,用错字段会导致计算偏差。
字段说明:
-
"o"
:开盘价 -
"c"
:收盘价(即当前价格) -
"h"
/"l"
:高/低 -
"v"
:成交量(外汇场景中可能是撮合量、报价量或模拟值) -
"t"
:时间戳(毫秒) -
"vw"
:加权平均价,部分策略需要用到
请基于你的业务逻辑清晰区分每个字段的用途,特别是做K线还原或指标计算时。
五、初始订阅粒度应谨慎
问题点: 有的交易所系统直接一次性订阅上百个交易对或多个时间周期,导致:
-
推送压力过大
-
客户端处理能力不足
-
网络瞬时断连或限流
解决方法:
-
首次接入阶段建议只订阅核心币种或外汇对(如 EURUSD)。
-
分批逐步扩展,观察内存与 CPU 使用情况后再做全量订阅。
六、异步任务调度建议集中管理
问题点: 大量异步任务如 ping、断线重连、解析处理等如果混杂在主逻辑中,极易导致协程泄漏、任务阻塞。
解决方法:
-
封装成类,结构化管理协程生命周期。
-
异常处理应尽量写在顶层,不要遗漏
try-except
。
七、接口变更要有监控机制
问题点: 行情服务方可能升级协议、字段改名、断点续传机制变更,导致数据接收异常但系统无感知。
解决方法:
-
在接收数据时检查字段完整性与合法性,缺字段立即报警。
-
trace ID 和日志结合使用,方便对账与回溯。
八、代码示例
import asyncio
import json
import websockets
# 外汇行情的websocket订阅地址
WS_URL = "wss://data.infoway.io/ws?business=forex&apikey=yourApiKey"
# 请先在官网www.infoway.io 申请免费API key
async def connect_and_receive():
async with websockets.connect(WS_URL) as websocket:
# 发送初始化消息,订阅EURUSD的1分钟K线
init_message = {
"code": 10004, # K线请求协议号
"trace": "423afec425004bd8a5e02e1ba5f9b2b0", # 可追溯ID(可用uuid替换)
"data": {
"arr": [
{
"type": 1, # 1分钟K线
"codes": "EURUSD" # 外汇货币对代码
}
]
}
}
await websocket.send(json.dumps(init_message))
# 设置ping任务
async def send_ping():
while True:
await asyncio.sleep(30)
ping_message = {
"code": 10010,
"trace": "423afec425004bd8a5e02e1ba5f9b2b0"
}
await websocket.send(json.dumps(ping_message))
# 启动ping任务协程
ping_task = asyncio.create_task(send_ping())
try:
# 持续接收消息
while True:
message = await websocket.recv()
print(f"Message received: {message}")
except websockets.exceptions.ConnectionClosedOK:
print("Connection closed normally")
finally:
# 取消ping任务
ping_task.cancel()
# 运行主函数
asyncio.run(connect_and_receive())
返回示例:
{
"c": "1.0845", // 当前价格(收盘价)
"h": "1.0852", // 该分钟内的最高价
"l": "1.0839", // 该分钟内的最低价
"o": "1.0840", // 开盘价
"pca": "0.0005", // 价格变化
"pfr": "0.05%", // 价格变化百分比
"s": "EURUSD", // 外汇货币对代码
"t": 1747550648097, // 时间戳(毫秒)
"ty": 1, // K线类型:1 表示1分钟K线
"v": "2.4", // 成交量(在外汇中通常是报价量或模拟值)
"vw": "1.0843" // 加权平均价格
}