Skip to content
Nexus Prop LLC

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", with overlay = true to draw on the price chart (without it the indicator gets its own pane) and short = "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 ... end block.
  • # 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) / 3

A 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 is true or false: write x != 0 or !ismissing(x), never just x.
  • for i in 1:10 ... end (also 10:-1:1, or for x in values). There is no while: every loop knows how many times it runs when it starts. break and continue work on the innermost loop.
  • return leaves a function; in on bar it ends the bar early.

Values

TypeValues
NumberNumbers, and missing for "no value"
Booltrue, false
StringText in double quotes
Colorcolor.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
end

An 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)
end

The 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
end

On 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

WhatLimit
Work per bar1,000,000 steps
Work per full run250,000,000 steps
Plots and candles128
Levels, bands128 each
Markersthe newest 10,000
Lines, boxes and polylinesthe newest 2,000 together
Higher timeframes8 per script, the last 20,000 of their bars
Order calls in a strategy100 per bar
Values kept in state100,000
Array length100,000
Settings200
Script size1,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.