Loops
for, for…of over arrays and maps, the range loop, while, break and continue in ChartScript, loops as expressions, and the per-bar loop limits.
Most of the time you do not need a loop: the script already runs once per bar, and functions like ta.highest or math.sum look back over many bars for you, efficiently. Loops are for the work in between: walking an array of levels you collected, scanning the last few bars for a pattern, or building a table row by row. ChartScript has counting loops, loops over collections and while loops.
#for: counting loops
let total = 0.0
for (let i = 0; i <= 9; i++) {
total += close[i]
}
plot(total / 10) // the same as ta.sma(close, 10), done the slow wayA counting loop has three parts in its parentheses, separated by ;:
- the start,
let i = 0: declares the loop variable, once, before the first pass; - the condition,
i <= 9: tested before every pass; the loop ends as soon as it is false; - the step,
i++: runs after every pass (and aftercontinue).
Change the step to count in other amounts, or down:
let evens = 0
for (let i = 0; i <= 10; i += 2) { // 0, 2, 4, 6, 8, 10
evens += i
}
plot(evens)- The condition compares the loop variable with a bound:
i < n,i <= n,i > nori >= n. The step isi++,i--,i += sori -= s. To count down, start high and step down:for (let i = 9; i >= 0; i--). - The bound is read again before every pass, so
i < arr.size()follows an array that grows or shrinks while the loop runs. - When the condition is false at the start, the loop runs zero times. A loop to
arr.size()over an empty array does nothing, which is what you want. - An
nabound ends the loop. - The loop variable belongs to the loop: it cannot be changed in the body (CS2404), and it does not exist after the loop.
Other shapes, such as two loop variables or a step on a different variable, are CS1052. Write them as a while loop.
//@chartscript 1
indicator("Two counters")
let s = 0.0
for (let i = 0, j = 1; i < 10; i++) {
s += i
}
plot(s)#The range loop
ChartScript also has a range loop, for (let i = a to b), with an optional step by s. It runs from a to b inclusive, and its direction is decided when the loop starts: when a is greater than b it counts down (for (let i = 10 to 0 by 2) gives 10, 8, … 0). The step is always used as a positive number. b is read again before every pass; the direction stays the one of the first reading.
Scripts converted automatically use the range loop where the direction could not be known in advance, because it keeps their exact behavior. In new code prefer the counting loop above: its direction is written out, and it has no empty-array trap.
#The empty-array trap
Because a range loop counts down when its end is below its start, for (let i = 0 to arr.size() - 1) on an empty array runs from 0 down to -1: twice, with indexes that do not exist. arr.get(0) then stops the script with CS5101. Use a counting loop with <, or loop over the elements instead:
const levels = array.new<float>()
if (close > open) {
levels.push(close)
}
let sum1 = 0.0
for (let i = 0; i < levels.size(); i++) { // no pass at all when empty
sum1 += levels.get(i)
}
let sum2 = 0.0
for (const lv of levels) { // or simply walk the elements
sum2 += lv
}
plot(sum1 - sum2)#for…of: over arrays and maps
for (const x of arr) visits each element of an array in order. To get the index too, unpack a pair (arr.entries() is accepted as well and means the same):
const closes = array.from(close, close[1], close[2], close[3])
let weighted = 0.0
for (const [i, c] of closes) {
weighted += c * (4 - i) // newest counts 4 times, oldest once
}
plot(weighted / 10)Over a map, for (const [key, value] of m) visits every entry in the order the keys were first added:
const counts = map.new<string, int>()
counts.put("up", close > open ? 1 : 0)
counts.put("down", close < open ? 1 : 0)
let total = 0
for (const [name, n] of counts) {
total += n
}
plot(total)for … of needs an array or a map (CS2702). A matrix is not iterable directly; loop over its rows with for (let r = 0; r < m.rows(); r++) and m.row(r).
//@chartscript 1
indicator("Not a collection")
for (const v of close) {
log.info(str.tostring(v))
}
plot(close)If you push to or remove from the array inside a for … of over it, the loop follows the new size: pushed elements are visited, and the loop ends early when elements are removed. That is well defined, but easy to misread, so the compiler adds a warning (CS5302) suggesting a counting loop instead.
#while
while (condition) repeats the body as long as the condition is true. Use it when you do not know the number of passes in advance:
// how many bars back was the close last this low or lower?
let back = 1
while (back < 100 && close[back] > close) {
back += 1
}
plot(back)Make sure something in the body moves the condition toward false. A loop that never ends is stopped by the loop limits. There is no do … while.
#break and continue
break leaves the innermost loop at once; continue skips to its next pass (in a counting loop, the step still runs). Both are only allowed inside a loop (CS2703).
//@chartscript 1
indicator("Bars since a wide range bar")
const lookback = input.int(50, "Look back", { minval: 1, maxval: 500 })
const factor = input.float(1.5, "Wide = range above ATR ×", { minval: 0.5, step: 0.1 })
const atr = ta.atr(14)
let found = int(na)
for (let i = 1; i <= lookback; i++) {
if (na(atr[i])) {
continue // not enough history yet for this bar
}
if (high[i] - low[i] > factor * atr[i]) {
found = i
break // the most recent one is enough
}
}
plot(found, "Bars since", color.accent, 2, plot.style_stepline)#Loops as expressions
A loop can produce a value: the value of the last line run in its body on the last pass.
const lastOdd = for (let i = 1; i <= 9; i += 2) {
i
}
plot(lastOdd) // 9This is occasionally handy, but a variable updated inside the loop is usually clearer.
#Stateful functions in loops
A function that remembers previous bars, such as ta.sma, keeps one state per place it is written in the code, not one per pass. Called in a loop, only the last call on each bar counts, and the compiler warns (CS2504):
//@chartscript 1
indicator("Averages in a loop")
let total = 0.0
for (let len = 10; len <= 30; len += 10) {
total += ta.sma(close, len) // one average state, not three
}
plot(total / 3)Here the editor also shows hint CS2102, because the loop counter is a length that changes. Write the calls out (ta.sma(close, 10) + ta.sma(close, 20) + ta.sma(close, 30)), or compute the average yourself from close[i] inside the loop.
#Loop limits
To keep charts responsive, loops have a budget:
| Limit | Value | When it is exceeded |
|---|---|---|
| passes of one loop on one bar | 100,000 | the script stops with CS5301 |
| passes of all loops together on one bar | 1,000,000 | the script stops with CS5303 |
Scripts also have an overall time budget per run. A loop over the last 500 bars on every bar of a 5,000-bar chart is 2.5 million passes, which is slow even if it stays under the per-bar limits. Prefer the built-in window functions (ta.highest, ta.lowest, math.sum, ta.sma) where they fit: they update in one step per bar. See Performance for more.
#Common mistakes
<=where<was meant.for (let i = 0; i <= arr.size(); i++)goes one past the last element; usei < arr.size().- A range loop over an empty array runs twice, counting down (see the empty-array trap).
- Indexing arrays with
[ ].arr[i]is history (the array on an earlier bar), not an element. Usearr.get(i). - Recalculating a window by hand. A loop over 200 bars summing
close[i]on every bar does 200 times the work ofta.sma(close, 200).