311 lines
9.1 KiB
JavaScript
311 lines
9.1 KiB
JavaScript
/**
|
|
* Created by rocketman1337345 on 8/14/16.
|
|
* Extended by RJPGriffin (Gryphon) on 30/5/18
|
|
*/
|
|
|
|
|
|
/**
|
|
Required Config:
|
|
|
|
config.pushbullet = {
|
|
// sends pushbullets if true
|
|
enabled: true,
|
|
// Send 'Gekko starting' message if true
|
|
sendMessageOnStart: true,
|
|
// Send Message for advice? Recommend Flase for paper, true for live
|
|
sendOnAdvice: true,
|
|
// Send Message on Trade Completion?
|
|
sendOnTrade: true,
|
|
// For Overall P/L calc. Pass in old balance if desired, else leave '0'
|
|
startingBalance: '0',
|
|
// your pushbullet API key
|
|
key: '',
|
|
// your email
|
|
email: '[email protected]',
|
|
// Messages will start with this tag
|
|
tag: '[GEKKO]'
|
|
};
|
|
|
|
|
|
**/
|
|
|
|
var pushbullet = require("pushbullet");
|
|
var _ = require('lodash');
|
|
const moment = require('moment');
|
|
const request = require('request');
|
|
var log = require('../core/log.js');
|
|
var util = require('../core/util.js');
|
|
var config = util.getConfig();
|
|
var pbConf = config.pushbullet;
|
|
|
|
var Pushbullet = function(done) {
|
|
_.bindAll(this);
|
|
|
|
this.pusher;
|
|
this.price = 'N/A';
|
|
this.startingBalance = pbConf.startingBalance === undefined ? 0 : Number(pbConf.startingBalance);
|
|
this.advicePrice = 0;
|
|
this.adviceTime = moment();
|
|
this.lastBuyTime = moment();
|
|
this.lastBuyBalance = 0;
|
|
this.hasBought = 0;
|
|
|
|
this.done = done;
|
|
this.setup();
|
|
};
|
|
|
|
Pushbullet.prototype.setup = function(done) {
|
|
|
|
var setupPushBullet = function(err, result) {
|
|
if (pbConf.sendMessageOnStart) {
|
|
var title = pbConf.tag;
|
|
var exchange = config.watch.exchange;
|
|
var currency = config.watch.currency;
|
|
var asset = config.watch.asset;
|
|
let tradeType = 'watching';
|
|
if (config.trader.enabled) {
|
|
tradeType = "Live Trading";
|
|
}
|
|
if (config.paperTrader.enabled) {
|
|
tradeType = "Paper Trading";
|
|
}
|
|
|
|
var body = `Gekko has started ${tradeType} ${asset}/${currency} on ${exchange}.`;
|
|
|
|
//If trading Advisor is enabled, add strategy and candle size information
|
|
if (config.tradingAdvisor.enabled) {
|
|
body += `\n\nUsing ${config.tradingAdvisor.method} strategy on M${config.tradingAdvisor.candleSize} candles.`
|
|
}
|
|
|
|
this.mail(title, body);
|
|
} else {
|
|
log.debug('Skipping Send message on startup')
|
|
}
|
|
};
|
|
setupPushBullet.call(this)
|
|
};
|
|
|
|
Pushbullet.prototype.processCandle = function(candle, done) {
|
|
this.price = candle.close;
|
|
|
|
done();
|
|
};
|
|
|
|
|
|
Pushbullet.prototype.processAdvice = function(advice) {
|
|
|
|
this.advicePrice = this.price;
|
|
this.adviceTime = advice.date;
|
|
|
|
if (pbConf.sendOnAdvice) {
|
|
|
|
var text = [
|
|
'Gekko has new advice for ',
|
|
capF(config.watch.exchange),
|
|
', advice is to go ',
|
|
capF(advice.recommendation),
|
|
'.\n\nThe current ',
|
|
config.watch.asset,
|
|
' price is ',
|
|
this.advicePrice
|
|
].join('');
|
|
|
|
var subject = pbConf.tag + ' New advice: go ' + advice.recommendation;
|
|
|
|
this.mail(subject, text);
|
|
}
|
|
};
|
|
|
|
Pushbullet.prototype.processTradeCompleted = function(trade) {
|
|
|
|
//Check Starting balance is initialized - if 0, initialize it
|
|
this.startingBalance = this.startingBalance ? this.startingBalance : trade.balance;
|
|
|
|
// If config variable doesn't exist (old config) defaults to send
|
|
let sendOnTrade = pbConf.sendOnTrade === undefined ? 1 : pbConf.sendOnTrade;
|
|
|
|
if (sendOnTrade) {
|
|
|
|
// Calculate exposure Time
|
|
let exposureTimeStr = '';
|
|
let balanceChangeStr = '\n';
|
|
let totBalanceChangeStr = '';
|
|
let subject = `${pbConf.tag} ${capF(trade.action)} complete`;
|
|
|
|
if (trade.action === 'buy') {
|
|
this.hasBought = 1; // Flag to ensure that the following variables have been filled
|
|
this.lastBuyTime = trade.date;
|
|
this.lastBuyBalance = trade.balance;
|
|
} else if (this.hasBought) { //if sell and we have previous buy data
|
|
exposureTimeStr = `\nExposure Time: ${moment.duration(trade.date.diff(this.lastBuyTime)).humanize()}`;
|
|
|
|
//Calculate balance change
|
|
let oBal = this.lastBuyBalance; // Old Balance
|
|
let nBal = trade.balance; // New Balance
|
|
let diffBal = Math.abs(nBal - oBal); // Balance Difference
|
|
let percDiffBal = (diffBal / oBal) * 100; // Percentage difference
|
|
|
|
|
|
if (nBal >= oBal) { // profit!
|
|
balanceChangeStr = `\n\nRound trip profit of: \n${getNumStr(diffBal)}${config.watch.currency} \n${getNumStr(percDiffBal,2)}%\n`
|
|
subject = `${subject}: +${getNumStr(percDiffBal,2)}%`
|
|
} else if (nBal < oBal) { // Loss :(
|
|
balanceChangeStr = `\n\nRound trip loss of: \n-${getNumStr(diffBal)}${config.watch.currency} \n-${getNumStr(percDiffBal,2)}%\n`
|
|
subject = `${subject}: -${getNumStr(percDiffBal,2)}%`
|
|
}
|
|
|
|
//Calculate overall P/l
|
|
let sBal = this.startingBalance;
|
|
let tDiffBal = Math.abs(nBal - sBal);
|
|
let percDiffTotBal = (tDiffBal / sBal) * 100;
|
|
if (nBal >= sBal) { // profit!
|
|
totBalanceChangeStr = `\nOverall gain of: \n${getNumStr(tDiffBal)}${config.watch.currency} \n${getNumStr(percDiffTotBal,2)}%\n`
|
|
} else if (nBal < sBal) { // Loss :(
|
|
totBalanceChangeStr = `\nOverall loss of \n-${getNumStr(tDiffBal)}${config.watch.currency} \n-${getNumStr(percDiffTotBal,2)}%\n`
|
|
|
|
} else if (trade.action === 'sell' && !this.hasBought) {
|
|
balanceChangeStr = `\n\nNot enough data for exposure time, round trip or overall performance yet. This will appear after bot has completed first round trip.`
|
|
}
|
|
}
|
|
|
|
let costOfTradeStr = `\nCost of Trade: ${getNumStr(trade.cost)}${config.watch.currency}, ${getNumStr(((trade.cost / (trade.amount*trade.price)) * 100), 2)}%`;
|
|
|
|
//build strings that are only sent for Live trading, not paperTrader
|
|
let orderFillTimeStr = '';
|
|
let slippageStr = '';
|
|
|
|
if (!config.paperTrader.enabled) {
|
|
let timeToComplete = moment.duration(trade.date.diff(this.adviceTime)).humanize();
|
|
orderFillTimeStr = `\nOrder fill Time: ${timeToComplete}`;
|
|
|
|
var slip;
|
|
//Slip direction is opposite for buy and sell
|
|
if (trade.price === this.advicePrice) {
|
|
slip = 0;
|
|
} else if (trade.action === 'buy') {
|
|
slip = 100 * ((trade.price - this.advicePrice) / this.advicePrice);
|
|
} else if (trade.action === 'sell') {
|
|
slip = 100 * ((this.advicePrice - trade.price) / this.advicePrice);
|
|
}
|
|
slippageStr = `\nSlipped ${getNumStr(slip,2)}% from advice @ ${getNumStr(this.advicePrice)}`;
|
|
|
|
|
|
}
|
|
|
|
var text = [
|
|
capF(config.watch.exchange), ' ', config.watch.asset, '/', config.watch.currency,
|
|
`\n\n${config.watch.asset} Trade Price: ${getNumStr(trade.price)}`,
|
|
`\n${getPastTense(trade.action)} ${getNumStr(trade.amount)} ${config.watch.asset}`,
|
|
orderFillTimeStr,
|
|
slippageStr,
|
|
costOfTradeStr,
|
|
exposureTimeStr,
|
|
balanceChangeStr,
|
|
totBalanceChangeStr,
|
|
'\nBalance: ', getNumStr(trade.balance), config.watch.currency,
|
|
].join('');
|
|
|
|
this.mail(subject, text);
|
|
}
|
|
};
|
|
|
|
|
|
// A long winded function to make sure numbers aren't displayed with too many decimal places
|
|
// and are a little humanized
|
|
function getNumStr(num, fixed = 4) {
|
|
let numStr = '';
|
|
|
|
if (typeof num != "number") {
|
|
num = Number(num);
|
|
if (isNaN(num)) {
|
|
// console.log("Pushbullet Plugin: Number Conversion Failed");
|
|
return "Conversion Failure";
|
|
}
|
|
}
|
|
|
|
|
|
if (Number.isInteger(num)) {
|
|
numStr = num.toString();
|
|
} else {
|
|
|
|
//Create modNum Max - Must be a better way...
|
|
let modNumMax = '1';
|
|
for (let i = 1; i < fixed; i++) {
|
|
modNumMax = modNumMax + '0';
|
|
}
|
|
modNumMax = Number(modNumMax);
|
|
|
|
let i = 0;
|
|
if (num < 1) {
|
|
let modNum = num - Math.floor(num);
|
|
while (modNum < modNumMax && i < 8) {
|
|
modNum *= 10;
|
|
i += 1;
|
|
}
|
|
} else {
|
|
i = fixed;
|
|
}
|
|
numStr = num.toFixed(i);
|
|
//Remove any excess zeros
|
|
while (numStr.charAt(numStr.length - 1) === '0') {
|
|
numStr = numStr.substring(0, numStr.length - 1);
|
|
}
|
|
|
|
//If last char remaining is a decimal point, remove it
|
|
if (numStr.charAt(numStr.length - 1) === '.') {
|
|
numStr = numStr.substring(0, numStr.length - 1);
|
|
}
|
|
|
|
}
|
|
|
|
//Add commas for thousands etc
|
|
let dp = numStr.indexOf('.'); //find deciaml point
|
|
if (dp < 0) { //no dp found
|
|
dp = numStr.length;
|
|
}
|
|
|
|
let insPos = dp - 3;
|
|
insCount = 0;
|
|
while (insPos > 0) {
|
|
insCount++;
|
|
numStr = numStr.slice(0, insPos) + ',' + numStr.slice(insPos);
|
|
insPos -= 3;
|
|
}
|
|
|
|
|
|
return (numStr);
|
|
}
|
|
|
|
function capF(inWord) { //Capitalise first letter of string
|
|
return (inWord.charAt(0).toUpperCase() + inWord.slice(1));
|
|
}
|
|
|
|
function getPastTense(action) {
|
|
let ret = '';
|
|
if (action === 'buy') {
|
|
ret = 'Bought'
|
|
} else if (action === 'sell') {
|
|
ret = 'Sold'
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Pushbullet.prototype.mail = function(subject, content, done) {
|
|
var pusher = new pushbullet(pbConf.key);
|
|
pusher.note(pbConf.email, subject, content, function(error, response) {
|
|
if (error || !response) {
|
|
log.error('Pushbullet ERROR:', error)
|
|
} else if (response && response.active) {
|
|
log.info('Pushbullet Message Sent')
|
|
}
|
|
});
|
|
};
|
|
|
|
Pushbullet.prototype.checkResults = function(err) {
|
|
if (err)
|
|
log.warn('error sending pushbullet message', err);
|
|
else
|
|
log.info('Send advice via pushbullet message.');
|
|
};
|
|
|
|
module.exports = Pushbullet; |