Files
Superalgos/TaskServer/node_modules/ccxt/js/exmo.js
T
2020-02-09 16:35:17 +01:00

1414 lines
76 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ArgumentsRequired, ExchangeError, OrderNotFound, AuthenticationError, InsufficientFunds, InvalidOrder, InvalidNonce, NotSupported, OnMaintenance, RateLimitExceeded } = require ('./base/errors');
// ---------------------------------------------------------------------------
module.exports = class exmo extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'exmo',
'name': 'EXMO',
'countries': [ 'ES', 'RU' ], // Spain, Russia
'rateLimit': 350, // once every 350 ms ≈ 180 requests per minute ≈ 3 requests per second
'version': 'v1',
'has': {
'CORS': false,
'fetchClosedOrders': 'emulated',
'fetchDepositAddress': true,
'fetchOpenOrders': true,
'fetchOrder': 'emulated',
'fetchOrders': 'emulated',
'fetchOrderTrades': true,
'fetchOrderBooks': true,
'fetchMyTrades': true,
'fetchTickers': true,
'withdraw': true,
'fetchTradingFee': true,
'fetchTradingFees': true,
'fetchFundingFees': true,
'fetchCurrencies': true,
'fetchTransactions': true,
},
'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',
'https://github.com/exmo-dev/exmo_api_lib/tree/master/nodejs',
],
'fees': 'https://exmo.com/en/docs/fees',
},
'api': {
'web': {
'get': [
'ctrl/feesAndLimits',
'en/docs/fees',
],
},
'public': {
'get': [
'currency',
'order_book',
'pair_settings',
'ticker',
'trades',
],
},
'private': {
'post': [
'user_info',
'order_create',
'order_cancel',
'user_open_orders',
'user_trades',
'user_cancelled_orders',
'order_trades',
'required_amount',
'deposit_address',
'withdraw_crypt',
'withdraw_get_txid',
'excode_create',
'excode_load',
'wallet_history',
],
},
},
'fees': {
'trading': {
'tierBased': false,
'percentage': true,
'maker': 0.2 / 100,
'taker': 0.2 / 100,
},
'funding': {
'tierBased': false,
'percentage': false, // fixed funding fees for crypto, see fetchFundingFees below
},
},
'options': {
'useWebapiForFetchingFees': false, // TODO: figure why Exmo bans us when we try to fetch() their web urls
'feesAndLimits': {
'success': 1,
'ctlr': 'feesAndLimits',
'error': '',
'data': {
'limits': [
{ 'pair': 'BTC/USD', 'min_q': '0.001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'BTC/EUR', 'min_q': '0.001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'BTC/RUB', 'min_q': '0.001', 'max_q': '1000', 'min_p': '1', 'max_p': '2000000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'BTC/UAH', 'min_q': '0.001', 'max_q': '1000', 'min_p': '1', 'max_p': '1500000', 'min_a': '10', 'max_a': '15000000' },
{ 'pair': 'BTC/PLN', 'min_q': '0.001', 'max_q': '1000', 'min_p': '0.001', 'max_p': '90000', 'min_a': '1', 'max_a': '2000000' },
{ 'pair': 'BTC/TRY', 'min_q': '0.001', 'max_q': '1000', 'min_p': '1', 'max_p': '800000', 'min_a': '40', 'max_a': '6000000' },
{ 'pair': 'ROOBEE/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.0001', 'max_a': '10' },
{ 'pair': 'ROOBEE/ETH', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'USDC/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'USDC/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '1000' },
{ 'pair': 'USDC/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'USDC/USDT', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'ETZ/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '10' },
{ 'pair': 'ETZ/ETH', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ETZ/USDT', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '1000' },
{ 'pair': 'PTI/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '10' },
{ 'pair': 'PTI/USDT', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000' },
{ 'pair': 'PTI/EOS', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.0000001', 'max_p': '5000', 'min_a': '0.01', 'max_a': '20000' },
{ 'pair': 'ATMCASH/BTC', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '10' },
{ 'pair': 'TRX/UAH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000' },
{ 'pair': 'ETH/TRY', 'min_q': '0.01', 'max_q': '5000', 'min_p': '0.1', 'max_p': '80000', 'min_a': '10', 'max_a': '6000000' },
{ 'pair': 'XRP/TRY', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '6000000' },
{ 'pair': 'XLM/TRY', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '6000000' },
{ 'pair': 'MNC/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100' },
{ 'pair': 'MNC/ETH', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '1000' },
{ 'pair': 'MNC/USD', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000' },
{ 'pair': 'DAI/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'DAI/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.000001', 'max_p': '10', 'min_a': '0.0001', 'max_a': '5000' },
{ 'pair': 'DAI/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'DAI/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '0.5', 'max_a': '30000000' },
{ 'pair': 'MKR/BTC', 'min_q': '0.001', 'max_q': '1000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.000001', 'max_a': '100' },
{ 'pair': 'MKR/DAI', 'min_q': '0.001', 'max_q': '1000', 'min_p': '0.5', 'max_p': '500000', 'min_a': '0.005', 'max_a': '500000' },
{ 'pair': 'QTUM/BTC', 'min_q': '0.1', 'max_q': '200000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'QTUM/ETH', 'min_q': '0.1', 'max_q': '200000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000' },
{ 'pair': 'QTUM/USD', 'min_q': '0.1', 'max_q': '200000', 'min_p': '0.00000001', 'max_p': '10000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'HB/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100' },
{ 'pair': 'SMART/BTC', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'SMART/USD', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'SMART/EUR', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'SMART/RUB', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'XEM/BTC', 'min_q': '10', 'max_q': '5000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00015', 'max_a': '100' },
{ 'pair': 'XEM/USD', 'min_q': '10', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'XEM/EUR', 'min_q': '10', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'GUSD/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100' },
{ 'pair': 'GUSD/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'GUSD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'LSK/BTC', 'min_q': '1', 'max_q': '200000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100' },
{ 'pair': 'LSK/USD', 'min_q': '1', 'max_q': '200000', 'min_p': '0.1', 'max_p': '1000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'LSK/RUB', 'min_q': '1', 'max_q': '200000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '50000000' },
{ 'pair': 'NEO/BTC', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'NEO/USD', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'NEO/RUB', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.001', 'max_p': '1500000', 'min_a': '50', 'max_a': '50000000' },
{ 'pair': 'ADA/BTC', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ADA/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'ADA/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '5000' },
{ 'pair': 'ZRX/BTC', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ZRX/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'GNT/BTC', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'GNT/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'TRX/BTC', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'TRX/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'TRX/RUB', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000' },
{ 'pair': 'GAS/BTC', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'GAS/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'INK/BTC', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'INK/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '5000' },
{ 'pair': 'INK/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'MNX/BTC', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'MNX/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'MNX/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'OMG/BTC', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'OMG/ETH', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'OMG/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'XLM/BTC', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'XLM/USD', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'XLM/RUB', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000' },
{ 'pair': 'EOS/BTC', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'EOS/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'BTG/BTC', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'BTG/USD', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'DXT/BTC', 'min_q': '100', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'DXT/USD', 'min_q': '100', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'BTCZ/BTC', 'min_q': '100', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.1', 'max_a': '100' },
{ 'pair': 'BCH/BTC', 'min_q': '0.003', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '5', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'BCH/USD', 'min_q': '0.003', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '30000', 'min_a': '0.0001', 'max_a': '500000' },
{ 'pair': 'BCH/RUB', 'min_q': '0.003', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '2000000', 'min_a': '0.0001', 'max_a': '50000000' },
{ 'pair': 'BCH/ETH', 'min_q': '0.003', 'max_q': '10000', 'min_p': '0.0000001', 'max_p': '200', 'min_a': '0.0001', 'max_a': '5000' },
{ 'pair': 'DASH/BTC', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'DASH/USD', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'DASH/RUB', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000' },
{ 'pair': 'ETH/BTC', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ETH/LTC', 'min_q': '0.01', 'max_q': '5000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '100000' },
{ 'pair': 'ETH/USD', 'min_q': '0.01', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'ETH/EUR', 'min_q': '0.01', 'max_q': '5000', 'min_p': '0.001', 'max_p': '10000', 'min_a': '1', 'max_a': '500000' },
{ 'pair': 'ETH/RUB', 'min_q': '0.01', 'max_q': '5000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '1', 'max_a': '50000000' },
{ 'pair': 'ETH/UAH', 'min_q': '0.01', 'max_q': '5000', 'min_p': '0.01', 'max_p': '1000000', 'min_a': '90', 'max_a': '15000000' },
{ 'pair': 'ETH/PLN', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.001', 'max_p': '8000', 'min_a': '1', 'max_a': '2000000' },
{ 'pair': 'ETC/BTC', 'min_q': '0.2', 'max_q': '1000', 'min_p': '0.0001', 'max_p': '0.5', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ETC/USD', 'min_q': '0.2', 'max_q': '1000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'ETC/RUB', 'min_q': '0.2', 'max_q': '1000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '50000000' },
{ 'pair': 'LTC/BTC', 'min_q': '0.05', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'LTC/USD', 'min_q': '0.05', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'LTC/EUR', 'min_q': '0.05', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'LTC/RUB', 'min_q': '0.05', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000' },
{ 'pair': 'ZEC/BTC', 'min_q': '0.01', 'max_q': '1000', 'min_p': '0.001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'ZEC/USD', 'min_q': '0.01', 'max_q': '1000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'ZEC/EUR', 'min_q': '0.01', 'max_q': '1000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'ZEC/RUB', 'min_q': '0.01', 'max_q': '1000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000' },
{ 'pair': 'XRP/BTC', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'XRP/USD', 'min_q': '1', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000' },
{ 'pair': 'XRP/RUB', 'min_q': '1', 'max_q': '100000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '50000000' },
{ 'pair': 'XMR/BTC', 'min_q': '0.03', 'max_q': '1000', 'min_p': '0.001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'XMR/USD', 'min_q': '0.03', 'max_q': '1000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'XMR/EUR', 'min_q': '0.03', 'max_q': '1000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'BTC/USDT', 'min_q': '0.001', 'max_q': '1000', 'min_p': '0.01', 'max_p': '30000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'ETH/USDT', 'min_q': '0.01', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'USDT/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.5', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'USDT/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'USD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000' },
{ 'pair': 'DOGE/BTC', 'min_q': '100', 'max_q': '100000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'WAVES/BTC', 'min_q': '0.5', 'max_q': '10000', 'min_p': '0.0001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100' },
{ 'pair': 'WAVES/RUB', 'min_q': '0.5', 'max_q': '10000', 'min_p': '1', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000' },
{ 'pair': 'KICK/BTC', 'min_q': '100', 'max_q': '10000000', 'min_p': '0.0000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'KICK/ETH', 'min_q': '100', 'max_q': '10000000', 'min_p': '0.000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '5000' },
{ 'pair': 'KICK/USDT', 'min_q': '100', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000' },
{ 'pair': 'EOS/EUR', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'BCH/EUR', 'min_q': '0.003', 'max_q': '100000', 'min_p': '0.01', 'max_p': '300000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'XRP/EUR', 'min_q': '1', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000' },
{ 'pair': 'XRP/UAH', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '15000000' },
{ 'pair': 'XEM/UAH', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000' },
{ 'pair': 'BCH/USDT', 'min_q': '0.003', 'max_q': '100000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'DASH/USDT', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000' },
{ 'pair': 'BCH/UAH', 'min_q': '0.003', 'max_q': '100000', 'min_p': '0.1', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000' },
{ 'pair': 'XRP/USDT', 'min_q': '1', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000' },
{ 'pair': 'USDT/UAH', 'min_q': '0.01', 'max_q': '100000', 'min_p': '1', 'max_p': '3000', 'min_a': '2', 'max_a': '15000000' },
{ 'pair': 'USDT/EUR', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'ZRX/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000' },
{ 'pair': 'BTG/ETH', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.01', 'max_a': '5000' },
{ 'pair': 'WAVES/USD', 'min_q': '0.5', 'max_q': '10000', 'min_p': '0.001', 'max_p': '3500', 'min_a': '0.5', 'max_a': '500000' },
{ 'pair': 'DOGE/USD', 'min_q': '100', 'max_q': '1000000000', 'min_p': '0.0000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000' },
{ 'pair': 'XRP/ETH', 'min_q': '1', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '5000' },
{ 'pair': 'DASH/UAH', 'min_q': '0.01', 'max_q': '200000', 'min_p': '0.01', 'max_p': '200000', 'min_a': '10', 'max_a': '15000000' },
{ 'pair': 'XMR/ETH', 'min_q': '0.03', 'max_q': '1000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000' },
{ 'pair': 'WAVES/ETH', 'min_q': '0.5', 'max_q': '10000', 'min_p': '0.00001', 'max_p': '30', 'min_a': '0.0035', 'max_a': '3500' },
{ 'pair': 'DCR/BTC', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100' },
{ 'pair': 'DCR/RUB', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '3000000' },
{ 'pair': 'DCR/UAH', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.25', 'max_a': '1000000' },
{ 'pair': 'ZAG/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'EXM/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0000001', 'max_a': '100' },
{ 'pair': 'VLX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'BTT/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100' },
{ 'pair': 'BTT/RUB', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100' },
{ 'pair': 'BTT/UAH', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100' },
],
'fees': [
{
'group': 'crypto',
'title': 'Криптовалюта',
'items': [
{ 'prov': 'EXM', 'dep': '0%', 'wd': '1 EXM' },
{ 'prov': 'BTC', 'dep': '0%', 'wd': '0.0005 BTC' },
{ 'prov': 'LTC', 'dep': '0%', 'wd': '0.01 LTC' },
{ 'prov': 'DOGE', 'dep': '0%', 'wd': '1 DOGE' },
{ 'prov': 'DASH', 'dep': '0%', 'wd': '0.01 DASH' },
{ 'prov': 'ETH', 'dep': '0%', 'wd': '0.01 ETH' },
{ 'prov': 'WAVES', 'dep': '0%', 'wd': '0.001 WAVES' },
{ 'prov': 'ZEC', 'dep': '0%', 'wd': '0.001 ZEC' },
{ 'prov': 'USDT', 'dep': '0%', 'wd': '5 USDT' },
{ 'prov': 'XMR', 'dep': '0%', 'wd': '0.05 XMR' },
{ 'prov': 'XRP', 'dep': '0%', 'wd': '0.02 XRP' },
{ 'prov': 'KICK', 'dep': '0%', 'wd': '50 KICK' },
{ 'prov': 'ETC', 'dep': '0%', 'wd': '0.01 ETC' },
{ 'prov': 'BCH', 'dep': '0%', 'wd': '0.001 BCH' },
{ 'prov': 'BTG', 'dep': '0%', 'wd': '0.001 BTG' },
{ 'prov': 'EOS', 'dep': '0%', 'wd': '0.05 EOS' },
{ 'prov': 'BTCZ', 'dep': '0%', 'wd': '5 BTCZ' },
{ 'prov': 'DXT', 'dep': '20 DXT', 'wd': '20 DXT' },
{ 'prov': 'XLM', 'dep': '0%', 'wd': '0.01 XLM' },
{ 'prov': 'MNX', 'dep': '0%', 'wd': '0.01 MNX' },
{ 'prov': 'OMG', 'dep': '0.1 OMG', 'wd': '0.5 OMG' },
{ 'prov': 'TRX', 'dep': '0%', 'wd': '1 TRX' },
{ 'prov': 'ADA', 'dep': '0%', 'wd': '1 ADA' },
{ 'prov': 'INK', 'dep': '10 INK', 'wd': '50 INK' },
{ 'prov': 'NEO', 'dep': '0%', 'wd': '0%' },
{ 'prov': 'GAS', 'dep': '0%', 'wd': '0%' },
{ 'prov': 'ZRX', 'dep': '0%', 'wd': '1 ZRX' },
{ 'prov': 'GNT', 'dep': '0%', 'wd': '1 GNT' },
{ 'prov': 'GUSD', 'dep': '0%', 'wd': '0.5 GUSD' },
{ 'prov': 'LSK', 'dep': '0%', 'wd': '0.1 LSK' },
{ 'prov': 'XEM', 'dep': '0%', 'wd': '5 XEM' },
{ 'prov': 'SMART', 'dep': '0%', 'wd': '0.5 SMART' },
{ 'prov': 'QTUM', 'dep': '0%', 'wd': '0.01 QTUM' },
{ 'prov': 'HB', 'dep': '0%', 'wd': '10 HB' },
{ 'prov': 'DAI', 'dep': '0%', 'wd': '1 DAI' },
{ 'prov': 'MKR', 'dep': '0%', 'wd': '0.005 MKR' },
{ 'prov': 'MNC', 'dep': '0%', 'wd': '15 MNC' },
{ 'prov': 'PTI', 'dep': '0%', 'wd': '10 PTI' },
{ 'prov': 'ATMCASH', 'dep': '0%', 'wd': '5 ATMCASH' },
{ 'prov': 'ETZ', 'dep': '0%', 'wd': '1 ETZ' },
{ 'prov': 'USDC', 'dep': '0%', 'wd': '0.5 USDC' },
{ 'prov': 'ROOBEE', 'dep': '0%', 'wd': '200 ROOBEE' },
{ 'prov': 'DCR', 'dep': '0%', 'wd': '0.01 DCR' },
{ 'prov': 'ZAG', 'dep': '0%', 'wd': '0%' },
{ 'prov': 'BTT', 'dep': '0%', 'wd': '100 BTT' },
{ 'prov': 'VLX', 'dep': '0%', 'wd': '1 VLX' },
],
},
{
'group': 'usd',
'title': 'USD',
'items': [
{ 'prov': 'Payeer', 'dep': '3.95%', 'wd': '-' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
{ 'prov': 'Perfect Money', 'dep': '-', 'wd': '0.5%' },
{ 'prov': 'AdvCash', 'dep': '3.45%', 'wd': '2.95%' },
{ 'prov': 'Visa', 'dep': '3.45%', 'wd': '-' },
{ 'prov': 'CryptoCapital', 'dep': '-', 'wd': '0.45%' },
{ 'prov': 'Visa/MasterCard (Simplex)', 'dep': '4.5%', 'wd': '-' },
],
},
{
'group': 'eur',
'title': 'EUR',
'items': [
{ 'prov': 'Visa', 'dep': '3.45%', 'wd': '-' },
{ 'prov': 'CryptoCapital', 'dep': '-', 'wd': '0.45%' },
{ 'prov': 'AdvCash', 'dep': '0.45%', 'wd': '-' },
{ 'prov': 'Wire Transfer', 'dep': '6.95 EUR', 'wd': '-' },
{ 'prov': 'SEPA', 'dep': '6.95 EUR', 'wd': '-' },
{ 'prov': 'Visa/MasterCard (Simplex)', 'dep': '4.5%', 'wd': '-' },
{ 'prov': 'Payeer', 'dep': '3.95%', 'wd': '-' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
],
},
{
'group': 'rub',
'title': 'RUB',
'items': [
{ 'prov': 'Yandex Money', 'dep': '2.95%', 'wd': '1.95%' },
{ 'prov': 'Payeer', 'dep': '3.95%', 'wd': '-' },
{ 'prov': 'AdvCash', 'dep': '3.95%', 'wd': '0.95%' },
{ 'prov': 'Qiwi', 'dep': '3.95%', 'wd': '2.95%' },
{ 'prov': 'Visa/MasterCard', 'dep': '3.95%', 'wd': '4.95% + 60 RUB' },
{ 'prov': 'WebMoney', 'dep': '-', 'wd': '3.5%' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
],
},
{
'group': 'pln',
'title': 'PLN',
'items': [
{ 'prov': 'CryptoCapital', 'dep': '-', 'wd': '0.45%' },
{ 'prov': 'Visa/MasterCard (Simplex)', 'dep': '4.5%', 'wd': '-' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
],
},
{
'group': 'try',
'title': 'TRY',
'items': [
{ 'prov': 'QR ile yatırma', 'dep': '5.95%', 'wd': '-' },
{ 'prov': 'Visa', 'dep': '3.45%', 'wd': '-' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
],
},
{
'group': 'uah',
'title': 'UAH',
'items': [
{ 'prov': 'Terminal', 'dep': '2.6%', 'wd': '-' },
{ 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' },
{ 'prov': 'AdvCash', 'dep': '-', 'wd': '2.95%' },
{ 'prov': 'Visa/MasterCard', 'dep': '2.6%', 'wd': '3.95% + 20 UAH' },
{ 'prov': 'Enfins', 'dep': '0%', 'wd': '1.95%' },
],
},
],
},
},
},
'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
'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)
'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
},
'broad': {
'API rate limit exceeded': RateLimitExceeded, // {"result":false,"error":"API rate limit exceeded for 99.33.55.224. Retry after 60 sec.","history":[],"begin":1579392000,"end":1579478400}
},
},
});
}
async fetchTradingFees (params = {}) {
if (this.options['useWebapiForFetchingFees']) {
const response = await this.webGetEnDocsFees (params);
let parts = response.split ('<td class="th_fees_2" colspan="2">');
let numParts = parts.length;
if (numParts !== 2) {
throw new NotSupported (this.id + ' fetchTradingFees format has changed');
}
const rest = parts[1];
parts = rest.split ('</td>');
numParts = parts.length;
if (numParts < 2) {
throw new NotSupported (this.id + ' fetchTradingFees format has changed');
}
const fee = parseFloat (parts[0].replace ('%', '')) * 0.01;
const taker = fee;
const maker = fee;
return {
// 'info': response,
'maker': maker,
'taker': taker,
};
} else {
return {
'maker': this.fees['trading']['maker'],
'taker': this.fees['trading']['taker'],
};
}
}
parseFixedFloatValue (input) {
if ((input === undefined) || (input === '-')) {
return undefined;
}
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 = {}) {
let response = undefined;
if (this.options['useWebapiForFetchingFees']) {
response = await this.webGetCtrlFeesAndLimits (params);
} else {
response = this.options['feesAndLimits'];
}
// the code below assumes all non-zero crypto fees are fixed (for now)
const withdraw = {};
const deposit = {};
const groups = this.safeValue (response['data'], 'fees');
const groupsByGroup = this.indexBy (groups, 'group');
const items = groupsByGroup['crypto']['items'];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const code = this.safeCurrencyCode (this.safeString (item, 'prov'));
const withdrawalFee = this.safeString (item, 'wd');
const depositFee = this.safeString (item, 'dep');
if (withdrawalFee !== undefined) {
if (withdrawalFee.length > 0) {
withdraw[code] = this.parseFixedFloatValue (withdrawalFee);
}
}
if (depositFee !== undefined) {
if (depositFee.length > 0) {
deposit[code] = this.parseFixedFloatValue (depositFee);
}
}
}
// sets fiat fees to undefined
const fiatGroups = this.toArray (this.omit (groupsByGroup, 'crypto'));
for (let i = 0; i < fiatGroups.length; i++) {
const code = this.safeCurrencyCode (this.safeString (fiatGroups[i], 'title'));
withdraw[code] = undefined;
deposit[code] = undefined;
}
const result = {
'info': response,
'withdraw': withdraw,
'deposit': deposit,
};
// cache them for later use
this.options['fundingFees'] = result;
return result;
}
async fetchCurrencies (params = {}) {
const fees = await this.fetchFundingFees (params);
// todo redesign the 'fee' property in currencies
const ids = Object.keys (fees['withdraw']);
const limitsByMarketId = this.indexBy (fees['info']['data']['limits'], 'pair');
const marketIds = Object.keys (limitsByMarketId);
const minAmounts = {};
const minPrices = {};
const minCosts = {};
const maxAmounts = {};
const maxPrices = {};
const maxCosts = {};
for (let i = 0; i < marketIds.length; i++) {
const marketId = marketIds[i];
const limit = limitsByMarketId[marketId];
const [ baseId, quoteId ] = marketId.split ('/');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
const maxAmount = this.safeFloat (limit, 'max_q');
const maxPrice = this.safeFloat (limit, 'max_p');
const maxCost = this.safeFloat (limit, 'max_a');
const minAmount = this.safeFloat (limit, 'min_q');
const minPrice = this.safeFloat (limit, 'min_p');
const minCost = this.safeFloat (limit, 'min_a');
minAmounts[base] = Math.min (this.safeFloat (minAmounts, base, minAmount), minAmount);
maxAmounts[base] = Math.max (this.safeFloat (maxAmounts, base, maxAmount), maxAmount);
minPrices[quote] = Math.min (this.safeFloat (minPrices, quote, minPrice), minPrice);
minCosts[quote] = Math.min (this.safeFloat (minCosts, quote, minCost), minCost);
maxPrices[quote] = Math.max (this.safeFloat (maxPrices, quote, maxPrice), maxPrice);
maxCosts[quote] = Math.max (this.safeFloat (maxCosts, quote, maxCost), maxCost);
}
const result = {};
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
const code = this.safeCurrencyCode (id);
const fee = this.safeValue (fees['withdraw'], code);
const active = true;
result[code] = {
'id': id,
'code': code,
'name': code,
'active': active,
'fee': fee,
'precision': 8,
'limits': {
'amount': {
'min': this.safeFloat (minAmounts, code),
'max': this.safeFloat (maxAmounts, code),
},
'price': {
'min': this.safeFloat (minPrices, code),
'max': this.safeFloat (maxPrices, code),
},
'cost': {
'min': this.safeFloat (minCosts, code),
'max': this.safeFloat (maxCosts, code),
},
},
'info': id,
};
}
return result;
}
async fetchMarkets (params = {}) {
const fees = await this.fetchTradingFees ();
const response = await this.publicGetPairSettings (params);
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);
result.push ({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'baseId': baseId,
'quoteId': quoteId,
'active': true,
'taker': fees['taker'],
'maker': fees['maker'],
'limits': {
'amount': {
'min': this.safeFloat (market, 'min_quantity'),
'max': this.safeFloat (market, 'max_quantity'),
},
'price': {
'min': this.safeFloat (market, 'min_price'),
'max': this.safeFloat (market, 'max_price'),
},
'cost': {
'min': this.safeFloat (market, 'min_amount'),
'max': this.safeFloat (market, 'max_amount'),
},
},
'precision': {
'amount': 8,
'price': 8,
},
'info': market,
});
}
return result;
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
const response = await this.privatePostUserInfo (params);
const result = { 'info': response };
const codes = Object.keys (this.currencies);
for (let i = 0; i < codes.length; i++) {
const code = codes[i];
const currencyId = this.currencyId (code);
const account = this.account ();
if (currencyId in response['balances']) {
account['free'] = this.safeFloat (response['balances'], currencyId);
}
if (currencyId in response['reserved']) {
account['used'] = this.safeFloat (response['reserved'], currencyId);
}
result[code] = account;
}
return this.parseBalance (result);
}
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, 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 + ' 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], undefined, 'bid', 'ask');
}
return result;
}
parseTicker (ticker, market = undefined) {
const timestamp = this.safeTimestamp (ticker, 'updated');
let symbol = undefined;
if (market !== undefined) {
symbol = market['symbol'];
}
const last = this.safeFloat (ticker, 'last_trade');
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': this.safeFloat (ticker, 'high'),
'low': this.safeFloat (ticker, 'low'),
'bid': this.safeFloat (ticker, 'buy_price'),
'bidVolume': undefined,
'ask': this.safeFloat (ticker, 'sell_price'),
'askVolume': undefined,
'vwap': undefined,
'open': undefined,
'close': last,
'last': last,
'previousClose': undefined,
'change': undefined,
'percentage': undefined,
'average': this.safeFloat (ticker, 'avg'),
'baseVolume': this.safeFloat (ticker, 'vol'),
'quoteVolume': this.safeFloat (ticker, 'vol_curr'),
'info': ticker,
};
}
async fetchTickers (symbols = undefined, params = {}) {
await this.loadMarkets ();
const response = await this.publicGetTicker (params);
const result = {};
const ids = Object.keys (response);
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
const market = this.markets_by_id[id];
const symbol = market['symbol'];
const ticker = response[id];
result[symbol] = this.parseTicker (ticker, market);
}
return result;
}
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) {
const timestamp = this.safeTimestamp (trade, 'date');
let fee = undefined;
let symbol = undefined;
const id = this.safeString (trade, 'trade_id');
const orderId = this.safeString (trade, 'order_id');
const price = this.safeFloat (trade, 'price');
const amount = this.safeFloat (trade, 'quantity');
const cost = this.safeFloat (trade, 'amount');
const side = this.safeString (trade, 'type');
const type = undefined;
if (market !== undefined) {
symbol = market['symbol'];
if (market['taker'] !== market['maker']) {
throw new ExchangeError (this.id + ' parseTrade can not deduce proper fee costs, taker and maker fees now differ');
}
if ((side === 'buy') && (amount !== undefined)) {
fee = {
'currency': market['base'],
'cost': amount * market['taker'],
'rate': market['taker'],
};
} else if ((side === 'sell') && (cost !== undefined)) {
fee = {
'currency': market['quote'],
'cost': cost * market['taker'],
'rate': market['taker'],
};
}
}
return {
'id': id,
'info': trade,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'symbol': symbol,
'order': orderId,
'type': type,
'side': side,
'takerOrMaker': undefined,
'price': price,
'amount': amount,
'cost': cost,
'fee': fee,
};
}
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));
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 prefix = (type === 'market') ? (type + '_') : '';
const market = this.market (symbol);
if ((type === 'market') && (price === undefined)) {
price = 0;
}
const request = {
'pair': market['id'],
'quantity': this.amountToPrecision (symbol, amount),
'type': prefix + side,
'price': this.priceToPrecision (symbol, price),
};
const response = await this.privatePostOrderCreate (this.extend (request, params));
const id = this.safeString (response, 'order_id');
const timestamp = this.milliseconds ();
amount = parseFloat (amount);
price = parseFloat (price);
const status = 'open';
const order = {
'id': id,
'info': response,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': undefined,
'status': status,
'symbol': symbol,
'type': type,
'side': side,
'price': price,
'cost': price * amount,
'amount': amount,
'remaining': amount,
'filled': 0.0,
'fee': undefined,
'trades': undefined,
};
this.orders[id] = order;
return order;
}
async cancelOrder (id, symbol = undefined, params = {}) {
await this.loadMarkets ();
const request = { 'order_id': id };
const response = await this.privatePostOrderCancel (this.extend (request, params));
if (id in this.orders) {
this.orders[id]['status'] = 'canceled';
}
return response;
}
async fetchOrder (id, symbol = undefined, params = {}) {
await this.loadMarkets ();
try {
const request = {
'order_id': id.toString (),
};
const response = await this.privatePostOrderTrades (this.extend (request, params));
//
// {
// "type": "buy",
// "in_currency": "BTC",
// "in_amount": "1",
// "out_currency": "USD",
// "out_amount": "100",
// "trades": [
// {
// "trade_id": 3,
// "date": 1435488248,
// "type": "buy",
// "pair": "BTC_USD",
// "order_id": 12345,
// "quantity": 1,
// "price": 100,
// "amount": 100
// }
// ]
// }
//
const order = this.parseOrder (response);
return this.extend (order, {
'id': id.toString (),
});
} catch (e) {
if (e instanceof OrderNotFound) {
if (id in this.orders) {
return this.orders[id];
}
}
}
throw new OrderNotFound (this.id + ' fetchOrder order id ' + id.toString () + ' not found in cache.');
}
async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) {
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
}
const request = {
'order_id': id.toString (),
};
const response = await this.privatePostOrderTrades (this.extend (request, params));
const trades = this.safeValue (response, 'trades');
return this.parseTrades (trades, market, since, limit);
}
updateCachedOrders (openOrders, symbol) {
// update local cache with open orders
for (let j = 0; j < openOrders.length; j++) {
const id = openOrders[j]['id'];
this.orders[id] = openOrders[j];
}
const openOrdersIndexedById = this.indexBy (openOrders, 'id');
const cachedOrderIds = Object.keys (this.orders);
for (let k = 0; k < cachedOrderIds.length; k++) {
// match each cached order to an order in the open orders array
// possible reasons why a cached order may be missing in the open orders array:
// - order was closed or canceled -> update cache
// - symbol mismatch (e.g. cached BTC/USDT, fetched ETH/USDT) -> skip
const id = cachedOrderIds[k];
let order = this.orders[id];
if (!(id in openOrdersIndexedById)) {
// cached order is not in open orders array
// if we fetched orders by symbol and it doesn't match the cached order -> won't update the cached order
if (symbol !== undefined && symbol !== order['symbol']) {
continue;
}
// order is cached but not present in the list of open orders -> mark the cached order as closed
if (order['status'] === 'open') {
order = this.extend (order, {
'status': 'closed', // likewise it might have been canceled externally (unnoticed by "us")
'cost': undefined,
'filled': order['amount'],
'remaining': 0.0,
});
if (order['cost'] === undefined) {
if (order['filled'] !== undefined) {
order['cost'] = order['filled'] * order['price'];
}
}
this.orders[id] = order;
}
}
}
return this.toArray (this.orders);
}
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const response = await this.privatePostUserOpenOrders (params);
const marketIds = Object.keys (response);
let orders = [];
for (let i = 0; i < marketIds.length; i++) {
const marketId = marketIds[i];
let market = undefined;
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
}
const parsedOrders = this.parseOrders (response[marketId], market);
orders = this.arrayConcat (orders, parsedOrders);
}
this.updateCachedOrders (orders, symbol);
return this.filterBySymbolSinceLimit (this.toArray (this.orders), symbol, since, limit);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.fetchOrders (symbol, since, limit, params);
const orders = this.filterBy (this.orders, 'status', 'open');
return this.filterBySymbolSinceLimit (orders, symbol, since, limit);
}
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.fetchOrders (symbol, since, limit, params);
const orders = this.filterBy (this.orders, 'status', 'closed');
return this.filterBySymbolSinceLimit (orders, symbol, since, limit);
}
parseOrder (order, market = undefined) {
//
// fetchOrders, fetchOpenOrders, fetchClosedOrders
//
// {
// "order_id": "14",
// "created": "1435517311",
// "type": "buy",
// "pair": "BTC_USD",
// "price": "100",
// "quantity": "1",
// "amount": "100"
// }
//
// fetchOrder
//
// {
// "type": "buy",
// "in_currency": "BTC",
// "in_amount": "1",
// "out_currency": "USD",
// "out_amount": "100",
// "trades": [
// {
// "trade_id": 3,
// "date": 1435488248,
// "type": "buy",
// "pair": "BTC_USD",
// "order_id": 12345,
// "quantity": 1,
// "price": 100,
// "amount": 100
// }
// ]
// }
//
let id = this.safeString (order, 'order_id');
let timestamp = this.safeTimestamp (order, 'created');
let symbol = undefined;
const side = this.safeString (order, 'type');
if (market === undefined) {
let marketId = undefined;
if ('pair' in order) {
marketId = order['pair'];
} else if (('in_currency' in order) && ('out_currency' in order)) {
if (side === 'buy') {
marketId = order['in_currency'] + '_' + order['out_currency'];
} else {
marketId = order['out_currency'] + '_' + order['in_currency'];
}
}
if ((marketId !== undefined) && (marketId in this.markets_by_id)) {
market = this.markets_by_id[marketId];
}
}
let amount = this.safeFloat (order, 'quantity');
if (amount === undefined) {
const amountField = (side === 'buy') ? 'in_amount' : 'out_amount';
amount = this.safeFloat (order, amountField);
}
let price = this.safeFloat (order, 'price');
let cost = this.safeFloat (order, 'amount');
let filled = 0.0;
const trades = [];
const transactions = this.safeValue (order, 'trades', []);
let feeCost = undefined;
let lastTradeTimestamp = undefined;
let average = undefined;
const numTransactions = transactions.length;
if (numTransactions > 0) {
feeCost = 0;
for (let i = 0; i < numTransactions; i++) {
const trade = this.parseTrade (transactions[i], market);
if (id === undefined) {
id = trade['order'];
}
if (timestamp === undefined) {
timestamp = trade['timestamp'];
}
if (timestamp > trade['timestamp']) {
timestamp = trade['timestamp'];
}
filled = this.sum (filled, trade['amount']);
feeCost = this.sum (feeCost, trade['fee']['cost']);
trades.push (trade);
}
lastTradeTimestamp = trades[numTransactions - 1]['timestamp'];
}
let remaining = undefined;
if (amount !== undefined) {
remaining = amount - filled;
}
let status = this.safeString (order, 'status'); // in case we need to redefine it for canceled orders
if (filled >= amount) {
status = 'closed';
} else {
status = 'open';
}
if (market === undefined) {
market = this.getMarketFromTrades (trades);
}
let feeCurrency = undefined;
if (market !== undefined) {
symbol = market['symbol'];
feeCurrency = market['quote'];
}
if (cost === undefined) {
if (price !== undefined) {
cost = price * filled;
}
} else {
if (filled > 0) {
if (average === undefined) {
average = cost / filled;
}
if (price === undefined) {
price = cost / filled;
}
}
}
const fee = {
'cost': feeCost,
'currency': feeCurrency,
};
return {
'id': id,
'datetime': this.iso8601 (timestamp),
'timestamp': timestamp,
'lastTradeTimestamp': lastTradeTimestamp,
'status': status,
'symbol': symbol,
'type': 'limit',
'side': side,
'price': price,
'cost': cost,
'amount': amount,
'filled': filled,
'remaining': remaining,
'average': average,
'trades': trades,
'fee': fee,
'info': order,
};
}
async fetchDepositAddress (code, params = {}) {
await this.loadMarkets ();
const response = await this.privatePostDepositAddress (params);
const depositAddress = this.safeString (response, code);
let address = undefined;
let tag = undefined;
if (depositAddress) {
const addressAndTag = depositAddress.split (',');
address = addressAndTag[0];
const numParts = addressAndTag.length;
if (numParts > 1) {
tag = addressAndTag[1];
}
}
this.checkAddress (address);
return {
'currency': code,
'address': address,
'tag': tag,
'info': response,
};
}
getMarketFromTrades (trades) {
const tradesBySymbol = this.indexBy (trades, 'pair');
const symbols = Object.keys (tradesBySymbol);
const numSymbols = symbols.length;
if (numSymbols === 1) {
return this.markets[symbols[0]];
}
return undefined;
}
calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) {
const market = this.markets[symbol];
const rate = market[takerOrMaker];
let cost = parseFloat (this.costToPrecision (symbol, amount * rate));
let key = 'quote';
if (side === 'sell') {
cost *= price;
} else {
key = 'base';
}
return {
'type': takerOrMaker,
'currency': market[key],
'rate': rate,
'cost': parseFloat (this.feeToPrecision (symbol, cost)),
};
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
await this.loadMarkets ();
const currency = this.currency (code);
const request = {
'amount': amount,
'currency': currency['id'],
'address': address,
};
if (tag !== undefined) {
request['invoice'] = tag;
}
const response = await this.privatePostWithdrawCrypt (this.extend (request, params));
return {
'info': response,
'id': response['task_id'],
};
}
parseTransactionStatus (status) {
const statuses = {
'transferred': 'ok',
'paid': 'ok',
'pending': 'pending',
'processing': 'pending',
};
return this.safeString (statuses, status, status);
}
parseTransaction (transaction, currency = undefined) {
//
// fetchTransactions
//
// {
// "dt": 1461841192,
// "type": "deposit",
// "curr": "RUB",
// "status": "processing",
// "provider": "Qiwi (LA) [12345]",
// "amount": "1",
// "account": "",
// "txid": "ec46f784ad976fd7f7539089d1a129fe46...",
// }
//
const timestamp = this.safeTimestamp (transaction, 'dt');
let amount = this.safeFloat (transaction, 'amount');
if (amount !== undefined) {
amount = Math.abs (amount);
}
const status = this.parseTransactionStatus (this.safeString (transaction, 'status'));
const txid = this.safeString (transaction, 'txid');
const type = this.safeString (transaction, 'type');
const currencyId = this.safeString (transaction, 'curr');
const code = this.safeCurrencyCode (currencyId, currency);
let address = this.safeString (transaction, 'account');
if (address !== undefined) {
const parts = address.split (':');
const numParts = parts.length;
if (numParts === 2) {
address = parts[1].replace (' ', '');
}
}
let fee = undefined;
// fixed funding fees only (for now)
if (!this.fees['funding']['percentage']) {
const key = (type === 'withdrawal') ? 'withdraw' : 'deposit';
let feeCost = this.safeFloat (this.options['fundingFees'][key], code);
// users don't pay for cashbacks, no fees for that
const provider = this.safeString (transaction, 'provider');
if (provider === 'cashback') {
feeCost = 0;
}
if (feeCost !== undefined) {
// withdrawal amount includes the fee
if (type === 'withdrawal') {
amount = amount - feeCost;
}
fee = {
'cost': feeCost,
'currency': code,
'rate': undefined,
};
}
}
return {
'info': transaction,
'id': undefined,
'currency': code,
'amount': amount,
'address': address,
'tag': undefined, // refix it properly
'status': status,
'type': type,
'updated': undefined,
'txid': txid,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'fee': fee,
};
}
async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
if (since !== undefined) {
request['date'] = parseInt (since / 1000);
}
let currency = undefined;
if (code !== undefined) {
currency = this.currency (code);
}
const response = await this.privatePostWalletHistory (this.extend (request, params));
//
// {
// "result": true,
// "error": "",
// "begin": "1493942400",
// "end": "1494028800",
// "history": [
// {
// "dt": 1461841192,
// "type": "deposit",
// "curr": "RUB",
// "status": "processing",
// "provider": "Qiwi (LA) [12345]",
// "amount": "1",
// "account": "",
// "txid": "ec46f784ad976fd7f7539089d1a129fe46...",
// },
// {
// "dt": 1463414785,
// "type": "withdrawal",
// "curr": "USD",
// "status": "paid",
// "provider": "EXCODE",
// "amount": "-1",
// "account": "EX-CODE_19371_USDda...",
// "txid": "",
// },
// ],
// }
//
return this.parseTransactions (response['history'], currency, since, limit);
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let url = this.urls['api'][api] + '/';
if (api !== 'web') {
url += this.version + '/';
}
url += path;
if ((api === 'public') || (api === 'web')) {
if (Object.keys (params).length) {
url += '?' + this.urlencode (params);
}
} else if (api === 'private') {
this.checkRequiredCredentials ();
const nonce = this.nonce ();
body = this.urlencode (this.extend ({ 'nonce': nonce }, params));
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Key': this.apiKey,
'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'),
};
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
nonce () {
return this.milliseconds ();
}
handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
if (response === undefined) {
return; // fallback to default error handler
}
if ('result' in response) {
//
// {"result":false,"error":"Error 50052: Insufficient funds"}
//
let success = this.safeValue (response, 'result', false);
if (typeof success === 'string') {
if ((success === 'true') || (success === '1')) {
success = true;
} else {
success = false;
}
}
if (!success) {
let code = undefined;
const message = this.safeString (response, 'error');
const errorParts = message.split (':');
const numParts = errorParts.length;
if (numParts > 1) {
const errorSubParts = errorParts[0].split (' ');
const numSubParts = errorSubParts.length;
code = (numSubParts > 1) ? errorSubParts[1] : errorSubParts[0];
}
const feedback = this.id + ' ' + body;
this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback);
this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback);
throw new ExchangeError (feedback);
}
}
}
};