diff --git a/extensions/strategies/sar/_codemap.js b/extensions/strategies/sar/_codemap.js new file mode 100644 index 0000000000..cf2b648217 --- /dev/null +++ b/extensions/strategies/sar/_codemap.js @@ -0,0 +1,6 @@ +module.exports = { + _ns: 'zenbot', + + 'strategies.sar': require('./strategy'), + 'strategies.list[]': '#strategies.sar' +} \ No newline at end of file diff --git a/extensions/strategies/sar/strategy.js b/extensions/strategies/sar/strategy.js new file mode 100644 index 0000000000..a30e413e61 --- /dev/null +++ b/extensions/strategies/sar/strategy.js @@ -0,0 +1,86 @@ +var z = require('zero-fill') + , n = require('numbro') + +module.exports = function container (get, set, clear) { + return { + name: 'sar', + description: 'Parabolic SAR', + + getOptions: function () { + this.option('period', 'period length', String, '1m') + this.option('min_periods', 'min. number of history periods', Number, 52) + this.option('sar_af', 'acceleration factor for parabolic SAR', Number, 0.025) + this.option('sar_max_af', 'max acceleration factor for parabolic SAR', Number, 0.55) + }, + + calculate: function (s) { + if (s.lookback.length >= s.options.min_periods) { + if (!s.trend) { + if (s.period.high > s.lookback[s.lookback.length - 1].high) { + // start with uptrend + s.trend = 'up' + s.sar = Math.min(s.lookback[1].low, s.lookback[0].low) + s.sar_ep = s.period.high + s.sar_af = s.options.sar_af + for (var idx = 0; idx < s.lookback.length; idx++) { + s.sar_ep = Math.max(s.sar_ep, s.lookback[idx].high) + } + } + else { + s.trend = 'down' + s.sar = Math.max(s.lookback[1].high, s.lookback[0].high) + s.sar_ep = s.period.low + s.sar_af = s.options.sar_af + for (var idx = 0; idx < s.lookback.length; idx++) { + s.sar_ep = Math.min(s.sar_ep, s.lookback[idx].low) + } + } + } + } + }, + + onPeriod: function (s, cb) { + if (typeof s.sar === 'number') { + if (s.trend === 'up') { + s.sar = Math.min(s.lookback[1].low, s.lookback[0].low, s.sar + (s.sar_af * (s.sar_ep - s.sar))) + } + else { + s.sar = Math.max(s.lookback[1].high, s.lookback[0].high, s.sar - (s.sar_af * (s.sar - s.sar_ep))) + } + if (s.trend === 'down') { + if (s.period.high >= s.sar) { + s.trend = 'up' + s.signal = 'buy' + s.sar_ep = s.period.low + s.sar_af = s.options.sar_af + } + else if (s.period.low < s.sar_ep && s.sar_af < s.options.sar_max_af) { + s.sar_ep = s.period.low + s.sar_af += s.options.sar_af + } + } + else if (s.trend === 'up') { + if (s.period.low <= s.sar) { + s.trend = 'down' + s.signal = 'sell' + s.sar_ep = s.period.high + s.sar_af = s.options.sar_af + } + else if (s.period.high > s.sar_ep && s.sar_af < s.options.sar_max_af) { + s.sar_ep = s.period.high + s.sar_af += s.options.sar_af + } + } + } + cb() + }, + + onReport: function (s) { + var cols = [] + if (typeof s.sar === 'number') { + cols.push(z(8, n(s.sar).subtract(s.period.close).divide(s.period.close).format('0.00%'), ' ').grey) + } + return cols + } + } +}