Plotting
A wrun indicator draws by declaring, not by calling: it declares
output(name, plot, panel, options) once at the top of the file and
writes one number per bar, and the chart draws that number as a line, a
column, a mark, or a tint from the declaration.
That is why the module never touches a color, and why every drawn output is a value an alert can follow once the indicator is published (Alerts). This page maps everything you might want to draw to its wrun form, and compiles the forms that exist.
The map
| You want | wrun form | Notes |
|---|---|---|
| a plotted series | output(name, line | bar | scatter | candle, panel) | the plot kind is the declaration, not a runtime argument |
| a line | output(name, line, panel, { color, width, line_style, opacity, glow, step, smooth, gradient, split }) | a filled line is area; glow is a soft halo in pixels (Styling); smooth bends it into curves, step draws it as horizontal and vertical segments, gradient shades the stroke by height, split colours it above and below a level or by slope |
| columns | output(name, bar, panel, { width, base, grading, stack }) | one value per bar; width is a share of the bar slot (1 = columns touch), base the level they grow from, grading fades them by magnitude, stack stacks them on their siblings |
| a histogram | output(name, histogram, panel, { color_by, colors, corner }) | columns grow from zero (or from base); a sign ladder colors them; corner rounds them, in pixels |
| candles | four consecutive candle outputs (open, high, low, close), or a box and a segment per bar | the chart draws the four as one candle; candle_style on the first output draws them hollow, as OHLC bars or as high-low bars; see below |
| a mark on some bars | output(name, shape, panel, { shape, shape_where, location, char, fill, fill_opacity, glow }) or render.shape | either picks the mark from ten shapes (char draws one character); location sits it above or below the bar or at the pane's edge |
| several marks or a pie per bar | none: one thing per bar per renderer | a one-summary pie is a panel.pie snapshot; a per-level picture is a plot.levels frame (Cards, frames and panels) |
| text at a price | render. | one mark per bar whose slot was written; a style other than plain text draws each as a tag (price_label, pill, callout, badge, box, knockout, emblem) |
| a price tag | render. for one tag at the newest bar, or render.text with the same style for a tag per bar | a label's style is also plain, pill, callout, badge, box, knockout or emblem |
| a corner readout | render. pinned to one of the nine anchors, a one-cell render., or a render.hud card with one tile | fixed position at one of nine anchors; the newest bar that wrote the slot wins |
| an output on its own scale over the candles | pane(name, { place: "price" }) and output(name, plot, overlay, { pane: name }) | open interest or a cumulative delta drawn over price on a hidden scale of its own (Panes) |
| several panes below the chart | pane(name, { title, height_frac, scale, min, max, format }) and output(name, plot, lower, { pane: name }) | up to four named panes, stacked in declaration order (Panes) |
| a table | render. for a grid of words, styled by the same literal (widths, fills, lines, per-cell colours that follow an output; Styled tables); render. for a card of typed tiles | cells are string slots; tiles read outputs and slots |
| a rectangle | box(...) per bar, draw.box for one object, or a box handle | Drawing objects |
| a grid of mini charts pinned to the viewport | none | a dashboard is a render.table or a render.hud card; a snapshot panel below the chart is a panel.* frame (Cards, frames and panels) |
| a statistics-strip row | render. | a strip row over an output |
| a heatmap, a curve over a category or an index, tiles | panel.heatmap, panel.line over a category or index x, panel.tiles | a frame snapshot in its own pane below the chart (Cards, frames and panels) |
| a horizontal level | output(name, line, panel, { role: "guide", align?, pill_style?, font_size?, axis_label? }) written to the constant every bar | one full-width line at the output's last value, out of the legend; align puts its label on the line, axis_label: true on the axis; or a segment from: 0, to: 1 |
| a background tint | render. | a tint per bar where the gate is nonzero; with width a vertical line per gated bar instead of a band |
| colored candles | render. | the bar's own candle tinted where the gate is nonzero |
| a fill between two lines | range("a", "b", { color }) between two drawn outputs for a band with edges, fill("a", "b", { color, opacity }) for the interior only, or a box per bar | the chart draws the range as a band with its two edges and the fill as shading with none (Styling) |
| a name and a description | the output name and description | names are unique per family by construction |
Outputs
output(name, plot, panel, options) returns a handle a box or
segment can name; a bare statement discards it.
plot:line,bar,area,histogram,candle,shape,scatter, ornonefor a data-only output (computed, never drawn: the building block for gates, palettes, and shape coordinates).panel:overlay(the price pane) orlower(its own pane). Put small-magnitude series (oscillators, percentages, counts) inlower; a z-score on the price axis hugs the axis floor.- Static style:
color,width,opacity(0..1),line_style("solid","dashed","dotted"),glow(a halo in pixels on a line, an area, columns, candles or marks),z(the paint order inside the pane, an integer -10..10),description(documents the output). A colour is"#rrggbb","#rrggbbaa"or a theme token such as"theme.up"(Styling). - Numbers:
format(price,%,si,int,0,0.0,0.00,0.000,usd,auto),decimals(0..8),signedandunit(printed after the value; unbounded on an output, as it always was) shape the legend, the hover card and the axis tag;decimalsandsignedneedformat, and aunitprints only beside one (Styling). - Per-bar color:
color_by: "<data-only output>"pluscolors(at least two entries). Each bar's value indexes the palette (floored); a finite value outside the palette takes entry 0, and a non-finite one draws the static color.color_packed_by: "<output>"reads a packed colour the module computes per bar instead (never besidecolor_by). Ashapeoutput takes either ladder too: each mark in its bar's colour. - Per-bar width:
width_bypluswidths(1..10 entries, each 0.5..20), the same ladder for line width. Onbarandhistogramoutputswidthandwidthsare a share of the bar slot (widthsentries from 0.05): 1 means the columns touch, 0.5 half the slot, 2 overlap. - Gated marks:
plot: shapewithshape_where: "<gate output>"draws a mark at the output's value only on bars where the gate is nonzero. - Axis tags:
axis_label: truetags this output's last value on the price axis (absent, only the first mounted output is tagged);axis_name: trueadds the indicator's name to that tag (plus the output's label when several outputs are tagged);price_line: truedraws a dotted line across the pane at the last value. The three are refused on a data-only output. pane: "<name>"puts the output in a named pane, declared withpane(...)below; absent, aloweroutput lands in the default pane.displacement_bars(an integer in -500..500) makes the chart draw the series shifted left or right without changing its values; past the loaded edge it extrapolates at the bar spacing.displacement_bars_by: { param: "<name>", scale?: 1, offset?: 0 }reads the shift from a setting instead:round(scale * value + offset)bars, clamped to -500..500, which wins overdisplacement_barswhile the setting is a finite number.
An output cannot color, widen, or gate itself: the palette index and the
gate must be different outputs. The rest of the vocabulary is on the
Styling page. The legend shows each output's label, or
its name read as words when there is none (bb_upper reads "Bb upper"),
and its value in the output's format; legend: false keeps an output
out of the legend, and legend({ title: "..." }) adds words after the
indicator's name in the legend (Legend).
Panes
pane(name, options) declares a pane of the indicator's own, at most
four per indicator, and pane: "<name>" on an output puts the output in
it. The name "lower" styles the default pane; any other name is a new
pane below the chart, stacked in declaration order, the default pane
first unless it is declared later. These time-series panes sit at the
bottom of the indicator's stack, next to the time axis; the indicator's
panels (panel.line, panel.bars and the rest, on
Cards, frames and panels) stack
above them. A declared pane with nothing drawn in it is refused, and so
is a pane beside a declared overlay, with one exception: beside
display({ overlay: "offchart" }) the indicator lives in the default
pane, so pane("lower", { format: "0.00" }) formats that pane's axis
(format, decimals, signed and unit only; any other pane or word
is refused). The options, every one optional:
pane("rsi", { title: "RSI {{period}}", height_frac: 0.2, min: 0, max: 100 });
pane("oi", { place: "price", scale: "log", format: "si" });
output("rsi", line, lower, { pane: "rsi", color: "#7c3aed" });
output("oi", line, overlay, { pane: "oi", color: "#f59e0b" });title(1..40 characters,{{param}}reads a setting): the pane's legend title. An indicator that homes below the chart (no drawn output on the price pane) keeps its own name on the pane holding its first drawn output, so that pane takes no title.place:"below"(the default) or"price". A pane placed on price draws its outputs over the candles on a hidden scale of their own (open interest or a cumulative delta on price); its outputs declareoverlay, a pane placed below takesloweroutputs, and the pair must match.height_frac(0.05..0.6): the pane's share of the chart height, refused on a pane placed on price. Absent, a pane below an on-price indicator takes 0.22 and an indicator that homes below keeps the chart's default pane size.scale:"linear"(the default),"log","percent"or"indexed";invert: trueflips the axis;padding: [top, bottom](each 0..0.4) sets the autoscale margins;minandmaxhold either end of the autoscale (RSI:min: 0, max: 100,minbelowmax; a log scale refusesminat or below 0). Dragging the axis by hand still works.format,decimals,signed,unit: the pane's axis ticks, the same four words an output takes (decimals,signedandunitneedformat). Absent, a lower pane prints K, M and B from 1,000 and its own precision below that.
An inverted pane turns a loss into something that hangs: write a drawdown as a positive percent and flip the axis, so zero sits at the top of the pane and the deepest loss reaches lowest, with the running peak drawn over price above it. The pane keeps a title because the indicator homes on the price pane.
output("peak", line, overlay, { color: "theme.muted", line_style: "dotted" });
pane("dd", { title: "Drawdown", height_frac: 0.18, invert: true, min: 0, padding: [0.02, 0.1], format: "%", decimals: 1 });
output("drawdown", area, lower, { pane: "dd", color: "#ef4444", fill_color: "#ef4444", fill_opacity: 0.25 });Ranges, fills, boxes, segments and renderers follow the pane of the
output they anchor to, and a card, feed or meter with panel: "lower"
sits in the indicator's lower pane too.
Lines, areas, columns, dots, marks
A line is a line output; a filled line is area. Columns are bar,
and a histogram is columns grown from zero, colored per bar through a
color_by ladder over a sign output (value >= 0 ? 1 : 0); base
grows the columns from another level instead (colors[0] at or above
it, colors[1] below). Dots are scatter. A mark on some bars is a
shape output plus a shape_where gate: write the price every bar and
let the gate decide which bars draw. A level such as 70 is a guide: a
line output with role: "guide" written to 70 on every bar, drawn
as one full-width line at its last finite value, kept out of the legend
and inside the pane's autoscale (an alert can still follow it). align
("left", "center", "right") puts the output's label on the line,
pill_style ("filled", the default, or "outlined") and font_size
(6..64) dress that label, and axis_label: true puts the value and the
label on the axis instead (never both). A guide takes no color_by,
color_packed_by, width_by or price_line.
The looks a line, an area, a column and a mark take beyond colour and width (the words are opt-in; absent, the output draws as it always has):
| Plot | Words | What they draw |
|---|---|---|
line | step: true | horizontal then vertical segments (trailing stops, funding steps); refused beside smooth |
line, area | smooth: true | the line (and an area's edge) bent into curves |
line | gradient: [...] | the stroke shaded by height, 2 to 8 colours listed from the top of the pane to the bottom; never beside color_by, color_packed_by or split |
line | split: "level" | "slope", up_color, down_color, split_level?, split_fill?, split_base? | "level": up_color above split_level (default 0), down_color below, and split_fill: true shades down to split_base (default split_level); "slope": up_color while rising, down_color while falling; both colours are required with split |
area | fill_color, fill_opacity, fill_gradient: [...], gradient_mode | the fill's own colour (default the line colour) at fill_opacity (0.4 on a flat fill, 1 on gradient stops), or 2 to 8 stops top to bottom laid over "pane" (the default), "fill" or "line"; fill_color and fill_gradient exclude each other |
area | fill_color_by + fill_colors, or fill_ | the fill coloured per bar by its own ladder (name the line's color_by output to follow the line); fill_opacity and opacity multiply each colour |
bar, histogram | width, base, grading: "linear" | "square", stack: "<name>" | width as a share of the bar slot; base the level the columns grow from; grading fades each column by its magnitude within the visible range; bars sharing a stack name stack in declaration order, positives above 0 and negatives below (never beside base, and every member on one pane) |
shape, scatter | shape, location, char, font_family, fill, fill_opacity | shape picks the mark (circle, the default, cross, triangle_up, triangle_down, diamond, arrow_up, arrow_down, flag, square, char); location is "absolute" (the default), "above_bar", "below_bar", "top" or "bottom"; shape: "char" with char (one character) draws that character in font_family (ui, mono, serif, rounded); fill: false draws the outline only and fill_opacity fades the interior, neither on a char mark |
| every drawn plot | glow, opacity, z | a halo in pixels; the opacity, which now fades every colour the output draws (ladders, packed colours, a column's sign pair, a candle's colours, marks, gradient stops); the paint order inside the pane |
triangle_down points down (apex at the bottom), triangle_up up.
param("fast", 9, { min: 1, max: 200 });
param("slow", 21, { min: 2, max: 400 });
output("fast", line, overlay, { color: "#2563eb", width: 2, description: "Fast average, a line" });
output("slow", area, overlay, { color: "#94a3b8", opacity: 0.3, description: "Slow average, drawn as an area" });
output("cross_mark", shape, overlay, { color: "#16a34a", shape_where: "crossed", description: "A mark on the low of each bullish-cross bar" });
output("crossed", none, overlay, { description: "1 on the bar the fast average crosses above the slow: the mark's gate" });
output("volume", bar, lower, { color: "#4ecdc4", description: "Volume as bars" });
output("delta", histogram, lower, { color_by: "delta_sign", colors: ["#ef5350", "#26a69a"], description: "Fast minus slow as columns, colored by sign" });
output("delta_sign", none, lower, { description: "0 negative, 1 positive: the histogram's palette index" });
output("spread", scatter, lower, { color: "#f97316", description: "Close minus fast, as dots" });
output("rsi", line, lower, { color: "#7c3aed", width: 2, description: "RSI" });
output("overbought", line, lower, { color: "#ff0000", width: 1, line_style: "dashed", description: "The 70 level, written on every bar" });
let fast = new Sma(9);
let slow = new Sma(21);
let rsi = new Rsi(14);
const cross = new Cross();
function onStart(): void {
fast = new Sma(i32(p_fast()));
slow = new Sma(i32(p_slow()));
rsi = new Rsi(14);
}
function onBar(): void {
const close = bar.close();
const low = bar.low();
const volume = bar.volume();
const fastValue = fast.update(close);
const slowValue = slow.update(close);
const rsiValue = rsi.update(close);
const crossed = cross.update(fastValue, slowValue);
if (isNaN(slowValue)) return;
const delta = fastValue - slowValue;
out_fast(fastValue);
out_slow(slowValue);
out_cross_mark(low);
out_crossed(crossed == 1 ? 1.0 : 0.0);
out_volume(volume);
out_delta(delta);
out_delta_sign(delta >= 0.0 ? 1.0 : 0.0);
out_spread(close - fastValue);
out_rsi(rsiValue);
out_overbought(70.0);
}cross_mark is written on every bar (the bar's low) and drawn only where
crossed is 1. The gate is itself an output, so a declared alert can
fire on it (Alerts). A two-line fill colored by sign (a
momentum fill) is this delta histogram, or a range() between the two
lines with a two-entry colors sign palette (Styling).
The other looks in the table, one coherent look per declaration. An
area's fill can fade: list the stops top to bottom and lay them over the
fill's own extent, so the colour is strongest at the line and gone at the
floor; axis_name puts the indicator's name on the axis tag beside the
value.
// Cumulative delta as an area: amber at the line, nothing at the pane floor, the indicator's name on the axis tag.
output("cvd", area, lower, { color: "#f59e0b", width: 2, smooth: true, fill_gradient: ["#f59e0b", "#f59e0b00"], gradient_mode: "fill", fill_opacity: 0.9, axis_label: true, axis_name: true, format: "si" });When the line already follows a ladder, the fill can follow the same one:
name the ladder output again on fill_color_by and give fill_colors a
rung per colour, so the edge and the fill switch together.
// RSI as an area: oversold, neutral and overbought are the three rungs of one ladder, read by the edge and by the fill.
output("rsi", area, lower, { color_by: "zone", colors: ["theme.up", "theme.muted", "theme.down"], fill_color_by: "zone", fill_colors: ["theme.up", "theme.muted", "theme.down"], fill_opacity: 0.3, smooth: true });
output("zone", none, lower, { description: "0 under 30, 1 between, 2 over 70: the index both ladders read" });A split line colours itself around a level or by slope, and split_fill
shades between the line and split_base, so an oscillator reads as a
two-tone area that reaches the floor instead of stopping at the split
level.
// A money flow oscillator on a 0..100 pane: green above 50, red below, the shading hanging from the line down to 0.
output("mfi", line, lower, { width: 2, smooth: true, split: "level", split_level: 50, up_color: "theme.up", down_color: "theme.down", split_fill: true, split_base: 0 });Columns that share a stack name pile up in declaration order, so two
volumes become one column per bar, the second share on top of the first;
grading fades the small columns.
// Buy and sell volume piled into one column per bar.
output("buy_volume", bar, lower, { color: "theme.up", width: 0.8, stack: "volume", grading: "linear" });
output("sell_volume", bar, lower, { color: "theme.down", width: 0.8, stack: "volume", grading: "linear" });A mark can be softened and lit: fill_opacity thins its interior and
glow puts a halo around it (render.shape takes the same two words),
which keeps a signal visible without hiding the candle under it.
// A faded diamond with a halo above each signal bar.
output("signal_mark", shape, overlay, { shape: "diamond", shape_where: "fired", location: "above_bar", color: "#f59e0b", fill_opacity: 0.4, glow: 6 });
output("fired", none, overlay, { description: "1 on the bars that fire the signal: the mark's gate" });A mark can also be a character: shape: "char" with char draws that
one character in font_family, here under each bar that crosses a
displaced average whose shift follows a setting through
displacement_bars_by (Outputs).
param.int("shift", 5, { min: -50, max: 50, label: "Shift (bars)" });
// A displaced average, and a character mark under each bar that crosses it.
output("dma", line, overlay, { color: "#38bdf8", width: 2, displacement_bars_by: { param: "shift" } });
output("cross_mark", shape, overlay, { shape: "char", char: "✕", font_family: "mono", shape_where: "crossed", location: "below_bar", color: "theme.text" });
output("crossed", none, overlay, { description: "1 on the bar the close crosses the displaced average: the mark's gate" });Columns can grow from a level other than zero, and a guide's on-line label can be an outlined pill rather than a filled one: RSI as columns from 50, the sign pair colouring them above and below it, with the 70 level labelled on the left.
// RSI as columns grown from 50, and the 70 level as a guide with an outlined pill.
output("rsi_bars", histogram, lower, { base: 50, colors: ["theme.up", "theme.down"], width: 0.6, grading: "square" });
output("overbought", line, lower, { role: "guide", align: "left", pill_style: "outlined", font_size: 10, color: "theme.muted", line_style: "dashed" });Candles
An output is one number per bar, so a candle takes four: declare four
consecutive candle outputs in the order open, high, low, close, and the
chart draws them as one candle, colored by the first output's colors
(bullish, then bearish) or its color_by ladder. A candle output that
does not start four consecutive candle outputs is refused when the chart
draws it ("candle output '
output("ha_open", candle, overlay, { colors: ["#4caf50", "#f44336"] });
output("ha_high", candle, overlay);
output("ha_low", candle, overlay);
output("ha_close", candle, overlay);The group's first output carries the candle look, and the other three
refuse these words by name: candle_style is "candle" (the default),
"hollow" (up candles outlined), "ohlc" (OHLC bars) or "high_low"
(high-low bars); border_colors and wick_colors take one colour for
both directions or [up, down] (absent, border and wick follow the body
colour); border_width is 0..10 (default 1); opacity fades every
candle colour, and glow puts a halo on the candles. The legend lists a
candle group as one entry reading its open, high, low and close
(Legend).
Hollow candles over the chart's own: the first output carries the look,
the borders and the wicks take colours of their own, and opacity lets
the real bars show through the group.
// Heikin Ashi drawn hollow: up candles outlined, down candles filled, one grey wick colour for both.
output("ha_open", candle, overlay, { colors: ["theme.up", "theme.down"], candle_style: "hollow", border_colors: ["theme.up", "theme.down"], wick_colors: ["theme.muted"], border_width: 1.5, opacity: 0.85 });
output("ha_high", candle, overlay);
output("ha_low", candle, overlay);
output("ha_close", candle, overlay);Or as a body box and a wick segment per bar, which draws the candle from the same four numbers and gives each part its own color:
const haOpen = output("ha_open", none, overlay, { description: "Heikin Ashi open" });
const haHigh = output("ha_high", none, overlay, { description: "Heikin Ashi high" });
const haLow = output("ha_low", none, overlay, { description: "Heikin Ashi low" });
const haClose = output("ha_close", none, overlay, { description: "Heikin Ashi close" });
const bullish = output("bullish", none, overlay, { description: "1 when the smoothed close is above the smoothed open" });
const bearish = output("bearish", none, overlay, { description: "1 otherwise" });
// The body: one box per bar between open and close, one declaration per color.
box("body_up", { top: haClose, bottom: haOpen, when: bullish, color: "#4caf50", opacity: 0.9, borderWidth: 0 });
box("body_down", { top: haOpen, bottom: haClose, when: bearish, color: "#f44336", opacity: 0.9, borderWidth: 0 });
// The wick: a vertical segment from the high to the low on the same bar.
segment("wick", { yFrom: haHigh, yTo: haLow, color: "#9ca3af", width: 1 });
let prevOpen: f64 = NaN;
let prevClose: f64 = NaN;
function onBar(): void {
const o = bar.open();
const h = bar.high();
const l = bar.low();
const c = bar.close();
const close = (o + h + l + c) / 4.0;
// The first bar seeds the open from the raw bar; after that it is the previous smoothed midpoint.
const open = isNaN(prevOpen) ? (o + c) / 2.0 : (prevOpen + prevClose) / 2.0;
const high = Math.max(h, Math.max(open, close));
const low = Math.min(l, Math.min(open, close));
prevOpen = open;
prevClose = close;
out_ha_open(open);
out_ha_high(high);
out_ha_low(low);
out_ha_close(close);
out_bullish(close >= open ? 1.0 : 0.0);
out_bearish(close < open ? 1.0 : 0.0);
}The four none outputs draw nothing on their own; the chart shows only
the boxes and wicks. A data-only output is not offered as an alert
condition, so write a drawn output (or declare an alert over the gate)
for anything you want to alert on (Alerts). The recursion
the sample writes by hand ships as HeikinAshi in ./sdk/ta-plus:
update(open, high, low, close) fills the fields open, high, low,
close and works over another market's candles too
(Extra indicators).
Text, labels, tables, strips, tints
Text does not travel as an output. A string("name", { max_bytes })
declaration adds a byte-capped slot the module writes once per bar in
onBar() through its generated senders (nothing to import): build a
line with sb_clear(), sb_text("..."), sb_int(n), sb_f64(x, decimals) and send it with str_<name>_sb(), or send a whole string with
str_<name>("..."). Renderers then place the text:
| Renderer | Draws |
|---|---|
render. | one text mark per bar whose slot was written, at (bar, y); plain text is placed by align and valign, sized per bar by size_by, coloured per bar by its ladder; a tag style (price_label, pill, callout, badge, box, knockout, emblem) takes the tag words instead: label_position, background_ (or background_ + background_), border_color, corner_radius, emblem_shape, emblem_color |
render., or render. | ONE label at (x, y), x an output in epoch seconds, or pinned to one of the nine anchors by position and nudged by offset ([x, y] px, each -200..200, pointing inward from the anchored edges: a positive x moves a right-anchored label left, a positive y moves a bottom-anchored label up); the newest bar that wrote a nonempty slot (with finite x and y, when placed by them) wins; the look words are background_, border_color, corner_radius, padding (0..64, default 4), font_weight, font_family, emblem_shape, emblem_color, size_by |
render. | a grid of string slots (rows * cols names, row-major); the newest bar where every cell was written wins; the look keys (width, column widths, fills, lines, font, alignment, header rows) and styles (one entry per styled cell) are on Styled tables |
render. | a shaped mark per bar at the output's value where the gate is nonzero, in color, or in the colors entry a color_by output picks per bar; width (px, any positive number; the engine clamps what it paints) is the mark size, location sits it "absolute", "above_bar", "below_bar", "top" or "bottom", glow adds a halo, shape: "char" with char draws one character in font_family, fill: false draws the outline only, fill_opacity fades the interior, tooltip is a template shown on the mark |
render. | a row in the statistics strip under the price pane, one cell per bar; colors is [bull, bear] on a diverging row or the ladder color_by indexes; priority 1..3 (default 2) keeps a row's text longest as bars narrow; visible: false draws no row at all |
render. | a background tint per bar where the gate is nonzero, static or by ladder; with width (0.5..10 px) a vertical line per gated bar through the pane instead of a tint, dashed by line_style |
render. | the bar's own candle (body and wick) tinted where the gate is nonzero, static or by ladder; untinted bars keep the chart's candle colors |
size is an integer pixel count, 6..64 (10 reads as small text, 16
as large). A shape's color_by + colors ladder follows the
bgcolor rules: the output's value is floored into colors, a finite
index outside the palette takes entry 0, a non-finite value draws no mark
(the static color never substitutes), and either half without the other
is refused. position on a table is one of nine
anchors (top_left, top_center, top_right, middle_left,
middle_center, middle_right, bottom_left, bottom_center,
bottom_right). format and polarity on a stats row ride to the chart
verbatim (si, signedSi, percent, price, raw; magnitude,
diverging, none). visible on a stats row (default true) takes
true, false or an "@<param>" reference to a param.bool: the
setting's default draws, the setting switches the row from the settings
dialog, and while it is off the row draws nothing at all, not even an
empty strip line. One setting may switch several rows. The strip's own
look is one sheet key beside its rows, stats_strip: grading (the
population each cell's heat is ranked against: rolling, the default,
daily, weekly, visible or whole), label_side (left or
right, the edge the label gutter sits on) and theme (candle, the
chart's candle colours, or teal_rose, viridis, inferno,
blue_red, mono). A param.choice paints a key through
style_targets ({ "path": ["stats_strip", "grading"] }) and
style_values over that key's words, with its default's word written in
the key; the key exists only beside a stats row. A row's color bound to
a param.color paints the pick, the default included; mark the target
"unset_at_default": true and the chart removes the colour while the
setting holds its default, so the strip's theme colours the row until the
user picks one. Declaring a string slot or a renderer switches the
derived sheet to the second runtime contract; the numeric outputs compute
exactly as before. style on a text or label renderer picks its look,
the same eight words on both: plain (text alone, placed by align and
valign), price_label (the price tag), pill (a rounded chip at the
value), callout (a leader line to its bar), badge (a dot in the
label's color), box (a rounded box, the handle label's default),
knockout (a box in the chart's background colour that hides what is
behind the text) and emblem (a small mark before the text, its shape
emblem_shape: dot, square, diamond, triangle_up,
triangle_down). A tag look takes label_position, background_color,
border_color and corner_radius; plain text takes align and
valign; the two sets never mix on one renderer. font_weight
(normal, medium, bold) and font_family (ui, mono, serif,
rounded) apply to both. The label styles, the tooltip template and
badges are on Labels, tooltips, badges.
A corner readout is a render.label with position (below), a one-cell
table or a HUD card (HUD and hover cards).
A per-bar readout, a live label, a dashboard, a strip row, a background tint, and a candle tint together:
param("period", 20, { min: 1, max: 200 });
param("stretch", 2, { min: 0.1, max: 20, description: "Percent from the average that counts as stretched" });
output("average", line, overlay, { color: "#38bdf8", width: 2, description: "Simple average" });
output("bar_time", none, overlay, { description: "Bar open in epoch seconds, the label's x" });
output("stretch_pct", none, overlay, { description: "Close distance from the average, percent" });
output("stretched", none, overlay, { description: "1 while the close is stretched from the average" });
output("volume", none, overlay, { description: "Volume, shown in the strip" });
string("readout", { max_bytes: 32 });
string("tag", { max_bytes: 32 });
string("stretch_label", { max_bytes: 16 });
string("stretch_text", { max_bytes: 16 });
// A text mark on every stretched bar (the slot is left unwritten on quiet bars).
render.text("stretch_mark", { y: "average", text: "readout", color: "#f59e0b", size: 10 });
// One label riding the newest bar, at the average.
render.label("average_tag", { x: "bar_time", y: "average", text: "tag", color: "#38bdf8", size: 11 });
// A fixed-position 1x2 dashboard: the newest bar where both cells were written wins.
render.table("stats", { rows: 1, cols: 2, cells: ["stretch_label", "stretch_text"], position: "top_right" });
// Volume as a statistics-strip row.
render.stats_row("volume_row", { output: "volume", title: "Volume", format: "si", polarity: "magnitude" });
// A background tint on stretched bars.
render.bgcolor("stretch_tint", { where: "stretched", color: "#f59e0b22" });
// The stretched bars' own candles, body and wick, in the same amber.
render.barcolor("stretch_candles", { where: "stretched", color: "#f59e0b" });
let sma = new Sma(20);
let threshold: f64 = 2.0;
function onStart(): void {
sma = new Sma(i32(p_period()));
threshold = p_stretch();
}
function onBar(): void {
const close = bar.close();
const volume = bar.volume();
const barTime = bar.time();
const value = sma.update(close);
if (isNaN(value)) return;
const stretch = value == 0.0 ? NaN : ((close - value) / value) * 100.0;
const stretched = Math.abs(stretch) >= threshold;
out_average(value);
out_bar_time(barTime);
out_stretch_pct(stretch);
out_stretched(stretched ? 1.0 : 0.0);
out_volume(volume);
if (stretched) {
sb_clear();
sb_f64(stretch, 1);
sb_text("%");
str_readout_sb();
}
sb_clear();
sb_text("SMA ");
sb_f64(value, 2);
str_tag_sb();
str_stretch_label("stretch");
sb_clear();
sb_f64(stretch, 2);
sb_text("%");
str_stretch_text_sb();
}Leaving readout unwritten on quiet bars is the whole gating story for
render.text: a slot not written that bar is absent, and an absent slot
draws nothing. A price tag on every signal bar is the same renderer with
style: "price_label": each written bar's text sits at y as a price
tag. A label the module moves, re-words, or deletes on a later bar is a
label handle (Drawing objects).
Mark size
width on a render.shape sets the mark's size in pixels, any positive
number; left out, the chart draws its own default size. Two sizes of one
mark grade a signal at a glance:
// Volume spikes marked under the bar: a large dot over three times the average volume, a small one over twice.
param.int("length", 20, { min: 2, max: 200, label: "Average length" });
output("low", none, overlay, { description: "The bar low, where the marks sit" });
output("big", none, overlay, { description: "1 when volume is over three times its average" });
output("small", none, overlay, { description: "1 when volume is over twice its average but not three times" });
render.shape("big_spike", { output: "low", shape: "circle", where: "big", color: "#f97316", width: 12 });
render.shape("spike", { output: "low", shape: "circle", where: "small", color: "#f97316", width: 6 });
let average = new Sma(20);
function onStart(): void {
average = new Sma(i32(p_length()));
}
function onBar(): void {
const volume = bar.volume();
const mean = average.update(volume);
if (isNaN(mean) || mean <= 0.0) return;
const ratio = volume / mean;
out_low(bar.low());
out_big(ratio > 3.0 ? 1.0 : 0.0);
out_small(ratio > 2.0 && ratio <= 3.0 ? 1.0 : 0.0);
}- One renderer per size.
widthis fixed in the declaration, so a large and a small dot are tworender.shapelines over the same output, each with its own gate. - Gates that never overlap.
smallis 1 only between two and three times the average, so a bar never carries both marks.
Fixed-position text
Text that sits at a viewport anchor and does not move with price is a
render.label pinned by position over a string slot the module
rewrites on every bar, so the newest bar decides the text:
render.label("status", { position: "top_right", text: "status_text", offset: [12, 8], style: "box", background_color: "theme.bg" }).
With position the label needs no x and y (declaring them beside it
is refused: a corner label is nudged by offset, not placed by
coordinates, and each number of the offset points inward from the
anchored edges), and its style is one of plain, box, knockout,
pill, badge or emblem (price_label and callout belong to a
label at a price). The other corner readouts are a one-cell
render.table at that anchor
(render.table("status", { rows: 1, cols: 1, cells: ["status_text"], position: "top_right" }))
and a render.hud card with a tile.pill or tile.value at the same
anchor (HUD and hover cards). A label the module owns
and moves is a handle (Drawing objects).
Legend, HUD and hover
Three surfaces read the indicator's newest values and are drawn by the
chart from declarations alone: the legend entry, a HUD card at one of the
nine anchors, and the card that opens when the cursor rests on a legend
entry, a HUD tile or a line. They share one vocabulary of blocks, and the
rest of this section documents them: legend({ title }), label,
format and render.legend on Legend;
hover(handle, [block.*]) and badges on
Hover cards;
render.hud(name, { position, title?, columns?, tiles, look?, ... }) on
HUD cards, with its surface and type
words and the twelve looks a card can wear; every block.* and tile.*
constructor, the colour, drawing, headline and height words of a tile,
on Blocks and tiles; the
three together, with a complete module, on
What the chart shows.
Ranges and panels
A rectangle between two times and two prices: per bar it is a box
between two outputs over a bar-offset span; for one object placed from
the newest bar it is draw.box with four coordinate outputs, two of
them epoch seconds from the time source; for a rectangle the module
keeps, grows, and deletes it is a box handle
(Drawing objects).
A heatmap, a curve over a category or an index, tiles, and a one-summary
pie are frame-backed panels: panel.heatmap, panel.line over a
category or index x, panel.tiles and panel.pie, each a JSON snapshot
the module writes and the chart draws in its own pane below the price
chart (Cards, frames and panels).
Viewport-pinned panels of raw bars and a variable number of marks per
bar have no renderer: every output is one number per bar on the chart's
time axis. For a dashboard use render.table over string slots; for a
per-level picture use a plot.levels frame docked on the price axis,
or the chart's own footprint view beside the wrun indicator.
The name requirement
A wrun indicator's outputs are
unique by construction (a duplicate output("sma", ...) is refused in
the editor's Console, duplicate output name 'sma'), the name is the
legend entry (read as words), and description is the optional long
text. Names follow one grammar across outputs, boxes, segments,
renderers, and drawings, and share one namespace: a renderer cannot reuse
an output's name.
When to use which
| You want | Use | Not |
|---|---|---|
| a value someone could alert on | a drawn output, or a declared alert over a gate | a box or a label (decorations are never alert targets) |
| a mark on some bars only | plot: shape with shape_where, or render.shape with where | a text renderer on every bar |
| a live readout at the newest bar | render.label from a string slot | render.text (one mark per bar) |
| text on every signal bar | render.text from a slot written on those bars | one label per signal |
| a corner readout that does not move with price | a render.label with a position, a one-cell render.table with a position, or a render.hud tile | a label placed by x and y |
| a dashboard | render.hud with typed tiles (and a look), or render.table for a grid of words | many labels |
| a word or a value in the legend | label and format on the output, render.legend for a slot's words | a text renderer in the corner |
| an answer when the cursor rests on a line or a tile | tooltip on the output, or a hover block list | a table the user has to read across |
| a label the module moves or deletes later | a label handle (draw.label(id)) | a renderer, which cannot be moved |
| a per-bar tint behind the bars | render.bgcolor | recoloring a line |
| trend or regime colored candles | render.barcolor | a background tint fighting the candle for contrast |
| a level | a line output with role: "guide" written to the constant | a drawing |
| an oscillator and a second study in panes of their own | two pane(...) declarations and pane on their outputs | one crowded lower pane |