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


class gate(Exchange, 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/en/latest/manual.html#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/en/latest/manual.html#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/en/latest/manual.html#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 as timestamp, 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/en/latest/manual.html#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/en/latest/manual.html#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)
