Files
ccxt_with_mt5/ccxt/ascendex.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

3527 lines
155 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.ascendex import ImplicitAPI
import hashlib
from ccxt.base.types import Account, Any, Balances, Currencies, Currency, DepositAddress, Int, Leverage, Leverages, LeverageTier, LeverageTiers, MarginMode, MarginModes, MarginModification, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFees, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import NotSupported
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise
class ascendex(Exchange, ImplicitAPI):
def describe(self) -> Any:
return self.deep_extend(super(ascendex, self).describe(), {
'id': 'ascendex',
'name': 'AscendEX',
'countries': ['SG'], # Singapore
# 8 requests per minute = 0.13333 per second => rateLimit = 750
# testing 400 works
'rateLimit': 400,
'certified': False,
'pro': True,
# new metainfo interface
'has': {
'CORS': None,
'spot': True,
'margin': True,
'swap': True,
'future': False,
'option': False,
'addMargin': True,
'cancelAllOrders': True,
'cancelOrder': True,
'createOrder': True,
'createOrders': True,
'createPostOnlyOrder': True,
'createReduceOnlyOrder': True,
'createStopLimitOrder': True,
'createStopMarketOrder': True,
'createStopOrder': True,
'fetchAccounts': True,
'fetchAllGreeks': False,
'fetchBalance': True,
'fetchClosedOrders': True,
'fetchCurrencies': True,
'fetchDepositAddress': True,
'fetchDepositAddresses': False,
'fetchDepositAddressesByNetwork': False,
'fetchDeposits': True,
'fetchDepositsWithdrawals': True,
'fetchDepositWithdrawFee': 'emulated',
'fetchDepositWithdrawFees': True,
'fetchFundingHistory': True,
'fetchFundingRate': 'emulated',
'fetchFundingRateHistory': False,
'fetchFundingRates': True,
'fetchGreeks': False,
'fetchIndexOHLCV': False,
'fetchLeverage': 'emulated',
'fetchLeverages': True,
'fetchLeverageTiers': True,
'fetchMarginMode': 'emulated',
'fetchMarginModes': True,
'fetchMarketLeverageTiers': 'emulated',
'fetchMarkets': True,
'fetchMarkOHLCV': False,
'fetchMySettlementHistory': False,
'fetchOHLCV': True,
'fetchOpenInterest': False,
'fetchOpenInterestHistory': False,
'fetchOpenOrders': True,
'fetchOption': False,
'fetchOptionChain': False,
'fetchOrder': True,
'fetchOrderBook': True,
'fetchOrders': False,
'fetchPosition': False,
'fetchPositionMode': False,
'fetchPositions': True,
'fetchPositionsRisk': False,
'fetchPremiumIndexOHLCV': False,
'fetchSettlementHistory': False,
'fetchTicker': True,
'fetchTickers': True,
'fetchTime': True,
'fetchTrades': True,
'fetchTradingFee': False,
'fetchTradingFees': True,
'fetchTransactionFee': False,
'fetchTransactionFees': False,
'fetchTransactions': 'emulated',
'fetchTransfer': False,
'fetchTransfers': False,
'fetchVolatilityHistory': False,
'fetchWithdrawal': False,
'fetchWithdrawals': True,
'reduceMargin': True,
'sandbox': True,
'setLeverage': True,
'setMarginMode': True,
'setPositionMode': False,
'transfer': True,
},
'timeframes': {
'1m': '1',
'5m': '5',
'15m': '15',
'30m': '30',
'1h': '60',
'2h': '120',
'4h': '240',
'6h': '360',
'12h': '720',
'1d': '1d',
'1w': '1w',
'1M': '1m',
},
'version': 'v2',
'urls': {
'logo': 'https://github.com/user-attachments/assets/55bab6b9-d4ca-42a8-a0e6-fac81ae557f1',
'api': {
'rest': 'https://ascendex.com',
},
'test': {
'rest': 'https://api-test.ascendex-sandbox.com',
},
'www': 'https://ascendex.com',
'doc': [
'https://ascendex.github.io/ascendex-pro-api/#ascendex-pro-api-documentation',
],
'fees': 'https://ascendex.com/en/feerate/transactionfee-traderate',
'referral': {
'url': 'https://ascendex.com/en-us/register?inviteCode=EL6BXBQM',
'discount': 0.25,
},
},
'api': {
'v1': {
'public': {
'get': {
'assets': 1,
'products': 1,
'ticker': 1,
'barhist/info': 1,
'barhist': 1,
'depth': 1,
'trades': 1,
'cash/assets': 1, # not documented
'cash/products': 1, # not documented
'margin/assets': 1, # not documented
'margin/products': 1, # not documented
'futures/collateral': 1,
'futures/contracts': 1,
'futures/ref-px': 1,
'futures/market-data': 1,
'futures/funding-rates': 1,
'risk-limit-info': 1,
'exchange-info': 1,
},
},
'private': {
'get': {
'info': 1,
'wallet/transactions': 1,
'wallet/deposit/address': 1,
'data/balance/snapshot': 1,
'data/balance/history': 1,
},
'accountCategory': {
'get': {
'balance': 1,
'order/open': 1,
'order/status': 1,
'order/hist/current': 1,
'risk': 1,
},
'post': {
'order': 1,
'order/batch': 1,
},
'delete': {
'order': 1,
'order/all': 1,
'order/batch': 1,
},
},
'accountGroup': {
'get': {
'cash/balance': 1,
'margin/balance': 1,
'margin/risk': 1,
'futures/collateral-balance': 1,
'futures/position': 1,
'futures/risk': 1,
'futures/funding-payments': 1,
'order/hist': 1,
'spot/fee': 1,
},
'post': {
'transfer': 1,
'futures/transfer/deposit': 1,
'futures/transfer/withdraw': 1,
},
},
},
},
'v2': {
'public': {
'get': {
'assets': 1,
'futures/contract': 1,
'futures/collateral': 1,
'futures/pricing-data': 1,
'futures/ticker': 1,
'risk-limit-info': 1,
},
},
'private': {
'data': {
'get': {
'order/hist': 1,
},
},
'get': {
'account/info': 1,
},
'accountGroup': {
'get': {
'order/hist': 1,
'futures/position': 1,
'futures/free-margin': 1,
'futures/order/hist/current': 1,
'futures/funding-payments': 1,
'futures/order/open': 1,
'futures/order/status': 1,
},
'post': {
'futures/isolated-position-margin': 1,
'futures/margin-type': 1,
'futures/leverage': 1,
'futures/transfer/deposit': 1,
'futures/transfer/withdraw': 1,
'futures/order': 1,
'futures/order/batch': 1,
'futures/order/open': 1,
'subuser/subuser-transfer': 1,
'subuser/subuser-transfer-hist': 1,
},
'delete': {
'futures/order': 1,
'futures/order/batch': 1,
'futures/order/all': 1,
},
},
},
},
},
'fees': {
'trading': {
'feeSide': 'get',
'tierBased': True,
'percentage': True,
'taker': self.parse_number('0.002'),
'maker': self.parse_number('0.002'),
},
},
'precisionMode': TICK_SIZE,
'options': {
'account-category': 'cash', # 'cash', 'margin', 'futures' # obsolete
'account-group': None,
'fetchClosedOrders': {
'method': 'v2PrivateDataGetOrderHist', # 'v1PrivateAccountCategoryGetOrderHistCurrent'
},
'defaultType': 'spot', # 'spot', 'margin', 'swap'
'accountsByType': {
'spot': 'cash',
'swap': 'futures',
'margin': 'margin',
},
'transfer': {
'fillResponseFromRequest': True,
},
'networks': {
'BSC': 'BEP20 ' + '(BSC)',
'ARB': 'arbitrum',
'SOL': 'Solana',
'AVAX': 'avalanche C chain',
'OMNI': 'Omni',
# 'TRC': 'TRC20',
'TRC20': 'TRC20',
'ERC20': 'ERC20',
'GO20': 'GO20',
'BEP2': 'BEP2',
'BTC': 'Bitcoin',
'BCH': 'Bitcoin ABC',
'LTC': 'Litecoin',
'MATIC': 'Matic Network',
'AKT': 'Akash',
},
},
'features': {
'default': {
'sandbox': True,
'createOrder': {
'marginMode': True,
'triggerPrice': True,
'triggerPriceType': None,
'triggerDirection': False,
'stopLossPrice': False, # todo with triggerprice
'takeProfitPrice': False, # todo with triggerprice
'attachedStopLossTakeProfit': None,
'timeInForce': {
'IOC': True,
'FOK': True,
'PO': True,
'GTD': False,
},
'hedged': False,
'trailing': False,
'leverage': False,
'marketBuyRequiresPrice': False,
'marketBuyByCost': False,
'selfTradePrevention': False,
'iceberg': False,
},
'createOrders': {
'max': 10,
},
'fetchMyTrades': None,
'fetchOrder': {
'marginMode': False,
'trigger': False,
'trailing': False,
'marketType': True,
'symbolRequired': False,
},
'fetchOpenOrders': {
'marginMode': False,
'limit': None,
'trigger': False,
'trailing': False,
'marketType': True,
'symbolRequired': False,
},
'fetchOrders': None,
'fetchClosedOrders': None,
'fetchOHLCV': {
'limit': 500,
},
},
'spot': {
'extends': 'default',
'fetchClosedOrders': {
'marginMode': False,
'limit': 1000,
'daysBack': 100000,
'daysBackCanceled': 1,
'untilDays': 100000,
'trigger': False,
'trailing': False,
'symbolRequired': False,
},
},
'forDerivatives': {
'extends': 'default',
'createOrder': {
# todo: implementation
'attachedStopLossTakeProfit': {
'triggerPriceType': {
'last': True,
'mark': False,
'index': False,
},
'price': False,
},
},
'fetchClosedOrders': {
'marginMode': False,
'limit': 1000,
'daysBack': None,
'daysBackCanceled': None,
'untilDays': None,
'trigger': False,
'trailing': False,
'symbolRequired': False,
},
},
'swap': {
'linear': {
'extends': 'forDerivatives',
},
'inverse': None,
},
'future': {
'linear': None,
'inverse': None,
},
},
'exceptions': {
'exact': {
# not documented
'1900': BadRequest, # {"code":1900,"message":"Invalid Http Request Input"}
'2100': AuthenticationError, # {"code":2100,"message":"ApiKeyFailure"}
'5002': BadSymbol, # {"code":5002,"message":"Invalid Symbol"}
'6001': BadSymbol, # {"code":6001,"message":"Trading is disabled on symbol."}
'6010': InsufficientFunds, # {'code': 6010, 'message': 'Not enough balance.'}
'60060': InvalidOrder, # {'code': 60060, 'message': 'The order is already filled or canceled.'}
'600503': InvalidOrder, # {"code":600503,"message":"Notional is too small."}
# documented
'100001': BadRequest, # INVALID_HTTP_INPUT Http request is invalid
'100002': BadRequest, # DATA_NOT_AVAILABLE Some required data is missing
'100003': BadRequest, # KEY_CONFLICT The same key exists already
'100004': BadRequest, # INVALID_REQUEST_DATA The HTTP request contains invalid field or argument
'100005': BadRequest, # INVALID_WS_REQUEST_DATA Websocket request contains invalid field or argument
'100006': BadRequest, # INVALID_ARGUMENT The arugment is invalid
'100007': BadRequest, # ENCRYPTION_ERROR Something wrong with data encryption
'100008': BadSymbol, # SYMBOL_ERROR Symbol does not exist or not valid for the request
'100009': AuthenticationError, # AUTHORIZATION_NEEDED Authorization is require for the API access or request
'100010': BadRequest, # INVALID_OPERATION The action is invalid or not allowed for the account
'100011': BadRequest, # INVALID_TIMESTAMP Not a valid timestamp
'100012': BadRequest, # INVALID_STR_FORMAT str format does not
'100013': BadRequest, # INVALID_NUM_FORMAT Invalid number input
'100101': ExchangeError, # UNKNOWN_ERROR Some unknown error
'150001': BadRequest, # INVALID_JSON_FORMAT Require a valid json object
'200001': AuthenticationError, # AUTHENTICATION_FAILED Authorization failed
'200002': ExchangeError, # TOO_MANY_ATTEMPTS Tried and failed too many times
'200003': ExchangeError, # ACCOUNT_NOT_FOUND Account not exist
'200004': ExchangeError, # ACCOUNT_NOT_SETUP Account not setup properly
'200005': ExchangeError, # ACCOUNT_ALREADY_EXIST Account already exist
'200006': ExchangeError, # ACCOUNT_ERROR Some error related with error
'200007': ExchangeError, # CODE_NOT_FOUND
'200008': ExchangeError, # CODE_EXPIRED Code expired
'200009': ExchangeError, # CODE_MISMATCH Code does not match
'200010': AuthenticationError, # PASSWORD_ERROR Wrong assword
'200011': ExchangeError, # CODE_GEN_FAILED Do not generate required code promptly
'200012': ExchangeError, # FAKE_COKE_VERIFY
'200013': ExchangeError, # SECURITY_ALERT Provide security alert message
'200014': PermissionDenied, # RESTRICTED_ACCOUNT Account is restricted for certain activity, such, or withdraw.
'200015': PermissionDenied, # PERMISSION_DENIED No enough permission for the operation
'300001': InvalidOrder, # INVALID_PRICE Order price is invalid
'300002': InvalidOrder, # INVALID_QTY Order size is invalid
'300003': InvalidOrder, # INVALID_SIDE Order side is invalid
'300004': InvalidOrder, # INVALID_NOTIONAL Notional is too small or too large
'300005': InvalidOrder, # INVALID_TYPE Order typs is invalid
'300006': InvalidOrder, # INVALID_ORDER_ID Order id is invalid
'300007': InvalidOrder, # INVALID_TIME_IN_FORCE Time In Force in order request is invalid
'300008': InvalidOrder, # INVALID_ORDER_PARAMETER Some order parameter is invalid
'300009': InvalidOrder, # TRADING_VIOLATION Trading violation on account or asset
'300011': InsufficientFunds, # INVALID_BALANCE No enough account or asset balance for the trading
'300012': BadSymbol, # INVALID_PRODUCT Not a valid product supported by exchange
'300013': InvalidOrder, # INVALID_BATCH_ORDER Some or all orders are invalid in batch order request
'300014': InvalidOrder, # {"code":300014,"message":"Order price doesn't conform to the required tick size: 0.1","reason":"TICK_SIZE_VIOLATION"}
'300020': InvalidOrder, # TRADING_RESTRICTED There is some trading restriction on account or asset
'300021': AccountSuspended, # {"code":300021,"message":"Trading disabled for self account.","reason":"TRADING_DISABLED"}
'300031': InvalidOrder, # NO_MARKET_PRICE No market price for market type order trading
'310001': InsufficientFunds, # INVALID_MARGIN_BALANCE No enough margin balance
'310002': InvalidOrder, # INVALID_MARGIN_ACCOUNT Not a valid account for margin trading
'310003': InvalidOrder, # MARGIN_TOO_RISKY Leverage is too high
'310004': BadSymbol, # INVALID_MARGIN_ASSET This asset does not support margin trading
'310005': InvalidOrder, # INVALID_REFERENCE_PRICE There is no valid reference price
'510001': ExchangeError, # SERVER_ERROR Something wrong with server.
'900001': ExchangeError, # HUMAN_CHALLENGE Human change do not pass
},
'broad': {},
},
'commonCurrencies': {
'XBT': 'XBT', # self is not BTC ! just another token
'BOND': 'BONDED',
'BTCBEAR': 'BEAR',
'BTCBULL': 'BULL',
'BYN': 'BeyondFi',
'PLN': 'Pollen',
},
})
def get_account(self, params={}):
# get current or provided bitmax sub-account
account = self.safe_value(params, 'account', self.options['account'])
lowercaseAccount = account.lower()
return self.capitalize(lowercaseAccount)
def fetch_currencies(self, params={}) -> Currencies:
"""
fetches all available currencies on an exchange
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an associative dictionary of currencies
"""
response = self.v2PublicGetAssets(params)
#
# {
# "code": "0",
# "data": [
# {
# "assetCode": "USDT",
# "assetName": "Tether",
# "precisionScale": 9,
# "nativeScale": 4,
# "blockChain": [
# {
# "chainName": "Solana",
# "withdrawFee": "2.0",
# "allowDeposit": True,
# "allowWithdraw": True,
# "minDepositAmt": "0.01",
# "minWithdrawal": "4.0",
# "numConfirmations": 1
# },
# ...
# ]
# },
# ]
# }
#
data = self.safe_list(response, 'data', [])
result: dict = {}
for i in range(0, len(data)):
currency = data[i]
id = self.safe_string(currency, 'assetCode')
code = self.safe_currency_code(id)
chains = self.safe_list(currency, 'blockChain', [])
precision = self.parse_number(self.parse_precision(self.safe_string(currency, 'nativeScale')))
networks = {}
for j in range(0, len(chains)):
networkEtnry = chains[j]
networkId = self.safe_string(networkEtnry, 'chainName')
networkCode = self.network_code_to_id(networkId)
networks[networkCode] = {
'fee': self.safe_number(networkEtnry, 'withdrawFee'),
'active': None,
'withdraw': self.safe_bool(networkEtnry, 'allowWithdraw'),
'deposit': self.safe_bool(networkEtnry, 'allowDeposit'),
'precision': precision,
'limits': {
'amount': {
'min': None,
'max': None,
},
'withdraw': {
'min': self.safe_number(networkEtnry, 'minWithdrawal'),
'max': None,
},
'deposit': {
'min': self.safe_number(networkEtnry, 'minDepositAmt'),
'max': None,
},
},
}
# todo type: if chainsLength == 0 and (assetName.endswith(' Staking') or assetName.find(' Reward ') >= 0 or assetName.find('Slot Auction') >= 0 or assetName.find(' Freeze Asset') >= 0):
result[code] = self.safe_currency_structure({
'id': id,
'code': code,
'info': currency,
'type': None,
'margin': None,
'name': self.safe_string(currency, 'assetName'),
'active': None,
'deposit': None,
'withdraw': None,
'fee': None,
'precision': precision,
'limits': {
'amount': {
'min': None,
'max': None,
},
'withdraw': {
'min': self.safe_number(currency, 'minWithdrawalAmt'),
'max': None,
},
},
'networks': networks,
})
return result
def fetch_markets(self, params={}) -> List[Market]:
"""
retrieves data on all markets for ascendex
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: an array of objects representing market data
"""
spotPromise = self.fetch_spot_markets(params)
contractPromise = self.fetch_contract_markets(params)
spotMarkets, contractMarkets = [spotPromise, contractPromise]
return self.array_concat(spotMarkets, contractMarkets)
def fetch_spot_markets(self, params={}) -> List[Market]:
productsPromise = self.v1PublicGetProducts(params)
#
# {
# "code": 0,
# "data": [
# {
# "symbol": "LBA/BTC",
# "baseAsset": "LBA",
# "quoteAsset": "BTC",
# "status": "Normal",
# "minNotional": "0.000625",
# "maxNotional": "6.25",
# "marginTradable": False,
# "commissionType": "Quote",
# "commissionReserveRate": "0.001",
# "tickSize": "0.000000001",
# "lotSize": "1"
# },
# ]
# }
#
cashPromise = self.v1PublicGetCashProducts(params)
#
# {
# "code": 0,
# "data": [
# {
# "symbol": "QTUM/BTC",
# "displayName": "QTUM/BTC",
# "domain": "BTC",
# "tradingStartTime": 1569506400000,
# "collapseDecimals": "0.0001,0.000001,0.00000001",
# "minQty": "0.000000001",
# "maxQty": "1000000000",
# "minNotional": "0.000625",
# "maxNotional": "12.5",
# "statusCode": "Normal",
# "statusMessage": "",
# "tickSize": "0.00000001",
# "useTick": False,
# "lotSize": "0.1",
# "useLot": False,
# "commissionType": "Quote",
# "commissionReserveRate": "0.001",
# "qtyScale": 1,
# "priceScale": 8,
# "notionalScale": 4
# }
# ]
# }
#
products, cash = [productsPromise, cashPromise]
productsData = self.safe_list(products, 'data', [])
productsById = self.index_by(productsData, 'symbol')
cashData = self.safe_list(cash, 'data', [])
cashAndPerpetualsById = self.index_by(cashData, 'symbol')
dataById = self.deep_extend(productsById, cashAndPerpetualsById)
ids = list(dataById.keys())
result = []
for i in range(0, len(ids)):
id = ids[i]
if id.find('-PERP') >= 0:
continue # skip perpetuals, endpoint returns them
market = dataById[id]
status = self.safe_string(market, 'status')
domain = self.safe_string(market, 'domain')
active = False
if ((status == 'Normal') or (status == 'InternalTrading')) and (domain != 'LeveragedETF'):
active = True
minQty = self.safe_number(market, 'minQty')
maxQty = self.safe_number(market, 'maxQty')
minPrice = self.safe_number(market, 'tickSize')
maxPrice: Num = None
underlying = self.safe_string_2(market, 'underlying', 'symbol')
parts = underlying.split('/')
baseId = self.safe_string(parts, 0)
quoteId = self.safe_string(parts, 1)
base = self.safe_currency_code(baseId)
quote = self.safe_currency_code(quoteId)
fee = self.safe_number(market, 'commissionReserveRate')
marginTradable = self.safe_bool(market, 'marginTradable', False)
result.append({
'id': id,
'symbol': base + '/' + quote,
'base': base,
'baseId': baseId,
'quote': quote,
'quoteId': quoteId,
'settle': None,
'settleId': None,
'type': 'spot',
'spot': True,
'margin': marginTradable,
'swap': False,
'future': False,
'option': False,
'active': active,
'contract': False,
'linear': None,
'inverse': None,
'taker': fee,
'maker': fee,
'contractSize': None,
'expiry': None,
'expiryDatetime': None,
'strike': None,
'optionType': None,
'precision': {
'amount': self.safe_number(market, 'lotSize'),
'price': self.safe_number(market, 'tickSize'),
},
'limits': {
'leverage': {
'min': None,
'max': None,
},
'amount': {
'min': minQty,
'max': maxQty,
},
'price': {
'min': minPrice,
'max': maxPrice,
},
'cost': {
'min': self.safe_number(market, 'minNotional'),
'max': self.safe_number(market, 'maxNotional'),
},
},
'created': self.safe_integer(market, 'tradingStartTime'),
'info': market,
})
return result
def fetch_contract_markets(self, params={}) -> List[Market]:
contracts = self.v2PublicGetFuturesContract(params)
#
# {
# "code": 0,
# "data": [
# {
# "symbol": "BTC-PERP",
# "status": "Normal",
# "displayName": "BTCUSDT",
# "settlementAsset": "USDT",
# "underlying": "BTC/USDT",
# "tradingStartTime": 1579701600000,
# "priceFilter": {
# "minPrice": "0.1",
# "maxPrice": "1000000",
# "tickSize": "0.1"
# },
# "lotSizeFilter": {
# "minQty": "0.0001",
# "maxQty": "1000000000",
# "lotSize": "0.0001"
# },
# "commissionType": "Quote",
# "commissionReserveRate": "0.001",
# "marketOrderPriceMarkup": "0.03",
# "marginRequirements": [
# {
# "positionNotionalLowerBound": "0",
# "positionNotionalUpperBound": "50000",
# "initialMarginRate": "0.01",
# "maintenanceMarginRate": "0.006"
# },
# ...
# ]
# }
# ]
# }
#
data = self.safe_list(contracts, 'data', [])
result = []
for i in range(0, len(data)):
market = data[i]
id = self.safe_string(market, 'symbol')
underlying = self.safe_string(market, 'underlying')
parts = underlying.split('/')
baseId = self.safe_string(parts, 0)
base = self.safe_currency_code(baseId)
quoteId = self.safe_string(parts, 1)
quote = self.safe_currency_code(quoteId)
settleId = self.safe_string(market, 'settlementAsset')
settle = self.safe_currency_code(settleId)
linear = settle == quote
inverse = settle == base
symbol = base + '/' + quote + ':' + settle
priceFilter = self.safe_dict(market, 'priceFilter')
lotSizeFilter = self.safe_dict(market, 'lotSizeFilter')
fee = self.safe_number(market, 'commissionReserveRate')
result.append({
'id': id,
'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_string(market, 'status') == 'Normal',
'contract': True,
'linear': linear,
'inverse': inverse,
'taker': fee,
'maker': fee,
'contractSize': self.parse_number('1'),
'expiry': None,
'expiryDatetime': None,
'strike': None,
'optionType': None,
'precision': {
'amount': self.safe_number(lotSizeFilter, 'lotSize'),
'price': self.safe_number(priceFilter, 'tickSize'),
},
'limits': {
'leverage': {
'min': None,
'max': None,
},
'amount': {
'min': self.safe_number(lotSizeFilter, 'minQty'),
'max': self.safe_number(lotSizeFilter, 'maxQty'),
},
'price': {
'min': self.safe_number(priceFilter, 'minPrice'),
'max': self.safe_number(priceFilter, 'maxPrice'),
},
'cost': {
'min': self.safe_number(market, 'minNotional'),
'max': self.safe_number(market, 'maxNotional'),
},
},
'created': self.safe_integer(market, 'tradingStartTime'),
'info': market,
})
return result
def fetch_time(self, params={}) -> Int:
"""
fetches the current integer timestamp in milliseconds from the ascendex server
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns int: the current integer timestamp in milliseconds from the ascendex server
"""
request: dict = {
'requestTime': self.milliseconds(),
}
response = self.v1PublicGetExchangeInfo(self.extend(request, params))
#
# {
# "code": 0,
# "data": {
# "requestTimeEcho": 1656560463601,
# "requestReceiveAt": 1656560464331,
# "latency": 730
# }
# }
#
data = self.safe_dict(response, 'data', {})
return self.safe_integer(data, 'requestReceiveAt')
def fetch_accounts(self, params={}) -> List[Account]:
"""
fetch all the accounts associated with a profile
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a dictionary of `account structures <https://docs.ccxt.com/#/?id=account-structure>` indexed by the account type
"""
accountGroup = self.safe_string(self.options, 'account-group')
response = None
if accountGroup is None:
response = self.v1PrivateGetInfo(params)
#
# {
# "code":0,
# "data":{
# "email":"igor.kroitor@gmail.com",
# "accountGroup":8,
# "viewPermission":true,
# "tradePermission":true,
# "transferPermission":true,
# "cashAccount":["cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda"],
# "marginAccount":["martXoh1v1N3EMQC5FDtSj5VHso8aI2Z"],
# "futuresAccount":["futc9r7UmFJAyBY2rE3beA2JFxav2XFF"],
# "userUID":"U6491137460"
# }
# }
#
data = self.safe_dict(response, 'data', {})
accountGroup = self.safe_string(data, 'accountGroup')
self.options['account-group'] = accountGroup
return [
{
'id': accountGroup,
'type': None,
'code': None,
'info': response,
},
]
def parse_balance(self, response) -> Balances:
result: dict = {
'info': response,
'timestamp': None,
'datetime': None,
}
balances = self.safe_list(response, 'data', [])
for i in range(0, len(balances)):
balance = balances[i]
code = self.safe_currency_code(self.safe_string(balance, 'asset'))
account = self.account()
account['free'] = self.safe_string(balance, 'availableBalance')
account['total'] = self.safe_string(balance, 'totalBalance')
result[code] = account
return self.safe_balance(result)
def parse_margin_balance(self, response):
result: dict = {
'info': response,
'timestamp': None,
'datetime': None,
}
balances = self.safe_list(response, 'data', [])
for i in range(0, len(balances)):
balance = balances[i]
code = self.safe_currency_code(self.safe_string(balance, 'asset'))
account = self.account()
account['free'] = self.safe_string(balance, 'availableBalance')
account['total'] = self.safe_string(balance, 'totalBalance')
debt = self.safe_string(balance, 'borrowed')
interest = self.safe_string(balance, 'interest')
account['debt'] = Precise.string_add(debt, interest)
result[code] = account
return self.safe_balance(result)
def parse_swap_balance(self, response):
result: dict = {
'info': response,
'timestamp': None,
'datetime': None,
}
data = self.safe_dict(response, 'data', {})
collaterals = self.safe_list(data, 'collaterals', [])
for i in range(0, len(collaterals)):
balance = collaterals[i]
code = self.safe_currency_code(self.safe_string(balance, 'asset'))
account = self.account()
account['total'] = self.safe_string(balance, 'balance')
result[code] = account
return self.safe_balance(result)
def fetch_balance(self, params={}) -> Balances:
"""
query for balance and get the amount of funds available for trading or funds locked in orders
https://ascendex.github.io/ascendex-pro-api/#cash-account-balance
https://ascendex.github.io/ascendex-pro-api/#margin-account-balance
https://ascendex.github.io/ascendex-futures-pro-api-v2/#position
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.type]: wallet type, 'spot', 'margin', or 'swap'
:param str [params.marginMode]: 'cross' or None, for spot margin trading, value of 'isolated' is invalid
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
"""
self.load_markets()
self.load_accounts()
marketType = None
marginMode = None
marketType, params = self.handle_market_type_and_params('fetchBalance', None, params)
marginMode, params = self.handle_margin_mode_and_params('fetchBalance', params)
isMargin = self.safe_bool(params, 'margin', False)
isCross = marginMode == 'cross'
marketType = 'margin' if (isMargin or isCross) else marketType
params = self.omit(params, 'margin')
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
accountCategory = self.safe_string(accountsByType, marketType, 'cash')
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
}
if (marginMode == 'isolated') and (marketType != 'swap'):
raise BadRequest(self.id + ' does not supported isolated margin trading')
if (accountCategory == 'cash') or (accountCategory == 'margin'):
request['account-category'] = accountCategory
response = None
if (marketType == 'spot') or (marketType == 'margin'):
response = self.v1PrivateAccountCategoryGetBalance(self.extend(request, params))
elif marketType == 'swap':
response = self.v2PrivateAccountGroupGetFuturesPosition(self.extend(request, params))
else:
raise NotSupported(self.id + ' fetchBalance() is not currently supported for ' + marketType + ' markets')
#
# cash
#
# {
# "code": 0,
# "data": [
# {
# "asset": "BCHSV",
# "totalBalance": "64.298000048",
# "availableBalance": "64.298000048",
# },
# ]
# }
#
# margin
#
# {
# "code": 0,
# "data": [
# {
# "asset": "BCHSV",
# "totalBalance": "64.298000048",
# "availableBalance": "64.298000048",
# "borrowed": "0",
# "interest": "0",
# },
# ]
# }
#
# swap
#
# {
# "code": 0,
# "data": {
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "ac": "FUTURES",
# "collaterals": [
# {"asset":"ADA","balance":"0.355803","referencePrice":"1.05095","discountFactor":"0.9"},
# {"asset":"USDT","balance":"0.000014519","referencePrice":"1","discountFactor":"1"}
# ],
# }j
# }
#
if marketType == 'swap':
return self.parse_swap_balance(response)
elif marketType == 'margin':
return self.parse_margin_balance(response)
else:
return self.parse_balance(response)
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
: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['id'],
}
response = self.v1PublicGetDepth(self.extend(request, params))
#
# {
# "code":0,
# "data":{
# "m":"depth-snapshot",
# "symbol":"BTC-PERP",
# "data":{
# "ts":1590223998202,
# "seqnum":115444921,
# "asks":[
# ["9207.5","18.2383"],
# ["9207.75","18.8235"],
# ["9208","10.7873"],
# ],
# "bids":[
# ["9207.25","0.4009"],
# ["9207","0.003"],
# ["9206.5","0.003"],
# ]
# }
# }
# }
#
data = self.safe_dict(response, 'data', {})
orderbook = self.safe_dict(data, 'data', {})
timestamp = self.safe_integer(orderbook, 'ts')
result = self.parse_order_book(orderbook, symbol, timestamp)
result['nonce'] = self.safe_integer(orderbook, 'seqnum')
return result
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
#
# {
# "symbol":"QTUM/BTC",
# "open":"0.00016537",
# "close":"0.00019077",
# "high":"0.000192",
# "low":"0.00016537",
# "volume":"846.6",
# "ask":["0.00018698","26.2"],
# "bid":["0.00018408","503.7"],
# "type":"spot"
# }
#
timestamp = None
marketId = self.safe_string(ticker, 'symbol')
type = self.safe_string(ticker, 'type')
delimiter = '/' if (type == 'spot') else None
symbol = self.safe_symbol(marketId, market, delimiter)
close = self.safe_string(ticker, 'close')
bid = self.safe_list(ticker, 'bid', [])
ask = self.safe_list(ticker, 'ask', [])
open = self.safe_string(ticker, 'open')
return self.safe_ticker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': None,
'high': self.safe_string(ticker, 'high'),
'low': self.safe_string(ticker, 'low'),
'bid': self.safe_string(bid, 0),
'bidVolume': self.safe_string(bid, 1),
'ask': self.safe_string(ask, 0),
'askVolume': self.safe_string(ask, 1),
'vwap': None,
'open': open,
'close': close,
'last': close,
'previousClose': None, # previous day close
'change': None,
'percentage': None,
'average': None,
'baseVolume': self.safe_string(ticker, 'volume'),
'quoteVolume': None,
'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
: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['id'],
}
response = self.v1PublicGetTicker(self.extend(request, params))
#
# {
# "code":0,
# "data":{
# "symbol":"BTC-PERP", # or "BTC/USDT"
# "open":"9073",
# "close":"9185.75",
# "high":"9185.75",
# "low":"9185.75",
# "volume":"576.8334",
# "ask":["9185.75","15.5863"],
# "bid":["9185.5","0.003"],
# "type":"derivatives", # or "spot"
# }
# }
#
data = self.safe_dict(response, 'data', {})
return self.parse_ticker(data, market)
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://ascendex.github.io/ascendex-pro-api/#ticker
https://ascendex.github.io/ascendex-futures-pro-api-v2/#ticker
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
self.load_markets()
request: dict = {}
market = None
if symbols is not None:
symbol = self.safe_string(symbols, 0)
market = self.market(symbol)
marketIds = self.market_ids(symbols)
request['symbol'] = ','.join(marketIds)
type = None
type, params = self.handle_market_type_and_params('fetchTickers', market, params)
response = None
if type == 'spot':
response = self.v1PublicGetTicker(self.extend(request, params))
else:
response = self.v2PublicGetFuturesTicker(self.extend(request, params))
#
# {
# "code":0,
# "data": {
# "symbol":"QTUM/BTC",
# "open":"0.00016537",
# "close":"0.00019077",
# "high":"0.000192",
# "low":"0.00016537",
# "volume":"846.6",
# "ask":["0.00018698","26.2"],
# "bid":["0.00018408","503.7"],
# "type":"spot"
# }
# }
#
data = self.safe_list(response, 'data', [])
if not isinstance(data, list):
return self.parse_tickers([data], symbols)
return self.parse_tickers(data, symbols)
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
#
# {
# "m":"bar",
# "s":"BTC/USDT",
# "data":{
# "i":"1",
# "ts":1590228000000,
# "o":"9139.59",
# "c":"9131.94",
# "h":"9139.99",
# "l":"9121.71",
# "v":"25.20648"
# }
# }
#
data = self.safe_dict(ohlcv, 'data', {})
return [
self.safe_integer(data, 'ts'),
self.safe_number(data, 'o'),
self.safe_number(data, 'h'),
self.safe_number(data, 'l'),
self.safe_number(data, 'c'),
self.safe_number(data, 'v'),
]
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
: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 = {
'symbol': market['id'],
'interval': self.safe_string(self.timeframes, timeframe, timeframe),
}
# if since and limit are not specified
# the exchange will return just 1 last candle by default
duration = self.parse_timeframe(timeframe)
options = self.safe_dict(self.options, 'fetchOHLCV', {})
defaultLimit = self.safe_integer(options, 'limit', 500)
until = self.safe_integer(params, 'until')
if since is not None:
request['from'] = since
if limit is None:
limit = defaultLimit
else:
limit = min(limit, defaultLimit)
toWithLimit = self.sum(since, limit * duration * 1000, 1)
if until is not None:
request['to'] = min(toWithLimit, until + 1)
else:
request['to'] = toWithLimit
elif until is not None:
request['to'] = until + 1
if limit is None:
limit = defaultLimit
else:
limit = min(limit, defaultLimit)
request['from'] = until - (limit * duration * 1000)
elif limit is not None:
request['n'] = limit # max 500
params = self.omit(params, 'until')
response = self.v1PublicGetBarhist(self.extend(request, params))
#
# {
# "code":0,
# "data":[
# {
# "m":"bar",
# "s":"BTC/USDT",
# "data":{
# "i":"1",
# "ts":1590228000000,
# "o":"9139.59",
# "c":"9131.94",
# "h":"9139.99",
# "l":"9121.71",
# "v":"25.20648"
# }
# }
# ]
# }
#
data = self.safe_list(response, 'data', [])
return self.parse_ohlcvs(data, market, timeframe, since, limit)
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
#
# public fetchTrades
#
# {
# "p":"9128.5", # price
# "q":"0.0030", # quantity
# "ts":1590229002385, # timestamp
# "bm":false, # if True, the buyer is the market maker, we only use self field to "define the side" of a public trade
# "seqnum":180143985289898554
# }
#
timestamp = self.safe_integer(trade, 'ts')
priceString = self.safe_string_2(trade, 'price', 'p')
amountString = self.safe_string(trade, 'q')
buyerIsMaker = self.safe_bool(trade, 'bm', False)
side = 'sell' if buyerIsMaker else 'buy'
market = self.safe_market(None, market)
return self.safe_trade({
'info': trade,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': market['symbol'],
'id': None,
'order': None,
'type': None,
'takerOrMaker': None,
'side': side,
'price': priceString,
'amount': amountString,
'cost': None,
'fee': None,
}, market)
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://ascendex.github.io/ascendex-pro-api/#market-trades
: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
: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['id'],
}
if limit is not None:
request['n'] = limit # max 100
response = self.v1PublicGetTrades(self.extend(request, params))
#
# {
# "code":0,
# "data":{
# "m":"trades",
# "symbol":"BTC-PERP",
# "data":[
# {"p":"9128.5","q":"0.0030","ts":1590229002385,"bm":false,"seqnum":180143985289898554},
# {"p":"9129","q":"0.0030","ts":1590229002642,"bm":false,"seqnum":180143985289898587},
# {"p":"9129.5","q":"0.0030","ts":1590229021306,"bm":false,"seqnum":180143985289899043}
# ]
# }
# }
#
records = self.safe_dict(response, 'data', {})
trades = self.safe_list(records, 'data', [])
return self.parse_trades(trades, market, since, limit)
def parse_order_status(self, status: Str):
statuses: dict = {
'PendingNew': 'open',
'New': 'open',
'PartiallyFilled': 'open',
'Filled': 'closed',
'Canceled': 'canceled',
'Rejected': 'rejected',
}
return self.safe_string(statuses, status, status)
def parse_order(self, order: dict, market: Market = None) -> Order:
#
# createOrder
#
# {
# "id": "16e607e2b83a8bXHbAwwoqDo55c166fa",
# "orderId": "16e85b4d9b9a8bXHbAwwoqDoc3d66830",
# "orderType": "Market",
# "symbol": "BTC/USDT",
# "timestamp": 1573576916201
# }
#
# & linear(fetchClosedOrders)
#
# {
# "ac": "FUTURES",
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "time": 1640819389454,
# "orderId": "a17e0874ecbdU0711043490bbtcpDU5X",
# "seqNum": -1,
# "orderType": "Limit",
# "execInst": "NULL_VAL", # NULL_VAL, ReduceOnly , ...
# "side": "Buy",
# "symbol": "BTC-PERP",
# "price": "30000",
# "orderQty": "0.002",
# "stopPrice": "0",
# "stopBy": "ref-px",
# "status": "Ack",
# "lastExecTime": 1640819389454,
# "lastQty": "0",
# "lastPx": "0",
# "avgFilledPx": "0",
# "cumFilledQty": "0",
# "fee": "0",
# "cumFee": "0",
# "feeAsset": "",
# "errorCode": "",
# "posStopLossPrice": "0",
# "posStopLossTrigger": "market",
# "posTakeProfitPrice": "0",
# "posTakeProfitTrigger": "market",
# "liquidityInd": "n"
# }
#
# fetchOrder, fetchOpenOrders, fetchClosedOrders
#
# {
# "symbol": "BTC/USDT",
# "price": "8131.22",
# "orderQty": "0.00082",
# "orderType": "Market",
# "avgPx": "7392.02",
# "cumFee": "0.005152238",
# "cumFilledQty": "0.00082",
# "errorCode": "",
# "feeAsset": "USDT",
# "lastExecTime": 1575953151764,
# "orderId": "a16eee20b6750866943712zWEDdAjt3",
# "seqNum": 2623469,
# "side": "Buy",
# "status": "Filled",
# "stopPrice": "",
# "execInst": "NULL_VAL" # "Post"(for postOnly orders), "reduceOnly"(for reduceOnly orders)
# }
#
# {
# "orderId": "a173ad938fc3U22666567717788c3b66", # orderId
# "seqNum": 18777366360, # sequence number
# "accountId": "cshwSjbpPjSwHmxPdz2CPQVU9mnbzPpt", # accountId
# "symbol": "BTC/USDT", # symbol
# "orderType": "Limit", # order type(Limit/Market/StopMarket/StopLimit)
# "side": "Sell", # order side(Buy/Sell)
# "price": "11346.77", # order price
# "stopPrice": "0", # stop price(0 by default)
# "orderQty": "0.01", # order quantity(in base asset)
# "status": "Canceled", # order status(Filled/Canceled/Rejected)
# "createTime": 1596344995793, # order creation time
# "lastExecTime": 1596344996053, # last execution time
# "avgFillPrice": "11346.77", # average filled price
# "fillQty": "0.01", # filled quantity(in base asset)
# "fee": "-0.004992579", # cummulative fee. if negative, self value is the commission charged; if possitive, self value is the rebate received.
# "feeAsset": "USDT" # fee asset
# }
#
# {
# "ac": "FUTURES",
# "accountId": "testabcdefg",
# "avgPx": "0",
# "cumFee": "0",
# "cumQty": "0",
# "errorCode": "NULL_VAL",
# "execInst": "NULL_VAL",
# "feeAsset": "USDT",
# "lastExecTime": 1584072844085,
# "orderId": "r170d21956dd5450276356bbtcpKa74",
# "orderQty": "1.1499",
# "orderType": "Limit",
# "price": "4000",
# "sendingTime": 1584072841033,
# "seqNum": 24105338,
# "side": "Buy",
# "status": "Canceled",
# "stopPrice": "",
# "symbol": "BTC-PERP"
# },
#
status = self.parse_order_status(self.safe_string(order, 'status'))
marketId = self.safe_string(order, 'symbol')
symbol = self.safe_symbol(marketId, market, '/')
timestamp = self.safe_integer_n(order, ['timestamp', 'sendingTime', 'time'])
lastTradeTimestamp = self.safe_integer(order, 'lastExecTime')
if timestamp is None:
timestamp = lastTradeTimestamp
price = self.safe_string(order, 'price')
amount = self.safe_string(order, 'orderQty')
average = self.safe_string_2(order, 'avgPx', 'avgFilledPx')
filled = self.safe_string_n(order, ['cumFilledQty', 'cumQty', 'fillQty'])
id = self.safe_string(order, 'orderId')
clientOrderId = self.safe_string(order, 'id')
if clientOrderId is not None:
if len(clientOrderId) < 1:
clientOrderId = None
rawTypeLower = self.safe_string_lower(order, 'orderType')
type = rawTypeLower
if rawTypeLower is not None:
if rawTypeLower == 'stoplimit':
type = 'limit'
if rawTypeLower == 'stopmarket':
type = 'market'
side = self.safe_string_lower(order, 'side')
feeCost = self.safe_number_2(order, 'cumFee', 'fee')
fee = None
if feeCost is not None:
feeCurrencyId = self.safe_string(order, 'feeAsset')
feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
fee = {
'cost': feeCost,
'currency': feeCurrencyCode,
}
triggerPrice = self.omit_zero(self.safe_string(order, 'stopPrice'))
reduceOnly = None
execInst = self.safe_string_lower(order, 'execInst')
if execInst == 'reduceonly':
reduceOnly = True
postOnly = None
if execInst == 'post':
postOnly = True
return self.safe_order({
'info': order,
'id': id,
'clientOrderId': clientOrderId,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': None,
'postOnly': postOnly,
'reduceOnly': reduceOnly,
'side': side,
'price': price,
'triggerPrice': triggerPrice,
'amount': amount,
'cost': None,
'average': average,
'filled': filled,
'remaining': None,
'status': status,
'fee': fee,
'trades': None,
}, market)
def fetch_trading_fees(self, params={}) -> TradingFees:
"""
fetch the trading fees for multiple markets
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
"""
self.load_markets()
self.load_accounts()
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
}
response = self.v1PrivateAccountGroupGetSpotFee(self.extend(request, params))
#
# {
# "code": "0",
# "data": {
# "domain": "spot",
# "userUID": "U1479576457",
# "vipLevel": "0",
# "fees": [
# {symbol: 'HT/USDT', fee: {taker: '0.001', maker: "0.001"}},
# {symbol: 'LAMB/BTC', fee: {taker: '0.002', maker: "0.002"}},
# {symbol: 'STOS/USDT', fee: {taker: '0.002', maker: "0.002"}},
# ...
# ]
# }
# }
#
data = self.safe_dict(response, 'data', {})
fees = self.safe_list(data, 'fees', [])
result: dict = {}
for i in range(0, len(fees)):
fee = fees[i]
marketId = self.safe_string(fee, 'symbol')
symbol = self.safe_symbol(marketId, None, '/')
takerMaker = self.safe_dict(fee, 'fee', {})
result[symbol] = {
'info': fee,
'symbol': symbol,
'maker': self.safe_number(takerMaker, 'maker'),
'taker': self.safe_number(takerMaker, 'taker'),
'percentage': None,
'tierBased': None,
}
return result
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
"""
@ignore
helper function to build request
: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 you want to trade in units of the base currency
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.timeInForce]: "GTC", "IOC", "FOK", or "PO"
:param bool [params.postOnly]: True or False
:param float [params.triggerPrice]: the price at which a trigger order is triggered at
:returns dict: request to be sent to the exchange
"""
market = self.market(symbol)
marginMode = None
marketType = None
marginMode, params = self.handle_margin_mode_and_params('createOrderRequest', params)
marketType, params = self.handle_market_type_and_params('createOrderRequest', market, params)
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
accountCategory = self.safe_string(accountsByType, marketType, 'cash')
if marginMode is not None:
accountCategory = 'margin'
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
clientOrderId = self.safe_string_2(params, 'clientOrderId', 'id')
request: dict = {
'account-group': accountGroup,
'account-category': accountCategory,
'symbol': market['id'],
'time': self.milliseconds(),
'orderQty': self.amount_to_precision(symbol, amount),
'orderType': type, # limit, market, stop_market, stop_limit
'side': side, # buy or sell,
# 'execInst': # Post for postOnly, ReduceOnly for reduceOnly
# 'respInst': 'ACK', # ACK, 'ACCEPT, DONE
}
isMarketOrder = ((type == 'market') or (type == 'stop_market'))
isLimitOrder = ((type == 'limit') or (type == 'stop_limit'))
timeInForce = self.safe_string(params, 'timeInForce')
postOnly = self.is_post_only(isMarketOrder, False, params)
reduceOnly = self.safe_bool(params, 'reduceOnly', False)
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
if isLimitOrder:
request['orderPrice'] = self.price_to_precision(symbol, price)
if timeInForce == 'IOC':
request['timeInForce'] = 'IOC'
if timeInForce == 'FOK':
request['timeInForce'] = 'FOK'
if postOnly:
request['postOnly'] = True
if triggerPrice is not None:
request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
if isLimitOrder:
request['orderType'] = 'stop_limit'
elif isMarketOrder:
request['orderType'] = 'stop_market'
if clientOrderId is not None:
request['id'] = clientOrderId
if market['spot']:
if accountCategory is not None:
request['category'] = accountCategory
else:
request['account-category'] = accountCategory
if reduceOnly:
request['execInst'] = 'ReduceOnly'
if postOnly:
request['execInst'] = 'Post'
params = self.omit(params, ['reduceOnly', 'triggerPrice'])
return self.extend(request, params)
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
"""
create a trade order on the exchange
https://ascendex.github.io/ascendex-pro-api/#place-order
https://ascendex.github.io/ascendex-futures-pro-api-v2/#new-order
:param str symbol: unified CCXT market symbol
:param str type: "limit" or "market"
:param str side: "buy" or "sell"
:param float amount: the amount of currency to trade
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.timeInForce]: "GTC", "IOC", "FOK", or "PO"
:param bool [params.postOnly]: True or False
:param float [params.triggerPrice]: the price at which a trigger order is triggered at
:param dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice that the attached take profit order will be triggered(perpetual swap markets only)
:param float [params.takeProfit.triggerPrice]: *swap only* take profit trigger price
:param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice that the attached stop loss order will be triggered(perpetual swap markets only)
:param float [params.stopLoss.triggerPrice]: *swap only* stop loss trigger price
:returns: `An order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
self.load_markets()
self.load_accounts()
market = self.market(symbol)
request = self.create_order_request(symbol, type, side, amount, price, params)
response = None
if market['swap']:
response = self.v2PrivateAccountGroupPostFuturesOrder(request)
else:
response = self.v1PrivateAccountCategoryPostOrder(request)
#
# spot
#
# {
# "code":0,
# "data": {
# "accountId":"cshwT8RKojkT1HoaA5UdeimR2SrmHG2I",
# "ac":"CASH",
# "action":"place-order",
# "status":"Ack",
# "info": {
# "symbol":"TRX/USDT",
# "orderType":"StopLimit",
# "timestamp":1654290662172,
# "id":"",
# "orderId":"a1812b6840ddU8191168955av0k6Eyhj"
# }
# }
# }
#
# swap
#
# {
# "code":0,
# "data": {
# "meta": {
# "id":"",
# "action":"place-order",
# "respInst":"ACK"
# },
# "order": {
# "ac":"FUTURES",
# "accountId":"futwT8RKojkT1HoaA5UdeimR2SrmHG2I",
# "time":1654290969965,
# "orderId":"a1812b6cf322U8191168955oJamfTh7b",
# "seqNum":-1,
# "orderType":"StopLimit",
# "execInst":"NULL_VAL",
# "side":"Buy",
# "symbol":"TRX-PERP",
# "price":"0.083",
# "orderQty":"1",
# "stopPrice":"0.082",
# "stopBy":"ref-px",
# "status":"Ack",
# "lastExecTime":1654290969965,
# "lastQty":"0",
# "lastPx":"0",
# "avgFilledPx":"0",
# "cumFilledQty":"0",
# "fee":"0",
# "cumFee":"0",
# "feeAsset":"",
# "errorCode":"",
# "posStopLossPrice":"0",
# "posStopLossTrigger":"market",
# "posTakeProfitPrice":"0",
# "posTakeProfitTrigger":"market",
# "liquidityInd":"n"
# }
# }
# }
#
data = self.safe_dict(response, 'data', {})
order = self.safe_dict_2(data, 'order', 'info', {})
return self.parse_order(order, market)
def create_orders(self, orders: List[OrderRequest], params={}):
"""
create a list of trade orders
https://ascendex.github.io/ascendex-pro-api/#place-batch-orders
https://ascendex.github.io/ascendex-futures-pro-api-v2/#place-batch-orders
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.timeInForce]: "GTC", "IOC", "FOK", or "PO"
:param bool [params.postOnly]: True or False
:param float [params.triggerPrice]: the price at which a trigger order is triggered at
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
self.load_markets()
self.load_accounts()
ordersRequests = []
symbol = None
marginMode = None
for i in range(0, len(orders)):
rawOrder = orders[i]
marketId = self.safe_string(rawOrder, 'symbol')
if symbol is None:
symbol = marketId
else:
if symbol != marketId:
raise BadRequest(self.id + ' createOrders() requires all orders to have the same symbol')
type = self.safe_string(rawOrder, 'type')
side = self.safe_string(rawOrder, 'side')
amount = self.safe_number(rawOrder, 'amount')
price = self.safe_number(rawOrder, 'price')
orderParams = self.safe_dict(rawOrder, 'params', {})
marginResult = self.handle_margin_mode_and_params('createOrders', orderParams)
currentMarginMode = marginResult[0]
if currentMarginMode is not None:
if marginMode is None:
marginMode = currentMarginMode
else:
if marginMode != currentMarginMode:
raise BadRequest(self.id + ' createOrders() requires all orders to have the same margin mode(isolated or cross)')
orderRequest = self.create_order_request(marketId, type, side, amount, price, orderParams)
ordersRequests.append(orderRequest)
market = self.market(symbol)
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
accountCategory = self.safe_string(accountsByType, market['type'], 'cash')
if marginMode is not None:
accountCategory = 'margin'
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {}
response = None
if market['swap']:
raise NotSupported(self.id + ' createOrders() is not currently supported for swap markets on ascendex')
# request['account-group'] = accountGroup
# request['category'] = accountCategory
# request['orders'] = ordersRequests
# response = self.v2PrivateAccountGroupPostFuturesOrderBatch(request)
else:
request['account-group'] = accountGroup
request['account-category'] = accountCategory
request['orders'] = ordersRequests
response = self.v1PrivateAccountCategoryPostOrderBatch(request)
#
# spot
#
# {
# "code": 0,
# "data": {
# "accountId": "cshdAKBO43TKIh2kJtq7FVVb42KIePyS",
# "ac": "CASH",
# "action": "batch-place-order",
# "status": "Ack",
# "info": [
# {
# "symbol": "BTC/USDT",
# "orderType": "Limit",
# "timestamp": 1699326589344,
# "id": "",
# "orderId": "a18ba7c1f6efU0711043490p3HvjjN5x"
# }
# ]
# }
# }
#
data = self.safe_dict(response, 'data', {})
info = self.safe_list(data, 'info', [])
return self.parse_orders(info, market)
def fetch_order(self, id: str, symbol: Str = None, params={}):
"""
fetches information on an order made by the user
https://ascendex.github.io/ascendex-pro-api/#query-order
https://ascendex.github.io/ascendex-futures-pro-api-v2/#query-order-by-id
:param str id: the order id
:param str symbol: unified symbol of the market the order was made in
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
self.load_markets()
self.load_accounts()
market = None
if symbol is not None:
market = self.market(symbol)
type, query = self.handle_market_type_and_params('fetchOrder', market, params)
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
accountCategory = self.safe_string(accountsByType, type, 'cash')
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
'account-category': accountCategory,
'orderId': id,
}
response = None
if (type == 'spot') or (type == 'margin'):
response = self.v1PrivateAccountCategoryGetOrderStatus(self.extend(request, query))
elif type == 'swap':
request['account-category'] = accountCategory
response = self.v2PrivateAccountGroupGetFuturesOrderStatus(self.extend(request, query))
else:
raise NotSupported(self.id + ' fetchOrder() is not currently supported for ' + type + ' markets')
#
# AccountCategoryGetOrderStatus
#
# {
# "code": 0,
# "accountCategory": "CASH",
# "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
# "data": [
# {
# "symbol": "BTC/USDT",
# "price": "8131.22",
# "orderQty": "0.00082",
# "orderType": "Market",
# "avgPx": "7392.02",
# "cumFee": "0.005152238",
# "cumFilledQty": "0.00082",
# "errorCode": "",
# "feeAsset": "USDT",
# "lastExecTime": 1575953151764,
# "orderId": "a16eee20b6750866943712zWEDdAjt3",
# "seqNum": 2623469,
# "side": "Buy",
# "status": "Filled",
# "stopPrice": "",
# "execInst": "NULL_VAL"
# }
# ]
# }
#
# AccountGroupGetFuturesOrderStatus
#
# {
# "code": 0,
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "ac": "FUTURES",
# "data": {
# "ac": "FUTURES",
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "time": 1640247020217,
# "orderId": "r17de65747aeU0711043490bbtcp0cmt",
# "seqNum": 28796162908,
# "orderType": "Limit",
# "execInst": "NULL_VAL",
# "side": "Buy",
# "symbol": "BTC-PERP",
# "price": "30000",
# "orderQty": "0.0021",
# "stopPrice": "0",
# "stopBy": "market",
# "status": "New",
# "lastExecTime": 1640247020232,
# "lastQty": "0",
# "lastPx": "0",
# "avgFilledPx": "0",
# "cumFilledQty": "0",
# "fee": "0",
# "cumFee": "0",
# "feeAsset": "USDT",
# "errorCode": "",
# "posStopLossPrice": "0",
# "posStopLossTrigger": "market",
# "posTakeProfitPrice": "0",
# "posTakeProfitTrigger": "market",
# "liquidityInd": "n"
# }
# }
#
data = self.safe_dict(response, 'data', {})
return self.parse_order(data, market)
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all unfilled currently open orders
https://ascendex.github.io/ascendex-pro-api/#list-open-orders
https://ascendex.github.io/ascendex-futures-pro-api-v2/#list-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
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
self.load_markets()
self.load_accounts()
market = None
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
type, query = self.handle_market_type_and_params('fetchOpenOrders', market, params)
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
accountCategory = self.safe_string(accountsByType, type, 'cash')
request: dict = {
'account-group': accountGroup,
'account-category': accountCategory,
}
response = None
if (type == 'spot') or (type == 'margin'):
response = self.v1PrivateAccountCategoryGetOrderOpen(self.extend(request, query))
elif type == 'swap':
request['account-category'] = accountCategory
response = self.v2PrivateAccountGroupGetFuturesOrderOpen(self.extend(request, query))
else:
raise NotSupported(self.id + ' fetchOpenOrders() is not currently supported for ' + type + ' markets')
#
# AccountCategoryGetOrderOpen
#
# {
# "ac": "CASH",
# "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
# "code": 0,
# "data": [
# {
# "avgPx": "0", # Average filled price of the order
# "cumFee": "0", # cumulative fee paid for self order
# "cumFilledQty": "0", # cumulative filled quantity
# "errorCode": "", # error code; could be empty
# "feeAsset": "USDT", # fee asset
# "lastExecTime": 1576019723550, # The last execution time of the order
# "orderId": "s16ef21882ea0866943712034f36d83", # server provided orderId
# "orderQty": "0.0083", # order quantity
# "orderType": "Limit", # order type
# "price": "7105", # order price
# "seqNum": 8193258, # sequence number
# "side": "Buy", # order side
# "status": "New", # order status on matching engine
# "stopPrice": "", # only available for stop market and stop limit orders; otherwise empty
# "symbol": "BTC/USDT",
# "execInst": "NULL_VAL" # execution instruction
# },
# ]
# }
#
# AccountGroupGetFuturesOrderOpen
#
# {
# "code": 0,
# "data": [
# {
# "ac": "FUTURES",
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "time": 1640247020217,
# "orderId": "r17de65747aeU0711043490bbtcp0cmt",
# "seqNum": 28796162908,
# "orderType": "Limit",
# "execInst": "NULL_VAL",
# "side": "Buy",
# "symbol": "BTC-PERP",
# "price": "30000",
# "orderQty": "0.0021",
# "stopPrice": "0",
# "stopBy": "market",
# "status": "New",
# "lastExecTime": 1640247020232,
# "lastQty": "0",
# "lastPx": "0",
# "avgFilledPx": "0",
# "cumFilledQty": "0",
# "fee": "0",
# "cumFee": "0",
# "feeAsset": "USDT",
# "errorCode": "",
# "posStopLossPrice": "0",
# "posStopLossTrigger": "market",
# "posTakeProfitPrice": "0",
# "posTakeProfitTrigger": "market",
# "liquidityInd": "n"
# }
# ]
# }
#
data = self.safe_list(response, 'data', [])
if accountCategory == 'futures':
return self.parse_orders(data, market, since, limit)
# a workaround for https://github.com/ccxt/ccxt/issues/7187
orders = []
for i in range(0, len(data)):
order = self.parse_order(data[i], market)
orders.append(order)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit)
def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetches information on multiple closed orders made by the user
https://ascendex.github.io/ascendex-pro-api/#list-history-orders-v2
https://ascendex.github.io/ascendex-futures-pro-api-v2/#list-current-history-orders
:param str symbol: unified market symbol of the market orders were made in
:param int [since]: the earliest time in ms to fetch orders for
:param int [limit]: the maximum number of order structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: the latest time in ms to fetch orders for
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
self.load_markets()
self.load_accounts()
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
# 'category': accountCategory,
# 'symbol': market['id'],
# 'orderType': 'market', # optional, string
# 'side': 'buy', # or 'sell', optional, case insensitive.
# 'status': 'Filled', # "Filled", "Canceled", or "Rejected"
# 'startTime': exchange.milliseconds(),
# 'endTime': exchange.milliseconds(),
# 'page': 1,
# 'pageSize': 100,
}
market = None
if symbol is not None:
market = self.market(symbol)
request['symbol'] = market['id']
type, query = self.handle_market_type_and_params('fetchClosedOrders', market, params)
options = self.safe_dict(self.options, 'fetchClosedOrders', {})
defaultMethod = self.safe_string(options, 'method', 'v2PrivateDataGetOrderHist')
method = self.get_supported_mapping(type, {
'spot': defaultMethod,
'margin': defaultMethod,
'swap': 'v2PrivateAccountGroupGetFuturesOrderHistCurrent',
})
if since is not None:
request['startTime'] = since
until = self.safe_string(params, 'until')
if until is not None:
request['endTime'] = until
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
accountCategory = self.safe_string(accountsByType, type, 'cash') # margin, futures
response = None
if method == 'v1PrivateAccountCategoryGetOrderHistCurrent':
request['account-group'] = accountGroup
request['account-category'] = accountCategory
if limit is not None:
request['limit'] = limit
response = self.v1PrivateAccountCategoryGetOrderHistCurrent(self.extend(request, query))
elif method == 'v2PrivateDataGetOrderHist':
request['account'] = accountCategory
if limit is not None:
request['limit'] = limit
response = self.v2PrivateDataGetOrderHist(self.extend(request, query))
elif method == 'v2PrivateAccountGroupGetFuturesOrderHistCurrent':
request['account-group'] = accountGroup
request['account-category'] = accountCategory
if limit is not None:
request['pageSize'] = limit
response = self.v2PrivateAccountGroupGetFuturesOrderHistCurrent(self.extend(request, query))
else:
raise NotSupported(self.id + ' fetchClosedOrders() is not currently supported for ' + type + ' markets')
#
# accountCategoryGetOrderHistCurrent
#
# {
# "code":0,
# "accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda",
# "ac":"CASH",
# "data":[
# {
# "seqNum":15561826728,
# "orderId":"a17294d305c0U6491137460bethu7kw9",
# "symbol":"ETH/USDT",
# "orderType":"Limit",
# "lastExecTime":1591635618200,
# "price":"200",
# "orderQty":"0.1",
# "side":"Buy",
# "status":"Canceled",
# "avgPx":"0",
# "cumFilledQty":"0",
# "stopPrice":"",
# "errorCode":"",
# "cumFee":"0",
# "feeAsset":"USDT",
# "execInst":"NULL_VAL"
# }
# ]
# }
#
# {
# "code": 0,
# "data": [
# {
# "orderId" : "a173ad938fc3U22666567717788c3b66", # orderId
# "seqNum" : 18777366360, # sequence number
# "accountId" : "cshwSjbpPjSwHmxPdz2CPQVU9mnbzPpt", # accountId
# "symbol" : "BTC/USDT", # symbol
# "orderType" : "Limit", # order type(Limit/Market/StopMarket/StopLimit)
# "side" : "Sell", # order side(Buy/Sell)
# "price" : "11346.77", # order price
# "stopPrice" : "0", # stop price(0 by default)
# "orderQty" : "0.01", # order quantity(in base asset)
# "status" : "Canceled", # order status(Filled/Canceled/Rejected)
# "createTime" : 1596344995793, # order creation time
# "lastExecTime": 1596344996053, # last execution time
# "avgFillPrice": "11346.77", # average filled price
# "fillQty" : "0.01", # filled quantity(in base asset)
# "fee" : "-0.004992579", # cummulative fee. if negative, self value is the commission charged; if possitive, self value is the rebate received.
# "feeAsset" : "USDT" # fee asset
# }
# ]
# }
#
# accountGroupGetFuturesOrderHistCurrent
#
# {
# "code": 0,
# "data": [
# {
# "ac": "FUTURES",
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "time": 1640245777002,
# "orderId": "r17de6444fa6U0711043490bbtcpJ2lI",
# "seqNum": 28796124902,
# "orderType": "Limit",
# "execInst": "NULL_VAL",
# "side": "Buy",
# "symbol": "BTC-PERP",
# "price": "30000",
# "orderQty": "0.0021",
# "stopPrice": "0",
# "stopBy": "market",
# "status": "Canceled",
# "lastExecTime": 1640246574886,
# "lastQty": "0",
# "lastPx": "0",
# "avgFilledPx": "0",
# "cumFilledQty": "0",
# "fee": "0",
# "cumFee": "0",
# "feeAsset": "USDT",
# "errorCode": "",
# "posStopLossPrice": "0",
# "posStopLossTrigger": "market",
# "posTakeProfitPrice": "0",
# "posTakeProfitTrigger": "market",
# "liquidityInd": "n"
# }
# ]
# }
#
data = self.safe_list(response, 'data', [])
if not isinstance(data, list):
data = self.safe_list(data, 'data', [])
return self.parse_orders(data, market, since, limit)
def cancel_order(self, id: str, symbol: Str = None, params={}):
"""
cancels an open order
https://ascendex.github.io/ascendex-pro-api/#cancel-order
https://ascendex.github.io/ascendex-futures-pro-api-v2/#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
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
self.load_markets()
self.load_accounts()
market = self.market(symbol)
type, query = self.handle_market_type_and_params('cancelOrder', market, params)
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
accountCategory = self.safe_string(accountsByType, type, 'cash')
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
'account-category': accountCategory,
'symbol': market['id'],
'time': self.milliseconds(),
'id': 'foobar',
}
clientOrderId = self.safe_string_2(params, 'clientOrderId', 'id')
if clientOrderId is None:
request['orderId'] = id
else:
request['id'] = clientOrderId
params = self.omit(params, ['clientOrderId', 'id'])
response = None
if (type == 'spot') or (type == 'margin'):
response = self.v1PrivateAccountCategoryDeleteOrder(self.extend(request, query))
elif type == 'swap':
request['account-category'] = accountCategory
response = self.v2PrivateAccountGroupDeleteFuturesOrder(self.extend(request, query))
else:
raise NotSupported(self.id + ' cancelOrder() is not currently supported for ' + type + ' markets')
#
# AccountCategoryDeleteOrder
#
# {
# "code": 0,
# "data": {
# "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
# "ac": "CASH",
# "action": "cancel-order",
# "status": "Ack",
# "info": {
# "id": "wv8QGquoeamhssvQBeHOHGQCGlcBjj23",
# "orderId": "16e6198afb4s8bXHbAwwoqDo2ebc19dc",
# "orderType": "", # could be empty
# "symbol": "ETH/USDT",
# "timestamp": 1573594877822
# }
# }
# }
#
# AccountGroupDeleteFuturesOrder
#
# {
# "code": 0,
# "data": {
# "meta": {
# "id": "foobar",
# "action": "cancel-order",
# "respInst": "ACK"
# },
# "order": {
# "ac": "FUTURES",
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "time": 1640244480476,
# "orderId": "r17de63086f4U0711043490bbtcpPUF4",
# "seqNum": 28795959269,
# "orderType": "Limit",
# "execInst": "NULL_VAL",
# "side": "Buy",
# "symbol": "BTC-PERP",
# "price": "30000",
# "orderQty": "0.0021",
# "stopPrice": "0",
# "stopBy": "market",
# "status": "New",
# "lastExecTime": 1640244480491,
# "lastQty": "0",
# "lastPx": "0",
# "avgFilledPx": "0",
# "cumFilledQty": "0",
# "fee": "0",
# "cumFee": "0",
# "feeAsset": "BTCPC",
# "errorCode": "",
# "posStopLossPrice": "0",
# "posStopLossTrigger": "market",
# "posTakeProfitPrice": "0",
# "posTakeProfitTrigger": "market",
# "liquidityInd": "n"
# }
# }
# }
#
data = self.safe_dict(response, 'data', {})
order = self.safe_dict_2(data, 'order', 'info', {})
return self.parse_order(order, market)
def cancel_all_orders(self, symbol: Str = None, params={}):
"""
cancel all open orders
https://ascendex.github.io/ascendex-pro-api/#cancel-all-orders
https://ascendex.github.io/ascendex-futures-pro-api-v2/#cancel-all-open-orders
:param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list with a single `order structure <https://docs.ccxt.com/#/?id=order-structure>` with the response assigned to the info property
"""
self.load_markets()
self.load_accounts()
market = None
if symbol is not None:
market = self.market(symbol)
type, query = self.handle_market_type_and_params('cancelAllOrders', market, params)
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
accountCategory = self.safe_string(accountsByType, type, 'cash')
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
'account-category': accountCategory,
'time': self.milliseconds(),
}
if symbol is not None:
request['symbol'] = market['id']
response = None
if (type == 'spot') or (type == 'margin'):
response = self.v1PrivateAccountCategoryDeleteOrderAll(self.extend(request, query))
elif type == 'swap':
request['account-category'] = accountCategory
response = self.v2PrivateAccountGroupDeleteFuturesOrderAll(self.extend(request, query))
else:
raise NotSupported(self.id + ' cancelAllOrders() is not currently supported for ' + type + ' markets')
#
# AccountCategoryDeleteOrderAll
#
# {
# "code": 0,
# "data": {
# "ac": "CASH",
# "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
# "action": "cancel-all",
# "info": {
# "id": "2bmYvi7lyTrneMzpcJcf2D7Pe9V1P9wy",
# "orderId": "",
# "orderType": "NULL_VAL",
# "symbol": "",
# "timestamp": 1574118495462
# },
# "status": "Ack"
# }
# }
#
# AccountGroupDeleteFuturesOrderAll
#
# {
# "code": 0,
# "data": {
# "ac": "FUTURES",
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "action": "cancel-all",
# "info": {
# "symbol":"BTC-PERP"
# }
# }
# }
#
return [self.safe_order({
'info': response,
})]
def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
#
# {
# "address": "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722",
# "destTag": "",
# "tagType": "",
# "tagId": "",
# "chainName": "ERC20",
# "numConfirmations": 20,
# "withdrawalFee": 1,
# "nativeScale": 4,
# "tips": []
# }
#
address = self.safe_string(depositAddress, 'address')
tagId = self.safe_string(depositAddress, 'tagId')
tag = self.safe_string(depositAddress, tagId)
self.check_address(address)
code = None if (currency is None) else currency['code']
chainName = self.safe_string(depositAddress, 'blockchain')
network = self.network_id_to_code(chainName, code)
return {
'info': depositAddress,
'currency': code,
'network': network,
'address': address,
'tag': tag,
}
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
"""
fetch the deposit address for a currency associated with self account
https://ascendex.github.io/ascendex-pro-api/#query-deposit-addresses
:param str code: unified currency code
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.network]: unified network code for deposit chain
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
"""
self.load_markets()
currency = self.currency(code)
networkCode = self.safe_string_2(params, 'network', 'chainName')
networkId = self.network_code_to_id(networkCode)
params = self.omit(params, ['chainName'])
request: dict = {
'asset': currency['id'],
'blockchain': networkId,
}
response = self.v1PrivateGetWalletDepositAddress(self.extend(request, params))
#
# {
# "code":0,
# "data":{
# "asset":"USDT",
# "assetName":"Tether",
# "address":[
# {
# "address":"1N22odLHXnLPCjC8kwBJPTayarr9RtPod6",
# "destTag":"",
# "tagType":"",
# "tagId":"",
# "chainName":"Omni",
# "numConfirmations":3,
# "withdrawalFee":4.7,
# "nativeScale":4,
# "tips":[]
# },
# {
# "address":"0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722",
# "destTag":"",
# "tagType":"",
# "tagId":"",
# "chainName":"ERC20",
# "numConfirmations":20,
# "withdrawalFee":1.0,
# "nativeScale":4,
# "tips":[]
# }
# ]
# }
# }
#
data = self.safe_dict(response, 'data', {})
addresses = self.safe_list(data, 'address', [])
numAddresses = len(addresses)
address = None
if numAddresses > 1:
addressesByChainName = self.index_by(addresses, 'chainName')
if networkId is None:
chainNames = list(addressesByChainName.keys())
chains = ', '.join(chainNames)
raise ArgumentsRequired(self.id + ' fetchDepositAddress() returned more than one address, a chainName parameter is required, one of ' + chains)
address = self.safe_dict(addressesByChainName, networkId, {})
else:
# first address
address = self.safe_dict(addresses, 0, {})
result = self.parse_deposit_address(address, currency)
return self.extend(result, {
'info': response,
})
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
"""
fetch all deposits made to an account
:param str code: unified currency code
:param int [since]: the earliest time in ms to fetch deposits for
:param int [limit]: the maximum number of deposits structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
request: dict = {
'txType': 'deposit',
}
return self.fetch_transactions(code, since, limit, self.extend(request, params))
def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
"""
fetch all withdrawals made from an account
:param str code: unified currency code
:param int [since]: the earliest time in ms to fetch withdrawals for
:param int [limit]: the maximum number of withdrawals structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
request: dict = {
'txType': 'withdrawal',
}
return self.fetch_transactions(code, since, limit, self.extend(request, params))
def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
"""
fetch history of deposits and withdrawals
:param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
:param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
:param int [limit]: max number of deposit/withdrawals to return, default is None
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
self.load_markets()
request: dict = {
# 'asset': currency['id'],
# 'page': 1,
# 'pageSize': 20,
# 'startTs': self.milliseconds(),
# 'endTs': self.milliseconds(),
# 'txType': undefned, # deposit, withdrawal
}
currency = None
if code is not None:
currency = self.currency(code)
request['asset'] = currency['id']
if since is not None:
request['startTs'] = since
if limit is not None:
request['pageSize'] = limit
response = self.v1PrivateGetWalletTransactions(self.extend(request, params))
#
# {
# "code": 0,
# "data": {
# "data": [
# {
# "requestId": "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB",
# "time": 1591606166000,
# "asset": "USDT",
# "transactionType": "deposit",
# "amount": "25",
# "commission": "0",
# "networkTransactionId": "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce",
# "status": "pending",
# "numConfirmed": 8,
# "numConfirmations": 20,
# "destAddress": {address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722"}
# }
# ],
# "page": 1,
# "pageSize": 20,
# "hasNext": False
# }
# }
#
data = self.safe_dict(response, 'data', {})
transactions = self.safe_list(data, 'data', [])
return self.parse_transactions(transactions, currency, since, limit)
def parse_transaction_status(self, status: Str):
statuses: dict = {
'reviewing': 'pending',
'pending': 'pending',
'confirmed': 'ok',
'rejected': 'rejected',
}
return self.safe_string(statuses, status, status)
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
#
# {
# "requestId": "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB",
# "time": 1591606166000,
# "asset": "USDT",
# "transactionType": "deposit",
# "amount": "25",
# "commission": "0",
# "networkTransactionId": "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce",
# "status": "pending",
# "numConfirmed": 8,
# "numConfirmations": 20,
# "destAddress": {
# "address": "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722",
# "destTag": "..." # for currencies that have it
# }
# }
#
destAddress = self.safe_dict(transaction, 'destAddress', {})
address = self.safe_string(destAddress, 'address')
tag = self.safe_string(destAddress, 'destTag')
timestamp = self.safe_integer(transaction, 'time')
currencyId = self.safe_string(transaction, 'asset')
amountString = self.safe_string(transaction, 'amount')
feeCostString = self.safe_string(transaction, 'commission')
amountString = Precise.string_sub(amountString, feeCostString)
code = self.safe_currency_code(currencyId, currency)
return {
'info': transaction,
'id': self.safe_string(transaction, 'requestId'),
'txid': self.safe_string(transaction, 'networkTransactionId'),
'type': self.safe_string(transaction, 'transactionType'),
'currency': code,
'network': None,
'amount': self.parse_number(amountString),
'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'address': address,
'addressFrom': None,
'addressTo': address,
'tag': tag,
'tagFrom': None,
'tagTo': tag,
'updated': None,
'comment': None,
'fee': {
'currency': code,
'cost': self.parse_number(feeCostString),
'rate': None,
},
'internal': False,
}
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
"""
fetch all open positions
:param str[]|None 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()
self.load_accounts()
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
}
response = self.v2PrivateAccountGroupGetFuturesPosition(self.extend(request, params))
#
# {
# "code": 0,
# "data": {
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "ac": "FUTURES",
# "collaterals": [
# {
# "asset": "USDT",
# "balance": "44.570287262",
# "referencePrice": "1",
# "discountFactor": "1"
# }
# ],
# "contracts": [
# {
# "symbol": "BTC-PERP",
# "side": "LONG",
# "position": "0.0001",
# "referenceCost": "-3.12277254",
# "unrealizedPnl": "-0.001700233",
# "realizedPnl": "0",
# "avgOpenPrice": "31209",
# "marginType": "isolated",
# "isolatedMargin": "1.654972977",
# "leverage": "2",
# "takeProfitPrice": "0",
# "takeProfitTrigger": "market",
# "stopLossPrice": "0",
# "stopLossTrigger": "market",
# "buyOpenOrderNotional": "0",
# "sellOpenOrderNotional": "0",
# "markPrice": "31210.723063672",
# "indexPrice": "31223.148857925"
# },
# ]
# }
# }
#
data = self.safe_dict(response, 'data', {})
position = self.safe_list(data, 'contracts', [])
result = []
for i in range(0, len(position)):
result.append(self.parse_position(position[i]))
symbols = self.market_symbols(symbols)
return self.filter_by_array_positions(result, 'symbol', symbols, False)
def parse_position(self, position: dict, market: Market = None):
#
# {
# "symbol": "BTC-PERP",
# "side": "LONG",
# "position": "0.0001",
# "referenceCost": "-3.12277254",
# "unrealizedPnl": "-0.001700233",
# "realizedPnl": "0",
# "avgOpenPrice": "31209",
# "marginType": "isolated",
# "isolatedMargin": "1.654972977",
# "leverage": "2",
# "takeProfitPrice": "0",
# "takeProfitTrigger": "market",
# "stopLossPrice": "0",
# "stopLossTrigger": "market",
# "buyOpenOrderNotional": "0",
# "sellOpenOrderNotional": "0",
# "markPrice": "31210.723063672",
# "indexPrice": "31223.148857925"
# },
#
marketId = self.safe_string(position, 'symbol')
market = self.safe_market(marketId, market)
notional = self.safe_string(position, 'buyOpenOrderNotional')
if Precise.string_eq(notional, '0'):
notional = self.safe_string(position, 'sellOpenOrderNotional')
marginType = self.safe_string(position, 'marginType')
marginMode = 'cross' if (marginType == 'crossed') else 'isolated'
collateral = None
if marginMode == 'isolated':
collateral = self.safe_string(position, 'isolatedMargin')
return self.safe_position({
'info': position,
'id': None,
'symbol': market['symbol'],
'notional': self.parse_number(notional),
'marginMode': marginMode,
'liquidationPrice': None,
'entryPrice': self.safe_number(position, 'avgOpenPrice'),
'unrealizedPnl': self.safe_number(position, 'unrealizedPnl'),
'percentage': None,
'contracts': self.safe_number(position, 'position'),
'contractSize': self.safe_number(market, 'contractSize'),
'markPrice': self.safe_number(position, 'markPrice'),
'lastPrice': None,
'side': self.safe_string_lower(position, 'side'),
'hedged': None,
'timestamp': None,
'datetime': None,
'lastUpdateTimestamp': None,
'maintenanceMargin': None,
'maintenanceMarginPercentage': None,
'collateral': collateral,
'initialMargin': None,
'initialMarginPercentage': None,
'leverage': self.safe_integer(position, 'leverage'),
'marginRatio': None,
'stopLossPrice': self.safe_number(position, 'stopLossPrice'),
'takeProfitPrice': self.safe_number(position, 'takeProfitPrice'),
})
def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
#
# {
# "time": 1640061364830,
# "symbol": "EOS-PERP",
# "markPrice": "3.353854865",
# "indexPrice": "3.3542",
# "openInterest": "14242",
# "fundingRate": "-0.000073026",
# "nextFundingTime": 1640073600000
# }
#
marketId = self.safe_string(contract, 'symbol')
symbol = self.safe_symbol(marketId, market)
currentTime = self.safe_integer(contract, 'time')
nextFundingRate = self.safe_number(contract, 'fundingRate')
nextFundingRateTimestamp = self.safe_integer(contract, 'nextFundingTime')
return {
'info': contract,
'symbol': symbol,
'markPrice': self.safe_number(contract, 'markPrice'),
'indexPrice': self.safe_number(contract, 'indexPrice'),
'interestRate': self.parse_number('0'),
'estimatedSettlePrice': None,
'timestamp': currentTime,
'datetime': self.iso8601(currentTime),
'previousFundingRate': None,
'nextFundingRate': None,
'previousFundingTimestamp': None,
'nextFundingTimestamp': None,
'previousFundingDatetime': None,
'nextFundingDatetime': None,
'fundingRate': nextFundingRate,
'fundingTimestamp': nextFundingRateTimestamp,
'fundingDatetime': self.iso8601(nextFundingRateTimestamp),
'interval': None,
}
def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
"""
fetch the funding rate for multiple markets
:param str[]|None symbols: list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `funding rates structures <https://docs.ccxt.com/#/?id=funding-rates-structure>`, indexe by market symbols
"""
self.load_markets()
symbols = self.market_symbols(symbols)
response = self.v2PublicGetFuturesPricingData(params)
#
# {
# "code": 0,
# "data": {
# "contracts": [
# {
# "time": 1640061364830,
# "symbol": "EOS-PERP",
# "markPrice": "3.353854865",
# "indexPrice": "3.3542",
# "openInterest": "14242",
# "fundingRate": "-0.000073026",
# "nextFundingTime": 1640073600000
# },
# ],
# "collaterals": [
# {
# "asset": "USDTR",
# "referencePrice": "1"
# },
# ]
# }
# }
#
data = self.safe_dict(response, 'data', {})
contracts = self.safe_list(data, 'contracts', [])
return self.parse_funding_rates(contracts, symbols)
def modify_margin_helper(self, symbol: str, amount, type, params={}) -> MarginModification:
self.load_markets()
self.load_accounts()
market = self.market(symbol)
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
amount = self.amount_to_precision(symbol, amount)
request: dict = {
'account-group': accountGroup,
'symbol': market['id'],
'amount': amount, # positive value for adding margin, negative for reducing
}
response = self.v2PrivateAccountGroupPostFuturesIsolatedPositionMargin(self.extend(request, params))
#
# Can only change margin for perpetual futures isolated margin positions
#
# {
# "code": 0
# }
#
if type == 'reduce':
amount = Precise.string_abs(amount)
return self.extend(self.parse_margin_modification(response, market), {
'amount': self.parse_number(amount),
'type': type,
})
def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
#
# addMargin/reduceMargin
#
# {
# "code": 0
# }
#
errorCode = self.safe_string(data, 'code')
status = 'ok' if (errorCode == '0') else 'failed'
return {
'info': data,
'symbol': market['symbol'],
'type': None,
'marginMode': 'isolated',
'amount': None,
'total': None,
'code': market['quote'],
'status': status,
'timestamp': None,
'datetime': None,
}
def reduce_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
"""
remove margin from a position
:param str symbol: unified market symbol
:param float amount: the amount of margin to remove
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=reduce-margin-structure>`
"""
return self.modify_margin_helper(symbol, -amount, 'reduce', params)
def add_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
"""
add margin
:param str symbol: unified market symbol
:param float amount: amount of margin to add
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=add-margin-structure>`
"""
return self.modify_margin_helper(symbol, amount, 'add', params)
def set_leverage(self, leverage: int, symbol: Str = None, params={}):
"""
set the level of leverage for a market
https://ascendex.github.io/ascendex-futures-pro-api-v2/#change-contract-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
:returns dict: response from the exchange
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
if (leverage < 1) or (leverage > 100):
raise BadRequest(self.id + ' leverage should be between 1 and 100')
self.load_markets()
self.load_accounts()
market = self.market(symbol)
if not market['swap']:
raise BadSymbol(self.id + ' setLeverage() supports swap contracts only')
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
'symbol': market['id'],
'leverage': leverage,
}
return self.v2PrivateAccountGroupPostFuturesLeverage(self.extend(request, params))
def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
"""
set margin mode to 'cross' or 'isolated'
https://ascendex.github.io/ascendex-futures-pro-api-v2/#change-margin-type
:param str marginMode: 'cross' or 'isolated'
: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 + ' setMarginMode() requires a symbol argument')
marginMode = marginMode.lower()
if marginMode == 'cross':
marginMode = 'crossed'
if marginMode != 'isolated' and marginMode != 'crossed':
raise BadRequest(self.id + ' setMarginMode() marginMode argument should be isolated or cross')
self.load_markets()
self.load_accounts()
market = self.market(symbol)
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
'symbol': market['id'],
'marginType': marginMode,
}
if not market['swap']:
raise BadSymbol(self.id + ' setMarginMode() supports swap contracts only')
return self.v2PrivateAccountGroupPostFuturesMarginType(self.extend(request, params))
def fetch_leverage_tiers(self, symbols: Strings = None, params={}) -> LeverageTiers:
"""
retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes
:param str[]|None symbols: list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a dictionary of `leverage tiers structures <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`, indexed by market symbols
"""
self.load_markets()
response = self.v2PublicGetFuturesContract(params)
#
# {
# "code":0,
# "data":[
# {
# "symbol":"BTC-PERP",
# "status":"Normal",
# "displayName":"BTCUSDT",
# "settlementAsset":"USDT",
# "underlying":"BTC/USDT",
# "tradingStartTime":1579701600000,
# "priceFilter":{"minPrice":"1","maxPrice":"1000000","tickSize":"1"},
# "lotSizeFilter":{"minQty":"0.0001","maxQty":"1000000000","lotSize":"0.0001"},
# "commissionType":"Quote",
# "commissionReserveRate":"0.001",
# "marketOrderPriceMarkup":"0.03",
# "marginRequirements":[
# {"positionNotionalLowerBound":"0","positionNotionalUpperBound":"50000","initialMarginRate":"0.01","maintenanceMarginRate":"0.006"},
# {"positionNotionalLowerBound":"50000","positionNotionalUpperBound":"200000","initialMarginRate":"0.02","maintenanceMarginRate":"0.012"},
# {"positionNotionalLowerBound":"200000","positionNotionalUpperBound":"2000000","initialMarginRate":"0.04","maintenanceMarginRate":"0.024"},
# {"positionNotionalLowerBound":"2000000","positionNotionalUpperBound":"20000000","initialMarginRate":"0.1","maintenanceMarginRate":"0.06"},
# {"positionNotionalLowerBound":"20000000","positionNotionalUpperBound":"40000000","initialMarginRate":"0.2","maintenanceMarginRate":"0.12"},
# {"positionNotionalLowerBound":"40000000","positionNotionalUpperBound":"1000000000","initialMarginRate":"0.333333","maintenanceMarginRate":"0.2"}
# ]
# }
# ]
# }
#
data = self.safe_list(response, 'data', [])
symbols = self.market_symbols(symbols)
return self.parse_leverage_tiers(data, symbols, 'symbol')
def parse_market_leverage_tiers(self, info, market: Market = None) -> List[LeverageTier]:
"""
:param dict info: Exchange market response for 1 market
:param dict market: CCXT market
"""
#
# {
# "symbol":"BTC-PERP",
# "status":"Normal",
# "displayName":"BTCUSDT",
# "settlementAsset":"USDT",
# "underlying":"BTC/USDT",
# "tradingStartTime":1579701600000,
# "priceFilter":{"minPrice":"1","maxPrice":"1000000","tickSize":"1"},
# "lotSizeFilter":{"minQty":"0.0001","maxQty":"1000000000","lotSize":"0.0001"},
# "commissionType":"Quote",
# "commissionReserveRate":"0.001",
# "marketOrderPriceMarkup":"0.03",
# "marginRequirements":[
# {"positionNotionalLowerBound":"0","positionNotionalUpperBound":"50000","initialMarginRate":"0.01","maintenanceMarginRate":"0.006"},
# {"positionNotionalLowerBound":"50000","positionNotionalUpperBound":"200000","initialMarginRate":"0.02","maintenanceMarginRate":"0.012"},
# {"positionNotionalLowerBound":"200000","positionNotionalUpperBound":"2000000","initialMarginRate":"0.04","maintenanceMarginRate":"0.024"},
# {"positionNotionalLowerBound":"2000000","positionNotionalUpperBound":"20000000","initialMarginRate":"0.1","maintenanceMarginRate":"0.06"},
# {"positionNotionalLowerBound":"20000000","positionNotionalUpperBound":"40000000","initialMarginRate":"0.2","maintenanceMarginRate":"0.12"},
# {"positionNotionalLowerBound":"40000000","positionNotionalUpperBound":"1000000000","initialMarginRate":"0.333333","maintenanceMarginRate":"0.2"}
# ]
# }
#
marginRequirements = self.safe_list(info, 'marginRequirements', [])
marketId = self.safe_string(info, 'symbol')
market = self.safe_market(marketId, market)
tiers = []
for i in range(0, len(marginRequirements)):
tier = marginRequirements[i]
initialMarginRate = self.safe_string(tier, 'initialMarginRate')
tiers.append({
'tier': self.sum(i, 1),
'symbol': self.safe_symbol(marketId, market, None, 'contract'),
'currency': market['quote'],
'minNotional': self.safe_number(tier, 'positionNotionalLowerBound'),
'maxNotional': self.safe_number(tier, 'positionNotionalUpperBound'),
'maintenanceMarginRate': self.safe_number(tier, 'maintenanceMarginRate'),
'maxLeverage': self.parse_number(Precise.string_div('1', initialMarginRate)),
'info': tier,
})
return tiers
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
#
# {
# "assetCode": "USDT",
# "assetName": "Tether",
# "precisionScale": 9,
# "nativeScale": 4,
# "blockChain": [
# {
# "chainName": "Omni",
# "withdrawFee": "30.0",
# "allowDeposit": True,
# "allowWithdraw": True,
# "minDepositAmt": "0.0",
# "minWithdrawal": "50.0",
# "numConfirmations": 3
# },
# ]
# }
#
blockChains = self.safe_list(fee, 'blockChain', [])
blockChainsLength = len(blockChains)
result: dict = {
'info': fee,
'withdraw': {
'fee': None,
'percentage': None,
},
'deposit': {
'fee': None,
'percentage': None,
},
'networks': {},
}
for i in range(0, blockChainsLength):
blockChain = blockChains[i]
networkId = self.safe_string(blockChain, 'chainName')
currencyCode = self.safe_string(currency, 'code')
networkCode = self.network_id_to_code(networkId, currencyCode)
result['networks'][networkCode] = {
'deposit': {'fee': None, 'percentage': None},
'withdraw': {'fee': self.safe_number(blockChain, 'withdrawFee'), 'percentage': False},
}
if blockChainsLength == 1:
result['withdraw']['fee'] = self.safe_number(blockChain, 'withdrawFee')
result['withdraw']['percentage'] = False
return result
def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
"""
fetch deposit and withdraw fees
https://ascendex.github.io/ascendex-pro-api/#list-all-assets
:param str[]|None codes: list of unified currency codes
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a list of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
"""
self.load_markets()
response = self.v2PublicGetAssets(params)
data = self.safe_list(response, 'data')
return self.parse_deposit_withdraw_fees(data, codes, 'assetCode')
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
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
"""
self.load_markets()
self.load_accounts()
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
currency = self.currency(code)
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
toId = self.safe_string(accountsByType, toAccount, toAccount)
if fromId != 'cash' and toId != 'cash':
raise ExchangeError(self.id + ' transfer() only supports direct balance transfer between spot and swap, spot and margin')
request: dict = {
'account-group': accountGroup,
'amount': self.currency_to_precision(code, amount),
'asset': currency['id'],
'fromAccount': fromId,
'toAccount': toId,
}
response = self.v1PrivateAccountGroupPostTransfer(self.extend(request, params))
#
# {"code": "0"}
#
transferOptions = self.safe_dict(self.options, 'transfer', {})
fillResponseFromRequest = self.safe_bool(transferOptions, 'fillResponseFromRequest', True)
transfer = self.parse_transfer(response, currency)
if fillResponseFromRequest:
transfer['fromAccount'] = fromAccount
transfer['toAccount'] = toAccount
transfer['amount'] = amount
transfer['currency'] = code
return transfer
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
#
# {"code": "0"}
#
status = self.safe_string(transfer, 'code')
currencyCode = self.safe_currency_code(None, currency)
return {
'info': transfer,
'id': None,
'timestamp': None,
'datetime': None,
'currency': currencyCode,
'amount': None,
'fromAccount': None,
'toAccount': None,
'status': self.parse_transfer_status(status),
}
def parse_transfer_status(self, status: Str) -> Str:
if status == '0':
return 'ok'
return 'failed'
def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch the history of funding payments paid and received on self account
https://ascendex.github.io/ascendex-futures-pro-api-v2/#funding-payment-history
:param str [symbol]: unified market symbol
:param int [since]: the earliest time in ms to fetch funding history for
:param int [limit]: the maximum number of funding history structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available 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()
self.load_accounts()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchFundingHistory', 'paginate')
if paginate:
return self.fetch_paginated_call_incremental('fetchFundingHistory', symbol, since, limit, params, 'page', 25)
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
}
market = None
if symbol is not None:
market = self.market(symbol)
request['symbol'] = market['id']
if limit is not None:
request['pageSize'] = limit
response = self.v2PrivateAccountGroupGetFuturesFundingPayments(self.extend(request, params))
#
# {
# "code": 0,
# "data": {
# "data": [
# {
# "timestamp": 1640476800000,
# "symbol": "BTC-PERP",
# "paymentInUSDT": "-0.013991178",
# "fundingRate": "0.000173497"
# },
# ],
# "page": 1,
# "pageSize": 3,
# "hasNext": True
# }
# }
#
data = self.safe_dict(response, 'data', {})
rows = self.safe_list(data, 'data', [])
return self.parse_incomes(rows, market, since, limit)
def parse_income(self, income, market: Market = None):
#
# {
# "timestamp": 1640476800000,
# "symbol": "BTC-PERP",
# "paymentInUSDT": "-0.013991178",
# "fundingRate": "0.000173497"
# }
#
marketId = self.safe_string(income, 'symbol')
timestamp = self.safe_integer(income, 'timestamp')
return {
'info': income,
'symbol': self.safe_symbol(marketId, market, '-', 'swap'),
'code': 'USDT',
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'id': None,
'amount': self.safe_number(income, 'paymentInUSDT'),
}
def fetch_margin_modes(self, symbols: Strings = None, params={}) -> MarginModes:
"""
fetches the set margin mode of the user
https://ascendex.github.io/ascendex-futures-pro-api-v2/#position
:param str[] [symbols]: a list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a list of `margin mode structures <https://docs.ccxt.com/#/?id=margin-mode-structure>`
"""
self.load_markets()
self.load_accounts()
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
}
response = self.v2PrivateAccountGroupGetFuturesPosition(self.extend(request, params))
#
# {
# "code": 0,
# "data": {
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "ac": "FUTURES",
# "collaterals": [
# {
# "asset": "USDT",
# "balance": "44.570287262",
# "referencePrice": "1",
# "discountFactor": "1"
# }
# ],
# "contracts": [
# {
# "symbol": "BTC-PERP",
# "side": "LONG",
# "position": "0.0001",
# "referenceCost": "-3.12277254",
# "unrealizedPnl": "-0.001700233",
# "realizedPnl": "0",
# "avgOpenPrice": "31209",
# "marginType": "isolated",
# "isolatedMargin": "1.654972977",
# "leverage": "2",
# "takeProfitPrice": "0",
# "takeProfitTrigger": "market",
# "stopLossPrice": "0",
# "stopLossTrigger": "market",
# "buyOpenOrderNotional": "0",
# "sellOpenOrderNotional": "0",
# "markPrice": "31210.723063672",
# "indexPrice": "31223.148857925"
# },
# ]
# }
# }
#
data = self.safe_dict(response, 'data', {})
marginModes = self.safe_list(data, 'contracts', [])
return self.parse_margin_modes(marginModes, symbols, 'symbol')
def parse_margin_mode(self, marginMode: dict, market=None) -> MarginMode:
marketId = self.safe_string(marginMode, 'symbol')
marginType = self.safe_string(marginMode, 'marginType')
margin = 'cross' if (marginType == 'crossed') else 'isolated'
return {
'info': marginMode,
'symbol': self.safe_symbol(marketId, market),
'marginMode': margin,
}
def fetch_leverages(self, symbols: Strings = None, params={}) -> Leverages:
"""
fetch the set leverage for all contract markets
https://ascendex.github.io/ascendex-futures-pro-api-v2/#position
:param str[] [symbols]: a list of unified market symbols
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a list of `leverage structures <https://docs.ccxt.com/#/?id=leverage-structure>`
"""
self.load_markets()
self.load_accounts()
account = self.safe_dict(self.accounts, 0, {})
accountGroup = self.safe_string(account, 'id')
request: dict = {
'account-group': accountGroup,
}
response = self.v2PrivateAccountGroupGetFuturesPosition(self.extend(request, params))
#
# {
# "code": 0,
# "data": {
# "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
# "ac": "FUTURES",
# "collaterals": [
# {
# "asset": "USDT",
# "balance": "44.570287262",
# "referencePrice": "1",
# "discountFactor": "1"
# }
# ],
# "contracts": [
# {
# "symbol": "BTC-PERP",
# "side": "LONG",
# "position": "0.0001",
# "referenceCost": "-3.12277254",
# "unrealizedPnl": "-0.001700233",
# "realizedPnl": "0",
# "avgOpenPrice": "31209",
# "marginType": "isolated",
# "isolatedMargin": "1.654972977",
# "leverage": "2",
# "takeProfitPrice": "0",
# "takeProfitTrigger": "market",
# "stopLossPrice": "0",
# "stopLossTrigger": "market",
# "buyOpenOrderNotional": "0",
# "sellOpenOrderNotional": "0",
# "markPrice": "31210.723063672",
# "indexPrice": "31223.148857925"
# },
# ]
# }
# }
#
data = self.safe_dict(response, 'data', {})
leverages = self.safe_list(data, 'contracts', [])
return self.parse_leverages(leverages, symbols, 'symbol')
def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
marketId = self.safe_string(leverage, 'symbol')
leverageValue = self.safe_integer(leverage, 'leverage')
marginType = self.safe_string(leverage, 'marginType')
marginMode = 'cross' if (marginType == 'crossed') else 'isolated'
return {
'info': leverage,
'symbol': self.safe_symbol(marketId, market),
'marginMode': marginMode,
'longLeverage': leverageValue,
'shortLeverage': leverageValue,
}
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
version = api[0]
access = api[1]
type = self.safe_string(api, 2)
url = ''
accountCategory = (type == 'accountCategory')
if accountCategory or (type == 'accountGroup'):
url += self.implode_params('/{account-group}', params)
params = self.omit(params, 'account-group')
request = self.implode_params(path, params)
url += '/api/pro/'
if version == 'v2':
if type == 'data':
request = 'data/' + version + '/' + request
else:
request = version + '/' + request
else:
url += version + '/'
if accountCategory:
url += self.implode_params('{account-category}/', params)
params = self.omit(params, 'account-category')
url += request
if (version == 'v1') and (request == 'cash/balance') or (request == 'margin/balance'):
request = 'balance'
if (version == 'v1') and (request == 'spot/fee'):
request = 'fee'
if request.find('subuser') >= 0:
parts = request.split('/')
request = parts[2]
params = self.omit(params, self.extract_params(path))
if access == 'public':
if params:
url += '?' + self.urlencode(params)
else:
self.check_required_credentials()
timestamp = str(self.milliseconds())
payload = timestamp + '+' + request
hmac = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64')
headers = {
'x-auth-key': self.apiKey,
'x-auth-timestamp': timestamp,
'x-auth-signature': hmac,
}
if method == 'GET':
if params:
url += '?' + self.urlencode(params)
else:
headers['Content-Type'] = 'application/json'
body = self.json(params)
url = self.urls['api']['rest'] + url
return {'url': url, 'method': method, 'body': body, 'headers': headers}
def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
if response is None:
return None # fallback to default error handler
#
# {"code": 6010, "message": "Not enough balance."}
# {"code": 60060, "message": "The order is already filled or canceled."}
# {"code":2100,"message":"ApiKeyFailure"}
# {"code":300001,"message":"Price is too low from market price.","reason":"INVALID_PRICE","accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda","ac":"CASH","action":"place-order","status":"Err","info":{"symbol":"BTC/USDT"}}
#
code = self.safe_string(response, 'code')
message = self.safe_string(response, 'message')
error = (code is not None) and (code != '0')
if error or (message is not None):
feedback = self.id + ' ' + body
self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
raise ExchangeError(feedback) # unknown message
return None