---
title: "Declarations and the sheet"
description: "A wrun indicator declares what it reads, writes and draws as top-level statements at the top of its file, and Run derives the sheet from them: the description…"
order: 130
section: "reference"
---

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

# Declarations and the sheet

A wrun indicator declares what it reads, writes and draws as top-level statements at the top of its file, and **Run** derives the sheet from them: the description of the indicator the chart draws from and a published indicator carries. This page is the grammar of those statements, the rules the editor enforces, the sheet's fields and provenance, and the runtime contract the sheet records. What each declaration does on the chart is on the page that owns it, and every signature with every option is one table on [Quick reference](quick-reference.md).

## param

`param(name, default, { required?, min?, max?, description? })` is a number field, read from `onStart()` on through `p_<name>()`. `param.<kind>(name, default, options?)` is a typed setting (`int`, `number`, `bool`, `choice`, `color`, `time`, `price`, `range`, `multi`, `list`, `source`, `timeframe`, `symbol`, `session`, `text`), each naming the control the dialog draws, its literal default and its reader: [Setting kinds](../settings/kinds.md). `param.text` (one line) and `param.text_area` (several) take words, read once in `onStart()` through `pt_<name>()` ([Text settings](../settings/kinds.md#text-settings)). The options, and which kind takes each: [Options on a setting](../settings/options.md). `market.tick_size()`, `market.price_precision()`, `market.kind()`, `market.point_value()`, `market.zone()`, `market.quote_is_usd()`, `chart.interval_sec()`, `chart.bg_color()`, `chart.fg_color()`, `chart.up_color()`, `chart.down_color()` and `chart.grid_color()` declare hidden settings the host writes before `onStart()`, 0 where it cannot know one ([Chart context](../settings/sessions-and-units.md#chart-context)).

## input

`input(name, source.field, options?)` declares one per-bar number, read in `onBar()` through `in_<name>()`; the chart's own candle needs no line (`bar.close()` and the other `bar.*()` readers), and the first input is the bar grid. The sources, their fields and knobs: [Data sources](../core-concepts/data-sources.md). The pins: `symbol` with `exchange` for another market ([Multi-source](../core-concepts/multi-source.md)); `interval` with `view`, `views`, `offset` and `bars` for a coarser leg ([Multi-timeframe](../core-concepts/multi-timeframe.md)); `missing` for what a bar with no observation reads. `input(name, <class>.cells, { max_cells, ... })` declares a celled input, a block of tuples per bar. No input reads another indicator's outputs.

## output

`output(name, plot?, panel?, options?)` declares a per-bar number, written in `onBar()` with `out_<name>(value)`; an output not written that bar stays `NaN` and draws nothing there. `plot` is `line`, `bar`, `area`, `histogram`, `candle`, `shape`, `scatter` or `none` (data-only: computed, never drawn); `panel` is `overlay` (the price pane) or `lower` (a pane below it), per output. The options are style keys and presentation keys: [Plotting](../presentation/plotting.md), [Styling](../presentation/styling.md). The styling round's words ride the same literal: the plot-kind looks (`step`, `smooth`, `gradient`, `split` with `up_color`, `down_color`, `split_level`, `split_fill` and `split_base`, `fill_color`, `fill_opacity`, `fill_gradient`, `gradient_mode`, `fill_color_by` plus `fill_colors`, `fill_color_packed_by`, `base`, `grading`, `stack`, `candle_style`, `border_colors`, `wick_colors`, `border_width`, `shape`, `location`, `char`, `font_family`, `fill`, `role`, `align`, `pill_style`, `font_size`, each refused by name off the plot kinds that take it), `z` (-10..10, the paint order), `pane` (a declared pane's name or `"lower"`), `color_packed_by` (a packed rgba output per bar), `displacement_bars_by` (`{ param, scale?, offset? }`), and the presentation keys `decimals` (0..8) and `signed` (both need `format`, whose words are `price`, `%`, `si`, `int`, `0`, `0.0`, `0.00`, `0.000`, `usd` and `auto`; `unit` prints beside one) and `axis_name`; every one reaches the sheet only ([Plotting](../presentation/plotting.md#lines-areas-columns-dots-marks)). Four more ride the same way: `show_price_display` (the output's price-axis tag under `display({ price_display: "per_output" })`), `show_price_display_by` (an output whose value switches that tag per run), `omit_if_empty` (no legend row or tag when no bar drew) and `barcolor: true` on a `none` output (its value picks the candle tint from `colors`, or `color_packed_by` names it) ([Styling](../presentation/styling.md#tags-the-legend-and-the-candle-tint)). `output(...)` returns a handle; bind it with a top-level `const` when a `box`, `segment`, `hover` or `alert` needs to name it.

## string

`string(name, { max_bytes, description? })` declares a text slot, written per bar in `onBar()` through `str_<name>(text)` or the `sb_*` builder and `str_<name>_sb()`; a slot not written that bar is absent. Slots feed renderers, drawings, blocks and the Console, never a plot ([Strings and text](../functions/text-formatting.md)). A slot named exactly `debug` is the indicator's debug log in the editor's Console ([Debugging](../faq/debugging.md)).

## render and draw

`render.text`, `render.label`, `render.table`, `render.shape`, `render.stats_row`, `render.bgcolor` and `render.barcolor` place text, marks and tints over outputs and slots; `render.legend` adds a legend entry and `render.hud` a card. `draw.line`, `draw.box`, `draw.polyline` and `draw.label` with a name as the first argument each declare one drawing, placed from the newest bar. Numeric references name outputs; `text` and table `cells` name string slots. `render.table` also takes the look keys and per-cell `styles` ([Styled tables](../presentation/cards-frames-panels.md#styled-tables)), and `render.shape` takes `width`, the mark's size in pixels ([Mark size](../presentation/plotting.md#mark-size)). Renderers: [Plotting](../presentation/plotting.md); legend and cards: [Legend](../presentation/legend.md), [HUD and hover cards](../presentation/hud-and-hover-cards.md); drawings and handles: [Drawing objects](../presentation/drawing-objects.md).

Since the styling round, `render.text` and `render.label` take `style` from eight words (`plain`, `price_label`, `pill`, `callout`, `badge`, `box`, `knockout`, `emblem`) and the look keys: `align` and `valign` on plain text; `label_position`, `background_color`, `border_color` and `corner_radius` on a tag (a tag key on plain text, or `align` on a tag, is refused); `font_weight`, `font_family`, `emblem_shape`, `emblem_color` and `size_by` on both; a text renderer's `background_color_by` plus `background_colors`, `color_by` plus `colors` or `color_packed_by`, and `panel`; a label's `padding`. A `render.label` pins to a corner with `position` (one of the nine anchors) and `offset` (`[x, y]`, each -200..200 px) instead of `x` and `y` (declaring both is refused; `price_label` and `callout` are refused there). `render.shape` adds `location` (`absolute`, `above_bar`, `below_bar`, `top`, `bottom`), `glow`, `char` (one character, with and only with `shape: "char"`), `font_family`, `fill`, `fill_opacity` and `tooltip`; `render.stats_row` adds `color`, `colors`, `color_by`, `color_packed_by` and `priority` (1..3); `render.bgcolor` adds `width` (0.5..10, a vertical line per gated bar) and `line_style`; `render.hud` takes its surface, type and look words. Every `draw.*` declaration takes its handle kind's look words in snake_case: `extend`, `arrow`, `glow`, `glow_color`, `sticky_right`, `axis_label`, `opacity` and `tooltip` on a line; `border_color`, `border_width`, `border_style`, `corner_radius`, `shape`, `gradient`, `gradient_direction`, `text` (a string slot), `text_color`, `font_size`, `font_weight`, `font_family`, `align`, `valign` and `padding` on a box, whose `color` is the fill; `fill_color`, `closed` and `smooth` on a polyline; `style`, `background_color`, `max_width`, `angle`, `emblem_shape` and `emblem_color` on a label. Every one is a presentation key, stripped before the compiler, and since the styling round every `render.text`, `render.label`, `render.shape` and `draw.*` declaration is erased whole ([Plotting](../presentation/plotting.md), [Drawing objects](../presentation/drawing-objects.md)).

## box, segment and range

`box(name, { top, bottom, ... })` and `segment(name, { yFrom, yTo, ... })` declare per-bar shapes over output HANDLES; `range(upper, lower, options?)` declares a band between two rendered outputs (ranges never dedupe: repeat the declaration for several bands); its `edge_width` is 0..10 (`0` for no edge lines) and it also takes `legend` (false drops the band's legend row), `label`, `z`, `fill` (false draws the two edge lines with no interior), `show_price_display` (the band's tags) and `omit_if_empty` (no legend row or tag when no bar had both edges). `fill(a, b, options?)` shades the interior between two drawn outputs on one pane with no edge lines (`color`, `opacity` 0..1 with default 0.25, `color_by` plus `colors`, `color_packed_by`, `z`; only a top-level `fill(` statement is read, a `.fill(` method on a handle is something else), and `pane(name, options?)` declares a pane of the indicator's own, at most four (`title`, `place` `"below"` or `"price"`, `height_frac` 0.05..0.6, `scale` `"linear"`, `"log"`, `"percent"` or `"indexed"`, `invert`, `padding` `[top, bottom]`, `min`, `max`, `format`, `decimals`, `signed`, `unit`), joined by an output's `pane: "<name>"`. A box also takes `borderStyle`, `z` and the colour ladders `colorBy` plus `colors`, `colorPackedBy`, `borderColorBy` plus `borderColors` and `borderColorPackedBy` (output handles for the `By` keys; a ladder half without the other is refused). All of these are erased before the compiler. What the chart draws: [Drawing objects](../presentation/drawing-objects.md), [Styling](../presentation/styling.md).

## alert

`alert(name, { when, message?, description?, text?, every_bar?, title? })` declares a signal the chart's alert dialog offers: `when` is the handle of the output whose false-to-true edge fires it (a data-only `none` output works, so no 0/1 line has to be drawn), `message` the fire text with placeholders such as `{{symbol}}`, `{{close}}` and `{{<output>}}` (a declared output's value on the fired bar), `description` the picker's sub-line, both at most 200 characters. `text` names a string slot: the words the file writes into it on the fired bar are the message, sent as one plain line of at most 200 characters. `every_bar: true` fires on every bar `when` holds, once per bar at most; the default fires on the false-to-true edge only ([Alerts](../functions/alerts.md#alert-words-and-every-bar)). `title` is the words the dialog, an armed alert and its notification show for the signal, any characters, 1 to 120 of them; the name keeps letters, digits, `.`, `_` and `-`. The declaration adds nothing to the module. A worked signal: [Alerts](../functions/alerts.md).

## Layout words

`page(title)`, `section(title, { toggle?, collapsed?, when? })`, `divider()` and `note(text)` lay the settings dialog out in source order; `presets({ Name: { param: value } })` ships named settings sets; `legend({ title })` sets the legend's title template. Sheet-only, compiled to nothing: [Pages, sections, dividers, notes](../settings/layout.md), [Presets](../settings/presets.md), [Legend](../presentation/legend.md). `display({ axis?, price_display?, overlay?, mount_order? })`, at most once, sets the indicator's look on the chart as a whole: `axis: false` gives it no price axis of its own (an indicator on the price pane rides the chart's), `price_display: "per_output"` tags each plot on the price axis by its own `show_price_display`, `overlay: "offchart"` homes it in its own pane below the chart, and `mount_order: "first_value"` lists its plots in the order they first draw ([Styling](../presentation/styling.md#tags-the-legend-and-the-candle-tint)).

## Other words

`hover(handle, [block.*])` is an output's hover card ([HUD and hover cards](../presentation/hud-and-hover-cards.md)); `handles.*` sets handle defaults ([Drawing objects](../presentation/drawing-objects.md)); `frame`, `panel.*`, `plot.levels`, `draw.ladder`, `draw.feed`, `draw.meter` and `out.inset` declare JSON snapshots and the panels and widgets drawn from them ([Cards, frames and panels](../presentation/cards-frames-panels.md)); `strategy({ ... })` places orders through the engine's broker ([Strategies overview](../strategies/overview.md)). `plot.heatmap`, `plot.footprint`, `plot.tpo`, `plot.profile` and `plot.matrix` declare the price canvases and `out.grid` a block of data-only outputs a heatmap reads ([Price canvases](../presentation/price-canvases.md)); `chart.contrast_guard(false)` and `chart.stack_handles(true)` are top-level flags with one boolean literal each (a binding is refused): the first keeps the author's colours as written on a light chart, the second stacks the indicator's corner-anchored handle groups below other indicators' groups ([Colors](../functions/colors-kit.md#theme-tokens)).

## A file that uses them

Two settings, two inputs, three outputs and the debug slot, with the hooks that read and write them (the bar loop in full: [Execution model](../core-concepts/execution-model.md)):

```typescript sample=fn-script-definition
// One output in its own pane (lower), labelled with a unit.
param("period", 14, { min: 2, max: 200, description: "RSI Period" });
// An on/off setting is a 0/1 param.
param("show_raw", 1, { min: 0, max: 1, description: "Write the raw RSI as well as the smoothed one" });
// The first input is the bar grid the funding input is aligned to; bar.close() reads through it.
input("close", ohlcv.close);
// A funding input, aligned to the close's rows; the chart serves it as a percent.
input("funding", funding.rate_close, { description: "Funding rate in percent, as of the bar" });
output("rsi", line, lower, { unit: "%", color: "#7c3aed", width: 2, description: "RSI (14)" });
output("smoothed", line, lower, { unit: "%", color: "#38bdf8", width: 1, description: "RSI smoothed by a 5-bar EMA" });
output("funding_pct", line, overlay, { unit: "%", color: "#f59e0b", description: "Funding in percent, on the price pane" });
// The debug log: a string slot named debug, printed in the editor's Console.
string("debug", { max_bytes: 32 });

let rsi = new Rsi(14);
const ema = new Ema(5);
let showRaw: bool = true;

function onStart(): void {
  rsi = new Rsi(i32(p_period()));
  showRaw = p_show_raw() > 0.5;
}

function onBar(): void {
  const value = rsi.update(bar.close());
  const fundingRate = in_funding();
  if (isNaN(value)) return;
  const smoothed = ema.update(value);
  out_rsi(showRaw ? value : NaN);
  out_smoothed(smoothed);
  out_funding_pct(fundingRate);
  sb_clear();
  sb_text("rsi ");
  sb_f64(value, 2);
  str_debug_sb();
}
```

A `0` in `show_raw` writes `NaN` to the raw line, which draws nothing: that is how a boolean toggle hides a plot.

## The declaration grammar

A declaration is a top-level statement of your file: a call to one of the words above with a string-literal name, a literal default and a literal options object, nothing imported. The build reads them without running the code, derives the sheet, and generates the readers and writers (`p_*`, `pb_*`, `in_*`, `out_*`, `str_*`, `sb_*`, `fb_*`) from the same object, so the two cannot disagree. A file with `onBar()` and no `output(...)` is refused at Run. A file that exports `init`, `state`, `finalize` or `reset` is the four-function form; both forms derive the same sheet ([The four-function form](../core-concepts/execution-model.md#the-four-function-form)).

```typescript
param("period", 14, { min: 2, max: 200, description: "Lookback window" });
param.bool("show_raw", true, { label: "Raw line" });
input("close", ohlcv.close);
input("btc_close", ohlcv.close, { symbol: "BTCUSDT", exchange: "BINANCE_FUTURES" });
output("value", line, lower, { unit: "score", label: "Score", format: "0.00" });
```

### Bindings

`"@<param>"` as the value of `color`, an entry of `colors` or `line_style` on an output or a text, label or legend renderer binds that spot to a `param.color` (for `line_style`, a `param.choice` over `solid`, `dashed`, `dotted`); as `interval` or `symbol` on an input it binds the pin to a `param.timeframe` or a `param.symbol`; a `param.source` declares the input it reads. The build writes the setting's default in the reference's place before the compiler runs and records the binding on the sheet (`style_targets`, `interval_param`, `symbol_param`, `field_param`), so the module's bytes never depend on a setting ([The Style page](../settings/style-page.md), [Picks, lanes, the cap](../settings/picks-and-lanes.md)). The same reference works on a `range`, a `fill`, a `box` and a `segment` (their colours, palette entries, gradient stops and line or border styles), on an output's other colour words (`fill_color`, `fill_colors`, `fill_gradient`, `gradient`, `up_color`, `down_color`, `border_colors`, `wick_colors`), and on the style keys of a `plot.levels` profile, where a word key takes a `param.choice` over that key's words, a number key a `param.number` or `param.int` whose range lies inside the key's, and a boolean key a `param.bool`. A data-only output refuses a paint binding (a `barcolor` output, which paints the candles, takes one), and so do a `draw.*` object, a `handles.*` default, a panel series and a tile. Every colour reference also takes `"@<param>/<alpha>"`, a panel series colour excepted: the setting's default at that alpha (a decimal from 0 to 1, replacing the colour's own) lands in the reference's place, as `rgba(r, g, b, a)` where the colour takes `rgba()` (an output's `color` and `colors`, a renderer's colours but a stats row's, a legend entry, a range's colours and gradient, a fill's `color`, a box's `color` and `borderColor`, a segment's `color`, a mini-chart grid's colours) and as `#rrggbbaa` where it takes a colour word, and the binding records the alpha, so a recolour keeps the translucency. `ticks_per_bar: "@<param>"` on a `volume_profile.cells` input binds the profile's bucket size to a `param.int` within 1..500: the sheet carries its default and `ticks_per_bar_param`, and the chart fetches again when it changes.

### Sheet-only keys

An output's presentation keys, a renderer's `style`, `tooltip`, `hover` and `badges`, the plot-kind looks, `z` and `pane`, an input's `ticks_per_bar`, `currency` and a profile's `missing`, and the whole of `hover`, `range`, `fill`, `pane`, `box`, `segment`, `alert`, `display`, the chart flags, every `plot.*` canvas, the layout words, `render.legend`, `render.hud` and (since the styling round) every `render.text`, `render.label`, `render.shape` and `draw.*` declaration reach the sheet only: the build strips them before the compiler runs, so a declaration with them compiles to the same bytes as one without. A `const` bound to a `string(...)` or `output(...)` is a handle for naming, dropped the same way.

### Rules every declaration follows

Each is a named build error on the declaration's line:

- Names are string literals; defaults and option values are literals; the code never runs at build time.
- Names may carry capitals. Params, inputs, string slots and frames are stored lowercase, with every option and `{{placeholder}}` that names one; outputs keep their spelling; each accessor also answers to the file's spelling. Two names of one family that differ only by case are refused; boxes, segments and alerts keep their spelling, so `Zone` and `zone` are two boxes.
- Declarations are top-level statements; one anywhere else names the file and line.
- Indexes follow declaration order: the first `input(...)` is slot 0, the primary input, and reordering declarations reorders slots while the accessors stay name-attached. A candle field read through `bar.<field>()` is served by a declared input of exactly that feed, else appended after the declared inputs under its own name, in the order `open`, `high`, `low`, `close`, `volume`, `bar_t`; a file with no `input(...)` line gets `close` as slot 0.
- Box and segment coordinates, a section's `toggle` and `when`, a `hover` target and an alert's `when` are output handles bound by a top-level `const` (`let`, `var` and `export const` bind too); the sheet records the output's name, never the handle. A string literal where a handle goes, or an unbound handle, is refused.
- `color_by`, `width_by` and `shape_where` name a DIFFERENT declared output; `colors` goes with `color_by` and `widths` with `width_by`, each half without the other refused.
- A typed setting's options belong to its kind; `when` names a declared `param.bool`; a `"@<param>"` reference names a setting of the kind the spot needs; a preset sets sheet params only. A setting cannot take a name the chart keeps (`symbol`, `exchange`, `interval`, `transformations`, `ticksPerBar`, `currency`, `runMode`, `devViewerTier` and the overlay's own keys), start with `__style__`, or collide with a name another setting derives (`band_lo`, `rth_tz`, `offset_unit`, `market_tick_size`).
- The expanded sheet holds at most 128 params (a `range` counts two, a `session` three, a `list` its `max` + 1, a `unit` list one more); every other cap is on [Limits](limits.md).
- Names share one namespace across outputs, boxes, segments, renderers, drawings and alerts.
- A celled input requires `max_cells` (`candles` gets the larger of `bars` and 2 when it is left out) and `book` requires `block_size`; scalar-feed knobs are refused on a celled input.

## The sheet Run derives

When you press **Run**, the editor derives the sheet from the declarations, checks it against the schema, and compiles; a refusal prints in the Console on the declaration's line, and Run stops there. There is no hand-written sheet. Read it at the Console prompt (`sheet`) and in **Copy for LLM**; a published indicator carries it. The sheets on this page omit the provenance fields every derived sheet carries (`generated_from`, `source_digest`, and `wasm_sha256` once the module compiles). These declarations, in a tab titled "Context Skeleton":

```typescript
param("period", 14, { min: 2, max: 200, description: "Lookback bars" });
input("close", ohlcv.close);
input("btc_close", ohlcv.close, { symbol: "BTCUSDT", exchange: "BINANCE_FUTURES", description: "Fixed BTC reference market" });
input("funding_rate", funding.rate_close);
output("value", line, lower, { unit: "score" });
output("raw", none);
output("lagging", line, lower, { displacement_bars: -26 });
```

derive every top-level field a scalar sheet has:

```json
{
  "id": "context-skeleton",
  "name": "Context Skeleton",
  "params": [
    {
      "name": "period",
      "default": 14,
      "min": 2,
      "max": 200,
      "description": "Lookback bars"
    }
  ],
  "inputSources": {
    "close": { "source": "ohlcv", "field": "close" },
    "btc_close": {
      "source": "ohlcv",
      "field": "close",
      "exchange": "BINANCE_FUTURES",
      "symbol": "BTCUSDT"
    },
    "funding_rate": { "source": "funding", "field": "rate_close" }
  },
  "inputs": [
    { "index": 0, "name": "close" },
    {
      "index": 1,
      "name": "btc_close",
      "description": "Fixed BTC reference market"
    },
    { "index": 2, "name": "funding_rate" }
  ],
  "outputs": [
    {
      "index": 0,
      "name": "value",
      "plot": "line",
      "panel": "lower",
      "unit": "score"
    },
    { "index": 1, "name": "raw", "plot": "" },
    {
      "index": 2,
      "name": "lagging",
      "plot": "line",
      "panel": "lower",
      "displacement_bars": -26
    }
  ]
}
```

| Field | What Run writes |
| --- | --- |
| `id`, `name` | from the editor tab's title (`Context Skeleton` derives `context-skeleton` and `Context Skeleton`); `name` is shown in lists, and publishing names the package `@yourname/<name>` ([Publishing](../functions/publishing.md)) |
| `abi_version` | the runtime contract, picked from the declarations you use; ABSENT means `"wrun-1"` (below) |
| `generated_from`, `source_digest` | `"declarations"`, and the sha256 of the source the sheet came from |
| `wasm_sha256` | the compiled module's sha256, stamped when Run compiles it |
| `params` | `{name, default, required?, min?, max?, description?}` with unique names, in declaration order; values reach the module positionally in that order |
| `inputSources` | keyed BY INPUT NAME: the source, the field and the options the declaration set, an interval always as the long word (`FOUR_HOURS`) |
| `inputs` | `{index, name, description?}`, indexes contiguous from 0 in declaration order; index 0 is the PRIMARY input, which follows the chart's market and interval (a pin on it is refused; an `odds` input is the exception); a `time` source cannot be primary |
| `outputs` | `{index, name, description?, plot?, panel?, unit?, displacement_bars?, ...style, ...presentation}`, indexes contiguous from 0, unique names; `plot: ""` is a data-only output (declared `none`); `displacement_bars` draws the value written at bar i at bar `i + displacement_bars`, the module never shifting a row |

The chart reads no warm-up field: the bars where `onBar()` writes nothing are the warm-up. The sheet is DERIVED state, serialized canonically (an unchanged source rewrites nothing): edit the declaration, never the sheet.

## Typed settings on the sheet

A typed setting adds its `type` (`int`, `number`, `boolean`, `select`, `color`, `time`, `price`, `multi`, `symbol`, `text`) and the dialog's keys (`label`, `step`, `group`, `row`, `hint`, `when`, `hide`, `slider`, `confirm`; `options` and `option_labels` on a select, `multi_bits` on a multi, `unit` and `unit_default` beside a unit menu); a composite expands to its members (`range` / `range_end`, `list` / `list_index` / `list_end`, `session` / `session_end`, `unit_for`); a host-applied setting carries `host` and a symbol its `symbol_default`; a binding records `style_targets` (and `style_values` on a line-style choice); a `text` setting carries `max_bytes`, `multiline` and its words as `text_default` (its `default` is 0); `market.*` and `chart.*` carry `host_fill` and `hidden`. None of these change what reaches the module: one number per sheet param, a text setting's number being its words' byte count, with the words on the text channel. The declarations below, in a tab titled "Typed Skeleton":

```typescript
page("Signal");
param.int("period", 14, { min: 2, max: 200, label: "Length" });
param.choice("kind", ["Simple", "Exponential"], "Simple");
const show = param.bool("show_band", true);
section("Band", { toggle: show });
param.range("band_pct", [0.5, 2.0], { min: 0, max: 10, step: 0.1, label: "Band width %" });
param.color("band_color", "#94a3b8");
input("close", ohlcv.close);
output("value", line, overlay, { label: "Average", format: "price" });
output("band_hi", line, overlay, { color: "@band_color", label: "Band", legend: false });
presets({ Tight: { band_pct_lo: 0.2, band_pct_hi: 1 } });
```

derive six sheet params from five settings (the range is two), the color's default written onto the output it paints plus the binding on the param, the layout, and the preset:

```json
{
  "id": "typed-skeleton",
  "name": "Typed Skeleton",
  "params": [
    {
      "name": "period",
      "default": 14,
      "min": 2,
      "max": 200,
      "type": "int",
      "step": 1,
      "label": "Length"
    },
    {
      "name": "kind",
      "default": 0,
      "type": "select",
      "options": [0, 1],
      "option_labels": ["Simple", "Exponential"]
    },
    {
      "name": "show_band",
      "default": 1,
      "min": 0,
      "max": 1,
      "type": "boolean"
    },
    {
      "name": "band_pct_lo",
      "default": 0.5,
      "min": 0,
      "max": 10,
      "type": "number",
      "step": 0.1,
      "label": "Band width %",
      "range": "band_pct",
      "range_end": "lo"
    },
    {
      "name": "band_pct_hi",
      "default": 2,
      "min": 0,
      "max": 10,
      "type": "number",
      "step": 0.1,
      "label": "Band width %",
      "range": "band_pct",
      "range_end": "hi"
    },
    {
      "name": "band_color",
      "default": 9975030760,
      "type": "color",
      "style_targets": [{ "output": "band_hi", "property": "color" }]
    }
  ],
  "inputSources": { "close": { "source": "ohlcv", "field": "close" } },
  "inputs": [{ "index": 0, "name": "close" }],
  "outputs": [
    {
      "index": 0,
      "name": "value",
      "plot": "line",
      "panel": "overlay",
      "label": "Average",
      "format": "price"
    },
    {
      "index": 1,
      "name": "band_hi",
      "plot": "line",
      "panel": "overlay",
      "color": "#94a3b8",
      "label": "Band",
      "legend": false
    }
  ],
  "layout": [
    { "kind": "page", "title": "Signal" },
    { "kind": "param", "name": "period" },
    { "kind": "param", "name": "kind" },
    { "kind": "param", "name": "show_band" },
    { "kind": "section", "title": "Band", "toggle": "show_band" },
    { "kind": "param", "name": "band_pct_lo" },
    { "kind": "param", "name": "band_color" }
  ],
  "presets": [
    { "name": "Tight", "values": { "band_pct_lo": 0.2, "band_pct_hi": 1 } }
  ]
}
```

Underneath, the module still receives six numbers in this order: the length, the choice's index, 1 or 0 for the toggle, the two ends of the range, and the packed color.

## Pins on the sheet

A pin lands on the input's `inputSources` entry as declared; one the user can change carries the setting's default plus `interval_param` or `symbol_param`, a profile's bucket size `ticks_per_bar_param`, and a `param.source` input `field_param`. A volume profile declared `missing: "empty"` carries the word too, and so does a `trades` input's `currency`. The chart checks every pin before anything is fetched and refuses one it cannot serve by name. What each source may pin, and what a leg, a view, an offset or `bars` reads: [Multi-timeframe](../core-concepts/multi-timeframe.md); another market and the `missing` policy: [Multi-source](../core-concepts/multi-source.md).

Liquidations first, read as 0 on quiet bars, with the close carried beside them (the schema refuses `missing: "carry"` on the first input): `input("liqs", liquidations.liquidations, { missing: "zero", description: "Total liquidations, 0 on quiet bars" })`, `input("close", ohlcv.close, { description: "Close, carried" })` and `output("liq_ratio", histogram, lower)`, in a tab titled "Dense Liquidations", derive:

```json
{
  "id": "dense-liquidations",
  "name": "Dense Liquidations",
  "params": [],
  "inputSources": {
    "liqs": {
      "source": "liquidations",
      "field": "liquidations",
      "missing": "zero"
    },
    "close": { "source": "ohlcv", "field": "close" }
  },
  "inputs": [
    {
      "index": 0,
      "name": "liqs",
      "description": "Total liquidations, 0 on quiet bars"
    },
    { "index": 1, "name": "close", "description": "Close, carried" }
  ],
  "outputs": [
    { "index": 0, "name": "liq_ratio", "plot": "histogram", "panel": "lower" }
  ]
}
```

A fixed BTC reference on its own 4h candles, as of close: `input("btc_4h", ohlcv.close, { symbol: "BTCUSDT", exchange: "BINANCE_FUTURES", interval: "4h", description: "BTC on its own 4h grid, as of close" })` beside `input("close", ohlcv.close)` and `output("ratio", line, lower)`, in a tab titled "BTC 4h Context", derive the sheet below, the interval spelled `FOUR_HOURS`. On a 4h chart the pin equals the chart's interval and reads BTC row by row; on a coarser chart it is finer than the chart, and Run refuses it.

```json
{
  "id": "btc-4h-context",
  "name": "BTC 4h Context",
  "params": [],
  "inputSources": {
    "close": { "source": "ohlcv", "field": "close" },
    "btc_4h": {
      "source": "ohlcv",
      "field": "close",
      "exchange": "BINANCE_FUTURES",
      "symbol": "BTCUSDT",
      "interval": "FOUR_HOURS"
    }
  },
  "inputs": [
    { "index": 0, "name": "close" },
    {
      "index": 1,
      "name": "btc_4h",
      "description": "BTC on its own 4h grid, as of close"
    }
  ],
  "outputs": [{ "index": 0, "name": "ratio", "plot": "line", "panel": "lower" }]
}
```

An `odds` input names its own Polymarket market by condition id (`0x` + 64 hex) in `symbol` and may be the first input ([Data sources](../core-concepts/data-sources.md)). This sample pins the placeholder market the chart's prediction starters ship, so Run refuses it until you paste your market's condition id into `symbol`:

```typescript sample=fn-pm-momentum
param("period", 5, { min: 1, max: 200, description: "Momentum lookback, bars" });
// The market is its condition id (0x + 64 hex): paste yours over this placeholder.
input("yes_odds", odds.close, { symbol: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", outcome: "YES", description: "Polymarket YES probability" });
output("momentum", line, lower, { description: "Rate of change of the YES probability" });

let roc = new Roc(5);

function onStart(): void {
  roc = new Roc(i32(p_period()));
}

function onBar(): void {
  out_momentum(roc.update(in_yes_odds()));
}
```

## Boxes and segments (per-bar shapes)

Two top-level arrays, derived from `box(...)` and `segment(...)`, legal on every contract: they add nothing to the module, and the chart evaluates them on EVERY bar row. The sheet records output NAMES under snake_case fields: `boxes` (at most 16), each `{name, top, bottom, x_from?, x_to?, when?, panel?, color?, border_color?, opacity?, border_width?, border_style?, z?, color_by?, colors?, color_packed_by?, border_color_by?, border_colors?, border_color_packed_by?}` (the `_by` keys name declared outputs whose per-bar value picks an entry of `colors` or `border_colors`, 1..64 colours each; the `_packed_by` keys an output carrying a packed colour per bar), and `segments` (at most 16), each `{name, y_from, y_to, x_from?, x_to?, when?, panel?, color?, width?, line_style?}`. The coordinates name declared outputs (data-only allowed); `x_from` / `x_to` are bar offsets, a literal (whole or fractional: a fraction places the edge inside its bar, at that share of the bar's interval) or the name of an output whose truncated per-bar value is the offset; `when` names a gate output (the bar is skipped unless its value is finite and nonzero). Every refusal names the field (`boxes.0.top`). The defaults and what the chart draws: [Drawing objects](../presentation/drawing-objects.md).

## Declared alerts

`alerts` (at most 64), derived from `alert(name, { when, message?, description?, text?, every_bar?, title? })`, each `{name, when, message?, description?, text?, every_bar?, title?}`: `when` names a declared output (any plot, data-only `""` included), and the alert fires on the bar that output turns from false to true (true is finite and nonzero; a `NaN` row is false and re-arms it), or on every bar it holds when `every_bar` is `true`; `text` records the slot's name. Legal on every contract; nothing new reaches the module. The golden-cross declarations on [Alerts](../functions/alerts.md), in a tab titled "Golden Cross", derive:

```json
{
  "id": "golden-cross",
  "name": "Golden Cross",
  "params": [
    {
      "name": "fast",
      "default": 21,
      "min": 1,
      "max": 200,
      "description": "Fast EMA length"
    },
    {
      "name": "slow",
      "default": 55,
      "min": 2,
      "max": 400,
      "description": "Slow EMA length"
    }
  ],
  "inputSources": { "close": { "source": "ohlcv", "field": "close" } },
  "inputs": [{ "index": 0, "name": "close" }],
  "outputs": [
    {
      "index": 0,
      "name": "fast",
      "description": "Fast EMA of the close",
      "plot": "line",
      "panel": "overlay",
      "color": "#2563eb"
    },
    {
      "index": 1,
      "name": "slow",
      "description": "Slow EMA of the close",
      "plot": "line",
      "panel": "overlay",
      "color": "#f97316"
    },
    { "index": 2, "name": "golden_cross", "plot": "" }
  ],
  "alerts": [
    {
      "name": "golden_cross_up",
      "when": "golden_cross",
      "message": "{{symbol}} golden cross at {{close}}",
      "description": "Fast EMA crosses above slow EMA"
    }
  ]
}
```

## Settings layout, presets, presentation

Four more top-level keys, written only when the file declares them and read by the chart alone: `layout`, `presets`, `legend_title` and `presentation`. Legal on every contract; none reaches the module.

| Key | Shape |
| --- | --- |
| `layout` | the dialog in source order: `{kind: "page", title}`, `{kind: "section", title, toggle?, collapsed?, when?}`, `{kind: "divider"}`, `{kind: "note", text}`, `{kind: "param", name}` (a composite once, under its first member's name; a hidden `market.*` param takes no entry); absent when the file uses no layout word and no `group` option, and the dialog keeps one flat list |
| `presets` | `[{name, values: {param: number | boolean | string}}]` as declared; the chart turns each value into the setting's own form when a preset is picked |
| `legend_title` | the `legend({ title })` template |
| `presentation` | `{legend?: [...], hud?: [...]}` from `render.legend` and `render.hud`, each entry with the keys of its declaration, the tiles in the block shape an output's `hover` uses |

A `presentation.hud` entry carries its surface, type and look words as declared (`width`, `offset`, `z`, `background_color`, `corner_radius`, `font_family`, `look`, `accent_color`, `chrome`, `safe_area`, `mobile` and the rest). Two more top-level arrays and two flags travel the same way: `panes` (from `pane(...)`, each `{name, title?, place?, height_frac?, scale?, invert?, padding?, min?, max?, format?, decimals?, signed?, unit?}`, at most 4), `fills` (from `fill(...)`, each `{between: [a, b], color?, opacity?, color_by?, colors?, color_packed_by?, z?}`), `contrast_guard` (boolean, default true) and `stack_handles` (boolean, default false).

What the dialog draws from each: [Pages, sections, dividers, notes](../settings/layout.md), [Presets](../settings/presets.md), [Legend](../presentation/legend.md), [HUD and hover cards](../presentation/hud-and-hover-cards.md); the Style page is derived from the outputs, with no key of its own ([The Style page](../settings/style-page.md)).

## ABI versions: the five contracts

Run writes `abi_version` from the declarations you use, and the module is validated and run under that contract and no other. The first contract is FROZEN: published indicators on it keep running bit-identically. Each later contract is ADDITIVE over the one before, the same entry points and scalar block plus one channel family, and freezes in turn. `range`, `box`, `segment` and `alert` are contract-neutral: declaring one never flips the sheet; so are `fill`, `pane`, the chart flags and the cell-fed canvases (`plot.heatmap`, `plot.footprint`, `plot.tpo`, `plot.profile`), which ride whichever contract their input already needs.

| Contract | Stamped by | Adds |
| --- | --- | --- |
| `"wrun-1"` (the field absent) | scalar-only declarations | params, inputs, outputs |
| `"wrun-2"` | a celled input, a string slot, a renderer or a declared drawing | the cell and string channels, the renderer and drawing vocabulary (below) |
| `"wrun-3"` | a handle, `strategy(...)` or a `bar.isLast()` call | the handle-keyed draw channel, the strategy channel, the last-bar flag ([Drawing objects](../presentation/drawing-objects.md), [Strategies overview](../strategies/overview.md)) |
| `"wrun-4"` | a frame (`frame(...)`, `panel.*`, `plot.levels`, `plot.matrix`, a `draw.ladder` or `draw.feed` HUD) | the frame channel ([Cards, frames and panels](../presentation/cards-frames-panels.md)) |
| `"wrun-5"` | a text setting (`param.text`, `param.text_area`) | the text channel (`wrun_param_len`, `wrun_param_bytes`): a text setting's words, handed over before the first bar ([Text settings](../settings/kinds.md#text-settings)) |
| `"wrun-6"` | a `bar.count()` read | the bar count (`wrun_bar_count`): how many bars the run holds, so a bar can tell whether a later bar exists ([Execution model](../core-concepts/execution-model.md#how-many-bars-the-run-holds)) |

The second contract's sheet additions, as Run derives them from `input("profile", volume_profile.cells, { max_cells: 256 })` and `input("book", book.cells, { max_cells: 1000, block_size: 10 })` in a tab titled "Orderflow Context":

```json
{
  "id": "orderflow-context",
  "name": "Orderflow Context",
  "abi_version": "wrun-2",
  "params": [],
  "inputSources": {
    "close": { "source": "ohlcv", "field": "close" },
    "profile": { "source": "volume_profile" },
    "book": { "source": "book", "block_size": 10 }
  },
  "inputs": [
    { "index": 0, "name": "close" },
    { "index": 1, "name": "profile", "cellType": "array", "max_cells": 256 },
    { "index": 2, "name": "book", "cellType": "array", "max_cells": 1000 }
  ],
  "outputs": [
    { "index": 0, "name": "imbalance", "plot": "line", "panel": "lower" }
  ]
}
```

`inputs[].cellType: "array"` marks a celled input, and `max_cells` beside it counts the class's TUPLES (the cap the chart enforces: an oversize bar refuses the run, never truncates); `block_size` and `max_depth` are recorded, and the chart serves the book it has stored as it is. A celled input keeps its positional slot in the scalar block (reading `NaN`), can never be the primary input, and joins the chart's rows exactly: a bar with no observation is a PRESENT EMPTY block (`0` cells, not `-1`), never a carried value and never a withheld row. The classes and each block's rules: [Data sources](../core-concepts/data-sources.md#celled-sources), [Multi-timeframe](../core-concepts/multi-timeframe.md).

## String slots, renderers, drawings

Outputs stay numbers; the second contract adds three top-level arrays, derived from `string(...)`, `render.*` and `draw.*`, that turn numbers and per-bar text into chart decorations. Numeric coordinates name declared OUTPUTS, text fields name declared STRING SLOTS, and the two namespaces never substitute for each other.

| Array | Entries |
| --- | --- |
| `string_slots` (at most 64) | `{index, name, max_bytes, description?}`, indexes contiguous from 0, unique names; a slot not written that bar is ABSENT, distinct from a written empty string |
| `renderers` (at most 64) | `{kind, name, ...}` with the keys of the matching `render.*` declaration, `kind` one of `text`, `label`, `table`, `shape`, `stats_row`, `bgcolor`, `barcolor` |
| `stats_strip` (beside a `stats_row` renderer) | `{grading?, label_side?, theme?}`, the strip's look in its words ([Plotting](../presentation/plotting.md)); a `param.choice` paints a key it holds through `style_targets` and `style_values` |
| `drawings` (at most 64) | `{kind, name, ...}` with the coordinate outputs of the matching `draw.*` declaration, `kind` one of `line`, `box`, `polyline`, `label`; x values are epoch SECONDS |

What each renderer draws and which row wins: [Plotting](../presentation/plotting.md); a declared drawing is evaluated on the LAST row only: [Drawing objects](../presentation/drawing-objects.md). The expanded render selection must stay under 2 MiB per run, refused as `wrun_render_result_too_large`. A 2x2 session-stats table plus a box around the session's price range. The box's top and bottom are drawn as lines on the price pane: a package with a drawn `overlay` output is homed there, and a declared drawing follows the pane of its y output (`top` for a box), so the box frames the candles while `range_pct` keeps its own pane below. Data-only outputs never decide a pane, so the two x coordinates, bar times, stay `none`. The four cells are rewritten every bar so the table shows the last complete row:

```typescript sample=fn-session-table
output("range_pct", line, lower, { unit: "%", description: "Session range as a percent of its low" });
// The box's coordinates, read by name. Its top and bottom are drawn on the price pane:
// a drawn overlay output homes the package there, and the box follows its `top` output's
// pane, so it frames the candles rather than the percent line. The two times stay data-only.
output("left", none);
output("top", line, overlay, { color: "#f59e0b", width: 1, description: "Session high so far, the box top" });
output("right", none);
output("bottom", line, overlay, { color: "#f59e0b", width: 1, description: "Session low so far, the box bottom" });
// The table's four cells: byte-capped string slots rewritten every bar.
string("close_label", { max_bytes: 16 });
string("close_text", { max_bytes: 32 });
string("range_label", { max_bytes: 16 });
string("range_text", { max_bytes: 32 });
render.table("session_stats", { rows: 2, cols: 2, cells: ["close_label", "close_text", "range_label", "range_text"], position: "top_right" });
draw.box("session_zone", { left: "left", top: "top", right: "right", bottom: "bottom", color: "#f59e0b" });

let high: f64 = NaN;
let low: f64 = NaN;
let left: f64 = NaN;
let right: f64 = NaN;

function onBar(): void {
  const close = bar.close();
  if (isNaN(high) || close > high) high = close;
  if (isNaN(low) || close < low) low = close;
  const t = bar.time();
  if (isNaN(left)) left = t;
  right = t;
  if (isNaN(low) || low <= 0.0) return;
  const range = (100.0 * (high - low)) / low;
  out_range_pct(range);
  out_left(left);
  out_top(high);
  out_right(right);
  out_bottom(low);
  str_close_label("close");
  sb_clear();
  sb_f64(close, 2);
  str_close_text_sb();
  str_range_label("range");
  sb_clear();
  sb_f64(range, 2);
  sb_text("%");
  str_range_text_sb();
}
```

Because every coordinate output is finite on the last bar, the box exists with `createdBar` = that bar; a module that wants a drawing REMOVED writes `NaN` to a coordinate output on the newest bar. Decorations never change the numbers the outputs carry.

## Validation, in one list

When Run checks the derived sheet, the schema refuses, naming the path (the Console puts a param, input, output or string-slot refusal on the declaration it came from). The caps, each with its message, are on [Limits](limits.md); the messages with their fixes are in [Common errors](../faq/common-errors.md). Beyond the caps and the rules above, the schema refuses:

| Family | Refused |
| --- | --- |
| Params | duplicate names; a `type` outside the nine words; a composite with a member missing or doubled; `hide` without `when`; `unit_default` outside `unit`; a `multi` default past its bits; a `when` or `unit_for` naming no param; a `style_targets` output that is not drawn; `unset_at_default` on a target of a setting that is not a `param.color`, or on a non-colour target; a `stats_strip` target the strip does not hold, or painted by anything but a choice over the key's words; a preset or presentation name used twice |
| Inputs | non-contiguous or duplicate indexes and names; an input without a source entry, or the reverse; a `time` primary; a lone pin half; `outcome` or `binding` outside odds; a condition id that is not `0x` + 64 hex; a required knob missing (`tenor`, `side`, `token`, `fund`, `publisher`, `series`) or a knob on a source that does not take it; a `delta` other than 5, 15, 25 or 35; `fund: "all"` on `etf_premium`; a `missing` value outside `carry`, `nan` and `zero`, `missing: "carry"` on the primary input, or any `missing` on a celled or `time` source; unknown fields per source |
| Celled inputs | `cellType` without the second contract or without `max_cells`; `max_cells` without `cellType`; a celled class under a scalar input, as the primary input, or with a `field` or an `interval` pin (`intrabar` and `candles` excepted); `block_size` or `max_depth` anywhere but `book` |
| Outputs and ranges | non-contiguous or duplicate indexes and names; a `format` outside the ten, `decimals` or `signed` without `format`; a `line_style` or `edge_line_style` outside solid, dashed and dotted; a block whose output or slot does not exist; an output badging itself; a style reference to an output that does not exist; range sides equal or not rendered; a range `color_by` without `colors` (bare `colors` is legal: the band sign palette) |
| Boxes, segments, alerts | a coordinate, offset or `when` gate naming no declared output; a `panel` outside overlay and lower |
| String slots, renderers, drawings | any of the three under the first contract; non-contiguous or duplicate slot indexes and names; a reference to an output or string slot that does not exist; a bgcolor, barcolor or shape ladder half without the other; a bgcolor or barcolor `where` naming no declared output; a text or label `style` outside the eight, a tag key on plain text or `align` on a tag, a corner label with `x` and `y` beside `offset`; a HUD `position` outside the nine anchors or its `columns` outside 1 and 2, a HUD `look`, `chrome` or tile `draw` outside its list |
| Styling words | a `theme.` word outside the seven; a `unit` past 8 characters beside a `format` on a pane, panel, level, card, ladder or canvas (an output's `unit` stays unbounded); a `corner_radius` outside 0..32, a `font_weight` or `font_family` outside their words; a look word on a plot kind that does not take it (`step` on a bar, `gradient` beside `color_by`, `split` without both colours, `stack` beside `base`, `char` without `shape: "char"`); a `pane` naming no declared pane, a declared pane with nothing drawn in it or more than 4, a `fill` whose sides sit on two panes, a `z` outside -10..10; a box or fill ladder half without the other, or a packed key beside its `_by` key |
