Limits reference

View as MarkdownOpen the editor

Every ceiling a wrun indicator runs under on the chart, in one place: the declaration caps Run checks when it derives the sheet, the runtime ceilings the browser sandbox enforces on every run, the data bounds, and the publish bounds.

Each row gives the exact number, what it protects, the shape that hits it, and the message it produces. Most of them are counts of declarations, because a wrun indicator declares almost everything once.

Plans

Free and Plus differ in how much you keep on your charts and in your account:

FreePlus
Indicators3 across your charts10 per chart
Indicator alerts225
Saved scripts100500

At the saved-script limit, saving a new script stops with "Saved-script limit reached (100 on your plan). Delete a script or upgrade to save new ones."

Declaration caps

Checked by Run in your browser when it derives the sheet from your declarations, before anything reaches the chart. A declaration over a cap is refused in the editor's Console, anchored on the declaration that breaks it and naming the field (boxes.0.top, renderers.2.size).

LimitValueWhat it capsWhen you hit itRefusal
Boxes16 per indicatorbox(...) declarations, each drawn once per barone declaration per occurrence instead of one gated declarationboxes must declare at most 16 entries
Segments16 per indicatorsegment(...) declarations, each drawn once per barthe same shapesegments must declare at most 16 entries
Renderers64 per indicatorrender.text, render.label, render.table, render.shape, render.stats_row, render.bgcolor, render.barcolora footprint-style file with a text tile per price levelrenderers must declare at most 64 entries
Drawings64 per indicatordraw.line, draw.box, draw.polyline, draw.label and the widgets, 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 draw.polyline (a polyline handle takes 100,000)a curve traced point by point in a declarationpolyline points must be <= 64 pairs
Declared alerts64 per indicator; message and description at most 200 characters each; title 1 to 120 characters on one line, without control or invisible characters; text names a declared string slot, whose words are cut at 200 characters when the alert firesalert(...) declarationsa signal per level instead of one gate outputalerts must declare at most 64 entries, message must be at most 200 characters, alert 'cross' title is 121 characters; it takes at most 120, alert 'cross' text names 'ghost', which is not a declared string slot
Handle defaultswidth 0.5..20, border_width 0..10, opacity 0..1, size and font_size 6..64, line_style and border_style solid/dashed/dotted, extend none/left/right/both, align left/center/right, valign top/middle/bottom, padding 0..64, corner_radius 0..32, glow 0..32, max_width 0..2000 (0 = no wrap), angle -90..90, gradient 2..8 colours, arrow none/start/end/both, axis_label none/price (text on a label), panel overlay/lower; the legacy borderWidth, borderColor and lineStyle spellings still read, never beside their snake_case twinsthe defaults one handles.<kind>(...) declaration sets for line, box, label or polyline, and the same keys on a declared draw.line, draw.box, draw.polyline or draw.labela label handle no longer needs a string slot: .mark() draws an emblem with no texthandles.line.width: width must be <= 20, a line has no text; use price
Table geometry32 rows x 8 colsone render.table; cells must list exactly rows * cols slot names; rows_by paints the first rows only (a data-only output's value on the table's bar, clamped to 0..rows)a dashboard dumptable rows must be <= 32, table cols must be <= 8, table 'stats' declares 2x2 = 4 cells but lists 3, render.table 'stats' rows_by names 'v', which is a drawn output; rows_by takes a data-only output (plot none) holding how many rows paint
Table lookwidth and each of column_widths 0..4096 px (0 = measured, one per column), cell_padding 0..64, offset within [-200, 200], border_width and grid_width 0..10, corner_radius 0..32, font_size 6..64, opacity 0..1, a gradient 2..8 colours, header_rows at most rows, a colspan or rowspan inside the grid at every cell carrying the slot, every colour ladder in a pair (<key>_by + <key>s, or <key>_packed_by, never both)the look keys and styles entries on one render.table (Styled tables)a span past the last column, a widths list shorter than the columnstable 'board' lists 1 column widths for 2 columns, table 'board' cell 'b' spans 2 columns from column 2 of 2, table 'board' declares text_color_by without text_colors (both or neither)
String slots64 per indicatorstring(...) declarationsone 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)
Frames8 per indicator, each at most 96 KiB (98,304 bytes)frame(...) declarations and their max_bytesa snapshot per widget instead of one frame per payloadnamed by field
Mini-chart grid1..12 panels of 0..100 candles; labels 1..24 characters, the title at most 40the frame of one draw.minichartsa grid of more than 12 markets or timeframesa grid holds at most 12 panels, a panel holds at most 100 candles
Mini-chart lookcolumns 1..4, panel_width 72..320, panel_height 48..220, gap 0..48, x and y -2000..2000, ma_width 0.5..5the options of one draw.minichartscolumns must be within 1..4
Docked profiles4 per indicator; a frame of 1..512 rows; width_frac 0.05..0.5 or width_px 16..600 (never both); offset x 0..4096; thickness_px 2..40; opacity, outside_opacity 0..1; series 1..8 with unique names; gradient 2..8 colours; border_width, value_area_width, poc_width 0..10 (the POC at least 1); font_size 6..64; step, scale_max above 0; labels_text 0..24 characters a row, tooltips 0..64, poc_label_text 1..24plot.levels(...) and its frame (Docked profiles)a level beside itself or on the other dock, a behind_candles profile on the lower pane, a scale: "fixed" with no scale_maxwidth_px is refused beside width_frac, scale_max needs scale 'fixed', wrun_frame_invalid for a frame whose series count or shape disagrees with the declaration
Level spans64 spans per frame, 1..512 prices each, 4096 rows across every spana plot.levels frame under span: "time"one span per bar instead of one per sessionwrun_frame_invalid: <frame>.spans
Panels8 per indicator; series 1..8 (a table 1..12); frame rows 2000 (a table 128, a pie or tiles 24); markers and guides 0..8; summary 0..5 rows; height_frac 0.05..0.9; title 1..40, caption 1..64; x_title, y_title 1..40; width 0..4096, column_widths 0..4096 each (one per column), cell_padding 0..64, offset -200..200, font_size 6..64, border_width and grid_width 0..10, background_gradient 2..8, palette 2..8, bins 2..200, columns 1..8, slice_gap 0..8, trail_width 0.5..6, a series width 0.5..20, marker and guide width 0.5..4, labels 1..24panel.<kind>(...) and its frame (Frames, panels and compact widgets)a 9th marker, a word on a kind that does not take it, a rich-line word on a boxed linemarkers allows at most 8, table allows at most 128 rows, series allows at most 12 columns on a table, <word> is only valid for <kinds>, <word> needs chrome 'grid' or 'none' on a time, category or stacked line
Side stripwidth_px 160..480, height_px 80..800, both with place: "side" only; the strip takes at most 40% of the chart and folds away under 480 pxa panel placed beside the chart or a matrix with dock: "side"a side panel on a narrow layoutwidth_px is only valid with place: 'side'
Heatmaps4 per indicator; palette 2..8 colours; auto_quantile 0.5..1; cell_gap 0..4; opacity 0..1; tooltip 1..200 characters, label 1..40; out.grid rows 2..128, and the grid's outputs count toward the 256plot.heatmap(...) and out.grid(...) (Price canvases)a grid with a row per tick, cells beside gridheatmaps must declare at most 4 entries, out.grid 'heat' rows must be between 2 and 128, heatmap 'h' takes cells or grid, not both
Profiles4 per indicator across footprints, TPOs and time-anchored profiles; imbalance_ratio 1.1..20; stacked_imbalances 0..10; value_area 0.5..0.95; poc_width 1..10; letter_minutes 1..240; a TPO palette 2..26; width_frac 0.05..1; outside_va_opacity, background_opacity 0..1plot.footprint(...), plot.tpo(...), plot.profile(...)a footprint over book cells, a TPO colour mode that needs profile cellsfootprint 'fp' does not take period, tpo 't' color_mode 'volume' needs cells from a volume_profile input, profile 'p' behind_candles needs span 'session' and a filled mode
Matrices4 per indicator, wrun-4 only; a frame of 1..128 prices, 1..12 columns and at most 1536 cells; cell text and column labels 1..16 characters; column_width 24..160; row_max_px 8..64; cell_padding 0..12; tooltip 1..200, label 1..40plot.matrix(...) and its framea matrix on a wrun-3 sheet, a 13th columnmatrices needs abi_version "wrun-4" (wrun-3 has no frame channel), wrun_frame_invalid: <frame>.cells
Widgets8 cards per indicator and 32 cards, feeds and meters per pane; a card's rows 1..12 (0..12 with a headline); title 1..40 (80 with a template), a row label 1..24 (64 with a template), a literal value 0..64; offset -4096..4096; width 80..1200; corner_radius 0..32; padding 0..24; border_width 0..10; font_size and title_font_size 6..64; background_gradient 2..8; state_colors exactly 4; a ladder frame 1..256 rows, width_frac 0.02..0.5, offset x 0..4096 with y 0; a feed 1..50 lines of 1..80 characters; a meter ramp 2..5, bar_height 2..40draw.card, draw.feed, draw.meter, draw.ladder (Status cards)a card with neither rows nor a headline, accent_color beside state_colorscard 'x' needs a row or a headline, title takes 1 to 40 characters, 80 with a {{template}}, offset entries must be integers from -4096 to 4096, accent_color and state_colors are exclusive
HUD cardswidth 80..1200 (300, or 180 at one column); offset -4096..4096; corner_radius 0..32; padding 0..24; border_width 0..10; text_glow 0..8; accent_colors 2..8; one headline tile per card; rings 1..3; a tile font_size 6..64, a tile height 16..120render.hud(...) and its tiles (HUD cards)two headline tiles, a fourth ringa HUD takes at most one headline tile, look must be one of 'default', 'glass', ...
Number formatdecimals 0..8; unit at most 8 characters on panes, panels, levels, cards, ladders and canvases; decimals and signed only beside format (an output's unit stays unbounded with or without format, as it always was; without format it is never printed)every format word (outputs, panes, panels, levels, cards, ladders, canvases)a unit longer than 8 characters on a pane, panel, level, card, ladder or canvasdecimals must be an integer from 0 to 8, unit takes at most 8 characters, decimals needs format
Text size6..64 pxsize on text and label renderers, and every font_sizesize must be >= 6, size must be <= 64, font_size must be an integer from 6 to 64
Type wordsfont_family ui/mono/serif/rounded, font_weight normal/medium/bold, valign top/middle/bottom, gradient_direction vertical/horizontal, corner_radius 0..32every surface that takes them (panels, levels, cards, feeds, meters, ladders, HUDs, handles, drawings, renderers, canvases)a CSS family name, a numeric weightfont_family must be one of 'ui', 'mono', 'serif', 'rounded', font_weight must be one of 'normal', 'medium', 'bold', corner_radius must be between 0 and 32
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, a whole or fractional literalfrom / 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..20 (0.05..20 on bar and histogram)widths 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..10borderWidth on a boxborder_width must be <= 10
Edge width1..10, integeredge_width on a rangeedge_width must be <= 10
Gradients2..8 colorsgradient on a range, a line or a docked profile, fill_gradient on an area, background_gradient on a widget, a HUD or a table, a box's gradientnamed by field, <key> must list 2 to 8 colours
Opacity0..1opacity on an output or a box (box default 0.2)named by field
Color string1..64 chars, or a theme tokenany color on a box, segment, renderer, or drawing; theme.up, theme.down, theme.text, theme.muted, theme.bg, theme.grid and theme.accent pass everywhere a colour goes (a colour param's default and presets excepted), and any other theme. word is refuseda theme. word outside the sevencolor must be a non-empty string of at most 64 chars, colour token must be one of theme.up, theme.down, theme.text, theme.muted, theme.bg, theme.grid, theme.accent, a colour param takes #rrggbb or #rrggbbaa; put the token on the colour key
Colour word#rrggbb, #rrggbbaa or a theme tokenevery colour key this round added (panels, levels, cards, feeds, meters, ladders, HUDs, canvases, and the frames they read)rgba(...) or a named colour on a panel seriesa colour must be #rrggbb, #rrggbbaa or one of theme.up, theme.down, theme.text, theme.muted, theme.bg, theme.grid, theme.accent
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 celled inputa celled input declared without itcelled input 'profile' needs max_cells, e.g. input("profile", volume_profile.cells, { max_cells: 512 }) ...
Bucket size from a settinga param.int whose min..max lies inside 1..500ticks_per_bar: "@<param>" on a volume_profile.cells inputan open-ended setting, or one that reaches past 500input 'profile' ticks_per_bar references "@ticks", whose min..max (open..open) must lie inside ticks_per_bar's range 1..500
Colour alphaa decimal from 0 to 1 after the setting's name, "@<param>/0.4"every colour bound to a setting: the default lands as rgba() where the colour takes it, as #rrggbbaa where it takes a colour worda panel series colour, which binds without an alphawhose alpha is not a decimal from 0 to 1 ("@bull/0.4"), a panel series colour binds without an alpha
Outputs1..256output declarations; the sandbox tracks 256 output slots per run (a file whose visuals are all frames may declare none)a file past 256 outputs, or a raw write past the tracked slotsWRUN output index 300 out of bounds for 256 tracked outputs
Settings128 per indicator, after expansion: most kinds count 1, a unit list on a number 1 more, a range 2, a session 3, a list its max + 1, a draw.minicharts grid that binds no setting 9 to 16 (its look's own settings)param.<kind> declarations and each hidden market.* setting; OpenMarket's cloud accepts at most 128 settings per indicator, so the build refuses the declaration that crosses the line (Picks, lanes, the cap)a list whose max alone crosses 128 (a list's max counts its slots and the count, so max is at most 127), or many compositesthe sheet would carry 129 params after expansion (a range derives two, a list max + 1, a session three, a unit list one more); hosted lanes cap params at 128
Text settingsmax_bytes 1..4096 UTF-8 bytes per setting, 256 when left out; the default within it and free of invisible and control charactersparam.text / param.text_area declarations (Setting kinds)a default longer than its cap, a pasted name carrying a bidi controlparam.text 'label' default is 12 UTF-8 bytes, over its max_bytes 4, param.text 'label' default carries a newline, a control or an invisible character

What the chart trims when it draws

These limits never refuse a run. The chart applies them when it draws, so one indicator can't bury another, hide the chart's own buttons, or draw more rows than a screen can show.

LimitValueWhat happens past it
Drawing and label text256 charactersthe text is cut and ends with an ellipsis
Drawing orderzorder -100..100a higher value draws as 100, a lower one as -100
Drawings per run2,000, counting every bar a segment draws, every declared drawing and every live handlethe newest 2,000 are drawn and the oldest are left out; the indicator's legend row says "Drawings capped", and its card says how many of the total were drawn
Widgets16 cards, feeds, meters and ladders per indicator per panethe ones past 16 are not drawn
Legend entries32 per indicator, each 64 charactersentries past 32 are not shown; longer text ends with an ellipsis
HUD cardsinside the price pane: below the symbol row and the legend, left of the price axisan offset that would move a card out is clamped; a card under 0.15 opacity lets clicks through
Card controlscontrols: "none"the glyphs stay hidden at rest; hovering always shows hide and collapse
TPO rows per period512 per day, week or monththe chart doubles the row height until every period fits, at most 6 times, for "auto" and a number alike; every run says so with a warning row in the Console and the warning on the legend chip: tpo 'tpo': row_height 1 coarsened to 8 (512 rows per day is the chart's limit) (Row height)
TPO rows on screen3 pxthinner rows merge on the price grid: a merged row's letters are the union of its rows' letters, its counts and volumes add up, and the POC and the value area are computed on the merged rows
TPO blocks262,144 per TPOthe blocks past it are not drawn, and the Console says so
Heatmap cells per indicatorthe chart's budget, across all of an indicator's heatmaps, the loaded history and the live bars together (one cell per book level, grid row or profile bucket on each bar)the oldest bars are dropped and the newest are drawn whole; the indicator's legend row says "Cells capped", and its card says how many bars are drawn

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 Declarations and the sheet.

Runtime ceilings

Enforced on every run by the sandbox your browser runs the module in. A breach refuses the WHOLE run by name and the editor's Console says why; nothing is truncated, clamped, or silently dropped. An indicator that already drew keeps its last good render, and its legend offers a retry.

LimitValueWhat it protectsWhen you hit itRefusal
Module memory4 MiB (4,194,304 bytes), fixedthe linear memory a module usesa buffer sized far past any param's maxWRUN memory 8388608 bytes exceeds limit 4194304 bytes
No growth after onStart()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 onBar()"The Indicator allocated memory after init() (65536 to 131072 bytes): the sandbox forbids growth once the bars start."
Output valuesfinite or NaNa chart and an alert that mean somethinga division by zero written as InfinityWRUN output 0 is Infinity; guard the calculation and emit NaN when the value is undefined
Run deadline20 s per run (the compile has its own 15 s)one runaway module never holds the sandbox: the worker is stopped and replacedunbounded loops, deep recursion"The run did not finish within 20 s."
Cell blockmax_cells tuples per bar, per celled inputthe one buffer of max_cells tuples the build allocates per celled input when the module starts; each bar's block is read into it once, and in_<input>_view() reads it in placea deeper book than the cap; a profile with more price buckets than the cap... input 1 bar at ts 1725580800000 has 1200 cells; max_cells is 1000, so the evaluation is refused (a block is never truncated); a volume profile declared missing: "empty" is not refused: that bar reads an empty block (in_<input>_cells() is 0) and the run goes on
TPO rows per bar4,096the rows one bar expands into at a TPO's row heighta declared row_height far below one bar's range ("auto" stays under it)tpo 'tpo': row_height 0.05 expands one bar into 5001 price rows, above the 4096-row limit (declare a larger row_height)
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 phaseonBar()the cell readers answer for the bar being evaluated; before the first bar (in onStart(), at module start) the count reads -1 and there is no blockthe host's cell import called in another phase, which the generated readers never doWRUN 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 phaseonBar()slots belong to the row being committeda string sent outside onBar()WRUN wrun_output_str is callable during finalize() only (called during state); ...
String slot bytesmax_bytes per slot (at most 4096)the buffer reserved 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; ...
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
Strings and frames per run2,097,152 (2 MiB), together; a live session counts as one runone run's text and frame snapshots across all barslong readouts on every bar of a long historyWRUN string and frame writes for this run total 2100000 bytes, over the per-run limit 2097152; 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 result8 MiB (8,388,608 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; handles and a strategy's result count too)many renderers over a long history with long strings; hundreds of live polylinesWRUN render result exceeds 8388608 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 creationa zone tracker that never deleteswrun_draw_kind_limit: 500 live box handles already exist; the cap is 500 per kind ...
Live handles in total1500every kind togetherthe same shape across kindswrun_draw_total_limit: 1500 live handles already exist; the cap is 1500 in total ...
Polyline handle points1..100,000 per callthe points one setPoints sends; the buffer must fit the module's memorya path that never trims its oldest pointswrun_draw_polyline_too_large, wrun_draw_polyline_empty, wrun_draw_polyline_out_of_bounds
Polyline points per run524,288 across the live polylinesthe points every live polyline holds, checked on each setPoints: a re-sent path gives its old points back first and a deleted one frees its ownhundreds of long paths kept alive at oncewrun_draw_polyline_budget
Draw calls per bar4096every handle call on one barre-sending every live handle on every barwrun_draw_calls_per_row: more than 4096 draw calls on one row ...
Draw phaseonBar()handle ops belong to the row being committeda handle operation outside onBar()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 file does not declare; 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 module contract is checked by Run before any of this: the four engine functions the build adds around onBar() with exact signatures (a file that exports them itself is checked the same way, The four-function form), 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 contract's allowlist. Before a module runs, the chart checks its bytes against the sheet's wasm_sha256 and refuses a mismatch. Those messages are in Common errors.

Data bounds

LimitValueWhere
Historythe bars the chart has loadedevery source is fetched over the chart's loaded window, and a pan back runs again over the extended window; warm-up is the file's decision: an output onBar() leaves unwritten is NaN and draws nothing
Live updatesthe forming bar, about once a second at mostticks in between are coalesced and the last one is never dropped; only the forming bar is replayed; a live gap wider than 250 bars rebuilds the run
Source deadline45 s per sourcea source that does not answer in time fails the run with its name
Empty sourcerefuseda required source that answers empty fails the run: "
Coarse pinsas of the coarser candle's closea pinned interval contributes to a row only once its candle has closed; that market's candles are fetched from two leg spans before the chart's first bar
Pinned intervalscoarser than the chart's and a whole multiple of it (WEEK counts 7 days)any other interval pin is refused by name before any fetch
Pinned venuescrypto, POLYGON, FX_OTCstocks and ETFs on POLYGON, forex, gold and silver on FX_OTC are served like a crypto pin; an index pin (POLYGON_INDICES) reads its missing fill with a warning row; a CME Group pin is refused by name (Multi-source)
Multi-day pins on session venues3d refuseda 3d candle on a stock, forex or gold market groups trading days, so the pin is refused before any fetch (wrun_pin_session_interval_unserved); pin 1d or 1w
Intrabarfiner than the chart's interval and dividing it evenly, 1m at the finestso the chart must be 2 minutes or coarser; max_cells at least the finer bars in one chart bar; closed finer bars only
Book depthat most 500 levels each sidethe chart's own order book at its own grouping, whatever block_size and max_depth say, so max_cells: 1000 covers any bar
Options chainthe live row onlythe chain arrives on the last row; size max_cells for the chain (a BTC chain is about 1,550 contracts)
Cell memory, per celled inputmax_cells x tuple width x 8 bytes4 f64 per volume_profile tuple, 3 per book tuple, 6 per intrabar bar, 10 per options_chain contract, all inside the 4 MiB of module memory; the build allocates this buffer once per celled input when the module starts, reads each bar's block into it once, and in_<input>_view() reads it in place (a copy through in_<input>_read(ptr) holds the block twice)
Alerts600 bars of the chart's interval, at mostthe window is the larger of the file's warmup() and its largest setting maximum (up to 500); an indicator that reads a coarser pin gets all 600 unless it also reads book or volume_profile cells, and those bars hold 600 / (leg / chart) candles of the pin (a 4h pin: 150 on 1h, 37 on 15m, 12 on 5m), so a longer higher-timeframe average arms but stays empty; another market's candles on a secondary ohlcv input and a coarser 1m to 1w pin that is a whole multiple of the chart's interval are served; a pin on the first input, a finer pin, a custom timeframe and a source alerts cannot evaluate are refused when you save the alert (Alerts)
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 8 MiB expanded render resultthe trades, orders and equity together with the drawings (wrun_render_result_too_large)
Strategy feedthe price input reads ohlcv on the chart's own market, no symbol or exchange pinrefused by name (wrun_strategy_feed_not_ohlcv); slippageModel: "bookEstimate" refused (wrun_strategy_slippage_model_unsupported); strategy.position and strategy.equity are reserved output names (wrun_strategy_reserved_output)

Publish bounds

LimitValue
Open source code256 KB ("The source is {size} KB; the limit is {limit} KB.")
What changedone line of up to 280 characters, from the second version
Namelowercase letters, digits, and dashes after your @yourname/ scope; fixed after the first publish

The module runs sandboxed: no filesystem, no network, no order capability. Each wrun indicator on a chart, a draft from Run included, counts against the chart's indicator limit. Publishing has the dialog.

Other bounds

Bounds that are not a number in the tables above.

ShapeIn wrun
Sources per indicatorNo per-indicator source budget. On the chart, a wrun indicator counts against your plan's per-chart indicator limit once per distinct source it reads (Data sources). Each source is fetched once per run over the chart's loaded bars, however many inputs read it; a pinned market or interval on ohlcv adds its own fetch, a funding or oi interval pin adds none.
Output objects per runNo object count. Every output is one number per bar, in at most 256 slots; decorations are declared once. Every bar emits a row, with NaN in each output onBar() did not write, so the leading NaN rows of a warm-up count toward the per-run budgets above. What reaches the chart is capped by the 8 MiB expanded render result, and past 2,000 drawings in a run the chart draws the newest 2,000.
Table cells32 x 8 = 256 cells per table renderer, each a string slot; 64 slots per indicator. A panel.table over a frame takes 128 rows by 12 columns instead, and a plot.matrix 128 prices by 12 columns within 1536 cells.
Collection sizeNo element cap. Collections live inside the 4 MiB module memory and are allocated before the first bar, at module start or in onStart().
Nesting and recursion depthThe compiler's own rules apply; the module's stack lives inside its memory, and the 20-second run deadline stops runaway recursion.
Backtest bars and runs per dayNo grant to spend: the chart's Strategy Tester runs a wrun strategy in your browser over the bars the chart has loaded and recomputes as you pan ("This strategy runs locally over the chart window and recomputes as you pan."), Reading the Strategy Tester.
Strategy declaration bounds (leverage, fees, funding)Checked by Run with the engine's rules: initialCapital and leverage more than 0, pyramiding an integer of at least 1, maintenanceMarginPercent 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 once before the first bar 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. The build sizes one buffer per celled input from it (in_<input>_capacity f64s, tuples x width) when the module starts, and in_<input>_view() reads it in place; size a book input for the chart's depth: at most 500 levels a side, so 1000 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 when you press Run, in the editor's Console, anchored on the declaration. A repeating shape is one declaration gated per bar with when, never one declaration per occurrence.
  • Segments count as drawings. A segment drawn on every bar of a long window is the shape that reaches 2,000 drawings per run; gate it with when or narrow its span.
  • The render cap counts expanded bytes. Sixty-four text renderers over a long history with long strings is the shape that reaches 8 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 onStart() 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 what the chart delivers: the chart's order book carries at most 500 levels a side, so max_cells is 1000; the cell buffer is the build's own, sized from that cap and read in place; and the readout slot is sized for its longest line:

input("close", ohlcv.close);
// The chart's order book carries at most 500 levels a side, so a bar never holds more than 1000 tuples:
// max_cells is a contract, and a bigger block would refuse the whole run.
// block_size is required by the declaration; the chart keeps its own price grouping.
input("book", book.cells, { max_cells: 1000, block_size: 10 });
output("imbalance", line, lower, { unit: "ratio" });
// The longest line is "500 bids / 500 asks" (19 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 });

function onBar(): void {
  const n = in_book_cells();
  if (n <= 0) return;
  // The build's own buffer, sized once at module start: max_cells x 3 cells per [price, size, side] tuple.
  const cells = in_book_view();
  let bidSize = 0.0;
  let askSize = 0.0;
  let bids: i32 = 0;
  let asks: i32 = 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;
  const imbalance = total > 0.0 ? (bidSize - askSize) / total : NaN;
  if (isNaN(imbalance)) return;
  out_imbalance(imbalance);
  sb_clear();
  sb_int(bids);
  sb_text(" bids / ");
  sb_int(asks);
  sb_text(" asks");
  str_depth_sb();
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine

The scan branches on each tuple's side, never on its position: the chart lists the bids best first, then the asks from the farthest to the best.