Already write scripts elsewhere?
If you already write scripts for charts in another language, this page maps the ideas you know to how they are written here, and lists the real differences.
If you have written indicators or strategies in other chart scripting languages, most of the ideas carry over: a script runs once for each bar, reads price series, keeps some values from bar to bar and draws what it works out. This page names each idea, shows how Cuprel writes it and links to the page that teaches it. It does not compare products; it only helps you find the words. If you have never written a script, start with Lesson 1: Your first script instead.
Idea by idea#
| The idea | How it is written here | Read |
|---|---|---|
| A header that says what kind of script it is | indicator "Name", strategy "Name" or module Name, with its settings indented under it | Syntax and layout |
| A setting the user can change | input length: int = 20, name = "Length", min = 1 | Inputs |
| Price series and earlier bars | open, high, low, close, volume; close[1] is one bar back | Values and types |
| Bar-by-bar execution and repaint | the script runs once per bar, oldest to newest; a forming bar changes until it closes | Lesson 16: Results that look too good |
| Lines and colours | plot(x, name = …, color = …, width = …, style = …), level, shade, chart.background | Plots and drawings |
| Labels, lines, boxes and tables | label.new, line.new, box.new, table.new | Lesson 8: Labels, lines and boxes |
| Conditions and missing values | if, if … then … else, match; na, is_na(x), x ?? 0 | na, errors and arithmetic |
| Functions and loops | fn name(…): type, for … in, while | Functions, types and enums |
| A value kept from bar to bar | state (and state day, which starts again each trading day); mut is recomputed each bar | let, mut and state |
| Another timeframe | data.series("240", expression, symbol = symbol.id), the last closed bar by default | Lesson 10: Other timeframes and symbols |
| Another symbol | not available in this release: the script is refused (E-REQ-UNDECLARED) or stops (E4001) | Lesson 10: Other timeframes and symbols |
| Alerts from a script | alert.when and alert.send compile and run, but alerts on your own scripts are switched off in this release | Lesson 11: Alerts that say something |
| Strategies and testing them | strategy "Name", orders.enter / orders.close / orders.exit, costs in setup: and execution:; tested in the Strategy Lab | Lesson 15: Testing in the Strategy Lab |
The real differences#
These are the points to read twice, because habits from other languages may lead you astray here.
- Editions. Write
edition = 1in the header. The edition says which reading of the language the script was written for, so a script keeps its meaning when a later edition exists. - Blocks by indentation. A block is the lines indented under it, four spaces a level. There are no braces and no end words, and each statement has a line of its own.
- The same numbers on every device. The engine computes the mathematical functions with one fixed implementation, so a script on the same bars gives the same result on every supported device. Determinism and limits says exactly what this covers.
- Budgets. Each bar may spend at most 500,000 operations and a run 600,000,000; history, drawings, requests and memory have limits too. A script that spends more than its budget is stopped with a message that names the bar. See Limits and budgets.
- na rules. Comparing a value with
nais never true, not evenna == na; the checker warns (W-NA-COMPARE). Askis_na(x), or replace a missing value withx ?? 0. - Lazy operators. The right side of
and,orand??, and the branches of a one-lineif … then … else, run only when they are needed. If you are used to a language where both sides always run, note that a call that remembers earlier bars (such astech.sma) may not stand there: the checker refuses it withE3302. Write it on a line of its own first:
indicator "A call that remembers, in an operand"
edition = 1
display = price
let strong = close > open and close > tech.sma(close, 20)
plot(if strong then close else na, name = "Strong bars", style = circles)indicator "Bound on its own line first"
edition = 1
display = price
let average = tech.sma(close, 20)
let strong = close > open and close > average
plot(if strong then close else na, name = "Strong bars", style = circles)- Private scripts. Every script belongs to your account. There is no way to share or publish one in this release.
- One layout. The editor's Format button (and Format when saving) rewrites a script in the one standard layout this guide uses, so you do not need to learn a style guide.
Lazy operators, E3302 and W3302 and Calls that keep a state explain the last rule in full.
The same idea, written in Cuprel#
Each program is complete: paste it into a new script, press Save and Add to chart.
A moving average with two settings:
indicator "Average with a setting"
edition = 1
display = price
input length: int = 20, name = "Length", min = 1
input src: source = close, name = "Source"
plot(tech.sma(src, length), name = "Average", color = color.orange, width = 2)Marks where a fast average crosses a slow one. The crossing tests are worked out on lines of their own:
indicator "Crossing marks"
edition = 1
display = price
let fast = tech.ema(close, 10)
let slow = tech.ema(close, 30)
let up = tech.crosses_above(fast, slow)
let down = tech.crosses_below(fast, slow)
plot(fast, name = "Fast", color = color.green)
plot(slow, name = "Slow", color = color.red)
mark.shape(up, name = "Up", shape = triangle_up, place = below, color = color.green)
mark.shape(down, name = "Down", shape = triangle_down, place = above, color = color.red)A value kept from bar to bar with state:
indicator "Bars since a new high"
edition = 1
display = pane
state count = 0
if close >= tech.highest(close, 20)
count = 0
else
count += 1
plot(count, name = "Bars since", style = histogram, color = color.teal)A value from the four-hour chart. By default it is the value of the last four-hour bar that has closed, so it does not change while that bar is still forming:
indicator "Four-hour average"
edition = 1
display = price
let h4 = data.series("240", tech.ema(close, 20), symbol = symbol.id)
plot(h4, name = "4h EMA (last closed bar)", color = color.orange, width = 2)Missing values: ?? gives a replacement, is_na asks whether a value is missing:
indicator "Missing values"
edition = 1
display = pane
let change = close - close[1]
let average = tech.sma(change, 50)
plot(average ?? 0, name = "Average change, 0 while unknown", color = color.blue)
plot(if is_na(average) then 1 else 0, name = "Still unknown", style = columns, color = color.gray)A function that gives back two results at once:
indicator "Range of the last bars"
edition = 1
display = price
fn range_of(length: int): (float, float)
return (tech.highest(high, length), tech.lowest(low, length))
let (top, bottom) = range_of(20)
plot(top, name = "Top", color = color.green)
plot(bottom, name = "Bottom", color = color.red)A small strategy with a size and a commission. The chart marks its trades; the Strategy Lab tests it over a longer history:
strategy "Crossing strategy"
edition = 1
display = price
setup:
size = sizing.lots(0.1)
fees = fee.per_lot(3.5)
let fast = tech.ema(close, 10)
let slow = tech.ema(close, 30)
if tech.crosses_above(fast, slow)
orders.enter("Long", side.long)
if tech.crosses_below(fast, slow)
orders.close("Long")
plot(fast, name = "Fast", color = color.green)
plot(slow, name = "Slow", color = color.red)