Bitget APIBitget API
统一账户经典账户
旧文档
  • 概览
  • API 文档
  • WebSocket
  • Agent Hub
  • SDK
  • 更新日志
Copied to clipboard
Websocket
公共频道
私有频道
Reality
SBE
    SBE 基本信息SBE BBO 接入指南SBE Level 50 接入指南SBE Public Trade 接入指南
SBE

SBE Level 50 接入指南

总览

字段说明
Topicbooks50
TemplateId1001
Schema 版本5
FormatSBE 二进制 frame (opcode = 2), little-endian
Depth提供 50 档买卖盘深度数据。订阅后首帧为全量快照(depthAction = 0),后续帧为增量更新(depthAction = 1),需合并到本地订单簿
Units时间戳为 microseconds (µs) ,但是仅精确到毫秒。毫秒时间加上 000 得到微秒格式时间。比如:毫秒时间 1726233600001 对应的微秒格式时间 (µs) 为 1726233600001000
更新频率20ms

连接

  • WebSocket URL: wss://ws.bitget.com/v3/ws/public/sbe
  • 心跳: 每 30 秒发送文本帧 "ping", 服务端返回 "pong"
  • 报文机制: 订阅阶段使用 JSON 文本帧;行情推送阶段使用 SBE 二进制帧,通过 WebSocket opCode 区分(opCode=1 文本, opCode=2 二进制)
  • 字段 seq 为单调递增的序列号,用于检测乱序
  • 字段 pseq 为前一帧的 seq,与 seq 配合用于检测丢包;快照帧的 pseq 为 0

订阅流程

1. 发送订阅请求

Code
{ "op": "subscribe", "args": [ { "instType": "usdt-futures", "topic": "books50", "symbol": "BTCUSDT" } ] }

参数说明:

参数类型说明
instTypestring产品类型: spot
usdt-futures
usdc-futures
coin-futures
topicstring固定值: books50
symbolstring交易对, 如 BTCUSDT, ETHUSDT

2. 订阅确认

Code
{ "event": "subscribe", "arg": { "instType": "usdt-futures", "topic": "books50", "symbol": "BTCUSDT" } }

3. 接收数据

订阅确认后, 每 20ms 推送一次 SBE 二进制帧。订阅后首帧为全量快照(depthAction = 0),后续帧均为增量更新(depthAction = 1)。报文中的 depthAction 字段可明确区分两者——将每个 update 帧合并到由前一 snapshot 帧构建的本地订单簿之上。

4. 取消订阅

Code
{ "op": "unsubscribe", "args": [ { "instType": "usdt-futures", "topic": "books50", "symbol": "BTCUSDT" } ] }

SBE 消息结构

价格/数量计算公式

Code
实际值 = mantissa × 10^exponent

示例: 尾数为 123456,指数为 -4,表示 12.3456(实际值 = 尾数 × 10 ^ 指数)

公共消息头 (8 bytes)

所有 SBE 消息必须包含固定 8 字节的头部,以便解析识别后续数据。

字段名类型 (Type)长度 (Byte)说明
blockLengthuint162Root 块长度
templateIduint162频道唯一标识,固定值 = 1001
schemaIduint162Schema ID
versionuint162Schema 版本

消息字段定义

p.s priceExponent 和 sizeExponent 在 XML Schema 中为固定值。价格和数量将会使用尾数和指数来表示。

序号字段名 (Field)类型 (Type)说明
-messageHeaderComposite固定头部,包含 blockLength, templateId, schemaId, version
1tsuint64撮合引擎时间戳
µs 微秒时间戳,但是仅精确到毫秒。毫秒时间加上 000 得到微秒格式时间。比如:毫秒时间 1726233600001 对应的微秒格式时间 (µs) 为 1726233600001000
2sequint64消息序列号,用于检测丢包或乱序
3priceExponentint8价格指数
4sizeExponentint8数量指数
5stsuint64流服务推送时间戳,微秒(µs)
6categoryuint8业务线:spot 现货 / usdt-futures U本位合约 / coin-futures 币本位合约 / usdc-futures USDC合约
7psequint64前一帧的 seq,用于检测增量推送的丢包或乱序;快照帧的 pseq 为 0
8depthActionuint8推送动作:0 = Snapshot(全量快照),1 = Update(增量更新,仅含发生变化的档位)
100paddinguint8填充字节
200asksgroupSize16Encoding固定 50 组卖单数组
201bidsgroupSize16Encoding固定 50 组买单数组
300symbolvarString[8]交易对名称,UTF-8 格式

