Skip to content
This repository has been archived by the owner on Feb 16, 2020. It is now read-only.

Commit

Permalink
Pushbullet Plugin update round 2 (#2607)
Browse files Browse the repository at this point in the history
* Add functionality to pushbullet plugin. Now gives trade information.

* Revert default plugin enebled to false

* Improved time source

* Merge branch 'develop' of https://github.com/askmike/gekko into pushbullet-update

* Updated formatting, Added Round Trip PL to subject

* Updated Start message and further formatting

* Fixed Cost Percentage Calculation
  • Loading branch information
GryphonR authored and askmike committed Dec 2, 2018
1 parent 0ec34c6 commit 617f819
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 109 deletions.
249 changes: 187 additions & 62 deletions plugins/pushbullet.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ config.pushbullet = {
enabled: true,
// Send 'Gekko starting' message if true
sendMessageOnStart: true,
// Send Message for advice?
// Send Message for advice? Recommend Flase for paper, true for live
sendOnAdvice: true,
// Send Message on Trade Completion?
sendOnTrade: true,
// disable advice printout if it's soft
muteSoft: true,
// For Overall P/L calc. Pass in old balance if desired, else leave '0'
startingBalance: '0',
// your pushbullet API key
key: '',
// your email
Expand All @@ -32,19 +32,23 @@ config.pushbullet = {
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 pushbulletConfig = config.pushbullet;
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();
Expand All @@ -53,25 +57,26 @@ var Pushbullet = function(done) {
Pushbullet.prototype.setup = function(done) {

var setupPushBullet = function(err, result) {
if (pushbulletConfig.sendMessageOnStart) {
var title = pushbulletConfig.tag;
if (pbConf.sendMessageOnStart) {
var title = pbConf.tag;
var exchange = config.watch.exchange;
var currency = config.watch.currency;
var asset = config.watch.asset;
var body = "Gekko has started watching " +
currency +
"/" +
asset +
" on " +
exchange +
".";

let tradeType = 'watching';
if (config.trader.enabled) {
body += "\nLive Trading is enabled"
tradeType = "Live Trading";
}
if (config.paperTrader.enabled) {
body += "\nPaper Trading is 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')
Expand All @@ -88,86 +93,206 @@ Pushbullet.prototype.processCandle = function(candle, done) {


Pushbullet.prototype.processAdvice = function(advice) {
if (advice.recommendation == "soft" && pushbulletConfig.muteSoft) return;

this.advicePrice = this.price;
this.adviceTime = advice.date;

if (pushbulletConfig.sendOnAdvice) {
if (pbConf.sendOnAdvice) {

var text = [
'Gekko has new advice for ',
config.watch.exchange,
capF(config.watch.exchange),
', advice is to go ',
advice.recommendation,
capF(advice.recommendation),
'.\n\nThe current ',
config.watch.asset,
' price is ',
this.advicePrice
].join('');

var subject = pushbulletConfig.tag + ' New advice: go ' + advice.recommendation;
var subject = pbConf.tag + ' New advice: go ' + advice.recommendation;

this.mail(subject, text);
}
};

Pushbullet.prototype.processTradeCompleted = function(trade) {
if (pushbulletConfig.sendOnTrade) {
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);
} else {
slip = '1234';
}

let tradeTime = trade.date;
let diff = tradeTime.diff(this.adviceTime);
let timeToComplete = moment.utc(diff).format("mm:ss");
//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;

// timeToComplete = trade.date - this.adviceTime;
// timeToComplete.format('h:mm:ss');
if (sendOnTrade) {

var text = [
config.watch.exchange,
' ',
config.watch.asset,
'/',
config.watch.currency,
'\nAdvice Price: ',
this.advicePrice,
'\nTrade Price: ',
trade.price,
'\nSlip: ',
slip.toFixed(2), '%',
'\nAdvice Time: ',
this.adviceTime.format("h:mm:ss"),
'\nTrade Time: ',
tradeTime.format("h:mm:ss"),
'\nTime to Fill: ',
timeToComplete
// 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

].join('');

var subject = '';
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)}%`;

subject = pushbulletConfig.tag + ' ' + trade.action + ' Complete ';
//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(pushbulletConfig.key);
pusher.note(pushbulletConfig.email, subject, content, function(error, response) {
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) {
Expand Down
Loading

0 comments on commit 617f819

Please sign in to comment.