Data types

View as MarkdownOpen the editor

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.

text
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.

text
let period: i32 = 20;
let cursor: i32 = 0;
const bars = i32(p_period());   // f64 to i32, explicitly

bool

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.

text
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).

text
out_filtered(condition ? value : NaN);   // a gap where the condition fails

Core 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: History is one ready-made (Stats, history and lists), and by hand it is a StaticArray<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).

text
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 wantwrun form
a number or a sliderparam.number("mult", 2.0, { min: 0.5, max: 5, step: 0.1 }) or param.int("period", 20, { min: 1, max: 200 }), slider: true for the slider; plain param(...) is the number field
a toggleparam.bool("show_open", true), read as a bool through pb_show_open()
a choice among optionsparam.choice("kind", ["line", "bar"], "line"), read as the index (0 = line, 1 = bar) and switched on in the code
a colorparam.color("fast", "#3b82f6") bound to the output with color: "@fast"; the reader hands the code the color packed into one number
a symbolparam.symbol("pair", "BINANCE_FUTURES:ETHUSDT") bound to an input with symbol: "@pair"
wordsparam.text("label", "Session average"), 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:

IdentifierDescription
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.rate_close (and rate_open, predicted_close, ...)Funding rates
liquidations.liquidationsLiquidation volume, optionally by side
oi.closeOpen interest
trades.volume with side: "BUY" or "SELL"Side-split trade volume
time.bar_open_secThe 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).

text
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));
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine

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);
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine

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; and let cursor: i32 = 0; read as documentation and stop an integer literal from silently making a variable an i32.
  • Keep decisions as outputs. A bool you want to draw or alert on is a 0.0 / 1.0 output declared none; the sheet turns it into a look.
  • Let NaN mean missing. Never write 0 for "not ready"; a zero is drawn and alerted on as a real value.