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.
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 (
iby convention) is an implicit local integer — it cannot be declared withvaror typed explicitly. - The
toboundary is inclusive:for i = 0 to 4iterates 5 times (i = 0, 1, 2, 3, 4). - The optional
bykeyword 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
breakkeyword exits the loop immediately. Thecontinuekeyword 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
forloops,whileloops 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.
breakandcontinueare supported insidewhileloops.
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 withfloat x = ...orint 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
varvariables to avoid recomputing values that don't change between bars. - Set conservative
maxIterationsguards inwhileloops 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
forandwhileloops 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
varto cache results, and always include guard counters inwhileloops to prevent runtime errors from infinite iteration.
Ideas for Script Advancement
- Array-Backed Rolling Statistics: Combine
forloops with Pine Script'sarraytype to implement rolling percentile calculations (e.g., median, 25th/75th percentile) that are not available as built-in functions, usingarray.sort()and index-based access. - Adaptive Convergence Loops: Build an adaptive RSI smoother using a
whileloop 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.
Comments
Post a Comment