For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/
John F. Ehlers - Cycle Analytics
-
Here's the Super Smoother and generic filter class based on John F. Ehlers book. Applicable to all common indicators for filtering/smoothing. Tested the
modified RSI
against the graph in the book and works fine.Feel free to create and share new indicators or discuss effectiveness below.
Supersmoother
class genericSuperSmoother(bt.Indicator): '''P.49 a=e(−1.414*3.14159 / Period) b=2 * a * Cos(1.414 * 180 / Period) c2=b c3=−a * a c1=1 − c2 − c3 Output=c1 * (Input + Input[1]) / 2 + c2 * Output[1] + c3 * Output[2] ''' lines=('outputLine','inputLine') params=( ('inputLine',None), ('smoothingPeriod',60), ) plotinfo=dict(subplot=True) plotlines=dict( inputLine=dict(_plotskip=True) ) def __init__(self): super(genericSuperSmoother, self).__init__() self.l.inputLine=self.p.inputLine a=math.exp(-1.414*math.pi/self.p.smoothingPeriod) b=2*a*math.cos(math.radians(1.414*180/self.p.smoothingPeriod)) self.p.c2=b self.p.c3=-a*a self.p.c1=1-self.p.c2-self.p.c3 def nextstart(self): self.l.outputLine[0]=self.l.outputLine[-1]=self.l.outputLine[-2]=0 def next(self): self.l.outputLine[0]=self.p.c1*(self.l.inputLine[0]+self.l.inputLine[-1])/2+self.p.c2*self.l.outputLine[-1]+self.p.c3*self.l.outputLine[-2]
Filtering
(I called Tuning below)class genericTuningClass(bt.Indicator): ''' The generic tuning ''' lines=('outputLine','inputLine') params=( ('filterType', 'twoPoleHighPass'), ('inputLine','self.data.close'), ('K', 1), ('bandwidth',0), ('period',60), ) plotinfo=dict(subplot=True) plotlines=dict( inputLine=dict(_plotskip=True) ) def __init__(self): super(genericTuningClass, self).__init__() self.addminperiod(self.p.period) self.l.inputLine=eval(self.p.inputLine) filterType=self.p.filterType radianMapping={'singlePole':math.radians(360/self.p.period), 'twoPoleHighPass':math.radians(0.707*360/self.p.period), 'twoPoleLowPass':math.radians(1.414*360/self.p.period)} try: radianSetting=radianMapping[self.p.filterType] except: if self.p.K ==1 and self.p.bandwidth ==0: raise TypeError radianSetting=math.radians(self.p.K * self.p.bandwidth * 360/self.p.period) alphaValue=(math.cos(radianSetting)+math.sin(radianSetting)-1)/math.cos(radianSetting) lambdaValue=math.cos(math.radians(360/self.p.period)) sigmaValue=(1/math.cos(math.radians(360*self.p.bandwidth/self.p.period)))-math.sqrt((1/math.cos(math.radians(360*self.p.bandwidth/self.p.period)))**2-1) #P.28 - Below filter set are value for 'b0','b1','b2','a1','a2' ('a0'=1 so skipped) FilterSet={'EMA':{'b0':alphaValue, 'b1':0, 'b2':0, 'a1':-(1-alphaValue), 'a2':0}, 'twoPoleLowPass':{'b0':alphaValue**2, 'b1':0, 'b2':0, 'a1':-2*(1-alphaValue), 'a2':0}, #High Pass Plus has b1=-(1+alpha/2) instead of -1(1-alpha/2) 'highPassPlus':{'b0':(1+alphaValue/2), 'b1':-(1+alphaValue/2), 'b2':0, 'a1':-(1-alphaValue), 'a2':0}, 'highPass':{'b0':(1-alphaValue/2), 'b1':-(1-alphaValue/2), 'b2':0, 'a1':-(1-alphaValue), 'a2':0}, 'twoPoleHighPass':{'b0':(1-alphaValue/2)**2, 'b1':-2*(1-alphaValue/2)**2, 'b2':(1-alphaValue/2)**2, 'a1':-2*(1-alphaValue), 'a2':(1-alphaValue)**2}, 'bandPass':{'b0':(1-sigmaValue)/2, 'b1':0, 'b2':-(1-sigmaValue)/2, 'a1':-lambdaValue*(1+sigmaValue), 'a2':sigmaValue}, 'bandStop':{'b0':(1+sigmaValue)/2, 'b1':-2*lambdaValue*(1+sigmaValue)/2, 'b2':(1+sigmaValue)/2, 'a1':-lambdaValue*(1+sigmaValue), 'a2':sigmaValue}} self.p.paramsSet=FilterSet[filterType] if (self.p.filterType=='bandPass' or self.p.filterType=='bandStop') and (sigmaValue==0 or lambdaValue==0): raise IndexError def nextstart(self): #Seed Recursive value self.l.outputLine[0]=self.l.outputLine[-1]=0 def next(self): #Formula=b0*input+b1*input[-1]+b2*input[-2]-a1*output[-1]-a2*output[-2] self.l.outputLine[0]= self.p.paramsSet['b0']*self.l.inputLine[0] \ +self.p.paramsSet['b1']*self.l.inputLine[-1] \ +self.p.paramsSet['b2']*self.l.inputLine[-2] \ -self.p.paramsSet['a1']*self.l.outputLine[-1]\ -self.p.paramsSet['a2']*self.l.outputLine[-2] ''' #For debug purpose only print('b0:{:.2f}, i0:{:.2f}, b1:{:.2f}, i1:{:.2f}, b2:{:.2f}, i2:{:.2f}, a1:{:.2f}, o1:{:.2f}, a2:{:.2f}, o2:{:.2f}. Output:{:.2f}'.format( self.p.paramsSet['b0'],self.l.inputLine[0], self.p.paramsSet['b1'],self.l.inputLine[-1], self.p.paramsSet['b2'],self.l.inputLine[-2], self.p.paramsSet['a1'],self.l.outputLine[-1], self.p.paramsSet['a2'],self.l.outputLine[-2], self.l.outputLine[0])) '''
Example
modified RSI
class modifiedRSI(bt.Indicator): lines=('modifiedRSI',) params=(('period',10), ('HPperiod',48), ('smoothingPeriod',10), ) plotinfo=dict(subplot=True) plotlines=dict( modifiedRSI=dict(_fill_gt=(70, 'red'), _fill_lt=(30, 'green')), ) def _plotinit(self): self.plotinfo.plotyhlines=[10,30,70,90] def __init__(self): self.l.HPclose=genericTuningClass(filterType='twoPoleHighPass',inputLine='self.data.close',period=self.p.HPperiod) self.l.smoothHPClose=genericSuperSmoother(inputLine=self.l.HPclose,smoothingPeriod=self.p.smoothingPeriod) self.l.upDay = bt.ind.UpDay(self.l.smoothHPClose) self.l.downDay = bt.ind.DownDay(self.l.smoothHPClose) self.l.upSum = bt.ind.SumN(self.l.upDay, period=self.p.period) self.l.downSum = bt.ind.SumN(self.l.downDay, period=self.p.period) self.l.denom=self.l.upSum+self.l.downSum self.l.modRSI = 100*self.l.upSum/self.l.denom self.l.modifiedRSI=genericSuperSmoother(inputLine=self.l.modRSI,smoothingPeriod=self.p.smoothingPeriod)
-
Thanks for your sharing. Can you explain in a few words what does this indicator do?
-
Basic idea behind is applying electric signal processing filter technique on any indicators to identify trend in a better way, that will filters out unwanted trend periods (either too long or too short) or try to auto-identify the correct trend period (I havent covered in here though).