Limits reference

Every ceiling an Indicator runs under, in one place: the declaration caps the sheet validator enforces, the runtime ceilings the sandbox enforces on every…

Every ceiling an Indicator runs under, in one place: the declaration caps the sheet validator enforces, the runtime ceilings the sandbox enforces on every evaluation, the data-plane bounds, and the package bounds. Each row gives the exact number, what it protects, the shape that hits it, and the refusal it produces. kScript (legacy) documents its sandbox the same way; the mapping between the two lists is at the end, because most kScript limits count objects a script creates at run time and an Indicator declares almost everything once.

Declaration caps

Checked by the same schema at author, build, install, and publish, so a sheet over a cap fails in the Problems lane or on om wrun build, never on a chart. Every refusal names the path (boxes.0.top, renderers.2.size).

LimitValueWhat it capsWhen you hit itRefusal
Boxes16 per sheetboxes[] entries, each drawn once per barone declaration per occurrence instead of one gated declarationboxes must declare at most 16 entries
Segments16 per sheetsegments[] entries, each drawn once per barthe same shapesegments must declare at most 16 entries
Renderers32 per packagerenderers[] (text, label, table, shape, stats_row, bgcolor)a footprint-style sheet with a text tile per price levelrenderers must declare at most 32 entries
Drawings64 per packagedrawings[] (line, box, polyline, label), the declared kind placed from the newest barmany run-level objects; a growing list is handles insteaddrawings must declare at most 64 entries
Polyline points64 pairspoints on one declared polyline drawing (a polyline handle takes 256)a curve traced point by point in a declarationpolyline points must be <= 64 pairs
Handle defaultswidth 0.5..20, border_width 0..10, opacity 0..1, size 6..64, line_style solid/dashed/dotted, extend none/left/right/both, align left/center/right (label only), panel overlay/lowerone entry per kind in handles (line, box, label, polyline); an unknown kind is refused, never droppedhandles.line.width: width must be <= 20, unknown handle kind 'circle'; the handle kinds are line, box, label, polyline
Handle labels need a slotat least 1 string slothandles.labelenabling label handles in a sheet with no string_slotshandles.label needs at least one string slot: a handle label takes its text from a declared slot ...
Third-contract fields"abi_version": "wrun-3"handles (even empty), a label renderer's position, a text renderer's stylea sheet under the second contract that adds one of themhandles needs abi_version "wrun-3" (wrun-2 has no draw channel; wrun-1 and wrun-2 are frozen)
Table geometry32 rows x 8 colsone table renderer; cells must list exactly rows * cols slot namesa dashboard dumptable rows must be <= 32, table cols must be <= 8, table 'stats' declares 2x2 = 4 cells but lists 3
String slots64 per packagestring_slots[]one slot per table cell on a big tablestring_slots must declare at most 64 slots
Slot bytes1..4096 per slotmax_bytes on one slota long per-bar readoutmax_bytes must be <= 4096 (the per-slot byte cap)
Text size6..64 pxsize on text and label rendererssize must be >= 6, size must be <= 64
Display offset-500..500 bars, integerdisplacement_bars on an outputa lagging span longer than 500 barsdisplacement_bars must be >= -500, displacement_bars must be <= 500, displacement_bars must be an integer count of bars
Shape offset-500..500 bars, integer literalx_from / x_to (code first from / to) on a box or segmenta zone anchored further back than 500 barsa literal bar offset must be within -500..500 bars of the current bar
Width ladder1..10 entries, each 0.5..20widths beside width_bywidths must list at most 10 entries, widths entries must be <= 20
Line width0.5..20width on an output or a segmentwidth must be <= 20
Border width0..10border_width on a boxborder_width must be <= 10
Edge width1..10, integeredge_width on a rangeedge_width must be <= 10
Opacity0..1opacity on an output or a box (box default 0.2)named by path
Color string1..64 charsany color on a box, segment, renderer, or drawingcolor must be a non-empty string of at most 64 chars
Box fill colorhex, rgb(), or hsl()a box color (the fill takes the opacity)a named color such as "red"color must be a hex, rgb() or hsl() color (the fill takes the opacity)
Paletteat least 2 entriescolors beside color_bycolor_by needs 'colors' with at least 2 entries
Celled caprequired, positive integer, counts TUPLESmax_cells on a cellType: "array" inputa celled input declared without itinput 'profile' declares cellType "array" without max_cells; guests preallocate max_cells * 8 bytes and the host refuses bigger blocks, so the cap is required
Outputs1..256outputs[]; the runtime tracks 256 output slots per runa sheet past 256 outputs, or a raw write past the tracked slotsWRUN output count must be an integer from 1 to 256, WRUN output index 300 out of bounds for 256 tracked outputs

