Styling

View as MarkdownOpen the editor

How outputs draw. The rule that keeps a wrun indicator honest: outputs are numbers, some numbers are decisions, and declarations map decisions to looks.

Styling lives in the output(...) options and in the range(), box() and segment() declarations at the top of the file, never in the module's code: the chart reads the declaration and draws it, and Run records it in the sheet it derives. A static look is one option per output; a look that changes per bar is a ladder, a data-only output whose floored value indexes a palette. What the user may change comes from the same declarations: a param.color bound to an output by name, and the Style page the dialog derives from the outputs. This page is the wrun vocabulary for each look (size, tooltips, z-order, palettes, gradients), and which looks have no form.

Every part a wrun indicator draws can be styled; for a part-by-part walk through one real indicator, start with Style anything and come back here for the vocabulary.

Placement, per output

  • plot: line, bar, area, histogram, candle, shape, scatter, or none for a data-only output: computed, never drawn, the building block for gates and palettes below. On the chart bar and histogram both draw as columns and shape and scatter as marks; candle takes four consecutive outputs (open, high, low, close) that draw as one candle (Plotting).
  • panel: overlay (the price chart) or lower (a pane below it). Put small-magnitude series (probabilities, oscillators) in lower; an odds-scale line on the price axis hugs the axis floor. The lower outputs of an indicator that also draws on price share one pane below the chart unless pane: "<name>" sends them to a pane declared with pane(name, { title, place, height_frac, scale, invert, padding, min, max, format }): up to four named panes, below the chart or, with place: "price", over the candles on a hidden scale of their own (Panes).
  • z: the paint order inside a pane, an integer -10..10 on an output, a range, a fill or a box. Absent, the engine's layer applies: tints 0, fills 1, lines, columns, bands, marks and boxes 2, text 3; ties keep declaration order.
  • Numbers: format names how the output's value prints in the legend, the hover card and its axis tag: price (the chart's price digits, or a lower pane's own step digits on its axis), % (the value with a percent sign), si (K, M, B, T from 1,000), int, 0, 0.0, 0.00, 0.000 (fixed digits), usd ($ with two decimals under 1,000, else $1.2K, $1.2M, $1.2B, $1.2T at one decimal, the sign before the $) and auto (six significant digits with , thousands). decimals (0..8) overrides the word's digits, signed: true puts a + on positives, and unit prints after the value once format is declared (an output's unit is not bounded, as it never was; keep it short, a host may cut a long one); decimals and signed without format are refused ("decimals needs format"). A unit on its own is recorded in the sheet as before and never printed. Without format the chart formats numbers itself: a lower pane prints K, M and B from 1,000 and its own precision below that, a value on the price pane prints at the chart's price precision. A pane's axis ticks take the same four words on pane(...).

Static style, per output

One option per output, read from the declaration:

wrun
output("overbought", line, lower, { color: "#ff0000", width: 1, line_style: "dashed", description: "The 70 level, written on every bar" });
output("delta", histogram, lower, { color_by: "delta_sign", colors: ["#ef5350", "#26a69a"], description: "Fast minus slow as columns, colored by sign" });
// Red while the close is under the average, green (and three times as wide) while it is over.
output("mid", line, overlay, { color_by: "regime", colors: ["#ef4444", "#22c55e"], width_by: "regime", widths: [1, 3] });
// The band between the two edges: dotted edge lines and a slate interior.
range("band_hi", "band_lo", { color: "#94a3b8", edge_width: 1, edge_line_style: "dotted" });
// A curved line split around zero, shaded below it; a fill between the two edges with no edge lines of its own.
output("osc", line, lower, { smooth: true, split: "level", up_color: "theme.up", down_color: "theme.down", split_fill: true });
fill("band_hi", "band_lo", { color: "theme.accent", opacity: 0.15 });
// The 70 level as a guide: one full-width line, labelled on the line, out of the legend.
output("upper", line, lower, { role: "guide", align: "right", color: "theme.muted" });
LookOptionWhat it draws
a colorcolor, or a colors palettethe line, column or mark color
a haloglowa soft halo in pixels on a line, an area, columns, candles, marks and scatter dots, and on a render.shape mark
rounded columnscornerrounded column corners on a histogram, in pixels
transparencyopacity0..1 on an output, fading every colour it draws: a ladder's rungs, a packed colour, a column's sign pair, a candle's body, border and wick, marks and gradient stops; per box, per fill, per handle and per card too
thicknesswidththe line width; widths the per-bar ladder, 1..10 entries, each 0.5..20; on columns a share of the bar slot, widths entries from 0.05 (1 = the columns touch)
a dashline_stylesolid, dashed, dotted; lines and areas take the dash, columns do not
curvessmootha line, or an area's edge, bent into curves
stepsstepa line as horizontal then vertical segments (a trailing stop, a funding step); never beside smooth
a stroke shaded by heightgradient on a line2 to 8 colours from the top of the pane to the bottom; never beside color_by or split
two colours around a level or by slopesplit, up_color, down_color, split_level, split_fill, split_base"level": up_color above split_level (default 0) and down_color below, split_fill shading down to split_base; "slope": up_color while rising, down_color while falling
an area's fillfill_color, fill_opacity, fill_gradient, gradient_mode, fill_color_by plus fill_colors, fill_color_packed_bythe fill's own colour (default the line's) at fill_opacity (0.4 flat, 1 on stops), or stops top to bottom over the pane, the fill or each column, or a colour per bar from its own ladder
a fill between two linesfill(a, b, { color, opacity, opacity_by, color_by, colors, color_packed_by, z })the interior only, no edge lines, between two drawn outputs on one pane, its opacity per bar from an output with opacity_by
a band's edgesedge_width, edge_line_style on a range()both edge lines at an integer width 0..10 (0 = no edge lines), solid, dashed or dotted
a faded bandgradient on a range()2 to 8 colors, listed top to bottom, shading the interior vertically instead of a flat tint
where the stops liegradientMode"pane" (the pane height, the default), "fill" (the band's own vertical extent) or "line" (per column, from upper down to lower)
a band's legend rowlegend, label on a range()legend: false drops the row; label names it
columns from a levelbasecolumns grow from base instead of zero, colors[0] at or above it and colors[1] below
columns faded by sizegrading"linear" or "square": each column's alpha follows its magnitude within the visible range
stacked columnsstackbars sharing a stack name stack in declaration order, positives above zero and negatives below
a candle's stylecandle_style, border_colors, wick_colors, border_width on a candle group's first output"candle", "hollow", "ohlc" or "high_low"; border and wick colours as [both] or [up, down]; the border width 0..10
the markshape, location, char, font_family, fill, fill_opacitythe shape (ten words, char one character in font_family); "absolute", "above_bar", "below_bar", "top" or "bottom"; the outline only, or a faded interior
a levelrole: "guide", align, pill_style, font_size, axis_labelone full-width line at the output's last value, out of the legend; a label on the line ("left", "center", "right"; "filled" or "outlined"; 6..64 px) or on the axis
paint orderzan integer -10..10 on an output, a range, a fill or a box; absent, the engine's layer (tints, fills, lines and columns and bands and marks and boxes, text) in declaration order
a theme colour"theme.up", "theme.down", "theme.text", "theme.muted", "theme.bg", "theme.grid", "theme.accent"the chart's own colour, resolved when it paints and again on a theme switch (Theme colours)
a color per barcolor_by plus colors, or color_packed_byeach bar's value indexes the palette, or carries a packed colour
a width per barwidth_by plus widthsthe same ladder for line width
an inset strip's colourscolor, colors, color_by, opacity on out.insetcolors without color_by is a sign pair (up at or above zero, down below); with color_by the bar's ladder colour
a HUD's looklook on render.hudone of twelve presets, every word of which a declared word overrides (HUD cards)

color (or a colors palette), width, opacity (0..1), line_style (solid, dashed, dotted; lines and areas take the dash, columns do not), glow (a soft halo in pixels on a line, an area, columns, candles, marks and scatter dots), corner (rounded column corners, in pixels), and description, which documents the output in the sheet. A colour is "#rrggbb", "#rrggbbaa" (the alpha in the last byte), a CSS colour name as before, or a theme token (below); opacity multiplies every colour the output draws, a ladder's rungs, a packed colour, a column's sign pair, a candle's border and wick and a gradient's stops included. The legend's and the Style page's words for an output come from label (the name read as words when absent) and its numbers from format, decimals, signed and unit (above); legend: false keeps an output out of the legend, visible: false starts it hidden, price_line: true draws a dotted line across the pane at its last value, axis_label: true tags that value on the price axis and axis_name: true adds the indicator's name to the tag (Plotting). The looks a plot kind takes beyond these (step, smooth, gradient and split on lines; fill_color, fill_opacity, fill_gradient and the fill ladder on areas; base, grading, stack and width as a share of the bar slot on columns; candle_style, border_colors, wick_colors and border_width on a candle group; shape, location, char, fill and fill_opacity on marks; role: "guide" for a level) are the table on Plotting. The per-look reading of this vocabulary, with the decision rule, is Decisions to looks.

Theme colours

Every colour word accepts a theme token beside the hex forms: theme.up and theme.down (the chart's candle colours), theme.text (the axis text), theme.muted (that text at 55 percent), theme.bg (the background), theme.grid (the grid lines) and theme.accent (the app's accent). The chart resolves a token when it paints and again when the theme switches, with no rerun, so a "theme.text" label reads on a dark and a light chart alike. A token rides every colour key of this page (outputs, ranges, fills, boxes, segments, renderers, drawings, handles, cards, HUDs, docked levels and panels), the colour strings inside a frame, and a packed colour the module writes (toPacked(theme.UP) from ./sdk/color, Colors kit). A param.color default or preset stays a hex colour ("a colour param takes #rrggbb or #rrggbbaa; put the token on the colour key"), and a theme. word outside the seven is refused by name. Two chart-level flags ride beside the tokens: chart.contrast_guard(false) keeps the author's colours as written on a light chart (the chart otherwise darkens colours that would vanish there), and chart.stack_handles(true) stacks this indicator's corner-anchored handle groups below other indicators' groups instead of overprinting them (Drawing objects).

The Style page

Every output you draw gets a row on the dialog's Style page without a declaration, and a spot the file binds to a setting (color: "@basis_color", line_style: "@style") is that setting's row instead, so a look never has two controls. The rows, the bindings and the browser-or-cloud rule are on The Style page.

Decisions to looks

  • Per-bar coloring: emit the decision as a data-only output (none), then on the styled output set color_by: "<that output>" plus a colors palette (at least 2 entries). Each bar's floored value indexes the palette; a finite value outside the palette takes entry 0, and a non-finite one draws the output's static color (entry 0 when the output declares no color). An output cannot color itself: the index expression is its own output.
  • Per-bar width: the color ladder's sibling. On the styled output set width_by: "<data-only output>" plus a widths ladder (1..10 entries, each 0.5..20); each bar's value indexes the ladder (floor; a finite out-of-range index takes entry 0). A non-finite value on either ladder draws that bar in the static style. Both halves or neither, each refused by name without the other. An output cannot set its own width.
  • Gated markers: a shape output with shape_where: "<gate output>" renders only where the gate is nonzero. An output cannot gate itself.
  • Banded ranges: range(upper, lower, options?) names two DIFFERENT drawn outputs (by name, as strings; a none output is refused: "range references '', which is not a rendered output"). The chart draws a band: both edge lines at edge_width (an integer 0..10; 0 draws the band with no edge lines) and edge_line_style (solid, dashed or dotted), with a tinted interior in color. A colors palette plus color_by (a data-only output) tints the band per bar (floor; a finite out-of-range value takes entry 0); color_by requires colors. colors WITHOUT color_by is the band's sign palette (a momentum fill): entry 0 where upper plots above lower, entry 1 where it plots below. gradient (2 to 8 colors, listed top to bottom) shades the interior vertically instead of a flat tint, laid over gradientMode: "pane" (the pane height, the default), "fill" (the band's own vertical extent) or "line" (per column, from upper down to lower). smooth is a boolean smoothing hint. legend: false drops the band's own legend row and label names it (default: the upper edge's label); z sets its paint order. Declare as many ranges as needed; the same pair may repeat with different options.
  • Fills: fill(a, b, options?) shades the interior between two drawn outputs on one pane and draws no edge lines, so each line keeps its own look. Options: color, opacity (0..1, default 0.25), the color_by plus colors ladder or color_packed_by for a colour per bar (a non-finite rung draws no fill that bar), opacity_by for an opacity per bar, and z. opacity_by names a declared output whose value on each bar is that bar's opacity (0..1), with any colour mode; a non-finite value keeps opacity. The two sides must be different drawn outputs on the same pane; colors without color_by, color_by without colors, color_packed_by beside color_by, and an opacity_by that names no declared output are refused.

A setting reaches a fill's opacity through opacity_by: the file writes the setting, scaled to 0..1, into a data-only output on every bar, and the fill reads it there.

wrun
param.int("cloudOpacity", 40, { min: 0, max: 100, label: "Cloud opacity" });
output("cloud_alpha", none); // the cloud's opacity on each bar, 0..1
fill("span_a", "span_b", { color_by: "cloud_side", colors: ["#22c55e", "#ef4444"], opacity_by: "cloud_alpha" });
// in onBar(): out_cloud_alpha(p_cloudOpacity() / 100.0);

A packed colour skips the palette: the module writes a colour per bar into a data-only output (toPacked from the colour kit, Colors) and the drawn output names it with color_packed_by; an area's fill reads the same number through fill_color_packed_by, so a heat that shifts smoothly needs no rungs.

wrun
// A heat area painted per bar from a colour the module computes: the edge and the fill both read it.
output("heat", area, lower, { color_packed_by: "heat_color", fill_color_packed_by: "heat_color", fill_opacity: 0.35, width: 2 });
output("heat_color", none, lower, { description: "A packed colour per bar, written with toPacked(...) in onBar()" });

A per-bar box takes the same two ladders for its fill and its border, under camelCase keys in the declaration (colorBy, borderColorBy, borderColorPackedBy, borderStyle), with output handles for the By keys (Boxes): a zone can follow the regime with a dashed border while a second box, drawn only where the band squeezes, takes its fill and border from one packed heat colour.

wrun
const hi = output("band_hi", line, overlay);
const lo = output("band_lo", line, overlay);
const regime = output("regime", none, overlay);
const heat = output("heat_color", none, overlay);
const squeezed = output("squeezed", none, overlay);
// The zone behind the last three bars: fill and border by the regime ladder, dashed, painted behind the lines.
box("zone", { top: hi, bottom: lo, from: -3, to: 0, colorBy: regime, colors: ["#ef444433", "#22c55e33"], borderColorBy: regime, borderColors: ["#ef4444", "#22c55e"], borderStyle: "dashed", z: -1 });
// The squeeze box: fill and border from one packed colour, where the gate is nonzero.
box("squeeze", { top: hi, bottom: lo, when: squeezed, colorPackedBy: heat, borderColorPackedBy: heat, opacity: 0.3, borderWidth: 2 });

Every reference is checked as you type and when you press Run: color_by, width_by, shape_where, and each range() side must name a declared (and, where required, drawn) output, and none may name its own output. A refusal points at the declaration in the editor's Console and names the field (for example "Output 'mid' (color_by): ...").

Styling dresses per-output SERIES. When the indicator should also put text, tables, shaped marks, or free-standing objects (lines, boxes, polylines, labels) on the chart, that is the renderer and drawing vocabulary: render.* and draw.* declarations over named outputs and string slots (Plotting, Drawing objects). Shapes that repeat on EVERY bar (a zone behind the last few bars, a projection ray from each bar) are the box() and segment() declarations over output handles (Boxes, Segments).

Tags, the legend and the candle tint

Four looks belong to the indicator as a whole: whether it keeps a price axis of its own, which plots tag their value on the price axis, the order its plots join the legend, and the pane it lives in. One top-level display({ ... }) declares them, at most once:

KeyWhat the chart doesAbsent
axis: falseno price axis of its own: an indicator on the price pane rides the chart's scaleits own axis
price_display: "per_output"every plot tags its value on the price axis unless its show_price_display is false; a mark tags only when its show_price_display is truethe first output alone tags
overlay: "offchart"the indicator lives in its own pane below the chart; pane("lower", { format: "0.00" }) formats that pane's axisthe outputs' panels decide
mount_order: "first_value"plots join the legend, and paint within one layer, in the order they first draw a valuedeclaration order, every range after every output

Five words sit on the plots themselves:

WordOnWhat it does
show_price_displayan output, a range, a render.shapethe plot's tag on the price axis, read under price_display: "per_output": false drops it, true forces one
show_price_display_byan outputa declared output whose value on the last bar switches the tag: nonzero on, zero off, NaN keeps show_price_display
omit_if_emptyan output, a rangetrue: a run that never drew it leaves it out, with no legend row and no tag (a plot drawn under an if)
fill: falsea rangethe two edge lines and no interior
barcolor: truea none outputthe candles take its colour: each bar's value (floored) picks an entry of colors, or color_packed_by names a packed colour; a NaN or out-of-range value leaves the candle as it is; one per indicator

Every colour bound to a setting also takes it at an alpha: "@<param>/0.45" paints the setting's colour at 45 percent, and a recolour keeps the 45; a panel series colour binds without one (The Style page).

// No price axis of its own, a tag per plot, and plots listed in the order they first draw a value.
display({ axis: false, price_display: "per_output", mount_order: "first_value" });
param.int("length", 20, { min: 2, max: 200, label: "Length" });
param.color("bull", "#22c55e", { label: "Bull" });
param.color("bear", "#ef4444", { label: "Bear" });
param.bool("tag_basis", true, { label: "Tag the basis" });
// The basis tags its value while the setting is on; the edges never tag.
output("basis", line, overlay, { color: "@bull", show_price_display_by: "basis_tag" });
output("upper", line, overlay, { color: "@bull/0.5", show_price_display: false });
output("lower", line, overlay, { color: "@bear/0.5", show_price_display: false });
output("basis_tag", none);
// A mark on each breakout: a run with none leaves it out of the legend.
output("breakout", shape, overlay, { shape: "triangle_up", location: "below_bar", color: "@bull", omit_if_empty: true });
// The two edges as lines with no interior, untagged.
range("upper", "lower", { fill: false, show_price_display: false });
// The candles in the trend's colour at 45 percent: 0 below the basis, 1 above.
output("tint", none, overlay, { barcolor: true, colors: ["@bear/0.45", "@bull/0.45"] });

let sma = new Sma(20);
let stdev = new Stdev(20);
let tag: f64 = 1.0;

function onStart(): void {
  sma = new Sma(i32(p_length()));
  stdev = new Stdev(i32(p_length()));
  tag = pb_tag_basis() ? 1.0 : 0.0;
}

function onBar(): void {
  const close = bar.close();
  const mid = sma.update(close);
  const sd = stdev.update(close);
  if (isNaN(mid) || isNaN(sd)) return;
  const hi = mid + 2.0 * sd;
  out_basis(mid);
  out_upper(hi);
  out_lower(mid - 2.0 * sd);
  out_basis_tag(tag);
  if (close > hi) out_breakout(bar.low());
  out_tint(close > mid ? 1.0 : 0.0);
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine

Refused by name: a tag word on a none output ("show_price_display needs a drawn output"), omit_if_empty on one ("omit_if_empty needs an output that draws a value"), barcolor on a drawn output, without colors or color_packed_by, beside color_by (its own value is the index) or on a second output, a range with fill: false and a gradient, a show_price_display_by that names no output, a second display(...) and a word display does not take (overlay: "onchart").

Text size, tooltips, alignment, z-order, palettes

The styling vocabulary, item by item:

You wantwrun form
text sizesize, an integer pixel count 6..64, on render.text and render.label, or size_by for a size per bar from an output; font_size on a guide's label, a box's text and a card
typefont_weight (normal, medium, bold) and font_family (ui, the app font; mono; serif; rounded, each a system stack, nothing downloads) on renderer text, labels, box text, cards, HUDs and docked levels
a tooltip on a plottooltip on the output, a template over its own value and any output or string slot: "{{label}} {{value:price}} · {{state}}"; shown on the legend entry and when the cursor nears the line (Labels, tooltips, badges)
a tooltip on a cell, label, mark or drawingtooltip on render.text, render.label, render.shape, draw.line, draw.box, draw.polyline and draw.label (one template per declaration, shown on its marks or object); a handle's tooltip(send) setter; a hover block list on an output or a label for a full card (HUD and hover cards); a table cell's tooltip in its styles entry, a string slot (Styled tables)
price tags, callouts, pillsstyle on render.text and render.label, eight words on both: plain, price_label (the price tag on the axis), pill (a rounded chip at the value), callout (a leader line to its bar), badge (a dot in the label's color), box, knockout (a box in the background colour) and emblem (a mark before the text); a tag look takes label_position, background_color, border_color and corner_radius (Labels, tooltips, badges)
text alignmentalign (left, center, right) and valign (top, middle, bottom) on plain render.text and render.label, on a handle or declared label, and on a box's text; the nine anchors (top_left ... bottom_right) are a table's, a HUD's or a corner render.label's position (Drawing objects)
z-orderz (-10..10) on an output, a range, a fill or a box, else the engine's layer in declaration order; a handle's zorder setter on lines, boxes, labels and polylines
a palettea colors literal array on the output, box, range, fill, bgcolor, barcolor, stats row or shape renderer; bucket the driving value into its index (Colors); an entry may be a param.color by name; color_packed_by names an output that carries a packed colour per bar instead
per-element glow, opacity, gradientsglow per output, per renderer mark and per handle (glow_color picks its colour); opacity per output (every colour it draws), per box and per handle; a color_by ladder per bar; a line's gradient by height, an area's fill_gradient, a range() band's vertical gradient, a box handle's gradient; corner rounds histogram columns, corner_radius the corners of labels, boxes, cards and HUDs
a setting behind a look"@<param>" in place of a colour, a colors or gradient entry, or a line style on an output, a range, a fill, a box, a segment, a legend entry or a docked plot.levels key; "@<param>/<alpha>" for the setting's colour at an alpha; a param.color on every colour word of a panel (Panel words); a param.choice over the looks on a HUD card's look (Looks) (The Style page)
a table's fill, frame, lines and widthsthe look keys on render.table: fills with opacity and gradients, a frame, the lines between cells, column widths, merged cells (Styled tables)

Value-driven styling, computing the style from the data per element, is the ladder: normalize the driving value, floor it into a bucket, write the bucket to a none output, and index a palette with it. The palette is finite, so the gradient is stepped, and the decision stays a number you can read at the Console prompt or hand to a declared alert.

A worked example

Bollinger-style bands with a regime-colored (and regime-widened) midline and a banded range. The declarations carry every look; the module computes four numbers per bar and never mentions a color:

param("period", 20, { min: 2, max: 400 });
param("band_width", 2, { min: 0.5, max: 4, description: "Stdev multiples" });
// Red while the close is under the average, green (and three times as wide) while it is over.
output("mid", line, overlay, { color_by: "regime", colors: ["#ef4444", "#22c55e"], width_by: "regime", widths: [1, 3] });
output("band_hi", line, overlay, { color: "#94a3b8", opacity: 0.6 });
output("band_lo", line, overlay, { color: "#94a3b8", opacity: 0.6 });
output("regime", none, overlay, { description: "0 below the average, 1 above: the palette and width index" });
// The band between the two edges: dotted edge lines and a slate interior.
range("band_hi", "band_lo", { color: "#94a3b8", edge_width: 1, edge_line_style: "dotted" });

let sma = new Sma(20);
let stdev = new Stdev(20);
let mult: f64 = 2.0;

function onStart(): void {
  const period = i32(p_period());
  sma = new Sma(period);
  stdev = new Stdev(period);
  mult = p_band_width();
}

function onBar(): void {
  const close = bar.close();
  const mid = sma.update(close);
  const sd = stdev.update(close);
  const regime = close > mid ? 1.0 : 0.0;
  if (isNaN(mid) || isNaN(sd)) return;
  out_mid(mid);
  out_band_hi(mid + mult * sd);
  out_band_lo(mid - mult * sd);
  out_regime(regime);
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine

The regime output computes on every bar but never draws; it exists so the palette (and the widths ladder) on mid has a decision to index. The band is one range() over the two drawn edges, so the chart shades it without a box per bar. The two params are number fields in the overlay's settings dialog (a param.int would step by whole numbers, and param.number with a step by that step), and changing one reruns the compiled module without recompiling it; the three lines each get a Style row.

The output(...) options carry color, colors, width, opacity, line_style, glow, corner, z, color_by, color_packed_by, shape_where, width_by, widths, displacement_bars, displacement_bars_by, unit, description, the plot-kind looks of Plotting (step, smooth, gradient, split and its four companions, fill_color, fill_opacity, fill_gradient, gradient_mode, fill_color_by, 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, pane), the presentation keys label, format, decimals, signed, legend, visible, price_line, axis_label, axis_name, tooltip, hover, badges, hint, and the chart words show_price_display, show_price_display_by, omit_if_empty and barcolor (Tags, the legend and the candle tint); ranges declare as range(upper, lower, options?), fills as fill(a, b, options?), panes as pane(name, options?), boxes and segments as box(name, options) / segment(name, options) over output handles, and declaration order sets the indexes (Declarations and the sheet).

Where the rules live

Every styling reference is validated as you type and when you press Run, and every refusal names the declaration and the field in the editor's Console; the messages are in Common errors. The module never draws: the chart reads the declarations, so the look is exactly what they describe, whatever the module computes.