# -*- 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 from ccxt.base.types import Any, Balances, Int, Order, OrderBook, 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.precise import Precise class aster(ccxt.async_support.aster): def describe(self) -> Any: return self.deep_extend(super(aster, self).describe(), { 'has': { 'ws': True, 'watchBalance': True, 'watchBidsAsks': True, 'watchMarkPrice': True, 'watchMarkPrices': True, 'watchTrades': True, 'watchTradesForSymbols': True, 'watchOrders': True, 'watchOrderBook': True, 'watchOrderBookForSymbols': True, 'watchOHLCV': True, 'watchOHLCVForSymbols': True, 'watchPositions': True, 'watchTicker': True, 'watchTickers': True, 'watchMyTrades': True, 'unWatchTicker': True, 'unWatchTickers': True, 'unWatchMarkPrice': True, 'unWatchMarkPrices': True, 'unWatchBidsAsks': True, 'unWatchTrades': True, 'unWatchTradesForSymbols': True, 'unWatchOrderBook': True, 'unWatchOrderBookForSymbols': True, 'unWatchOHLCV': True, 'unWatchOHLCVForSymbols': True, }, 'urls': { 'api': { 'ws': { 'public': { 'spot': 'wss://sstream.asterdex.com/stream', 'swap': 'wss://fstream.asterdex.com/stream', }, 'private': { 'spot': 'wss://sstream.asterdex.com/ws', 'swap': 'wss://fstream.asterdex.com/ws', }, }, }, }, 'options': { 'listenKey': { 'spot': None, 'swap': None, }, 'lastAuthenticatedTime': { 'spot': 0, 'swap': 0, }, 'listenKeyRefreshRate': { 'spot': 3600000, # 60 minutes 'swap': 3600000, }, 'watchBalance': { 'fetchBalanceSnapshot': False, # or True 'awaitBalanceSnapshot': True, # whether to wait for the balance snapshot before providing updates }, 'wallet': 'wb', # wb = wallet balance, cw = cross balance 'watchPositions': { 'fetchPositionsSnapshot': True, # or False 'awaitPositionsSnapshot': True, # whether to wait for the positions snapshot before providing updates }, }, 'streaming': {}, 'exceptions': {}, }) def get_account_type_from_url(self, url: str) -> str: if url.find('fstream') > -1: return 'swap' return 'spot' 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#simplified-ticker-by-symbol https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#compact-tickers-for-all-symbols-in-the-entire-market https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#full-ticker-per-symbol https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#complete-ticker-for-all-trading-pairs-on-the-entire-market https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-mini-ticker-stream https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-mini-tickers-stream https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-ticker-streams https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-tickers-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 ` """ params['callerMethodName'] = 'watchTicker' await self.load_markets() symbol = self.safe_symbol(symbol) tickers = await self.watch_tickers([symbol], params) return tickers[symbol] async def un_watch_ticker(self, symbol: str, params={}) -> Any: """ unWatches a price ticker https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#simplified-ticker-by-symbol https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#compact-tickers-for-all-symbols-in-the-entire-market https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#full-ticker-per-symbol https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#complete-ticker-for-all-trading-pairs-on-the-entire-market https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-mini-ticker-stream https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-mini-tickers-stream https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-ticker-streams https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-tickers-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 ` """ params['callerMethodName'] = 'unWatchTicker' return await self.un_watch_tickers([symbol], params) 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#compact-tickers-for-all-symbols-in-the-entire-market https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#complete-ticker-for-all-trading-pairs-on-the-entire-market https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-mini-tickers-stream https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-tickers-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 ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchTickers') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'SUBSCRIBE', 'params': subscriptionArgs, } for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@ticker') messageHashes.append('ticker:' + market['symbol']) newTicker = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) if self.newUpdates: result: dict = {} result[newTicker['symbol']] = newTicker return result 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#compact-tickers-for-all-symbols-in-the-entire-market https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#complete-ticker-for-all-trading-pairs-on-the-entire-market https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-mini-tickers-stream https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#all-market-tickers-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 ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchTickers') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'UNSUBSCRIBE', 'params': subscriptionArgs, } for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@ticker') messageHashes.append('unsubscribe:ticker:' + market['symbol']) return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) async def watch_mark_price(self, symbol: str, params={}) -> Ticker: """ watches a mark price for a specific market https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream-for-all-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 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 ` """ params['callerMethodName'] = 'watchMarkPrice' await self.load_markets() symbol = self.safe_symbol(symbol) tickers = await self.watch_mark_prices([symbol], params) return tickers[symbol] async def un_watch_mark_price(self, symbol: str, params={}) -> Any: """ unWatches a mark price for a specific market https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream-for-all-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 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 ` """ params['callerMethodName'] = 'unWatchMarkPrice' return await self.un_watch_mark_prices([symbol], params) async def watch_mark_prices(self, symbols: Strings = None, params={}) -> Tickers: """ watches the mark price for all markets https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream https://asterdex.github.io/aster-api-website/futures-v3/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 ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchMarkPrices') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'SUBSCRIBE', 'params': subscriptionArgs, } use1sFreq = self.safe_bool(params, 'use1sFreq', True) for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) suffix = '@1s' if (use1sFreq) else '' subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@markPrice' + suffix) messageHashes.append('ticker:' + market['symbol']) newTicker = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) if self.newUpdates: result = {} result[newTicker['symbol']] = newTicker return result return self.filter_by_array(self.tickers, 'symbol', symbols) async def un_watch_mark_prices(self, symbols: Strings = None, params={}) -> Any: """ watches the mark price for all markets https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#mark-price-stream https://asterdex.github.io/aster-api-website/futures-v3/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 ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchMarkPrices') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'UNSUBSCRIBE', 'params': subscriptionArgs, } use1sFreq = self.safe_bool(params, 'use1sFreq', True) for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) suffix = '@1s' if (use1sFreq) else '' subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@markPrice' + suffix) messageHashes.append('unsubscribe:ticker:' + market['symbol']) return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) def handle_ticker(self, client: Client, message): # # { # "e": "24hrTicker", # "E": 1754451187277, # "s": "CAKEUSDT", # "p": "-0.08800", # "P": "-3.361", # "w": "2.58095", # "c": "2.53000", # "Q": "5", # "o": "2.61800", # "h": "2.64700", # "l": "2.52400", # "v": "15775", # "q": "40714.46000", # "O": 1754364780000, # "C": 1754451187274, # "F": 6571389, # "L": 6574507, # "n": 3119 # } # { # "e": "markPriceUpdate", # "E": 1754660466000, # "s": "BTCUSDT", # "p": "116809.60000000", # "P": "116595.54012838", # "i": "116836.93534884", # "r": "0.00010000", # "T": 1754668800000 # } # marketType = self.get_account_type_from_url(client.url) ticker = message parsed = self.parse_ws_ticker(ticker, marketType) symbol = parsed['symbol'] messageHash = 'ticker:' + symbol self.tickers[symbol] = parsed client.resolve(self.tickers[symbol], messageHash) def parse_ws_ticker(self, message, marketType): event = self.safe_string(message, 'e') marketId = self.safe_string(message, 's') timestamp = self.safe_integer(message, 'E') market = self.safe_market(marketId, None, None, marketType) last = self.safe_string(message, 'c') if event == 'markPriceUpdate': return self.safe_ticker({ 'symbol': market['symbol'], 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'info': message, 'markPrice': self.safe_string(message, 'p'), 'indexPrice': self.safe_string(message, 'i'), }) return self.safe_ticker({ 'symbol': market['symbol'], 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'high': self.safe_string(message, 'h'), 'low': self.safe_string(message, 'l'), 'bid': None, 'bidVolume': None, 'ask': None, 'askVolume': None, 'vwap': self.safe_string(message, 'w'), 'open': self.safe_string(message, 'o'), 'close': last, 'last': last, 'previousClose': None, '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) async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers: """ watches best bid & ask for symbols https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#best-order-book-information-by-symbol https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#best-order-book-information-across-the-entire-market https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-book-ticker-streams https://asterdex.github.io/aster-api-website/futures-v3/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, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) if symbolsLength == 0: raise ArgumentsRequired(self.id + ' watchBidsAsks() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'SUBSCRIBE', 'params': subscriptionArgs, } for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@bookTicker') messageHashes.append('bidask:' + market['symbol']) newTicker = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) if self.newUpdates: result = {} result[newTicker['symbol']] = newTicker return result return self.filter_by_array(self.bidsasks, 'symbol', symbols) async def un_watch_bids_asks(self, symbols: Strings = None, params={}) -> Any: """ unWatches best bid & ask for symbols https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#best-order-book-information-by-symbol https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#best-order-book-information-across-the-entire-market https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#individual-symbol-book-ticker-streams https://asterdex.github.io/aster-api-website/futures-v3/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, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) if symbolsLength == 0: raise ArgumentsRequired(self.id + ' unWatchBidsAsks() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'UNSUBSCRIBE', 'params': subscriptionArgs, } for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@bookTicker') messageHashes.append('unsubscribe:bidask:' + market['symbol']) return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) def handle_bid_ask(self, client: Client, message): # # { # "e": "bookTicker", # "u": 157240846459, # "s": "BTCUSDT", # "b": "122046.7", # "B": "1.084", # "a": "122046.8", # "A": "0.001", # "T": 1754896692922, # "E": 1754896692926 # } # marketType = self.get_account_type_from_url(client.url) data = message marketId = self.safe_string(data, 's') market = self.safe_market(marketId, None, None, marketType) ticker = self.parse_ws_bid_ask(data, market) symbol = ticker['symbol'] self.bidsasks[symbol] = ticker messageHash = 'bidask:' + symbol client.resolve(ticker, messageHash) def parse_ws_bid_ask(self, message, market=None): timestamp = self.safe_integer(message, 'T') return self.safe_ticker({ 'symbol': market['symbol'], 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'ask': self.safe_string(message, 'a'), 'askVolume': self.safe_string(message, 'A'), 'bid': self.safe_string(message, 'b'), 'bidVolume': self.safe_string(message, 'B'), 'info': message, }, market) async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ watches information on multiple trades made in a market https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#collection-transaction-flow https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#tick-by-tick-trades https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#aggregate-trade-streams :param str symbol: unified market symbol of the market trades were made in :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trade structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ params['callerMethodName'] = 'watchTrades' return await self.watch_trades_for_symbols([symbol], since, limit, params) async def un_watch_trades(self, symbol: str, params={}) -> Any: """ unsubscribe from the trades channel https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#collection-transaction-flow https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#tick-by-tick-trades https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#aggregate-trade-streams :param str symbol: unified market symbol of the market trades were made in :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ params['callerMethodName'] = 'unWatchTrades' return await self.un_watch_trades_for_symbols([symbol], params) 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#collection-transaction-flow https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#tick-by-tick-trades https://asterdex.github.io/aster-api-website/futures-v3/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 :returns dict[]: a list of `trade structures ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchTradesForSymbols') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'SUBSCRIBE', 'params': subscriptionArgs, 'id': 1, } for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) marketId = self.safe_string_lower(market, 'id') subscriptionArgs.append(marketId + '@aggTrade') messageHashes.append('trade::' + market['symbol']) trades = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) 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: """ unsubscribe from the trades channel https://github.com/asterdex/api-docs/blob/master/aster-finance-spot-api.md#collection-transaction-flow https://github.com/asterdex/api-docs/blob/master/aster-finance-futures-api.md#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 :returns dict[]: a list of `trade structures ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchTradesForSymbols') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'UNSUBSCRIBE', 'params': subscriptionArgs, } for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@aggTrade') messageHashes.append('unsubscribe:trade:' + market['symbol']) return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) def handle_trade(self, client: Client, message): # # { # "e": "aggTrade", # "E": 1754551358681, # "a": 20505890, # "s": "BTCUSDT", # "p": "114783.7", # "q": "0.020", # "f": 26024678, # "l": 26024682, # "T": 1754551358528, # "m": False # } # marketType = self.get_account_type_from_url(client.url) trade = message marketId = self.safe_string(trade, 's') market = self.safe_market(marketId, None, None, marketType) parsed = self.parse_ws_trade(trade, market) symbol = parsed['symbol'] if not (symbol in self.trades): limit = self.safe_integer(self.options, 'tradesLimit', 1000) self.trades[symbol] = ArrayCache(limit) stored = self.trades[symbol] stored.append(parsed) client.resolve(stored, 'trade::' + symbol) def parse_ws_trade(self, trade, market=None) -> Trade: # # public watchTrades(spot) # # { # "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 # } # e = self.safe_string(trade, 'e') isPublicTrade = (e == 'trade') or (e == 'aggTrade') id = self.safe_string_2(trade, 't', 'a') timestamp = self.safe_integer(trade, 'T') price = self.safe_string_2(trade, 'L', 'p') amount = None if isPublicTrade: amount = self.safe_string(trade, 'q') else: # private trades, amount is in 'l' field, quantity of the last filled trade amount = self.safe_string(trade, 'l') 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') defaultType = self.safe_string(self.options, 'defaultType', 'spot') if (market is None) else market['type'] symbol = self.safe_symbol(marketId, market, None, defaultType) 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, }) 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#limited-depth-information https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#incremental-depth-information https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#partial-book-depth-streams https://asterdex.github.io/aster-api-website/futures-v3/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 """ params['callerMethodName'] = 'watchOrderBook' return await self.watch_order_book_for_symbols([symbol], limit, params) async def un_watch_order_book(self, symbol: str, params={}) -> Any: """ unsubscribe from the orderbook channel https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#limited-depth-information https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#incremental-depth-information https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#partial-book-depth-streams https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#diff-book-depth-streams :param str symbol: symbol of the market to unwatch the trades for :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.limit]: orderbook limit, default is None :returns dict: A dictionary of `order book structures ` indexed by market symbols """ params['callerMethodName'] = 'unWatchOrderBook' return await self.un_watch_order_book_for_symbols([symbol], 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#limited-depth-information https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#incremental-depth-information https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#partial-book-depth-streams https://asterdex.github.io/aster-api-website/futures-v3/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, True, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchOrderBookForSymbols') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'SUBSCRIBE', 'params': subscriptionArgs, } if limit is None or (limit != 5 and limit != 10 and limit != 20): limit = 20 for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@depth' + str(limit)) messageHashes.append('orderbook:' + market['symbol']) orderbook = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) return orderbook.limit() async def un_watch_order_book_for_symbols(self, symbols: List[str], params={}) -> Any: """ unsubscribe from the orderbook channel https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#limited-depth-information https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#incremental-depth-information https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#partial-book-depth-streams https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#diff-book-depth-streams :param str[] symbols: unified symbol of the market to unwatch the trades for :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.limit]: orderbook limit, default is None :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, True, True) firstMarket = self.get_market_from_symbols(symbols) type = self.safe_string(firstMarket, 'type', 'swap') symbolsLength = len(symbols) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchOrderBookForSymbols') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'UNSUBSCRIBE', 'params': subscriptionArgs, } limit = self.safe_number(params, 'limit') params = self.omit(params, 'limit') if limit is None or (limit != 5 and limit != 10 and limit != 20): limit = 20 for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@depth' + limit) messageHashes.append('unsubscribe:orderbook:' + market['symbol']) return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) def handle_order_book(self, client: Client, message): # # { # "e": "depthUpdate", # "E": 1754556878284, # "T": 1754556878031, # "s": "BTCUSDT", # "U": 156391349814, # "u": 156391349814, # "pu": 156391348236, # "b": [ # [ # "114988.3", # "0.147" # ] # ], # "a": [ # [ # "114988.4", # "1.060" # ] # ] # } # marketType = self.get_account_type_from_url(client.url) data = message marketId = self.safe_string(data, 's') timestamp = self.safe_integer(data, 'T') market = self.safe_market(marketId, None, None, marketType) symbol = market['symbol'] if not (symbol in self.orderbooks): self.orderbooks[symbol] = self.order_book() orderbook = self.orderbooks[symbol] snapshot = self.parse_order_book(data, symbol, timestamp, 'b', 'a') orderbook.reset(snapshot) messageHash = 'orderbook' + ':' + symbol self.orderbooks[symbol] = orderbook client.resolve(orderbook, messageHash) async def watch_ohlcv(self, symbol: str, timeframe='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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#k-line-streams https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#klinecandlestick-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 :returns int[][]: A list of candles ordered, open, high, low, close, volume """ params['callerMethodName'] = 'watchOHLCV' await self.load_markets() symbol = self.safe_symbol(symbol) result = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params) return result[symbol][timeframe] async def un_watch_ohlcv(self, symbol: str, timeframe='1m', params={}) -> Any: """ unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#k-line-streams https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#klinecandlestick-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 :returns int[][]: A list of candles ordered, open, high, low, close, volume """ params['callerMethodName'] = 'unWatchOHLCV' return await self.un_watch_ohlcv_for_symbols([[symbol, timeframe]], params) 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#k-line-streams https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#klinecandlestick-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 :returns dict: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() symbolsLength = len(symbolsAndTimeframes) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'watchOHLCVForSymbols') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') symbols = self.get_list_from_object_values(symbolsAndTimeframes, 0) marketSymbols = self.market_symbols(symbols, None, False, True, True) firstMarket = self.market(marketSymbols[0]) type = self.safe_string(firstMarket, 'type', 'swap') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'SUBSCRIBE', 'params': subscriptionArgs, } for i in range(0, len(symbolsAndTimeframes)): data = symbolsAndTimeframes[i] symbolString = self.safe_string(data, 0) market = self.market(symbolString) symbolString = market['symbol'] unfiedTimeframe = self.safe_string(data, 1) timeframeId = self.safe_string(self.timeframes, unfiedTimeframe, unfiedTimeframe) subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@kline_' + timeframeId) messageHashes.append('ohlcv:' + market['symbol'] + ':' + unfiedTimeframe) symbol, timeframe, stored = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) if self.newUpdates: limit = stored.getLimit(symbol, limit) filtered = self.filter_by_since_limit(stored, 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://asterdex.github.io/aster-api-website/spot-v3/websocket-market-streams/#k-line-streams https://asterdex.github.io/aster-api-website/futures-v3/websocket-market-streams/#klinecandlestick-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 :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() symbolsLength = len(symbolsAndTimeframes) methodName = None methodName, params = self.handle_param_string(params, 'callerMethodName', 'unWatchOHLCVForSymbols') params = self.omit(params, 'callerMethodName') if symbolsLength == 0: raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a non-empty array of symbols') symbols = self.get_list_from_object_values(symbolsAndTimeframes, 0) marketSymbols = self.market_symbols(symbols, None, False, True, True) firstMarket = self.market(marketSymbols[0]) type = self.safe_string(firstMarket, 'type', 'swap') url = self.urls['api']['ws']['public'][type] subscriptionArgs = [] messageHashes = [] request: dict = { 'method': 'UNSUBSCRIBE', 'params': subscriptionArgs, } for i in range(0, len(symbolsAndTimeframes)): data = symbolsAndTimeframes[i] symbolString = self.safe_string(data, 0) market = self.market(symbolString) symbolString = market['symbol'] unfiedTimeframe = self.safe_string(data, 1) timeframeId = self.safe_string(self.timeframes, unfiedTimeframe, unfiedTimeframe) subscriptionArgs.append(self.safe_string_lower(market, 'id') + '@kline_' + timeframeId) messageHashes.append('unsubscribe:ohlcv:' + market['symbol'] + ':' + unfiedTimeframe) return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) def handle_ohlcv(self, client: Client, message): # # { # "e": "kline", # "E": 1754655777119, # "s": "BTCUSDT", # "k": { # "t": 1754655720000, # "T": 1754655779999, # "s": "BTCUSDT", # "i": "1m", # "f": 26032629, # "L": 26032629, # "o": "116546.9", # "c": "116546.9", # "h": "116546.9", # "l": "116546.9", # "v": "0.011", # "n": 1, # "x": False, # "q": "1282.0159", # "V": "0.000", # "Q": "0.0000", # "B": "0" # } # } # marketType = self.get_account_type_from_url(client.url) data = message marketId = self.safe_string(data, 's') market = self.safe_market(marketId, None, None, marketType) symbol = market['symbol'] kline = self.safe_dict(data, 'k') timeframeId = self.safe_string(kline, 'i') timeframe = self.find_timeframe(timeframeId) ohlcvsByTimeframe = self.safe_value(self.ohlcvs, symbol) if ohlcvsByTimeframe is None: self.ohlcvs[symbol] = {} if self.safe_value(ohlcvsByTimeframe, timeframe) is None: limit = self.safe_integer(self.options, 'OHLCVLimit', 1000) self.ohlcvs[symbol][timeframe] = ArrayCacheByTimestamp(limit) stored = self.ohlcvs[symbol][timeframe] parsed = self.parse_ws_ohlcv(kline) stored.append(parsed) messageHash = 'ohlcv:' + symbol + ':' + timeframe resolveData = [symbol, timeframe, stored] client.resolve(resolveData, messageHash) def parse_ws_ohlcv(self, ohlcv, market=None) -> list: return [ self.safe_integer(ohlcv, 't'), self.safe_number(ohlcv, 'o'), self.safe_number(ohlcv, 'h'), self.safe_number(ohlcv, 'l'), self.safe_number(ohlcv, 'c'), self.safe_number(ohlcv, 'v'), ] async def authenticate(self, type='spot', params={}): time = self.milliseconds() lastAuthenticatedTimeOptions = self.safe_dict(self.options, 'lastAuthenticatedTime', {}) lastAuthenticatedTime = self.safe_integer(lastAuthenticatedTimeOptions, type, 0) listenKeyRefreshRateOptions = self.safe_dict(self.options, 'listenKeyRefreshRate', {}) listenKeyRefreshRate = self.safe_integer(listenKeyRefreshRateOptions, type, 3600000) # 1 hour if time - lastAuthenticatedTime > listenKeyRefreshRate: response = None if type == 'spot': response = await self.sapiPrivatePostV3ListenKey(params) else: response = await self.fapiPrivatePostV3ListenKey(params) self.options['listenKey'][type] = self.safe_string(response, 'listenKey') self.options['lastAuthenticatedTime'][type] = time params = self.extend({'type': type}, params) self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params) async def keep_alive_listen_key(self, params={}): type = self.safe_string(params, 'type', 'spot') listenKeyOptions = self.safe_dict(self.options, 'listenKey', {}) listenKey = self.safe_string(listenKeyOptions, type) if listenKey is None: return try: if type == 'spot': await self.sapiPrivatePutV3ListenKey() # self.extend the expiry else: await self.fapiPrivatePutV3ListenKey() # self.extend the expiry except Exception as error: url = self.urls['api']['ws']['private'][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['listenKey'][type] = None self.options['lastAuthenticatedTime'][type] = 0 return # whether or not to schedule another listenKey keepAlive request listenKeyRefreshOptions = self.safe_dict(self.options, 'listenKeyRefresh', {}) listenKeyRefreshRate = self.safe_integer(listenKeyRefreshOptions, 'listenKeyRefreshRate', 3600000) self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params) def get_private_url(self, type='spot'): listenKeyOptions = self.safe_dict(self.options, 'listenKey', {}) listenKey = self.safe_string(listenKeyOptions, type) url = self.urls['api']['ws']['private'][type] + '/' + listenKey return url async def watch_balance(self, params={}) -> Balances: """ query for balance and get the amount of funds available for trading or funds locked in orders https://asterdex.github.io/aster-api-website/spot-v3/websocket-account-info/#payload-account_update https://asterdex.github.io/aster-api-website/futures-v3/user-data-streams/#event-balance-and-position-update :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.type]: 'spot' or 'swap', default is 'spot' :returns dict: a `balance structure ` """ await self.load_markets() type = None type, params = self.handle_market_type_and_params('watchBalance', None, params, type) await self.authenticate(type, params) url = self.get_private_url(type) client = self.client(url) self.set_balance_cache(client, type) 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 set_balance_cache(self, client: Client, type): 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) else: self.balance[type] = {} async def load_balance_snapshot(self, client, messageHash, type): params: dict = { 'type': type, } 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 if messageHash in client.futures: future = client.futures[messageHash] future.resolve() client.resolve(self.balance[type], type + ':balance') def handle_balance(self, client: Client, message): # # spot balance update # { # "B": [ # { # "a": "USDT", # "f": "16.29445191", # "l": "0" # }, # { # "a": "ETH", # "f": "0.00199920", # "l": "0" # } # ], # "e": "outboundAccountPosition", # "T": 1768547778317, # "u": 1768547778317, # "E": 1768547778321, # "m": "ORDER" # } # # swap balance and position update # { # "e": "ACCOUNT_UPDATE", # "T": 1768551627708, # "E": 1768551627710, # "a": { # "B": [ # { # "a": "USDT", # "wb": "39.41184271", # "cw": "39.41184271", # "bc": "0" # } # ], # "P": [ # { # "s": "ETHUSDT", # "pa": "0", # "ep": "0.00000000", # "cr": "-0.59070000", # "up": "0", # "mt": "isolated", # "iw": "0", # "ps": "BOTH", # "ma": "USDT" # } # ], # "m": "ORDER" # } # } # accountType = self.get_account_type_from_url(client.url) messageHash = accountType + ':balance' if self.balance[accountType] is None: self.balance[accountType] = {} self.balance[accountType]['info'] = message message = self.safe_dict(message, 'a', message) B = self.safe_list(message, 'B', []) wallet = self.safe_string(self.options, 'wallet', 'wb') 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) async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]: """ watch all open positions https://asterdex.github.io/aster-api-website/futures-v3/user-data-streams/#event-balance-and-position-update :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 :returns dict[]: a list of `position structure ` """ await self.load_markets() type = 'swap' await self.authenticate(type, params) url = self.get_private_url(type) client = self.client(url) self.set_positions_cache(client) messageHashes = [] messageHash = 'positions' symbols = self.market_symbols(symbols, 'swap', True, True) if symbols is None: messageHashes.append(messageHash) else: for i in range(0, len(symbols)): symbol = symbols[i] messageHashes.append(messageHash + '::' + symbol) fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True) awaitPositionsSnapshot = self.handle_option('watchPositions', 'awaitPositionsSnapshot', True) cache = self.positions if fetchPositionsSnapshot and awaitPositionsSnapshot and cache is None: snapshot = await client.future('fetchPositionsSnapshot') return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True) newPositions = await self.watch_multiple(url, messageHashes, 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): if self.positions is not None: return fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', False) if fetchPositionsSnapshot: messageHash = 'fetchPositionsSnapshot' if not (messageHash in client.futures): client.future(messageHash) self.spawn(self.load_positions_snapshot, client, messageHash) else: self.positions = ArrayCacheBySymbolBySide() async def load_positions_snapshot(self, client, messageHash): positions = await self.fetch_positions() self.positions = ArrayCacheBySymbolBySide() cache = self.positions 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 if messageHash in client.futures: future = client.futures[messageHash] future.resolve(cache) client.resolve(cache, 'positions') def handle_positions(self, client, message): # # { # "e": "ACCOUNT_UPDATE", # "T": 1768551627708, # "E": 1768551627710, # "a": { # "B": [ # { # "a": "USDT", # "wb": "39.41184271", # "cw": "39.41184271", # "bc": "0" # } # ], # "P": [ # { # "s": "ETHUSDT", # "pa": "0", # "ep": "0.00000000", # "cr": "-0.59070000", # "up": "0", # "mt": "isolated", # "iw": "0", # "ps": "BOTH", # "ma": "USDT" # } # ], # "m": "ORDER" # } # } # messageHash = 'positions' if self.positions is None: self.positions = ArrayCacheBySymbolBySide() cache = self.positions 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, messageHash) if not self.is_empty(messageHashes): for i in range(0, len(newPositions)): position = newPositions[i] symbol = position['symbol'] symbolMessageHash = messageHash + '::' + symbol client.resolve(position, symbolMessageHash) client.resolve(newPositions, '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 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://asterdex.github.io/aster-api-website/spot-v3/websocket-account-info/#payload-order-update https://asterdex.github.io/aster-api-website/futures-v3/user-data-streams/#event-order-update :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 str [params.type]: 'spot' or 'swap', default is 'spot' if symbol is not provided :returns dict[]: a list of `order structures ` """ await self.load_markets() market = None if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash = 'orders' type = None type, params = self.handle_market_type_and_params('watchOrders', market, params, type) await self.authenticate(type, params) if market is not None: messageHash += '::' + symbol url = self.get_private_url(type) client = self.client(url) self.set_balance_cache(client, type) orders = await self.watch_multiple(url, [messageHash], None, [type]) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True) 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 https://asterdex.github.io/aster-api-website/spot-v3/websocket-account-info/#payload-order-update https://asterdex.github.io/aster-api-website/futures-v3/user-data-streams/#event-order-update :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 str [params.type]: 'spot' or 'swap', default is 'spot' if symbol is not provided :returns dict[]: a list of `trade structures ` """ await self.load_markets() market = None if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash = 'myTrades' type = None type, params = self.handle_market_type_and_params('watchOrders', market, params, type) await self.authenticate(type, params) if market is not None: messageHash += '::' + symbol url = self.get_private_url(type) client = self.client(url) self.set_balance_cache(client, type) trades = await self.watch_multiple(url, [messageHash], None, [type]) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True) def handle_order_update(self, client: Client, message): rawOrder = self.safe_dict(message, 'o', message) e = self.safe_string(message, 'e') if (e == 'ORDER_TRADE_UPDATE') or (e == 'ALGO_UPDATE'): message = self.safe_dict(message, 'o', message) self.handle_order(client, rawOrder) self.handle_my_trade(client, message) def handle_my_trade(self, client: Client, message): messageHash = 'myTrades' executionType = self.safe_string(message, 'x') if executionType == 'TRADE': isSwap = client.url.find('fstream') >= 0 type = 'swap' if isSwap else 'spot' fakeMarket = self.safe_market_structure({'type': type}) trade = self.parse_ws_trade(message, fakeMarket) 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): # # 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 # } # # swap # { # "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 # } # messageHash = 'orders' market = self.get_market_from_order(client, message) if self.orders is None: limit = self.safe_integer(self.options, 'ordersLimit', 1000) self.orders = ArrayCacheBySymbolById(limit) cache = self.orders parsed = self.parse_ws_order(message, market) symbol = market['symbol'] cache.append(parsed) messageHashes = self.find_message_hashes(client, messageHash) if not self.is_empty(messageHashes): symbolMessageHash = messageHash + '::' + symbol client.resolve(cache, symbolMessageHash) client.resolve(cache, messageHash) def parse_ws_order(self, order, market=None): executionType = self.safe_string(order, 'x') marketId = self.safe_string(order, 's') market = self.safe_market(marketId, market) 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, } rawStatus = self.safe_string(order, 'X') status = self.parse_order_status(rawStatus) clientOrderId = self.safe_string_2(order, 'C', 'caid') if (clientOrderId is None) or (len(clientOrderId) == 0): clientOrderId = self.safe_string(order, 'c') stopPrice = self.safe_string_n(order, ['P', 'sp', 'tp']) 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': market['symbol'], 'id': self.safe_string_2(order, 'i', 'aid'), 'clientOrderId': clientOrderId, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': lastTradeTimestamp, 'lastUpdateTimestamp': lastUpdateTimestamp, 'type': self.parseOrderType(self.safe_string_lower(order, 'o')), 'timeInForce': timeInForce, 'postOnly': None, 'reduceOnly': self.safe_bool(order, 'R'), 'side': self.safe_string_lower(order, 'S'), 'price': self.safe_string(order, 'p'), 'stopPrice': stopPrice, 'triggerPrice': stopPrice, 'amount': self.safe_string(order, 'q'), 'cost': self.safe_string(order, 'Z'), 'average': self.safe_string(order, 'ap'), 'filled': self.safe_string(order, 'z'), 'remaining': None, 'status': status, 'fee': fee, 'trades': None, }) def get_market_from_order(self, client: Client, order): marketId = self.safe_string(order, 's') marketType = self.get_account_type_from_url(client.url) return self.safe_market(marketId, None, None, marketType) def handle_balance_and_position(self, client: Client, message): self.handle_balance(client, message) self.handle_positions(client, message) def handle_message(self, client: Client, message): messageInner = self.safe_dict(message, 'data', message) # can be either wrapped in 'data' or full object itself event = self.safe_string(messageInner, 'e') methods: dict = { '24hrTicker': self.handle_ticker, 'aggTrade': self.handle_trade, 'depthUpdate': self.handle_order_book, 'kline': self.handle_ohlcv, 'markPriceUpdate': self.handle_ticker, 'bookTicker': self.handle_bid_ask, 'outboundAccountPosition': self.handle_balance, 'ACCOUNT_UPDATE': self.handle_balance_and_position, 'executionReport': self.handle_order_update, 'ORDER_TRADE_UPDATE': self.handle_order_update, } method = self.safe_value(methods, event) if method is not None: method(client, messageInner)