# -*- 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.pacifica import ImplicitAPI import math from ccxt.base.types import Any, Balances, Currency, Int, LedgerEntry, Leverage, MarginMode, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, Transaction, TransferEntry from typing import List from ccxt.base.errors import ExchangeError from ccxt.base.errors import PermissionDenied 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 NotSupported from ccxt.base.errors import RateLimitExceeded from ccxt.base.errors import ExchangeNotAvailable from ccxt.base.errors import RequestTimeout from ccxt.base.decimal_to_precision import TICK_SIZE from ccxt.base.precise import Precise class pacifica(Exchange, ImplicitAPI): def describe(self) -> Any: return self.deep_extend(super(pacifica, self).describe(), { 'id': 'pacifica', 'name': 'Pacifica', 'countries': [], 'version': 'v1', 'isSandboxModeEnabled': False, # is testnet api 'rateLimit': 50, # 125 requests per minute without api-key(300 with api-key) ~ 2 req/sec = 1 req/500 ms. 'certified': False, 'pro': True, 'dex': True, 'has': { 'CORS': None, 'spot': False, 'margin': False, 'swap': True, 'future': True, 'option': False, 'addMargin': False, 'borrowCrossMargin': False, 'borrowIsolatedMargin': False, 'cancelAllOrders': True, 'cancelAllOrdersAfter': False, 'cancelOrder': True, 'cancelOrders': True, 'cancelOrdersForSymbols': None, 'closeAllPositions': False, 'closePosition': False, 'createMarketBuyOrderWithCost': False, 'createMarketOrderWithCost': False, 'createMarketSellOrderWithCost': False, 'createOrder': True, 'createOrders': True, 'createOrderWithTakeProfitAndStopLoss': True, 'createReduceOnlyOrder': True, 'createStopOrder': True, 'editOrder': True, 'editOrders': False, 'fetchAccounts': True, 'fetchBalance': True, 'fetchBorrowInterest': False, 'fetchBorrowRateHistories': False, 'fetchBorrowRateHistory': False, 'fetchCanceledAndClosedOrders': True, 'fetchCanceledOrders': True, 'fetchClosedOrders': True, 'fetchCrossBorrowRate': False, 'fetchCrossBorrowRates': False, 'fetchCurrencies': False, 'fetchDepositAddress': False, 'fetchDepositAddresses': False, 'fetchDeposits': False, 'fetchDepositWithdrawFee': 'emulated', 'fetchDepositWithdrawFees': False, 'fetchFundingHistory': True, 'fetchFundingRate': False, 'fetchFundingRateHistory': True, 'fetchFundingRates': True, 'fetchIndexOHLCV': False, 'fetchIsolatedBorrowRate': False, 'fetchIsolatedBorrowRates': False, 'fetchLedger': True, 'fetchLeverage': True, 'fetchLeverageTiers': False, 'fetchLiquidations': False, 'fetchMarginMode': True, 'fetchMarketLeverageTiers': False, 'fetchMarkets': True, 'fetchMarkOHLCV': False, 'fetchMyLiquidations': False, 'fetchMyTrades': True, 'fetchOHLCV': True, 'fetchOpenInterest': True, 'fetchOpenInterestHistory': False, 'fetchOpenInterests': True, 'fetchOpenOrders': True, 'fetchOrder': True, 'fetchOrderBook': True, 'fetchOrders': True, 'fetchOrderTrades': False, 'fetchPosition': True, 'fetchPositionMode': False, 'fetchPositions': True, 'fetchPositionsRisk': False, 'fetchPremiumIndexOHLCV': False, 'fetchStatus': None, 'fetchTicker': 'emulated', 'fetchTickers': True, 'fetchTime': None, 'fetchTrades': True, 'fetchTradingFee': True, 'fetchTradingFees': False, 'fetchTransfer': False, 'fetchTransfers': False, 'fetchWithdrawal': False, 'fetchWithdrawals': False, 'reduceMargin': False, 'repayCrossMargin': False, 'repayIsolatedMargin': False, 'sandbox': True, 'setLeverage': True, 'setMarginMode': True, 'setPositionMode': False, 'transfer': True, 'withdraw': True, }, 'timeframes': { '1m': '1m', '3m': '3m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '2h': '2h', '4h': '4h', '8h': '8h', '12h': '12h', '1d': '1d', }, 'hostname': 'pacifica.fi', 'urls': { 'logo': 'https://github.com/user-attachments/assets/f795515a-828e-4a04-8fca-bf19fcf17ea4', 'api': { 'public': 'https://api.{hostname}', 'private': 'https://api.{hostname}', }, 'test': { 'public': 'https://test-api.{hostname}', 'private': 'https://test-api.{hostname}', }, 'www': 'https://www.pacifica.fi', 'doc': 'https://docs.pacifica.fi/api-documentation/api/rest-api', 'fees': 'https://docs.pacifica.fi/trading-on-pacifica/trading-fees', 'referral': 'https://app.pacifica.fi?referral=ccxt', }, 'api': { 'public': { 'get': { # ~12 weight depends on the limit 3 max for api-key, but min without api-key 'info': 1, 'info/prices': 1, 'kline': 12, 'kline/mark': 12, 'book': 1, 'trades': 1, # Recent 'funding_rate/history': 1, 'account': 1, 'account/settings': 1, 'positions': 1, 'trades/history': 12, 'funding/history': 1, 'portfolio': 1, 'account/balance/history': 12, 'orders': 1, 'orders/history': 12, 'orders/history_by_id': 1, 'account/builder_codes/approvals': 1, }, }, 'private': { 'post': { 'account/leverage': 1, 'account/margin': 1, 'account/withdraw': 1, 'account/subaccount/create': 1, 'account/subaccount/list': 1, 'account/subaccount/transfer': 1, 'orders/create': 1, 'orders/create_market': 1, 'orders/stop/create': 1, 'positions/tpsl': 1, 'orders/cancel': 0.5, 'orders/cancel_all': 0.5, 'orders/stop/cancel': 0.5, 'orders/edit': 1, 'orders/batch': 1, 'account/builder_codes/approve': 1, 'account/builder_codes/revoke': 1, 'agent/bind': 1, 'account/api_keys/create': 1, 'account/api_keys/revoke': 1, 'account/api_keys': 1, }, }, }, 'fees': { 'swap': { 'taker': self.parse_number('0.0004'), 'maker': self.parse_number('0.00015'), }, }, # # Reminder: # If you're using an agent wallet, its private key must also be in the privateKey field in requiredCredentials. # However, walletAddress must ALWAYS be equal to the main address. For the agent wallet address, there's a field in options: agentAddress # 'requiredCredentials': { 'apiKey': False, 'secret': False, 'walletAddress': False, # agentAddress, apiKey only in options. 'privateKey': True, # base58 solana private key }, 'exceptions': { 'exact': { '400': BadRequest, '403': PermissionDenied, '404': BadRequest, '409': ExchangeError, '422': ExchangeError, '429': RateLimitExceeded, '500': ExchangeError, '503': ExchangeNotAvailable, '504': RequestTimeout, }, 'broad': { 'UNKNOWN': ExchangeError, 'ACCOUNT_NOT_FOUND': ExchangeError, 'BOOK_NOT_FOUND': ExchangeError, 'INVALID_TICK_LEVEL': InvalidOrder, 'INSUFFICIENT_BALANCE': InsufficientFunds, 'ORDER_NOT_FOUND': OrderNotFound, 'OVER_WITHDRAWAL': InsufficientFunds, 'INVALID_LEVERAGE': ExchangeError, 'CANNOT_UPDATE_MARGIN': ExchangeError, 'POSITION_NOT_FOUND': ExchangeError, 'POSITION_TPSL_LIMIT_EXCEEDED': InvalidOrder, }, }, 'precisionMode': TICK_SIZE, 'commonCurrencies': {}, 'options': { 'agentAddress': None, 'apiKey': None, 'builderCode': 'CCXT', # case sensitive 'feeRate': '0.01', # default rate for builder fee approval 0.01% 'builderFee': True, 'batchOrdersMax': 10, 'defaultType': 'swap', 'defaultSlippage': '0.5', 'expiryWindow': 5000, 'maxCostHugeWithApiKey': 3, 'marketHelperProps': [], 'defaultMarginMode': 'cross', 'builderSupportOperations': { 'create_market_order': True, 'create_limit_order': True, 'create_stop_order': True, 'set_position_tpsl': True, }, }, 'features': { 'default': { 'sandbox': True, 'createOrder': { 'marginMode': False, 'triggerPrice': False, 'triggerPriceType': None, 'triggerDirection': False, 'stopLossPrice': False, 'takeProfitPrice': False, 'attachedStopLossTakeProfit': { 'triggerPriceType': { 'last': False, 'mark': False, 'index': False, }, 'triggerPrice': True, 'type': True, 'price': True, }, 'timeInForce': { 'IOC': True, 'FOK': False, 'PO': True, 'GTD': False, }, 'hedged': False, 'trailing': False, 'leverage': False, 'marketBuyByCost': False, 'marketBuyRequiresPrice': False, 'selfTradePrevention': False, 'iceberg': False, }, 'createOrders': { 'max': 10, }, 'editOrder': { 'side': False, 'type': False, }, 'fetchMyTrades': { 'marginMode': False, 'limit': 100, 'daysBack': None, 'untilDays': None, 'symbolRequired': False, }, 'fetchOrder': { 'marginMode': False, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchOpenOrders': { 'marginMode': False, 'limit': 100, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchOrders': { 'marginMode': False, 'limit': 100, 'daysBack': None, 'untilDays': None, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchClosedOrders': { 'marginMode': False, 'limit': 100, 'daysBack': None, 'daysBackCanceled': None, 'untilDays': None, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchOHLCV': { 'limit': 3950, }, 'fetchLedger': { 'code': False, }, }, 'forPerps': { 'extends': 'default', 'createOrder': { 'stopLossPrice': True, 'takeProfitPrice': True, 'attachedStopLossTakeProfit': None, }, }, 'spot': None, 'swap': { 'linear': { 'extends': 'forPerps', }, 'inverse': { 'extends': 'forPerps', }, }, 'future': { 'linear': { 'extends': 'forPerps', }, 'inverse': { 'extends': 'forPerps', }, }, }, }) def initialize_client(self): try: self.handle_builder_fee_approval() except Exception as e: return False return True def handle_builder_fee_approval(self): if self.isSandboxModeEnabled: # At self stage, building codes are mostly only on the mainnet. return False buildFee = self.safe_bool(self.options, 'builderFee', True) if not buildFee: return False # skip if builder fee is not enabled approvedBuilderFee = self.safe_bool(self.options, 'approvedBuilderFee', False) if approvedBuilderFee: return True # skip if builder fee is already approved try: builder = self.safe_string(self.options, 'builderCode', 'CCXT') # case sensitive maxFeeRate = self.safe_string(self.options, 'feeRate', '0.01') self.approve_builder_code(builder, maxFeeRate) self.options['approvedBuilderFee'] = True except Exception as e: self.options['builderFee'] = False # disable builder fee if an error occurs return True def fetch_markets(self, params={}) -> List[Market]: """ retrieves data on all markets for pacifica :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: an array of objects representing market data """ if self.check_required_credentials(False): self.initialize_client() self.load_account_settings() swapMarkets = self.fetch_swap_markets(params) return swapMarkets def fetch_swap_markets(self, params={}) -> List[Market]: """ retrieves data on all swap markets for pacifica https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-market-info :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: an array of objects representing market data """ response = self.publicGetInfo(params) # meta # { # "success": True, # "data": [ # { # "symbol": "ETH", # "tick_size": "0.1", # "min_tick": "0", # "max_tick": "1000000", # "lot_size": "0.0001", # "max_leverage": 50, # "isolated_only": False, # "min_order_size": "10", # "max_order_size": "5000000", # "funding_rate": "0.0000125", # "next_funding_rate": "0.0000125", # "created_at": 1748881333944 # }, # { # "symbol": "BTC", # "tick_size": "1", # "min_tick": "0", # "max_tick": "1000000", # "lot_size": "0.00001", # "max_leverage": 50, # "isolated_only": False, # "min_order_size": "10", # "max_order_size": "5000000", # "funding_rate": "0.0000125", # "next_funding_rate": "0.0000125", # "created_at": 1748881333944 # }, # .... # ], # "error": null, # "code": null # } meta = self.safe_list(response, 'data', []) results = [] for i in range(0, len(meta)): results.append(meta[i]) return self.parse_markets(results) def parse_market(self, market: dict) -> Market: # { # "symbol": "ETH", # "tick_size": "0.1", # "min_tick": "0", # "max_tick": "1000000", # "lot_size": "0.0001", # "max_leverage": 50, # "isolated_only": False, # "min_order_size": "10", # "max_order_size": "5000000", # "funding_rate": "0.0000125", # "next_funding_rate": "0.0000125", # "created_at": 1748881333944 # }, # { # "symbol": "BTC", # "tick_size": "1", # "min_tick": "0", # "max_tick": "1000000", # "lot_size": "0.00001", # "max_leverage": 50, # "isolated_only": False, # "min_order_size": "10", # "max_order_size": "5000000", # "funding_rate": "0.0000125", # "next_funding_rate": "0.0000125", # "created_at": 1748881333944 # }, quoteId = 'usdc' settleId = 'usdc' id = self.safe_string(market, 'symbol') baseId = id.lower() baseName = id.upper() base = self.safe_currency_code(baseName) quote = self.safe_currency_code(quoteId) settle = self.safe_currency_code(settleId) symbol = base + '/' + quote contract = True swap = True if contract: if swap: symbol = symbol + ':' + settle fees = self.safe_dict(self.fees, 'swap', {}) taker = self.safe_number(fees, 'taker') maker = self.safe_number(fees, 'maker') amountPrecisionStr = self.safe_string(market, 'lot_size') pricePrecisionStr = self.safe_string(market, 'tick_size') active = True # there is no non-active markets comes from endpoint market info return self.safe_market_structure({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'baseName': baseName, 'quoteId': quoteId, 'settleId': settleId, 'type': 'swap', 'spot': False, 'margin': None, 'swap': swap, 'future': False, 'option': False, 'active': active, 'contract': contract, 'linear': True, 'inverse': False, 'taker': taker, 'maker': maker, 'contractSize': self.parse_number('1'), 'expiry': None, 'expiryDatetime': None, 'strike': None, 'optionType': None, 'precision': { 'amount': self.parse_number(amountPrecisionStr), 'price': self.parse_number(pricePrecisionStr), }, 'limits': { 'leverage': { 'min': 1, 'max': self.safe_integer(market, 'max_leverage'), }, 'amount': { 'min': None, 'max': None, }, 'price': { 'min': self.safe_string(market, 'min_tick'), 'max': self.safe_string(market, 'max_tick'), }, 'cost': { 'min': None, 'max': None, }, }, 'created': None, 'marginModes': {'cross': True, 'isolated': True}, 'info': market, }) 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.pacifica.fi/api-documentation/api/rest-api/account/get-account-info :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns dict: a `balance structure ` """ userAccount = None userAccount, params = self.handle_origin_and_single_address('fetchBalance', params) request = { 'account': userAccount, } response = self.publicGetAccount(self.extend(request, params)) # { # "success": True, # "data": { # "balance": "2000.000000", # "fee_level": 0, # "maker_fee": "0.00015", # "taker_fee": "0.0004", # "account_equity": "2150.250000", # "available_to_spend": "1800.750000", # "available_to_withdraw": "1500.850000", # "pending_balance": "0.000000", # "total_margin_used": "349.500000", # "cross_mmr": "420.690000", # "positions_count": 2, # "orders_count": 3, # "stop_orders_count": 1, # "updated_at": 1716200000000, # "use_ltp_for_stop_orders": False # }, # "error": null, # "code": null # } data = self.safe_dict(response, 'data', {}) result = { 'info': data, } result['free'] = {} result['used'] = {} result['total'] = {} totalBalance = self.safe_number(data, 'account_equity') usedMargin = self.safe_number(data, 'total_margin_used') freeBalance = self.safe_number(data, 'available_to_spend') result['total']['USDC'] = totalBalance result['used']['USDC'] = usedMargin result['free']['USDC'] = freeBalance timestamp = self.safe_integer(data, 'updated_at') result['timestamp'] = timestamp result['datetime'] = self.iso8601(timestamp) return self.safe_balance(result) def fetch_leverage(self, symbol: str, params={}) -> Leverage: """ fetch the set leverage for a market :param str symbol: unified symbol of the market :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns dict: a `leverage structure ` """ self.load_account_settings() self.load_markets() market = self.market(symbol) userAccount = None userAccount, params = self.handle_origin_and_single_address('fetchLeverage', params) cacheAddress = self.walletAddress settings = None if userAccount == cacheAddress: settings = self.handle_option('fetchLeverage', 'settings', None) else: request: dict = { 'account': userAccount, } settings = self.fetch_account_settings(self.extend(request, params)) setting = self.safe_dict(settings, symbol, None) if setting is None: # NOTE: Upon account creation, all markets have margin settings default to cross margin and leverage default to max. # When querying self endpoint, all markets with default margin and leverage settings on self account will return blank. return self.parse_leverage_from_market(market) else: return self.parse_leverage_from_setting(symbol, setting) def parse_leverage_from_setting(self, symbol: Str, setting: dict) -> Leverage: # { # "WLFI/USDC:USDC": { # "symbol": "WLFI", # "isolated": False, # "leverage": 5, # "created_at": 1758085929703, # "updated_at": 1758086074002 # }, # } isIsolated = self.safe_bool(setting, 'isolated', False) leverage = self.safe_integer(setting, 'leverage') marginMode = 'isolated' if isIsolated else 'cross' return { 'info': setting, 'symbol': symbol, 'marginMode': marginMode, 'longLeverage': leverage, 'shortLeverage': leverage, } def parse_leverage_from_market(self, market: Market) -> Leverage: marketLimits = self.safe_dict(market, 'limits', {}) leverageLimits = self.safe_dict(marketLimits, 'leverage', {}) return { 'info': market, 'symbol': self.safe_string(market, 'symbol'), 'marginMode': self.handle_option('fetchLeverage', 'defaultMarginMode', 'cross'), 'longLeverage': self.safe_integer(leverageLimits, 'max'), 'shortLeverage': self.safe_integer(leverageLimits, 'max'), } def fetch_account_settings(self, params={}) -> dict: """ fetch account's market settings. Settings are cached for walletAddress. To refresh the cache, call loadAccountSettings with refresh=true https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-settings :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns dict: Dict repacked from list by symbol key """ userAccount = None userAccount, params = self.handle_origin_and_single_address('fetchAccountSettings', params) request: dict = { 'account': userAccount, } response = self.publicGetAccountSettings(self.extend(request, params)) # { # "success": True, # "data": [ # { # "symbol": "WLFI", # "isolated": False, # "leverage": 5, # "created_at": 1758085929703, # "updated_at": 1758086074002 # } # ], # "error": null, # "code": null # } return self.parse_account_settings(self.safe_list(response, 'data', [])) def load_account_settings(self, refresh: bool = False, params={}): settings = self.handle_option('loadAccountSettings', 'settings', None) if (settings is None) or (refresh is True): self.options['settings'] = self.create_safe_dictionary() settings = self.fetch_account_settings(params) self.options['settings'] = settings def parse_account_settings(self, settings: List[Any]) -> dict: settingsLen = len(settings) if settingsLen == 0: return {} settingsBySymbol = {} for i in range(0, len(settings)): marketId = settings[i]['symbol'] market = self.safe_market(marketId) symbol = market['symbol'] settingsBySymbol[symbol] = settings[i] return settingsBySymbol def fetch_margin_mode(self, symbol: str, params={}) -> MarginMode: """ fetches the margin mode of the trading pair :param str symbol: unified symbol of the market to fetch the margin mode for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns dict: a `margin mode structure ` """ self.load_account_settings() userAccount = None userAccount, params = self.handle_origin_and_single_address('fetchMarginMode', params) cacheAddress = self.walletAddress settings = None if userAccount == cacheAddress: settings = self.handle_option('fetchMarginMode', 'settings', None) else: request: dict = { 'account': userAccount, } settings = self.fetch_account_settings(self.extend(request, params)) # { # "WLFI/USDC:USDC": { # "symbol": "WLFI", # "isolated": False, # "leverage": 5, # "created_at": 1758085929703, # "updated_at": 1758086074002 # }, # } setting = self.safe_dict(settings, symbol, None) if setting is None: # NOTE: Upon account creation, all markets have margin settings default to cross margin and leverage default to max. # When querying self endpoint, all markets with default margin and leverage settings on self account will return blank. return { 'symbol': symbol, 'marginMode': self.handle_option('fetchMarginMode', 'defaultMarginMode', 'cross'), } else: return self.parse_margin_mode_from_setting(symbol, setting) def parse_margin_mode_from_setting(self, symbol: Str, setting: dict) -> MarginMode: # { # "symbol": "WLFI", # "isolated": False, # "leverage": 5, # "created_at": 1758085929703, # "updated_at": 1758086074002 # # } isIsolated = self.safe_bool(setting, 'isolated', False) marginMode = 'isolated' if isIsolated else 'cross' return { 'symbol': symbol, 'marginMode': marginMode, 'info': setting, } 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.pacifica.fi/api-documentation/api/rest-api/markets/get-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 :param int [params.aggLevel]: aggregation level for price grouping. Defaults to 1. Can be 1, 10, 100, 1000, 10000 :returns dict: A dictionary of `order book structures ` indexed by market symbols """ self.load_markets() market = self.market(symbol) aggLevel = None aggLevel, params = self.handle_option_and_params(params, 'fetchOrderBook', 'aggLevel', 1) request: dict = { 'symbol': market['id'], 'agg_level': aggLevel, } response = self.publicGetBook(self.extend(request, params)) # { # "success": True, # "data": { # "s": "BTC", # "l": [ # [ # { # "p": "106504", # "a": "0.26203", # "n": 1 # }, # { # "p": "106498", # "a": "0.29281", # "n": 1 # } # ], # [ # { # "p": "106559", # "a": "0.26802", # "n": 1 # }, # { # "p": "106564", # "a": "0.3002", # "n": 1 # }, # ] # ], # "t": 1751370536325 # }, # "error": null, # "code": null # } data = self.safe_dict(response, 'data', {}) levels = self.safe_list(data, 'l', []) result: dict = { 'bids': self.safe_list(levels, 0, []), 'asks': self.safe_list(levels, 1, []), } timestamp = self.safe_integer(data, 't') return self.parse_order_book(result, self.safe_symbol(None, market), timestamp, 'bids', 'asks', 'p', 'a') def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates: """ retrieves data on all swap markets for pacifica :param str[] [symbols]: list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: an array of objects representing market data """ response = self.publicGetInfoPrices(params) # # { # "success": True, # "data": [ # { # "funding": "0.00010529", # "mark": "1.084819", # "mid": "1.08615", # "next_funding": "0.00011096", # "open_interest": "3634796", # "oracle": "1.084524", # "symbol": "XPL", # "timestamp": 1759222967974, # "volume_24h": "20896698.0672", # "yesterday_price": "1.3412" # } # ], # "error": null, # "code": null # } # result = self.safe_list(response, 'data', []) return self.parse_funding_rates(result, symbols) def parse_funding_rate(self, info, market: Market = None) -> FundingRate: # # { # "funding": "0.00010529", # "mark": "1.084819", # "mid": "1.08615", # "next_funding": "0.00011096", # "open_interest": "3634796", # "oracle": "1.084524", # "symbol": "XPL", # "timestamp": 1759222967974, # "volume_24h": "20896698.0672", # "yesterday_price": "1.3412" # } # marketId = self.safe_string(info, 'symbol') market = self.safe_market(marketId, market) symbol = market['symbol'] funding = self.safe_number(info, 'funding') markPx = self.safe_number(info, 'mark') oraclePx = self.safe_number(info, 'oracle') nextFundingRate = self.safe_number(info, 'next_funding') timestamp = self.safe_integer(info, 'timestamp') fundingTimestamp = (int(math.floor(self.milliseconds()) / 60 / 60 / 1000) + 1) * 60 * 60 * 1000 return { 'info': info, 'symbol': symbol, 'markPrice': markPx, 'indexPrice': oraclePx, 'interestRate': None, 'estimatedSettlePrice': None, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'fundingRate': funding, 'fundingTimestamp': fundingTimestamp, 'fundingDatetime': self.iso8601(fundingTimestamp), 'nextFundingRate': nextFundingRate, 'nextFundingTimestamp': None, 'nextFundingDatetime': None, 'previousFundingRate': None, 'previousFundingTimestamp': None, 'previousFundingDatetime': None, 'interval': '1h', } 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.pacifica.fi/api-documentation/api/rest-api/markets/get-candle-data :param str symbol: unified symbol of the market to fetch OHLCV data for :param str timeframe: the length of time each candle represents, support '1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d' :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. 'limit' is priority :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 int[][]: A list of candles ordered, open, high, low, close, volume """ if since is None: raise ArgumentsRequired(self.id + ' fetchOHLCV() requires a "since" argument') if symbol is None: raise ArgumentsRequired(self.id + ' fetchOHLCV() requires a "symbol" argument') defaultMaxLimit = 3950 # 4000 by docs, but in fact >~3960 returns error self.load_markets() market = self.market(symbol) paginate = False paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate', False) if paginate: return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, defaultMaxLimit) tf = self.safe_string(self.timeframes, timeframe, timeframe) request: dict = { 'symbol': market['id'], 'interval': tf, 'start_time': since, } request, params = self.handle_until_option('end_time', request, params) nowMillis = self.milliseconds() until = self.safe_integer(request, 'end_time') if until is None: if limit is not None: until = since + (limit * (self.parse_timeframe(tf) * 1000)) - 1 if until is None: until = since + (defaultMaxLimit * (self.parse_timeframe(tf) * 1000)) - 1 if until > nowMillis: until = nowMillis request['end_time'] = until response = self.publicGetKline(self.extend(request, params)) # # { # "success": True, # "data": [ # { # "t": 1748954160000, # "T": 1748954220000, # "s": "BTC", # "i": "1m", # "o": "105376", # "c": "105376", # "h": "105376", # "l": "105376", # "v": "0.00022", # "n": 2 # } # ], # "error": null, # "code": null # } # candles = self.safe_list(response, 'data', []) return self.parse_ohlcvs(candles, market, timeframe, since, limit) def parse_ohlcv(self, ohlcv, market: Market = None) -> list: # # { # "t": 1748954160000, # "T": 1748954220000, # "s": "BTC", # "i": "1m", # "o": "105376", # "c": "105376", # "h": "105376", # "l": "105376", # "v": "0.00022", # "n": 2 # } # return [ self.safe_integer(ohlcv, 't'), self.safe_number(ohlcv, 'o'), self.safe_number(ohlcv, 'h'), self.safe_number(ohlcv, 'l'), self.safe_number(ohlcv, 'c'), self.safe_number(ohlcv, 'v'), ] def fetch_trades(self, symbol: Str, since: Int = None, limit: Int = None, params={}): """ get the list of most recent trades for a particular symbol https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-recent-trades :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 :returns Trade[]: a list of `trade structures ` """ self.load_markets() market = self.market(symbol) request = { 'symbol': market['id'], } response = self.publicGetTrades(self.extend(request, params)) # # { # "success": True, # "data": [ # { # "event_type": "fulfill_taker", # "price": "104721", # "amount": "0.0001", # "side": "close_long", # "cause": "normal", # "created_at": 1765006315306 # } # ], # "error": null, # "code": null, # "last_order_id": 1557404170 # } # recentTrades = self.safe_list(response, 'data', []) return self.parse_trades(recentTrades, market, since, limit) def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ fetch all trades made by the user https://docs.pacifica.fi/api-documentation/api/rest-api/account/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 int [params.until]: timestamp in ms of the latest trade :param str [params.account]: will default to walletAddress if not provided :param str [params.cursor]: pagination cursor from prev request(manual use) :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 Trade[]: a list of `trade structures ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) paginate = False paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate', False) userAddress = None userAddress, params = self.handle_origin_and_single_address('fetchMyTrades', params) defaultLimit = 100 # Default max limit if paginate: return self.fetch_paginated_call_cursor('fetchMyTrades', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit) request: dict = {} request, params = self.handle_until_option('end_time', request, params) request['account'] = userAddress if symbol is not None: request['symbol'] = market['id'] if limit is not None: request['limit'] = limit if since is not None: request['start_time'] = since response = self.publicGetTradesHistory(self.extend(request, params)) # # { # "success": True, # "data": [ # { # "history_id": 19329801, # "order_id": 315293920, # "client_order_id": "acf...", # "symbol": "LDO", # "amount": "0.1", # "price": "1.1904", # "entry_price": "1.176247", # "fee": "0", # "pnl": "-0.001415", # "event_type": "fulfill_maker", # "side": "close_short", # "created_at": 1759215599188, # "cause": "normal" # }, # ... # ], # "next_cursor": "11111Z5RK", # not included to info! # "has_more": True # not included to info! # } # data = self.add_pagination_cursor_to_result(response) return self.parse_trades(data, market, since, limit) def parse_trade(self, trade: dict, market: Market = None) -> Trade: # # user trades: # { # "history_id": 19329801, # "order_id": 315293920, # "client_order_id": "acf...", # "symbol": "LDO", # "amount": "0.1", # "price": "1.1904", # "entry_price": "1.176247", # "fee": "0", # "pnl": "-0.001415", # "event_type": "fulfill_maker", # "side": "close_short", # "created_at": 1759215599188, # "cause": "normal" # }, # recent public trades: # { # "event_type": "fulfill_taker", # "price": "104721", # "amount": "0.0001", # "side": "close_long", # "cause": "normal", # "created_at": 1765006315306 # } # eventType = self.safe_string(trade, 'event_type') timestamp = self.safe_integer(trade, 'created_at') price = self.safe_string(trade, 'price') amount = self.safe_string(trade, 'amount') symbol = self.safe_symbol(None, market) id = self.safe_string(trade, 'history_id') side = self.safe_string(trade, 'side') if side == 'open_long': side = 'buy' elif side == 'close_long': side = 'sell' elif side == 'open_short': side = 'sell' elif side == 'close_short': side = 'buy' fee = self.safe_string(trade, 'fee') orderId = self.safe_string(trade, 'order_id') takerOrMaker = None if eventType is not None: takerOrMaker = 'maker' if (eventType == 'fulfill_maker') else 'taker' # public trades have no orderId if orderId is None: takerOrMaker = None return self.safe_trade({ 'info': trade, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'symbol': symbol, 'id': id, 'order': orderId, 'type': None, 'side': side, 'takerOrMaker': takerOrMaker, 'price': price, 'amount': amount, 'cost': None, 'fee': { 'cost': fee, 'currency': 'USDC', 'rate': None, }, }, market) def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ create a trade order https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-limit-order https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-market-order https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-stop-order https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-position-tp-sl :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. Not used for set tpsl order! :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.triggerPrice]: The price a trigger order is triggered at :param float [params.stopLossPrice]: the price that a stop loss order is triggered at(optional provide stopLossCloid) :param float [params.takeProfitPrice]: the price that a take profit order is triggered at(optional provide takeProfitCloid) :param str [params.timeInForce]: "GTC", "IOC", or "PO" or "ALO" or "PO_TOB"(or "TOB" - PO by top of book) :param boolean [params.reduceOnly]: Ensures that the executed order does not flip the opened position. :param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479) :param int [params.expiryWindow]: time to live in milliseconds :returns dict: an `order structure ` """ self.load_markets() self.initialize_client() request, operationType = self.create_order_request(symbol, type, side, amount, price, params) params = self.omit(params, [ 'reduceOnly', 'clientOrderId', 'stopLimitPrice', 'timeInForce', 'triggerPrice', 'stopLossCloid', 'stopLossPrice', 'stopLossLimitPrice', 'takeProfitCloid', 'takeProfitPrice', 'takeProfitLimitPrice', 'expiryWindow', ]) response = None if operationType == 'create_market_order': response = self.privatePostOrdersCreateMarket(self.extend(request, params)) elif operationType == 'create_stop_order': response = self.privatePostOrdersStopCreate(self.extend(request, params)) elif operationType == 'set_position_tpsl': response = self.privatePostPositionsTpsl(self.extend(request, params)) else: # create_order response = self.privatePostOrdersCreate(self.extend(request, params)) # # { # 'success': True, # 'data': { # "order_id": 12345 # }, # } # success = self.safe_bool(response, 'success', False) status = None if not success: status = 'rejected' else: status = 'open' order = self.safe_dict(response, 'data', {}) orderId = self.safe_string(order, 'order_id') return self.safe_order({'id': orderId, 'status': status, 'info': response, 'symbol': symbol}) def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ @ignore create a trade order https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-limit-order https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-market-order https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-stop-order https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-position-tp-sl :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, but can be used of Trigger Order. :param dict [params]: extra parameters specific to the exchange API endpoint :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(optional provide stopLossCloid) :param float [params.takeProfitPrice]: the price that a take profit order is triggered at(optional provide takeProfitCloid) :param str [params.timeInForce]: "GTC", "IOC", or "PO" or "ALO" or "PO_TOB"(or "TOB" - PO by top of book) :param boolean [params.reduceOnly]: Ensures that the executed order does not flip the opened position. :param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479) :param int [params.expiryWindow]: time to live in milliseconds :returns dict: an [order structure] """ market = self.market(symbol) sigPayload: dict = { 'symbol': market['id'], 'side': self.map_side(side), } operationType = None reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only', False) orderType = type.upper() triggerPrice = self.safe_string(params, 'triggerPrice') stopLossPrice = self.safe_string(params, 'stopLossPrice') takeProfitPrice = self.safe_string(params, 'takeProfitPrice') tifRaw = self.safe_string_upper(params, 'timeInForce') isMarket = orderType == 'MARKET' isTakeProfitOrder = (takeProfitPrice is not None) isStopLossOrder = (stopLossPrice is not None) isStopOrder = (triggerPrice is not None) timeInForce = self.map_time_in_force(tifRaw) if isMarket: operationType = 'create_market_order' sigPayload['reduce_only'] = reduceOnly defaultSlippage = self.handle_option('createOrder', 'defaultSlippage', '0.5') slippage = self.safe_string_2(params, 'slippage', 'slippage_percent', defaultSlippage) sigPayload['slippage_percent'] = slippage elif (isTakeProfitOrder or isStopLossOrder) and (price is None): # the tpsl endpoint does not accept a 'price' parameter operationType = 'set_position_tpsl' elif isStopOrder: operationType = 'create_stop_order' sigPayload['reduce_only'] = reduceOnly stopClientOrderId = self.safe_string(params, 'clientOrderId') params = self.omit(params, ['clientOrderId']) stopPayload = { 'amount': self.amount_to_precision(symbol, amount), 'stop_price': self.price_to_precision(symbol, triggerPrice), } if stopClientOrderId is not None: stopPayload['client_order_id'] = stopClientOrderId if price is not None: stopPayload['limit_price'] = self.price_to_precision(symbol, price) sigPayload['stop_order'] = stopPayload else: operationType = 'create_order' sigPayload['reduce_only'] = reduceOnly if timeInForce is None: sigPayload['tif'] = 'GTC' else: sigPayload['tif'] = timeInForce if isTakeProfitOrder: tpPayload: dict = { 'stop_price': self.price_to_precision(symbol, takeProfitPrice), } if price is not None: tpPayload['limit_price'] = self.price_to_precision(symbol, price) sigPayload['take_profit'] = tpPayload if isStopLossOrder: slPayload: dict = { 'stop_price': self.price_to_precision(symbol, stopLossPrice), } if price is not None: slPayload['limit_price'] = self.price_to_precision(symbol, price) sigPayload['stop_loss'] = slPayload if price is not None and operationType == 'create_order': sigPayload['price'] = self.price_to_precision(symbol, price) if amount is not None and (operationType != 'create_stop_order' and operationType != 'set_position_tpsl'): sigPayload['amount'] = self.amount_to_precision(symbol, amount) clientOrderId = self.safe_string_n(params, ['clientOrderId']) if clientOrderId is not None: sigPayload['client_order_id'] = clientOrderId request = self.post_action_request(operationType, sigPayload, params) return [request, operationType] def batch_orders_request(self, actions: List[Any]): # # [ # { # "type":"Create", # "data":{ # "account":"42trU9A5...", # "signature":"5UpRZ14Q...", # "timestamp":1749190500355, # "expiry_window":5000, # "symbol":"BTC", # "price":"100000", # "reduce_only":false, # "amount":"0.1", # "side":"bid", # "tif":"GTC", # "client_order_id":"57a5efb1-bb96-49a5-8bfd-f25d5f22bc7e" # } # }, # { # "type":"Cancel", # "data":{ # "account":"42trU9A5...", # "signature":"4NDFHyTG...", # "timestamp":1749190500355, # "expiry_window":5000, # "symbol":"BTC", # "order_id":42069 # } # } # ] # # Create(Only Limit or Market, never stop order or tpsl order) # Cancel(Only common(limit) orders) # lenActions = len(actions) maxLen = self.handle_option('batchOrdersRequest', 'batchOrdersMax') if maxLen is not None: if lenActions > maxLen: raise ExchangeError(self.id + ' batchOrdersRequest() too many orders to create/cancel. Limit is ' + maxLen) return { 'actions': actions, } def create_orders_request(self, orders: List[OrderRequest], params={}): actions = [] timestamp = self.milliseconds() # unified sequence for i in range(0, len(orders)): order = orders[i] symbol = self.safe_string(order, 'symbol') side = self.safe_string(order, 'side') price = self.safe_string(order, 'price') type = self.safe_string(order, 'type', 'limit') orderParams = self.safe_dict(order, 'params', {}) orderParams['timestamp'] = timestamp amount = self.safe_string(order, 'amount') amountNumber = self.parse_number(amount) priceNumber = self.parse_number(price) if type != 'limit': raise NotSupported(self.id + ' createOrders() supports only type = "limit"! Your value type=' + type) requestList = self.create_order_request(symbol, type, side, amountNumber, priceNumber, orderParams) action = { 'type': 'Create', 'data': requestList[0], } actions.append(action) return self.batch_orders_request(actions) def create_orders(self, orders: List[OrderRequest], params={}): """ create a list of trade orders. It is supports only limit orders and have a random jitter ~100-300ms! https://docs.pacifica.fi/api-documentation/api/rest-api/orders/batch-order :param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type(optional or 'limit'), side, amount, price and params :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `order structure ` """ self.load_markets() self.initialize_client() request = self.create_orders_request(orders) response = self.privatePostOrdersBatch(self.extend(request, params)) # { # "success": True, # "data": { # "results": [ # { # "success": True, # "order_id": 470506, # "error": null # }, # { # "success": True, # } # ] # }, # "error": null, # "code": null # } # data = self.safe_dict(response, 'data', {}) results = self.safe_list(data, 'results', []) ordersToReturn = [] for i in range(0, len(results)): order = results[i] error = self.safe_string(order, 'error', None) success = self.safe_bool(order, 'success', False) status = None if (error is not None) or (not success): status = 'rejected' else: status = 'open' orderId = self.safe_string(order, 'order_id') ordersToReturn.append(self.safe_order({'info': order, 'id': orderId, 'status': status})) return ordersToReturn def cancel_orders(self, ids: List[str], symbol: Str = None, params={}): """ cancel multiple orders https://docs.pacifica.fi/api-documentation/api/rest-api/orders/batch-order :param str[] ids: order ids. An ids list is always required(can be empty). Both ids and clientOrderIds can be passed simultaneously. :param str [symbol]: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :param string|str[] [params.clientOrderIds]: client order ids,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479) :param int [params.expiryWindow]: time to live in milliseconds :returns dict: an list of `order structures ` """ self.load_markets() self.initialize_client() if symbol is None: raise ArgumentsRequired(self.id + ' cancelOrders() requires a "symbol" argument!') request = self.cancel_orders_request(ids, symbol, params) params = self.omit(params, ['expiryWindow', 'clientOrderIds']) response = self.privatePostOrdersBatch(self.extend(request, params)) # # { # "success": True, # "data": { # "results": [ # { # "success": True, # "order_id": 470506, # "error": null # }, # { # "success": True, # } # ] # }, # "error": null, # "code": null # } # data = self.safe_dict(response, 'data', {}) results = self.safe_list(data, 'results', []) ordersToReturn = [] for i in range(0, len(results)): order = results[i] error = self.safe_string(order, 'error', None) success = self.safe_bool(order, 'success', False) status = None if (error is not None) or (not success): status = 'closed' else: status = 'canceled' ordersToReturn.append(self.safe_order({'info': order, 'status': status, 'symbol': symbol})) return ordersToReturn def cancel_orders_request(self, ids: List[Str], symbol: Str = None, params={}): actions = [] for i in range(0, len(ids)): id = ids[i] request = self.cancel_order_request(id, symbol, params) action = { 'type': 'Cancel', 'data': request, } actions.append(action) clientOrderIds = self.safe_list(params, 'clientOrderIds', []) params = self.omit(params, 'clientOrderIds') for i in range(0, len(clientOrderIds)): cloid = clientOrderIds[i] cloidParams = { 'clientOrderId': cloid, } request = self.cancel_order_request(cloid, symbol, self.extend(cloidParams, params)) action = { 'type': 'Cancel', 'data': request, } actions.append(action) return self.batch_orders_request(actions) def cancel_all_orders(self, symbol: Str = None, params={}): """ cancel all open orders in a market https://docs.pacifica.fi/api-documentation/api/rest-api/orders/cancel-all-orders :param str symbol:(optional) unified market symbol of the market to cancel orders in. :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.excludeReduceOnly]: whether to exclude reduce-only orders :param int [params.expiryWindow]: time to live in milliseconds :returns dict[]: a list of `order structures ` """ self.load_markets() self.initialize_client() request = self.cancel_all_orders_request(symbol, params) params = self.omit(params, ['excludeReduceOnly', 'expiryWindow']) response = self.privatePostOrdersCancelAll(self.extend(request, params)) # # { # success: True, # data: { # "cancelled_count": 5, # }, # code: null, # error: null # } # return [ self.safe_order({ 'info': response, }), ] def cancel_all_orders_request(self, symbol: Str, params={}): operationType = 'cancel_all_orders' sigPayload: dict = {} excludeReduceOnly = self.safe_bool(params, 'excludeReduceOnly', False) sigPayload['exclude_reduce_only'] = excludeReduceOnly if symbol is not None: market = self.market(symbol) sigPayload['all_symbols'] = False sigPayload['symbol'] = market['id'] else: sigPayload['all_symbols'] = True request = self.post_action_request(operationType, sigPayload, params) return request def cancel_order(self, id: str, symbol: Str = None, params={}): """ cancels an open order https://docs.pacifica.fi/api-documentation/api/rest-api/orders/cancel-stop-order#response https://docs.pacifica.fi/api-documentation/api/rest-api/orders/cancel-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.stop]: necessary if self is to cancel a stop order. :param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479) :param int [params.expiryWindow]: time to live in milliseconds :returns dict: An `order structure ` """ self.load_markets() self.initialize_client() if symbol is None: raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument') request = self.cancel_order_request(id, symbol, params) isStopOrder = self.safe_bool_2(params, 'trigger', 'stop', False) params = self.omit(params, ['expiryWindow', 'trigger', 'stop', 'clientOrderId']) response = None if isStopOrder: response = self.privatePostOrdersStopCancel(self.extend(request, params)) else: response = self.privatePostOrdersCancel(self.extend(request, params)) # # response: # { # "success": True, # "data": null # } # success = self.safe_bool(response, 'success', False) status = 'canceled' if success else 'closed' return self.safe_order({'id': id, 'status': status, 'info': response, 'symbol': symbol}) def cancel_order_request(self, id: Str, symbol: Str = None, params={}): market = self.market(symbol) isStopOrder = self.safe_bool_2(params, 'trigger', 'stop', False) operationType = None if isStopOrder: operationType = 'cancel_stop_order' else: operationType = 'cancel_order' clientOrderId = self.safe_string(params, 'clientOrderId') sigPayload: dict = { 'symbol': market['id'], } if clientOrderId is not None: sigPayload['client_order_id'] = clientOrderId else: sigPayload['order_id'] = self.parse_to_int(id) request = self.post_action_request(operationType, sigPayload, params) return request 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.pacifica.fi/api-documentation/api/rest-api/orders/edit-order :param str id: edit order id :param str symbol: unified symbol of the market to edit an order in :param str type: 'market' or 'limit' WARN is not usable! :param str side: 'buy' or 'sell' WARN is not usable! :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 :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479) :param int [params.expiryWindow]: time to live in milliseconds :returns dict: an `order structure ` """ self.load_markets() self.initialize_client() market = self.market(symbol) request = self.edit_order_request(id, symbol, type, side, amount, price, market, params) params = self.omit(params, ['expiryWindow', 'clientOrderId']) response = self.privatePostOrdersEdit(self.extend(request, params)) # # { # 'data': { # "order_id": 123498765 # } # } # data = self.safe_dict(response, 'data', {}) orderId = self.safe_string(data, 'order_id') return self.safe_order({'id': orderId, 'info': response, 'symbol': symbol}) def edit_order_request(self, id: str, symbol: str, type: str, side: str, amount: Num, price: Num, market: Market, params={}): if amount is None: raise ArgumentsRequired(self.id + ' editOrder() requires an amount!') if price is None: raise ArgumentsRequired(self.id + ' editOrder() requires a price') operationType = 'edit_order' clientOrderId = self.safe_string(params, 'clientOrderId') priceNormalized = self.price_to_precision(symbol, price) amountNormalized = self.amount_to_precision(symbol, amount) sigPayload: dict = { 'symbol': market['id'], 'price': priceNormalized, 'amount': amountNormalized, } if (clientOrderId is None) and (id is None): raise ArgumentsRequired('self.id' + 'editOrder() requires either "id" or "clientOrderId"') if clientOrderId is not None: sigPayload['client_order_id'] = clientOrderId else: sigPayload['order_id'] = self.parse_to_int(id) request = self.post_action_request(operationType, sigPayload, params) return request def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetches historical funding rate prices https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-historical-funding :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 :param str [params.cursor]: pagination cursor from prev request(manual use) :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 `funding rate structures ` """ self.load_markets() if symbol is None: raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument') market = self.market(symbol) paginate = False paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate', False) defaultLimit = 100 # Default max limit if paginate: return self.fetch_paginated_call_cursor('fetchFundingRateHistory', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit) request: dict = { 'symbol': market['id'], } if limit is not None: request['limit'] = limit response = self.publicGetFundingRateHistory(self.extend(request, params)) # # { # "success": True, # "data": [ # { # "oracle_price": "117170.410304", # "bid_impact_price": "117126", # "ask_impact_price": "117142", # "funding_rate": "0.0000125", # "next_funding_rate": "0.0000125", # "created_at": 1753806934249 # }, # ... # ], # "next_cursor": "11114Lz77", # "has_more": True # } # data = self.add_pagination_cursor_to_result(response) result = [] for i in range(0, len(data)): entry = data[i] timestamp = self.safe_integer(entry, 'created_at') result.append({ 'info': entry, 'symbol': market['symbol'], 'fundingRate': self.safe_number(entry, 'funding_rate'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), }) sorted = self.sort_by(result, 'timestamp') return self.filter_by_since_limit(sorted, since, limit, 'timestamp') 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.pacifica.fi/api-documentation/api/rest-api/markets/get-prices :param str[] [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 ` """ self.load_markets() symbols = self.market_symbols(symbols) response = self.publicGetInfoPrices(params) # # { # "success": True, # "data": [ # { # "funding": "0.00010529", # "mark": "1.084819", # "mid": "1.08615", # "next_funding": "0.00011096", # "open_interest": "3634796", # "oracle": "1.084524", # "symbol": "XPL", # "timestamp": 1759222967974, # "volume_24h": "20896698.0672", # "yesterday_price": "1.3412" # } # ], # "error": null, # "code": null # } # data = self.safe_list(response, 'data', []) result: dict = {} for i in range(0, len(data)): info = data[i] ticker = self.parse_ticker(info) symbol = self.safe_string(ticker, 'symbol') result[symbol] = ticker return self.filter_by_array_tickers(result, 'symbol', symbols) def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker: # # { # "funding": "0.00010529", # "mark": "1.084819", # "mid": "1.08615", # "next_funding": "0.00011096", # "open_interest": "3634796", # "oracle": "1.084524", # "symbol": "XPL", # "timestamp": 1759222967974, # "volume_24h": "20896698.0672", # "yesterday_price": "1.3412" # } # marketId = self.safe_string(ticker, 'symbol') market = self.safe_market(marketId, market) symbol = market['symbol'] timestamp = self.safe_integer(ticker, 'timestamp') return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'previousClose': self.safe_number(ticker, 'yesterday_price'), 'close': self.safe_number(ticker, 'mid'), 'bid': None, 'ask': None, 'quoteVolume': self.safe_number(ticker, 'volume_24h'), 'info': ticker, }, market) def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetch all unfilled currently closed orders :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch open orders for :param int [limit]: the maximum number of open orders structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns Order[]: a list of `order structures ` """ self.load_markets() orders = self.fetch_orders(symbol, None, None, params) # don't filter here because we don't want to catch open orders closedOrders = self.filter_by_array(orders, 'status', ['closed'], False) return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit) def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetch all canceled orders :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch open orders for :param int [limit]: the maximum number of open orders structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns Order[]: a list of `order structures ` """ self.load_markets() orders = self.fetch_orders(symbol, None, None, params) # don't filter here because we don't want to catch open orders closedOrders = self.filter_by_array(orders, 'status', ['canceled'], False) return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit) def fetch_canceled_and_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetch all closed and canceled orders :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch open orders for :param int [limit]: the maximum number of open orders structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns Order[]: a list of `order structures ` """ self.load_markets() orders = self.fetch_orders(symbol, None, None, params) # don't filter here because we don't want to catch open orders closedOrders = self.filter_by_array(orders, 'status', ['canceled', 'closed', 'rejected'], False) return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit) def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetch all unfilled currently open orders https://docs.pacifica.fi/api-documentation/api/rest-api/orders/get-open-orders :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch open orders for :param int [limit]: the maximum number of open orders structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns Order[]: a list of `order structures ` """ self.load_markets() userAddress = None userAddress, params = self.handle_origin_and_single_address('fetchOpenOrders', params) request: dict = { 'account': userAddress, } market = None if symbol is not None: market = self.market(symbol) response = self.publicGetOrders(self.extend(request, params)) # # { # "success": True, # "data": [ # { # "order_id": 315979358, # "client_order_id": "add9a4b5-c7f7-4124-b57f-86982d86d479", # "symbol": "ASTER", # "side": "ask", # "price": "1.836", # "initial_amount": "85.33", # "filled_amount": "0", # "cancelled_amount": "0", # "stop_price": null, # "order_type": "limit", # "stop_parent_order_id": null, # "reduce_only": False, # "created_at": 1759224706737, # "updated_at": 1759224706737 # } # ], # "error": null, # "code": null, # "last_order_id": 1557370337 # } # data = self.safe_list(response, 'data', []) return self.parse_orders(data, market, since, limit) def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ fetch all orders https://docs.pacifica.fi/api-documentation/api/rest-api/orders/get-order-history :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch open orders for :param int [limit]: the maximum number of open orders structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :param str [params.cursor]: pagination cursor from prev request(manual use) :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 Order[]: a list of `order structures ` """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate', False) defaultLimit = 100 # max default 100 if paginate: return self.fetch_paginated_call_cursor('fetchOrders', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit) userAddress = None userAddress, params = self.handle_origin_and_single_address('fetchOrders', params) market = None if symbol is not None: market = self.market(symbol) request: dict = { 'account': userAddress, } if limit is not None: request['limit'] = limit response = self.publicGetOrdersHistory(self.extend(request, params)) # # { # "success": True, # "data": [ # { # "order_id": 315992721, # "client_order_id": "ade", # "symbol": "XPL", # "side": "ask", # "initial_price": "1.0865", # "average_filled_price": "0", # "amount": "984", # "filled_amount": "0", # "order_status": "open", # "order_type": "limit", # "stop_price": null, # "stop_parent_order_id": null, # "reduce_only": False, # "reason": null, # "created_at": 1759224893638, # "updated_at": 1759224893638 # }, # ... # ], # "next_cursor": "1111Hyd74", # "has_more": True # } # data = self.add_pagination_cursor_to_result(response) orders = self.parse_orders(data, market, since, limit) return orders def add_pagination_cursor_to_result(self, response): data = self.safe_list(response, 'data', []) paginationCursor = self.safe_string(response, 'next_cursor') hasMore = self.safe_bool(response, 'has_more', False) dataLength = len(data) if hasMore: if (paginationCursor is not None) and (dataLength > 0): first = data[0] first['next_cursor'] = paginationCursor first['has_more'] = hasMore data[0] = first return data def fetch_order(self, id: str, symbol: Str = None, params={}): """ fetches information on an order made by the user https://docs.pacifica.fi/api-documentation/api/rest-api/orders/get-order-history-by-id :param str id: order id :param str symbol:(optional) unified symbol of the market the order was made in :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: An `order structure ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) request: dict = { 'order_id': id, } response = self.publicGetOrdersHistoryById(self.extend(request, params)) # # { # "success": True, # "data": [ # { # "history_id": 641452639, # "order_id": 315992721, # "client_order_id": "ade1aa6...", # "symbol": "XPL", # "side": "ask", # "price": "1.0865", # "initial_amount": "984", # "filled_amount": "0", # "cancelled_amount": "984", # "event_type": "cancel", # "order_type": "limit", # "order_status": "cancelled", # "stop_price": null, # "stop_parent_order_id": null, # "reduce_only": False, # "created_at": 1759224895038 # }, # { # "history_id": 641452513, # "order_id": 315992721, # "client_order_id": "ade1aa6...", # "symbol": "XPL", # "side": "ask", # "price": "1.0865", # "initial_amount": "984", # "filled_amount": "0", # "cancelled_amount": "0", # "event_type": "make", # "order_type": "limit", # "order_status": "open", # "stop_price": null, # "stop_parent_order_id": null, # "reduce_only": False, # "created_at": 1759224893638 # } # ], # "error": null, # "code": null # } # data = self.safe_list(response, 'data', []) # return last state sorted = self.sort_by(data, 'created_at') lastIdx = len(sorted) lastInfo = {} if lastIdx > 0: lastInfo = sorted[0] return self.parse_order(lastInfo, market) def parse_order_status(self, status: Str): statuses: dict = { 'open': 'open', 'partially_filled': 'open', 'filled': 'closed', 'cancelled': 'canceled', 'rejected': 'failed', } return self.safe_string(statuses, status, status) def map_time_in_force(self, tifRaw: Str): tifMap: dict = { 'GTC': 'GTC', 'IOC': 'IOC', 'PO': 'ALO', 'POST_ONLY': 'ALO', 'PO_TOB': 'TOB', 'TOB': 'TOB', 'ALO': 'ALO', } tif = None if tifRaw is not None: tif = tifRaw.upper() return self.safe_string(tifMap, tif, None) def map_side(self, sideRaw: str): sideMap: dict = { 'sell': 'ask', 'buy': 'bid', } return self.safe_string(sideMap, sideRaw, sideRaw) def parse_order_type(self, status: str): statuses: dict = { 'stop_limit': 'limit', 'stop_market': 'market', 'take_profit_limit': 'limit', 'stop_loss_limit': 'limit', 'take_profit_market': 'market', 'stop_loss_market': 'market', } return self.safe_string(statuses, status, status) def parse_order(self, order: dict, market: Market = None) -> Order: # # fetchOpenOrders # [ # { # "order_id": 315979358, # "client_order_id": "add9a4b5-c7f7-4124-b57f-86982d86d479", # "symbol": "ASTER", # "side": "ask", # "price": "1.836", # "initial_amount": "85.33", # "filled_amount": "0", # "cancelled_amount": "0", # "stop_price": null, # "order_type": "limit", # "stop_parent_order_id": null, # "reduce_only": False, # "created_at": 1759224706737, # "updated_at": 1759224706737 # } # ], # # fetchOrders # [ # { # "order_id": 315992721, # "client_order_id": "ade", # "symbol": "XPL", # "side": "ask", # "initial_price": "1.0865", # "average_filled_price": "0", # "amount": "984", # "filled_amount": "0", # "order_status": "open", # "order_type": "limit", # "stop_price": null, # "stop_parent_order_id": null, # "reduce_only": False, # "reason": null, # "created_at": 1759224893638, # "updated_at": 1759224893638 # }, # ] # # fetchOrder # { # "history_id": 641452639, # "order_id": 315992721, # "client_order_id": "ade1aa6...", # "symbol": "XPL", # "side": "ask", # "price": "1.0865", # "initial_amount": "984", # "filled_amount": "0", # "cancelled_amount": "984", # "event_type": "cancel", # "order_type": "limit", # "order_status": "cancelled", # "stop_price": null, # "stop_parent_order_id": null, # "reduce_only": False, # "created_at": 1759224895038 # } # # websocket watchOrders # { # "i": 1559665358, # "I": null, # "u": "BrZp5bidJ3WUvceSq7X78bhjTfZXeezzGvGEV4hAYKTa", # "s": "BTC", # "d": "bid", # "p": "89501", # "ip": "89501", # "lp": "89501", # "a": "0.00012", # "f": "0.00012", # "oe": "fulfill_limit", # "os": "filled", # "ot": "limit", # "sp": null, # "si": null, # "r": False, # "ct": 1765017049008, # "ut": 1765017219639, # "li": 1559696133 # } # marketId = self.safe_string_2(order, 'symbol', 's') symbol = None if symbol is not None: market = self.safe_market(marketId, market) symbol = market['symbol'] timestamp = self.safe_integer_2(order, 'created_at', 'ct') status = self.safe_string_2(order, 'order_status', 'os', 'open') # open if method is fetchOpenOrders side = self.safe_string(order, 'side', 'd') if side is not None: side = 'buy' if (side == 'bid') else 'sell' totalAmount = self.safe_string_2(order, 'initial_amount', 'a') filledAmount = self.safe_string_2(order, 'filled_amount', 'f') remaining = Precise.string_sub(totalAmount, filledAmount) return self.safe_order({ 'info': order, 'id': self.safe_string_2(order, 'order_id', 'i'), 'clientOrderId': self.safe_string_2(order, 'client_order_id', 'I'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': None, 'lastUpdateTimestamp': self.safe_integer_2(order, 'updated_at', 'ut'), 'symbol': symbol, 'type': self.parse_order_type(self.safe_string_lower_2(order, 'order_type', 'ot')), 'timeInForce': None, 'postOnly': None, 'reduceOnly': self.safe_bool_2(order, 'reduce_only', 'r'), 'side': side, 'price': self.safe_string_2(order, 'price', 'lp'), 'triggerPrice': self.safe_number_2(order, 'stop_price', 'sp'), 'amount': totalAmount, 'cost': None, 'average': self.safe_string_2(order, 'average_filled_price', 'p'), 'filled': filledAmount, 'remaining': remaining, 'status': self.parse_order_status(status), 'fee': None, 'trades': None, }, market) def fetch_position(self, symbol: str, params={}): """ fetch data on an open position https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-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 :param str [params.account]: will default to walletAddress if not provided :returns dict: a `position structure ` """ positions = self.fetch_positions([symbol], params) return self.safe_dict(positions, 0, {}) def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]: """ fetch all open positions https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-positions :param str[] [symbols]: list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns dict[]: a list of `position structure ` """ self.load_markets() userAddress = None userAddress, params = self.handle_origin_and_single_address('fetchPositions', params) symbols = self.market_symbols(symbols) request: dict = { 'account': userAddress, } response = self.publicGetPositions(self.extend(request, params)) # { # "success": True, # "data": [ # { # "symbol": "AAVE", # "side": "ask", # "amount": "223.72", # "entry_price": "279.283134", # "margin": "0", # only shown for isolated margin # "funding": "13.159593", # "isolated": False, # "created_at": 1754928414996, # "updated_at": 1759223365538 # } # ], # "error": null, # "code": null, # "last_order_id": 1557431179 # } data = self.safe_list(response, 'data', []) result = [] for i in range(0, len(data)): result.append(self.parse_position(data[i], None)) return self.filter_by_array_positions(result, 'symbol', symbols, False) def parse_position(self, position: dict, market: Market = None): # # { # "symbol": "AAVE", # "side": "ask", # "amount": "223.72", # "entry_price": "279.283134", # "margin": "0", # only shown for isolated margin # "funding": "13.159593", # "isolated": False, # "created_at": 1754928414996, # "updated_at": 1759223365538 # } # marketId = self.safe_string(position, 'symbol') market = self.safe_market(marketId, market) symbol = market['symbol'] margin = self.safe_string(position, 'margin') marginMode = 'isolated' if (margin is not None and margin != '0') else 'cross' isIsolated = (marginMode == 'isolated') side = self.safe_string(position, 'side') if side is not None: side = 'long' if (side == 'bid') else 'short' createdAt = self.safe_integer(position, 'created_at') return self.safe_position({ 'info': position, 'id': None, 'symbol': symbol, 'timestamp': createdAt, 'datetime': self.iso8601(createdAt), 'isolated': isIsolated, 'hedged': None, 'side': side, 'contracts': self.safe_number(position, 'amount'), 'contractSize': None, 'entryPrice': self.safe_number(position, 'entry_price'), 'markPrice': None, 'notional': None, 'leverage': None, 'collateral': margin, 'initialMargin': None, 'maintenanceMargin': None, 'initialMarginPercentage': None, 'maintenanceMarginPercentage': None, 'unrealizedPnl': None, 'liquidationPrice': None, 'marginMode': marginMode, 'percentage': None, }) def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}): """ set margin mode(symbol) https://docs.pacifica.fi/api-documentation/api/rest-api/account/update-margin-mode :param str marginMode: margin mode must be either [isolated, cross] :param str symbol: unified market symbol of the market the position is held in, default is None :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.expiryWindow]: time to live in milliseconds :returns dict: response from the exchange """ operationType = 'update_margin_mode' if symbol is None: raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument') self.load_markets() market = self.market(symbol) isIsolated = (marginMode == 'isolated') sigPayload: dict = { 'symbol': market['id'], 'is_isolated': isIsolated, } request = self.post_action_request(operationType, sigPayload, params) params = self.omit(params, ['expiryWindow']) response = self.privatePostAccountMargin(request) # { # "success": True # } return response def set_leverage(self, leverage: int, symbol: Str = None, params={}): """ set the level of leverage for a market https://docs.pacifica.fi/api-documentation/api/rest-api/account/update-leverage :param float leverage: the rate of leverage :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.expiryWindow]: time to live in milliseconds :returns dict: response from the exchange """ operationType = 'update_leverage' if symbol is None: raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument') self.load_markets() market = self.market(symbol) sigPayload: dict = { 'symbol': market['id'], 'leverage': leverage, } request = self.post_action_request(operationType, sigPayload, params) params = self.omit(params, ['expiryWindow']) response = self.privatePostAccountLeverage(request) # { # "success": True # } return response def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction: """ make a withdrawal(only support native USDC) https://docs.pacifica.fi/api-documentation/api/rest-api/account/request-withdrawal :param str code: unified currency code :param float amount: the amount to withdraw :param str address: the address to withdraw to :param str tag: :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.expiryWindow]: time to live in milliseconds :returns dict: a `transaction structure ` """ operationType = 'withdraw' self.load_markets() self.check_address(address) sigPayload: dict = { 'amount': str(amount), } request = self.post_action_request(operationType, sigPayload, params) params = self.omit(params, ['expiryWindow']) response = self.privatePostAccountWithdraw(self.extend(request, params)) return {'info': response} def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface: """ fetch the trading fees for a market https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-info :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :returns dict: a `fee structure ` """ self.load_markets() userAddress = None userAddress, params = self.handle_origin_and_single_address('fetchTradingFee', params) market = self.market(symbol) request: dict = { 'account': userAddress, } response = self.publicGetAccount(self.extend(request, params)) # { # "success": True, # "data": { # "balance": "2000.000000", # "fee_level": 0, # "maker_fee": "0.00015", # "taker_fee": "0.0004", # "account_equity": "2150.250000", # "available_to_spend": "1800.750000", # "available_to_withdraw": "1500.850000", # "pending_balance": "0.000000", # "total_margin_used": "349.500000", # "cross_mmr": "420.690000", # "positions_count": 2, # "orders_count": 3, # "stop_orders_count": 1, # "updated_at": 1716200000000, # "use_ltp_for_stop_orders": False # }, # "error": null, # "code": null # } data: dict = self.safe_dict(response, 'data', {}) return self.parse_trading_fee(data, market) def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface: # # { # "balance": "2000.000000", # "fee_level": 0, # "maker_fee": "0.00015", # "taker_fee": "0.0004", # "account_equity": "2150.250000", # "available_to_spend": "1800.750000", # "available_to_withdraw": "1500.850000", # "pending_balance": "0.000000", # "total_margin_used": "349.500000", # "cross_mmr": "420.690000", # "positions_count": 2, # "orders_count": 3, # "stop_orders_count": 1, # "updated_at": 1716200000000, # "use_ltp_for_stop_orders": False # } # # symbol = self.safe_symbol(None, market) return { 'info': fee, 'symbol': symbol, 'maker': self.safe_number(fee, 'maker_fee'), 'taker': self.safe_number(fee, 'taker_fee'), 'percentage': None, 'tierBased': None, } def fetch_open_interests(self, symbols: Strings = None, params={}): """ Retrieves the open interest for a list of symbols :param str[] [symbols]: 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() symbols = self.market_symbols(symbols) swapMarkets = self.fetch_swap_markets() return self.parse_open_interests(swapMarkets, symbols) def fetch_open_interest(self, symbol: str, params={}): """ retrieves the open interest of a contract trading pair :param str symbol: unified CCXT market symbol :param dict [params]: exchange specific parameters :returns dict: an `open interest structure ` """ symbol = self.symbol(symbol) self.load_markets() ois = self.fetch_open_interests([symbol], params) return ois[symbol] def parse_open_interest(self, interest, market: Market = None): # # { # "funding": "0.00010529", # "mark": "1.084819", # "mid": "1.08615", # "next_funding": "0.00011096", # "open_interest": "3634796", # "oracle": "1.084524", # "symbol": "XPL", # "timestamp": 1759222967974, # "volume_24h": "20896698.0672", # "yesterday_price": "1.3412" # } # marketId = self.safe_string(interest, 'symbol') symbol = None if marketId is not None: market = self.safe_market(marketId, market) symbol = market['symbol'] interestValue = None markPrice = self.safe_string(interest, 'mark') openInterest = self.safe_string(interest, 'open_interest') if (openInterest is not None) and (markPrice is not None): interestValue = Precise.string_mul(openInterest, markPrice) timestamp = self.safe_integer(interest, 'timestamp') return self.safe_open_interest({ 'symbol': self.safe_symbol(symbol), 'openInterestAmount': self.parse_number(openInterest), 'openInterestValue': self.parse_number(interestValue), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'info': interest, }, market) def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]: """ fetch the history of changes, actions done by the user or operations that altered the balance of the user https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-balance-history :param str [code]: unified currency code :param int [since]: timestamp in ms of the earliest ledger entry :param int [limit]: max number of ledger entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.account]: will default to walletAddress if not provided :param str [params.cursor]: pagination cursor from prev request(manual use) :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 `ledger structure ` """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate', False) userAddress = None userAddress, params = self.handle_origin_and_single_address('fetchLedger', params) defaultLimit = 100 # Default max limit if paginate: return self.fetch_paginated_call_cursor('fetchLedger', code, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit) request: dict = { 'account': userAddress, } if limit is not None: request['limit'] = limit response = self.publicGetAccountBalanceHistory(self.extend(request, params)) # { # "success": True, # "data": [ # { # "amount": "100.000000", # "balance": "1200.000000", # "pending_balance": "0.000000", # "event_type": "deposit", # "created_at": 1716200000000 # } # ... # ], # "next_cursor": "11114Lz77", # "has_more": True # } data = self.add_pagination_cursor_to_result(response) return self.parse_ledger(data, None, since, limit) def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry: # # { # "amount": "100.000000", # "balance": "1200.000000", # "pending_balance": "0.000000", # "event_type": "deposit", # "created_at": 1716200000000 # } # timestamp = self.safe_integer(item, 'created_at') type = self.safe_string(item, 'event_type') amount = self.safe_string(item, 'amount') balance = self.safe_string(item, 'balance') return self.safe_ledger_entry({ 'info': item, 'id': None, 'direction': None, 'account': None, 'referenceAccount': None, 'referenceId': None, 'type': self.parse_ledger_entry_type(type), 'currency': None, 'amount': self.parse_number(amount), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'before': None, 'after': self.parse_number(balance), 'status': None, 'fee': None, }, currency) def parse_ledger_entry_type(self, type): ledgerType: dict = { 'subaccount_transfer': 'transfer', 'deposit': 'transaction', 'deposit_release': 'transaction', 'withdraw': 'transaction', 'trade': 'trade', 'market_liquidation': 'trade', 'backstop_liquidation': 'trade', 'adl_liquidation': 'trade', 'funding': 'funding', 'fee': 'fee', 'rebate': 'rebate', 'cashback': 'cashback', 'referral': 'referral', 'airdrop': 'airdrop', 'payout': 'payout', } return self.safe_string(ledgerType, type, type) 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 :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 str [params.account]: will default to walletAddress if not provided :param str [params.cursor]: pagination cursor from prev request :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :returns dict: a `funding history structure ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) paginate = False paginate, params = self.handle_option_and_params(params, 'fetchFundingHistory', 'paginate', False) userAddress = None userAddress, params = self.handle_origin_and_single_address('fetchFundingHistory', params) request: dict = { 'account': userAddress, } if limit is not None: request['limit'] = limit defaultLimit = 100 if paginate: return self.fetch_paginated_call_cursor('fetchFundingHistory', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit) response = self.publicGetFundingHistory(self.extend(request, params)) # { # "success": True, # "data": [ # { # "history_id": 2287920, # "symbol": "PUMP", # "side": "ask", # "amount": "39033804", # "payout": "2.617479", # "rate": "0.0000125", # "created_at": 1759222804122 # }, # ... # ], # "next_cursor": "11114Lz77", # "has_more": True # } data = self.add_pagination_cursor_to_result(response) return self.parse_incomes(data, market, since, limit) def parse_income(self, income, market: Market = None): # # { # "history_id": 2287920, # "symbol": "PUMP", # "side": "ask", # "amount": "39033804", # "payout": "2.617479", # "rate": "0.0000125", # "created_at": 1759222804122 # } # id = self.safe_string(income, 'history_id') timestamp = self.safe_integer(income, 'created_at') marketId = self.safe_string(income, 'symbol') market = self.safe_market(marketId, market) symbol = market['symbol'] amount = self.safe_string(income, 'amount') code = self.safe_currency_code('USDC') rate = self.safe_number(income, 'rate') return { 'info': income, 'symbol': symbol, 'code': code, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'id': id, 'amount': self.parse_number(amount), 'rate': rate, } def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry: """ transfer currency internally between wallets on the same account https://docs.pacifica.fi/api-documentation/api/rest-api/subaccounts/subaccount-fund-transfer :param str code: unified currency code :param float amount: amount to transfer :param str fromAccount: account to transfer from *spot, swap* :param str toAccount: account to transfer to *swap, spot or address* :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.expiryWindow]: time to live in milliseconds :returns dict: a `transfer structure ` """ operationType = 'transfer_funds' sigPayload = { 'to_account': toAccount, 'amount': amount, } request = self.post_action_request(operationType, sigPayload, params) params = self.omit(params, ['expiryWindow']) response = self.privatePostAccountSubaccountTransfer(self.extend(request, params)) # # { # "success": True, # "data": { # "success": True, # "error": null # }, # "error": null, # "code": null # } # data = self.safe_dict(response, 'data', {}) return self.parse_transfer(data) def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry: # # { # "success": True, # "data": { # "success": True, # "error": null # }, # "error": null, # "code": null # } # return { 'info': transfer, 'id': None, 'timestamp': None, 'datetime': None, 'currency': None, 'amount': None, 'fromAccount': None, 'toAccount': None, 'status': 'ok', } def create_sub_account(self, name: str, params={}): """ creates a sub-account under the main account :param str name: unused argument :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.expiryWindow]: time to live in milliseconds :param str [params.subAccountAddress]: - The public key(address) of the sub-account to use for creation :param str [params.subAccountPrivateKey]: - The private key of the sub-account to use for creation :returns dict: a response object """ finalHeaders = {} agentAddress = None agentAddress, params = self.handle_option('createSubAccount', 'agentAddress', None) originAddress = None originAddress, params = self.handle_origin_and_single_address('createSubAccount', params) if originAddress is None: raise ArgumentsRequired(self.id + ' createSubAccount() requires "originAddress" in params or "walletAddress" in requiredCredentials') if agentAddress is not None: finalHeaders['agent_wallet'] = agentAddress subAccountAddress = None subAccountAddress, params = self.handle_option_and_params(params, 'createSubAccount', 'subAccountAddress') subAccountPrivateKey = None subAccountPrivateKey, params = self.handle_option_and_params(params, 'createSubAccount', 'subAccountPrivateKey') if subAccountAddress is None: raise ArgumentsRequired(self.id + ' createSubAccount() requires a "subAccountAddress"!') if subAccountPrivateKey is None: raise ArgumentsRequired(self.id + ' createSubAccount() requires a "subAccountPrivateKey"!') timestamp = self.milliseconds() expiryWindow = None expiryWindow, params = self.handle_option_and_params_2(params, 'createSubAccount', 'expiryWindow', 'expiry_window', 5000) subaccountSignatureHeader = { 'timestamp': timestamp, 'expiry_window': expiryWindow, 'type': 'subaccount_initiate', } subSigPayload = { 'account': originAddress, } subaccountSignature = self.sign_message(subaccountSignatureHeader, subSigPayload, subAccountPrivateKey) mainSignatureHeader = { 'timestamp': timestamp, 'expiry_window': expiryWindow, 'type': 'subaccount_confirm', } mainSigPayload = { 'signature': subaccountSignature, } main_signature = self.sign_message(mainSignatureHeader, mainSigPayload, self.privateKey) finalHeaders['main_account'] = originAddress finalHeaders['subaccount'] = subAccountAddress finalHeaders['sub_signature'] = subaccountSignature finalHeaders['main_signature'] = main_signature finalHeaders['timestamp'] = timestamp finalHeaders['expiry_window'] = expiryWindow request = finalHeaders response = self.privatePostAccountSubaccountCreate(request) # # { # "success": True, # "data": null, # "error": null, # "code": null, # } # return response def bind_agent_wallet(self, agentAddress: str, params={}): operationType = 'bind_agent_wallet' sigPayload = { 'agent_wallet': agentAddress, } request = self.post_action_request(operationType, sigPayload, params) return self.privatePostAgentBind(self.extend(request, params)) def create_api_key(self, params={}): operationType = 'create_api_key' sigPayload = {} request = self.post_action_request(operationType, sigPayload, params) return self.privatePostAccountApiKeysCreate(self.extend(request, params)) def revoke_api_key(self, apiKey: str, params={}): operationType = 'revoke_api_key' sigPayload = { 'api_key': apiKey, } request = self.post_action_request(operationType, sigPayload, params) return self.privatePostAccountApiKeysRevoke(self.extend(request, params)) def fetch_api_keys(self, params={}): operationType = 'list_api_keys' sigPayload = {} request = self.post_action_request(operationType, sigPayload, params) return self.privatePostAccountApiKeys(self.extend(request, params)) def approve_builder_code(self, builderCode: str, maxFeeRate: str, params={}): operationType = 'approve_builder_code' sigPayload = { 'builder_code': builderCode, 'max_fee_rate': maxFeeRate, } request = self.post_action_request(operationType, sigPayload, params) return self.privatePostAccountBuilderCodesApprove(self.extend(request, params)) def fetch_builder_approvals(self, address: str): request = { 'account': address, } return self.publicGetAccountBuilderCodesApprovals(self.extend(request)) def revoke_builder_code(self, builderCode: str, params={}): operationType = 'revoke_builder_code' sigPayload = { 'builder_code': builderCode, } request = self.post_action_request(operationType, sigPayload, params) return self.privatePostAccountBuilderCodesRevoke(self.extend(request, params)) def handle_origin_and_single_address(self, methodName: str, params: dict): address = None address, params = self.handle_param_string_2(params, 'account', 'address', None) # self is for get endpoints that accept account or address if address is not None: return [address, params] address1 = self.walletAddress if address1 is not None: return [address1, params] raise ArgumentsRequired(self.id + ' ' + methodName + '() requires address either as "exchange.walletAddress = ..." or or "address" in params') def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody): if response is None: return None # fallback to default error handler # # {"success":false,"data":null,"error":"Beta access required. Signer must redeem a valid beta code.","code":403} # {"success":false,"data":null,"error":"Agent not authorized for account","code":400} # {"success":false,"data":null,"error":"Internal server error","code":500} # inCode = self.safe_integer(response, 'code') # actually if all ok -> code = None or code = 200 message = self.safe_string(response, 'error') error = None if inCode is None or inCode == 200: error = False else: error = True nonEmptyMessage = ((message is not None) and (message != '')) if error or nonEmptyMessage: feedback = self.id + ' ' + body self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback) # Try deeper catch first self.throw_exactly_matched_exception(self.exceptions['exact'], inCode, feedback) self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback) raise ExchangeError(feedback) # unknown message return None def sign(self, path, api='public', method='GET', params={}, headers=None, body=None): isTestnet = self.isSandboxModeEnabled urlKey = 'test' if (isTestnet) else 'api' host = self.implode_hostname(self.urls[urlKey][api]) url = host + '/api/' + self.version + '/' + self.implode_params(path, params) params = self.omit(params, self.extract_params(path)) paramsLen = params headers = { 'Content-Type': 'application/json', } if method == 'GET' and paramsLen: url += '?' + self.urlencode(params) headers['Accept'] = '*/*' if method == 'POST': body = self.json(params) if self.handle_option('sign', 'apiKey', None) is not None: headers['PF-API-KEY'] = self.options['apiKey'] return {'url': url, 'method': method, 'body': body, 'headers': headers} def calculate_rate_limiter_cost(self, api, method, path, params, config={}): cost = self.safe_string(config, 'cost', '1') costNumber = self.parse_number(cost) # 1 is normal POST/GET, 0.5 is cancels, 3-12 is heavy GET if costNumber > 1: if self.handle_option(method, 'apiKey', None) is not None: costWithKey = self.handle_option( method, 'maxCostHugeWithApiKey', 3 ) return costWithKey return costNumber def sort_json_keys(self, value: Any) -> Any: if isinstance(value, dict): result = {} keys = list(value.keys()) sortedKeys = self.sort(keys) for i in range(0, len(sortedKeys)): key = sortedKeys[i] result[key] = self.sort_json_keys(value[key]) return result elif isinstance(value, list): result = [] for i in range(0, len(value)): result.append(self.sort_json_keys(value[i])) return result else: return value def prepare_message(self, header: dict, payload: dict) -> str: if header['type'] is None or header['timestamp'] is None or header['expiry_window'] is None: raise ArgumentsRequired(self.id + ' prepareMessage() requires type, timestamp, expiry_window in header') data = self.extend(header, {'data': payload}) sorted = self.sort_json_keys(data) return self.json(sorted) def sign_message(self, header: dict, payload: dict, privateKey: str) -> str: message = self.prepare_message(header, payload) messageBytes = self.encode(message) secretBytes = self.base58_to_binary(privateKey) seed = self.array_slice(secretBytes, 0, 32) signatureBase64 = self.eddsa(messageBytes, seed, 'ed25519') signatureBinary = self.base64_to_binary(signatureBase64) signatureBase58 = self.binary_to_base58(signatureBinary) return signatureBase58 def post_action_request(self, operationType: Str, sigPayload: dict, params: dict) -> dict: self.check_required_credentials() # check credentials every post action if operationType == 'None': raise ArgumentsRequired(self.id + ' action: ' + operationType + ' postActionRequest() requires "operationType"') if not self.isSandboxModeEnabled: # At self stage, building codes are mostly only on the mainnet. useBuilder = self.handle_option('postActionRequest', 'builderFee', True) builderCode = None if useBuilder: builderCode = self.handle_option('postActionRequest', 'builderCode') if builderCode is not None: isOperationSupportBuilder = self.safe_bool(self.options['builderSupportOperations'], operationType, False) if isOperationSupportBuilder: sigPayload['builder_code'] = builderCode expiryWindow = None expiryWindow, params = self.handle_option_and_params_2(params, 'postActionRequest', 'expiryWindow', 'expiry_window', 5000) timestamp = self.safe_integer(params, 'timestamp', self.milliseconds()) signatureHeader = { 'timestamp': timestamp, 'expiry_window': expiryWindow, 'type': operationType, } signature = self.sign_message(signatureHeader, sigPayload, self.privateKey) finalHeaders = {} agentAddress = None agentAddress, params = self.handle_option_and_params(params, 'postActionRequest', 'agentAddress') originAddress = None originAddress, params = self.handle_origin_and_single_address('postActionRequest', params) if originAddress is None: raise ArgumentsRequired(self.id + ' action: ' + operationType + ' postActionRequest() requires "originAddress" in params or "walletAddress" in requiredCredentials') finalHeaders['account'] = originAddress if agentAddress is not None: finalHeaders['agent_wallet'] = agentAddress finalHeaders['signature'] = signature finalHeaders['timestamp'] = self.safe_integer(signatureHeader, 'timestamp') finalHeaders['expiry_window'] = self.safe_integer(signatureHeader, 'expiry_window') request = self.extend(finalHeaders, sigPayload) return request