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

Custom Indicator: Schaff Cycle



  • Hello! After a lot of trail and error it finally worked. However, the output seems wierd. The formula is based on this implementation: https://www.tradingview.com/script/dbxXeuw2-Indicator-Schaff-Trend-Cycle-STC/

    It seems that setting some values to 0 if the have never been initiated causes wierd results. Is there a way to tell Backtrader that if there a no value to put it to 0? I think I'm not totally clear how to define the starting-value of recusive objects.

    class schaff_trend_cycle(bt.Indicator):
        lines = ('schaff_cycle', 'f1', 'f2', 'pf',)
    
        params = (('length', 20),
                  ('slowLength', 23),
                  ('fastLength', 50),
                  ('factor', 0.5),
                  )
    
        def __init__(self):
            self.m = bt.ind.MACDHisto(self.data.close, period_me1=self.p.fastLength, period_me2=self.p.slowLength)
            self.v1 = bt.ind.Lowest(self.m, period=self.p.length)
            self.v2 = bt.ind.Highest(self.m, period=self.p.length) - self.v1
            self.l.f1 = bt.If(self.v2 > 0, (self.m - self.v1) / self.v2 * 100, self.lines.f1(-1))
            self.l.pf = self.l.pf(-1) + (self.p.factor * (self.l.f1 - self.l.pf(-1)))
            self.v3 = bt.ind.Lowest(self.l.pf, period=self.p.length)
            self.v4 = bt.ind.Highest(self.l.pf, period=self.p.length) - self.v3
            self.l.f2 = bt.If(self.v4 > 0, ((self.l.pf - self.v3) / self.v4) * 100, self.l.f2(-1))
            self.l.schaff_cycle = self.l.schaff_cycle(-1) + (
                    self.p.factor * (self.l.f2 - self.l.schaff_cycle(-1)))
    
        def nextstart(self):
            self.l.f2[0] = 0.0
            self.l.pf[0] = 0.0
            self.l.schaff_cycle[0] = 0.0
    

    And here is the output:

    8164aa75-9a0c-4f62-b662-5d8ff7970d09-image.png

    On first glance it looks amazing. However, you quickly realize that the Schaff-Cycle is inverted?

    Thanks so much for your help!



  • Read this article about development of Recursive indicators - Blog - Recursive indicators. It seems you next() call need to be used, not __init()__



  • But shouldn't it be possible to define it declarative and put it all in init and just give him one starting value? Also, with next and nextstart I have the problem that some values are dependent on values[-1], which are only used to calculate the indicator.

        def next(self):
                self.m = bt.ind.MACDHisto(self.data.close, period_me1=self.p.fastLength, period_me2=self.p.slowLength)
                self.v1 = bt.ind.Lowest(self.m, period=self.p.length)
                self.v2 = bt.ind.Highest(self.m, period=self.p.length) - self.v1
                self.v3 = bt.ind.Lowest(self.l.pf, period=self.p.length)
                self.v4 = bt.ind.Highest(self.l.pf, period=self.p.length) - self.v3
                self.l.f1 = bt.If(self.v2 > 0, ((self.m - self.v1) / self.v2) *100, self.l.f1[-1])
                self.l.f2 = bt.If(self.v4 > 0, ((self.l.pf - self.v3) / self.v4) * 100, self.l.f2[-1])
                self.l.pf = self.l.pf + (self.p.factor * (self.l.f1 - self.l.pf))
                self.l.schaff_cycle = self.l.schaff_cycle[-1] + (
                        self.p.factor * (self.l.f2 - self.l.schaff_cycle[-1]))
        
    
    
    Traceback (most recent call last):
      File "/home/vincenzot/Documents/fx_xd/fx_xd/fx_xd_bot.py", line 204, in <module>
        opt_runs = cerebro.run(runonce=False)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/cerebro.py", line 1127, in run
        runstrat = self.runstrategies(iterstrat)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/cerebro.py", line 1298, in runstrategies
        self._runnext(runstrats)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/cerebro.py", line 1630, in _runnext
        strat._next()
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/strategy.py", line 347, in _next
        super(Strategy, self)._next()
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineiterator.py", line 263, in _next
        indicator._next()
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineiterator.py", line 282, in _next
        self.nextstart()  # only called for the 1st value
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineiterator.py", line 347, in nextstart
        self.next()
      File "/home/vincenzot/Documents/fx_xd/fx_xd/custom_indicators.py", line 61, in next
        self.l.f2 = bt.If(self.v4 > 0, ((self.l.pf - self.v3) / self.v4) * 100, self.l.f2[-1])
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineroot.py", line 227, in __sub__
        return self._operation(other, operator.__sub__)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineroot.py", line 88, in _operation
        return self._operation_stage2(other, operation, r=r)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineroot.py", line 209, in _operation_stage2
        other = other[0]
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineseries.py", line 467, in __getitem__
        return self.lines[0][key]
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/linebuffer.py", line 163, in __getitem__
        return self.array[self.idx + ago]
    IndexError: array index out of range```
    
    If I put it all in next, it gives me this error. Do I also have to put them in __init__?


  • By adding the 0.0 in nextstart I get the following error:

        def next(self):
            self.m = bt.ind.MACDHisto(self.data.close, period_me1=self.p.fastLength, period_me2=self.p.slowLength)
            self.v1 = bt.ind.Lowest(self.m, period=self.p.length)
            self.v2 = bt.ind.Highest(self.m, period=self.p.length) - self.v1
            self.v3 = bt.ind.Lowest(self.l.pf, period=self.p.length)
            self.v4 = bt.ind.Highest(self.l.pf, period=self.p.length) - self.v3
            self.l.f1 = bt.If(self.v2 > 0, ((self.m - self.v1) / self.v2) *100, self.l.f1[-1])
            self.l.f2 = bt.If(self.v4 > 0, ((self.l.pf - self.v3) / self.v4) * 100, self.l.f2[-1])
            self.l.pf = self.l.pf + (self.p.factor * (self.l.f1 - self.l.pf))
            self.l.schaff_cycle = self.l.schaff_cycle[-1] + (
                    self.p.factor * (self.l.f2 - self.l.schaff_cycle[-1]))
    
        def nextstart(self):
            self.l.f1[0] = 0.0
            self.l.f2[0] = 0.0
            self.l.schaff_cycle[0] = 0.0
    
    Traceback (most recent call last):
      File "/home/vincenzot/Documents/fx_xd/fx_xd/fx_xd_bot.py", line 204, in <module>
        opt_runs = cerebro.run(runonce=False)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/cerebro.py", line 1127, in run
        runstrat = self.runstrategies(iterstrat)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/cerebro.py", line 1298, in runstrategies
        self._runnext(runstrats)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/cerebro.py", line 1630, in _runnext
        strat._next()
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/strategy.py", line 347, in _next
        super(Strategy, self)._next()
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineiterator.py", line 263, in _next
        indicator._next()
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineiterator.py", line 280, in _next
        self.next()
      File "/home/vincenzot/Documents/fx_xd/fx_xd/custom_indicators.py", line 61, in next
        self.l.f2 = bt.If(self.v4 > 0, ((self.l.pf - self.v3) / self.v4) * 100, self.l.f2[-1])
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineroot.py", line 227, in __sub__
        return self._operation(other, operator.__sub__)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineroot.py", line 88, in _operation
        return self._operation_stage2(other, operation, r=r)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineroot.py", line 209, in _operation_stage2
        other = other[0]
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineseries.py", line 467, in __getitem__
        return self.lines[0][key]
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/linebuffer.py", line 163, in __getitem__
        return self.array[self.idx + ago]
    IndexError: array index out of range
    
    Process finished with exit code 1
    
    


  • @Vincenzo-Timmel said in Custom Indicator: Schaff Cycle:

    But shouldn't it be possible to define it declarative and put it all in init and just give him one starting value?

    My experience is that recursive indicators are always developed using cycles, but you can try it different way.

    I think Lowest, Highest, MACDHisto etc should go to __init__(). Otherwise they are defined on every next() call. With all my respect, you may want to red one more time thru the docs, since what is written right now contradicts all bt concepts.

    Did you subclass your indicator from bt.indicators.PeriodN as example shows or from bt.indicator? Former has some means to set up min period of the indicator.



  • Hm, I pretty much read trough all the example but I couldn't find one where not a line is defined recursive, but a value from which an indicator gets calculated. I'm unsure how to initialize those. This is also a version I had but I sadly always got the same error and I couldn't fix it:

        def __init__(self):
            self.m = bt.ind.MACDHisto(self.data.close, period_me1=self.p.fastLength, period_me2=self.p.slowLength)
            self.v1 = bt.ind.Lowest(self.m, period=self.p.length)
            self.v2 = bt.ind.Highest(self.m, period=self.p.length) - self.v1
    
    
        def next(self):
            self.f1[0] = (self.m[0] - self.v1[0]) / self.v2[0] * 100 if self.v2[0] > 0 else self.f1[-1]
            self.pf[0] = self.pf[-1] + (self.p.factor * (self.f1[0] - self.pf[-1]))
            self.v3 = bt.ind.Lowest(self.pf, period=self.p.length)
            self.v4 = bt.ind.Highest(self.pf, period=self.p.length) - self.v3
            self.f2[0] = ((self.pf[0] - self.v3[0]) / self.v4[0]) * 100 if self.v4[0] > 0 else self.f2[-1]
            self.l.schaff_cycle[0] = self.l.schaff_cycle[-1] + (
                    self.p.factor * (self.f2[0] - self.l.schaff_cycle[-1]))
    
    Traceback (most recent call last):
      File "/home/vincenzot/Documents/fx_xd/fx_xd/fx_xd_bot.py", line 209, in <module>
        opt_runs = cerebro.run(runonce=False)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/cerebro.py", line 1127, in run
        runstrat = self.runstrategies(iterstrat)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/cerebro.py", line 1298, in runstrategies
        self._runnext(runstrats)
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/cerebro.py", line 1630, in _runnext
        strat._next()
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/strategy.py", line 347, in _next
        super(Strategy, self)._next()
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineiterator.py", line 263, in _next
        indicator._next()
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineiterator.py", line 282, in _next
        self.nextstart()  # only called for the 1st value
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineiterator.py", line 347, in nextstart
        self.next()
      File "/home/vincenzot/Documents/fx_xd/fx_xd/custom_indicators.py", line 37, in next
        self.f1[0] = (self.m[0] - self.v1[0]) / self.v2[0] * 100 if self.v2[0] > 0 else self.f1[-1]
      File "/home/vincenzot/anaconda3/envs/fx_xd/lib/python3.7/site-packages/backtrader/lineseries.py", line 461, in __getattr__
        return getattr(self.lines, name)
    AttributeError: 'Lines_LineSeries_LineIterator_DataAccessor_Indicat' object has no attribute 'f1'
    
    Process finished with exit code 1
    
    

    The only thing missing is how to initialize this, where to give f1 the first value. I can't just define it in nextstart with a float because it has to be an array.

    self.f1[0] = (self.m[0] - self.v1[0]) / self.v2[0] * 100 if self.v2[0] > 0 else self.f1[-1]
    

    Thanks so much for your help btw. I think I still don't 100% how next, nextstart, and init work together.



  • I don't think that this is the only thing you are missing. To initialize the value is very easy:

    def nextstart(self):
        self.l.f1[0] = 0.0
    

    But here is couple other items to correct:

    • you call undefined variables such as self.f1 or self.pf - this is the main reason of the error returned
    • this in the next()
            self.v3 = bt.ind.Lowest(self.pf, period=self.p.length)
            self.v4 = bt.ind.Highest(self.pf, period=self.p.length) - self.v3
    
    

    and then

    .... self.v3[0] ....
    .... self.v4[0] ....
    

    doesn't work.


Log in to reply
 

});