Library · Pine Script v5 · Library

ZigZag

by rubystar0512· code credited to TradingView

This library calculates ZigZag pivots and draws connecting lines on the price chart.

View on GitHub

GitHub stars5on the repository
Pine versionv5runs as is
Lines of code365
Settings8inputs
LicenceNone statedfile: MPL-2.0
Last update2 years ago13 Sep 2024

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new library, select all of the template text and paste. Press Save and give it a name.
  4. 4Publish or import itA library draws nothing. Publish it from the editor, then import its functions in your own scripts.

Source Code

365 lines of Pine Script v5 from ThirdTask/ZIGZAG_Library.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

ZIGZAG_Library.pine
// 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
// v7, 2023.10.26

// 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 ———————————————————— Library types and functions


// @type                                Provides calculation and display properties to `ZigZag` objects. 
// @field devThreshold                  The minimum percentage deviation from a point before the `ZigZag` changes direction. 
// @field depth                         The number of bars required for pivot detection. 
// @field lineColor                     The color of each line drawn by the `ZigZag`.
// @field extendLast                    A condition allowing a line to connect the most recent pivot with the current close. 
// @field displayReversalPrice          A condition to display the pivot price in the pivot label. 
// @field displayCumulativeVolume       A condition to display the cumulative volume for the pivot segment in the pivot label. 
// @field displayReversalPriceChange    A condition to display the change in price or percent from the previous pivot in each pivot label. 
// @field differencePriceMode           The reversal change display mode. Options are "Absolute" or "Percent". 
// @field draw                          A condition to determine whether the `ZigZag` displays lines and labels. 
// @field allowZigZagOnOneBar           A condition to allow double pivots i.e., when a large bar makes both a pivot high and a pivot low.
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
    bool   allowZigZagOnOneBar = true


// @type                                Represents a significant level that indicates directional movement or potential support and resistance.
// @field ln                            A `line` object connecting the `start` and `end` chart points. 
// @field lb                            A `label` object to display pivot values. 
// @field isHigh                        A condition to determine whether the pivot is a pivot high. 
// @field vol                           The cumulative volume for the pivot segment. 
// @field start                         A `chart.point` object representing the coordinates of the previous point.
// @field end                           A `chart.point` object representing the coordinate of the current point.
export type Pivot
    line        ln
    label       lb
    bool        isHigh 
    float       vol
    chart.point start
    chart.point end


// @type                                An object to maintain a Zig Zag's settings, pivots, and cumulative volume. 
// @field settings                      A `Settings` object to provide calculation and display properties.
// @field pivots                        An array of `Pivot` objects. 
// @field sumVol                        The volume sum for the current `Pivot` object's line segment. 
// @field extend                        A `Pivot` object used to project a line from the last pivot point to the current bar. 
export type ZigZag
    Settings     settings
    array<Pivot> pivots
    float        sumVol = 0
    Pivot        extend = na



// @function                            Identifies a pivot point when the `src` has not reached beyond its value 
//                                      from `length` bars ago. Finds pivot highs when `isHigh` is `true`, and 
//                                      finds pivot lows otherwise.
// @param src                           (series float) The data series to calculate the pivot value from. 
// @param length                        (series float) The length in bars required for pivot confirmation. 
// @param isHigh                        (simple bool) Determines whether the pivot is a pivot high or pivot low. 
// @returns                             (chart.point) A `chart.point` object when a pivot is found, `na` otherwise.
findPivotPoint(series float src, series float length, simple bool isHigh) =>
    float pivotPrice = nz(src[length])
    if length == 0
        chart.point.new(time, bar_index, pivotPrice)
    else if length * 2 <= bar_index
        bool isFound = true
        for i = 0 to math.abs(length - 1)
            if (isHigh and src[i] > pivotPrice) or (not isHigh and src[i] < pivotPrice)
                isFound := false
                break
        for i = length + 1 to 2 * length
            if (isHigh and src[i] >= pivotPrice) or (not isHigh and src[i] <= pivotPrice)
                isFound := false
                break
        if isFound
            chart.point.new(time[length], bar_index[length], pivotPrice)


// @function                            Calculates the deviation percentage between the `price` and the `basePrice`. 
// @param basePrice                     (series float) The start price. 
// @param price                         (series float) The end price. 
// @returns                             (float) The signed deviation percentage. 
calcDev(series float basePrice, series float price) =>
    float result = 100 * (price - basePrice) / math.abs(basePrice)


