2529 lines
106 KiB
Python
2529 lines
106 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.base.exchange import Exchange
|
|
from ccxt.abstract.paradex import ImplicitAPI
|
|
from ccxt.base.types import Any, Balances, Currency, Greeks, Int, Leverage, MarginMode, Market, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, Trade, Transaction
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.errors import PermissionDenied
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import OperationRejected
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class paradex(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(paradex, self).describe(), {
|
|
'id': 'paradex',
|
|
'name': 'Paradex',
|
|
'countries': [],
|
|
'version': 'v1',
|
|
'rateLimit': 50,
|
|
'certified': False,
|
|
'pro': True,
|
|
'dex': True,
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': False,
|
|
'margin': False,
|
|
'swap': True,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'borrowCrossMargin': False,
|
|
'borrowIsolatedMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelAllOrdersAfter': False,
|
|
'cancelOrder': False,
|
|
'cancelOrders': False,
|
|
'cancelOrdersForSymbols': False,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createMarketBuyOrderWithCost': False,
|
|
'createMarketOrderWithCost': False,
|
|
'createMarketSellOrderWithCost': False,
|
|
'createOrder': True,
|
|
'createOrders': False,
|
|
'createReduceOnlyOrder': False,
|
|
'createStopOrder': True,
|
|
'createTriggerOrder': True,
|
|
'editOrder': False,
|
|
'fetchAccounts': False,
|
|
'fetchAllGreeks': True,
|
|
'fetchBalance': True,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchCanceledOrders': False,
|
|
'fetchClosedOrders': False,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': False,
|
|
'fetchDepositAddress': False,
|
|
'fetchDepositAddresses': False,
|
|
'fetchDeposits': True,
|
|
'fetchDepositWithdrawFee': False,
|
|
'fetchDepositWithdrawFees': False,
|
|
'fetchFundingHistory': False,
|
|
'fetchFundingRate': False,
|
|
'fetchFundingRateHistory': False,
|
|
'fetchFundingRates': False,
|
|
'fetchGreeks': True,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchLedger': False,
|
|
'fetchLeverage': True,
|
|
'fetchLeverageTiers': False,
|
|
'fetchLiquidations': True,
|
|
'fetchMarginMode': True,
|
|
'fetchMarketLeverageTiers': False,
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMyLiquidations': False,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterest': True,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrders': True,
|
|
'fetchOrderTrades': False,
|
|
'fetchPosition': True,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': True,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchStatus': True,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'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': True,
|
|
'setMarginMode': True,
|
|
'setPositionMode': False,
|
|
'transfer': False,
|
|
'withdraw': False,
|
|
},
|
|
'timeframes': {
|
|
'1m': 1,
|
|
'3m': 3,
|
|
'5m': 5,
|
|
'15m': 15,
|
|
'30m': 30,
|
|
'1h': 60,
|
|
},
|
|
'hostname': 'paradex.trade',
|
|
'urls': {
|
|
'logo': 'https://github.com/user-attachments/assets/84628770-784e-4ec4-a759-ec2fbb2244ea',
|
|
'api': {
|
|
'v1': 'https://api.prod.{hostname}/v1',
|
|
},
|
|
'test': {
|
|
'v1': 'https://api.testnet.{hostname}/v1',
|
|
},
|
|
'www': 'https://www.paradex.trade/',
|
|
'doc': 'https://docs.api.testnet.paradex.trade/',
|
|
'fees': 'https://docs.paradex.trade/getting-started/trading-fees',
|
|
'referral': 'https://app.paradex.trade/r/ccxt24',
|
|
},
|
|
'api': {
|
|
'public': {
|
|
'get': {
|
|
'bbo/{market}': 1,
|
|
'funding/data': 1,
|
|
'markets': 1,
|
|
'markets/klines': 1,
|
|
'markets/summary': 1,
|
|
'orderbook/{market}': 1,
|
|
'insurance': 1,
|
|
'referrals/config': 1,
|
|
'system/config': 1,
|
|
'system/state': 1,
|
|
'system/time': 1,
|
|
'trades': 1,
|
|
'vaults': 1,
|
|
'vaults/balance': 1,
|
|
'vaults/config': 1,
|
|
'vaults/history': 1,
|
|
'vaults/positions': 1,
|
|
'vaults/summary': 1,
|
|
'vaults/transfers': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'account': 1,
|
|
'account/info': 1,
|
|
'account/history': 1,
|
|
'account/margin': 1,
|
|
'account/profile': 1,
|
|
'account/subaccounts': 1,
|
|
'balance': 1,
|
|
'fills': 1,
|
|
'funding/payments': 1,
|
|
'positions': 1,
|
|
'tradebusts': 1,
|
|
'transactions': 1,
|
|
'liquidations': 1,
|
|
'orders': 1,
|
|
'orders-history': 1,
|
|
'orders/by_client_id/{client_id}': 1,
|
|
'orders/{order_id}': 1,
|
|
'points_data/{market}/{program}': 1,
|
|
'referrals/qr-code': 1,
|
|
'referrals/summary': 1,
|
|
'transfers': 1,
|
|
'algo/orders': 1,
|
|
'algo/orders-history': 1,
|
|
'algo/orders/{algo_id}': 1,
|
|
'vaults/account-summary': 1,
|
|
},
|
|
'post': {
|
|
'account/margin/{market}': 1,
|
|
'account/profile/max_slippage': 1,
|
|
'account/profile/referral_code': 1,
|
|
'account/profile/username': 1,
|
|
'auth': 1,
|
|
'onboarding': 1,
|
|
'orders': 1,
|
|
'orders/batch': 1,
|
|
'algo/orders': 1,
|
|
'vaults': 1,
|
|
},
|
|
'put': {
|
|
'orders/{order_id}': 1,
|
|
},
|
|
'delete': {
|
|
'orders': 1,
|
|
'orders/by_client_id/{client_id}': 1,
|
|
'orders/{order_id}': 1,
|
|
'algo/orders/{algo_id}': 1,
|
|
},
|
|
},
|
|
},
|
|
'fees': {
|
|
'swap': {
|
|
'taker': self.parse_number('0.0002'),
|
|
'maker': self.parse_number('0.0002'),
|
|
},
|
|
'spot': {
|
|
'taker': self.parse_number('0.0002'),
|
|
'maker': self.parse_number('0.0002'),
|
|
},
|
|
},
|
|
'requiredCredentials': {
|
|
'apiKey': False,
|
|
'secret': False,
|
|
'walletAddress': True,
|
|
'privateKey': True,
|
|
},
|
|
'exceptions': {
|
|
'exact': {
|
|
'VALIDATION_ERROR': AuthenticationError,
|
|
'BINDING_ERROR': OperationRejected,
|
|
'INTERNAL_ERROR': ExchangeError,
|
|
'NOT_FOUND': BadRequest,
|
|
'SERVICE_UNAVAILABLE': ExchangeError,
|
|
'INVALID_REQUEST_PARAMETER': BadRequest,
|
|
'ORDER_ID_NOT_FOUND': InvalidOrder,
|
|
'ORDER_IS_CLOSED': InvalidOrder,
|
|
'ORDER_IS_NOT_OPEN_YET': InvalidOrder,
|
|
'CLIENT_ORDER_ID_NOT_FOUND': InvalidOrder,
|
|
'DUPLICATED_CLIENT_ID': InvalidOrder,
|
|
'INVALID_PRICE_PRECISION': OperationRejected,
|
|
'INVALID_SYMBOL': OperationRejected,
|
|
'INVALID_TOKEN': OperationRejected,
|
|
'INVALID_ETHEREUM_ADDRESS': OperationRejected,
|
|
'INVALID_ETHEREUM_SIGNATURE': OperationRejected,
|
|
'INVALID_STARKNET_ADDRESS': OperationRejected,
|
|
'INVALID_STARKNET_SIGNATURE': OperationRejected,
|
|
'STARKNET_SIGNATURE_VERIFICATION_FAILED': AuthenticationError,
|
|
'BAD_STARKNET_REQUEST': BadRequest,
|
|
'ETHEREUM_SIGNER_MISMATCH': BadRequest,
|
|
'ETHEREUM_HASH_MISMATCH': BadRequest,
|
|
'NOT_ONBOARDED': BadRequest,
|
|
'INVALID_TIMESTAMP': BadRequest,
|
|
'INVALID_SIGNATURE_EXPIRATION': AuthenticationError,
|
|
'ACCOUNT_NOT_FOUND': AuthenticationError,
|
|
'INVALID_ORDER_SIGNATURE': AuthenticationError,
|
|
'PUBLIC_KEY_INVALID': BadRequest,
|
|
'UNAUTHORIZED_ETHEREUM_ADDRESS': BadRequest,
|
|
'ETHEREUM_ADDRESS_ALREADY_ONBOARDED': BadRequest,
|
|
'MARKET_NOT_FOUND': BadRequest,
|
|
'ALLOWLIST_ENTRY_NOT_FOUND': BadRequest,
|
|
'USERNAME_IN_USE': AuthenticationError,
|
|
'GEO_IP_BLOCK': PermissionDenied,
|
|
'ETHEREUM_ADDRESS_BLOCKED': PermissionDenied,
|
|
'PROGRAM_NOT_FOUND': BadRequest,
|
|
'INVALID_DASHBOARD': OperationRejected,
|
|
'MARKET_NOT_OPEN': BadRequest,
|
|
'INVALID_REFERRAL_CODE': OperationRejected,
|
|
'PARENT_ADDRESS_ALREADY_ONBOARDED': BadRequest,
|
|
'INVALID_PARENT_ACCOUNT': OperationRejected,
|
|
'INVALID_VAULT_OPERATOR_CHAIN': OperationRejected,
|
|
'VAULT_OPERATOR_ALREADY_ONBOARDED': OperationRejected,
|
|
'VAULT_NAME_IN_USE': OperationRejected,
|
|
'BATCH_SIZE_OUT_OF_RANGE': OperationRejected,
|
|
'ISOLATED_MARKET_ACCOUNT_MISMATCH': OperationRejected,
|
|
'POINTS_SUMMARY_NOT_FOUND': OperationRejected,
|
|
'-32700': BadRequest, # Parse error
|
|
'-32600': BadRequest, # Invalid request
|
|
'-32601': BadRequest, # Method not found
|
|
'-32602': BadRequest, # Invalid parameterss
|
|
'-32603': ExchangeError, # Internal error
|
|
'100': BadRequest, # Method error
|
|
'40110': AuthenticationError, # Malformed Bearer Token
|
|
'40111': AuthenticationError, # Invalid Bearer Token
|
|
'40112': PermissionDenied, # Geo IP blocked
|
|
},
|
|
'broad': {
|
|
'missing or malformed jwt': AuthenticationError,
|
|
},
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'commonCurrencies': {
|
|
},
|
|
'options': {
|
|
'paradexAccount': None, # add {"privateKey": "copy Paradex Private Key from UI", "publicKey": "used when onboard(optional)", "address": "copy Paradex Address from UI"}
|
|
'broker': 'CCXT',
|
|
},
|
|
'features': {
|
|
'spot': None,
|
|
'forSwap': {
|
|
'sandbox': True,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerDirection': True, # todo
|
|
'triggerPriceType': None,
|
|
'stopLossPrice': False, # todo
|
|
'takeProfitPrice': False, # todo
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': False,
|
|
'PO': True,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'trailing': False,
|
|
'leverage': False,
|
|
'marketBuyByCost': False,
|
|
'marketBuyRequiresPrice': False,
|
|
'selfTradePrevention': True, # todo
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None, # todo
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 100, # todo
|
|
'daysBack': 100000, # todo
|
|
'untilDays': 100000, # todo
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': 100, # todo
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': {
|
|
'marginMode': False,
|
|
'limit': 100,
|
|
'daysBack': 100000, # todo
|
|
'untilDays': 100000, # todo
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchClosedOrders': None, # todo
|
|
'fetchOHLCV': {
|
|
'limit': None, # todo by from/to
|
|
},
|
|
},
|
|
'swap': {
|
|
'linear': {
|
|
'extends': 'forSwap',
|
|
},
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
})
|
|
|
|
def fetch_time(self, params={}) -> Int:
|
|
"""
|
|
fetches the current integer timestamp in milliseconds from the exchange server
|
|
|
|
https://docs.api.testnet.paradex.trade/#get-system-time-unix-milliseconds
|
|
|
|
: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.publicGetSystemTime(params)
|
|
#
|
|
# {
|
|
# "server_time": "1681493415023"
|
|
# }
|
|
#
|
|
return self.safe_integer(response, 'server_time')
|
|
|
|
def fetch_status(self, params={}):
|
|
"""
|
|
the latest known information on the availability of the exchange API
|
|
|
|
https://docs.api.testnet.paradex.trade/#get-system-state
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `status structure <https://docs.ccxt.com/#/?id=exchange-status-structure>`
|
|
"""
|
|
response = self.publicGetSystemState(params)
|
|
#
|
|
# {
|
|
# "status": "ok"
|
|
# }
|
|
#
|
|
status = self.safe_string(response, 'status')
|
|
return {
|
|
'status': 'ok' if (status == 'ok') else 'maintenance',
|
|
'updated': None,
|
|
'eta': None,
|
|
'url': None,
|
|
'info': response,
|
|
}
|
|
|
|
def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for bitget
|
|
|
|
https://docs.api.testnet.paradex.trade/#list-available-markets
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = self.publicGetMarkets(params)
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "symbol": "BODEN-USD-PERP",
|
|
# "base_currency": "BODEN",
|
|
# "quote_currency": "USD",
|
|
# "settlement_currency": "USDC",
|
|
# "order_size_increment": "1",
|
|
# "price_tick_size": "0.00001",
|
|
# "min_notional": "200",
|
|
# "open_at": 1717065600000,
|
|
# "expiry_at": 0,
|
|
# "asset_kind": "PERP",
|
|
# "position_limit": "2000000",
|
|
# "price_bands_width": "0.2",
|
|
# "max_open_orders": 50,
|
|
# "max_funding_rate": "0.05",
|
|
# "delta1_cross_margin_params": {
|
|
# "imf_base": "0.2",
|
|
# "imf_shift": "180000",
|
|
# "imf_factor": "0.00071",
|
|
# "mmf_factor": "0.5"
|
|
# },
|
|
# "price_feed_id": "9LScEHse1ioZt2rUuhwiN6bmYnqpMqvZkQJDNUpxVHN5",
|
|
# "oracle_ewma_factor": "0.14999987905913592",
|
|
# "max_order_size": "520000",
|
|
# "max_funding_rate_change": "0.0005",
|
|
# "max_tob_spread": "0.2"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'results')
|
|
return self.parse_markets(data)
|
|
|
|
def parse_market(self, market: dict) -> Market:
|
|
#
|
|
# {
|
|
# "symbol": "BODEN-USD-PERP",
|
|
# "base_currency": "BODEN",
|
|
# "quote_currency": "USD",
|
|
# "settlement_currency": "USDC",
|
|
# "order_size_increment": "1",
|
|
# "price_tick_size": "0.00001",
|
|
# "min_notional": "200",
|
|
# "open_at": 1717065600000,
|
|
# "expiry_at": 0,
|
|
# "asset_kind": "PERP",
|
|
# "position_limit": "2000000",
|
|
# "price_bands_width": "0.2",
|
|
# "max_open_orders": 50,
|
|
# "max_funding_rate": "0.05",
|
|
# "delta1_cross_margin_params": {
|
|
# "imf_base": "0.2",
|
|
# "imf_shift": "180000",
|
|
# "imf_factor": "0.00071",
|
|
# "mmf_factor": "0.5"
|
|
# },
|
|
# "price_feed_id": "9LScEHse1ioZt2rUuhwiN6bmYnqpMqvZkQJDNUpxVHN5",
|
|
# "oracle_ewma_factor": "0.14999987905913592",
|
|
# "max_order_size": "520000",
|
|
# "max_funding_rate_change": "0.0005",
|
|
# "max_tob_spread": "0.2"
|
|
# }
|
|
#
|
|
# {
|
|
# "symbol":"BTC-USD-96000-C",
|
|
# "base_currency":"BTC",
|
|
# "quote_currency":"USD",
|
|
# "settlement_currency":"USDC",
|
|
# "order_size_increment":"0.001",
|
|
# "price_tick_size":"0.01",
|
|
# "min_notional":"100",
|
|
# "open_at":"1736764200000",
|
|
# "expiry_at":"0",
|
|
# "asset_kind":"PERP_OPTION",
|
|
# "market_kind":"cross",
|
|
# "position_limit":"10",
|
|
# "price_bands_width":"0.05",
|
|
# "iv_bands_width":"0.05",
|
|
# "max_open_orders":"100",
|
|
# "max_funding_rate":"0.02",
|
|
# "option_cross_margin_params":{
|
|
# "imf":{
|
|
# "long_itm":"0.2",
|
|
# "short_itm":"0.15",
|
|
# "short_otm":"0.1",
|
|
# "short_put_cap":"0.5",
|
|
# "premium_multiplier":"1"
|
|
# },
|
|
# "mmf":{
|
|
# "long_itm":"0.1",
|
|
# "short_itm":"0.075",
|
|
# "short_otm":"0.05",
|
|
# "short_put_cap":"0.5",
|
|
# "premium_multiplier":"0.5"
|
|
# }
|
|
# },
|
|
# "price_feed_id":"GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU",
|
|
# "oracle_ewma_factor":"0.20000046249626113",
|
|
# "max_order_size":"2",
|
|
# "max_funding_rate_change":"0.02",
|
|
# "max_tob_spread":"0.2",
|
|
# "interest_rate":"0.0001",
|
|
# "clamp_rate":"0.02",
|
|
# "option_type":"CALL",
|
|
# "strike_price":"96000",
|
|
# "funding_period_hours":"24",
|
|
# "tags":[
|
|
# ]
|
|
# }
|
|
#
|
|
assetKind = self.safe_string(market, 'asset_kind')
|
|
isOption = (assetKind == 'PERP_OPTION')
|
|
type = 'option' if (isOption) else 'swap'
|
|
isSwap = (type == 'swap')
|
|
marketId = self.safe_string(market, 'symbol')
|
|
quoteId = self.safe_string(market, 'quote_currency')
|
|
baseId = self.safe_string(market, 'base_currency')
|
|
quote = self.safe_currency_code(quoteId)
|
|
base = self.safe_currency_code(baseId)
|
|
settleId = self.safe_string(market, 'settlement_currency')
|
|
settle = self.safe_currency_code(settleId)
|
|
symbol = base + '/' + quote + ':' + settle
|
|
expiry = self.safe_integer(market, 'expiry_at')
|
|
optionType = self.safe_string(market, 'option_type')
|
|
strikePrice = self.safe_string(market, 'strike_price')
|
|
takerFee = self.parse_number('0.0003')
|
|
makerFee = self.parse_number('-0.00005')
|
|
if isOption:
|
|
optionTypeSuffix = 'C' if (optionType == 'CALL') else 'P'
|
|
symbol = symbol + '-' + strikePrice + '-' + optionTypeSuffix
|
|
makerFee = self.parse_number('0.0003')
|
|
else:
|
|
expiry = None
|
|
return self.safe_market_structure({
|
|
'id': marketId,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': settle,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': settleId,
|
|
'type': type,
|
|
'spot': False,
|
|
'margin': None,
|
|
'swap': isSwap,
|
|
'future': False,
|
|
'option': isOption,
|
|
'active': self.safe_bool(market, 'enableTrading'),
|
|
'contract': True,
|
|
'linear': True,
|
|
'inverse': False,
|
|
'taker': takerFee,
|
|
'maker': makerFee,
|
|
'contractSize': self.parse_number('1'),
|
|
'expiry': expiry,
|
|
'expiryDatetime': None if (expiry == 0) else self.iso8601(expiry),
|
|
'strike': self.parse_number(strikePrice),
|
|
'optionType': self.safe_string_lower(market, 'option_type'),
|
|
'precision': {
|
|
'amount': self.safe_number(market, 'order_size_increment'),
|
|
'price': self.safe_number(market, 'price_tick_size'),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': None,
|
|
'max': self.safe_number(market, 'max_order_size'),
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': self.safe_number(market, 'min_notional'),
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': None,
|
|
'info': market,
|
|
})
|
|
|
|
def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://docs.api.testnet.paradex.trade/#ohlcv-for-a-symbol
|
|
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str timeframe: the length of time each candle represents
|
|
:param int [since]: timestamp in ms of the earliest candle to fetch
|
|
:param int [limit]: the maximum amount of candles to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest candle to fetch
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'resolution': self.safe_string(self.timeframes, timeframe, timeframe),
|
|
'symbol': market['id'],
|
|
}
|
|
now = self.milliseconds()
|
|
duration = self.parse_timeframe(timeframe)
|
|
until = self.safe_integer_2(params, 'until', 'till', now)
|
|
params = self.omit(params, ['until', 'till'])
|
|
if since is not None:
|
|
request['start_at'] = since
|
|
if limit is not None:
|
|
request['end_at'] = self.sum(since, duration * (limit + 1) * 1000) - 1
|
|
else:
|
|
request['end_at'] = until
|
|
else:
|
|
request['end_at'] = until
|
|
if limit is not None:
|
|
request['start_at'] = until - duration * (limit + 1) * 1000 + 1
|
|
else:
|
|
request['start_at'] = until - duration * 101 * 1000 + 1
|
|
response = self.publicGetMarketsKlines(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "results": [
|
|
# [
|
|
# 1720071900000,
|
|
# 58961.3,
|
|
# 58961.3,
|
|
# 58961.3,
|
|
# 58961.3,
|
|
# 1591
|
|
# ]
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'results', [])
|
|
return self.parse_ohlcvs(data, market, timeframe, since, limit)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# [
|
|
# 1720071900000,
|
|
# 58961.3,
|
|
# 58961.3,
|
|
# 58961.3,
|
|
# 58961.3,
|
|
# 1591
|
|
# ]
|
|
#
|
|
return [
|
|
self.safe_integer(ohlcv, 0),
|
|
self.safe_number(ohlcv, 1),
|
|
self.safe_number(ohlcv, 2),
|
|
self.safe_number(ohlcv, 3),
|
|
self.safe_number(ohlcv, 4),
|
|
self.safe_number(ohlcv, 5),
|
|
]
|
|
|
|
def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
|
|
|
https://docs.api.testnet.paradex.trade/#list-available-markets-summary
|
|
|
|
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
request: dict = {
|
|
'market': 'ALL',
|
|
}
|
|
response = self.publicGetMarketsSummary(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "symbol": "BTC-USD-PERP",
|
|
# "oracle_price": "68465.17449906",
|
|
# "mark_price": "68465.17449906",
|
|
# "last_traded_price": "68495.1",
|
|
# "bid": "68477.6",
|
|
# "ask": "69578.2",
|
|
# "volume_24h": "5815541.397939004",
|
|
# "total_volume": "584031465.525259686",
|
|
# "created_at": 1718170156580,
|
|
# "underlying_price": "67367.37268422",
|
|
# "open_interest": "162.272",
|
|
# "funding_rate": "0.01629574927887",
|
|
# "price_change_rate_24h": "0.009032"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'results', [])
|
|
return self.parse_tickers(data, symbols)
|
|
|
|
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.api.testnet.paradex.trade/#list-available-markets-summary
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'market': market['id'],
|
|
}
|
|
response = self.publicGetMarketsSummary(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "symbol": "BTC-USD-PERP",
|
|
# "oracle_price": "68465.17449906",
|
|
# "mark_price": "68465.17449906",
|
|
# "last_traded_price": "68495.1",
|
|
# "bid": "68477.6",
|
|
# "ask": "69578.2",
|
|
# "volume_24h": "5815541.397939004",
|
|
# "total_volume": "584031465.525259686",
|
|
# "created_at": 1718170156580,
|
|
# "underlying_price": "67367.37268422",
|
|
# "open_interest": "162.272",
|
|
# "funding_rate": "0.01629574927887",
|
|
# "price_change_rate_24h": "0.009032"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'results', [])
|
|
ticker = self.safe_dict(data, 0, {})
|
|
return self.parse_ticker(ticker, market)
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USD-PERP",
|
|
# "oracle_price": "68465.17449906",
|
|
# "mark_price": "68465.17449906",
|
|
# "last_traded_price": "68495.1",
|
|
# "bid": "68477.6",
|
|
# "ask": "69578.2",
|
|
# "volume_24h": "5815541.397939004",
|
|
# "total_volume": "584031465.525259686",
|
|
# "created_at": 1718170156581,
|
|
# "underlying_price": "67367.37268422",
|
|
# "open_interest": "162.272",
|
|
# "funding_rate": "0.01629574927887",
|
|
# "price_change_rate_24h": "0.009032"
|
|
# }
|
|
#
|
|
percentage = self.safe_string(ticker, 'price_change_rate_24h')
|
|
if percentage is not None:
|
|
percentage = Precise.string_mul(percentage, '100')
|
|
last = self.safe_string(ticker, 'last_traded_price')
|
|
marketId = self.safe_string(ticker, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
timestamp = self.safe_integer(ticker, 'created_at')
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': None,
|
|
'low': None,
|
|
'bid': self.safe_string(ticker, 'bid'),
|
|
'bidVolume': None,
|
|
'ask': self.safe_string(ticker, 'ask'),
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': None,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': percentage,
|
|
'average': None,
|
|
'baseVolume': None,
|
|
'quoteVolume': self.safe_string(ticker, 'volume_24h'),
|
|
'markPrice': self.safe_string(ticker, 'mark_price'),
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
|
|
https://docs.api.testnet.paradex.trade/#get-market-orderbook
|
|
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the maximum amount of order book entries to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {'market': market['id']}
|
|
response = self.publicGetOrderbookMarket(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "market": "BTC-USD-PERP",
|
|
# "seq_no": 14115975,
|
|
# "last_updated_at": 1718172538340,
|
|
# "asks": [
|
|
# [
|
|
# "69578.2",
|
|
# "3.019"
|
|
# ]
|
|
# ],
|
|
# "bids": [
|
|
# [
|
|
# "68477.6",
|
|
# "0.1"
|
|
# ]
|
|
# ]
|
|
# }
|
|
#
|
|
if limit is not None:
|
|
request['depth'] = limit
|
|
timestamp = self.safe_integer(response, 'last_updated_at')
|
|
orderbook = self.parse_order_book(response, market['symbol'], timestamp)
|
|
orderbook['nonce'] = self.safe_integer(response, 'seq_no')
|
|
return orderbook
|
|
|
|
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.api.testnet.paradex.trade/#trade-tape
|
|
|
|
: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
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchTrades', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_cursor('fetchTrades', symbol, since, limit, params, 'next', 'cursor', None, 100)
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'market': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['page_size'] = limit
|
|
if since is not None:
|
|
request['start_at'] = since
|
|
request, params = self.handle_until_option('end_at', request, params)
|
|
response = self.publicGetTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "next": "...",
|
|
# "prev": "...",
|
|
# "results": [
|
|
# {
|
|
# "id": "1718154353750201703989430001",
|
|
# "market": "BTC-USD-PERP",
|
|
# "side": "BUY",
|
|
# "size": "0.026",
|
|
# "price": "69578.2",
|
|
# "created_at": 1718154353750,
|
|
# "trade_type": "FILL"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
trades = self.safe_list(response, 'results', [])
|
|
for i in range(0, len(trades)):
|
|
trades[i]['next'] = self.safe_string(response, 'next')
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# fetchTrades(public)
|
|
#
|
|
# {
|
|
# "id": "1718154353750201703989430001",
|
|
# "market": "BTC-USD-PERP",
|
|
# "side": "BUY",
|
|
# "size": "0.026",
|
|
# "price": "69578.2",
|
|
# "created_at": 1718154353750,
|
|
# "trade_type": "FILL"
|
|
# }
|
|
#
|
|
# fetchMyTrades(private)
|
|
#
|
|
# {
|
|
# "id": "1718947571560201703986670001",
|
|
# "side": "BUY",
|
|
# "liquidity": "TAKER",
|
|
# "market": "BTC-USD-PERP",
|
|
# "order_id": "1718947571540201703992340000",
|
|
# "price": "64852.9",
|
|
# "size": "0.01",
|
|
# "fee": "0.1945587",
|
|
# "fee_currency": "USDC",
|
|
# "created_at": 1718947571569,
|
|
# "remaining_size": "0",
|
|
# "client_id": "",
|
|
# "fill_type": "FILL"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(trade, 'market')
|
|
market = self.safe_market(marketId, market)
|
|
id = self.safe_string(trade, 'id')
|
|
timestamp = self.safe_integer(trade, 'created_at')
|
|
priceString = self.safe_string(trade, 'price')
|
|
amountString = self.safe_string(trade, 'size')
|
|
side = self.safe_string_lower(trade, 'side')
|
|
liability = self.safe_string_lower(trade, 'liquidity', 'taker')
|
|
isTaker = liability == 'taker'
|
|
takerOrMaker = 'taker' if (isTaker) else 'maker'
|
|
currencyId = self.safe_string(trade, 'fee_currency')
|
|
code = self.safe_currency_code(currencyId)
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'id': id,
|
|
'order': self.safe_string(trade, 'order_id'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': market['symbol'],
|
|
'type': None,
|
|
'takerOrMaker': takerOrMaker,
|
|
'side': side,
|
|
'price': priceString,
|
|
'amount': amountString,
|
|
'cost': None,
|
|
'fee': {
|
|
'cost': self.safe_string(trade, 'fee'),
|
|
'currency': code,
|
|
'rate': None,
|
|
},
|
|
}, market)
|
|
|
|
def fetch_open_interest(self, symbol: str, params={}):
|
|
"""
|
|
retrieves the open interest of a contract trading pair
|
|
|
|
https://docs.api.testnet.paradex.trade/#list-available-markets-summary
|
|
|
|
:param str symbol: unified CCXT market symbol
|
|
:param dict [params]: exchange specific parameters
|
|
:returns dict} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure:
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
if not market['contract']:
|
|
raise BadRequest(self.id + ' fetchOpenInterest() supports contract markets only')
|
|
request: dict = {
|
|
'market': market['id'],
|
|
}
|
|
response = self.publicGetMarketsSummary(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "symbol": "BTC-USD-PERP",
|
|
# "oracle_price": "68465.17449906",
|
|
# "mark_price": "68465.17449906",
|
|
# "last_traded_price": "68495.1",
|
|
# "bid": "68477.6",
|
|
# "ask": "69578.2",
|
|
# "volume_24h": "5815541.397939004",
|
|
# "total_volume": "584031465.525259686",
|
|
# "created_at": 1718170156580,
|
|
# "underlying_price": "67367.37268422",
|
|
# "open_interest": "162.272",
|
|
# "funding_rate": "0.01629574927887",
|
|
# "price_change_rate_24h": "0.009032"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'results', [])
|
|
interest = self.safe_dict(data, 0, {})
|
|
return self.parse_open_interest(interest, market)
|
|
|
|
def parse_open_interest(self, interest, market: Market = None):
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USD-PERP",
|
|
# "oracle_price": "68465.17449904",
|
|
# "mark_price": "68465.17449906",
|
|
# "last_traded_price": "68495.1",
|
|
# "bid": "68477.6",
|
|
# "ask": "69578.2",
|
|
# "volume_24h": "5815541.397939004",
|
|
# "total_volume": "584031465.525259686",
|
|
# "created_at": 1718170156580,
|
|
# "underlying_price": "67367.37268422",
|
|
# "open_interest": "162.272",
|
|
# "funding_rate": "0.01629574927887",
|
|
# "price_change_rate_24h": "0.009032"
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(interest, 'created_at')
|
|
marketId = self.safe_string(interest, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
return self.safe_open_interest({
|
|
'symbol': symbol,
|
|
'openInterestAmount': self.safe_string(interest, 'open_interest'),
|
|
'openInterestValue': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'info': interest,
|
|
}, market)
|
|
|
|
def hash_message(self, message):
|
|
return '0x' + self.hash(message, 'keccak', 'hex')
|
|
|
|
def sign_hash(self, hash, privateKey):
|
|
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 get_system_config(self):
|
|
cachedConfig: dict = self.safe_dict(self.options, 'systemConfig')
|
|
if cachedConfig is not None:
|
|
return cachedConfig
|
|
response = self.publicGetSystemConfig()
|
|
#
|
|
# {
|
|
# "starknet_gateway_url": "https://potc-testnet-sepolia.starknet.io",
|
|
# "starknet_fullnode_rpc_url": "https://pathfinder.api.testnet.paradex.trade/rpc/v0_7",
|
|
# "starknet_chain_id": "PRIVATE_SN_POTC_SEPOLIA",
|
|
# "block_explorer_url": "https://voyager.testnet.paradex.trade/",
|
|
# "paraclear_address": "0x286003f7c7bfc3f94e8f0af48b48302e7aee2fb13c23b141479ba00832ef2c6",
|
|
# "paraclear_decimals": 8,
|
|
# "paraclear_account_proxy_hash": "0x3530cc4759d78042f1b543bf797f5f3d647cde0388c33734cf91b7f7b9314a9",
|
|
# "paraclear_account_hash": "0x41cb0280ebadaa75f996d8d92c6f265f6d040bb3ba442e5f86a554f1765244e",
|
|
# "oracle_address": "0x2c6a867917ef858d6b193a0ff9e62b46d0dc760366920d631715d58baeaca1f",
|
|
# "bridged_tokens": [
|
|
# {
|
|
# "name": "TEST USDC",
|
|
# "symbol": "USDC",
|
|
# "decimals": 6,
|
|
# "l1_token_address": "0x29A873159D5e14AcBd63913D4A7E2df04570c666",
|
|
# "l1_bridge_address": "0x8586e05adc0C35aa11609023d4Ae6075Cb813b4C",
|
|
# "l2_token_address": "0x6f373b346561036d98ea10fb3e60d2f459c872b1933b50b21fe6ef4fda3b75e",
|
|
# "l2_bridge_address": "0x46e9237f5408b5f899e72125dd69bd55485a287aaf24663d3ebe00d237fc7ef"
|
|
# }
|
|
# ],
|
|
# "l1_core_contract_address": "0x582CC5d9b509391232cd544cDF9da036e55833Af",
|
|
# "l1_operator_address": "0x11bACdFbBcd3Febe5e8CEAa75E0Ef6444d9B45FB",
|
|
# "l1_chain_id": "11155111",
|
|
# "liquidation_fee": "0.2"
|
|
# }
|
|
#
|
|
self.options['systemConfig'] = response
|
|
return response
|
|
|
|
def prepare_paradex_domain(self, l1=False):
|
|
systemConfig = self.get_system_config()
|
|
if l1 is True:
|
|
l1D = {
|
|
'name': 'Paradex',
|
|
'chainId': systemConfig['l1_chain_id'],
|
|
'version': '1',
|
|
}
|
|
return l1D
|
|
domain = {
|
|
'name': 'Paradex',
|
|
'chainId': systemConfig['starknet_chain_id'],
|
|
'version': 1,
|
|
}
|
|
return domain
|
|
|
|
def retrieve_account(self):
|
|
cachedAccount: dict = self.safe_dict(self.options, 'paradexAccount')
|
|
if cachedAccount is not None:
|
|
return cachedAccount
|
|
self.check_required_credentials()
|
|
systemConfig = self.get_system_config()
|
|
domain = self.prepare_paradex_domain(True)
|
|
messageTypes = {
|
|
'Constant': [
|
|
{'name': 'action', 'type': 'string'},
|
|
],
|
|
}
|
|
message = {
|
|
'action': 'STARK Key',
|
|
}
|
|
msg = self.eth_encode_structured_data(domain, messageTypes, message)
|
|
signature = self.sign_message(msg, self.privateKey)
|
|
account = self.retrieve_stark_account(
|
|
signature,
|
|
systemConfig['paraclear_account_hash'],
|
|
systemConfig['paraclear_account_proxy_hash']
|
|
)
|
|
self.options['paradexAccount'] = account
|
|
return account
|
|
|
|
def onboarding(self, params={}):
|
|
account = self.retrieve_account()
|
|
req = {
|
|
'action': 'Onboarding',
|
|
}
|
|
domain = self.prepare_paradex_domain()
|
|
messageTypes = {
|
|
'Constant': [
|
|
{'name': 'action', 'type': 'felt'},
|
|
],
|
|
}
|
|
msg = self.starknet_encode_structured_data(domain, messageTypes, req, account['address'])
|
|
signature = self.starknet_sign(msg, account['privateKey'])
|
|
params['signature'] = signature
|
|
params['account'] = account['address']
|
|
params['public_key'] = account['publicKey']
|
|
response = self.privatePostOnboarding(params)
|
|
return response
|
|
|
|
def authenticate_rest(self, params={}):
|
|
cachedToken = self.safe_string(self.options, 'authToken')
|
|
now = self.nonce()
|
|
if cachedToken is not None:
|
|
cachedExpires = self.safe_integer(self.options, 'expires')
|
|
if now < cachedExpires:
|
|
return cachedToken
|
|
account = self.retrieve_account()
|
|
# https://docs.paradex.trade/api-reference/general-information/authentication
|
|
expires = now + 180
|
|
req = {
|
|
'method': 'POST',
|
|
'path': '/v1/auth',
|
|
'body': '',
|
|
'timestamp': now,
|
|
'expiration': expires,
|
|
}
|
|
domain = self.prepare_paradex_domain()
|
|
messageTypes = {
|
|
'Request': [
|
|
{'name': 'method', 'type': 'felt'},
|
|
{'name': 'path', 'type': 'felt'},
|
|
{'name': 'body', 'type': 'felt'},
|
|
{'name': 'timestamp', 'type': 'felt'},
|
|
{'name': 'expiration', 'type': 'felt'},
|
|
],
|
|
}
|
|
msg = self.starknet_encode_structured_data(domain, messageTypes, req, account['address'])
|
|
signature = self.starknet_sign(msg, account['privateKey'])
|
|
params['signature'] = signature
|
|
params['account'] = account['address']
|
|
params['timestamp'] = req['timestamp']
|
|
params['expiration'] = req['expiration']
|
|
response = self.privatePostAuth(params)
|
|
#
|
|
# {
|
|
# jwt_token: "ooooccxtooootoooootheoooomoonooooo"
|
|
# }
|
|
#
|
|
token = self.safe_string(response, 'jwt_token')
|
|
self.options['authToken'] = token
|
|
self.options['expires'] = expires
|
|
return token
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# {
|
|
# "account": "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
|
|
# "avg_fill_price": "26000",
|
|
# "client_id": "x1234",
|
|
# "cancel_reason": "NOT_ENOUGH_MARGIN",
|
|
# "created_at": 1681493746016,
|
|
# "flags": [
|
|
# "REDUCE_ONLY"
|
|
# ],
|
|
# "id": "123456",
|
|
# "instruction": "GTC",
|
|
# "last_updated_at": 1681493746016,
|
|
# "market": "BTC-USD-PERP",
|
|
# "price": "26000",
|
|
# "published_at": 1681493746016,
|
|
# "received_at": 1681493746016,
|
|
# "remaining_size": "0",
|
|
# "seq_no": 1681471234972000000,
|
|
# "side": "BUY",
|
|
# "size": "0.05",
|
|
# "status": "NEW",
|
|
# "stp": "EXPIRE_MAKER",
|
|
# "timestamp": 1681493746016,
|
|
# "trigger_price": "26000",
|
|
# "type": "MARKET"
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(order, 'created_at')
|
|
orderId = self.safe_string(order, 'id')
|
|
clientOrderId = self.omit_zero(self.safe_string(order, 'client_id'))
|
|
marketId = self.safe_string(order, 'market')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
price = self.safe_string(order, 'price')
|
|
amount = self.safe_string(order, 'size')
|
|
orderType = self.safe_string(order, 'type')
|
|
cancelReason = self.safe_string(order, 'cancel_reason')
|
|
status = self.safe_string(order, 'status')
|
|
if cancelReason is not None:
|
|
if cancelReason == 'NOT_ENOUGH_MARGIN' or cancelReason == 'ORDER_EXCEEDS_POSITION_LIMIT':
|
|
status = 'rejected'
|
|
else:
|
|
status = 'canceled'
|
|
side = self.safe_string_lower(order, 'side')
|
|
average = self.omit_zero(self.safe_string(order, 'avg_fill_price'))
|
|
remaining = self.omit_zero(self.safe_string(order, 'remaining_size'))
|
|
lastUpdateTimestamp = self.safe_integer(order, 'last_updated_at')
|
|
flags = self.safe_list(order, 'flags', [])
|
|
reduceOnly = None
|
|
if 'REDUCE_ONLY' in flags:
|
|
reduceOnly = True
|
|
return self.safe_order({
|
|
'id': orderId,
|
|
'clientOrderId': clientOrderId,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'lastUpdateTimestamp': lastUpdateTimestamp,
|
|
'status': self.parse_order_status(status),
|
|
'symbol': symbol,
|
|
'type': self.parse_order_type(orderType),
|
|
'timeInForce': self.parse_time_in_force(self.safe_string(order, 'instruction')),
|
|
'postOnly': None,
|
|
'reduceOnly': reduceOnly,
|
|
'side': side,
|
|
'price': price,
|
|
'triggerPrice': self.safe_string(order, 'trigger_price'),
|
|
'takeProfitPrice': None,
|
|
'stopLossPrice': None,
|
|
'average': average,
|
|
'amount': amount,
|
|
'filled': None,
|
|
'remaining': remaining,
|
|
'cost': None,
|
|
'trades': None,
|
|
'fee': {
|
|
'cost': None,
|
|
'currency': None,
|
|
},
|
|
'info': order,
|
|
}, market)
|
|
|
|
def parse_time_in_force(self, timeInForce: Str):
|
|
timeInForces: dict = {
|
|
'IOC': 'IOC',
|
|
'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 = {
|
|
'NEW': 'open',
|
|
'UNTRIGGERED': 'open',
|
|
'OPEN': 'open',
|
|
'CLOSED': 'closed',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
return status
|
|
|
|
def parse_order_type(self, type: Str):
|
|
types: dict = {
|
|
'LIMIT': 'limit',
|
|
'MARKET': 'market',
|
|
'STOP_LIMIT': 'limit',
|
|
'STOP_MARKET': 'market',
|
|
}
|
|
return self.safe_string_lower(types, type, type)
|
|
|
|
def convert_short_string(self, str: str):
|
|
# TODO: add stringToBase16 in exchange
|
|
return '0x' + self.binary_to_base16(self.base64_to_binary(self.string_to_base64(str)))
|
|
|
|
def scale_number(self, num: str):
|
|
return Precise.string_mul(num, '100000000')
|
|
|
|
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://docs.api.prod.paradex.trade/#create-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 fullfilled, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param float [params.stopPrice]: alias for triggerPrice
|
|
:param float [params.triggerPrice]: The price a trigger order is triggered at
|
|
:param float [params.stopLossPrice]: the price that a stop loss order is triggered at
|
|
:param float [params.takeProfitPrice]: the price that a take profit order is triggered at
|
|
:param str [params.timeInForce]: "GTC", "IOC", or "POST_ONLY"
|
|
:param bool [params.postOnly]: True or False
|
|
:param bool [params.reduceOnly]: Ensures that the executed order does not flip the opened position.
|
|
:param str [params.clientOrderId]: a unique id for the order
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only')
|
|
orderType = type.upper()
|
|
orderSide = side.upper()
|
|
request: dict = {
|
|
'market': market['id'],
|
|
'side': orderSide,
|
|
'type': orderType, # LIMIT/MARKET/STOP_LIMIT/STOP_MARKET,STOP_LOSS_MARKET,STOP_LOSS_LIMIT,TAKE_PROFIT_MARKET,TAKE_PROFIT_LIMIT
|
|
}
|
|
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
|
|
stopLossPrice = self.safe_string(params, 'stopLossPrice')
|
|
takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
|
|
isMarket = orderType == 'MARKET'
|
|
isTakeProfitOrder = (takeProfitPrice is not None)
|
|
isStopLossOrder = (stopLossPrice is not None)
|
|
isStopOrder = (triggerPrice is not None) or isTakeProfitOrder or isStopLossOrder
|
|
timeInForce = self.safe_string_upper(params, 'timeInForce')
|
|
postOnly = self.is_post_only(isMarket, None, params)
|
|
if not isMarket:
|
|
if postOnly:
|
|
request['instruction'] = 'POST_ONLY'
|
|
elif timeInForce == 'ioc':
|
|
request['instruction'] = 'IOC'
|
|
if price is not None:
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
clientOrderId = self.safe_string_n(params, ['clOrdID', 'clientOrderId', 'client_order_id'])
|
|
if clientOrderId is not None:
|
|
request['client_id'] = clientOrderId
|
|
sizeString = '0'
|
|
stopPrice = None
|
|
if isStopOrder:
|
|
# flags: Reduce_Only must be provided for TPSL orders.
|
|
if isMarket:
|
|
if isStopLossOrder:
|
|
stopPrice = self.price_to_precision(symbol, stopLossPrice)
|
|
reduceOnly = True
|
|
request['type'] = 'STOP_LOSS_MARKET'
|
|
elif isTakeProfitOrder:
|
|
stopPrice = self.price_to_precision(symbol, takeProfitPrice)
|
|
reduceOnly = True
|
|
request['type'] = 'TAKE_PROFIT_MARKET'
|
|
else:
|
|
stopPrice = self.price_to_precision(symbol, triggerPrice)
|
|
sizeString = self.amount_to_precision(symbol, amount)
|
|
request['type'] = 'STOP_MARKET'
|
|
else:
|
|
if isStopLossOrder:
|
|
stopPrice = self.price_to_precision(symbol, stopLossPrice)
|
|
reduceOnly = True
|
|
request['type'] = 'STOP_LOSS_LIMIT'
|
|
elif isTakeProfitOrder:
|
|
stopPrice = self.price_to_precision(symbol, takeProfitPrice)
|
|
reduceOnly = True
|
|
request['type'] = 'TAKE_PROFIT_LIMIT'
|
|
else:
|
|
stopPrice = self.price_to_precision(symbol, triggerPrice)
|
|
sizeString = self.amount_to_precision(symbol, amount)
|
|
request['type'] = 'STOP_LIMIT'
|
|
else:
|
|
sizeString = self.amount_to_precision(symbol, amount)
|
|
if stopPrice is not None:
|
|
request['trigger_price'] = stopPrice
|
|
request['size'] = sizeString
|
|
if reduceOnly:
|
|
request['flags'] = [
|
|
'REDUCE_ONLY',
|
|
]
|
|
params = self.omit(params, ['reduceOnly', 'reduce_only', 'clOrdID', 'clientOrderId', 'client_order_id', 'postOnly', 'timeInForce', 'stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice'])
|
|
account = self.retrieve_account()
|
|
now = self.nonce()
|
|
orderReq = {
|
|
'timestamp': now * 1000,
|
|
'market': self.convert_short_string(request['market']),
|
|
'side': '1' if (orderSide == 'BUY') else '2',
|
|
'orderType': self.convert_short_string(request['type']),
|
|
'size': self.scale_number(request['size']),
|
|
'price': '0' if (isMarket) else self.scale_number(request['price']),
|
|
}
|
|
domain = self.prepare_paradex_domain()
|
|
messageTypes = {
|
|
'Order': [
|
|
{'name': 'timestamp', 'type': 'felt'},
|
|
{'name': 'market', 'type': 'felt'},
|
|
{'name': 'side', 'type': 'felt'},
|
|
{'name': 'orderType', 'type': 'felt'},
|
|
{'name': 'size', 'type': 'felt'},
|
|
{'name': 'price', 'type': 'felt'},
|
|
],
|
|
}
|
|
msg = self.starknet_encode_structured_data(domain, messageTypes, orderReq, account['address'])
|
|
signature = self.starknet_sign(msg, account['privateKey'])
|
|
request['signature'] = signature
|
|
request['signature_timestamp'] = orderReq['timestamp']
|
|
response = self.privatePostOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "account": "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
|
|
# "avg_fill_price": "26000",
|
|
# "cancel_reason": "NOT_ENOUGH_MARGIN",
|
|
# "client_id": "x1234",
|
|
# "created_at": 1681493746016,
|
|
# "flags": [
|
|
# "REDUCE_ONLY"
|
|
# ],
|
|
# "id": "123456",
|
|
# "instruction": "GTC",
|
|
# "last_updated_at": 1681493746016,
|
|
# "market": "BTC-USD-PERP",
|
|
# "price": "26000",
|
|
# "published_at": 1681493746016,
|
|
# "received_at": 1681493746016,
|
|
# "remaining_size": "0",
|
|
# "seq_no": 1681471234972000000,
|
|
# "side": "BUY",
|
|
# "size": "0.05",
|
|
# "status": "NEW",
|
|
# "stp": "EXPIRE_MAKER",
|
|
# "timestamp": 1681493746016,
|
|
# "trigger_price": "26000",
|
|
# "type": "MARKET"
|
|
# }
|
|
#
|
|
order = self.parse_order(response, market)
|
|
return order
|
|
|
|
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://docs.api.prod.paradex.trade/#cancel-order
|
|
https://docs.api.prod.paradex.trade/#cancel-open-order-by-client-order-id
|
|
|
|
: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 str [params.clientOrderId]: a unique id for the order
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
request: dict = {}
|
|
clientOrderId = self.safe_string_n(params, ['clOrdID', 'clientOrderId', 'client_order_id'])
|
|
response = None
|
|
if clientOrderId is not None:
|
|
request['client_id'] = clientOrderId
|
|
response = self.privateDeleteOrdersByClientIdClientId(self.extend(request, params))
|
|
else:
|
|
request['order_id'] = id
|
|
response = self.privateDeleteOrdersOrderId(self.extend(request, params))
|
|
#
|
|
# if success, no response...
|
|
#
|
|
return self.parse_order(response)
|
|
|
|
def cancel_all_orders(self, symbol: Str = None, params={}):
|
|
"""
|
|
cancel all open orders in a market
|
|
|
|
https://docs.api.prod.paradex.trade/#cancel-all-open-orders
|
|
|
|
:param str symbol: unified market symbol of the market to cancel orders in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument')
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'market': market['id'],
|
|
}
|
|
response = self.privateDeleteOrders(self.extend(request, params))
|
|
#
|
|
# if success, no response...
|
|
#
|
|
return [self.safe_order({'info': response})]
|
|
|
|
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://docs.api.prod.paradex.trade/#get-order
|
|
https://docs.api.prod.paradex.trade/#get-order-by-client-id
|
|
|
|
:param str id: the 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 str [params.clientOrderId]: a unique id for the order
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
request: dict = {}
|
|
clientOrderId = self.safe_string_n(params, ['clOrdID', 'clientOrderId', 'client_order_id'])
|
|
params = self.omit(params, ['clOrdID', 'clientOrderId', 'client_order_id'])
|
|
response = None
|
|
if clientOrderId is not None:
|
|
request['client_id'] = clientOrderId
|
|
response = self.privateGetOrdersByClientIdClientId(self.extend(request, params))
|
|
else:
|
|
request['order_id'] = id
|
|
response = self.privateGetOrdersOrderId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": "1718941725080201704028870000",
|
|
# "account": "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3",
|
|
# "market": "BTC-USD-PERP",
|
|
# "side": "SELL",
|
|
# "type": "LIMIT",
|
|
# "size": "10.153",
|
|
# "remaining_size": "10.153",
|
|
# "price": "70784.5",
|
|
# "status": "CLOSED",
|
|
# "created_at": 1718941725082,
|
|
# "last_updated_at": 1718958002991,
|
|
# "timestamp": 1718941724678,
|
|
# "cancel_reason": "USER_CANCELED",
|
|
# "client_id": "",
|
|
# "seq_no": 1718958002991595738,
|
|
# "instruction": "GTC",
|
|
# "avg_fill_price": "",
|
|
# "stp": "EXPIRE_TAKER",
|
|
# "received_at": 1718958510959,
|
|
# "published_at": 1718958510960,
|
|
# "flags": [],
|
|
# "trigger_price": "0"
|
|
# }
|
|
#
|
|
return self.parse_order(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.api.prod.paradex.trade/#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 str [params.side]: 'buy' or 'sell'
|
|
:param boolean [params.paginate]: set to True if you want to fetch orders with pagination
|
|
:param int params['until']: timestamp in ms of the latest order to fetch
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_cursor('fetchOrders', symbol, since, limit, params, 'next', 'cursor', None, 50)
|
|
request: dict = {}
|
|
market: Market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['market'] = market['id']
|
|
if since is not None:
|
|
request['start_at'] = since
|
|
if limit is not None:
|
|
request['page_size'] = limit
|
|
request, params = self.handle_until_option('end_at', request, params)
|
|
response = self.privateGetOrdersHistory(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "next": "eyJmaWx0ZXIiMsIm1hcmtlciI6eyJtYXJrZXIiOiIxNjc1NjUwMDE3NDMxMTAxNjk5N=",
|
|
# "prev": "eyJmaWx0ZXIiOnsiTGltaXQiOjkwfSwidGltZSI6MTY4MTY3OTgzNzk3MTMwOTk1MywibWFya2VyIjp7Im1zMjExMD==",
|
|
# "results": [
|
|
# {
|
|
# "account": "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
|
|
# "avg_fill_price": "26000",
|
|
# "cancel_reason": "NOT_ENOUGH_MARGIN",
|
|
# "client_id": "x1234",
|
|
# "created_at": 1681493746016,
|
|
# "flags": [
|
|
# "REDUCE_ONLY"
|
|
# ],
|
|
# "id": "123456",
|
|
# "instruction": "GTC",
|
|
# "last_updated_at": 1681493746016,
|
|
# "market": "BTC-USD-PERP",
|
|
# "price": "26000",
|
|
# "published_at": 1681493746016,
|
|
# "received_at": 1681493746016,
|
|
# "remaining_size": "0",
|
|
# "seq_no": 1681471234972000000,
|
|
# "side": "BUY",
|
|
# "size": "0.05",
|
|
# "status": "NEW",
|
|
# "stp": "EXPIRE_MAKER",
|
|
# "timestamp": 1681493746016,
|
|
# "trigger_price": "26000",
|
|
# "type": "MARKET"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
orders = self.safe_list(response, 'results', [])
|
|
paginationCursor = self.safe_string(response, 'next')
|
|
ordersLength = len(orders)
|
|
if (paginationCursor is not None) and (ordersLength > 0):
|
|
first = orders[0]
|
|
first['next'] = paginationCursor
|
|
orders[0] = first
|
|
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.api.prod.paradex.trade/#paradex-rest-api-orders
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
request: dict = {}
|
|
market: Market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['market'] = market['id']
|
|
response = self.privateGetOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "account": "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
|
|
# "avg_fill_price": "26000",
|
|
# "client_id": "x1234",
|
|
# "cancel_reason": "NOT_ENOUGH_MARGIN",
|
|
# "created_at": 1681493746016,
|
|
# "flags": [
|
|
# "REDUCE_ONLY"
|
|
# ],
|
|
# "id": "123456",
|
|
# "instruction": "GTC",
|
|
# "last_updated_at": 1681493746016,
|
|
# "market": "BTC-USD-PERP",
|
|
# "price": "26000",
|
|
# "published_at": 1681493746016,
|
|
# "received_at": 1681493746016,
|
|
# "remaining_size": "0",
|
|
# "seq_no": 1681471234972000000,
|
|
# "side": "BUY",
|
|
# "size": "0.05",
|
|
# "status": "NEW",
|
|
# "stp": "EXPIRE_MAKER",
|
|
# "timestamp": 1681493746016,
|
|
# "trigger_price": "26000",
|
|
# "type": "MARKET"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
orders = self.safe_list(response, 'results', [])
|
|
return self.parse_orders(orders, market, since, limit)
|
|
|
|
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.api.prod.paradex.trade/#list-balances
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
response = self.privateGetBalance()
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "token": "USDC",
|
|
# "size": "99980.2382266290601",
|
|
# "last_updated_at": 1718529757240
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'results', [])
|
|
return self.parse_balance(data)
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
result: dict = {'info': response}
|
|
for i in range(0, len(response)):
|
|
balance = self.safe_dict(response, i, {})
|
|
currencyId = self.safe_string(balance, 'token')
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['total'] = self.safe_string(balance, 'size')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://docs.api.prod.paradex.trade/#list-fills
|
|
|
|
: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]: 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)
|
|
:param int [params.until]: the latest time in ms to fetch entries for
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_cursor('fetchMyTrades', symbol, since, limit, params, 'next', 'cursor', None, 100)
|
|
request: dict = {}
|
|
market: Market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['market'] = market['id']
|
|
if limit is not None:
|
|
request['page_size'] = limit
|
|
if since is not None:
|
|
request['start_at'] = since
|
|
request, params = self.handle_until_option('end_at', request, params)
|
|
response = self.privateGetFills(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "next": null,
|
|
# "prev": null,
|
|
# "results": [
|
|
# {
|
|
# "id": "1718947571560201703986670001",
|
|
# "side": "BUY",
|
|
# "liquidity": "TAKER",
|
|
# "market": "BTC-USD-PERP",
|
|
# "order_id": "1718947571540201703992340000",
|
|
# "price": "64852.9",
|
|
# "size": "0.01",
|
|
# "fee": "0.1945587",
|
|
# "fee_currency": "USDC",
|
|
# "created_at": 1718947571569,
|
|
# "remaining_size": "0",
|
|
# "client_id": "",
|
|
# "fill_type": "FILL"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
trades = self.safe_list(response, 'results', [])
|
|
for i in range(0, len(trades)):
|
|
trades[i]['next'] = self.safe_string(response, 'next')
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
def fetch_position(self, symbol: str, params={}):
|
|
"""
|
|
fetch data on an open position
|
|
|
|
https://docs.api.prod.paradex.trade/#list-open-positions
|
|
|
|
:param str symbol: unified market symbol of the market the position is held in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
positions = self.fetch_positions([market['symbol']], params)
|
|
return self.safe_dict(positions, 0, {})
|
|
|
|
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
"""
|
|
fetch all open positions
|
|
|
|
https://docs.api.prod.paradex.trade/#list-open-positions
|
|
|
|
:param str[] [symbols]: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
response = self.privateGetPositions()
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "id": "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3-BTC-USD-PERP",
|
|
# "market": "BTC-USD-PERP",
|
|
# "status": "OPEN",
|
|
# "side": "LONG",
|
|
# "size": "0.01",
|
|
# "average_entry_price": "64839.96053748",
|
|
# "average_entry_price_usd": "64852.9",
|
|
# "realized_pnl": "0",
|
|
# "unrealized_pnl": "-2.39677214",
|
|
# "unrealized_funding_pnl": "-0.11214013",
|
|
# "cost": "648.39960537",
|
|
# "cost_usd": "648.529",
|
|
# "cached_funding_index": "35202.1002351",
|
|
# "last_updated_at": 1718950074249,
|
|
# "last_fill_id": "1718947571560201703986670001",
|
|
# "seq_no": 1718950074249176253,
|
|
# "liquidation_price": ""
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'results', [])
|
|
return self.parse_positions(data, symbols)
|
|
|
|
def parse_position(self, position: dict, market: Market = None):
|
|
#
|
|
# {
|
|
# "id": "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3-BTC-USD-PERP",
|
|
# "market": "BTC-USD-PERP",
|
|
# "status": "OPEN",
|
|
# "side": "LONG",
|
|
# "size": "0.01",
|
|
# "average_entry_price": "64839.96053748",
|
|
# "average_entry_price_usd": "64852.9",
|
|
# "realized_pnl": "0",
|
|
# "unrealized_pnl": "-2.39677214",
|
|
# "unrealized_funding_pnl": "-0.11214013",
|
|
# "cost": "648.39960537",
|
|
# "cost_usd": "648.529",
|
|
# "cached_funding_index": "35202.1002351",
|
|
# "last_updated_at": 1718950074249,
|
|
# "last_fill_id": "1718947571560201703986670001",
|
|
# "seq_no": 1718950074249176253,
|
|
# "liquidation_price": ""
|
|
# }
|
|
#
|
|
marketId = self.safe_string(position, 'market')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
side = self.safe_string_lower(position, 'side')
|
|
quantity = self.safe_string(position, 'size')
|
|
if side != 'long':
|
|
quantity = Precise.string_mul('-1', quantity)
|
|
timestamp = self.safe_integer(position, 'time')
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': self.safe_string(position, 'id'),
|
|
'symbol': symbol,
|
|
'entryPrice': self.safe_string(position, 'average_entry_price'),
|
|
'markPrice': None,
|
|
'notional': None,
|
|
'collateral': self.safe_string(position, 'cost'),
|
|
'unrealizedPnl': self.safe_string(position, 'unrealized_pnl'),
|
|
'side': side,
|
|
'contracts': self.parse_number(quantity),
|
|
'contractSize': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'hedged': None,
|
|
'maintenanceMargin': None,
|
|
'maintenanceMarginPercentage': None,
|
|
'initialMargin': None,
|
|
'initialMarginPercentage': None,
|
|
'leverage': None,
|
|
'liquidationPrice': None,
|
|
'marginRatio': None,
|
|
'marginMode': None,
|
|
'percentage': None,
|
|
})
|
|
|
|
def fetch_liquidations(self, symbol: str, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
retrieves the public liquidations of a trading pair
|
|
|
|
https://docs.api.prod.paradex.trade/#list-liquidations
|
|
|
|
:param str symbol: unified CCXT market symbol
|
|
:param int [since]: the earliest time in ms to fetch liquidations for
|
|
:param int [limit]: the maximum number of liquidation structures to retrieve
|
|
:param dict [params]: exchange specific parameters for the huobi api endpoint
|
|
:param int [params.until]: timestamp in ms of the latest liquidation
|
|
:returns dict: an array of `liquidation structures <https://docs.ccxt.com/#/?id=liquidation-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
request: dict = {}
|
|
if since is not None:
|
|
request['from'] = since
|
|
else:
|
|
request['from'] = 1
|
|
market = self.market(symbol)
|
|
request, params = self.handle_until_option('to', request, params)
|
|
response = self.privateGetLiquidations(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "created_at": 1697213130097,
|
|
# "id": "0x123456789"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'results', [])
|
|
return self.parse_liquidations(data, market, since, limit)
|
|
|
|
def parse_liquidation(self, liquidation, market: Market = None):
|
|
#
|
|
# {
|
|
# "created_at": 1697213130097,
|
|
# "id": "0x123456789"
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(liquidation, 'created_at')
|
|
return self.safe_liquidation({
|
|
'info': liquidation,
|
|
'symbol': None,
|
|
'contracts': None,
|
|
'contractSize': None,
|
|
'price': None,
|
|
'side': None,
|
|
'baseValue': None,
|
|
'quoteValue': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
})
|
|
|
|
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.api.prod.paradex.trade/#paradex-rest-api-transfers
|
|
|
|
: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 int [params.until]: the latest time in ms to fetch entries for
|
|
: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 list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchDeposits', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_cursor('fetchDeposits', code, since, limit, params, 'next', 'cursor', None, 100)
|
|
request: dict = {}
|
|
if limit is not None:
|
|
request['page_size'] = limit
|
|
if since is not None:
|
|
request['start_at'] = since
|
|
request, params = self.handle_until_option('end_at', request, params)
|
|
response = self.privateGetTransfers(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "next": null,
|
|
# "prev": null,
|
|
# "results": [
|
|
# {
|
|
# "id": "1718940471200201703989430000",
|
|
# "account": "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3",
|
|
# "kind": "DEPOSIT",
|
|
# "status": "COMPLETED",
|
|
# "amount": "100000",
|
|
# "token": "USDC",
|
|
# "created_at": 1718940471208,
|
|
# "last_updated_at": 1718941455546,
|
|
# "txn_hash": "0x73a415ca558a97bbdcd1c43e52b45f1e0486a0a84b3bb4958035ad6c59cb866",
|
|
# "external_txn_hash": "",
|
|
# "socialized_loss_factor": ""
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
rows = self.safe_list(response, 'results', [])
|
|
deposits = []
|
|
for i in range(0, len(rows)):
|
|
row = rows[i]
|
|
if row['kind'] == 'DEPOSIT':
|
|
deposits.append(row)
|
|
return self.parse_transactions(deposits, None, since, limit)
|
|
|
|
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.api.prod.paradex.trade/#paradex-rest-api-transfers
|
|
|
|
: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 int [params.until]: the latest time in ms to fetch withdrawals for
|
|
: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 list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchWithdrawals', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_cursor('fetchWithdrawals', code, since, limit, params, 'next', 'cursor', None, 100)
|
|
request: dict = {}
|
|
if limit is not None:
|
|
request['page_size'] = limit
|
|
if since is not None:
|
|
request['start_at'] = since
|
|
request, params = self.handle_until_option('end_at', request, params)
|
|
response = self.privateGetTransfers(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "next": null,
|
|
# "prev": null,
|
|
# "results": [
|
|
# {
|
|
# "id": "1718940471200201703989430000",
|
|
# "account": "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3",
|
|
# "kind": "DEPOSIT",
|
|
# "status": "COMPLETED",
|
|
# "amount": "100000",
|
|
# "token": "USDC",
|
|
# "created_at": 1718940471208,
|
|
# "last_updated_at": 1718941455546,
|
|
# "txn_hash": "0x73a415ca558a97bbdcd1c43e52b45f1e0486a0a84b3bb4958035ad6c59cb866",
|
|
# "external_txn_hash": "",
|
|
# "socialized_loss_factor": ""
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
rows = self.safe_list(response, 'results', [])
|
|
deposits = []
|
|
for i in range(0, len(rows)):
|
|
row = rows[i]
|
|
if row['kind'] == 'WITHDRAWAL':
|
|
deposits.append(row)
|
|
return self.parse_transactions(deposits, None, since, limit)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# fetchDeposits & fetchWithdrawals
|
|
#
|
|
# {
|
|
# "id": "1718940471200201703989430000",
|
|
# "account": "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3",
|
|
# "kind": "DEPOSIT",
|
|
# "status": "COMPLETED",
|
|
# "amount": "100000",
|
|
# "token": "USDC",
|
|
# "created_at": 1718940471208,
|
|
# "last_updated_at": 1718941455546,
|
|
# "txn_hash": "0x73a415ca558a97bbdcd1c43e52b45f1e0486a0a84b3bb4958035ad6c59cb866",
|
|
# "external_txn_hash": "",
|
|
# "socialized_loss_factor": ""
|
|
# }
|
|
#
|
|
id = self.safe_string(transaction, 'id')
|
|
address = self.safe_string(transaction, 'account')
|
|
txid = self.safe_string(transaction, 'txn_hash')
|
|
currencyId = self.safe_string(transaction, 'token')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
timestamp = self.safe_integer(transaction, 'created_at')
|
|
updated = self.safe_integer(transaction, 'last_updated_at')
|
|
type = self.safe_string(transaction, 'kind')
|
|
type = 'deposit' if (type == 'DEPOSIT') else 'withdrawal'
|
|
status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
|
|
amount = self.safe_number(transaction, 'amount')
|
|
return {
|
|
'info': transaction,
|
|
'id': id,
|
|
'txid': txid,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'network': None,
|
|
'address': address,
|
|
'addressTo': address,
|
|
'addressFrom': None,
|
|
'tag': None,
|
|
'tagTo': None,
|
|
'tagFrom': None,
|
|
'type': type,
|
|
'amount': amount,
|
|
'currency': code,
|
|
'status': status,
|
|
'updated': updated,
|
|
'internal': None,
|
|
'comment': None,
|
|
'fee': None,
|
|
}
|
|
|
|
def parse_transaction_status(self, status: Str):
|
|
statuses: dict = {
|
|
'PENDING': 'pending',
|
|
'AVAILABLE': 'pending',
|
|
'COMPLETED': 'ok',
|
|
'FAILED': 'failed',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def fetch_margin_mode(self, symbol: str, params={}) -> MarginMode:
|
|
"""
|
|
fetches the margin mode of a specific symbol
|
|
|
|
https://docs.api.testnet.paradex.trade/#get-account-margin-configuration
|
|
|
|
:param str symbol: unified symbol of the market the order was made in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `margin mode structure <https://docs.ccxt.com/#/?id=margin-mode-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'market': market['id'],
|
|
}
|
|
response = self.privateGetAccountMargin(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "account": "0x6343248026a845b39a8a73fbe9c7ef0a841db31ed5c61ec1446aa9d25e54dbc",
|
|
# "configs": [
|
|
# {
|
|
# "market": "SOL-USD-PERP",
|
|
# "leverage": 50,
|
|
# "margin_type": "CROSS"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
configs = self.safe_list(response, 'configs')
|
|
return self.parse_margin_mode(self.safe_dict(configs, 0), market)
|
|
|
|
def parse_margin_mode(self, rawMarginMode: dict, market=None) -> MarginMode:
|
|
marketId = self.safe_string(rawMarginMode, 'market')
|
|
market = self.safe_market(marketId, market)
|
|
marginMode = self.safe_string_lower(rawMarginMode, 'margin_type')
|
|
return {
|
|
'info': rawMarginMode,
|
|
'symbol': market['symbol'],
|
|
'marginMode': marginMode,
|
|
}
|
|
|
|
def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
|
|
"""
|
|
set margin mode to 'cross' or 'isolated'
|
|
|
|
https://docs.api.testnet.paradex.trade/#set-margin-configuration
|
|
|
|
:param str marginMode: 'cross' or 'isolated'
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param float [params.leverage]: the rate of leverage
|
|
:returns dict: response from the exchange
|
|
"""
|
|
self.check_required_argument('setMarginMode', symbol, 'symbol')
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
market: Market = self.market(symbol)
|
|
leverage: Str = None
|
|
leverage, params = self.handle_option_and_params(params, 'setMarginMode', 'leverage', 1)
|
|
request: dict = {
|
|
'market': market['id'],
|
|
'leverage': leverage,
|
|
'margin_type': self.encode_margin_mode(marginMode),
|
|
}
|
|
return self.privatePostAccountMarginMarket(self.extend(request, params))
|
|
|
|
def fetch_leverage(self, symbol: str, params={}) -> Leverage:
|
|
"""
|
|
fetch the set leverage for a market
|
|
|
|
https://docs.api.testnet.paradex.trade/#get-account-margin-configuration
|
|
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `leverage structure <https://docs.ccxt.com/#/?id=leverage-structure>`
|
|
"""
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'market': market['id'],
|
|
}
|
|
response = self.privateGetAccountMargin(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "account": "0x6343248026a845b39a8a73fbe9c7ef0a841db31ed5c61ec1446aa9d25e54dbc",
|
|
# "configs": [
|
|
# {
|
|
# "market": "SOL-USD-PERP",
|
|
# "leverage": 50,
|
|
# "margin_type": "CROSS"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
configs = self.safe_list(response, 'configs')
|
|
return self.parse_leverage(self.safe_dict(configs, 0), market)
|
|
|
|
def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
|
|
marketId = self.safe_string(leverage, 'market')
|
|
market = self.safe_market(marketId, market)
|
|
marginMode = self.safe_string_lower(leverage, 'margin_type')
|
|
return {
|
|
'info': leverage,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'marginMode': marginMode,
|
|
'longLeverage': self.safe_integer(leverage, 'leverage'),
|
|
'shortLeverage': self.safe_integer(leverage, 'leverage'),
|
|
}
|
|
|
|
def encode_margin_mode(self, mode):
|
|
modes = {
|
|
'cross': 'CROSS',
|
|
'isolated': 'ISOLATED',
|
|
}
|
|
return self.safe_string(modes, mode, mode)
|
|
|
|
def set_leverage(self, leverage: int, symbol: Str = None, params={}):
|
|
"""
|
|
set the level of leverage for a market
|
|
|
|
https://docs.api.testnet.paradex.trade/#set-margin-configuration
|
|
|
|
:param float leverage: the rate of leverage
|
|
:param str [symbol]: unified market symbol(is mandatory for swap markets)
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.marginMode]: 'cross' or 'isolated'
|
|
:returns dict: response from the exchange
|
|
"""
|
|
self.check_required_argument('setLeverage', symbol, 'symbol')
|
|
self.authenticate_rest()
|
|
self.load_markets()
|
|
market: Market = self.market(symbol)
|
|
marginMode: Str = None
|
|
marginMode, params = self.handle_margin_mode_and_params('setLeverage', params, 'cross')
|
|
request: dict = {
|
|
'market': market['id'],
|
|
'leverage': leverage,
|
|
'margin_type': self.encode_margin_mode(marginMode),
|
|
}
|
|
return self.privatePostAccountMarginMarket(self.extend(request, params))
|
|
|
|
def fetch_greeks(self, symbol: str, params={}) -> Greeks:
|
|
"""
|
|
fetches an option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
|
|
|
|
https://docs.api.testnet.paradex.trade/#list-available-markets-summary
|
|
|
|
:param str symbol: unified symbol of the market to fetch greeks for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'market': market['id'],
|
|
}
|
|
response = self.publicGetMarketsSummary(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "symbol": "BTC-USD-114000-P",
|
|
# "mark_price": "10835.66892602",
|
|
# "mark_iv": "0.71781855",
|
|
# "delta": "-0.98726024",
|
|
# "greeks": {
|
|
# "delta": "-0.9872602390817709",
|
|
# "gamma": "0.000004560958862297231",
|
|
# "vega": "227.11344863639806",
|
|
# "rho": "-302.0617972461581",
|
|
# "vanna": "0.06609830491614832",
|
|
# "volga": "925.9501532805552"
|
|
# },
|
|
# "last_traded_price": "10551.5",
|
|
# "bid": "10794.9",
|
|
# "bid_iv": "0.05",
|
|
# "ask": "10887.3",
|
|
# "ask_iv": "0.8783283",
|
|
# "last_iv": "0.05",
|
|
# "volume_24h": "0",
|
|
# "total_volume": "195240.72672261014",
|
|
# "created_at": 1747644009995,
|
|
# "underlying_price": "103164.79162649",
|
|
# "open_interest": "0",
|
|
# "funding_rate": "0.000004464241170536191",
|
|
# "price_change_rate_24h": "0.074915",
|
|
# "future_funding_rate": "0.0001"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'results', [])
|
|
greeks = self.safe_dict(data, 0, {})
|
|
return self.parse_greeks(greeks, market)
|
|
|
|
def fetch_all_greeks(self, symbols: Strings = None, params={}) -> List[Greeks]:
|
|
"""
|
|
fetches all option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
|
|
|
|
https://docs.api.testnet.paradex.trade/#list-available-markets-summary
|
|
|
|
:param str[] [symbols]: unified symbols of the markets to fetch greeks for, all markets are returned if not assigned
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols, None, True, True, True)
|
|
request: dict = {
|
|
'market': 'ALL',
|
|
}
|
|
response = self.publicGetMarketsSummary(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "results": [
|
|
# {
|
|
# "symbol": "BTC-USD-114000-P",
|
|
# "mark_price": "10835.66892602",
|
|
# "mark_iv": "0.71781855",
|
|
# "delta": "-0.98726024",
|
|
# "greeks": {
|
|
# "delta": "-0.9872602390817709",
|
|
# "gamma": "0.000004560958862297231",
|
|
# "vega": "227.11344863639806",
|
|
# "rho": "-302.0617972461581",
|
|
# "vanna": "0.06609830491614832",
|
|
# "volga": "925.9501532805552"
|
|
# },
|
|
# "last_traded_price": "10551.5",
|
|
# "bid": "10794.9",
|
|
# "bid_iv": "0.05",
|
|
# "ask": "10887.3",
|
|
# "ask_iv": "0.8783283",
|
|
# "last_iv": "0.05",
|
|
# "volume_24h": "0",
|
|
# "total_volume": "195240.72672261014",
|
|
# "created_at": 1747644009995,
|
|
# "underlying_price": "103164.79162649",
|
|
# "open_interest": "0",
|
|
# "funding_rate": "0.000004464241170536191",
|
|
# "price_change_rate_24h": "0.074915",
|
|
# "future_funding_rate": "0.0001"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
results = self.safe_list(response, 'results', [])
|
|
return self.parse_all_greeks(results, symbols)
|
|
|
|
def parse_greeks(self, greeks: dict, market: Market = None) -> Greeks:
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USD-114000-P",
|
|
# "mark_price": "10835.66892602",
|
|
# "mark_iv": "0.71781855",
|
|
# "delta": "-0.98726024",
|
|
# "greeks": {
|
|
# "delta": "-0.9872602390817709",
|
|
# "gamma": "0.000004560958862297231",
|
|
# "vega": "227.11344863639806",
|
|
# "rho": "-302.0617972461581",
|
|
# "vanna": "0.06609830491614832",
|
|
# "volga": "925.9501532805552"
|
|
# },
|
|
# "last_traded_price": "10551.5",
|
|
# "bid": "10794.9",
|
|
# "bid_iv": "0.05",
|
|
# "ask": "10887.3",
|
|
# "ask_iv": "0.8783283",
|
|
# "last_iv": "0.05",
|
|
# "volume_24h": "0",
|
|
# "total_volume": "195240.72672261014",
|
|
# "created_at": 1747644009995,
|
|
# "underlying_price": "103164.79162649",
|
|
# "open_interest": "0",
|
|
# "funding_rate": "0.000004464241170536191",
|
|
# "price_change_rate_24h": "0.074915",
|
|
# "future_funding_rate": "0.0001"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(greeks, 'symbol')
|
|
market = self.safe_market(marketId, market, None, 'option')
|
|
symbol = market['symbol']
|
|
timestamp = self.safe_integer(greeks, 'created_at')
|
|
greeksData = self.safe_dict(greeks, 'greeks', {})
|
|
return {
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'delta': self.safe_number(greeksData, 'delta'),
|
|
'gamma': self.safe_number(greeksData, 'gamma'),
|
|
'theta': None,
|
|
'vega': self.safe_number(greeksData, 'vega'),
|
|
'rho': self.safe_number(greeksData, 'rho'),
|
|
'vanna': self.safe_number(greeksData, 'vanna'),
|
|
'volga': self.safe_number(greeksData, 'volga'),
|
|
'bidSize': None,
|
|
'askSize': None,
|
|
'bidImpliedVolatility': self.safe_number(greeks, 'bid_iv'),
|
|
'askImpliedVolatility': self.safe_number(greeks, 'ask_iv'),
|
|
'markImpliedVolatility': self.safe_number(greeks, 'mark_iv'),
|
|
'bidPrice': self.safe_number(greeks, 'bid'),
|
|
'askPrice': self.safe_number(greeks, 'ask'),
|
|
'markPrice': self.safe_number(greeks, 'mark_price'),
|
|
'lastPrice': self.safe_number(greeks, 'last_traded_price'),
|
|
'underlyingPrice': self.safe_number(greeks, 'underlying_price'),
|
|
'info': greeks,
|
|
}
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
url = self.implode_hostname(self.urls['api'][self.version]) + '/' + self.implode_params(path, params)
|
|
query = self.omit(params, self.extract_params(path))
|
|
if api == 'public':
|
|
if query:
|
|
url += '?' + self.urlencode(query)
|
|
elif api == 'private':
|
|
headers = {
|
|
'Accept': 'application/json',
|
|
'PARADEX-PARTNER': self.safe_string(self.options, 'broker', 'CCXT'),
|
|
}
|
|
# TODO: optimize
|
|
if path == 'auth':
|
|
headers['PARADEX-STARKNET-ACCOUNT'] = query['account']
|
|
headers['PARADEX-STARKNET-SIGNATURE'] = query['signature']
|
|
headers['PARADEX-TIMESTAMP'] = str(query['timestamp'])
|
|
headers['PARADEX-SIGNATURE-EXPIRATION'] = str(query['expiration'])
|
|
elif path == 'onboarding':
|
|
headers['PARADEX-ETHEREUM-ACCOUNT'] = self.walletAddress
|
|
headers['PARADEX-STARKNET-ACCOUNT'] = query['account']
|
|
headers['PARADEX-STARKNET-SIGNATURE'] = query['signature']
|
|
headers['PARADEX-TIMESTAMP'] = str(self.nonce())
|
|
headers['Content-Type'] = 'application/json'
|
|
body = self.json({
|
|
'public_key': query['public_key'],
|
|
})
|
|
else:
|
|
token = self.options['authToken']
|
|
headers['Authorization'] = 'Bearer ' + token
|
|
if method == 'POST':
|
|
headers['Content-Type'] = 'application/json'
|
|
body = self.json(query)
|
|
else:
|
|
url = url + '?' + self.urlencode(query)
|
|
# headers = {
|
|
# 'Accept': 'application/json',
|
|
# 'Authorization': 'Bearer ' + self.apiKey,
|
|
# }
|
|
# if method == 'POST':
|
|
# body = self.json(query)
|
|
# headers['Content-Type'] = 'application/json'
|
|
# else:
|
|
# if query:
|
|
# url += '?' + self.urlencode(query)
|
|
# }
|
|
# }
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
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
|
|
#
|
|
# {
|
|
# "data": null,
|
|
# "error": "NOT_ONBOARDED",
|
|
# "message": "User has never called /onboarding endpoint"
|
|
# }
|
|
#
|
|
errorCode = self.safe_string(response, 'error')
|
|
if errorCode is not None:
|
|
feedback = self.id + ' ' + body
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
|
|
raise ExchangeError(feedback) # unknown message
|
|
return None
|