# -*- coding: utf-8 -*-

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt_zagrava/ccxt_zagrava/blob/master/CONTRIBUTING.md#how-to-contribute-code

import ccxt_zagrava.async_support
from ccxt_zagrava.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
import hashlib
from ccxt_zagrava.base.types import Balances, Int, Order, OrderBook, Position, Str, Strings, Ticker, Trade
from ccxt_zagrava.async_support.base.ws.client import Client
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import BadSymbol
from ccxt_zagrava.base.errors import NetworkError
from ccxt_zagrava.base.errors import InvalidNonce


class htx(ccxt_zagrava.async_support.htx):

    def describe(self):
        return self.deep_extend(super(htx, self).describe(), {
            'has': {
                'ws': True,
                'createOrderWs': False,
                'editOrderWs': False,
                'fetchOpenOrdersWs': False,
                'fetchOrderWs': False,
                'cancelOrderWs': False,
                'cancelOrdersWs': False,
                'cancelAllOrdersWs': False,
                'fetchTradesWs': False,
                'fetchBalanceWs': False,
                'watchOrderBook': True,
                'watchOrders': True,
                'watchTickers': False,
                'watchTicker': True,
                'watchTrades': True,
                'watchMyTrades': True,
                'watchBalance': True,
                'watchOHLCV': True,
            },
            'urls': {
                'api': {
                    'ws': {
                        'api': {
                            'spot': {
                                'public': 'wss://{hostname}/ws',
                                'private': 'wss://{hostname}/ws/v2',
                                'feed': 'wss://{hostname}/feed',
                            },
                            'future': {
                                'linear': {
                                    'public': 'wss://api.hbdm.com/linear-swap-ws',
                                    'private': 'wss://api.hbdm.com/linear-swap-notification',
                                },
                                'inverse': {
                                    'public': 'wss://api.hbdm.com/ws',
                                    'private': 'wss://api.hbdm.com/notification',
                                },
                            },
                            'swap': {
                                'inverse': {
                                    'public': 'wss://api.hbdm.com/swap-ws',
                                    'private': 'wss://api.hbdm.com/swap-notification',
                                },
                                'linear': {
                                    'public': 'wss://api.hbdm.com/linear-swap-ws',
                                    'private': 'wss://api.hbdm.com/linear-swap-notification',
                                },
                            },
                        },
                        # these settings work faster for clients hosted on AWS
                        'api-aws': {
                            'spot': {
                                'public': 'wss://api-aws.huobi.pro/ws',
                                'private': 'wss://api-aws.huobi.pro/ws/v2',
                                'feed': 'wss://{hostname}/feed',
                            },
                            'future': {
                                'linear': {
                                    'public': 'wss://api.hbdm.vn/linear-swap-ws',
                                    'private': 'wss://api.hbdm.vn/linear-swap-notification',
                                },
                                'inverse': {
                                    'public': 'wss://api.hbdm.vn/ws',
                                    'private': 'wss://api.hbdm.vn/notification',
                                },
                            },
                            'swap': {
                                'linear': {
                                    'public': 'wss://api.hbdm.vn/linear-swap-ws',
                                    'private': 'wss://api.hbdm.vn/linear-swap-notification',
                                },
                                'inverse': {
                                    'public': 'wss://api.hbdm.vn/swap-ws',
                                    'private': 'wss://api.hbdm.vn/swap-notification',
                                },
                            },
                        },
                    },
                },
            },
            'options': {
                'tradesLimit': 1000,
                'OHLCVLimit': 1000,
                'api': 'api',  # or api-aws for clients hosted on AWS
                'watchOrderBook': {
                    'maxRetries': 3,
                },
                'ws': {
                    'gunzip': True,
                },
                'watchTicker': {
                    'name': 'market.{marketId}.detail',  # 'market.{marketId}.bbo' or 'market.{marketId}.ticker'
                },
            },
            'exceptions': {
                'ws': {
                    'exact': {
                        'bad-request': BadRequest,  # { ts: 1586323747018,  status: 'error',    'err-code': 'bad-request',  err-msg': 'invalid mbp.150.symbol linkusdt', id: '2'}
                        '2002': AuthenticationError,  # {action: 'sub', code: 2002, ch: 'accounts.update#2', message: 'invalid.auth.state'}
                        '2021': BadRequest,
                        '2001': BadSymbol,  # {action: 'sub', code: 2001, ch: 'orders#2ltcusdt', message: 'invalid.symbol'}
                        '2011': BadSymbol,  # {op: 'sub', cid: '1649149285', topic: 'orders_cross.ltc-usdt', 'err-code': 2011, 'err-msg': "Contract doesn't exist.", ts: 1649149287637}
                        '2040': BadRequest,  # {op: 'sub', cid: '1649152947', 'err-code': 2040, 'err-msg': 'Missing required parameter.', ts: 1649152948684}
                        '4007': BadRequest,  # {op: 'sub', cid: '1', topic: 'accounts_unify.USDT', 'err-code': 4007, 'err-msg': 'Non - single account user is not available, please check through the cross and isolated account asset interface', ts: 1698419318540}
                    },
                },
            },
        })

    def request_id(self):
        requestId = self.sum(self.safe_integer(self.options, 'requestId', 0), 1)
        self.options['requestId'] = requestId
        return str(requestId)

    async def watch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        options = self.safe_value(self.options, 'watchTicker', {})
        topic = self.safe_string(options, 'name', 'market.{marketId}.detail')
        if topic == 'market.{marketId}.ticker' and market['type'] != 'spot':
            raise BadRequest(self.id + ' watchTicker() with name market.{marketId}.ticker is only allowed for spot markets, use market.{marketId}.detail instead')
        messageHash = self.implode_params(topic, {'marketId': market['id']})
        url = self.get_url_by_market_type(market['type'], market['linear'])
        return await self.subscribe_public(url, symbol, messageHash, None, params)

    def handle_ticker(self, client: Client, message):
        #
        # "market.btcusdt.detail"
        #     {
        #         "ch": "market.btcusdt.detail",
        #         "ts": 1583494163784,
        #         "tick": {
        #             "id": 209988464418,
        #             "low": 8988,
        #             "high": 9155.41,
        #             "open": 9078.91,
        #             "close": 9136.46,
        #             "vol": 237813910.5928412,
        #             "amount": 26184.202558551195,
        #             "version": 209988464418,
        #             "count": 265673
        #         }
        #     }
        # "market.btcusdt.bbo"
        #     {
        #         "ch": "market.btcusdt.bbo",
        #         "ts": 1671941599613,
        #         "tick": {
        #             "seqId": 161499562790,
        #             "ask": 16829.51,
        #             "askSize": 0.707776,
        #             "bid": 16829.5,
        #             "bidSize": 1.685945,
        #             "quoteTime": 1671941599612,
        #             "symbol": "btcusdt"
        #         }
        #     }
        #
        tick = self.safe_value(message, 'tick', {})
        ch = self.safe_string(message, 'ch')
        parts = ch.split('.')
        marketId = self.safe_string(parts, 1)
        market = self.safe_market(marketId)
        ticker = self.parse_ticker(tick, market)
        timestamp = self.safe_value(message, 'ts')
        ticker['timestamp'] = timestamp
        ticker['datetime'] = self.iso8601(timestamp)
        symbol = ticker['symbol']
        self.tickers[symbol] = ticker
        client.resolve(ticker, ch)
        return message

    async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        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 [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        messageHash = 'market.' + market['id'] + '.trade.detail'
        url = self.get_url_by_market_type(market['type'], market['linear'])
        trades = await self.subscribe_public(url, symbol, messageHash, 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: Client, message):
        #
        #     {
        #         "ch": "market.btcusdt.trade.detail",
        #         "ts": 1583495834011,
        #         "tick": {
        #             "id": 105004645372,
        #             "ts": 1583495833751,
        #             "data": [
        #                 {
        #                     "id": 1.050046453727319e+22,
        #                     "ts": 1583495833751,
        #                     "tradeId": 102090727790,
        #                     "amount": 0.003893,
        #                     "price": 9150.01,
        #                     "direction": "sell"
        #                 }
        #             ]
        #         }
        #     }
        #
        tick = self.safe_value(message, 'tick', {})
        data = self.safe_value(tick, 'data', {})
        ch = self.safe_string(message, 'ch')
        parts = ch.split('.')
        marketId = self.safe_string(parts, 1)
        market = self.safe_market(marketId)
        symbol = market['symbol']
        tradesCache = self.safe_value(self.trades, symbol)
        if tradesCache is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            tradesCache = ArrayCache(limit)
            self.trades[symbol] = tradesCache
        for i in range(0, len(data)):
            trade = self.parse_trade(data[i], market)
            tradesCache.append(trade)
        client.resolve(tradesCache, ch)
        return message

    async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        interval = self.safe_string(self.timeframes, timeframe, timeframe)
        messageHash = 'market.' + market['id'] + '.kline.' + interval
        url = self.get_url_by_market_type(market['type'], market['linear'])
        ohlcv = await self.subscribe_public(url, symbol, messageHash, 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: Client, message):
        #
        #     {
        #         "ch": "market.btcusdt.kline.1min",
        #         "ts": 1583501786794,
        #         "tick": {
        #             "id": 1583501760,
        #             "open": 9094.5,
        #             "close": 9094.51,
        #             "low": 9094.5,
        #             "high": 9094.51,
        #             "amount": 0.44639786263800907,
        #             "vol": 4059.76919054,
        #             "count": 16
        #         }
        #     }
        #
        ch = self.safe_string(message, 'ch')
        parts = ch.split('.')
        marketId = self.safe_string(parts, 1)
        market = self.safe_market(marketId)
        symbol = market['symbol']
        interval = self.safe_string(parts, 3)
        timeframe = self.find_timeframe(interval)
        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
        tick = self.safe_value(message, 'tick')
        parsed = self.parse_ohlcv(tick, market)
        stored.append(parsed)
        client.resolve(stored, ch)

    async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        :see: https://huobiapi.github.io/docs/dm/v1/en/#subscribe-market-depth-data
        :see: https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#subscribe-incremental-market-depth-data
        :see: https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-subscribe-incremental-market-depth-data
        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 [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        allowedLimits = [20, 150]
        # 2) 5-level/20-level incremental MBP is a tick by tick feed,
        # which means whenever there is an order book change at that level, it pushes an update
        # 150-levels/400-level incremental MBP feed is based on the gap
        # between two snapshots at 100ms interval.
        if limit is None:
            limit = 150 if market['spot'] else 20
        if not self.in_array(limit, allowedLimits):
            raise ExchangeError(self.id + ' watchOrderBook market accepts limits of 20 and 150 only')
        messageHash = None
        if market['spot']:
            messageHash = 'market.' + market['id'] + '.mbp.' + str(limit)
        else:
            messageHash = 'market.' + market['id'] + '.depth.size_' + str(limit) + '.high_freq'
        url = self.get_url_by_market_type(market['type'], market['linear'], False, True)
        method = self.handle_order_book_subscription
        if not market['spot']:
            params = self.extend(params)
            params['data_type'] = 'incremental'
            method = None
        orderbook = await self.subscribe_public(url, symbol, messageHash, method, params)
        return orderbook.limit()

    def handle_order_book_snapshot(self, client: Client, message, subscription):
        #
        #     {
        #         "id": 1583473663565,
        #         "rep": "market.btcusdt.mbp.150",
        #         "status": "ok",
        #         "ts": 1698359289261,
        #         "data": {
        #             "seqNum": 104999417756,
        #             "bids": [
        #                 [9058.27, 0],
        #                 [9058.43, 0],
        #                 [9058.99, 0],
        #             ],
        #             "asks": [
        #                 [9084.27, 0.2],
        #                 [9085.69, 0],
        #                 [9085.81, 0],
        #             ]
        #         }
        #     }
        #
        symbol = self.safe_string(subscription, 'symbol')
        messageHash = self.safe_string(subscription, 'messageHash')
        id = self.safe_string(message, 'id')
        lastTimestamp = self.safe_integer(subscription, 'lastTimestamp')
        try:
            orderbook = self.orderbooks[symbol]
            data = self.safe_value(message, 'data')
            messages = orderbook.cache
            firstMessage = self.safe_value(messages, 0, {})
            snapshot = self.parse_order_book(data, symbol)
            tick = self.safe_value(firstMessage, 'tick')
            sequence = self.safe_integer(tick, 'prevSeqNum')
            nonce = self.safe_integer(data, 'seqNum')
            snapshot['nonce'] = nonce
            snapshotTimestamp = self.safe_integer(message, 'ts')
            subscription['lastTimestamp'] = snapshotTimestamp
            snapshotLimit = self.safe_integer(subscription, 'limit')
            snapshotOrderBook = self.order_book(snapshot, snapshotLimit)
            client.resolve(snapshotOrderBook, id)
            if (sequence is None) or (nonce < sequence):
                maxAttempts = self.handle_option('watchOrderBook', 'maxRetries', 3)
                numAttempts = self.safe_integer(subscription, 'numAttempts', 0)
                # retry to synchronize if we have not reached maxAttempts yet
                if numAttempts < maxAttempts:
                    # safety guard
                    if messageHash in client.subscriptions:
                        numAttempts = self.sum(numAttempts, 1)
                        delayTime = self.sum(1000, lastTimestamp - snapshotTimestamp)
                        subscription['numAttempts'] = numAttempts
                        client.subscriptions[messageHash] = subscription
                        self.delay(delayTime, self.watch_order_book_snapshot, client, message, subscription)
                else:
                    # raise upon failing to synchronize in maxAttempts
                    raise InvalidNonce(self.id + ' failed to synchronize WebSocket feed with the snapshot for symbol ' + symbol + ' in ' + str(maxAttempts) + ' attempts')
            else:
                orderbook.reset(snapshot)
                # unroll the accumulated deltas
                for i in range(0, len(messages)):
                    self.handle_order_book_message(client, messages[i])
                orderbook.cache = []
                self.orderbooks[symbol] = orderbook
                client.resolve(orderbook, messageHash)
        except Exception as e:
            del client.subscriptions[messageHash]
            del self.orderbooks[symbol]
            client.reject(e, messageHash)

    async def watch_order_book_snapshot(self, client, message, subscription):
        messageHash = self.safe_string(subscription, 'messageHash')
        symbol = self.safe_string(subscription, 'symbol')
        limit = self.safe_integer(subscription, 'limit')
        timestamp = self.safe_integer(message, 'ts')
        params = self.safe_value(subscription, 'params')
        attempts = self.safe_integer(subscription, 'numAttempts', 0)
        market = self.market(symbol)
        url = self.get_url_by_market_type(market['type'], market['linear'], False, True)
        requestId = self.request_id()
        request: dict = {
            'req': messageHash,
            'id': requestId,
        }
        # self is a temporary subscription by a specific requestId
        # it has a very short lifetime until the snapshot is received over ws
        snapshotSubscription: dict = {
            'id': requestId,
            'messageHash': messageHash,
            'symbol': symbol,
            'limit': limit,
            'params': params,
            'numAttempts': attempts,
            'lastTimestamp': timestamp,
            'method': self.handle_order_book_snapshot,
        }
        try:
            orderbook = await self.watch(url, requestId, request, requestId, snapshotSubscription)
            return orderbook.limit()
        except Exception as e:
            del client.subscriptions[messageHash]
            client.reject(e, messageHash)
        return None

    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: Client, message):
        # spot markets
        #
        #     {
        #         "ch": "market.btcusdt.mbp.150",
        #         "ts": 1583472025885,
        #         "tick": {
        #             "seqNum": 104998984994,
        #             "prevSeqNum": 104998984977,
        #             "bids": [
        #                 [9058.27, 0],
        #                 [9058.43, 0],
        #                 [9058.99, 0],
        #             ],
        #             "asks": [
        #                 [9084.27, 0.2],
        #                 [9085.69, 0],
        #                 [9085.81, 0],
        #             ]
        #         }
        #     }
        #
        # non-spot market update
        #
        #     {
        #         "ch":"market.BTC220218.depth.size_150.high_freq",
        #         "tick":{
        #             "asks":[],
        #             "bids":[
        #                 [43445.74,1],
        #                 [43444.48,0],
        #                 [40593.92,9]
        #             ],
        #             "ch":"market.BTC220218.depth.size_150.high_freq",
        #             "event":"update",
        #             "id":152727500274,
        #             "mrid":152727500274,
        #             "ts":1645023376098,
        #             "version":37536690
        #         },
        #         "ts":1645023376098
        #     }
        # non-spot market snapshot
        #
        #     {
        #         "ch":"market.BTC220218.depth.size_150.high_freq",
        #         "tick":{
        #             "asks":[
        #                 [43445.74,1],
        #                 [43444.48,0],
        #                 [40593.92,9]
        #             ],
        #             "bids":[
        #                 [43445.74,1],
        #                 [43444.48,0],
        #                 [40593.92,9]
        #             ],
        #             "ch":"market.BTC220218.depth.size_150.high_freq",
        #             "event":"snapshot",
        #             "id":152727500274,
        #             "mrid":152727500274,
        #             "ts":1645023376098,
        #             "version":37536690
        #         },
        #         "ts":1645023376098
        #     }
        #
        ch = self.safe_value(message, 'ch')
        parts = ch.split('.')
        marketId = self.safe_string(parts, 1)
        market = self.safe_market(marketId)
        symbol = market['symbol']
        orderbook = self.orderbooks[symbol]
        tick = self.safe_value(message, 'tick', {})
        seqNum = self.safe_integer(tick, 'seqNum')
        prevSeqNum = self.safe_integer(tick, 'prevSeqNum')
        event = self.safe_string(tick, 'event')
        version = self.safe_integer(tick, 'version')
        timestamp = self.safe_integer(message, 'ts')
        if event == 'snapshot':
            snapshot = self.parse_order_book(tick, symbol, timestamp)
            orderbook.reset(snapshot)
            orderbook['nonce'] = version
        if (prevSeqNum is not None) and prevSeqNum > orderbook['nonce']:
            raise InvalidNonce(self.id + ' watchOrderBook() received a mesage out of order')
        spotConditon = market['spot'] and (prevSeqNum == orderbook['nonce'])
        nonSpotCondition = market['contract'] and (version - 1 == orderbook['nonce'])
        if spotConditon or nonSpotCondition:
            asks = self.safe_value(tick, 'asks', [])
            bids = self.safe_value(tick, 'bids', [])
            self.handle_deltas(orderbook['asks'], asks)
            self.handle_deltas(orderbook['bids'], bids)
            orderbook['nonce'] = seqNum if spotConditon else version
            orderbook['timestamp'] = timestamp
            orderbook['datetime'] = self.iso8601(timestamp)

    def handle_order_book(self, client: Client, message):
        #
        # deltas
        #
        # spot markets
        #
        #     {
        #         "ch": "market.btcusdt.mbp.150",
        #         "ts": 1583472025885,
        #         "tick": {
        #             "seqNum": 104998984994,
        #             "prevSeqNum": 104998984977,
        #             "bids": [
        #                 [9058.27, 0],
        #                 [9058.43, 0],
        #                 [9058.99, 0],
        #             ],
        #             "asks": [
        #                 [9084.27, 0.2],
        #                 [9085.69, 0],
        #                 [9085.81, 0],
        #             ]
        #         }
        #     }
        #
        # non spot markets
        #
        #     {
        #         "ch":"market.BTC220218.depth.size_150.high_freq",
        #         "tick":{
        #             "asks":[],
        #             "bids":[
        #                 [43445.74,1],
        #                 [43444.48,0],
        #                 [40593.92,9]
        #             ],
        #             "ch":"market.BTC220218.depth.size_150.high_freq",
        #             "event":"update",
        #             "id":152727500274,
        #             "mrid":152727500274,
        #             "ts":1645023376098,
        #             "version":37536690
        #         },
        #         "ts":1645023376098
        #     }
        #
        messageHash = self.safe_string(message, 'ch')
        tick = self.safe_dict(message, 'tick')
        event = self.safe_string(tick, 'event')
        ch = self.safe_string(message, 'ch')
        parts = ch.split('.')
        marketId = self.safe_string(parts, 1)
        symbol = self.safe_symbol(marketId)
        if not (symbol in self.orderbooks):
            size = self.safe_string(parts, 3)
            sizeParts = size.split('_')
            limit = self.safe_integer(sizeParts, 1)
            self.orderbooks[symbol] = self.order_book({}, limit)
        orderbook = self.orderbooks[symbol]
        if (event is None) and (orderbook['nonce'] is None):
            orderbook.cache.append(message)
        else:
            self.handle_order_book_message(client, message)
            client.resolve(orderbook, messageHash)

    def handle_order_book_subscription(self, client: Client, message, subscription):
        symbol = self.safe_string(subscription, 'symbol')
        market = self.market(symbol)
        limit = self.safe_integer(subscription, 'limit')
        self.orderbooks[symbol] = self.order_book({}, limit)
        if market['spot']:
            self.spawn(self.watch_order_book_snapshot, client, message, subscription)

    async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        watches information on multiple trades made by the user
        :param str symbol: unified market symbol of the market trades were made in
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trade structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=trade-structure>`
        """
        self.check_required_credentials()
        await self.load_markets()
        type = None
        marketId = '*'  # wildcard
        market = None
        messageHash = None
        channel = None
        trades = None
        subType = None
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            type = market['type']
            subType = 'linear' if market['linear'] else 'inverse'
            marketId = market['lowercaseId']
        else:
            type = self.safe_string(self.options, 'defaultType', 'spot')
            type = self.safe_string(params, 'type', type)
            subType = self.safe_string_2(self.options, 'subType', 'defaultSubType', 'linear')
            subType = self.safe_string(params, 'subType', subType)
            params = self.omit(params, ['type', 'subType'])
        if type == 'spot':
            mode = None
            if mode is None:
                mode = self.safe_string_2(self.options, 'watchMyTrades', 'mode', '0')
                mode = self.safe_string(params, 'mode', mode)
                params = self.omit(params, 'mode')
            messageHash = 'trade.clearing' + '#' + marketId + '#' + mode
            channel = messageHash
        else:
            channelAndMessageHash = self.get_order_channel_and_message_hash(type, subType, market, params)
            channel = self.safe_string(channelAndMessageHash, 0)
            orderMessageHash = self.safe_string(channelAndMessageHash, 1)
            # we will take advantage of the order messageHash because already handles stuff
            # like symbol/margin/subtype/type variations
            messageHash = orderMessageHash + ':' + 'trade'
        trades = await self.subscribe_private(channel, messageHash, type, subType, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    def get_order_channel_and_message_hash(self, type, subType, market=None, params={}):
        messageHash = None
        channel = None
        orderType = self.safe_string(self.options, 'orderType', 'orders')  # orders or matchOrders
        orderType = self.safe_string(params, 'orderType', orderType)
        params = self.omit(params, 'orderType')
        marketCode = market['lowercaseId'].lower() if (market is not None) else None
        baseId = market['baseId'] if (market is not None) else None
        prefix = orderType
        messageHash = prefix
        if subType == 'linear':
            # USDT Margined Contracts Example: LTC/USDT:USDT
            marginMode = self.safe_string(params, 'margin', 'cross')
            marginPrefix = prefix + '_cross' if (marginMode == 'cross') else prefix
            messageHash = marginPrefix
            if marketCode is not None:
                messageHash += '.' + marketCode
                channel = messageHash
            else:
                channel = marginPrefix + '.' + '*'
        elif type == 'future':
            # inverse futures Example: BCH/USD:BCH-220408
            if baseId is not None:
                channel = prefix + '.' + baseId.lower()
                messageHash = channel
            else:
                channel = prefix + '.' + '*'
        else:
            # inverse swaps: Example: BTC/USD:BTC
            if marketCode is not None:
                channel = prefix + '.' + marketCode
                messageHash = channel
            else:
                channel = prefix + '.' + '*'
        return [channel, messageHash]

    async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        watches information on multiple orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        type = None
        subType = None
        market = None
        suffix = '*'  # wildcard
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            type = market['type']
            suffix = market['lowercaseId']
            subType = 'linear' if market['linear'] else 'inverse'
        else:
            type = self.safe_string(self.options, 'defaultType', 'spot')
            type = self.safe_string(params, 'type', type)
            subType = self.safe_string_2(self.options, 'subType', 'defaultSubType', 'linear')
            subType = self.safe_string(params, 'subType', subType)
            params = self.omit(params, ['type', 'subType'])
        messageHash = None
        channel = None
        if type == 'spot':
            messageHash = 'orders' + '#' + suffix
            channel = messageHash
        else:
            channelAndMessageHash = self.get_order_channel_and_message_hash(type, subType, market, params)
            channel = self.safe_string(channelAndMessageHash, 0)
            messageHash = self.safe_string(channelAndMessageHash, 1)
        orders = await self.subscribe_private(channel, messageHash, type, subType, params)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_since_limit(orders, since, limit, 'timestamp', True)

    def handle_order(self, client: Client, message):
        #
        # spot
        #
        #     {
        #         "action":"push",
        #         "ch":"orders#btcusdt",  # or "orders#*" for global subscriptions
        #         "data": {
        #             "orderSource": "spot-web",
        #             "orderCreateTime": 1645116048355,
        #             "accountId": 44234548,
        #             "orderPrice": "100",
        #             "orderSize": "0.05",
        #             "symbol": "ethusdt",
        #             "type": "buy-limit",
        #             "orderId": "478861479986886",
        #             "eventType": "creation",
        #             "clientOrderId": '',
        #             "orderStatus": "submitted"
        #         }
        #     }
        #
        # spot wrapped trade
        #
        #     {
        #         "action": "push",
        #         "ch": "orders#ltcusdt",
        #         "data": {
        #             "tradePrice": "130.01",
        #             "tradeVolume": "0.0385",
        #             "tradeTime": 1648714741525,
        #             "aggressor": True,
        #             "execAmt": "0.0385",
        #             "orderSource": "spot-web",
        #             "orderSize": "0.0385",
        #             "remainAmt": "0",
        #             "tradeId": 101541578884,
        #             "symbol": "ltcusdt",
        #             "type": "sell-market",
        #             "eventType": "trade",
        #             "clientOrderId": '',
        #             "orderStatus": "filled",
        #             "orderId": 509835753860328
        #         }
        #     }
        #
        # non spot order
        #
        # {
        #     "contract_type": "swap",
        #     "pair": "LTC-USDT",
        #     "business_type": "swap",
        #     "op": "notify",
        #     "topic": "orders_cross.ltc-usdt",
        #     "ts": 1650354508696,
        #     "symbol": "LTC",
        #     "contract_code": "LTC-USDT",
        #     "volume": 1,
        #     "price": 110.34,
        #     "order_price_type": "lightning",
        #     "direction": "sell",
        #     "offset": "close",
        #     "status": 6,
        #     "lever_rate": 1,
        #     "order_id": "966002354015051776",
        #     "order_id_str": "966002354015051776",
        #     "client_order_id": null,
        #     "order_source": "web",
        #     "order_type": 1,
        #     "created_at": 1650354508649,
        #     "trade_volume": 1,
        #     "trade_turnover": 11.072,
        #     "fee": -0.005536,
        #     "trade_avg_price": 110.72,
        #     "margin_frozen": 0,
        #     "profit": -0.045,
        #     "trade": [
        #       {
        #         "trade_fee": -0.005536,
        #         "fee_asset": "USDT",
        #         "real_profit": 0.473,
        #         "profit": -0.045,
        #         "trade_id": 86678766507,
        #         "id": "86678766507-966002354015051776-1",
        #         "trade_volume": 1,
        #         "trade_price": 110.72,
        #         "trade_turnover": 11.072,
        #         "created_at": 1650354508656,
        #         "role": "taker"
        #       }
        #     ],
        #     "canceled_at": 0,
        #     "fee_asset": "USDT",
        #     "margin_asset": "USDT",
        #     "uid": "359305390",
        #     "liquidation_type": "0",
        #     "margin_mode": "cross",
        #     "margin_account": "USDT",
        #     "is_tpsl": 0,
        #     "real_profit": 0.473,
        #     "trade_partition": "USDT",
        #     "reduce_only": 1
        #   }
        #
        #
        messageHash = self.safe_string_2(message, 'ch', 'topic')
        data = self.safe_value(message, 'data')
        marketId = self.safe_string(message, 'contract_code')
        if marketId is None:
            marketId = self.safe_string(data, 'symbol')
        market = self.safe_market(marketId)
        parsedOrder = None
        if data is not None:
            # spot updates
            eventType = self.safe_string(data, 'eventType')
            if eventType == 'trade':
                # when a spot order is filled we get an update message
                # with the trade info
                parsedTrade = self.parse_order_trade(data, market)
                # inject trade in existing order by faking an order object
                orderId = self.safe_string(parsedTrade, 'order')
                trades = [parsedTrade]
                status = self.parse_order_status(self.safe_string_2(data, 'orderStatus', 'status', 'closed'))
                filled = self.safe_string(data, 'execAmt')
                remaining = self.safe_string(data, 'remainAmt')
                order: dict = {
                    'id': orderId,
                    'trades': trades,
                    'status': status,
                    'symbol': market['symbol'],
                    'filled': self.parse_number(filled),
                    'remaining': self.parse_number(remaining),
                }
                parsedOrder = order
            else:
                parsedOrder = self.parse_ws_order(data, market)
        else:
            # contract branch
            parsedOrder = self.parse_ws_order(message, market)
            rawTrades = self.safe_value(message, 'trade', [])
            tradesLength = len(rawTrades)
            if tradesLength > 0:
                tradesObject: dict = {
                    'trades': rawTrades,
                    'ch': messageHash,
                    'symbol': marketId,
                }
                # inject order params in every trade
                extendTradeParams: dict = {
                    'order': self.safe_string(parsedOrder, 'id'),
                    'type': self.safe_string(parsedOrder, 'type'),
                    'side': self.safe_string(parsedOrder, 'side'),
                }
                # trades arrive inside an order update
                # we're forwarding them to handleMyTrade
                # so they can be properly resolved
                self.handle_my_trade(client, tradesObject, extendTradeParams)
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        cachedOrders = self.orders
        cachedOrders.append(parsedOrder)
        client.resolve(self.orders, messageHash)
        # when we make a global subscription(for contracts only) our message hash can't have a symbol/currency attached
        # so we're removing it here
        genericMessageHash = messageHash.replace('.' + market['lowercaseId'], '')
        lowerCaseBaseId = self.safe_string_lower(market, 'baseId')
        genericMessageHash = genericMessageHash.replace('.' + lowerCaseBaseId, '')
        client.resolve(self.orders, genericMessageHash)

    def parse_ws_order(self, order, market=None):
        #
        # spot
        #
        #     {
        #         "orderSource": "spot-web",
        #         "orderCreateTime": 1645116048355,  # creating only
        #         "accountId": 44234548,
        #         "orderPrice": "100",
        #         "orderSize": "0.05",
        #         "orderValue": "3.71676361",  # market-buy only
        #         "symbol": "ethusdt",
        #         "type": "buy-limit",
        #         "orderId": "478861479986886",
        #         "eventType": "creation",
        #         "clientOrderId": '',
        #         "orderStatus": "submitted"
        #         "lastActTime":1645118621810  # except creating
        #         "execAmt":"0"
        #     }
        #
        # swap order
        #
        #     {
        #         "contract_type": "swap",
        #         "pair": "LTC-USDT",
        #         "business_type": "swap",
        #         "op": "notify",
        #         "topic": "orders_cross.ltc-usdt",
        #         "ts": 1648717911384,
        #         "symbol": "LTC",
        #         "contract_code": "LTC-USDT",
        #         "volume": 1,
        #         "price": 129.13,
        #         "order_price_type": "lightning",
        #         "direction": "sell",
        #         "offset": "close",
        #         "status": 6,
        #         "lever_rate": 5,
        #         "order_id": "959137967397068800",
        #         "order_id_str": "959137967397068800",
        #         "client_order_id": null,
        #         "order_source": "web",
        #         "order_type": 1,
        #         "created_at": 1648717911344,
        #         "trade_volume": 1,
        #         "trade_turnover": 12.952,
        #         "fee": -0.006476,
        #         "trade_avg_price": 129.52,
        #         "margin_frozen": 0,
        #         "profit": -0.005,
        #         "trade": [
        #             {
        #                 "trade_fee": -0.006476,
        #                 "fee_asset": "USDT",
        #                 "real_profit": -0.005,
        #                 "profit": -0.005,
        #                 "trade_id": 83619995370,
        #                 "id": "83619995370-959137967397068800-1",
        #                 "trade_volume": 1,
        #                 "trade_price": 129.52,
        #                 "trade_turnover": 12.952,
        #                 "created_at": 1648717911352,
        #                 "role": "taker"
        #             }
        #         ],
        #         "canceled_at": 0,
        #         "fee_asset": "USDT",
        #         "margin_asset": "USDT",
        #         "uid": "359305390",
        #         "liquidation_type": "0",
        #         "margin_mode": "cross",
        #         "margin_account": "USDT",
        #         "is_tpsl": 0,
        #         "real_profit": -0.005,
        #         "trade_partition": "USDT",
        #         "reduce_only": 1
        #     }
        #
        #     {
        #         "op":"notify",
        #         "topic":"orders.ada",
        #         "ts":1604388667226,
        #         "symbol":"ADA",
        #         "contract_type":"quarter",
        #         "contract_code":"ADA201225",
        #         "volume":1,
        #         "price":0.0905,
        #         "order_price_type":"post_only",
        #         "direction":"sell",
        #         "offset":"open",
        #         "status":6,
        #         "lever_rate":20,
        #         "order_id":773207641127878656,
        #         "order_id_str":"773207641127878656",
        #         "client_order_id":null,
        #         "order_source":"web",
        #         "order_type":1,
        #         "created_at":1604388667146,
        #         "trade_volume":1,
        #         "trade_turnover":10,
        #         "fee":-0.022099447513812154,
        #         "trade_avg_price":0.0905,
        #         "margin_frozen":0,
        #         "profit":0,
        #         "trade":[],
        #         "canceled_at":0,
        #         "fee_asset":"ADA",
        #         "uid":"123456789",
        #         "liquidation_type":"0",
        #         "is_tpsl": 0,
        #         "real_profit": 0
        #     }
        #
        lastTradeTimestamp = self.safe_integer_2(order, 'lastActTime', 'ts')
        created = self.safe_integer(order, 'orderCreateTime')
        marketId = self.safe_string_2(order, 'contract_code', 'symbol')
        market = self.safe_market(marketId, market)
        symbol = self.safe_symbol(marketId, market)
        amount = self.safe_string_2(order, 'orderSize', 'volume')
        status = self.parse_order_status(self.safe_string_2(order, 'orderStatus', 'status'))
        id = self.safe_string_2(order, 'orderId', 'order_id')
        clientOrderId = self.safe_string_2(order, 'clientOrderId', 'client_order_id')
        price = self.safe_string_2(order, 'orderPrice', 'price')
        filled = self.safe_string(order, 'execAmt')
        typeSide = self.safe_string(order, 'type')
        feeCost = self.safe_string(order, 'fee')
        fee = None
        if feeCost is not None:
            feeCurrencyId = self.safe_string(order, 'fee_asset')
            fee = {
                'cost': feeCost,
                'currency': self.safe_currency_code(feeCurrencyId),
            }
        avgPrice = self.safe_string(order, 'trade_avg_price')
        rawTrades = self.safe_value(order, 'trade')
        typeSideParts = []
        if typeSide is not None:
            typeSideParts = typeSide.split('-')
        type = self.safe_string_lower(typeSideParts, 1)
        if type is None:
            type = self.safe_string(order, 'order_price_type')
        side = self.safe_string_lower(typeSideParts, 0)
        if side is None:
            side = self.safe_string(order, 'direction')
        cost = self.safe_string(order, 'orderValue')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': created,
            'datetime': self.iso8601(created),
            'lastTradeTimestamp': lastTradeTimestamp,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'amount': amount,
            'filled': filled,
            'remaining': None,
            'cost': cost,
            'fee': fee,
            'average': avgPrice,
            'trades': rawTrades,
        }, market)

    def parse_order_trade(self, trade, market=None):
        # spot private wrapped trade
        #
        #     {
        #         "tradePrice": "130.01",
        #         "tradeVolume": "0.0385",
        #         "tradeTime": 1648714741525,
        #         "aggressor": True,
        #         "execAmt": "0.0385",
        #         "orderSource": "spot-web",
        #         "orderSize": "0.0385",
        #         "remainAmt": "0",
        #         "tradeId": 101541578884,
        #         "symbol": "ltcusdt",
        #         "type": "sell-market",
        #         "eventType": "trade",
        #         "clientOrderId": '',
        #         "orderStatus": "filled",
        #         "orderId": 509835753860328
        #     }
        #
        market = self.safe_market(None, market)
        symbol = market['symbol']
        tradeId = self.safe_string(trade, 'tradeId')
        price = self.safe_string(trade, 'tradePrice')
        amount = self.safe_string(trade, 'tradeVolume')
        order = self.safe_string(trade, 'orderId')
        timestamp = self.safe_integer(trade, 'tradeTime')
        type = self.safe_string(trade, 'type')
        side = None
        if type is not None:
            typeParts = type.split('-')
            side = typeParts[0]
            type = typeParts[1]
        aggressor = self.safe_value(trade, 'aggressor')
        takerOrMaker = None
        if aggressor is not None:
            takerOrMaker = 'taker' if aggressor else 'maker'
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': tradeId,
            'order': order,
            'type': type,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': None,
        }, market)

    async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
        """
        :see: https://www.huobi.com/en-in/opend/newApiPages/?id=8cb7de1c-77b5-11ed-9966-0242ac110003
        :see: https://www.huobi.com/en-in/opend/newApiPages/?id=8cb7df0f-77b5-11ed-9966-0242ac110003
        :see: https://www.huobi.com/en-in/opend/newApiPages/?id=28c34a7d-77ae-11ed-9966-0242ac110003
        :see: https://www.huobi.com/en-in/opend/newApiPages/?id=5d5156b5-77b6-11ed-9966-0242ac110003
        watch all open positions. Note: huobi has one channel for each marginMode and type
        :param str[]|None symbols: list of unified market symbols
        :param dict params: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `position structure <https://docs.ccxt_zagrava.com/en/latest/manual.html#position-structure>`
        """
        await self.load_markets()
        market = None
        messageHash = ''
        if not self.is_empty(symbols):
            market = self.get_market_from_symbols(symbols)
            messageHash = '::' + ','.join(symbols)
        type = None
        subType = None
        if market is not None:
            type = market['type']
            subType = 'linear' if market['linear'] else 'inverse'
        else:
            type, params = self.handle_market_type_and_params('watchPositions', market, params)
            if type == 'spot':
                type = 'future'
            subType, params = self.handle_option_and_params(params, 'watchPositions', 'subType', subType)
        symbols = self.market_symbols(symbols)
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('watchPositions', params, 'cross')
        isLinear = (subType == 'linear')
        url = self.get_url_by_market_type(type, isLinear, True)
        messageHash = marginMode + ':positions' + messageHash
        channel = 'positions_cross.*' if (marginMode == 'cross') else 'positions.*'
        newPositions = await self.subscribe_private(channel, messageHash, type, subType, params)
        if self.newUpdates:
            return newPositions
        return self.filter_by_symbols_since_limit(self.positions[url][marginMode], symbols, since, limit, False)

    def handle_positions(self, client, message):
        #
        #    {
        #        op: 'notify',
        #        topic: 'positions_cross',
        #        ts: 1696767149650,
        #        event: 'snapshot',
        #        data: [
        #          {
        #            contract_type: 'swap',
        #            pair: 'BTC-USDT',
        #            business_type: 'swap',
        #            liquidation_price: null,
        #            symbol: 'BTC',
        #            contract_code: 'BTC-USDT',
        #            volume: 1,
        #            available: 1,
        #            frozen: 0,
        #            cost_open: 27802.2,
        #            cost_hold: 27802.2,
        #            profit_unreal: 0.0175,
        #            profit_rate: 0.000629446590557581,
        #            profit: 0.0175,
        #            margin_asset: 'USDT',
        #            position_margin: 27.8197,
        #            lever_rate: 1,
        #            direction: 'buy',
        #            last_price: 27819.7,
        #            margin_mode: 'cross',
        #            margin_account: 'USDT',
        #            trade_partition: 'USDT',
        #            position_mode: 'dual_side'
        #          },
        #        ]
        #    }
        #
        url = client.url
        topic = self.safe_string(message, 'topic', '')
        marginMode = 'cross' if (topic == 'positions_cross') else 'isolated'
        if self.positions is None:
            self.positions = {}
        clientPositions = self.safe_value(self.positions, url)
        if clientPositions is None:
            self.positions[url] = {}
        clientMarginModePositions = self.safe_value(clientPositions, marginMode)
        if clientMarginModePositions is None:
            self.positions[url][marginMode] = ArrayCacheBySymbolBySide()
        cache = self.positions[url][marginMode]
        rawPositions = self.safe_value(message, 'data', [])
        newPositions = []
        timestamp = self.safe_integer(message, 'ts')
        for i in range(0, len(rawPositions)):
            rawPosition = rawPositions[i]
            position = self.parse_position(rawPosition)
            position['timestamp'] = timestamp
            position['datetime'] = self.iso8601(timestamp)
            newPositions.append(position)
            cache.append(position)
        messageHashes = self.find_message_hashes(client, marginMode + ':positions::')
        for i in range(0, len(messageHashes)):
            messageHash = messageHashes[i]
            parts = messageHash.split('::')
            symbolsString = parts[1]
            symbols = symbolsString.split(',')
            positions = self.filter_by_array(newPositions, 'symbol', symbols, False)
            if not self.is_empty(positions):
                client.resolve(positions, messageHash)
        client.resolve(newPositions, marginMode + ':positions')

    async def watch_balance(self, params={}) -> Balances:
        """
        watch balance and get the amount of funds available for trading or funds locked in orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        type = None
        type, params = self.handle_market_type_and_params('watchBalance', None, params)
        subType = None
        subType, params = self.handle_sub_type_and_params('watchBalance', None, params, 'linear')
        isUnifiedAccount = self.safe_value_2(params, 'isUnifiedAccount', 'unified', False)
        params = self.omit(params, ['isUnifiedAccount', 'unified'])
        await self.load_markets()
        messageHash = None
        channel = None
        marginMode = None
        if type == 'spot':
            mode = self.safe_string_2(self.options, 'watchBalance', 'mode', '2')
            mode = self.safe_string(params, 'mode', mode)
            messageHash = 'accounts.update' + '#' + mode
            channel = messageHash
        else:
            symbol = self.safe_string(params, 'symbol')
            currency = self.safe_string(params, 'currency')
            market = self.market(symbol) if (symbol is not None) else None
            currencyCode = self.currency(currency) if (currency is not None) else None
            marginMode = self.safe_string(params, 'margin', 'cross')
            params = self.omit(params, ['currency', 'symbol', 'margin'])
            prefix = 'accounts'
            messageHash = prefix
            if subType == 'linear':
                if isUnifiedAccount:
                    # usdt contracts account
                    prefix = 'accounts_unify'
                    messageHash = prefix
                    channel = prefix + '.' + 'usdt'
                else:
                    # usdt contracts account
                    prefix = prefix + '_cross' if (marginMode == 'cross') else prefix
                    messageHash = prefix
                    if marginMode == 'isolated':
                        # isolated margin only allows filtering by symbol3
                        if symbol is not None:
                            messageHash += '.' + market['id']
                            channel = messageHash
                        else:
                            # subscribe to all
                            channel = prefix + '.' + '*'
                    else:
                        # cross margin
                        if currencyCode is not None:
                            channel = prefix + '.' + currencyCode['id']
                            messageHash = channel
                        else:
                            # subscribe to all
                            channel = prefix + '.' + '*'
            elif type == 'future':
                # inverse futures account
                if currencyCode is not None:
                    messageHash += '.' + currencyCode['id']
                    channel = messageHash
                else:
                    # subscribe to all
                    channel = prefix + '.' + '*'
            else:
                # inverse swaps account
                if market is not None:
                    messageHash += '.' + market['id']
                    channel = messageHash
                else:
                    # subscribe to all
                    channel = prefix + '.' + '*'
        subscriptionParams: dict = {
            'type': type,
            'subType': subType,
            'margin': marginMode,
        }
        # we are differentiating the channel from the messageHash for global subscriptions(*)
        # because huobi returns a different topic than the topic sent. Example: we send
        # "accounts.*" and "accounts" is returned so we're setting channel = "accounts.*" and
        # messageHash = "accounts" allowing handleBalance to freely resolve the topic in the message
        return await self.subscribe_private(channel, messageHash, type, subType, params, subscriptionParams)

    def handle_balance(self, client: Client, message):
        # spot
        #
        #     {
        #         "action": "push",
        #         "ch": "accounts.update#0",
        #         "data": {
        #             "currency": "btc",
        #             "accountId": 123456,
        #             "balance": "23.111",
        #             "available": "2028.699426619837209087",
        #             "changeType": "transfer",
        #             "accountType":"trade",
        #             "seqNum": "86872993928",
        #             "changeTime": 1568601800000
        #         }
        #     }
        #
        # inverse future
        #
        #     {
        #         "op":"notify",
        #         "topic":"accounts.ada",
        #         "ts":1604388667226,
        #         "event":"order.match",
        #         "data":[
        #             {
        #                 "symbol":"ADA",
        #                 "margin_balance":446.417641681222726716,
        #                 "margin_static":445.554085945257745136,
        #                 "margin_position":11.049723756906077348,
        #                 "margin_frozen":0,
        #                 "margin_available":435.367917924316649368,
        #                 "profit_real":21.627049781983019459,
        #                 "profit_unreal":0.86355573596498158,
        #                 "risk_rate":40.000796572150656768,
        #                 "liquidation_price":0.018674308027108984,
        #                 "withdraw_available":423.927036163274725677,
        #                 "lever_rate":20,
        #                 "adjust_factor":0.4
        #             }
        #         ],
        #         "uid":"123456789"
        #     }
        #
        # usdt / linear future, swap
        #
        #     {
        #         "op":"notify",
        #         "topic":"accounts.btc-usdt",  # or "accounts" for global subscriptions
        #         "ts":1603711370689,
        #         "event":"order.open",
        #         "data":[
        #             {
        #                 "margin_mode":"cross",
        #                 "margin_account":"USDT",
        #                 "margin_asset":"USDT",
        #                 "margin_balance":30.959342395,
        #                 "margin_static":30.959342395,
        #                 "margin_position":0,
        #                 "margin_frozen":10,
        #                 "profit_real":0,
        #                 "profit_unreal":0,
        #                 "withdraw_available":20.959342395,
        #                 "risk_rate":153.796711975,
        #                 "position_mode":"dual_side",
        #                 "contract_detail":[
        #                     {
        #                         "symbol":"LTC",
        #                         "contract_code":"LTC-USDT",
        #                         "margin_position":0,
        #                         "margin_frozen":0,
        #                         "margin_available":20.959342395,
        #                         "profit_unreal":0,
        #                         "liquidation_price":null,
        #                         "lever_rate":1,
        #                         "adjust_factor":0.01,
        #                         "contract_type":"swap",
        #                         "pair":"LTC-USDT",
        #                         "business_type":"swap",
        #                         "trade_partition":"USDT"
        #                     },
        #                 ],
        #                 "futures_contract_detail":[],
        #             }
        #         ]
        #     }
        #
        # inverse future
        #
        #     {
        #         "op":"notify",
        #         "topic":"accounts.ada",
        #         "ts":1604388667226,
        #         "event":"order.match",
        #         "data":[
        #             {
        #                 "symbol":"ADA",
        #                 "margin_balance":446.417641681222726716,
        #                 "margin_static":445.554085945257745136,
        #                 "margin_position":11.049723756906077348,
        #                 "margin_frozen":0,
        #                 "margin_available":435.367917924316649368,
        #                 "profit_real":21.627049781983019459,
        #                 "profit_unreal":0.86355573596498158,
        #                 "risk_rate":40.000796572150656768,
        #                 "liquidation_price":0.018674308027108984,
        #                 "withdraw_available":423.927036163274725677,
        #                 "lever_rate":20,
        #                 "adjust_factor":0.4
        #             }
        #         ],
        #         "uid":"123456789"
        #     }
        #
        channel = self.safe_string(message, 'ch')
        data = self.safe_value(message, 'data', [])
        timestamp = self.safe_integer(data, 'changeTime', self.safe_integer(message, 'ts'))
        self.balance['timestamp'] = timestamp
        self.balance['datetime'] = self.iso8601(timestamp)
        self.balance['info'] = data
        if channel is not None:
            # spot balance
            currencyId = self.safe_string(data, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(data, 'available')
            account['total'] = self.safe_string(data, 'balance')
            self.balance[code] = account
            self.balance = self.safe_balance(self.balance)
            client.resolve(self.balance, channel)
        else:
            # contract balance
            dataLength = len(data)
            if dataLength == 0:
                return
            first = self.safe_value(data, 0, {})
            topic = self.safe_string(message, 'topic')
            splitTopic = topic.split('.')
            messageHash = self.safe_string(splitTopic, 0)
            subscription = self.safe_value_2(client.subscriptions, messageHash, messageHash + '.*')
            if subscription is None:
                # if subscription not found means that we subscribed to a specific currency/symbol
                # and we use the first data entry to find it
                # Example: topic = 'accounts'
                # client.subscription hash = 'accounts.usdt'
                # we do 'accounts' + '.' + data[0]]['margin_asset'] to get it
                currencyId = self.safe_string_2(first, 'margin_asset', 'symbol')
                messageHash += '.' + currencyId.lower()
                subscription = self.safe_value(client.subscriptions, messageHash)
            type = self.safe_string(subscription, 'type')
            subType = self.safe_string(subscription, 'subType')
            if topic == 'accounts_unify':
                # {
                #     "margin_asset": "USDT",
                #     "margin_static": 10,
                #     "cross_margin_static": 10,
                #     "margin_balance": 10,
                #     "cross_profit_unreal": 0,
                #     "margin_frozen": 0,
                #     "withdraw_available": 10,
                #     "cross_risk_rate": null,
                #     "cross_swap": [],
                #     "cross_future": [],
                #     "isolated_swap": []
                # }
                marginAsset = self.safe_string(first, 'margin_asset')
                code = self.safe_currency_code(marginAsset)
                marginFrozen = self.safe_string(first, 'margin_frozen')
                unifiedAccount = self.account()
                unifiedAccount['free'] = self.safe_string(first, 'withdraw_available')
                unifiedAccount['used'] = marginFrozen
                self.balance[code] = unifiedAccount
                self.balance = self.safe_balance(self.balance)
                client.resolve(self.balance, 'accounts_unify')
            elif subType == 'linear':
                margin = self.safe_string(subscription, 'margin')
                if margin == 'cross':
                    fieldName = 'futures_contract_detail' if (type == 'future') else 'contract_detail'
                    balances = self.safe_value(first, fieldName, [])
                    balancesLength = len(balances)
                    if balancesLength > 0:
                        for i in range(0, len(balances)):
                            balance = balances[i]
                            marketId = self.safe_string_2(balance, 'contract_code', 'margin_account')
                            market = self.safe_market(marketId)
                            currencyId = self.safe_string(balance, 'margin_asset')
                            currency = self.safe_currency(currencyId)
                            code = self.safe_string(market, 'settle', currency['code'])
                            # the exchange outputs positions for delisted markets
                            # https://www.huobi.com/support/en-us/detail/74882968522337
                            # we skip it if the market was delisted
                            if code is not None:
                                account = self.account()
                                account['free'] = self.safe_string_2(balance, 'margin_balance', 'margin_available')
                                account['used'] = self.safe_string(balance, 'margin_frozen')
                                accountsByCode: dict = {}
                                accountsByCode[code] = account
                                symbol = market['symbol']
                                self.balance[symbol] = self.safe_balance(accountsByCode)
                else:
                    # isolated margin
                    for i in range(0, len(data)):
                        isolatedBalance = data[i]
                        account = self.account()
                        account['free'] = self.safe_string(isolatedBalance, 'margin_balance', 'margin_available')
                        account['used'] = self.safe_string(isolatedBalance, 'margin_frozen')
                        currencyId = self.safe_string_2(isolatedBalance, 'margin_asset', 'symbol')
                        code = self.safe_currency_code(currencyId)
                        self.balance[code] = account
                        self.balance = self.safe_balance(self.balance)
            else:
                # inverse branch
                for i in range(0, len(data)):
                    balance = data[i]
                    currencyId = self.safe_string(balance, 'symbol')
                    code = self.safe_currency_code(currencyId)
                    account = self.account()
                    account['free'] = self.safe_string(balance, 'margin_available')
                    account['used'] = self.safe_string(balance, 'margin_frozen')
                    self.balance[code] = account
                    self.balance = self.safe_balance(self.balance)
            client.resolve(self.balance, messageHash)

    def handle_subscription_status(self, client: Client, message):
        #
        #     {
        #         "id": 1583414227,
        #         "status": "ok",
        #         "subbed": "market.btcusdt.mbp.150",
        #         "ts": 1583414229143
        #     }
        #
        id = self.safe_string(message, 'id')
        subscriptionsById = self.index_by(client.subscriptions, 'id')
        subscription = self.safe_value(subscriptionsById, id)
        if subscription is not None:
            method = self.safe_value(subscription, 'method')
            if method is not None:
                method(client, message, subscription)
                return
            # clean up
            if id in client.subscriptions:
                del client.subscriptions[id]

    def handle_system_status(self, client: Client, message):
        #
        # todo: answer the question whether handleSystemStatus should be renamed
        # and unified for any usage pattern that
        # involves system status and maintenance updates
        #
        #     {
        #         "id": "1578090234088",  # connectId
        #         "type": "welcome",
        #     }
        #
        return message

    def handle_subject(self, client: Client, message):
        # spot
        #     {
        #         "ch": "market.btcusdt.mbp.150",
        #         "ts": 1583472025885,
        #         "tick": {
        #             "seqNum": 104998984994,
        #             "prevSeqNum": 104998984977,
        #             "bids": [
        #                 [9058.27, 0],
        #                 [9058.43, 0],
        #                 [9058.99, 0],
        #             ],
        #             "asks": [
        #                 [9084.27, 0.2],
        #                 [9085.69, 0],
        #                 [9085.81, 0],
        #             ]
        #         }
        #     }
        # non spot
        #
        #     {
        #         "ch":"market.BTC220218.depth.size_150.high_freq",
        #         "tick":{
        #             "asks":[],
        #             "bids":[
        #                 [43445.74,1],
        #                 [43444.48,0],
        #                 [40593.92,9]
        #             ],
        #             "ch":"market.BTC220218.depth.size_150.high_freq",
        #             "event":"update",
        #             "id":152727500274,
        #             "mrid":152727500274,
        #             "ts":1645023376098,
        #             "version":37536690
        #         },
        #         "ts":1645023376098
        #     }
        #
        # spot private trade
        #
        #     {
        #         "action":"push",
        #         "ch":"trade.clearing#ltcusdt#1",
        #         "data":{
        #             "eventType":"trade",
        #             "symbol":"ltcusdt",
        #             # ...
        #         },
        #     }
        #
        # spot order
        #
        #     {
        #         "action":"push",
        #         "ch":"orders#btcusdt",
        #         "data": {
        #             "orderSide":"buy",
        #             "lastActTime":1583853365586,
        #             "clientOrderId":"abc123",
        #             "orderStatus":"rejected",
        #             "symbol":"btcusdt",
        #             "eventType":"trigger",
        #             "errCode": 2002,
        #             "errMessage":"invalid.client.order.id(NT)"
        #         }
        #     }
        #
        # contract order
        #
        #     {
        #         "op":"notify",
        #         "topic":"orders.ada",
        #         "ts":1604388667226,
        #         # ?
        #     }
        #
        ch = self.safe_value(message, 'ch', '')
        parts = ch.split('.')
        type = self.safe_string(parts, 0)
        if type == 'market':
            methodName = self.safe_string(parts, 2)
            methods: dict = {
                'depth': self.handle_order_book,
                'mbp': self.handle_order_book,
                'detail': self.handle_ticker,
                'bbo': self.handle_ticker,
                'ticker': self.handle_ticker,
                'trade': self.handle_trades,
                'kline': self.handle_ohlcv,
            }
            method = self.safe_value(methods, methodName)
            if method is not None:
                method(client, message)
                return
        # private spot subjects
        privateParts = ch.split('#')
        privateType = self.safe_string(privateParts, 0, '')
        if privateType == 'trade.clearing':
            self.handle_my_trade(client, message)
            return
        if privateType.find('accounts.update') >= 0:
            self.handle_balance(client, message)
            return
        if privateType == 'orders':
            self.handle_order(client, message)
            return
        # private contract subjects
        op = self.safe_string(message, 'op')
        if op == 'notify':
            topic = self.safe_string(message, 'topic', '')
            if topic.find('orders') >= 0:
                self.handle_order(client, message)
            if topic.find('account') >= 0:
                self.handle_balance(client, message)
            if topic.find('positions') >= 0:
                self.handle_positions(client, message)

    async def pong(self, client, message):
        #
        #     {ping: 1583491673714}
        #     {action: "ping", data: {ts: 1645108204665}}
        #     {op: "ping", ts: "1645202800015"}
        #
        try:
            ping = self.safe_integer(message, 'ping')
            if ping is not None:
                await client.send({'pong': ping})
                return
            action = self.safe_string(message, 'action')
            if action == 'ping':
                data = self.safe_value(message, 'data')
                pingTs = self.safe_integer(data, 'ts')
                await client.send({'action': 'pong', 'data': {'ts': pingTs}})
                return
            op = self.safe_string(message, 'op')
            if op == 'ping':
                pingTs = self.safe_integer(message, 'ts')
                await client.send({'op': 'pong', 'ts': pingTs})
        except Exception as e:
            error = NetworkError(self.id + ' pong failed ' + self.json(e))
            client.reset(error)

    def handle_ping(self, client: Client, message):
        self.spawn(self.pong, client, message)

    def handle_authenticate(self, client: Client, message):
        #
        # spot
        #
        #     {
        #         "action": "req",
        #         "code": 200,
        #         "ch": "auth",
        #         "data": {}
        #     }
        #
        # non spot
        #
        #    {
        #        "op": "auth",
        #        "type": "api",
        #        "err-code": 0,
        #        "ts": 1645200307319,
        #        "data": {"user-id": "35930539"}
        #    }
        #
        promise = client.futures['auth']
        promise.resolve(message)

    def handle_error_message(self, client: Client, message):
        #
        #     {
        #         "action": "sub",
        #         "code": 2002,
        #         "ch": "accounts.update#2",
        #         "message": "invalid.auth.state"
        #      }
        #
        #     {
        #         "ts": 1586323747018,
        #         "status": "error",
        #         'err-code': "bad-request",
        #         'err-msg': "invalid mbp.150.symbol linkusdt",
        #         "id": "2"
        #     }
        #
        #     {
        #         "op": "sub",
        #         "cid": "1",
        #         "topic": "accounts_unify.USDT",
        #         "err-code": 4007,
        #         'err-msg': "Non - single account user is not available, please check through the cross and isolated account asset interface",
        #         "ts": 1698419490189
        #     }
        #     {
        #         "action":"req",
        #         "code":2002,
        #         "ch":"auth",
        #         "message":"auth.fail"
        #     }
        #
        status = self.safe_string(message, 'status')
        if status == 'error':
            id = self.safe_string(message, 'id')
            subscriptionsById = self.index_by(client.subscriptions, 'id')
            subscription = self.safe_value(subscriptionsById, id)
            if subscription is not None:
                errorCode = self.safe_string(message, 'err-code')
                try:
                    self.throw_exactly_matched_exception(self.exceptions['ws']['exact'], errorCode, self.json(message))
                    raise ExchangeError(self.json(message))
                except Exception as e:
                    messageHash = self.safe_string(subscription, 'messageHash')
                    client.reject(e, messageHash)
                    client.reject(e, id)
                    if id in client.subscriptions:
                        del client.subscriptions[id]
            return False
        code = self.safe_string_2(message, 'code', 'err-code')
        if code is not None and ((code != '200') and (code != '0')):
            feedback = self.id + ' ' + self.json(message)
            try:
                self.throw_exactly_matched_exception(self.exceptions['ws']['exact'], code, feedback)
                raise ExchangeError(feedback)
            except Exception as e:
                if isinstance(e, AuthenticationError):
                    client.reject(e, 'auth')
                    method = 'auth'
                    if method in client.subscriptions:
                        del client.subscriptions[method]
                    return False
                else:
                    client.reject(e)
        return message

    def handle_message(self, client: Client, message):
        if self.handle_error_message(client, message):
            #
            #     {"id":1583414227,"status":"ok","subbed":"market.btcusdt.mbp.150","ts":1583414229143}
            #
            # first ping format
            #
            #    {"ping": 1645106821667}
            #
            # second ping format
            #
            #    {"action":"ping","data":{"ts":1645106821667}}
            #
            # third pong format
            #
            #
            # auth spot
            #
            #     {
            #         "action": "req",
            #         "code": 200,
            #         "ch": "auth",
            #         "data": {}
            #     }
            #
            # auth non spot
            #
            #    {
            #        "op": "auth",
            #        "type": "api",
            #        "err-code": 0,
            #        "ts": 1645200307319,
            #        "data": {"user-id": "35930539"}
            #    }
            #
            # trade
            #
            #     {
            #         "action":"push",
            #         "ch":"trade.clearing#ltcusdt#1",
            #         "data":{
            #             "eventType":"trade",
            #             # ?
            #         }
            #     }
            #
            if 'id' in message:
                self.handle_subscription_status(client, message)
                return
            if 'action' in message:
                action = self.safe_string(message, 'action')
                if action == 'ping':
                    self.handle_ping(client, message)
                    return
                if action == 'sub':
                    self.handle_subscription_status(client, message)
                    return
            if 'ch' in message:
                if message['ch'] == 'auth':
                    self.handle_authenticate(client, message)
                    return
                else:
                    # route by channel aka topic aka subject
                    self.handle_subject(client, message)
                    return
            if 'op' in message:
                op = self.safe_string(message, 'op')
                if op == 'ping':
                    self.handle_ping(client, message)
                    return
                if op == 'auth':
                    self.handle_authenticate(client, message)
                    return
                if op == 'sub':
                    self.handle_subscription_status(client, message)
                    return
                if op == 'notify':
                    self.handle_subject(client, message)
                    return
            if 'ping' in message:
                self.handle_ping(client, message)

    def handle_my_trade(self, client: Client, message, extendParams={}):
        #
        # spot
        #
        #     {
        #         "action":"push",
        #         "ch":"trade.clearing#ltcusdt#1",
        #         "data":{
        #             "eventType":"trade",
        #             "symbol":"ltcusdt",
        #             "orderId":"478862728954426",
        #             "orderSide":"buy",
        #             "orderType":"buy-market",
        #             "accountId":44234548,
        #             "source":"spot-web",
        #             "orderValue":"5.01724137",
        #             "orderCreateTime":1645124660365,
        #             "orderStatus":"filled",
        #             "feeCurrency":"ltc",
        #             "tradePrice":"118.89",
        #             "tradeVolume":"0.042200701236437042",
        #             "aggressor":true,
        #             "tradeId":101539740584,
        #             "tradeTime":1645124660368,
        #             "transactFee":"0.000041778694224073",
        #             "feeDeduct":"0",
        #             "feeDeductType":""
        #         }
        #     }
        #
        # contract
        #
        #     {
        #         "symbol": "ADA/USDT:USDT"
        #         "ch": "orders_cross.ada-usdt"
        #         "trades": [
        #             {
        #                 "trade_fee":-0.022099447513812154,
        #                 "fee_asset":"ADA",
        #                 "trade_id":113913755890,
        #                 "id":"113913755890-773207641127878656-1",
        #                 "trade_volume":1,
        #                 "trade_price":0.0905,
        #                 "trade_turnover":10,
        #                 "created_at":1604388667194,
        #                 "profit":0,
        #                 "real_profit": 0,
        #                 "role":"maker"
        #             }
        #         ],
        #     }
        #
        if self.myTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.myTrades = ArrayCacheBySymbolById(limit)
        cachedTrades = self.myTrades
        messageHash = self.safe_string(message, 'ch')
        if messageHash is not None:
            data = self.safe_value(message, 'data')
            if data is not None:
                parsed = self.parse_ws_trade(data)
                symbol = self.safe_string(parsed, 'symbol')
                if symbol is not None:
                    cachedTrades.append(parsed)
                    client.resolve(self.myTrades, messageHash)
            else:
                # self trades object is artificially created
                # in handleOrder
                rawTrades = self.safe_value(message, 'trades', [])
                marketId = self.safe_value(message, 'symbol')
                market = self.market(marketId)
                for i in range(0, len(rawTrades)):
                    trade = rawTrades[i]
                    parsedTrade = self.parse_trade(trade, market)
                    # add extra params(side, type, ...) coming from the order
                    parsedTrade = self.extend(parsedTrade, extendParams)
                    cachedTrades.append(parsedTrade)
                # messageHash here is the orders one, so
                # we have to recreate the trades messageHash = orderMessageHash + ':' + 'trade'
                tradesHash = messageHash + ':' + 'trade'
                client.resolve(self.myTrades, tradesHash)
                # when we make an global order sub we have to send the channel like self
                # ch = orders_cross.* and we store messageHash = 'orders_cross'
                # however it is returned with the specific order update symbol: ch = orders_cross.btc-usd
                # since self is a global sub, our messageHash does not specify any symbol(ex: orders_cross:trade)
                # so we must remove it
                genericOrderHash = messageHash.replace('.' + market['lowercaseId'], '')
                lowerCaseBaseId = self.safe_string_lower(market, 'baseId')
                genericOrderHash = genericOrderHash.replace('.' + lowerCaseBaseId, '')
                genericTradesHash = genericOrderHash + ':' + 'trade'
                client.resolve(self.myTrades, genericTradesHash)

    def parse_ws_trade(self, trade, market=None):
        # spot private
        #
        #     {
        #         "eventType":"trade",
        #         "symbol":"ltcusdt",
        #         "orderId":"478862728954426",
        #         "orderSide":"buy",
        #         "orderType":"buy-market",
        #         "accountId":44234548,
        #         "source":"spot-web",
        #         "orderValue":"5.01724137",
        #         "orderCreateTime":1645124660365,
        #         "orderStatus":"filled",
        #         "feeCurrency":"ltc",
        #         "tradePrice":"118.89",
        #         "tradeVolume":"0.042200701236437042",
        #         "aggressor":true,
        #         "tradeId":101539740584,
        #         "tradeTime":1645124660368,
        #         "transactFee":"0.000041778694224073",
        #         "feeDeduct":"0",
        #         "feeDeductType":""
        #     }
        #
        symbol = self.safe_symbol(self.safe_string(trade, 'symbol'))
        side = self.safe_string_2(trade, 'side', 'orderSide')
        tradeId = self.safe_string(trade, 'tradeId')
        price = self.safe_string(trade, 'tradePrice')
        amount = self.safe_string(trade, 'tradeVolume')
        order = self.safe_string(trade, 'orderId')
        timestamp = self.safe_integer(trade, 'tradeTime')
        market = self.market(symbol)
        orderType = self.safe_string(trade, 'orderType')
        aggressor = self.safe_value(trade, 'aggressor')
        takerOrMaker = None
        if aggressor is not None:
            takerOrMaker = 'taker' if aggressor else 'maker'
        type = None
        orderTypeParts = []
        if orderType is not None:
            orderTypeParts = orderType.split('-')
            type = self.safe_string(orderTypeParts, 1)
        fee = None
        feeCurrency = self.safe_currency_code(self.safe_string(trade, 'feeCurrency'))
        if feeCurrency is not None:
            fee = {
                'cost': self.safe_string(trade, 'transactFee'),
                'currency': feeCurrency,
            }
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': tradeId,
            'order': order,
            'type': type,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': fee,
        }, market)

    def get_url_by_market_type(self, type, isLinear=True, isPrivate=False, isFeed=False):
        api = self.safe_string(self.options, 'api', 'api')
        hostname: dict = {'hostname': self.hostname}
        hostnameURL = None
        url = None
        if type == 'spot':
            if isPrivate:
                hostnameURL = self.urls['api']['ws'][api]['spot']['private']
            else:
                if isFeed:
                    hostnameURL = self.urls['api']['ws'][api]['spot']['feed']
                else:
                    hostnameURL = self.urls['api']['ws'][api]['spot']['public']
            url = self.implode_params(hostnameURL, hostname)
        else:
            baseUrl = self.urls['api']['ws'][api][type]
            subTypeUrl = baseUrl['linear'] if isLinear else baseUrl['inverse']
            url = subTypeUrl['private'] if isPrivate else subTypeUrl['public']
        return url

    async def subscribe_public(self, url, symbol, messageHash, method=None, params={}):
        requestId = self.request_id()
        request: dict = {
            'sub': messageHash,
            'id': requestId,
        }
        subscription: dict = {
            'id': requestId,
            'messageHash': messageHash,
            'symbol': symbol,
            'params': params,
        }
        if method is not None:
            subscription['method'] = method
        return await self.watch(url, messageHash, self.extend(request, params), messageHash, subscription)

    async def subscribe_private(self, channel, messageHash, type, subtype, params={}, subscriptionParams={}):
        requestId = self.request_id()
        subscription: dict = {
            'id': requestId,
            'messageHash': messageHash,
            'params': params,
        }
        extendedSubsription = self.extend(subscription, subscriptionParams)
        request = None
        if type == 'spot':
            request = {
                'action': 'sub',
                'ch': channel,
            }
        else:
            request = {
                'op': 'sub',
                'topic': channel,
                'cid': requestId,
            }
        isLinear = subtype == 'linear'
        url = self.get_url_by_market_type(type, isLinear, True)
        hostname = self.urls['hostnames']['spot'] if (type == 'spot') else self.urls['hostnames']['contract']
        authParams: dict = {
            'type': type,
            'url': url,
            'hostname': hostname,
        }
        await self.authenticate(authParams)
        return await self.watch(url, messageHash, self.extend(request, params), channel, extendedSubsription)

    async def authenticate(self, params={}):
        url = self.safe_string(params, 'url')
        hostname = self.safe_string(params, 'hostname')
        type = self.safe_string(params, 'type')
        if url is None or hostname is None or type is None:
            raise ArgumentsRequired(self.id + ' authenticate requires a url, hostname and type argument')
        self.check_required_credentials()
        messageHash = 'auth'
        relativePath = url.replace('wss://' + hostname, '')
        client = self.client(url)
        future = client.future(messageHash)
        authenticated = self.safe_value(client.subscriptions, messageHash)
        if authenticated is None:
            timestamp = self.ymdhms(self.milliseconds(), 'T')
            signatureParams = None
            if type == 'spot':
                signatureParams = {
                    'accessKey': self.apiKey,
                    'signatureMethod': 'HmacSHA256',
                    'signatureVersion': '2.1',
                    'timestamp': timestamp,
                }
            else:
                signatureParams = {
                    'AccessKeyId': self.apiKey,
                    'SignatureMethod': 'HmacSHA256',
                    'SignatureVersion': '2',
                    'Timestamp': timestamp,
                }
            signatureParams = self.keysort(signatureParams)
            auth = self.urlencode(signatureParams)
            payload = "\n".join(['GET', hostname, relativePath, auth])  # eslint-disable-line quotes
            signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64')
            request = None
            if type == 'spot':
                newParams: dict = {
                    'authType': 'api',
                    'accessKey': self.apiKey,
                    'signatureMethod': 'HmacSHA256',
                    'signatureVersion': '2.1',
                    'timestamp': timestamp,
                    'signature': signature,
                }
                request = {
                    'params': newParams,
                    'action': 'req',
                    'ch': 'auth',
                }
            else:
                request = {
                    'op': 'auth',
                    'type': 'api',
                    'AccessKeyId': self.apiKey,
                    'SignatureMethod': 'HmacSHA256',
                    'SignatureVersion': '2',
                    'Timestamp': timestamp,
                    'Signature': signature,
                }
            requestId = self.request_id()
            subscription: dict = {
                'id': requestId,
                'messageHash': messageHash,
                'params': params,
            }
            self.watch(url, messageHash, request, messageHash, subscription)
        return await future
