Pine Script v6 Basic Syntax Overview: Core Rules, Operators, and Clean Code Patterns

Writing reliable Pine Script v6 code requires a precise understanding of its syntax rules, variable declaration semantics, and operator behaviors — areas where subtle misunderstandings frequently cause compilation errors or incorrect runtime logic. This article provides a rigorous, fact-checked reference for the foundational syntax elements of Pine Script v6, including corrected explanations of var/varip, versioning requirements, data types, and function return semantics.


1. The //@version=6 Annotation

The version annotation must appear on the first line of every Pine Script file. It is not merely a comment — it instructs the compiler to parse and evaluate the script using the specified language version's grammar and built-in library.

//@version=6
indicator("My Indicator", overlay = true)



⚠️ Common Misconception: It is sometimes assumed that omitting //@version=6 causes the compiler to silently fall back to a recent stable version. In practice, the compiler defaults to Pine Script v1 when no annotation is present. Because v1 does not recognize modern declarations such as indicator(), strategy(), or var-style assignments, the script will fail to compile or produce entirely unexpected results. The annotation is therefore mandatory to access any v6 feature.

2. Script Entry Points

Every Pine Script must declare exactly one top-level entry point. The three available entry points determine the script's execution context:

Function Purpose Can Place Orders?
indicator() Overlay or pane-based visual indicator No
strategy() Backtesting and live strategy execution Yes
library() Reusable exported function library No

3. Data Types

Pine Script v6 is a statically typed language with type inference. The compiler resolves types at compile time based on context. The core primitive types are listed below.

Type Description Example Literal
int 64-bit signed integer 42
float 64-bit IEEE 754 double-precision float 3.14
bool Boolean true/false true
string Immutable text sequence "hello"
color RGBA color value color.red
label / line / box Drawing object references label.new(...)

The na literal: na is not a standalone data type. It is a special literal representing a missing or undefined value. It can be assigned to any compatible series variable (e.g., float, int, bool, string, or object references). When a series value is na, most arithmetic operations on it will also produce na, which propagates through calculations. Use na(x) to test for it explicitly.

//@version=6
indicator("na Example")
float myVal = na          // Declares a float series initialized to na
bool isNa = na(myVal)     // true on the first bar where myVal has not been set

4. Variable Declaration and Assignment

Pine Script v6 provides three declaration keywords, each with distinct lifetime and mutability semantics. Understanding these precisely is critical to avoiding logic errors.

4.1 Standard Declaration (=)

A variable declared without a keyword is re-evaluated on every bar. Its value is not retained between bars.

float sma20 = ta.sma(close, 20)  // Recalculated fresh on every bar

4.2 var — Single Initialization, Persistent State

A variable declared with var is initialized exactly once when the script instance first loads (i.e., on the very first historical bar the script processes). After that initial assignment, the variable retains its value across all subsequent bars. It is only updated when your code explicitly reassigns it using :=.

⚠️ Common Misconception: It is incorrect to say that var initializes on bar_index == 0. The initialization occurs when the script instance loads, which corresponds to the first bar in the chart's history. The distinction matters in contexts like security calls or library functions where the execution context may differ.
var int barCount = 0      // Initialized once when the script loads
barCount := barCount + 1  // Explicitly updated on every bar using :=

4.3 varip — Intrabar Persistence (Realtime Only)

A variable declared with varip ("var intrabar persistent") has a more nuanced lifecycle:

  • On historical bars: the variable is reinitialized to its declared value at the start of each bar. It does not persist its value from one historical bar to the next.
  • On the realtime bar: the variable retains its value between successive ticks within that single bar, allowing accumulation of intrabar data.
⚠️ Common Misconception: varip does not automatically update on every tick. It simply preserves whatever value your code assigns to it between ticks on the realtime bar. On historical bars, it behaves like a standard variable that resets each bar.
varip int tickCount = 0   // Resets each historical bar; persists within the realtime bar
tickCount := tickCount + 1

