Backtrader Community

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    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

    Indicators/Strategies/Analyzers
    2
    3
    437
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • B
      barton05 last edited by

      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)
      
      1 Reply Last reply Reply Quote 1
      • Princeton Wong
        Princeton Wong last edited by

        Thanks for your sharing. Can you explain in a few words what does this indicator do?

        B 1 Reply Last reply Reply Quote 0
        • B
          barton05 @Princeton Wong last edited by

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

          1 Reply Last reply Reply Quote 0
          • 1 / 1
          • First post
            Last post
          Copyright © 2016, 2017, 2018, 2019, 2020, 2021 NodeBB Forums | Contributors