UNPKG

sfccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

1,094 lines (1,075 loc) 136 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ArgumentsRequired, AuthenticationError, ExchangeError, InsufficientFunds, InvalidOrder, BadSymbol, PermissionDenied, BadRequest } = require ('./base/errors'); const { TICK_SIZE } = require ('./base/functions/number'); const Precise = require ('./base/Precise'); // --------------------------------------------------------------------------- module.exports = class ascendex extends Exchange { describe () { return this.deepExtend (super.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': undefined, 'spot': true, 'margin': true, 'swap': true, 'future': true, 'option': false, 'addMargin': true, 'cancelAllOrders': true, 'cancelOrder': true, 'createOrder': true, 'createPostOnlyOrder': true, 'createReduceOnlyOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'fetchAccounts': true, 'fetchBalance': true, 'fetchClosedOrders': true, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDepositAddresses': false, 'fetchDepositAddressesByNetwork': false, 'fetchDeposits': true, 'fetchFundingHistory': false, 'fetchFundingRate': 'emulated', 'fetchFundingRateHistory': false, 'fetchFundingRates': true, 'fetchIndexOHLCV': false, 'fetchLeverage': false, 'fetchLeverageTiers': true, 'fetchMarginMode': false, 'fetchMarketLeverageTiers': 'emulated', 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': false, 'fetchPosition': false, 'fetchPositionMode': false, 'fetchPositions': true, 'fetchPositionsRisk': false, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTime': true, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransactionFee': false, 'fetchTransactionFees': false, 'fetchTransactions': true, 'fetchTransfer': false, 'fetchTransfers': false, 'fetchWithdrawal': false, 'fetchWithdrawals': true, 'reduceMargin': 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://user-images.githubusercontent.com/1294454/112027508-47984600-8b48-11eb-9e17-d26459cc36c6.jpg', '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, // not documented '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, }, }, 'private': { 'get': { 'account/info': 1, }, 'accountGroup': { 'get': { 'order/hist': 1, 'futures/position': 1, 'futures/free-margin': 1, 'futures/order/hist/current': 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': this.parseNumber ('0.002'), 'maker': this.parseNumber ('0.002'), }, }, 'precisionMode': TICK_SIZE, 'options': { 'account-category': 'cash', // 'cash', 'margin', 'futures' // obsolete 'account-group': undefined, 'fetchClosedOrders': { 'method': 'v1PrivateAccountGroupGetOrderHist', // 'v1PrivateAccountGroupGetAccountCategoryOrderHistCurrent' }, 'defaultType': 'spot', // 'spot', 'margin', 'swap' 'accountsByType': { 'spot': 'cash', 'swap': 'futures', 'future': 'futures', 'margin': 'margin', }, 'transfer': { 'fillResponseFromRequest': true, }, }, '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 String 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 as trading, 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': InvalidOrder, // TRADING_DISABLED Trading is disabled on account or asset '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': { 'BOND': 'BONDED', 'BTCBEAR': 'BEAR', 'BTCBULL': 'BULL', 'BYN': 'BeyondFi', 'PLN': 'Pollen', }, }); } getAccount (params = {}) { // get current or provided bitmax sub-account const account = this.safeValue (params, 'account', this.options['account']); const lowercaseAccount = account.toLowerCase (); return this.capitalize (lowercaseAccount); } async fetchCurrencies (params = {}) { /** * @method * @name ascendex#fetchCurrencies * @description fetches all available currencies on an exchange * @param {object} params extra parameters specific to the ascendex api endpoint * @returns {object} an associative dictionary of currencies */ const assets = await this.v1PublicGetAssets (params); // // { // "code":0, // "data":[ // { // "assetCode" : "LTCBULL", // "assetName" : "3X Long LTC Token", // "precisionScale" : 9, // "nativeScale" : 4, // "withdrawalFee" : "0.2", // "minWithdrawalAmt" : "1.0", // "status" : "Normal" // }, // ] // } // const margin = await this.v1PublicGetMarginAssets (params); // // { // "code":0, // "data":[ // { // "assetCode":"BTT", // "borrowAssetCode":"BTT-B", // "interestAssetCode":"BTT-I", // "nativeScale":0, // "numConfirmations":1, // "withdrawFee":"100.0", // "minWithdrawalAmt":"1000.0", // "statusCode":"Normal", // "statusMessage":"", // "interestRate":"0.001" // } // ] // } // const cash = await this.v1PublicGetCashAssets (params); // // { // "code":0, // "data":[ // { // "assetCode":"LTCBULL", // "nativeScale":4, // "numConfirmations":20, // "withdrawFee":"0.2", // "minWithdrawalAmt":"1.0", // "statusCode":"Normal", // "statusMessage":"" // } // ] // } // const assetsData = this.safeValue (assets, 'data', []); const marginData = this.safeValue (margin, 'data', []); const cashData = this.safeValue (cash, 'data', []); const assetsById = this.indexBy (assetsData, 'assetCode'); const marginById = this.indexBy (marginData, 'assetCode'); const cashById = this.indexBy (cashData, 'assetCode'); const dataById = this.deepExtend (assetsById, marginById, cashById); const ids = Object.keys (dataById); const result = {}; for (let i = 0; i < ids.length; i++) { const id = ids[i]; const currency = dataById[id]; const code = this.safeCurrencyCode (id); const scale = this.safeString2 (currency, 'precisionScale', 'nativeScale'); const precision = this.parseNumber (this.parsePrecision (scale)); const fee = this.safeNumber2 (currency, 'withdrawFee', 'withdrawalFee'); const status = this.safeString2 (currency, 'status', 'statusCode'); const active = (status === 'Normal'); const margin = ('borrowAssetCode' in currency); result[code] = { 'id': id, 'code': code, 'info': currency, 'type': undefined, 'margin': margin, 'name': this.safeString (currency, 'assetName'), 'active': active, 'deposit': undefined, 'withdraw': undefined, 'fee': fee, 'precision': precision, 'limits': { 'amount': { 'min': precision, 'max': undefined, }, 'withdraw': { 'min': this.safeNumber (currency, 'minWithdrawalAmt'), 'max': undefined, }, }, }; } return result; } async fetchMarkets (params = {}) { /** * @method * @name ascendex#fetchMarkets * @description retrieves data on all markets for ascendex * @param {object} params extra parameters specific to the exchange api endpoint * @returns {[object]} an array of objects representing market data */ const products = await this.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" // }, // ] // } // const cash = await this.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 // } // ] // } // const perpetuals = await this.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"} // ] // } // ] // } // const productsData = this.safeValue (products, 'data', []); const productsById = this.indexBy (productsData, 'symbol'); const cashData = this.safeValue (cash, 'data', []); const perpetualsData = this.safeValue (perpetuals, 'data', []); const cashAndPerpetualsData = this.arrayConcat (cashData, perpetualsData); const cashAndPerpetualsById = this.indexBy (cashAndPerpetualsData, 'symbol'); const dataById = this.deepExtend (productsById, cashAndPerpetualsById); const ids = Object.keys (dataById); const result = []; for (let i = 0; i < ids.length; i++) { const id = ids[i]; const market = dataById[id]; let baseId = this.safeString (market, 'baseAsset'); let quoteId = this.safeString (market, 'quoteAsset'); const settleId = this.safeValue (market, 'settlementAsset'); let base = this.safeCurrencyCode (baseId); let quote = this.safeCurrencyCode (quoteId); const settle = this.safeCurrencyCode (settleId); const status = this.safeString (market, 'status'); const domain = this.safeString (market, 'domain'); let active = false; if (((status === 'Normal') || (status === 'InternalTrading')) && (domain !== 'LeveragedETF')) { active = true; } const spot = settle === undefined; const swap = !spot; const linear = swap ? true : undefined; let minQty = this.safeNumber (market, 'minQty'); let maxQty = this.safeNumber (market, 'maxQty'); let minPrice = this.safeNumber (market, 'tickSize'); let maxPrice = undefined; let symbol = base + '/' + quote; if (swap) { const lotSizeFilter = this.safeValue (market, 'lotSizeFilter'); minQty = this.safeNumber (lotSizeFilter, 'minQty'); maxQty = this.safeNumber (lotSizeFilter, 'maxQty'); const priceFilter = this.safeValue (market, 'priceFilter'); minPrice = this.safeNumber (priceFilter, 'minPrice'); maxPrice = this.safeNumber (priceFilter, 'maxPrice'); const underlying = this.safeString (market, 'underlying'); const parts = underlying.split ('/'); baseId = this.safeString (parts, 0); quoteId = this.safeString (parts, 1); base = this.safeCurrencyCode (baseId); quote = this.safeCurrencyCode (quoteId); symbol = base + '/' + quote + ':' + settle; } const fee = this.safeNumber (market, 'commissionReserveRate'); const marginTradable = this.safeValue (market, 'marginTradable', false); result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, 'type': swap ? 'swap' : 'spot', 'spot': spot, 'margin': spot ? marginTradable : undefined, 'swap': swap, 'future': false, 'option': false, 'active': active, 'contract': swap, 'linear': linear, 'inverse': swap ? !linear : undefined, 'taker': fee, 'maker': fee, 'contractSize': swap ? this.parseNumber ('1') : undefined, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { 'amount': this.safeNumber (market, 'lotSize'), 'price': this.safeNumber (market, 'tickSize'), }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': minQty, 'max': maxQty, }, 'price': { 'min': minPrice, 'max': maxPrice, }, 'cost': { 'min': this.safeNumber (market, 'minNotional'), 'max': this.safeNumber (market, 'maxNotional'), }, }, 'info': market, }); } return result; } async fetchTime (params = {}) { /** * @method * @name ascendex#fetchTime * @description fetches the current integer timestamp in milliseconds from the ascendex server * @param {object} params extra parameters specific to the ascendex api endpoint * @returns {int} the current integer timestamp in milliseconds from the ascendex server */ const request = { 'requestTime': this.milliseconds (), }; const response = await this.v1PublicGetExchangeInfo (this.extend (request, params)); // // { // "code": 0, // "data": { // "requestTimeEcho": 1656560463601, // "requestReceiveAt": 1656560464331, // "latency": 730 // } // } // const data = this.safeValue (response, 'data'); return this.safeInteger (data, 'requestReceiveAt'); } async fetchAccounts (params = {}) { /** * @method * @name ascendex#fetchAccounts * @description fetch all the accounts associated with a profile * @param {object} params extra parameters specific to the ascendex api endpoint * @returns {object} a dictionary of [account structures]{@link https://docs.ccxt.com/en/latest/manual.html#account-structure} indexed by the account type */ let accountGroup = this.safeString (this.options, 'account-group'); let response = undefined; if (accountGroup === undefined) { response = await this.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" // } // } // const data = this.safeValue (response, 'data', {}); accountGroup = this.safeString (data, 'accountGroup'); this.options['account-group'] = accountGroup; } return [ { 'id': accountGroup, 'type': undefined, 'currency': undefined, 'info': response, }, ]; } parseBalance (response) { const timestamp = this.milliseconds (); const result = { 'info': response, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), }; const balances = this.safeValue (response, 'data', []); for (let i = 0; i < balances.length; i++) { const balance = balances[i]; const code = this.safeCurrencyCode (this.safeString (balance, 'asset')); const account = this.account (); account['free'] = this.safeString (balance, 'availableBalance'); account['total'] = this.safeString (balance, 'totalBalance'); result[code] = account; } return this.safeBalance (result); } parseMarginBalance (response) { const timestamp = this.milliseconds (); const result = { 'info': response, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), }; const balances = this.safeValue (response, 'data', []); for (let i = 0; i < balances.length; i++) { const balance = balances[i]; const code = this.safeCurrencyCode (this.safeString (balance, 'asset')); const account = this.account (); account['free'] = this.safeString (balance, 'availableBalance'); account['total'] = this.safeString (balance, 'totalBalance'); const debt = this.safeString (balance, 'borrowed'); const interest = this.safeString (balance, 'interest'); account['debt'] = Precise.stringAdd (debt, interest); result[code] = account; } return this.safeBalance (result); } parseSwapBalance (response) { const timestamp = this.milliseconds (); const result = { 'info': response, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), }; const data = this.safeValue (response, 'data', {}); const collaterals = this.safeValue (data, 'collaterals', []); for (let i = 0; i < collaterals.length; i++) { const balance = collaterals[i]; const code = this.safeCurrencyCode (this.safeString (balance, 'asset')); const account = this.account (); account['total'] = this.safeString (balance, 'balance'); result[code] = account; } return this.safeBalance (result); } async fetchBalance (params = {}) { /** * @method * @name ascendex#fetchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @param {object} params extra parameters specific to the ascendex api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ await this.loadMarkets (); await this.loadAccounts (); let query = undefined; let marketType = undefined; [ marketType, query ] = this.handleMarketTypeAndParams ('fetchBalance', undefined, params); const isMargin = this.safeValue (params, 'margin', false); marketType = isMargin ? 'margin' : marketType; params = this.omit (params, 'margin'); const options = this.safeValue (this.options, 'fetchBalance', {}); const accountsByType = this.safeValue (this.options, 'accountsByType', {}); const accountCategory = this.safeString (accountsByType, marketType, 'cash'); const account = this.safeValue (this.accounts, 0, {}); const accountGroup = this.safeString (account, 'id'); const request = { 'account-group': accountGroup, }; const defaultMethod = this.safeString (options, 'method', 'v1PrivateAccountCategoryGetBalance'); const method = this.getSupportedMapping (marketType, { 'spot': defaultMethod, 'margin': defaultMethod, 'swap': 'v2PrivateAccountGroupGetFuturesPosition', }); if ((accountCategory === 'cash') || (accountCategory === 'margin')) { request['account-category'] = accountCategory; } const response = await this[method] (this.extend (request, query)); // // 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 this.parseSwapBalance (response); } else if (marketType === 'margin') { return this.parseMarginBalance (response); } else { return this.parseBalance (response); } } async fetchOrderBook (symbol, limit = undefined, params = {}) { /** * @method * @name ascendex#fetchOrderBook * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int|undefined} limit the maximum amount of order book entries to return * @param {object} params extra parameters specific to the ascendex api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols */ await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; const response = await this.v1PublicGetDepth (this.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"], // ] // } // } // } // const data = this.safeValue (response, 'data', {}); const orderbook = this.safeValue (data, 'data', {}); const timestamp = this.safeInteger (orderbook, 'ts'); const result = this.parseOrderBook (orderbook, symbol, timestamp); result['nonce'] = this.safeInteger (orderbook, 'seqnum'); return result; } parseTicker (ticker, market = undefined) { // // { // "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" // } // const timestamp = undefined; const marketId = this.safeString (ticker, 'symbol'); const type = this.safeString (ticker, 'type'); const delimiter = (type === 'spot') ? '/' : undefined; const symbol = this.safeSymbol (marketId, market, delimiter); const close = this.safeString (ticker, 'close'); const bid = this.safeValue (ticker, 'bid', []); const ask = this.safeValue (ticker, 'ask', []); const open = this.safeString (ticker, 'open'); return this.safeTicker ({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': undefined, 'high': this.safeString (ticker, 'high'), 'low': this.safeString (ticker, 'low'), 'bid': this.safeString (bid, 0), 'bidVolume': this.safeString (bid, 1), 'ask': this.safeString (ask, 0), 'askVolume': this.safeString (ask, 1), 'vwap': undefined, 'open': open, 'close': close, 'last': close, 'previousClose': undefined, // previous day close 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': this.safeString (ticker, 'volume'), 'quoteVolume': undefined, 'info': ticker, }, market); } async fetchTicker (symbol, params = {}) { /** * @method * @name ascendex#fetchTicker * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the ascendex api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; const response = await this.v1PublicGetTicker (this.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" // } // } // const data = this.safeValue (response, 'data', {}); return this.parseTicker (data, market); } async fetchTickers (symbols = undefined, params = {}) { /** * @method * @name ascendex#fetchTickers * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market * @see https://ascendex.github.io/ascendex-pro-api/#ticker * @see https://ascendex.github.io/ascendex-futures-pro-api-v2/#ticker * @param {[string]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned * @param {object} params extra parameters specific to the ascendex api endpoint * @returns {object} an array of [ticker structures]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const request = {}; let market = undefined; if (symbols !== undefined) { const symbol = this.safeValue (symbols, 0); market = this.market (symbol); const marketIds = this.marketIds (symbols); request['symbol'] = marketIds.join (','); } let type = undefined; [ type, params ] = this.handleMarketTypeAndParams ('fetchTickers', market, params); let response = undefined; if (type === 'spot') { response = await this.v1PublicGetTicker (this.extend (request, params)); } else { response = await this.v2PublicGetFuturesTicker (this.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" // } // ] // } // const data = this.safeValue (response, 'data', []); if (!Array.isArray (data)) { return this.parseTickers ([ data ], symbols); } return this.parseTickers (data, symbols); } parseOHLCV (ohlcv, market = undefined) { // // { // "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" // } // } // const data = this.safeValue (ohlcv, 'data', {}); return [ this.safeInteger (data, 'ts'), this.safeNumber (data, 'o'), this.safeNumber (data, 'h'), this.safeNumber (data, 'l'), this.safeNumber (data, 'c'), this.safeNumber (data, 'v'), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name ascendex#fetchOHLCV * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @param {string} symbol unified symbol of the market to fetch OHLCV data for * @param {string} timeframe the length of time each candle represents * @param {int|undefined} since timestamp in ms of the earliest candle to fetch * @param {int|undefined} limit the maximum amount of candles to fetch * @param {object} params extra parameters specific to the ascendex api endpoint * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume */ await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'interval': this.timeframes[timeframe], }; // if since and limit are not specified // the exchange will return just 1 last candle by default const duration = this.parseTimeframe (timeframe); const options = this.safeValue (this.options, 'fetchOHLCV', {}); const defaultLimit = this.safeInteger (options, 'limit', 500); if (since !== undefined) { request['from'] = since; if (limit === undefined) { lim