Files
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

2573 lines
120 KiB
Python

# -*- 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.async_support.base.exchange import Exchange
from ccxt.abstract.derive import ImplicitAPI
import asyncio
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
async 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 = await self.publicPostGetTime(params)
#
# {
# "result": 1735846536758,
# "id": "f1c03d21-f886-4c5a-9a9d-33dd06f180f0"
# }
#
return self.safe_integer(response, 'result')
async 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 = await 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
async 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 = await asyncio.gather(*[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
async def fetch_spot_markets(self, params={}) -> List[Market]:
request: dict = {
'expired': False,
'instrument_type': 'erc20',
}
response = await self.publicPostGetAllInstruments(self.extend(request, params))
result = self.safe_dict(response, 'result', {})
data = self.safe_list(result, 'instruments', [])
return self.parse_markets(data)
async def fetch_swap_markets(self, params={}) -> List[Market]:
request: dict = {
'expired': False,
'instrument_type': 'perp',
}
response = await self.publicPostGetAllInstruments(self.extend(request, params))
result = self.safe_dict(response, 'result', {})
data = self.safe_list(result, 'instruments', [])
return self.parse_markets(data)
async def fetch_option_markets(self, params={}) -> List[Market]:
request: dict = {
'expired': False,
'instrument_type': 'option',
}
response = await 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,
})
async 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 <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
market = self.market(symbol)
request: dict = {
'instrument_name': market['id'],
}
response = await 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)
async 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 <https://docs.ccxt.com/#/?id=public-trades>`
"""
await 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 = await 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)
async 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 <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
"""
await 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 = await 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)
async 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 <https://docs.ccxt.com/#/?id=funding-rate-structure>`
"""
response = await 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)
async 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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
await 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 = await self.privatePostOrderDebug(self.extend(request, params))
else:
response = await 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
async 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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
await 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 = await 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
async 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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
await 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 = await self.privatePostCancelByLabel(self.extend(request, params))
else:
request['order_id'] = id
if isTrigger:
response = await self.privatePostCancelTriggerOrder(self.extend(request, params))
else:
response = await 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)
async 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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
await 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 = await self.privatePostCancelByInstrument(self.extend(request, params))
else:
response = await 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})]
async 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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate')
if paginate:
return await 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 = await 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)
async 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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
extendedParams = self.extend(params, {'status': 'open'})
return await self.fetch_orders(symbol, since, limit, extendedParams)
async 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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
extendedParams = self.extend(params, {'status': 'filled'})
return await self.fetch_orders(symbol, since, limit, extendedParams)
async 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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
extendedParams = self.extend(params, {'status': 'cancelled'})
return await 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)
async 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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
await 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 = await 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)
async 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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
await self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
if paginate:
return await 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 = await 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)
async 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 <https://docs.ccxt.com/#/?id=position-structure>`
"""
await 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 = await 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,
})
async 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 <https://docs.ccxt.com/#/?id=funding-history-structure>`
"""
await self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchFundingHistory', 'paginate')
if paginate:
return await 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 = await 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,
}
async 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 <https://docs.ccxt.com/#/?id=balance-structure>`
"""
await self.load_markets()
deriveWalletAddress = None
deriveWalletAddress, params = self.handle_derive_wallet_address('fetchBalance', params)
request = {
'wallet': deriveWalletAddress,
}
response = await 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)
async 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 <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
await 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 = await 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)
async 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 <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
await 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 = await 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}