Skip to content

Commit

Permalink
Strategies: TA (ema+macd) and Trust/Distrust (DeviaVir#285)
Browse files Browse the repository at this point in the history
node-gyp and node v8.x are not friends, stick to boron for now

Rename talib's MACD (DeviaVir#2)

* Rename talib's MACD so it doesn't overlap with original macd
  • Loading branch information
DeviaVir committed Jul 28, 2017
1 parent e45c277 commit 52f46f2
Show file tree
Hide file tree
Showing 13 changed files with 618 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:latest
FROM node:boron

RUN mkdir -p /app
WORKDIR /app
Expand Down
6 changes: 6 additions & 0 deletions extensions/strategies/ta_ema/_codemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
_ns: 'zenbot',

'strategies.ta_ema': require('./strategy'),
'strategies.list[]': '#strategies.ta_ema'
}
96 changes: 96 additions & 0 deletions extensions/strategies/ta_ema/strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
var z = require('zero-fill')
, n = require('numbro')

module.exports = function container (get, set, clear) {
return {
name: 'ta_ema',
description: 'Buy when (EMA - last(EMA) > 0) and sell when (EMA - last(EMA) < 0). Optional buy on low RSI.',

getOptions: function () {
this.option('period', 'period length', String, '10m')
this.option('min_periods', 'min. number of history periods', Number, 52)
this.option('trend_ema', 'number of periods for trend EMA', Number, 20)
this.option('neutral_rate', 'avoid trades if abs(trend_ema) under this float (0 to disable, "auto" for a variable filter)', Number, 0.06)
this.option('oversold_rsi_periods', 'number of periods for oversold RSI', Number, 20)
this.option('oversold_rsi', 'buy when RSI reaches this value', Number, 30)
},

calculate: function (s) {
get('lib.ta_ema')(s, 'trend_ema', s.options.trend_ema)
if (s.options.oversold_rsi) {
// sync RSI display with oversold RSI periods
s.options.rsi_periods = s.options.oversold_rsi_periods
get('lib.rsi')(s, 'oversold_rsi', s.options.oversold_rsi_periods)
if (!s.in_preroll && s.period.oversold_rsi <= s.options.oversold_rsi && !s.oversold && !s.cancel_down) {
s.oversold = true
if (s.options.mode !== 'sim' || s.options.verbose) console.log(('\noversold at ' + s.period.oversold_rsi + ' RSI, preparing to buy\n').cyan)
}
}
if (s.period.trend_ema && s.lookback[0] && s.lookback[0].trend_ema) {
s.period.trend_ema_rate = (s.period.trend_ema - s.lookback[0].trend_ema) / s.lookback[0].trend_ema * 100
}
if (s.options.neutral_rate === 'auto') {
get('lib.stddev')(s, 'trend_ema_stddev', Math.floor(s.options.trend_ema / 2), 'trend_ema_rate')
}
else {
s.period.trend_ema_stddev = s.options.neutral_rate
}
},

onPeriod: function (s, cb) {
if (!s.in_preroll && typeof s.period.oversold_rsi === 'number') {
if (s.oversold) {
s.oversold = false
s.trend = 'oversold'
s.signal = 'buy'
s.cancel_down = true
return cb()
}
}
if (typeof s.period.trend_ema_stddev === 'number') {
if (s.period.trend_ema_rate > s.period.trend_ema_stddev) {
if (s.trend !== 'up') {
s.acted_on_trend = false
}
s.trend = 'up'
s.signal = !s.acted_on_trend ? 'buy' : null
s.cancel_down = false
}
else if (!s.cancel_down && s.period.trend_ema_rate < (s.period.trend_ema_stddev * -1)) {
if (s.trend !== 'down') {
s.acted_on_trend = false
}
s.trend = 'down'
s.signal = !s.acted_on_trend ? 'sell' : null
}
}
cb()
},

onReport: function (s) {
var cols = []
if (typeof s.period.trend_ema_stddev === 'number') {
var color = 'grey'
if (s.period.trend_ema_rate > s.period.trend_ema_stddev) {
color = 'green'
}
else if (s.period.trend_ema_rate < (s.period.trend_ema_stddev * -1)) {
color = 'red'
}
cols.push(z(8, n(s.period.trend_ema_rate).format('0.0000'), ' ')[color])
if (s.period.trend_ema_stddev) {
cols.push(z(8, n(s.period.trend_ema_stddev).format('0.0000'), ' ').grey)
}
}
else {
if (s.period.trend_ema_stddev) {
cols.push(' ')
}
else {
cols.push(' ')
}
}
return cols
}
}
}
6 changes: 6 additions & 0 deletions extensions/strategies/ta_macd/_codemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
_ns: 'zenbot',

