Every script runs in a sandbox with fixed ceilings. They exist so one script cannot exhaust memory, hang the render, or starve other scripts on the chart. You will never notice most of them. The table below lists each limit, the exact error you get when you cross it, and the kind of script that runs into it.
Limits at a glance
| Limit | Value | What it caps | When you hit it | Error when exceeded |
|---|---|---|---|---|
| Source budget | 20 | Distinct data-source subscriptions per script | Wide multi-venue aggregation. Weighted types count for more (orderbook = 3) | Script exceeds the source budget (21/20) (build); Data source limit exceeded at <line>:<col>: count=21, limit=MAX_SOURCES_PER_SCRIPT, max=20 (run) |
MAX_OUTPUT_OBJECTS | 50000 | Total plotted/drawn objects across the run | Pushing thousands of values into a plot in a loop | Output object limit exceeded at <line>:<col> (max 50000) |
MAX_DRAWINGS_PER_KIND | 500 | Drawings of a single kind (lines, boxes, labels, …) | Drawing a line.new / box.new / label.new per bar on long history | Drawing per-kind limit exceeded at <line>:<col>: kind=line, count=501, max=500 |
| Total drawings | 3000 | Drawings across all kinds combined | Many drawing kinds at once on long history | per-kind error fires first for any single kind |
MAX_TABLE_CELLS | 10000 | Cells in a single table.new (rows × cols) | A large dashboard or data dump table | table.new cell limit exceeded at <line>:<col>: rows=101, cols=100, cells=10100, max=10000 |
MAX_COLLECTION_SIZE | 100000 | Elements in one array or map | Accumulating every bar's value into one collection forever | Collection size limit exceeded at <line>:<col> (max 100000) |
MAX_STRUCT_CONSTRUCTION_DEPTH | 64 | Nesting depth when building a struct/object literal | Deeply nested literal built in one expression | Struct construction depth limit exceeded at <line>:<col> (max 64) |
MAX_RECURSION_DEPTH | 200 | Self-calls deep a user function may go | Unbounded or very deep recursion | Recursion depth limit exceeded at <line>:<col> (max 200) |
A few notes that save debugging time:
- Source budget is per distinct subscription. Identical
source(...)calls dedupe to one. Derived series (htf(), math on an existing source) cost nothing because they fetch no new data. The editor and the runtime enforce the same 20, so a script that runs is also accepted by validation. - Drawings have two ceilings. 500 of any single kind, and 3000 across all kinds. The per-kind limit almost always trips first.
- Most limits are about loops on long history. If you create or push something once per bar without bound, you are the script these protect against. Cap the work, or gate it behind
isLastBar.
Examples
These scripts each cross one limit on purpose, so you can see the shape that triggers it.
Output objects, by pushing past 50000 values into a plot:
scripts/probes/limits/max_output_objects_boundary.ks
//@version=2
define(title="Max Output Objects Boundary", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
var values = []
if (isLastBar) {
for (var n = 0; n < 50001; n += 1) {
values.push(trade.close + n / 1000)
}
}
plotLine(value=values, colors=["#dc2626"], width=1, label=["Repeated"], desc=["repeated plot values"])
plotLine(value=trade.close, colors=["#2563eb"], width=2, label=["Close"], desc=["anchor for output object boundary"])Table cells, with a 101 × 100 table that lands at 10100 cells:
scripts/probes/limits/max_table_cells_boundary.ks
//@version=2
define(title="Max Table Cells Boundary", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
if (isLastBar) {
table.new("top_right", 101, 100, {bgcolor: "#ffffff"})
}
plotLine(value=trade.close, colors=["#dc2626"], width=2, label=["Close"], desc=["anchor for table cell boundary"])Recursion depth, with a function that descends 201 levels:
scripts/probes/limits/max_recursion_depth_boundary.ks
//@version=2
define(title="Max Recursion Depth Boundary", position="offchart", axis=true)
func descend(n) {
if (n <= 0) {
return 0
}
return 1 + descend(n - 1)
}
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
var score = trade.close
if (isLastBar) {
score = descend(201)
}
plotLine(value=score, colors=["#dc2626"], width=2, label=["Score"], desc=["recursion depth boundary"])