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 | 10 weighted slots | Weighted data-source slots per script (MAX_SOURCES_PER_SCRIPT): native streams cost 1, heavier types can cost more, the chart spine can cost 0 | Wide multi-venue aggregation within the slot budget | Data source limit exceeded at <line>:<col>: count=X, limit=MAX_SOURCES_PER_SCRIPT, max=10 |
MAX_OUTPUT_OBJECTS | 2,000,000 | Total materialized output objects across the run | Pushing values into batched plots in a loop on long history | Output object limit exceeded at <line>:<col> (max 2000000) |
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, weighted. Identical
source(...)calls dedupe to one. Derived series (htf(), math on an existing source) cost nothing because they fetch no new data. The budget is 10 weighted slots; how editor validation and the runtime surface it is being unified as part of source-slot billing, so treat 10 slots as the contract. - 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.
Backtest grants
Strategy backtests run under their own grants, separate from the sandbox limits above (launch defaults; server-tunable):
| Tier | Replay depth | Compute budget | Runs per day |
|---|---|---|---|
| Free | 1,000 bars | 10 s | 20 |
| Plus | 20,000 bars | 20 s | 200 |
Exceeding the daily allowance returns Daily backtest limit reached. The counter resets at midnight UTC. Sub-bar fill resolution and order-book slippage data load for plus-tier backtests; see Strategies.
Strategy declaration bounds (perps params since engine 3.0.63)
These broker parameters are validated when a strategy is compiled. Perps-specific parameters are accepted on every strategy declaration since engine 3.0.63, but only affect economics when instrument="perps" is active.
| Parameter | Type and bounds | Default |
|---|---|---|
instrument | "spot" or "perps" | "spot" |
leverage | number greater than 0 | 1 |
maintenanceMarginPercent | number at least 0 and less than 100 | 0.5 |
makerFeePercent | non-negative number | 0 |
takerFeePercent | non-negative number | 0 |
funding | "data" or "off" | "data" |
onLiquidation | "continue" or "halt" | "continue" |
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:
//@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:
//@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:
//@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"])