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

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

from ccxt.pro.base.exchange import Exchange
import ccxt.async_support
from ccxt.pro.base.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
import hashlib
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import NotSupported


class mexc(Exchange, ccxt.async_support.mexc):

    def describe(self):
        return self.deep_extend(super(mexc, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchMyTrades': True,
                'watchOHLCV': True,
                'watchOrderBook': True,
                'watchOrders': True,
                'watchTicker': True,
                'watchTickers': False,  # for now
                'watchTrades': True,
            },
            'urls': {
                'api': {
                    'ws': {
                        'spot': 'wss://wbs.mexc.com/raw/ws',
                        'swap': 'wss://contract.mexc.com/ws',
                    },
                },
            },
            'options': {
                'timeframes': {
                    '1m': 'Min1',
                    '5m': 'Min5',
                    '15m': 'Min15',
                    '30m': 'Min30',
                    '1h': 'Min60',
                    '4h': 'Hour4',
                    '8h': 'Hour8',
                    '1d': 'Day1',
                    '1w': 'Week1',
                    '1M': 'Month1',
                },
            },
            'streaming': {
                'ping': self.ping,
                'keepAlive': 10000,
            },
            'exceptions': {
                'ws': {
                    'exact': {
                        'signature validation failed': AuthenticationError,  # {channel: 'sub.personal', msg: 'signature validation failed'}
                    },
                    'broad': {
                        'Contract not exists': BadSymbol,  # {channel: 'rs.error', data: 'Contract not exists', ts: 1651509181535}
                    },
                },
            },
        })

    async def watch_ticker(self, symbol, params={}):
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the mexc api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        channel = 'sub.ticker'
        messageHash = 'ticker' + ':' + symbol
        requestParams = {
            'symbol': market['id'],
        }
        if market['type'] == 'spot':
            raise NotSupported(self.id + ' watchTicker does not support spot markets')
        else:
            return await self.watch_swap_public(messageHash, channel, requestParams, params)

    def handle_ticker(self, client, message):
        #
        #     {
        #         channel: 'push.ticker',
        #         data: {
        #           amount24: 491939387.90105,
        #           ask1: 39530.5,
        #           bid1: 39530,
        #           contractId: 10,
        #           fairPrice: 39533.4,
        #           fundingRate: 0.00015,
        #           high24Price: 40310.5,
        #           holdVol: 187680157,
        #           indexPrice: 39538.5,
        #           lastPrice: 39530,
        #           lower24Price: 38633,
        #           maxBidPrice: 43492,
        #           minAskPrice: 35584.5,
        #           riseFallRate: 0.0138,
        #           riseFallValue: 539.5,
        #           symbol: 'BTC_USDT',
        #           timestamp: 1651160401009,
        #           volume24: 125171687
        #         },
        #         symbol: 'BTC_USDT',
        #         ts: 1651160401009
        #     }
        #
        data = self.safe_value(message, 'data', {})
        marketId = self.safe_string(message, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        ticker = self.parse_ticker(data, market)
        self.tickers[symbol] = ticker
        messageHash = 'ticker:' + symbol
        client.resolve(ticker, messageHash)
        return message

    async def watch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the mexc api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        requestParams = {}
        symbol = market['symbol']
        type = market['type']
        timeframes = self.safe_value(self.options, 'timeframes', {})
        timeframeValue = self.safe_string(timeframes, timeframe)
        channel = 'sub.kline'
        messageHash = 'kline' + ':' + timeframeValue + ':' + symbol
        requestParams['symbol'] = market['id']
        requestParams['interval'] = timeframeValue
        if since is not None:
            requestParams['start'] = since
        ohlcv = None
        if type == 'spot':
            ohlcv = await self.watch_spot_public(messageHash, channel, requestParams, params)
        else:
            ohlcv = await self.watch_swap_public(messageHash, channel, requestParams, params)
        if self.newUpdates:
            limit = ohlcv.getLimit(symbol, limit)
        return self.filter_by_since_limit(ohlcv, since, limit, 0, True)

    def handle_ohlcv(self, client, message):
        # spot
        #   {
        #       symbol: 'BTC_USDT',
        #       data: {
        #         symbol: 'BTC_USDT',
        #         interval: 'Min1',
        #         t: 1651230720,
        #         o: 38870.18,
        #         c: 38867.55,
        #         h: 38873.19,
        #         l: 38867.05,
        #         v: 71031.87886502,
        #         q: 1.827357,
        #         e: 38867.05,
        #         rh: 38873.19,
        #         rl: 38867.05
        #       },
        #       channel: 'push.kline',
        #       symbol_display: 'BTC_USDT'
        #   }
        #
        # swap
        #
        #   {
        #       channel: 'push.kline',
        #       data: {
        #         a: 325653.3287,
        #         c: 38839,
        #         h: 38909.5,
        #         interval: 'Min1',
        #         l: 38833,
        #         o: 38901.5,
        #         q: 83808,
        #         rc: 38839,
        #         rh: 38909.5,
        #         rl: 38833,
        #         ro: 38909.5,
        #         symbol: 'BTC_USDT',
        #         t: 1651230660
        #       },
        #       symbol: 'BTC_USDT',
        #       ts: 1651230713067
        #   }
        #
        marketId = self.safe_string(message, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        data = self.safe_value(message, 'data', {})
        interval = self.safe_string(data, 'interval')
        messageHash = 'kline' + ':' + interval + ':' + symbol
        timeframes = self.safe_value(self.options, 'timeframes', {})
        timeframe = self.find_timeframe(interval, timeframes)
        parsed = self.parse_ws_ohlcv(data, 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

    def parse_ws_ohlcv(self, ohlcv, market=None):
        #
        # spot
        #    {
        #       symbol: 'BTC_USDT',
        #       interval: 'Min1',
        #       t: 1651230720,
        #       o: 38870.18,
        #       c: 38867.55,
        #       h: 38873.19,
        #       l: 38867.05,
        #       v: 71031.87886502,
        #       q: 1.827357,
        #       e: 38867.05,
        #       rh: 38873.19,
        #       rl: 38867.05
        #     }
        #
        # swap
        #
        #   {
        #        a: 325653.3287,
        #        c: 38839,
        #        h: 38909.5,
        #        interval: 'Min1',
        #        l: 38833,
        #        o: 38901.5,
        #        q: 83808,
        #        rc: 38839,
        #        rh: 38909.5,
        #        rl: 38833,
        #        ro: 38909.5,
        #        symbol: 'BTC_USDT',
        #        t: 1651230660
        #    }
        #
        return [
            self.safe_integer_product(ohlcv, 't', 1000),
            self.safe_number(ohlcv, 'o'),
            self.safe_number(ohlcv, 'h'),
            self.safe_number(ohlcv, 'l'),
            self.safe_number(ohlcv, 'c'),
            self.safe_number_2(ohlcv, 'v', 'q'),
        ]

    async def watch_order_book(self, symbol, limit=None, params={}):
        """
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the mexc api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        messageHash = 'orderbook' + ':' + symbol
        requestParams = {
            'symbol': market['id'],
        }
        if limit is not None:
            if limit != 5 and limit != 10 and limit != 20:
                raise BadRequest(self.id + ' watchOrderBook limit parameter cannot be different from 5, 10 or 20')
        else:
            limit = 20
        orderbook = None
        if market['type'] == 'swap':
            channel = 'sub.depth'
            requestParams['compress'] = True
            requestParams['limit'] = limit
            orderbook = await self.watch_swap_public(messageHash, channel, requestParams, params)
        else:
            channel = 'sub.limit.depth'
            requestParams['depth'] = limit
            orderbook = await self.watch_spot_public(messageHash, channel, requestParams, params)
        return orderbook.limit()

    def handle_order_book(self, client, message):
        #
        # swap
        #  {
        #      "channel":"push.depth",
        #      "data":{
        #         "asks":[
        #            [
        #               39146.5,
        #               11264,
        #               1
        #            ]
        #         ],
        #         "bids":[
        #            [
        #               39144,
        #               35460,
        #               1
        #            ]
        #         ],
        #         "end":4895965272,
        #         "begin":4895965271
        #      },
        #      "symbol":"BTC_USDT",
        #      "ts":1651239652372
        #  }
        #
        # spot
        # {
        #     "channel":"push.limit.depth",
        #     "symbol":"BTC_USDT",
        #     "data":{
        #        "asks":[
        #           [
        #              "38694.68",
        #              "2.250996"
        #           ],
        #        ],
        #        "bids":[
        #           [
        #              "38694.65",
        #              "0.783084"
        #           ],
        #        ]
        #     },
        #     "depth":5,
        #     "version":"1170951528"
        #  }
        #
        marketId = self.safe_string(message, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        data = self.safe_value(message, 'data')
        timestamp = self.safe_integer(message, 'ts')
        snapshot = self.parse_order_book(data, symbol, timestamp)
        nonce = self.safe_number(data, 'end')
        if nonce is None:
            nonce = self.safe_number(message, 'version')
        snapshot['nonce'] = nonce
        orderbook = self.safe_value(self.orderbooks, symbol)
        if orderbook is None:
            orderbook = self.order_book(snapshot)
            self.orderbooks[symbol] = orderbook
        else:
            # spot channels always return entire snapshots
            # whereas swap channels return incremental updates
            # after the first message
            if market['type'] == 'spot':
                orderbook.reset(snapshot)
            else:
                self.handle_order_book_message(client, message, orderbook)
        messageHash = 'orderbook' + ':' + symbol
        client.resolve(orderbook, messageHash)

    def handle_order_book_message(self, client, message, orderbook):
        #
        #  {
        #      "channel":"push.depth",
        #      "data":{
        #         "asks":[
        #            [
        #               39146.5,
        #               11264,
        #               1
        #            ]
        #         ],
        #         "bids":[
        #            [
        #               39144,
        #               35460,
        #               1
        #            ]
        #         ],
        #         "end":4895965272,
        #         "begin":4895965271
        #      },
        #      "symbol":"BTC_USDT",
        #      "ts":1651239652372
        #
        data = self.safe_value(message, 'data', {})
        nonce = self.safe_number(data, 'end')
        asks = self.safe_value(data, 'asks', [])
        bids = self.safe_value(data, 'bids', [])
        self.handle_deltas(orderbook['asks'], asks)
        self.handle_deltas(orderbook['bids'], bids)
        timestamp = self.safe_integer(message, 'ts')
        marketId = self.safe_string(message, 'symbol')
        symbol = self.safe_symbol(marketId)
        orderbook['nonce'] = nonce
        orderbook['symbol'] = symbol
        orderbook['timestamp'] = timestamp
        orderbook['datetime'] = self.iso8601(timestamp)
        return orderbook

    def handle_delta(self, bookside, delta):
        #
        #  [
        #     39146.5,
        #     11264,
        #     1
        #  ]
        #
        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])

    async def watch_trades(self, symbol, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the mexc api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        channel = 'sub.deal'
        messageHash = 'trades' + ':' + symbol
        requestParams = {
            'symbol': market['id'],
        }
        trades = None
        if market['type'] == 'spot':
            trades = await self.watch_spot_public(messageHash, channel, requestParams, params)
        else:
            trades = await self.watch_swap_public(messageHash, channel, requestParams, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def handle_trades(self, client, message):
        #
        # swap trades
        #     {
        #         "channel":"push.deal",
        #         "data":{
        #             "M":1,
        #             "O":1,
        #             "T":1,
        #             "p":6866.5,
        #             "t":1587442049632,
        #             "v":2096
        #         },
        #         "symbol":"BTC_USDT",
        #         "ts":1587442022003
        #     }
        #
        # spot trades
        #
        #    {
        #        "symbol":"BTC_USDT",
        #        "data":{
        #           "deals":[
        #              {
        #                 "t":1651227552839,
        #                 "p":"39190.01",
        #                 "q":"0.001357",
        #                 "T":2
        #              }
        #           ]
        #        },
        #        "channel":"push.deal"
        #     }
        #
        marketId = self.safe_string(message, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['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
        data = self.safe_value(message, 'data', {})
        trades = None
        if 'deals' in data:
            trades = self.safe_value(data, 'deals', [])
        else:
            trades = [data]
        for j in range(0, len(trades)):
            parsedTrade = self.parse_ws_trade(trades[j], market)
            stored.append(parsedTrade)
        messageHash = 'trades' + ':' + symbol
        client.resolve(stored, messageHash)

    async def watch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        watches information on multiple trades made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the mexc api endpoint
        :returns [dict]: a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure
        """
        await self.load_markets()
        messageHash = 'trade'
        market = None
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash += ':' + market['symbol']
        type = None
        type, params = self.handle_market_type_and_params('watchMyTrades', market, params)
        trades = None
        if type == 'spot':
            raise NotSupported(self.id + ' watchMyTrades does not support spot markets')
        else:
            trades = await self.watch_swap_private(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_trade(self, client, message, subscription=None):
        #
        # swap trade
        #    {
        #        channel: 'push.personal.order.deal',
        #        data: {
        #          category: 1,
        #          fee: 0.00060288,
        #          feeCurrency: 'USDT',
        #          id: '311655369',
        #          isSelf: False,
        #          orderId: '276461245253669888',
        #          positionMode: 1,
        #          price: 100.48,
        #          profit: 0.0003,
        #          side: 4,
        #          symbol: 'LTC_USDT',
        #          taker: True,
        #          timestamp: 1651583897276,
        #          vol: 1
        #        },
        #        ts: 1651583897291
        #    }
        #
        data = self.safe_value(message, 'data', {})
        marketId = self.safe_string(data, 'symbol')
        market = self.safe_market(marketId)
        parsed = self.parse_ws_trade(data, market)
        if self.myTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.myTrades = ArrayCacheBySymbolById(limit)
        trades = self.myTrades
        trades.append(parsed)
        channel = 'trade'
        # non-symbol specific
        client.resolve(trades, channel)
        channel += ':' + market['symbol']
        client.resolve(trades, channel)

    def parse_ws_trade(self, trade, market=None):
        #
        # public spot
        #    {
        #       "t":1651227552839,
        #       "p":"39190.01",
        #       "q":"0.001357",
        #       "T":2
        #    }
        #
        # public swap
        #
        #   {
        #     "M":1,
        #     "O":1,
        #     "T":1,
        #     "p":6866.5,
        #     "t":1587442049632,
        #     "v":2096
        #   }
        #
        # private swap
        #   {
        #       category: 1,
        #       fee: 0.00060288,2
        #       feeCurrency: 'USDT',
        #       id: '311655369',
        #       isSelf: False,
        #       orderId: '276461245253669888',
        #       positionMode: 1,
        #       price: 100.48,
        #       profit: 0.0003,
        #       side: 4,
        #       symbol: 'LTC_USDT',
        #       taker: True,
        #       timestamp: 1651583897276,
        #       vol: 1
        #   }
        #
        timestamp = self.safe_integer_2(trade, 'timestamp', 't')
        marketId = self.safe_string(trade, 'symbol')
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        priceString = self.safe_string_2(trade, 'price', 'p')
        amountString = self.safe_string_2(trade, 'vol', 'q')
        if amountString is None:
            amountString = self.safe_string(trade, 'v')
        rawSide = self.safe_string(trade, 'T')
        side = None
        if rawSide is None:
            rawSide = self.safe_string(trade, 'side')
            side = self.parse_swap_side(rawSide)
        else:
            side = 'buy' if (rawSide == '1') else 'sell'
        id = self.safe_string(trade, 'id')
        if id is None:
            id = str(timestamp) + '-' + market['id'] + '-' + amountString
        feeCostString = self.safe_string(trade, 'fee')
        fee = None
        if feeCostString is not None:
            feeCurrencyId = self.safe_string(trade, 'feeCurrency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCostString,
                'currency': feeCurrencyCode,
            }
        orderId = self.safe_string(trade, 'orderId')
        isTaker = self.safe_value(trade, 'taker', True)
        takerOrMaker = 'taker' if isTaker else 'maker'
        return self.safe_trade({
            'info': trade,
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': fee,
        }, market)

    async def watch_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        watches information on multiple orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the mexc api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        await self.load_markets()
        messageHash = 'order'
        market = None
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash += ':' + market['symbol']
        type = None
        type, params = self.handle_market_type_and_params('watchOrders', market, params)
        orders = None
        if type == 'spot':
            orders = await self.watch_spot_private(messageHash, params)
        else:
            orders = await self.watch_swap_private(messageHash, params)
        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, message, subscription=None):
        #
        # spot order
        #     {
        #         symbol: 'LTC_USDT',
        #         data: {
        #           price: 100.25,
        #           quantity: 0.0498,
        #           amount: 4.99245,
        #           remainAmount: 0.01245,
        #           remainQuantity: 0,
        #           remainQ: 0,
        #           remainA: 0,
        #           id: '0b1bf3a33916499f8d1a711a7d5a6fc4',
        #           status: 2,
        #           tradeType: 1,
        #           orderType: 3,
        #           createTime: 1651499416000,
        #           isTaker: 1,
        #           symbolDisplay: 'LTC_USDT',
        #           clientOrderId: ''
        #         },
        #         channel: 'push.personal.order',
        #         eventTime: 1651499416639,
        #         symbol_display: 'LTC_USDT'
        #     }
        #
        # spot trigger
        #
        #   {
        #       symbol: 'LTC_USDT',
        #       data: {
        #         id: '048dddc31b9a451084b8db8b561a0e33',
        #         market: 'USDT',
        #         currency: 'LTC',
        #         triggerType: 'LE',
        #         triggerPrice: 80,
        #         tradeType: 'BUY',
        #         orderType: 100,
        #         price: 70,
        #         quantity: 0.0857,
        #         state: 'NEW',
        #         createTime: 1651578450223,
        #         currencyDisplay: 'LTC'
        #       },
        #       channel: 'push.personal.trigger.order',
        #       symbol_display: 'LTC_USDT'
        #     }
        #
        #  swap order
        # {
        #     channel: 'push.personal.order',
        #     data: {
        #       category: 1,
        #       createTime: 1651500368131,
        #       dealAvgPrice: 0,
        #       dealVol: 0,
        #       errorCode: 0,
        #       externalOid: '_m_4a78c91ca8be4c4580d94e637b1f70d1',
        #       feeCurrency: 'USDT',
        #       leverage: 1,
        #       makerFee: 0,
        #       openType: 2,
        #       orderId: '276110898672819715',
        #       orderMargin: 0.5006,
        #       orderType: 1,
        #       positionId: 0,
        #       positionMode: 1,
        #       price: 50,
        #       profit: 0,
        #       remainVol: 1,
        #       side: 1,
        #       state: 2,
        #       symbol: 'LTC_USDT',
        #       takerFee: 0,
        #       updateTime: 1651500368142,
        #       usedMargin: 0,
        #       version: 1,
        #       vol: 1
        #     },
        #     ts: 1651500368149
        #   }
        #
        data = self.safe_value(message, 'data', {})
        marketId = self.safe_string(message, 'symbol')
        if marketId is None:
            marketId = self.safe_string(data, 'symbol')
        market = self.safe_market(marketId)
        parsed = self.parse_ws_order(data, market)
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        orders = self.orders
        orders.append(parsed)
        channel = 'order'
        # non-symbol specific
        client.resolve(orders, channel)
        channel += ':' + market['symbol']
        client.resolve(orders, channel)

    def parse_ws_order(self, order, market=None):
        #
        # spot order
        #     {
        #           price: 100.25,
        #           quantity: 0.0498,
        #           amount: 4.99245,
        #           remainAmount: 0.01245,
        #           remainQuantity: 0,
        #           remainQ: 0,
        #           remainA: 0,
        #           id: '0b1bf3a33916499f8d1a711a7d5a6fc4',
        #           status: 2,
        #           tradeType: 1,  # 1 = buy, 2 = sell
        #           orderType: 3,  # 1 = limit, 3 = market, 100 = 'limit
        #           createTime: 1651499416000,
        #           isTaker: 1,
        #           symbolDisplay: 'LTC_USDT',
        #           clientOrderId: ''
        #     }
        #
        # spot trigger order
        #    {
        #        id: '048dddc31b9a451084b8db8b561a0e33',
        #        market: 'USDT',
        #        currency: 'LTC',
        #        triggerType: 'LE',
        #        triggerPrice: 80,
        #        tradeType: 'BUY',
        #        orderType: 100,
        #        price: 70,
        #        quantity: 0.0857,
        #        state: 'NEW',
        #        createTime: 1651578450223,
        #        currencyDisplay: 'LTC'
        #    }
        #
        #  swap order
        #   {
        #       category: 1,
        #       createTime: 1651500368131,
        #       dealAvgPrice: 0,
        #       dealVol: 0,
        #       errorCode: 0,
        #       externalOid: '_m_4a78c91ca8be4c4580d94e637b1f70d1',
        #       feeCurrency: 'USDT',
        #       leverage: 1,
        #       makerFee: 0,
        #       openType: 2,
        #       orderId: '276110898672819715',
        #       orderMargin: 0.5006,
        #       orderType: 1,  # 5 = market, 1 = limit,
        #       positionId: 0,
        #       positionMode: 1,
        #       price: 50,
        #       profit: 0,
        #       remainVol: 1,
        #       side: 1,
        #       state: 2,
        #       symbol: 'LTC_USDT',
        #       takerFee: 0,
        #       updateTime: 1651500368142,
        #       usedMargin: 0,
        #       version: 1,
        #       vol: 1
        #     }
        #
        id = self.safe_string_2(order, 'orderId', 'id')
        state = self.safe_string_2(order, 'state', 'status')
        timestamp = self.safe_integer(order, 'createTime')
        price = self.safe_string(order, 'price')
        amount = self.safe_string_2(order, 'quantity', 'vol')
        remaining = self.safe_string(order, 'remainQuantity')
        filled = self.safe_string(order, 'dealVol')
        cost = self.safe_string(order, 'amount')
        avgPrice = self.safe_string(order, 'dealAvgPrice')
        marketId = self.safe_string_2(order, 'symbol', 'symbolDisplay')
        symbol = self.safe_symbol(marketId, market, '_')
        sideCheck = self.safe_string(order, 'side')
        side = self.parse_swap_side(sideCheck)
        if side is None:
            tradeType = self.safe_string_lower(order, 'tradeType')
            if (tradeType == 'ask') or (tradeType == '2'):
                side = 'sell'
            elif (tradeType == 'bid') or (tradeType == '1'):
                side = 'buy'
            else:
                side = tradeType
        status = self.parse_ws_order_status(state, market)
        clientOrderId = self.safe_string_2(order, 'client_order_id', 'orderId')
        if clientOrderId == '':
            clientOrderId = None
        rawType = self.safe_string(order, 'orderType')
        isMarket = (rawType == '3') or (rawType == '5')
        type = 'market' if isMarket else 'limit'
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': self.safe_integer(order, 'updateTime'),
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'average': avgPrice,
            'amount': amount,
            'cost': cost,
            'filled': filled,
            'remaining': remaining,
            'fee': None,
            'trades': None,
            'info': order,
        }, market)

    def parse_swap_side(self, side):
        sides = {
            '1': 'open long',
            '2': 'close short',
            '3': 'open short',
            '4': 'close long',
        }
        return self.safe_string(sides, side)

    def parse_ws_order_status(self, status, market=None):
        statuses = {}
        if market['type'] == 'spot':
            statuses = {
                # spot limit/market
                '1': 'open',
                '2': 'closed',
                '3': 'open',
                '4': 'canceled',
                '5': 'open',
                # spot trigger only
                'NEW': 'open',
                'FILLED': 'closed',
                'PARTIALLY_FILLED': 'open',
                'CANCELED': 'canceled',
                'PARTIALLY_CANCELED': 'canceled',
            }
        else:
            statuses = {
                '2': 'open',
                '3': 'closed',
                '4': 'canceled',
            }
        return self.safe_string(statuses, status, status)

    async def watch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the mexc api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        await self.load_markets()
        messageHash = 'balance'
        type = None
        type, params = self.handle_market_type_and_params('watchBalance', None, params)
        if type == 'spot':
            raise NotSupported(self.id + ' watchBalance does not support spot markets')
        else:
            return self.watch_swap_private(messageHash, params)

    def handle_balance(self, client, message):
        #
        # swap balance
        #
        # {
        #     channel: 'push.personal.asset',
        #     data: {
        #       availableBalance: 49.2076809226,
        #       bonus: 0,
        #       currency: 'USDT',
        #       frozenBalance: 0.5006,
        #       positionMargin: 0
        #     },
        #     ts: 1651501676430
        # }
        #
        data = self.safe_value(message, 'data')
        currencyId = self.safe_string(data, 'currency')
        code = self.safe_currency_code(currencyId)
        account = self.account()
        account['free'] = self.safe_string(data, 'availableBalance')
        account['used'] = self.safe_string(data, 'frozenBalance')
        self.balance[code] = account
        self.balance = self.safe_balance(self.balance)
        messageHash = 'balance'
        client.resolve(self.balance, messageHash)

    async def watch_swap_public(self, messageHash, channel, requestParams, params={}):
        url = self.urls['api']['ws']['swap']
        request = {
            'method': channel,
            'param': requestParams,
        }
        message = self.extend(request, params)
        return await self.watch(url, messageHash, message, messageHash)

    async def watch_spot_public(self, messageHash, channel, requestParams, params={}):
        url = self.urls['api']['ws']['spot']
        request = {
            'op': channel,
        }
        extendedRequest = self.extend(request, requestParams)
        message = self.extend(extendedRequest, params)
        return await self.watch(url, messageHash, message, messageHash)

    async def watch_spot_private(self, messageHash, params={}):
        self.check_required_credentials()
        channel = 'sub.personal'
        url = self.urls['api']['ws']['spot']
        timestamp = str(self.milliseconds())
        request = {
            'op': channel,
            'api_key': self.apiKey,
            'req_time': timestamp,
        }
        sortedParams = self.keysort(request)
        sortedParams['api_secret'] = self.secret
        encodedParams = self.urlencode(sortedParams)
        hash = self.hash(self.encode(encodedParams), 'md5')
        request['sign'] = hash
        extendedRequest = self.extend(request, params)
        return await self.watch(url, messageHash, extendedRequest, channel)

    async def watch_swap_private(self, messageHash, params={}):
        self.check_required_credentials()
        channel = 'login'
        url = self.urls['api']['ws']['swap']
        timestamp = str(self.milliseconds())
        payload = self.apiKey + timestamp
        signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256)
        request = {
            'method': channel,
            'param': {
                'apiKey': self.apiKey,
                'signature': signature,
                'reqTime': timestamp,
            },
        }
        extendedRequest = self.extend(request, params)
        message = self.extend(extendedRequest, params)
        return await self.watch(url, messageHash, message, channel)

    def handle_error_message(self, client, message):
        #
        #   {channel: 'sub.personal', msg: 'signature validation failed'}
        #
        #   {
        #       channel: 'rs.error',
        #       data: 'Contract not exists',
        #       ts: 1651509181535
        #   }
        #
        channel = self.safe_string(message, 'channel')
        try:
            feedback = self.id + ' ' + self.json(message)
            if channel.find('error') >= 0:
                data = self.safe_value(message, 'data')
                if isinstance(data, str):
                    self.throw_exactly_matched_exception(self.exceptions['ws']['exact'], data, feedback)
                    self.throw_broadly_matched_exception(self.exceptions['ws']['broad'], data, feedback)
            if channel == 'sub.personal':
                msg = self.safe_string(message, 'msg')
                self.throw_exactly_matched_exception(self.exceptions['ws']['exact'], msg, feedback)
        except Exception as e:
            if isinstance(e, AuthenticationError):
                return False
        return message

    def handle_authenticate(self, client, message):
        #
        #  {channel: 'rs.login', data: 'success', ts: 1651486643082}
        #
        #  {channel: 'sub.personal', msg: 'OK'}
        #
        return message

    def handle_message(self, client, message):
        #
        # spot pong
        #
        #  "ping"
        #
        # swap pong
        #  {channel: 'pong', data: 1651570941402, ts: 1651570941402}
        #
        # auth spot
        #
        #  {channel: 'sub.personal', msg: 'OK'}
        #
        # auth swap
        #
        #  {channel: 'rs.login', data: 'success', ts: 1651486643082}
        #
        # subscription
        #
        #  {channel: 'rs.sub.depth', data: 'success', ts: 1651239594401}
        #
        # swap ohlcv
        #     {
        #         "channel":"push.kline",
        #         "data":{
        #             "a":233.740269343644737245,
        #             "c":6885,
        #             "h":6910.5,
        #             "interval":"Min60",
        #             "l":6885,
        #             "o":6894.5,
        #             "q":1611754,
        #             "symbol":"BTC_USDT",
        #             "t":1587448800
        #         },
        #         "symbol":"BTC_USDT",
        #         "ts":1587442022003
        #     }
        #
        # swap ticker
        #     {
        #         channel: 'push.ticker',
        #         data: {
        #           amount24: 491939387.90105,
        #           ask1: 39530.5,
        #           bid1: 39530,
        #           contractId: 10,
        #           fairPrice: 39533.4,
        #           fundingRate: 0.00015,
        #           high24Price: 40310.5,
        #           holdVol: 187680157,
        #           indexPrice: 39538.5,
        #           lastPrice: 39530,
        #           lower24Price: 38633,
        #           maxBidPrice: 43492,
        #           minAskPrice: 35584.5,
        #           riseFallRate: 0.0138,
        #           riseFallValue: 539.5,
        #           symbol: 'BTC_USDT',
        #           timestamp: 1651160401009,
        #           volume24: 125171687
        #         },
        #         symbol: 'BTC_USDT',
        #         ts: 1651160401009
        #       }
        #
        # swap trades
        #     {
        #         "channel":"push.deal",
        #         "data":{
        #             "M":1,
        #             "O":1,
        #             "T":1,
        #             "p":6866.5,
        #             "t":1587442049632,
        #             "v":2096
        #         },
        #         "symbol":"BTC_USDT",
        #         "ts":1587442022003
        #     }
        #
        # spot trades
        #
        #    {
        #        "symbol":"BTC_USDT",
        #        "data":{
        #           "deals":[
        #              {
        #                 "t":1651227552839,
        #                 "p":"39190.01",
        #                 "q":"0.001357",
        #                 "T":2
        #              }
        #           ]
        #        },
        #        "channel":"push.deal"
        #     }
        #
        # spot order
        #     {
        #         symbol: 'LTC_USDT',
        #         data: {
        #           price: 100.25,
        #           quantity: 0.0498,
        #           amount: 4.99245,
        #           remainAmount: 0.01245,
        #           remainQuantity: 0,
        #           remainQ: 0,
        #           remainA: 0,
        #           id: '0b1bf3a33916499f8d1a711a7d5a6fc4',
        #           status: 2,
        #           tradeType: 1,
        #           orderType: 3,
        #           createTime: 1651499416000,
        #           isTaker: 1,
        #           symbolDisplay: 'LTC_USDT',
        #           clientOrderId: ''
        #         },
        #         channel: 'push.personal.order',
        #         eventTime: 1651499416639,
        #         symbol_display: 'LTC_USDT'
        #     }
        #
        if not self.handle_error_message(client, message):
            return
        if message == 'pong':
            self.handle_pong(client, message)
            return
        channel = self.safe_string(message, 'channel')
        methods = {
            'pong': self.handle_pong,
            'rs.login': self.handle_authenticate,
            'push.deal': self.handle_trades,
            'orderbook': self.handle_order_book,
            'push.kline': self.handle_ohlcv,
            'push.ticker': self.handle_ticker,
            'push.depth': self.handle_order_book,
            'push.limit.depth': self.handle_order_book,
            'push.personal.order': self.handle_order,
            'push.personal.trigger.order': self.handle_order,
            'push.personal.plan.order': self.handle_order,
            'push.personal.order.deal': self.handle_my_trade,
            'push.personal.asset': self.handle_balance,
        }
        method = self.safe_value(methods, channel)
        if method is not None:
            method(client, message)

    def ping(self, client):
        type = self.safe_string(self.options, 'defaultType', 'spot')
        if type == 'spot':
            return 'ping'
        return {'method': 'ping'}

    def handle_pong(self, client, message):
        #
        # {channel: 'pong', data: 1651570941402, ts: 1651570941402}
        #
        client.lastPong = self.milliseconds()
        return message
