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

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

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


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

    def describe(self):
        return self.deep_extend(super(bybit, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchMyTrades': True,
                'watchOHLCV': True,
                'watchOrderBook': True,
                'watchOrders': True,
                'watchTicker': True,
                'watchTickers': False,  # for now
                'watchTrades': True,
            },
            'urls': {
                'api': {
                    'ws': {
                        'inverse': {
                            'public': 'wss://stream.{hostname}/realtime',
                            'private': 'wss://stream.{hostname}/realtime',
                        },
                        'linear': {
                            'public': 'wss://stream.{hostname}/realtime_public',
                            'private': 'wss://stream.{hostname}/realtime_private',
                        },
                        'spot': {
                            'public': 'wss://stream.{hostname}/spot/quote/ws/v2',
                            'private': 'wss://stream.{hostname}/spot/ws',
                        },
                        'usdc': {
                            'option': {
                                'public': 'wss://stream.{hostname}/trade/option/usdc/public/v1',
                                'private': 'wss://stream.{hostname}/trade/option/usdc/private/v1',
                            },
                            'swap': {
                                'public': 'wss://stream.{hostname}/perpetual/ws/v1/realtime_public',
                                'private': 'wss://stream.{hostname}/trade/option/usdc/private/v1',
                            },
                        },
                    },
                },
                'test': {
                    'ws': {
                        'inverse': {
                            'public': 'wss://stream-testnet.{hostname}/realtime',
                            'private': 'wss://stream-testnet.{hostname}/realtime',
                        },
                        'linear': {
                            'public': 'wss://stream-testnet.{hostname}/realtime_public',
                            'private': 'wss://stream-testnet.{hostname}/realtime_private',
                        },
                        'spot': {
                            'public': 'wss://stream-testnet.{hostname}/spot/quote/ws/v2',
                            'private': 'wss://stream-testnet.{hostname}/spot/ws',
                        },
                        'usdc': {
                            'option': {
                                'public': 'wss://stream-testnet.{hostname}/trade/option/usdc/public/v1',
                                'private': 'wss://stream-testnet.{hostname}/trade/option/usdc/private/v1',
                            },
                            'swap': {
                                'public': 'wss://stream-testnet.{hostname}/perpetual/ws/v1/realtime_public',
                                'private': 'wss://stream-testnet.{hostname}/trade/option/usdc/private/v1',
                            },
                        },
                    },
                },
            },
            'options': {
                'watchTicker': {
                    'name': 'realtimes',  # or bookTicker
                },
            },
            'streaming': {
                'ping': self.ping,
                'keepAlive': 20000,
            },
            'exceptions': {
                'ws': {
                    'exact': {
                    },
                },
            },
        })

    def get_url_by_market_type(self, symbol=None, isPrivate=False, method=None, params={}):
        accessibility = 'private' if isPrivate else 'public'
        isUsdcSettled = None
        isSpot = None
        type = None
        isLinear = None
        market = None
        url = self.urls['api']['ws']
        if symbol is not None:
            market = self.market(symbol)
            isUsdcSettled = market['settle'] == 'USDC'
            isSpot = market['spot']
            type = market['type']
            isLinear = market['linear']
        else:
            type, params = self.handle_market_type_and_params(method, None, params)
            defaultSubType = self.safe_string(self.options, 'defaultSubType', 'linear')
            subType = self.safe_string(params, 'subType', defaultSubType)
            defaultSettle = self.safe_string(self.options, 'defaultSettle')
            defaultSettle = self.safe_string_2(params, 'settle', 'defaultSettle', defaultSettle)
            isUsdcSettled = (defaultSettle == 'USDC')
            isSpot = (type == 'spot')
            isLinear = (subType == 'linear')
        if isSpot:
            url = url['spot'][accessibility]
        elif isUsdcSettled:
            url = url['usdc'][type][accessibility]
        elif isLinear:
            url = url['linear'][accessibility]
        else:
            # inverse
            url = url['inverse'][accessibility]
        url = self.implode_hostname(url)
        return url

    def clean_params(self, params):
        params = self.omit(params, ['type', 'subType', 'settle', 'defaultSettle'])
        return params

    async def watch_ticker(self, symbol, params={}):
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the bybit api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        messageHash = 'ticker:' + market['symbol']
        url = self.get_url_by_market_type(symbol, False, params)
        params = self.clean_params(params)
        if market['spot']:
            options = self.safe_value(self.options, 'watchTicker', {})
            channel = self.safe_string(options, 'name', 'realtimes')
            reqParams = {
                'symbol': market['id'],
            }
            return await self.watch_spot_public(url, channel, messageHash, reqParams, params)
        else:
            channel = 'instrument_info.100ms.' + market['id']
            reqParams = [channel]
            return await self.watch_contract_public(url, messageHash, reqParams, params)

    def handle_ticker(self, client, message):
        #
        # {
        #     topic: 'bookTicker',
        #     params: {symbol: 'BTCUSDT', binary: 'false', symbolName: 'BTCUSDT'},
        #     data: {
        #       symbol: 'BTCUSDT',
        #       bidPrice: '29150.8',
        #       bidQty: '0.171947',
        #       askPrice: '29156.72',
        #       askQty: '0.017764',
        #       time: 1652956849565
        #     }
        # }
        #
        #  spot realtimes
        #    {
        #        topic: 'realtimes',
        #        params: {symbol: 'BTCUSDT', binary: 'false', symbolName: 'BTCUSDT'},
        #        data: {
        #          t: 1652883737410,
        #          s: 'BTCUSDT',
        #          o: '30422.68',
        #          h: '30715',
        #          l: '29288.44',
        #          c: '29462.94',
        #          v: '4350.340495',
        #          qv: '130497543.0334267',
        #          m: '-0.0315'
        #        }
        #    }
        #
        # swap/futures use an incremental approach sending first the snapshot and then the updates
        #
        # snapshot message
        #     {
        #         "topic":"instrument_info.100ms.BTCUSDT",
        #         "type":"snapshot",
        #         "data":{
        #            "id":1,
        #            "symbol":"BTCUSDT",
        #            "last_price_e4":"291050000",
        #            "last_price":"29105.00",
        #            "bid1_price_e4":"291045000",
        #            "bid1_price":"29104.50",
        #            "ask1_price_e4":"291050000",
        #            "ask1_price":"29105.00",
        #            "last_tick_direction":"ZeroPlusTick",
        #            "prev_price_24h_e4":"297900000",
        #            "prev_price_24h":"29790.00",
        #            "price_24h_pcnt_e6":"-22994",
        #            "high_price_24h_e4":"300200000",
        #            "high_price_24h":"30020.00",
        #            "low_price_24h_e4":"286330000",
        #            "low_price_24h":"28633.00",
        #            "prev_price_1h_e4":"291435000",
        #            "prev_price_1h":"29143.50",
        #            "price_1h_pcnt_e6":"-1321",
        #            "mark_price_e4":"291148200",
        #            "mark_price":"29114.82",
        #            "index_price_e4":"291173600",
        #            "index_price":"29117.36",
        #            "open_interest_e8":"2725210700000",
        #            "total_turnover_e8":"6184585271557950000",
        #            "turnover_24h_e8":"373066109692150560",
        #            "total_volume_e8":"3319897492699924",
        #            "volume_24h_e8":"12774825300000",
        #            "funding_rate_e6":"-97",
        #            "predicted_funding_rate_e6":"100",
        #            "cross_seq":"11834024892",
        #            "created_at":"1970-01-01T00:00:00.000Z",
        #            "updated_at":"2022-05-19T08:52:10.000Z",
        #            "next_funding_time":"2022-05-19T16:00:00Z",
        #            "count_down_hour":"8",
        #            "funding_rate_interval":"8",
        #            "settle_time_e9":"0",
        #            "delisting_status":"0"
        #         },
        #         "cross_seq":"11834024953",
        #         "timestamp_e6":"1652950330515050"
        #     }
        #
        # update message
        #    {
        #        "topic":"instrument_info.100ms.BTCUSDT",
        #        "type":"delta",
        #        "data":{
        #           "update":[
        #              {
        #                 "id":1,
        #                 "symbol":"BTCUSDT",
        #                 "open_interest_e8":"2721359000000",
        #                 "cross_seq":"11834107074",
        #                 "created_at":"1970-01-01T00:00:00.000Z",
        #                 "updated_at":"2022-05-19T08:54:18.000Z"
        #              }
        #           ]
        #        },
        #        "cross_seq":"11834107125",
        #        "timestamp_e6":"1652950458616087"
        #    }
        #
        topic = self.safe_string(message, 'topic', '')
        if (topic == 'realtimes') or (topic == 'bookTicker'):
            # spot markets
            data = self.safe_value(message, 'data')
            ticker = self.parse_ws_ticker(data)
            symbol = ticker['symbol']
            self.tickers[symbol] = ticker
            messageHash = 'ticker:' + symbol
            client.resolve(ticker, messageHash)
            return
        updateType = self.safe_string(message, 'type', '')
        data = self.safe_value(message, 'data', {})
        symbol = None
        if updateType == 'snapshot':
            parsed = self.parse_ws_ticker(data)
            symbol = parsed['symbol']
            self.tickers[symbol] = parsed
        if updateType == 'delta':
            topicParts = topic.split('.')
            topicLength = len(topicParts)
            marketId = self.safe_string(topicParts, topicLength - 1)
            market = self.market(marketId)
            symbol = market['symbol']
            updates = self.safe_value(data, 'update', [])
            ticker = self.safe_value(self.tickers, symbol, {})
            for i in range(0, len(updates)):
                update = updates[i]
                ticker = self.update_ticker(ticker, update)
            self.tickers[symbol] = ticker
        messageHash = 'ticker:' + symbol
        client.resolve(self.tickers[symbol], messageHash)

    def update_ticker(self, ticker, update):
        # First we update the raw ticker with the new values
        # then we parse it again, although we could just
        # update the changed values in the already parsed ticker
        # doing that would lead to an inconsistent info object
        # inside ticker
        rawTicker = ticker['info']
        updateKeys = list(update.keys())
        updateLength = len(updateKeys)
        if updateLength > 0:
            for i in range(0, len(updateKeys)):
                key = updateKeys[i]
                if key in rawTicker:
                    rawTicker[key] = update[key]
            parsed = self.parse_ws_ticker(rawTicker)
            return parsed
        return ticker

    def parse_ws_ticker(self, ticker, market=None):
        #
        # spot
        #   {
        #          symbol: 'BTCUSDT',
        #          bidPrice: '29150.8',
        #          bidQty: '0.171947',
        #          askPrice: '29156.72',
        #          askQty: '0.017764',
        #          time: 1652956849565
        #   }
        #
        #   {
        #          t: 1652883737410,
        #          s: 'BTCUSDT',
        #          o: '30422.68',
        #          h: '30715',
        #          l: '29288.44',
        #          c: '29462.94',
        #          v: '4350.340495',
        #          qv: '130497543.0334267',
        #          m: '-0.0315'
        #    }
        #
        # swap
        #
        #   {
        #            "id":1,
        #            "symbol":"BTCUSDT",
        #            "last_price_e4":"291050000",
        #            "last_price":"29105.00",
        #            "bid1_price_e4":"291045000",
        #            "bid1_price":"29104.50",
        #            "ask1_price_e4":"291050000",
        #            "ask1_price":"29105.00",
        #            "last_tick_direction":"ZeroPlusTick",
        #            "prev_price_24h_e4":"297900000",
        #            "prev_price_24h":"29790.00",
        #            "price_24h_pcnt_e6":"-22994",
        #            "high_price_24h_e4":"300200000",
        #            "high_price_24h":"30020.00",
        #            "low_price_24h_e4":"286330000",
        #            "low_price_24h":"28633.00",
        #            "prev_price_1h_e4":"291435000",
        #            "prev_price_1h":"29143.50",
        #            "price_1h_pcnt_e6":"-1321",
        #            "mark_price_e4":"291148200",
        #            "mark_price":"29114.82",
        #            "index_price_e4":"291173600",
        #            "index_price":"29117.36",
        #            "open_interest_e8":"2725210700000",
        #            "total_turnover_e8":"6184585271557950000",
        #            "turnover_24h_e8":"373066109692150560",
        #            "total_volume_e8":"3319897492699924",
        #            "volume_24h_e8":"12774825300000",
        #            "funding_rate_e6":"-97",
        #            "predicted_funding_rate_e6":"100",
        #            "cross_seq":"11834024892",
        #            "created_at":"1970-01-01T00:00:00.000Z",
        #            "updated_at":"2022-05-19T08:52:10.000Z",
        #            "next_funding_time":"2022-05-19T16:00:00Z",
        #            "count_down_hour":"8",
        #            "funding_rate_interval":"8",
        #            "settle_time_e9":"0",
        #            "delisting_status":"0"
        #         },
        #         "cross_seq":"11834024953",
        #         "timestamp_e6":"1652950330515050"
        #     }
        #
        # option
        #    {
        #        "symbol":"BTC-19NOV21-58000-P",
        #        "bidPrice":"421",
        #        "askPrice":"465",
        #        "bidIv":"0.7785",
        #        "askIv":"0.8012",
        #        "bidSize":"17",
        #        "askSize":"18",
        #        "markPrice":"442.51157238",
        #        "markPriceIv":"0.7897",
        #        "indexPrice":"67102.13",
        #        "underlyingPrice":"67407.49",
        #        "lastPrice":"0",
        #        "delta":"-0.10385629",
        #        "gamma":"0.00002132",
        #        "theta":"-82.72572574",
        #        "vega":"19.33584131",
        #        "change24h":"0",
        #        "volume24h":"0",
        #        "turnover24h":"0",
        #        "high24h":"0",
        #        "low24h":"0",
        #        "totalVolume":"0",
        #        "totalTurnover":"0",
        #        "openInterest":"0",
        #        "predictedDeliveryPrice":"62330.90608575"
        #    }
        #
        timestamp = self.safe_integer_2(ticker, 'time', 't')
        if timestamp is None:
            timestamp = self.parse8601(self.safe_string_2(ticker, 'updated_at', 'updatedAt'))
            if timestamp is None:
                timestampE9 = self.safe_string(ticker, 'updated_at_e9')
                timestamp = Precise.string_div(timestampE9, '1000000')
                timestamp = self.parse_number(timestamp)
                timestamp = int(timestamp) if (timestamp is not None) else None
        marketId = self.safe_string_2(ticker, 'symbol', 's')
        symbol = self.safe_symbol(marketId, market)
        last = self.safe_string_n(ticker, ['l', 'last_price', 'lastPrice'])
        open = self.safe_string_n(ticker, ['prev_price_24h', 'o', 'prevPrice24h'])
        quoteVolume = self.safe_string_n(ticker, ['v', 'turnover24h'])
        if quoteVolume is None:
            quoteVolume = self.safe_string_2(ticker, 'turnover_24h_e8', 'turnover24hE8')
            quoteVolume = Precise.string_div(quoteVolume, '100000000')
        baseVolume = self.safe_string_n(ticker, ['qv', 'volume24h', 'volume_24h'])
        if baseVolume is None:
            baseVolume = self.safe_string_2(ticker, 'volume_24h_e8', 'volume24hE8')
            baseVolume = Precise.string_div(baseVolume, '100000000')
        bid = self.safe_string_n(ticker, ['bidPrice', 'bid1_price', 'bid1Price'])
        ask = self.safe_string_n(ticker, ['askPrice', 'ask1_price', 'ask1Price'])
        high = self.safe_string_n(ticker, ['high_price_24h', 'high24h', 'h', 'highPrice24h'])
        low = self.safe_string_n(ticker, ['low_price_24h', 'low24h', 'l', 'lowPrice24h'])
        percentage = self.safe_string(ticker, 'm')
        if percentage is None:
            percentage = self.safe_string_2(ticker, 'price_24h_pcnt_e6', 'price24hPcntE6')
            percentage = Precise.string_div(percentage, '1000000')
        change = self.safe_string(ticker, 'change24h')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': high,
            'low': low,
            'bid': bid,
            'bidVolume': self.safe_string_2(ticker, 'bidSize', 'bidQty'),
            'ask': ask,
            'askVolume': self.safe_string_2(ticker, 'askSize', 'askQty'),
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    async def watch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the bybit api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        interval = self.timeframes[timeframe]
        url = self.get_url_by_market_type(symbol, False, params)
        params = self.clean_params(params)
        messageHash = 'kline' + ':' + timeframe + ':' + symbol
        ohlcv = None
        if market['spot']:
            channel = 'kline'
            reqParams = {
                'symbol': market['id'],
                'klineType': timeframe,  # spot uses the same timeframe as ours
            }
            ohlcv = await self.watch_spot_public(url, channel, messageHash, reqParams, params)
        else:
            prefix = 'candle' if market['linear'] else 'klineV2'
            channel = prefix + '.' + interval + '.' + market['id']
            reqParams = [channel]
            ohlcv = await self.watch_contract_public(url, messageHash, reqParams, params)
        if self.newUpdates:
            limit = ohlcv.getLimit(symbol, limit)
        return self.filter_by_since_limit(ohlcv, since, limit, 0, True)

    def handle_ohlcv(self, client, message):
        #
        # swap
        #    {
        #        topic: 'klineV2.1.LTCUSD',
        #        data: [
        #          {
        #            start: 1652893140,
        #            end: 1652893200,
        #            open: 67.9,
        #            close: 67.84,
        #            high: 67.91,
        #            low: 67.84,
        #            volume: 56,
        #            turnover: 0.82528936,
        #            timestamp: '1652893152874413',
        #            confirm: False,
        #            cross_seq: 63544166
        #          }
        #        ],
        #        timestamp_e6: 1652893152874413
        #    }
        #
        # spot
        #    {
        #        topic: 'kline',
        #        params: {
        #          symbol: 'LTCUSDT',
        #          binary: 'false',
        #          klineType: '1m',
        #          symbolName: 'LTCUSDT'
        #        },
        #        data: {
        #          t: 1652893440000,
        #          s: 'LTCUSDT',
        #          sn: 'LTCUSDT',
        #          c: '67.92',
        #          h: '68.05',
        #          l: '67.92',
        #          o: '68.05',
        #          v: '9.71302'
        #        }
        #    }
        #
        data = self.safe_value(message, 'data', {})
        topic = self.safe_string(message, 'topic')
        if isinstance(data, list):
            # swap messages
            topicParts = topic.split('.')
            topicLength = len(topicParts)
            marketId = self.safe_string(topicParts, topicLength - 1)
            timeframe = self.safe_string(topicParts, topicLength - 2)
            marketIds = {}
            for i in range(0, len(data)):
                ohlcv = data[i]
                market = self.market(marketId)
                symbol = market['symbol']
                parsed = self.parse_ws_ohlcv(ohlcv, market)
                stored = self.safe_value(self.ohlcvs, symbol)
                if stored is None:
                    limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
                    stored = ArrayCacheByTimestamp(limit)
                    self.ohlcvs[symbol] = stored
                stored.append(parsed)
                marketIds[symbol] = timeframe
            keys = list(marketIds.keys())
            for i in range(0, len(keys)):
                symbol = keys[i]
                interval = marketIds[symbol]
                timeframe = self.find_timeframe(interval)
                messageHash = 'kline' + ':' + timeframe + ':' + symbol
                stored = self.safe_value(self.ohlcvs, symbol)
                client.resolve(stored, messageHash)
        else:
            # spot messages
            params = self.safe_value(message, 'params', {})
            data = self.safe_value(message, 'data')
            marketId = self.safe_string(params, 'symbol')
            timeframe = self.safe_string(params, 'klineType')
            market = self.market(marketId)
            parsed = self.parse_ws_ohlcv(data, market)
            symbol = market['symbol']
            stored = self.safe_value(self.ohlcvs, symbol)
            if stored is None:
                limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
                stored = ArrayCacheByTimestamp(limit)
                self.ohlcvs[symbol] = stored
            stored.append(parsed)
            messageHash = 'kline' + ':' + timeframe + ':' + symbol
            client.resolve(stored, messageHash)

    def parse_ws_ohlcv(self, ohlcv, market=None):
        #
        # swap
        #   {
        #      start: 1652893140,
        #      end: 1652893200,
        #      open: 67.9,
        #      close: 67.84,
        #      high: 67.91,
        #      low: 67.84,
        #      volume: 56,
        #      turnover: 0.82528936,
        #      timestamp: '1652893152874413',  # microseconds
        #      confirm: False,
        #      cross_seq: 63544166
        #   }
        #
        # spot
        #
        #   {
        #      t: 1652893440000,
        #      s: 'LTCUSDT',
        #      sn: 'LTCUSDT',
        #      c: '67.92',
        #      h: '68.05',
        #      l: '67.92',
        #      o: '68.05',
        #      v: '9.71302'
        #   }
        #
        timestamp = self.safe_integer(ohlcv, 't')
        if timestamp is None:
            timestamp = self.safe_timestamp(ohlcv, 'start')
        return [
            timestamp,
            self.safe_number_2(ohlcv, 'open', 'o'),
            self.safe_number_2(ohlcv, 'high', 'h'),
            self.safe_number_2(ohlcv, 'low', 'l'),
            self.safe_number_2(ohlcv, 'close', 'c'),
            self.safe_number_2(ohlcv, 'volume', 'v'),
        ]

    async def watch_order_book(self, symbol, limit=None, params={}):
        """
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the bybit api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        url = self.get_url_by_market_type(symbol, False, params)
        params = self.clean_params(params)
        messageHash = 'orderbook' + ':' + symbol
        orderbook = None
        if market['spot']:
            channel = 'depth'
            reqParams = {
                'symbol': market['id'],
            }
            orderbook = await self.watch_spot_public(url, channel, messageHash, reqParams, params)
        else:
            channel = None
            if market['option']:
                channel = 'delta.orderbook100' + '.' + market['marketId']
            else:
                if limit is not None:
                    if limit != 25 and limit != 200:
                        raise BadRequest(self.id + ' watchOrderBook limit argument must be either 25 or 200')
                else:
                    limit = 25
                prefix = 'orderBookL2_25' if (limit == 25) else 'orderBook_200.100ms'
                channel = prefix + '.' + market['id']
            reqParams = [channel]
            orderbook = await self.watch_contract_public(url, messageHash, reqParams, params)
        return orderbook.limit()

    def handle_order_book(self, client, message):
        #
        # spot snapshot
        # {
        #     topic: 'depth',
        #     params: {symbol: 'BTCUSDT', binary: 'false', symbolName: 'BTCUSDT'},
        #     data: {
        #       s: 'BTCUSDT',
        #       t: 1652970523792,
        #       v: '34407758_140450607_2',
        #       b: [
        #            [
        #                "9780.79",
        #                "0.01"
        #            ],
        #       ],
        #       a: [
        #            [
        #               "9781.21",
        #               "0.042842"
        #            ]
        #       ]
        #     }
        #   }
        #
        # contract snapshot
        #    {
        #        topic: 'orderBookL2_25.BTCUSDT',
        #        type: 'snapshot',
        #        data: {
        #          order_book: [
        #              {
        #                  "price":"29907.50",
        #                  "symbol":"BTCUSDT",
        #                  "id":"299075000",
        #                  "side":"Buy",
        #                  "size":0.763
        #              }
        #          ]
        #        },
        #        cross_seq: '11846360142',
        #        timestamp_e6: '1652973544516741'
        #    }
        #
        # contract delta
        #
        # {
        #     topic: 'orderBookL2_25.BTCUSDT',
        #     type: 'delta',
        #     data: {
        #         "delete": [
        #             {
        #                   "price": "3001.00",
        #                   "symbol": "BTCUSDT",
        #                   "id": 30010000,
        #                   "side": "Sell"
        #             }
        #          ],
        #          "update": [
        #             {
        #                   "price": "2999.00",
        #                   "symbol": "BTCUSDT",
        #                   "id": 29990000,
        #                   "side": "Buy",
        #                   "size": 8
        #             }
        #          ],
        #          "insert": [
        #             {
        #                   "price": "2998.00",
        #                   "symbol": "BTCUSDT",
        #                   "id": 29980000,
        #                   "side": "Buy",
        #                   "size": 8
        #             }
        #            ],
        #          },
        #          cross_seq: '11848736847',
        #          timestamp_e6: '1652976712534987'
        #     }
        #
        topic = self.safe_string(message, 'topic', '')
        data = self.safe_value(message, 'data', {})
        if topic == 'depth':
            # spot branch, we get the snapshot in every message
            marketId = self.safe_string(data, 's')
            market = self.market(marketId)
            symbol = market['symbol']
            timestamp = self.safe_integer(data, 't')
            snapshot = self.parse_order_book(data, symbol, timestamp, 'b', 'a')
            orderbook = None
            if not (symbol in self.orderbooks):
                orderbook = self.order_book(snapshot)
                self.orderbooks[symbol] = orderbook
            else:
                orderbook = self.orderbooks[symbol]
                orderbook.reset(snapshot)
            messageHash = 'orderbook' + ':' + symbol
            client.resolve(orderbook, messageHash)
            return
        if topic.find('orderBook') >= 0:
            # contract branch
            type = self.safe_string(message, 'type')
            topicParts = topic.split('.')
            topicLength = len(topicParts)
            marketId = self.safe_string(topicParts, topicLength - 1)
            market = self.market(marketId)
            symbol = market['symbol']
            messageHash = 'orderbook' + ':' + symbol
            nonce = self.safe_integer_2(message, 'cross_seq', 'crossSeq')
            timestamp = self.safe_integer_product_2(message, 'timestamp_e6', 'timestampE6', 0.001)
            if type == 'snapshot':
                rawOrderBook = self.safe_value_2(data, 'order_book', 'orderBook', data)
                snapshot = self.parse_order_book(rawOrderBook, symbol, timestamp, 'Buy', 'Sell', 'price', 'size')
                snapshot['nonce'] = nonce
                orderbook = None
                if not (symbol in self.orderbooks):
                    orderbook = self.order_book(snapshot)
                    self.orderbooks[symbol] = orderbook
                else:
                    orderbook = self.orderbooks[symbol]
                    orderbook.reset(snapshot)
            elif type == 'delta':
                deleted = self.safe_value(data, 'delete', [])
                updated = self.safe_value(data, 'update', [])
                inserted = self.safe_value(data, 'insert', [])
                updatedDeleted = []
                for i in range(0, len(deleted)):
                    entry = deleted[i]
                    entry['size'] = 0
                    updatedDeleted.append(entry)
                deltas = updatedDeleted
                deltas = self.array_concat(deltas, updated)
                deltas = self.array_concat(deltas, inserted)
                orderbook = self.safe_value(self.orderbooks, symbol)
                orderbook['nonce'] = nonce
                orderbook['timestamp'] = timestamp
                orderbook['datetime'] = self.iso8601(timestamp)
                self.handle_deltas(orderbook, deltas)
            client.resolve(self.orderbooks[symbol], messageHash)

    def handle_deltas(self, orderbook, deltas):
        #
        #   [
        #      {
        #            "price": "2999.00",
        #            "symbol": "BTCUSDT",
        #            "id": 29990000,
        #            "side": "Buy",
        #            "size": 8
        #      }
        #   ]
        #
        for i in range(0, len(deltas)):
            delta = deltas[i]
            side = self.safe_string(delta, 'side')
            if side == 'Buy':
                self.handle_delta(orderbook['bids'], deltas[i])
            else:
                self.handle_delta(orderbook['asks'], deltas[i])

    def handle_delta(self, bookside, delta):
        #
        #   {
        #         "price": "2999.00",
        #         "symbol": "BTCUSDT",
        #         "id": 29990000,
        #         "side": "Buy",
        #         "size": 8
        #   }
        #
        price = self.safe_number(delta, 'price')
        amount = self.safe_number(delta, 'size')
        bookside.store(price, amount)

    async def watch_trades(self, symbol, since=None, limit=None, params={}):
        """
        watches information on multiple trades made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the bybit api endpoint
        :returns [dict]: a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure
        """
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        commonChannel = 'trade'
        url = self.get_url_by_market_type(symbol, False, params)
        params = self.clean_params(params)
        messageHash = commonChannel + ':' + symbol
        trades = None
        if market['spot']:
            reqParams = {
                'symbol': market['id'],
            }
            trades = await self.watch_spot_public(url, commonChannel, messageHash, reqParams, params)
        else:
            channel = None
            if market['option']:
                channel = 'recenttrades' + '.' + market['baseId']
            else:
                channel = commonChannel + '.' + market['id']
            reqParams = [channel]
            trades = await self.watch_contract_public(url, messageHash, reqParams, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def handle_trades(self, client, message):
        #
        # swap
        #    {
        #        topic: 'trade.BTCUSDT',
        #        data: [
        #          {
        #            symbol: 'BTCUSDT',
        #            tick_direction: 'ZeroPlusTick',
        #            price: '29678.00',
        #            size: 0.025,
        #            timestamp: '2022-05-19T13:36:01.000Z',
        #            trade_time_ms: '1652967361915',
        #            side: 'Buy',
        #            trade_id: '78352b1f-17b7-522a-9eea-b06f0deaf23e'
        #          }
        #        ]
        #    }
        #
        # spot
        #
        #    {
        #        topic: 'trade',
        #        params: {symbol: 'BTCUSDT', binary: 'false', symbolName: 'BTCUSDT'},
        #        data: {
        #          v: '2290000000003002848',
        #          t: 1652967602261,
        #          p: '29698.82',
        #          q: '0.189531',
        #          m: True
        #        }
        #    }
        #
        marketId = None
        data = self.safe_value(message, 'data', [])
        topic = self.safe_string(message, 'topic')
        trades = None
        if not isinstance(data, list):
            # spot markets
            params = self.safe_value(message, 'params', {})
            marketId = self.safe_string(params, 'symbol')
            # injecting marketId in trade
            data['symbol'] = marketId
            trades = [data]
        else:
            # contract markets
            parts = topic.split('.')
            marketId = self.safe_string(parts, 1)
            trades = data
        market = self.safe_market(marketId)
        symbol = market['symbol']
        stored = self.safe_value(self.trades, symbol)
        if stored is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            stored = ArrayCache(limit)
            self.trades[symbol] = stored
        for j in range(0, len(trades)):
            parsed = self.parse_ws_trade(trades[j], market)
            stored.append(parsed)
        messageHash = 'trade' + ':' + symbol
        client.resolve(stored, messageHash)

    def parse_ws_trade(self, trade, market=None):
        #
        # swap public
        #
        #     {
        #       symbol: 'BTCUSDT',
        #       tick_direction: 'ZeroPlusTick',
        #       price: '29678.00',
        #       size: 0.025,
        #       timestamp: '2022-05-19T13:36:01.000Z',
        #       trade_time_ms: '1652967361915',
        #       side: 'Buy',
        #       trade_id: '78352b1f-17b7-522a-9eea-b06f0deaf23e'
        #     }
        #
        # swap private
        #    {
        #        symbol: 'LTCUSDT',
        #        side: 'Buy',
        #        order_id: '6773a86c-24c3-4066-90b6-d45c6653f35f',
        #        exec_id: '91d4962c-828f-58f1-9a1e-388c357d9344',
        #        order_link_id: '',
        #        price: 71.8,
        #        order_qty: 0.1,
        #        exec_type: 'Trade',
        #        exec_qty: 0.1,
        #        exec_fee: 0.004308,
        #        leaves_qty: 0,
        #        is_maker: False,
        #        trade_time: '2022-05-23T14:08:08.875206Z'
        #    }
        #
        # option
        #
        #     {
        #         "symbol":"BTC-31DEC21-36000-P",
        #         "tradeId":"787bf079-b6a5-5bc0-a76d-59dad9036e7b",
        #         "price":"371",
        #         "size":"0.01",
        #         "tradeTime":"1636510323144",
        #         "side":"Buy",
        #         "crossSeq":"118388"
        #     }
        #
        # usdc
        #   {
        #       "orderId":"290b1b83-b6bb-4327-9839-8c5b9c322b4c",
        #       "orderLinkId":"",
        #       "tradeId":"3a69833e-f23c-5530-83a9-ccbb4af34926",
        #       "symbol":"BTCPERP",
        #       "side":"Sell",
        #       "execPrice":"30331",
        #       "execQty":"0.001",
        #       "execFee":"0.0181986",
        #       "feeRate":"0.0006",
        #       "tradeTime":1653321686805,
        #       "lastLiquidityInd":"TAKER",
        #       "execValue":"30.331",
        #       "execType":"TRADE"
        #   }
        #
        # spot public
        #
        #    {
        #      'symbol': 'BTCUSDT',  # artificially added
        #       v: '2290000000003002848',  # trade id
        #       t: 1652967602261,
        #       p: '29698.82',
        #       q: '0.189531',
        #       m: True
        #     }
        #
        # spot private
        #
        #     {
        #         'e': 'ticketInfo',
        #         'E': '1653313467249',  # event time
        #         's': 'LTCUSDT',
        #         'q': '0.13621',
        #         't': '1653313467227',  # timestamp
        #         'p': '72.43',
        #         'T': '2200000000004436641',  # trade Id
        #         'o': '1162472050160422400',  # order Id
        #         'c': '1653313466834',  # client Id
        #         'O': '1162471954312183040',
        #         'a': '24478790',  # account id
        #         'A': '18478961',
        #         'm': False,  # isMaker
        #     }
        #
        id = self.safe_string_n(trade, ['trade_id', 'v', 'tradeId', 'T', 'exec_id'])
        marketId = self.safe_string_2(trade, 'symbol', 's')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        price = self.safe_string_n(trade, ['p', 'price', 'execPrice'])
        amount = self.safe_string_n(trade, ['q', 'size', 'exec_qty', 'execQty'])
        cost = self.safe_string_2(trade, 'exec_value', 'execValue')
        timestamp = self.safe_integer_n(trade, ['trade_time_ms', 't', 'tradeTime', 'tradeTimeMs'])
        if timestamp is None:
            timestamp = self.parse8601(self.safe_string(trade, 'trade_time'))
        side = self.safe_string_lower(trade, 'side')
        isMaker = self.safe_value_2(trade, 'm', 'is_maker')
        if isMaker is None:
            lastLiquidityInd = self.safe_string(trade, 'lastLiquidityInd')
            isMaker = (lastLiquidityInd == 'MAKER')
        takerOrMaker = 'maker' if isMaker else 'taker'
        orderId = self.safe_string_n(trade, ['o', 'order_id', 'tradeTime'])
        fee = None
        isContract = self.safe_value(market, 'contract')
        if isContract:
            feeCost = self.safe_string_2(trade, 'exec_fee', 'execFee')
            if feeCost is not None:
                feeCurrency = market['quote'] if market['linear'] else market['base']
                fee = {
                    'cost': feeCost,
                    'currency': feeCurrency,
                }
        return self.safe_trade({
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }, market)

    async def watch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        watches information on multiple trades made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the bybit api endpoint
        :returns [dict]: a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure
        """
        method = 'watchMyTrades'
        messageHash = 'usertrade'
        await self.load_markets()
        market = None
        type = None
        isUsdcSettled = None
        url = self.get_url_by_market_type(symbol, True, method, params)
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash += ':' + symbol
            type = market['type']
            isUsdcSettled = market['settle'] == 'USDC'
        else:
            type, params = self.handle_market_type_and_params(method, None, params)
            settle = self.safe_string(self.options, 'defaultSettle')
            settle = self.safe_string_2(params, 'settle', 'defaultSettle', settle)
            isUsdcSettled = settle == 'USDC'
        params = self.clean_params(params)
        trades = None
        if type == 'spot':
            trades = await self.watch_spot_private(url, messageHash, params)
        else:
            channel = None
            if isUsdcSettled:
                channel = 'user.openapi.option.trade' if (type == 'option') else 'user.openapi.perp.trade'
            else:
                channel = 'execution'
            reqParams = [channel]
            messageHash += ':' + channel
            trades = await self.watch_contract_private(url, messageHash, reqParams, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def handle_my_trades(self, client, message):
        #
        # spot
        #
        #   [
        #       {
        #           'e': 'ticketInfo',
        #           'E': '1653313467249',
        #           's': 'LTCUSDT',
        #           'q': '0.13621',
        #           't': '1653313467227',
        #           'p': '72.43',
        #           'T': '2200000000004436641',
        #           'o': '1162472050160422400',
        #           'c': '1653313466834',
        #           'O': '1162471954312183040',
        #           'a': '24478790',
        #           'A': '18478961',
        #           'm': False,
        #       }
        #   ]
        #
        # usdc
        #
        #   {
        #       "id":"b4c38cdc-5708-4c24-9f26-b3468f7b9658",
        #       "topic":"user.openapi.perp.trade",
        #       "creationTime":1653321605512,
        #       "data":{
        #          "result":[
        #             {
        #                "orderId":"cfafeaae-f4a5-4ee5-bd03-b899251b1557",
        #                "orderLinkId":"",
        #                "tradeId":"29a3b7da-f593-55c4-9f23-afd9d6715668",
        #                "symbol":"BTCPERP",
        #                "side":"Buy",
        #                "execPrice":"30333.5",
        #                "execQty":"0.001",
        #                "execFee":"0.0182001",
        #                "feeRate":"0.0006",
        #                "tradeTime":1653321605486,
        #                "lastLiquidityInd":"TAKER",
        #                "execValue":"30.3335",
        #                "execType":"TRADE"
        #             }
        #          ],
        #          "version":5,
        #          "baseLine":1
        #       }
        #   }
        #
        topic = self.safe_string(message, 'topic', '')
        data = []
        if isinstance(message, list):
            data = message
        else:
            data = self.safe_value(message, 'data', [])
            if 'result' in data:
                # usdc
                data = data['result']
        if self.myTrades is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            self.myTrades = ArrayCacheBySymbolById(limit)
        trades = self.myTrades
        marketSymbols = {}
        for i in range(0, len(data)):
            rawTrade = data[i]
            parsed = self.parse_ws_trade(rawTrade)
            symbol = parsed['symbol']
            marketSymbols[symbol] = True
            trades.append(parsed)
        symbols = list(marketSymbols.keys())
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            messageHash = 'usertrade:' + symbol + ':' + topic
            client.resolve(trades, messageHash)
        # non-symbol specific
        messageHash = 'usertrade:' + topic
        client.resolve(trades, messageHash)

    async def watch_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        watches information on multiple orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the bybit api endpoint
        :returns [dict]: a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure
        """
        method = 'watchOrders'
        messageHash = 'order'
        await self.load_markets()
        market = None
        type = None
        isUsdcSettled = None
        url = self.get_url_by_market_type(symbol, True, method, params)
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            messageHash += ':' + symbol
            type = market['type']
            isUsdcSettled = market['settle'] == 'USDC'
        else:
            type, params = self.handle_market_type_and_params(method, None, params)
            settle = self.safe_string(self.options, 'defaultSettle')
            settle = self.safe_string_2(params, 'settle', 'defaultSettle', settle)
            isUsdcSettled = settle == 'USDC'
        params = self.clean_params(params)
        orders = None
        if type == 'spot':
            orders = await self.watch_spot_private(url, messageHash, params)
        else:
            channel = None
            if isUsdcSettled:
                channel = 'user.openapi.option.order' if (type == 'option') else 'user.openapi.perp.order'
            else:
                orderType = self.safe_string(params, 'orderType')
                stop = self.safe_value(params, 'stop', False)
                isStopOrder = stop or (orderType == 'stop') or (orderType == 'conditional')
                params = self.omit(params, ['stop', 'orderType'])
                channel = 'stop_order' if isStopOrder else 'order'
            reqParams = [channel]
            messageHash += ':' + channel
            orders = await self.watch_contract_private(url, messageHash, reqParams, params)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    def handle_order(self, client, message, subscription=None):
        #
        # spot order
        #
        #     [
        #         {
        #           e: 'executionReport',
        #           E: '1653297251061',
        #           s: 'LTCUSDT',
        #           c: '1653297250740',
        #           S: 'SELL',
        #           o: 'MARKET_OF_BASE',
        #           f: 'GTC',
        #           q: '0.16233',
        #           p: '0',
        #           X: 'NEW',
        #           i: '1162336018974750208',
        #           M: '0',
        #           l: '0',
        #           z: '0',
        #           L: '0',
        #           n: '0',
        #           N: '',
        #           u: True,
        #           w: True,
        #           m: False,
        #           O: '1653297251042',
        #           Z: '0',
        #           A: '0',
        #           C: False,
        #           v: '0',
        #           d: 'NO_LIQ'
        #         }
        #     ]
        #
        # swap order
        #     {
        #         topic: 'order',
        #         action: '',
        #         data: [
        #           {
        #             order_id: 'f52071fd-6604-4cff-8112-82958ec55d3f',
        #             order_link_id: '',
        #             symbol: 'LTCUSDT',
        #             side: 'Buy',
        #             order_type: 'Market',
        #             price: 75.5,
        #             qty: 0.1,
        #             leaves_qty: 0,
        #             last_exec_price: 71.93,
        #             cum_exec_qty: 0.1,
        #             cum_exec_value: 7.193,
        #             cum_exec_fee: 0.0043158,
        #             time_in_force: 'ImmediateOrCancel',
        #             create_type: 'CreateByUser',
        #             cancel_type: 'UNKNOWN',
        #             order_status: 'Filled',
        #             take_profit: 0,
        #             stop_loss: 0,
        #             trailing_stop: 0,
        #             create_time: '2022-05-23T09:32:34.266539338Z',
        #             update_time: '2022-05-23T09:32:34.270607105Z',
        #             reduce_only: False,
        #             close_on_trigger: False,
        #             position_idx: '1'
        #           }
        #         ]
        #     }
        # usdc order
        #
        #     {
        #         "id":"401a6485-5701-49f2-9d0b-c09d97d56748",
        #         "topic":"user.openapi.perp.order",
        #         "creationTime":1653304042137,
        #         "data":{
        #            "result":[
        #               {
        #                  "orderId":"fe2e0765-fa47-4516-a962-fb021b9418e7",
        #                  "orderLinkId":"",
        #                  "createdAt":1653304042104,
        #                  "updatedAt":1653304042107,
        #                  "symbol":"BTCPERP",
        #                  "orderStatus":"New",
        #                  "side":"Buy",
        #                  "price":"20000.0000",
        #                  "qty":"0.001",
        #                  "cumExecQty":"0",
        #                  "leavesQty":"0.001",
        #                  "orderIM":"20.012",
        #                  "realisedPnl":null,
        #                  "orderType":"Limit",
        #                  "reduceOnly":0,
        #                  "timeInForce":"GoodTillCancel",
        #                  "cumExecFee":"0",
        #                  "orderPnl":"",
        #                  "basePrice":"",
        #                  "cumExecValue":"0",
        #                  "closeOnTrigger":"false",
        #                  "triggerBy":"UNKNOWN",
        #                  "takeProfit":"0",
        #                  "stopLoss":"0",
        #                  "tpTriggerBy":"UNKNOWN",
        #                  "slTriggerBy":"UNKNOWN",
        #                  "triggerPrice":"0",
        #                  "stopOrderType":"UNKNOWN",
        #                  "cancelType":"UNKNOWN"
        #               }
        #            ],
        #            "version":2,
        #            "baseLine":1,
        #            "dataType":"CHANGE"
        #         }
        #      }
        #
        topic = self.safe_string(message, 'topic', '')
        data = []
        isSpot = False
        if isinstance(message, list):
            data = message
            isSpot = True
        else:
            data = self.safe_value(message, 'data', [])
            if 'result' in data:
                # usdc
                data = data['result']
        dataLength = len(data)
        if dataLength == 0:
            return
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        orders = self.orders
        marketSymbols = {}
        for i in range(0, len(data)):
            rawOrder = data[i]
            parsed = None
            if isSpot:
                # spot orders have a different format
                # from the REST API
                parsed = self.parse_ws_order(rawOrder)
            else:
                parsed = self.parse_order(rawOrder)
            symbol = parsed['symbol']
            marketSymbols[symbol] = True
            orders.append(parsed)
        symbols = list(marketSymbols.keys())
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            messageHash = 'order:' + symbol + ':' + topic
            client.resolve(orders, messageHash)
        messageHash = 'order:' + topic
        # non-symbol specific
        client.resolve(orders, messageHash)

    def parse_ws_order(self, order, market=None):
        #
        #    {
        #        e: 'executionReport',
        #        E: '1653297251061',  # timestamp
        #        s: 'LTCUSDT',  # symbol
        #        c: '1653297250740',  # user id
        #        S: 'SELL',  # side
        #        o: 'MARKET_OF_BASE',  # order type
        #        f: 'GTC',  # time in force
        #        q: '0.16233',  # quantity
        #        p: '0',  # price
        #        X: 'NEW',  # status
        #        i: '1162336018974750208',  # order id
        #        M: '0',
        #        l: '0',  # last filled
        #        z: '0',  # total filled
        #        L: '0',  # last traded price
        #        n: '0',  # trading fee
        #        N: '',  # fee asset
        #        u: True,
        #        w: True,
        #        m: False,  # is limit_maker
        #        O: '1653297251042',  # order creation
        #        Z: '0',  # total filled
        #        A: '0',  # account id
        #        C: False,  # is close
        #        v: '0',  # leverage
        #        d: 'NO_LIQ'
        #    }
        #
        id = self.safe_string(order, 'i')
        marketId = self.safe_string(order, 's')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.safe_integer(order, 'O')
        price = self.safe_string(order, 'p')
        if price == '0':
            price = None  # market orders
        amount = self.safe_string(order, 'q')
        filled = self.safe_string(order, 'z')
        status = self.parse_order_status(self.safe_string(order, 'X'))
        side = self.safe_string_lower(order, 'S')
        lastTradeTimestamp = self.safe_string(order, 'E')
        timeInForce = self.safe_string(order, 'f')
        type = self.safe_string_lower(order, 'o')
        if type.find('market') >= 0:
            type = 'market'
        fee = None
        feeCost = self.safe_string(order, 'n')
        if feeCost is not None and feeCost != '0':
            feeCurrencyId = self.safe_string(order, 'N')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'amount': amount,
            'cost': None,
            'average': None,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': fee,
            'trades': None,
        }, market)

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

    def handle_balance(self, client, message):
        #
        # swap
        #
        #   {
        #       "topic":"wallet",
        #       "data":[
        #          {
        #             "wallet_balance":10.052857,
        #             "available_balance":5.049857
        #             "coin": "BTC",  # if not available = usdt
        #          }
        #       ]
        #   }
        #
        # spot message
        #
        # [
        #     {
        #        "e":"outboundAccountInfo",
        #        "E":"1653039590765",
        #        "T":true,
        #        "W":true,
        #        "D":true,
        #        "B":[
        #           {
        #              "a":"USDT",
        #              "f":"14.6634752497",
        #              "l":"10"
        #           }
        #        ]
        #     }
        # ]
        #
        topic = self.safe_string(message, 'topic')
        messageHash = 'balance'
        if topic == 'wallet':
            data = self.safe_value(message, 'data', [])
            # swap
            for i in range(0, len(data)):
                account = self.account()
                balance = data[i]
                currencyId = self.safe_string(balance, 'coin', 'USDT')
                code = self.safe_currency_code(currencyId)
                account['free'] = self.safe_string(balance, 'available_balance')
                account['total'] = self.safe_string(balance, 'wallet_balance')
                self.balance[code] = account
                self.balance = self.safe_balance(self.balance)
            messageHash += ':' + 'swap'
            client.resolve(self.balance, messageHash)
            return
        if isinstance(message, list):
            # spot balance
            for i in range(0, len(message)):
                balances = self.safe_value(message[i], 'B', [])
                for j in range(0, len(balances)):
                    balance = balances[j]
                    account = self.account()
                    code = self.safe_currency_code(self.safe_string(balance, 'a'))
                    account['free'] = self.safe_string(balance, 'f')
                    account['used'] = self.safe_string(balance, 'l')
                    self.balance[code] = account
                    self.balance = self.safe_balance(self.balance)
            messageHash += ':' + 'spot'
            client.resolve(self.balance, messageHash)

    async def watch_contract_public(self, url, messageHash, reqParams={}, params={}):
        request = {
            'op': 'subscribe',
            'args': reqParams,
        }
        message = self.extend(request, params)
        return await self.watch(url, messageHash, message, messageHash)

    async def watch_spot_public(self, url, channel, messageHash, reqParams={}, params={}):
        reqParams = self.extend(reqParams, {
            'binary': False,
        })
        request = {
            'topic': channel,
            'event': 'sub',
            'params': reqParams,
        }
        message = self.extend(request, params)
        return await self.watch(url, messageHash, message, messageHash)

    async def watch_spot_private(self, url, messageHash, params={}):
        channel = 'private'
        # sending the authentication message automatically
        # subscribes to all 3 private topics.
        self.check_required_credentials()
        expires = self.milliseconds() + 10000
        expires = str(expires)
        path = 'GET/realtime'
        auth = path + expires
        signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256, 'hex')
        request = {
            'op': 'auth',
            'args': [
                self.apiKey, expires, signature,
            ],
        }
        return await self.watch(url, messageHash, request, channel)

    async def watch_contract_private(self, url, messageHash, reqParams, params={}):
        await self.authenticate_contract(url, params)
        return await self.watch_contract_public(url, messageHash, reqParams, params)

    async def authenticate_contract(self, url, params={}):
        self.check_required_credentials()
        messageHash = 'login'
        client = self.client(url)
        future = self.safe_value(client.subscriptions, messageHash)
        if future is None:
            future = client.future('authenticated')
            expires = self.milliseconds() + 10000
            expires = str(expires)
            path = 'GET/realtime'
            auth = path + expires
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256, 'hex')
            request = {
                'op': 'auth',
                'args': [
                    self.apiKey, expires, signature,
                ],
            }
            self.spawn(self.watch, url, messageHash, request, messageHash, future)
        return await future

    def handle_error_message(self, client, message):
        #
        #   {
        #       success: False,
        #       ret_msg: 'error:invalid op',
        #       conn_id: '5e079fdd-9c7f-404d-9dbf-969d650838b5',
        #       request: {op: '', args: null}
        #   }
        #
        # auth error
        #
        #   {
        #       success: False,
        #       ret_msg: 'error:USVC1111',
        #       conn_id: 'e73770fb-a0dc-45bd-8028-140e20958090',
        #       request: {
        #         op: 'auth',
        #         args: [
        #           '9rFT6uR4uz9Imkw4Wx',
        #           '1653405853543',
        #           '542e71bd85597b4db0290f0ce2d13ed1fd4bb5df3188716c1e9cc69a879f7889'
        #         ]
        #   }
        #
        #   {code: '-10009', desc: 'Invalid period!'}
        #
        code = self.safe_integer(message, 'code')
        try:
            if code is not None:
                feedback = self.id + ' ' + self.json(message)
                self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
            success = self.safe_value(message, 'success', False)
            if not success:
                ret_msg = self.safe_string(message, 'ret_msg')
                request = self.safe_value(message, 'request', {})
                op = self.safe_string(request, 'op')
                if op == 'auth':
                    raise AuthenticationError('Authentication failed: ' + ret_msg)
        except Exception as e:
            if isinstance(e, AuthenticationError):
                client.reject(e, 'authenticated')
                method = 'login'
                if method in client.subscriptions:
                    del client.subscriptions[method]
                return False
        return message

    def handle_message(self, client, message):
        #
        #    {
        #        topic: 'realtimes',
        #        params: {symbol: 'BTCUSDT', binary: 'false', symbolName: 'BTCUSDT'},
        #        data: {
        #          t: 1652883737410,
        #          s: 'BTCUSDT',
        #          o: '30422.68',
        #          h: '30715',
        #          l: '29288.44',
        #          c: '29462.94',
        #          v: '4350.340495',
        #          qv: '130497543.0334267',
        #          m: '-0.0315'
        #        }
        #    }
        #    {
        #        topic: 'klineV2.1.LTCUSD',
        #        data: [
        #          {
        #            start: 1652893140,
        #            end: 1652893200,
        #            open: 67.9,
        #            close: 67.84,
        #            high: 67.91,
        #            low: 67.84,
        #            volume: 56,
        #            turnover: 0.82528936,
        #            timestamp: '1652893152874413',
        #            confirm: False,
        #            cross_seq: 63544166
        #          }
        #        ],
        #        timestamp_e6: 1652893152874413
        #    }
        #
        #    {
        #        topic: 'kline',
        #        event: 'sub',
        #        params: {
        #          symbol: 'LTCUSDT',
        #          binary: 'false',
        #          klineType: '1m',
        #          symbolName: 'LTCUSDT'
        #        },
        #        code: '0',
        #        msg: 'Success'
        #    }
        #
        #    {
        #        topic: 'trade.BTCUSDT',
        #        data: [
        #          {
        #            symbol: 'BTCUSDT',
        #            tick_direction: 'ZeroPlusTick',
        #            price: '29678.00',
        #            size: 0.025,
        #            timestamp: '2022-05-19T13:36:01.000Z',
        #            trade_time_ms: '1652967361915',
        #            side: 'Buy',
        #            trade_id: '78352b1f-17b7-522a-9eea-b06f0deaf23e'
        #          }
        #        ]
        #    }
        #    {
        #        success: True,
        #        ret_msg: '',
        #        conn_id: '55f508ad-d17b-48d8-8b19-669280a25a72',
        #        request: {
        #          op: 'auth',
        #          args: [
        #            'cH4MQfkrFNKYiLfpVB',
        #            '1653038985746',
        #            'eede78af3eb916ffc569a5c1466b83e36034324f480ca2684728d17fb606acae'
        #          ]
        #        }
        #    }
        #
        if not self.handle_error_message(client, message):
            return
        # contract pong
        ret_msg = self.safe_string(message, 'ret_msg')
        if ret_msg == 'pong':
            self.handle_pong(client, message)
            return
        # spot pong
        pong = self.safe_integer(message, 'pong')
        if pong is not None:
            self.handle_pong(client, message)
            return
        # usdc pong
        op = self.safe_string(message, 'op')
        if op == 'pong':
            self.handle_pong(client, message)
            return
        event = self.safe_string(message, 'event')
        if event == 'sub':
            self.handle_subscription_status(client, message)
            return
        # contract public and private
        topic = self.safe_string(message, 'topic', '')
        if (topic.find('kline') >= 0 or topic.find('candle') >= 0):
            self.handle_ohlcv(client, message)
            return
        if (topic.find('realtimes') >= 0 or topic.find('instrument_info') >= 0):
            self.handle_ticker(client, message)
            return
        if (topic.find('trade') >= 0):
            if (topic.find('user') >= 0):
                self.handle_my_trades(client, message)
                return
            self.handle_trades(client, message)
        if topic.find('orderBook') >= 0:
            self.handle_order_book(client, message)
            return
        if topic.find('order') >= 0:
            self.handle_order(client, message)
            return
        methods = {
            'realtimes': self.handle_ticker,
            'bookTicker': self.handle_ticker,
            'depth': self.handle_order_book,
            'wallet': self.handle_balance,
            'execution': self.handle_my_trades,
        }
        method = self.safe_value(methods, topic)
        if method is not None:
            method(client, message)
        # contract auth acknowledgement
        request = self.safe_value(message, 'request', {})
        reqOp = self.safe_string(request, 'op')
        if reqOp == 'auth':
            self.handle_authenticate(client, message)
        # usdc auth
        type = self.safe_string(message, 'type')
        if type == 'AUTH_RESP':
            self.handle_authenticate(client, message)
        # private spot topics
        if isinstance(message, list):
            first = self.safe_value(message, 0)
            topic = self.safe_string(first, 'e')
            if topic == 'outboundAccountInfo':
                self.handle_balance(client, message)
            if topic == 'executionReport':
                self.handle_order(client, message)
            if topic == 'ticketInfo':
                self.handle_my_trades(client, message)

    def ping(self, client):
        url = client.url
        timestamp = self.milliseconds()
        if url.find('spot') >= 0:
            return {'ping': timestamp}
        return {'op': 'ping'}

    def handle_pong(self, client, message):
        #
        #   {
        #       success: True,
        #       ret_msg: 'pong',
        #       conn_id: 'db3158a0-8960-44b9-a9de-ac350ee13158',
        #       request: {op: 'ping', args: null}
        #   }
        #
        #   {pong: 1653296711335}
        #
        client.lastPong = self.safe_integer(message, 'pong')
        return message

    def handle_authenticate(self, client, message):
        #
        #    {
        #        success: True,
        #        ret_msg: '',
        #        conn_id: '55f508ad-d17b-48d8-8b19-669280a25a72',
        #        request: {
        #          op: 'auth',
        #          args: [
        #            'cH4MQfkrFNKYiLfpVB',
        #            '1653038985746',
        #            'eede78af3eb916ffc569a5c1466b83e36034324f480ca2684728d17fb606acae'
        #          ]
        #        }
        #    }
        #
        # self will only be effective for swap markets,
        # spot markets don't have self 'authenticated' future
        client.resolve(message, 'authenticated')
        return message

    def handle_subscription_status(self, client, message):
        #
        #    {
        #        topic: 'kline',
        #        event: 'sub',
        #        params: {
        #          symbol: 'LTCUSDT',
        #          binary: 'false',
        #          klineType: '1m',
        #          symbolName: 'LTCUSDT'
        #        },
        #        code: '0',
        #        msg: 'Success'
        #    }
        #
        return message
