From f7f29b95d24de850873ed6428643f86f89bcf9df Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Mon, 18 Jun 2018 06:54:18 -0400 Subject: [PATCH] Add strategies that utilize TA-Lib and Tulip-Lib (#1621) * Add Backing Libs ta-lib Bollinger Bands ta-lib Stochastic Oscillator ta-lib Stochastic RSI Oscillator Tulip-lib Bollinger Bands Tulip-lib MACD Tulip-lib RSI Tulip-lib Stochastic Oscillator Tulip-lib Stochastic RSI Oscillator * Add strategy ta_srsi_bollinger This Strategy uses SRSI to detect when to buy and sell verifying price position using Bollinger Bands * Add Strategy ti_stoch_bollinger This strategy uses Stochastic Oscillator to detect buy and sell signals verifying using bollinger bands This utilizes the Tulip Libs * Add strategy ti_bollinger This is a duplicate of native bollinger strategy but implemented using Tulip libs. This was for testing the ti_bollinger backing lib. This can be used by itself but does not detect trends so most likely will result in losses. * Add Strategy ti_stoch This is the Stochastic Oscillator implmented using the Tulip lib. this buys and sells at set SO crossover points. This is mainly for testing the backing library. It can be used standalone but does not check for trends so it is likely to result in losses * Add Stragegy ti_stoch_bollinger This strategy buys and sells using Stochastic Oscillator crossover points and verifies using bollinger. implemented us Tulip lib * Fix bugs in Stoch RSI. add option for passing in market data * Fix bugs in Stoch. add option for passing in market data * Set Bollinger time divider to default. adjust but/sell calculation --- .../strategies/ta_srsi_bollinger/strategy.js | 161 ++++++++++++++++++ .../strategies/ta_stoch_bollinger/strategy.js | 150 ++++++++++++++++ .../strategies/ti_bollinger/strategy.js | 97 +++++++++++ extensions/strategies/ti_stoch/strategy.js | 100 +++++++++++ .../strategies/ti_stoch_bollinger/strategy.js | 142 +++++++++++++++ lib/ta_bollinger.js | 86 ++++++++++ lib/ta_stoch.js | 96 +++++++++++ lib/ta_stochrsi.js | 101 +++++++++++ lib/ti_bollinger.js | 55 ++++++ lib/ti_macd.js | 46 +++++ lib/ti_rsi.js | 52 ++++++ lib/ti_stoch.js | 50 ++++++ lib/ti_stochrsi.js | 89 ++++++++++ 13 files changed, 1225 insertions(+) create mode 100644 extensions/strategies/ta_srsi_bollinger/strategy.js create mode 100644 extensions/strategies/ta_stoch_bollinger/strategy.js create mode 100644 extensions/strategies/ti_bollinger/strategy.js create mode 100644 extensions/strategies/ti_stoch/strategy.js create mode 100644 extensions/strategies/ti_stoch_bollinger/strategy.js create mode 100644 lib/ta_bollinger.js create mode 100644 lib/ta_stoch.js create mode 100644 lib/ta_stochrsi.js create mode 100644 lib/ti_bollinger.js create mode 100644 lib/ti_macd.js create mode 100644 lib/ti_rsi.js create mode 100644 lib/ti_stoch.js create mode 100644 lib/ti_stochrsi.js 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')} + }) +} + +