Pine Script Variable Declaration: Mastering var, varip, and Proper Initialization Across Historical Bars

Variable declaration is the foundational building block of any Pine Script indicator or strategy. Understanding the precise behavioral differences between standard assignment, var, and varip — and knowing exactly when each value is initialized or updated across historical and real-time bars — is essential for writing correct, predictable, and performant Pine Script code.

1. The Pine Script Execution Model: Why Variable Scope Matters

Pine Script executes your script once per bar, from the oldest historical bar to the most recent, and then continues on each new real-time tick. This bar-by-bar execution model is the core reason why variable declaration semantics matter so much. A variable declared without var is re-evaluated and re-initialized on every single bar execution. A variable declared with var persists its value across bars. Understanding this distinction prevents the most common logical errors in Pine Script development.

graph TD A["Script Starts Executing"] --> B["Bar N: Execute Script"] B --> C{"Variable Declaration Type?"} C -->|"Standard = "| D["Re-initialize from RHS expression"] C -->|"var"| E{"First Bar Ever?"} C -->|"varip"| F{"First Bar Ever?"} E -->|"Yes"| G["Initialize once from RHS"] E -->|"No"| H["Retain value from Bar N-1"] F -->|"Yes"| I["Initialize once from RHS"] F -->|"No"| J["Retain value — even across intrabar ticks"] D --> K["Value available for this bar only"] G --> L["Value persists to Bar N+1"] H --> L I --> M["Value persists — no tick rollback"] J --> M K --> N["Next Bar"] L --> N M --> N N --> B

2. Standard Variable Declaration (No Keyword)

The most basic form of variable declaration uses the = operator. On every bar execution, the right-hand side expression is evaluated and the result is assigned to the variable. There is no memory of the previous bar's value — the variable is effectively re-created on each bar.

Syntax

//@version=6
indicator("Standard Variable Demo", overlay=true)

// Declared without 'var': re-initialized on EVERY bar
myClose = close          // 'myClose' holds the current bar's closing price
mySum = high + low       // Re-calculated fresh on every bar

plot(myClose, title="Close", color=color.blue)
plot(mySum,   title="H+L",   color=color.orange)

Key Fact: The type of a standard variable is inferred at compile time from its initial assignment. Pine Script is a statically typed language with type inference — you do not need to explicitly write float, int, or bool in most cases.

Type Inference Table

Assignment Example Inferred Type Notes
x = 10 int Integer literal
x = 10.0 float Float literal
x = close float Built-in series is float
x = true bool Boolean literal
x = "hello" string String literal
x = bar_index int Built-in integer series

3. The var Keyword: Persistent State Across Bars

The var keyword instructs the Pine Script runtime to initialize a variable only once — on the very first bar the script processes. After that initial assignment, the variable retains its value from bar to bar. To update it on subsequent bars, you use the := reassignment operator.

This is the correct mechanism for building accumulators, counters, running totals, and state machines in Pine Script.

Mathematical Model

Consider a running cumulative sum $S_n$ of closing prices:

$$S_n = S_{n-1} + C_n$$

where $C_n$ is the closing price on bar $n$. This recurrence relation requires that $S_{n-1}$ be remembered from the previous bar — exactly what var provides.

🔽 [Click to expand] View Full Pine Script Code — var Cumulative Sum Example
//@version=6
indicator("var Keyword Demo — Cumulative Sum", overlay=false)

// 'var' initializes cumulativeSum ONCE on the first processed bar.
// It retains its value across all subsequent bars.
var float cumulativeSum = 0.0

// ':=' is REQUIRED here because cumulativeSum was declared with 'var'.
// We are reassigning a variable from an outer (script-level) scope.
cumulativeSum := cumulativeSum + close

// A counter using 'var' — counts how many bars have been processed
var int barCount = 0
barCount := barCount + 1

// A running maximum using 'var'
var float runningMax = na          // Initialized to 'na' (missing value) on first bar
if na(runningMax)
    runningMax := close            // First bar: set to current close
else
    runningMax := math.max(runningMax, close)  // Subsequent bars: keep the max

// Plot the results
plot(cumulativeSum, title="Cumulative Sum of Close",  color=color.blue,   linewidth=2)
plot(barCount,      title="Bar Count",                color=color.gray,   linewidth=1)
plot(runningMax,    title="Running Maximum of Close", color=color.green,  linewidth=2)

