OpenScriptv0.5.0Documentation
GitHub

OS4xxx Runtime errors

The errors a script raises while a bar runs, when a value it computed cannot be used: a history index, a length, an array index, a deleted drawing or an absent loop bound.

On this page
  1. When they appear
  2. A script that guards against them
  3. Every code at a glance
  4. History reads
  5. Whole numbers and names
  6. Arrays
  7. Drawing objects and tables
  8. Colours, dates and strings
  9. Loops

This page covers the OS4xxx codes of OpenScript (also called OpenAlgo Script): the errors raised while a bar runs, after the script has compiled cleanly. They are the first codes that depend on the data. A script can run perfectly for 30,000 bars and then stop on the one bar where a computed length comes out as 7.5, or an array is read before it holds enough elements. Knowing what each code means lets you find the line and the value quickly, and write the guard that keeps the next script from reaching it.

When they appear

A runtime error is raised on the bar that produced the value, which may be deep in history or the newest bar. The message fills in the real value it met, such as sma's len was 7.5 on this bar, so you can see which value went wrong. The script never skips the bar silently, because a gap with no explanation looks exactly like a gap the script meant.

  • On a chart, the study draws nothing. When you add it, /trading shows a notice with the code, the message and the fix. If a study already on the chart starts failing later, for example when a new bar arrives, the Objects panel lists it with the status Error.
  • In the Backtest panel, the strategy stops trading at that bar: the report lists only the trades made before it, and the equity curve runs flat from there to the end of the range. The panel does not show the error itself, so a curve that goes flat and stays flat is worth checking for one. See Backtesting.
  • In a deployed strategy, the run stops at that bar, sends nothing further and writes the code, the bar, the line and the column to the run's log. See Sandbox and live.

Some of these problems have a compile-time twin: a literal the compiler can see is refused before any bar runs (OS3004 for some whole-number arguments, such as the rows of a table(), and OS3008 for a name outside an accepted set). In version 0.5.0 that check does not cover history indexes or lengths: close[1.5], close[-1], sma(close, 7.5) and str.repeat("ab", 2.5) all compile and then stop on bar 0.

A script that guards against them

Every guard in this study is there because of one of the codes below. It runs on any instrument and interval.

version 1
study("Window momentum")

len = input(21, "Window, in bars", min = 2, max = 500)

// A length computed from an input is rounded before a call receives it,
// because sma counts whole bars (OS4003).
half = floor(len / 2)
smooth = sma(close, half)

// A history index is a whole number, zero or more (OS4001).
back = max(0, round(len / 4))
momentum = smooth - smooth[back]

// An array is read only inside its extent (OS4004).
var window: array<number> = []
if not isNone(momentum)
    push(window, momentum)
if size(window) > len
    shift(window)
oldest = size(window) > 0 ? element(window, 0) : none

// A bound computed from data is absent until the data exists, so the loop
// is guarded rather than left to stop the bar (OS4013).
sinceCross = barsSince(crossUp(close, smooth))
stretch = none
if not isNone(sinceCross)
    span = min(sinceCross, 50)
    total = 0.0
    for i = 0 to span
        total += close[i] - smooth[i]
    stretch = total / (span + 1)

plot(momentum, "Momentum", aqua)
plot(oldest, "Momentum a window ago", silver)
plot(stretch, "Average distance above the mean since the cross", orange)

Every code at a glance

Seven of the thirteen codes are reserved for checks the engine does not make yet. The table says what happens today in each case, so you know which guard to write now.

CodeWhat it catchesIn version 0.5.0
OS4001A history index that is fractional or negativeRaised
OS4002A history read deeper than limits(history = n) keepsRaised
OS4003A length, count or position that is fractional, or below what the function acceptsRaised
OS4004An array index outside the arrayRaised, and also covers OS4006 and OS4008
OS4005A setter on a deleted drawing objectRaised
OS4006Taking an element from an empty arrayNot raised yet: OS4004, or absent
OS4007A reversed or out of range sliceNot raised yet: the slice is shortened, or OS4003 for a negative bound
OS4008A table cell outside the tableNot raised yet: OS4004
OS4009A colour channel out of rangeNot raised yet: the channel is clamped
OS4010A calendar field out of rangeNot raised yet: the date rolls over
OS4011A string position outside the stringNot raised yet: a shorter or empty string
OS4012A computed name outside the accepted setNot raised yet: absent, or a fallback, depending on the call
OS4013A loop bound that is absentRaised