Names share one namespace across outputs, boxes, segments, renderers, and drawings; color_by, width_by, and shape_where cannot name their own output; each ladder half without the other is refused by name. The full validation list is at the end of Script definition.

Runtime ceilings

Enforced on every evaluation by the sandbox, on the chart and on your machine alike. A breach refuses the WHOLE evaluation by name; nothing is truncated, clamped, or silently dropped.

LimitValueWhat it protectsWhen you hit itRefusal
Module memory4 MiB (4,194,304 bytes); OM_WRUN_MEMORY_LIMIT_BYTES overrides on your machinethe linear memory a module declares and uses; validated from the binary's memory section before instantiation, then re-checked livea buffer sized far past any param's maxWRUN declared memory 0 initial 8388608 bytes (128 pages) exceeds limit 4194304 bytes, WRUN memory 8388608 bytes exceeds limit 4194304 bytes
No growth after init()0 bytes of growthflat per-bar memory across a long history and the forming bar's replaysan array that grows per bar, string concatenation in finalize()WRUN memory grew from 65536 to 131072 bytes after init; allocate persistent state in init and keep per-bar state/finalize work allocation-free
Output valuesfinite or NaNa chart and a metric that mean somethinga division by zero written as InfinityWRUN output 0 is Infinity; guard the calculation and emit NaN when the value is undefined
Evaluation deadline30 s of wall clock per run by default (pack, queue, execute, unpack); a lane may arm its own deadline; there is no instruction meteringone runaway module never holds a workerunbounded loops, deep recursionWRUN execution timeout after 30000ms; the worker is killed and replaced
Cell blockmax_cells tuples per bar, per celled inputthe buffer the module preallocated from in_<input>_capacitya deeper book than the cap; a profile with more price buckets than the capwrun_cell_block_too_large: input 'book' bar at ts 1725580800 has 48 cells (tuples); max_cells is 40. Blocks are never truncated, then the two ways out (raise max_cells, or cap max_depth on a book)
Cell read boundsinside exported memorya copy that would overrun the module's memorya pointer outside the preallocated bufferWRUN wrun_arg_bytes(1, 65536): 1536 bytes do not fit the module's exported memory of 65536 bytes; reserve max_cells * 8 bytes per celled input and pass a pointer inside that buffer
Cell phasestate() onlythe cell accessors read the bar being evaluateda cell read in init() or finalize()WRUN wrun_arg_len is callable during state() only (called during finalize); read cell blocks inside state() and carry what finalize() needs in module state
String phasefinalize() onlyslots belong to the row being committeda string sent from state()WRUN wrun_output_str is callable during finalize() only (called during state)
String slot bytesmax_bytes per slot (at most 4096)the buffer each host reserves per slota line longer than the slotWRUN string slot 0 write of 40 bytes exceeds the slot's max_bytes 32; strings are never truncated; shorten the text or raise max_bytes in metadata.string_slots
String bytes per row65,536 (64 KiB)one bar's text across all slotsmany slots near their cap on one barWRUN string writes for one row total 70000 bytes, over the per-row limit 65536; emit less text per bar
String bytes per run8,388,608 (8 MiB)one evaluation's text across all barslong readouts on every bar of a long historyWRUN string writes for this run total 8400000 bytes, over the per-run limit 8388608; emit less text or fewer rows
UTF-8valid sequences onlya slot never carries a replacement character silentlyraw bytes sent through the host importWRUN string slot 0 write of 5 bytes is not valid UTF-8; wrun_output_str carries UTF-8 text only (encode before writing)
Expanded render result2 MiB (2,097,152 bytes)the selection the chart receives after the run: 16 bytes per selected entry, 8 per carried number, the UTF-8 bytes of every carried string (table cells charge 8 more per cell; a live handle charges 16 plus 8 per number, 16 per polyline point, and its strings)many renderers over a long history with long strings; hundreds of live polylinesWRUN render result exceeds 2097152 bytes expanded (wrun_render_result_too_large): fewer rows, shorter strings, or fewer renderers/drawings
Live handles per kind500the handles of one kind alive at once, counted on every creation (the same ceiling kScript put on each drawing kind)a zone tracker that never deletesWRUN wrun_draw_box(7): 501 live box handles would exceed the cap of 500 per kind (wrun_draw_kind_limit); delete handles the Indicator no longer needs
Live handles in total1500every kind togetherthe same shape across kinds... would exceed the cap of 1500 in total (wrun_draw_total_limit)
Polyline handle points1..256 per callthe points one setPoints sends; the buffer must fit the module's memorya path that never trims its oldest pointsWRUN wrun_draw_polyline(0): 300 points exceed the cap of 256 (wrun_draw_polyline_too_large), wrun_draw_polyline_empty, wrun_draw_polyline_out_of_bounds
Draw calls per bar4096every handle call on one barre-sending every live handle on every barWRUN draw calls for one row exceed the cap of 4096 (wrun_draw_calls_per_row); emit fewer draw calls per bar
Draw phasefinalize() only; bar.isLast() also in state()handle ops belong to the row being committeda handle call from state()WRUN wrun_draw_box is callable during finalize() only (called during state) (wrun_draw_phase)
Handle identityids >= 0, one space across kinds, kind declared in handles, coordinates finitethe ledger the chart mirrors, kind by kinda negative id; a label on a box's id; a kind the sheet does not list; a NaN corner; a setter on an id nobody holdswrun_draw_id_negative, wrun_draw_kind_mismatch, wrun_draw_kind_undeclared, wrun_draw_non_finite, wrun_draw_handle_missing
Handle style valuesthe prop's range, integer where required, legal on the kindthe same setter table the engine enforcessize on a line; width of 30; style of 7wrun_draw_prop_unsupported, wrun_draw_style_out_of_range, wrun_draw_prop_unknown
Handle label texta declared slot, written on the bar of the draw callthe text a label carries is the slot's bytes at call timetext(...) before the slot's sender ran that barwrun_draw_label_slot_absent, wrun_draw_label_slot_undeclared