Critical Behavioral Rules for var

Behavior Standard (=) Persistent (var)
Initialization frequency Every bar Once (first processed bar)
Retains previous bar value No Yes
Reassignment operator = :=
Suitable for accumulators No Yes
Suitable for per-bar calculations Yes Yes (with explicit update)

4. The varip Keyword: Intrabar Tick Persistence

varip (variable intrabar persistent) extends the behavior of var with one critical difference: while var variables roll back to their confirmed value at the start of each new real-time bar update (tick), varip variables do NOT roll back between ticks. The value set during one tick is preserved into the next tick of the same bar.

This distinction is only observable on real-time bars. On historical bars, var and varip behave identically.

Tick-Level Behavior Comparison

Scenario var varip
Historical bars Initialized once, persists bar-to-bar Identical to var
Real-time bar, Tick 1 Starts from last confirmed bar value Starts from last confirmed bar value
Real-time bar, Tick 2 Rolls back to last confirmed bar value before re-executing Retains the value set during Tick 1 — NO rollback
Use case Standard bar-level state Tick counters, intrabar event tracking
🔽 [Click to expand] View Full Pine Script Code — varip Tick Counter Example
//@version=6
indicator("varip Keyword Demo — Tick Counter", overlay=false)

// 'varip': initialized once, value persists between intrabar ticks.
// On historical bars, behaves the same as 'var'.
varip int tickCount = 0

// ':=' is required to reassign a varip variable.
// On real-time bars, this increments WITHOUT rolling back between ticks.
tickCount := tickCount + 1

// 'var' version for comparison: this WILL roll back on each tick
// of a real-time bar, so it effectively counts confirmed bars only.
var int barCountVar = 0
barCountVar := barCountVar + 1

// Plot both counters
// On historical data they will look identical.
// On real-time bars, tickCount will grow faster (one increment per tick).
plot(tickCount,   title="Tick Count (varip)", color=color.red,   linewidth=2)
plot(barCountVar, title="Bar Count (var)",    color=color.blue,  linewidth=2)

// Display current values in a table for clarity
var table infoTable = table.new(position.top_right, 2, 3,
     bgcolor=color.white, border_width=1)

if barstate.islast
    table.cell(infoTable, 0, 0, "Variable",   text_color=color.black, text_size=size.small)
    table.cell(infoTable, 1, 0, "Value",       text_color=color.black, text_size=size.small)
    table.cell(infoTable, 0, 1, "tickCount",   text_color=color.red,   text_size=size.small)
    table.cell(infoTable, 1, 1, str.tostring(tickCount),
               text_color=color.red,   text_size=size.small)
    table.cell(infoTable, 0, 2, "barCountVar", text_color=color.blue,  text_size=size.small)
    table.cell(infoTable, 1, 2, str.tostring(barCountVar),
               text_color=color.blue,  text_size=size.small)

5. The := Reassignment Operator — When Is It Required?

A common source of confusion is when to use = versus :=. The rule is precise and unambiguous in Pine Script v6:

  • = is used for the initial declaration of a variable (with or without var/varip), and also for standard per-bar assignments where no prior declaration in an outer scope exists.
  • := is required when you are reassigning a variable that was declared with var or varip, or when modifying a variable that was declared in an outer (enclosing) scope from within an inner block (e.g., inside an if block).
🔽 [Click to expand] View Full Pine Script Code — Assignment Operator Rules
//@version=6
indicator("Assignment Operator Rules Demo", overlay=false)

// --- CORRECT USAGE ---

// 1. Standard declaration: use '='
standardVar = close * 2.0          // Re-initialized every bar — correct

// 2. var declaration: use '=' for initial declaration
var float persistentVar = 0.0      // Initialized ONCE on first bar

// 3. Reassigning a 'var' variable: use ':='
persistentVar := persistentVar + 1.0   // Correct: ':=' for var reassignment

// 4. Reassigning from within an inner scope: use ':='
var float conditionalVar = 0.0
if close > open
    conditionalVar := close        // ':=' required: modifying outer-scope var variable
else
    conditionalVar := open         // ':=' required: same reason

// 5. varip declaration and reassignment
varip int ticksAboveOpen = 0
if close > open
    ticksAboveOpen := ticksAboveOpen + 1   // ':=' required for varip