'strategies.ta_macd': require('./strategy'),
'strategies.list[]': '#strategies.ta_macd'
}
86 changes: 86 additions & 0 deletions extensions/strategies/ta_macd/strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
var z = require('zero-fill')
, n = require('numbro')

module.exports = function container (get, set, clear) {
return {
name: 'ta_macd',
description: 'Buy when (MACD - Signal > 0) and sell when (MACD - Signal < 0).',

getOptions: function () {
this.option('period', 'period length', String, '1h')
this.option('min_periods', 'min. number of history periods', Number, 52)
this.option('ema_short_period', 'number of periods for the shorter EMA', Number, 12)
this.option('ema_long_period', 'number of periods for the longer EMA', Number, 26)
this.option('signal_period', 'number of periods for the signal EMA', Number, 9)
this.option('up_trend_threshold', 'threshold to trigger a buy signal', Number, 0)
this.option('down_trend_threshold', 'threshold to trigger a sold signal', Number, 0)
this.option('overbought_rsi_periods', 'number of periods for overbought RSI', Number, 25)
this.option('overbought_rsi', 'sold when RSI exceeds this value', Number, 70)
},

calculate: function (s) {
if (s.options.overbought_rsi) {
// sync RSI display with overbought RSI periods
s.options.rsi_periods = s.options.overbought_rsi_periods
get('lib.rsi')(s, 'overbought_rsi', s.options.overbought_rsi_periods)
if (!s.in_preroll && s.period.overbought_rsi >= s.options.overbought_rsi && !s.overbought) {
s.overbought = true
if (s.options.mode === 'sim' && s.options.verbose) console.log(('\noverbought at ' + s.period.overbought_rsi + ' RSI, preparing to sold\n').cyan)
}
}

// compture MACD
/*get('lib.ema')(s, 'ema_short', s.options.ema_short_period)
get('lib.ema')(s, 'ema_long', s.options.ema_long_period)
if (s.period.ema_short && s.period.ema_long) {
s.period.macd = (s.period.ema_short - s.period.ema_long)
get('lib.ema')(s, 'signal', s.options.signal_period, 'macd')
if (s.period.signal) {
s.period.macd_histogram = s.period.macd - s.period.signal
}
}*/
get('lib.ta_macd')(s,'macd','macd_histogram','macd_signal',s.options.ema_long_period,s.options.ema_short_period,s.options.signal_period)
},

onPeriod: function (s, cb) {
if (!s.in_preroll && typeof s.period.overbought_rsi === 'number') {
if (s.overbought) {
s.overbought = false
s.trend = 'overbought'
s.signal = 'sold'
return cb()
}
}

if (typeof s.period.macd_histogram === 'number' && typeof s.lookback[0].macd_histogram === 'number') {
if ((s.period.macd_histogram - s.options.up_trend_threshold) > 0 && (s.lookback[0].macd_histogram - s.options.up_trend_threshold) <= 0) {
s.signal = 'buy';
} else if ((s.period.macd_histogram + s.options.down_trend_threshold) < 0 && (s.lookback[0].macd_histogram + s.options.down_trend_threshold) >= 0) {
s.signal = 'sell';
} else {
s.signal = null; // hold
}
}
cb()
},

onReport: function (s) {
var cols = []
if (typeof s.period.macd_histogram === 'number') {
var color = 'grey'
if (s.period.macd_histogram > 0) {
color = 'green'
}
else if (s.period.macd_histogram < 0) {
color = 'red'
}
cols.push(z(8, n(s.period.macd_histogram).format('+00.0000'), ' ')[color])
cols.push(z(8, n(s.period.overbought_rsi).format('00'), ' ').cyan)
}
else {
cols.push(' ')
}
return cols
}
}
}
6 changes: 6 additions & 0 deletions extensions/strategies/trust_distrust/_codemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
_ns: 'zenbot',

'strategies.trust_distrust': require('./strategy'),
'strategies.list[]': '#strategies.trust_distrust'
}
136 changes: 136 additions & 0 deletions extensions/strategies/trust_distrust/strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
var z = require('zero-fill')
, n = require('numbro')

