UNPKG

@proton/ccxt

Version:

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

1,024 lines (1,022 loc) 96 kB
// ---------------------------------------------------------------------------- // 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 // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import Exchange from './abstract/exmo.js'; import { ArgumentsRequired, ExchangeError, OrderNotFound, AuthenticationError, InsufficientFunds, InvalidOrder, InvalidNonce, OnMaintenance, RateLimitExceeded, BadRequest, PermissionDenied } from './base/errors.js'; import { Precise } from './base/Precise.js'; import { TICK_SIZE } from './base/functions/number.js'; import { sha512 } from './static_dependencies/noble-hashes/sha512.js'; // --------------------------------------------------------------------------- export default class exmo extends Exchange { describe() { return this.deepExtend(super.describe(), { 'id': 'exmo', 'name': 'EXMO', 'countries': ['LT'], 'rateLimit': 350, 'version': 'v1.1', 'has': { 'CORS': undefined, 'spot': true, 'margin': true, 'swap': false, 'future': false, 'option': false, 'addMargin': true, 'cancelOrder': true, 'cancelOrders': false, 'createDepositAddress': false, 'createOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'fetchAccounts': false, 'fetchBalance': true, 'fetchCanceledOrders': true, 'fetchCurrencies': true, 'fetchDeposit': true, 'fetchDepositAddress': true, 'fetchDeposits': true, 'fetchDepositsWithdrawals': true, 'fetchDepositWithdrawFee': 'emulated', 'fetchDepositWithdrawFees': true, 'fetchFundingHistory': false, 'fetchFundingRate': false, 'fetchFundingRateHistory': false, 'fetchFundingRates': false, 'fetchIndexOHLCV': false, 'fetchMarginMode': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenInterestHistory': false, 'fetchOpenOrders': true, 'fetchOrder': 'emulated', 'fetchOrderBook': true, 'fetchOrderBooks': true, 'fetchOrderTrades': true, 'fetchPositionMode': false, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransactionFees': true, 'fetchTransactions': true, 'fetchTransfer': false, 'fetchTransfers': false, 'fetchWithdrawal': true, 'fetchWithdrawals': true, 'reduceMargin': true, 'setMargin': false, 'transfer': false, 'withdraw': true, }, 'timeframes': { '1m': '1', '5m': '5', '15m': '15', '30m': '30', '45m': '45', '1h': '60', '2h': '120', '3h': '180', '4h': '240', '1d': 'D', '1w': 'W', '1M': 'M', }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/27766491-1b0ea956-5eda-11e7-9225-40d67b481b8d.jpg', 'api': { 'public': 'https://api.exmo.com', 'private': 'https://api.exmo.com', 'web': 'https://exmo.me', }, 'www': 'https://exmo.me', 'referral': 'https://exmo.me/?ref=131685', 'doc': [ 'https://exmo.me/en/api_doc?ref=131685', ], 'fees': 'https://exmo.com/en/docs/fees', }, 'api': { 'web': { 'get': [ 'ctrl/feesAndLimits', 'en/docs/fees', ], }, 'public': { 'get': [ 'currency', 'currency/list/extended', 'order_book', 'pair_settings', 'ticker', 'trades', 'candles_history', 'required_amount', 'payments/providers/crypto/list', ], }, 'private': { 'post': [ 'user_info', 'order_create', 'order_cancel', 'stop_market_order_create', 'stop_market_order_cancel', 'user_open_orders', 'user_trades', 'user_cancelled_orders', 'order_trades', 'deposit_address', 'withdraw_crypt', 'withdraw_get_txid', 'excode_create', 'excode_load', 'code_check', 'wallet_history', 'wallet_operations', 'margin/user/order/create', 'margin/user/order/update', 'margin/user/order/cancel', 'margin/user/position/close', 'margin/user/position/margin_add', 'margin/user/position/margin_remove', 'margin/currency/list', 'margin/pair/list', 'margin/settings', 'margin/funding/list', 'margin/user/info', 'margin/user/order/list', 'margin/user/order/history', 'margin/user/order/trades', 'margin/user/order/max_quantity', 'margin/user/position/list', 'margin/user/position/margin_remove_info', 'margin/user/position/margin_add_info', 'margin/user/wallet/list', 'margin/user/wallet/history', 'margin/user/trade/list', 'margin/trades', 'margin/liquidation/feed', ], }, }, 'fees': { 'trading': { 'feeSide': 'get', 'tierBased': true, 'percentage': true, 'maker': this.parseNumber('0.004'), 'taker': this.parseNumber('0.004'), }, 'transaction': { 'tierBased': false, 'percentage': false, // fixed transaction fees for crypto, see fetchDepositWithdrawFees below }, }, 'options': { 'networks': { 'ETH': 'ERC20', 'TRX': 'TRC20', }, 'fetchTradingFees': { 'method': 'fetchPrivateTradingFees', // or 'fetchPublicTradingFees' }, 'margin': { 'fillResponseFromRequest': true, }, }, 'commonCurrencies': { 'GMT': 'GMT Token', }, 'precisionMode': TICK_SIZE, 'exceptions': { 'exact': { '40005': AuthenticationError, '40009': InvalidNonce, '40015': ExchangeError, '40016': OnMaintenance, '40017': AuthenticationError, '40032': PermissionDenied, '40033': PermissionDenied, '40034': RateLimitExceeded, '50052': InsufficientFunds, '50054': InsufficientFunds, '50304': OrderNotFound, '50173': OrderNotFound, '50277': InvalidOrder, '50319': InvalidOrder, '50321': InvalidOrder, '50381': InvalidOrder, // {"result":false,"error":"Error 50381: More than 2 decimal places are not permitted for pair BTC_USD"} }, 'broad': { 'range period is too long': BadRequest, 'invalid syntax': BadRequest, 'API rate limit exceeded': RateLimitExceeded, // {"result":false,"error":"API rate limit exceeded for x.x.x.x. Retry after 60 sec.","history":[],"begin":1579392000,"end":1579478400} }, }, }); } async modifyMarginHelper(symbol, amount, type, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const request = { 'position_id': market['id'], 'quantity': amount, }; let method = undefined; if (type === 'add') { method = 'privatePostMarginUserPositionMarginAdd'; } else if (type === 'reduce') { method = 'privatePostMarginUserPositionMarginReduce'; } const response = await this[method](this.extend(request, params)); // // {} // const margin = this.parseMarginModification(response, market); const options = this.safeValue(this.options, 'margin', {}); const fillResponseFromRequest = this.safeValue(options, 'fillResponseFromRequest', true); if (fillResponseFromRequest) { margin['type'] = type; margin['amount'] = amount; } return margin; } parseMarginModification(data, market = undefined) { // // {} // return { 'info': data, 'type': undefined, 'amount': undefined, 'code': this.safeValue(market, 'quote'), 'symbol': this.safeSymbol(undefined, market), 'total': undefined, 'status': 'ok', }; } async reduceMargin(symbol, amount, params = {}) { /** * @method * @name exmo#reduceMargin * @description remove margin from a position * @param {string} symbol unified market symbol * @param {float} amount the amount of margin to remove * @param {object} params extra parameters specific to the exmo api endpoint * @returns {object} a [margin structure]{@link https://docs.ccxt.com/#/?id=reduce-margin-structure} */ return await this.modifyMarginHelper(symbol, amount, 'reduce', params); } async addMargin(symbol, amount, params = {}) { /** * @method * @name exmo#addMargin * @description add margin * @param {string} symbol unified market symbol * @param {float} amount amount of margin to add * @param {object} params extra parameters specific to the exmo api endpoint * @returns {object} a [margin structure]{@link https://docs.ccxt.com/#/?id=add-margin-structure} */ return await this.modifyMarginHelper(symbol, amount, 'add', params); } async fetchTradingFees(params = {}) { /** * @method * @name exmo#fetchTradingFees * @description fetch the trading fees for multiple markets * @param {object} params extra parameters specific to the exmo api endpoint * @returns {object} a dictionary of [fee structures]{@link https://docs.ccxt.com/#/?id=fee-structure} indexed by market symbols */ let method = this.safeString(params, 'method'); params = this.omit(params, 'method'); if (method === undefined) { const options = this.safeValue(this.options, 'fetchTradingFees', {}); method = this.safeString(options, 'method', 'fetchPrivateTradingFees'); } return await this[method](params); } async fetchPrivateTradingFees(params = {}) { await this.loadMarkets(); const response = await this.privatePostMarginPairList(params); // // { // pairs: [{ // name: 'EXM_USD', // buy_price: '0.02728391', // sell_price: '0.0276', // last_trade_price: '0.0276', // ticker_updated: '1646956050056696046', // is_fair_price: true, // max_price_precision: '8', // min_order_quantity: '1', // max_order_quantity: '50000', // min_order_price: '0.00000001', // max_order_price: '1000', // max_position_quantity: '50000', // trade_taker_fee: '0.05', // trade_maker_fee: '0', // liquidation_fee: '0.5', // max_leverage: '3', // default_leverage: '3', // liquidation_level: '5', // margin_call_level: '7.5', // position: '1', // updated: '1638976144797807397' // } // ... // ] // } // const pairs = this.safeValue(response, 'pairs', []); const result = {}; for (let i = 0; i < pairs.length; i++) { const pair = pairs[i]; const marketId = this.safeString(pair, 'name'); const symbol = this.safeSymbol(marketId, undefined, '_'); const makerString = this.safeString(pair, 'trade_maker_fee'); const takerString = this.safeString(pair, 'trade_taker_fee'); const maker = this.parseNumber(Precise.stringDiv(makerString, '100')); const taker = this.parseNumber(Precise.stringDiv(takerString, '100')); result[symbol] = { 'info': pair, 'symbol': symbol, 'maker': maker, 'taker': taker, 'percentage': true, 'tierBased': true, }; } return result; } async fetchPublicTradingFees(params = {}) { await this.loadMarkets(); const response = await this.publicGetPairSettings(params); // // { // BTC_USD: { // min_quantity: '0.00002', // max_quantity: '1000', // min_price: '1', // max_price: '150000', // max_amount: '500000', // min_amount: '1', // price_precision: '2', // commission_taker_percent: '0.3', // commission_maker_percent: '0.3' // }, // } // const result = {}; for (let i = 0; i < this.symbols.length; i++) { const symbol = this.symbols[i]; const market = this.market(symbol); const fee = this.safeValue(response, market['id'], {}); const makerString = this.safeString(fee, 'commission_maker_percent'); const takerString = this.safeString(fee, 'commission_taker_percent'); const maker = this.parseNumber(Precise.stringDiv(makerString, '100')); const taker = this.parseNumber(Precise.stringDiv(takerString, '100')); result[symbol] = { 'info': fee, 'symbol': symbol, 'maker': maker, 'taker': taker, 'percentage': true, 'tierBased': true, }; } return result; } parseFixedFloatValue(input) { if ((input === undefined) || (input === '-')) { return undefined; } if (input === '') { return 0; } const isPercentage = (input.indexOf('%') >= 0); const parts = input.split(' '); const value = parts[0].replace('%', ''); const result = parseFloat(value); if ((result > 0) && isPercentage) { throw new ExchangeError(this.id + ' parseFixedFloatValue() detected an unsupported non-zero percentage-based fee ' + input); } return result; } async fetchTransactionFees(codes = undefined, params = {}) { /** * @method * @name exmo#fetchTransactionFees * @description *DEPRECATED* please use fetchDepositWithdrawFees instead * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#4190035d-24b1-453d-833b-37e0a52f88e2 * @param {[string]|undefined} codes list of unified currency codes * @param {object} params extra parameters specific to the exmo api endpoint * @returns {object} a list of [transaction fees structures]{@link https://docs.ccxt.com/#/?id=fees-structure} */ await this.loadMarkets(); const cryptoList = await this.publicGetPaymentsProvidersCryptoList(params); // // { // "BTC":[ // { "type":"deposit", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.001 BTC. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 }, // { "type":"withdraw", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"350", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.0005 BTC", "currency_confirmations":6 } // ], // "ETH":[ // { "type":"withdraw", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"500", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.004 ETH", "currency_confirmations":4 }, // { "type":"deposit", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.01 ETH. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 } // ], // "USDT":[ // { "type":"deposit", "name":"USDT (OMNI)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":false,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2 }, // { "type":"withdraw", "name":"USDT (OMNI)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":false,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"5 USDT", "currency_confirmations":6 }, // { "type":"deposit", "name":"USDT (ERC20)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2 }, // { // "type":"withdraw", // "name":"USDT (ERC20)", // "currency_name":"USDT", // "min":"55", // "max":"200000", // "enabled":true, // "comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Recommendation: Due to the high load of ERC20 network, using TRC20 address for withdrawal is recommended.", // "commission_desc":"10 USDT", // "currency_confirmations":6 // }, // { "type":"deposit", "name":"USDT (TRC20)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":true,"comment":"Minimum deposit amount is 10 USDT. Only TRON main network supported", "commission_desc":"0%", "currency_confirmations":2 }, // { "type":"withdraw", "name":"USDT (TRC20)", "currency_name":"USDT", "min":"10", "max":"150000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Only TRON main network supported.", "commission_desc":"1 USDT", "currency_confirmations":6 } // ], // "XLM":[ // { "type":"deposit", "name":"XLM", "currency_name":"XLM", "min":"1", "max":"1000000", "enabled":true,"comment":"Attention! A deposit without memo(invoice) will not be credited. Minimum deposit amount is 1 XLM. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 }, // { "type":"withdraw", "name":"XLM", "currency_name":"XLM", "min":"21", "max":"1000000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales.", "commission_desc":"0.01 XLM", "currency_confirmations":1 } // ], // } // const result = {}; const cryptoListKeys = Object.keys(cryptoList); for (let i = 0; i < cryptoListKeys.length; i++) { const code = cryptoListKeys[i]; if (codes !== undefined && !this.inArray(code, codes)) { continue; } result[code] = { 'deposit': undefined, 'withdraw': undefined, }; const currency = this.currency(code); const currencyId = this.safeString(currency, 'id'); const providers = this.safeValue(cryptoList, currencyId, []); for (let j = 0; j < providers.length; j++) { const provider = providers[j]; const typeInner = this.safeString(provider, 'type'); const commissionDesc = this.safeString(provider, 'commission_desc'); const fee = this.parseFixedFloatValue(commissionDesc); result[code][typeInner] = fee; } result[code]['info'] = providers; } // cache them for later use this.options['transactionFees'] = result; return result; } async fetchDepositWithdrawFees(codes = undefined, params = {}) { /** * @method * @name exmo#fetchDepositWithdrawFees * @description fetch deposit and withdraw fees * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#4190035d-24b1-453d-833b-37e0a52f88e2 * @param {[string]|undefined} codes list of unified currency codes * @param {object} params extra parameters specific to the exmo api endpoint * @returns {object} a list of [transaction fees structures]{@link https://docs.ccxt.com/#/?id=fees-structure} */ await this.loadMarkets(); const response = await this.publicGetPaymentsProvidersCryptoList(params); // // { // "USDT": [ // { // "type": "deposit", // or "withdraw" // "name": "USDT (ERC20)", // "currency_name": "USDT", // "min": "10", // "max": "0", // "enabled": true, // "comment": "Minimum deposit amount is 10 USDT", // "commission_desc": "0%", // "currency_confirmations": 2 // }, // ... // ], // ... // } // const result = this.parseDepositWithdrawFees(response, codes); // cache them for later use this.options['transactionFees'] = result; return result; } parseDepositWithdrawFee(fee, currency = undefined) { // // [ // { // "type": "deposit", // or "withdraw" // "name": "BTC", // "currency_name": "BTC", // "min": "0.001", // "max": "0", // "enabled": true, // "comment": "Minimum deposit amount is 0.001 BTC. We do not support BSC and BEP20 network, please consider this when sending funds", // "commission_desc": "0%", // "currency_confirmations": 1 // }, // ... // ] // const result = this.depositWithdrawFee(fee); for (let i = 0; i < fee.length; i++) { const provider = fee[i]; const type = this.safeString(provider, 'type'); const networkId = this.safeString(provider, 'name'); const networkCode = this.networkIdToCode(networkId, this.safeString(currency, 'code')); const commissionDesc = this.safeString(provider, 'commission_desc'); let splitCommissionDesc = []; let percentage = undefined; if (commissionDesc !== undefined) { splitCommissionDesc = commissionDesc.split('%'); const splitCommissionDescLength = splitCommissionDesc.length; percentage = splitCommissionDescLength >= 2; } const network = this.safeValue(result['networks'], networkCode); if (network === undefined) { result['networks'][networkCode] = { 'withdraw': { 'fee': undefined, 'percentage': undefined, }, 'deposit': { 'fee': undefined, 'percentage': undefined, }, }; } result['networks'][networkCode][type] = { 'fee': this.parseFixedFloatValue(this.safeString(splitCommissionDesc, 0)), 'percentage': percentage, }; } return this.assignDefaultDepositWithdrawFees(result); } async fetchCurrencies(params = {}) { /** * @method * @name exmo#fetchCurrencies * @description fetches all available currencies on an exchange * @param {object} params extra parameters specific to the exmo api endpoint * @returns {object} an associative dictionary of currencies */ // const currencyList = await this.publicGetCurrencyListExtended(params); // // [ // {"name":"VLX","description":"Velas"}, // {"name":"RUB","description":"Russian Ruble"}, // {"name":"BTC","description":"Bitcoin"}, // {"name":"USD","description":"US Dollar"} // ] // const cryptoList = await this.publicGetPaymentsProvidersCryptoList(params); // // { // "BTC":[ // { "type":"deposit", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.001 BTC. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 }, // { "type":"withdraw", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"350", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.0005 BTC", "currency_confirmations":6 } // ], // "ETH":[ // { "type":"withdraw", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"500", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.004 ETH", "currency_confirmations":4 }, // { "type":"deposit", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.01 ETH. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 } // ], // "USDT":[ // { "type":"deposit", "name":"USDT (OMNI)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":false,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2 }, // { "type":"withdraw", "name":"USDT (OMNI)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":false,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"5 USDT", "currency_confirmations":6 }, // { "type":"deposit", "name":"USDT (ERC20)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2 }, // { "type":"withdraw", "name":"USDT (ERC20)", "currency_name":"USDT", "min":"55", "max":"200000", "enabled":true, "comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Recommendation: Due to the high load of ERC20 network, using TRC20 address for withdrawal is recommended.", "commission_desc":"10 USDT", "currency_confirmations":6 }, // { "type":"deposit", "name":"USDT (TRC20)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":true,"comment":"Minimum deposit amount is 10 USDT. Only TRON main network supported", "commission_desc":"0%", "currency_confirmations":2 }, // { "type":"withdraw", "name":"USDT (TRC20)", "currency_name":"USDT", "min":"10", "max":"150000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Only TRON main network supported.", "commission_desc":"1 USDT", "currency_confirmations":6 } // ], // "XLM":[ // { "type":"deposit", "name":"XLM", "currency_name":"XLM", "min":"1", "max":"1000000", "enabled":true,"comment":"Attention! A deposit without memo(invoice) will not be credited. Minimum deposit amount is 1 XLM. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 }, // { "type":"withdraw", "name":"XLM", "currency_name":"XLM", "min":"21", "max":"1000000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales.", "commission_desc":"0.01 XLM", "currency_confirmations":1 } // ], // } // const result = {}; for (let i = 0; i < currencyList.length; i++) { const currency = currencyList[i]; const currencyId = this.safeString(currency, 'name'); const name = this.safeString(currency, 'description'); const providers = this.safeValue(cryptoList, currencyId); let active = false; let type = 'crypto'; const limits = { 'deposit': { 'min': undefined, 'max': undefined, }, 'withdraw': { 'min': undefined, 'max': undefined, }, }; let fee = undefined; let depositEnabled = undefined; let withdrawEnabled = undefined; if (providers === undefined) { active = true; type = 'fiat'; } else { for (let j = 0; j < providers.length; j++) { const provider = providers[j]; const typeInner = this.safeString(provider, 'type'); const minValue = this.safeString(provider, 'min'); let maxValue = this.safeString(provider, 'max'); if (Precise.stringEq(maxValue, '0.0')) { maxValue = undefined; } const activeProvider = this.safeValue(provider, 'enabled'); if (typeInner === 'deposit') { if (activeProvider && !depositEnabled) { depositEnabled = true; } else if (!activeProvider) { depositEnabled = false; } } else if (typeInner === 'withdraw') { if (activeProvider && !withdrawEnabled) { withdrawEnabled = true; } else if (!activeProvider) { withdrawEnabled = false; } } if (activeProvider) { active = true; const limitMin = this.numberToString(limits[typeInner]['min']); if ((limits[typeInner]['min'] === undefined) || (Precise.stringLt(minValue, limitMin))) { limits[typeInner]['min'] = minValue; limits[typeInner]['max'] = maxValue; if (typeInner === 'withdraw') { const commissionDesc = this.safeString(provider, 'commission_desc'); fee = this.parseFixedFloatValue(commissionDesc); } } } } } const code = this.safeCurrencyCode(currencyId); result[code] = { 'id': currencyId, 'code': code, 'name': name, 'type': type, 'active': active, 'deposit': depositEnabled, 'withdraw': withdrawEnabled, 'fee': fee, 'precision': this.parseNumber('1e-8'), 'limits': limits, 'info': providers, 'networks': {}, }; } return result; } async fetchMarkets(params = {}) { /** * @method * @name exmo#fetchMarkets * @description retrieves data on all markets for exmo * @param {object} params extra parameters specific to the exchange api endpoint * @returns {[object]} an array of objects representing market data */ const response = await this.publicGetPairSettings(params); // // { // "BTC_USD":{ // "min_quantity":"0.0001", // "max_quantity":"1000", // "min_price":"1", // "max_price":"30000", // "max_amount":"500000", // "min_amount":"1", // "price_precision":8, // "commission_taker_percent":"0.4", // "commission_maker_percent":"0.4" // }, // } // const keys = Object.keys(response); const result = []; for (let i = 0; i < keys.length; i++) { const id = keys[i]; const market = response[id]; const symbol = id.replace('_', '/'); const [baseId, quoteId] = symbol.split('/'); const base = this.safeCurrencyCode(baseId); const quote = this.safeCurrencyCode(quoteId); const takerString = this.safeString(market, 'commission_taker_percent'); const makerString = this.safeString(market, 'commission_maker_percent'); result.push({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': undefined, 'baseId': baseId, 'quoteId': quoteId, 'settleId': undefined, 'type': 'spot', 'spot': true, 'margin': true, 'swap': false, 'future': false, 'option': false, 'active': undefined, 'contract': false, 'linear': undefined, 'inverse': undefined, 'taker': this.parseNumber(Precise.stringDiv(takerString, '100')), 'maker': this.parseNumber(Precise.stringDiv(makerString, '100')), 'contractSize': undefined, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { 'amount': this.parseNumber('1e-8'), 'price': this.parseNumber(this.parsePrecision(this.safeString(market, 'price_precision'))), }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': this.safeNumber(market, 'min_quantity'), 'max': this.safeNumber(market, 'max_quantity'), }, 'price': { 'min': this.safeNumber(market, 'min_price'), 'max': this.safeNumber(market, 'max_price'), }, 'cost': { 'min': this.safeNumber(market, 'min_amount'), 'max': this.safeNumber(market, 'max_amount'), }, }, 'info': market, }); } return result; } async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name exmo#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 exmo 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'], 'resolution': this.safeString(this.timeframes, timeframe, timeframe), }; const options = this.safeValue(this.options, 'fetchOHLCV'); const maxLimit = this.safeInteger(options, 'maxLimit', 3000); const duration = this.parseTimeframe(timeframe); const now = this.milliseconds(); if (since === undefined) { if (limit === undefined) { limit = 1000; // cap default at generous amount } if (limit > maxLimit) { limit = maxLimit; // avoid exception } request['from'] = this.parseToInt(now / 1000) - limit * duration - 1; request['to'] = this.parseToInt(now / 1000); } else { request['from'] = this.parseToInt(since / 1000) - 1; if (limit === undefined) { request['to'] = this.parseToInt(now / 1000); } else { if (limit > maxLimit) { throw new BadRequest(this.id + ' fetchOHLCV() will serve ' + maxLimit.toString() + ' candles at most'); } const to = this.sum(since, limit * duration * 1000); request['to'] = this.parseToInt(to / 1000); } } const response = await this.publicGetCandlesHistory(this.extend(request, params)); // // { // "candles":[ // {"t":1584057600000,"o":0.02235144,"c":0.02400233,"h":0.025171,"l":0.02221,"v":5988.34031761}, // {"t":1584144000000,"o":0.0240373,"c":0.02367413,"h":0.024399,"l":0.0235,"v":2027.82522329}, // {"t":1584230400000,"o":0.02363458,"c":0.02319242,"h":0.0237948,"l":0.02223196,"v":1707.96944997}, // ] // } // const candles = this.safeValue(response, 'candles', []); return this.parseOHLCVs(candles, market, timeframe, since, limit); } parseOHLCV(ohlcv, market = undefined) { // // { // "t":1584057600000, // "o":0.02235144, // "c":0.02400233, // "h":0.025171, // "l":0.02221, // "v":5988.34031761 // } // return [ this.safeInteger(ohlcv, 't'), this.safeNumber(ohlcv, 'o'), this.safeNumber(ohlcv, 'h'), this.safeNumber(ohlcv, 'l'), this.safeNumber(ohlcv, 'c'), this.safeNumber(ohlcv, 'v'), ]; } parseBalance(response) { const result = { 'info': response }; const free = this.safeValue(response, 'balances', {}); const used = this.safeValue(response, 'reserved', {}); const currencyIds = Object.keys(free); for (let i = 0; i < currencyIds.length; i++) { const currencyId = currencyIds[i]; const code = this.safeCurrencyCode(currencyId); const account = this.account(); if (currencyId in free) { account['free'] = this.safeString(free, currencyId); } if (currencyId in used) { account['used'] = this.safeString(used, currencyId); } result[code] = account; } return this.safeBalance(result); } async fetchBalance(params = {}) { /** * @method * @name exmo#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 exmo api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ await this.loadMarkets(); const response = await this.privatePostUserInfo(params); // // { // "uid":131685, // "server_date":1628999600, // "balances":{ // "EXM":"0", // "USD":"0", // "EUR":"0", // "GBP":"0", // }, // } // return this.parseBalance(response); } async fetchOrderBook(symbol, limit = undefined, params = {}) { /** * @method * @name exmo#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 exmo api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ await this.loadMarkets(); const market = this.market(symbol); const request = { 'pair': market['id'], }; if (limit !== undefined) { request['limit'] = limit; } const response = await this.publicGetOrderBook(this.extend(request, params)); const result = this.safeValue(response, market['id']); return this.parseOrderBook(result, market['symbol'], undefined, 'bid', 'ask'); } async fetchOrderBooks(symbols = undefined, limit = undefined, params = {}) { /** * @method * @name exmo#fetchOrderBooks * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data for multiple markets * @param {[string]|undefined} symbols list of unified market symbols, all symbols fetched if undefined, default is undefined * @param {int|undefined} limit max number of entries per orderbook to return, default is undefined * @param {object} params extra parameters specific to the exmo api endpoint * @returns {object} a dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbol */ await this.loadMarkets(); let ids = undefined; if (symbols === undefined) { ids = this.ids.join(','); // max URL length is 2083 symbols, including http schema, hostname, tld, etc... if (ids.length > 2048) { const numIds = this.ids.length; throw new ExchangeError(this.id + ' fetchOrderBooks() has ' + numIds.toString() + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); } } else { ids = this.marketIds(symbols); ids = ids.join(','); } const request = { 'pair': ids, }; if (limit !== undefined) { request['limit'] = limit; } const response = await this.publicGetOrderBook(this.extend(request, params)); const result = {}; const marketIds = Object.keys(response); for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i]; const symbol = this.safeSymbol(marketId); result[symbol] = this.parseOrderBook(response[marketId], symbol, undefined, 'bid', 'ask'); } return result; } parseTicker(ticker, market = undefined) { // // { // "buy_price":"0.00002996", // "sell_price":"0.00003002", // "last_trade":"0.00002992", // "high":"0.00003028", // "low":"0.00002935", // "avg":"0.00002963", // "vol":"1196546.3163222", // "vol_curr":"35.80066578", // "updated":1642291733 // } // const timestamp = this.safeTimestamp(ticker, 'updated'); market = this.safeMarket(undefined, market); const last = this.safeString(ticker, 'last_trade'); return this.safeTicker({ 'symbol': market['symbol'], 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'high': this.safeString(ticker, 'high'), 'low': this.safeString(ticker, 'low'), 'bid': this.safeString(ticker, 'buy_price'), 'bidVolume': undefined, 'ask': this.safeString(ticker, 'sell_price'), 'askVolume': undefined, 'vwap': undefined, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': this.safeString(ticker, 'avg'), 'baseVolume': this.safeString(ticker, 'vol'), 'quoteVolume': this.safeString(ticker, 'vol_curr'), 'info': ticker, }, market); } async fetchTickers(symbols = undefined, params = {}) { /** * @method * @name exmo#fetchTickers * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market * @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 exmo api endpoint * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ await this.loadMarke