This repository has been archived on 2025-10-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
gekko/plugins/pushbullet.js
2021-06-06 20:25:14 +02:00

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;