OpenScriptv0.5.0Documentation
GitHub

OS5xxx Limits

The errors raised when a script runs out of a budget: loop turns, array and string size, drawing objects, nesting, state, program size, data requests and time per bar.

On this page
  1. When they appear
  2. A script that stays inside its budgets
  3. Loops and time
  4. Memory: arrays, strings and drawings
  5. Program shape: nesting, state and size
  6. Host ceilings

This page covers the OS5xxx codes of OpenScript (also called OpenAlgo Script): the errors raised when a script reaches one of the budgets it runs inside. Every limit is a number you can see, and reaching one is always reported, never absorbed: a loop that runs out of turns stops the bar rather than breaking out early and handing you a plausible wrong number. Most of these codes point at a script that grows without bound, such as a loop with no exit, an array nobody trims or a drawing created on every bar, so the fix is usually a small change to the script rather than a bigger budget.

When they appear

The codes fall into three groups by the moment they are found.

WhenCodesWhat happens
When the script compilesOS5005 for nesting in the sourceShown in the console under the editor. Nothing runs
When the program loads, before bar 0OS5003, OS5004, OS5006, OS5009, and OS5005 for call depthThe study or run is refused whole and nothing is drawn. On a chart, /trading shows the code and the message in a notice
While a bar runsOS5001, OS5002, OS5007, OS5008, OS5010The run stops at that bar, as for any runtime error

The first five rows of the table below have defaults every script meets. The last row holds ceilings that exist only when the host sets them. The host is the application the engine runs inside: in OpenAlgo, the /trading page for charts and backtests, and the strategy runner on the OpenAlgo server for a deployed strategy. Neither sets a ceiling on program size, state, data requests or time per bar, so OS5003, OS5004, OS5006, OS5007 and OS5009 come only from hosts that set their own, such as a server running many strategies for many people, or your own integration.

BudgetDefaultChanged by the scriptCode
Loop turns per bar, all loops together2,000,000Yes, limits(loops = n)OS5001
Elements in one array1,000,000NoOS5002
Characters in one string100,000NoOS5008
Drawing objects held at once10,000NoOS5010
Nesting in the source, and nested calls128 levels, and 64 callsNoOS5005
Time per bar, state regions, data requests, program size, limits() valuesNo ceiling unless the host sets oneNoOS5007, OS5004, OS5006, OS5009, OS5003

Limits explains every budget in full, with the reasoning behind each number.

A script that stays inside its budgets

This study draws a box on every 20 bar breakout, keeps a rolling window of closes and measures the current rising run. Each part is written so that it cannot grow without bound, however long the chart.

version 1
study("Recent breakouts", overlay = true)

keep = input(20, "Boxes kept", min = 1, max = 500)

// Drawings: delete the oldest as the newest arrives, and remove its element
// with it, so the count stays bounded (OS5010).
var zones: array<box> = []
if crossUp(close, highest(high, 20)[1])
    // Times are in milliseconds, so 3600000 is one hour to the right.
    push(zones, draw.box(time, high, time + 3600000, low))
if size(zones) > keep
    draw.delete(element(zones, 0))
    shift(zones)

// Arrays: a window with a fixed length, never a log that only grows (OS5002).
var closes: array<number> = []
push(closes, close)
if size(closes) > 250
    shift(closes)

// Loops: every while has a cap and a counter that moves (OS5001).
run = 0
while run < 50 and close[run] > close[run + 1]
    run += 1

plot(avg(closes), "Mean of the last 250 closes", silver)
plot(close[run], "Start of the current rising run", aqua, style = "step")

Loops and time

OS5001 Loop budget exhausted

Errorruntime

This bar used its budget loop iterations, and the loop at line line was still running.

What it means

Every turn of every loop in a bar counts against one budget, 2,000,000 turns by default, shared by all the loops that run in that bar and reset at the start of the next, so a long chart is never by itself a reason to fail. When the budget runs out, the bar stops. The engine does not break out of the loop and carry on, because a loop cut short produces a number that looks right and is not. The message names the budget and the line of the loop that was still running, and the fix suggests a larger budget.

Almost always the cause is a while whose condition never becomes false, because nothing in its body changes what the condition tests. Make sure every loop ends first: give a while a counter and a cap, as the example above does. Raise the budget with limits(loops = n) under the declaration only when the work really is that large, and say why in a comment. See Control flow.