module.exports = function container (get, set, clear) {
return {
name: 'trust_distrust',
description: 'Sell when price higher than $sell_min% and highest point - $sell_threshold% is reached. Buy when lowest price point + $buy_threshold% reached.',

getOptions: function () {
this.option('period', 'period length', String, '30m')
this.option('min_periods', 'min. number of history periods', Number, 52)
this.option('sell_threshold', 'sell when the top drops at least below this percentage', Number, 2)
this.option('sell_threshold_max', 'sell when the top drops lower than this max, regardless of sell_min (panic sell, 0 to disable)', Number, 0)
this.option('sell_min', 'do not act on anything unless the price is this percentage above the original price', Number, 1)
this.option('buy_threshold', 'buy when the bottom increased at least above this percentage', Number, 2)
this.option('buy_threshold_max', 'wait for multiple buy signals before buying (kill whipsaw, 0 to disable)', Number, 0)
this.option('greed', 'sell if we reach this much profit (0 to be greedy and either win or lose)', Number, 0)
},

calculate: function (s) {
if (typeof s.trust_distrust_start_greed === 'undefined') {
s.trust_distrust_start_greed = s.period.high
}
if (typeof s.trust_distrust_start === 'undefined') {
s.trust_distrust_start = s.period.high
}
if (typeof s.trust_distrust_highest === 'undefined') {
s.trust_distrust_highest = s.period.high
}
if (typeof s.trust_distrust_lowest === 'undefined') {
s.trust_distrust_lowest = s.period.high
}
if (typeof s.trust_distrust_last_action === 'undefined') {
s.trust_distrust_last_action = null
}
if (typeof s.trust_distrust_buy_threshold_max === 'undefined') {
s.trust_distrust_buy_threshold_max = 0
}

// when our current price is higher than what we recorded, overwrite
if (s.period.high > s.trust_distrust_highest) {
s.trust_distrust_highest = s.period.high
}

// when our current price is lower than what we recorded, overwrite
if (s.trust_distrust_lowest > s.period.high) {
s.trust_distrust_lowest = s.period.high
}
},

onPeriod: function (s, cb) {
if (s.greedy) {
s.signal = s.trust_distrust_last_action
return cb()
}

// sell logic
if (s.trust_distrust_last_action !== 'sell') {
if ( s.period.high > (s.trust_distrust_start + (s.trust_distrust_start / 100 * s.options.sell_min))) { // we are above minimum we want to sell for, or going so low we should "panic sell"
if (s.period.high < (s.trust_distrust_highest - (s.trust_distrust_highest / 100 * s.options.sell_threshold))) { // we lost sell_threshold from highest point
s.signal = 'sell'

s.trust_distrust_last_action = 'sell'
s.trust_distrust_start = s.period.high
s.trust_distrust_highest = s.period.high
s.trust_distrust_lowest = s.period.high

return cb()
}
}

if (s.options.sell_threshold_max > 0 && s.period.high < (s.trust_distrust_highest - (s.trust_distrust_highest / 100 * s.options.sell_threshold_max))) { // we panic sell
s.signal = 'sell'

s.trust_distrust_last_action = 'sell'
s.trust_distrust_start = s.period.high
s.trust_distrust_highest = s.period.high
s.trust_distrust_lowest = s.period.high

return cb()
}
}

if (s.options.greed > 0 && s.period.high > (s.trust_distrust_start_greed + (s.trust_distrust_start_greed / 100 * s.options.greed))) { // we are not greedy, sell if this profit is reached
s.signal = 'sell'

s.trust_distrust_last_action = 'sell'
s.trust_distrust_start = s.period.high
s.trust_distrust_highest = s.period.high
s.trust_distrust_lowest = s.period.high
s.greedy = true

return cb()
}

// buy logic
if (s.trust_distrust_last_action !== 'buy') {
if(s.period.high < s.trust_distrust_start && s.period.high > (s.trust_distrust_lowest + (s.trust_distrust_lowest / 100 * s.options.buy_threshold))) { // we grew above buy threshold from lowest point
if (s.options.buy_threshold_max > 0 && s.trust_distrust_buy_threshold_max < s.options.buy_threshold_max) {
s.trust_distrust_buy_threshold_max++
return cb()
}
s.trust_distrust_buy_threshold_max = 0
s.signal = 'buy'

s.trust_distrust_last_action = 'buy'
s.trust_distrust_start = s.period.high
s.trust_distrust_highest = s.period.high
s.trust_distrust_lowest = s.period.high

return cb()
}
}

// repeat last signal
if (s.signal === null) {
s.signal = s.trust_distrust_last_action
}
return cb()
},

onReport: function (s) {
var cols = []
var color = 'grey'
if (s.period.high > s.trust_distrust_start) {
color = 'green'
}
else if (s.period.high < s.trust_distrust_lowest) {
color = 'red'
}
cols.push(z(8, n(s.period.high).format('0.0000'), ' ')[color])
cols.push(z(8, n(s.trust_distrust_start).format('0.0000'), ' ').grey)
return cols
}
}
}
2 changes: 2 additions & 0 deletions lib/_codemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module.exports = {
_ns: 'zenbot',
_folder: 'lib',

'ta_ema': require('./ta_ema'),
'ta_macd': require('./ta_macd'),
'cci': require('./cci'),
'ema': require('./ema'),
'engine': require('./engine'),
Expand Down
Loading

0 comments on commit 52f46f2

Please sign in to comment.