Howwise RSS
Trading Software8 min read

Pine Script valuewhen Explained: Occurrence and [n] Offset

Short answer

Pine Script valuewhen (ta.valuewhen) returns a series that, on every bar, holds the source value from the bar where the condition was last true; the trailing [n] in ta.valuewhen(cond, src, 0)[n] just reads that series n bars back.

Key takeaways
  • ta.valuewhen returns a float series: on each bar it holds src as it was on the Nth most recent bar where the condition was true, and it never scans a window or picks a maximum.
  • The window test belongs to the fractal condition; valuewhen only remembers src[depth] at the confirmation bar.
  • valuewhen(...)[depth] on a confirmation bar equals valuewhen(..., 1) only while pivots are at least depth+1 bars apart; on non-confirmation bars the two can differ once the offset form has switched to the newest pivot, as on bar 10 (14 vs 13).
  • For readability, ta.pivothigh plus var variables stores both price and bar_index of the last two pivots with no offset tricks.
  • Call valuewhen on every bar at global scope, and sample price and oscillator on the same bar (the peak bar) when comparing pivots.

What does Pine Script valuewhen (ta.valuewhen) return on each bar?

The signature is ta.valuewhen(condition, source, occurrence). On every bar it returns the value of source on the bar where condition was true for the Nth most recent time, counting the current bar. Occurrence 0 is the latest true bar, 1 is the one before it, and so on.

Before the condition has been true enough times, the result is na. Between true bars the result simply stays at the remembered value. It does not look at a window of bars and it does not return a highest or lowest value; it is a memory cell that is overwritten each time the condition fires.

In Pine everything is a series, so the return type is series float (the type of source). On the current bar it behaves as one number, and across bars it forms a series. That is why the history operator applies to it exactly as it does to close: ta.valuewhen(cond, src, 0)[3] is the remembered value as it was three bars ago. The [] is applied to the result of the call, not to an argument.

//@version=5
indicator("valuewhen basics", overlay=true)
cond  = close > open
last  = ta.valuewhen(cond, close, 0)   // close of the latest up bar (may be this bar)
prev  = ta.valuewhen(cond, close, 1)   // close of the up bar before that
older = last[3]                        // what 'last' held 3 bars ago
plot(last)
plot(prev, color=color.orange)

Why does the pivot code use both src[depth] and [depth]?

A typical divergence script defines prevTop = ta.valuewhen(topFractal, src[depth], 0)[depth] with depth = 2. The two offsets do different jobs. Inside, src[depth] reads the peak, because a fractal is only confirmed depth bars after its middle bar. Outside, [depth] shifts the remembered series back so that, on the confirmation bar, it still shows the previous pivot.

The example below uses depth = 2 and these highs: 10, 11, 13, 12, 11, 12, 14, 13, 12, 13, 15, 14, 13, 12 on bars 0 to 13. Peaks are at bars 2, 6 and 10, confirmed at bars 4, 8 and 12. vw0 is valuewhen(top, high[2], 0), vw0[2] is the offset form, and vw1 is occurrence 1.

BarhightopFractalvw0vw0[2]vw1
0 to 310, 11, 13, 12falsenanana
411true (peak 13)13nana
512false13nana
614false1313na
713false1313na
812true (peak 14)141313
913false141313
1015false141413
1114false141413
1213true (peak 15)151414
1312false151414
  1. On bar 8 the pivot at bar 6 (14) is confirmed. vw0 already shows 14, so without an offset you would compare the new pivot with itself.
  2. vw0[2] on bar 8 reads vw0 as it was on bar 6, which is 13: the previous pivot. That is the value you want next to the new pivot.
  3. The offset form is only reliable on the confirmation bar. On bar 10, vw0[2] is already 14 (the current pivot) because the stored value changed on bar 8.
Tip: Read the table by columns: vw0 is a staircase that steps up on confirmation bars; vw0[2] is the same staircase delayed by two bars; vw1 is the same staircase but one step behind, with no delay.