Asks Group

序号字段名 (Field)类型 (Type)说明
1priceint64卖一价
实际价格 = 有效价格 price × 10^指数 priceExponent
2sizeint64卖一量
实际数量 = 有效数量 size × 10^指数 sizeExponent

Bids Group

序号字段名 (Field)类型 (Type)说明
1priceint64买一价
实际价格 = 有效价格 price × 10^指数 priceExponent
2sizeint64买一量
实际数量 = 有效数量 size × 10^指数 sizeExponent

depthAction 取值

depthAction 字段决定如何解读当前帧的 asks / bids 分组。

取值名称说明
0Snapshot全量快照。asks / bids 分组为完整档位,且 pseq 为 0
1Update增量更新。仅包含相对上一帧发生变化的档位;若某档位的 size = 0,表示该价位已被摘除

对于 depthAction = 1 的帧,将收到的档位应用到本地订单簿:以新的 size 覆盖该价位,当 size = 0 时删除该价位。

二进制布局总览

Code
┌─────────────┬──────────────┬────────────────────────┬────────────────────────┬──────────┐ │ Header (8B) │ Root (40B) │ Asks: GrpHdr(4B)+N×16B │ Bids: GrpHdr(4B)+N×16B │ Symbol │ └─────────────┴──────────────┴────────────────────────┴────────────────────────┴──────────┘

解码示例

原始二进制 (hex)

下例为一帧增量更新(depthAction = 1),仅携带发生变化的买卖档位。Snapshot 帧(depthAction = 0)结构相同,区别在于 pseq 为 0,且 asks / bids 分组为完整档位。

Code
28 00 E9 03 01 00 05 00 <- header: blockLength=40, templateId=1001, schemaId=1, version=5 00 40 1E 18 24 0A 06 00 <- ts (uint64 LE) = 1700000000000000 2A 00 00 00 00 00 00 00 <- seq (uint64 LE) = 42 FE <- priceExponent (int8) = -2 FC <- sizeExponent (int8) = -4 E8 43 1E 18 24 0A 06 00 <- sts (uint64 LE) = 1700000000001000 01 <- category (uint8) = 1 (usdt-futures) 29 00 00 00 00 00 00 00 <- pseq (uint64 LE) = 41 01 <- depthAction (uint8) = 1 (Update) 00 00 00 00 <- padding4 10 00 02 00 <- asks group: entryBlockLength=16, numInGroup=2 52 33 64 00 00 00 00 00 <- asks[0].price = 6566738 10 27 00 00 00 00 00 00 <- asks[0].size = 10000 B6 33 64 00 00 00 00 00 <- asks[1].price = 6566838 20 4E 00 00 00 00 00 00 <- asks[1].size = 20000 10 00 02 00 <- bids group: entryBlockLength=16, numInGroup=2 26 32 64 00 00 00 00 00 <- bids[0].price = 6566438 30 75 00 00 00 00 00 00 <- bids[0].size = 30000 C2 31 64 00 00 00 00 00 <- bids[1].price = 6566338 40 9C 00 00 00 00 00 00 <- bids[1].size = 40000 07 42 54 43 55 53 44 54 <- symbol: length=7, "BTCUSDT"

解码后 JSON

Code
{ "header": { "block_length": 40, "template_id": 1001, "schema_id": 1, "version": 5 }, "ts": 1700000000000000, "seq": 42, "price_exponent": -2, "size_exponent": -4, "sts": 1700000000001000, "category": 1, "pseq": 41, "depth_action": 1, "asks": [ { "price": "65667.38", "size": "1.0000" }, { "price": "65668.38", "size": "2.0000" } ], "bids": [ { "price": "65664.38", "size": "3.0000" }, { "price": "65663.38", "size": "4.0000" } ], "symbol": "BTCUSDT" }

Python 接入示例

