---
title: "Setting kinds"
description: "Fifteen kinds, one row each: the declaration, the control the dialog draws, the reader your code calls."
order: 23
section: "settings"
---

<!-- source: docs/indicators/settings/kinds.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# 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

```typescript
// 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.number(name, 2.0, { min, max, step })` | a number field that steps by `step` |
| `bool` | `param.bool(name, true)` | an on/off toggle |
| `choice` | `param.choice(name, ["Simple", "Exp"], "Simple")` | a menu of the labels |
| `color` | `param.color(name, "#2962ff")` | a color picker |
| `time` | `param.time(name, "2024-01-01 00:00")` | a date and time field with **Pick**: the next click on the chart sets it |
| `price` | `param.price(name, 0.0)` | a number field with **Pick**: the next click on the chart sets the price |
| `range` | `param.range(name, [0.5, 2.0], { min, max })` | one slider with two handles |
| `multi` | `param.multi(name, ["Mon", "Tue"], ["Mon"])` | a chip per option up to four options, a multi-select menu past that |
| `list` | `param.list(name, [5.0, 20.0], { max })` | an editable list of numbers with **Add**, up to `max` long |
| `source` | `param.source(name, ohlcv.close)` | a menu of `open`, `high`, `low`, `close`, `hl2`, `hlc3`, `ohlc4`, `volume`, `hlcc4` |
| `timeframe` | `param.timeframe(name, "chart")` | a menu of `chart`, `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1d`, `1w` |
| `symbol` | `param.symbol(name, "BINANCE:ETHUSDT")` | a market button that opens the symbol search |
| `session` | `param.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 |
| `text` | `param.text(name, "Session VWAP", { max_bytes })`, or `param.text_area(name, ...)` 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](style-page.md)) |
| `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](picks-and-lanes.md#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](../presentation/styling.md)) |
| 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](../functions/time-and-sessions-kit.md)), and the integer math underneath is on [Time and sessions](../core-concepts/time-and-sessions.md) |
| 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](options.md)) |

## What the dialog draws

![a whole-number field that steps by one, with a hint glyph after its label](/wrun/images/wrun-settings-int.svg)

![a number field with a unit picker beside it](/wrun/images/wrun-settings-number-unit.svg)

![a bounded whole number drawn as a slider with its readout](/wrun/images/wrun-settings-slider.svg)

![a toggle and a timeframe menu sharing one line of the dialog](/wrun/images/wrun-settings-bool-row.svg)

![a menu of the labels a choice lists](/wrun/images/wrun-settings-choice.svg)

![a color picker](/wrun/images/wrun-settings-color.svg)

![a date and time field with its Pick button](/wrun/images/wrun-settings-time.svg)

![a price field with its Pick button](/wrun/images/wrun-settings-price.svg)

![one slider with two handles](/wrun/images/wrun-settings-range.svg)

![a chip per option of a multi-select](/wrun/images/wrun-settings-multi.svg)

![an editable list of numbers with an Add button](/wrun/images/wrun-settings-list.svg)

![a menu of the price fields a source can read](/wrun/images/wrun-settings-source.svg)

![a market button that opens the symbol search](/wrun/images/wrun-settings-symbol.svg)

![a day strip with the window shaded, a start and an end on the 24-hour clock, and a zone menu](/wrun/images/wrun-settings-session.svg)

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.

```typescript
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_<name>_start()`, `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.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](sessions-and-units.md#the-markets-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>()`.

```typescript sample=fn-text-settings
// 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.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

| 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](options.md)) | |

`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

| 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](../reference/versions.md)); 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](#text-settings).

```typescript sample=fn-typed-inputs
// 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](../presentation/plotting.md)).

## 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](picks-and-lanes.md)).
- Every refusal, with the words the Console prints:
  [What the build checks](checks.md).

## Related

- [Options on a setting](options.md): the keys every kind takes, and which kind takes which
- [Pages, sections, dividers, notes](layout.md): where a row lands
- [Sessions and units](sessions-and-units.md): the session strip and the unit picker in full
- [What the build checks](checks.md): the refusal table
