Learn
Language tour
The parts of the language you need for your own indicators. Each part points to a complete program that you can paste into the editor.
Header and edition
A script starts with a header: indicator and a name in quotes. The indented lines under it describe the script, not the bars: edition = 1 for the edition of the language, and display = price or display = pane for where it draws. After the header come the inputs and the code. See Simple moving average and, in the wiki, Syntax and layout.
Series and bars
The script runs once for each bar, top to bottom. open, high, low, close and volume are series: one value for each bar, and the value you read is the value of the bar the script is on. Square brackets read an earlier bar: close[1] is the close one bar ago, and close[50] reaches further back.
// Reading earlier bars: `close[n]` is the close n bars ago, and the engine keeps a bounded history for every series.
// This compares the close with the close 50 bars ago, 100 bars ago, and the highest close of the last 200.
indicator "Look back"
edition = 1
display = pane
let change_50 = close - close[50]
let change_100 = close - close[100]
let from_peak = close - tech.highest(close, 200)
plot(change_50, name = "Change over 50 bars", color = color.blue)
plot(change_100, name = "Change over 100 bars", color = color.purple)
plot(from_peak, name = "From the 200-bar high", color = color.red)
History is bounded. A series keeps 2,048 bars to start with and can grow to 8,192 bars; a read past the cap stops the run with a named error. See the limits.
Names that keep a value
let gives a name to a value of the bar; it never changes after it is set. state takes its value on the first bar and keeps it from bar to bar, so a script can count: count = 0 sets it again and count += 1 adds to it.
// A value the script keeps between bars: `state` starts once, `mut` changes it.
// This counts the bars since the close last made a 20-bar high, and resets it on every new high.
indicator "Bars since a high"
edition = 1
display = pane
input length: int = 20, name = "Length", min = 2
state count = 0
let new_high = close >= tech.highest(close, length)
if new_high
count = 0
else
count += 1
plot(count, name = "Bars since", style = histogram, color = color.teal)
In the wiki: let, mut and state.
Inputs
An input line declares a setting of the script, with a type and a first value. The samples use whole numbers (input length: int = 20, name = "Length", min = 1), decimals with a step (input mult: float = 2.0, name = "Deviations", min = 0.1, step = 0.1), texts and times. Inside the script an input is a plain value. See Bollinger bands, Weekday mornings and, in the wiki, Inputs.
Plots, levels and marks
plotdraws a value on every bar, as a line or withstyle = histogram.leveldraws a fixed level, such as an overbought line: RSI written out.shadefills the space between plots: Bollinger bands.mark.shapeandmark.charmark the bars where a condition is true: Inside bars.chart.backgroundtints the background of a bar: Trend background.label.newputs a label at a bar and a price: Swing pivots.
In the wiki: Plots and drawings.
Conditions
A condition is a value that is true or false, built with comparisons, and, or and not. A block if runs the indented lines under it only when its condition is true, with an optional else. if ... then ... else chooses between values inside a line, as in let tint = if close > trend then color.fade(color.green, 88) else color.fade(color.red, 88).
// A candlestick pattern from its definition: an inside bar has a high below and a low above the bar before it.
// A close above the mother bar's high after an inside bar is marked as a break.
indicator "Inside bars"
edition = 1
display = price
let inside = high < high[1] and low > low[1]
let broke_up = inside[1] and close > high[2]
let broke_down = inside[1] and close < low[2]
mark.char(inside, name = "Inside bar", char = "i", place = above, color = color.gray)
mark.shape(broke_up, name = "Break up", shape = triangle_up, place = below, color = color.green)
mark.shape(broke_down, name = "Break down", shape = triangle_down, place = above, color = color.red)
inside[1] reads the condition one bar ago: a condition is a series too. In the wiki: Choices and loops.
Functions
Built-in functions come in groups: tech for indicators such as tech.sma, tech.ema, tech.rma, tech.atr and tech.highest, math for numbers, color for colours. You can write your own with fn: a block with typed parameters and a return type, or a one-line function after =. A parameter can have a default value.
// Your own functions: a block function with a guard, a one-line function, and an argument given by name.
indicator "User functions"
edition = 1
display = pane
fn body_share(open_price: float, close_price: float, high_price: float, low_price: float): float
let range = high_price - low_price
if range == 0
return na
return math.abs(close_price - open_price) / range
fn scaled(x: float, factor: float = 100.0): float = x * factor
plot(scaled(body_share(open, close, high, low)), name = "Body as a percent of the range")
plot(scaled(0.5), name = "Half", color = color.gray)
A function that keeps a state, such as a moving average, keeps a separate state for every place it is called from. In the wiki: Functions, types and enums.
Missing values
na means that there is no value: a moving average has none on the first bars, before it has enough of them. Ask for it with is_na, as in if is_na(trend) then na else tint; a comparison with na is never true, and the editor warns about it. Plotting na leaves a gap.
// A background that follows the trend: green above the moving average, red below it, nothing while it is not known yet.
indicator "Trend background"
edition = 1
display = price
input length: int = 50, name = "Length", min = 2
let trend = tech.sma(close, length)
let tint = if close > trend then color.fade(color.green, 88) else color.fade(color.red, 88)
chart.background(if is_na(trend) then na else tint, name = "Trend")
plot(trend, name = "Trend line", color = color.gray)
In the wiki: na, errors and arithmetic.
Time and sessions
A script reads the time of the bar, bar.time, not the clock of the computer. clock.weekday_of and clock.hour_of take that time apart in a time zone you name. A session is a span of the day in a zone: session london = 08:00 .. 16:30 @ "Europe/London". london.active is true on the bars inside it, and state session london declares a value that starts again at the start of each session.
// A trading session is two times and a zone. `state session` keeps a value that restarts at the start of each session,
// so this draws the high of the London session so far, and the high of the New York session so far.
indicator "Session highs"
edition = 1
display = price
session london = 08:00 .. 16:30 @ "Europe/London"
session newyork = 09:30 .. 16:00 @ "America/New_York"
state session london london_high = high
state session newyork newyork_high = high
if london.active
london_high = math.max(london_high, high)
if newyork.active
newyork_high = math.max(newyork_high, high)
plot(london_high, name = "London high so far", color = color.green)
plot(newyork_high, name = "New York high so far", color = color.orange)
More on time: Weekday mornings and, in the wiki, Trading day and sessions.
Next
The sample programs put these parts together, and Limits and honest notes says what a script can and cannot do in this release.
Next: the third lesson of the course in the MillionChart wiki, Lines, colours and levels. The whole course: Write your own scripts.