# -*- 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 Balances, Int, Order, OrderBook, Str, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import NetworkError


class ascendex(ccxt.async_support.ascendex):

    def describe(self):
        return self.deep_extend(super(ascendex, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchOHLCV': True,
                'watchOrderBook': True,
                'watchOrders': True,
                'watchTicker': False,
                'watchTrades': True,
            },
            'urls': {
                'api': {
                    'ws': {
                        'public': 'wss://ascendex.com:443/api/pro/v2/stream',
                        'private': 'wss://ascendex.com:443/{accountGroup}/api/pro/v2/stream',
                    },
                },
                'test': {
                    'ws': {
                        'public': 'wss://api-test.ascendex-sandbox.com:443/api/pro/v2/stream',
                        'private': 'wss://api-test.ascendex-sandbox.com:443/{accountGroup}/api/pro/v2/stream',
                    },
                },
            },
            'options': {
                'tradesLimit': 1000,
                'ordersLimit': 1000,
                'OHLCVLimit': 1000,
                'categoriesAccount': {
                    'cash': 'spot',
                    'futures': 'swap',
                    'margin': 'margin',
                },
            },
        })

    async def watch_public(self, messageHash, params={}):
        url = self.urls['api']['ws']['public']
        id = self.nonce()
        request = {
            'id': str(id),
            'op': 'sub',
        }
        message = self.extend(request, params)
        return await self.watch(url, messageHash, message, messageHash)

    async def watch_private(self, channel, messageHash, params={}):
        await self.load_accounts()
        accountGroup = self.safe_string(self.options, 'account-group')
        url = self.urls['api']['ws']['private']
        url = self.implode_params(url, {'accountGroup': accountGroup})
        id = self.nonce()
        request = {
            'id': str(id),
            'op': 'sub',
            'ch': channel,
        }
        message = self.extend(request, params)
        await self.authenticate(url, params)
        return await self.watch(url, messageHash, message, channel)

    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']
        if (limit is None) or (limit > 1440):
            limit = 100
        interval = self.safe_string(self.timeframes, timeframe, timeframe)
        channel = 'bar' + ':' + interval + ':' + market['id']
        params = {
            'ch': channel,
        }
        ohlcv = await self.watch_public(channel, 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):
        #
        # {
        #     "m": "bar",
        #     "s": "ASD/USDT",
        #     "data": {
        #         "i":  "1",
        #         "ts": 1575398940000,
        #         "o":  "0.04993",
        #         "c":  "0.04970",
        #         "h":  "0.04993",
        #         "l":  "0.04970",
        #         "v":  "8052"
        #     }
        # }
        #
        marketId = self.safe_string(message, 's')
        symbol = self.safe_symbol(marketId)
        channel = self.safe_string(message, 'm')
        data = self.safe_value(message, 'data', {})
        interval = self.safe_string(data, 'i')
        messageHash = channel + ':' + interval + ':' + marketId
        timeframe = self.find_timeframe(interval)
        market = self.market(symbol)
        parsed = self.parse_ohlcv(message, 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
        stored.append(parsed)
        client.resolve(stored, messageHash)
        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.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        channel = 'trades' + ':' + market['id']
        params = self.extend(params, {
            'ch': channel,
        })
        trades = await self.watch_public(channel, 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):
        #
        # {
        #     "m": "trades",
        #     "symbol": "BTC/USDT",
        #     "data": [
        #       {
        #         "p": "40744.28",
        #         "q": "0.00150",
        #         "ts": 1647514330758,
        #         "bm": True,
        #         "seqnum": 72057633465800320
        #       }
        #     ]
        # }
        #
        marketId = self.safe_string(message, 'symbol')
        symbol = self.safe_symbol(marketId)
        channel = self.safe_string(message, 'm')
        messageHash = channel + ':' + marketId
        market = self.market(symbol)
        rawData = self.safe_value(message, 'data')
        if rawData is None:
            rawData = []
        trades = self.parse_trades(rawData, market)
        tradesArray = self.safe_value(self.trades, symbol)
        if tradesArray is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            tradesArray = ArrayCache(limit)
        for i in range(0, len(trades)):
            tradesArray.append(trades[i])
        self.trades[symbol] = tradesArray
        client.resolve(tradesArray, messageHash)

    async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> 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)
        channel = 'depth' + ':' + market['id']
        params = self.extend(params, {
            'ch': channel,
        })
        orderbook = await self.watch_public(channel, params)
        return orderbook.limit()

    async def watch_order_book_snapshot(self, symbol: str, limit: Int = None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        action = 'depth-snapshot'
        channel = action + ':' + market['id']
        params = self.extend(params, {
            'action': action,
            'args': {
                'symbol': market['id'],
            },
            'op': 'req',
        })
        orderbook = await self.watch_public(channel, params)
        return orderbook.limit()

    async def fetch_order_book_snapshot(self, symbol: str, limit: Int = None, params={}):
        restOrderBook = await self.fetch_rest_order_book_safe(symbol, limit, params)
        if not (symbol in self.orderbooks):
            self.orderbooks[symbol] = self.order_book()
        orderbook = self.orderbooks[symbol]
        orderbook.reset(restOrderBook)
        return orderbook

    def handle_order_book_snapshot(self, client: Client, message):
        #
        # {
        #     "m": "depth",
        #     "symbol": "BTC/USDT",
        #     "data": {
        #       "ts": 1647520500149,
        #       "seqnum": 28590487626,
        #       "asks": [
        #         [Array], [Array], [Array],
        #         [Array], [Array], [Array],
        #       ],
        #       "bids": [
        #         [Array], [Array], [Array],
        #         [Array], [Array], [Array],
        #       ]
        #     }
        #   }
        #
        marketId = self.safe_string(message, 'symbol')
        symbol = self.safe_symbol(marketId)
        channel = self.safe_string(message, 'm')
        messageHash = channel + ':' + symbol
        orderbook = self.orderbooks[symbol]
        data = self.safe_value(message, 'data')
        snapshot = self.parse_order_book(data, symbol)
        snapshot['nonce'] = self.safe_integer(data, 'seqnum')
        orderbook.reset(snapshot)
        # unroll the accumulated deltas
        messages = orderbook.cache
        for i in range(0, len(messages)):
            messageItem = messages[i]
            self.handle_order_book_message(client, messageItem, orderbook)
        self.orderbooks[symbol] = orderbook
        client.resolve(orderbook, messageHash)

    def handle_order_book(self, client: Client, message):
        #
        #   {
        #       "m": "depth",
        #       "symbol": "BTC/USDT",
        #       "data": {
        #         "ts": 1647515136144,
        #         "seqnum": 28590470736,
        #         "asks": [[Array], [Array]],
        #         "bids": [[Array], [Array], [Array], [Array], [Array], [Array]]
        #       }
        #   }
        #
        channel = self.safe_string(message, 'm')
        marketId = self.safe_string(message, 'symbol')
        symbol = self.safe_symbol(marketId)
        messageHash = channel + ':' + marketId
        orderbook = self.safe_value(self.orderbooks, symbol)
        if orderbook is None:
            orderbook = self.order_book({})
        if orderbook['nonce'] is None:
            orderbook.cache.append(message)
        else:
            self.handle_order_book_message(client, message, orderbook)
            client.resolve(orderbook, messageHash)

    def handle_delta(self, bookside, delta):
        #
        # ["40990.47","0.01619"],
        #
        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, orderbook):
        #
        # {
        #     "m":"depth",
        #     "symbol":"BTC/USDT",
        #     "data":{
        #        "ts":1647527417715,
        #        "seqnum":28590257013,
        #        "asks":[
        #           ["40990.47","0.01619"],
        #           ["41021.21","0"],
        #           ["41031.59","0.06096"]
        #        ],
        #        "bids":[
        #           ["40990.46","0.76114"],
        #           ["40985.18","0"]
        #        ]
        #     }
        #  }
        #
        data = self.safe_value(message, 'data', {})
        seqNum = self.safe_integer(data, 'seqnum')
        if seqNum > orderbook['nonce']:
            asks = self.safe_value(data, 'asks', [])
            bids = self.safe_value(data, 'bids', [])
            self.handle_deltas(orderbook['asks'], asks)
            self.handle_deltas(orderbook['bids'], bids)
            orderbook['nonce'] = seqNum
            timestamp = self.safe_integer(data, 'ts')
            orderbook['timestamp'] = timestamp
            orderbook['datetime'] = self.iso8601(timestamp)
        return orderbook

    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.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        type, query = self.handle_market_type_and_params('watchBalance', None, params)
        channel = None
        messageHash = None
        if (type == 'spot') or (type == 'margin'):
            accountCategories = self.safe_value(self.options, 'accountCategories', {})
            accountCategory = self.safe_string(accountCategories, type, 'cash')  # cash, margin,
            accountCategory = accountCategory.upper()
            channel = 'order:' + accountCategory  # order and balance share the same channel
            messageHash = 'balance:' + type
        else:
            channel = 'futures-account-update'
            messageHash = 'balance:swap'
        return await self.watch_private(channel, messageHash, query)

    def handle_balance(self, client: Client, message):
        #
        # cash account
        #
        # {
        #     "m": "balance",
        #     "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        #     "ac": "CASH",
        #     "data": {
        #         "a" : "USDT",
        #         "sn": 8159798,
        #         "tb": "600",
        #         "ab": "600"
        #     }
        # }
        #
        # margin account
        #
        # {
        #     "m": "balance",
        #     "accountId": "marOxpKJV83dxTRx0Eyxpa0gxc4Txt0P",
        #     "ac": "MARGIN",
        #     "data": {
        #         "a"  : "USDT",
        #         "sn" : 8159802,
        #         "tb" : "400",  # total Balance
        #         "ab" : "400",  # available balance
        #         "brw": "0",  # borrowws
        #         "int": "0"  # interest
        #     }
        # }
        #
        # futures
        # {
        #     "m"     : "futures-account-update",            # message
        #     "e"     : "ExecutionReport",                   # event type
        #     "t"     : 1612508562129,                       # time
        #     "acc"   : "futures-account-id",         # account ID
        #     "at"    : "FUTURES",                           # account type
        #     "sn"    : 23128,                               # sequence number,
        #     "id"    : "r177710001cbU3813942147C5kbFGOan",
        #     "col": [
        #       {
        #         "a": "USDT",               # asset code
        #         "b": "1000000",            # balance
        #         "f": "1"                   # discount factor
        #       }
        #     ],
        #     (...)
        #
        channel = self.safe_string(message, 'm')
        result = None
        type = None
        if (channel == 'order') or (channel == 'futures-order'):
            data = self.safe_value(message, 'data')
            marketId = self.safe_string(data, 's')
            market = self.safe_market(marketId)
            baseAccount = self.account()
            baseAccount['free'] = self.safe_string(data, 'bab')
            baseAccount['total'] = self.safe_string(data, 'btb')
            quoteAccount = self.account()
            quoteAccount['free'] = self.safe_string(data, 'qab')
            quoteAccount['total'] = self.safe_string(data, 'qtb')
            if market['contract']:
                type = 'swap'
                result = self.safe_value(self.balance, type, {})
            else:
                type = market['type']
                result = self.safe_value(self.balance, type, {})
            result[market['base']] = baseAccount
            result[market['quote']] = quoteAccount
        else:
            accountType = self.safe_string_lower_2(message, 'ac', 'at')
            categoriesAccounts = self.safe_value(self.options, 'categoriesAccount')
            type = self.safe_string(categoriesAccounts, accountType, 'spot')
            result = self.safe_value(self.balance, type, {})
            data = self.safe_value(message, 'data')
            balances = None
            if data is None:
                balances = self.safe_value(message, 'col')
            else:
                balances = [data]
            for i in range(0, len(balances)):
                balance = balances[i]
                code = self.safe_currency_code(self.safe_string(balance, 'a'))
                account = self.account()
                account['free'] = self.safe_string(balance, 'ab')
                account['total'] = self.safe_string_2(balance, 'tb', 'b')
                result[code] = account
        messageHash = 'balance' + ':' + type
        client.resolve(self.safe_balance(result), messageHash)

    async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        :see: https://ascendex.github.io/ascendex-pro-api/#channel-order-and-balance
        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()
        market = None
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
        type, query = self.handle_market_type_and_params('watchOrders', market, params)
        messageHash = None
        channel = None
        if type != 'spot' and type != 'margin':
            channel = 'futures-order'
            messageHash = 'order:FUTURES'
        else:
            accountCategories = self.safe_value(self.options, 'accountCategories', {})
            accountCategory = self.safe_string(accountCategories, type, 'cash')  # cash, margin
            accountCategory = accountCategory.upper()
            messageHash = 'order' + ':' + accountCategory
            channel = messageHash
        if symbol is not None:
            messageHash = messageHash + ':' + symbol
        orders = await self.watch_private(channel, messageHash, query)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    def handle_order(self, client: Client, message):
        #
        # spot order
        # {
        #   "m": "order",
        #   "accountId": "cshF5SlR9ukAXoDOuXbND4dVpBMw9gzH",
        #   "ac": "CASH",
        #   "data": {
        #     "sn": 19399016185,
        #     "orderId": "r17f9d7983faU7223046196CMlrj3bfC",
        #     "s": "LTC/USDT",
        #     "ot": "Limit",
        #     "t": 1647614461160,
        #     "p": "50",
        #     "q": "0.1",
        #     "sd": "Buy",
        #     "st": "New",
        #     "ap": "0",
        #     "cfq": "0",
        #     "sp": '',
        #     "err": '',
        #     "btb": "0",
        #     "bab": "0",
        #     "qtb": "8",
        #     "qab": "2.995",
        #     "cf": "0",
        #     "fa": "USDT",
        #     "ei": "NULL_VAL"
        #   }
        # }
        #
        #  futures order
        # {
        #     "m": "futures-order",
        #     "sn": 19399927636,
        #     "e": "ExecutionReport",
        #     "a": "futF5SlR9ukAXoDOuXbND4dVpBMw9gzH",  # account id
        #     "ac": "FUTURES",
        #     "t": 1647622515434,  # last execution time
        #      (...)
        # }
        #
        accountType = self.safe_string(message, 'ac')
        messageHash = 'order:' + accountType
        data = self.safe_value(message, 'data', message)
        order = self.parse_ws_order(data)
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        orders = self.orders
        orders.append(order)
        symbolMessageHash = messageHash + ':' + order['symbol']
        client.resolve(orders, symbolMessageHash)
        client.resolve(orders, messageHash)

    def parse_ws_order(self, order, market=None):
        #
        # spot order
        #    {
        #          "sn": 19399016185,  #sequence number
        #          "orderId": "r17f9d7983faU7223046196CMlrj3bfC",
        #          "s": "LTC/USDT",
        #          "ot": "Limit",  # order type
        #          "t": 1647614461160,  # last execution timestamp
        #          "p": "50",  # price
        #          "q": "0.1",  # quantity
        #          "sd": "Buy",  # side
        #          "st": "New",  # status
        #          "ap": "0",  # average fill price
        #          "cfq": "0",  # cumulated fill quantity
        #          "sp": '',  # stop price
        #          "err": '',
        #          "btb": "0",  # base asset total balance
        #          "bab": "0",  # base asset available balance
        #          "qtb": "8",  # quote asset total balance
        #          "qab": "2.995",  # quote asset available balance
        #          "cf": "0",  # cumulated commission
        #          "fa": "USDT",  # fee asset
        #          "ei": "NULL_VAL"
        #        }
        #
        #  futures order
        # {
        #     "m": "futures-order",
        #     "sn": 19399927636,
        #     "e": "ExecutionReport",
        #     "a": "futF5SlR9ukAXoDOuXbND4dVpBMw9gzH",  # account id
        #     "ac": "FUTURES",
        #     "t": 1647622515434,  # last execution time
        #     "ct": 1647622515413,  # order creation time
        #     "orderId": "r17f9df469b1U7223046196Okf5Kbmd",
        #     "sd": "Buy",  # side
        #     "ot": "Limit",  # order type
        #     "ei": "NULL_VAL",
        #     "q": "1",  # quantity
        #     "p": "50",  #price
        #     "sp": "0",  # stopPrice
        #     "spb": '',  # stopTrigger
        #     "s": "LTC-PERP",  # symbol
        #     "st": "New",  # state
        #     "err": '',
        #     "lp": "0",  # last filled price
        #     "lq": "0",  # last filled quantity(base asset)
        #     "ap": "0",  # average filled price
        #     "cfq": "0",  # cummulative filled quantity(base asset)
        #     "f": "0",  # commission fee of the current execution
        #     "cf": "0",  # cumulative commission fee
        #     "fa": "USDT",  # fee asset
        #     "psl": "0",
        #     "pslt": "market",
        #     "ptp": "0",
        #     "ptpt": "market"
        #   }
        #
        status = self.parse_order_status(self.safe_string(order, 'st'))
        marketId = self.safe_string(order, 's')
        timestamp = self.safe_integer(order, 't')
        symbol = self.safe_symbol(marketId, market, '/')
        lastTradeTimestamp = self.safe_integer(order, 't')
        price = self.safe_string(order, 'p')
        amount = self.safe_string(order, 'q')
        average = self.safe_string(order, 'ap')
        filled = self.safe_string(order, 'cfq')
        id = self.safe_string(order, 'orderId')
        type = self.safe_string_lower(order, 'ot')
        side = self.safe_string_lower(order, 'sd')
        feeCost = self.safe_number(order, 'cf')
        fee = None
        if feeCost is not None:
            feeCurrencyId = self.safe_string(order, 'fa')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        stopPrice = self.parse_number(self.omit_zero(self.safe_string(order, 'sp')))
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'triggerPrice': stopPrice,
            'amount': amount,
            'cost': None,
            'average': average,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': fee,
            'trades': None,
        }, market)

    def handle_error_message(self, client: Client, message):
        #
        # {
        #     "m": "disconnected",
        #     "code": 100005,
        #     "reason": "INVALID_WS_REQUEST_DATA",
        #     "info": "Session is disconnected due to missing pong message from the client"
        #   }
        #
        errorCode = self.safe_integer(message, 'code')
        try:
            if errorCode is not None:
                feedback = self.id + ' ' + self.json(message)
                self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
                messageString = self.safe_value(message, 'message')
                if messageString is not None:
                    self.throw_broadly_matched_exception(self.exceptions['broad'], messageString, feedback)
            return False
        except Exception as e:
            if isinstance(e, AuthenticationError):
                messageHash = 'authenticated'
                client.reject(e, messageHash)
                if messageHash in client.subscriptions:
                    del client.subscriptions[messageHash]
            else:
                client.reject(e)
            return True

    def handle_authenticate(self, client: Client, message):
        #
        #     {m: "auth", id: "1647605234", code: 0}
        #
        messageHash = 'authenticated'
        client.resolve(message, messageHash)

    def handle_message(self, client: Client, message):
        if self.handle_error_message(client, message):
            return
        #
        #     {m: "ping", hp: 3}
        #
        #     {m: "sub", ch: "bar:BTC/USDT", code: 0}
        #
        #     {m: 'sub', id: "1647515701", ch: "depth:BTC/USDT", code: 0}
        #
        #     {m: "connected", type: "unauth"}
        #
        #     {m: "auth", id: "1647605234", code: 0}
        #
        # order or balance sub
        # {
        #     "m": "sub",
        #     "id": "1647605952",
        #     "ch": "order:cshF5SlR9ukAXoDOuXbND4dVpBMw9gzH", or futures-order
        #     "code": 0
        #   }
        #
        # ohlcv
        #  {
        #     "m": "bar",
        #     "s": "BTC/USDT",
        #     "data": {
        #       "i": "1",
        #       "ts": 1647510060000,
        #       "o": "40813.93",
        #       "c": "40804.57",
        #       "h": "40814.21",
        #       "l": "40804.56",
        #       "v": "0.01537"
        #     }
        #   }
        #
        # trades
        #
        #    {
        #        "m": "trades",
        #        "symbol": "BTC/USDT",
        #        "data": [
        #          {
        #            "p": "40762.26",
        #            "q": "0.01500",
        #            "ts": 1647514306759,
        #            "bm": True,
        #            "seqnum": 72057633465795180
        #          }
        #        ]
        #    }
        #
        # orderbook deltas
        #
        # {
        #     "m":"depth",
        #     "symbol":"BTC/USDT",
        #     "data":{
        #        "ts":1647527417715,
        #        "seqnum":28590257013,
        #        "asks":[
        #           ["40990.47","0.01619"],
        #           ["41021.21","0"],
        #           ["41031.59","0.06096"]
        #        ],
        #        "bids":[
        #           ["40990.46","0.76114"],
        #           ["40985.18","0"]
        #        ]
        #     }
        #  }
        #
        # orderbook snapshot
        #  {
        #     "m": "depth-snapshot",
        #     "symbol": "BTC/USDT",
        #     "data": {
        #       "ts": 1647525938513,
        #       "seqnum": 28590504772,
        #       "asks": [
        #         [Array], [Array], [Array], [Array], [Array], [Array], [Array],
        #         [Array], [Array], [Array], [Array], [Array], [Array], [Array],
        #         [Array], [Array], [Array], [Array], [Array], [Array], [Array],
        #          (...)
        #       ]
        #  }
        #
        # spot order update
        #  {
        #      "m": "order",
        #      "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        #      "ac": "CASH",
        #      "data": {
        #          "s":       "BTC/USDT",
        #          "sn":       8159711,
        #          "sd":      "Buy",
        #          "ap":      "0",
        #          "bab":     "2006.5974027",
        #          "btb":     "2006.5974027",
        #          "cf":      "0",
        #          "cfq":     "0",
        #          (...)
        #      }
        #  }
        # future order update
        # {
        #     "m": "futures-order",
        #     "sn": 19404258063,
        #     "e": "ExecutionReport",
        #     "a": "futF5SlR9ukAXoDOuXbND4dVpBMw9gzH",
        #     "ac": "FUTURES",
        #     "t": 1647681792543,
        #     "ct": 1647622515413,
        #     "orderId": "r17f9df469b1U7223046196Okf5KbmdL",
        #         (...)
        #     "ptpt": "None"
        #   }
        #
        # balance update cash
        # {
        #     "m": "balance",
        #     "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        #     "ac": "CASH",
        #     "data": {
        #         "a" : "USDT",
        #         "sn": 8159798,
        #         "tb": "600",
        #         "ab": "600"
        #     }
        # }
        #
        # balance update margin
        # {
        #     "m": "balance",
        #     "accountId": "marOxpKJV83dxTRx0Eyxpa0gxc4Txt0P",
        #     "ac": "MARGIN",
        #     "data": {
        #         "a"  : "USDT",
        #         "sn" : 8159802,
        #         "tb" : "400",
        #         "ab" : "400",
        #         "brw": "0",
        #         "int": "0"
        #     }
        # }
        #
        subject = self.safe_string(message, 'm')
        methods = {
            'ping': self.handle_ping,
            'auth': self.handle_authenticate,
            'sub': self.handle_subscription_status,
            'depth': self.handle_order_book,
            'depth-snapshot': self.handle_order_book_snapshot,
            'trades': self.handle_trades,
            'bar': self.handle_ohlcv,
            'balance': self.handle_balance,
            'futures-account-update': self.handle_balance,
        }
        method = self.safe_value(methods, subject)
        if method is not None:
            method(client, message)
        if (subject == 'order') or (subject == 'futures-order'):
            # self.handle_order(client, message)
            # balance updates may be in the order structure
            # they may also be standalone balance updates related to account transfers
            self.handle_order(client, message)
            if subject == 'order':
                self.handle_balance(client, message)

    def handle_subscription_status(self, client: Client, message):
        #
        #     {m: "sub", ch: "bar:BTC/USDT", code: 0}
        #
        #     {m: 'sub', id: "1647515701", ch: "depth:BTC/USDT", code: 0}
        #
        channel = self.safe_string(message, 'ch', '')
        if channel.find('depth') > -1 and not (channel.find('depth-snapshot') > -1):
            self.handle_order_book_subscription(client, message)
        return message

    def handle_order_book_subscription(self, client: Client, message):
        channel = self.safe_string(message, 'ch')
        parts = channel.split(':')
        marketId = parts[1]
        market = self.safe_market(marketId)
        symbol = market['symbol']
        if symbol in self.orderbooks:
            del self.orderbooks[symbol]
        self.orderbooks[symbol] = self.order_book({})
        if self.options['defaultType'] == 'swap' or market['contract']:
            self.spawn(self.fetch_order_book_snapshot, symbol)
        else:
            self.spawn(self.watch_order_book_snapshot, symbol)

    async def pong(self, client, message):
        #
        #     {m: "ping", hp: 3}
        #
        try:
            await client.send({'op': 'pong', 'hp': self.safe_integer(message, 'hp')})
        except Exception as e:
            error = NetworkError(self.id + ' handlePing failed with error ' + self.json(e))
            client.reset(error)

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

    async def authenticate(self, url, params={}):
        self.check_required_credentials()
        messageHash = 'authenticated'
        client = self.client(url)
        future = self.safe_value(client.subscriptions, messageHash)
        if future is None:
            timestamp = str(self.milliseconds())
            urlParts = url.split('/')
            partsLength = len(urlParts)
            path = self.safe_string(urlParts, partsLength - 1)
            version = self.safe_string(urlParts, partsLength - 2)
            auth = timestamp + '+' + version + '/' + path
            secret = self.base64_to_binary(self.secret)
            signature = self.hmac(self.encode(auth), secret, hashlib.sha256, 'base64')
            request = {
                'op': 'auth',
                'id': str(self.nonce()),
                't': timestamp,
                'key': self.apiKey,
                'sig': signature,
            }
            future = await self.watch(url, messageHash, self.extend(request, params), messageHash)
            client.subscriptions[messageHash] = future
        return future
