From acfcad623c58ab0fd947e423d6aba3e87efad210 Mon Sep 17 00:00:00 2001 From: Matias Benitez Date: Wed, 25 Jul 2018 11:53:35 -0300 Subject: [PATCH 1/5] Bot on-memory cache proposal --- TradingBotProcessMainLoop.js | 1 + 1 file changed, 1 insertion(+) diff --git a/TradingBotProcessMainLoop.js b/TradingBotProcessMainLoop.js index 53d88c752..0b1c98814 100644 --- a/TradingBotProcessMainLoop.js +++ b/TradingBotProcessMainLoop.js @@ -21,6 +21,7 @@ run: run }; + bot["botCache"] = new Map(); let processConfig; let cloudStorage; let UI_COMMANDS; From 306f39d5134f195257888635fe85e585a787f3ab Mon Sep 17 00:00:00 2001 From: Matias Benitez Date: Thu, 2 Aug 2018 08:33:46 -0300 Subject: [PATCH 2/5] Draft multi exchange and binance client --- Assistant.js | 17 +- BinanceClient.js | 540 +++++++++++++++++++++++++++++++++++++++++++ Context.js | 2 +- ExchangeAPI.js | 359 ++++++++-------------------- PoloniexAPIClient.js | 126 +++++++--- 5 files changed, 744 insertions(+), 300 deletions(-) create mode 100644 BinanceClient.js diff --git a/Assistant.js b/Assistant.js index 32b181745..152ff676e 100644 --- a/Assistant.js +++ b/Assistant.js @@ -89,7 +89,7 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getMarketRateFromExchange -> Entering function."); } - exchangeAPI.getTicker(global.MARKET.assetA + '_' + global.MARKET.assetB, onTicker); + exchangeAPI.getTicker(global.MARKET, onTicker); return; @@ -663,10 +663,10 @@ sumAssetB = thisObject.truncDecimals(sumAssetB); if (position.amountB !== sumAssetB) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> position.amountB = " + position.amountB); - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> sumAssetB = " + sumAssetB); + logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> position.amountB = " + position.amountB); + logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> sumAssetB = " + sumAssetB); - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> Cannot be confirmed that the order was executed. It must be manually cancelled by the user or cancelled by the exchange itself."); + logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> Cannot be confirmed that the order was executed. It must be manually cancelled by the user or cancelled by the exchange itself."); logger.write(MODULE_NAME, "[HINT] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> If the process was abruptally cancelled and then restarted, it is possible that now is not sincronized with the exchange."); logger.write(MODULE_NAME, "[HINT] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> In any case, to continue, you must manually delete the orders at the exchange."); callBack(global.DEFAULT_FAIL_RESPONSE); @@ -1230,6 +1230,13 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } + + function getPublicTradeHistory(assetA, assetB, startTime, endTime, callback) { + + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTradeHistory -> Entering function."); } + + exchangeAPI.getPublicTradeHistory(assetA, assetB, startTime, endTime, callback); + } function recalculateRateAverages() { @@ -1409,7 +1416,7 @@ function truncDecimals(pFloatValue) { - return parseFloat(parseFloat(pFloatValue).toFixed(8)); + return parseFloat(parseFloat(pFloatValue).toFixed(6)); //TODO Number of decimals depends on the exchange } }; \ No newline at end of file diff --git a/BinanceClient.js b/BinanceClient.js new file mode 100644 index 000000000..b2b88253f --- /dev/null +++ b/BinanceClient.js @@ -0,0 +1,540 @@ +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/Context.js b/Context.js index 9fceb3106..9dd5053a5 100644 --- a/Context.js +++ b/Context.js @@ -329,7 +329,7 @@ */ const INITIAL_INVESTMENT_A = 0; // This is just for this release of the platform. - const INITIAL_INVESTMENT_B = .001; // This is just for this release of the platform. + const INITIAL_INVESTMENT_B = .002; // This is just for this release of the platform. thisObject.executionContext = { investment: { // This is used to calculate profits. diff --git a/ExchangeAPI.js b/ExchangeAPI.js index d3ea64b8a..21ba4a886 100644 --- a/ExchangeAPI.js +++ b/ExchangeAPI.js @@ -16,7 +16,8 @@ getExecutedTrades: getExecutedTrades, putPosition: putPosition, movePosition: movePosition, - getTicker: getTicker + getTicker: getTicker, + getPublicTradeHistory: getPublicTradeHistory }; let poloniexApiClient; @@ -24,12 +25,12 @@ return thisObject; function initialize(callBackFunction) { - try { 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); + //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); callBackFunction(global.DEFAULT_OK_RESPONSE); @@ -40,147 +41,65 @@ } } - function getOpenPositions(pMarket, callBackFunction) { - + /* + * 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 + * }; + */ + function getOpenPositions(pMarket, callBack) { try { 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, onExchangeCallReturned); + poloniexApiClient.API.returnOpenOrders(pMarket.assetA, pMarket.assetB, callBack); - function onExchangeCallReturned(err, exchangeResponse) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getOpenPositions -> onExchangeCallReturned -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getOpenPositions -> onExchangeCallReturned -> err = " + err); } - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { logger.write(MODULE_NAME, "[INFO] getOpenPositions -> onExchangeCallReturned -> exchangeResponse = " + JSON.stringify(exchangeResponse)); } - - poloniexApiClient.API.analizeResponse(logger, err, exchangeResponse, callBackFunction, onResponseOk); - - function onResponseOk() { - - /* - - This is what we receive from the exchange. We will convert this to our standard format for later use. - - [ { orderNumber: '151918418632', - type: 'sell', - rate: '20000.00000000', - startingAmount: '0.00010000', - amount: '0.00010000', - total: '2.00000000', - date: '2018-02-24 11:14:17', - margin: 0 } ] - - */ - - let exchangePositions = []; - - for (let i = 0; i < exchangeResponse.length; i++) { - - let openPosition = { - id: exchangeResponse[i].orderNumber, - type: exchangeResponse[i].type, - rate: exchangeResponse[i].rate, - amountA: exchangeResponse[i].total, - amountB: exchangeResponse[i].amount, - date: (new Date(exchangeResponse[i].date)).valueOf() - }; - - exchangePositions.push(openPosition); - } - - callBackFunction(global.DEFAULT_OK_RESPONSE, exchangePositions); - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] getOpenPositions -> onExchangeCallReturned -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } } catch (err) { logger.write(MODULE_NAME, "[ERROR] getOpenPositions -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); } } - function getExecutedTrades(pPositionId, callBackFunction) { - + /* + * Returns the trades for a given order number. + * The object returned is an array of positions: + * position = { + * id, + * type, + * rate, + * amountA, + * amountB, + * fee, + * datetime + * }; + */ + function getExecutedTrades(pPositionId, callBack) { try { 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, onExchangeCallReturned); + poloniexApiClient.API.returnOrderTrades(pPositionId, callBack); - function onExchangeCallReturned(err, exchangeResponse) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getExecutedTrades -> onExchangeCallReturned -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getExecutedTrades -> onExchangeCallReturned -> err = " + err); } - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { logger.write(MODULE_NAME, "[INFO] getExecutedTrades -> onExchangeCallReturned -> exchangeResponse = " + JSON.stringify(exchangeResponse)); } - - poloniexApiClient.API.analizeResponse(logger, err, exchangeResponse, callBackFunction, onResponseOk); - - function onResponseOk() { - - /* - - This is what we receive from the exchange. We will convert this to our standard format for later use. - - [ - { - "globalTradeID": 20825863, - "tradeID": 147142, - "currencyPair": - "BTC_XVC", - "type": "buy", - "rate": "0.00018500", - "amount": "455.34206390", - "total": "0.08423828", - "fee": "0.00200000", - "date": "2016-03-14 01:04:36" - }, - ...] - - */ - - let trades = []; - - for (let i = 0; i < exchangeResponse.length; i++) { - - let trade = { - id: exchangeResponse[i].tradeID, - type: exchangeResponse[i].type, - rate: exchangeResponse[i].rate, - amountA: exchangeResponse[i].total, - amountB: exchangeResponse[i].amount, - fee: exchangeResponse[i].fee, - date: (new Date(exchangeResponse[i].date)).valueOf() - } - - trades.push(trade); - } - - callBackFunction(global.DEFAULT_OK_RESPONSE, trades); - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] getExecutedTrades -> onExchangeCallReturned -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } } catch (err) { logger.write(MODULE_NAME, "[ERROR] getExecutedTrades -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); } } - function putPosition(pMarket, pType, pRate, pAmountA, pAmountB, callBackFunction) { - + /* + * Creates a new buy or sell order. + * The orderNumber is returned. + */ + function putPosition(pMarket, pType, pRate, pAmountA, pAmountB, callBack) { try { if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> Entering function."); } @@ -191,186 +110,90 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> pAmountB = " + pAmountB); } if (pType === "buy") { - poloniexApiClient.API.buy(pMarket.assetA, pMarket.assetB, pRate, pAmountB, onExchangeCallReturned); + poloniexApiClient.API.buy(pMarket.assetA, pMarket.assetB, pRate, pAmountB, callBack); return; } if (pType === "sell") { - poloniexApiClient.API.sell(pMarket.assetA, pMarket.assetB, pRate, pAmountB, onExchangeCallReturned); + poloniexApiClient.API.sell(pMarket.assetA, pMarket.assetB, pRate, pAmountB, callBack); return; } logger.write(MODULE_NAME, "[ERROR] putPosition -> pType must be either 'buy' or 'sell'."); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); return; - function onExchangeCallReturned(err, exchangeResponse) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> onExchangeCallReturned -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> onExchangeCallReturned -> err = " + err); } - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { logger.write(MODULE_NAME, "[INFO] putPosition -> onExchangeCallReturned -> exchangeResponse = " + JSON.stringify(exchangeResponse)); } - - poloniexApiClient.API.analizeResponse(logger, err, exchangeResponse, callBackFunction, onResponseOk); - - function onResponseOk() { - - /* - - This is what we can receive from the exchange. - - { - "orderNumber":31226040, - "resultingTrades": - [{ - "amount":"338.8732", - "date":"2014-10-18 23:03:21", - "rate":"0.00000173", - "total":"0.00058625", - "tradeID":"16164", - "type":"buy" - }] - } - - */ - - callBackFunction(global.DEFAULT_OK_RESPONSE, exchangeResponse.orderNumber); - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] putPosition -> onExchangeCallReturned -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } catch (err) { logger.write(MODULE_NAME, "[ERROR] putPosition -> err = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); } } - function movePosition(pPosition, pNewRate, pNewAmountB, callBackFunction) { - + /* + * Move an existing position to the new rate. + * The new orderNumber is returned. + */ + function movePosition(pPosition, pNewRate, pNewAmountB, callBack) { try { 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.id, pNewRate, pNewAmountB, onExchangeCallReturned); - - function onExchangeCallReturned(err, exchangeResponse) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> onExchangeCallReturned -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> onExchangeCallReturned -> err = " + err); } - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> onExchangeCallReturned -> exchangeResponse = " + JSON.stringify(exchangeResponse)); } - - try { - - poloniexApiClient.API.analizeResponse(logger, err, exchangeResponse, callBackFunction, onResponseOk); - - function onResponseOk() { - - if (exchangeResponse.success !== 1) { - logger.write(MODULE_NAME, "[ERROR] movePosition -> onExchangeCallReturned -> exchangeResponse.success = " + exchangeResponse.success); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - /* - - This is what we can receive from the exchange. - - { - "orderNumber":31226040, - "resultingTrades": - [{ - "amount":"338.8732", - "date":"2014-10-18 23:03:21", - "rate":"0.00000173", - "total":"0.00058625", - "tradeID":"16164", - "type":"buy" - }] - } - - */ - - callBackFunction(global.DEFAULT_OK_RESPONSE, exchangeResponse.orderNumber); - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] movePosition -> onExchangeCallReturned -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } + poloniexApiClient.API.moveOrder(pPosition, pNewRate, pNewAmountB, callBack); } catch (err) { logger.write(MODULE_NAME, "[ERROR] movePosition -> err = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); } } - function getTicker(pMarket, callBackFunction) { - + /* + * 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(onExchangeCallReturned); - - function onExchangeCallReturned(err, exchangeResponse) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> onExchangeCallReturned -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> onExchangeCallReturned -> err = " + err); } - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { logger.write(MODULE_NAME, "[INFO] movePosition -> onExchangeCallReturned -> exchangeResponse = " + JSON.stringify(exchangeResponse)); } - - try { - - poloniexApiClient.API.analizeResponse(logger, err, exchangeResponse, callBackFunction, onResponseOk); - - function onResponseOk() { - - /* - - This is what we can receive from the exchange. - - {"BTC_LTC":{ "last":"0.0251", - "lowestAsk":"0.02589999", - "highestBid":"0.0251", - "percentChange":"0.02390438", - "baseVolume":"6.16485315", - "quoteVolume":"245.82513926" - }, - "BTC_NXT":{ "last":"0.00005730", - "lowestAsk":"0.00005710", - "highestBid":"0.00004903", - "percentChange":"0.16701570", - "baseVolume":"0.45347489", - "quoteVolume":"9094" - }, - ... } - - */ - - let ticker = { - bid: Number(exchangeResponse[pMarket].highestBid), - ask: Number(exchangeResponse[pMarket].lowestAsk), - last: Number(exchangeResponse[pMarket].last) - }; - - callBackFunction(global.DEFAULT_OK_RESPONSE, ticker); - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] movePosition -> onExchangeCallReturned -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } + poloniexApiClient.API.returnTicker(pMarket.assetA, pMarket.assetB, callBack); } catch (err) { logger.write(MODULE_NAME, "[ERROR] movePosition -> err = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); + } + } + + /* + * 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, + * amountA, + * amountB, + * datetime + * }; + */ + 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); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] getTradeHistory -> err = " + err.message); + callback(global.DEFAULT_FAIL_RESPONSE); } } }; \ No newline at end of file diff --git a/PoloniexAPIClient.js b/PoloniexAPIClient.js index fa378f521..5014b3f17 100644 --- a/PoloniexAPIClient.js +++ b/PoloniexAPIClient.js @@ -1,4 +1,6 @@ -exports.newPoloniexAPIClient = function newPoloniexAPIClient(pKey, pSecret) { +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. @@ -84,7 +86,7 @@ err = 'Empty response'; } - callback(err, body); + thisObject.analizeResponse(err, body, callback); }); return thisObject; @@ -137,8 +139,21 @@ ///// // PUBLIC METHODS - returnTicker: function (callback) { - return thisObject._public('returnTicker', callback); + 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) { @@ -220,7 +235,25 @@ currencyPair: joinCurrencies(currencyA, currencyB) }; - return thisObject._private('returnOpenOrders', parameters, callback); + 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) { @@ -236,7 +269,26 @@ orderNumber: orderNumber }; - return thisObject._private('returnOrderTrades', parameters, callback); + 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) { @@ -246,7 +298,15 @@ amount: amount }; - return thisObject._private('buy', parameters, callback); + 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) { @@ -256,7 +316,15 @@ amount: amount }; - return thisObject._private('sell', parameters, callback); + 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) { @@ -275,7 +343,15 @@ amount: amount ? amount : null }; - return thisObject._private('moveOrder', parameters, callback); + 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) { @@ -399,7 +475,7 @@ return thisObject._private('toggleAutoRenew', { orderNumber: orderNumber }, callback); }, - analizeResponse: function (logger, exchangeErr, exchangeResponse, notOkCallBack, okCallBack) { + analizeResponse: function (exchangeErr, exchangeResponse, callBack) { const FULL_LOG = true; const LOG_FILE_CONTENT = true; @@ -407,10 +483,10 @@ /* This function analizes the different situations we might encounter trying to access Poloniex and returns appropiate standard errors. */ - try { + let stringExchangeResponse = JSON.stringify(exchangeResponse); + let stringExchangeErr = JSON.stringify(exchangeErr); - 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); } @@ -429,33 +505,31 @@ 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); - notOkCallBack(global.DEFAULT_RETRY_RESPONSE); + callBack(global.DEFAULT_RETRY_RESPONSE); return; } else { - if (JSON.stringify(exchangeResponse).indexOf("error") > 0) { + 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) = " + JSON.stringify(exchangeErr)); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeErr = " + exchangeErr); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeResponse = " + exchangeResponse); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> JSON.stringify(exchangeResponse) = " + JSON.stringify(exchangeResponse)); + 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 (JSON.stringify(exchangeResponse).indexOf("Not enough") > 0) { + if (stringExchangeResponse.indexOf("Not enough") > 0) { let err = { resutl: global.CUSTOM_FAIL_RESPONSE.result, message: exchangeResponse.error }; - notOkCallBack(err); + callBack(err); } else { - notOkCallBack(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); } return; @@ -464,22 +538,22 @@ if (exchangeErr) { logger.write(MODULE_NAME, "[ERROR] analizeResponse -> Unexpected error trying to contact the Exchange."); - logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeErr = " + exchangeErr); + logger.write(MODULE_NAME, "[ERROR] analizeResponse -> exchangeErr = " + stringExchangeErr); - notOkCallBack(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); return; } else { logger.write(MODULE_NAME, "[INFO] analizeResponse -> No problem found."); - okCallBack(); + callBack(global.DEFAULT_OK_RESPONSE, exchangeResponse); return; } } } catch (err) { logger.write(MODULE_NAME, "[ERROR] analizeResponse -> err.message = " + err.message); - notOkCallBack(global.DEFAULT_FAIL_RESPONSE); + callBack(global.DEFAULT_FAIL_RESPONSE); return; } } From 0c7df9e08b450586cb41ac60c5507d30a990d8ae Mon Sep 17 00:00:00 2001 From: Matias Benitez Date: Mon, 13 Aug 2018 15:36:29 -0300 Subject: [PATCH 3/5] 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 From f5135b8df40391196c4ac14cb13e6b202f330de0 Mon Sep 17 00:00:00 2001 From: Matias Benitez Date: Thu, 16 Aug 2018 15:10:47 -0300 Subject: [PATCH 4/5] Merge changes --- Assistant.js | 24 ++-- Context.js | 14 ++- Project.njsproj | 15 ++- Root.js | 170 ++++++++++++++++++++++------ Run.js | 4 +- StatusReport.js | 20 +++- TradingBotProcessMainLoop.js | 12 +- exchange/ExchangeAPI.js | 7 +- exchange/exchangeUtils.js | 11 +- exchange/wrappers/binanceClient.js | 12 +- exchange/wrappers/poloniexClient.js | 3 +- 11 files changed, 224 insertions(+), 68 deletions(-) diff --git a/Assistant.js b/Assistant.js index 8856864fb..e34cf5884 100644 --- a/Assistant.js +++ b/Assistant.js @@ -487,7 +487,12 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> positionFound -> Entering function."); } - /* + /* We update the final amountA based on what we got from the exchange. + This is needed since the values could be different from what we calculate. + */ + position.amountA = exchangePosition.amountA; + + /* We need to know if the order was partially executed. To know that, we compare the amounts on file with the ones received from the exchange. If they are the same, then the order is intact. Otherwise, to confirm a partial execution, @@ -497,8 +502,7 @@ if (position.amountB === exchangePosition.amountB) { - /* Position is still there, untouched. We update the final amountA on exchange. */ - position.amountA = exchangePosition.amountA; + /* Position is still there, untouched. Nothing to do here. */ next(); return; @@ -558,10 +562,7 @@ if (thisPosition.id === pPositionId) { - let feeRate = 0.0025; // Default fee - - if (Math.random(1) < 0.5) - feeRate = 0.0015; // Some times we will pay less fee + let feeRate = 0.002; // Default backtesting fee simulation let trade = { id: Math.trunc(Math.random(1) * 1000000), @@ -630,7 +631,6 @@ callBack(global.DEFAULT_FAIL_RESPONSE); } } - } catch (err) { logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> err = " + err.message); callBack(global.DEFAULT_FAIL_RESPONSE); @@ -749,9 +749,9 @@ let exchangeParam = exchangeAPI.getMaxDecimalPositions(); let minValue = '0.' + (1).toPrecision(exchangeParam-1).split('.').reverse().join(''); - let decimalPositions = parseFloat(parseFloat(minValue).toFixed(exchangeParam)); + let exchangePrecision = parseFloat(parseFloat(minValue).toFixed(exchangeParam)); - if (Math.abs(position.amountA - sumAssetA) > decimalPositions || Math.abs(position.amountB - sumAssetB) > decimalPositions) { + if (Math.abs(position.amountA - sumAssetA) > exchangePrecision || Math.abs(position.amountB - sumAssetB) > exchangePrecision) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> position.amountA = " + position.amountA); logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> sumAssetA = " + sumAssetA); @@ -857,8 +857,8 @@ context.executionContext.balance.assetA = thisObject.truncDecimals(context.executionContext.balance.assetA + assetA); context.executionContext.balance.assetB = thisObject.truncDecimals(context.executionContext.balance.assetB - assetB); - let calculatedBalance = - context.executionContext.availableBalance.assetA = thisObject.truncDecimals(context.executionContext.availableBalance.assetA + assetA); + + 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. */ diff --git a/Context.js b/Context.js index 9dd5053a5..133598a8b 100644 --- a/Context.js +++ b/Context.js @@ -70,15 +70,18 @@ let runIndex; // This is the index for this run and depends on the startMode. + let totalAlgobots; + return thisObject; - function initialize(pStatusDependencies, callBackFunction) { + function initialize(pStatusDependencies, pTotalAlgobots, callBackFunction) { try { if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } statusDependencies = pStatusDependencies; + totalAlgobots = pTotalAlgobots; /* @@ -114,8 +117,6 @@ case global.DEFAULT_OK_RESPONSE.result: { if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> onDone -> Execution finished well."); } - bot.hasTheBotJustStarted = false; - callBackFunction(global.DEFAULT_OK_RESPONSE); return; } @@ -328,8 +329,8 @@ once we advance into multiple competitions scheme. */ - const INITIAL_INVESTMENT_A = 0; // This is just for this release of the platform. - const INITIAL_INVESTMENT_B = .002; // This is just for this release of the platform. + const INITIAL_INVESTMENT_A = 0; // This is just for this release of the platform. + const INITIAL_INVESTMENT_B = .002 / totalAlgobots; // This is just for this release of the platform. thisObject.executionContext = { investment: { // This is used to calculate profits. @@ -384,6 +385,9 @@ switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> onDone -> Execution finished well."); } + + bot.hasTheBotJustStarted = false; + callBackFunction(global.DEFAULT_OK_RESPONSE); return; } diff --git a/Project.njsproj b/Project.njsproj index a7e60900f..52d61dad1 100644 --- a/Project.njsproj +++ b/Project.njsproj @@ -39,6 +39,12 @@ Code + + + + + + Code @@ -52,13 +58,12 @@ Code - + Code Code - Code @@ -70,8 +75,14 @@ + + + + + + diff --git a/Root.js b/Root.js index cf2dd5839..2e689d2a0 100644 --- a/Root.js +++ b/Root.js @@ -386,7 +386,7 @@ exports.newRoot = function newRoot() { } - /* We tesst each type of start Mode to get what to run and how. */ + /* We test each type of start Mode to get what to run and how. */ if (processConfig.startMode.allMonths !== undefined) { @@ -756,57 +756,163 @@ exports.newRoot = function newRoot() { function runTradingBot(pBotConfig, pProcessConfig) { try { - const DEBUG_MODULE = require(ROOT_DIR + 'DebugLog'); - let logger; - logger = DEBUG_MODULE.newDebugLog(); - logger.bot = pBotConfig; + if (FULL_LOG === true) { console.log(logDisplace + "[INFO] start -> findProcess -> runTradingBot -> Entering function."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> findProcess -> runTradingBot -> Entering function."); } + if (pBotConfig.genes !== undefined) { - let tradingBotMainLoop = TRADING_BOT_MAIN_LOOP_MODULE.newTradingBotProcessMainLoop(pBotConfig, logger); - tradingBotMainLoop.initialize(UI_COMMANDS, pProcessConfig, onInitializeReady); + /* Here, based on the gene we will create one instance for each combination of genes */ - function onInitializeReady(err) { + let valueMatrix = []; - if (err.result === global.DEFAULT_OK_RESPONSE.result) { + for (let i = 0; i < pBotConfig.genes.length; i++) { - tradingBotMainLoop.run(whenRunFinishes); + let gene = pBotConfig.genes[i]; - function whenRunFinishes(err) { + let possibleValues = []; - pBotConfig.loopCounter = 0; + for (let j = gene.lowerLimit; j <= gene.upperLimit; j++) { - let botId; + possibleValues.push(j); - botId = pBotConfig.devTeam + "." + pBotConfig.codeName + "." + pBotConfig.process; + } - if (err.result === global.DEFAULT_OK_RESPONSE.result) { + valueMatrix.push(possibleValues); - logger.write(MODULE_NAME, "[INFO] start -> findProcess -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully."); - logger.write(MODULE_NAME, "[INFO] start -> findProcess -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId); + } - console.log(logDisplace + "Root : [INFO] start -> findProcess -> runTradingBot -> onInitializeReady -> whenStartFinishes -> botId = " + botId); - console.log(logDisplace + "Root : [INFO] start -> findProcess -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully."); - logger.persist(); + /* + Now we have all the possible genes values in a multi-dimensional matrix. + We will go thorugh each of the elements of each dimension and get each combination possible with the other dimmensions. + */ + + let combinations = []; + let combination = []; + let dimensionIndex = 0; + + calculateCombinations(dimensionIndex, combination); + + function calculateCombinations(pDimensionIndex, pCombination) { + + let dimension = valueMatrix[pDimensionIndex]; + + for (let j = 0; j < dimension.length; j++) { + + let copy = JSON.parse(JSON.stringify(pCombination)); + + let value = dimension[j]; + copy.push(value); + + if (pDimensionIndex < valueMatrix.length - 1) { + + calculateCombinations(pDimensionIndex + 1, copy); } else { - logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> onInitializeReady -> whenStartFinishes -> err = " + err.message); - logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. "); - logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bye."); - logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId); - console.log(logDisplace + "Root : [ERROR] start -> findProcess -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished with errors. Please check the logs."); - logger.persist(); + combinations.push(copy); + } } + } - } else { - logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> onInitializeReady -> err = " + err.message); - logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> onInitializeReady -> Bot will not be started. "); - console.log(logDisplace + "Root : [ERROR] start -> findProcess -> runTradingBot -> onInitializeReady -> err = " + err.message); + /* At this point we have an array with all the possible combinations, we just need to create an instance for each one and set the genes according to that. */ - logger.persist(); + for (let i = 0; i < combinations.length; i++) { + + let botConfig = JSON.parse(JSON.stringify(pBotConfig)); + let combination = combinations[i]; + let genes = {}; + let clonKey = ""; + + for (let j = 0; j < botConfig.genes.length; j++) { + + genes[botConfig.genes[j].name] = combination[j]; + clonKey = clonKey + "." + combination[j]; + + } + + let clonName = "Clon" + clonKey; + + botConfig.filePathRoot = botConfig.devTeam + "/" + botConfig.codeName + "-" + clonName + "/" + global.PLATFORM_CONFIG.codeName + "." + global.PLATFORM_CONFIG.version.major + "." + global.PLATFORM_CONFIG.version.minor + "/" + global.EXCHANGE_NAME + "/" + botConfig.dataSetVersion; + + botConfig.instance = "Clon" + clonKey; + botConfig.instanceIndex = i; + + setTimeout(execute, i * Math.random() * 10 * 1000); + + function execute() { + + createBotInstance(genes, combinations.length, botConfig); + + } + } + } else { + + /* If the bot does not have any genes at all */ + + let genes = {}; + pBotConfig.instance = "Master"; + pBotConfig.instanceIndex = 0; + + createBotInstance(genes, 1, pBotConfig); + + } + + + function createBotInstance(pGenes, pTotalInstances, pBotConfig) { + + const DEBUG_MODULE = require(ROOT_DIR + 'DebugLog'); + let logger; + + logger = DEBUG_MODULE.newDebugLog(); + logger.bot = pBotConfig; + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> findProcess -> runTradingBot -> createBotInstance -> Entering function."); } + + let tradingBotMainLoop = TRADING_BOT_MAIN_LOOP_MODULE.newTradingBotProcessMainLoop(pBotConfig, logger); + tradingBotMainLoop.initialize(UI_COMMANDS, pProcessConfig, onInitializeReady); + + function onInitializeReady(err) { + + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + + tradingBotMainLoop.run(pGenes, pTotalInstances, whenRunFinishes); + + function whenRunFinishes(err) { + + pBotConfig.loopCounter = 0; + + let botId; + + botId = pBotConfig.devTeam + "." + pBotConfig.codeName + "." + pBotConfig.instance + "." + pBotConfig.process; + + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + + logger.write(MODULE_NAME, "[INFO] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully."); + logger.write(MODULE_NAME, "[INFO] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId); + + console.log(logDisplace + "Root : [INFO] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> whenStartFinishes -> botId = " + botId); + console.log(logDisplace + "Root : [INFO] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully."); + logger.persist(); + + } else { + + logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> whenStartFinishes -> err = " + err.message); + logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. "); + logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> whenStartFinishes -> Bye."); + logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId); + console.log(logDisplace + "Root : [ERROR] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> whenStartFinishes -> Bot execution finished with errors. Please check the logs."); + logger.persist(); + } + } + + } else { + logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> err = " + err.message); + logger.write(MODULE_NAME, "[ERROR] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> Bot will not be started. "); + console.log(logDisplace + "Root : [ERROR] start -> findProcess -> runTradingBot -> createBotInstance -> onInitializeReady -> err = " + err.message); + + logger.persist(); + } } } } diff --git a/Run.js b/Run.js index fbfb498a1..4911e433d 100644 --- a/Run.js +++ b/Run.js @@ -97,10 +97,10 @@ function readExchangeAPIKey() { console.log("[HINT] You need to have a file at this path -> " + filePath); global.EXCHANGE_KEYS = { - Poloniex: { + Poloniex: [{ Key: "", Secret: "" - } + }] } startRoot(); diff --git a/StatusReport.js b/StatusReport.js index a736ae325..155f2aaf9 100644 --- a/StatusReport.js +++ b/StatusReport.js @@ -88,10 +88,24 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> load -> Entering function."); } - let rootPath = owner.devTeam + "/" + owner.bot + "." + owner.botVersion.major + "." + owner.botVersion.minor + "/" + global.PLATFORM_CONFIG.codeName + "." + global.PLATFORM_CONFIG.version.major + "." + global.PLATFORM_CONFIG.version.minor + "/" + global.EXCHANGE_NAME + "/" + owner.dataSetVersion; let fileName = "Status.Report." + global.MARKET.assetA + '_' + global.MARKET.assetB + ".json"; - let filePath = rootPath + "/Reports/" + owner.process + timePath; - + let filePath; + + let ownerId = owner.devTeam + "-" + owner.bot + "-" + owner.botVersion.major + "-" + owner.botVersion.minor + "-" + owner.process + "-" + owner.dataSetVersion; + let botId = bot.devTeam + "-" + bot.codeName + "-" + bot.version.major + "-" + bot.version.minor + "-" + bot.process + "-" + bot.dataSetVersion; + + if (ownerId !== botId) { + + let rootPath = owner.devTeam + "/" + owner.bot + "." + owner.botVersion.major + "." + owner.botVersion.minor + "/" + global.PLATFORM_CONFIG.codeName + "." + global.PLATFORM_CONFIG.version.major + "." + global.PLATFORM_CONFIG.version.minor + "/" + global.EXCHANGE_NAME + "/" + owner.dataSetVersion; + + filePath = rootPath + "/Reports/" + owner.process + timePath; + + } else { + + filePath = bot.filePathRoot + "/Reports/" + owner.process + timePath; + + } + if (bot.type === 'Trading') { fileName = bot.startMode + "." + fileName; } diff --git a/TradingBotProcessMainLoop.js b/TradingBotProcessMainLoop.js index 8a40bd772..877150c6b 100644 --- a/TradingBotProcessMainLoop.js +++ b/TradingBotProcessMainLoop.js @@ -132,7 +132,7 @@ } } - function run(callBackFunction) { + function run(pGenes, pTotalAlgobots, callBackFunction) { try { if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[INFO] run -> Entering function."); } @@ -160,7 +160,7 @@ return str.length < max ? pad(" " + str, max) : str; } - console.log(new Date().toISOString() + " " + pad(bot.codeName, 20) + " " + pad(bot.process, 30) + " Entered into Main Loop # " + pad(Number(bot.loopCounter) + 1, 8)); + console.log(new Date().toISOString() + " " + pad(bot.codeName, 20) + " " + pad(bot.instance, 20) + " " + pad(bot.process, 30) + " Entered into Main Loop # " + pad(Number(bot.loopCounter) + 1, 8)); /* For each loop we want to create a new log file. */ @@ -535,7 +535,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeContext -> Entering function."); } context = CONTEXT.newContext(bot, logger, BLOB_STORAGE, UTILITIES, STATUS_REPORT); - context.initialize(statusDependencies, onInizialized); + context.initialize(statusDependencies, pTotalAlgobots, onInizialized); function onInizialized(err) { @@ -756,7 +756,7 @@ usertBot = USER_BOT_MODULE.newUserBot(bot, logger, COMMONS_MODULE); - usertBot.initialize(assistant, onInizialized); + usertBot.initialize(assistant, pGenes, onInizialized); function onInizialized(err) { @@ -1058,7 +1058,7 @@ if (now - bot.loopStartTime > processConfig.normalWaitTime) { logger.write(MODULE_NAME, "[ERROR] run -> loop -> checkLoopHealth -> Dead loop found -> pLastLoop = " + pLastLoop); - console.log((new Date().toISOString() + " [ERROR] run -> loop -> checkLoopHealth -> " + pad(bot.codeName, 20) + " " + pad(bot.process, 30) + " Loop # " + pad(Number(bot.loopCounter), 5) + " found dead. Resurrecting it now.")); + console.log((new Date().toISOString() + " [ERROR] run -> loop -> checkLoopHealth -> " + pad(bot.codeName, 20) + " " + pad(bot.instance, 20) + " " + pad(bot.process, 30) + " Loop # " + pad(Number(bot.loopCounter), 5) + " found dead. Resurrecting it now.")); logger.persist(); // We persist the logs of the failed execution. clearTimeout(nextLoopTimeoutHandle); // We cancel the timeout in case the original loop was still running and schedulled to reexecute. @@ -1066,7 +1066,7 @@ } else { - console.log((new Date().toISOString() + " [WARN] run -> loop -> checkLoopHealth -> " + pad(bot.codeName, 20) + " " + pad(bot.process, 30) + " Loop # " + pad(Number(bot.loopCounter), 5) + " found delayed but still alive. No action taken.")); + console.log((new Date().toISOString() + " [WARN] run -> loop -> checkLoopHealth -> " + pad(bot.codeName, 20) + " " + pad(bot.instance, 20) + " " + pad(bot.process, 30) + " Loop # " + pad(Number(bot.loopCounter), 5) + " found delayed but still alive. No action taken.")); } } diff --git a/exchange/ExchangeAPI.js b/exchange/ExchangeAPI.js index c205f26c6..235d2d7a5 100644 --- a/exchange/ExchangeAPI.js +++ b/exchange/ExchangeAPI.js @@ -31,7 +31,8 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - let exchange = bot.products[0].exchangeList[0].name.toLowerCase() + 'Client.js'; //TODO Improve + //let exchange = bot.products[0].exchangeList[0].name.toLowerCase() + 'Client.js'; //TODO Define + let exchange = 'binanceClient.js'; //TODO Define let api = require('./wrappers/' + exchange); apiClient = api.newAPIClient(global.EXCHANGE_KEYS[global.EXCHANGE_NAME].Key, global.EXCHANGE_KEYS[global.EXCHANGE_NAME].Secret, logger); @@ -168,8 +169,8 @@ let check = isValidOrder({ market: getMarketConfig(), api: apiClient, - pAmountB: truncDecimals(pAmountB), - pRate: truncDecimals(pRate) + amount: truncDecimals(pAmountB), + price: truncDecimals(pRate) }); if (check.valid) { diff --git a/exchange/exchangeUtils.js b/exchange/exchangeUtils.js index 4ac0fe9fd..853d6d825 100644 --- a/exchange/exchangeUtils.js +++ b/exchange/exchangeUtils.js @@ -83,9 +83,18 @@ const includes = function(str, list) { return _.some(list, item => str.includes(item)); } + +const getMarketConfig = function(exchangeProperties) { + return _.find(exchangeProperties.markets, (p) => { + return _.first(p.pair) === global.MARKET.assetA.toUpperCase() && + _.last(p.pair) === global.MARKET.assetB.toUpperCase(); + }); +} + module.exports = { retry: retryInstance, includes: includes, - isValidOrder + isValidOrder, + getMarketConfig } \ No newline at end of file diff --git a/exchange/wrappers/binanceClient.js b/exchange/wrappers/binanceClient.js index d97b182c7..a46ca7d24 100644 --- a/exchange/wrappers/binanceClient.js +++ b/exchange/wrappers/binanceClient.js @@ -44,7 +44,9 @@ sell: sell, movePosition: movePosition, getPublicTradeHistory: getPublicTradeHistory, - getExchangeProperties: getExchangeProperties + getExchangeProperties: getExchangeProperties, + isValidPrice: isValidPrice, + isValidLot: isValidLot }; return thisObject; @@ -65,6 +67,14 @@ return exchangeProperties; } + function truncDecimals(pFloatValue) { + + let decimals = 6; + + return parseFloat(parseFloat(pFloatValue).toFixed(decimals)); + + } + /* * Returns the price for a given pair of assets. * The object returned is an array of trades: diff --git a/exchange/wrappers/poloniexClient.js b/exchange/wrappers/poloniexClient.js index 1f6529d7b..0f38e13d5 100644 --- a/exchange/wrappers/poloniexClient.js +++ b/exchange/wrappers/poloniexClient.js @@ -58,7 +58,8 @@ sell: sell, movePosition: movePosition, getPublicTradeHistory: getPublicTradeHistory, - getExchangeProperties: getExchangeProperties + getExchangeProperties: getExchangeProperties, + isValidLot: isValidLot }; return thisObject; From 729f8c2923d1c608a01fe96c6ae61bb111b57fb9 Mon Sep 17 00:00:00 2001 From: Matias Benitez Date: Tue, 28 Aug 2018 17:22:20 -0300 Subject: [PATCH 5/5] Adding new exchange poloniex fixes and execution configuraiton --- Assistant.js | 2 +- Context.js | 10 +++- ExtractionBotProcessMainLoop.js | 81 +++++++++++++++++++++++++++-- Root.js | 28 ++++------ Run.js | 32 +++++++++++- exchange/ExchangeAPI.js | 19 +++---- exchange/wrappers/poloniex.js | 33 ++++++------ exchange/wrappers/poloniexClient.js | 57 +++++++++----------- 8 files changed, 181 insertions(+), 81 deletions(-) diff --git a/Assistant.js b/Assistant.js index b81316373..4e00404df 100644 --- a/Assistant.js +++ b/Assistant.js @@ -760,7 +760,7 @@ 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_FAIL_RESPONSE); + callBack(global.DEFAULT_RETRY_RESPONSE); return; } diff --git a/Context.js b/Context.js index 133598a8b..4c38f417b 100644 --- a/Context.js +++ b/Context.js @@ -330,7 +330,7 @@ */ const INITIAL_INVESTMENT_A = 0; // This is just for this release of the platform. - const INITIAL_INVESTMENT_B = .002 / totalAlgobots; // This is just for this release of the platform. + const INITIAL_INVESTMENT_B = truncDecimals(.002 / totalAlgobots); // This is just for this release of the platform. thisObject.executionContext = { investment: { // This is used to calculate profits. @@ -562,4 +562,12 @@ } } + function truncDecimals(pFloatValue, pDecimals) { + + if (!pDecimals) pDecimals = 8; // Default value + + return parseFloat(parseFloat(pFloatValue).toFixed(pDecimals)); + + } + }; \ No newline at end of file diff --git a/ExtractionBotProcessMainLoop.js b/ExtractionBotProcessMainLoop.js index a70615bd7..6778ea5c9 100644 --- a/ExtractionBotProcessMainLoop.js +++ b/ExtractionBotProcessMainLoop.js @@ -171,7 +171,7 @@ const UTILITIES = require(ROOT_DIR + 'CloudUtilities'); const STATUS_REPORT = require(ROOT_DIR + 'StatusReport'); - const POLONIEX_CLIENT_MODULE = require(ROOT_DIR + 'PoloniexAPIClient'); + const EXCHANGE_API = require(ROOT_DIR + 'exchange/ExchangeAPI'); const STATUS_DEPENDENCIES = require(ROOT_DIR + 'StatusDependencies'); /* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */ @@ -182,6 +182,7 @@ let userBot; let statusDependencies; + let exchangeAPI; let nextWaitTime; @@ -206,7 +207,7 @@ switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well."); - initializeUserBot(); + initializeExchangeAPI(); return; } case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. @@ -261,13 +262,87 @@ } } + function initializeExchangeAPI() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeExchangeAPI -> Entering function."); } + + exchangeAPI = EXCHANGE_API.newExchangeAPI(bot, logger); + + exchangeAPI.initialize(onInizialized); + + function onInizialized(err) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeContext -> onInizialized -> onInizialized -> Entering function."); } + + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeExchangeAPI -> onInizialized -> Execution finished well."); } + initializeUserBot(); + return; + } + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> onInizialized -> Retry Later. Requesting Execution Retry."); + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> onInizialized -> Operation Failed. Aborting the process."); + logger.persist(); + clearInterval(fixedTimeLoopIntervalHandle); + clearTimeout(nextLoopTimeoutHandle); + clearTimeout(checkLoopHealthHandle); + bot.enableCheckLoopHealth = false; + callBackFunction(err); + return; + } + default: { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result); + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> onInizialized -> Unhandled err.result received. -> err.message = " + err.message); + + logger.persist(); + clearInterval(fixedTimeLoopIntervalHandle); + clearTimeout(nextLoopTimeoutHandle); + clearTimeout(checkLoopHealthHandle); + bot.enableCheckLoopHealth = false; + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeContext -> onInizialized -> onInizialized -> err = " + err.message); + logger.persist(); + clearInterval(fixedTimeLoopIntervalHandle); + clearTimeout(nextLoopTimeoutHandle); + clearTimeout(checkLoopHealthHandle); + bot.enableCheckLoopHealth = false; + callBackFunction(err); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeExchangeAPI -> err = " + err.message); + logger.persist(); + clearInterval(fixedTimeLoopIntervalHandle); + clearTimeout(nextLoopTimeoutHandle); + clearTimeout(checkLoopHealthHandle); + bot.enableCheckLoopHealth = false; + callBackFunction(err); + } + } + function initializeUserBot() { try { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> Entering function."); } - usertBot = USER_BOT_MODULE.newUserBot(bot, logger, COMMONS_MODULE, UTILITIES, BLOB_STORAGE, STATUS_REPORT, POLONIEX_CLIENT_MODULE); + usertBot = USER_BOT_MODULE.newUserBot(bot, logger, COMMONS_MODULE, UTILITIES, BLOB_STORAGE, STATUS_REPORT, exchangeAPI); usertBot.initialize(statusDependencies, pMonth, pYear, onInizialized); diff --git a/Root.js b/Root.js index 2e689d2a0..db5bbb587 100644 --- a/Root.js +++ b/Root.js @@ -29,17 +29,6 @@ exports.newRoot = function newRoot() { message: "Custom Message" }; - /* Current state of the art fixed parameters */ - - global.MARKET = { - assetA: "USDT", - assetB: "BTC", - }; - - /* Currently only one exchange is supported. */ - - global.EXCHANGE_NAME = "Poloniex"; - /* This is the Execution Datetime */ global.EXECUTION_DATETIME = new Date(); @@ -218,8 +207,9 @@ exports.newRoot = function newRoot() { for (let p = 0; p < global.PLATFORM_CONFIG.executionList.length; p++) { let listItem = PLATFORM_CONFIG.executionList[p]; + let executionListItem = global.EXECUTION_CONFIG.executionList[p]; - if (listItem.enabled !== "true") { + if (listItem.enabled !== "true" || executionListItem.enabled !== "true") { console.log(logDisplace + "Root : [INFO] start -> Skipping process for being disabled."); console.log(logDisplace + "Root : [INFO] start -> listItem.process = " + listItem.process); @@ -523,14 +513,14 @@ exports.newRoot = function newRoot() { if (processConfig.startMode.live !== undefined) { - if (processConfig.startMode.live.run === "true") { + if (processConfig.startMode.live.run === "true" && global.EXECUTION_CONFIG.startMode.live.run === "true") { botConfig.startMode = "Live"; let month = pad((new Date()).getUTCMonth() + 1, 2); let year = (new Date()).getUTCFullYear(); - if (processConfig.startMode.live.resumeExecution === "true") { + if (processConfig.startMode.live.resumeExecution === "true" && global.EXECUTION_CONFIG.startMode.live.resumeExecution === "true") { botConfig.hasTheBotJustStarted = false; } else { botConfig.hasTheBotJustStarted = true; @@ -550,7 +540,7 @@ exports.newRoot = function newRoot() { if (processConfig.startMode.backtest !== undefined) { - if (processConfig.startMode.backtest.run === "true") { + if (processConfig.startMode.backtest.run === "true" && global.EXECUTION_CONFIG.startMode.backtest.run === "true") { botConfig.startMode = "Backtest"; botConfig.backtest = processConfig.startMode.backtest; @@ -578,15 +568,15 @@ exports.newRoot = function newRoot() { if (processConfig.startMode.competition !== undefined) { - if (processConfig.startMode.competition.run === "true") { + if (processConfig.startMode.competition.run === "true" && global.EXECUTION_CONFIG.startMode.competition.run === "true") { botConfig.startMode = "Competition"; botConfig.competition = processConfig.startMode.competition; - if (processConfig.startMode.competition.resumeExecution === "true") { - botConfig.hasTheBotJustStarted = false; - } else { + if (processConfig.startMode.competition.resumeExecution === "false" && global.EXECUTION_CONFIG.startMode.competition.resumeExecution === "false") { botConfig.hasTheBotJustStarted = true; + } else { + botConfig.hasTheBotJustStarted = false; } switch (botConfig.type) { diff --git a/Run.js b/Run.js index 4911e433d..532736048 100644 --- a/Run.js +++ b/Run.js @@ -6,6 +6,13 @@ global.USER_LOGGED_IN = "Ciencias"; global.DEV_TEAM = "AAMasters"; global.AT_BREAKPOINT = false; // This is used only when running at the browser. +/* Default parameters can be changed by the execution configuration */ +global.EXCHANGE_NAME = "Poloniex"; +global.MARKET = { + assetA: "USDT", + assetB: "BTC" +}; + process.on('uncaughtException', function (err) { console.log('[INFO] Run -> uncaughtException -> err.message = ' + err.message); return; @@ -75,7 +82,7 @@ function readEmailConfiguration() { filePath = '../' + 'Email-Config' + '/' + 'Email.Config.json'; global.EMAIL_CONFIG = JSON.parse(fs.readFileSync(filePath, 'utf8')); - readExchangeAPIKey(); + readExecutionConfiguration(); } catch (err) { console.log("[ERROR] readEmailConfiguration -> err = " + err.message); @@ -83,6 +90,29 @@ function readEmailConfiguration() { } } +function readExecutionConfiguration() { + let filePath; + try { + let fs = require('fs'); + filePath = '../Execution-Config/Execution.Config.json'; + let executionProperties = JSON.parse(fs.readFileSync(filePath, 'utf8')); + global.DEV_TEAM = executionProperties.devTeam; + global.EXCHANGE_NAME = executionProperties.exchangeName; + global.MARKET = executionProperties.market; + global.EXECUTION_CONFIG = { + executionList: executionProperties.executionList, + startMode: executionProperties.startMode + }; + + readExchangeAPIKey(); + } + catch (err) { + console.log("[ERROR] readExecutionConfiguration -> err = " + err.message); + console.log("[HINT] You need to have a file at this path -> " + filePath); + } +} + +//TODO Move this method inside ExchangeAPI function readExchangeAPIKey() { try { diff --git a/exchange/ExchangeAPI.js b/exchange/ExchangeAPI.js index 3a61cbec1..b0b21b78d 100644 --- a/exchange/ExchangeAPI.js +++ b/exchange/ExchangeAPI.js @@ -31,9 +31,10 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - let exchange = bot.products[0].exchangeList[0].name.toLowerCase() + 'Client.js'; //TODO Define - //let exchange = 'binanceClient.js'; //TODO Define + let exchange = global.EXCHANGE_NAME.toLowerCase() + 'Client.js'; let api = require('./wrappers/' + exchange); + if (!bot.instanceIndex) + bot.instanceIndex = 0; apiClient = api.newAPIClient(global.EXCHANGE_KEYS[global.EXCHANGE_NAME][bot.instanceIndex].Key, global.EXCHANGE_KEYS[global.EXCHANGE_NAME][bot.instanceIndex].Secret, logger); callBackFunction(global.DEFAULT_OK_RESPONSE); @@ -222,13 +223,13 @@ * It's possible that the exchange doesn't support this method. * The object returned is an array of trades: * trade = { - * tradeIdAtExchange, - * marketIdAtExchange, - * type, - * rate, - * amountA, - * amountB, - * datetime + * tradeID, String + * globalTradeID, String + * type, String + * rate, Number + * amountA, Number + * amountB, Number + * date Date * }; */ function getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack) { diff --git a/exchange/wrappers/poloniex.js b/exchange/wrappers/poloniex.js index 85b114496..b486342b3 100644 --- a/exchange/wrappers/poloniex.js +++ b/exchange/wrappers/poloniex.js @@ -1,4 +1,5 @@ // Source: https://github.com/premasagar/poloniex.js +// Documentation: https://poloniex.com/support/api/ module.exports = (function () { 'use strict'; @@ -179,6 +180,22 @@ module.exports = (function () { return this._public('returnLoanOrders', { currency: currency }, 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._public('returnTradeHistory', parameters, callback); + }, + ///// // PRIVATE METHODS @@ -233,22 +250,6 @@ module.exports = (function () { 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 diff --git a/exchange/wrappers/poloniexClient.js b/exchange/wrappers/poloniexClient.js index 0f38e13d5..ad2d14378 100644 --- a/exchange/wrappers/poloniexClient.js +++ b/exchange/wrappers/poloniexClient.js @@ -40,14 +40,10 @@ 'ECONNRESET', 'Connection timed out', 'Connection Error', - 'Order not found, or you are not the person who placed it.' + 'Order not found, or you are not the person who placed it.', + 'ETIMEDOUT' ]; - // errors that might mean the API call succeeded - const unknownResultErrors = [ - 'ETIMEDOUT', - ] - let API = new Poloniex(pKey, pSecret); let thisObject = { @@ -248,18 +244,18 @@ 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 + * tradeID, String + * globalTradeID, String + * type, String + * rate, Number + * amountA, Number + * amountB, Number + * date Date * }; */ function getPublicTradeHistory(assetA, assetB, startTime, endTime, callBack) { @@ -269,13 +265,13 @@ if (err.result === global.DEFAULT_OK_RESPONSE.result) { for (let i = 0; i < response.length; i++) { let trade = { - tradeIdAtExchange: response[i].tradeID, + tradeID: response[i].tradeID, + globalTradeID: response[i].globalTradeID, 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() + total: Number(response[i].total), + amount: Number(response[i].amount), + date: new Date(response[i].date) } trades.push(trade); } @@ -283,7 +279,7 @@ callBack(err, trades); }; - const fetch = next => API.returnPublicTradeHistory(assetA, assetB, startTime, endTime, analizeResponse(next)); + const fetch = next => API.returnTradeHistory(assetA, assetB, startTime, endTime, analizeResponse(next)); retry(null, fetch, handle); } @@ -298,20 +294,25 @@ 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) { + + if (includes(exchangeErr.code, recoverableErrors)) { + error.notFatal = true; + } + } else if (!exchangeResponse || exchangeResponse === undefined) { error = { result: global.DEFAULT_FAIL_RESPONSE.result, - message: 'Empty response' + message: 'Empty response', + notFatal: true }; } else if (includes(exchangeResponse.error, ['Please complete the security check to proceed.'])) { error = { @@ -338,13 +339,7 @@ result: global.DEFAULT_FAIL_RESPONSE.result, message: exchangeResponse.error }; - } - - if (error) { - if (includes(error.message, recoverableErrors)) { - error.notFatal = true; - } - } + } return callBack(error, exchangeResponse);