# -*- 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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=trade-structure>`
        """
        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 <https://docs.ccxt.com/?id=trade-structure>`
        """
        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 <https://docs.ccxt.com/?id=public-trades>`
        """
        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 <https://docs.ccxt.com/?id=public-trades>`
        """
        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 <https://docs.ccxt.com/?id=order-book-structure>` 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 <https://docs.ccxt.com/?id=order-book-structure>` 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 <https://docs.ccxt.com/?id=order-book-structure>` 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 <https://docs.ccxt.com/?id=order-book-structure>` 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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/en/latest/manual.html#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 <https://docs.ccxt.com/?id=order-structure>`
        """
        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 <https://docs.ccxt.com/?id=trade-structure>`
        """
        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)
