# -*- 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

from ccxt.pro.base.exchange import Exchange
import ccxt.async_support
from ccxt.pro.base.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
from ccxt.base.errors import ExchangeError
from ccxt.base.precise import Precise


class binance(Exchange, ccxt.async_support.binance):

    def describe(self):
        return self.deep_extend(super(binance, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchMyTrades': True,
                'watchOHLCV': True,
                'watchOrderBook': True,
                'watchOrders': True,
                'watchTicker': True,
                'watchTickers': False,  # for now
                'watchTrades': True,
            },
            'urls': {
                'test': {
                    'ws': {
                        'spot': 'wss://testnet.binance.vision/ws',
                        'margin': 'wss://testnet.binance.vision/ws',
                        'future': 'wss://stream.binancefuture.com/ws',
                        'delivery': 'wss://dstream.binancefuture.com/ws',
                    },
                },
                'api': {
                    'ws': {
                        'spot': 'wss://stream.binance.com:9443/ws',
                        'margin': 'wss://stream.binance.com:9443/ws',
                        'future': 'wss://fstream.binance.com/ws',
                        'delivery': 'wss://dstream.binance.com/ws',
                    },
                },
            },
            'options': {
                # get updates every 1000ms or 100ms
                # or every 0ms in real-time for futures
                'watchOrderBookRate': 100,
                'tradesLimit': 1000,
                'ordersLimit': 1000,
                'OHLCVLimit': 1000,
                'requestId': {},
                'watchOrderBookLimit': 1000,  # default limit
                'watchTrades': {
                    'name': 'trade',  # 'trade' or 'aggTrade'
                },
                'watchTicker': {
                    'name': 'ticker',  # ticker = 1000ms L1+OHLCV, bookTicker = real-time L1
                },
                '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
                'listenKeyRefreshRate': 1200000,  # 20 mins
                'ws': {
                    'cost': 5,
                },
            },
        })

    def request_id(self, url):
        options = self.safe_value(self.options, 'requestId', {})
        previousValue = self.safe_integer(options, url, 0)
        newValue = self.sum(previousValue, 1)
        self.options['requestId'][url] = newValue
        return newValue

    async def watch_order_book(self, symbol, limit=None, params={}):
        """
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the binance api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        #
        # todo add support for <levels>-snapshots(depth)
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#partial-book-depth-streams        # <symbol>@depth<levels>@100ms or <symbol>@depth<levels>(1000ms)
        # valid <levels> are 5, 10, or 20
        #
        # default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000
        if limit is not None:
            if (limit != 5) and (limit != 10) and (limit != 20) and (limit != 50) and (limit != 100) and (limit != 500) and (limit != 1000):
                raise ExchangeError(self.id + ' watchOrderBook limit argument must be None, 5, 10, 20, 50, 100, 500 or 1000')
        #
        await self.load_markets()
        defaultType = self.safe_string_2(self.options, 'watchOrderBook', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        query = self.omit(params, 'type')
        market = self.market(symbol)
        #
        # notice the differences between trading futures and spot trading
        # the algorithms use different urls in step 1
        # delta caching and merging also differs in steps 4, 5, 6
        #
        # spot/margin
        # https://binance-docs.github.io/apidocs/spot/en/#how-to-manage-a-local-order-book-correctly
        #
        # 1. Open a stream to wss://stream.binance.com:9443/ws/bnbbtc@depth.
        # 2. Buffer the events you receive from the stream.
        # 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 .
        # 4. Drop any event where u is <= lastUpdateId in the snapshot.
        # 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1.
        # 6. While listening to the stream, each new event's U should be equal to the previous event's u+1.
        # 7. The data in each event is the absolute quantity for a price level.
        # 8. If the quantity is 0, remove the price level.
        # 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal.
        #
        # futures
        # https://binance-docs.github.io/apidocs/futures/en/#how-to-manage-a-local-order-book-correctly
        #
        # 1. Open a stream to wss://fstream.binance.com/stream?streams=btcusdt@depth.
        # 2. Buffer the events you receive from the stream. For same price, latest received update covers the previous one.
        # 3. Get a depth snapshot from https://fapi.binance.com/fapi/v1/depth?symbol=BTCUSDT&limit=1000 .
        # 4. Drop any event where u is < lastUpdateId in the snapshot.
        # 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId
        # 6. While listening to the stream, each new event's pu should be equal to the previous event's u, otherwise initialize the process from step 3.
        # 7. The data in each event is the absolute quantity for a price level.
        # 8. If the quantity is 0, remove the price level.
        # 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal.
        #
        name = 'depth'
        messageHash = market['lowercaseId'] + '@' + name
        url = self.urls['api']['ws'][type]  # + '/' + messageHash
        requestId = self.request_id(url)
        watchOrderBookRate = self.safe_string(self.options, 'watchOrderBookRate', '100')
        request = {
            'method': 'SUBSCRIBE',
            'params': [
                messageHash + '@' + watchOrderBookRate + 'ms',
            ],
            'id': requestId,
        }
        subscription = {
            'id': str(requestId),
            'messageHash': messageHash,
            'name': name,
            'symbol': market['symbol'],
            'method': self.handle_order_book_subscription,
            'limit': limit,
            'type': type,
            'params': params,
        }
        message = self.extend(request, query)
        # 1. Open a stream to wss://stream.binance.com:9443/ws/bnbbtc@depth.
        orderbook = await self.watch(url, messageHash, message, messageHash, subscription)
        return orderbook.limit()

    async def fetch_order_book_snapshot(self, client, message, subscription):
        defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
        type = self.safe_value(subscription, 'type')
        symbol = self.safe_string(subscription, 'symbol')
        messageHash = self.safe_string(subscription, 'messageHash')
        limit = self.safe_integer(subscription, 'limit', defaultLimit)
        params = self.safe_value(subscription, 'params')
        # 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 .
        # todo: self is a synch blocking call in ccxt.php - make it async
        # default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000
        snapshot = await self.fetch_order_book(symbol, limit, params)
        orderbook = self.safe_value(self.orderbooks, symbol)
        if orderbook is None:
            # if the orderbook is dropped before the snapshot is received
            return
        orderbook.reset(snapshot)
        # unroll the accumulated deltas
        messages = orderbook.cache
        for i in range(0, len(messages)):
            message = messages[i]
            U = self.safe_integer(message, 'U')
            u = self.safe_integer(message, 'u')
            pu = self.safe_integer(message, 'pu')
            if type == 'future':
                # 4. Drop any event where u is < lastUpdateId in the snapshot
                if u < orderbook['nonce']:
                    continue
                # 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId
                if (U <= orderbook['nonce']) and (u >= orderbook['nonce']) or (pu == orderbook['nonce']):
                    self.handle_order_book_message(client, message, orderbook)
            else:
                # 4. Drop any event where u is <= lastUpdateId in the snapshot
                if u <= orderbook['nonce']:
                    continue
                # 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1
                if ((U - 1) <= orderbook['nonce']) and ((u - 1) >= orderbook['nonce']):
                    self.handle_order_book_message(client, message, orderbook)
        self.orderbooks[symbol] = orderbook
        client.resolve(orderbook, messageHash)

    def handle_delta(self, bookside, delta):
        price = self.safe_float(delta, 0)
        amount = self.safe_float(delta, 1)
        bookside.store(price, amount)

    def handle_deltas(self, bookside, deltas):
        for i in range(0, len(deltas)):
            self.handle_delta(bookside, deltas[i])

    def handle_order_book_message(self, client, message, orderbook):
        u = self.safe_integer(message, 'u')
        self.handle_deltas(orderbook['asks'], self.safe_value(message, 'a', []))
        self.handle_deltas(orderbook['bids'], self.safe_value(message, 'b', []))
        orderbook['nonce'] = u
        timestamp = self.safe_integer(message, 'E')
        orderbook['timestamp'] = timestamp
        orderbook['datetime'] = self.iso8601(timestamp)
        return orderbook

    def handle_order_book(self, client, message):
        #
        # initial snapshot is fetched with ccxt's fetchOrderBook
        # the feed does not include a snapshot, just the deltas
        #
        #     {
        #         "e": "depthUpdate",  # Event type
        #         "E": 1577554482280,  # Event time
        #         "s": "BNBBTC",  # Symbol
        #         "U": 157,  # First update ID in event
        #         "u": 160,  # Final update ID in event
        #         "b": [ # bids
        #             ["0.0024", "10"],  # price, size
        #         ],
        #         "a": [ # asks
        #             ["0.0026", "100"],  # price, size
        #         ]
        #     }
        #
        marketId = self.safe_string(message, 's')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        name = 'depth'
        messageHash = market['lowercaseId'] + '@' + name
        orderbook = self.safe_value(self.orderbooks, symbol)
        if orderbook is None:
            #
            # https://github.com/ccxt/ccxt/issues/6672
            #
            # Sometimes Binance sends the first delta before the subscription
            # confirmation arrives. At that point the orderbook is not
            # initialized yet and the snapshot has not been requested yet
            # therefore it is safe to drop these premature messages.
            #
            return
        nonce = self.safe_integer(orderbook, 'nonce')
        if nonce is None:
            # 2. Buffer the events you receive from the stream.
            orderbook.cache.append(message)
        else:
            try:
                U = self.safe_integer(message, 'U')
                u = self.safe_integer(message, 'u')
                pu = self.safe_integer(message, 'pu')
                if pu is None:
                    # spot
                    # 4. Drop any event where u is <= lastUpdateId in the snapshot
                    if u > orderbook['nonce']:
                        timestamp = self.safe_integer(orderbook, 'timestamp')
                        conditional = None
                        if timestamp is None:
                            # 5. The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1
                            conditional = ((U - 1) <= orderbook['nonce']) and ((u - 1) >= orderbook['nonce'])
                        else:
                            # 6. While listening to the stream, each new event's U should be equal to the previous event's u+1.
                            conditional = ((U - 1) == orderbook['nonce'])
                        if conditional:
                            self.handle_order_book_message(client, message, orderbook)
                            if nonce < orderbook['nonce']:
                                client.resolve(orderbook, messageHash)
                        else:
                            # todo: client.reject from handleOrderBookMessage properly
                            raise ExchangeError(self.id + ' handleOrderBook received an out-of-order nonce')
                else:
                    # future
                    # 4. Drop any event where u is < lastUpdateId in the snapshot
                    if u >= orderbook['nonce']:
                        # 5. The first processed event should have U <= lastUpdateId AND u >= lastUpdateId
                        # 6. While listening to the stream, each new event's pu should be equal to the previous event's u, otherwise initialize the process from step 3
                        if (U <= orderbook['nonce']) or (pu == orderbook['nonce']):
                            self.handle_order_book_message(client, message, orderbook)
                            if nonce <= orderbook['nonce']:
                                client.resolve(orderbook, messageHash)
                        else:
                            # todo: client.reject from handleOrderBookMessage properly
                            raise ExchangeError(self.id + ' handleOrderBook received an out-of-order nonce')
            except Exception as e:
                del self.orderbooks[symbol]
                del client.subscriptions[messageHash]
                client.reject(e, messageHash)

    def handle_order_book_subscription(self, client, message, subscription):
        defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000)
        symbol = self.safe_string(subscription, 'symbol')
        limit = self.safe_integer(subscription, 'limit', defaultLimit)
        if symbol in self.orderbooks:
            del self.orderbooks[symbol]
        self.orderbooks[symbol] = self.order_book({}, limit)
        # fetch the snapshot in a separate async call
        self.spawn(self.fetch_order_book_snapshot, client, message, subscription)

    def handle_subscription_status(self, client, message):
        #
        #     {
        #         "result": null,
        #         "id": 1574649734450
        #     }
        #
        id = self.safe_string(message, 'id')
        subscriptionsById = self.index_by(client.subscriptions, 'id')
        subscription = self.safe_value(subscriptionsById, id, {})
        method = self.safe_value(subscription, 'method')
        if method is not None:
            method(client, message, subscription)
        return message

    async def watch_trades(self, symbol, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the binance api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        options = self.safe_value(self.options, 'watchTrades', {})
        name = self.safe_string(options, 'name', 'trade')
        messageHash = market['lowercaseId'] + '@' + name
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        watchTradesType = self.safe_string_2(options, 'type', 'defaultType', defaultType)
        type = self.safe_string(params, 'type', watchTradesType)
        query = self.omit(params, 'type')
        url = self.urls['api']['ws'][type]
        requestId = self.request_id(url)
        request = {
            'method': 'SUBSCRIBE',
            'params': [
                messageHash,
            ],
            'id': requestId,
        }
        subscribe = {
            'id': requestId,
        }
        trades = await self.watch(url, messageHash, self.extend(request, query), messageHash, subscribe)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def parse_trade(self, trade, market=None):
        #
        # public watchTrades
        #
        #     {
        #         e: 'trade',       # event type
        #         E: 1579481530911,  # event time
        #         s: 'ETHBTC',      # symbol
        #         t: 158410082,     # trade id
        #         p: '0.01914100',  # price
        #         q: '0.00700000',  # quantity
        #         b: 586187049,     # buyer order id
        #         a: 586186710,     # seller order id
        #         T: 1579481530910,  # trade time
        #         m: False,         # is the buyer the market maker
        #         M: True           # binance docs say it should be ignored
        #     }
        #
        #     {
        #        "e": "aggTrade",  # Event type
        #        "E": 123456789,   # Event time
        #        "s": "BNBBTC",    # Symbol
        #        "a": 12345,       # Aggregate trade ID
        #        "p": "0.001",     # Price
        #        "q": "100",       # Quantity
        #        "f": 100,         # First trade ID
        #        "l": 105,         # Last trade ID
        #        "T": 123456785,   # Trade time
        #        "m": True,        # Is the buyer the market maker?
        #        "M": True         # Ignore
        #     }
        #
        # private watchMyTrades spot
        #
        #     {
        #         e: 'executionReport',
        #         E: 1611063861489,
        #         s: 'BNBUSDT',
        #         c: 'm4M6AD5MF3b1ERe65l4SPq',
        #         S: 'BUY',
        #         o: 'MARKET',
        #         f: 'GTC',
        #         q: '2.00000000',
        #         p: '0.00000000',
        #         P: '0.00000000',
        #         F: '0.00000000',
        #         g: -1,
        #         C: '',
        #         x: 'TRADE',
        #         X: 'PARTIALLY_FILLED',
        #         r: 'NONE',
        #         i: 1296882607,
        #         l: '0.33200000',
        #         z: '0.33200000',
        #         L: '46.86600000',
        #         n: '0.00033200',
        #         N: 'BNB',
        #         T: 1611063861488,
        #         t: 109747654,
        #         I: 2696953381,
        #         w: False,
        #         m: False,
        #         M: True,
        #         O: 1611063861488,
        #         Z: '15.55951200',
        #         Y: '15.55951200',
        #         Q: '0.00000000'
        #     }
        #
        # private watchMyTrades future/delivery
        #
        #     {
        #         s: 'BTCUSDT',
        #         c: 'pb2jD6ZQHpfzSdUac8VqMK',
        #         S: 'SELL',
        #         o: 'MARKET',
        #         f: 'GTC',
        #         q: '0.001',
        #         p: '0',
        #         ap: '33468.46000',
        #         sp: '0',
        #         x: 'TRADE',
        #         X: 'FILLED',
        #         i: 13351197194,
        #         l: '0.001',
        #         z: '0.001',
        #         L: '33468.46',
        #         n: '0.00027086',
        #         N: 'BNB',
        #         T: 1612095165362,
        #         t: 458032604,
        #         b: '0',
        #         a: '0',
        #         m: False,
        #         R: False,
        #         wt: 'CONTRACT_PRICE',
        #         ot: 'MARKET',
        #         ps: 'BOTH',
        #         cp: False,
        #         rp: '0.00335000',
        #         pP: False,
        #         si: 0,
        #         ss: 0
        #     }
        #
        executionType = self.safe_string(trade, 'x')
        isTradeExecution = (executionType == 'TRADE')
        if not isTradeExecution:
            return super(binance, self).parse_trade(trade, market)
        id = self.safe_string_2(trade, 't', 'a')
        timestamp = self.safe_integer(trade, 'T')
        price = self.safe_float_2(trade, 'L', 'p')
        amount = self.safe_float(trade, 'q')
        if isTradeExecution:
            amount = self.safe_float(trade, 'l', amount)
        cost = self.safe_float(trade, 'Y')
        if cost is None:
            if (price is not None) and (amount is not None):
                cost = price * amount
        marketId = self.safe_string(trade, 's')
        symbol = self.safe_symbol(marketId)
        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_float(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 {
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': type,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def handle_trade(self, client, message):
        # the trade streams push raw trade information in real-time
        # each trade has a unique buyer and seller
        marketId = self.safe_string(message, 's')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        lowerCaseId = self.safe_string_lower(message, 's')
        event = self.safe_string(message, 'e')
        messageHash = lowerCaseId + '@' + event
        trade = self.parse_trade(message, market)
        tradesArray = self.safe_value(self.trades, symbol)
        if tradesArray is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            tradesArray = ArrayCache(limit)
        tradesArray.append(trade)
        self.trades[symbol] = tradesArray
        client.resolve(tradesArray, messageHash)

    async def watch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :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|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the binance api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        marketId = market['lowercaseId']
        interval = self.timeframes[timeframe]
        name = 'kline'
        messageHash = marketId + '@' + name + '_' + interval
        options = self.safe_value(self.options, 'watchOHLCV', {})
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        watchOHLCVType = self.safe_string_2(options, 'type', 'defaultType', defaultType)
        type = self.safe_string(params, 'type', watchOHLCVType)
        query = self.omit(params, 'type')
        url = self.urls['api']['ws'][type]
        requestId = self.request_id(url)
        request = {
            'method': 'SUBSCRIBE',
            'params': [
                messageHash,
            ],
            'id': requestId,
        }
        subscribe = {
            'id': requestId,
        }
        ohlcv = await self.watch(url, messageHash, self.extend(request, query), messageHash, subscribe)
        if self.newUpdates:
            limit = ohlcv.getLimit(symbol, limit)
        return self.filter_by_since_limit(ohlcv, since, limit, 0, True)

    def handle_ohlcv(self, client, message):
        #
        #     {
        #         e: 'kline',
        #         E: 1579482921215,
        #         s: 'ETHBTC',
        #         k: {
        #             t: 1579482900000,
        #             T: 1579482959999,
        #             s: 'ETHBTC',
        #             i: '1m',
        #             f: 158411535,
        #             L: 158411550,
        #             o: '0.01913200',
        #             c: '0.01913500',
        #             h: '0.01913700',
        #             l: '0.01913200',
        #             v: '5.08400000',
        #             n: 16,
        #             x: False,
        #             q: '0.09728060',
        #             V: '3.30200000',
        #             Q: '0.06318500',
        #             B: '0'
        #         }
        #     }
        #
        marketId = self.safe_string(message, 's')
        lowercaseMarketId = self.safe_string_lower(message, 's')
        event = self.safe_string(message, 'e')
        kline = self.safe_value(message, 'k')
        interval = self.safe_string(kline, 'i')
        # use a reverse lookup in a static map instead
        timeframe = self.find_timeframe(interval)
        messageHash = lowercaseMarketId + '@' + event + '_' + interval
        parsed = [
            self.safe_integer(kline, 't'),
            self.safe_float(kline, 'o'),
            self.safe_float(kline, 'h'),
            self.safe_float(kline, 'l'),
            self.safe_float(kline, 'c'),
            self.safe_float(kline, 'v'),
        ]
        symbol = self.safe_symbol(marketId)
        self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
        stored = self.safe_value(self.ohlcvs[symbol], timeframe)
        if stored is None:
            limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
            stored = ArrayCacheByTimestamp(limit)
            self.ohlcvs[symbol][timeframe] = stored
        stored.append(parsed)
        client.resolve(stored, messageHash)

    async def watch_ticker(self, symbol, params={}):
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the binance api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        marketId = market['lowercaseId']
        options = self.safe_value(self.options, 'watchTicker', {})
        name = self.safe_string(options, 'name', 'ticker')
        messageHash = marketId + '@' + name
        defaultType = self.safe_string_2(self.options, 'defaultType', 'spot')
        watchTickerType = self.safe_string_2(options, 'type', 'defaultType', defaultType)
        type = self.safe_string(params, 'type', watchTickerType)
        query = self.omit(params, 'type')
        url = self.urls['api']['ws'][type]
        requestId = self.request_id(url)
        request = {
            'method': 'SUBSCRIBE',
            'params': [
                messageHash,
            ],
            'id': requestId,
        }
        subscribe = {
            'id': requestId,
        }
        return await self.watch(url, messageHash, self.extend(request, query), messageHash, subscribe)

    def handle_ticker(self, client, message):
        #
        # 24hr rolling window ticker statistics for a single symbol
        # These are NOT the statistics of the UTC day, but a 24hr rolling window for the previous 24hrs
        # Update Speed 1000ms
        #
        #     {
        #         e: '24hrTicker',      # event type
        #         E: 1579485598569,     # event time
        #         s: 'ETHBTC',          # symbol
        #         p: '-0.00004000',     # price change
        #         P: '-0.209',          # price change percent
        #         w: '0.01920495',      # weighted average price
        #         x: '0.01916500',      # the price of the first trade before the 24hr rolling window
        #         c: '0.01912500',      # last(closing) price
        #         Q: '0.10400000',      # last quantity
        #         b: '0.01912200',      # best bid
        #         B: '4.10400000',      # best bid quantity
        #         a: '0.01912500',      # best ask
        #         A: '0.00100000',      # best ask quantity
        #         o: '0.01916500',      # open price
        #         h: '0.01956500',      # high price
        #         l: '0.01887700',      # low price
        #         v: '173518.11900000',  # base volume
        #         q: '3332.40703994',   # quote volume
        #         O: 1579399197842,     # open time
        #         C: 1579485597842,     # close time
        #         F: 158251292,         # first trade id
        #         L: 158414513,         # last trade id
        #         n: 163222,            # total number of trades
        #     }
        #
        event = self.safe_string(message, 'e', 'bookTicker')
        if event == '24hrTicker':
            event = 'ticker'
        wsMarketId = self.safe_string_lower(message, 's')
        messageHash = wsMarketId + '@' + event
        timestamp = None
        now = self.milliseconds()
        if event == 'bookTicker':
            # take the event timestamp, if available, for spot tickers it is not
            timestamp = self.safe_integer(message, 'E', now)
        else:
            # take the timestamp of the closing price for candlestick streams
            timestamp = self.safe_integer(message, 'C', now)
        marketId = self.safe_string(message, 's')
        symbol = self.safe_symbol(marketId)
        last = self.safe_float(message, 'c')
        result = {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(message, 'h'),
            'low': self.safe_float(message, 'l'),
            'bid': self.safe_float(message, 'b'),
            'bidVolume': self.safe_float(message, 'B'),
            'ask': self.safe_float(message, 'a'),
            'askVolume': self.safe_float(message, 'A'),
            'vwap': self.safe_float(message, 'w'),
            'open': self.safe_float(message, 'o'),
            'close': last,
            'last': last,
            'previousClose': self.safe_float(message, 'x'),  # previous day close
            'change': self.safe_float(message, 'p'),
            'percentage': self.safe_float(message, 'P'),
            'average': None,
            'baseVolume': self.safe_float(message, 'v'),
            'quoteVolume': self.safe_float(message, 'q'),
            'info': message,
        }
        self.tickers[symbol] = result
        client.resolve(result, messageHash)

    async def authenticate(self, params={}):
        time = self.milliseconds()
        type = self.safe_string_2(self.options, 'defaultType', 'authenticate', 'spot')
        type = self.safe_string(params, 'type', type)
        options = self.safe_value(self.options, type, {})
        lastAuthenticatedTime = self.safe_integer(options, 'lastAuthenticatedTime', 0)
        listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 1200000)
        delay = self.sum(listenKeyRefreshRate, 10000)
        if time - lastAuthenticatedTime > delay:
            method = 'publicPostUserDataStream'
            if type == 'future':
                method = 'fapiPrivatePostListenKey'
            elif type == 'delivery':
                method = 'dapiPrivatePostListenKey'
            elif type == 'margin':
                method = 'sapiPostUserDataStream'
            response = await getattr(self, method)()
            self.options[type] = self.extend(options, {
                'listenKey': self.safe_string(response, 'listenKey'),
                'lastAuthenticatedTime': time,
            })
            self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params)

    async def keep_alive_listen_key(self, params={}):
        # https://binance-docs.github.io/apidocs/spot/en/#listen-key-spot
        type = self.safe_string_2(self.options, 'defaultType', 'authenticate', 'spot')
        type = self.safe_string(params, 'type', type)
        options = self.safe_value(self.options, type, {})
        listenKey = self.safe_string(options, 'listenKey')
        if listenKey is None:
            # A network error happened: we can't renew a listen key that does not exist.
            return
        method = 'publicPutUserDataStream'
        if type == 'future':
            method = 'fapiPrivatePutListenKey'
        elif type == 'delivery':
            method = 'dapiPrivatePutListenKey'
        elif type == 'margin':
            method = 'sapiPutUserDataStream'
        request = {
            'listenKey': listenKey,
        }
        time = self.milliseconds()
        sendParams = self.omit(params, 'type')
        try:
            await getattr(self, method)(self.extend(request, sendParams))
        except Exception as error:
            url = self.urls['api']['ws'][type] + '/' + self.options[type]['listenKey']
            client = self.client(url)
            messageHashes = list(client.futures.keys())
            for i in range(0, len(messageHashes)):
                messageHash = messageHashes[i]
                client.reject(error, messageHash)
            self.options[type] = self.extend(options, {
                'listenKey': None,
                'lastAuthenticatedTime': 0,
            })
            return
        self.options[type] = self.extend(options, {
            'listenKey': listenKey,
            'lastAuthenticatedTime': time,
        })
        # whether or not to schedule another listenKey keepAlive request
        clients = list(self.clients.values())
        listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 1200000)
        for i in range(0, len(clients)):
            client = clients[i]
            subscriptionKeys = list(client.subscriptions.keys())
            for j in range(0, len(subscriptionKeys)):
                subscribeType = subscriptionKeys[j]
                if subscribeType == type:
                    return self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, params)

    def set_balance_cache(self, client, type):
        if type in client.subscriptions:
            return None
        options = self.safe_value(self.options, 'watchBalance')
        fetchBalanceSnapshot = self.safe_value(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):
        response = await self.fetch_balance({'type': type})
        self.balance[type] = self.extend(response, self.safe_value(self.balance, type, {}))
        # don't remove the future from the .futures cache
        future = client.futures[messageHash]
        future.resolve()
        client.resolve(self.balance[type], type + ':balance')

    async def watch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the binance api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        await self.load_markets()
        await self.authenticate(params)
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        url = self.urls['api']['ws'][type] + '/' + self.options[type]['listenKey']
        client = self.client(url)
        self.set_balance_cache(client, type)
        options = self.safe_value(self.options, 'watchBalance')
        fetchBalanceSnapshot = self.safe_value(options, 'fetchBalanceSnapshot', False)
        awaitBalanceSnapshot = self.safe_value(options, 'awaitBalanceSnapshot', True)
        if fetchBalanceSnapshot and awaitBalanceSnapshot:
            await client.future(type + ':fetchBalanceSnapshot')
        messageHash = type + ':balance'
        message = None
        return await self.watch(url, messageHash, message, type)

    def handle_balance(self, client, message):
        #
        # sent upon a balance update not related to orders
        #
        #     {
        #         e: 'balanceUpdate',
        #         E: 1629352505586,
        #         a: 'IOTX',
        #         d: '0.43750000',
        #         T: 1629352505585
        #     }
        #
        # sent upon creating or filling an order
        #
        #     {
        #         "e": "outboundAccountPosition",  # Event type
        #         "E": 1564034571105,             # Event Time
        #         "u": 1564034571073,             # Time of last account update
        #         "B": [                         # Balances Array
        #             {
        #                 "a": "ETH",                 # Asset
        #                 "f": "10000.000000",        # Free
        #                 "l": "0.000000"             # Locked
        #             }
        #         ]
        #     }
        #
        # future/delivery
        #
        #     {
        #         "e": "ACCOUNT_UPDATE",            # Event Type
        #         "E": 1564745798939,               # Event Time
        #         "T": 1564745798938 ,              # Transaction
        #         "i": "SfsR",                      # Account Alias
        #         "a": {                           # Update Data
        #             "m":"ORDER",                  # Event reason type
        #             "B":[                        # Balances
        #                 {
        #                     "a":"BTC",                # Asset
        #                     "wb":"122624.12345678",   # Wallet Balance
        #                     "cw":"100.12345678"       # Cross Wallet Balance
        #                 },
        #             ],
        #             "P":[
        #                 {
        #                     "s":"BTCUSD_200925",      # Symbol
        #                     "pa":"0",                 # Position Amount
        #                     "ep":"0.0",               # Entry Price
        #                     "cr":"200",               #(Pre-fee) Accumulated Realized
        #                     "up":"0",                 # Unrealized PnL
        #                     "mt":"isolated",          # Margin Type
        #                     "iw":"0.00000000",        # Isolated Wallet(if isolated position)
        #                     "ps":"BOTH"               # Position Side
        #                 },
        #             ]
        #         }
        #     }
        #
        wallet = self.safe_value(self.options, 'wallet', 'wb')  # cw for cross wallet
        # each account is connected to a different endpoint
        # and has exactly one subscriptionhash which is the account type
        subscriptions = list(client.subscriptions.keys())
        accountType = subscriptions[0]
        messageHash = accountType + ':balance'
        self.balance[accountType]['info'] = message
        event = self.safe_string(message, 'e')
        if event == 'balanceUpdate':
            currencyId = self.safe_string(message, 'a')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            delta = self.safe_string(message, 'd')
            if code in self.balance[accountType]:
                previousValue = self.balance[accountType][code]['free']
                if not isinstance(previousValue, str):
                    previousValue = self.number_to_string(previousValue)
                account['free'] = Precise.string_add(previousValue, delta)
            else:
                account['free'] = delta
            self.balance[accountType][code] = account
        else:
            message = self.safe_value(message, 'a', message)
            B = self.safe_value(message, 'B')
            for i in range(0, len(B)):
                entry = B[i]
                currencyId = self.safe_string(entry, 'a')
                code = self.safe_currency_code(currencyId)
                account = self.account()
                account['free'] = self.safe_string(entry, 'f')
                account['used'] = self.safe_string(entry, 'l')
                account['total'] = self.safe_string(entry, wallet)
                self.balance[accountType][code] = account
        timestamp = self.safe_integer(message, 'E')
        self.balance[accountType]['timestamp'] = timestamp
        self.balance[accountType]['datetime'] = self.iso8601(timestamp)
        self.balance[accountType] = self.safe_balance(self.balance[accountType])
        client.resolve(self.balance[accountType], messageHash)

    async def watch_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        watches information on multiple orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the binance api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        await self.load_markets()
        await self.authenticate(params)
        messageHash = 'orders'
        market = None
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash += ':' + symbol
        type = None
        type, params = self.handle_market_type_and_params('watchOrders', market, params)
        url = self.urls['api']['ws'][type] + '/' + self.options[type]['listenKey']
        client = self.client(url)
        self.set_balance_cache(client, type)
        message = None
        orders = await self.watch(url, messageHash, message, type)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    def parse_ws_order(self, order, market=None):
        #
        # spot
        #
        #     {
        #         "e": "executionReport",        # Event type
        #         "E": 1499405658658,            # Event time
        #         "s": "ETHBTC",                 # Symbol
        #         "c": "mUvoqJxFIILMdfAW5iGSOW",  # Client order ID
        #         "S": "BUY",                    # Side
        #         "o": "LIMIT",                  # Order type
        #         "f": "GTC",                    # Time in force
        #         "q": "1.00000000",             # Order quantity
        #         "p": "0.10264410",             # Order price
        #         "P": "0.00000000",             # Stop price
        #         "F": "0.00000000",             # Iceberg quantity
        #         "g": -1,                       # OrderListId
        #         "C": null,                     # Original client order ID; This is the ID of the order being canceled
        #         "x": "NEW",                    # Current execution type
        #         "X": "NEW",                    # Current order status
        #         "r": "NONE",                   # Order reject reason; will be an error code.
        #         "i": 4293153,                  # Order ID
        #         "l": "0.00000000",             # Last executed quantity
        #         "z": "0.00000000",             # Cumulative filled quantity
        #         "L": "0.00000000",             # Last executed price
        #         "n": "0",                      # Commission amount
        #         "N": null,                     # Commission asset
        #         "T": 1499405658657,            # Transaction time
        #         "t": -1,                       # Trade ID
        #         "I": 8641984,                  # Ignore
        #         "w": True,                     # Is the order on the book?
        #         "m": False,                    # Is self trade the maker side?
        #         "M": False,                    # Ignore
        #         "O": 1499405658657,            # Order creation time
        #         "Z": "0.00000000",             # Cumulative quote asset transacted quantity
        #         "Y": "0.00000000"              # Last quote asset transacted quantity(i.e. lastPrice * lastQty),
        #         "Q": "0.00000000"              # Quote Order Qty
        #     }
        #
        # future
        #
        #     {
        #         "s":"BTCUSDT",                 # Symbol
        #         "c":"TEST",                    # Client Order Id
        #                                        # special client order id:
        #                                        # starts with "autoclose-": liquidation order
        #                                        # "adl_autoclose": ADL auto close order
        #         "S":"SELL",                    # Side
        #         "o":"TRAILING_STOP_MARKET",    # Order Type
        #         "f":"GTC",                     # Time in Force
        #         "q":"0.001",                   # Original Quantity
        #         "p":"0",                       # Original Price
        #         "ap":"0",                      # Average Price
        #         "sp":"7103.04",                # Stop Price. Please ignore with TRAILING_STOP_MARKET order
        #         "x":"NEW",                     # Execution Type
        #         "X":"NEW",                     # Order Status
        #         "i":8886774,                   # Order Id
        #         "l":"0",                       # Order Last Filled Quantity
        #         "z":"0",                       # Order Filled Accumulated Quantity
        #         "L":"0",                       # Last Filled Price
        #         "N":"USDT",                    # Commission Asset, will not push if no commission
        #         "n":"0",                       # Commission, will not push if no commission
        #         "T":1568879465651,             # Order Trade Time
        #         "t":0,                         # Trade Id
        #         "b":"0",                       # Bids Notional
        #         "a":"9.91",                    # Ask Notional
        #         "m":false,                     # Is self trade the maker side?
        #         "R":false,                     # Is self reduce only
        #         "wt":"CONTRACT_PRICE",         # Stop Price Working Type
        #         "ot":"TRAILING_STOP_MARKET",   # Original Order Type
        #         "ps":"LONG",                   # Position Side
        #         "cp":false,                    # If Close-All, pushed with conditional order
        #         "AP":"7476.89",                # Activation Price, only puhed with TRAILING_STOP_MARKET order
        #         "cr":"5.0",                    # Callback Rate, only puhed with TRAILING_STOP_MARKET order
        #         "rp":"0"                       # Realized Profit of the trade
        #     }
        #
        executionType = self.safe_string(order, 'x')
        orderId = self.safe_string(order, 'i')
        marketId = self.safe_string(order, 's')
        symbol = self.safe_symbol(marketId)
        timestamp = self.safe_integer(order, 'O')
        T = self.safe_integer(order, 'T')
        lastTradeTimestamp = None
        if executionType == 'NEW':
            if timestamp is None:
                timestamp = T
        elif executionType == 'TRADE':
            lastTradeTimestamp = T
        fee = None
        feeCost = self.safe_float(order, 'n')
        if (feeCost is not None) and (feeCost > 0):
            feeCurrencyId = self.safe_string(order, 'N')
            feeCurrency = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrency,
            }
        price = self.safe_float(order, 'p')
        amount = self.safe_float(order, 'q')
        side = self.safe_string_lower(order, 'S')
        type = self.safe_string_lower(order, 'o')
        filled = self.safe_float(order, 'z')
        cumulativeQuote = self.safe_float(order, 'Z')
        remaining = amount
        average = self.safe_float(order, 'ap')
        cost = cumulativeQuote
        if filled is not None:
            if cost is None:
                if price is not None:
                    cost = filled * price
            if amount is not None:
                remaining = max(amount - filled, 0)
            if (average is None) and (cumulativeQuote is not None) and (filled > 0):
                average = cumulativeQuote / filled
        rawStatus = self.safe_string(order, 'X')
        status = self.parse_order_status(rawStatus)
        trades = None
        clientOrderId = self.safe_string(order, 'C')
        if (clientOrderId is None) or (len(clientOrderId) == 0):
            clientOrderId = self.safe_string(order, 'c')
        stopPrice = self.safe_float_2(order, 'P', 'sp')
        timeInForce = self.safe_string(order, 'f')
        if timeInForce == 'GTX':
            # GTX means "Good Till Crossing" and is an equivalent way of saying Post Only
            timeInForce = 'PO'
        return {
            'info': order,
            'symbol': symbol,
            'id': orderId,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': trades,
        }

    def handle_order_update(self, client, message):
        #
        # spot
        #
        #     {
        #         "e": "executionReport",        # Event type
        #         "E": 1499405658658,            # Event time
        #         "s": "ETHBTC",                 # Symbol
        #         "c": "mUvoqJxFIILMdfAW5iGSOW",  # Client order ID
        #         "S": "BUY",                    # Side
        #         "o": "LIMIT",                  # Order type
        #         "f": "GTC",                    # Time in force
        #         "q": "1.00000000",             # Order quantity
        #         "p": "0.10264410",             # Order price
        #         "P": "0.00000000",             # Stop price
        #         "F": "0.00000000",             # Iceberg quantity
        #         "g": -1,                       # OrderListId
        #         "C": null,                     # Original client order ID; This is the ID of the order being canceled
        #         "x": "NEW",                    # Current execution type
        #         "X": "NEW",                    # Current order status
        #         "r": "NONE",                   # Order reject reason; will be an error code.
        #         "i": 4293153,                  # Order ID
        #         "l": "0.00000000",             # Last executed quantity
        #         "z": "0.00000000",             # Cumulative filled quantity
        #         "L": "0.00000000",             # Last executed price
        #         "n": "0",                      # Commission amount
        #         "N": null,                     # Commission asset
        #         "T": 1499405658657,            # Transaction time
        #         "t": -1,                       # Trade ID
        #         "I": 8641984,                  # Ignore
        #         "w": True,                     # Is the order on the book?
        #         "m": False,                    # Is self trade the maker side?
        #         "M": False,                    # Ignore
        #         "O": 1499405658657,            # Order creation time
        #         "Z": "0.00000000",             # Cumulative quote asset transacted quantity
        #         "Y": "0.00000000"              # Last quote asset transacted quantity(i.e. lastPrice * lastQty),
        #         "Q": "0.00000000"              # Quote Order Qty
        #     }
        #
        # future
        #
        #     {
        #         "e":"ORDER_TRADE_UPDATE",           # Event Type
        #         "E":1568879465651,                  # Event Time
        #         "T":1568879465650,                  # Trasaction Time
        #         "o": {
        #             "s":"BTCUSDT",                  # Symbol
        #             "c":"TEST",                     # Client Order Id
        #                                             # special client order id:
        #                                             # starts with "autoclose-": liquidation order
        #                                             # "adl_autoclose": ADL auto close order
        #             "S":"SELL",                     # Side
        #             "o":"TRAILING_STOP_MARKET",     # Order Type
        #             "f":"GTC",                      # Time in Force
        #             "q":"0.001",                    # Original Quantity
        #             "p":"0",                        # Original Price
        #             "ap":"0",                       # Average Price
        #             "sp":"7103.04",                 # Stop Price. Please ignore with TRAILING_STOP_MARKET order
        #             "x":"NEW",                      # Execution Type
        #             "X":"NEW",                      # Order Status
        #             "i":8886774,                    # Order Id
        #             "l":"0",                        # Order Last Filled Quantity
        #             "z":"0",                        # Order Filled Accumulated Quantity
        #             "L":"0",                        # Last Filled Price
        #             "N":"USDT",                     # Commission Asset, will not push if no commission
        #             "n":"0",                        # Commission, will not push if no commission
        #             "T":1568879465651,              # Order Trade Time
        #             "t":0,                          # Trade Id
        #             "b":"0",                        # Bids Notional
        #             "a":"9.91",                     # Ask Notional
        #             "m":false,                      # Is self trade the maker side?
        #             "R":false,                      # Is self reduce only
        #             "wt":"CONTRACT_PRICE",          # Stop Price Working Type
        #             "ot":"TRAILING_STOP_MARKET",    # Original Order Type
        #             "ps":"LONG",                    # Position Side
        #             "cp":false,                     # If Close-All, pushed with conditional order
        #             "AP":"7476.89",                 # Activation Price, only puhed with TRAILING_STOP_MARKET order
        #             "cr":"5.0",                     # Callback Rate, only puhed with TRAILING_STOP_MARKET order
        #             "rp":"0"                        # Realized Profit of the trade
        #         }
        #     }
        #
        e = self.safe_string(message, 'e')
        if e == 'ORDER_TRADE_UPDATE':
            message = self.safe_value(message, 'o', message)
        self.handle_my_trade(client, message)
        self.handle_order(client, message)

    async def watch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        watches information on multiple trades made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the binance api endpoint
        :returns [dict]: a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure
        """
        await self.load_markets()
        await self.authenticate(params)
        defaultType = self.safe_string_2(self.options, 'watchMyTrades', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        url = self.urls['api']['ws'][type] + '/' + self.options[type]['listenKey']
        messageHash = 'myTrades'
        if symbol is not None:
            messageHash += ':' + symbol
        client = self.client(url)
        self.set_balance_cache(client, type)
        message = None
        trades = await self.watch(url, messageHash, message, type)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    def handle_my_trade(self, client, message):
        messageHash = 'myTrades'
        executionType = self.safe_string(message, 'x')
        if executionType == 'TRADE':
            trade = self.parse_trade(message)
            orderId = self.safe_string(trade, 'order')
            tradeFee = self.safe_value(trade, 'fee')
            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 fees is not None:
                            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_value(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, message):
        messageHash = 'orders'
        parsed = self.parse_ws_order(message)
        symbol = self.safe_string(parsed, 'symbol')
        orderId = self.safe_string(parsed, 'id')
        if symbol is not None:
            if self.orders is None:
                limit = self.safe_integer(self.options, 'ordersLimit', 1000)
                self.orders = ArrayCacheBySymbolById(limit)
            cachedOrders = self.orders
            orders = self.safe_value(cachedOrders.hashmap, symbol, {})
            order = self.safe_value(orders, orderId)
            if order is not None:
                fee = self.safe_value(order, 'fee')
                if fee is not None:
                    parsed['fee'] = fee
                fees = self.safe_value(order, 'fees')
                if fees is not None:
                    parsed['fees'] = fees
                parsed['trades'] = self.safe_value(order, 'trades')
                parsed['timestamp'] = self.safe_integer(order, 'timestamp')
                parsed['datetime'] = self.safe_string(order, 'datetime')
            cachedOrders.append(parsed)
            client.resolve(self.orders, messageHash)
            messageHashSymbol = messageHash + ':' + symbol
            client.resolve(self.orders, messageHashSymbol)

    def handle_message(self, client, message):
        methods = {
            'depthUpdate': self.handle_order_book,
            'trade': self.handle_trade,
            'aggTrade': self.handle_trade,
            'kline': self.handle_ohlcv,
            '24hrTicker': self.handle_ticker,
            'bookTicker': self.handle_ticker,
            'outboundAccountPosition': self.handle_balance,
            'balanceUpdate': self.handle_balance,
            'ACCOUNT_UPDATE': self.handle_balance,
            'executionReport': self.handle_order_update,
            'ORDER_TRADE_UPDATE': self.handle_order_update,
        }
        event = self.safe_string(message, 'e')
        method = self.safe_value(methods, event)
        if method is None:
            requestId = self.safe_string(message, 'id')
            if requestId is not None:
                return self.handle_subscription_status(client, message)
            # special case for the real-time bookTicker, since it comes without an event identifier
            #
            #     {
            #         u: 7488717758,
            #         s: 'BTCUSDT',
            #         b: '28621.74000000',
            #         B: '1.43278800',
            #         a: '28621.75000000',
            #         A: '2.52500800'
            #     }
            #
            if event is None:
                self.handle_ticker(client, message)
        else:
            return method(client, message)