The export contract is checked statically before any of this: the four exports with exact signatures, exported memory when the cell, string, or polyline channel is imported, every present draw import with its exact signature, and an import set limited to the ABI's allowlist (a rejection lists the legal set and, for a draw import under an older contract, names abi_version "wrun-3" as the way in). Those messages are in Common errors.

Data-plane bounds

LimitValueWhere
om metric series --bars1..500, default 30the newest N bars, newest = the still-forming bar
Warm-up inferenceparams up to 500the fetch planner widens the window to the largest window-shaped param (period, fast, slow, ...) up to 500; a param above 500 is read as a threshold, not a lookback, so a window longer than that declares warmup_bars
Explicit windowat least warmup_bars + 1a caller's windowBars below the sheet's warm-up plus one computable row is refused (wrun_window_bars_invalid)
Composition depth4 packagesa source: "metric" chain (outer to innermost)
Coarse pinsas of the coarser candle's closea pinned interval contributes to a primary row only once its candle has closed; the fetch widens by two source intervals
Book depthmax_depth levels per sidebounds the block a book input receives, so max_cells can be 2 x max_depth
Metric fetch deadline (strategies)OM_DEADLINE_METRIC_FETCH, 30 s by defaulta strategy's metric fetch chain is aborted past it and the tick records metric_timeout
Celled consumersalerts, om metric get / om metric series, chart previewsbacktests and screens refuse celled packages by name (wrun_celled_metric_unsupported); an unserved class refuses before any fetch (wrun_cells_unavailable)
Chart previewone preview per pane; undisplaced outputs onlyan output with a nonzero displacement_bars is refused (wrun_preview_displacement_unsupported)
Strategy order ids64 distinct per run (closeAll counts as one)the 65th distinct id refuses the run (wrun_strategy_id_limit); an id is a slot, re-issuing one replaces its pending order
Strategy id text64 bytes of UTF-8 per id, from name or OCA namelonger text refuses by name (wrun_strategy_id_too_long), never truncated; invalid UTF-8 refuses (wrun_strategy_id_invalid_utf8)
Strategy calls per bar4096 order calls and getter reads per barover it refuses (wrun_strategy_calls_per_row); an infinite price refuses (wrun_strategy_non_finite), NaN is absent
Closed trades per run10000the 10001st closed trade refuses on the bar it closes (wrun_strategy_trade_limit); every engine rejection (pyramiding, legs, sizing, margin, the forming bar) is counted in rejectedOrders, never a refusal
Strategy result bytesinside the 2 MiB expanded render result16 per trade or order record plus 8 per number, 8 per equity cell (wrun_render_result_too_large)
Strategy feedthe price input reads ohlcv on the instance's own market, no symbol or exchange pinrefused at the sheet (wrun_strategy_feed_not_ohlcv); slippage_model: "bookEstimate" refused (wrun_strategy_slippage_model_unsupported); strategy.position and strategy.equity are reserved output names (wrun_strategy_reserved_output)

