# -*- 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 from ccxt.base.types import Any, Bool, Int, Market, Order, OrderBook, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade from ccxt.async_support.base.ws.client import Client from typing import List class paradex(ccxt.async_support.paradex): def describe(self) -> Any: return self.deep_extend(super(paradex, self).describe(), { 'has': { 'ws': True, 'watchFundingRate': True, 'watchFundingRates': True, 'watchTicker': True, 'watchTickers': True, 'watchOrderBook': True, 'watchOrders': True, 'watchTrades': True, 'watchTradesForSymbols': False, 'watchBalance': False, 'watchOHLCV': False, }, 'urls': { 'logo': 'https://x.com/tradeparadex/photo', 'api': { 'ws': 'wss://ws.api.prod.paradex.trade/v1', }, 'test': { 'ws': 'wss://ws.api.testnet.paradex.trade/v1', }, 'www': 'https://www.paradex.trade/', 'doc': 'https://docs.api.testnet.paradex.trade/', 'fees': 'https://docs.paradex.trade/getting-started/trading-fees', 'referral': '', }, 'options': {}, 'streaming': {}, }) def request_id(self): requestId = self.sum(self.safe_integer(self.options, 'requestId', 0), 1) self.options['requestId'] = requestId return requestId async def authenticate(self, params={}): url = self.urls['api']['ws'] client = self.client(url) messageHash = 'authenticated' future = client.reusableFuture('authenticated') authenticated = self.safe_value(client.subscriptions, messageHash) if authenticated is None: token = await self.authenticateRest() request: dict = { 'jsonrpc': '2.0', 'id': self.request_id(), 'method': 'auth', 'params': { 'bearer': token, }, } self.watch(url, messageHash, self.deep_extend(request, params), messageHash) return await future def handle_authentication_message(self, client: Client, message): # # { # "jsonrpc": "2.0", # "id": 1, # "result": {"node_id": "73cf456f7cb78d59"} # } # result = self.safe_dict(message, 'result') if result is not None: # client.resolve(True, messageHash) future = self.safe_value(client.futures, 'authenticated') if future is not None: future.resolve(True) 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.paradex.trade/ws/web-socket-channels/trades/trades :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() messageHash = 'trades.' if symbol is not None: market = self.market(symbol) messageHash += market['id'] else: messageHash += 'ALL' url = self.urls['api']['ws'] request: dict = { 'jsonrpc': '2.0', 'method': 'subscribe', 'params': { 'channel': messageHash, }, } trades = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) def handle_trade(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "trades.ALL", # "data": { # "id": "1718179273230201709233240002", # "market": "kBONK-USD-PERP", # "side": "BUY", # "size": "34028", # "price": "0.028776", # "created_at": 1718179273230, # "trade_type": "FILL" # } # } # } # params = self.safe_dict(message, 'params', {}) data = self.safe_dict(params, 'data', {}) parsedTrade = self.parse_trade(data) symbol = parsedTrade['symbol'] messageHash = self.safe_string(params, 'channel') stored = self.safe_value(self.trades, symbol) if stored is None: stored = ArrayCache(self.safe_integer(self.options, 'tradesLimit', 1000)) self.trades[symbol] = stored stored.append(parsedTrade) client.resolve(stored, messageHash) return message async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://docs.paradex.trade/ws/web-socket-channels/order-book/order-book :param str symbol: unified symbol of the market to fetch the order book for :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() market = self.market(symbol) messageHash = 'order_book.' + market['id'] + '.snapshot@15@100ms' url = self.urls['api']['ws'] request: dict = { 'jsonrpc': '2.0', 'method': 'subscribe', 'params': { 'channel': messageHash, }, } orderbook = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash) return orderbook.limit() def handle_order_book(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "order_book.BTC-USD-PERP.snapshot@15@50ms", # "data": { # "seq_no": 14127815, # "market": "BTC-USD-PERP", # "last_updated_at": 1718267837265, # "update_type": "s", # "inserts": [ # { # "side": "BUY", # "price": "67629.7", # "size": "0.992" # }, # { # "side": "SELL", # "price": "69378.6", # "size": "3.137" # } # ], # "updates": [], # "deletes": [] # } # } # } # params = self.safe_dict(message, 'params', {}) data = self.safe_dict(params, 'data', {}) marketId = self.safe_string(data, 'market') market = self.safe_market(marketId) timestamp = self.safe_integer(data, 'last_updated_at') symbol = market['symbol'] if not (symbol in self.orderbooks): self.orderbooks[symbol] = self.order_book() orderbookData = { 'bids': [], 'asks': [], } inserts = self.safe_list(data, 'inserts') for i in range(0, len(inserts)): insert = self.safe_dict(inserts, i) side = self.safe_string(insert, 'side') price = self.safe_string(insert, 'price') size = self.safe_string(insert, 'size') if side == 'BUY': orderbookData['bids'].append([price, size]) else: orderbookData['asks'].append([price, size]) orderbook = self.orderbooks[symbol] snapshot = self.parse_order_book(orderbookData, symbol, timestamp, 'bids', 'asks') snapshot['nonce'] = self.safe_integer(data, 'seq_no') orderbook.reset(snapshot) messageHash = self.safe_string(params, 'channel') client.resolve(orderbook, messageHash) 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://docs.paradex.trade/ws/web-socket-channels/markets-summary/markets-summary :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() symbol = self.symbol(symbol) channel = 'markets_summary' url = self.urls['api']['ws'] request: dict = { 'jsonrpc': '2.0', 'method': 'subscribe', 'params': { 'channel': channel, }, } messageHash = channel + '.' + symbol return await self.watch(url, messageHash, self.deep_extend(request, params), 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://docs.paradex.trade/ws/web-socket-channels/markets-summary/markets-summary :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) channel = 'markets_summary' url = self.urls['api']['ws'] request: dict = { 'jsonrpc': '2.0', 'method': 'subscribe', 'params': { 'channel': channel, }, } messageHashes = [] if symbols is not None and isinstance(symbols, list): for i in range(0, len(symbols)): messageHash = channel + '.' + symbols[i] messageHashes.append(messageHash) else: messageHashes.append(channel) newTicker = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), messageHashes) if self.newUpdates: result: dict = {} result[newTicker['symbol']] = newTicker return result return self.filter_by_array(self.tickers, 'symbol', symbols) async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ watches information on multiple orders made by the user https://docs.paradex.trade/ws/web-socket-channels/orders/orders :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() messageHash = 'orders' channel = 'orders.' if symbol is not None: market = self.market(symbol) symbol = market['symbol'] channel += market['id'] messageHash += ':' + symbol else: channel += 'ALL' url = self.urls['api']['ws'] request: dict = { 'jsonrpc': '2.0', 'method': 'subscribe', 'params': { 'channel': channel, }, } orders = await self.watch(url, messageHash, self.deep_extend(request, params), channel) 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): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "orders.ALL", # "data": { # "account": "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x", # "avg_fill_price": "26000", # "client_id": "x1234", # "cancel_reason": "", # "created_at": 1681493746016, # "flags": ["REDUCE_ONLY"], # "id": "123456", # "instruction": "GTC", # "last_updated_at": 1681493746016, # "market": "BTC-USD-PERP", # "price": "26000", # "remaining_size": "0", # "side": "BUY", # "size": "0.05", # "status": "NEW", # "type": "LIMIT" # } # } # } # params = self.safe_dict(message, 'params', {}) data = self.safe_dict(params, 'data', {}) parsed = self.parse_order(data) symbol = self.safe_string(parsed, 'symbol') if self.orders is None: limit = self.safe_integer(self.options, 'ordersLimit', 1000) self.orders = ArrayCacheBySymbolById(limit) self.orders.append(parsed) messageHash = 'orders' client.resolve(self.orders, messageHash) if symbol is not None: symbolMessageHash = messageHash + ':' + symbol client.resolve(self.orders, symbolMessageHash) def handle_ticker(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "markets_summary", # "data": { # "symbol": "ORDI-USD-PERP", # "oracle_price": "49.80885481", # "mark_price": "49.80885481", # "last_traded_price": "62.038", # "bid": "49.822", # "ask": "58.167", # "volume_24h": "0", # "total_volume": "54542628.66054200416", # "created_at": 1718334307698, # "underlying_price": "47.93", # "open_interest": "6999.5", # "funding_rate": "0.03919997509811", # "price_change_rate_24h": "" # } # } # } # params = self.safe_dict(message, 'params', {}) data = self.safe_dict(params, 'data', {}) marketId = self.safe_string(data, 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] channel = self.safe_string(params, 'channel') messageHash = channel + '.' + symbol ticker = self.parse_ticker(data, market) self.tickers[symbol] = ticker client.resolve(ticker, channel) client.resolve(ticker, messageHash) return message async def watch_funding_rate(self, symbol: str, params={}) -> FundingRate: """ watch the current funding rate for a symbol https://docs.paradex.trade/ws/web-socket-channels/funding-data-market-symbol/funding-data-market-symbol :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `funding rate structure ` """ await self.load_markets() symbol = self.symbol(symbol) channel = 'funding_data' url = self.urls['api']['ws'] request: dict = { 'jsonrpc': '2.0', 'method': 'subscribe', 'params': { 'channel': channel, }, } messageHash = channel + '.' + symbol return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash) async def watch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates: """ watch the funding rate for multiple markets https://docs.paradex.trade/ws/web-socket-channels/markets-summary/markets-summary :param str[] [symbols]: a list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `funding rate structure ` """ await self.load_markets() symbols = self.market_symbols(symbols) channel = 'funding_data' url = self.urls['api']['ws'] request: dict = { 'jsonrpc': '2.0', 'method': 'subscribe', 'params': { 'channel': channel, }, } messageHashes = [] if symbols is not None: symbolsLength = len(symbols) if symbolsLength > 0: for i in range(0, len(symbols)): messageHash = channel + '.' + symbols[i] messageHashes.append(messageHash) else: messageHashes.append(channel) # if an empty array is passed, subscribe to all funding rates else: messageHashes.append(channel) newFundingRates = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), messageHashes) if self.newUpdates: result: dict = {} result[newFundingRates['symbol']] = newFundingRates return result return self.filter_by_array(self.fundingRates, 'symbol', symbols) def handle_funding_rate(self, client: Client, message): # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "funding_data", # "data": { # "market": "TRUMP-USD-PERP", # "funding_index": "-0.551694014226244835", # "funding_premium": "-0.000509914923994872836", # "funding_rate": "-0.00014969570582", # "funding_rate_8h": "-0.00014969", # "funding_period_hours": 8, # "created_at": 1771506636154 # } # } # } # params = self.safe_dict(message, 'params', {}) data = self.safe_dict(params, 'data', {}) fundingRate = self.parse_funding_rate_ws(data) symbol = fundingRate['symbol'] self.fundingRates[symbol] = fundingRate channel = self.safe_string(params, 'channel') messageHash = channel + '.' + symbol client.resolve(fundingRate, messageHash) def parse_funding_rate_ws(self, contract, market: Market = None) -> FundingRate: # # { # "market": "TRUMP-USD-PERP", # "funding_index": "-0.551694014226244835", # "funding_premium": "-0.000509914923994872836", # "funding_rate": "-0.00014969570582", # "funding_rate_8h": "-0.00014969", # "funding_period_hours": 8, # "created_at": 1771506636154 # } # marketId = self.safe_string(contract, 'market') symbol = self.safe_symbol(marketId, market) timestamp = self.safe_integer(contract, 'created_at') fundingPeriod = self.safe_string(contract, 'funding_period_hours') return { 'info': contract, 'symbol': symbol, 'markPrice': None, 'indexPrice': None, 'interestRate': self.parse_number('0'), 'estimatedSettlePrice': None, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'fundingRate': self.safe_number(contract, 'funding_rate'), 'fundingTimestamp': None, 'fundingDatetime': None, 'nextFundingRate': None, 'nextFundingTimestamp': None, 'nextFundingDatetime': None, 'previousFundingRate': None, 'previousFundingTimestamp': None, 'previousFundingDatetime': None, 'interval': fundingPeriod + 'h', } def handle_error_message(self, client: Client, message) -> Bool: # # { # "jsonrpc": "2.0", # "id": 0, # "error": { # "code": -32600, # "message": "invalid subscribe request", # "data": "invalid channel" # }, # "usIn": 1718179125962419, # "usDiff": 76, # "usOut": 1718179125962495 # } # error = self.safe_dict(message, 'error') if error is None: return True else: errorCode = self.safe_string(error, 'code') if errorCode is not None: feedback = self.id + ' ' + self.json(error) self.throw_exactly_matched_exception(self.exceptions['exact'], '-32600', feedback) messageString = self.safe_value(error, 'message') if messageString is not None: self.throw_broadly_matched_exception(self.exceptions['broad'], messageString, feedback) return False def handle_message(self, client: Client, message): if not self.handle_error_message(client, message): return # # auth response # # { # "jsonrpc": "2.0", # "id": 1, # "result": {"node_id": "73cf456f7cb78d59"} # } # # subscription message # # { # "jsonrpc": "2.0", # "method": "subscription", # "params": { # "channel": "trades.ALL", # "data": { # "id": "1718179273230201709233240002", # "market": "kBONK-USD-PERP", # "side": "BUY", # "size": "34028", # "price": "0.028776", # "created_at": 1718179273230, # "trade_type": "FILL" # } # } # } # result = self.safe_value(message, 'result') if result is not None: self.handle_authentication_message(client, message) return data = self.safe_dict(message, 'params') if data is not None: channel = self.safe_string(data, 'channel') parts = channel.split('.') name = self.safe_string(parts, 0) methods: dict = { 'trades': self.handle_trade, 'order_book': self.handle_order_book, 'markets_summary': self.handle_ticker, 'orders': self.handle_order, 'funding_data': self.handle_funding_rate, } method = self.safe_value(methods, name) if method is not None: method(client, message)