386 lines
17 KiB
JavaScript
386 lines
17 KiB
JavaScript
exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, BLOB_STORAGE, STATUS_REPORT, EXCHANGE_API) {
|
|
|
|
const FULL_LOG = true;
|
|
const LOG_FILE_CONTENT = false;
|
|
|
|
const GMT_SECONDS = ':00.000 GMT+0000';
|
|
const GMT_MILI_SECONDS = '.000 GMT+0000';
|
|
|
|
const MODULE_NAME = "User Bot";
|
|
|
|
const TRADES_FOLDER_NAME = "Trades";
|
|
|
|
thisObject = {
|
|
initialize: initialize,
|
|
start: start
|
|
};
|
|
|
|
let charlyStorage = BLOB_STORAGE.newBlobStorage(bot, logger);
|
|
|
|
let utilities = UTILITIES.newCloudUtilities(bot, logger);
|
|
|
|
let statusDependencies;
|
|
|
|
return thisObject;
|
|
|
|
function initialize(pStatusDependencies, pMonth, pYear, callBackFunction) {
|
|
|
|
try {
|
|
|
|
logger.fileName = MODULE_NAME;
|
|
logger.initialize();
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
|
|
|
|
statusDependencies = pStatusDependencies;
|
|
|
|
charlyStorage.initialize(bot.devTeam, onCharlyInizialized);
|
|
|
|
function onCharlyInizialized(err) {
|
|
|
|
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> onCharlyInizialized -> Initialization Succeed."); }
|
|
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
|
|
|
} else {
|
|
logger.write(MODULE_NAME, "[ERROR] initialize -> onCharlyInizialized -> err = " + err.message);
|
|
callBackFunction(err);
|
|
}
|
|
}
|
|
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
We are going to generate 2 files:
|
|
|
|
A. The first one will contain all the trades of the current minute and will be store in a folder that we will create for this if it does not exist.
|
|
This file will be incomplete, since we are at the current minute and some trades will happen after we retrieve the information from the exchange,
|
|
but this is not a problem, since the second file is going to fix this. This file is only usefull for viewing a partial candle as it is being built
|
|
at the head of the market.
|
|
|
|
B. The second file will contain all the trades of the previous minute. This will override the incomplete file written a minute before.
|
|
|
|
FILE FORMAT
|
|
-----------
|
|
|
|
Array of records with this information:
|
|
|
|
1. Trade Id provided by the exchange.
|
|
2. Trade Type: "buy" or "sell"
|
|
3. Trade Rate: the rate of the transaction.
|
|
4. Amount Asset A
|
|
5. Amount Asset B
|
|
6. Time: Seconds and Milliseconds the trade happened. (the rest of the time and date whoever reads the file already know it since it is organized in folders according to this.)
|
|
|
|
*/
|
|
|
|
function start(callBackFunction) {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); }
|
|
|
|
let market = global.MARKET;
|
|
|
|
let currentDate; // This will hold the current datetime of each execution.
|
|
let previousMinute; // This hold the current time minus 60 seconds.
|
|
let marketQueue; // This is the queue of all markets to be procesesd at each interval.
|
|
|
|
let dateForPathA;
|
|
let dateForPathB;
|
|
|
|
let filePathA;
|
|
let filePathB;
|
|
|
|
let reportFilePath = global.EXCHANGE_NAME + "/Processes/" + bot.process;
|
|
|
|
let exchangeCallTime;
|
|
|
|
firstSteps();
|
|
|
|
function firstSteps() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> firstSteps -> Entering function."); }
|
|
|
|
currentDate = bot.processDatetime;
|
|
|
|
previousMinute = new Date(currentDate.valueOf() - 60000);
|
|
|
|
dateForPathA = currentDate.getUTCFullYear() + '/' + utilities.pad(currentDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDate.getUTCDate(), 2) + '/' + utilities.pad(currentDate.getUTCHours(), 2) + '/' + utilities.pad(currentDate.getUTCMinutes(), 2);
|
|
dateForPathB = previousMinute.getUTCFullYear() + '/' + utilities.pad(previousMinute.getUTCMonth() + 1, 2) + '/' + utilities.pad(previousMinute.getUTCDate(), 2) + '/' + utilities.pad(previousMinute.getUTCHours(), 2) + '/' + utilities.pad(previousMinute.getUTCMinutes(), 2);
|
|
|
|
filePathA = bot.filePathRoot + "/Output/" + TRADES_FOLDER_NAME + '/' + dateForPathA;
|
|
filePathB = bot.filePathRoot + "/Output/" + TRADES_FOLDER_NAME + '/' + dateForPathB;
|
|
|
|
getTheTrades();
|
|
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> firstSteps -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function getTheTrades() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getTheTrades -> Entering function."); }
|
|
|
|
const datetime = parseInt(currentDate.valueOf() / 1000);
|
|
|
|
/*
|
|
We request to the Exchange API some more records than needed, anyway we will discard records out of the range we need.
|
|
To do this we substract 120 seconds and add 10 seconds to the already calculated current date.
|
|
*/
|
|
|
|
const startTime = datetime - 120;
|
|
const endTime = datetime + 10;
|
|
|
|
exchangeCallTime = new Date();
|
|
|
|
EXCHANGE_API.getPublicTradeHistory(market.assetA, market.assetB, startTime, endTime, onExchangeCallReturned);
|
|
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> getTheTrades -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function onExchangeCallReturned(err, exchangeResponse) {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> onExchangeCallReturned -> Entering function."); }
|
|
|
|
if (FULL_LOG === true) {
|
|
|
|
let exchangeResponseTime = new Date();
|
|
let timeDifference = (exchangeResponseTime.valueOf() - exchangeCallTime.valueOf()) / 1000;
|
|
logger.write(MODULE_NAME, "[INFO] start -> onExchangeCallReturned -> Call time recorded = " + timeDifference + " seconds.");
|
|
}
|
|
|
|
tradesReadyToBeSaved(exchangeResponse);
|
|
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> onExchangeCallReturned -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function tradesReadyToBeSaved(exchangeResponse) {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> Entering function."); }
|
|
|
|
let fileRecordCounterA = 0;
|
|
let fileRecordCounterB = 0;
|
|
|
|
let fileNameA = '' + market.assetA + '_' + market.assetB + '.json';
|
|
let fileNameB = '' + market.assetA + '_' + market.assetB + '.json';
|
|
|
|
let needSeparator;
|
|
let separator;
|
|
|
|
needSeparator = false;
|
|
|
|
let fileContent = "";
|
|
|
|
/* First we create the file A */
|
|
|
|
fileContent = fileContent + '[';
|
|
|
|
for (i = 0; i < exchangeResponse.length; i++) {
|
|
|
|
let record = exchangeResponse[exchangeResponse.length - 1 - i]; // In Poloniex the order of the records is by date DESC so we change it to ASC
|
|
|
|
const trade = {
|
|
tradeIdAtExchange: record.tradeID,
|
|
marketIdAtExchange: record.globalTradeID,
|
|
type: record.type,
|
|
rate: record.rate,
|
|
amountA: record.total,
|
|
amountB: record.amount,
|
|
datetime: new Date(record.date + GMT_MILI_SECONDS)
|
|
};
|
|
|
|
trade.seconds = trade.datetime.getUTCSeconds();
|
|
|
|
if (trade.datetime.getUTCMinutes() === currentDate.getUTCMinutes()) {
|
|
|
|
if (needSeparator === false) {
|
|
|
|
needSeparator = true;
|
|
separator = '';
|
|
|
|
} else {
|
|
separator = ',';
|
|
}
|
|
|
|
fileContent = fileContent + separator + '[' + trade.tradeIdAtExchange + ',"' + trade.type + '",' + trade.rate + ',' + trade.amountA + ',' + trade.amountB + ',' + trade.seconds + ']';
|
|
|
|
fileRecordCounterA++;
|
|
}
|
|
}
|
|
|
|
fileContent = fileContent + ']';
|
|
|
|
charlyStorage.createTextFile(filePathA, fileNameA, fileContent + '\n', onFirstFileACreated);
|
|
|
|
function onFirstFileACreated(err) {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFirstFileACreated -> Entering function."); }
|
|
|
|
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> tradesReadyToBeSaved -> onFirstFileACreated -> err = " + err.message);
|
|
callBackFunction(err);
|
|
return;
|
|
}
|
|
|
|
if (LOG_FILE_CONTENT === true) {
|
|
logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFirstFileACreated -> Content written = " + fileContent);
|
|
}
|
|
|
|
logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFirstFileACreated -> Finished with File A @ " + market.assetA + "_" + market.assetB);
|
|
logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFirstFileACreated -> Records inserted = " + fileRecordCounterA);
|
|
logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFirstFileACreated -> Path = " + filePathA + "/" + fileNameA + "");
|
|
|
|
generateFileB();
|
|
}
|
|
|
|
function generateFileB() {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> generateFileB -> Entering function."); }
|
|
|
|
fileContent = "";
|
|
needSeparator = false;
|
|
|
|
fileContent = fileContent + '[';
|
|
|
|
for (i = 0; i < exchangeResponse.length; i++) {
|
|
|
|
let record = exchangeResponse[exchangeResponse.length - 1 - i]; // In Poloniex the order of the records is by date DESC so we change it to ASC
|
|
|
|
const trade = {
|
|
tradeIdAtExchange: record.tradeID,
|
|
marketIdAtExchange: record.globalTradeID,
|
|
type: record.type,
|
|
rate: record.rate,
|
|
amountA: record.total,
|
|
amountB: record.amount,
|
|
datetime: new Date(record.date + GMT_MILI_SECONDS)
|
|
};
|
|
|
|
trade.seconds = trade.datetime.getUTCSeconds();
|
|
|
|
if (trade.datetime.getUTCMinutes() === previousMinute.getUTCMinutes()) {
|
|
|
|
if (needSeparator === false) {
|
|
|
|
needSeparator = true;
|
|
separator = '';
|
|
|
|
} else {
|
|
separator = ',';
|
|
}
|
|
|
|
fileContent = fileContent + separator + '[' + trade.tradeIdAtExchange + ',"' + trade.type + '",' + trade.rate + ',' + trade.amountA + ',' + trade.amountB + ',' + trade.seconds + ']';
|
|
|
|
fileRecordCounterB++;
|
|
}
|
|
}
|
|
|
|
fileContent = fileContent + ']';
|
|
|
|
charlyStorage.createTextFile(filePathB, fileNameB, fileContent + '\n', onFileBCreated);
|
|
|
|
function onFileBCreated(err) {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFileBCreated -> Entering function."); }
|
|
|
|
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> tradesReadyToBeSaved -> onFileBCreated -> err = " + err.message);
|
|
callBackFunction(err);
|
|
return;
|
|
}
|
|
|
|
if (LOG_FILE_CONTENT === true) {
|
|
logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFileBCreated -> Content written = " + fileContent);
|
|
}
|
|
|
|
logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFileBCreated -> Finished with File B @ " + market.assetA + "_" + market.assetB);
|
|
logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFileBCreated -> Content written -> Records inserted = " + fileRecordCounterB);
|
|
logger.write(MODULE_NAME, "[INFO] start -> tradesReadyToBeSaved -> onFileBCreated -> Content written -> Path = " + filePathB + "/" + fileNameB + "");
|
|
|
|
writeStatusReport();
|
|
}
|
|
}
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> tradesReadyToBeSaved -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function writeStatusReport() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeStatusReport -> Entering function."); }
|
|
|
|
let key = bot.devTeam + "-" + bot.codeName + "-" + bot.process + "-" + bot.dataSetVersion;
|
|
|
|
let statusReport = statusDependencies.statusReports.get(key);
|
|
|
|
statusReport.file = {
|
|
firstFile: { // This date points to the file that might be incomplete.
|
|
year: currentDate.getUTCFullYear(),
|
|
month: (currentDate.getUTCMonth() + 1),
|
|
days: currentDate.getUTCDate(),
|
|
hours: currentDate.getUTCHours(),
|
|
minutes: currentDate.getUTCMinutes()
|
|
},
|
|
lastFile: { // This will point to the last file written with is complete. That means it has all the trades in it.
|
|
year: previousMinute.getUTCFullYear(),
|
|
month: (previousMinute.getUTCMonth() + 1),
|
|
days: previousMinute.getUTCDate(),
|
|
hours: previousMinute.getUTCHours(),
|
|
minutes: previousMinute.getUTCMinutes()
|
|
}
|
|
};
|
|
|
|
statusReport.save(onSaved);
|
|
|
|
function onSaved(err) {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeStatusReport -> onSaved -> Entering function."); }
|
|
|
|
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> onSaved -> err = " + err.message);
|
|
callBackFunction(err);
|
|
return;
|
|
}
|
|
|
|
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
|
}
|
|
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
};
|