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

Strategies: TA (ema+macd) and Trust/Distrust #285

Merged
merged 2 commits into from
Jul 28, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

ADD . /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')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you comment what exactly this strategy does at the top? What you wrote in the description of this pr would be really awesome.

, 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')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're missing a min_periods value here, which causes a NaN when trying to calculate the number of days to backfill here.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this min_periods shouldn't be part of the strategy, as it's needed in trade.js... but I added it, as I see that all other strategies, except for SAR, have also added it (but don't actually do anything with it).

Good catch!

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
}
},
Copy link
Owner Author

@DeviaVir DeviaVir Jun 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: might want to consider calculating EMA/MACD (both?) to check if we are in an up or down trend, and advise buy/sell accordingly.

currently we sell when we drop a certain percentage under the top, but if EMA/MACD says we are in an upward trend, it doesn't make sense to sell.
currently we buy when we grow a certain percentage above the deepest point, but if EMA/MACD says we are in a downward trend, it doesn't make sense to buy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @DeviaVir, did you manage to implement this idea of yours?


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
}
}
}
4 changes: 3 additions & 1 deletion lib/_codemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ module.exports = {
_ns: 'zenbot',
_folder: 'lib',

'ta_ema': require('./ta_ema'),
'ta_macd': require('./ta_macd'),
'ema': require('./ema'),
'engine': require('./engine'),
'normalize-selector': require('./normalize-selector'),
'rsi': require('./rsi'),
'stddev': require('./stddev')
}
}
Loading