Binance 私有数据流接入的核心问题不在"连上",而在"连上之后怎么保证本地状态和交易所一致"。现货、U 本位合约、币本位合约、期权各有一套独立的 listenKey 机制,有效期、WebSocket 地址和事件字段都不完全相同。断线重连本身不复杂,难的是重连期间错过的订单和余额变动怎么补。Binance 官方开发者社区给出的建议很直接:stream 断开后,用 REST 接口查询相关数据来查补遗漏。没有更巧的办法。
先分清你接的是哪个市场的私有流
Binance 的私有数据流不是统一的一套。你接现货、U 本位合约、币本位合约还是期权,listenKey 的创建地址、有效期和推送事件都不一样。
现货的私有流通过 WebSocket API 订阅,也可以走传统的 listenKey 方式。listenKey 创建后有效期 60 分钟,可以用 PUT 延长 60 分钟,连接本身最长 24 小时会被断开。推送的事件包括 executionReport(订单更新)和 outboundAccountPosition(余额变动)。
U 本位合约的 listenKey 同样 60 分钟有效,PUT 续期,连接 24 小时上限。WebSocket 地址是 wss://fstream.binance.com/private,订阅格式为 /ws/。推送 ORDER_TRADE_UPDATE 和 ACCOUNT_UPDATE。
币本位合约的机制类似,但事件字段和推送逻辑有细微差别,ACCOUNT_UPDATE 会推送所有 symbol 的仓位信息,而不是只推送变动的部分。
期权的私有流地址不同,连接同样 24 小时上限,ACCOUNT_UPDATE 每 10 秒会周期性推送一次(如果有仓位),这个周期性推送在断线对账时有额外用途,后面会讲。
有一个较新的变化:Binance 推出了 listenToken 方案,作为 listenKey 的推荐替代。通过 POST /sapi/v1/userListenToken 获取 token,默认和最大有效期都是 24 小时,比 listenKey 的 60 分钟长得多。通过 WebSocket API 的 userDataStream.subscribe.listenToken 方法订阅。如果未在过期前续期,会推送 eventStreamTerminated 事件,之后不再推送任何数据,直到创建新 token 并重新订阅。如果你是新接入的项目,优先考虑 listenToken,续期频率低,少一个定时任务。
订单和余额的推送逻辑,决定了你怎么对账
现货的订单更新走 executionReport,余额变动走 outboundAccountPosition。这里有一个 Binance 开发者社区讨论过很多次的老问题:这两个事件之间没有直接的关联字段。你收到一个 executionReport 显示订单成交了,接下来的 outboundAccountPosition 会反映余额变化,但你无法从事件本身确认"这个余额变动就是那笔订单造成的"。
官方没有提供 executionReport 和 outboundAccountPosition 之间的订单 ID 映射。社区里的做法是:先根据 executionReport 里的成交数据在本地乐观更新余额,等 outboundAccountPosition 到达后再覆盖。前提是假设 executionReport 总是先于对应的 outboundAccountPosition 到达。这个假设在正常负载下通常成立,但官方没有承诺严格顺序,所以覆盖逻辑是必要的。
合约这边,ORDER_TRADE_UPDATE 和 ACCOUNT_UPDATE 是分开的。合约的 ACCOUNT_UPDATE 有一个重要特性:未成交或已撤销的订单不会触发这个事件,因为仓位和余额没有实际变化。也就是说,你挂了一个限价单没成交,只会收到 ORDER_TRADE_UPDATE,不会收到 ACCOUNT_UPDATE。如果你只靠 ACCOUNT_UPDATE 来感知订单状态,会漏掉挂单和撤单。
事件顺序方面,官方文档明确说:对于同一连接上的相同事件类型,T(撮合引擎成交时间)和 E(事件生成时间)都严格有序。建议用 E 字段做跨事件类型的排序,尤其是比较 ORDER_TRADE_UPDATE 和市场数据流的时候。
断线重连后的对账,没有捷径
重连本身很简单:WebSocket 断开后,检查 listenKey 是否还有效(合约的 listenKey 60 分钟过期,如果断线时间短可以直接用旧 key 重连;如果收到 -1125 listenKey does not exist 或 listenKeyExpired 事件,就重新 POST 创建)。现货的 listenKey 过期会推送 listenKeyExpired 事件,之后不再推送任何数据。
关键在重连之后。 你断线期间发生的成交和余额变动,WebSocket 不会补推。Binance 开发者社区的建议是:stream 断开后,主动用 REST 接口查询数据来查补遗漏。具体查什么:
订单:用 GET /api/v3/openOrders(现货)或对应的合约接口查当前挂单,用 GET /api/v3/allOrders 或 GET /fapi/v1/allOrders 查历史订单,对比本地记录的订单状态。重点看断线时间窗口内状态从 NEW 或 PARTIALLY_FILLED 变成 FILLED 或 CANCELED 的订单。
余额:用现货的 GET /api/v3/account 或合约的 GET /fapi/v2/account 拉取全量余额,覆盖本地缓存的余额。不要尝试用增量事件去"补",直接全量覆盖最可靠。
期权有一个稍好的条件:ACCOUNT_UPDATE 在有仓位时每 10 秒会周期性推送一次。这意味着即使你错过了一两个变动事件,最多等 10 秒会收到一次全量或半全量的账户状态推送,对账窗口比现货和合约短。但 10 秒的窗口内仍然可能发生成交,所以 REST 对账仍然是需要的。
一个容易被忽略的坑:合约 testnet 的 ACCOUNT_UPDATE 可能不推送
Binance 开发者社区有一个 2026 年 6 月的帖子,报告了 testnet 合约账户的 ACCOUNT_UPDATE 事件完全不推送的问题。订单成交了,ORDER_TRADE_UPDATE 正常收到,userTrades 和 income 接口也能查到手续费和已实现盈亏,但 ACCOUNT_UPDATE 从未触发,REST 的 positionRisk 和 account.totalWalletBalance 也停留在旧值。
这个问题的前提是 testnet 特定账户,主网是否会出现类似情况无法从该帖确认。但它提示了一个对账策略:不要假设 ACCOUNT_UPDATE 一定会到达。如果你的策略依赖 ACCOUNT_UPDATE 来更新仓位和余额,在长时间没有收到该事件时,应该主动用 REST 拉取一次账户状态做校验,而不是无限等待。帖子里的账户后来通过了知识测评、重置了钱包,但 aggregator 没有重新挂载事件流,状态一直冻结。主网如果遇到类似现象,需要联系 Binance 支持而不是自己反复重连。
给你的接入建议
如果你是新项目,用 listenToken 替代 listenKey。24 小时有效期减少了续期逻辑的复杂度,eventStreamTerminated 事件也让过期处理更明确。如果你维护的是已有系统,listenKey 还能用,但把 60 分钟续期的定时任务写对——PUT 失败返回 -1125 时立即重新 POST 创建,不要重试 PUT。
对账逻辑按这个顺序做:重连成功后,先 REST 拉全量余额和挂单,覆盖本地状态;再根据断线时间窗口查历史订单,修正本地订单状态;之后恢复 WebSocket 增量处理。 不要试图用事件去补断线期间的空缺,做不到。
参考资料
- Binance Developers·Listen Token 用户数据流,页面未标明更新日期;核查日期:2026-10-21。
- Binance Developers·账户信息流(U 本位合约),页面未标明更新日期;核查日期:2026-10-21。
- Binance Developers·User Data Streams(币本位合约),页面未标明更新日期;核查日期:2026-10-21。
- Binance Developers·User Data Streams(期权),页面未标明更新日期;核查日期:2026-10-21。
- Binance Developers·Trade Data Stream(现货,deprecated),页面未标明更新日期;核查日期:2026-10-21。
- Binance Developer Community·websocket账户推送断线重连,页面未标明更新日期;核查日期:2026-10-21。
- Binance Developer Community·User Data stream events consistency,页面未标明更新日期;核查日期:2026-10-21。
- Binance Developer Community·Testnet Futures account: ACCOUNT_UPDATE events not emitted,页面未标明更新日期;核查日期:2026-10-21。



