本文适合量化开发、后端工程、交易系统研发人员阅读,分享真实项目踩坑经验,讲解多市场行情源接入、数据归一化的工程落地思路,附带可运行 Python WebSocket 示例代码。
做量化或者交易类后台开发的同学大概率都遇到过一类难题:当业务需要同时对接外汇、贵金属、加密货币、股票等多类资产行情时,看似只是多接入几个API,实际落地会冒出大量适配工作。今天结合我过往开发多资产监控系统的实战经历,聊聊行情API选型要点,以及如何通过工程设计抹平不同市场接口之间的数据差异。
之前我在搭建一套多资产监控面板,需求是单页面同时展示欧美货币对、黄金、比特币以及多支美股。一开始我预估难度不高:分别对接对应数据源,拿到行情之后渲染前端即可。
真正进入开发阶段才发现现实远比设想复杂。项目中先后对接三家不同的数据服务商:一家负责外汇与贵金属,一家提供加密货币行情,股票数据则使用另一套接口。但各家的鉴权机制、品种编码规范、时间戳格式、返回字段全部自成体系。大量开发时间消耗在编写数据适配转换逻辑,看板本身的业务实现反而占比不高。
多市场行情对接,常见的兼容痛点
经过这次项目,我总结出多数据源接入场景下几个高频坑点:
- 标的编码规范不统一 同样是 EURUSD,不同服务商有的写为
EURUSD,有的使用EUR/USD;加密货币存在BTC‑USDT、BTCUSDT两种分隔写法;股票代码还需要追加交易所后缀。直接把A服务商的编码传入B服务商接口,基本无法获取有效行情数据。 - 时间戳与字段数据类型混乱 时间戳单位不统一,秒、毫秒、时间字符串混杂;价格、成交量字段,部分接口返回数字,部分直接返回字符串类型,后续计算很容易抛出异常。
- 数据推送模式与策略不一致 部分数据源支持 WebSocket 长连接实时推送,部分只能依靠轮询拉取;各家心跳保活、访问限流规则都有自己的标准,无法直接复用同一套客户端逻辑。
- 各市场交易时间模型差异大 加密货币是7×24小时不间断交易;外汇周末休市;股票存在开盘、午休、法定节假日停盘。如果代码写死统一告警规则,例如“10秒无数据就告警”,休市时段就会产生大量无效告警,淹没真正的故障信息。
行情API选型思路:降低适配层的开发成本
踩过坑之后,我在评估多资产行情API时,首要关注点,就是看服务商能否把跨品类资产尽量收敛到同一套通信协议。主要考察这几项:
- 是否复用同一 Token 完成鉴权;
- 不同品种的订阅报文结构是否保持一致;
- 行情推送返回字段定义是否尽量统一;
- 文档是否提供完整、可查阅的品种编码对照表。
以上条件匹配度越高,业务侧需要编写的适配代码就越少。
当然也不能追求绝对理想化。即便是同一服务商,股票、外汇、加密货币往往会拆分不同接入端点,字段细节依然会存在少量差异。务实方案是优先选择协议标准化程度较高的API,再在业务层编写适配逻辑消化剩余差异。
除此之外,限流策略与服务稳定性同样需要重点评估。免费套餐普遍存在严格配额,同时订阅多个品类,很容易触发限流,接入前务必确认配额上限,合理规划订阅标的数量。同时官方文档应当清晰给出断线重连、心跳保活相关说明。
在实际项目中我经常使用 AllTick API,它将外汇、贵金属、加密货币整合在同一个 WebSocket 接入端点;美股、港股、A股等股票资产使用另外一套端点,两套链路的订阅交互协议保持一致,能够有效减少适配层开发工作量。
工程方案:定义内部行情模型,隔离第三方差异
无论外部数据源返回格式如何,我的实践方案是在应用内部抽象一套专属行情实体 Quote,只保留业务真正需要的核心字段:数据源标识、品种代码、价格、成交量、毫秒级时间戳。
所有第三方原始行情数据进入系统后,第一步统一做格式转换,映射为这套内部模型。上层业务,无论是量化策略计算、数据分析,还是监控看板渲染,全部依赖内部 Quote 对象,完全不感知外部接口原始格式。
这套设计带来非常直观的工程收益:后续切换数据源,仅需要修改数据转换函数,上层业务逻辑无需改动;新增一类交易市场,只需要补充数据源配置。之前项目中,依靠这套架构,新增港股、美股行情,半天就完成调试上线。
实操代码:单进程实现多路 WebSocket 行情接入
下面是可直接用于调试的 Python 示例,同一个进程维护多条 WebSocket 连接,分别消费多资产行情与股票行情,链路互相隔离,单个连接故障不会影响其余行情数据流。
import asyncio
import json
import os
import uuid
from dataclasses import dataclass
import websockets
BASE = "wss://quote.alltick.co"
FEEDS = {
"multi": { # 外汇、贵金属、加密货币
"path": "/quote-b-ws-api",
"codes": ["EURUSD", "GOLD", "BTCUSDT"],
},
"stock": { # 美股、港股、A股
"path": "/quote-stock-b-ws-api",
"codes": ["AAPL.US", "700.HK", "600519.SH"],
},
}
heartbeat = {"cmd_id": 22000, "seq_id": 1, "trace": "heartbeat", "data": {}}
@dataclass
class Quote:
feed: str
code: str
price: float
volume: float
ts_ms: int
def normalize(feed, tick):
"""把不同市场的推送统一成内部的Quote"""
return Quote(
feed=feed,
code=tick["code"],
price=float(tick["price"]),
volume=float(tick["volume"]),
ts_ms=int(tick["tick_time"]),
)
async def run_feed(name, cfg, queue):
token = "你申请到的AllTick_token"
uri = f"{BASE}{cfg['path']}?token={token}"
subscribe = {
"cmd_id": 22004,
"seq_id": 1,
"trace": str(uuid.uuid4()),
"data": {"symbol_list": [{"code": c} for c in cfg["codes"]]},
}
while True: # 断线后自动重连
try:
async with websockets.connect(uri) as ws:
await ws.send(json.dumps(subscribe))
async def beat():
while True:
await asyncio.sleep(10)
await ws.send(json.dumps(heartbeat))
task = asyncio.create_task(beat())
try:
async for raw in ws:
msg = json.loads(raw)
if msg.get("cmd_id") == 22998:
await queue.put(normalize(name, msg["data"]))
finally:
task.cancel()
except (websockets.ConnectionClosed, OSError):
await asyncio.sleep(3)
async def consumer(queue):
while True:
q = await queue.get()
print(q.feed, q.code, q.price, q.ts_ms)
async def main():
queue = asyncio.Queue()
await asyncio.gather(
consumer(queue),
*[run_feed(n, c, queue) for n, c in FEEDS.items()],
)
asyncio.run(main())
代码逻辑说明:各个市场独立建立 WebSocket 连接,独立处理订阅、心跳保活、断线重试;解析完成的数据投递至公共异步队列,统一消费处理。单条链路断开,其余市场行情依旧正常工作。
⚠️ 开发注意点
- 同一条 WebSocket 会话下,重复发送订阅指令会覆盖原有订阅列表。动态新增交易品种时,需要提交完整的品种集合。
- 接入新数据源,务必打印原始报文校验时间字段单位,避免后续时间序列计算全部错位。
数据归一化后,仍需要处理的工程细节
完成内部模型转换不等于万事大吉,还有几个现实问题需要处理:
- 分市场设计行情告警逻辑 不要使用固定超时时间一刀切判断行情异常。加密货币告警超时阈值可以设置较小;外汇、股票需要结合交易日历。外汇周末停止推送属于正常业务现象,不应该触发告警。
- 警惕浮点数计算精度问题 外汇报价常为5位小数,A股2位小数,加密货币最高可达8位小数。涉及金额计算场景,建议将价格转为 Decimal 类型运算,规避浮点数累积误差。
- 历史K线数据同样做模型适配 实时行情依赖 WebSocket,历史K线一般调用 REST 接口获取,两套接口返回结构往往不一致。同样复用内部 Quote 模型,REST 返回结果先做转换再入库。实现策略读取实时数据、回测历史数据复用同一套业务对象。
总结
多资产行情接入没有捷径。选型阶段优先选择协议规范、文档完善的行情API;架构层面把第三方接口差异收拢在适配层,保障上层业务代码干净解耦。我现在启动量化相关项目,都会先定义好内部行情数据模型,后续开发流程会顺畅很多。
参考文档:https://alltick.co/apis/zh
GitHub:https://github.com/alltick/alltick-realtime-forex-crypto-stock-tick-finance-websocket-api
