From 0c7df9e08b450586cb41ac60c5507d30a990d8ae Mon Sep 17 00:00:00 2001 From: Matias Benitez Date: Mon, 13 Aug 2018 15:36:29 -0300 Subject: [PATCH] Adding multiple exchange support --- Assistant.js | 82 +- BinanceClient.js | 540 --- PoloniexAPIClient.js | 566 --- TradingBotProcessMainLoop.js | 5 +- ExchangeAPI.js => exchange/ExchangeAPI.js | 194 +- exchange/exchangeUtils.js | 91 + exchange/wrappers/beautifier.js | 183 + exchange/wrappers/binance.js | 368 ++ exchange/wrappers/binanceClient.js | 350 ++ exchange/wrappers/binanceProperties.json | 4594 +++++++++++++++++++++ exchange/wrappers/poloniex.js | 438 ++ exchange/wrappers/poloniexClient.js | 357 ++ exchange/wrappers/poloniexProperties.json | 3513 ++++++++++++++++ 13 files changed, 10065 insertions(+), 1216 deletions(-) delete mode 100644 BinanceClient.js delete mode 100644 PoloniexAPIClient.js rename ExchangeAPI.js => exchange/ExchangeAPI.js (57%) create mode 100644 exchange/exchangeUtils.js create mode 100644 exchange/wrappers/beautifier.js create mode 100644 exchange/wrappers/binance.js create mode 100644 exchange/wrappers/binanceClient.js create mode 100644 exchange/wrappers/binanceProperties.json create mode 100644 exchange/wrappers/poloniex.js create mode 100644 exchange/wrappers/poloniexClient.js create mode 100644 exchange/wrappers/poloniexProperties.json diff --git a/Assistant.js b/Assistant.js index 152ff676e..8856864fb 100644 --- a/Assistant.js +++ b/Assistant.js @@ -412,15 +412,15 @@ /* - Here we check that all the positions we know we still have at the exchange. If they are not, we will try to take appropiate + Here we check that all the positions we know are still valid at the exchange. If they are not, we will try to take appropiate actions. Reasons why the positions might not be there are: 1. The user / account owner closed the positions manually. - 2. The exchange for some eventuality closed the positions. In some exchanges positions have an expiratin time. + 2. The exchange for some eventuality closed the positions. In some exchanges positions have an expiration time. 3. The orders were executed. - For as 1 and 2 are similar unexpected situations and we will stop the bot execution when we detect we are there. Number 3 is an - expected behaviour and we take appropiate action. + Situations 1 and 2 are similar unexpected and we will stop the bot execution when we detect them. Number 3 is an + expected behaviour and we will take appropiate action. Also, the position might still be there but it might have been partially executed. To detect that we need to compare the position amounts we have on file to the one we are receiving from the exchange. @@ -445,7 +445,7 @@ /* This removes all orders leaving only the not executed position. Consider that we do want executed positions to be recored on the file so as to - have a record of their execution. But at next execution these records must be deleted bedore the new Execution Context file is created and + have a record of their execution. But at next execution these records must be deleted before the new Execution Context file is created and this is what we do here. */ @@ -495,9 +495,10 @@ */ - if (position.amountB === thisObject.truncDecimals(exchangePosition.amountB)) { + if (position.amountB === exchangePosition.amountB) { - /* Position is still there, untouched. Nothing to do here. */ + /* Position is still there, untouched. We update the final amountA on exchange. */ + position.amountA = exchangePosition.amountA; next(); return; @@ -629,6 +630,7 @@ callBack(global.DEFAULT_FAIL_RESPONSE); } } + } catch (err) { logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> err = " + err.message); callBack(global.DEFAULT_FAIL_RESPONSE); @@ -715,8 +717,8 @@ /* To confirm everything is ok, we will add all the amounts on trades plus the remaining amounts - at the order at the exchange and they must be equal to the one on file. Otherwise something very strange could - have happened, in which case we will halt the bot execution. + at the order at the exchange and they must be equal to the one on file. Otherwise an inconsistent + state has been detected, in which case we will halt the bot execution. */ @@ -737,17 +739,30 @@ sumAssetA = thisObject.truncDecimals(sumAssetA); sumAssetB = thisObject.truncDecimals(sumAssetB); - if (Math.abs(thisObject.truncDecimals(position.amountA) - sumAssetA) > 0.00000001 || Math.abs(position.amountB - sumAssetB) > 0.00000001) { + /* + * Next let's get the max decimal positions for the pair being trade returned by the exchange and validate that at least + * one decimal position less than the max matches. The reason for this is because there could be some + * rounding on the exchange when there are partial executed orders that will interfiere with the exact validation. + * + * IE: For Poloniex USDT/BTC the max returned decimal positions is 8, it means we will validate that the diference is less than 0.0000001 + */ + + let exchangeParam = exchangeAPI.getMaxDecimalPositions(); + let minValue = '0.' + (1).toPrecision(exchangeParam-1).split('.').reverse().join(''); + let decimalPositions = parseFloat(parseFloat(minValue).toFixed(exchangeParam)); + + if (Math.abs(position.amountA - sumAssetA) > decimalPositions || Math.abs(position.amountB - sumAssetB) > decimalPositions) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> position.amountA = " + position.amountA); - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> sumAssetA = " + sumAssetA); - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> position.amountB = " + position.amountB); - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> sumAssetB = " + sumAssetB); + logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> position.amountA = " + position.amountA); + logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> sumAssetA = " + sumAssetA); + logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> position.amountB = " + position.amountB); + logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> sumAssetB = " + sumAssetB); - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> Cannot be confirmed that a partiall execution was done well."); + logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> Cannot confirm that a partial execution was done well."); - /* There are diferences on the responses between the getPosition and getTrades that causes some issues, let's retry. */ - callBack(global.DEFAULT_RETRY_RESPONSE); + /* There are diferences on the responses between the getPosition and getTrades that causes some issues, let's retry. */ + + callBack(global.DEFAULT_FAIL_RESPONSE); return; } @@ -813,13 +828,13 @@ let feeAmount = thisObject.truncDecimals(Number(trade.fee) * Number(trade.amountB)); - assetA = thisObject.truncDecimals(trade.amountA); - assetB = thisObject.truncDecimals(trade.amountB) - feeAmount; + assetA = Number(trade.amountA); + assetB = Number(trade.amountB) - feeAmount; - context.executionContext.balance.assetA = thisObject.truncDecimals(context.executionContext.balance.assetA) - assetA; - context.executionContext.balance.assetB = thisObject.truncDecimals(context.executionContext.balance.assetB) + assetB; + context.executionContext.balance.assetA = thisObject.truncDecimals(context.executionContext.balance.assetA - assetA); + context.executionContext.balance.assetB = thisObject.truncDecimals(context.executionContext.balance.assetB + assetB); - context.executionContext.availableBalance.assetB = thisObject.truncDecimals(context.executionContext.availableBalance.assetB) + assetB; + context.executionContext.availableBalance.assetB = thisObject.truncDecimals(context.executionContext.availableBalance.assetB + assetB); /* Not the available balance for asset A is not affected since it was already reduced when the order was placed. */ @@ -836,13 +851,14 @@ let feeAmount = thisObject.truncDecimals(Number(trade.fee) * Number(trade.amountA)); - assetA = thisObject.truncDecimals(trade.amountA) - feeAmount; - assetB = thisObject.truncDecimals(trade.amountB); + assetA = Number(trade.amountA) - feeAmount; + assetB = Number(trade.amountB); - context.executionContext.balance.assetA = thisObject.truncDecimals(context.executionContext.balance.assetA) + assetA; - context.executionContext.balance.assetB = thisObject.truncDecimals(context.executionContext.balance.assetB) - assetB; + context.executionContext.balance.assetA = thisObject.truncDecimals(context.executionContext.balance.assetA + assetA); + context.executionContext.balance.assetB = thisObject.truncDecimals(context.executionContext.balance.assetB - assetB); - context.executionContext.availableBalance.assetA = thisObject.truncDecimals(context.executionContext.availableBalance.assetA) + assetA; + let calculatedBalance = + context.executionContext.availableBalance.assetA = thisObject.truncDecimals(context.executionContext.availableBalance.assetA + assetA); /* Not the available balance for asset B is not affected since it was already reduced when the order was placed. */ @@ -1067,15 +1083,13 @@ if (position.type === 'buy') { - context.executionContext.availableBalance.assetA = thisObject.truncDecimals(context.executionContext.availableBalance.assetA) - pAmountA; - context.executionContext.availableBalance.assetA = thisObject.truncDecimals(context.executionContext.availableBalance.assetA); + context.executionContext.availableBalance.assetA = thisObject.truncDecimals(context.executionContext.availableBalance.assetA - pAmountA); context.newHistoryRecord.lastBuyRate = pRate; } if (position.type === 'sell') { - context.executionContext.availableBalance.assetB = thisObject.truncDecimals(context.executionContext.availableBalance.assetB) - pAmountB; - context.executionContext.availableBalance.assetB = thisObject.truncDecimals(context.executionContext.availableBalance.assetB); + context.executionContext.availableBalance.assetB = thisObject.truncDecimals(context.executionContext.availableBalance.assetB - pAmountB); context.newHistoryRecord.lastSellRate = pRate; } @@ -1414,9 +1428,11 @@ } } - function truncDecimals(pFloatValue) { + function truncDecimals(pFloatValue, pDecimals) { - return parseFloat(parseFloat(pFloatValue).toFixed(6)); //TODO Number of decimals depends on the exchange + if (!pDecimals) pDecimals = 8; // Default value + + return parseFloat(parseFloat(pFloatValue).toFixed(pDecimals)); } }; \ No newline at end of file diff --git a/BinanceClient.js b/BinanceClient.js deleted file mode 100644 index b2b88253f..000000000 --- a/BinanceClient.js +++ /dev/null @@ -1,540 +0,0 @@ -exports.newBinanceAPIClient = function newBinanceAPIClient(pKey, pSecret, logger) { - - const FULL_LOG = true; - const LOG_FILE_CONTENT = true; - const MODULE_NAME = "Binance API Client"; - - // MAX Decimal numers: 6 - /* - /* ATENTION: This Library is used both a the cloud (AACloud) and also at the AAWeb on the server side without any modifications. - /* There is a second version, stripped of all the inner functionality that runs at AAWeb browser side, which channels the requests to the server side one. - /* - /* Documentation @ https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md - /* @ https://support.binance.com/hc/en-us/articles/115000594711-Trading-Rule - */ - - let EMPTY_RESPONSE = ""; - let NOT_IMPLEMENTED = "Method not implemented yet."; - - let newFunction = new API(pKey, pSecret); - - let thisObject = { - API: newFunction - }; - - return thisObject; - - function API(key, secret) { - 'use strict'; - - // Module dependencies - var crypto = require('crypto'), - request = require('request'); - - // Constants - var recvWindow = '10000', - API_URL = 'https://api.binance.com'; - - // Helper methods - function joinCurrencies(currencyA, currencyB) { - // If only one arg, then return the first - if (typeof currencyB !== 'string') { - return currencyA; - } - - return currencyB + '' + currencyA; - } - - function getDate() { - let localDate = new Date(); - let date = new Date(Date.UTC( - localDate.getUTCFullYear(), - localDate.getUTCMonth(), - localDate.getUTCDate(), - localDate.getUTCHours(), - localDate.getUTCMinutes(), - localDate.getUTCSeconds(), - localDate.getUTCMilliseconds())); - return date.valueOf(); - } - - function truncDecimals(pFloatValue) { - - return parseFloat(parseFloat(pFloatValue).toFixed(6)); - - } - - // Generate headers signed by thisObject user's key and secret. - // The secret is encapsulated and never exposed - function _getSignature(parameters) { - var paramString, signature; - - if (!key || !secret) { - throw 'Binance: Error. API key and secret required'; - } - - // Convert to `arg1=foo&arg2=bar` - paramString = Object.keys(parameters).map(function (param) { - return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); - }).join('&'); - - signature = crypto.createHmac('sha256', secret).update(paramString).digest('hex'); - - return { - signature: signature - }; - }; - - // Currently, thisObject fails with `Error: CERT_UNTRUSTED` - // Binance.STRICT_SSL can be set to `false` to avoid thisObject. Use with caution. - // Will be removed in future, once thisObject is resolved. - const STRICT_SSL = true; - - // Prototype - let thisObject = { - - // Make an API request - _request: function (options, callback) { - if (!('headers' in options)) { - options.headers = {}; - } - - options.json = true; - options.headers['X-MBX-APIKEY'] = key; - options.strictSSL = STRICT_SSL; - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] request -> options = " + JSON.stringify(options)); } - - request(options, function (err, response, body) { - - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] response -> err = " + JSON.stringify(err) + ". response: " + JSON.stringify(response) + ". body:" + JSON.stringify(body)); } - - // Empty response - if (!err && (typeof body === 'undefined' || body === null)) { - err = 'Empty response'; - } - - thisObject.analizeResponse(err, body, callback); - }); - - return thisObject; - }, - - // Make an API request MARKET_DATA or USER_STREAM: Endpoint requires sending a valid API-Key. - _public: function (command, parameters, callback) { - var options; - - if (typeof parameters === 'function') { - callback = parameters; - parameters = {}; - } - - parameters || (parameters = {}); - options = { - method: 'GET', - url: API_URL + command, - qs: parameters - }; - - return thisObject._request(options, callback); - }, - - // Make an API request MARKET_DATA or USER_STREAM: Endpoint requires sending a valid API-Key. - _signed: function (command, parameters, callback) { - var options; - - if (typeof parameters === 'function') { - callback = parameters; - parameters = {}; - } - - parameters || (parameters = {}); - options = { - method: 'GET', - url: API_URL + command, - qs: parameters - }; - options.qs.signature = _getSignature(parameters).signature; - return thisObject._request(options, callback); - }, - - // Make an API request TRADE or USER_DATA: Endpoint requires sending a valid API-Key and signature. - _private: function (command, parameters, callback, pMethod) { - var options; - let method; - - if (typeof parameters === 'function') { - callback = parameters; - parameters = {}; - } - - if (pMethod === 'DELETE') { - method = pMethod; - } else { - method = 'POST'; // Default value - } - - parameters || (parameters = {}); - options = { - method: method, - url: API_URL + command, - form: parameters, - qs: _getSignature(parameters) - }; - - return thisObject._request(options, callback); - }, - - /////// NON SIGNED METHODS - - returnTicker: function (currencyA, currencyB, callback) { - var parameters = { - symbol: joinCurrencies(currencyA, currencyB) - }; - - thisObject._public('/api/v1/ticker/24hr', parameters, processResponse); - - function processResponse(err, response) { - let ticker; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - ticker = { - bid: Number(response.bidPrice), - ask: Number(response.askPrice), - last: Number(response.lastPrice) - }; - } - callback(err, ticker); - } - }, - - return24hVolume: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED , callback); - }, - - returnOrderBook: function (currencyA, currencyB, depth, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnOrderBooks: function (depth, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnPublicTradeHistory: function (currencyA, currencyB, start, end, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnChartData: function (currencyA, currencyB, period, start, end, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnCurrencies: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnLoanOrders: function (currency, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - /////// SIGNED METHODS - - returnBalances: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnCompleteBalances: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnDepositAddresses: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - generateNewAddress: function (currency, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnDepositsWithdrawals: function (start, end, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnOpenOrders: function (currencyA, currencyB, callback) { - var parameters = { - symbol: joinCurrencies(currencyA, currencyB), - recvWindow: recvWindow, - timestamp: getDate().valueOf() - }; - - thisObject._signed('/api/v3/openOrders', parameters, processResponse); - - function processResponse(err, response) { - let exchangePositions = []; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - for (let i = 0; i < response.length; i++) { - let openPosition = { - id: response[i].orderId, - type: response[i].side.toLowerCase(), - rate: Number(response[i].price), - amountA: truncDecimals(response[i].origQty * response[i].price), - amountB: Number(response[i].origQty), - date: (new Date(response[i].time)).valueOf() - }; - exchangePositions.push(openPosition); - } - } - callback(err, exchangePositions); - } - }, - - returnTradeHistory: function (currencyA, currencyB, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnOrderTrades: function (orderNumber, callback) { - - var parameters = { - symbol: joinCurrencies('USDT', 'BTC'), // TODO add parameters - timestamp: getDate().valueOf() - }; - - thisObject._signed('/api/v3/myTrades', parameters, processResponse); - - function processResponse(err, response) { - let exchangeTrades = []; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - for (let i = 0; i < response.length; i++) { - if (response[i].orderId === orderNumber) { - let type = "sell" - if (response[i].isBuyer === true) { - type = "buy"; - } - let trade = { - id: response[i].orderId, - type: type, - rate: Number(response[i].price), - amountA: truncDecimals(response[i].qty * response[i].price), - amountB: Number(response[i].qty), - fee: Number(response[i].commission), - date: (new Date(response[i].time)).valueOf() - }; - exchangeTrades.push(trade); - break; - } - } - } - callback(err, exchangeTrades); - } - }, - - buy: function (currencyA, currencyB, rate, amount, callback) { - thisObject._putOrder(currencyA, currencyB, rate, amount, 'BUY', callback); - }, - - sell: function (currencyA, currencyB, rate, amount, callback) { - thisObject._putOrder(currencyA, currencyB, rate, amount, 'SELL', callback); - }, - - _putOrder: function (currencyA, currencyB, rate, amount, side, callback) { - - // TODO ERROR management, if total < 10 return error on sells - - var parameters = { - symbol: joinCurrencies(currencyA, currencyB), - side: side, - type: 'LIMIT', - quantity: truncDecimals(amount), - price: rate, - timestamp: getDate().valueOf(), - timeInForce: 'GTC' - }; - - thisObject._private('/api/v3/order', parameters, processResponse); - - function processResponse(err, response) { - let orderNumber; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - orderNumber = response.orderId; - } - callback(err, orderNumber); - } - }, - - cancelOrder: function (currencyA, currencyB, orderNumber, callback) { - var parameters = { - symbol: joinCurrencies(currencyA, currencyB), - orderId: orderNumber, - timestamp: getDate().valueOf(), - }; - - thisObject._private('/api/v3/order', parameters, processResponse, 'DELETE'); - - function processResponse(err, response) { - let orderNumber; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - orderNumber = response.orderId; - } - callback(err, orderNumber); - } - }, - - moveOrder: function (position, rate, amount, callback) { - thisObject.cancelOrder('USDT', 'BTC', orderNumber, processResponse); //TODO Maybe we will need to query the order to check executedQty - - function processResponse(err, response) { - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - if (position.type === "buy") - thisObject.buy('USDT', 'BTC', rate, truncDecimals(amount), callback); - else - thisObject.sell('USDT', 'BTC', rate, truncDecimals(amount), callback); - } else { - callback(err); - } - } - }, - - withdraw: function (currency, amount, address, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnFeeInfo: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnAvailableAccountBalances: function (account, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnTradableBalances: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - transferBalance: function (currency, amount, fromAccount, toAccount, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnMarginAccountSummary: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - marginBuy: function (currencyA, currencyB, rate, amount, lendingRate, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - marginSell: function (currencyA, currencyB, rate, amount, lendingRate, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - getMarginPosition: function (currencyA, currencyB, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - closeMarginPosition: function (currencyA, currencyB, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - createLoanOffer: function (currency, amount, duration, autoRenew, lendingRate, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - cancelLoanOffer: function (orderNumber, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnOpenLoanOffers: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnActiveLoans: function (callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - returnLendingHistory: function (start, end, limit, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - toggleAutoRenew: function (orderNumber, callback) { - analizeResponse(EMPTY_RESPONSE, NOT_IMPLEMENTED, callback); - }, - - analizeResponse: function (exchangeErr, exchangeResponse, callBack) { - /* This function analizes the different situations we might encounter trying to access Binance and returns appropiate standard errors. */ - - let stringExchangeResponse = JSON.stringify(exchangeResponse); - let stringExchangeErr = JSON.stringify(exchangeErr); - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeErr = " + stringExchangeErr); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeResponse = " + stringExchangeResponse); } - - if (stringExchangeErr.indexOf("ETIMEDOUT") > 0 || - stringExchangeErr.indexOf("ENOTFOUND") > 0 || - stringExchangeErr.indexOf("ECONNREFUSED") > 0 || - stringExchangeErr.indexOf("ESOCKETTIMEDOUT") > 0 || - stringExchangeErr.indexOf("ECONNRESET") > 0 || - (stringExchangeResponse !== undefined && ( - stringExchangeResponse.indexOf("Connection timed out") > 0 || - stringExchangeResponse.indexOf("Connection Error") > 0 || - stringExchangeResponse.indexOf("Bad gateway") > 0 || - stringExchangeResponse.indexOf("Internal error. Please try again") > 0))) { - - logger.write(MODULE_NAME, "[WARN] analizeResponse -> Timeout reached or connection problem while trying to access the Exchange API. Requesting new execution later."); - logger.write(MODULE_NAME, "[WARN] analizeResponse -> stringExchangeErr = " + stringExchangeErr); - - callBack(global.DEFAULT_RETRY_RESPONSE); - return; - - } else { - - if (stringExchangeResponse.indexOf("code") > 0) { - - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> Unexpected response from the Exchange."); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> JSON.stringify(exchangeErr) = " + stringExchangeErr); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> JSON.stringify(exchangeResponse) = " + stringExchangeResponse); - - /* {"error":"Not enough USDT.","success":0}*/ - - if (stringExchangeResponse.indexOf("Not enough") > 0) { - - let err = { - resutl: global.CUSTOM_FAIL_RESPONSE.result, - message: exchangeResponse.error - }; - - callBack(err); - - } else { - - callBack(global.DEFAULT_FAIL_RESPONSE); - - } - return; - } - - if (exchangeErr) { - - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> Unexpected error trying to contact the Exchange."); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeErr = " + stringExchangeErr); - - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - - } else { - - logger.write(MODULE_NAME, "[INFO] analizeResponse -> No problem found."); - callBack(global.DEFAULT_OK_RESPONSE, exchangeResponse); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> err.message = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - }; - - return thisObject; - } - - -} diff --git a/PoloniexAPIClient.js b/PoloniexAPIClient.js deleted file mode 100644 index 5014b3f17..000000000 --- a/PoloniexAPIClient.js +++ /dev/null @@ -1,566 +0,0 @@ -exports.newPoloniexAPIClient = function newPoloniexAPIClient(pKey, pSecret, logger) { - - // MAX Decimal numers: 8 - - /* - /* ATENTION: This Library is used both a the cloud (AACloud) and also at the AAWeb on the server side without any modifications. - /* There is a second version, stripped of all the inner functionality that runs at AAWeb browser side, which channels the requests to the server side one. - */ - - let newFunction = new API(pKey, pSecret); - - let thisObject = { - API: newFunction - }; - - return thisObject; - - function API(key, secret) { - 'use strict'; - - // Module dependencies - var crypto = require('crypto'), - request = require('request'), - nonce = require('nonce')(); - - // Constants - var version = '0.0.8', - PUBLIC_API_URL = 'https://poloniex.com/public', - PRIVATE_API_URL = 'https://poloniex.com/tradingApi', - USER_AGENT = 'poloniex.js ' + version; - //USER_AGENT = 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0' - - // Helper methods - function joinCurrencies(currencyA, currencyB) { - // If only one arg, then return the first - if (typeof currencyB !== 'string') { - return currencyA; - } - - return currencyA + '_' + currencyB; - } - - // Generate headers signed by thisObject user's key and secret. - // The secret is encapsulated and never exposed - function _getPrivateHeaders(parameters) { - var paramString, signature; - - if (!key || !secret) { - throw 'Poloniex: Error. API key and secret required'; - } - - // Convert to `arg1=foo&arg2=bar` - paramString = Object.keys(parameters).map(function (param) { - return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); - }).join('&'); - - signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); - - return { - Key: key, - Sign: signature - }; - }; - - // Currently, thisObject fails with `Error: CERT_UNTRUSTED` - // Poloniex.STRICT_SSL can be set to `false` to avoid thisObject. Use with caution. - // Will be removed in future, once thisObject is resolved. - const STRICT_SSL = true; - - // Prototype - let thisObject = { - - // Make an API request - _request: function (options, callback) { - if (!('headers' in options)) { - options.headers = {}; - } - - options.json = true; - options.headers['User-Agent'] = USER_AGENT; - options.strictSSL = STRICT_SSL; - - request(options, function (err, response, body) { - // Empty response - if (!err && (typeof body === 'undefined' || body === null)) { - err = 'Empty response'; - } - - thisObject.analizeResponse(err, body, callback); - }); - - return thisObject; - }, - - // Make a public API request - _public: function (command, parameters, callback) { - var options; - - if (typeof parameters === 'function') { - callback = parameters; - parameters = {}; - } - - parameters || (parameters = {}); - parameters.command = command; - options = { - method: 'GET', - url: PUBLIC_API_URL, - qs: parameters - }; - - options.qs.command = command; - return thisObject._request(options, callback); - }, - - // Make a private API request - _private: function (command, parameters, callback) { - var options; - - if (typeof parameters === 'function') { - callback = parameters; - parameters = {}; - } - - parameters || (parameters = {}); - parameters.command = command; - parameters.nonce = nonce(); - - options = { - method: 'POST', - url: PRIVATE_API_URL, - form: parameters, - headers: _getPrivateHeaders(parameters) - }; - - return thisObject._request(options, callback); - }, - - ///// - // PUBLIC METHODS - - returnTicker: function (currencyA, currencyB, callback) { - thisObject._public('returnTicker', processResponse); - - function processResponse(err, response) { - let ticker; - let market = joinCurrencies(currencyA, currencyB); - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - ticker = { - bid: Number(response[market].highestBid), - ask: Number(response[market].lowestAsk), - last: Number(response[market].last) - }; - } - callback(err, ticker); - } - }, - - return24hVolume: function (callback) { - return thisObject._public('return24hVolume', callback); - }, - - returnOrderBook: function (currencyA, currencyB, depth, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB), - depth: depth - }; - - return thisObject._public('returnOrderBook', parameters, callback); - }, - - returnOrderBooks: function (depth, callback) { - var parameters = { - currencyPair: 'all', - depth: depth - }; - - return thisObject._public('returnOrderBook', parameters, callback); - }, - - returnPublicTradeHistory: function (currencyA, currencyB, start, end, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB), - start: start, - end: end - }; - - return thisObject._public('returnTradeHistory', parameters, callback); - }, - - returnChartData: function (currencyA, currencyB, period, start, end, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB), - period: period, - start: start, - end: end - }; - - return thisObject._public('returnChartData', parameters, callback); - }, - - returnCurrencies: function (callback) { - return thisObject._public('returnCurrencies', callback); - }, - - returnLoanOrders: function (currency, callback) { - return thisObject._public('returnLoanOrders', { currency: currency }, callback); - }, - - ///// - // PRIVATE METHODS - - returnBalances: function (callback) { - return thisObject._private('returnBalances', {}, callback); - }, - - returnCompleteBalances: function (callback) { - return thisObject._private('returnCompleteBalances', {}, callback); - }, - - returnDepositAddresses: function (callback) { - return thisObject._private('returnDepositAddresses', {}, callback); - }, - - generateNewAddress: function (currency, callback) { - return thisObject._private('returnDepositsWithdrawals', { currency: currency }, callback); - }, - - returnDepositsWithdrawals: function (start, end, callback) { - return thisObject._private('returnDepositsWithdrawals', { start: start, end: end }, callback); - }, - - returnOpenOrders: function (currencyA, currencyB, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB) - }; - - thisObject._private('returnOpenOrders', parameters, processResponse); - - function processResponse(err, response) { - let exchangePositions = []; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - for (let i = 0; i < response.length; i++) { - let openPosition = { - id: response[i].orderNumber, - type: response[i].type, - rate: response[i].rate, - amountA: response[i].total, - amountB: response[i].amount, - date: (new Date(response[i].time)).valueOf() - }; - exchangePositions.push(openPosition); - } - } - callback(err, exchangePositions); - } - }, - - returnTradeHistory: function (currencyA, currencyB, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB) - }; - - return thisObject._private('returnTradeHistory', parameters, callback); - }, - - returnOrderTrades: function (orderNumber, callback) { - var parameters = { - orderNumber: orderNumber - }; - - thisObject._private('returnOrderTrades', parameters, processResponse); - - function processResponse(err, response) { - let trades = []; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - for (let i = 0; i < response.length; i++) { - let trade = { - id: response[i].tradeID, - type: response[i].type, - rate: response[i].rate, - amountA: response[i].total, - amountB: response[i].amount, - fee: response[i].fee, - date: (new Date(response[i].date)).valueOf() - } - trades.push(trade); - } - } - callback(err, trades); - } - }, - - buy: function (currencyA, currencyB, rate, amount, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB), - rate: rate, - amount: amount - }; - - thisObject._private('buy', parameters, processResponse); - - function processResponse(err, response) { - let orderNumber; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - orderNumber = response.orderNumber; - } - callback(err, orderNumber); - } - }, - - sell: function (currencyA, currencyB, rate, amount, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB), - rate: rate, - amount: amount - }; - - thisObject._private('sell', parameters, processResponse); - - function processResponse(err, response) { - let orderNumber; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - orderNumber = response.orderNumber; - } - callback(err, orderNumber); - } - }, - - cancelOrder: function (currencyA, currencyB, orderNumber, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB), - orderNumber: orderNumber - }; - - return thisObject._private('cancelOrder', parameters, callback); - }, - - moveOrder: function (orderNumber, rate, amount, callback) { - var parameters = { - orderNumber: orderNumber, - rate: rate, - amount: amount ? amount : null - }; - - thisObject._private('moveOrder', parameters, processResponse); - - function processResponse(err, response) { - let orderNumber; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { - orderNumber = response.orderNumber; - } - callback(err, orderNumber); - } - }, - - withdraw: function (currency, amount, address, callback) { - var parameters = { - currency: currency, - amount: amount, - address: address - }; - - return thisObject._private('withdraw', parameters, callback); - }, - - returnFeeInfo: function (callback) { - return thisObject._private('returnFeeInfo', {}, callback); - }, - - returnAvailableAccountBalances: function (account, callback) { - var options = {}; - if (account) { - options.account = account; - } - return thisObject._private('returnAvailableAccountBalances', options, callback); - }, - - returnTradableBalances: function (callback) { - return thisObject._private('returnTradableBalances', {}, callback); - }, - - transferBalance: function (currency, amount, fromAccount, toAccount, callback) { - var parameters = { - currency: currency, - amount: amount, - fromAccount: fromAccount, - toAccount: toAccount - }; - - return thisObject._private('transferBalance', parameters, callback); - }, - - returnMarginAccountSummary: function (callback) { - return thisObject._private('returnMarginAccountSummary', {}, callback); - }, - - marginBuy: function (currencyA, currencyB, rate, amount, lendingRate, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB), - rate: rate, - amount: amount, - lendingRate: lendingRate ? lendingRate : null - }; - - return thisObject._private('marginBuy', parameters, callback); - }, - - marginSell: function (currencyA, currencyB, rate, amount, lendingRate, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB), - rate: rate, - amount: amount, - lendingRate: lendingRate ? lendingRate : null - }; - - return thisObject._private('marginSell', parameters, callback); - }, - - getMarginPosition: function (currencyA, currencyB, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB) - }; - - return thisObject._private('getMarginPosition', parameters, callback); - }, - - closeMarginPosition: function (currencyA, currencyB, callback) { - var parameters = { - currencyPair: joinCurrencies(currencyA, currencyB) - }; - - return thisObject._private('closeMarginPosition', parameters, callback); - }, - - createLoanOffer: function (currency, amount, duration, autoRenew, lendingRate, callback) { - var parameters = { - currency: currency, - amount: amount, - duration: duration, - autoRenew: autoRenew, - lendingRate: lendingRate - }; - - return thisObject._private('createLoanOffer', parameters, callback); - }, - - cancelLoanOffer: function (orderNumber, callback) { - var parameters = { - orderNumber: orderNumber - }; - - return thisObject._private('cancelLoanOffer', parameters, callback); - }, - - returnOpenLoanOffers: function (callback) { - return thisObject._private('returnOpenLoanOffers', {}, callback); - }, - - returnActiveLoans: function (callback) { - return thisObject._private('returnActiveLoans', {}, callback); - }, - - returnLendingHistory: function (start, end, limit, callback) { - var parameters = { - start: start, - end: end, - limit: limit - }; - - return thisObject._private('returnLendingHistory', parameters, callback); - }, - - toggleAutoRenew: function (orderNumber, callback) { - return thisObject._private('toggleAutoRenew', { orderNumber: orderNumber }, callback); - }, - - analizeResponse: function (exchangeErr, exchangeResponse, callBack) { - - const FULL_LOG = true; - const LOG_FILE_CONTENT = true; - const MODULE_NAME = "Poloniex API Client"; - - /* This function analizes the different situations we might encounter trying to access Poloniex and returns appropiate standard errors. */ - - let stringExchangeResponse = JSON.stringify(exchangeResponse); - let stringExchangeErr = JSON.stringify(exchangeErr); - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeErr = " + stringExchangeErr); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeResponse = " + stringExchangeResponse); } - - if (stringExchangeErr.indexOf("ETIMEDOUT") > 0 || - stringExchangeErr.indexOf("ENOTFOUND") > 0 || - stringExchangeErr.indexOf("ECONNREFUSED") > 0 || - stringExchangeErr.indexOf("ESOCKETTIMEDOUT") > 0 || - stringExchangeErr.indexOf("ECONNRESET") > 0 || - (stringExchangeResponse !== undefined && ( - stringExchangeResponse.indexOf("Connection timed out") > 0 || - stringExchangeResponse.indexOf("Connection Error") > 0 || - stringExchangeResponse.indexOf("Bad gateway") > 0 || - stringExchangeResponse.indexOf("Internal error. Please try again") > 0))) { - - logger.write(MODULE_NAME, "[WARN] analizeResponse -> Timeout reached or connection problem while trying to access the Exchange API. Requesting new execution later."); - logger.write(MODULE_NAME, "[WARN] analizeResponse -> stringExchangeErr = " + stringExchangeErr); - - callBack(global.DEFAULT_RETRY_RESPONSE); - return; - - } else { - - if (stringExchangeResponse.indexOf("error") > 0) { - - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> Unexpected response from the Exchange."); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> JSON.stringify(exchangeErr) = " + stringExchangeErr); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeResponse = " + stringExchangeResponse); - - /* {"error":"Not enough USDT.","success":0}*/ - - if (stringExchangeResponse.indexOf("Not enough") > 0) { - - let err = { - resutl: global.CUSTOM_FAIL_RESPONSE.result, - message: exchangeResponse.error - }; - - callBack(err); - - } else { - - callBack(global.DEFAULT_FAIL_RESPONSE); - - } - return; - } - - if (exchangeErr) { - - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> Unexpected error trying to contact the Exchange."); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeErr = " + stringExchangeErr); - - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - - } else { - - logger.write(MODULE_NAME, "[INFO] analizeResponse -> No problem found."); - callBack(global.DEFAULT_OK_RESPONSE, exchangeResponse); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> err.message = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - }; - - return thisObject; - } - - -} diff --git a/TradingBotProcessMainLoop.js b/TradingBotProcessMainLoop.js index 0b1c98814..8a40bd772 100644 --- a/TradingBotProcessMainLoop.js +++ b/TradingBotProcessMainLoop.js @@ -176,8 +176,7 @@ /* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */ const UTILITIES = require(ROOT_DIR + 'CloudUtilities'); - const POLONIEX_CLIENT_MODULE = require(ROOT_DIR + 'PoloniexAPIClient'); - const EXCHANGE_API = require(ROOT_DIR + 'ExchangeAPI'); + const EXCHANGE_API = require(ROOT_DIR + 'exchange/ExchangeAPI'); const CONTEXT = require(ROOT_DIR + 'Context'); const ASSISTANT = require(ROOT_DIR + 'Assistant'); const STATUS_REPORT = require(ROOT_DIR + 'StatusReport'); @@ -608,7 +607,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeExchangeAPI -> Entering function."); } - exchangeAPI = EXCHANGE_API.newExchangeAPI(bot, logger, POLONIEX_CLIENT_MODULE); + exchangeAPI = EXCHANGE_API.newExchangeAPI(bot, logger); exchangeAPI.initialize(onInizialized); diff --git a/ExchangeAPI.js b/exchange/ExchangeAPI.js similarity index 57% rename from ExchangeAPI.js rename to exchange/ExchangeAPI.js index 21ba4a886..c205f26c6 100644 --- a/ExchangeAPI.js +++ b/exchange/ExchangeAPI.js @@ -1,26 +1,28 @@ -exports.newExchangeAPI = function newExchangeAPI(BOT, logger, POLONIEX_CLIENT_MODULE) { +exports.newExchangeAPI = function newExchangeAPI(bot, logger) { /* This module allows trading bots to connect to the exchange and do trading operations on it. So far it can only work with Poloniex. */ + const _ = require('lodash'); + const isValidOrder = require('./exchangeUtils').isValidOrder; - const MODULE_NAME = "Exchange API"; - - let bot = BOT; + let MODULE_NAME = "Exchange API"; let thisObject = { initialize: initialize, + getTicker: getTicker, getOpenPositions: getOpenPositions, getExecutedTrades: getExecutedTrades, putPosition: putPosition, movePosition: movePosition, - getTicker: getTicker, - getPublicTradeHistory: getPublicTradeHistory + getPublicTradeHistory: getPublicTradeHistory, + getExchangeProperties: getExchangeProperties, + getMaxDecimalPositions: getMaxDecimalPositions }; - let poloniexApiClient; + let apiClient; return thisObject; @@ -29,30 +31,93 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - //poloniexApiClient = POLONIEX_CLIENT_MODULE.newPoloniexAPIClient(global.EXCHANGE_KEYS[global.EXCHANGE_NAME].Key, global.EXCHANGE_KEYS[global.EXCHANGE_NAME].Secret, logger); - poloniexApiClient = POLONIEX_CLIENT_MODULE.newBinanceAPIClient(global.EXCHANGE_KEYS[global.EXCHANGE_NAME].Key, global.EXCHANGE_KEYS[global.EXCHANGE_NAME].Secret, logger); + let exchange = bot.products[0].exchangeList[0].name.toLowerCase() + 'Client.js'; //TODO Improve + let api = require('./wrappers/' + exchange); + apiClient = api.newAPIClient(global.EXCHANGE_KEYS[global.EXCHANGE_NAME].Key, global.EXCHANGE_KEYS[global.EXCHANGE_NAME].Secret, logger); callBackFunction(global.DEFAULT_OK_RESPONSE); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.message); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } + /* + * Position Object = { + * id, String + * type, String + * rate, Number + * amountA, Number + * amountB, Number + * fee, Number + * datetime Date + * }; + */ + function truncDecimals(pFloatValue) { + let decimals = getMaxDecimalPositions(); + return parseFloat(parseFloat(pFloatValue).toFixed(decimals)); + } + + /* + * Return number of decimals for the current market + */ + function getMaxDecimalPositions() { + return getMarketConfig().maxDecimals; + } + + /* + * Return number of decimals for the current market + */ + function getMarketConfig() { + return _.find(getExchangeProperties().markets, (p) => { + return _.first(p.pair) === global.MARKET.assetA.toUpperCase() && + _.last(p.pair) === global.MARKET.assetB.toUpperCase(); + }); + } + + /* + * Return number of decimals for the current market + */ + function getExchangeProperties() { + try { + + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getExchangeProperties -> Entering function."); } + + return apiClient.getExchangeProperties(); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] getExchangeProperties -> err = " + err.message); + callBack(global.DEFAULT_FAIL_RESPONSE); + } + } + + /* + * Returns the price for a given pair of assets. + * The object returned is an array of trades: + * ticker = { + * bid, Number + * ask, Number + * last Number + * }; + */ + function getTicker(pMarket, callBack) { + try { + + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTicker -> Entering function."); } + + apiClient.getTicker(pMarket, callBack); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] getTicker -> err = " + err.message); + callBack(global.DEFAULT_FAIL_RESPONSE); + } + } + /* * Returns the open positions ath the exchange for a given market and user account. - * The object returned is an array of positions: - * position = { - * id, - * type, - * rate, - * amountA, - * amountB, - * fee, (not required) - * datetime - * }; + * The object returned is an array of positions + * */ function getOpenPositions(pMarket, callBack) { try { @@ -60,7 +125,7 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getOpenPositions -> Entering function."); } if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getOpenPositions -> pMarket = " + JSON.stringify(pMarket)); } - poloniexApiClient.API.returnOpenOrders(pMarket.assetA, pMarket.assetB, callBack); + apiClient.getOpenPositions(pMarket, callBack); } catch (err) { logger.write(MODULE_NAME, "[ERROR] getOpenPositions -> Error = " + err.message); @@ -70,16 +135,7 @@ /* * Returns the trades for a given order number. - * The object returned is an array of positions: - * position = { - * id, - * type, - * rate, - * amountA, - * amountB, - * fee, - * datetime - * }; + * The object returned is an array of positions */ function getExecutedTrades(pPositionId, callBack) { try { @@ -87,7 +143,7 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getExecutedTrades -> Entering function."); } if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getExecutedTrades -> pPositionId = " + pPositionId); } - poloniexApiClient.API.returnOrderTrades(pPositionId, callBack); + apiClient.getExecutedTrades(pPositionId, callBack); } catch (err) { logger.write(MODULE_NAME, "[ERROR] getExecutedTrades -> Error = " + err.message); @@ -97,7 +153,7 @@ /* * Creates a new buy or sell order. - * The orderNumber is returned. + * The orderNumber is returned. String */ function putPosition(pMarket, pType, pRate, pAmountA, pAmountB, callBack) { try { @@ -105,24 +161,36 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> Entering function."); } if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pMarket = " + JSON.stringify(pMarket)); } if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pType = " + pType); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pRate = " + pRate); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pAmountA = " + pAmountA); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pAmountB = " + pAmountB); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pRate = " + truncDecimals(pRate)); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pAmountA = " + truncDecimals(pAmountA)); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pAmountB = " + truncDecimals(pAmountB)); } - if (pType === "buy") { - poloniexApiClient.API.buy(pMarket.assetA, pMarket.assetB, pRate, pAmountB, callBack); - return; - } + let check = isValidOrder({ + market: getMarketConfig(), + api: apiClient, + pAmountB: truncDecimals(pAmountB), + pRate: truncDecimals(pRate) + }); - if (pType === "sell") { - poloniexApiClient.API.sell(pMarket.assetA, pMarket.assetB, pRate, pAmountB, callBack); - return; + if (check.valid) { + if (pType === "buy") { + apiClient.buy(pMarket.assetA, pMarket.assetB, truncDecimals(pRate), truncDecimals(pAmountB), callBack); + return; + } + + if (pType === "sell") { + apiClient.sell(pMarket.assetA, pMarket.assetB, truncDecimals(pRate), truncDecimals(pAmountB), callBack); + return; + } + + logger.write(MODULE_NAME, "[ERROR] putPosition -> pType must be either 'buy' or 'sell'."); + callBack(global.DEFAULT_FAIL_RESPONSE); + + } else { + logger.write(MODULE_NAME, "[ERROR] putPosition -> The order is invalid: " + check.reason); + callBack(global.DEFAULT_FAIL_RESPONSE); } - logger.write(MODULE_NAME, "[ERROR] putPosition -> pType must be either 'buy' or 'sell'."); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } catch (err) { logger.write(MODULE_NAME, "[ERROR] putPosition -> err = " + err.message); callBack(global.DEFAULT_FAIL_RESPONSE); @@ -138,31 +206,9 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> Entering function."); } if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> pPosition = " + JSON.stringify(pPosition)); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> pNewRate = " + pNewRate); } - - poloniexApiClient.API.moveOrder(pPosition, pNewRate, pNewAmountB, callBack); + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> pNewRate = " + truncDecimals(pNewRate)); } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] movePosition -> err = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - /* - * Returns the price for a given pair of assets. - * The object returned is an array of trades: - * ticker = { - * bid, - * ask, - * last - * }; - */ - function getTicker(pMarket, callBack) { - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTicker -> Entering function."); } - - poloniexApiClient.API.returnTicker(pMarket.assetA, pMarket.assetB, callBack); + apiClient.movePosition(pPosition, truncDecimals(pNewRate), truncDecimals(pNewAmountB), callBack); } catch (err) { logger.write(MODULE_NAME, "[ERROR] movePosition -> err = " + err.message); @@ -184,16 +230,16 @@ * datetime * }; */ - function getPublicTradeHistory(assetA, assetB, startTime, endTime, callback) { + function getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack) { try { if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTradeHistory -> Entering function."); } - poloniexApiClient.API.returnPublicTradeHistory(assetA, assetB, startTime, endTime, callback); + apiClient.getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack); } catch (err) { logger.write(MODULE_NAME, "[ERROR] getTradeHistory -> err = " + err.message); - callback(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); } } }; \ No newline at end of file diff --git a/exchange/exchangeUtils.js b/exchange/exchangeUtils.js new file mode 100644 index 000000000..4ac0fe9fd --- /dev/null +++ b/exchange/exchangeUtils.js @@ -0,0 +1,91 @@ +// generic low level reusuable utils for interacting with exchanges. + +const retry = require('retry'); +const _ = require('lodash'); + +const retryInstance = (options, checkFn, callback) => { + if(!options) { + options = { + retries: 100, + factor: 1.2, + minTimeout: 1 * 1000, + maxTimeout: 4 * 1000 + }; + } + + let attempt = 0; + + const operation = retry.operation(options); + operation.attempt(function(currentAttempt) { + checkFn((err, result) => { + if (!err) { + return callback(global.DEFAULT_OK_RESPONSE, result); + } else { + if (err.result === global.CUSTOM_OK_RESPONSE.result) { + return callback(err, result); + } + } + + let maxAttempts = err.retry; + if(maxAttempts === true) + maxAttempts = 10; + + if(err.retry && attempt++ < maxAttempts) { + return operation.retry(err); + } + + if(err.notFatal) { + if(err.backoffDelay) { + return setTimeout(() => operation.retry(err), err.backoffDelay); + } + + return operation.retry(err); + } + + callback(err, result); + }); + }); +} + +const isValidOrder = ({api, market, amount, price}) => { + let reason = false; + + // Check amount + if(amount < market.minimalOrder.amount) { + reason = 'Amount is too small'; + } + + // Some exchanges have restrictions on prices + if( + _.isFunction(api.isValidPrice) && + !api.isValidPrice(price) + ) { + reason = 'Price is not valid'; + } + + if( + _.isFunction(api.isValidLot) && + !api.isValidLot(price, amount) + ) { + reason = 'Lot size is too small'; + } + + return { + reason, + valid: !reason + } +} + +const includes = function(str, list) { + if (!_.isString(str)) + return false; + + return _.some(list, item => str.includes(item)); +} + +module.exports = { + retry: retryInstance, + includes: includes, + isValidOrder + +} \ No newline at end of file diff --git a/exchange/wrappers/beautifier.js b/exchange/wrappers/beautifier.js new file mode 100644 index 000000000..5e5e6a051 --- /dev/null +++ b/exchange/wrappers/beautifier.js @@ -0,0 +1,183 @@ +const _ = require('underscore'); + +class Beautifier { + + constructor() { + this._beautifications = { + aggTrades: { + a: 'aggTradeId', + p: 'price', + q: 'quantity', + f: 'firstTradeId', + l: 'lastTradeId', + T: 'timestamp', + m: 'maker', + M: 'bestPriceMatch' + }, + klines: { + 0: 'openTime', + 1: 'open', + 2: 'high', + 3: 'low', + 4: 'close', + 5: 'volume', + 6: 'closeTime', + 7: 'quoteAssetVolume', + 8: 'trades', + 9: 'takerBaseAssetVolume', + 10: 'takerQuoteAssetVolume', + 11: 'ignored' + }, + bids: [ + { + 0: 'price', + 1: 'quantity', + 2: 'ignored' + } + ], + asks: [ + { + 0: 'price', + 1: 'quantity', + 2: 'ignored' + } + ], + depthUpdateEvent: { + e: 'eventType', + E: 'eventTime', + s: 'symbol', + u: 'updateId', + b: 'bidDepthDelta', + a: 'askDepthDelta' + }, + bidDepthDelta: [ + { + 0: 'price', + 1: 'quantity', + 2: 'ignored' + } + ], + askDepthDelta: [ + { + 0: 'price', + 1: 'quantity', + 2: 'ignored' + } + ], + klineEvent: { + e: 'eventType', + E: 'eventTime', + s: 'symbol', + k: 'kline' + }, + kline: { + t: 'startTime', + T: 'endTime', + s: 'symbol', + i: 'interval', + f: 'firstTradeId', + L: 'lastTradeId', + o: 'open', + c: 'close', + h: 'high', + l: 'low', + v: 'volume', + n: 'trades', + x: 'final', + q: 'quoteVolume', + V: 'volumeActive', + Q: 'quoteVolumeActive', + B: 'ignored' + }, + aggTradeEvent: { + e: 'eventType', + E: 'eventTime', + s: 'symbol', + a: 'tradeId', + p: 'price', + q: 'quantity', + f: 'firstTradeId', + l: 'lastTradeId', + T: 'time', + m: 'maker', + M: 'ignored' + }, + outboundAccountInfoEvent: { + e: 'eventType', + E: 'eventTime', + m: 'makerCommission', + t: 'takerCommission', + b: 'buyerCommission', + s: 'sellerCommission', + T: 'canTrade', + W: 'canWithdraw', + D: 'canDeposit', + B: 'balances' + }, + balances: [ + { + a: 'asset', + f: 'availableBalance', + l: 'onOrderBalance' + } + ], + executionReportEvent: { + e: 'eventType', + E: 'eventTime', + s: 'symbol', + c: 'newClientOrderId', + S: 'side', + o: 'orderType', + f: 'cancelType', // GTC 'Good till Cancel', IOC: 'Immediate or Cancel' + q: 'quantity', + p: 'price', + x: 'executionType', + X: 'orderStatus', + r: 'rejectReason', + i: 'orderId', + l: 'lastTradeQuantity', + z: 'accumulatedQuantity', + L: 'lastTradePrice', + n: 'commission', + N: 'commissionAsset', + m: 'maker', + T: 'tradeTime', + t: 'tradeId' + } + } + } + + beautify(object, type) { + const beautifications = _.isObject(type) ? type : this._beautifications[type]; + if (!beautifications) { + return object; + } + let newItem = {}; + _.each(object, (value, key) => { + if (_.isArray(value)) { + const newArray = []; + if (_.isArray(this._beautifications[beautifications[key]])) { + _.each(value, arrayValue => { + newArray.push(this.beautify(arrayValue, this._beautifications[beautifications[key]][0])); + }); + } else { + _.each(value, (value, key) => { + newItem.push(this.beautify(value, beautifications[key])) + }); + } + const newKey = beautifications[key] || key; + newItem[newKey] = newArray; + } else if (_.isObject(value)) { + const newKey = beautifications[key] || key; + newItem[newKey] = this.beautify(value, beautifications[key]); + } else { + const newKey = beautifications[key] || key; + newItem[newKey] = value; + } + }); + return newItem; + } + +} + +module.exports = Beautifier; \ No newline at end of file diff --git a/exchange/wrappers/binance.js b/exchange/wrappers/binance.js new file mode 100644 index 000000000..624c0fc06 --- /dev/null +++ b/exchange/wrappers/binance.js @@ -0,0 +1,368 @@ +// Source: https://github.com/zoeyg/binance/blob/master/lib/rest.js + +const request = require('request'); +const qs = require('querystring'); +const _ = require('underscore'); +const crypto = require('crypto'); +const Beautifier = require('./beautifier.js'); +const assert = require('assert'); + +class BinanceRest { + + constructor({ + key, + secret, + recvWindow = false, + timeout = 15000, + disableBeautification = false, + handleDrift = false + }) { + this.key = key; + this.secret = secret; + this.recvWindow = recvWindow; + this.timeout = timeout; + this.disableBeautification = disableBeautification; + this.handleDrift = handleDrift; + + this._beautifier = new Beautifier(); + this._baseUrl = 'https://api.binance.com/'; + this._drift = 0; + this._syncInterval = 0; + } + + _makeRequest(query, callback, route, security, method, attempt = 0) { + assert(_.isUndefined(callback) || _.isFunction(callback), 'callback must be a function or undefined'); + assert(_.isObject(query), 'query must be an object'); + + let queryString; + const type = _.last(route.split('/')), + options = { + url: `${this._baseUrl}${route}`, + timeout: this.timeout + }; + + if (security === 'SIGNED') { + if (this.recvWindow) { + query.recvWindow = this.recvWindow; + } + queryString = qs.stringify(query); + options.url += '?' + queryString; + if (options.url.substr(options.url.length - 1) !== '?') { + options.url += '&'; + } + options.url += `signature=${this._sign(queryString)}`; + } else { + queryString = qs.stringify(query); + if (queryString) { + options.url += '?' + queryString; + } + } + if (security === 'API-KEY' || security === 'SIGNED') { + options.headers = { 'X-MBX-APIKEY': this.key }; + } + if (method) { + options.method = method; + } + + const action = cb => { + request(options, (err, response, body) => { + let payload; + try { + payload = JSON.parse(body); + } catch (e) { + payload = body; + } + if (err) { + cb(err, payload); + } else if (response.statusCode < 200 || response.statusCode > 299) { + /* + * If we get a response that the timestamp is ahead of the server, + * calculate the drift and then attempt the request again + */ + if (response.statusCode === 400 && payload.code === -1021 && + this.handleDrift && attempt === 0) { + + this.calculateDrift() + .then(() => { + query.timestamp = this._getTime() + this._drift; + return this._makeRequest(query, cb, route, security, method, + ++attempt); + }); + } else { + cb(new Error(`Response code ${response.statusCode}`), payload); + } + } else { + if (_.isArray(payload)) { + payload = _.map(payload, (item) => { + return this._doBeautifications(item, type); + }); + } else { + payload = this._doBeautifications(payload); + } + cb(err, payload); + } + }); + }; + if (callback) { + action(callback); + } else { + return new Promise((resolve, reject) => action((err, payload) => { + if (err) { + if (payload === undefined) { + reject(err); + } else { + reject(payload); + } + } else { + resolve(payload); + } + })); + } + } + + _doBeautifications(response, route) { + if (this.disableBeautification) { + return response; + } + return this._beautifier.beautify(response, route); + } + + _sign(queryString) { + return crypto.createHmac('sha256', this.secret) + .update(queryString) + .digest('hex'); + } + + _setTimestamp(query) { + if (!query.timestamp) { + query.timestamp = this._getTime() + this._drift; + } + } + + _getTime() { + return new Date().getTime(); + } + + calculateDrift() { + const systemTime = this._getTime(); + return this.time() + .then((response) => { + // Calculate the approximate trip time from here to binance + const transitTime = parseInt((this._getTime() - systemTime) / 2); + this._drift = response.serverTime - (systemTime + transitTime); + }); + } + + startTimeSync(interval = 300000, onRecalculateDriftCb) { + return new Promise((resolve, reject) => { + // If there's already an interval running, clear it and reset values + if (this._syncInterval !== 0) { + this.endTimeSync(); + return resolve(); + } + + // Calculate initial drift value and setup interval to periodically update it + this.calculateDrift() + .then(resolve) + .catch(reject); + + this._syncInterval = setInterval(() => { + const promise = this.calculateDrift(); + + if (_.isFunction(onRecalculateDriftCb)) { + onRecalculateDriftCb(promise); + } + }, interval); + }); + } + + endTimeSync() { + clearInterval(this._syncInterval); + this._drift = 0; + this._syncInterval = 0; + } + + // Public APIs + ping(callback) { + return this._makeRequest({}, callback, 'api/v1/ping'); + } + + time(callback) { + return this._makeRequest({}, callback, 'api/v1/time'); + } + + depth(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + + return this._makeRequest(query, callback, 'api/v1/depth'); + } + + trades(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + + return this._makeRequest(query, callback, 'api/v1/trades'); + } + + historicalTrades(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + + return this._makeRequest(query, callback, 'api/v1/historicalTrades', 'API-KEY'); + } + + aggTrades(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + + return this._makeRequest(query, callback, 'api/v1/aggTrades'); + } + + exchangeInfo(callback) { + return this._makeRequest({}, callback, 'api/v1/exchangeInfo'); + } + + klines(query = {}, callback) { + return this._makeRequest(query, callback, 'api/v1/klines'); + } + + ticker24hr(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + + return this._makeRequest(query, callback, 'api/v1/ticker/24hr'); + } + + tickerPrice(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + + return this._makeRequest(query, callback, 'api/v3/ticker/price'); + } + + bookTicker(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + + return this._makeRequest(query, callback, 'api/v3/ticker/bookTicker'); + } + + allBookTickers(callback) { + return this._makeRequest({}, callback, 'api/v1/ticker/allBookTickers'); + } + + allPrices(callback) { + return this._makeRequest({}, callback, 'api/v1/ticker/allPrices'); + } + + // Private APIs + newOrder(query = {}, callback) { + this._setTimestamp(query); + return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED', 'POST'); + } + + testOrder(query = {}, callback) { + this._setTimestamp(query); + return this._makeRequest(query, callback, 'api/v3/order/test', 'SIGNED', 'POST'); + } + + queryOrder(query = {}, callback) { + this._setTimestamp(query); + return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED'); + } + + cancelOrder(query = {}, callback) { + this._setTimestamp(query); + return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED', 'DELETE'); + } + + openOrders(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + this._setTimestamp(query); + return this._makeRequest(query, callback, 'api/v3/openOrders', 'SIGNED'); + } + + allOrders(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + this._setTimestamp(query); + return this._makeRequest(query, callback, 'api/v3/allOrders', 'SIGNED'); + } + + account(query = {}, callback) { + if (_.isFunction(query)) { + callback = query; + query = {}; + } + this._setTimestamp(query); + return this._makeRequest(query, callback, 'api/v3/account', 'SIGNED'); + } + + myTrades(query = {}, callback) { + if (_.isString(query)) { + query = { symbol: query }; + } + this._setTimestamp(query); + return this._makeRequest(query, callback, 'api/v3/myTrades', 'SIGNED'); + } + + withdraw(query = {}, callback) { + this._setTimestamp(query); + return this._makeRequest(query, callback, 'wapi/v3/withdraw.html', 'SIGNED', 'POST'); + } + + depositHistory(query = {}, callback) { + if (_.isString(query)) { + query = { asset: query }; + } + this._setTimestamp(query); + return this._makeRequest(query, callback, 'wapi/v3/depositHistory.html', 'SIGNED'); + } + + withdrawHistory(query = {}, callback) { + if (_.isString(query)) { + query = { asset: query }; + } + this._setTimestamp(query); + return this._makeRequest(query, callback, 'wapi/v3/withdrawHistory.html', 'SIGNED'); + } + + depositAddress(query = {}, callback) { + if (_.isString(query)) { + query = { asset: query }; + } + this._setTimestamp(query); + return this._makeRequest(query, callback, 'wapi/v3/depositAddress.html', 'SIGNED'); + } + + accountStatus(callback) { + const query = {}; + this._setTimestamp(query); + return this._makeRequest(query, callback, 'wapi/v3/accountStatus.html', 'SIGNED'); + } + + startUserDataStream(callback) { + return this._makeRequest({}, callback, 'api/v1/userDataStream', 'API-KEY', 'POST'); + } + + keepAliveUserDataStream(query = {}, callback) { + return this._makeRequest(query, callback, 'api/v1/userDataStream', 'API-KEY', 'PUT'); + } + + closeUserDataStream(query = {}, callback) { + return this._makeRequest(query, callback, 'api/v1/userDataStream', 'API-KEY', 'DELETE'); + } +} + +module.exports = BinanceRest; \ No newline at end of file diff --git a/exchange/wrappers/binanceClient.js b/exchange/wrappers/binanceClient.js new file mode 100644 index 000000000..d97b182c7 --- /dev/null +++ b/exchange/wrappers/binanceClient.js @@ -0,0 +1,350 @@ +exports.newAPIClient = function newAPIClient(pKey, pSecret, logger) { + + /* + /* ATENTION: This Library is used both a the cloud (AACloud) and also at the AAWeb on the server side without any modifications. + /* There is a second version, stripped of all the inner functionality that runs at AAWeb browser side, which channels the requests to the server side one. + */ + + const FULL_LOG = true; + const LOG_FILE_CONTENT = true; + const MODULE_NAME = "binanceClient"; + + const retry = require('../exchangeUtils').retry; + const includes = require('../exchangeUtils').includes; + const _ = require('lodash'); + const Binance = require("./binance"); + const exchangeProperties = require('./poloniexProperties.json'); + + const recoverableErrors = [ + 'SOCKETTIMEDOUT', + 'TIMEDOUT', + 'CONNRESET', + 'CONNREFUSED', + 'NOTFOUND', + 'Error -1021', + 'Response code 429', + 'Response code 5', + 'ETIMEDOUT' + ]; + + let API = new Binance({ + key: pKey, + secret: pSecret, + timeout: 15000, + recvWindow: 60000, + disableBeautification: false, + handleDrift: true, + }); + + let thisObject = { + getTicker: getTicker, + getOpenPositions: getOpenPositions, + getExecutedTrades: getExecutedTrades, + buy: buy, + sell: sell, + movePosition: movePosition, + getPublicTradeHistory: getPublicTradeHistory, + getExchangeProperties: getExchangeProperties + }; + + return thisObject; + + function joinCurrencies(currencyA, currencyB) { + // If only one arg, then return the first + if (typeof currencyB !== 'string') { + return currencyA; + } + + return currencyB + '' + currencyA; + } + + /* + * Returns the properties for this exchange + */ + function getExchangeProperties() { + return exchangeProperties; + } + + /* + * Returns the price for a given pair of assets. + * The object returned is an array of trades: + * ticker = { + * bid, Number + * ask, Number + * last Number + * }; + */ + function getTicker(pMarket, callBack) { + + let market = joinCurrencies(pMarket.assetA, pMarket.assetB); + const handle = (err, response) => { + let ticker; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + ticker = { + bid: Number(response.bidPrice), + ask: Number(response.askPrice), + last: Number(response.lastPrice) + }; + } + callBack(err, ticker); + }; + + const fetch = next => API.ticker24hr(market, analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* + * Returns the open positions ath the exchange for a given market and user account. + * The object returned is an array of positions: + * position = { + * id, + * type, + * rate, Number + * amountA, Number + * amountB, Number + * fee, (not required) Number + * datetime + * }; + */ + function getOpenPositions(pMarket, callBack) { + + let market = joinCurrencies(pMarket.assetA, pMarket.assetB); + const handle = (err, response) => { + let exchangePositions = []; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + for (let i = 0; i < response.length; i++) { + let openPosition = { + id: response[i].orderId, + type: response[i].side.toLowerCase(), + rate: Number(response[i].price), + amountA: truncDecimals(response[i].origQty * response[i].price), + amountB: Number(response[i].origQty), + date: (new Date(response[i].time)).valueOf() + }; + exchangePositions.push(openPosition); + } + } + callBack(err, exchangePositions); + }; + + const fetch = next => API.openOrders(market, analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* + * Returns the trades for a given order number. + * The object returned is an array of positions: + * position = { + * id, + * type, + * rate, Number + * amountA, Number + * amountB, Number + * fee, Number + * datetime + * }; + */ + function getExecutedTrades(pPositionId, callBack) { + + let market = joinCurrencies(global.MARKET.assetA, global.MARKET.assetB); + const handle = (err, response) => { + let exchangeTrades = []; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + for (let i = 0; i < response.length; i++) { + if (response[i].orderId === pPositionId) { + let type = "sell" + if (response[i].isBuyer === true) { + type = "buy"; + } + let trade = { + id: response[i].orderId, + type: type, + rate: Number(response[i].price), + amountA: truncDecimals(response[i].qty * response[i].price), + amountB: Number(response[i].qty), + fee: Number(response[i].commission), + date: (new Date(response[i].time)).valueOf() + }; + exchangeTrades.push(trade); + break; + } + } + } + callBack(err, exchangeTrades); + }; + + const fetch = next => API.myTrades(market, analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* + * Creates a new buy order. + * The orderNumber is returned. + */ + function buy(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) { + + var parameters = { + symbol: joinCurrencies(pCurrencyA, pCurrencyB), + side: 'BUY', + type: 'LIMIT', + quantity: pAmount, + price: pRate, + timeInForce: 'GTC' + }; + const handle = (err, response) => { + let orderNumber; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + orderNumber = response.orderId; + } + callBack(err, orderNumber); + }; + + const fetch = next => API.newOrder(parameters, analizeResponse(next)); + + retry(null, fetch, handle); + + } + + /* + * Creates a new sell order. + * The orderNumber is returned. String + */ + function sell(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) { + + var parameters = { + symbol: joinCurrencies(pCurrencyA, pCurrencyB), + side: 'SELL', + type: 'LIMIT', + quantity: pAmount, + price: pRate, + timeInForce: 'GTC' + }; + const handle = (err, response) => { + let orderNumber; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + orderNumber = response.orderId; + } + callBack(err, orderNumber); + }; + + const fetch = next => API.newOrder(parameters, analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* //TODO Complete partial orders + * Move an existing position to the new rate. + * The new orderNumber is returned. String + */ + function movePosition(pPosition, pNewRate, pNewAmountB, callBack) { + + var parameters = { + symbol: joinCurrencies(global.MARKET.assetA, global.MARKET.assetB), + orderId: pPosition.id + }; + + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] moveOrder -> Step1 - Cancelling Position: " + JSON.stringify(pPosition)); } + + const handle = (err, response) => { + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] moveOrder -> Step1 Complete - Position Cancelled. Proceeding to put the new position."); } + + if (position.type === "buy") + thisObject.buy(global.MARKET.assetA, global.MARKET.assetB, pNewRate, pNewAmountB, callBack); + else + thisObject.sell(global.MARKET.assetA, global.MARKET.assetB, pNewRate, pNewAmountB, callBack); + } else { + callBack(err); + } + }; + + const fetch = next => API.cancelOrder(parameters, analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* //TODO Pending Review + * Returns all the trade history from the Exchange since startTime to endTime orderd by tradeId. + * It's possible that the exchange doesn't support this method. + * The object returned is an array of trades: + * trade = { + * tradeIdAtExchange, + * marketIdAtExchange, + * type, + * rate, Number + * amountA, Number + * amountB, Number + * datetime + * }; + */ + function getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] getPublicTradeHistory -> Not implemented yet."); } + callBack(global.DEFAULT_FAIL_RESPONSE); + } + + function isValidPrice(price) { + return price >= market.minimalOrder.price; + } + + function isValidLot(price, amount) { + return amount * price >= market.minimalOrder.order; + } + + function analizeResponse(callBack) { + + return (exchangeErr, exchangeResponse) => { + let error; + + /* This function analizes the different situations we might encounter trying to access Poloniex and returns appropiate standard errors. */ + + let stringExchangeErr = JSON.stringify(exchangeErr); + let stringExchangeResponse = JSON.stringify(exchangeResponse); + + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeErr = " + stringExchangeErr); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeResponse = " + stringExchangeResponse); } + + if (exchangeErr) { + error = global.DEFAULT_FAIL_RESPONSE; + } else if (!exchangeResponse) { + error = { + result: global.DEFAULT_FAIL_RESPONSE.result, + message: 'Empty response' + }; + } else if (includes(exchangeResponse.error, [''])) { + error = { + result: global.DEFAULT_FAIL_RESPONSE.result, + message: exchangeResponse + }; + } else if (includes(exchangeResponse.error, ['Order does not exist.'])) { + error = { + result: global.CUSTOM_OK_RESPONSE.result, + message: exchangeResponse.error + }; + } else if (exchangeResponse.error) { + error = { + result: global.DEFAULT_FAIL_RESPONSE.result, + message: exchangeResponse.error + }; + } + + if (error) { + if (includes(error.message, recoverableErrors)) { + error.notFatal = true; + } + } + + return callBack(error, exchangeResponse); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] analizeResponse -> err.message = " + err.message); + return callBack(global.DEFAULT_FAIL_RESPONSE, exchangeResponse); + } + } + } + +} diff --git a/exchange/wrappers/binanceProperties.json b/exchange/wrappers/binanceProperties.json new file mode 100644 index 000000000..ad4438de0 --- /dev/null +++ b/exchange/wrappers/binanceProperties.json @@ -0,0 +1,4594 @@ +{ + "assets": [ + "NULS", + "VEN", + "BNB", + "NEO", + "LINK", + "SALT", + "IOTA", + "ETC", + "AST", + "KNC", + "WTC", + "SNGLS", + "EOS", + "SNT", + "MCO", + "BTC", + "OAX", + "OMG", + "GAS", + "BQX", + "QTUM", + "BNT", + "DNT", + "ICN", + "SNM", + "STRAT", + "ZRX", + "FUN", + "LTC", + "ETH", + "XVG", + "BCC", + "ADA", + "ADX", + "AE", + "AGI", + "AION", + "AMB", + "APPC", + "ARK", + "ARN", + "BAT", + "BCD", + "BCN", + "BCPT", + "BLZ", + "BRD", + "BTG", + "BTS", + "CDT", + "CHAT", + "CLOAK", + "CMT", + "CND", + "CVC", + "DASH", + "DATA", + "DENT", + "DGD", + "DLT", + "EDO", + "ELF", + "ENG", + "ENJ", + "EVX", + "FUEL", + "GNT", + "GRS", + "GTO", + "GVT", + "GXS", + "HSR", + "ICX", + "INS", + "IOST", + "IOTX", + "KEY", + "KMD", + "LEND", + "LOOM", + "LRC", + "LSK", + "LUN", + "MANA", + "MDA", + "MFT", + "MOD", + "MTH", + "MTL", + "NANO", + "NAS", + "NAV", + "NCASH", + "NEBL", + "NPXS", + "NXS", + "ONT", + "OST", + "PIVX", + "POA", + "POE", + "POWR", + "PPT", + "QKC", + "QLC", + "QSP", + "RCN", + "RDN", + "REP", + "REQ", + "RLC", + "RPX", + "SC", + "SKY", + "STEEM", + "STORJ", + "STORM", + "SUB", + "SYS", + "THETA", + "TNB", + "TNT", + "TRIG", + "TRX", + "TUSD", + "VIA", + "VIB", + "VIBE", + "WABI", + "WAN", + "WAVES", + "WINGS", + "WPR", + "XEM", + "XLM", + "XMR", + "XRP", + "XZC", + "YOYO", + "ZEC", + "ZEN", + "ZIL" + ], + "currencies": [ + "BNB", + "BTC", + "ETH", + "USDT" + ], + "markets": [ + { + "pair": [ + "BNB", + "NULS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BNB", + "VEN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "BNB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "NULS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "NEO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "NULS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "LINK" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "SALT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "IOTA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "ETC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "AST" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "KNC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "WTC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "SNGLS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "EOS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "SNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "MCO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "BTC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.000001, + "price": 0.01, + "order": 10 + } + }, + { + "pair": [ + "ETH", + "OAX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "OMG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "GAS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "BQX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "WTC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "QTUM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "BNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "DNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "ICN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "SNM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "SNM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "SNGLS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "BQX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "NEO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "KNC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "STRAT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "ZRX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "QTUM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "FUN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "LTC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "LINK" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "ETH" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "XVG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "STRAT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "ZRX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "IOTA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "BCC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "OMG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "MCO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "SALT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "ADA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "ADA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ADA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "ADA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.1, + "price": 0.00001, + "order": 10 + } + }, + { + "pair": [ + "BNB", + "ADX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "ADX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ADX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "AE" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "AE" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "AE" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "AGI" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "AGI" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "AGI" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "AION" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "AION" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "AION" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "AMB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "AMB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "AMB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "APPC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "APPC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "APPC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "ARK" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ARK" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "ARN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ARN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "AST" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BNB", + "BAT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "BAT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "BAT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "BCC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.00001, + "price": 0.01, + "order": 1 + } + }, + { + "pair": [ + "ETH", + "BCC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "BCC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.00001, + "price": 0.01, + "order": 10 + } + }, + { + "pair": [ + "BTC", + "BCD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "BCD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "BCN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "BCN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "BCN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "BCPT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "BCPT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "BCPT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "BLZ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "BLZ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "BLZ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "BNB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "BNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BNB", + "BRD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "BRD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "BRD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "BTG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "BTG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "BTS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "BTS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "BTS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "CDT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "CDT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "CHAT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "CHAT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "CLOAK" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "CLOAK" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "CMT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "CMT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "CMT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "CND" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "CND" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "CND" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "CVC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "CVC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "CVC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "DASH" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "DASH" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "DATA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "DATA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "DENT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "DENT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "DGD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "DGD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "DLT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "DLT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "DLT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "DNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "EDO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "EDO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "ELF" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ELF" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "ENG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ENG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "ENJ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "ENJ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ENJ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "EOS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "EOS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "USDT", + "EOS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 10 + } + }, + { + "pair": [ + "BNB", + "ETC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 1 + } + }, + { + "pair": [ + "ETH", + "ETC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "ETC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 10 + } + }, + { + "pair": [ + "BTC", + "EVX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "EVX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "FUEL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "FUEL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "FUN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BNB", + "GNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "GNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "GNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "GRS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "GRS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "GTO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "GTO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "GTO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "GVT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "GVT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "GXS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "GXS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "HSR" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "HSR" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "ICN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BNB", + "ICX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "ICX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ICX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "ICX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 10 + } + }, + { + "pair": [ + "BTC", + "INS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "INS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "IOST" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "IOST" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "IOTA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "USDT", + "IOTA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 10 + } + }, + { + "pair": [ + "BTC", + "IOTX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "IOTX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "KEY" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "KEY" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "KMD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "KMD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "LEND" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "LEND" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "LOOM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "LOOM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "LOOM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "LRC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "LRC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "LSK" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "LSK" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "LSK" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "LTC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.00001, + "price": 0.01, + "order": 1 + } + }, + { + "pair": [ + "ETH", + "LTC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "LTC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.00001, + "price": 0.01, + "order": 10 + } + }, + { + "pair": [ + "BTC", + "LUN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "LUN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "MANA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "MANA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "MCO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "MDA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "MDA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "MFT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "MFT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "MFT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "MOD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "MOD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "MTH" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "MTH" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "MTL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "MTL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "NANO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "NANO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "NANO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "NAS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "NAS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "NAS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "NAV" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "NAV" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "NAV" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "NCASH" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "NCASH" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "NCASH" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "NEBL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "NEBL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "NEBL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "NEO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.001, + "order": 1 + } + }, + { + "pair": [ + "USDT", + "NEO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.001, + "order": 10 + } + }, + { + "pair": [ + "BTC", + "NPXS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "NPXS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "NXS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "NXS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "NXS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "OAX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BNB", + "ONT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "ONT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ONT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "ONT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.001, + "order": 10 + } + }, + { + "pair": [ + "BNB", + "OST" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "OST" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "OST" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "PIVX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "PIVX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "PIVX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "POA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "POA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "POA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "POE" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "POE" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "POWR" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "POWR" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "POWR" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "PPT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "PPT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "QKC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "QKC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "QLC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "QLC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "QLC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "QSP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "QSP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "QSP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "QTUM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "USDT", + "QTUM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.001, + "order": 10 + } + }, + { + "pair": [ + "BNB", + "RCN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "RCN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "RCN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "RDN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "RDN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "RDN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "REP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "REP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "REP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "REQ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "REQ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "RLC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "RLC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "RLC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "RPX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "RPX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "RPX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "SC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "SC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "SC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "SKY" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "SKY" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "SKY" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "SNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BNB", + "STEEM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "STEEM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "STEEM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "STORJ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "STORJ" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "STORM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "STORM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "STORM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "SUB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "SUB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "SYS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "SYS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "SYS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "THETA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "THETA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "THETA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "TNB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "TNB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "TNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "TNT" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "TRIG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "TRIG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "TRIG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "TRX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "TRX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "TRX" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.1, + "price": 0.00001, + "order": 10 + } + }, + { + "pair": [ + "BNB", + "TUSD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "TUSD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "TUSD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "TUSD" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 10 + } + }, + { + "pair": [ + "BTC", + "VEN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "VEN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "VEN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 10 + } + }, + { + "pair": [ + "BNB", + "VIA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "VIA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "VIA" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "VIB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BTC", + "VIBE" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "VIBE" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "ETH", + "VIB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "WABI" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "WABI" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "WABI" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "WAN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "WAN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "WAN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "WAVES" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "WAVES" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 1e-7, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "WAVES" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "WINGS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "WINGS" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-7, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "WPR" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "WPR" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "WTC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 1 + } + }, + { + "pair": [ + "BNB", + "XEM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "XEM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "XEM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "XLM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "XLM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "XLM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "XLM" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 10 + } + }, + { + "pair": [ + "BTC", + "XMR" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "XMR" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "XRP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.00001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "XRP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "XRP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "XRP" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.1, + "price": 0.00001, + "order": 10 + } + }, + { + "pair": [ + "BTC", + "XVG" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "BNB", + "XZC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "XZC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "XZC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.000001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "YOYO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "YOYO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "YOYO" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "BTC", + "ZEC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ZEC" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "ZEN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "ZEN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.000001, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ZEN" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.001, + "price": 0.00001, + "order": 0.01 + } + }, + { + "pair": [ + "BNB", + "ZIL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 0.000001, + "order": 1 + } + }, + { + "pair": [ + "BTC", + "ZIL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.001 + } + }, + { + "pair": [ + "ETH", + "ZIL" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 1, + "price": 1e-8, + "order": 0.01 + } + }, + { + "pair": [ + "USDT", + "BNB" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.01, + "price": 0.0001, + "order": 10 + } + }, + { + "pair": [ + "USDT", + "ETH" + ], + "maxDecimals": 6, + "minimalOrder": { + "amount": 0.00001, + "price": 0.01, + "order": 10 + } + } + ] +} \ No newline at end of file diff --git a/exchange/wrappers/poloniex.js b/exchange/wrappers/poloniex.js new file mode 100644 index 000000000..85b114496 --- /dev/null +++ b/exchange/wrappers/poloniex.js @@ -0,0 +1,438 @@ +// Source: https://github.com/premasagar/poloniex.js + +module.exports = (function () { + 'use strict'; + + // Module dependencies + var crypto = require('crypto'), + request = require('request'), + nonce = require('nonce')(); + + // Constants + var version = '0.0.9', + PUBLIC_API_URL = 'https://poloniex.com/public', + PRIVATE_API_URL = 'https://poloniex.com/tradingApi', + USER_AGENT = 'poloniex.js ' + version; + //USER_AGENT = 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0' + + // Helper methods + function joinCurrencies(currencyA, currencyB) { + // If only one arg, then return the first + if (typeof currencyB !== 'string') { + return currencyA; + } + + return currencyA + '_' + currencyB; + } + + // Constructor + function Poloniex(key, secret) { + // Generate headers signed by this user's key and secret. + // The secret is encapsulated and never exposed + this._getPrivateHeaders = function (parameters) { + var paramString, signature; + + if (!key || !secret) { + throw 'Poloniex: Error. API key and secret required'; + } + + // Convert to `arg1=foo&arg2=bar` + paramString = Object.keys(parameters).map(function (param) { + return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); + }).join('&'); + + signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); + + return { + Key: key, + Sign: signature + }; + }; + } + + // Currently, this fails with `Error: CERT_UNTRUSTED` + // Poloniex.STRICT_SSL can be set to `false` to avoid this. Use with caution. + // Will be removed in future, once this is resolved. + Poloniex.STRICT_SSL = true; + + // Customisable user agent string + Poloniex.USER_AGENT = USER_AGENT; + + // Prototype + Poloniex.prototype = { + constructor: Poloniex, + + // Make an API request + _request: function (options, callback) { + if (!('headers' in options)) { + options.headers = {}; + } + + options.json = true; + options.headers['User-Agent'] = Poloniex.USER_AGENT; + options.strictSSL = Poloniex.STRICT_SSL; + options.timeout = 60 * 1000; + + request(options, function (err, response, body) { + // Empty response + if (!err && (typeof body === 'undefined' || body === null)) { + err = 'Empty response'; + } + + callback(err, body); + }); + + return this; + }, + + // Make a public API request + _public: function (command, parameters, callback) { + var options; + + if (typeof parameters === 'function') { + callback = parameters; + parameters = {}; + } + + parameters || (parameters = {}); + parameters.command = command; + options = { + method: 'GET', + url: PUBLIC_API_URL, + qs: parameters + }; + + options.qs.command = command; + return this._request(options, callback); + }, + + // Make a private API request + _private: function (command, parameters, callback) { + var options; + + if (typeof parameters === 'function') { + callback = parameters; + parameters = {}; + } + + parameters || (parameters = {}); + parameters.command = command; + parameters.nonce = nonce(); + + options = { + method: 'POST', + url: PRIVATE_API_URL, + form: parameters, + headers: this._getPrivateHeaders(parameters) + }; + + return this._request(options, callback); + }, + + ///// + // PUBLIC METHODS + + returnTicker: function (callback) { + return this._public('returnTicker', callback); + }, + + return24hVolume: function (callback) { + return this._public('return24hVolume', callback); + }, + + returnOrderBook: function (currencyA, currencyB, callback) { + var currencyPair; + + if (typeof currencyB === 'function') { + currencyPair = currencyA; + callback = currencyB; + currencyB = null; + } + + else { + currencyPair = joinCurrencies(currencyA, currencyB); + } + + var parameters = { + currencyPair: currencyPair + }; + + return this._public('returnOrderBook', parameters, callback); + }, + + returnChartData: function (currencyA, currencyB, period, start, end, callback) { + var parameters = { + currencyPair: joinCurrencies(currencyA, currencyB), + period: period, + start: start, + end: end + }; + + return this._public('returnChartData', parameters, callback); + }, + + returnCurrencies: function (callback) { + return this._public('returnCurrencies', callback); + }, + + returnLoanOrders: function (currency, callback) { + return this._public('returnLoanOrders', { currency: currency }, callback); + }, + + ///// + // PRIVATE METHODS + + returnBalances: function (callback) { + return this._private('returnBalances', {}, callback); + }, + + returnCompleteBalances: function (account, callback) { + var parameters = {}; + + if (typeof account === 'function') { + callback = account; + } + + else if (typeof account === 'string' && !!account) { + parameters.account = account; + } + + return this._private('returnCompleteBalances', parameters, callback); + }, + + returnDepositAddresses: function (callback) { + return this._private('returnDepositAddresses', {}, callback); + }, + + generateNewAddress: function (currency, callback) { + return this._private('generateNewAddress', { currency: currency }, callback); + }, + + returnDepositsWithdrawals: function (start, end, callback) { + return this._private('returnDepositsWithdrawals', { start: start, end: end }, callback); + }, + + // can be called with `returnOpenOrders('all', callback)` + returnOpenOrders: function (currencyA, currencyB, callback) { + var currencyPair; + + if (typeof currencyB === 'function') { + currencyPair = currencyA; + callback = currencyB; + currencyB = null; + } + + else { + currencyPair = joinCurrencies(currencyA, currencyB); + } + + var parameters = { + currencyPair: currencyPair + }; + + return this._private('returnOpenOrders', parameters, callback); + }, + + returnTradeHistory: function (currencyA, currencyB, start, end, callback) { + if (arguments.length < 5) { + callback = start; + start = Date.now() / 1000 - 60 * 60; + end = Date.now() / 1000 + 60 * 60; // Some buffer in case of client/server time out of sync. + } + + var parameters = { + currencyPair: joinCurrencies(currencyA, currencyB), + start: start, + end: end + }; + + return this._private('returnTradeHistory', parameters, callback); + }, + + returnOrderTrades: function (orderNumber, callback) { + var parameters = { + orderNumber: orderNumber + }; + + return this._private('returnOrderTrades', parameters, callback); + }, + + buy: function (currencyA, currencyB, rate, amount, callback) { + var parameters = { + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private('buy', parameters, callback); + }, + + sell: function (currencyA, currencyB, rate, amount, callback) { + var parameters = { + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount + }; + + return this._private('sell', parameters, callback); + }, + + cancelOrder: function (currencyA, currencyB, orderNumber, callback) { + var parameters = { + currencyPair: joinCurrencies(currencyA, currencyB), + orderNumber: orderNumber + }; + + return this._private('cancelOrder', parameters, callback); + }, + + moveOrder: function (orderNumber, rate, amount, callback) { + var parameters = { + orderNumber: orderNumber, + rate: rate, + amount: amount ? amount : null + }; + + return this._private('moveOrder', parameters, callback); + }, + + withdraw: function (currency, amount, address, callback) { + var parameters = { + currency: currency, + amount: amount, + address: address + }; + + return this._private('withdraw', parameters, callback); + }, + + returnFeeInfo: function (callback) { + return this._private('returnFeeInfo', {}, callback); + }, + + returnAvailableAccountBalances: function (account, callback) { + var parameters = {}; + + if (typeof account === 'function') { + callback = account; + } + + else if (typeof account === 'string' && !!account) { + parameters.account = account; + } + + return this._private('returnAvailableAccountBalances', parameters, callback); + }, + + returnTradableBalances: function (callback) { + return this._private('returnTradableBalances', {}, callback); + }, + + transferBalance: function (currency, amount, fromAccount, toAccount, callback) { + var parameters = { + currency: currency, + amount: amount, + fromAccount: fromAccount, + toAccount: toAccount + }; + + return this._private('transferBalance', parameters, callback); + }, + + returnMarginAccountSummary: function (callback) { + return this._private('returnMarginAccountSummary', {}, callback); + }, + + marginBuy: function (currencyA, currencyB, rate, amount, lendingRate, callback) { + var parameters = { + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount, + lendingRate: lendingRate ? lendingRate : null + }; + + return this._private('marginBuy', parameters, callback); + }, + + marginSell: function (currencyA, currencyB, rate, amount, lendingRate, callback) { + var parameters = { + currencyPair: joinCurrencies(currencyA, currencyB), + rate: rate, + amount: amount, + lendingRate: lendingRate ? lendingRate : null + }; + + return this._private('marginSell', parameters, callback); + }, + + getMarginPosition: function (currencyA, currencyB, callback) { + var parameters = { + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private('getMarginPosition', parameters, callback); + }, + + closeMarginPosition: function (currencyA, currencyB, callback) { + var parameters = { + currencyPair: joinCurrencies(currencyA, currencyB) + }; + + return this._private('closeMarginPosition', parameters, callback); + }, + + createLoanOffer: function (currency, amount, duration, autoRenew, lendingRate, callback) { + var parameters = { + currency: currency, + amount: amount, + duration: duration, + autoRenew: autoRenew, + lendingRate: lendingRate + }; + + return this._private('createLoanOffer', parameters, callback); + }, + + cancelLoanOffer: function (orderNumber, callback) { + var parameters = { + orderNumber: orderNumber + }; + + return this._private('cancelLoanOffer', parameters, callback); + }, + + returnOpenLoanOffers: function (callback) { + return this._private('returnOpenLoanOffers', {}, callback); + }, + + returnActiveLoans: function (callback) { + return this._private('returnActiveLoans', {}, callback); + }, + + returnLendingHistory: function (start, end, limit, callback) { + var parameters = { + start: start, + end: end, + limit: limit + }; + + return this._private('returnLendingHistory', parameters, callback); + }, + + toggleAutoRenew: function (orderNumber, callback) { + return this._private('toggleAutoRenew', { orderNumber: orderNumber }, callback); + } + + }; + + // Backwards Compatibility + Poloniex.prototype.getTicker = Poloniex.prototype.returnTicker; + Poloniex.prototype.get24hVolume = Poloniex.prototype.return24hVolume; + Poloniex.prototype.getOrderBook = Poloniex.prototype.returnOrderBook; + Poloniex.prototype.getTradeHistory = Poloniex.prototype.returnChartData; + Poloniex.prototype.myBalances = Poloniex.prototype.returnBalances; + Poloniex.prototype.myOpenOrders = Poloniex.prototype.returnOpenOrders; + Poloniex.prototype.myTradeHistory = Poloniex.prototype.returnTradeHistory; + + return Poloniex; +})(); \ No newline at end of file diff --git a/exchange/wrappers/poloniexClient.js b/exchange/wrappers/poloniexClient.js new file mode 100644 index 000000000..1f6529d7b --- /dev/null +++ b/exchange/wrappers/poloniexClient.js @@ -0,0 +1,357 @@ +exports.newAPIClient = function newAPIClient(pKey, pSecret, logger) { + + /* + /* ATENTION: This Library is used both a the cloud (AACloud) and also at the AAWeb on the server side without any modifications. + /* There is a second version, stripped of all the inner functionality that runs at AAWeb browser side, which channels the requests to the server side one. + */ + + const FULL_LOG = true; + const LOG_FILE_CONTENT = true; + const MODULE_NAME = "poloniexClient"; + + const retry = require('../exchangeUtils').retry; + const includes = require('../exchangeUtils').includes; + const _ = require('lodash'); + const Poloniex = require("./poloniex"); + const exchangeProperties = require('./poloniexProperties.json'); + + const recoverableErrors = [ + 'SOCKETTIMEDOUT', + 'TIMEDOUT', + 'CONNRESET', + 'CONNREFUSED', + 'NOTFOUND', + '429', + '522', + '504', + '503', + '500', + '502', + 'Empty response', + 'Please try again in a few minutes.', + 'Nonce must be greater than', + 'Internal error. Please try again.', + 'Connection timed out. Please try again.', + 'ENOTFOUND', + 'ECONNREFUSED', + 'CONNREFUSED', + 'NOTFOUND', + 'ESOCKETTIMEDOUT', + 'ECONNRESET', + 'Connection timed out', + 'Connection Error', + 'Order not found, or you are not the person who placed it.' + ]; + + // errors that might mean the API call succeeded + const unknownResultErrors = [ + 'ETIMEDOUT', + ] + + let API = new Poloniex(pKey, pSecret); + + let thisObject = { + getTicker: getTicker, + getOpenPositions: getOpenPositions, + getExecutedTrades: getExecutedTrades, + buy: buy, + sell: sell, + movePosition: movePosition, + getPublicTradeHistory: getPublicTradeHistory, + getExchangeProperties: getExchangeProperties + }; + + return thisObject; + + function joinCurrencies(currencyA, currencyB) { + // If only one arg, then return the first + if (typeof currencyB !== 'string') { + return currencyA; + } + + return currencyA + '_' + currencyB; + } + + + /* + * Returns the properties for this exchange + */ + function getExchangeProperties() { + return exchangeProperties; + } + + /* + * Returns the price for a given pair of assets. + * The object returned is an array of trades: + * ticker = { + * bid, Number + * ask, Number + * last Number + * }; + */ + function getTicker(pMarket, callBack) { + + const handle = (err, response) => { + let ticker; + let market = joinCurrencies(pMarket.assetA, pMarket.assetB); + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + ticker = { + bid: Number(response[market].highestBid), + ask: Number(response[market].lowestAsk), + last: Number(response[market].last) + }; + } + callBack(err, ticker); + }; + + const fetch = next => API.getTicker(analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* + * Returns the open positions ath the exchange for a given market and user account. + * The object returned is an array of positions: + * position = { + * id, + * type, + * rate, Number + * amountA, Number + * amountB, Number + * fee, (not required) Number + * datetime + * }; + */ + function getOpenPositions(pMarket, callBack) { + + const handle = (err, response) => { + let exchangePositions = []; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + for (let i = 0; i < response.length; i++) { + let openPosition = { + id: response[i].orderNumber, + type: response[i].type, + rate: Number(response[i].rate), + amountA: Number(response[i].total), + amountB: Number(response[i].amount), + date: (new Date(response[i].time)).valueOf() + }; + exchangePositions.push(openPosition); + } + } + callBack(err, exchangePositions); + }; + + const fetch = next => API.returnOpenOrders(pMarket.assetA, pMarket.assetB, analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* + * Returns the trades for a given order number. + * The object returned is an array of positions: + * position = { + * id, + * type, + * rate, Number + * amountA, Number + * amountB, Number + * fee, Number + * datetime + * }; + */ + function getExecutedTrades(pPositionId, callBack) { + + const handle = (err, response) => { + let trades = []; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + for (let i = 0; i < response.length; i++) { + let trade = { + id: response[i].tradeID, + type: response[i].type, + rate: Number(response[i].rate), + amountA: Number(response[i].total), + amountB: Number(response[i].amount), + fee: Number(response[i].fee), + date: (new Date(response[i].date)).valueOf() + } + trades.push(trade); + } + } else if (err.result === global.CUSTOM_OK_RESPONSE.result) { + // No trades found at the exchange for the position, but returning empy trades object. + err = global.DEFAULT_OK_RESPONSE; + } + callBack(err, trades); + }; + + const fetch = next => API.returnOrderTrades(pPositionId, analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* + * Creates a new buy order. + * The orderNumber is returned. String + */ + function buy(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) { + + const handle = (err, response) => { + let orderNumber; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + orderNumber = response.orderNumber; + } + callBack(err, orderNumber); + }; + + const fetch = next => API.buy(pCurrencyA, pCurrencyB, pRate, pAmount, analizeResponse(next)); + + retry(null, fetch, handle); + + } + + /* + * Creates a new sell order. + * The orderNumber is returned. String + */ + function sell(pCurrencyA, pCurrencyB, pRate, pAmount, callBack) { + + const handle = (err, response) => { + let orderNumber; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + orderNumber = response.orderNumber; + } + callBack(err, orderNumber); + }; + + const fetch = next => API.sell(pCurrencyA, pCurrencyB, pRate, pAmount, analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* + * Move an existing position to the new rate. + * The orderNumber is returned. String + */ + function movePosition(pPosition, pNewRate, pNewAmountB, callBack) { + + const handle = (err, response) => { + let orderNumber; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + orderNumber = response.orderNumber; + } + callBack(err, orderNumber); + }; + + const fetch = next => API.moveOrder(pPosition.id, pNewRate, pNewAmountB, analizeResponse(next)); + + retry(null, fetch, handle); + } + + /* //TODO Pending Review + * Returns all the trade history from the Exchange since startTime to endTime orderd by tradeId. + * It's possible that the exchange doesn't support this method. + * The object returned is an array of trades: + * trade = { + * tradeIdAtExchange, + * marketIdAtExchange, + * type, + * rate, Number + * amountA, Number + * amountB, Number + * datetime + * }; + */ + function getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack) { + + const handle = (err, response) => { + let trades = []; + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + for (let i = 0; i < response.length; i++) { + let trade = { + tradeIdAtExchange: response[i].tradeID, + type: response[i].type, + rate: Number(response[i].rate), + amountA: Number(response[i].total), + amountB: Number(response[i].amount), + fee: Number(response[i].fee), + datetime: (new Date(response[i].date)).valueOf() + } + trades.push(trade); + } + } + callBack(err, trades); + }; + + const fetch = next => API.returnPublicTradeHistory(assetA, assetB, startTime, endTime, analizeResponse(next)); + + retry(null, fetch, handle); + } + + function isValidLot(price, amount) { + return amount * price >= 0.0001; + } + + function analizeResponse(callBack) { + + return (exchangeErr, exchangeResponse) => { + let error; + + /* This function analizes the different situations we might encounter trying to access Poloniex and returns appropiate standard errors. */ + + let stringExchangeErr = JSON.stringify(exchangeErr); + let stringExchangeResponse = JSON.stringify(exchangeResponse); + + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeErr = " + stringExchangeErr); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] analizeResponse -> exchangeResponse = " + stringExchangeResponse); } + + if (exchangeErr) { + error = global.DEFAULT_FAIL_RESPONSE; + } else if (!exchangeResponse) { + error = { + result: global.DEFAULT_FAIL_RESPONSE.result, + message: 'Empty response' + }; + } else if (includes(exchangeResponse.error, ['Please complete the security check to proceed.'])) { + error = { + result: global.DEFAULT_FAIL_RESPONSE.result, + message: 'The IP has been flagged by CloudFlare.' + }; + } else if (includes(exchangeResponse.error, [''])) { + error = { + result: global.DEFAULT_FAIL_RESPONSE.result, + message: exchangeResponse + }; + } else if (includes(exchangeResponse.error, ['Not enough'])) { + error = { + result: global.DEFAULT_RETRY_RESPONSE.result, + message: exchangeResponse.error + }; + } else if (includes(exchangeResponse.error, ['Order not found, or you are not the person who placed it.'])) { + error = { + result: global.CUSTOM_OK_RESPONSE.result, + message: exchangeResponse.error + }; + } else if (exchangeResponse.error) { + error = { + result: global.DEFAULT_FAIL_RESPONSE.result, + message: exchangeResponse.error + }; + } + + if (error) { + if (includes(error.message, recoverableErrors)) { + error.notFatal = true; + } + } + + return callBack(error, exchangeResponse); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] analizeResponse -> err.message = " + err.message); + return callBack(global.DEFAULT_FAIL_RESPONSE, exchangeResponse); + } + } + } + +} diff --git a/exchange/wrappers/poloniexProperties.json b/exchange/wrappers/poloniexProperties.json new file mode 100644 index 000000000..a5716ba3b --- /dev/null +++ b/exchange/wrappers/poloniexProperties.json @@ -0,0 +1,3513 @@ +{ + "currencies": ["BTC", "ETH", "XMR", "USDT"], + "assets": [ + "1CR", "ABY", "AC", "ACH", "ADN", "AEON", "AERO", "AIR", "AMP", "APH", + "ARCH", "AUR", "AXIS", "BALLS", "BANK", "BBL", "BBR", "BCC", "BCH", "BCN", + "BCY", "BDC", "BDG", "BELA", "BITCNY", "BITS", "BITUSD", "BLK", "BLOCK", + "BLU", "BNS", "BONES", "BOST", "BTC", "BTCD", "BTCS", "BTM", "BTS", + "BURN", "BURST", "C2", "CACH", "CAI", "CC", "CCN", "CGA", "CHA", "CINNI", + "CLAM", "CNL", "CNMT", "CNOTE", "COMM", "CON", "CORG", "CRYPT", "CURE", + "CVC", "CYC", "DAO", "DASH", "DCR", "DGB", "DICE", "DIEM", "DIME", "DIS", "DNS", + "DOGE", "DRKC", "DRM", "DSH", "DVK", "EAC", "EBT", "ECC", "EFL", "EMC2", + "EMO", "ENC", "ETC", "ETH", "eTOK", "EXE", "EXP", "FAC", "FCN", "FCT", + "FIBRE", "FLAP", "FLDC", "FLO", "FLT", "FOX", "FRAC", "FRK", "FRQ", "GNO", + "FVZ", "FZ", "FZN", "GAME", "GAP", "GAS", "GDN", "GEMZ", "GEO", "GIAR", "GLB", + "GML", "GNS", "GNT", "GOLD", "GPC", "GPUC", "GRC", "GRCX", "GRS", "GUE", "H2O", + "HIRO", "HOT", "HUC", "HUGE", "HVC", "HYP", "HZ", "IFC", "INDEX", "IOC", + "ITC", "IXC", "JLH", "JPC", "JUG", "KDC", "KEY", "LBC", "LC", "LCL", + "LEAF", "LGC", "LOL", "LOVE", "LQD", "LSK", "LTBC", "LTC", "LTCX", + "MAID", "MAST", "MAX", "MCN", "MEC", "METH", "MIL", "MIN", "MINT", "MMC", + "MMNXT", "MMXIV", "MNTA", "MON", "MRC", "MRS", "MTS", "MUN", "MYR", + "MZC", "N5X", "NAS", "NAUT", "NAV", "NBT", "NEOS", "NL", "NMC", "NOBL", + "NOTE", "NOXT", "NRS", "NSR", "NTX", "NXT", "NXTI", "OMG", "OMNI", "OPAL", + "PAND", "PASC", "PAWN", "PIGGY", "PINK", "PLX", "PMC", "POT", "PPC", "PRC", + "PRT", "PTS", "Q2C", "QBK", "QCN", "QORA", "QTL", "RADS", "RBY", "RDD", "REP", + "RIC", "RZR", "SBD", "SC", "SDC", "SHIBE", "SHOPX", "SILK", "SJCX", + "SLR", "SMC", "SOC", "SPA", "SQL", "SRCC", "SRG", "SSD", "STEEM", "STR", + "SUM", "SUN", "SWARM", "SXC", "SYNC", "SYS", "TAC", "TOR", "TRUST", + "TWE", "UIS", "ULTC", "UNITY", "URO", "USDE", "USDT", "UTC", "UTIL", + "UVC", "VIA", "VOOT", "VOX", "VRC", "VTC", "WC", "WDC", "WIKI", "WOLF", + "X13", "XAI", "XAP", "XBC", "XC", "XCH", "XCN", "XCP", "XCR", "XDN", + "XDP", "XEM", "XHC", "XLB", "XMG", "XMR", "XPB", "XPM", "XRP", "XSI", + "XST", "XSV", "XUSD", "XVC", "XXC", "BCH", "YACC", "YANG", "YC", "YIN", "ZEC", + "ZRX" + ], + "markets": [ + { + "pair": [ + "BTC", + "1CR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ABY" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "AC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ACH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ADN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "AEON" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "AERO" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "AIR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "AMP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "APH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ARCH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "AUR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "AXIS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BALLS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BANK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BBL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BBR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BCC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BCN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BCH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BCY" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BDC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BDG" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BELA" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BITCNY" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BITS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BITUSD" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BLK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BLOCK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BLU" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BNS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BONES" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BOST" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BTCD" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BTCS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BTM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BTS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BURN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "BURST" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "C2" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CACH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CAI" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CCN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CGA" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CHA" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CINNI" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CLAM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CNL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CNMT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CNOTE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "COMM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CON" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CORG" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CRYPT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CURE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CVC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "CYC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DAO" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DASH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DCR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DGB" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DICE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DIEM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DIME" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DIS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DNS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DOGE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DRKC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DRM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DSH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "DVK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "EAC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "EBT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ECC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "EFL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "EMC2" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "EMO" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ENC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ETC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ETH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "eTOK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "EXE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "EXP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FAC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FCN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FCT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FIBRE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FLAP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FLDC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FLO" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FLT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FOX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FRAC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FRK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FRQ" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FVZ" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FZ" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "FZN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GAME" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GAP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GAS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GDN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GEMZ" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GEO" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GIAR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GLB" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GML" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GNS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GOLD" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GPC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GPUC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GRC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GRCX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GRS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "GUE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "H2O" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "HIRO" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "HOT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "HUC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "HUGE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "HVC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "HYP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "HZ" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "IFC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "INDEX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "IOC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ITC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "IXC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "JLH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "JPC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "JUG" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "KDC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "KEY" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LBC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LCL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LEAF" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LGC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LOL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LOVE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LQD" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LSK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LTBC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LTC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "LTCX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MAID" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MAST" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MAX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MCN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MEC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "METH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MIL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MIN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MINT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MMC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MMNXT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MMXIV" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MNTA" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MON" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MRC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MRS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MTS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MUN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MYR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "MZC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "N5X" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NAS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NAUT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NAV" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NBT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NEOS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NMC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NOBL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NOTE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NOXT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NRS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NSR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NTX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NXT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "NXTI" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "OMG" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "OMNI" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "OPAL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PAND" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PASC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PAWN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PIGGY" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PINK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PLX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PMC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "POT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PPC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PRC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PRT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "PTS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "Q2C" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "QBK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "QCN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "QORA" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "QTL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "RADS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "RBY" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "RDD" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "REP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "RIC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "RZR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SBD" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SDC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SHIBE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SHOPX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SILK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SJCX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SLR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SMC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SOC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SPA" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SQL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SRCC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SRG" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SSD" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "STEEM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "STR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SUM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SUN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SWARM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SXC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SYNC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "SYS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "TAC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "TOR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "TRUST" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "TWE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "UIS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ULTC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "UNITY" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "URO" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "USDE" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "USDT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "UTC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "UTIL" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "UVC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "VIA" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "VOOT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "VOX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "VRC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "VTC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "WC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "WDC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "WIKI" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "WOLF" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "X13" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XAI" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XAP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XBC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XCH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XCN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XCP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XCR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XDN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XDP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XEM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XHC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XLB" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XMG" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XMR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XPB" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XPM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XRP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XSI" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XST" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XSV" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XUSD" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XVC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "XXC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "YACC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "YANG" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "YC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "YIN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ZEC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "BTC", + "ZRX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "BTC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "BCH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "DASH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "ETC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "ETH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "LTC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "NXT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "REP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "STR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "XMR" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "XRP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "USDT", + "ZEC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "CVC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "ETC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "BCH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "GAS" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "GNO" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "GNT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "LSK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "OMG" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "REP" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "STEEM" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "ZEC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "ETH", + "ZRX" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "XMR", + "BCN" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "XMR", + "BLK" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "XMR", + "BTCD" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "XMR", + "DASH" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "XMR", + "LTC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "XMR", + "MAID" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "XMR", + "NXT" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + }, + { + "pair": [ + "XMR", + "ZEC" + ], + "maxDecimals": 8, + "minimalOrder": { + "amount": 0.0001, + "unit": "asset" + } + } + ] +} \ No newline at end of file