Indicator · Pine Script v6 · On the chart

Zig Zag Labeler

by northis

Calculates and plots zigzag swing lines directly on the price chart based on user-defined deviation and depth settings.

View on GitHub

Repaints

GitHub stars3on the repository
Pine versionv6current
Lines of code271
Settings29inputs
LicenceMIT
Last update5 days ago25 Oct 2026

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 indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

271 lines of Pine Script v6 from PineScript/ZigzagIndicator.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

ZigzagIndicator.pine
//@version=6
indicator("Zig Zag Labeler", overlay = true, max_lines_count = 500, max_labels_count = 500)

// ═══════════════════════════════════════════════════════════════
// Zigzag Settings
// ═══════════════════════════════════════════════════════════════
float devInput   = input.float(5.0, "Deviation (%)", minval = 0.00001, maxval = 100.0, step = 0.5, group = "Zigzag")
int   depthInput = input.int(10, "Depth", minval = 2, group = "Zigzag")
color lineClr    = input.color(#2962FF, "Line color", group = "Zigzag")
bool  extLast    = input.bool(true, "Extend to last bar", group = "Zigzag")

// ═══════════════════════════════════════════════════════════════
// Display Settings
// ═══════════════════════════════════════════════════════════════
color  labeledClr   = input.color(#FF6D00, "Labeled segment color", group = "Display")
int    maxSegsShow  = input.int(30, "Max segments to number", minval = 5, maxval = 100, group = "Display")
bool   showTable    = input.bool(true, "Show export table", group = "Display")
string labelTicker  = input.string("", "Labels locked to ticker", group = "Display", tooltip = "Paste ticker (e.g. EURUSD). Labels only apply when chart matches. Empty = any symbol.")
string labelTf      = input.string("", "Labels locked to timeframe", group = "Display", tooltip = "Paste timeframe (e.g. 60, D). Empty = any timeframe.")

// ═══════════════════════════════════════════════════════════════
// Label Slots (segment 1 = most recent confirmed zigzag segment)
// ═══════════════════════════════════════════════════════════════
string MODEL_NONE = "None"

int    s1  = input.int(0, "Seg #", minval = 0, group = "Slot 1",  tooltip = "1 = newest segment, 0 = disabled")
string m1  = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 1")
int    s2  = input.int(0, "Seg #", minval = 0, group = "Slot 2",  tooltip = "1 = newest segment, 0 = disabled")
string m2  = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 2")
int    s3  = input.int(0, "Seg #", minval = 0, group = "Slot 3",  tooltip = "1 = newest segment, 0 = disabled")
string m3  = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 3")
int    s4  = input.int(0, "Seg #", minval = 0, group = "Slot 4",  tooltip = "1 = newest segment, 0 = disabled")
string m4  = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 4")
int    s5  = input.int(0, "Seg #", minval = 0, group = "Slot 5",  tooltip = "1 = newest segment, 0 = disabled")
string m5  = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 5")
int    s6  = input.int(0, "Seg #", minval = 0, group = "Slot 6",  tooltip = "1 = newest segment, 0 = disabled")
string m6  = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 6")
int    s7  = input.int(0, "Seg #", minval = 0, group = "Slot 7",  tooltip = "1 = newest segment, 0 = disabled")
string m7  = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 7")
int    s8  = input.int(0, "Seg #", minval = 0, group = "Slot 8",  tooltip = "1 = newest segment, 0 = disabled")
string m8  = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 8")
int    s9  = input.int(0, "Seg #", minval = 0, group = "Slot 9",  tooltip = "1 = newest segment, 0 = disabled")
string m9  = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 9")
int    s10 = input.int(0, "Seg #", minval = 0, group = "Slot 10", tooltip = "1 = newest segment, 0 = disabled")
string m10 = input.string("None", "Model", options = ["None", "Impulse", "Zigzag", "Double Zigzag", "Diag.Contr.Init.", "Diag.Contr.End."], group = "Slot 10")

// ═══════════════════════════════════════════════════════════════
// Zigzag Calculation (no external library)
// ═══════════════════════════════════════════════════════════════
int depth = depthInput < 4 ? 2 : int(depthInput / 2)

float ph = ta.pivothigh(high, depth, depth)
float pl = ta.pivotlow(low, depth, depth)

// Parallel arrays storing zigzag pivot data
var array<float> zzPrice  = array.new<float>()
var array<int>   zzBar    = array.new<int>()
var array<int>   zzTime   = array.new<int>()
var array<bool>  zzIsHigh = array.new<bool>()
var array<line>  zzLines  = array.new<line>()


// Process pivot high
if not na(ph)
    int pivBar  = bar_index - depth
    int pivTime = time[depth]
    int n = zzPrice.size()
    if n == 0
        zzPrice.push(ph)
        zzBar.push(pivBar)
        zzTime.push(pivTime)
        zzIsHigh.push(true)
    else
        bool lastIsHigh = zzIsHigh.last()
        float lastPrice = zzPrice.last()

        if lastIsHigh
            if ph > lastPrice
                zzPrice.set(n - 1, ph)
                zzBar.set(n - 1, pivBar)
                zzTime.set(n - 1, pivTime)
                if zzLines.size() > 0
                    zzLines.last().set_xy2(pivBar, ph)
        else
            float dev = 100.0 * (ph - lastPrice) / math.abs(lastPrice)
            if dev >= devInput
                zzPrice.push(ph)
                zzBar.push(pivBar)
                zzTime.push(pivTime)
                zzIsHigh.push(true)
                line ln = line.new(zzBar.get(n - 1), zzPrice.get(n - 1), pivBar, ph, color = lineClr, width = 2)
                zzLines.push(ln)

// Process pivot low
if not na(pl)
    int pivBar  = bar_index - depth
    int pivTime = time[depth]
    int n = zzPrice.size()
    if n == 0
        zzPrice.push(pl)
        zzBar.push(pivBar)
        zzTime.push(pivTime)
        zzIsHigh.push(false)
    else
        bool lastIsHigh = zzIsHigh.last()
        float lastPrice = zzPrice.last()

        if not lastIsHigh
            if pl < lastPrice
                zzPrice.set(n - 1, pl)
                zzBar.set(n - 1, pivBar)
                zzTime.set(n - 1, pivTime)
                if zzLines.size() > 0
                    zzLines.last().set_xy2(pivBar, pl)
        else
            float dev = 100.0 * (pl - lastPrice) / math.abs(lastPrice)
            if dev <= -devInput
                zzPrice.push(pl)
                zzBar.push(pivBar)
                zzTime.push(pivTime)
                zzIsHigh.push(false)
                line ln = line.new(zzBar.get(n - 1), zzPrice.get(n - 1), pivBar, pl, color = lineClr, width = 2)
                zzLines.push(ln)

// Extension line from last pivot to current bar
var line extLine = na
if extLast and barstate.islast and zzPrice.size() > 0
    if not na(extLine)
        extLine.delete()
    bool lastIsHigh = zzIsHigh.last()
    float curPrice  = lastIsHigh ? low : high
    extLine := line.new(zzBar.last(), zzPrice.last(), bar_index, curPrice, color = lineClr, width = 2, style = line.style_dashed)

// ═══════════════════════════════════════════════════════════════
// Segment Labeling, Table, and Alert (on last bar)
// ═══════════════════════════════════════════════════════════════
var array<label> segLabels = array.new<label>()
var table tbl = table.new(
     position.bottom_right, 8, 11,
     bgcolor = color.new(color.black, 85),
     border_color = color.new(color.gray, 50),
     border_width = 1)

// Check if labels should apply to this symbol/timeframe
bool symbolMatch = (labelTicker == "" or str.contains(str.upper(syminfo.tickerid), str.upper(labelTicker))) and (labelTf == "" or timeframe.period == labelTf)

// Warning table when symbol doesn't match
var table warnTbl = table.new(position.top_left, 1, 1, bgcolor = color.new(color.red, 70), border_color = color.red, border_width = 1)
if barstate.islast and not symbolMatch and labelTicker != ""
    warnTbl.cell(0, 0, "Labels locked to " + labelTicker + (labelTf != "" ? " / " + labelTf : ""), text_color = color.white, text_size = size.normal)

if barstate.islast
    // Clean up old segment number labels
    if segLabels.size() > 0
        for i = 0 to segLabels.size() - 1
            label.delete(segLabels.get(i))
        segLabels.clear()

    int segCount = zzLines.size()

    // Collect slot data into arrays for iteration
    array<int>    slotSegs = array.from(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)
    array<string> slotMods = array.from(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10)

    // Build lookup: segment number (1=newest) -> model type
    map<int, string> labelMap = map.new<int, string>()
    if symbolMatch
        for j = 0 to 9
            int sn = slotSegs.get(j)
            string mt = slotMods.get(j)
            if sn > 0 and mt != MODEL_NONE and sn <= segCount
                labelMap.put(sn, mt)

    // Display segment numbers on the last N segments
    int endSeg = segCount < maxSegsShow ? segCount : maxSegsShow
    if endSeg > 0
        for k = 1 to endSeg
            int lineIdx       = segCount - k
            int startPivotIdx = lineIdx
            int endPivotIdx   = lineIdx + 1

            int sBar   = zzBar.get(startPivotIdx)
            int eBar   = zzBar.get(endPivotIdx)
            float sPrc = zzPrice.get(startPivotIdx)
            float ePrc = zzPrice.get(endPivotIdx)

            int midBar     = int(math.round(math.avg(sBar, eBar)))
            float midPrice = math.avg(sPrc, ePrc)

            // Reset line to default appearance
            line ln = zzLines.get(lineIdx)
            if not na(ln)
                ln.set_color(lineClr)
                ln.set_width(1)

            bool isLabeled = labelMap.contains(k)
            string txt = str.tostring(k)
            color clr  = color.new(lineClr, 30)

            if isLabeled
                txt := txt + "\n" + labelMap.get(k)
                clr := labeledClr
                if not na(ln)
                    ln.set_color(labeledClr)
                    ln.set_width(2)

            label lb = label.new(
                 midBar, midPrice, txt,
                 xloc = xloc.bar_index, style = label.style_none,
                 textcolor = color.white, size = size.large)
            segLabels.push(lb)

    // ═══ Export Table ═══
    if showTable and segCount > 0 and symbolMatch
        tbl.cell(0, 0, "Ticker",      text_color = color.white, text_size = size.tiny, bgcolor = color.new(color.teal, 60))
        tbl.cell(1, 0, "TF",          text_color = color.white, text_size = size.tiny, bgcolor = color.new(color.teal, 60))
        tbl.cell(2, 0, "Broker",      text_color = color.white, text_size = size.tiny, bgcolor = color.new(color.teal, 60))
        tbl.cell(3, 0, "Start Time",  text_color = color.white, text_size = size.tiny, bgcolor = color.new(color.teal, 60))
        tbl.cell(4, 0, "Start Price", text_color = color.white, text_size = size.tiny, bgcolor = color.new(color.teal, 60))
        tbl.cell(5, 0, "End Time",    text_color = color.white, text_size = size.tiny, bgcolor = color.new(color.teal, 60))
        tbl.cell(6, 0, "End Price",   text_color = color.white, text_size = size.tiny, bgcolor = color.new(color.teal, 60))
        tbl.cell(7, 0, "Model",       text_color = color.white, text_size = size.tiny, bgcolor = color.new(color.teal, 60))

        int row = 1
        string alertPayload = ""

        for j = 0 to 9
            int sn = slotSegs.get(j)
            string mt = slotMods.get(j)
            if sn > 0 and mt != MODEL_NONE and sn <= segCount
                int lineIdx       = segCount - sn
                int startPivotIdx = lineIdx
                int endPivotIdx   = lineIdx + 1

                string startTimeStr = str.format("{0,date,yyyy-MM-dd HH:mm}", zzTime.get(startPivotIdx))
                string endTimeStr   = str.format("{0,date,yyyy-MM-dd HH:mm}", zzTime.get(endPivotIdx))
                string startPrcStr  = str.tostring(zzPrice.get(startPivotIdx), format.mintick)
                string endPrcStr    = str.tostring(zzPrice.get(endPivotIdx), format.mintick)

                tbl.cell(0, row, syminfo.ticker,   text_color = color.white, text_size = size.tiny)
                tbl.cell(1, row, timeframe.period,  text_color = color.white, text_size = size.tiny)
                tbl.cell(2, row, syminfo.prefix,    text_color = color.white, text_size = size.tiny)
                tbl.cell(3, row, startTimeStr,      text_color = color.white, text_size = size.tiny)
                tbl.cell(4, row, startPrcStr,       text_color = color.white, text_size = size.tiny)
                tbl.cell(5, row, endTimeStr,        text_color = color.white, text_size = size.tiny)
                tbl.cell(6, row, endPrcStr,         text_color = color.white, text_size = size.tiny)
                tbl.cell(7, row, mt,                text_color = color.orange, text_size = size.tiny)
                row += 1

                // Build JSON entry for alert webhook
                string stIso = str.format("{0,date,yyyy-MM-dd'T'HH:mm:ss}", zzTime.get(startPivotIdx))
                string etIso = str.format("{0,date,yyyy-MM-dd'T'HH:mm:ss}", zzTime.get(endPivotIdx))
                string entry = '{"ticker":"' + syminfo.ticker
                     + '","tf":"' + timeframe.period
                     + '","broker":"' + syminfo.prefix
                     + '","startTime":"' + stIso
                     + '","startPrice":' + str.tostring(zzPrice.get(startPivotIdx))
                     + ',"endTime":"' + etIso
                     + '","endPrice":' + str.tostring(zzPrice.get(endPivotIdx))
                     + ',"model":"' + mt + '"}'
                alertPayload := alertPayload + (alertPayload != "" ? "," : "") + entry

        // Clear unused data rows
        if row <= 10
            for r = row to 10
                for c = 0 to 7
                    tbl.cell(c, r, "", bgcolor = color.new(color.black, 100))

        // Fire alert with JSON array of all labeled segments
        if alertPayload != ""
            alert("[" + alertPayload + "]", alert.freq_once_per_bar_close)

Overview

Zig Zag Labeler allows traders to number recent swing segments and assign specific Elliott Wave structural models, including impulses, zigzags, and diagonals, to designated segment slots. The indicator includes filtering options to lock visual labels to specific tickers or timeframes and displays an export table on the chart. As with standard zigzag algorithms, historical pivot points repaint until confirmed by subsequent price movement.

Details

Published by

northis on GitHub

Draws

On the price chart

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

  • Signals can change on the live bar or after it closes (repainting)

Settings

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

SettingTypeDefaultVariable
Deviation (%)float5.0devInput
Depthint10depthInput
Line colorcolor#2962FFlineClr
Extend to last barbooltrueextLast
Labeled segment colorcolor#FF6D00labeledClr
Max segments to numberint30maxSegsShow
Show export tablebooltrueshowTable
Labels locked to tickerstringlabelTicker
Labels locked to timeframestringlabelTf
Seg #int0s1
ModelstringNonem1
Seg #int0s2
Show all 29 settings
SettingTypeDefaultVariable
ModelstringNonem2
Seg #int0s3
ModelstringNonem3
Seg #int0s4
ModelstringNonem4
Seg #int0s5
ModelstringNonem5
Seg #int0s6
ModelstringNonem6
Seg #int0s7
ModelstringNonem7
Seg #int0s8
ModelstringNonem8
Seg #int0s9
ModelstringNonem9
Seg #int0s10
ModelstringNonem10
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

257 stars

This indicator overlays an intraday SPX and ES chart with a midnight session range, regular-trading-hours opening levels, and a VWAP projection box. It draws the midnight high, mid, and low, flags edge rejections within an absolute tolerance, and plots optiona

Indicator v6 TrendSupport & resistance
Updated 7 weeks ago · None stated GitHub repository of SPX Midnight Box + Snag Logic
53 stars

Apex Trend & Liquidity Master calculates a trend cloud using customizable moving averages expanded by a volatility multiplier directly on the main price chart. It identifies dynamic support and resistance liquidity zones from swing high and low pivots, extendi

Indicator v6 TrendSupport & resistance
Updated 2 months ago · MPL-2.0 GitHub repository of Apex Trend & Liquidity Master

Market Monitor - 1m

by chris-c-thomas
14 stars

Market Monitor - 1m is an overlay indicator designed for multi-chart monitoring and directional bias assessment. It plots a fast, mid, and slow EMA ribbon with a fill cloud, session VWAP with standard deviation bands, and previous day key levels. It evaluates

Indicator v6 TrendMoving averages Repaints
Updated 6 months ago · MIT GitHub repository of Market Monitor - 1m
13 stars

The Adaptive Trend Finder calculates logarithmic linear regression channels overlaid on the price chart. Operating on the most recent historical bar, it fits a log-transformed regression line and standard deviation bands. It evaluates linear correlation using

Indicator v6 TrendSupport & resistance Repaints
Updated 6 months ago · MIT GitHub repository of Adaptive Trend Finder (log)

5 EMAs

by hirawatt
11 stars

5 EMAs calculates and plots five Exponential Moving Averages derived from five configurable timeframes directly onto the main price chart. It also plots the high and low lines fetched from an additional custom timeframe, accompanied by a small on-screen table

Indicator v4 Moving averagesTrend Repaints
Updated 2 years ago · MIT GitHub repository of 5 EMAs
9 stars

Adaptive Supertrend is an overlay indicator that dynamically adjusts its Supertrend band width using a composite conviction score. Conviction is derived from ATR percentile ranking, trend force from EMA distance, and chop detection, narrowing the bands during

Indicator v6 TrendVolatility & bands
Updated 3 days ago · MIT GitHub repository of Adaptive Supertrend

More Support & resistance scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

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

Compare brokers