What you get
kScript gives you two collection types and a set of functional methods to work them:
- Arrays are ordered lists. Build them with
[...]literals and work them withpush,get,set,length(), reducers, and mutators. - Maps are key/value stores. Create one with
{}and useset,get, andsize(). - Arrow lambdas (
(x) => ...) feed the array reducers:map,filter,reduce,find,some,every, andforEach.
Together these replace most hand-written loops. The reducers are the idiomatic way to transform a list, and they come with iteration guards built in, so a runaway transform stops loudly instead of hanging the chart. For a deeper tour of lambdas and the reducer family, see Lambdas & Reducers.
Quick reference
var xs = [1, 2, 3] // array literal
xs.push(4) // append
xs.get(0) // read by index
xs.set(0, 99) // write by index
xs.length() // count
var m = {} // map literal
m.set("k", 10) // write
m.get("k") // read
m.size() // entry count
xs.map((v) => v * 2) // transform each element
xs.filter((v) => v > 2) // keep matching elements
xs.reduce((sum, v) => sum + v, 0) // fold to one value
xs.find((v) => v > 2) // first match, || na
xs.some((v) => v > 2) // true if any match
xs.every((v) => v > 0) // true if all match
xs.forEach((v) => { ... }) // run a side effect per element
xs.avg() // numeric reducers, since 3.0.13 (in-flight)
xs.sum()
xs.median()
xs.stdev() // population standard deviation
xs.variance() // population variance
xs.min()
xs.max()
xs.range()
xs.reverse() // reverse in place and return the array
xs.first() // first element
xs.last() // last element
xs.shift() // remove and return first element
xs.unshift(0) // prependA worked example
This script exercises the whole surface in one pass: it builds an array from the current bar's prices, mutates it, runs every reducer over it, stuffs results into a map, then sums everything into one plottable score. It is dense on purpose, as a map of what fits together.
//@version=2
define(title="Collections Happy Path", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
var prices = [trade.open, trade.high, trade.low]
prices.push(trade.close)
prices.set(0, prices.get(0) + barIndex / 10)
var transformed = prices.map((value, index) => value + index)
var filtered = transformed.filter((value) => value > trade.low)
var reduced = filtered.reduce((total, value) => total + value, 0)
var firstLarge = transformed.find((value) => value > trade.close)
var hasHigh = transformed.some((value) => value > trade.high)
var allPositive = transformed.every((value) => value > 0)
var sideEffectTotal = 0
transformed.forEach((value) => {
sideEffectTotal += value / 1000
})
var levels = {}
levels.set("reduced", reduced)
levels.set("first", nz(firstLarge, trade.close))
levels.set("flags", (hasHigh ? 1 : 0) + (allPositive ? 1 : 0))
var score = levels.get("reduced") + levels.get("first") + levels.get("flags") + prices.length() + levels.size() + sideEffectTotal
plotLine(value=score, colors=["#2563eb"], width=2, label=["Collection score"], desc=["arrays maps lambdas and reducers"])A few idioms worth lifting out of that wall:
prices.set(0, prices.get(0) + barIndex / 10)reads, modifies, and writes one slot in place.nz(firstLarge, trade.close)matters becausefindreturnsnawhen nothing matches;nzsupplies a fallback so the arithmetic stays finite. See na and Color.- The
(value, index)lambda inmapshows the optional index parameter; drop it when you don't need it.
What you'll see: one line whose value moves every bar, since prices is rebuilt from live OHLC and barIndex feeds into it.
Numeric reducers and manipulators
Since 3.0.13 (in-flight), numeric arrays expose avg, sum, median, stdev, variance, min, max, and range. The statistics use population math: variance() divides by n, and stdev() is the square root of that population variance. Empty-array reducers return na. Arrays also expose reverse, first, last, shift, and unshift for common positional edits.
The probe below reported ok: true. The reducer score for [1, 2, 3, 4] started at 25.368033988749893, which includes population stdev = 1.118033988749895 and variance = 1.25; the empty reducer score started at 8, proving all eight empty reducers returned na before the probe counted them.
//@version=2
define(title="Array Reducers Manipulators", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
var values = [1, 2, 3, 4]
var reducerScore = values.avg() + values.sum() + values.median() + values.stdev() + values.variance() + values.min() + values.max() + values.range()
var reversed = values.reverse()
var shifted = reversed.shift()
reversed.unshift(trade.close)
var manipulatorScore = shifted + reversed.first() + reversed.last() + reversed.length()
var empty = []
var emptyScore = (isna(empty.avg()) ? 1 : 0) + (isna(empty.sum()) ? 1 : 0) + (isna(empty.median()) ? 1 : 0) + (isna(empty.stdev()) ? 1 : 0) + (isna(empty.variance()) ? 1 : 0) + (isna(empty.min()) ? 1 : 0) + (isna(empty.max()) ? 1 : 0) + (isna(empty.range()) ? 1 : 0)
plotLine(value=reducerScore + barIndex / 1000, colors=["#2563eb"], width=2, label=["Reducers"], desc=["array numeric reducers use population statistics"])
plotLine(value=manipulatorScore, colors=["#16a34a"], width=2, label=["Manipulators"], desc=["reverse first last shift and unshift mutate or read arrays"])
plotLine(value=emptyScore + barIndex / 1000, colors=["#dc2626"], width=2, label=["Empty"], desc=["empty array reducers return na"])The two limits
Collections are bounded so a script can't exhaust memory or silently mis-type a list.
Size ceiling: 100,000 elements
A single collection tops out at 100,000 elements. Push past it and the run stops with Collection size limit exceeded at 9:5 (max 100000), pointing at the offending line. In practice you only hit this by accident, usually an unbounded push in a loop.
//@version=2
define(title="Collection Size Boundary", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
if (isLastBar) {
var values = []
for (var n = 0; n < 100001; n += 1) {
values.push(n)
}
}
plotLine(value=trade.close, colors=["#dc2626"], width=2, label=["Close"], desc=["anchor for collection size boundary"])Arrays are typed
An array has one element type, inferred from how you first fill it. Mixing types is rejected at compile time. Here a numeric array gets a string pushed onto it:
//@version=2
define(title="Array Type Mismatch Boundary", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
var values = [1, 2, 3]
values.push("not a number")
plotLine(value=trade.close, colors=["#dc2626"], width=2, label=["Close"], desc=["anchor for array type mismatch boundary"])This fails to compile with Array element type mismatch: expected number, got string at 6:13. The takeaway: keep an array homogeneous. If you genuinely need to carry mixed data per row, reach for a struct instead of forcing it into one array.