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

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

import ccxt_zagrava.async_support
from ccxt_zagrava.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
from ccxt_zagrava.base.types import Balances, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade
from ccxt_zagrava.async_support.base.ws.client import Client
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import NotSupported
from ccxt_zagrava.base.errors import ExchangeNotAvailable
from ccxt_zagrava.base.errors import RequestTimeout
from ccxt_zagrava.base.precise import Precise


class coinex(ccxt_zagrava.async_support.coinex):

    def describe(self):
        return self.deep_extend(super(coinex, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchTicker': True,
                'watchTickers': True,
                'watchTrades': True,
                'watchMyTrades': False,  # can query but can't subscribe
                'watchOrders': True,
                'watchOrderBook': True,
                'watchOHLCV': True,  # only for swap markets
                'fetchOHLCVWs': True,
            },
            'urls': {
                'api': {
                    'ws': {
                        'spot': 'wss://socket.coinex.com/',
                        'swap': 'wss://perpetual.coinex.com/',
                    },
                },
            },
            'options': {
                'watchOHLCVWarning': True,
                'timeframes': {
                    '1m': 60,
                    '3m': 180,
                    '5m': 300,
                    '15m': 900,
                    '30m': 1800,
                    '1h': 3600,
                    '2h': 7200,
                    '4h': 14400,
                    '6h': 21600,
                    '12h': 43200,
                    '1d': 86400,
                    '3d': 259200,
                    '1w': 604800,
                },
                'account': 'spot',
                'watchOrderBook': {
                    'limits': [5, 10, 20, 50],
                    'defaultLimit': 50,
                    'aggregations': ['10', '1', '0', '0.1', '0.01'],
                    'defaultAggregation': '0',
                },
            },
            'streaming': {
            },
            'exceptions': {
                'codes': {
                    '1': BadRequest,  # Parameter error
                    '2': ExchangeError,  # Internal error
                    '3': ExchangeNotAvailable,  # Service unavailable
                    '4': NotSupported,  # Method unavailable
                    '5': RequestTimeout,  # Service timeout
                    '6': AuthenticationError,  # Permission denied
                },
            },
        })

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

    def handle_ticker(self, client: Client, message):
        #
        #  spot
        #
        #     {
        #         "method": "state.update",
        #         "params": [{
        #             "BTCUSDT": {
        #                 "last": "31577.89",
        #                 "open": "29318.36",
        #                 "close": "31577.89",
        #                 "high": "32222.19",
        #                 "low": "29317.21",
        #                 "volume": "630.43024965",
        #                 "sell_total": "13.66143951",
        #                 "buy_total": "2.76410939",
        #                 "period": 86400,
        #                 "deal": "19457487.84611409070000000000"
        #             }
        #         }]
        #     }
        #
        #  swap
        #
        #     {
        #         "method": "state.update",
        #         "params": [{
        #             "BTCUSDT": {
        #                 "period": 86400,
        #                 "funding_time": 422,
        #                 "position_amount": "285.6246",
        #                 "funding_rate_last": "-0.00097933",
        #                 "funding_rate_next": "0.00022519",
        #                 "funding_rate_predict": "0.00075190",
        #                 "insurance": "17474289.49925859030905338270",
        #                 "last": "31570.08",
        #                 "sign_price": "31568.09",
        #                 "index_price": "31561.85000000",
        #                 "open": "29296.11",
        #                 "close": "31570.08",
        #                 "high": "32463.40",
        #                 "low": "29296.11",
        #                 "volume": "8774.7318",
        #                 "deal": "270675177.827928219109030017258398",
        #                 "sell_total": "19.2230",
        #                 "buy_total": "25.7814"
        #             }
        #         }]
        #     }
        #
        defaultType = self.safe_string(self.options, 'defaultType')
        params = self.safe_value(message, 'params', [])
        rawTickers = self.safe_value(params, 0, {})
        keys = list(rawTickers.keys())
        newTickers = []
        for i in range(0, len(keys)):
            marketId = keys[i]
            rawTicker = rawTickers[marketId]
            symbol = self.safe_symbol(marketId, None, None, defaultType)
            market = self.safe_market(marketId, None, None, defaultType)
            parsedTicker = self.parse_ws_ticker(rawTicker, market)
            self.tickers[symbol] = parsedTicker
            newTickers.append(parsedTicker)
        messageHashes = self.find_message_hashes(client, 'tickers::')
        for i in range(0, len(messageHashes)):
            messageHash = messageHashes[i]
            parts = messageHash.split('::')
            symbolsString = parts[1]
            symbols = symbolsString.split(',')
            tickers = self.filter_by_array(newTickers, 'symbol', symbols)
            tickersSymbols = list(tickers.keys())
            numTickers = len(tickersSymbols)
            if numTickers > 0:
                client.resolve(tickers, messageHash)
        client.resolve(newTickers, 'tickers')

    def parse_ws_ticker(self, ticker, market=None):
        #
        #  spot
        #
        #     {
        #         "last": "31577.89",
        #         "open": "29318.36",
        #         "close": "31577.89",
        #         "high": "32222.19",
        #         "low": "29317.21",
        #         "volume": "630.43024965",
        #         "sell_total": "13.66143951",
        #         "buy_total": "2.76410939",
        #         "period": 86400,
        #         "deal": "19457487.84611409070000000000"
        #     }
        #
        #  swap
        #
        #     {
        #         "period": 86400,
        #         "funding_time": 422,
        #         "position_amount": "285.6246",
        #         "funding_rate_last": "-0.00097933",
        #         "funding_rate_next": "0.00022519",
        #         "funding_rate_predict": "0.00075190",
        #         "insurance": "17474289.49925859030905338270",
        #         "last": "31570.08",
        #         "sign_price": "31568.09",
        #         "index_price": "31561.85000000",
        #         "open": "29296.11",
        #         "close": "31570.08",
        #         "high": "32463.40",
        #         "low": "29296.11",
        #         "volume": "8774.7318",
        #         "deal": "270675177.827928219109030017258398",
        #         "sell_total": "19.2230",
        #         "buy_total": "25.7814"
        #     }
        #
        defaultType = self.safe_string(self.options, 'defaultType')
        return self.safe_ticker({
            'symbol': self.safe_symbol(None, market, None, defaultType),
            'timestamp': None,
            'datetime': None,
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': None,
            'bidVolume': self.safe_string(ticker, 'buy_total'),
            'ask': None,
            'askVolume': self.safe_string(ticker, 'sell_total'),
            'vwap': None,
            'open': self.safe_string(ticker, 'open'),
            'close': self.safe_string(ticker, 'close'),
            'last': self.safe_string(ticker, 'last'),
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_string(ticker, 'volume'),
            'quoteVolume': self.safe_string(ticker, 'deal'),
            'info': ticker,
        }, market)

    async def watch_balance(self, params={}) -> Balances:
        """
        watch balance and get the amount of funds available for trading or funds locked in orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        await self.authenticate(params)
        messageHash = 'balance'
        type = None
        type, params = self.handle_market_type_and_params('watchBalance', None, params)
        url = self.urls['api']['ws'][type]
        currencies = list(self.currencies_by_id.keys())
        subscribe: dict = {
            'method': 'asset.subscribe',
            'params': currencies,
            'id': self.request_id(),
        }
        request = self.deep_extend(subscribe, params)
        return await self.watch(url, messageHash, request, messageHash)

    def handle_balance(self, client: Client, message):
        #
        #     {
        #         "method": "asset.update",
        #         "params": [
        #             {
        #                 "BTC": {
        #                     "available": "250",
        #                     "frozen": "10",
        #                 }
        #             }
        #         ],
        #         "id": null
        #     }
        #
        params = self.safe_value(message, 'params', [])
        first = self.safe_value(params, 0, {})
        self.balance['info'] = first
        currencies = list(first.keys())
        for i in range(0, len(currencies)):
            currencyId = currencies[i]
            code = self.safe_currency_code(currencyId)
            available = self.safe_string(first[currencyId], 'available')
            frozen = self.safe_string(first[currencyId], 'frozen')
            account = self.account()
            account['free'] = available
            account['used'] = frozen
            self.balance[code] = account
            self.balance = self.safe_balance(self.balance)
        messageHash = 'balance'
        client.resolve(self.balance, messageHash)

    def handle_trades(self, client: Client, message):
        #
        #     {
        #         "method": "deals.update",
        #         "params": [
        #             "BTCUSD",
        #             [{
        #                 "type": "sell",
        #                 "time": 1496458040.059284,
        #                 "price ": "46444.74",
        #                 "id": 29433,
        #                 "amount": "0.00120000"
        #             }]
        #         ],
        #         "id": null
        #     }
        #
        params = self.safe_value(message, 'params', [])
        marketId = self.safe_string(params, 0)
        trades = self.safe_value(params, 1, [])
        defaultType = self.safe_string(self.options, 'defaultType')
        market = self.safe_market(marketId, None, None, defaultType)
        symbol = market['symbol']
        messageHash = 'trades:' + 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
        for i in range(0, len(trades)):
            trade = trades[i]
            parsed = self.parse_ws_trade(trade, market)
            stored.append(parsed)
        self.trades[symbol] = stored
        client.resolve(self.trades[symbol], messageHash)

    def parse_ws_trade(self, trade, market=None):
        #
        #     {
        #         "type": "sell",
        #         "time": 1496458040.059284,
        #         "price ": "46444.74",
        #         "id": 29433,
        #         "amount": "0.00120000"
        #     }
        #
        timestamp = self.safe_timestamp(trade, 'time')
        defaultType = self.safe_string(self.options, 'defaultType')
        return self.safe_trade({
            'id': self.safe_string(trade, 'id'),
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': self.safe_symbol(None, market, None, defaultType),
            'order': None,
            'type': None,
            'side': self.safe_string(trade, 'type'),
            'takerOrMaker': None,
            'price': self.safe_string(trade, 'price'),
            'amount': self.safe_string(trade, 'amount'),
            'cost': None,
            'fee': None,
        }, market)

    def handle_ohlcv(self, client: Client, message):
        #
        #  spot
        #     {
        #         "error": null,
        #         "result": [
        #           [
        #             1673846940,
        #             "21148.74",
        #             "21148.38",
        #             "21148.75",
        #             "21138.66",
        #             "1.57060173",
        #             "33214.9138778914"
        #           ],
        #         ]
        #         "id": 1,
        #     }
        #  swap
        #     {
        #         "method": "kline.update",
        #         "params": [
        #             [
        #                 1654019640,   # timestamp
        #                 "32061.99",   # open
        #                 "32061.28",   # close
        #                 "32061.99",   # high
        #                 "32061.28",   # low
        #                 "0.1285",     # amount base
        #                 "4119.943736"  # amount quote
        #             ]
        #         ],
        #         "id": null
        #     }
        #
        candles = self.safe_value_2(message, 'params', 'result', [])
        messageHash = 'ohlcv'
        id = self.safe_string(message, 'id')
        ohlcvs = self.parse_ohlcvs(candles)
        if id is not None:
            # spot subscription response
            client.resolve(ohlcvs, messageHash)
            return
        keys = list(self.ohlcvs.keys())
        keysLength = len(keys)
        if keysLength == 0:
            self.ohlcvs['unknown'] = {}
            limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
            stored = ArrayCacheByTimestamp(limit)
            self.ohlcvs['unknown']['unknown'] = stored
        ohlcv = self.ohlcvs['unknown']['unknown']
        for i in range(0, len(ohlcvs)):
            candle = ohlcvs[i]
            ohlcv.append(candle)
        client.resolve(ohlcv, messageHash)

    async def watch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket007_state_subscribe
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        tickers = await self.watch_tickers([symbol], params)
        return self.safe_value(tickers, symbol)

    async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket007_state_subscribe
        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
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        type = None
        type, params = self.handle_market_type_and_params('watchTickers', None, params)
        url = self.urls['api']['ws'][type]
        messageHash = 'tickers'
        if symbols is not None:
            messageHash = 'tickers::' + ','.join(symbols)
        subscribe: dict = {
            'method': 'state.subscribe',
            'id': self.request_id(),
            'params': [],
        }
        request = self.deep_extend(subscribe, params)
        newTickers = await self.watch(url, messageHash, request, messageHash)
        if self.newUpdates:
            return newTickers
        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]:
        """
        :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket012_deal_subcribe
        :see: https://viabtc.github.io/coinex_api_en_doc/futures/#docsfutures002_websocket019_deal_subcribe
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        type = None
        type, params = self.handle_market_type_and_params('watchTrades', market, params)
        url = self.urls['api']['ws'][type]
        messageHash = 'trades:' + symbol
        subscriptionHash = 'trades'
        subscribedSymbols = self.safe_value(self.options, 'watchTradesSubscriptions', [])
        subscribedSymbols.append(market['id'])
        message: dict = {
            'method': 'deals.subscribe',
            'params': subscribedSymbols,
            'id': self.request_id(),
        }
        self.options['watchTradesSubscriptions'] = subscribedSymbols
        request = self.deep_extend(message, params)
        trades = await self.watch(url, messageHash, request, subscriptionHash)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket017_depth_subscribe_multi
        :see: https://viabtc.github.io/coinex_api_en_doc/futures/#docsfutures002_websocket011_depth_subscribe_multi
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        type = None
        type, params = self.handle_market_type_and_params('watchOrderBook', market, params)
        url = self.urls['api']['ws'][type]
        name = 'orderbook'
        messageHash = name + ':' + symbol
        options = self.safe_value(self.options, 'watchOrderBook', {})
        limits = self.safe_value(options, 'limits', [])
        if limit is None:
            limit = self.safe_value(options, 'defaultLimit', 50)
        if not self.in_array(limit, limits):
            raise NotSupported(self.id + ' watchOrderBook() limit must be one of ' + ', '.join(limits))
        defaultAggregation = self.safe_string(options, 'defaultAggregation', '0')
        aggregations = self.safe_value(options, 'aggregations', [])
        aggregation = self.safe_string(params, 'aggregation', defaultAggregation)
        if not self.in_array(aggregation, aggregations):
            raise NotSupported(self.id + ' watchOrderBook() aggregation must be one of ' + ', '.join(aggregations))
        params = self.omit(params, 'aggregation')
        watchOrderBookSubscriptions = self.safe_value(self.options, 'watchOrderBookSubscriptions', {})
        watchOrderBookSubscriptions[symbol] = [market['id'], limit, aggregation, True]
        subscribe: dict = {
            'method': 'depth.subscribe_multi',
            'id': self.request_id(),
            'params': list(watchOrderBookSubscriptions.values()),
        }
        self.options['watchOrderBookSubscriptions'] = watchOrderBookSubscriptions
        subscriptionHash = self.hash(self.encode(self.json(watchOrderBookSubscriptions)), 'sha256')
        request = self.deep_extend(subscribe, params)
        orderbook = await self.watch(url, messageHash, request, subscriptionHash, request)
        return orderbook.limit()

    async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        :see: https://viabtc.github.io/coinex_api_en_doc/futures/#docsfutures002_websocket023_kline_subscribe
        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']
        type = None
        type, params = self.handle_market_type_and_params('watchOHLCV', market, params)
        if type != 'swap':
            raise NotSupported(self.id + ' watchOHLCV() is only supported for swap markets. Try using fetchOHLCV() instead')
        url = self.urls['api']['ws'][type]
        messageHash = 'ohlcv'
        watchOHLCVWarning = self.safe_bool(self.options, 'watchOHLCVWarning', True)
        client = self.safe_value(self.clients, url, {})
        clientSub = self.safe_value(client, 'subscriptions', {})
        existingSubscription = self.safe_value(clientSub, messageHash)
        subSymbol = self.safe_string(existingSubscription, 'symbol')
        subTimeframe = self.safe_string(existingSubscription, 'timeframe')
        # due to nature of coinex response can only watch one symbol at a time
        if watchOHLCVWarning and existingSubscription is not None and (subSymbol != symbol or subTimeframe != timeframe):
            raise ExchangeError(self.id + ' watchOHLCV() can only watch one symbol and timeframe at a time. To supress self warning set watchOHLCVWarning to False in options')
        timeframes = self.safe_value(self.options, 'timeframes', {})
        subscribe: dict = {
            'method': 'kline.subscribe',
            'id': self.request_id(),
            'params': [
                market['id'],
                self.safe_integer(timeframes, timeframe),
            ],
        }
        subscription: dict = {
            'symbol': symbol,
            'timeframe': timeframe,
        }
        request = self.deep_extend(subscribe, params)
        ohlcvs = await self.watch(url, messageHash, request, messageHash, subscription)
        if self.newUpdates:
            limit = ohlcvs.getLimit(symbol, limit)
        return self.filter_by_since_limit(ohlcvs, since, limit, 0)

    async def fetch_ohlcv_ws(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket005_kline_query
        query 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 query OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the exchange API endpoint
        :param int|None params['end']: the end time for spot markets, self.seconds() is set
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        type, query = self.handle_market_type_and_params('fetchOHLCV', market, params)
        url = self.urls['api']['ws'][type]
        symbol = market['symbol']
        messageHash = 'ohlcv'
        timeframes = self.safe_value(self.options, 'timeframes', {})
        timeframe = self.safe_string(timeframes, timeframe, timeframe)
        if since is None:
            since = 1640995200  # January 1, 2022
        id = self.request_id()
        subscribe: dict = {
            'method': 'kline.query',
            'params': [
                market['id'],
                self.parse_to_int(since / 1000),
                self.safe_integer(params, 'end', self.seconds()),
                self.parse_to_int(timeframe),
            ],
            'id': id,
        }
        subscription: dict = {
            'id': id,
            'future': messageHash,
        }
        subscriptionHash = id
        request = self.deep_extend(subscribe, query)
        ohlcvs = await self.watch(url, messageHash, request, subscriptionHash, subscription)
        return self.filter_by_since_limit(ohlcvs, since, limit, 0)

    def handle_delta(self, bookside, delta):
        bidAsk = self.parse_bid_ask(delta, 0, 1)
        bookside.storeArray(bidAsk)

    def handle_deltas(self, bookside, deltas):
        for i in range(0, len(deltas)):
            self.handle_delta(bookside, deltas[i])

    def handle_order_book(self, client: Client, message):
        #
        #     {
        #         "method": "depth.update",
        #         "params": [
        #             False,
        #             {
        #                 "asks": [
        #                     ["46350.52", "1.07871851"],
        #                     ...
        #                 ],
        #                 "bids": [
        #                     ["46349.61", "0.04000000"],
        #                     ...
        #                 ],
        #                 "last": "46349.93",
        #                 "time": 1639987469166,
        #                 "checksum": 1533284725
        #             },
        #             "BTCUSDT"
        #         ],
        #         "id": null
        #     }
        #
        isSwap = client.url.find('perpetual') >= 0
        marketType = 'swap' if isSwap else 'spot'
        params = self.safe_value(message, 'params', [])
        fullOrderBook = self.safe_value(params, 0)
        orderbook = self.safe_value(params, 1)
        marketId = self.safe_string(params, 2)
        market = self.safe_market(marketId, None, None, marketType)
        symbol = market['symbol']
        name = 'orderbook'
        messageHash = name + ':' + symbol
        timestamp = self.safe_integer(orderbook, 'time')
        currentOrderBook = self.safe_value(self.orderbooks, symbol)
        if fullOrderBook:
            snapshot = self.parse_order_book(orderbook, symbol, timestamp)
            if currentOrderBook is None:
                orderbook = self.order_book(snapshot)
                self.orderbooks[symbol] = orderbook
            else:
                orderbook = self.orderbooks[symbol]
                orderbook.reset(snapshot)
        else:
            asks = self.safe_value(orderbook, 'asks', [])
            bids = self.safe_value(orderbook, 'bids', [])
            self.handle_deltas(currentOrderBook['asks'], asks)
            self.handle_deltas(currentOrderBook['bids'], bids)
            currentOrderBook['nonce'] = timestamp
            currentOrderBook['timestamp'] = timestamp
            currentOrderBook['datetime'] = self.iso8601(timestamp)
            self.orderbooks[symbol] = currentOrderBook
        # self.checkOrderBookChecksum(self.orderbooks[symbol])
        client.resolve(self.orderbooks[symbol], messageHash)

    async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        await self.load_markets()
        await self.authenticate(params)
        messageHash = 'orders'
        market = None
        type, query = self.handle_market_type_and_params('watchOrders', market, params)
        message: dict = {
            'method': 'order.subscribe',
            'id': self.request_id(),
        }
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            message['params'] = [market['id']]
            messageHash += ':' + symbol
        else:
            message['params'] = []
        url = self.urls['api']['ws'][type]
        request = self.deep_extend(message, query)
        orders = await self.watch(url, messageHash, request, messageHash, request)
        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
        #
        #      {
        #          "method": "order.update",
        #          "params": [
        #              1,
        #              {
        #                  "id": 77782469357,
        #                  "type": 1,
        #                  "side": 2,
        #                  "user": 1849116,
        #                  "account": 0,
        #                  "option": 2,
        #                  "ctime": 1653961043.048967,
        #                  "mtime": 1653961043.048967,
        #                  "market": "BTCUSDT",
        #                  "source": "web",
        #                  "client_id": '',
        #                  "price": "1.00",
        #                  "amount": "1.00000000",
        #                  "taker_fee": "0.0020",
        #                  "maker_fee": "0.0020",
        #                  "left": "1.00000000",
        #                  "deal_stock": "0",
        #                  "deal_money": "0",
        #                  "money_fee": "0",
        #                  "stock_fee": "0",
        #                  "asset_fee": "0",
        #                  "fee_discount": "1",
        #                  "last_deal_amount": "0",
        #                  "last_deal_price": "0",
        #                  "last_deal_time": 0,
        #                  "last_deal_id": 0,
        #                  "last_role": 0,
        #                  "fee_asset": null,
        #                  "stop_id": 0
        #              }
        #          ],
        #          "id": null
        #      }
        #
        #  swap
        #
        #      {
        #          "method": "order.update",
        #          "params": [
        #              1,
        #              {
        #                  "order_id": 23423462821,
        #                  "position_id": 0,
        #                  "stop_id": 0,
        #                  "market": "BTCUSDT",
        #                  "type": 1,
        #                  "side": 2,
        #                  "target": 0,
        #                  "effect_type": 1,
        #                  "user_id": 1849116,
        #                  "create_time": 1653961509.25049,
        #                  "update_time": 1653961509.25049,
        #                  "source": "web",
        #                  "price": "1.00",
        #                  "amount": "1.0000",
        #                  "taker_fee": "0.00050",
        #                  "maker_fee": "0.00030",
        #                  "left": "1.0000",
        #                  "deal_stock": "0.00000000000000000000",
        #                  "deal_fee": "0.00000000000000000000",
        #                  "deal_profit": "0.00000000000000000000",
        #                  "last_deal_amount": "0.00000000000000000000",
        #                  "last_deal_price": "0.00000000000000000000",
        #                  "last_deal_time": 0,
        #                  "last_deal_id": 0,
        #                  "last_deal_type": 0,
        #                  "last_deal_role": 0,
        #                  "client_id": '',
        #                  "fee_asset": '',
        #                  "fee_discount": "0.00000000000000000000",
        #                  "deal_asset_fee": "0.00000000000000000000",
        #                  "leverage": "3",
        #                  "position_type": 2
        #              }
        #          ],
        #          "id": null
        #      }
        #
        params = self.safe_value(message, 'params', [])
        order = self.safe_value(params, 1, {})
        parsedOrder = self.parse_ws_order(order)
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        orders = self.orders
        orders.append(parsedOrder)
        messageHash = 'orders'
        client.resolve(self.orders, messageHash)
        messageHash += ':' + parsedOrder['symbol']
        client.resolve(self.orders, messageHash)

    def parse_ws_order(self, order, market=None):
        #
        #  spot
        #
        #       {
        #           "id": 77782469357,
        #           "type": 1,
        #           "side": 2,
        #           "user": 1849116,
        #           "account": 0,
        #           "option": 2,
        #           "ctime": 1653961043.048967,
        #           "mtime": 1653961043.048967,
        #           "market": "BTCUSDT",
        #           "source": "web",
        #           "client_id": '',
        #           "price": "1.00",
        #           "amount": "1.00000000",
        #           "taker_fee": "0.0020",
        #           "maker_fee": "0.0020",
        #           "left": "1.00000000",
        #           "deal_stock": "0",
        #           "deal_money": "0",
        #           "money_fee": "0",
        #           "stock_fee": "0",
        #           "asset_fee": "0",
        #           "fee_discount": "1",
        #           "last_deal_amount": "0",
        #           "last_deal_price": "0",
        #           "last_deal_time": 0,
        #           "last_deal_id": 0,
        #           "last_role": 0,
        #           "fee_asset": null,
        #           "stop_id": 0
        #       }
        #
        #  swap
        #
        #      {
        #          "order_id": 23423462821,
        #          "position_id": 0,
        #          "stop_id": 0,
        #          "market": "BTCUSDT",
        #          "type": 1,
        #          "side": 2,
        #          "target": 0,
        #          "effect_type": 1,
        #          "user_id": 1849116,
        #          "create_time": 1653961509.25049,
        #          "update_time": 1653961509.25049,
        #          "source": "web",
        #          "price": "1.00",
        #          "amount": "1.0000",
        #          "taker_fee": "0.00050",
        #          "maker_fee": "0.00030",
        #          "left": "1.0000",
        #          "deal_stock": "0.00000000000000000000",
        #          "deal_fee": "0.00000000000000000000",
        #          "deal_profit": "0.00000000000000000000",
        #          "last_deal_amount": "0.00000000000000000000",
        #          "last_deal_price": "0.00000000000000000000",
        #          "last_deal_time": 0,
        #          "last_deal_id": 0,
        #          "last_deal_type": 0,
        #          "last_deal_role": 0,
        #          "client_id": '',
        #          "fee_asset": '',
        #          "fee_discount": "0.00000000000000000000",
        #          "deal_asset_fee": "0.00000000000000000000",
        #          "leverage": "3",
        #          "position_type": 2
        #      }
        #
        #  order.update_stop
        #
        #       {
        #           "id": 78006745870,
        #           "type": 1,
        #           "side": 2,
        #           "user": 1849116,
        #           "account": 1,
        #           "option": 70,
        #           "direction": 1,
        #           "ctime": 1654171725.131976,
        #           "mtime": 1654171725.131976,
        #           "market": "BTCUSDT",
        #           "source": "web",
        #           "client_id": '',
        #           "stop_price": "1.00",
        #           "price": "1.00",
        #           "amount": "1.00000000",
        #           "taker_fee": "0.0020",
        #           "maker_fee": "0.0020",
        #           "fee_discount": "1",
        #           "fee_asset": null,
        #           "status": 0
        #       }
        #
        timestamp = self.safe_timestamp_2(order, 'update_time', 'mtime')
        marketId = self.safe_string(order, 'market')
        typeCode = self.safe_string(order, 'type')
        type = self.safe_string({
            '1': 'limit',
            '2': 'market',
        }, typeCode)
        sideCode = self.safe_string(order, 'side')
        side = self.safe_string({
            '1': 'sell',
            '2': 'buy',
        }, sideCode)
        remaining = self.safe_string(order, 'left')
        amount = self.safe_string(order, 'amount')
        status = self.safe_string(order, 'status')
        defaultType = self.safe_string(self.options, 'defaultType')
        market = self.safe_market(marketId, market, None, defaultType)
        cost = self.safe_string(order, 'deal_money')
        filled = self.safe_string(order, 'deal_stock')
        average = None
        if market['swap']:
            leverage = self.safe_string(order, 'leverage')
            cost = Precise.string_div(filled, leverage)
            average = Precise.string_div(filled, amount)
            filled = None
        fee = None
        feeCost = self.omit_zero(self.safe_string(order, 'money_fee'))
        if feeCost is not None:
            feeCurrencyId = self.safe_string(order, 'fee_asset', market['quote'])
            fee = {
                'currency': self.safe_currency_code(feeCurrencyId),
                'cost': feeCost,
            }
        return self.safe_order({
            'info': order,
            'id': self.safe_string_2(order, 'order_id', 'id'),
            'clientOrderId': self.safe_string(order, 'client_id'),
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': self.safe_timestamp(order, 'last_deal_time'),
            'symbol': market['symbol'],
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': self.safe_string(order, 'price'),
            'stopPrice': self.safe_string(order, 'stop_price'),
            'triggerPrice': self.safe_string(order, 'stop_price'),
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'cost': cost,
            'average': average,
            'status': self.parse_ws_order_status(status),
            'fee': fee,
            'trades': None,
        }, market)

    def parse_ws_order_status(self, status):
        statuses: dict = {
            '0': 'pending',
            '1': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def handle_message(self, client: Client, message):
        error = self.safe_value(message, 'error')
        if error is not None:
            raise ExchangeError(self.id + ' ' + self.json(error))
        method = self.safe_string(message, 'method')
        handlers: dict = {
            'state.update': self.handle_ticker,
            'asset.update': self.handle_balance,
            'deals.update': self.handle_trades,
            'depth.update': self.handle_order_book,
            'order.update': self.handle_orders,
            'kline.update': self.handle_ohlcv,
            'order.update_stop': self.handle_orders,
        }
        handler = self.safe_value(handlers, method)
        if handler is not None:
            handler(client, message)
            return
        self.handle_subscription_status(client, message)

    def handle_authentication_message(self, client: Client, message):
        #
        #     {
        #         "error": null,
        #         "result": {
        #             "status": "success"
        #         },
        #         "id": 1
        #     }
        #
        messageHashSpot = 'authenticated:spot'
        messageHashSwap = 'authenticated:swap'
        # client.resolve(message, messageHashSpot)
        # client.resolve(message, messageHashSwap)
        spotFuture = self.safe_value(client.futures, messageHashSpot)
        spotFuture.resolve(True)
        swapFutures = self.safe_value(client.futures, messageHashSwap)
        swapFutures.resolve(True)
        return message

    def handle_subscription_status(self, client: Client, message):
        id = self.safe_integer(message, 'id')
        subscription = self.safe_value(client.subscriptions, id)
        if subscription is not None:
            futureIndex = self.safe_string(subscription, 'future')
            if futureIndex == 'ohlcv':
                self.handle_ohlcv(client, message)
                return
            future = self.safe_value(client.futures, futureIndex)
            if future is not None:
                future.resolve(True)
            del client.subscriptions[id]

    async def authenticate(self, params={}):
        type = None
        type, params = self.handle_market_type_and_params('authenticate', None, params)
        url = self.urls['api']['ws'][type]
        client = self.client(url)
        time = self.milliseconds()
        isSpot = (type == 'spot')
        spotMessageHash = 'authenticated:spot'
        swapMessageHash = 'authenticated:swap'
        messageHash = spotMessageHash if isSpot else swapMessageHash
        future = client.future(messageHash)
        authenticated = self.safe_value(client.subscriptions, messageHash)
        if type == 'spot':
            if authenticated is not None:
                return await future
            requestId = self.request_id()
            subscribe: dict = {
                'id': requestId,
                'future': spotMessageHash,
            }
            signData = 'access_id=' + self.apiKey + '&tonce=' + self.number_to_string(time) + '&secret_key=' + self.secret
            hash = self.hash(self.encode(signData), 'md5')
            request: dict = {
                'method': 'server.sign',
                'params': [
                    self.apiKey,
                    hash.upper(),
                    time,
                ],
                'id': requestId,
            }
            self.watch(url, messageHash, request, requestId, subscribe)
            client.subscriptions[messageHash] = True
            return await future
        else:
            if authenticated is not None:
                return await future
            requestId = self.request_id()
            subscribe: dict = {
                'id': requestId,
                'future': swapMessageHash,
            }
            signData = 'access_id=' + self.apiKey + '&timestamp=' + self.number_to_string(time) + '&secret_key=' + self.secret
            hash = self.hash(self.encode(signData), 'sha256', 'hex')
            request: dict = {
                'method': 'server.sign',
                'params': [
                    self.apiKey,
                    hash.lower(),
                    time,
                ],
                'id': requestId,
            }
            self.watch(url, messageHash, request, requestId, subscribe)
            client.subscriptions[messageHash] = True
            return await future
