Skip to content
GetProfitable
Search

Variables, var, if and ternaries

Lesson 5 · about 11 min

Because the script re-runs on every bar, a plain variable is created fresh each time. That is fine for "what is the SMA on this bar" and useless for "how many bars in a row has price been above the SMA", which needs to remember something from the previous run. Pine gives you var for that. Combined with if and the ternary operator, it covers nearly all the control flow you will ever write.

Declaration versus reassignment

= declares a new variable. := changes the value of one that already exists. Using = twice on the same name is an error ("already defined"), and using := on a name that has not been declared is also an error. This split is deliberate: the compiler wants to know which lines create state and which lines change it.

//@version=6
indicator("Assignment demo", overlay=true)
level = ta.sma(close, 20)   // declared, recomputed every bar
if close > level
    level := level * 1.001  // reassigned on this bar only
plot(level)

var: initialise once, then persist

A variable declared with var is created on the first bar and keeps its value from bar to bar until you reassign it.

//@version=6
indicator("var demo", overlay=false)
sma = ta.sma(close, 20)
var int barsAbove = 0
var float highestClose = na
if close > sma
    barsAbove += 1
else
    barsAbove := 0
if na(highestClose) or close > highestClose
    highestClose := close
plot(barsAbove, "Bars above SMA", style=plot.style_columns, color=color.teal)

Without var, barsAbove = 0 would run on every bar and the count would never exceed one. With it, the count accumulates. highestClose is declared as float because na alone gives the compiler no type to infer.

var is also the tool for objects you create once and update, such as a table or a label that follows the last bar, which module 4 uses heavily.

A relative, varip, persists across live ticks inside a bar rather than resetting when a bar recalculates. It is a specialised tool for counting ticks or volume deltas in real time, and it makes a script behave differently on historical bars than live ones. Avoid it until you have read the repainting lesson in module 5.

if, else if, else

Blocks are indented by four spaces. else if chains are allowed.

//@version=6
indicator("if demo", overlay=true)
rsi = ta.rsi(close, 14)
var string state = "neutral"
if rsi > 70
    state := "overbought"
else if rsi < 30
    state := "oversold"
else
    state := "neutral"
barcolor(state == "overbought" ? color.red : state == "oversold" ? color.green : na)

An if can also be used as an expression that returns the last value of the chosen branch:

mult = if timeframe.isintraday
    1.5
else
    2.0

The ternary

condition ? valueIfTrue : valueIfFalse is the one-line version and the most common way to pick a colour or plot something conditionally. Ternaries chain, as in the barcolor line above; read chained ternaries left to right and keep them to two or three levels before switching to if or switch.

Both branches must have the same type, with na allowed on either side. close > open ? close : na is a float series that is na on down bars, and that is exactly what plot() needs for a conditional plot.

switch

For several discrete cases, switch reads better than a ternary chain:

maType = "EMA"
ma = switch maType
    "SMA" => ta.sma(close, 20)
    "EMA" => ta.ema(close, 20)
    => ta.wma(close, 20)

The final case with no value is the default. A switch with no expression after the keyword works like an if/else if chain on booleans.

Do not hide ta.* calls inside conditions

Functions such as ta.sma, ta.rsi and ta.highest keep internal history and must be evaluated on every bar to stay correct. Calling them only inside an if that is sometimes false gives you a compiler warning and, worse, a value that skips bars. The fix is to compute on every bar and use the result conditionally:

rsi = ta.rsi(close, 14)          // every bar
signal = close > open and rsi > 50

The switch example above technically breaks this rule, because only one branch is evaluated per bar. It compiles and works when maType is fixed for the whole run, but computing all three averages and then selecting is the pattern that never surprises you.

Key idea: = creates, := changes, var remembers across bars. Compute series on every bar; decide what to do with them conditionally.

Try it: Write an indicator that counts consecutive up closes with var int streak = 0, resets it on a down close, and colours the background when the streak reaches three. Then remove var and observe what the count does.

Recap

  • = declares and runs every bar; := reassigns an existing variable.
  • var initialises once and persists across bars; declare its type when starting from na.
  • if/else if/else blocks are indented; if can also be used as an expression.
  • The ternary c ? a : b picks a value or colour per bar; branches must share a type, na allowed.
  • Evaluate ta.* functions on every bar, not inside conditions, so their history stays consistent.

See it drawn

Original diagrams for the ideas on this page. Illustrative, not real market data.

Bearish divergence between price and RSIA price line whose second peak is higher than its first, drawn above an RSI panel whose second peak is lower than its first, with the two peaks joined by sloping dashed lines.PRICEhigher highRSI (14)70overbought30oversoldlower high
Divergence between price and RSI. RSI measures how one-sided recent price moves have been on a 0–100 scale. Here price sets a higher peak while RSI sets a lower one, so the second push carried less momentum than the first.