-
Notifications
You must be signed in to change notification settings - Fork 2k
Strategies: TA (ema+macd) and Trust/Distrust #285
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
FROM node:latest | ||
FROM node:boron | ||
|
||
ADD . /app | ||
WORKDIR /app | ||
|
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' | ||
} |
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 | ||
} | ||
} | ||
} |
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' | ||
} |
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 | ||
} | ||
} | ||
} |
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' | ||
} |
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') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're missing a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like this 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 | ||
} | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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.