Operators
Arithmetic, comparison, logical, nullish, ternary and history operators, int division, how na flows through them, line breaks and precedence.
Operators combine values: close - open, rsi > 70, up && !down. ChartScript has a small, conventional set. The parts worth reading closely are division, which keeps fractions even between two whole numbers, and how na behaves, which decides what your script does on bars with missing data.
#Arithmetic
| Operator | Meaning | Example |
|---|---|---|
+ | add; also joins two strings | high + low, "BTC" + "USDT" |
- | subtract; in front of a value, negate | close - open, -change |
* | multiply | atr * 2 |
/ | divide | (high + low) / 2 |
% | remainder | bar_index % 5 |
When both sides are int, the result is an int where it can be (see below); when either side is a float, the result is a float. There is no power operator: use math.pow(x, 2).
#Division and remainder
Division never throws away a fraction by surprise:
int / intgives the exact result.7 / 2is3.5(a float);4 / 2is2(an int, because the result is whole).- If you want whole-number division, say so:
int(7 / 2)is3(toward zero),math.floor(-7 / 2)is-4(down). %takes the sign of the left side:7 % 3is1,-7 % 3is-1.- Dividing by zero,
x % 0, and any result too large to represent givena, never infinity.
Because bar_index / 2 may have a fraction, it is a float, and storing it in an int variable is refused:
//@chartscript 1
indicator("Whole bars")
const halfway: int = last_bar_index / 2
plot(halfway)Write const halfway: int = int(last_bar_index / 2). The same rule is why /= needs a float variable.
//@chartscript 1
indicator("Every fifth bar", { overlay: true })
const every = input.int(5, "Every N bars", { minval: 1 })
const isNth = bar_index % every == 0
const blockNo = int(bar_index / every) // whole-number division
const shade = blockNo % 2 == 0 ? color.new(color.blue, 92) : color.new(color.orange, 92)
bgcolor(shade)
plotshape(isNth, "Nth bar", shape.circle, location.belowbar, color.accent, { size: size.tiny })#Comparison
| Operator | Meaning |
|---|---|
== or === | equal |
!= or !== | not equal |
< <= | less than, less than or equal |
> >= | greater than, greater than or equal |
Comparisons give a bool. Numbers, strings, colors, bools and enum values can be compared for equality; < and friends work on numbers. Float comparison is exact, so prefer a tolerance for computed values (math.abs(a - b) < 1e-9).
=== and !== mean exactly the same as == and !=. ChartScript never converts a value to another type to compare it, so there is nothing for the triple form to prevent; it is accepted so that the habit does no harm. The editor writes == and !=.
Comparisons do not chain the mathematical way: a < b < c compares a < b (a bool) with c and fails. Write a < b && b < c.
#Logical
| Operator | Meaning |
|---|---|
&& | true when both sides are true |
|| | true when either side is true |
! | flips true and false |
Both sides must be bool, and the result is always a bool: a number is not a condition (CS2204 when the value could be na, CS2701 for other types). Write count != 0, not count.
&& and || stop as soon as the answer is known: in a && b, b is not evaluated when a is false. That makes guards like !na(prev) && close > prev safe.
The words and, or and not are not operators. Using one between two values is a syntax error (CS1030) that names the operator to use:
//@chartscript 1
indicator("Words")
const strong = close > open and volume > volume[1]
plot(strong ? 1 : 0)The single & and | do not exist either (CS1020); there are no bitwise operators.
#The nullish operator ??
a ?? b is a, or b when a is na. It is the short form of nz(a, b) and works for any type:
const prevHigh = ta.highest(high, 20)[1] ?? high // high on the first bars, where there is no previous value
const ratio = volume / ta.sma(volume, 20)[1] ?? 1.0 // 1.0 while the average is not ready
plot(prevHigh, "Previous 20-bar high")?? binds more loosely than the arithmetic and comparison operators: a ?? b + 1 is a ?? (b + 1), and x / y ?? 0 is (x / y) ?? 0. Add parentheses when you mean something else.
#The ternary operator ?:
condition ? a : b is a when the condition is true and b otherwise. It is the quickest way to choose a value per bar:
const barColour = close >= open ? color.up : color.down
const dir = close > open ? 1 : close < open ? -1 : 0 // nests to the right
plot(close, "Close", barColour)Both branches must have compatible types (an int and a float give a float; a number and a string do not mix). na (or its alias null) is allowed in a branch and takes the type of the other one: close > open ? close : na. A branch that would make a bool na is refused (CS2204), since a bool cannot be na.
Only the chosen branch runs. That matters for stateful functions: cond ? ta.sma(close, 5) : 0 only feeds the average on bars where cond is true, and the compiler warns (CS2503). Compute the average first, then choose.
#The history operator [ ]
x[n] reads the value of x from n bars ago, for any expression: close[1], ta.rsi(close, 14)[3], (high - low)[1]. The offset must be 0 or more (CS2302 for a negative constant). Before the first bar the result is na. See Execution model for buffer sizes.
The [ must be on the same line as the value. A line that starts with [ begins a new statement, so x on one line and [1] on the next is not a history read.
On arrays, square brackets do not index: arr[1] is the array as it was on the previous bar. Use arr.get(1) for the element at position 1.
#Assignment operators
= changes a variable, and += -= *= /= %= change it with an operation; x++ and x-- add or subtract 1. Declaring is done with const, let or state in front of the name. Assignments are statements, not expressions: you cannot write a = b = 0, and x++ cannot appear inside an expression (CS1054). See Variables.
#Operators and na
| Expression | Result when an operand is na |
|---|---|
a + b, a - b, a * b, a / b, a % b, -a | na |
a < b, a == b, a != b, … | false |
a && b, a || b, !a | cannot happen: bools are never na |
a ?? b | b when a is na |
c ? a : b | follows the condition; the chosen side may be na |
So if (close > ta.sma(close, 200)) is simply false on the first 199 bars. Comparing directly with na (or null) is refused because it would always be false (CS2203); use na(x).
#Long expressions and line breaks
Semicolons are optional: a line break ends a statement. A long expression can go on over several lines when the line break comes where the expression is clearly not finished:
- inside
( … )or[ … ], or inside an options object{ … }; - after an operator, a comma,
.,?,:or=at the end of a line; - before a line that starts with
.,?,:,&&,||,??,*,/,%, a comparison, orelse.
const score = (close > open ? 1 : 0) +
(volume > ta.sma(volume, 20) ? 1 : 0) +
(ta.rsi(close, 14) > 50 ? 1 : 0)
const strong = score >= 2
&& close > ta.ema(close, 50)
plot(strong ? score : 0)A line that starts with (, [, +, - or ! always begins a new statement. A line starting with + or - would then do nothing, so it is an error (CS1055): put the operator at the end of the previous line, as above, or wrap the whole expression in parentheses.
Two statements on one line need a ; between them (CS1057).
#Precedence
Operators higher in the table bind tighter. Operators on the same row are evaluated left to right, except ?:, which groups from the right.
| Level | Operators |
|---|---|
| 1 (tightest) | . member, ( ) call, [ ] history |
| 2 | unary ! - + |
| 3 | * / % |
| 4 | + - |
| 5 | < <= > >= |
| 6 | == === != !== |
| 7 | && |
| 8 | || |
| 9 | ?? |
| 10 (loosest) | ? : |
Some consequences:
a + b * cisa + (b * c).-close[1]is-(close[1]): history binds before the minus.!a == bis(!a) == b, because!binds tightly. Write!(a == b)ora != bto negate a comparison.a || b && cisa || (b && c).a || b ? x : yis(a || b) ? x : y.
When in doubt, add parentheses. They cost nothing and make the intent obvious to the next reader.