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:
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 everynext()
call. With all my respect, you may want to red one more time thru the docs, since what is written right now contradicts allbt
concepts.Did you subclass your indicator from
bt.indicators.PeriodN
as example shows or frombt.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
orself.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.
- you call undefined variables such as