Functions, arrays and na handling
Lesson 6 · about 12 min
Three more tools and you can read almost any community script: user-defined functions for reuse, arrays for collections that do not fit the one-value-per-bar model, and the handful of built-ins that deal with na. The lesson ends with the compile errors that stop most beginners, and what each one means.
User-defined functions
A single-expression function goes on one line with =>. A longer one puts the body on indented lines, and its last expression is the return value.
//@version=6
indicator("Functions demo", overlay=false)
pct(a, b) => (a - b) / b * 100
rangePct(len) =>
hi = ta.highest(high, len)
lo = ta.lowest(low, len)
(hi - lo) / lo * 100
gap = pct(open, close[1])
plot(gap, "Gap %", style=plot.style_histogram, color=gap >= 0 ? color.green : color.red)
plot(rangePct(20), "20-bar range %", color=color.orange)
Functions must be declared before they are used and cannot call themselves. Parameter types are optional; add them when it clarifies what the function expects. Inside a function, every ta.* call keeps its own history, so calling rangePct(20) and rangePct(50) in the same script gives two independent, correct results.
A function can return several values as a tuple, and you unpack them with square brackets:
hiLo(len) =>
[ta.highest(high, len), ta.lowest(low, len)]
[h20, l20] = hiLo(20)
Functions cannot contain plot(), strategy.entry() or other global-scope-only calls. Compute inside, plot outside.
Arrays
A series gives you one value per bar. An array gives you a list that lives in one variable, which is what you need for "the last N pivot prices" or "every trade's result".
//@version=6
indicator("Array demo", overlay=false)
var closes = array.new<float>()
array.push(closes, close)
if array.size(closes) > 20
array.shift(closes)
avg20 = array.avg(closes)
plot(avg20, "Rolling mean via array", color=color.blue)
plot(ta.sma(close, 20), "SMA 20", color=color.orange)
var is essential here; without it the array would be re-created empty on every bar. Common calls: array.push (append), array.shift (remove the first), array.pop (remove the last), array.get(a, i) (read index i, zero-based), array.set, array.size, array.avg, array.max, array.min, array.sort, array.slice. Version 6 also allows method-style calls on arrays, so closes.push(close) and closes.size() mean the same as the namespaced versions.
Reading past the end of an array is a runtime error that stops the script with a red message on the chart, so guard array.get with a size check.
na, nz and friends
na is the "no value" marker for numbers, strings and colours. Anything arithmetic involving na produces na, and any comparison with it is false. You cannot test for it with ==; use the built-ins:
na(x)returnstruewhenxisna.nz(x)returns0instead ofna;nz(x, y)returnsyinstead.fixnan(x)carries the last non-navalue forward.math.max(a, b)andmath.min(a, b)propagatenain v6, so wrap arguments innz()if either can be missing.
The first bar of a chart has close[1] equal to na, every lookback function is na until it has enough history, and request.security can return na when the other symbol has no bar. Deciding what to do about na is part of every non-trivial script: plot it as a gap (fine), replace it with zero (fine for a histogram, wrong for a price), or carry forward (right for a level, wrong for a count).
Key idea: Functions package logic and keep their own history; arrays hold lists across bars when declared with
var;namust be tested withna()and replaced withnz(), never compared with==.
The errors everyone hits
| Message (abridged) | What it means |
|---|---|
| Mismatched input ... expecting 'end of line' | Indentation or a stray character; check tabs versus spaces |
| Undeclared identifier 'X' | Typo, wrong case, or used before its declaration |
| Cannot call 'plot' in local scope | plot() is inside if or a function; move it out, use a ternary |
| An argument of 'series int' type cannot be used ... | Qualifier error; that parameter needs simple or input |
| Variable 'X' is already defined | You used = where you meant := |
Cannot use := ... undeclared |
You used := before any = |
| The function 'ta.sma' should be called on each calculation... | Warning: a ta.* call is inside a condition; compute it every bar |
| Index out of bounds (runtime) | array.get past the end, or x[n] deeper than max_bars_back |
| Script has too many ... plots | Over the 64-output limit; use labels or lines instead |
The runtime "referencing beyond max bars back" error appears when you use close[500] on a chart with fewer bars loaded, or when a variable is referenced with a large or dynamic offset. Setting max_bars_back=500 in the declaration, or max_bars_back(variable, 500) for one series, fixes it.
Try it: Write
pctFromHigh(len)that returns how far close is below the highest high oflenbars, in percent. Plot it for 20 and 52 bars. Then push each bar's value into an array capped at 100 entries and plot the array's average alongside.
Recap
- User functions use
=>; multi-line bodies return their last expression; they can return tuples and keep independentta.*history. - Arrays are lists that persist across bars when declared with
var; guardarray.getwitharray.size. - Test for missing values with
na(), replace withnz()orfixnan(); comparing tonawith==is always false. plot(),strategy.*orders andalertcondition()must be in the global scope.- Most compile errors are indentation, declaration versus reassignment, or a qualifier mismatch; the message names the exact problem.
See it drawn
Original diagrams for the ideas on this page. Illustrative, not real market data.