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

Commit

Permalink
Add notifiers: slack, ifttt, xmpp, pushbullet (#595)
Browse files Browse the repository at this point in the history
  • Loading branch information
DeviaVir committed Oct 6, 2017
1 parent 6d881e4 commit af42c42
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 50 deletions.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ From left to right:

### The `macd` strategy

The moving average convergence divergence calculation is a lagging indicator, used to follow trends.
The moving average convergence divergence calculation is a lagging indicator, used to follow trends.

- Can be very effective for trading periods of 1h, with a shorter period like 15m it seems too erratic and the Moving Averages are kind of lost.
- It's not firing multiple 'buy' or 'sold' signals, only one per trend, which seems to lead to a better quality trading scheme.
Expand Down Expand Up @@ -467,6 +467,30 @@ Trade when % change from last two 1m periods is higher than average.
- In a market with predictable price surges and corrections, `--profit_stop_enable_pct=10` will try to sell when the last buy hits 10% profit and then drops to 9% (the drop % is set with `--profit_stop_pct`). However in strong, long uptrends this option may end up causing a sell too early.
- For Kraken and GDAX you may wish to use `--order_type="taker"`, this uses market orders instead of limit orders. You usually pay a higher fee, but you can be sure that your order is filled instantly. This means that the sim will more closely match your live trading. Please note that GDAX does not charge maker fees (limit orders), so you will need to choose between not paying fees and running the risk orders do not get filled on time, or paying somewhat high % of fees and making sure your orders are always filled on time.

## Notifiers

Zenbot employs various notifiers to keep you up to date on the bot's actions. We currently send a notification on a buy and on a sell signal.

### pushbullet

Supply zenbot with your api key and device ID and we will send your notifications to your device.
https://www.pushbullet.com/

### Slack

Supply zenbot with a webhook URI and zenbot will push notifications to your webhook.
https://slack.com/

### XMPP

Supply zenbot with your XMPP credentials and zenbot will send notifications by connecting to your XMPP, sending the notification, and disconnecting.
https://xmpp.org/

### IFTTT

Supply zenbot with your IFTTT maker key and zenbot will push notifications to your IFTTT.
https://ifttt.com/maker_webhooks

## Manual trade tools

Zenbot's order execution engine can also be used for manual trades. Benefits include:
Expand Down
53 changes: 33 additions & 20 deletions conf-sample.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ c.profit_stop_pct = 1

// avoid trading at a slippage above this pct
c.max_slippage_pct = 5
// buy with this % of currency balance (WARNING : sim won't work properly if you set this value to 100)
// buy with this % of currency balance (WARNING : sim won't work properly if you set this value to 100)
c.buy_pct = 99
// sell with this % of asset balance (WARNING : sim won't work properly if you set this value to 100)
c.sell_pct = 99
Expand Down Expand Up @@ -134,22 +134,35 @@ c.balance_snapshot_period = '15m'
// avg. amount of slippage to apply to sim trades
c.avg_slippage_pct = 0.045

//xmpp configs

c.xmppon=0 // 0 xmpp disabled; 1 xmpp enabled (credentials should be correct)

if (c.xmppon) {

c.xmpp = require('simple-xmpp');

c.xmpp.connect({
jid : 'trader@domain.com', //xmpp account trader bot
password : 'Password', //xmpp password
host : 'domain.com', //xmpp domain
port : 5222 //xmpp port
});

c.xmppto="MeMyselfAndI@domain.com" //xmpp alert to friend
}
//end xmpp configs

// Notifiers:
c.notifiers = {}

// xmpp config
c.notifiers.xmpp = {}
c.notifiers.xmpp.on = false // false xmpp disabled; true xmpp enabled (credentials should be correct)
c.notifiers.xmpp.jid = 'trader@domain.com'
c.notifiers.xmpp.password = 'Password'
c.notifiers.xmpp.host = 'domain.com'
c.notifiers.xmpp.port = 5222
c.notifiers.xmpp.to = 'MeMyselfAndI@domain.com'
// end xmpp configs

// pushbullets configs
c.notifiers.pushbullet = {}
c.notifiers.pushbullet.on = false // false pushbullets disabled; true pushbullets enabled (key should be correct)
c.notifiers.pushbullet.key = 'YOUR-API-KEY'
c.notifiers.pushbullet.deviceID = 'YOUR-DEVICE-ID'
// end pushbullets configs

// ifttt configs
c.notifiers.ifttt = {}
c.notifiers.ifttt.on = false // false ifttt disabled; true ifttt enabled (key should be correct)
c.notifiers.ifttt.makerKey = 'YOUR-API-KEY'
c.notifiers.ifttt.eventName = 'zenbot'
// end ifttt configs

// slack config
c.notifiers.slack = {}
c.notifiers.slack.on = false
c.notifiers.slack.webhook_url = ''
// end slack config
43 changes: 28 additions & 15 deletions docs/developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document is written to help developers implement new extensions for Zenbot.
It is reverse engineered from inspecting the Zenbot files and the GDAX extension and is not a definitive guide for developing an extension.

Any contribution that makes this document better is certainly welcome.

The document is an attempt to describe the interface functions used for communication with an exchange and a few helper functions. Each function has a set of calling parameters and return values and statuses

The input parameters are packed in the "opts" object, and the results from invoking the function are returned in an object.
Expand All @@ -27,7 +27,7 @@ The expected content of "err" is as follows:

Some named errors are already handled by the main program (see getTrades below). These are:
```
'ETIMEDOUT', // possibly recoverable
'ETIMEDOUT', // possibly recoverable
'ENOTFOUND', // not recoverable (404?)
'ECONNRESET' // possibly recoverable
```
Expand All @@ -37,7 +37,7 @@ Zenbot may have some GDAX-specific code. In particular that pertains to return v
```
name: 'some_exchange_name'
historyScan: 'forward', 'backward' or false
makerFee: exchange_maker_fee (numeric) // Set by a function if the exchange supports it
makerFee: exchange_maker_fee (numeric) // Set by a function if the exchange supports it
takerFee: exchange_taker_fee (numeric) // Else set with a constant
backfillRateLimit: some_value_fitting_exchange_policy or 0
```
Expand Down Expand Up @@ -96,7 +96,7 @@ Input:
Return:
```
trades.length
(array of?) {
(array of?) {
trade_id: some_id
time: 'transaction_time',
size: trade_size,
Expand All @@ -107,8 +107,8 @@ Return:
Expected error codes if error:
```
err.code
'ETIMEDOUT', // possibly recoverable
'ETIMEDOUT', // possibly recoverable
'ENOTFOUND', // not recoverable
'ECONNRESET' // possibly recoverable
```
Expand All @@ -131,7 +131,7 @@ Input:
```
Return:
```
balance.asset
balance.asset
balance.asset_hold
balance.currency
balance.currency_hold
Expand All @@ -141,7 +141,7 @@ Callback:
cb(null, balance)
```
Comment:
Asset vs asset_hold and currency vs currency_hold is kind of mysterious to me.
Asset vs asset_hold and currency vs currency_hold is kind of mysterious to me.
For most exchanges I would just return something similar to available_asset and available_currency
For exchanges that returns some other values, I would do the calculation on the extension layer
and not leave it to engine.js, because available_asset and available_currency are only interesting
Expand All @@ -157,11 +157,11 @@ Called from:
- https://github.com/carlos8f/zenbot/blob/master/commands/sell.js

Input:
```
```
opts.product_id
```
Return:
```
```
{bid: value_of_bid, ask: value_of_ask}
```
Callback:
Expand Down Expand Up @@ -193,7 +193,7 @@ Called from:
- https://github.com/carlos8f/zenbot/blob/master/lib/engine.js

Input:
```
```
opts.price
opts.size
```
Expand All @@ -214,7 +214,7 @@ Called from:
- https://github.com/carlos8f/zenbot/blob/master/lib/engine.js

Input:
```
```
opts.price
opts.size
```
Expand All @@ -234,16 +234,16 @@ getOrder: function (opts, cb)
Called from:
- https://github.com/carlos8f/zenbot/blob/master/lib/engine.js

Input:
Input:
```
opts.order_id
opts.product_id
```
Returns:
```
order.status
```
Expected values in https://github.com/carlos8f/zenbot/blob/master/lib/engine.js:
```
Expected values in https://github.com/carlos8f/zenbot/blob/master/lib/engine.js:
- 'done', 'rejected'
If 'rejected' order.reject_reason = some_reason ('post only')
Is '*post only*' spesific for GDAX?
Expand Down Expand Up @@ -277,3 +277,16 @@ Callback:
```javascript

```

## Extensions

Zenbot offers various extensions, arguably it is what makes zenbot so awesome.

### Extending notifiers

If you wish to add a new notifier, please follow these steps:

- create a your-service-name.js in `/extensions/notifiers/` make sure to use the same function returns as other notifiers
- add `'notifiers.your-service-name': require('./your-service-name')` to `/extensions/notifiers/_codemap.js`
- add config bootstrap to `/conf-sample.js`
- send us a PR with your new service :)
4 changes: 1 addition & 3 deletions extensions/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# zenbot extensions

To support various exchanges or strategies, zenbot "extensions" can be made.
To support various exchanges, strategies or notifiers, zenbot "extensions" can be made.
This is a directory with a `_codemap.js` at root, which zenbot will scan and add to the codebase.
Zenbot comes with `gdax` exchange and `trend_ema_rate` strategy as built-in (example) extensions, but other extensions
can live in external git repos and be cloned, dropped or symlinked here.

You may have to `npm install` in the extension directory, and/or copy and configure `conf-sample.js` to `conf.js` for it to work.
8 changes: 8 additions & 0 deletions extensions/notifiers/_codemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
_ns: 'zenbot',

'notifiers.ifttt': require('./ifttt'),
'notifiers.pushbullet': require('./pushbullet'),
'notifiers.slack': require('./slack'),
'notifiers.xmpp': require('./xmpp')
}
24 changes: 24 additions & 0 deletions extensions/notifiers/ifttt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
var request = require('request')

module.exports = function container (get, set, clear) {
var ifttt = {
pushMessage: function(config, title, message) {
var postData = {'value1': title , 'value2': message }

function callback(error) {
if (error) {
console.log('Error happened: '+ error)
}
}

var options = {
method: 'POST',
url: 'https://maker.ifttt.com/trigger/' + config.eventName + '/with/key/' + config.makerKey,
json: postData
}

request(options, callback)
}
}
return ifttt
}
16 changes: 16 additions & 0 deletions extensions/notifiers/pushbullet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
var pusher = require('pushbullet')

module.exports = function container (get, set, clear) {
var pushbullet = {
pushMessage: function(config, title, message) {
var pb = new pusher(config.key)
pb.note(config.deviceID, title, message, (err) => {
if (err) {
console.log('error: Push message failed, ' + err)
return
}
})
}
}
return pushbullet
}
17 changes: 17 additions & 0 deletions extensions/notifiers/slack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
var IncomingWebhook = require('@slack/client').IncomingWebhook

module.exports = function container (get, set, clear) {
var slack = {
pushMessage: function(config, title, message) {
var slackWebhook = new IncomingWebhook(config.webhook_url || '')

slackWebhook.send(title + ': ' + message, function (err) {
if (err) {
console.error('\nerror: slack webhook')
console.error(err)
}
})
}
}
return slack
}
19 changes: 19 additions & 0 deletions extensions/notifiers/xmpp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
var simplexmpp = require('simple-xmpp')

module.exports = function container (get, set, clear) {
var xmpp = {
pushMessage: function(config, title, message) {
simplexmpp.connect({
jid : config.jid,
password : config.password,
host : config.host,
port : config.port
})

simplexmpp.send(config.to, title + ': ' + message)

simplexmpp.disconnect()
}
}
return xmpp
}
3 changes: 2 additions & 1 deletion lib/_codemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ module.exports = {
'rsi': require('./rsi'),
'sma': require('./sma'),
'srsi': require('./srsi'),
'stddev': require('./stddev')
'stddev': require('./stddev'),
'notify': require('./notify')
}
16 changes: 6 additions & 10 deletions lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var nice_errors = new RegExp(/(slippage protection|loss protection)/)

module.exports = function container (get, set, clear) {
var c = get('conf')
var notify = get('lib.notify')
return function (s) {
var so = s.options
s.selector = get('lib.normalize-selector')(so.selector)
Expand Down Expand Up @@ -182,9 +183,9 @@ module.exports = function container (get, set, clear) {
if (err) return cb(err)
s.start_price = n(quote.ask).value()
s.start_capital = n(s.balance.currency).add(n(s.balance.asset).multiply(quote.ask)).value()
if (c.xmppon) c.xmpp.send(c.xmppto,'Balance: ' + s.start_capital + '\n') // xmpp

notify.pushMessage('Balance', 'sync balance ' + s.start_capital + '\n')

cb()
})
}
Expand Down Expand Up @@ -434,10 +435,7 @@ module.exports = function container (get, set, clear) {
}, c.wait_for_settlement)
}
else {
//console.log('\nplacing buy order at ' + fc(price) + ', ' + fc(quote.bid - Number(price)) + ' under best bid\n')

if (c.xmppon) c.xmpp.send(c.xmppto,'placing buy order at ' + fc(price) + ', ' + fc(quote.bid - Number(price)) + ' under best bid\n')

notify.pushMessage('Buy', 'placing buy order at ' + fc(price) + ', ' + fc(quote.bid - Number(price)) + ' under best bid\n')
doOrder()
}
}
Expand Down Expand Up @@ -478,9 +476,7 @@ module.exports = function container (get, set, clear) {
}, c.wait_for_settlement)
}
else {
//console.log('\nplacing sell order at ' + fc(price) + ', ' + fc(Number(price) - quote.bid) + ' over best ask\n')
if (c.xmppon) c.xmpp.send(c.xmppto,'placing sell order at ' + fc(price) + ', ' + fc(Number(price) - quote.bid) + ' over best ask\n')

notify.pushMessage('Sell', 'placing sell order at ' + fc(price) + ', ' + fc(Number(price) - quote.bid) + ' over best ask\n')
doOrder()
}
}
Expand Down

0 comments on commit af42c42

Please sign in to comment.