Pine Script v6 Data Types, var vs varip, and Variable Scope
What a Pine Script variable holds, when its value is known, how long it lasts and where you can see it: types, qualifiers, var vs varip and scope, with short scripts for v6.
Most confusing Pine Script bugs come down to one of four questions about a variable. What kind of value does it hold? When is that value known? How long does it keep it? And where in the script can you see it?
Get those four right and the error messages start making sense, counters stop resetting, and values survive from one bar to the next the way you meant them to. This guide takes each question in turn, with short scripts you can paste into the Pine Editor, all written for Pine Script version 6.
If you haven't written a script yet, start with Getting Started with Pine Script v6: Your First Indicator and come back.
This article covers how values behave inside Pine. For the next step, TradingView webhook automation explains how an alert becomes an order on NinjaTrader 8 or a linked Tradovate account.
First, how a script runs
Everything below follows from one fact: your script runs once for every bar on the chart, oldest to newest. On the bar that's still forming, it runs again every time the price updates.
A normal variable is created fresh on each of those runs. Whatever it held on the previous bar is gone unless you ask Pine to keep it. Most "why does my variable keep resetting?" questions end there.
What kind of value: the five basic types
Pine Script has five fundamental types:
int: whole numbers, such as20orbar_indexfloat: numbers with decimals, such ascloseor4.25bool:trueorfalsecolor: colors, such ascolor.greenor#2962FFstring: text, such as"Long"orsyminfo.ticker
You rarely have to write the type. length = 20 is an int and price = close is a float, and Pine works that out for you. You do need it when the starting value doesn't reveal the type, and the common case is na, Pine's "no value yet":
float lastCrossPrice = na
na on its own could be any type, so Pine asks you to say which.
Three rules about these types changed in version 6, and older tutorials get them wrong:
Whole-number division keeps the fraction. 5 / 2 is 2.5 in version 6. Round it yourself with math.floor() or math.round() if you want a whole number.
Numbers are not true or false on their own. if volume no longer compiles. Write if volume > 0, or convert with bool().
A bool is never na. It's always true or false, and na() no longer accepts a bool.
A whole number is converted to a decimal automatically wherever a float is expected, so you can pass 20 where Pine wants 20.0.
Types for drawings, collections and your own data
Beyond the basic five, Pine has types for things you draw (line, label, box, table, polyline, linefill), types for collections (array, matrix, map), and types you define yourself.
A user-defined type groups related values under one name:
type Signal
float price
int barIndex
string side
You create one with Signal.new(close, bar_index, "long") and read its fields with a dot, such as mySignal.price. Enums, a fixed list of named options, are also available. They arrived in June 2024, before version 6, so they aren't a version 6 feature despite what some guides say.
Drawings and collections behave differently from numbers in one important way: the variable holds a reference to the object, not the object itself. Two variables can point at the same label, and changing it through one changes what you see through the other.
When the value is known: const, input, simple and series
Every value also carries a qualifier that says when it becomes known. From earliest to latest:
- const: fixed when the script compiles, like the literal
20or"Long" - input: fixed when you set the script's inputs, like the result of
input.int() - simple: fixed on the first bar and unchanged after it, like
syminfo.ticker - series: can change on every bar, like
closeor anything calculated from it
The order matters because a value can always be used where a later qualifier is allowed, but not the other way round. A const works anywhere, and a series only works where the parameter accepts a value that changes bar to bar.
That's what an error mentioning "series" and "simple" is telling you: you passed a value that changes from bar to bar to a parameter that needs it fixed from the first bar on. The fix is usually to pass an input or a constant instead of something calculated from price.
How long the value lasts: var
By default, a variable is re-created on every bar. Compare these two counters:
//@version=6
indicator("var vs a plain variable")
var int withVar = 0
withVar += 1
int withoutVar = 0
withoutVar += 1
plot(withVar, "With var", color = color.blue)
plot(withoutVar, "Without var", color = color.orange)
The orange line sits flat at 1, because withoutVar starts at 0 on every bar and is increased once. The blue line climbs by one per bar, because var sets withVar to 0 only on the first bar and keeps its value from then on.
var is how you remember things across bars: the price of the last signal, a running total, the highest high since a session opened. It's also how you reuse a drawing instead of creating a new one on every bar:
//@version=6
indicator("Last close label", overlay = true)
var label lastLabel = label.new(bar_index, close, "")
label.set_xy(lastLabel, bar_index, high)
label.set_text(lastLabel, str.tostring(close, format.mintick))
Without var, this script would create a new label on every bar, and TradingView keeps only the most recent 50 labels by default. With it, one label is created once and moved to the latest bar.
How long the value lasts: varip
On a bar that's still forming, Pine runs your script on every price update. Before each of those runs it performs a rollback: variables go back to the values they held when the previous bar closed. That's why a var counter doesn't race upward on every tick of a live bar. Only the value at the bar's close is kept.
varip opts a variable out of the rollback. It keeps every change, including changes made on the ticks of a live bar:
//@version=6
indicator("Updates on this bar")
varip int updates = 0
if barstate.isnew
updates := 0
updates += 1
plot(updates, "Updates this bar")
On a live chart, the line climbs with every price update and resets when a new bar opens. On historical bars it reads 1, because historical bars run once each.
That difference is exactly why varip needs care. Its values exist only while the chart is live. Reload the chart and the live bars become historical, and the counts are gone. TradingView's own manual lists varip scripts among the common causes of repainting, where a script shows one thing in real time and another on history. Use varip for things that are genuinely about the live feed, such as counting updates or timing, and never let a trading signal depend on it. Pine Script Repainting covers how to test for this.
For strategies, one more detail: a strategy only runs on every tick of a live bar when its strategy() declaration sets calc_on_every_tick = true. Without that, there are no intrabar runs for varip to keep.
Where you can see it: scope
A variable declared at the left margin of your script is global and can be used anywhere below it. A variable declared inside an indented block, such as an if, a loop or a function, is local and exists only inside that block.
This doesn't compile, because bodySize only exists inside the if:
if close > open
bodySize = close - open
plot(bodySize)
Declare it outside, then reassign it inside with :=:
float bodySize = na
if close > open
bodySize := close - open
plot(bodySize)
The difference between = and := is the whole fix. = declares a new variable. := changes one that already exists.
Functions can't reassign global variables
A user-defined function can read global variables, but it can't reassign them. This fails:
var float lastCross = na
remember(price) =>
lastCross := price
Instead, have the function return a value, and do the assignment in the main script where the variable lives:
var float lastCross = na
crossPrice(fast, slow) =>
ta.crossover(fast, slow) ? close : na
newCross = crossPrice(ta.sma(close, 20), ta.sma(close, 50))
if not na(newCross)
lastCross := newCross
Objects are the one place this feels different. A function can't point a global variable at a new array, but because the variable holds a reference, the function can still change what's inside the array it was given:
var closes = array.new<float>()
addClose(arr, value) =>
array.push(arr, value)
if array.size(arr) > 10
array.shift(arr)
arr
addClose(closes, close)
After this runs, closes holds the last ten closing prices.
Putting it to work
Most indicators and strategies that trade use these ideas together: a var to remember the last entry price, a user-defined type to keep an entry's price, bar and side in one place, and global variables assigned in the main script rather than inside functions.
When those values need to reach your broker, they go into an alert message. Using Pine Script Variables shows how to put them into an alert, and CrossTrade turns that alert into an order on NinjaTrader 8 or Tradovate.
Frequently asked questions
What is the difference between var and varip in Pine Script?
var keeps a value from one bar to the next, but on a live bar it is rolled back before each price update. varip also keeps every change made during a live bar. varip values only exist while the chart is live, so they can repaint.
Why does my Pine Script variable reset on every bar?
A variable declared without var is created again on every bar. Declare it with var to set it once on the first bar and keep its value after that.
What does series mean in Pine Script?
series is the qualifier for a value that can change on every bar, such as close. Some parameters only accept a value fixed from the first bar on, which is why an error can mention series and simple.
Can a bool be na in Pine Script v6?
No. In version 6 a bool is always true or false, and na() no longer accepts a bool value.
Can a Pine Script function change a global variable?
It can read one but cannot reassign it. Return a value from the function and assign it in the global scope. A function can still change the contents of an array or object passed to it.
Keep learning
New to CrossTrade? Start your free 7-day trial and send your first Pine Script alert to a simulation account. Pine questions are welcome in the CrossTrade Discord.
CrossTrade is execution and automation software, not a broker, financial adviser, or trading strategy. Pine Script features are set by TradingView and change. Futures trading involves substantial risk. Test every automated workflow in simulation and verify orders at your broker.
