Variables
Declaring variables with const and let, changing them with =, keeping values across bars with state and live state, tuples, scope and once blocks.
A variable gives a name to a value so you can use it again. In ChartScript there are two different operations: declaring a variable, with const or let in front, and changing an existing one, with a plain =. Keeping them apart is the single most useful habit when you start, because the compiler uses the difference to catch typos and accidental shadowing.
#Declaring with const and let
const mid = (high + low) / 2
const len = input.int(20, "Length")
const level: float = na // with a type, needed when the value is na
plot(mid)constdeclares a variable you will not change. This is the usual choice: most values in a script are computed once per bar and only read after that.letdeclares a variable you will change later in the same bar, for example inside anif.
Both are declared again on every bar the line runs, starting from their initial value; that is what makes mid follow the price. A declaration's value has history (mid[1] is the previous bar's value; see Execution model). To keep a value from one bar to the next, use state.
A type annotation goes after the name, with a colon: let total: float = 0. You need one when the starting value does not say the type, as with na. A let with a type may leave out the value: let level: float starts as na.
Declaring the same name twice in one block is an error (CS2002). If you meant to change it, drop the const / let on the second line (and declare the first one with let).
//@chartscript 1
indicator("Declared twice")
const signal = 0
const signal = 1
plot(signal)#Reassigning with = and compound operators
A plain = gives an existing variable a new value. The compound forms are shortcuts:
| Write | Same as |
|---|---|
x = 5 | set x to 5 |
x += 2 | x = x + 2 |
x -= 2 | x = x - 2 |
x *= 2 | x = x * 2 |
x /= 2 | x = x / 2 (needs a float variable) |
x %= 2 | x = x % 2 |
x++ | x += 1 |
x-- | x -= 1 |
x++ and x-- are statements of their own. They cannot be used inside an expression: y = x++ is CS1054.
The new value must fit the variable's type: an int variable cannot take a float. Because dividing can produce a fraction, /= needs a float variable; declare it as let total: float = 0 (or let total = 0.0) if you plan to divide it.
Only variables declared with let, state or live state can be changed. Assigning to a const is CS2404, and the quick fix turns the const into let. Built-in values such as close and function parameters cannot be changed at all (also CS2404).
Assigning to a name that was never declared is CS2401. The quick fix "Declare with let" adds the let for you; if the name is a typo, correct it instead.
//@chartscript 1
indicator("Never declared")
streak = streak + 1
plot(streak)#Shadowing
A declaration inside a block (an if, a loop) with the same name as a variable outside creates a new variable that hides the outer one until the block ends. That is almost always a mistake: the const or let should not be there. The compiler warns (CS2402) and offers the fix:
//@chartscript 1
indicator("Shadowed")
const trend = 0
if (close > open) {
const trend = 1 // a new trend inside the if; the outer one stays 0
}
plot(trend)What this code meant is let trend = 0 at the top and trend = 1 inside the if; the plot then shows 1 on up bars.
#state: keep a value across bars
A const or let starts fresh on every bar. Declare the variable with state instead and the starting value is used only once, on the first bar the line runs; after that, the variable keeps whatever you last assigned:
//@chartscript 1
indicator("Running high since start", { overlay: true })
state runHigh: float = na
runHigh = na(runHigh) ? high : math.max(runHigh, high)
plot(runHigh, "Running high", color.up, 2)Use state for counters, running totals, the last value of something, and for arrays, maps and drawings you want to build up over time. Without state, const levels = array.new<float>() gives a new empty array on every bar.
During the live bar, state variables are rolled back before each update, so one bar always counts once. Execution model explains the details.
var does not exist in ChartScript (CS1050): write state for a value kept between bars, or let for an ordinary variable. The quick fixes offer both.
#live state
live state works like state, but its value is not rolled back between the live updates of a bar. Use it for things that really are about updates, such as counting them or remembering the previous update's price:
live state lastPrice: float = na
live state upTicks: int = 0
if (!na(lastPrice) && close > lastPrice) {
upTicks += 1
}
lastPrice = close
plot(upTicks, "Up updates since start")On history every bar runs once, so a live state variable counts bars there; only the live bar shows the extra updates. This means the same chart can show different values on a bar depending on whether you watched it live. Avoid live state in anything you want to compare with history, such as alert conditions.
varip is not a ChartScript word: it gives CS1050 with the quick fix "Use live state".
//@chartscript 1
indicator("Updates on the live bar")
live state updates: int = 0
updates += 1
plot(updates)#Constants
A const whose value is built only from literals and other such constants is known when the script is compiled. You can use it wherever ChartScript needs a fixed value, such as the default of an input or the title of an input group:
const FAST: int = 9
const SLOW: int = 21
const GROUP: string = "Averages"
const fastLen = input.int(FAST, "Fast", { group: GROUP })
const slowLen = input.int(SLOW, "Slow", { group: GROUP })
plot(ta.ema(close, fastLen) - ta.ema(close, slowLen))Constants are useful for numbers you repeat and for group titles of inputs. Writing their names in capitals is a convention, not a rule. In a library, export const makes a constant available to scripts that import it (Libraries).
#Tuples
A function can return several values at once (see Functions). Unpack them with square brackets; use _ for a value you do not need:
const [macdLine, signalLine, hist] = ta.macd(close, 12, 26, 9)
const [_, upper, lower] = ta.bb(close, 20, 2)
plot(hist)The number of names must match the number of values (CS2012). Tuple declarations always declare new variables; to update existing ones, unpack into new names and assign them.
#Scope
A variable exists from its declaration to the end of the block it was declared in. A block is the part between { and }: the body of an if, else, loop or function, or a switch case.
//@chartscript 1
indicator("Out of scope")
if (close > open) {
const gain = close - open
}
plot(gain) // gain only exists inside the ifDeclare the variable with let before the block and assign it inside:
let gain = 0.0
if (close > open) {
gain = close - open
}
plot(gain)Variables declared at the top level are global. Functions can read them, but can change them only if they were declared with state or live state (CS2403). Function parameters cannot be reassigned (CS2404); copy one into a let first.
#once
A once block runs on the first bar where its condition is true, and never again. Without a condition (once { … }) it runs on the first bar.
//@chartscript 1
indicator("First big move", { overlay: true })
state firstBigMoveAt: float = na
const bigMove = high - low > 1.5 * ta.atr(14)[1]
once (bigMove) {
firstBigMoveAt = close
}
plot(firstBigMoveAt, "Close of the first big move", color.orange, 2)The "already ran" flag behaves like a state variable: if the condition first comes true during a live update, and the next update takes it back, the block can run again on the next update. On closed bars it runs exactly once. once is only a keyword at the start of such a block, so you can still use once as an ordinary variable name elsewhere (though it is clearer not to). The same goes for state and live.
#Common mistakes
constorletwhere a change was meant. Inside a block it silently creates a new variable (CS2402 warns); at the same level it fails (CS2002). To change a variable, write justx = ….- Changing a
const. Declare it withletif it has to change (CS2404). - Forgetting
stateon an accumulator.let count = 0followed bycount += 1is 1 on every bar. state x = na. Give it a type:state x: float = na(CS2201).- Writing
varorvarip. Usestate/let, orlive state(CS1050). - Changing a global from a function. Declare the global with
state, or better, return the new value and assign it at the top level.