Setting kinds
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
// 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 });| Kind | Declare it as | The dialog draws |
|---|---|---|
int | param.int(name, 20, { min, max }) | a number field that steps by 1 |
number | param. | a number field that steps by step |
bool | param. | an on/off toggle |
choice | param. | a menu of the labels |
color | param. | a color picker |
time | param. | a date and time field with Pick: the next click on the chart sets it |
price | param. | a number field with Pick: the next click on the chart sets the price |
range | param. | one slider with two handles |
multi | param. | a chip per option up to four options, a multi-select menu past that |
list | param. | an editable list of numbers with Add, up to max long |
source | param. | a menu of open, high, low, close, hl2, hlc3, ohlc4, volume, hlcc4 |
timeframe | param. | a menu of chart, 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w |
symbol | param. | a market button that opens the symbol search |
session | param. | a day strip with the window shaded, a start and an end on the 24-hour clock, and a zone menu |
text | param., or param. for several lines | a 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):
| Kind | onStart() reads |
|---|---|
int | p_length(), a whole number |
number | p_mult() |
bool | pb_show_bands(), a bool |
choice | p_kind(), the picked index (0 for the first label) |
color | p_basis_color(), the color packed into one number; most files bind it to an output instead (The Style page) |
time | p_since(), epoch seconds |
price | p_floor() |
range | p_band_pct_lo() and p_band_pct_hi() |
multi | p_days(), a bitmask (bit 0 is the first option); multiHas(mask, i) tests one |
list | p_lookbacks(), an f64[] of the filled slots |
source | nothing: it declares the input too, and in_src() reads the picked field per bar |
timeframe | nothing: { interval: "@htf" } on an input pins that input to the pick |
symbol | nothing: { symbol: "@pair" } on an input pins that input to the pick |
session | p_rth_start() and p_rth_end() (minutes from midnight), p_rth_tz() (the zone's index) |
text | pt_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 need | wrun form |
|---|---|
| a number, a float | param.number, or plain param |
| an integer | param.int, read whole |
| a slider | param.int or param.number with min, max and slider: true |
| an on/off toggle | param.bool, read with pb_<name>() |
| a dropdown | param.choice, read as the picked index |
| a multi-select | param.multi, read as a bitmask |
| a color picker | param.color, bound to an output with color: "@name" |
| a palette the user picks | one 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 dropdown | param.source, which declares the input too; a second feed is a second input |
| a timeframe field | param.timeframe bound to an input with interval: "@name"; an interval pin written on the input needs no setting |
| a symbol field | param.symbol bound to an input with symbol: "@name"; a symbol + exchange pin written on the input needs no setting |
| a session field | param.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 field | param.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 tooltip | the options label, step, group, hint (Options on a setting) |
What the dialog draws
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.
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();
}| Kind | Readers in ./gen/params |
|---|---|
int, number, price, time, choice, multi, color, plain param | p_<name>(): f64 |
bool | pb_<name>(): bool (and p_<name>(), 1 or 0) |
range | p_<name>_lo(), p_<name>_hi() |
list | p_<name>(): f64[], the filled slots in order |
session | p_, p_<name>_end(), p_<name>_tz() |
text | pt_<name>(): string, the words (and p_<name>(), their UTF-8 byte count) |
a unit picker | p_<name>() and p_<name>_unit(), the unit's code |
market., market. | p_, p_ |
market.kind(), market., market.zone(), market. | p_market_kind(), p_, p_market_zone(), p_ (The market's facts) |
source, timeframe, symbol | none 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);
}param.textdraws a one-line field, andparam.text_areaa box for several lines.pt_label()returns the typed words as astring. Read it inonStart()and keep it in a module variable.- The
tagslot 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
| Option | What it does | Default |
|---|---|---|
max_bytes | the 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 |
multiline | keeps newlines in a param.text field (param.text_area always does) | false |
label, hint, group, row, when, hide, confirm, required, description | the 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_bytesare 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
| Rule | What the build says |
|---|---|
| the default is a string | param.text 'label' default must be a string literal (expected a string literal) |
max_bytes is 1 to 4096 | param.text 'label' max_bytes takes an integer from 1 to 4096, not 5000 |
the default fits max_bytes | param.text 'label' default is 15 UTF-8 bytes, over its max_bytes 4 |
| the default is plain text | param.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 options | param.text 'label' takes no min option (its value is not a number field) |
| a text area is always several lines | param.text_area 'notes' is multi-line by definition; drop multiline: false (param.text takes the flag) |
| a preset sets words | preset 'Fast' value for 'label' takes a string literal (a text setting), not 5 |
a preset fits max_bytes | preset 'Fast' value for 'label' is 70 UTF-8 bytes, over the setting's max_bytes 64 |
| history is sized from numbers | warmup 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);
}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") forparam.color, one of the options forparam.choice. - A setting outside
min..max, or off itsstepgrid (counted frommin, 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 withi32(p_length()). - A
param.rangeneedsminandmax, and aparam.listneedsmax, the most items the dialog may hold. - A
param.sourcedefault may be a blend (hl2,hlc3,ohlc4,hlcc4): every place the indicator runs blends the bar's candle the same way. source,timeframeandsymbolare 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.