Conditionals
if, else if, else and switch in ChartScript, used as statements or as expressions that return a value, and what to keep out of them.
Conditionals let a script do different things on different bars: count a streak only on up bars, pick a color by trend, choose a calculation from a setting. ChartScript has if and switch. Both can be used as statements (do this or that) and as expressions (give me this value or that one).
#if, else if, else
state streak: int = 0
if (close > open) {
streak += 1
} else if (close < open) {
streak = 0
} else {
log.info("doji on bar {0}", bar_index)
}
plot(streak)- The condition goes in parentheses and must be a
bool. A number or a string is not a condition (CS2701), and a value that could benais refused with CS2204. Compare explicitly:if (diff != 0),if (!na(level) && close > level). - The body is the block between
{and}. For a single statement the braces can be left out (if (close > open) ups += 1), but with braces it is easier to add a line later. - You can chain as many
else ifas you need. The first branch whose condition is true runs; the others are skipped. - A comparison involving
nais false, so theelsepath runs on bars where data is missing.
#if as an expression
An if can produce a value. Write it after const x = (or let x =); each branch's value is its last line:
const rsi = ta.rsi(close, 14)
const zone = if (rsi > 70) {
1
} else if (rsi < 30) {
-1
} else {
0
}
plot(zone)When there is no else and no branch matches, the result is na (or false for a bool). Branches can also return several values as a tuple:
const [bodyLow, bodyHigh] = if (close >= open) {
[open, close]
} else {
[close, open]
}
plot(bodyLow)
plot(bodyHigh)For a simple two-way choice the ternary operator is shorter: const zoneColour = close > open ? color.up : color.down.
#switch
switch picks one case out of several. It comes in two forms.
With a subject, each case value is compared with the subject using ==, and default catches everything else:
//@chartscript 1
indicator("Average of your choice", { overlay: true })
const kind = input.string("EMA", "Average", { options: ["SMA", "EMA", "WMA", "Hull"] })
const len = input.int(20, "Length", { minval: 2 })
const sma = ta.sma(close, len)
const ema = ta.ema(close, len)
const wma = ta.wma(close, len)
const hull = ta.hma(close, len)
const avg = switch (kind) {
case "SMA": sma
case "EMA": ema
case "WMA": wma
default: hull
}
plot(avg, "Average", color.accent, 2)The four averages are calculated on every bar before the switch chooses one. That keeps each of them fed with every bar (see stateful functions below); the extra work is tiny.
Without a subject, write switch (true): each case is a condition, and the first true one wins:
const rsi = ta.rsi(close, 14)
const tint = switch (true) {
case rsi > 70: color.down
case rsi < 30: color.up
default: color.muted
}
plot(rsi, "RSI", tint)#How cases work
- Cases never fall through. A case ends where the next
case, thedefaultor the closing}begins; only one case runs. You do not needbreak. - A
breakas the last line of a case is accepted and does nothing, so code written with the habit still works. Abreakanywhere else in a case (outside a loop inside that case) is CS1053: use anif/elseinside the case instead. - Several labels in a row share the body that follows:
case "Sat": case "Sun": …. - Without a
defaultand without a match, aswitchexpression givesna. - A case body can be a single expression after the colon, or several lines; as a value, the case gives its last line.
- The subject can be a number, string, bool, color or enum. With an enum, a misspelled case name is caught when you compile.
state ups: int = 0
state downs: int = 0
switch (true) {
case close > open:
ups += 1
downs = 0
case close < open:
downs += 1
ups = 0
}
plot(ups - downs)#What to keep out of conditions
#Outputs
plot, plotshape, hline, fill, bgcolor, inputs and alert.on describe the script's outputs and settings once, for all bars, so they must be at the top level (CS3007, CS3201 for alert.on):
//@chartscript 1
indicator("Plot in an if", { overlay: true })
if (close > open) {
plot(close)
}Put the condition into the value instead. na hides the plot on the bars where it does not apply:
plot(close > open ? close : na, "Up closes", color.up, { style: plot.style_circles })Drawings (label.new, line.new …), log.* and alert.send are actions, not outputs, so they are fine inside an if.
#Stateful functions
Functions that remember previous bars, such as ta.sma, ta.ema, ta.crossover or x[1] inside a function, only advance on the bars where their line actually runs. Inside an if they skip the bars where the condition was false, which is rarely what you want, so the compiler warns (CS2503):
//@chartscript 1
indicator("Average in a branch")
let avg = 0.0
if (close > open) {
avg = ta.sma(close, 10) // this average only sees up bars
}
plot(avg)Calculate first, then decide:
const sma10 = ta.sma(close, 10)
const avg = close > open ? sma10 : 0.0
plot(avg)If you really do want an average of only the up bars, keep the call in the branch and the warning tells the next reader that it is on purpose.
#Good to know
- Braces make the block; indentation is free. The editor indents with 4 spaces, which keeps nested blocks readable.
- A long condition can go over several lines: break the line after
&&or||, or start the next line with it (see line breaks). ifblocks create a new scope: a variable declared inside does not exist after the block (see Variables).- A deeply nested
ifchain often reads better as aswitch (true).