UNPKG

ccxt-look

Version:

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

1,054 lines (1,030 loc) 73.2 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ArgumentsRequired, ExchangeError, OrderNotFound, AuthenticationError, InsufficientFunds, InvalidOrder, InvalidNonce, OnMaintenance, RateLimitExceeded, BadRequest, PermissionDenied } = require ('./base/errors'); const Precise = require ('./base/Precise'); // --------------------------------------------------------------------------- module.exports = class exmo extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'exmo', 'name': 'EXMO', 'countries': [ 'LT' ], // Lithuania 'rateLimit': 350, // once every 350 ms ≈ 180 requests per minute ≈ 3 requests per second 'version': 'v1.1', 'has': { 'CORS': undefined, 'spot': true, 'margin': undefined, // has but unimplemented 'swap': false, 'future': false, 'option': false, 'cancelOrder': true, 'createOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'fetchBalance': true, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchFundingFees': true, 'fetchFundingHistory': false, 'fetchFundingRate': false, 'fetchFundingRateHistory': false, 'fetchFundingRates': false, 'fetchIndexOHLCV': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': 'emulated', 'fetchOrderBook': true, 'fetchOrderBooks': true, 'fetchOrderTrades': true, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransactions': true, 'fetchTransfer': false, 'fetchTransfers': false, 'fetchWithdrawals': true, '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'), }, 'funding': { 'tierBased': false, 'percentage': false, // fixed funding fees for crypto, see fetchFundingFees below }, }, 'options': { 'networks': { 'ETH': 'ERC20', 'TRX': 'TRC20', }, 'fetchTradingFees': { 'method': 'fetchPrivateTradingFees', // or 'fetchPublicTradingFees' }, }, 'commonCurrencies': { 'GMT': 'GMT Token', }, 'exceptions': { 'exact': { '40005': AuthenticationError, // Authorization error, incorrect signature '40009': InvalidNonce, // '40015': ExchangeError, // API function do not exist '40016': OnMaintenance, // {"result":false,"error":"Error 40016: Maintenance work in progress"} '40017': AuthenticationError, // Wrong API Key '40032': PermissionDenied, // {"result":false,"error":"Error 40032: Access is denied for this API key"} '40033': PermissionDenied, // {"result":false,"error":"Error 40033: Access is denied, this resources are temporarily blocked to user"} '40034': RateLimitExceeded, // {"result":false,"error":"Error 40034: Access is denied, rate limit is exceeded"} '50052': InsufficientFunds, '50054': InsufficientFunds, '50304': OrderNotFound, // "Order was not found '123456789'" (fetching order trades for an order that does not have trades yet) '50173': OrderNotFound, // "Order with id X was not found." (cancelling non-existent, closed and cancelled order) '50277': InvalidOrder, '50319': InvalidOrder, // Price by order is less than permissible minimum for this pair '50321': InvalidOrder, // Price by order is more than permissible maximum for this pair '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 fetchTradingFees (params = {}) { 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 fetchFundingFees (params = {}) { await this.loadMarkets (); 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 = { 'info': cryptoList, 'withdraw': {}, 'deposit': {}, }; for (let i = 0; i < currencyList.length; i++) { const currency = currencyList[i]; const currencyId = this.safeString (currency, 'name'); const code = this.safeCurrencyCode (currencyId); const providers = this.safeValue (cryptoList, currencyId, []); for (let j = 0; j < providers.length; j++) { const provider = providers[j]; const type = this.safeString (provider, 'type'); const commissionDesc = this.safeString (provider, 'commission_desc'); const newFee = this.parseFixedFloatValue (commissionDesc); const previousFee = this.safeNumber (result[type], code); if ((previousFee === undefined) || ((newFee !== undefined) && (newFee < previousFee))) { result[type][code] = newFee; } } } // cache them for later use this.options['fundingFees'] = result; return result; } async fetchCurrencies (params = {}) { // 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 type = this.safeString (provider, 'type'); const minValue = this.safeNumber (provider, 'min'); let maxValue = this.safeNumber (provider, 'max'); if (maxValue === 0.0) { maxValue = undefined; } const activeProvider = this.safeValue (provider, 'enabled'); if (type === 'deposit') { if (activeProvider && !depositEnabled) { depositEnabled = true; } else if (!activeProvider) { depositEnabled = false; } } else if (type === 'withdraw') { if (activeProvider && !withdrawEnabled) { withdrawEnabled = true; } else if (!activeProvider) { withdrawEnabled = false; } } if (activeProvider) { active = true; if ((limits[type]['min'] === undefined) || (minValue < limits[type]['min'])) { limits[type]['min'] = minValue; limits[type]['max'] = maxValue; if (type === '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': 8, 'limits': limits, 'info': providers, }; } return result; } async fetchMarkets (params = {}) { 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': false, 'swap': false, 'future': false, 'option': false, 'active': true, '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': parseInt ('8'), 'price': this.safeInteger (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 = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'resolution': this.timeframes[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) { throw new ArgumentsRequired (this.id + ' fetchOHLCV() requires a since argument or a limit argument'); } else { if (limit > maxLimit) { throw new BadRequest (this.id + ' fetchOHLCV() will serve ' + maxLimit.toString () + ' candles at most'); } request['from'] = parseInt (now / 1000) - limit * duration - 1; request['to'] = parseInt (now / 1000); } } else { request['from'] = parseInt (since / 1000) - 1; if (limit === undefined) { request['to'] = parseInt (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'] = parseInt (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 = {}) { 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 = {}) { 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, symbol, undefined, 'bid', 'ask'); } async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { 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]; let symbol = marketId; if (marketId in this.markets_by_id) { const market = this.markets_by_id[marketId]; symbol = market['symbol']; } 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, false); } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); const response = await this.publicGetTicker (params); // // { // "ADA_BTC":{ // "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 result = {}; const marketIds = Object.keys (response); for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i]; const market = this.safeMarket (marketId, undefined, '_'); const symbol = market['symbol']; const ticker = this.safeValue (response, marketId); result[symbol] = this.parseTicker (ticker, market); } return this.filterByArray (result, 'symbol', symbols); } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const response = await this.publicGetTicker (params); const market = this.market (symbol); return this.parseTicker (response[market['id']], market); } parseTrade (trade, market = undefined) { // // fetchTrades (public) // // { // "trade_id":165087520, // "date":1587470005, // "type":"buy", // "quantity":"1.004", // "price":"0.02491461", // "amount":"0.02501426" // }, // // fetchMyTrades, fetchOrderTrades // // { // "trade_id": 3, // "date": 1435488248, // "type": "buy", // "pair": "BTC_USD", // "order_id": 12345, // "quantity": 1, // "price": 100, // "amount": 100, // "exec_type": "taker", // "commission_amount": "0.02", // "commission_currency": "BTC", // "commission_percent": "0.2" // } // const timestamp = this.safeTimestamp (trade, 'date'); let symbol = undefined; const id = this.safeString (trade, 'trade_id'); const orderId = this.safeString (trade, 'order_id'); const priceString = this.safeString (trade, 'price'); const amountString = this.safeString (trade, 'quantity'); const costString = this.safeString (trade, 'amount'); const side = this.safeString (trade, 'type'); const type = undefined; const marketId = this.safeString (trade, 'pair'); if (marketId !== undefined) { if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; } else { const [ baseId, quoteId ] = marketId.split ('_'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); symbol = base + '/' + quote; } } if ((symbol === undefined) && (market !== undefined)) { symbol = market['symbol']; } const takerOrMaker = this.safeString (trade, 'exec_type'); let fee = undefined; const feeCostString = this.safeString (trade, 'commission_amount'); if (feeCostString !== undefined) { const feeCurrencyId = this.safeString (trade, 'commission_currency'); const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); let feeRateString = this.safeString (trade, 'commission_percent'); if (feeRateString !== undefined) { feeRateString = Precise.stringDiv (feeRateString, '1000', 18); } fee = { 'cost': feeCostString, 'currency': feeCurrencyCode, 'rate': feeRateString, }; } return this.safeTrade ({ 'id': id, 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'order': orderId, 'type': type, 'side': side, 'takerOrMaker': takerOrMaker, 'price': priceString, 'amount': amountString, 'cost': costString, 'fee': fee, }, market); } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'pair': market['id'], }; const response = await this.publicGetTrades (this.extend (request, params)); // // { // "ETH_BTC":[ // { // "trade_id":165087520, // "date":1587470005, // "type":"buy", // "quantity":"1.004", // "price":"0.02491461", // "amount":"0.02501426" // }, // { // "trade_id":165087369, // "date":1587469938, // "type":"buy", // "quantity":"0.94", // "price":"0.02492348", // "amount":"0.02342807" // } // ] // } // const data = this.safeValue (response, market['id'], []); return this.parseTrades (data, market, since, limit); } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { // a symbol is required but it can be a single string, or a non-empty array if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument (a single symbol or an array)'); } await this.loadMarkets (); let pair = undefined; let market = undefined; if (Array.isArray (symbol)) { const numSymbols = symbol.length; if (numSymbols < 1) { throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a non-empty symbol array'); } const marketIds = this.marketIds (symbol); pair = marketIds.join (','); } else { market = this.market (symbol); pair = market['id']; } const request = { 'pair': pair, }; if (limit !== undefined) { request['limit'] = limit; } const response = await this.privatePostUserTrades (this.extend (request, params)); let result = []; const marketIds = Object.keys (response); for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i]; let symbol = undefined; if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; symbol = market['symbol']; } else { const [ baseId, quoteId ] = marketId.split ('_'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); symbol = base + '/' + quote; } const items = response[marketId]; const trades = this.parseTrades (items, market, since, limit, { 'symbol': symbol, }); result = this.arrayConcat (result, trades); } return this.filterBySinceLimit (result, since, limit); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const prefix = (type === 'market') ? (type + '_') : ''; const orderType = prefix + side; let orderPrice = price; if ((type === 'market') && (price === undefined)) { orderPrice = 0; } const request = { 'pair': market['id'], // 'leverage': 2, 'quantity': this.amountToPrecision (symbol, amount), // spot - buy, sell, market_buy, market_sell, market_buy_total, market_sell_total // margin - limit_buy, limit_sell, market_buy, market_sell, stop_buy, stop_sell, stop_limit_buy, stop_limit_sell, trailing_stop_buy, trailing_stop_sell 'type': orderType, 'price': this.priceToPrecision (symbol, orderPrice), // 'stop_price': this.priceToPrecision (symbol, stopPrice), // 'distance': 0, // distance for trailing stop orders // 'expire': 0, // expiration timestamp in UTC timezone for the order, unless expire is 0 // 'client_id': 123, // optional, must be a positive integer // 'comment': '', // up to 50 latin symbols, whitespaces, underscores }; let method = 'privatePostOrderCreate'; let clientOrderId = this.safeValue2 (params, 'client_id', 'clientOrderId'); if (clientOrderId !== undefined) { clientOrderId = this.safeInteger2 (params, 'client_id', 'clientOrderId'); if (clientOrderId === undefined) { throw new BadRequest (this.id + ' createOrder() client order id must be an integer / numeric literal'); } else { request['client_id'] = clientOrderId; } params = this.omit (params, [ 'client_id', 'clientOrderId' ]); } if ((type === 'stop') || (type === 'stop_limit') || (type === 'trailing_stop')) { const stopPrice = this.safeNumber2 (params, 'stop_price', 'stopPrice'); if (stopPrice === undefined) { throw new InvalidOrder (this.id + ' createOrder() requires a stopPrice extra param for a ' + type + ' order'); } else { params = this.omit (params, [ 'stopPrice', 'stop_price' ]); request['stop_price'] = this.priceToPrecision (symbol, stopPrice); method = 'privatePostMarginUserOrderCreate'; } } const response = await this[method] (this.extend (request, params)); const id = this.safeString (response, 'order_id'); const timestamp = this.milliseconds (); const status = 'open';