Last active
February 23, 2023 15:27
-
-
Save niquedegraaff/6d8dd07fd96b34504e45f308c052f4ef to your computer and use it in GitHub Desktop.
ZigZag version 4 candidate for Tradingview
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ | |
// © TradingView | |
//@version=5 | |
library("ZigZag", overlay = true) | |
// ZigZag Library | |
// v4, 2023.02.23 | |
// This code was written using the recommendations from the Pine Script™ User Manual's Style Guide: | |
// https://www.tradingview.com/pine-script-docs/en/v5/writing/Style_guide.html | |
//#region ———————————————————— Inputs | |
float deviationInput = input.float(5.0, "Deviation (%)", minval = 0.00001, maxval = 100.0) | |
int depthInput = input.int(10, "Depth", minval = 1) | |
color lineColorInput = input.color(#2962FF, "Line Color") | |
bool extendInput = input.bool(true, "Extend to Last Bar") | |
bool showPriceInput = input.bool(true, "Display Reversal Price") | |
bool showVolInput = input.bool(true, "Display Cumulative Volume") | |
bool showChgInput = input.bool(true, "Display Reversal Price Change", inline = "Price Rev") | |
string priceDiffInput = input.string("Absolute", "", options = ["Absolute", "Percent"], inline = "Price Rev") | |
//#endregion | |
//#region ———————————————————— Library functions | |
// @type Provides calculation and display attributes to ZigZag objects. | |
// @field devThreshold The minimum percentage deviation from a point before the ZigZag will change direction. | |
// @field depth The number of bars required for pivot detection. | |
// @field lineColor Line color. | |
// @field extendLast Condition allowing a line to connect the most recent pivot with the current close. | |
// @field displayReversalPrice Condition to display the pivot price in the pivot label. | |
// @field displayCumulativeVolume Condition to display the cumulative volume for the pivot segment in the pivot label. | |
// @field displayReversalPriceChange Condition to display the change in price or percent from the previous pivot in the pivot label. | |
// @field differencePriceMode Reversal change display mode. Options are "Absolute" or "Percent". | |
// @field draw Condition to display lines and labels. | |
export type Settings | |
float devThreshold = 5.0 | |
int depth = 10 | |
color lineColor = #2962FF | |
bool extendLast = true | |
bool displayReversalPrice = true | |
bool displayCumulativeVolume = true | |
bool displayReversalPriceChange = true | |
string differencePriceMode = "Absolute" | |
bool draw = true | |
// @type A coordinate containing bar, price, and time information. | |
// @field tm A value in UNIX time. | |
// @field price A value on the Y axis (price). | |
// @field barIndex A `bar_index`. | |
export type Point | |
int tm | |
float price | |
int barIndex | |
// @type A level of significance used to determine directional movement or potential support and resistance. | |
// @field ln A line object connecting the `start` and `end` Point objects. | |
// @field lb A label object to display pivot values. | |
// @field isHigh A condition to determine if the pivot is a pivot high. | |
// @field isHigher A condition to determine if the pivot is higher than previous (high or low) pivot | |
// @field vol Volume for the pivot segment. | |
// @field start The coordinate of the previous Point. | |
// @field end The coordinate of the current Point. | |
export type Pivot | |
line ln | |
label lb | |
bool isHigh | |
bool isHigher | |
float vol | |
Point start | |
Point end | |
// @type An object to maintain Zig Zag settings, pivots, and volume. | |
// @field settings Settings object to provide calculation and display attributes. | |
// @field pivots An array of Pivot objects. | |
// @field sumVol The volume sum for the pivot segment. | |
// @field extend Pivot object used to project a line from the last pivot to the last bar. | |
export type ZigZag | |
Settings settings | |
array<Pivot> pivots | |
float sumVol = 0 | |
Pivot extend = na | |
// @function Finds a pivot point if the `src` has not been exceeded over the `length` of bars. Finds pivot highs when `isHigh` is true, and pivot lows otherwise. | |
// @param src (series float) Data series to calculate the pivot value. | |
// @param length (series float) Length in bars required for pivot confirmation. | |
// @param isHigh (simple bool) Condition to determine if the Pivot is a pivot high or pivot low. | |
// @returns (Point) A Point object when a pivot is found, and `na` otherwise. | |
findPivotPoint(series float src, series float length, simple bool isHigh) => | |
float p = nz(src[length]) | |
if length == 0 | |
Point.new(time, p, bar_index) | |
else if length * 2 <= bar_index | |
bool isFound = true | |
for i = 0 to math.abs(length - 1) | |
if (isHigh and src[i] > p) or (not isHigh and src[i] < p) | |
isFound := false | |
break | |
for i = length + 1 to 2 * length | |
if (isHigh and src[i] >= p) or (not isHigh and src[i] <= p) | |
isFound := false | |
break | |
if isFound | |
Point.new(time[length], p, bar_index[length]) | |
// @function Calculates the absolute deviation percentage between the `price` and the `basePrice`. | |
// @param basePrice (series float) Start price. | |
// @param price (series float) End price. | |
// @returns (float) Absolute deviation percentage. | |
calcDev(series float basePrice, series float price) => | |
float result = math.abs(100 * (price - basePrice) / basePrice) | |
// @function Calculates the difference between the `start` and `end` point as a price or percentage difference and converts the value to a string variable. | |
// @param start (series float) Start price. | |
// @param end (series float) End price. | |
// @param settings (series Settings) A Settings object. | |
// @returns (string) A string representation of the difference between points. | |
priceRotationDiff(series float start, series float end, Settings settings) => | |
float diff = end - start | |
string sign = math.sign(diff) > 0 ? "+" : "" | |
string diffStr = settings.differencePriceMode == "Absolute" ? str.tostring(diff, format.mintick) : str.tostring(diff * 100 / start, format.percent) | |
string result = str.format("({0}{1})", sign, diffStr) | |
// @function Creates a string variable containing the price, cumulative volume, and change in price for the pivot. | |
// @param start (series float) Start price. | |
// @param end (series float) End price. | |
// @param vol (series float) Volume. | |
// @param settings (series Settings) A Settings object. | |
// @returns (string) A string to be displayed in pivot labels. | |
priceRotationAggregate(series float start, series float end, series float vol, Settings settings) => | |
string str = "" | |
if settings.displayReversalPrice | |
str += str.tostring(end, format.mintick) + " " | |
if settings.displayReversalPriceChange | |
str += priceRotationDiff(start, end, settings) + " " | |
if settings.displayCumulativeVolume | |
str += "\n" + str.tostring(vol, format.volume) | |
str | |
// @function Produces a label at the `p` Point if `settings` display attributes are enabled. | |
// @param isHigh (series bool) Condition to determine the label color and location. | |
// @param p (series Point) A Point object. | |
// @param settings (series Settings) A Settings object. | |
// @returns (void) Function has no return. | |
makePivotLabel(series bool isHigh, Point p, Settings settings) => | |
if settings.displayReversalPrice or settings.displayReversalPriceChange or settings.displayCumulativeVolume | |
[yloc, txtColor] = switch | |
isHigh => [yloc.abovebar, color.green] | |
=> [yloc.belowbar, color.red] | |
label.new(p.tm, p.price, style = label.style_none, xloc = xloc.bar_time, yloc = yloc, textcolor = txtColor) | |
// @function Updates Pivot attributes including Point objects, volume, label text, and label and line object locations. | |
// @param this (series Pivot) Pivot object to be updated. | |
// @param end (series Point) Point to set the Pivot to. | |
// @param vol (series float) Volume of the Pivot. | |
// @param settings (series Settings) A Settings object. | |
// @returns (void) Function has no return. | |
updatePivot(Pivot this, Point end, float vol, Settings settings) => | |
this.end := end | |
this.vol := vol | |
if not na(this.lb) | |
label.set_xy(this.lb, this.end.tm, this.end.price) | |
label.set_text(this.lb, priceRotationAggregate(this.start.price, this.end.price, this.vol, settings)) | |
line.set_xy2(this.ln, this.end.tm, this.end.price) | |
// @function Creates a new Pivot object and assigns a line and label if enabled in the `settings`. | |
// @param start (series Point) The start Point of the Pivot. | |
// @param end (series Point) The end Point of the Pivot. | |
// @param vol (series float) Volume of the Pivot. | |
// @param isHigh (series bool) Condition to determine if the Pivot is a pivot high or pivot low. | |
// @param settings (series settings) Settings object. | |
// @returns (Pivot) The new Pivot object. | |
newPivot(series Point start, series Point end, series float vol, series bool isHigh, series Settings settings) => | |
Pivot p = Pivot.new(na, na, isHigh, vol, start, end) | |
if settings.draw | |
p.ln := line.new(start.tm, start.price, end.tm, end.price, xloc = xloc.bar_time, color = settings.lineColor, width = 2) | |
p.lb := makePivotLabel(isHigh, end, settings) | |
updatePivot(p, end, vol, settings) | |
p | |
// @function Deletes line and label objects from `this` Pivot. | |
// @param this (series Pivot) A Pivot object. | |
// @returns (void) Function has no return. | |
delete(series Pivot this) => | |
if not na(this.ln) | |
line.delete(this.ln) | |
this.ln := na | |
if not na(this.lb) | |
label.delete(this.lb) | |
this.lb := na | |
// @function Determines if price of the `p` Point is greater than the end price of `this` Pivot. | |
// @param this (series Pivot) A Pivot object. | |
// @param p (series Point) A Point object. | |
// @returns (bool) true if the price of `p` is greater than `this` Pivot price. | |
isMorePrice(series Pivot this, series Point p) => | |
int m = this.isHigh ? 1 : -1 | |
bool result = p.price * m > this.end.price * m | |
// @function Returns the last Pivot of `this` ZigZag if there is at least one Pivot to return, and `na` otherwise. | |
// @param this (series ZigZag) A ZigZag object. | |
// @returns (Pivot) The last Pivot in the ZigZag. | |
export lastPivot(series ZigZag this) => | |
int s = array.size(this.pivots) | |
Pivot result = s > 0 ? array.get(this.pivots, s - 1) : na | |
// @function Updates the last Pivot of `this` ZigZag to the `p` Point and sets the volume to 0. | |
// @param this (series ZigZag) A ZigZag object. | |
// @param p (series Point) The Point to set the Pivot to. | |
// @returns (void) Function has no return. | |
updateLastPivot(series ZigZag this, series Point p) => | |
Pivot lastPivot = lastPivot(this) | |
if array.size(this.pivots) == 1 | |
lastPivot.start := p | |
if this.settings.draw | |
line.set_xy1(lastPivot.ln, p.tm, p.price) | |
updatePivot(lastPivot, p, lastPivot.vol + this.sumVol, this.settings) | |
this.sumVol := 0 | |
// @function Pushes a `new` Pivot into the array within `this` ZigZag. | |
// @param this (series ZigZag) A ZigZag object. | |
// @param new (series Pivot) The Pivot to add to the ZigZag. | |
// @returns (void) Function has no return. | |
newPivotFound(series ZigZag this, series Pivot new) => | |
array.push(this.pivots, new) | |
this.sumVol := 0 | |
// @function Determines if a new ZigZag line has been found or the existing line needs updating by comparing new pivots to the existing ZigZag Point. Updates `this` ZigZag and returns true if either condition occurs. | |
// @param this (series ZigZag) A ZigZag object. | |
// @param isHigh (series bool) Condition to look for pivot high or pivot low. | |
// @param p (Point) A Point object to compare to the current ZigZag Point. | |
// @returns (bool) true if a new ZigZag line is found or last zigzag line has changed. | |
newPivotPointFound(series ZigZag this, simple bool isHigh, series Point p) => | |
bool result = false | |
Pivot lastPivot = lastPivot(this) | |
if not na(lastPivot) | |
if lastPivot.isHigh == isHigh | |
if isMorePrice(lastPivot, p) | |
updateLastPivot(this, p) | |
result := true | |
else | |
if calcDev(lastPivot.end.price, p.price) >= this.settings.devThreshold | |
newPivotFound(this, newPivot(lastPivot.end, p, this.sumVol, isHigh, this.settings)) | |
result := true | |
else | |
newPivotFound(this, newPivot(p, p, this.sumVol, isHigh, this.settings)) | |
result := true | |
result | |
// @function Determines if a new ZigZag Point has been found. | |
// @param this (series ZigZag) a ZigZag object. | |
// @param src (series float) Data series to calculate the pivot. | |
// @param isHigh (simple bool) Condition to look for pivot high or pivot low. | |
// @param depth (series int) The length of bars to look for pivots. | |
// @returns (bool) true if a new Zig Zag line is found or the last Zig Zag line has changed. | |
tryFindPivot(series ZigZag this, series float src, simple bool isHigh, series int depth) => | |
Point point = findPivotPoint(src, depth, isHigh) | |
bool result = not na(point) ? newPivotPointFound(this, isHigh, point) : false | |
// @function Updates `this` ZigZag object with new pivots, volume, lines, labels. NOTE: The function must be called on every bar for accurate calculations. | |
// @param this (series ZigZag) a ZigZag object. | |
// @returns (bool) true if a new Zig Zag line is found or the last Zig Zag line has changed. | |
export update(series ZigZag this) => | |
int depth = math.floor(this.settings.depth / 2) | |
this.sumVol += nz(volume[depth]) | |
bool somethingChanged = tryFindPivot(this, high, true, depth) or tryFindPivot(this, low, false, depth) | |
Pivot lastPivot = lastPivot(this) | |
float remVol = math.sum(volume, math.max(depth, 1)) | |
if this.settings.extendLast and barstate.islast and not na(lastPivot) | |
bool isHigh = not lastPivot.isHigh | |
bool isHigher = array.size(pivots) > 2 ? lastPivot.end.price > array.get(pivots, array.size(pivots) -3).end.price : na | |
float curSeries = isHigh ? high : low | |
Point end = Point.new(time, curSeries, bar_index) | |
if na(this.extend) or somethingChanged | |
if not na(this.extend) | |
delete(this.extend) | |
this.extend := newPivot(lastPivot.end, end, this.sumVol, isHigh, this.settings) | |
updatePivot(this.extend, end, this.sumVol + remVol, this.settings) | |
somethingChanged | |
// @function Instantiates a new ZigZag object with `settings`. If no settings are provided, a default ZigZag object is created. | |
// @param settings (series Settings) A Settings object. | |
// @returns (ZigZag) A new ZigZag instance. | |
export newInstance(series Settings settings = na) => | |
ZigZag result = ZigZag.new(na(settings) ? Settings.new() : settings, array.new<Pivot>()) | |
//#endregion | |
//#region ———————————————————— Example Code | |
var Settings settings = | |
Settings.new( | |
deviationInput, depthInput, | |
lineColorInput, extendInput, | |
showPriceInput, showVolInput, | |
showChgInput, priceDiffInput) | |
var ZigZag zigZag = newInstance(settings) | |
update(zigZag) | |
//#endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment