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);