Code
"""Bitget books50 SBE WebSocket 订阅示例""" import asyncio import json import struct from decimal import Decimal import websockets WS_URL = "wss://ws.bitget.com/v3/ws/public/sbe" INST_TYPE = "usdt-futures" SYMBOL = "BTCUSDT" TOPIC = "books50" DEPTH_ACTION = {0: "Snapshot", 1: "Update"} def decode_depth50(data: bytes) -> dict: """解码 Depth50 (templateId=1001) SBE 帧""" block_length, template_id, schema_id, version = struct.unpack_from('<HHHH', data, 0) assert template_id == 1001, f"unexpected templateId: {template_id}" offset = 8 base = offset ts, = struct.unpack_from('<Q', data, offset); offset += 8 seq, = struct.unpack_from('<Q', data, offset); offset += 8 price_exp, = struct.unpack_from('<b', data, offset); offset += 1 size_exp, = struct.unpack_from('<b', data, offset); offset += 1 sts, = struct.unpack_from('<Q', data, offset); offset += 8 category, = struct.unpack_from('<B', data, offset); offset += 1 # sinceVersion=5:pseq + depthAction pseq = depth_action = None if version >= 5: pseq, = struct.unpack_from('<Q', data, offset); offset += 8 depth_action, = struct.unpack_from('<B', data, offset); offset += 1 # 跳过 padding,以 blockLength 为准 offset = base + block_length def decode_group(off): entry_bl, num = struct.unpack_from('<HH', data, off); off += 4 levels = [] for _ in range(num): start = off p, = struct.unpack_from('<q', data, off); off += 8 s, = struct.unpack_from('<q', data, off); off += 8 off = start + entry_bl levels.append({ "price": str(Decimal(p) * Decimal(10) ** price_exp), "size": str(Decimal(s) * Decimal(10) ** size_exp), }) return levels, off asks, offset = decode_group(offset) bids, offset = decode_group(offset) sym_len, = struct.unpack_from('<B', data, offset); offset += 1 symbol = data[offset:offset + sym_len].decode('utf-8') return { "ts": ts, "seq": seq, "pseq": pseq, "price_exponent": price_exp, "size_exponent": size_exp, "sts": sts, "category": category, "depth_action": depth_action, "asks": asks, "bids": bids, "symbol": symbol, } async def main(): async with websockets.connect(WS_URL) as ws: # 订阅 await ws.send(json.dumps({ "op": "subscribe", "args": [{"instType": INST_TYPE, "topic": TOPIC, "symbol": SYMBOL}] })) print(f"[SUB] {INST_TYPE} {TOPIC} {SYMBOL}") # 心跳 async def ping_loop(): while True: await asyncio.sleep(20) await ws.send("ping") print("[PING] sent") asyncio.create_task(ping_loop()) async for message in ws: if isinstance(message, bytes): try: msg = decode_depth50(message) ts_ms = msg['ts'] // 1000 action = DEPTH_ACTION.get(msg['depth_action'], '?') print(f"\n[Depth50] {msg['symbol']} ts={ts_ms}ms seq={msg['seq']} pseq={msg['pseq']} sts={msg['sts']} category={msg['category']}") print(f" price_exp={msg['price_exponent']} size_exp={msg['size_exponent']} action={action}") print(f" asks ({len(msg['asks'])} levels):") for i, lvl in enumerate(msg['asks'][:5]): print(f" [{i}] price={lvl['price']} size={lvl['size']}") print(f" bids ({len(msg['bids'])} levels):") for i, lvl in enumerate(msg['bids'][:5]): print(f" [{i}] price={lvl['price']} size={lvl['size']}") except Exception as e: print(f"[ERROR] {e} raw={message.hex()}") else: if message == "pong": print("[PONG] received") else: print(f"[TEXT] {message}") if __name__ == "__main__": asyncio.run(main())
SBE BBO 接入指南SBE Public Trade 接入指南
On this page
  • 总览
  • 连接
  • 订阅流程
    • 1. 发送订阅请求
    • 2. 订阅确认
    • 3. 接收数据
    • 4. 取消订阅
  • SBE 消息结构
    • 价格/数量计算公式
    • 公共消息头 (8 bytes)
    • 消息字段定义
    • 二进制布局总览
  • 解码示例
    • 原始二进制 (hex)
    • 解码后 JSON
  • Python 接入示例
JSON
JSON
JSON
Javascript
Javascript
Javascript
Javascript