# -*- 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, ArrayCacheByTimestamp
import hashlib
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import NotSupported
from ccxt.base.errors import InvalidNonce
from ccxt.base.errors import AuthenticationError


class gate(ccxt.async_support.gate):

    def describe(self):
        return self.deep_extend(super(gate, self).describe(), {
            'has': {
                'ws': True,
                'watchOrderBook': True,
                'watchTicker': True,
                'watchTickers': True,  # for now
                'watchTrades': True,
                'watchMyTrades': True,
                'watchOHLCV': True,
                'watchBalance': True,
                'watchOrders': True,
            },
            'urls': {
                'api': {
                    'ws': 'wss://ws.gate.io/v4',
                    'spot': 'wss://api.gateio.ws/ws/v4/',
                    'swap': {
                        'usdt': 'wss://fx-ws.gateio.ws/v4/ws/usdt',
                        'btc': 'wss://fx-ws.gateio.ws/v4/ws/btc',
                    },
                    'future': {
                        'usdt': 'wss://fx-ws.gateio.ws/v4/ws/delivery/usdt',
                        'btc': 'wss://fx-ws.gateio.ws/v4/ws/delivery/btc',
                    },
                    'option': 'wss://op-ws.gateio.live/v4/ws',
                },
                'test': {
                    'swap': {
                        'usdt': 'wss://fx-ws-testnet.gateio.ws/v4/ws/usdt',
                        'btc': 'wss://fx-ws-testnet.gateio.ws/v4/ws/btc',
                    },
                    'future': {
                        'usdt': 'wss://fx-ws-testnet.gateio.ws/v4/ws/usdt',
                        'btc': 'wss://fx-ws-testnet.gateio.ws/v4/ws/btc',
                    },
                    'option': 'wss://op-ws-testnet.gateio.live/v4/ws',
                },
            },
            'options': {
                'tradesLimit': 1000,
                'OHLCVLimit': 1000,
                'watchTradesSubscriptions': {},
                'watchTickerSubscriptions': {},
                'watchOrderBookSubscriptions': {},
                'watchTicker': {
                    'name': 'tickers',  # or book_ticker
                },
                'watchOrderBook': {
                    'interval': '100ms',
                    'snapshotDelay': 10,  # how many deltas to cache before fetching a snapshot
                    'maxRetries': 3,
                },
                'watchBalance': {
                    'settle': 'usdt',  # or btc
                    'spot': 'spot.balances',  # spot.margin_balances, spot.funding_balances or spot.cross_balances
                },
            },
            'exceptions': {
                'ws': {
                    'exact': {
                        '2': BadRequest,
                        '4': AuthenticationError,
                        '6': AuthenticationError,
                        '11': AuthenticationError,
                    },
                },
            },
        })

    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 gate 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()
        market = self.market(symbol)
        symbol = market['symbol']
        marketId = market['id']
        interval, query = self.handle_option_and_params(params, 'watchOrderBook', 'interval', '100ms')
        messageType = self.get_type_by_market(market)
        channel = messageType + '.order_book_update'
        messageHash = 'orderbook' + ':' + symbol
        url = self.get_url_by_market(market)
        payload = [marketId, interval]
        if limit is None:
            limit = 100
        if market['contract']:
            stringLimit = str(limit)
            payload.append(stringLimit)
        subscription = {
            'symbol': symbol,
            'limit': limit,
        }
        orderbook = await self.subscribe_public(url, messageHash, payload, channel, subscription, query)
        return orderbook.limit()

    def handle_order_book_subscription(self, client, message, subscription):
        symbol = self.safe_string(subscription, 'symbol')
        limit = self.safe_integer(subscription, 'limit')
        self.orderbooks[symbol] = self.order_book({}, limit)

    def handle_order_book(self, client, message):
        #
        # spot
        #
        #     {
        #         time: 1650189272,
        #         channel: 'spot.order_book_update',
        #         event: 'update',
        #         result: {
        #             t: 1650189272515,
        #             e: 'depthUpdate',
        #             E: 1650189272,
        #             s: 'GMT_USDT',
        #             U: 140595902,
        #             u: 140595902,
        #             b: [
        #                 ['2.51518', '228.119'],
        #                 ['2.50587', '1510.11'],
        #                 ['2.49944', '67.6'],
        #             ],
        #             a: [
        #                 ['2.5182', '4.199'],
        #                 ['2.51926', '1874'],
        #                 ['2.53528', '96.529'],
        #             ]
        #         }
        #     }
        #
        # swap
        #
        #     {
        #         id: null,
        #         time: 1650188898,
        #         channel: 'futures.order_book_update',
        #         event: 'update',
        #         error: null,
        #         result: {
        #             t: 1650188898938,
        #             s: 'GMT_USDT',
        #             U: 1577718307,
        #             u: 1577719254,
        #             b: [
        #                 {p: '2.5178', s: 0},
        #                 {p: '2.5179', s: 0},
        #                 {p: '2.518', s: 0},
        #             ],
        #             a: [
        #                 {p: '2.52', s: 0},
        #                 {p: '2.5201', s: 0},
        #                 {p: '2.5203', s: 0},
        #             ]
        #         }
        #     }
        #
        channel = self.safe_string(message, 'channel')
        channelParts = channel.split('.')
        rawMarketType = self.safe_string(channelParts, 0)
        isSpot = rawMarketType == 'spot'
        marketType = 'spot' if isSpot else 'contract'
        delta = self.safe_value(message, 'result')
        deltaStart = self.safe_integer(delta, 'U')
        deltaEnd = self.safe_integer(delta, 'u')
        marketId = self.safe_string(delta, 's')
        symbol = self.safe_symbol(marketId, None, '_', marketType)
        messageHash = 'orderbook:' + symbol
        storedOrderBook = self.safe_value(self.orderbooks, symbol, self.order_book({}))
        nonce = self.safe_integer(storedOrderBook, 'nonce')
        if nonce is None:
            cacheLength = 0
            if storedOrderBook is not None:
                cacheLength = len(storedOrderBook.cache)
            snapshotDelay = self.handle_option('watchOrderBook', 'snapshotDelay', 10)
            waitAmount = snapshotDelay if isSpot else 0
            if cacheLength == waitAmount:
                # max limit is 100
                subscription = client.subscriptions[messageHash]
                limit = self.safe_integer(subscription, 'limit')
                self.spawn(self.load_order_book, client, messageHash, symbol, limit)
            storedOrderBook.cache.append(delta)
            return
        elif nonce >= deltaEnd:
            return
        elif nonce >= deltaStart - 1:
            self.handle_delta(storedOrderBook, delta)
        else:
            error = InvalidNonce(self.id + ' orderbook update has a nonce bigger than u')
            del client.subscriptions[messageHash]
            del self.orderbooks[symbol]
            client.reject(error, messageHash)
        client.resolve(storedOrderBook, messageHash)

    def get_cache_index(self, orderBook, cache):
        nonce = self.safe_integer(orderBook, 'nonce')
        firstDelta = cache[0]
        firstDeltaStart = self.safe_integer(firstDelta, 'U')
        if nonce < firstDeltaStart:
            return -1
        for i in range(0, len(cache)):
            delta = cache[i]
            deltaStart = self.safe_integer(delta, 'U')
            deltaEnd = self.safe_integer(delta, 'u')
            if (nonce >= deltaStart - 1) and (nonce < deltaEnd):
                return i
        return len(cache)

    def handle_bid_asks(self, bookSide, bidAsks):
        for i in range(0, len(bidAsks)):
            bidAsk = bidAsks[i]
            if isinstance(bidAsk, list):
                bookSide.storeArray(self.parse_bid_ask(bidAsk))
            else:
                price = self.safe_float(bidAsk, 'p')
                amount = self.safe_float(bidAsk, 's')
                bookSide.store(price, amount)

    def handle_delta(self, orderbook, delta):
        timestamp = self.safe_integer(delta, 't')
        orderbook['timestamp'] = timestamp
        orderbook['datetime'] = self.iso8601(timestamp)
        orderbook['nonce'] = self.safe_integer(delta, 'u')
        bids = self.safe_value(delta, 'b', [])
        asks = self.safe_value(delta, 'a', [])
        storedBids = orderbook['bids']
        storedAsks = orderbook['asks']
        self.handle_bid_asks(storedBids, bids)
        self.handle_bid_asks(storedAsks, asks)

    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 gate api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        marketId = market['id']
        url = self.get_url_by_market(market)
        messageType = self.get_type_by_market(market)
        topic, query = self.handle_option_and_params(params, 'watchTicker', 'method', 'tickers')
        channel = messageType + '.' + topic
        messageHash = 'ticker:' + symbol
        payload = [marketId]
        return await self.subscribe_public(url, messageHash, payload, channel, None, query)

    async def watch_tickers(self, symbols=None, params={}):
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
        :param [str] symbols: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the gate api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        if symbols is None:
            raise ArgumentsRequired(self.id + ' watchTickers requires symbols')
        market = self.market(symbols[0])
        messageType = self.get_type_by_market(market)
        marketIds = self.market_ids(symbols)
        topic, query = self.handle_option_and_params(params, 'watchTicker', 'method', 'tickers')
        channel = messageType + '.' + topic
        messageHash = 'tickers'
        url = self.get_url_by_market(market)
        ticker = await self.subscribe_public(url, messageHash, marketIds, channel, None, query)
        result = {}
        if self.newUpdates:
            result[ticker['symbol']] = ticker
        else:
            result = self.tickers
        return self.filter_by_array(result, 'symbol', symbols, True)

    def handle_ticker(self, client, message):
        #
        #    {
        #        time: 1649326221,
        #        channel: 'spot.tickers',
        #        event: 'update',
        #        result: {
        #          currency_pair: 'BTC_USDT',
        #          last: '43444.82',
        #          lowest_ask: '43444.82',
        #          highest_bid: '43444.81',
        #          change_percentage: '-4.0036',
        #          base_volume: '5182.5412425462',
        #          quote_volume: '227267634.93123952',
        #          high_24h: '47698',
        #          low_24h: '42721.03'
        #        }
        #    }
        #    {
        #        time: 1671363004,
        #        time_ms: 1671363004235,
        #        channel: 'spot.book_ticker',
        #        event: 'update',
        #        result: {
        #          t: 1671363004228,
        #          u: 9793320464,
        #          s: 'BTC_USDT',
        #          b: '16716.8',
        #          B: '0.0134',
        #          a: '16716.9',
        #          A: '0.0353'
        #        }
        #    }
        #
        channel = self.safe_string(message, 'channel')
        parts = channel.split('.')
        rawMarketType = self.safe_string(parts, 0)
        marketType = 'contract' if (rawMarketType == 'futures') else 'spot'
        result = self.safe_value(message, 'result')
        if not isinstance(result, list):
            result = [result]
        for i in range(0, len(result)):
            ticker = result[i]
            marketId = self.safe_string(ticker, 's')
            market = self.safe_market(marketId, None, '_', marketType)
            parsed = self.parse_ticker(ticker, market)
            symbol = parsed['symbol']
            self.tickers[symbol] = parsed
            messageHash = 'ticker:' + symbol
            client.resolve(parsed, messageHash)
            client.resolve(parsed, 'tickers')

    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 gate 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)
        symbol = market['symbol']
        marketId = market['id']
        messageType = self.get_type_by_market(market)
        channel = messageType + '.trades'
        messageHash = 'trades:' + symbol
        url = self.get_url_by_market(market)
        payload = [marketId]
        trades = await self.subscribe_public(url, messageHash, payload, channel, None, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def handle_trades(self, client, message):
        #
        # {
        #     time: 1648725035,
        #     channel: 'spot.trades',
        #     event: 'update',
        #     result: [{
        #       id: 3130257995,
        #       create_time: 1648725035,
        #       create_time_ms: '1648725035923.0',
        #       side: 'sell',
        #       currency_pair: 'LTC_USDT',
        #       amount: '0.0116',
        #       price: '130.11'
        #     }]
        # }
        #
        result = self.safe_value(message, 'result')
        if not isinstance(result, list):
            result = [result]
        parsedTrades = self.parse_trades(result)
        for i in range(0, len(parsedTrades)):
            trade = parsedTrades[i]
            symbol = trade['symbol']
            cachedTrades = self.safe_value(self.trades, symbol)
            if cachedTrades is None:
                limit = self.safe_integer(self.options, 'tradesLimit', 1000)
                cachedTrades = ArrayCache(limit)
                self.trades[symbol] = cachedTrades
            cachedTrades.append(trade)
            hash = 'trades:' + symbol
            client.resolve(cachedTrades, hash)

    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 gate api endpoint
        :returns [[int]]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        marketId = market['id']
        interval = self.safe_string(self.timeframes, timeframe, timeframe)
        messageType = self.get_type_by_market(market)
        channel = messageType + '.candlesticks'
        messageHash = 'candles:' + interval + ':' + market['symbol']
        url = self.get_url_by_market(market)
        payload = [interval, marketId]
        ohlcv = await self.subscribe_public(url, messageHash, payload, channel, None, params)
        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):
        #
        # {
        #     "time": 1606292600,
        #     "channel": "spot.candlesticks",
        #     "event": "update",
        #     "result": {
        #       "t": "1606292580",  # total volume
        #       "v": "2362.32035",  # volume
        #       "c": "19128.1",  # close
        #       "h": "19128.1",  # high
        #       "l": "19128.1",  # low
        #       "o": "19128.1",  # open
        #       "n": "1m_BTC_USDT"  # sub
        #     }
        #   }
        #
        channel = self.safe_string(message, 'channel')
        channelParts = channel.split('.')
        rawMarketType = self.safe_string(channelParts, 0)
        marketType = 'spot' if (rawMarketType == 'spot') else 'contract'
        result = self.safe_value(message, 'result')
        if not isinstance(result, list):
            result = [result]
        marketIds = {}
        for i in range(0, len(result)):
            ohlcv = result[i]
            subscription = self.safe_string(ohlcv, 'n', '')
            parts = subscription.split('_')
            timeframe = self.safe_string(parts, 0)
            prefix = timeframe + '_'
            marketId = subscription.replace(prefix, '')
            symbol = self.safe_symbol(marketId, None, '_', marketType)
            parsed = self.parse_ohlcv(ohlcv)
            stored = self.safe_value(self.ohlcvs, symbol)
            if stored is None:
                limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
                stored = ArrayCacheByTimestamp(limit)
                self.ohlcvs[symbol] = stored
            stored.append(parsed)
            marketIds[symbol] = timeframe
        keys = list(marketIds.keys())
        for i in range(0, len(keys)):
            symbol = keys[i]
            timeframe = marketIds[symbol]
            interval = self.find_timeframe(timeframe)
            hash = 'candles' + ':' + interval + ':' + symbol
            stored = self.safe_value(self.ohlcvs, symbol)
            client.resolve(stored, hash)

    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 gate api endpoint
        :returns [dict]: a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
        """
        await self.load_markets()
        subType = None
        type = None
        marketId = 'not all'
        market = None
        if symbol is not None:
            market = self.market(symbol)
            marketId = market['id']
        type, params = self.handle_market_type_and_params('watchMyTrades', market, params)
        subType, params = self.handle_sub_type_and_params('watchMyTrades', market, params)
        messageType = self.get_supported_mapping(type, {
            'spot': 'spot',
            'margin': 'spot',
            'future': 'futures',
            'swap': 'futures',
            'option': 'options',
        })
        channel = messageType + '.usertrades'
        messageHash = 'myTrades'
        if symbol is not None:
            messageHash += ':' + symbol
        isInverse = (subType == 'inverse')
        url = self.get_url_by_market_type(type, isInverse)
        payload = [marketId]
        # uid required for non spot markets
        requiresUid = (type != 'spot')
        trades = await self.subscribe_private(url, messageHash, payload, channel, params, requiresUid)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    def handle_my_trades(self, client, message):
        #
        # {
        #     "time": 1543205083,
        #     "channel": "futures.usertrades",
        #     "event": "update",
        #     "error": null,
        #     "result": [
        #       {
        #         "id": "3335259",
        #         "create_time": 1628736848,
        #         "create_time_ms": 1628736848321,
        #         "contract": "BTC_USD",
        #         "order_id": "4872460",
        #         "size": 1,
        #         "price": "40000.4",
        #         "role": "maker"
        #       }
        #     ]
        # }
        #
        result = self.safe_value(message, 'result', [])
        tradesLength = len(result)
        if tradesLength == 0:
            return
        cachedTrades = self.myTrades
        if cachedTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            cachedTrades = ArrayCacheBySymbolById(limit)
            self.myTrades = cachedTrades
        parsed = self.parse_trades(result)
        marketIds = {}
        for i in range(0, len(parsed)):
            trade = parsed[i]
            cachedTrades.append(trade)
            symbol = trade['symbol']
            marketIds[symbol] = True
        keys = list(marketIds.keys())
        for i in range(0, len(keys)):
            market = keys[i]
            hash = 'myTrades:' + market
            client.resolve(cachedTrades, hash)
        client.resolve(cachedTrades, 'myTrades')

    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 gate api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        await self.load_markets()
        type = None
        subType = None
        type, params = self.handle_market_type_and_params('watchBalance', None, params)
        subType, params = self.handle_sub_type_and_params('watchBalance', None, params)
        isInverse = (subType == 'inverse')
        url = self.get_url_by_market_type(type, isInverse)
        requiresUid = (type != 'spot')
        channelType = self.get_supported_mapping(type, {
            'spot': 'spot',
            'margin': 'spot',
            'future': 'futures',
            'swap': 'futures',
            'option': 'options',
        })
        channel = channelType + '.balances'
        messageHash = type + '.balance'
        return await self.subscribe_private(url, messageHash, None, channel, params, requiresUid)

    def handle_balance(self, client, message):
        #
        # spot order fill
        #   {
        #       time: 1653664351,
        #       channel: 'spot.balances',
        #       event: 'update',
        #       result: [
        #         {
        #           timestamp: '1653664351',
        #           timestamp_ms: '1653664351017',
        #           user: '10406147',
        #           currency: 'LTC',
        #           change: '-0.0002000000000000',
        #           total: '0.09986000000000000000',
        #           available: '0.09986000000000000000'
        #         }
        #       ]
        #   }
        #
        # account transfer
        #
        #    {
        #        id: null,
        #        time: 1653665088,
        #        channel: 'futures.balances',
        #        event: 'update',
        #        error: null,
        #        result: [
        #          {
        #            balance: 25.035008537,
        #            change: 25,
        #            text: '-',
        #            time: 1653665088,
        #            time_ms: 1653665088286,
        #            type: 'dnw',
        #            user: '10406147'
        #          }
        #        ]
        #   }
        #
        # swap order fill
        #   {
        #       id: null,
        #       time: 1653665311,
        #       channel: 'futures.balances',
        #       event: 'update',
        #       error: null,
        #       result: [
        #         {
        #           balance: 20.031873037,
        #           change: -0.0031355,
        #           text: 'LTC_USDT:165551103273',
        #           time: 1653665311,
        #           time_ms: 1653665311437,
        #           type: 'fee',
        #           user: '10406147'
        #         }
        #       ]
        #   }
        #
        result = self.safe_value(message, 'result', [])
        timestamp = self.safe_integer(message, 'time')
        self.balance['info'] = result
        self.balance['timestamp'] = timestamp
        self.balance['datetime'] = self.iso8601(timestamp)
        for i in range(0, len(result)):
            rawBalance = result[i]
            account = self.account()
            currencyId = self.safe_string(rawBalance, 'currency', 'USDT')  # when not present it is USDT
            code = self.safe_currency_code(currencyId)
            account['free'] = self.safe_string(rawBalance, 'available')
            account['total'] = self.safe_string_2(rawBalance, 'total', 'balance')
            self.balance[code] = account
        channel = self.safe_string(message, 'channel')
        parts = channel.split('.')
        rawType = self.safe_string(parts, 0)
        channelType = self.get_supported_mapping(rawType, {
            'spot': 'spot',
            'futures': 'swap',
            'options': 'option',
        })
        messageHash = channelType + '.balance'
        self.balance = self.safe_balance(self.balance)
        client.resolve(self.balance, 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 gate api endpoint
        :param str params['type']: spot, margin, swap, future, or option. Required if listening to all symbols.
        :param boolean params['isInverse']: if future, listen to inverse or linear contracts
        :returns [dict]: a list of [order structures]{@link 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']
        type = None
        query = None
        type, query = self.handle_market_type_and_params('watchOrders', market, params)
        typeId = self.get_supported_mapping(type, {
            'spot': 'spot',
            'margin': 'spot',
            'future': 'futures',
            'swap': 'futures',
            'option': 'options',
        })
        channel = typeId + '.orders'
        messageHash = 'orders'
        payload = ['!' + 'all']
        if symbol is not None:
            messageHash += ':' + market['id']
            payload = [market['id']]
        subType = None
        subType, query = self.handle_sub_type_and_params('watchOrders', market, query)
        isInverse = (subType == 'inverse')
        url = self.get_url_by_market_type(type, isInverse)
        # uid required for non spot markets
        requiresUid = (type != 'spot')
        orders = await self.subscribe_private(url, messageHash, payload, channel, query, requiresUid)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_since_limit(orders, since, limit, 'timestamp', True)

    def handle_order(self, client, message):
        #
        # {
        #     "time": 1605175506,
        #     "channel": "spot.orders",
        #     "event": "update",
        #     "result": [
        #       {
        #         "id": "30784435",
        #         "user": 123456,
        #         "text": "t-abc",
        #         "create_time": "1605175506",
        #         "create_time_ms": "1605175506123",
        #         "update_time": "1605175506",
        #         "update_time_ms": "1605175506123",
        #         "event": "put",
        #         "currency_pair": "BTC_USDT",
        #         "type": "limit",
        #         "account": "spot",
        #         "side": "sell",
        #         "amount": "1",
        #         "price": "10001",
        #         "time_in_force": "gtc",
        #         "left": "1",
        #         "filled_total": "0",
        #         "fee": "0",
        #         "fee_currency": "USDT",
        #         "point_fee": "0",
        #         "gt_fee": "0",
        #         "gt_discount": True,
        #         "rebated_fee": "0",
        #         "rebated_fee_currency": "USDT"
        #       }
        #     ]
        # }
        #
        orders = self.safe_value(message, 'result', [])
        limit = self.safe_integer(self.options, 'ordersLimit', 1000)
        if self.orders is None:
            self.orders = ArrayCacheBySymbolById(limit)
        stored = self.orders
        marketIds = {}
        parsedOrders = self.parse_orders(orders)
        for i in range(0, len(parsedOrders)):
            parsed = parsedOrders[i]
            # inject order status
            info = self.safe_value(parsed, 'info')
            event = self.safe_string(info, 'event')
            if event == 'put':
                parsed['status'] = 'open'
            elif event == 'finish':
                parsed['status'] = 'closed'
            stored.append(parsed)
            symbol = parsed['symbol']
            market = self.market(symbol)
            marketIds[market['id']] = True
        keys = list(marketIds.keys())
        for i in range(0, len(keys)):
            messageHash = 'orders:' + keys[i]
            client.resolve(self.orders, messageHash)
        client.resolve(self.orders, 'orders')

    def handle_error_message(self, client, message):
        # {
        #     time: 1647274664,
        #     channel: 'futures.orders',
        #     event: 'subscribe',
        #     error: {code: 2, message: 'unknown contract BTC_USDT_20220318'},
        # }
        # {
        #     time: 1647276473,
        #     channel: 'futures.orders',
        #     event: 'subscribe',
        #     error: {
        #       code: 4,
        #       message: '{"label":"INVALID_KEY","message":"Invalid key provided"}\n'
        #     },
        #     result: null
        #   }
        error = self.safe_value(message, 'error', {})
        code = self.safe_integer(error, 'code')
        if code is not None:
            id = self.safe_string(message, 'id')
            subscriptionsById = self.index_by(client.subscriptions, 'id')
            subscription = self.safe_value(subscriptionsById, id)
            if subscription is not None:
                try:
                    self.throw_exactly_matched_exception(self.exceptions['ws']['exact'], code, self.json(message))
                except Exception as e:
                    messageHash = self.safe_string(subscription, 'messageHash')
                    client.reject(e, messageHash)
                    if id in client.subscriptions:
                        del client.subscriptions[id]

    def handle_balance_subscription(self, client, message, subscription=None):
        self.balance = {}

    def handle_subscription_status(self, client, message):
        channel = self.safe_string(message, 'channel', '')
        methods = {
            'balance': self.handle_balance_subscription,
            'order_book': self.handle_order_book_subscription,
        }
        id = self.safe_integer(message, 'id')
        subscriptionsById = self.index_by(client.subscriptions, 'id')
        subscription = self.safe_value(subscriptionsById, id)
        if subscription is not None:
            keys = list(methods.keys())
            for i in range(0, len(keys)):
                key = keys[i]
                if channel.find(key) >= 0:
                    method = methods[key]
                    method(client, message, subscription)

    def handle_message(self, client, message):
        #
        # subscribe
        #    {
        #        time: 1649062304,
        #        id: 1649062303,
        #        channel: 'spot.candlesticks',
        #        event: 'subscribe',
        #        result: {status: 'success'}
        #    }
        #
        # candlestick
        #    {
        #        time: 1649063328,
        #        channel: 'spot.candlesticks',
        #        event: 'update',
        #        result: {
        #          t: '1649063280',
        #          v: '58932.23174896',
        #          c: '45966.47',
        #          h: '45997.24',
        #          l: '45966.47',
        #          o: '45975.18',
        #          n: '1m_BTC_USDT',
        #          a: '1.281699'
        #        }
        #     }
        #
        #  orders
        #   {
        #       "time": 1630654851,
        #       "channel": "options.orders", or futures.orders or spot.orders
        #       "event": "update",
        #       "result": [
        #          {
        #             "contract": "BTC_USDT-20211130-65000-C",
        #             "create_time": 1637897000,
        #               (...)
        #       ]
        #   }
        # orderbook
        #   {
        #       time: 1649770525,
        #       channel: 'spot.order_book_update',
        #       event: 'update',
        #       result: {
        #         t: 1649770525653,
        #         e: 'depthUpdate',
        #         E: 1649770525,
        #         s: 'LTC_USDT',
        #         U: 2622525645,
        #         u: 2622525665,
        #         b: [
        #           [Array], [Array],
        #           [Array], [Array],
        #           [Array], [Array],
        #           [Array], [Array],
        #           [Array], [Array],
        #           [Array]
        #         ],
        #         a: [
        #           [Array], [Array],
        #           [Array], [Array],
        #           [Array], [Array],
        #           [Array], [Array],
        #           [Array], [Array],
        #           [Array]
        #         ]
        #       }
        #     }
        #
        # balance update
        #
        #    {
        #        time: 1653664351,
        #        channel: 'spot.balances',
        #        event: 'update',
        #        result: [
        #          {
        #            timestamp: '1653664351',
        #            timestamp_ms: '1653664351017',
        #            user: '10406147',
        #            currency: 'LTC',
        #            change: '-0.0002000000000000',
        #            total: '0.09986000000000000000',
        #            available: '0.09986000000000000000'
        #          }
        #        ]
        #    }
        #
        self.handle_error_message(client, message)
        event = self.safe_string(message, 'event')
        if event == 'subscribe':
            self.handle_subscription_status(client, message)
            return
        channel = self.safe_string(message, 'channel', '')
        channelParts = channel.split('.')
        channelType = self.safe_value(channelParts, 1)
        v4Methods = {
            'usertrades': self.handle_my_trades,
            'candlesticks': self.handle_ohlcv,
            'orders': self.handle_order,
            'tickers': self.handle_ticker,
            'book_ticker': self.handle_ticker,
            'trades': self.handle_trades,
            'order_book_update': self.handle_order_book,
            'balances': self.handle_balance,
        }
        method = self.safe_value(v4Methods, channelType)
        if method is not None:
            method(client, message)

    def get_url_by_market(self, market):
        baseUrl = self.urls['api'][market['type']]
        if market['contract']:
            return baseUrl['usdt'] if market['linear'] else baseUrl['btc']
        else:
            return baseUrl

    def get_type_by_market(self, market):
        if market['spot']:
            return 'spot'
        elif market['option']:
            return 'options'
        else:
            return 'futures'

    def get_url_by_market_type(self, type, isInverse=False):
        if type == 'spot':
            spotUrl = self.urls['api']['spot']
            if spotUrl is None:
                raise NotSupported(self.id + ' does not have a testnet for the ' + type + ' market type.')
            return spotUrl
        elif type == 'swap':
            baseUrl = self.urls['api']['swap']
            return baseUrl['btc'] if isInverse else baseUrl['usdt']
        elif type == 'future':
            baseUrl = self.urls['api']['future']
            return baseUrl['btc'] if isInverse else baseUrl['usdt']
        elif type == 'option':
            return self.urls['api']['option']

    def request_id(self):
        # their support said that reqid must be an int32, not documented
        reqid = self.sum(self.safe_integer(self.options, 'reqid', 0), 1)
        self.options['reqid'] = reqid
        return reqid

    async def subscribe_public(self, url, messageHash, payload, subscriptionHash, subscription, params={}):
        requestId = self.request_id()
        time = self.seconds()
        request = {
            'id': requestId,
            'time': time,
            'channel': subscriptionHash,
            'event': 'subscribe',
            'payload': payload,
        }
        if subscription is None:
            subscription = {}
        subscription = self.extend(subscription, {
            'id': requestId,
            'messageHash': messageHash,
        })
        message = self.extend(request, params)
        return await self.watch(url, messageHash, message, messageHash, subscription)

    async def subscribe_private(self, url, messageHash, payload, subscriptionHash, params, requiresUid=False):
        self.check_required_credentials()
        # uid is required for some subscriptions only so it's not a part of required credentials
        if requiresUid:
            if self.uid is None or len(self.uid) == 0:
                raise ArgumentsRequired(self.id + ' requires uid to subscribe')
            idArray = [self.uid]
            if payload is None:
                payload = idArray
            else:
                payload = self.array_concat(idArray, payload)
        time = self.seconds()
        event = 'subscribe'
        signaturePayload = 'channel=' + subscriptionHash + '&' + 'event=' + event + '&' + 'time=' + str(time)
        signature = self.hmac(self.encode(signaturePayload), self.encode(self.secret), hashlib.sha512, 'hex')
        auth = {
            'method': 'api_key',
            'KEY': self.apiKey,
            'SIGN': signature,
        }
        requestId = self.request_id()
        request = {
            'id': requestId,
            'time': time,
            'channel': subscriptionHash,
            'event': 'subscribe',
            'auth': auth,
        }
        if payload is not None:
            request['payload'] = payload
        message = self.extend(request, params)
        subscription = {
            'id': requestId,
            'messageHash': messageHash,
        }
        return await self.watch(url, messageHash, message, messageHash, subscription)