History reads

x[n] reads the value of x as it stood n bars ago. See Bars and history.

OS4001 History index is not usable

Errorruntime

[index] is not a whole number of bars at or above zero.

What it means

x[n] counts bars back from the current one, so n has to be a whole number of bars, zero or more. There is no half a bar ago, and a negative index would read the future, which no script can do. The usual cause is a computed index: close[len / 2] with an odd len, or an offset that drops below zero after a subtraction on some bar.

The check runs while the bar executes, even for a literal, so close[1.5] and close[-1] compile and then stop on bar 0. Reading further back than the chart goes is not this error: close[500] on bar 20 is simply absent, because that value never existed.

How to fix it

Wrap the index in floor() or round(), and clamp a computed index with max(0, n).

Before

prev = close[len / 2]

After

prev = close[floor(len / 2)]

OS4002 History index is deeper than the retained depth

Errorruntime

[index] reaches past the retained depth of depth bars.

What it means

By default the engine keeps the whole history of every series your script reads, so any depth works and this error never appears. It appears only after a limits(history = n) line caps the depth, and a read then reaches further back than n bars. With limits(history = 50), close[50] works, and close[120] is absent for the first 120 bars (like any read before the chart begins) and then stops the run on bar 120, the first bar where the value would have existed.

The difference from absence is deliberate. A read before the first bar is absent because the value never existed; a read past the kept depth is an error because the value existed and was thrown away, and a quiet gap there would hide a real bug. The message suggests a depth that covers the deepest read. See Limits.

How to fix it

Raise the depth in one place: limits(history = suggested).

Before

study("Long lookback")
limits(history = 50)

old = close[120]

After

study("Long lookback")
limits(history = 120)

old = close[120]

Whole numbers and names

OS4003 A whole number was required here

Errorruntime

name's argument was found on this bar; a whole number was required.

What it means

Lengths, counts and positions passed to a function are whole numbers: sma() averages a whole number of bars, and str.repeat() repeats a string a whole number of times. The value is not rounded for you, because a length of 7.5 is a mistake in the script and rounding it silently would hide the mistake. The usual cause is arithmetic on an input, such as len / 2 when len is odd, or a length derived from volatility.

The same code stops a value that is whole but below what the parameter accepts: sma(close, 0), sma(close, -3) and a negative position in slice() all raise it, although the message still says "a whole number was required". Read it as "a usable whole number". The message names the function, the parameter and the value it received on that bar, for example sma's len was 0 on this bar.

In version 0.5.0 a literal such as sma(close, 7.5) also compiles and stops on bar 0. Round every length you compute with floor(), round() or ceil(), and keep it at 1 or more with max() when it can shrink.

How to fix it

Round the value before passing it: floor() towards zero, round() to nearest.

Before

s = sma(close, len / 2)

After

s = sma(close, floor(len / 2))

OS4012 That value is not one of the accepted names

Errorruntime

argument accepts values; found was computed on this bar.

What it means

Some parameters accept only a fixed set of names, such as the order of sort() ("asc" or "desc") or the type of ma(). A name written as a literal is checked when the script compiles, with OS3008. This code is for a name the script computes, such as a ternary that picks between two strings, when one of them is not in the set.

Not raised yet. In version 0.5.0 nothing raises OS4012, and what happens instead is quieter than an error, and depends on the call. ma(close, 20, kind) with kind computed as "exponential" is absent on every bar and plots nothing. sort() given a computed name it does not accept, such as "ascending" or "descending", sorts in ascending order, so a computed "descending" quietly sorts the wrong way. Take the choice from an input() with an options list, so only accepted names can reach the call.

How to fix it

Produce the value from an input() with an options list, so only accepted names can reach the call.

Before

sortOrder = up ? "ascending" : "desc"
sort(values, sortOrder)

After

