# -*- coding: utf-8 -*- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code import ccxt.async_support from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp import hashlib from ccxt.base.types import Any, Balances, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade from ccxt.async_support.base.ws.client import Client from typing import List from ccxt.base.errors import AuthenticationError from ccxt.base.errors import ArgumentsRequired from ccxt.base.errors import NotSupported class mexc(ccxt.async_support.mexc): def describe(self) -> Any: return self.deep_extend(super(mexc, self).describe(), { 'has': { 'ws': True, 'cancelAllOrdersWs': False, 'cancelOrdersWs': False, 'cancelOrderWs': False, 'createOrderWs': False, 'editOrderWs': False, 'fetchBalanceWs': False, 'fetchOpenOrdersWs': False, 'fetchOrderWs': False, 'fetchTradesWs': False, 'watchBalance': True, 'watchMyTrades': True, 'watchOHLCV': True, 'watchOrderBook': True, 'watchOrders': True, 'watchTicker': True, 'watchTickers': True, 'watchBidsAsks': True, 'watchTrades': True, 'watchTradesForSymbols': False, 'unWatchTicker': True, 'unWatchTickers': True, 'unWatchBidsAsks': True, 'unWatchOHLCV': True, 'unWatchOrderBook': True, 'unWatchTrades': True, }, 'urls': { 'api': { 'ws': { 'spot': 'wss://wbs-api.mexc.com/ws', 'swap': 'wss://contract.mexc.com/edge', }, }, }, 'options': { 'listenKeyRefreshRate': 1200000, 'decompressBinary': False, # TODO add reset connection after #16754 is merged 'timeframes': { '1m': 'Min1', '5m': 'Min5', '15m': 'Min15', '30m': 'Min30', '1h': 'Min60', '4h': 'Hour4', '8h': 'Hour8', '1d': 'Day1', '1w': 'Week1', '1M': 'Month1', }, 'watchOrderBook': { 'snapshotDelay': 25, 'snapshotMaxRetries': 3, }, 'listenKey': None, }, 'streaming': { 'ping': self.ping, 'keepAlive': 8000, }, 'exceptions': { }, }) async def watch_ticker(self, symbol: str, params={}) -> Ticker: """ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market https://mexcdevelop.github.io/apidocs/spot_v3_en/#individual-symbol-book-ticker-streams https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels https://mexcdevelop.github.io/apidocs/spot_v3_en/#miniticker :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 boolean [params.miniTicker]: set to True for using the miniTicker endpoint :returns dict: a `ticker structure ` """ await self.load_markets() market = self.market(symbol) messageHash = 'ticker:' + market['symbol'] if market['spot']: channel = 'spot@public.aggre.bookTicker.v3.api.pb@100ms@' + market['id'] return await self.watch_spot_public(channel, messageHash, params) else: channel = 'sub.ticker' requestParams: dict = { 'symbol': market['id'], } return await self.watch_swap_public(channel, messageHash, requestParams, params) def handle_ticker(self, client: Client, message): # # swap # # { # "symbol": "BTC_USDT", # "data": { # "symbol": "BTC_USDT", # "lastPrice": 76376.1, # "riseFallRate": -0.0006, # "fairPrice": 76374.4, # "indexPrice": 76385.8, # "volume24": 962062810, # "amount24": 7344207079.96768, # "maxBidPrice": 84024.3, # "minAskPrice": 68747.2, # "lower24Price": 75620.2, # "high24Price": 77210, # "timestamp": 1731137509138, # "bid1": 76376.2, # "ask1": 76376.3, # "holdVol": 95479623, # "riseFallValue": -46.5, # "fundingRate": 0.0001, # "zone": "UTC+8", # "riseFallRates": [-0.0006, 0.1008, 0.2262, 0.2628, 0.2439, 1.0564], # "riseFallRatesOfTimezone": [0.0065, -0.0013, -0.0006] # }, # "channel": "push.ticker", # "ts": 1731137509138 # } # # spot # # { # "c": "spot@public.bookTicker.v3.api@BTCUSDT", # "d": { # "A": "4.70432", # "B": "6.714863", # "a": "20744.54", # "b": "20744.17" # }, # "s": "BTCUSDT", # "t": 1678643605721 # } # # spot miniTicker # # { # "d": { # "s": "BTCUSDT", # "p": "76522", # "r": "0.0012", # "tr": "0.0012", # "h": "77196.3", # "l": "75630.77", # "v": "584664223.92", # "q": "7666.720258", # "lastRT": "-1", # "MT": "0", # "NV": "--", # "t": "1731135533126" # }, # "c": "spot@public.miniTicker.v3.api@BTCUSDT@UTC+8", # "t": 1731135533126, # "s": "BTCUSDT" # } # self.handle_bid_ask(client, message) rawTicker = self.safe_dict_n(message, ['d', 'data', 'publicAggreBookTicker']) marketId = self.safe_string_2(message, 's', 'symbol') timestamp = self.safe_integer_2(message, 't', 'sendTime') market = self.safe_market(marketId) symbol = market['symbol'] ticker = None if market['spot']: ticker = self.parse_ws_ticker(rawTicker, market) ticker['timestamp'] = timestamp ticker['datetime'] = self.iso8601(timestamp) else: ticker = self.parse_ticker(rawTicker, market) self.tickers[symbol] = ticker messageHash = 'ticker:' + symbol client.resolve(ticker, messageHash) async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list https://mexcdevelop.github.io/apidocs/spot_v3_en/#individual-symbol-book-ticker-streams https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels https://mexcdevelop.github.io/apidocs/spot_v3_en/#minitickers :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.miniTicker]: set to True for using the miniTicker endpoint :returns dict: a `ticker structure ` """ await self.load_markets() symbols = self.market_symbols(symbols, None) messageHashes = [] firstSymbol = self.safe_string(symbols, 0) market = None if firstSymbol is not None: market = self.market(firstSymbol) type = None type, params = self.handle_market_type_and_params('watchTickers', market, params) isSpot = (type == 'spot') url = self.urls['api']['ws']['spot'] if (isSpot) else self.urls['api']['ws']['swap'] request: dict = {} if isSpot: raise NotSupported(self.id + ' watchTickers does not support spot markets') # miniTicker = False # miniTicker, params = self.handle_option_and_params(params, 'watchTickers', 'miniTicker') # topics = [] # if not miniTicker: # if symbols is None: # raise ArgumentsRequired(self.id + ' watchTickers required symbols argument for the bookTicker channel') # } # marketIds = self.market_ids(symbols) # for i in range(0, len(marketIds)): # marketId = marketIds[i] # messageHashes.append('ticker:' + symbols[i]) # channel = 'spot@public.bookTicker.v3.api@' + marketId # topics.append(channel) # } # else: # topics.append('spot@public.miniTickers.v3.api@UTC+8') # if symbols is None: # messageHashes.append('spot:ticker') # else: # for i in range(0, len(symbols)): # messageHashes.append('ticker:' + symbols[i]) # } # } # } # request['method'] = 'SUBSCRIPTION' # request['params'] = topics else: request['method'] = 'sub.tickers' request['params'] = {} messageHashes.append('ticker') ticker = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) if isSpot and self.newUpdates: result: dict = {} result[ticker['symbol']] = ticker return result return self.filter_by_array(self.tickers, 'symbol', symbols) def handle_tickers(self, client: Client, message): # # swap # # { # "channel": "push.tickers", # "data": [ # { # "symbol": "ETH_USDT", # "lastPrice": 2324.5, # "riseFallRate": 0.0356, # "fairPrice": 2324.32, # "indexPrice": 2325.44, # "volume24": 25868309, # "amount24": 591752573.9792, # "maxBidPrice": 2557.98, # "minAskPrice": 2092.89, # "lower24Price": 2239.39, # "high24Price": 2332.59, # "timestamp": 1725872514111 # } # ], # "ts": 1725872514111 # } # # spot # # { # "c": "spot@public.bookTicker.v3.api@BTCUSDT", # "d": { # "A": "4.70432", # "B": "6.714863", # "a": "20744.54", # "b": "20744.17" # }, # "s": "BTCUSDT", # "t": 1678643605721 # } # # spot miniTicker # # { # "d": { # "s": "BTCUSDT", # "p": "76522", # "r": "0.0012", # "tr": "0.0012", # "h": "77196.3", # "l": "75630.77", # "v": "584664223.92", # "q": "7666.720258", # "lastRT": "-1", # "MT": "0", # "NV": "--", # "t": "1731135533126" # }, # "c": "spot@public.miniTicker.v3.api@BTCUSDT@UTC+8", # "t": 1731135533126, # "s": "BTCUSDT" # } # data = self.safe_list_2(message, 'data', 'd') channel = self.safe_string(message, 'c', '') marketId = self.safe_string(message, 's') market = self.safe_market(marketId) channelStartsWithSpot = channel.startswith('spot') marketIdIsUndefined = marketId is None isSpot = channelStartsWithSpot if marketIdIsUndefined else market['spot'] spotPrefix = 'spot:' messageHashPrefix = spotPrefix if isSpot else '' topic = messageHashPrefix + 'ticker' result = [] for i in range(0, len(data)): entry = data[i] ticker = None if isSpot: ticker = self.parse_ws_ticker(entry, market) else: ticker = self.parse_ticker(entry) symbol = ticker['symbol'] self.tickers[symbol] = ticker result.append(ticker) messageHash = 'ticker:' + symbol client.resolve(ticker, messageHash) client.resolve(result, topic) def parse_ws_ticker(self, ticker, market=None): # protobuf ticker # "bidprice": "93387.28", # Best bid price # "bidquantity": "3.73485", # Best bid quantity # "askprice": "93387.29", # Best ask price # "askquantity": "7.669875" # Best ask quantity # # spot # # { # "A": "4.70432", # "B": "6.714863", # "a": "20744.54", # "b": "20744.17" # } # # spot miniTicker # # { # "s": "BTCUSDT", # "p": "76521", # "r": "0.0012", # "tr": "0.0012", # "h": "77196.3", # "l": "75630.77", # "v": "584664223.92", # "q": "7666.720258", # "lastRT": "-1", # "MT": "0", # "NV": "--", # "t": "1731135533126" # } # marketId = self.safe_string(ticker, 's') timestamp = self.safe_integer(ticker, 't') price = self.safe_string(ticker, 'p') return self.safe_ticker({ 'info': ticker, 'symbol': self.safe_symbol(marketId, market), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'open': None, 'high': self.safe_number(ticker, 'h'), 'low': self.safe_number(ticker, 'l'), 'close': price, 'last': price, 'bid': self.safe_number_2(ticker, 'b', 'bidPrice'), 'bidVolume': self.safe_number_2(ticker, 'B', 'bidQuantity'), 'ask': self.safe_number_2(ticker, 'a', 'askPrice'), 'askVolume': self.safe_number_2(ticker, 'A', 'askQuantity'), 'vwap': None, 'previousClose': None, 'change': None, 'percentage': self.safe_number(ticker, 'tr'), 'average': None, 'baseVolume': self.safe_number(ticker, 'v'), 'quoteVolume': self.safe_number(ticker, 'q'), }, market) async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers: """ https://mexcdevelop.github.io/apidocs/spot_v3_en/#individual-symbol-book-ticker-streams watches best bid & ask for symbols :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, False, True) marketType = None if symbols is None: raise ArgumentsRequired(self.id + ' watchBidsAsks required symbols argument') markets = self.markets_for_symbols(symbols) marketType, params = self.handle_market_type_and_params('watchBidsAsks', markets[0], params) isSpot = marketType == 'spot' if not isSpot: raise NotSupported(self.id + ' watchBidsAsks only support spot market') messageHashes = [] topics = [] for i in range(0, len(symbols)): if isSpot: market = self.market(symbols[i]) topics.append('spot@public.aggre.bookTicker.v3.api.pb@100ms@' + market['id']) messageHashes.append('bidask:' + symbols[i]) url = self.urls['api']['ws']['spot'] request: dict = { 'method': 'SUBSCRIPTION', 'params': topics, } ticker = await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) if self.newUpdates: tickers: dict = {} tickers[ticker['symbol']] = ticker return tickers return self.filter_by_array(self.bidsasks, 'symbol', symbols) def handle_bid_ask(self, client: Client, message): # # { # "c": "spot@public.bookTicker.v3.api@BTCUSDT", # "d": { # "A": "4.70432", # "B": "6.714863", # "a": "20744.54", # "b": "20744.17" # }, # "s": "BTCUSDT", # "t": 1678643605721 # } # parsedTicker = self.parse_ws_bid_ask(message) symbol = self.safe_string(parsedTicker, 'symbol') if symbol is None: return self.bidsasks[symbol] = parsedTicker messageHash = 'bidask:' + symbol client.resolve(parsedTicker, messageHash) def parse_ws_bid_ask(self, ticker, market=None): data = self.safe_dict(ticker, 'd') marketId = self.safe_string(ticker, 's') market = self.safe_market(marketId, market) symbol = self.safe_string(market, 'symbol') timestamp = self.safe_integer(ticker, 't') return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'ask': self.safe_number(data, 'a'), 'askVolume': self.safe_number(data, 'A'), 'bid': self.safe_number(data, 'b'), 'bidVolume': self.safe_number(data, 'B'), 'info': ticker, }, market) async def watch_spot_public(self, channel, messageHash, params={}): unsubscribed = self.safe_bool(params, 'unsubscribed', False) params = self.omit(params, ['unsubscribed']) url = self.urls['api']['ws']['spot'] method = 'UNSUBSCRIPTION' if (unsubscribed) else 'SUBSCRIPTION' request: dict = { 'method': method, 'params': [channel], } return await self.watch(url, messageHash, self.extend(request, params), messageHash) async def watch_spot_private(self, channel, messageHash, params={}): self.check_required_credentials() listenKey = await self.authenticate(channel) url = self.urls['api']['ws']['spot'] + '?listenKey=' + listenKey request: dict = { 'method': 'SUBSCRIPTION', 'params': [channel], } return await self.watch(url, messageHash, self.extend(request, params), channel) async def watch_swap_public(self, channel, messageHash, requestParams, params={}): url = self.urls['api']['ws']['swap'] request: dict = { 'method': channel, 'param': requestParams, } message = self.extend(request, params) return await self.watch(url, messageHash, message, messageHash) async def watch_swap_private(self, messageHash, params={}): self.check_required_credentials() channel = 'login' url = self.urls['api']['ws']['swap'] timestamp = str(self.milliseconds()) payload = self.apiKey + timestamp signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256) request: dict = { 'method': channel, 'param': { 'apiKey': self.apiKey, 'signature': signature, 'reqTime': timestamp, }, } message = self.extend(request, params) return await self.watch(url, messageHash, message, channel) async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ https://www.mexc.com/api-docs/spot-v3/websocket-market-streams#trade-streams watches historical candlestick data containing the open, high, low, and close price, and the volume of a market :param str symbol: unified symbol of the market to fetch OHLCV data for :param str timeframe: the length of time each candle represents :param int [since]: timestamp in ms of the earliest candle to fetch :param int [limit]: the maximum amount of candles to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] timeframes = self.safe_value(self.options, 'timeframes', {}) timeframeId = self.safe_string(timeframes, timeframe) messageHash = 'candles:' + symbol + ':' + timeframe ohlcv = None if market['spot']: channel = 'spot@public.kline.v3.api.pb@' + market['id'] + '@' + timeframeId ohlcv = await self.watch_spot_public(channel, messageHash, params) else: channel = 'sub.kline' requestParams: dict = { 'symbol': market['id'], 'interval': timeframeId, } ohlcv = await self.watch_swap_public(channel, messageHash, requestParams, params) if self.newUpdates: limit = ohlcv.getLimit(symbol, limit) return self.filter_by_since_limit(ohlcv, since, limit, 0, True) def handle_ohlcv(self, client: Client, message): # # spot # # { # "d": { # "e": "spot@public.kline.v3.api", # "k": { # "t": 1678642261, # "o": 20626.94, # "c": 20599.69, # "h": 20626.94, # "l": 20597.06, # "v": 27.678686, # "a": 570332.77, # "T": 1678642320, # "i": "Min1" # } # }, # "c": "spot@public.kline.v3.api@BTCUSDT@Min1", # "t": 1678642276459, # "s": "BTCUSDT" # } # # swap # # { # "channel": "push.kline", # "data": { # "a": 325653.3287, # "c": 38839, # "h": 38909.5, # "interval": "Min1", # "l": 38833, # "o": 38901.5, # "q": 83808, # "rc": 38839, # "rh": 38909.5, # "rl": 38833, # "ro": 38909.5, # "symbol": "BTC_USDT", # "t": 1651230660 # }, # "symbol": "BTC_USDT", # "ts": 1651230713067 # } # protobuf # { # "channel":"spot@public.kline.v3.api.pb@BTCUSDT@Min1", # "symbol":"BTCUSDT", # "symbolId":"2fb942154ef44a4ab2ef98c8afb6a4a7", # "createTime":"1754737941062", # "publicSpotKline":{ # "interval":"Min1", # "windowStart":"1754737920", # "openingPrice":"117317.31", # "closingPrice":"117325.26", # "highestPrice":"117341", # "lowestPrice":"117317.3", # "volume":"3.12599854", # "amount":"366804.43", # "windowEnd":"1754737980" # } # } # parsed: dict = None symbol: Str = None timeframe: Str = None if 'publicSpotKline' in message: symbol = self.symbol(self.safe_string(message, 'symbol')) data = self.safe_dict(message, 'publicSpotKline', {}) timeframeId = self.safe_string(data, 'interval') timeframe = self.find_timeframe(timeframeId, self.options['timeframes']) parsed = self.parse_ws_ohlcv(data, self.safe_market(symbol)) else: d = self.safe_value_2(message, 'd', 'data', {}) rawOhlcv = self.safe_value(d, 'k', d) timeframeId = self.safe_string_2(rawOhlcv, 'i', 'interval') timeframes = self.safe_value(self.options, 'timeframes', {}) timeframe = self.find_timeframe(timeframeId, timeframes) marketId = self.safe_string_2(message, 's', 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] parsed = self.parse_ws_ohlcv(rawOhlcv, market) messageHash = 'candles:' + symbol + ':' + timeframe self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {}) stored = self.safe_value(self.ohlcvs[symbol], timeframe) if stored is None: limit = self.safe_integer(self.options, 'OHLCVLimit', 1000) stored = ArrayCacheByTimestamp(limit) self.ohlcvs[symbol][timeframe] = stored stored.append(parsed) client.resolve(stored, messageHash) def parse_ws_ohlcv(self, ohlcv, market=None) -> list: # # spot # # { # "t": 1678642260, # "o": 20626.94, # "c": 20599.69, # "h": 20626.94, # "l": 20597.06, # "v": 27.678686, # "a": 570332.77, # "T": 1678642320, # "i": "Min1" # } # # swap # { # "symbol": "BTC_USDT", # "interval": "Min1", # "t": 1680055080, # "o": 27301.9, # "c": 27301.8, # "h": 27301.9, # "l": 27301.8, # "a": 8.19054, # "q": 3, # "ro": 27301.8, # "rc": 27301.8, # "rh": 27301.8, # "rl": 27301.8 # } # protobuf # # "interval":"Min1", # "windowStart":"1754737920", # "openingPrice":"117317.31", # "closingPrice":"117325.26", # "highestPrice":"117341", # "lowestPrice":"117317.3", # "volume":"3.12599854", # "amount":"366804.43", # "windowEnd":"1754737980" # return [ self.safe_timestamp_2(ohlcv, 't', 'windowStart'), self.safe_number_2(ohlcv, 'o', 'openingPrice'), self.safe_number_2(ohlcv, 'h', 'highestPrice'), self.safe_number_2(ohlcv, 'l', 'lowestPrice'), self.safe_number_2(ohlcv, 'c', 'closingPrice'), self.safe_number_2(ohlcv, 'v', 'volume'), ] async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ https://www.mexc.com/api-docs/spot-v3/websocket-market-streams#trade-streams https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels 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.frequency]: the frequency of the order book updates, default is '10ms', can be '100ms' or '10ms :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] messageHash = 'orderbook:' + symbol orderbook = None if market['spot']: frequency = None frequency, params = self.handle_option_and_params(params, 'watchOrderBook', 'frequency', '100ms') channel = 'spot@public.aggre.depth.v3.api.pb@' + frequency + '@' + market['id'] orderbook = await self.watch_spot_public(channel, messageHash, params) else: channel = 'sub.depth' requestParams: dict = { 'symbol': market['id'], } orderbook = await self.watch_swap_public(channel, messageHash, requestParams, params) return orderbook.limit() def handle_order_book_subscription(self, client: Client, message): # spot # {id: 0, code: 0, msg: "spot@public.increase.depth.v3.api@BTCUSDT"} # msg = self.safe_string(message, 'msg') parts = msg.split('@') marketId = self.safe_string(parts, 2) symbol = self.safe_symbol(marketId) self.orderbooks[symbol] = self.order_book({}) def get_cache_index(self, orderbook, cache): # return the first index of the cache that can be applied to the orderbook or -1 if not possible nonce = self.safe_integer(orderbook, 'nonce') firstDelta = self.safe_value(cache, 0) firstDeltaNonce = self.safe_integer_n(firstDelta, ['r', 'version', 'fromVersion']) if nonce < firstDeltaNonce - 1: return -1 for i in range(0, len(cache)): delta = cache[i] deltaNonce = self.safe_integer_n(delta, ['r', 'version', 'fromVersion']) if deltaNonce >= nonce: return i return len(cache) def handle_order_book(self, client: Client, message): # # spot # { # "c": "spot@public.increase.depth.v3.api@BTCUSDT", # "d": { # "asks": [{ # "p": "20290.89", # "v": "0.000000" # }], # "e": "spot@public.increase.depth.v3.api", # "r": "3407459756" # }, # "s": "BTCUSDT", # "t": 1661932660144 # } # # # # swap # { # "channel":"push.depth", # "data":{ # "asks":[ # [ # 39146.5, # 11264, # 1 # ] # ], # "bids":[ # [ # 39144, # 35460, # 1 # ] # ], # "end":4895965272, # "begin":4895965271 # }, # "symbol":"BTC_USDT", # "ts":1651239652372 # } # protofbuf # { # "channel":"spot@public.aggre.depth.v3.api.pb@100ms@BTCUSDT", # "symbol":"BTCUSDT", # "sendTime":"1754741322152", # "publicAggreDepths":{ # "asks":[ # { # "price":"117145.49", # "quantity":"0" # } # ], # "bids":[ # { # "price":"117053.41", # "quantity":"1.86837271" # } # ], # "eventType":"spot@public.aggre.depth.v3.api.pb@100ms", # "fromVersion":"43296363236", # "toVersion":"43296363255" # } # } # data = self.safe_dict_n(message, ['d', 'data', 'publicAggreDepths']) marketId = self.safe_string_2(message, 's', 'symbol') symbol = self.safe_symbol(marketId) messageHash = 'orderbook:' + symbol subscription = self.safe_value(client.subscriptions, messageHash) limit = self.safe_integer(subscription, 'limit') if not (symbol in self.orderbooks): self.orderbooks[symbol] = self.order_book() storedOrderBook = self.orderbooks[symbol] nonce = self.safe_integer(storedOrderBook, 'nonce') shouldReturn = False if nonce is None: cacheLength = len(storedOrderBook.cache) snapshotDelay = self.handle_option('watchOrderBook', 'snapshotDelay', 25) if cacheLength == snapshotDelay: self.spawn(self.load_order_book, client, messageHash, symbol, limit, {}) storedOrderBook.cache.append(data) return try: self.handle_delta(storedOrderBook, data) timestamp = self.safe_integer_n(message, ['t', 'ts', 'sendTime']) storedOrderBook['timestamp'] = timestamp storedOrderBook['datetime'] = self.iso8601(timestamp) except Exception as e: del client.subscriptions[messageHash] client.reject(e, messageHash) # return shouldReturn = True if shouldReturn: return # go requirement client.resolve(storedOrderBook, messageHash) def handle_bookside_delta(self, bookside, bidasks): # # [{ # "p": "20290.89", # "v": "0.000000" # }] # for i in range(0, len(bidasks)): bidask = bidasks[i] if isinstance(bidask, list): bookside.storeArray(bidask) else: price = self.safe_float_2(bidask, 'p', 'price') amount = self.safe_float_2(bidask, 'v', 'quantity') bookside.store(price, amount) def handle_delta(self, orderbook, delta): existingNonce = self.safe_integer(orderbook, 'nonce') deltaNonce = self.safe_integer_n(delta, ['r', 'version', 'fromVersion']) if deltaNonce < existingNonce: # even when doing < comparison, self happens: https://app.travis-ci.com/github/ccxt/ccxt/builds/269234741#L1809 # so, we just skip old updates return orderbook['nonce'] = deltaNonce asks = self.safe_list(delta, 'asks', []) bids = self.safe_list(delta, 'bids', []) asksOrderSide = orderbook['asks'] bidsOrderSide = orderbook['bids'] self.handle_bookside_delta(asksOrderSide, asks) self.handle_bookside_delta(bidsOrderSide, bids) async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ https://www.mexc.com/api-docs/spot-v3/websocket-market-streams#trade-streams https://mexcdevelop.github.io/apidocs/contract_v1_en/#public-channels get the list of most recent trades for a particular symbol :param str symbol: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] messageHash = 'trades:' + symbol trades = None if market['spot']: channel = 'spot@public.aggre.deals.v3.api.pb@100ms@' + market['id'] trades = await self.watch_spot_public(channel, messageHash, params) else: channel = 'sub.deal' requestParams: dict = { 'symbol': market['id'], } trades = await self.watch_swap_public(channel, messageHash, requestParams, params) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) def handle_trades(self, client: Client, message): # protobuf # { # "channel": "spot@public.aggre.deals.v3.api.pb@100ms@BTCUSDT", # "publicdeals": { # "dealsList": [ # { # "price": "93220.00", # Trade price # "quantity": "0.04438243", # Trade quantity # "tradetype": 2, # Trade type(1: Buy, 2: Sell) # "time": 1736409765051 # Trade time # } # ], # "eventtype": "spot@public.aggre.deals.v3.api.pb@100ms" # Event type # }, # "symbol": "BTCUSDT", # Trading pair # "sendtime": 1736409765052 # Event time # } # # { # "c": "spot@public.deals.v3.api@BTCUSDT", # "d": { # "deals": [{ # "p": "20382.70", # "v": "0.043800", # "S": 1, # "t": 1678593222456, # },], # "e": "spot@public.deals.v3.api", # }, # "s": "BTCUSDT", # "t": 1678593222460, # } # # swap # { # "symbol": "BTC_USDT", # "data": [ # { # "p": 114350.4, # "v": 4, # "T": 2, # "O": 3, # "M": 2, # "t": 1760368563597 # } # ], # "channel": "push.deal", # "ts": 1680055941870 # } # marketId = self.safe_string_2(message, 's', 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] messageHash = 'trades:' + symbol stored = self.safe_value(self.trades, symbol) if stored is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) stored = ArrayCache(limit) self.trades[symbol] = stored d = self.safe_dict_n(message, ['d', 'publicAggreDeals']) trades = self.safe_list_2(d, 'deals', 'dealsList', [d]) if d is None: trades = self.safe_list(message, 'data', []) for j in range(0, len(trades)): parsedTrade = None if market['spot']: parsedTrade = self.parse_ws_trade(trades[j], market) else: parsedTrade = self.parse_trade(trades[j], market) stored.append(parsedTrade) client.resolve(stored, messageHash) async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ https://www.mexc.com/api-docs/spot-v3/websocket-user-data-streams#spot-account-deals https://mexcdevelop.github.io/apidocs/contract_v1_en/#private-channels watches information on multiple trades made by the user :param str symbol: unified market symbol of the market trades were made in :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trade structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ await self.load_markets() messageHash = 'myTrades' market = None if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash = messageHash + ':' + symbol type = None type, params = self.handle_market_type_and_params('watchMyTrades', market, params) trades = None if type == 'spot': channel = 'spot@private.deals.v3.api.pb' trades = await self.watch_spot_private(channel, messageHash, params) else: trades = await self.watch_swap_private(messageHash, params) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True) def handle_my_trade(self, client: Client, message, subscription=None): # # { # "c": "spot@private.deals.v3.api", # "d": { # "p": "22339.99", # "v": "0.000235", # "S": 1, # "T": 1678670940695, # "t": "9f6a47fb926442e496c5c4c104076ae3", # "c": '', # "i": "e2b9835d1b6745f8a10ab74a81a16d50", # "m": 0, # "st": 0 # }, # "s": "BTCUSDT", # "t": 1678670940700 # } # { # channel: "spot@private.deals.v3.api.pb", # symbol: "MXUSDT", # sendTime: 1736417034332, # privateDeals { # price: "3.6962", # quantity: "1", # amount: "3.6962", # tradeType: 2, # tradeId: "505979017439002624X1", # orderId: "C02__505979017439002624115", # feeAmount: "0.0003998377369698171", # feeCurrency: "MX", # time: 1736417034280 # } # } # messageHash = 'myTrades' data = self.safe_dict_n(message, ['d', 'data', 'privateDeals']) futuresMarketId = self.safe_string(data, 'symbol') marketId = self.safe_string_2(message, 's', 'symbol', futuresMarketId) market = self.safe_market(marketId) symbol = market['symbol'] trade = None if market['spot']: trade = self.parse_ws_trade(data, market) else: trade = self.parse_trade(data, market) trades = self.myTrades if trades is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) trades = ArrayCacheBySymbolById(limit) self.myTrades = trades trades.append(trade) client.resolve(trades, messageHash) symbolSpecificMessageHash = messageHash + ':' + symbol client.resolve(trades, symbolSpecificMessageHash) def parse_ws_trade(self, trade, market=None): # # public trade(protobuf) # { # "p": "20382.70", # "v": "0.043800", # "S": 1, # "t": 1678593222456, # } # private trade # { # "S": 1, # "T": 1661938980268, # "c": "", # "i": "c079b0fcb80a46e8b128b281ce4e4f38", # "m": 1, # "p": "1.008", # "st": 0, # "t": "4079b1522a0b40e7919f609e1ea38d44", # "v": "5" # } # # d: { # p: '1.0005', # v: '5.71', # a: '5.712855', # S: 1, # T: 1714325698237, # t: 'edafcd9fdc2f426e82443d114691f724', # c: '', # i: 'C02__413321238354677760043', # m: 0, # st: 0, # n: '0.005712855', # N: 'USDT' # } # protobuf # # { # price: "3.6962", # quantity: "1", # amount: "3.6962", # tradeType: 2, # tradeId: "505979017439002624X1", # orderId: "C02__505979017439002624115", # feeAmount: "0.0003998377369698171", # feeCurrency: "MX", # time: 1736417034280 # } # timestamp = self.safe_integer_2(trade, 'T', 'time') tradeId = self.safe_string_2(trade, 't', 'tradeId') if timestamp is None: timestamp = self.safe_integer(trade, 't') tradeId = None priceString = self.safe_string_2(trade, 'p', 'price') amountString = self.safe_string_2(trade, 'v', 'quantity') rawSide = self.safe_string_2(trade, 'S', 'tradeType') side = 'buy' if (rawSide == '1') else 'sell' isMaker = self.safe_integer(trade, 'm') feeAmount = self.safe_string_2(trade, 'n', 'feeAmount') feeCurrencyId = self.safe_string_2(trade, 'N', 'feeCurrency') return self.safe_trade({ 'info': trade, 'id': tradeId, 'order': self.safe_string_2(trade, 'i', 'orderId'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'symbol': self.safe_symbol(None, market), 'type': None, 'side': side, 'takerOrMaker': 'maker' if (isMaker) else 'taker', 'price': priceString, 'amount': amountString, 'cost': self.safe_string(trade, 'amount'), 'fee': { 'cost': feeAmount, 'currency': self.safe_currency_code(feeCurrencyId), }, }, market) async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ https://www.mexc.com/api-docs/spot-v3/websocket-user-data-streams#spot-account-orders https://mexcdevelop.github.io/apidocs/spot_v3_en/#margin-account-orders 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 :param str|None params['type']: the type of orders to retrieve, can be 'spot' or 'margin' :returns dict[]: a list of `order structures ` """ await self.load_markets() messageHash = 'orders' market = None if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash = messageHash + ':' + symbol type = None type, params = self.handle_market_type_and_params('watchOrders', market, params) orders = None if type == 'spot': channel = 'spot@private.orders.v3.api.pb' orders = await self.watch_spot_private(channel, messageHash, params) else: orders = await self.watch_swap_private(messageHash, params) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True) def handle_order(self, client: Client, message): # # spot # { # "c": "spot@private.orders.v3.api", # "d": { # "A":8.0, # "O":1661938138000, # "S":1, # "V":10, # "a":8, # "c":"", # "i":"e03a5c7441e44ed899466a7140b71391", # "m":0, # "o":1, # "p":0.8, # "s":1, # "v":10, # "ap":0, # "cv":0, # "ca":0 # }, # "s": "MXUSDT", # "t": 1661938138193 # } # spot - stop # { # "c": "spot@private.orders.v3.api", # "d": { # "N":"USDT", # "O":1661938853715, # "P":0.9, # "S":1, # "T":"LE", # "i":"f6d82e5f41d745f59fe9d3cafffd80b5", # "o":100, # "p":1.01, # "s":"NEW", # "v":6 # }, # "s": "MXUSDT", # "t": 1661938853727 # } # margin # { # "c": "margin@private.orders.v3.api", # "d":{ # "O":1661938138000, # "p":"0.8", # "a":"8", # "v":"10", # "da":"0", # "dv":"0", # "A":"8.0", # "V":"10", # "n": "0", # "N": "USDT", # "S":1, # "o":1, # "s":1, # "i":"e03a5c7441e44ed899466a7140b71391", # }, # "s": "MXUSDT", # "t":1661938138193 # } # protobuf # { # channel: "spot@private.orders.v3.api.pb", # symbol: "MXUSDT", # sendTime: 1736417034281, # privateOrders {} # } # messageHash = 'orders' data = self.safe_dict_n(message, ['d', 'data', 'privateOrders']) futuresMarketId = self.safe_string(data, 'symbol') marketId = self.safe_string_2(message, 's', 'symbol', futuresMarketId) market = self.safe_market(marketId) symbol = market['symbol'] parsed = None if market['spot']: parsed = self.parse_ws_order(data, market) else: parsed = self.parse_order(data, market) orders = self.orders if orders is None: limit = self.safe_integer(self.options, 'ordersLimit', 1000) orders = ArrayCacheBySymbolById(limit) self.orders = orders orders.append(parsed) client.resolve(orders, messageHash) symbolSpecificMessageHash = messageHash + ':' + symbol client.resolve(orders, symbolSpecificMessageHash) def parse_ws_order(self, order, market=None): # # spot # { # "A":8.0, # "O":1661938138000, # "S":1, # "V":10, # "a":8, # "c":"", # "i":"e03a5c7441e44ed899466a7140b71391", # "m":0, # "o":1, # "p":0.8, # "s":1, # "v":10, # "ap":0, # "cv":0, # "ca":0 # } # spot - stop # { # "N":"USDT", # "O":1661938853715, # "P":0.9, # "S":1, # "T":"LE", # "i":"f6d82e5f41d745f59fe9d3cafffd80b5", # "o":100, # "p":1.01, # "s":"NEW", # "v":6 # } # margin # { # "O":1661938138000, # "p":"0.8", # "a":"8", # "v":"10", # "da":"0", # "dv":"0", # "A":"8.0", # "V":"10", # "n": "0", # "N": "USDT", # "S":1, # "o":1, # "s":1, # "i":"e03a5c7441e44ed899466a7140b71391", # } # protofbuf spot order # { # "id":"C02__583905164440776704043", # "price":"0.001053", # "quantity":"2000", # "amount":"0", # "avgPrice":"0.001007", # "orderType":5, # "tradeType":1, # "remainAmount":"0.092", # "remainQuantity":"0", # "lastDealQuantity":"2000", # "cumulativeQuantity":"2000", # "cumulativeAmount":"2.014", # "status":2, # "createTime":"1754996075502" # } # timestamp = self.safe_integer(order, 'createTime') side = self.safe_string(order, 'tradeType') status = self.safe_string(order, 'status') type = self.safe_string(order, 'orderType') fee = None feeCurrency = self.safe_string(order, 'N') if feeCurrency is not None: fee = { 'currency': feeCurrency, 'cost': None, } return self.safe_order({ 'id': self.safe_string(order, 'id'), 'clientOrderId': self.safe_string(order, 'clientId'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': None, 'status': self.parse_ws_order_status(status, market), 'symbol': self.safe_symbol(None, market), 'type': self.parse_ws_order_type(type), 'timeInForce': self.parse_ws_time_in_force(type), 'side': 'buy' if (side == '1') else 'sell', 'price': self.safe_string(order, 'price'), 'stopPrice': None, 'triggerPrice': None, 'average': self.safe_string(order, 'avgPrice'), 'amount': self.safe_string(order, 'quantity'), 'cost': self.safe_string(order, 'amount'), 'filled': self.safe_string(order, 'cumulativeQuantity'), 'remaining': self.safe_string(order, 'remainQuantity'), 'fee': fee, 'trades': None, 'info': order, }, market) def parse_ws_order_status(self, status, market=None): statuses: dict = { '1': 'open', # new order '2': 'closed', # filled '3': 'open', # partially filled '4': 'canceled', # canceled '5': 'closed', # partially filled then canceled 'NEW': 'open', 'CANCELED': 'canceled', 'EXECUTED': 'closed', 'FAILED': 'rejected', } return self.safe_string(statuses, status, status) def parse_ws_order_type(self, type): types: dict = { '1': 'limit', # LIMIT_ORDER '2': 'limit', # POST_ONLY '3': None, # IMMEDIATE_OR_CANCEL '4': None, # FILL_OR_KILL '5': 'market', # MARKET_ORDER '100': 'limit', # STOP_LIMIT } return self.safe_string(types, type) def parse_ws_time_in_force(self, timeInForce): timeInForceIds: dict = { '1': 'GTC', # LIMIT_ORDER '2': 'PO', # POST_ONLY '3': 'IOC', # IMMEDIATE_OR_CANCEL '4': 'FOK', # FILL_OR_KILL '5': 'GTC', # MARKET_ORDER '100': 'GTC', # STOP_LIMIT } return self.safe_string(timeInForceIds, timeInForce) async def watch_balance(self, params={}) -> Balances: """ https://www.mexc.com/api-docs/spot-v3/websocket-user-data-streams#spot-account-update 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 ` """ await self.load_markets() type = None type, params = self.handle_market_type_and_params('watchBalance', None, params) messageHash = 'balance:' + type if type == 'spot': channel = 'spot@private.account.v3.api.pb' return await self.watch_spot_private(channel, messageHash, params) else: return await self.watch_swap_private(messageHash, params) def handle_balance(self, client: Client, message): # # spot # # { # channel: "spot@private.account.v3.api.pb", # createTime: "1758134605364", # sendTime: "1758134605373", # privateAccount: { # vcoinName: "USDT", # coinId: "128f589271cb4951b03e71e6323eb7be", # balanceAmount: "0.006016465074677006", # balanceAmountChange: "-4.4022", # frozenAmount: "4.4022", # frozenAmountChange: "4.4022", # type: "ENTRUST_PLACE", # time: "1758134605364", # } # } # # # swap balance # # { # "channel": "push.personal.asset", # "data": { # "availableBalance": 67.2426683348, # "bonus": 0, # "currency": "USDT", # "frozenBalance": 0, # "positionMargin": 1.36945756 # }, # "ts": 1680059188190 # } # channel = self.safe_string(message, 'channel') type = 'spot' if (channel == 'spot@private.account.v3.api.pb') else 'swap' messageHash = 'balance:' + type data = self.safe_dict_n(message, ['data', 'privateAccount']) futuresTimestamp = self.safe_integer_2(message, 'ts', 'createTime') timestamp = self.safe_integer_2(data, 'time', futuresTimestamp) if not (type in self.balance): self.balance[type] = {} self.balance[type]['info'] = data self.balance[type]['timestamp'] = timestamp self.balance[type]['datetime'] = self.iso8601(timestamp) currencyId = self.safe_string_n(data, ['currency', 'vcoinName']) code = self.safe_currency_code(currencyId) account = self.account() account['free'] = self.safe_string_2(data, 'balanceAmount', 'availableBalance') account['used'] = self.safe_string_n(data, ['frozenBalance', 'frozenAmount']) self.balance[type][code] = account self.balance[type] = self.safe_balance(self.balance[type]) client.resolve(self.balance[type], messageHash) async def un_watch_ticker(self, symbol: str, params={}) -> Any: """ unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ await self.load_markets() market = self.market(symbol) messageHash = 'unsubscribe:ticker:' + market['symbol'] url = None channel = None if market['spot']: channel = 'spot@public.aggre.bookTicker.v3.api.pb@100ms@' + market['id'] url = self.urls['api']['ws']['spot'] params['unsubscribed'] = True self.watch_spot_public(channel, messageHash, params) else: channel = 'unsub.ticker' requestParams: dict = { 'symbol': market['id'], } url = self.urls['api']['ws']['swap'] self.watch_swap_public(channel, messageHash, requestParams, params) client = self.client(url) self.handle_unsubscriptions(client, [messageHash]) return None async def un_watch_tickers(self, symbols: Strings = None, params={}) -> Any: """ unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ await self.load_markets() symbols = self.market_symbols(symbols, None) messageHashes = [] firstSymbol = self.safe_string(symbols, 0) market = None if firstSymbol is not None: market = self.market(firstSymbol) type = None type, params = self.handle_market_type_and_params('watchTickers', market, params) isSpot = (type == 'spot') url = self.urls['api']['ws']['spot'] if (isSpot) else self.urls['api']['ws']['swap'] request: dict = {} if isSpot: raise NotSupported(self.id + ' watchTickers does not support spot markets') # miniTicker = False # miniTicker, params = self.handle_option_and_params(params, 'watchTickers', 'miniTicker') # topics = [] # if not miniTicker: # if symbols is None: # raise ArgumentsRequired(self.id + ' watchTickers required symbols argument for the bookTicker channel') # } # marketIds = self.market_ids(symbols) # for i in range(0, len(marketIds)): # marketId = marketIds[i] # messageHashes.append('unsubscribe:ticker:' + symbols[i]) # channel = 'spot@public.bookTicker.v3.api@' + marketId # topics.append(channel) # } # else: # topics.append('spot@public.miniTickers.v3.api@UTC+8') # if symbols is None: # messageHashes.append('unsubscribe:spot:ticker') # else: # for i in range(0, len(symbols)): # messageHashes.append('unsubscribe:ticker:' + symbols[i]) # } # } # } # request['method'] = 'UNSUBSCRIPTION' # request['params'] = topics else: request['method'] = 'unsub.tickers' request['params'] = {} messageHashes.append('unsubscribe:ticker') client = self.client(url) self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) self.handle_unsubscriptions(client, messageHashes) return None async def un_watch_bids_asks(self, symbols: Strings = None, params={}) -> Any: """ unWatches best bid & ask for symbols :param str[] symbols: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, True, False, True) marketType = None if symbols is None: raise ArgumentsRequired(self.id + ' watchBidsAsks required symbols argument') markets = self.markets_for_symbols(symbols) marketType, params = self.handle_market_type_and_params('watchBidsAsks', markets[0], params) isSpot = marketType == 'spot' if not isSpot: raise NotSupported(self.id + ' watchBidsAsks only support spot market') messageHashes = [] topics = [] for i in range(0, len(symbols)): if isSpot: market = self.market(symbols[i]) topics.append('spot@public.aggre.bookTicker.v3.api.pb@100ms@' + market['id']) messageHashes.append('unsubscribe:bidask:' + symbols[i]) url = self.urls['api']['ws']['spot'] request: dict = { 'method': 'UNSUBSCRIPTION', 'params': topics, } client = self.client(url) self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes) self.handle_unsubscriptions(client, messageHashes) return None async def un_watch_ohlcv(self, symbol: str, timeframe: str = '1m', params={}) -> Any: """ unWatches 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 dict [params]: extra parameters specific to the exchange API endpoint :param dict [params.timezone]: if provided, kline intervals are interpreted in that timezone instead of UTC, example '+08:00' :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] timeframes = self.safe_value(self.options, 'timeframes', {}) timeframeId = self.safe_string(timeframes, timeframe) messageHash = 'unsubscribe:candles:' + symbol + ':' + timeframe url = None if market['spot']: url = self.urls['api']['ws']['spot'] channel = 'spot@public.kline.v3.api.pb@' + market['id'] + '@' + timeframeId params['unsubscribed'] = True self.watch_spot_public(channel, messageHash, params) else: url = self.urls['api']['ws']['swap'] channel = 'unsub.kline' requestParams: dict = { 'symbol': market['id'], 'interval': timeframeId, } self.watch_swap_public(channel, messageHash, requestParams, params) client = self.client(url) self.handle_unsubscriptions(client, [messageHash]) return None async def un_watch_order_book(self, symbol: str, params={}) -> Any: """ unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data :param str symbol: unified array of symbols :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.frequency]: the frequency of the order book updates, default is '10ms', can be '100ms' or '10ms :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] messageHash = 'unsubscribe:orderbook:' + symbol url = None if market['spot']: url = self.urls['api']['ws']['spot'] frequency = None frequency, params = self.handle_option_and_params(params, 'watchOrderBook', 'frequency', '100ms') channel = 'spot@public.aggre.depth.v3.api.pb@' + frequency + '@' + market['id'] params['unsubscribed'] = True self.watch_spot_public(channel, messageHash, params) else: url = self.urls['api']['ws']['swap'] channel = 'unsub.depth' requestParams: dict = { 'symbol': market['id'], } self.watch_swap_public(channel, messageHash, requestParams, params) client = self.client(url) self.handle_unsubscriptions(client, [messageHash]) return None async def un_watch_trades(self, symbol: str, params={}) -> Any: """ unsubscribes from the trades channel :param str symbol: unified symbol of the market to fetch trades for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.name]: the name of the method to call, 'trade' or 'aggTrade', default is 'trade' :returns dict[]: a list of `trade structures ` """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] messageHash = 'unsubscribe:trades:' + symbol url = None if market['spot']: url = self.urls['api']['ws']['spot'] channel = 'spot@public.aggre.deals.v3.api.pb@100ms@' + market['id'] params['unsubscribed'] = True self.watch_spot_public(channel, messageHash, params) else: url = self.urls['api']['ws']['swap'] channel = 'unsub.deal' requestParams: dict = { 'symbol': market['id'], } self.watch_swap_public(channel, messageHash, requestParams, params) client = self.client(url) self.handle_unsubscriptions(client, [messageHash]) return None def handle_unsubscriptions(self, client: Client, messageHashes: List[str]): for i in range(0, len(messageHashes)): messageHash = messageHashes[i] subMessageHash = messageHash.replace('unsubscribe:', '') self.clean_unsubscription(client, subMessageHash, messageHash) if messageHash.find('ticker') >= 0: symbol = messageHash.replace('unsubscribe:ticker:', '') if symbol.find('unsubscribe') >= 0: # unWatchTickers symbols = list(self.tickers.keys()) for j in range(0, len(symbols)): del self.tickers[symbols[j]] elif symbol in self.tickers: del self.tickers[symbol] elif messageHash.find('bidask') >= 0: symbol = messageHash.replace('unsubscribe:bidask:', '') if symbol in self.bidsasks: del self.bidsasks[symbol] elif messageHash.find('candles') >= 0: splitHashes = messageHash.split(':') symbol = self.safe_string(splitHashes, 2) if len(splitHashes) > 4: symbol += ':' + self.safe_string(splitHashes, 3) if symbol in self.ohlcvs: del self.ohlcvs[symbol] elif messageHash.find('orderbook') >= 0: symbol = messageHash.replace('unsubscribe:orderbook:', '') if symbol in self.orderbooks: del self.orderbooks[symbol] elif messageHash.find('trades') >= 0: symbol = messageHash.replace('unsubscribe:trades:', '') if symbol in self.trades: del self.trades[symbol] async def authenticate(self, subscriptionHash, params={}): # we only need one listenKey since ccxt shares connections listenKey = self.safe_string(self.options, 'listenKey') if listenKey is not None: return listenKey response = await self.spotPrivatePostUserDataStream(params) # # { # "listenKey": "pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1" # } # listenKey = self.safe_string(response, 'listenKey') self.options['listenKey'] = listenKey listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 1200000) self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, listenKey, params) return listenKey async def keep_alive_listen_key(self, listenKey, params={}): if listenKey is None: return request: dict = { 'listenKey': listenKey, } try: await self.spotPrivatePutUserDataStream(self.extend(request, params)) listenKeyRefreshRate = self.safe_integer(self.options, 'listenKeyRefreshRate', 1200000) self.delay(listenKeyRefreshRate, self.keep_alive_listen_key, listenKey, params) except Exception as error: url = self.urls['api']['ws']['spot'] + '?listenKey=' + listenKey client = self.client(url) self.options['listenKey'] = None client.reject(error) del self.clients[url] def handle_pong(self, client: Client, message): client.lastPong = self.milliseconds() return message def handle_subscription_status(self, client: Client, message): # # { # "id": 0, # "code": 0, # "msg": "spot@public.increase.depth.v3.api@BTCUSDT" # } # Set the default to an empty string if the message is empty during the test. msg = self.safe_string(message, 'msg', '') if msg == 'PONG': self.handle_pong(client, message) elif msg.find('@') > -1: parts = msg.split('@') channel = self.safe_string(parts, 1) methods: dict = { 'public.increase.depth.v3.api': self.handle_order_book_subscription, 'public.aggre.depth.v3.api.pb': self.handle_order_book_subscription, } method = self.safe_value(methods, channel) if method is not None: method(client, message) def handle_protobuf_message(self, client: Client, message): # protobuf message decoded # { # "channel":"spot@public.kline.v3.api.pb@BTCUSDT@Min1", # "symbol":"BTCUSDT", # "symbolId":"2fb942154ef44a4ab2ef98c8afb6a4a7", # "createTime":"1754737941062", # "publicSpotKline":{ # "interval":"Min1", # "windowStart":"1754737920", # "openingPrice":"117317.31", # "closingPrice":"117325.26", # "highestPrice":"117341", # "lowestPrice":"117317.3", # "volume":"3.12599854", # "amount":"366804.43", # "windowEnd":"1754737980" # } # } channel = self.safe_string(message, 'channel') channelParts = channel.split('@') channelId = self.safe_string(channelParts, 1) if channelId == 'public.kline.v3.api.pb': self.handle_ohlcv(client, message) elif channelId == 'public.aggre.deals.v3.api.pb': self.handle_trades(client, message) elif channelId == 'public.aggre.bookTicker.v3.api.pb': self.handle_ticker(client, message) elif channelId == 'public.aggre.depth.v3.api.pb': self.handle_order_book(client, message) elif channelId == 'private.account.v3.api.pb': self.handle_balance(client, message) elif channelId == 'private.deals.v3.api.pb': self.handle_my_trade(client, message) elif channelId == 'private.orders.v3.api.pb': self.handle_order(client, message) return True def handle_message(self, client: Client, message): if isinstance(message, str): if message == 'Invalid listen key': error = AuthenticationError(self.id + ' invalid listen key') client.reject(error) return if self.is_binary_message(message): message = self.decode_proto_msg(message) self.handle_protobuf_message(client, message) return if 'msg' in message: self.handle_subscription_status(client, message) return c = self.safe_string(message, 'c') channel = None if c is None: channel = self.safe_string(message, 'channel') else: parts = c.split('@') channel = self.safe_string(parts, 1) methods: dict = { 'public.deals.v3.api': self.handle_trades, 'push.deal': self.handle_trades, 'public.kline.v3.api': self.handle_ohlcv, 'push.kline': self.handle_ohlcv, 'public.bookTicker.v3.api': self.handle_ticker, 'public.miniTicker.v3.api': self.handle_ticker, 'public.miniTickers.v3.api': self.handle_tickers, 'push.ticker': self.handle_ticker, 'push.tickers': self.handle_tickers, 'public.increase.depth.v3.api': self.handle_order_book, 'push.depth': self.handle_order_book, 'private.orders.v3.api': self.handle_order, 'push.personal.order': self.handle_order, 'private.account.v3.api': self.handle_balance, 'push.personal.asset': self.handle_balance, 'private.deals.v3.api': self.handle_my_trade, 'push.personal.order.deal': self.handle_my_trade, 'pong': self.handle_pong, } if channel in methods: method = methods[channel] method(client, message) def ping(self, client: Client): return {'method': 'ping'}