Appearance
实时行情 WebSocket 推送接口文档
概述
本文档描述如何通过 WebSocket 协议连接行情推送服务,订阅并接收实时行情数据(市场行情、涨停预警等)。
- 传输协议:WSS(WebSocket over TLS)
- 连接地址:
wss://zhunData.cn/ws/ - 鉴权方式:URL 查询参数
key,如wss://zhunData.cn/ws/?key=your_api_key - 数据压缩:服务端推送的行情数据使用
zstandard压缩 +msgpack序列化 - 文本消息:控制类消息(订阅确认、错误等)使用 JSON 文本格式
客户端 Demo 下载
功能包含:WSS 连接、自动重连、订阅/取消订阅、zstandard 解压 + msgpack 反序列化、Key 过期提醒处理、内存与流量监控日志。
1. 建立连接
连接地址
wss://zhunData.cn/ws/?key={AUTH_KEY}鉴权 Key 通过 URL 查询参数 key 传递。
连接示例(Python)
python
import asyncio
import ssl
import websockets
WS_URI = "wss://zhunData.cn/ws/"
AUTH_KEY = "your_api_key"
async def connect():
ws_uri = f"{WS_URI}?key={AUTH_KEY}"
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.CERT_NONE
async with websockets.connect(ws_uri, ssl=ssl_ctx) as ws:
print("连接成功!")
# ... 后续订阅与接收逻辑连接被拒绝的关闭码
| 关闭码 | 说明 |
|---|---|
| 4001 | 缺少鉴权 Key(URL 中未提供 key 参数) |
| 4003 | Key 无效或已过期 |
| 4009 | Key 已被其他连接占用(同一 Key 不允许重复连接) |
| 1013 | 背压断开(客户端发送缓冲区超过 1MB,服务端主动断开) |
| 4011 | Key 过期超过宽限期,服务端主动断开 |
2. 订阅行情
连接成功后,客户端需发送订阅消息才能接收行情推送。
2.1 订阅请求
json
{
"action": "subscribe",
"channel": "market_quote",
"symbols": ["tick.stock.sz.000001", "tick.stock.sh.600744"]
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
action | string | 是 | 固定为 "subscribe" |
channel | string | 是 | 频道名称,见下方频道列表 |
symbols | string[] | 是 | 订阅标的符号列表,见下方符号格式说明 |
2.2 频道列表
| 频道 | 说明 |
|---|---|
market_quote | 市场行情数据 |
limit_alert | 涨停预警数据 |
2.3 标的符号格式
符号格式为 tick.{asset_type}.{suffix}.{code},支持通配符。
完整格式:
tick.{asset_type}.{suffix}.{code}合法资产类型(asset_type):
| 资产类型 | 说明 |
|---|---|
stock | 股票 |
index | 指数 |
fund | 基金 |
bond | 债券 |
合法交易所后缀(suffix):
| 后缀 | 说明 |
|---|---|
sz | 深交所 |
sh | 上交所 |
bj | 北交所 |
* | 通配(所有交易所) |
符号示例:
| 符号 | 说明 |
|---|---|
tick.stock.sz.000001 | 深市平安银行 |
tick.stock.sh.600744 | 沪市华银电力 |
tick.fund.sh.516100 | 沪市基金 |
tick.fund.sz.159227 | 深市基金 |
tick.stock.* | 订阅所有股票(通配) |
tick.index.sz.* | 订阅深市所有指数(通配) |
tick.fund.* | 订阅所有基金(通配) |
2.4 订阅权限
订阅请求需通过权限校验,权限由 Key 绑定的订阅类型决定:
| 订阅类型 | 说明 | 权限范围 |
|---|---|---|
{asset}_all | 全量订阅 | 可订阅该资产类型的通配符和任意单个标的 |
{asset}_100 | 百标的订阅 | 仅可订阅该资产类型的单个标的,最多 100 个 |
权限校验规则:
- 通配符订阅(如
tick.stock.*):需要{asset}_all权限 - 单标的订阅(如
tick.stock.sz.000001):需要{asset}_all或{asset}_100权限 {asset}_100类型下,单标的总数不可超过 100 个
2.5 订阅确认
订阅成功:
json
{
"type": "subscribe_ok",
"channel": "market_quote",
"symbols": ["tick.stock.sz.000001", "tick.stock.sh.600744"]
}订阅被拒绝:
json
{
"type": "subscribe_rejected",
"channel": "market_quote",
"symbols": ["tick.stock.sz.000001"],
"msg": "Permission denied for tick.stock.sz.000001: no stock data subscription"
}拒绝原因包括:
- 符号格式不合法
- 无该资产类型的订阅权限
{asset}_100类型超出 100 个单标的限制- 频道无效或符号列表为空
3. 取消订阅
取消订阅请求
json
{
"action": "unsubscribe",
"channels": ["market_quote"]
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
action | string | 是 | 固定为 "unsubscribe" |
channels | string[] | 是 | 要取消的频道列表 |
取消订阅确认
json
{
"type": "unsubscribe_ok",
"channels": ["market_quote"]
}注意:取消订阅是频道级别的,会清除该频道下所有已订阅的标的。
4. 接收推送数据
服务端推送的数据分为两类:文本消息(JSON)和二进制消息(压缩行情数据)。
4.1 文本消息
文本消息为 JSON 格式,用于控制类通知。
4.1.1 服务端错误
json
{
"type": "error",
"msg": "Invalid JSON format"
}4.1.2 Key 即将过期提醒
当 Key 距过期不足 7 天时,连接成功后服务端会主动推送:
json
{
"type": "key_expiring_soon",
"key": "f7dbf1ee-xxxx",
"expired_at": "2026-07-30T00:00:00",
"days_remaining": 5.2,
"msg": "Key will expire in 5.2 days, please renew"
}4.1.3 Key 已过期警告
Key 已过期但仍在 8 小时宽限期内时,服务端会定期推送:
json
{
"type": "key_expired_warning",
"key": "f7dbf1ee-xxxx",
"expired_at": "2026-07-25T00:00:00",
"grace_hours_remaining": 6.5,
"msg": "Key expired, will disconnect in 6.5 hours"
}4.1.4 Key 过期断开连接
Key 过期超过宽限期后,服务端主动断开连接:
json
{
"type": "key_expired_disconnect",
"msg": "Key f7dbf1ee-xxxx expired over 8 hours, disconnecting"
}随后连接将被关闭,关闭码为 4011。
4.2 二进制消息(行情数据)
行情数据以二进制帧推送,需经过以下步骤解析:
二进制数据 → zstandard 解压 → msgpack 反序列化 → 行情字典/列表解压后的数据可能是单个字典或字典列表(批量推送)。
解析示例(Python):
python
import msgpack
import zstandard as zstd
dctx = zstd.ZstdDecompressor()
async for message in ws:
if isinstance(message, bytes):
raw_bytes = dctx.decompress(message)
result = msgpack.unpackb(raw_bytes, raw=False)
if isinstance(result, list):
for item in result:
handle_tick(item)
else:
handle_tick(result)4.2.1 市场行情数据(market_quote)
外层结构:
| 字段 | 类型 | 说明 |
|---|---|---|
type | string | 消息类型,值为 "market_quote" |
key | string | 标的符号,如 "tick.stock.sz.000001" |
data | object | 行情明细,字段如下 |
data 行情明细字段:
| 字段 | 类型 | 说明 |
|---|---|---|
time | long | 行情时间戳(毫秒) |
lastPrice | float | 最新成交价 |
open | float | 今日开盘价 |
high | float | 今日最高价 |
low | float | 今日最低价 |
lastClose | float | 昨日收盘价 |
amount | float | 累计成交金额 |
volume | float | 累计成交数量 |
pvolume | float | 原始成交数量(未复权) |
openInt | float | 持仓量(期货适用) |
askPrice | float | 卖一委托价 |
bidPrice | float | 买一委托价 |
askVol | float | 卖一委托量 |
bidVol | float | 买一委托量 |
4.2.2 涨停预警数据(limit_alert)
| 字段 | 类型 | 说明 |
|---|---|---|
type | string | 消息类型,值为 "limit_alert" |
key | string | 标的符号 |
data | object | 预警数据 |
5. Key 过期机制
| 阶段 | 时间节点 | 行为 |
|---|---|---|
| 正常使用 | 过期前 | 正常推送行情 |
| 即将过期提醒 | 距过期不足 7 天 | 连接时推送 key_expiring_soon |
| 过期宽限期 | 过期后 8 小时内 | 推送 key_expired_warning,仍可接收行情 |
| 过期断开 | 超过宽限期 | 推送 key_expired_disconnect,关闭连接(4011) |
6. 断线重连建议
服务端不保证消息持久化,断线期间的数据不会重传。建议客户端实现自动重连机制:
python
import random
RECONNECT_MIN = 5
RECONNECT_MAX = 10
async def run_client():
while True:
try:
ws_uri = f"{WS_URI}?key={AUTH_KEY}"
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.CERT_NONE
async with websockets.connect(ws_uri, ssl=ssl_ctx) as ws:
await subscribe_client(ws)
await receive_data(ws)
except (websockets.ConnectionClosed, ConnectionRefusedError, OSError, asyncio.TimeoutError):
wait_time = random.randint(RECONNECT_MIN, RECONNECT_MAX)
await asyncio.sleep(wait_time)重连注意事项:
- 重连后需重新发送订阅请求,服务端不会保留上次的订阅状态
- 建议使用随机退避时间(5~10 秒),避免大量客户端同时重连
- 同一 Key 同时只允许一个连接,新连接会因 Key 已被占用而被拒绝(关闭码 4009)
7. 完整客户端示例
python
import asyncio
import json
import random
import ssl
import websockets
import msgpack
import zstandard as zstd
WS_URI = "wss://zhunData.cn/ws/"
AUTH_KEY = "your_api_key"
RECONNECT_MIN = 5
RECONNECT_MAX = 10
dctx = zstd.ZstdDecompressor()
async def subscribe_client(ws):
sub_msg = {
"action": "subscribe",
"channel": "market_quote",
"symbols": [
"tick.stock.*",
]
}
await ws.send(json.dumps(sub_msg))
response = await ws.recv()
print(f"服务端确认: {response}")
async def receive_data(ws):
async for message in ws:
try:
if isinstance(message, str):
text_data = json.loads(message)
msg_type = text_data.get("type")
if msg_type == "subscribe_ok":
print(f"订阅成功: {text_data}")
elif msg_type == "subscribe_rejected":
print(f"订阅拒绝: {text_data}")
elif msg_type == "key_expiring_soon":
print(f"Key即将过期: 剩余{text_data.get('days_remaining')}天")
elif msg_type == "key_expired_warning":
print(f"Key已过期: 宽限剩余{text_data.get('grace_hours_remaining')}小时")
elif msg_type == "key_expired_disconnect":
print(f"Key过期断开: {text_data.get('msg')}")
elif msg_type == "error":
print(f"服务端错误: {text_data.get('msg')}")
else:
print(f"文本消息: {text_data}")
else:
raw_bytes = dctx.decompress(message)
result = msgpack.unpackb(raw_bytes, raw=False)
if isinstance(result, list):
for item in result:
handle_tick(item)
else:
handle_tick(result)
except Exception as e:
print(f"解析失败: {e}")
def handle_tick(tick_data):
msg_type = tick_data.get("type")
key = tick_data.get("key", "N/A")
data = tick_data.get("data", {})
if msg_type == "limit_alert":
print(f"涨停预警: {key}")
elif msg_type == "market_quote":
tick_time = data.get("time")
if tick_time:
dt = datetime.fromtimestamp(tick_time / 1000)
print(f"行情: {key} time={dt}")
async def run_client():
while True:
try:
ws_uri = f"{WS_URI}?key={AUTH_KEY}"
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.CERT_NONE
async with websockets.connect(ws_uri, ssl=ssl_ctx) as ws:
print("连接成功!")
await subscribe_client(ws)
await receive_data(ws)
except (websockets.ConnectionClosed, ConnectionRefusedError, OSError, asyncio.TimeoutError) as e:
print(f"连接断开: {e}")
wait_time = random.randint(RECONNECT_MIN, RECONNECT_MAX)
print(f"{wait_time}秒后重连...")
await asyncio.sleep(wait_time)
if __name__ == "__main__":
asyncio.run(run_client())8. 消息速查表
客户端 → 服务端
| action | 字段 | 说明 |
|---|---|---|
subscribe | channel, symbols | 订阅指定频道的标的 |
unsubscribe | channels | 取消指定频道的订阅 |
服务端 → 客户端(文本)
| type | 说明 |
|---|---|
subscribe_ok | 订阅成功 |
subscribe_rejected | 订阅被拒绝 |
unsubscribe_ok | 取消订阅成功 |
error | 服务端错误 |
key_expiring_soon | Key 即将过期提醒 |
key_expired_warning | Key 已过期宽限期警告 |
key_expired_disconnect | Key 过期断开连接通知 |
服务端 → 客户端(二进制)
| type (解压后) | 说明 |
|---|---|
market_quote | 市场行情推送 |
limit_alert | 涨停预警推送 |