The bar loop
kScript runs your whole script once per bar, oldest bar first, walking forward to the newest. Think of the body of your script as a loop body that the engine calls for every candle on the chart. On bar 0 it computes your plots for bar 0, on bar 1 it recomputes them for bar 1, and so on. The line you write once produces a value at every point in time, which is what gives you a series to draw.
That has one consequence worth internalizing: a source feed is what drives the loop. The engine builds its bar sequence from the data you load, so a script with no source never enters the loop and produces nothing. More on that below.
barIndex and isLastBar
Two values let you reason about where you are in the walk:
barIndexis the position of the current bar, starting at0on the oldest bar and counting up.isLastBaristrueonly on the most recent bar, andfalseeverywhere else. Use it to do something exactly once, at the live edge of the chart (draw a label, snapshot a final total).
Both are available anywhere in the per-bar body, including inside ternaries and conditions.
History indexing
Because the script re-runs each bar, you often want a previous bar's value: the close from one bar ago, the high from ten bars ago. That is history indexing, written series[n], where [1] is one bar back and [0] is the current bar.
History indexing only works on a timeseries (or an array). It is the engine remembering past values of a series for you. A plain var holds just the current bar's scalar, so indexing it is a compile error. If you need closeSeries[1], store the value in a timeseries first:
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
timeseries closeSeries = trade.close // now closeSeries[1] is validSee Variables for the full var vs timeseries distinction.
A complete example
This script ties the three ideas together. It anchors on ohlcv, reports barIndex (nudged up by one on the final bar so you can spot the live edge), and computes the one-bar change in close by indexing back through a timeseries.
//@version=2
define(title="Bar Loop Anchor", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
timeseries closeSeries = trade.close
var previousClose = barIndex > 0 ? closeSeries[1] : closeSeries
var closeDelta = closeSeries - previousClose
var loopValue = barIndex + (isLastBar ? 1 : 0)
plotLine(value=loopValue, colors=["#2563eb"], label=["barIndex"], desc=["barIndex with isLastBar offset"])
plotLine(value=closeDelta, colors=["#dc2626"], label=["closeDelta"], desc=["history indexing through a timeseries"])What you'll see: the barIndex line is a straight ramp from 0 up to the last bar's index, ticking one higher on the final bar thanks to the isLastBar offset. The closeDelta line is the bar-over-bar price change, hovering around zero and spiking when price moves. Note the guard barIndex > 0 ? closeSeries[1] : closeSeries: on bar 0 there is no prior bar to look back to, so the script falls back to the current value instead.
You need a source to reach the loop
Since source data is what generates the bar sequence, an offchart script that never opens a source has nothing to iterate. It will compile and start, then stop with zero bars to compute and a message telling you no source data reached the bar loop. Nothing draws, because there were no bars and therefore no plot points.
The fix is to add an anchor before you plot anything derived:
//@version=2
define(title="Anchored", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
// now the loop runs, && you can plot anything off trade
plotLine(value=trade.close, colors=["#2563eb"], label=["Close"], desc=["chart close"])This applies even when your indicator doesn't obviously use price. If you compute something from time or constants alone, you still need a source open so the engine knows which bars to walk.
var versus timeseries, at a glance
The split that matters in the bar loop is history:
- Use
timeserieswhen you want history indexing:closeSeries[1],high[10]. Source data and anything you plan to look back through belongs here. - Use
varwhen you only need the current bar's value: a scalar, a one-bar calculation, an indicator reading you consume immediately.
Indexing a var (closeNow[1]) fails at compile time with a message that indexing is only allowed on timeseries and arrays. That error is the engine telling you to promote the value to a timeseries. The full treatment, including persist for carrying state across bars, is in Variables.