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

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

import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
import hashlib
from ccxt.base.types import Any, Balances, Int, Market, Order, OrderBook, Position, Str, Strings, Ticker, Tickers, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import BadRequest
from ccxt.base.errors import NotSupported


class weex(ccxt.async_support.weex):

    def describe(self) -> Any:
        return self.deep_extend(super(weex, self).describe(), {
            'has': {
                'watchBalance': True,
                'watchBidsAsks': True,
                'watchMyTrades': True,
                'watchOHLCV': True,
                'watchOHLCVForSymbols': True,
                'watchOrderBook': False,
                'watchOrderBookForSymbols': False,
                'watchOrders': True,
                'watchPositions': True,
                'watchTicker': True,
                'watchTickers': True,
                'watchTrades': True,
                'watchTradesForSymbols': True,
                'unWatchBidsAsks': True,
                'unWatchMyTrades': True,
                'unWatchOHLCV': True,
                'unWatchOHLCVForSymbols': True,
                'unWatchOrderBook': False,
                'unWatchOrderBookForSymbols': False,
                'unWatchOrders': True,
                'unWatchPositions': True,
                'unWatchTicker': True,
                'unWatchTickers': True,
                'unWatchTrades': True,
                'unWatchTradesForSymbols': True,
            },
            'urls': {
                'api': {
                    'ws': {
                        'spot': 'wss://ws-spot.weex.com/v3/ws',
                        'contract': 'wss://ws-contract.weex.com/v3/ws',
                    },
                },
            },
            'options': {
                'ws': {
                    'options': {
                        'headers': {
                            'User-Agent': 'ccxt',  # the exchange requires headers
                        },
                    },
                },
                'watchOHLCV': {
                    'priceType': 'LAST_PRICE',  # or 'MARK_PRICE' for swap markets
                },
                'watchOHLCVForSymbols': {
                    'priceType': 'LAST_PRICE',  # or 'MARK_PRICE' for swap markets
                },
                'watchOrderBook': {
                    'depth': '200',  # or '15'
                },
                'watchOrderBookForSymbols': {
                    'depth': '200',  # or '15'
                },
                'watchBalance': {
                    'fetchBalanceSnapshot': True,
                    'awaitBalanceSnapshot': True,
                },
                'watchPositions': {
                    'fetchPositionsSnapshot': True,
                    'awaitPositionsSnapshot': True,
                },
            },
            'streaming': {},
        })

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

    async def subscribe_public(self, messageHashes, channels, isContract=False, params={}, subscription={}):
        id = self.request_id()
        method = 'SUBSCRIBE'
        unsubscribe = self.safe_bool(subscription, 'unsubscribe', False)
        if unsubscribe:
            method = 'UNSUBSCRIBE'
        message: dict = {
            'id': id,
            'method': method,
            'params': channels,
        }
        subscription = self.extend(subscription, {'id': id})
        type = 'contract' if isContract else 'spot'
        url = self.urls['api']['ws'][type] + '/public'
        return await self.watch_multiple(url, messageHashes, self.deep_extend(message, params), messageHashes, subscription)

    async def subscribe_private(self, messageHash, subscribeHash, channel, isContract=False, params={}, subscription={}):
        type = 'contract' if isContract else 'spot'
        url = self.urls['api']['ws'][type] + '/private'
        self.authenticate(url)
        method = 'SUBSCRIBE'
        unsubscribe = self.safe_bool(subscription, 'unsubscribe', False)
        if unsubscribe:
            method = 'UNSUBSCRIBE'
        id = self.request_id()
        message: dict = {
            'id': id,
            'method': method,
            'params': [channel],
        }
        subscription = self.extend(subscription, {'id': id})
        return await self.watch(url, messageHash, self.deep_extend(message, params), subscribeHash, subscription)

    def authenticate(self, url):
        self.check_required_credentials()
        if (self.clients is not None) and (url in self.clients):
            return
        timestamp = self.nonce()
        payload = str(timestamp) + '/v3/ws/private'
        signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64')
        originalHeaders = self.options['ws']['options']['headers']
        userAgent = self.safe_string(originalHeaders, 'User-Agent', 'ccxt')
        extendedOptions: dict = {
            'ws': {
                'options': {
                    'headers': {
                        'User-Agent': userAgent,
                        'ACCESS-KEY': self.apiKey,
                        'ACCESS-SIGN': signature,
                        'ACCESS-PASSPHRASE': self.password,
                        'ACCESS-TIMESTAMP': self.number_to_string(timestamp),
                    },
                },
            },
        }
        self.extend_exchange_options(extendedOptions)
        # instantiate client
        self.client(url)
        # return headers to original state
        defaultOptions: dict = {
            'ws': {
                'options': {
                    'headers': {
                        'User-Agent': userAgent,
                    },
                },
            },
        }
        self.extend_exchange_options(defaultOptions)

    async def watch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market

        https://www.weex.com/api-doc/spot/Websocket/public/Tickers-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Tickers-Channel

        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.name]: stream to use can be ticker or miniTicker
        :returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
        """
        await self.load_markets()
        symbol = self.symbol(symbol)
        tickers = await self.watch_tickers([symbol], params)
        return tickers[symbol]

    async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list

        https://www.weex.com/api-doc/spot/Websocket/public/Tickers-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Tickers-Channel

        :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 `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols, None, False, True)
        firstMarket = self.get_market_from_symbols(symbols)
        isContract = firstMarket['contract']
        topic = 'ticker'
        messageHashes = []
        channels = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            channelName = market['id'] + '@' + topic
            messageHash = topic + '::' + symbol
            messageHashes.append(messageHash)
            channels.append(channelName)
        newTicker = await self.subscribe_public(messageHashes, channels, isContract, params)
        if self.newUpdates:
            result: dict = {}
            result[newTicker['symbol']] = newTicker
            return result
        return self.filter_by_array(self.tickers, 'symbol', symbols)

    async def un_watch_ticker(self, symbol: str, params={}) -> Any:
        """
        unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list

        https://www.weex.com/api-doc/spot/Websocket/public/Tickers-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Tickers-Channel

        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
        """
        return await self.un_watch_tickers([symbol], params)

    async def un_watch_tickers(self, symbols: Strings = None, params={}) -> Any:
        """
        unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list

        https://www.weex.com/api-doc/spot/Websocket/public/Tickers-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Tickers-Channel

        :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 `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols, None, False, True)
        firstMarket = self.get_market_from_symbols(symbols)
        isContract = firstMarket['contract']
        topic = 'ticker'
        subHashes = []
        channels = []
        unSubHashes = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            channelName = market['id'] + '@' + topic
            messageHash = topic + '::' + symbol
            unSubMessageHash = 'unsubscribe::' + messageHash
            subHashes.append(messageHash)
            channels.append(channelName)
            unSubHashes.append(unSubMessageHash)
        subscription = {
            'unsubscribe': True,
            'symbols': symbols,
            'messageHashes': unSubHashes,
            'subMessageHashes': subHashes,
            'topic': topic,
        }
        return await self.subscribe_public(unSubHashes, channels, isContract, params, subscription)

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "e": "ticker",
        #         "E": 1776081628845,
        #         "s": "ETHUSDT",
        #         "d": [
        #             {
        #                 "p": "-18.93",
        #                 "P": "-0.008592",
        #                 "w": "2192.40298388",
        #                 "c": "2184.20",
        #                 "o": "2203.13",
        #                 "h": "2217.34",
        #                 "l": "2173.32",
        #                 "v": "359395.800",
        #                 "q": "787940424.31399",
        #                 "O": 1775995200000,
        #                 "C": 1776081600000,
        #                 "n": 485169,
        #                 "m": "2184.28",
        #                 "i": "2185.2025"
        #             }
        #         ]
        #     }
        #
        market = self.get_market_from_client_and_message(client, message)
        tickers = self.safe_list(message, 'd', [])
        data = self.safe_dict(tickers, 0, {})
        ticker = self.parse_ws_ticker(data, market)
        symbol = market['symbol']
        messageHash = 'ticker::' + symbol
        self.tickers[symbol] = ticker
        client.resolve(self.tickers[symbol], messageHash)

    def parse_ws_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #     {
        #         "p": "-18.93",
        #         "P": "-0.008592",
        #         "w": "2192.40298388",
        #         "c": "2184.20",
        #         "o": "2203.13",
        #         "h": "2217.34",
        #         "l": "2173.32",
        #         "v": "359395.800",
        #         "q": "787940424.31399",
        #         "O": 1775995200000,
        #         "C": 1776081600000,
        #         "n": 485169,
        #         "m": "2184.28",
        #         "i": "2185.2025"
        #     }
        #
        timestamp = self.safe_integer(ticker, 'C')
        close = self.safe_string(ticker, 'c')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'h'),
            'low': self.safe_string(ticker, 'l'),
            'bid': self.safe_string(ticker, 'b'),
            'bidVolume': self.safe_string(ticker, 'B'),
            'ask': self.safe_string(ticker, 'a'),
            'askVolume': self.safe_string(ticker, 'A'),
            'vwap': self.safe_string(ticker, 'w'),
            'open': self.safe_string(ticker, 'o'),
            'close': close,
            'last': close,
            'previousClose': self.safe_string(ticker, 'x'),
            'change': self.safe_string(ticker, 'p'),
            'percentage': self.safe_string(ticker, 'P'),
            'average': self.safe_string(ticker, 'w'),
            'baseVolume': self.safe_string(ticker, 'v'),
            'quoteVolume': self.safe_string(ticker, 'q'),
            'markPrice': self.safe_string(ticker, 'm'),
            'indexPrice': self.safe_string(ticker, 'i'),
            'info': ticker,
        }, market)

    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

        https://www.weex.com/api-doc/spot/Websocket/public/Trades-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Trades-Channel

        :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>`
        """
        return await self.watch_trades_for_symbols([symbol], since, limit, params)

    async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a list of symbols

        https://www.weex.com/api-doc/spot/Websocket/public/Trades-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Trades-Channel

        :param str[] symbols: 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()
        symbols = self.market_symbols(symbols, None, False, True)
        firstMarket = self.get_market_from_symbols(symbols)
        isContract = firstMarket['contract']
        topic = 'trade'
        messageHashes = []
        channels = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            channelName = market['id'] + '@' + topic
            messageHash = topic + '::' + symbol
            messageHashes.append(messageHash)
            channels.append(channelName)
        trades = await self.subscribe_public(messageHashes, channels, isContract, params)
        if self.newUpdates:
            first = self.safe_value(trades, 0)
            tradeSymbol = self.safe_string(first, 'symbol')
            limit = trades.getLimit(tradeSymbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    async def un_watch_trades(self, symbol: str, params={}) -> Any:
        """
        unsubscribes from the trades channel

        https://www.weex.com/api-doc/spot/Websocket/public/Trades-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Trades-Channel

        :param str symbol: unified symbol of the market to fetch trades for
        :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()
        return await self.un_watch_trades_for_symbols([symbol], params)

    async def un_watch_trades_for_symbols(self, symbols: List[str], params={}) -> Any:
        """
        unsubscribes from the trades channel

        https://www.weex.com/api-doc/spot/Websocket/public/Trades-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Trades-Channel

        :param str[] symbols: unified symbol of the market to fetch trades for
        :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()
        symbols = self.market_symbols(symbols, None, False, True)
        firstMarket = self.get_market_from_symbols(symbols)
        isContract = firstMarket['contract']
        topic = 'trade'
        subHashes = []
        channels = []
        unSubHashes = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            channelName = market['id'] + '@' + topic
            messageHash = topic + '::' + symbol
            unSubMessageHash = 'unsubscribe::' + messageHash
            subHashes.append(messageHash)
            channels.append(channelName)
            unSubHashes.append(unSubMessageHash)
        subscription = {
            'unsubscribe': True,
            'symbols': symbols,
            'messageHashes': unSubHashes,
            'subMessageHashes': subHashes,
            'topic': 'trades',
        }
        return await self.subscribe_public(unSubHashes, channels, isContract, params, subscription)

    def handle_trade(self, client: Client, message):
        #
        #     {
        #         "e": "trade",
        #         "E": 1776104608321,
        #         "s": "ETHUSDT",
        #         "d": [
        #             {
        #                 "T": 1776104608298,
        #                 "t": "41099265-7985-4f4c-af93-2cc3bc1cf13b",
        #                 "p": "2225.15",
        #                 "q": "0.02525",
        #                 "v": "56.1850375",
        #                 "m": False
        #             }
        #         ]
        #     }
        #
        market = self.get_market_from_client_and_message(client, message)
        symbol = market['symbol']
        messageHash = 'trade::' + symbol
        if not (symbol in self.trades):
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.trades[symbol] = ArrayCache(limit)
        tradesArray = self.trades[symbol]
        data = self.safe_list(message, 'd', [])
        newTrades = []
        for i in range(0, len(data)):
            rawTrade = self.safe_dict(data, i, {})
            trade = self.parse_ws_trade(rawTrade, market)
            newTrades.append(trade)
        sorted = self.sort_by(newTrades, 'timestamp')
        for j in range(0, len(sorted)):
            sortedTrade = sorted[j]
            tradesArray.append(sortedTrade)
        self.trades[symbol] = tradesArray
        client.resolve(tradesArray, messageHash)

    def parse_ws_trade(self, trade, market=None):
        #
        #     {
        #         "T": 1776089287762,
        #         "t": "df4d1af1-71e8-400d-9571-f2cee2e6bea8",
        #         "p": "2203.73",
        #         "q": "7.214",
        #         "v": "15897.70822",
        #         "m": False
        #     }
        #
        timestamp = self.safe_integer(trade, 'T')
        return self.safe_trade({
            'info': trade,
            'id': self.safe_string(trade, 't'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'order': None,
            'type': None,
            'side': None,
            'takerOrMaker': None,
            'price': self.safe_string(trade, 'p'),
            'amount': self.safe_string(trade, 'q'),
            'cost': self.safe_string(trade, 'v'),
            'fee': None,
        }, market)

    async def watch_ohlcv(self, symbol: str, timeframe: str = '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

        https://www.weex.com/api-doc/spot/Websocket/public/Candlesticks-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Candlesticks-Channel

        :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
        """
        extendedParams = self.extend(params, {
            'callerMethodName': 'watchOHLCV',
        })
        result = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, extendedParams)
        return result[symbol][timeframe]

    async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
        """
        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://www.weex.com/api-doc/spot/Websocket/public/Candlesticks-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Candlesticks-Channel

        :param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
        :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 dict: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        callerMethodName = self.safe_string(params, 'callerMethodName', 'watchOHLCVForSymbols')
        params = self.omit(params, 'callerMethodName')
        channels = []
        messageHashes = []
        firstEntry = self.safe_list(symbolsAndTimeframes, 0, [])
        firstSymbol = self.safe_string(firstEntry, 0)
        firstMarket = self.market(firstSymbol)
        isContract = firstMarket['contract']
        priceType = 'LAST_PRICE'
        if isContract:
            priceType, params = self.handle_option_and_params_2(params, callerMethodName, 'price', 'priceType', priceType)
        for i in range(0, len(symbolsAndTimeframes)):
            data = self.safe_list(symbolsAndTimeframes, i)
            symbolString = self.safe_string(data, 0)
            market = self.market(symbolString)
            if market['type'] != firstMarket['type']:
                raise BadRequest(self.id + ' ' + callerMethodName + ' market symbols must be of the same type')
            symbolString = market['symbol']
            unifiedTimeframe = self.safe_string(data, 1, '1')
            interval = self.safe_string(self.timeframes, unifiedTimeframe, unifiedTimeframe)
            channel = market['id'] + '@kline_' + interval + '_' + priceType
            messageHash = 'ohlcv::' + symbolString + '::' + unifiedTimeframe
            channels.append(channel)
            messageHashes.append(messageHash)
        symbol, timeframe, stored = await self.subscribe_public(messageHashes, channels, isContract, params)
        if self.newUpdates:
            limit = stored.getLimit(symbol, limit)
        filtered = self.filter_by_since_limit(stored, since, limit, 0, True)
        return self.create_ohlcv_object(symbol, timeframe, filtered)

    async def un_watch_ohlcv(self, symbol: str, timeframe='1m', params={}) -> Any:
        """
        unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://www.weex.com/api-doc/spot/Websocket/public/Candlesticks-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Candlesticks-Channel

        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        params['callerMethodName'] = 'unWatchOHLCV'
        return await self.un_watch_ohlcv_for_symbols([[symbol, timeframe]], params)

    async def un_watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], params={}) -> Any:
        """
        unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://www.weex.com/api-doc/spot/Websocket/public/Candlesticks-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Candlesticks-Channel

        :param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
        :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()
        callerMethodName = self.safe_string(params, 'callerMethodName', 'unWatchOHLCVForSymbols')
        params = self.omit(params, 'callerMethodName')
        channels = []
        subHashes = []
        unSubHashes = []
        firstEntry = self.safe_list(symbolsAndTimeframes, 0, [])
        firstSymbol = self.safe_string(firstEntry, 0)
        firstMarket = self.market(firstSymbol)
        isContract = firstMarket['contract']
        priceType = 'LAST_PRICE'
        if isContract:
            priceType, params = self.handle_option_and_params_2(params, callerMethodName, 'price', 'priceType', priceType)
        for i in range(0, len(symbolsAndTimeframes)):
            data = self.safe_list(symbolsAndTimeframes, i)
            symbolString = self.safe_string(data, 0)
            market = self.market(symbolString)
            if market['type'] != firstMarket['type']:
                raise BadRequest(self.id + ' ' + callerMethodName + ' market symbols must be of the same type')
            symbolString = market['symbol']
            unifiedTimeframe = self.safe_string(data, 1, '1')
            interval = self.safe_string(self.timeframes, unifiedTimeframe, unifiedTimeframe)
            channel = market['id'] + '@kline_' + interval + '_' + priceType
            messageHash = 'ohlcv::' + symbolString + '::' + unifiedTimeframe
            unSubMessageHash = 'unsubscribe::' + messageHash
            channels.append(channel)
            subHashes.append(messageHash)
            unSubHashes.append(unSubMessageHash)
        subscription = {
            'unsubscribe': True,
            'symbolsAndTimeframes': symbolsAndTimeframes,
            'messageHashes': unSubHashes,
            'subMessageHashes': subHashes,
            'topic': 'ohlcv',
        }
        return await self.subscribe_public(unSubHashes, channels, isContract, params, subscription)

    def handle_ohlcv(self, client: Client, message):
        #
        #     {
        #         e: 'kline',
        #         E: 1776095535012,
        #         s: 'ETHUSDT',
        #         p: 'LAST_PRICE',
        #         d: [
        #             {
        #                 t: 1776092400000,
        #                 T: 1776096000000,
        #                 s: 'ETHUSDT',
        #                 i: '1h',
        #                 o: '2234.18',
        #                 c: '2205.15',
        #                 h: '2236.43',
        #                 l: '2199.53',
        #                 v: '12505.60574',
        #                 n: 3381,
        #                 q: '27682528.6655305',
        #                 V: '6420.47929',
        #                 Q: '14213680.1906424'
        #             }
        #         ]
        #     }
        #
        market = self.get_market_from_client_and_message(client, message)
        symbol = market['symbol']
        if not (symbol in self.ohlcvs):
            self.ohlcvs[symbol] = {}
        data = self.safe_list(message, 'd', [])
        firstEntry = self.safe_dict(data, 0, {})
        interval = self.safe_string(firstEntry, 'i')
        timeframe = self.find_timeframe(interval)
        if not (timeframe in self.ohlcvs[symbol]):
            limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
            self.ohlcvs[symbol][timeframe] = ArrayCacheByTimestamp(limit)
        stored = self.ohlcvs[symbol][timeframe]
        for i in range(0, len(data)):
            entry = self.safe_dict(data, i, {})
            parsed = self.parse_ws_ohlcv(entry)
            stored.append(parsed)
        messageHash = 'ohlcv::' + symbol + '::' + timeframe
        resolveData = [symbol, timeframe, stored]
        client.resolve(resolveData, messageHash)

    def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
        #
        #     {
        #         t: 1776092400000,
        #         T: 1776096000000,
        #         s: 'ETHUSDT',
        #         i: '1h',
        #         o: '2234.18',
        #         c: '2205.15',
        #         h: '2236.43',
        #         l: '2199.53',
        #         v: '12505.60574',
        #         n: 3381,
        #         q: '27682528.6655305',
        #         V: '6420.47929',
        #         Q: '14213680.1906424'
        #     }
        #
        return [
            self.safe_integer(ohlcv, 't'),
            self.safe_number(ohlcv, 'o'),
            self.safe_number(ohlcv, 'h'),
            self.safe_number(ohlcv, 'l'),
            self.safe_number(ohlcv, 'c'),
            self.safe_number(ohlcv, 'v'),
        ]

    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

        https://www.weex.com/api-doc/spot/Websocket/public/Depth-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Depth-Channel

        :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
        """
        params = self.extend(params, {
            'callerMethodName': 'watchOrderBook',
        })
        return await self.watch_order_book_for_symbols([symbol], limit, params)

    async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
        """
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://www.weex.com/api-doc/spot/Websocket/public/Depth-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Depth-Channel

        :param str[] symbols: unified array of symbols
        :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()
        symbols = self.market_symbols(symbols, None, False, True)
        firstMarket = self.get_market_from_symbols(symbols)
        isContract = firstMarket['contract']
        callerMethodName = self.safe_string(params, 'callerMethodName', 'watchOrderBookForSymbols')
        params = self.omit(params, 'callerMethodName')
        depth = '200'
        depth, params = self.handle_option_and_params(params, callerMethodName, 'depth', depth)
        messageHashes = []
        channels = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            messageHash = 'orderbook::' + symbol
            channel = market['id'] + '@depth' + depth
            messageHashes.append(messageHash)
            channels.append(channel)
        subscription: dict = {
            'limit': limit,
        }
        orderbook = await self.subscribe_public(messageHashes, channels, isContract, params, subscription)
        return orderbook.limit()

    async def un_watch_order_book(self, symbol: str, params={}) -> Any:
        """
        unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://www.weex.com/api-doc/spot/Websocket/public/Depth-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Depth-Channel

        :param str symbol: unified array of symbols
        :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
        """
        params = self.extend(params, {
            'callerMethodName': 'unWatchOrderBook',
        })
        return await self.un_watch_order_book_for_symbols([symbol], params)

    async def un_watch_order_book_for_symbols(self, symbols: List[str], params={}) -> Any:
        """
        unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://www.weex.com/api-doc/spot/Websocket/public/Depth-Channel
        https://www.weex.com/api-doc/contract/Websocket/public/Depth-Channel

        :param str[] symbols: unified array of symbols
        :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()
        symbols = self.market_symbols(symbols, None, False, True)
        firstMarket = self.get_market_from_symbols(symbols)
        isContract = firstMarket['contract']
        callerMethodName = self.safe_string(params, 'callerMethodName', 'unWatchOrderBookForSymbols')
        params = self.omit(params, 'callerMethodName')
        depth = '200'
        depth, params = self.handle_option_and_params(params, callerMethodName, 'depth', depth)
        subHashes = []
        channels = []
        unSubHashes = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            messageHash = 'orderbook::' + symbol
            channel = market['id'] + '@depth' + depth
            unSubMessageHash = 'unsubscribe::' + messageHash
            subHashes.append(messageHash)
            channels.append(channel)
            unSubHashes.append(unSubMessageHash)
        subscription = {
            'unsubscribe': True,
            'symbols': symbols,
            'messageHashes': unSubHashes,
            'subMessageHashes': subHashes,
            'topic': 'orderbook',
        }
        return await self.subscribe_public(unSubHashes, channels, isContract, params, subscription)

    def handle_order_book(self, client: Client, message):
        #
        #     {
        #         "e": "depth",
        #         "E": 1776098967972,
        #         "s": "ETHUSDT",
        #         "U": 14181847790,
        #         "u": 14181847802,
        #         "l": 200,
        #         "d": "CHANGED",
        #         "b": [["2227.21", "0"], ["2227.20", "46.519"]],
        #         "a": [["2227.21", "44.092"], ["2227.26", "0"]]
        #     }
        #
        market = self.get_market_from_client_and_message(client, message)
        symbol = market['symbol']
        messageHash = 'orderbook::' + symbol
        if not (symbol in self.orderbooks):
            subscription = self.safe_dict(client.subscriptions, messageHash, {})
            limit = self.safe_integer(subscription, 'limit')
            if limit is not None:
                self.orderbooks[symbol] = self.order_book({}, limit)
            else:
                self.orderbooks[symbol] = self.order_book({})
        orderbook = self.orderbooks[symbol]
        timestamp = self.safe_integer(message, 'E')
        event = self.safe_string(message, 'e')
        nonce = self.safe_integer(message, 'u')
        if event == 'depthSnapshot':
            parsed = self.parse_order_book(message, symbol, timestamp, 'b', 'a')
            parsed['nonce'] = nonce
            orderbook.reset(parsed)
        else:
            asks = self.safe_list(message, 'a', [])
            bids = self.safe_list(message, 'b', [])
            self.handle_deltas(orderbook['asks'], asks)
            self.handle_deltas(orderbook['bids'], bids)
            orderbook['timestamp'] = timestamp
            orderbook['datetime'] = self.iso8601(timestamp)
            orderbook['nonce'] = nonce
        client.resolve(orderbook, messageHash)

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

    async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
        """
        watches best bid & ask for spot symbols

        https://www.weex.com/api-doc/spot/Websocket/public/BookTicker-Channel

        :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 `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols, None, False, True)
        firstMarket = self.get_market_from_symbols(symbols)
        if firstMarket['contract']:
            raise NotSupported(self.id + ' watchBidsAsks is supported for spot markets only')
        messageHashes = []
        channels = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            channelName = market['id'] + '@' + 'bookTicker'
            messageHash = 'bidask::' + symbol
            messageHashes.append(messageHash)
            channels.append(channelName)
        newTicker = await self.subscribe_public(messageHashes, channels, False, params)
        if self.newUpdates:
            result = {}
            result[newTicker['symbol']] = newTicker
            return result
        return self.filter_by_array(self.bidsasks, 'symbol', symbols)

    async def un_watch_bids_asks(self, symbols: Strings = None, params={}) -> Any:
        """
        unWatches best bid & ask for spot symbols

        https://www.weex.com/api-doc/spot/Websocket/public/BookTicker-Channel

        :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 `ticker structure <https://docs.ccxt.com/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols, None, False, True)
        firstMarket = self.get_market_from_symbols(symbols)
        if firstMarket['contract']:
            raise NotSupported(self.id + ' unWatchBidsAsks is supported for spot markets only')
        subHashes = []
        channels = []
        unSubHashes = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            channelName = market['id'] + '@' + 'bookTicker'
            messageHash = 'bidask::' + symbol
            unSubMessageHash = 'unsubscribe::' + messageHash
            subHashes.append(messageHash)
            channels.append(channelName)
            unSubHashes.append(unSubMessageHash)
        subscription = {
            'unsubscribe': True,
            'symbols': symbols,
            'messageHashes': unSubHashes,
            'subMessageHashes': subHashes,
            'topic': 'bidsasks',
        }
        return await self.subscribe_public(unSubHashes, channels, False, params, subscription)

    def handle_bid_ask(self, client: Client, message):
        #
        #     {
        #         "e": "bookTicker",
        #         "E": 1776103547551,
        #         "s": "ETHUSDT",
        #         "u": 1776103547547,
        #         "b": "2227.39",
        #         "B": "1.05512",
        #         "a": "2227.40",
        #         "A": "6.30889"
        #     }
        #
        market = self.get_market_from_client_and_message(client, message)
        ticker = self.parse_ws_bid_ask(message, market)
        symbol = ticker['symbol']
        self.bidsasks[symbol] = ticker
        messageHash = 'bidask::' + symbol
        client.resolve(ticker, messageHash)

    def parse_ws_bid_ask(self, message, market=None):
        timestamp = self.safe_integer(message, 'E')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'ask': self.safe_string(message, 'a'),
            'askVolume': self.safe_string(message, 'A'),
            'bid': self.safe_string(message, 'b'),
            'bidVolume': self.safe_string(message, 'B'),
            'info': message,
        }, market)

    async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        watches information on multiple trades made by the user

        https://www.weex.com/api-doc/spot/Websocket/private/Fill-Channel
        https://www.weex.com/api-doc/contract/Websocket/private/Fill-Channel

        :param str symbol: unified market symbol of the market trades were made in
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trade structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.type]: spot or swap, default is spot if symbol is not provided
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/?id=trade-structure>`
        """
        await self.load_markets()
        marketType = None
        market = None
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
        marketType, params = self.handle_market_type_and_params('watchMyTrades', market, params)
        isContract = (marketType != 'spot')
        messageHash = 'myContractTrades' if isContract else 'myTrades'
        subscriptionHash = messageHash
        if symbol is not None:
            messageHash += '::' + symbol
        channel = 'fill'
        trades = await self.subscribe_private(messageHash, subscriptionHash, channel, isContract, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    async def un_watch_my_trades(self, symbol: Str = None, params={}) -> Any:
        """
        unWatches information on multiple trades made by the user

        https://www.weex.com/api-doc/spot/Websocket/private/Fill-Channel
        https://www.weex.com/api-doc/contract/Websocket/private/Fill-Channel

        :param str [symbol]: not used by the exchange
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.type]: spot or swap, default is spot
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        if symbol is not None:
            raise NotSupported(self.id + ' unWatchMyTrades does not support a symbol argument. Unsubscribing from myTrades is global for all symbols.')
        marketType = None
        marketType, params = self.handle_market_type_and_params('unWatchMyTrades', None, params)
        isContract = (marketType != 'spot')
        subHash = 'myContractTrades' if isContract else 'myTrades'
        unSubHash = 'unsubscribe::' + subHash
        channel = 'fill'
        subscription = {
            'unsubscribe': True,
            'messageHashes': [unSubHash],
            'subMessageHashes': [subHash],
            'topic': 'myTrades',
            'subHashIsPrefix': True,
        }
        return await self.subscribe_private(unSubHash, unSubHash, channel, isContract, params, subscription)

    def handle_my_trades(self, client: Client, message):
        #
        # spot
        #     {
        #         e: 'fill',
        #         E: 1776174283564,
        #         v: 83,
        #         msgEvent: 'OrderUpdate',
        #         d: [
        #             {
        #                 id: '738928502249620072',
        #                 symbol: 'DOGEUSDT',
        #                 baseCoin: 'DOGE',
        #                 quoteCoin: 'USDT',
        #                 orderId: '738928502174122600',
        #                 orderSide: 'SELL',
        #                 fillSize: '200.0',
        #                 fillValue: '19.098000',
        #                 fillFee: '0.01909800',
        #                 direction: 'TAKER',
        #                 createdTime: '1776174283564',
        #                 updatedTime: '1776174283564'
        #             }
        #         ]
        #     }
        #
        # swap
        #     {
        #         "id": "738957755401896296",
        #         "coin": "USDT",
        #         "symbol": "DOGEUSDT",
        #         "orderId": "738957755376730472",
        #         "marginMode": "CROSSED",
        #         "separatedMode": "COMBINED",
        #         "separatedOpenOrderId": "0",
        #         "positionSide": "LONG",
        #         "orderSide": "BUY",
        #         "fillSize": "100",
        #         "fillValue": "9.59500",
        #         "fillFee": "0.00767600",
        #         "liquidateFee": "0",
        #         "realizePnl": "0",
        #         "direction": "TAKER",
        #         "createdTime": "1776181258059",
        #         "updatedTime": "1776181258059"
        #     }
        #
        if self.myTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.myTrades = ArrayCacheBySymbolById(limit)
        trades = self.myTrades
        data = self.safe_list(message, 'd', [])
        symbols: dict = {}
        for i in range(0, len(data)):
            trade = self.safe_dict(data, i, {})
            parsed = self.parse_ws_my_trade(trade)
            symbol = parsed['symbol']
            symbols[symbol] = True
            trades.append(parsed)
        messageHash = 'myTrades'
        symbolKeys = list(symbols.keys())
        market = self.get_market_from_symbols(symbolKeys)
        if market['contract']:
            messageHash = 'myContractTrades'
        for j in range(0, len(symbolKeys)):
            symbol = symbolKeys[j]
            symbolMessageHash = messageHash + '::' + symbol
            client.resolve(trades, symbolMessageHash)
        client.resolve(trades, messageHash)

    def parse_ws_my_trade(self, trade, market=None):
        #
        # spot
        #     {
        #         id: '738928502249620072',
        #         symbol: 'DOGEUSDT',
        #         baseCoin: 'DOGE',
        #         quoteCoin: 'USDT',
        #         orderId: '738928502174122600',
        #         orderSide: 'SELL',
        #         fillSize: '200.0',
        #         fillValue: '19.098000',
        #         fillFee: '0.01909800',
        #         direction: 'TAKER',
        #         createdTime: '1776174283564',
        #         updatedTime: '1776174283564'
        #     }
        #
        timestamp = self.safe_integer(trade, 'createdTime')
        marketId = self.safe_string(trade, 'symbol')
        marketType = 'spot'
        positionSide = self.safe_string(trade, 'positionSide')
        if positionSide is not None:
            marketType = 'swap'
        market = self.safe_market(marketId, None, None, marketType)
        side = self.safe_string_lower(trade, 'orderSide')
        fee = None
        commission = self.safe_string(trade, 'fillFee')
        if commission is not None:
            commissionAsset = self.safe_string(trade, 'coin')
            feeCurrency = self.safe_currency_code(commissionAsset)
            if marketType == 'spot':
                if side == 'buy':
                    feeCurrency = market['base']
                else:
                    feeCurrency = market['quote']
            fee = {
                'cost': commission,
                'currency': feeCurrency,
            }
        return self.safe_trade({
            'info': trade,
            'id': self.safe_string(trade, 'id'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'order': self.safe_string(trade, 'orderId'),
            'type': self.safe_string(trade, 'type'),
            'side': side,
            'takerOrMaker': self.safe_string_lower(trade, 'direction'),
            'price': None,
            'amount': self.safe_string(trade, 'fillSize'),
            'cost': self.safe_string(trade, 'fillValue'),
            'fee': fee,
        })

    async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        watches information on multiple orders made by the user

        https://www.weex.com/api-doc/spot/Websocket/private/Order-Channel
        https://www.weex.com/api-doc/contract/Websocket/private/Order-Channel

        :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
        :param str [params.type]: spot or swap, default is spot if symbol is not provided
        :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']
        marketType = None
        marketType, params = self.handle_market_type_and_params('watchOrders', market, params)
        isContract = (marketType != 'spot')
        messageHash = 'contractOrders' if isContract else 'orders'
        subscriptionHash = messageHash
        if symbol is not None:
            messageHash += '::' + symbol
        channel = 'orders'
        orders = await self.subscribe_private(messageHash, subscriptionHash, channel, isContract, params)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    async def un_watch_orders(self, symbol: Str = None, params={}) -> Any:
        """
        unWatches information on multiple orders made by the user

        https://www.weex.com/api-doc/spot/Websocket/private/Order-Channel
        https://www.weex.com/api-doc/contract/Websocket/private/Order-Channel

        :param str [symbol]: not used by the exchange
        :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>`
        """
        if symbol is not None:
            raise NotSupported(self.id + ' unWatchOrders does not support a symbol argument. Unsubscribing from orders is global for all symbols.')
        marketType = None
        marketType, params = self.handle_market_type_and_params('unWatchOrders', None, params)
        isContract = (marketType != 'spot')
        subHash = 'contractOrders' if isContract else 'orders'
        unSubHash = 'unsubscribe::' + subHash
        channel = 'orders'
        subscription = {
            'unsubscribe': True,
            'messageHashes': [unSubHash],
            'subMessageHashes': [subHash],
            'topic': 'orders',
            'subHashIsPrefix': True,
        }
        return await self.subscribe_private(unSubHash, unSubHash, channel, isContract, params, subscription)

    def handle_orders(self, client: Client, message):
        #
        #     {
        #         "e": "orders",
        #         "E": 1776184415058,
        #         "v": 153,
        #         "msgEvent": "OrderUpdate",
        #         "d": [
        #             {
        #                 "id": "738970996765098600",
        #                 "symbol": "DOGEUSDT",
        #                 "baseCoin": "DOGE",
        #                 "quoteCoin": "USDT",
        #                 "orderSide": "SELL",
        #                 "price": "0",
        #                 "size": "200.0",
        #                 "value": "0",
        #                 "clientOrderId": "b-WEEX111125-bf78d975ca38422bb6ea65",
        #                 "type": "MARKET",
        #                 "timeInForce": "IOC",
        #                 "reduceOnly": False,
        #                 "triggerPrice": "0",
        #                 "orderSource": "API",
        #                 "openTpslParentOrderId": "0",
        #                 "setOpenTp": False,
        #                 "setOpenSl": False,
        #                 "takerFeeRate": "0.001",
        #                 "makerFeeRate": "0.001",
        #                 "feeDiscount": "1",
        #                 "takerFeeDiscount": "1",
        #                 "makerFeeDiscount": "1",
        #                 "status": "FILLED",
        #                 "triggerTime": "0",
        #                 "triggerPriceTime": "0",
        #                 "triggerPriceValue": "0",
        #                 "cancelReason": "UNKNOWN_ORDER_CANCEL_REASON",
        #                 "latestFillPrice": "0.09571",
        #                 "maxFillPrice": "0.09571",
        #                 "minFillPrice": "0.09571",
        #                 "cumFillSize": "200.0",
        #                 "cumFillValue": "19.142000",
        #                 "cumFillFee": "0.01914200",
        #                 "createdTime": "1776184415046",
        #                 "updatedTime": "1776184415058"
        #             }
        #         ]
        #     }
        #
        data = self.safe_list(message, 'd', [])
        symbols: dict = {}
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        orders = self.orders
        for i in range(0, len(data)):
            rawOrder = self.safe_dict(data, i, {})
            parsed = self.parse_ws_order(rawOrder)
            orders.append(parsed)
            symbol = parsed['symbol']
            symbols[symbol] = True
        messageHash = 'orders'
        symbolKeys = list(symbols.keys())
        market = self.get_market_from_symbols(symbolKeys)
        if market['contract']:
            messageHash = 'contractOrders'
        for i in range(0, len(symbolKeys)):
            symbol = symbolKeys[i]
            symbolMessageHash = messageHash + '::' + symbol
            client.resolve(orders, symbolMessageHash)
        client.resolve(self.orders, messageHash)

    def parse_ws_order(self, order, market=None):
        #
        # spot
        #     {
        #         "id": "738970996765098600",
        #         "symbol": "DOGEUSDT",
        #         "baseCoin": "DOGE",
        #         "quoteCoin": "USDT",
        #         "orderSide": "SELL",
        #         "price": "0",
        #         "size": "200.0",
        #         "value": "0",
        #         "clientOrderId": "b-WEEX111125-bf78d975ca38422bb6ea65",
        #         "type": "MARKET",
        #         "timeInForce": "IOC",
        #         "reduceOnly": False,
        #         "triggerPrice": "0",
        #         "orderSource": "API",
        #         "openTpslParentOrderId": "0",
        #         "setOpenTp": False,
        #         "setOpenSl": False,
        #         "takerFeeRate": "0.001",
        #         "makerFeeRate": "0.001",
        #         "feeDiscount": "1",
        #         "takerFeeDiscount": "1",
        #         "makerFeeDiscount": "1",
        #         "status": "FILLED",
        #         "triggerTime": "0",
        #         "triggerPriceTime": "0",
        #         "triggerPriceValue": "0",
        #         "cancelReason": "UNKNOWN_ORDER_CANCEL_REASON",
        #         "latestFillPrice": "0.09571",
        #         "maxFillPrice": "0.09571",
        #         "minFillPrice": "0.09571",
        #         "cumFillSize": "200.0",
        #         "cumFillValue": "19.142000",
        #         "cumFillFee": "0.01914200",
        #         "createdTime": "1776184415046",
        #         "updatedTime": "1776184415058"
        #     }
        #
        # swap
        #     {
        #         "id": "617414920861909658",
        #         "coin": "USDT",
        #         "symbol": "BTCUSDT",
        #         "marginMode": "CROSSED",
        #         "separatedMode": "COMBINED",
        #         "separatedOpenOrderId": "0",
        #         "positionSide": "LONG",
        #         "orderSide": "BUY",
        #         "price": "0.0",
        #         "size": "0.10000",
        #         "clientOrderId": "1747203186927FPIZRP",
        #         "type": "MARKET",
        #         "timeInForce": "IOC",
        #         "reduceOnly": False,
        #         "triggerPrice": "0",
        #         "triggerPriceType": "CONTRACT_PRICE",
        #         "orderSource": "WEB",
        #         "openTpslParentOrderId": "0",
        #         "positionTpsl": False,
        #         "setOpenTp": False,
        #         "setOpenSl": False,
        #         "leverage": "20",
        #         "takerFeeRate": "0.0006",
        #         "makerFeeRate": "0.0002",
        #         "feeDiscount": "1",
        #         "liquidateFeeRate": "0.01",
        #         "status": "PENDING",
        #         "triggerTime": "0",
        #         "triggerPriceTime": "0",
        #         "triggerPriceValue": "0",
        #         "cancelReason": "UNKNOWN_ORDER_CANCEL_REASON",
        #         "latestFillPrice": "0",
        #         "maxFillPrice": "0",
        #         "minFillPrice": "0",
        #         "cumFillSize": "0",
        #         "cumFillValue": "0",
        #         "cumFillFee": "0",
        #         "cumLiquidateFee": "0",
        #         "cumRealizePnl": "0",
        #         "createdTime": "1747203188148",
        #         "updatedTime": "1747203188148"
        #     }
        #
        timestamp = self.safe_integer(order, 'createdTime')
        marketId = self.safe_string(order, 'symbol')
        marketType = 'spot'
        positionSide = self.safe_string(order, 'positionSide')
        if positionSide is not None:
            marketType = 'swap'
        market = self.safe_market(marketId, None, None, marketType)
        side = self.safe_string_lower(order, 'orderSide')
        fee = None
        commission = self.safe_string(order, 'cumFillFee')
        if commission is not None:
            commissionAsset = self.safe_string(order, 'coin')
            feeCurrency = self.safe_currency_code(commissionAsset)
            if marketType == 'spot':
                if side == 'buy':
                    feeCurrency = market['base']
                else:
                    feeCurrency = market['quote']
            fee = {
                'cost': commission,
                'currency': feeCurrency,
            }
        rawStatus = self.safe_string_lower(order, 'status')
        rawType = self.safe_string(order, 'type')
        triggerPrice = self.omit_zero(self.safe_string(order, 'triggerPrice'))
        stopLossPrice = None
        takeProfitPrice = None
        if rawType == 'TAKE_PROFIT_MARKET' or rawType == 'TAKE_PROFIT':
            takeProfitPrice = triggerPrice
        elif rawType == 'STOP_LOSS' or rawType == 'STOP' or rawType == 'STOP_MARKET':
            stopLossPrice = triggerPrice
        return self.safe_order({
            'id': self.safe_string(order, 'id'),
            'clientOrderId': self.safe_string(order, 'clientOrderId'),
            'symbol': market['symbol'],
            'type': self.parseOrderType(rawType),
            'timeInForce': self.safe_string(order, 'timeInForce'),
            'postOnly': None,
            'reduceOnly': self.safe_bool(order, 'reduceOnly'),
            'side': side,
            'amount': self.safe_string(order, 'size'),
            'price': self.safe_string(order, 'price'),
            'triggerPrice': triggerPrice,
            'cost': self.safe_string(order, 'cumFillValue'),
            'filled': self.safe_string(order, 'cumFillSize'),
            'remaining': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': fee,
            'status': self.parse_order_status(rawStatus),
            'lastTradeTimestamp': None,
            'lastUpdateTimestamp': self.safe_integer(order, 'updatedTime'),
            'average': None,
            'trades': None,
            'stopLossPrice': stopLossPrice,
            'takeProfitPrice': takeProfitPrice,
            'info': order,
        }, market)

    async def watch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders

        https://www.weex.com/api-doc/spot/Websocket/private/Account-Channel
        https://www.weex.com/api-doc/contract/Websocket/private/Account-Channel

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.type]: 'spot' or 'swap', default is 'spot'
        :returns dict: a `balance structure <https://docs.ccxt.com/?id=balance-structure>`
        """
        await self.load_markets()
        type = None
        type, params = self.handle_market_type_and_params('watchBalance', None, params)
        isContract = (type != 'spot')
        urlType = 'contract' if isContract else 'spot'
        url = self.urls['api']['ws'][urlType] + '/private'
        self.authenticate(url)
        client = self.client(url)
        self.set_balance_cache(client, type)
        options = self.safe_dict(self.options, 'watchBalance')
        fetchBalanceSnapshot = self.safe_bool(options, 'fetchBalanceSnapshot', False)
        awaitBalanceSnapshot = self.safe_bool(options, 'awaitBalanceSnapshot', True)
        if fetchBalanceSnapshot and awaitBalanceSnapshot:
            await client.future(type + ':fetchBalanceSnapshot')
        messageHash = type + ':' + 'balance'
        return await self.subscribe_private(messageHash, type, 'account', isContract, params)

    def set_balance_cache(self, client: Client, type):
        if (type in client.subscriptions) and (type in self.balance):
            return
        options = self.safe_dict(self.options, 'watchBalance')
        fetchBalanceSnapshot = self.safe_bool(options, 'fetchBalanceSnapshot', False)
        if fetchBalanceSnapshot:
            messageHash = type + ':fetchBalanceSnapshot'
            if not (messageHash in client.futures):
                client.future(messageHash)
                self.spawn(self.load_balance_snapshot, client, messageHash, type)
        else:
            self.balance[type] = {}

    async def load_balance_snapshot(self, client, messageHash, type):
        params: dict = {
            'type': type,
        }
        response = await self.fetch_balance(params)
        self.balance[type] = self.extend(response, self.safe_value(self.balance, type, {}))
        # don't remove the future from the .futures cache
        if messageHash in client.futures:
            future = client.futures[messageHash]
            future.resolve()
            client.resolve(self.balance[type], type + ':balance')

    def handle_balance(self, client: Client, message):
        #
        # spot
        #     {
        #         "e": "account",
        #         "E": 1776187844633,
        #         "v": 178,
        #         "msgEvent": "DepositUpdate",
        #         "d": [
        #             {
        #                 "coin": "USDT",
        #                 "equity": "47.98428060",
        #                 "available": "47.98428060",
        #                 "frozen": "0"
        #             }
        #         ]
        #     }
        #
        # coontract
        #     {
        #         "e": "account",
        #         "E": 1776189629849,
        #         "v": 281,
        #         "msgEvent": "DepositUpdate",
        #         "d": [
        #             {
        #                 "coin": "USDT",
        #                 "marginMode": "CROSSED",
        #                 "crossSymbol": "0",
        #                 "isolatedPositionId": "0",
        #                 "amount": "0.00000000",
        #                 "pendingDepositAmount": "20.00000000",
        #                 "pendingWithdrawAmount": "0.00000000",
        #                 "pendingTransferInAmount": "0",
        #                 "pendingTransferOutAmount": "0",
        #                 "liquidating": False,
        #                 "legacyAmount": "0.00000000",
        #                 "cumDepositAmount": "167.50000925",
        #                 "cumWithdrawAmount": "166.94609514",
        #                 "cumTransferInAmount": "0",
        #                 "cumTransferOutAmount": "0",
        #                 "cumMarginMoveInAmount": "10.86162763",
        #                 "cumMarginMoveOutAmount": "10.83205378",
        #                 "cumPositionOpenLongAmount": "305.59400",
        #                 "cumPositionOpenShortAmount": "238.95700",
        #                 "cumPositionCloseLongAmount": "305.86600000",
        #                 "cumPositionCloseShortAmount": "238.94700000",
        #                 "cumPositionFillFeeAmount": "0.00761040",
        #                 "cumPositionLiquidateFeeAmount": "0",
        #                 "cumPositionFundingAmount": "0.00049824",
        #                 "cumOrderFillFeeIncomeAmount": "0",
        #                 "cumOrderLiquidateFeeIncomeAmount": "0",
        #                 "createdTime": "1775605824300",
        #                 "updatedTime": "1776189629849"
        #             }
        #         ]
        #     }
        #
        url = client.url
        accountType = 'spot'
        if url.find('contract') >= 0:
            accountType = 'swap'
        messageHash = accountType + ':balance'
        if self.balance[accountType] is None:
            self.balance[accountType] = {}
        self.balance[accountType]['info'] = message
        balanceUpdates = self.safe_list(message, 'd', [])
        for i in range(0, len(balanceUpdates)):
            entry = self.safe_dict(balanceUpdates, i)
            currencyId = self.safe_string(entry, 'coin')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string_2(entry, 'available', 'amount')
            account['used'] = self.safe_string(entry, 'frozen')
            account['total'] = self.safe_string_2(entry, 'equity', 'legacyAmount')
            self.balance[accountType][code] = account
        timestamp = self.safe_integer(message, 'E')
        self.balance[accountType]['timestamp'] = timestamp
        self.balance[accountType]['datetime'] = self.iso8601(timestamp)
        self.balance[accountType] = self.safe_balance(self.balance[accountType])
        client.resolve(self.balance[accountType], messageHash)

    async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
        """

        https://www.weex.com/api-doc/contract/Websocket/private/Positions-Channel

        watch all open positions
        :param str[]|None symbols: list of unified market symbols
        :param int [since]: the earliest time in ms to fetch positions for
        :param int [limit]: the maximum number of position structures to retrieve
        :param dict params: extra parameters specific to the exchange API endpoint
        :param int [params.accountNumber]: account number to query orders for, required
        :returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
        """
        await self.load_markets()
        url = self.urls['api']['ws']['contract'] + '/private'
        self.authenticate(url)
        client = self.client(url)
        symbols = self.market_symbols(symbols, 'swap', True)
        messageHash = 'positions'
        subscriptionHash = messageHash
        if symbols is not None:
            messageHash += '::' + ','.join(symbols)
        channel = 'positions'
        self.set_positions_cache(client, params)
        fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
        awaitPositionsSnapshot = self.handle_option('watchPositions', 'awaitPositionsSnapshot', True)
        if fetchPositionsSnapshot and awaitPositionsSnapshot and self.positions is None:
            snapshot = await client.future('fetchPositionsSnapshot')
            return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
        newPositions = await self.subscribe_private(messageHash, subscriptionHash, channel, True, params)
        if self.newUpdates:
            return newPositions
        return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit, True)

    def set_positions_cache(self, client: Client, params: dict = {}):
        fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', False)
        if fetchPositionsSnapshot:
            messageHash = 'fetchPositionsSnapshot'
            if not (messageHash in client.futures):
                client.future(messageHash)
                self.spawn(self.load_positions_snapshot, client, messageHash, params)
        else:
            self.positions = ArrayCacheBySymbolById()

    async def load_positions_snapshot(self, client, messageHash, params):
        positions = await self.fetch_positions(None, params)
        self.positions = ArrayCacheBySymbolById()
        cache = self.positions
        for i in range(0, len(positions)):
            position = positions[i]
            cache.append(position)
        # don't remove the future from the .futures cache
        future = client.futures[messageHash]
        future.resolve(cache)
        client.resolve(cache, 'positions')

    async def un_watch_positions(self, symbols: Strings = None, params={}) -> Any:
        """
        unWatches all open positions

        https://www.weex.com/api-doc/contract/Websocket/private/Positions-Channel

        :param str[] [symbols]: not used by the exchange, unsubscription from positions is global for all symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: status of the unwatch request
        """
        if symbols is not None:
            raise NotSupported(self.id + ' unWatchPositions does not support a symbols argument. Unsubscribing from positions is global for all symbols.')
        subHash = 'positions'
        unSubHash = 'unsubscribe::' + subHash
        channel = 'positions'
        subscription = {
            'unsubscribe': True,
            'messageHashes': [unSubHash],
            'subMessageHashes': [subHash],
            'topic': 'positions',
            'subHashIsPrefix': True,
        }
        return await self.subscribe_private(unSubHash, unSubHash, channel, True, params, subscription)

    def handle_positions(self, client, message):
        #
        #     {
        #         "e": "positions",
        #         "E": 1776192398399,
        #         "v": 319,
        #         "msgEvent": "OrderUpdate",
        #         "d": [
        #             {
        #                 "id": "739004481374519656",
        #                 "coin": "USDT",
        #                 "symbol": "DOGEUSDT",
        #                 "side": "LONG",
        #                 "marginMode": "CROSSED",
        #                 "separatedMode": "COMBINED",
        #                 "separatedOpenOrderId": "0",
        #                 "leverage": "11",
        #                 "size": "100",
        #                 "openValue": "9.31100",
        #                 "openFee": "0.00744880",
        #                 "fundingFee": "0",
        #                 "isolatedMargin": "0",
        #                 "autoAppendIsolatedMargin": False,
        #                 "cumOpenSize": "100",
        #                 "cumOpenValue": "9.31100",
        #                 "cumOpenFee": "0.00744880",
        #                 "cumCloseSize": "0",
        #                 "cumCloseValue": "0",
        #                 "cumCloseFee": "0",
        #                 "cumFundingFee": "0",
        #                 "cumLiquidateFee": "0",
        #                 "createdMatchSequenceId": "5792711540",
        #                 "updatedMatchSequenceId": "5792711540",
        #                 "createdTime": "1776192398399",
        #                 "updatedTime": "1776192398399"
        #             }
        #         ]
        #     }
        #
        if self.positions is None:
            self.positions = ArrayCacheBySymbolById()
        cache = self.positions
        newPositions = []
        data = self.safe_list(message, 'd', [])
        for i in range(0, len(data)):
            rawPosition = self.safe_dict(data, i, {})
            position = self.parse_ws_position(rawPosition)
            cache.append(position)
            newPositions.append(position)
        messageHashes = self.find_message_hashes(client, 'positions::')
        for i in range(0, len(messageHashes)):
            messageHash = messageHashes[i]
            parts = messageHash.split('::')
            symbolsString = parts[1]
            symbols = symbolsString.split(',')
            positions = self.filter_by_array(newPositions, 'symbol', symbols, False)
            if not self.is_empty(positions):
                client.resolve(positions, messageHash)
        client.resolve(newPositions, 'positions')

    def parse_ws_position(self, position, market=None):
        # same api
        return self.parse_position(position, market)

    def get_market_from_client_and_message(self, client: Client, message):
        url = client.url
        marketType = 'spot'
        if url.find('contract') >= 0:
            marketType = 'swap'
        marketId = self.safe_string(message, 's')
        market = self.safe_market(marketId, None, None, marketType)
        return market

    async def pong(self, client: Client, message):
        #
        #     {"event": "ping", "time": "1776078750000"} - public
        #
        #     {"type": "ping", "time": "1776172740000"} - private
        #
        response: dict = {
            'id': self.request_id(),
            'method': 'PONG',
        }
        await client.send(response)

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

    def handle_subscription_status(self, client: Client, message):
        #
        #     {"result": True, "id": 2}
        #
        id = self.safe_string(message, 'id')
        subscriptionsById = self.index_by(client.subscriptions, 'id')
        subscription = self.safe_dict(subscriptionsById, id, {})
        unsubscribe = self.safe_bool(subscription, 'unsubscribe', False)
        if unsubscribe:
            subHashIsPrefix = self.safe_bool(subscription, 'subHashIsPrefix', False)
            messageHashes = self.safe_list(subscription, 'messageHashes', [])
            subHashes = self.safe_list(subscription, 'subMessageHashes', [])
            for i in range(0, len(messageHashes)):
                unSubHash = self.safe_string(messageHashes, i)
                subHash = self.safe_string(subHashes, i)
                self.clean_unsubscription(client, subHash, unSubHash, subHashIsPrefix)
            self.clean_cache(subscription)
        return message

    def handle_error_message(self, client: Client, message):
        #
        #     {
        #         "result": False,
        #         "id": 1,
        #         "msg": "INVALID_ARGUMENT: invalid symbol : ASDFS_SPBL"
        #     }
        #
        result = self.safe_bool(message, 'result', True)
        if not result:
            msg = self.safe_string(message, 'msg', '')
            feedback = self.id + ' ' + self.json(message)
            try:
                self.throw_exactly_matched_exception(self.exceptions['exact'], msg, feedback)
                self.throw_broadly_matched_exception(self.exceptions['broad'], msg, feedback)
                raise ExchangeError(feedback)
            except Exception as error:
                client.reject(error)
                return True
        return False

    def handle_message(self, client: Client, message):
        #
        #     {"id": "5", "method": "PONG"}
        #
        #     {"result": True, "id": 2}
        #
        #     {
        #         "result": False,
        #         "id": 1,
        #         "msg": "INVALID_ARGUMENT: invalid symbol : ASDFS_SPBL"
        #     }
        #
        if self.handle_error_message(client, message):
            return
        id = self.safe_string(message, 'id')
        if id is not None:
            self.handle_subscription_status(client, message)
            return
        event = self.safe_string_n(message, ['e', 'event', 'type'])
        if event == 'ping':
            self.handle_ping(client, message)
        elif event == 'ticker':
            self.handle_ticker(client, message)
        elif (event == 'trade') or (event == 'tradeSnapshot'):
            self.handle_trade(client, message)
        elif (event == 'kline') or (event == 'klineSnapshot'):
            self.handle_ohlcv(client, message)
        elif (event == 'depth') or (event == 'depthSnapshot'):
            self.handle_order_book(client, message)
        elif event == 'bookTicker':
            self.handle_bid_ask(client, message)
        elif event == 'fill':
            self.handle_my_trades(client, message)
        elif event == 'orders':
            self.handle_orders(client, message)
        elif event == 'account':
            self.handle_balance(client, message)
        elif event == 'positions':
            self.handle_positions(client, message)
