Series and Bars
How history indexing, self-referential series, provisional bars and repainting actually behave.
Built-in series are open, high, low, close, volume, plus the derived helpers hl2, hlc3 and ohlc4. Bar metadata is available as bar.index, bar.time and bar.isLast. On the first bar of a data set, history references beyond the available range return na, and any arithmetic involving na produces na rather than throwing.
Indexing is always backwards from the current bar. close[0] and close are the same value; close[1] is the previous bar. A common bug is comparing close to ta.highest(high, 20) and wondering why the breakout never fires — the current bar is inside that window, so its own high is part of the maximum. Shifting the window with [1] compares against the prior range, which is what a breakout means.
The current bar is provisional until its interval ends. On live data the script re-evaluates on every update, so a condition can become true and then false again within one bar. Use the barClosed guard, or set an alert to fire only on bar close, whenever a decision should not be revisited. Backtests evaluate on closed bars by default for the same reason.
A series may read its own history, which is how every recursive average is written: series ema = na(ema[1]) ? ta.sma(src, len) : k * src + (1 - k) * ema[1]. On the first bar there is no previous value, so ema[1] is na there, and na(x) is the test for exactly that condition. The seed branch is not decoration — na propagates through arithmetic, so a self-referential series with no na() case is na on the first bar and therefore na on every bar after it. State that cannot be written as a function of its own previous value, because it is amended more than once within a single bar, uses var and := instead; Variables and State covers both.
Repainting is the consequence of all this. A script that reads a provisional value, or that requests a higher timeframe without waiting for that timeframe to close, will show one thing live and a different thing when the chart is reloaded. The compiler warns about the common patterns, and data.request takes a lookahead argument that defaults to off precisely so the safe behaviour is the default one.
Example
AlgoBeamScript — Ribbon Breakout strategy
//@version=2
strategy("Ribbon Breakout",
overlay = true,
capital = 25000,
sizing = size.percent(15),
commission = bps(5),
slippage = bps(2))
lookback = input.int(40, "Breakout lookback", minval = 5)
atrLen = input.int(14, "ATR length", minval = 2)
riskAtr = input.float(1.8, "Stop distance in ATR", step = 0.1)
rr = input.float(2.4, "Reward to risk", step = 0.1)
window = input.time("09:30-15:45", "Trading window")
series hh = ta.highest(high, lookback)[1]
series ll = ta.lowest(low, lookback)[1]
series atr = ta.atr(atrLen)
series trend = ta.ema(close, 200)
fn stopFor(bool isLong) {
return isLong ? close - riskAtr * atr : close + riskAtr * atr
}
longSignal = close > hh and close > trend and time.within(window)
shortSignal = close < ll and close < trend and time.within(window)
// Bar-persistent state. A var runs its initialiser once, on the first bar,
// and then keeps whatever the previous bar left in it; := is how it is
// reassigned. That is what lets the stop ratchet: it may tighten toward
// price on any bar, but it never gives ground until a fresh signal resets it.
var longStop = stopFor(true)
var shortStop = stopFor(false)
longStop := longSignal ? stopFor(true) : math.max(longStop, stopFor(true))
shortStop := shortSignal ? stopFor(false) : math.min(shortStop, stopFor(false))
strategy.entry("RB Long", side.long, when = longSignal, comment = "breakout")
strategy.exit("RB Long",
stop = longStop,
target = close + riskAtr * atr * rr)
strategy.entry("RB Short", side.short, when = shortSignal, comment = "breakdown")
strategy.exit("RB Short",
stop = shortStop,
target = close - riskAtr * atr * rr)
strategy.close.all(when = time.crossed(window.end), comment = "flat overnight")
plot(hh, title = "Upper band", color = color.bull, style = plot.step)
plot(ll, title = "Lower band", color = color.bear, style = plot.step)
plot(longStop, title = "Long stop", color = color.fg_subtle)Copy it, change one input, and run it again — the numbers are deterministic, so a difference in the output is always a difference you made.