Files
Superalgos/Trading-Process/User.Bot.js
T
matbenitez bc16cdb244 Moving Positions
Adding fix for moving positions and reducing message priority.
2018-05-07 12:23:47 -03:00

422 lines
20 KiB
JavaScript

var LRCIndicator = require('./LRCIndicator');
exports.newUserBot = function newUserBot(BOT, DEBUG_MODULE, COMMONS_MODULE) {
let bot = BOT;
const FULL_LOG = true;
const MODULE_NAME = "User Bot";
const LOG_INFO = true;
const logger = DEBUG_MODULE.newDebugLog();
logger.fileName = MODULE_NAME;
logger.bot = bot;
let thisObject = {
initialize: initialize,
start: start
};
let assistant; // You will receive a reference to the Traing Platform Advanced Algos Assistant at initialization.
/*
The Traing Platform Advanced Algos Assistant object represents the implementation of the assistant that will allow to create and move positions on the exchange market.
More details about these objects:
datasource = {
candlesFiles: new Map, // Complete sets of candles for different Time Periods. For Time Periods < 1hs sets are of current day only, otherwise whole market.
candlesMap: new Map, // The last 10 candles for each Time Period will be stored here.
stairsFiles: new Map, // Complete sets of patterns for different Time Periods. For Time Periods < 1hs sets are of current day only, otherwise whole market.
stairsMap: new Map // The patterns we are currently in will be stored here.
};
assistant = {
putPosition: putPosition, // To create a buy or sell position.
movePosition: movePosition // To modify an existing position.
};
*/
let oliviaStorage; // This is an example of dependency to other bots
let commons;
return thisObject;
function initialize(pAssistant, callBackFunction) {
try {
if (LOG_INFO === true) { logger.write("[INFO] initialize -> Entering function."); }
logger.fileName = MODULE_NAME;
commons = COMMONS_MODULE;
assistant = pAssistant;
let key = "AAMasters-AAOlivia-Candles-Multi-Period-Daily-dataSet.V1";
oliviaStorage = assistant.dataDependencies.dataSets.get(key);
callBackFunction(global.DEFAULT_OK_RESPONSE);
} catch (err) {
logger.write("[ERROR] initialize -> onDone -> err = " + err.message);
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
}
}
function pad(str, max) {
str = str.toString();
return str.length < max ? pad("0" + str, max) : str;
}
function start(callBackFunction) {
try {
if (LOG_INFO === true) { logger.write("[INFO] start -> Entering function."); }
/*
This trading bot will use an strategy based on the interpretation of the Linear Regression Curve Channel.
*/
businessLogic(onDone);
function onDone(err) {
try {
switch (err.result) {
case global.DEFAULT_OK_RESPONSE.result: {
logger.write("[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("[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("[ERROR] start -> onDone -> Operation Failed. Aborting the process. err = " + err.message);
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
return;
}
}
} catch (err) {
logger.write("[ERROR] start -> onDone -> err = " + err.message);
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
}
}
function businessLogic(callBack) {
if (LOG_INFO === true) { logger.write("[INFO] start -> businessLogic -> Entering function."); }
getChannelTilt(botDecision);
function botDecision(err, channelTilt) {
if (LOG_INFO === true) { logger.write("[INFO] start -> businessLogic -> botDecision -> LRC Channel Tilt:" + channelTilt); }
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
logger.write("[ERROR] start -> businessLogic -> err = " + err.message);
callBack(global.DEFAULT_RETRY_RESPONSE);
} else {
if (channelTilt == 1) {
createBuyPosition(callBack);
} else if (channelTilt == -1) {
createSellPosition(callBack);
} else {
if (LOG_INFO === true) { logger.write("[INFO] start -> processBotDecision -> Nothing to do, there isn't a sell or buy oportunity."); }
callBack(global.DEFAULT_OK_RESPONSE);
}
}
}
}
function createBuyPosition(callBack) {
if (LOG_INFO === true) { logger.write("[INFO] start -> createBuyPosition -> Entering function."); }
let positions = assistant.getPositions();
let assetABalance = assistant.getAvailableBalance().assetA;
let currentRate = assistant.getMarketRate();
let amountA = assistant.getAvailableBalance().assetA;
let amountB = amountA / currentRate;
if(positions.length > 0 && positions[0].type === "buy" && (bot.processDatetime.valueOf() - positions[0].date) > (60000 * 5)){
assistant.movePosition(positions[0], currentRate, callBack);
if (LOG_INFO === true) { logger.write("[INFO] start -> createBuyPosition -> Artuditu is moving an existing position to a new rate: " + currentRate.toFixed(8) + ". Position: " + JSON.stringify(positions[0])); }
let message = "I'm moving an existing buy position to a new rate: " + currentRate.toFixed(8);
assistant.sendMessage(1, "Moving Position", message);
} else if(assetABalance > 0){
assistant.putPosition("buy", currentRate, amountA, amountB, callBack);
let message = "I'm creating a new buy position at rate: " + currentRate.toFixed(8) + ". " + MARKET.assetA +" amount: " + amountA.toFixed(8) + ". " + MARKET.assetB + " amount: " + amountB.toFixed(8);
assistant.sendMessage(1, "Buying", message);
} else {
if (LOG_INFO === true) { logger.write("[INFO] start -> createBuyPosition -> There is not enough available balance to buy. Available balance: " + assetABalance ); }
callBack(global.DEFAULT_OK_RESPONSE);
}
}
function createSellPosition(callBack) {
if (LOG_INFO === true) { logger.write("[INFO] start -> createSellPosition -> Entering function."); }
let positions = assistant.getPositions();
let assetBBalance = assistant.getAvailableBalance().assetB;
let currentRate = assistant.getMarketRate();
let amountB = assistant.getAvailableBalance().assetB;
let amountA = amountB * currentRate;
if(positions.length > 0 && positions[0].type === "sell" && (bot.processDatetime.valueOf() - positions[0].date) > (60000 * 5)){
assistant.movePosition(positions[0], currentRate, callBack);
if (LOG_INFO === true) { logger.write("[INFO] start -> createBuyPosition -> Artuditu is moving an existing position to a new rate: " + currentRate.toFixed(8) + ". Position: " + JSON.stringify(positions[0])); }
let message = "I'm moving an existing sell position to a new rate: " + currentRate.toFixed(8);
assistant.sendMessage(1, "Moving Position", message);
}else if(assetBBalance > 0){
assistant.putPosition("sell", currentRate, amountA, amountB, callBack);
if (LOG_INFO === true) { logger.write("[INFO] start -> createSellPosition -> Artuditu put a new SELL Position at rate: " + currentRate + ". Amount traded asset A: " + amountA + ". Amount traded asset B: " + amountB); }
let message = "I'm creating a new sell position at rate: " + currentRate.toFixed(8) + ". " + MARKET.assetA +" amount: " + amountA.toFixed(8) + ". " + MARKET.assetB + " amount: " + amountB.toFixed(8);
assistant.sendMessage(1, "Selling", message);
} else {
if (LOG_INFO === true) { logger.write("[INFO] start -> createBuyPosition -> There is not enough available balance to sell. Available balance: " + assetBBalance ); }
callBack(global.DEFAULT_OK_RESPONSE);
}
}
function getChannelTilt(callBack) {
if (LOG_INFO === true) { logger.write("[INFO] start -> getChannelTilt -> Entering function."); }
const CHANNEL_DOWN = -1;
const CHANNEL_UP = 1;
const NO_CHANNEL = 0;
const maxLRCDepth = 62; // 61 to be able to calculate current lrc60, plus one to calculate previous lrc60
const maxBackwardsCount = 60;
let backwardsCount = 0;
let candleArray = [];
let queryDate = new Date(bot.processDatetime);
let candleFile = getDailyFile(queryDate, onDailyFileReceived);
function onDailyFileReceived(err, candleFile) {
if (LOG_INFO === true) { logger.write("[INFO] start -> getChannelTilt -> onDailyFileReceived." ); }
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
for (let i = 0; i < candleFile.length; i++) {
let candle = {
open: undefined,
close: undefined,
min: 10000000000000,
max: 0,
begin: undefined,
end: undefined,
direction: undefined
};
candle.min = candleFile[i][0];
candle.max = candleFile[i][1];
candle.open = candleFile[i][2];
candle.close = candleFile[i][3];
candle.begin = candleFile[i][4];
candle.end = candleFile[i][5];
if (candle.open > candle.close) { candle.direction = 'down'; }
if (candle.open < candle.close) { candle.direction = 'up'; }
if (candle.open === candle.close) { candle.direction = 'side'; }
if (LOG_INFO === true) { logger.write("[INFO] Candle Date: " + new Date(candle.begin).toISOString() + ". Process Date: " + bot.processDatetime.toISOString()); }
if (candleArray.length < maxLRCDepth && candle.begin <= bot.processDatetime.valueOf()) {
candleArray.push(candleFile[i]);
}
}
if (LOG_INFO === true) { logger.write("[INFO] start -> getChannelTilt -> Candle Array Length: " + candleArray.length); }
if (candleArray.length >= maxLRCDepth) {
if (LOG_INFO === true) { logger.write("[INFO] start -> getChannelTilt -> All candles available proceed with LRC calculations."); }
performLRCCalculations(callBack);
}else if (backwardsCount <= maxBackwardsCount) {
if (LOG_INFO === true) { logger.write("[INFO] start -> getChannelTilt -> Getting file for day before."); }
queryDate.setDate(queryDate.getDate() - 1);
getDailyFile(queryDate, onDailyFileReceived);
backwardsCount++;
} else {
logger.write("[ERROR] start -> getChannelTilt -> Not enough history to calculate LRC.");
callBack(global.DEFAULT_RETRY_RESPONSE);
}
}
}
function performLRCCalculations(callBack){
/*
* It's needed to order since it's possible that we need to get another file and it will put an older candle at the end of the array.
*/
candleArray.sort(function (a, b) {
return a[4] - b[4];
});
let lrcPoints = calculateLRC(candleArray);
let lrc15 = lrcPoints.minimumChannelValue;
let lrc30 = lrcPoints.middleChannelValue;
let lrc60 = lrcPoints.maximumChannelValue;
/**
* We take the last candle (because it's the newest) and calculate the LRC points again to detect the tilt.
*/
let previousCandleArray = candleArray.slice(0, candleArray.length - 1);
let lrcPreviousPoints = calculateLRC(previousCandleArray);
let previousLrc15 = lrcPreviousPoints.minimumChannelValue;
let previousLrc30 = lrcPreviousPoints.middleChannelValue;
let previousLrc60 = lrcPreviousPoints.maximumChannelValue;
let channelTilt = NO_CHANNEL;
let ruleApplied = "";
if (lrc60 < lrc30 && lrc15 > lrc30 && lrc30 < lrc15) {
if (lrc15 > previousLrc15 && lrc30 > previousLrc30 && lrc60 > previousLrc60) {
channelTilt = CHANNEL_UP; // The channel points UP
ruleApplied += "Rule_1.";
}
}
if (lrc15 < lrc30 && lrc60 > lrc30 && lrc30 < lrc60) {
if (lrc15 < previousLrc15 && lrc30 < previousLrc30 && lrc60 < previousLrc60) {
channelTilt = CHANNEL_DOWN; // The channel points DOWN
ruleApplied += "Rule_2.";
}
}
if (lrc15 < previousLrc15 && lrc30 <= previousLrc30) {
// 15 AND 30 changed direction from up to down
channelTilt = CHANNEL_DOWN;
ruleApplied += "Rule_3a.";
}
if (lrc15 > previousLrc15 && lrc30 >= previousLrc30) {
// 15 AND 30 changed direction from down to up
channelTilt = CHANNEL_UP;
ruleApplied += "Rule_3b.";
}
let logMessage = bot.processDatetime.toISOString() + "\t" + ruleApplied + "\t" + channelTilt + "\t" + lrc15 + "\t" + lrc30 + "\t" + lrc60 ;
if (LOG_INFO === true) { logger.write("[INFO] start -> getChannelTilt -> performLRCCalculations -> Results: "+ logMessage); }
callBack(global.DEFAULT_OK_RESPONSE, channelTilt);
}
}
function getDailyFile(dateTime, onDailyFileReceived) {
try {
if (FULL_LOG === true) { logger.write("[INFO] start -> getChannelTilt -> getDailyFile -> Entering function."); }
//TODO Hardcoded parameters
let periodTime = 1800000;
let periodName = "30-min";
let pFileName = MARKET.assetA + "_" + MARKET.assetB + ".json";
let pFilePath = "Candles/Multi-Period-Daily/";
pFilePath += periodName;
pFilePath += "/" + dateTime.getUTCFullYear();
pFilePath += "/" + pad(dateTime.getUTCMonth() + 1, 2);
pFilePath += "/" + pad(dateTime.getUTCDate(), 2);
oliviaStorage.getTextFile(pFilePath, pFileName, onFileReceived);
function onFileReceived(err, text) {
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
if (FULL_LOG === true) { logger.write("[INFO] start -> getChannelTilt -> getDailyFile -> onFileReceived > Entering Function."); }
let candleFile = JSON.parse(text);
onDailyFileReceived(global.DEFAULT_OK_RESPONSE, candleFile);
} else {
logger.write("[ERROR] start -> getChannelTilt -> getDailyFile -> onFileReceived -> Failed to get the file. Will abort the process and request a retry.");
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
return;
}
}
} catch (err) {
logger.write("[ERROR] start -> getChannelTilt -> getDailyFile -> err = " + err.message);
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
}
}
function calculateLRC(candlesArray) {
if (LOG_INFO === true) { logger.write("[INFO] start -> getChannelTilt -> calculateLRC -> Entering function."); }
let minimumChannelDepth = 15;
let middleChannelDepth = 30;
let maximumChannelDepth = 60;
let lrcPoints = {
minimumChannelValue: 0,
middleChannelValue: 0,
maximumChannelValue: 0,
lrcTimeBegin: 0
};
let lrcMinIndicator = new LRCIndicator(minimumChannelDepth);
let lrcMidIndicator = new LRCIndicator(middleChannelDepth);
let lrcMaxIndicator = new LRCIndicator(maximumChannelDepth);
let currentCandle = candlesArray[candlesArray.length - 1];
for (let i = 0; i < candlesArray.length; i++) {
let tempCandle = candlesArray[i];
let averagePrice = (tempCandle[0] + tempCandle[1] + tempCandle[2] + tempCandle[3]) / 4; // TODO Check which price should be take to get the LRC
lrcMinIndicator.update(averagePrice);
lrcMidIndicator.update(averagePrice);
lrcMaxIndicator.update(averagePrice);
}
if (FULL_LOG === true) {
logger.write("[INFO] start -> getChannelTilt -> calculateLRC -> candlesArray.length: " + candlesArray.length + ". lrcMinIndicator: " + lrcMinIndicator.age + ". lrcMidIndicator: "
+ lrcMidIndicator.age + ". lrcMaxIndicator: " + lrcMaxIndicator.age);
}
if (FULL_LOG === true) {
logger.write("[INFO] start -> getChannelTilt -> calculateLRC -> Values: " + "lrcMinIndicator: " + lrcMinIndicator.result + ". lrcMidIndicator: "
+ lrcMidIndicator.result + ". lrcMaxIndicator: " + lrcMaxIndicator.result);
}
/*
* Only if there is enough history the result will be calculated
*/
if (lrcMinIndicator.result != false && lrcMidIndicator.result != false && lrcMaxIndicator.result != false) {
lrcPoints.minimumChannelValue = lrcMinIndicator.result;
lrcPoints.middleChannelValue = lrcMidIndicator.result;
lrcPoints.maximumChannelValue = lrcMaxIndicator.result;
lrcPoints.lrcTimeBegin = currentCandle[4];
return lrcPoints;
} else {
logger.write("[ERROR] start -> getChannelTilt -> calculateLRC -> There is not enough history to calculate the LRC.");
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
}
}
} catch (err) {
logger.write("[ERROR] start -> err = " + err.message);
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
}
}
};