exports.newUserBot = function newUserBot (bot, logger) { /* Creating a new instance from the platform of this bot: - bot: An instance of the bot configuration - logger: An instance of the platform logger that allow both: to save logs on cloud execution and browser execution */ // Variable used for logs, this will be passed to the logger instance const MODULE_NAME = 'User Bot' // Debug log level const FULL_LOG = true // Info log level const LOG_INFO = true /* The reference to the Traing Platform Advanced Algos Assistant that will allow to put positions on the exchange. */ let assistant /* This is a dependency example to other bots, in this case to the the LRC Indicator. */ let gaussStorage /* This objects returns two public functions that will be used to integrate with the platform. */ let thisObject = { initialize: initialize, start: start } return thisObject /* The initialize function must be implemented by all trading bots. pAssistant: the instance of the platform which methods will allow tu put getPositions pGenes: This variable will be used during initialization to get the parameters from the platform. At the moment it won't be used but in future releases it will allow to create different clones of the algonet. */ function initialize (pAssistant, pGenes, callBackFunction) { try { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] initialize -> Entering function.') } // Integration with platform logger, when running on cloud logger.fileName = MODULE_NAME /* We keep a local reference to the assistant that will allow to put positions on the exchange and it's created and managed by the platform */ assistant = pAssistant /* On 'assistant.dataDependencies.dataSets' we will receive the data files retrieved for those defined on the bot configuration Note that the key is: TeamName-BotName-BotProductFolder-BotProcessName-BotDataSetVersion */ let key = 'AAVikings-AAGauss-LRC-Points-Multi-Period-Daily-dataSet.V1' gaussStorage = assistant.dataDependencies.dataSets.get(key) /* Once Completed we must return the global.DEFAULT_OK_RESPONSE */ callBackFunction(global.DEFAULT_OK_RESPONSE) } catch (err) { logger.write(MODULE_NAME, '[ERROR] initialize -> onDone -> err = ' + err.message) callBackFunction(global.DEFAULT_FAIL_RESPONSE) } } /* The start function is called by the platform for executing the bot every 1 minute */ function start (callBackFunction) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> Entering function.') } /* This breakpoint will be called once the web plataform reach the graphical stop point */ if (global.AT_BREAKPOINT === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Plot Breakpoint Hit."); } } const market = global.MARKET const CHANNEL_DOWN = -1 const CHANNEL_UP = 1 const NO_CHANNEL = 0 // Some parameters defined locally, they are used to explore different variations. const TRADE_TARGET = 2.0 let targetTradeEnabled = false let stopLossEnabled = false let stopLossPorcentage = 0.01 /* Some variables that are stored and retrieved on every execution. They are stored and managed by the assistant using a key-par value. Objects are not allowed. */ let lastBuyRate = assistant.remindMeOf(LAST_BUY_RATE_KEY) if (lastBuyRate === undefined) { let currentRate = assistant.getMarketRate() lastBuyRate = currentRate assistant.rememberThis(LAST_BUY_RATE_KEY, lastBuyRate) } // Bypass business logic and create a buy position directly based on a different rule let lastSellRate = assistant.remindMeOf(LAST_SELL_RATE_KEY) if (targetTradeEnabled === true && assistant.getAvailableBalance().assetA > 1 && lastSellRate !== undefined) { let currentROI = ((lastSellRate - assistant.getTicker().bid) / lastSellRate) * 100 if (currentROI > TRADE_TARGET) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> Current trade ROI is greater than default target. Buying...') } createBuyPosition(onDone) return } } businessLogic(onDone) function onDone (err) { try { switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { logger.write(MODULE_NAME, '[INFO] start -> onDone -> Execution finished well. :-)') callBackFunction(global.DEFAULT_OK_RESPONSE) 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] start -> onDone -> Retry Later. Requesting Execution Retry.') callBackFunction(global.DEFAULT_RETRY_RESPONSE) 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] start -> onDone -> Operation Failed. Aborting the process. err = ' + err.message) callBackFunction(global.DEFAULT_FAIL_RESPONSE) return } } } catch (err) { logger.write(MODULE_NAME, '[ERROR] start -> onDone -> err = ' + err.message) callBackFunction(global.DEFAULT_FAIL_RESPONSE) } } function businessLogic (callBack) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> Entering function.') } let firstChannelTilt = 0 getChannelTilt(firstTiltCheck); function firstTiltCheck (err, channelTilt) { try { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> firstTiltCheck -> Entering Function.') } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { logger.write(MODULE_NAME, '[ERROR] start -> businessLogic -> firstTiltCheck -> err = ' + err.message) callBack(global.DEFAULT_FAIL_RESPONSE) } else { if (channelTilt == 1) { createBuyPosition(callBack) } else if (channelTilt == -1) { firstChannelTilt = -1 getSecondChannelTilt(secondTiltCheck) } else { if (LOG_INFO === true) { logger.write(MODULE_NAME, "[INFO] start -> businessLogic -> firstTiltCheck -> Nothing to do, there isn't a buy or sell oportunity.") } callBack(global.DEFAULT_OK_RESPONSE) } } } catch (err) { logger.write(MODULE_NAME, '[ERROR] start -> businessLogic -> firstTiltCheck -> err = ' + err.message) callBackFunction(global.DEFAULT_FAIL_RESPONSE) } } function secondTiltCheck (err, secondChannelTilt) { try { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> secondTiltCheck -> Entering Function.') } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { logger.write(MODULE_NAME, '[ERROR] start -> businessLogic -> secondTiltCheck -> err = ' + err.message) callBack(global.DEFAULT_FAIL_RESPONSE) } else { if (firstChannelTilt == -1 && secondChannelTilt == -1) { createSellPosition(callBack) } else { if (LOG_INFO === true) { logger.write(MODULE_NAME, "[INFO] start -> businessLogic -> secondTiltCheck -> Nothing to do, there isn't a sell oportunity.") } callBack(global.DEFAULT_OK_RESPONSE) } } } catch (err) { logger.write(MODULE_NAME, '[ERROR] start -> businessLogic -> secondTiltCheck -> err = ' + err.message) callBackFunction(global.DEFAULT_FAIL_RESPONSE) } } } function getChannelTilt (callBack) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> getChannelTilt -> Entering function.') } let lrcPoint, previousLRCPoint let queryDate = new Date(bot.processDatetime) getLRCPointsFile(queryDate, onLRCPointsFileReceived) function onLRCPointsFileReceived (err, lrcPointsFile) { try { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> getChannelTilt -> onLRCPointsFileReceived.') } let lastIndexLrcPointsFile = lrcPointsFile.length - 1 let lastAvailableDateTime = lrcPointsFile[lastIndexLrcPointsFile][0] if (bot.processDatetime.valueOf() <= lastAvailableDateTime || !isExecutionToday()) { for (let i = 0; i < lrcPointsFile.length; i++) { if (bot.processDatetime.valueOf() >= lrcPointsFile[i][0] && bot.processDatetime.valueOf() < lrcPointsFile[i][1]) { lrcPoint = { begin: lrcPointsFile[i][0], end: lrcPointsFile[i][1], min: lrcPointsFile[i][2], mid: lrcPointsFile[i][3], max: lrcPointsFile[i][4] } if (i >= 1) { let previous = i - 1 previousLRCPoint = { begin: lrcPointsFile[previous][0], end: lrcPointsFile[previous][1], min: lrcPointsFile[previous][2], mid: lrcPointsFile[previous][3], max: lrcPointsFile[previous][4] } applyBotRules(lrcPoint, previousLRCPoint, callBack) } else { queryDate.setDate(queryDate.getDate() - 1) getLRCPointsFile(queryDate, onPreviousLRCPointsFileReceived) } return } } logger.write(MODULE_NAME, '[ERROR] start -> businessLogic -> getChannelTilt -> onLRCPointsFileReceived. The expected LRC Point was not found: ' + bot.processDatetime.valueOf()) callBack(global.DEFAULT_FAIL_RESPONSE) } else { // Running live we will process last available LRCPoint only if it's delayed 25 minutes top let maxTolerance = 25 * 60 * 1000 if (bot.processDatetime.valueOf() <= (lastAvailableDateTime + maxTolerance)) { lrcPoint = { begin: lrcPointsFile[lastIndexLrcPointsFile][0], end: lrcPointsFile[lastIndexLrcPointsFile][1], min: lrcPointsFile[lastIndexLrcPointsFile][2], mid: lrcPointsFile[lastIndexLrcPointsFile][3], max: lrcPointsFile[lastIndexLrcPointsFile][4] } if (lastIndexLrcPointsFile >= 1) { let previous = lastIndexLrcPointsFile - 1 previousLRCPoint = { begin: lrcPointsFile[previous][0], end: lrcPointsFile[previous][1], min: lrcPointsFile[previous][2], mid: lrcPointsFile[previous][3], max: lrcPointsFile[previous][4] } applyBotRules(lrcPoint, previousLRCPoint, callBack) } else { queryDate.setDate(queryDate.getDate() - 1) getLRCPointsFile(queryDate, onPreviousLRCPointsFileReceived) } } else { if (LOG_INFO === true) logger.write(MODULE_NAME, '[WARN] start -> getChannelTilt -> onLRCPointsFileReceived. Available candle older than 5 minutes. Skeeping execution.') callBack(global.DEFAULT_OK_RESPONSE, NO_CHANNEL) // TODO CUSTOM_OK_RESPONSE } } } catch (err) { logger.write(MODULE_NAME, '[ERROR] start -> getChannelTilt -> onLRCPointsFileReceived -> err = ' + err.message) callBackFunction(global.DEFAULT_FAIL_RESPONSE) } } function onPreviousLRCPointsFileReceived (err, lrcPointsFile) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> getChannelTilt -> onLRCPointsFileReceived.') } let previous = lrcPointsFile.length - 1 previousLRCPoint = { begin: lrcPointsFile[previous][0], end: lrcPointsFile[previous][1], min: lrcPointsFile[previous][2], mid: lrcPointsFile[previous][3], max: lrcPointsFile[previous][4] } applyBotRules(lrcPoint, previousLRCPoint, callBack) } } function getSecondChannelTilt (callBack) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> getSecondChannelTilt -> Entering function.') } const CHANNEL_DOWN = -1 const CHANNEL_UP = 1 const NO_CHANNEL = 0 let lrcPoint, previousLRCPoint getLRCPointsMarketFile(onLRCPointsFileReceived) function onLRCPointsFileReceived (err, lrcPointsFile) { try { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> getSecondChannelTilt -> onLRCPointsFileReceived.') } let lastIndexLrcPointsFile = lrcPointsFile.length - 1 let lastAvailableDateTime = lrcPointsFile[lastIndexLrcPointsFile][0] if (bot.processDatetime.valueOf() <= lastAvailableDateTime || !isExecutionToday()) { for (let i = lrcPointsFile.length - 1; i >= 0; i--) { if (bot.processDatetime.valueOf() >= lrcPointsFile[i][0] && bot.processDatetime.valueOf() < lrcPointsFile[i][1]) { lrcPoint = { begin: lrcPointsFile[i][0], end: lrcPointsFile[i][1], min: lrcPointsFile[i][2], mid: lrcPointsFile[i][3], max: lrcPointsFile[i][4] } if (i >= 1) { let previous = i - 1 previousLRCPoint = { begin: lrcPointsFile[previous][0], end: lrcPointsFile[previous][1], min: lrcPointsFile[previous][2], mid: lrcPointsFile[previous][3], max: lrcPointsFile[previous][4] } applyBotRules(lrcPoint, previousLRCPoint, callBack) } else { logger.write(MODULE_NAME, '[ERROR] start -> businessLogic -> getSecondChannelTilt -> onLRCPointsFileReceived. The expected previous LRC Point was not found.') } return } } logger.write(MODULE_NAME, '[ERROR] start -> businessLogic -> getSecondChannelTilt -> onLRCPointsFileReceived. The expected LRC Point was not found.' + bot.processDatetime.valueOf() + '. ' + JSON.stringify(lrcPointsFile)) callBack(global.DEFAULT_FAIL_RESPONSE) } else { // Running live we will process last available LRCPoint only if it's delayed 25 minutes top let maxTolerance = 25 * 60 * 1000 if (bot.processDatetime.valueOf() <= (lastAvailableDateTime + maxTolerance)) { lrcPoint = { begin: lrcPointsFile[lastIndexLrcPointsFile][0], end: lrcPointsFile[lastIndexLrcPointsFile][1], min: lrcPointsFile[lastIndexLrcPointsFile][2], mid: lrcPointsFile[lastIndexLrcPointsFile][3], max: lrcPointsFile[lastIndexLrcPointsFile][4] } if (lastIndexLrcPointsFile >= 1) { let previous = lastIndexLrcPointsFile - 1 previousLRCPoint = { begin: lrcPointsFile[previous][0], end: lrcPointsFile[previous][1], min: lrcPointsFile[previous][2], mid: lrcPointsFile[previous][3], max: lrcPointsFile[previous][4] } applyBotRules(lrcPoint, previousLRCPoint, callBack) } else { logger.write(MODULE_NAME, '[ERROR] start -> businessLogic -> getSecondChannelTilt -> onLRCPointsFileReceived. The expected previous LRC Point was not found.') } } else { if (LOG_INFO === true) logger.write(MODULE_NAME, '[WARN] start -> getSecondChannelTilt -> onLRCPointsFileReceived. Available candle older than 5 minutes. Skeeping execution.') callBack(global.DEFAULT_OK_RESPONSE, NO_CHANNEL) // TODO CUSTOM_OK_RESPONSE } } } catch (err) { logger.write(MODULE_NAME, '[ERROR] start -> getSecondChannelTilt -> onLRCPointsFileReceived -> err = ' + err.message) callBackFunction(global.DEFAULT_FAIL_RESPONSE) } } function onPreviousLRCPointsFileReceived (err, lrcPointsFile) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> getSecondChannelTilt -> onLRCPointsFileReceived.') } let previous = lrcPointsFile.length - 1 previousLRCPoint = { begin: lrcPointsFile[previous][0], end: lrcPointsFile[previous][1], min: lrcPointsFile[previous][2], mid: lrcPointsFile[previous][3], max: lrcPointsFile[previous][4] } applyBotRules(lrcPoint, previousLRCPoint, callBack) } } function applyBotRules (lrcPoint, previousLRCPoint, callBack) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> applyBotRules -> Entering Function.') } let channelTilt = NO_CHANNEL let ruleApplied = '' if (lrcPoint.max < lrcPoint.mid && lrcPoint.min > lrcPoint.mid && lrcPoint.mid < lrcPoint.min) { if (lrcPoint.min > previousLRCPoint.min && lrcPoint.mid > previousLRCPoint.mid && lrcPoint.max > previousLRCPoint.max) { // The channel points UP channelTilt = CHANNEL_UP ruleApplied += 'Rule_1.' } } if (lrcPoint.min < lrcPoint.mid && lrcPoint.max > lrcPoint.mid && lrcPoint.mid < lrcPoint.max) { if (lrcPoint.min < previousLRCPoint.min && lrcPoint.mid < previousLRCPoint.mid && lrcPoint.max < previousLRCPoint.max) { // The channel points DOWN channelTilt = CHANNEL_DOWN ruleApplied += 'Rule_2.' } } if (lrcPoint.min < previousLRCPoint.min && lrcPoint.mid <= previousLRCPoint.mid) { // 15 AND 30 changed direction from up to down if (checkToSellWithStopLoss()) { channelTilt = CHANNEL_DOWN ruleApplied += 'Rule_3a.' } } if (lrcPoint.min > previousLRCPoint.min && lrcPoint.mid >= previousLRCPoint.mid) { // 15 AND 30 changed direction from down to up channelTilt = CHANNEL_UP ruleApplied += 'Rule_3b.' } let logMessage = bot.processDatetime.toISOString() + '. Rule: ' + ruleApplied + '. Tilt: ' + channelTilt + ' (' + Number(lrcPoint.min).toLocaleString() + '|' + Number(lrcPoint.mid).toLocaleString() + '|' + Number(lrcPoint.max).toLocaleString() + ')' if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> getChannelTilt -> applyBotRules -> Results: ' + logMessage) } callBack(global.DEFAULT_OK_RESPONSE, channelTilt) } function checkToSellWithStopLoss () { let allowSell = true let assetBBalance = assistant.getAvailableBalance().assetB let currentRate = assistant.getMarketRate() let amountB = assistant.getAvailableBalance().assetB let amountA = amountB * currentRate if (stopLossEnabled) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> getChannelTilt -> applyBotRules -> manageStopLossConfiguration -> Stop Loss Enabled, checking to sell.') } // Get the current investment value let investment = 0 if (assistant.getAvailableBalance() !== undefined) { investment = assistant.getAvailableBalance().assetB * lastBuyRate } // Calculate maximum loss setup let currentLoss = amountA - investment let stopLossValue = investment * stopLossPorcentage if (currentLoss > 0) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> getChannelTilt -> applyBotRules -> manageStopLossConfiguration -> No loss, we can sell.') } } else if (assetBBalance > 0) { if (Math.abs(currentLoss) > stopLossValue) { let message = 'Sell to avoid loss more than ' + Number(stopLossPorcentage * 100).toLocaleString() + '% of our initial investment. ' message += 'Combined ROI on current execution: ' if (assistant.getCombinedProfits().assetA > 0) { message += Number(assistant.getCombinedProfits().assetA).toLocaleString() + '%. ' } else { message += Number(assistant.getCombinedProfits().assetB).toLocaleString() + '%. ' } if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> getChannelTilt -> applyBotRules -> manageStopLossConfiguration -> Stop loss, sell: ' + message) } assistant.sendMessage(3, 'Stop Loss', message) } else { let currentRisk = currentLoss / investment let message = 'Not sell now. Risking ' + Number(currentRisk * 100).toLocaleString() + '% of our initial investment' message += '. Last Buy: $' + Number(lastBuyRate).toLocaleString() message += '. Not selling at: $' + Number(currentRate).toLocaleString() if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> getChannelTilt -> applyBotRules -> manageStopLossConfiguration -> Stop loss, not sell: ' + message) } assistant.sendMessage(3, 'Stop Loss', message) allowSell = false } } } return allowSell } function getLRCPointsFile (dateTime, callback) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> getLRCPointsFile -> Entering function.') } let datePath = dateTime.getUTCFullYear() + '/' + pad(dateTime.getUTCMonth() + 1, 2) + '/' + pad(dateTime.getUTCDate(), 2) let filePath = 'LRC-Points/Multi-Period-Daily/30-min/' + datePath let fileName = market.assetA + '_' + market.assetB + '.json' if (bot.startMode === 'Backtest' && bot.botCache.has(filePath)) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> getLRCPointsFile -> Getting the file from local cache.') } cleanUpCache(bot.botCache, 'LRC-Points/Multi-Period-Daily/30-min', dateTime) callback(global.DEFAULT_OK_RESPONSE, bot.botCache.get(filePath)) } else { gaussStorage.getTextFile(filePath, fileName, onFileReceived) } function onFileReceived (err, text) { if (err.result === global.DEFAULT_OK_RESPONSE.result) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> getChannelTilt -> getLRCPointsFile -> onFileReceived > Entering Function.') } let lrcPointsFile = JSON.parse(text) bot.botCache.set(filePath, lrcPointsFile) callback(global.DEFAULT_OK_RESPONSE, lrcPointsFile) } else { logger.write(MODULE_NAME, '[ERROR] start -> getChannelTilt -> getLRCPointsFile -> onFileReceived -> Failed to get the file. Will abort the process and request a retry.') callBackFunction(global.DEFAULT_RETRY_RESPONSE) return } } function cleanUpCache (fileCache, path, dateTimeCacheFile) { let date = new Date(dateTimeCacheFile) date.setDate(date.getDate() - 2) let datePath = date.getUTCFullYear() + '/' + pad(date.getUTCMonth() + 1, 2) + '/' + pad(date.getUTCDate(), 2) let cachePosition = path + '/' + datePath // Remove and go back one day if (fileCache.has(cachePosition)) { fileCache.delete(cachePosition) date.setDate(date.getDate() - 1) cleanUpCache(fileCache, date) } } } function getLRCPointsMarketFile (callback) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] getLRCPointsMarketFile -> Entering function.') } let filePath = 'LRC-Points/Multi-Period-Market/02-hs' let fileName = market.assetA + '_' + market.assetB + '.json' if (bot.startMode === 'Backtest' && bot.botCache.has(filePath)) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] getLRCPointsMarketFile -> Getting the file from local cache.') } callback(global.DEFAULT_OK_RESPONSE, bot.botCache.get(filePath)) } else { gaussStorage.getTextFile(filePath, fileName, onFileReceived) } function onFileReceived (err, text) { if (err.result === global.DEFAULT_OK_RESPONSE.result) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] getLRCPointsMarketFile -> onFileReceived > Entering Function.') } let lrcPointsFile = JSON.parse(text) bot.botCache.set(filePath, lrcPointsFile) callback(global.DEFAULT_OK_RESPONSE, lrcPointsFile) } else { logger.write(MODULE_NAME, '[ERROR]getLRCPointsMarketFile -> onFileReceived -> Failed to get the file. Will abort the process and request a retry.') callBackFunction(global.DEFAULT_RETRY_RESPONSE) return } } } function createBuyPosition (callBack) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> createBuyPosition -> Entering function.') } // We get the current positions we have on the exchange let positions = assistant.getPositions() let assetABalance = assistant.getAvailableBalance().assetA let currentRate = assistant.getTicker().ask // *.9 let amountA = assistant.getAvailableBalance().assetA let amountB = Number((amountA / currentRate).toFixed(8)) if (positions.length > 0 && positions[0].type === 'buy' && positions[0].status !== 'executed') { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> createBuyPosition -> Moving an existing BUY position to a new price: $' + Number(currentRate).toLocaleString()) } let message = 'Moving an existing buy position to a new price: $' + Number(currentRate).toLocaleString() message += '. Combined ROI on current execution: ' + getCombinedProfit() + '%. ' assistant.sendMessage(6, 'Moving Position', message) message = bot.processDatetime.toISOString() + ' - ' + message assistant.sendEmail('Alerts', message, bot.emailSubscriptions) assistant.rememberThis(LAST_BUY_RATE_KEY, currentRate) assistant.movePosition(positions[0], currentRate, callBack) } else if (assetABalance > 0) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> createBuyPosition -> Artuditu put a new BUY position at price: $' + Number(currentRate).toLocaleString()) } let message = 'Creating a new buy position. Price: $' + Number(currentRate).toLocaleString() message += '. Combined ROI on current execution: ' + getCombinedProfit() + '%. ' assistant.sendMessage(7, 'Buying', message) message = bot.processDatetime.toISOString() + ' - ' + message assistant.sendEmail('Alerts', message, bot.emailSubscriptions) assistant.rememberThis(LAST_BUY_RATE_KEY, currentRate) assistant.putPosition('buy', currentRate, amountA, amountB, callBack) } else { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> createBuyPosition -> Not enough available balance to buy.') } callBack(global.DEFAULT_OK_RESPONSE) } } function createSellPosition (callBack) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> createSellPosition -> Entering function.') } // We get the current positions we have on the exchange let positions = assistant.getPositions() let assetBBalance = assistant.getAvailableBalance().assetB let currentRate = assistant.getTicker().bid // *1.1 let amountB = assistant.getAvailableBalance().assetB let amountA = amountB * currentRate if (positions.length > 0 && positions[0].type === 'sell' && positions[0].status !== 'executed') { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> createSellPosition -> Artuditu is moving an existing SELL position to a new price: $' + Number(currentRate).toLocaleString()) } let message = 'Moving an existing sell position to a new price: $' + Number(currentRate).toLocaleString() message += '. Combined ROI on current execution: ' + getCombinedProfit() + '%. ' assistant.sendMessage(6, 'Moving Position', message) message = bot.processDatetime.toISOString() + ' - ' + message assistant.sendEmail('Alerts', message, bot.emailSubscriptions) assistant.rememberThis(LAST_SELL_RATE_KEY, currentRate) assistant.movePosition(positions[0], currentRate, callBack) } else if (assetBBalance > 0) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> createSellPosition -> Artuditu put a new SELL position at price: $' + Number(currentRate).toLocaleString()) } let message = 'Creating a new sell position. Price: $' + Number(currentRate).toLocaleString() message += '. Combined ROI on current execution: ' + getCombinedProfit() + '%. ' assistant.sendMessage(7, 'Selling', message) message = bot.processDatetime.toISOString() + ' - ' + message assistant.sendEmail('Alerts', message, bot.emailSubscriptions) assistant.rememberThis(LAST_SELL_RATE_KEY, currentRate) assistant.putPosition('sell', currentRate, amountA, amountB, callBack) } else { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] start -> businessLogic -> createSellPosition -> There is not enough available balance to sell.') } callBack(global.DEFAULT_OK_RESPONSE) } } function pad (str, max) { str = str.toString() return str.length < max ? pad('0' + str, max) : str } function isExecutionToday () { let localDate = new Date() let today = new Date(Date.UTC( localDate.getUTCFullYear(), localDate.getUTCMonth(), localDate.getUTCDate())) return (today.getUTCFullYear() === bot.processDatetime.getUTCFullYear() && today.getUTCMonth() === bot.processDatetime.getUTCMonth() && today.getUTCDate() === bot.processDatetime.getUTCDate()) } function getCombinedProfit () { if (assistant.getCombinedProfits() !== undefined) { if (assistant.getCombinedProfits().assetA > 0) { return Number(assistant.getCombinedProfits().assetA).toLocaleString() } else { return Number(assistant.getCombinedProfits().assetB).toLocaleString() } } } } }