# -*- coding: utf-8 -*- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code from ccxt.base.exchange import Exchange from ccxt.abstract.derive import ImplicitAPI from ccxt.base.types import Any, Balances, Bool, Currencies, Currency, Int, Market, MarketType, Num, Order, OrderSide, OrderType, Position, Str, Strings, Ticker, FundingRate, Trade, Transaction from typing import List from ccxt.base.errors import ExchangeError from ccxt.base.errors import AuthenticationError from ccxt.base.errors import ArgumentsRequired from ccxt.base.errors import BadRequest from ccxt.base.errors import InsufficientFunds from ccxt.base.errors import InvalidOrder from ccxt.base.errors import OrderNotFound from ccxt.base.errors import RateLimitExceeded from ccxt.base.decimal_to_precision import TICK_SIZE from ccxt.base.precise import Precise class derive(Exchange, ImplicitAPI): def describe(self) -> Any: return self.deep_extend(super(derive, self).describe(), { 'id': 'derive', 'name': 'derive', 'countries': [], 'version': 'v1', 'rateLimit': 50, 'certified': False, 'pro': True, 'dex': True, 'has': { 'CORS': None, 'spot': False, 'margin': False, 'swap': False, 'future': False, 'option': False, 'addMargin': False, 'borrowCrossMargin': False, 'borrowIsolatedMargin': False, 'cancelAllOrders': True, 'cancelAllOrdersAfter': False, 'cancelOrder': True, 'cancelOrders': False, 'cancelOrdersForSymbols': False, 'closeAllPositions': False, 'closePosition': False, 'createMarketBuyOrderWithCost': False, 'createMarketOrderWithCost': False, 'createMarketSellOrderWithCost': False, 'createOrder': True, 'createOrders': False, 'createReduceOnlyOrder': False, 'createStopOrder': False, 'createTriggerOrder': False, 'editOrder': True, 'fetchAccounts': False, 'fetchBalance': True, 'fetchBorrowInterest': False, 'fetchBorrowRateHistories': False, 'fetchBorrowRateHistory': False, 'fetchCanceledAndClosedOrders': False, 'fetchCanceledOrders': True, 'fetchClosedOrders': True, 'fetchCrossBorrowRate': False, 'fetchCrossBorrowRates': False, 'fetchCurrencies': True, 'fetchDepositAddress': False, 'fetchDepositAddresses': False, 'fetchDeposits': True, 'fetchDepositWithdrawFee': 'emulated', 'fetchDepositWithdrawFees': False, 'fetchFundingHistory': True, 'fetchFundingRate': True, 'fetchFundingRateHistory': True, 'fetchFundingRates': False, 'fetchIndexOHLCV': False, 'fetchIsolatedBorrowRate': False, 'fetchIsolatedBorrowRates': False, 'fetchLedger': True, 'fetchLeverage': False, 'fetchLeverageTiers': False, 'fetchLiquidations': False, 'fetchMarginMode': None, 'fetchMarketLeverageTiers': False, 'fetchMarkets': True, 'fetchMarkOHLCV': False, 'fetchMyLiquidations': False, 'fetchMyTrades': True, 'fetchOHLCV': False, 'fetchOpenInterest': False, 'fetchOpenInterestHistory': False, 'fetchOpenInterests': False, 'fetchOpenOrders': True, 'fetchOrder': False, 'fetchOrderBook': False, 'fetchOrders': True, 'fetchOrderTrades': True, 'fetchPosition': False, 'fetchPositionMode': False, 'fetchPositions': True, 'fetchPositionsRisk': False, 'fetchPremiumIndexOHLCV': False, 'fetchTicker': True, 'fetchTickers': False, 'fetchTime': True, 'fetchTrades': True, 'fetchTradingFee': False, 'fetchTradingFees': False, 'fetchTransfer': False, 'fetchTransfers': False, 'fetchWithdrawal': False, 'fetchWithdrawals': True, 'reduceMargin': False, 'repayCrossMargin': False, 'repayIsolatedMargin': False, 'sandbox': True, 'setLeverage': False, 'setMarginMode': False, 'setPositionMode': False, 'transfer': False, 'withdraw': False, }, 'timeframes': { '1m': '1m', '3m': '3m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '2h': '2h', '4h': '4h', '8h': '8h', '12h': '12h', '1d': '1d', '3d': '3d', '1w': '1w', '1M': '1M', }, 'hostname': 'derive.xyz', 'urls': { 'logo': 'https://github.com/user-attachments/assets/f835b95f-033a-43dd-b6bb-24e698fc498c', 'api': { 'public': 'https://api.lyra.finance/public', 'private': 'https://api.lyra.finance/private', }, 'test': { 'public': 'https://api-demo.lyra.finance/public', 'private': 'https://api-demo.lyra.finance/private', }, 'www': 'https://www.derive.xyz/', 'doc': 'https://docs.derive.xyz/docs/', 'fees': 'https://docs.derive.xyz/reference/fees-1/', 'referral': 'https://www.derive.xyz/invite/3VB0B', }, 'api': { 'public': { 'get': [ 'get_all_currencies', ], 'post': [ 'build_register_session_key_tx', 'register_session_key', 'deregister_session_key', 'login', 'statistics', 'get_all_currencies', 'get_currency', 'get_instrument', 'get_all_instruments', 'get_instruments', 'get_ticker', 'get_latest_signed_feeds', 'get_option_settlement_prices', 'get_spot_feed_history', 'get_spot_feed_history_candles', 'get_funding_rate_history', 'get_trade_history', 'get_option_settlement_history', 'get_liquidation_history', 'get_interest_rate_history', 'get_transaction', 'get_margin', 'margin_watch', 'validate_invite_code', 'get_points', 'get_all_points', 'get_points_leaderboard', 'get_descendant_tree', 'get_tree_roots', 'get_swell_percent_points', 'get_vault_assets', 'get_etherfi_effective_balances', 'get_kelp_effective_balances', 'get_bridge_balances', 'get_ethena_participants', 'get_vault_share', 'get_vault_statistics', 'get_vault_balances', 'estimate_integrator_points', 'create_subaccount_debug', 'deposit_debug', 'withdraw_debug', 'send_quote_debug', 'execute_quote_debug', 'get_invite_code', 'register_invite', 'get_time', 'get_live_incidents', 'get_maker_programs', 'get_maker_program_scores', ], }, 'private': { 'post': [ 'get_account', 'create_subaccount', 'get_subaccount', 'get_subaccounts', 'get_all_portfolios', 'change_subaccount_label', 'get_notificationsv', 'update_notifications', 'deposit', 'withdraw', 'transfer_erc20', 'transfer_position', 'transfer_positions', 'order', 'replace', 'order_debug', 'get_order', 'get_orders', 'get_open_orders', 'cancel', 'cancel_by_label', 'cancel_by_nonce', 'cancel_by_instrument', 'cancel_all', 'cancel_trigger_order', 'get_order_history', 'get_trade_history', 'get_deposit_history', 'get_withdrawal_history', 'send_rfq', 'cancel_rfq', 'cancel_batch_rfqs', 'get_rfqs', 'poll_rfqs', 'send_quote', 'cancel_quote', 'cancel_batch_quotes', 'get_quotes', 'poll_quotes', 'execute_quote', 'rfq_get_best_quote', 'get_margin', 'get_collaterals', 'get_positions', 'get_option_settlement_history', 'get_subaccount_value_history', 'expired_and_cancelled_history', 'get_funding_history', 'get_interest_history', 'get_erc20_transfer_history', 'get_liquidation_history', 'liquidate', 'get_liquidator_history', 'session_keys', 'edit_session_key', 'register_scoped_session_key', 'get_mmp_config', 'set_mmp_config', 'reset_mmp', 'set_cancel_on_disconnect', 'get_invite_code', 'register_invite', ], }, }, 'fees': { }, 'requiredCredentials': { 'apiKey': False, 'secret': False, 'walletAddress': True, 'privateKey': True, }, 'exceptions': { 'exact': { '-32000': RateLimitExceeded, # Rate limit exceeded '-32100': RateLimitExceeded, # Number of concurrent websocket clients limit exceeded '-32700': BadRequest, # Parse error '-32600': BadRequest, # Invalid Request '-32601': BadRequest, # Method not found '-32602': InvalidOrder, # {"id":"55e66a3d-6a4e-4a36-a23d-5cf8a91ef478","error":{"code":"","message":"Invalid params"}} '-32603': InvalidOrder, # {"code":"-32603","message":"Internal error","data":"SubAccount matching query does not exist."} '9000': InvalidOrder, # Order confirmation timeout '10000': BadRequest, # Manager not found '10001': BadRequest, # Asset is not an ERC20 token '10002': BadRequest, # Sender and recipient wallet do not match '10003': BadRequest, # Sender and recipient subaccount IDs are the same '10004': InvalidOrder, # Multiple currencies not supported '10005': BadRequest, # Maximum number of subaccounts per wallet reached '10006': BadRequest, # Maximum number of session keys per wallet reached '10007': BadRequest, # Maximum number of assets per subaccount reached '10008': BadRequest, # Maximum number of expiries per subaccount reached '10009': BadRequest, # Recipient subaccount ID of the transfer cannot be 0 '10010': InvalidOrder, # PMRM only supports USDC asset collateral. Cannot trade spot markets. '10011': InsufficientFunds, # ERC20 allowance is insufficient '10012': InsufficientFunds, # ERC20 balance is less than transfer amount '10013': ExchangeError, # There is a pending deposit for self asset '10014': ExchangeError, # There is a pending withdrawal for self asset '11000': InsufficientFunds, # Insufficient funds '11002': InvalidOrder, # Order rejected from queue '11003': InvalidOrder, # Already cancelled '11004': InvalidOrder, # Already filled '11005': InvalidOrder, # Already expired '11006': OrderNotFound, # {"code":"11006","message":"Does not exist","data":"Open order with id: 804018f3-b092-40a3-a933-b29574fa1ff8 does not exist."} '11007': InvalidOrder, # Self-crossing disallowed '11008': InvalidOrder, # Post-only reject '11009': InvalidOrder, # Zero liquidity for market or IOC/FOK order '11010': InvalidOrder, # Post-only invalid order type '11011': InvalidOrder, # {"code":11011,"message":"Invalid signature expiry","data":"Order must expire in 300 sec or more"} '11012': InvalidOrder, # {"code":"11012","message":"Invalid amount","data":"Amount must be a multiple of 0.01"} '11013': InvalidOrder, # {"code":"11013","message":"Invalid limit price","data":{"limit":"10000","bandwidth":"92530"}} '11014': InvalidOrder, # Fill-or-kill not filled '11015': InvalidOrder, # MMP frozen '11016': InvalidOrder, # Already consumed '11017': InvalidOrder, # Non unique nonce '11018': InvalidOrder, # Invalid nonce date '11019': InvalidOrder, # Open orders limit exceeded '11020': InsufficientFunds, # Negative ERC20 balance '11021': InvalidOrder, # Instrument is not live '11022': InvalidOrder, # Reject timestamp exceeded '11023': InvalidOrder, # {"code":"11023","message":"Max fee order param is too low","data":"signed max_fee must be >= 194.420835871999983091712000000000000000"} '11024': InvalidOrder, # {"code":11024,"message":"Reduce only not supported with self time in force"} '11025': InvalidOrder, # Reduce only reject '11026': BadRequest, # Transfer reject '11027': InvalidOrder, # Subaccount undergoing liquidation '11028': InvalidOrder, # Replaced order filled amount does not match expected state. '11050': InvalidOrder, # Trigger order was cancelled between the time worker sent order and engine processed order '11051': InvalidOrder, # {"code":"11051","message":"Trigger price must be higher than the current price for stop orders and vice versa for take orders","data":"Trigger price 9000.0 must be < or > current price 102671.2 depending on trigger type and direction."} '11052': InvalidOrder, # Trigger order limit exceeded(separate limit from regular orders) '11053': InvalidOrder, # Index and last-trade trigger price types not supported yet '11054': InvalidOrder, # {"code":"11054","message":"Trigger orders cannot replace or be replaced"} '11055': InvalidOrder, # Market order limit_price is unfillable at the given trigger price '11100': InvalidOrder, # Leg instruments are not unique '11101': InvalidOrder, # RFQ not found '11102': InvalidOrder, # Quote not found '11103': InvalidOrder, # Quote leg does not match RFQ leg '11104': InvalidOrder, # Requested quote or RFQ is not open '11105': InvalidOrder, # Requested quote ID references a different RFQ ID '11106': InvalidOrder, # Invalid RFQ counterparty '11107': InvalidOrder, # Quote maker total cost too high '11200': InvalidOrder, # Auction not ongoing '11201': InvalidOrder, # Open orders not allowed '11202': InvalidOrder, # Price limit exceeded '11203': InvalidOrder, # Last trade ID mismatch '12000': InvalidOrder, # Asset not found '12001': InvalidOrder, # Instrument not found '12002': BadRequest, # Currency not found '12003': BadRequest, # USDC does not have asset caps per manager '13000': BadRequest, # Invalid channels '14000': BadRequest, # {"code": 14000, "message": "Account not found"} '14001': InvalidOrder, # {"code": 14001, "message": "Subaccount not found"} '14002': BadRequest, # Subaccount was withdrawn '14008': BadRequest, # Cannot reduce expiry using registerSessionKey RPC route '14009': BadRequest, # Session key expiry must be > utc_now + 10 min '14010': BadRequest, # Session key already registered for self account '14011': BadRequest, # Session key already registered with another account '14012': BadRequest, # Address must be checksummed '14013': BadRequest, # str is not a valid ethereum address '14014': InvalidOrder, # {"code":"14014","message":"Signature invalid for message or transaction","data":"Signature does not match data"} '14015': BadRequest, # Transaction count for given wallet does not match provided nonce '14016': BadRequest, # The provided signed raw transaction contains function name that does not match the expected function name '14017': BadRequest, # The provided signed raw transaction contains contract address that does not match the expected contract address '14018': BadRequest, # The provided signed raw transaction contains function params that do not match any expected function params '14019': BadRequest, # The provided signed raw transaction contains function param values that do not match the expected values '14020': BadRequest, # The X-LyraWallet header does not match the requested subaccount_id or wallet '14021': BadRequest, # The X-LyraWallet header not provided '14022': AuthenticationError, # Subscription to a private channel failed '14023': InvalidOrder, # {"code":"14023","message":"Signer in on-chain related request is not wallet owner or registered session key","data":"Session key does not belong to wallet"} '14024': BadRequest, # Chain ID must match the current roll up chain id '14025': BadRequest, # The private request is missing a wallet or subaccount_id param '14026': BadRequest, # Session key not found '14027': AuthenticationError, # Unauthorized maker '14028': BadRequest, # Cross currency RFQ not supported '14029': AuthenticationError, # Session key IP not whitelisted '14030': BadRequest, # Session key expired '14031': AuthenticationError, # Unauthorized key scope '14032': BadRequest, # Scope should not be changed '16000': AuthenticationError, # You are in a restricted region that violates our terms of service. '16001': AuthenticationError, # Account is disabled due to compliance violations, please contact support to enable it. '16100': AuthenticationError, # Sentinel authorization is invalid '17000': BadRequest, # This accoount does not have a shareable invite code '17001': BadRequest, # Invalid invite code '17002': BadRequest, # Invite code already registered for self account '17003': BadRequest, # Invite code has no remaining uses '17004': BadRequest, # Requirement for successful invite registration not met '17005': BadRequest, # Account must register with a valid invite code to be elligible for points '17006': BadRequest, # Point program does not exist '17007': BadRequest, # Invalid leaderboard page number '18000': BadRequest, # Invalid block number '18001': BadRequest, # Failed to estimate block number. Please try again later. '18002': BadRequest, # The provided smart contract owner does not match the wallet in LightAccountFactory.getAddress() '18003': BadRequest, # Vault ERC20 asset does not exist '18004': BadRequest, # Vault ERC20 pool does not exist '18005': BadRequest, # Must add asset to pool before getting balances '18006': BadRequest, # Invalid Swell season. Swell seasons are in the form 'swell_season_X'. '18007': BadRequest, # Vault not found '19000': BadRequest, # Maker program not found }, 'broad': { }, }, 'precisionMode': TICK_SIZE, 'commonCurrencies': { }, 'options': { 'deriveWalletAddress': '', # a derive wallet address "0x"-prefixed hexstring 'id': '0x0ad42b8e602c2d3d475ae52d678cf63d84ab2749', }, }) def set_sandbox_mode(self, enable: bool): super(derive, self).set_sandbox_mode(enable) self.options['sandboxMode'] = enable def fetch_time(self, params={}): """ fetches the current integer timestamp in milliseconds from the exchange server https://docs.derive.xyz/reference/post_public-get-time :param dict [params]: extra parameters specific to the exchange API endpoint :returns int: the current integer timestamp in milliseconds from the exchange server """ response = self.publicPostGetTime(params) # # { # "result": 1735846536758, # "id": "f1c03d21-f886-4c5a-9a9d-33dd06f180f0" # } # return self.safe_integer(response, 'result') def fetch_currencies(self, params={}) -> Currencies: """ fetches all available currencies on an exchange https://docs.derive.xyz/reference/post_public-get-all-currencies :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an associative dictionary of currencies """ result: dict = {} tokenResponse = self.publicGetGetAllCurrencies(params) # # { # "result": [ # { # "currency": "SEI", # "instrument_types": [ # "perp" # ], # "protocol_asset_addresses": { # "perp": "0x7225889B75fd34C68eA3098dAE04D50553C09840", # "option": null, # "spot": null, # "underlying_erc20": null # }, # "managers": [ # { # "address": "0x28c9ddF9A3B29c2E6a561c1BC520954e5A33de5D", # "margin_type": "SM", # "currency": null # } # ], # "srm_im_discount": "0", # "srm_mm_discount": "0", # "pm2_collateral_discounts": [], # "borrow_apy": "0", # "supply_apy": "0", # "total_borrow": "0", # "total_supply": "0", # "asset_cap_and_supply_per_manager": { # "perp": { # "SM": [ # { # "current_open_interest": "0", # "interest_cap": "2000000", # "manager_currency": null # } # ] # }, # "option": {}, # "erc20": {} # }, # "market_type": "SRM_PERP_ONLY", # "spot_price": "0.2193542905042081", # "spot_price_24h": "0.238381655533635830" # }, # "id": "7e07fe1d-0ab4-4d2b-9e22-b65ce9e232dc" # } # currencies = self.safe_list(tokenResponse, 'result', []) for i in range(0, len(currencies)): currency = currencies[i] currencyId = self.safe_string(currency, 'currency') code = self.safe_currency_code(currencyId) result[code] = self.safe_currency_structure({ 'id': currencyId, 'name': None, 'code': code, 'precision': None, 'active': None, 'fee': None, 'networks': None, 'deposit': None, 'withdraw': None, 'limits': { 'deposit': { 'min': None, 'max': None, }, 'withdraw': { 'min': None, 'max': None, }, }, 'info': currency, }) return result def fetch_markets(self, params={}) -> List[Market]: """ retrieves data on all markets for bybit https://docs.derive.xyz/reference/post_public-get-all-instruments :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: an array of objects representing market data """ spotMarketsPromise = self.fetch_spot_markets(params) swapMarketsPromise = self.fetch_swap_markets(params) optionMarketsPromise = self.fetch_option_markets(params) spotMarkets, swapMarkets, optionMarkets = [spotMarketsPromise, swapMarketsPromise, optionMarketsPromise] # # { # "result": { # "instruments": [ # { # "instrument_type": "perp", # "instrument_name": "BTC-PERP", # "scheduled_activation": 1701840228, # "scheduled_deactivation": 9223372036854776000, # "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.00005", # "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": "10538.574363381759146829", # "funding_rate": "0.0000125" # }, # "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" # } # ], # "pagination": { # "num_pages": 1, # "count": 1 # } # }, # "id": "a06bc0b2-8e78-4536-a21f-f785f225b5a5" # } # result = self.array_concat(spotMarkets, swapMarkets) result = self.array_concat(result, optionMarkets) return result def fetch_spot_markets(self, params={}) -> List[Market]: request: dict = { 'expired': False, 'instrument_type': 'erc20', } response = self.publicPostGetAllInstruments(self.extend(request, params)) result = self.safe_dict(response, 'result', {}) data = self.safe_list(result, 'instruments', []) return self.parse_markets(data) def fetch_swap_markets(self, params={}) -> List[Market]: request: dict = { 'expired': False, 'instrument_type': 'perp', } response = self.publicPostGetAllInstruments(self.extend(request, params)) result = self.safe_dict(response, 'result', {}) data = self.safe_list(result, 'instruments', []) return self.parse_markets(data) def fetch_option_markets(self, params={}) -> List[Market]: request: dict = { 'expired': False, 'instrument_type': 'option', } response = self.publicPostGetAllInstruments(self.extend(request, params)) result = self.safe_dict(response, 'result', {}) data = self.safe_list(result, 'instruments', []) return self.parse_markets(data) def parse_market(self, market: dict) -> Market: type = self.safe_string(market, 'instrument_type') marketType: MarketType spot = False margin = True swap = False option = False linear: Bool = None inverse: Bool = None baseId = self.safe_string(market, 'base_currency') quoteId = self.safe_string(market, 'quote_currency') base = self.safe_currency_code(baseId) quote = self.safe_currency_code(quoteId) marketId = self.safe_string(market, 'instrument_name') symbol = base + '/' + quote settleId: Str = None settle: Str = None expiry: Num = None strike: Num = None optionType: Str = None optionLetter: Str = None if type == 'erc20': spot = True marketType = 'spot' elif type == 'perp': margin = False settleId = 'USDC' settle = self.safe_currency_code(settleId) symbol = base + '/' + quote + ':' + settle swap = True linear = True inverse = False marketType = 'swap' elif type == 'option': settleId = 'USDC' settle = self.safe_currency_code(settleId) margin = False option = True marketType = 'option' optionDetails = self.safe_dict(market, 'option_details') expiry = self.safe_timestamp(optionDetails, 'expiry') strike = self.safe_integer(optionDetails, 'strike') optionLetter = self.safe_string(optionDetails, 'option_type') symbol = base + '/' + quote + ':' + settle + '-' + self.yymmdd(expiry) + '-' + self.number_to_string(strike) + '-' + optionLetter if optionLetter == 'P': optionType = 'put' else: optionType = 'call' linear = True inverse = False return self.safe_market_structure({ 'id': marketId, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, 'type': marketType, 'spot': spot, 'margin': margin, 'swap': swap, 'future': False, 'option': option, 'active': self.safe_bool(market, 'is_active'), 'contract': (swap or option), 'linear': linear, 'inverse': inverse, 'contractSize': None if (spot) else 1, 'expiry': expiry, 'expiryDatetime': self.iso8601(expiry), 'taker': self.safe_number(market, 'taker_fee_rate'), 'maker': self.safe_number(market, 'maker_fee_rate'), 'strike': strike, 'optionType': optionType, 'precision': { 'amount': self.safe_number(market, 'amount_step'), 'price': self.safe_number(market, 'tick_size'), }, 'limits': { 'leverage': { 'min': None, 'max': None, }, 'amount': { 'min': self.safe_number(market, 'minimum_amount'), 'max': self.safe_number(market, 'maximum_amount'), }, 'price': { 'min': None, 'max': None, }, 'cost': { 'min': None, 'max': None, }, }, 'created': None, 'info': market, }) def fetch_ticker(self, symbol: str, params={}) -> Ticker: """ fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market https://docs.derive.xyz/reference/post_public-get-ticker :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 ` """ self.load_markets() market = self.market(symbol) request: dict = { 'instrument_name': market['id'], } response = self.publicPostGetTicker(self.extend(request, params)) # # spot # # { # "result": { # "instrument_type": "perp", # "instrument_name": "BTC-PERP", # "scheduled_activation": 1701840228, # "scheduled_deactivation": 9223372036854776000, # "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.00005", # "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": "10512.580833189805742522", # "funding_rate": "-0.000022223906766867" # }, # "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.012", # "best_ask_price": "99567.9", # "best_bid_amount": "0.129", # "best_bid_price": "99554.5", # "five_percent_bid_depth": "11.208", # "five_percent_ask_depth": "11.42", # "option_pricing": null, # "index_price": "99577.2", # "mark_price": "99543.642926357933902181684970855712890625", # "stats": { # "contract_volume": "464.712", # "num_trades": "10681", # "open_interest": "72.804739389481989861", # "high": "99519.1", # "low": "97254.1", # "percent_change": "0.0128", # "usd_change": "1258.1" # }, # "timestamp": 1736140984000, # "min_price": "97591.2", # "max_price": "101535.1" # }, # "id": "bbd7c271-c2be-48f7-b93a-26cf6d4cb79f" # } # data = self.safe_dict(response, 'result', {}) return self.parse_ticker(data, market) def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker: # # { # "instrument_type": "perp", # "instrument_name": "BTC-PERP", # "scheduled_activation": 1701840228, # "scheduled_deactivation": 9223372036854776000, # "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.00005", # "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": "10512.580833189805742522", # "funding_rate": "-0.000022223906766867" # }, # "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.012", # "best_ask_price": "99567.9", # "best_bid_amount": "0.129", # "best_bid_price": "99554.5", # "five_percent_bid_depth": "11.208", # "five_percent_ask_depth": "11.42", # "option_pricing": null, # "index_price": "99577.2", # "mark_price": "99543.642926357933902181684970855712890625", # "stats": { # "contract_volume": "464.712", # "num_trades": "10681", # "open_interest": "72.804739389481989861", # "high": "99519.1", # "low": "97254.1", # "percent_change": "0.0128", # "usd_change": "1258.1" # }, # "timestamp": 1736140984000, # "min_price": "97591.2", # "max_price": "101535.1" # } # marketId = self.safe_string(ticker, 'instrument_name') timestamp = self.safe_integer_omit_zero(ticker, 'timestamp') symbol = self.safe_symbol(marketId, market) stats = self.safe_dict(ticker, 'stats') change = self.safe_string(stats, 'percent_change') return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'high': self.safe_string(stats, 'high'), 'low': self.safe_string(stats, 'low'), 'bid': self.safe_string(ticker, 'best_bid_price'), 'bidVolume': self.safe_string(ticker, 'best_bid_amount'), 'ask': self.safe_string(ticker, 'best_ask_price'), 'askVolume': self.safe_string(ticker, 'best_ask_amount'), 'vwap': None, 'open': None, 'close': None, 'last': None, 'previousClose': None, 'change': change, 'percentage': Precise.string_mul(change, '100'), 'average': None, 'baseVolume': None, 'quoteVolume': None, 'indexPrice': self.safe_string(ticker, 'index_price'), 'markPrice': self.safe_string(ticker, 'mark_price'), 'info': ticker, }, market) def fetch_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.derive.xyz/reference/post_public-get-trade-history :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 int [params.until]: the latest time in ms to fetch trades for :returns Trade[]: a list of `trade structures ` """ self.load_markets() request: dict = {} market = None if symbol is not None: market = self.market(symbol) request['instrument_name'] = market['id'] if limit is not None: if limit > 1000: limit = 1000 request['page_size'] = limit # default 100, max 1000 if since is not None: request['from_timestamp'] = since until = self.safe_integer(params, 'until') params = self.omit(params, ['until']) if until is not None: request['to_timestamp'] = until response = self.publicPostGetTradeHistory(self.extend(request, params)) # # { # "result": { # "trades": [ # { # "trade_id": "9dbc88b0-f0c4-4439-9cc1-4e6409d4eafb", # "instrument_name": "BTC-PERP", # "timestamp": 1736153910930, # "trade_price": "98995.3", # "trade_amount": "0.033", # "mark_price": "98990.875914388161618263", # "index_price": "99038.050611100001501184", # "direction": "sell", # "quote_id": null, # "wallet": "0x88B6BB87fbFac92a34F8155aaA35c87B5b166fA9", # "subaccount_id": 8250, # "tx_status": "settled", # "tx_hash": "0x020bd735b312f867f17f8cc254946d87cfe9f2c8ff3605035d8129082eb73723", # "trade_fee": "0.980476701049890015", # "liquidity_role": "taker", # "realized_pnl": "-2.92952402688793509", # "realized_pnl_excl_fees": "-1.949047325838045075" # } # ], # "pagination": { # "num_pages": 598196, # "count": 598196 # } # }, # "id": "b8539544-6975-4497-8163-5e51a38e4aa7" # } # result = self.safe_dict(response, 'result', {}) data = self.safe_list(result, 'trades', []) return self.parse_trades(data, market, since, limit) def parse_trade(self, trade: dict, market: Market = None) -> Trade: # # { # "subaccount_id": 130837, # "order_id": "30c48194-8d48-43ac-ad00-0d5ba29eddc9", # "instrument_name": "BTC-PERP", # "direction": "sell", # "label": "test1234", # "quote_id": null, # "trade_id": "f8a30740-488c-4c2d-905d-e17057bafde1", # "timestamp": 1738065303708, # "mark_price": "102740.137375457314192317", # "index_price": "102741.553409299981533184", # "trade_price": "102700.6", # "trade_amount": "0.01", # "liquidity_role": "taker", # "realized_pnl": "0", # "realized_pnl_excl_fees": "0", # "is_transfer": False, # "tx_status": "settled", # "trade_fee": "1.127415534092999815", # "tx_hash": "0xc55df1f07330faf86579bd8a6385391fbe9e73089301149d8550e9d29c9ead74", # "transaction_id": "e18b9426-3fa5-41bb-99d3-8b54fb4d51bb" # } # marketId = self.safe_string(trade, 'instrument_name') symbol = self.safe_symbol(marketId, market) timestamp = self.safe_integer(trade, 'timestamp') fee = { 'currency': 'USDC', 'cost': self.safe_string(trade, 'trade_fee'), } return self.safe_trade({ 'info': trade, 'id': self.safe_string(trade, 'trade_id'), 'order': self.safe_string(trade, 'order_id'), 'symbol': symbol, 'side': self.safe_string_lower(trade, 'direction'), 'type': None, 'takerOrMaker': self.safe_string(trade, 'liquidity_role'), 'price': self.safe_string(trade, 'trade_price'), 'amount': self.safe_string(trade, 'trade_amount'), 'cost': None, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'fee': fee, }, market) def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetches historical funding rate prices https://docs.derive.xyz/reference/post_public-get-funding-rate-history :param str symbol: unified symbol of the market to fetch the funding rate history for :param int [since]: timestamp in ms of the earliest funding rate to fetch :param int [limit]: the maximum amount of funding rate structures to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `funding rate structures ` """ self.load_markets() market = self.market(symbol) request: dict = { 'instrument_name': market['id'], } if since is not None: request['start_timestamp'] = since until = self.safe_integer(params, 'until') params = self.omit(params, ['until']) if until is not None: request['to_timestamp'] = until response = self.publicPostGetFundingRateHistory(self.extend(request, params)) # # { # "result": { # "funding_rate_history": [ # { # "timestamp": 1736215200000, # "funding_rate": "-0.000020014" # } # ] # }, # "id": "3200ab8d-0080-42f0-8517-c13e3d9201d8" # } # result = self.safe_dict(response, 'result', {}) data = self.safe_list(result, 'funding_rate_history', []) rates = [] for i in range(0, len(data)): entry = data[i] timestamp = self.safe_integer(entry, 'timestamp') rates.append({ 'info': entry, 'symbol': market['symbol'], 'fundingRate': self.safe_number(entry, 'funding_rate'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), }) sorted = self.sort_by(rates, 'timestamp') return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit) def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate: """ fetch the current funding rate https://docs.derive.xyz/reference/post_public-get-funding-rate-history :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `funding rate structure ` """ response = self.fetch_funding_rate_history(symbol, None, 1, params) # # [ # { # "info": { # "timestamp": 1736157600000, # "funding_rate": "-0.000008872" # }, # "symbol": "BTC/USD:USDC", # "fundingRate": -0.000008872, # "timestamp": 1736157600000, # "datetime": "2025-01-06T10:00:00.000Z" # } # ] # data = self.safe_dict(response, 0) return self.parse_funding_rate(data) def parse_funding_rate(self, contract, market: Market = None) -> FundingRate: symbol = self.safe_string(contract, 'symbol') fundingTimestamp = self.safe_integer(contract, 'timestamp') return { 'info': contract, 'symbol': symbol, 'markPrice': None, 'indexPrice': None, 'interestRate': None, 'estimatedSettlePrice': None, 'timestamp': None, 'datetime': None, 'fundingRate': self.safe_number(contract, 'fundingRate'), 'fundingTimestamp': fundingTimestamp, 'fundingDatetime': self.iso8601(fundingTimestamp), 'nextFundingRate': None, 'nextFundingTimestamp': None, 'nextFundingDatetime': None, 'previousFundingRate': None, 'previousFundingTimestamp': None, 'previousFundingDatetime': None, 'interval': None, } def hash_order_message(self, order): accountHash = self.hash(self.eth_abi_encode([ 'bytes32', 'uint256', 'uint256', 'address', 'bytes32', 'uint256', 'address', 'address', ], order), 'keccak', 'binary') sandboxMode = self.safe_bool(self.options, 'sandboxMode', False) DOMAIN_SEPARATOR = '9bcf4dc06df5d8bf23af818d5716491b995020f377d3b7b64c29ed14e3dd1105' if (sandboxMode) else 'd96e5f90797da7ec8dc4e276260c7f3f87fedf68775fbe1ef116e996fc60441b' binaryDomainSeparator = self.base16_to_binary(DOMAIN_SEPARATOR) prefix = self.base16_to_binary('1901') return self.hash(self.binary_concat(prefix, binaryDomainSeparator, accountHash), 'keccak', 'hex') def sign_order(self, order, privateKey): hashOrder = self.hash_order_message(order) return self.sign_hash(hashOrder[-64:], privateKey[-64:]) def hash_message(self, message): binaryMessage = self.encode(message) binaryMessageLength = self.binary_length(binaryMessage) x19 = self.base16_to_binary('19') newline = self.base16_to_binary('0a') prefix = self.binary_concat(x19, self.encode('Ethereum Signed Message:'), newline, self.encode(self.number_to_string(binaryMessageLength))) return '0x' + self.hash(self.binary_concat(prefix, binaryMessage), 'keccak', 'hex') def sign_hash(self, hash, privateKey): self.check_required_credentials() signature = self.ecdsa(hash[-64:], privateKey[-64:], 'secp256k1', None) r = signature['r'] s = signature['s'] v = self.int_to_base16(self.sum(27, signature['v'])) return '0x' + r.rjust(64, '0') + s.rjust(64, '0') + v def sign_message(self, message, privateKey): return self.sign_hash(self.hash_message(message), privateKey[-64:]) def parse_units(self, num: str, dec='1000000000000000000'): return Precise.string_mul(num, dec) def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ create a trade order https://docs.derive.xyz/reference/post_private-order :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much of currency you want to trade in units of base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.subaccount_id]: *required* the subaccount id :param float [params.triggerPrice]: The price a trigger order is triggered at :param dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered(perpetual swap markets only) :param float [params.takeProfit.triggerPrice]: take profit trigger price :param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered(perpetual swap markets only) :param float [params.stopLoss.triggerPrice]: stop loss trigger price :param float [params.max_fee]: *required* the maximum fee you are willing to pay for the order :returns dict: an `order structure ` """ self.load_markets() market = self.market(symbol) if price is None: raise ArgumentsRequired(self.id + ' createOrder() requires a price argument') subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('createOrder', params) test = self.safe_bool(params, 'test', False) reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only') timeInForce = self.safe_string_lower_2(params, 'timeInForce', 'time_in_force') postOnly = self.safe_bool(params, 'postOnly') orderType = type.lower() orderSide = side.lower() nonce = self.milliseconds() # Order signature expiry must be between 2592000 and 7776000 sec from now signatureExpiry = self.safe_integer(params, 'signature_expiry_sec', self.seconds() + 7776000) ACTION_TYPEHASH = self.base16_to_binary('4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17') sandboxMode = self.safe_bool(self.options, 'sandboxMode', False) TRADE_MODULE_ADDRESS = '0x87F2863866D85E3192a35A73b388BD625D83f2be' if (sandboxMode) else '0xB8D20c2B7a1Ad2EE33Bc50eF10876eD3035b5e7b' priceString = self.number_to_string(price) maxFee = None maxFee, params = self.handle_option_and_params(params, 'createOrder', 'max_fee') if maxFee is None: raise ArgumentsRequired(self.id + ' createOrder() requires a max_fee argument in params') maxFeeString = self.number_to_string(maxFee) amountString = self.number_to_string(amount) tradeModuleDataHash = self.hash(self.eth_abi_encode([ 'address', 'uint', 'int', 'int', 'uint', 'uint', 'bool', ], [ market['info']['base_asset_address'], self.parse_to_numeric(market['info']['base_asset_sub_id']), self.convert_to_big_int(self.parse_units(priceString)), self.convert_to_big_int(self.parse_units(self.amount_to_precision(symbol, amountString))), self.convert_to_big_int(self.parse_units(maxFeeString)), subaccountId, orderSide == 'buy', ]), 'keccak', 'binary') deriveWalletAddress = None deriveWalletAddress, params = self.handle_derive_wallet_address('createOrder', params) signature = self.sign_order([ ACTION_TYPEHASH, subaccountId, nonce, TRADE_MODULE_ADDRESS, tradeModuleDataHash, signatureExpiry, deriveWalletAddress, self.walletAddress, ], self.privateKey) request: dict = { 'instrument_name': market['id'], 'direction': orderSide, 'order_type': orderType, 'nonce': nonce, 'amount': amountString, 'limit_price': priceString, 'max_fee': maxFeeString, 'subaccount_id': subaccountId, 'signature_expiry_sec': signatureExpiry, 'referral_code': self.safe_string(self.options, 'id', '0x0ad42b8e602c2d3d475ae52d678cf63d84ab2749'), 'signer': self.walletAddress, } if reduceOnly is not None: request['reduce_only'] = reduceOnly if reduceOnly and postOnly: raise InvalidOrder(self.id + ' cannot use reduce only with post only time in force') if postOnly is not None: request['time_in_force'] = 'post_only' elif timeInForce is not None: request['time_in_force'] = timeInForce stopLoss = self.safe_value(params, 'stopLoss') takeProfit = self.safe_value(params, 'takeProfit') triggerPriceType = self.safe_string(params, 'trigger_price_type', 'mark') if stopLoss is not None: stopLossPrice = self.safe_string(stopLoss, 'triggerPrice', stopLoss) request['trigger_price'] = stopLossPrice request['trigger_type'] = 'stoploss' request['trigger_price_type'] = triggerPriceType elif takeProfit is not None: takeProfitPrice = self.safe_string(takeProfit, 'triggerPrice', takeProfit) request['trigger_price'] = takeProfitPrice request['trigger_type'] = 'takeprofit' request['trigger_price_type'] = triggerPriceType clientOrderId = self.safe_string(params, 'clientOrderId') if clientOrderId is not None: request['label'] = clientOrderId request['signature'] = signature params = self.omit(params, ['reduceOnly', 'reduce_only', 'timeInForce', 'time_in_force', 'postOnly', 'test', 'clientOrderId', 'stopPrice', 'triggerPrice', 'trigger_price', 'stopLoss', 'takeProfit', 'trigger_price_type']) response = None if test: response = self.privatePostOrderDebug(self.extend(request, params)) else: response = self.privatePostOrder(self.extend(request, params)) # # { # "result": { # "raw_data": { # "subaccount_id": 130837, # "nonce": 1736923517552, # "module": "0x87F2863866D85E3192a35A73b388BD625D83f2be", # "expiry": 86400, # "owner": "0x108b9aF9279a525b8A8AeAbE7AC2bA925Bc50075", # "signer": "0x108b9aF9279a525b8A8AeAbE7AC2bA925Bc50075", # "signature": "0xaa4f42b2f3da33c668fa703ea872d4c3a6b55aca66025b5119e3bebb6679fe2e2794638db51dcace21fc39a498047835994f07eb59f311bb956ce057e66793d1c", # "data": { # "asset": "0xAFB6Bb95cd70D5367e2C39e9dbEb422B9815339D", # "sub_id": 0, # "limit_price": "10000", # "desired_amount": "0.001", # "worst_fee": "0", # "recipient_id": 130837, # "is_bid": True, # "trade_id": "" # } # }, # "encoded_data": "0x000000000000000000000000afb6bb95cd70d5367e2c39e9dbeb422b9815339d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021e19e0c9bab240000000000000000000000000000000000000000000000000000000038d7ea4c680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ff150000000000000000000000000000000000000000000000000000000000000001", # "encoded_data_hashed": "0xe88fb416bc54dba2d288988f1a82fee40fd792ed555b3471b5f6b4b810d279b4", # "action_hash": "0x273a0befb3751fa991edc7ed73582456c3b50ae964d458c8f472e932fb6a0069", # "typed_data_hash": "0x123e2d2f3d5b2473b4e260f51c6459d6bf904e5db8f042a3ea63be8d55329ce9" # }, # "id": "f851c8c4-dddf-4b77-93cf-aeddd0966f29" # } # { # "result": { # "order": { # "subaccount_id": 130837, # "order_id": "96349ebb-7d46-43ae-81c7-7ab390444293", # "instrument_name": "BTC-PERP", # "direction": "buy", # "label": "", # "quote_id": null, # "creation_timestamp": 1737467576257, # "last_update_timestamp": 1737467576257, # "limit_price": "10000", # "amount": "0.01", # "filled_amount": "0", # "average_price": "0", # "order_fee": "0", # "order_type": "limit", # "time_in_force": "gtc", # "order_status": "open", # "max_fee": "210", # "signature_expiry_sec": 1737468175989, # "nonce": 1737467575989, # "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A", # "signature": "0xd1ca49df1fa06bd805bb59b132ff6c0de29bf973a3e01705abe0a01cc956e4945ed9eb99ab68f3df4c037908113cac5a5bfc3a954a0b7103cdab285962fa6a51c", # "cancel_reason": "", # "mmp": False, # "is_transfer": False, # "replaced_order_id": null, # "trigger_type": null, # "trigger_price_type": null, # "trigger_price": null, # "trigger_reject_message": null # }, # "trades": [] # }, # "id": "397087fa-0125-42af-bfc3-f66166f9fb55" # } # result = self.safe_dict(response, 'result') rawOrder = self.safe_dict(result, 'raw_data') if rawOrder is None: rawOrder = self.safe_dict(result, 'order') order = self.parse_order(rawOrder, market) order['type'] = type return order def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}): """ edit a trade order https://docs.derive.xyz/reference/post_private-replace :param str id: order id :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much of currency you want to trade in units of base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.subaccount_id]: *required* the subaccount id :returns dict: an `order structure ` """ self.load_markets() market = self.market(symbol) subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('editOrder', params) reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only') timeInForce = self.safe_string_lower_2(params, 'timeInForce', 'time_in_force') postOnly = self.safe_bool(params, 'postOnly') orderType = type.lower() orderSide = side.lower() nonce = self.milliseconds() signatureExpiry = self.safe_number(params, 'signature_expiry_sec', self.seconds() + 7776000) # TODO: subaccount id / trade module address ACTION_TYPEHASH = self.base16_to_binary('4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17') sandboxMode = self.safe_bool(self.options, 'sandboxMode', False) TRADE_MODULE_ADDRESS = '0x87F2863866D85E3192a35A73b388BD625D83f2be' if (sandboxMode) else '0xB8D20c2B7a1Ad2EE33Bc50eF10876eD3035b5e7b' priceString = self.number_to_string(price) maxFeeString = self.safe_string(params, 'max_fee', '0') amountString = self.number_to_string(amount) tradeModuleDataHash = self.hash(self.eth_abi_encode([ 'address', 'uint', 'int', 'int', 'uint', 'uint', 'bool', ], [ market['info']['base_asset_address'], self.parse_to_numeric(market['info']['base_asset_sub_id']), self.convert_to_big_int(self.parse_units(priceString)), self.convert_to_big_int(self.parse_units(self.amount_to_precision(symbol, amountString))), self.convert_to_big_int(self.parse_units(maxFeeString)), subaccountId, orderSide == 'buy', ]), 'keccak', 'binary') deriveWalletAddress = None deriveWalletAddress, params = self.handle_derive_wallet_address('editOrder', params) signature = self.sign_order([ ACTION_TYPEHASH, subaccountId, nonce, TRADE_MODULE_ADDRESS, tradeModuleDataHash, signatureExpiry, deriveWalletAddress, self.walletAddress, ], self.privateKey) request: dict = { 'instrument_name': market['id'], 'order_id_to_cancel': id, 'direction': orderSide, 'order_type': orderType, 'nonce': nonce, 'amount': amountString, 'limit_price': priceString, 'max_fee': maxFeeString, 'subaccount_id': subaccountId, 'signature_expiry_sec': signatureExpiry, 'signer': self.walletAddress, } if reduceOnly is not None: request['reduce_only'] = reduceOnly if reduceOnly and postOnly: raise InvalidOrder(self.id + ' cannot use reduce only with post only time in force') if postOnly is not None: request['time_in_force'] = 'post_only' elif timeInForce is not None: request['time_in_force'] = timeInForce clientOrderId = self.safe_string(params, 'clientOrderId') if clientOrderId is not None: request['label'] = clientOrderId request['signature'] = signature params = self.omit(params, ['reduceOnly', 'reduce_only', 'timeInForce', 'time_in_force', 'postOnly', 'clientOrderId']) response = self.privatePostReplace(self.extend(request, params)) # # { # "result": # { # "cancelled_order": # { # "subaccount_id": 130837, # "order_id": "c2337704-f1af-437d-91c8-dddb9d6bac59", # "instrument_name": "BTC-PERP", # "direction": "buy", # "label": "test1234", # "quote_id": null, # "creation_timestamp": 1737539743959, # "last_update_timestamp": 1737539764234, # "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": "cancelled", # "max_fee": "211", # "signature_expiry_sec": 1737540343631, # "nonce": 1737539743631, # "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A", # "signature": "0xdb669e18f407a3efa816b79c0dd3bac1c651d4dbf3caad4db67678ce9b81c76378d787a08143a30707eb0827ce4626640767c9f174358df1b90611bd6d1391711b", # "cancel_reason": "user_request", # "mmp": False, # "is_transfer": False, # "replaced_order_id": null, # "trigger_type": null, # "trigger_price_type": null, # "trigger_price": null, # "trigger_reject_message": null, # }, # "order": # { # "subaccount_id": 130837, # "order_id": "97af0902-813f-4892-a54b-797e5689db05", # "instrument_name": "BTC-PERP", # "direction": "buy", # "label": "test1234", # "quote_id": null, # "creation_timestamp": 1737539764154, # "last_update_timestamp": 1737539764154, # "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": "open", # "max_fee": "211", # "signature_expiry_sec": 1737540363890, # "nonce": 1737539763890, # "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A", # "signature": "0xef2c459ab4797cbbd7d97b47678ff172542af009bac912bf53e7879cf92eb1aa6b1a6cf40bf0928684f5394942fb424cc2db71eac0eaf7226a72480034332f291c", # "cancel_reason": "", # "mmp": False, # "is_transfer": False, # "replaced_order_id": "c2337704-f1af-437d-91c8-dddb9d6bac59", # "trigger_type": null, # "trigger_price_type": null, # "trigger_price": null, # "trigger_reject_message": null, # }, # "trades": [], # "create_order_error": null, # }, # "id": "fb19e991-15f6-4c80-a20c-917e762a1a38", # } # result = self.safe_dict(response, 'result') rawOrder = self.safe_dict(result, 'order') order = self.parse_order(rawOrder, market) return order def cancel_order(self, id: str, symbol: Str = None, params={}): """ https://docs.derive.xyz/reference/post_private-cancel cancels an open order :param str id: order id :param str symbol: unified symbol of the market the order was made in :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.trigger]: whether the order is a trigger/algo order :param str [params.subaccount_id]: *required* the subaccount id :returns dict: An `order structure ` """ if symbol is None: raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument') self.load_markets() market: Market = self.market(symbol) isTrigger = self.safe_bool_2(params, 'trigger', 'stop', False) subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('cancelOrder', params) params = self.omit(params, ['trigger', 'stop']) request: dict = { 'instrument_name': market['id'], 'subaccount_id': subaccountId, } clientOrderIdUnified = self.safe_string(params, 'clientOrderId') clientOrderIdExchangeSpecific = self.safe_string(params, 'label', clientOrderIdUnified) isByClientOrder = clientOrderIdExchangeSpecific is not None response = None if isByClientOrder: request['label'] = clientOrderIdExchangeSpecific params = self.omit(params, ['clientOrderId', 'label']) response = self.privatePostCancelByLabel(self.extend(request, params)) else: request['order_id'] = id if isTrigger: response = self.privatePostCancelTriggerOrder(self.extend(request, params)) else: response = self.privatePostCancel(self.extend(request, params)) # # { # "result": { # "subaccount_id": 130837, # "order_id": "de4f30b6-0dcb-4df6-9222-c1a27f1ad80d", # "instrument_name": "BTC-PERP", # "direction": "buy", # "label": "test1234", # "quote_id": null, # "creation_timestamp": 1737540100989, # "last_update_timestamp": 1737540574696, # "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": "cancelled", # "max_fee": "211", # "signature_expiry_sec": 1737540700726, # "nonce": 1737540100726, # "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A", # "signature": "0x9cd1a6e32a0699929e4e090c08c548366b1353701ec56e02d5cdf37fc89bd19b7b29e00e57e8383bb6336d73019027a7e2a4364f40859e7a949115024c7f199a1b", # "cancel_reason": "user_request", # "mmp": False, # "is_transfer": False, # "replaced_order_id": "4ccc89ba-3c3d-4047-8900-0aa5fb4ef706", # "trigger_type": null, # "trigger_price_type": null, # "trigger_price": null, # "trigger_reject_message": null # }, # "id": "cef61e2a-cb13-4779-8e6b-535361981fad" # } # # { # "result": { # "cancelled_orders": 1 # }, # "id": "674e075e-1e8a-4a47-99ff-75efbdd2370f" # } # extendParams: dict = {'symbol': symbol} order = self.safe_dict(response, 'result') if isByClientOrder: extendParams['client_order_id'] = clientOrderIdExchangeSpecific return self.extend(self.parse_order(order, market), extendParams) def cancel_all_orders(self, symbol: Str = None, params={}): """ https://docs.derive.xyz/reference/post_private-cancel-by-instrument https://docs.derive.xyz/reference/post_private-cancel-all cancel all open orders in a market :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.subaccount_id]: *required* the subaccount id :returns dict: an list of `order structures ` """ self.load_markets() market: Market = None if symbol is not None: market = self.market(symbol) subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('cancelAllOrders', params) request: dict = { 'subaccount_id': subaccountId, } response = None if market is not None: request['instrument_name'] = market['id'] response = self.privatePostCancelByInstrument(self.extend(request, params)) else: response = self.privatePostCancelAll(self.extend(request, params)) # # { # "result": { # "cancelled_orders": 0 # }, # "id": "9d633799-2098-4559-b547-605bb6f4d8f4" # } # # { # "id": "45548646-c74f-4ca2-9de4-551e6de49afa", # "result": "ok" # } # return [self.safe_order({'info': response})] def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetches information on multiple orders made by the user https://docs.derive.xyz/reference/post_private-get-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 :param boolean [params.paginate]: set to True if you want to fetch orders with pagination :param boolean [params.trigger]: whether the order is a trigger/algo order :param str [params.subaccount_id]: *required* the subaccount id :returns Order[]: a list of `order structures ` """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate') if paginate: return self.fetch_paginated_call_incremental('fetchOrders', symbol, since, limit, params, 'page', 500) isTrigger = self.safe_bool_2(params, 'trigger', 'stop', False) params = self.omit(params, ['trigger', 'stop']) subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('fetchOrders', params) request: dict = { 'subaccount_id': subaccountId, } market: Market = None if symbol is not None: market = self.market(symbol) request['instrument_name'] = market['id'] if limit is not None: request['page_size'] = limit else: request['page_size'] = 500 if isTrigger: request['status'] = 'untriggered' response = self.privatePostGetOrders(self.extend(request, params)) # # { # "result": { # "subaccount_id": 130837, # "orders": [ # { # "subaccount_id": 130837, # "order_id": "63a80cb8-387b-472b-a838-71cd9513c365", # "instrument_name": "BTC-PERP", # "direction": "buy", # "label": "test1234", # "quote_id": null, # "creation_timestamp": 1737551053207, # "last_update_timestamp": 1737551053207, # "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": "open", # "max_fee": "211", # "signature_expiry_sec": 1737551652765, # "nonce": 1737551052765, # "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A", # "signature": "0x35535ccb1bcad509ecc435c79e966174db6403fc9aeee1e237d08a941014c57b59279dfe4be39e081f9921a53eaad59cb2a151d9f52f2d05fc47e6280254952e1c", # "cancel_reason": "", # "mmp": False, # "is_transfer": False, # "replaced_order_id": null, # "trigger_type": null, # "trigger_price_type": null, # "trigger_price": null, # "trigger_reject_message": null # } # ], # "pagination": { # "num_pages": 1, # "count": 1 # } # }, # "id": "e5a88d4f-7ac7-40cd-aec9-e0e8152b8b92" # } # data = self.safe_value(response, 'result') page = self.safe_integer(params, 'page') if page is not None: pagination = self.safe_dict(data, 'pagination') currentPage = self.safe_integer(pagination, 'num_pages') if page > currentPage: return [] orders = self.safe_list(data, 'orders') return self.parse_orders(orders, market, since, limit) def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetches information on multiple orders made by the user https://docs.derive.xyz/reference/post_private-get-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 :param boolean [params.paginate]: set to True if you want to fetch orders with pagination :returns Order[]: a list of `order structures ` """ self.load_markets() extendedParams = self.extend(params, {'status': 'open'}) return self.fetch_orders(symbol, since, limit, extendedParams) def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetches information on multiple orders made by the user https://docs.derive.xyz/reference/post_private-get-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 :param boolean [params.paginate]: set to True if you want to fetch orders with pagination :returns Order[]: a list of `order structures ` """ self.load_markets() extendedParams = self.extend(params, {'status': 'filled'}) return self.fetch_orders(symbol, since, limit, extendedParams) def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetches information on multiple canceled orders made by the user https://docs.derive.xyz/reference/post_private-get-orders :param str symbol: unified market symbol of the market the 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 boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :returns dict[]: a list of `order structures ` """ self.load_markets() extendedParams = self.extend(params, {'status': 'cancelled'}) return self.fetch_orders(symbol, since, limit, extendedParams) def parse_time_in_force(self, timeInForce: Str): timeInForces: dict = { 'ioc': 'IOC', 'fok': 'FOK', 'gtc': 'GTC', 'post_only': 'PO', } return self.safe_string(timeInForces, timeInForce, None) def parse_order_status(self, status: Str): if status is not None: statuses: dict = { 'open': 'open', 'untriggered': 'open', 'filled': 'closed', 'cancelled': 'canceled', 'expired': 'rejected', } return self.safe_string(statuses, status, status) return status def parse_order(self, rawOrder: dict, market: Market = None) -> Order: # # { # "subaccount_id": 130837, # "nonce": 1736923517552, # "module": "0x87F2863866D85E3192a35A73b388BD625D83f2be", # "expiry": 86400, # "owner": "0x108b9aF9279a525b8A8AeAbE7AC2bA925Bc50075", # "signer": "0x108b9aF9279a525b8A8AeAbE7AC2bA925Bc50075", # "signature": "0xaa4f42b2f3da33c668fa703ea872d4c3a6b55aca66025b5119e3bebb6679fe2e2794638db51dcace21fc39a498047835994f07eb59f311bb956ce057e66793d1c", # "data": { # "asset": "0xAFB6Bb95cd70D5367e2C39e9dbEb422B9815339D", # "sub_id": 0, # "limit_price": "10000", # "desired_amount": "0.001", # "worst_fee": "0", # "recipient_id": 130837, # "is_bid": True, # "trade_id": "" # } # } # { # "subaccount_id": 130837, # "order_id": "96349ebb-7d46-43ae-81c7-7ab390444293", # "instrument_name": "BTC-PERP", # "direction": "buy", # "label": "", # "quote_id": null, # "creation_timestamp": 1737467576257, # "last_update_timestamp": 1737467576257, # "limit_price": "10000", # "amount": "0.01", # "filled_amount": "0", # "average_price": "0", # "order_fee": "0", # "order_type": "limit", # "time_in_force": "gtc", # "order_status": "open", # "max_fee": "210", # "signature_expiry_sec": 1737468175989, # "nonce": 1737467575989, # "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A", # "signature": "0xd1ca49df1fa06bd805bb59b132ff6c0de29bf973a3e01705abe0a01cc956e4945ed9eb99ab68f3df4c037908113cac5a5bfc3a954a0b7103cdab285962fa6a51c", # "cancel_reason": "", # "mmp": False, # "is_transfer": False, # "replaced_order_id": null, # "trigger_type": null, # "trigger_price_type": null, # "trigger_price": null, # "trigger_reject_message": null # } order = self.safe_dict(rawOrder, 'data') if order is None: order = rawOrder timestamp = self.safe_integer_2(rawOrder, 'creation_timestamp', 'nonce') orderId = self.safe_string(order, 'order_id') marketId = self.safe_string(order, 'instrument_name') if marketId is not None: market = self.safe_market(marketId, market) symbol = market['symbol'] price = self.safe_string(order, 'limit_price') average = self.safe_string(order, 'average_price') amount = self.safe_string(order, 'desired_amount') filled = self.safe_string(order, 'filled_amount') fee = self.safe_string(order, 'order_fee') orderType = self.safe_string_lower(order, 'order_type') isBid = self.safe_bool(order, 'is_bid') side = self.safe_string(order, 'direction') if side is None: if isBid: side = 'buy' else: side = 'sell' triggerType = self.safe_string(order, 'trigger_type') stopLossPrice = None takeProfitPrice = None triggerPrice = None if triggerType is not None: triggerPrice = self.safe_string(order, 'trigger_price') if triggerType == 'stoploss': stopLossPrice = triggerPrice else: takeProfitPrice = triggerPrice lastUpdateTimestamp = self.safe_integer(rawOrder, 'last_update_timestamp') status = self.safe_string(order, 'order_status') timeInForce = self.safe_string(order, 'time_in_force') return self.safe_order({ 'id': orderId, 'clientOrderId': self.safe_string(order, 'label'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': None, 'lastUpdateTimestamp': lastUpdateTimestamp, 'status': self.parse_order_status(status), 'symbol': symbol, 'type': orderType, 'timeInForce': self.parse_time_in_force(timeInForce), 'postOnly': None, # handled in safeOrder 'reduceOnly': self.safe_bool(order, 'reduce_only'), 'side': side, 'price': price, 'triggerPrice': triggerPrice, 'takeProfitPrice': takeProfitPrice, 'stopLossPrice': stopLossPrice, 'average': average, 'amount': amount, 'filled': filled, 'remaining': None, 'cost': None, 'trades': None, 'fee': { 'cost': fee, 'currency': 'USDC', }, 'info': order, }, market) def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetch all the trades made from a single order https://docs.derive.xyz/reference/post_private-get-trade-history :param str id: order id :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trades 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 ` """ self.load_markets() subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('fetchOrderTrades', params) request: dict = { 'order_id': id, 'subaccount_id': subaccountId, } market: Market = None if symbol is not None: market = self.market(symbol) request['instrument_name'] = market['id'] if limit is not None: request['page_size'] = limit if since is not None: request['from_timestamp'] = since response = self.privatePostGetTradeHistory(self.extend(request, params)) # # { # "result": { # "subaccount_id": 130837, # "trades": [ # { # "subaccount_id": 130837, # "order_id": "30c48194-8d48-43ac-ad00-0d5ba29eddc9", # "instrument_name": "BTC-PERP", # "direction": "sell", # "label": "test1234", # "quote_id": null, # "trade_id": "f8a30740-488c-4c2d-905d-e17057bafde1", # "timestamp": 1738065303708, # "mark_price": "102740.137375457314192317", # "index_price": "102741.553409299981533184", # "trade_price": "102700.6", # "trade_amount": "0.01", # "liquidity_role": "taker", # "realized_pnl": "0", # "realized_pnl_excl_fees": "0", # "is_transfer": False, # "tx_status": "settled", # "trade_fee": "1.127415534092999815", # "tx_hash": "0xc55df1f07330faf86579bd8a6385391fbe9e73089301149d8550e9d29c9ead74", # "transaction_id": "e18b9426-3fa5-41bb-99d3-8b54fb4d51bb" # } # ], # "pagination": { # "num_pages": 1, # "count": 1 # } # }, # "id": "a16f798c-a121-44e2-b77e-c38a063f8a99" # } # result = self.safe_dict(response, 'result', {}) trades = self.safe_list(result, 'trades', []) return self.parse_trades(trades, market, since, limit, params) def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetch all trades made by the user https://docs.derive.xyz/reference/post_private-get-trade-history :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trades structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.paginate]: set to True if you want to fetch trades with pagination :param str [params.subaccount_id]: *required* the subaccount id :returns Trade[]: a list of `trade structures ` """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate') if paginate: return self.fetch_paginated_call_incremental('fetchMyTrades', symbol, since, limit, params, 'page', 500) subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('fetchMyTrades', params) request: dict = { 'subaccount_id': subaccountId, } market: Market = None if symbol is not None: market = self.market(symbol) request['instrument_name'] = market['id'] if limit is not None: request['page_size'] = limit if since is not None: request['from_timestamp'] = since response = self.privatePostGetTradeHistory(self.extend(request, params)) # # { # "result": { # "subaccount_id": 130837, # "trades": [ # { # "subaccount_id": 130837, # "order_id": "30c48194-8d48-43ac-ad00-0d5ba29eddc9", # "instrument_name": "BTC-PERP", # "direction": "sell", # "label": "test1234", # "quote_id": null, # "trade_id": "f8a30740-488c-4c2d-905d-e17057bafde1", # "timestamp": 1738065303708, # "mark_price": "102740.137375457314192317", # "index_price": "102741.553409299981533184", # "trade_price": "102700.6", # "trade_amount": "0.01", # "liquidity_role": "taker", # "realized_pnl": "0", # "realized_pnl_excl_fees": "0", # "is_transfer": False, # "tx_status": "settled", # "trade_fee": "1.127415534092999815", # "tx_hash": "0xc55df1f07330faf86579bd8a6385391fbe9e73089301149d8550e9d29c9ead74", # "transaction_id": "e18b9426-3fa5-41bb-99d3-8b54fb4d51bb" # } # ], # "pagination": { # "num_pages": 1, # "count": 1 # } # }, # "id": "a16f798c-a121-44e2-b77e-c38a063f8a99" # } # result = self.safe_dict(response, 'result', {}) page = self.safe_integer(params, 'page') if page is not None: pagination = self.safe_dict(result, 'pagination') currentPage = self.safe_integer(pagination, 'num_pages') if page > currentPage: return [] trades = self.safe_list(result, 'trades', []) return self.parse_trades(trades, market, since, limit, params) def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]: """ fetch all open positions https://docs.derive.xyz/reference/post_private-get-positions :param str[] [symbols]: not used by kraken fetchPositions() :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 `position structure ` """ self.load_markets() subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('fetchPositions', params) request: dict = { 'subaccount_id': subaccountId, } params = self.omit(params, ['subaccount_id']) response = self.privatePostGetPositions(self.extend(request, params)) # # { # "result": { # "subaccount_id": 130837, # "positions": [ # { # "instrument_type": "perp", # "instrument_name": "BTC-PERP", # "amount": "-0.02", # "average_price": "102632.9105389869500088", # "realized_pnl": "0", # "unrealized_pnl": "-2.6455959784245548835819950103759765625", # "total_fees": "2.255789220260999824", # "average_price_excl_fees": "102745.7", # "realized_pnl_excl_fees": "0", # "unrealized_pnl_excl_fees": "-0.3898067581635550595819950103759765625", # "net_settlements": "-4.032902047219498639", # "cumulative_funding": "-0.004677736347850093", # "pending_funding": "0", # "mark_price": "102765.190337908177752979099750518798828125", # "index_price": "102767.657193800017641472", # "delta": "1", # "gamma": "0", # "vega": "0", # "theta": "0", # "mark_value": "1.38730606879471451975405216217041015625", # "maintenance_margin": "-101.37788426911356509663164615631103515625", # "initial_margin": "-132.2074413704858670826070010662078857421875", # "open_orders_margin": "264.116085900726830004714429378509521484375", # "leverage": "8.6954476205089299495699106539379941746377322586618", # "liquidation_price": "109125.705451984322280623018741607666015625", # "creation_timestamp": 1738065303840 # } # ] # }, # "id": "167350f1-d9fc-41d4-9797-1c78f83fda8e" # } # result = self.safe_dict(response, 'result', {}) positions = self.safe_list(result, 'positions', []) return self.parse_positions(positions, symbols) def parse_position(self, position: dict, market: Market = None): # # { # "instrument_type": "perp", # "instrument_name": "BTC-PERP", # "amount": "-0.02", # "average_price": "102632.9105389869500088", # "realized_pnl": "0", # "unrealized_pnl": "-2.6455959784245548835819950103759765625", # "total_fees": "2.255789220260999824", # "average_price_excl_fees": "102745.7", # "realized_pnl_excl_fees": "0", # "unrealized_pnl_excl_fees": "-0.3898067581635550595819950103759765625", # "net_settlements": "-4.032902047219498639", # "cumulative_funding": "-0.004677736347850093", # "pending_funding": "0", # "mark_price": "102765.190337908177752979099750518798828125", # "index_price": "102767.657193800017641472", # "delta": "1", # "gamma": "0", # "vega": "0", # "theta": "0", # "mark_value": "1.38730606879471451975405216217041015625", # "maintenance_margin": "-101.37788426911356509663164615631103515625", # "initial_margin": "-132.2074413704858670826070010662078857421875", # "open_orders_margin": "264.116085900726830004714429378509521484375", # "leverage": "8.6954476205089299495699106539379941746377322586618", # "liquidation_price": "109125.705451984322280623018741607666015625", # "creation_timestamp": 1738065303840 # } # contract = self.safe_string(position, 'instrument_name') market = self.safe_market(contract, market) size = self.safe_string(position, 'amount') side: Str = None if Precise.string_gt(size, '0'): side = 'long' else: side = 'short' contractSize = self.safe_string(market, 'contractSize') markPrice = self.safe_string(position, 'mark_price') timestamp = self.safe_integer(position, 'creation_timestamp') unrealisedPnl = self.safe_string(position, 'unrealized_pnl') size = Precise.string_abs(size) notional = Precise.string_mul(size, markPrice) return self.safe_position({ 'info': position, 'id': None, 'symbol': self.safe_string(market, 'symbol'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastUpdateTimestamp': None, 'initialMargin': self.safe_string(position, 'initial_margin'), 'initialMarginPercentage': None, 'maintenanceMargin': self.safe_string(position, 'maintenance_margin'), 'maintenanceMarginPercentage': None, 'entryPrice': None, 'notional': self.parse_number(notional), 'leverage': self.safe_number(position, 'leverage'), 'unrealizedPnl': self.parse_number(unrealisedPnl), 'contracts': self.parse_number(size), 'contractSize': self.parse_number(contractSize), 'marginRatio': None, 'liquidationPrice': self.safe_number(position, 'liquidation_price'), 'markPrice': self.parse_number(markPrice), 'lastPrice': None, 'collateral': None, 'marginMode': None, 'side': side, 'percentage': None, 'hedged': None, 'stopLossPrice': None, 'takeProfitPrice': None, }) def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetch the history of funding payments paid and received on self account https://docs.derive.xyz/reference/post_private-get-funding-history :param str [symbol]: unified market symbol :param int [since]: the earliest time in ms to fetch funding history for :param int [limit]: the maximum number of funding history structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :returns dict: a `funding history structure ` """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchFundingHistory', 'paginate') if paginate: return self.fetch_paginated_call_incremental('fetchFundingHistory', symbol, since, limit, params, 'page', 500) subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('fetchFundingHistory', params) request: dict = { 'subaccount_id': subaccountId, } market: Market = None if symbol is not None: market = self.market(symbol) request['instrument_name'] = market['id'] if since is not None: request['start_timestamp'] = since if limit is not None: request['page_size'] = limit response = self.privatePostGetFundingHistory(self.extend(request, params)) # # { # "result": { # "events": [ # { # "instrument_name": "BTC-PERP", # "timestamp": 1738066618272, # "funding": "-0.004677736347850093", # "pnl": "-0.944081615774632967" # }, # { # "instrument_name": "BTC-PERP", # "timestamp": 1738066617964, # "funding": "0", # "pnl": "-0.437556413479249408" # }, # { # "instrument_name": "BTC-PERP", # "timestamp": 1738065307565, # "funding": "0", # "pnl": "-0.39547479770461644" # } # ], # "pagination": { # "num_pages": 1, # "count": 3 # } # }, # "id": "524b817f-2108-467f-8795-511066f4acec" # } # result = self.safe_dict(response, 'result', {}) page = self.safe_integer(params, 'page') if page is not None: pagination = self.safe_dict(result, 'pagination') currentPage = self.safe_integer(pagination, 'num_pages') if page > currentPage: return [] events = self.safe_list(result, 'events', []) return self.parse_incomes(events, market, since, limit) def parse_income(self, income, market: Market = None): # # { # "instrument_name": "BTC-PERP", # "timestamp": 1738065307565, # "funding": "0", # "pnl": "-0.39547479770461644" # } # marketId = self.safe_string(income, 'instrument_name') symbol = self.safe_symbol(marketId, market) rate = self.safe_string(income, 'funding') code = self.safe_currency_code('USDC') timestamp = self.safe_integer(income, 'timestamp') return { 'info': income, 'symbol': symbol, 'code': code, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'id': None, 'amount': None, 'rate': rate, } def fetch_balance(self, params={}) -> Balances: """ query for balance and get the amount of funds available for trading or funds locked in orders https://docs.derive.xyz/reference/post_private-get-all-portfolios :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `balance structure ` """ self.load_markets() deriveWalletAddress = None deriveWalletAddress, params = self.handle_derive_wallet_address('fetchBalance', params) request = { 'wallet': deriveWalletAddress, } response = self.privatePostGetAllPortfolios(self.extend(request, params)) # # { # "result": [{ # "subaccount_id": 130837, # "label": "", # "currency": "all", # "margin_type": "SM", # "is_under_liquidation": False, # "positions_value": "0", # "collaterals_value": "318.0760325000001103035174310207366943359375", # "subaccount_value": "318.0760325000001103035174310207366943359375", # "positions_maintenance_margin": "0", # "positions_initial_margin": "0", # "collaterals_maintenance_margin": "238.557024375000082727638073265552520751953125", # "collaterals_initial_margin": "190.845619500000083235136116854846477508544921875", # "maintenance_margin": "238.557024375000082727638073265552520751953125", # "initial_margin": "190.845619500000083235136116854846477508544921875", # "open_orders_margin": "0", # "projected_margin_change": "0", # "open_orders": [], # "positions": [], # "collaterals": [ # { # "asset_type": "erc20", # "asset_name": "ETH", # "currency": "ETH", # "amount": "0.1", # "mark_price": "3180.760325000000438272", # "mark_value": "318.0760325000001103035174310207366943359375", # "cumulative_interest": "0", # "pending_interest": "0", # "initial_margin": "190.845619500000083235136116854846477508544921875", # "maintenance_margin": "238.557024375000082727638073265552520751953125", # "realized_pnl": "0", # "average_price": "3184.891931", # "unrealized_pnl": "-0.413161", # "total_fees": "0", # "average_price_excl_fees": "3184.891931", # "realized_pnl_excl_fees": "0", # "unrealized_pnl_excl_fees": "-0.413161", # "open_orders_margin": "0", # "creation_timestamp": 1736860533493 # } # ] # }], # "id": "27b9a64e-3379-4ce6-a126-9fb941c4a970" # } # result = self.safe_list(response, 'result') return self.parse_balance(result) def parse_balance(self, response) -> Balances: result: dict = { 'info': response, } for i in range(0, len(response)): subaccount = response[i] collaterals = self.safe_list(subaccount, 'collaterals', []) for j in range(0, len(collaterals)): balance = collaterals[j] code = self.safe_currency_code(self.safe_string(balance, 'currency')) account = self.safe_dict(result, code) if account is None: account = self.account() account['total'] = self.safe_string(balance, 'amount') else: amount = self.safe_string(balance, 'amount') account['total'] = Precise.string_add(account['total'], amount) result[code] = account return self.safe_balance(result) def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]: """ fetch all deposits made to an account https://docs.derive.xyz/reference/post_private-get-deposit-history :param str code: unified currency code :param int [since]: the earliest time in ms to fetch deposits for :param int [limit]: the maximum number of deposits 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 `transaction structures ` """ self.load_markets() subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('fetchDeposits', params) request: dict = { 'subaccount_id': subaccountId, } if since is not None: request['start_timestamp'] = since response = self.privatePostGetDepositHistory(self.extend(request, params)) # # { # "result": { # "events": [ # { # "timestamp": 1736860533599, # "transaction_id": "f2069395-ec00-49f5-925a-87202a5d240f", # "asset": "ETH", # "amount": "0.1", # "tx_status": "settled", # "tx_hash": "0xeda21a315c59302a19c42049b4cef05a10b685302b6cc3edbaf49102d91166d4", # "error_log": {} # } # ] # }, # "id": "ceebc730-22ab-40cd-9941-33ceb2a74389" # } # currency = self.safe_currency(code) result = self.safe_dict(response, 'result', {}) events = self.safe_list(result, 'events') return self.parse_transactions(events, currency, since, limit, params) def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]: """ fetch all withdrawals made from an account https://docs.derive.xyz/reference/post_private-get-withdrawal-history :param str code: unified currency code :param int [since]: the earliest time in ms to fetch withdrawals for :param int [limit]: the maximum number of withdrawals 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 `transaction structures ` """ self.load_markets() subaccountId = None subaccountId, params = self.handle_derive_subaccount_id('fetchWithdrawals', params) request: dict = { 'subaccount_id': subaccountId, } if since is not None: request['start_timestamp'] = since response = self.privatePostGetWithdrawalHistory(self.extend(request, params)) # # { # "result": { # "events": [ # { # "timestamp": 1736860533599, # "transaction_id": "f2069395-ec00-49f5-925a-87202a5d240f", # "asset": "ETH", # "amount": "0.1", # "tx_status": "settled", # "tx_hash": "0xeda21a315c59302a19c42049b4cef05a10b685302b6cc3edbaf49102d91166d4", # "error_log": {} # } # ] # }, # "id": "ceebc730-22ab-40cd-9941-33ceb2a74389" # } # currency = self.safe_currency(code) result = self.safe_dict(response, 'result', {}) events = self.safe_list(result, 'events') return self.parse_transactions(events, currency, since, limit, params) def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction: # # { # "timestamp": 1736860533599, # "transaction_id": "f2069395-ec00-49f5-925a-87202a5d240f", # "asset": "ETH", # "amount": "0.1", # "tx_status": "settled", # "tx_hash": "0xeda21a315c59302a19c42049b4cef05a10b685302b6cc3edbaf49102d91166d4", # "error_log": {} # } # code = self.safe_string(transaction, 'asset') timestamp = self.safe_integer(transaction, 'timestamp') txId = self.safe_string(transaction, 'tx_hash') if txId == '0x0': txId = None return { 'info': transaction, 'id': None, 'txid': txId, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'address': None, 'addressFrom': None, 'addressTo': None, 'tag': None, 'tagFrom': None, 'tagTo': None, 'type': None, 'amount': self.safe_number(transaction, 'amount'), 'currency': code, 'status': self.parse_transaction_status(self.safe_string(transaction, 'tx_status')), 'updated': None, 'comment': None, 'internal': None, 'fee': None, 'network': None, } def parse_transaction_status(self, status: Str): statuses: dict = { 'settled': 'ok', 'reverted': 'failed', } return self.safe_string(statuses, status, status) def handle_derive_subaccount_id(self, methodName: str, params: dict): derivesubAccountId = None derivesubAccountId, params = self.handle_option_and_params(params, methodName, 'subaccount_id') if (derivesubAccountId is not None) and (derivesubAccountId != ''): self.options['subaccount_id'] = derivesubAccountId # saving in options return [derivesubAccountId, params] optionsWallet = self.safe_string(self.options, 'subaccount_id') if optionsWallet is not None: return [optionsWallet, params] raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a subaccount_id parameter inside \'params\' or exchange.options[\'subaccount_id\']=ID.') def handle_derive_wallet_address(self, methodName: str, params: dict): deriveWalletAddress = None deriveWalletAddress, params = self.handle_option_and_params(params, methodName, 'deriveWalletAddress') if (deriveWalletAddress is not None) and (deriveWalletAddress != ''): self.options['deriveWalletAddress'] = deriveWalletAddress # saving in options return [deriveWalletAddress, params] optionsWallet = self.safe_string(self.options, 'deriveWalletAddress') if optionsWallet is not None: return [optionsWallet, params] raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a deriveWalletAddress parameter inside \'params\' or exchange.options[\'deriveWalletAddress\'] = ADDRESS, the address can find in HOME => Developers tab.') def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody): if not response: return None # fallback to default error handler error = self.safe_dict(response, 'error') if error is not None: errorCode = self.safe_string(error, 'code') feedback = self.id + ' ' + self.json(response) self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback) self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback) raise ExchangeError(feedback) return None def sign(self, path, api='public', method='GET', params={}, headers=None, body=None): url = self.urls['api'][api] + '/' + path if method == 'POST': headers = { 'Content-Type': 'application/json', } if api == 'private': now = str(self.milliseconds()) signature = self.sign_message(now, self.privateKey) headers['X-LyraWallet'] = self.safe_string(self.options, 'deriveWalletAddress') headers['X-LyraTimestamp'] = now headers['X-LyraSignature'] = signature body = self.json(params) return {'url': url, 'method': method, 'body': body, 'headers': headers}