Edgoro
Indicator · Language features

Pine Script max_bars_back: dynamic lookbacks that work

Read history with a variable offset in Pine Script v6: when max_bars_back() is needed, how to size it, and the var accumulator that needs no history buffer.

Short answer

Pine keeps a limited history for each series and sizes it from the offsets it sees while the script loads, so an offset that grows later, such as close[barsSinceSignal] after a long trend, can exceed it. Call max_bars_back(close, 1000) with the largest offset you need, or, better, replace the lookback with a var variable that accumulates as bars arrive.

The script

The average close since the last EMA cross, computed twice: with a loop that reads back through history, and with running totals that need none. Paste it into a new indicator in the Pine Editor, then save and add it to the chart.

Pine Script v6
//@version=6
indicator("Average close since the last cross", overlay = true)

maxLook = input.int(1000, "Longest lookback for the loop", minval = 10, maxval = 4900)

fast = ta.ema(close, 20)
slow = ta.ema(close, 50)
bool crossed = ta.cross(fast, slow)
int  since   = ta.barssince(crossed)

// Method 1: loop back over the bars since the cross, reading close[i]
float loopAvg = na
if not na(since) and since <= maxLook
    float total = 0.0
    for i = 0 to since
        total += close[i]
    loopAvg := total / (since + 1)
// The offsets above can reach 4900 bars in a long trend: keep that much close history from the start
max_bars_back(close, 5000)

// Method 2: the same average from running totals, with no history at all
var float runSum   = 0.0
var int   runCount = 0
if crossed
    runSum   := 0.0
    runCount := 0
runSum   += close
runCount += 1
float runAvg = na(since) ? na : runSum / runCount

plot(loopAvg, "Average since the cross (loop)", color.orange, 3)
plot(runAvg, "Average since the cross (var)", color.blue, 1)
plotshape(crossed, "Cross", shape.circle, location.abovebar, color.gray, size = size.tiny)

How it works

  • Where the problem comes from. close[i] with i up to since is an offset decided at run time. Pine sizes the history it keeps for close from what it sees on the first bars; if a trend later lasts longer than that, the script stops with The requested historical offset is beyond the historical buffer's limit, often only on live bars or in Bar Replay.
  • max_bars_back(close, 5000) reserves 5,000 bars of close history from the first bar. The call can sit anywhere in the script; the number must be a constant. The input caps the loop at 4,900 so the offset always fits.
  • Method 2 keeps a running sum and count in var variables, resets them on the cross and adds the current close on every bar. The result is the same average (the two lines overlap), with no history read and one addition per bar instead of a loop.
  • Before the first cross since is na, and both averages are hidden.

Pitfalls

  • It works on history, then fails live. Buffers are sized while the script loads historical bars and aren't resized afterwards. An offset larger than any seen during loading, which typically shows up on a live bar, stops the script. Size the buffer for the largest offset the script can ask for.
  • Sizing the wrong series. The buffer belongs to the series you index. If the loop reads src[i] where src comes from an input, call max_bars_back(src, 5000), not max_bars_back(close, 5000).
  • max_bars_back = 5000 in indicator(). The declaration's parameter resizes the history of every series in the script. It's simpler but slower and heavier than one max_bars_back() call for the series that needs it; very large buffers add to Memory limits exceeded.
  • A hard ceiling. Most series can't be buffered beyond 5,000 bars. For anything longer, accumulate with var as in method 2, or store selected values in an array.
  • Loops that grow with the offset. Reading back through since bars on every bar makes the work grow with the length of the trend, and on long charts it can reach Loop is too long. The running totals don't have that problem.

Variations

Other running values since the event. The same idea gives the highest high since the cross without ta.highest(high, since + 1) and its growing lookback:

// Highest high since the cross, also without any lookback
var float hiSince = na
hiSince := crossed ? high : math.max(nz(hiSince, high), high)
plot(hiSince, "Highest high since the cross", color.teal, style = plot.style_stepline)

The value at the event. When you only need a value from the bar of the event (the close at the cross, not every close since), ta.valuewhen(crossed, close, 0) does it without any offset; see the valuewhen and barssince example.

Questions

What does max_bars_back do in Pine Script?

It sets how many past bars of a series Pine keeps, so that references like close[i] can reach that far back. max_bars_back(close, 1000) reserves 1,000 bars of close history from the start of the script.

What is the maximum max_bars_back value?

Most series can be buffered up to 5,000 bars. If you need more, keep running values with var or store the values you need in an array instead of reading history.

Why does my script work on history but fail in real time?

Pine sized the history buffer from the offsets it saw while loading historical bars. A larger offset that only appears later, on a live bar or in Bar Replay, falls outside the buffer. Set it explicitly with max_bars_back().

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:

“Plot the average close and the highest high since the last 20/50 EMA cross, using running totals so the script never needs a long history buffer.”