How to fix it

Fix the exit condition, or raise the budget in one line: limits(loops = suggested).

Before

while close[i] > close[i + 1]
    total += close[i]

After

while i < 500 and close[i] > close[i + 1]
    total += close[i]
    i += 1

OS5007 The bar took too long

Errorhost

Bar bar ran for ms ms and the host allows max ms.

What it means

A host that runs many strategies can give each bar a wall clock budget, so that one slow script cannot hold up the rest. The message names the bar, the time it took and the budget. There is no limits() option for this budget, and the engine reads no clock at all unless the host asks it to, so the same script cannot pass on a fast machine and fail on a slow one by default.

A slow bar is nearly always repeating work over the whole history: a loop from 0 to bar.index costs one turn on the first bar and 40,001 on bar 40,000, so the study slows down as the chart grows. Carry a running value in a var instead, or use cum(). See Profiling and speed.

How to fix it

Keep a running value in a var and update it per bar instead of recomputing over the whole history.

Before

total = 0.0
for i = 0 to bar.index
    total += close[i]

After

var total = 0.0
total += close

Memory: arrays, strings and drawings

OS5002 The array is too large

Errorruntime

An array holds at most max elements; name reached size.

What it means

An array holds at most 1,000,000 elements, and limits() cannot raise that. The ceiling exists so that one script cannot use up the memory of the browser tab and take the chart down with it. The message names the array and the size it reached.

An array that large is almost never a real need. It is a window that nothing trims: a var array with one push() per bar and no shift(), which on a chart of 1 minute bars grows by 375 elements every NSE session. Decide how much of the past you need, and drop the oldest element as you append, as the example above does.

How to fix it

Drop the oldest element as you append: if size(arr) > 500, shift(arr).

Before

var window = [0.0]
push(window, close)

After

var window = [0.0]
push(window, close)
if size(window) > 500
    shift(window)

OS5008 The string is too long

Errorruntime

A string holds at most max characters; this one reached found.

What it means

A string holds at most 100,000 characters. The length is checked before the string is built, so a str.repeat() asked for a huge result stops cleanly instead of using up memory. The message gives the length the string would have reached.

One shape reaches this ceiling, and it is almost always the same one: text appended to a var string on every bar, a log that nothing ever trims. Keep the pieces in an array, trim it to the lines you show, and join only those with str.join() on the last bar.

How to fix it

Keep the pieces in an array, trim it to the rows you display, and join only those.

Before

var logLines = ""
logLines += text(close) + "\n"

After

var logLines: array<string> = []
push(logLines, text(close))
if size(logLines) > 50
    shift(logLines)

OS5010 Too many drawing objects

Errorruntime

A script holds at most max drawing objects; this one would be number found.

What it means

A script holds at most 10,000 drawing objects at once. A drawing lasts until the script deletes it with draw.delete(), and dropping the last name that refers to it does not delete it, so a script that creates a line or a box on every bar and deletes none keeps growing for as long as the chart is open. When one more object would pass the ceiling, the bar stops and the message names the number it would have reached. The oldest object is never dropped to make room, because a study that is right on the right of the chart and quietly wrong on the left is worse than one that stops.

Delete what you no longer want and bound the set: keep the objects in an array, and when it is longer than you want, delete the oldest object and remove its element together. draw.count() tells you how many objects the script holds. See Lines and boxes.

How to fix it

Delete each object when it stops being wanted, and bound the set: keep the objects in an array, and when it is longer than you want, delete the oldest object and remove the element.

Before

var zones: array<box> = []
push(zones, draw.box(time, low, time, high))

After

var zones: array<box> = []
push(zones, draw.box(time, low, time, high))
if size(zones) > 50
    draw.delete(element(zones, 0))
    shift(zones)

Program shape: nesting, state and size

OS5005 Nesting is too deep

Errorparse

construct is nested found deep and the ceiling is max.

What it means

Expressions and blocks may nest 128 levels deep in the source, and a function may call a function 64 levels deep. Deeper source is refused when the script compiles, and a program whose calls would nest deeper is refused when it loads. The ceilings keep the compiler and the engine inside a bounded stack, so no file can freeze the page. They are far above anything written by hand, and generated source is the usual way to reach them.

