@@ -64,6 +64,7 @@ def clientConnectionLost(self, connector, reason):
6464class BinanceSocketManager (threading .Thread ):
6565
6666 STREAM_URL = 'wss://stream.binance.com:9443/'
67+ FSTREAM_URL = 'wss://fstream.binance.com/'
6768
6869 WEBSOCKET_DEPTH_5 = '5'
6970 WEBSOCKET_DEPTH_10 = '10'
@@ -84,9 +85,10 @@ def __init__(self, client, user_timeout=DEFAULT_USER_TIMEOUT):
8485 self ._conns = {}
8586 self ._client = client
8687 self ._user_timeout = user_timeout
87- self ._timers = {'user' : None , 'margin' : None }
88+ self ._timers = {'user' : None , 'margin' : None }
8889 self ._listen_keys = {'user' : None , 'margin' : None }
8990 self ._account_callbacks = {'user' : None , 'margin' : None }
91+ # Isolated margin sockets will be opened under the 'symbol' name
9092
9193 def _start_socket (self , path , callback , prefix = 'ws/' ):
9294 if path in self ._conns :
@@ -102,7 +104,21 @@ def _start_socket(self, path, callback, prefix='ws/'):
102104 self ._conns [path ] = connectWS (factory , context_factory )
103105 return path
104106
105- def start_depth_socket (self , symbol , callback , depth = None ):
107+ def _start_futures_socket (self , path , callback , prefix = 'stream?streams=' ):
108+ if path in self ._conns :
109+ return False
110+
111+ factory_url = self .FSTREAM_URL + prefix + path
112+ factory = BinanceClientFactory (factory_url )
113+ factory .protocol = BinanceClientProtocol
114+ factory .callback = callback
115+ factory .reconnect = True
116+ context_factory = ssl .ClientContextFactory ()
117+
118+ self ._conns [path ] = connectWS (factory , context_factory )
119+ return path
120+
121+ def start_depth_socket (self , symbol , callback , depth = None , interval = None ):
106122 """Start a websocket for symbol market depth returning either a diff or a partial book
107123
108124 https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#partial-book-depth-streams
@@ -113,6 +129,8 @@ def start_depth_socket(self, symbol, callback, depth=None):
113129 :type callback: function
114130 :param depth: optional Number of depth entries to return, default None. If passed returns a partial book instead of a diff
115131 :type depth: str
132+ :param interval: optional interval for updates, default None. If not set, updates happen every second. Must be 0, None (1s) or 100 (100ms)
133+ :type interval: int
116134
117135 :returns: connection key string if successful, False otherwise
118136
@@ -169,6 +187,11 @@ def start_depth_socket(self, symbol, callback, depth=None):
169187 socket_name = symbol .lower () + '@depth'
170188 if depth and depth != '1' :
171189 socket_name = '{}{}' .format (socket_name , depth )
190+ if interval :
191+ if interval in [0 , 100 ]:
192+ socket_name = '{}@{}ms' .format (socket_name , interval )
193+ else :
194+ raise ValueError ("Websocket interval value not allowed. Allowed values are [0, 100]" )
172195 return self ._start_socket (socket_name , callback )
173196
174197 def start_kline_socket (self , symbol , callback , interval = Client .KLINE_INTERVAL_1MINUTE ):
@@ -406,6 +429,99 @@ def start_ticker_socket(self, callback):
406429 """
407430 return self ._start_socket ('!ticker@arr' , callback )
408431
432+ def start_symbol_mark_price_socket (self , symbol , callback , fast = True ):
433+ """Start a websocket for a symbol's futures mark price
434+ https://binance-docs.github.io/apidocs/futures/en/#mark-price-stream
435+ :param symbol: required
436+ :type symbol: str
437+ :param callback: callback function to handle messages
438+ :type callback: function
439+ :returns: connection key string if successful, False otherwise
440+ Message Format
441+ .. code-block:: python
442+ {
443+ "e": "markPriceUpdate", // Event type
444+ "E": 1562305380000, // Event time
445+ "s": "BTCUSDT", // Symbol
446+ "p": "11185.87786614", // Mark price
447+ "r": "0.00030000", // Funding rate
448+ "T": 1562306400000 // Next funding time
449+ }
450+ """
451+ stream_name = '@markPrice@1s' if fast else '@markPrice'
452+ return self ._start_futures_socket (symbol .lower () + stream_name , callback )
453+
454+ def start_all_mark_price_socket (self , callback , fast = True ):
455+ """Start a websocket for all futures mark price data
456+ By default all symbols are included in an array.
457+ https://binance-docs.github.io/apidocs/futures/en/#mark-price-stream-for-all-market
458+ :param callback: callback function to handle messages
459+ :type callback: function
460+ :returns: connection key string if successful, False otherwise
461+ Message Format
462+ .. code-block:: python
463+
464+ [
465+ {
466+ "e": "markPriceUpdate", // Event type
467+ "E": 1562305380000, // Event time
468+ "s": "BTCUSDT", // Symbol
469+ "p": "11185.87786614", // Mark price
470+ "r": "0.00030000", // Funding rate
471+ "T": 1562306400000 // Next funding time
472+ }
473+ ]
474+ """
475+ stream_name = '!markPrice@arr@1s' if fast else '!markPrice@arr'
476+ return self ._start_futures_socket (stream_name , callback )
477+
478+ def start_symbol_ticker_futures_socket (self , symbol , callback ):
479+ """Start a websocket for a symbol's ticker data
480+ By default all markets are included in an array.
481+ https://binance-docs.github.io/apidocs/futures/en/#individual-symbol-book-ticker-streams
482+ :param symbol: required
483+ :type symbol: str
484+ :param callback: callback function to handle messages
485+ :type callback: function
486+ :returns: connection key string if successful, False otherwise
487+ .. code-block:: python
488+ [
489+ {
490+ "u":400900217, // order book updateId
491+ "s":"BNBUSDT", // symbol
492+ "b":"25.35190000", // best bid price
493+ "B":"31.21000000", // best bid qty
494+ "a":"25.36520000", // best ask price
495+ "A":"40.66000000" // best ask qty
496+ }
497+ ]
498+ """
499+ return self ._start_futures_socket (symbol .lower () + '@bookTicker' , callback )
500+
501+ def start_all_ticker_futures_socket (self , callback ):
502+ """Start a websocket for all ticker data
503+ By default all markets are included in an array.
504+ https://binance-docs.github.io/apidocs/futures/en/#all-book-tickers-stream
505+ :param callback: callback function to handle messages
506+ :type callback: function
507+ :returns: connection key string if successful, False otherwise
508+ Message Format
509+ .. code-block:: python
510+ [
511+ {
512+ "u":400900217, // order book updateId
513+ "s":"BNBUSDT", // symbol
514+ "b":"25.35190000", // best bid price
515+ "B":"31.21000000", // best bid qty
516+ "a":"25.36520000", // best ask price
517+ "A":"40.66000000" // best ask qty
518+ }
519+ ]
520+ """
521+
522+
523+ return self ._start_futures_socket ('!bookTicker' , callback )
524+
409525 def start_symbol_book_ticker_socket (self , symbol , callback ):
410526 """Start a websocket for the best bid or ask's price or quantity for a specified symbol.
411527
@@ -482,6 +598,7 @@ def start_user_socket(self, callback):
482598 """Start a websocket for user data
483599
484600 https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md
601+ https://binance-docs.github.io/apidocs/spot/en/#listen-key-spot
485602
486603 :param callback: callback function to handle messages
487604 :type callback: function
@@ -496,9 +613,9 @@ def start_user_socket(self, callback):
496613 return self ._start_account_socket ('user' , user_listen_key , callback )
497614
498615 def start_margin_socket (self , callback ):
499- """Start a websocket for margin data
616+ """Start a websocket for cross- margin data
500617
501- https://github.com/ binance-exchange/binance-official-api- docs/blob/master/user-data-stream.md
618+ https://binance-docs.github.io/apidocs/spot/en/#listen-key-margin
502619
503620 :param callback: callback function to handle messages
504621 :type callback: function
@@ -512,6 +629,25 @@ def start_margin_socket(self, callback):
512629 # and start the socket with this specific key
513630 return self ._start_account_socket ('margin' , margin_listen_key , callback )
514631
632+ def start_isolated_margin_socket (self , symbol , callback ):
633+ """Start a websocket for isolated margin data
634+
635+ https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin
636+
637+ :param symbol: required - symbol for the isolated margin account
638+ :type symbol: str
639+ :param callback: callback function to handle messages
640+ :type callback: function
641+
642+ :returns: connection key string if successful, False otherwise
643+
644+ Message Format - see Binance API docs for all types
645+ """
646+ # Get the isolated margin listen key
647+ isolated_margin_listen_key = self ._client .isolated_margin_stream_get_listen_key (symbol )
648+ # and start the socket with this specific kek
649+ return self ._start_account_socket (symbol , isolated_margin_listen_key , callback )
650+
515651 def _start_account_socket (self , socket_type , listen_key , callback ):
516652 """Starts one of user or margin socket"""
517653 self ._check_account_socket_open (listen_key )
@@ -542,10 +678,15 @@ def _keepalive_account_socket(self, socket_type):
542678 if socket_type == 'user' :
543679 listen_key_func = self ._client .stream_get_listen_key
544680 callback = self ._account_callbacks [socket_type ]
545- else :
681+ listen_key = listen_key_func ()
682+ elif socket_type == 'margin' : # cross-margin
546683 listen_key_func = self ._client .margin_stream_get_listen_key
547684 callback = self ._account_callbacks [socket_type ]
548- listen_key = listen_key_func ()
685+ listen_key = listen_key_func ()
686+ else : # isolated margin
687+ listen_key_func = self ._client .isolated_margin_stream_get_listen_key
688+ callback = self ._account_callbacks .get (socket_type , None )
689+ listen_key = listen_key_func (socket_type ) # Passing symbol for islation margin
549690 if listen_key != self ._listen_keys [socket_type ]:
550691 self ._start_account_socket (socket_type , listen_key , callback )
551692
@@ -565,18 +706,26 @@ def stop_socket(self, conn_key):
565706 self ._conns [conn_key ].disconnect ()
566707 del (self ._conns [conn_key ])
567708
568- # check if we have a user stream socket
569- if len (conn_key ) >= 60 and conn_key [:60 ] == self ._listen_keys ['user' ]:
570- self ._stop_account_socket ('user' )
709+ # OBSOLETE - removed when adding isolated margin. Loop over keys instead
710+ # # check if we have a user stream socket
711+ # if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys['user']:
712+ # self._stop_account_socket('user')
713+
714+ # # or a margin stream socket
715+ # if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys['margin']:
716+ # self._stop_account_socket('margin')
717+
718+ # NEW - Loop over keys in _listen_keys dictionary to find a match on
719+ # user, cross-margin and isolated margin:
720+ for key , value in self ._listen_keys .items ():
721+ if len (conn_key ) >= 60 and conn_key [:60 ] == value :
722+ self ._stop_account_socket (key )
571723
572- # or a margin stream socket
573- if len (conn_key ) >= 60 and conn_key [:60 ] == self ._listen_keys ['margin' ]:
574- self ._stop_account_socket ('margin' )
575724
576725 def _stop_account_socket (self , socket_type ):
577- if not self ._listen_keys [ socket_type ] :
726+ if not self ._listen_keys . get ( socket_type , None ) :
578727 return
579- if self ._timers [ socket_type ] :
728+ if self ._timers . get ( socket_type , None ) :
580729 self ._timers [socket_type ].cancel ()
581730 self ._timers [socket_type ] = None
582731 self ._listen_keys [socket_type ] = None
0 commit comments