Skip to content
ChartDeck ChartScript docs Account Open the terminal

Collections

Arrays, maps and matrices in ChartScript: creating them, keeping them across bars, the most useful functions, errors and size limits.

A series gives you one value per bar. Collections hold many values at once, independent of bars: the last ten swing highs, a count per weekday, a grid of correlations. ChartScript has three kinds:

KindHoldsTypical useLimit
arrayan ordered listlevels, recent events, values to average100,000 elements
mapkey → value pairscounts or totals per name, per hour, per enum value50,000 entries
matrixrows × columns of one typegrids, small linear algebra100,000 cells

Every collection function can be called in two ways: array.push(arr, x) or, as a method, arr.push(x). The examples use the method form, which reads more naturally.

#Arrays

#Creating

ChartScriptFragmentOpen in editor
const a = array.new<float>()  // empty
const b = array.new<float>(5, 0.0)  // five elements, all 0.0
const c = array.new_int(3, 7)  // shortcut forms: new_float, new_int, new_bool, new_string, new_color, new_label, new_line, new_box
const d = array.from(high, low, close)  // from values; the type comes from them
const e: float[] = array.new<float>()  // T[] is another way to write array<T>
plot(b.size() + c.size() + d.size())

An array can hold any one type: numbers, strings, colors, bools, drawings, your own user types, even other arrays.

#Keeping an array across bars

An array declared with const or let is a new, empty array on every bar. To collect values over time, declare it with state:

ChartScriptOpen in editor
//@chartscript 1
indicator("Average of the last swing highs", { overlay: true })
const keep = input.int(5, "Swings to average", { minval: 1, maxval: 50 })
state swings = array.new<float>()
const ph = ta.pivothigh(high, 5, 5)
if (!na(ph)) {
    swings.push(ph)
    if (swings.size() > keep) {
        swings.shift()  // drop the oldest
    }
}
const avgSwing = swings.size() > 0 ? swings.avg() : na
plot(avgSwing, "Average swing high", color.orange, 2, plot.style_stepline)

Changes to a state array during a live update are rolled back before the next update, like any state value. See Execution model.

#Reading and changing

TaskCall
number of elementsa.size()
element at an indexa.get(i); a.first(), a.last()
replace an elementa.set(i, x)
add at the end / at the fronta.push(x) / a.unshift(x)
insert at an indexa.insert(i, x)
remove from the end / the front / an indexa.pop() / a.shift() / a.remove(i) (each returns the removed value)
empty ita.clear()
searcha.includes(x), a.indexof(x), a.lastindexof(x), a.binary_search(x) on a sorted array
sorta.sort(), a.sort(order.descending); a.sort_indices() gives the order without moving anything
copy, part, joina.copy(), a.slice(from, to), a.concat(b)
statisticsa.sum(), a.avg(), a.min(), a.max(), a.median(), a.mode(), a.stdev(), a.variance(), a.range(), a.percentrank(i)
texta.join(", ") (string arrays)

Negative indexes count from the end: a.get(-1) is the last element, a.get(-2) the one before.

An index past the end stops the script with CS5101, which names the index and the size. It is a runtime error, not na, because a silent na would hide the bug. Check a.size() first when the array can be short.

The full list, with every parameter, is on the array reference page.

#Square brackets are history, not indexing

a[1] is not the element at index 1. It is the array as it was on the previous bar, which for a state array is the same array (the compiler warns, CS2301). Use a.get(1).

ChartScriptWarning: CS2301Open in editor
//@chartscript 1
indicator("Brackets on an array")
state closes = array.new<float>()
closes.push(close)
const prev = closes[1]  // the same array, not an element
plot(na(prev) ? 0 : prev.size())  // grows every bar, like closes itself

#Maps

A map stores values under keys. Keys can be int, float, bool, string, color or an enum; values can be any type.

ChartScriptOpen in editor
//@chartscript 1
indicator("Up-bar rate by hour (UTC)")
state upByHour = map.new<int, int>()
state allByHour = map.new<int, int>()
const h = hour(time)
allByHour.put(h, nz(allByHour.get(h)) + 1)
if (close > open) {
    upByHour.put(h, nz(upByHour.get(h)) + 1)
}
const rate = allByHour.contains(h) ? nz(upByHour.get(h)) / allByHour.get(h) * 100 : na
plot(rate, "Up bars this hour, %", color.accent, { style: plot.style_columns })
hline(50, "Half", color.muted, hline.style_dotted)
TaskCall
store (replaces an existing value)m.put(key, value), returns the old value or na
readm.get(key), na when the key is missing
testm.contains(key)
removem.remove(key), returns the removed value or na
size, keys, valuesm.size(), m.keys(), m.values() (arrays, in insertion order)
copy, merge, emptym.copy(), m.put_all(other), m.clear()
  • A missing key gives na of the value type. nz turns that into 0, as above.
  • Maps remember insertion order: m.keys() and for (const [k, v] of m) (see Loops) go through the keys in the order they were first added.
  • A key that is na stops the script (CS5105).

The full list is on the map reference page.

#Matrices

A matrix is a grid with a fixed type. Create it with a size and a starting value, then read and write cells by row and column (both from 0):

ChartScriptFragmentOpen in editor
const m = matrix.new<float>(2, 3, 0.0)  // 2 rows, 3 columns of 0.0
m.set(0, 2, close)
m.set(1, 0, open)
const topRight = m.get(0, 2)
const firstRow = m.row(0)  // an array
plot(topRight + firstRow.size() + m.rows() + m.columns())

Beyond the basics (rows, columns, row, col, add_row, add_col, remove_row, remove_col, fill, copy, submatrix, transpose, sort, reshape, concat) there is a set of linear-algebra functions for numeric matrices: mult, sum, diff, pow, det, inv, pinv, rank, trace, kron, eigenvalues, eigenvectors, plus checks such as is_square and is_symmetric. Note that m.sum(other) adds two matrices cell by cell; it is not the total of the cells.

  • Operations whose sizes do not fit (multiplying a 2×3 matrix by a 2×3 matrix) stop the script with CS5106.
  • eigenvalues and eigenvectors take square symmetric matrices in this version (CS5107 otherwise).
  • A matrix cannot be used directly in for … of; loop with for (let r = 0; r < m.rows(); r++) and take m.row(r).

The full list is on the matrix reference page.

#References and copies

Collections are passed by reference. const b = a does not copy the array; both names point to the same one, and a function you pass it to can change it. When you need an independent copy, use a.copy() (m.copy() for maps and matrices).

A collection variable can also be na (for example let xs: float[] = na). Calling a method on it stops the script with CS5102.

#Limits

LimitValueError
elements in one array100,000CS5103
entries in one map50,000CS5103
cells in one matrix100,000CS5103

Collections also count toward the script's overall memory budget (CS5403). A state array that grows by one element per bar on a long chart is fine; one that grows by thousands per bar is not. Trim old elements with shift() once you have enough.

#Common mistakes

  • Missing state. Declared with const or let, the array is rebuilt empty on every bar, so size() never grows past what one bar adds.
  • a[i] instead of a.get(i). Brackets are history.
  • A range loop for (let i = 0 to a.size() - 1) over an empty array. It runs twice, counting down; see the empty-array trap. Write for (let i = 0; i < a.size(); i++) or for (const x of a).
  • Treating a missing map key as 0. It is na; wrap it in nz() before adding to it.

The ChartScript documentation is in English for now; translations will follow.