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

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

import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
import hashlib
from ccxt.base.types import Any, Balances, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import AuthenticationError
from ccxt.base.precise import Precise


class phemex(ccxt.async_support.phemex):

    def describe(self) -> Any:
        return self.deep_extend(super(phemex, self).describe(), {
            'has': {
                'ws': True,
                'watchTicker': True,
                'watchTickers': True,
                'watchTrades': True,
                'watchMyTrades': True,
                'watchOrders': True,
                'watchOrderBook': True,
                'watchOHLCV': True,
                'watchPositions': None,  # TODO
                # mutli-endpoints are not supported: https://github.com/ccxt/ccxt/pull/21490
                'watchOrderBookForSymbols': False,
                'watchTradesForSymbols': False,
                'watchOHLCVForSymbols': False,
                'watchBalance': True,
            },
            'urls': {
                'test': {
                    'ws': 'wss://testnet-api.phemex.com/ws',
                },
                'api': {
                    'ws': 'wss://ws.phemex.com',
                },
            },
            'options': {
                'tradesLimit': 1000,
                'OHLCVLimit': 1000,
            },
            'streaming': {
                'keepAlive': 9000,
            },
        })

    def from_en(self, en, scale):
        if en is None:
            return None
        precise = Precise(en)
        precise.decimals = self.sum(precise.decimals, scale)
        precise.reduce()
        return str(precise)

    def from_ep(self, ep, market=None):
        if (ep is None) or (market is None):
            return ep
        return self.from_en(ep, self.safe_integer(market, 'priceScale'))

    def from_ev(self, ev, market=None):
        if (ev is None) or (market is None):
            return ev
        return self.from_en(ev, self.safe_integer(market, 'valueScale'))

    def from_er(self, er, market=None):
        if (er is None) or (market is None):
            return er
        return self.from_en(er, self.safe_integer(market, 'ratioScale'))

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

    def parse_swap_ticker(self, ticker, market=None):
        #
        #     {
        #         "close": 442800,
        #         "fundingRate": 10000,
        #         "high": 445400,
        #         "indexPrice": 442621,
        #         "low": 428400,
        #         "markPrice": 442659,
        #         "open": 432200,
        #         "openInterest": 744183,
        #         "predFundingRate": 10000,
        #         "symbol": "LTCUSD",
        #         "turnover": 8133238294,
        #         "volume": 934292
        #     }
        #
        marketId = self.safe_string(ticker, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        timestamp = self.safe_integer_product(ticker, 'timestamp', 0.000001)
        lastString = self.from_ep(self.safe_string(ticker, 'close'), market)
        last = self.parse_number(lastString)
        quoteVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 'turnover'), market))
        baseVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 'volume'), market))
        change = None
        percentage = None
        average = None
        openString = self.omit_zero(self.from_ep(self.safe_string(ticker, 'open'), market))
        open = self.parse_number(openString)
        if (openString is not None) and (lastString is not None):
            change = self.parse_number(Precise.string_sub(lastString, openString))
            average = self.parse_number(Precise.string_div(Precise.string_add(lastString, openString), '2'))
            percentage = self.parse_number(Precise.string_mul(Precise.string_sub(Precise.string_div(lastString, openString), '1'), '100'))
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.parse_number(self.from_ep(self.safe_string(ticker, 'high'), market)),
            'low': self.parse_number(self.from_ep(self.safe_string(ticker, 'low'), market)),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,  # previous day close
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'markPrice': self.parse_number(self.from_ep(self.safe_string(ticker, 'markPrice'), market)),
            'indexPrice': self.parse_number(self.from_ep(self.safe_string(ticker, 'indexPrice'), market)),
            'info': ticker,
        })

    def parse_perpetual_ticker(self, ticker, market=None):
        #
        #    [
        #        "STXUSDT",
        #        "0.64649",
        #        "0.8628",
        #        "0.61215",
        #        "0.71737",
        #        "4519387",
        #        "3210827.98166",
        #        "697635",
        #        "0.71720205",
        #        "0.71720205",
        #        "0.0001",
        #        "0.0001",
        #    ]
        #
        marketId = self.safe_string(ticker, 0)
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        lastString = self.from_ep(self.safe_string(ticker, 4), market)
        last = self.parse_number(lastString)
        quoteVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 6), market))
        baseVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 5), market))
        change = None
        percentage = None
        average = None
        openString = self.omit_zero(self.from_ep(self.safe_string(ticker, 1), market))
        open = self.parse_number(openString)
        if (openString is not None) and (lastString is not None):
            change = self.parse_number(Precise.string_sub(lastString, openString))
            average = self.parse_number(Precise.string_div(Precise.string_add(lastString, openString), '2'))
            percentage = self.parse_number(Precise.string_mul(Precise.string_sub(Precise.string_div(lastString, openString), '1'), '100'))
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': None,
            'datetime': None,
            'high': self.parse_number(self.from_ep(self.safe_string(ticker, 2), market)),
            'low': self.parse_number(self.from_ep(self.safe_string(ticker, 3), market)),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,  # previous day close
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        })

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "spot_market24h": {
        #             "askEp": 958148000000,
        #             "bidEp": 957884000000,
        #             "highEp": 962000000000,
        #             "lastEp": 958220000000,
        #             "lowEp": 928049000000,
        #             "openEp": 935597000000,
        #             "symbol": "sBTCUSDT",
        #             "turnoverEv": 146074214388978,
        #             "volumeEv": 15492228900
        #         },
        #         "timestamp": 1592847265888272100
        #     }
        #
        # swap
        #
        #     {
        #         "market24h": {
        #             "close": 442800,
        #             "fundingRate": 10000,
        #             "high": 445400,
        #             "indexPrice": 442621,
        #             "low": 428400,
        #             "markPrice": 442659,
        #             "open": 432200,
        #             "openInterest": 744183,
        #             "predFundingRate": 10000,
        #             "symbol": "LTCUSD",
        #             "turnover": 8133238294,
        #             "volume": 934292
        #         },
        #         "timestamp": 1592845585373374500
        #     }
        #
        # perpetual
        #
        #    {
        #        "data": [
        #            [
        #                "STXUSDT",
        #                "0.64649",
        #                "0.8628",
        #                "0.61215",
        #                "0.71737",
        #                "4519387",
        #                "3210827.98166",
        #                "697635",
        #                "0.71720205",
        #                "0.71720205",
        #                "0.0001",
        #                "0.0001",
        #            ],
        #            ...
        #        ],
        #        "fields": [
        #            "symbol",
        #            "openRp",
        #            "highRp",
        #            "lowRp",
        #            "lastRp",
        #            "volumeRq",
        #            "turnoverRv",
        #            "openInterestRv",
        #            "indexRp",
        #            "markRp",
        #            "fundingRateRr",
        #            "predFundingRateRr",
        #        ],
        #        "method": "perp_market24h_pack_p.update",
        #        "timestamp": "1677094918686806209",
        #        "type": "snapshot",
        #    }
        #
        tickers = []
        if 'market24h' in message:
            ticker = self.safe_value(message, 'market24h')
            tickers.append(self.parse_swap_ticker(ticker))
        elif 'spot_market24h' in message:
            ticker = self.safe_value(message, 'spot_market24h')
            tickers.append(self.parse_ticker(ticker))
        elif 'data' in message:
            data = self.safe_value(message, 'data', [])
            for i in range(0, len(data)):
                tickers.append(self.parse_perpetual_ticker(data[i]))
        for i in range(0, len(tickers)):
            ticker = tickers[i]
            symbol = ticker['symbol']
            messageHash = 'ticker:' + symbol
            timestamp = self.safe_integer_product(message, 'timestamp', 0.000001)
            ticker['timestamp'] = timestamp
            ticker['datetime'] = self.iso8601(timestamp)
            self.tickers[symbol] = ticker
            client.resolve(ticker, messageHash)

    async def watch_balance(self, params={}) -> Balances:
        """

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-account-order-position-aop
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-account-order-position-aop
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-wallet-order-messages

        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
        :param str [params.settle]: set to USDT to use hedged perpetual api
        :returns dict: a `balance structure <https://docs.ccxt.com/?id=balance-structure>`
        """
        await self.load_markets()
        type = None
        type, params = self.handle_market_type_and_params('watchBalance', None, params)
        usePerpetualApi = self.safe_string(params, 'settle') == 'USDT'
        messageHash = ':balance'
        messageHash = 'perpetual' + messageHash if usePerpetualApi else type + messageHash
        return await self.subscribe_private(type, messageHash, params)

    def handle_balance(self, type, client, message):
        # spot
        #    [
        #       {
        #           "balanceEv": 0,
        #           "currency": "BTC",
        #           "lastUpdateTimeNs": "1650442638722099092",
        #           "lockedTradingBalanceEv": 0,
        #           "lockedWithdrawEv": 0,
        #           "userID": 2647224
        #         },
        #         {
        #           "balanceEv": 1154232337,
        #           "currency": "USDT",
        #           "lastUpdateTimeNs": "1650442617610017597",
        #           "lockedTradingBalanceEv": 0,
        #           "lockedWithdrawEv": 0,
        #           "userID": 2647224
        #         }
        #    ]
        # swap
        #    [
        #        {
        #            "accountBalanceEv": 0,
        #            "accountID": 26472240001,
        #            "bonusBalanceEv": 0,
        #            "currency": "BTC",
        #            "totalUsedBalanceEv": 0,
        #            "userID": 2647224
        #        }
        #    ]
        # perpetual
        #    [
        #        {
        #            "accountBalanceRv": "1508.452588802237",
        #            "accountID": 9328670003,
        #            "bonusBalanceRv": "0",
        #            "currency": "USDT",
        #            "totalUsedBalanceRv": "343.132599666883",
        #            "userID": 932867
        #        }
        #    ]
        #
        self.balance['info'] = message
        for i in range(0, len(message)):
            balance = message[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            currency = self.safe_value(self.currencies, code, {})
            scale = self.safe_integer(currency, 'valueScale', 8)
            account = self.account()
            used = self.safe_string(balance, 'totalUsedBalanceRv')
            if used is None:
                usedEv = self.safe_string(balance, 'totalUsedBalanceEv')
                if usedEv is None:
                    lockedTradingBalanceEv = self.safe_string(balance, 'lockedTradingBalanceEv')
                    lockedWithdrawEv = self.safe_string_2(balance, 'lockedWithdrawEv', 'lockedWithdrawRv')
                    usedEv = Precise.string_add(lockedTradingBalanceEv, lockedWithdrawEv)
                used = self.from_en(usedEv, scale)
            total = self.safe_string(balance, 'accountBalanceRv')
            if total is None:
                totalEv = self.safe_string_2(balance, 'accountBalanceEv', 'balanceEv')
                total = self.from_en(totalEv, scale)
            account['used'] = used
            account['total'] = total
            self.balance[code] = account
            self.balance = self.safe_balance(self.balance)
        messageHash = type + ':balance'
        client.resolve(self.balance, messageHash)

    def handle_trades(self, client: Client, message):
        #
        #     {
        #         "sequence": 1795484727,
        #         "symbol": "sBTCUSDT",
        #         "trades": [
        #             [1592891002064516600, "Buy", 964020000000, 1431000],
        #             [1592890978987934500, "Sell", 963704000000, 1401800],
        #             [1592890972918701800, "Buy", 963938000000, 2018600],
        #         ],
        #         "type": "snapshot"
        #     }
        #  perpetual
        #     {
        #         "sequence": 1230197759,
        #         "symbol": "BTCUSDT",
        #         "trades_p": [
        #             [
        #                 1677094244729433000,
        #                 "Buy",
        #                 "23800.4",
        #                 "2.455",
        #             ],
        #         ],
        #         "type": "snapshot",
        #     }
        #
        name = 'trade'
        marketId = self.safe_string(message, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        messageHash = name + ':' + symbol
        stored = self.safe_value(self.trades, symbol)
        if stored is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            stored = ArrayCache(limit)
            self.trades[symbol] = stored
        trades = self.safe_value_2(message, 'trades', 'trades_p', [])
        parsed = self.parse_trades(trades, market)
        for i in range(0, len(parsed)):
            stored.append(parsed[i])
        client.resolve(stored, messageHash)

    def handle_ohlcv(self, client: Client, message):
        #
        #     {
        #         "kline": [
        #             [1592905200, 60, 960688000000, 960709000000, 960709000000, 960400000000, 960400000000, 848100, 8146756046],
        #             [1592905140, 60, 960718000000, 960716000000, 960717000000, 960560000000, 960688000000, 4284900, 41163743512],
        #             [1592905080, 60, 960513000000, 960684000000, 960718000000, 960684000000, 960718000000, 4880500, 46887494349],
        #         ],
        #         "sequence": 1804401474,
        #         "symbol": "sBTCUSDT",
        #         "type": "snapshot"
        #     }
        # perpetual
        #     {
        #         "kline_p": [
        #             [
        #                 1677094560,
        #                 60,
        #                 "23746.2",
        #                 "23746.1",
        #                 "23757.6",
        #                 "23736.9",
        #                 "23754.8",
        #                 "34.273",
        #                 "813910.208",
        #             ],
        #         ],
        #         "sequence": 1230786017,
        #         "symbol": "BTCUSDT",
        #         "type": "incremental",
        #     }
        #
        marketId = self.safe_string(message, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        candles = self.safe_value_2(message, 'kline', 'kline_p', [])
        first = self.safe_value(candles, 0, [])
        interval = self.safe_string(first, 1)
        timeframe = self.find_timeframe(interval)
        if timeframe is not None:
            messageHash = 'kline:' + timeframe + ':' + symbol
            ohlcvs = self.parse_ohlcvs(candles, market)
            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
            for i in range(0, len(ohlcvs)):
                candle = ohlcvs[i]
                stored.append(candle)
            client.resolve(stored, messageHash)

    async def watch_ticker(self, symbol: str, params={}) -> Ticker:
        """

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-24-hours-ticker
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-24-hours-ticker
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-24-hours-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.com/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        isSwap = market['swap']
        settleIsUSDT = market['settle'] == 'USDT'
        name = 'spot_market24h'
        if isSwap:
            name = 'perp_market24h_pack_p' if settleIsUSDT else 'market24h'
        url = self.urls['api']['ws']
        requestId = self.request_id()
        subscriptionHash = name + '.subscribe'
        messageHash = 'ticker:' + symbol
        subscribe: dict = {
            'method': subscriptionHash,
            'id': requestId,
            'params': [],
        }
        request = self.deep_extend(subscribe, params)
        return await self.watch(url, messageHash, request, subscriptionHash)

    async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-24-hours-ticker
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-24-hours-ticker
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-24-hours-ticker

        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
        :param str[] [symbols]: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.channel]: the channel to subscribe to, tickers by default. Can be tickers, sprd-tickers, index-tickers, block-tickers
        :returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols, None, False)
        first = symbols[0]
        market = self.market(first)
        isSwap = market['swap']
        settleIsUSDT = market['settle'] == 'USDT'
        name = 'spot_market24h'
        if isSwap:
            name = 'perp_market24h_pack_p' if settleIsUSDT else 'market24h'
        url = self.urls['api']['ws']
        requestId = self.request_id()
        subscriptionHash = name + '.subscribe'
        messageHashes = []
        for i in range(0, len(symbols)):
            messageHashes.append('ticker:' + symbols[i])
        subscribe: dict = {
            'method': subscriptionHash,
            'id': requestId,
            'params': [],
        }
        request = self.deep_extend(subscribe, params)
        ticker = await self.watch_multiple(url, messageHashes, request, messageHashes)
        if self.newUpdates:
            result: dict = {}
            result[ticker['symbol']] = ticker
            return result
        return self.filter_by_array(self.tickers, 'symbol', symbols)

    async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-trade
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-trade
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-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.com/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        url = self.urls['api']['ws']
        requestId = self.request_id()
        isSwap = market['swap']
        settleIsUSDT = market['settle'] == 'USDT'
        name = 'trade_p' if (isSwap and settleIsUSDT) else 'trade'
        messageHash = 'trade:' + symbol
        method = name + '.subscribe'
        subscribe: dict = {
            'method': method,
            'id': requestId,
            'params': [
                market['id'],
            ],
        }
        request = self.deep_extend(subscribe, params)
        trades = await self.watch(url, messageHash, request, messageHash)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-orderbook
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-orderbook-for-new-model
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-30-levels-orderbook
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-full-orderbook

        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.com/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        url = self.urls['api']['ws']
        requestId = self.request_id()
        isSwap = market['swap']
        settleIsUSDT = market['settle'] == 'USDT'
        name = 'orderbook_p' if (isSwap and settleIsUSDT) else 'orderbook'
        messageHash = 'orderbook:' + symbol
        method = name + '.subscribe'
        subscribe: dict = {
            'method': method,
            'id': requestId,
            'params': [
                market['id'],
            ],
        }
        request = self.deep_extend(subscribe, params)
        orderbook = await self.watch(url, messageHash, request, messageHash)
        return orderbook.limit()

    async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-kline
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-kline
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-kline

        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']
        url = self.urls['api']['ws']
        requestId = self.request_id()
        isSwap = market['swap']
        settleIsUSDT = market['settle'] == 'USDT'
        name = 'kline_p' if (isSwap and settleIsUSDT) else 'kline'
        messageHash = 'kline:' + timeframe + ':' + symbol
        method = name + '.subscribe'
        subscribe: dict = {
            'method': method,
            'id': requestId,
            'params': [
                market['id'],
                self.safe_integer(self.timeframes, timeframe),
            ],
        }
        request = self.deep_extend(subscribe, params)
        ohlcv = await self.watch(url, messageHash, request, messageHash)
        if self.newUpdates:
            limit = ohlcv.getLimit(symbol, limit)
        return self.filter_by_since_limit(ohlcv, since, limit, 0, True)

    def custom_handle_delta(self, bookside, delta, market=None):
        bidAsk = self.custom_parse_bid_ask(delta, 0, 1, market)
        bookside.storeArray(bidAsk)

    def custom_handle_deltas(self, bookside, deltas, market=None):
        for i in range(0, len(deltas)):
            self.custom_handle_delta(bookside, deltas[i], market)

    def handle_order_book(self, client: Client, message):
        #
        #     {
        #         "book": {
        #             "asks": [
        #                 [960316000000, 6993800],
        #                 [960318000000, 13183000],
        #                 [960319000000, 9170200],
        #             ],
        #             "bids": [
        #                 [959941000000, 8385300],
        #                 [959939000000, 10296600],
        #                 [959930000000, 3672400],
        #             ]
        #         },
        #         "depth": 30,
        #         "sequence": 1805784701,
        #         "symbol": "sBTCUSDT",
        #         "timestamp": 1592908460404461600,
        #         "type": "snapshot"
        #     }
        #  perpetual
        #    {
        #        "depth": 30,
        #        "orderbook_p": {
        #            "asks": [
        #                [
        #                    "23788.5",
        #                    "0.13",
        #                ],
        #            ],
        #            "bids": [
        #                [
        #                    "23787.8",
        #                    "1.836",
        #                ],
        #            ],
        #        },
        #        "sequence": 1230347368,
        #        "symbol": "BTCUSDT",
        #        "timestamp": "1677093457306978852",
        #        "type": "snapshot",
        #    }
        #
        marketId = self.safe_string(message, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        type = self.safe_string(message, 'type')
        depth = self.safe_integer(message, 'depth')
        name = 'orderbook'
        messageHash = name + ':' + symbol
        nonce = self.safe_integer(message, 'sequence')
        timestamp = self.safe_integer_product(message, 'timestamp', 0.000001)
        if type == 'snapshot':
            book = self.safe_value_2(message, 'book', 'orderbook_p', {})
            snapshot = self.custom_parse_order_book(book, symbol, timestamp, 'bids', 'asks', 0, 1, market)
            snapshot['nonce'] = nonce
            orderbook = self.order_book(snapshot, depth)
            self.orderbooks[symbol] = orderbook
            client.resolve(orderbook, messageHash)
        else:
            if symbol in self.orderbooks:
                orderbook = self.orderbooks[symbol]
                changes = self.safe_dict_2(message, 'book', 'orderbook_p', {})
                asks = self.safe_list(changes, 'asks', [])
                bids = self.safe_list(changes, 'bids', [])
                self.custom_handle_deltas(orderbook['asks'], asks, market)
                self.custom_handle_deltas(orderbook['bids'], bids, market)
                orderbook['nonce'] = nonce
                orderbook['timestamp'] = timestamp
                orderbook['datetime'] = self.iso8601(timestamp)
                self.orderbooks[symbol] = orderbook
                client.resolve(orderbook, messageHash)

    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.com/?id=trade-structure>`
        """
        await self.load_markets()
        market = None
        type = None
        messageHash = 'trades:'
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash = messageHash + market['symbol']
            if market['settle'] == 'USDT':
                params = self.extend(params)
                params['settle'] = 'USDT'
        type, params = self.handle_market_type_and_params('watchMyTrades', market, params)
        if symbol is None:
            settle = self.safe_string(params, 'settle')
            messageHash = (messageHash + 'perpetual') if (settle == 'USDT') else (messageHash + type)
        trades = await self.subscribe_private(type, messageHash, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    def handle_my_trades(self, client: Client, message):
        #
        # swap
        #    [
        #        {
        #            "avgPriceEp":4138763000000,
        #            "baseCurrency":"BTC",
        #            "baseQtyEv":0,
        #            "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2",
        #            "execBaseQtyEv":30100,
        #            "execFeeEv":31,
        #            "execID":"d3b10cfa-84e3-5752-828e-78a79617e598",
        #            "execPriceEp":4138763000000,
        #            "execQuoteQtyEv":1245767663,
        #            "feeCurrency":"BTC",
        #            "lastLiquidityInd":"RemovedLiquidity",
        #            "ordType":"Market",
        #            "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195",
        #            "priceEp":4549022000000,
        #            "qtyType":"ByQuote",
        #            "quoteCurrency":"USDT",
        #            "quoteQtyEv":1248000000,
        #            "side":"Buy",
        #            "symbol":"sBTCUSDT",
        #            "tradeType":"Trade",
        #            "transactTimeNs":"1650442617609928764",
        #            "userID":2647224
        #        }
        #    ]
        # perpetual
        #    [
        #        {
        #            "accountID": 9328670003,
        #            "action": "New",
        #            "actionBy": "ByUser",
        #            "actionTimeNs": 1666858780876924611,
        #            "addedSeq": 77751555,
        #            "apRp": "0",
        #            "bonusChangedAmountRv": "0",
        #            "bpRp": "0",
        #            "clOrdID": "c0327a7d-9064-62a9-28f6-2db9aaaa04e0",
        #            "closedPnlRv": "0",
        #            "closedSize": "0",
        #            "code": 0,
        #            "cumFeeRv": "0",
        #            "cumQty": "0",
        #            "cumValueRv": "0",
        #            "curAccBalanceRv": "1508.489893982237",
        #            "curAssignedPosBalanceRv": "24.62786650928",
        #            "curBonusBalanceRv": "0",
        #            "curLeverageRr": "-10",
        #            "curPosSide": "Buy",
        #            "curPosSize": "0.043",
        #            "curPosTerm": 1,
        #            "curPosValueRv": "894.0689",
        #            "curRiskLimitRv": "1000000",
        #            "currency": "USDT",
        #            "cxlRejReason": 0,
        #            "displayQty": "0.003",
        #            "execFeeRv": "0",
        #            "execID": "00000000-0000-0000-0000-000000000000",
        #            "execPriceRp": "20723.7",
        #            "execQty": "0",
        #            "execSeq": 77751555,
        #            "execStatus": "New",
        #            "execValueRv": "0",
        #            "feeRateRr": "0",
        #            "leavesQty": "0.003",
        #            "leavesValueRv": "63.4503",
        #            "message": "No error",
        #            "ordStatus": "New",
        #            "ordType": "Market",
        #            "orderID": "fa64c6f2-47a4-4929-aab4-b7fa9bbc4323",
        #            "orderQty": "0.003",
        #            "pegOffsetValueRp": "0",
        #            "posSide": "Long",
        #            "priceRp": "21150.1",
        #            "relatedPosTerm": 1,
        #            "relatedReqNum": 11,
        #            "side": "Buy",
        #            "slTrigger": "ByMarkPrice",
        #            "stopLossRp": "0",
        #            "stopPxRp": "0",
        #            "symbol": "BTCUSDT",
        #            "takeProfitRp": "0",
        #            "timeInForce": "ImmediateOrCancel",
        #            "tpTrigger": "ByLastPrice",
        #            "tradeType": "Amend",
        #            "transactTimeNs": 1666858780881545305,
        #            "userID": 932867
        #        },
        #        ...
        #    ]
        #
        channel = 'trades'
        tradesLength = len(message)
        if tradesLength == 0:
            return
        cachedTrades = self.myTrades
        if cachedTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            cachedTrades = ArrayCacheBySymbolById(limit)
        marketIds: dict = {}
        type = None
        for i in range(0, len(message)):
            rawTrade = message[i]
            marketId = self.safe_string(rawTrade, 'symbol')
            market = self.safe_market(marketId)
            parsed = self.parse_trade(rawTrade)
            cachedTrades.append(parsed)
            symbol = parsed['symbol']
            if type is None:
                type = 'perpetual' if (market['settle'] == 'USDT') else market['type']
            marketIds[symbol] = True
        keys = list(marketIds.keys())
        for i in range(0, len(keys)):
            market = keys[i]
            hash = channel + ':' + market
            client.resolve(cachedTrades, hash)
        # generic subscription
        messageHash = channel + ':' + type
        client.resolve(cachedTrades, 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.com/?id=order-structure>`
        """
        await self.load_markets()
        messageHash = 'orders:'
        market = None
        type = None
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash = messageHash + market['symbol']
            if market['settle'] == 'USDT':
                params = self.extend(params)
                params['settle'] = 'USDT'
        type, params = self.handle_market_type_and_params('watchOrders', market, params)
        isUSDTSettled = self.safe_string(params, 'settle') == 'USDT'
        if symbol is None:
            messageHash = (messageHash + 'perpetual') if (isUSDTSettled) else (messageHash + type)
        orders = await self.subscribe_private(type, messageHash, params)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    def handle_orders(self, client: Client, message):
        # spot update
        # {
        #        "closed":[
        #           {
        #              "action":"New",
        #              "avgPriceEp":4138763000000,
        #              "baseCurrency":"BTC",
        #              "baseQtyEv":0,
        #              "bizError":0,
        #              "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2",
        #              "createTimeNs":"1650442617606017583",
        #              "cumBaseQtyEv":30100,
        #              "cumFeeEv":31,
        #              "cumQuoteQtyEv":1245767663,
        #              "cxlRejReason":0,
        #              "feeCurrency":"BTC",
        #              "leavesBaseQtyEv":0,
        #              "leavesQuoteQtyEv":0,
        #              "ordStatus":"Filled",
        #              "ordType":"Market",
        #              "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195",
        #              "pegOffsetValueEp":0,
        #              "priceEp":4549022000000,
        #              "qtyType":"ByQuote",
        #              "quoteCurrency":"USDT",
        #              "quoteQtyEv":1248000000,
        #              "side":"Buy",
        #              "stopPxEp":0,
        #              "symbol":"sBTCUSDT",
        #              "timeInForce":"ImmediateOrCancel",
        #              "tradeType":"Trade",
        #              "transactTimeNs":"1650442617609928764",
        #              "triggerTimeNs":0,
        #              "userID":2647224
        #           }
        #        ],
        #        "fills":[
        #           {
        #              "avgPriceEp":4138763000000,
        #              "baseCurrency":"BTC",
        #              "baseQtyEv":0,
        #              "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2",
        #              "execBaseQtyEv":30100,
        #              "execFeeEv":31,
        #              "execID":"d3b10cfa-84e3-5752-828e-78a79617e598",
        #              "execPriceEp":4138763000000,
        #              "execQuoteQtyEv":1245767663,
        #              "feeCurrency":"BTC",
        #              "lastLiquidityInd":"RemovedLiquidity",
        #              "ordType":"Market",
        #              "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195",
        #              "priceEp":4549022000000,
        #              "qtyType":"ByQuote",
        #              "quoteCurrency":"USDT",
        #              "quoteQtyEv":1248000000,
        #              "side":"Buy",
        #              "symbol":"sBTCUSDT",
        #              "tradeType":"Trade",
        #              "transactTimeNs":"1650442617609928764",
        #              "userID":2647224
        #           }
        #        ],
        #        "open":[
        #           {
        #              "action":"New",
        #              "avgPriceEp":0,
        #              "baseCurrency":"LTC",
        #              "baseQtyEv":0,
        #              "bizError":0,
        #              "clOrdID":"2c0e5eb5-efb7-60d3-2e5f-df175df412ef",
        #              "createTimeNs":"1650446670073853755",
        #              "cumBaseQtyEv":0,
        #              "cumFeeEv":0,
        #              "cumQuoteQtyEv":0,
        #              "cxlRejReason":0,
        #              "feeCurrency":"LTC",
        #              "leavesBaseQtyEv":0,
        #              "leavesQuoteQtyEv":1000000000,
        #              "ordStatus":"New",
        #              "ordType":"Limit",
        #              "orderID":"d2aad92f-50f5-441a-957b-8184b146e3fb",
        #              "pegOffsetValueEp":0,
        #              "priceEp":5000000000,
        #              "qtyType":"ByQuote",
        #              "quoteCurrency":"USDT",
        #              "quoteQtyEv":1000000000,
        #              "side":"Buy",
        #            }
        #        ]
        #  },
        # perpetual
        #    [
        #        {
        #          "accountID": 40183400003,
        #          "action": "New",
        #          "actionBy": "ByUser",
        #          "actionTimeNs": "1674110665380190869",
        #          "addedSeq": 678760103,
        #          "apRp": "0",
        #          "bonusChangedAmountRv": "0",
        #          "bpRp": "0",
        #          "clOrdID": '',
        #          "cl_req_code": 0,
        #          "closedPnlRv": "0",
        #          "closedSize": "0",
        #          "code": 0,
        #          "cumFeeRv": "0",
        #          "cumQty": "0.001",
        #          "cumValueRv": "20.849",
        #          "curAccBalanceRv": "19.9874906",
        #          "curAssignedPosBalanceRv": "0",
        #          "curBonusBalanceRv": "0",
        #          "curLeverageRr": "-10",
        #          "curPosSide": "Buy",
        #          "curPosSize": "0.001",
        #          "curPosTerm": 1,
        #          "curPosValueRv": "20.849",
        #          "curRiskLimitRv": "1000000",
        #          "currency": "USDT",
        #          "cxlRejReason": 0,
        #          "displayQty": "0.001",
        #          "execFeeRv": "0.0125094",
        #          "execID": "b88d2950-04a2-52d8-8927-346059900242",
        #          "execPriceRp": "20849",
        #          "execQty": "0.001",
        #          "execSeq": 678760103,
        #          "execStatus": "TakerFill",
        #          "execValueRv": "20.849",
        #          "feeRateRr": "0.0006",
        #          "lastLiquidityInd": "RemovedLiquidity",
        #          "leavesQty": "0",
        #          "leavesValueRv": "0",
        #          "message": "No error",
        #          "ordStatus": "Filled",
        #          "ordType": "Market",
        #          "orderID": "79620ed2-54c6-4645-a35c-7057e687c576",
        #          "orderQty": "0.001",
        #          "pegOffsetProportionRr": "0",
        #          "pegOffsetValueRp": "0",
        #          "posSide": "Long",
        #          "priceRp": "21476.3",
        #          "relatedPosTerm": 1,
        #          "relatedReqNum": 4,
        #          "side": "Buy",
        #          "slTrigger": "ByMarkPrice",
        #          "stopLossRp": "0",
        #          "stopPxRp": "0",
        #          "symbol": "BTCUSDT",
        #          "takeProfitRp": "0",
        #          "timeInForce": "ImmediateOrCancel",
        #          "tpTrigger": "ByLastPrice",
        #          "tradeType": "Trade",
        #          "transactTimeNs": "1674110665387882268",
        #          "userID": 4018340
        #        },
        #        ...
        #    ]
        #
        trades = []
        parsedOrders = []
        if ('closed' in message) or ('fills' in message) or ('open' in message):
            closed = self.safe_value(message, 'closed', [])
            open = self.safe_value(message, 'open', [])
            orders = self.array_concat(open, closed)
            ordersLength = len(orders)
            if ordersLength == 0:
                return
            trades = self.safe_value(message, 'fills', [])
            for i in range(0, len(orders)):
                rawOrder = orders[i]
                parsedOrder = self.parse_order(rawOrder)
                parsedOrders.append(parsedOrder)
        else:
            messageLength = len(message)
            if messageLength == 0:
                return
            for i in range(0, len(message)):
                update = message[i]
                action = self.safe_string(update, 'action')
                if (action is not None) and (action != 'Cancel'):
                    # order + trade info together
                    trades.append(update)
                parsedOrder = self.parse_ws_swap_order(update)
                parsedOrders.append(parsedOrder)
        self.handle_my_trades(client, trades)
        limit = self.safe_integer(self.options, 'ordersLimit', 1000)
        marketIds: dict = {}
        if self.orders is None:
            self.orders = ArrayCacheBySymbolById(limit)
        type = None
        stored = self.orders
        for i in range(0, len(parsedOrders)):
            parsed = parsedOrders[i]
            stored.append(parsed)
            symbol = parsed['symbol']
            market = self.market(symbol)
            if type is None:
                isUsdt = market['settle'] == 'USDT'
                type = 'perpetual' if isUsdt else market['type']
            marketIds[symbol] = True
        keys = list(marketIds.keys())
        for i in range(0, len(keys)):
            currentMessageHash = 'orders' + ':' + keys[i]
            client.resolve(self.orders, currentMessageHash)
        # resolve generic subscription(spot or swap)
        messageHash = 'orders:' + type
        client.resolve(self.orders, messageHash)

    def parse_ws_swap_order(self, order, market=None):
        #
        # swap
        #    {
        #        "accountID":26472240002,
        #        "action":"Cancel",
        #        "actionBy":"ByUser",
        #        "actionTimeNs":"1650450096104760797",
        #        "addedSeq":26975849309,
        #        "bonusChangedAmountEv":0,
        #        "clOrdID":"d9675963-5e4e-6fc8-898a-ec8b934c1c61",
        #        "closedPnlEv":0,
        #        "closedSize":0,
        #        "code":0,
        #        "cumQty":0,
        #        "cumValueEv":0,
        #        "curAccBalanceEv":400079,
        #        "curAssignedPosBalanceEv":0,
        #        "curBonusBalanceEv":0,
        #        "curLeverageEr":0,
        #        "curPosSide":"None",
        #        "curPosSize":0,
        #        "curPosTerm":1,
        #        "curPosValueEv":0,
        #        "curRiskLimitEv":5000000000,
        #        "currency":"USD",
        #        "cxlRejReason":0,
        #        "displayQty":0,
        #        "execFeeEv":0,
        #        "execID":"00000000-0000-0000-0000-000000000000",
        #        "execPriceEp":0,
        #        "execQty":1,
        #        "execSeq":26975862338,
        #        "execStatus":"Canceled",
        #        "execValueEv":0,
        #        "feeRateEr":0,
        #        "leavesQty":0,
        #        "leavesValueEv":0,
        #        "message":"No error",
        #        "ordStatus":"Canceled",
        #        "ordType":"Limit",
        #        "orderID":"8141deb9-8f94-48f6-9421-a4e3a791537b",
        #        "orderQty":1,
        #        "pegOffsetValueEp":0,
        #        "priceEp":9521,
        #        "relatedPosTerm":1,
        #        "relatedReqNum":4,
        #        "side":"Buy",
        #        "slTrigger":"ByMarkPrice",
        #        "stopLossEp":0,
        #        "stopPxEp":0,
        #        "symbol":"ADAUSD",
        #        "takeProfitEp":0,
        #        "timeInForce":"GoodTillCancel",
        #        "tpTrigger":"ByLastPrice",
        #        "transactTimeNs":"1650450096108143014",
        #        "userID":2647224
        #    }
        # perpetual
        #    {
        #        "accountID": 40183400003,
        #        "action": "New",
        #        "actionBy": "ByUser",
        #        "actionTimeNs": "1674110665380190869",
        #        "addedSeq": 678760103,
        #        "apRp": "0",
        #        "bonusChangedAmountRv": "0",
        #        "bpRp": "0",
        #        "clOrdID": '',
        #        "cl_req_code": 0,
        #        "closedPnlRv": "0",
        #        "closedSize": "0",
        #        "code": 0,
        #        "cumFeeRv": "0",
        #        "cumQty": "0.001",
        #        "cumValueRv": "20.849",
        #        "curAccBalanceRv": "19.9874906",
        #        "curAssignedPosBalanceRv": "0",
        #        "curBonusBalanceRv": "0",
        #        "curLeverageRr": "-10",
        #        "curPosSide": "Buy",
        #        "curPosSize": "0.001",
        #        "curPosTerm": 1,
        #        "curPosValueRv": "20.849",
        #        "curRiskLimitRv": "1000000",
        #        "currency": "USDT",
        #        "cxlRejReason": 0,
        #        "displayQty": "0.001",
        #        "execFeeRv": "0.0125094",
        #        "execID": "b88d2950-04a2-52d8-8927-346059900242",
        #        "execPriceRp": "20849",
        #        "execQty": "0.001",
        #        "execSeq": 678760103,
        #        "execStatus": "TakerFill",
        #        "execValueRv": "20.849",
        #        "feeRateRr": "0.0006",
        #        "lastLiquidityInd": "RemovedLiquidity",
        #        "leavesQty": "0",
        #        "leavesValueRv": "0",
        #        "message": "No error",
        #        "ordStatus": "Filled",
        #        "ordType": "Market",
        #        "orderID": "79620ed2-54c6-4645-a35c-7057e687c576",
        #        "orderQty": "0.001",
        #        "pegOffsetProportionRr": "0",
        #        "pegOffsetValueRp": "0",
        #        "posSide": "Long",
        #        "priceRp": "21476.3",
        #        "relatedPosTerm": 1,
        #        "relatedReqNum": 4,
        #        "side": "Buy",
        #        "slTrigger": "ByMarkPrice",
        #        "stopLossRp": "0",
        #        "stopPxRp": "0",
        #        "symbol": "BTCUSDT",
        #        "takeProfitRp": "0",
        #        "timeInForce": "ImmediateOrCancel",
        #        "tpTrigger": "ByLastPrice",
        #        "tradeType": "Trade",
        #        "transactTimeNs": "1674110665387882268",
        #        "userID": 4018340
        #    }
        #
        id = self.safe_string(order, 'orderID')
        clientOrderId = self.safe_string(order, 'clOrdID')
        if (clientOrderId is not None) and (len(clientOrderId) < 1):
            clientOrderId = None
        marketId = self.safe_string(order, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        status = self.parse_order_status(self.safe_string(order, 'ordStatus'))
        side = self.safe_string_lower(order, 'side')
        type = self.parseOrderType(self.safe_string(order, 'ordType'))
        price = self.safe_string(order, 'priceRp', self.from_ep(self.safe_string(order, 'priceEp'), market))
        amount = self.safe_string(order, 'orderQty')
        filled = self.safe_string(order, 'cumQty')
        remaining = self.safe_string(order, 'leavesQty')
        timestamp = self.safe_integer_product(order, 'actionTimeNs', 0.000001)
        cost = self.safe_string(order, 'cumValueRv', self.from_ev(self.safe_string(order, 'cumValueEv'), market))
        lastTradeTimestamp = self.safe_integer_product(order, 'transactTimeNs', 0.000001)
        if lastTradeTimestamp == 0:
            lastTradeTimestamp = None
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'timeInForce'))
        stopPrice = self.safe_string(order, 'stopPx')
        postOnly = (timeInForce == 'PO')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'triggerPrice': stopPrice,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'cost': cost,
            'average': None,
            'status': status,
            'fee': None,
            'trades': None,
        }, market)

    def handle_message(self, client: Client, message):
        # private spot update
        # {
        #     "orders": {closed: [], fills: [], open: []},
        #     "sequence": 40435835,
        #     "timestamp": "1650443245600839241",
        #     "type": "snapshot",
        #     "wallets": [
        #       {
        #         "balanceEv": 0,
        #         "currency": "BTC",
        #         "lastUpdateTimeNs": "1650442638722099092",
        #         "lockedTradingBalanceEv": 0,
        #         "lockedWithdrawEv": 0,
        #         "userID": 2647224
        #       },
        #       {
        #         "balanceEv": 1154232337,
        #         "currency": "USDT",
        #         "lastUpdateTimeNs": "1650442617610017597",
        #         "lockedTradingBalanceEv": 0,
        #         "lockedWithdrawEv": 0,
        #         "userID": 2647224
        #       }
        #     ]
        # }
        # private swap update
        # {
        #     "sequence": 83839628,
        #     "timestamp": "1650382581827447829",
        #     "type": "snapshot",
        #     "accounts": [
        #       {
        #         "accountBalanceEv": 0,
        #         "accountID": 26472240001,
        #         "bonusBalanceEv": 0,
        #         "currency": "BTC",
        #         "totalUsedBalanceEv": 0,
        #         "userID": 2647224
        #       }
        #     ],
        #     "orders": [],
        #     "positions": [
        #       {
        #         "accountID": 26472240001,
        #         "assignedPosBalanceEv": 0,
        #         "avgEntryPriceEp": 0,
        #         "bankruptCommEv": 0,
        #         "bankruptPriceEp": 0,
        #         "buyLeavesQty": 0,
        #         "buyLeavesValueEv": 0,
        #         "buyValueToCostEr": 1150750,
        #         "createdAtNs": 0,
        #         "crossSharedBalanceEv": 0,
        #         "cumClosedPnlEv": 0,
        #         "cumFundingFeeEv": 0,
        #         "cumTransactFeeEv": 0,
        #         "curTermRealisedPnlEv": 0,
        #         "currency": "BTC",
        #         "dataVer": 2,
        #         "deleveragePercentileEr": 0,
        #         "displayLeverageEr": 10000000000,
        #         "estimatedOrdLossEv": 0,
        #         "execSeq": 0,
        #         "freeCostEv": 0,
        #         "freeQty": 0,
        #         "initMarginReqEr": 1000000,
        #         "lastFundingTime": "1640601827712091793",
        #         "lastTermEndTime": 0,
        #         "leverageEr": 0,
        #         "liquidationPriceEp": 0,
        #         "maintMarginReqEr": 500000,
        #         "makerFeeRateEr": 0,
        #         "markPriceEp": 507806777,
        #         "orderCostEv": 0,
        #         "posCostEv": 0,
        #         "positionMarginEv": 0,
        #         "positionStatus": "Normal",
        #         "riskLimitEv": 10000000000,
        #         "sellLeavesQty": 0,
        #         "sellLeavesValueEv": 0,
        #         "sellValueToCostEr": 1149250,
        #         "side": "None",
        #         "size": 0,
        #         "symbol": "BTCUSD",
        #         "takerFeeRateEr": 0,
        #         "term": 1,
        #         "transactTimeNs": 0,
        #         "unrealisedPnlEv": 0,
        #         "updatedAtNs": 0,
        #         "usedBalanceEv": 0,
        #         "userID": 2647224,
        #         "valueEv": 0
        #       }
        #     ]
        # }
        id = self.safe_string(message, 'id')
        if id in client.subscriptions:
            method = client.subscriptions[id]
            del client.subscriptions[id]
            if method is not True:
                method(client, message)
                return
        methodName = self.safe_string(message, 'method', '')
        if ('market24h' in message) or ('spot_market24h' in message) or (methodName.find('perp_market24h_pack_p') >= 0):
            self.handle_ticker(client, message)
            return
        elif ('trades' in message) or ('trades_p' in message):
            self.handle_trades(client, message)
            return
        elif ('kline' in message) or ('kline_p' in message):
            self.handle_ohlcv(client, message)
            return
        elif ('book' in message) or ('orderbook_p' in message):
            self.handle_order_book(client, message)
            return
        if ('orders' in message) or ('orders_p' in message):
            orders = self.safe_value_2(message, 'orders', 'orders_p', {})
            self.handle_orders(client, orders)
        if ('accounts' in message) or ('accounts_p' in message) or ('wallets' in message):
            type = 'swap' if ('accounts' in message) else 'spot'
            if 'accounts_p' in message:
                type = 'perpetual'
            accounts = self.safe_value_n(message, ['accounts', 'accounts_p', 'wallets'], [])
            self.handle_balance(type, client, accounts)

    def handle_authenticate(self, client: Client, message):
        #
        # {
        #     "error": null,
        #     "id": 1234,
        #     "result": {
        #       "status": "success"
        #     }
        # }
        #
        result = self.safe_value(message, 'result')
        status = self.safe_string(result, 'status')
        messageHash = 'authenticated'
        if status == 'success':
            client.resolve(message, messageHash)
        else:
            error = AuthenticationError(self.id + ' ' + self.json(message))
            client.reject(error, messageHash)
            if messageHash in client.subscriptions:
                del client.subscriptions[messageHash]

    async def subscribe_private(self, type, messageHash, params={}):
        await self.load_markets()
        await self.authenticate()
        url = self.urls['api']['ws']
        requestId = self.seconds()
        settleIsUSDT = (self.safe_value(params, 'settle', '') == 'USDT')
        params = self.omit(params, 'settle')
        channel = 'aop.subscribe'
        if type == 'spot':
            channel = 'wo.subscribe'
        if settleIsUSDT:
            channel = 'aop_p.subscribe'
        request = {
            'id': requestId,
            'method': channel,
            'params': [],
        }
        request = self.extend(request, params)
        return await self.watch(url, messageHash, request, channel)

    async def authenticate(self, params={}):
        self.check_required_credentials()
        url = self.urls['api']['ws']
        client = self.client(url)
        requestId = self.request_id()
        messageHash = 'authenticated'
        future = self.safe_value(client.subscriptions, messageHash)
        if future is None:
            expiryDelta = self.safe_integer(self.options, 'expires', 120)
            expiration = self.seconds() + expiryDelta
            payload = self.apiKey + str(expiration)
            signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256)
            method = 'user.auth'
            request: dict = {
                'method': method,
                'params': ['API', self.apiKey, signature, expiration],
                'id': requestId,
            }
            subscriptionHash = str(requestId)
            message = self.extend(request, params)
            if not (messageHash in client.subscriptions):
                client.subscriptions[subscriptionHash] = self.handle_authenticate
            future = await self.watch(url, messageHash, message, messageHash)
            client.subscriptions[messageHash] = future
        return future
