Pine Script valuewhen Explained: Occurrence and [n] Offset
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.
- 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.
| Bar | high | topFractal | vw0 | vw0[2] | vw1 |
|---|---|---|---|---|---|
| 0 to 3 | 10, 11, 13, 12 | false | na | na | na |
| 4 | 11 | true (peak 13) | 13 | na | na |
| 5 | 12 | false | 13 | na | na |
| 6 | 14 | false | 13 | 13 | na |
| 7 | 13 | false | 13 | 13 | na |
| 8 | 12 | true (peak 14) | 14 | 13 | 13 |
| 9 | 13 | false | 14 | 13 | 13 |
| 10 | 15 | false | 14 | 14 | 13 |
| 11 | 14 | false | 14 | 14 | 13 |
| 12 | 13 | true (peak 15) | 15 | 14 | 14 |
| 13 | 12 | false | 15 | 14 | 14 |
- 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.
- 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.
- 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.
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)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.
| Form | Value on confirmation bar | Value on other bars | Depends on spacing? |
|---|---|---|---|
| valuewhen(c, s[d], 0) | Current (new) pivot | Latest pivot | No |
| valuewhen(c, s[d], 1) | Previous pivot | Previous pivot | No |
| valuewhen(c, s[d], 0)[d] | Previous pivot if spacing > d | Previous 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)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.
| Symptom | Cause | Fix |
|---|---|---|
| Compare shows na for the first pivots | Condition not yet true twice, plus depth bars of offset | Guard with not na(...) before comparing |
| Divergence fires on every bar after a pivot | Offset form used off the confirmation bar | Gate with the pivot condition or use occurrence 1 |
| Markers appear to move back in time | plotshape with offset=-depth draws on the peak bar, but the signal exists only depth bars later | Alert and trade logic on the confirmation bar, draw with the offset only for display |
| Oscillator pivot misaligned | Oscillator sampled at confirmation bar, not osc[depth] | Use osc[depth] inside valuewhen |
| Flat tops missing | Strict > on equal highs | Decide a tie rule deliberately and test spacing |
| Inconsistent history | valuewhen called inside a conditional | Move to global scope |
- Plot ta.valuewhen(top, high[depth], 0) and its [depth] and occurrence-1 variants on one chart.
- Add if top then label.new(bar_index, high, str.tostring(pxNow) + " / " + str.tostring(pxPrev)) to see the values at the confirmation bar.
- 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.