How to Use 'for' and 'while' Loops in Pine Script v6: A Complete Technical Guide

Pine Script executes once per bar by default, but many quantitative algorithms require iterating over a range of values or historical data within a single bar's execution context. Understanding how for and while loops work in Pine Script v6 — including their scoping rules, variable update mechanics, and performance implications — is essential for building robust, mathematically sound indicators and strategies.

1. Execution Model: Why Loops Matter in Pine Script

Pine Script's runtime processes bars sequentially from left to right on the chart. On each bar, your script runs top-to-bottom exactly once. Loops allow you to perform multiple iterations within a single bar's execution, enabling calculations like manual moving averages, custom lookback aggregations, or iterative optimization routines.

graph TD A["Bar N Execution Starts"] --> B["Script runs top-to-bottom"] B --> C{"Loop Encountered?"} C -- "for loop" --> D["Iterate over fixed integer range start to end by step"] C -- "while loop" --> E["Evaluate boolean condition"] D --> F["Execute loop body Update outer variables with :="] E -- "true" --> G["Execute loop body Update outer variables with :="] E -- "false" --> H["Exit while loop"] F --> I{"i <= end?"} I -- "yes" --> F I -- "no" --> J["Exit for loop"] G --> K{"Guard limit reached?"} K -- "no" --> E K -- "yes" --> H J --> L["Continue script execution"] H --> L L --> M["Bar N Execution Ends"] M --> N["Move to Bar N+1"]

2. The for Loop: Syntax and Mechanics

The for loop in Pine Script iterates over a fixed integer range. Its syntax is strictly defined as follows:

//@version=6
// for loop basic syntax:
// for  =  to  [by ]
//     

Key rules for the for loop:

  • The loop variable (i by convention) is an implicit local integer — it cannot be declared with var or typed explicitly.
  • The to boundary is inclusive: for i = 0 to 4 iterates 5 times (i = 0, 1, 2, 3, 4).
  • The optional by keyword sets the step increment. Default step is 1. A negative step (e.g., by -1) iterates in reverse.
  • Loops are statements, not expressions. They do not return a value. Only user-defined functions implicitly return the value of their last evaluated expression — loops do not share this behavior. Variables must be declared before the loop and updated inside it.
  • The break keyword exits the loop immediately. The continue keyword skips to the next iteration.

2.1 Practical Example: Manual Simple Moving Average

The standard built-in ta.sma() is optimized internally, but implementing it manually with a for loop demonstrates the core pattern for historical data iteration:

The mathematical definition of a Simple Moving Average over $N$ periods is:

$$\text{SMA}(N) = \frac{1}{N} \sum_{k=0}^{N-1} \text{close}[k]$$

where $\text{close}[k]$ denotes the closing price $k$ bars ago.

🔽 [Click to expand] View Full Pine Script Code — Manual SMA with for Loop
//@version=6
indicator("Manual SMA via for Loop", overlay=true)

// --- Input ---
int length = input.int(14, title="SMA Length", minval=1)

// --- Manual SMA Calculation ---
// IMPORTANT: Declare the accumulator BEFORE the loop.
// Loops are statements and do not return values.
float sumClose = 0.0  // Accumulator initialized to 0 on each bar

// Iterate from 0 (current bar) to length-1 (oldest bar in window)
for i = 0 to length - 1
    // close[i] accesses the closing price 'i' bars ago
    // The [] operator is Pine Script's historical reference operator
    sumClose := sumClose + close[i]  // Accumulate each historical close

// Compute the average after the loop completes
float manualSMA = sumClose / length

// --- Reference: Built-in SMA for verification ---
float builtinSMA = ta.sma(close, length)

// --- Plot both for visual comparison ---
plot(manualSMA, title="Manual SMA", color=color.blue, linewidth=2)
plot(builtinSMA, title="Built-in SMA", color=color.orange, linewidth=1, style=plot.style_circles)

// --- Validation: Flag any divergence greater than floating-point epsilon ---
bool diverged = math.abs(manualSMA - builtinSMA) > 1e-9
bgcolor(diverged ? color.new(color.red, 80) : na, title="Divergence Alert")

