UNPKG

ccxt

Version:

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

1,022 lines (1,020 loc) • 129 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'; // --------------------------------------------------------------------------- /** * @class exmo * @augments Exchange */ export default class exmo extends Exchange { describe() { return this.deepExtend(super.describe(), { 'id': 'exmo', 'name': 'EXMO', 'countries': ['LT'], 'rateLimit': 100, 'version': 'v1.1', 'has': { 'CORS': undefined, 'spot': true, 'margin': true, 'swap': false, 'future': false, 'option': false, 'addMargin': true, 'cancelOrder': true, 'cancelOrders': false, 'createDepositAddress': false, 'createMarketBuyOrder': true, 'createMarketBuyOrderWithCost': true, 'createMarketOrderWithCost': true, 'createOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'editOrder': true, 'fetchAccounts': false, 'fetchBalance': true, 'fetchCanceledOrders': true, 'fetchCurrencies': true, 'fetchDeposit': true, 'fetchDepositAddress': true, 'fetchDepositAddresses': false, 'fetchDepositAddressesByNetwork': false, '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, 'fetchPosition': false, 'fetchPositionHistory': false, 'fetchPositionMode': false, 'fetchPositions': false, 'fetchPositionsHistory': false, 'fetchPositionsRisk': false, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransactionFees': true, 'fetchTransactions': 'emulated', '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, }, }, 'features': { 'spot': { 'sandbox': false, 'createOrder': { 'marginMode': true, 'triggerPrice': true, 'triggerPriceType': undefined, 'triggerDirection': false, 'stopLossPrice': false, 'takeProfitPrice': false, 'attachedStopLossTakeProfit': undefined, 'timeInForce': { 'IOC': true, 'FOK': true, 'PO': true, 'GTD': true, }, 'hedged': false, 'selfTradePrevention': false, 'trailing': false, 'leverage': true, 'marketBuyByCost': true, 'marketBuyRequiresPrice': false, 'iceberg': false, }, 'createOrders': undefined, 'fetchMyTrades': { 'marginMode': true, 'limit': 100, 'daysBack': undefined, 'untilDays': undefined, 'symbolRequired': true, }, 'fetchOrder': { 'marginMode': false, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOpenOrders': { 'marginMode': false, 'limit': undefined, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOrders': undefined, 'fetchClosedOrders': undefined, 'fetchOHLCV': { 'limit': 1000, // todo, not in request }, }, 'swap': { 'linear': undefined, 'inverse': undefined, }, 'future': { 'linear': undefined, 'inverse': undefined, }, }, 'commonCurrencies': { 'GMT': 'GMT Token', }, 'precisionMode': TICK_SIZE, 'exceptions': { 'exact': { '140333': InvalidOrder, '140434': BadRequest, '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 response = undefined; if (type === 'add') { response = await this.privatePostMarginUserPositionMarginAdd(this.extend(request, params)); } else if (type === 'reduce') { response = await this.privatePostMarginUserPositionMarginRemove(this.extend(request, params)); } // // {} // const margin = this.parseMarginModification(response, market); const options = this.safeValue(this.options, 'margin', {}); const fillResponseFromRequest = this.safeBool(options, 'fillResponseFromRequest', true); if (fillResponseFromRequest) { margin['type'] = type; margin['amount'] = amount; } return margin; } parseMarginModification(data, market = undefined) { // // {} // return { 'info': data, 'symbol': this.safeSymbol(undefined, market), 'type': undefined, 'marginMode': 'isolated', 'amount': undefined, 'total': undefined, 'code': this.safeValue(market, 'quote'), 'status': 'ok', 'timestamp': undefined, 'datetime': undefined, }; } /** * @method * @name exmo#reduceMargin * @description remove margin from a position * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#eebf9f25-0289-4946-9482-89872c738449 * @param {string} symbol unified market symbol * @param {float} amount the amount of margin to remove * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [margin structure]{@link https://docs.ccxt.com/#/?id=reduce-margin-structure} */ async reduceMargin(symbol, amount, params = {}) { return await this.modifyMarginHelper(symbol, amount, 'reduce', params); } /** * @method * @name exmo#addMargin * @description add margin * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#143ef808-79ca-4e49-9e79-a60ea4d8c0e3 * @param {string} symbol unified market symbol * @param {float} amount amount of margin to add * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [margin structure]{@link https://docs.ccxt.com/#/?id=add-margin-structure} */ async addMargin(symbol, amount, params = {}) { return await this.modifyMarginHelper(symbol, amount, 'add', params); } /** * @method * @name exmo#fetchTradingFees * @description fetch the trading fees for multiple markets * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#90927062-256c-4b03-900f-2b99131f9a54 * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#7de7e75c-5833-45a8-b937-c2276d235aaa * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a dictionary of [fee structures]{@link https://docs.ccxt.com/#/?id=fee-structure} indexed by market symbols */ async fetchTradingFees(params = {}) { const options = this.safeValue(this.options, 'fetchTradingFees', {}); const defaultMethod = this.safeString(options, 'method', 'fetchPrivateTradingFees'); const method = this.safeString(params, 'method', defaultMethod); params = this.omit(params, 'method'); if (method === 'fetchPrivateTradingFees') { return await this.fetchPrivateTradingFees(params); } else { return await this.fetchPublicTradingFees(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; } /** * @method * @name exmo#fetchTransactionFees * @deprecated * @description 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 exchange API endpoint * @returns {object} a list of [transaction fees structures]{@link https://docs.ccxt.com/#/?id=fees-structure} */ async fetchTransactionFees(codes = undefined, params = {}) { 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; } /** * @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 exchange API endpoint * @returns {object} a list of [transaction fees structures]{@link https://docs.ccxt.com/#/?id=fees-structure} */ async fetchDepositWithdrawFees(codes = undefined, params = {}) { 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); } /** * @method * @name exmo#fetchCurrencies * @description fetches all available currencies on an exchange * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#7cdf0ca8-9ff6-4cf3-aa33-bcec83155c49 * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#4190035d-24b1-453d-833b-37e0a52f88e2 * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} an associative dictionary of currencies */ async fetchCurrencies(params = {}) { const promises = []; // promises.push(this.publicGetCurrencyListExtended(params)); // // [ // {"name":"VLX","description":"Velas"}, // {"name":"RUB","description":"Russian Ruble"}, // {"name":"BTC","description":"Bitcoin"}, // {"name":"USD","description":"US Dollar"} // ] // promises.push(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 responses = await Promise.all(promises); const currencyList = responses[0]; const cryptoList = responses[1]; const result = {}; for (let i = 0; i < currencyList.length; i++) { const currency = currencyList[i]; const currencyId = this.safeString(currency, 'name'); const code = this.safeCurrencyCode(currencyId); let type = 'crypto'; const networks = {}; const providers = this.safeList(cryptoList, currencyId); if (providers === undefined) { type = 'fiat'; } else { for (let j = 0; j < providers.length; j++) { const provider = providers[j]; const name = this.safeString(provider, 'name'); // get network-id by removing extra things let networkId = name.replace(currencyId + ' ', ''); networkId = networkId.replace('(', ''); const replaceChar = ')'; // transpiler trick networkId = networkId.replace(replaceChar, ''); const networkCode = this.networkIdToCode(networkId); if (!(networkCode in networks)) { networks[networkCode] = { 'id': networkId, 'network': networkCode, 'active': undefined, 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'limits': { 'withdraw': { 'min': undefined, 'max': undefined, }, 'deposit': { 'min': undefined, 'max': undefined, }, }, 'info': [], // set as array, because of multiple network sub-entries }; } const typeInner = this.safeString(provider, 'type'); const minValue = this.safeString(provider, 'min'); const maxValue = this.safeString(provider, 'max'); const activeProvider = this.safeBool(provider, 'enabled'); const networkEntry = networks[networkCode]; if (typeInner === 'deposit') { networkEntry['deposit'] = activeProvider; networkEntry['limits']['deposit']['min'] = minValue; networkEntry['limits']['deposit']['max'] = maxValue; } else if (typeInner === 'withdraw') { networkEntry['withdraw'] = activeProvider; networkEntry['limits']['withdraw']['min'] = minValue; networkEntry['limits']['withdraw']['max'] = maxValue; } const info = this.safeList(networkEntry, 'info'); info.push(provider); networkEntry['info'] = info; networks[networkCode] = networkEntry; } } result[code] = this.safeCurrencyStructure({ 'id': currencyId, 'code': code, 'name': this.safeString(currency, 'description'), 'type': type, 'active': undefined, 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'precision': this.parseNumber('1e-8'), 'limits': { 'withdraw': { 'min': undefined, 'max': undefined, }, 'deposit': { 'min': undefined, 'max': undefined, }, }, 'info': { 'currency': currency, 'providers': providers, }, 'networks': networks, }); } return result; } /** * @method * @name exmo#fetchMarkets * @description retrieves data on all markets for exmo * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#7de7e75c-5833-45a8-b937-c2276d235aaa * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} an array of objects representing market data */ async fetchMarkets(params = {}) { const promises = []; promises.push(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" // }, // } // let marginPairsDict = {}; const fetchMargin = this.checkRequiredCredentials(false); if (fetchMargin) { promises.push(this.privatePostMarginPairList(params)); // // { // "pairs": [ // { // "buy_price": "55978.85", // "default_leverage": "3", // "is_fair_price": true, // "last_trade_price": "55999.23", // "liquidation_fee": "2", // "liquidation_level": "10", // "margin_call_level": "15", // "max_leverage": "3", // "max_order_price": "150000", // "max_order_quantity": "1", // "max_position_quantity": "1", // "max_price_precision": 2, // "min_order_price": "1", // "min_order_quantity": "0.00002", // "name": "BTC_USD", // "position": 1, // "sell_price": "55985.51", // "ticker_updated": "1619019818936107989", // "trade_maker_fee": "0", // "trade_taker_fee": "0.05", // "updated": "1619008608955599013" // } // ] // } // } const responses = await Promise.all(promises); const spotResponse = responses[0]; if (fetchMargin) { const marginPairs = responses[1]; const pairs = this.safeList(marginPairs, 'pairs'); marginPairsDict = this.indexBy(pairs, 'name'); } const keys = Object.keys(spotResponse); const result = []; for (let i = 0; i < keys.length; i++) { const id = keys[i]; const market = spotResponse[id]; const marginMarket = this.safeDict(marginPairsDict, 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'); const maxQuantity = this.safeString(market, 'max_quantity'); const marginMaxQuantity = this.safeString(marginMarket, 'max_order_quantity'); result.push({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': undefined, 'baseId': baseId, 'quoteId': quoteId, 'settleId': undefined, 'type': 'spot', 'spot': true, 'margin': marginMarket !== undefined, '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': this.safeNumber(market, 'leverage'), }, 'amount': { 'min': this.safeNumber(market, 'min_quantity'), 'max': this.parseNumber(Precise.stringMax(maxQuantity, marginMaxQuantity)), }, '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'), }, }, 'created': undefined, 'info': market, }); } return result; } /** * @method * @name exmo#fetchOHLCV * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#65eeb949-74e5-4631-9184-c38387fe53e8 * @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} [since] timestamp in ms of the earliest candle to fetch * @param {int} [limit] the maximum amount of candles to fetch * @param {object} [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 as timestamp, open, high, low, close, volume */ async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const until = this.safeIntegerProduct(params, 'until', 0.001); const untilIsDefined = (until !== undefined); const request = { 'symbol': market['id'], 'resolution': this.safeString(this.timeframes, timeframe, timeframe), }; const maxLimit = 3000; const duration = this.parseTimeframe(timeframe); const now = this.parseToInt(this.milliseconds() / 1000); if (since === undefined) { const to = untilIsDefined ? Math.min(until, now) : now; if (limit === undefined) { limit = 1000; // cap default at generous amount } else { limit = Math.min(limit, maxLimit); } request['from'] = to - (limit * duration) - 1; request['to'] = to; } else { request['from'] = this.parseToInt(since / 1000) - 1; if (untilIsDefined) { request['to'] = Math.min(until, now); } else { if (limit === undefined) { limit = maxLimit; } else { limit = Math.min(limit, maxLimit); } const to = this.sum(since, limit * duration); request['to'] = Math.min(to, now); } } params = this.omit(params, 'until'); 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.safeList(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(ohlc