4.4 Assignment Operator Summary

Operator Usage Context
= Initial declaration of a new variable
:= Reassignment of a var/varip variable, or modification of a variable from an outer scope

5. Operators

5.1 Arithmetic Operators

Operator Operation Notes
+AdditionAlso string concatenation
-Subtraction
*Multiplication
/DivisionReturns na on division by zero
%ModuloInteger remainder

5.2 Comparison and Logical Operators

Operator Meaning
==Equal to
!=Not equal to
> / <Greater / Less than
>= / <=Greater / Less than or equal
andLogical AND
orLogical OR
notLogical NOT

5.3 Ternary Operator

The ternary operator provides inline conditional evaluation: $condition\ ?\ value_{true}\ :\ value_{false}$

float signal = close > open ? 1.0 : -1.0  // Bullish bar = 1.0, bearish = -1.0

6. Control Flow

6.1 if / else if / else

Blocks are delimited by indentation. Pine Script uses relative indentation — the compiler requires consistent nesting within a block, but does not mandate a specific number of spaces or tabs. Mixing indentation levels inconsistently within the same block will cause a compilation error.

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

// Determine trend direction based on close vs. SMA
float sma50 = ta.sma(close, 50)

string trend = ""
if close > sma50
    trend := "Uptrend"       // Assigned when close is above SMA
else if close < sma50
    trend := "Downtrend"     // Assigned when close is below SMA
else
    trend := "Neutral"       // Assigned when close equals SMA exactly

// Display the trend label on the last bar
if barstate.islast
    label.new(bar_index, high, trend, style = label.style_label_down)

6.2 for Loop

The for loop iterates over an integer range. The loop variable is local to the loop body.

float total = 0.0
for i = 0 to 9          // Iterates i = 0, 1, 2, ..., 9 (inclusive)
    total := total + close[i]  // Accumulate last 10 closes
float manualSma = total / 10

6.3 while Loop

int n = 0
while n < 5
    n := n + 1  // Increment until condition is false

7. Functions

User-defined functions encapsulate reusable logic. A function returns the value of the last executed statement, or the value of an explicit return statement if one is present. This means the return value is determined by execution flow, not simply by the last line written in the function body.

⚠️ Common Misconception: It is sometimes stated that a Pine Script function always returns "the last expression." More precisely, it returns the value of the last executed statement — which may differ from the last written line if the function contains conditional branches. An explicit return statement can also terminate execution early and specify the return value directly.
🔽 [Click to expand] View Full Pine Script Code
//@version=6
indicator("Function Demo")

// Function: returns the higher of two values
// The last executed statement determines the return value
highestOf(float a, float b) =>
    if a >= b
        a           // Returned when a >= b
    else
        b           // Returned when b > a

// Function: explicit return for early exit
clampedRsi(int length) =>
    float r = ta.rsi(close, length)
    if na(r)
        50.0  // Early return for na case
    r                // Last executed statement when r is valid

plot(highestOf(close, open), title = "Higher of Close/Open")
plot(clampedRsi(14), title = "Clamped RSI")
graph TD A["Script Loads"] --> B["var variables initialized once"] A --> C["varip variables initialized"] B --> D["Historical Bar N"] C --> E["varip resets at start of each historical bar"] D --> F["Bar Execution: if/for/while/functions run"] E --> F F --> G{"Is this the Realtime Bar?"} G -- No --> H["Move to next historical bar"] H --> D G -- Yes --> I["Realtime Tick Received"] I --> J["varip value persists between ticks"] J --> K["var value persists between ticks"] K --> L{"New tick arrives?"} L -- Yes --> I L -- No --> M["Bar Closes: script waits for next bar"]

8. The series Concept

Every variable in Pine Script that references price or indicator data is implicitly a series — an ordered array of values, one per bar. The history-referencing operator [] accesses past values:

$$close[0] = \text{current bar's close},\quad close[1] = \text{previous bar's close},\quad close[n] = n\text{ bars ago}$$
float momentum = close[0] - close[5]  // 5-bar price momentum

9. Common Misconceptions — Quick Reference

Misconception Correct Behavior
Omitting //@version=6 defaults to a recent stable version Defaults to v1; modern functions like indicator() will fail
var initializes on bar_index == 0 var initializes when the script instance first loads, on the first bar it processes
varip persists across all bars like var varip resets each historical bar; persists only within the realtime bar between ticks
na is a data type na is a special literal for missing values, assignable to compatible series types
Functions always return the last written expression Functions return the last executed statement, or an explicit return value
Pine Script requires exactly 4 spaces for indentation Relative indentation is used; consistency within a block is required, not a fixed space count


10. Complete Syntax Reference Script

🔽 [Click to expand] View Full Pine Script Code
//@version=6
// ─────────────────────────────────────────────
// Pine Script v6 Basic Syntax Reference Script
// Demonstrates: var, varip, operators, control
// flow, functions, series, and na handling
// ─────────────────────────────────────────────
indicator("v6 Syntax Reference", overlay = false)

// ── 1. var: initialized once when script loads ──
var int totalBars = 0          // Persists across all bars
totalBars := totalBars + 1     // Incremented on every bar

// ── 2. varip: resets each historical bar ──
varip int intraTicks = 0       // Resets per historical bar; persists within realtime bar
intraTicks := intraTicks + 1

// ── 3. na handling ──
float maybeNa = bar_index < 5 ? na : close  // na for first 5 bars
bool isNaVal = na(maybeNa)                  // Test for na explicitly

// ── 4. Ternary operator ──
float direction = close > open ? 1.0 : -1.0  // 1.0 = bullish, -1.0 = bearish

// ── 5. User-defined function with conditional return ──
highestOf(float a, float b) =>
    if a >= b
        a   // Returned when a is greater or equal
    else
        b   // Returned when b is strictly greater

// ── 6. for loop: manual 10-bar sum ──
float rollingSum = 0.0
for i = 0 to 9
    rollingSum := rollingSum + close[i]  // Accumulate 10 bars
float manualSma10 = rollingSum / 10.0

// ── 7. if/else control flow ──
float sma20 = ta.sma(close, 20)
float trendSignal = 0.0
if close > sma20
    trendSignal := 1.0   // Above SMA: bullish
else if close < sma20
    trendSignal := -1.0  // Below SMA: bearish
// else: trendSignal remains 0.0

// ── 8. Series history reference ──
float momentum5 = close[0] - close[5]  // 5-bar momentum

// ── 9. Plots ──
plot(manualSma10,  title = "Manual SMA10",  color = color.blue)
plot(trendSignal,  title = "Trend Signal",  color = color.orange)
plot(momentum5,    title = "Momentum(5)",   color = color.purple)
plot(totalBars,    title = "Bar Count",     color = color.gray)




11. Conclusion

  • Versioning is non-negotiable: //@version=6 must appear on line 1. Without it, the compiler defaults to v1, making all modern syntax unavailable.
  • Variable lifetime is semantically precise: var initializes once at script load and persists indefinitely; varip resets on each historical bar and persists only within the realtime bar between ticks — these are distinct behaviors with significant runtime implications.
  • Function return values follow execution flow: A Pine Script function returns the value of the last executed statement or an explicit return, not necessarily the last written line — a distinction that matters in branching logic.

Suggested Advancements

  1. Type System Deep Dive: Extend this reference with a dedicated article on Pine Script's type hierarchy, including simple, series, const, and input qualifiers, and how they affect function argument compatibility.
  2. Execution Model Visualization: Build an annotated script that logs var vs. varip values to a table on each bar, providing a live, interactive demonstration of their differing persistence semantics directly in the Pine Editor.

Related Posts

Comments

Popular posts from this blog

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

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

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