Package bounds

LimitValue
Registry tarball200 MiB
Extracted package512 MiB
Archive entries4,096
max_cells preallocation, per celled inputmax_cells x tuple width x 8 bytes (4 f64 per volume_profile or tape tuple, 3 per book tuple), inside the 4 MiB
Live tape retention, per marketAt most two hours or 200,000 prints, filtered by the smallest active min_size; no REST backfill

Nothing from a package executes at install time, the module runs sandboxed (no filesystem, no network, no order capability), and an install verifies wasm_sha256 before every run.

kScript (legacy) limits, mapped

kScript (legacy) limitIn Indicators
Source budget (10 weighted slots per script)No slot budget. Every input(...) is one declared fetch, planned and deduplicated by the daemon per source key (symbol + exchange, interval, side, tenor, block_size are facets of that key). The chart reads its own market for every input.
MAX_OUTPUT_OBJECTS (2,000,000 per run)No object count. Every output is one number per bar, in at most 256 slots; decorations are declared once. The only size cap on what reaches the chart is the 2 MiB expanded render result.
MAX_DRAWINGS_PER_KIND (500) and 3,000 totalHandles: 500 live per kind and 1500 in total, refused by name on the creation that would exceed them, so a tracker deletes what it no longer needs exactly as it did in kScript. Declared shapes have no per-kind object count: 16 boxes and 16 segments, each evaluated on every bar (a 500-bar window holds up to 16,000 shapes); 64 run-level drawings evaluated on the newest bar; 32 renderers.
MAX_TABLE_CELLS (10,000)32 x 8 = 256 cells per table renderer, each a string slot; 64 slots per package.
MAX_COLLECTION_SIZE (100,000 elements)No element cap. Collections live inside the 4 MiB module memory and are allocated before the first bar.
MAX_STRUCT_CONSTRUCTION_DEPTH (64), MAX_RECURSION_DEPTH (200)The compiler's own rules apply; the module's stack lives inside its memory, and the evaluation deadline stops runaway recursion.
Backtest grants (1,000 or 20,000 bars, runs per day)om backtest @scope/name replays a strategy package over the history the data plane serves, through the daemon's own runtime and the engine's broker; the CLI carries no per-tier bar grant. Backtests and screens refuse celled packages.
Strategy declaration bounds (leverage, fees, funding)The same bounds, checked at build time with the engine's rules: initial_capital and leverage more than 0, pyramiding an integer of at least 1, maintenance_margin_percent at least 0 and under 100, the fee and slippage rates at least 0, enum fields the engine's literals. A setting linked to a param is checked at init() and falls back to the default when out of range, reported. Writing strategies has the table.

