Data types
The types a wrun indicator is built from. The file is AssemblyScript, TypeScript syntax over fixed-width numbers, so every value has a declared width and the compiler names a mismatch before anything runs.
An indicator has two
numeric widths, a boolean, module-internal strings, fixed-size arrays,
and classes, and a missing value is simply NaN.
Primitive types
f64
A 64-bit float: every param, input, and output crosses the host boundary
as one. Prices, volumes, scores, ratios, and decisions (0.0 or 1.0)
are all f64. A literal with a decimal point is an f64; a whole-number
literal is not, so annotate or write the point.
let price: f64 = 45000.5;
let volume: f64 = 1000.0; // 1000 alone would be an i32
let value: f64 = NaN; // "no value yet"i32
A 32-bit integer: a period, a ring-buffer cursor, a loop counter, and
what Cross.update() answers. Params arrive as
f64, so a period becomes an integer with an explicit cast:
new Sma(i32(p_period())). Integer division truncates: 7 / 2 on two
i32 values is 3.
let period: i32 = 20;
let cursor: i32 = 0;
const bars = i32(p_period()); // f64 to i32, explicitlybool
true or false, from comparisons (<, >, ==, !=) and logical
operators (&&, ||, !). A bool never leaves the module on its own: a
decision becomes an output by turning it into a number, usually with a
ternary.
const isUptrend: bool = fast > slow;
out_is_uptrend(isUptrend ? 1.0 : 0.0);string
Text lives inside the module: labels you build for a text renderer, keys in
a Map, comparisons of two literals. Strings never become params, inputs,
or outputs; the only way out is a declared string slot written in
onBar() (presentation/plotting.md). Building a string allocates, and
the module never frees memory, so per-bar text goes through the
allocation-free line builder (sb_text, sb_f64) rather than +.
Concatenating a number needs .toString(): "close " + close is refused
with Type 'f64' is not assignable to type 'String'.
NaN is the missing value
There is no missing-value type. A missing f64 is NaN: every TA class
returns it until its window is warm, and writing it to an output draws a
gap. Test it with isNaN(x); x == NaN is always false. Arithmetic with
NaN stays NaN (na-and-scalar-types.md).
out_filtered(condition ? value : NaN); // a gap where the condition failsCore wrun types
The per-bar value
There is no series type with a whole history behind it. An input is a
reader function that returns this bar's value, and the host calls
onBar() once per bar, oldest first. History is whatever you keep:
- a remembered value (
prevClose) for the previous bar; - a TA class (
Sma,Ema,Rsi, ...) for anything windowed, since each one keeps its own window; - a ring buffer when you need the last N values yourself:
Historyis one ready-made (Stats, history and lists), and by hand it is aStaticArray<f64>plus a cursor.
Inputs are read-only by construction (bar.close() or in_<name>()
returns a number), and they are read in onBar(). The full model is
core-concepts/execution-model.md.
StaticArray<f64>
A fixed-size array allocated once, the workhorse for windows. Size it from
a param's declared max at module scope or in onStart(), never per bar.
Array<f64> (growable) and Map<K, V> exist too (collections.md).
const MAX_BARS = 200;
const window = new StaticArray<f64>(MAX_BARS);class
Your own typed structs with methods (user-defined-types.md).
Input and configuration types
Params reach the module as numbers or words
Every setting reaches the module through its generated reader, readable
from onStart() on, whatever control the dialog draws for it; the kind is
the declaration (Setting kinds). Every kind reads
as an f64 except text, whose words pt_<name>() reads as a string:
| Setting you want | wrun form |
|---|---|
| a number or a slider | param. or param., slider: true for the slider; plain param(...) is the number field |
| a toggle | param., read as a bool through pb_show_open() |
| a choice among options | param., read as the index (0 = line, 1 = bar) and switched on in the code |
| a color | param. bound to the output with color: "@fast"; the reader hands the code the color packed into one number |
| a symbol | param. bound to an input with symbol: "@pair" |
| words | param., read once in onStart() with pt_label() as a string |
Data sources are members
An indicator names a source as a member of a source namespace, and the field is part of the reference:
| Identifier | Description |
|---|---|
ohlcv.close (and open, high, low, volume) | Price and volume; the chart's own candle needs no declaration (bar.close() and the other bar fields read it) |
funding. (and rate_open, predicted_close, ...) | Funding rates |
liquidations. | Liquidation volume, optionally by side |
oi.close | Open interest |
trades.volume with side: "BUY" or "SELL" | Side-split trade volume |
time. | The bar's open time in epoch seconds |
The complete catalog, celled classes included, is data-sources.md.
Placement is per output
An indicator places each output: overlay (the price pane) or lower
(its own pane), as the third argument of output(...).
Visual and plotting types
Color
A color is a string literal in a declaration: #rrggbb, #rrggbbaa,
rgb(), hsl(), or a named color on an output. It is never a runtime
value; there is no color variable, and a per-bar color is a data-only
output indexing a declared colors palette through color_by
(color-constants.md).
output("fast", line, overlay, { color: "#FF6B35" });
output("mid", line, overlay, { color_by: "regime", colors: ["#ef4444", "#22c55e"] });Plot and shape kinds
The second argument of output(...) is the plot kind: line, bar,
area, histogram, candle, shape, scatter, or none (data-only).
A shape output draws a mark at its value; the mark's kind is the host's
default, and render.shape picks one of circle, cross,
triangle_up, triangle_down, diamond, arrow_up, arrow_down,
flag, square (presentation/plotting.md).
Practical examples
Colors for multi-line plots
An indicator declares each line's color on its output, and the palette lives in the file's declarations:
output("sma10", line, overlay, { color: "#FF6B35", width: 2, description: "10-period simple moving average" });
output("sma20", line, overlay, { color: "#3B82F6", width: 2, description: "20-period simple moving average" });
output("sma50", line, overlay, { color: "#10B981", width: 2, description: "50-period simple moving average" });
let sma10 = new Sma(10);
let sma20 = new Sma(20);
let sma50 = new Sma(50);
function onBar(): void {
const close = bar.close();
// Each line starts when its own window is warm: the shorter averages draw first.
out_sma10(sma10.update(close));
out_sma20(sma20.update(close));
out_sma50(sma50.update(close));
}The three periods are fixed here, so there is no onStart(); each line
draws as soon as its own average is warm, the slower two writing NaN (a
gap) until their own windows fill.
Conditional plotting with NaN
Combine a condition with NaN to draw a value only when the condition
holds:
param("limit", 50, { min: 0, max: 1000000, description: "Volume a bar must exceed to be drawn" });
output("filtered", line, overlay, { color: "#dc2626", width: 2, description: "Close, only on bars whose volume exceeds the limit" });
output("volume", histogram, lower, { color: "#94a3b8", description: "Volume" });
let limit: f64 = 50.0;
function onStart(): void {
limit = p_limit();
}
function onBar(): void {
const close = bar.close();
const volume = bar.volume();
// NaN draws nothing: the line breaks wherever the condition fails, and the volume pane still draws.
out_filtered(volume > limit ? close : NaN);
out_volume(volume);
}Writing NaN to one output leaves the other outputs drawing; returning
early from onBar() before any write leaves the whole bar blank. Both are
correct; pick by whether anything on the bar is meaningful.
Type conversion and indexing
A candle is not one value with indexed columns. An indicator reads the
chart's own candle one field at a time (bar.open(), bar.high(),
bar.low(), bar.close(), bar.volume()) and the timestamp through
bar.time() (seconds, not
milliseconds). There is no priceIndex argument anywhere: a class takes
the number you hand it. Casts are explicit (i32(x), f64(n)) and
indexing a number is refused (type-system.md lists the messages).
Best practices
- Name by what it is.
prevClose,windowHigh,barsSeen: a module- level variable's name should say what it carries across bars. - Annotate module-level state.
let value: f64 = NaN;andlet cursor: i32 = 0;read as documentation and stop an integer literal from silently making a variable ani32. - Keep decisions as outputs. A
boolyou want to draw or alert on is a0.0/1.0output declarednone; the sheet turns it into a look. - Let
NaNmean missing. Never write0for "not ready"; a zero is drawn and alerted on as a real value.