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

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

import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
import hashlib
from ccxt.base.types import Balances, Int, Order, OrderBook, Str, Ticker, 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 ArgumentsRequired
from ccxt.base.errors import NotSupported


class deribit(ccxt.async_support.deribit):

    def describe(self):
        return self.deep_extend(super(deribit, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchTicker': True,
                'watchTickers': False,
                'watchTrades': True,
                'watchTradesForSymbols': True,
                'watchMyTrades': True,
                'watchOrders': True,
                'watchOrderBook': True,
                'watchOrderBookForSymbols': True,
                'watchOHLCV': True,
                'watchOHLCVForSymbols': True,
            },
            'urls': {
                'test': {
                    'ws': 'wss://test.deribit.com/ws/api/v2',
                },
                'api': {
                    'ws': 'wss://www.deribit.com/ws/api/v2',
                },
            },
            'options': {
                'ws': {
                    'timeframes': {
                        '1m': '1',
                        '3m': '3',
                        '5m': '5',
                        '15m': '15',
                        '30m': '30',
                        '1h': '60',
                        '2h': '120',
                        '4h': '180',
                        '6h': '360',
                        '12h': '720',
                        '1d': '1D',
                    },
                    # watchTrades replacement
                    'watchTradesForSymbols': {
                        'interval': '100ms',  # 100ms, agg2, raw
                    },
                    # watchOrderBook replacement
                    'watchOrderBookForSymbols': {
                        'interval': '100ms',  # 100ms, agg2, raw
                        'useDepthEndpoint': False,  # if True, it will use the {books.group.depth.interval} endpoint instead of the {books.interval} endpoint
                        'depth': '20',  # 1, 10, 20
                        'group': 'none',  # none, 1, 2, 5, 10, 25, 100, 250
                    },
                },
                'currencies': ['BTC', 'ETH', 'SOL', 'USDC'],
            },
            'streaming': {
            },
            'exceptions': {
            },
        })

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

    async def watch_balance(self, params={}) -> Balances:
        """
        :see: https://docs.deribit.com/#user-portfolio-currency
        watch balance and get the amount of funds available for trading or funds locked in orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        await self.authenticate(params)
        messageHash = 'balance'
        url = self.urls['api']['ws']
        currencies = self.safe_value(self.options, 'currencies', [])
        channels = []
        for i in range(0, len(currencies)):
            currencyCode = currencies[i]
            channels.append('user.portfolio.' + currencyCode)
        subscribe: dict = {
            'jsonrpc': '2.0',
            'method': 'private/subscribe',
            'params': {
                'channels': channels,
            },
            'id': self.request_id(),
        }
        request = self.deep_extend(subscribe, params)
        return await self.watch(url, messageHash, request, messageHash, request)

    def handle_balance(self, client: Client, message):
        #
        # subscription
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "user.portfolio.btc",
        #             "data": {
        #                 "total_pl": 0,
        #                 "session_upl": 0,
        #                 "session_rpl": 0,
        #                 "projected_maintenance_margin": 0,
        #                 "projected_initial_margin": 0,
        #                 "projected_delta_total": 0,
        #                 "portfolio_margining_enabled": False,
        #                 "options_vega": 0,
        #                 "options_value": 0,
        #                 "options_theta": 0,
        #                 "options_session_upl": 0,
        #                 "options_session_rpl": 0,
        #                 "options_pl": 0,
        #                 "options_gamma": 0,
        #                 "options_delta": 0,
        #                 "margin_balance": 0.0015,
        #                 "maintenance_margin": 0,
        #                 "initial_margin": 0,
        #                 "futures_session_upl": 0,
        #                 "futures_session_rpl": 0,
        #                 "futures_pl": 0,
        #                 "fee_balance": 0,
        #                 "estimated_liquidation_ratio_map": {},
        #                 "estimated_liquidation_ratio": 0,
        #                 "equity": 0.0015,
        #                 "delta_total_map": {},
        #                 "delta_total": 0,
        #                 "currency": "BTC",
        #                 "balance": 0.0015,
        #                 "available_withdrawal_funds": 0.0015,
        #                 "available_funds": 0.0015
        #             }
        #         }
        #     }
        #
        params = self.safe_value(message, 'params', {})
        data = self.safe_value(params, 'data', {})
        self.balance['info'] = data
        currencyId = self.safe_string(data, 'currency')
        currencyCode = self.safe_currency_code(currencyId)
        balance = self.parse_balance(data)
        self.balance[currencyCode] = balance
        messageHash = 'balance'
        client.resolve(self.balance, messageHash)

    async def watch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        :see: https://docs.deribit.com/#ticker-instrument_name-interval
        watches a price ticker, a statistical calculation with the information for a specific market.
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.interval]: specify aggregation and frequency of notifications. Possible values: 100ms, raw
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        url = self.urls['api']['ws']
        interval = self.safe_string(params, 'interval', '100ms')
        params = self.omit(params, 'interval')
        await self.load_markets()
        if interval == 'raw':
            await self.authenticate()
        channel = 'ticker.' + market['id'] + '.' + interval
        message: dict = {
            'jsonrpc': '2.0',
            'method': 'public/subscribe',
            'params': {
                'channels': ['ticker.' + market['id'] + '.' + interval],
            },
            'id': self.request_id(),
        }
        request = self.deep_extend(message, params)
        return await self.watch(url, channel, request, channel, request)

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "ticker.BTC_USDC-PERPETUAL.raw",
        #             "data": {
        #                 "timestamp": 1655393725040,
        #                 "stats": [Object],
        #                 "state": "open",
        #                 "settlement_price": 21729.5891,
        #                 "open_interest": 164.501,
        #                 "min_price": 20792.9376,
        #                 "max_price": 21426.225,
        #                 "mark_price": 21109.555,
        #                 "last_price": 21132,
        #                 "instrument_name": "BTC_USDC-PERPETUAL",
        #                 "index_price": 21122.3937,
        #                 "funding_8h": -0.00022427,
        #                 "estimated_delivery_price": 21122.3937,
        #                 "current_funding": -0.00010782,
        #                 "best_bid_price": 21106,
        #                 "best_bid_amount": 1.143,
        #                 "best_ask_price": 21113,
        #                 "best_ask_amount": 0.327
        #             }
        #         }
        #     }
        #
        params = self.safe_value(message, 'params', {})
        data = self.safe_value(params, 'data', {})
        marketId = self.safe_string(data, 'instrument_name')
        symbol = self.safe_symbol(marketId)
        ticker = self.parse_ticker(data)
        messageHash = self.safe_string(params, 'channel')
        self.tickers[symbol] = ticker
        client.resolve(ticker, messageHash)

    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
        :see: https://docs.deribit.com/#trades-instrument_name-interval
        :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
        :param str [params.interval]: specify aggregation and frequency of notifications. Possible values: 100ms, raw
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        params['callerMethodName'] = 'watchTrades'
        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
        :see: https://docs.deribit.com/#trades-instrument_name-interval
        :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>`
        """
        interval = None
        interval, params = self.handle_option_and_params(params, 'watchTradesForSymbols', 'interval', '100ms')
        if interval == 'raw':
            await self.authenticate()
        trades = await self.watch_multiple_wrapper('trades', interval, symbols, params)
        if self.newUpdates:
            first = self.safe_dict(trades, 0)
            tradeSymbol = self.safe_string(first, 'symbol')
            limit = trades.getLimit(tradeSymbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def handle_trades(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "trades.BTC_USDC-PERPETUAL.100ms",
        #             "data": [{
        #                 "trade_seq": 501899,
        #                 "trade_id": "USDC-2436803",
        #                 "timestamp": 1655397355998,
        #                 "tick_direction": 2,
        #                 "price": 21026,
        #                 "mark_price": 21019.9719,
        #                 "instrument_name": "BTC_USDC-PERPETUAL",
        #                 "index_price": 21031.7847,
        #                 "direction": "buy",
        #                 "amount": 0.049
        #             }]
        #         }
        #     }
        #
        params = self.safe_dict(message, 'params', {})
        channel = self.safe_string(params, 'channel', '')
        parts = channel.split('.')
        marketId = self.safe_string(parts, 1)
        interval = self.safe_string(parts, 2)
        symbol = self.safe_symbol(marketId)
        market = self.safe_market(marketId)
        trades = self.safe_list(params, 'data', [])
        if self.safe_value(self.trades, symbol) is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.trades[symbol] = ArrayCache(limit)
        stored = self.trades[symbol]
        for i in range(0, len(trades)):
            trade = trades[i]
            parsed = self.parse_trade(trade, market)
            stored.append(parsed)
        self.trades[symbol] = stored
        messageHash = 'trades|' + symbol + '|' + interval
        client.resolve(self.trades[symbol], messageHash)

    async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of trades associated with the user
        :see: https://docs.deribit.com/#user-trades-instrument_name-interval
        :param str symbol: unified symbol of the market to fetch trades for. Use 'any' to watch all trades
        :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
        :param str [params.interval]: specify aggregation and frequency of notifications. Possible values: 100ms, raw
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        await self.authenticate(params)
        if symbol is not None:
            await self.load_markets()
            symbol = self.symbol(symbol)
        url = self.urls['api']['ws']
        interval = self.safe_string(params, 'interval', 'raw')
        params = self.omit(params, 'interval')
        channel = 'user.trades.any.any.' + interval
        message: dict = {
            'jsonrpc': '2.0',
            'method': 'private/subscribe',
            'params': {
                'channels': [channel],
            },
            'id': self.request_id(),
        }
        request = self.deep_extend(message, params)
        trades = await self.watch(url, channel, request, channel, request)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    def handle_my_trades(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "user.trades.any.any.raw",
        #             "data": [{
        #                 "trade_seq": 149546319,
        #                 "trade_id": "219381310",
        #                 "timestamp": 1655421193564,
        #                 "tick_direction": 0,
        #                 "state": "filled",
        #                 "self_trade": False,
        #                 "reduce_only": False,
        #                 "profit_loss": 0,
        #                 "price": 20236.5,
        #                 "post_only": False,
        #                 "order_type": "market",
        #                 "order_id": "46108941243",
        #                 "matching_id": null,
        #                 "mark_price": 20233.96,
        #                 "liquidity": "T",
        #                 "instrument_name": "BTC-PERPETUAL",
        #                 "index_price": 20253.31,
        #                 "fee_currency": "BTC",
        #                 "fee": 2.5e-7,
        #                 "direction": "buy",
        #                 "amount": 10
        #             }]
        #         }
        #     }
        #
        params = self.safe_value(message, 'params', {})
        channel = self.safe_string(params, 'channel', '')
        trades = self.safe_value(params, 'data', [])
        cachedTrades = self.myTrades
        if cachedTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            cachedTrades = ArrayCacheBySymbolById(limit)
        parsed = self.parse_trades(trades)
        marketIds: dict = {}
        for i in range(0, len(parsed)):
            trade = parsed[i]
            cachedTrades.append(trade)
            symbol = trade['symbol']
            marketIds[symbol] = True
        client.resolve(cachedTrades, channel)

    async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        :see: https://docs.deribit.com/#book-instrument_name-group-depth-interval
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.interval]: Frequency of notifications. Events will be aggregated over self interval. Possible values: 100ms, raw
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        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
        :see: https://docs.deribit.com/#book-instrument_name-group-depth-interval
        :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
        """
        interval = None
        interval, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'interval', '100ms')
        if interval == 'raw':
            await self.authenticate()
        descriptor = ''
        useDepthEndpoint = None  # for more info, see comment in .options
        useDepthEndpoint, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'useDepthEndpoint', False)
        if useDepthEndpoint:
            depth = None
            depth, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'depth', '20')
            group = None
            group, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'group', 'none')
            descriptor = group + '.' + depth + '.' + interval
        else:
            descriptor = interval
        orderbook = await self.watch_multiple_wrapper('book', descriptor, symbols, params)
        return orderbook.limit()

    def handle_order_book(self, client: Client, message):
        #
        #  snapshot
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "book.BTC_USDC-PERPETUAL.raw",
        #             "data": {
        #                 "type": "snapshot",
        #                 "timestamp": 1655395057025,
        #                 "instrument_name": "BTC_USDC-PERPETUAL",
        #                 "change_id": 1550694837,
        #                 "bids": [
        #                     ["new", 20987, 0.487],
        #                     ["new", 20986, 0.238],
        #                 ],
        #                 "asks": [
        #                     ["new", 20999, 0.092],
        #                     ["new", 21000, 1.238],
        #                 ]
        #             }
        #         }
        #     }
        #
        #  change
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "book.BTC_USDC-PERPETUAL.raw",
        #             "data": {
        #                 "type": "change",
        #                 "timestamp": 1655395168086,
        #                 "prev_change_id": 1550724481,
        #                 "instrument_name": "BTC_USDC-PERPETUAL",
        #                 "change_id": 1550724483,
        #                 "bids": [
        #                     ["new", 20977, 0.109],
        #                     ["delete", 20975, 0]
        #                 ],
        #                 "asks": []
        #             }
        #         }
        #     }
        #
        params = self.safe_value(message, 'params', {})
        data = self.safe_value(params, 'data', {})
        channel = self.safe_string(params, 'channel')
        parts = channel.split('.')
        descriptor = ''
        partsLength = len(parts)
        isDetailed = partsLength == 5
        if isDetailed:
            group = self.safe_string(parts, 2)
            depth = self.safe_string(parts, 3)
            interval = self.safe_string(parts, 4)
            descriptor = group + '.' + depth + '.' + interval
        else:
            interval = self.safe_string(parts, 2)
            descriptor = interval
        marketId = self.safe_string(data, 'instrument_name')
        symbol = self.safe_symbol(marketId)
        timestamp = self.safe_integer(data, 'timestamp')
        if not (symbol in self.orderbooks):
            self.orderbooks[symbol] = self.counted_order_book()
        storedOrderBook = self.orderbooks[symbol]
        asks = self.safe_list(data, 'asks', [])
        bids = self.safe_list(data, 'bids', [])
        self.handle_deltas(storedOrderBook['asks'], asks)
        self.handle_deltas(storedOrderBook['bids'], bids)
        storedOrderBook['nonce'] = timestamp
        storedOrderBook['timestamp'] = timestamp
        storedOrderBook['datetime'] = self.iso8601(timestamp)
        storedOrderBook['symbol'] = symbol
        self.orderbooks[symbol] = storedOrderBook
        messageHash = 'book|' + symbol + '|' + descriptor
        client.resolve(storedOrderBook, messageHash)

    def clean_order_book(self, data):
        bids = self.safe_list(data, 'bids', [])
        asks = self.safe_list(data, 'asks', [])
        cleanedBids = []
        for i in range(0, len(bids)):
            cleanedBids.append([bids[i][1], bids[i][2]])
        cleanedAsks = []
        for i in range(0, len(asks)):
            cleanedAsks.append([asks[i][1], asks[i][2]])
        data['bids'] = cleanedBids
        data['asks'] = cleanedAsks
        return data

    def handle_delta(self, bookside, delta):
        price = delta[1]
        amount = delta[2]
        if delta[0] == 'new' or delta[0] == 'change':
            bookside.storeArray([price, amount, 1])
        elif delta[0] == 'delete':
            bookside.storeArray([price, amount, 0])

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

    async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        :see: https://docs.deribit.com/#user-orders-instrument_name-raw
        watches information on multiple orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
        """
        await self.load_markets()
        await self.authenticate(params)
        if symbol is not None:
            symbol = self.symbol(symbol)
        url = self.urls['api']['ws']
        currency = self.safe_string(params, 'currency', 'any')
        interval = self.safe_string(params, 'interval', 'raw')
        kind = self.safe_string(params, 'kind', 'any')
        params = self.omit(params, 'interval', 'currency', 'kind')
        channel = 'user.orders.' + kind + '.' + currency + '.' + interval
        message: dict = {
            'jsonrpc': '2.0',
            'method': 'private/subscribe',
            'params': {
                'channels': [channel],
            },
            'id': self.request_id(),
        }
        request = self.deep_extend(message, params)
        orders = await self.watch(url, channel, request, channel, request)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    def handle_orders(self, client: Client, message):
        # Does not return a snapshot of current orders
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "user.orders.any.any.raw",
        #             "data": {
        #                 "web": True,
        #                 "time_in_force": "good_til_cancelled",
        #                 "replaced": False,
        #                 "reduce_only": False,
        #                 "profit_loss": 0,
        #                 "price": 50000,
        #                 "post_only": False,
        #                 "order_type": "limit",
        #                 "order_state": "open",
        #                 "order_id": "46094375191",
        #                 "max_show": 10,
        #                 "last_update_timestamp": 1655401625037,
        #                 "label": '',
        #                 "is_liquidation": False,
        #                 "instrument_name": "BTC-PERPETUAL",
        #                 "filled_amount": 0,
        #                 "direction": "sell",
        #                 "creation_timestamp": 1655401625037,
        #                 "commission": 0,
        #                 "average_price": 0,
        #                 "api": False,
        #                 "amount": 10
        #             }
        #         }
        #     }
        #
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        params = self.safe_value(message, 'params', {})
        channel = self.safe_string(params, 'channel', '')
        data = self.safe_value(params, 'data', {})
        orders = []
        if isinstance(data, list):
            orders = self.parse_orders(data)
        else:
            order = self.parse_order(data)
            orders = [order]
        cachedOrders = self.orders
        for i in range(0, len(orders)):
            cachedOrders.append(orders[i])
        client.resolve(self.orders, channel)

    async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        :see: https://docs.deribit.com/#chart-trades-instrument_name-resolution
        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        symbol = self.symbol(symbol)
        ohlcvs = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params)
        return ohlcvs[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
        :see: https://docs.deribit.com/#chart-trades-instrument_name-resolution
        :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 int[][]: A list of candles ordered, open, high, low, close, volume
        """
        symbolsLength = len(symbolsAndTimeframes)
        if symbolsLength == 0 or not isinstance(symbolsAndTimeframes[0], list):
            raise ArgumentsRequired(self.id + " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like  [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]")
        symbol, timeframe, candles = await self.watch_multiple_wrapper('chart.trades', None, symbolsAndTimeframes, params)
        if self.newUpdates:
            limit = candles.getLimit(symbol, limit)
        filtered = self.filter_by_since_limit(candles, since, limit, 0, True)
        return self.create_ohlcv_object(symbol, timeframe, filtered)

    def handle_ohlcv(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "chart.trades.BTC_USDC-PERPETUAL.1",
        #             "data": {
        #                 "volume": 0,
        #                 "tick": 1655403420000,
        #                 "open": 20951,
        #                 "low": 20951,
        #                 "high": 20951,
        #                 "cost": 0,
        #                 "close": 20951
        #             }
        #         }
        #     }
        #
        params = self.safe_dict(message, 'params', {})
        channel = self.safe_string(params, 'channel', '')
        parts = channel.split('.')
        marketId = self.safe_string(parts, 2)
        rawTimeframe = self.safe_string(parts, 3)
        market = self.safe_market(marketId)
        symbol = market['symbol']
        wsOptions = self.safe_dict(self.options, 'ws', {})
        timeframes = self.safe_dict(wsOptions, 'timeframes', {})
        unifiedTimeframe = self.find_timeframe(rawTimeframe, timeframes)
        self.ohlcvs[symbol] = self.safe_dict(self.ohlcvs, symbol, {})
        if self.safe_value(self.ohlcvs[symbol], unifiedTimeframe) is None:
            limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
            self.ohlcvs[symbol][unifiedTimeframe] = ArrayCacheByTimestamp(limit)
        stored = self.ohlcvs[symbol][unifiedTimeframe]
        ohlcv = self.safe_dict(params, 'data', {})
        # data contains a single OHLCV candle
        parsed = self.parse_ws_ohlcv(ohlcv, market)
        stored.append(parsed)
        self.ohlcvs[symbol][unifiedTimeframe] = stored
        resolveData = [symbol, unifiedTimeframe, stored]
        messageHash = 'chart.trades|' + symbol + '|' + rawTimeframe
        client.resolve(resolveData, messageHash)

    def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
        #
        #    {
        #        "c": "28909.0",
        #        "o": "28915.4",
        #        "h": "28915.4",
        #        "l": "28896.1",
        #        "v": "27.6919",
        #        "T": 1696687499999,
        #        "t": 1696687440000
        #    }
        #
        return [
            self.safe_integer(ohlcv, 'tick'),
            self.safe_number(ohlcv, 'open'),
            self.safe_number(ohlcv, 'high'),
            self.safe_number(ohlcv, 'low'),
            self.safe_number(ohlcv, 'close'),
            self.safe_number(ohlcv, 'volume'),
        ]

    async def watch_multiple_wrapper(self, channelName: str, channelDescriptor: Str, symbolsArray=None, params={}):
        await self.load_markets()
        url = self.urls['api']['ws']
        rawSubscriptions = []
        messageHashes = []
        isOHLCV = (channelName == 'chart.trades')
        symbols = self.get_list_from_object_values(symbolsArray, 0) if isOHLCV else symbolsArray
        self.market_symbols(symbols, None, False)
        for i in range(0, len(symbolsArray)):
            current = symbolsArray[i]
            market = None
            if isOHLCV:
                market = self.market(current[0])
                unifiedTf = current[1]
                rawTf = self.safe_string(self.timeframes, unifiedTf, unifiedTf)
                channelDescriptor = rawTf
            else:
                market = self.market(current)
            message = channelName + '.' + market['id'] + '.' + channelDescriptor
            rawSubscriptions.append(message)
            messageHashes.append(channelName + '|' + market['symbol'] + '|' + channelDescriptor)
        request: dict = {
            'jsonrpc': '2.0',
            'method': 'public/subscribe',
            'params': {
                'channels': rawSubscriptions,
            },
            'id': self.request_id(),
        }
        extendedRequest = self.deep_extend(request, params)
        maxMessageByteLimit = 32768 - 1  # 'Message Too Big: limit 32768B'
        jsonedText = self.json(extendedRequest)
        if len(jsonedText) >= maxMessageByteLimit:
            raise ExchangeError(self.id + ' requested subscription length over limit, try to reduce symbols amount')
        return await self.watch_multiple(url, messageHashes, extendedRequest, rawSubscriptions)

    def handle_message(self, client: Client, message):
        #
        # error
        #     {
        #         "jsonrpc": "2.0",
        #         "id": 1,
        #         "error": {
        #             "message": "Invalid params",
        #             "data": {
        #                 "reason": "invalid format",
        #                 "param": "nonce"
        #             },
        #             "code": -32602
        #         },
        #         "usIn": "1655391709417993",
        #         "usOut": "1655391709418049",
        #         "usDiff": 56,
        #         "testnet": False
        #     }
        #
        # subscribe
        #     {
        #         "jsonrpc": "2.0",
        #         "id": 2,
        #         "result": ["ticker.BTC_USDC-PERPETUAL.raw"],
        #         "usIn": "1655393625889396",
        #         "usOut": "1655393625889518",
        #         "usDiff": 122,
        #         "testnet": False
        #     }
        #
        # notification
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "ticker.BTC_USDC-PERPETUAL.raw",
        #             "data": {
        #                 "timestamp": 1655393724752,
        #                 "stats": [Object],
        #                 "state": "open",
        #                 "settlement_price": 21729.5891,
        #                 "open_interest": 164.501,
        #                 "min_price": 20792.9001,
        #                 "max_price": 21426.1864,
        #                 "mark_price": 21109.4757,
        #                 "last_price": 21132,
        #                 "instrument_name": "BTC_USDC-PERPETUAL",
        #                 "index_price": 21122.3937,
        #                 "funding_8h": -0.00022427,
        #                 "estimated_delivery_price": 21122.3937,
        #                 "current_funding": -0.00011158,
        #                 "best_bid_price": 21106,
        #                 "best_bid_amount": 1.143,
        #                 "best_ask_price": 21113,
        #                 "best_ask_amount": 0.402
        #             }
        #         }
        #     }
        #
        error = self.safe_value(message, 'error')
        if error is not None:
            raise ExchangeError(self.id + ' ' + self.json(error))
        params = self.safe_value(message, 'params')
        channel = self.safe_string(params, 'channel')
        if channel is not None:
            parts = channel.split('.')
            channelId = self.safe_string(parts, 0)
            userHandlers: dict = {
                'trades': self.handle_my_trades,
                'portfolio': self.handle_balance,
                'orders': self.handle_orders,
            }
            handlers: dict = {
                'ticker': self.handle_ticker,
                'book': self.handle_order_book,
                'trades': self.handle_trades,
                'chart': self.handle_ohlcv,
                'user': self.safe_value(userHandlers, self.safe_string(parts, 1)),
            }
            handler = self.safe_value(handlers, channelId)
            if handler is not None:
                handler(client, message)
                return
            raise NotSupported(self.id + ' no handler found for self message ' + self.json(message))
        result = self.safe_value(message, 'result', {})
        accessToken = self.safe_string(result, 'access_token')
        if accessToken is not None:
            self.handle_authentication_message(client, message)

    def handle_authentication_message(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "id": 1,
        #         "result": {
        #             "token_type": "bearer",
        #             "scope": "account:read_write block_trade:read_write connection custody:read_write mainaccount name:ccxt trade:read_write wallet:read_write",
        #             "refresh_token": "1686927372328.1EzFBRmt.logRQWXkPA1oE_Tk0gRsls9Hau7YN6a321XUBnxvR4x6cryhbkKcniUJU-czA8_zKXrqQGpQmfoDwhLIjIsWCvRuu6otbg-LKWlrtTX1GQqLcPaTTHAdZGTMV-HM8HiS03QBd9MIXWRfF53sKj2hdR9nZPZ6MH1XrkpAZPB_peuEEB9wlcc3elzWEZFtCmiy1fnQ8TPHwAJMt3nuUmEcMLt_-F554qrsg_-I66D9xMiifJj4dBemdPfV_PkGPRIwIoKlxDjyv2-xfCw-4eKyo6Hu1m2h6gT1DPOTxSXcBgfBQjpi-_uY3iAIj7U6xjC46PHthEdquhEuCTZl7UfCRZSAWwZA",
        #             "expires_in": 31536000,
        #             "access_token": "1686923272328.1CkwEx-u.qHradpIulmuoeboKMEi8PkQ1_4DF8yFE2zywBTtkD32sruVC53b1HwL5OWRuh2nYAndXff4xuXIMRkkEfMAFCeq24prihxxinoS8DDVkKBxedGx4CUPJFeXjmh7wuRGqQOLg1plXOpbF3fwF2KPEkAuETwcpcVY6K9HUVjutNRfxFe2TR7CvuS9x8TATvoPeu7H1ezYl-LkKSaRifdTXuwituXgp4oDbPRyQLniEBWuYF9rY7qbABxuOJlXI1VZ63u7Bh0mGWei-KeVeqHGNpy6OgrFRPXPxa9_U7vaxCyHW3zZ9959TQ1QUMLWtUX-NLBEv3BT5eCieW9HORYIOKfsgkpd3"
        #         },
        #         "usIn": "1655391872327712",
        #         "usOut": "1655391872328515",
        #         "usDiff": 803,
        #         "testnet": False
        #     }
        #
        messageHash = 'authenticated'
        client.resolve(message, messageHash)
        return message

    async def authenticate(self, params={}):
        url = self.urls['api']['ws']
        client = self.client(url)
        time = self.milliseconds()
        timeString = self.number_to_string(time)
        nonce = timeString
        messageHash = 'authenticated'
        future = self.safe_value(client.subscriptions, messageHash)
        if future is None:
            self.check_required_credentials()
            requestId = self.request_id()
            signature = self.hmac(self.encode(timeString + '\n' + nonce + '\n'), self.encode(self.secret), hashlib.sha256)
            request: dict = {
                'jsonrpc': '2.0',
                'id': requestId,
                'method': 'public/auth',
                'params': {
                    'grant_type': 'client_signature',
                    'client_id': self.apiKey,
                    'timestamp': time,
                    'signature': signature,
                    'nonce': nonce,
                    'data': '',
                },
            }
            future = await self.watch(url, messageHash, self.extend(request, params), messageHash)
            client.subscriptions[messageHash] = future
        return future
