Hyper Fast Lines Based Zig Zag Indicator 3
-
Hi,
Peak valley detection is an indicator that appears simple and isn't. This is my third zig zag indicator optimized to be lines-based in an init rather than step-by-step in a next() or once. It is blindingly fast.
I'd be interested in hearing if anyone can detect any bugs. Please note that the zigzag line will be the unaltered turning points, but zigzag_peak and zigzag_valley are altered by the param plotdistance to move them away from the underlying data for visualization purposes.
I've gotten it down to seven lines, not quite half my previous attempt. The extra two lines are for display purposes.
If anyone can beat this for speed or efficiency I'd be very open to seeing what you can do.
''' Author: B. Bradford MIT License Copyright (c) 2020 B. Bradford Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import backtrader as bt import copy class bbzigzag(bt.Indicator): plotinfo = dict(subplot=True, zigzag=dict(_name='zigzag', color='darkblue', ls='--', _skipnan=True), ) plotlines = dict( zigzag_peak=dict(marker='v', markersize=7.0, color='red', fillstyle='full', ls=''), zigzag_valley=dict(marker='^', markersize=7.0, color='red', fillstyle='full', ls=''), #zigzag=dict(_name='zigzag', color='red', ls='--', _skipnan=True), ) params = ( ('plotdistance', 0.03), # distance to plot arrows (alters high/low indicator lines but not zigzag line) ) lines = ('zigzag', 'zigzag_peak', 'zigzag_valley',) def __init__(self): tmp = copy.copy(self.data) tmp = bt.If(self.data(0) == self.data(-1), tmp(-1) + 0.000001, self.data(0)) self.zigzag_peak = bt.If(bt.And(tmp(0)>tmp(-1), tmp(0)>tmp(1)), self.data(0), float('nan')) tmp = copy.copy(self.data) tmp = bt.If(self.data(0) == self.data(-1), tmp(-1) - 0.000001, self.data(0)) self.zigzag_valley = bt.If(bt.And(tmp(0) < tmp(-1), tmp(0) < tmp(1)), self.data(0), float('nan')) self.lines.zigzag = bt.If(self.zigzag_peak, self.zigzag_peak, bt.If(self.zigzag_valley, self.zigzag_valley, float('nan'))) self.lines.zigzag_peak = self.zigzag_peak * (1 + self.p.plotdistance) self.lines.zigzag_valley = self.zigzag_valley * (1 - self.p.plotdistance)
This is also profoundly easy to change -- for example to allow it to ignore small retracements simply change the params section to:
params = ( ('up_retrace', 0.0), # 0.02 ('dn_retrace', 0.0), # 0.02 ('plotdistance', 0.03), # distance to plot arrows (alters high/low indicator lines but not zigzag line)
And change the tmpline from detecting equal bar entries to a range of bar entries to accommodate each retracement amount.
-
And a screenshot with it used on an SMA:
-
I realize this chart isn't the best illustration as the SMA wiggles and results in some doubling up of peak/valley indications which makes it difficult to see if it's showing a high or low at points.
-
Just to mention if you're attempting to use this live it obviously looks ahead one bar into the future which isn't allowed. If you're using it for preloaded historical data in a batch it works for identifying the exact turning point rather than one bar late. But for live data you'll need to move it one bar back by subtracting one from every bar reference as below. I added a couple of del lines just to prompt a bit of memory cleanup but they are entirely optional.
And yeah, I should use camelcase for the class name. Added a couple of comments to help with anyone who wants to understand the code.
def __init__(self): #Make a copy tmp = copy.copy(self.data) #Peak shift tmp = bt.If(self.data(-1) == self.data(-2), tmp(-2) + 0.000001, self.data(-1)) #Find peaks self.zigzag_peak = bt.If(bt.And(tmp(-1)>tmp(-2), tmp(-1)>tmp(0)), self.data(-1), float('nan')) del tmp tmp = copy.copy(self.data) #valley shift tmp = bt.If(self.data(-1) == self.data(-2), tmp(-2) - 0.000001, self.data(-1)) #Find valleys: self.zigzag_valley = bt.If(bt.And(tmp(-1) < tmp(-2), tmp(-1) < tmp(0)), self.data(-1), float('nan')) self.lines.zigzag = bt.If(self.zigzag_peak, self.zigzag_peak, bt.If(self.zigzag_valley, self.zigzag_valley, float('nan'))) self.lines.zigzag_peak = self.zigzag_peak * (1 + self.p.plotdistance) self.lines.zigzag_valley = self.zigzag_valley * (1 - self.p.plotdistance) del tmp
-
Fixed a real time bar delay bug and tightened the code:
def __init__(self): cpy = copy.copy(self.data) tmp = bt.If(self.data(0) == self.data(-1), cpy(-1) + 0.000001, self.data(0)) self.lines.zigzag_peak = bt.If(bt.And(tmp(-1)>tmp(-2), tmp(-1)>tmp(0)), self.data(-1), float('nan')) cpy = copy.copy(self.data) tmp = bt.If(self.data(0) == self.data(-1), cpy(-1) - 0.000001, self.data(0)) self.lines.zigzag_valley = bt.If(bt.And(tmp(-1) < tmp(-2), tmp(-1) < tmp(0)), self.data(-1), float('nan')) self.lines.zigzag = bt.If(self.zigzag_peak, self.zigzag_peak, bt.If(self.zigzag_valley, self.zigzag_valley, float('nan')))
-
self.lines.zigzag[idx] = (self.zigzag_peak[idx] - absplotdist) if self.zigzag_peak[idx] == self.zigzag_peak[idx] else (self.zigzag_valley[idx] + absplotdist) if self.zigzag_valley[idx] == self.zigzag_valley[idx] else float('NaN') self.lines.zigzag[idx] = (self.zigzag_peak[idx] - absplotdist) if True else (self.zigzag_valley[idx] + absplotdist) if True else float('NaN')
Thank for sharing.
About your former version of zigzag code, what's the difference with these two line of the code?
-