Skip to content

Operators ​

Why does a comparison, a division or close[1] give a value you didn't expect? Four rules account for most surprises. Two floats within 1e-10 of each other are equal. A division by zero gives an infinity, which na() reads as na. A na makes every comparison false. And x[k] reads the value k bars back, which is na before the chart has that many bars.

pine
//@version=6
indicator("Body as a share of the range")
float span = high - low             // 0 on a bar where high equals low
float share = (close - open) / span // 0 / 0 there, which is na
plot(na(share) ? 0 : share, "Share") // na() catches it, and an infinity

The operators reference lists every operator and the order they bind in. This page explains the rules behind them.

When two numbers are equal ​

==, !=, <, >, <= and >= treat two floats less than 1e-10 apart as equal. So 0.1 + 0.2 == 0.3 is true, though the two floats differ in their last bits, and x > y is false when x is above y by less than 1e-10. This is TradingView's rule too.

The margin is a fixed 1e-10, whatever the size of the numbers. Large numbers are untouched by it: 1e15 == 1e15 + 1 is false. Tiny ones are swallowed by it. A series whose values sit near 1e-11, such as a price ratio of two very different assets, can read as flat, with every bar equal to the one before:

pine
//@version=6
indicator("Tiny values read flat")
float tiny = close / 1e15  // around 1e-11
int up = int(ta.cum(close > close[1] ? 1 : 0))
int tinyUp = int(ta.cum(tiny > tiny[1] ? 1 : 0)) // stays 0
int scaledUp = int(ta.cum(tiny * 1e15 > tiny[1] * 1e15 ? 1 : 0))
plot(up, "Up bars")
plot(tinyUp, "Up bars, tiny")
plot(scaledUp, "Up bars, scaled back")

Scale such values up before you compare them, as the last line does. Functions compare differently from operators: array.includes and map keys compare floats exactly, so a key of 0.1 + 0.2 computed at run time isn't the key 0.3. See array equality and map keys.

Dividing ​

/ keeps the fraction, between two ints too: 7 / 2 is 3.5. Wrap it in int() or math.floor() for 3. % gives the remainder with the left side's sign, so -7 % 3 is -1, and it works on floats: 7.5 % 2 is 1.5.

A number divided by zero is an infinity: positive for a positive number, negative for a negative one. Zero divided by zero is na. A literal division by zero, such as close / 0, is refused in strict Pine, as TradingView refuses it at compile time; in AmenoScript it runs.

An infinity then travels on. Some places treat it as a number and some as na:

WhereAn infinity reads as
na(), nz(), bool()na: na(x) is true, nz(x, 0) is 0, bool(x) is false
math.roundna
==, !=false, against anything, itself included
<, >, <=, >=its sign: positive infinity is above every number and negative infinity below. Against itself all four are false
+, -, *, /, int(), math.abs, math.max, math.min, math.sign, fixnana number, kept as an infinity. Times 0, or minus itself, it is na
A ta.* window, and array.sum, avg, min, max and the other array statisticsa missing value, skipped as na is
plot()nothing drawn: any value past 1e100 draws as na
str.tostringNaN. See what na and an infinity print

So guard the denominator, or test the result with na(), as the example at the top does. na() catches both the infinity and the na.

na in an expression ​

Arithmetic with na on either side gives na. Every comparison with na on either side is false, so close > na is false and so is na != na. A condition that reads na takes the else way, and a ternary on it picks its second value. Test for na with na(x), never with x == na: see na.

and, or and not ​

and and or stop as soon as the left side decides. In false and f() and true or f(), f never runs, so whatever it would change stays as it was. Put a cheap test, or one that guards the next, on the left: not na(x) and x > 0.

A bool is never na, so wherever and, or or not reads a missing bool, it reads false. See a bool is never na.

The ternary ​

cond ? a : b is a where cond is true and b where it isn't. Only the value it picks runs. That matters for a ta.* call written in one of the two: it runs only on the bars that pick it, so its history has gaps and it reads differently from the same call on every bar. Call it before the ternary and pick between the values. See State in functions and branches.

Reading back with x[k] ​

x[k] is the value x had k bars ago: close[1] is the last bar's close and close[0] this bar's. It works on any series, a calculation included, so ta.sma(close, 20)[1] is the last bar's average and (high - low)[1] the last bar's range.

  • Before there are k bars, x[k] is na. close[1] is na on the first bar, and so is anything built from it.
  • It counts bars, not changes. A variable has a value on every bar, whether or not a line changed it there, and x[1] reads the value it held at the end of the bar before.
  • A fraction in k is cut toward zero: close[7.5] is close[7].
  • A negative k reads na here, since there is no later bar to read. TradingView stops the script there when it runs, so keep k at 0 or more.
  • A literal's history, such as 5[1], reads 5 here from the second bar on. TradingView refuses it when it compiles: read back a name or an expression.
  • An object's field is read back through the object: (p[1]).price, not p.price[1]. TradingView refuses the second; this engine runs it and reads na.

How far back a script can read has no limit here: max_bars_back does nothing, and close[5000] reads wherever the chart has 5,000 bars before it. Inside a function, history follows the function's calls, which State in functions and branches explains.

Which way operators bind ​

* binds before +, comparisons before and, and and before or, as in most languages; the precedence table has the full order.

== binds as tightly as < here

TradingView binds == and != after <, >, <= and >=, so a > b == c > d compares two conditions there. This engine reads it as ((a > b) == c) > d, which gives another answer. Put each comparison in parentheses: (a > b) == (c > d).

Traps ​

  • Dividing by a range or a volume. Either can be 0 on a quiet bar. Test the result with na().
  • Comparing tiny values. Under 1e-10 apart reads as equal: scale them first.
  • x == na. It is never true. Use na(x).
  • close[1] on the first bar. It is na, and so is every comparison with it.
  • A ta.* call inside a ternary. It sees only the bars that picked it.
  • a > b == c > d. Parenthesise each comparison.

See also ​

AmenoScript is compatible with Pine Script v6. Pine Script is a trademark of TradingView, Inc., which has no part in AmenoScript.