Notes that save debugging time

  • max_cells is a contract, not a hint. Size the buffer from the generated in_<input>_capacity (tuples x width), and give a book input a max_depth so the cap is knowable: 2 x max_depth tuples.
  • Strings never truncate. Pick max_bytes for the longest line the slot will ever carry; the generated builder counts the bytes a line REQUIRES, so an oversize line refuses by name instead of clipping.
  • Most caps are counts of declarations. You meet them at build time, in the Problems lane or on om wrun build, and the message names the field. A repeating shape is one declaration gated per bar with when, never one declaration per occurrence.
  • The render cap counts expanded bytes. Thirty-two text renderers over a long history with long strings is the shape that reaches 2 MiB; leave slots unwritten on quiet bars so those rows select nothing.
  • Delete what you no longer draw. A handle stays alive until delete(); the per-kind cap is met by the tracker that creates a zone on every pivot and never frees one. Keep a bounded pool of ids and delete the oldest before reusing its id, and re-send only the handles that changed on a bar (the calls-per-bar cap counts every setter).
  • Allocate once. Buffers are sized at module start or in init() from a param's max; per-bar allocation is the one pattern that both slows a long history and trips the no-growth rule.

Example: the caps as contracts

A book-imbalance Indicator that sizes everything from its declarations: max_depth bounds the fetch, max_cells is exactly twice it, the cell buffer comes from the generated capacity, and the readout slot is sized for its longest line:

import { book, input, line, lower, ohlcv, output, render, string } from "./sdk/declare";
import { in_book_capacity, in_book_cells, in_book_read, in_close } from "./gen/inputs";
import { emitRow, out_imbalance } from "./gen/outputs";
import { sb_clear, sb_int, sb_text, str_depth_sb } from "./gen/strings";

input("close", ohlcv.close);
// max_depth caps each side at 20 levels, so a bar can never carry more than 40 tuples:
// max_cells is a contract, and a bigger block would refuse the whole evaluation.
input("book", book.cells, { max_cells: 40, block_size: 10, max_depth: 20 });
output("imbalance", line, lower, { unit: "ratio" });
// The longest line is "20 bids / 20 asks" (17 bytes); 32 leaves room and stays under the 4096 cap.
string("depth", { max_bytes: 32 });
render.text("depth_mark", { y: "imbalance", text: "depth", size: 10 });

// Sized once from the generated capacity: max_cells x 3 cells per [price, size, side] tuple.
const cells = new StaticArray<f64>(in_book_capacity);
let imbalance: f64 = NaN;
let bids: i32 = 0;
let asks: i32 = 0;

export function init(): void {}

export function state(): i32 {
  in_close();
  const n = in_book_cells();
  if (n <= 0) return 0;
  if (in_book_read(i32(changetype<usize>(cells))) < 0) return 0;
  let bidSize = 0.0;
  let askSize = 0.0;
  bids = 0;
  asks = 0;
  for (let i = 0; i + 2 < n; i += 3) {
    if (cells[i + 2] > 0.0) {
      bidSize += cells[i + 1];
      bids += 1;
    } else {
      askSize += cells[i + 1];
      asks += 1;
    }
  }
  const total = bidSize + askSize;
  imbalance = total > 0.0 ? (bidSize - askSize) / total : NaN;
  return isNaN(imbalance) ? 0 : 1;
}

export function finalize(): void {
  out_imbalance(imbalance);
  sb_clear();
  sb_int(bids);
  sb_text(" bids / ");
  sb_int(asks);
  sb_text(" asks");
  str_depth_sb();
  emitRow();
}

export function reset(): void {
  imbalance = NaN;
  bids = 0;
  asks = 0;
}

Pick the venue's bucket width for block_size first:

om block-sizes --exchange BINANCE_FUTURES