ccxt-look
Version:
1,054 lines (1,030 loc) • 73.2 kB
JavaScript
'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';