Setting kinds

View as MarkdownOpen the editor

Fifteen kinds, one row each: the declaration, the control the dialog draws, the reader your code calls.

What it is

Every kind keeps the contract of plain param(name, default, { required, min, max, description }): a top-level statement, a literal default, and a generated reader, readable from onStart() on. Plain param(...) still declares a number field, and a file that uses it runs exactly as before. What a menu chooses at runtime is still a number in the module: two moving-average kinds are two classes, and the param.choice index picks which one writes the output.

Declare it

wrun
// a number field, a toggle, a menu, two handles on one slider, a text field
param.int("length", 20, { min: 2, max: 500 });
param.text("label", "Session VWAP", { max_bytes: 64 });
param.bool("show_bands", true);
param.choice("kind", ["Simple", "Exponential"], "Simple");
param.range("band_pct", [0.5, 2.0], { min: 0, max: 10 });
KindDeclare it asThe dialog draws
intparam.int(name, 20, { min, max })a number field that steps by 1
numberparam.number(name, 2.0, { min, max, step })a number field that steps by step
boolparam.bool(name, true)an on/off toggle
choiceparam.choice(name, ["Simple", "Exp"], "Simple")a menu of the labels
colorparam.color(name, "#2962ff")a color picker
timeparam.time(name, "2024-01-01 00:00")a date and time field with Pick: the next click on the chart sets it
priceparam.price(name, 0.0)a number field with Pick: the next click on the chart sets the price
rangeparam.range(name, [0.5, 2.0], { min, max })one slider with two handles
multiparam.multi(name, ["Mon", "Tue"], ["Mon"])a chip per option up to four options, a multi-select menu past that
listparam.list(name, [5.0, 20.0], { max })an editable list of numbers with Add, up to max long
sourceparam.source(name, ohlcv.close)a menu of open, high, low, close, hl2, hlc3, ohlc4, volume, hlcc4
timeframeparam.timeframe(name, "chart")a menu of chart, 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w
symbolparam.symbol(name, "BINANCE:ETHUSDT")a market button that opens the symbol search
sessionparam.session(name, "09:30-16:00", { tz })a day strip with the window shaded, a start and an end on the 24-hour clock, and a zone menu
textparam.text(name, "Session VWAP", { max_bytes }), or param.text_area(name, ...) for several linesa text field (a text area for text_area) with a "never paste keys or passwords" hint

