Skip to content
ChartDeck ChartScript docs Account Open the terminal

Functions

Writing your own functions with function or arrow syntax: parameters, defaults, named arguments, return, several results, per-call state, rules.

A function packages a calculation under a name so you can use it many times, with different inputs, without copying code. ChartScript functions are short to write, and they carry one feature that ordinary programming languages do not have: every place you call a function keeps its own history, so a function that uses ta.ema works correctly when you call it twice with different series.

#Declaring a function

There are two ways to write a function. A short one is an arrow function stored in a const: the parameters in parentheses, => and an expression, whose value is the result:

ChartScriptFragmentOpen in editor
const mid = (a, b) => (a + b) / 2
plot(mid(high, low))

For more than one line, use function, a name, the parameters and a body in braces. return gives the result:

ChartScriptFragmentOpen in editor
function bodyPct(o, c, h, l) {
    const rng = h - l
    return rng == 0 ? 0.0 : math.abs(c - o) / rng * 100
}
plot(bodyPct(open, close, high, low))

An arrow function can also have a body in braces: const f = (a, b) => { … return v } means the same as function f(a, b) { … return v }. Pick whichever reads better; the style guide uses arrow functions for one-liners and function for the rest.

Functions are declared at the top level of the script, and may be called before the line where they are declared. Variables declared inside the body are local to it.

#return

return gives the function's result, and it can only be the last statement of the body (or the last statement of each branch of a final if / else or switch, where each branch returns its own value). There is no early exit: a return with more statements after it is CS1051.

ChartScriptError: CS1051
//@chartscript 1
indicator("Early return")
function safeRatio(a, b) {
    return na
    const r = a / b
}
plot(safeRatio(close, open))

Choose the result with if / else or the ternary operator instead:

ChartScriptFragmentOpen in editor
function safeRatio(a: float, b: float) {
    if (b == 0) {
        return na
    } else {
        return a / b
    }
}
plot(safeRatio(close, open))

Without a return, a function gives the value of its last expression, so function mark() { label.new(bar_index, high, "x") } returns the label. A bare return at the end returns nothing.

#Parameters

#Types and qualifiers

A parameter without a type accepts any type that makes the body valid: mid(a, b) works for ints, floats and series. Add a type after a colon (len: int) when you want the compiler to check calls, or when the body alone does not say what is expected (for example, to call methods on it):

ChartScriptFragmentOpen in editor
const rangeOf = (xs: float[]) => xs.max() - xs.min()
plot(rangeOf(array.from(high, high[1], high[2])))

You can also add a qualifier before the type, as in len: simple int. That is needed when the parameter goes to a built-in that requires it, and it makes a wrong call fail at the call. See Series and qualifiers.

A type after the parameter list, as in function bodyPct(o, c, h, l): float { … }, documents what the function returns. It is accepted and kept by the editor, but not yet checked.

#Default values and named arguments

Give a parameter a default with =. Callers may then leave it out, or set it by name in an options object { name: value } as the last argument:

ChartScriptFragmentOpen in editor
function band(src, len: int = 20, mult: float = 2.0) {
    const centre = ta.sma(src, len)
    return centre + mult * ta.stdev(src, len)
}
const a = band(close)  // len 20, mult 2.0
const b = band(close, 50)  // len 50, mult 2.0
const c = band(close, { mult: 3.0 })  // len 20, mult 3.0
plot(a)
plot(b)
plot(c)

Positional arguments come first, and the options object is always the last argument (CS1058 otherwise). Named arguments make calls with several numbers much easier to read, and they work for built-ins too: ta.ema(hlc3, { length: 21 }).

#Returning several values

Return a tuple, a list of values in square brackets (return [hi, lo]), and unpack it at the call with const [a, b] = …:

ChartScriptOpen in editor
//@chartscript 1
indicator("Channel", { overlay: true })
function channel(len: int) {
    const hi = ta.highest(high, len)
    const lo = ta.lowest(low, len)
    return [hi, lo, (hi + lo) / 2]
}

