Skip to content

Commit bdbb102

Browse files
committed
feat: Add USD-M futures WebSocket URL category support (public/market/private)
Binance is retiring legacy USD-M futures WebSocket URLs on 2026-04-23. This migrates all fstream methods to use the new categorized URL structure.
1 parent 47a7c93 commit bdbb102

File tree

2 files changed

+56
-21
lines changed

2 files changed

+56
-21
lines changed

binance/ws/keepalive_websocket.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,15 @@ async def __aexit__(self, *args, **kwargs):
5454
await super().__aexit__(*args, **kwargs)
5555

5656
def _build_path(self):
57-
self._path = self._listen_key
57+
if self._keepalive_type in ("futures", "coin_futures"):
58+
self._path = f"?listenKey={self._listen_key}"
59+
self._prefix = "ws"
60+
else:
61+
self._path = self._listen_key
5862
time_unit = getattr(self._client, "TIME_UNIT", None)
5963
if time_unit:
60-
self._path = f"{self._listen_key}?timeUnit={time_unit}"
64+
sep = "&" if "?" in self._path else "?"
65+
self._path = f"{self._path}{sep}timeUnit={time_unit}"
6166

6267
async def _before_connect(self):
6368
if self._keepalive_type == "user":

binance/ws/streams.py

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def __init__(
7373
self.ws_kwargs = {}
7474

7575
if verbose:
76-
logging.getLogger('binance.ws').setLevel(logging.DEBUG)
76+
logging.getLogger("binance.ws").setLevel(logging.DEBUG)
7777

7878
def _get_stream_url(self, stream_url: Optional[str] = None):
7979
if stream_url:
@@ -136,7 +136,11 @@ def _get_account_socket(
136136
return self._conns[conn_id]
137137

138138
def _get_futures_socket(
139-
self, path: str, futures_type: FuturesType, prefix: str = "stream?streams="
139+
self,
140+
path: str,
141+
futures_type: FuturesType,
142+
prefix: str = "stream?streams=",
143+
category: Optional[str] = None,
140144
):
141145
socket_type: BinanceSocketType = BinanceSocketType.USD_M_FUTURES
142146
if futures_type == FuturesType.USD_M:
@@ -145,6 +149,8 @@ def _get_futures_socket(
145149
stream_url = self.FSTREAM_TESTNET_URL
146150
elif self.demo:
147151
stream_url = self.FSTREAM_DEMO_URL
152+
if category:
153+
stream_url = f"{stream_url}{category}/"
148154
else:
149155
stream_url = self.DSTREAM_URL
150156
if self.testnet:
@@ -340,7 +346,9 @@ def kline_futures_socket(
340346
"""
341347

342348
path = f"{symbol.lower()}_{contract_type.value}@continuousKline_{interval}"
343-
return self._get_futures_socket(path, prefix="ws/", futures_type=futures_type)
349+
return self._get_futures_socket(
350+
path, prefix="ws/", futures_type=futures_type, category="market"
351+
)
344352

345353
def miniticker_socket(self, update_time: int = 1000):
346354
"""Start a miniticker websocket for all trades
@@ -466,7 +474,7 @@ def aggtrade_futures_socket(
466474
467475
"""
468476
return self._get_futures_socket(
469-
symbol.lower() + "@aggTrade", futures_type=futures_type
477+
symbol.lower() + "@aggTrade", futures_type=futures_type, category="market"
470478
)
471479

472480
def symbol_miniticker_socket(self, symbol: str):
@@ -621,7 +629,9 @@ def futures_ticker_socket(self):
621629
}
622630
]
623631
"""
624-
return self._get_futures_socket("!ticker@arr", FuturesType.USD_M)
632+
return self._get_futures_socket(
633+
"!ticker@arr", FuturesType.USD_M, category="market"
634+
)
625635

626636
def futures_coin_ticker_socket(self):
627637
"""Start a websocket for all ticker data
@@ -707,7 +717,7 @@ def symbol_mark_price_socket(
707717
"""
708718
stream_name = "@markPrice@1s" if fast else "@markPrice"
709719
return self._get_futures_socket(
710-
symbol.lower() + stream_name, futures_type=futures_type
720+
symbol.lower() + stream_name, futures_type=futures_type, category="market"
711721
)
712722

713723
def all_mark_price_socket(
@@ -734,7 +744,9 @@ def all_mark_price_socket(
734744
]
735745
"""
736746
stream_name = "!markPrice@arr@1s" if fast else "!markPrice@arr"
737-
return self._get_futures_socket(stream_name, futures_type=futures_type)
747+
return self._get_futures_socket(
748+
stream_name, futures_type=futures_type, category="market"
749+
)
738750

739751
def symbol_ticker_futures_socket(
740752
self, symbol: str, futures_type: FuturesType = FuturesType.USD_M
@@ -758,7 +770,7 @@ def symbol_ticker_futures_socket(
758770
]
759771
"""
760772
return self._get_futures_socket(
761-
symbol.lower() + "@bookTicker", futures_type=futures_type
773+
symbol.lower() + "@bookTicker", futures_type=futures_type, category="public"
762774
)
763775

764776
def individual_symbol_ticker_futures_socket(
@@ -779,7 +791,7 @@ def individual_symbol_ticker_futures_socket(
779791
}
780792
"""
781793
return self._get_futures_socket(
782-
symbol.lower() + "@ticker", futures_type=futures_type
794+
symbol.lower() + "@ticker", futures_type=futures_type, category="market"
783795
)
784796

785797
def all_ticker_futures_socket(
@@ -811,7 +823,10 @@ def all_ticker_futures_socket(
811823
]
812824
"""
813825

814-
return self._get_futures_socket(channel, futures_type=futures_type)
826+
category = "public" if "bookTicker" in channel else "market"
827+
return self._get_futures_socket(
828+
channel, futures_type=futures_type, category=category
829+
)
815830

816831
def symbol_book_ticker_socket(self, symbol: str):
817832
"""Start a websocket for the best bid or ask's price or quantity for a specified symbol.
@@ -889,7 +904,10 @@ def options_multiplex_socket(self, streams: List[str]):
889904
return self._get_options_socket(stream_path, prefix="stream?")
890905

891906
def futures_multiplex_socket(
892-
self, streams: List[str], futures_type: FuturesType = FuturesType.USD_M
907+
self,
908+
streams: List[str],
909+
futures_type: FuturesType = FuturesType.USD_M,
910+
category: str = "market",
893911
):
894912
"""Start a multiplexed socket using a list of socket names.
895913
User stream sockets can not be included.
@@ -902,6 +920,7 @@ def futures_multiplex_socket(
902920
903921
:param streams: list of stream names in lower case
904922
:param futures_type: use USD-M or COIN-M futures default USD-M
923+
:param category: stream category for USD-M futures URL routing ("public", "market", or "private"), default "market"
905924
906925
:returns: connection key string if successful, False otherwise
907926
@@ -910,7 +929,7 @@ def futures_multiplex_socket(
910929
"""
911930
path = f"streams={'/'.join(streams)}"
912931
return self._get_futures_socket(
913-
path, prefix="stream?", futures_type=futures_type
932+
path, prefix="stream?", futures_type=futures_type, category=category
914933
)
915934

916935
def user_socket(self):
@@ -945,6 +964,7 @@ def futures_user_socket(self):
945964
stream_url = self.FSTREAM_TESTNET_URL
946965
elif self.demo:
947966
stream_url = self.FSTREAM_DEMO_URL
967+
stream_url += "private/"
948968
return self._get_account_socket("futures", stream_url=stream_url)
949969

950970
def coin_futures_user_socket(self):
@@ -989,6 +1009,7 @@ def futures_socket(self):
9891009
stream_url = self.FSTREAM_TESTNET_URL
9901010
elif self.demo:
9911011
stream_url = self.FSTREAM_DEMO_URL
1012+
stream_url += "private/"
9921013
return self._get_account_socket("futures", stream_url=stream_url)
9931014

9941015
def coin_futures_socket(self):
@@ -1121,7 +1142,9 @@ def options_depth_socket(self, symbol: str, depth: str = "10"):
11211142
"""
11221143
return self._get_options_socket(symbol.upper() + "@depth" + str(depth))
11231144

1124-
def futures_depth_socket(self, symbol: str, depth: str = "10", futures_type=FuturesType.USD_M):
1145+
def futures_depth_socket(
1146+
self, symbol: str, depth: str = "10", futures_type=FuturesType.USD_M
1147+
):
11251148
"""Subscribe to a futures depth data stream
11261149
11271150
https://binance-docs.github.io/apidocs/futures/en/#partial-book-depth-streams
@@ -1133,7 +1156,9 @@ def futures_depth_socket(self, symbol: str, depth: str = "10", futures_type=Futu
11331156
:param futures_type: use USD-M or COIN-M futures default USD-M
11341157
"""
11351158
return self._get_futures_socket(
1136-
symbol.lower() + "@depth" + str(depth), futures_type=futures_type
1159+
symbol.lower() + "@depth" + str(depth),
1160+
futures_type=futures_type,
1161+
category="public",
11371162
)
11381163

11391164
def futures_rpi_depth_socket(self, symbol: str, futures_type=FuturesType.USD_M):
@@ -1149,7 +1174,9 @@ def futures_rpi_depth_socket(self, symbol: str, futures_type=FuturesType.USD_M):
11491174
:param futures_type: use USD-M or COIN-M futures default USD-M
11501175
"""
11511176
return self._get_futures_socket(
1152-
symbol.lower() + "@rpiDepth@500ms", futures_type=futures_type
1177+
symbol.lower() + "@rpiDepth@500ms",
1178+
futures_type=futures_type,
1179+
category="public",
11531180
)
11541181

11551182
def options_new_symbol_socket(self):
@@ -1194,7 +1221,9 @@ def options_open_interest_socket(self, symbol: str, expiration_date: str):
11941221
:param expiration_date: The expiration date (e.g., "221125" for Nov 25, 2022)
11951222
:type expiration_date: str
11961223
"""
1197-
return self._get_options_socket(symbol.upper() + "@openInterest@" + expiration_date)
1224+
return self._get_options_socket(
1225+
symbol.upper() + "@openInterest@" + expiration_date
1226+
)
11981227

11991228
def options_mark_price_socket(self, symbol: str):
12001229
"""Subscribe to an options mark price stream.
@@ -1277,8 +1306,7 @@ def __init__(
12771306
async def _before_socket_listener_start(self):
12781307
assert self._client
12791308
self._bsm = BinanceSocketManager(
1280-
client=self._client,
1281-
max_queue_size=self._max_queue_size
1309+
client=self._client, max_queue_size=self._max_queue_size
12821310
)
12831311

12841312
def _start_async_socket(
@@ -1291,7 +1319,9 @@ def _start_async_socket(
12911319
start_time = time.time()
12921320
while not self._bsm:
12931321
if time.time() - start_time > 5:
1294-
raise RuntimeError("Binance Socket Manager failed to initialize after 5 seconds")
1322+
raise RuntimeError(
1323+
"Binance Socket Manager failed to initialize after 5 seconds"
1324+
)
12951325
time.sleep(0.1)
12961326
socket = getattr(self._bsm, socket_name)(**params)
12971327
socket_path: str = path or socket._path # noqa

0 commit comments

Comments
 (0)