What onStart() reads, per kind (the reader takes the setting's name):

KindonStart() reads
intp_length(), a whole number
numberp_mult()
boolpb_show_bands(), a bool
choicep_kind(), the picked index (0 for the first label)
colorp_basis_color(), the color packed into one number; most files bind it to an output instead (The Style page)
timep_since(), epoch seconds
pricep_floor()
rangep_band_pct_lo() and p_band_pct_hi()
multip_days(), a bitmask (bit 0 is the first option); multiHas(mask, i) tests one
listp_lookbacks(), an f64[] of the filled slots
sourcenothing: it declares the input too, and in_src() reads the picked field per bar
timeframenothing: { interval: "@htf" } on an input pins that input to the pick
symbolnothing: { symbol: "@pair" } on an input pins that input to the pick
sessionp_rth_start() and p_rth_end() (minutes from midnight), p_rth_tz() (the zone's index)
textpt_label(), the typed words as a string (p_label() reads their UTF-8 byte count)

The defaults are literals, read from the text without running it: a number, true or false, a string, an array of literals, or for param.source an ohlcv.<field> reference. A param.time default is "YYYY-MM-DD HH:MM" in UTC or a whole number of epoch seconds. A param.symbol default is "EXCHANGE:SYMBOL" in the chart's own ids. A param.source default is any word of its menu, a blend such as hl2 included (A blended default). A param.multi takes at most 53 options (one bit each), and a param.range needs min and max.

By the setting you need

Setting you needwrun form
a number, a floatparam.number, or plain param
an integerparam.int, read whole
a sliderparam.int or param.number with min, max and slider: true
an on/off toggleparam.bool, read with pb_<name>()
a dropdownparam.choice, read as the picked index
a multi-selectparam.multi, read as a bitmask
a color pickerparam.color, bound to an output with color: "@name"
a palette the user picksone param.color per entry, each bound into a colors palette by "@name"; a color_by ladder then indexes the user's colors (Styling)
a source dropdownparam.source, which declares the input too; a second feed is a second input
a timeframe fieldparam.timeframe bound to an input with interval: "@name"; an interval pin written on the input needs no setting
a symbol fieldparam.symbol bound to an input with symbol: "@name"; a symbol + exchange pin written on the input needs no setting
a session fieldparam.session and inSession(...) per bar; a window fixed in the file is a Session from ./sdk/clock (Clock and sessions kit), and the integer math underneath is on Time and sessions
a text fieldparam.text (one line) or param.text_area (several lines), read with pt_<name>(); the words are plain text within max_bytes
a label, a step, a group, a tooltipthe options label, step, group, hint (Options on a setting)

What the dialog draws

a whole-number field that steps by one, with a hint glyph after its label
a number field with a unit picker beside it
a bounded whole number drawn as a slider with its readout
a toggle and a timeframe menu sharing one line of the dialog
a menu of the labels a choice lists
a color picker
a date and time field with its Pick button
a price field with its Pick button
one slider with two handles
a chip per option of a multi-select
an editable list of numbers with an Add button
a menu of the price fields a source can read
a market button that opens the symbol search
a day strip with the window shaded, a start and an end on the 24-hour clock, and a zone menu

A row that differs from its default shows a dot and a reset button that names the default.

Read it in onStart()

Every reader works from onStart() on: the chart serves the settings there, before the first bar, and a reader called later, in onBar() or a helper, returns the same cached value. Read each setting into a module variable in onStart() and use the variable per bar, or read it where you use it.

wrun
function onStart(): void {
  const n = i32(p_length());
  sma = new Sma(n);
  useEma = p_kind() == 1.0;
  bandsOn = pb_show_bands();
  lowPct = p_band_pct_lo();
  highPct = p_band_pct_hi();
}
KindReaders in ./gen/params
int, number, price, time, choice, multi, color, plain paramp_<name>(): f64
boolpb_<name>(): bool (and p_<name>(), 1 or 0)
rangep_<name>_lo(), p_<name>_hi()
listp_<name>(): f64[], the filled slots in order
sessionp_<name>_start(), p_<name>_end(), p_<name>_tz()
textpt_<name>(): string, the words (and p_<name>(), their UTF-8 byte count)
a unit pickerp_<name>() and p_<name>_unit(), the unit's code
market.tick_size(), market.price_precision()p_market_tick_size(), p_market_price_precision()
market.kind(), market.point_value(), market.zone(), market.quote_is_usd()p_market_kind(), p_market_point_value(), p_market_zone(), p_market_quote_is_usd() (The market's facts)
source, timeframe, symbolnone to call: the chart applies the pick

Text settings

A text setting is a field the user types words into: a label, a note, a list of tickers. Declare it like any other setting, and read the words once, in onStart(), with pt_<name>().

// A moving average with a corner readout that says what you typed in the settings.
param.int("length", 20, { min: 1, max: 500 });
// One line of text, at most 64 bytes.
param.text("label", "Session average", { max_bytes: 64, hint: "Shown in the corner of the chart" });
// Several lines of text. The readout shows the first line.
param.text_area("notes", "Buy the dip above it.\nStand aside below it.", { max_bytes: 512 });
// A preset may set a text setting like any other.
presets({ Fast: { length: 9, label: "Fast average" }, Slow: { length: 50, label: "Slow average" } });

output("average", line, overlay, { color: "#2563eb" });
// Sized for the label's 64 bytes plus a space and a number.
string("tag", { max_bytes: 96 });
string("note", { max_bytes: 512 });
render.table("corner", { rows: 2, cols: 1, cells: ["tag", "note"], position: "top_left" });

let average = new Sma(20);
let label: string = "";
let firstNote: string = "";

// Text settings are read once, here, through pt_<name>().
function onStart(): void {
  average = new Sma(i32(p_length()));
  label = pt_label();
  firstNote = pt_notes().split("\n")[0];
}

function onBar(): void {
  const value = average.update(bar.close());
  if (isNaN(value)) return;
  out_average(value);
  sb_clear();
  sb_text(label);
  sb_text(" ");
  sb_auto(value);
  str_tag_sb();
  str_note(firstNote);
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine
  • param.text draws a one-line field, and param.text_area a box for several lines.
  • pt_label() returns the typed words as a string. Read it in onStart() and keep it in a module variable.
  • The tag slot holds the label, a space and a number, so it is sized for the label's 64 bytes plus the rest. A line longer than its slot is refused, never cut.
  • A preset sets a text setting with its words: Fast: { label: "Fast average" }.

Options

OptionWhat it doesDefault
max_bytesthe most UTF-8 bytes the field takes (a plain letter is 1 byte, an accented one 2, most CJK characters 3)256, at most 4096
multilinekeeps newlines in a param.text field (param.text_area always does)false
label, hint, group, row, when, hide, confirm, required, descriptionthe words every setting takes (Options on a setting)

pt_<name>() reads the words. p_<name>() reads their length in bytes.

What the user can type

Plain text. Before your code reads the words, the chart and every other place the indicator runs clean them the same way:

  • Invisible characters go: right-to-left overrides, zero-width characters and the other format marks. A "Binance" disguised with them reads as plain "Binance".
  • Control characters go. On a one-line field each newline becomes a space.
  • Words longer than max_bytes are refused, never cut. The dialog says "Too long: 70 of 64 bytes. Shorten the text to apply it."

The words are never shown as a link or as markup, and they never fill a placeholder: a typed {{close}} stays those eight characters. Every text field carries the hint "Never paste keys or passwords here." The words are saved like any other setting and are never sent anywhere shared.

Limits and refusals

RuleWhat the build says
the default is a stringparam.text 'label' default must be a string literal (expected a string literal)
max_bytes is 1 to 4096param.text 'label' max_bytes takes an integer from 1 to 4096, not 5000
the default fits max_bytesparam.text 'label' default is 15 UTF-8 bytes, over its max_bytes 4
the default is plain textparam.text 'label' default carries a newline, a control or an invisible character; typed text is stripped of bidi controls, zero-width characters and control characters, so a default cannot carry them
a text field takes no number optionsparam.text 'label' takes no min option (its value is not a number field)
a text area is always several linesparam.text_area 'notes' is multi-line by definition; drop multiline: false (param.text takes the flag)
a preset sets wordspreset 'Fast' value for 'label' takes a string literal (a text setting), not 5
a preset fits max_bytespreset 'Fast' value for 'label' is 70 UTF-8 bytes, over the setting's max_bytes 64
history is sized from numberswarmup term names 'label', a text setting: a window is sized from numbers, so name a numeric param (length)

A text setting counts as one of the 128 settings. Declaring one moves the file to the wrun-5 contract (or keeps it on a later one) when you build (Versions and contracts); a place that cannot read text settings yet refuses the indicator by name instead of running it without the words.

Fourteen kinds in one module

An average of a picked price field with percent bands, an offset in a picked unit, a reference close on a picked timeframe, a ratio against a picked market, a floor line and a session filter over picked weekdays. Every setting is read in onStart() and kept in a module variable. Text, the fifteenth kind, has its own module under Text settings.

// int: a whole number.
param.int("length", 20, { min: 2, max: 500 });
// choice: a menu; the reader returns the picked index, 0 for Simple.
param.choice("kind", ["Simple", "Exponential"], "Simple", { label: "Average" });
// source: declares the primary input too; in_src() reads the picked field.
param.source("src", ohlcv.close);
// bool: a toggle, read as a bool.
param.bool("show_bands", true);
// range: a low..high pair on one slider.
param.range("band_pct", [0.5, 2.0], { min: 0, max: 10, step: 0.1, label: "Band width %" });
// number with a unit picker: the value and the unit's code.
param.number("offset", 0.0, { min: -100, max: 100, unit: ["price", "ticks", "%", "atr"], unit_default: "%" });
// A bounded int drawn as a slider: the ATR an atr-unit offset scales by.
param.int("atr_length", 14, { min: 2, max: 50, slider: true, label: "ATR length" });
// color: painted onto the basis line below, by name.
param.color("basis_color", "#2962ff");
// timeframe and symbol: each pins the input that names it.
param.timeframe("htf", "chart", { label: "Reference timeframe" });
input("close_htf", ohlcv.close, { interval: "@htf" });
param.symbol("pair", "BINANCE_FUTURES:ETHUSDT", { label: "Ratio against" });
input("close_pair", ohlcv.close, { symbol: "@pair" });
// list: up to four momentum lookbacks.
param.list("lookbacks", [5.0, 20.0], { max: 4 });
// time and price: both can be picked on the chart.
param.time("since", "2024-01-01 00:00", { label: "Start" });
param.price("floor", 0.0, { hint: "0 draws no floor" });
// session and multi: a window in its zone, and the weekdays it applies on.
param.session("rth", "09:30-16:00", { tz: "America/New_York", label: "Active hours" });
param.multi("days", ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], ["Mon", "Tue", "Wed", "Thu", "Fri"]);
// The tick size, written by the chart into a hidden setting.
market.tick_size();
output("basis", line, overlay, { color: "@basis_color", width: 2 });
output("upper", line, overlay, { color: "#26a69a" });
output("lower", line, overlay, { color: "#ef5350" });
output("reference", line, overlay, { color: "#94a3b8", line_style: "dashed" });
output("floor", line, overlay, { color: "#f59e0b" });
output("momentum", line, lower, { color: "#8b5cf6" });
output("ratio", line, lower, { color: "#0ea5e9" });

let sma = new Sma(20);
let ema = new Ema(20);
let atr = new Atr(14);
let rocs: Roc[] = [];
let useEma = false;
let bandsOn = true;
let lowPct = 0.5;
let highPct = 2.0;
let offset = 0.0;
let offsetUnit = 0;
let tick = 0.0;
let since = 0.0;
let floorPrice = 0.0;
let dayMask = 0.0;
let sessionStart = 570.0;
let sessionEnd = 960.0;
let zone = 0;

// Every setting is read here, once, and kept in module state.
function onStart(): void {
  const n = i32(p_length());
  sma = new Sma(n);
  ema = new Ema(n);
  useEma = p_kind() == 1.0;
  bandsOn = pb_show_bands();
  lowPct = p_band_pct_lo();
  highPct = p_band_pct_hi();
  offset = p_offset();
  offsetUnit = i32(p_offset_unit());
  atr = new Atr(i32(p_atr_length()));
  tick = p_market_tick_size();
  since = p_since();
  floorPrice = p_floor();
  dayMask = p_days();
  sessionStart = p_rth_start();
  sessionEnd = p_rth_end();
  zone = i32(p_rth_tz());
  const lookbacks = p_lookbacks();
  rocs = [];
  for (let i = 0; i < lookbacks.length; i += 1) rocs.push(new Roc(i32(lookbacks[i])));
}

function onBar(): void {
  const t = bar.time();
  const close = in_src();
  const reference = in_close_htf();
  const pair = in_close_pair();
  const simple = sma.update(close);
  const exponential = ema.update(close);
  let basis = useEma ? exponential : simple;
  const atrValue = atr.update(bar.high(), bar.low(), close);
  // The offset in the unit the dialog picked, as a price distance.
  if (isFinite(basis)) basis += unitToPrice(offset, offsetUnit, close, tick, atrValue);
  const ratio = isFinite(pair) && pair != 0.0 ? close / pair : NaN;
  let sum = 0.0;
  let count = 0;
  for (let i = 0; i < rocs.length; i += 1) {
    const r = rocs[i].update(close);
    if (isFinite(r)) {
      sum += r;
      count += 1;
    }
  }
  const momentum = count > 0 ? sum / f64(count) : NaN;
  // 0 = Monday, from the bar's UTC day: the bit the days setting is tested by.
  const weekday = i32((Math.floor(t / 86400.0) + 3.0) % 7.0);
  const active = inSession(t, sessionStart, sessionEnd, zone) && multiHas(dayMask, weekday);
  if (!isFinite(basis) || t < since) return;
  out_basis(basis);
  // A hidden line is NaN: nothing is drawn.
  out_upper(bandsOn ? basis * (1.0 + highPct / 100.0) : NaN);
  out_lower(bandsOn ? basis * (1.0 - lowPct / 100.0) : NaN);
  out_reference(reference);
  out_floor(floorPrice > 0.0 ? floorPrice : NaN);
  // Momentum draws inside the session window only.
  out_momentum(active ? momentum : NaN);
  out_ratio(ratio);
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine

Both averages update on every bar whatever the menu says, so the choice only picks which one writes basis; changing it in the settings dialog reruns the indicator over the loaded bars.

The Typed inputs tour card in the editor's template list (typed-inputs-tour) is this module with a two-page layout, presets, a hover card, a HUD and legend entries declared beside it (Plotting).

Gotchas

  • A kind that takes no option refuses it by name, on the declaration's line: param.bool 'x' takes no min option (its value is not a number field).
  • Each kind checks its own default's shape: a whole number for param.int, hex ("#rrggbb" or "#rrggbbaa") for param.color, one of the options for param.choice.
  • A setting outside min..max, or off its step grid (counted from min, or from 0 without one), is refused by name before the module runs: params.length must align to step (1) from min (1), got 14.5. The default is checked the same way, so keep it on the grid. The grid allows float rounding: a value a hair off a step (1002.000001 on a step of 1) passes as typed, so read a whole number with i32(p_length()).
  • A param.range needs min and max, and a param.list needs max, the most items the dialog may hold.
  • A param.source default may be a blend (hl2, hlc3, ohlc4, hlcc4): every place the indicator runs blends the bar's candle the same way.
  • source, timeframe and symbol are applied by the chart, in your browser only for now; an indicator that runs on OpenMarket's servers keeps the declared default (Picks, lanes, the cap).
  • Every refusal, with the words the Console prints: What the build checks.