For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

# 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):

def next(self):
data = self.data.get(size=self.p.period)
hh = max(data)
ll = min(data)
self.l.stochastic = (self.data - hh) / (hh - ll)
self.l.D = btind.SMA(self.l.stochastic, period=self.p.period_dfast)
self.l.K = 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,
'%.2f' % self.rsi.rsi,
'%.2f' % self.stochRsi.stochastic,
'%.2f' % self.stochRsi.D])

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 = (self.data - hh) / (hh - ll)
23         #print(self.l.stochastic)
---> 24         self.l.D = btind.SMA(self.l.stochastic, period=self.p.period_dfast)
25         self.l.K = btind.SMA(self.l.D, period=self.p.period_kslow)

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):

def next(self):
data = self.data.get(size=self.p.period)
hh = max(data)
ll = min(data)
num = self.data - ll
den = hh - ll

if (den == 0 and num == 0):
self.l.stochastic = 50
elif (den == 0):
self.l.stochastic = 0
else:
self.l.stochastic = 100 * ((self.data - ll) / (hh - ll))
``````

def next(self):
....
self.l.D = btind.SMA(self.l.stochastic, period=self.p.period_dfast)
self.l.K = 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):

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 use `self.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` and `0/x`, backtrader already provides a predefined `bt.DivZeroByZero` or `bt.DivByZero` (See the `RSI` and `Stochastic` code for the usage)

Because both `hh` and `ll` already take into account the period, you don't have to deal with it. And there is no need to use `data.get`, because the calculation can be done directly on the spot.

My suggestion is that you look at:

And

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 the `Stochastic`, which needs series with at least 3 data points (`High`, `Low` and `Last`)

• 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)
``````

This is NOT calculating the `Stochastic`, because it is based on a single point series. It is calculating a specific variant of the `Stochastic`, which needs series with at least 3 data points (`High`, `Low` and `Last`)

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)

});