// --- WHAT HAPPENS WITHOUT ':=' on a var variable ---
// The following would create a NEW local variable shadowing the outer one,
// NOT update the persistent variable. This is a logical error, not always
// a compile error, and is a frequent source of bugs:
//
// var float bugExample = 100.0
// if someCondition
//     bugExample = 999.0   // This creates a LOCAL variable, not updating bugExample!
//                          // Use ':=' to correctly update the outer var.

plot(persistentVar, title="Persistent Counter",    color=color.purple)
plot(conditionalVar, title="Conditional Tracker",  color=color.teal)
plot(ticksAboveOpen, title="Ticks Above Open",     color=color.orange)

6. Initializing Variables with na

na is a special Pine Script literal representing a missing or undefined value. It is not a data type itself. When you initialize a var variable with na, you must explicitly provide a type annotation so the compiler knows what type the variable will eventually hold.

🔽 [Click to expand] View Full Pine Script Code
//@version=6
indicator("na Initialization Demo", overlay=true)

// CORRECT: Type annotation required when initializing with 'na'
var float lastPivotHigh = na       // Compiler knows this is a float
var int   lastPivotBar  = na       // Compiler knows this is an int

// Detect a simple pivot high (price higher than 2 bars before and after)
// Note: This uses a 2-bar lookback, so it lags by 2 bars
bool isPivotHigh = high[2] > high[3] and high[2] > high[1]

if isPivotHigh
    lastPivotHigh := high[2]       // Update with the pivot high price
    lastPivotBar  := bar_index - 2 // Update with the pivot bar index

// Plot a horizontal line at the last detected pivot high
plot(lastPivotHigh, title="Last Pivot High", color=color.red,
     style=plot.style_linebr, linewidth=2)

Important: Without the explicit type annotation (e.g., var float), writing var x = na will cause a compilation error because the compiler cannot infer the type from na alone.

7. Complete Comparison: Standard vs var vs varip

graph TD A["Script Starts Executing"] --> B["Bar N: Execute Script"] B --> C{"Variable Declaration Type?"} C -->|"Standard = "| D["Re-initialize from RHS expression"] C -->|"var"| E{"First Bar Ever?"} C -->|"varip"| F{"First Bar Ever?"} E -->|"Yes"| G["Initialize once from RHS"] E -->|"No"| H["Retain value from Bar N-1"] F -->|"Yes"| I["Initialize once from RHS"] F -->|"No"| J["Retain value — even across intrabar ticks"] D --> K["Value available for this bar only"] G --> L["Value persists to Bar N+1"] H --> L I --> M["Value persists — no tick rollback"] J --> M K --> N["Next Bar"] L --> N M --> N N --> B
Feature Standard (=) var varip
Initialization Every bar Once (first bar) Once (first bar)
Persists bar-to-bar No Yes Yes
Rolls back on real-time tick N/A (re-initialized) Yes (rolls back to confirmed value) No (retains tick value)
Reassignment operator = := :=
Type annotation needed for na init Yes Yes Yes
Typical use case Per-bar calculations Accumulators, state machines Intrabar tick counters

8. Conclusion

  • Standard assignment (=) re-initializes a variable on every bar execution. It has no memory of previous bars and is appropriate for per-bar derived values.
  • var initializes a variable exactly once on the first processed bar and retains its value across all subsequent bars. It is the correct tool for building running totals, counters, state machines, and any calculation requiring cross-bar memory. Reassignment requires :=.
  • varip extends var by additionally preventing rollback between intrabar ticks on real-time bars, making it suitable for tick-level event counting. On historical bars, it is behaviorally identical to var.

Ideas for Script Advancement

  1. State Machine Pattern: Combine var int state = 0 with a series of if/else if blocks and := reassignments to build a multi-state trend-following engine (e.g., states: 0=neutral, 1=uptrend, 2=downtrend, 3=reversal). This pattern cleanly encodes complex sequential logic without nested conditions.
  2. Intrabar Volume Profiler: Use varip float tickVolume = 0.0 combined with barstate.isconfirmed to accumulate tick-level volume within a bar and reset it on bar close, building a real-time intrabar volume distribution tool.

Comments

Popular posts from this blog

Pine Script v6: indicator() vs strategy() — Core Functional Differences Explained

Pine Script v6 plot() Function: Complete Guide to Lines, Histograms, and Circles