diff --git a/extensions/strategies/ta_srsi_bollinger/strategy.js b/extensions/strategies/ta_srsi_bollinger/strategy.js new file mode 100644 index 0000000000..cf46a56e29 --- /dev/null +++ b/extensions/strategies/ta_srsi_bollinger/strategy.js @@ -0,0 +1,161 @@ +let z = require('zero-fill') + , n = require('numbro') + , ta_srsi = require('../../../lib/ta_stochrsi') + , ta_bollinger = require('../../../lib/ta_bollinger') + , Phenotypes = require('../../../lib/phenotype') +module.exports = { + name: 'srsi_bollinger', + description: 'Stochastic RSI BollingerBand Strategy', + + getOptions: function () { + this.option('period', 'period length, same as --period_length', String, '5m') + this.option('period_length', 'period length, same as --period', String, '5m') + this.option('min_periods', 'min. number of history periods', Number, 200) + this.option('rsi_periods', 'number of RSI periods', 14) + this.option('srsi_periods', 'number of Stochastic RSI periods',Number, 9) + this.option('srsi_k', '%D line', Number, 3) + this.option('srsi_d', '%D line', Number, 3) + this.option('srsi_k_sell', 'K must be above this before selling', Number, 60) + this.option('srsi_k_buy', 'K must be below this before buying', Number, 30) + this.option('srsi_dType','D type mode : SMA,EMA,WMA,DEMA,TEMA,TRIMA,KAMA,MAMA,T3', String, 'SMA'), + + //'SMA','EMA','WMA','DEMA','TEMA','TRIMA','KAMA','MAMA','T3' + + + this.option('bollinger_size', 'period size', Number, 14) + this.option('bollinger_updev', 'Upper Bollinger Time Divisor', Number, 2) + this.option('bollinger_dndev', 'Lower Bollinger Time Divisor', Number, 2) + this.option('bollinger_dType','mode: : SMA,EMA,WMA,DEMA,TEMA,TRIMA,KAMA,MAMA,T3', String, 'SMA') + this.option('bollinger_upper_bound_pct', 'pct the current price should be near the bollinger upper bound before we sell', Number, 1) + this.option('bollinger_lower_bound_pct', 'pct the current price should be near the bollinger lower bound before we buy', Number, 1) + + + }, + + + calculate: function (s) { + + if (s.in_preroll) return + + }, + + onPeriod: function (s, cb) { + //make sure we have all values + if (s.in_preroll) return cb() + + ta_bollinger(s,'tabollinger',s.options.bollinger_size, s.options.bollinger_updev, s.options.bollinger_dndev, s.options.bollinger_dType). + then(function(inbol){ + ta_srsi(s, 'srsi', s.options.srsi_periods, s.options.srsi_k, s.options.srsi_d, s.options.srsi_dType). + then(function(inres) { + + if (!inres) return cb() + var divergent = inres.outFastK[inres.outFastK.length-1] - inres.outFastD[inres.outFastD.length-1] + s.period.srsi_D = inres.outFastD[inres.outFastD.length-1] + s.period.srsi_K = inres.outFastK[inres.outFastK.length-1] + var last_divergent = inres.outFastK[inres.outFastK.length-2] - inres.outFastD[inres.outFastD.length-2] + var _switch = 0//s.lookback[0]._switch + var nextdivergent = (( divergent + last_divergent ) /2) + (divergent - last_divergent) + if ((last_divergent <= 0 && (divergent > 0)) ) _switch = 1 // price rising + if ((last_divergent >= 0 && (divergent < 0)) ) _switch = -1 // price falling + + s.period.divergent = divergent + s.period._switch = _switch + + + + let upperBound = inbol.outRealUpperBand[inbol.outRealUpperBand.length-1] + let lowerBound = inbol.outRealLowerBand[inbol.outRealLowerBand.length-1] + let midBound =inbol.outRealMiddleBand[inbol.outRealMiddleBand.length-1] + if (!s.period.bollinger) s.period.bollinger = {} + + s.period.bollinger.upperBound = upperBound + s.period.bollinger.lowerBound = lowerBound + s.period.bollinger.midBound = midBound + + + // K is fast moving + + s.signal = null + if (_switch != 0 ) + { + if (s.period.close > ((upperBound / 100) * (100 - s.options.bollinger_upper_bound_pct)) && nextdivergent < divergent && _switch == -1 && s.period.srsi_K > s.options.srsi_k_sell) + { + s.signal = 'sell' + } + else + if (s.period.close < ((lowerBound / 100) * (100 + s.options.bollinger_lower_bound_pct)) && nextdivergent >= divergent && _switch == 1 && s.period.srsi_K < s.options.srsi_k_buy) + { + s.signal = 'buy' + } + + } + + cb() + }).catch(function(){ + cb()}) + + }).catch(function(){ + cb()}) + + }, + + onReport: function (s) { + var cols = [] + if (s.period.bollinger) { + if (s.period.bollinger.upperBound && s.period.bollinger.lowerBound) { + let upperBound = s.period.bollinger.upperBound + let lowerBound = s.period.bollinger.lowerBound + var color = 'grey' + if (s.period.close > (upperBound / 100) * (100 - s.options.bollinger_upper_bound_pct)) { + color = 'green' + } else if (s.period.close < (lowerBound / 100) * (100 + s.options.bollinger_lower_bound_pct)) { + color = 'red' + } + cols.push(z(8, n(s.period.close).format('+00.0000'), ' ')[color]) + cols.push(z(8, n(lowerBound).format('0.000000').substring(0,7), ' ').cyan) + cols.push(z(8, n(upperBound).format('0.000000').substring(0,7), ' ').cyan) + cols.push(z(8, n(s.period.srsi_D).format('0.0000').substring(0,7), ' ').cyan) + cols.push(z(8, n(s.period.srsi_K).format('0.0000').substring(0,7), ' ').cyan) + cols.push(z(5, n(s.period.divergent).format('0').substring(0,7), ' ').cyan) + cols.push(z(2, n(s.period._switch).format('0').substring(0,2), ' ').cyan) + } + } + else { + cols.push(' ') + } + return cols + }, + + phenotypes: + { + // -- common + period_length: Phenotypes.ListOption(['1m', '2m', '3m', '4m', '5m', '10m','15m']),//, '10m','15m','30m','45m','60m' + min_periods: Phenotypes.Range(52, 150), + markdown_buy_pct: Phenotypes.RangeFactor(-1.0, 1.0, 0.1), + markup_sell_pct: Phenotypes.RangeFactor(-1.0, 1.0, 0.1), + order_type: Phenotypes.ListOption(['maker', 'taker']), + sell_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.1), + buy_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.1), + profit_stop_enable_pct: Phenotypes.RangeFactor(0.0, 5.0, 0.1), + profit_stop_pct: Phenotypes.RangeFactor(0.0, 50.0, 0.1), + + // -- strategy + rsi_periods: Phenotypes.Range(10, 20), + srsi_periods: Phenotypes.Range(5, 30), + srsi_k: Phenotypes.Range(1, 30), + srsi_d: Phenotypes.Range(1, 30), + srsi_k_sell: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + srsi_k_buy: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + srsi_dType: Phenotypes.ListOption(['SMA','EMA','WMA','DEMA','TEMA','TRIMA','KAMA','MAMA','T3']), + + + + bollinger_size: Phenotypes.RangeFactor(10, 25, 1), + bollinger_updev: Phenotypes.RangeFactor(1, 3.0, 0.1), + bollinger_dndev: Phenotypes.RangeFactor(1, 3.0, 0.1), + bollinger_dType: Phenotypes.ListOption(['SMA','EMA','WMA','DEMA','TEMA','TRIMA','KAMA','MAMA','T3']), + bollinger_upper_bound_pct: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + bollinger_lower_bound_pct: Phenotypes.RangeFactor(0.0, 100.0, 1.0) + + } +} diff --git a/extensions/strategies/ta_stoch_bollinger/strategy.js b/extensions/strategies/ta_stoch_bollinger/strategy.js new file mode 100644 index 0000000000..3e8e83dd1f --- /dev/null +++ b/extensions/strategies/ta_stoch_bollinger/strategy.js @@ -0,0 +1,150 @@ +let z = require('zero-fill') + , n = require('numbro') + , ta_stoch = require('../../../lib/ta_stoch') + , ta_bollinger = require('../../../lib/ta_bollinger') + , Phenotypes = require('../../../lib/phenotype') +module.exports = { + name: 'ta_stoch_bollinger', + description: 'Stochastic BollingerBand Strategy', + + getOptions: function () { + this.option('period', 'period length, same as --period_length', String, '5m') + this.option('period_length', 'period length, same as --period', String, '5m') + this.option('min_periods', 'min. number of history periods', Number, 200) + this.option('rsi_periods', 'Time period for building the Fast-K line', Number, 14) + this.option('stoch_periods', 'Time period for building the Fast-K line', Number, 5) + this.option('stoch_k', 'Smoothing for making the Slow-K line. Usually set to 3', Number, 3) + this.option('stoch_k_ma_type','Type of Moving Average for Slow-K : SMA,EMA,WMA,DEMA,TEMA,TRIMA,KAMA,MAMA,T3', String, 'SMA'), + this.option('stoch_d', 'Smoothing for making the Slow-D line', Number, 3) + this.option('stoch_d_ma_type','Type of Moving Average for Slow-D : SMA,EMA,WMA,DEMA,TEMA,TRIMA,KAMA,MAMA,T3', String, 'SMA'), + this.option('stoch_k_sell', 'K must be above this before selling', Number, 70) + this.option('stoch_k_buy', 'K must be below this before buying', Number, 30) + + this.option('bollinger_size', 'period size', Number, 14) + this.option('bollinger_updev', '', Number, 2) + this.option('bollinger_dndev', '', Number, 2) + this.option('bollinger_dType','mode: : SMA,EMA,WMA,DEMA,TEMA,TRIMA,KAMA,MAMA,T3', String, 'SMA') + this.option('bollinger_upper_bound_pct', 'pct the current price should be near the bollinger upper bound before we sell', Number,0) + this.option('bollinger_lower_bound_pct', 'pct the current price should be near the bollinger lower bound before we buy', Number, 0) + }, + + + calculate: function (s) { + if (s.in_preroll) return + }, + + onPeriod: function (s, cb) { + //make sure we have all values + if (s.in_preroll) return cb() + ta_bollinger(s,'tabollinger',s.options.bollinger_size, s.options.bollinger_updev, s.options.bollinger_dndev, s.options.bollinger_dType). + then(function(inbol){ + ta_stoch(s, 'stoch', s.options.stoch_periods, s.options.stoch_k, s.options.stoch_k_ma_type, s.options.stoch_d, s.options.stoch_d_ma_type). + then(function(inres) { + + if (!inres) return cb() + var divergent = inres.k[inres.k.length-1] - inres.d[inres.k.length-1] + s.period.stoch_D = inres.d[inres.d.length-1] + s.period.stoch_K = inres.k[inres.k.length-1] + var last_divergent = inres.k[inres.k.length-2] - inres.d[inres.d.length-2] + var _switch = 0 + var nextdivergent = (( divergent + last_divergent ) /2) + (divergent - last_divergent) + if ((last_divergent <= 0 && (divergent > 0)) ) _switch = 1 // price rising + if ((last_divergent >= 0 && (divergent < 0)) ) _switch = -1 // price falling + + s.period.divergent = divergent + s.period._switch = _switch + + let upperBound = inbol.outRealUpperBand[inbol.outRealUpperBand.length-1] + let lowerBound = inbol.outRealLowerBand[inbol.outRealLowerBand.length-1] + let midBound =inbol.outRealMiddleBand[inbol.outRealMiddleBand.length-1] + if (!s.period.bollinger) s.period.bollinger = {} + + s.period.bollinger.upperBound = upperBound + s.period.bollinger.lowerBound = lowerBound + s.period.bollinger.midBound = midBound + + + // K is fast moving + + s.signal = null + if (_switch != 0 ) + { + if (s.period.close >= midBound && s.period.close >= ((upperBound / 100) * (100 + s.options.bollinger_upper_bound_pct)) && nextdivergent < divergent && _switch == -1 && s.period.stoch_K > s.options.stoch_k_sell) + { + s.signal = 'sell' + } + else + if (s.period.close < (lowerBound / 100) * (100 + s.options.bollinger_lower_bound_pct) && nextdivergent >= divergent && _switch == 1 && s.period.stoch_K < s.options.stoch_k_buy) + { + s.signal = 'buy' + } + } + + cb() + }).catch(function(){ + cb()}) + + }).catch(function(){ + cb()}) + }, + + onReport: function (s) { + var cols = [] + if (s.period.bollinger) { + if (s.period.bollinger.upperBound && s.period.bollinger.lowerBound) { + let upperBound = s.period.bollinger.upperBound + let lowerBound = s.period.bollinger.lowerBound + var color = 'grey' + if (s.period.close > (upperBound / 100) * ( 100 + s.options.bollinger_upper_bound_pct)) { color = 'green' + } + if (s.period.close < (lowerBound / 100) * ( 100 - s.options.bollinger_lower_bound_pct)) { color = 'red' + } + cols.push(z(8, n(s.period.close).format('+00.0000'), ' ')[color]) + cols.push(z(8, n(lowerBound).format('0.000000').substring(0,7), ' ').cyan) + cols.push(z(8, n(upperBound).format('0.000000').substring(0,7), ' ').cyan) + cols.push(z(8, n(s.period.stoch_D).format('0.0000').substring(0,7), ' ').cyan) + cols.push(z(8, n(s.period.stoch_K).format('0.0000').substring(0,7), ' ').cyan) + cols.push(z(5, n(s.period.divergent).format('0').substring(0,7), ' ').cyan) + cols.push(z(2, n(s.period._switch).format('0').substring(0,2), ' ').cyan) + } + } + else { + cols.push(' ') + } + return cols + }, + + phenotypes: + { + // -- common + period_length: Phenotypes.ListOption(['1m', '2m', '3m', '4m', '5m', '10m','15m']),//, '10m','15m','30m','45m','60m' + min_periods: Phenotypes.Range(52, 150), + markdown_buy_pct: Phenotypes.RangeFactor(-1.0, 1.0, 0.1), + markup_sell_pct: Phenotypes.RangeFactor(-1.0, 1.0, 0.1), + order_type: Phenotypes.ListOption(['maker', 'taker']), + sell_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.1), + buy_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.1), + profit_stop_enable_pct: Phenotypes.RangeFactor(0.0, 5.0, 0.1), + profit_stop_pct: Phenotypes.RangeFactor(0.0, 50.0, 0.1), + + // -- strategy + rsi_periods: Phenotypes.Range(10, 30), + stoch_periods: Phenotypes.Range(5, 30), + stoch_k: Phenotypes.Range(1, 10), + stoch_k_ma_type: Phenotypes.ListOption(['SMA','EMA','WMA','DEMA','TEMA','TRIMA','KAMA','MAMA','T3']), + stoch_d: Phenotypes.Range(1, 10), + stoch_k_sell: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + stoch_k_buy: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + stoch_d_ma_type: Phenotypes.ListOption(['SMA','EMA','WMA','DEMA','TEMA','TRIMA','KAMA','MAMA','T3']), + + + + bollinger_size: Phenotypes.RangeFactor(10, 25, 1), + bollinger_updev: Phenotypes.RangeFactor(1, 3.0, 0.1), + bollinger_dndev: Phenotypes.RangeFactor(1, 3.0, 0.1), + bollinger_dType: Phenotypes.ListOption(['SMA','EMA','WMA','DEMA','TEMA','TRIMA','KAMA','MAMA','T3']), + bollinger_upper_bound_pct: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + bollinger_lower_bound_pct: Phenotypes.RangeFactor(0.0, 100.0, 1.0) + + } +} diff --git a/extensions/strategies/ti_bollinger/strategy.js b/extensions/strategies/ti_bollinger/strategy.js new file mode 100644 index 0000000000..666d6983e3 --- /dev/null +++ b/extensions/strategies/ti_bollinger/strategy.js @@ -0,0 +1,97 @@ +var z = require('zero-fill') + , n = require('numbro') + , tulip_bollinger = require('../../../lib/ti_bollinger') + , Phenotypes = require('../../../lib/phenotype') + +module.exports = { + name: 'ti_bollinger', + description: 'Buy when (Signal ≤ Lower Bollinger Band) and sell when (Signal ≥ Upper Bollinger Band).', + + getOptions: function () { + this.option('period', 'period length, same as --period_length', String, '5m') + this.option('period_length', 'period length, same as --period', String, '5m') + this.option('bollinger_size', 'period size', Number, 14) + this.option('bollinger_time', 'times of standard deviation between the upper band and the moving averages', Number, 2) + this.option('bollinger_upper_bound_pct', 'pct the current price should be near the bollinger upper bound before we sell', Number, 0) + this.option('bollinger_lower_bound_pct', 'pct the current price should be near the bollinger lower bound before we buy', Number, 0) + }, + + calculate: function (s) { + if (s.in_preroll) return + }, + + onPeriod: function (s, cb) { + + tulip_bollinger(s,'tulip_bollinger', s.options.bollinger_size, s.options.bollinger_time). + then(function(result) + { + if (!result) cb() + let bollinger = { + LowerBand: result.LowerBand[result.LowerBand.length-1], + MiddleBand: result.MiddleBand[result.MiddleBand.length-1], + UpperBand: result.UpperBand[result.UpperBand.length-1] + } + s.period.report = bollinger + if (bollinger.UpperBand) { + let upperBound = (bollinger.UpperBand / 100) * (100 - s.options.bollinger_upper_bound_pct) + let lowerBound = (bollinger.LowerBand / 100) * (100 + s.options.bollinger_lower_bound_pct) + s.signal = null // hold + if (s.period.close < lowerBound ) { + s.signal = 'buy' + } + if (s.period.close > upperBound ) { + s.signal = 'sell' + } + + + } + cb() + }).catch(function(){ + s.signal = null // hold + cb() + }) + }, + + onReport: function (s) { + var cols = [] + if (s.period.report) { + if (s.period.report.UpperBand && s.period.report.LowerBand) { + let upperBound = s.period.report.UpperBand + let lowerBound = s.period.report.LowerBand + var color = 'grey' + if (s.period.close > (upperBound / 100) * (100 - s.options.bollinger_upper_bound_pct)) { + color = 'green' + } else if (s.period.close < (lowerBound / 100) * (100 + s.options.bollinger_lower_bound_pct)) { + color = 'red' + } + cols.push(z(8, n(s.period.close).format('+00.0000'), ' ')[color]) + cols.push(z(8, n(lowerBound).format('0.000000').substring(0,7), ' ').cyan) + cols.push(z(8, n(upperBound).format('0.000000').substring(0,7), ' ').cyan) + } + } + else { + cols.push(' ') + } + return cols + }, + + phenotypes: { + // -- common + period_length: Phenotypes.RangePeriod(1, 120, 'm'), + markdown_buy_pct: Phenotypes.RangeFactor(-1.0, 5.0, 0.1), + markup_sell_pct: Phenotypes.RangeFactor(-1.0, 5.0, 0.1), + order_type: Phenotypes.ListOption(['maker', 'taker']), + sell_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.01), + buy_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.01), + profit_stop_enable_pct: Phenotypes.RangeFactor(0.0, 5.0,0.1), + profit_stop_pct: Phenotypes.RangeFactor(0.0, 20.0,0.1), + rsi_periods: Phenotypes.Range(6, 16), + + // -- strategy + bollinger_size: Phenotypes.RangeFactor(1, 30, 1), + bollinger_time: Phenotypes.RangeFactor(1.0, 14.0, 0.1), + bollinger_upper_bound_pct: Phenotypes.RangeFactor(0.0, 100.0, 0.1), + bollinger_lower_bound_pct: Phenotypes.RangeFactor(0.0, 100.0, 0.1) + } +} + diff --git a/extensions/strategies/ti_stoch/strategy.js b/extensions/strategies/ti_stoch/strategy.js new file mode 100644 index 0000000000..48f3eb9fc0 --- /dev/null +++ b/extensions/strategies/ti_stoch/strategy.js @@ -0,0 +1,100 @@ +var z = require('zero-fill') + , n = require('numbro') + , tulip_stoch = require('../../../lib/ti_stoch') + , Phenotypes = require('../../../lib/phenotype') + +module.exports = { + name: 'ti_stoch', + description: 'Buy when (Signal ≤ srsi_k_buy) and sell when (Signal ≥ srsi_k_sell). (this should not be used alone. you will lose over time)', + + getOptions: function () { + this.option('period', 'period length, same as --period_length', String, '5m') + this.option('period_length', 'period length, same as --period', String, '5m') + this.option('rsi_periods', 'number of RSI periods', 14) + this.option('stoch_kperiods', 'number of RSI periods', Number, 9) + this.option('stoch_k', '%D line', Number, 3) + this.option('stoch_d', '%D line', Number, 3) + this.option('stoch_k_sell', 'K must be above this before selling', Number, 80) + this.option('stoch_k_buy', 'K must be below this before buying', Number, 10) + }, + + calculate: function (s) { + if (s.in_preroll) return + }, + + onPeriod: function (s, cb) { + if (s.in_preroll) return cb() + tulip_stoch(s,'tulip_stoch', s.options.rsi_periods, s.options.stoch_k, s.options.stoch_d). + then(function(result) + { + if (!result) return cb() + if (result.k.length == 0) return cb() + var divergent = result.k[result.k.length-1] - result.d[result.d.length-1] + s.period.srsi_D = result.d[result.d.length-1] + s.period.srsi_K = result.k[result.k.length-1] + var last_divergent = result.k[result.k.length-2] - result.d[result.d.length-2] + var _switch = 0//s.lookback[0]._switch + var nextdivergent = (( divergent + last_divergent ) /2) + (divergent - last_divergent) + if ((last_divergent <= 0 && (divergent > 0)) ) _switch = 1 // price rising + if ((last_divergent >= 0 && (divergent < 0)) ) _switch = -1 // price falling + + s.period.divergent = divergent + s.period._switch = _switch + + s.signal = null + if (_switch != 0 ) + { + if (_switch == -1 && s.period.srsi_K > s.options.stoch_k_sell) + { + s.signal = 'sell' + } + else + if ( nextdivergent >= divergent && _switch == 1 && s.period.srsi_K < s.options.stoch_k_buy) + { + s.signal = 'buy' + } + + } + + return cb() + }).catch(function(){ + s.signal = null // hold + return cb() + }) + + }, + + onReport: function (s) { + var cols = [] + + cols.push(z(8, n(s.period.close).format('+00.0000'), ' ').cyan) + cols.push(z(8, n( s.period.srsi_D).format('0.000000').substring(0,7), ' ').cyan) + cols.push(z(8, n(s.period.srsi_K).format('0.000000').substring(0,7), ' ').cyan) + cols.push(z(8, n(s.period.divergent).format('0').substring(0,3), ' ').cyan) + cols.push(z(8, n( s.period._switch ).format('0').substring(0,2), ' ').cyan) + + + return cols + }, + + phenotypes: { + // -- common + period_length: Phenotypes.RangePeriod(1, 120, 'm'), + markdown_buy_pct: Phenotypes.RangeFactor(-1.0, 5.0, 0.1), + markup_sell_pct: Phenotypes.RangeFactor(-1.0, 5.0, 0.1), + order_type: Phenotypes.ListOption(['maker', 'taker']), + sell_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.01), + buy_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.01), + profit_stop_enable_pct: Phenotypes.RangeFactor(0.0, 5.0,0.1), + profit_stop_pct: Phenotypes.RangeFactor(0.0, 20.0,0.1), + + // -- strategy + rsi_periods: Phenotypes.Range(10, 30), + stoch_periods: Phenotypes.Range(5, 30), + stoch_k: Phenotypes.Range(1, 10), + stoch_d: Phenotypes.Range(1, 10), + stoch_k_sell: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + stoch_k_buy: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + } +} + diff --git a/extensions/strategies/ti_stoch_bollinger/strategy.js b/extensions/strategies/ti_stoch_bollinger/strategy.js new file mode 100644 index 0000000000..1d35da1577 --- /dev/null +++ b/extensions/strategies/ti_stoch_bollinger/strategy.js @@ -0,0 +1,142 @@ +let z = require('zero-fill') + , n = require('numbro') + , ti_stoch = require('../../../lib/ti_stoch') + , ti_bollinger = require('../../../lib/ti_bollinger') + , Phenotypes = require('../../../lib/phenotype') +module.exports = { + name: 'ti_stoch_bollinger', + description: 'Stochastic BollingerBand Strategy', + + getOptions: function () { + this.option('period', 'period length, same as --period_length', String, '5m') + this.option('period_length', 'period length, same as --period', String, '5m') + this.option('min_periods', 'min. number of history periods', Number, 200) + this.option('rsi_periods', 'number of RSI periods', 14) + this.option('stoch_kperiods', 'number of RSI periods', Number, 9) + this.option('stoch_k', '%D line', Number, 5) + this.option('stoch_d', '%D line', Number, 3) + this.option('stoch_k_sell', 'K must be above this before selling', Number, 70) + this.option('stoch_k_buy', 'K must be below this before buying', Number, 20) + + this.option('bollinger_size', 'period size', Number, 14) + this.option('bollinger_time', 'times of standard deviation between the upper band and the moving averages', Number, 2) + this.option('bollinger_upper_bound_pct', 'pct the current price should be near the bollinger upper bound before we sell', Number, 0) + this.option('bollinger_lower_bound_pct', 'pct the current price should be near the bollinger lower bound before we buy', Number, 0) + }, + + + calculate: function (s) { + if (s.in_preroll) return + }, + + onPeriod: function (s, cb) { + //make sure we have all values + if (s.in_preroll) return cb() + + ti_bollinger(s,'ti_bollinger', s.options.bollinger_size, s.options.bollinger_time). + then(function(inbol){ + ti_stoch(s,'ti_stoch', s.options.stoch_kperiods, s.options.stoch_k, s.options.stoch_d). + then(function(inres) { + + if (!inres) return cb() + if (inres.k.length == 0) return cb() + var divergent = inres.k[inres.k.length-1] - inres.d[inres.d.length-1] + s.period.stoch_D = inres.d[inres.d.length-1] + s.period.stoch_K = inres.k[inres.k.length-1] + var last_divergent = inres.k[inres.k.length-2] - inres.d[inres.d.length-2] + var _switch = 0 + var nextdivergent = (( divergent + last_divergent ) /2) + (divergent - last_divergent) + if ((last_divergent <= 0 && (divergent > 0)) ) _switch = 1 // price rising + if ((last_divergent >= 0 && (divergent < 0)) ) _switch = -1 // price falling + + s.period.divergent = divergent + s.period._switch = _switch + + + let LowerBand= inbol.LowerBand[inbol.LowerBand.length-1] + let MiddleBand= inbol.MiddleBand[inbol.MiddleBand.length-1] + let UpperBand= inbol.UpperBand[inbol.UpperBand.length-1] + let bollinger = { + LowerBand: LowerBand, + MiddleBand: MiddleBand, + UpperBand: UpperBand + } + s.period.report = bollinger + + + // K is fast moving + + s.signal = null + if (_switch != 0 ) + { + if (s.period.close >= MiddleBand && s.period.close >= ((UpperBand / 100) * (100 + s.options.bollinger_upper_bound_pct)) && nextdivergent < divergent && _switch == -1 && s.period.stoch_K > s.options.stoch_k_sell) + { + s.signal = 'sell' + } + else + if (s.period.close < (LowerBand / 100) * (100 + s.options.bollinger_lower_bound_pct) && nextdivergent >= divergent && _switch == 1 && s.period.stoch_K < s.options.stoch_k_buy) + { + s.signal = 'buy' + } + + } + + cb() + }).catch(function(){ + cb()}) + }).catch(function(){ + cb()}) + }, + + onReport: function (s) { + var cols = [] + if (s.period.report) { + + let upperBound = s.period.report.UpperBand + let lowerBound = s.period.report.LowerBand + var color = 'grey' + if (s.period.close > (upperBound / 100) * ( 100 + s.options.bollinger_upper_bound_pct)) { color = 'green' } + if (s.period.close < (lowerBound / 100) * ( 100 - s.options.bollinger_lower_bound_pct)) { color = 'red' } + cols.push(z(8, n(s.period.close).format('+00.0000'), ' ')[color]) + cols.push(z(8, n(lowerBound).format('0.000000').substring(0,7), ' ').cyan) + cols.push(z(8, n(upperBound).format('0.000000').substring(0,7), ' ').cyan) + cols.push(z(8, n(s.period.stoch_D).format('0.0000').substring(0,7), ' ').cyan) + cols.push(z(8, n(s.period.stoch_K).format('0.0000').substring(0,7), ' ').cyan) + cols.push(z(5, n(s.period.divergent).format('0').substring(0,7), ' ').cyan) + cols.push(z(2, n(s.period._switch).format('0').substring(0,2), ' ').cyan) + + } + else { + cols.push(' ') + } + return cols + }, + + phenotypes: + { + // -- common + period_length: Phenotypes.ListOption(['1m', '2m', '3m', '4m', '5m', '10m','15m']),//, '10m','15m','30m','45m','60m' + min_periods: Phenotypes.Range(52, 150), + markdown_buy_pct: Phenotypes.RangeFactor(-1.0, 1.0, 0.1), + markup_sell_pct: Phenotypes.RangeFactor(-1.0, 1.0, 0.1), + order_type: Phenotypes.ListOption(['maker', 'taker']), + sell_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.1), + buy_stop_pct: Phenotypes.RangeFactor(0.0, 50.0,0.1), + profit_stop_enable_pct: Phenotypes.RangeFactor(0.0, 5.0, 0.1), + profit_stop_pct: Phenotypes.RangeFactor(0.0, 50.0, 0.1), + + // -- strategy + rsi_periods: Phenotypes.Range(10, 30), + stoch_periods: Phenotypes.Range(5, 30), + stoch_k: Phenotypes.Range(1, 10), + stoch_d: Phenotypes.Range(1, 10), + stoch_k_sell: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + stoch_k_buy: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + + bollinger_size: Phenotypes.RangeFactor(10, 25, 1), + bollinger_time: Phenotypes.RangeFactor(1, 3.0, 0.1), + bollinger_upper_bound_pct: Phenotypes.RangeFactor(0.0, 100.0, 1.0), + bollinger_lower_bound_pct: Phenotypes.RangeFactor(0.0, 100.0, 1.0) + + } +} diff --git a/lib/ta_bollinger.js b/lib/ta_bollinger.js new file mode 100644 index 0000000000..cd2f805e06 --- /dev/null +++ b/lib/ta_bollinger.js @@ -0,0 +1,86 @@ +var talib = require('talib') + +module.exports = function ta_bollinger(s, key, rsi_periods, DevUp, DevDn, d_ma_type) +{ + return new Promise(function(resolve, reject) { + + //dont calculate until we have enough data + if (s.lookback.length >= rsi_periods) { + + let tmpMarket = s.lookback.slice(0, 1000).map(x=>x.close) + tmpMarket.reverse() + //add current period + tmpMarket.push(s.period.close) + + //doublecheck length. + if (tmpMarket.length >= rsi_periods) { + // extract int from string input for ma_type + let optInMAType = getMaTypeFromString(d_ma_type) + talib.execute({ + name: 'BBANDS', + startIdx: tmpMarket.length-1 , + endIdx: tmpMarket.length -1, + inReal: tmpMarket, + optInTimePeriod: rsi_periods, //RSI 14 default + optInNbDevUp: DevUp, // "Deviation multiplier for upper band" Real Default 2 + optInNbDevDn: DevDn, //"Deviation multiplier for lower band" Real Default 2 + optInMAType:optInMAType // "Type of Moving Average" default 0 + + }, function (err, result) { + if (err) { + console.log(err) + reject(err, result) + return + } + + resolve({ + outRealUpperBand: result.result.outRealUpperBand, + outRealMiddleBand: result.result.outRealMiddleBand, + outRealLowerBand: result.result.outRealLowerBand + }) + + }) + } + else { + reject('MarketLenth not populated enough') + } + } + else{ + reject('MarketLenth not populated enough') + } + + }) +} + +/** + * Extract int from string input eg (SMA = 0) + * + * @see https://github.com/oransel/node-talib + * @see https://github.com/markcheno/go-talib/blob/master/talib.go#L20 + */ +function getMaTypeFromString(maType) { + // no constant in lib? + + switch (maType.toUpperCase()) { + case 'SMA': + return 0 + case 'EMA': + return 1 + case 'WMA': + return 2 + case 'DEMA': + return 3 + case 'TEMA': + return 4 + case 'TRIMA': + return 5 + case 'KAMA': + return 6 + case 'MAMA': + return 7 + case 'T3': + return 8 + default: + return 0 + } +} diff --git a/lib/ta_stoch.js b/lib/ta_stoch.js new file mode 100644 index 0000000000..4e2489565c --- /dev/null +++ b/lib/ta_stoch.js @@ -0,0 +1,96 @@ +var talib = require('talib') + +module.exports = function stoch(s, key, k_periods, sk_periods, k_ma_type, d_periods, d_ma_type, optMarket) +{ + return new Promise(function(resolve, reject) { + + + let tmpMarket = optMarket + if (!tmpMarket) + { + + tmpMarket = s.lookback.slice(0, 1000) + tmpMarket.reverse() + //add current period + tmpMarket.push(s.period) + } + + let tmpMarketHigh = tmpMarket.map(x => x.high) + let tmpMarketClose = tmpMarket.map(x => x.close) + let tmpMarketLow = tmpMarket.map(x => x.low) + + + if (tmpMarket.length >= Math.max(k_periods,d_periods,sk_periods) ) { + + + + + let optInSlowDMAType = getMaTypeFromString(d_ma_type) + let optInSlowKMAType = getMaTypeFromString(k_ma_type) + talib.execute({ + name: 'STOCH', + startIdx: 0 , + endIdx: tmpMarketClose.length - 1, + high: tmpMarketHigh, + low: tmpMarketLow, + close: tmpMarketClose, + optInFastK_Period:k_periods, // K 5 default + optInSlowK_Period:sk_periods, //Slow K 3 default + optInSlowK_MAType:optInSlowKMAType, //Slow K maType default 0 + optInSlowD_Period:d_periods, // D 3 default + optInSlowD_MAType:optInSlowDMAType // type of Fast D default 0 + + }, function (err, result) { + if (err) { + console.log(err) + reject(err, result) + return + } + + + resolve({ + k: result.result.outSlowK, + d: result.result.outSlowD + }) + }) + + } + else + { + resolve() + } + }) +} + +/** + * Extract int from string input eg (SMA = 0) + * + * @see https://github.com/oransel/node-talib + * @see https://github.com/markcheno/go-talib/blob/master/talib.go#L20 + */ +function getMaTypeFromString(maType) { + // no constant in lib? + + switch (maType.toUpperCase()) { + case 'SMA': + return 0 + case 'EMA': + return 1 + case 'WMA': + return 2 + case 'DEMA': + return 3 + case 'TEMA': + return 4 + case 'TRIMA': + return 5 + case 'KAMA': + return 6 + case 'MAMA': + return 7 + case 'T3': + return 8 + default: + return 0 + } +} diff --git a/lib/ta_stochrsi.js b/lib/ta_stochrsi.js new file mode 100644 index 0000000000..be6ed7d21d --- /dev/null +++ b/lib/ta_stochrsi.js @@ -0,0 +1,101 @@ +var talib = require('talib') + +module.exports = function srsi(s, key, rsi_periods, k_periods, d_periods, d_ma_type, optMarket) +{ + return new Promise(function(resolve, reject) { + + + + // Returns the parameters needed to execute left comment for latter reference + //var o = talib.explain('STOCHRSI') + + let tmpMarket = optMarket + if (!tmpMarket) + { + tmpMarket = s.lookback.slice(0, 1000).map(x=>x.close) + tmpMarket.reverse() + //add current period + tmpMarket.push(s.period.close) + } + else + { + tmpMarket = tmpMarket.map(x=>x.close) + } + + + //dont calculate until we have enough data + if (tmpMarket.length > rsi_periods) { + + //doublecheck length. + if (tmpMarket.length >= rsi_periods) { + // extract int from string input for ma_type + let optInMAType = getMaTypeFromString(d_ma_type) + talib.execute({ + name: 'STOCHRSI', + startIdx: 0 , + endIdx: tmpMarket.length -1, + inReal: tmpMarket, + optInTimePeriod: rsi_periods, //RSI 14 default + optInFastK_Period:k_periods, // K 5 default + optInFastD_Period:d_periods, // D 3 default + optInFastD_MAType:optInMAType // type of Fast D default 0 + + }, function (err, result) { + if (err) { + console.log(err) + reject(err, result) + return + } + + resolve({ + outFastK: result.result.outFastK, + outFastD: result.result.outFastD + }) + + + }) + } + else { + resolve() + } + } + else + { + resolve() + } + + }) +} + +/** + * Extract int from string input eg (SMA = 0) + * + * @see https://github.com/oransel/node-talib + * @see https://github.com/markcheno/go-talib/blob/master/talib.go#L20 + */ +function getMaTypeFromString(maType) { + // no constant in lib? + + switch (maType.toUpperCase()) { + case 'SMA': + return 0 + case 'EMA': + return 1 + case 'WMA': + return 2 + case 'DEMA': + return 3 + case 'TEMA': + return 4 + case 'TRIMA': + return 5 + case 'KAMA': + return 6 + case 'MAMA': + return 7 + case 'T3': + return 8 + default: + return 0 + } +} diff --git a/lib/ti_bollinger.js b/lib/ti_bollinger.js new file mode 100644 index 0000000000..5463ba30ce --- /dev/null +++ b/lib/ti_bollinger.js @@ -0,0 +1,55 @@ +var tulind = require('tulind') + + +module.exports = function ti_bollinger(s, key, rsi_periods, StdDev, optMarket) +{ + + return new Promise(function(resolve, reject) { + + //dont calculate until we have enough data + + let tmpMarket = optMarket + if (!tmpMarket) + { + tmpMarket = s.lookback.slice(0, 1000).map(x=>x.close) + tmpMarket.reverse() + //add current period + tmpMarket.push(s.period.close) + } + else + { + tmpMarket = tmpMarket.map(x=>x.close) + } + if ( tmpMarket.length >= rsi_periods) { + //doublecheck length. + if (tmpMarket.length >= rsi_periods) { + // extract int from string input for ma_type + + tulind.indicators.bbands.indicator( + [tmpMarket], + [rsi_periods, StdDev] + , function (err, result) { + if (err) { + console.log(err) + reject(err, result) + return + } + + resolve({ + LowerBand: result[0], + MiddleBand: result[1], + UpperBand: result[2] + + }) + }) + } + else { + reject('MarketLenth not populated enough') + } + + } else { + reject('MarketLenth not populated enough')} + }) +} + + diff --git a/lib/ti_macd.js b/lib/ti_macd.js new file mode 100644 index 0000000000..e4ef510996 --- /dev/null +++ b/lib/ti_macd.js @@ -0,0 +1,46 @@ +var tulind = require('tulind') + + +module.exports = function macd(s, key, shortPeriod, longPeriod, signalPeriod ,optMarket) +{ + return new Promise(function(resolve, reject) { + + if (s.lookback.length >= Math.max(shortPeriod,longPeriod) ) { + + let tmpMarket = optMarket + if (!tmpMarket) + { + tmpMarket = s.lookback.slice(0, 1000).map(x=>x.close) + tmpMarket.reverse() + //add current period + tmpMarket.push(s.period.close) + } + else + { + tmpMarket = tmpMarket.map(x=>x.close) + } + tulind.indicators.macd.indicator( + [tmpMarket], + [shortPeriod, longPeriod, signalPeriod] + , function (err, result) { + if (err) { + console.log(err) + reject(err, result) + return + } + + resolve({ + macd: result[0], + macd_signal: result[1], + macd_histogram: result[2] + }) + }) + + } + else + { + reject() + } + }) +} + diff --git a/lib/ti_rsi.js b/lib/ti_rsi.js new file mode 100644 index 0000000000..606ab3d651 --- /dev/null +++ b/lib/ti_rsi.js @@ -0,0 +1,52 @@ +var tulind = require('tulind') + + +module.exports = function ti_rsi(s, key, rsi_period, optMarket) +{ + + return new Promise(function(resolve, reject) { + + //dont calculate until we have enough data + + let tmpMarket = optMarket + if (!tmpMarket) + { + tmpMarket = s.lookback.slice(0, 1000).map(x=>x.close) + tmpMarket.reverse() + //add current period + tmpMarket.push(s.period.close) + } + else + { + tmpMarket = tmpMarket.map(x=>x.close) + } + + if ( tmpMarket.length >= rsi_period) { + //doublecheck length. + if (tmpMarket.length >= rsi_period) { + // extract int from string input for ma_type + + tulind.indicators.rsi.indicator( + [tmpMarket], + [rsi_period] + , function (err, result) { + if (err) { + console.log(err) + reject(err, result) + return + } + resolve({ + rsi: result[0] + }) + }) + } + else { + reject('MarketLenth not populated enough') + } + + } else { + reject('MarketLenth not populated enough')} + }) +} + + diff --git a/lib/ti_stoch.js b/lib/ti_stoch.js new file mode 100644 index 0000000000..2ea2187eff --- /dev/null +++ b/lib/ti_stoch.js @@ -0,0 +1,50 @@ +var tulind = require('tulind') + + +module.exports = function stoch(s, key, k_periods, sk_periods, d_periods, optMarket) +{ + return new Promise(function(resolve, reject) { + + if (s.lookback.length >= Math.max(k_periods,d_periods,sk_periods) ) { + + //dont calculate until we have enough data + let tmpMarket = optMarket + if (!tmpMarket) + { + + tmpMarket = s.lookback.slice(0, 1000) + tmpMarket.reverse() + //add current period + tmpMarket.push(s.period) + } + + let tmpMarketHigh = tmpMarket.map(x => x.high) + let tmpMarketClose = tmpMarket.map(x => x.close) + let tmpMarketLow = tmpMarket.map(x => x.low) + // addCurrentPeriod + + + tulind.indicators.stoch.indicator( + [tmpMarketHigh,tmpMarketLow, tmpMarketClose ], + [k_periods, sk_periods, d_periods] + , function (err, result) { + if (err) { + console.log(err) + reject(err, result) + return + } + + resolve({ + k: result[0], + d: result[1] + }) + }) + + } + else + { + resolve() + } + }) +} + diff --git a/lib/ti_stochrsi.js b/lib/ti_stochrsi.js new file mode 100644 index 0000000000..bab6fc0833 --- /dev/null +++ b/lib/ti_stochrsi.js @@ -0,0 +1,89 @@ +var tulind = require('tulind') + + +module.exports = function ti_stochrsi(s, key, rsi_period, k_periods, d_periods, optMarket) +{ + + return new Promise(function(resolve, reject) { + + //dont calculate until we have enough data + + let tmpMarket = optMarket + if (!tmpMarket) + { + tmpMarket = s.lookback.slice(0, 1000).map(x=>x.close) + tmpMarket.reverse() + //add current period + tmpMarket.push(s.period.close) + } + else + { + tmpMarket = tmpMarket.map(x=>x.close) + } + + if ( tmpMarket.length >= rsi_period) { + //doublecheck length. + if (tmpMarket.length >= rsi_period) { + // extract int from string input for ma_type + + tulind.indicators.rsi.indicator( + [tmpMarket], + [rsi_period] + , function (err, result) { + if (err) { + console.log(err) + reject(err, result) + return + } + let trsi = result[0] + // 0 oldest -- end newest + trsi.reverse() + let stochRSI = [] + + for(let i = 0; i < (k_periods + d_periods - 1); i++) { + let rsiForPeriod = trsi.slice(i, rsi_period + i) + let highestRSI = Math.max(...rsiForPeriod) + let lowestRSI = Math.min(...rsiForPeriod) + + if(highestRSI == lowestRSI) { + stochRSI.push(0) + } else { + stochRSI.push(((trsi[ (rsi_period - 1) + i] - lowestRSI) / (highestRSI - lowestRSI)) ) + } + } + + let percentK = [] + for(let i = 0; i < k_periods; i++) { + let kData = stochRSI.slice(i, k_periods + i) + if(kData.length == k_periods) { + percentK.push(kData.reduce((a,b) => a + b, 0) / kData.length ) + } + } + + let percentD = [] + for(let i = 0; i < d_periods; i++) { + let dData = stochRSI.slice(i, d_periods + i) + if(dData.length == d_periods) { + percentD.push(dData.reduce((a,b) => a + b, 0) / dData.length) + } + } + + + + resolve({ + stochRSI: stochRSI, + stochk :percentK, + stochd :percentD + }) + }) + } + else { + reject('MarketLenth not populated enough') + } + + } else { + reject('MarketLenth not populated enough')} + }) +} + +