const len = input.int(20, "Length", { minval: 1 })
const [upper, lower, middle] = channel(len)
const pu = plot(upper, "Upper", color.up)
const pl = plot(lower, "Lower", color.down)
plot(middle, "Middle", color.muted, { style: plot.style_linebr })
fill(pu, pl, color.new(color.accent, 92), "Channel")

Use _ for values you do not need: const [upper, _, _] = channel(20).

#Each call keeps its own state

Stateful code inside a function (ta.* calls, x[1], state variables) gets a separate state for every place the function is called. So this works as you would hope:

ChartScriptFragmentOpen in editor
const smooth = (src, len) => ta.ema(ta.ema(src, len), len)
const fastLine = smooth(close, 9)  // its own two EMAs
const slowLine = smooth(close, 21)  // two more, independent of the first pair
plot(fastLine - slowLine)

The same applies to state inside a function: each call site has its own copy, which makes small counters easy to reuse.

ChartScriptFragmentOpen in editor
function barsTrue(cond: bool) {
    state n: int = 0
    n = cond ? n + 1 : 0
    return n
}
const upRun = barsTrue(close > open)
const downRun = barsTrue(close < open)
plot(upRun - downRun)

#Stateful calls in conditions

Because a function call's state only advances on bars where the call runs, calling a stateful function inside an if or one side of ?: changes its result. The compiler warns once per place (CS2503):

ChartScriptWarning: CS2503Open in editor
//@chartscript 1
indicator("Conditional call")
const trendStrength = (src) => src - ta.sma(src, 50)
const v = close > open ? trendStrength(close) : 0.0  // the SMA only sees up bars
plot(v)

Call the function on every bar and choose afterwards:

ChartScriptFragmentOpen in editor
const trendStrength = (src) => src - ta.sma(src, 50)
const ts = trendStrength(close)
const v = close > open ? ts : 0.0
plot(v)

A function with no state (only arithmetic, math.*, str.*) is safe to call anywhere; the compiler knows the difference and only warns for stateful ones.

#Rules

  • No recursion. A function may not call itself, directly or through another function (CS2501). Use a loop instead.
  • No functions inside functions (CS2502). Declare helpers at the top level. Functions are not values either: an arrow function anywhere but a top-level const is an error.
  • Parameters are read-only (CS2404). Copy one into a local variable if you need to change it.
  • Globals are read-only inside functions unless declared with state or live state (CS2403). Returning a value and assigning it at the top level is usually clearer.
  • No outputs inside functions. plot, hline, inputs and alert.on belong at the top level (CS3007). Drawings, log.* and alert.send are fine.
  • Collections and objects are passed by reference. A function that pushes onto an array you passed changes your array. Numbers, strings, colors and bools are copied.
ChartScriptError: CS2501
//@chartscript 1
indicator("Factorial")
const fact = (n) => n <= 1 ? 1 : n * fact(n - 1)
plot(fact(5))

The loop version:

ChartScriptFragmentOpen in editor
function fact(n: int) {
    let result = 1
    for (let i = 2; i <= math.max(n, 2); i++) {
        result *= i
    }
    return result
}
plot(fact(5))

#Documenting a function

Comment lines starting with //@ directly above a function feed the hover card in the editor, and for libraries the published reference:

ChartScriptFragmentOpen in editor
//@function Percentage distance of a price from a reference.
//@param price The price to measure.
//@param ref The reference price.
//@returns The distance in percent; positive when price is above ref.
const pctFrom = (price: float, ref: float) => (price - ref) / ref * 100
plot(pctFrom(close, ta.sma(close, 50)))

#Good to know

  • Names of built-in namespaces (ta, math, str, and data namespaces such as basis, oi or flow) cannot be used for your own functions or variables (CS2003); pick a different name.
  • A function that ends with an action and no value (for example only log.info(...)) returns nothing; assigning its result is CS2013.
  • To call a function as value.name(), declare it as a method.
  • To share functions between scripts, put them in a library.

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