3. The while Loop: Syntax and Mechanics

The while loop repeats a block as long as a boolean condition evaluates to true. It is suited for iterative algorithms where the number of iterations is not known in advance (e.g., Newton-Raphson convergence, binary search).

// while loop basic syntax:
// while 
//     

Critical safety rules for while loops:

  • Always guarantee termination. An infinite loop will cause a runtime error: "Script has exceeded the maximum number of operations."
  • Like for loops, while loops are statements, not expressions — they do not return a value. Declare result variables before the loop and update them inside the body.
  • Use a guard counter as a safety mechanism in complex iterative algorithms.
  • break and continue are supported inside while loops.

3.1 Practical Example: Newton-Raphson Square Root Approximation

The Newton-Raphson method for computing $\sqrt{S}$ uses the iterative formula:

$$x_{n+1} = \frac{1}{2}\left(x_n + \frac{S}{x_n}\right)$$

This converges quadratically, making it an ideal while loop use case:

🔽 [Click to expand] View Full Pine Script Code — Newton-Raphson with while Loop
//@version=6
indicator("Newton-Raphson Sqrt via while Loop", overlay=false)

// --- Target value: use the current bar's volume as the input S ---
float S = math.abs(close)  // Ensure S >= 0 for a valid square root

// --- Newton-Raphson Iterative Square Root ---
// Declare result variables BEFORE the loop (loops are statements, not expressions)
float x = S / 2.0          // Initial guess: S/2 is a reasonable starting point
float tolerance = 1e-10    // Convergence threshold
int maxIterations = 100    // Safety guard to prevent infinite loops
int iterCount = 0          // Iteration counter

// Loop until convergence or safety limit is reached
while math.abs(x * x - S) > tolerance and iterCount < maxIterations
    x := (x + S / x) / 2.0  // Apply Newton-Raphson update formula
    iterCount := iterCount + 1  // Increment guard counter

// x now holds the approximated square root of S
float approxSqrt = x

// --- Reference: Built-in math.sqrt for verification ---
float builtinSqrt = math.sqrt(S)

// --- Plot both results ---
plot(approxSqrt, title="Newton-Raphson Sqrt", color=color.purple, linewidth=2)
plot(builtinSqrt, title="Built-in Sqrt", color=color.yellow, linewidth=1)

// --- Plot iteration count to monitor convergence speed ---
plot(iterCount, title="Iterations Used", color=color.gray)

// --- Divergence check ---
bool sqrtDiverged = math.abs(approxSqrt - builtinSqrt) > 1e-6
bgcolor(sqrtDiverged ? color.new(color.red, 80) : na, title="Sqrt Divergence Alert")

4. Comparison: for vs while in Pine Script v6

Property for Loop while Loop
Iteration Control Fixed integer range (start to end) Boolean condition (evaluated each iteration)
Step Control Optional by keyword (default: 1) Manual update inside body
Return Value None (loops are statements) None (loops are statements)
Infinite Loop Risk Low (bounded by range) High if condition never becomes false
Best Use Case Historical lookback, array iteration Convergence algorithms, dynamic search
Supports break/continue Yes Yes
Loop Variable Scope Local to loop block No implicit loop variable

5. Variable Scoping and the := Operator Inside Loops

A common source of confusion for developers coming from Python or JavaScript is Pine Script's variable scoping rules inside loops. The key principle is:

Variables declared outside a loop with float x = ... or int x = ... can be updated inside the loop using the := reassignment operator.

This is because := is required when modifying a variable that already exists in an outer scope. The = operator inside a loop block would create a new local variable shadowing the outer one, which is almost never the intended behavior for accumulators.

🔽 [Click to expand] View Full Pine Script Code — Scoping Demonstration
//@version=6
indicator("Loop Scoping Demo", overlay=false)

int length = 5

// --- CORRECT PATTERN ---
// Declare accumulator in outer scope, update with := inside loop
float correctSum = 0.0  // Outer scope declaration
for i = 0 to length - 1
    correctSum := correctSum + close[i]  // := updates the outer variable
