diff --git a/.vs/AACloudPlatform/v15/.suo b/.vs/AACloudPlatform/v15/.suo index 650fa4136..8663604b4 100644 Binary files a/.vs/AACloudPlatform/v15/.suo and b/.vs/AACloudPlatform/v15/.suo differ diff --git a/Assistant.js b/Assistant.js index e780a16bf..b6a813d51 100644 --- a/Assistant.js +++ b/Assistant.js @@ -35,7 +35,7 @@ return thisObject; - function initialize(pBotContext, pProcessDatetime, pExchangeAPI, callBackFunction) { + function initialize(pProcessDatetime, pBotContext, pExchangeAPI, callBackFunction) { try { diff --git a/Run.js b/Run.js index 2e37de4ec..fb0aa43d9 100644 --- a/Run.js +++ b/Run.js @@ -79,44 +79,13 @@ const DEFAULT_RETRY_RESPONSE = { message: "Retry Later" }; -if (bot.type === "trading") { - let tradingBot = newTradingBot(bot, botConfig); - tradingBot.initialize(onInitializeReady); - - function onInitializeReady(err) { - - if (err === null) { - - tradingBot.start(whenStartFinishes); - - function whenStartFinishes() { - - if (err === null) { - - logger.write("[INFO] trading bot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully. :-)"); - - } else { - - logger.write("[ERR] trading bot -> onInitializeReady -> whenStartFinishes -> err = " + err.message); - logger.write("[ERR] trading bot -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. "); - logger.write("[ERR] trading bot -> onInitializeReady -> whenStartFinishes -> Bye. :-("); - } - } - - } else { - logger.write("[ERR] trading bot -> onInitializeReady -> err = " + err.message); - logger.write("[ERR] trading bot -> onInitializeReady -> Bot will not be started. " ); - } - - } - return; -} const START_ALL_MONTHS = false; const START_ONE_MONTH = true; const INTERVAL_EXECUTOR_MODULE = require('./Interval Executor'); +const TRADING_BOT_MODULE = require('./TradingBot'); /* Now we will see which are the script Arguments. We expect the name of the process to run, since that is what it should be defined at the Task Scheduller. */ @@ -190,6 +159,13 @@ for (let i = 0; i < botConfig.processes.length; i++) { function startProcess() { + if (bot.type === "trading") { + + runTradingBot(); + + return; + } + const newIntervalExecutor = INTERVAL_EXECUTOR_MODULE.newIntervalExecutor(bot); newIntervalExecutor.initialize(vmConfig.bot.path, processConfig, undefined, undefined, onInitializeReady); @@ -211,3 +187,34 @@ for (let i = 0; i < botConfig.processes.length; i++) { } +function runTradingBot() { + + let tradingBot = TRADING_BOT_MODULE.newTradingBot(bot); + tradingBot.initialize(vmConfig.bot.path, processConfig, onInitializeReady); + + function onInitializeReady(err) { + + if (err.result !== DEFAULT_OK_RESPONSE.result) { + + tradingBot.start(whenStartFinishes); + + function whenStartFinishes() { + + if (err.result !== DEFAULT_OK_RESPONSE.result) { + + logger.write("[INFO] trading bot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully. :-)"); + + } else { + + logger.write("[ERR] trading bot -> onInitializeReady -> whenStartFinishes -> err = " + err.message); + logger.write("[ERR] trading bot -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. "); + logger.write("[ERR] trading bot -> onInitializeReady -> whenStartFinishes -> Bye. :-("); + } + } + + } else { + logger.write("[ERR] trading bot -> onInitializeReady -> err = " + err.message); + logger.write("[ERR] trading bot -> onInitializeReady -> Bot will not be started. "); + } + } +} \ No newline at end of file diff --git a/TradingBot.js b/TradingBot.js index 9eb871ec9..ab726eea3 100644 --- a/TradingBot.js +++ b/TradingBot.js @@ -1,213 +1,242 @@ -exports.newTradingBot = function newTradingBot(BOT, CONFIG) { +exports.newTradingBot = function newTradingBot(BOT) { let bot = BOT; const ROOT_DIR = './'; - const MODULE_NAME = "Interval Executor"; + const MODULE_NAME = "Trading Bot"; const LOG_INFO = true; - let INTERVAL_MODULE; + let USER_BOT_MODULE; const DEBUG_MODULE = require(ROOT_DIR + 'Debug Log'); const logger = DEBUG_MODULE.newDebugLog(); logger.fileName = MODULE_NAME; logger.bot = bot; - intervalExecutor = { + thisObject = { initialize: initialize, start: start }; - const SHUTDOWN_EVENT = require(ROOT_DIR + 'Azure Web Job Shutdown'); - let shutdownEvent = SHUTDOWN_EVENT.newShutdownEvent(bot); - - const UTILITIES = require(ROOT_DIR + 'Utilities'); - let utilities = UTILITIES.newUtilities(bot); - - let year; - let month; - let processConfig; - return intervalExecutor; + return thisObject; - function initialize(pBotPath, pProcessConfig, pYear, pMonth, callBackFunction) { + function initialize(pBotPath, pProcessConfig, callBackFunction) { try { - + if (LOG_INFO === true) { logger.write("[INFO] initialize -> Entering function."); } processConfig = pProcessConfig; - year = pYear; - month = pMonth; + USER_BOT_MODULE = require(pBotPath + "/" + pProcessConfig.name + "/" + 'User.Bot'); - let logText; - - if (year !== undefined) { - - month = utilities.pad(month, 2); // Adding a left zero when needed. - logger.fileName = MODULE_NAME + "-" + year + "-" + month; - logText = "[INFO] initialize - Entering function 'initialize' " + " @ " + year + "-" + month; - - } else { - - logger.fileName = MODULE_NAME; - logText = "[INFO] initialize - Entering function 'initialize' "; - - } - - console.log(logText); - logger.write(logText); - - INTERVAL_MODULE = require(pBotPath + "/" + pProcessConfig.name + "/" + 'Interval'); - - callBackFunction(); + callBackFunction(DEFAULT_OK_RESPONSE); } catch (err) { - - const logText = "[ERROR] initialize - ' ERROR : " + err.message; - console.log(logText); - logger.write(logText); - logger.write(err.stack); - + logger.write("[ERROR] initialize -> err = " + err.message); + callBackFunction(DEFAULT_FAIL_RESPONSE); } } - - function start() { + function start(callBackFunction) { try { + if (LOG_INFO === true) { logger.write("[INFO] start -> Entering function."); } - if (year !== undefined) { + loop(); - if (LOG_INFO === true) { - logger.write("[INFO] Entering function 'start', with year = " + year + " and month = " + month); - } - - } else { - - if (LOG_INFO === true) { - logger.write("[INFO] Entering function 'start'."); - } - - } - - - - let intervalId; - - if (processConfig.intervalMaxLoops === 0) { - - intervalId = setInterval(startNewInterval, processConfig.intervalLapse); - - } - - let intervalLoopCounter = 0; - - startNewInterval(); // First, inmadiate run. - - function startNewInterval() { + function loop() { try { + if (LOG_INFO === true) { logger.write("[INFO] start -> loop -> Entering function."); } - if (LOG_INFO === true) { + /* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */ - if (processConfig.intervalMaxLoops !== 0) { + const UTILITIES = require(ROOT_DIR + 'Utilities'); + const AZURE_FILE_STORAGE = require(ROOT_DIR + 'Azure File Storage'); + const MARKETS_MODULE = require(ROOT_DIR + 'Markets'); + const DEBUG_MODULE = require(ROOT_DIR + 'Debug Log'); + const POLONIEX_CLIENT_MODULE = require(ROOT_DIR + 'Poloniex API Client'); + const EXCHANGE_API = require(ROOT_DIR + 'ExchangeAPI'); + const CONTEXT = require(ROOT_DIR + 'Context'); + const DATASOURCE = require(ROOT_DIR + 'Datasource'); + const ASSISTANT = require(ROOT_DIR + 'Assistant'); - logger.write("[INFO] Entering function 'startNewInterval', next execution inmediately after the current Interval finishes."); + /* 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. */ - } else { + let processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution. + processDatetime = new Date(processDatetime.valueOf() - 30 * 24 * 60 * 60 * 1000); // we go 30 days back in time since candles are currently not up to date. - logger.write("[INFO] Entering function 'startNewInterval', next execution in " + processConfig.intervalLapse / 1000 / 60 + " minutes."); + /* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */ + let context; + let datasource; + let exchangeAPI; + let assistant; + let userBot; + + let nextWaitTime; + + function initializeContext() { + + context = CONTEXT.newContext(bot, DEBUG_MODULE, AZURE_FILE_STORAGE); + context.initialize(processDatetime, onInizialized); + + function onInizialized(err) { + + if (err.result === DEFAULT_OK_RESPONSE.result) { + initializeDatasource(); + } else { + logger.write("[ERROR] start -> loop -> initializeContext -> err = " + err.message); + callBackFunction(err); + } } - } + function initializeDatasource() { - if (shutdownEvent.isShuttingDown() === false) { + datasource = DATASOURCE.newDatasource(bot, DEBUG_MODULE, AZURE_FILE_STORAGE); + datasource.initialize(onInizialized); - const UTILITIES = require(ROOT_DIR + 'Utilities'); - const AZURE_FILE_STORAGE = require(ROOT_DIR + 'Azure File Storage'); - const MARKETS_MODULE = require(ROOT_DIR + 'Markets'); - const DEBUG_MODULE = require(ROOT_DIR + 'Debug Log'); - const POLONIEX_CLIENT_MODULE = require(ROOT_DIR + 'Poloniex API Client'); - - let fileProcessingInterval = INTERVAL_MODULE.newInterval(bot, UTILITIES, AZURE_FILE_STORAGE, DEBUG_MODULE, MARKETS_MODULE, POLONIEX_CLIENT_MODULE); - - fileProcessingInterval.initialize(year, month, onReadyToStart); - - function onReadyToStart() { - - fileProcessingInterval.start(loopControl); + function onInizialized(err) { + if (err.result === DEFAULT_OK_RESPONSE.result) { + initializeExchangeAPI(); + } else { + logger.write("[ERROR] start -> loop -> initializeDatasource -> err = " + err.message); + callBackFunction(err); + } } - - } - else { - - clearInterval(intervalId); - - const logText = "[INFO] requestNewData - Terminating Set Interval - About to exit gracefully the process. "; - console.log(logText); - logger.write(logText); - } - } - catch (err) { - const logText = "[ERROR] 'startNewInterval' - ERROR : " + err.message; - logger.write(logText); + function initializeExchangeAPI() { + + exchangeAPI = EXCHANGE_API.newExchangeAPI(bot, DEBUG_MODULE, POLONIEX_CLIENT_MODULE); + exchangeAPI.initialize(onInizialized); + + function onInizialized(err) { + + if (err.result === DEFAULT_OK_RESPONSE.result) { + initializeAssistant(); + } else { + logger.write("[ERROR] start -> loop -> initializeExchangeAPI -> err = " + err.message); + callBackFunction(err); + } + } + } + + function initializeAssistant() { + + assistant = ASSISTANT.newAssistant(bot, DEBUG_MODULE); + assistant.initialize(processDatetime, context, exchangeAPI, onInizialized); + + function onInizialized(err) { + + if (err.result === DEFAULT_OK_RESPONSE.result) { + initializeUserBot(); + } else { + logger.write("[ERROR] start -> loop -> initializeAssistant -> err = " + err.message); + callBackFunction(err); + } + } + } + + function initializeUserBot() { + + usertBot = USER_BOT_MODULE.newUserBot(bot, DEBUG_MODULE); + + let platform = { + context: context, + datasource: datasource, + assistant: assistant, + processDatetime: processDatetime + }; + + usertBot.initialize(platform, onInizialized); + + function onInizialized(err) { + + if (err.result === DEFAULT_OK_RESPONSE.result) { + startUserBot(); + } else { + logger.write("[ERROR] start -> loop -> initializeUserBot -> err = " + err.message); + callBackFunction(err); + } + } + } + + function startUserBot() { + + usertBot.start(platform, onFinished); + + function onFinished(err) { + + switch (err.result) { + case DEFAULT_OK_RESPONSE.result: { + nextWaitTime = 'Normal'; + saveContext(); + return; + } + break; + case DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + nextWaitTime = 'Retry'; + loopControl(nextWaitTime); // Note that we do not save the context, since the processing was aborted. + return; + } + break; + case DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write("[ERROR] start -> loop -> startUserBot -> Operation Failed. Aborting the process."); + callBackFunction(err); + return; + } + break; + } + } + } + + function saveContext() { + + context.saveThemAll(onFinished); + + function onFinished(err) { + + if (err.result === DEFAULT_OK_RESPONSE.result) { + loopControl(nextWaitTime); + } else { + logger.write("[ERROR] start -> loop -> saveContext -> err = " + err.message); + callBackFunction(err); + } + } + } + + } catch (err) { + logger.write("[ERROR] start -> loop -> err = " + err.message); + callBackFunction(err); } } - function loopControl(moreIntervalExecutionsNeeded, nextIntervalLapse) { + function loopControl(nextWaitTime) { - if (moreIntervalExecutionsNeeded === true) { + /* Trading bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */ - intervalLoopCounter++; - - if (intervalLoopCounter === processConfig.intervalMaxLoops) { - - const logText = "[WARN] 'processConfig.intervalMaxLoops' reached. Stopping execution. "; - logger.write(logText); - - } else { - - const logText = "[WARN] 'loopControl'. Staring new Interval. "; - logger.write(logText); - - if (nextIntervalLapse === undefined) { - - setTimeout(startNewInterval, processConfig.intervalLapse);// Looped Execution. - - } else { - - setTimeout(startNewInterval, nextIntervalLapse); - - } + switch (nextWaitTime) { + case 'Normal': { + if (LOG_INFO === true) { logger.write("[INFO] start -> loopControl -> Restarting Loop in " + (processConfig.executionWaitTime / 1000) + " seconds."); } + setTimeout(loop, processConfig.executionWaitTime); } - - } else { - - logger.write("[WARN] Received the signal from last Interval execution to not call it anymore. Stopping execution. "); - - } - + break; + case 'Retry': { + if (LOG_INFO === true) { logger.write("[INFO] start -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); } + setTimeout(loop, processConfig.retryWaitTime); + } + break; + } } - } + catch (err) { - const logText = "[ERROR] 'Start' - ERROR : " + err.message; + logger.write("[ERROR] start -> err = " + err.message); logger.write(logText); } } }; - -/* - - const NEXT_BOT_EXECUTION = 10 * 1000; // If something fails and we need to retry after a few seconds, we will use this amount of time to request a new execution of this bot code. - - -let processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution. -processDatetime = new Date(processDatetime.valueOf() - 30 * 24 * 60 * 60 * 1000); // we go 30 days back in time since candles are currently not up to date. -*/