# -*- 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 ExchangeError from ccxt.base.errors import ArgumentsRequired from ccxt.base.errors import NotSupported class deribit(ccxt.async_support.deribit): def describe(self) -> Any: return self.deep_extend(super(deribit, self).describe(), { 'has': { 'ws': True, 'watchBalance': True, 'watchTicker': True, 'watchTickers': True, 'watchBidsAsks': True, 'watchTrades': True, 'watchTradesForSymbols': True, 'watchMyTrades': True, 'watchOrders': True, 'watchOrderBook': True, 'watchOrderBookForSymbols': True, 'watchOHLCV': True, 'watchOHLCVForSymbols': True, }, 'urls': { 'test': { 'ws': 'wss://test.deribit.com/ws/api/v2', }, 'api': { 'ws': 'wss://www.deribit.com/ws/api/v2', }, }, 'options': { 'ws': { 'timeframes': { '1m': '1', '3m': '3', '5m': '5', '15m': '15', '30m': '30', '1h': '60', '2h': '120', '4h': '180', '6h': '360', '12h': '720', '1d': '1D', }, # watchTrades replacement 'watchTradesForSymbols': { 'interval': '100ms', # 100ms, agg2, raw }, # watchOrderBook replacement 'watchOrderBookForSymbols': { 'interval': '100ms', # 100ms, agg2, raw 'useDepthEndpoint': False, # if True, it will use the {books.group.depth.interval} endpoint instead of the {books.interval} endpoint 'depth': '20', # 1, 10, 20 'group': 'none', # none, 1, 2, 5, 10, 25, 100, 250 }, }, 'currencies': ['BTC', 'ETH', 'SOL', 'USDC'], }, 'streaming': { }, 'exceptions': { }, }) def request_id(self): requestId = self.sum(self.safe_integer(self.options, 'requestId', 0), 1) self.options['requestId'] = requestId return requestId async def watch_balance(self, params={}) -> Balances: """ https://docs.deribit.com/#user-portfolio-currency watch balance and get the amount of funds available for trading or funds locked in orders :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `balance structure ` """ await self.authenticate(params) messageHash = 'balance' url = self.urls['api']['ws'] currencies = self.safe_value(self.options, 'currencies', []) channels = [] for i in range(0, len(currencies)): currencyCode = currencies[i] channels.append('user.portfolio.' + currencyCode) subscribe: dict = { 'jsonrpc': '2.0', 'method': 'private/subscribe', 'params': { 'channels': channels, }, 'id': self.request_id(), } request = self.deep_extend(subscribe, params) return await self.watch(url, messageHash, request, messageHash, request) def handle_balance(self, client: Client, message): # # subscription # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "user.portfolio.btc", # "data": { # "total_pl": 0, # "session_upl": 0, # "session_rpl": 0, # "projected_maintenance_margin": 0, # "projected_initial_margin": 0, # "projected_delta_total": 0, # "portfolio_margining_enabled": False, # "options_vega": 0, # "options_value": 0, # "options_theta": 0, # "options_session_upl": 0, # "options_session_rpl": 0, # "options_pl": 0, # "options_gamma": 0, # "options_delta": 0, # "margin_balance": 0.0015, # "maintenance_margin": 0, # "initial_margin": 0, # "futures_session_upl": 0, # "futures_session_rpl": 0, # "futures_pl": 0, # "fee_balance": 0, # "estimated_liquidation_ratio_map": {}, # "estimated_liquidation_ratio": 0, # "equity": 0.0015, # "delta_total_map": {}, # "delta_total": 0, # "currency": "BTC", # "balance": 0.0015, # "available_withdrawal_funds": 0.0015, # "available_funds": 0.0015 # } # } # } # params = self.safe_value(message, 'params', {}) data = self.safe_value(params, 'data', {}) self.balance['info'] = data currencyId = self.safe_string(data, 'currency') currencyCode = self.safe_currency_code(currencyId) balance = self.parse_balance(data) self.balance[currencyCode] = balance messageHash = 'balance' client.resolve(self.balance, messageHash) async def watch_ticker(self, symbol: str, params={}) -> Ticker: """ https://docs.deribit.com/#ticker-instrument_name-interval watches a price ticker, a statistical calculation with the information for a specific market. :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.interval]: specify aggregation and frequency of notifications. Possible values: 100ms, raw :returns dict: a `ticker structure ` """ await self.load_markets() market = self.market(symbol) url = self.urls['api']['ws'] interval = self.safe_string(params, 'interval', '100ms') params = self.omit(params, 'interval') await self.load_markets() if interval == 'raw': await self.authenticate() channel = 'ticker.' + market['id'] + '.' + interval message: dict = { 'jsonrpc': '2.0', 'method': 'public/subscribe', 'params': { 'channels': ['ticker.' + market['id'] + '.' + interval], }, 'id': self.request_id(), } request = self.deep_extend(message, params) return await self.watch(url, channel, request, channel, request) async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ https://docs.deribit.com/#ticker-instrument_name-interval watches 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 :param str [params.interval]: specify aggregation and frequency of notifications. Possible values: 100ms, raw :returns dict: a `ticker structure ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, False) url = self.urls['api']['ws'] interval = self.safe_string(params, 'interval', '100ms') params = self.omit(params, 'interval') await self.load_markets() if interval == 'raw': await self.authenticate() channels = [] for i in range(0, len(symbols)): market = self.market(symbols[i]) channels.append('ticker.' + market['id'] + '.' + interval) message: dict = { 'jsonrpc': '2.0', 'method': 'public/subscribe', 'params': { 'channels': channels, }, 'id': self.request_id(), } request = self.deep_extend(message, params) newTickers = await self.watch_multiple(url, channels, request, channels, request) if self.newUpdates: tickers: dict = {} tickers[newTickers['symbol']] = newTickers return tickers return self.filter_by_array(self.tickers, 'symbol', symbols) def handle_ticker(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "ticker.BTC_USDC-PERPETUAL.raw", # "data": { # "timestamp": 1655393725040, # "stats": [Object], # "state": "open", # "settlement_price": 21729.5891, # "open_interest": 164.501, # "min_price": 20792.9376, # "max_price": 21426.225, # "mark_price": 21109.555, # "last_price": 21132, # "instrument_name": "BTC_USDC-PERPETUAL", # "index_price": 21122.3937, # "funding_8h": -0.00022427, # "estimated_delivery_price": 21122.3937, # "current_funding": -0.00010782, # "best_bid_price": 21106, # "best_bid_amount": 1.143, # "best_ask_price": 21113, # "best_ask_amount": 0.327 # } # } # } # params = self.safe_value(message, 'params', {}) data = self.safe_value(params, 'data', {}) marketId = self.safe_string(data, 'instrument_name') symbol = self.safe_symbol(marketId) ticker = self.parse_ticker(data) messageHash = self.safe_string(params, 'channel') self.tickers[symbol] = ticker client.resolve(ticker, messageHash) async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers: """ https://docs.deribit.com/#quote-instrument_name 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, False) url = self.urls['api']['ws'] channels = [] for i in range(0, len(symbols)): market = self.market(symbols[i]) channels.append('quote.' + market['id']) message: dict = { 'jsonrpc': '2.0', 'method': 'public/subscribe', 'params': { 'channels': channels, }, 'id': self.request_id(), } request = self.deep_extend(message, params) newTickers = await self.watch_multiple(url, channels, request, channels, request) if self.newUpdates: tickers: dict = {} tickers[newTickers['symbol']] = newTickers return tickers return self.filter_by_array(self.bidsasks, 'symbol', symbols) def handle_bid_ask(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "quote.BTC_USDT", # "data": { # "best_bid_amount": 0.026, # "best_ask_amount": 0.026, # "best_bid_price": 63908, # "best_ask_price": 63940, # "instrument_name": "BTC_USDT", # "timestamp": 1727765131750 # } # } # } # params = self.safe_dict(message, 'params', {}) data = self.safe_dict(params, 'data', {}) ticker = self.parse_ws_bid_ask(data) symbol = ticker['symbol'] self.bidsasks[symbol] = ticker messageHash = self.safe_string(params, 'channel') client.resolve(ticker, messageHash) def parse_ws_bid_ask(self, ticker, market=None): marketId = self.safe_string(ticker, 'instrument_name') market = self.safe_market(marketId, market) symbol = self.safe_string(market, 'symbol') timestamp = self.safe_integer(ticker, 'timestamp') return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'ask': self.safe_string(ticker, 'best_ask_price'), 'askVolume': self.safe_string(ticker, 'best_ask_amount'), 'bid': self.safe_string(ticker, 'best_bid_price'), 'bidVolume': self.safe_string(ticker, 'best_bid_amount'), 'info': ticker, }, market) async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get the list of most recent trades for a particular symbol https://docs.deribit.com/#trades-instrument_name-interval :param str symbol: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.interval]: specify aggregation and frequency of notifications. Possible values: 100ms, raw :returns dict[]: a list of `trade structures ` """ params['callerMethodName'] = 'watchTrades' return await self.watch_trades_for_symbols([symbol], since, limit, params) async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get the list of most recent trades for a list of symbols https://docs.deribit.com/#trades-instrument_name-interval :param str[] symbols: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ interval = None interval, params = self.handle_option_and_params(params, 'watchTradesForSymbols', 'interval', '100ms') if interval == 'raw': await self.authenticate() trades = await self.watch_multiple_wrapper('trades', interval, symbols, params) if self.newUpdates: first = self.safe_dict(trades, 0) tradeSymbol = self.safe_string(first, 'symbol') limit = trades.getLimit(tradeSymbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) def handle_trades(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "trades.BTC_USDC-PERPETUAL.100ms", # "data": [{ # "trade_seq": 501899, # "trade_id": "USDC-2436803", # "timestamp": 1655397355998, # "tick_direction": 2, # "price": 21026, # "mark_price": 21019.9719, # "instrument_name": "BTC_USDC-PERPETUAL", # "index_price": 21031.7847, # "direction": "buy", # "amount": 0.049 # }] # } # } # params = self.safe_dict(message, 'params', {}) channel = self.safe_string(params, 'channel', '') parts = channel.split('.') marketId = self.safe_string(parts, 1) interval = self.safe_string(parts, 2) symbol = self.safe_symbol(marketId) market = self.safe_market(marketId) trades = self.safe_list(params, 'data', []) if self.safe_value(self.trades, symbol) is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) self.trades[symbol] = ArrayCache(limit) stored = self.trades[symbol] for i in range(0, len(trades)): trade = trades[i] parsed = self.parse_trade(trade, market) stored.append(parsed) self.trades[symbol] = stored messageHash = 'trades|' + symbol + '|' + interval client.resolve(self.trades[symbol], messageHash) async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get the list of trades associated with the user https://docs.deribit.com/#user-trades-instrument_name-interval :param str symbol: unified symbol of the market to fetch trades for. Use 'any' to watch all trades :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.interval]: specify aggregation and frequency of notifications. Possible values: 100ms, raw :returns dict[]: a list of `trade structures ` """ await self.authenticate(params) if symbol is not None: await self.load_markets() symbol = self.symbol(symbol) url = self.urls['api']['ws'] interval = self.safe_string(params, 'interval', 'raw') params = self.omit(params, 'interval') channel = 'user.trades.any.any.' + interval message: dict = { 'jsonrpc': '2.0', 'method': 'private/subscribe', 'params': { 'channels': [channel], }, 'id': self.request_id(), } request = self.deep_extend(message, params) trades = await self.watch(url, channel, request, channel, request) return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True) def handle_my_trades(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "user.trades.any.any.raw", # "data": [{ # "trade_seq": 149546319, # "trade_id": "219381310", # "timestamp": 1655421193564, # "tick_direction": 0, # "state": "filled", # "self_trade": False, # "reduce_only": False, # "profit_loss": 0, # "price": 20236.5, # "post_only": False, # "order_type": "market", # "order_id": "46108941243", # "matching_id": null, # "mark_price": 20233.96, # "liquidity": "T", # "instrument_name": "BTC-PERPETUAL", # "index_price": 20253.31, # "fee_currency": "BTC", # "fee": 2.5e-7, # "direction": "buy", # "amount": 10 # }] # } # } # params = self.safe_value(message, 'params', {}) channel = self.safe_string(params, 'channel', '') trades = self.safe_value(params, 'data', []) cachedTrades = self.myTrades if cachedTrades is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) cachedTrades = ArrayCacheBySymbolById(limit) parsed = self.parse_trades(trades) marketIds: dict = {} for i in range(0, len(parsed)): trade = parsed[i] cachedTrades.append(trade) symbol = trade['symbol'] marketIds[symbol] = True client.resolve(cachedTrades, channel) async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ https://docs.deribit.com/#book-instrument_name-group-depth-interval watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data :param str symbol: unified symbol of the market to fetch the order book for :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.interval]: Frequency of notifications. Events will be aggregated over self interval. Possible values: 100ms, raw :returns dict: A dictionary of `order book structures ` indexed by market symbols """ params['callerMethodName'] = 'watchOrderBook' return await self.watch_order_book_for_symbols([symbol], limit, params) async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook: """ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://docs.deribit.com/#book-instrument_name-group-depth-interval :param str[] symbols: unified array of symbols :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ interval = None interval, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'interval', '100ms') if interval == 'raw': await self.authenticate() descriptor = '' useDepthEndpoint = None # for more info, see comment in .options useDepthEndpoint, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'useDepthEndpoint', False) if useDepthEndpoint: depth = None depth, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'depth', '20') group = None group, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'group', 'none') descriptor = group + '.' + depth + '.' + interval else: descriptor = interval orderbook = await self.watch_multiple_wrapper('book', descriptor, symbols, params) return orderbook.limit() def handle_order_book(self, client: Client, message): # # snapshot # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "book.BTC_USDC-PERPETUAL.raw", # "data": { # "type": "snapshot", # "timestamp": 1655395057025, # "instrument_name": "BTC_USDC-PERPETUAL", # "change_id": 1550694837, # "bids": [ # ["new", 20987, 0.487], # ["new", 20986, 0.238], # ], # "asks": [ # ["new", 20999, 0.092], # ["new", 21000, 1.238], # ] # } # } # } # # change # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "book.BTC_USDC-PERPETUAL.raw", # "data": { # "type": "change", # "timestamp": 1655395168086, # "prev_change_id": 1550724481, # "instrument_name": "BTC_USDC-PERPETUAL", # "change_id": 1550724483, # "bids": [ # ["new", 20977, 0.109], # ["delete", 20975, 0] # ], # "asks": [] # } # } # } # params = self.safe_value(message, 'params', {}) data = self.safe_value(params, 'data', {}) channel = self.safe_string(params, 'channel') parts = channel.split('.') descriptor = '' partsLength = len(parts) isDetailed = partsLength == 5 if isDetailed: group = self.safe_string(parts, 2) depth = self.safe_string(parts, 3) interval = self.safe_string(parts, 4) descriptor = group + '.' + depth + '.' + interval else: interval = self.safe_string(parts, 2) descriptor = interval marketId = self.safe_string(data, 'instrument_name') symbol = self.safe_symbol(marketId) timestamp = self.safe_integer(data, 'timestamp') if not (symbol in self.orderbooks): self.orderbooks[symbol] = self.counted_order_book() storedOrderBook = self.orderbooks[symbol] asks = self.safe_list(data, 'asks', []) bids = self.safe_list(data, 'bids', []) self.handle_deltas(storedOrderBook['asks'], asks) self.handle_deltas(storedOrderBook['bids'], bids) storedOrderBook['nonce'] = timestamp storedOrderBook['timestamp'] = timestamp storedOrderBook['datetime'] = self.iso8601(timestamp) storedOrderBook['symbol'] = symbol self.orderbooks[symbol] = storedOrderBook messageHash = 'book|' + symbol + '|' + descriptor client.resolve(storedOrderBook, messageHash) def clean_order_book(self, data): bids = self.safe_list(data, 'bids', []) asks = self.safe_list(data, 'asks', []) cleanedBids = [] for i in range(0, len(bids)): cleanedBids.append([bids[i][1], bids[i][2]]) cleanedAsks = [] for i in range(0, len(asks)): cleanedAsks.append([asks[i][1], asks[i][2]]) data['bids'] = cleanedBids data['asks'] = cleanedAsks return data def handle_delta(self, bookside, delta): price = delta[1] amount = delta[2] if delta[0] == 'new' or delta[0] == 'change': bookside.storeArray([price, amount, 1]) elif delta[0] == 'delete': bookside.storeArray([price, amount, 0]) def handle_deltas(self, bookside, deltas): for i in range(0, len(deltas)): self.handle_delta(bookside, deltas[i]) async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ https://docs.deribit.com/#user-orders-instrument_name-raw watches information on multiple orders made by the user :param str symbol: unified market symbol of the market orders were made in :param int [since]: the earliest time in ms to fetch orders for :param int [limit]: the maximum number of order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `order structures ` """ await self.load_markets() await self.authenticate(params) if symbol is not None: symbol = self.symbol(symbol) url = self.urls['api']['ws'] currency = self.safe_string(params, 'currency', 'any') interval = self.safe_string(params, 'interval', 'raw') kind = self.safe_string(params, 'kind', 'any') params = self.omit(params, 'interval', 'currency', 'kind') channel = 'user.orders.' + kind + '.' + currency + '.' + interval message: dict = { 'jsonrpc': '2.0', 'method': 'private/subscribe', 'params': { 'channels': [channel], }, 'id': self.request_id(), } request = self.deep_extend(message, params) orders = await self.watch(url, channel, request, channel, request) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True) def handle_orders(self, client: Client, message): # Does not return a snapshot of current orders # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "user.orders.any.any.raw", # "data": { # "web": True, # "time_in_force": "good_til_cancelled", # "replaced": False, # "reduce_only": False, # "profit_loss": 0, # "price": 50000, # "post_only": False, # "order_type": "limit", # "order_state": "open", # "order_id": "46094375191", # "max_show": 10, # "last_update_timestamp": 1655401625037, # "label": '', # "is_liquidation": False, # "instrument_name": "BTC-PERPETUAL", # "filled_amount": 0, # "direction": "sell", # "creation_timestamp": 1655401625037, # "commission": 0, # "average_price": 0, # "api": False, # "amount": 10 # } # } # } # if self.orders is None: limit = self.safe_integer(self.options, 'ordersLimit', 1000) self.orders = ArrayCacheBySymbolById(limit) params = self.safe_value(message, 'params', {}) channel = self.safe_string(params, 'channel', '') data = self.safe_value(params, 'data', {}) orders = [] if isinstance(data, list): orders = self.parse_orders(data) else: order = self.parse_order(data) orders = [order] cachedOrders = self.orders for i in range(0, len(orders)): cachedOrders.append(orders[i]) client.resolve(self.orders, channel) async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ https://docs.deribit.com/#chart-trades-instrument_name-resolution watches historical candlestick data containing the open, high, low, and close price, and the volume of a market :param str symbol: unified symbol of the market to fetch OHLCV data for :param str timeframe: the length of time each candle represents :param int [since]: timestamp in ms of the earliest candle to fetch :param int [limit]: the maximum amount of candles to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() symbol = self.symbol(symbol) ohlcvs = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params) return ohlcvs[symbol][timeframe] async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}): """ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://docs.deribit.com/#chart-trades-instrument_name-resolution :param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']] :param int [since]: timestamp in ms of the earliest candle to fetch :param int [limit]: the maximum amount of candles to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns int[][]: A list of candles ordered, open, high, low, close, volume """ symbolsLength = len(symbolsAndTimeframes) if symbolsLength == 0 or not isinstance(symbolsAndTimeframes[0], list): raise ArgumentsRequired(self.id + " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]") symbol, timeframe, candles = await self.watch_multiple_wrapper('chart.trades', None, symbolsAndTimeframes, params) if self.newUpdates: limit = candles.getLimit(symbol, limit) filtered = self.filter_by_since_limit(candles, since, limit, 0, True) return self.create_ohlcv_object(symbol, timeframe, filtered) def handle_ohlcv(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "chart.trades.BTC_USDC-PERPETUAL.1", # "data": { # "volume": 0, # "tick": 1655403420000, # "open": 20951, # "low": 20951, # "high": 20951, # "cost": 0, # "close": 20951 # } # } # } # params = self.safe_dict(message, 'params', {}) channel = self.safe_string(params, 'channel', '') parts = channel.split('.') marketId = self.safe_string(parts, 2) rawTimeframe = self.safe_string(parts, 3) market = self.safe_market(marketId) symbol = market['symbol'] wsOptions = self.safe_dict(self.options, 'ws', {}) timeframes = self.safe_dict(wsOptions, 'timeframes', {}) unifiedTimeframe = self.find_timeframe(rawTimeframe, timeframes) self.ohlcvs[symbol] = self.safe_dict(self.ohlcvs, symbol, {}) if self.safe_value(self.ohlcvs[symbol], unifiedTimeframe) is None: limit = self.safe_integer(self.options, 'OHLCVLimit', 1000) self.ohlcvs[symbol][unifiedTimeframe] = ArrayCacheByTimestamp(limit) stored = self.ohlcvs[symbol][unifiedTimeframe] ohlcv = self.safe_dict(params, 'data', {}) # data contains a single OHLCV candle parsed = self.parse_ws_ohlcv(ohlcv, market) stored.append(parsed) self.ohlcvs[symbol][unifiedTimeframe] = stored resolveData = [symbol, unifiedTimeframe, stored] messageHash = 'chart.trades|' + symbol + '|' + rawTimeframe client.resolve(resolveData, messageHash) def parse_ws_ohlcv(self, ohlcv, market=None) -> list: # # { # "c": "28909.0", # "o": "28915.4", # "h": "28915.4", # "l": "28896.1", # "v": "27.6919", # "T": 1696687499999, # "t": 1696687440000 # } # return [ self.safe_integer(ohlcv, 'tick'), self.safe_number(ohlcv, 'open'), self.safe_number(ohlcv, 'high'), self.safe_number(ohlcv, 'low'), self.safe_number(ohlcv, 'close'), self.safe_number(ohlcv, 'volume'), ] async def watch_multiple_wrapper(self, channelName: str, channelDescriptor: Str, symbolsArray=None, params={}): await self.load_markets() url = self.urls['api']['ws'] rawSubscriptions = [] messageHashes = [] isOHLCV = (channelName == 'chart.trades') symbols = self.get_list_from_object_values(symbolsArray, 0) if isOHLCV else symbolsArray self.market_symbols(symbols, None, False) for i in range(0, len(symbolsArray)): current = symbolsArray[i] market = None if isOHLCV: market = self.market(current[0]) unifiedTf = current[1] rawTf = self.safe_string(self.timeframes, unifiedTf, unifiedTf) channelDescriptor = rawTf else: market = self.market(current) message = channelName + '.' + market['id'] + '.' + channelDescriptor rawSubscriptions.append(message) messageHashes.append(channelName + '|' + market['symbol'] + '|' + channelDescriptor) request: dict = { 'jsonrpc': '2.0', 'method': 'public/subscribe', 'params': { 'channels': rawSubscriptions, }, 'id': self.request_id(), } extendedRequest = self.deep_extend(request, params) maxMessageByteLimit = 32768 - 1 # 'Message Too Big: limit 32768B' jsonedText = self.json(extendedRequest) if len(jsonedText) >= maxMessageByteLimit: raise ExchangeError(self.id + ' requested subscription length over limit, try to reduce symbols amount') return await self.watch_multiple(url, messageHashes, extendedRequest, rawSubscriptions) def handle_message(self, client: Client, message): # # error # { # "jsonrpc": "2.0", # "id": 1, # "error": { # "message": "Invalid params", # "data": { # "reason": "invalid format", # "param": "nonce" # }, # "code": -32602 # }, # "usIn": "1655391709417993", # "usOut": "1655391709418049", # "usDiff": 56, # "testnet": False # } # # subscribe # { # "jsonrpc": "2.0", # "id": 2, # "result": ["ticker.BTC_USDC-PERPETUAL.raw"], # "usIn": "1655393625889396", # "usOut": "1655393625889518", # "usDiff": 122, # "testnet": False # } # # notification # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "ticker.BTC_USDC-PERPETUAL.raw", # "data": { # "timestamp": 1655393724752, # "stats": [Object], # "state": "open", # "settlement_price": 21729.5891, # "open_interest": 164.501, # "min_price": 20792.9001, # "max_price": 21426.1864, # "mark_price": 21109.4757, # "last_price": 21132, # "instrument_name": "BTC_USDC-PERPETUAL", # "index_price": 21122.3937, # "funding_8h": -0.00022427, # "estimated_delivery_price": 21122.3937, # "current_funding": -0.00011158, # "best_bid_price": 21106, # "best_bid_amount": 1.143, # "best_ask_price": 21113, # "best_ask_amount": 0.402 # } # } # } # error = self.safe_value(message, 'error') if error is not None: raise ExchangeError(self.id + ' ' + self.json(error)) params = self.safe_value(message, 'params') channel = self.safe_string(params, 'channel') if channel is not None: parts = channel.split('.') channelId = self.safe_string(parts, 0) userHandlers: dict = { 'trades': self.handle_my_trades, 'portfolio': self.handle_balance, 'orders': self.handle_orders, } handlers: dict = { 'ticker': self.handle_ticker, 'quote': self.handle_bid_ask, 'book': self.handle_order_book, 'trades': self.handle_trades, 'chart': self.handle_ohlcv, 'user': self.safe_value(userHandlers, self.safe_string(parts, 1)), } handler = self.safe_value(handlers, channelId) if handler is not None: handler(client, message) return raise NotSupported(self.id + ' no handler found for self message ' + self.json(message)) result = self.safe_value(message, 'result', {}) accessToken = self.safe_string(result, 'access_token') if accessToken is not None: self.handle_authentication_message(client, message) def handle_authentication_message(self, client: Client, message): # # { # "jsonrpc": "2.0", # "id": 1, # "result": { # "token_type": "bearer", # "scope": "account:read_write block_trade:read_write connection custody:read_write mainaccount name:ccxt trade:read_write wallet:read_write", # "refresh_token": "1686927372328.1EzFBRmt.logRQWXkPA1oE_Tk0gRsls9Hau7YN6a321XUBnxvR4x6cryhbkKcniUJU-czA8_zKXrqQGpQmfoDwhLIjIsWCvRuu6otbg-LKWlrtTX1GQqLcPaTTHAdZGTMV-HM8HiS03QBd9MIXWRfF53sKj2hdR9nZPZ6MH1XrkpAZPB_peuEEB9wlcc3elzWEZFtCmiy1fnQ8TPHwAJMt3nuUmEcMLt_-F554qrsg_-I66D9xMiifJj4dBemdPfV_PkGPRIwIoKlxDjyv2-xfCw-4eKyo6Hu1m2h6gT1DPOTxSXcBgfBQjpi-_uY3iAIj7U6xjC46PHthEdquhEuCTZl7UfCRZSAWwZA", # "expires_in": 31536000, # "access_token": "1686923272328.1CkwEx-u.qHradpIulmuoeboKMEi8PkQ1_4DF8yFE2zywBTtkD32sruVC53b1HwL5OWRuh2nYAndXff4xuXIMRkkEfMAFCeq24prihxxinoS8DDVkKBxedGx4CUPJFeXjmh7wuRGqQOLg1plXOpbF3fwF2KPEkAuETwcpcVY6K9HUVjutNRfxFe2TR7CvuS9x8TATvoPeu7H1ezYl-LkKSaRifdTXuwituXgp4oDbPRyQLniEBWuYF9rY7qbABxuOJlXI1VZ63u7Bh0mGWei-KeVeqHGNpy6OgrFRPXPxa9_U7vaxCyHW3zZ9959TQ1QUMLWtUX-NLBEv3BT5eCieW9HORYIOKfsgkpd3" # }, # "usIn": "1655391872327712", # "usOut": "1655391872328515", # "usDiff": 803, # "testnet": False # } # messageHash = 'authenticated' client.resolve(message, messageHash) return message async def authenticate(self, params={}): url = self.urls['api']['ws'] client = self.client(url) time = self.milliseconds() timeString = self.number_to_string(time) nonce = timeString messageHash = 'authenticated' future = self.safe_value(client.subscriptions, messageHash) if future is None: self.check_required_credentials() requestId = self.request_id() lineBreak = "\n" # eslint-disable-line quotes signature = self.hmac(self.encode(timeString + lineBreak + nonce + lineBreak), self.encode(self.secret), hashlib.sha256) request: dict = { 'jsonrpc': '2.0', 'id': requestId, 'method': 'public/auth', 'params': { 'grant_type': 'client_signature', 'client_id': self.apiKey, 'timestamp': time, 'signature': signature, 'nonce': nonce, 'data': '', }, } future = await self.watch(url, messageHash, self.extend(request, params), messageHash) client.subscriptions[messageHash] = future return future