The fix is also the readable change: give the inner part of a deep expression a name at the top level, and use the name. Recursion, a function calling itself, is not allowed at all (OS2005), so the call depth of any program is known before the first bar.

How to fix it

Flatten it: give the inner expression a name at the top level and use the name.

Before

z = a ? b ? c ? d ? 1 : 2 : 3 : 4 : 5

After

inner = c ? (d ? 1 : 2) : 3
z = a ? (b ? inner : 4) : 5

OS5004 The program needs more state regions than the engine allows

Errorhost

This program needs found state regions and the engine allows max; first calls second on several paths.

What it means

Every stateful call, such as ema() or a function that keeps a var, stores what it carries from bar to bar in its own state region, one per call path (each distinct route through the calls that reaches it). When a function calls another more than once, and is itself called more than once, the paths multiply: two calls of outer, each calling inner twice, keep four separate averages. A host may cap the number of regions, and a program over the cap is refused when it loads. The message names the two functions where the multiplication happens.

Call the inner function once at the top level, give its result a name, and pass the name down or read its history with [], as the fix below does. See User functions.

How to fix it

Call the inner function once at the top level, give its result a name, and pass that name down.

Before

fn inner(src) => ema(src, 20)
fn outer(src) => inner(src) - inner(src[1])
v = outer(close) + outer(hlc3)

After

fn inner(src) => ema(src, 20)
base = inner(close)
v = base - base[1]

OS5009 The program is too large

Errorhost

This file compiles to found instructions and the ceiling is max.

What it means

The compiled program is held in memory for every chart and every running strategy that uses it, so a host may set how large a program it will hold. A file that compiles to more instructions than that is refused when it loads, naming the count and the ceiling. You will not reach this by writing a study by hand.

A file that size is nearly always the same block repeated with small changes, such as a dozen moving averages written out line by line, or generated source. Move the repeated block into a function with fn and call it, and delete branches the script no longer uses.

How to fix it

Move the repeated block into a fn and call it, and delete branches the script no longer uses.

Before

a1 = sma(close, 10)
a2 = sma(close, 20)
a3 = sma(close, 30)

After

fn avgOf(len) => sma(close, len)
a1 = avgOf(10)
a2 = avgOf(20)
a3 = avgOf(30)

Host ceilings

OS5003 The host refused this limits() value

Errorhost

This host allows option up to max; the file asks for found.

What it means

A script sets two of its own budgets with limits(): loops and history. A host may refuse to spend more than a certain amount on either, and when a file asks for more, the host refuses it when it loads rather than quietly running it on a smaller budget, because a script that ran under a budget it did not ask for would produce numbers nobody could reproduce. The message names the option, the value the file asked for and the most the host allows.

For loops the comparison uses the default budget of 2,000,000 when the file writes no limits() line, so a host whose loop ceiling is lower refuses a file that never mentions limits(). The history depth has no default to compare, so a file without limits(history = n) is never refused for it. Lower the value, or run the file on a host that allows more. This is the host telling you which of its limits you met, not a fault in your script.

How to fix it

Lower option to max or below, or run the file on a host that allows more.

Before

limits(loops = 500_000_000)

After

limits(loops = 50_000_000)

OS5006 Too many outstanding data requests

Errorhost

This file makes found data requests and the host allows max.

What it means

Each req.timeframe() or req.symbol() read is a separate series the host fetches and keeps in step with the chart. A host may cap how many one file makes, and a file with more is refused when it loads, naming the count and the ceiling. It is refused rather than having the extra reads dropped, because a dropped read is a plot that quietly turns absent.

Every read written in the file counts, even two identical ones: req.timeframe("1D", high) written twice is two requests. Read each series once, assign it to a name and reuse the name, including for its history (dayHigh[1]), and delete reads whose results are unused. See Higher timeframes.

Related. Limits, Profiling and speed, OS4xxx Runtime errors, Reading an error, Control flow, Collections

How to fix it

Keep one request per symbol and timeframe, reuse the name it assigns, and delete the requests whose results are unused.

Before

dayHigh = req.timeframe("1D", high)
prevHigh = req.timeframe("1D", high)[1]
weekHigh = req.timeframe("1W", high)

After

dayHigh = req.timeframe("1D", high)
prevHigh = dayHigh[1]
weekHigh = req.timeframe("1W", high)