How does the fractal condition do the window test?

A common misconception is that valuewhen examines the 5-bar window. It does not. The window test is the boolean topFractal. valuewhen only samples src[depth] on bars where that boolean is true.

With depth = 2, the middle bar is two bars back, so the condition compares high[2] with two bars on each side. The right-hand neighbours high[1] and high[0] exist only now, which is why the pivot is known two bars late.

//@version=5
indicator("Fractal + valuewhen", overlay=true)
depth = 2
src   = high

// strict > on all four neighbours; the peak sits at src[depth]
topFractal = src[depth] > src[depth + 1] and src[depth] > src[depth + 2] and src[depth] > src[depth - 1] and src[depth] > src[depth - 2]

peak    = ta.valuewhen(topFractal, src[depth], 0)  // latest confirmed peak
prevPk  = ta.valuewhen(topFractal, src[depth], 1)  // the peak before it

plotshape(topFractal, style=shape.triangledown, location=location.abovebar, offset=-depth)
plot(peak, "latest")
plot(prevPk, "previous", color=color.orange)
Warning: Strict > means equal highs produce no fractal at all (two equal neighbours fail the test), so flat tops silently vanish. Switching one side to >= can create adjacent fractals and break the spacing assumption in the next section.

Occurrence 1 or the [depth] offset: when do they differ?

At a confirmation bar, valuewhen(..., 1) returns the pivot before the one just confirmed. valuewhen(..., 0)[depth] returns the latest pivot confirmed as of depth bars ago. These match only if no other pivot was confirmed in the last depth bars before the current one.

With strict 5-bar fractals (depth = 2), two peaks cannot be closer than 3 bars, so confirmations are at least 3 bars apart and the forms agree on confirmation bars. On non-confirmation bars they differ only once the offset form has already switched to the newest pivot, as on bar 10 (14 vs 13). On bar 9 they are equal (13 and 13).

They also disagree on confirmation bars whenever ties or looser rules allow pivots closer than depth+1 bars. Occurrence 1 is the safer choice because it depends on counting pivots, not on bar spacing.

The offset form also adds depth extra na bars at the start, since the shifted series needs history.

FormValue on confirmation barValue on other barsDepends on spacing?
valuewhen(c, s[d], 0)Current (new) pivotLatest pivotNo
valuewhen(c, s[d], 1)Previous pivotPrevious pivotNo
valuewhen(c, s[d], 0)[d]Previous pivot if spacing > dPrevious pivot, or the current pivot once the shift catches up (bar 10)Yes

How do I get the previous pivot high and its bar index more clearly?

ta.pivothigh(src, left, right) returns the pivot price on the confirmation bar (right bars after the peak) and na on every other bar. Storing it with var variables avoids offsets entirely and lets you keep the bar index, which valuewhen cannot return alongside the price without a second call.

The peak bar is bar_index - right, because the pivot is reported right bars late. Update the variables only when ph is not na, and shift the old values into the previous slots first.

//@version=5
indicator("Previous pivot high", overlay=true)
left = 2
right = 2
ph = ta.pivothigh(high, left, right)

var float lastPH = na
var int   lastPHBar = na
var float prevPH = na
var int   prevPHBar = na

if not na(ph)
    prevPH    := lastPH
    prevPHBar := lastPHBar
    lastPH    := ph
    lastPHBar := bar_index - right

plot(prevPH, "previous pivot high", style=plot.style_linebr)
Tip: ta.pivothigh also allows different left and right lengths (for example 5 and 2), which the hand-rolled five-bar fractal does not. Check how it treats equal highs on your own data rather than assuming it matches your strict > rule.

How do I write a divergence comparison with valuewhen?