// @function                            Calculates the difference between the `start` and `end` point as a price or 
//                                      percentage difference and converts its value to a "string". 
// @param start                         (series float) The start price. 
// @param end                           (series float) The 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 = switch 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" containing the price, cumulative volume, and change in price 
//                                      for the pivot.  
// @param start                         (series float) The start price. 
// @param end                           (series float) The end price. 
// @param vol                           (series float) The pivot's cumulative volume.
// @param settings                      (series Settings) A `Settings` object.
// @returns                             (string) A "string" to display 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                            Creates a label with coordinates from the `point` if the `settings` display 
//                                      properties allow it. 
// @param isHigh                        (series bool) The condition to determine the label's color and location. 
// @param point                         (series chart.point) A `chart.point` object. 
// @param settings                      (series Settings) A `Settings` object. 
// @returns                             (void) The function does not return a value. 
makePivotLabel(series bool isHigh, chart.point point, 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(point, style = label.style_none, xloc = xloc.bar_time, yloc = yloc, textcolor = txtColor)


// @function                            Updates a `Pivot` object's properties, including its `end` point, 
//                                      cumulative volume, label text, and label and line drawing locations. 
//                                      Can be used as a function or method.
// @param this                          (series Pivot) The `Pivot` object to update. 
// @param end                           (series chart.point) A new `chart.point` for the `end` field of the `Pivot`. 
// @param vol                           (series float) The cumulative volume of the `Pivot`.
// @param settings                      (series Settings) A `Settings` object. 
// @returns                             (void) The function does not return a value.
method updatePivot(Pivot this, chart.point end, float vol, Settings settings) =>
    this.end := end
    this.vol := vol
    if not na(this.lb)
        this.lb.set_point(this.end)
        this.lb.set_text(priceRotationAggregate(this.start.price, this.end.price, this.vol, settings))
    this.ln.set_second_point(this.end)


// @function                            Creates a new `Pivot` object, and assigns a line and label if the `draw` field 
//                                      of the `settings` allows it.
// @param start                         (series chart.point) A `chart.point` for the `start` of the `Pivot`. 
// @param end                           (series chart.point) A `chart.point` for the `end` of the `Pivot`. 
// @param vol                           (series float) The cumulative volume of the `Pivot`. 
// @param isHigh                        (series bool) Specifies whether the `Pivot` represents a pivot high or pivot low. 
// @param settings                      (series settings) A `Settings` object. 
// @returns                             (Pivot) The new `Pivot` object. 
newPivot(
     series chart.point start, series chart.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, end, xloc = xloc.bar_time, color = settings.lineColor, width = 2)
        p.lb := makePivotLabel(isHigh, end, settings) 
    p.updatePivot(end, vol, settings)
    p


// @function                            Deletes the `line` and `label` objects assigned to the `ln` and `lb` fields in 
//                                      a `Pivot` object. 
//                                      Can be used as a function or method.
// @param this                          (series Pivot) The `Pivot` object to modify. 
// @returns                             (void) The function does not return a value.
method delete(series Pivot this) =>
    if not na(this.ln)
        this.ln.delete()
    if not na(this.lb)
        this.lb.delete()


// @function                            Determines whether the `price` of the `point` reaches past the `price` of the 
//                                      `end` chart point of a `Pivot` object. 
//                                      Can be used as a function or method.
// @param this                          (series Pivot) A `Pivot` object. 
// @param point                         (series chart.point) A `chart.point` object.
// @returns                             (bool) `true` if the `price` of the `point` reaches past that of the `end` 
//                                      in the `Pivot` object, `false` otherwise.
method isMorePrice(series Pivot this, series chart.point point) => 
    int m = this.isHigh ? 1 : -1
    bool result = point.price * m > this.end.price * m


// @function                            Returns the last `Pivot` object from a `ZigZag` instance if it contains at 
//                                      least one `Pivot`, and `na` otherwise.
//                                      Can be used as a function or method.
// @param this                          (series ZigZag) A `ZigZag` object. 
// @returns                             (Pivot) The last `Pivot` object in the `ZigZag`. 
export method lastPivot(series ZigZag this) =>
    int numberOfPivots = this.pivots.size()
    Pivot result = numberOfPivots > 0 ? this.pivots.get(numberOfPivots - 1) : na


// @function                            Updates the fields of the last `Pivot` in a `ZigZag` object and sets the 
//                                      `sumVol` of the `ZigZag` to 0. 
//                                      Can be used as a function or method.
// @param this                          (series ZigZag) A `ZigZag` object. 
// @param point                         (series chart.point) The `chart.point` for the `start` of the last `Pivot`.
// @returns                             (void) The function does not return a value. 
method updateLastPivot(series ZigZag this, series chart.point point) =>
    Pivot lastPivot = this.lastPivot()
    if this.pivots.size() == 1
        lastPivot.start := point
        if this.settings.draw
            lastPivot.ln.set_first_point(point)
    lastPivot.updatePivot(point, lastPivot.vol + this.sumVol, this.settings)
    this.sumVol := 0


// @function                            Pushes a new `Pivot` object into the `pivots` array of a `ZigZag` instance. 
//                                      Can be used as a function or method.
// @param this                          (series ZigZag) A `ZigZag` object.
// @param new                           (series Pivot) The new `Pivot` to add to the ZigZag. 
// @returns                             (void) The function does not return a value. 
method newPivotFound(series ZigZag this, series Pivot new) =>
    this.pivots.push(new)
    this.sumVol := 0


// @function                            Determines if a new pivot point is detected or if the properties of the 
//                                      last `Pivot` in the `ZigZag` need to be updated by comparing the `end` of the 
//                                      last `Pivot` to a new `point`. Updates the `ZigZag` and returns `true` if 
//                                      either condition occurs. 
//                                      Can be used as a function or method.
// @param this                          (series ZigZag) A `ZigZag` object. 
// @param isHigh                        (series bool) Determines whether it checks for a pivot high or pivot low.
// @param point                         (chart.point) A `chart.point` to compare to the `end` of the last 
//                                      `Pivot` in the `ZigZag`. 
// @returns                             (bool) `true` if it updates the last `Pivot` or adds a new `Pivot` to 
//                                      the `ZigZag`, `false` otherwise.
method newPivotPointFound(series ZigZag this, simple bool isHigh, series chart.point point) =>
    bool result = false
    Pivot lastPivot = this.lastPivot()
    if not na(lastPivot)
        if lastPivot.isHigh == isHigh 
            if lastPivot.isMorePrice(point)
                this.updateLastPivot(point)
                result := true
        else
            float dev = calcDev(lastPivot.end.price, point.price)
            if (not lastPivot.isHigh and dev >= this.settings.devThreshold) or 
                 (lastPivot.isHigh and dev <= -1 * this.settings.devThreshold)
                newPivotFound(this, newPivot(lastPivot.end, point, this.sumVol, isHigh, this.settings))
                result := true
    else
        this.newPivotFound(newPivot(point, point, this.sumVol, isHigh, this.settings))
        result := true
    result


// @function                            Tries to find a new pivot point for the `ZigZag` instance. Updates the
//                                      `ZigZag` and returns `true` when it registers a detected pivot.
//                                      Can be used as a function or method.
// @param this                          (series ZigZag) A `ZigZag` object.
// @param src                           (series float) The data series to calculate the pivot value from.  
// @param isHigh                        (simple bool) Determines whether it checks for a pivot high or pivot low. 
// @param depth                         (series int) The number of bars to search for new pivots. 
// @param registerPivot                 (series bool) A condition that determines whether or not to register a pivot. 
// @returns                             (bool) `true` when a new pivot point is registered and the `ZigZag` is updated, 
//                                      `false` otherwise.
method tryFindPivot(
     series ZigZag this, series float src, simple bool isHigh, series int depth, series bool registerPivot = true
 ) =>
    chart.point point = findPivotPoint(src, depth, isHigh)
    bool result = not na(point) and registerPivot ? this.newPivotPointFound(isHigh, point) : false


// @function                            Updates a `ZigZag` objects with new pivots, volume, lines, and labels.
//                                      NOTE: This function must be called on every bar for accurate calculations.
//                                      Can be used as a function or method.
// @param this                          (series ZigZag) A `ZigZag` object.         
// @returns                             (bool) `true` when a new pivot point is registered and the `ZigZag` is updated, 
//                                      `false` otherwise.
export method update(series ZigZag this) =>
    int depth = math.max(2, math.floor(this.settings.depth / 2))
    this.sumVol += nz(volume[depth])
    bool somethingChanged = this.tryFindPivot(high, true, depth)
    somethingChanged := this.tryFindPivot(
         low, false, depth, this.settings.allowZigZagOnOneBar or not somethingChanged
     ) or somethingChanged
    Pivot lastPivot = this.lastPivot()
    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
        float curSeries = isHigh ? high : low
        chart.point end = chart.point.new(time, bar_index, curSeries)
        if na(this.extend) or somethingChanged
            if not na(this.extend)
                this.extend.delete()
            this.extend := newPivot(lastPivot.end, end, this.sumVol, isHigh, this.settings)
        this.extend.updatePivot(end, this.sumVol + remVol, this.settings)
    somethingChanged


// @function                            Instantiates a new `ZigZag` object with optional `settings`. 
//                                      If no `settings` are provided, creates a `ZigZag` object with default settings. 
// @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

// @variable The deviation percentage from the last local high or low required to form a new Zig Zag point.
float deviationInput = input.float(5.0, "Deviation (%)", minval = 0.00001, maxval = 100.0)
// @variable The number of bars in the pivot calculation.
int depthInput = input.int(10, "Depth", minval = 1)
// @variable The color of the Zig Zag's lines.
color lineColorInput = input.color(#2962FF, "Line Color")
// @variable If `true`, the Zig Zag will also display a line connecting the last known pivot to the current `close`.
bool extendInput = input.bool(true, "Extend to Last Bar")
// @variable If `true`, the pivot labels will display their price values.
bool showPriceInput = input.bool(true, "Display Reversal Price")
// @variable If `true`, each pivot label will display the volume accumulated since the previous pivot.
bool showVolInput = input.bool(true, "Display Cumulative Volume")
// @variable If `true`, each pivot label will display the change in price from the previous pivot.
bool showChgInput = input.bool(true, "Display Reversal Price Change", inline = "Price Rev")
// @variable Controls whether the labels show price changes as raw values or percentages when `showChgInput` is `true`.
string priceDiffInput = input.string("Absolute", "", options = ["Absolute", "Percent"], inline = "Price Rev")

// @variable A `Settings` instance for `ZigZag` creation.
var Settings settings = 
 Settings.new(
     deviationInput, depthInput, 
     lineColorInput, extendInput, 
     showPriceInput, showVolInput, 
     showChgInput,   priceDiffInput
 )

// @variable A `ZigZag` object created using the `settings`.
var ZigZag zigZag = newInstance(settings)

// Update the `zigZag` on every bar.
zigZag.update()
//#endregion

Overview

ZigZag changes direction when price moves beyond a percentage deviation after a set pivot depth, then stores each pivot with optional labels for reversal price, cumulative volume, and absolute or percent price change. Settings control deviation, depth, line color, last-bar extension, and label content. It is a reusable library, not a strategy, so it has no entries, exits, or stop loss.

Details

Published by

rubystar0512 on GitHub

Code credit

TradingView, as written in the script's header

Type

Library, Pine Script v5

Draws

Library (no plots)

Category

Support & resistance

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Deviation (%)float5.0deviationInput
Depthint10depthInput
Line Colorcolor#2962FFlineColorInput
Extend to Last BarbooltrueextendInput
Display Reversal PricebooltrueshowPriceInput
Display Cumulative VolumebooltrueshowVolInput
Display Reversal Price ChangebooltrueshowChgInput
priceDiffInputstringAbsolutepriceDiffInput
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

PineDraw

by 345-strategies
5 stars

PineDraw is a Pine Script v6 library that provides object primitives for drawing and updating price levels on the chart. It enables indicators and strategies to maintain lines, boxes, and labels in place by updating existing objects instead of deleting and rec

Library v6 Support & resistance
Updated 2 days ago · MPL-2.0 GitHub repository of PineDraw

FVGDetectorLibrary

by alvinng214
2 stars

This Pine Script library detects and plots Smart Money Concept Fair Value Gaps (FVGs) for both demand and supply zones directly on the price chart. It exports a detection function that accepts configurable filter thresholds ranging from standard three-bar imba

Library v5 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · None stated GitHub repository of FVGDetectorLibrary

SMC Engine Private

by skipp-dev
1 stars

This Pine Script v6 library serves as an internal engine for Smart Money Concepts (SMC), providing types, tracking, and rendering logic for order blocks, fair value gaps, and market structure. It calculates order block parameters including quality scores, heig

Library v6 SMC / ICT / price actionSupport & resistance
Updated 2 days ago · None stated GitHub repository of SMC Engine Private

Daily Deviation

by Electrified-Trading
2 stars

This library calculates arrays of daily price deviations from the open to the high, low, and close over a user-specified number of days. It optionally filters outliers using a maximum deviation threshold. The fromOpen function returns a normalized value showin

Library v5 Volatility & bandsSupport & resistance
Updated 1 year ago · MPL-2.0 GitHub repository of Daily Deviation

SFA FlowLiquidity v1

by genesisgrubio39
1 stars

The SFA FlowLiquidity library computes structural flow and liquidity events from price data. It detects sweep ups and downs with associated scores, identifies liquidity levels, and applies session and regime filters. Persistent arrays track liquidity prices, b

Library v6 SMC / ICT / price actionSupport & resistance
Updated 10 months ago · None stated GitHub repository of SFA FlowLiquidity v1

FunctionLinearRegression

by ricardosantos79
261 stars

This Pine Script v5 library provides functions to calculate and plot linear regression channels using sample point arrays. It evaluates paired X and Y arrays to calculate the regression slope, intercept, fitted values, maximum deviation, minimum deviation, and

Library v5 Trend
Updated 4 years ago · None stated GitHub repository of FunctionLinearRegression

More libraries

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers