Edgoro
Indicator · Signals and price structure

Pine Script pivot high and low support and resistance

Turn ta.pivothigh() and ta.pivotlow() into support and resistance lines in Pine Script v6: each level extends until a bar closes through it, tracked in arrays.

Short answer

ta.pivothigh(high, left, right) returns the pivot's price on the bar that confirms it, right bars after the pivot itself, and na on every other bar. Draw a line from bar_index - right at that price, keep its ID in an array, and stop extending it once a bar closes beyond the level.

The script

Resistance from pivot highs and support from pivot lows, drawn as lines that extend to the right until a close breaks them, then turn dotted and stop. Paste it into a new indicator in the Pine Editor, then save and add it to the chart.

Pine Script v6
//@version=6
indicator("Pivot support and resistance", overlay = true, max_lines_count = 100)

leftBars  = input.int(10, "Left bars", minval = 1)
rightBars = input.int(5, "Right bars", minval = 1)
maxLevels = input.int(6, "Unbroken levels to keep per side", minval = 1, maxval = 25)

ph = ta.pivothigh(high, leftBars, rightBars)
pl = ta.pivotlow(low, leftBars, rightBars)

// Unbroken levels, oldest first
var array<line> resLines = array.new<line>()
var array<line> supLines = array.new<line>()

// A new pivot: a line from the pivot bar, extended to the right
if not na(ph)
    resLines.push(line.new(bar_index - rightBars, ph, bar_index, ph, color = color.red, width = 2, extend = extend.right))
    if resLines.size() > maxLevels
        resLines.shift().delete()
if not na(pl)
    supLines.push(line.new(bar_index - rightBars, pl, bar_index, pl, color = color.teal, width = 2, extend = extend.right))
    if supLines.size() > maxLevels
        supLines.shift().delete()

// A level is broken when a bar closes beyond it: end the line there and stop tracking it.
// The loops go backwards so removing an element doesn't skip the next one.
if resLines.size() > 0
    for i = resLines.size() - 1 to 0
        line l = resLines.get(i)
        if close > l.get_y1()
            l.set_extend(extend.none)
            l.set_x2(bar_index)
            l.set_style(line.style_dotted)
            resLines.remove(i)
if supLines.size() > 0
    for i = supLines.size() - 1 to 0
        line l = supLines.get(i)
        if close < l.get_y1()
            l.set_extend(extend.none)
            l.set_x2(bar_index)
            l.set_style(line.style_dotted)
            supLines.remove(i)

plotshape(not na(ph), "Pivot high", shape.triangledown, location.abovebar, color.red, offset = -rightBars, size = size.tiny)
plotshape(not na(pl), "Pivot low", shape.triangleup, location.belowbar, color.teal, offset = -rightBars, size = size.tiny)

How it works

  • ta.pivothigh(high, leftBars, rightBars). A pivot high is a bar whose high is above the leftBars bars before it and the rightBars bars after it. Pine can only know that rightBars bars later, so the function returns the price on that later bar and na everywhere else.
  • line.new(bar_index - rightBars, ph, bar_index, ph, ..., extend = extend.right) starts the line on the pivot bar itself and extends it to the right edge of the chart.
  • Two arrays of line IDs (var, so they persist) hold the levels that are still unbroken. Past maxLevels, the oldest is deleted from the chart.
  • Breaks. On every bar the loops check each unbroken level: a close above a resistance (or below a support) ends its line at the current bar, turns it dotted and removes it from the array. Broken lines stay visible until the max_lines_count limit retires them.
  • The if size() > 0 guards matter: with an empty array, for i = -1 to 0 would count from -1 up to 0 and get(-1) would fail.
  • offset = -rightBars draws the small triangles back on the pivot bars, for reading only (see the pitfalls).

Pitfalls

  • Pivots are known late. The level exists rightBars bars after the pivot. The triangles drawn with a negative offset make it look as if the pivot was known on its own bar; a backtest or alert must only use it from the confirmation bar. See the pivots section of repainting explained.
  • Testing the pivot as a bool. if ph doesn't compile in v6: a float isn't a condition (if condition must be bool). Test not na(ph).
  • Calling ta.pivothigh() inside a block. Like every ta.* function it keeps its own history, so it must run on every bar: call it at the top level and test the result in the if (should be called on each calculation).
  • Removing while looping forward. Removing element i shifts the next one into position i, so a forward loop skips it. Loop backwards, as here, or collect the indices first.
  • Too many lines. Each level is a line, and the default limit is about 50 per script. Set max_lines_count (up to 500), and keep the number of tracked levels small.

Variations

The nearest unbroken level above and below the close. One loop per side finds the closest level, which can feed a plot, a table or an alert.

// Nearest unbroken resistance above the close and support below it
float nearestRes = na
float nearestSup = na
for l in resLines
    float y = l.get_y1()
    if y > close and (na(nearestRes) or y < nearestRes)
        nearestRes := y
for l in supLines
    float y = l.get_y1()
    if y < close and (na(nearestSup) or y > nearestSup)
        nearestSup := y
plot(nearestRes, "Nearest resistance", color.red, style = plot.style_linebr)
plot(nearestSup, "Nearest support", color.teal, style = plot.style_linebr)

Zones instead of lines. Replace line.new() with box.new(bar_index - rightBars, ph + 0.25 * atr, bar_index, ph - 0.25 * atr, extend = extend.right), with atr = ta.atr(14) calculated at the top level, to draw a band around each pivot, and raise max_boxes_count.

Questions

What do leftbars and rightbars mean in ta.pivothigh?

A pivot high must be higher than the leftbars bars before it and the rightbars bars after it. Larger values find fewer, more significant swings. rightbars is also the delay: the pivot is only confirmed that many bars later.

Does ta.pivothigh repaint?

Not after confirmation: once returned, the value doesn't change. But it arrives rightbars bars after the pivot, and drawing it back on the pivot bar with an offset makes the chart look earlier than it really was.

How do I get the last pivot high on every bar?

Use ta.valuewhen(not na(ph), ph, 0), with ph = ta.pivothigh(high, 10, 5) on its own line. It returns the most recent pivot high on every bar, and ta.valuewhen(not na(ph), ph, 1) the one before.

Are these the same as classic pivot points?

No. Classic pivot points are levels calculated from the previous day's (or week's) high, low and close. Pivot highs and lows are swing points found in the price itself. Both are on this site: pivot points have their own indicator guide.

Every script on this page was checked with Edgoro Pine Check (Pine Script v6 rules). Pine Check is a static checker, not TradingView's compiler: TradingView compiles the script when you save it in the Pine Editor. The scripts are examples to learn from, not trading systems. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

Need a different version of this script?

Describe the change in plain English and Edgoro writes the Pine Script v6 for you, checked with Pine Check. For example:

“Draw support and resistance lines from pivot highs and lows with 10 bars left and 5 right, extend them until a close breaks them, and alert me on each break.”