sortOrder = up ? "asc" : "desc"
sort(values, sortOrder)

Arrays

An array holds the elements your script put into it, numbered from 0 to size - 1. See Collections.

OS4004 Array index out of range

Errorruntime

Index index is outside name, which holds size elements.

What it means

Reading or writing an array outside its elements is an error, not absence, because the extent is something your script chose: an index past the end means the script has lost count, while a read before the start of a price history is only data that does not exist yet. Common causes are reading values[10] before eleven elements have been pushed, using size(values) as the index of the last element (it is size(values) - 1), and a loop that runs one step too far. element() and set() are held to the same rule.

In version 0.5.0 this code also covers two cases that have their own codes planned. shift() or pop() on an empty array raise it, with the index described as "the first element" or "the last element" (OS4006), and so does a cell() written outside a table's grid, with the index given as a row and column pair (OS4008).

How to fix it

Guard the read with size(name), and index from size(name) - 1 for the last element.

Before

last = values[10]

After

last = size(values) > 10 ? values[10] : none

OS4006 The array is empty

Errorruntime

name cannot take an element from an empty array.

What it means

Taking an element out of an empty array has no answer, and neither has the average, the lowest or the highest of no values. This code is planned to stop the bar in all of those cases, rather than let a script drain an array without noticing.

Not raised yet. In version 0.5.0 nothing raises OS4006. shift() and pop() on an empty array raise OS4004 instead, and avg(), min() and max() of an empty array return absent. Test size(arr) > 0 before any of these calls: it is correct today and stays correct when this code arrives.

How to fix it

Test size(arr) > 0 before the call.

Before

oldest = shift(window)

After

oldest = size(window) > 0 ? shift(window) : none

OS4007 Slice range is invalid

Errorruntime

slice(from, to) is not a range inside an array of size elements.

What it means

slice() takes the elements from from, included, up to to, not included, so a usable range satisfies 0 <= from <= to <= size. A reversed range, or one that runs outside the array, is always a calculation that went wrong: a slice is never read backwards.

Not raised yet. In version 0.5.0 nothing raises OS4007. slice() takes whatever range it is given: a reversed range gives an empty array, and a range that runs past the end stops at the last element. A negative bound is the one case that does stop the bar, with OS4003. Clamp both bounds yourself, as the fix below shows, so the script does not depend on any of that.

How to fix it

Clamp the bounds: from = max(0, from) and to = min(size(arr), to), with from at or below to.

Before

tail = slice(values, size(values), 0)

After

tail = slice(values, max(0, size(values) - 10), size(values))

Drawing objects and tables

OS4005 The drawing object no longer exists

Errorruntime

This kind was deleted on bar bar and cannot be changed.

What it means

A drawing object (a line, label, box or polyline) lives from the bar that creates it until the bar that deletes it with draw.delete(). Deleting it does not clear the name that refers to it, so a var that held the object still holds it afterwards, now pointing at nothing. The next setter that reaches it, such as draw.setTo() on the same bar or a later one, stops the run, and the message says which bar the object was deleted on.

Forget the object in the same place you delete it: assign none to the name straight after draw.delete(), and test it with isNone() before changing it. If the object sits in an array, remove its element too. A table is never deleted, so a table never raises this. See Lines and boxes.

How to fix it

Assign none to the name on the same path as the delete, and test isNone() on it before changing the object.

Before

var top = none
if isNone(top)
    top = draw.line(time, low, time, high)
if close < open
    draw.delete(top)
draw.setTo(top, time, high)

After

var top = none
if isNone(top)
    top = draw.line(time, low, time, high)
if close < open
    draw.delete(top)
    top = none
if not isNone(top)
    draw.setTo(top, time, high)

OS4008 Table cell is outside the table

Errorruntime

Cell (row, column) is outside a table of rows rows and columns columns.

What it means

A table's rows and columns are fixed when the script declares it with table(), because the grid is part of the study's layout on the chart. A cell() written outside that grid has nowhere to go. Rows and columns are numbered from 0, so a table declared with 2 rows has rows 0 and 1, and writing to row 2 is the classic off-by-one.

