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?



  • @Princeton-Wong

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


Log in to reply
 

});