1899 lines
82 KiB
Python
1899 lines
82 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.apex import ImplicitAPI
|
|
import hashlib
|
|
import math
|
|
from ccxt.base.types import Account, Any, Balances, Currencies, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, Trade, MarketInterface, TransferEntry
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import RateLimitExceeded
|
|
from ccxt.base.decimal_to_precision import TRUNCATE
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class apex(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(apex, self).describe(), {
|
|
'id': 'apex',
|
|
'name': 'Apex',
|
|
'countries': [],
|
|
'version': 'v3',
|
|
'rateLimit': 20, # 600 requests per minute, 10 request per second
|
|
'certified': False,
|
|
'pro': True,
|
|
'dex': True,
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': False,
|
|
'margin': False,
|
|
'swap': True,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'borrowCrossMargin': False,
|
|
'borrowIsolatedMargin': False,
|
|
'borrowMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelAllOrdersAfter': False,
|
|
'cancelOrder': True,
|
|
'cancelOrders': False,
|
|
'cancelOrdersForSymbols': False,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createMarketBuyOrderWithCost': False,
|
|
'createMarketOrderWithCost': False,
|
|
'createMarketSellOrderWithCost': False,
|
|
'createOrder': True,
|
|
'createOrders': False,
|
|
'createPostOnlyOrder': True,
|
|
'createReduceOnlyOrder': True,
|
|
'createStopOrder': True,
|
|
'createTriggerOrder': True,
|
|
'editOrder': False,
|
|
'fetchAccounts': True,
|
|
'fetchAllGreeks': False,
|
|
'fetchBalance': True,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRate': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchBorrowRates': False,
|
|
'fetchBorrowRatesPerSymbol': False,
|
|
'fetchCanceledAndClosedOrders': False,
|
|
'fetchCanceledOrders': False,
|
|
'fetchClosedOrders': False,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': True,
|
|
'fetchDepositAddress': False,
|
|
'fetchDepositAddresses': False,
|
|
'fetchDeposits': False,
|
|
'fetchDepositWithdrawFee': False,
|
|
'fetchDepositWithdrawFees': False,
|
|
'fetchFundingHistory': True,
|
|
'fetchFundingRate': False,
|
|
'fetchFundingRateHistory': True,
|
|
'fetchFundingRates': False,
|
|
'fetchGreeks': False,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchLedger': False,
|
|
'fetchLeverage': False,
|
|
'fetchLeverageTiers': False,
|
|
'fetchLiquidations': False,
|
|
'fetchMarginMode': False,
|
|
'fetchMarketLeverageTiers': False,
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMyLiquidations': False,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterest': True,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenInterests': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOption': False,
|
|
'fetchOptionChain': False,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrders': True,
|
|
'fetchOrderTrades': True,
|
|
'fetchPosition': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': True,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': True,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': False,
|
|
'fetchTradingFees': False,
|
|
'fetchTransfer': True,
|
|
'fetchTransfers': True,
|
|
'fetchVolatilityHistory': False,
|
|
'fetchWithdrawal': False,
|
|
'fetchWithdrawals': False,
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'sandbox': True,
|
|
'setLeverage': True,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'transfer': False,
|
|
'withdraw': False,
|
|
},
|
|
'timeframes': {
|
|
'1m': '1',
|
|
'5m': '5',
|
|
'15m': '15',
|
|
'30m': '30',
|
|
'1h': '60',
|
|
'2h': '120',
|
|
'4h': '240',
|
|
'6h': '360',
|
|
'12h': '720',
|
|
'1d': 'D',
|
|
'1w': 'W',
|
|
'1M': 'M',
|
|
},
|
|
'hostname': 'omni.apex.exchange',
|
|
'urls': {
|
|
'logo': 'https://github.com/user-attachments/assets/fef8f2f7-4265-46aa-965e-33a91881cb00',
|
|
'api': {
|
|
'public': 'https://{hostname}/api',
|
|
'private': 'https://{hostname}/api',
|
|
},
|
|
'test': {
|
|
'public': 'https://testnet.omni.apex.exchange/api',
|
|
'private': 'https://testnet.omni.apex.exchange/api',
|
|
},
|
|
'www': 'https://apex.exchange/',
|
|
'doc': 'https://api-docs.pro.apex.exchange',
|
|
'fees': 'https://apex-pro.gitbook.io/apex-pro/apex-omni-live-now/trading-perpetual-contracts/trading-fees',
|
|
'referral': 'https://omni.apex.exchange/trade',
|
|
},
|
|
'api': {
|
|
'public': {
|
|
'get': {
|
|
'v3/symbols': 1,
|
|
'v3/history-funding': 1,
|
|
'v3/ticker': 1,
|
|
'v3/klines': 1,
|
|
'v3/trades': 1,
|
|
'v3/depth': 1,
|
|
'v3/time': 1,
|
|
'v3/data/all-ticker-info': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'v3/account': 1,
|
|
'v3/account-balance': 1,
|
|
'v3/fills': 1,
|
|
'v3/order-fills': 1,
|
|
'v3/order': 1,
|
|
'v3/history-orders': 1,
|
|
'v3/order-by-client-order-id': 1,
|
|
'v3/funding': 1,
|
|
'v3/historical-pnl': 1,
|
|
'v3/open-orders': 1,
|
|
'v3/transfers': 1,
|
|
'v3/transfer': 1,
|
|
},
|
|
'post': {
|
|
'v3/delete-open-orders': 1,
|
|
'v3/delete-client-order-id': 1,
|
|
'v3/delete-order': 1,
|
|
'v3/order': 1,
|
|
'v3/set-initial-margin-rate': 1,
|
|
'v3/transfer-out': 1,
|
|
'v3/contract-transfer-out': 1,
|
|
},
|
|
},
|
|
},
|
|
'httpExceptions': {
|
|
'403': RateLimitExceeded, # Forbidden -- You request too many times
|
|
},
|
|
'exceptions': {
|
|
# Uncodumented explanation of error strings:
|
|
# - oc_diff: order cost needed to place self order
|
|
# - new_oc: total order cost of open orders including the order you are trying to open
|
|
# - ob: order balance - the total cost of current open orders
|
|
# - ab: available balance
|
|
'exact': {
|
|
'20006': 'apikey sign error', # apikey sign error
|
|
'20016': 'request para error', # apikey sign error
|
|
'10001': BadRequest,
|
|
},
|
|
'broad': {
|
|
'ORDER_PRICE_MUST_GREETER_ZERO': InvalidOrder,
|
|
'ORDER_POSSIBLE_LEAD_TO_ACCOUNT_LIQUIDATED': InvalidOrder,
|
|
'ORDER_WITH_THIS_PRICE_CANNOT_REDUCE_POSITION_ONLY': InvalidOrder,
|
|
},
|
|
},
|
|
'fees': {
|
|
'swap': {
|
|
'taker': self.parse_number('0.0005'),
|
|
'maker': self.parse_number('0.0002'),
|
|
},
|
|
},
|
|
'requiredCredentials': {
|
|
'apiKey': True,
|
|
'secret': True,
|
|
'walletAddress': False,
|
|
'privateKey': False,
|
|
'password': True,
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'commonCurrencies': {},
|
|
'options': {
|
|
'defaultType': 'swap',
|
|
'defaultSlippage': 0.05,
|
|
'brokerId': '6956',
|
|
},
|
|
'features': {
|
|
'default': {
|
|
'sandbox': True,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerPriceType': None,
|
|
'triggerDirection': False,
|
|
'stopLossPrice': False, # todo
|
|
'takeProfitPrice': False, # todo
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': True,
|
|
'PO': True,
|
|
'GTD': True,
|
|
},
|
|
'hedged': False,
|
|
'selfTradePrevention': False,
|
|
'trailing': True, # todo unify
|
|
'leverage': False,
|
|
'marketBuyByCost': False,
|
|
'marketBuyRequiresPrice': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None,
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 500,
|
|
'daysBack': 100000,
|
|
'untilDays': 100000,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': {
|
|
'marginMode': False,
|
|
'limit': 100,
|
|
'daysBack': 100000,
|
|
'untilDays': 100000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchClosedOrders': None,
|
|
'fetchOHLCV': {
|
|
'limit': 200,
|
|
},
|
|
},
|
|
'swap': {
|
|
'linear': {
|
|
'extends': 'default',
|
|
},
|
|
'inverse': None,
|
|
},
|
|
},
|
|
})
|
|
|
|
def fetch_time(self, params={}):
|
|
"""
|
|
fetches the current integer timestamp in milliseconds from the exchange server
|
|
|
|
https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-system-time-v3
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int: the current integer timestamp in milliseconds from the exchange server
|
|
"""
|
|
response = self.publicGetV3Time(params)
|
|
data = self.safe_dict(response, 'data', {})
|
|
#
|
|
# {
|
|
# "data": {
|
|
# "time": 1738837534454
|
|
# }
|
|
# }
|
|
return self.safe_integer(data, 'time')
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
#
|
|
# {
|
|
# "totalEquityValue": "100.000000",
|
|
# "availableBalance": "100.000000",
|
|
# "initialMargin": "100.000000",
|
|
# "maintenanceMargin": "100.000000",
|
|
# "symbolToOraclePrice": {
|
|
# "BTC-USDC": {
|
|
# "oraclePrice": "20000",
|
|
# "createdTime": 124566
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
timestamp = self.milliseconds()
|
|
result: dict = {
|
|
'info': response,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
}
|
|
code = 'USDT'
|
|
account = self.account()
|
|
account['free'] = self.safe_string(response, 'availableBalance')
|
|
account['total'] = self.safe_string(response, 'totalEquityValue')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for account info
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-retrieve-user-account-balance
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = self.privateGetV3AccountBalance(params)
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_balance(data)
|
|
|
|
def parse_account(self, account: dict) -> Account:
|
|
accountId = self.safe_string(account, 'id', '0')
|
|
return {
|
|
'id': accountId,
|
|
'type': None,
|
|
'code': None,
|
|
'info': account,
|
|
}
|
|
|
|
def fetch_account(self, params={}) -> Account:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-retrieve-user-account-data
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = self.privateGetV3Account(params)
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_account(data)
|
|
|
|
def fetch_currencies(self, params={}) -> Currencies:
|
|
"""
|
|
fetches all available currencies on an exchange
|
|
|
|
https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-all-config-data-v3
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an associative dictionary of currencies
|
|
"""
|
|
response = self.publicGetV3Symbols(params)
|
|
data = self.safe_dict(response, 'data', {})
|
|
spotConfig = self.safe_dict(data, 'spotConfig', {})
|
|
multiChain = self.safe_dict(spotConfig, 'multiChain', {})
|
|
# "spotConfig": {
|
|
# "assets": [
|
|
# {
|
|
# "tokenId": "141",
|
|
# "token": "USDT",
|
|
# "displayName": "Tether USD Coin",
|
|
# "decimals": 18,
|
|
# "showStep": "0.01",
|
|
# "iconUrl": "https://static-pro.apex.exchange/chains/chain_tokens/Ethereum/Ethereum_USDT.svg",
|
|
# "l2WithdrawFee": "0",
|
|
# "enableCollateral": True,
|
|
# "enableCrossCollateral": False,
|
|
# "crossCollateralDiscountRate": null,
|
|
# "isGray": False
|
|
# }
|
|
# ],
|
|
# "multiChain": {
|
|
# "chains": [
|
|
# {
|
|
# "chain": "Arbitrum One",
|
|
# "chainId": "9",
|
|
# "chainType": "0",
|
|
# "l1ChainId": "42161",
|
|
# "chainIconUrl": "https://static-pro.apex.exchange/chains/chain_logos/Arbitrum.svg",
|
|
# "contractAddress": "0x3169844a120c0f517b4eb4a750c08d8518c8466a",
|
|
# "swapContractAddress": "0x9e07b6Aef1bbD9E513fc2Eb8873e311E80B4f855",
|
|
# "stopDeposit": False,
|
|
# "feeLess": False,
|
|
# "gasLess": False,
|
|
# "gasToken": "ETH",
|
|
# "dynamicFee": True,
|
|
# "gasTokenDecimals": 18,
|
|
# "feeGasLimit": 300000,
|
|
# "blockTimeSeconds": 2,
|
|
# "rpcUrl": "https://arb.pro.apex.exchange",
|
|
# "minSwapUsdtAmount": "",
|
|
# "maxSwapUsdtAmount": "",
|
|
# "webRpcUrl": "https://arb.pro.apex.exchange",
|
|
# "webTxUrl": "https://arbiscan.io/tx/",
|
|
# "backupRpcUrl": "https://arb-mainnet.g.alchemy.com/v2/rGlYUbRHtUav5mfeThCPtsV9GLPt2Xq5",
|
|
# "txConfirm": 20,
|
|
# "withdrawGasFeeLess": False,
|
|
# "tokens": [
|
|
# {
|
|
# "decimals": 6,
|
|
# "iconUrl": "https://static-pro.apex.exchange/chains/chain_tokens/Arbitrum/Arbitrum_USDT.svg",
|
|
# "token": "USDT",
|
|
# "tokenAddress": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
|
|
# "pullOff": False,
|
|
# "withdrawEnable": True,
|
|
# "slippage": "",
|
|
# "isDefaultToken": False,
|
|
# "displayToken": "USDT",
|
|
# "needResetApproval": True,
|
|
# "minFee": "2",
|
|
# "maxFee": "40",
|
|
# "feeRate": "0.0001",
|
|
# "maxWithdraw": "",
|
|
# "minDeposit": "",
|
|
# "minWithdraw": "",
|
|
# "maxFastWithdrawAmount": "40000",
|
|
# "minFastWithdrawAmount": "1",
|
|
# "isGray": False
|
|
# },
|
|
# {
|
|
# "decimals": 6,
|
|
# "iconUrl": "https://static-pro.apex.exchange/chains/chain_tokens/Arbitrum/Arbitrum_USDC.svg",
|
|
# "token": "USDC",
|
|
# "tokenAddress": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
|
|
# "pullOff": False,
|
|
# "withdrawEnable": True,
|
|
# "slippage": "",
|
|
# "isDefaultToken": False,
|
|
# "displayToken": "USDC",
|
|
# "needResetApproval": True,
|
|
# "minFee": "2",
|
|
# "maxFee": "20",
|
|
# "feeRate": "0.0001",
|
|
# "maxWithdraw": "",
|
|
# "minDeposit": "",
|
|
# "minWithdraw": "",
|
|
# "maxFastWithdrawAmount": "1",
|
|
# "minFastWithdrawAmount": "1",
|
|
# "isGray": False
|
|
# }
|
|
# ]
|
|
# }
|
|
# ]
|
|
# }
|
|
rows = self.safe_list(spotConfig, 'assets', [])
|
|
chains = self.safe_list(multiChain, 'chains', [])
|
|
result: dict = {}
|
|
for i in range(0, len(rows)):
|
|
currency = rows[i]
|
|
currencyId = self.safe_string(currency, 'token')
|
|
code = self.safe_currency_code(currencyId)
|
|
name = self.safe_string(currency, 'displayName')
|
|
networks: dict = {}
|
|
for j in range(0, len(chains)):
|
|
chain = chains[j]
|
|
tokens = self.safe_list(chain, 'tokens', [])
|
|
for f in range(0, len(tokens)):
|
|
token = tokens[f]
|
|
tokenName = self.safe_string(token, 'token')
|
|
if tokenName == currencyId:
|
|
networkId = self.safe_string(chain, 'chainId')
|
|
networkCode = self.network_id_to_code(networkId)
|
|
networks[networkCode] = {
|
|
'info': chain,
|
|
'id': networkId,
|
|
'network': networkCode,
|
|
'active': None,
|
|
'deposit': not self.safe_bool(chain, 'depositDisable'),
|
|
'withdraw': self.safe_bool(token, 'withdrawEnable'),
|
|
'fee': self.safe_number(token, 'minFee'),
|
|
'precision': self.parse_number(self.parse_precision(self.safe_string(token, 'decimals'))),
|
|
'limits': {
|
|
'withdraw': {
|
|
'min': self.safe_number(token, 'minWithdraw'),
|
|
'max': None,
|
|
},
|
|
'deposit': {
|
|
'min': self.safe_number(chain, 'minDeposit'),
|
|
'max': None,
|
|
},
|
|
},
|
|
}
|
|
networkKeys = list(networks.keys())
|
|
networksLength = len(networkKeys)
|
|
emptyChains = networksLength == 0 # non-functional coins
|
|
valueForEmpty = False if emptyChains else None
|
|
result[code] = self.safe_currency_structure({
|
|
'info': currency,
|
|
'code': code,
|
|
'id': currencyId,
|
|
'type': 'crypto',
|
|
'name': name,
|
|
'active': None,
|
|
'deposit': valueForEmpty,
|
|
'withdraw': valueForEmpty,
|
|
'fee': None,
|
|
'precision': None,
|
|
'limits': {
|
|
'amount': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'withdraw': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'deposit': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'networks': networks,
|
|
})
|
|
return result
|
|
|
|
def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for apex
|
|
|
|
https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-all-config-data-v3
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = self.publicGetV3Symbols(params)
|
|
data = self.safe_dict(response, 'data', {})
|
|
contractConfig = self.safe_dict(data, 'contractConfig', {})
|
|
perpetualContract = self.safe_list(contractConfig, 'perpetualContract', [])
|
|
# {
|
|
# "perpetualContract":[
|
|
# {
|
|
# "baselinePositionValue": "50000.0000",
|
|
# "crossId": 30002,
|
|
# "crossSymbolId": 10,
|
|
# "crossSymbolName": "BTCUSDT",
|
|
# "digitMerge": "0.1,0.2,0.4,1,2",
|
|
# "displayMaxLeverage": "100",
|
|
# "displayMinLeverage": "1",
|
|
# "enableDisplay": True,
|
|
# "enableOpenPosition": True,
|
|
# "enableTrade": True,
|
|
# "fundingImpactMarginNotional": "6",
|
|
# "fundingInterestRate": "0.0003",
|
|
# "incrementalInitialMarginRate": "0.00250",
|
|
# "incrementalMaintenanceMarginRate": "0.00100",
|
|
# "incrementalPositionValue": "50000.0000",
|
|
# "initialMarginRate": "0.01",
|
|
# "maintenanceMarginRate": "0.005",
|
|
# "maxOrderSize": "50",
|
|
# "maxPositionSize": "100",
|
|
# "minOrderSize": "0.0010",
|
|
# "maxMarketPriceRange": "0.025",
|
|
# "settleAssetId": "USDT",
|
|
# "baseTokenId": "BTC",
|
|
# "stepSize": "0.001",
|
|
# "symbol": "BTC-USDT",
|
|
# "symbolDisplayName": "BTCUSDT",
|
|
# "tickSize": "0.1",
|
|
# "maxMaintenanceMarginRate": "0.5000",
|
|
# "maxPositionValue": "5000000.0000",
|
|
# "tagIconUrl": "https://static-pro.apex.exchange/icon/LABLE_HOT.svg",
|
|
# "tag": "HOT",
|
|
# "riskTip": False,
|
|
# "defaultInitialMarginRate": "0.05",
|
|
# "klineStartTime": 0,
|
|
# "maxMarketSizeBuffer": "0.98",
|
|
# "enableFundingSettlement": True,
|
|
# "indexPriceDecimals": 2,
|
|
# "indexPriceVarRate": "0.001",
|
|
# "openPositionOiLimitRate": "0.05",
|
|
# "fundingMaxRate": "0.000234",
|
|
# "fundingMinRate": "-0.000234",
|
|
# "fundingMaxValue": "",
|
|
# "enableFundingMxValue": True,
|
|
# "l2PairId": "50001",
|
|
# "settleTimeStamp": 0,
|
|
# "isPrelaunch": False,
|
|
# "riskLimitConfig": {},
|
|
# "category": "L1"
|
|
# }
|
|
# ]
|
|
# }
|
|
return self.parse_markets(perpetualContract)
|
|
|
|
def parse_market(self, market: dict) -> Market:
|
|
id = self.safe_string(market, 'symbol')
|
|
id2 = self.safe_string(market, 'crossSymbolName')
|
|
quoteId = self.safe_string(market, 'l2PairId')
|
|
baseId = self.safe_string(market, 'baseTokenId')
|
|
quote = self.safe_string(market, 'settleAssetId')
|
|
base = self.safe_currency_code(baseId)
|
|
settleId = self.safe_string(market, 'settleAssetId')
|
|
settle = self.safe_currency_code(settleId)
|
|
symbol = baseId + '/' + quote + ':' + settle
|
|
expiry = 0
|
|
takerFee = self.parse_number('0.0002')
|
|
makerFee = self.parse_number('0.0005')
|
|
return self.safe_market_structure({
|
|
'id': id,
|
|
'id2': id2,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': settle,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': settleId,
|
|
'type': 'swap',
|
|
'spot': False,
|
|
'margin': None,
|
|
'swap': True,
|
|
'future': False,
|
|
'option': False,
|
|
'active': self.safe_bool(market, 'enableTrade'),
|
|
'contract': True,
|
|
'linear': True,
|
|
'inverse': False,
|
|
'taker': takerFee,
|
|
'maker': makerFee,
|
|
'contractSize': self.safe_number(market, 'minOrderSize'),
|
|
'expiry': None if (expiry == 0) else expiry,
|
|
'expiryDatetime': None if (expiry == 0) else self.iso8601(expiry),
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': self.safe_number(market, 'stepSize'),
|
|
'price': self.safe_number(market, 'tickSize'),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': self.safe_number(market, 'displayMinLeverage'),
|
|
'max': self.safe_number(market, 'displayMaxLeverage'),
|
|
},
|
|
'amount': {
|
|
'min': self.safe_number(market, 'minOrderSize'),
|
|
'max': self.safe_number(market, 'maxOrderSize'),
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': None,
|
|
'info': market,
|
|
})
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# "symbol": "BTCUSDT",
|
|
# "price24hPcnt": "0.450141",
|
|
# "lastPrice": "43511.50",
|
|
# "highPrice24h": "43513.50",
|
|
# "lowPrice24h": "29996.00",
|
|
# "markPrice": "43513.50",
|
|
# "indexPrice": "40828.94",
|
|
# "openInterest": "2036854775808",
|
|
# "turnover24h": "5626085.23749999",
|
|
# "volume24h": "169.317",
|
|
# "fundingRate": "0",
|
|
# "predictedFundingRate": "0",
|
|
# "nextFundingTime": "10:00:00",
|
|
# "tradeCount": 100
|
|
# }
|
|
#
|
|
timestamp = self.milliseconds()
|
|
marketId = self.safe_string(ticker, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = self.safe_symbol(marketId, market)
|
|
last = self.safe_string(ticker, 'lastPrice')
|
|
percentage = self.safe_string(ticker, 'price24hPcnt')
|
|
quoteVolume = self.safe_string(ticker, 'turnover24h')
|
|
baseVolume = self.safe_string(ticker, 'volume24h')
|
|
high = self.safe_string(ticker, 'highPrice24h')
|
|
low = self.safe_string(ticker, 'lowPrice24h')
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': high,
|
|
'low': low,
|
|
'bid': None,
|
|
'bidVolume': None,
|
|
'ask': None,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': None,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': percentage,
|
|
'average': None,
|
|
'baseVolume': baseVolume,
|
|
'quoteVolume': quoteVolume,
|
|
'markPrice': self.safe_string(ticker, 'markPrice'),
|
|
'indexPrice': self.safe_string(ticker, 'indexPrice'),
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
|
|
https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-ticker-data-v3
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id2'],
|
|
}
|
|
response = self.publicGetV3Ticker(self.extend(request, params))
|
|
tickers = self.safe_list(response, 'data', [])
|
|
rawTicker = self.safe_dict(tickers, 0, {})
|
|
return self.parse_ticker(rawTicker, market)
|
|
|
|
def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
|
|
https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-ticker-data-v3
|
|
|
|
:param str symbols: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = self.publicGetV3DataAllTickerInfo(params)
|
|
tickers = self.safe_list(response, 'data', [])
|
|
return self.parse_tickers(tickers, symbols)
|
|
|
|
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://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-candlestick-chart-data-v3
|
|
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str timeframe: the length of time each candle represents
|
|
:param int [since]: timestamp in ms of the earliest candle to fetch
|
|
:param int [limit]: the maximum amount of candles to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest candle to fetch
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'interval': self.safe_string(self.timeframes, timeframe, timeframe),
|
|
'symbol': market['id2'],
|
|
}
|
|
if limit is None:
|
|
limit = 200 # default is 200 when requested with `since`
|
|
request['limit'] = limit # max 200, default 200
|
|
request, params = self.handle_until_option('end', request, params, 0.001)
|
|
if since is not None:
|
|
request['start'] = int(math.floor(since / 1000))
|
|
response = self.publicGetV3Klines(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
OHLCVs = self.safe_list(data, market['id2'], [])
|
|
return self.parse_ohlcvs(OHLCVs, market, timeframe, since, limit)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# {
|
|
# "start": 1647511440000,
|
|
# "symbol": "BTC-USD",
|
|
# "interval": "1",
|
|
# "low": "40000",
|
|
# "high": "45000",
|
|
# "open": "45000",
|
|
# "close": "40000",
|
|
# "volume": "1.002",
|
|
# "turnover": "3"
|
|
# } {"s":"BTCUSDT","i":"1","t":1741265880000,"c":"90235","h":"90235","l":"90156","o":"90156","v":"0.052","tr":"4690.4466"}
|
|
#
|
|
return [
|
|
self.safe_integer_n(ohlcv, ['start', 't']),
|
|
self.safe_number_n(ohlcv, ['open', 'o']),
|
|
self.safe_number_n(ohlcv, ['high', 'h']),
|
|
self.safe_number_n(ohlcv, ['low', 'l']),
|
|
self.safe_number_n(ohlcv, ['close', 'c']),
|
|
self.safe_number_n(ohlcv, ['volume', 'v']),
|
|
]
|
|
|
|
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://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-market-depth-v3
|
|
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the maximum amount of order book entries to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id2'],
|
|
}
|
|
if limit is None:
|
|
limit = 100 # default is 200 when requested with `since`
|
|
request['limit'] = limit # max 100, default 100
|
|
response = self.publicGetV3Depth(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "a": [
|
|
# [
|
|
# "96576.3",
|
|
# "0.399"
|
|
# ],
|
|
# [
|
|
# "96577.6",
|
|
# "0.106"
|
|
# ]
|
|
# ],
|
|
# "b": [
|
|
# [
|
|
# "96565.2",
|
|
# "0.131"
|
|
# ],
|
|
# [
|
|
# "96565.1",
|
|
# "0.038"
|
|
# ]
|
|
# ],
|
|
# "s": "BTCUSDT",
|
|
# "u": 18665465
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
timestamp = self.milliseconds()
|
|
orderbook = self.parse_order_book(data, market['symbol'], timestamp, 'b', 'a')
|
|
orderbook['nonce'] = self.safe_integer(data, 'u')
|
|
return orderbook
|
|
|
|
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get the list of most recent trades for a particular symbol
|
|
|
|
https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-newest-trading-data-v3
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: the latest time in ms to fetch trades for
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id2'],
|
|
}
|
|
if limit is None:
|
|
limit = 500 # default is 50
|
|
request['limit'] = limit
|
|
response = self.publicGetV3Trades(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "i": "993f7f85-9215-5723-9078-2186ae140847",
|
|
# "p": "96534.3",
|
|
# "S": "Sell",
|
|
# "v": "0.261",
|
|
# "s": "BTCUSDT",
|
|
# "T": 1739118072710
|
|
# },
|
|
# {
|
|
# "i": "c947c9cf-8c18-5784-89c3-91bdf86ddde8",
|
|
# "p": "96513.5",
|
|
# "S": "Sell",
|
|
# "v": "0.042",
|
|
# "s": "BTCUSDT",
|
|
# "T": 1739118075944
|
|
# }
|
|
# ]
|
|
#
|
|
trades = self.safe_list(response, 'data', [])
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# [
|
|
# {
|
|
# "i": "993f7f85-9215-5723-9078-2186ae140847",
|
|
# "p": "96534.3",
|
|
# "S": "Sell",
|
|
# "v": "0.261",
|
|
# "s": "BTCUSDT",
|
|
# "T": 1739118072710
|
|
# }
|
|
# ]
|
|
#
|
|
marketId = self.safe_string_n(trade, ['s', 'symbol'])
|
|
market = self.safe_market(marketId, market)
|
|
id = self.safe_string_n(trade, ['i', 'id'])
|
|
timestamp = self.safe_integer_n(trade, ['t', 'T', 'createdAt'])
|
|
priceString = self.safe_string_n(trade, ['p', 'price'])
|
|
amountString = self.safe_string_n(trade, ['v', 'size'])
|
|
side = self.safe_string_lower_n(trade, ['S', 'side'])
|
|
type = self.safe_string_n(trade, ['type'])
|
|
fee = self.safe_string_n(trade, ['fee'])
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'id': id,
|
|
'order': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': market['symbol'],
|
|
'type': type,
|
|
'takerOrMaker': None,
|
|
'side': side,
|
|
'price': priceString,
|
|
'amount': amountString,
|
|
'cost': None,
|
|
'fee': fee,
|
|
}, market)
|
|
|
|
def fetch_open_interest(self, symbol: str, params={}):
|
|
"""
|
|
retrieves the open interest of a contract trading pair
|
|
|
|
https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-ticker-data-v3
|
|
|
|
:param str symbol: unified CCXT market symbol
|
|
:param dict [params]: exchange specific parameters
|
|
:returns dict} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure:
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id2'],
|
|
}
|
|
response = self.publicGetV3Ticker(self.extend(request, params))
|
|
tickers = self.safe_list(response, 'data', [])
|
|
rawTicker = self.safe_dict(tickers, 0, {})
|
|
return self.parse_open_interest(rawTicker, market)
|
|
|
|
def parse_open_interest(self, interest, market: Market = None):
|
|
#
|
|
# {
|
|
# "symbol": "BTCUSDT",
|
|
# "price24hPcnt": "0.450141",
|
|
# "lastPrice": "43511.50",
|
|
# "highPrice24h": "43513.50",
|
|
# "lowPrice24h": "29996.00",
|
|
# "markPrice": "43513.50",
|
|
# "indexPrice": "40828.94",
|
|
# "openInterest": "2036854775808",
|
|
# "turnover24h": "5626085.23749999",
|
|
# "volume24h": "169.317",
|
|
# "fundingRate": "0",
|
|
# "predictedFundingRate": "0",
|
|
# "nextFundingTime": "10:00:00",
|
|
# "tradeCount": 100
|
|
# }
|
|
#
|
|
timestamp = self.milliseconds()
|
|
marketId = self.safe_string(interest, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = self.safe_symbol(marketId, market)
|
|
return self.safe_open_interest({
|
|
'symbol': symbol,
|
|
'openInterestAmount': self.safe_string(interest, 'openInterest'),
|
|
'openInterestValue': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'info': interest,
|
|
}, market)
|
|
|
|
def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches historical funding rate prices
|
|
|
|
https://api-docs.pro.apex.exchange/#publicapi-v3-for-omni-get-funding-rate-history-v3
|
|
|
|
: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 int [params.until]: timestamp in ms of the latest funding rate
|
|
: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>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
|
|
self.load_markets()
|
|
request: dict = {}
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
if since is not None:
|
|
request['beginTimeInclusive'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
page = self.safe_integer(params, 'page')
|
|
if page is not None:
|
|
request['page'] = page
|
|
endTimeExclusive = self.safe_integer_n(params, ['endTime', 'endTimeExclusive', 'until'])
|
|
if endTimeExclusive is not None:
|
|
request['endTimeExclusive'] = endTimeExclusive
|
|
response = self.publicGetV3HistoryFunding(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "historyFunds": [
|
|
# {
|
|
# "symbol": "BTC-USD",
|
|
# "rate": "0.0000125000",
|
|
# "price": "31297.5000008009374142",
|
|
# "fundingTime": 12315555,
|
|
# "fundingTimestamp": 12315555
|
|
# }
|
|
# ],
|
|
# "totalSize": 11
|
|
# }
|
|
#
|
|
rates = []
|
|
data = self.safe_dict(response, 'data', {})
|
|
resultList = self.safe_list(data, 'historyFunds', [])
|
|
for i in range(0, len(resultList)):
|
|
entry = resultList[i]
|
|
timestamp = self.safe_integer(entry, 'fundingTimestamp')
|
|
marketId = self.safe_string(entry, 'symbol')
|
|
rates.append({
|
|
'info': entry,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'fundingRate': self.safe_number(entry, 'rate'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
})
|
|
sorted = self.sort_by(rates, 'timestamp')
|
|
return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# {
|
|
# "id": "1234",
|
|
# "clientId": "1234",
|
|
# "accountId": "12345",
|
|
# "symbol": "BTC-USD",
|
|
# "side": "SELL",
|
|
# "price": "18000",
|
|
# "limitFee": "100",
|
|
# "fee": "100",
|
|
# "triggerPrice": "1.2",
|
|
# "trailingPercent": "0.12",
|
|
# "size": "100",
|
|
# "remainingSize": "100",
|
|
# "type": "LIMIT",
|
|
# "createdAt": 1647502440973,
|
|
# "updatedTime": 1647502440973,
|
|
# "expiresAt": 1647502440973,
|
|
# "status": "PENDING",
|
|
# "timeInForce": "GOOD_TIL_CANCEL",
|
|
# "postOnly": False,
|
|
# "reduceOnly": False,
|
|
# "stopPnl": False,
|
|
# "latestMatchFillPrice": "reason",
|
|
# "cumMatchFillSize": "0.1",
|
|
# "cumMatchFillValue": "1000",
|
|
# "cumMatchFillFee": "1",
|
|
# "cumSuccessFillSize": "0.1",
|
|
# "cumSuccessFillValue": "1000",
|
|
# "cumSuccessFillFee": "1",
|
|
# "triggerPriceType": "INDEX",
|
|
# "isOpenTpslOrder": True,
|
|
# "isSetOpenTp": True,
|
|
# "isSetOpenSl": False,
|
|
# "openTpParam": {
|
|
# "side": "SELL",
|
|
# "price": "18000",
|
|
# "limitFee": "100",
|
|
# "clientOrderId": "111100",
|
|
# "triggerPrice": "1.2",
|
|
# "trailingPercent": "0.12",
|
|
# "size": "100"
|
|
# },
|
|
# "openSlParam": {
|
|
# "side": "SELL",
|
|
# "price": "18000",
|
|
# "limitFee": "100",
|
|
# "clientOrderId": "111100",
|
|
# "triggerPrice": "1.2",
|
|
# "trailingPercent": "0.12",
|
|
# "size": "100"
|
|
# }
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(order, 'createdAt')
|
|
orderId = self.safe_string(order, 'id')
|
|
clientOrderId = self.safe_string(order, 'clientId')
|
|
marketId = self.safe_string(order, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
price = self.safe_string(order, 'price')
|
|
amount = self.safe_string(order, 'size')
|
|
orderType = self.safe_string(order, 'type')
|
|
status = self.safe_string(order, 'status')
|
|
side = self.safe_string_lower(order, 'side')
|
|
# average = self.omit_zero(self.safe_string(order, 'avg_fill_price'))
|
|
remaining = self.omit_zero(self.safe_string(order, 'remainingSize'))
|
|
lastUpdateTimestamp = self.safe_integer(order, 'updatedTime')
|
|
return self.safe_order({
|
|
'id': orderId,
|
|
'clientOrderId': clientOrderId,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'lastUpdateTimestamp': lastUpdateTimestamp,
|
|
'status': self.parse_order_status(status),
|
|
'symbol': symbol,
|
|
'type': self.parse_order_type(orderType),
|
|
'timeInForce': self.parse_time_in_force(self.safe_string(order, 'timeInForce')),
|
|
'postOnly': self.safe_bool(order, 'postOnly'),
|
|
'reduceOnly': self.safe_bool(order, 'reduceOnly'),
|
|
'side': side,
|
|
'price': price,
|
|
'triggerPrice': self.safe_string(order, 'triggerPrice'),
|
|
'takeProfitPrice': None,
|
|
'stopLossPrice': None,
|
|
'average': None,
|
|
'amount': amount,
|
|
'filled': None,
|
|
'remaining': remaining,
|
|
'cost': None,
|
|
'trades': None,
|
|
'fee': {
|
|
'cost': self.safe_string(order, 'fee'),
|
|
'currency': market['settleId'],
|
|
},
|
|
'info': order,
|
|
}, market)
|
|
|
|
def parse_time_in_force(self, timeInForce: Str):
|
|
timeInForces: dict = {
|
|
'GOOD_TIL_CANCEL': 'GOOD_TIL_CANCEL',
|
|
'FILL_OR_KILL': 'FILL_OR_KILL',
|
|
'IMMEDIATE_OR_CANCEL': 'IMMEDIATE_OR_CANCEL',
|
|
'POST_ONLY': 'POST_ONLY',
|
|
}
|
|
return self.safe_string(timeInForces, timeInForce, None)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
if status is not None:
|
|
statuses: dict = {
|
|
'PENDING': 'open',
|
|
'OPEN': 'open',
|
|
'FILLED': 'filled',
|
|
'CANCELING': 'canceled',
|
|
'CANCELED': 'canceled',
|
|
'UNTRIGGERED': 'open',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
return status
|
|
|
|
def parse_order_type(self, type: Str):
|
|
types: dict = {
|
|
'LIMIT': 'limit',
|
|
'MARKET': 'market',
|
|
'STOP_LIMIT': 'limit',
|
|
'STOP_MARKET': 'market',
|
|
'TAKE_PROFIT_LIMIT': 'limit',
|
|
'TAKE_PROFIT_MARKET': 'market',
|
|
}
|
|
return self.safe_string(types, type, type)
|
|
|
|
def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface:
|
|
if market is None and marketId is not None:
|
|
if marketId in self.markets:
|
|
market = self.markets[marketId]
|
|
elif marketId in self.markets_by_id:
|
|
market = self.markets_by_id[marketId]
|
|
else:
|
|
newMarketId = self.add_hyphen_before_usdt(marketId)
|
|
if newMarketId in self.markets_by_id:
|
|
markets = self.markets_by_id[newMarketId]
|
|
numMarkets = len(markets)
|
|
if numMarkets > 0:
|
|
if self.markets_by_id[newMarketId][0]['id2'] == marketId:
|
|
market = self.markets_by_id[newMarketId][0]
|
|
return super(apex, self).safe_market(marketId, market, delimiter, marketType)
|
|
|
|
def generate_random_client_id_omni(self, _accountId: str):
|
|
accountId = _accountId or str(self.rand_number(12))
|
|
return 'apexomni-' + accountId + '-' + str(self.milliseconds()) + '-' + str(self.rand_number(6))
|
|
|
|
def add_hyphen_before_usdt(self, symbol: str):
|
|
uppercaseSymbol = symbol.upper()
|
|
index = uppercaseSymbol.find('USDT')
|
|
symbolChar = self.safe_string(symbol, index - 1)
|
|
if index > 0 and symbolChar != '-':
|
|
return symbol[0:index] + '-' + symbol[index:]
|
|
return symbol
|
|
|
|
def get_seeds(self):
|
|
seeds = self.safe_string(self.options, 'seeds')
|
|
if seeds is None:
|
|
raise ArgumentsRequired(self.id + ' the "seeds" key is required in the options to access private endpoints. You can find it in API Management > Omni Key, and then set it.options["seeds"] = XXXX')
|
|
return seeds
|
|
|
|
def get_account_id(self):
|
|
accountId = self.safe_string(self.options, 'accountId', '0')
|
|
if accountId == '0':
|
|
accountData = self.fetch_account()
|
|
self.options['accountId'] = self.safe_string(accountData, 'id', '0')
|
|
return self.options['accountId']
|
|
|
|
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-post-creating-orders
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market' or 'limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much of currency you want to trade in units of base currency
|
|
:param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param float [params.triggerPrice]: The price a trigger order is triggered at
|
|
:param float [params.stopLossPrice]: The price a stop loss order is triggered at
|
|
:param float [params.takeProfitPrice]: The price a take profit order is triggered at
|
|
:param str [params.timeInForce]: "GTC", "IOC", or "POST_ONLY"
|
|
:param bool [params.postOnly]: True or False
|
|
:param bool [params.reduceOnly]: Ensures that the executed order does not flip the opened position.
|
|
:param str [params.clientOrderId]: a unique id for the order
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
orderType = type.upper()
|
|
orderSide = side.upper()
|
|
orderSize = self.amount_to_precision(symbol, amount)
|
|
orderPrice = '0'
|
|
if price is not None:
|
|
orderPrice = self.price_to_precision(symbol, price)
|
|
fees = self.safe_dict(self.fees, 'swap', {})
|
|
taker = self.safe_string(fees, 'taker', '0.0005')
|
|
maker = self.safe_string(fees, 'maker', '0.0002')
|
|
limitFee = self.decimal_to_precision(Precise.string_add(Precise.string_mul(Precise.string_mul(orderPrice, orderSize), taker), self.number_to_string(market['precision']['price'])), TRUNCATE, market['precision']['price'], self.precisionMode, self.paddingMode)
|
|
timeNow = self.milliseconds()
|
|
triggerPrice = self.safe_string(params, 'triggerPrice')
|
|
stopLossPrice = self.safe_string(params, 'stopLossPrice')
|
|
takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
|
|
if stopLossPrice is not None:
|
|
orderType = 'STOP_MARKET' if (orderType == 'MARKET') else 'STOP_LIMIT'
|
|
triggerPrice = stopLossPrice
|
|
elif takeProfitPrice is not None:
|
|
orderType = 'TAKE_PROFIT_MARKET' if (orderType == 'MARKET') else 'TAKE_PROFIT_LIMIT'
|
|
triggerPrice = takeProfitPrice
|
|
isMarket = orderType == 'MARKET'
|
|
if isMarket and (price is None):
|
|
raise ArgumentsRequired(self.id + ' createOrder() requires a price argument for market orders')
|
|
timeInForce = self.safe_string_upper(params, 'timeInForce')
|
|
postOnly = self.is_post_only(isMarket, None, params)
|
|
if timeInForce is None:
|
|
timeInForce = 'GOOD_TIL_CANCEL'
|
|
if not isMarket:
|
|
if postOnly:
|
|
timeInForce = 'POST_ONLY'
|
|
elif timeInForce == 'ioc':
|
|
timeInForce = 'IMMEDIATE_OR_CANCEL'
|
|
params = self.omit(params, 'timeInForce')
|
|
params = self.omit(params, 'postOnly')
|
|
clientOrderId = self.safe_string_n(params, ['clientId', 'clientOrderId', 'client_order_id'])
|
|
accountId = self.get_account_id()
|
|
if clientOrderId is None:
|
|
clientOrderId = self.generate_random_client_id_omni(accountId)
|
|
params = self.omit(params, ['clientId', 'clientOrderId', 'client_order_id', 'stopLossPrice', 'takeProfitPrice', 'triggerPrice'])
|
|
orderToSign = {
|
|
'accountId': accountId,
|
|
'slotId': clientOrderId,
|
|
'nonce': clientOrderId,
|
|
'pairId': market['quoteId'],
|
|
'size': orderSize,
|
|
'price': orderPrice,
|
|
'direction': orderSide,
|
|
'makerFeeRate': maker,
|
|
'takerFeeRate': taker,
|
|
}
|
|
if triggerPrice is not None:
|
|
orderToSign['triggerPrice'] = self.price_to_precision(symbol, triggerPrice)
|
|
signature = self.get_zk_contract_signature_obj(self.remove0x_prefix(self.get_seeds()), orderToSign)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'side': orderSide,
|
|
'type': orderType, # LIMIT/MARKET/STOP_LIMIT/STOP_MARKET
|
|
'size': orderSize,
|
|
'price': orderPrice,
|
|
'limitFee': limitFee,
|
|
'expiration': int(math.floor(timeNow / 1000 + 30 * 24 * 60 * 60)),
|
|
'timeInForce': timeInForce,
|
|
'clientId': clientOrderId,
|
|
'brokerId': self.safe_string(self.options, 'brokerId', '6956'),
|
|
}
|
|
if triggerPrice is not None:
|
|
request['triggerPrice'] = self.price_to_precision(symbol, triggerPrice)
|
|
request['signature'] = signature
|
|
response = self.privatePostV3Order(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_order(data, market)
|
|
|
|
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
|
"""
|
|
transfer currency internally between wallets on the same account
|
|
:param str code: unified currency code
|
|
:param float amount: amount to transfer
|
|
:param str fromAccount: account to transfer from
|
|
:param str toAccount: account to transfer to
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.transferId]: UUID, which is unique across the platform
|
|
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
|
|
"""
|
|
self.load_markets()
|
|
configResponse = self.publicGetV3Symbols(params)
|
|
configData = self.safe_dict(configResponse, 'data', {})
|
|
contractConfig = self.safe_dict(configData, 'contractConfig', {})
|
|
contractAssets = self.safe_list(contractConfig, 'assets', [])
|
|
spotConfig = self.safe_dict(configData, 'spotConfig', {})
|
|
spotAssets = self.safe_list(spotConfig, 'assets', [])
|
|
globalConfig = self.safe_dict(spotConfig, 'global', {})
|
|
receiverAddress = self.safe_string(globalConfig, 'contractAssetPoolEthAddress', '')
|
|
receiverZkAccountId = self.safe_string(globalConfig, 'contractAssetPoolZkAccountId', '')
|
|
receiverSubAccountId = self.safe_string(globalConfig, 'contractAssetPoolSubAccount', '')
|
|
receiverAccountId = self.safe_string(globalConfig, 'contractAssetPoolAccountId', '')
|
|
accountResponse = self.privateGetV3Account(params)
|
|
accountData = self.safe_dict(accountResponse, 'data', {})
|
|
spotAccount = self.safe_dict(accountData, 'spotAccount', {})
|
|
zkAccountId = self.safe_string(spotAccount, 'zkAccountId', '')
|
|
subAccountId = self.safe_string(spotAccount, 'defaultSubAccountId', '0')
|
|
subAccounts = self.safe_list(spotAccount, 'subAccounts', [])
|
|
nonce = '0'
|
|
if len(subAccounts) > 0:
|
|
nonce = self.safe_string(subAccounts[0], 'nonce', '0')
|
|
ethAddress = self.safe_string(accountData, 'ethereumAddress', '')
|
|
accountId = self.safe_string(accountData, 'id', '')
|
|
currency = {}
|
|
assets = []
|
|
if fromAccount is not None and fromAccount.lower() == 'contract':
|
|
assets = contractAssets
|
|
else:
|
|
assets = spotAssets
|
|
for i in range(0, len(assets)):
|
|
if self.safe_string(assets[i], 'token', '') == code:
|
|
currency = assets[i]
|
|
tokenId = self.safe_string(currency, 'tokenId', '')
|
|
amountNumber = self.parse_to_int(amount * (math.pow(10, self.safe_number(currency, 'decimals', 0))))
|
|
timestampSeconds = self.parse_to_int(self.milliseconds() / 1000)
|
|
clientOrderId = self.safe_string_n(params, ['clientId', 'clientOrderId', 'client_order_id'])
|
|
if clientOrderId is None:
|
|
clientOrderId = self.generate_random_client_id_omni(self.safe_string(self.options, 'accountId'))
|
|
params = self.omit(params, ['clientId', 'clientOrderId', 'client_order_id'])
|
|
if fromAccount is not None and fromAccount.lower() == 'contract':
|
|
formattedUint32 = '4294967295'
|
|
zkSignAccountId = Precise.string_mod(accountId, formattedUint32)
|
|
expireTime = timestampSeconds + 3600 * 24 * 28
|
|
orderToSign = {
|
|
'zkAccountId': zkSignAccountId,
|
|
'receiverAddress': ethAddress,
|
|
'subAccountId': subAccountId,
|
|
'receiverSubAccountId': subAccountId,
|
|
'tokenId': tokenId,
|
|
'amount': str(amountNumber),
|
|
'fee': '0',
|
|
'nonce': clientOrderId,
|
|
'timestampSeconds': expireTime,
|
|
'isContract': True,
|
|
}
|
|
signature = self.get_zk_transfer_signature_obj(self.remove0x_prefix(self.get_seeds()), orderToSign)
|
|
request: dict = {
|
|
'amount': amount,
|
|
'expireTime': expireTime,
|
|
'clientWithdrawId': clientOrderId,
|
|
'signature': signature,
|
|
'token': code,
|
|
'ethAddress': ethAddress,
|
|
}
|
|
response = self.privatePostV3ContractTransferOut(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
currentTime = self.milliseconds()
|
|
return self.extend(self.parse_transfer(data, self.currency(code)), {
|
|
'timestamp': currentTime,
|
|
'datetime': self.iso8601(currentTime),
|
|
'amount': self.parse_number(amount),
|
|
'fromAccount': 'contract',
|
|
'toAccount': 'spot',
|
|
})
|
|
else:
|
|
orderToSign = {
|
|
'zkAccountId': zkAccountId,
|
|
'receiverAddress': receiverAddress,
|
|
'subAccountId': subAccountId,
|
|
'receiverSubAccountId': receiverSubAccountId,
|
|
'tokenId': tokenId,
|
|
'amount': str(amountNumber),
|
|
'fee': '0',
|
|
'nonce': nonce,
|
|
'timestampSeconds': timestampSeconds,
|
|
}
|
|
signature = self.get_zk_transfer_signature_obj(self.remove0x_prefix(self.get_seeds()), orderToSign)
|
|
request: dict = {
|
|
'amount': str(amount),
|
|
'timestamp': timestampSeconds,
|
|
'clientTransferId': clientOrderId,
|
|
'signature': signature,
|
|
'zkAccountId': zkAccountId,
|
|
'subAccountId': subAccountId,
|
|
'fee': '0',
|
|
'token': code,
|
|
'tokenId': tokenId,
|
|
'receiverAccountId': receiverAccountId,
|
|
'receiverZkAccountId': receiverZkAccountId,
|
|
'receiverSubAccountId': receiverSubAccountId,
|
|
'receiverAddress': receiverAddress,
|
|
'nonce': nonce,
|
|
}
|
|
response = self.privatePostV3TransferOut(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
currentTime = self.milliseconds()
|
|
return self.extend(self.parse_transfer(data, self.currency(code)), {
|
|
'timestamp': currentTime,
|
|
'datetime': self.iso8601(currentTime),
|
|
'amount': self.parse_number(amount),
|
|
'fromAccount': 'spot',
|
|
'toAccount': 'contract',
|
|
})
|
|
|
|
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
|
currencyId = self.safe_string(transfer, 'coin')
|
|
timestamp = self.safe_integer(transfer, 'timestamp')
|
|
fromAccount = self.safe_string(transfer, 'fromAccount')
|
|
toAccount = self.safe_string(transfer, 'toAccount')
|
|
return {
|
|
'info': transfer,
|
|
'id': self.safe_string_n(transfer, ['transferId', 'id']),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'currency': self.safe_currency_code(currencyId, currency),
|
|
'amount': self.safe_number(transfer, 'amount'),
|
|
'fromAccount': fromAccount,
|
|
'toAccount': toAccount,
|
|
'status': self.safe_string(transfer, 'status'),
|
|
}
|
|
|
|
def cancel_all_orders(self, symbol: Str = None, params={}) -> List[Order]:
|
|
"""
|
|
cancel all open orders in a market
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-post-cancel-all-open-orders
|
|
|
|
:param str symbol: unified market symbol of the market to cancel orders in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = None
|
|
request: dict = {}
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
response = self.privatePostV3DeleteOpenOrders(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
return [self.parse_order(data, market)]
|
|
|
|
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-post-cancel-order
|
|
|
|
:param str id: order id
|
|
@param symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = {}
|
|
clientOrderId = self.safe_string_n(params, ['clientId', 'clientOrderId', 'client_order_id'])
|
|
response = None
|
|
if clientOrderId is not None:
|
|
request['id'] = clientOrderId
|
|
params = self.omit(params, ['clientId', 'clientOrderId', 'client_order_id'])
|
|
response = self.privatePostV3DeleteClientOrderId(self.extend(request, params))
|
|
else:
|
|
request['id'] = id
|
|
response = self.privatePostV3DeleteOrder(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.safe_order(data)
|
|
|
|
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-order-id
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-order-by-clientorderid
|
|
|
|
:param str id: the order id
|
|
:param str symbol: unified symbol of the market the order was made in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.clientOrderId]: a unique id for the order
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
clientOrderId = self.safe_string_n(params, ['clientId', 'clientOrderId', 'client_order_id'])
|
|
response = None
|
|
if clientOrderId is not None:
|
|
request['id'] = clientOrderId
|
|
params = self.omit(params, ['clientId', 'clientOrderId', 'client_order_id'])
|
|
response = self.privateGetV3OrderByClientOrderId(self.extend(request, params))
|
|
else:
|
|
request['id'] = id
|
|
response = self.privateGetV3Order(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_order(data)
|
|
|
|
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple orders made by the user
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-open-orders
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = self.privateGetV3OpenOrders(params)
|
|
orders = self.safe_list(response, 'data', [])
|
|
return self.parse_orders(orders, None, since, limit)
|
|
|
|
def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple orders made by the user *classic accounts only*
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-all-order-history
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve, default 100
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param dict [params.until]: end time, ms
|
|
:param boolean [params.status]: "PENDING", "OPEN", "FILLED", "CANCELED", "EXPIRED", "UNTRIGGERED"
|
|
:param boolean [params.side]: BUY or SELL
|
|
:param str [params.type]: "LIMIT", "MARKET","STOP_LIMIT", "STOP_MARKET", "TAKE_PROFIT_LIMIT","TAKE_PROFIT_MARKET"
|
|
:param str [params.orderType]: "ACTIVE","CONDITION","HISTORY"
|
|
:param boolean [params.page]: Page numbers start from 0
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
if since is not None:
|
|
request['beginTimeInclusive'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
endTimeExclusive = self.safe_integer_n(params, ['endTime', 'endTimeExclusive', 'until'])
|
|
if endTimeExclusive is not None:
|
|
request['endTimeExclusive'] = endTimeExclusive
|
|
params = self.omit(params, ['endTime', 'endTimeExclusive', 'until'])
|
|
response = self.privateGetV3HistoryOrders(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
orders = self.safe_list(data, 'orders', [])
|
|
return self.parse_orders(orders, market, since, limit)
|
|
|
|
def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all the trades made from a single order
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-trade-history
|
|
|
|
:param str id: order id
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch trades for
|
|
:param int [limit]: the maximum number of trades to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
clientOrderId = self.safe_string_2(params, 'clientOrderId', 'clientId')
|
|
if clientOrderId is not None:
|
|
request['clientOrderId'] = clientOrderId
|
|
else:
|
|
request['orderId'] = id
|
|
params = self.omit(params, ['clientOrderId', 'clientId'])
|
|
response = self.privateGetV3OrderFills(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
orders = self.safe_list(data, 'orders', [])
|
|
return self.parse_trades(orders, None, since, limit)
|
|
|
|
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches information on multiple orders made by the user *classic accounts only*
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-trade-history
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve, default 100
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param dict [params.until]: end time
|
|
:param boolean [params.side]: BUY or SELL
|
|
:param str [params.orderType]: "LIMIT", "MARKET","STOP_LIMIT", "STOP_MARKET", "TAKE_PROFIT_LIMIT","TAKE_PROFIT_MARKET"
|
|
:param boolean [params.page]: Page numbers start from 0
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
if since is not None:
|
|
request['beginTimeInclusive'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
endTimeExclusive = self.safe_integer_n(params, ['endTime', 'endTimeExclusive', 'until'])
|
|
if endTimeExclusive is not None:
|
|
request['endTimeExclusive'] = endTimeExclusive
|
|
params = self.omit(params, ['endTime', 'endTimeExclusive', 'until'])
|
|
response = self.privateGetV3Fills(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
orders = self.safe_list(data, 'orders', [])
|
|
return self.parse_trades(orders, market, since, limit)
|
|
|
|
def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches information on multiple orders made by the user *classic accounts only*
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-funding-rate
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve, default 100
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param dict [params.until]: end time, ms
|
|
:param boolean [params.side]: BUY or SELL
|
|
:param boolean [params.page]: Page numbers start from 0
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=funding-history-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
if since is not None:
|
|
request['beginTimeInclusive'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
endTimeExclusive = self.safe_integer_n(params, ['endTime', 'endTimeExclusive', 'until'])
|
|
if endTimeExclusive is not None:
|
|
params = self.omit(params, ['endTime', 'endTimeExclusive', 'until'])
|
|
request['endTimeExclusive'] = endTimeExclusive
|
|
response = self.privateGetV3Funding(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
fundingValues = self.safe_list(data, 'fundingValues', [])
|
|
return self.parse_incomes(fundingValues, market, since, limit)
|
|
|
|
def parse_income(self, income, market: Market = None):
|
|
#
|
|
# {
|
|
# "id": "1234",
|
|
# "symbol": "BTC-USDT",
|
|
# "fundingValue": "10000",
|
|
# "rate": "0.0000125000",
|
|
# "positionSize": "500",
|
|
# "price": "90",
|
|
# "side": "LONG",
|
|
# "status": "SUCCESS",
|
|
# "fundingTime": 1647502440973,
|
|
# "transactionId": "1234556"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(income, 'symbol')
|
|
market = self.safe_market(marketId, market, None, 'contract')
|
|
code = 'USDT'
|
|
timestamp = self.safe_integer(income, 'fundingTime')
|
|
return {
|
|
'info': income,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'code': code,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'id': self.safe_string(income, 'id'),
|
|
'amount': self.safe_number(income, 'fundingValue'),
|
|
'rate': self.safe_number(income, 'rate'),
|
|
}
|
|
|
|
def set_leverage(self, leverage: int, symbol: Str = None, params={}):
|
|
"""
|
|
set the level of leverage for a market
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-post-sets-the-initial-margin-rate-of-a-contract
|
|
|
|
:param float leverage: the rate of leverage
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: response from the exchange
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
leverageString = self.number_to_string(leverage)
|
|
initialMarginRate = Precise.string_div('1', leverageString, 4)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'initialMarginRate': initialMarginRate,
|
|
}
|
|
response = self.privatePostV3SetInitialMarginRate(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
return data
|
|
|
|
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
"""
|
|
fetch all open positions
|
|
|
|
https://api-docs.pro.apex.exchange/#privateapi-v3-for-omni-get-retrieve-user-account-data
|
|
|
|
:param str[] [symbols]: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = self.privateGetV3Account(params)
|
|
data = self.safe_dict(response, 'data', {})
|
|
positions = self.safe_list(data, 'positions', [])
|
|
return self.parse_positions(positions, symbols)
|
|
|
|
def parse_position(self, position: dict, market: Market = None):
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "status": "",
|
|
# "side": "LONG",
|
|
# "size": "0.000",
|
|
# "entryPrice": "0.00",
|
|
# "exitPrice": "",
|
|
# "createdAt": 1690366452416,
|
|
# "updatedTime": 1690366452416,
|
|
# "fee": "0.000000",
|
|
# "fundingFee": "0.000000",
|
|
# "lightNumbers": "",
|
|
# "customInitialMarginRate": "0"
|
|
# }
|
|
marketId = self.safe_string(position, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
side = self.safe_string_lower(position, 'side')
|
|
quantity = self.safe_string(position, 'size')
|
|
timestamp = self.safe_integer(position, 'updatedTime')
|
|
leverage = 20
|
|
customInitialMarginRate = self.safe_string_n(position, ['customInitialMarginRate', 'customImr'], '0')
|
|
if self.precision_from_string(customInitialMarginRate) != 0:
|
|
leverage = self.parse_to_int(Precise.string_div('1', customInitialMarginRate, 4))
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': self.safe_string(position, 'id'),
|
|
'symbol': symbol,
|
|
'entryPrice': self.safe_string(position, 'entryPrice'),
|
|
'markPrice': None,
|
|
'notional': None,
|
|
'collateral': None,
|
|
'unrealizedPnl': None,
|
|
'side': side,
|
|
'contracts': self.parse_number(quantity),
|
|
'contractSize': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'hedged': None,
|
|
'maintenanceMargin': None,
|
|
'maintenanceMarginPercentage': None,
|
|
'initialMargin': None,
|
|
'initialMarginPercentage': None,
|
|
'leverage': leverage,
|
|
'liquidationPrice': None,
|
|
'marginRatio': None,
|
|
'marginMode': None,
|
|
'percentage': None,
|
|
})
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
url = self.implode_hostname(self.urls['api'][api]) + '/' + path
|
|
headers = {
|
|
'User-Agent': 'apex-CCXT',
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
}
|
|
signPath = '/api/' + path
|
|
signBody = body
|
|
if method.upper() != 'POST':
|
|
if params:
|
|
signPath += '?' + self.rawencode(params)
|
|
url += '?' + self.rawencode(params)
|
|
else:
|
|
sortedQuery = self.keysort(params)
|
|
signBody = self.rawencode(sortedQuery)
|
|
if api == 'private':
|
|
self.check_required_credentials()
|
|
timestamp = str(self.milliseconds())
|
|
messageString = timestamp + method.upper() + signPath
|
|
if signBody is not None:
|
|
messageString = messageString + signBody
|
|
signature = self.hmac(self.encode(messageString), self.encode(self.string_to_base64(self.secret)), hashlib.sha256, 'base64')
|
|
headers['APEX-SIGNATURE'] = signature
|
|
headers['APEX-API-KEY'] = self.apiKey
|
|
headers['APEX-TIMESTAMP'] = timestamp
|
|
headers['APEX-PASSPHRASE'] = self.password
|
|
return {'url': url, 'method': method, 'body': signBody, 'headers': headers}
|
|
|
|
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
|
#
|
|
# {"code":3,"msg":"Order price must be greater than 0. Order price is 0.","key":"ORDER_PRICE_MUST_GREETER_ZERO","detail":{"price":"0"}}
|
|
# {"code":400,"msg":"strconv.ParseInt: parsing \"dsfdfsd\": invalid syntax","timeCost":5320995}
|
|
#
|
|
if response is None:
|
|
return None
|
|
errorCode = self.safe_integer(response, 'code')
|
|
if errorCode is not None and errorCode != 0:
|
|
feedback = self.id + ' ' + body
|
|
message = self.safe_string_2(response, 'key', 'msg')
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
|
status = str(code)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], status, feedback)
|
|
raise ExchangeError(feedback)
|
|
return None
|