Min Period for custom StochasticRSI indicator
-
I am trying to create a stochasticRSI indicator. I have set the self.addminperiod but the strategy next() gets called prior to this.
Note1: This stochasticRSI indicator takes the calculated RSI value and calculates the stochastic oscillator value. A stochasticRSI with a period of 14, 14, 3, 3 would use an RSI with a period of 14, then after 14 RSI values it would calculate the Stochastic using the last 14 RSI values. It would then calculate a fastD and a slowK which are the simple moving average of the stochastic and the simple moving average of the %D respectively.
Note2: In the below example I have minute data, but I am resampling it to 4H. The strategy is intended to run every minute but look at the 4H StochasticRSI value.
class StochasticRSI(bt.Indicator): lines = ('stochastic', 'D', 'K') params = (('period', 14),('period_dfast', 3), ('period_kslow', 3)) def __init__(self): self.addminperiod(self.p.period + self.p.period_dfast + self.p.period_kslow) def next(self): data = self.data.get(size=self.p.period) hh = max(data) ll = min(data) self.l.stochastic[0] = (self.data[0] - hh) / (hh - ll) self.l.D[0] = btind.SMA(self.l.stochastic, period=self.p.period_dfast) self.l.K[0] = btind.SMA(self.l.D, period=self.p.period_kslow)
And I use it via
class St(bt.Strategy): params = dict(multi=True) def __init__(self): self.rsi = rsi = btind.RSI(self.data1) self.stochRsi = StochasticRSI(rsi) def next(self): txt = ','.join( ['%04d' % len(self), '%04d' % len(self.data0), '%04d' % len(self.data1), self.data.datetime.date(0).isoformat(), '%.2f' % self.data0.close[0], '%.2f' % self.rsi.rsi[0], '%.2f' % self.stochRsi.stochastic[0], '%.2f' % self.stochRsi.D[0]]) print(txt)
Here is the output and you can see that the output is starting at Hour 15 (3rd column) which shows that the RSI indicator is preventing the strategy from being called until there is a value RSI (after period=14 hours). I would expect nan values from Hour 15 to Hour 32 for StochasticRSI as the Stochastic RSI is waiting for 14 RSI values plus another 3 for the D moving average and another 3 for the K moving average.
The StochasticRSI indicator next() only gets called after the RSI min period (14) + Stochastic RSI min period (14+3+3=20 Hours) but how come the strategy gets called after the first 14 hours and not after the first 32 hours?
2882,2882,0015,2018-07-07,6607.49,52.47,nan,nan 2883,2883,0015,2018-07-07,6609.01,52.47,nan,nan 2884,2884,0015,2018-07-07,6609.01,52.47,nan,nan 2885,2885,0015,2018-07-07,6609.01,52.47,nan,nan 2886,2886,0015,2018-07-07,6609.00,52.47,nan,nan ... 6958,6958,0033,2018-07-09,6712.15,57.46,nan,nan 6959,6959,0033,2018-07-09,6712.16,57.46,nan,nan 6960,6960,0033,2018-07-09,6712.15,57.46,nan,nan --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-219-444180449bef> in <module>() 1 # Run over everything ----> 2 cerebro.run(runonce=False) ... <ipython-input-214-8cd4d0d08c3e> in next(self) 22 self.l.stochastic[0] = (self.data[0] - hh) / (hh - ll) 23 #print(self.l.stochastic[0]) ---> 24 self.l.D[0] = btind.SMA(self.l.stochastic, period=self.p.period_dfast) 25 self.l.K[0] = btind.SMA(self.l.D, period=self.p.period_kslow) ~/miniconda3/envs/pythontrading/lib/python3.5/site-packages/backtrader/linebuffer.py in __setitem__(self, ago, value) 220 value (variable): value to be set 221 ''' --> 222 self.array[self.idx + ago] = value 223 for binding in self.bindings: 224 binding[ago] = value TypeError: a float is required
And I'm also getting this error when trying to calculate the simple moving average of the stochastic value.
-
I'm adding here how I solved this problem. I'm not sure its the best way but it seems to work. This is also the code for a StochasticRsi indicator as seen in TradingView.com.
Note 1: Its not possible to use the backtrader Stochastic Indicator as the built in one needs data.close whereas here the stochastic is calculated off the RSI value.
Note 2: The is called the StochasticRsi, however its assuming that you are passing in the RSI as the data source. You could pass in any other data source and it would calculate the Stochastic of that also.
In particular I'm not sure whether the two edge case values of 50 and 0 when the denominator of the stochastic is zero are the correct values to be defaulting to.
class StochasticRSI(bt.Indicator): lines = ('D', 'K',) params = (('period', 14), ('period_dfast', 3), ('period_kslow', 3)) plotinfo = dict( # Add extra margins above and below the 1s and -1s plotymargin=0.15, # Plot a reference horizontal line at 20 and 80 plothlines=[20, 80], # Simplify the y scale to 100 and 0 plotyticks=[100, 0]) # Chose the colors of the D and K lines plotlines = dict(D=dict(color='g'), K=dict(color='b')) def _plotlabel(self): # This method returns a list of labels that will be displayed # behind the name of the indicator on the plot. e.g. StochasticRSI (14, 3, 3) # The period must always be there plabels = [self.p.period, self.p.period_dfast, self.p.period_kslow] return plabels def __init__(self): S = Stochastic(period=self.p.period) self.l.D = D = btind.SMA(S, period=self.p.period_dfast) self.l.K = btind.SMA(D, period=self.p.period_kslow) class Stochastic(bt.Indicator): lines = ('stochastic',) params = (('period', 14),) def __init__(self): self.addminperiod(self.p.period) def next(self): data = self.data.get(size=self.p.period) hh = max(data) ll = min(data) num = self.data[0] - ll den = hh - ll if (den == 0 and num == 0): self.l.stochastic[0] = 50 elif (den == 0): self.l.stochastic[0] = 0 else: self.l.stochastic[0] = 100 * ((self.data[0] - ll) / (hh - ll))
-
Your major problems
@sfkiwi said in Min Period for custom StochasticRSI indicator:
def next(self):
....
self.l.D[0] = btind.SMA(self.l.stochastic, period=self.p.period_dfast)
self.l.K[0] = btind.SMA(self.l.D, period=self.p.period_kslow)This is wrong. Indicators, be it inside a Strategy or inside another indicators, are not instantiated during
next
. It wouldn't make any sense to repeat this operation for each and every bar.def __init__(self): self.addminperiod(self.p.period + self.p.period_dfast + self.p.period_kslow) def next(self): data = self.data.get(size=self.p.period) hh = max(data) ll = min(data)
There is no need neither to use
addminperiod
nor to useself.data.get
. For example:def __init__(self): hh = bt.ind.Highest(self.data, period=self.p.period) # or the alias bt.MaxN(self.data, period=self.p.period) ll = bt.ind.Lowest(self.data, period=self.p.period) # or the alias bt.MinN(self.data, period=self.p.period) self.l.stochastic = 100.0 * (self.data - ll) / (hh - ll)
To handle the edge cases of
0/0
and0/x
, backtrader already provides a predefinedbt.DivZeroByZero
orbt.DivByZero
(See theRSI
andStochastic
code for the usage)Because both
hh
andll
already take into account the period, you don't have to deal with it. And there is no need to usedata.get
, because the calculation can be done directly on the spot.My suggestion is that you look at:
- Docs - Strategy, specifically at the lifecycle
- Docs - Indicator Development
And
- Docs - Platform Concepts, look for
Stage 1
andStage 2
@sfkiwi said in Min Period for custom StochasticRSI indicator:
Note 2: The is called the StochasticRsi, however its assuming that you are passing in the RSI as the data source. You could pass in any other data source and it would calculate the Stochastic of that also.
This is NOT calculating the
Stochastic
, because it is based on a single point series. It is calculating a specific variant of theStochastic
, which needs series with at least 3 data points (High
,Low
andLast
) -
Thanks very helpful, here is what I ended up with:
class StochasticRsi(bt.Indicator): lines = ('D', 'K',) params = (('period', 14), ('period_dfast', 3), ('period_kslow', 3), ('movav', btind.MovAv.Simple), ('upperband', 80.0), ('lowerband', 20.0), ('safediv', True), ('safezero', 0)) plotinfo = dict( # Add extra margins above and below the 1s and -1s plotymargin=0.15, # Plot a reference horizontal line at 1.0 and -1.0 plothlines=[20, 80], # Simplify the y scale to 1.0 and -1.0 plotyticks=[100, 0]) plotlines = dict(D=dict(_name='%D', ls='--'), K=dict(_name='%K')) def _plotinit(self): self.plotinfo.plotyhlines = [self.p.upperband, self.p.lowerband] def __init__(self): hh = bt.ind.Highest(self.data, period=self.p.period) ll = bt.ind.Lowest(self.data, period=self.p.period) num = self.data - ll den = hh - ll if self.p.safediv: stoch = 100.0 * DivByZero(num, den, zero=self.p.safezero) else: stoch = 100.0 * (num / den) self.l.D = D = self.p.movav(stoch, period=self.p.period_dfast) self.l.K = self.p.movav(D, period=self.p.period_kslow)
@backtrader said in Min Period for custom StochasticRSI indicator:
This is NOT calculating the
Stochastic
, because it is based on a single point series. It is calculating a specific variant of theStochastic
, which needs series with at least 3 data points (High
,Low
andLast
)I won't argue with you on that, however this is how tradingview.com calculates their 'StochasticRsi' indicator.
-
As some people would for sure describe it: "It looks very canonical"
-
@sfkiwi Thanks for making the basis of this.
Though this thread is old, hopefully this will help anyone getting results they are not expecting.
But I think at the end of your code it looks like you have k and d calc backwards to have it match tradingview.self.l.D = D = self.p.movav(stoch, period=self.p.period_dfast)
self.l.K = self.p.movav(D, period=self.p.period_kslow)should be:
self.l.K = K = self.p.movav(stoch, period=self.p.period_kslow)
self.l.D = self.p.movav(K, period=self.p.period_dfast)