Skip to content
GetProfitable
Search

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) returns true when x is na.
  • nz(x) returns 0 instead of na; nz(x, y) returns y instead.
  • fixnan(x) carries the last non-na value forward.
  • math.max(a, b) and math.min(a, b) propagate na in v6, so wrap arguments in nz() 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; na must be tested with na() and replaced with nz(), 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 of len bars, 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 independent ta.* history.
  • Arrays are lists that persist across bars when declared with var; guard array.get with array.size.
  • Test for missing values with na(), replace with nz() or fixnan(); comparing to na with == is always false.
  • plot(), strategy.* orders and alertcondition() 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.

Payoff of a long call at expiryA flat loss equal to the premium below the strike, turning upward at 45 degrees above it.Profit / loss per share08595115125Strike 105Max loss 3 — the premium paidBreakeven 108Profit keeps growingUnderlying price at expiry
Buying a call: payoff at expiry. A 105-strike call bought for 3 loses that whole 3 if the price finishes at or below 105, breaks even at 108, then gains a dollar for every dollar higher. The loss is capped at the premium; the upside is not capped.

Finished this module? Take the module quiz.