NexuScript 1.5
How a NexuScript script is written.
The rules every script follows, from the first line to the limits it runs within. Each name links to its entry in the syntax reference.
How a script is written
- The first line is
nexscript 1. - Next comes
indicator "Title", withoverlay = trueto draw on the price chart (without it the indicator gets its own pane) andshort = "T"for the name in the legend. - Then, in any order: settings (
input), constants (const), values kept from bar to bar (state), horizontal lines (level), lists of choices (enum) and your own functions. - Exactly one
on bar ... endblock. #starts a comment that runs to the end of the line.#= ... =#is a comment over several lines.- A line ends a statement. A statement may continue on the next line when the line ends inside brackets, after an operator or after a comma.
- Blocks end with
end. Indentation is for reading only; four spaces is usual.
Settings: input
input length = 20, title = "Length", min = 1, max = 500
input mult = 2.0, title = "Multiplier", step = 0.1
input src = close, title = "Source"
input kind = "SMA", title = "Type", options = ["SMA", "EMA", "WMA"]
input show_ma = false, title = "Moving average", group = "Smoothing"
input ma_length = 14, title = "Length", group = "Smoothing", when = show_ma => true
input line_color = color.hex("#d9a441"), title = "Color"
input other = Symbol("ES"), title = "Symbol"
input htf = Timeframe("D"), title = "Higher timeframe"The kind of setting comes from the default value: a whole number, a number with decimals (2.0), a switch (true or false), text (a dropdown with options), a colour, a price source (open, high, low, close, hl2, hlc3, ohlc4, hlcc4), a symbol or a timeframe. Other options: title, min, max, step, group (a heading), inline (rows that share one line), tab, tooltip and when (show the row only while another setting has a value). A setting is stored under its name, so renaming the variable starts a new setting; key = "name" keeps an old one. Inside the script a setting cannot be changed.
Constants, state and levels
const zone = "America/New_York"
state count = 0
state range_high = missing
state recent::Array{Number} = []
level "Zero", 0
level "Overbought", 70, color = color.red, style = "dotted"A const never changes. A state keeps its value from one bar to the next; on each update of the newest bar it starts again from its value after the last closed bar. A level is a fixed line across the pane, dashed unless style says "solid" or "dotted".
Choices: enum
enum Average
sma = "SMA"
ema = "EMA"
end
input kind = Average.ema, title = "Average"Functions
function body_share()
let range = high - low
return range > 0 ? abs(close - open) / range : missing
end
typical(h, l, c) = (h + l + c) / 3A function takes its arguments by value. A parameter may have a default (basis = 1.0) and a type (x::Number).
Statements
let x = ...declares a variable you may change later;const x = ...one you may not.x = ...,x += ...,-=,*=,/=change it.let (basis, upper, lower) = ta.bands(close, 20)takes the parts of a result in order.if ... elseif ... else ... end. A condition istrueorfalse: writex != 0or!ismissing(x), never justx.for i in 1:10 ... end(also10:-1:1, orfor x in values). There is nowhile: every loop knows how many times it runs when it starts.breakandcontinuework on the innermost loop.returnleaves a function; inon barit ends the bar early.
Values
| Type | Values |
|---|---|
Number | Numbers, and missing for "no value" |
Bool | true, false |
String | Text in double quotes |
Color | color.blue, color.hex("#22c55e"), color.rgb(34, 197, 94), color.none |
Array{T} | A list, written [1, 2, 3]. The first element is xs[1]. |
| Tuples | (fast = f, slow = s), read with t.fast |
missing is a value that is not there: a gap, or a value not computed yet. Arithmetic with missing gives missing, comparisons with it are false (except !=), and plotting it leaves a gap. Test it with ismissing(x); coalesce(x, 0) gives 0 instead.
Text is joined with *: "RSI " * string(length). Inside text, $(...) puts a value in: "Close $(close) at $(hour())".
Operators: + - * / % ^ on numbers, == != < <= > >=, && || ! on Bool, and c ? a : b. 0 < x <= 10 checks both sides.
Past values: x[n]
let change = close - close[1]
let fast = ta.ema(close, 9)
let rising = fast > fast[1]x[n] is the value n bars ago. It works on the bar values, on price source settings, on state values and on variables declared directly in the on bar block (not inside an if or a loop).
Functions that remember: call them on every bar
The ta functions (except ta.true_range), higher and your functions that use them remember earlier bars. Each place where you write such a call keeps its own memory, so it must run on every bar. NexuScript checks this and refuses a call inside an if whose condition changes from bar to bar, or inside a loop. The fix is always the same: compute first, decide after.
on bar
let avg = ta.sma(close, 20)
if close > open
plot("Average", avg)
end
endAn if on a setting is fine, because it takes the same branch on every bar.
Other timeframes: higher
on bar
let daily = higher("D") do
(close = close[1], ema = ta.ema(close, 10))
end
plot("Yesterday's close", daily.close)
endThe block runs once per bar of the higher timeframe ("5", "15", "60", "240", "D", "W", or any number of minutes) of the chart's symbol, or of symbol = "ES". A closed chart bar gets the result of the last higher bar that had closed when it opened; the newest chart bar gets the forming higher bar. The block's last line is its result: a number, a Bool or a tuple of those. It cannot draw. A script may have up to 8 of them.
The chart clock and shading
Scripts that start with nexscript 1.3 can read the chart clock, the session and more about the symbol, and shade between any two values.
nexscript 1.3
indicator "Bar clock", overlay = true
on bar
let avg = ta.ema(close, 20)
shade("Trend", close, avg, color = close > avg ? color.fade(color.green, 80) : color.fade(color.red, 80))
if bar.last
hline(avg, color = color.orange, dash = "dashed")
table([
["Bar closes in", clock.countdown],
["Session", session.is_open ? "open" : "closed"],
["Trading day", string(clock.trading_day)],
["Chart time", clock.format(clock.now, "HH:mm", zone = "chart")],
["Tick value", string(symbol.tick_value) * " " * symbol.currency]
])
end
endOn closed bars the clock is each bar's closing time, so what a script drew in the past never changes. On the newest bar it is the time of the latest price update, and the script runs on each update, so the countdown moves as prices arrive. In a backtest every bar is closed. Press Reference and search clock, session or symbol for everything you can read.
Limits
| What | Limit |
|---|---|
| Work per bar | 1,000,000 steps |
| Work per full run | 250,000,000 steps |
| Plots and candles | 128 |
| Levels, bands | 128 each |
| Markers | the newest 10,000 |
| Lines, boxes and polylines | the newest 2,000 together |
| Higher timeframes | 8 per script, the last 20,000 of their bars |
| Order calls in a strategy | 100 per bar |
Values kept in state | 100,000 |
| Array length | 100,000 |
| Settings | 200 |
| Script size | 1,000,000 bytes |
A script that goes over a limit stops with the line where it happened, and the rest of the chart keeps working.
Not in NexuScript 1
- Sending a strategy's orders from the chart.
- Changing a drawing after it is drawn: draw it again on the next bar.
- Reading the clock, random numbers, files, the network or the page.