float correctAvg = correctSum / length

// --- INCORRECT PATTERN (illustrative — do not use) ---
// Using = inside the loop creates a NEW local variable each iteration.
// The outer 'wrongSum' is never updated.
// float wrongSum = 0.0
// for i = 0 to length - 1
//     float wrongSum = wrongSum + close[i]  // This shadows the outer variable!
// float wrongAvg = wrongSum / length  // wrongSum is still 0.0 here!

// --- Plot the correct result ---
plot(correctAvg, title="Correct Loop Average", color=color.teal)

// --- Clarifying Note ---
// In Pine Script, ONLY user-defined functions implicitly return the value
// of their last evaluated expression. Loops (for/while) are statements
// and do NOT return values. This differs from languages like Rust or
// Ruby where blocks can be expressions. Always use the declare-then-update
// pattern shown above.

6. Performance Considerations: Loop Complexity and Bar Limits

Pine Script enforces a maximum operations limit per bar. Nested loops or loops with large iteration counts can trigger a runtime error. The computational complexity of nested loops is:

$$O(\text{outer\_iterations} \times \text{inner\_iterations})$$

For example, a double loop with both bounds set to 500 performs $500 \times 500 = 250{,}000$ iterations per bar, which is likely to exceed Pine Script's execution limits on historical data with many bars.

Best practices for loop performance:

  • Prefer built-in functions (ta.sma(), ta.ema(), math.sqrt()) over manual loops when available — they are implemented in native code and are significantly faster.
  • Use arrays (array.push(), array.sum()) for batch operations instead of manual accumulation loops where possible.
  • Cache intermediate results using var variables to avoid recomputing values that don't change between bars.
  • Set conservative maxIterations guards in while loops to prevent runaway execution.

6.1 Example: Using var to Cache Loop Results

🔽 [Click to expand] View Full Pine Script Code — Caching with var
//@version=6
indicator("Loop Result Caching with var", overlay=false)

int length = 20

// var initializes ONCE on the first processed bar and retains its value
// across subsequent bars. This is useful for storing state that only
// needs to be recomputed under specific conditions.
var float cachedResult = na
var int lastComputedBar = -1

// Only recompute the manual sum every 'length' bars to reduce operations
// (This is a demonstration of caching logic, not a production SMA)
bool shouldRecompute = (bar_index - lastComputedBar) >= length or na(cachedResult)

if shouldRecompute
    float tempSum = 0.0  // Declare accumulator before the loop
    for i = 0 to length - 1
        tempSum := tempSum + close[i]  // Update accumulator inside loop
    cachedResult := tempSum / length   // Store result in var variable
    lastComputedBar := bar_index       // Record when we last computed

plot(cachedResult, title="Cached Manual Average", color=color.orange)
plot(ta.sma(close, length), title="Reference SMA", color=color.blue)

7. Conclusion

  • Loops are statements, not expressions: Both for and while loops in Pine Script v6 do not return values. Variables must be declared in the outer scope and updated inside the loop using the := reassignment operator. This is distinct from user-defined functions, which implicitly return the value of their last evaluated expression.
  • Scoping discipline is mandatory: Using = inside a loop creates a new local variable, silently shadowing the outer accumulator. Always use := to update variables declared outside the loop body.
  • Performance awareness is critical: Pine Script enforces per-bar operation limits. Prefer native built-in functions over manual loops, use var to cache results, and always include guard counters in while loops to prevent runtime errors from infinite iteration.

Ideas for Script Advancement

  1. Array-Backed Rolling Statistics: Combine for loops with Pine Script's array type to implement rolling percentile calculations (e.g., median, 25th/75th percentile) that are not available as built-in functions, using array.sort() and index-based access.
  2. Adaptive Convergence Loops: Build an adaptive RSI smoother using a while loop that iterates until the smoothed value converges within a dynamic tolerance based on recent volatility (e.g., ATR-normalized threshold), creating a volatility-aware momentum indicator.

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