Types, and series versus simple
Lesson 4 · about 11 min
Pine has a small set of types and one idea layered on top of them that no other mainstream language has: every value also carries a qualifier that says when it is known. Most compile errors that make no sense at first are qualifier errors, so this lesson is worth reading slowly.
The types
| Type | Examples |
|---|---|
int |
20, bar_index, strategy.closedtrades |
float |
close, 1.5, ta.atr(14) |
bool |
true, close > open |
string |
"EMA", syminfo.ticker |
color |
color.red, color.new(color.blue, 50) |
| objects | label, line, table, box, arrays, maps, user-defined types |
You rarely write the type name because the compiler infers it from the value on the right of =. Writing it is allowed and useful in two places: when you declare something as na and the compiler cannot guess (float stopPrice = na), and when you want a function argument checked (f(float x) => ...).
Ints promote to floats automatically. Nothing else converts silently: "12" + 1 is an error, and so is plot(true). Use str.tostring() to turn numbers into text and int(x) or math.round(x) to go from float to int.
Version 6 changed one thing that catches people: a bool can no longer be na. Comparisons involving na values evaluate to false, and var bool flag = na will not compile. Initialise booleans to true or false.
Everything is a time series
When you write sma = ta.sma(close, 20), sma is not one number. It is a series with one value per bar, and the script sees the value for the bar it is currently processing. The history-referencing operator [] reaches back: sma[1] is the previous bar's value and close[5] is five bars ago. On the first bar, close[1] is na because there is no earlier bar.
//@version=6
indicator("Series demo", overlay=false)
change1 = close - close[1]
up3 = close > close[1] and close[1] > close[2] and close[2] > close[3]
plot(change1, "1-bar change", style=plot.style_histogram, color=change1 >= 0 ? color.green : color.red)
plotchar(up3, "Three up closes", char="▲", location=location.top, color=color.green)
ta.change(close) is a built-in for the first line, and ta.rising(close, 3) for the second, but writing them by hand once shows what the operator does.
The qualifiers
Every value is one of, from most to least restrictive:
- const: a literal known when you write the code.
20,"EMA",color.red. - input: known when the script is added to the chart, from an
input.*call. Fixed for the whole run. - simple: known before the first bar and constant for all bars.
syminfo.mintick,timeframe.period, or anything computed only from const and input values. - series: can differ from bar to bar.
close,ta.sma(close, 20),bar_index, and anything computed from a series.
The rule that matters: some built-in parameters require an argument no weaker than simple. ta.ema(source, length) needs a simple int length. request.security() needs a simple string timeframe. Try this:
//@version=6
indicator("Qualifier error demo", overlay=true)
len = bar_index % 2 == 0 ? 20 : 50 // series int: changes per bar
plot(ta.ema(close, len)) // error
The error reads roughly: "Cannot call 'ta.ema' with argument 'length'='len'. An argument of 'series int' type cannot be used because the function requires 'simple int'." Once you know the vocabulary that sentence is precise: you fed a per-bar value where a fixed value was required. ta.sma happens to accept a series length; ta.ema and ta.rma do not, because their recursive formulas need a fixed length to be well defined.
Key idea: The type says what a value is; the qualifier says when it is known. A
seriesvalue cannot be handed to a parameter that needssimple, and the compiler's error message tells you exactly which one you tripped.
Reading types in the reference
Every entry in the reference manual lists parameters with both parts, for example length (simple int) or source (series int/float). When a function refuses your argument, check that entry. If the parameter says simple and your value comes from an input.* call, you are fine; input values count as simple. If it comes from a calculation on close, it is series and you need another approach, usually computing the value with a function that accepts series or restructuring so the length is an input.
Declaring with an explicit type
//@version=6
indicator("Typed declarations", overlay=true)
int length = 20
float basis = ta.sma(close, length)
bool above = close > basis
string note = above ? "above" : "below"
color c = above ? color.green : color.red
plot(basis, "Basis", color=c)
plotchar(above, "Above", char="•", location=location.top, color=c)
This compiles identically to the untyped version. The explicit types are documentation for the next person, which is usually you in three months.
Try it: Take the "Qualifier error demo" and fix it two ways: first by replacing
ta.emawithta.sma, then by replacinglenwithinput.int(20, "Length"). Read the tooltip onta.emaand find the wordsimplein it.
Recap
- Types are int, float, bool, string, color and objects; the compiler infers them, and bools cannot be
nain v6. - Every variable is a time series;
x[n]reads the value n bars back, and it isnawhen there is no such bar. - Qualifiers run const, input, simple, series; a value keeps the weakest qualifier of anything it was computed from.
- Functions like
ta.emaandrequest.securityrequiresimplearguments; a per-bar value there is a compile error. - Inputs count as simple, which is why lengths and timeframes normally come from
input.*.
See it drawn
Original diagrams for the ideas on this page. Illustrative, not real market data.