# -*- coding: utf-8 -*- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code import ccxt.async_support from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp import hashlib from ccxt.base.types import Any, Balances, Int, Liquidation, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, Trade from ccxt.async_support.base.ws.client import Client from typing import List from ccxt.base.errors import ArgumentsRequired from ccxt.base.errors import BadRequest from ccxt.base.errors import NotSupported from ccxt.base.errors import ChecksumError from ccxt.base.precise import Precise class binance(ccxt.async_support.binance): def describe(self) -> Any: superDescribe = super(binance, self).describe() return self.deep_extend(superDescribe, self.describe_data()) def describe_data(self): return { 'has': { 'ws': True, 'watchBalance': True, 'watchLiquidations': True, 'watchLiquidationsForSymbols': True, 'watchMyLiquidations': True, 'watchMyLiquidationsForSymbols': True, 'watchBidsAsks': True, 'watchMyTrades': True, 'watchOHLCV': True, 'watchOHLCVForSymbols': True, 'watchOrderBook': True, 'watchOrderBookForSymbols': True, 'watchOrders': True, 'watchOrdersForSymbols': True, 'watchPositions': True, 'watchTicker': True, 'watchTickers': True, 'watchMarkPrices': True, 'watchMarkPrice': True, 'watchTrades': True, 'watchTradesForSymbols': True, 'createOrderWs': True, 'editOrderWs': True, 'cancelOrderWs': True, 'cancelOrdersWs': False, 'cancelAllOrdersWs': True, 'fetchBalanceWs': True, 'fetchDepositsWs': False, 'fetchMarketsWs': False, 'fetchMyTradesWs': True, 'fetchOHLCVWs': True, 'fetchOrderBookWs': True, 'fetchOpenOrdersWs': True, 'fetchOrderWs': True, 'fetchOrdersWs': True, 'fetchPositionWs': True, 'fetchPositionForSymbolWs': True, 'fetchPositionsWs': True, 'fetchTickerWs': True, 'fetchTradesWs': True, 'fetchTradingFeesWs': False, 'fetchWithdrawalsWs': False, 'unWatchTicker': True, 'unWatchTickers': True, 'unWatchOHLCV': True, 'unWatchOHLCVForSymbols': True, 'unWatchOrderBook': True, 'unWatchOrderBookForSymbols': True, 'unWatchTrades': True, 'unWatchTradesForSymbols': True, 'unWatchMyTrades': False, 'unWatchOrders': False, 'unWatchPositions': False, 'unWatchMarkPrices': True, 'unWatchMarkPrice': True, }, 'urls': { 'test': { 'ws': { 'spot': 'wss://stream.testnet.binance.vision/ws', 'margin': 'wss://stream.testnet.binance.vision/ws', 'future': 'wss://fstream.binancefuture.com/ws', 'delivery': 'wss://dstream.binancefuture.com/ws', 'ws-api': { 'spot': 'wss://ws-api.testnet.binance.vision/ws-api/v3', 'future': 'wss://testnet.binancefuture.com/ws-fapi/v1', 'delivery': 'wss://testnet.binancefuture.com/ws-dapi/v1', }, }, }, 'demo': { 'ws': { 'spot': 'wss://demo-stream.binance.com/ws', 'margin': 'wss://demo-stream.binance.com/ws', 'future': 'wss://fstream.binancefuture.com/ws', 'delivery': 'wss://dstream.binancefuture.com/ws', 'ws-api': { 'spot': 'wss://demo-ws-api.binance.com/ws-api/v3', 'future': 'wss://testnet.binancefuture.com/ws-fapi/v1', 'delivery': 'wss://testnet.binancefuture.com/ws-dapi/v1', }, }, }, 'api': { 'ws': { 'spot': 'wss://stream.binance.com:9443/ws', 'margin': 'wss://stream.binance.com:9443/ws', 'future': 'wss://fstream.binance.com/ws', 'delivery': 'wss://dstream.binance.com/ws', 'ws-api': { 'spot': 'wss://ws-api.binance.com:443/ws-api/v3', 'future': 'wss://ws-fapi.binance.com/ws-fapi/v1', 'delivery': 'wss://ws-dapi.binance.com/ws-dapi/v1', }, 'papi': 'wss://fstream.binance.com/pm/ws', }, }, 'doc': 'https://developers.binance.com/en', }, 'streaming': { 'keepAlive': 180000, }, 'options': { 'returnRateLimits': False, 'streamLimits': { 'spot': 50, # max 1024 'margin': 50, # max 1024 'future': 50, # max 200 'delivery': 50, # max 200 }, 'subscriptionLimitByStream': { 'spot': 200, 'margin': 200, 'future': 200, 'delivery': 200, }, 'streamBySubscriptionsHash': self.create_safe_dictionary(), 'streamIndex': -1, # get updates every 1000ms or 100ms # or every 0ms in real-time for futures 'watchOrderBookRate': 100, 'liquidationsLimit': 1000, 'myLiquidationsLimit': 1000, 'tradesLimit': 1000, 'ordersLimit': 1000, 'OHLCVLimit': 1000, 'requestId': self.create_safe_dictionary(), 'watchOrderBookLimit': 1000, # default limit 'watchTrades': { 'name': 'trade', # 'trade' or 'aggTrade' }, 'watchTicker': { 'name': 'ticker', # ticker or miniTicker or ticker_ }, 'watchTickers': { 'name': 'ticker', # ticker or miniTicker or ticker_ }, 'watchOHLCV': { 'name': 'kline', # or indexPriceKline or markPriceKline(coin-m futures) }, 'watchOrderBook': { 'maxRetries': 3, 'checksum': True, }, 'watchBalance': { 'fetchBalanceSnapshot': False, # or True 'awaitBalanceSnapshot': True, # whether to wait for the balance snapshot before providing updates }, 'watchLiquidationsForSymbols': { 'defaultType': 'swap', }, 'watchPositions': { 'fetchPositionsSnapshot': True, # or False 'awaitPositionsSnapshot': True, # whether to wait for the positions snapshot before providing updates }, 'wallet': 'wb', # wb = wallet balance, cw = cross balance 'listenKeyRefreshRate': 1200000, # 20 mins 'ws': { 'cost': 5, }, 'tickerChannelsMap': { '24hrTicker': 'ticker', '24hrMiniTicker': 'miniTicker', 'markPriceUpdate': 'markPrice', # rolling window tickers '1hTicker': 'ticker_1h', '4hTicker': 'ticker_4h', '1dTicker': 'ticker_1d', 'bookTicker': 'bookTicker', }, }, } def request_id(self, url): options = self.safe_dict(self.options, 'requestId', self.create_safe_dictionary()) previousValue = self.safe_integer(options, url, 0) newValue = self.sum(previousValue, 1) self.options['requestId'][url] = newValue return newValue def is_spot_url(self, client: Client): return(client.url.find('/stream') > -1) or (client.url.find('demo-stream') > -1) def stream(self, type: Str, subscriptionHash: Str, numSubscriptions=1): streamBySubscriptionsHash = self.safe_dict(self.options, 'streamBySubscriptionsHash', self.create_safe_dictionary()) stream = self.safe_string(streamBySubscriptionsHash, subscriptionHash) if stream is None: streamIndex = self.safe_integer(self.options, 'streamIndex', -1) streamLimits = self.safe_value(self.options, 'streamLimits') streamLimit = self.safe_integer(streamLimits, type) streamIndex = streamIndex + 1 normalizedIndex = streamIndex % streamLimit self.options['streamIndex'] = streamIndex stream = self.number_to_string(normalizedIndex) self.options['streamBySubscriptionsHash'][subscriptionHash] = stream subscriptionsByStreams = self.safe_value(self.options, 'numSubscriptionsByStream') if subscriptionsByStreams is None: self.options['numSubscriptionsByStream'] = self.create_safe_dictionary() subscriptionsByStream = self.safe_integer(self.options['numSubscriptionsByStream'], stream, 0) newNumSubscriptions = subscriptionsByStream + numSubscriptions subscriptionLimitByStream = self.safe_integer(self.options['subscriptionLimitByStream'], type, 200) if newNumSubscriptions > subscriptionLimitByStream: raise BadRequest(self.id + ' reached the limit of subscriptions by stream. Increase the number of streams, or increase the stream limit or subscription limit by stream if the exchange allows.') self.options['numSubscriptionsByStream'][stream] = subscriptionsByStream + numSubscriptions return stream async def watch_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Liquidation]: """ watch the public liquidations of a trading pair https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Liquidation-Order-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Liquidation-Order-Streams :param str symbol: unified CCXT market symbol :param int [since]: the earliest time in ms to fetch liquidations for :param int [limit]: the maximum number of liquidation structures to retrieve :param dict [params]: exchange specific parameters for the bitmex api endpoint :returns dict: an array of `liquidation structures ` """ return await self.watch_liquidations_for_symbols([symbol], since, limit, params) async def watch_liquidations_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Liquidation]: """ watch the public liquidations of a trading pair https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/All-Market-Liquidation-Order-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/All-Market-Liquidation-Order-Streams :param str[] symbols: list of unified market symbols :param int [since]: the earliest time in ms to fetch liquidations for :param int [limit]: the maximum number of liquidation structures to retrieve :param dict [params]: exchange specific parameters for the bitmex api endpoint :returns dict: an array of `liquidation structures ` """ await self.load_markets() subscriptionHashes = [] messageHashes = [] streamHash = 'liquidations' symbols = self.market_symbols(symbols, None, True, True) if self.is_empty(symbols): subscriptionHashes.append('!' + 'forceOrder@arr') messageHashes.append('liquidations') else: for i in range(0, len(symbols)): market = self.market(symbols[i]) subscriptionHashes.append(market['lowercaseId'] + '@forceOrder') messageHashes.append('liquidations::' + symbols[i]) streamHash += '::' + ','.join(symbols) firstMarket = self.get_market_from_symbols(symbols) type = None type, params = self.handle_market_type_and_params('watchLiquidationsForSymbols', firstMarket, params) if type == 'spot': raise BadRequest(self.id + ' watchLiquidationsForSymbols is not supported for spot symbols') subType = None subType, params = self.handle_sub_type_and_params('watchLiquidationsForSymbols', firstMarket, params) if self.isLinear(type, subType): type = 'future' elif self.isInverse(type, subType): type = 'delivery' numSubscriptions = len(subscriptionHashes) url = self.urls['api']['ws'][type] + '/' + self.stream(type, streamHash, numSubscriptions) requestId = self.request_id(url) request = { 'method': 'SUBSCRIBE', 'params': subscriptionHashes, 'id': requestId, } subscribe = { 'id': requestId, } newLiquidations = await self.watch_multiple(url, messageHashes, self.extend(request, params), subscriptionHashes, subscribe) if self.newUpdates: return newLiquidations return self.filter_by_symbols_since_limit(self.liquidations, symbols, since, limit, True) def handle_liquidation(self, client: Client, message): # # future # { # "e":"forceOrder", # "E":1698871323061, # "o":{ # "s":"BTCUSDT", # "S":"BUY", # "o":"LIMIT", # "f":"IOC", # "q":"1.437", # "p":"35100.81", # "ap":"34959.70", # "X":"FILLED", # "l":"1.437", # "z":"1.437", # "T":1698871323059 # } # } # delivery # { # "e":"forceOrder", # Event Type # "E": 1591154240950, # Event Time # "o":{ # "s":"BTCUSD_200925", # Symbol # "ps": "BTCUSD", # Pair # "S":"SELL", # Side # "o":"LIMIT", # Order Type # "f":"IOC", # Time in Force # "q":"1", # Original Quantity # "p":"9425.5", # Price # "ap":"9496.5", # Average Price # "X":"FILLED", # Order Status # "l":"1", # Order Last Filled Quantity # "z":"1", # Order Filled Accumulated Quantity # "T": 1591154240949, # Order Trade Time # } # } # rawLiquidation = self.safe_value(message, 'o', {}) marketId = self.safe_string(rawLiquidation, 's') market = self.safe_market(marketId, None, '', 'contract') symbol = market['symbol'] liquidation = self.parse_ws_liquidation(rawLiquidation, market) liquidations = self.safe_value(self.liquidations, symbol) if liquidations is None: limit = self.safe_integer(self.options, 'liquidationsLimit', 1000) liquidations = ArrayCache(limit) liquidations.append(liquidation) self.liquidations[symbol] = liquidations client.resolve([liquidation], 'liquidations') client.resolve([liquidation], 'liquidations::' + symbol) def parse_ws_liquidation(self, liquidation, market=None): # # future # { # "s":"BTCUSDT", # "S":"BUY", # "o":"LIMIT", # "f":"IOC", # "q":"1.437", # "p":"35100.81", # "ap":"34959.70", # "X":"FILLED", # "l":"1.437", # "z":"1.437", # "T":1698871323059 # } # delivery # { # "s":"BTCUSD_200925", # Symbol # "ps": "BTCUSD", # Pair # "S":"SELL", # Side # "o":"LIMIT", # Order Type # "f":"IOC", # Time in Force # "q":"1", # Original Quantity # "p":"9425.5", # Price # "ap":"9496.5", # Average Price # "X":"FILLED", # Order Status # "l":"1", # Order Last Filled Quantity # "z":"1", # Order Filled Accumulated Quantity # "T": 1591154240949, # Order Trade Time # } # myLiquidation # { # "s":"BTCUSDT", # Symbol # "c":"TEST", # Client Order Id # # special client order id: # # starts with "autoclose-": liquidation order # # "adl_autoclose": ADL auto close order # # "settlement_autoclose-": settlement order for delisting or delivery # "S":"SELL", # Side # "o":"TRAILING_STOP_MARKET", # Order Type # "f":"GTC", # Time in Force # "q":"0.001", # Original Quantity # "p":"0", # Original Price # "ap":"0", # Average Price # "sp":"7103.04", # Stop Price. Please ignore with TRAILING_STOP_MARKET order # "x":"NEW", # Execution Type # "X":"NEW", # Order Status # "i":8886774, # Order Id # "l":"0", # Order Last Filled Quantity # "z":"0", # Order Filled Accumulated Quantity # "L":"0", # Last Filled Price # "N":"USDT", # Commission Asset, will not push if no commission # "n":"0", # Commission, will not push if no commission # "T":1568879465650, # Order Trade Time # "t":0, # Trade Id # "b":"0", # Bids Notional # "a":"9.91", # Ask Notional # "m":false, # Is self trade the maker side? # "R":false, # Is self reduce only # "wt":"CONTRACT_PRICE", # Stop Price Working Type # "ot":"TRAILING_STOP_MARKET",// Original Order Type # "ps":"LONG", # Position Side # "cp":false, # If Close-All, pushed with conditional order # "AP":"7476.89", # Activation Price, only puhed with TRAILING_STOP_MARKET order # "cr":"5.0", # Callback Rate, only puhed with TRAILING_STOP_MARKET order # "pP": False, # If price protection is turned on # "si": 0, # ignore # "ss": 0, # ignore # "rp":"0", # Realized Profit of the trade # "V":"EXPIRE_TAKER", # STP mode # "pm":"OPPONENT", # Price match mode # "gtd":0 # TIF GTD order auto cancel time # } # marketId = self.safe_string(liquidation, 's') market = self.safe_market(marketId, market, None, 'swap') timestamp = self.safe_integer(liquidation, 'T') return self.safe_liquidation({ 'info': liquidation, 'symbol': self.safe_symbol(marketId, market), 'contracts': self.safe_number(liquidation, 'l'), 'contractSize': self.safe_number(market, 'contractSize'), 'price': self.safe_number(liquidation, 'ap'), 'side': self.safe_string_lower(liquidation, 'S'), 'baseValue': None, 'quoteValue': None, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), }) async def watch_my_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Liquidation]: """ watch the private liquidations of a trading pair https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams/Event-Order-Update https://developers.binance.com/docs/derivatives/coin-margined-futures/user-data-streams/Event-Order-Update :param str symbol: unified CCXT market symbol :param int [since]: the earliest time in ms to fetch liquidations for :param int [limit]: the maximum number of liquidation structures to retrieve :param dict [params]: exchange specific parameters for the bitmex api endpoint :returns dict: an array of `liquidation structures ` """ return self.watch_my_liquidations_for_symbols([symbol], since, limit, params) async def watch_my_liquidations_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Liquidation]: """ watch the private liquidations of a trading pair https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams/Event-Order-Update https://developers.binance.com/docs/derivatives/coin-margined-futures/user-data-streams/Event-Order-Update :param str[] symbols: list of unified market symbols :param int [since]: the earliest time in ms to fetch liquidations for :param int [limit]: the maximum number of liquidation structures to retrieve :param dict [params]: exchange specific parameters for the bitmex api endpoint :returns dict: an array of `liquidation structures ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, True, True) market = self.get_market_from_symbols(symbols) messageHashes = ['myLiquidations'] if not self.is_empty(symbols): for i in range(0, len(symbols)): symbol = symbols[i] messageHashes.append('myLiquidations::' + symbol) type = None type, params = self.handle_market_type_and_params('watchMyLiquidationsForSymbols', market, params) subType = None subType, params = self.handle_sub_type_and_params('watchMyLiquidationsForSymbols', market, params) if self.isLinear(type, subType): type = 'future' elif self.isInverse(type, subType): type = 'delivery' await self.authenticate(params) url = self.urls['api']['ws'][type] + '/' + self.options[type]['listenKey'] message = None newLiquidations = await self.watch_multiple(url, messageHashes, message, [type]) if self.newUpdates: return newLiquidations return self.filter_by_symbols_since_limit(self.liquidations, symbols, since, limit) def handle_my_liquidation(self, client: Client, message): # # { # "s":"BTCUSDT", # Symbol # "c":"TEST", # Client Order Id # # special client order id: # # starts with "autoclose-": liquidation order # # "adl_autoclose": ADL auto close order # # "settlement_autoclose-": settlement order for delisting or delivery # "S":"SELL", # Side # "o":"TRAILING_STOP_MARKET", # Order Type # "f":"GTC", # Time in Force # "q":"0.001", # Original Quantity # "p":"0", # Original Price # "ap":"0", # Average Price # "sp":"7103.04", # Stop Price. Please ignore with TRAILING_STOP_MARKET order # "x":"NEW", # Execution Type # "X":"NEW", # Order Status # "i":8886774, # Order Id # "l":"0", # Order Last Filled Quantity # "z":"0", # Order Filled Accumulated Quantity # "L":"0", # Last Filled Price # "N":"USDT", # Commission Asset, will not push if no commission # "n":"0", # Commission, will not push if no commission # "T":1568879465650, # Order Trade Time # "t":0, # Trade Id # "b":"0", # Bids Notional # "a":"9.91", # Ask Notional # "m":false, # Is self trade the maker side? # "R":false, # Is self reduce only # "wt":"CONTRACT_PRICE", # Stop Price Working Type # "ot":"TRAILING_STOP_MARKET",// Original Order Type # "ps":"LONG", # Position Side # "cp":false, # If Close-All, pushed with conditional order # "AP":"7476.89", # Activation Price, only puhed with TRAILING_STOP_MARKET order # "cr":"5.0", # Callback Rate, only puhed with TRAILING_STOP_MARKET order # "pP": False, # If price protection is turned on # "si": 0, # ignore # "ss": 0, # ignore # "rp":"0", # Realized Profit of the trade # "V":"EXPIRE_TAKER", # STP mode # "pm":"OPPONENT", # Price match mode # "gtd":0 # TIF GTD order auto cancel time # } # orderType = self.safe_string(message, 'o') if orderType != 'LIQUIDATION': return marketId = self.safe_string(message, 's') market = self.safe_market(marketId, None, None, 'swap') symbol = self.safe_symbol(marketId, market) liquidation = self.parse_ws_liquidation(message, market) myLiquidations = self.safe_value(self.myLiquidations, symbol) if myLiquidations is None: limit = self.safe_integer(self.options, 'myLiquidationsLimit', 1000) myLiquidations = ArrayCache(limit) myLiquidations.append(liquidation) self.myLiquidations[symbol] = myLiquidations client.resolve([liquidation], 'myLiquidations') client.resolve([liquidation], 'myLiquidations::' + symbol) async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#partial-book-depth-streams https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#diff-depth-stream https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams :param str symbol: unified symbol of the market to fetch the order book for :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ # # todo add support for -snapshots(depth) # https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#partial-book-depth-streams # @depth@100ms or @depth(1000ms) # valid are 5, 10, or 20 # # default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000 # # notice the differences between trading futures and spot trading # the algorithms use different urls in step 1 # delta caching and merging also differs in steps 4, 5, 6 # # spot/margin # https://binance-docs.github.io/apidocs/spot/en/#how-to-manage-a-local-order-book-correctly # # 1. Open a stream to wss://stream.binance.com:9443/ws/bnbbtc@depth. # 2. Buffer the events you receive from the stream. # 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 . # 4. Drop any event where u is <= lastUpdateId in the snapshot. # 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1. # 6. While listening to the stream, each new event's U should be equal to the previous event's u+1. # 7. The data in each event is the absolute quantity for a price level. # 8. If the quantity is 0, remove the price level. # 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal. # # futures # https://binance-docs.github.io/apidocs/futures/en/#how-to-manage-a-local-order-book-correctly # # 1. Open a stream to wss://fstream.binance.com/stream?streams=btcusdt@depth. # 2. Buffer the events you receive from the stream. For same price, latest received update covers the previous one. # 3. Get a depth snapshot from https://fapi.binance.com/fapi/v1/depth?symbol=BTCUSDT&limit=1000 . # 4. Drop any event where u is < lastUpdateId in the snapshot. # 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId # 6. While listening to the stream, each new event's pu should be equal to the previous event's u, otherwise initialize the process from step 3. # 7. The data in each event is the absolute quantity for a price level. # 8. If the quantity is 0, remove the price level. # 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal. # return await self.watch_order_book_for_symbols([symbol], limit, params) async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook: """ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#partial-book-depth-streams https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#diff-depth-stream https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams :param str[] symbols: unified array of symbols :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() symbols = self.market_symbols(symbols, None, False, True, True) firstMarket = self.market(symbols[0]) type = firstMarket['type'] if firstMarket['contract']: type = 'future' if firstMarket['linear'] else 'delivery' name = 'depth' streamHash = 'multipleOrderbook' if symbols is not None: symbolsLength = len(symbols) if symbolsLength > 200: raise BadRequest(self.id + ' watchOrderBookForSymbols() accepts 200 symbols at most. To watch more symbols call watchOrderBookForSymbols() multiple times') streamHash += '::' + ','.join(symbols) watchOrderBookRate = self.safe_string(self.options, 'watchOrderBookRate', '100') subParams = [] messageHashes = [] for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) messageHashes.append('orderbook::' + symbol) subscriptionHash = market['lowercaseId'] + '@' + name symbolHash = subscriptionHash + '@' + watchOrderBookRate + 'ms' subParams.append(symbolHash) messageHashesLength = len(messageHashes) url = self.urls['api']['ws'][type] + '/' + self.stream(type, streamHash, messageHashesLength) requestId = self.request_id(url) request: dict = { 'method': 'SUBSCRIBE', 'params': subParams, 'id': requestId, } subscription: dict = { 'id': str(requestId), 'name': name, 'symbols': symbols, 'method': self.handle_order_book_subscription, 'limit': limit, 'type': type, 'params': params, } orderbook = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes, subscription) return orderbook.limit() async def un_watch_order_book_for_symbols(self, symbols: List[str], params={}) -> Any: """ unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#partial-book-depth-streams https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#diff-depth-stream https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams :param str[] symbols: unified array of symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() symbols = self.market_symbols(symbols, None, False, True, True) firstMarket = self.market(symbols[0]) type = firstMarket['type'] if firstMarket['contract']: type = 'future' if firstMarket['linear'] else 'delivery' name = 'depth' streamHash = 'multipleOrderbook' if symbols is not None: streamHash += '::' + ','.join(symbols) watchOrderBookRate = self.safe_string(self.options, 'watchOrderBookRate', '100') subParams = [] subMessageHashes = [] messageHashes = [] for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subMessageHashes.append('orderbook::' + symbol) messageHashes.append('unsubscribe:orderbook:' + symbol) subscriptionHash = market['lowercaseId'] + '@' + name symbolHash = subscriptionHash + '@' + watchOrderBookRate + 'ms' subParams.append(symbolHash) messageHashesLength = len(subMessageHashes) url = self.urls['api']['ws'][type] + '/' + self.stream(type, streamHash, messageHashesLength) requestId = self.request_id(url) request: dict = { 'method': 'UNSUBSCRIBE', 'params': subParams, 'id': requestId, } subscription: dict = { 'unsubscribe': True, 'id': str(requestId), 'symbols': symbols, 'subMessageHashes': subMessageHashes, 'messageHashes': messageHashes, 'topic': 'orderbook', } return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes, subscription) async def un_watch_order_book(self, symbol: str, params={}) -> Any: """ unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#partial-book-depth-streams https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#diff-depth-stream https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Partial-Book-Depth-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Diff-Book-Depth-Streams :param str symbol: unified array of symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ return await self.un_watch_order_book_for_symbols([symbol], params) async def fetch_order_book_ws(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#order-book https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/websocket-api/Order-Book :param str symbol: unified symbol of the market to fetch the order book for :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() market = self.market(symbol) payload: dict = { 'symbol': market['id'], } if limit is not None: payload['limit'] = limit marketType = self.get_market_type('fetchOrderBookWs', market, params) if marketType != 'future': raise BadRequest(self.id + ' fetchOrderBookWs only supports swap markets') url = self.urls['api']['ws']['ws-api'][marketType] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'createOrderWs', 'returnRateLimits', False) payload['returnRateLimits'] = returnRateLimits params = self.omit(params, 'test') message: dict = { 'id': messageHash, 'method': 'depth', 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_fetch_order_book, } orderbook = await self.watch(url, messageHash, message, messageHash, subscription) orderbook['symbol'] = market['symbol'] return orderbook def handle_fetch_order_book(self, client: Client, message): # # { # "id":"51e2affb-0aba-4821-ba75-f2625006eb43", # "status":200, # "result":{ # "lastUpdateId":1027024, # "E":1589436922972, # "T":1589436922959, # "bids":[ # [ # "4.00000000", # "431.00000000" # ] # ], # "asks":[ # [ # "4.00000200", # "12.00000000" # ] # ] # } # } # messageHash = self.safe_string(message, 'id') result = self.safe_dict(message, 'result') timestamp = self.safe_integer(result, 'T') orderbook = self.parse_order_book(result, None, timestamp) orderbook['nonce'] = self.safe_integer_2(result, 'lastUpdateId', 'u') client.resolve(orderbook, messageHash) async def fetch_order_book_snapshot(self, client, message, subscription): symbol = self.safe_string(subscription, 'symbol') messageHash = 'orderbook::' + symbol try: defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000) type = self.safe_value(subscription, 'type') limit = self.safe_integer(subscription, 'limit', defaultLimit) params = self.safe_value(subscription, 'params') # 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 . # todo: self is a synch blocking call - make it async # default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000 snapshot = await self.fetch_rest_order_book_safe(symbol, limit, params) if self.safe_value(self.orderbooks, symbol) is None: # if the orderbook is dropped before the snapshot is received return orderbook = self.orderbooks[symbol] orderbook.reset(snapshot) # unroll the accumulated deltas messages = orderbook.cache orderbook.cache = [] for i in range(0, len(messages)): messageItem = messages[i] U = self.safe_integer(messageItem, 'U') u = self.safe_integer(messageItem, 'u') pu = self.safe_integer(messageItem, 'pu') if type == 'future': # 4. Drop any event where u is < lastUpdateId in the snapshot if u < orderbook['nonce']: continue # 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId if (U <= orderbook['nonce']) and (u >= orderbook['nonce']) or (pu == orderbook['nonce']): self.handle_order_book_message(client, messageItem, orderbook) else: # 4. Drop any event where u is <= lastUpdateId in the snapshot if u <= orderbook['nonce']: continue # 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1 if ((U - 1) <= orderbook['nonce']) and ((u - 1) >= orderbook['nonce']): self.handle_order_book_message(client, messageItem, orderbook) self.orderbooks[symbol] = orderbook client.resolve(orderbook, messageHash) except Exception as e: del client.subscriptions[messageHash] client.reject(e, messageHash) def handle_delta(self, bookside, delta): price = self.safe_float(delta, 0) amount = self.safe_float(delta, 1) bookside.store(price, amount) def handle_deltas(self, bookside, deltas): for i in range(0, len(deltas)): self.handle_delta(bookside, deltas[i]) def handle_order_book_message(self, client: Client, message, orderbook): u = self.safe_integer(message, 'u') self.handle_deltas(orderbook['asks'], self.safe_value(message, 'a', [])) self.handle_deltas(orderbook['bids'], self.safe_value(message, 'b', [])) orderbook['nonce'] = u timestamp = self.safe_integer(message, 'E') orderbook['timestamp'] = timestamp orderbook['datetime'] = self.iso8601(timestamp) return orderbook def handle_order_book(self, client: Client, message): # # initial snapshot is fetched with ccxt's fetchOrderBook # the feed does not include a snapshot, just the deltas # # { # "e": "depthUpdate", # Event type # "E": 1577554482280, # Event time # "s": "BNBBTC", # Symbol # "U": 157, # First update ID in event # "u": 160, # Final update ID in event # "b": [ # bids # ["0.0024", "10"], # price, size # ], # "a": [ # asks # ["0.0026", "100"], # price, size # ] # } # isSpot = self.is_spot_url(client) marketType = 'spot' if (isSpot) else 'swap' marketId = self.safe_string(message, 's') market = self.safe_market(marketId, None, None, marketType) symbol = market['symbol'] messageHash = 'orderbook::' + symbol if not (symbol in self.orderbooks): # # https://github.com/ccxt/ccxt/issues/6672 # # Sometimes Binance sends the first delta before the subscription # confirmation arrives. At that point the orderbook is not # initialized yet and the snapshot has not been requested yet # therefore it is safe to drop these premature messages. # return orderbook = self.orderbooks[symbol] nonce = self.safe_integer(orderbook, 'nonce') if nonce is None: # 2. Buffer the events you receive from the stream. orderbook.cache.append(message) else: try: U = self.safe_integer(message, 'U') u = self.safe_integer(message, 'u') pu = self.safe_integer(message, 'pu') if pu is None: # spot # 4. Drop any event where u is <= lastUpdateId in the snapshot if u > orderbook['nonce']: timestamp = self.safe_integer(orderbook, 'timestamp') conditional = None if timestamp is None: # 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1 conditional = ((U - 1) <= orderbook['nonce']) and ((u - 1) >= orderbook['nonce']) else: # 6. While listening to the stream, each new event's U should be equal to the previous event's u+1. conditional = ((U - 1) == orderbook['nonce']) if conditional: self.handle_order_book_message(client, message, orderbook) if nonce < orderbook['nonce']: client.resolve(orderbook, messageHash) else: checksum = self.handle_option('watchOrderBook', 'checksum', True) if checksum: # todo: client.reject from handleOrderBookMessage properly raise ChecksumError(self.id + ' ' + self.orderbook_checksum_message(symbol)) else: # future # 4. Drop any event where u is < lastUpdateId in the snapshot if u >= orderbook['nonce']: # 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId # 6. While listening to the stream, each new event's pu should be equal to the previous event's u, otherwise initialize the process from step 3 if (U <= orderbook['nonce']) or (pu == orderbook['nonce']): self.handle_order_book_message(client, message, orderbook) if nonce <= orderbook['nonce']: client.resolve(orderbook, messageHash) else: checksum = self.handle_option('watchOrderBook', 'checksum', True) if checksum: # todo: client.reject from handleOrderBookMessage properly raise ChecksumError(self.id + ' ' + self.orderbook_checksum_message(symbol)) except Exception as e: del self.orderbooks[symbol] del client.subscriptions[messageHash] client.reject(e, messageHash) def handle_order_book_subscription(self, client: Client, message, subscription): defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000) # messageHash = self.safe_string(subscription, 'messageHash') symbolOfSubscription = self.safe_string(subscription, 'symbol') # watchOrderBook symbols = self.safe_value(subscription, 'symbols', [symbolOfSubscription]) # watchOrderBookForSymbols limit = self.safe_integer(subscription, 'limit', defaultLimit) # handle list of symbols for i in range(0, len(symbols)): symbol = symbols[i] if symbol in self.orderbooks: del self.orderbooks[symbol] self.orderbooks[symbol] = self.order_book({}, limit) subscription = self.extend(subscription, {'symbol': symbol}) # fetch the snapshot in a separate async call self.spawn(self.fetch_order_book_snapshot, client, message, subscription) def handle_subscription_status(self, client: Client, message): # # { # "result": null, # "id": 1574649734450 # } # id = self.safe_string(message, 'id') subscriptionsById = self.index_by(client.subscriptions, 'id') subscription = self.safe_value(subscriptionsById, id, {}) method = self.safe_value(subscription, 'method') if method is not None: method(client, message, subscription) isUnSubMessage = self.safe_bool(subscription, 'unsubscribe', False) if isUnSubMessage: self.handle_un_subscription(client, subscription) return message def handle_un_subscription(self, client: Client, subscription: dict): messageHashes = self.safe_list(subscription, 'messageHashes', []) subMessageHashes = self.safe_list(subscription, 'subMessageHashes', []) for j in range(0, len(messageHashes)): unsubHash = messageHashes[j] subHash = subMessageHashes[j] self.clean_unsubscription(client, subHash, unsubHash) self.clean_cache(subscription) async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get the list of most recent trades for a list of symbols https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#aggregate-trades https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#recent-trades https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Aggregate-Trade-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Aggregate-Trade-Streams :param str[] symbols: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.name]: the name of the method to call, 'trade' or 'aggTrade', default is 'trade' :returns dict[]: a list of `trade structures ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, False, True, True) streamHash = 'multipleTrades' if symbols is not None: symbolsLength = len(symbols) if symbolsLength > 200: raise BadRequest(self.id + ' watchTradesForSymbols() accepts 200 symbols at most. To watch more symbols call watchTradesForSymbols() multiple times') streamHash += '::' + ','.join(symbols) name = None name, params = self.handle_option_and_params(params, 'watchTradesForSymbols', 'name', 'trade') params = self.omit(params, 'callerMethodName') firstMarket = self.market(symbols[0]) type = firstMarket['type'] if firstMarket['contract']: type = 'future' if firstMarket['linear'] else 'delivery' messageHashes = [] subParams = [] for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) messageHashes.append('trade::' + symbol) rawHash = market['lowercaseId'] + '@' + name subParams.append(rawHash) query = self.omit(params, 'type') subParamsLength = len(subParams) url = self.urls['api']['ws'][type] + '/' + self.stream(type, streamHash, subParamsLength) requestId = self.request_id(url) request: dict = { 'method': 'SUBSCRIBE', 'params': subParams, 'id': requestId, } subscribe: dict = { 'id': requestId, } trades = await self.watch_multiple(url, messageHashes, self.extend(request, query), messageHashes, subscribe) if self.newUpdates: first = self.safe_value(trades, 0) tradeSymbol = self.safe_string(first, 'symbol') limit = trades.getLimit(tradeSymbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) async def un_watch_trades_for_symbols(self, symbols: List[str], params={}) -> Any: """ unsubscribes from the trades channel https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#aggregate-trades https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#recent-trades https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Aggregate-Trade-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Aggregate-Trade-Streams :param str[] symbols: unified symbol of the market to fetch trades for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.name]: the name of the method to call, 'trade' or 'aggTrade', default is 'trade' :returns dict[]: a list of `trade structures ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, False, True, True) streamHash = 'multipleTrades' if symbols is not None: symbolsLength = len(symbols) if symbolsLength > 200: raise BadRequest(self.id + ' watchTradesForSymbols() accepts 200 symbols at most. To watch more symbols call watchTradesForSymbols() multiple times') streamHash += '::' + ','.join(symbols) name = None name, params = self.handle_option_and_params(params, 'watchTradesForSymbols', 'name', 'trade') params = self.omit(params, 'callerMethodName') firstMarket = self.market(symbols[0]) type = firstMarket['type'] if firstMarket['contract']: type = 'future' if firstMarket['linear'] else 'delivery' subMessageHashes = [] subParams = [] messageHashes = [] for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subMessageHashes.append('trade::' + symbol) messageHashes.append('unsubscribe:trade:' + symbol) rawHash = market['lowercaseId'] + '@' + name subParams.append(rawHash) query = self.omit(params, 'type') subParamsLength = len(subParams) url = self.urls['api']['ws'][type] + '/' + self.stream(type, streamHash, subParamsLength) requestId = self.request_id(url) request: dict = { 'method': 'UNSUBSCRIBE', 'params': subParams, 'id': requestId, } subscription: dict = { 'unsubscribe': True, 'id': str(requestId), 'subMessageHashes': subMessageHashes, 'messageHashes': messageHashes, 'symbols': symbols, 'topic': 'trades', } return await self.watch_multiple(url, messageHashes, self.extend(request, query), messageHashes, subscription) async def un_watch_trades(self, symbol: str, params={}) -> Any: """ unsubscribes from the trades channel https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#aggregate-trades https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#recent-trades https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Aggregate-Trade-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Aggregate-Trade-Streams :param str symbol: unified symbol of the market to fetch trades for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.name]: the name of the method to call, 'trade' or 'aggTrade', default is 'trade' :returns dict[]: a list of `trade structures ` """ await self.load_markets() return await self.un_watch_trades_for_symbols([symbol], params) async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get the list of most recent trades for a particular symbol https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#aggregate-trades https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#recent-trades https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Aggregate-Trade-Streams https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Aggregate-Trade-Streams :param str symbol: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.name]: the name of the method to call, 'trade' or 'aggTrade', default is 'trade' :returns dict[]: a list of `trade structures ` """ params['callerMethodName'] = 'watchTrades' return await self.watch_trades_for_symbols([symbol], since, limit, params) def parse_ws_trade(self, trade, market=None) -> Trade: # # public watchTrades # # { # "e": "trade", # event type # "E": 1579481530911, # event time # "s": "ETHBTC", # symbol # "t": 158410082, # trade id # "p": "0.01914100", # price # "q": "0.00700000", # quantity # "b": 586187049, # buyer order id # "a": 586186710, # seller order id # "T": 1579481530910, # trade time # "m": False, # is the buyer the market maker # "M": True # binance docs say it should be ignored # } # # { # "e": "aggTrade", # Event type # "E": 123456789, # Event time # "s": "BNBBTC", # Symbol # "a": 12345, # Aggregate trade ID # "p": "0.001", # Price # "q": "100", # Quantity # "f": 100, # First trade ID # "l": 105, # Last trade ID # "T": 123456785, # Trade time # "m": True, # Is the buyer the market maker? # "M": True # Ignore # } # # private watchMyTrades spot # # { # "e": "executionReport", # "E": 1611063861489, # "s": "BNBUSDT", # "c": "m4M6AD5MF3b1ERe65l4SPq", # "S": "BUY", # "o": "MARKET", # "f": "GTC", # "q": "2.00000000", # "p": "0.00000000", # "P": "0.00000000", # "F": "0.00000000", # "g": -1, # "C": '', # "x": "TRADE", # "X": "PARTIALLY_FILLED", # "r": "NONE", # "i": 1296882607, # "l": "0.33200000", # "z": "0.33200000", # "L": "46.86600000", # "n": "0.00033200", # "N": "BNB", # "T": 1611063861488, # "t": 109747654, # "I": 2696953381, # "w": False, # "m": False, # "M": True, # "O": 1611063861488, # "Z": "15.55951200", # "Y": "15.55951200", # "Q": "0.00000000" # } # # private watchMyTrades future/delivery # # { # "s": "BTCUSDT", # "c": "pb2jD6ZQHpfzSdUac8VqMK", # "S": "SELL", # "o": "MARKET", # "f": "GTC", # "q": "0.001", # "p": "0", # "ap": "33468.46000", # "sp": "0", # "x": "TRADE", # "X": "FILLED", # "i": 13351197194, # "l": "0.001", # "z": "0.001", # "L": "33468.46", # "n": "0.00027086", # "N": "BNB", # "T": 1612095165362, # "t": 458032604, # "b": "0", # "a": "0", # "m": False, # "R": False, # "wt": "CONTRACT_PRICE", # "ot": "MARKET", # "ps": "BOTH", # "cp": False, # "rp": "0.00335000", # "pP": False, # "si": 0, # "ss": 0 # } # executionType = self.safe_string(trade, 'x') isTradeExecution = (executionType == 'TRADE') if not isTradeExecution: return self.parse_trade(trade, market) id = self.safe_string_2(trade, 't', 'a') timestamp = self.safe_integer(trade, 'T') price = self.safe_string_2(trade, 'L', 'p') amount = self.safe_string(trade, 'q') if isTradeExecution: amount = self.safe_string(trade, 'l', amount) cost = self.safe_string(trade, 'Y') if cost is None: if (price is not None) and (amount is not None): cost = Precise.string_mul(price, amount) marketId = self.safe_string(trade, 's') marketType = 'contract' if ('ps' in trade) else 'spot' symbol = self.safe_symbol(marketId, None, None, marketType) side = self.safe_string_lower(trade, 'S') takerOrMaker = None orderId = self.safe_string(trade, 'i') if 'm' in trade: if side is None: side = 'sell' if trade['m'] else 'buy' # self is reversed intentionally takerOrMaker = 'maker' if trade['m'] else 'taker' fee = None feeCost = self.safe_string(trade, 'n') if feeCost is not None: feeCurrencyId = self.safe_string(trade, 'N') feeCurrencyCode = self.safe_currency_code(feeCurrencyId) fee = { 'cost': feeCost, 'currency': feeCurrencyCode, } type = self.safe_string_lower(trade, 'o') return self.safe_trade({ 'info': trade, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'symbol': symbol, 'id': id, 'order': orderId, 'type': type, 'takerOrMaker': takerOrMaker, 'side': side, 'price': price, 'amount': amount, 'cost': cost, 'fee': fee, }) def handle_trade(self, client: Client, message): # the trade streams push raw trade information in real-time # each trade has a unique buyer and seller isSpot = self.is_spot_url(client) marketType = 'spot' if (isSpot) else 'contract' marketId = self.safe_string(message, 's') market = self.safe_market(marketId, None, None, marketType) symbol = market['symbol'] messageHash = 'trade::' + symbol trade = self.parse_ws_trade(message, market) tradesArray = self.safe_value(self.trades, symbol) if tradesArray is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) tradesArray = ArrayCache(limit) tradesArray.append(trade) self.trades[symbol] = tradesArray client.resolve(tradesArray, messageHash) async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#klines https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Kline-Candlestick-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Kline-Candlestick-Streams :param str symbol: unified symbol of the market to fetch OHLCV data for :param str timeframe: the length of time each candle represents :param int [since]: timestamp in ms of the earliest candle to fetch :param int [limit]: the maximum amount of candles to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :param dict [params.timezone]: if provided, kline intervals are interpreted in that timezone instead of UTC, example '+08:00' :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] params['callerMethodName'] = 'watchOHLCV' result = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params) return result[symbol][timeframe] async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}): """ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#klines https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Kline-Candlestick-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Kline-Candlestick-Streams :param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']] :param int [since]: timestamp in ms of the earliest candle to fetch :param int [limit]: the maximum amount of candles to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :param dict [params.timezone]: if provided, kline intervals are interpreted in that timezone instead of UTC, example '+08:00' :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() klineType = None klineType, params = self.handle_param_string_2(params, 'channel', 'name', 'kline') symbols = self.get_list_from_object_values(symbolsAndTimeframes, 0) marketSymbols = self.market_symbols(symbols, None, False, False, True) firstMarket = self.market(marketSymbols[0]) type = firstMarket['type'] if firstMarket['contract']: type = 'future' if firstMarket['linear'] else 'delivery' isSpot = (type == 'spot') timezone = None timezone, params = self.handle_param_string(params, 'timezone', None) isUtc8 = (timezone is not None) and ((timezone == '+08:00') or Precise.string_eq(timezone, '8')) rawHashes = [] messageHashes = [] for i in range(0, len(symbolsAndTimeframes)): symAndTf = symbolsAndTimeframes[i] symbolString = symAndTf[0] timeframeString = symAndTf[1] interval = self.safe_string(self.timeframes, timeframeString, timeframeString) market = self.market(symbolString) marketId = market['lowercaseId'] if klineType == 'indexPriceKline': # weird behavior for index price kline we can't use the perp suffix marketId = marketId.replace('_perp', '') shouldUseUTC8 = (isUtc8 and isSpot) suffix = '@+08:00' utcSuffix = suffix if shouldUseUTC8 else '' rawHashes.append(marketId + '@' + klineType + '_' + interval + utcSuffix) messageHashes.append('ohlcv::' + market['symbol'] + '::' + timeframeString) url = self.urls['api']['ws'][type] + '/' + self.stream(type, 'multipleOHLCV') requestId = self.request_id(url) request = { 'method': 'SUBSCRIBE', 'params': rawHashes, 'id': requestId, } subscribe = { 'id': requestId, } params = self.omit(params, 'callerMethodName') res = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes, subscribe) symbol, timeframe, candles = res if self.newUpdates: limit = candles.getLimit(symbol, limit) filtered = self.filter_by_since_limit(candles, since, limit, 0, True) return self.create_ohlcv_object(symbol, timeframe, filtered) async def un_watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], params={}) -> Any: """ unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#klines https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Kline-Candlestick-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Kline-Candlestick-Streams :param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']] :param dict [params]: extra parameters specific to the exchange API endpoint :param dict [params.timezone]: if provided, kline intervals are interpreted in that timezone instead of UTC, example '+08:00' :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() klineType = None klineType, params = self.handle_param_string_2(params, 'channel', 'name', 'kline') symbols = self.get_list_from_object_values(symbolsAndTimeframes, 0) marketSymbols = self.market_symbols(symbols, None, False, False, True) firstMarket = self.market(marketSymbols[0]) type = firstMarket['type'] if firstMarket['contract']: type = 'future' if firstMarket['linear'] else 'delivery' isSpot = (type == 'spot') timezone = None timezone, params = self.handle_param_string(params, 'timezone', None) isUtc8 = (timezone is not None) and ((timezone == '+08:00') or Precise.string_eq(timezone, '8')) rawHashes = [] subMessageHashes = [] messageHashes = [] for i in range(0, len(symbolsAndTimeframes)): symAndTf = symbolsAndTimeframes[i] symbolString = symAndTf[0] timeframeString = symAndTf[1] interval = self.safe_string(self.timeframes, timeframeString, timeframeString) market = self.market(symbolString) marketId = market['lowercaseId'] if klineType == 'indexPriceKline': # weird behavior for index price kline we can't use the perp suffix marketId = marketId.replace('_perp', '') shouldUseUTC8 = (isUtc8 and isSpot) suffix = '@+08:00' utcSuffix = suffix if shouldUseUTC8 else '' rawHashes.append(marketId + '@' + klineType + '_' + interval + utcSuffix) subMessageHashes.append('ohlcv::' + market['symbol'] + '::' + timeframeString) messageHashes.append('unsubscribe::ohlcv::' + market['symbol'] + '::' + timeframeString) url = self.urls['api']['ws'][type] + '/' + self.stream(type, 'multipleOHLCV') requestId = self.request_id(url) request = { 'method': 'UNSUBSCRIBE', 'params': rawHashes, 'id': requestId, } subscribe = { 'unsubscribe': True, 'id': str(requestId), 'symbols': symbols, 'symbolsAndTimeframes': symbolsAndTimeframes, 'subMessageHashes': subMessageHashes, 'messageHashes': messageHashes, 'topic': 'ohlcv', } params = self.omit(params, 'callerMethodName') return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes, subscribe) async def un_watch_ohlcv(self, symbol: str, timeframe: str = '1m', params={}) -> Any: """ unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#klines https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Kline-Candlestick-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Kline-Candlestick-Streams :param str symbol: unified symbol of the market to fetch OHLCV data for :param str timeframe: the length of time each candle represents :param dict [params]: extra parameters specific to the exchange API endpoint :param dict [params.timezone]: if provided, kline intervals are interpreted in that timezone instead of UTC, example '+08:00' :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] params['callerMethodName'] = 'watchOHLCV' return await self.un_watch_ohlcv_for_symbols([[symbol, timeframe]], params) def handle_ohlcv(self, client: Client, message): # # { # "e": "kline", # "E": 1579482921215, # "s": "ETHBTC", # "k": { # "t": 1579482900000, # "T": 1579482959999, # "s": "ETHBTC", # "i": "1m", # "f": 158411535, # "L": 158411550, # "o": "0.01913200", # "c": "0.01913500", # "h": "0.01913700", # "l": "0.01913200", # "v": "5.08400000", # "n": 16, # "x": False, # "q": "0.09728060", # "V": "3.30200000", # "Q": "0.06318500", # "B": "0" # } # } # event = self.safe_string(message, 'e') eventMap: dict = { 'indexPrice_kline': 'indexPriceKline', 'markPrice_kline': 'markPriceKline', } event = self.safe_string(eventMap, event, event) kline = self.safe_value(message, 'k') marketId = self.safe_string_2(kline, 's', 'ps') if event == 'indexPriceKline': # indexPriceKline doesn't have the _PERP suffix marketId = self.safe_string(message, 'ps') interval = self.safe_string(kline, 'i') # use a reverse lookup in a static map instead unifiedTimeframe = self.find_timeframe(interval) parsed = [ self.safe_integer(kline, 't'), self.safe_float(kline, 'o'), self.safe_float(kline, 'h'), self.safe_float(kline, 'l'), self.safe_float(kline, 'c'), self.safe_float(kline, 'v'), ] isSpot = self.is_spot_url(client) marketType = 'spot' if (isSpot) else 'contract' symbol = self.safe_symbol(marketId, None, None, marketType) messageHash = 'ohlcv::' + symbol + '::' + unifiedTimeframe self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {}) stored = self.safe_value(self.ohlcvs[symbol], unifiedTimeframe) if stored is None: limit = self.safe_integer(self.options, 'OHLCVLimit', 1000) stored = ArrayCacheByTimestamp(limit) self.ohlcvs[symbol][unifiedTimeframe] = stored stored.append(parsed) resolveData = [symbol, unifiedTimeframe, stored] client.resolve(resolveData, messageHash) async def fetch_ticker_ws(self, symbol: str, params={}) -> Ticker: """ fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.method]: method to use can be ticker.price or ticker.book :param boolean [params.returnRateLimits]: return the rate limits for the exchange :returns dict: a `ticker structure ` """ await self.load_markets() market = self.market(symbol) payload: dict = { 'symbol': market['id'], } type = self.get_market_type('fetchTickerWs', market, params) if type != 'future': raise BadRequest(self.id + ' fetchTickerWs only supports swap markets') url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) subscription: dict = { 'method': self.handle_ticker_ws, } returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'fetchTickerWs', 'returnRateLimits', False) payload['returnRateLimits'] = returnRateLimits params = self.omit(params, 'test') method = None method, params = self.handle_option_and_params(params, 'fetchTickerWs', 'method', 'ticker.book') message: dict = { 'id': messageHash, 'method': method, 'params': self.sign_params(self.extend(payload, params)), } ticker = await self.watch(url, messageHash, message, messageHash, subscription) return ticker async def fetch_ohlcv_ws(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ query historical candlestick data containing the open, high, low, and close price, and the volume of a market https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#klines :param str symbol: unified symbol of the market to query OHLCV data for :param str timeframe: the length of time each candle represents :param int since: timestamp in ms of the earliest candle to fetch :param int limit: the maximum amount of candles to fetch :param dict params: extra parameters specific to the exchange API endpoint :param int params['until']: timestamp in ms of the earliest candle to fetch EXCHANGE SPECIFIC PARAMETERS :param str params['timeZone']: default=0(UTC) :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() market = self.market(symbol) marketType = self.get_market_type('fetchOHLCVWs', market, params) if marketType != 'spot' and marketType != 'future': raise BadRequest(self.id + ' fetchOHLCVWs only supports spot or swap markets') url = self.urls['api']['ws']['ws-api'][marketType] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'fetchOHLCVWs', 'returnRateLimits', False) payload: dict = { 'symbol': self.market_id(symbol), 'returnRateLimits': returnRateLimits, 'interval': self.timeframes[timeframe], } until = self.safe_integer(params, 'until') params = self.omit(params, 'until') if since is not None: payload['startTime'] = since if limit is not None: payload['limit'] = limit if until is not None: payload['endTime'] = until message: dict = { 'id': messageHash, 'method': 'klines', 'params': self.extend(payload, params), } subscription: dict = { 'method': self.handle_fetch_ohlcv, } return await self.watch(url, messageHash, message, messageHash, subscription) def handle_fetch_ohlcv(self, client: Client, message): # # { # "id": "1dbbeb56-8eea-466a-8f6e-86bdcfa2fc0b", # "status": 200, # "result": [ # [ # 1655971200000, # Kline open time # "0.01086000", # Open price # "0.01086600", # High price # "0.01083600", # Low price # "0.01083800", # Close price # "2290.53800000", # Volume # 1655974799999, # Kline close time # "24.85074442", # Quote asset volume # 2283, # Number of trades # "1171.64000000", # Taker buy base asset volume # "12.71225884", # Taker buy quote asset volume # "0" # Unused field, ignore # ] # ], # "rateLimits": [ # { # "rateLimitType": "REQUEST_WEIGHT", # "interval": "MINUTE", # "intervalNum": 1, # "limit": 6000, # "count": 2 # } # ] # } # result = self.safe_list(message, 'result') parsed = self.parse_ohlcvs(result) # use a reverse lookup in a static map instead messageHash = self.safe_string(message, 'id') client.resolve(parsed, messageHash) async def watch_ticker(self, symbol: str, params={}) -> Ticker: """ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#individual-symbol-mini-ticker-stream https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#all-market-mini-tickers-stream https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Individual-Symbol-Ticker-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/All-Market-Mini-Tickers-Stream https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/All-Market-Mini-Tickers-Stream https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Individual-Symbol-Ticker-Streams :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.name]: stream to use can be ticker or miniTicker :returns dict: a `ticker structure ` """ await self.load_markets() symbol = self.symbol(symbol) tickers = await self.watch_tickers([symbol], self.extend(params, {'callerMethodName': 'watchTicker'})) return tickers[symbol] async def watch_mark_price(self, symbol: str, params={}) -> Ticker: """ watches a mark price for a specific market https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Mark-Price-Stream :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.use1sFreq]: *default is True* if set to True, the mark price will be updated every second, otherwise every 3 seconds :returns dict: a `ticker structure ` """ await self.load_markets() symbol = self.symbol(symbol) tickers = await self.watch_mark_prices([symbol], self.extend(params, {'callerMethodName': 'watchMarkPrice'})) return tickers[symbol] async def watch_mark_prices(self, symbols: Strings = None, params={}) -> Tickers: """ watches the mark price for all markets https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Mark-Price-Stream-for-All-market :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.use1sFreq]: *default is True* if set to True, the mark price will be updated every second, otherwise every 3 seconds :returns dict: a `ticker structure ` """ channelName = None # for now watchmarkPrice uses the same messageHash # so it's impossible to watch both at the same time # refactor self to use different messageHashes channelName, params = self.handle_option_and_params(params, 'watchMarkPrices', 'name', 'markPrice') newTickers = await self.watch_multi_ticker_helper('watchMarkPrices', channelName, symbols, params) if self.newUpdates: return newTickers return self.filter_by_array(self.tickers, 'symbol', symbols) async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#individual-symbol-mini-ticker-stream https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#all-market-mini-tickers-stream https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Individual-Symbol-Ticker-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/All-Market-Mini-Tickers-Stream https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/All-Market-Mini-Tickers-Stream https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Individual-Symbol-Ticker-Streams :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ channelName = None channelName, params = self.handle_option_and_params(params, 'watchTickers', 'name', 'ticker') if channelName == 'bookTicker': raise BadRequest(self.id + ' deprecation notice - to subscribe for bids-asks, use watch_bids_asks() method instead') newTickers = await self.watch_multi_ticker_helper('watchTickers', channelName, symbols, params) if self.newUpdates: return newTickers return self.filter_by_array(self.tickers, 'symbol', symbols) async def un_watch_tickers(self, symbols: Strings = None, params={}) -> Any: """ unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#individual-symbol-mini-ticker-stream https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#all-market-mini-tickers-stream https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Individual-Symbol-Ticker-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/All-Market-Mini-Tickers-Stream https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/All-Market-Mini-Tickers-Stream https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Individual-Symbol-Ticker-Streams :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ channelName = None channelName, params = self.handle_option_and_params(params, 'watchTickers', 'name', 'ticker') if channelName == 'bookTicker': raise BadRequest(self.id + ' deprecation notice - to subscribe for bids-asks, use watch_bids_asks() method instead') return await self.watch_multi_ticker_helper('unWatchTickers', channelName, symbols, params, True) async def un_watch_mark_prices(self, symbols: Strings = None, params={}) -> Any: """ unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Mark-Price-Stream :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ channelName = None channelName, params = self.handle_option_and_params(params, 'watchMarkPrices', 'name', 'markPrice') await self.load_markets() return await self.watch_multi_ticker_helper('unWatchMarkPrices', channelName, symbols, params, True) async def un_watch_mark_price(self, symbol: str, params={}) -> Any: """ unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Mark-Price-Stream :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ return await self.un_watch_mark_prices([symbol], params) async def un_watch_ticker(self, symbol: str, params={}) -> Any: """ unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#individual-symbol-mini-ticker-stream https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams#all-market-mini-tickers-stream https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Individual-Symbol-Ticker-Streams https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/All-Market-Mini-Tickers-Stream https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/All-Market-Mini-Tickers-Stream https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/Individual-Symbol-Ticker-Streams :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ return await self.un_watch_tickers([symbol], params) async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers: """ watches best bid & ask for symbols https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#symbol-order-book-ticker https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams/All-Book-Tickers-Stream https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/All-Book-Tickers-Stream :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, False, True) result = await self.watch_multi_ticker_helper('watchBidsAsks', 'bookTicker', symbols, params) if self.newUpdates: return result return self.filter_by_array(self.bidsasks, 'symbol', symbols) async def watch_multi_ticker_helper(self, methodName, channelName: str, symbols: Strings = None, params={}, isUnsubscribe: bool = False): await self.load_markets() symbols = self.market_symbols(symbols, None, True, False, True) isBidAsk = (channelName == 'bookTicker') isMarkPrice = (channelName == 'markPrice') use1sFreq = self.safe_bool(params, 'use1sFreq', True) firstMarket = None marketType = None symbolsDefined = (symbols is not None) if symbolsDefined: firstMarket = self.market(symbols[0]) defaultMarket = 'swap' if (isMarkPrice) else None marketType, params = self.handle_market_type_and_params(methodName, firstMarket, params, defaultMarket) subType = None subType, params = self.handle_sub_type_and_params(methodName, firstMarket, params) rawMarketType = None if self.isLinear(marketType, subType): rawMarketType = 'future' elif self.isInverse(marketType, subType): rawMarketType = 'delivery' elif marketType == 'spot': rawMarketType = marketType else: raise NotSupported(self.id + ' ' + methodName + '() does not support options markets') if isMarkPrice and not self.in_array(marketType, ['swap', 'future']): raise NotSupported(self.id + ' ' + methodName + '() does not support ' + marketType + ' markets yet') subscriptionArgs = [] messageHashes = [] unsubscribeMessageHashes = [] suffix = '' if isMarkPrice: suffix = '@1s' if (use1sFreq) else '' unifiedPrefix: Str = None if isBidAsk: unifiedPrefix = 'bidask' elif isMarkPrice: unifiedPrefix = 'markPrice' else: unifiedPrefix = 'ticker' if symbolsDefined: for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subscriptionArgs.append(market['lowercaseId'] + '@' + channelName + suffix) messageHashes.append(unifiedPrefix + ':' + channelName + '@' + symbol) if isUnsubscribe: unsubscribeMessageHashes.append('unsubscribe::' + unifiedPrefix + ':' + channelName + '@' + symbol) else: if isBidAsk: if marketType == 'spot': raise ArgumentsRequired(self.id + ' ' + methodName + '() requires symbols for self channel for spot markets') subscriptionArgs.append('!' + channelName) elif isMarkPrice: subscriptionArgs.append('!' + channelName + '@arr' + suffix) else: subscriptionArgs.append('!' + channelName + '@arr') messageHashes.append(unifiedPrefix + 's:' + channelName) unsubscribeMessageHashes.append('unsubscribe::' + channelName) streamHash = channelName if symbolsDefined: streamHash = channelName + '::' + ','.join(symbols) url = self.urls['api']['ws'][rawMarketType] + '/' + self.stream(rawMarketType, streamHash) requestId = self.request_id(url) request: dict = { 'method': 'UNSUBSCRIBE' if isUnsubscribe else 'SUBSCRIBE', 'params': subscriptionArgs, 'id': requestId, } hashes = messageHashes subscription: dict = { 'id': requestId, } if isUnsubscribe: subscription = { 'unsubscribe': True, 'id': str(requestId), 'subMessageHashes': messageHashes, 'messageHashes': unsubscribeMessageHashes, 'symbols': symbols, 'topic': 'ticker', } hashes = unsubscribeMessageHashes result = await self.watch_multiple(url, hashes, self.deep_extend(request, params), hashes, subscription) if isUnsubscribe: return result # for efficiency, we have two type of returned structure here - if symbols array was provided, then individual # ticker dict comes in, otherwise all-tickers dict comes in if not symbolsDefined: return result else: newDict: dict = {} newDict[result['symbol']] = result return newDict def parse_ws_ticker(self, message, marketType): # markPrice # { # "e": "markPriceUpdate", # Event type # "E": 1562305380000, # Event time # "s": "BTCUSDT", # Symbol # "p": "11794.15000000", # Mark price # "i": "11784.62659091", # Index price # "P": "11784.25641265", # Estimated Settle Price, only useful in the last hour before the settlement starts # "r": "0.00038167", # Funding rate # "T": 1562306400000 # Next funding time # } # # ticker # { # "e": "24hrTicker", # event type # "E": 1579485598569, # event time # "s": "ETHBTC", # symbol # "p": "-0.00004000", # price change # "P": "-0.209", # price change percent # "w": "0.01920495", # weighted average price # "x": "0.01916500", # the price of the first trade before the 24hr rolling window # "c": "0.01912500", # last(closing) price # "Q": "0.10400000", # last quantity # "b": "0.01912200", # best bid # "B": "4.10400000", # best bid quantity # "a": "0.01912500", # best ask # "A": "0.00100000", # best ask quantity # "o": "0.01916500", # open price # "h": "0.01956500", # high price # "l": "0.01887700", # low price # "v": "173518.11900000", # base volume # "q": "3332.40703994", # quote volume # "O": 1579399197842, # open time # "C": 1579485597842, # close time # "F": 158251292, # first trade id # "L": 158414513, # last trade id # "n": 163222, # total number of trades # } # # miniTicker # { # "e": "24hrMiniTicker", # "E": 1671617114585, # "s": "MOBBUSD", # "c": "0.95900000", # "o": "0.91200000", # "h": "1.04000000", # "l": "0.89400000", # "v": "2109995.32000000", # "q": "2019254.05788000" # } # fetchTickerWs # { # "symbol":"BTCUSDT", # "price":"72606.70", # "time":1712526204284 # } # fetchTickerWs - ticker.book # { # "lastUpdateId":1027024, # "symbol":"BTCUSDT", # "bidPrice":"4.00000000", # "bidQty":"431.00000000", # "askPrice":"4.00000200", # "askQty":"9.00000000", # "time":1589437530011, # } # marketId = self.safe_string_2(message, 's', 'symbol') symbol = self.safe_symbol(marketId, None, None, marketType) event = self.safe_string(message, 'e', 'bookTicker') if event == '24hrTicker': event = 'ticker' if event == 'markPriceUpdate': # handle self separately because some fields clash with the ticker fields return self.safe_ticker({ 'symbol': symbol, 'timestamp': self.safe_integer(message, 'E'), 'datetime': self.iso8601(self.safe_integer(message, 'E')), 'info': message, 'markPrice': self.safe_string(message, 'p'), 'indexPrice': self.safe_string(message, 'i'), }) timestamp = None if event == 'bookTicker': # take the event timestamp, if available, for spot tickers it is not timestamp = self.safe_integer_2(message, 'E', 'time') else: # take the timestamp of the closing price for candlestick streams timestamp = self.safe_integer_n(message, ['C', 'E', 'time']) market = self.safe_market(marketId, None, None, marketType) last = self.safe_string_2(message, 'c', 'price') return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'high': self.safe_string(message, 'h'), 'low': self.safe_string(message, 'l'), 'bid': self.safe_string_2(message, 'b', 'bidPrice'), 'bidVolume': self.safe_string_2(message, 'B', 'bidQty'), 'ask': self.safe_string_2(message, 'a', 'askPrice'), 'askVolume': self.safe_string_2(message, 'A', 'askQty'), 'vwap': self.safe_string(message, 'w'), 'open': self.safe_string(message, 'o'), 'close': last, 'last': last, 'previousClose': self.safe_string(message, 'x'), # previous day close 'change': self.safe_string(message, 'p'), 'percentage': self.safe_string(message, 'P'), 'average': None, 'baseVolume': self.safe_string(message, 'v'), 'quoteVolume': self.safe_string(message, 'q'), 'info': message, }, market) def handle_ticker_ws(self, client: Client, message): # # ticker.price # { # "id":"1", # "status":200, # "result":{ # "symbol":"BTCUSDT", # "price":"73178.50", # "time":1712527052374 # } # } # ticker.book # { # "id":"9d32157c-a556-4d27-9866-66760a174b57", # "status":200, # "result":{ # "lastUpdateId":1027024, # "symbol":"BTCUSDT", # "bidPrice":"4.00000000", # "bidQty":"431.00000000", # "askPrice":"4.00000200", # "askQty":"9.00000000", # "time":1589437530011 # Transaction time # } # } # messageHash = self.safe_string(message, 'id') result = self.safe_value(message, 'result', {}) ticker = self.parse_ws_ticker(result, 'future') client.resolve(ticker, messageHash) def handle_bids_asks(self, client: Client, message): # # arrives one symbol dict or array of symbol dicts # # { # "u": 7488717758, # "s": "BTCUSDT", # "b": "28621.74000000", # "B": "1.43278800", # "a": "28621.75000000", # "A": "2.52500800" # } # self.handle_tickers_and_bids_asks(client, message, 'bidasks') def handle_tickers(self, client: Client, message): # # arrives one symbol dict or array of symbol dicts # # { # "e": "24hrTicker", # event type # "E": 1579485598569, # event time # "s": "ETHBTC", # symbol # "p": "-0.00004000", # price change # "P": "-0.209", # price change percent # "w": "0.01920495", # weighted average price # "x": "0.01916500", # the price of the first trade before the 24hr rolling window # "c": "0.01912500", # last(closing) price # "Q": "0.10400000", # last quantity # "b": "0.01912200", # best bid # "B": "4.10400000", # best bid quantity # "a": "0.01912500", # best ask # "A": "0.00100000", # best ask quantity # "o": "0.01916500", # open price # "h": "0.01956500", # high price # "l": "0.01887700", # low price # "v": "173518.11900000", # base volume # "q": "3332.40703994", # quote volume # "O": 1579399197842, # open time # "C": 1579485597842, # close time # "F": 158251292, # first trade id # "L": 158414513, # last trade id # "n": 163222, # total number of trades # } # self.handle_tickers_and_bids_asks(client, message, 'tickers') def handle_mark_prices(self, client: Client, message): self.handle_tickers_and_bids_asks(client, message, 'markPrices') def handle_tickers_and_bids_asks(self, client: Client, message, methodType): isSpot = self.is_spot_url(client) marketType = 'spot' if (isSpot) else 'contract' isBidAsk = (methodType == 'bidasks') isMarkPrice = (methodType == 'markPrices') unifiedPrefix: Str = None if isBidAsk: unifiedPrefix = 'bidask' elif isMarkPrice: unifiedPrefix = 'markPrice' else: unifiedPrefix = 'ticker' channelName = None resolvedMessageHashes = [] rawTickers = [] newTickers: dict = {} if isinstance(message, list): rawTickers = message else: rawTickers.append(message) for i in range(0, len(rawTickers)): ticker = rawTickers[i] event = self.safe_string(ticker, 'e') if isBidAsk: event = 'bookTicker' # in `handleMessage`, bookTicker doesn't have identifier, so manually set here channelName = self.safe_string(self.options['tickerChannelsMap'], event, event) if channelName is None: continue parsedTicker = self.parse_ws_ticker(ticker, marketType) symbol = parsedTicker['symbol'] newTickers[symbol] = parsedTicker if isBidAsk: self.bidsasks[symbol] = parsedTicker else: self.tickers[symbol] = parsedTicker messageHash = unifiedPrefix + ':' + channelName + '@' + symbol resolvedMessageHashes.append(messageHash) client.resolve(parsedTicker, messageHash) # resolve batch endpoint length = len(resolvedMessageHashes) if length > 0: batchMessageHash = unifiedPrefix + 's:' + channelName client.resolve(newTickers, batchMessageHash) def sign_params(self, params={}): self.check_required_credentials() defaultRecvWindow = self.safe_integer(self.options, 'recvWindow') if defaultRecvWindow is not None: params['recvWindow'] = defaultRecvWindow recvWindow = self.safe_integer(params, 'recvWindow') if recvWindow is not None: params['recvWindow'] = recvWindow extendedParams = self.extend({ 'timestamp': self.nonce(), 'apiKey': self.apiKey, }, params) extendedParams = self.keysort(extendedParams) query = self.rawencode(extendedParams) signature = None if self.secret.find('PRIVATE KEY') > -1: if len(self.secret) > 120: signature = self.rsa(query, self.secret, 'sha256') else: signature = self.eddsa(self.encode(query), self.secret, 'ed25519') else: signature = self.hmac(self.encode(query), self.encode(self.secret), hashlib.sha256) extendedParams['signature'] = signature return extendedParams async def ensure_user_data_stream_ws_subscribe_signature(self, marketType: str = 'spot'): """ watches best bid & ask for symbols @param marketType {string} only support on 'spot' {@link https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/user-data-stream-requests#subscribe-to-user-data-stream-through-signature-subscription-user_data Binance User Data Stream Documentation} :returns: Promise The subscription ID for the user data stream """ url = self.urls['api']['ws']['ws-api'][marketType] client = self.client(url) subscriptions = client.subscriptions subscriptionsKeys = list(subscriptions.keys()) accountType = self.get_account_type_from_subscriptions(subscriptionsKeys) if accountType == marketType: return client.subscriptions[marketType] = True requestId = self.request_id(url) messageHash = str(requestId) message: dict = { 'id': messageHash, 'method': 'userDataStream.subscribe.signature', 'params': self.sign_params({}), } subscription: dict = { 'id': messageHash, 'method': self.handle_user_data_stream_subscribe, 'subscription': marketType, } await self.watch(url, messageHash, message, messageHash, subscription) def handle_user_data_stream_subscribe(self, client: Client, message): # # { # "id": 1, # "status": 200, # "result": { # "subscriptionId": 0 # } # } # messageHash = self.safe_string(message, 'id') subscriptions = client.subscriptions subscriptionsKeys = list(subscriptions.keys()) accountType = self.get_account_type_from_subscriptions(subscriptionsKeys) result = self.safe_dict(message, 'result', {}) subscriptionId = self.safe_integer(result, 'subscriptionId') if subscriptionId is None: del client.subscriptions[accountType] client.reject(message, accountType) client.resolve(message, messageHash) async def authenticate(self, params={}): time = self.milliseconds() type = None type, params = self.handle_market_type_and_params('authenticate', None, params) subType = None subType, params = self.handle_sub_type_and_params('authenticate', None, params) isPortfolioMargin = None isPortfolioMargin, params = self.handle_option_and_params_2(params, 'authenticate', 'papi', 'portfolioMargin', False) if self.isLinear(type, subType): type = 'future' elif self.isInverse(type, subType): type = 'delivery' # For spot use WebSocket API signature subscription if type == 'spot': await self.ensure_user_data_stream_ws_subscribe_signature('spot') return marginMode = None marginMode, params = self.handle_margin_mode_and_params('authenticate', params) isIsolatedMargin = (marginMode == 'isolated') isCrossMargin = (marginMode == 'cross') or (marginMode is None) symbol = self.safe_string(params, 'symbol') params = self.omit(params, 'symbol') options = self.safe_value(self.options, type, {}) lastAuthenticatedTime = self.safe_integer(options, 'lastAuthenticatedTime', 0) listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 1200000) delay = self.sum(listenKeyRefreshRate, 10000) if time - lastAuthenticatedTime > delay: response = None if isPortfolioMargin: response = await self.papiPostListenKey(params) params = self.extend(params, {'portfolioMargin': True}) elif type == 'future': response = await self.fapiPrivatePostListenKey(params) elif type == 'delivery': response = await self.dapiPrivatePostListenKey(params) elif type == 'margin' and isCrossMargin: response = await self.sapiPostUserDataStream(params) elif isIsolatedMargin: if symbol is None: raise ArgumentsRequired(self.id + ' authenticate() requires a symbol argument for isolated margin mode') marketId = self.market_id(symbol) params = self.extend(params, {'symbol': marketId}) response = await self.sapiPostUserDataStreamIsolated(params) else: response = await self.publicPostUserDataStream(params) self.options[type] = self.extend(options, { 'listenKey': self.safe_string(response, 'listenKey'), 'lastAuthenticatedTime': time, }) self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params) async def keep_alive_listen_key(self, params={}): # https://binance-docs.github.io/apidocs/spot/en/#listen-key-spot type = self.safe_string_2(self.options, 'defaultType', 'authenticate', 'spot') type = self.safe_string(params, 'type', type) isPortfolioMargin = None isPortfolioMargin, params = self.handle_option_and_params_2(params, 'keepAliveListenKey', 'papi', 'portfolioMargin', False) subTypeInfo = self.handle_sub_type_and_params('keepAliveListenKey', None, params) subType = subTypeInfo[0] if self.isLinear(type, subType): type = 'future' elif self.isInverse(type, subType): type = 'delivery' options = self.safe_value(self.options, type, {}) listenKey = self.safe_string(options, 'listenKey') if listenKey is None: # A network error happened: we can't renew a listen key that does not exist. return request: dict = {} symbol = self.safe_string(params, 'symbol') params = self.omit(params, ['type', 'symbol']) time = self.milliseconds() try: if isPortfolioMargin: await self.papiPutListenKey(self.extend(request, params)) params = self.extend(params, {'portfolioMargin': True}) elif type == 'future': await self.fapiPrivatePutListenKey(self.extend(request, params)) elif type == 'delivery': await self.dapiPrivatePutListenKey(self.extend(request, params)) else: request['listenKey'] = listenKey if type == 'margin': request['symbol'] = symbol await self.sapiPutUserDataStream(self.extend(request, params)) else: await self.publicPutUserDataStream(self.extend(request, params)) except Exception as error: urlType = type if isPortfolioMargin: urlType = 'papi' url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey'] client = self.client(url) messageHashes = list(client.futures.keys()) for i in range(0, len(messageHashes)): messageHash = messageHashes[i] client.reject(error, messageHash) self.options[type] = self.extend(options, { 'listenKey': None, 'lastAuthenticatedTime': 0, }) return self.options[type] = self.extend(options, { 'listenKey': listenKey, 'lastAuthenticatedTime': time, }) # whether or not to schedule another listenKey keepAlive request clients = list(self.clients.values()) listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 1200000) for i in range(0, len(clients)): client = clients[i] subscriptionKeys = list(client.subscriptions.keys()) for j in range(0, len(subscriptionKeys)): subscribeType = subscriptionKeys[j] if subscribeType == type: self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params) return def set_balance_cache(self, client: Client, type, isPortfolioMargin=False): if (type in client.subscriptions) and (type in self.balance): return options = self.safe_value(self.options, 'watchBalance') fetchBalanceSnapshot = self.safe_bool(options, 'fetchBalanceSnapshot', False) if fetchBalanceSnapshot: messageHash = type + ':fetchBalanceSnapshot' if not (messageHash in client.futures): client.future(messageHash) self.spawn(self.load_balance_snapshot, client, messageHash, type, isPortfolioMargin) else: self.balance[type] = {} async def load_balance_snapshot(self, client, messageHash, type, isPortfolioMargin): params: dict = { 'type': type, } if isPortfolioMargin: params['portfolioMargin'] = True response = await self.fetch_balance(params) self.balance[type] = self.extend(response, self.safe_value(self.balance, type, {})) # don't remove the future from the .futures cache future = client.futures[messageHash] future.resolve() client.resolve(self.balance[type], type + ':balance') async def fetch_balance_ws(self, params={}) -> Balances: """ fetch balance and get the amount of funds available for trading or funds locked in orders https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api/Futures-Account-Balance https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/account-requests#account-information-user_data https://developers.binance.com/docs/derivatives/coin-margined-futures/account/websocket-api :param dict [params]: extra parameters specific to the exchange API endpoint :param str|None [params.type]: 'future', 'delivery', 'savings', 'funding', or 'spot' :param str|None [params.marginMode]: 'cross' or 'isolated', for margin trading, uses self.options.defaultMarginMode if not passed, defaults to None/None/None :param str[]|None [params.symbols]: unified market symbols, only used in isolated margin mode :param str|None [params.method]: method to use. Can be account.balance, account.status, v2/account.balance or v2/account.status :returns dict: a `balance structure ` """ await self.load_markets() type = self.get_market_type('fetchBalanceWs', None, params) if type != 'spot' and type != 'future' and type != 'delivery': raise BadRequest(self.id + ' fetchBalanceWs only supports spot or swap markets') url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'fetchBalanceWs', 'returnRateLimits', False) payload: dict = { 'returnRateLimits': returnRateLimits, } method = None method, params = self.handle_option_and_params(params, 'fetchBalanceWs', 'method', 'account.status') message: dict = { 'id': messageHash, 'method': method, 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_account_status_ws if (method == 'account.status') else self.handle_balance_ws, } return await self.watch(url, messageHash, message, messageHash, subscription) def handle_balance_ws(self, client: Client, message): # # messageHash = self.safe_string(message, 'id') rawBalance = None if isinstance(message['result'], list): # account.balance rawBalance = self.safe_list(message, 'result', []) else: # account.status result = self.safe_dict(message, 'result', {}) rawBalance = self.safe_list(result, 'assets', []) parsedBalances = self.parseBalanceCustom(rawBalance) client.resolve(parsedBalances, messageHash) def handle_account_status_ws(self, client: Client, message): # # spot # { # "id": "605a6d20-6588-4cb9-afa0-b0ab087507ba", # "status": 200, # "result": { # "makerCommission": 15, # "takerCommission": 15, # "buyerCommission": 0, # "sellerCommission": 0, # "canTrade": True, # "canWithdraw": True, # "canDeposit": True, # "commissionRates": { # "maker": "0.00150000", # "taker": "0.00150000", # "buyer": "0.00000000", # "seller": "0.00000000" # }, # "brokered": False, # "requireSelfTradePrevention": False, # "updateTime": 1660801833000, # "accountType": "SPOT", # "balances": [{ # "asset": "BNB", # "free": "0.00000000", # "locked": "0.00000000" # }, # { # "asset": "BTC", # "free": "1.3447112", # "locked": "0.08600000" # }, # { # "asset": "USDT", # "free": "1021.21000000", # "locked": "0.00000000" # } # ], # "permissions": [ # "SPOT" # ] # } # } # swap # messageHash = self.safe_string(message, 'id') result = self.safe_dict(message, 'result', {}) parsedBalances = self.parseBalanceCustom(result) client.resolve(parsedBalances, messageHash) async def fetch_position_ws(self, symbol: str, params={}) -> List[Position]: """ fetch data on an open position https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Position-Information :param str symbol: unified market symbol of the market the position is held in :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `position structure ` """ return await self.fetch_positions_ws([symbol], params) async def fetch_positions_ws(self, symbols: Strings = None, params={}) -> List[Position]: """ fetch all open positions https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Position-Information https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/websocket-api/Position-Information :param str[] [symbols]: list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.returnRateLimits]: set to True to return rate limit informations, defaults to False. :param str|None [params.method]: method to use. Can be account.position or v2/account.position :returns dict[]: a list of `position structure ` """ await self.load_markets() payload: dict = {} market = None symbols = self.market_symbols(symbols, 'swap', True, True, True) if symbols is not None: symbolsLength = len(symbols) if symbolsLength == 1: market = self.market(symbols[0]) payload['symbol'] = market['id'] type = self.get_market_type('fetchPositionsWs', market, params) if type != 'future' and type != 'delivery': raise BadRequest(self.id + ' fetchPositionsWs only supports swap markets') url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'fetchPositionsWs', 'returnRateLimits', False) payload['returnRateLimits'] = returnRateLimits method = None method, params = self.handle_option_and_params(params, 'fetchPositionsWs', 'method', 'account.position') message: dict = { 'id': messageHash, 'method': method, 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_positions_ws, } result = await self.watch(url, messageHash, message, messageHash, subscription) return self.filter_by_array_positions(result, 'symbol', symbols, False) def handle_positions_ws(self, client: Client, message): # # { # id: '1', # status: 200, # result: [ # { # symbol: 'BTCUSDT', # positionAmt: '-0.014', # entryPrice: '42901.1', # breakEvenPrice: '30138.83333142', # markPrice: '71055.98470333', # unRealizedProfit: '-394.16838584', # liquidationPrice: '137032.02272908', # leverage: '123', # maxNotionalValue: '50000', # marginType: 'cross', # isolatedMargin: '0.00000000', # isAutoAddMargin: 'false', # positionSide: 'BOTH', # notional: '-994.78378584', # isolatedWallet: '0', # updateTime: 1708906343111, # isolated: False, # adlQuantile: 2 # }, # ... # ] # } # # messageHash = self.safe_string(message, 'id') result = self.safe_list(message, 'result', []) positions = [] for i in range(0, len(result)): parsed = self.parse_position_risk(result[i]) entryPrice = self.safe_string(parsed, 'entryPrice') if (entryPrice != '0') and (entryPrice != '0.0') and (entryPrice != '0.00000000'): positions.append(parsed) client.resolve(positions, messageHash) async def watch_balance(self, params={}) -> Balances: """ watch balance and get the amount of funds available for trading or funds locked in orders :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.portfolioMargin]: set to True if you would like to watch the balance of a portfolio margin account :returns dict: a `balance structure ` """ await self.load_markets() await self.authenticate(params) defaultType = self.safe_string(self.options, 'defaultType', 'spot') type = self.safe_string(params, 'type', defaultType) subType = None subType, params = self.handle_sub_type_and_params('watchBalance', None, params) isPortfolioMargin = None isPortfolioMargin, params = self.handle_option_and_params_2(params, 'watchBalance', 'papi', 'portfolioMargin', False) if self.isLinear(type, subType): type = 'future' elif self.isInverse(type, subType): type = 'delivery' url = '' urlType = type if type == 'spot': # route to WebSocket API connection where the user data stream is subscribed url = self.urls['api']['ws']['ws-api'][type] else: if isPortfolioMargin: urlType = 'papi' url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey'] client = self.client(url) self.set_balance_cache(client, type, isPortfolioMargin) self.set_positions_cache(client, type, None, isPortfolioMargin) options = self.safe_dict(self.options, 'watchBalance') fetchBalanceSnapshot = self.safe_bool(options, 'fetchBalanceSnapshot', False) awaitBalanceSnapshot = self.safe_bool(options, 'awaitBalanceSnapshot', True) if fetchBalanceSnapshot and awaitBalanceSnapshot: await client.future(type + ':fetchBalanceSnapshot') messageHash = type + ':balance' message = None return await self.watch(url, messageHash, message, type) def handle_balance(self, client: Client, message): # # sent upon a balance update not related to orders # # { # "e": "balanceUpdate", # "E": 1629352505586, # "a": "IOTX", # "d": "0.43750000", # "T": 1629352505585 # } # # sent upon creating or filling an order # # { # "e": "outboundAccountPosition", # Event type # "E": 1564034571105, # Event Time # "u": 1564034571073, # Time of last account update # "B": [ # Balances Array # { # "a": "ETH", # Asset # "f": "10000.000000", # Free # "l": "0.000000" # Locked # } # ] # } # # future/delivery # # { # "e": "ACCOUNT_UPDATE", # Event Type # "E": 1564745798939, # Event Time # "T": 1564745798938 , # Transaction # "i": "SfsR", # Account Alias # "a": { # Update Data # "m":"ORDER", # Event reason type # "B":[ # Balances # { # "a":"BTC", # Asset # "wb":"122624.12345678", # Wallet Balance # "cw":"100.12345678" # Cross Wallet Balance # }, # ], # "P":[ # { # "s":"BTCUSD_200925", # Symbol # "pa":"0", # Position Amount # "ep":"0.0", # Entry Price # "cr":"200", #(Pre-fee) Accumulated Realized # "up":"0", # Unrealized PnL # "mt":"isolated", # Margin Type # "iw":"0.00000000", # Isolated Wallet(if isolated position) # "ps":"BOTH" # Position Side # }, # ] # } # } # externalLockUpdate # { # "e": "externalLockUpdate", # Event Type # "E": 1581557507324, # Event Time # "a": "NEO", # Asset # "d": "10.00000000", # Delta # "T": 1581557507268 # Transaction Time # } # wallet = self.safe_string(self.options, 'wallet', 'wb') # cw for cross wallet # each account is connected to a different endpoint subscriptions = client.subscriptions subscriptionsKeys = list(subscriptions.keys()) accountType = self.get_account_type_from_subscriptions(subscriptionsKeys) messageHash = accountType + ':balance' if self.balance[accountType] is None: self.balance[accountType] = {} self.balance[accountType]['info'] = message event = self.safe_string(message, 'e') if event == 'balanceUpdate': currencyId = self.safe_string(message, 'a') code = self.safe_currency_code(currencyId) account = self.account() delta = self.safe_string(message, 'd') if code in self.balance[accountType]: previousValue = self.balance[accountType][code]['free'] if not isinstance(previousValue, str): previousValue = self.number_to_string(previousValue) account['free'] = Precise.string_add(previousValue, delta) else: account['free'] = delta self.balance[accountType][code] = account else: message = self.safe_dict(message, 'a', message) B = self.safe_list(message, 'B') for i in range(0, len(B)): entry = B[i] currencyId = self.safe_string(entry, 'a') code = self.safe_currency_code(currencyId) account = self.account() account['free'] = self.safe_string(entry, 'f') account['used'] = self.safe_string(entry, 'l') account['total'] = self.safe_string(entry, wallet) self.balance[accountType][code] = account timestamp = self.safe_integer(message, 'E') self.balance[accountType]['timestamp'] = timestamp self.balance[accountType]['datetime'] = self.iso8601(timestamp) self.balance[accountType] = self.safe_balance(self.balance[accountType]) client.resolve(self.balance[accountType], messageHash) def get_account_type_from_subscriptions(self, subscriptions: List[str]) -> str: accountType = '' for i in range(0, len(subscriptions)): subscription = subscriptions[i] if (subscription == 'spot') or (subscription == 'margin') or (subscription == 'future') or (subscription == 'delivery'): accountType = subscription break return accountType def get_market_type(self, method, market, params={}): type = None type, params = self.handle_market_type_and_params(method, market, params) subType = None subType, params = self.handle_sub_type_and_params(method, market, params) if self.isLinear(type, subType): type = 'future' elif self.isInverse(type, subType): type = 'delivery' return type async def create_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order: """ create a trade order https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/trading-requests#place-new-order-trade https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/New-Order https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/websocket-api :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much of currency you want to trade in units of base currency :param float|None [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean params['test']: test order, default False :param boolean params['returnRateLimits']: set to True to return rate limit information, default False :returns dict: an `order structure ` """ await self.load_markets() market = self.market(symbol) marketType = self.get_market_type('createOrderWs', market, params) if marketType != 'spot' and marketType != 'future' and marketType != 'delivery': raise BadRequest(self.id + ' createOrderWs only supports spot or swap markets') url = self.urls['api']['ws']['ws-api'][marketType] requestId = self.request_id(url) messageHash = str(requestId) sor = self.safe_bool_2(params, 'sor', 'SOR', False) params = self.omit(params, 'sor', 'SOR') payload = self.create_order_request(symbol, type, side, amount, price, params) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'createOrderWs', 'returnRateLimits', False) payload['returnRateLimits'] = returnRateLimits test = self.safe_bool(params, 'test', False) params = self.omit(params, 'test') message: dict = { 'id': messageHash, 'method': 'order.place', 'params': self.sign_params(self.extend(payload, params)), } if test: if sor: message['method'] = 'sor.order.test' else: message['method'] = 'order.test' subscription: dict = { 'method': self.handle_order_ws, } return await self.watch(url, messageHash, message, messageHash, subscription) def handle_order_ws(self, client: Client, message): # # { # "id": 1, # "status": 200, # "result": { # "symbol": "BTCUSDT", # "orderId": 7663053, # "orderListId": -1, # "clientOrderId": "x-R4BD3S82d8959d0f5114499487a614", # "transactTime": 1687642291434, # "price": "25000.00000000", # "origQty": "0.00100000", # "executedQty": "0.00000000", # "cummulativeQuoteQty": "0.00000000", # "status": "NEW", # "timeInForce": "GTC", # "type": "LIMIT", # "side": "BUY", # "workingTime": 1687642291434, # "fills": [], # "selfTradePreventionMode": "NONE" # }, # "rateLimits": [ # { # "rateLimitType": "ORDERS", # "interval": "SECOND", # "intervalNum": 10, # "limit": 50, # "count": 1 # }, # { # "rateLimitType": "ORDERS", # "interval": "DAY", # "intervalNum": 1, # "limit": 160000, # "count": 1 # }, # { # "rateLimitType": "REQUEST_WEIGHT", # "interval": "MINUTE", # "intervalNum": 1, # "limit": 1200, # "count": 12 # } # ] # } # messageHash = self.safe_string(message, 'id') result = self.safe_dict(message, 'result', {}) order = self.parse_order(result) client.resolve(order, messageHash) def handle_orders_ws(self, client: Client, message): # # { # "id": 1, # "status": 200, # "result": [{ # "symbol": "BTCUSDT", # "orderId": 7665584, # "orderListId": -1, # "clientOrderId": "x-R4BD3S82b54769abdd3e4b57874c52", # "price": "26000.00000000", # "origQty": "0.00100000", # "executedQty": "0.00000000", # "cummulativeQuoteQty": "0.00000000", # "status": "NEW", # "timeInForce": "GTC", # "type": "LIMIT", # "side": "BUY", # "stopPrice": "0.00000000", # "icebergQty": "0.00000000", # "time": 1687642884646, # "updateTime": 1687642884646, # "isWorking": True, # "workingTime": 1687642884646, # "origQuoteOrderQty": "0.00000000", # "selfTradePreventionMode": "NONE" # }, # ... # ], # "rateLimits": [{ # "rateLimitType": "REQUEST_WEIGHT", # "interval": "MINUTE", # "intervalNum": 1, # "limit": 1200, # "count": 14 # }] # } # messageHash = self.safe_string(message, 'id') result = self.safe_list(message, 'result', []) orders = self.parse_orders(result) client.resolve(orders, messageHash) async def edit_order_ws(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}) -> Order: """ edit a trade order https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/trading-requests#cancel-and-replace-order-trade https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Modify-Order https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/websocket-api/Modify-Order :param str id: order id :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much of the currency you want to trade in units of the base currency :param float|None [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `order structure ` """ await self.load_markets() market = self.market(symbol) marketType = self.get_market_type('editOrderWs', market, params) if marketType != 'spot' and marketType != 'future' and marketType != 'delivery': raise BadRequest(self.id + ' editOrderWs only supports spot or swap markets') url = self.urls['api']['ws']['ws-api'][marketType] requestId = self.request_id(url) messageHash = str(requestId) isSwap = (marketType == 'future' or marketType == 'delivery') payload = None if marketType == 'spot': payload = self.editSpotOrderRequest(id, symbol, type, side, amount, price, params) elif isSwap: payload = self.editContractOrderRequest(id, symbol, type, side, amount, price, params) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'editOrderWs', 'returnRateLimits', False) payload['returnRateLimits'] = returnRateLimits message: dict = { 'id': messageHash, 'method': 'order.modify' if (isSwap) else 'order.cancelReplace', 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_edit_order_ws, } return await self.watch(url, messageHash, message, messageHash, subscription) def handle_edit_order_ws(self, client: Client, message): # # spot # { # "id": 1, # "status": 200, # "result": { # "cancelResult": "SUCCESS", # "newOrderResult": "SUCCESS", # "cancelResponse": { # "symbol": "BTCUSDT", # "origClientOrderId": "x-R4BD3S82813c5d7ffa594104917de2", # "orderId": 7665177, # "orderListId": -1, # "clientOrderId": "mbrnbQsQhtCXCLY45d5q7S", # "price": "26000.00000000", # "origQty": "0.00100000", # "executedQty": "0.00000000", # "cummulativeQuoteQty": "0.00000000", # "status": "CANCELED", # "timeInForce": "GTC", # "type": "LIMIT", # "side": "BUY", # "selfTradePreventionMode": "NONE" # }, # "newOrderResponse": { # "symbol": "BTCUSDT", # "orderId": 7665584, # "orderListId": -1, # "clientOrderId": "x-R4BD3S82b54769abdd3e4b57874c52", # "transactTime": 1687642884646, # "price": "26000.00000000", # "origQty": "0.00100000", # "executedQty": "0.00000000", # "cummulativeQuoteQty": "0.00000000", # "status": "NEW", # "timeInForce": "GTC", # "type": "LIMIT", # "side": "BUY", # "workingTime": 1687642884646, # "fills": [], # "selfTradePreventionMode": "NONE" # } # }, # "rateLimits": [{ # "rateLimitType": "ORDERS", # "interval": "SECOND", # "intervalNum": 10, # "limit": 50, # "count": 1 # }, # { # "rateLimitType": "ORDERS", # "interval": "DAY", # "intervalNum": 1, # "limit": 160000, # "count": 3 # }, # { # "rateLimitType": "REQUEST_WEIGHT", # "interval": "MINUTE", # "intervalNum": 1, # "limit": 1200, # "count": 12 # } # ] # } # swap # { # "id":"1", # "status":200, # "result":{ # "orderId":667061487, # "symbol":"LTCUSDT", # "status":"NEW", # "clientOrderId":"x-xcKtGhcu91a74c818749ee42c0f70", # "price":"82.00", # "avgPrice":"0.00", # "origQty":"1.000", # "executedQty":"0.000", # "cumQty":"0.000", # "cumQuote":"0.00000", # "timeInForce":"GTC", # "type":"LIMIT", # "reduceOnly":false, # "closePosition":false, # "side":"BUY", # "positionSide":"BOTH", # "stopPrice":"0.00", # "workingType":"CONTRACT_PRICE", # "priceProtect":false, # "origType":"LIMIT", # "priceMatch":"NONE", # "selfTradePreventionMode":"NONE", # "goodTillDate":0, # "updateTime":1712918927511 # } # } # messageHash = self.safe_string(message, 'id') result = self.safe_dict(message, 'result', {}) newSpotOrder = self.safe_dict(result, 'newOrderResponse') order = None if newSpotOrder is not None: order = self.parse_order(newSpotOrder) else: order = self.parse_order(result) client.resolve(order, messageHash) async def cancel_order_ws(self, id: str, symbol: Str = None, params={}) -> Order: """ cancel multiple orders https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/trading-requests#cancel-order-trade https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Cancel-Order https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/websocket-api/Cancel-Order :param str id: order id :param str [symbol]: unified market symbol, default is None :param dict [params]: extra parameters specific to the exchange API endpoint :param str|None [params.cancelRestrictions]: Supported values: ONLY_NEW - Cancel will succeed if the order status is NEW. ONLY_PARTIALLY_FILLED - Cancel will succeed if order status is PARTIALLY_FILLED. :returns dict: an list of `order structures ` """ await self.load_markets() if symbol is None: raise BadRequest(self.id + ' cancelOrderWs requires a symbol') market = self.market(symbol) type = self.get_market_type('cancelOrderWs', market, params) url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'cancelOrderWs', 'returnRateLimits', False) payload: dict = { 'symbol': self.market_id(symbol), 'returnRateLimits': returnRateLimits, } clientOrderId = self.safe_string_2(params, 'origClientOrderId', 'clientOrderId') if clientOrderId is not None: payload['origClientOrderId'] = clientOrderId else: payload['orderId'] = self.parse_to_int(id) params = self.omit(params, ['origClientOrderId', 'clientOrderId']) message: dict = { 'id': messageHash, 'method': 'order.cancel', 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_order_ws, } return await self.watch(url, messageHash, message, messageHash, subscription) async def cancel_all_orders_ws(self, symbol: Str = None, params={}): """ cancel all open orders in a market https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/trading-requests#cancel-open-orders-trade :param str [symbol]: unified market symbol of the market to cancel orders in :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `order structures ` """ await self.load_markets() market = self.market(symbol) type = self.get_market_type('cancelAllOrdersWs', market, params) if type != 'spot': raise BadRequest(self.id + ' cancelAllOrdersWs only supports spot markets') url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'cancelAllOrdersWs', 'returnRateLimits', False) payload: dict = { 'symbol': self.market_id(symbol), 'returnRateLimits': returnRateLimits, } message: dict = { 'id': messageHash, 'method': 'openOrders.cancelAll', 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_orders_ws, } return await self.watch(url, messageHash, message, messageHash, subscription) async def fetch_order_ws(self, id: str, symbol: Str = None, params={}) -> Order: """ fetches information on an order made by the user https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/trading-requests#query-order-user_data https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Query-Order https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/websocket-api/Query-Order :param str id: order id :param str [symbol]: unified symbol of the market the order was made in :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: An `order structure ` """ await self.load_markets() if symbol is None: raise BadRequest(self.id + ' cancelOrderWs requires a symbol') market = self.market(symbol) type = self.get_market_type('fetchOrderWs', market, params) if type != 'spot' and type != 'future' and type != 'delivery': raise BadRequest(self.id + ' fetchOrderWs only supports spot or swap markets') url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'fetchOrderWs', 'returnRateLimits', False) payload: dict = { 'symbol': self.market_id(symbol), 'returnRateLimits': returnRateLimits, } clientOrderId = self.safe_string_2(params, 'origClientOrderId', 'clientOrderId') if clientOrderId is not None: payload['origClientOrderId'] = clientOrderId else: payload['orderId'] = self.parse_to_int(id) message: dict = { 'id': messageHash, 'method': 'order.status', 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_order_ws, } return await self.watch(url, messageHash, message, messageHash, subscription) async def fetch_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetches information on multiple orders made by the user https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/trading-requests#order-lists :param str symbol: unified market symbol of the market orders were made in :param int|None [since]: the earliest time in ms to fetch orders for :param int|None [limit]: the maximum number of order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.orderId]: order id to begin at :param int [params.startTime]: earliest time in ms to retrieve orders for :param int [params.endTime]: latest time in ms to retrieve orders for :param int [params.limit]: the maximum number of order structures to retrieve :returns dict[]: a list of `order structures ` """ await self.load_markets() if symbol is None: raise BadRequest(self.id + ' fetchOrdersWs requires a symbol') market = self.market(symbol) type = self.get_market_type('fetchOrdersWs', market, params) if type != 'spot': raise BadRequest(self.id + ' fetchOrdersWs only supports spot markets') url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'fetchOrderWs', 'returnRateLimits', False) payload: dict = { 'symbol': self.market_id(symbol), 'returnRateLimits': returnRateLimits, } message: dict = { 'id': messageHash, 'method': 'allOrders', 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_orders_ws, } orders = await self.watch(url, messageHash, message, messageHash, subscription) return self.filter_by_symbol_since_limit(orders, symbol, since, limit) async def fetch_closed_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetch closed orders https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/trading-requests#order-lists :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch open orders for :param int [limit]: the maximum number of open orders structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `order structures ` """ orders = await self.fetch_orders_ws(symbol, since, limit, params) closedOrders = [] for i in range(0, len(orders)): order = orders[i] if order['status'] == 'closed': closedOrders.append(order) return closedOrders async def fetch_open_orders_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetch all unfilled currently open orders https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/trading-requests#current-open-orders-user_data :param str symbol: unified market symbol :param int|None [since]: the earliest time in ms to fetch open orders for :param int|None [limit]: the maximum number of open orders structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `order structures ` """ await self.load_markets() market = self.market(symbol) type = self.get_market_type('fetchOpenOrdersWs', market, params) if type != 'spot' and type != 'future': raise BadRequest(self.id + ' fetchOpenOrdersWs only supports spot or swap markets') url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'fetchOrderWs', 'returnRateLimits', False) payload: dict = { 'returnRateLimits': returnRateLimits, } if symbol is not None: payload['symbol'] = self.market_id(symbol) message: dict = { 'id': messageHash, 'method': 'openOrders.status', 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_orders_ws, } orders = await self.watch(url, messageHash, message, messageHash, subscription) return self.filter_by_symbol_since_limit(orders, symbol, since, limit) async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ watches information on multiple orders made by the user https://developers.binance.com/docs/binance-spot-api-docs/user-data-stream#order-update https://developers.binance.com/docs/margin_trading/trade-data-stream/Event-Order-Update https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams/Event-Order-Update :param str symbol: unified market symbol of the market the orders were made in :param int [since]: the earliest time in ms to fetch orders for :param int [limit]: the maximum number of order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param str|None [params.marginMode]: 'cross' or 'isolated', for spot margin :param boolean [params.portfolioMargin]: set to True if you would like to watch portfolio margin account orders :returns dict[]: a list of `order structures ` """ await self.load_markets() messageHash = 'orders' market = None if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash += ':' + symbol type = None type, params = self.handle_market_type_and_params('watchOrders', market, params) subType = None subType, params = self.handle_sub_type_and_params('watchOrders', market, params) if self.isLinear(type, subType): type = 'future' elif self.isInverse(type, subType): type = 'delivery' params = self.extend(params, {'type': type, 'symbol': symbol, 'subType': subType}) # needed inside authenticate for isolated margin await self.authenticate(params) marginMode = None marginMode, params = self.handle_margin_mode_and_params('watchOrders', params) urlType = type if (type == 'margin') or ((type == 'spot') and (marginMode is not None)): urlType = 'spot' # spot-margin shares the same stream spot isPortfolioMargin = None isPortfolioMargin, params = self.handle_option_and_params_2(params, 'watchOrders', 'papi', 'portfolioMargin', False) url = '' if type == 'spot': # route orders to ws-api user data stream url = self.urls['api']['ws']['ws-api'][type] else: if isPortfolioMargin: urlType = 'papi' url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey'] client = self.client(url) self.set_balance_cache(client, type, isPortfolioMargin) self.set_positions_cache(client, type, None, isPortfolioMargin) message = None orders = await self.watch(url, messageHash, message, type) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True) def parse_ws_order(self, order, market=None): # # spot # # { # "e": "executionReport", # Event type # "E": 1499405658658, # Event time # "s": "ETHBTC", # Symbol # "c": "mUvoqJxFIILMdfAW5iGSOW", # Client order ID # "S": "BUY", # Side # "o": "LIMIT", # Order type # "f": "GTC", # Time in force # "q": "1.00000000", # Order quantity # "p": "0.10264410", # Order price # "P": "0.00000000", # Stop price # "F": "0.00000000", # Iceberg quantity # "g": -1, # OrderListId # "C": null, # Original client order ID; This is the ID of the order being canceled # "x": "NEW", # Current execution type # "X": "NEW", # Current order status # "r": "NONE", # Order reject reason; will be an error code. # "i": 4293153, # Order ID # "l": "0.00000000", # Last executed quantity # "z": "0.00000000", # Cumulative filled quantity # "L": "0.00000000", # Last executed price # "n": "0", # Commission amount # "N": null, # Commission asset # "T": 1499405658657, # Transaction time # "t": -1, # Trade ID # "I": 8641984, # Ignore # "w": True, # Is the order on the book? # "m": False, # Is self trade the maker side? # "M": False, # Ignore # "O": 1499405658657, # Order creation time # "Z": "0.00000000", # Cumulative quote asset transacted quantity # "Y": "0.00000000" # Last quote asset transacted quantity(i.e. lastPrice * lastQty), # "Q": "0.00000000" # Quote Order Qty # } # # future # # { # "s":"BTCUSDT", # Symbol # "c":"TEST", # Client Order Id # # special client order id: # # starts with "autoclose-": liquidation order # # "adl_autoclose": ADL auto close order # "S":"SELL", # Side # "o":"TRAILING_STOP_MARKET", # Order Type # "f":"GTC", # Time in Force # "q":"0.001", # Original Quantity # "p":"0", # Original Price # "ap":"0", # Average Price # "sp":"7103.04", # Stop Price. Please ignore with TRAILING_STOP_MARKET order # "x":"NEW", # Execution Type # "X":"NEW", # Order Status # "i":8886774, # Order Id # "l":"0", # Order Last Filled Quantity # "z":"0", # Order Filled Accumulated Quantity # "L":"0", # Last Filled Price # "N":"USDT", # Commission Asset, will not push if no commission # "n":"0", # Commission, will not push if no commission # "T":1568879465651, # Order Trade Time # "t":0, # Trade Id # "b":"0", # Bids Notional # "a":"9.91", # Ask Notional # "m":false, # Is self trade the maker side? # "R":false, # Is self reduce only # "wt":"CONTRACT_PRICE", # Stop Price Working Type # "ot":"TRAILING_STOP_MARKET", # Original Order Type # "ps":"LONG", # Position Side # "cp":false, # If Close-All, pushed with conditional order # "AP":"7476.89", # Activation Price, only puhed with TRAILING_STOP_MARKET order # "cr":"5.0", # Callback Rate, only puhed with TRAILING_STOP_MARKET order # "rp":"0" # Realized Profit of the trade # } # executionType = self.safe_string(order, 'x') orderId = self.safe_string(order, 'i') marketId = self.safe_string(order, 's') marketType = 'contract' if ('ps' in order) else 'spot' symbol = self.safe_symbol(marketId, None, None, marketType) timestamp = self.safe_integer(order, 'O') T = self.safe_integer(order, 'T') lastTradeTimestamp = None if executionType == 'NEW' or executionType == 'AMENDMENT' or executionType == 'CANCELED': if timestamp is None: timestamp = T elif executionType == 'TRADE': lastTradeTimestamp = T lastUpdateTimestamp = T fee = None feeCost = self.safe_string(order, 'n') if (feeCost is not None) and (Precise.string_gt(feeCost, '0')): feeCurrencyId = self.safe_string(order, 'N') feeCurrency = self.safe_currency_code(feeCurrencyId) fee = { 'cost': feeCost, 'currency': feeCurrency, } price = self.safe_string(order, 'p') amount = self.safe_string(order, 'q') side = self.safe_string_lower(order, 'S') type = self.safe_string_lower(order, 'o') filled = self.safe_string(order, 'z') cost = self.safe_string(order, 'Z') average = self.safe_string(order, 'ap') rawStatus = self.safe_string(order, 'X') status = self.parse_order_status(rawStatus) trades = None clientOrderId = self.safe_string(order, 'C') if (clientOrderId is None) or (len(clientOrderId) == 0): clientOrderId = self.safe_string(order, 'c') stopPrice = self.safe_string_2(order, 'P', 'sp') timeInForce = self.safe_string(order, 'f') if timeInForce == 'GTX': # GTX means "Good Till Crossing" and is an equivalent way of saying Post Only timeInForce = 'PO' return self.safe_order({ 'info': order, 'symbol': symbol, 'id': orderId, 'clientOrderId': clientOrderId, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': lastTradeTimestamp, 'lastUpdateTimestamp': lastUpdateTimestamp, 'type': type, 'timeInForce': timeInForce, 'postOnly': None, 'reduceOnly': self.safe_bool(order, 'R'), 'side': side, 'price': price, 'stopPrice': stopPrice, 'triggerPrice': stopPrice, 'amount': amount, 'cost': cost, 'average': average, 'filled': filled, 'remaining': None, 'status': status, 'fee': fee, 'trades': trades, }) def handle_order_update(self, client: Client, message): # # spot # # { # "e": "executionReport", # Event type # "E": 1499405658658, # Event time # "s": "ETHBTC", # Symbol # "c": "mUvoqJxFIILMdfAW5iGSOW", # Client order ID # "S": "BUY", # Side # "o": "LIMIT", # Order type # "f": "GTC", # Time in force # "q": "1.00000000", # Order quantity # "p": "0.10264410", # Order price # "P": "0.00000000", # Stop price # "F": "0.00000000", # Iceberg quantity # "g": -1, # OrderListId # "C": null, # Original client order ID; This is the ID of the order being canceled # "x": "NEW", # Current execution type # "X": "NEW", # Current order status # "r": "NONE", # Order reject reason; will be an error code. # "i": 4293153, # Order ID # "l": "0.00000000", # Last executed quantity # "z": "0.00000000", # Cumulative filled quantity # "L": "0.00000000", # Last executed price # "n": "0", # Commission amount # "N": null, # Commission asset # "T": 1499405658657, # Transaction time # "t": -1, # Trade ID # "I": 8641984, # Ignore # "w": True, # Is the order on the book? # "m": False, # Is self trade the maker side? # "M": False, # Ignore # "O": 1499405658657, # Order creation time # "Z": "0.00000000", # Cumulative quote asset transacted quantity # "Y": "0.00000000" # Last quote asset transacted quantity(i.e. lastPrice * lastQty), # "Q": "0.00000000" # Quote Order Qty # } # # future # # { # "e":"ORDER_TRADE_UPDATE", # Event Type # "E":1568879465651, # Event Time # "T":1568879465650, # Trasaction Time # "o": { # "s":"BTCUSDT", # Symbol # "c":"TEST", # Client Order Id # # special client order id: # # starts with "autoclose-": liquidation order # # "adl_autoclose": ADL auto close order # "S":"SELL", # Side # "o":"TRAILING_STOP_MARKET", # Order Type # "f":"GTC", # Time in Force # "q":"0.001", # Original Quantity # "p":"0", # Original Price # "ap":"0", # Average Price # "sp":"7103.04", # Stop Price. Please ignore with TRAILING_STOP_MARKET order # "x":"NEW", # Execution Type # "X":"NEW", # Order Status # "i":8886774, # Order Id # "l":"0", # Order Last Filled Quantity # "z":"0", # Order Filled Accumulated Quantity # "L":"0", # Last Filled Price # "N":"USDT", # Commission Asset, will not push if no commission # "n":"0", # Commission, will not push if no commission # "T":1568879465651, # Order Trade Time # "t":0, # Trade Id # "b":"0", # Bids Notional # "a":"9.91", # Ask Notional # "m":false, # Is self trade the maker side? # "R":false, # Is self reduce only # "wt":"CONTRACT_PRICE", # Stop Price Working Type # "ot":"TRAILING_STOP_MARKET", # Original Order Type # "ps":"LONG", # Position Side # "cp":false, # If Close-All, pushed with conditional order # "AP":"7476.89", # Activation Price, only puhed with TRAILING_STOP_MARKET order # "cr":"5.0", # Callback Rate, only puhed with TRAILING_STOP_MARKET order # "rp":"0" # Realized Profit of the trade # } # } # e = self.safe_string(message, 'e') if e == 'ORDER_TRADE_UPDATE': message = self.safe_dict(message, 'o', message) self.handle_my_trade(client, message) self.handle_order(client, message) self.handle_my_liquidation(client, message) async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]: """ watch all open positions :param str[]|None symbols: list of unified market symbols :param number [since]: since timestamp :param number [limit]: limit :param dict params: extra parameters specific to the exchange API endpoint :param boolean [params.portfolioMargin]: set to True if you would like to watch positions in a portfolio margin account :returns dict[]: a list of `position structure ` """ await self.load_markets() market = None messageHash = '' symbols = self.market_symbols(symbols) if not self.is_empty(symbols): market = self.get_market_from_symbols(symbols) messageHash = '::' + ','.join(symbols) type = None type, params = self.handle_market_type_and_params('watchPositions', market, params) if type == 'spot' or type == 'margin': type = 'future' subType = None subType, params = self.handle_sub_type_and_params('watchPositions', market, params) if self.isLinear(type, subType): type = 'future' elif self.isInverse(type, subType): type = 'delivery' marketTypeObject: dict = {} marketTypeObject['type'] = type marketTypeObject['subType'] = subType await self.authenticate(self.extend(marketTypeObject, params)) messageHash = type + ':positions' + messageHash isPortfolioMargin = None isPortfolioMargin, params = self.handle_option_and_params_2(params, 'watchPositions', 'papi', 'portfolioMargin', False) urlType = type if isPortfolioMargin: urlType = 'papi' url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey'] client = self.client(url) self.set_balance_cache(client, type, isPortfolioMargin) self.set_positions_cache(client, type, symbols, isPortfolioMargin) fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True) awaitPositionsSnapshot = self.handle_option('watchPositions', 'awaitPositionsSnapshot', True) cache = self.safe_value(self.positions, type) if fetchPositionsSnapshot and awaitPositionsSnapshot and cache is None: snapshot = await client.future(type + ':fetchPositionsSnapshot') return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True) newPositions = await self.watch(url, messageHash, None, type) if self.newUpdates: return newPositions return self.filter_by_symbols_since_limit(cache, symbols, since, limit, True) def set_positions_cache(self, client: Client, type, symbols: Strings = None, isPortfolioMargin=False): if type == 'spot': return if self.positions is None: self.positions = {} if type in self.positions: return fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', False) if fetchPositionsSnapshot: messageHash = type + ':fetchPositionsSnapshot' if not (messageHash in client.futures): client.future(messageHash) self.spawn(self.load_positions_snapshot, client, messageHash, type, isPortfolioMargin) else: self.positions[type] = ArrayCacheBySymbolBySide() async def load_positions_snapshot(self, client, messageHash, type, isPortfolioMargin): params: dict = { 'type': type, } if isPortfolioMargin: params['portfolioMargin'] = True positions = await self.fetch_positions(None, params) self.positions[type] = ArrayCacheBySymbolBySide() cache = self.positions[type] for i in range(0, len(positions)): position = positions[i] contracts = self.safe_number(position, 'contracts', 0) if contracts > 0: cache.append(position) # don't remove the future from the .futures cache future = client.futures[messageHash] future.resolve(cache) client.resolve(cache, type + ':position') def handle_positions(self, client, message): # # { # e: 'ACCOUNT_UPDATE', # T: 1667881353112, # E: 1667881353115, # a: { # B: [{ # a: 'USDT', # wb: '1127.95750089', # cw: '1040.82091149', # bc: '0' # }], # P: [{ # s: 'BTCUSDT', # pa: '-0.089', # ep: '19700.03933', # cr: '-1260.24809979', # up: '1.53058860', # mt: 'isolated', # iw: '87.13658940', # ps: 'BOTH', # ma: 'USDT' # }], # m: 'ORDER' # } # } # # each account is connected to a different endpoint # and has exactly one subscriptionhash which is the account type subscriptions = client.subscriptions subscriptionsKeys = list(subscriptions.keys()) accountType = self.get_account_type_from_subscriptions(subscriptionsKeys) if self.positions is None: self.positions = {} if not (accountType in self.positions): self.positions[accountType] = ArrayCacheBySymbolBySide() cache = self.positions[accountType] data = self.safe_dict(message, 'a', {}) rawPositions = self.safe_list(data, 'P', []) newPositions = [] for i in range(0, len(rawPositions)): rawPosition = rawPositions[i] position = self.parse_ws_position(rawPosition) timestamp = self.safe_integer(message, 'E') position['timestamp'] = timestamp position['datetime'] = self.iso8601(timestamp) newPositions.append(position) cache.append(position) messageHashes = self.find_message_hashes(client, accountType + ':positions::') for i in range(0, len(messageHashes)): messageHash = messageHashes[i] parts = messageHash.split('::') symbolsString = parts[1] symbols = symbolsString.split(',') positions = self.filter_by_array(newPositions, 'symbol', symbols, False) if not self.is_empty(positions): client.resolve(positions, messageHash) client.resolve(newPositions, accountType + ':positions') def parse_ws_position(self, position, market=None): # # { # "s": "BTCUSDT", # Symbol # "pa": "0", # Position Amount # "ep": "0.00000", # Entry Price # "cr": "200", #(Pre-fee) Accumulated Realized # "up": "0", # Unrealized PnL # "mt": "isolated", # Margin Type # "iw": "0.00000000", # Isolated Wallet(if isolated position) # "ps": "BOTH" # Position Side # } # marketId = self.safe_string(position, 's') contracts = self.safe_string(position, 'pa') contractsAbs = Precise.string_abs(self.safe_string(position, 'pa')) positionSide = self.safe_string_lower(position, 'ps') hedged = True if positionSide == 'both': hedged = False if not Precise.string_eq(contracts, '0'): if Precise.string_lt(contracts, '0'): positionSide = 'short' else: positionSide = 'long' return self.safe_position({ 'info': position, 'id': None, 'symbol': self.safe_symbol(marketId, None, None, 'swap'), 'notional': None, 'marginMode': self.safe_string(position, 'mt'), 'liquidationPrice': None, 'entryPrice': self.safe_number(position, 'ep'), 'unrealizedPnl': self.safe_number(position, 'up'), 'percentage': None, 'contracts': self.parse_number(contractsAbs), 'contractSize': None, 'markPrice': None, 'side': positionSide, 'hedged': hedged, 'timestamp': None, 'datetime': None, 'maintenanceMargin': None, 'maintenanceMarginPercentage': None, 'collateral': None, 'initialMargin': None, 'initialMarginPercentage': None, 'leverage': None, 'marginRatio': None, }) async def fetch_my_trades_ws(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ fetch all trades made by the user https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/account-requests#account-trade-history-user_data :param str symbol: unified market symbol :param int|None [since]: the earliest time in ms to fetch trades for :param int|None [limit]: the maximum number of trades structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.endTime]: the latest time in ms to fetch trades for :param int [params.fromId]: first trade Id to fetch :returns dict[]: a list of `trade structures ` """ await self.load_markets() if symbol is None: raise BadRequest(self.id + ' fetchMyTradesWs requires a symbol') market = self.market(symbol) type = self.get_market_type('fetchMyTradesWs', market, params) if type != 'spot' and type != 'future': raise BadRequest(self.id + ' fetchMyTradesWs does not support ' + type + ' markets') url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'fetchMyTradesWs', 'returnRateLimits', False) payload: dict = { 'symbol': self.market_id(symbol), 'returnRateLimits': returnRateLimits, } if since is not None: payload['startTime'] = since if limit is not None: payload['limit'] = limit fromId = self.safe_integer(params, 'fromId') if fromId is not None and since is not None: raise BadRequest(self.id + ' fetchMyTradesWs does not support fetching by both fromId and since parameters at the same time') message: dict = { 'id': messageHash, 'method': 'myTrades', 'params': self.sign_params(self.extend(payload, params)), } subscription: dict = { 'method': self.handle_trades_ws, } trades = await self.watch(url, messageHash, message, messageHash, subscription) return self.filter_by_symbol_since_limit(trades, symbol, since, limit) async def fetch_trades_ws(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ fetch all trades made by the user https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/market-data-requests#recent-trades :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trades structures to retrieve, default=500, max=1000 :param dict [params]: extra parameters specific to the exchange API endpoint EXCHANGE SPECIFIC PARAMETERS :param int [params.fromId]: trade ID to begin at :returns dict[]: a list of `trade structures ` """ await self.load_markets() market = self.market(symbol) type = self.get_market_type('fetchTradesWs', market, params) if type != 'spot' and type != 'future': raise BadRequest(self.id + ' fetchTradesWs does not support ' + type + ' markets') url = self.urls['api']['ws']['ws-api'][type] requestId = self.request_id(url) messageHash = str(requestId) returnRateLimits = False returnRateLimits, params = self.handle_option_and_params(params, 'fetchTradesWs', 'returnRateLimits', False) payload: dict = { 'symbol': self.market_id(symbol), 'returnRateLimits': returnRateLimits, } if limit is not None: payload['limit'] = limit message: dict = { 'id': messageHash, 'method': 'trades.historical', 'params': self.extend(payload, params), } subscription: dict = { 'method': self.handle_trades_ws, } trades = await self.watch(url, messageHash, message, messageHash, subscription) return self.filter_by_since_limit(trades, since, limit) def handle_trades_ws(self, client: Client, message): # # fetchMyTradesWs # # { # "id": "f4ce6a53-a29d-4f70-823b-4ab59391d6e8", # "status": 200, # "result": [ # { # "symbol": "BTCUSDT", # "id": 1650422481, # "orderId": 12569099453, # "orderListId": -1, # "price": "23416.10000000", # "qty": "0.00635000", # "quoteQty": "148.69223500", # "commission": "0.00000000", # "commissionAsset": "BNB", # "time": 1660801715793, # "isBuyer": False, # "isMaker": True, # "isBestMatch": True # }, # ... # ], # } # # fetchTradesWs # # { # "id": "f4ce6a53-a29d-4f70-823b-4ab59391d6e8", # "status": 200, # "result": [ # { # "id": 0, # "price": "0.00005000", # "qty": "40.00000000", # "quoteQty": "0.00200000", # "time": 1500004800376, # "isBuyerMaker": True, # "isBestMatch": True # } # ... # ], # } # messageHash = self.safe_string(message, 'id') result = self.safe_list(message, 'result', []) trades = self.parse_trades(result) client.resolve(trades, messageHash) async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ watches information on multiple trades made by the user :param str symbol: unified market symbol of the market orders were made in :param int [since]: the earliest time in ms to fetch orders for :param int [limit]: the maximum number of order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.portfolioMargin]: set to True if you would like to watch trades in a portfolio margin account :returns dict[]: a list of `trade structures ` """ await self.load_markets() type = None market = None if symbol is not None: market = self.market(symbol) symbol = market['symbol'] type, params = self.handle_market_type_and_params('watchMyTrades', market, params) subType = None subType, params = self.handle_sub_type_and_params('watchMyTrades', market, params) if self.isLinear(type, subType): type = 'future' elif self.isInverse(type, subType): type = 'delivery' messageHash = 'myTrades' if symbol is not None: symbol = self.symbol(symbol) messageHash += ':' + symbol params = self.extend(params, {'type': market['type'], 'symbol': symbol}) await self.authenticate(self.extend({'type': type, 'subType': subType}, params)) urlType = type # we don't change type because the listening key is different if type == 'margin': urlType = 'spot' # spot-margin shares the same stream spot isPortfolioMargin = None isPortfolioMargin, params = self.handle_option_and_params_2(params, 'watchMyTrades', 'papi', 'portfolioMargin', False) url = '' if type == 'spot': url = self.urls['api']['ws']['ws-api'][type] else: if isPortfolioMargin: urlType = 'papi' url = self.urls['api']['ws'][urlType] + '/' + self.options[type]['listenKey'] client = self.client(url) self.set_balance_cache(client, type, isPortfolioMargin) self.set_positions_cache(client, type, None, isPortfolioMargin) message = None trades = await self.watch(url, messageHash, message, type) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True) def handle_my_trade(self, client: Client, message): messageHash = 'myTrades' executionType = self.safe_string(message, 'x') if executionType == 'TRADE': trade = self.parse_ws_trade(message) orderId = self.safe_string(trade, 'order') tradeFee = self.safe_dict(trade, 'fee', {}) tradeFee = self.extend({}, tradeFee) symbol = self.safe_string(trade, 'symbol') if orderId is not None and tradeFee is not None and symbol is not None: cachedOrders = self.orders if cachedOrders is not None: orders = self.safe_value(cachedOrders.hashmap, symbol, {}) order = self.safe_value(orders, orderId) if order is not None: # accumulate order fees fees = self.safe_value(order, 'fees') fee = self.safe_value(order, 'fee') if not self.is_empty(fees): insertNewFeeCurrency = True for i in range(0, len(fees)): orderFee = fees[i] if orderFee['currency'] == tradeFee['currency']: feeCost = self.sum(tradeFee['cost'], orderFee['cost']) order['fees'][i]['cost'] = float(self.currency_to_precision(tradeFee['currency'], feeCost)) insertNewFeeCurrency = False break if insertNewFeeCurrency: order['fees'].append(tradeFee) elif fee is not None: if fee['currency'] == tradeFee['currency']: feeCost = self.sum(fee['cost'], tradeFee['cost']) order['fee']['cost'] = float(self.currency_to_precision(tradeFee['currency'], feeCost)) elif fee['currency'] is None: order['fee'] = tradeFee else: order['fees'] = [fee, tradeFee] order['fee'] = None else: order['fee'] = tradeFee # save self trade in the order orderTrades = self.safe_list(order, 'trades', []) orderTrades.append(trade) order['trades'] = orderTrades # don't append twice cause it breaks newUpdates mode # self order already exists in the cache if self.myTrades is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) self.myTrades = ArrayCacheBySymbolById(limit) myTrades = self.myTrades myTrades.append(trade) client.resolve(self.myTrades, messageHash) messageHashSymbol = messageHash + ':' + symbol client.resolve(self.myTrades, messageHashSymbol) def handle_order(self, client: Client, message): parsed = self.parse_ws_order(message) symbol = self.safe_string(parsed, 'symbol') orderId = self.safe_string(parsed, 'id') if symbol is not None: if self.orders is None: limit = self.safe_integer(self.options, 'ordersLimit', 1000) self.orders = ArrayCacheBySymbolById(limit) cachedOrders = self.orders orders = self.safe_value(cachedOrders.hashmap, symbol, {}) order = self.safe_value(orders, orderId) if order is not None: fee = self.safe_value(order, 'fee') if fee is not None: parsed['fee'] = fee fees = self.safe_value(order, 'fees') if fees is not None: parsed['fees'] = fees parsed['trades'] = self.safe_value(order, 'trades') timestamp = self.safe_integer(parsed, 'timestamp') if timestamp is None: parsed['timestamp'] = self.safe_integer(order, 'timestamp') parsed['datetime'] = self.safe_string(order, 'datetime') cachedOrders.append(parsed) messageHash = 'orders' symbolSpecificMessageHash = 'orders:' + symbol client.resolve(cachedOrders, messageHash) client.resolve(cachedOrders, symbolSpecificMessageHash) def handle_acount_update(self, client, message): self.handle_balance(client, message) self.handle_positions(client, message) def handle_ws_error(self, client: Client, message): # # { # "error": { # "code": 2, # "msg": "Invalid request: invalid stream" # }, # "id": 1 # } # id = self.safe_string(message, 'id') rejected = False error = self.safe_dict(message, 'error', {}) code = self.safe_integer(error, 'code') msg = self.safe_string(error, 'msg') try: self.handle_errors(code, msg, client.url, '', {}, self.json(error), error, {}, {}) except Exception as e: rejected = True # private endpoint uses id client.reject(e, id) # public endpoint stores messageHash in subscriptions subscriptionKeys = list(client.subscriptions.keys()) for i in range(0, len(subscriptionKeys)): subscriptionHash = subscriptionKeys[i] subscriptionId = self.safe_string(client.subscriptions[subscriptionHash], 'id') subscription = self.safe_string(client.subscriptions[subscriptionHash], 'subscription') if id == subscriptionId: client.reject(e, subscriptionHash) if subscription is not None: del client.subscriptions[subscription] if not rejected: client.reject(message, id) # reset connection if 5xx error codeString = self.safe_string(error, 'code') if (codeString is not None) and (codeString[0] == '5'): client.reset(message) def handle_event_stream_terminated(self, client: Client, message): # # { # e: 'eventStreamTerminated', # E: 1757896885229 # } # event = self.safe_string(message, 'e') subscriptions = client.subscriptions subscriptionsKeys = list(subscriptions.keys()) accountType = self.get_account_type_from_subscriptions(subscriptionsKeys) if event == 'eventStreamTerminated': del client.subscriptions[accountType] client.reject(message, accountType) def handle_message(self, client: Client, message): # handle WebSocketAPI eventMsg = self.safe_dict(message, 'event') if eventMsg is not None: message = eventMsg status = self.safe_string(message, 'status') error = self.safe_value(message, 'error') if (error is not None) or (status is not None and status != '200'): self.handle_ws_error(client, message) return # user subscription wraps message in subscriptionId and event id = self.safe_string(message, 'id') subscriptions = self.safe_value(client.subscriptions, id) method = self.safe_value(subscriptions, 'method') if method is not None: method(client, message) return # handle other APIs methods: dict = { 'depthUpdate': self.handle_order_book, 'trade': self.handle_trade, 'aggTrade': self.handle_trade, 'kline': self.handle_ohlcv, 'markPrice_kline': self.handle_ohlcv, 'indexPrice_kline': self.handle_ohlcv, '1hTicker@arr': self.handle_tickers, '4hTicker@arr': self.handle_tickers, '1dTicker@arr': self.handle_tickers, '24hrTicker@arr': self.handle_tickers, '24hrMiniTicker@arr': self.handle_tickers, '1hTicker': self.handle_tickers, '4hTicker': self.handle_tickers, '1dTicker': self.handle_tickers, '24hrTicker': self.handle_tickers, '24hrMiniTicker': self.handle_tickers, 'markPriceUpdate': self.handle_mark_prices, 'markPriceUpdate@arr': self.handle_mark_prices, 'bookTicker': self.handle_bids_asks, # there is no "bookTicker@arr" endpoint 'outboundAccountPosition': self.handle_balance, 'balanceUpdate': self.handle_balance, 'ACCOUNT_UPDATE': self.handle_acount_update, 'executionReport': self.handle_order_update, 'ORDER_TRADE_UPDATE': self.handle_order_update, 'forceOrder': self.handle_liquidation, 'eventStreamTerminated': self.handle_event_stream_terminated, 'externalLockUpdate': self.handle_balance, } event = self.safe_string(message, 'e') if isinstance(message, list): data = message[0] event = self.safe_string(data, 'e') + '@arr' method = self.safe_value(methods, event) if method is None: requestId = self.safe_string(message, 'id') if requestId is not None: self.handle_subscription_status(client, message) return # special case for the real-time bookTicker, since it comes without an event identifier # # { # "u": 7488717758, # "s": "BTCUSDT", # "b": "28621.74000000", # "B": "1.43278800", # "a": "28621.75000000", # "A": "2.52500800" # } # if event is None and ('a' in message) and ('b' in message): self.handle_bids_asks(client, message) else: method(client, message)