Skip to content
GetProfitable
Search

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 series value cannot be handed to a parameter that needs simple, 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.ema with ta.sma, then by replacing len with input.int(20, "Length"). Read the tooltip on ta.ema and find the word simple in it.

Recap

  • Types are int, float, bool, string, color and objects; the compiler infers them, and bools cannot be na in v6.
  • Every variable is a time series; x[n] reads the value n bars back, and it is na when 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.ema and request.security require simple arguments; 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.

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.
One daily candle broken into four six-hour candlesA tall daily candle on the left and the four six-hour candles that make it up on the right, with dashed lines linking the day's open to the first candle and the day's close to the last.ONE DAILY CANDLEFOUR 6-HOUR CANDLEScloseopenhighlow=00:0006:0012:0018:00one dayThe same trading, summed up in one bar or spelled out in four.
How timeframes stack up. A daily candle is not different data, only coarser data: it opens where the first six-hour candle opened, closes where the last one closed, and its wicks reach the highest and lowest prices any of the four touched.