Not raised yet. In version 0.5.0 nothing raises OS4008: a cell outside the grid stops the run with the broader OS4004, whose message gives the row and column pair and the number of cells. Declare the table with the shape you write. See Tables.

How to fix it

Declare the table with the shape the script writes: table("Summary", rows, columns).

Before

t = table("Summary", 2, 2)
cell(t, 2, 0, "Total")

After

t = table("Summary", 3, 2)
cell(t, 2, 0, "Total")

Colours, dates and strings

OS4009 Colour channel is out of range

Errorruntime

name's argument is found; channels run 0 to 255 and alpha runs 0 to 1.

What it means

The red, green and blue channels of rgb() and rgba() run from 0 to 255, and the alpha (opacity) of rgba() and withAlpha() runs from 0 to 1. A red, green or blue value written as a literal outside 0 to 255 is refused when the script compiles (OS3004). This code is for a channel computed from data, such as a heat colour scaled by a strength that can run past 1, which is a bug in the calculation.

Not raised yet. In version 0.5.0 nothing raises OS4009: the colour is built with the channel clamped to its range, so a red channel computed as 300 is drawn as 255 and an alpha computed as 2 is drawn fully opaque, and the bar carries on. Clamp the value yourself where you compute it, with clamp(), so the decision is visible in the script. See Colors.

How to fix it

Clamp the value where it is computed: rgb(min(255, max(0, r)), g, b).

Before

tint = rgb(255 * strength, 0, 0)

After

tint = rgb(min(255, max(0, 255 * strength)), 0, 0)

OS4010 Calendar field is out of range

Errorruntime

field is found; it runs range.

What it means

date.from() builds a timestamp from a year, a month, a day and optional time fields, and each field has a range: a month runs from 1 to 12, a day from 1 to the length of the month, an hour from 0 to 23. A month of 13 is a script bug, usually month + 1 in December. In a session test, a date that silently moves is a whole day of wrong signals.

Not raised yet. In version 0.5.0 nothing raises OS4010: a field past its range rolls over into the next one, so date.from(2026, 13, 1) is 1 January 2027 and date.from(2026, 2, 30) is 2 March 2026. Carry the overflow yourself with mod() and floor(), as the fix below does, so the script says what it means. See Sessions and time.

How to fix it

Pass a value inside range, carrying the overflow into the field above it as the example does.

Before

t = date.from(2026, month + 1, 1)

After

t = date.from(2026 + floor(month / 12), mod(month, 12) + 1, 1)

OS4011 String position is outside the string

Errorruntime

Position index is outside a string of length characters.

What it means

str.substring() counts characters from 0, and the positions it is given have to address characters that exist. A position past the end usually comes from a parser that assumed a symbol was longer than it is, for example taking the eleventh character of a short NSE symbol such as SBIN.

Not raised yet. In version 0.5.0 nothing raises OS4011: a range that runs past the end gives the part that exists, which can be an empty string. Check str.length() before you take part of a string, as the fix below does.

How to fix it

Guard with str.length(s), or clamp the position with min() before the call.

Before

c = str.substring(sym, 10, 11)

After

c = str.length(sym) > 10 ? str.substring(sym, 10, 11) : ""

Loops

OS4013 A loop bound is absent

Errorruntime

This loop's bound is absent on this bar.

What it means

A for loop needs its start, its limit and its step before it can begin. Absent values flow through arithmetic and comparisons, but a loop cannot run a partial number of times: it either runs or it does not. So when a bound comes out absent, typically during warmup because it was computed from an indicator that has no value yet, the bar stops, rather than skipping the loop and drawing a plot that looks computed.

Decide what warmup means for the loop. Guard it with isNone() so the early bars skip it on purpose, as the sinceCross loop in the example at the top of this page does, or give the bound a fallback with orElse() where a fallback is genuinely right. See Control flow and Absent values.

Related. Reading an error, OS5xxx Limits, Debugging, Bars and history, Absent values, Warmup

How to fix it

Give the bound a value with orElse(), or guard the loop with isNone() so a warmup bar skips it deliberately.

Before

for i = 0 to lookback
    total += close[i]

After

if not isNone(lookback)
    for i = 0 to lookback
        total += close[i]