3099 lines
129 KiB
Python
3099 lines
129 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
|
|
from ccxt.base.exchange import Exchange
|
|
from ccxt.abstract.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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=order-book-structure>` 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 <https://docs.ccxt.com/?id=trade-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=trade-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, '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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=order-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=order-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=funding-rate-history-structure>` 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 <https://docs.ccxt.com/?id=funding-rate-history-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=ticker-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=order-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=order-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=order-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=order-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=order-structure>`
|
|
"""
|
|
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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=ledger-entry-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 <https://docs.ccxt.com/?id=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 <https://docs.ccxt.com/?id=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
|