# -*- 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, Order, OrderBook, Str, Ticker, Trade from ccxt.async_support.base.ws.client import Client from typing import List from ccxt.base.errors import ExchangeError from ccxt.base.errors import AuthenticationError from ccxt.base.errors import UnsubscribeError class derive(ccxt.async_support.derive): def describe(self) -> Any: return self.deep_extend(super(derive, self).describe(), { 'has': { 'ws': False, 'watchBalance': False, 'watchMyTrades': True, 'watchOHLCV': False, 'watchOrderBook': True, 'watchOrders': True, 'watchTicker': True, 'watchTickers': False, 'watchBidsAsks': False, 'watchTrades': True, 'watchTradesForSymbols': False, 'watchPositions': False, }, 'urls': { 'api': { 'ws': 'wss://api.lyra.finance/ws', }, 'test': { 'ws': 'wss://api-demo.lyra.finance/ws', }, }, 'options': { 'tradesLimit': 1000, 'ordersLimit': 1000, 'requestId': {}, }, 'streaming': { 'keepAlive': 9000, }, 'exceptions': { 'ws': { 'exact': {}, }, }, }) def request_id(self, url): options = self.safe_value(self.options, 'requestId', {}) previousValue = self.safe_integer(options, url, 0) newValue = self.sum(previousValue, 1) self.options['requestId'][url] = newValue return newValue async def watch_public(self, messageHash, message, subscription): url = self.urls['api']['ws'] requestId = self.request_id(url) request = self.extend(message, { 'id': requestId, }) subscription = self.extend(subscription, { 'id': requestId, 'method': 'subscribe', }) return await self.watch(url, messageHash, request, messageHash, subscription) async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ https://docs.derive.xyz/reference/orderbook-instrument_name-group-depth 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 :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() if limit is None: limit = 10 market = self.market(symbol) topic = 'orderbook.' + market['id'] + '.10.' + self.number_to_string(limit) request: dict = { 'method': 'subscribe', 'params': { 'channels': [ topic, ], }, } subscription: dict = { 'name': topic, 'symbol': symbol, 'limit': limit, 'params': params, } orderbook = await self.watch_public(topic, request, subscription) return orderbook.limit() def handle_order_book(self, client: Client, message): # # { # method: 'subscription', # params: { # channel: 'orderbook.BTC-PERP.10.1', # data: { # timestamp: 1738331231506, # instrument_name: 'BTC-PERP', # publish_id: 628419, # bids: [['104669', '40']], # asks: [['104736', '40']] # } # } # } # params = self.safe_dict(message, 'params') data = self.safe_dict(params, 'data') marketId = self.safe_string(data, 'instrument_name') market = self.safe_market(marketId) symbol = market['symbol'] topic = self.safe_string(params, 'channel') if not (symbol in self.orderbooks): defaultLimit = self.safe_integer(self.options, 'watchOrderBookLimit', 1000) subscription = client.subscriptions[topic] limit = self.safe_integer(subscription, 'limit', defaultLimit) self.orderbooks[symbol] = self.order_book({}, limit) orderbook = self.orderbooks[symbol] timestamp = self.safe_integer(data, 'timestamp') snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks') orderbook.reset(snapshot) client.resolve(orderbook, topic) async def watch_ticker(self, symbol: str, params={}) -> Ticker: """ https://docs.derive.xyz/reference/ticker-instrument_name-interval watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ await self.load_markets() market = self.market(symbol) topic = 'ticker.' + market['id'] + '.100' request: dict = { 'method': 'subscribe', 'params': { 'channels': [ topic, ], }, } subscription: dict = { 'name': topic, 'symbol': symbol, 'params': params, } return await self.watch_public(topic, request, subscription) def handle_ticker(self, client: Client, message): # # { # method: 'subscription', # params: { # channel: 'ticker.BTC-PERP.100', # data: { # timestamp: 1738485104439, # instrument_ticker: { # instrument_type: 'perp', # instrument_name: 'BTC-PERP', # scheduled_activation: 1701840228, # scheduled_deactivation: '9223372036854775807', # is_active: True, # tick_size: '0.1', # minimum_amount: '0.01', # maximum_amount: '10000', # amount_step: '0.001', # mark_price_fee_rate_cap: '0', # maker_fee_rate: '0.0001', # taker_fee_rate: '0.0003', # base_fee: '0.1', # base_currency: 'BTC', # quote_currency: 'USD', # option_details: null, # perp_details: { # index: 'BTC-USD', # max_rate_per_hour: '0.004', # min_rate_per_hour: '-0.004', # static_interest_rate: '0.0000125', # aggregate_funding: '10581.779418721074588722', # funding_rate: '0.000024792239208858' # }, # erc20_details: null, # base_asset_address: '0xDBa83C0C654DB1cd914FA2710bA743e925B53086', # base_asset_sub_id: '0', # pro_rata_fraction: '0', # fifo_min_allocation: '0', # pro_rata_amount_step: '0.1', # best_ask_amount: '0.131', # best_ask_price: '99898.6', # best_bid_amount: '0.056', # best_bid_price: '99889.1', # five_percent_bid_depth: '11.817', # five_percent_ask_depth: '9.116', # option_pricing: null, # index_price: '99883.8', # mark_price: '99897.52408421244763303548098', # stats: { # contract_volume: '92.395', # num_trades: '2924', # open_interest: '33.743468027373780786', # high: '102320.4', # low: '99064.3', # percent_change: '-0.021356', # usd_change: '-2178' # }, # timestamp: 1738485165881, # min_price: '97939.1', # max_price: '101895.2' # } # } # } # } # params = self.safe_dict(message, 'params') rawData = self.safe_dict(params, 'data') data = self.safe_dict(rawData, 'instrument_ticker') topic = self.safe_value(params, 'channel') ticker = self.parse_ticker(data) self.tickers[ticker['symbol']] = ticker client.resolve(ticker, topic) return message async def un_watch_order_book(self, symbol: str, params={}) -> Any: """ unsubscribe from the orderbook channel :param str symbol: unified symbol of the market to fetch the order book for :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.limit]: orderbook limit, default is None :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() limit = self.safe_integer(params, 'limit') if limit is None: limit = 10 market = self.market(symbol) topic = 'orderbook.' + market['id'] + '.10.' + self.number_to_string(limit) messageHash = 'unwatch' + topic request: dict = { 'method': 'unsubscribe', 'params': { 'channels': [ topic, ], }, } subscription: dict = { 'name': topic, } return await self.un_watch_public(messageHash, request, subscription) async def un_watch_trades(self, symbol: str, params={}) -> Any: """ unsubscribe from the trades channel :param str symbol: unified symbol of the market to unwatch the trades for :param dict [params]: extra parameters specific to the exchange API endpoint :returns any: status of the unwatch request """ await self.load_markets() market = self.market(symbol) topic = 'trades.' + market['id'] messageHah = 'unwatch' + topic request: dict = { 'method': 'unsubscribe', 'params': { 'channels': [ topic, ], }, } subscription: dict = { 'name': topic, } return await self.un_watch_public(messageHah, request, subscription) async def un_watch_public(self, messageHash, message, subscription): url = self.urls['api']['ws'] requestId = self.request_id(url) request = self.extend(message, { 'id': requestId, }) subscription = self.extend(subscription, { 'id': requestId, 'method': 'unsubscribe', }) return await self.watch(url, messageHash, request, messageHash, subscription) def handle_order_book_un_subscription(self, client: Client, topic): parsedTopic = topic.split('.') marketId = self.safe_string(parsedTopic, 1) market = self.safe_market(marketId) symbol = market['symbol'] if symbol in self.orderbooks: del self.orderbooks[symbol] if topic in client.subscriptions: del client.subscriptions[topic] error = UnsubscribeError(self.id + ' orderbook ' + symbol) client.reject(error, topic) client.resolve(error, 'unwatch' + topic) def handle_trades_un_subscription(self, client: Client, topic): parsedTopic = topic.split('.') marketId = self.safe_string(parsedTopic, 1) market = self.safe_market(marketId) symbol = market['symbol'] if symbol in self.orderbooks: del self.trades[symbol] if topic in client.subscriptions: del client.subscriptions[topic] error = UnsubscribeError(self.id + ' trades ' + symbol) client.reject(error, topic) client.resolve(error, 'unwatch' + topic) def handle_un_subscribe(self, client: Client, message): # # { # id: 1, # result: { # status: {'orderbook.BTC-PERP.10.10': 'ok'}, # remaining_subscriptions: [] # } # } # result = self.safe_dict(message, 'result') status = self.safe_dict(result, 'status') if status is not None: topics = list(status.keys()) for i in range(0, len(topics)): topic = topics[i] if topic.find('orderbook') >= 0: self.handle_order_book_un_subscription(client, topic) elif topic.find('trades') >= 0: self.handle_trades_un_subscription(client, topic) return message async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ watches information on multiple trades made in a market https://docs.derive.xyz/reference/trades-instrument_name :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() market = self.market(symbol) topic = 'trades.' + market['id'] request: dict = { 'method': 'subscribe', 'params': { 'channels': [ topic, ], }, } subscription: dict = { 'name': topic, 'symbol': symbol, 'params': params, } trades = await self.watch_public(topic, request, subscription) if self.newUpdates: limit = trades.getLimit(market['symbol'], limit) return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True) def handle_trade(self, client: Client, message): # # params = self.safe_dict(message, 'params') data = self.safe_dict(params, 'data') topic = self.safe_value(params, 'channel') parsedTopic = topic.split('.') marketId = self.safe_string(parsedTopic, 1) market = self.safe_market(marketId) symbol = market['symbol'] tradesArray = self.safe_value(self.trades, symbol) if tradesArray is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) tradesArray = ArrayCache(limit) for i in range(0, len(data)): trade = self.parse_trade(data[i]) tradesArray.append(trade) self.trades[symbol] = tradesArray client.resolve(tradesArray, topic) async def authenticate(self, params={}): self.check_required_credentials() url = self.urls['api']['ws'] client = self.client(url) messageHash = 'authenticated' future = client.reusableFuture(messageHash) authenticated = self.safe_value(client.subscriptions, messageHash) if authenticated is None: requestId = self.request_id(url) now = str(self.milliseconds()) signature = self.signMessage(now, self.privateKey) deriveWalletAddress = self.safe_string(self.options, 'deriveWalletAddress') request: dict = { 'id': requestId, 'method': 'public/login', 'params': { 'wallet': deriveWalletAddress, 'timestamp': now, 'signature': signature, }, } # subscription: Dict = { # 'name': topic, # 'symbol': symbol, # 'params': params, # } message = self.extend(request, params) self.watch(url, messageHash, message, messageHash, message) return await future async def watch_private(self, messageHash, message, subscription): await self.authenticate() url = self.urls['api']['ws'] requestId = self.request_id(url) request = self.extend(message, { 'id': requestId, }) subscription = self.extend(subscription, { 'id': requestId, 'method': 'subscribe', }) return await self.watch(url, messageHash, request, messageHash, subscription) async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ https://docs.derive.xyz/reference/subaccount_id-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 [params.subaccount_id]: *required* the subaccount id :returns dict[]: a list of `order structures ` """ await self.load_markets() subaccountId = None subaccountId, params = self.handleDeriveSubaccountId('watchOrders', params) topic = self.number_to_string(subaccountId) + '.orders' messageHash = topic if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash += ':' + symbol request: dict = { 'method': 'subscribe', 'params': { 'channels': [ topic, ], }, } subscription: dict = { 'name': topic, 'params': params, } message = self.extend(request, params) orders = await self.watch_private(messageHash, message, subscription) 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): # # { # method: 'subscription', # params: { # channel: '130837.orders', # data: [ # { # subaccount_id: 130837, # order_id: '1f44c564-5658-4b69-b8c4-4019924207d5', # instrument_name: 'BTC-PERP', # direction: 'buy', # label: 'test1234', # quote_id: null, # creation_timestamp: 1738578974146, # last_update_timestamp: 1738578974146, # limit_price: '10000', # amount: '0.01', # filled_amount: '0', # average_price: '0', # order_fee: '0', # order_type: 'limit', # time_in_force: 'post_only', # order_status: 'untriggered', # max_fee: '219', # signature_expiry_sec: 1746354973, # nonce: 1738578973570, # signer: '0x30CB7B06AdD6749BbE146A6827502B8f2a79269A', # signature: '0xc6927095f74a0d3b1aeef8c0579d120056530479f806e9d2e6616df742a8934c69046361beae833b32b25c0145e318438d7d1624bb835add956f63aa37192f571c', # cancel_reason: '', # mmp: False, # is_transfer: False, # replaced_order_id: null, # trigger_type: 'stoploss', # trigger_price_type: 'mark', # trigger_price: '102800', # trigger_reject_message: null # } # ] # } # } # params = self.safe_dict(message, 'params') topic = self.safe_string(params, 'channel') rawOrders = self.safe_list(params, 'data') for i in range(0, len(rawOrders)): data = rawOrders[i] parsed = self.parse_order(data) symbol = self.safe_string(parsed, 'symbol') orderId = self.safe_string(parsed, 'id') if symbol is not None: if self.orders is None: limit = self.safe_integer(self.options, 'ordersLimit', 1000) self.orders = ArrayCacheBySymbolById(limit) cachedOrders = self.orders orders = self.safe_value(cachedOrders.hashmap, symbol, {}) order = self.safe_value(orders, orderId) if order is not None: fee = self.safe_value(order, 'fee') if fee is not None: parsed['fee'] = fee fees = self.safe_value(order, 'fees') if fees is not None: parsed['fees'] = fees parsed['trades'] = self.safe_value(order, 'trades') parsed['timestamp'] = self.safe_integer(order, 'timestamp') parsed['datetime'] = self.safe_string(order, 'datetime') cachedOrders.append(parsed) messageHashSymbol = topic + ':' + symbol client.resolve(self.orders, messageHashSymbol) client.resolve(self.orders, topic) async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ https://docs.derive.xyz/reference/subaccount_id-trades watches information on multiple trades 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 [params.subaccount_id]: *required* the subaccount id :returns dict[]: a list of `trade structures ` """ await self.load_markets() subaccountId = None subaccountId, params = self.handleDeriveSubaccountId('watchMyTrades', params) topic = self.number_to_string(subaccountId) + '.trades' messageHash = topic if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash += ':' + symbol request: dict = { 'method': 'subscribe', 'params': { 'channels': [ topic, ], }, } subscription: dict = { 'name': topic, 'params': params, } message = self.extend(request, params) trades = await self.watch_private(messageHash, message, subscription) 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): # # myTrades = self.myTrades if myTrades is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) myTrades = ArrayCacheBySymbolById(limit) params = self.safe_dict(message, 'params') topic = self.safe_string(params, 'channel') rawTrades = self.safe_list(params, 'data') for i in range(0, len(rawTrades)): trade = self.parse_trade(message) myTrades.append(trade) client.resolve(myTrades, topic) messageHash = topic + trade['symbol'] client.resolve(myTrades, messageHash) def handle_error_message(self, client: Client, message) -> Bool: # # { # id: '690c6276-0fc6-4121-aafa-f28bf5adedcb', # error: {code: -32600, message: 'Invalid Request'} # } # if not ('error' in message): return False errorMessage = self.safe_dict(message, 'error') errorCode = self.safe_string(errorMessage, 'code') try: if errorCode is not None: feedback = self.id + ' ' + self.json(message) self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback) raise ExchangeError(feedback) return False except Exception as error: if isinstance(error, AuthenticationError): messageHash = 'authenticated' client.reject(error, messageHash) if messageHash in client.subscriptions: del client.subscriptions[messageHash] else: client.reject(error) return True def handle_message(self, client: Client, message): if self.handle_error_message(client, message): return methods: dict = { 'orderbook': self.handle_order_book, 'ticker': self.handle_ticker, 'trades': self.handle_trade, 'orders': self.handle_order, 'mytrades': self.handle_my_trade, } event = None params = self.safe_dict(message, 'params') if params is not None: channel = self.safe_string(params, 'channel') if channel is not None: parsedChannel = channel.split('.') if (channel.find('orders') >= 0) or channel.find('trades') > 0: event = self.safe_string(parsedChannel, 1) # {subaccounr_id}.trades if event == 'trades': event = 'mytrades' else: event = self.safe_string(parsedChannel, 0) method = self.safe_value(methods, event) if method is not None: method(client, message) return if 'id' in message: id = self.safe_string(message, 'id') subscriptionsById = self.index_by(client.subscriptions, 'id') subscription = self.safe_value(subscriptionsById, id, {}) if 'method' in subscription: if subscription['method'] == 'public/login': self.handle_auth(client, message) elif subscription['method'] == 'unsubscribe': self.handle_un_subscribe(client, message) # could handleSubscribe def handle_auth(self, client: Client, message): # # { # id: 1, # result: [130837] # } # messageHash = 'authenticated' ids = self.safe_list(message, 'result') if len(ids) > 0: # client.resolve(message, messageHash) future = self.safe_value(client.futures, 'authenticated') future.resolve(True) else: error = AuthenticationError(self.json(message)) client.reject(error, messageHash) # allows further authentication attempts if messageHash in client.subscriptions: del client.subscriptions['authenticated']