Divergence logic compares price and an oscillator at the same two pivots. Sample both on the peak bar, using the same condition and the same [depth] offset inside, so the price peak and oscillator value are aligned. Sampling the oscillator on the confirmation bar instead compares values from different bars and produces false divergences.

Evaluate everything on the confirmation bar, where occurrence 1 gives the previous pivot for both series.

//@version=5
indicator("Pivot divergence logic", overlay=false)
depth = 2
osc = ta.stoch(close, high, low, 14)
osc := ta.sma(osc, 3)

top = high[depth] > high[depth+1] and high[depth] > high[depth+2] and high[depth] > high[depth-1] and high[depth] > high[depth-2]

// computed every bar at global scope
pxNow  = ta.valuewhen(top, high[depth], 0)
pxPrev = ta.valuewhen(top, high[depth], 1)
oscNow  = ta.valuewhen(top, osc[depth], 0)
oscPrev = ta.valuewhen(top, osc[depth], 1)

// on the confirmation bar: higher price peak, lower oscillator peak
bearDiv = top and pxNow > pxPrev and oscNow < oscPrev
plotshape(bearDiv, "divergence", shape.xcross, location.top, offset=-depth)

What goes wrong, and how do I debug it?

Most bugs come from where valuewhen is called or which bar is sampled. In v5 the occurrence argument is a series int, but the function still has to see every bar to build its history, so call it at global scope, not inside an if, a for loop, or a function that runs only sometimes. In v6, and and or short-circuit, so a valuewhen placed only on the right of an and may not execute every bar; assign it to a variable first. ta.valuewhen exists unchanged in v5 and v6, and the logic here is code engineering, not trading advice.

To debug, plot the raw result and drop labels on confirmation bars so you can watch the staircase shift.

SymptomCauseFix
Compare shows na for the first pivotsCondition not yet true twice, plus depth bars of offsetGuard with not na(...) before comparing
Divergence fires on every bar after a pivotOffset form used off the confirmation barGate with the pivot condition or use occurrence 1
Markers appear to move back in timeplotshape with offset=-depth draws on the peak bar, but the signal exists only depth bars laterAlert and trade logic on the confirmation bar, draw with the offset only for display
Oscillator pivot misalignedOscillator sampled at confirmation bar, not osc[depth]Use osc[depth] inside valuewhen
Flat tops missingStrict > on equal highsDecide a tie rule deliberately and test spacing
Inconsistent historyvaluewhen called inside a conditionalMove to global scope
  1. Plot ta.valuewhen(top, high[depth], 0) and its [depth] and occurrence-1 variants on one chart.
  2. Add if top then label.new(bar_index, high, str.tostring(pxNow) + " / " + str.tostring(pxPrev)) to see the values at the confirmation bar.
  3. In v6, add log.info("{0} {1} {2}", bar_index, pxNow, pxPrev) inside if top and read the Pine Logs pane; in v5 use a table showing the last values.

Common questions

Does ta.valuewhen return a single value or a series?

It returns a series float, like every Pine value. On the current bar it is one number, and because it is a series you can apply [] to it, as in ta.valuewhen(cond, src, 0)[2].

Why is the occurrence 0 value sometimes the current bar's data?

Occurrence 0 includes the current bar. If the condition is true now, valuewhen returns source from this very bar, which is why the previous event needs occurrence 1.

Can valuewhen return the highest value in the last N bars?

No. It only remembers source on bars where the condition was true. For a windowed extreme use ta.highest(src, length), or build the condition around it.

Why does my valuewhen result lag the pivot I see on the chart?

A pivot needs depth bars to its right to be confirmed, so the condition is first true depth bars after the peak. The value is correct but known late; offset=-depth only moves the drawing.

Should I use valuewhen or ta.pivothigh with var variables?

Use ta.pivothigh with var when you need the pivot bar index or a different left and right length, since it is easier to read. Use valuewhen for compact one-line sampling of several series at the same condition.

Related topics:pine scriptvaluewhenpivotsdivergencetradingview