# OpenScript 0.5.0 complete reference

OpenScript (also called OpenAlgo Script) is an open trading language for studies and strategies. This file holds the whole documentation at https://openalgo.in/script as plain markdown.

# Getting started

## Introduction

Source: https://openalgo.in/script/getting-started/introduction

OpenScript is a language for writing what you want to see on a chart and what you want to trade when you see it. It is also called OpenAlgo Script, and it is the scripting language of the /trading page in OpenAlgo. This page covers what the language is, the two kinds of script you can write, where they run, why a script compiles to data instead of code, and the two open libraries that let any financial portal run the same scripts. Read it first: every other page assumes the ideas here.

New to OpenScript? Start with [Language basics](/script/getting-started/language-basics), the whole language on one page.


## A first look

A file written in OpenScript is a **script**. Here is a complete one, a **study**: a script that computes values and draws them, and never trades. It draws Supertrend, a trailing band built from the average true range (how far price typically moves in one bar). The band sits under price in green while the trend is up and above it in red while it is down, the side it protects is shaded, every flip is labelled, and an alert is raised when the direction changes. It works on any instrument: an NSE or BSE stock, a NIFTY future on NFO, or a crude oil contract on MCX.

```openscript
// Supertrend: a band that trails price by a multiple of the average true
// range, green under price in an uptrend and red above it in a downtrend.
version 1

study("Supertrend", overlay = true, precision = 2)

atrLen = input(10, "ATR length", min = 1, max = 200)
factor = input(3.0, "Factor", min = 0.01, max = 20)

st = supertrend(factor, atrLen)
band = st[0]
isLong = st[1] < 0

// Two plots, so the line can change colour when the trend flips.
middle = plot((open + close) / 2, "Body middle", fade(silver, 100))
upLine = plot(isLong ? band : none, "Up trend", lime, width = 2)
downLine = plot(isLong ? none : band, "Down trend", red, width = 2)
fill(upLine, middle, fade(lime, 90))
fill(downLine, middle, fade(red, 90))

turnedUp = isLong and not isLong[1]
turnedDown = not isLong and isLong[1]

if turnedUp
    signal("BUY", lime, at = "below", shape = "label")

if turnedDown
    signal("SELL", red, at = "above", shape = "label")

if turnedUp or turnedDown
    alert("Supertrend changed direction at " + text(close, 2), id = "supertrend-change")
```

Read it from the top:

- A line that starts with `//` is a comment, which the compiler ignores.
- `version 1` fixes the language version the file is written in, so it keeps meaning the same thing under every later release.
- `study(...)` names the script and, with `overlay = true`, puts it on the price pane.
- `input()` turns a value into a setting you can change from the chart without editing the file.
- `supertrend()` comes from the standard library, with no prefix and no import. It returns two values in an array: `st[0]` is the band and `st[1]` is the direction, which is negative while the trend is up.
- Two `plot()` calls draw the band, one while the trend is up and one while it is down, because a single plot keeps one colour. `none` leaves a plot empty on a bar. A fully transparent plot of the middle of each candle body gives `fill()` something to shade the band against.
- `isLong[1]` is the direction one bar earlier, so `turnedUp` and `turnedDown` are true only on the bar where the trend flips.
- `signal()` draws the BUY and SELL labels on those bars, and `alert()` raises an alert with the price. [Alerts from scripts](/script/alerts/overview) explains when a script's alert fires.

This is that study on a BHEL 15 minute chart, with the band trailing price, the protected side shaded and every flip labelled:


The same idea becomes a **strategy**, a script that also places orders, when you change the declaration and add them. Here is a shorter version that trades the flips:

```openscript
version 1

strategy("Supertrend, traded", overlay = true, qty = 1)

atrLen = input(10, "ATR length", min = 1, max = 200)
factor = input(3.0, "Factor", min = 0.01, max = 20)

st = supertrend(factor, atrLen)
band = st[0]
isLong = st[1] < 0

plot(isLong ? band : none, "Up trend", lime, width = 2)
plot(isLong ? none : band, "Down trend", red, width = 2)

if isLong and not isLong[1] and pos.isFlat
    buy()

if not isLong and isLong[1] and pos.isLong
    close()
```

`buy()` opens a long position and `close()` flattens it. `pos.isFlat` and `pos.isLong` read the position the strategy holds, so it buys only when it holds nothing and closes only when it is long. The band on the chart and the band the strategy trades on are written once, in the same lines of the same file. They cannot drift apart, because there is only one definition of them.

## One idea behind the whole language

A script runs **once per bar**, from the first line to the last, oldest bar first. There is no main function and no event handler: the file itself is the body of a loop the engine runs over every bar on the chart. `close` is the closing price of the bar being computed, `close[1]` is the one before it, and a value that cannot exist yet (a 20 bar average on bar 5) is absent rather than zero. The [execution model](/script/language/execution-model) page explains this in full, and it explains most of the language.

## Studies and strategies

A study and a strategy are the same language and the same file format. The only difference is the declaration on the first statement, and what that declaration allows.

| | Study | Strategy |
|---|---|---|
| Declared with | `study("Name", ...)` | `strategy("Name", ...)` |
| Computes and draws | Yes | Yes |
| Places orders with `buy()`, `sell()` and `close()` | No, that is [OS7001](/script/errors/orders#os7001) | Yes |
| Reads its own position with `pos.*` | No | Yes |
| On the /trading chart | Added from the Scripts panel or the Indicators dialog | The same, with its trades simulated in the browser |
| Backtest | Not applicable: a study has no trades | The Backtest panel, in the browser |
| Runs on the server and sends orders | No | Deployed from the Strategies panel |

Start with a study. When the chart shows what you expect, change the declaration and add orders. [Your first strategy](/script/getting-started/first-strategy) walks through exactly that.

## Where it runs: the /trading page

Everything happens in the /trading page of OpenAlgo. The right-hand toolbar holds eight panels: Watchlist, Option chain, Objects, Alerts, Scripts, Backtest, Strategies and Assistant. Three of them are for OpenScript.

| Panel | What you do there |
|---|---|
| Scripts | Write, check and save scripts, and apply one to the chart |
| Backtest | Run a saved strategy over the history of the instrument on the chart, in your browser |
| Strategies | Deploy a strategy on an instrument so the OpenAlgo server runs it and sends its orders |

A saved script also appears in the chart's Indicators dialog under **My scripts**, beside the built-in indicators, and every `input()` it declares becomes a field in its settings dialog.

The Strategies panel sends orders through OpenAlgo's own order path, so a deployed strategy trades the way every other part of OpenAlgo does: against the sandbox while OpenAlgo is in analyzer mode, and with your broker while it is in live mode. Test in sandbox trading (analyzer mode in OpenAlgo) first. The [Sandbox and live](/script/strategies/sandbox-and-live) page covers the details.

The [Quickstart](/script/getting-started/quickstart) takes you from an empty panel to a study on an NSE chart in about five minutes.

## Why it compiles to data, not code

The compiler does not turn your script into JavaScript or Python. It turns it into a **compiled program**: a flat list of instructions plus a few tables of constants, inputs and outputs, in a documented and versioned format. An **engine**, the part that executes compiled programs, runs a script by walking that list, bar by bar.

That one decision has practical consequences you benefit from even if you never think about it.

- **Nothing is ever executed as code.** No part of the system evaluates text. The compiler and engine run inside the /trading page under the strict content security policy OpenAlgo already sets, with no exception made for scripts.
- **A script cannot reach anything.** A script can only name what the instruction set offers. There is no way to write a network call, open a file or touch the page around it, so a study shared by a stranger can draw a wrong line but cannot place an order or read your account.
- **A runaway script stops.** The engine owns the loop, so it counts instructions, memory and time on every bar and stops a script that exceeds its budget. One failing script does not take anything else down.
- **The browser and the server agree.** When you save a script that compiles, OpenAlgo stores its compiled program beside the source, stamped with a fingerprint (a hash) of the exact text it came from. The chart and the Backtest panel compile that saved text in your browser; a strategy deployed from the Strategies panel runs the stored program on the server, in a separate process, with the Python engine. One compiler and one text give one program, and the fingerprint lets the server refuse a program that does not match its source.

The [Compiled program](/script/integrate/compiled-program) page describes the format for anyone who wants to read it.

## Two libraries for your own portal

OpenScript is an open project, not a feature locked inside one product. The language ships as two libraries that anyone can build into their own financial portal, trading terminal or research tool, commercial or not. Both are licensed under Apache 2.0, both have zero runtime dependencies, and both are at version 0.5.0.

| Library | Install | Language | What it gives you |
|---|---|---|---|
| `openalgo-script` | npm | JavaScript and TypeScript | The compiler and the engine; six headless editor functions, meaning functions with no screen of their own that an editor calls (highlight, complete, diagnose, hover, signature and format); an adapter that draws a compiled study on openalgo-charts; the backtest |
| `openscript` | PyPI | Python 3.12 or newer | An engine that runs compiled programs on a server, with no compiler in it |

```bash
npm install openalgo-script
pip install openscript
```

The /trading page itself is built this way: the Scripts panel, the chart and the Backtest panel use `openalgo-script` in the browser, and the Strategies panel runs deployed strategies with `openscript` on the server. A platform with its own chart or its own editor replaces the adapter for that piece and keeps the rest. The [Two libraries](/script/integrate/overview) page explains the pieces and how to adopt them one at a time.

## What version 0.5.0 does, and what is planned

The language is young, and these pages say plainly what works today.

- **Works now.** Studies with plots, fills, levels, markers, bar colours, backgrounds, drawing objects, tables and alerts. Reading another timeframe or another instrument on the chart. Strategies with market, limit and stop orders, backtested in the browser with commission and slippage, simulated on the chart, and deployed from the Strategies panel.
- **Planned.** Some names in the library are declared but not implemented yet, for example the risk-based sizing helpers such as `order.qtyForRisk()` and the account figures such as `pos.equity`. The compiler refuses a planned name where you wrote it, with [OS2020](/script/errors/names-and-types#os2020) and a message that says it is planned, rather than letting the script fail later. The reference marks every planned entry.
- **Not modelled yet.** A stop and target attached with `exit()` are not filled by the 0.5.0 backtest, and a strategy that calls `exit()` is refused by the Strategies panel. Manage exits in the script with `close()` for now, as [Your first strategy](/script/getting-started/first-strategy) shows.

### What the /trading page does not supply yet

A script can ask for facts about the market that the /trading page does not pass to the engine in this release. Where a fact is missing, the value that reads it has no value on any bar, and a condition built on it is never true. The script still compiles, so it is worth knowing before you wonder why a study drew nothing.

| Fact or feature | On the chart | Backtest panel | Strategies panel |
|---|---|---|---|
| Session hours, read by `session.isFirstBar` and the other `session.*` values | No value | No value | Refused |
| Lot size, read by `chart.lotSize` | No value | Stated | Stated |
| Another timeframe, read with `req.timeframe()` | Works | No value, because the panel does not state the chart's interval | Refused |
| Another instrument, read with `req.symbol()` | Works | Refused with [OS6006](/script/errors/data#os6006) | Refused |
| Drawing objects and tables | Work | Run, but a backtest shows only trades | Refused |

The Strategies panel also refuses a strategy that reads the calendar (the `date.*` functions) or sizes its orders in anything but units, and every refusal names its reason. [Example scripts](/script/getting-started/example-scripts) shows what these limits mean for twelve complete scripts.

The [Release notes](/script/resources/release-notes) list every change and what is still to come.

## How these pages are organised

| Section | Read it for |
|---|---|
| [Getting started](/script/getting-started/quickstart) | A first study, a first strategy, the editor and twelve complete examples |
| [Language](/script/language/script-structure) | How a script is built and how it runs, bar by bar |
| [Data and time](/script/data/timeframes) | Timeframes, other instruments, the NSE session and repainting |
| [Visuals](/script/visuals/overview) | Everything a script can draw |
| [Inputs and settings](/script/inputs/inputs) | Making a script adjustable from the chart |
| [Alerts](/script/alerts/overview) | Raising alerts from a script and managing them in /trading |
| [Strategies](/script/strategies/overview) | Orders, exits, sizing, costs, backtesting and deployment |
| [Writing scripts](/script/writing/style-guide) | Style, debugging, limits and troubleshooting |
| [Reference](/script/reference/keywords) | Every keyword, operator and library name, generated from the compiler |
| [Errors](/script/errors/overview) | Every diagnostic code, with its cause and fix |
| [Integrate](/script/integrate/overview) | Building OpenScript into your own portal |

Every code block on these pages is checked with the real compiler before the documentation is published, so an example you copy is an example that compiles.

**Related.** [Quickstart](/script/getting-started/quickstart), [Your first strategy](/script/getting-started/first-strategy), [The editor](/script/getting-started/the-editor), [Example scripts](/script/getting-started/example-scripts), [Execution model](/script/language/execution-model), [Two libraries](/script/integrate/overview), [Glossary](/script/resources/glossary)


## Language basics

Source: https://openalgo.in/script/getting-started/language-basics

This page is the whole language in one pass. OpenScript, also called OpenAlgo Script, is small: a script is a list of statements that runs once per bar, and everything below follows from that. Each section states the rules exactly and briefly, then links to the page that covers the topic in full. Read it top to bottom once, then keep it open as a map while you write your first scripts.

## What a script is

A **script** is a text file written in OpenScript. It either draws on the chart (a **study**) or also places orders (a **strategy**). The engine that runs it executes the whole file once per bar, from the first line to the last, starting with the oldest bar on the chart and ending with the newest. There is no main function and no event handler: the file itself is the body of a loop over the bars.

Here is a complete script:

```openscript
version 1
study("EMA 20", overlay = true)

plot(ema(close, 20), "EMA 20", orange)
```

On a 5 minute NIFTY futures chart, the engine runs these lines on the first bar of the history, then on the next, and so on up to the newest bar. On each run, `close` is that bar's closing price, `ema()` updates its average and `plot()` writes one value to the line.

You write scripts in the Scripts panel of the /trading page. Choose New script, type, and press Ctrl+S. Every save compiles the script, and the console under the editor lists each problem with its line, its error code and the fix. Then apply the script to the chart.


The [Quickstart](/script/getting-started/quickstart) walks through those steps, and the [Execution model](/script/language/execution-model) explains the bar-by-bar run in full.

## Lexical elements

The rules for how text becomes code: lines, indentation, comments, names and literal values.

### Whitespace and newlines

A statement ends at the end of its line, and a line holds one statement. There is no statement separator: a `;` is error `OS1007`. Spaces between tokens do not change the meaning, and the canonical layout puts one around an operator and none inside brackets (`a + b`, `f(a, b)`, `close[1]`). Blank lines are ignored.

A file is UTF-8 text. Outside string literals, only ASCII letters, digits, spaces, newlines and the language's punctuation are allowed. A non-breaking space or a curly quotation mark pasted from a document is `OS1001`, reported at that character. Inside a string, any Unicode text is fine.

### Indentation forms blocks

A **block** is a group of lines that belong to a header line such as `if`, `else`, `for`, `while`, `switch`, `case`, `default` or a multi-line `fn`. The block is every following line indented more deeply than the header, and it ends at the first line indented the same as the header or less. There are no braces and no `end` keyword.

```openscript
upBar = false
if close > open
    upBar = true
    signal("UP")
background(upBar ? fade(lime, 92) : none)
```

The two indented lines belong to the `if`. The last line is back at the header's indentation, so it runs on every bar.

- Indent with spaces. A tab in the indentation is `OS1002`.
- Every line of one block carries exactly the same indentation. One space more or less is `OS1003`.
- Four spaces is the convention. Any amount deeper than the header is accepted.
- A header with no indented line after it is `OS1010`.
- Blank lines and lines that hold only a comment take no part in the rule, so they never open or close a block.

### Line continuation

A long statement continues onto the next line in three cases:

1. A `(` or `[` is still open.
2. The line ends with a binary operator, a comma, `?`, `:` or `=`.
3. The line ends with a backslash, `\`.

```openscript
trend = ema(close, 9) +             // ends with an operator
        ema(close, 21) +
        ema(close, 50)

spread = ema(close, 9) \
         - ema(close, 21)           // the backslash carried it here

plot(trend / 3, "Mean of three EMAs",
     color = aqua,                  // the bracket is still open
     width = 2)
plot(spread, "EMA 9 less EMA 21")
```

A continuation line must be indented more deeply than the line its statement began on, so it can never be read as a new statement. A continuation at the same indentation or less is `OS1028`.

### Comments

A comment starts at `//` and runs to the end of the line. A `//` inside a string is ordinary text. There are no block comments: `/* ... */` is `OS1026`, so comment out a region by putting `//` on each line.

```openscript
// A comment on its own line.
len = 14                // A comment after code.
plot(sma(close, len), "SMA 14")
```

### Identifiers

An **identifier** (a name) starts with an ASCII letter or an underscore and continues with ASCII letters, digits and underscores. Names are case sensitive, so `fastLen` and `fastlen` are two different names.

| Written | Result |
|---|---|
| `fastLength`, `_scratch`, `ema9` | Legal names |
| `2fast` | `OS1029`: a name cannot start with a digit |
| `längd` | `OS1001`: names are ASCII |
| `step`, `color`, `series` | `OS1019`: reserved words |
| `close`, `plot`, `level` | `OS2002`: library names, see [Variables](#variables) |

The convention, not enforced, is `camelCase` for names and functions, and `UPPER_SNAKE` for values you treat as constants.

### Reserved words

These 36 words are reserved. None of them can be a variable, a function or a parameter name, and using one as a name is `OS1019`.

| Group | Words |
|---|---|
| Declarations | `study`, `strategy` |
| Control flow | `if`, `else`, `for`, `to`, `step`, `in`, `while`, `break`, `continue`, `switch`, `case`, `default` |
| Functions | `fn`, `return` |
| Persistence | `var`, `live` |
| Logic | `and`, `or`, `not` |
| Literals | `true`, `false`, `none` |
| Type names | `number`, `string`, `bool`, `color`, `series`, `array` |
| Reserved for a later version | `as`, `import`, `is`, `map`, `matrix`, `type` |

A reserved word is still legal as a named argument label, because a label is matched against the called function's parameters and is never looked up as a name. `plot(x, "X", color = aqua)` is correct. [Keywords](/script/reference/keywords) describes every word.

### Literals

A **literal** is a value written directly in the source.

| Kind | Examples | Rules |
|---|---|---|
| Number | `42`, `3.14`, `.5`, `1_000_000`, `2.5e-4`, `0xFF` | One numeric type, a finite 64-bit floating point value. Underscores group digits and mean nothing. `0x` starts hexadecimal. There is no octal form, so `010` is ten. `-2` is the minus operator applied to `2` |
| String | `"BUY"`, `'He said "go"'` | Double and single quotes mean the same thing. Escapes: `\\`, `\"`, `\'`, `\n`, `\t`, `\r`, `\0` and `\uXXXX`. A string cannot span two lines |
| Boolean | `true`, `false` | Type `bool`. Not numbers: `0` is not false and `1` is not true |
| Colour | `aqua`, `#ff8800`, `#ff880080` | One of nineteen named colours, or hex `#rrggbb`, or hex `#rrggbbaa` whose last byte is the alpha (opacity): `ff` is solid, `00` invisible. Any other number of hex digits is `OS1027` |
| Absent | `none` | The value that means "there is no value here". See [Series and past values](#series-and-past-values) |

An array is a list of values of one type, written in square brackets: `[24000.0, 24500.0, 25000.0]`. Its elements are read with `[i]`, counting from 0, and [Collections](/script/language/collections) covers arrays. The named colours are `aqua`, `black`, `blue`, `brown`, `fuchsia`, `gray`, `green`, `lime`, `maroon`, `navy`, `olive`, `orange`, `pink`, `purple`, `red`, `silver`, `teal`, `white` and `yellow`.

[Script structure](/script/language/script-structure) has every layout rule, and [Types and values](/script/language/types-and-values) covers each type in full.

## Script structure

Every file has the same shape, in this order:

1. **The version line**, `version 1`: the first line that is not blank and not a comment. It fixes the language version, so the file keeps its meaning under every later release. It is optional, but without it the compiler warns with `OS8003`. Anything else above it is `OS1021`.
2. **One declaration**, `study("Title", ...)` or `strategy("Title", ...)`. It names the script and sets options such as `overlay = true`, which draws on the price pane instead of a pane of its own. Write it directly under the version line. A file with no declaration is `OS2007`, and a second one is `OS2008`.
3. **An optional `limits(...)` line**, directly under the declaration, for scripts that need a larger loop budget or a bounded history.
4. **Statements**, run in order, top to bottom, once per bar.

```openscript
version 1

study("Average with a setting", overlay = true, precision = 2)

len   = input(20, "Length", min = 2, max = 200)
basis = sma(close, len)

plot(basis, "SMA", orange)
```

`input()` makes `len` a setting you can change from the chart without editing the file. Because statements run in order, a name must be assigned above the line that reads it; reading it earlier is `OS2001`. Some calls declare the fixed shape of the study and must sit at the top level, never inside a block: `plot()`, `fill()`, `level()` and `table()` (`OS3006`), and `input()` (`OS3007`). To hide a plot on some bars, plot `none` there instead of wrapping it in an `if`.

[Script structure](/script/language/script-structure) covers each part, and [Declarations](/script/reference/declarations) lists every option of `study()` and `strategy()`.

## Price and bar values

A script reads the bar's prices by bare name, with no declaration. Each is a `series number`: one value per bar, and `[n]` reads an earlier bar.

| Name | Holds |
|---|---|
| `open` | The price of the first trade in the bar |
| `high` | The highest traded price in the bar |
| `low` | The lowest traded price in the bar |
| `close` | The closing price. On a bar still forming, the latest traded price |
| `volume` | The quantity traded in the bar. Absent, not zero, where the host (the application feeding the bars, such as the /trading page) supplies none |
| `oi` | Open interest: futures or options contracts outstanding at the end of the bar. Absent where none is supplied, as for a cash equity or an index |
| `hl2` | `(high + low) / 2`, the midpoint |
| `hlc3` | `(high + low + close) / 3`, the typical price |
| `ohlc4` | `(open + high + low + close) / 4` |
| `hlcc4` | `(high + low + close + close) / 4`, the close counted twice |
| `time` | The instant the bar opened, in milliseconds since 1 January 1970, UTC |
| `timeClose` | The instant the bar's interval ends. **Planned**: using it today is `OS2020`. Until it arrives, write `time + chart.intervalMinutes * 60000` on an intraday chart |

The `bar` namespace says where the script is in the run. These are the facts you will use most:

| Name | Holds |
|---|---|
| `bar.index` | The bar's position in the loaded data. The oldest bar is 0 |
| `bar.count` | Bars seen so far, `bar.index + 1` |
| `bar.isFirst` | `true` on the oldest bar only |
| `bar.isLast` | `true` on the newest bar only |
| `bar.isConfirmed` | `true` once the bar's interval has elapsed. Every historical bar is confirmed; the newest bar of a moving chart is not, until it closes |

```openscript
version 1
study("Body as a share of the range", precision = 1)

body     = close - open
barRange = high - low
share    = barRange > 0 ? body / barRange * 100 : none

plot(share, "Body, percent of range", share >= 0 ? lime : red, style = "histogram")
background(bar.isConfirmed ? none : fade(yellow, 90))
```

The background marks the newest bar while it is still forming. [Price and volume](/script/reference/price-and-volume) and [bar.*](/script/reference/bar) document every value.

## Operators

### Arithmetic

`+`, `-`, `*`, `/` and `%` work on numbers. `/` is always real division, so `7 / 2` is `3.5`. `%` is the remainder with the sign of the left operand, so `-7 % 3` is `-1`. Division by zero gives `none`, not an error. There is no power operator: write `pow()`, as `pow(x, 2)`.

`+` also joins two strings, and does nothing else. There is no implicit conversion anywhere, so `"Close " + 5` is `OS2003`. Convert with `text()`: `"Close " + text(close, 2)`.

### Comparison

`<`, `<=`, `>` and `>=` compare two numbers or two strings and give a `bool`. If either side is `none`, the result is `none`, not `false`.

`==` and `!=` compare two values of the same type, and any value against `none`. They always answer `true` or `false`, so `x == none` is a working test for absence.

A comparison cannot be chained. `a < b < c` is `OS1008`; write `a < b and b < c`.

### Logical

`and`, `or` and `not` take `bool` values. They use three-valued logic, where `none` means unknown, and `and` and `or` short-circuit: the right side runs only when it can still change the answer. `!`, `&&` and `||` do not exist, and each is `OS1001` with the word to use instead.

### The conditional operator

`cond ? a : b` gives `a` when `cond` is `true` and `b` otherwise, including when `cond` is `none`. Both arms have the same type, or one arm is `none`; arms of two different types are `OS2012`. Only the chosen arm runs.

```openscript
direction = close > open ? "up" : close < open ? "down" : "flat"
plot(direction == "up" ? 1 : 0, "Up bar")
```

### Assignment and compound assignment

`name = expression` declares a name or updates it. The compound forms are shorthand: `x += 1` is `x = x + 1`, and the same holds for `-=`, `*=`, `/=` and `%=`. Assignment is a statement, not an operator, so it has no place in the precedence table and `if x = 5` is `OS1006` (write `==` to compare). There is no `++` or `--`.

### The history operator

`src[n]` reads the value of `src` from `n` bars ago, so `close[1]` is the previous bar's close and `close[0]` is `close`. On an array, the same brackets read an element: `levels[0]` is the first element. The compiler knows which from the type. [Series and past values](#series-and-past-values) has the rules.

### Precedence

Precedence decides which operator takes its operands first. Operators on a higher row bind more tightly.

| Level | Operators | Associativity | Notes |
|---|---|---|---|
| 1 | `(expr)`, `f(args)`, `a[i]`, `a.b` | Left | Grouping, call, history or element, member |
| 2 | unary `-`, unary `+`, `not` | Right | |
| 3 | `*`, `/`, `%` | Left | |
| 4 | binary `+`, binary `-` | Left | `+` also joins strings |
| 5 | `<`, `<=`, `>`, `>=` | None | Cannot be chained |
| 6 | `==`, `!=` | None | Cannot be chained |
| 7 | `and` | Left | Short-circuits |
| 8 | `or` | Left | Short-circuits |
| 9 | `cond ? a : b` | Right | Only the chosen arm runs |

Left associative means `a - b - c` is `(a - b) - c`. Right associative means `not not x` is `not (not x)` and `p ? a : q ? b : c` is `p ? a : (q ? b : c)`.

Parentheses group first, whatever the operators inside them:

```openscript
a = 2
b = 3
c = 4
r1 = a + b * c          // 14: * binds before +
r2 = (a + b) * c        // 20: the parentheses group first
r3 = -a % b             // -2: unary minus first, then %
plot(r1 + r2 + r3, "Sum, which is 32")
```

The one trap: `not` binds more tightly than a comparison, so `not close > open` means `(not close) > open`, which is an error because `close` is a number. Write the parentheses:

```openscript
downBar = not close > open
```

```openscript
downBar = not (close > open)
plot(downBar ? 1 : 0, "Down bar")
```

[Operators](/script/language/operators) explains each operator with examples, and the [operator reference](/script/reference/operators) gives every operand type and result.

## Series and past values

A **series** is the per-bar history of a value. Because the file runs on every bar, any name you assign at the top level holds one value per bar, just as `close` does: `body = close - open` is this bar's body on every bar.

`[n]` reads a series `n` bars back. It works on the built-in series, on any name assigned at the top level of the file, on a call that returns a series, and on a series parameter of your own function. Anything else, such as a name first assigned inside a block, is `OS2004`: assign the value to a top-level name first.

```openscript
version 1
study("Bar to bar change", precision = 2)

barMove  = close - close[1]         // none on bar 0: no bar before it
prevMove = barMove[1]               // a top-level name has history too

plot(barMove, "Change", barMove >= 0 ? lime : red, style = "histogram")
plot(prevMove, "Previous change", gray)
```

On the first bars there is nothing to read: `close[1]` on bar 0 is `none`, and so is `close[n]` on any bar where `n` is greater than `bar.index`. The value is never clamped to the oldest bar and never zero. **Absence** follows a few fixed rules. Arithmetic and `<`, `<=`, `>`, `>=` with a `none` operand give `none`; `==` and `!=` always answer `true` or `false`; a condition that is `none` takes the false branch of an `if`, a `while` or `? :`; and a plot of `none` draws a gap. A library function that needs `k` bars returns `none` until it has them, which is all that **warmup** is: `ema(close, 20)` starts on bar 19. To replace an absent value with a fallback, use `orElse()`, and to test for one, `isNone()`.

[Bars and history](/script/language/bars-and-history), [Absent values](/script/language/absent-values) and [Warmup](/script/language/warmup) cover each part.

## Blocks and control flow

Control flow runs inside one bar: a loop repeats within the bar being computed, and the next bar starts from the top of the file again.

### if and else

The condition must be a `bool`, or `none`, which takes the false branch. A number or a string is `OS2011`: `0` is not false and `""` is not false, so write the comparison you mean. `else if` is two words on one line and does not add indentation.

```openscript
barTint = gray
if close > open
    barTint = lime
else if close < open
    barTint = red
barColor(barTint)
```

### switch

`switch` is a statement that runs one arm. The value form compares a subject with each `case`; the condition form, with no subject, takes the first `case` whose condition is true. Arms do not fall through, `default` is optional and comes last, and a `case` may list several values separated by commas. Declare any name the arms set before the `switch`.

```openscript
style = input("intraday", "Trading style", options = ["scalp", "intraday", "swing", "positional"])

len = 21
switch style
    case "scalp"
        len = 9
    case "swing", "positional"
        len = 50

r = rsi(close, 14)
zone = 0
switch
    case r > 70
        zone = 1
    case r < 30
        zone = -1

plot(ema(close, len), "EMA")
plot(zone, "RSI zone")
```

### for

`for i = start to end` counts with both ends included, so `for i = 0 to 9` runs ten times. Add `step n` to change the increment; a descending loop must say `step -1`, and a range that runs the wrong way for its step does not run at all. `for item in array` visits each element. The loop variable belongs to the loop and cannot be assigned in the body (`OS2006`).

```openscript
total = 0.0
for i = 0 to 9
    total += close[i]

levels = [24000.0, 24500.0, 25000.0]
below = 0
for lvl in levels
    if close > lvl
        below += 1

plot(total / 10, "Mean of the last 10 closes")
plot(below, "Round numbers below the close")
```

### while

`while` repeats its block while the condition holds, checking it before each pass. A condition that becomes `none` ends the loop.

```openscript
back = 0
while back < 50 and close[back + 1] < close
    back += 1
plot(back, "Lower closes in a row before this bar")
```

### break and continue

`break` leaves the innermost `for` or `while`. `continue` skips to its next pass. Either one outside a loop is `OS1009`.

```openscript
lastUp = -1
for i = 0 to 20
    if isNone(close[i])
        continue
    if close[i] > open[i]
        lastUp = i
        break
plot(lastUp, "Bars back to the last up bar")
```

Every loop iteration on a bar counts against a budget of 2,000,000 per bar; running past it is `OS5001`, and `limits(loops = ...)` raises it. [Control flow](/script/language/control-flow) covers every form and the budget.

## Built-in functions

The library's everyday functions are bare names: `ema(close, 20)`, `rsi(close, 14)`, `crossUp(fast, slow)`. The long tail sits behind a namespace and a dot, such as `date.hour(time)`, `str.length(s)` or `chart.symbol`. The namespaces are `bar`, `chart`, `session`, `date`, `str`, `math`, `pos`, `order`, `leg`, `book`, `draw` and `req`.

- **Named arguments.** An argument can be given by its parameter's name, as `width = 2`. Positional arguments come first, then named ones. A positional argument after a named one is `OS3005`, and a name the function does not have is `OS3002`, whose message lists the names that exist.
- **Defaults.** A parameter with a default can be left out. `rsi(close)` uses its default length of 14. Every reference entry shows the signature with its defaults, as `rsi()` does.
- **Nesting.** A call can be an argument of another call: `ema(rsi(close, 14), 5)` smooths the RSI.

```openscript
version 1
study("RSI, smoothed", precision = 2, range = [0, 100])

r      = rsi(close)                 // len left out: the default, 14
longR  = rsi(close, len = 21)       // a named argument
smooth = ema(rsi(close, 14), 5)     // one call inside another

plot(r, "RSI 14", purple)
plot(longR, "RSI 21", color = gray, width = 1)
plot(smooth, "Smoothed RSI 14", orange, width = 2)
level(70, "Overbought", red)
level(30, "Oversold", lime)
```

**Several outputs.** An indicator with more than one line returns an `array<number>` holding this bar's values in a fixed order, and you read each by its position. `macd()` returns the MACD line, the signal and the histogram:

```openscript
version 1
study("MACD", precision = 2)

m = macd(close)                     // [macd line, signal, histogram]

plot(m[0], "MACD", aqua)
plot(m[1], "Signal", orange)
plot(m[2], "Histogram", gray, style = "histogram")
```

Here `m[1]` picks an element, not a past bar. To read an element's history, name it first: `sig = m[1]`, then `sig[1]`.

A call that keeps state from bar to bar, such as `ema()`, belongs at the top level where it runs on every bar. Inside an `if` it advances only on the bars where the branch runs, and the compiler warns with `OS8001`. Compute it first, then use the result in the branch.

[Technical analysis](/script/reference/technical-analysis) lists the multi-output functions, and the Reference section documents every function.

## Variables

### Assignment and reassignment

The first assignment to a name declares it; a later assignment updates it. The first value with a definite type fixes the name's type, so assigning a value of another type later is `OS2003`. A name first set to `none` takes its type from the first definite value assigned to it.

A plain name is **recomputed on every bar**. `upNow = close > open` starts fresh on each bar and remembers nothing from the last one, except through `upNow[1]`.

### var

`var name = initial` sets the value once, on the first bar the line runs, and then **keeps** whatever the name holds from bar to bar. The initial value is required: `var total` alone is `OS1011`, and `var total = none` is the empty start.

```openscript
version 1
study("Up bars so far", precision = 0)

var upBars = 0                  // set once, on the first bar
if close > open
    upBars += 1                 // the var above, updated
plot(upBars, "Up bars so far", lime)
```

On the newest bar of a moving chart, the engine runs the bar again on each update and first restores every `var` to what it held at the end of the previous bar. A running count therefore counts bars, not updates, and matches a backtest over the same bars.

### Block scope

A name first assigned inside a block belongs to that block and cannot be read after it:

```openscript
if close > open
    body = close - open
plot(body, "Body")
```

Assign the name above the block, and the block updates it instead:

```openscript
body = 0.0
if close > open
    body = close - open
plot(body, "Body of up bars")
```

### No shadowing

A name exists once. Declaring a name in an inner scope when the same name exists outside it is `OS2002`, with the line of the outer declaration in the message. Inside a function body, assigning a name that the file already declares is a second declaration of it, so it is `OS2002` too.

```openscript
threshold = 70
if close > open
    var threshold = 80
```

The library's names, its series, functions and colours, count as outside names. So a library name such as `level`, `plot`, `close`, `count` or `signal` cannot be reused as a variable name, and trying is `OS2002`:

```openscript
level = 70
```

Pick a name the library does not use, such as `upperLevel` or `upCount`. [Variables and scope](/script/language/variables-and-scope) and [Persistence](/script/language/persistence) cover the rules in full.

## User functions

`fn` declares a function of your own. The one-line form writes the result after `=>`. The block form ends with an expression, which is the result; `return` leaves early. Parameters may have defaults and optional type annotations.

```openscript
version 1
study("Z-score of the close", precision = 2)

fn barChange(src) => src - src[1]

fn zscore(src, len = 20) =>
    m = sma(src, len)
    s = stdev(src, len)
    (src - m) / s

z = zscore(close)
plot(z, "Z-score", aqua)
plot(barChange(z), "Change in z-score", gray)
level(0, "Mean", gray)
```

A function is declared at the top level and can be called above its declaration. It cannot call itself (`OS2005`), because state such as a `var` or an `ema()` inside it is kept separately for each place it is called. [User functions](/script/language/functions) covers parameters, return values and per-call state.

## Output

Five calls cover most of what a study shows. The first three declare fixed parts of the study and sit at the top level. The last two can go anywhere, typically inside an `if`.

| Call | What it does | Where |
|---|---|---|
| `plot()` | Draws one value per bar as a line, step, area, histogram or column. The title is required | Top level only |
| `fill()` | Shades the region between two plots. It takes the handles `plot()` returned, not values | Top level only |
| `level()` | Draws a horizontal line across the pane at the price from the last bar | Top level only |
| `signal()` | Puts a named marker on this bar | Anywhere |
| `alert()` | Raises an alert with a message built on this bar | Anywhere |

A signal and an alert on a bar that is still forming wait for the bar to close, so they fire only on the final values.

```openscript
version 1
study("Band breakout", overlay = true, precision = 2)

bb    = bollinger(close, 20, 2)     // [basis, upper, lower]
basis = bb[0]
upper = bb[1]
lower = bb[2]

pUpper = plot(upper, "Upper band", aqua)
pLower = plot(lower, "Lower band", aqua)
plot(basis, "Basis", orange)
fill(pUpper, pLower, fade(aqua, 90))

level(highest(high, 50), "50 bar high", gray)

if crossUp(close, upper)
    signal("BREAKOUT", color = lime, at = "below", shape = "arrowUp")
    alert(chart.symbol + " closed above the upper band at " + text(close, 2), id = "band-breakout")
```

On a 15 minute chart of an NSE stock, this draws the band, shades it, marks the highest high of the last 50 bars, puts an arrow on each bar that closes above the band, and raises an alert for it ([Alerts from scripts](/script/alerts/overview) explains when a script's alert fires on the /trading chart). [Visuals](/script/visuals/overview) covers everything a script can draw, including bar colours, backgrounds, drawing objects and tables, and [Alerts from scripts](/script/alerts/overview) covers alerts.

## Study or strategy

A study and a strategy are the same language; the only difference is the declaration and what it allows. A `study()` computes and draws. A `strategy()` accepts every study option, adds trading options such as `capital` and `qty`, and may place orders with `buy()`, `sell()` and `close()` and read its own position with `pos.*`; an order call in a study is `OS7001`. Because one file holds both the plotted and the traded numbers, they cannot disagree. Test a strategy in the Backtest panel, and then in sandbox trading (analyzer mode in OpenAlgo), before trading it with real money.

```openscript
version 1
strategy("EMA cross", overlay = true, qty = 1)

fast = ema(close, 9)
slow = ema(close, 21)
plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)

if crossUp(fast, slow) and pos.isFlat
    buy()
if crossDown(fast, slow) and pos.isLong
    close()
```

Written as a call, `close()` is the order that flattens the position; read bare, `close` is the price. [Your first strategy](/script/getting-started/first-strategy) builds one step by step.

## Where to next

- [Quickstart](/script/getting-started/quickstart): write, save and apply a first study in the /trading page.
- [Your first strategy](/script/getting-started/first-strategy): turn a study into a strategy and backtest it.
- [Example scripts](/script/getting-started/example-scripts): twelve complete scripts, explained.
- [Execution model](/script/language/execution-model): the bar-by-bar run in depth.
- [Absent values](/script/language/absent-values): every rule for `none`, and the mistakes it prevents.
- [Inputs](/script/inputs/inputs): make a script adjustable from the chart.
- [Reference](/script/reference/keywords): every keyword, operator and library name.
- [Errors](/script/errors/overview): every error code, with its cause and fix.
- [Glossary](/script/resources/glossary): the terms used across these pages.


## Quickstart

Source: https://openalgo.in/script/getting-started/quickstart

This page takes you from an empty Scripts panel to your own study drawn on an NSE chart, then shows you how to change its settings, find it again and read an error. It takes about five minutes and assumes nothing beyond an OpenAlgo login. Every step names the real button you press.


## Before you start

You need three things:

- **OpenAlgo running, and you logged in.** Open the /trading page.
- **An API key.** The chart reads market data with your OpenAlgo API key. If the page says "No API key found for charting.", follow its **Generate an API key** link, create one, and come back.
- **A chart with some history.** Click the symbol button at the left of the chart's toolbar (it reads **Search symbol** until a symbol is loaded), search for an NSE stock such as RELIANCE, and pick **15m** from the timeframe button beside it. Any instrument works. On a 15 minute chart, /trading loads about two months of history, which is plenty for the averages on this page.


## 1. Open the Scripts panel

The right-hand edge of /trading is a toolbar of panels: Watchlist, Option chain, Objects, Alerts, Scripts, Backtest, Strategies and Assistant. Click **Scripts** (hover a button to see its name). The panel opens beside the chart. Click the same button again, or press Esc when the cursor is not in the editor, to close it.

The first time, the panel says "Nothing written yet." and offers a **New script** button. Later, it reopens the script you last opened, and the script's name at the top of the panel is a menu of every script you have saved.


## 2. Create a study

Click **New script**. A small form opens at the top of the panel:

1. **Name.** Type `ema-cross`. A name can hold letters, digits, dots, dashes and underscores, starts with a letter or a digit, and is at most 64 characters long. It becomes the file name `ema-cross.oscript`.
2. **Kind.** Leave **Study** selected. The hint under it reads "Computes and draws on the chart."
3. Click **Create**, or press Enter.

The editor opens with a starter study already in it, titled from the name you typed:

```openscript
version 1
study("Ema cross", overlay = true)

length = input(20, "Length")
average = sma(close, length)

plot(average, "Average", aqua)
```

That is a complete, working study: a 20 bar simple moving average drawn on the price. You could apply it now. Instead, make it a little more useful.

## 3. Write the study

Select everything in the editor and replace it with this:

```openscript
version 1

study("EMA cross", overlay = true, precision = 2)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)
src     = input(close, "Source")

fast = ema(src, fastLen)
slow = ema(src, slowLen)

fastPlot = plot(fast, "Fast", aqua, width = 2)
slowPlot = plot(slow, "Slow", orange, width = 2)
fill(fastPlot, slowPlot, fade(aqua, 90))

if crossUp(fast, slow)
    signal("BUY")

if crossDown(fast, slow)
    signal("SELL")
```

What each part does:

| Line | Does |
|---|---|
| `version 1` | Fixes the language version, so the file means the same thing under every later release |
| `study(..., overlay = true, precision = 2)` | Names the study, draws it on the price pane and shows two decimals in its legend |
| `input()` | Creates a setting with a default, a label and, for a number, limits. `input(close, "Source")` offers a menu of price series. You change any of them later without editing the file |
| `ema()` | Exponential moving average of the source over the given number of bars |
| `plot()` | Draws one line. The title (`"Fast"`) is what the legend and the settings dialog call it |
| `fill()` | Shades between the two plotted lines. `fade()` makes aqua 90 percent transparent |
| `crossUp()`, `crossDown()` | True on the bar where the fast average crosses above, or below, the slow one |
| `signal()` | Puts a labelled marker on that bar |

Indent the two `signal` lines with four spaces. OpenScript uses indentation to mark a block, and a tab in the indentation is an error ([OS1002](/script/errors/syntax#os1002)). The editor's Tab key does not insert spaces, so type the spaces yourself.

## 4. Save it

Click **Save**, or press Ctrl+S (Cmd+S on a keyboard with a Command key). Saving compiles the script, which means the compiler reads it and checks it for mistakes, then writes it to OpenAlgo. The picture shows the panel just after saving a longer study, the [HalfTrend](/script/getting-started/example-scripts#halftrend) from Example scripts, with **Ready** in the status bar:


Watch the status bar at the bottom of the panel. While you type it says **Unsaved changes**. After the save it says one of these:

| Status bar | Means |
|---|---|
| **Ready** | The script compiled with nothing to report. You can apply it |
| **Ready, with 1 warning** | It compiled and will run; the console has a note worth reading |
| **1 error, so it will not run yet** | It was saved, but it does not compile. Open the console to see why |

The editor colours the text with the language's own highlighter, and the right end of the status bar shows where your cursor is, as **Ln** and **Col**.

## 5. Put it on the chart

Click **Apply to chart**: the play button beside the script's name at the top of the panel. It is enabled once the script is saved and compiles; until then, hovering it says "Save a script that compiles, and it can be applied to the chart."

The study appears on the chart: two moving averages over the candles, the band between them shaded, and **BUY** and **SELL** labels on the bars where they cross. A legend row named **EMA cross**, followed by its settings (`9 21 close`), appears at the top left of the chart.

The picture shows a 20 and 50 bar variant of this study on a daily SBIN chart, with the band shaded green where the fast average leads and red where the slow one does, and the crossings labelled Golden cross and Death cross. Its code is [EMA cross in colour](/script/getting-started/example-scripts#ema-cross-in-colour) in Example scripts:


Both lines start a little way in from the left edge of the history. That is correct. A 21 bar average has no value until 21 bars exist, so OpenScript leaves the first bars empty rather than drawing a made-up number. The [Warmup](/script/language/warmup) page explains the exact rule.

If a message says the study "needs more history than the bars loaded, so it has nothing to draw yet", the chart holds fewer bars than the longest average needs. The chart shows that message for any study that has no value on any loaded bar. Scroll the chart back to load older bars, or pick another timeframe.

## 6. Change a setting

Click the gear button on the study's legend row. The settings dialog opens with two tabs:

- **Inputs** holds one field per `input()` in your script: **Fast length** and **Slow length** with the limits you gave them, and **Source** as a menu.
- **Style** holds one row per plot, named by its title (`Fast` and `Slow`), with a checkbox to show or hide it and a button for its colour, opacity, thickness and line style.


Change **Fast length** to 5 and click **Ok**. The chart recomputes at once; the script itself is unchanged. **Defaults** at the bottom left puts the fields back to the values the script declares.

## 7. Find it again

Your study now lives in OpenAlgo, not in this browser tab. To add it to any chart later:

1. Click **Indicators** in the chart's toolbar.
2. Under **Yours**, click **My scripts**.
3. Click **EMA cross**.


A script appears under **My scripts** as soon as it is saved and compiles, above the built-in indicators under **Library**. The study's legend row also carries a braces button, `{}`, beside the gear, which opens its source in the Scripts panel. From any chart you are one click from the code that drew it.

## 8. Read an error

Mistakes are part of writing a script, and every error comes with its cause and a fix. Try one on purpose. On line 9 of your study, change `fastLen` to `fastlen`, with a small l:

```openscript
fast = ema(src, fastlen)
```

Save. The status bar reads **1 error, so it will not run yet**, the number 9 turns red in the gutter, and the console button at the left of the status bar turns red and shows **2**, the number of messages waiting. Click it to open the console. Among the entries is this one:

```text
OS2001  line 9, column 17
fast = ema(src, fastlen)
                ^^^^^^^
fastlen is not defined at this point in the file.
Fix: Assign fastlen above this line, move this line below its assignment, or correct the spelling to fastLen.
```


The code, [OS2001](/script/errors/names-and-types#os2001), is stable, so you can look it up on the [errors pages](/script/errors/overview). The fix names the exact change: the input is called `fastLen`, with a capital L. The second message is a warning, [OS8018](/script/errors/warnings#os8018): the **Fast length** input is never read, which is true while the typo stands. A warning never stops a script from running. Correct the spelling, save, and the status bar returns to **Ready**.

## What you have now

- A file, `ema-cross.oscript`, saved in OpenAlgo and listed in the Scripts panel's menu.
- A study on the chart, with a settings dialog built from its inputs.
- The same study one click away in the Indicators dialog, on any chart and any instrument.

## Where to go next

- [Your first strategy](/script/getting-started/first-strategy) turns this study into a strategy and backtests it in the Backtest panel.
- [The editor](/script/getting-started/the-editor) covers every control in the Scripts panel.
- [Example scripts](/script/getting-started/example-scripts) has twelve complete scripts to read and adapt.
- [Execution model](/script/language/execution-model) explains why a script runs once per bar, which explains most of the language.

**Related.** [Introduction](/script/getting-started/introduction), [Inputs](/script/inputs/inputs), [Plots](/script/visuals/plots), [Labels and shapes](/script/visuals/labels-and-shapes), [Reading an error](/script/errors/overview), [Troubleshooting](/script/writing/troubleshooting)


## Your first strategy

Source: https://openalgo.in/script/getting-started/first-strategy

A strategy is a study that also places orders. This page takes the EMA cross study from the [Quickstart](/script/getting-started/quickstart) and turns it into a strategy one step at a time: first the orders, then realistic costs, then a stop and a target. Then it runs the strategy over history in the Backtest panel and explains every figure the panel reports. By the end you have a strategy you can deploy to sandbox trading (analyzer mode in OpenAlgo) from the Strategies panel.

## A strategy is a study with orders

There is no separate strategy language and no separate file type. You change the word `study` to `strategy` in the declaration, and the order functions become available in the same file that is already doing the plotting. Calling `buy()` in a file declared with `study()` is [OS7001](/script/errors/orders#os7001), and the first fix it offers is exactly that change.

This is the point of the design, not a convenience. The averages on the chart and the averages the strategy trades on are written once, in the same lines of the same file, so what you see and what you backtest are the same calculation and cannot drift apart.

## Step 1. Change the declaration and add orders

In the Scripts panel, create a new script called `ema-cross-traded` with **Kind** set to **Strategy**, and replace its starter with this. Compared with the study, the word `study` has become `strategy`, and the two `signal` markers at the end have become orders:

```openscript
version 1

strategy("EMA cross, traded", overlay = true, precision = 2)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)
src     = input(close, "Source")

fast = ema(src, fastLen)
slow = ema(src, slowLen)

fastPlot = plot(fast, "Fast", aqua, width = 2)
slowPlot = plot(slow, "Slow", orange, width = 2)
fill(fastPlot, slowPlot, fade(aqua, 90))

goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy()

if goFlat and pos.isLong
    close()
```

Four things are worth knowing about those last lines.

- **`buy()` with no arguments is a market order** for the strategy's default quantity, which is 1 unless the declaration says otherwise. A market order fills at the next price available. Give it `limit =` and it becomes a limit order, which fills only at that price or better; give it `stop =` and it becomes a stop order, which waits until the price reaches that level.
- **`close()` flattens the position**, closing all of it so the strategy holds nothing. The same word, read without brackets, is `close`, the bar's closing price. The compiler tells them apart by the brackets, so `ema(close, 9)` and `close()` mean different things in one file.
- **The position is read from fills, not intentions.** `pos.isFlat` and `pos.isLong` change only when an order has actually filled. The guards stop a second buy while already long, and a close while already flat.
- **Signals are computed at the top level.** `goLong` and `goFlat` are worked out on every bar and then tested inside the `if`. A stateful call such as `crossUp()` written inside a branch would only advance on the bars that branch runs, which the compiler warns about as [OS8001](/script/errors/warnings#os8001).

> **The strategy starter the panel creates is itself a working 9 and 21 bar EMA cross that trades with `buy` and `close`. You can apply it before you change a line, and come back to this step when you want the inputs and the shaded band.**

## Step 2. Apply it and see the trades

Save with **Save** or Ctrl+S, and wait for **Ready** in the status bar. Now press **Apply to chart**, the play button beside the script's name.

For a strategy, Apply does two things at once. It adds the strategy's plots to the chart with a legend row and a settings dialog, exactly as for a study. Then it switches the right-hand panel to **Backtest**, selects this strategy and runs it over the history of the instrument on the chart. Every fill is marked on the price with an arrow and a label that says what it did and its signed size: `Long` and `+1` where a long opened, `Exit long` and `-1` where it closed. The strategy in this picture also trades short, so it shows `Short` and `Exit short` marks as well.


Nothing is sent to your broker. The marks come from the Backtest panel's run, which simulates the orders in your browser, so every mark on the chart is a fill of a trade listed in the panel's report.

## Step 3. Say what trading costs

A backtest with no costs flatters every strategy, and most of all a strategy that takes many small profits. The declaration is where a strategy states its capital, its order size and its costs. Replace the `strategy(...)` line with the declaration below, and leave the rest of the file, including the `version 1` line, as it is:

```openscript
version 1

strategy("EMA cross, traded", overlay = true, precision = 2,
         capital = 500000, qty = input(1, "Quantity", min = 1), qtyType = "units",
         pyramiding = 1, fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)
```

A declaration can run over several lines: the open bracket carries it on until the matching close. `strategy()` accepts every option `study()` does, so `overlay` and `precision` mean what they meant before. The rest are trading options:

| Option | Default | Means |
|---|---|---|
| `capital` | `100000` | Starting equity for the backtest, in the instrument's currency |
| `qty` | `1` | Order size when an order names none. Written as an `input()`, it becomes a setting you can change |
| `qtyType` | `"units"` | What `qty` counts: `"units"`, `"lots"`, `"cash"` or `"equityPercent"` |
| `pyramiding` | `1` | How many entries in one direction are allowed before another is refused |
| `fillOn` | `"nextOpen"` | Where an order is filled: at the next bar's open, or `"close"` of the signal bar |
| `slippage` | `0` | How many ticks (the instrument's smallest price step) each market or stop fill is moved against you |
| `commission` | `0` | The charge, in the unit `commissionType` names |
| `commissionType` | `"perTrade"` | `"perTrade"` (per order), `"perUnit"` or `"percent"` |
| `product` | `"intraday"` | `"intraday"` for a position closed the same day, or `"overnight"` for one carried |

Slippage is the gap between the price you expected and the price you got. A limit order fills at its own price or not at all, so slippage is not charged on it.

Two of those defaults are deliberate and worth keeping.

- **Fill at the next bar's open.** A decision made from a bar's close cannot be filled at that same close in a real market: by the time the bar has closed, the price has gone. The next open is what actually happens to you, so it is what the backtest does unless you say otherwise.
- **Costs start at zero because OpenScript will not guess yours.** Set them. With `commission = 20` and `commissionType = "perTrade"`, each order is charged 20, so a round trip costs 40. On an NSE stock trading near 1,250, a one share position has to move more than 40 rupees just to break even, which is exactly the kind of fact a backtest exists to tell you.

For NSE and BSE equities, `"units"` means shares. For futures and options on NFO or MCX, where you trade in lots, keep `qtyType = "units"` and state the size in units (a multiple of the lot size): the Strategies panel sends only quantities stated in units.

## Step 4. A stop and a target

A stop limits what a trade can lose, and a target takes the profit. Here both are set from the average true range (`atr()`), a measure of how far price typically moves in one bar, gaps included, at the moment of entry, held in `var` variables so they stay fixed for the life of the trade, and checked on every bar:

```openscript
atrLen     = input(14,  "ATR length", min = 1, max = 200)
stopMult   = input(1.5, "Stop, in ATR", min = 0.2, max = 20)
targetMult = input(3.0, "Target, in ATR", min = 0.2, max = 40)

fast     = ema(close, 9)
slow     = ema(close, 21)
atrValue = atr(atrLen)

var entryStop   = none
var entryTarget = none

if crossUp(fast, slow) and pos.isFlat and not isNone(atrValue)
    entryStop   = close - stopMult * atrValue
    entryTarget = close + targetMult * atrValue
    buy()

stopHit   = pos.isLong and close < entryStop
targetHit = pos.isLong and close > entryTarget

if pos.isLong and (stopHit or targetHit)
    entryStop   = none
    entryTarget = none
    close()
```

How it works:

- `var entryStop = none` creates the variable once and keeps its value from bar to bar. A plain assignment would be recomputed on every bar, and the stop would drift with the market. See [Persistence](/script/language/persistence).
- `not isNone(atrValue)` skips the entry while the ATR is still warming up. On the first 13 bars `atr()` has no value, so a stop computed from it would be `none` too. A trade entered then would have no stop at all: `close < entryStop` is never true while `entryStop` is `none`. The guard makes sure every trade starts with both levels set.
- The exit tests the bar's close against the levels and calls `close()`, which fills at the next bar's open. On a gap, that open can be beyond the level, and the backtest reports the price you would really have got.

> **The language also has `exit()`, which attaches a stop and a target to a position in one call. In version 0.5.0 the backtest does not fill the levels `exit()` sets, and the Strategies panel refuses to start a strategy that calls `exit()` or `order.bracket()`, because OpenAlgo's order path has no single order that pairs a stop with a target yet. Until both are in place, manage exits in the script with `close()`, as this page does.**

## The finished strategy

Everything together, with the stop, target and entry price drawn on the chart while a trade is open:

```openscript
version 1

strategy("EMA cross, traded", overlay = true, precision = 2,
         capital = 500000, qty = input(1, "Quantity", min = 1), qtyType = "units",
         pyramiding = 1, fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

fastLen    = input(9,   "Fast length", min = 1, max = 500)
slowLen    = input(21,  "Slow length", min = 1, max = 500)
src        = input(close, "Source")
atrLen     = input(14,  "ATR length", min = 1, max = 200)
stopMult   = input(1.5, "Stop, in ATR", min = 0.2, max = 20)
targetMult = input(3.0, "Target, in ATR", min = 0.2, max = 40)

fast     = ema(src, fastLen)
slow     = ema(src, slowLen)
atrValue = atr(atrLen)

goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

// Held in var, so the levels stay where they were set on the entry bar
// instead of moving with each new bar's volatility.
var entryStop   = none
var entryTarget = none

if goLong and pos.isFlat and not isNone(atrValue)
    entryStop   = close - stopMult * atrValue
    entryTarget = close + targetMult * atrValue
    buy()

stopHit   = pos.isLong and close < entryStop
targetHit = pos.isLong and close > entryTarget

if pos.isLong and (goFlat or stopHit or targetHit)
    entryStop   = none
    entryTarget = none
    close()

fastPlot = plot(fast, "Fast", aqua, width = 2)
slowPlot = plot(slow, "Slow", orange, width = 2)
fill(fastPlot, slowPlot, fade(aqua, 90))

plot(pos.isLong ? entryStop : none, "Stop", red, style = "step")
plot(pos.isLong ? entryTarget : none, "Target", lime, style = "step")
plot(pos.isLong ? pos.avgPrice : none, "Entry", fade(silver, 40), style = "step")
```

The last three plots use `style = "step"`, which draws each level as a flat line that jumps when the value changes. They show a value only while `pos.isLong` is true and draw nothing otherwise, which is how a plot is hidden: give it `none`, never wrap it in an `if` ([OS3006](/script/errors/arguments#os3006)). Because the levels are held in `var`, the red and green lines are the levels the trade was actually opened with, not levels recomputed from today's volatility.

## Backtest it

Save, then open **Backtest** on the right-hand toolbar and choose the strategy.


The panel runs a saved strategy over the history of the instrument and timeframe on the chart. There is no symbol box on purpose: a backtest of something other than what you are looking at is the result most easily misread. The header shows what the run is of, for example `RELIANCE 15m`.

| Control | What it does |
|---|---|
| **Strategy** | Every saved script that declares itself a strategy. Studies are not listed: a study places no orders, so it has nothing to backtest |
| **From**, **To** | The date range. It starts at two months back on a minute timeframe, two years on an hourly or daily one, five years on weekly and ten on monthly, until you set dates yourself |
| **Run backtest** | Runs the strategy with the current dates and settings. It reads **Running** while it works |
| **Settings** | Opens by itself when the script declares inputs. One box per `input()`; a box left empty uses the script's own default |

A run starts on its own when you choose a strategy or when the chart's instrument or timeframe changes. A change to a date or a setting waits for **Run backtest**, so a half typed value never starts a run.

Under **Settings**, the panel also lists what the strategy **Declared by the script**: Capital, Order size, Pyramiding, Commission, Slippage and Fills on. These come from the `strategy()` line and are shown rather than offered: to change them, edit the script. The only exception is a size you made an input, as `qty = input(1, "Quantity", min = 1)` does above, which the panel marks **(yours)** and lets you set. Values typed here are for testing on this chart only; they never reach a strategy running from the Strategies panel.

A run covers up to 100,000 bars and happens in your browser, compiled from the same saved text as the program a deployed strategy runs. Nothing is sent to a broker. The run reads the instrument's tick size and lot size from OpenAlgo; if it cannot find them, it says so and uses a tick of 0.05 and a lot of 1, and every money figure rests on those.

## Reading the report

When the run finishes, the panel shows its figures, an equity curve, a line such as `12 fills marked on the chart. 1,050 bars, 40ms. Tick 0.05, lot 1.`, and a table of every trade.


| Figure | Is | Read it knowing |
|---|---|---|
| **Net profit** | Realised profit over the range, after the costs the script declared | It is gross of every cost you left at zero |
| **Return** | Net profit as a percentage of `capital` | It says nothing about how much of the time that capital was in use |
| **Trades** | Closed trades in the range | Under about thirty trades, the figures below are anecdotes |
| **Win rate** | The share of closed trades that made money after charges | Meaningless alone: a high win rate with one very large loser is a losing strategy |
| **Profit factor** | Gross profit divided by gross loss | Below 1 the strategy loses. A dash means it cannot be worked out, for example when no trade lost |
| **Expectancy** | The average net profit per closed trade | The number that scales with how often you trade |
| **Max drawdown** | The largest fall in equity from a peak, shown as a negative amount | Read this first. It decides whether you could have stayed with the strategy |
| **Max run-up** | The largest rise in equity from a low point | The best stretch the run had. Set it beside the drawdown, not in place of it |

Equity here is the capital plus the profit so far, with an open position valued at each bar's close.


The **Trades** table lists each trade's **Side**, **Entry**, **Exit** and **Net**. A trade still open at the last bar reads `open` in the Exit column; its charges are counted and its profit is not, because it has not been realised. When the run ends holding a position, a **Position now** box shows it, marked to the latest price OpenAlgo has for the instrument, with a reminder that nothing is held at your broker because of it.

Three ways a good looking report can mislead you, all of them under your control:

1. **Costs left at zero.** Set `commission` and `slippage` before you believe anything.
2. **A fill model that is too kind.** `fillOn = "close"` fills you at a price that had gone by the time you decided. Keep the default.
3. **Too few trades.** Widen the range or use a shorter timeframe until the trade count means something.

## From backtest to sandbox

A backtest says what would have happened. The next step is to watch the strategy trade on its own as new bars arrive, with no money at risk. Open **Strategies** on the right-hand toolbar and click **Deploy a strategy**. Choose the strategy, then the instrument, exchange, interval and product, fill in any settings, and click **Deploy**. The deployment appears as a row. The panel's header shows **Analyzer** or **Live**. While OpenAlgo is in analyzer mode, the row's start button reads **Start in sandbox**, and every order goes to the sandbox rather than to your broker. In live mode, as in the picture below, it reads **Start live**, and orders go to your broker. The mode is set elsewhere in OpenAlgo, not in this panel, so check the header before you press the button.


A deployed strategy runs as a process on the OpenAlgo server, so closing the browser stops nothing. The server runs the compiled program saved beside your script, which is why a strategy must be saved without errors before it can be started.

The server refuses to start a script that does any of these, and the refusal names the reason:

- calls `exit()` or `order.bracket()`;
- reads the session or the calendar, for example `session.isFirstBar` or the `date.*` functions;
- sizes its orders in anything but units;
- reads another timeframe or another instrument with `req.timeframe()` or `req.symbol()`;
- creates drawing objects or tables.

The strategy on this page does none of those. The [Sandbox and live](/script/strategies/sandbox-and-live) page covers deployment in full.

## When nothing trades

A strategy that compiles, runs and never trades raises no error. Work down this list:

1. **Is the entry condition ever true?** Plot it for a moment on its own axis, so the price scale is not squashed: `plot(goLong ? 1 : 0, "Entry", scale = "left")`, and look for the spikes.
2. **Is it only true during warmup?** The `not isNone(atrValue)` guard blocks entries until the ATR has a value.
3. **Did the first trade ever close?** If `pos.isFlat` never becomes true again, every later entry is blocked by the guard.
4. **Does the condition read something the panel does not supply?** In this release the Backtest panel does not tell the engine the chart's interval, its timezone or the session's hours. So `chart.interval`, `session.isFirstBar` and `session.isLastBar` have no value in a backtest, and neither has a day, week or month `req.timeframe()` read, or a `session.isIn()` or `date.*` call that names no zone. A condition built on them is never true. Name the zone, as in `session.isIn("0915-1530", "Asia/Kolkata")`, and filter on an intraday read such as `"1h"`, which the backtest folds from the chart's own bars.

Two more cases show a message instead of a report:

- **A range where the instrument did not trade**, over a holiday or before listing, returns no bars, and the panel says "No bars came back for that instrument over that range."
- **A range that is too long.** More than 100,000 bars is refused with a message that names the count. Shorten the range or use a longer timeframe.

Plotting an intermediate value, as in the first question, is the fastest way to answer the first three. See [Debugging](/script/writing/debugging).

**Related.** [Strategies overview](/script/strategies/overview), [Orders](/script/strategies/orders), [Exits and brackets](/script/strategies/exits-and-brackets), [Position and sizing](/script/strategies/position-and-sizing), [Costs and fills](/script/strategies/costs-and-fills), [Backtesting](/script/strategies/backtesting), [Reading a report](/script/strategies/reading-a-report), [Sandbox and live](/script/strategies/sandbox-and-live)


## The editor

Source: https://openalgo.in/script/getting-started/the-editor

The Scripts panel is where you write OpenScript in /trading. This page walks through every part of it: the header and its menus, creating a script, the editing area, the checks that run when you save, the console that reports them, where a saved script lives, and how a script reaches the chart. It ends with what the panel does not do yet, so you know which habits to bring with you.


## Opening the panel

Click **Scripts** on the toolbar at the right edge of /trading. The panel opens between the chart and the toolbar, and works on the chart pane you last clicked. Click **Scripts** again, or press Esc while the cursor is not in a text field, to close it and give the chart its full width back.

The panel is 480 pixels wide to begin with. Drag its inner edge to make it anywhere from 320 to 760 pixels wide; it is the one panel allowed to grow that far, because while you write, the code is the thing you are working on and the chart is the reference. The width is remembered.

When you open the panel, it reopens the script you last opened in this browser. A study's source can also be opened from the chart: see [From the chart to the source](#from-the-chart-to-the-source).

> **Save before you switch away. Opening another panel on the toolbar, or another script from the menu, replaces what is in the editor with the saved file, and unsaved changes are lost without a prompt.**

## The header


The header is one row, and it is the panel's whole navigation.

| Control | What it does |
|---|---|
| Script name | The open script's name, without the `.oscript` ending, or **No script**. Click it for a menu: **Recent** lists the three scripts you opened most recently in this browser, then every other saved script, then **New script** |
| Kind badge | **STUDY** or **STRATEGY**, read from the script's declaration |
| Apply to chart | The play button. Puts the saved script on the chart. See [Applying a script](#applying-a-script) |
| **Save** | Checks and saves the script. Enabled when there are unsaved changes |
| Script actions | The three dots. **New script**, and **Delete script** |

## Creating a script

Choose **New script** from either menu, or from the button in an empty panel. A form opens under the header:

- **Name.** Letters, digits, dots, dashes and underscores, starting with a letter or a digit, up to 64 characters. The panel adds the `.oscript` ending. **Create** stays disabled until the name is valid, and once you start typing the form says what is wrong with it.
- **Kind.** **Study** computes and draws on the chart. **Strategy** draws and also places orders. Choose before you write, because the two start from different code.
- **Create**, or Enter, makes the file. **Cancel**, or Esc, closes the form.

A new script is never blank. It opens with a working starter titled from the name you typed, so `range-breakout` becomes `"Range breakout"`. A study starts as a moving average with a length setting:

```openscript
version 1
study("Range breakout", overlay = true)

length = input(20, "Length")
average = sma(close, length)

plot(average, "Average", aqua)
```

A strategy starts as an EMA cross that buys and closes:

```openscript
version 1
strategy("Range breakout", overlay = true, qty = 1)

fast = ema(close, 9)
slow = ema(close, 21)

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)

if crossUp(fast, slow)
    buy(qty = 1)

if crossDown(fast, slow)
    close()
```

Both are saved the moment they are created, so the new script is in the menu straight away.

## Writing

Here the editor holds the [HalfTrend](/script/getting-started/example-scripts#halftrend) study from Example scripts:


The editing area is plain text with three aids.

**Highlighting from the language itself.** The colours come from the OpenScript lexer (the part of the compiler that splits text into words and symbols), not from a separate list of words kept by the panel. Keywords, library functions, library values such as `close` and `aqua`, numbers, strings, colour literals such as `#ff8800`, comments and punctuation each have their own shade. Because the colours come from the language's own tables, a name added to the language is coloured as soon as OpenAlgo ships that version. A script longer than 65,536 characters is shown without colours, to keep typing quick.

**A numbered gutter.** Every line has its number, so a message about line 13 is easy to find. When the script has an error, the number of the line the first error is on turns red.

**No wrapping.** A long line stays one line and the area scrolls sideways, so each line number always sits beside the line it names.

Three habits matter in this editor:

- **Indent with spaces.** A block is the lines indented under an `if`, a `for` or a function, and four spaces per level is the convention. A tab in the indentation is an error ([OS1002](/script/errors/syntax#os1002)). The Tab key moves the focus out of the editor instead of inserting spaces, so type the spaces.
- **Keep one statement per line.** There are no semicolons and no braces. A long call can carry on over several lines while its brackets are open. See [Script structure](/script/language/script-structure).
- **Paste freely.** Text pasted from a file whose lines end with a carriage return as well as a line feed is normalised as it arrives, so line and column numbers in messages always match what you see.

## Checking and the console

The panel checks a script with the real compiler **when you open it and every time you save**. It does not check while you type: make a change, save, and read the result.

The status bar at the bottom of the panel always says where the script stands:

| Status bar | Means |
|---|---|
| **Working** | A save, open or delete is in progress |
| **Unsaved changes** | The text differs from the saved file. Save to check it |
| **1 error, so it will not run yet** (or **2 errors**, and so on) | Saved, but it does not compile. It cannot be applied or backtested |
| **Ready, with 1 warning** | It compiles and will run. A warning is worth reading |
| **Ready** | It compiles with nothing to report |

The right end of the status bar shows the cursor position as **Ln** and **Col**, counted the same way as the messages.

The button at the left of the status bar opens the **console**, a drawer under the editor. The button shows how many messages there are and turns red when any of them is an error. The console is closed by default and says "Nothing to report." when a script is clean.


Each message in the console has four parts: its code and position, the line it is about with the exact characters underlined, what is wrong, and how to fix it. Take this study, with `length` misspelt on line 9:

```openscript
version 1
study("RSI", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)

level(70, "Overbought", fade(red, 50))
level(30, "Oversold", fade(lime, 50))

plot(rsi(close, lenght), "RSI", purple, width = 2)
```

Saving it puts this error in the console:

```text
OS2001  line 9, column 17
plot(rsi(close, lenght), "RSI", purple, width = 2)
                ^^^^^^
lenght is not defined at this point in the file.
Fix: Assign lenght above this line, move this line below its assignment, or correct the spelling to len.
```

It also lists a warning, [OS8018](/script/errors/warnings#os8018), because the **Length** input is never read while the typo stands. Messages are listed in line order, so the warning about line 4 comes first.

- **Errors** stop the script from running until they are fixed. Their codes run from OS1 to OS7, and the first digit says what kind of problem it is: 1 is syntax, 2 names and types, 3 arguments, 4 runtime, 5 limits, 6 data and 7 orders. Most of the ones you see when you save are OS1, OS2 and OS3; most OS4 to OS7 codes are raised while a script runs.
- **Warnings** start with OS8 and never stop anything. Each one describes a shape that is almost always a mistake, such as a value assigned and never read ([OS8010](/script/errors/warnings#os8010)) or a stateful call inside a branch ([OS8001](/script/errors/warnings#os8001)). A stateful call is one that remembers earlier bars, such as `ema()` or `crossUp()`.

Every code is documented, with its cause and a before and after example, on the [errors pages](/script/errors/overview). The codes you will meet most often while writing:

| Code | Says |
|---|---|
| [OS1002](/script/errors/syntax#os1002) | A line is indented with a tab |
| [OS1003](/script/errors/syntax#os1003) | One line of a block is indented differently from the others |
| [OS2001](/script/errors/names-and-types#os2001) | A name is used before it is assigned, or is misspelt |
| [OS2003](/script/errors/names-and-types#os2003) | Two types do not mix, such as a string plus a number. Use `text()` |
| [OS3006](/script/errors/arguments#os3006) | A call such as `plot` is inside an `if`. Keep it at the top level and pass `none` to hide it |
| [OS8003](/script/errors/warnings#os8003) | The file has no `version 1` line |

The console also shows problems that are not about the script, in red: a save the server refused, or "There is no chart open to add this study to." when Apply had nowhere to go.

## Saving

Click **Save**, or press Ctrl+S (Cmd+S on a keyboard with a Command key). A save does three things, in order:

1. **Checks the script**, and shows the result in the status bar and the console.
2. **Writes the source**, whatever the check said. A script with errors is still saved, so a half finished idea is never lost.
3. **Stores the compiled program** beside the source, but only when the script compiles. The stored program is what the Strategies panel runs on the server; the chart and the Backtest panel compile the saved source in your browser. A script saved with errors has no stored program and cannot be applied, backtested or deployed until it is fixed and saved again.

Scripts are stored on the OpenAlgo server, as one `.oscript` file each in the `strategies/openscript` folder, so they are the same from any browser you log in from. An installation that runs OpenAlgo in a container keeps that folder on a named volume, so your scripts survive a rebuild and an upgrade. A script can be up to 256 KB.

> **The panel keeps no revision history in this release. Each save replaces the file; the server keeps a copy of the previous save beside it, as a backup, and nothing older. If you want a history of every change, keep the `strategies/openscript` folder under version control, or copy a script before a change you are unsure of.**

## Applying a script

**Apply to chart**, the play button, is enabled when the open script is saved and compiles. Until then, hovering it says "Save a script that compiles, and it can be applied to the chart." What it does depends on the kind of script.

- **A study** is added to the chart pane you last clicked, as it was last saved, with a legend row and a settings dialog built from its inputs. Each press adds another copy, so to replace an older copy, remove it with the x on its legend row, or with **Remove** under **Active** in the Indicators dialog.
- **A strategy** is added to the chart the same way, and then the panel switches to **Backtest**, which runs the strategy over the chart's history and marks every fill on the price. See [Your first strategy](/script/getting-started/first-strategy).

If no chart is open, nothing is added and the console says so.

## From the chart to the source

A saved script that compiles is listed in the chart's **Indicators** dialog, under **Yours**, **My scripts**, above the built-in indicators under **Library**. The dialog reads your scripts each time it opens, so a script saved a moment ago is already there.


Every script you wrote carries a braces button, `{}`, on its legend row, beside the gear that opens its settings. Clicking it opens that script's source in the Scripts panel. If that script is already open in the panel, it is left as it is, unsaved edits included.

## Deleting a script

**Delete script** in the Script actions menu deletes the open script straight away, with no confirmation and no undo. It removes the source, its backup and its compiled program. A deployment of a deleted strategy can no longer start, so remove its deployments in the Strategies panel as well.

## What the editor does not do yet

The `openalgo-script` library ships six editor functions: highlighting, completion, diagnostics, hover, signature help and formatting. The /trading panel uses the highlighting, and shows the compiler's diagnostics when you open or save a script rather than as you type.

So, in this release, the panel does not:

- offer completion as you type, or show a function's signature while you fill in its arguments;
- show a card when you hover over a name;
- format the file for you.

The reference covers what those features would show you. Every entry in the [Reference](/script/reference/technical-analysis) section lists a function's signature, its arguments with their defaults and accepted values, and the first bar it has a value on, all read from the compiler. If you build OpenScript into your own portal, all six functions are available to you: see [Editor integration](/script/integrate/editor-integration).

## Keyboard

| Keys | Does |
|---|---|
| Ctrl+S, Cmd+S | Save the open script |
| Enter | In the new script form, create the script |
| Esc | In the new script form, cancel it. Elsewhere, when the cursor is not in a text field, close the panel |
| Tab | Moves the focus out of the editor. Type spaces to indent |

**Related.** [Quickstart](/script/getting-started/quickstart), [Your first strategy](/script/getting-started/first-strategy), [Script structure](/script/language/script-structure), [Reading an error](/script/errors/overview), [Debugging](/script/writing/debugging), [Troubleshooting](/script/writing/troubleshooting), [Editor integration](/script/integrate/editor-integration)


## Example scripts

Source: https://openalgo.in/script/getting-started/example-scripts

These twelve scripts are complete files you can paste into the Scripts panel, save and apply. Each one is small enough to read in a few minutes and makes one or two ideas of the language concrete: nine are studies and three are strategies, and they run from a first EMA cross to a two leg options premium strategy. For every script, this page says what it draws, which language features it shows and why they are written the way they are, and where it runs in /trading today. After them, [Showcase scripts](/script/getting-started/example-scripts#showcase-scripts) gives three studies written to look their best on a chart, each with the screenshot it drew.

The comments inside each script say why a line is written the way it is, not what it does. For what a function does, follow its link to the reference.

## The twelve at a glance

| # | Script | Kind | Shows |
|---|---|---|---|
| 1 | [EMA cross](#1-ema-cross) | Study | Inputs, two averages, a shaded band and a crossing marker |
| 2 | [Trailing volatility stop](#2-trailing-volatility-stop) | Study | Values carried between bars with `var`, `orElse()`, bar colouring |
| 3 | [Anchored VWAP](#3-anchored-vwap) | Study | A time input, running totals, absence as "not started yet" |
| 4 | [RSI divergence](#4-rsi-divergence) | Study | Pivots and their lag, lines and labels drawn between two past points |
| 5 | [Opening range](#5-opening-range) | Study | The session's first bar, state that resets each day, background shading |
| 6 | [Combined premium](#6-combined-premium) | Study | Reading two option contracts that are not on the chart, and an alert |
| 7 | [Higher timeframe bias](#7-higher-timeframe-bias) | Study | A daily reading on an intraday chart, stated so it cannot repaint |
| 8 | [Dashboard](#8-dashboard) | Study | A table fixed to a corner, a user function, `switch` |
| 9 | [Supply and demand zones](#9-supply-and-demand-zones) | Study | Boxes created, extended and deleted over hundreds of bars |
| 10 | [EMA cross, bracketed](#10-ema-cross-bracketed) | Strategy | Costs, a stop and a target fixed at entry, risk-based sizing |
| 11 | [Opening range breakout](#11-opening-range-breakout) | Strategy | One trade per session, the range as the stop, an exit on the clock |
| 12 | [Short premium, combined stop](#12-short-premium-combined-stop) | Strategy | Two legs managed as one position on their combined price |

## Where they run in /trading today

Every script here compiles with no error. A few use features the /trading page does not supply to the engine yet, and it is better to know that before you apply one than to wonder why it drew nothing.

| Script | On the chart | Backtest panel | Strategies panel |
|---|---|---|---|
| 1, 2, 4, 7, 8, 9 | Draws as described | Not a strategy | Not a strategy |
| 3 Anchored VWAP | Draws as described, with the anchor time read as UTC | Not a strategy | Not a strategy |
| 5 Opening range | Draws nothing yet: needs the session's opening time | Not a strategy | Not a strategy |
| 6 Combined premium | Draws the premium; two parts need facts the chart does not state yet | Not a strategy | Not a strategy |
| 10 EMA cross, bracketed | Draws and simulates its trades | Runs. Its `exit()` levels are not filled, so trades close on the opposite cross | Refused: it calls `exit()` |
| 11 Opening range breakout | Opens no trades: needs the session's opening time | Opens no trades, for the same reason | Refused: it reads the session, calls `exit()` and sizes in lots |
| 12 Short premium | Draws the premium, opens no trades: needs the session | Refused with OS6006: it reads another instrument | Refused: it reads the session, the calendar and another instrument, and sizes in lots |
| Showcase: HalfTrend, Bollinger Bands, EMA cross in colour | Draws as described | Not a strategy | Not a strategy |

The notes under each script explain the reason. [Your first strategy](/script/getting-started/first-strategy) builds a strategy that runs in all three places.

## 1. EMA cross

The script every reader meets first, and the one the [Quickstart](/script/getting-started/quickstart) builds. Two exponential moving averages (`ema()`), the band between them shaded with `fill()`, and a `signal()` marker on each bar where they cross. It works on any instrument and any timeframe.

```openscript
// Two exponential moving averages, the band between them, and a marker on the
// bar where they cross.
//
// The crossing is a signal() rather than a plotted value, because a crossing is
// an event on one bar, not a number on every bar.

version 1

study("EMA cross", overlay = true, precision = 2)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)
src     = input(close, "Source")

fast = ema(src, fastLen)
slow = ema(src, slowLen)

// Both crosses are computed at the top level, on every bar. Computed inside the
// branch that uses them, they would advance only on the bars that branch runs,
// which is warning OS8001 and a missed crossing.
up   = crossUp(fast, slow)
down = crossDown(fast, slow)

// fill() shades between two plots, and takes the plots themselves rather than
// the series they draw, so both plots are named.
fastPlot = plot(fast, "Fast", aqua, width = 2)
slowPlot = plot(slow, "Slow", orange, width = 2)

// The band inherits the warmup of the two lines it joins: neither average has a
// value before its window fills, so the fill simply starts where they do.
fill(fastPlot, slowPlot, fade(aqua, 90))

if up
    signal("BUY")

if down
    signal("SELL")
```

A version dressed for the chart, with 20 and 50 bar averages, the band coloured by which one leads and each crossing labelled, is [EMA cross in colour](/script/getting-started/example-scripts#ema-cross-in-colour) under Showcase scripts, with its screenshot.

What to notice:

- **The ordinary case is short.** No namespace on `ema`, no prefix on `aqua`, and one `signal("BUY")` for a marker.
- **Stateful calls live at the top level.** `crossUp()` and `crossDown()` compare this bar with the previous one, so they must see every bar. The script computes them first and tests the result inside the `if`.
- **A band belongs to two plots.** `plot()` returns a handle when you name it, and `fill()` takes two handles. Passing an expression instead of a plot is [OS3020](/script/errors/arguments#os3020).
- **`input(close, "Source")`** gives the settings dialog a menu of price series, so the same study can average the typical price (`hlc3`) without an edit.

## 2. Trailing volatility stop

A band that trails price at a multiple of the average true range (`atr()`), flips side when price closes through it, and recolours the candles by direction. It is built by hand to show two ideas at work: values carried from one bar to the next with `var`, and `orElse()` supplying a starting value where the past does not exist yet. The library's own `supertrend()` computes the same kind of band in one call.

```openscript
// A trailing volatility stop: the two raw bands it is built from, the one band
// in force, and the price candles recoloured by which side price is on.

version 1

study("Trailing volatility stop", overlay = true, precision = 2)

atrLen = input(10,  "ATR length", min = 1, max = 200)
mult   = input(3.0, "Band width, in ATR", min = 0.5, max = 20)
paint  = input(true, "Recolour the candles")

atrValue = atr(atrLen)

// The raw bands, recomputed from scratch every bar. The trailing bands are
// pulled towards them.
rawLower = hl2 - mult * atrValue
rawUpper = hl2 + mult * atrValue

var lowerBand = none
var upperBand = none
var dir = 1

// Read the two bands before they are reassigned below. At this point in the
// file a var still holds the previous bar's value, so no [1] read is needed.
prevLower = lowerBand
prevUpper = upperBand

// Until the ATR has warmed up there is no previous band, and max and min pass
// absence through like any other calculation. Trailing against an absent band
// would leave the band absent for the rest of the chart, because each bar
// trails the one before. So the previous band falls back to the raw band.
heldLower = orElse(prevLower, rawLower)
heldUpper = orElse(prevUpper, rawUpper)

lowerBand = close[1] > heldLower ? max(rawLower, heldLower) : rawLower
upperBand = close[1] < heldUpper ? min(rawUpper, heldUpper) : rawUpper

// The direction flips against the band as it stood on the previous bar, not the
// band this bar just produced, or every bar would trigger its own flip.
if not isNone(prevUpper) and close > prevUpper
    dir = 1
else if not isNone(prevLower) and close < prevLower
    dir = -1

stopLine = dir == 1 ? lowerBand : upperBand

// Two plots rather than one, each with a gap where the other is in force, so a
// flip jumps cleanly instead of drawing a diagonal from one side to the other.
plot(dir ==  1 ? stopLine : none, "Stop, long",  lime, width = 2)
plot(dir == -1 ? stopLine : none, "Stop, short", red,  width = 2)

upperPlot = plot(rawUpper, "Upper band", fade(silver, 60))
lowerPlot = plot(rawLower, "Lower band", fade(silver, 60))
fill(upperPlot, lowerPlot, fade(silver, 94))

barColor(paint ? (dir == 1 ? lime : red) : none)

// bar.isFirst guards the flip test: on bar 0 the previous direction is absent,
// dir != dir[1] is true, and the study would mark a flip that never happened.
if not bar.isFirst and dir != dir[1]
    signal(dir == 1 ? "TREND UP" : "TREND DOWN")
```

The same kind of trailing band on a BHEL 15 minute chart, from a Supertrend study with a 10 bar ATR and a multiplier of 3 that shades between the band and the candles and labels each flip BUY or SELL:


What to notice:

- **`var` remembers.** `var lowerBand = none` is set once, on the first bar, and then keeps its value from bar to bar. Read before it is reassigned, it still holds the previous bar's value. See [Persistence](/script/language/persistence).
- **Absence travels.** `max()` and `min()` of an absent value are absent. Without `orElse()`, one absent start would make the band absent forever. See [Absent values](/script/language/absent-values).
- **A plot is hidden with `none`.** Each stop line is given `none` on the bars where the other side is in force.
- **`barColor()`** repaints the candles, and gives them back their own colour when handed `none`.

## 3. Anchored VWAP

The volume weighted average price measured from a date and time you choose, with standard deviation bands around it. On an NSE chart, anchor it at a results day, a budget day or the start of the month, and it shows the average price paid by everyone who traded since.

```openscript
// Volume weighted average price measured from a date and time you pick, with
// standard deviation bands around it.

version 1

study("Anchored VWAP", overlay = true, precision = 2)

anchorTime = input("2025-01-01 09:15", "Anchor", kind = "time")
src        = input(hlc3, "Source")
bandMult   = input(1.0, "Band width, in standard deviations", min = 0.1, max = 5)
showBands  = input(true, "Show the bands")

// A time input is typed as a date and a clock time, and reads in the script as
// a timestamp that compares directly with time. An anchor that cannot be read
// is absent: the comparison below is then absent on every bar, nothing
// accumulates, and the plot is empty rather than wrong.

var priceVolume  = 0.0
var squareVolume = 0.0
var totalVolume  = 0.0
var started      = false

if not started and time >= anchorTime
    started = true

// Three running sums rather than a list of every bar since the anchor. The
// newest bar is recomputed as each update arrives, and every var is restored to
// its previous bar's value before each recompute, so the sums count bars and
// not updates.
if started
    priceVolume  += src * volume
    squareVolume += src * src * volume
    totalVolume  += volume

// vwap and variance are library names, and assigning to one is OS2002, so the
// two readings computed here have names of their own.
ready     = started and totalVolume > 0
vwapValue = ready ? priceVolume / totalVolume : none

// Rounding can leave the variance very slightly below zero on a long run of
// near identical prices. It is floored at zero before the square root; without
// the floor, sqrt would return none and leave a one bar hole in both bands.
varianceValue = ready ? max(squareVolume / totalVolume - vwapValue * vwapValue, 0) : none
dev           = ready ? sqrt(varianceValue) : none

upper = showBands ? vwapValue + bandMult * dev : none
lower = showBands ? vwapValue - bandMult * dev : none

plot(vwapValue, "Anchored VWAP", orange, width = 2)
// With the bands switched off, both edges are absent on every bar, so the fill
// stops with them and needs no test of its own.
upperPlot = plot(upper, "Upper band", aqua)
lowerPlot = plot(lower, "Lower band", aqua)
fill(upperPlot, lowerPlot, fade(aqua, 92))

// Mark the anchor bar. orElse is needed on bar 0, where started[1] is absent:
// an absent condition takes the false branch, so an anchor on the very first
// bar would otherwise go unmarked.
if started and not orElse(started[1], false)
    signal("ANCHOR")
```

What to notice:

- **`kind = "time"`** makes the input a date and time field. In the study's settings dialog it is a text box in the form `YYYY-MM-DD HH:MM`.
- **Running totals in `var`** are safe on a chart that is still receiving ticks, because the newest bar is rolled back and recomputed rather than counted twice. See [Realtime and confirmation](/script/language/realtime-and-confirmation).
- **Absence means "not started".** Before the anchor, `ready` is false and every reading is `none`, so nothing is drawn.
- **`sqrt()`** of a negative number returns `none` rather than failing. The script floors the value with `max()` instead of relying on that.

> **In this release the /trading chart reads a time input as UTC, not as Indian time. IST is UTC plus 5 hours 30 minutes, so to anchor at the 09:15 IST open of 1 January 2025, type `2025-01-01 03:45`. The default, `2025-01-01 09:15`, anchors at 14:45 IST that day.**

## 4. RSI divergence

Regular divergence between price and the relative strength index (`rsi()`): a line drawn between two RSI pivots when the oscillator disagrees with the price extremes under them. A pivot is a turning point: a bar whose value is the highest (or lowest) of a few bars on each side of it. Divergence is price and the oscillator moving in opposite directions between two pivots. A higher high in price with a lower high in RSI is marked bearish; a lower low with a higher low is marked bullish.

```openscript
// Regular divergence between price and an oscillator: a line drawn between the
// two oscillator pivots that disagree with the two price extremes under them.

version 1

study("RSI divergence", precision = 2, range = [0, 100])

rsiLen    = input(14, "RSI length", min = 2, max = 200)
leftBars  = input(5,  "Pivot left bars",  min = 1, max = 50)
rightBars = input(5,  "Pivot right bars", min = 1, max = 50)
maxSpan   = input(60, "Longest divergence, in bars", min = 5, max = 500)

oscillator = rsi(close, rsiLen)

level(70, "Overbought", fade(red, 50))
level(50, "Middle", fade(gray, 60))
level(30, "Oversold", fade(lime, 50))
plot(oscillator, "RSI", purple, width = 2)

// A pivot is only known rightBars bars after the bar it happened on, so every
// value this study reads is offset by that much. That lag is the honest cost of
// a pivot: the marker appears late because the pivot could not be known sooner.
pivotUp   = pivotHigh(oscillator, leftBars, rightBars)
pivotDown = pivotLow(oscillator, leftBars, rightBars)

var lastHighOsc   = none
var lastHighPrice = none
var lastHighTime  = none
var lastHighBar   = none

var lastLowOsc   = none
var lastLowPrice = none
var lastLowTime  = none
var lastLowBar   = none

if not isNone(pivotUp)
    thisPrice = high[rightBars]
    thisTime  = time[rightBars]
    thisBar   = bar.index - rightBars
    // Both bar indices come from the same run, so their difference is a real
    // bar count. The drawing is anchored to the time, because loading older
    // history renumbers every bar index and would drag the line sideways.
    known  = not isNone(lastHighBar)
    nearby = known and thisBar - lastHighBar <= maxSpan
    if nearby and thisPrice > lastHighPrice and pivotUp < lastHighOsc
        draw.line(lastHighTime, lastHighOsc, thisTime, pivotUp, color = red, width = 2)
        draw.label(thisTime, pivotUp, "Bearish", color = red, textColor = white)
        signal("BEARISH")
    lastHighOsc   = pivotUp
    lastHighPrice = thisPrice
    lastHighTime  = thisTime
    lastHighBar   = thisBar

if not isNone(pivotDown)
    thisPrice = low[rightBars]
    thisTime  = time[rightBars]
    thisBar   = bar.index - rightBars
    known  = not isNone(lastLowBar)
    nearby = known and thisBar - lastLowBar <= maxSpan
    if nearby and thisPrice < lastLowPrice and pivotDown > lastLowOsc
        draw.line(lastLowTime, lastLowOsc, thisTime, pivotDown, color = lime, width = 2)
        draw.label(thisTime, pivotDown, "Bullish", color = lime, textColor = black)
        signal("BULLISH")
    lastLowOsc   = pivotDown
    lastLowPrice = thisPrice
    lastLowTime  = thisTime
    lastLowBar   = thisBar
```

Without `overlay = true`, the study gets its own pane under the price, and `range = [0, 100]` pins that pane's scale so the three levels stay put. Here is an RSI pane with the same three levels:


What to notice:

- **Pivots arrive late, visibly.** `pivotHigh()` and `pivotLow()` return a value `rightBars` bars after the turn, because only then is it known. The script reads `high`, `low` and `time` `rightBars` bars back to find the turning bar.
- **Drawings anchor to time.** `draw.line()` and `draw.label()` take a bar time, not a bar index, because an index shifts when older history loads. See [Lines and boxes](/script/visuals/lines-and-boxes).
- **Block scope.** `thisPrice` inside the first `if` and `thisPrice` inside the second are two separate names that exist only in their blocks.
- **`level()`** draws the 70, 50 and 30 reference lines.

## 5. Opening range

The high and low of the first minutes of each session, held for the rest of the day, with the first break of either side marked once. On NSE, where the session opens at 09:15 IST, the default 15 minute range covers 09:15 to 09:30.

```openscript
// The high and low of the first minutes of each session, held for the rest of
// the day, with the first break of either side marked once.

version 1

study("Opening range", overlay = true, precision = 2)

rangeMinutes = input(15, "Opening range, in minutes", min = 1, max = 240)
shadeOpacity = input(1,  "Shading opacity, 0 turns the shading off",
                     min = 0, max = 1)

var openTime  = none
var rangeHigh = none
var rangeLow  = none
var broken    = 0

// Everything resets on the session's first bar rather than on a change of date,
// because a session is what an exchange opens: an evening session that runs
// past midnight is one session and two dates.
if session.isFirstBar
    openTime  = time
    rangeHigh = high
    rangeLow  = low
    broken    = 0

// Milliseconds since the open rather than a clock comparison, so the script
// says the same thing in any time zone and needs no calendar.
elapsed = isNone(openTime) ? none : time - openTime
forming = not isNone(elapsed) and elapsed < rangeMinutes * 60000

if forming and not session.isFirstBar
    rangeHigh = max(rangeHigh, high)
    rangeLow  = min(rangeLow, low)

// The first break of either side, once per session. broken is a number rather
// than a bool, so it also records which side broke.
if not forming and broken == 0 and not isNone(rangeHigh)
    if close > rangeHigh
        broken = 1
        signal("BREAK UP")
    else if close < rangeLow
        broken = -1
        signal("BREAK DOWN")

highPlot = plot(rangeHigh, "Range high", aqua,   width = 2, style = "step")
lowPlot  = plot(rangeLow,  "Range low",  orange, width = 2, style = "step")

// The shading switch is an opacity of zero, not a colour of none: fill() reads
// an absent colour as "no colour given" and shades with a default. opacity dims
// whatever colour is there, and zero is off. It is a number input because
// opacity is settled before the first bar and takes a number from 0 to 1.
fill(highPlot, lowPlot, fade(aqua, 93), opacity = shadeOpacity)

// A background rather than a fourth line, because "the range is still forming"
// is a fact about the whole bar and has no price to sit at.
background(forming ? fade(silver, 92) : none)
```

What to notice:

- **`session.isFirstBar`** is true on the first bar of each trading session. Resetting state there, instead of on a change of date, is what makes the script right for MCX's evening session as well as NSE's day session. See [Sessions and time](/script/data/sessions-and-time).
- **Time arithmetic in milliseconds.** `time` is a timestamp in milliseconds, so `rangeMinutes * 60000` is the length of the range.
- **`style = "step"`** draws a level that changes in steps rather than sloping between bars.
- **`background()`** shades the bars while the range is forming.

> **In this release the /trading chart does not tell the engine when the exchange session opens, so `session.isFirstBar` has no value there and this study draws nothing but its legend row. The chart may also say the study "needs more history than the bars loaded": it shows that message for any study with no value on any bar, and here the cause is the missing session hours, not the history. The script is correct OpenScript and runs wherever the host states its session hours.**

## 6. Combined premium

The combined price of two option legs, such as the call and put of a NIFTY straddle on NFO (a call and a put at the same strike and expiry), read from two contracts that are not on the chart, with the session's opening premium held as a reference and an alert when the premium has decayed by a chosen percentage.

```openscript
// The combined price of two option legs, read from two instruments that are not
// the one on the chart, with the session's opening premium held as a reference.

version 1

study("Combined premium", precision = 2, format = "price")

// Plain text inputs: type each leg's trading symbol. An instrument picker input
// is planned, and until it exists the legs are typed in.
callLeg   = input("", "Call leg")
putLeg    = input("", "Put leg")
lots      = input(1,  "Lots", min = 1, max = 100)
targetPct = input(30, "Decay to mark, in percent of the opening premium", min = 1, max = 99)

// Each leg is read at the chart's own interval and aligned to the chart's bars,
// so the sum below adds two prices from the same moment. The read uses the
// default mode, "confirmed", which never repaints.
callPrice = req.symbol(callLeg, chart.interval, close, exchange = chart.exchange)
putPrice  = req.symbol(putLeg,  chart.interval, close, exchange = chart.exchange)

// If either leg has no bar at this time, the sum is absent, not half a
// position. A missing leg shows as a gap in the line instead of a smaller
// number that looks like a profit.
premium = callPrice + putPrice
money   = premium * lots * chart.lotSize

var opening = none

if session.isFirstBar
    opening = none

if isNone(opening) and not isNone(premium)
    opening = premium

// Decay is positive when the seller of both legs is ahead, which is the sign a
// premium seller expects, so the subtraction is written that way round.
decay = isNone(opening) ? none : (opening - premium) / opening * 100

plot(premium, "Combined premium", orange, width = 2)
plot(opening, "Opening premium", fade(silver, 40), style = "step")
plot(money,   "Position value", aqua, scale = "left")

// The condition is the if around the call, not an argument to alert(). decay is
// absent on any bar where either leg is missing, and an absent condition takes
// the false branch, so neither the alert nor the marker fires on a gap.
if decay >= targetPct
    alert("Combined premium decayed " + text(decay, 1) + " percent",
          id = "premium-decay")
    signal("TARGET")
```

To use it, open a chart on NFO, for example the NIFTY future or one of the two legs, and type both legs' trading symbols into the study's settings. Both legs are read on the chart's own exchange and at the chart's own interval.

What to notice:

- **`req.symbol()`** reads another instrument's series, aligned to the chart's bars. See [Other instruments](/script/data/other-instruments).
- **Absence protects the sum.** If one leg has no bar, `premium` is `none`, the line breaks, and no condition built on it is true.
- **`alert()`** raises an alert with a message and an id; the `if` around it is the condition. See [Alerts from scripts](/script/alerts/overview).
- **`scale = "left"`** puts the position value on its own axis, so rupees and premium points do not share a scale.
- **Options pricing.** This study adds traded prices. When you need a fair value for an Indian index option, price it with Black-76 off the synthetic future, not with a spot-based model.

> **In this release the /trading chart does not state a lot size or session hours to the engine. `chart.lotSize` is then absent, so the **Position value** line stays empty, and `session.isFirstBar` has no value, so the opening premium is taken once, at the first bar both legs have, rather than at every session's open. In the default confirmed mode each leg is its latest closed bar, one bar behind the chart, and both legs lag together, so the sum still adds two prices from the same moment; `mode = "developing"` reads the forming bar instead and can repaint. `chart.exchange` is absent on the chart as well, and the legs are then looked up on the chart's own exchange. The combined premium draws as described; its alert follows the chart's rule for script alerts, so during market hours it may not fire (see [Alerts from scripts](/script/alerts/overview)).**

## 7. Higher timeframe bias

A daily trend reading drawn over an intraday chart: a 20 day exponential moving average of daily closes, stepped across every 15 minute bar, with the candles and background coloured by whether the last finished day closed above or below it. The script states in its source which of the three ways of reading a higher timeframe it uses, so a reader can see that it never repaints. A study repaints when a value it has already drawn on a past bar later changes, so the history on the chart shows signals that were not there at the time.

```openscript
// A higher timeframe trend reading, drawn over an intraday chart, stating in
// the source which of the three readings it takes.

version 1

study("Higher timeframe bias", overlay = true, precision = 2)

biasTf  = input("1D", "Bias timeframe", kind = "interval")
biasLen = input(20,   "Bias average length", min = 2, max = 500)
paint   = input(true, "Recolour the candles")

// mode is written into the source, not offered as a setting, on purpose. The
// three readings are:
//
//   "confirmed"  the last higher timeframe bar that has finished, held constant
//                across the one now forming. It never uses a bar that had not
//                happened yet, so what the chart shows today is what it showed
//                at the time.
//   "developing" the higher timeframe bar as it stands on this bar: its high so
//                far, its close so far. Honest too, but the value changes while
//                that bar is open, so a signal taken from it can be withdrawn.
//   "lookahead"  the finished values of the higher timeframe bar, on every bar
//                inside it. This reads the future and repaints, and the compiler
//                warns about it (OS8005).
//
// "confirmed" is the default and the only one of the three that never repaints.
// A setting would let a reader change the honesty of the study without reading
// it, which is what writing the mode here prevents.
biasClose   = req.timeframe(biasTf, close, mode = "confirmed")
biasAverage = req.timeframe(biasTf, ema(close, biasLen), mode = "confirmed")

// The average is computed on the daily bars, then sampled onto every intraday
// bar. That is not the same series as a 20 bar average of intraday closes.
up   = not isNone(biasAverage) and biasClose > biasAverage
down = not isNone(biasAverage) and biasClose < biasAverage

// A step plot, because the value changes once per daily bar, and a line sloping
// between two daily readings would suggest intraday values that were never read.
plot(biasAverage, "Bias average", orange, width = 2, style = "step")

barColor(paint ? (up ? lime : down ? red : none) : none)
background(up ? fade(lime, 95) : down ? fade(red, 95) : none)

// Signals wait for the bar to close, because this file does not set
// onUnconfirmed. Setting it would make the compiler warn on the two reads above
// (OS8002): an unconfirmed intraday bar reading a higher timeframe is where
// repainting comes from even when the mode is honest.
if up and not orElse(up[1], false)
    signal("BIAS UP")

if down and not orElse(down[1], false)
    signal("BIAS DOWN")
```


What to notice:

- **`req.timeframe()`** evaluates an expression, here `ema(close, biasLen)`, on the higher timeframe's bars. See [Higher timeframes](/script/data/higher-timeframes).
- **The mode is written out.** `mode = "confirmed"` is the default; writing it makes the choice visible. See [Repainting](/script/data/repainting).
- **`kind = "interval"`** makes the input a timeframe menu.
- **Flip markers** compare `up` with `up[1]`, through `orElse()` so the first bar does not count as a flip.
- **The daily bars come from the chart's own bars.** On the /trading chart, a read of the chart's own instrument at a longer timeframe is built from the intraday bars already loaded. A 15 minute chart loads about two months of history, enough for 20 daily closes; a 1 minute chart loads about a week, too few, and the average has no value there.

## 8. Dashboard

A panel fixed to a corner of the chart with the readings a discretionary trader glances at: trend, RSI and its zone, ATR as a percentage of price, where price sits in its recent range, and volume against its average. It draws no line at all.

```openscript
// A panel fixed to a corner of the chart holding the readings a discretionary
// trader glances at, rather than a line drawn per bar.

version 1

study("Dashboard", overlay = true)

rsiLen  = input(14, "RSI length", min = 2, max = 200)
atrLen  = input(14, "ATR length", min = 1, max = 200)
lookback = input(20, "Range lookback", min = 2, max = 500)
corner  = input("topRight", "Corner",
                options = ["topLeft", "topRight", "bottomLeft", "bottomRight"])

// The table is declared once, before the first bar, like a plot: the chart has
// to know what it is reserving room for before any data arrives. Only the
// contents of the cells change per bar. The title comes first and is required.
panel = table("Readings", rows = 7, cols = 2, position = corner,
              textColor = silver, bgColor = fade(black, 25))

oscillator = rsi(close, rsiLen)
atrValue   = atr(atrLen)
atrPercent = atrValue / close * 100

highest20 = highest(high, lookback)
lowest20  = lowest(low, lookback)
span      = highest20 - lowest20
rangePct  = span > 0 ? (close - lowest20) / span * 100 : none

trend = ema(close, 20) > ema(close, 50)
volumeRatio = volume / sma(volume, lookback)

// One place that decides what an absent reading looks like in a cell. A blank
// cell and a zero are both wrong: the first hides that the study is still
// warming up, the second invents a number.
fn show(value, decimals) => isNone(value) ? "warming up" : text(value, decimals)

// The condition form of switch, because the arms test different things rather
// than matching one value. zone is declared before the switch: an arm cannot
// introduce a name that outlives it.
zone = "neutral"
switch
    case oscillator > 70
        zone = "overbought"
    case oscillator < 30
        zone = "oversold"
    default
        zone = "neutral"

zoneColor = oscillator > 70 ? red : oscillator < 30 ? lime : silver

// Written only on the newest bar. The panel shows one state, the current one,
// so writing it on every bar would cost thousands of writes to show the last.
// The newest bar is recomputed as each update arrives and rewrites the same
// cells.
if bar.isLast
    cell(panel, 0, 0, chart.symbol, textColor = white)
    cell(panel, 0, 1, chart.interval, textColor = white)

    cell(panel, 1, 0, "Trend")
    // Not trend ? "up" : "down" on its own: an absent condition takes the false
    // arm, so the panel would read "down" for the first fifty bars and mean it.
    cell(panel, 1, 1, isNone(trend) ? "warming up" : (trend ? "up" : "down"),
         textColor = isNone(trend) ? silver : (trend ? lime : red))

    cell(panel, 2, 0, "RSI")
    cell(panel, 2, 1, show(oscillator, 1), textColor = zoneColor)

    cell(panel, 3, 0, "Zone")
    cell(panel, 3, 1, zone, textColor = zoneColor)

    cell(panel, 4, 0, "ATR, percent of price")
    cell(panel, 4, 1, show(atrPercent, 2))

    cell(panel, 5, 0, "Position in " + text(lookback, 0) + " bar range")
    cell(panel, 5, 1, show(rangePct, 0) + " percent")

    cell(panel, 6, 0, "Volume against average")
    cell(panel, 6, 1, show(volumeRatio, 2), textColor = volumeRatio > 2 ? orange : silver)
```


What to notice:

- **`table()`** is declared at the top level, once; `cell()` writes into it on any bar. See [Tables](/script/visuals/tables).
- **`bar.isLast`** limits the writing to the newest bar, which is the only one the panel shows.
- **A user function.** `fn show(value, decimals) => ...` is a one-line function that turns an absent reading into the words "warming up". See [User functions](/script/language/functions).
- **`switch` without a subject** runs the first `case` whose condition is true. See [Control flow](/script/language/control-flow).
- **An input as a fixed option.** `position = corner` takes the corner from a menu input. A table's position is settled before the first bar, and an option settled that early accepts an `input()` value directly.

## 9. Supply and demand zones

Supply zones drawn as boxes where price turned down, demand zones where it turned up, each extended to the right while it holds, and deleted when price closes through it or it grows too old. The whole output is geometry, so the study declares no plot.

```openscript
// Supply and demand boxes drawn where price turned, extended right while they
// hold, and deleted when price closes through them or they get too old.

version 1

study("Supply and demand zones", overlay = true, precision = 2)

leftBars  = input(5,  "Pivot left bars",  min = 1, max = 50)
rightBars = input(5,  "Pivot right bars", min = 1, max = 50)
maxAge    = input(200, "Delete a zone after this many bars", min = 10, max = 5000)
maxZones  = input(12,  "Live zones per side", min = 1, max = 100)

// One array of drawing objects and four of plain numbers describing them. A
// drawing object can be changed and deleted but not read back, so the script
// remembers what it drew in order to decide later whether price has broken it.
var zones      = []
var zoneTop    = []
var zoneBottom = []
var zoneSide   = []
var zoneBar    = []

// Counted downwards, so removing element i does not renumber an element the
// loop has yet to visit. Counting upwards with a removal inside skips elements.
for i = size(zones) - 1 to 0 step -1
    top    = element(zoneTop, i)
    bottom = element(zoneBottom, i)
    side   = element(zoneSide, i)
    age    = bar.index - element(zoneBar, i)

    // A supply zone dies when price closes above it, a demand zone when price
    // closes below it. Closing through, not touching: a wick into a zone is the
    // zone working.
    broken = side > 0 ? close > top : close < bottom

    if broken or age > maxAge
        draw.delete(element(zones, i))
        remove(zones, i)
        remove(zoneTop, i)
        remove(zoneBottom, i)
        remove(zoneSide, i)
        remove(zoneBar, i)
        continue

    // Extended to this bar's own time rather than beyond it, because where the
    // next bar starts is not something the script knows.
    draw.setTo(element(zones, i), time, bottom)
    draw.setTooltip(element(zones, i), (side > 0 ? "Supply" : "Demand") +
                    ", " + text(age, 0) + " bars old")

pivotUp   = pivotHigh(high, leftBars, rightBars)
pivotDown = pivotLow(low, leftBars, rightBars)

// A zone spans the extreme of the turning bar and the far edge of its body: the
// part of the move nobody traded back through.
if not isNone(pivotUp) and size(zones) < maxZones * 2
    startTime = time[rightBars]
    zoneHigh  = high[rightBars]
    zoneLow   = max(open[rightBars], close[rightBars])
    shape = draw.box(startTime, zoneHigh, time, zoneLow,
                     color = red, fillColor = fade(red, 85), text = "Supply")
    push(zones, shape)
    push(zoneTop, zoneHigh)
    push(zoneBottom, zoneLow)
    push(zoneSide, 1)
    push(zoneBar, bar.index - rightBars)

if not isNone(pivotDown) and size(zones) < maxZones * 2
    startTime = time[rightBars]
    zoneHigh  = min(open[rightBars], close[rightBars])
    zoneLow   = low[rightBars]
    shape = draw.box(startTime, zoneHigh, time, zoneLow,
                     color = lime, fillColor = fade(lime, 85), text = "Demand")
    push(zones, shape)
    push(zoneTop, zoneHigh)
    push(zoneBottom, zoneLow)
    push(zoneSide, -1)
    push(zoneBar, bar.index - rightBars)

// This study declares no plot. Nothing it produces is one value per bar, and a
// plotted count would put a flat line across the price scale to say what the
// boxes already say.
```


What to notice:

- **Drawing objects live across bars.** `draw.box()` creates a box, `draw.setTo()` moves its right edge, and `draw.delete()` removes it. See [Lines and boxes](/script/visuals/lines-and-boxes).
- **Arrays in `var`.** `var zones = []` is created once and kept; `push()`, `remove()`, `element()` and `size()` manage it. See [Collections](/script/language/collections).
- **A descending loop.** `step -1` walks the list from the end, so a removal never skips the next element. `continue` moves on to the next zone.
- **No plot is fine.** A study whose whole output is drawings needs no `plot`.

## 10. EMA cross, bracketed

The crossing from script 1, traded: a long entry on the upward cross, a stop and a target fixed at entry from the ATR, a size chosen so every trade risks the same amount, and an exit on the downward cross. It also declares its capital and costs.

```openscript
// The same crossing as the first example, traded: a stop and a target fixed at
// entry, and a size chosen so that the stop costs the same on every trade.

version 1

strategy("EMA cross, bracketed", overlay = true, precision = 2,
         capital = 500000, qtyType = "units", qty = 1,
         product = "intraday", pyramiding = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

fastLen    = input(9,    "Fast length", min = 1, max = 500)
slowLen    = input(21,   "Slow length", min = 1, max = 500)
atrLen     = input(14,   "ATR length",  min = 1, max = 200)
stopMult   = input(2.0,  "Stop, in ATR",   min = 0.2, max = 20)
targetMult = input(3.0,  "Target, in ATR", min = 0.2, max = 40)
riskAmount = input(5000, "Amount risked per trade", min = 1)

fast = ema(close, fastLen)
slow = ema(close, slowLen)
atrValue = atr(atrLen)

stopDistance   = stopMult * atrValue
targetDistance = targetMult * atrValue

// Size from the distance to the stop, so a wide stop buys fewer units and every
// trade risks the same amount of money.

// chart.lotSize is absent, not 1, when the host has not said what a lot is, and
// absence passes through max as it does through arithmetic. So the fallback
// goes inside: without orElse every size would be absent and the strategy would
// never place an order.
lotUnits  = max(orElse(chart.lotSize, 1), 1)
rawUnits  = stopDistance > 0 ? riskAmount / stopDistance : none
orderQty  = isNone(rawUnits) ? none : floor(rawUnits / lotUnits) * lotUnits

var entryStop   = none
var entryTarget = none

flat    = pos.size == 0
canSize = not isNone(orderQty) and orderQty > 0

if crossUp(fast, slow) and flat and canSize
    // The levels come from this bar's close, and the order fills at the next
    // bar's open, which is the default and the honest one. The report shows the
    // slippage between the two rather than hiding it.
    //
    // exit() takes absolute prices, and both levels go in one call, never two,
    // so a gap through both cannot fill them as separate orders.
    entryStop   = close - stopDistance
    entryTarget = close + targetDistance
    buy(qty = orderQty, tag = "entry")
    exit(tag = "entry", stop = entryStop, limit = entryTarget)

if crossDown(fast, slow) and pos.size > 0
    entryStop   = none
    entryTarget = none
    close()

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)

// The stop as sent, not as it would be recomputed now. Plotting the distance
// from the current close would draw a line that trails the price and was never
// an order.
plot(pos.size > 0 ? entryStop : none,   "Stop",   red,  style = "step")
plot(pos.size > 0 ? entryTarget : none, "Target", lime, style = "step")
plot(pos.size > 0 ? pos.avgPrice : none, "Entry", fade(silver, 40), style = "step")
```

What to notice:

- **A strategy is a study with orders.** The same crossing as script 1, with `buy()`, `exit()` and `close()` added and the declaration changed.
- **Risk-based sizing by hand.** The size is the money at risk divided by the stop distance, rounded down to whole lots with `floor()`. `chart.lotSize` is absent when the host states no lot size, so `orElse()` supplies 1.
- **Levels held in `var`** are the levels actually sent, so the plotted stop is the stop the trade was opened with.
- **`pos.size` and `pos.avgPrice`** read the position from fills.

> **In version 0.5.0 the backtest does not fill the levels `exit()` sets, so in the Backtest panel every trade of this script closes on the downward cross, and the stop and target lines are drawn but never filled. The Strategies panel refuses to start a strategy that calls `exit()`. To trade this idea today, manage the stop and target in the script with `close()`, as [Your first strategy](/script/getting-started/first-strategy) does.**

## 11. Opening range breakout

The opening range of script 5, traded once per session: long on a close above the range, short on a close below it, the far side of the range as the stop, a target at a multiple of the range width, and a hard exit five hours after the open. Sized in lots, for an index future on NFO or a commodity future on MCX.

```openscript
// The opening range of the fifth example, traded once per session, with the
// other side of the range as the stop and a hard exit by the clock.

version 1

strategy("Opening range breakout", overlay = true, precision = 2,
         capital = 500000, qtyType = "lots", qty = 1,
         product = "intraday", pyramiding = 1, closeOnSessionEnd = true,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

rangeMinutes = input(15,  "Opening range, in minutes", min = 1, max = 240)
holdMinutes  = input(300, "Flat this many minutes after the open", min = 5, max = 1440)
targetMult   = input(2.0, "Target, in range widths", min = 0.2, max = 10)
lots         = input(1,   "Lots", min = 1, max = 100)

var openTime  = none
var rangeHigh = none
var rangeLow  = none
var traded    = false

if session.isFirstBar
    openTime  = time
    rangeHigh = high
    rangeLow  = low
    traded    = false

elapsed = isNone(openTime) ? none : time - openTime
forming = not isNone(elapsed) and elapsed < rangeMinutes * 60000

if forming and not session.isFirstBar
    rangeHigh = max(rangeHigh, high)
    rangeLow  = min(rangeLow, low)

rangeWidth = isNone(rangeHigh) ? none : rangeHigh - rangeLow

// One entry per session, and only after the range has finished forming. traded
// is set at the entry rather than cleared at the exit, so a trade stopped out
// at ten past the open does not re-enter at quarter past.
ready = not forming and not traded and pos.size == 0
ok    = not isNone(rangeWidth) and rangeWidth > 0

if ready and ok and close > rangeHigh
    traded = true
    buy(qty = lots, tag = "entry")
    // The far side of the range is the stop, because that is the level that
    // says the breakout was wrong. A multiple of volatility would be a second
    // opinion about a level the market has already drawn.
    exit(tag = "entry", stop = rangeLow, limit = rangeHigh + rangeWidth * targetMult)

if ready and ok and close < rangeLow
    traded = true
    sell(qty = lots, tag = "entry")
    exit(tag = "entry", stop = rangeHigh, limit = rangeLow - rangeWidth * targetMult)

// The clock exit is neither a stop nor a target: it is the admission that a
// position that has not worked in five hours is not going to. closeOnSessionEnd
// is declared as well, for hosts that state session hours; /trading states
// none in this release, so there it has no effect.
if pos.size != 0 and not isNone(elapsed) and elapsed >= holdMinutes * 60000
    close()

highPlot = plot(rangeHigh, "Range high", aqua,   width = 2, style = "step")
lowPlot  = plot(rangeLow,  "Range low",  orange, width = 2, style = "step")
fill(highPlot, lowPlot, fade(aqua, 93))
background(forming ? fade(silver, 92) : none)
```

What to notice:

- **Both directions.** `sell()` opens a short on the downside break, and the same `exit()` call protects either side.
- **`qtyType = "lots"`** counts the order size in lots, converted to units through the instrument's lot size.
- **One trade per session**, held in `var traded`, reset on `session.isFirstBar`.
- **A time exit.** `elapsed >= holdMinutes * 60000` flattens with `close()` five hours after the open, before the NSE close at 15:30 IST.

> **In this release the /trading chart and Backtest panel do not state the session's opening time to the engine, so `session.isFirstBar` has no value there, the range never forms and the script opens no trades. The Strategies panel refuses it, because it reads the session, calls `exit()` and sizes in lots. It is shown here for the language: the pattern is the one to reach for wherever a host states its session hours.**

## 12. Short premium, combined stop

Two option legs sold together and managed as one position: one stop, one target and one clock exit, all measured on the sum of the two prices. The chart carries one leg, for example the NIFTY call; the script reads the other with `req.symbol()` and raises an alert for it, because a strategy trades the instrument on its chart.

```openscript
// Two option legs sold together and managed as one position: one stop, one
// target, one clock exit, all measured on the sum of the two prices rather than
// on either leg.

version 1

strategy("Short premium, combined stop", precision = 2,
         capital = 500000, qtyType = "lots", qty = 1,
         product = "intraday", pyramiding = 1, closeOnSessionEnd = true,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

// A plain text input: type the other leg's trading symbol. An instrument picker
// input is planned, and until it exists the leg is typed in.
otherLeg    = input("", "The other leg's symbol")
lots        = input(1,  "Lots per leg", min = 1, max = 100)
tradeDay    = input(4,  "Weekday to trade, 1 is Monday", min = 1, max = 7)
entryMinute = input(20, "Enter this many minutes after the open", min = 0, max = 1440)
exitMinute  = input(330, "Flat this many minutes after the open", min = 1, max = 1440)
stopPct     = input(30, "Stop, in percent of the entry premium", min = 1, max = 500)
targetPct   = input(50, "Target, in percent of the entry premium", min = 1, max = 99)

// The chart carries one leg and the script reads the other. A leg the chart
// does not show has no bars a backtest could fill against, so this script
// trades the chart's leg and routes the other through the alerts below.
otherPrice = req.symbol(otherLeg, chart.interval, close, exchange = chart.exchange)

// The position is the sum, so the sum is what is managed. Stopping each leg
// separately is the classic way to take two losses on a day the legs were
// hedging each other.
premium = close + otherPrice

var openTime     = none
var entryPremium = none
var doneToday    = false

if session.isFirstBar
    openTime  = time
    doneToday = false

// Cleared before the entry below can set a fresh one, so a position closed at
// the session end does not leave its entry premium behind for the next day.
// Written the other way round, it would wipe the level on the entry bar itself,
// because the position is still flat until the fill.
if pos.isFlat
    entryPremium = none

elapsed = isNone(openTime) ? none : time - openTime

// A day of the week rather than a list of dates, because the day this strategy
// wants recurs every week and a list of dates goes stale.
rightDay   = date.dayOfWeek(time) == tradeDay
priced     = not isNone(premium)
afterEntry = not isNone(elapsed) and elapsed >= entryMinute * 60000
afterExit  = not isNone(elapsed) and elapsed >= exitMinute * 60000

if rightDay and priced and afterEntry and pos.isFlat and not doneToday
    sell(qty = lots, tag = "premium")
    entryPremium = premium
    doneToday    = true
    alert("SELL " + text(lots, 0) + " lots of " + otherLeg, id = "other-leg-entry")

// Positive when the seller is losing, which is the direction the stop cares
// about. Absence does the guarding: entryPremium is absent while flat and
// premium is absent whenever either leg has no bar, so movePct is absent and no
// test below is true.
movePct = (premium - entryPremium) / entryPremium * 100

hit     = not isNone(movePct) and movePct >= stopPct
banked  = not isNone(movePct) and movePct <= -targetPct
timeOut = afterExit

// One close, with the reason attached, rather than three blocks that could each
// send one on the same bar. The position stays short until the fill on the next
// bar's open, so three blocks whose conditions were all true would send three
// closing orders against one short position.
if pos.isShort and (hit or banked or timeOut)
    close()
    alert("BUY " + text(lots, 0) + " lots of " + otherLeg, id = "other-leg-exit")
    signal(hit ? "STOP" : (banked ? "TARGET" : "TIME"))

// No ternary on these: entryPremium is absent while flat, absence passes through
// the arithmetic, and a plot given an absent value draws a gap, not a zero.
plot(premium, "Combined premium", orange, width = 2)
plot(entryPremium, "Entry premium", fade(silver, 40), style = "step")
plot(entryPremium * (1 + stopPct / 100), "Stop", red, style = "step")
plot(entryPremium * (1 - targetPct / 100), "Target", lime, style = "step")
```

What to notice:

- **Managed on the sum.** The stop and target are percentages of the combined entry premium, not of either leg.
- **A weekly schedule.** `date.dayOfWeek()` picks the weekday, for example the day before a weekly expiry.
- **Absence as a guard.** While flat, `entryPremium` is `none`, so `movePct` is `none` and none of the exit tests can be true.
- **One exit with a reason.** A single `close()` carries the reason in its `signal()` marker, so the position can never be closed three times over.
- **The second leg is an alert.** The strategy trades the chart's leg; the other leg's orders go out as `alert()` messages with their own ids. On /trading this script does not run today, as the note below explains.

> **The Backtest panel refuses this script with [OS6006](/script/errors/data#os6006), because a backtest holds only the chart's own bars and this script reads another instrument. On the /trading chart it draws the combined premium but opens no trades, because `session.isFirstBar` has no value there. The Strategies panel refuses it, because it reads the session, the calendar and another instrument, and sizes in lots. A strategy that wants both legs in its own books declares them with `leg.relative()`, which is planned; see [Legs and books](/script/strategies/multi-leg-and-books).**

## Showcase scripts

Three more studies, written to look their best on a chart as well as to teach. Each file below is the exact script that drew the screenshot under it, so what you paste is what you see. All three are studies: they draw on the /trading chart as described and place no orders.

### HalfTrend

A trend level that holds flat through noise and turns only when the other side of the recent range gives way. While the trend is up, the study keeps the highest value the low of that range has reached since the turn, and it turns down when the average high of the last few bars falls below it and the bar closes below the previous bar's low. A downtrend is the mirror image. The level is blue while the trend is up and red while it is down, a shaded channel rides beside it, and every flip is labelled. **Amplitude** is the number of bars in the range it watches, and the channel sits a multiple of half the average true range (`atr()`) away from the level.

```openscript
// HalfTrend: a trend level that holds flat through noise and turns only when
// the other side of the range gives way. Blue while the trend is up, red while
// it is down, with a shaded channel and a label on every flip.
version 1

study("HalfTrend", overlay = true, precision = 2)

amplitude = input(2, "Amplitude", min = 1, max = 100)
channelDev = input(2, "Channel deviation", min = 0, max = 20)
atrLen = input(100, "ATR length", min = 1, max = 500)

half = atr(atrLen) / 2
dev = channelDev * half
rollHigh = highest(high, amplitude)
rollLow = lowest(low, amplitude)
meanHigh = sma(high, amplitude)
meanLow = sma(low, amplitude)
prevHigh = bar.isFirst ? high : high[1]
prevLow = bar.isFirst ? low : low[1]

// trend is 0 while up and 1 while down; armed is the flip being watched for.
var trend = 0
var armed = 0
var maxLow = low
var minHigh = high
var upLevel = low
var downLevel = high

wasTrend = bar.isFirst ? -1 : trend

if armed == 1
    maxLow = max(orElse(rollLow, maxLow), maxLow)
    if not isNone(meanHigh) and meanHigh < maxLow and close < prevLow
        trend = 1
        armed = 0
        minHigh = orElse(rollHigh, high)
else
    minHigh = min(orElse(rollHigh, minHigh), minHigh)
    if not isNone(meanLow) and meanLow > minHigh and close > prevHigh
        trend = 0
        armed = 1
        maxLow = orElse(rollLow, low)

flipUp = trend == 0 and wasTrend == 1
flipDown = trend == 1 and wasTrend == 0

// On a flip the new level starts where the other side ended, so it steps.
if trend == 0
    upLevel = flipUp ? downLevel : wasTrend == -1 ? maxLow : max(maxLow, upLevel)
else
    downLevel = flipDown ? upLevel : wasTrend == -1 ? minHigh : min(minHigh, downLevel)

ht = trend == 0 ? upLevel : downLevel

upLine = plot(trend == 0 ? ht : none, "Up trend", #2962ff, width = 2)
downLine = plot(trend == 1 ? ht : none, "Down trend", #ef5350, width = 2)
upEdge = plot(trend == 0 ? ht - dev : none, "Channel low", fade(#2962ff, 55), width = 1)
downEdge = plot(trend == 1 ? ht + dev : none, "Channel high", fade(#ef5350, 55), width = 1)
fill(upLine, upEdge, fade(#2962ff, 82))
fill(downLine, downEdge, fade(#ef5350, 82))

if flipUp
    signal("Buy", #2962ff, at = "below", shape = "label")

if flipDown
    signal("Sell", #ef5350, at = "above", shape = "label")
```

On a BHEL 15 minute NSE chart with the default settings:


What it shows:

- **State carried across bars.** `trend`, `armed`, `maxLow`, `minHigh`, `upLevel` and `downLevel` are declared with `var`, so each is set once, on the first bar, and then starts every bar from the value the previous bar left. `wasTrend` reads `trend` above the lines that reassign it, so it holds the previous bar's trend, and `-1` on the first bar, so nothing there counts as a flip. See [Persistence](/script/language/persistence).
- **Two plots, so the line can change colour.** `Up trend` has a value only while the trend is up and `Down trend` only while it is down; each is `none` on the other side. A flip is a clean step from one line to the other, never a diagonal drawn through the candles.
- **Fills to a channel edge.** Each level has a faint edge plot `dev` away from it, below the up level and above the down level, and `fill()` shades between the level and its edge. The fill colours are written in the call with `fade()`, so the chart draws them as given.
- **A label on a flip.** `flipUp` and `flipDown` compare this bar's trend with `wasTrend`, and `signal()` with `shape = "label"` puts a Buy plate on the bar that turned up (`at = "below"`) and a Sell plate on the bar that turned down (`at = "above"`).
- **The start of the chart, handled.** `orElse()` supplies a starting value while `highest()` and `lowest()` have none yet, and the `isNone()` tests make each flip test wait until the averages it compares have values.

### Bollinger Bands

A 20 bar simple average with bands two standard deviations either side of it, the space between the bands shaded, and a label where price closes outside a band. Wide bands mean a volatile market, narrow ones a quiet one.

```openscript
// Bollinger Bands: a 20 bar mean with bands two standard deviations either
// side, the space between them shaded, and a label where price closes
// outside a band.
version 1

study("Bollinger Bands", overlay = true, precision = 2)

len = input(20, "Length", min = 1, max = 500)
mult = input(2, "Deviations", min = 0.5, max = 5)

bb = bollinger(close, len, mult)
basis = bb[0]
upper = bb[1]
lower = bb[2]

plot(basis, "Basis", orange, width = 2)
upperPlot = plot(upper, "Upper", #2962ff, width = 1.5)
lowerPlot = plot(lower, "Lower", #2962ff, width = 1.5)
fill(upperPlot, lowerPlot, fade(#2962ff, 88))

if crossUp(close, upper)
    signal("Breakout", #26a69a, at = "above", shape = "label")

if crossDown(close, lower)
    signal("Breakdown", #ef5350, at = "below", shape = "label")
```

On a BHEL 15 minute NSE chart with the default settings:


What it shows:

- **A function with several outputs.** `bollinger()` returns the basis, the upper band and the lower band in one array, from one call and one piece of state. The script names each one, `basis = bb[0]` and so on, so the rest of the file reads as words rather than positions. See [Plotting a function with several outputs](/script/visuals/plots#plotting-a-function-with-several-outputs).
- **A fill between two plots.** The two bands are plotted into named handles, `upperPlot` and `lowerPlot`, and `fill()` shades between them. The basis needs no handle, because nothing fills to it.
- **Signals on a band cross.** `crossUp()` of the close over the upper band marks a Breakout, and `crossDown()` of the close under the lower band marks a Breakdown. Each is written in an `if` condition at the top level, so it runs on every bar and never misses a crossing.
- **Inputs with limits.** `min` and `max` on each `input()` keep the settings dialog to lengths and widths that make sense.

### EMA cross in colour

Script 1 dressed for the chart: a 20 and a 50 bar exponential average, the band between them green while the fast average leads and red while the slow one does, and a labelled marker on every crossover. The labels use the traditional names: a golden cross for an upward cross and a death cross for a downward one.

```openscript
// EMA cross: a fast and a slow exponential average, the band between them
// shaded by trend, and a labelled marker on every crossover.
version 1

study("EMA cross", overlay = true, precision = 2)

fastLen = input(20, "Fast length", min = 1, max = 500)
slowLen = input(50, "Slow length", min = 1, max = 500)

fast = ema(close, fastLen)
slow = ema(close, slowLen)

fastPlot = plot(fast, "Fast EMA", aqua, width = 2)
slowPlot = plot(slow, "Slow EMA", orange, width = 2)

// Green while the fast average is above the slow one, red while below.
fill(fastPlot, slowPlot, colorUp = fade(lime, 80), colorDown = fade(red, 80))

if crossUp(fast, slow)
    signal("Golden cross", lime, at = "below", shape = "label")

if crossDown(fast, slow)
    signal("Death cross", red, at = "above", shape = "label")
```

On a daily SBIN chart:


What it shows:

- **One fill, two colours.** `fill()` with `colorUp` and `colorDown` colours the band by which plot is on top. `colorUp` applies where the first plot named, `fastPlot`, is at or above the second, so green means the fast average leads. See [Fills](/script/visuals/fills#two-colours-for-which-side-leads).
- **Labelled crossovers.** Each `signal()` states its text, its colour, its side of the bar and its shape, so a Golden cross is a lime plate below the bar and a Death cross a red plate above it.
- **Transparency in the colours.** `fade(lime, 80)` and `fade(red, 80)` carry the band's transparency and `opacity` is left alone, which is how [Fills](/script/visuals/fills#opacity) recommends shading a band with two colours.

**Related.** [Quickstart](/script/getting-started/quickstart), [Your first strategy](/script/getting-started/first-strategy), [Visuals overview](/script/visuals/overview), [Strategies overview](/script/strategies/overview), [Style guide](/script/writing/style-guide), [Troubleshooting](/script/writing/troubleshooting)


# Language

## Script structure

Source: https://openalgo.in/script/language/script-structure

Every OpenScript file (OpenScript is also called OpenAlgo Script) has the same shape: a version line, one declaration, an optional limits line and a body of statements. This page covers that shape and the layout rules the compiler holds every file to: one statement per line, blocks made of indentation, comments, and how a long statement continues onto the next line. Read it before your first script, and come back to it when the console under the editor reports an error whose code starts with OS1 (the syntax errors).

## A complete script

Here is a small study with every part labelled. Paste it into the Scripts panel of the /trading page, save it, and apply it to any NSE chart.

```openscript
version 1

// The declaration: names the script and decides where it draws.
study("EMA pair", overlay = true, precision = 2)

// Settings, one row each in the settings dialog.
fastLen = input(9, "Fast length", min = 1, max = 200)
slowLen = input(21, "Slow length", min = 2, max = 500)

// The body: runs once per bar, top to bottom.
fast = ema(close, fastLen)
slow = ema(close, slowLen)

if crossUp(fast, slow)
    signal("CROSS UP")

plot(fast, "Fast EMA", aqua)
plot(slow, "Slow EMA", orange)
```

Read it as a list of statements the engine (the part of OpenScript that runs a compiled script) runs on the oldest bar, then again on the next bar, and so on to the newest. That per-bar loop is the subject of [Execution model](/script/language/execution-model). This page is about the text itself.

## The four parts of a file

| Part | Required | Example | Where it goes |
|---|---|---|---|
| Version line | No, but always write it | `version 1` | The first line that is not blank and not a comment |
| Declaration | Yes, exactly one | `study("EMA pair", overlay = true)` | Directly under the version line |
| Limits line | No | `limits(loops = 5_000_000)` | Only immediately after the declaration |
| Body | Yes | everything else | Runs top to bottom, once per bar |

Comments and blank lines may appear anywhere, including above the version line.

## The version line

`version 1` states the language version the file was written for. It is a bare statement, not a function call, so the application running your script can read it with a one-line scan before it reads anything else.

The line is optional, and you should still write it. The language promises that a script which compiles under a version keeps compiling under every later release and keeps producing the same numbers: a later version may add keywords, functions, options and types, but never changes what an existing construct means. A file that says `version 1` is always read by the rules of version 1. A file without the line is compiled with the newest version the compiler knows, and the compiler reports warning [OS8003](/script/errors/warnings#os8003) with the line to add.

The version line must come first. A statement above it is error [OS1021](/script/errors/syntax#os1021):

```openscript
study("Too late")
version 1
```

## The declaration

The declaration names the script and says what kind of file it is.

| Declaration | Makes | Can place orders |
|---|---|---|
| `study("Name", ...)` | A study: plots, levels, fills, markers, tables and alerts | No |
| `strategy("Name", ...)` | A strategy: everything a study does, plus orders | Yes |

`strategy()` accepts every option `study()` accepts and adds the trading options (capital, quantity, costs, fills). The same file plots and trades, so the numbers on the chart and the numbers in the backtest are the same numbers.

Every file carries exactly one declaration. Write it as the first statement, directly under the version line, so a reader knows what the file is before reading anything else. A file with no declaration is error [OS2007](/script/errors/names-and-types#os2007), and a file with two is [OS2008](/script/errors/names-and-types#os2008):

```openscript
version 1
study("First")
study("Second")
```

Declaration options are read once, before the first bar, so each must be fixed by then: a literal, arithmetic over literals, or an `input()`. An option that depends on a bar's data is error [OS3003](/script/errors/arguments#os3003), because the legend and the settings dialog are built before bar 0. The options you reach for most often:

| Option | Default | Controls |
|---|---|---|
| `title` | required | The name in the legend and the indicator list. The first positional argument |
| `overlay` | `false` | `true` draws on the price chart, `false` gives the study its own pane |
| `precision` | `4` | Decimals on the axis and in the legend, a whole number from 0 to 10 |
| `range` | none | A fixed scale for the study's pane, such as `[0, 100]` for an oscillator |
| `format` | `"price"` | `"price"`, `"percent"` or `"volume"` axis formatting |
| `onUnconfirmed` | `false` | Allow signals, alerts and orders on a bar that is still forming |

[Declarations](/script/reference/declarations) lists every option of both declarations with its default.


## The limits line

The engine gives every script a budget of 2,000,000 loop iterations per bar, which almost every script stays far inside. `limits()` raises it for a script that genuinely needs more. Most scripts never write one.

This study compares every pair of closes in its window, which is `n * (n - 1) / 2` comparisons on each bar: about 5,000 at the default of 100, and about 4.5 million at the largest setting of 3,000. The larger settings would go over the default budget, so the script raises it.

```openscript
version 1
study("Trend score", precision = 2)
limits(loops = 5_000_000)

n = input(100, "Window", min = 10, max = 3000)

// +1 for each pair where the newer close is higher, -1 where it is lower.
score = 0.0
for i = 0 to n - 2
    for j = i + 1 to n - 1
        score += sign(close[i] - close[j])

// From -1 (every close lower than the one before it) to +1 (every close higher).
plot(score / (n * (n - 1) / 2), "Trend score", aqua)
```

A script that needs this many iterations does millions of operations on every bar, so expect it to be slow on a long chart.

| Rule | If you break it |
|---|---|
| It is optional and appears at most once | [OS3014](/script/errors/arguments#os3014) |
| It must be the statement immediately after the declaration | [OS3014](/script/errors/arguments#os3014) |
| Its arguments are literal numbers, not arithmetic and not inputs | [OS3015](/script/errors/arguments#os3015) |
| Its options are `loops` (loop iterations per bar) and `history` (how many bars of each series are kept) | [OS3002](/script/errors/arguments#os3002) for any other name |
| The application running the script may refuse a value larger than it will run | [OS5003](/script/errors/limits#os5003), naming the largest value it allows |

[Limits](/script/writing/limits) covers every budget the engine enforces.

## Statements

A statement ends at the end of its line, and a line holds at most one statement. There is no separator: a `;` is error [OS1007](/script/errors/syntax#os1007).

```openscript
fast = ema(close, 9); slow = ema(close, 21)
```

The body is made of these statements:

| Statement | Example | Covered in |
|---|---|---|
| Assignment | `fast = ema(close, 9)`, `total += volume` | [Variables and scope](/script/language/variables-and-scope) |
| Persistent declaration | `var upBars = 0` | [Persistence](/script/language/persistence) |
| `if`, `else if`, `else` | `if close > open` | [Control flow](/script/language/control-flow) |
| `for`, `while`, `break`, `continue` | `for i = 0 to 9` | [Control flow](/script/language/control-flow) |
| `switch` | `switch method` | [Control flow](/script/language/control-flow) |
| A call on its own | `signal("BUY")`, `plot(fast, "Fast")` | [Visuals](/script/visuals/overview) |
| Function declaration | `fn mid() => (high + low) / 2` | [User functions](/script/language/functions) |
| `return` | `return none` | [User functions](/script/language/functions) |

Statements run in source order, so a name must be assigned on a line above the one that reads it. Function declarations are the exception: a `fn` may sit anywhere at the top level of the file, including below the lines that call it.

## Blocks and indentation

A block is the group of lines that belongs to a header line: `if`, `else`, `for`, `while`, `case`, `default` or a multi-line `fn`. The block is every following line indented more deeply than the header, and it ends at the first line indented the same as the header or less. There are no braces and no `end` keyword.

```openscript
version 1
study("Strong up bars", overlay = true)

range14 = atr(14)

if close > open
    body = close - open

// Blank lines and comment lines do not end a block, wherever they sit.
    if body > range14
        signal("STRONG UP")
plot(ema(close, 20), "EMA 20", aqua)
```

The `plot` line is back at the left edge, so it is outside both blocks and runs on every bar.

The rules are mechanical:

| Rule | If you break it |
|---|---|
| Indent with spaces only. A tab in the leading whitespace is refused | [OS1002](/script/errors/syntax#os1002) |
| Every line of one block has exactly the same indentation, to the space | [OS1003](/script/errors/syntax#os1003) |
| A block is indented more deeply than its header | [OS1003](/script/errors/syntax#os1003) |
| A header has at least one indented line under it | [OS1010](/script/errors/syntax#os1010) |
| A line is not indented deeper unless a header opened a block | [OS1003](/script/errors/syntax#os1003) |
| Braces do not exist | [OS1001](/script/errors/syntax#os1001) |

Four spaces per level is the convention and the canonical layout (below), but any consistent amount is accepted. Tabs are refused because a tab's width is an editor setting, and a file whose meaning depended on it would change meaning when someone else opened it.

A blank line, or a line holding only a comment, carries no indentation at all. It never opens or closes a block, so the comment at the left edge in the example above does not end the `if` block.

A line indented differently from the lines around it, even by one space, is the error you will meet most:

```openscript
if close > open
    body = close - open
     signal("UP")
```

`else if` is two words on one line and does not add a level of indentation. Only `fn` has a single-line form, written after `=>`. Every other header takes its body on the next line, even a body of one statement:

```openscript
fn barRange() => high - low  // single-line function, no block

if crossUp(close, ema(close, 20))  // one statement, still its own block
    signal("ABOVE EMA")
```

## Comments

A comment starts at `//` and runs to the end of the line. A `//` inside a string is ordinary text.

```openscript
// A comment on its own line.
len = 14  // A comment after code.
plot(sma(close, len), "SMA // 14 bars", aqua)  // The // in the title is text.
```

There are no block comments: `/*` and `*/` are error [OS1026](/script/errors/syntax#os1026). To comment out a region, put `//` at the start of each line.

## Line continuation

A long statement continues onto the next line in three cases:

1. A `(` or `[` is still open.
2. The line ends with a binary operator (such as `+`, `and`, `>`), a comma, `?`, `:` or `=`.
3. The line ends with a backslash `\`.

```openscript
version 1
study("Ribbon mean", overlay = true, precision = 2)

// Case 2: each line ends with +.
ribbon = ema(close, 9) +
        ema(close, 21) +
        ema(close, 50)

// Case 1: the ( of plot is still open.
plot(ribbon / 3, "Ribbon mean",
        color = aqua,
        width = 2)

// Case 3: a backslash at the end of the line.
message = "Close " + \
        text(close, 2)

t = table("Last close", 1, 1)
if bar.isLast
    cell(t, 0, 0, message)
```

A continuation line must be indented more deeply than the line its statement began on, so it can never be mistaken for a new statement. One indented the same or less is error [OS1028](/script/errors/syntax#os1028). Blank lines and comment lines inside a continuation are ignored, so you can comment each argument of a long call on its own line.

## Names

A name (also called an identifier) starts with an ASCII letter or an underscore and continues with ASCII letters, digits and underscores. Names are case sensitive, so `fastLen` and `fastlen` are two names.

| Written | Result |
|---|---|
| `fastLength`, `_scratch`, `ema9` | Legal names |
| `2fast` | [OS1029](/script/errors/syntax#os1029): a name cannot start with a digit |
| `längd` | [OS1001](/script/errors/syntax#os1001): names are ASCII |
| `step`, `color`, `series` as a variable | [OS1019](/script/errors/syntax#os1019): a reserved word |

The convention, not enforced, is `camelCase` for names and functions and `UPPER_SNAKE` for values you treat as constants.

Reserved words cannot be names, but they can be named-argument labels. `plot(x, "X", color = aqua)` is correct: the label `color` is matched against the call's parameters and never looked up as a variable. [Keywords](/script/reference/keywords) lists every reserved word.

## Source text

A file is UTF-8 text. A byte order mark at the start is ignored, and Windows line endings are normalised, so a file compiles the same on every machine.

Outside string literals, only ASCII letters, digits, spaces, newlines and the language's punctuation are legal. A non-breaking space or a curly quotation mark pasted from a document is error [OS1001](/script/errors/syntax#os1001), reported at that character with the plain character to use instead. Inside a string any Unicode text is fine, so a label can read `"₹ per lot"`.

## The canonical layout

A file has one canonical layout: the spacing every example in this documentation uses. It moves whitespace and nothing else, so laying a file out this way never changes what it means.

| Rule | Canonical form |
|---|---|
| Block indentation | Four spaces per level |
| A continuation line | Eight spaces past the line that began the statement |
| Between tokens | One space: `a + b`, `x = -1`. Columns lined up by hand are not kept |
| No space | Inside brackets, before a comma, around a dot, before a call's or an index's bracket: `f(a, b)`, `chart.symbol`, `close[1]` |
| A comment after code | Two spaces clear of the code |
| Blank lines | At most one in a row, and the file ends with one line ending |

Line breaks inside a statement stay where you wrote them. The `openalgo-script` library includes a formatter that writes this layout, for applications built on it. The /trading editor does not reformat your code in this release, so keep to the layout by hand. [The editor](/script/getting-started/the-editor) describes what the /trading editor does today, and the [Style guide](/script/writing/style-guide) covers naming and layout conventions.

## Errors you may meet

| Code | Means | Fix |
|---|---|---|
| [OS1001](/script/errors/syntax#os1001) | A character the language does not use | Retype it as plain ASCII, or use the word the message names (`not`, `and`, `pow`) |
| [OS1002](/script/errors/syntax#os1002) | A tab in indentation | Indent with spaces |
| [OS1003](/script/errors/syntax#os1003) | Indentation does not match the block | Make every line of a block match exactly |
| [OS1007](/script/errors/syntax#os1007) | A `;` | Put the second statement on its own line |
| [OS1010](/script/errors/syntax#os1010) | A header with no body | Indent the body under the header |
| [OS1021](/script/errors/syntax#os1021) | The version line is not first | Move it to the top |
| [OS1026](/script/errors/syntax#os1026) | A block comment | Use `//` on each line |
| [OS1028](/script/errors/syntax#os1028) | A continuation line is not indented | Indent it past the statement's first line |
| [OS2007](/script/errors/names-and-types#os2007) | No declaration | Add `study("Name")` under the version line |
| [OS2008](/script/errors/names-and-types#os2008) | Two declarations | Keep one |
| [OS3014](/script/errors/arguments#os3014) | `limits()` in the wrong place, or written twice | Put one directly under the declaration |
| [OS8003](/script/errors/warnings#os8003) | No version line | Add `version 1` as the first line |

**Related.** [Execution model](/script/language/execution-model), [Variables and scope](/script/language/variables-and-scope), [Control flow](/script/language/control-flow), [Declarations](/script/reference/declarations), [Keywords](/script/reference/keywords), [Syntax errors](/script/errors/syntax)


## Execution model

Source: https://openalgo.in/script/language/execution-model

An OpenScript file has no main function, no event handler and no entry point. The file itself is the body of a loop that the engine (the part of OpenScript that runs a compiled script) runs over every bar of the chart, oldest first. This page explains that loop: what is computed afresh on every bar, what is carried forward, what is fixed before the first bar, and what the chart receives at the end. Almost every surprise in a per-bar language is a surprise about when something runs, so the rest of the documentation leans on this page.

## The file is the body of a loop

```text
for each bar in the data, oldest first:
    run every top-level statement of the file, first line to last
```

That is the whole model. A file of forty lines is forty statements the engine runs for bar 0, again for bar 1, again for bar 2, all the way to the newest bar, and then again on the newest bar each time it updates.

```openscript
version 1
study("Trace")

n = bar.index
plot(n, "Bar index")
```

On a 5-minute chart of an NSE stock, one session from 09:15 to 15:30 IST is 75 bars, and a year of sessions is about eighteen thousand. The line `n = bar.index` runs once for each of them. You never write this loop and you cannot see it: it is the shape of the language.

## Compiled once, run once per bar

Two things happen at two different times.

```text
source text
   -> checked by the compiler -> compiled program      once, before bar 0
   -> for each bar: run the program top to bottom      once per bar
```

**Compilation happens once.** Names are resolved, types are checked, the set of plotted columns is fixed, the settings dialog is built from the `input()` calls, and the whole script becomes a compiled program: plain data, a list of instructions the engine walks.

**Execution happens once per bar.** Every instruction runs again for every bar.

A few calls live in both worlds. `plot()`, `plotCandles()`, `fill()`, `level()`, `table()` and `input()` declare the fixed shape of the study: how many columns it has, what they are called, what the legend shows and which rows the settings dialog has. That shape is read once, when the script is compiled. Their **arguments** are still evaluated on every bar, which is how one `plot` statement produces one value per bar.

This is why those calls must sit at the top level of the file, outside every block. A `plot`, `plotCandles`, `fill`, `level` or `table` inside an `if` is error [OS3006](/script/errors/arguments#os3006), and an `input()` inside a block is [OS3007](/script/errors/arguments#os3007), because the chart cannot build a legend for a column that might not exist:

```openscript
trending = ema(close, 20) > ema(close, 50)
if trending
    plot(ema(close, 20), "EMA 20", aqua)
```

The fix is always the same: keep the plot at the top level and plot the absent value `none` on the bars you want hidden. An absent value on a plot is a gap in the line, never a zero.

```openscript
version 1
study("EMA while trending", overlay = true)

ema20 = ema(close, 20)
trending = ema20 > ema(close, 50)

plot(trending ? ema20 : none, "EMA 20", aqua)
```

Everything else may appear inside any block: `signal()`, `alert()`, `background()`, `barColor()`, `cell()`, `print()`, the `draw.*` functions and every order function. They are per-bar events or per-bar paint, so they belong inside the per-bar logic.

## Bars and the bar facts

`bar.index` is the zero-based position of the bar being run within the data the engine was given. The oldest bar loaded is 0. The `bar` namespace holds the other facts about the current bar:

| Name | Type | Means |
|---|---|---|
| `bar.index` | `series number` | Position of this bar, the oldest is 0 |
| `bar.count` | `series number` | `bar.index + 1`, the bars seen so far |
| `bar.isFirst` | `series bool` | `bar.index == 0` |
| `bar.isLast` | `series bool` | This is the newest bar in the data |
| `bar.isConfirmed` | `series bool` | This bar's interval has elapsed |
| `bar.isRealtime` | `series bool` | Real-time updates are driving this bar |
| `bar.isNew` | `series bool` | The last update added a bar rather than replacing one |
| `bar.updates` | `series number` | How many times this bar has been run, counting from 1 |

A bar index is a position in the data you loaded, not a permanent address for a moment in market history. Scroll far enough left and the chart loads older bars: every index then shifts by the number of bars that arrived. Subtracting two indices from the same run is safe, because both come from the same numbering, but storing an index and comparing it later is not. Store `time` for anything that has to survive more history arriving: a bar's opening instant does not move. That is also why the `draw.*` objects are anchored to a time and a price rather than to an index.

```openscript
version 1
study("Minutes since the highest high", precision = 0)

var peak = none
var peakTime = none

if isNone(peak) or high > peak
    peak = high
    peakTime = time

// time is milliseconds since 1970 in UTC, so a difference is milliseconds.
plot((time - peakTime) / 60000, "Minutes since the highest high", aqua)
```

## Recomputed, remembered, fixed

Every name in a script has one of these lifetimes:

| Name | Lives for | Set by | Readable with `[]` |
|---|---|---|---|
| A plain top-level name | One bar | Its assignment, on every bar | Yes |
| A name first assigned inside a block | One bar, inside that block only | Its assignment, on the bars the block runs | No, [OS2004](/script/errors/names-and-types#os2004) |
| A `var` | The whole run | Its initial value once, then any assignment | Yes, when it is at the top level |
| A `live var` | The whole run, and it ignores rollback (below) | The same | Yes, when it is at the top level |
| A name holding an `input()` | The whole run, one value | The settings dialog, before bar 0 | Yes, when it is at the top level |

A line with `var` on it is remembered. Every other assignment is recomputed from nothing on each bar. The previous value of a recomputed name is not lost, because the history operator `[1]` can read it, but it is not the starting point for this bar:

```openscript
tally = tally + 1
```

`tally` does not exist yet on this bar when the right-hand side is read, so this is error [OS2001](/script/errors/names-and-types#os2001). Reading the previous bar instead compiles, and is absent for ever:

```openscript
tally = 0
tally = tally[1] + 1  // absent on every bar
plot(tally, "Never draws")
```

On bar 0 there is no previous bar, so `tally[1]` is absent and the sum is absent. On bar 1 it reads bar 0's absent value, and so on. Keeping a running count is what `var` is for, and [Persistence](/script/language/persistence) is the page about it.

## Five bars, line by line

Here is a complete script, followed by every value it holds on its first five bars.

```openscript
version 1
study("Up share", precision = 2)

// Recomputed from the bar's own data every bar.
delta = close - close[1]

// Remembered. The initial value is set once, on bar 0.
var up = 0

// An absent condition takes the false branch, which is what happens on bar 0.
if delta > 0
    up = up + 1

// Recomputed, but from a remembered value.
ratio = up / bar.count

plot(delta, "Change", aqua)
plot(ratio, "Up share", orange)
```

Given five bars that close at 100, 102, 101, 104 and 104:

| Bar | `close` | `close[1]` | `delta` | `up` entering | Branch | `up` leaving | `bar.count` | `ratio` |
|---|---|---|---|---|---|---|---|---|
| 0 | 100 | absent | absent | 0, just set | not taken | 0 | 1 | 0.00 |
| 1 | 102 | 100 | 2 | 0 | taken | 1 | 2 | 0.50 |
| 2 | 101 | 102 | -1 | 1 | not taken | 1 | 3 | 0.33 |
| 3 | 104 | 101 | 3 | 1 | taken | 2 | 4 | 0.50 |
| 4 | 104 | 104 | 0 | 2 | not taken | 2 | 5 | 0.40 |

Each column shows a rule rather than an accident:

- **Bar 0 has no previous bar.** `close[1]` is absent, not 100 and not zero. Nothing is clamped to the start of history. See [Bars and history](/script/language/bars-and-history).
- **Absence propagates through arithmetic.** One absent operand makes `delta` absent on bar 0, so the "Change" line starts at bar 1.
- **An absent condition takes the false branch.** On bar 0, `delta > 0` is itself absent, and the `if` skips its block because execution has to go somewhere. See [Absent values](/script/language/absent-values).
- **`up` is set exactly once.** The `var` line is reached on every bar, but its initial value is applied only on the first bar that reaches it.
- **`delta` is computed from scratch every bar; `up` is not.** On bar 3, `up` enters holding 1 because bar 2 left it there.
- **Zero is not false.** On bar 4 the change is exactly 0 and `0 > 0` is false, so the branch is not taken. A condition is always a comparison or a `bool`, never a number.

## Source order is execution order

Inside the file, a name must be assigned on a line above the one that reads it. Reading it earlier is error [OS2001](/script/errors/names-and-types#os2001), not an absent value:

```openscript
plot(slow, "Slow", orange)
slow = ema(close, 21)
```

Functions are the exception. A function declaration is not a per-bar statement, and the compiler collects every declaration before it checks any body, so a `fn` may be called above the line that declares it:

```openscript
plot(smoothed(close), "Smoothed", aqua)

fn smoothed(src) => sma(src, 9)
```

Order also decides what a `var` holds at a given line, and this is an idiom worth learning. A `var` read **before** the line that reassigns it still holds the previous bar's value:

```openscript
version 1
study("Trailing stop", overlay = true, precision = 2)

raw = low - 2 * atr(14)

var trail = none

// At this line trail still holds what the previous bar left in it.
prevTrail = trail

// Ratchet up while the previous bar closed above the stop, and start again
// from raw once it closed below.
trail = close[1] > orElse(prevTrail, raw) ? max(raw, orElse(prevTrail, raw)) : raw

plot(trail, "Trailing stop", lime, width = 2)
```

That is how a trailing stop reads its own previous value. Moving the `prevTrail` line below the assignment would change the meaning of the script, and the compiler cannot warn about it, because both orders are legal and both are useful somewhere.

## Blocks run inside the bar

A block is not a separate pass over the data. An `if` body is part of this bar's run, and it runs or does not run on this bar alone. The same holds for every `for`, `while` and `switch`.

Two scope rules keep this predictable. An assignment to a name that already exists outside the block updates that name. A name first assigned inside a block does not exist outside it. So you never have to ask which of two variables a line writes to, and a `var` or a function parameter that reuses a name from outside is error [OS2002](/script/errors/names-and-types#os2002). [Variables and scope](/script/language/variables-and-scope) covers both rules in full.

## A call keeps state, per call site

Some library functions remember something between bars. `ema()` needs the previous bar's average to produce this bar's, and so do `rma()`, `atr()`, `vwap()`, `cum()` and every function that reads a window of past bars. The reference marks each of these "Keeps state".

**State belongs to the call site, not to the function.** A call site is one place in the source where a function is called. Two calls written in two places are two independent pieces of state, and that is what lets one helper be used twice:

```openscript
version 1
study("Bars since", precision = 0)

fn barsSinceTrue(cond) =>
    var n = none
    if cond
        n = 0
    else if not isNone(n)
        n = n + 1
    n

sinceUp = barsSinceTrue(close > open)  // its own counter
sinceHigh = barsSinceTrue(high > high[1])  // a separate counter

plot(sinceUp, "Bars since an up close", aqua)
plot(sinceHigh, "Bars since a higher high", orange)
```

Three consequences follow:

- A call inside a loop is one call site, so every iteration shares its one piece of state. For state per iteration, keep an array and index it. The compiler warns about a stateful call inside a loop with [OS8001](/script/errors/warnings#os8001).
- A function may not call itself, directly or through other functions. That is error [OS2005](/script/errors/names-and-types#os2005); write a loop.
- **A call site that does not run on a bar leaves its value absent for that bar, and its state does not advance.**

The last rule is the one that bites. A stateful call inside a branch only advances on the bars the branch runs, so the average is built from a subset of bars that nobody meant. The compiler reports warning [OS8001](/script/errors/warnings#os8001):

```openscript
trending = close > ema(close, 50)
if trending
    e = ema(close, 20)  // OS8001: advances only on trending bars
    barColor(close > e ? lime : red)
```

Compute the value on every bar at the top level, and use the result inside the branch:

```openscript
version 1
study("Colour while trending", overlay = true)

e = ema(close, 20)
trending = close > ema(close, 50)

if trending
    barColor(close > e ? lime : red)
```

## Loops run inside one bar

`for` and `while` run to completion inside a single bar. A loop is not a way to move across bars: the bar loop already does that. A loop is for walking an array, or a fixed window of history within this bar.

```openscript
version 1
study("Window mean, by hand", overlay = true, precision = 2)

len = input(20, "Length", min = 1, max = 500)

total = 0.0
for i = 0 to len - 1
    total += close[i]

plot(total / len, "Mean", aqua)
```

That script is correct, and `sma()` does the same work with a running total and an exact, documented warmup (the number of bars a function needs before it has a value; see [Warmup](/script/language/warmup)). The loop version is also absent for the first `len - 1` bars, because `close[i]` past the start of history is absent and absence propagates through `+=`.

Every loop iteration counts against a per-bar budget: 2,000,000 iterations summed over every loop run during one bar. Going over is error [OS5001](/script/errors/limits#os5001), which stops the script at that bar (in /trading a notice appears when the study is added, and the Objects panel shows its status as Error) rather than quietly leaving the loop with a plausible wrong number. The budget resets on every bar, so a long history is never itself a reason to fail, and a script that needs more raises it with `limits(loops = ...)` under its declaration. [Control flow](/script/language/control-flow) covers the loop forms and the budget.

## The newest bar runs more than once

While the market is open, the newest bar is still forming. The engine runs your script on it again every time it updates, and `bar.updates` counts those runs. Two rules make this safe:

- **Rollback.** Before each rerun of the forming bar, every `var` (and every array a `var` holds) is restored to what it held at the end of the previous bar. Running the forming bar ten times gives the same answer as running it once, so a counter counts bars, not updates, and a chart agrees with a backtest over the same data. A `live var` opts out of rollback for the rare script that means to count updates.
- **Events wait for the close.** `signal()`, `alert()` and orders on a bar that is still forming are held until the bar is confirmed. If the condition is no longer true when the bar closes, they never happen. A declaration opts in to acting earlier with `onUnconfirmed = true`.

[Realtime and confirmation](/script/language/realtime-and-confirmation) covers the forming bar in full, and [Persistence](/script/language/persistence) covers `var` and `live var`.

## Same bars, same numbers

The same compiled program over the same bars produces the same output on every engine, every time. That promise is what makes a backtest comparable with the chart, and the chart comparable with the same strategy when it is deployed, whether in sandbox trading (analyzer mode in OpenAlgo) or with real orders. To keep it:

- All arithmetic is 64-bit floating point, rounded to nearest with ties to even, in the order the source writes it. No engine may reorder operations or fuse two of them into one.
- Arrays are walked in index order. There is no unordered collection.
- There is no randomness anywhere in the language, and no reading of the clock during a bar except through `chart.now()`, whose value the application running the script supplies.
- Upper and lower case conversion and month names do not depend on the machine's locale (its language and region settings).

If two runs of your script disagree, the data disagreed.

## What reaches the chart

At the end of each bar the engine has one value for every plotted column, plus whatever markers, colours, table cells and drawings the bar asked for. The chart receives the study as one description: plotted columns, fills, levels, markers, the table, drawings, the pane background, bar colours, a fixed pane range and the settings.

Here is one such description drawn: a [HalfTrend](/script/getting-started/example-scripts#halftrend) study on a BHEL 15 minute chart, which hands the chart four plotted columns (the trend level twice, once per direction, and a faint channel edge for each), two fills and a marker on each flip:


Two rules about that hand-off are worth carrying with you:

- **An absent value is a gap, never a zero.** A plot breaks its line, a fill stops, a bar colour leaves the bar its own colour, a table cell is blank.
- **Signals, alerts and orders wait for the bar to close**, unless the declaration says otherwise.

## Four mistakes this model causes

| Mistake | What happens | Fix |
|---|---|---|
| Expecting a total to accumulate without `var` | `total = total + x` is [OS2001](/script/errors/names-and-types#os2001); `total = total[1] + x` is absent for ever | `var total = 0`, then `total += x` |
| Computing an indicator where it is used | A stateful call inside a branch advances only on some bars ([OS8001](/script/errors/warnings#os8001)) | Compute at the top level, branch on the result |
| Wrapping a plot in a condition | [OS3006](/script/errors/arguments#os3006) | Plot `none` on the bars to hide |
| Storing a bar index and trusting it later | Indices shift when older history loads | Store `time` instead |

**Related.** [Script structure](/script/language/script-structure), [Bars and history](/script/language/bars-and-history), [Persistence](/script/language/persistence), [Warmup](/script/language/warmup), [Realtime and confirmation](/script/language/realtime-and-confirmation), [Variables and scope](/script/language/variables-and-scope), [User functions](/script/language/functions)


## Types and values

Source: https://openalgo.in/script/language/types-and-values

Every value in an OpenScript script has a type, and the compiler works each one out for you: there are no type declarations to write for ordinary names. This page covers the small, closed set of types, the difference between a series (one value per bar) and a single value (one value for the whole run), what an `input()` gives you, the few explicit conversions, and the conversions the language refuses. Knowing these rules lets you read a type error (codes starting OS20) and see the fix straight away.

## A first look

```openscript
version 1
study("Types at a glance", precision = 2)

len = input(14, "RSI length", min = 2, max = 100)  // number, fixed for the run
r = rsi(close, len)  // series number
hot = r > 70  // series bool
mood = hot ? "overbought" : "normal"  // series string
tint = hot ? red : purple  // series color
bands = [30.0, 50.0, 70.0]  // array<number>
t = table("RSI", 1, 1)  // table, a runtime object

plot(r, "RSI", tint, width = 2)
level(element(bands, 2), "Upper", gray)
level(element(bands, 0), "Lower", gray)

if bar.isLast
    cell(t, 0, 0, "RSI " + text(r, 1) + " is " + mood)
```

No line names a type, and every name still has exactly one. The compiler infers it from the first assignment and holds the name to it for the rest of the file.

## The types

| Type | Holds | Written as |
|---|---|---|
| `number` | One finite real number | `42`, `3.14`, `0xFF`, `1_000_000` |
| `string` | Text, as Unicode code points | `"BUY"`, `'BUY'` |
| `bool` | `true` or `false` | `true`, `false` |
| `color` | Red, green, blue and alpha (opacity) | `aqua`, `#ff8800`, `rgb(255, 136, 0)` |
| `none` | The absent value | `none` |
| `series T` | One `T` per bar | No literal: `close`, `ema(close, 9)` |
| `array<T>` | An ordered, resizable list of `T` | `[1, 2, 3]` |

Two facts about this table are worth reading twice.

**`none` belongs to every type.** A `series number` may hold `none` on any bar, and so may a `string` or a `color`. A name that is a number on most bars and absent on a few is an ordinary `series number`, not a mixed type. [Absent values](/script/language/absent-values) covers `none` in full.

**`series T` is the same `T`, once per bar.** It is not a separate family of types you convert to and from. The language moves between the two automatically in the one direction that loses nothing, described under [Broadcast](#broadcast-the-one-automatic-conversion) below.

The library also returns two kinds of handle that are not ordinary values: declaration handles from `plot()`, `fill()` and `level()`, and runtime objects from the `draw.*` functions and `table()`. They are covered at the end of this page.

## Numbers

A `number` is a 64-bit floating point value that is always finite. There is one numeric type: no separate integer, decimal, price or quantity type. A length, a bar count, a lot size and a price are all `number`, so there are no conversions between them to get wrong.

```openscript
a = 42
b = 3.14
c = .5  // a leading digit is optional
d = 1_000_000  // underscores group digits and mean nothing
e = 2.5e-4
f = 0xFF  // hexadecimal, 255
g = 010  // ten: there is no octal form
plot(a + b + c + d + e + f + g, "Sum")
```

A negative number is the minus operator applied to a literal, so `-2` works anywhere an expression does, including an argument list.

**Where a whole number is required, a fraction is refused, never truncated.** A lookback length, a history offset and an array index must be whole numbers. A length of 14.5 stops the script on the bar where it happens, with an error, rather than being rounded, because a length of 14.5 is a bug and rounding it would hide the bug. Say which way you want it rounded, where a reader can see it:

```openscript
version 1
study("Half length", overlay = true, precision = 2)

len = input(21, "Slow length", min = 2, max = 500)

// len / 2 is 10.5 when len is 21, and sma refuses a length of 10.5.
halfLen = floor(len / 2)

plot(sma(close, len), "Slow", orange, width = 2)
plot(sma(close, halfLen), "Fast", aqua, width = 2)
```

| Where a fraction arrives | Error, raised on the bar it happens |
|---|---|
| A library length, such as `sma(close, 10.5)` | [OS4003](/script/errors/runtime#os4003) |
| A history offset, such as `close[1.5]` | [OS4001](/script/errors/runtime#os4001) |
| An array index, such as `levels[1.5]` | [OS4004](/script/errors/runtime#os4004) |

These are errors in a running script, not in the text, so the compiler accepts `sma(close, 10.5)` and the error appears when the script runs.

**Infinity and not-a-number do not exist.** An operation whose real answer does not exist or is not finite gives `none` instead: `1 / 0`, `0 / 0`, `sqrt(-1)` and `log(0)` are all absent. The line on the chart breaks rather than spiking to a value it cannot scale.

**Time is a number.** `time` is the bar's opening instant in milliseconds since 1 January 1970, UTC. There is no separate time type, so an elapsed time is plain subtraction, and fifteen minutes is `15 * 60000`. The `date.*` functions read calendar fields from it; see [Sessions and time](/script/data/sessions-and-time).

```openscript
version 1
study("Minutes between bars", precision = 0)

// time is in milliseconds, so a difference divided by 60000 is minutes.
gap = (time - time[1]) / 60000

plot(gap, "Minutes since the previous bar", aqua, style = "histogram")
```

On a 5-minute NSE chart the bars inside a session read 5, and the first bar of each session stands tall with the overnight gap, taller still after a weekend or a holiday.

## Strings

A string literal uses double quotes or single quotes, and the two mean exactly the same thing. Two delimiters exist so that a string containing one kind of quote needs no escapes.

```openscript
t = table("Strings", 3, 1)

a = "BUY"
b = 'He said "exit"'
c = "Premium in ₹"

if bar.isLast
    cell(t, 0, 0, a)
    cell(t, 1, 0, b)
    cell(t, 2, 0, c)
```

Inside a string, a backslash starts an escape sequence: `\\` (a backslash), `\"`, `\'`, `\n` (new line), `\t` (tab), `\r`, `\0` and `\uXXXX` (a character by its code, with exactly four hexadecimal digits). Any other backslash sequence is [OS1005](/script/errors/syntax#os1005), and a string that runs to the end of the line without its closing quote is [OS1004](/script/errors/syntax#os1004). A string literal cannot span two lines; join two with `+`.

`+` joins two strings and does nothing else. `"count: " + 5` is an error, not `"count: 5"`: convert the number first with `text()`.

Strings compare with `<`, `<=`, `>` and `>=` by Unicode code point, so every uppercase ASCII letter sorts before every lowercase one. That order is the same on every machine and in every locale. It is not a dictionary order.

The `str.*` functions search, split, pad and format strings; see [Strings](/script/reference/string).

## Booleans

`true` and `false` are of type `bool`, and they are not numbers. `0` is not false, `1` is not true and `""` is not false. A condition in an `if`, a `while` or a ternary must be a `bool` (or `none`), and anything else is error [OS2011](/script/errors/names-and-types#os2011):

```openscript
hits = count(close > open, 10)
if hits
    signal("SOME UP BARS")
```

Write the test you mean:

```openscript
hits = count(close > open, 10)
if hits > 0
    signal("SOME UP BARS")
```

To turn a condition into a number, say so with a ternary: `cond ? 1 : 0`. To count how many of the last 50 bars a condition held on, the library already has `count()`.

## Colors

A colour is written as one of the nineteen named colours or as a hex literal, and built or changed by a small set of functions. Alpha is a colour's opacity: fully opaque hides what is behind it, and zero is invisible.

| Form | Example | Means |
|---|---|---|
| A named colour | `aqua` | One of nineteen bare names, fully opaque |
| Hex, 24 bit | `#ff8800` | Red, green and blue |
| Hex with alpha | `#ff880080` | The same, with an alpha byte |
| `rgb()` | `rgb(255, 136, 0)` | Channels 0 to 255, opaque |
| `rgba()` | `rgba(255, 136, 0, 0.5)` | The same, with alpha 0 to 1 |
| `fade()` | `fade(aqua, 88)` | The colour at 88 percent **transparency** |
| `withAlpha()` | `withAlpha(aqua, 0.12)` | The colour at an **opacity** of 0.12 |
| `mix()` | `mix(red, lime, 0.5)` | A blend: weight 0 gives the first, 1 the second |

The named colours are `aqua`, `black`, `blue`, `brown`, `fuchsia`, `gray`, `green`, `lime`, `maroon`, `navy`, `olive`, `orange`, `pink`, `purple`, `red`, `silver`, `teal`, `white` and `yellow`. They are bare names with no prefix.

> **`fade` and `withAlpha` run in opposite directions. `fade(aqua, 88)` is nearly invisible, and `withAlpha(aqua, 0.88)` is nearly solid.**

Two colours are equal when all four channels match, so `#ff8800 == rgb(255, 136, 0)` is `true`. [Colors](/script/visuals/colors) covers gradients and colour per bar.

## Arrays

An `array<T>` is ordered, mutable, resizable and holds elements of one type, which is what lets `size()`, `avg()` and `sort()` each mean one thing.

```openscript
version 1
study("Rolling window", precision = 2)

length = input(20, "Window", min = 2, max = 500)

var window: array<number> = []

push(window, close)
if size(window) > length
    shift(window)

// For the first length - 1 bars the window is still filling, so this averages
// fewer closes than length. sma(close, length) would be absent there instead.
plot(avg(window), "Rolling mean", aqua, width = 2)
```

Four rules carry most of the surprises:

| Rule | Detail |
|---|---|
| An empty literal needs its element type | `[]` takes its type from an annotation (`var hits: array<number> = []`) or from the first `push`, `unshift`, `insert` or `set` into it. With neither it is [OS2015](/script/errors/names-and-types#os2015) |
| A literal cannot mix types | `["RSI", 14]` is [OS2013](/script/errors/names-and-types#os2013). Keep two arrays side by side instead |
| An array is a reference | `b = a` gives two names for one array. `copy()` makes an independent one. `==` asks whether two names are the same array; `arrayEqual()` compares contents |
| An index outside `0` to `size - 1` is an error | [OS4004](/script/errors/runtime#os4004), because the script chose the array's extent |

That last rule is the opposite of history. Reading past the start of the data with `close[500]` gives `none`, because that value never existed. Reading past the end of an array is an error, because the script asked for something it never created. [Collections](/script/language/collections) covers every array operation.

## Series and single values

This is the distinction that matters most.

A **series** is the per-bar history of a value. `series number` is one number per bar and `series bool` one boolean per bar. Reading a series bare gives the value on the bar being run, and the history operator `[n]` gives the value `n` bars back.

```openscript
a = close  // this bar's close
b = close[1]  // the previous bar's close
c = close[0]  // the same as close
plot(a - b + c, "Example")
```

A **single value** is one value for the whole run. `14` is a single value. So are `chart.tickSize`, `chart.lotSize` and the result of `input(14, "Length")`.

| Ask | If yes | Examples |
|---|---|---|
| Does it change from bar to bar? | It is a series | `close`, `ema(close, 9)`, `bar.index`, `session.isFirstBar` |
| Was it fixed before bar 0? | It is a single value | `input(14, "Length")`, `chart.tickSize`, `3.14` |
| Is it a name at the top level of the file? | It accepts `[]` either way | `len = 14` allows `len[1]`: absent on bar 0, which has no previous bar, and 14 after it |

### What an input gives you

An `input()` is a single value: the user sets it in the settings dialog before the first bar, and it stays the same on every bar of the run. The one exception is a source input, `input(close, "Source")`, which returns a `series number`, because what the user picks (the close, the high, `hlc3` and so on) is itself a series.

```openscript
version 1
study("Inputs and their types", overlay = true, precision = 2)

len = input(20, "Length", min = 2, max = 500)  // number
src = input(close, "Source")  // series number

plot(sma(src, len), "Average of the chosen source", aqua)
```


### Which values have history

A value accepts `[]` in exactly four cases:

1. It is a built-in series: `open`, `high`, `low`, `close`, `volume`, `time`, `hl2`, `hlc3`, `ohlc4`, `hlcc4`, and the per-bar facts in the `bar` and `session` namespaces.
2. It is a name assigned at the **top level of the file**.
3. It is a call to a function that returns a series, such as `ema(close, 9)[1]`.
4. It is a parameter of a user function that receives a series. Then `[]` reads the history of whatever the caller passed.

`[]` on anything else is error [OS2004](/script/errors/names-and-types#os2004). The instrument facts in `chart.*` are the clearest case: they cannot differ from bar to bar, so they carry no history, and `chart.tickSize[1]` is OS2004. A name first assigned inside a block has no history either:

```openscript
if close > open
    body = close - open
    prevBody = body[1]
```

Give the value a name at the top level and read that name's history. Hide the bars you do not want by plotting `none`, rather than by moving the plot into an `if`:

```openscript
version 1
study("Body history", precision = 2)

trending = ema(close, 20) > ema(close, 50)
body = close - open  // top level, so it has history

plot(body, "Body", fade(aqua, 60), style = "histogram")
plot(trending ? body[1] : none, "Previous body while trending", aqua)
```

History is kept only for names the script asked for by name because keeping it costs memory on every bar. A script that kept every temporary inside every loop could not run fifty thousand bars in a browser tab. [Bars and history](/script/language/bars-and-history) covers the history operator in full.

### Broadcast, the one automatic conversion

A single value used where a series is expected is **broadcast**: it is treated as that same value on every bar. The reverse also holds: a series used where a single value is expected means this bar's value.

```openscript
len = input(21, "Length")
plot(ema(close, 9), "EMA 9", aqua)  // 9 is broadcast to every bar
plot(ema(close, len), "EMA len", orange)  // an input works the same way
```

Broadcast is the only automatic conversion in the language, and it changes no value. Everything else is an explicit call.

When you pass an expression to a series parameter of your own function, the engine keeps that expression's per-bar values for that call, so `[]` inside the function reads real history:

```openscript
version 1
study("Change of source", precision = 2)

fn change2(src) => src - src[1]

// src[1] inside the function is the previous bar's hlc3.
plot(change2(hlc3), "Change in typical price", aqua)
```

### Several results in one array

A library function with more than one output returns an `array<number>` holding this bar's outputs in a documented order: `macd()` gives `[macd, signal, histogram]` and `bollinger()` gives `[basis, upper, lower]`. The array is never absent and never changes length; each element is absent until its own warmup ends.

```openscript
version 1
study("MACD", precision = 4)

m = macd(close, 12, 26, 9)

plot(m[0], "MACD", aqua, width = 2)
plot(m[1], "Signal", orange, width = 2)
plot(m[2], "Histogram", gray, style = "histogram")
```

Here `m[0]` is element access, not history, because `m` is an array. The compiler always knows which from the type. Where a reader could doubt it, write the explicit form: `element()` always reads an element of an array, and `history()` always reads a series some bars back.

## Conversions

There are four conversions, and each is a call.

| Call | Takes | Gives | Notes |
|---|---|---|---|
| `text(x)` | Any value | `string` | `text(none)` is the string `"none"` |
| `text(x, decimals)` | `number` | `string` | Fixed decimals, halves rounded away from zero: `text(2.5, 0)` is `"3"`. Absent when `x` is absent |
| `toNumber(s)` | `string` | `number` | `none` when the string does not parse |
| `toBool(x)` | `bool` or `none` | `bool` | `none` becomes `false`; a bool stays as it is |

The reference entries are `text()`, `toNumber()` and `toBool()`.

```openscript
version 1
study("Formatted readings", precision = 2)

r = rsi(close, 14)
a = atr(14)
t = table("Readings", 1, 1)

// text(x, decimals) passes an absent value on, and + then makes the whole
// message absent, so the guard sits in front of the message.
message = isNone(r) ? "warming up" : "RSI " + text(r, 1) + ", ATR " + text(a, 2)

plot(r, "RSI", purple, width = 2)

if bar.isLast
    cell(t, 0, 0, message)
```

`toNumber` is the one conversion that can fail, and it fails the way the rest of the language does, by returning `none`: `toNumber("12.5")` is `12.5` and `toNumber("12.5%")` is absent. Test the result with `isNone()` before relying on it.

`toBool` exists to turn a possibly absent condition into a definite one. It is not a way to read a number as true or false: do not pass it a number (`toBool(1)` gives `false`). Write `n != 0` for that.

The two are spelled `toNumber` and `toBool` because `number` and `bool` are reserved words, and a call must start with a name. Writing `number("12.5")` is [OS1019](/script/errors/syntax#os1019).

## Conversions the language refuses

There is no implicit conversion between any two types, in any direction.

| You write | What happens | Write instead |
|---|---|---|
| `1 + true` | [OS2003](/script/errors/names-and-types#os2003): `number` and `bool` | `1 + (flag ? 1 : 0)` |
| `"count: " + 5` | [OS2003](/script/errors/names-and-types#os2003): `string` and `number` | `"count: " + text(5)` |
| `if hits` | [OS2011](/script/errors/names-and-types#os2011): a condition must be a `bool` | `if hits > 0` |
| `up ? 1 : "down"` | [OS2012](/script/errors/names-and-types#os2012): the arms disagree | Make both arms one type, or use `none` |
| `["RSI", 14]` | [OS2013](/script/errors/names-and-types#os2013): a mixed array | Two arrays side by side |
| `flag == 1` | [OS2003](/script/errors/names-and-types#os2003): `bool` and `number` | `flag == true`, or just `flag` |
| `len = 14`, later `len = "fourteen"` | [OS2003](/script/errors/names-and-types#os2003): the type was fixed | A second name |

The last row is a rule of its own: **a name's type is fixed by its first assignment that gives it a definite type.** Assigning a different type later is OS2003, even when the two lines are pages apart. A first assignment of `none` fixes nothing, because `none` belongs to every type, so `var stop = none` followed by `stop = low` makes `stop` a number.

```openscript
len = 14
len = "fourteen"
```

## Type annotations

A type annotation is a type written after a name, with a colon: `var hits: array<number> = []`. You rarely need one. The places you do are an empty array, where there is nothing to infer from, and the parameters of a function other people will call, where the annotation documents what it takes.

```openscript
version 1
study("Annotations", overlay = true, precision = 2)

// An empty array: the annotation is the only place its type can come from.
var upLows: array<number> = []

// Parameters other people will call: the header says what each one takes.
fn band(src: series number, len: number = 20, mult: number = 2) =>
    sma(src, len) + mult * stdev(src, len)

if close > open
    push(upLows, low)
    if size(upLows) > 5
        shift(upLows)

plot(band(close), "Upper band", aqua)
plot(size(upLows) > 0 ? min(upLows) : none, "Lowest of the last five up-bar lows", orange)
```

The type names are `number`, `string`, `bool`, `color` and `array<T>`, with `series` in front where a per-bar value is meant, plus the runtime object types `line`, `label`, `box`, `polyline` and `table`. There is no `int`: any other name is [OS2016](/script/errors/names-and-types#os2016).

## Handles and runtime objects

Some calls return something that is neither a number, a string, a bool, a colour nor an array. There are two kinds, and their rules are opposites.

| Kind | Returned by | What it is |
|---|---|---|
| Declaration handle | `plot()`, `plotCandles()`, `fill()`, `level()` | A name for part of the study's fixed shape, known before bar 0 |
| Runtime object | `draw.line()`, `draw.label()`, `draw.box()`, `draw.polyline()`, `table()` | An ordinary value the script creates, keeps and changes as bars arrive |

A declaration handle has one use: naming the two plots a `fill()` shades between. It cannot be held in a `var`, put in an array, passed to a function or compared, and trying is an error.

```openscript
version 1
study("Channel", overlay = true)

upper = plot(highest(high, 20), "Upper", aqua)
lower = plot(lowest(low, 20), "Lower", aqua)
fill(upper, lower, fade(aqua, 88))
```

A runtime object is a reference, like an array: it can live in a `var`, sit in an array, and be passed to and returned from a function. A drawing stays on the chart until the script deletes it. [Lines and boxes](/script/visuals/lines-and-boxes) and [Tables](/script/visuals/tables) cover them.

## Errors you may meet

| Code | Means | Usual fix |
|---|---|---|
| [OS2003](/script/errors/names-and-types#os2003) | Two types do not mix, or a name changed type | Convert with `text`, `toNumber` or `toBool`, or use a second name |
| [OS2004](/script/errors/names-and-types#os2004) | The value has no history | Name the value at the top level of the file and read that name |
| [OS2011](/script/errors/names-and-types#os2011) | A condition is not a `bool` | Write the test out: `x > 0`, `isNone(x)`, `s != ""` |
| [OS2012](/script/errors/names-and-types#os2012) | The ternary arms have different types | Make them agree, or use `none` for one arm |
| [OS2013](/script/errors/names-and-types#os2013) | An array literal mixes types | Split it into two arrays |
| [OS2015](/script/errors/names-and-types#os2015) | An empty array literal has no element type | Annotate it: `var hits: array<number> = []` |
| [OS2016](/script/errors/names-and-types#os2016) | An annotation names a type that does not exist | Use `number`, `string`, `bool`, `color` or `array<T>` |
| [OS4001](/script/errors/runtime#os4001) | A history offset is negative or fractional | Round it with `floor()` or `round()`, and keep it at zero or above |
| [OS4003](/script/errors/runtime#os4003) | A length that must be whole is not | Round it where it is computed |
| [OS4004](/script/errors/runtime#os4004) | An array index is outside the array | Guard the read with `size(arr)` |

**Related.** [Absent values](/script/language/absent-values), [Operators](/script/language/operators), [Variables and scope](/script/language/variables-and-scope), [Bars and history](/script/language/bars-and-history), [Collections](/script/language/collections), [Types reference](/script/reference/types)


## Absent values

Source: https://openalgo.in/script/language/absent-values

A per-bar script meets "there is no value here" all the time: the first fourteen bars of a 14-bar RSI, the previous close on the very first bar, a ratio whose denominator was zero on one bar. OpenScript has one value for all of these, written `none`, and exact rules for what it does. This page covers where `none` comes from, how it moves through every operator, how to test for it and replace it, and the specific mistakes it causes. Learn it early: the bars where it bites are the oldest bars, off the left edge of the screen, so a study can be wrong for weeks before anyone scrolls back to see it.

## A first example

```openscript
version 1
study("Warmup made visible", precision = 2, range = [0, 100])

r = rsi(close, 14)

level(70, "Overbought", fade(red, 50))
level(30, "Oversold", fade(lime, 50))
plot(r, "RSI", purple, width = 2)

// r is absent until bar 14. Shade those bars instead of leaving them blank.
background(isNone(r) ? fade(gray, 90) : none)
```

The RSI line starts at bar 14 because `rsi()` has no value before it. No zero is drawn and nothing is invented: the plot simply has a gap, and the grey shading shows where the study starts. The number of bars a function needs before it has a value is its warmup; every reference entry states it.

## What none is

`none` is the absent value. Its type is also called `none`, and it belongs to every other type: a `series number` may hold `none` on any bar, and so may a `string` or a `color`. A name that holds a number on most bars and `none` on a few is an ordinary `series number`.

The language could have answered "no value here" with a plausible number such as zero, or by stopping the script. It does neither. Zero produces charts that look right and are not, and stopping would kill a study that is correct over fifty thousand bars because one bar had no volume. Instead `none` has a precise meaning and travels visibly to the chart, where it draws a gap.

## Where it comes from

| Source | Example | Absent on |
|---|---|---|
| Warmup | `sma(close, 20)` | Bars 0 to 18 |
| Warmup built on changes | `rsi(close, 14)` | Bars 0 to 13: one more than you might expect, because a change needs two bars |
| History past the start | `close[1]` | Bar 0. `close[100]` on bars 0 to 99 |
| Division by zero | `up / down` | Any bar where `down` is 0, including `0 / 0` |
| No finite real answer | `sqrt(-1)`, `log(0)` | Whenever the argument has no answer |
| A window that touches a gap | `sma(src, 20)` where `src` was absent on a bar | Every bar whose window includes that bar |
| Data the instrument does not have | `volume` or `oi` on an instrument without it, such as an index | Every bar |
| An instrument fact not stated | `chart.tickSize`, `chart.lotSize`, `chart.hasVolume` | The whole run, when the application running the script does not state the fact |
| A higher timeframe before its first bar closes | `req.timeframe("1D", high)` | Until the first daily bar has closed |
| Another instrument before it arrives | `req.symbol(...)` | Until its bars arrive. Test `req.isReady()` |
| A condition that has never held | `barsSince()`, `valueWhen()` | Every bar before the first true one |
| A position fact while flat | `pos.avgPrice` | Every bar with no open position |
| A string that does not parse | `toNumber("12.5%")` | That call |
| A stateful call in a branch that did not run | `ema` inside an `if` | Every bar the branch was skipped |
| Your own choice | `ready ? value : none` | Wherever you say |

The last row matters as much as the rest. Writing `none` on purpose is how a script says "draw nothing here", and it is the supported way to hide a plot on some bars.

## Rule 1: arithmetic propagates it

**If any operand of an arithmetic operator is absent, the result is absent.**

```openscript
none + 1  // none
none * 0  // none, not 0
none - none  // none
-none  // none
(none + 1) * 2  // none
"a" + none  // none
```

`none * 0` is absent, not zero. The operand was not zero; it was unknown, and an unknown quantity times zero is only zero if it was a number at all. One rule with no special cases is easier to carry, and it has a practical payoff: the absent value reaches the plot and draws a gap instead of being absorbed into a number that looks right.

Joining strings follows the same rule. `"a" + none` is absent, so a message built from one absent part is an absent message, not a partial one. `text()` with one argument turns `none` into the string `"none"`, so `"a" + text(x)` always gives a message. The two-argument form, `text(x, 2)`, passes the absence on.

Arithmetic with no answer produces absence for the same reason: division by zero (including `0 / 0` and a remainder by zero), `sqrt()` below zero, `log()` at or below zero, and anything that would overflow to infinity. Infinity is not a value in the language.

```openscript
version 1
study("Up volume over down volume", precision = 4)

up = sum(close > open ? volume : 0, 20)
down = sum(close < open ? volume : 0, 20)

// On a window with no down bars, down is 0 and the ratio is absent, so the
// line breaks instead of spiking to a value the chart cannot scale.
plot(up / down, "Up over down", aqua, width = 2)
```

## Rule 2: ordered comparison propagates it

**If either side of `<`, `<=`, `>` or `>=` is absent, the result is absent, not `false`.**

```openscript
none < 5  // none
5 > none  // none
none <= none  // none
```

This rule repays understanding. If a comparison with an absent side returned `false`, then during warmup `a > b` and `a <= b` would both be false, and a script that branches on one and assumes the other is its opposite would take the wrong path without anyone noticing. Because both are absent together, `not (a > b)` always equals `a <= b`, for every input.

The practical consequence: **a skipped branch is not proof of the opposite.** When an `if` does not run its block, the condition was false or it was absent, and those are different facts. If your script needs to tell them apart, ask with `isNone()`.

A comparison written directly against `none` is absent on every bar, so a branch on it can never run. The compiler reports warning [OS8012](/script/errors/warnings#os8012) on each of the three lines above, and on a test like this one:

```openscript
r = rsi(close, 14)
if r > none
    signal("NEVER")
```

## Rule 3: equality does not propagate it

**`==` and `!=` always return `true` or `false`, never absent.**

```openscript
none == none  // true
none == 5  // false
5 != none  // true
```

Equality is the deliberate exception, because a question that could itself be absent would leave no way to ask it. `x == none` and `isNone(x)` mean exactly the same thing, and both work directly in an `if`.

The flip side catches everyone once. On bar 0, `dir[1]` is absent, so `dir != dir[1]` is `true`, and a script that marks a change of state marks one on the very first bar of every chart. Require the previous value to be present, as mistake 7 below shows.

## Rule 4: and, or and not use three-valued logic

`and`, `or` and `not` treat `none` as "unknown": three-valued logic means each operand is true, false or unknown.

| `a` | `b` | `a and b` | `a or b` |
|---|---|---|---|
| `true` | `true` | `true` | `true` |
| `true` | `false` | `false` | `true` |
| `true` | `none` | `none` | `true` |
| `false` | any | `false` | `b` |
| `none` | `true` | `none` | `true` |
| `none` | `false` | `false` | `none` |
| `none` | `none` | `none` | `none` |

`not none` is `none`.

One sentence produces every row: **an unknown operand is absorbed exactly when the other operand decides the answer by itself.** Under `and` that is a `false`; under `or` it is a `true`. Everywhere else the answer really does depend on the value nobody has, so it stays unknown.

**Both operators are commutative**: swapping the operands never changes the answer. `a or b` equals `b or a`, and `a and b` equals `b and a`, for every combination including absent ones. So each pair of guards below gives one answer on every bar:

```openscript
x = rsi(close, 14)

if isNone(x) or x > 70  // "absent or above 70"
    background(fade(gray, 90))
if x > 70 or isNone(x)  // the same answer, on every bar
    background(fade(gray, 90))

if not isNone(x) and x > 70  // "present and above 70"
    signal("HOT")
if x > 70 and not isNone(x)  // the same answer, on every bar
    signal("HOT")
```

What operand order still decides is which operand runs. The right side of `or` is skipped only when the left side is `true`, and the right side of `and` only when the left side is `false`. An absent left side skips nothing, because the right side could still settle the answer. [Operators](/script/language/operators#short-circuit-evaluation) covers this in detail.

## Rule 5: an absent condition takes the false branch

**A condition that is absent takes the false branch.** This applies to `if`, `else if`, `while`, the ternary and the condition form of `switch`.

```openscript
if rsi(close, 14) > 70  // absent during warmup, so the block is skipped
    signal("OVERBOUGHT")
```

This is the one place absence is absorbed rather than passed on, and it is unavoidable: execution has to go somewhere. It is safe here in a way that a comparison returning `false` would not be, because the absorbing happens at the branch, where you can see it, rather than inside an expression three lines earlier.

## Testing for it

| Test | Returns | Use it for |
|---|---|---|
| `isNone(x)` | `bool`, never absent | The direct question, see `isNone()` |
| `x == none` | `bool`, never absent | The same question |
| `not isNone(x)` | `bool`, never absent | The first half of a presence guard |
| `req.isReady(read)` | `series bool` | Whether another instrument's bars have arrived, see `req.isReady()` |

`isNone(x)` and `x == none` are interchangeable. Both always give a definite answer, and neither can surprise you.

## Replacing it

`orElse(x, fallback)` (`orElse()`) gives `x` when it is present and `fallback` when it is not. It is the right tool when the fallback is a genuine answer rather than an invented one:

```openscript
r = rsi(close, 14)
safe = orElse(r, 50)  // 50 is the neutral RSI reading, a real answer
plot(safe, "RSI, neutral during warmup")
```

It is the wrong tool when the fallback would be a price, a quantity or anything a later comparison treats as data. `orElse(stop, 0)` turns "we have no stop" into "our stop is at zero", and every `close > stop` after it is true.

Three library functions ignore absent bars on purpose, and their names say so: `sumSkip()`, `avgSkip()` and `countPresent()`. Every other function that reads a window propagates: if any bar in the window is absent, the result on that bar is absent.

```openscript
version 1
study("Sparse average", precision = 2)

// Only bars that closed up contribute a value; the rest are absent.
upMove = close > open ? close - open : none

plot(sma(upMove, 20), "Plain average: absent when the window has a gap", gray)
plot(avgSkip(upMove, 20), "Average of the bars that had a value", aqua)
plot(countPresent(upMove, 20), "How many of the 20 had a value", orange)
```

On a real chart the plain average hardly ever draws, because it needs twenty up bars in a row. The count draws from bar 19 (the twentieth bar) onwards, and the skipping average draws on every bar from there whose window holds at least one up bar.

The order of preference, from best to last resort:

1. **Let it propagate.** A gap in a line is the truth, and costs nothing.
2. **Guard it** with `isNone` and take a different path.
3. **Replace it** with `orElse`, where the fallback is a real answer.
4. **Skip it** with a `Skip` function, where the window is genuinely sparse.

## Where it leaves the script

**On the chart, absence is a gap, never a zero.** A plot breaks its line, a fill stops, a bar colour leaves the bar its own colour, a background leaves the bar unshaded and a table cell is blank. That is why a plot is hidden on some bars by plotting `none`, never by wrapping the plot in an `if` (which is [OS3006](/script/errors/arguments#os3006)).

```openscript
version 1
study("EMA while trending", overlay = true)

ema20 = ema(close, 20)
trending = ema20 > ema(close, 50)

plot(trending ? ema20 : none, "EMA while trending", aqua)
```

**On an order, absence is refused loudly.** An order given an absent price or quantity does not place a malformed order and does not substitute a value. It is refused with error [OS7002](/script/errors/orders#os7002), naming the argument that was absent, and like every error in a running script it stops the strategy at that bar. An order is the one place where doing nothing quietly would be worse than stopping visibly. Leaving an argument out is different from passing an absent one: `buy()` uses the declaration's default size, while `buy(qty = q)` with `q` absent is refused.

## Mistakes, and the fix for each

### 1. Treating a skipped branch as proof of the opposite

```openscript
r = rsi(close, 14)
zone = "mid"
if r > 70
    zone = "high"
else if r < 30
    zone = "low"
// During warmup both tests are absent, neither branch runs, and zone says
// "mid" about bars that have no RSI at all: the warmup bars are shaded too.
background(zone == "mid" ? fade(gray, 90) : none)
```

Give "we do not know yet" a value of its own:

```openscript
r = rsi(close, 14)
zone = "unknown"
if not isNone(r)
    zone = r > 70 ? "high" : (r < 30 ? "low" : "mid")
background(zone == "mid" ? fade(gray, 90) : none)
```

### 2. A self-referencing series that is absent for ever

```openscript
seen = 0
seen = seen[1] + 1  // absent on bar 0, and on every bar after it
plot(seen, "Never draws")
```

On bar 0, `seen[1]` is absent, so the sum is absent, and every later bar reads an absent predecessor. One absent bar at the start poisons the whole run. The fix is `var`, which keeps a value from one bar to the next:

```openscript
var seen = 0
seen += 1  // 1, 2, 3, ...
plot(seen, "Bars seen")
```

Where a calculation genuinely needs its own previous value, seed it with `orElse`:

```openscript
version 1
study("Trailing stop", overlay = true, precision = 2)

mult = input(3.0, "Band width, in ATR", min = 0.5, max = 20)
raw = low - mult * atr(14)

var trail = none

// A var still holds the previous bar's value here, so prev is the stop as it
// stood one bar ago.
prev = trail

// On the first bar with an ATR, prev is absent. orElse seeds the stop from
// the raw value there, so it does not stay absent for ever.
trail = close[1] > orElse(prev, raw) ? max(raw, orElse(prev, raw)) : raw

plot(trail, "Trailing stop", lime, width = 2)
```

### 3. Guarding with the wrong operator

```openscript
x = rsi(close, 14)
if isNone(x) and x > 70  // never true, whether x is present or absent
    signal("NEVER")
if not isNone(x) or x > 70  // true whenever x is present, whatever its value
    signal("ALWAYS")
```

The two that work are `not isNone(x) and x > 70` ("present and above 70") and `isNone(x) or x > 70` ("absent or above 70"). Swapping the sides of the broken guards changes nothing, because both operators are commutative. It is the operator that has to match the sentence you mean.

### 4. Replacing a price with zero

```openscript
rawStop = lowest(low, 20)[1]
stop = orElse(rawStop, 0)
if close > stop  // true on every bar where the stop was absent
    signal("ABOVE STOP")
```

Zero is a price. Keep the absence and guard the test:

```openscript
rawStop = lowest(low, 20)[1]
if not isNone(rawStop) and close > rawStop
    signal("ABOVE STOP")
```

### 5. Expecting none times zero to be zero

A weight of zero does not cancel an absent factor. When some terms of a weighted sum should drop out, drop them explicitly:

```openscript
value = rsi(close, 14)
weight = 0.5
contribution = isNone(value) ? 0 : value * weight
plot(contribution, "Contribution")
```

### 6. A stateful call inside a branch

```openscript
trending = close > ema(close, 50)
if trending
    e = ema(close, 20)  // advances only on trending bars
    barColor(close > e ? lime : red)
```

A call site that does not run on a bar leaves its value absent for that bar and its state untouched, so the average is built from a subset of bars nobody meant. This is warning [OS8001](/script/errors/warnings#os8001). Compute the value on every bar and use it inside the branch:

```openscript
e = ema(close, 20)
trending = close > ema(close, 50)
plot(trending ? e : none, "EMA while trending", aqua)
```

### 7. Marking a change of state that never happened

```openscript
dir = close > ema(close, 20) ? 1 : -1
if dir != dir[1]
    signal("FLIP")
```

This marks two flips that never happened. On bar 0, `dir[1]` is absent, and equality is total, so an absent side does not make the test absent: it makes it `true`. And during the EMA's warmup the condition is absent, so the ternary takes its false arm and `dir` reads `-1` on bars that have no EMA at all; the first bar with a real reading of `1` then looks like a flip. Keep `dir` absent until it means something, and require a previous value:

```openscript
e = ema(close, 20)
dir = isNone(e) ? none : (close > e ? 1 : -1)
if not isNone(dir[1]) and dir != dir[1]
    signal("FLIP")
```

### 8. Reading a counter as zero before it has counted

`barsSince()` and `valueWhen()` are absent, not zero, before their condition has ever been true. Zero would read as "it happened on this bar", which is the opposite of the truth.

```openscript
fast = ema(close, 9)
slow = ema(close, 21)
since = barsSince(crossUp(fast, slow))
fresh = not isNone(since) and since <= 3
background(fresh ? fade(lime, 90) : none)
```

### 9. Building a message out of an absent part

```openscript
version 1
study("Last cross", overlay = true)

t = table("Last cross", 1, 1)
entryPrice = valueWhen(crossUp(close, ema(close, 20)), close)

// Absent before the first cross: text(x, 2) passes the absence on, and +
// then makes the whole message absent, so the cell would be blank.
message = "Last cross at " + text(entryPrice, 2)

if bar.isLast
    cell(t, 0, 0, isNone(message) ? "No cross yet" : message)
```

A message joined with `+` is absent if any part of it is. Guard the message as a whole, as here, or convert a fragile part with the one-argument `text(x)`, which writes an absent value as `"none"` instead of passing it on.

### 10. Confusing no volume with zero volume

`volume` is absent, not zero, on an instrument that has no volume, such as an index. Zero is a real reading that means nobody traded. Do not paper over the difference with `orElse(volume, 0)`: an index would then look like a stock nobody trades.

```openscript
version 1
study("Relative volume", precision = 2)

len = input(20, "Average length", min = 2, max = 500)

avgVolume = sma(volume, len)

// On an instrument with no volume, volume is absent and so is the ratio, so
// the pane stays empty instead of showing a flat line at zero.
ratio = volume / avgVolume

level(1, "Average", gray)
plot(ratio, "Volume against its average", aqua, width = 2, style = "histogram")
```

`chart.hasVolume` says whether the instrument reports volume at all, but it is itself absent when the application running the script does not state it, so test the value with `isNone(volume)` when you need a definite answer.

### 11. Sending an absent quantity to an order

A size computed from a risk budget divides by the distance to the stop, and that distance can be zero. The division is then absent, the order is refused with [OS7002](/script/errors/orders#os7002), and the strategy stops at that bar. That is the designed behaviour, but it ends the run over one awkward bar. Guard the entry, so such a bar simply places no order:

```openscript
version 1
strategy("Risk-sized entry", overlay = true)

riskPerTrade = input(2000, "Risk per trade, in rupees", min = 100)

fast = ema(close, 9)
slow = ema(close, 21)
stopPrice = lowest(low, 10)

// Absent when close equals stopPrice, because that divides by zero.
qty = floor(riskPerTrade / (close - stopPrice))

if crossUp(fast, slow) and not isNone(qty) and qty > 0
    buy(qty = qty)
if crossDown(fast, slow)
    close()
```

### 12. Hiding a gap instead of reading it

```openscript
value = rsi(close, 14)
plot(orElse(value, 0), "RSI, with a fake zero")
```

The line now runs along zero during warmup and dives to zero on every absent bar, which looks like data and is not. Plot the value itself and let the gap show.

## A short discipline

- Assume every library value is absent until its warmup is over, and look the warmup up in the reference rather than guessing it.
- Write a guard as `not isNone(x) and ...` or `isNone(x) or ...`, and check the operator, not the order of the operands.
- Give a name a starting value above any `if` that assigns to it.
- Never fall back to zero for a price, a quantity or a level. Fall back only to a value that is genuinely the answer.
- Treat every warning (codes starting OS8) as a real finding. Several exist for exactly the bugs on this page.
- When a plot has a hole, read the hole: it tells you which bars had no value.

## Errors and warnings you may meet

| Code | Means | Fix |
|---|---|---|
| [OS3006](/script/errors/arguments#os3006) | A plot inside a block, often written to hide it | Plot `none` on the bars to hide |
| [OS7002](/script/errors/orders#os7002) | An order received an absent price or quantity | Guard the order with `isNone` |
| [OS8001](/script/errors/warnings#os8001) | A stateful call inside a branch | Compute it at the top level |
| [OS8009](/script/errors/warnings#os8009) | A plot is absent on every bar | Check the warmup and the guard that feeds it |
| [OS8012](/script/errors/warnings#os8012) | A comparison against `none` | Use `isNone(x)` or `x == none` |

**Related.** [Types and values](/script/language/types-and-values), [Operators](/script/language/operators), [Control flow](/script/language/control-flow), [Warmup](/script/language/warmup), [Persistence](/script/language/persistence), [General functions](/script/reference/general)


## Variables and scope

Source: https://openalgo.in/script/language/variables-and-scope

A variable in OpenScript is a name you assign a value to. There is no keyword to declare an ordinary name: the first assignment creates it, and every later assignment updates it. This page covers how a name is declared, when it is recomputed and when it survives into the next bar, which lines can see it (its scope), and why the language refuses to let two variables share a name. With these rules you can point at any name in a script and say where it was declared, what it holds and whether it carries forward.

## A complete example

```openscript
version 1
study("Spread", precision = 2)

lookback = input(20, "Average length", min = 2, max = 500)

spread = high - low  // declares spread
spread = spread / close * 100  // updates it, as a percentage of price

var widest = 0.0  // survives from bar to bar
if spread > widest
    widest = spread  // updates the file-scope widest

avgSpread = sma(spread, lookback)

plot(spread, "Spread, percent of price", aqua, width = 2)
plot(avgSpread, "Average spread", orange)
plot(widest, "Widest so far", gray, style = "step")
```

`spread` is declared and updated on every bar. `widest` is declared once with `var` and keeps its value between bars. The `if` block updates `widest` rather than creating a second one, because a name that already exists outside a block is updated from inside it.

## Declaring and updating

**The first assignment to a name in a scope declares it. Every later assignment updates it.**

Two rules follow immediately.

**A name's type is fixed by its first assignment.** Assigning a value of a different type later is error [OS2003](/script/errors/names-and-types#os2003), even when the two lines are pages apart. A name is one thing for the life of the script, so a reader can look at one line and know what it holds. A first assignment of `none` fixes no type, because `none` belongs to every type; the type then comes from the first assignment that gives a definite value.

```openscript
len = 14
len = "fourteen"
```

**A name must be assigned above the line that reads it.** The file runs top to bottom on every bar, so reading a name before its assignment is error [OS2001](/script/errors/names-and-types#os2001), not an absent value. Function declarations are the one exception, covered [below](#order-of-declaration).

```openscript
plot(slow, "Slow", orange)
slow = ema(close, 21)
```

## A plain assignment is recomputed on every bar

A name assigned without `var` is computed afresh on every bar. Its previous value is still readable through the history operator, as `name[1]`, but it is not the starting point for this bar's computation.

```openscript
seen = seen + 1
```

`seen` does not exist yet on this bar when `seen + 1` is read, so that line is OS2001. Reading the previous bar instead compiles, and never produces a value:

```openscript
seen = 0
seen = seen[1] + 1  // absent on bar 0, and on every bar after it
plot(seen, "Never draws")
```

On bar 0 there is no previous bar, `seen[1]` is absent, and the sum is absent. Every later bar reads an absent predecessor, so one absent bar at the start poisons the whole run. That is what `var` fixes.

## var keeps a value across bars

`var name = initial` declares a name whose initial value is set once and which then keeps whatever it holds from one bar to the next.

```openscript
version 1
study("Running high", overlay = true, precision = 2)

// Set to none once, on bar 0, then kept from bar to bar.
var highestSeen = none
if isNone(highestSeen) or high > highestSeen
    highestSeen = high

plot(highestSeen, "Highest high so far", aqua, style = "step")
```

| Rule | Consequence |
|---|---|
| The initial value is set once, on the first bar the declaration is reached | A `var` inside an `if` that is false for the first hundred bars is set on bar 100 |
| `var` may appear at the top level, inside a block or inside a function | Persistence is available wherever a value is |
| A `var` inside a block is still scoped to that block | How long a value lives and where its name can be seen are separate questions |
| `var` rolls back on the forming bar | Running the newest bar ten times gives the same answer as running it once |
| The declaration needs an initial value | `var seen` alone is [OS1011](/script/errors/syntax#os1011). Write `var seen = none` for an empty start |

### Rollback, and why a running total is safe

While the market is open the newest bar is still forming, and the engine runs the script on it again on every update. Before each rerun, every `var` is restored to what it held at the end of the previous bar (this is called rollback). So a counter counts bars, not updates, and a chart shows the same numbers as a backtest over the same data.

```openscript
version 1
study("Bars, not updates")

var seen = 0
seen += 1

plot(seen, "Bars seen", aqua, width = 2)
```

`live var` is the same except that it does not roll back, so it keeps counting across the updates of the forming bar. It exists for one purpose, counting or accumulating within a bar, and it is spelled with an extra word because a script that uses one produces different numbers on a real-time chart than in a backtest. The compiler reports warning [OS8011](/script/errors/warnings#os8011) to make sure that is what you meant. [Persistence](/script/language/persistence) covers `var`, `live var` and rollback in full.

### History and persistence are different questions

| Written | Means |
|---|---|
| `close[1]` | History: what `close` was one bar ago |
| `var x = 0` | Persistence: `x` carries into the next bar |
| `x[1]` | Both: what the persistent `x` was one bar ago |

A useful consequence: at any line of the file, a `var` still holds the previous bar's value until the line that reassigns it. So a script can compare this bar's value with the last one by reading the `var` before it changes:

```openscript
version 1
study("Up-close streaks", precision = 0)

var streak = 0

// At this line streak still holds the count the previous bar left in it.
prevStreak = streak

streak = close > close[1] ? streak + 1 : 0

// A run of three or more up closes ended on this bar.
if prevStreak >= 3 and streak == 0
    signal("STREAK ENDED")

plot(streak, "Up closes in a row", aqua, style = "step")
```

## The three scopes

A scope is a region of the script in which a name can be seen.

| Scope | Holds | Created by |
|---|---|---|
| Global | The library: built-in series, functions and colour names | The language |
| File | Every name assigned at the top level, and every `fn` | The file |
| Block | Names first assigned inside it | Each `if` block, `else` block, `for` or `while` body, `case` or `default` arm, and function body |

Blocks nest, and a block can see everything its enclosing blocks and the file can see. A function's parameters belong to its body's scope.

## Declaration versus update

This is the whole of scoping in two sentences:

**A name is declared by its first assignment in a scope. An assignment to a name that already exists in an enclosing scope updates that name and does not create a new one.**

```openscript
version 1
study("Threshold")

volatile = atr(14) > sma(atr(14), 50)

threshold = 70  // declared in the file scope
if volatile
    threshold = 80  // updates the file-scope name

plot(threshold, "Threshold", aqua)  // 80 on volatile bars, 70 on the rest
```

```openscript
volatile = atr(14) > sma(atr(14), 50)
if volatile
    scratch = high - low  // declared in the block scope
plot(scratch, "Scratch", aqua)  // OS2001: scratch is not visible here
```

Together those rules mean you never have to ask which of two variables a line writes to. There is exactly one `threshold`, and exactly one place `scratch` can be read.

## What a block can see and keep

A name first assigned inside a block is visible **from its assignment to the end of that block, including blocks nested inside it.** Nothing else.

| Where it is first assigned | Visible to |
|---|---|
| The top level of the file | Every line below it, including inside every block and every function |
| Inside an `if` block | The rest of that block and blocks nested in it. Not the `else`, not a sibling `if`, not anything after the block |
| Inside an `else` block | The rest of that block only |
| Inside a `for` or `while` body | The rest of that body, on that iteration. The next iteration starts fresh |
| Inside a `case` or `default` arm | The rest of that arm. No other arm, and nothing after the `switch` |
| Inside a function body | The rest of that body |

Three consequences catch everyone once:

- **A name declared in an `if` cannot be read in its `else`.** They are two blocks.
- **A name declared in a loop body does not accumulate.** It is a fresh name each time round. To carry a value between iterations, declare it above the loop.
- **A name declared in a block has no history.** History is kept only for names at the top level of the file, so `inner[1]` inside a block is [OS2004](/script/errors/names-and-types#os2004). Name the value at the top level instead.

```openscript
version 1
study("Mean candle body", precision = 2)

lookback = input(20, "Lookback", min = 2, max = 500)

// Declared above the loop: a name declared inside the body would be a fresh
// name on every iteration and would carry nothing between them.
total = 0.0
seen = 0

for i = 0 to lookback - 1
    body = abs(close[i] - open[i])  // a fresh name on each iteration
    if isNone(body)
        continue
    total += body
    seen += 1

plot(seen > 0 ? total / seen : none, "Mean body", aqua, width = 2)
```

## There is no shadowing

Shadowing means declaring a second variable with the same name as one in an enclosing scope, so that one name refers to two things. OpenScript does not allow it: **declaring a name in an inner scope when the same name already exists in an enclosing scope is error [OS2002](/script/errors/names-and-types#os2002).** The message gives the line of the outer declaration.

Inside an `if` or a loop a plain assignment cannot shadow, because an assignment to an outer name updates it; only a `var` that reuses an outer name can, and it is OS2002. Inside a function body it is easier to do by accident: a function may read names from the file scope, but an assignment inside the body always declares a name of the function's own. So assigning a file-scope name inside a function is a second declaration of that name, and is OS2002.

```openscript
len = 20

fn smooth(src) =>
    len = 9
    sma(src, len)
```

The fix is to rename:

```openscript
len = 20

fn smooth(src) =>
    innerLen = 9
    sma(src, innerLen)

plot(smooth(close), "Smoothed", aqua)
plot(sma(close, len), "SMA", orange)
```

A function parameter follows the same rule: a parameter named after a file-scope name, or after a library name such as `close`, is OS2002.

Shadowing is banned because the most expensive bug in a per-bar script is a value that is right in one place and stale in another, and two variables sharing one name is the shortest path to it.

### Library names are taken

The library's names (built-in series, functions and colours) live in the global scope, so assigning to one is a shadowing attempt and is OS2002 as well.

```openscript
close = 5
```

The names that bite most often are the short, obvious ones a script wants for its own values: `count`, `sum`, `avg`, `min`, `max`, `size`, `change`, `variance`, `stdev`, `level`, `fill`, `signal`, `time`, `open`, `high`, `low`, `close` and `volume`. If a name feels natural enough that the library probably took it, it probably did: `upCount`, `totalVolume` and `dayHigh` are free.

## Order of declaration

Within the file scope a name must be assigned before it is read, reading top to bottom, because the file is the body of the per-bar loop and runs in source order.

Functions are the exception. A function declaration is not a per-bar statement, and the compiler collects every one before it checks any body, so a `fn` may be called above its declaration:

```openscript
version 1
study("Helper below", overlay = true, precision = 2)

plot(helper(close), "Smoothed", aqua, width = 2)

fn helper(src) => sma(src, 9)
```

## Loop variables

The loop variable of a `for` belongs to the loop: it does not exist after the loop, and it may not be assigned in the body. Assigning it is [OS2006](/script/errors/names-and-types#os2006); to leave early, use `break`.

```openscript
for i = 0 to 9
    if close[i] > high
        i = 9
```

The `for x in arr` form scopes `x` the same way, and assigning `x` in the body is OS2006 too.

## switch arms declare nothing that outlives them

Because a name first assigned inside an arm belongs to that arm, a name the arms of a `switch` set must be declared before the `switch`. This makes the "declared in one arm only" bug impossible to write, and it puts the default where a reader sees it first.

```openscript
version 1
study("Selectable length", overlay = true, precision = 2)

method = input("medium", "Speed", options = ["fast", "medium", "slow"])

// Declared here, so it exists whatever the switch does.
len = 21

switch method
    case "fast"
        len = 9
    case "slow"
        len = 50

plot(sma(close, len), "SMA", aqua, width = 2)
```

## Names inside a function

A function body is a block scope. Its parameters live there, and anything it assigns is its own. It can read file-scope names, and the ban on shadowing means it can never accidentally declare a second one.

A function body may use `var`, and may call library functions that keep state. **State belongs to the call site (the place in the source where the function is called), not to the function**, so two calls in two places are two independent pieces of state. [User functions](/script/language/functions) covers this in full.

```openscript
version 1
study("Bars since", precision = 0)

fn sinceTrue(cond) =>
    var n = none
    if cond
        n = 0
    else if not isNone(n)
        n = n + 1
    n

sinceUp = sinceTrue(close > open)  // its own counter
sinceHigh = sinceTrue(high > high[1])  // a separate counter

plot(sinceUp, "Bars since an up close", aqua)
plot(sinceHigh, "Bars since a higher high", orange)
```

## Patterns worth copying

### Declare above, refine inside

Give a name its "we do not know yet" value above the branch that refines it, so warmup is an explicit state rather than an accident:

```openscript
r = rsi(close, 14)

zone = "unknown"
if not isNone(r)
    zone = r > 70 ? "high" : (r < 30 ? "low" : "mid")

// Warmup bars stay "unknown", so they are never shaded as overbought.
background(zone == "high" ? fade(red, 85) : none)
```

### Reset at the start of each day

State that belongs to a trading day resets on the day's first bar. Test for it with the calendar, not with a bar count, because the number of bars in a day changes with the interval and with holidays and short sessions.

```openscript
version 1
study("Day extremes", overlay = true, precision = 2)

// The first bar of a new calendar day in the chart's time zone, or bar 0.
newDay = bar.isFirst or not date.isSameDay(time, time[1])

var dayHigh = none
var dayLow = none

if newDay
    dayHigh = high
    dayLow = low
else
    dayHigh = max(dayHigh, high)
    dayLow = min(dayLow, low)

plot(dayHigh, "Day high", aqua, width = 2, style = "step")
plot(dayLow, "Day low", orange, width = 2, style = "step")
```

Where the application running the script states the instrument's session hours, `session.isFirstBar` marks the first bar of each session instead; see [Sessions and time](/script/data/sessions-and-time).

### Store a time, not a bar index

`bar.index` is a position in the data the engine was given. Loading older history renumbers every bar, so an index stored in a `var` and compared later is compared against something that moved. Store `time`, which does not move:

```openscript
version 1
study("Minutes since the last cross up", precision = 0)

fast = ema(close, 9)
slow = ema(close, 21)

var crossTime = none  // survives more history being loaded
if crossUp(fast, slow)
    crossTime = time

// Absent until the first cross, because time - none is none.
plot((time - crossTime) / 60000, "Minutes since the last cross up", aqua)
```

Subtracting two indices from the same run is still fine, because both come from the same numbering.

## Naming rules

Names are ASCII, start with a letter or an underscore, and are case sensitive. The convention, not enforced, is `camelCase` for names and functions and `UPPER_SNAKE` for values a script treats as constants.

The reserved words cannot be used as names: `and`, `array`, `as`, `bool`, `break`, `case`, `color`, `continue`, `default`, `else`, `false`, `fn`, `for`, `if`, `import`, `in`, `is`, `live`, `map`, `matrix`, `none`, `not`, `number`, `or`, `return`, `series`, `step`, `string`, `strategy`, `study`, `switch`, `to`, `true`, `type`, `var` and `while`. Using one is [OS1019](/script/errors/syntax#os1019). Some are reserved for later versions and do nothing today; reserving them now means adding them later cannot break a script that used one. [Keywords](/script/reference/keywords) describes each one.

## Errors and warnings you may meet

| Code | Means | Fix |
|---|---|---|
| [OS1011](/script/errors/syntax#os1011) | `var` with no initial value | `var name = none` is the empty start |
| [OS1019](/script/errors/syntax#os1019) | A reserved word used as a name | Rename it |
| [OS2001](/script/errors/names-and-types#os2001) | The name is not defined at this point | Assign it above this line, or fix the spelling |
| [OS2002](/script/errors/names-and-types#os2002) | The name already exists in an enclosing scope | Rename the inner one, or update the outer one from a block instead |
| [OS2003](/script/errors/names-and-types#os2003) | The name changed type | Use a second name |
| [OS2004](/script/errors/names-and-types#os2004) | The value has no history | Name it at the top level and read that name |
| [OS2005](/script/errors/names-and-types#os2005) | A function calls itself | Write a loop |
| [OS2006](/script/errors/names-and-types#os2006) | The loop variable was assigned in the body | Use `break`, or a separate name |
| [OS8010](/script/errors/warnings#os8010) | A name is assigned and never read | Use it, or delete the line |
| [OS8011](/script/errors/warnings#os8011) | A `live var` makes real-time and backtest differ | Use `var`, unless counting updates within a bar is the intent |

**Related.** [Execution model](/script/language/execution-model), [Persistence](/script/language/persistence), [Types and values](/script/language/types-and-values), [Control flow](/script/language/control-flow), [User functions](/script/language/functions), [Style guide](/script/writing/style-guide)


## Operators

Source: https://openalgo.in/script/language/operators

Operators combine values into expressions: `close - open`, `r > 70`, `trending and volume > avgVolume`. OpenScript has a short list of them, nine precedence levels in all, and every one has an exact rule for what it does when an operand is absent. This page teaches you to read any expression the way the compiler reads it: what binds first, what division gives you, what a comparison against an absent value returns, and when the right side of an `and` or an `or` is evaluated at all. The [Operators reference](/script/reference/operators) lists every operator mark with its operand and result types.

## A worked example

```openscript
version 1
study("Down bars on heavy volume", precision = 2)

avgVolume = sma(volume, 20)
body = close - open
weighted = body + body[1] * 2  // body + (body[1] * 2)
heavyDown = not (close > open) and volume > avgVolume  // (not ...) and (...)

plot(weighted, "Weighted body", aqua)
plot(heavyDown ? 1 : isNone(heavyDown) ? none : 0, "Heavy down bar", orange, style = "histogram")
```

Read the last line with the absence rules in mind. `avgVolume` is absent for the first nineteen bars, so the comparison `volume > avgVolume` is absent there. On a down bar the left side of `and` is `true` and `heavyDown` is absent; on an up bar the left side is `false`, which decides the answer alone, and `heavyDown` is `false`. The nested ternary plots a gap where the answer is unknown instead of a confident zero.

## Precedence

Precedence decides which operator takes its operands first when an expression has several, as multiplication does before addition in arithmetic. The table lists the tightest binding first. Every level groups left to right except where the notes say otherwise.

| Level | Operators | Notes |
|---|---|---|
| 1 | `(expr)`, `f(args)`, `a[i]`, `a.b` | Grouping, call, history or element, member |
| 2 | unary `-`, unary `+`, `not` | Right to left |
| 3 | `*`, `/`, `%` | |
| 4 | `+`, `-` | |
| 5 | `<`, `<=`, `>`, `>=` | At most one per expression |
| 6 | `==`, `!=` | At most one per expression |
| 7 | `and` | Short-circuits |
| 8 | `or` | Short-circuits |
| 9 | `cond ? a : b` | Right to left |

```openscript
a = 1
b = 2
c = 3
x = 4
y = 5

r1 = a + b * c  // a + (b * c), 7
r2 = -x % y  // (-x) % y, -4
r3 = close[1] * 2  // (close[1]) * 2
r4 = x > 0 ? 1 : x < 0 ? -1 : 0  // x > 0 ? 1 : (x < 0 ? -1 : 0), 1

plot(r1 + r2 + r3 + r4, "Sum")
```

Assignment is not in the table because it is not an operator. It is a statement, which is why `if x = 5` does not compile: that is [OS1006](/script/errors/syntax#os1006), with the fix naming `==`.

### The one precedence trap

`not` binds tighter than comparison, so `not close > open` means `(not close) > open`. `not` needs a `bool`, and `close` is a number, so the line is error [OS2011](/script/errors/names-and-types#os2011) rather than the test you meant. Write the parentheses:

```openscript
downBar = not close > open
```

```openscript
downBar = not (close > open)
plot(downBar ? 1 : 0, "Down bar")
```

The presence guard `not isNone(x) and x > 5` needs no extra parentheses, because the call's own brackets already group `isNone(x)`: it reads as `(not isNone(x)) and (x > 5)`.

## Arithmetic

`+`, `-`, `*`, `/` and `%` work on `number`. There is one numeric type, so there is one division, and `/` is always real division: `7 / 2` is `3.5`. There is no integer division operator. When you want a whole number, say which way to round with a call the reader can see.

| Expression | Value | Why |
|---|---|---|
| `7 / 2` | `3.5` | Division is always real |
| `-7 / 2` | `-3.5` | The same |
| `floor(7 / 2)` | `3` | `floor()` rounds toward negative infinity |
| `floor(-7 / 2)` | `-4` | So a negative value goes down |
| `trunc(-7 / 2)` | `-3` | `trunc()` rounds toward zero |
| `round(-7 / 2)` | `-4` | `round()` rounds to nearest, halves away from zero |
| `7 % 2` | `1` | The remainder after division |
| `-7 % 3` | `-1` | `%` takes the sign of the left operand |
| `7 % -3` | `1` | The same |
| `mod(-7, 3)` | `2` | `mod()` takes the sign of the right operand |
| `mod(7, -3)` | `-2` | The same |
| `7 / 0`, `0 / 0`, `7 % 0` | `none` | Division by zero has no answer |

Two remainders exist because both are wanted. `%` suits a "distance past a multiple" calculation; `mod` suits an index into a repeating cycle. The two agree whenever both operands are positive, which covers wrapping a bar count or a position in a session.

There is no power operator: `pow(x, y)` is the power function. See `pow()`.

Every arithmetic operator propagates absence: if either operand is absent, so is the result, including `none * 0`. Arithmetic with no finite answer gives `none` rather than infinity or an error. [Absent values](/script/language/absent-values) covers both rules.

```openscript
version 1
study("Stop a few ticks under the low", overlay = true, precision = 2)

steps = input(3, "Distance, in ticks", min = 1, max = 100)

// chart.tickSize is absent when the application running the script does not
// state one. The product is then absent, and so is the level, so nothing is
// drawn rather than a price the exchange would not accept.
offset = steps * chart.tickSize
stopLevel = roundToTick(lowest(low, 20) - offset)

plot(stopLevel, "Stop level", red, width = 2, style = "step")
```

## Joining strings

`+` also joins two strings, and does nothing else. `"a" + 5` is [OS2003](/script/errors/names-and-types#os2003); convert the number with `text()` first.

```openscript
t = table("Last close", 1, 1)
message = "Close " + text(close, 2) + " on " + chart.symbol

if bar.isLast
    cell(t, 0, 0, message)
```

An absent operand makes the whole string absent, so a message with one absent part is no message at all: if `chart.symbol` were absent, the cell above would be blank. `text(x)` with one argument writes an absent value as the string `"none"`, which is the way to show an absence in the output rather than lose the whole string.

## Comparison

`<`, `<=`, `>` and `>=` compare two numbers or two strings. Strings compare by Unicode code point, which is the same in every locale; it is not a dictionary order.

**A comparison cannot be chained.** `30 < r < 70` is error [OS1008](/script/errors/syntax#os1008). Write the middle value twice:

```openscript
r = rsi(close, 14)
inBand = 30 < r < 70
```

```openscript
r = rsi(close, 14)
inBand = 30 < r and r < 70
plot(inBand ? 1 : 0, "RSI between 30 and 70")
```

Chaining is refused rather than given the mathematical meaning because a reader could take it two ways, and a form with two plausible meanings has no place in a language that places orders.

**Ordered comparison propagates absence.** If either side is absent, the result is absent, not `false`. That keeps `not (a > b)` equal to `a <= b` for every input: during warmup both are absent and both branches are skipped. A comparison written against `none` itself is therefore absent on every bar, and is warning [OS8012](/script/errors/warnings#os8012); test with `isNone()` instead.

## Equality

`==` and `!=` always return `true` or `false`, never absent. That is the deliberate exception to propagation, because a question that could not be answered would be no use.

| Case | Result |
|---|---|
| `none == none` | `true` |
| `none == 5` | `false` |
| `5 != none` | `true` |
| Two colours | Equal when all four channels match |
| Two arrays | Equal when they are the same array, not when their contents match |
| Two values of different types | [OS2003](/script/errors/names-and-types#os2003), except against `none`, which is always allowed |

`arrayEqual()` compares the contents of two arrays. `==` compares identity, because an array is a reference: `b = a` gives two names for one array.

Because equality is total, `x != x[1]` is `true` on bar 0, where `x[1]` is absent, and a marker for a change of state would fire on the first bar of every chart. The same trap waits at the end of warmup, when a value that was absent becomes present. Keep the state absent until it means something, and require the previous value to be present:

```openscript
version 1
study("Direction flips", overlay = true, precision = 2)

fast = ema(close, 9)
slow = ema(close, 21)

// Absent until both averages exist. Without the guard the ternary would read
// -1 through the warmup, and the first real reading could look like a flip.
dir = isNone(slow) ? none : (fast > slow ? 1 : -1)

if not isNone(dir[1]) and dir != dir[1]
    signal(dir == 1 ? "TREND UP" : "TREND DOWN")

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)
```

## Logical operators

The logical operators are the words `and`, `or` and `not`. They take `bool` operands and use three-valued logic, where `none` means "unknown".

| `a` | `b` | `a and b` | `a or b` |
|---|---|---|---|
| `true` | `true` | `true` | `true` |
| `true` | `false` | `false` | `true` |
| `true` | `none` | `none` | `true` |
| `false` | any | `false` | `b` |
| `none` | `true` | `none` | `true` |
| `none` | `false` | `false` | `none` |
| `none` | `none` | `none` | `none` |

`not none` is `none`, and `not not x` is legal and means `x`.

An unknown operand is absorbed exactly when the other one decides the answer by itself: a `false` under `and`, a `true` under `or`.

**Both operators are commutative.** `a and b` equals `b and a`, and `a or b` equals `b or a`, for every combination of `true`, `false` and `none`, so the order you write the operands in never changes the answer. The usual rules for negating a combination hold too, absent operands included: `not (a and b)` is `not a or not b`. What decides whether a guard works is the operator, not the side a test sits on: `isNone(x) and x > 5` is never true, however it is written.

### Short-circuit evaluation

**An operand is evaluated only if it can change the result.** Skipping the rest of an expression once its answer is settled is called short-circuiting.

| Expression | Left side is | Right side evaluated | Result |
|---|---|---|---|
| `a and b` | `false` | No | `false` |
| `a and b` | `true` | Yes | `b` |
| `a and b` | `none` | Yes | `false` when `b` is `false`, otherwise `none` |
| `a or b` | `true` | No | `true` |
| `a or b` | `false` | Yes | `b` |
| `a or b` | `none` | Yes | `true` when `b` is `true`, otherwise `none` |

An absent left side settles nothing on its own, so the right side is evaluated in both cases. Only a left side that decides alone skips the right side.

Order does not change what an expression means, but it does change what runs. Put the cheaper or more often decisive test on the left, so its answer saves the work on the right. This line saves the right side on bar 0, because `bar.isFirst` is `true` there:

```openscript
newDay = bar.isFirst or not date.isSameDay(time, time[1])
background(newDay ? fade(silver, 85) : none)
```

**Keep stateful calls out of the right side.** A call that keeps state between bars, such as `rsi()`, `ema()` or a user function with a `var` in it, does not advance on a bar where it is skipped, and its value is absent there. The compiler reports warning [OS8001](/script/errors/warnings#os8001):

```openscript
useFilter = input(true, "Use the filter")
if useFilter and rsi(close, 14) > 70
    signal("HIGH")
```

Compute it at the top level, where it runs on every bar, and use the result in the guard:

```openscript
version 1
study("Filtered overbought", precision = 2)

useFilter = input(true, "Use the filter")
len = input(14, "RSI length", min = 2, max = 200)

r = rsi(close, len)

if useFilter and r > 70
    signal("HIGH")

plot(r, "RSI", purple, width = 2)
level(70, "Overbought", fade(red, 50))
```

`&&`, `||` and `!` do not exist; the operators are the words.

## The ternary

`cond ? a : b` (the ternary, or conditional operator) chooses a value. The condition must be a `bool` or absent, and an absent condition takes the false arm. Both arms must have the same type, or one arm may be `none`, and only the taken arm is evaluated.

```openscript
r = rsi(close, 14)
tint = close > open ? lime : red

// A chain nests to the right, so it reads as a list of cases with the last one
// the default. The first case keeps the warmup bars absent.
zone = isNone(r) ? none : r > 70 ? 1 : r < 30 ? -1 : 0

plot(r, "RSI", tint)
plot(zone, "Zone: 1 above 70, -1 below 30")
```

Arms of different types are [OS2012](/script/errors/names-and-types#os2012). There is no expression form of `switch`: the ternary chooses a value, and `switch` chooses a block.

Because only the taken arm runs, the ternary is a safe guard for a division: `down > 0 ? up / down : none`. For the same reason a stateful call inside an arm only advances on the bars that take that arm, and is warning [OS8001](/script/errors/warnings#os8001). Take the call at the top level and put its result in the arm:

```openscript
version 1
study("Up-volume share", precision = 2)

flow = sum(close > open ? volume : 0, 20)
traded = sum(volume, 20)
share = traded > 0 ? flow / traded : none

plot(share, "Share of volume on up bars", aqua)
```

The ternary is also how a plot is hidden on some bars, since `plot()` must stay at the top level: `plot(trending ? ema20 : none, "EMA 20", aqua)`.

## History and element access

`a[i]` means one of two things, chosen at compile time from the type of `a`:

| `a` is | `a[i]` means | Example |
|---|---|---|
| A series | The value `i` bars back | `close[1]`, the previous close |
| An array | Element `i`, counting from 0 | `levels[0]`, the first element |

```openscript
version 1
study("History and elements", overlay = true, precision = 2)

var recent: array<number> = []
push(recent, close)
if size(recent) > 20
    shift(recent)

oldest = recent[0]  // element: the first item in the array
back19 = close[19]  // history: the close 19 bars ago

plot(oldest, "Oldest close in the array", silver)
plot(back19, "Close 19 bars back", orange)
```

Once the array holds its twenty closes, the two lines are one line. Before that, on the first nineteen bars, `recent[0]` is the very first close while `close[19]` is absent: element access reads what the array holds, and history reads what the data holds.

| Situation | Result |
|---|---|
| `x[n]` where `n` is greater than `bar.index` | `none`: not clamped, not zero, not an error |
| `x[n]` where `n` is absent | `none` |
| `x[n]` where `n` is negative or not whole | [OS4001](/script/errors/runtime#os4001), which stops the script at that bar |
| `x[n]` deeper than the history the engine keeps | [OS4002](/script/errors/runtime#os4002), naming the `limits(history = ...)` that raises it |
| `arr[i]` outside `0` to `size - 1` | [OS4004](/script/errors/runtime#os4004), an error |

Reading past the start of history is absence, because that value never existed. Reading past the end of an array is an error, because the script asked for something it never created.

Where a reader could doubt which meaning a line uses, write the explicit form: `history()` always reads a series some bars back, and `element()` always reads an element of an array. [Bars and history](/script/language/bars-and-history) covers the history operator in full.

## Member access

`a.b` reads a member of a namespace, such as `bar.isConfirmed`, `chart.symbol` or `session.isFirstBar`, or calls one, such as `date.dayOfWeek()`. A name after the dot that the namespace does not have is [OS2009](/script/errors/names-and-types#os2009).

## Assignment

Assignment is a statement, never an expression.

| Form | Means |
|---|---|
| `name = expression` | Declare the name in this scope, or update it if it already exists in an enclosing one |
| `name += expression` | `name = name + expression` |
| `name -= expression` | `name = name - expression` |
| `name *= expression` | `name = name * expression` |
| `name /= expression` | `name = name / expression` |
| `name %= expression` | `name = name % expression` |

The compound forms obey every rule of the long form, including absence: `x += none` leaves `x` absent. A name's type is fixed by its first assignment, and a different type later is [OS2003](/script/errors/names-and-types#os2003).

```openscript
version 1
study("Cumulative volume", precision = 0)

var total = 0.0

// Without the guard, one bar with no volume would make total absent for ever.
if not isNone(volume)
    total += volume

plot(total, "Cumulative volume", silver, style = "area")
```

## Operators that do not exist

| You might write | Write instead | Error |
|---|---|---|
| `!cond` | `not cond` | [OS1001](/script/errors/syntax#os1001) |
| `a && b`, `a \|\| b` | `a and b`, `a or b` | [OS1001](/script/errors/syntax#os1001) |
| `a ^ b`, `a ** b` | `pow(a, b)` | [OS1001](/script/errors/syntax#os1001) |
| `i++` | `i += 1` | [OS1001](/script/errors/syntax#os1001) |
| `i--` | `i -= 1` | [OS1022](/script/errors/syntax#os1022) |
| `a & b`, `a \| b`, `~a` | Nothing: there are no bitwise operators | [OS1001](/script/errors/syntax#os1001) |
| `a < b < c` | `a < b and b < c` | [OS1008](/script/errors/syntax#os1008) |
| `if x = 5` | `if x == 5` | [OS1006](/script/errors/syntax#os1006) |
| `a; b` | Two lines | [OS1007](/script/errors/syntax#os1007) |
| `{ ... }` | Indentation | [OS1001](/script/errors/syntax#os1001) |

```openscript
up = close > open
down = !up
```

The console under the editor shows each of these with its line, its code and the fix:


## Absent operands at a glance

| Operator | With an absent operand |
|---|---|
| unary `-`, unary `+`, `not` | Absent |
| `*`, `/`, `%`, `+`, `-` | Absent if either operand is |
| `+` on strings | Absent if either operand is |
| `<`, `<=`, `>`, `>=` | Absent if either operand is |
| `==`, `!=` | Never absent: `none == none` is `true` |
| `and`, `or` | Three-valued, see [the table above](#logical-operators) |
| `? :` condition | An absent condition takes the false arm |
| `a[n]` with `n` absent | Absent |

**Related.** [Types and values](/script/language/types-and-values), [Absent values](/script/language/absent-values), [Control flow](/script/language/control-flow), [Bars and history](/script/language/bars-and-history), [Operators reference](/script/reference/operators), [Math](/script/reference/math)


## Control flow

Source: https://openalgo.in/script/language/control-flow

Control flow decides which statements run on a bar and how many times. OpenScript has `if`, `else if` and `else` for choosing a block, the ternary for choosing a value, `for` and `while` for loops, `break` and `continue` to leave a loop or skip to its next round, and `switch` for choosing among several blocks. This page covers every form, what each one does when its condition is absent, the budget that stops a runaway loop, and the loops the library already writes for you.

## Everything runs inside one bar

The file is the body of the per-bar loop: for each bar, oldest first, the engine runs every top-level statement from the first line to the last. So every form on this page runs **inside one bar**. An `if` decides what happens on this bar. A `for` runs to completion within this bar. Nothing here carries a value to the next bar; that is what `var` is for (see [Persistence](/script/language/persistence)).

One consequence first, because it is the most common early mistake. `plot()`, `plotCandles()`, `fill()`, `level()`, `table()` and `input()` define the fixed shape of the study, which must be known before bar 0. They may appear only at the top level, never inside a block. Putting one of the first five inside an `if` is error [OS3006](/script/errors/arguments#os3006), and an `input()` there is [OS3007](/script/errors/arguments#os3007). To hide output on some bars, give it the absent value:

```openscript
version 1
study("EMA while trending", overlay = true)

ema20 = ema(close, 20)
trending = ema20 > ema(close, 50)

plot(trending ? ema20 : none, "EMA 20", aqua)  // correct: none leaves a gap
```

## Blocks

A block is the set of lines indented under a header line (`if`, `else`, `for`, `while`, `case`, `default` or a multi-line `fn`). It ends at the first line indented the same as the header or less. Indent with spaces only, and give every line of a block exactly the same indentation. [Script structure](/script/language/script-structure#blocks-and-indentation) has the full rules and their errors.

## if, else if, else

```openscript
version 1
study("Candle colours", overlay = true)

avgVolume = sma(volume, 20)

if close > open and volume > avgVolume
    barColor(lime)
else if close < open and volume > avgVolume
    barColor(red)
else
    barColor(gray)
```

`else if` is two words on one line and does not add a level of indentation. Any number of `else if` branches may follow an `if`, and at most one `else` comes last.

The condition must be a `bool` or the absent value. A number or a string is not a condition, and writing one is error [OS2011](/script/errors/names-and-types#os2011). Writing `=` where you meant `==` is [OS1006](/script/errors/syntax#os1006), because assignment is a statement and can never be a condition.

```openscript
mode = 1
if mode = 1
    signal("MODE ONE")
```

### An absent condition takes the false branch

A condition that evaluates to `none` takes the false branch. This is the one place the language absorbs absence instead of passing it on, and it has to: execution must go somewhere.

```openscript
version 1
study("Strong bars", overlay = true)

len = input(20, "Volume average", min = 2, max = 500)
avgVolume = sma(volume, len)

// avgVolume is absent for the first len - 1 bars, so the first test is absent
// there and its block does not run. A bar cannot be strong against an
// average that does not exist yet.
if close > open and volume > avgVolume
    signal("STRONG")
else if close < open
    signal("WEAK")
```

Notice what the `else if` does **not** mean. It does not mean "every bar the first test did not catch". During warmup the first test is absent and the second may still be true, and after warmup a bar can fail the first test for two different reasons. When absence is possible, an `else` is not proof of the opposite.

The safe pattern is to give a name its "we do not know yet" value above the branch, and refine it only when the inputs are present:

```openscript
r = rsi(close, 14)

zone = "unknown"
if not isNone(r)
    zone = r > 70 ? "high" : (r < 30 ? "low" : "mid")

// Warmup bars stay "unknown", so they are not shaded as "mid".
background(zone == "mid" ? fade(gray, 90) : none)
```

A condition written as a literal `true` or `false` makes one branch dead, and is warning [OS8017](/script/errors/warnings#os8017). It is usually a test pinned during debugging and left behind.

## The ternary chooses a value

`cond ? a : b` gives `a` when the condition is true and `b` otherwise. Prefer it whenever a decision produces a value rather than an action: it keeps a plot at the top level, keeps a colour on one line, and leaves no branch that can forget to assign.

```openscript
r = rsi(close, 14)
tint = close > open ? lime : red
zone = isNone(r) ? none : r > 70 ? 1 : r < 30 ? -1 : 0

plot(r, "RSI", tint)
plot(zone, "Zone: 1 above 70, -1 below 30")
```

The ternary groups right to left, so a chain reads top to bottom as a list of cases with the last as the default. Both arms must have the same type, or one arm may be `none`. Only the taken arm is evaluated, and an absent condition takes the false arm, exactly as `if` does; that is why the chain above tests `isNone(r)` first. [Operators](/script/language/operators#the-ternary) covers the ternary in full.

## for

`for` has two forms. The range form counts, and is inclusive at both ends. The `in` form walks an array.

```openscript
version 1
study("Loop forms", precision = 2)

// Range form: ten iterations, i = 0, 1, 2, ... 9.
total = 0.0
for i = 0 to 9
    total += close[i]

// Counting down needs step -1. The last value written is close[0].
newest = 0.0
for i = 9 to 0 step -1
    newest = close[i]

// The in form visits each element of an array.
levels = [20.0, 50.0, 80.0]
sumLevels = 0.0
for lvl in levels
    sumLevels += lvl

plot(total / 10, "Mean of 10 closes", aqua)
plot(newest, "Close, read last", orange)
plot(sumLevels, "Sum of levels", gray)
```

| Rule | Detail |
|---|---|
| `step` defaults to `1` | Write it only when it is something else |
| The range is never reversed for you | `for i = 9 to 0` runs zero times, and the compiler warns with [OS8015](/script/errors/warnings#os8015). Write `step -1` to count down |
| A step of `0` | [OS3004](/script/errors/arguments#os3004), because that loop could never finish |
| An absent start, end or step | [OS4013](/script/errors/runtime#os4013) stops the script at that bar, rather than quietly running the loop zero times during warmup |
| The loop variable belongs to the loop | It does not exist after the loop ends |
| The loop variable cannot be assigned in the body | [OS2006](/script/errors/names-and-types#os2006). Use `break` to leave early |
| The `in` form visits indices `0` to `size - 1` as measured on entry | Elements appended during the loop are not visited |
| If the array shrinks past the loop's position | The loop stops |

```openscript
for i = 0 to 9
    if close[i] > high
        i = 9
```

```openscript
lastAbove = -1
for i = 0 to 9
    if close[i] > high
        lastAbove = i
        break
plot(lastAbove, "Bars back to a close above this high")
```

## while

```openscript
prices = [22000.0, 22100.0, 22200.0, 22300.0]
i = 0
while i < size(prices) and prices[i] < close
    i += 1
plot(i, "Levels below the close")
```

The condition is checked before each iteration, with the same rule as `if`: a `bool` or absent, and an absent condition ends the loop.

A `while` has no counter of its own, so the body must move towards the exit, and it pays to write the bound into the condition rather than trust the data. The condition above has its two tests in the right order: the bound first, so `prices[i]` is never read out of range, and the data test second. Reversed, it would read `prices[i]` before checking `i`, and once `i` reached 4 on a bar where every level is below the close, that read is error [OS4004](/script/errors/runtime#os4004). Because `and` skips its right side when the left side is `false`, the bound protects the read.

## break and continue

`break` leaves the innermost `for` or `while`. `continue` skips the rest of this iteration and goes on to that loop's next one. Either one outside a loop is [OS1009](/script/errors/syntax#os1009); to leave a function early, use `return`.

```openscript
version 1
study("Mean body, skipping gaps", precision = 2)

lookback = input(20, "Lookback", min = 2, max = 500)

total = 0.0
seen = 0

for i = 0 to lookback - 1
    // continue says "this bar has nothing to contribute" and keeps the
    // accumulation at one level of indentation.
    if isNone(close[i]) or isNone(open[i])
        continue
    total += abs(close[i] - open[i])
    seen += 1

plot(seen > 0 ? total / seen : none, "Mean body", aqua, width = 2)
```

## switch

`switch` chooses one block among several. It is a statement, not an expression, and it has two forms.

**The value form** compares a subject with each `case`:

```openscript
method = input("medium", "Speed", options = ["fast", "medium", "slow", "verySlow"])

len = 21
switch method
    case "fast"
        len = 9
    case "slow", "verySlow"
        len = 50
    default
        len = 21

plot(sma(close, len), "Average", aqua)
```

**The condition form** has no subject and takes the first arm whose condition is true:

```openscript
r = rsi(close, 14)

zone = "mid"
switch
    case r > 70
        zone = "high"
    case r < 30
        zone = "low"
    default
        zone = "mid"

barColor(zone == "high" ? red : zone == "low" ? lime : none)
```

| Rule | Detail |
|---|---|
| Arms do not fall through | Each arm ends at the next `case` or `default`, and only one arm runs |
| A `case` may list several values, separated by commas | Every value must have the subject's type, or it is [OS2003](/script/errors/names-and-types#os2003) |
| `default` is optional and must be last | Anywhere else is [OS1017](/script/errors/syntax#os1017). With no `default` and no match, nothing happens |
| An absent `case` condition in the condition form | Is not taken, like any absent condition. In the example above, the warmup bars fall through to `default` |
| An arm declares nothing that outlives it | A name the arms set must be declared before the `switch` |

The last rule follows from block scope: a name first assigned inside an arm belongs to that arm. So declare the name above the `switch` with the value a reader should assume when no arm matches, and let the arms refine it. That makes the "declared in one arm only" bug impossible to write.

```openscript
method = input("fast", "Speed", options = ["fast", "slow"])
switch method
    case "fast"
        len = 9
    case "slow"
        len = 21
plot(sma(close, len), "Average")
```

```openscript
version 1
study("Selectable average", overlay = true, precision = 2)

method = input("medium", "Speed", options = ["fast", "medium", "slow"])
src = input(close, "Source")

// Declared before the switch, with the value to assume when no arm matches.
len = 21

switch method
    case "fast"
        len = 9
    case "slow"
        len = 50

plot(sma(src, len), "Average", aqua, width = 2)
```

## The loop budget

A script runs inside a chart, often inside a browser tab, and a loop whose exit is never reached would freeze it. So every loop is counted.

**Every iteration of every loop, summed over all the loops run during one bar, counts against a per-bar budget of 2,000,000 iterations. Going over is error [OS5001](/script/errors/limits#os5001), which names the loop that was running when the budget ran out.**

- **It is per bar, not per loop.** One nested loop and ten loops in a row are treated alike, and splitting a loop in two does not get around it.
- **It resets on every bar.** A long history is never a reason to fail by itself. Fifty thousand bars doing two hundred iterations each is fine; one bar doing three million is not.
- **It stops the script rather than leaving the loop.** OS5001 stops the script at that bar and the chart shows the error. Quietly leaving the loop would produce a plausible wrong number, which is worse than no number.

The budget is a default, not a ceiling. A script that genuinely needs more raises it in one place, the line directly under the declaration:

```openscript
limits(loops = 5_000_000)
```

`limits()` is optional, appears at most once, and takes literal numbers; its two options are `loops` and `history`. The application running the script may refuse a value larger than it will run, with [OS5003](/script/errors/limits#os5003). [Script structure](/script/language/script-structure#the-limits-line) has a complete script that needs the line, and [Limits](/script/writing/limits) covers every budget.

A separate ceiling applies to nesting: an expression, block or call nested far deeper than anything written by hand is [OS5005](/script/errors/limits#os5005). Give the inner expression a name and use the name.

## Loops you do not need

Most loops in a first script are a window calculation written out by hand. The library already has them, each exact about its warmup and cheaper than a loop.

| What you want | Write this | Reference |
|---|---|---|
| The highest high of the last 20 bars | `highest(high, 20)` | `highest()` |
| How many bars back that high was | `highestBars(high, 20)` | `highestBars()` |
| The total of the last 20 closes | `sum(close, 20)` | `sum()` |
| The mean of the last 20 closes | `sma(close, 20)` | `sma()` |
| The mean, ignoring absent bars | `avgSkip(close, 20)` | `avgSkip()` |
| How many of the last 50 bars closed up | `count(close > open, 50)` | `count()` |
| Bars since a condition last held | `barsSince(cond)` | `barsSince()` |
| The close when a condition last held | `valueWhen(cond, close)` | `valueWhen()` |
| A running total from the first bar | `cum(volume)` | `cum()` |
| The change over ten bars | `change(close, 10)` | `change()` |
| Whether the last five changes were all up | `rising(close, 5)` | `rising()` |
| Where this bar ranks in its window | `percentRank(close, 100)` | `percentRank()` |
| The middle value of a window | `median(close, 20)` | `median()` |
| A crossing of two series | `crossUp(fast, slow)` | `crossUp()` |

The most common case of all needs no loop: a fixed lookback reads history directly with `[]`.

```openscript
// By hand.
hi = close
for i = 1 to 19
    hi = max(hi, close[i])

// The same, in one call, with an exact warmup.
hi20 = highest(close, 20)

plot(hi, "By hand", gray)
plot(hi20, "Library", aqua)
```

Both are absent for the first nineteen bars, because `close[i]` past the start of history is absent and `max()` of an absent value is absent. The library call's warmup is fixed and documented, though, which is how a study matches another implementation of the same indicator to the last decimal.

**Keep a running total instead of recomputing a window.** A loop over the last two hundred bars on every bar does two hundred times the work of updating one number. When the quantity is cumulative, a `var` and one addition per bar give the same answer, and rollback keeps it correct on the forming bar:

```openscript
version 1
study("Cumulative signed volume", precision = 0)

var running = 0.0

// One addition per bar, instead of a loop back to the first bar. The guard
// keeps one bar with no volume from making the total absent for ever.
if not isNone(volume)
    running += close > open ? volume : (close < open ? -volume : 0)

plot(running, "Signed volume", aqua, width = 2)
```

### Loops worth writing

A loop is the right tool when it walks a collection the script built itself, not a window of bars. The most important case is a list you remove from: walk it downwards, so removing element `i` cannot renumber an element the loop has yet to visit.

```openscript
version 1
study("Unbroken pivot highs", overlay = true, precision = 2)

leftBars = input(5, "Pivot left bars", min = 1, max = 50)
rightBars = input(5, "Pivot right bars", min = 1, max = 50)

var levels: array<number> = []

// Counted downwards, because remove() renumbers every element above the one
// it removes, and an ascending loop would step over the next one. While the
// list is empty the range runs from -1 to 0 downwards, and never runs.
for i = size(levels) - 1 to 0 step -1
    if close > element(levels, i)
        remove(levels, i)

pivot = pivotHigh(high, leftBars, rightBars)
if not isNone(pivot)
    push(levels, pivot)

plot(size(levels), "Pivot highs still unbroken", aqua, width = 2, style = "step")
```

[Profiling and speed](/script/writing/profiling) covers what loops and history reads cost.

## Errors and warnings you may meet

| Code | Means | Fix |
|---|---|---|
| [OS1002](/script/errors/syntax#os1002) | A tab in indentation | Indent with spaces |
| [OS1003](/script/errors/syntax#os1003) | Indentation does not match the block | Make every line of a block match exactly |
| [OS1006](/script/errors/syntax#os1006) | `=` used as a condition | Write `==` |
| [OS1009](/script/errors/syntax#os1009) | `break` or `continue` outside a loop | Move it into the loop, or use `return` |
| [OS1010](/script/errors/syntax#os1010) | A header with no body | Indent the body under it |
| [OS1017](/script/errors/syntax#os1017) | `case` or `default` out of place | Keep arms inside the `switch`, `default` last |
| [OS2006](/script/errors/names-and-types#os2006) | The loop variable is assigned in the body | Use `break`, or a separate name |
| [OS2011](/script/errors/names-and-types#os2011) | A condition is not a `bool` | Write the test out |
| [OS3004](/script/errors/arguments#os3004) | A `for` step of zero | Use a non-zero step |
| [OS3006](/script/errors/arguments#os3006) | `plot`, `plotCandles`, `fill`, `level` or `table` inside a block | Move it to the top level; pass `none` to hide it |
| [OS4013](/script/errors/runtime#os4013) | A loop bound is absent | Guard the loop, or give the bound a value with `orElse` |
| [OS5001](/script/errors/limits#os5001) | The per-bar loop budget ran out | Fix the exit condition, or raise `limits(loops = ...)` |
| [OS8015](/script/errors/warnings#os8015) | The loop never runs | Add `step -1`, or swap the bounds |
| [OS8017](/script/errors/warnings#os8017) | The condition is constant | Restore the test that was meant |

**Related.** [Execution model](/script/language/execution-model), [Absent values](/script/language/absent-values), [Variables and scope](/script/language/variables-and-scope), [Operators](/script/language/operators), [Collections](/script/language/collections), [Series functions](/script/reference/series)


## User functions

Source: https://openalgo.in/script/language/functions

A user function gives a name to a computation, so you write it once and use it in several places, and a fix goes in one line instead of four. This page covers the two forms of `fn`, parameters with types and defaults, named arguments at the call, returning one value or several, the scope rules inside a function, and the rule that matters most: a function that remembers something between bars keeps a separate memory at each place you call it. A function in OpenScript is not a value and cannot be passed around; it is a named piece of the script.

## A complete example

```openscript
version 1
study("Z score", precision = 2)

len = input(50, "Length", min = 5, max = 500)
band = input(2, "Band", min = 1, max = 5)

// How many standard deviations the source sits from its own mean.
fn zscore(src, n) =>
    m = sma(src, n)
    s = stdev(src, n)
    (src - m) / s

level(0, "Mean", gray)
level(band, "Upper", fade(red, 50))
level(0 - band, "Lower", fade(lime, 50))

plot(zscore(close, len), "Close", aqua, width = 2)
plot(zscore(hlc3, len), "Typical price", orange)
```

Two things in this script come up again below. The two calls to `zscore` are two separate call sites (a call site is one place in the source where a function is called), each with its own copy of everything `sma()` and `stdev()` remember. And on a window where price never moved, `s` is zero, so the division gives the absent value rather than an error, and the plot shows a gap on exactly those bars.

## The two forms

A function is declared with `fn`. In the single-line form, everything after `=>` is the result. In the multi-line form, the body is indented under the header and its last line, a bare expression, is the result.

```openscript
fn typicalPrice() => (high + low + close) / 3

fn zscore(src, len) =>
    m = sma(src, len)
    s = stdev(src, len)
    (src - m) / s

plot(typicalPrice(), "Typical price", aqua)
plot(zscore(close, 20), "Z score", orange, scale = "left")
```

Most helpers are one line, and the short form keeps them one line.

## Where a function may appear

| Rule | If you break it |
|---|---|
| A function is declared at the top level of the file | [OS1023](/script/errors/syntax#os1023) inside a block |
| Functions cannot be nested inside other functions | [OS1023](/script/errors/syntax#os1023) |
| A function may be called above its declaration | Not an error |
| A name can be declared as a function only once | [OS2017](/script/errors/names-and-types#os2017) |
| A function is not a value | [OS2014](/script/errors/names-and-types#os2014): `src = ema` has no meaning |
| A function cannot call itself, directly or through others | [OS2005](/script/errors/names-and-types#os2005) |
| A body cannot declare the study's shape | `plot`, `plotCandles`, `fill`, `level` or `table` inside a body is [OS3006](/script/errors/arguments#os3006); `input` is [OS3007](/script/errors/arguments#os3007) |

The set of functions, like the set of plots, is fixed before bar 0. Because the compiler collects every function declaration before it checks any body, you can keep helpers at the bottom of the file:

```openscript
version 1
study("Helper below", overlay = true)

plot(smoothed(close), "Smoothed", aqua)

fn smoothed(src) => ema(src, 9)
```

## Parameters, defaults and named arguments

A parameter may carry a type annotation (its type, written after a colon), a default value, both or neither.

```openscript
fn band(src: series number, len: number = 20, mult: number = 2) =>
    basis = sma(src, len)
    dev = mult * stdev(src, len)
    basis + dev

plot(band(close), "Upper band", aqua)
plot(band(close, mult = 3), "Wider band", gray)
```

An annotation is optional and is checked when it is there. Without one, the type is inferred from how the parameter is used and from what the calls pass. Annotate anything another person will call, because the header is the only part of a function a reader sees before the body; leave the annotations off a three-line private helper if they add nothing. The type names are `number`, `string`, `bool`, `color` and `array<T>`, with `series` in front for a per-bar value. There is no integer type, so a length is a `number`; writing `int` is [OS2016](/script/errors/names-and-types#os2016).

At a call, arguments may be positional (matched by their order), named (`mult = 3`), or positional followed by named:

| Call | Legal | Means |
|---|---|---|
| `band(close)` | Yes | `len` is 20, `mult` is 2 |
| `band(close, 50)` | Yes | `len` is 50, `mult` is 2 |
| `band(close, mult = 3)` | Yes | `len` stays 20, `mult` is 3 |
| `band(src = close, len = 50, mult = 3)` | Yes | Every argument named |
| `band(close, len = 20, 3)` | No | [OS3005](/script/errors/arguments#os3005): a positional argument after a named one |
| `band(close, 50, 2, 1)` | No | [OS3001](/script/errors/arguments#os3001): too many arguments |
| `band(close, multiple = 3)` | No | [OS3002](/script/errors/arguments#os3002): no such parameter; the fix lists the names that exist and the closest one |
| `band(close, 50, len = 30)` | No | [OS3013](/script/errors/arguments#os3013): `len` given twice |

Named arguments keep a call readable once a function grows a third and fourth option, and defaults in the header show which options a caller may leave out without opening the body.

A parameter name follows the variable rules: it cannot be a reserved word such as `color` or `step` ([OS1019](/script/errors/syntax#os1019)), cannot repeat within the list ([OS2018](/script/errors/names-and-types#os2018)), and cannot reuse a file-scope name or a library name such as `close` ([OS2002](/script/errors/names-and-types#os2002)).

## Series parameters and history

A parameter that receives a series accepts the caller's expression, and `[]` inside the function reads the real history of that expression:

```openscript
fn barChange(src) => src - src[1]

plot(barChange(hlc3), "Change in typical price", aqua)
```

The engine keeps the per-bar values of `hlc3` for that call, so `src[1]` is the previous bar's typical price and nothing has to be prepared by hand. On bar 0 there is no previous bar, so the result is absent and the plot starts at bar 1. A parameter annotated as a plain `number` has no history, and `[]` on it is [OS2004](/script/errors/names-and-types#os2004); annotate it `series number` if the body reads `[]` on it.

**A plain value is broadcast into a series parameter.** Passing `9` where a series is expected means nine on every bar, so one function works with literals, with inputs and with per-bar values.

**A local name inside a function has no history.** `[]` works on a built-in series, a top-level name, a call that returns a series and a series parameter. A name first assigned inside a function body is none of those, so `[]` on it is [OS2004](/script/errors/names-and-types#os2004):

```openscript
fn wrong(src) =>
    mid = (src + src[1]) / 2
    mid - mid[1]
```

Take the history from the parameter instead, or compute the value at the top level of the file, name it there and pass it in:

```openscript
fn midChange(src) =>
    mid = (src + src[1]) / 2
    mid - (src[1] + src[2]) / 2

plot(midChange(close), "Change of the two-bar midpoint", aqua)
```

## Returning a value

If the last line of the body is a bare expression, that is the result. A `return expression` statement leaves the function at once with that value, and a bare `return` leaves with the absent value.

```openscript
version 1
study("Stop distance", overlay = true, precision = 2)

fn positionStop(side, entry, distance) =>
    if isNone(entry) or isNone(distance)
        return none
    if side > 0
        return entry - distance
    entry + distance

longStop = positionStop(1, close, 2 * atr(14))
plot(longStop, "Long stop, 2 ATR under the close", red)
```

Use the final expression for the ordinary path and `return` for an early exit. A function whose body ends in something other than an expression, and which never reaches a `return`, gives the absent value; if a plot built on it never starts, check that first.

## Returning several values

A function with more than one result returns an `array<number>` holding this bar's results in a documented order. The library does the same: `macd()` returns `[macd, signal, histogram]` and `bollinger()` returns `[basis, upper, lower]`. Follow that convention and a reader who has met one has met them all.

```openscript
version 1
study("Deviation bands", overlay = true, precision = 2)

len = input(20, "Length", min = 2, max = 500)
mult = input(2, "Deviation multiple", min = 1, max = 5)

// Returns [basis, upper, lower], in that order, always.
fn bands(src, n, k) =>
    basis = sma(src, n)
    dev = k * stdev(src, n)
    [basis, basis + dev, basis - dev]

// Read the elements into top-level names at once. Names read better, and a
// top-level name has history.
b = bands(close, len, mult)
basis = b[0]
upper = b[1]
lower = b[2]

top = plot(upper, "Upper", aqua)
bottom = plot(lower, "Lower", aqua)
plot(basis, "Basis", orange, width = 2)
fill(top, bottom, fade(aqua, 90))
```

Three rules make this safe:

- **The array never changes length.** All three elements exist from bar 0, each absent until its own warmup ends. An array that grew as warmup finished would make `b[2]` an out-of-range error at the left edge of the chart only, the worst place for a bug to hide.
- **One function, not three.** Three separate functions would be three call sites, each with its own copy of the averaging state, doing the shared work three times per bar.
- **`b[1]` is an element, not a bar.** On an array `[]` means an element; on a series it means history. Where a reader could doubt which, write `element()` or `history()`.

## Scope inside a function

A function body is a block scope inside the file scope, which is inside the global scope where the library lives. A name first assigned in the body belongs to the body and cannot be read outside it.

A body can read a file-scope name, but it cannot change one. An assignment inside a function always declares a name of the function's own, so assigning a name that already exists in the file scope is a second declaration of it, and is [OS2002](/script/errors/names-and-types#os2002):

```openscript
len = 20

fn helper(src) =>
    len = 9
    sma(src, len)
```

Prefer functions that read only their parameters. Such a function can be moved, tested and later shared without carrying the rest of the file with it, and its answer does not depend on where in the file it is called. [Variables and scope](/script/language/variables-and-scope) covers the scope rules in full.

## A function that remembers: state per call site

A function body may declare a `var`, and may call library functions that keep state of their own, such as `ema()`, `rma()`, `barsSince()` or `cum()`. Either makes the function stateful.

**State belongs to the call site, not to the function. Two calls in two places are two independent pieces of state.**

```openscript
version 1
study("Bars since", precision = 0)

fn since(cond) =>
    var n = none
    if cond
        n = 0
    else if not isNone(n)
        n = n + 1
    n

sinceUp = since(close > open)  // its own counter
sinceHigh = since(high > high[1])  // a separate counter

plot(sinceUp, "Bars since an up close", aqua)
plot(sinceHigh, "Bars since a higher high", orange)
```

This is what makes a stateful helper reusable. If the two calls shared one `n`, the second would corrupt the first and the function could be used only once per script.

It works because the compiler gives every call site its own fixed slots for state, once, before bar 0. Nothing is allocated while a bar runs, which keeps the rule cheap, and it is also why recursion is not allowed: a function calling itself would need a number of slots nobody knows until the bar runs.

### The bug this causes

The rule bites when you assume the opposite: that a function holding a counter holds one counter for the whole script.

```openscript
version 1
strategy("Tagged entries", overlay = true)

fast = ema(close, 9)
slow = ema(close, 21)

// This looks like one counter for the file. It is two: one per call site.
fn nextTag() =>
    var n = 0
    n += 1
    "T" + text(n, 0)

if crossUp(fast, slow)
    buy(qty = 1, tag = nextTag())  // counts T1, T2, T3, ...

if crossDown(fast, slow)
    sell(qty = 1, tag = nextTag())  // also counts T1, T2, T3, ...
```

The first long entry is tagged `T1`, and so is the first short entry. A tag is meant to name one order in the backtest report and to let the script refer to that part of its position later, as `close(tag = "T1")` does, and a tag two entries share names neither of them on its own. Nothing errors. The compiler does warn, with [OS8001](/script/errors/warnings#os8001) on each call, because each `nextTag()` sits inside a branch and advances only on the bars that branch runs. Take that warning as the thread to pull.

The fix is to decide where the state belongs:

```openscript
version 1
strategy("Tagged entries", overlay = true)

fast = ema(close, 9)
slow = ema(close, 21)

// One counter, in the file scope, where there is exactly one of it.
var tagSeq = 0

// No var and no stateful call: its call sites share nothing to drift apart.
fn tagFor(n) => "T" + text(n, 0)

if crossUp(fast, slow)
    tagSeq += 1
    buy(qty = 1, tag = tagFor(tagSeq))

if crossDown(fast, slow)
    tagSeq += 1
    sell(qty = 1, tag = tagFor(tagSeq))
```

| The state must be | Put it in | Because |
|---|---|---|
| Shared by the whole script | A `var` in the file scope | There is exactly one file scope |
| Separate for each use | A `var` inside the function | Each call site gets its own copy |
| Separate for each element of a list | An array the script owns, indexed | A call site is one site however many times a loop runs it |

### A call inside a loop is still one call site

A loop body is written once, so a call in it is one call site with one piece of state, and every iteration writes into it. That is right for a running total across the loop, and wrong when you wanted one counter per element:

```openscript
fn countIf(cond) =>
    var n = 0
    if cond
        n += 1
    n

tests = [close > open, high > high[1], volume > volume[1]]

// Wrong: every iteration writes into the same n, so this is one counter
// shared by three questions.
shared = 0
for i = 0 to size(tests) - 1
    shared = countIf(element(tests, i))
plot(shared, "One counter, three questions", red)
```

When you want state per element, the element's index has to appear somewhere, and an array is where it appears:

```openscript
version 1
study("Three counters", precision = 0)

tests = [close > open, high > high[1], volume > volume[1]]

// One counter per question, indexed by the loop.
var counts: array<number> = [0.0, 0.0, 0.0]
for i = 0 to size(tests) - 1
    if element(tests, i)
        set(counts, i, element(counts, i) + 1)

plot(element(counts, 0), "Up bars", aqua)
plot(element(counts, 1), "Higher highs", orange)
plot(element(counts, 2), "Rising volume", purple)
```

### A call site that does not run

**A call site that does not run on a bar leaves its value absent for that bar, and its state does not advance.** The previous value is not carried forward either.

```openscript
trending = close > ema(close, 50)
if trending
    e = ema(close, 20)  // advances only on trending bars
    barColor(close > e ? lime : red)
```

Running the call anyway would execute code the script said to skip, and carrying the last value forward would draw a flat line that looks like data. Absence is honest: the line breaks on exactly the bars the call was skipped. Because this is almost always a mistake, the compiler reports [OS8001](/script/errors/warnings#os8001). The fix is always the same: compute on every bar at the top level, and move the condition from around the calculation to around the value.

```openscript
e = ema(close, 20)
trending = close > ema(close, 50)
plot(trending ? e : none, "EMA while trending", aqua)
```

## Recursion is not allowed

Recursion means a function calling itself. It is not allowed, directly or through a cycle of functions, and the compiler reports [OS2005](/script/errors/names-and-types#os2005) and names the cycle.

```openscript
fn lookbackSum(n) =>
    if n <= 0
        return 0
    close[n] + lookbackSum(n - 1)
```

Write the loop instead:

```openscript
fn lookbackSum(n) =>
    total = 0.0
    for i = 0 to n
        total += close[i]
    total

plot(lookbackSum(4), "Sum of the last five closes", aqua)
```

## A worked example for F&O

A helper that turns a capital amount into whole lots of a futures or options contract. `chart.lotSize` is the contract's lot size when the application running the script states it, and absent when it does not, so the script takes a lot size from its settings as the fallback. That fallback is a genuine answer the user gives, not an invented number.

```openscript
version 1
study("Lots for the capital", precision = 0)

capital = input(500000, "Capital, in rupees", min = 10000)
lotSetting = input(1, "Lot size, when the chart does not state one", min = 1)

// The instrument's own lot size when it is known, otherwise the setting.
lotSize = orElse(chart.lotSize, lotSetting)

// Whole lots the capital pays for in full at this bar's price.
fn lotsFor(amount: number, price: series number, lot: number) =>
    floor(amount / (price * lot))

plot(lotsFor(capital, close, lotSize), "Lots", aqua, style = "step")
```

On an option chart the price is the premium, so this is the number of lots the capital buys outright. On a futures chart it is the number of lots the capital covers at full contract value, which is more cautious than sizing by margin.

## Errors and warnings you may meet

| Code | Means | Fix |
|---|---|---|
| [OS1019](/script/errors/syntax#os1019) | A reserved word used as a parameter name | Rename the parameter |
| [OS1023](/script/errors/syntax#os1023) | A function declared inside a block or another function | Move it to the top level |
| [OS2002](/script/errors/names-and-types#os2002) | A local or parameter reuses an outer name | Rename it |
| [OS2004](/script/errors/names-and-types#os2004) | `[]` on something with no history | Take history from a series parameter, or name the value at the top level |
| [OS2005](/script/errors/names-and-types#os2005) | A function calls itself | Write a loop |
| [OS2014](/script/errors/names-and-types#os2014) | A function used as a value | Call it and use the result |
| [OS2016](/script/errors/names-and-types#os2016) | An unknown type in an annotation | Use `number`, `string`, `bool`, `color` or `array<T>` |
| [OS2017](/script/errors/names-and-types#os2017) | Two functions share a name | Rename one |
| [OS2018](/script/errors/names-and-types#os2018) | A parameter name appears twice | Rename the second |
| [OS3001](/script/errors/arguments#os3001) | Wrong number of arguments | Check the header; defaults let you pass fewer |
| [OS3002](/script/errors/arguments#os3002) | An unknown named argument | Use a name the fix lists |
| [OS3005](/script/errors/arguments#os3005) | A positional argument after a named one | Put positional arguments first |
| [OS3013](/script/errors/arguments#os3013) | An argument given twice | Keep one |
| [OS8001](/script/errors/warnings#os8001) | A stateful call inside a branch or a loop | Compute at the top level, branch on the value |

**Related.** [Variables and scope](/script/language/variables-and-scope), [Execution model](/script/language/execution-model), [Persistence](/script/language/persistence), [Collections](/script/language/collections), [Objects and methods](/script/language/objects-and-methods), [Libraries](/script/language/libraries), [Absent values](/script/language/absent-values)


## Bars and history

Source: https://openalgo.in/script/language/bars-and-history

A script runs once per bar, and on each bar it can look back at the bars before it. This page covers the history operator `[n]` that does the looking: which values you can look back through, what you get on the first bars of a chart where there is nothing behind you, which offsets are errors, and what a long lookback costs. Almost every study reads the past, so the rules here decide whether yours draws the right thing on the oldest bars and whether it keeps running on a long chart.

## A first example

On a daily NSE chart, `open - close[1]` is the overnight gap: this bar's open against the previous session's close. Three reads of the past, each a single expression:

```openscript
version 1
study("Three reads", overlay = true, precision = 2)

// The overnight gap: this bar's open against the previous bar's close.
gap = open - close[1]

// The change over one bar, which the library also spells change(close).
diff = close - close[1]

// Whether the previous bar's range sits inside the one before it.
inside = high[1] < high[2] and low[1] > low[2]

plot(gap, "Gap", aqua)
plot(diff, "Change", orange)

if inside
    signal("INSIDE", at = "above")
```

The inside-bar test reads `high[1]` and `high[2]`, not `high` and `high[1]`. An inside bar is only known once the inner bar has finished, and the bar you are on may still be forming. Asking about bars that have closed is the honest way to write it; [Realtime and confirmation](/script/language/realtime-and-confirmation) explains why.

## The history operator

A **series** is a value with one entry per bar: a number, a bool, a string or a colour. Reading a series bare gives its value on the bar being executed. Writing `[n]` after it gives the value `n` bars earlier.

```openscript
last = close          // this bar's close
prev = close[1]       // the previous bar's close
same = close[0]       // identical to close
old  = close[20]      // the close twenty bars ago

plot(last - prev, "One bar change")
plot(same - old, "Twenty bar change")
```

The offset always counts backwards from the bar being executed, never forwards and never from the start of the data. On bar 500, `close[3]` is bar 497's close; on bar 501 the same expression reads bar 498. The window slides with the bars.

`history()` is the same read written as a call: `history(close, 3)` means `close[3]`. Use it where a bare `[]` would be hard to read, and see [Series or array](#series-or-array) for the one place it matters.

## What has history

A value accepts `[]` as history in exactly four cases:

| Case | Example | Why it has history |
|---|---|---|
| A built-in series | `close[1]`, `volume[3]`, `time[5]` | The engine stores one value per bar from the chart's data |
| A name assigned at the top level of the file | `diff = close - open`, then `diff[1]` | The compiler keeps that name's value for every bar |
| A call that returns a series | `ema(close, 20)[1]` | Its per-bar results are kept |
| A parameter of your own function | `fn f(src) => src - src[1]` | The caller's expression is kept for that call |

Anything else has no stored past, and `[]` on it is error [OS2004](/script/errors/names-and-types#os2004). The usual case is a temporary expression:

```openscript
// The bracketed expression is a temporary, so it has no history.
if (close - open)[1] > 0
    signal("PREVIOUS BAR WAS UP")
```

The fix is one line: give the value a name at the top level and read the name's history.

```openscript
body = close - open
if body[1] > 0
    signal("PREVIOUS BAR WAS UP")
```

The rule is there for memory. Keeping a value for every bar costs space on every bar of the chart, and keeping it for every temporary inside every loop would not run fifty thousand bars in a browser tab. Naming a value at the top level is how a script says "keep this one".

## The first bars

On the oldest bar of the chart there is nothing behind you, so a read past the start of the data gives the absent value, `none`. It is not an error, it is not zero, and it is not the oldest bar repeated.

**`x[n]` is absent whenever `n` is greater than `bar.index`.**

Here is one series at the left edge of a chart whose first four closes are 100, 102, 101 and 104:

| Bar | `close` | `close[1]` | `close[2]` | `close[3]` | `close[4]` |
|---|---|---|---|---|---|
| 0 | 100 | absent | absent | absent | absent |
| 1 | 102 | 100 | absent | absent | absent |
| 2 | 101 | 102 | 100 | absent | absent |
| 3 | 104 | 101 | 102 | 100 | absent |

Absent is the truthful answer. If `close[1]` on bar 0 quietly gave `close`, then `close - close[1]` would be exactly zero on bar 0: a reading of "no change" drawn on the chart where no change was ever measured. Instead the absence carries through the subtraction and the plot leaves a gap.

Everything downstream follows the ordinary rules of [Absent values](/script/language/absent-values):

```openscript
change1  = close - close[1]           // absent on bar 0
higher   = close[1] > close[2]        // absent on bars 0 and 1, not false
isStart  = isNone(close[1])           // true on bar 0: the way to ask
prevOrNow = orElse(close[1], close)   // the previous close, or this one at the start

plot(change1, "Change")               // a gap on bar 0
plot(higher ? 1 : 0, "Higher")        // 0 on bars 0 and 1: see below
plot(isStart ? 1 : 0, "First bar")
plot(prevOrNow, "Previous close")
```

A comparison with an absent side is itself absent, and an absent condition takes the false branch of an `if` or a ternary. That is why the `Higher` plot above shows 0 on bars 0 and 1 rather than a gap: `higher` is absent there, and `higher ? 1 : 0` takes its false branch. So on the first bars `a > b` and `a <= b` are both absent, and a script that branches on one and assumes the other is its opposite takes neither path. That keeps the rule `not (a > b)` means `a <= b` true everywhere else, at the cost of one thing to remember at the left edge. Ask with `isNone()` when it matters.

## Offsets

The number inside the brackets must be a whole number of bars, at or above zero.

| Offset | Result |
|---|---|
| `0` up to `bar.index` | The value on that bar |
| Greater than `bar.index` | Absent |
| Absent | Absent |
| Not a whole number, such as `2.5` | Error [OS4001](/script/errors/runtime#os4001), and the script stops on that bar |
| Negative, such as `-1` | Error [OS4001](/script/errors/runtime#os4001), and the script stops on that bar |
| Deeper than the history the engine keeps | Error [OS4002](/script/errors/runtime#os4002). The message names the depth kept, and the fix gives the `limits()` line that raises it |

**A fractional offset is refused rather than rounded.** A lookback of 2.5 bars is a bug in the script, and rounding it would hide the bug behind a number that looks right. Say which whole number you mean:

```openscript
len  = input(20, "Length", min = 2, max = 200)
back = round(len / 2)
mid  = close[back]
plot(mid, "Close half a window ago")
```

**A negative offset is never available.** Reading the future is the one thing a per-bar language must never make easy, so there is no option or mode that turns it on for `[]`. A study that wants to draw a value to the right of the current bar shifts where the line is drawn, not what it reads: `plot(value, "Title", offset = 26)` moves the drawing and changes no number.

**An absent offset gives an absent result.** Offsets are often computed, so this happens more than you might expect:

```openscript
back      = lowestBars(low, 20)   // absent for the first 19 bars
priceThen = close[back]           // so this is absent for those bars too
plot(priceThen, "Close at the 20 bar low")
```

**OS4002 is an error, not an absent value, on purpose.** By default the engine keeps the full history of every series for the data it was given, so OS4002 only appears when a depth has been set. It means the value existed and was thrown away, which is a different fact from a value that never existed, and reporting both as a gap would hide a configuration mistake behind an innocent-looking blank.

## Looking back through an expression

There are two ways to read the past of something you computed.

**Name it at the top level.** This is the everyday answer, it costs one line, and it usually makes the script easier to read:

```openscript
version 1
study("Range expansion", precision = 2)

// Naming the range is what gives it history.
barRange = high - low
mean     = sma(barRange, 20)

// The range grew on each of the last two bars.
expanding = barRange > barRange[1] and barRange[1] > barRange[2]

plot(barRange, "Range", aqua)
plot(mean, "Mean range", orange)

if expanding and barRange > mean
    signal("EXPANDING")
```

**Use `history(expr, n)`.** It reads exactly what `[n]` would, and it accepts an expression directly. Its first value is on bar `n`.

```openscript
prevBody = history(close - open, 1)   // the previous bar's body
plot(prevBody, "Previous body")
```

A name only has history when it is assigned at the **top level**. A name first assigned inside an `if`, a loop or a function body belongs to that block, lives for one bar, and `[]` on it is OS2004:

```openscript
trending = close > ema(close, 50)
if trending
    inner = close - open
    signal(inner[1] > 0 ? "UP BEFORE" : "DOWN BEFORE")
```

When you want history for something that only has a meaning on some bars, compute it on every bar at the top level and let it be absent on the others:

```openscript
trending = close > ema(close, 50)

// A top-level name, absent on the bars you did not want.
trendBody   = trending ? close - open : none
wasPositive = orElse(trendBody[1] > 0, false)

plot(wasPositive ? 1 : 0, "Previous trend bar was up")
```

## History through a function parameter

A parameter of a function you write can carry history, and it reads the history of whatever the caller passed:

```openscript
version 1
study("Slope", precision = 4)

fn slope(src, n) => (src - src[n]) / n

plot(slope(hlc3, 5), "Typical price slope", aqua)
plot(slope(ema(close, 20), 5), "Average slope", orange)
```

Passing an expression to such a parameter makes the engine keep that expression's values **for that call**. The two calls above keep two separate series, because they are two call sites. This is the same rule that gives two calls of a stateful function such as `ema()` their own state, and [User functions](/script/language/functions) covers it in full. Two things follow:

- A call that does not run on a bar keeps nothing for that bar, so its parameter's history has a hole exactly where the call was skipped. Call at the top level and branch on the result.
- Each call site keeps its own copy, so calling one helper in ten places keeps ten series. That is the price of the convenience, and [What a deep lookback costs](#what-a-deep-lookback-costs) puts it in context.

## Series or array

The same brackets have a second meaning. `a[i]` is history when `a` is a series and **element access** when `a` is an array. The compiler knows which from the type of `a`, so nothing is decided while the script runs.

| Written | `a` is a series | `a` is an array |
|---|---|---|
| `a[1]` | The value one bar ago | The second element |
| Past the end | Absent: a value that never existed | Error [OS4004](/script/errors/runtime#os4004): a mistake in the script |
| Explicit form | `history(a, 1)` | `element(a, 1)` |

The different treatment of "past the end" is deliberate. A lookback past the start of the chart asks for a measurement that was never taken, so it is absent. An array has a size the script itself chose, so an index outside it is a bug, and it stops the script with a message naming the index and the size.

A call with several outputs, such as `macd()` or `bollinger()`, returns an array holding this bar's outputs. Read the elements with `[]`, and to look back at one of them, give it a top-level name first:

```openscript
version 1
study("MACD slope", precision = 4)

m = macd(close, 12, 26, 9)

// Name the elements. Each name is a series, so it has history.
line = m[0]
sig  = m[1]

climbing = line > line[1]
crossed  = crossUp(line, sig)

plot(line, "MACD", aqua)
plot(sig, "Signal", orange)
plot(m[2], "Histogram", gray, style = "histogram")

if climbing and crossed
    signal("UP")
```

> **History of a whole array**
The compiler accepts `history(arr, 1)` on an array, but in release 0.5.0 it gives the absent value on every bar rather than last bar's array, and indexing that result, as in `history(m, 1)[0]`, stops the script with OS4004 on the first bar. Name the element you need at the top level, as above, and read that name's history.

[Collections](/script/language/collections) covers arrays in full.

## Library functions that read history for you

Most lookbacks you would write by hand already exist as library calls, each with an exact first bar and a name that says what it means. Prefer them.

| You might write | Write instead | First value |
|---|---|---|
| `close - close[1]` | `change(close)` | bar 1 |
| `close - close[len]` | `change(close, len)` | bar `len` |
| A loop taking the largest `high[i]` | `highest(high, len)` | bar `len - 1` |
| A loop taking the smallest `low[i]` | `lowest(low, len)` | bar `len - 1` |
| A loop adding `close[i]` | `sum(close, len)` | bar `len - 1` |
| `a > b and a[1] <= b[1]` | `crossUp(a, b)` | bar 1 |
| Counting bars since a condition | `barsSince(cond)` | The first bar the condition is true |
| Remembering a value from a past condition | `valueWhen(cond, src)` | The first bar the condition is true |
| How many bars ago the window's high was set | `highestBars(high, len)` | bar `len - 1` |

Each has a full entry in the reference: `change()`, `highest()`, `lowest()`, `sum()`, `crossUp()`, `barsSince()`, `valueWhen()` and `highestBars()`.

Two of these are worth a note. `crossUp(a, b)` means "was at or below, then above", so two lines that touch and then separate count as one crossing; with a coarse tick size on a low-priced NSE stock, the strict "below, then above" version would miss real crossings. And `barsSince` and `valueWhen` are absent, not zero, until the condition has been true at least once, because zero would read as "it happened on this bar".

## What a deep lookback costs

There are three separate costs, and it helps to know which one you are paying.

**Memory, per kept series.** The compiler keeps a name's past only when some line of the program reads it with `[]`. `diff = close - open` costs nothing extra unless something writes `diff[n]`. When a series is kept, the engine keeps its full history for the data it was given, so on fifty thousand bars each kept series is fifty thousand values. The application running the script (the host, such as the /trading page) can set a bound, and a script that needs a deeper one asks for it on the line straight after its declaration:

```openscript
version 1
study("Long memory")
limits(history = 20000)

plot(close[5000], "Close 5000 bars back")
```

`limits()` takes `loops` and `history`, its values must be literal numbers, and it must be the statement immediately after the declaration ([OS3014](/script/errors/arguments#os3014) and [OS3015](/script/errors/arguments#os3015) otherwise). A host may refuse a value larger than it is willing to run, and it says so with [OS5003](/script/errors/limits#os5003) rather than quietly giving the script less than it asked for.

**Loop iterations, per bar.** A loop that walks back over history spends the per-bar loop budget: 2,000,000 iterations by default, summed over every loop on the bar. A loop of 200 is cheap on one bar and adds up across fifty thousand of them. The library call gives the same number without the loop:

```openscript
// 200 iterations on every bar.
hi = high
for i = 1 to 199
    hi = max(hi, high[i])

// The same value from a running window, with a stated first bar.
hi2 = highest(high, 200)

plot(hi, "Loop high")
plot(hi2, "Window high")
```

Both lines give the same numbers from the same first bar, bar 199, because `max()` with an absent argument is absent. The library call is still the better choice: it is one line instead of four, its first bar is stated in the reference rather than worked out by you, and it does not spend the loop budget.

**Warmup, per chain.** A lookback of `n` bars cannot have a value before bar `n`, and that absence travels through everything built on it. A study built from a 200 bar lookback of a 20 bar average has nothing to show until bar 219. [Warmup](/script/language/warmup) shows how to count it.

A rule of thumb that keeps all three in view: look back as far as the idea needs and no further, and use a library window function wherever one exists. Reading `close[1]` and `close[2]` is free. Looping over a thousand bars on every bar is a script you will want to [profile](/script/writing/profiling).

## Common mistakes

| Mistake | Symptom | Fix |
|---|---|---|
| Expecting `close[1]` to be a number on the first bar | A plot starts one bar late, or a condition never fires at the left edge | Accept the gap, or say what the first bar should use: `orElse(close[1], close)` |
| Writing `[]` on a temporary or a block name | OS2004 | Assign the value to a name at the top level and read that name |
| Reading `arr[1]` on an array and expecting last bar's value | The wrong number, silently | `arr[1]` is the second element. Name the element at the top level and read its history |
| A computed offset that is fractional or negative | OS4001 stops the script | Wrap it in `round()` or `floor()`, and clamp it with `max(0, n)` |

**Related.** [Execution model](/script/language/execution-model), [Persistence](/script/language/persistence), [Warmup](/script/language/warmup), [Absent values](/script/language/absent-values), [Realtime and confirmation](/script/language/realtime-and-confirmation), [Collections](/script/language/collections), [Series functions](/script/reference/series)


## Persistence

Source: https://openalgo.in/script/language/persistence

Every line of a script runs again on every bar, so a plain name starts each bar with no value at all. When you want a running count, a running total, a trailing level or a list that grows over the run, you declare the name with `var`, and it keeps whatever it holds from one bar to the next. This page covers how `var` works, where its initial value is set, how it differs from reading the past with `[]`, what happens to it while the newest bar is still forming, and the one narrow case for `live var`.

## A first example

This study counts how many bars have closed higher than they opened, as a percentage of all bars so far. On a daily NSE chart it reads as the share of up days.

```openscript
version 1
study("Up bar share", precision = 1)

// Set once, on the first bar, then kept from bar to bar.
var total  = 0
var upBars = 0

// These run on every bar and start from what the previous bar left.
total += 1
if close > open
    upBars += 1

plot(upBars / total * 100, "Percent of bars that closed up", aqua)
```

On bar 0 the two `var` lines create the counters and set them to zero. On every later bar the `var` lines do nothing: the value already exists, so control moves past them. The assignments below them run on every bar, and each one starts from the value the previous bar left behind.

## Why a plain name forgets

A name assigned without `var` is computed fresh on every bar. At the moment its line runs, this bar's value does not exist yet, so a line that builds on itself cannot compile:

```openscript
tally = tally + 1
plot(tally, "Tally")
```

The error is [OS2001](/script/errors/names-and-types#os2001): the name is not defined at that point in the file. Reading the previous bar's value through history does not rescue it either, because history and persistence are different things (see [Persistence is not history](#persistence-is-not-history)). A per-bar language needs a way to say "keep this one", and that is `var`.

## var

`var name = initial` declares a name whose initial value is set once and which then keeps whatever it holds from bar to bar. Compare the three kinds of name:

| | Plain name | `var` | `live var` |
|---|---|---|---|
| Assigned on every bar | Yes, by its own line | Only where the script assigns it | Only where the script assigns it |
| Value at the start of a bar | None until its line runs | What the previous bar left | What the previous execution left |
| Survives to the next bar | No | Yes | Yes |
| Restored before the forming bar runs again | Not applicable | Yes | No |
| Readable with `[]` at the top level | Yes | Yes | Yes |
| Same numbers on a realtime chart as in a backtest | Yes | Yes | No, by design |

The declaration and its initial value are one statement. `var` with no value is [OS1011](/script/errors/syntax#os1011), and the fix is to start it at `none`:

```openscript
var tally
```

```openscript
var tally = none
tally = orElse(tally, 0) + 1
plot(tally, "Bars so far")
```

**A `var` may start from a setting.** `var tally = input(0, "Start")` begins a running total at a number the reader chooses in the settings dialog. The name is then a `var`, not the setting itself, so it can no longer be used inside a higher timeframe read: `req.timeframe("1D", sma(close, len))` works when `len = input(20, "Length")`, and is error [OS6003](/script/errors/data#os6003) when `len` is a `var`. Only put `var` on a setting you intend to change during the run.

## When the initial value is set

**The initial value is set once, on the first bar on which control reaches the declaration.** Most `var` lines sit at the top level, so for most scripts that is bar 0. A `var` inside a conditional block is different: it is absent until the first bar the block runs.

```openscript
// Reached for the first time on the first bar that closes above its prior
// 50 bar high, which may be bar 60 or bar 600.
breakout = close > highest(high, 50)[1]
if breakout
    var firstBreakout = close
    signal(close > firstBreakout ? "ABOVE FIRST BREAKOUT" : "BREAKOUT")
```

That is not a special case. It is what "reached for the first time" means, and it is sometimes exactly what you want: a value seeded from the first bar that meets a condition rather than from the first bar of the data. If you meant "from the first bar of the data", declare the `var` at the top level instead. Either way, a one-line comment saying which you meant saves the next reader a minute.

## var inside a function

A `var` may appear at the top level, inside a block or inside a function. Inside a function it gives each call its own memory, which is what makes a stateful helper reusable:

```openscript
version 1
study("Run length", precision = 0)

// How many bars in a row the condition has held.
fn runLength(cond) =>
    var n = 0
    n = cond ? n + 1 : 0
    n

upRun   = runLength(close > open)   // its own counter
downRun = runLength(close < open)   // a separate counter

plot(upRun, "Up run", lime)
plot(downRun, "Down run", red)
```

State is allocated per call site, not per function, so these two calls never see each other's `n`. [User functions](/script/language/functions) explains the rule and the one mistake it causes.

## Lifetime and scope

`var` decides how long a value lives. The block it sits in decides where the name can be seen. The two are independent:

```openscript
breakout = close > highest(high, 20)[1]

if breakout
    var highWater = high        // kept across bars, but visible only in this block
    highWater = max(highWater, high)

plot(highWater, "High water", aqua)
```

The value above really does survive from bar to bar; the name just cannot be read outside the block that declared it. To plot it, declare it at the top level and assign it inside the block:

```openscript
breakout = close > highest(high, 20)[1]

var highWater = none

if breakout
    highWater = isNone(highWater) ? high : max(highWater, high)

plot(highWater, "High water", aqua)
```

An assignment inside a block to a name that already exists outside it updates that name. There is never a second variable with the same name, so a `var` line inside the block that reuses an outer name is error [OS2002](/script/errors/names-and-types#os2002), and the message gives the line of the first declaration. Drop the word `var` and the line becomes an ordinary assignment to the outer name.

```openscript
stopLevel = close
if close > open
    var stopLevel = high
plot(stopLevel, "Stop level")
```

[Variables and scope](/script/language/variables-and-scope) covers scope in full.

## Reading a var before you change it

The file runs top to bottom, so a `var` read **above** the line that changes it still holds the previous bar's value. This is how a trailing level compares against itself without a single `[1]`:

```openscript
version 1
study("Trailing low", overlay = true, precision = 2)

var trail = none

// At this line, trail still holds what the previous bar left in it.
prevTrail = trail

// Seed on the first bar, then only ever ratchet upward.
trail = isNone(prevTrail) ? low : max(prevTrail, low)

// Compare with the level as it stood before this bar moved it. Comparing
// with the new level would let every bar trigger itself.
if not isNone(prevTrail) and close < prevTrail
    signal("BROKEN")

plot(trail, "Trail", lime, width = 2)
```

Move the `prevTrail` line below the assignment and the script means something else. Both orders are legal and both are useful somewhere, so the compiler cannot warn you. When the order matters, say so in a comment.

## Persistence is not history

These two are often confused, and they answer different questions:

```openscript
var runs = 0
runs += 1

prevClose = close[1]    // history: what close was one bar ago
prevRuns  = runs[1]     // both: what the persistent runs was one bar ago

plot(prevClose, "Previous close")
plot(prevRuns, "Previous count")
```

| Question | History | Persistence |
|---|---|---|
| Written as | `x[n]` or `history(x, n)` | `var x = ...` |
| Gives you | The value on an earlier bar | The value this bar starts with |
| Absent at the start | Yes, for reads past the first bar | Only if the initial value is `none` |
| Costs | One stored value per bar | One value for the whole run |

A plain name has history and no persistence, which is why building a counter from its own history never gets off the ground:

```openscript
tally = 0
tally = tally[1] + 1    // compiles, and is absent on every bar
plot(tally, "Tally")
```

On bar 0, `tally[1]` is absent because there is no bar before it, so the sum is absent. On bar 1, `tally[1]` reads bar 0's final value, which was absent, and so on forever. The plot is empty. A top-level `var` has both history and persistence, and `var tally = 0` with `tally += 1` counts correctly.

## A var holding an array

An array is held by reference. A `var` holding one keeps the same array for the whole run, with everything the script has pushed into it:

```openscript
version 1
study("Rolling window", precision = 2)

len = input(100, "Window", min = 2, max = 5000)

// Created once. One close is appended per bar and the oldest dropped.
var closes: array<number> = []

push(closes, close)
if size(closes) > len
    shift(closes)

plot(avg(closes), "Rolling mean", aqua)
plot(stdev(closes), "Rolling deviation", orange)
```

Two cautions come with references. Assigning one array name to another gives two names for one array, so use `copy()` when you want an independent one. And `[]` on an array is element access, not history, so `closes[0]` is the oldest element in the window, not last bar's array. [Collections](/script/language/collections) covers arrays in full.

## The forming bar and the rollback rule

On a chart receiving real-time updates, the newest bar is executed again on every update: its `close` is the latest traded price, its `high` can still rise and its `volume` is still growing. If a `var` simply carried on from one execution to the next, a counter would count ticks instead of bars. The engine prevents that:

> **The rollback rule**
Before each re-execution of the forming bar, the engine restores every persistent value to what it held at the end of the **previous** bar.

Persistent values means `var` names, the contents of arrays they hold, the state of stateful calls such as `ema()`, and the drawing objects the script created. Executing the forming bar ten times leaves the same state as executing it once.

```openscript
version 1
study("Bar counter", precision = 0)

var barTotal = 0
barTotal += 1

plot(barTotal, "Bars")
```

Say bars 0 to 40 have finished, so bar 40 ended with `barTotal` at 41. Here is the new bar 41 receiving three updates, the last of which closes it:

| Execution of bar 41 | `bar.updates` | `barTotal` restored to | `barTotal` after the line | Chart shows |
|---|---|---|---|---|
| First update | 1 | 41 | 42 | 42 |
| Second update | 2 | 41 | 42 | 42 |
| Third update, the bar closes | 3 | 41 | 42 | 42, and final |

Without the rule, the counter would climb once per tick and the same script would give different numbers on a realtime chart than in a backtest of the same bars. With it, a backtest is a faithful record of what the script would have done.

So on the forming bar there are two kinds of value:

- **A value that moves during the bar and settles at the close.** Anything computed from this bar's `close`, `high`, `low` or `volume`. It is recomputed from scratch on each update, so nothing accumulates, and its last value is the one the bar keeps.
- **A value that changes only once per bar.** Anything computed from `close[1]` and older, and any `var` assigned only under a condition that cannot flicker. These are already final while the bar is still forming.

Prefer the second kind wherever a decision is involved; [Realtime and confirmation](/script/language/realtime-and-confirmation) explains how.

## live var

`live var` is identical to `var` except that it is **not** rolled back, so it keeps its value across the updates of the forming bar.

```openscript
version 1
study("Updates", precision = 0)

live var runs = 0
runs += 1

plot(runs, "Executions since the study loaded")
```

On history each bar runs once, so `runs` climbs by one per bar exactly as a `var` would. On the forming bar it also climbs by one on every update, because nothing restores it. That difference is the whole of `live var`.

It exists for one purpose: counting or accumulating over the updates within a bar, such as an activity figure in a dashboard. It is spelled with an extra word because a script that uses it gives different numbers on a realtime chart than in a backtest, and the compiler reports warning [OS8011](/script/errors/warnings#os8011) on every `live var` so the difference is never a surprise.

Never use it to hold a trading decision. A stop that was set on a tick that has since been rolled back is a stop nobody can reproduce. If you only want to know how many times this bar has run, you do not need a counter at all: `bar.updates` is built in and starts again at 1 on each new bar.

## Four bugs to recognise

### The absent seed that never recovers

This is the most common persistence bug:

```openscript
var highWater = none

// Wrong: on the first bar highWater is absent, so the comparison is absent,
// so the branch is not taken, so highWater stays absent forever.
if high > highWater
    highWater = high

plot(highWater, "High water")
```

A comparison with an absent side is absent, and an absent condition takes the false branch. The value is never seeded, and the plot is empty. Either fix is one line:

```openscript
var highWater = none

// Ask the question an absent value can answer.
if isNone(highWater) or high > highWater
    highWater = high

plot(highWater, "High water")
```

```openscript
var highWater = none

// Or give the comparison something to work with.
highWater = max(orElse(highWater, high), high)

plot(highWater, "High water")
```

### The counter that counts the wrong thing

```openscript
live var barsInTrade = 0
if pos.size != 0
    barsInTrade += 1
plot(barsInTrade, "Bars in trade")
```

On history this counts bars. On a realtime chart it counts updates, because `live var` is not rolled back, so a position held through two hundred ticks reports two hundred bars, and the backtest and the realtime run disagree about the same trade. Drop the word `live`:

```openscript
var barsInTrade = 0
barsInTrade = pos.size != 0 ? barsInTrade + 1 : 0
plot(barsInTrade, "Bars in trade")
```

The rule of thumb: **`var`, unless counting updates is the measurement.**

### The warmup branch that silently changes an answer

```openscript
var regime = "unknown"

// During warmup rsi is absent, the comparison is absent, and neither branch
// runs. regime keeps its starting value and the study reports "unknown" as
// though it were a reading. The second rsi call only runs on some bars.
if rsi(close, 14) > 50
    regime = "up"
else if rsi(close, 14) < 50
    regime = "down"

plot(regime == "up" ? 1 : 0, "Up regime")
```

Two things are wrong here. The stateful call appears twice, which is two call sites and two independent pieces of state, and the second one only runs on the bars where the first test fails; the compiler reports that with warning [OS8001](/script/errors/warnings#os8001). And the absent case during warmup is not handled at all. Compute once at the top level, and make the absent case explicit:

```openscript
r = rsi(close, 14)              // one call site, computed every bar

var regime = "unknown"
if not isNone(r)
    regime = r > 50 ? "up" : "down"

plot(regime == "up" ? 1 : 0, "Up regime")
```

Now a reader can see that "unknown" means warmup. The error list also includes warning [OS8004](/script/errors/warnings#os8004) for a branch on a possibly absent condition that sets a value used later, but the compiler does not raise it in this release, so the explicit test is yours to write.

### The late initial value

```openscript
if bar.index > 100
    var anchor = close       // set on bar 101, not bar 0
    signal(close > anchor ? "ABOVE ANCHOR" : "BELOW ANCHOR")
```

This is correct behaviour that surprises people: a `var` inside a conditional block is absent until the first bar on which control reaches it. Declare it at the top level if you meant the first bar of the data.

## A worked example: a session accumulator

Everything on this page in one script: values that reset each session, accumulate across bars, survive the forming bar without double counting, and handle their own absence. It computes a session volume weighted average price from the 09:15 open of each NSE session.

```openscript
version 1
study("Session volume weighted price", overlay = true, precision = 2)

src = input(hlc3, "Source")

// The session's first bar where the host states session hours, and the first
// bar of each IST day where it does not, as on the /trading chart.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

// Three running totals rather than an array of every bar in the session.
// The rollback rule restores all three before the forming bar runs again,
// so they count bars, not ticks, and a realtime chart agrees with a backtest.
var priceVolume = 0.0
var totalVolume = 0.0
var sessionBars = 0

// The reset is an assignment, not a second var line: a var line sets its
// value once for the whole run, and this has to happen every session.
if newSession
    priceVolume = 0.0
    totalVolume = 0.0
    sessionBars = 0

priceVolume += src * volume
totalVolume += volume
sessionBars += 1

// Absent means "not ready", rather than a zero that would look like a price.
ready = totalVolume > 0
value = ready ? priceVolume / totalVolume : none

plot(value, "Session VWAP", orange, width = 2)
plot(sessionBars, "Bars this session", fade(silver, 40), scale = "left")
```

Three details here are persistence decisions rather than style:

- The reset happens inside `if newSession`, on the first bar of each session, rather than by a second `var` line. `newSession` is `session.isFirstBar` where the host states the instrument's session hours; the /trading chart does not in this release, so there a new IST date marks the same bar for an NSE session.
- The readiness test is `totalVolume > 0`, not `sessionBars > 0`. An index has no traded volume, so its bars carry a volume of zero or no volume at all, depending on the data. With zero the total stays at zero and the test is false; with no volume the total is absent, the test is absent and takes the false branch. Either way the study draws nothing rather than dividing by zero.
- Nothing needs `live var`. Every number settles when its bar closes, which is the only way the line on the chart today can be the line that was on it at the time.

The library already has this calculation as `vwap()`, which restarts every session. It needs the session hours too, so on the /trading chart it has no value in this release, and `vwapAnchor()` with the same `newSession` as its anchor gives the same line. Writing it by hand is how you learn the pattern for the accumulators the library does not have.

**Related.** [Execution model](/script/language/execution-model), [Bars and history](/script/language/bars-and-history), [Warmup](/script/language/warmup), [Realtime and confirmation](/script/language/realtime-and-confirmation), [Variables and scope](/script/language/variables-and-scope), [User functions](/script/language/functions), [Collections](/script/language/collections)


## Warmup

Source: https://openalgo.in/script/language/warmup

Put a 20 period average on a chart and the line does not start at the oldest bar; it starts at the twentieth. That gap at the left edge is **warmup**: the bars a calculation needs before it has an honest answer. This page shows the exact first bar of the common calls, how warmups add up when calls are chained, what the absent values at the left edge do to everything downstream, and how to handle warmup in a study, in a table and in a strategy that places orders. Getting it right is the difference between a study that is silent until it knows something and one that draws numbers it made up.

## A first example

```openscript
version 1
study("Warmup", overlay = true, precision = 2)

e = ema(close, 20)      // absent on bars 0 to 18, a number from bar 19

plot(e, "EMA 20", aqua) // the line simply starts at bar 19
```

A twenty period average of the first bar would have to average twenty closes, and nineteen of them do not exist. The language has one answer for "there is no value here", the absent value `none`, and the left edge of a chart is where you meet it most. On a 5 minute NSE chart an average of 200 bars needs 200 bars, and a session from 09:15 to 15:30 holds 75 of them, so the line starts in the third session of the data.

## There is no warmup phase

A script runs on bar 0 exactly as it runs on bar 40,000. Every statement executes, every branch is evaluated, every assignment happens. There is no start-up mode, no flag to check and no bar at which the study "starts for real". What differs on the early bars is only that some calls have nothing to return yet:

> **The whole of warmup**
A function that needs `k` bars returns the absent value until `k` bars exist. Everything else follows from how absent values travel.

Absent is the only honest picture of a measurement that was never taken. Returning zero would draw a line at zero and call it data. Repeating the first available value backwards would draw a flat shelf that looks like a quiet market. Absence draws nothing.

## Every function states its first bar

Every library call states the first bar it can produce a value on, counting the oldest bar as 0. "Bar `len - 1`" means the call is absent on bars 0 to `len - 2` and has a value from bar `len - 1` onward, on exactly those bars and no others. The number is exact, not "after a while", so you can count it by hand and rely on the count.

A working selection:

| Call | First bar with a value |
|---|---|
| `sma(src, len)`, `ema(src, len)`, `rma(src, len)`, `wma(src, len)` | `len - 1` |
| `stdev(src, len)`, `variance(src, len)` | `len - 1` |
| `highest(src, len)`, `lowest(src, len)`, `sum(src, len)`, `count(cond, len)` | `len - 1` |
| `median`, `percentile`, `percentRank`, `correlation`, `covariance` | `len - 1` |
| `bollinger(src, len, mult)`, `donchian(len)` | `len - 1`, every element |
| `atr(len)`, `natr(len)`, `cci(len)`, `cmf(len)` | `len - 1` |
| `keltner(len, mult, atrLen)` | `max(len, atrLen) - 1`, every element |
| `change(src)`, `crossUp(a, b)`, `crossDown(a, b)`, `cross(a, b)` | 1 |
| `change(src, len)`, `mom(src, len)`, `roc(src, len)` | `len` |
| `rsi(src, len)`, `mfi(len)`, `chop(len)`, `hv(src, len)`, `aroon(len)` | `len` |
| `rising(src, len)`, `falling(src, len)` | `len` |
| `history(src, n)` | `n` |
| `pivotHigh(src, left, right)`, `pivotLow(src, left, right)` | `left + right` |
| `hma(src, len)` | `len + round(sqrt(len)) - 2` |
| `dema(src, len)` | `2 * len - 2` |
| `tema(src, len)` | `3 * len - 3` |
| `trix(src, len)` | `3 * len - 2` |
| `dpo(src, len)` | `len + floor(len / 2)` |
| `macd(src, fast, slow, signal)` | element 0 at `max(fast, slow) - 1`, elements 1 and 2 at `max(fast, slow) + signal - 2` |
| `adx(diLen, adxLen)` | elements 1 and 2 at `diLen`, element 0 at `diLen + adxLen - 1` |
| `supertrend(factor, atrLen)` | `atrLen` |
| `psar()` | 1 |
| `obv()`, `cum(src)`, `trueRange()` | 0 |

The reference lists the first bar of every call under **First value**, for example `sma()` and `macd()`. When the number matters to your script, read it there rather than estimating it.

Three entries deserve a note.

**A call with several outputs returns an array whose elements warm up separately.** The array itself is never absent and never changes length; each element is absent until it is reached. So `m = macd(close, 12, 26, 9)` gives `m[0]` from bar 25 and `m[1]` and `m[2]` from bar 33. If the array grew as warmup completed, `m[1]` would be an out-of-range error on the early bars, and a script would break only at the left edge of a chart, the worst place for it.

**A `max` in a first bar is not decoration.** A call that combines two lengths has nothing to report until both calculations exist, so its first bar follows the longer length, whichever argument that is. Nothing stops a script from setting `fast` above `slow`, and the row states the `max` so that case is covered.

**`trueRange()` on bar 0 is `high - low`.** The other two terms of its definition need the previous close, which is absent there. This is a deliberate exception to absence: a bar's own range is a true statement about that bar, and it is why `atr(14)` has a value from bar 13 rather than bar 14.

## Why some lengths cost one bar more

`sma(close, 14)` has a value from bar 13, and `rsi(close, 14)` from bar 14. The difference is not a quirk.

An average of 14 values needs 14 bars, and bars 0 to 13 are 14 bars, so bar 13 is the first. `rsi()` averages 14 **changes**, a change needs two bars, so 14 changes need 15 bars and bar 14 is the first. Every call that works on changes rather than levels carries the same extra bar. When you count a chain by hand, this is the step people get wrong: ask whether each call reads levels or the differences between them.

## Warmups add up

A call whose input is absent on a bar is absent on that bar too, so warmups add. Written as arithmetic, **the first bar of a chain is the sum of each stage's first bar**:

```openscript
// ema(close, 10) first has a value at bar 9.
// sma(..., 10) needs 10 present values, which arrive on bars 9 to 18.
// First value: bar 9 + 9 = bar 18.
smoothed = sma(ema(close, 10), 10)
plot(smoothed, "Smoothed average")
```

A three stage study, with the count written where the next reader will look for it:

```openscript
version 1
study("Stretch", precision = 2, range = [0, 100])

rsiLen = input(14, "RSI length", min = 2, max = 200)
smooth = input(9, "Smoothing", min = 1, max = 100)
window = input(50, "Extreme window", min = 2, max = 500)

// First value, stage by stage, with the default settings:
//   rsi(close, 14)        bar 14
//   ema(..., 9)           plus 8  = bar 22
//   highest(..., 50)      plus 49 = bar 71
// The study draws nothing before bar 71.
r        = rsi(close, rsiLen)
smoothed = ema(r, smooth)
peak     = highest(smoothed, window)
trough   = lowest(smoothed, window)

span    = peak - trough
stretch = span > 0 ? (smoothed - trough) / span * 100 : none

level(80, "High", fade(red, 50))
level(20, "Low", fade(lime, 50))
plot(stretch, "Stretch", purple, width = 2)
```

Three details in that script are warmup decisions:

- The lengths are inputs, so the first bar changes with the settings. The comment states the arithmetic for the defaults, which is the useful thing to write down.
- `span > 0` guards the division. Dividing by zero gives the absent value rather than an error, so the guard is not strictly needed, but it tells the reader that a flat window is a known case.
- Nothing tries to fill in the first 71 bars. The pane is empty there, and that is the report.

A length can also be a series that changes from bar to bar. What a call does after its length changes is not the same for every call: some carry on at once with the new length, and others go absent again until they have enough bars for it. Use a fixed length, usually an `input()`, whenever you need to know the first bar exactly.

## What an absent value does downstream

Warmup matters because of what the absent value does after it. Here is all of it in one table:

| Where an absent value lands | What happens |
|---|---|
| `+`, `-`, `*`, `/`, unary `-` | The result is absent. `none * 0` is absent, not zero |
| Joining strings with `+` | Absent. Use `text()` to turn it into words: `text(none)` is `"none"` |
| `<`, `<=`, `>`, `>=` | The result is absent, not false |
| `==`, `!=` | Never absent. `none == none` is true, `none == 5` is false |
| `and`, `or`, `not` | Three-valued: `true or none` is true, `false and none` is false, `true and none` is absent |
| An `if`, `while` or ternary condition | The false branch is taken |
| A windowed library call | If any bar in the window is absent, the result is absent |
| `sumSkip()`, `avgSkip()`, `countPresent()` | Absent bars are skipped. These three, and only these three |
| A plot | The line breaks, and a fill between two plots stops |
| `barColor()`, `background()` | Nothing is painted; the bar keeps its own colour |
| An order's price or quantity | Error [OS7002](/script/errors/orders#os7002), naming the argument, and the script stops on that bar |

The comparison row repays the most thought. During warmup `a > b` and `a <= b` are **both** absent, so both are false as conditions, and a script that branches on one and assumes the other is its opposite takes neither path. That keeps `not (a > b)` equal to `a <= b` everywhere else. Equality is the deliberate exception, because without it there would be no way to ask whether a value is there: `x == none` and `isNone(x)` mean the same thing, and `isNone()` is the clearer way to write it. [Absent values](/script/language/absent-values) covers the rules in full.

## The shape where warmup changes an answer

There is one shape in which warmup silently changes a study's output instead of leaving a visible gap: a persistent value set inside a branch whose condition can be absent.

```openscript
var regime = "unknown"

// During warmup the comparison is absent, so the else branch runs,
// and regime says "down" for bars where RSI has no value at all.
if rsi(close, 14) > 50
    regime = "up"
else
    regime = "down"

plot(regime == "up" ? 1 : 0, "Up regime")
```

An absent condition takes the false branch, so for the first fourteen bars this study reports a "down" regime it never computed, and the `var` carries that into every later bar that does not overwrite it. Make the absent case explicit and the problem disappears:

```openscript
r = rsi(close, 14)              // one call site, computed every bar

var regime = "unknown"
if not isNone(r)
    regime = r > 50 ? "up" : "down"

plot(regime == "up" ? 1 : 0, "Up regime")
```

Now a reader can see that "unknown" means warmup. The same fix applies whenever a value that is absent during warmup feeds a branch that sets something durable: a stop level, a position flag, a session high. The error list includes warning [OS8004](/script/errors/warnings#os8004) for this shape, but the compiler does not raise it in this release, so the explicit test is yours to write. The compiler does warn with [OS8001](/script/errors/warnings#os8001) when a stateful call such as `rsi()` sits inside a branch, which is the other half of the same mistake.

## Seeing how many bars a study needs

Three ways, in increasing order of certainty.

**Count it.** Add each stage's first bar from the reference, remembering the extra bar for calls that read changes. This is exact and it works before you have any data.

**Look at the chart.** Scroll to the oldest bar. The first bar of each line is its warmup, and a line that never starts belongs to a study whose warmup is longer than the history loaded.

**Probe it.** Put the answer on the chart. This removes all doubt, particularly when the lengths are inputs:

```openscript
version 1
study("Warmup probe", precision = 2)

// The value whose warmup you want to know.
value = ema(rsi(close, 14), 9)

// The first bar on which it existed. isNone guards the assignment so this
// records the first bar only; absent means "not yet".
var firstBar = none
if isNone(firstBar) and not isNone(value)
    firstBar = bar.index

t = table("Warmup", 4, 2, position = "topRight")
cell(t, 0, 0, "bars loaded")
cell(t, 0, 1, text(bar.count))
cell(t, 1, 0, "first value at bar")
cell(t, 1, 1, isNone(firstBar) ? "not yet" : text(firstBar))
cell(t, 2, 0, "present in last 100")
cell(t, 2, 1, text(countPresent(value, 100)))
cell(t, 3, 0, "has a value now")
cell(t, 3, 1, text(not isNone(value)))

plot(value, "Value", aqua, width = 2)
```

For this chain the table reads "first value at bar 22": 14 for `rsi()` plus 8 for the 9 bar `ema()`. `countPresent(value, 100)` has a first bar of its own, bar 99, so its cell reads "none" until a hundred bars have loaded; `text(none)` is the word `"none"`, which is why the cell is not blank. A bar index is fine to display within one run like this, but do not store one across a reload: loading older history renumbers every bar.

When you want the number as a series rather than in a table, mark the bar where the value appears and carry that bar's index forward with `valueWhen()`:

```openscript
value    = ema(rsi(close, 14), 9)

// True on one bar only: the first bar with a value after a bar without one.
started  = not isNone(value) and isNone(value[1])
startBar = valueWhen(started, bar.index)

plot(startBar, "First bar with a value")    // 22 for this chain
```

Do not reach for `barsSince(not isNone(value))` here. `barsSince()` counts from the **last** bar its condition held, and once the value exists the condition holds on every bar, so it reads 0 everywhere and tells you nothing about the start.

## How much history to load

Two numbers matter, and they add:

```text
bars to load = the study's warmup + the bars you actually want to read
```

A study whose first value is on bar 71 has 71 bars of warmup (bars 0 to 70). On a chart where you want to read the last 500 bars, it needs at least 571 bars. Load exactly 71 and it draws nothing at all; load 72 and it draws a single point.

Then load a margin beyond that, for this reason. A recursive average, one that builds each value from its own previous value, is seeded at a stated bar of the data it was given: `ema(src, n)` is seeded on bar `n - 1` with the simple average of the first `n` values, and `rma()` states its own seed the same way. The seeding is exact, so the same data always gives the same numbers. It also means the seed sits at a different moment in market history when you load a longer range, and the values after it, while they converge quickly, are not identical to the ones from a shorter load.

Two practical consequences:

- Load a comfortable margin beyond the warmup, so the recursive averages have long since converged over the region you are actually reading.
- Fix the date range of anything you intend to compare. A backtest of the same script over a fixed range reproduces months later. The same backtest over "whatever history the chart had open" does not, and the difference can be small enough to look like noise and large enough to change a marginal trade.

## Filling in a warmup value

`orElse(x, fallback)` gives `fallback` wherever `x` is absent (`orElse()`). It is the right tool about half the time.

**Right: a display default**, where the substitute is clearly a label rather than a measurement.

```openscript
version 1
study("Reading, or waiting", precision = 2, range = [0, 100])

r = rsi(close, 14)

t = table("RSI", 1, 2, position = "topRight")
cell(t, 0, 0, "RSI")
cell(t, 0, 1, isNone(r) ? "warming up" : text(r, 1))

// Shade the pane while the study has nothing to say.
background(isNone(r) ? fade(gray, 90) : none)

plot(r, "RSI", purple, width = 2)
```

**Right: seeding a persistent value on its first bar**, where without the fallback the value would stay absent forever:

```openscript
rawBand = low - 2 * atr(10)

var band = none
prevBand = band
band = max(rawBand, orElse(prevBand, rawBand))

plot(band, "Rising band")
```

**Wrong: feeding a substitute into a decision**, where it manufactures a signal the data never produced.

```openscript
r = rsi(close, 14)

// Wrong. During warmup orElse hands crossUp an exact 50 on every bar, so the
// first real reading above 50 reports a crossing from a number this line invented.
if crossUp(orElse(r, 50), 50)
    signal("UP, invented")

// Right. During warmup the comparison inside crossUp is absent, the branch is
// not taken, and the first marker is the first real crossing.
if crossUp(r, 50)
    signal("UP")
```

The rule that covers both: **substitute for something a person will read, never for something the script will act on.** A made-up number that reaches a decision cannot be told apart from a real one afterwards.

## Warmup in a strategy

A strategy meets warmup at the worst moment, because an order built from absent inputs cannot simply be skipped like a drawing. The language is loud about it on purpose: **an order given an absent price or quantity is error [OS7002](/script/errors/orders#os7002), naming the argument, and the script stops on that bar.** It never sends an order at a size or price nobody chose, and it never substitutes a value for you.

So a strategy should reach its order line only when every input exists:

```openscript
version 1
strategy("Sized by volatility", overlay = true, precision = 2,
         capital = 500000, qty = 1, qtyType = "units",
         fillOn = "nextOpen", slippage = 1)

atrLen     = input(14, "ATR length", min = 1, max = 200)
stopMult   = input(2.0, "Stop, in ATR", min = 0.2, max = 20)
riskAmount = input(5000, "Amount risked per trade", min = 1)

fast     = ema(close, 9)
slow     = ema(close, 21)
atrValue = atr(atrLen)

// Absent for the first 13 bars, so the distance and the size are absent too.
stopDistance = stopMult * atrValue
rawUnits     = stopDistance > 0 ? riskAmount / stopDistance : none
orderQty     = isNone(rawUnits) ? none : floor(rawUnits)

// The guard is the point. Without it the strategy could reach buy() during
// warmup with an absent quantity and stop with OS7002.
canSize = not isNone(orderQty) and orderQty > 0

if crossUp(fast, slow) and pos.size == 0 and canSize
    buy(qty = orderQty)

if crossDown(fast, slow) and pos.size > 0
    close()

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)
```

Here `crossUp(fast, slow)` is itself absent during warmup, so the guard is belt and braces. Write it anyway: the day someone replaces the entry condition with one that can be true on bar 0, the guard is what stands between the change and a strategy that stops with OS7002 on one of its first bars. On NFO futures, round the quantity to the contract's lot size as well; [Position and sizing](/script/strategies/position-and-sizing) covers lots.

## Warmups that are not bar counts

A few first bars are a condition rather than a number, and no arithmetic gives you a bar index for them:

| Call | First value |
|---|---|
| `vwap()` | The first bar of each session. It starts again every session |
| `vwapAnchor()` | The first bar the reset condition is true |
| `barsSince()`, `valueWhen()` | The first bar the condition is true, which may be never |
| `req.timeframe()` with the default mode | The first bar after a higher timeframe bar has closed |
| `req.symbol()` | The same, and not before the other instrument's bars have arrived |

Three consequences:

**`barsSince` and `valueWhen` are absent, not zero, before the condition has ever held.** Zero would read as "it happened on this bar", the opposite of the truth.

**A higher timeframe read on a fresh chart is absent for a while, measured in coarse bars.** A daily read on a 5 minute chart is absent until the first daily bar in the data has closed, which can be 75 fine bars or more. `req.isReady()` tells you whether the requested bars have arrived at all, and `req.error()` carries the reason a read failed. [Higher timeframes](/script/data/higher-timeframes) covers this.

**A session anchored value restarts.** It does not warm up once; it warms up every session. A study built on `vwap` at 09:15 is reporting a single bar's worth of information, so say so on the chart if a reader might mistake it for a settled average.

**Related.** [Bars and history](/script/language/bars-and-history), [Absent values](/script/language/absent-values), [Persistence](/script/language/persistence), [Realtime and confirmation](/script/language/realtime-and-confirmation), [Execution model](/script/language/execution-model), [Technical analysis reference](/script/reference/technical-analysis), [Troubleshooting](/script/writing/troubleshooting)


## Realtime and confirmation

Source: https://openalgo.in/script/language/realtime-and-confirmation

Every bar on a chart is finished except the newest one. While the market is open that bar is still forming: its close is the latest traded price, its high can still rise and its volume is still growing. This page explains how OpenScript runs a script on a bar that is still changing, why a condition can be true and then false within one bar, what the language does by default so that you never act on a crossing that did not survive the close, and how to opt in to acting earlier when you mean to. It also explains why a replay or a backtest of your script shows what you would have seen at the time, and the few things in a script that break that.

## A first example

Put this study on any intraday chart during market hours, 09:15 to 15:30 IST for NSE, and watch the table:

```openscript
version 1
study("Bar state", overlay = true)

t = table("Bar state", 4, 2, position = "topRight")

cell(t, 0, 0, "bar.index")
cell(t, 0, 1, text(bar.index))
cell(t, 1, 0, "confirmed")
cell(t, 1, 1, text(bar.isConfirmed))
cell(t, 2, 0, "realtime")
cell(t, 2, 1, text(bar.isRealtime))
cell(t, 3, 0, "updates")
cell(t, 3, 1, text(bar.updates))
```

While the newest bar forms, "updates" climbs with each price update and "confirmed" reads false. When the interval ends a new bar appears, `bar.index` goes up by one and "updates" starts again from 1. That climbing number is the script being run again and again on one bar.

## The newest bar is run again

OpenScript handles a forming bar by re-running the script on it:

> **Re-execution**
The newest bar of a chart receiving real-time updates is executed again on every update: every tick, or every time the chart receives a new snapshot of the bar.

A script that runs five thousand times over five thousand bars of history then runs a five thousand and first time, and a five thousand and second time, all on the newest bar, until its interval ends and the next bar appears. The `bar.index` family tells you where you are:

| Name | True or counts |
|---|---|
| `bar.isConfirmed` | This bar's interval has ended and it will not change again |
| `bar.isRealtime` | Real-time updates are driving this execution, rather than a one-off history load |
| `bar.isNew` | The last update added a new bar rather than replacing the forming one |
| `bar.isLast` | This is the newest bar in the data |
| `bar.updates` | How many times this bar has been executed, counting from 1 |

`bar.isConfirmed` is true for every historical bar, and for the newest bar once its interval has ended. It is the flag a script uses to refuse to act on a bar that is still moving.

## What moves and what is settled

On the forming bar:

| Value | On the forming bar |
|---|---|
| `open` | Fixed at the first trade of the interval |
| `high` | Can only rise |
| `low` | Can only fall |
| `close` | The latest traded price, so it moves both ways |
| `volume` | Grows |
| `time` | Fixed: it is the bar's opening time |
| `close[1]`, `high[1]` and anything older | Fixed, and never changes again |
| `bar.index`, `bar.count` | Fixed for this bar |
| `bar.updates` | Rises with each execution |

That table is the whole hazard in one grid. A condition built from `close` is a question about a number that is still moving. The same condition built from `close[1]` is a question about a number that is final.

```openscript
// Moves during the bar: the answer can be withdrawn before the close.
breakingOut = close > highest(high, 20)[1]

// Settled: a statement about the bar that already closed.
brokeOut = close[1] > highest(high, 20)[2]

plot(breakingOut ? 1 : 0, "Breaking out now")
plot(brokeOut ? 1 : 0, "Broke out last bar")
```

Neither is wrong. They answer different questions, and a script should know which one it is asking.

## The rollback rule

Running the script again on the same bar would double every running total, so the engine does not simply run it again:

> **The rollback rule**
Before each re-execution of the forming bar, the engine restores every persistent value to what it held at the end of the **previous** bar.

Persistent values means `var` names, the contents of arrays they hold, the state of stateful calls such as `ema()` and `cum()`, and the set of drawing objects the script has created. The effect is that executing the forming bar twice leaves the same state as executing it once:

```openscript
var bars = 0
bars += 1

plot(bars, "Bars")     // counts bars, not updates
```

Without the rule the counter would climb once per update, and the same script would give different numbers on a realtime chart than in a backtest of the same bars. `live var` opts out of rollback for the one case where counting updates is the measurement; [Persistence](/script/language/persistence) covers it, with a worked table.

What a script **may** do on a forming bar: everything computational. Read values, compute, plot, draw, colour bars, write a table, read `bar.isConfirmed` and branch on it. All of it is recomputed from the previous bar's state on each update, so nothing piles up.

## A condition can be true, then false, on the same bar

Take a 9 period `ema()` crossing a 21 period one on a 5 minute chart of an NSE stock. Both averages are computed from `close`, and on the forming bar `close` is the latest price. Here is one bar, from 10:05 to 10:10, executed four times, with illustrative prices. The previous bar ended with the fast average at 101.00 and the slow one at 101.04, so the fast average starts the bar below the slow one:

| Time | `bar.updates` | `close` | fast | slow | `crossUp(fast, slow)` |
|---|---|---|---|---|---|
| 10:05:12 | 1 | 101.10 | 101.02 | 101.05 | false |
| 10:06:40 | 2 | 101.45 | 101.09 | 101.08 | **true** |
| 10:08:03 | 3 | 101.20 | 101.04 | 101.05 | false |
| 10:09:58 | 4 | 101.55 | 101.11 | 101.09 | **true** |
| 10:10:00 | bar closes | 101.55 | 101.11 | 101.09 | **true**, and final |

The condition was true, then false, then true again within one bar. Nothing is broken: each row is a correct answer about the data at that instant, and because of the rollback rule, row 3 is not computed on top of row 2; both start from the previous bar's state.

A condition on the forming bar is **provisional**. Acting on it means acting on something that may not be true when the bar finishes. Had that crossing placed an order, you would have bought at 10:06:40 and at 10:08:03 held a position justified by a crossing that no longer existed.

## What the default protects you from

By default you cannot make that mistake by accident:

> **Held until the close**
A script does not emit a `signal`, fire an `alert`, or place, change or cancel an order on a bar that is still forming. Those calls are held until the bar is confirmed, and if the condition that produced them is no longer true at the close, they never happen.

Applied to the table above: at 10:06:40 the marker and the order are held. At 10:08:03 the condition is false and the held call is dropped. At 10:09:58 it is true and held again. At 10:10:00 the bar closes with the condition true, and one signal fires, once.

```openscript
version 1
strategy("Confirmed only", overlay = true, precision = 2)

fast = ema(close, 9)
slow = ema(close, 21)

// No guard needed: the order is placed when the bar closes,
// and only if the crossing is still there.
if crossUp(fast, slow)
    buy(qty = 1)

if crossDown(fast, slow)
    close()

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)
```

The fill then follows the declaration's `fillOn` option, which defaults to `"nextOpen"`: a decision made from a bar's close cannot really be filled at that same close, so the default does not pretend it can. [Costs and fills](/script/strategies/costs-and-fills) covers the choice.

## Three ways to act only on a confirmed bar

**Do nothing, and let the default work.** This is right for most scripts. The hold is not a delay you pay for: the bar had to close before the answer was final.

**Guard with `bar.isConfirmed`.** Needed when the file has opted in with `onUnconfirmed`, and useful for anything the engine does not hold for you, such as a table write or a drawing change you only want at the close:

```openscript
fast = ema(close, 9)
slow = ema(close, 21)
crossed = crossUp(fast, slow)

if crossed and bar.isConfirmed
    signal("BUY")
```

**Ask about the previous bar instead.** This moves the whole question one bar back, so every value it reads is already final. The cost is one bar of lag, paid visibly:

```openscript
version 1
study("Acting a bar late", overlay = true, precision = 2)

fast = ema(close, 9)
slow = ema(close, 21)

// crossUp on the previous bar: every value it reads is settled, so this is
// true on exactly one bar and stays true for that bar's whole life.
crossed = orElse(crossUp(fast, slow)[1], false)

if crossed
    signal("BUY, confirmed")

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)
```

On bar 0 there is no previous bar, so `crossUp(...)[1]` is absent. An absent condition takes the false branch anyway, but `orElse()` makes that explicit and lets `crossed` be combined with `and` and `or` without spreading absence.

Without `onUnconfirmed`, the signal above is still held until its own bar closes, so it appears one full bar after the crossing. The approach earns that lag in a file that sets `onUnconfirmed = true`: there the signal can fire on the first update of the new bar, and the question it answers is already settled, so it cannot flicker.

| Approach | Acts on | Lag | Use when |
|---|---|---|---|
| The default hold | The bar that just closed | None beyond the close | Almost always |
| A `bar.isConfirmed` guard | The bar that just closed | None beyond the close | The file sets `onUnconfirmed`, or the action is not held for you |
| `cond[1]` | The bar before the current one | One bar, unless the file sets `onUnconfirmed` | The file sets `onUnconfirmed` and the decision must read only settled values |

## Opting in with onUnconfirmed

A study or strategy can act on a forming bar by saying so in its declaration:

```openscript
version 1
strategy("Intrabar", overlay = true, onUnconfirmed = true)

fast = ema(close, 9)
slow = ema(close, 21)
crossed = crossUp(fast, slow)

// The engine no longer holds the order, so the script guards it itself.
if crossed and bar.isConfirmed
    buy(qty = 1)

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
```

Three things change the moment you set it, and you should want all three:

- Signals, alerts and orders act on the forming bar as soon as their condition holds, on each execution where it holds. A signal can appear on one update and vanish on the next, an order is sent from a crossing that may not survive the close, and an alert can fire even though the condition is gone by the close (still at most once per bar under the default frequency). Guard each of them unless acting early is the point.
- The compiler reports warning [OS8002](/script/errors/warnings#os8002) on every higher timeframe read in the file, because a forming fine bar reading a coarser bar is where repainting comes from.
- The alert frequency `"everyUpdate"` becomes available. Asking for it without `onUnconfirmed` is error [OS3009](/script/errors/arguments#os3009):

```openscript
if close > open
    alert("Up bar", id = "up-bar", frequency = "everyUpdate")
```

The option lives in the declaration rather than in a global setting because the choice belongs in the file, where a reviewer reads it. A script that says nothing cannot act on a forming bar, and one that can says so on its declaration line.

## Alerts and how often they fire

An `alert()` follows the same rule as a signal. The held call fires when the bar closes, and if the condition is no longer true by then it never fires. That is what makes an alert worth acting on.

```openscript
fast = ema(close, 9)
slow = ema(close, 21)
crossed = crossUp(fast, slow)

if crossed
    alert("Fast crossed above slow at " + text(close, 2), id = "cross-up")
```

| `frequency` | Means |
|---|---|
| `"oncePerBar"` | At most one alert per bar. The default |
| `"once"` | The first time only, for the life of this study on the chart |
| `"everyUpdate"` | On every execution of the bar. Requires `onUnconfirmed = true` |

Two behaviours to know before you rely on alerts:

**Give every alert an `id`.** The `id` is the alert's stable name, so a subscription survives an edit to the script. With no `id`, one is derived from the call's position in the file, which changes when a line is inserted above it, and the compiler warns with [OS8008](/script/errors/warnings#os8008).

**Adding a study to a chart fires nothing for the history already on it.** An alert is a statement about now. A study added at noon that raised four hundred alerts for the morning's bars would be useless.

> **On the /trading chart in this release**
The chart judges a script's alerts once for each new bar, when the bar first arrives. During market hours that is the bar's first tick, before the close the alert is waiting for, and the chart does not look at the bar again, so the alert may not fire. A bar that reaches the chart already closed does fire it. Every `frequency` also behaves as `"oncePerBar"` there. To be told reliably, plot the condition as 1 or 0 and put a study alert on that plot, as [Alerts on a script condition](/script/alerts/alerts-in-trading#alerts-on-a-script-condition) shows.

[Alerts from scripts](/script/alerts/overview) covers messages, frequency and what happens after an alert fires.

## Drawing on a forming bar

Drawing objects are created and changed by the script, so they would be the obvious place for a realtime chart to collect rubbish: one line per update, thousands per session. The rollback rule covers them. The set of objects is restored to what it was at the end of the previous bar before the forming bar runs again, so a label created on the forming bar is created once, not once per update.

```openscript
version 1
study("Last swing", overlay = true, precision = 2)

left  = input(5, "Pivot left bars", min = 1, max = 50)
right = input(5, "Pivot right bars", min = 1, max = 50)

ph = pivotHigh(high, left, right)

if not isNone(ph)
    // Anchored to a time, not to a bar index: loading older history
    // renumbers every index and would drag the label sideways.
    draw.label(time[right], ph, "H", color = red, textColor = white)
```

Two details here are about confirmation rather than drawing. A pivot is reported `right` bars **after** the bar it formed on, because that is the first bar on which it is knowable. Reporting it at the pivot bar would be a lookahead: the value would appear on history at a bar where no script could have had it. So the marker appears late, and that lateness is the honest cost of a pivot. And the anchor is `time[right]`, the time of the pivot bar, which stays put when more history loads. [Objects and methods](/script/language/objects-and-methods) covers drawing objects in full.

## Why a replay shows what you saw at the time

The replay in /trading steps through a chart's bars oldest first and lets you watch a study build itself. What you see in a replay is what you would have seen at the time, for the same reasons that make a backtest trustworthy:

- **The script cannot read forward.** `x[n]` looks back only, and a negative offset is an error. The single exception has to be written out in the source and is warned about: a higher timeframe read with `mode = "lookahead"`.
- **Every historical bar is executed once, confirmed.** A history load hands the engine finished bars, so each runs with `bar.isConfirmed` true and the same values a realtime run would have settled on.
- **The forming bar is idempotent.** Idempotent means that doing it again changes nothing: because of rollback, executing bar 4999 once, or forty times as updates arrive, leaves the same state behind for bar 5000.
- **Arithmetic is fixed.** Numbers are 64-bit floating point, rounded the same way every time, in the order the source writes them. There is no randomness in the language and no reading of the clock during a bar except `chart.now()`.
- **Warmup is exact.** A call's first bar is stated, so a replay starts drawing each line on exactly the bar the realtime chart did. See [Warmup](/script/language/warmup).

Put together: **running a script over the first N bars gives exactly what a full run shows at bar N.** That is what a replay depends on, and it is also what lets a backtest be compared with a realtime run.

For higher timeframe reads with `req.timeframe()`, the `mode` argument is what keeps this true:

| Mode | What it reads | Repaints |
|---|---|---|
| `"confirmed"` | Only higher timeframe bars that have closed | Never. The default |
| `"developing"` | Also the higher timeframe bar currently forming | On the newest bars, within the current higher timeframe period |
| `"lookahead"` | A higher timeframe bar's final value from its first lower timeframe bar | On history, permanently and by design |

`"confirmed"` is the default and the only mode that never repaints. The other two must be written out, so a script that repaints says so on the line that causes it:

```openscript
dailyHigh = req.timeframe("1D", high, mode = "lookahead")
plot(dailyHigh, "Day high, final")
```

[Repainting](/script/data/repainting) covers the modes in depth.

## What breaks a replay

If a replay does not reproduce what a realtime run showed, look for one of these in the file:

| Cause | Why it breaks the replay | What the language does |
|---|---|---|
| A `"lookahead"` read | It uses a higher timeframe bar's final value on the lower timeframe bars inside it, information nobody had at the time | Warning [OS8005](/script/errors/warnings#os8005) on the line that reads it |
| `onUnconfirmed = true` | A signal, alert or order can act on a forming bar whose condition is gone by the close. History only runs finished bars, so that action never appears again | The option is written in the declaration, and [OS8002](/script/errors/warnings#os8002) warns on each higher timeframe read in the same file |
| A `live var` | It is not rolled back, so its value depends on how many updates arrived, which a replay cannot know | Warning [OS8011](/script/errors/warnings#os8011) |
| `chart.now()` | It is the chart's clock, not the bar's time, so a comparison with it says something different tomorrow | Nothing: use `time` for anything about the bar |

## A checklist before you trade a script

Six questions, each with a one-line answer in the source:

1. Does the file set `onUnconfirmed`? If so, is every signal, alert and order guarded with `bar.isConfirmed`, and did you mean to take that on?
2. Does any higher timeframe read name a mode other than `"confirmed"`? If so, the study repaints.
3. Does any `live var` feed a decision rather than a display?
4. Does any condition mix a moving value with a settled one in a way that reads as more certain than it is? Write `close[1]` where you mean "the bar that closed".
5. Is `fillOn` still `"nextOpen"`? Changing it to `"close"` fills at the very price the decision was made from.
6. Does the study look the same after a reload as it did before? If not, one of the causes in the table above is in the file.

Then run it in sandbox trading (analyzer mode in OpenAlgo) before live trading; [Sandbox and live](/script/strategies/sandbox-and-live) walks through it.

**Related.** [Execution model](/script/language/execution-model), [Persistence](/script/language/persistence), [Bars and history](/script/language/bars-and-history), [Warmup](/script/language/warmup), [Repainting](/script/data/repainting), [Alerts from scripts](/script/alerts/overview), [bar.* reference](/script/reference/bar)


## Collections

Source: https://openalgo.in/script/language/collections

A collection holds several values under one name. In this release OpenScript has one collection type, the **array**: an ordered list whose elements all have the same type. This page covers making arrays, reading and changing them, the two lifetimes an array can have, looping over one safely, the arrays that multi-output calls return, and how to get the effect of a map or a matrix today. `map` and `matrix` are reserved words for a later language version and are not available yet; the last sections say exactly what that means for a script you write now.

## A first example

This study keeps the last 20 closes in an array, sorts a copy, drops the two highest and two lowest, and plots the mean of the rest. A series can give you `close[20]`, but it cannot be sorted; that is the job an array does.

```openscript
version 1
study("Trimmed mean", overlay = true, precision = 2)

len  = input(20, "Window", min = 5, max = 200)
trim = input(2, "Values dropped from each end", min = 0, max = 10)

// Kept for the whole run: one close appended per bar, the oldest dropped.
// Without the trim this array would grow on every bar of the chart.
var window: array<number> = []
push(window, close)
if size(window) > len
    shift(window)

ready = size(window) == len and trim * 2 < len

middle = none
if ready
    // Rebuilt every bar and thrown away. copy() matters: sorting the window
    // itself would scramble the arrival order that shift() depends on.
    sorted = copy(window)
    sort(sorted, "asc")
    middle = avg(slice(sorted, trim, len - trim))

plot(middle, "Trimmed mean", aqua, width = 2)
plot(ready ? avg(window) : none, "Plain mean", orange)
```

Three details are worth copying. `middle` is declared before the `if` and assigned inside it, because a name first assigned inside a block belongs to that block. `copy()` is there because `sort()` works in place. And the trim runs before anything reads the window, so the window is never longer than `len`.

## What arrays are for

A series already gives you the past, so an array is not for remembering price history. It is for the jobs a series cannot do:

| Job | Example |
|---|---|
| A window you need to reshape | Sort the last 20 closes and drop the extremes |
| A set the script grows and shrinks | The zones currently drawn, the levels still in play |
| Several facts per item, kept side by side | Four arrays describing the boxes a study drew |
| Several outputs from one call | `bollinger()` returns its basis, upper and lower band |

If what you want is "the value n bars ago", use `[]` on a series and write no array at all. [Bars and history](/script/language/bars-and-history) covers that.

## Making an array

An `array<T>` is ordered, can change size, and holds one type of element. Write a literal in square brackets:

```openscript
levels = [20.0, 50.0, 80.0]         // array<number>
names  = ["NIFTY", "BANKNIFTY"]     // array<string>
flags  = [true, false, true]        // array<bool>
shades = [red, orange, lime]        // array<color>
var hits: array<number> = []        // empty, so the type is written down

plot(size(levels) + size(names) + size(flags) + size(shades) + size(hits), "Elements")
```

The element type can be `number`, `string`, `bool`, `color`, or one of the object types `line`, `label`, `box`, `polyline` and `table`. An `array<box>` or `array<line>` is how a study keeps the drawings it will come back to. Three rules:

**Every element has the same type.** A literal that mixes types is error [OS2013](/script/errors/names-and-types#os2013):

```openscript
mixed = [1.0, "one"]
plot(size(mixed), "Size")
```

**An empty literal needs a type.** It takes one from an annotation, or from the first `push`, `unshift`, `insert` or `set` that puts an element into it. With neither it is error [OS2015](/script/errors/names-and-types#os2015). Write the annotation anyway: it is the only documentation the next reader gets.

```openscript
empty = []
plot(size(empty), "Size")
```

**An array cannot hold arrays.** `array<array<number>>` is error [OS2019](/script/errors/names-and-types#os2019) in this release. For a grid, use one flat array and index arithmetic, as [Living without a matrix](#living-without-a-matrix) shows.

```openscript
var grid: array<array<number>> = []
```

## An array is a reference

**Assigning an array to another name gives two names for one array.** It does not copy.

```openscript
a = [1.0, 2.0, 3.0]
b = a
set(b, 0, 99.0)     // a[0] is now 99 too: a and b are the same array
c = copy(a)         // c is independent

sameArray   = a == b              // true: the same array
sameContent = arrayEqual(a, c)    // true: equal elements in the same order

plot(a[0], "First element of a")
plot(sameArray and sameContent ? 1 : 0, "Both true")
```

Copying on every assignment would make passing a large array to a function quietly expensive on every bar, and the cost would be invisible in the source. So copying is explicit, and it is one word. For the same reason `==` on two arrays asks whether they are the same array; `arrayEqual()` compares what they hold.

## Reading and writing elements

`a[i]` reads element `i` when `a` is an array, counting from 0. `element(a, i)` is the same read written as a call (`element()`), and `set(a, i, v)` writes one element (`set()`).

The same brackets mean history when the value is a series, and the compiler decides which from the type. The one place a human reader can be misled is an array held in a `var`, where `prices[1]` might be read as "last bar's prices". It is the second element. Where a line could be read either way, prefer `element(prices, 1)`.

An index outside `0` to `size - 1` is error [OS4004](/script/errors/runtime#os4004), which names the index and the size and stops the script on that bar. That is deliberately the opposite of history: `close[500]` on bar 7 is absent because that value never existed, while `element(arr, 500)` on an array of seven elements is a mistake in the script, because the array's size is something the script chose.

## Every operation

All of these are bare names, available in every script.

**Size and reading**

| Call | Written | Does |
|---|---|---|
| `size()` | `size(arr)` | The number of elements |
| `element()` | `element(arr, i)` or `arr[i]` | Element `i` |
| `indexOf()` | `indexOf(arr, v)` | The first index holding `v`, or `-1` |
| `arrayEqual()` | `arrayEqual(a, b)` | Whether two arrays hold equal elements in the same order |

**Changing in place**

| Call | Written | Does |
|---|---|---|
| `set()` | `set(arr, i, v)` | Writes element `i` |
| `sort()` | `sort(arr, order)` | Sorts, `"asc"` or `"desc"`. The order is required |
| `reverse()` | `reverse(arr)` | Reverses the order |

**Growing and shrinking**

| Call | Written | Does |
|---|---|---|
| `push()` | `push(arr, v)` | Appends to the end |
| `pop()` | `pop(arr)` | Removes and returns the last element |
| `unshift()` | `unshift(arr, v)` | Inserts at the front |
| `shift()` | `shift(arr)` | Removes and returns the first element |
| `insert()` | `insert(arr, i, v)` | Inserts before index `i` |
| `remove()` | `remove(arr, i)` | Removes and returns element `i` |
| `clear()` | `clear(arr)` | Removes everything |

**New arrays from old**

| Call | Written | Does |
|---|---|---|
| `slice()` | `slice(arr, from, to)` | A new array, `from` included, `to` excluded |
| `copy()` | `copy(arr)` | An independent copy |

**Statistics over the whole array**

| Call | Written | Does | On an empty array |
|---|---|---|---|
| `sum()` | `sum(arr)` | The total | 0 |
| `avg()` | `avg(arr)` | The mean | Absent |
| `min()`, `max()` | `min(arr)`, `max(arr)` | The smallest and largest | Absent |
| `stdev()` | `stdev(arr)` | The population standard deviation | Absent |

Several of these names also have a windowed form over a series, and the compiler picks the right one from the arguments: `sum(prices)` totals an array, `sum(close, 20)` totals the last twenty closes. One name for one idea, in two shapes, settled before the first bar.

`sort()` has no default order, so leaving it out is error [OS3012](/script/errors/arguments#os3012):

```openscript
levels = [3.0, 1.0, 2.0]
sort(levels)
plot(levels[0], "Lowest")
```

## Errors, and what this release raises

| Code | When | Note |
|---|---|---|
| [OS4004](/script/errors/runtime#os4004) | An index outside `0` to `size - 1` | Also raised by `pop` and `shift` on an empty array in this release |
| [OS5002](/script/errors/limits#os5002) | An array passes 1,000,000 elements | `limits()` does not raise this ceiling |
| [OS2013](/script/errors/names-and-types#os2013) | A literal mixing types | Arrays hold one type |
| [OS2015](/script/errors/names-and-types#os2015) | An empty literal with no type | Annotate it |
| [OS2019](/script/errors/names-and-types#os2019) | An array of arrays, or of plots | Flatten it |
| [OS3012](/script/errors/arguments#os3012) | `sort` without an order | Say `"asc"` or `"desc"` |

The error list also includes [OS4006](/script/errors/runtime#os4006), for taking an element from an empty array, and [OS4007](/script/errors/runtime#os4007), for a slice whose bounds are not `0 <= from <= to <= size`. The compiler and engine raise neither in this release. Instead, `pop` and `shift` on an empty array raise OS4004, summarising an empty array gives the values in the table above, and `slice()` takes whatever part of the range falls inside the array, returning a shorter or empty array. Test `size(arr) > 0` before taking an element, and keep slice bounds inside the array, so the script behaves the same when those codes arrive.

OS5002 exists so one runaway script cannot exhaust a browser tab and take the chart with it. It is almost always the same bug: a window that is appended to on every bar and never trimmed. The trim is two lines, as in the first example.

## Two lifetimes: the bar and the run

This is the distinction that decides how most array code should be written.

**An array made by a plain assignment is built fresh on every bar.** The literal runs again, a new array exists, and last bar's array is gone. That is what you want for scratch work: a sorted copy, a slice, a set of candidates you rank and throw away.

**An array held in a `var` is made once and lives for the whole run**, with everything the script has pushed into it.

| | Plain assignment | `var` |
|---|---|---|
| Created | Every bar | Once |
| Holds | This bar's working values | Everything the run has added |
| Grows without limit | No | Yes, unless you trim it |
| Rolled back while the newest bar forms | Not applicable, it is rebuilt | Yes, contents included |
| Typical use | Sort, slice, rank, then discard | A rolling window, a set of drawings |

The rollback row matters on a chart receiving real-time updates. The newest bar is executed again on every update, and before each re-execution the engine restores every persistent value, array contents included, to what it held at the end of the previous bar. So a script that pushes one element per bar pushes one per bar, not one per update, and a realtime chart agrees with a backtest of the same bars. [Persistence](/script/language/persistence) covers the rule.

## Iterating safely

There are two loop forms:

```openscript
var window: array<number> = []
push(window, close)
if size(window) > 10
    shift(window)

total = 0.0
for price in window                 // over the elements
    total += price

total2 = 0.0
for i = 0 to size(window) - 1       // over the indices
    total2 += element(window, i)

plot(total, "Sum by element")
plot(total2, "Sum by index")
```

The `in` form visits indices `0` to `size - 1` as measured when the loop starts, so elements appended during the loop are not visited. The index form written as `0 to size(window) - 1` runs zero times on an empty array, because a loop with a positive step and an end below its start does not run.

**When a loop removes elements, count downwards.** Removing element `i` renumbers every element after it. Counting down means the elements the loop has yet to visit keep their numbers, so nothing is skipped:

```openscript
var levels: array<number> = []
var ages:   array<number> = []
maxAge = 50

// Add this bar's high as a level, then age every level by one bar.
push(levels, high)
push(ages, 0)
for i = 0 to size(ages) - 1
    set(ages, i, element(ages, i) + 1)

// Remove old levels, from the end backwards.
for i = size(levels) - 1 to 0 step -1
    if element(ages, i) > maxAge
        remove(levels, i)
        remove(ages, i)

plot(size(levels), "Levels kept")
```

Going forwards with a removal inside is the classic way to skip every other match, and it only shows up when two neighbours are removed on the same bar. A descending loop must say `step -1`: with a positive step and an end below the start, the body simply does not run.

Every iteration of every loop on a bar counts against the per-bar loop budget of 2,000,000 iterations. [Control flow](/script/language/control-flow) covers `for`, `while`, `break`, `continue` and the budget.

## Arrays that come back from a call

A library call with more than one output returns an `array<number>` holding this bar's outputs in a documented order:

```openscript
version 1
study("MACD", precision = 4)

src    = input(close, "Source")
fast   = input(12, "Fast", min = 1, max = 500)
slow   = input(26, "Slow", min = 2, max = 500)
smooth = input(9, "Signal", min = 1, max = 500)

// One array per bar, rebuilt every bar.
m = macd(src, fast, slow, smooth)

// Name the elements. Each name is a series, so it has history.
line = m[0]
sig  = m[1]
hist = m[2]

level(0, "Zero", gray)
plot(line, "MACD", aqua, width = 2)
plot(sig, "Signal", orange)
plot(hist, "Histogram", hist > 0 ? lime : red, style = "histogram")

if crossUp(line, sig)
    signal("UP")
```

The returned array is never absent and never changes length. Each element has its own first bar and is absent until then, so `m[2]` is a valid read on bar 0 that simply holds nothing yet. The crossing is read from the named series, which have history because they are top-level names: `m[1]` is element 1, not one bar ago.

> **History of a whole array**
`history(m, 1)` compiles, but in release 0.5.0 it gives the absent value on every bar rather than last bar's array. To look back at an output, name it at the top level, as `line` and `sig` are above, and read `line[1]`.

## Parallel arrays

This release has no record type, so a list of things that each have several fields is written as several arrays kept the same length and indexed together:

```openscript
var zoneTop:    array<number> = []
var zoneBottom: array<number> = []
var zoneStart:  array<number> = []

pivot = pivotLow(low, 5, 5)
if not isNone(pivot)
    push(zoneTop, max(open[5], close[5]))
    push(zoneBottom, low[5])
    push(zoneStart, time[5])

plot(size(zoneTop), "Zones")
```

Every operation that adds an item pushes to all the arrays, and every operation that removes one removes from all of them, at the same index, in the same block. That discipline is the whole technique: the moment one array is updated without the others, the script holds nonsense that no error will catch. It is also why the descending removal loop matters so much here, since one missed `remove` misaligns every record after it. [Objects and methods](/script/language/objects-and-methods) uses the same shape for drawings. A record type, declared with `type`, is planned for a later language version and would replace this shape; it is not available yet.

## Maps and matrices: what is true today

| Type | Status in this release |
|---|---|
| `array<T>` | Available, as described above |
| `map<K, V>` | Planned. A reserved word with no implementation: no annotation, no functions |
| `matrix<T>` | Planned. A reserved word with no implementation |

Because they are reserved, you cannot use `map` or `matrix` as names either. Trying is error [OS1019](/script/errors/syntax#os1019), which says the word is reserved rather than unknown:

```openscript
map = 1
plot(map, "Map")
```

Writing one in a type annotation is error [OS2016](/script/errors/names-and-types#os2016), because in this release neither word names a type:

```openscript
var prices: map = none
```

What is intended for a later version, stated so nobody plans around a different answer:

- **`map<K, V>` with `string` and `number` keys, iterating in the order the keys were added.** The order is a requirement, not a convenience: a script must give the same output every time it runs, and a collection with no defined order would not.
- **`matrix<T>` as a two-dimensional numeric container**, with element access, row and column operations, and the small amount of linear algebra that correlation and regression studies need.

Both will arrive with a new language version. A script that declares `version 1` keeps compiling and keeps producing the same numbers, so nothing you write today breaks when they land. The words are reserved now precisely so that adding them later cannot break a script that used one as a name.

## Living without a map

A map associates a key with a value. Two arrays kept in step give you one, and for the handful of keys a chart script uses, the search is not the slow part of anything. This study counts, for each weekday, the share of bars that closed up:

```openscript
version 1
study("Up share by weekday", precision = 0)

// Keys and values, kept in step. Seven slots, although NSE trades Monday to
// Friday: a special weekend session would otherwise push the index outside
// the array, which is OS4004.
var dayNames: array<string> = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
var dayUp:    array<number> = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
var dayTotal: array<number> = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

// dayOfWeek is 1 for Monday, so the index is one less.
i = date.dayOfWeek(time) - 1

if bar.isConfirmed
    set(dayTotal, i, element(dayTotal, i) + 1)
    if close > open
        set(dayUp, i, element(dayUp, i) + 1)

panel = table("Up share by weekday", 8, 2)

if bar.isLast
    cell(panel, 0, 0, "Day", textColor = white)
    cell(panel, 0, 1, "Up share", textColor = white)
    for row = 0 to 6
        total = element(dayTotal, row)
        share = total > 0 ? element(dayUp, row) / total * 100 : none
        cell(panel, row + 1, 0, element(dayNames, row))
        cell(panel, row + 1, 1, isNone(share) ? "no data" : text(share, 0) + " percent")
```

When the script does not know the keys in advance, `indexOf()` finds the slot, and `-1` means the key is new. Here the keys are round levels, the nearest multiple of 100 to each close, and the value is how many bars have closed nearest that level so far:

```openscript
version 1
study("Closes near round levels", precision = 0)

// Keys and values, kept in step. The pair of arrays is the map.
var roundLevels: array<number> = []
var closesNear:  array<number> = []

// The key for this bar: the nearest multiple of 100 to the close.
key = round(close / 100) * 100

// Find the key, or add it.
slot = indexOf(roundLevels, key)
if slot == -1
    push(roundLevels, key)
    push(closesNear, 1.0)
else
    set(closesNear, slot, element(closesNear, slot) + 1)

// How many bars so far have closed nearest the same level as this one.
plot(element(closesNear, indexOf(roundLevels, key)), "Closes near this level", aqua)
plot(size(roundLevels), "Levels seen", gray)
```

The keys come from the data, so the set grows only as price reaches new levels. Keep the number of keys small and bounded. An unbounded key set built from data is the shape that reaches OS5002, and it is also the shape that will genuinely want a map when one exists.

## Living without a matrix

A two-dimensional grid is a flat array plus one line of index arithmetic, which is the same layout a matrix would use underneath. This study counts up bars and down bars for each hour of the day; on an NSE chart the hours that fill are 9 to 15.

```openscript
version 1
study("Up share by hour", precision = 0)

hours = 24
cols  = 2       // column 0 counts up bars, column 1 counts down bars

// One flat array, read as a grid of 24 rows and 2 columns.
var grid: array<number> = []
if bar.isFirst
    for k = 0 to hours * cols - 1
        push(grid, 0.0)

// row * width + col is the whole technique; writing it once keeps it in one place.
fn at(row, col, width) => row * width + col

h = date.hour(time)

if bar.isConfirmed and close != open
    col = close > open ? 0 : 1
    i = at(h, col, cols)
    set(grid, i, element(grid, i) + 1)

// The share of up bars in the hour this bar falls in.
ups   = element(grid, at(h, 0, cols))
downs = element(grid, at(h, 1, cols))
plot(ups + downs > 0 ? ups / (ups + downs) * 100 : none, "Up share this hour", aqua)
level(50, "Even", gray)
```

The helper `at` holds no `var` and calls nothing stateful, so its several call sites share nothing and it is safe anywhere. A helper that held state would not be; [User functions](/script/language/functions) explains the difference. `date.hour()` reads the hour in the chart's time zone, so the rows line up with the exchange's clock.

**Related.** [Bars and history](/script/language/bars-and-history), [Persistence](/script/language/persistence), [Control flow](/script/language/control-flow), [User functions](/script/language/functions), [Objects and methods](/script/language/objects-and-methods), [Types and values](/script/language/types-and-values), [Collections reference](/script/reference/collections)


## Objects and methods

Source: https://openalgo.in/script/language/objects-and-methods

Most of what a script draws is a column with one value per bar, declared before the first bar and filled in as the bars arrive. An **object** is different: the script creates it at a moment it chooses, it stays where it was put, and the script can move it, restyle it or delete it any number of bars later. This page covers the objects OpenScript has in this release (the drawing objects `line`, `label`, `box` and `polyline`, and tables), how a script keeps hold of them with handles, and how to stop them piling up on a long chart. It also says plainly what is not in this release: user-defined types with `type`, and method call syntax.

## What this release supports

| Feature | Status | Written as |
|---|---|---|
| Drawing objects: line, label, box, polyline | Available | `draw.line(...)`, then `draw.setTo(handle, ...)` |
| Tables | Available | `table(...)` at the top level, then `cell(...)` |
| Handles held in `var`, arrays and function arguments | Available | `var zone = none`, `array<box>` |
| User-defined record types with fields | Planned. `type` is a reserved word | Not available |
| Method call syntax, `handle.setText(...)` | Not in version 1 | Use `draw.setText(handle, ...)` |
| Function values, passing a function as an argument | Planned | Not available |

Writing `type` today is error [OS1019](/script/errors/syntax#os1019), because the word is reserved for the later version that adds record types:

```openscript
type = input("fast", "Mode", options = ["fast", "slow"])
```

A function name used as a value is error [OS2014](/script/errors/names-and-types#os2014):

```openscript
fn square(x) => x * x
f = square
```

Everything else on this page works today.

## A first example

The most common object script is not a set of objects at all. It is one label, created once and moved every bar: here, a tag beside the latest price showing the close and the 14 bar ATR.

```openscript
version 1
study("Price tag", overlay = true, precision = 2)

atrLen = input(14, "ATR length", min = 1, max = 200)

spread = atr(atrLen)

var tag = none

// Created once. Creating it on every bar instead would leave one label per
// bar on the chart, all but one of them behind the visible window.
if bar.isFirst
    tag = draw.label(time, close, "", color = fade(black, 20), textColor = white)

// Moved and rewritten every bar. The guard says what the reader needs to know:
// until the label exists there is nothing to move. (A change call given an
// absent handle does nothing, so the guard also keeps the text work from running.)
if not isNone(tag)
    draw.setAt(tag, time, close)
    reading = isNone(spread) ? "warming up" : text(spread, 2)
    draw.setText(tag, text(close, 2) + "  ATR " + reading)
    draw.setTextColor(tag, close > open ? lime : red)
```

Note the string. There is no automatic conversion from number to text, so `"ATR " + spread` is error [OS2003](/script/errors/names-and-types#os2003) and the number has to go through `text()`. And `text()` of an absent value is the word "none", which on a chart helps nobody, so the script decides what a warming-up reading looks like and writes that decision in the ternary.

## An object is not a plot

| | A plot | An object |
|---|---|---|
| Declared | Once, at the top level, before bar 0 | On any bar, anywhere in the script |
| Shape | One value per bar | Two points, one point, or a path |
| Changed later | No: you give it a new value each bar | Yes: move it, restyle it, change its text |
| Hidden | By giving it the absent value | By deleting it |
| Removed | Never | By `draw.delete`, and only by that |

The split exists because the two do different jobs. A legend, a price axis and a settings dialog must exist before the first bar runs, so the set of plotted columns is fixed at compile time, and a `plot()` inside an `if` is error [OS3006](/script/errors/arguments#os3006). Geometry has no such constraint: a trendline between two swing points is not a column of numbers, and nothing needs to know in advance how many there will be. A value per bar is a plot; a thing with a position is an object.

## The four drawing objects

| Call | Anchors | Makes | For |
|---|---|---|---|
| `draw.line()` | `draw.line(t1, p1, t2, p2)` | a `line` | A trendline, a level, a ray |
| `draw.label()` | `draw.label(t, p, text)` | a `label` | A plate of text at a point |
| `draw.box()` | `draw.box(t1, p1, t2, p2)` | a `box` | A zone: supply, demand, an opening range |
| `draw.polyline()` | `draw.polyline(times, prices)` | a `polyline` | A path or closed shape through many points |

Each also takes optional styling arguments by name: `color`, `width` and `style` for a line (with `extendLeft` and `extendRight`), `color`, `textColor`, `align` and `tooltip` for a label, `color`, `fillColor`, `opacity`, `width`, `text`, `textColor` and `tooltip` for a box, and `color`, `width`, `closed`, `fillColor` and `opacity` for a polyline. The reference entries list every default.

`draw.polyline` takes two arrays kept in step, one of times and one of prices, rather than one array of points, because there is no record type to make a point from yet. Here it draws the last five closes as a path, updated in place on the newest bar:

```openscript
version 1
study("Recent path", overlay = true)

var path = none

if bar.isLast
    times  = [time[4], time[3], time[2], time[1], time]
    prices = [close[4], close[3], close[2], close[1], close]
    if isNone(path)
        path = draw.polyline(times, prices, color = aqua, width = 2)
    else
        draw.setPoints(path, times, prices)
```

## Anchors are a time and a price

Every object is positioned by a timestamp and a price, never by a bar index. That is a rule about correctness. `bar.index` is a position inside the data the engine happened to be given, so loading more history renumbers every bar and would drag anything anchored to an index sideways. A bar's `time` does not move.

```openscript
rightBars = 5
pivot = pivotHigh(high, 5, rightBars)

// The pivot is known only rightBars bars after it happened, so its anchor is
// the time of the bar it happened on, not the time of the bar we are on now.
if not isNone(pivot)
    draw.label(time[rightBars], high[rightBars], "Swing high", color = red)
```

Subtracting two bar indices within one run is fine, because both come from the same numbering: `bar.index - element(zoneBar, i)` is a real count of bars. It is storing an index and comparing it after a reload that breaks. The error list includes warning [OS8014](/script/errors/warnings#os8014) for a persistent value holding a bar index, but the compiler does not raise it in this release, so the habit is yours: store `time`.

## Verb-first calls instead of methods

Changing an object reads verb first, with the object as the first argument:

```openscript
if bar.isLast
    zone = draw.box(time[10], high[10], time, low[10], color = lime)
    draw.setText(zone, "Demand, 10 bars old")
    draw.setColor(zone, teal)
    draw.setFillColor(zone, fade(teal, 85))
```

There is no `zone.setText(...)`. The dot in `draw.setText` is not a method call: `draw` is a namespace and `setText` is a name inside it, exactly as `math.pi` and `session.isFirstBar` are names inside theirs. The language has one meaning for `a.b`, "the member `b` of the namespace `a`", so method syntax on a handle is error [OS2001](/script/errors/names-and-types#os2001):

```openscript
zone = draw.box(time[10], high[10], time, low[10])
zone.setText("Demand")
```

Version 1 has nothing a method could be built from: no `type`, so a value has no fields, and no function values, so nothing can be attached to a value. Verb-first calls also put the interesting word at the start of the line, which is what you scan for when reading somebody else's study.

## Every change call

| Call | Applies to | Does |
|---|---|---|
| `draw.setFrom()` | line, box | Moves the first anchor |
| `draw.setTo()` | line, box | Moves the second anchor |
| `draw.setBounds()` | line, box | Moves both anchors in one call |
| `draw.setAt()` | label | Moves a label |
| `draw.setPoints()` | polyline | Replaces the path |
| `draw.setText()` | label, box | Changes the text |
| `draw.setColor()` | line, label, box, polyline | Changes the line or border colour |
| `draw.setTextColor()` | label, box | Changes the text colour |
| `draw.setFillColor()` | box, polyline | Changes the fill |
| `draw.setWidth()` | line, box, polyline | Changes the thickness |
| `draw.setStyle()` | line | `"solid"`, `"dashed"` or `"dotted"` |
| `draw.setExtend()` | line | Continues the line to the edge of the pane |
| `draw.setTooltip()` | label, box | Text shown while the pointer rests on it |
| `draw.delete()` | line, label, box, polyline | Removes one object |
| `draw.deleteAll()` | all | Removes every object this script created |
| `draw.count()` | none | How many objects the script holds now |

Every one of these may appear anywhere: inside an `if`, inside a loop, inside a function. They are per-bar events, not declarations.

**The "applies to" column is checked.** An object kind is on the list when its creation call takes that property: only a line has a style, only a label and a box take a tooltip, only a line and a box have a second anchor. Passing another kind is error [OS3011](/script/errors/arguments#os3011) at that argument, so a call that would have drawn nothing is caught before the script runs:

```openscript
tag = draw.label(time, close, "Close")
draw.setStyle(tag, "dashed")
```

## Handles

A creation call returns a **handle**, and the handle is an ordinary value: it can be assigned, kept in a `var`, pushed into an array and passed to a function. That is what lets a script come back to an object on a later bar.

| Handle | Made by | Lives | Notes |
|---|---|---|---|
| `line`, `label`, `box`, `polyline` | The `draw` calls | Until you delete it | An ordinary value: store it, pass it, keep it in an array |
| `plot` | `plot()` | The whole run | Fixed at compile time. It exists so `fill()` can name two plots, and cannot be stored in a `var` |
| `table` | `table()` | The whole run | Declared at the top level like a plot; `cell()` names it |

The absent value is the natural "no object yet" for a drawing handle, and `isNone()` is the test. Starting a handle at `none` gives it its type from the first assignment. A plot handle is not a value that changes per bar, so putting one in a `var` is error [OS2003](/script/errors/names-and-types#os2003):

```openscript
p = plot(close, "Close")
var kept = p
```

## Lifetime

**An object lasts until the script deletes it**: not until the next bar, not until the chart scrolls. A script may hold a limited number at once (10,000 by default), and a script that would go past the limit stops with error [OS5010](/script/errors/limits#os5010) rather than having its oldest drawing quietly dropped. A silent drop would give a study that is right on a short chart and wrong on a long one, in a way the source does not show. The cost of that choice is that housekeeping is your job, which is what the rest of this page is about.

One piece of housekeeping is done for you. The newest bar of a chart receiving real-time updates is executed again on every update, and the rollback rule restores the set of objects to what it was at the end of the previous bar before each re-execution. So a script that creates one line per bar creates one per bar, not one per update. [Realtime and confirmation](/script/language/realtime-and-confirmation) covers the rule.

## A capped set of objects

When a study draws one object per event, the script owns the cap. An array of handles and one trim is the whole pattern:

```openscript
version 1
study("Breakout rays", overlay = true, precision = 2)

len  = input(20, "Lookback", min = 2, max = 500)
keep = input(5, "Rays kept", min = 1, max = 50)

// The prior window's high, read one bar back so this bar's own high
// cannot be part of the level it is breaking.
priorHigh = highest(high, len)[1]
broke = close > priorHigh

// An array of handles. Its element type comes from the first push.
var rays = []

if broke
    ray = draw.line(time, priorHigh, time, priorHigh, aqua, 1)
    draw.setExtend(ray, false, true)
    push(rays, ray)

// Oldest first, because shift takes from the front. Deleting the object and
// removing its handle happen in one statement, so they cannot drift apart.
while size(rays) > keep
    draw.delete(shift(rays))

plot(priorHigh, "Prior high", gray, style = "step")
```

Two details make this safe. The trim is a `while` loop outside the `if`, so it enforces the cap on every bar however the array grew, rather than trusting that each push added exactly one handle. And `draw.delete(shift(rays))` deletes exactly the handle it removes, which is the shape to copy: a delete in one place and a removal in another is how a script ends up holding handles to objects that are gone.

## Deleting, and the stale handle

A handle outlives its object. Deleting the object does not clear the variable, and using a handle after its object is gone is error [OS4005](/script/errors/runtime#os4005), with a message naming the bar the object was deleted on. The script stops on that bar.

Here is the pattern, complete, with the guard in place. It keeps one demand zone from the latest swing low, stretches it right while it holds, and removes it when price closes below it:

```openscript
version 1
study("One demand zone", overlay = true, precision = 2)

leftBars  = input(5, "Pivot left bars", min = 1, max = 50)
rightBars = input(5, "Pivot right bars", min = 1, max = 50)

// A pivot is reported rightBars bars after the bar it formed on, the first
// bar on which it could honestly be known.
pivot = pivotLow(low, leftBars, rightBars)

var zone       = none
var zoneBottom = none

// A new pivot replaces the old zone. Nothing deletes the old object for you,
// so the script does it before overwriting the handle.
if not isNone(pivot)
    if not isNone(zone)
        draw.delete(zone)
    zoneTop    = min(open[rightBars], close[rightBars])
    zoneBottom = low[rightBars]
    zone = draw.box(time[rightBars], zoneTop, time, zoneBottom,
                    color = lime, fillColor = fade(lime, 85), text = "Demand")

// Price closed through it, so the zone is finished. Delete the object and
// forget the handle in the same block.
if not isNone(zone) and close < zoneBottom
    draw.delete(zone)
    zone = none
    zoneBottom = none

// Still alive: stretch its right edge to this bar. setTo moves the second
// anchor only, so the left edge stays where the pivot put it.
if not isNone(zone)
    draw.setTo(zone, time, zoneBottom)
```

The supply and demand study in [Example scripts](/script/getting-started/example-scripts#9-supply-and-demand-zones) applies the same pattern to many zones at once, on both sides of price:


Take out the two lines that set `zone` and `zoneBottom` back to `none` and the study still compiles, draws correctly for a while, and then stops with OS4005 on the first bar a zone is broken, because `draw.setTo()` is handed a box that no longer exists.

That is an error rather than a call that quietly does nothing because a script changing a deleted object has lost track of its own state, and it will keep losing track. The silent version of this bug is a study that appears to work while half its drawing calls land nowhere.

| Wrong | Right |
|---|---|
| `draw.delete(zone)` and leave `zone` holding the handle | `draw.delete(zone)`, then `zone = none` |
| Guard with a separate bool that can drift out of step | Guard with `isNone(zone)` |
| Delete in one branch and clear the handle in another | Do both in the same block |

## When to delete

| Situation | Do |
|---|---|
| The object describes a condition that has ended | Delete it on the bar the condition ends, and clear the handle |
| The object is old enough to be noise | Keep its creation time beside the handle and delete on age |
| The study draws one object per event | Cap the count and delete oldest first |
| The whole picture depends only on the current state | `draw.deleteAll()` and redraw, but only when `bar.isLast` is true |
| Every object is the study's output and still means something | Keep it, and say so in a comment |

`draw.deleteAll()` followed by a redraw is the simplest correct approach when a study shows the present state rather than a history, but doing it on every bar means rebuilding the picture on every bar of the chart to display the last one. Guard it with `bar.isLast` and the cost disappears.

While you develop a drawing-heavy study, put `draw.count()` in a table cell. A number that climbs forever is the symptom of every bug on this page.

## Objects cannot be read back

An object can be written to and deleted. There is no call that asks a box where its edges are, or a label what its text says. So a script that needs to reason about what it drew has to remember it: an array of handles, and one array per fact about them, all indexed together.

```openscript
var zones:      array<box> = []
var zoneTop:    array<number> = []
var zoneBottom: array<number> = []
var zoneTime:   array<number> = []

if bar.isConfirmed and close > open and close[1] < open[1]
    push(zones, draw.box(time[1], high[1], time, low[1], color = teal))
    push(zoneTop, high[1])
    push(zoneBottom, low[1])
    push(zoneTime, time[1])

// Drop the oldest record from all four arrays together.
if size(zones) > 20
    draw.delete(shift(zones))
    shift(zoneTop)
    shift(zoneBottom)
    shift(zoneTime)

plot(size(zones), "Zones held")
```

That is the parallel array technique from [Collections](/script/language/collections), and here it is not a style choice but the only option. Plan for two consequences: a removal loop over these arrays must count downwards, so removing one record cannot renumber a record the loop has yet to visit, and every add and every remove must touch all the arrays in the same block. The complete supply and demand example in [Example scripts](/script/getting-started/example-scripts) does exactly this over hundreds of bars.

Objects being write-only is a known gap rather than a settled design. Write scripts so the answer does not matter: keep the facts you need in your own arrays, and treat the object as a picture of them.

## Tables

A table is the other object a script creates, and it behaves differently on purpose. It is declared at the top level, before bar 0, like a plot, because the pane has to know what it is reserving room for:

```openscript
version 1
study("Session panel", overlay = true, precision = 2)

panel = table("Session", 3, 2, position = "topRight", textColor = silver)

spread = atr(14)

// The session's first bar, or of the IST day where the host states no
// session hours, as on the /trading chart.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

// Bars since the session opened, counted with a var that restarts each session.
var sessionBars = 0
sessionBars = newSession ? 1 : sessionBars + 1

// Written only on the newest bar. The panel shows one state, the current one,
// so writing it on every bar would be work thrown away on every bar but the last.
if bar.isLast
    cell(panel, 0, 0, "Symbol")
    cell(panel, 0, 1, chart.symbol, textColor = white)
    cell(panel, 1, 0, "ATR 14")
    cell(panel, 1, 1, isNone(spread) ? "warming up" : text(spread, 2))
    cell(panel, 2, 0, "Bars this session")
    cell(panel, 2, 1, text(sessionBars))
```

The dashboard in [Example scripts](/script/getting-started/example-scripts#8-dashboard) is built the same way, with more rows, and also writes only on the newest bar:


`table()` is top level only, for the same reason `plot()` is. `cell()` and `clear(panel)` may appear anywhere, and `clear` empties every cell so a table can be rebuilt from scratch. There is no `draw.delete` for a table: it is a fixed part of the study, like a plot, and lives as long as the study does. A cell outside the declared rows and columns stops the script; in this release it raises [OS4004](/script/errors/runtime#os4004), naming the index, while the more specific [OS4008](/script/errors/runtime#os4008) in the error list is not raised yet. [Tables](/script/visuals/tables) covers layout and styling.

## Errors you will meet

| Code | Means | Usual fix |
|---|---|---|
| [OS4005](/script/errors/runtime#os4005) | A change call was given an object that was deleted | Set the handle to `none` when you delete, and guard with `isNone` |
| [OS3011](/script/errors/arguments#os3011) | A change call was given the wrong kind of object | Check the "applies to" column above |
| [OS3006](/script/errors/arguments#os3006) | `plot`, `fill`, `level` or `table` inside a block | Move it to the top level; hide a plot by plotting `none` |
| [OS4004](/script/errors/runtime#os4004) | A cell outside the table, or an index outside an array of handles | Declare enough rows and columns, or check the index |
| [OS5010](/script/errors/limits#os5010) | More objects than a script may hold | Cap the set and delete as you drop handles |
| [OS2001](/script/errors/names-and-types#os2001) | Method syntax such as `zone.setText(...)` | Write `draw.setText(zone, ...)` |

## User types and methods: what is planned

`type` is reserved in version 1 for user-declared record types and the field access that goes with them. When it arrives it is meant to replace the parallel arrays above with one array of records, and to give `draw.polyline` a natural array of points. Until then, the parallel array shape is the way to keep several facts per object. The exact spelling of records, and whether handles gain any method-style calls, will be settled with that language version; nothing in this release depends on it, and a script that compiles under `version 1` keeps compiling and keeps drawing the same thing afterwards.

**Related.** [Collections](/script/language/collections), [Realtime and confirmation](/script/language/realtime-and-confirmation), [User functions](/script/language/functions), [Libraries](/script/language/libraries), [Lines and boxes](/script/visuals/lines-and-boxes), [Labels and shapes](/script/visuals/labels-and-shapes), [Drawing objects reference](/script/reference/drawing)


## Libraries

Source: https://openalgo.in/script/language/libraries

Sooner or later you write a helper, a band calculation or a trailing stop, that you want in more than one script. This page covers how to share code between OpenScript files. In this release there is no `import`: every function a script calls is either in the standard library or declared in that same file. So the page has two halves. The first is what to do today, which is real work with a real payoff: organise a script so its helpers are portable, and share them as a canonical copy with a revision header. The second is the design for libraries and `import`, written down now so that the way you organise scripts today survives it.

## Where the language stands today

| Thing | Status in this release |
|---|---|
| One file, one `study()` or `strategy()` declaration | How every script works |
| `fn` at the top level of that file | The unit of reuse |
| `import` of another file | Planned. A reserved word, not implemented |
| `as`, for naming an import | Planned. A reserved word, not implemented |
| `type`, for a record a library could return | Planned. A reserved word, not implemented |

Writing `import` today is error [OS1019](/script/errors/syntax#os1019), which says the word is reserved rather than unknown. That difference is deliberate: it tells you the gap is known and named.

```openscript
import = "bands"
```

## A first example: one file, organised to be split

A long script falls naturally into three regions: the declaration and inputs, the helpers, and the per-bar body that uses them. Keeping them in that order, with the helpers in one block, costs nothing and turns any later extraction into moving lines rather than rewriting them.

```openscript
version 1
study("Squeeze", overlay = true, precision = 2)

// Inputs

len     = input(20, "Basis length", min = 2, max = 500)
mult    = input(2.0, "Deviation multiple", min = 0.5, max = 5)
atrLen  = input(10, "ATR length", min = 1, max = 200)
atrMult = input(1.5, "ATR multiple", min = 0.5, max = 5)

// Helpers
// Each takes what it needs as arguments and reads nothing from the file,
// so any one of them could move to another script unchanged.

// [basis, upper, lower]. First value at bar n - 1, the warmup of sma and stdev.
fn deviationBands(src, n, k) =>
    mid   = sma(src, n)
    width = k * stdev(src, n)
    [mid, mid + width, mid - width]

// [basis, upper, lower]. First value at bar max(n, aLen) - 1.
fn rangeBands(src, n, aLen, k) =>
    mid   = ema(src, n)
    width = k * atr(aLen)
    [mid, mid + width, mid - width]

// True when the first band pair sits inside the second. Absent while either
// pair is still warming up, which is the honest answer then.
fn inside(a, b) => element(a, 1) < element(b, 1) and element(a, 2) > element(b, 2)

// Body

dev = deviationBands(close, len, mult)
rng = rangeBands(close, len, atrLen, atrMult)

squeezed = inside(dev, rng)

upper = plot(dev[1], "Upper", aqua)
lower = plot(dev[2], "Lower", aqua)
plot(dev[0], "Basis", orange, width = 2)
fill(upper, lower, fade(aqua, 90))

background(squeezed ? fade(yellow, 90) : none)

if squeezed and not squeezed[1]
    signal("SQUEEZE")
```

Everything in the helpers block could be pasted into another file with no edits, because none of it mentions `len`, `mult`, `atrLen` or `atrMult`. (The one catch is a clash of local names, covered in the next section.) Each helper returns several values as one array, the same convention the standard library uses for `bollinger()` and `macd()`; [Collections](/script/language/collections) covers reading them.

## The test: does the helper need the file?

Read the body of a helper and ask whether every name in it is a parameter, a local, or a standard library name. If one is not, the helper belongs to this file and cannot move.

```openscript
len = input(20, "Length", min = 2, max = 500)

// Legal, and stuck here. It reads len from the file, so it means something
// different in a file where len means something else, and nothing at all in
// a file with no len.
fn basisHere(src) => sma(src, len)

// Portable. Everything it needs arrives through the call.
fn basisOver(src, n) => sma(src, n)

plot(basisHere(close), "Basis, file length")
plot(basisOver(close, 50), "Basis, 50 bars")
```

The language allows the first form, because a function body is a scope nested inside the file, and it is sometimes fine for a throwaway helper in a short script. It costs three things: the function can no longer be understood from its own text, it cannot be tested on its own, and it cannot be moved. A parameter costs a few characters.

**Local names travel with the helper.** A name declared inside a function body may not also be declared at the top level of the same file, before or after the function, because there is never a second variable with the same name. So a helper whose body declares `mid` cannot be pasted into a file that already has a top-level `mid`: that is error [OS2002](/script/errors/names-and-types#os2002).

```openscript
fn deviationBands(src, n, k) =>
    mid   = sma(src, n)
    width = k * stdev(src, n)
    [mid, mid + width, mid - width]

mid = hl2
plot(deviationBands(close, 20, 2)[1] - mid, "Upper band distance")
```

Two helpers may use the same local names as each other, so the fix is on the consuming side: rename the file's own name, or give the helper's locals names a consuming script is unlikely to use.

The same test rules out a few other shapes:

| A helper that | Cannot move freely, because |
|---|---|
| Calls `input()` | `input` is top level only, so it is already error [OS3007](/script/errors/arguments#os3007) inside a function |
| Calls `plot()`, `fill()`, `level()` or `table()` | These are top level only too ([OS3006](/script/errors/arguments#os3006)): the study's fixed shape is built before bar 0 |
| Calls `signal()`, `background()` or `barColor()` | Legal anywhere, but now it paints the consuming script's chart, which is that script's decision |
| Places an order | Only a `strategy()` file may, so the helper cannot be used in a study ([OS7001](/script/errors/orders#os7001)) |
| Reads `chart.symbol` or another instrument fact and branches on it | Portable, but it behaves differently per chart, which has to be documented |

A helper that computes and returns is portable. A helper that draws or trades is part of a particular script.

## Sharing a block between scripts today

With no `import`, sharing means copying, and copies drift apart. The discipline that keeps that manageable is small.

Keep one canonical copy of the block in a script of its own in the Scripts panel, and give the block a header saying what it is and which revision this copy is:

```openscript
// bands, revision 4
// Canonical copy: the "bands" script in the Scripts panel.
// Changed in 4: rangeBands takes the ATR length separately from the basis
// length. A call written for revision 3 passes one length and now gets the
// wrong second band, so check your call sites.

fn deviationBands(src, n, k) =>
    mid   = sma(src, n)
    width = k * stdev(src, n)
    [mid, mid + width, mid - width]
```


Then every script that uses the block carries the same header, and one look tells you whether it is behind:

```openscript
version 1
study("Deviation bands", overlay = true, precision = 2)

len  = input(20, "Basis length", min = 2, max = 500)
mult = input(2.0, "Deviation multiple", min = 0.5, max = 5)

// bands, revision 4
// Canonical copy: the "bands" script in the Scripts panel.

fn deviationBands(src, n, k) =>
    mid   = sma(src, n)
    width = k * stdev(src, n)
    [mid, mid + width, mid - width]

// Body

b = deviationBands(close, len, mult)

up = plot(b[1], "Upper", aqua)
dn = plot(b[2], "Lower", aqua)
plot(b[0], "Basis", orange, width = 2)
fill(up, dn, fade(aqua, 92))
```

Be honest about the cost. A copy is a fork: a bug fixed in the canonical script is not fixed in the six scripts that copied it, and nothing in the language will tell you. Two habits keep it manageable. Copy blocks that are small and stable rather than large and changing, and write the revision in the header every time, because a header that is sometimes missing is a header nobody trusts. The Scripts panel keeps no revision history in this release, so the header is the only record of which copy is which; [The editor](/script/getting-started/the-editor) covers saving and backups.

## State stays with the caller

A helper may hold a `var`, and may call stateful library functions such as `ema()` or `cum()`. Wherever the helper lives, **its state is allocated per call site, in the script that calls it**:

```openscript
// A trailing low that only ratchets upward.
fn trailingLow(src) =>
    var trail = none
    trail = isNone(trail) ? src : max(trail, src)
    trail

fast = trailingLow(low)        // one trailing level
slow = trailingLow(low[5])     // a second, independent one

plot(fast, "Trail on this low")
plot(slow, "Trail on the low 5 bars back")
```

The two calls have two independent trailing levels, and two different scripts on the same chart have their own again. So a helper author can write a stateful helper without asking who else calls it, and a caller can use one twice without the calls interfering. [User functions](/script/language/functions) explains the rule.

Two things follow for anyone writing a shared helper. Never describe a function as though there is one of it: if it counts something, it counts per call site. And never write a helper whose correctness depends on being called on every bar, because a caller will put it inside a branch, its state will advance only on the bars the branch runs, and the compiler will warn with [OS8001](/script/errors/warnings#os8001).

## What a library will be

> **Planned**
Nothing in this section compiles in this release. What is already fixed is the set of reserved words and the compatibility promise. The spelling below is the intended shape, not a final specification, written down so that the way you organise scripts today is the way you will organise them then.

A library is a file with a declaration of its own that exports functions. A script imports it under a name and calls through that name:

```text
// Planned: a library file. Not valid in this release.

version 1

library("bands", version = "1.2.0")

// Exported: part of the published surface, and bound by the versioning rules.
export fn deviationBands(src: series number, n: number, k: number = 2) =>
    mid   = sma(src, n)
    width = k * stdev(src, n)
    [mid, mid + width, mid - width]

// Not exported: private to the library, free to change in any release.
fn midpoint(a, b) => (a + b) / 2
```

```text
// Planned: a script that uses it. Not valid in this release.

version 1

study("Bands", overlay = true, precision = 2)

import "bands@1.2.0" as bands

b = bands.deviationBands(close, 20, 2)
plot(b[0], "Basis", orange)
```

Three properties of that design follow from decisions the language has already made:

- **The import names the version.** A script that did not pin a version would have its numbers changed by somebody else's edit.
- **The import binds a name, and calls go through it.** `bands.deviationBands` is a namespace member, the same shape as `math.pi` and `session.isFirstBar`, so the dot keeps its one meaning. Two libraries can export the same function name without colliding, and a reader sees which library a line depends on without scrolling to the imports. Because a library's names sit behind its own name, the local-name clash described above goes away for imported code.
- **A library has its own declaration.** A file carries exactly one declaration, and a library is not a study: it declares no inputs, plots nothing and trades nothing.

### What may be exported

| Exportable | Why |
|---|---|
| A function | The unit of reuse, and the only thing a caller can call |
| A function returning several values as an `array<number>` | Already the convention throughout the standard library |
| A constant, written as a function with no arguments | Nothing else can carry a value across a file boundary |

| Not exportable | Why not |
|---|---|
| An `input()` | Inputs build the settings dialog of a study before bar 0. A library adding rows to a dialog it does not own would make the caller's settings unpredictable from the caller's own source |
| A `plot`, `fill`, `level` or `table` | The chart surface belongs to the calling study: its legend, its axis, its saved layout |
| An order | Only a strategy trades, and a library that placed orders would be trading from a file the strategy's author did not read |
| A file-level `var` | State shared between unrelated callers would make one script's numbers depend on whether another ran first |

The line through all four is the same: a library computes, and the caller decides what to do with the answer. That is what makes a library safe to use by reading its signature rather than its body.

### Versioning

A library version is a promise about what a caller's numbers will do, the same promise the language makes about itself: a script that compiled and produced a number keeps compiling and producing that number.

| Change | Version part to raise | Why |
|---|---|---|
| Add a new exported function | Minor | Nothing a caller already uses has moved |
| Add an optional parameter, with a default, at the end | Minor | Every existing call means what it meant |
| Improve the implementation with identical output | Patch | Nothing observable changed |
| Fix a comment or documentation | Patch | The same |
| Add a required parameter | Major | Every existing call breaks at compile time |
| Reorder parameters | Major | Positional calls keep compiling and silently change meaning |
| Rename a parameter | Major | Named calls break |
| Change a default value | Major | A call relying on the default changes its numbers with no edit |
| Change the arithmetic so a result differs | Major | A chart that redraws itself after an update is worse than one that is slightly off in a documented way |
| Change a warmup length | Major | The caller's plot starts on a different bar, and any guard on absence behaves differently |
| Remove a function | Never | Deprecate it instead |

Much of that table is about changes that keep compiling. A change that breaks the build costs an afternoon. A change that compiles and moves the numbers is a strategy that traded differently with nobody knowing why, which is why reordering a parameter or changing a default is treated as seriously as removing a function.

### A published version never changes

**Once a library version is published, its contents never change. To change anything, publish a new version.**

A backtest is only evidence if it can be run again and give the same answer. A library version that could be edited after publication would break that: a script pinned to the same version would give different numbers on Tuesday than on Monday, with nothing in the script itself to explain it. A caller who pinned `1.2.0` reviewed `1.2.0`, and can go on trusting that review for as long as the pin stands.

In practice:

- Publishing is one way. There is no edit; a correction is a new version.
- Withdrawing a version may mark it as unsafe, but does not alter it, because something running may be pinned to it.
- A version number is a name, not a ranking. `1.2.1` does not replace `1.2.0`; it sits beside it, and a caller moves when the caller decides to.

### Deprecating instead of removing

The language never removes a construct that turns out to be a mistake: it keeps working, the compiler warns and names the replacement, and it is still there several versions later. A library is meant to behave the same way:

```text
// Planned. Not valid in this release.

// Deprecated in 2.1.0. Use bandsWithSource, which takes the source explicitly
// instead of assuming close. This function keeps working and keeps returning
// the numbers it always returned.
export fn bands(n, k) => bandsWithSource(close, n, k)
```

The old function stays, written in terms of the new one so the two cannot drift apart. A caller sees a warning and moves when they have time, rather than on a day when a deployed strategy is holding a position.

## Writing helpers a stranger can rely on

These habits make a shared block safe today and a library easy later:

| Do | Because |
|---|---|
| One concern per block | A caller copies what they use, and a revision touches the scripts it actually affects |
| State each function's first value in a comment above it | The caller's plot starts where your warmup says; see [Warmup](/script/language/warmup) |
| State what the function does with an absent input | Absence carries through by default; if yours does something else, that is news |
| Return several values as one array with a documented order | Three functions would be three call sites computing the shared work three times |
| Keep the surface small | Everything you share is a promise you keep for years |
| Write example calls in a comment | They double as the checks you want when you change the implementation |

| Do not | Because |
|---|---|
| Read instrument facts silently | The function behaves differently per chart and the caller cannot see why |
| Draw, paint, signal or alert | That is the caller's chart, and the caller's decision |
| Keep state that assumes one caller | A call site can be anywhere, including inside a loop or a branch |
| Use short, common local names | Until `import` exists, a helper's locals must not clash with the caller's top-level names |

## What is decided and what is not

| Decided | Meaning for you |
|---|---|
| `import`, `as` and `type` are reserved in version 1 | Using one as a name is an error today, so adding the feature later cannot break your script |
| A script that compiles under `version 1` keeps compiling and producing the same numbers | Nothing you write today breaks when libraries arrive |
| A construct that turns out to be a mistake is deprecated, never removed | The same |
| State is allocated per call site | A stateful helper stays reusable wherever it lives |

| Not decided | Note |
|---|---|
| The spelling of the library declaration, `export` and `import` | The sketches above are intent, not specification |
| How a library is named and where the application running a script finds it | A path, a name and a version, or some mix |
| Whether a library may export a type once `type` exists | Open |

Write your helpers to take parameters and return values, keep them in one block with a header, and none of the open questions can cost you a rewrite.

**Related.** [User functions](/script/language/functions), [Collections](/script/language/collections), [Objects and methods](/script/language/objects-and-methods), [Warmup](/script/language/warmup), [Variables and scope](/script/language/variables-and-scope), [Sharing scripts](/script/writing/sharing-scripts), [Style guide](/script/writing/style-guide)


# Data and time

## Timeframes

Source: https://openalgo.in/script/data/timeframes

Every script in OpenScript (also called OpenAlgo Script) runs on the bars of one chart, and the chart's **interval** decides how much trading each bar covers. This page shows how to read that interval from inside a script, how a timeframe is written when you ask for one, and how to convert a period you think of in minutes into a count of bars. It matters because almost every library call counts in bars, so a study tuned on a five minute chart can mean something quite different when you drop it on a daily one.

## What an interval is

The chart hands the engine (the part of OpenScript that runs your script, once per bar) a list of bars, oldest first, and the interval is the rule that decided where one bar ended and the next began. On a five minute chart of an NSE stock, each bar covers five minutes of trading, and a full 09:15 to 15:30 IST session holds 75 of them. On a daily chart, each bar covers one whole session.

Two facts about a bar sit under everything on this page:

- **A bar covers a known span of time.** Every bar carries `time`, the instant it opened, as milliseconds since 1 January 1970 in UTC. `timeClose`, the instant a bar ends, is planned and not available yet, so a script that needs the end of a bar adds the interval to `time`.
- **The newest bar is not finished.** Until its interval has elapsed, its `close` is the last traded price rather than a closing price, and `bar.isConfirmed` is `false`. [Realtime and confirmation](/script/language/realtime-and-confirmation) covers what that means for signals and orders.

### Clock intervals and calendar intervals

Minutes and hours are measured by the clock. Days, weeks and months are measured by the calendar and by the instrument's session.

A daily bar is not 1440 minutes of trading. It is one session, which is 375 minutes on NSE and BSE, a much longer day running into the late evening on MCX, or a shorter day when the exchange closes early. A monthly bar is 28 to 31 days depending on the month.

`chart.intervalMinutes` is the interval's **nominal** length in minutes, worked out from how the interval is written. It is 5 on a `"5m"` chart, 1440 on a `"1D"` chart and 10080 on a `"1W"` chart. A month has no fixed length, so on a `"1M"` chart it is absent. `chart.isIntraday` is `true` only for an interval shorter than one day.

| Interval | Measured by | `chart.intervalMinutes` | `chart.isIntraday` |
|---|---|---|---|
| `"1m"` to `"4h"`, and any count of minutes | The clock | The interval in minutes | `true` |
| `"1D"` | The session | `1440` | `false` |
| `"1W"` | The calendar | `10080` | `false` |
| `"1M"` | The calendar | absent | absent |
| An interval the language cannot read, such as `"D"` | Unknown | absent | absent |

The 1440 of a daily chart counts clock minutes, not trading minutes, so never divide by it to count time the market was open. The last row matters in /trading, which names its daily chart `D`: see [the last section](#where-the-interval-is-known-in-trading).

## How a timeframe is written

When a script names a timeframe, for a [higher timeframe read](/script/data/higher-timeframes) or an [interval input](/script/inputs/inputs#interval), it writes a count and a unit as a string.

| Written | Means |
|---|---|
| `"1m"`, `"5m"`, `"15m"`, `"75m"` | Minutes |
| `"1h"`, `"2h"`, `"4h"` | Hours |
| `"1D"` | Days |
| `"1W"` | Weeks |
| `"1M"`, `"3M"` | Months |
| `"60"` | A bare number is minutes, so this is the same as `"1h"` |

Two rules to hold on to:

- **The unit letter is case sensitive.** `"1M"` is one month and `"1m"` is one minute. Minutes and hours are written in lower case and days, weeks and months in upper case, so `"1d"`, `"1w"` and `"1H"` are not timeframes, and neither is a unit with no count, such as `"D"`.
- **A bare number is minutes.** That form exists so that a script can pass on an interval written as a plain count of minutes without reformatting it.

A string written in the source that is not one of these forms is refused by the compiler with OS6001, naming the value:

```openscript
// "1d" is not a timeframe: the day unit is an upper case D.
dayHigh = req.timeframe("1d", high)
plot(dayHigh, "Day high")
```

## Reading the chart's own interval

Four values describe the interval the script is running on. They live in the `chart` namespace.

| Value | Type | Holds |
|---|---|---|
| `chart.interval` | `string` | The interval as the host names it, for example `"5m"` |
| `chart.intervalMinutes` | `number` | The interval's nominal length in minutes, absent for a month or an interval the language cannot read |
| `chart.isIntraday` | `bool` | Whether the interval is shorter than one day, absent where `chart.intervalMinutes` is |
| `chart.timezone` | `string` | The zone the chart's time axis is labelled in, such as `"Asia/Kolkata"` |

These are plain values, not series. The interval is the same for the whole run, because changing the chart's interval loads different bars and starts a new run. So they carry no history, and `chart.interval[1]` is refused with OS2004. That constancy is also what makes it safe to branch on them at the top level of a file.

```openscript
version 1

study("What am I running on", overlay = true)

// A table is declared once, before the first bar. Only its cell contents
// change from bar to bar.
panel = table("Chart", 3, 2, position = "topRight", textColor = silver)

// Written only on the newest bar: the panel shows one state, the current one.
if bar.isLast
    cell(panel, 0, 0, "Interval")
    cell(panel, 0, 1, chart.interval, textColor = white)

    cell(panel, 1, 0, "Minutes per bar")
    cell(panel, 1, 1, isNone(chart.intervalMinutes)
                      ? "not known"
                      : text(chart.intervalMinutes, 0))

    cell(panel, 2, 0, "Intraday")
    cell(panel, 2, 1, chart.isIntraday ? "yes" : "no")
```

The absent case is written out rather than left to `text()`, because `text(none)` is the string `"none"`, and a panel that reads "none" does not tell anyone why.

## A length in bars is not a length in time

Almost every library call takes a length in **bars**. `sma(close, 20)` averages twenty bars, whatever a bar is on this chart. That is the right default, because a bar is the unit a study is drawn in, but it means the same script covers a different span of time on every interval.

| Chart interval | `sma(close, 20)` covers |
|---|---|
| `"1m"` | 20 minutes |
| `"5m"` | 100 minutes |
| `"15m"` | 5 hours, most of an NSE session |
| `"1h"` | 20 hourly bars, about three NSE sessions |
| `"1D"` | 20 sessions, about a month of trading |

None of those is wrong. The trouble starts when a length chosen because it worked on one interval is carried to another, where the same number means something else. Decide once which unit the study is really about, and say so in the input's title.

| The study is about | State the length in | Why |
|---|---|---|
| The shape of the last N bars | Bars | The pattern is made of bars |
| The last two hours of trading | Minutes, converted to bars | That is the unit the trader thinks in |
| The time since the day's first bar | Milliseconds from `time` | Neither bars nor a fixed clock span |
| Yesterday or last week | A coarser interval read | See [Higher timeframes](/script/data/higher-timeframes) |

## Converting minutes into bars

The arithmetic is one division. The three guards around it are what make it correct.

```openscript
periodMinutes = input(120, "Average length, in minutes", min = 5, max = 1440)
barsWanted = max(1, round(periodMinutes / chart.intervalMinutes))
plot(sma(close, barsWanted), "Time based mean")
```

**Guard one: the chart may not be an intraday one.** On a `"1D"` chart the division is 120 by 1440, which rounds to 0 and is floored to 1, so the study quietly becomes a one bar average. On a `"1M"` chart, or a daily chart whose interval the language cannot read, `chart.intervalMinutes` is absent. Absence carries through the division and through `max()`, and `sma()` given an absent length draws nothing on any bar, with no error to say why. Test `chart.isIntraday` first, and treat an absent answer as "no" with `orElse()`.

**Guard two: a length must be a whole number of 1 or more.** A fractional length is refused rather than truncated: the study stops on that bar with OS4003, because a length of 14.5 is a bug and rounding it quietly would hide the bug. So round it, then floor the result at 1, because `round(0.4)` is 0 and a length of zero stops the study the same way.

**Guard three: the answer is exact only inside a session.** Sixty minutes is twelve five minute bars while the market is open. It is not twelve bars across the overnight gap, a weekend or a holiday, because no bars exist in those spans. A bar count counts bars that traded, not wall clock time.

Here is the whole pattern in a script:

```openscript
version 1

study("Time based mean", overlay = true, precision = 2)

periodMinutes = input(120, "Average length, in minutes", min = 5, max = 1440)

// One place decides what a chart the clock cannot measure means for this study.
// orElse, because chart.isIntraday is absent where the interval is not one the
// language can read, and absent should mean "not usable" here.
usable = orElse(chart.isIntraday, false)

// The length is computed on every bar and floored at 1, so sma() always gets a
// legal length. The output is hidden at the plot, with none, rather than by
// putting sma() inside a branch: a stateful call inside a branch advances only
// on the bars where the branch runs, which is warning OS8001 and a broken line.
barsWanted = usable ? max(1, round(periodMinutes / chart.intervalMinutes)) : 1
line = sma(close, barsWanted)

plot(usable ? line : none, "Time based mean", aqua, width = 2)

// A study that cannot say what it means on this chart says so, once.
note = table("Note", 1, 1, position = "bottomRight", textColor = silver)
if bar.isLast and not usable
    cell(note, 0, 0, "This study needs an intraday chart")
```

What the division produces for a 120 minute input:

| Chart interval | 120 divided by the interval | Rounded | Floored at 1 | Span really covered |
|---|---|---|---|---|
| `"1m"` | 120 | 120 | 120 | 120 minutes |
| `"5m"` | 24 | 24 | 24 | 120 minutes |
| `"45m"` | 2.67 | 3 | 3 | 135 minutes |
| `"4h"` | 0.5 | 1 | 1 | 240 minutes |
| `"1D"` | 0.08 | 0 | 1 | Not used: `chart.isIntraday` is `false`, so the study hides itself |

The last two rows are the honest part. On a four hour chart, "the last two hours" cannot exist: the finest thing the chart knows is four hours, and one bar is the closest answer. If that is not acceptable for a particular study, test for it and hide the output, as the daily case does.

## Measuring in milliseconds instead

`time` is UTC milliseconds, so the difference between two bars' times is a real duration, whatever number of bars sits between them. This study shades the first minutes of each trading day, and says the same thing on a one minute chart and a fifteen minute one:

```openscript
version 1

study("First minutes of the day", overlay = true, precision = 2)

windowMinutes = input(15, "Window, in minutes", min = 1, max = 240)

// A new calendar day in India Standard Time. NSE, BSE and MCX sessions never
// run past midnight IST, so for them a new day is a new session.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

var dayOpen = none
if newDay
    dayOpen = time

// Milliseconds since the day's first bar, not a bar count.
elapsed = isNone(dayOpen) ? none : time - dayOpen
inWindow = not isNone(elapsed) and elapsed < windowMinutes * 60000

background(inWindow ? fade(silver, 92) : none)
plot(inWindow ? high : none, "High in the window", aqua, style = "step")
```

The language's own answer to "is this the first bar of the session" is `session.isFirstBar`. It depends on the host supplying the instrument's session hours, and [Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today) explains where that works in /trading today and why this study tests the date instead.

Two units, two jobs:

| Measure in | Right when | Misleading when |
|---|---|---|
| Bars | The study is about the last N bars of price action | Across a session break, a holiday, or a feed with missing bars |
| Milliseconds from `time` | The study is about a span of trading time | Across the overnight gap, where the span includes hours nobody traded |

Neither is right everywhere. What matters is that you choose, and that the choice is visible in the source.

## Warmup moves with the interval

A call that needs `len` bars is absent until `len` bars exist, and there is no hidden warmup beyond that. When a length comes from a conversion, the number of empty bars at the left edge of the chart changes with the interval, and a reader who does not expect it may read the gap as a bug.

| Chart interval | `periodMinutes` | Length in bars | Absent bars at the start |
|---|---|---|---|
| `"1m"` | 120 | 120 | 119 |
| `"5m"` | 120 | 24 | 23 |
| `"1h"` | 120 | 2 | 1 |

Warmups also add up. `sma(ema(close, 10), 10)` is absent until bar 18, because the inner call is absent for its first nine bars and the outer call then needs ten present values. A converted length that feeds a stack of calls can leave a long empty stretch on a fine interval. That is the study saying it does not have the data yet. [Warmup](/script/language/warmup) has the full rules.

## One study, two behaviours

Sometimes a study really has one behaviour for a chart the clock measures and another for a chart the calendar measures. Write both, and let the interval choose.

```openscript
version 1

study("Interval aware range", overlay = true, precision = 2)

// Two inputs in two units, because there is no honest conversion between a
// month and a minute. The titles say which chart each one applies to.
barLookback    = input(20,  "Lookback on a daily or longer chart, in bars", min = 2, max = 500)
minuteLookback = input(240, "Lookback on an intraday chart, in minutes", min = 5, max = 1440)

// Declared before the if and updated inside it, so the name is visible after
// the block. chart.intervalMinutes is always a number when chart.isIntraday
// is true, and an absent chart.isIntraday skips the block, which keeps the
// bar lookback.
len = barLookback
if chart.isIntraday
    len = max(1, round(minuteLookback / chart.intervalMinutes))

top    = highest(high, len)
bottom = lowest(low, len)

topPlot    = plot(top, "Lookback high", aqua, width = 2, style = "step")
bottomPlot = plot(bottom, "Lookback low", orange, width = 2, style = "step")
fill(topPlot, bottomPlot, fade(aqua, 93))
```

The `"step"` style is deliberate. These two levels hold flat for bars at a time and then jump, when a new extreme is set or an old one leaves the lookback, so a sloping line between two values would draw prices that were never read. Draw any value that is held constant between updates as a step.

## Where the interval is known in /trading

The chart's interval reaches a script differently depending on where in /trading it runs.

| Where the script runs | What it is told |
|---|---|
| On the chart, as a study | The chart's interval as /trading names it, and the chart's timezone |
| In the Backtest panel | Nothing about the interval. The run fetches bars of the chart's instrument at the chart's interval over the panel's dates, but `chart.interval`, `chart.intervalMinutes`, `chart.isIntraday` and `chart.timezone` are absent |
| In the Strategies panel | The interval the deployment names |


/trading names minute and hour intervals the way the language does, such as `5m`, `15m` and `1h`, so on those charts every value on this page is present. Its daily, weekly and monthly charts are named `D`, `W` and `M`, which are not timeframes the language reads (it writes `1D`, `1W` and `1M`). On a daily chart `chart.interval` is therefore `"D"`, and `chart.intervalMinutes` and `chart.isIntraday` are both absent, which is why the examples above treat an absent `chart.isIntraday` as "not intraday".

> **A strategy that branches on `chart.isIntraday` behaves differently in the Backtest panel than on the chart: there the value is absent, and an absent condition takes the false branch of an `if` or a ternary. If a strategy needs a length that depends on the interval, take the length in bars as an input so the backtest and the chart run the same numbers. [Backtesting](/script/strategies/backtesting) covers the panel itself.**

## Mistakes worth naming

- **Dividing by `chart.intervalMinutes` with no guard.** The study works on every intraday chart. On a daily one it quietly becomes a one bar average or draws nothing at all, and the reason is several lines away from the symptom.
- **Assuming `time - time[1]` is the interval.** It is, inside a session. At the day's first bar it is the overnight gap, on Monday it is the weekend, and after a holiday it is several days. Read `chart.intervalMinutes` when you need the interval.
- **Remembering a bar by its index.** A bar index is a position in the history the engine was given, and every index shifts when older bars are loaded. Store the bar's `time` instead, which never moves.
- **Passing a fractional length.** `sma(close, 20 * 1.5)` is 30 and fine. `sma(close, len / 2)` is not, for any odd `len`. Round where the length is computed, so there is one place to read.
- **Using an interval input as a length.** An interval input returns a timeframe string such as `"1h"`, not a number of bars. It belongs in a read, not in a length argument.

**Related:** [Higher timeframes](/script/data/higher-timeframes), [Sessions and time](/script/data/sessions-and-time), [Repainting](/script/data/repainting), [Other instruments](/script/data/other-instruments), [Warmup](/script/language/warmup), [chart.* reference](/script/reference/chart)


## Higher timeframes

Source: https://openalgo.in/script/data/higher-timeframes

A five minute chart of an NSE stock knows nothing about yesterday's high as a single number, or about the trend on the daily chart. `req.timeframe()` gives a script those answers: it computes an expression on coarser bars and hands the result back on every bar of the chart. This page covers how that works in OpenScript (also called OpenAlgo Script), the rules the timeframe must follow, and the one argument that matters most, `mode`, which decides whether the study can ever show you something that was not knowable at the time.

Most of what people want from a higher timeframe is yesterday's numbers, so here is that first:

```openscript
version 1

study("Previous day levels", overlay = true, precision = 2)

// Each read is one daily series. The default mode, "confirmed", reads only
// days that have closed, so on every bar of today these are yesterday's.
prevHigh  = req.timeframe("1D", high)
prevLow   = req.timeframe("1D", low)
prevClose = req.timeframe("1D", close)

// Arithmetic on reads you already have costs nothing. A fourth read would.
prevMid = (prevHigh + prevLow) / 2

prevHighPlot = plot(prevHigh,  "Previous high",  aqua,   width = 2, style = "step")
prevLowPlot  = plot(prevLow,   "Previous low",   orange, width = 2, style = "step")
plot(prevClose, "Previous close", silver, style = "step")
plot(prevMid,   "Previous mid",   fade(silver, 50), style = "step")
fill(prevHighPlot, prevLowPlot, fade(aqua, 94))
```

Put it on an intraday chart and the four levels step once a day, at the first bar of each session. No `[1]` is needed inside the read: a confirmed daily read already is the last day that closed.


## What folding means

A study on a five minute chart runs once per five minute bar. A daily value has no five minute counterpart, so asking for one is two questions:

1. Compute this expression on daily bars.
2. Give me the answer that was available on each five minute bar.

`req.timeframe` answers both. It groups the chart's bars into coarse bars, computes the expression over those coarse bars, and samples the result back onto the chart, one value per chart bar. The grouping is called the **fold**, and it is why a coarse intraday interval has to be a whole multiple of the chart's: the engine counts chart bars into each coarse bar, and a coarse bar that ended halfway through a chart bar could not be counted.

| Argument | Type | Means |
|---|---|---|
| `timeframe` | `string` | The coarse interval, written as on [Timeframes](/script/data/timeframes#how-a-timeframe-is-written) |
| `expr` | any | The expression to compute on the coarse bars |
| `mode` | `string` | `"confirmed"` (the default), `"developing"` or `"lookahead"`. See [the mode](#the-mode) |

The call returns a series of whatever `expr` produces: a number for a price or an average, a bool for a comparison, a string for a formatted label.

## Rules for the timeframe

| Rule | What happens when it is broken |
|---|---|
| The timeframe must be a form the language knows | OS6001, from the compiler for a literal, or when the study loads for a value from an input |
| It must not be finer than the chart's interval | OS6002 when the study loads, naming both intervals |
| An intraday timeframe must be a whole multiple of the chart's | OS6015 when the study loads, suggesting an interval that works |
| Day, week and month timeframes are folded by the calendar | They are exempt from the multiple rule |
| The timeframe is fixed before the first bar | OS3003 from the compiler when it depends on bar data |

OS6002 is worth understanding rather than memorising. Folding a finer interval into a coarser bar needs data from inside that bar, and the chart was never given it. An engine that filled the gap would be inventing prices, so the request is refused. If you need five minute detail, put the chart on five minutes and fold upward.

The timeframe is fixed before the first bar because the engine sets up each read once and keeps it in step with the chart. So a timeframe comes from a literal or an `input()`, never from the bar's data:

```openscript
// Refused: the timeframe depends on this bar's prices.
tf = close > open ? "1h" : "1D"
h = req.timeframe(tf, high)
plot(h, "Coarse high")
```

```openscript
// Correct: the reader picks the timeframe once, before the first bar.
tf = input("1h", "Higher timeframe", kind = "interval")
h = req.timeframe(tf, high)
plot(h, "Coarse high")
```

## What the expression means inside a read

`expr` is compiled as a separate program over the coarse bars. Inside it, `open`, `high`, `low`, `close`, `volume` and `time` belong to the coarse bars, and every library call reads coarse bars.

```openscript
dayHigh   = req.timeframe("1D", high)              // yesterday's high
dayTrend  = req.timeframe("1D", ema(close, 20))    // a 20 day EMA of daily closes
dayStrong = req.timeframe("1D", close > open)      // a bool, folded
plot(dayHigh, "Yesterday's high", style = "step")
plot(dayTrend, "Daily EMA 20")
background(dayStrong ? fade(lime, 95) : none)
```

`req.timeframe("1D", ema(close, 20))` is a twenty day average of daily closes, sampled onto every bar of the chart. It is not `ema(close, 20)` computed on the chart's own bars, and it is not close to it. That difference is the whole reason to make the read.

Three restrictions apply inside `expr`:

- **A name from the rest of the file may be read only when it is fixed before the first bar**: a literal, arithmetic over literals, or an `input()`. A value computed on this chart's bars has no counterpart on the coarse bars, so reading one is OS6003. A `var` that starts from an input counts as a per-bar name, because a later line may change it.
- **An `input()` may be written inside `expr` itself.** It is the same setting and the same row of the settings dialog as it would be behind a name.
- **Orders, markers, drawings and alerts do not belong inside `expr`.** An order function there is OS7003 and a drawing or alert call is OS3006. The expression is a calculation over other bars, not a second script with effects of its own.

```openscript
len = input(20, "Length", min = 2, max = 500)
trendUp = req.timeframe("1D", ema(close, len) > ema(close, len * 2))
sameIdea = req.timeframe("1D", ema(close, input(50, "Coarse length", min = 2)))
plot(sameIdea, "Daily EMA")
bgUp = trendUp ? fade(lime, 94) : none
background(bgUp)
```

```openscript
atrNow = atr(14)
// Refused: atrNow is computed on this chart's bars.
wide = req.timeframe("1D", high - low > atrNow)
plot(wide ? 1 : 0, "Wide day")
```

### History inside the read and outside it

This is the most common mistake with a coarse read, and the rule is one sentence: **`[]` inside `expr` counts coarse bars, and `[]` on the result counts chart bars.**

| Expression | Counts | On a 5 minute chart at 11:20 it holds |
|---|---|---|
| `req.timeframe("1D", high)` | Days | Yesterday's high, the last day that closed |
| `req.timeframe("1D", high[1])` | Days | The high of the day before yesterday |
| `req.timeframe("1D", high, mode = "developing")` | Days | Today's high so far |
| `req.timeframe("1D", high)[1]` | Chart bars | What the read held at 11:15, which is yesterday's high again |

Read the first row twice before writing `[1]` inside a read. A confirmed read is already one day back, so `high[1]` inside it is two days back. A script that wrote it meaning "yesterday" would draw a level from the wrong session and never look wrong.

## The mode

A coarse bar takes many chart bars to form. On any chart bar inside it, there are exactly three things a read can hand back, and `mode` names which one.

| Mode | Reads | On a forming coarse bar it returns | Can repaint | First value |
|---|---|---|---|---|
| `"confirmed"` | Only coarse bars that have closed | The last closed coarse bar's value, held until the next one closes | Never | Once the first coarse bar has closed |
| `"developing"` | The coarse bar that is forming | The value so far of the coarse bar this chart bar is inside | On the newest bars, while the coarse bar forms | Once the first coarse bar has begun |
| `"lookahead"` | The coarse bar's final value | The final value of the coarse bar this chart bar is inside, even on its first chart bar | On history, permanently | Wherever the coarse bar exists |

`"confirmed"` is the default and the only mode that never repaints. The other two must be written out, so a script that can repaint says so on the line that causes it, in a word a reviewer sees, and a script that says nothing cannot repaint this way.

The compiler warns about a `"lookahead"` read with OS8005. A `"developing"` read carries no warning: the mode word on the line is its disclosure, which is why it has to be written. The compiled study records the mode of every read, so a host can mark a lookahead study as repainting; the /trading legend does not show such a mark today, so the warning and the word in the source are the disclosure. [Repainting](/script/data/repainting) covers the whole subject.

### The same hour, read three ways

Take a five minute NSE chart and an hourly read. On an IST chart an hourly read runs from 10:30 to 11:30 ([why](#where-the-coarse-bars-begin)). Say that hour opens at 100.0, works up to 104.0 and closes there, and the hour before it closed at 101.0.

| Chart bar | `"confirmed"` | `"developing"` | `"lookahead"` |
|---|---|---|---|
| 10:30 | 101.0 | 100.2 | 104.0 |
| 10:45 | 101.0 | 100.9 | 104.0 |
| 11:00 | 101.0 | 102.4 | 104.0 |
| 11:25 | 101.0 | 103.8 | 104.0 |
| 11:30 | 104.0 | the next hour so far | the next hour's close |

Read the columns, not the rows.

- **Confirmed** is flat for the whole hour and steps once, at 11:30, to the value the 10:30 hour finished at. Everything it shows at 11:00 was knowable at 11:00. It is one hour behind by construction, and that lag is the price of never being wrong about the past.
- **Developing** moves with the market. At 11:00 it says what the hour has done so far, which is true and useful. It is also a statement about an hour that has not finished: the 11:00 value is not the hour's result, it changes with every price update while the newest bar forms, and a signal built on it at 11:00 can be contradicted by the time the hour closes.
- **Lookahead** shows 104.0 at 10:30, which nobody could have known at 10:30. On a historical chart this column looks brilliant: every breakout is anticipated. It is the shape of a study that looks perfect on history and loses money when traded.

### The three modes in one script

Put them on a chart together once, and the difference stops being abstract. The compiler warns about the lookahead read, which is the point of this study.

```openscript
version 1

study("Three readings of one hour", overlay = true, precision = 2)

tf = input("1h", "Coarse interval", kind = "interval")

// Three reads of the same expression, for the comparison. A working study
// makes one read and reuses the name.
confirmed  = req.timeframe(tf, close, mode = "confirmed")
developing = req.timeframe(tf, close, mode = "developing")
lookahead  = req.timeframe(tf, close, mode = "lookahead")

confirmedPlot = plot(confirmed, "Confirmed", aqua, width = 2, style = "step")
plot(developing, "Developing", orange, width = 2, style = "step")
lookaheadPlot = plot(lookahead, "Lookahead", red, width = 2, style = "step")

// The shaded gap is how much this study would be cheating if it traded from
// the red line.
fill(lookaheadPlot, confirmedPlot, fade(red, 90))
```

### When each mode is the right answer

| You want | Mode | Because |
|---|---|---|
| A trend filter a strategy trades from | `"confirmed"` | Only a closed bar is a fact, and the backtest must match what trading would have done |
| The day's range so far, on a dashboard | `"developing"` | A person is reading it, not trading it, and "so far" is the question |
| A finished coarse candle drawn across history, as a picture | `"lookahead"` | The picture is the point, and nothing trades from it |
| Yesterday's high as a level | `"confirmed"`, the default | It is the last day that closed, and nothing about a closed day can change |

The mode is written as a literal. A mode taken from an input is refused by the compiler with OS3003, because a setting would let a reader change the honesty of a study without reading a line of it.

## Where the coarse bars begin

**Day, week and month reads follow the calendar in the chart's timezone.** A `"1D"` read groups each trading day, which on NSE, BSE and MCX is one session, because none of their sessions runs past midnight IST. A `"1W"` week starts on Monday. A `"1M"` month is the calendar month.

**Intraday reads count fixed periods from midnight UTC**, which is 05:30 IST. That has a visible effect on an Indian chart:

| Read | Coarse bars on an NSE chart |
|---|---|
| `"15m"` | 09:15 to 09:30, 09:30 to 09:45, and so on, lined up with the open |
| `"30m"` | 09:30 to 10:00, 10:00 to 10:30, and so on, with 09:15 to 09:30 as a short first bar |
| `"1h"` | 09:30 to 10:30, 10:30 to 11:30, and so on, with 09:15 to 09:30 as a short first hour |

So a `"30m"` or `"1h"` read does not start its first coarse bar at the 09:15 open, and the first one of each day covers only fifteen minutes. A study that needs coarse bars lined up with the open, such as the first half hour's range, is better written with a [time window](/script/data/sessions-and-time#windows-inside-the-day) on the chart's own bars.

A period that divides 24 hours evenly, such as 15, 30, 60 or 120 minutes, falls at the same clock times every day. One that does not, such as 75 minutes, falls at different times from one day to the next, so prefer periods that divide the day.

## Warmup and the left edge

A confirmed read is absent until the first coarse bar has **closed**. On a five minute chart with a `"1D"` read, that is the whole of the first day on the chart: the first value appears at the open of the second. If the coarse expression has a warmup of its own, add it. `req.timeframe("1D", ema(close, 20))` needs twenty closed days, so on an intraday chart the line starts about a month in. On the /trading chart, scrolling back past the loaded range loads older bars, which is how you give a long daily warmup enough history.

Absence propagates, so write a comparison against a read so that warmup is handled on purpose rather than by accident:

```openscript
bias = req.timeframe("1D", ema(close, 20))

// close > bias is absent during warmup, not false. Test for it, so that "up"
// and "down" are both false while the read has no value yet.
up   = not isNone(bias) and close > bias
down = not isNone(bias) and close < bias
barColor(up ? lime : down ? red : none)
```

## A working example: a daily bias on an intraday chart

```openscript
version 1

study("Higher timeframe bias", overlay = true, precision = 2)

biasTf  = input("1D", "Bias interval", kind = "interval")
biasLen = input(20,   "Bias average length", min = 2, max = 500)
paint   = input(false, "Recolour the candles")

// The mode is a literal on purpose: an input would let a reader change the
// honesty of the study from the settings dialog.
biasClose   = req.timeframe(biasTf, close, mode = "confirmed")
biasAverage = req.timeframe(biasTf, ema(close, biasLen), mode = "confirmed")

up   = not isNone(biasAverage) and biasClose > biasAverage
down = not isNone(biasAverage) and biasClose < biasAverage

plot(biasAverage, "Bias average", orange, width = 2, style = "step")

barColor(paint ? (up ? lime : down ? red : none) : none)
background(up ? fade(lime, 95) : down ? fade(red, 95) : none)

// orElse on the previous bar, because on bar 0 there is no previous bar, and
// an absent condition would leave the very first flip unmarked.
if up and not orElse(up[1], false)
    signal("BIAS UP")

if down and not orElse(down[1], false)
    signal("BIAS DOWN")
```

The file does not set `onUnconfirmed`, so each marker waits for its chart bar to close. The next section says why that matters here.

## Reads and the moving bar

By default, a `signal()`, an `alert()` or an order does not fire on a chart bar that is still moving. It waits until the bar closes, and if the condition is no longer true by then it never happens. A study or strategy opts out with `onUnconfirmed = true`, and when it does, the compiler warns about every read in the file, of another interval or another instrument, with OS8002, whatever the read's mode:

```openscript
version 1

study("Unguarded bias", overlay = true, onUnconfirmed = true)

dayHigh = req.timeframe("1D", high)
plot(dayHigh, "Previous high", aqua, style = "step")

if crossUp(close, dayHigh)
    signal("ABOVE YESTERDAY")
```

The reason is stacked uncertainty. The chart bar is still moving, so its own `close` will change, and acting on it and a coarse read together means acting on values that can each still be withdrawn. Drop `onUnconfirmed = true`, or guard every use of the read with `bar.isConfirmed`.

## The cost of a read

Each read is one folded series the engine builds from the chart's bars and keeps in step with them.

- **A host may set a ceiling on reads per file.** Going over it is OS5006, which names how many the file makes and how many are allowed. The /trading chart sets no ceiling today, but the habit that keeps you under one is worth having anyway: make one read per timeframe and expression, assign it to a name, and reuse the name.
- **Arithmetic on reads you already have is cheap.** A study that makes three reads and does arithmetic on them is cheap. A study that makes thirty is one to rewrite.

Drawing a coarse bar as a candle currently takes four reads, one per price. A single call that returns the whole coarse bar, `req.candle()`, is planned for exactly this reason.

## Higher timeframes in /trading

| Where the script runs | Intraday reads such as `"15m"` or `"1h"` | Day, week and month reads |
|---|---|---|
| On the chart, as a study | Folded from the chart's own bars | Folded in the chart's timezone, Asia/Kolkata unless you changed it in the chart settings |
| In the Backtest panel | Folded from the chart's bars | Absent on every bar, because the backtest run is not told the chart's timezone |

> **A strategy that filters its trades with a daily read, as in the bias example above, takes no trades in the Backtest panel today: the read is absent, so the filter is never true. `req.error()` on the read says why: the host did not supply a timezone. Test such a strategy by drawing it on the chart, or use an intraday read such as `"1h"` for the filter.**

The rules for the timeframe need the chart's interval. /trading names its daily, weekly and monthly charts `D`, `W` and `M`, which the language cannot read, so on those charts OS6002 and OS6015 are never raised. A `"1h"` read on a /trading daily chart is not refused: every day becomes its own coarse bar, and the read quietly hands back the previous day's value. On a daily chart, read only `"1D"` or coarser.

**Related:** [Timeframes](/script/data/timeframes), [Repainting](/script/data/repainting), [Other instruments](/script/data/other-instruments), [Sessions and time](/script/data/sessions-and-time), [req.* reference](/script/reference/request), [Warmup](/script/language/warmup)


## Other instruments

Source: https://openalgo.in/script/data/other-instruments

A study often needs a second instrument: a stock measured against the NIFTY index, a future against its index, or two option legs added into one premium. `req.symbol()` computes an expression on another instrument's bars and places the answer on the bars of your chart. This page covers the call in OpenScript (also called OpenAlgo Script), which of the other instrument's bars each mode hands you, how to tell whether the answer has arrived, what a missing bar means, and what each read costs.

Here is the idea in one study: a stock's close divided by the NIFTY index, which rises when the stock outperforms the index.

```openscript
version 1

study("Relative strength against NIFTY", precision = 4)

benchName     = input("NIFTY",     "Benchmark symbol")
benchExchange = input("NSE_INDEX", "Benchmark exchange")
smoothLen     = input(20, "Smoothing, in bars", min = 1, max = 200)

// The benchmark's close at the chart's own interval. "developing" hands back
// the benchmark's bar at the same time as this chart bar; the default,
// "confirmed", would hand back the one before it (see "Which bar a read gives
// you" below). Absent until the host has answered, and on any bar the
// benchmark has no bar for.
bench = req.symbol(benchName, chart.interval, close, exchange = benchExchange, mode = "developing")

// Absence carries through the division, so a missing benchmark bar is a gap in
// the line rather than a ratio against zero.
ratio = close / bench

plot(ratio, "Stock over benchmark", aqua, width = 2)
plot(sma(ratio, smoothLen), "Smoothed", orange)
```

## The call

```openscript
peer = req.symbol("NIFTY", "1D", close, exchange = "NSE_INDEX")
plot(peer, "NIFTY, last closed day", style = "step")
```

| Argument | Type | Means |
|---|---|---|
| `symbol` | `string` | The instrument to read, in OpenAlgo's symbol format |
| `timeframe` | `string` | The interval to read it at, written as on [Timeframes](/script/data/timeframes#how-a-timeframe-is-written) |
| `expr` | any | The expression to compute on that instrument's bars |
| `exchange` | `string` | Where it trades. Defaults to the chart's own exchange |
| `mode` | `string` | `"confirmed"` (the default), `"developing"` or `"lookahead"`, as on [Higher timeframes](/script/data/higher-timeframes#the-mode). See [which bar a read gives you](#which-bar-a-read-gives-you) |

Symbols are written the way OpenAlgo writes them everywhere:

| Instrument | Symbol | Exchange |
|---|---|---|
| An NSE or BSE stock | The base symbol, such as `SBIN` or `RELIANCE` | `NSE` or `BSE` |
| An index | `NIFTY`, `BANKNIFTY`, `SENSEX` | `NSE_INDEX` or `BSE_INDEX` |
| A future | Symbol, expiry as `DDMMMYY`, then `FUT`, such as `NIFTY30JAN25FUT` | `NFO`, `BFO` or `MCX` |
| An option | Symbol, expiry, strike, then `CE` or `PE`, such as `NIFTY30JAN2521500CE` | `NFO` or `BFO` |

The chart's symbol search in /trading lists instruments in this format, with the exchange beside each one, so it is the quickest way to find the exact text to type into a symbol input.


Inside `expr`, the built-in series are the **requested** instrument's, at the requested interval. In `req.symbol("NIFTY", "1D", ema(close, 20) > ema(close, 50), exchange = "NSE_INDEX")` both averages are computed from the index's daily closes. The same restrictions apply as in a higher timeframe read: a name from the rest of the file may be read inside `expr` only when it is fixed before the first bar (a literal or an `input()`, otherwise OS6003), and orders, drawings and alerts do not belong there.

## Which bar a read gives you

The mode decides which of the other instrument's bars reaches each bar of your chart. A confirmed read only hands back a bar that has closed, and the engine counts the other instrument's newest bar as still forming until its next bar begins. At the chart's own interval, that puts a confirmed read **one bar behind**.

| Mode | At the chart's own interval, a chart bar gets | On a chart bar where the other instrument has no bar |
|---|---|---|
| `"confirmed"`, the default | The other instrument's previous bar, even when both instruments traded in this one | An earlier value, held |
| `"developing"` | The other instrument's bar at the same time | absent |

On a five minute chart, at the 10:00 bar, a confirmed read of `close` is the other instrument's 09:55 close, and a developing read is its 10:00 close. Where the other instrument has no 10:00 bar, the confirmed read still holds its 09:50 close at 10:00, because its 09:55 bar only counts as closed once the next bar begins, and the developing read is absent.

**Compare two instruments at the same instant with `mode = "developing"`.** A ratio, a spread or a sum of option legs is only meaningful when both sides are the same bar, and a confirmed read at the chart's own interval never is. The price is the one the mode's name admits. On history every value is final, but on the live chart the other instrument's bars arrive by fetch, which the /trading chart repeats as each new chart bar begins, so the newest values, the bar that has just closed included, can still change when that fetch lands. That is fine for a line you read. Think twice before an alert or an order acts on it, as [Repainting](/script/data/repainting) explains.

**Keep the default for a coarser read.** `req.symbol("NIFTY", "1D", high, exchange = "NSE_INDEX")` is the high of the last day that closed, which is what "yesterday's NIFTY high" means, and it never moves.

## The symbol is fixed before the first bar

The host fetches each requested series once, keyed by its symbol, exchange and timeframe, and keeps it in step with the chart. So the symbol comes from a literal or an input, never from bar data:

```openscript
// Refused: the symbol depends on this bar's prices.
name = close > open ? "SBIN" : "INFY"
other = req.symbol(name, chart.interval, close)
plot(other, "Other")
```

A dedicated instrument picker, `input(..., kind = "symbol")`, is planned. It is not in this version, and the compiler says so:

```openscript
leg = input("", "Second leg", kind = "symbol")
plot(close, "Close")
```

Until it lands, take the symbol as a plain text input, as the example at the top does, and the settings dialog shows a text box to type it in.

## Waiting for the answer

A read of another instrument cannot finish until the host supplies that instrument's bars, and that takes a moment. While it is outstanding:

- the read is **absent**, on every bar,
- everything in the study that does not depend on the read keeps drawing,
- and when the bars arrive, the study is calculated again over its whole history with the read present.

A chart that showed a broken line for a second and then a complete one did not repaint. It had not been answered yet, and the gap was the honest statement of that.

Two functions report on a read. Assign the read to a name and pass the name:

| Call | Returns | Means |
|---|---|---|
| `req.isReady()` | `series bool` | The host has answered |
| `req.error()` | `series string` | Why the read failed, as a sentence, or `""` |

A read that fails stays absent, and the reason is available to the script through `req.error`. These are the codes a host can report:

| Code | When | The message carries |
|---|---|---|
| OS6007 | The host does not know the symbol on that exchange | The symbol and the exchange |
| OS6008 | The instrument exists and had no bars over the chart's range | The symbol and the interval |
| OS6009 | The data source refused or did not answer | The source's own reason |
| OS6014 | The data source does not store that interval for the instrument | The intervals it does serve |
| OS5006 | The file makes more reads than the host allows | How many it makes and how many are allowed |

On the /trading chart, a fetch the data source refuses reports OS6009 with the source's own words, which is how a misspelt symbol usually shows up, and a fetch that returns no bars reports OS6008.

OS6007 exists, rather than an empty series, because an empty series looks exactly like an instrument that did not trade, and those two situations call for opposite responses from the person reading the chart.

### A study that says what state it is in

```openscript
version 1

study("Peer comparison", precision = 2)

peerName     = input("NIFTY",     "Instrument to compare")
peerExchange = input("NSE_INDEX", "Its exchange")

peer  = req.symbol(peerName, chart.interval, close, exchange = peerExchange, mode = "developing")
ready = req.isReady(peer)
why   = req.error(peer)

// Computed on every bar. An unanswered read gives an absent ratio and a gap.
ratio = close / peer
plot(ratio, "This instrument over the peer", aqua, width = 2)

// A study whose data has not arrived says so, rather than leaving an empty pane.
panel = table("Status", 2, 2, position = "topRight", textColor = silver)
if bar.isLast
    cell(panel, 0, 0, "Peer")
    cell(panel, 0, 1, peerName, textColor = white)

    cell(panel, 1, 0, "State")
    cell(panel, 1, 1, why != "" ? why : (ready ? "ready" : "loading"),
         textColor = why != "" ? red : (ready ? lime : silver))
```

## Alignment by timestamp

The expression is computed on the other instrument's own bars, at the requested interval, and the result is then placed on the chart's bars. **The chart's bars are always the time axis.**

- **Alignment is by instant.** `time` is UTC milliseconds, so two instruments align correctly with no timezone arithmetic anywhere.
- **What the other instrument did between two chart bars is not visible** except through what the expression computed. If the detail matters, read a finer interval or put the chart on one.
- **A time when only the other market is open has no chart bar to land on.** An MCX contract read on an NSE chart shows nothing of its evening trade.

How a developing read at the chart's own interval behaves bar by bar:

| Situation on a chart bar | What the read gives |
|---|---|
| Both instruments traded in that bar | The other instrument's value for that bar |
| The other instrument did not trade in that bar | absent |
| The other instrument's market is closed while the chart's is open | absent |
| The read has not been answered yet | absent, with `req.isReady` false |

A confirmed read never goes absent once it has a first value. Where the other instrument has no bar, it holds an earlier one, and after the other market closes for the day it keeps holding until that market's next bar begins.

## What a missing bar means

**In a developing read, an absent bar is absent.** It is not zero, it is not the previous value carried forward, and it is not an error. The language promises one thing about it: any arithmetic touching an absent value is absent, all the way to the plot, where it draws a gap.

That promise is what makes a combined option premium trustworthy. If one leg of a straddle has no bar at this instant, the sum of the two legs is absent. It is not the other leg's price. A sum that quietly dropped the missing leg would show the premium halving, which reads on the chart as a profitable decay and is in fact a data gap. A confirmed read would not drop the leg, but it would add this bar's price of one leg to an older price of the other, which is wrong in a way no gap shows.

Why a bar can be missing:

| Cause | Typical shape |
|---|---|
| The instrument did not trade in that bar | A far strike or a thin contract, with gaps through the day |
| The two markets keep different hours | An MCX contract read on an NSE chart, or the other way round, absent at one end of the day |
| A holiday on one exchange and not the other | A whole day absent |
| The contract had not been listed yet | Absent at the left edge, then present; OS6008 when the whole range is empty |
| The contract has expired | Present at the left edge, then absent |

### Filling a gap, deliberately

Sometimes holding the last known value is what you want: a slow instrument read against a fast one, where a one bar gap is noise. A developing read will not do it for you, because holding a stale value is a decision with a cost. Make the decision in the open, and show where it was made:

```openscript
version 1

study("Peer, held through gaps", precision = 2)

peerName     = input("NIFTY",     "Instrument to compare")
peerExchange = input("NSE_INDEX", "Its exchange")
maxStale     = input(5, "Hold a stale value for at most this many bars", min = 1, max = 100)

peer = req.symbol(peerName, chart.interval, close, exchange = peerExchange, mode = "developing")

present = not isNone(peer)

// valueWhen holds what the read said the last time it said anything, and
// barsSince says how many bars ago that was.
lastKnown = valueWhen(present, peer)
staleness = barsSince(present)

// The held value is used only while it is fresh enough.
usable = not isNone(staleness) and staleness <= maxStale
held = usable ? lastKnown : none

plot(peer, "Peer, as read", aqua, width = 2)
plot(held, "Peer, held", fade(aqua, 60), style = "step")

// Held bars are shaded, so nobody mistakes memory for data.
background(present ? none : (usable ? fade(orange, 90) : fade(red, 92)))
```

Three things in that script are worth copying: the raw read is plotted as well, so the gaps stay visible; the hold has a limit, so an expired contract cannot draw a flat line forever; and the held bars are marked. `valueWhen()` and `barsSince()` have the details.

## The cost of a read

| Cost | Size | What to do about it |
|---|---|---|
| One fetched series per symbol, exchange and timeframe | A host may set a ceiling, OS5006 when exceeded; the /trading chart sets none today | Make one read per series, assign it to a name, reuse the name |
| Waiting for the answer | One fetch when the study loads | Draw what does not depend on it, and show the status |
| A recalculation when the answer arrives | The whole history, once | Nothing: it is the correct behaviour |
| Reading the answered series on each bar | A lookup | Nothing |

The rule that falls out of the table: **a read is expensive and arithmetic is not.** Derive everything you can from the reads you already have.

```openscript
// Two reads, and the midpoint worked out from them rather than read a third time.
dayHigh = req.symbol("NIFTY", "1D", high, exchange = "NSE_INDEX")
dayLow  = req.symbol("NIFTY", "1D", low,  exchange = "NSE_INDEX")
dayMid  = (dayHigh + dayLow) / 2
plot(dayMid, "NIFTY previous day mid", style = "step")
```

## Chart facts describe the chart's instrument

`chart.tickSize`, `chart.lotSize`, `chart.exchange` and the rest of the `chart` namespace describe the instrument **the chart is showing**, never the one a read names. There is no per-read equivalent, so a study that reads another instrument and needs its lot size takes it as an input and says so in the title.

On the /trading chart, a study is not told the chart instrument's exchange or lot size either, so `chart.exchange` and `chart.lotSize` are absent there. Take a lot size as an input whenever a study works in money.

## Index against its future

The gap between a future and its index, the **basis**, is positive when the future trades above the index. Put this study on the chart of the index, such as NIFTY, and type the current month's contract:

```openscript
version 1

study("Futures basis", precision = 2)

futName     = input("", "Future, such as NIFTY30JAN25FUT")
futExchange = input("NFO", "Exchange of the future")

// The future's bar at the same time as the index bar, so the difference is
// taken between two prices of one instant.
fut   = req.symbol(futName, chart.interval, close, exchange = futExchange, mode = "developing")
basis = fut - close

level(0, "Zero", gray)
plot(basis, "Basis", aqua, width = 2)
plot(sma(basis, 20), "Basis, 20 bar average", orange)
```

The basis is empty until the future is typed in, and absent on any bar the future did not trade.

## A worked example: two legs, one number

This study adds two option legs into one combined premium, holds the day's first premium as a reference, and marks when the premium has decayed by a chosen percentage. Put it on the chart of the underlying, such as the NIFTY index, and type the two legs in OpenAlgo's format.

```openscript
version 1

study("Combined premium", precision = 2, format = "price")

callLeg     = input("", "Call leg, such as NIFTY30JAN2521500CE")
putLeg      = input("", "Put leg, such as NIFTY30JAN2521500PE")
legExchange = input("NFO", "Exchange of the legs")
lots        = input(1,  "Lots", min = 1, max = 100)
lotSize     = input(1,  "Units in one lot", min = 1)
targetPct   = input(30, "Decay to mark, in percent of the opening premium", min = 1, max = 99)

// Both legs at the chart's own interval and in the developing mode, so each
// chart bar gets both legs' bars at that same time and the sum below is a sum
// of one instant.
callPrice = req.symbol(callLeg, chart.interval, close, exchange = legExchange, mode = "developing")
putPrice  = req.symbol(putLeg,  chart.interval, close, exchange = legExchange, mode = "developing")

// If either leg has no bar at this instant, the sum is absent, not half a position.
premium = callPrice + putPrice
money   = premium * lots * lotSize

// A new trading day in IST. NSE sessions never cross midnight, so a new date
// is a new session.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

var opening = none
if newDay
    opening = none
if isNone(opening) and not isNone(premium)
    opening = premium

// Positive when the seller of both legs is ahead.
decay = isNone(opening) ? none : (opening - premium) / opening * 100

plot(premium, "Combined premium", orange, width = 2)
plot(opening, "Opening premium", fade(silver, 40), style = "step")
plot(money,   "Position value", aqua, scale = "left")

if decay >= targetPct
    alert("Combined premium decayed " + text(decay, 1) + " percent", id = "decay-target")
    signal("TARGET")
```

`decay >= targetPct` is absent on any bar where either leg is missing, and an absent condition takes the false branch, so neither the alert nor the marker fires on a gap. The premium stays empty until both legs are filled in. The lot size is an input because it is the legs' lot size, not the chart's, and because exchanges revise lot sizes: enter the current one for your contract.

The alert and the marker act on developing reads, so on the live chart they see the legs' prices as last fetched when the bar closes. Treat the alert as a prompt to look at the premium, not as an instruction to trade.

On the /trading chart in this release, the marker draws as described, but the script's own `alert()` may not fire while the market is open: the chart judges a script's alerts once, when a bar first arrives, before it has closed. To be told reliably, plot `decay >= targetPct ? 1 : 0` as well and put a study alert on that plot, as [Alerts on a script condition](/script/alerts/alerts-in-trading#alerts-on-a-script-condition) shows.

## Other instruments in /trading

| Where the script runs | What happens to `req.symbol` |
|---|---|
| On the chart, as a study | The read is fetched through the same data feed the chart uses. A read that names no exchange is asked on the chart's own exchange |
| In the Backtest panel | The run is refused before its first bar with OS6006: the backtest holds only the chart's own bars and cannot fetch another instrument |

On a /trading chart of minute or hour bars, reading at `chart.interval` works as the examples show. /trading names its daily interval `D`, which is not a timeframe the language reads, so a read at `chart.interval` on a daily chart stops with OS6001 when the study loads. On a daily chart write `"1D"` as the read's timeframe instead.

## Mistakes worth naming

- **Comparing with a confirmed read at the chart's own interval.** It is one bar behind, so a ratio, a spread or a sum of legs mixes two different bars. Write `mode = "developing"`.
- **Treating an absent read as zero.** `orElse(peer, 0)` compiles and is almost never right: a price of zero is a price, and it wins every `min` and loses every `max` it enters.
- **Deriving a symbol from bar data.** Refused with OS3003. Symbols come from literals and inputs.
- **Two reads of the same series.** One of them is waste, and both count against any ceiling the host sets.
- **Assuming the other instrument keeps the chart's hours.** An MCX contract trades into the evening and an NSE stock does not, and the difference shows up as absent bars at one end of the day.
- **Using `chart.lotSize` for a leg.** It describes the chart's instrument, whatever the read names.

**Related:** [Higher timeframes](/script/data/higher-timeframes), [Repainting](/script/data/repainting), [Sessions and time](/script/data/sessions-and-time), [Absent values](/script/language/absent-values), [req.* reference](/script/reference/request)


## Sessions and time

Source: https://openalgo.in/script/data/sessions-and-time

Trading happens in sessions, and a session is not the same thing as a calendar day. This page covers how OpenScript (also called OpenAlgo Script) represents time, how to find the first bar of a trading day, how to test whether a bar falls inside a window such as 09:15 to 09:30 IST, how to cope with holidays, and which of these tools work in each part of the /trading page today.

## One instant, two calendars

Every bar carries `time`: the instant it opened, in **milliseconds since 1 January 1970, UTC**. That number is the same everywhere, it never shifts, and it is the one thing in the language that is safe to store and compare later.

A calendar turns that instant into a year, a month, a day and an hour. By default the language uses the chart's own:

> **Every function in the `date` and `session` namespaces reads a timestamp in the chart's timezone, `chart.timezone`, unless a `zone` argument names another.**

The default is the chart's axis rather than UTC because a session study that disagreed with the labels on the chart would be wrong in the way that is hardest to see: every number consistent, every one of them hours away from what you are looking at. On the /trading chart the timezone is the **Timezone** setting in the chart's settings, which is Asia/Kolkata unless you change it.

A zone is written as an area and a location, such as `"Asia/Kolkata"` or `"America/New_York"`, or as `"UTC"`. It is never a fixed offset and never an abbreviation, because an abbreviation can mean different offsets in different places. `"IST"` is not a zone name: a study that passes it stops with OS6005 when it runs.

```openscript
hourOnChart = date.hour(time)                    // in the chart's timezone
hourInIndia = date.hour(time, "Asia/Kolkata")    // in a named zone
plot(hourInIndia, "Hour, IST")
plot(hourOnChart, "Hour, chart zone")
```

## The session namespace

A **session** is the instrument's trading session as the host defines it. It is not a window the script invents, and that matters: the host knows about a special session or an early close, and a script does not.

| Name | Returns | Means | Available |
|---|---|---|---|
| `session.isFirstBar` | `series bool` | This is the session's first bar | Yes, where the host supplies session hours |
| `session.isLastBar` | `series bool` | This is the session's last scheduled bar | Yes, where the host supplies session hours |
| `session.isIn()` | `series bool` | This bar falls inside a window you write | Yes |
| `session.isOpen` | `series bool` | This bar falls inside the instrument's session | Planned |
| `session.startTime` | `series number` | When this bar's session opened | Planned |
| `session.endTime` | `series number` | When this bar's session is scheduled to close | Planned |
| `session.barIndex` | `series number` | This bar's position within its session | Planned |
| `session.isHoliday()` | `bool` | Whether a date is a trading holiday | Planned |
| `session.nextOpen` | `series number` | When the next session opens | Planned |

`session.isFirstBar` and `session.isLastBar` are worked out from the instrument's session hours, which the host states. **Where the host states none, both are absent on every bar.** That is the honest answer, since a guessed session would be wrong somewhere, and it is the situation in /trading today: see [the last section](#sessions-and-the-clock-in-trading-today). `timeClose`, the instant a bar ends, and `date.add`, calendar arithmetic, are planned as well.

Using a planned name is refused by the compiler with OS2020:

```openscript
opened = session.startTime
plot(time - opened, "Milliseconds since the open")
```

## A calendar day is not a session

A session is what an exchange opens and closes. A date is what a calendar says. They line up on many instruments and not on others.

| Case | Sessions | Dates |
|---|---|---|
| An NSE or BSE day, 09:15 to 15:30 IST | One | One |
| An MCX day, which runs into the late evening IST | One | One |
| A market whose evening session runs past midnight | One | Two |
| A half day before a holiday | One, shorter | One |
| A holiday or a weekend day | None | One |

Everything in the language that resets "per day" is meant to reset per **session**. `vwap()` restarts when the session opens, so it needs the session hours too. A `"1D"` [higher timeframe read](/script/data/higher-timeframes) groups the bars of one calendar day in the chart's timezone, which on an Indian exchange is one session. Write your own state the same way.

**For Indian exchanges the two line up.** No NSE, BSE or MCX session runs past midnight IST, so a new calendar day in IST is a new session. That gives a test that works on the /trading chart today, when the chart is not told the session hours.

## The first bar of the day

The language's answer is `session.isFirstBar`. On Indian instruments you can also test for a new IST date, which is what this study does, so it works on the /trading chart now:

```openscript
version 1

study("Day open, high and low", overlay = true, precision = 2)

// A new trading day: the first bar on the chart, or a bar on a different IST
// date from the bar before it. Named zone, so a chart set to another timezone
// still splits days at midnight IST.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

var dayOpen = none
var dayHigh = none
var dayLow  = none

if newDay
    dayOpen = open
    dayHigh = high
    dayLow  = low
else
    dayHigh = max(dayHigh, high)
    dayLow  = min(dayLow, low)

plot(dayOpen, "Day open", silver, style = "step")
dayHighPlot = plot(dayHigh, "Day high", aqua,   style = "step")
dayLowPlot  = plot(dayLow,  "Day low",  orange, style = "step")
fill(dayHighPlot, dayLowPlot, fade(aqua, 94))
```

The oldest day on the chart may have started before the first loaded bar, so its open, high and low describe only the part of the day the chart holds. Every later day is complete.

On a host that supplies session hours, replace the `newDay` line with `newDay = session.isFirstBar` and the study works on any market, including one whose session crosses midnight.

### Tests that look right and are not

| Written as | Fails when |
|---|---|
| `date.hour(time) == 9 and date.minute(time) == 15` | The market opens late, a special session runs at other hours, or the chart's interval puts no bar at 09:15 |
| `bar.index % 75 == 0` | A half day, a missing bar, or any day whose bar count is not what the script assumed |
| `not date.isSameDay(time, time[1])` with no zone | The chart's timezone is changed away from IST, or the market has a session that crosses midnight |
| `date.dayOfWeek(time) != date.dayOfWeek(time[1])` with no zone | The same cases |

The `isSameDay` test has one more trap, on the oldest bar. There `time[1]` is absent, so `date.isSameDay(time, time[1])` is absent, the `not` of it is absent too, and an absent condition takes the false branch: the first day on the chart never starts. Start the test with `isNone(time[1]) or`, as the study above does.

## The last bar of the day

`session.isLastBar` is worked out from the session's **schedule**: the scheduled close and the chart's interval. It is true on the bar that reaches the scheduled close, while that bar runs, rather than being noticed when the next session's first bar arrives. That is what a strategy that must be flat by the close needs: waiting to see the next session's first bar means the position has already been held overnight.

The schedule is also its limit. If trading stops before the last scheduled bar, no bar that day has `session.isLastBar` true, so a strategy that must be flat also needs a clock based exit.

Where session hours are not supplied, as in /trading today, square off by the clock instead: a window with `session.isIn()` on the chart, or the IST arithmetic in [the last section](#sessions-and-the-clock-in-trading-today), which works in every part of /trading.

## Windows inside the day

`session.isIn()` tests whether a bar falls inside a window you write as a string `"HHMM-HHMM"`, with an optional list of days.

```openscript
version 1

study("Opening minutes and late entries", overlay = true)

// The first fifteen minutes of trading, Monday to Friday, in IST.
opening = session.isIn("0915-0930:12345", "Asia/Kolkata")

// The last half hour, when an intraday system usually stops opening trades.
lateDay = session.isIn("1500-1530", "Asia/Kolkata")

background(opening ? fade(aqua, 88) : lateDay ? fade(orange, 90) : none)
```

| Part | Means |
|---|---|
| `HHMM-HHMM` | Start and end, on the chart's clock unless a zone is given |
| `:12345` | Days, 1 for Monday through 7 for Sunday |
| End before start | The window crosses midnight, as `"2100-0200"` does |

Days are numbered with Monday as 1, the same as `date.dayOfWeek()`, so the trading week is one range and a weekday test is `date.dayOfWeek(time) <= 5`. A window the engine cannot read, such as `"9:15-15:30"`, is absent on every bar, so the test is never true: check the spelling if a window never matches.

## Calendar fields

The `date` namespace turns a timestamp into fields and back. These are the ones a trading script reaches for:

| Call | Gives |
|---|---|
| `date.year()`, `date.month()`, `date.day()` | Calendar date parts |
| `date.hour()`, `date.minute()`, `date.second()` | Clock parts |
| `date.dayOfWeek()` | 1 for Monday through 7 for Sunday |
| `date.dayOfYear()`, `date.weekOfYear()` | Position in the year |
| `date.isSameDay()` | Whether two instants fall on one calendar day |
| `date.startOfDay()`, `date.startOfWeek()`, `date.startOfMonth()` | Midnight at the start of the day, the Monday, the first of the month |
| `date.from()` | A timestamp built from year, month, day, hour, minute and second |
| `date.format()` | A timestamp rendered as text |

Every one of them takes an optional trailing `zone`. `date.format()` takes a small, fixed set of placeholders and copies every other character through:

| Placeholder | Gives | Placeholder | Gives |
|---|---|---|---|
| `yyyy` | Four digit year | `HH` | Two digit hour, 24 hour clock |
| `MM` | Two digit month | `mm` | Two digit minute |
| `dd` | Two digit day | `ss` | Two digit second |
| `MMM` | Three letter month | `EEE` | Three letter weekday |

Month and weekday names are English whatever the machine's language, so one script always draws the same chart.

```openscript
version 1

study("Day clock", overlay = true)

newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

var dayStart = none
var barsToday = 0
if newDay
    dayStart = time
    barsToday = 0
barsToday += 1

panel = table("Day clock", 3, 2, position = "topRight", textColor = silver)

// Written on the newest bar only: the panel shows the current state.
if bar.isLast
    cell(panel, 0, 0, "Newest bar")
    cell(panel, 0, 1, date.format(time, "EEE dd MMM HH:mm", "Asia/Kolkata"), textColor = white)
    cell(panel, 1, 0, "Day's first bar")
    cell(panel, 1, 1, date.format(dayStart, "HH:mm", "Asia/Kolkata"), textColor = white)
    cell(panel, 2, 0, "Bars today")
    cell(panel, 2, 1, text(barsToday, 0))
```

An anchor the reader picks, such as the start of an anchored average, is an input of kind `"time"`. The dialog stores the date and time as text, and the script receives a timestamp in UTC milliseconds, ready to compare with `time`:

```openscript
// In /trading the text is read as a UTC clock: 03:45 UTC is 09:15 IST.
anchor = input("2025-01-02 03:45", "Anchor, UTC", kind = "time")
started = time >= anchor
background(started ? fade(aqua, 95) : none)
```

> **In /trading today, the text of a time input is read as a **UTC** clock, not in the chart's timezone, on the chart and in the Backtest panel alike. `2025-01-02 09:15` means 09:15 UTC, which is 14:45 IST. To anchor at an IST time, subtract 5 hours 30 minutes when you type it, and say "UTC" in the input's title so the reader does too. The Strategies panel does not run a script with a time input at all.**

## Holidays

**There is no holiday calendar in this version.** `session.isHoliday()` is planned. Until then a holiday is not a flag a script can read. It is a shape in the data:

> On a holiday, there are no bars.

That sentence has consequences:

- **Never count calendar days to find an earlier session.** "Five days ago" is four sessions in a week with one holiday. Use a confirmed `"1D"` read with history inside the expression, which counts trading days.
- **Never assume `time - time[1]` is one interval.** It is one interval inside a session, the overnight gap at the day's first bar, the weekend on Monday, and several days after a holiday.
- **Never assume a day has a fixed number of bars.** A special session or an early close is a real session with fewer of them.
- **Two exchanges can have different holidays.** That is one way a [read of another instrument](/script/data/other-instruments) produces absent bars.

A script can still notice that a trading day is missing, which is often enough to widen a stop or skip a trade:

```openscript
version 1

study("Missing days", overlay = true)

newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

// Calendar days between this bar's date and the previous bar's date, in IST.
// Rounded, because the division is of two midnights.
today     = date.startOfDay(time, "Asia/Kolkata")
yesterday = isNone(time[1]) ? none : date.startOfDay(time[1], "Asia/Kolkata")
daysSkipped = isNone(yesterday) ? none : round((today - yesterday) / 86400000)

// A Monday normally follows a Friday, three days back. Any other weekday
// follows the day before. More than that means a trading day did not happen.
expected = date.dayOfWeek(time, "Asia/Kolkata") == 1 ? 3 : 1
missing = newDay and not isNone(daysSkipped) and daysSkipped > expected

if missing
    signal("DAY MISSING", orange, at = "above")

background(missing ? fade(orange, 88) : none)
```

This is a heuristic, and it says so: it assumes a Monday to Friday week, and it cannot tell a public holiday from a day the data feed did not deliver. It reports what it can observe, which is that a trading day the pattern expected did not arrive.

## The wall clock

`chart.now()` is the chart's wall clock in UTC milliseconds, and it is the only clock reading a script has. Everything else is a function of the bars. Use it to ask how old the newest bar is, not to compute anything historical, because a value computed from it changes every time the study runs:

```openscript
ageMinutes = (chart.now() - time) / 60000
plot(bar.isLast ? ageMinutes : none, "Minutes since the newest bar opened")
```

## Sessions and the clock in /trading today

The same script meets three different hosts in /trading, and they do not all supply the same facts yet.

| Where the script runs | `date.*` and `session.isIn` | `session.isFirstBar` and `session.isLastBar` |
|---|---|---|
| On the chart, as a study | Read in the chart's timezone | Absent: /trading does not yet give the chart's engine the instrument's session hours. `vwap()`, which restarts at each session's open, is absent for the same reason |
| In the Backtest panel | Absent: the backtest run is not told the chart's timezone | Absent |
| In the Strategies panel, as a deployed strategy | The runner refuses to start a script that calls them, because its engine reads a clock only in UTC and an Indian instrument's calendar is Asia/Kolkata. It refuses a time input for the same reason | The runner refuses to start a script that reads them |

The studies on this page use `date.*` and `session.isIn`, so they work on the chart. A strategy that has to behave the same on the chart, in the Backtest panel and when deployed needs a clock that none of those hosts can take away: arithmetic on `time` itself.

India does not observe daylight saving, so India Standard Time is always exactly 5 hours 30 minutes ahead of UTC. Adding that offset to `time` and dividing gives the IST day and the minute of the IST day with no calendar function at all. (A fixed offset is wrong for any zone that changes its clocks, which is why the language never uses one. For IST it is exact all year.)

```openscript
version 1

strategy("Intraday window by the IST clock", overlay = true, product = "intraday")

fastLen   = input(9,    "Fast length", min = 1, max = 500)
slowLen   = input(21,   "Slow length", min = 1, max = 500)
firstHHMM = input(930,  "First entry, HHMM IST", min = 915, max = 1530)
lastHHMM  = input(1445, "Last entry, HHMM IST", min = 915, max = 1530)
exitHHMM  = input(1515, "Square off from, HHMM IST", min = 915, max = 1530)

// IST is UTC plus 5 hours 30 minutes, all year.
IST_OFFSET = 19800000
DAY_MS     = 86400000

// Minutes since midnight IST at the bar's open: 555 is 09:15.
istMinute = floor(mod(time + IST_OFFSET, DAY_MS) / 60000)

firstMinute = floor(firstHHMM / 100) * 60 + mod(firstHHMM, 100)
lastMinute  = floor(lastHHMM / 100) * 60 + mod(lastHHMM, 100)
exitMinute  = floor(exitHHMM / 100) * 60 + mod(exitHHMM, 100)

// Crosses computed at the top level, on every bar, so their state is right.
fast   = ema(close, fastLen)
slow   = ema(close, slowLen)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

inEntryWindow = istMinute >= firstMinute and istMinute <= lastMinute
squareOffTime = istMinute >= exitMinute

if inEntryWindow and goLong and pos.size == 0
    buy(qty = 1)

if pos.size > 0 and (goFlat or squareOffTime)
    close()
```

The same arithmetic gives a new IST day without a date function: `istDay = floor((time + IST_OFFSET) / DAY_MS)` changes at midnight IST, so `istDay != orElse(istDay[1], -1)` is true on each day's first bar.

## Mistakes worth naming

- **Resetting on a date change in the wrong zone.** Split days in IST by naming `"Asia/Kolkata"`, so a chart set to another timezone still splits them at midnight IST.
- **Waiting for the next day's first bar to go flat.** By then the position is overnight. Square off on the last bar or by the clock.
- **Using an abbreviation or an offset as a zone.** `"IST"` stops the study with OS6005. Write `"Asia/Kolkata"`.
- **Comparing `date.hour(time)` with a hard coded open.** The interval decides where bars start and the exchange decides when it opens. Both change.
- **Remembering the day's first bar by `bar.index`.** Every index shifts when older history loads. Store `time` instead.

**Related:** [Timeframes](/script/data/timeframes), [Higher timeframes](/script/data/higher-timeframes), [Other instruments](/script/data/other-instruments), [Repainting](/script/data/repainting), [session.* reference](/script/reference/session), [date.* reference](/script/reference/date)


## Repainting

Source: https://openalgo.in/script/data/repainting

A study **repaints** when what it shows for a past bar is not what it showed while that bar was happening. It is the difference between a study that looks brilliant on history and one that makes money, and most traders who have been caught by it could not say afterwards which line did it. This page defines repainting precisely, names the four ways an OpenScript (also called OpenAlgo Script) file can cause it, shows how to catch it, and lists the few cases where it is acceptable.

## What repainting is

> **A study repaints when the value it shows for a bar, after that bar has closed, differs from the value it showed while that bar was the newest one.**

Two different things move on a chart as new prices arrive, and only one of them is repainting.

| Movement | Example | Repainting? |
|---|---|---|
| The newest bar's value changes while the bar is still forming | `close` ticks up, so `sma(close, 20)` ticks up with it | No. The bar is not finished and neither is the answer |
| A closed bar's value changes later | A marker appears on a bar that is already twenty bars old | Yes |
| A closed bar's value changes when the chart is reloaded | The study drew nothing there this morning and draws a signal there now | Yes, and this is the worst kind |

The first row is ordinary. A study computed from a moving price moves, and nobody is misled, because the bar is visibly the newest one.

The other two rows are the problem, and it is not cosmetic. A backtest, a run of a strategy over past bars, measures what a study would have told you at the time. If the study's history is not what it said at the time, the backtest measured something that never existed: real arithmetic over imaginary signals.

### Why history hides it

A repainting study's past looks right. Every marker sits at a sensible place, every level is respected, every trend is entered near its start. It looks like a study that works, because it has been told the answers. The first evidence is usually a signal that appears and then disappears as you watch, and the second is a trading record that does not resemble the backtest.

## The four ways a script causes it

### 1. Acting on a bar that has not finished

A forming bar's `close` is the last traded price, its `high` may still be exceeded and its `low` may still be broken. Anything computed from them is provisional. The language defends you from this by default, in two ways:

- **Effects wait for the close.** `signal()`, `alert()` and every order function do not fire on a bar that is still moving. The call waits until the bar closes, and if the condition is no longer true by then, it never happens. That is what makes a signal worth acting on.
- **Persistent values roll back.** Before each re-run of the moving bar, every `var` is restored to what it held at the end of the previous bar. Running the moving bar ten times gives the same answer as running it once, so the chart and a backtest of the same data agree. [Persistence](/script/language/persistence) has the details.

A script gives up those defences by writing `onUnconfirmed = true` in its declaration, by making a `"developing"` [higher timeframe read](/script/data/higher-timeframes#the-mode), or by writing `live var` instead of `var`.

```openscript
version 1

// Repaints. The entry is taken from a close that is still moving, so a bar that
// pokes above the level at 10:31 and falls back by 10:35 leaves a trade in the
// backtest that the market never offered anyone.
strategy("Breakout, unguarded", overlay = true, onUnconfirmed = true)

level20 = highest(high, 20)[1]

if close > level20
    buy(qty = 1)
```

```openscript
version 1

// Does not repaint. Without onUnconfirmed, the engine waits for the bar to
// close before it sends the order, and sends nothing if the close is back
// below the level.
strategy("Breakout, confirmed", overlay = true)

level20 = highest(high, 20)[1]

if close > level20
    buy(qty = 1)
```

The second script needs no extra state, no flag and no "wait one bar" logic. The default already is the guard, which is why `onUnconfirmed = true` has a long name and sits in the declaration, where a reviewer reads it first.

`live var` belongs here too. It keeps its value across the updates of the moving bar, so a value accumulated in one differs between the chart and a backtest of the same data, by design. The compiler says so with OS8011.

### 2. Reading a bar that had not happened yet

The second cause is a read that answers with information from the future. In OpenScript there is exactly one way to write it, and it is spelled out:

```openscript
// The finished value of the day, shown on every chart bar of that day,
// including the ones before the value existed.
dayClose = req.timeframe("1D", close, mode = "lookahead")
plot(dayClose, "Day's close", style = "step")
```

`"lookahead"` gives a coarse bar's final value from its first chart bar. On a five minute NSE chart with a daily read, it hands 09:15 the number the day will close at. Every study built on it anticipates the day perfectly on history, and knows nothing extra when traded, because at the time there is no future to read.

The mode exists for one honest purpose, drawing a finished coarse candle across history as a picture, and its name makes sure nobody reaches it by accident. The compiler warns about it with OS8005. The compiled study also records the mode, so a host can mark the study as repainting; the /trading legend does not show such a mark today, so the warning and the word in the source are the disclosure.

The library refuses this shape everywhere else. `pivotHigh()` and `pivotLow()` report a pivot on the bar `right` bars **after** it formed, the first bar on which it is knowable. And a strategy's orders fill at the next bar's open by default, not at the close of the bar that decided them, because a decision made from a close cannot be filled at that same close in the real market. [Costs and fills](/script/strategies/costs-and-fills) covers the fill model.

### 3. Drawing a decision back in time

The third cause is different in kind. The values are honest, and the **drawing** is placed in the past:

```openscript
// pivotHigh reports a swing high on the bar five bars after it, the first bar
// on which it is known. Both drawings below put it back at the high itself.
pivot = pivotHigh(high, 5, 5)
plot(pivot, "Pivot", red, offset = -5)          // drawn five bars back
if not isNone(pivot)
    draw.label(time[5], pivot, "Swing high")    // anchored at the pivot bar's time
```

Nothing here is invented. The pivot really was at that bar, and the script found out five bars later. But the chart's past gains a marker it did not have five bars ago, so a reader who scrolls back sees a study that seems to have called the high at the high.

That makes it a disclosure problem rather than a data problem. Say in the study's title or comments that the marker arrives `right` bars late, and never let a strategy act at the anchor bar's price. A positive `offset` draws into the space past the newest bar and is not a repaint at all: nothing in the past moved.

### 4. Keeping state that the next load will not reproduce

The quietest cause. The study does not read the future and does not act on an unfinished bar; it just gives a different answer the next time the chart is loaded.

| Shape | Why a reload differs | The compiler |
|---|---|---|
| A `live var` accumulating over price updates | History has no updates within a bar, so the reloaded value is the bar by bar one | OS8011 |
| A stateful call inside a branch | Its state advances only on the bars where the branch ran | OS8001 |
| A persistent value holding `bar.index` | Every index shifts when older history is loaded | OS8014, a listed code the compiler does not raise yet |
| A running total from bar 0: `cum()`, `obv()` | Bar 0 moves when more history loads, so the total starts somewhere else | No warning: this is what a running total is |
| A seeded average near the left edge | `ema(src, n)` is seeded from the first `n` bars, and those are different bars when more history loads | No warning: the seeding is part of the definition, and its effect fades as the average moves on |

The first three are script bugs. The last two are properties of the measurements, and the defence is to keep them off the part of the chart you decide from: do not trade from the first bars of a freshly loaded chart, and do not compare a running total across two different history loads. Where you need to remember a bar, store its `time`, which never moves.

## How to see it

Four tests, cheapest first.

**Read the source for four words and two shapes.** The words are `"developing"`, `"lookahead"`, `onUnconfirmed` and `live var`. A file with none of them cannot repaint in the first two ways above. That is not a rule of thumb, it is a property of the language: the default mode is `"confirmed"`, effects wait for the close, and `var` rolls back. The shapes are the third way: a negative `offset` on a plot, and a drawing anchored at a past bar's time, such as `time[5]`.

**Watch one bar close.** Note the study's value while the newest bar is forming, wait for the bar to close, and compare. The value should settle once and never move again. If a marker appears and disappears while you watch, the study acts on unconfirmed data.

**Reload the chart.** Note where the markers are, reload /trading, and look again. Markers that moved fail the fourth test above.

**Measure it.** History cannot show you a repaint, because history is the repainted version. So measure the drift as bars form, with a script. The compiler warns about the `live var` lines, and for this study that is the point:

```openscript
version 1

// A meter, not a signal. It records what a read said on the first update of
// each bar and plots how far the read has moved since.
study("Repaint meter", precision = 4)

tf = input("1h", "Interval to test", kind = "interval")

watched = req.timeframe(tf, close, mode = "developing")

// live var on purpose: an ordinary var is restored before every re-run of the
// moving bar, which would erase the very thing being measured.
live var seenAt = none
live var firstValue = none

if seenAt != time
    seenAt = time
    firstValue = watched

drift = isNone(firstValue) ? none : watched - firstValue

plot(drift, "Drift since this bar opened", orange, width = 2)
level(0, "No drift", gray)
```

On history every bar runs once, so the line is flat at zero and the study looks pointless. Leave it on a chart through a trading session and it will not be flat: its height is how far a signal taken from the same read could move before the bar closes. Change `"developing"` to `"confirmed"` and the line stays at zero, which is what the default mode buys you.

## What the compiler tells you

Warnings never stop a script. They are reported on the line, with a code and a fix, because the shapes they name are almost always mistakes.

| Code | Says | Why it matters here |
|---|---|---|
| OS8001 | A stateful call inside a branch advances only on the bars where the branch runs | The call's history depends on which bars ran, so a reload can differ |
| OS8002 | The file sets `onUnconfirmed = true` and reads another interval or instrument | Two sources of provisional data stacked |
| OS8005 | A read uses `"lookahead"` | The study shows values its bars could not have known |
| OS8011 | A `live var` keeps its value across the updates of the moving bar | The chart and a backtest differ by design |

OS8004 (a branch on an absent condition that changes a value used later) and OS8014 (a persistent value that holds a bar index) are listed among the warning codes, but the compiler does not raise them yet, so check for those two shapes yourself. A `"developing"` read carries no warning: the mode word on the line is its disclosure. [OS8xxx Warnings](/script/errors/warnings) has every code.

What the compiler cannot tell you is whether a repaint is acceptable. It does not know whether the study is a dashboard a person reads or a filter a strategy trades. That judgement is yours, which is why the mechanism is a disclosure rather than a ban.

## What is not repainting

A study accused of repainting is often doing something else.

- **The newest bar moving.** A study computed from a moving price moves. Wait for the close.
- **An empty left edge.** A call that needs `len` bars is absent until `len` bars exist, and absence draws a gap rather than a zero so that you can see it. [Warmup](/script/language/warmup) explains the rules.
- **An alert that did not fire.** A condition true at 10:31 and false at the close produces no alert, on purpose. That is effects waiting for the close, not a missed signal.
- **A late pivot.** A marker that arrives five bars after the high is the honest cost of knowing a pivot. Drawing it back at the high is the part that needs disclosing.
- **A read that arrives late.** A [read of another instrument](/script/data/other-instruments) is absent until the host answers, and the study is then calculated again with it present. The gap was the truth at the time.

## When repainting is acceptable

When the reader can see it and nothing acts on it. That one rule covers every acceptable case:

| Case | Shape | Why it is acceptable |
|---|---|---|
| A finished daily candle drawn over history | `"lookahead"` | The picture is the point, and nothing trades from it |
| A dashboard showing the day's range so far | `"developing"` | "So far" is the question, and a person is reading it |
| A swing marker drawn back at its pivot | Negative `offset` | The lag is real, disclosed, and nothing trades at that bar |
| A counter of price updates within a bar | `live var` | Counting updates is the stated intent |

And the rule that follows: **no order, and no alert a person will act on, may depend on a repainting value.** If a `"developing"` read is on the chart for the eye, drive the decisions from a confirmed read of the same expression. Two reads of one expression in two modes is cheap and makes the split explicit:

```openscript
version 1

study("Day range, read twice", overlay = true, precision = 2)

// For the eye: what the day has done so far. It moves, and that is the point.
soFar = req.timeframe("1D", high, mode = "developing")

// For the decisions: only days that have closed. It never moves.
settled = req.timeframe("1D", high, mode = "confirmed")

plot(soFar,   "Today's high so far", fade(aqua, 40), style = "step")
plot(settled, "Yesterday's high",    aqua, width = 2, style = "step")

// The marker reads the settled value only. Nothing here acts on soFar.
if crossUp(close, settled)
    signal("ABOVE YESTERDAY")
```

## A review checklist

Run down this list before a study is trusted with money.

| Question | Where to look | A good answer |
|---|---|---|
| Does any read say `"developing"` or `"lookahead"`? | Every `req.` call | None, or one whose result nothing trades from |
| Does the declaration say `onUnconfirmed = true`? | The `study` or `strategy` line | No, or every decision is guarded by `bar.isConfirmed` |
| Is any `var` a `live var`? | Every declaration | No, unless counting updates is the study's subject |
| Does a persistent value hold `bar.index`? | Every `var` assignment | No: it holds `time` |
| Is any stateful call inside an `if` or a ternary arm? | Every branch | No: computed at the top level, hidden with `none` |
| Does any plot use a negative `offset`, or any drawing anchor at a past bar? | Every `plot` and `draw.` call | Only where the lag is stated in the title or comments |
| Did the study survive a reload? | The chart | The markers are where they were |

**Related:** [Higher timeframes](/script/data/higher-timeframes), [Realtime and confirmation](/script/language/realtime-and-confirmation), [Persistence](/script/language/persistence), [Other instruments](/script/data/other-instruments), [Backtesting](/script/strategies/backtesting)


# Visuals

## Visuals overview

Source: https://openalgo.in/script/visuals/overview

A script produces two kinds of output: numbers, and pictures made from numbers. This page is the map of the pictures. It names every call in OpenScript (also called OpenAlgo Script) that draws on a chart, says what each one is good at, and gives you four questions that pick the right one. Read it before the detailed pages, so that when you want a line, a band, a marker or a shaded zone you already know which call to reach for.

The four questions are always the same:

1. Is the thing one value per bar, or a shape that spans several bars?
2. Does it have a price to sit at, or is it a fact about the whole bar?
3. Is it fixed before the first bar runs, or does it appear and disappear as bars arrive?
4. Which pane should a reader find it in?

Answer those four and the call chooses itself.

## One study, one of each surface

Here is a complete overlay study (one drawn over the price candles) that uses most of the drawing calls at once, so you can see them side by side. Save it in the Scripts panel of the /trading page and apply it to any NSE chart.

```openscript
version 1
study("Surface tour", overlay = true)

len = input(20, "Length", min = 2, max = 500)

basis = sma(close, len)
band  = 2 * stdev(close, len)

// Two named plots, so the fill has two columns to shade between.
pUpper = plot(basis + band, "Upper", aqua)
pLower = plot(basis - band, "Lower", aqua)
plot(basis, "Basis", orange, width = 2)

fill(pUpper, pLower, color = aqua, opacity = 0.08)

// A regime has no price to sit at, so it is painted rather than plotted.
// Absent during warmup, so the candles keep their own colours there.
trendColor = isNone(basis) ? none : (close > basis ? lime : red)
barColor(trendColor)
background(close > basis + band ? fade(orange, 93) : none)

// An event, on the one bar it happened.
if crossUp(close, basis + band)
    signal("BREAKOUT", at = "above", shape = "triangleUp")
```

Read it as: three plotted lines, one shaded band between two of them, a colour on every candle, a faint wash on the bars where price closed above the upper band, and a marker on the bars where it first crossed out.

## The surfaces

Everything a script draws lands on one of seven surfaces.

| Surface | Calls | What it draws |
|---|---|---|
| Plotted columns | `plot()`, `plotCandles()` | One value per bar, drawn as a line, a step, an area, a histogram, columns or candles |
| Shaded bands | `fill()` | The region between two plotted columns |
| Horizontal levels | `level()` | One line straight across the pane at one price |
| Bar markers | `signal()` | A named marker on one bar |
| Per-bar paint | `barColor()`, `background()` | A colour for the bar's candle, or a shade behind the bar's whole column |
| Drawing objects | `draw.line()`, `draw.label()`, `draw.box()`, `draw.polyline()` | Objects anchored to a time and a price, created, moved and deleted over many bars |
| The pinned grid | `table()`, `cell()` | A panel fixed to a corner of the pane, showing the current state rather than a history |

Plots, fills and markers together look like this on a real chart. This [HalfTrend](/script/getting-started/example-scripts#halftrend) study on a BHEL 15 minute chart draws its trend level as two plots, blue while the trend is up and red while it is down, shades a channel beside the level with two fills, and puts a labelled marker on each flip:


Two more calls produce output that is not drawn at all. People look for them here, so they belong on the map: `alert()` raises an alert on the bar it runs on, and `print()` writes a value to the script's log. See [Alerts from scripts](/script/alerts/overview) and [Debugging](/script/writing/debugging).

## If you want this, use that

Keep this table open while you write. The last column names the page that covers the call in full.

| If you want | Use | Explained in |
|---|---|---|
| A moving average drawn over price | `plot(value, "Title", colour)` | [Plots](/script/visuals/plots) |
| A value that changes once a session or once a day | `plot(..., style = "step")` | [Plots](/script/visuals/plots#the-six-styles) |
| An oscillator histogram coloured by sign | `plot(h, "Hist", color = h > 0 ? lime : red, style = "histogram")` | [Plots](/script/visuals/plots#colour-one-colour-or-a-colour-per-bar) |
| Volume as columns in a pane of its own | `plot(volume, "Volume", style = "column")` | [Plots](/script/visuals/plots#formatting-the-numbers) |
| Candles built from your own four values | `plotCandles(o, h, l, c, "Title")` | [Plots](/script/visuals/plots#candle-plots) |
| A plot pushed forward or back along the time axis | `plot(..., offset = n)` | [Plots](/script/visuals/plots#offsetting-a-plot) |
| One line of a pane study drawn over price | `plot(..., overlay = true)` | [Plots](/script/visuals/plots#pane-and-scale) |
| A second series on the left price axis | `plot(..., scale = "left")` | [Plots](/script/visuals/plots#pane-and-scale) |
| A shaded band between two lines | `fill(plotA, plotB, ...)` | [Fills](/script/visuals/fills) |
| A band that changes colour with which line leads | `fill(a, b, colorUp = ..., colorDown = ...)` | [Fills](/script/visuals/fills#two-colours-for-which-side-leads) |
| Shading between a line and a fixed value | An invisible second plot, then `fill` | [Fills](/script/visuals/fills#filling-between-a-plot-and-a-fixed-value) |
| A fixed reference line at 70, 30 or zero | `level(70, "Overbought", ...)` | [Levels](/script/visuals/levels) |
| A line at the previous day's high | `level(dayHigh, "Previous day high", ...)` | [Levels](/script/visuals/levels#levels-computed-from-the-data) |
| A pane whose scale never rescales | `study(..., range = [0, 100])` | [Levels](/script/visuals/levels#fixing-a-pane-s-range) |
| How many decimals a study's own pane shows | `study(..., precision = 2)` | [Levels](/script/visuals/levels#the-price-axis) |
| A pane axis that reads as a percentage or a volume | `study(..., format = "percent")` | [Levels](/script/visuals/levels#the-price-axis) |
| A colour that fades, blends or changes per bar | `fade`, `mix`, a ternary | [Colors](/script/visuals/colors) |
| A mark on the one bar something happened | `signal("BUY")` | [Labels and shapes](/script/visuals/labels-and-shapes) |
| A plate of text at a price | `draw.label(t, p, "Text")` | [Labels and shapes](/script/visuals/labels-and-shapes) |
| The candles recoloured by regime | `barColor(colour)` | [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds) |
| The whole bar column shaded behind everything | `background(colour)` | [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds) |
| A trendline between two points in the past | `draw.line(t1, p1, t2, p2)` | [Lines and boxes](/script/visuals/lines-and-boxes) |
| A zone that is extended and later deleted | `draw.box(...)`, then the `draw` setters | [Lines and boxes](/script/visuals/lines-and-boxes) |
| A readings panel in a corner | `table(...)` and `cell(...)` | [Tables](/script/visuals/tables) |
| An alert when a condition is met | `alert(...)` | [Alerts from scripts](/script/alerts/overview) |
| A value written to the log while debugging | `print(value)` | [Debugging](/script/writing/debugging) |

Three rows of that table are worth their reasons, because they are where people most often reach for the wrong call.

**A crossing is not a plot.** A crossing happens on one bar. A plotted column carries one value on every bar, so the only way it can show an event is by being absent everywhere else, which spends a whole column on one dot. `signal()` is the call for an event: one call, one named marker, with `at` and `shape` when you care where it sits and what it looks like.

**A regime is not a price.** "The trend is up", "the opening range is still forming", "volatility is unusually high": none of these has a price to sit at. Drawing one as a line puts a flat series through the price scale saying something that has nothing to do with price. `background()` shades the bar's whole column and `barColor()` recolours the candle itself. Both take `none` to mean "leave this bar alone", which is how a conditional paint switches itself off.

**A fixed reference is not a plot either.** A line at 70 does not need a column of identical numbers, a value in the legend and a group of rows in the settings dialog. `level()` draws it with one call and none of those.

## Where each call may appear

Some drawing calls declare the fixed shape of the study, and the rest are per-bar events. This rule catches everyone once.

| Calls | Where they may appear | Why |
|---|---|---|
| `plot`, `plotCandles`, `fill`, `level`, `table` | Top level only. Inside an `if`, a loop or a function it is [OS3006](/script/errors/arguments#os3006) | They declare the study's fixed shape. The legend, the settings dialog and the pane are built before the first bar runs, so the set of columns, bands, levels and grids must be known when the script compiles |
| `input` | Top level only. Inside a block or a function it is [OS3007](/script/errors/arguments#os3007) | Each input is one row of the settings dialog, built before the first bar |
| `signal`, `barColor`, `background`, `cell`, `print`, `alert`, everything in `draw` | Anywhere | They are per-bar events and per-bar paint. Nothing about them has to be known in advance |

So you never hide a plot by wrapping it in an `if`:

```openscript
trending = adx(14, 14)[0] > 25
ema20 = ema(close, 20)

if trending
    plot(ema20, "EMA 20", aqua)
```

You hide it by giving it the absent value, `none`, on the bars where you do not want it:

```openscript
trending = adx(14, 14)[0] > 25
ema20 = ema(close, 20)

plot(trending ? ema20 : none, "EMA 20", aqua)
```

The same idea works on every surface. Absence reaching a drawing surface is a gap, never a zero, and that one rule is what makes "hide it with `none`" work everywhere without a second mechanism. [Absent values](/script/language/absent-values) explains where `none` comes from.

| Surface | What an absent value does |
|---|---|
| A plotted column | The line breaks. Nothing is drawn on that bar |
| A shaded band | The band stops, and resumes where both ends have values again |
| A level | A level is drawn at its price on the last bar, so an absent price there means no line at all |
| A marker | No marker on that bar |
| `barColor` | The candle keeps its own colour |
| `background` | The bar's column is not shaded |
| A table cell | The cell is left blank |

## What costs a plot slot

A **plot slot** is one plotted column. It keeps a value for every bar, shows its current value in the study's legend (the row at the top of the pane with the study's name), and gets its own group in the Style tab of the settings dialog, where the user can change its colour, opacity, thickness, line style and plot style without editing your script. Only two calls create one.

| Call | Plot slots | Kept per bar |
|---|---|---|
| `plot(...)` | One | Its value, plus a colour when the colour is worked out bar by bar |
| `plotCandles(...)` | One | Four values: open, high, low and close |
| `fill(...)` | None: it names two plots that already exist | Nothing of its own |
| `level(...)` | None | Its price, of which the last bar's is drawn |
| `signal(...)` | None | Its text, on the bars it fires |
| `barColor(...)`, `background(...)` | None | One colour per bar each |
| `table(...)`, `cell(...)` | None | The cells as last written |
| `draw.*` | None | The objects the script is holding |
| `alert(...)` | None | Its condition and its message |

Two consequences come up in real scripts.

**A function with several outputs costs one slot per output you draw.** `macd()` returns three numbers in one array, from one call and one piece of state. Drawing all three is three `plot` calls and three slots. Drawing only the histogram is one slot, and the other two outputs cost nothing.

**A shaded band needs its two edge plots even when you do not want the lines.** When the band is the whole point, keep the plots and make them invisible with a fully transparent colour. `fade()` takes transparency, and 100 is invisible:

```openscript
basis = sma(close, 20)
band  = 2 * stdev(close, 20)

pUpper = plot(basis + band, "Upper", fade(aqua, 100))
pLower = plot(basis - band, "Lower", fade(aqua, 100))
fill(pUpper, pLower, color = aqua, opacity = 0.08)
```

An invisible plot draws nothing, and the legend leaves out its value. Name the band's colour yourself, as here: a band given no colour takes its first plot's colour, and that colour is invisible.

Only plots appear in the Style tab. When you pass a colour input straight to a plot, by the input's own name, the plot's colour in the Style tab and the input are one setting. See [Inputs](/script/inputs/inputs) and [Settings and style](/script/inputs/settings-and-style).

## Which pane a thing lands in

A study is either drawn over the instrument's candles or given a pane of its own, and the declaration decides which. `overlay` defaults to `false`, so a study with no `overlay` argument gets its own pane below the price.

```openscript
version 1
study("Relative strength", precision = 2, range = [0, 100])

plot(rsi(close, 14), "RSI", purple, width = 2)
```


The declaration sets the default for everything the file draws. Two calls can override it item by item, and the rest cannot.

| Thing | Where it draws | Can one item be moved? |
|---|---|---|
| `plot(...)` | The study's pane | Yes: `overlay = true` puts this one column on the price pane |
| `plotCandles(...)` | The study's pane | No: it has no `overlay` argument |
| `fill(...)` | The study's pane | Yes, with its own `overlay` argument. Move its two plots with it |
| `level(...)` | The study's pane | No: a level has no `overlay` argument |
| `signal(...)` | Over price, above or below the candle. In a study with its own pane, above or below the study's first plot | No |
| `background(...)` | The study's pane, the full height of the bar's column | No |
| `barColor(...)` | The instrument's own candles on the price pane, even from a study with its own pane | No |
| `table(...)` | A corner of the study's pane | No, but `position` picks the corner |
| `draw.*` | The study's pane, at a time and a price on that pane's scale | No |

`barColor` is the one that surprises people, and it is deliberate: a trend reading computed in a pane below the chart is most useful painted onto the candles the trader is watching, and forcing the study to be an overlay just to reach them would mean giving up its own axis. The candles are one object, so when two studies both colour them, only one study's colouring is shown, decided by the order of the studies on the chart.

A study with its own pane and no plot at all draws no markers, because its markers have no line to sit on.

Within a pane, a plot's `scale` argument picks the price axis it maps to:

| `scale` | Means |
|---|---|
| `"right"` | The right-hand axis. The default |
| `"left"` | The left-hand axis, for a second series in different units. It appears when a plot uses it |
| `"none"` | A hidden scale of its own, fitted to this column alone, so it never stretches the axis the other plots read against |

## Declaration options that shape the picture

Five options on `study(...)` decide how the whole study is drawn. Each must be fixed when the script compiles. They are covered in full in [Declarations](/script/reference/declarations).

| Option | Default | Controls |
|---|---|---|
| `overlay` | `false` | `true` draws on the price pane, `false` gives the study its own pane |
| `precision` | `4` | Decimals on the axis and legend of a study with its own pane, a whole number from 0 to 10. Over the price pane the instrument's own formatting is kept |
| `format` | `"price"` | `"price"`, `"percent"` or `"volume"`: how the study's own pane axis and legend read |
| `range` | `none` | `[min, max]` fixes the study pane's scale, as in `[0, 100]` |
| `scale` | `"right"` | Accepted, but the /trading chart does not apply it in this release. Set `scale` on each plot instead |

## The order things are painted in

The /trading chart paints each pane in four layers, bottom to top:

| Layer | Holds |
|---|---|
| 1, behind the data | Shading from `background(...)` and bands from `fill(...)` |
| 2 | The instrument's candles (on the price pane), then plotted columns and candle plots in the order they were added |
| 3 | Levels, markers from `signal(...)` and drawing objects from `draw` |
| 4, on top | The grid from `table(...)` and `cell(...)`, which covers whatever is under its corner |

Four facts are enough to design with:

- Bands and backgrounds sit behind the candles and lines, so they never hide them. A strong one still drowns their colours, so keep both faint. `fade(silver, 92)` reads as a tint; `silver` at full strength reads as a bug.
- A `fill` given no colour takes its first plot's colour at twelve percent strength. Name a colour and you get that colour, dimmed only by `opacity`, which starts at 1.
- Within one study, plots are painted in the order you declare them, so a plot declared first sits under the ones declared after it. That is the cheapest control you have over what covers what.
- An absent value removes a layer for that bar rather than painting a zero over what is underneath.

## Two more worked examples

### An oscillator with its own pane, scale and lines

```openscript
version 1
study("RSI with a zone", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)

r = rsi(close, len)

// Levels, not plots: horizontal lines with no columns and no legend values.
level(70, "Overbought", fade(red, 40))
level(30, "Oversold", fade(lime, 40))

pOsc = plot(r, "RSI", purple, width = 2)

// The midline exists only so the fill has a second column to name.
// Fully transparent, so it draws no line of its own.
pMid = plot(50, "Midline", fade(gray, 100))

fill(pOsc, pMid, colorUp = fade(lime, 86), colorDown = fade(red, 86))
```

`range = [0, 100]` fixes the pane's scale, so 70 sits at the same height on every chart you open. That is the reason to fix a range: a reading with real bounds should keep them on screen.

### A pane study that reaches onto the price chart

```openscript
version 1
study("Trend strength", precision = 2, range = [0, 100])

diLen  = input(14, "DI length",  min = 1, max = 100)
adxLen = input(14, "ADX length", min = 1, max = 100)

strength = adx(diLen, adxLen)[0]

// Computed at the top level, on every bar. A stateful call placed inside a
// branch would only advance on the bars that branch ran.
e20 = ema(close, 20)

level(25, "Trending above here", fade(gray, 50))
plot(strength, "ADX", purple, width = 2)

// This one column belongs where the trader is looking, not down here.
plot(strength > 25 ? e20 : none, "EMA 20 while trending", orange, width = 2, overlay = true)

// And the candles themselves carry the regime: greyed out while the market
// is not trending, left alone while it is and during warmup.
barColor(isNone(strength) or strength > 25 ? none : fade(gray, 60))
```

Three things are worth noticing. The study owns a pane, yet one of its columns is on the price pane and so is its bar colouring. The average is computed on every bar and only the drawing is conditional: computing it inside the ternary or an `if` would raise warning [OS8001](/script/errors/warnings#os8001) and leave holes in the line. And `barColor(none)` on the trending bars leaves those candles their own colour, which is a stronger picture than painting them a second colour. The `isNone` test keeps the warmup bars, where `adx()` has no value yet, from being greyed out as if they were quiet.

## Choosing between a plot, a level and a drawing

These three overlap, and picking the wrong one gives a script that works and then becomes painful to change.

| | `plot` | `level` | `draw.line` |
|---|---|---|---|
| Shape | One value per bar | One horizontal line across the pane | A segment between two anchored points |
| How many | Fixed when the script compiles, one per call | Fixed when the script compiles, one per call | As many as the script creates, at any time |
| Value | A different value on every bar | One price, read again every bar; the last bar's wins | Two points, movable later |
| History | Every bar's value is drawn, and the series can be read back with `[]` | None: you see one line | Each object stays until deleted |
| Cost | A plot slot | No plot slot | No plot slot, but memory per object |
| Good for | An indicator, a band edge, a stop that moves every bar | A threshold, the previous day's high, a zero line | A trendline between two pivots, a zone, an annotation |

The tie-breaker: **if the thing has a value on every bar, plot it**. If it has one value that the whole pane should show at a single height, make it a level. If it starts somewhere in the past and ends somewhere else, draw it.

Related: [Plots](/script/visuals/plots), [Levels](/script/visuals/levels), [Fills](/script/visuals/fills), [Colors](/script/visuals/colors), [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds), [Labels and shapes](/script/visuals/labels-and-shapes), [Lines and boxes](/script/visuals/lines-and-boxes), [Tables](/script/visuals/tables), [Plotting reference](/script/reference/plotting).


## Plots

Source: https://openalgo.in/script/visuals/plots

A **plot** is one value per bar, drawn. That is the whole idea, and everything else on this page is an argument that decides how those values look: the shape, the colour on each bar, the thickness, the pane and axis they land on, how far they are shifted along the time axis, and how their numbers are formatted. `plot` computes nothing itself. The values come from wherever you calculated them: a library function, an expression, or a `var` that carries state from bar to bar.

Most studies are built from plots, and a [fill](/script/visuals/fills) always names two of them, so this is the drawing call you will use most.

## A first example

Two exponential moving averages drawn over the candles of any NSE stock or index future:

```openscript
version 1
study("Two averages", overlay = true)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)

plot(ema(close, fastLen), "Fast EMA", aqua, width = 2)
plot(ema(close, slowLen), "Slow EMA", orange)
```

The picture shows the same two lines at 20 and 50 bars on a daily SBIN chart, with two additions from later pages: the band between them, shaded with `fill()`, and a label on each crossing from `signal()`:


Each `plot` call adds one line to the chart and its current value to the study's legend, the row at the top of the pane that carries the study's name. It also adds a group to the Style tab of the settings dialog, named by the plot's title, where the user can change the line's colour, opacity, thickness, line style and plot style later.

## The call

`plot()` takes the value and a title, then optional arguments by name:

| Argument | Default | Means |
|---|---|---|
| `value` | required | The number to draw on this bar. `none` draws nothing on that bar |
| `title` | required | Names the plot in the Style tab of the settings dialog. No two plots in a file may share one |
| `color` | `none`: the chart's default colour, the same for every plot that names none | One colour, or a different colour on each bar |
| `width` | `1.5` | Line thickness |
| `style` | `"line"` | One of six shapes, listed below |
| `offset` | `0` | A whole number of bars to shift the drawing: positive to the right, negative to the left |
| `overlay` | the declaration's | `true` puts this one column on the price pane |
| `precision` | the declaration's, in a study with its own pane | Decimals on the price scale this plot maps to |
| `format` | the declaration's, in a study with its own pane | `"price"`, `"percent"` or `"volume"`, on that same scale |
| `scale` | `"right"` | `"right"`, `"left"` or `"none"` |

Only `value` and `color` are read on every bar. The rest (`title`, `width`, `style`, `offset`, `overlay`, `precision`, `format` and `scale`) describe the column itself and are **fixed before the first bar**. Write each one as a literal in the call, or pass an `input()` so the user can change it. A name you computed, even from constants, counts as bar data and raises [OS3003](/script/errors/arguments#os3003):

```openscript
thick = 1 + 1
plot(close, "Close", aqua, width = thick)
```

```openscript
thick = input(2, "Line width", min = 1, max = 5)
plot(close, "Close", aqua, width = thick)
```

Two plots in one file may not share a title ([OS3017](/script/errors/arguments#os3017)), because the Style tab tells plots apart by their titles.

### The handle a plot returns

`plot` returns a **handle** that names the column. You need it only when a `fill()` has to shade against this column, and then you keep it in an ordinary name at the top level:

```openscript
basis = sma(close, 20)
pUpper = plot(basis + 2 * stdev(close, 20), "Upper", aqua)
pLower = plot(basis - 2 * stdev(close, 20), "Lower", aqua)
fill(pUpper, pLower, color = aqua, opacity = 0.08)
```

A handle exists only while the script compiles. It cannot be held in a `var` ([OS2003](/script/errors/names-and-types#os2003)), passed to a function, stored in an array or read with `[]`, because the set of plotted columns is fixed before bar 0 and a handle that could travel at run time would let a script decide halfway through the chart which columns exist.

### Top level only

`plot` declares part of the study's fixed shape, so it must sit at the top level of the file. Inside an `if`, a loop or a function it is [OS3006](/script/errors/arguments#os3006). The fix is always the same: plot `none` on the bars you want hidden, as shown in [Hiding a plot](#hiding-a-plot-and-what-absence-draws).

## The six styles

| `style` | Draws | Use it when |
|---|---|---|
| `"line"` | A line joining consecutive values | The value changes every bar and the slope between bars means something. The default, and right most of the time |
| `"lineWithMarkers"` | The same line with a mark at every value | The values are sparse, or read one bar at a time rather than as a curve |
| `"step"` | A flat segment per bar, with a vertical jump where the value changes | The value changes only occasionally: once a session, once a day, once per trade |
| `"area"` | A line with the region below it shaded down to the bottom of the pane | One quantity whose level, rather than its shape, is the story |
| `"histogram"` | A bar from zero to the value | A signed quantity that reads above and below a zero line |
| `"column"` | A column from zero to the value, like a volume bar | A quantity that is never negative, such as volume |

Any other string is [OS3008](/script/errors/arguments#os3008).

The choice with a real reason behind it is `"step"`. A value that changes once a day and is drawn as a line slopes gently from yesterday's reading to today's, and every point along that slope is a price the script never read. The step says the truth: the value was this, then it became that. Use it for a higher timeframe value, a session's opening range, a stop held constant between trades, and anything else that is piecewise constant (flat for a stretch of bars, then a jump).

```openscript
// Read once per day. A sloping line would imply intraday readings that never existed.
plot(req.timeframe("1D", high), "Previous day high", aqua, width = 2, style = "step")
```

The six names are the same six the Style tab offers under Plot style, so the user can restyle any plot afterwards. Your choice is the sensible default, not a lock.

## Colour: one colour, or a colour per bar

**A constant colour and a per-bar colour are the same argument.** Pass a fixed colour and it becomes the plot's style colour, with no per-bar cost. Pass an expression that gives different colours on different bars and the compiler stores a colour beside each value.

```openscript
fast = ema(close, 9)
hist = macd(close, 12, 26, 9)[2]

plot(fast, "Fast", aqua)                                                     // one colour
plot(hist, "Histogram", color = hist > 0 ? lime : red, style = "histogram")  // a colour per bar
```

One argument covers both on purpose: a script that starts with one colour and later wants two should not have to switch functions, rename its plot and lose the user's saved styling.

Four things about the per-bar form are worth knowing:

- **The colour is only used where the value exists.** On a warmup bar the value is absent, the line is broken, and whatever the colour expression produced there is never drawn.
- **The colour is an ordinary expression, so absence reaches it too.** An absent condition takes the false branch of a ternary, so `x > 0 ? lime : red` gives red on bars where `x` is absent. When the look of those bars matters, test for absence explicitly with `isNone()` or `orElse()`.
- **An absent colour does not hide anything.** On a bar where the colour is `none` but the value exists, the bar is drawn in the plot's own colour. To hide a bar, make its value `none`.
- **A per-bar colour is drawn exactly as computed.** The colour setting in the Style tab does not change it. A colour held in a name counts as per bar too, even when the name holds a constant, so write a fixed colour in the call itself. [Colors](/script/visuals/colors#a-colour-per-bar) has the full rule.

Named colours, hex literals, `fade()`, `rgb()`, `mix()` and the rest are covered on [Colors](/script/visuals/colors).

## Width and visual weight

`width` defaults to `1.5`. Two plots at the same width read as equally important, which is the one rule worth following:

```openscript
basis = sma(close, 20)
upper = basis + 2 * stdev(close, 20)

plot(basis, "Basis", orange, width = 2)                  // the line that matters
plot(upper, "Upper", aqua)                               // supporting
plot(close[1], "Previous close", fade(silver, 55))       // reference
```

Thickness and transparency do different jobs. Thickness says "follow this". Transparency says "this is here if you need it". A study that reaches only for thickness ends up with five heavy lines and no hierarchy.

## Offsetting a plot

`offset` shifts **where the column is drawn, never what it contains**. A positive offset pushes the drawing right, into the empty space past the newest bar. A negative offset pushes it left, back over history. It must be a whole number ([OS3004](/script/errors/arguments#os3004) otherwise), written as a literal or passed as an input.

Keeping the values unshifted is the point: a later line of the script can still compare them with today's close without a correction a reader has to check. Two cases cover almost every use.

**A projection drawn into the future.** The cloud of `ichimoku()` is computed on the current bar and drawn 26 bars forward:

```openscript
i = ichimoku(9, 26, 52)

// Drawn 26 bars to the right. i[2] and i[3] themselves are untouched.
pA = plot(i[2], "Span A", lime, offset = 26)
pB = plot(i[3], "Span B", red,  offset = 26)
fill(pA, pB, colorUp = fade(lime, 88), colorDown = fade(red, 88))
```

**A value drawn back where it belongs.** A pivot is only known `right` bars after the bar it formed on, which is why `pivotHigh()` reports it there. To draw the mark on the pivot bar itself, shift the column left by the same amount:

```openscript
// A pivot with 5 bars each side is reported 5 bars late,
// so the drawing is pushed back 5 bars to where it formed.
ph = pivotHigh(high, 5, 5)
plot(ph, "Pivot high", orange, style = "lineWithMarkers", offset = -5)
```

This does not make the script know the pivot any earlier. The lag is real and stays real; `offset` only stops the picture lying about which bar the value belongs to.

If you want the shift as a setting, give the input the exact value to pass, such as `input(-5, "Shift")`. Arithmetic on an input inside the call, such as putting a minus sign in front of it, is refused, because the value must be fixed before the first bar.

## Pane and scale

Every plot lands in the pane the declaration chose. `overlay = true` on one plot moves that column to the price pane and leaves the rest of the study where it was, which is how a study with its own axis still puts one line where the trader is looking:

```openscript
version 1
study("Trend strength", precision = 2, range = [0, 100])

plot(adx(14, 14)[0], "ADX", purple, width = 2)
plot(ema(close, 20), "EMA 20", orange, width = 2, overlay = true)
```

Within a pane, `scale` picks the axis:

| `scale` | Means | Use it for |
|---|---|---|
| `"right"` | The right-hand price axis. The default | Everything, normally |
| `"left"` | The left-hand axis, which appears when a plot uses it | A second series in different units on the same pane |
| `"none"` | A hidden scale of its own, fitted to this column alone | A column whose size would otherwise flatten everything else on the pane |

A second axis is the honest answer when two series share no units. Volume is counted in shares and the close in rupees; on one axis, one of them would be a flat line at the edge of the pane:

```openscript
version 1
study("Close and volume", precision = 2)

// Declared first, so the columns sit under the line.
plot(volume, "Volume", fade(aqua, 50), style = "column", scale = "left", format = "volume")
plot(close, "Close", orange, width = 2)
```

The `format` on the volume plot formats the left axis only, which is exactly what a plot's own formatting is for. The next section explains.

## Formatting the numbers

Two options control how a plot's numbers read, and they work at two levels. On the declaration they set the formatting of a study with its own pane:

| Option | Default | Means |
|---|---|---|
| `precision` | `4` | Decimals on the study's axis and legend, a whole number from 0 to 10 |
| `format` | `"price"` | `"price"`, `"percent"` or `"volume"`: axis and legend formatting |

What each format shows:

| `format` | Reads as |
|---|---|
| `"price"` | The number with `precision` decimals |
| `"percent"` | The number with `precision` decimals and a percent sign after it |
| `"volume"` | Large numbers shortened, so 1250000 reads 1.25M. `precision` is not used |

```openscript
version 1
study("Volume and its average", format = "volume")

len = input(20, "Average length", min = 2, max = 500)

// Columns, because volume is never negative, coloured by the bar's direction.
plot(volume, "Volume", color = close > open ? fade(lime, 35) : fade(red, 35), style = "column")
plot(sma(volume, len), "Average", orange, width = 2)
```

On a plot, the same two names set the formatting of **the price scale the plot maps to**, not of that one line, because formatting belongs to an axis and an axis is shared by everything on it. In a study with its own pane that is useful for a second axis, as in the Close and volume example above.

Over the price pane leave them off. The axis there is the instrument's own, so in a study declared with `overlay = true` the compiler raises warning [OS8007](/script/errors/warnings#os8007) for any plot that sets either option, whichever scale the plot maps to. The declaration's own `precision` and `format` do not reach the price pane either: an overlay study keeps the instrument's formatting. When a reading needs its own format, draw it in a study with its own pane.

Formatting is display only. `format = "percent"` adds the sign and changes no number, so a change of one and a half percent must be computed as `1.5`, not `0.015`. `precision = 2` rounds nothing either: the numbers your script computed are the numbers it computed, and `precision` only decides how many digits a reader sees. To change the value itself, use `round()`.

## Candle plots

`plotCandles()` draws candles rather than a line: four source columns (open, high, low, close), one plot slot, and colours that split on whether the close is at or above the open.

| Argument | Default | Means |
|---|---|---|
| `open`, `high`, `low`, `close` | required | The four prices of each candle |
| `title` | required | The name in the Style tab |
| `colorUp` | `lime` | Body colour where the close is at or above the open |
| `colorDown` | `red` | Body colour where the close is below the open |
| `wickColor` | `none` | The wick colour. `none` gives each wick its body's colour |
| `borderColor` | `none` | The body outline colour. `none` gives each outline its body's colour |

The four colours may change from bar to bar. `plotCandles` has no `width`, `style` or `overlay` argument: its candles land in the study's pane.

Its everyday use is a candle built from your own prices. Here each candle is smoothed from the bar before it, which turns a choppy run of mixed candles into longer runs of one colour. It is drawn in a pane of its own so it is never mistaken for the real candles:

```openscript
version 1
study("Smoothed candles", precision = 2)

// The close is the average of the bar's four prices.
smoothClose = (open + high + low + close) / 4

// The open is the midpoint of the previous smoothed candle's body.
var smoothOpen = none
smoothOpen = isNone(smoothOpen) ? (open + close) / 2 : (smoothOpen + smoothClose[1]) / 2

// The wicks still reach the bar's real extremes.
smoothHigh = max(high, max(smoothOpen, smoothClose))
smoothLow  = min(low, min(smoothOpen, smoothClose))

plotCandles(smoothOpen, smoothHigh, smoothLow, smoothClose, "Smoothed")
```

On bars where the values are absent, such as during warmup, no candle is drawn, by the same gap rule as every other surface.

## Hiding a plot, and what absence draws

There is exactly one way to hide a plot on a bar: give it `none`.

```openscript
version 1
strategy("Entry and stop lines", overlay = true)

fast  = ema(close, 9)
slow  = ema(close, 21)
atr14 = atr(14)

if crossUp(fast, slow)
    buy()
if crossDown(fast, slow)
    close()

// pos.avgPrice is absent while the strategy is flat, and so is anything
// computed from it, so both lines exist only while a position is open.
plot(pos.avgPrice, "Entry", orange, style = "step")
plot(pos.avgPrice - 2 * atr14, "Stop", red)
```

The average true range is computed at the top level on every bar. Written inside a ternary or an `if` that runs only while a position is open, it would advance only on those bars and warn with [OS8001](/script/errors/warnings#os8001).

Absence is not a special case added for this. It is the same value a library function returns during [warmup](/script/language/warmup), that a division by zero produces, and that `close[1]` has on bar 0. A plot draws a gap wherever its value is absent, so every one of those cases gives a line that starts where the data starts and breaks where the data breaks, with no extra code from you. A plot whose value is absent on every bar can never draw anything, and the compiler warns about it with [OS8009](/script/errors/warnings#os8009).

A related pattern comes up whenever a line changes sides. A trailing stop that flips from below price to above price is two plots, each absent while the other is in use:

```openscript
version 1
study("Supertrend lines", overlay = true)

factor = input(3.0, "Factor", min = 0.5, max = 10)
atrLen = input(10, "ATR length", min = 1, max = 100)

st     = supertrend(factor, atrLen)
stLine = st[0]
dir    = st[1]

// direction is -1 while the trend is up and 1 while it is down.
plot(dir == -1 ? stLine : none, "Supertrend, up",   lime, width = 2)
plot(dir == 1  ? stLine : none, "Supertrend, down", red,  width = 2)
```

Here is the pattern on a BHEL 15 minute chart, from a Supertrend study with the same settings that also shades between the line and the candles and labels each flip BUY or SELL. The green line runs below price and the red one above it, and each flip is a clean break from one to the other:


Two plots rather than one line with a per-bar colour, because a single line that jumped from one side of price to the other would draw a vertical segment through the candles on the flip bar, at prices the stop never was.

## Markers and events

A plot draws a value on every bar. An event happens on one bar. Two tools cover the middle ground.

`style = "lineWithMarkers"` puts a mark on every value of an ordinary plot. Use it where the values are sparse, such as a pivot series that is absent on most bars, so the few values that exist read as points.

`signal()` puts one named marker on the bar it runs on, and it may appear anywhere in the file, including inside an `if`, because it is a per-bar event rather than a declared column:

```openscript
fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow)
    signal("BUY", at = "below", shape = "triangleUp")

if crossDown(fast, slow)
    signal("SELL", at = "above", shape = "triangleDown")
```

A marker's `at`, `shape` and `color` are fixed before the first bar, so each must be a literal or an input; only its text is read per bar. A signal also waits for the bar to close unless the declaration sets `onUnconfirmed = true`. [Labels and shapes](/script/visuals/labels-and-shapes) covers every position and shape.

## Plotting a function with several outputs

A function with more than one output returns an array holding this bar's outputs, in the order its reference entry lists. One call, one piece of state, and as many plots as you want to draw:

```openscript
version 1
study("MACD", precision = 2)

fastLen = input(12, "Fast",   min = 1, max = 200)
slowLen = input(26, "Slow",   min = 1, max = 400)
sigLen  = input(9,  "Signal", min = 1, max = 100)

// One call, shared by all three plots. Three separate calls would be three
// independent pieces of state doing the same smoothing three times per bar.
m = macd(close, fastLen, slowLen, sigLen)

level(0, "Zero", fade(gray, 55), style = "solid")

plot(m[2], "Histogram", color = m[2] > 0 ? lime : red, style = "histogram")
plot(m[0], "MACD",   aqua,   width = 2)
plot(m[1], "Signal", orange, width = 2)
```

The returned array is never absent and never changes length. Each element carries its own warmup and is `none` until it has a value, so `m[1]` is a legal read on bar 0 and simply has nothing there.

Note the order of the three plots: the histogram is declared first so that it sits under the two lines. Declaration order is the cheapest control you have over what covers what within one pane.

## Common mistakes

| Mistake | What happens | Fix |
|---|---|---|
| An `if` wrapped around a `plot` | [OS3006](/script/errors/arguments#os3006) | `plot(cond ? value : none, "Title")` |
| A stateful call such as `ema` inside the branch that uses it | Warning [OS8001](/script/errors/warnings#os8001), and a line with holes in it | Compute it at the top level and use the result in the branch |
| `width`, `style` or `offset` taken from a computed name | [OS3003](/script/errors/arguments#os3003) | Write a literal, or pass an `input()` |
| Two plots with the same title | [OS3017](/script/errors/arguments#os3017) | Give every plot its own title |
| `precision` or `format` on a plot in an overlay study | Warning [OS8007](/script/errors/warnings#os8007), and the instrument's axis is reformatted | Leave them off, or draw the reading in a study with its own pane |
| Expecting `format = "percent"` to multiply by 100 | A fraction such as 0.015 reads as 0.015 percent | Compute the percentage yourself |
| Hiding a bar by giving it a `none` colour | The bar is drawn in the plot's own colour | Give the value `none` instead |
| Expecting `offset` to change the values | It never does, and nothing warns | Shift the values yourself with `[]` if that is what you meant |
| A sloping line for a value that changes once a day | The picture implies readings that were never taken | `style = "step"` |
| Five lines all at `width = 2` | No hierarchy; a reader cannot tell what to follow | One heavy line, the rest at the default, references faded |

Related: [Visuals overview](/script/visuals/overview), [Fills](/script/visuals/fills), [Levels](/script/visuals/levels), [Colors](/script/visuals/colors), [Labels and shapes](/script/visuals/labels-and-shapes), [Plotting reference](/script/reference/plotting).


## Levels

Source: https://openalgo.in/script/visuals/levels

A **level** is one horizontal line straight across a pane at one price. It takes one call, costs no plot slot and keeps no history. This page covers when to use a level rather than a plot, how to compute a level from the data (the previous day's high on an intraday NIFTY chart, say), the one rule that explains everything surprising about levels, and the study options that fix a pane's range and control what its price axis says.

## A first example

A relative strength index (RSI) in its own pane, with its two thresholds and a midline:

```openscript
version 1
study("RSI with levels", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)

// Declared before the plot, as reference lines for the reading.
level(70, "Overbought", fade(red, 40))
level(50, "Middle", fade(gray, 70), style = "dotted")
level(30, "Oversold", fade(lime, 40))

plot(rsi(close, len), "RSI", purple, width = 2)
```


Each level is drawn as a line across the pane, with its title in a small plate at the left end of the line and its price tagged on the right-hand axis in the level's colour.

Three details are deliberate. The thresholds are faded, because they are reference and the RSI is data. The midline is dotted rather than dashed, one step further back, because it is a reference for the references. And `range = [0, 100]` pins the pane's scale, so 70 and 30 sit at the same height on every chart you open.

## What a level is, and what it is not

A plot draws a different value on every bar. A level draws one value across the whole pane. The difference is not cosmetic:

| | `level` | `plot` |
|---|---|---|
| What is drawn | One horizontal line across the pane | A column of one value per bar |
| Plot slot | None | One |
| Value in the legend | No | Yes |
| Group in the Style tab | No | Yes |
| History | None: you see one line, not where it used to be | Every bar's value is drawn at that bar |
| Price | Read every bar; the line is drawn at the last bar's value | Every bar's value is drawn at that bar |
| Can a `fill` name it | No | Yes |

A level is the right call for a threshold a reader needs to see everywhere on the pane: 70 and 30 on an oscillator, zero under a signed histogram, the previous day's high on a price chart. It is the wrong call for anything whose past positions are part of the picture.

## The call

`level()` takes a price and four optional arguments:

| Argument | Default | Means |
|---|---|---|
| `price` | required | The price to draw the line at. `none` draws nothing |
| `title` | `""` | The label at the left end of the line. No two levels in a file may share one |
| `color` | `gray` | The line colour |
| `style` | `"dashed"` | `"solid"`, `"dashed"` or `"dotted"` |
| `width` | `1` | Thickness |

Only `price` is read per bar. `title`, `color`, `style` and `width` are **fixed before the first bar**: write them in the call, as a literal or a colour built from literals such as `fade(red, 40)`, or pass an `input()` by its own name. A colour that changes from bar to bar is [OS3003](/script/errors/arguments#os3003), and so is a colour taken from a name you computed earlier:

```openscript
r = rsi(close, 14)
level(70, "Overbought", r > 70 ? red : gray)
```

A level whose colour the user can change takes a colour input straight into the call. To make that colour faded, put the fade in the input's default, because wrapping `fade()` around the input inside the call is refused:

```openscript
obColor = input(fade(red, 40), "Overbought line")
level(70, "Overbought", obColor)
```

`level` declares part of the study's fixed shape, so it is top level only, like `plot` and `fill`. Inside an `if` it is [OS3006](/script/errors/arguments#os3006). To make a level disappear, give it an absent price.

The default style is dashed on purpose. A reference line is not data. A solid line at 70 competes with the oscillator it is a reference for, and a reader has to work out which of the two lines is the reading. A dashed line reads as scaffolding at any distance, which is what it is.

## Levels computed from the data

A level's price does not have to be a constant. It can be any expression, and it is read again on every bar. Here the previous day's range is drawn on an intraday chart of an NSE stock or index future:

```openscript
version 1
study("Previous day range", overlay = true)

// The default "confirmed" mode reads only daily bars that have closed, so
// these are the previous day's numbers and they never repaint.
dayHigh = req.timeframe("1D", high)
dayLow  = req.timeframe("1D", low)
dayMid  = (dayHigh + dayLow) / 2

level(dayHigh, "Previous day high", fade(aqua, 25), width = 2)
level(dayMid,  "Previous day midpoint", fade(gray, 55), style = "dotted")
level(dayLow,  "Previous day low", fade(orange, 25), width = 2)
```

Three lines, no plot slots, and they move to the new day's numbers by themselves when the next session's first bar arrives. See [Higher timeframes](/script/data/higher-timeframes) for how `req.timeframe()` reads a coarser interval without repainting.

A level's price counts when the chart fits the pane's scale to what is on screen, so a level far from the current price stretches the pane to keep itself in view. That is usually what you want from the previous day's high; it is a reason not to draw levels at prices nobody needs to see.

### The rule that follows: the last bar wins

**A level's price is evaluated on every bar, and the line drawn is the one from the last bar the script ran on.**

Everything surprising about levels comes from that rule. A level has no history. There is one line, and its height is whatever the expression produced on the most recent bar. Scrolling back to last month does not show you where the level was last month; it shows the same line at today's height.

Two consequences follow, each with its fix.

**If the price is absent on the last bar, no line is drawn at all.** This obvious-looking line usually draws nothing, because only the day's first bar has a value and the last bar is rarely that one:

```openscript
// A new trading day: the first bar on the chart, or a bar on a different IST
// date from the bar before it.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

// Absent on every bar except the day's first, so usually nothing is drawn.
level(newDay ? open : none, "Day open", aqua)
```

The fix is to hold the value in a `var`, which keeps it from one bar to the next ([Persistence](/script/language/persistence)):

```openscript
version 1
study("Day open", overlay = true)

newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

var dayOpen = none

// Captured on the day's first bar (09:15 on NSE) and carried on every bar
// after it, so the level has a value on whichever bar turns out to be the last.
if newDay
    dayOpen = open

level(dayOpen, "Day open", fade(aqua, 25), style = "solid", width = 2)
```

The first bar of the day is found here by a change of date in IST, which is right for NSE, BSE and MCX because none of their sessions runs past midnight. The language's own `session.isFirstBar` needs the exchange's session hours, which the /trading chart does not supply in this release, so it has no value there. [Sessions and time](/script/data/sessions-and-time) explains both.

**If you want to see where the level used to be, it is not a level.** A value whose past matters is a column, and a column is a plot. Use `style = "step"` so the picture says "it was this, then it became that" rather than sloping between readings that never happened:

```openscript
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

var dayOpen = none
if newDay
    dayOpen = open

// Every past day's open, at the bars it applied to.
plot(dayOpen, "Day open", fade(aqua, 25), width = 2, style = "step")
```

Both forms are right; they answer different questions. The level answers "where is it now", from anywhere on the chart. The step plot answers "where was it then". A study that wants both draws both, and pays one plot slot for the second.

## Choosing between a level, a step plot and a drawing

| | `level` | `plot(..., style = "step")` | `draw.line` |
|---|---|---|---|
| Extent | The whole pane, edge to edge | The bars the value existed on | Between two anchored points, optionally extended |
| How many | Fixed when the script compiles, one per call | Fixed when the script compiles, one per call | As many as the script creates, at any time |
| Past positions | Not shown | Shown | Shown, and each one stays until deleted |
| Cost | None | One plot slot | Memory per object |
| Readable back | No | Yes, with `[]` | No |
| Good for | A threshold, today's reference prices, a zero line | A reference whose history matters | A trendline, a zone, a channel between two pivots |

In one sentence each: **a threshold is a level, a reference with a history is a step plot, and a geometric object between two points in time is a drawing.** Drawings are covered on [Lines and boxes](/script/visuals/lines-and-boxes).

## Fixing a pane's range

```openscript
version 1
study("RSI", precision = 2, range = [0, 100])

plot(rsi(close, 14), "RSI", purple, width = 2)
```

`range` takes `[min, max]` and pins the study pane's scale. It is a study option, so it applies to the whole pane. Write both bounds as plain numbers, low first. An input, arithmetic such as `50 + 50`, or a low that is not below the high is [OS3016](/script/errors/arguments#os3016). Negative bounds and decimals are fine.

Why pin it at all? An unpinned pane fits its scale to the values on screen and the levels in it, so the same reading sits at a different height on every chart and at every zoom, and a quiet month is stretched to fill the pane like a dramatic one. Pinning the range fixes the heights: 70 is always the same distance from the top, and a quiet month looks quiet, which is the whole value of a bounded oscillator.

| Reading | Pin it? | Why |
|---|---|---|
| A 0 to 100 oscillator, such as RSI or stochastic | Yes, `[0, 100]` | It has real bounds, and the thresholds have fixed meaning |
| A -100 to 100 oscillator | Yes | The same, and zero sits in the middle of the pane |
| A 0 to 1 position within a band | Yes, or a little wider | It has real bounds, and values outside them are themselves the signal |
| A trend strength reading that lives between 10 and 40 | Yes, to the useful part | `[0, 60]` uses the pane; `[0, 100]` wastes half of it |
| A difference of two moving averages | No | It has no bounds, and its scale changes with the instrument |
| Volume, a spread, an option premium | No | The useful scale changes from week to week |
| A percentage change | Usually not | The interesting range depends on the instrument and the interval |

Pinning a range is a promise that values outside it do not need to be read: the pane cuts them off at its edge. If the reading can leave the range and you still want to see it, do not pin it.

A pinned range pairs naturally with levels: the levels give the pane fixed landmarks, and the range keeps those landmarks at the same height.

```openscript
version 1
study("Position in the band", precision = 2, range = [-0.25, 1.25])

len  = input(20,  "Length", min = 2, max = 500)
mult = input(2.0, "Deviations", min = 0.1, max = 10)

// 0 at the lower band and 1 at the upper one. The range is widened a quarter
// each way, so a close outside the band is visible instead of cut off.
p = bbPercent(close, len, mult)

level(1,   "Upper band", fade(red, 40))
level(0,   "Lower band", fade(lime, 40))
level(0.5, "Basis", fade(gray, 70), style = "dotted")

plot(p, "Position", purple, width = 2)
```

## The price axis

Four settings decide what the axis beside a study's own pane says:

| Want | Set | Where |
|---|---|---|
| How many decimals | `precision`, a whole number from 0 to 10 | The declaration, or a plot for the scale it maps to |
| Whether it reads as a price, a percentage or a volume | `format`: `"price"`, `"percent"` or `"volume"` | The declaration, or a plot for the scale it maps to |
| Which side of the pane a column maps to | `scale`: `"right"`, `"left"` or `"none"` | Each plot. The declaration accepts `scale` too, but the /trading chart does not apply it in this release |
| The fixed extent of the scale | `range = [min, max]` | The declaration |

`"percent"` writes the number with a percent sign after it and changes nothing else. `"volume"` shortens large numbers, so 1250000 reads 1.25M, and does not use `precision`:

```openscript
version 1
study("Traded quantity", format = "volume")

plot(volume, "Volume", fade(aqua, 30), style = "column")
```

Set `precision` and `format` on the declaration whenever the whole pane reads the same way, which is almost always. Set on an individual `plot`, they format **the price scale that plot maps to**, not that one line, because formatting belongs to an axis and an axis is shared.

None of this reaches the price pane. There the axis is the instrument's own: an overlay study's declared `precision` and `format` are not applied to it, and in a study declared with `overlay = true` the compiler raises warning [OS8007](/script/errors/warnings#os8007) for any plot that sets either one. [Plots](/script/visuals/plots#formatting-the-numbers) has the details.

Formatting is display only. `format = "percent"` multiplies nothing by a hundred and `precision = 2` rounds nothing. Use `round()` when you want the value itself changed.

### Titles and axis tags

A level's `title` is its label, shown at the left end of the line. Give every level a title even when the height looks obvious, because a pane with four unlabelled dashed lines is a pane whose author knew what they meant and whose reader does not.

```openscript
level(70, "Overbought", fade(red, 40))       // labelled
level(30, color = fade(lime, 40))            // a line with no explanation
```

The right-hand axis carries tags the chart writes for you: each level's price, and the latest value of the plots on that axis, each in its own colour. When you want words beside a value, anchor a [label](/script/visuals/labels-and-shapes) to the newest bar and move it as new bars arrive:

```openscript
version 1
study("EMA with a tag", overlay = true)

e = ema(close, 20)

var tag = none

// Only on the newest bar: one label, created once and then moved, rather
// than a new label on every bar.
if bar.isLast
    if isNone(tag)
        tag = draw.label(time, e, "EMA 20 " + text(e, 2), color = fade(orange, 25), textColor = black)
    else
        draw.setAt(tag, time, e)
        draw.setText(tag, "EMA 20 " + text(e, 2))

plot(e, "EMA 20", orange, width = 2)
```

That is more work than a level, and it buys something neither a level nor a plot gives: a name and a value together, beside the newest bar where a trader is reading.

## Common reference lines

The ones almost every study wants:

| Line | Written |
|---|---|
| Zero, under a signed oscillator | `level(0, "Zero", fade(gray, 55), style = "solid")` |
| Oscillator thresholds | `level(70, "Overbought", fade(red, 40))` and `level(30, "Oversold", fade(lime, 40))` |
| A midline | `level(50, "Middle", fade(gray, 70), style = "dotted")` |
| The previous day's high and low | `level(req.timeframe("1D", high), "Previous day high", fade(aqua, 25))` |
| Today's open, held in a `var` | Capture on the day's first bar, then `level(dayOpen, "Day open")` |
| A price the user types | `myLine = input(0.0, "My line")`, then `level(myLine, "My line", fade(aqua, 25))` |
| A position's average price, in a strategy | `level(pos.avgPrice, "Entry", fade(silver, 30))` |

Write each `input()` on its own line at the top rather than nesting it inside the `level` call: every input is a row of the settings dialog, and a reader scanning the file wants all of them in one block.

The last row is a level whose price is absent some of the time, which is exactly right: `pos.avgPrice` is absent while the strategy is flat, an entry line means nothing then, and an absent price draws no line. A stop a set distance from the entry works the same way. Compute the average true range at the top level first, so its state advances on every bar:

```openscript
version 1
strategy("Entry and stop", overlay = true)

mult  = input(2.0, "Stop, in ATR", min = 0.5, max = 10)

fast  = ema(close, 9)
slow  = ema(close, 21)
atr14 = atr(14)

if crossUp(fast, slow)
    buy()
if crossDown(fast, slow)
    close()

// Drawn only while a position is open on the newest bar.
level(pos.avgPrice, "Entry", fade(silver, 30))
level(pos.avgPrice - mult * atr14, "Stop", fade(red, 30))
```

Because the last bar wins, these two lines show the trade that is open now, and nothing between trades. To see the entry of every past trade, plot `pos.avgPrice` with `style = "step"` instead.

## Common mistakes

| Mistake | What happens | Fix |
|---|---|---|
| Wrapping `level` in an `if` | [OS3006](/script/errors/arguments#os3006) | Give the price `none` on the bars where there is none |
| A colour that changes per bar | [OS3003](/script/errors/arguments#os3003) | A level has one colour. Use a plot for a line whose colour changes |
| `fade()` around a colour input inside the call | Refused when the script compiles | Put the fade in the input's default |
| A level whose price is set on one bar only | Usually nothing is drawn, because the last bar decides | Hold the value in a `var` |
| Expecting to scroll back and see where the level was | A level has no history | Use `plot(..., style = "step")` |
| Trying to `fill` between a plot and a level | A level is not a plot and cannot be one end of a fill | Plot the constant, fully transparent if it should not show. See [Fills](/script/visuals/fills#filling-between-a-plot-and-a-fixed-value) |
| An input or arithmetic in `range` | [OS3016](/script/errors/arguments#os3016) | Two plain numbers, low first |
| Pinning a range on an unbounded reading | The line leaves the pane and you cannot see it | Only pin a reading with real bounds |
| `precision` or `format` on a plot in an overlay study | Warning [OS8007](/script/errors/warnings#os8007) | Leave them off over the price pane |
| Solid reference lines as heavy as the data | The reader cannot tell the reference from the reading | Dashed or dotted, faded, thin |
| Levels with no titles | Nobody else can read the pane | Title every one |

Related: [Visuals overview](/script/visuals/overview), [Plots](/script/visuals/plots), [Fills](/script/visuals/fills), [Lines and boxes](/script/visuals/lines-and-boxes), [Declarations](/script/reference/declarations), [Plotting reference](/script/reference/plotting).


## Fills

Source: https://openalgo.in/script/visuals/fills

A **fill** paints the region between two plotted columns. It costs no plot slot of its own: it names two plots that already exist and tells the chart to colour the space between them. This page covers how to write one, how to give each side of a crossing its own colour, how strong to make the shading, how to make a band start and stop, and how to shade between a line and a fixed value.

## Why a shaded region reads better than two lines

Two lines on a chart ask the reader to do work. To know whether the fast average is above the slow one somewhere on the left of the screen, the reader has to find both lines at that point, tell which is which by colour, and compare their heights, again for every part of the chart they look at.

A shaded region between them does that work once and turns it into a colour. The band is green where the fast line leads and red where the slow one does, and the reader sees the answer the moment they see the chart. The width of the band carries a second fact for free: how far apart the two lines are.

The same argument applies to a volatility band, where the band's thickness is the reading and the edges are incidental, and to an oscillator shaded to its midline, where the shaded mass above and below is the story.

It does not apply to two unrelated series that happen to share a pane. Shading between them invents a quantity, the gap, that means nothing. Fill between two lines only when the region between them is itself a fact.

## A first example

Two exponential moving averages, the band between them coloured by which one leads, and a marker on each crossing:

```openscript
version 1
study("EMA cross, shaded", overlay = true)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)

fast = ema(close, fastLen)
slow = ema(close, slowLen)

pFast = plot(fast, "Fast", aqua,   width = 2)
pSlow = plot(slow, "Slow", orange, width = 2)

// pFast is the first argument, so colorUp is the colour where the FAST
// average is the higher of the two.
fill(pFast, pSlow, colorUp = fade(lime, 85), colorDown = fade(red, 85))

if crossUp(fast, slow)
    signal("BUY", at = "below", shape = "triangleUp")

if crossDown(fast, slow)
    signal("SELL", at = "above", shape = "triangleDown")
```

The same idea with 20 and 50 bar averages on a daily SBIN chart, with the crossings labelled Golden cross and Death cross in place of the triangles:


The band and the markers say the same thing at two distances. The markers give you the exact bar when you look closely. The band gives you the regime from across the room.

## The call

`fill()` takes two plot handles, then optional arguments by name:

| Argument | Default | Means |
|---|---|---|
| `plotA` | required | The first plot. "Up" is measured against this one |
| `plotB` | required | The second plot |
| `color` | `none` | One colour for the whole band |
| `colorUp` | `none` | The colour where `plotA` is at or above `plotB` |
| `colorDown` | `none` | The colour where `plotB` is above `plotA` |
| `opacity` | `1` | A dimmer over the colours, from 0 to 1 |
| `overlay` | the declaration's | `true` draws the band on the price pane |

With none of the three colours given, the band takes `plotA`'s colour at twelve percent strength, faint enough not to drown what is behind it, and it follows that plot if the user restyles it. `opacity` and `overlay` are fixed before the first bar, so write them as literals or pass an `input()`.

**Give a band fixed colours.** A colour counts as fixed when it is written in the call (a named colour, a hex literal, or a colour built from literals such as `fade(lime, 85)`) or when it is a colour input passed by its own name, which then follows the settings dialog. The language also accepts a colour worked out per bar, such as `cond ? orange : none` or a colour held in a name, but the /trading chart in this release draws each band in fixed colours and does not draw a per-bar one: such a band falls back to its first plot's colour at twelve percent. To switch a band on and off, use its ends, as [Where a fill stops](#where-a-fill-stops) shows.

`color` sets both sides at once, so giving it together with `colorUp` or `colorDown` is [OS3010](/script/errors/arguments#os3010):

```openscript
pFast = plot(ema(close, 9), "Fast", aqua)
pSlow = plot(ema(close, 21), "Slow", orange)
fill(pFast, pSlow, color = aqua, colorUp = lime)
```

`fill` declares part of the study's fixed shape, so it is top level only, like `plot` and `level`. Inside an `if` it is [OS3006](/script/errors/arguments#os3006). To make a band appear and disappear, give it absent ends on the bars where it should be off.

## A fill names plots, not series

The two positional arguments are **plot handles**, the value `plot()` returns. A band is written in three lines, not one:

```openscript
basis = sma(close, 20)
band  = 2 * stdev(close, 20)

pUpper = plot(basis + band, "Upper", aqua)
pLower = plot(basis - band, "Lower", aqua)
fill(pUpper, pLower, color = aqua, opacity = 0.08)
```

Passing a series instead is [OS3020](/script/errors/arguments#os3020), whose message says the argument must be a plot declared by `plot()` or `plotCandles()`:

```openscript
fast = ema(close, 9)
slow = ema(close, 21)
fill(fast, slow)
```

A fill has no values and no column of its own. The region is worked out, bar by bar, from the two columns the plots already carry. Three consequences follow:

- **A fill inherits its ends' warmup.** If both ends have no value for the first 20 bars, the band starts at bar 20 with no code from you. You never write a warmup guard for a fill.
- **A fill keeps no values of its own.** It adds nothing to the legend and has no group in the Style tab.
- **You cannot fill to something that is not a plot.** A level is not a plot, and neither is a bare number. The workaround is one line, covered in [Filling between a plot and a fixed value](#filling-between-a-plot-and-a-fixed-value).

A `plotCandles()` call returns a plot handle too. A band drawn to one follows its close column.

## One colour

The simplest band: one colour, one opacity.

```openscript
version 1
study("Bollinger band", overlay = true)

len  = input(20,  "Length", min = 2, max = 500)
mult = input(2.0, "Deviations", min = 0.1, max = 10)

b = bollinger(close, len, mult)

pUpper = plot(b[1], "Upper", fade(aqua, 45))
pLower = plot(b[2], "Lower", fade(aqua, 45))
plot(b[0], "Basis", orange, width = 2)

// The band's thickness is the reading, so the shading carries it and the two
// edges are faded out of the way.
fill(pUpper, pLower, color = aqua, opacity = 0.07)
```

Note the hierarchy: the basis is the heavy line, the edges are faint, and the shading is fainter still. A band drawn with three equally strong elements has three things competing for attention where there is only one reading.

## Two colours, for which side leads

`colorUp` and `colorDown` replace `color` when the two ends cross each other.

**`colorUp` is the colour where the first plot is above the second.** That is the only thing to remember, and it is worth a comment in the script the first few times, because a band that is green on the wrong side says the opposite of what you meant and looks entirely plausible while doing it. The chart splits the band exactly where the two lines cross, so the colours change at the crossing rather than a bar late.

The same idea works in a study with its own pane. Here the moving average convergence divergence (MACD) line and its signal line are shaded by which one leads:

```openscript
version 1
study("MACD, shaded", precision = 2)

m = macd(close, 12, 26, 9)

pMacd   = plot(m[0], "MACD",   aqua,   width = 2)
pSignal = plot(m[1], "Signal", orange, width = 2)

// pMacd is the first argument, so colorUp is the colour where the MACD line
// is above its signal line.
fill(pMacd, pSignal, colorUp = fade(lime, 80), colorDown = fade(red, 80))
```

## Opacity

Two places can make a fill see-through, and they do different jobs:

| Where | Range | What it is for |
|---|---|---|
| `opacity` | 0 to 1, where 1 is solid | A dimmer over the colours. The default is 1, so it changes nothing until you set it |
| The colour itself, through `fade()` or `rgba()` | `fade` takes 0 to 100 transparency, `rgba` takes 0 to 1 alpha | How see-through this particular colour is |

**Pick one and leave the other alone.** A band faded twice is a band nobody can see, and the next person to open the script cannot tell which of the two numbers to change. For a band with one colour, pass a plain colour and set `opacity`. For a band with `colorUp` and `colorDown`, put the transparency in the colours, so the two sides can differ, and leave `opacity` at its default.

Whichever you use, keep a band faint. The chart paints bands behind the candles and lines, so a band never hides them, but a strong one drowns their colours and the chart becomes hard to read. The default for a band with no colour, twelve percent, is a good guide: stay near a tenth of solid, and if you find yourself going much past a fifth, check whether what you want is really a [background](/script/visuals/bar-coloring-and-backgrounds) wash rather than a band.

## Where a fill stops

A fill stops wherever either of its ends has no value, and resumes where both come back. That is the same gap rule that breaks a line, applied to a region, and it has three everyday uses.

**Warmup.** Neither end exists yet, so the band starts where the data does. No code.

**A band that appears and disappears with its edges.** Make the ends absent on the bars where the band should be off. Here the opening range of an NSE session is built during its first fifteen minutes and then drawn, edges and shading together, for the rest of the day:

```openscript
version 1
study("Opening range band", overlay = true)

// A new trading day: the first bar on the chart, or a bar on a different IST
// date from the bar before it.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

// Holds for bars that open from 09:15 up to, but not including, 09:30.
forming = session.isIn("0915-0930")

var rangeHigh = none
var rangeLow  = none

if newDay
    rangeHigh = high
    rangeLow  = low
else if forming
    rangeHigh = max(rangeHigh, high)
    rangeLow  = min(rangeLow, low)

// While the range is forming both ends are absent, so the two lines and the
// band between them start only once it is complete. No guard is written.
pHigh = plot(forming ? none : rangeHigh, "Range high", aqua,   style = "step")
pLow  = plot(forming ? none : rangeLow,  "Range low",  orange, style = "step")

fill(pHigh, pLow, color = aqua, opacity = 0.09)
```

The day is found by a change of IST date because the /trading chart does not supply the exchange's session hours in this release, so `session.isFirstBar` has no value there. [Sessions and time](/script/data/sessions-and-time) explains why the date test is right for Indian exchanges.

**A band that switches off while its visible edges stay.** Draw the edges as ordinary plots, then give the band a second pair of ends: invisible copies of the edges that are absent whenever the band should be off. The lines carry straight on, and the shading appears only where both copies have values:

```openscript
version 1
study("Squeeze", overlay = true)

len = input(20, "Length", min = 2, max = 500)

b = bollinger(close, len, 2)
k = keltner(len, 1.5, len, "ema")

// The deviation band has contracted inside the range band: price is coiled.
// That is a state, so it is shading rather than another line.
squeezed = b[1] < k[1] and b[2] > k[2]

// The two lines are always drawn.
plot(b[1], "Upper", fade(aqua, 40))
plot(b[2], "Lower", fade(aqua, 40))

// Invisible copies that exist only during a squeeze. The band is drawn
// between them, so it switches on and off while the lines above carry on.
qUpper = plot(squeezed ? b[1] : none, "Squeeze upper", fade(orange, 100))
qLower = plot(squeezed ? b[2] : none, "Squeeze lower", fade(orange, 100))

fill(qUpper, qLower, color = orange, opacity = 0.18)
```

During warmup the comparison has no value and the ternary takes its false branch, so the copies are absent and no band is drawn, which is the right picture.

## Filling between a plot and a fixed value

`level()` draws a horizontal line, but it is not a plot and has no column of values, so it cannot be one end of a fill. There is no argument that makes it one.

The fix is one line: **plot the constant as a second column, and make it invisible if you do not want the line.** A number used where a series is expected is repeated on every bar, so `plot(50, ...)` is a legal, constant column.

```openscript
version 1
study("RSI, shaded to the midline", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)

r = rsi(close, len)

level(70, "Overbought", fade(red, 40))
level(30, "Oversold",   fade(lime, 40))

pOsc = plot(r, "RSI", purple, width = 2)

// A level cannot be one end of a fill, so the midline is plotted instead.
// fade takes transparency and 100 is invisible, so it draws no line.
pMid = plot(50, "Midline", fade(gray, 100))

fill(pOsc, pMid, colorUp = fade(lime, 86), colorDown = fade(red, 86))
```

This reads better than three lines at 70, 50 and 30 with nothing between them, because the shaded mass above and below the midline is exactly what the oscillator measures.

Plotting the constant costs one line and keeps every option open. An invisible plot draws nothing and the legend leaves out its value, and when the "constant" later needs to move you change one expression. The same technique shades a line to any moving reference: a session open, the previous close, an entry price. Plot the reference, fade it as far as you want, and fill to it, naming the band's colour yourself: a band with no colour takes its first plot's colour, and an invisible plot's colour is invisible.

## Which pane

A fill lands in the pane the declaration chose, and `overlay = true` moves it to the price pane. The one rule to respect is that **a fill and the two plots it names must end up in the same pane**. The compiler does not check this. A band in a different pane from its plots is drawn against that pane's price scale, away from the lines it was meant to join.

So when you move the plots onto the price pane, move the fill with them. Here a study keeps a reading in its own pane and draws the cloud of `ichimoku()` over price:

```openscript
version 1
study("Cloud width", precision = 2)

i = ichimoku(9, 26, 52)

// In the study's own pane: how thick the cloud is, and which span leads.
width = i[2] - i[3]
plot(width, "Cloud width", color = width > 0 ? lime : red, style = "histogram", offset = 26)

// On the price pane: the cloud itself. Both plots and the fill carry overlay.
pA = plot(i[2], "Span A", lime, offset = 26, overlay = true)
pB = plot(i[3], "Span B", red,  offset = 26, overlay = true)
fill(pA, pB, colorUp = fade(lime, 88), colorDown = fade(red, 88), overlay = true)
```

`fill` has no `offset` of its own. The band is shifted by its first plot's offset, so a band drawn 26 bars forward gives both of its plots `offset = 26` and the region moves with them.

## Putting it together: a shaded Supertrend

A trailing stop from `supertrend()`, shaded between the stop and the middle of each candle, with a marker where the trend flips. The shading makes the regime readable at a glance, and it stops and restarts on its own at each flip because one end of each band is absent on the other side.

```openscript
version 1
study("Supertrend, shaded", overlay = true)

factor = input(3.0, "Factor", min = 0.5, max = 10)
atrLen = input(10, "ATR length", min = 1, max = 100)

st     = supertrend(factor, atrLen)
stLine = st[0]
dir    = st[1]

// The middle of each candle, as an invisible column the bands can reach.
pMid = plot(hl2, "Candle midpoint", fade(gray, 100))

// direction is -1 while the trend is up and 1 while it is down.
pUp   = plot(dir == -1 ? stLine : none, "Supertrend, up",   lime, width = 2)
pDown = plot(dir == 1  ? stLine : none, "Supertrend, down", red,  width = 2)

// Colours named here, because the first plot of each band is invisible.
fill(pMid, pUp,   color = fade(lime, 88))
fill(pMid, pDown, color = fade(red, 88))

if dir == -1 and dir[1] == 1
    signal("BUY", lime, at = "below")

if dir == 1 and dir[1] == -1
    signal("SELL", red, at = "above")
```

The same picture on a BHEL 15 minute chart, from a Supertrend study with a 10 bar ATR and a factor of 3 that shades to the middle of each candle's body rather than to `hl2`. The band is green below price while the trend is up and red above it while the trend is down, and it breaks off at each flip:


## Recipes

| Picture | How |
|---|---|
| A volatility band | Two plots for the edges, then `fill(a, b, color = c, opacity = 0.07)` |
| A cross, shaded by which side leads | `fill(a, b, colorUp = ..., colorDown = ...)` |
| An oscillator shaded to its midline | Plot the midline at `fade(colour, 100)`, then fill to it with a named colour |
| A band only while a condition holds | Invisible copies of the edges that are `none` while the condition is off, and the fill between the copies |
| A band that appears and disappears with its edges | Make the ends `none` on the off bars |
| The shading without the edges | Plot both edges at `fade(colour, 100)` and name the fill's colour |
| A displaced cloud | Both plots carry the same `offset` |
| A band on the price pane from a study with its own pane | Both plots and the fill carry `overlay = true` |

## Common mistakes

| Mistake | What happens | Fix |
|---|---|---|
| `fill(fast, slow)` with series rather than handles | [OS3020](/script/errors/arguments#os3020) | Assign the plots to names and pass those |
| `color` together with `colorUp` or `colorDown` | [OS3010](/script/errors/arguments#os3010) | Use `color` alone, or the two sides alone |
| `colorUp` on the wrong side | A picture that says the opposite, convincingly | `colorUp` is where the **first** argument is above the second |
| A band colour worked out per bar, or held in a name | The /trading chart draws the first plot's colour at twelve percent instead | Write the colour in the call, or pass a colour input by its own name |
| No colour, and an invisible first plot | An invisible band | Name the band's colour |
| `fade` on the colour and a low `opacity` as well | A band nobody can see, and two numbers to guess between | Use one or the other |
| Wrapping `fill` in an `if` | [OS3006](/script/errors/arguments#os3006) | Make the ends `none` on the off bars |
| Filling to a `level` | A level is not a plot | Plot the constant, fully transparent if it should not show |
| The fill in one pane and its plots in another | No compiler message; the band lands away from its lines | Give both plots and the fill the same `overlay` |
| Shading between two unrelated series | Invents a quantity that means nothing | Only fill where the gap is itself a fact |

Related: [Visuals overview](/script/visuals/overview), [Plots](/script/visuals/plots), [Levels](/script/visuals/levels), [Colors](/script/visuals/colors), [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds), [Lines and boxes](/script/visuals/lines-and-boxes), [Plotting reference](/script/reference/plotting).


## Colors

Source: https://openalgo.in/script/visuals/colors

Every line, band, marker and shaded bar a script draws takes a colour. This page covers how to name one, build one from channels, make it transparent by the right amount for the surface it lands on, vary it with a value or from bar to bar, and choose a palette that stays readable whether the reader's chart is light or dark. Colour carries meaning on a chart (up or down, calm or stretched), so a few careful choices here make a study far easier to read.

## A first example

A relative strength index (RSI) whose line changes colour with its zone, with a faint wash behind the pane while it is overbought:

```openscript
version 1
study("RSI zones", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)

r = rsi(close, len)

// Three states, and none while the RSI has no value yet.
zoneColor = isNone(r) ? none : (r > 70 ? red : (r < 30 ? lime : purple))

level(70, "Overbought", fade(red, 40))
level(30, "Oversold", fade(lime, 40))

plot(r, "RSI", color = zoneColor, width = 2)
background(r > 70 ? fade(red, 92) : none)
```

Four colour ideas are in those lines: named colours (`red`, `lime`, `purple`), transparency with `fade`, a colour that changes per bar, and `none` meaning "paint nothing here" for the background. The rest of the page takes them one at a time.

## Named colours

Nineteen names are built in, written bare with no prefix. Each is fully opaque, and its channels are fixed by the language, so `aqua` is the same colour on every engine that runs your script. That is the reason to prefer a name over a hex value you half remember.

| Name | Red, green, blue | Hex |
|---|---|---|
| `aqua` | 0, 255, 255 | `#00ffff` |
| `black` | 0, 0, 0 | `#000000` |
| `blue` | 0, 0, 255 | `#0000ff` |
| `brown` | 165, 42, 42 | `#a52a2a` |
| `fuchsia` | 255, 0, 255 | `#ff00ff` |
| `gray` | 128, 128, 128 | `#808080` |
| `green` | 0, 128, 0 | `#008000` |
| `lime` | 0, 255, 0 | `#00ff00` |
| `maroon` | 128, 0, 0 | `#800000` |
| `navy` | 0, 0, 128 | `#000080` |
| `olive` | 128, 128, 0 | `#808000` |
| `orange` | 255, 165, 0 | `#ffa500` |
| `pink` | 255, 192, 203 | `#ffc0cb` |
| `purple` | 128, 0, 128 | `#800080` |
| `red` | 255, 0, 0 | `#ff0000` |
| `silver` | 192, 192, 192 | `#c0c0c0` |
| `teal` | 0, 128, 128 | `#008080` |
| `white` | 255, 255, 255 | `#ffffff` |
| `yellow` | 255, 255, 0 | `#ffff00` |

The names are ordinary built-in values of type `color`, not keywords. Assigning to one is [OS2002](/script/errors/names-and-types#os2002), the same error as redeclaring any other name that already exists:

```openscript
aqua = #00e5ff
```

## Hex literals

A colour can also be written in hex, with six digits or with eight, where the last two are alpha (how opaque the colour is):

```openscript
plot(ema(close, 9), "Fast", #ff8800)       // six digits, fully opaque
plot(ema(close, 21), "Slow", #ff880080)    // eight digits: the last two are alpha
```

A hex literal becomes four numbers when the script compiles: red, green and blue from 0 to 255, and alpha from 0 to 1. The alpha byte is divided by 255, so `80` (128) is an alpha of about 0.5. The three-digit shorthand, such as `#f80`, is not a colour literal and does not compile.

Two colours are equal when all four channels match. `aqua == aqua` is true, and a faded colour is not equal to the colour it came from.

## Building a colour

| Call | Returns | For |
|---|---|---|
| `rgb()` `(r, g, b)` | `color` | Channels 0 to 255, fully opaque |
| `rgba()` `(r, g, b, a)` | `color` | The same, with alpha from 0 to 1, where 1 is opaque |
| `fade()` `(color, percent)` | `color` | The same colour at `percent` **transparency**, where 100 is invisible |
| `withAlpha()` `(color, a)` | `color` | The same colour at a stated alpha, 0 to 1, where 1 is opaque |
| `mix()` `(a, b, weight)` | `color` | A blend of two colours: weight 0 gives `a` and 1 gives `b` |
| `alpha()` `(color)` | `number` | Read a colour's alpha back, 0 to 1 |

```openscript
plot(sma(close, 20), "Basis", rgb(255, 136, 0), width = 2)
plot(sma(close, 50), "Slow", rgba(0, 150, 255, 0.6))
```

Two more are named in the language and not available yet: `hsl()`, for hue, saturation and lightness, and `gradient()`, for positioning a value between two colours. Calling either is [OS2020](/script/errors/names-and-types#os2020) in version 0.5.0:

```openscript
plot(close, "Close", hsl(200, 80, 50))
```

Until they arrive, `mix()` with a weight you compute yourself does the work of `gradient`, as shown in [Colour that follows a value](#colour-that-follows-a-value).

### Channels out of range

A channel written as a number that is out of range, or not a whole number, is refused when the script compiles, with [OS3004](/script/errors/arguments#os3004):

```openscript
plot(close, "Close", rgb(300, 0, 0))
```

A channel computed from data is a different case. It is meant to raise runtime error [OS4009](/script/errors/runtime#os4009), because a colour computed from data that lands at 300 is a bug in the computation. In version 0.5.0 nothing raises OS4009 yet: the engine rounds the channel to a whole number, clamps it to 0 to 255, and the bar carries on. Do not rely on that. Where a computed channel can run past its end, clamp it where you compute it with `clamp()`, so a reader can see the decision:

```openscript
// How far the close sits above its 20 bar low, in average true ranges,
// turned into a red channel from 0 to 255.
stretch = (close - lowest(low, 20)) / atr(14)
redness = clamp(stretch * 64, 0, 255)

plot(close, "Close", rgb(redness, 80, 80))
```

## Transparency, and its two conventions

This is the one part of colour that catches everybody once.

**`fade` takes transparency. `withAlpha` takes opacity. They run in opposite directions.**

| Call | 0 means | 100 or 1 means |
|---|---|---|
| `fade(color, percent)` | Fully opaque | Invisible |
| `withAlpha(color, a)` | Invisible | Fully opaque |

For an opaque colour the two describe the same thing: `fade(aqua, 60)` and `withAlpha(aqua, 0.4)` are both aqua at 40 percent strength. Most scripts use `fade`. `withAlpha` suits a script that computes an opacity from data, because it takes that number as it is.

```openscript
plot(sma(close, 20), "Faded 60 percent", fade(aqua, 60))
plot(sma(close, 50), "Alpha 0.4", withAlpha(aqua, 0.4))
```

The Opacity control in the settings dialog's Style tab runs the way `withAlpha` does, from 0 (invisible) to 100 (solid), so a user who sets it to 40 sees roughly what `fade(c, 60)` gives. Keep the two directions straight when you tell a user what to change.

Apply `fade` once, to an opaque colour such as a named one. Fading a colour that is already transparent gives different answers in version 0.5.0 depending on where the colour is worked out: in a colour fixed before the first bar the outer fade replaces the inner one, and in a colour computed on a bar the two multiply. To give an exact alpha to a colour that already carries some transparency, use `withAlpha`, which always sets the alpha outright.

The right amount of transparency depends entirely on what the colour lands on:

| Surface | Typical | Why |
|---|---|---|
| A plotted line | Opaque, or `fade(c, 20)` for a secondary line | A line is thin: transparency mostly costs legibility |
| A fill between two plots | `fade(c, 85)` to `fade(c, 95)` | It covers a large area around the candles |
| A box fill | `opacity = 0.08` to `0.15` on `draw.box` (its default is 0.12) | The same, and boxes often overlap each other |
| A pane background | `fade(c, 90)` or more | It covers the full height of the bar |
| A table background | `fade(black, 25)` | It sits over the chart and should still let it through |
| A label plate | Opaque | The text has to be readable against it |

The rule behind the table: **the bigger the area, the more transparent it must be.** A fill at 50 percent transparency looks reasonable in a screenshot of twenty bars and turns the chart into a wash at two hundred.

## Colour that follows a value

`mix()` places a colour between two others. The weight is yours to compute, which means it is yours to scale and yours to bound:

```openscript
version 1
study("Volume heat", overlay = true)

lookback = input(20, "Average over", min = 2, max = 500)
hottest  = input(3.0, "Ratio that counts as hot", min = 1.5, max = 10)

ratio = volume / sma(volume, lookback)

// A weight is a position between two colours, so it is clamped to 0 to 1.
weight = isNone(ratio) ? none : clamp((ratio - 1) / (hottest - 1), 0, 1)

heat = isNone(weight) ? none : mix(fade(aqua, 40), red, weight)

barColor(heat)
```

Three steps in that script apply to every colour scale you will write:

1. **Scale first.** A weight is a fraction of the way between two readings, so divide the raw value by the range you consider meaningful. Here that range is an input, because "hot" is a judgement.
2. **Clamp second.** A ratio has no upper bound and a weight does. `mix` does not refuse a weight outside 0 to 1: it carries on past the far colour until the channels hit their limits, which is rarely the colour you meant.
3. **Handle absence third.** `barColor(none)` leaves the candle its own colour, which is the right picture during warmup: the study says nothing about bars it knows nothing about.

`mix` blends alpha as well as the three channels, so the blend above runs from aqua at 60 percent strength to solid red. It rounds each channel to a whole number, so every colour a script computes has whole channels, just like a literal.

For a two-sided scale, blend from the middle outwards rather than end to end:

```openscript
strength = rsi(close, 14)
weight   = isNone(strength) ? none : clamp(abs(strength - 50) / 50, 0, 1)
tint     = isNone(weight) ? silver : mix(silver, strength > 50 ? lime : red, weight)

plot(close, "Close", tint, width = 2)
```

That reads correctly at 50, where both sides are grey. A blend straight from lime to red would put olive, `mix(lime, red, 0.5)`, at exactly the value the reader cares about most.

## A colour per bar

**A constant colour and a per-bar colour are the same argument.** Pass a fixed colour and it becomes the plot's style colour. Pass an expression that gives a colour each bar and the compiler stores one beside each value:

```openscript
m = macd(close, 12, 26, 9)
plot(m[2], "Histogram", color = m[2] > 0 ? lime : red, style = "histogram")
```

One argument covers both because a script that starts with one colour and later wants two should not have to move to a different function. The cost is worth knowing: a constant colour costs nothing per bar, and a computed one costs one more value per bar.

**What counts as fixed.** A colour is fixed only when it is written in the call itself: a named colour, a hex literal, a colour built from literals such as `fade(red, 40)`, or a colour input passed by its own name. Everything else is treated as a colour per bar, including a name you assigned a constant colour to, and `fade()` applied to an input.

Not every surface takes a per-bar colour:

| Surface | A fixed colour | A colour per bar |
|---|---|---|
| `plot(value, title, color)` | The plot's style colour, which the user can change in the Style tab | Yes. It is drawn as computed, and the Style tab no longer changes it |
| `plotCandles(..., colorUp, colorDown, wickColor, borderColor)` | The candles' colours | Yes |
| `barColor(color)` | Every bar the same | Yes, the usual case |
| `background(color)` | Every bar the same | Yes, the usual case |
| `cell(..., textColor, bgColor)` | The cell's colours | Yes: they are read each time the cell is written |
| `fill(a, b, color, colorUp, colorDown)` | The band's colours | Accepted, but the /trading chart does not draw it in this release: the band takes its first plot's colour at twelve percent instead |
| `level(price, title, color)` | The line's colour | No: [OS3003](/script/errors/arguments#os3003) |
| `signal(text, color)` | The marker's colour | No: [OS3003](/script/errors/arguments#os3003) |
| `table(..., textColor, bgColor)` | The grid's colours | No: [OS3003](/script/errors/arguments#os3003) |

A colour taken from a name you computed, even from constants, counts as per bar, so it is [OS3003](/script/errors/arguments#os3003) where a fixed one is required:

```openscript
OVERBOUGHT = fade(red, 40)
level(70, "Overbought", OVERBOUGHT)
```

Write `fade(red, 40)` in the `level` call instead, or declare a colour input with that default.

## An absent colour

`none` is a valid colour, and passing it is never an error. What it does depends on the surface:

| Surface | An absent colour on a bar |
|---|---|
| `barColor` | The candle keeps its own colour |
| `background` | The bar's column is not shaded |
| `plot` | The bar is drawn in the plot's own colour from the Style tab. To hide a plot on a bar, make its value `none`, not its colour |
| `fill` | `none` means the band has no colour of its own, so it takes its first plot's colour at twelve percent |

For the two paint calls this is how a conditional paint switches itself off, so a script never needs a separate call to clear one:

```openscript
paint = input(true, "Recolour the candles")
up    = close > open
risky = atr(14) > 2 * atr(100)

barColor(paint ? (up ? lime : red) : none)     // the input switches it off
background(risky ? fade(red, 92) : none)       // only risky bars are shaded
```

Watch the interaction with absent conditions, because it is where a script tells a lie without meaning to. An absent condition takes the false branch, so `trendUp ? lime : red` paints every warmup bar red, as if the trend were down. What you want is three states:

```openscript
basis   = ema(close, 50)
trendUp = close > basis

tint = isNone(basis) ? none : (trendUp ? lime : red)
barColor(tint)
```

Absent for "I do not know yet", lime for up, red for down. The chart then shows its own candle colours across the warmup, which is the truth. [Absent values](/script/language/absent-values) explains the rules behind this.

## Colours for light and dark charts

**A script cannot read the chart's theme.** Nothing in the `chart` namespace reports whether the background is light or dark, so the colours you pick have to work on both. A study that only looks right on its author's theme is a study half its readers will restyle or discard.

| Purpose | A choice that works on both | Why |
|---|---|---|
| A primary line | `aqua`, `orange`, `purple`, `fuchsia` | Mid-tone, saturated hues stand out against both a near-white and a near-black background |
| A secondary line | `fade(silver, 50)` or `gray` | Grey reads as secondary on both |
| A line meant to recede | Transparency, not a near-background colour | No single colour disappears into both themes |
| Up and down | `lime` and `red`, plus a difference that is not colour | Colour alone fails a reader who cannot separate red and green |
| Text on a coloured plate | A text colour chosen for the plate: `white` on `red` or `navy`, `black` on `lime` or `yellow` | The plate is the background for that text, and it does not change with the theme |
| A fill or a background | The colour at 85 to 95 percent transparency | The theme's own background shows through and does the work |

Be careful with `white` and `black`. Each is invisible on one of the two themes, so neither belongs on a line, a marker or a drawing where it is the only carrier of meaning. They are fine as the text colour on a plate whose colour you chose, because there the background is the plate rather than the chart.

"A difference that is not colour" means giving two things a second way to tell them apart: a solid line against a dashed one, a triangle up against a triangle down, a marker above the bar against one below it. Then the picture still works for a reader who sees your lime and your red as the same grey.

## Letting the user choose

The Style tab of the settings dialog gives every plot its own colour, opacity, thickness, line style and plot style, whether or not the script asks, so a reader can always restyle a fixed-colour plot. Where one colour is central to the study, declare it as an input and pass it through **by its own name**:

```openscript
version 1
study("Bands", overlay = true)

len       = input(20,   "Length", min = 2, max = 500)
mult      = input(2.0,  "Width, in deviations", min = 0.5, max = 5)
bandColor = input(aqua, "Band colour")

b = bollinger(close, len, mult)

// Every colour here is the input itself, so one setting restyles the three
// lines and the band together.
plot(b[0], "Basis", bandColor, width = 2)
upper = plot(b[1], "Upper", bandColor)
lower = plot(b[2], "Lower", bandColor)

fill(upper, lower, color = bandColor, opacity = 0.1)
```

When a plot's colour is a colour input passed by its own name, the plot's colour in the Style tab and the input are one setting, so the user has one value to change rather than two that disagree. A band given the input follows it too, and `opacity` keeps it faint whatever colour the user picks.

Two things break that link, because each turns the colour into a per-bar colour: copying the input into another name (`LINE = bandColor`) and fading it in the call (`fade(bandColor, 45)`). A plot still draws such a colour, but the Style tab no longer changes it; a band in the /trading chart falls back to its first plot's colour; and a `level` or `signal` refuses it. When you want a faded version the user can change, declare it as its own input with a faded default, such as `input(fade(aqua, 45), "Edge colour")`.

See [Inputs](/script/inputs/inputs) for colour inputs and [Settings and style](/script/inputs/settings-and-style) for the rows the settings dialog generates.

## Common mistakes

| Symptom | Cause | Fix |
|---|---|---|
| A fill drowns the candles' colours | Transparency too low | `fade(c, 88)` or higher for a band |
| Nothing is drawn at all | `fade(c, 100)` | 100 is invisible; `fade` takes transparency, not opacity |
| A line hidden with a `none` colour still shows | On a plot, an absent colour falls back to the plot's own colour | Make the value `none` instead |
| Every warmup bar is painted the "down" colour | An absent condition taking the false branch | `isNone(x) ? none : (cond ? upColor : downColor)` |
| [OS3004](/script/errors/arguments#os3004) on an `rgb` call | A channel literal outside 0 to 255, or not whole | Fix the number |
| A computed channel gives an unexpected colour | The channel ran past its range | `clamp` it where you compute it |
| [OS3003](/script/errors/arguments#os3003) on a `level` or `signal` colour | The colour was computed, or taken from a name | Write it in the call, or pass an input by its own name |
| A band ignores the colour you computed for it | The /trading chart does not draw a per-bar band colour | Give the band fixed colours |
| The Style tab does not change a plot's colour | The colour is computed per bar, or held in another name | Pass the colour input straight to the plot |
| [OS2020](/script/errors/names-and-types#os2020) on `hsl` or `gradient` | Both are planned, not yet available | Use `rgb`, or `mix` with a computed weight |
| The study is invisible on a dark chart | `black` used as a line colour | `gray` or `silver`, or a mid-tone hue |
| Label text cannot be read | Text colour picked for the theme rather than the plate | Choose the text colour from the plate colour |
| Two series look identical to some readers | Colour is the only difference | Add a line style, a shape or a position difference |
| A colour scale is muddy in the middle | Blending end to end across a neutral midpoint | Blend from the middle outwards on each side |

Related: [Visuals overview](/script/visuals/overview), [Plots](/script/visuals/plots), [Fills](/script/visuals/fills), [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds), [Labels and shapes](/script/visuals/labels-and-shapes), [Tables](/script/visuals/tables), [Colors reference](/script/reference/color).


## Bar colouring and backgrounds

Source: https://openalgo.in/script/visuals/bar-coloring-and-backgrounds

Two calls paint bars rather than values. `barColor()` recolours the
instrument's own candles, and `background()` shades the full height of a
bar's column behind everything else in the pane. Reach for them when the thing
you want to show is a state the bar is in, such as a trend direction, a
volatility regime or the first fifteen minutes of the NSE session, rather than
a number with a price to sit at.

This page covers both calls, how to switch a paint off on the bars that do not
matter, which study owns the candles when several want them, and how to decide
between shading bars and drawing a zone with a top and a bottom.

## A first example

This study colours each candle by which side of a slow exponential moving
average (EMA) a fast one is on, and leaves the candles alone until both averages
have a value:

```openscript
version 1
study("Trend regime", overlay = true, precision = 2)

fastLen = input(20, "Fast length", min = 1, max = 500)
slowLen = input(50, "Slow length", min = 1, max = 500)
paint   = input(true, "Recolour the candles")

fast = ema(close, fastLen)
slow = ema(close, slowLen)

// Absent until the slow average has warmed up, not false.
up = fast > slow

// Three states: warming up, up and down.
tint = isNone(up) ? none : (up ? lime : red)

barColor(paint ? tint : none)

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)
```

Three lines in it carry the ideas the rest of this page builds on:

- **`up` is absent on the early bars, not false.** A comparison with an absent
  operand is absent, so for the first 49 bars `up` has no value. See
  [Absent values](/script/language/absent-values).
- **`tint` has three states.** An absent condition takes the false branch of a
  ternary, so `up ? lime : red` would paint every warmup bar red and claim a
  downtrend nobody measured. Testing `isNone()` first and passing `none`
  leaves those bars their own colour.
- **`paint` is a switch.** It lets a reader keep the two lines and hand the
  candles back to another study without deleting anything. The section on
  [which study owns the candles](#only-one-study-colours-the-candles) explains
  why that matters.

## The two calls

| | `barColor(color)` | `background(color)` |
|---|---|---|
| Paints | The instrument's own candles | The full height of the bar's column |
| Sits | In front, as the candle itself | Behind the candles, plots, fills and drawings |
| Passing `none` | Leaves the bar its own colour | Leaves the bar unshaded |
| Called twice on one bar | The last call wins | The last call wins |
| Cost | One colour per bar | One colour per bar |

Both calls may appear anywhere a statement may: at the top level, inside an
`if`, inside a loop or inside a function. They are per-bar paint rather than
part of the study's fixed shape, so the top-level rule that applies to
`plot()`, `fill()`, `level()` and `table()` does not apply to them.

They are also the cheapest thing a script can put on a chart. Neither creates an
object, holds a handle or needs deleting, so a study that paints fifty thousand
bars costs one colour per bar and nothing more.

Passing an absent colour is never an error. `barColor(none)` and
`background(none)` are how a conditional paint switches itself off on the bars
it has nothing to say about.

## Recolouring the candles

`barColor` does not add anything to the chart. It changes what is already there,
and that makes it a trade rather than a gain: **a candle's colour already tells
the reader whether the bar closed above or below its open**, and a study that
repaints it replaces that fact with its own.

Two habits follow from that:

- **Paint a state the reader cannot see otherwise.** Trend direction, a regime,
  which side of a trailing stop price is on. Not something the candle already
  shows.
- **Paint only the bars that matter.** A study that marks eleven interesting
  bars out of two hundred should pass `none` on the other hundred and
  eighty-nine rather than painting every bar in a slightly different shade of
  the same idea.

A trailing stop is the classic case, because "which side of the stop is price
on" is exactly the state a trader wants to see at a glance. The built-in
`supertrend()` computes one: a stop that trails price at a multiple of the
average true range (ATR, the typical size of one bar's move). It returns an
array of two numbers, the stop line in `st[0]` and the direction in `st[1]`:

```openscript
version 1
study("Supertrend candles", overlay = true, precision = 2)

factor = input(3.0, "Band width, in ATR", min = 0.5, max = 20)
atrLen = input(10, "ATR length", min = 1, max = 200)
paint  = input(true, "Recolour the candles")

st       = supertrend(factor, atrLen)
stop     = st[0]
longSide = st[1] < 0    // direction is -1 while long and 1 while short

plot(longSide ? stop : none, "Stop, long", lime, width = 2)
plot(longSide ? none : stop, "Stop, short", red, width = 2)

tint = isNone(longSide) ? none : (longSide ? lime : red)
barColor(paint ? tint : none)
```

The screenshot shows the same kind of stop drawn by a Supertrend study on an NSE
chart, with the space between the stop and price shaded and a marker where the
trend flips. Its candles keep their own colours: that is the part `barColor`
changes.


### Only one study colours the candles

Inside one script the rule is simple: `barColor` writes one colour per bar, so a
script that calls it three times on a bar writes the same place three times and
the last call wins. The same holds for `background`.

Between studies there is a second rule. A candle has one body and one border, so
there is no honest way to split it between two studies that both have an
opinion: **only one study colours the candles at a time.** On the /trading
chart, when several studies recolour the candles, **the one added to the chart
most recently owns them**, and the bar colouring of every other study is not
drawn. The chart recomputes its studies in the order they were added, every
time, so the same study wins on every redraw and the candles do not flicker
between two meanings.

Three consequences are worth knowing while you write:

- Adding a second colouring study takes the candles, which is usually what the
  person adding it meant.
- Adding a study that does not call `barColor` changes nothing.
- A study in its own pane that calls `barColor` still recolours the price
  candles, and takes part in the same rule.

Hiding the owning study in the legend withdraws its colours at once. The candles
show their own colours until another colouring study next recomputes, on the
next update of the chart.

Backgrounds need no such rule. Two translucent shadings compose, so every study
that shades a bar is drawn.

What the rule asks of you as an author is two small courtesies:

1. **Give the user a switch.** An input named "Recolour the candles" lets a
   reader keep your study and hand the candles to another one.
2. **Say so in the title or the description.** A study that quietly takes the
   candles is hard to debug when two of them are on one chart.

## Shading bars with background

`background` paints the full height of the bar's column, behind the candles,
the plots, the fills and everything else in the pane the study draws in. For an
overlay study that is the price pane. For a study with its own pane it is that
pane.

There is no start and no end to give it. **A shaded region is a run of
consecutive bars that each paint themselves**, and it ends on the first bar that
does not. This study shades the first fifteen minutes of every session, the
stretch when many intraday traders on NSE and BSE wait for the opening range to
settle:

```openscript
version 1
study("Opening window", overlay = true)

window = input("0915-0930", "Shade this window")
shade  = input(true, "Shade it")

// Read in the chart's own timezone, so 0915 is 09:15 IST on an NSE chart.
inWindow = session.isIn(window)

background(shade and inWindow ? fade(silver, 92) : none)
```

`session.isIn()` takes a window written `"HHMM-HHMM"`, with an optional list of
weekdays after a colon: `"0915-1530:12345"` is 09:15 to 15:30, Monday to
Friday, where 1 is Monday and 7 is Sunday. A bar is inside the window when the
time it opens is, and the end time itself is outside, so on a 5 minute chart
`"0915-0930"` shades the bars that open at 09:15, 09:20 and 09:25. A window
whose end is before its start crosses midnight. Change the input to
`"1500-1530"` and the same study shades the last half hour, when intraday
positions are being squared off. The [Sessions and time](/script/data/sessions-and-time)
page covers session windows in full.

> **Keep backgrounds faint. A background covers the whole height of the bar, so**
anything much below 85 percent transparency turns the shaded stretch into a
block with a chart faintly visible inside it. Start at `fade(c, 90)` and go up.
`fade()` takes transparency, not opacity: `fade(c, 100)` is invisible.

## A bar or a price: background or a box

This is the question that decides, every time, whether to shade bars or to draw
a zone. Ask what the thing you want to show is attached to.

**A supply zone is attached to prices.** It runs from, say, 24,180 to 24,240 on
a NIFTY future, and it means something at those prices and not at others. Price
can be inside it, above it or below it, and its top and bottom are the
information. That is a box: two times, two prices, a handle and a lifecycle. See
[Lines and boxes](/script/visuals/lines-and-boxes).

**A regime is attached to bars.** "Volatility is expanded", "the session is in
its first fifteen minutes", "the daily trend is up", "this study is still
warming up": none of these is truer at 24,180 than at 24,240. They are
statements about a moment, and a moment on a chart is a bar. That is a
background, which paints the whole height of the bar because the whole height is
what the statement covers.

Painting a regime bar by bar, rather than as a region, is what makes it behave:

| Property | Why per-bar paint gets it right |
|---|---|
| The shading is exactly as long as the condition | It is recomputed from the condition on every bar, so it cannot be one bar too wide |
| It needs no anchor | There is no start time to store and no end time to guess |
| It needs no deletion | Nothing persists between bars, so nothing accumulates |
| It cannot drift when older history loads | There is no index and no anchor to move |
| It follows a changed input at once | The condition is recomputed; a drawn region would have to be found and redrawn |
| It costs one colour per bar | Not one object per region |

The reverse holds too: a background cannot say "between these two prices", so a
supply zone painted as a background claims the whole chart is supply.

| The fact you want to show | Call | Anchored to |
|---|---|---|
| A value the chart has on every bar | `plot()` | The bar and the value |
| The space between two values | `fill()` | Two plots |
| A fixed reference price | `level()` | A price |
| A price band over a stretch of time | `draw.box()` | Two times and two prices |
| A direction this bar is in | `barColor()` | The bar |
| A regime this bar is in | `background()` | The bar |
| Something that happened on this bar | `signal()` | The bar |
| The current reading of several things | `table()` | A corner of the pane |

## Using both at once

The two calls carry different kinds of fact, so a study can use both without
saying anything twice: direction on the candles, regime behind them.

```openscript
version 1
study("Volatility regime", overlay = true, precision = 2)

atrLen  = input(14, "ATR length", min = 1, max = 200)
meanLen = input(50, "Compare against", min = 2, max = 500)
hot     = input(1.5, "Expanded above this ratio", min = 1, max = 5)
cold    = input(0.7, "Compressed below this ratio", min = 0.1, max = 1)
paint   = input(true, "Recolour the candles")

// Named once and used twice: each atr() call site keeps its own state.
atrValue = atr(atrLen)
ratio    = atrValue / sma(atrValue, meanLen)

up   = close > ema(close, meanLen)
tint = isNone(up) ? none : (up ? lime : red)

// The candles carry direction. The background carries the regime.
barColor(paint ? tint : none)

background(isNone(ratio) ? none :
           (ratio > hot ? fade(orange, 90) :
           (ratio < cold ? fade(navy, 90) : none)))
```

`ratio` compares this bar's ATR with its own average over the last `meanLen`
bars. At the default settings, above 1.5 the market is moving half as much again
as usual and the bar is shaded orange; below 0.7 it is unusually quiet and the
bar is shaded navy; in between it is left alone.

The nested ternary is the ordinary way to write a three-way choice, and it stays
readable because each arm is one call. When a fourth state arrives, move the
choice into a `switch` before the paint call rather than adding another level:

```openscript
atrValue = atr(14)
ratio    = atrValue / sma(atrValue, 50)

tone = none
switch
    case isNone(ratio)
        tone = none
    case ratio > 1.5
        tone = fade(orange, 90)
    case ratio < 0.7
        tone = fade(navy, 90)
    default
        tone = none

background(tone)
```

`tone` is declared before the `switch` because a name first assigned inside an
arm belongs to that arm and does not exist after it. Declaring it first also puts
the default where a reader sees it. See [Control flow](/script/language/control-flow).

## Paint on the forming bar

The newest bar of a chart that is receiving updates runs again on every update,
and **paint is recomputed from scratch each time**. Neither call waits for the
bar to close the way a `signal()`, an `alert()` or an order does.

That difference is deliberate. A signal is a claim that something happened, and
a claim that evaporates before the close was never worth making, so signals wait
for confirmation. Paint states what the data shows right now, it costs nothing
to redraw, and nothing accumulates: the bar's output is thrown away and rebuilt
on every update, so the last state is the only state.

If a paint should appear only on a settled bar, say so with
`bar.isConfirmed`:

```openscript
tint = close > open ? lime : red
barColor(bar.isConfirmed ? tint : none)
```

[Realtime and confirmation](/script/language/realtime-and-confirmation) explains
the forming bar in full.

## Candles a study draws itself

`barColor` recolours the instrument's candles. A study that produces candles of
its own, such as smoothed candles or a higher timeframe candle, draws them with
`plotCandles()` and colours them there:

```openscript
version 1
study("Smoothed candles", overlay = false, precision = 2)

len = input(5, "Smoothing", min = 1, max = 50)

plotCandles(ema(open, len), ema(high, len), ema(low, len), ema(close, len), "Smoothed",
            colorUp = fade(lime, 40), colorDown = fade(red, 40))
```

The two do not interact. `plotCandles` is a plotted column with four sources, so
it follows the plot rules: top level only, hidden on a bar by passing absent
values, and coloured through its own arguments. `barColor` is per-bar paint on
the instrument's candles. Reaching for the wrong one is the usual way a higher
timeframe candle study ends up fighting the chart. [Plots](/script/visuals/plots)
covers `plotCandles` in full.

## Common mistakes

| Symptom | Cause | Fix |
|---|---|---|
| The warmup bars are painted as a downtrend | An absent condition took the false branch | `isNone(cond) ? none : (cond ? up : down)` |
| The shaded stretch hides the candles | Transparency too low | `fade(c, 90)` or higher for a background |
| Your candle colours do not show with two studies loaded | A study added to the chart after yours also recolours the candles | Turn one off at its switch; only one study owns the candles |
| Nobody can keep the study without its candle colours | No switch declared | Add an input and pass `none` when it is off |
| A zone drawn as a background covers the whole pane | A price band painted as a regime | Use `draw.box()`, which has a top and a bottom |
| The colour changes while the bar is forming | The condition really does change during the bar | Guard with `bar.isConfirmed` if only settled bars should paint |
| Nothing is painted at all | `fade(c, 100)`, which is fully transparent | `fade` takes transparency; use 85 to 95 for a background |

**Related.** [Colors](/script/visuals/colors) for building the colours these
calls take, [Lines and boxes](/script/visuals/lines-and-boxes) for a zone with a
top and a bottom, [Labels and shapes](/script/visuals/labels-and-shapes) for
marking one bar rather than a stretch, [Tables](/script/visuals/tables) for
stating the regime in words, and the reference entries `barColor()` and
`background()`.


## Labels and shapes

Source: https://openalgo.in/script/visuals/labels-and-shapes

Two calls put text and marks on a chart. `signal()` marks an event on the bar
it happened on, such as a crossover or a breakout. `draw.label()` places a
plate of text at a time and a price you choose, and keeps it there until your
script moves or deletes it.

This page shows how to choose between them, how to position each one so it reads
the same on an NSE stock at 23.40 and an index future in the tens of thousands,
how to put detail in a tooltip, and when a label is the wrong tool altogether.

## Two tools, and the difference matters

| | `signal(text, ...)` | `draw.label(t, p, text, ...)` |
|---|---|---|
| Is | A marker on this bar | An object you own |
| Anchored to | The bar it fired on: above it, below it or on it | A time and a price you compute |
| Lifecycle | None. It fires on a bar or it does not | Created, moved, retexted and deleted by your script |
| How many | At most one per call site per bar | As many as you create |
| On the forming bar | Waits until the bar closes | Drawn at once, rolled back when the bar runs again |
| What it costs | Nothing to manage | One object, held until you delete it |

The short version: **an event on a bar is a `signal`; a thing placed on the
chart is a `draw.label`.** A crossing, a breakout and a gap are events. The
reading pinned beside the newest bar and the caption on a zone are things.

Choosing wrongly is not a matter of style. A `signal` has no lifecycle to get
wrong, no handle to go stale and no object count to keep down, so every event
marked with a label instead is work you have taken on for nothing.

## Markers with signal

Here is a complete study that marks both directions of a crossover between two
exponential moving averages (EMAs), a fast one and a slow one:

```openscript
version 1
study("Crossing markers", overlay = true, precision = 2)

fastLen = input(9, "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)

// Both averages are computed on every bar, outside any if.
fast = ema(close, fastLen)
slow = ema(close, slowLen)

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)

// State the side on every call.
if crossUp(fast, slow)
    signal("BUY", color = lime, at = "below", shape = "triangleUp")

if crossDown(fast, slow)
    signal("SELL", color = red, at = "above", shape = "triangleDown")
```

The averages are computed at the top level and only the markers sit inside the
`if`. A stateful call such as `ema()` advances only on the bars where it runs,
so one computed inside the branch would see only the crossing bars and draw a
broken line. The compiler warns about that with
[OS8001](/script/errors/warnings#os8001).

`signal` is the whole of shape plotting in OpenScript. One call carries the
text, the colour, the position and the shape:

| Argument | Takes | What it does |
|---|---|---|
| `text` | `string` | The text the marker carries. No text on a bar means no marker on that bar |
| `color` | `color` | The marker's colour. Leave it out for the chart's default marker colour |
| `at` | `"above"`, `"below"` or `"price"` | Above the bar (the default), below it, or on the bar itself |
| `shape` | one of the ten shapes below | The mark itself. `"label"`, a plate carrying the text, is the default |

| Shapes | Names |
|---|---|
| Plate | `"label"` |
| Direction | `"arrowUp"`, `"arrowDown"`, `"triangleUp"`, `"triangleDown"` |
| Point | `"circle"`, `"square"`, `"diamond"`, `"cross"` |
| Flag | `"flag"` |

**Always say where the marker goes.** `at` defaults to `"above"`, so a call that
names no side sits above the bar whatever its text says. Nothing reads the word
"BUY" and moves the marker below the bar for you.

Here is the default `"label"` shape on a BHEL 15 minute chart. The
[Bollinger Bands](/script/getting-started/example-scripts#bollinger-bands) study
from Example scripts puts a teal Breakout plate on each bar whose close crossed
above the upper band and a red Breakdown plate on each bar whose close crossed
below the lower band, with a colour and a side stated in each call:


### Position, shape and colour are fixed before the first bar

`at`, `shape` and `color` are part of the marker's declaration, which is settled
before the first bar runs. Each must be a literal or an `input()`. Only the
text is read on every bar, so a side chosen from bar data is refused with
[OS3003](/script/errors/arguments#os3003):

```openscript
side = close > open ? "above" : "below"
signal("MOVE", at = side)
```

When you want two sides or two colours, write two calls, as the crossing example
does. When you want the reader to choose, make it an input:

```openscript
where = input("below", "Marker position", options = ["above", "below", "price"])
tone  = input(lime, "Marker colour")

if crossUp(close, ema(close, 20))
    signal("UP", color = tone, at = where, shape = "circle")
```

### One call site, one marker per bar

Each `signal()` call written in your source (its **call site**) is one marker
series with its own identity. The call site is the line in the source, not each
time that line runs. Two consequences follow:

- **A call site that fires twice on one bar keeps the last text.** That happens
  inside a loop, and when a function that calls `signal` is called more than
  once on a bar. A loop that signals once per element marks the bar once, with
  the text of the last element. If you need one mark per element, you need a
  drawing object per element, with the lifecycle that implies.
- **Markers are rebuilt from the script on every run.** A signal that stops
  firing because you changed an input leaves nothing behind. You never clear
  markers, and there is no marker equivalent of `draw.delete()`.

### Conditional markers

Unlike `plot()`, which must sit at the top level, `signal` may appear anywhere:
inside an `if`, a loop or a function. These lines mark a close above the highest
high of the previous 20 bars:

```openscript
breakout = close > highest(high, 20)[1]

if breakout
    signal("BREAK", at = "above", shape = "flag")
```

The same marker can be written without the `if`:

```openscript
breakout = close > highest(high, 20)[1]

signal(breakout ? "BREAK" : none, at = "above", shape = "flag")
```

The first is what most scripts write. The second works because an absent text is
the absence of an event, not an error: no text on the bar, no marker. Use one
form or the other, not both: two call sites are two markers on the same bar.

### Markers wait for the bar to close

A signal does not fire on a bar that is still forming. The call is deferred until
the bar closes, and if the condition that produced it is no longer true by then,
the marker never appears. That is what you want from a mark you might act on: a
marker that appears halfway through a bar and vanishes before the close is only
showing you a condition that did not survive the bar.

A study that really does want intrabar markers says so with
`onUnconfirmed = true` in its declaration, and then guards any marker that should
stay settled with `bar.isConfirmed`:

```openscript
version 1
study("Intrabar markers", overlay = true, onUnconfirmed = true)

surge = volume > 3 * sma(volume, 20)

// Fires as soon as the forming bar qualifies.
if surge
    signal("VOL", at = "below", shape = "circle")

// Waits for the close, as a signal would by default.
if surge and close > open and bar.isConfirmed
    signal("VOL UP", at = "above", shape = "arrowUp")
```

[Realtime and confirmation](/script/language/realtime-and-confirmation) covers
the forming bar, and [Alerts from scripts](/script/alerts/overview) covers
turning events into alerts.

### Numbers in marker text

Text is a `string`, and OpenScript never converts a number to text on its own, so
joining the two is [OS2003](/script/errors/names-and-types#os2003):

```openscript
r = rsi(close, 14)
signal("RSI " + r)
```

Convert with `text()`. How it treats an absent value decides what your marker
shows during warmup:

| You write | On a warmup bar, where the value is absent |
|---|---|
| `"RSI " + text(r, 1)` | `text` with decimals returns absent, the whole string is absent, and no marker appears |
| `"RSI " + text(r)` | `text` without decimals returns the string `"none"`, so the marker reads `RSI none` |
| `"RSI " + show(r, 1)` with the helper below | The marker reads `RSI warming up` |

One helper settles it for a whole script:

```openscript
fn show(value, decimals) => isNone(value) ? "warming up" : text(value, decimals)

r = rsi(close, 14)

if crossUp(r, 30)
    signal("RSI " + show(r, 1), at = "below", shape = "arrowUp")
```

Keep marker text short. A marker sits among the candles, and a long caption on
every signal hides the price action the study is about. Put detail in a
[tooltip](#tooltips) instead.

## Labels with draw.label

A label is a drawing object: it has a handle, a lifecycle, setters, and a
deletion you are responsible for. Everything on
[Lines and boxes](/script/visuals/lines-and-boxes) about anchoring, moving,
capping and deleting objects applies to labels unchanged.

| Argument | Takes | Default |
|---|---|---|
| `t` | A time, in milliseconds | Required |
| `p` | A price on the pane's scale | Required |
| `text` | `string` | Required |
| `color` | The plate colour | `none`: no plate, only the text is drawn |
| `textColor` | The text colour | `white` |
| `align` | `"left"`, `"center"` or `"right"` | `"center"` |
| `tooltip` | Text shown while the pointer rests on the label | `""` |

The plate is centred vertically on the price `p`. `align` decides where it sits
against the time `t`: `"center"` centres the plate on it, `"left"` puts the
plate's left edge there so the text reads to the right of the point, and
`"right"` puts its right edge there. Give a label a `color` unless you want bare
text: with the defaults it is white text with nothing behind it, which is hard to
read wherever the chart background is light. A label whose time or price is
absent is not drawn at all, though it still counts as an object until you
delete it.

Once a label exists, these calls change it:

| Call | Changes |
|---|---|
| `draw.setAt()` | Where it sits: a new time and price |
| `draw.setText()` | Its text |
| `draw.setColor()` | The plate colour |
| `draw.setTextColor()` | The text colour |
| `draw.setTooltip()` | The tooltip |
| `draw.delete()` | Removes it |

The most useful label in most studies is the one pinned above the newest bar
that states what the study currently reads. It is one object for the life of the
chart, created once and then moved:

```openscript
version 1
study("Current reading", overlay = true, precision = 2)

rsiLen = input(14, "RSI length", min = 2, max = 200)

oscillator = rsi(close, rsiLen)
band       = atr(14)

fn show(value, decimals) => isNone(value) ? "warming up" : text(value, decimals)

var tag = none

// Only the newest bar carries the label, so the work happens once per update
// rather than once per bar of history.
if bar.isLast
    caption = "RSI " + show(oscillator, 1)
    plate   = isNone(oscillator) ? gray : (oscillator > 70 ? red : (oscillator < 30 ? lime : silver))

    if isNone(tag)
        tag = draw.label(time, high + band, caption, color = plate, textColor = black)
    else
        // Moved and retexted, never recreated.
        draw.setAt(tag, time, high + band)
        draw.setText(tag, caption)
        draw.setColor(tag, plate)
```

Note where `band` is computed. `atr()` keeps state and advances only on the
bars where it runs, so calling it inside `if bar.isLast` would give it one bar of
history and an absent result. It is computed at the top level and used inside the
branch.

## Placing a label

A signal takes a side, and the chart places the marker clear of the bar. A label
takes a price, and the price is yours to compute. There is no "above the bar"
for a label and no pixel offset anywhere in the language.

That is deliberate. A pixel offset means one thing on a chart zoomed out to five
years and another on the same chart zoomed into an hour. A fixed price offset
fails the other way: 5 points is a huge gap on a stock at 23.40 and invisible on
an index future. **An offset measured in the instrument's own volatility works
everywhere**, so pad labels with a multiple of `atr()`, the average true range
(the typical size of one bar's move on this instrument and timeframe).

This study labels each pivot with its price. A **pivot high** is a bar whose
high is above the highs of the `leftBars` bars before it and the `rightBars`
bars after it; a pivot low is the same for lows:

```openscript
version 1
study("Pivot labels", overlay = true, precision = 2)

leftBars  = input(5, "Pivot left bars", min = 1, max = 50)
rightBars = input(5, "Pivot right bars", min = 1, max = 50)
padding   = input(0.5, "Padding, in ATR", min = 0, max = 5)
keep      = input(30, "Labels to keep", min = 1, max = 500)

pivotUp   = pivotHigh(high, leftBars, rightBars)
pivotDown = pivotLow(low, leftBars, rightBars)

// One ATR read at the top level, used by both branches below.
pad = atr(14) * padding

var tags = []

if not isNone(pivotUp) and not isNone(pad)
    push(tags, draw.label(time[rightBars], pivotUp + pad, text(pivotUp, 2),
                          color = red, textColor = white))

if not isNone(pivotDown) and not isNone(pad)
    push(tags, draw.label(time[rightBars], pivotDown - pad, text(pivotDown, 2),
                          color = lime, textColor = black))

// Labels are objects, so the list is capped. A while, because one bar can
// add two labels.
while size(tags) > keep
    draw.delete(shift(tags))
```

Three details in that script are the point of it:

- **The anchor time is `time[rightBars]`.** A pivot is only known `rightBars`
  bars after the bar it formed on, so `pivotHigh()` reports it late. The label
  belongs on the bar where the pivot formed, not on the bar that reported it.
- **The padding is a multiple of ATR**, so the label clears the candle on any
  instrument and any timeframe.
- **The list is capped.** A label is an object, and an uncapped list of objects
  is the one mistake that turns a good study into a slow chart. The `pad` test
  matters too: until ATR has warmed up, `pivotUp + pad` is absent, and a label
  with no price is not drawn but still counts as an object.

To put a label at a level rather than beside a bar, anchor it at the level:
`draw.label(time, rangeHigh, "Range high")`. A label's price is on the scale of
the pane the study draws in, so in a study with its own pane the anchor is an
oscillator reading, not a price.

## Tooltips

A tooltip is text that appears while the pointer rests on an object. Labels and
boxes take one, through the `tooltip` argument when you create them or
`draw.setTooltip()` later. Lines and polylines do not: `draw.line` has no
`tooltip` argument ([OS3002](/script/errors/arguments#os3002)), and
`draw.setTooltip` given a line or a polyline is
[OS3011](/script/errors/arguments#os3011). Write `\n` inside the text to start a
new line, in a tooltip or a caption.

The division of labour keeps a chart readable:

| Goes in the caption | Goes in the tooltip |
|---|---|
| What this is, in two or three words | The numbers behind it |
| The one value the eye needs | When it formed, and how old it is |
| Nothing that changes every bar | Anything that changes every bar |

This study marks every gap at the 09:15 open with a two-word caption, and keeps
the numbers in the tooltip:

```openscript
version 1
study("Opening gaps", overlay = true, precision = 2)

minGap = input(0.2, "Smallest gap, percent", min = 0, max = 10)
keep   = input(20, "Gaps to keep", min = 1, max = 250)

pad = atr(14) * 0.5

// The first bar of a trading day: a bar on a different IST date from the bar
// before it. On the oldest bar time[1] is absent, so it is never a gap.
newDay = not isNone(time[1]) and not date.isSameDay(time, time[1], "Asia/Kolkata")

var tags = []

if newDay and not isNone(pad)
    prev   = close[1]
    gapPct = (open - prev) / prev * 100

    if abs(gapPct) >= minGap
        up      = gapPct > 0
        caption = up ? "Gap up" : "Gap down"
        detail  = "Open " + text(open, 2) + "\nPrevious close " + text(prev, 2) +
                  "\nGap " + text(gapPct, 2) + " percent"

        push(tags, draw.label(time, up ? high + pad : low - pad, caption,
                              color = up ? lime : red, textColor = black, tooltip = detail))
        if size(tags) > keep
            draw.delete(shift(tags))
```

On the first bar of a day, `close[1]` is the last close of the previous
session, so the gap is measured against the close a trader saw the evening
before. The tooltip holds three lines of numbers; the caption stays two words. A
tooltip costs nothing visually, which makes it the place for the detail you were
tempted to put in the caption.

The language's own test for the first bar of a session is
`session.isFirstBar`. It needs the instrument's session hours, which the
/trading chart does not give the engine yet, so there it has no value and a
study built on it draws nothing. On NSE, BSE and MCX a new IST date is a new
session, so the date test above works on the chart today. See
[Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today).

## When a label is the wrong tool

| What you want to show | Use | Not a label, because |
|---|---|---|
| A value the chart has on every bar | `plot()` | A label per bar is an object per bar saying what one column says, and a script holds at most 10,000 |
| An event on one bar | `signal()` | A marker has no handle, no cap and no deletion to get wrong |
| The current state of several readings | `table()` | A grid is pinned to a corner and does not move with price |
| A regime that spans bars | `background()` or `barColor()` | A regime has no price, so it has nothing to anchor to |
| A price band over a stretch of time | `draw.box()` | A box has the extent; a label at its corner is a caption, not the zone |
| A number you want to read later | `print()` | The log takes a value per bar without drawing anything |

The failure worth naming is **the label per bar**. It looks fine while you test on
two hundred bars and is unusable on a real chart: the labels overlap into a grey
band, the chart slows in proportion to the history loaded, and past 10,000
objects the script stops with [OS5010](/script/errors/limits#os5010). The
information was a plot all along. If you find yourself writing `draw.label`
outside an `if`, stop and ask what the plot would be.

The second is **the label used as a dashboard**. Six labels stacked above the
last bar form a table that moves when price moves, covers the candles around it,
and has to be repositioned as the instrument reprices. A [table](/script/visuals/tables)
stays in the corner you pin it to, keeps its rows lined up, and takes the same
number of lines to write.

## Common mistakes

| Symptom | Cause | Fix |
|---|---|---|
| A marker is on the wrong side of the bar | `at` was left out, so it took the default `"above"` | State `at` on every call |
| OS3003 on a `signal` call | `at`, `shape` or `color` depends on bar data | Use a literal or an input, or write two calls |
| A marker is missing on early bars | `text(value, decimals)` of an absent value is absent | Use a `show` helper that says "warming up" |
| A marker reads `RSI none` | `text(value)` of an absent value is the string `"none"` | Test `isNone()` first |
| A marker appears and disappears during a bar | `onUnconfirmed = true` in the declaration | Remove it, or guard with `bar.isConfirmed` |
| A loop that should mark several things marks one | One call site makes at most one marker per bar | Draw an object per element instead |
| The label sits inside the candle | Anchored at `high` with no padding | Anchor at `high + atr(14) * 0.5` |
| The label is several bars right of its pivot | Anchored at `time` on the bar that reported the pivot | Anchor at `time[rightBars]` |
| The chart slows as history loads, or the script stops with OS5010 | One label per bar, or an uncapped list | Cap the list, or use a plot |
| A label is bare white text | No `color`, so the label has no plate | Pass a plate colour |
| A study that marks the session open draws nothing on the /trading chart | It tests `session.isFirstBar`, which has no value there | Test for a new IST date, as the gaps study does |
| OS2003 on the marker text | A number joined to a string | Convert it with `text(value, decimals)` |

**Related.** [Lines and boxes](/script/visuals/lines-and-boxes) for the
anchoring and lifecycle rules every label shares, [Tables](/script/visuals/tables)
for the dashboard a stack of labels is trying to be,
[Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds) for
marking a stretch of bars, [Colors](/script/visuals/colors) for plate and text
colours, and the reference entries `signal()` and `draw.label()`.


## Lines and boxes

Source: https://openalgo.in/script/visuals/lines-and-boxes

A plot is one value per bar. Some things you want on a chart are not: a
trendline between two swing highs, a supply zone that holds until price closes
through it, the opening range of today's session. For those, OpenScript has
drawing objects: shapes your script creates on one bar, keeps across many, moves
as bars arrive and deletes when they stop being useful.

This page covers the three shape constructors, `draw.line()`, `draw.box()`
and `draw.polyline()`, and the discipline that keeps a drawing study fast on a
long chart: anchor on time, create once and move, and decide how every object
dies before you write the line that creates it. Labels are drawing objects too;
what goes in them is on [Labels and shapes](/script/visuals/labels-and-shapes).

## Why an object and not a plot

| You want | Use | Because |
|---|---|---|
| A value the chart has on every bar | `plot()` | The chart already knows where it goes: bar by bar, at that bar's value |
| A line between two moments that are not adjacent | `draw.line()` | Two anchors, and nothing to say about the bars between them |
| A rectangle over a price band for a stretch of time | `draw.box()` | A start, an end, a top and a bottom, none of which is per bar |
| A path through many points | `draw.polyline()` | One shape, many anchors, optionally closed and filled |
| A fact about a bar with no price attached | `background()` or `barColor()` | See [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds) |

The test that settles it: if you can write the thing down as a number for every
bar, plot it. A trailing stop is a plot. The line joining the two swing highs of
a divergence (price making a higher high while an oscillator such as RSI makes a
lower one) is not, because on the bars between them there is no value to state.

## The constructors

All drawing calls live in the `draw` namespace and may appear anywhere a
statement may: inside an `if`, a loop or a function. They are not part of the
study's fixed shape, so the top-level rule for `plot()` and `table()` does
not apply to them.

| Call | Draws | Key arguments |
|---|---|---|
| `draw.line(t1, p1, t2, p2)` | A straight line between two points | `color` (default `gray`), `width`, `style` (`"solid"`, `"dashed"` or `"dotted"`), `extendLeft`, `extendRight` |
| `draw.box(t1, p1, t2, p2)` | A rectangle between two corners | `color` for the border (default `none`, no border), `fillColor` (default `none`, no fill), `opacity` (default 0.12), `width`, `text`, `textColor`, `tooltip` |
| `draw.polyline(times, prices)` | A path through every point | `color` (default `gray`), `width`, `closed`, `fillColor`, `opacity` (default 0.12) |
| `draw.label(t, p, text)` | A plate of text at a point | See [Labels and shapes](/script/visuals/labels-and-shapes) |

Each returns an object of type `line`, `box`, `polyline` or `label`. An object
is an ordinary value: you can name it inside a block, keep it in a `var`, hold a
set of them in an array, pass one to a function and compare one with `none`.
The [drawing reference](/script/reference/drawing) lists every argument with its
type and default.

Two details of boxes are easy to miss:

- **`opacity` dims the fill colour you give it.** The fill is drawn at
  `opacity` times the colour's own strength. `fillColor = red` with the default
  0.12 is a faint red. `fillColor = fade(red, 85)` is already 85 percent
  transparent, and dimmed again to 0.12 it is all but invisible. Pass a plain
  colour and set the strength with `opacity`.
- **A box's `text` sits on a plate at the centre of the box**, in the border
  colour, written in `textColor` (white by default).

`draw.polyline` takes two arrays of the same length, one of times and one of
prices, read index by index. **The path is copied when you call it**: pushing to
those arrays afterwards does not redraw the shape. `draw.setPoints()` is how a
path changes. A point whose time or price is absent leaves a gap in the path,
and a path with a gap is drawn open and unfilled, whatever `closed` says.

## An anchor is a time and a price

**Every anchor is a timestamp in milliseconds (UTC) and a price on the pane's
scale.** Never a bar index, never a pixel, never an offset from the right edge.

The reason is how charts load data. `bar.index` counts bars from the start of
the data the chart was given, so loading an older year of history renumbers
every bar; a line anchored at index 12,400 would jump somewhere else entirely.
`time` does not move: the bar that opened at 09:15 on a given day opened then
however much history sits to its left.

The price side follows the pane the study draws in. An overlay study anchors on
the instrument's price scale. A study with its own pane anchors on that pane's
scale, so a divergence line between two RSI readings is anchored at, say, 71.4
and 64.8.

Pivots are where anchoring usually goes wrong, so here it is done right:

```openscript
version 1
study("Swing line", overlay = true, precision = 2)

leftBars  = input(5, "Pivot left bars", min = 1, max = 50)
rightBars = input(5, "Pivot right bars", min = 1, max = 50)

pivot = pivotHigh(high, leftBars, rightBars)

var lastTime  = none
var lastPrice = none

if not isNone(pivot)
    // A pivot is reported rightBars bars after the bar it formed on,
    // so the anchor is that older bar's time.
    pivotTime = time[rightBars]

    if not isNone(lastTime)
        draw.line(lastTime, lastPrice, pivotTime, pivot, color = orange, width = 2)

    lastTime  = pivotTime
    lastPrice = pivot
```

That script is right about anchoring and wrong about lifecycle: it creates one
line per pivot and never removes any. [Decide the lifecycle first](#decide-the-lifecycle-first)
fixes it.

## Create once, then move

A handle held in a `var` refers to the same object on the next bar. That is the
whole mechanism behind a well-behaved drawing study: **create once, then move
and restyle the same object for as long as it is needed.**

| Call | Takes | Changes |
|---|---|---|
| `draw.setFrom()` | A line or a box | The first anchor |
| `draw.setTo()` | A line or a box | The second anchor |
| `draw.setBounds()` | A line or a box | Both anchors in one call |
| `draw.setAt()` | A label | Its anchor |
| `draw.setPoints()` | A polyline | The whole path |
| `draw.setText()` | A label or a box | The caption |
| `draw.setColor()` | Any object | The line, border or plate colour |
| `draw.setTextColor()` | A label or a box | The text colour |
| `draw.setFillColor()` | A box or a polyline | The fill |
| `draw.setWidth()` | A line, a box or a polyline | The line thickness |
| `draw.setStyle()` | A line | `"solid"`, `"dashed"` or `"dotted"` |
| `draw.setExtend()` | A line | Whether it continues to the pane edge, left and right |
| `draw.setTooltip()` | A label or a box | The text shown on hover |
| `draw.delete()` | Any object | Removes it |
| `draw.deleteAll()` | Nothing | Removes every object this script created |
| `draw.count()` | Nothing | Returns how many objects this script holds |

Each setter names the kinds of object it takes, because only those kinds have
the property. Passing another kind is refused before the first bar with
[OS3011](/script/errors/arguments#os3011):

```openscript
zone = draw.box(time[10], high, time, low)
draw.setStyle(zone, "dashed")
```

Here is the pattern in full. Two dashed lines mark the previous session's high
and low, and there are exactly two of them on a chart of any length:

```openscript
version 1
study("Previous session high and low", overlay = true, precision = 2)

// The first bar of a trading day: the first bar on the chart, or a bar on a
// different IST date from the bar before it.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

var dayStart    = none
var runningHigh = none
var runningLow  = none
var highLine    = none
var lowLine     = none

if newDay
    // The day that just ended is complete, so its high and low are final.
    // Each line runs across that day and extends right, across today.
    if not isNone(runningHigh)
        if isNone(highLine)
            // Created when the second day on the chart begins.
            highLine = draw.line(dayStart, runningHigh, time, runningHigh,
                                 color = red, style = "dashed", extendRight = true)
            lowLine  = draw.line(dayStart, runningLow, time, runningLow,
                                 color = lime, style = "dashed", extendRight = true)
        else
            // Moved as each later day begins, never redrawn.
            draw.setBounds(highLine, dayStart, runningHigh, time, runningHigh)
            draw.setBounds(lowLine, dayStart, runningLow, time, runningLow)

    dayStart    = time
    runningHigh = high
    runningLow  = low
else
    runningHigh = max(runningHigh, high)
    runningLow  = min(runningLow, low)
```

The `isNone(highLine)` test does the work of a constructor: the first time
through it creates, every time after it moves. A `var` that starts as `none` and
a test for absence is the idiom for "I have not made this yet" throughout the
language.

The lines are moved once a day, on its first bar, not on every bar, because
they have nothing to follow in between: `extendRight` carries them across the day on its own. The
oldest day on the chart may have started before the first loaded bar, so the
first pair of lines can describe only part of a day; every pair after it is
complete.

> **The language's own test for the first bar of a session is**
`session.isFirstBar`. The /trading chart does not yet give the engine the
instrument's session hours, so there it has no value, and a study that resets on
it never resets. On NSE, BSE and MCX a new date in IST is a new session, so the
examples on this page test the date. See
[Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today).

## Extending to the right

The chart has empty space to the right of the newest bar. Lines and boxes reach
into it differently.

**A line extends.** `extendRight = true` continues the line past its second
anchor to the edge of the pane, and keeps doing so as the chart scrolls.
`extendLeft = true` does the same to the left, and `draw.setExtend()` changes
either later. An extended line needs no maintenance: its anchors fix its slope
and the chart draws the rest.

Give an extended line two different times. Its slope comes from the gap between
the anchors, and a line whose two anchors share a time is vertical, so extending
it draws a vertical line through the whole pane. That is why the study above
anchors each line at the start of the day it describes rather than at the bar
that creates it.

**A box does not.** A box has no extend argument, so a box that should reach the
current bar has its right edge moved there on each bar with `draw.setTo()`.
That is one call per bar against one object, which is cheap.

Avoid projecting an edge a fixed number of bars past the newest bar. It looks
easy: `chart.intervalMinutes` gives the length of one bar, and `time - time[1]`
seems to as well. Both are wrong exactly where it matters. The bar after 15:25
on a 5 minute NSE chart opens at 09:15 the next trading day, not at 15:30, and
`time - time[1]` is five minutes inside a session but the whole overnight gap
across one. The first bars of a session are where a projected edge is looked at
hardest. So set a box's right edge to `time`, this bar's own opening instant,
and use a line with `extendRight` for anything that must reach further.

This study draws a box around the opening range of each session, the high and
low of the first minutes after the 09:15 open, and keeps the last few sessions:

```openscript
version 1
study("Opening range box", overlay = true, precision = 2)

rangeMinutes = input(15, "Opening range, in minutes", min = 1, max = 240)
keepSessions = input(5, "Sessions to keep", min = 1, max = 60)

// The first bar of a trading day, by its IST date.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

var openTime  = none
var rangeHigh = none
var rangeLow  = none
var zone      = none
var zones     = []

if newDay
    openTime  = time
    rangeHigh = high
    rangeLow  = low
    // Let go of the previous session's box; the zones list still holds it.
    zone      = none

// Milliseconds since the day's first bar opened.
elapsed = isNone(openTime) ? none : time - openTime
forming = not isNone(elapsed) and elapsed < rangeMinutes * 60000

if forming
    rangeHigh = max(rangeHigh, high)
    rangeLow  = min(rangeLow, low)

    if isNone(zone)
        zone = draw.box(openTime, rangeHigh, time, rangeLow,
                        color = aqua, fillColor = aqua, opacity = 0.08)
        push(zones, zone)
        if size(zones) > keepSessions
            // shift removes the oldest handle and returns it for deleting.
            draw.delete(shift(zones))
    else
        draw.setBounds(zone, openTime, rangeHigh, time, rangeLow)

else if not isNone(zone)
    // The range is complete: only the right edge follows the session.
    draw.setTo(zone, time, rangeLow)
```

While the range is forming, `draw.setBounds()` moves both corners, because the
high and the low can still change. Once it is complete only the right edge
moves. The oldest day on the chart may start after 09:15, and then its box
covers the first minutes the chart holds rather than the true opening range.

## Decide the lifecycle first

**An object stays on the chart until your script deletes it.** Nothing is ever
removed to make room. A script may hold at most 10,000 objects at once, and one
that tries to create another stops with [OS5010](/script/errors/limits#os5010)
rather than silently dropping old ones. [Limits](/script/writing/limits) lists
every limit the engine enforces.

Holding objects comes with one obligation: **decide how each object dies before
you write the line that creates it.** Every correct drawing study uses one of three
shapes:

| Shape | Looks like | Objects held | Use it when |
|---|---|---|---|
| One object, moved forever | Create under `isNone(handle)`, then `draw.set...` | One per thing drawn | The thing always exists: a level, a channel, today's range |
| A capped list | `push` on create, `draw.delete(shift(list))` over the cap | At most the cap | One object per event: zones, divergences, breakouts |
| Create and forget | A bare `draw.line(...)` on an event | One per event, forever | Only when the number of events is known to be small |

The swing line study above is the third shape. On a chart with two thousand
pivots it leaves two thousand lines, each of which the chart must hold and redraw
whenever you pan. Turning it into the second shape takes one input, one list
and three lines:

```openscript
version 1
study("Swing lines, capped", overlay = true, precision = 2)

leftBars  = input(5, "Pivot left bars", min = 1, max = 50)
rightBars = input(5, "Pivot right bars", min = 1, max = 50)
keep      = input(20, "Lines to keep", min = 1, max = 500)

pivot = pivotHigh(high, leftBars, rightBars)

var lastTime  = none
var lastPrice = none
var lines     = []

if not isNone(pivot)
    pivotTime = time[rightBars]

    if not isNone(lastTime)
        push(lines, draw.line(lastTime, lastPrice, pivotTime, pivot,
                              color = orange, width = 2))
        // Deleted on the bar that goes over the cap, so no bar ends
        // holding more than keep lines.
        if size(lines) > keep
            draw.delete(shift(lines))

    lastTime  = pivotTime
    lastPrice = pivot
```

### Arrays of objects

`var lines = []` has no type written on it. The first `push()` fixes the
element type, which is how an empty array literal learns what it holds. You can
also write the type yourself, which reads well when the first push is far away:

```openscript
var zones: array<box> = []

if bar.isLast and size(zones) == 0
    push(zones, draw.box(time[20], high[20], time, low, color = teal))
```

An empty literal with neither an annotation nor a first use is
[OS2015](/script/errors/names-and-types#os2015):

```openscript
var zones = []
```

Deleting an object does not remove it from an array that holds it, so delete the
object and then remove the element. `draw.delete(shift(list))` does both in one
line, because `shift()` returns the element it removes. [Collections](/script/language/collections)
covers arrays in full.

Two more rules fall out of the same thinking:

- **Delete from a list downwards.** A loop that walks a list upwards and removes
  elements as it goes skips the element after every removal, because removal
  renumbers everything above it. `for i = size(list) - 1 to 0 step -1` makes
  that impossible.
- **`draw.deleteAll()` is a reset, not a maintenance plan.** Deleting and
  redrawing everything on every bar is correct and wasteful: it rebuilds the
  whole drawing layer fifty thousand times to show the state of the last bar. It
  earns its place in a study that draws a small fixed set of objects for the
  current state only, on `bar.isLast`.

`draw.count()` is the health check. A study that draws should be able to say
how many objects it holds, and a count that climbs without limit on a long chart
is the bug this section exists to prevent. `print(draw.count())` on the last bar
tells you; see [Debugging](/script/writing/debugging).

## Deleted objects and stale handles

A handle held in a `var` outlives the object it names. Deleting the object does
not blank the handle, and a setter called on a deleted object stops the script
with [OS4005](/script/errors/runtime#os4005), naming the bar the object was
deleted on, rather than doing nothing.

That is on purpose: a script changing an object it already deleted has lost
track of its own state, and failing at the first stale call names the bar where
it happened. A setter given `none`, on the other hand, does nothing. So the fix
is one line: **set the handle to `none` beside every delete**, and test
`isNone()` before every change.

```openscript
var zone       = none
var zoneTop    = none
var zoneBottom = none

pivot = pivotHigh(high, 5, 5)
band  = atr(14)

// A zone below each swing high, while none is standing.
if not isNone(pivot) and not isNone(band) and isNone(zone)
    zoneTop    = pivot
    zoneBottom = pivot - band
    zone       = draw.box(time[5], zoneTop, time, zoneBottom, color = red)

// A close above the zone breaks it.
if not isNone(zone) and close > zoneTop
    draw.delete(zone)
    zone = none    // without this line the setter below raises OS4005

if not isNone(zone)
    draw.setTo(zone, time, zoneBottom)
```

## Objects on the forming bar

The newest bar of a chart receiving updates runs again on every update. Drawing
objects follow the same rollback rule as `var` values: before each run of the
forming bar, the set of objects is restored to what it was at the end of the
previous bar. A script that creates a line on a condition does not gain one line
per update, and a chart left open all day shows the same objects as the same
study loaded afresh over the same bars.

The one thing to remember: **hold object handles in `var`, never in `live var`.**
A `live var` deliberately keeps its value through the rollback, but the object
does not. Suppose the line below is first created on a forming bar. On the next
update the rollback removes the line, while `guide` still names it. `isNone(guide)`
is now false, so the line is never created again, and a setter given a handle to
an object that no longer exists does nothing. The line vanishes after one update
and never comes back, with no error to say why. The compiler flags every
`live var` with [OS8011](/script/errors/warnings#os8011):

```openscript
live var guide = none

if isNone(guide)
    guide = draw.line(time[1], close[1], time, close)
else
    draw.setTo(guide, time, close)
```

See [Realtime and confirmation](/script/language/realtime-and-confirmation) and
[Persistence](/script/language/persistence).

## Objects are written, not read

There is no call that asks an object where it is. You can create an object, move
it, restyle it and delete it, but not read its top, its bottom or its anchors
back.

So a script that needs to reason later about what it drew keeps the numbers
itself, in arrays parallel to the handles. That is not a workaround: the numbers
a zone was built from are the script's own data, and keeping them is what lets
the script decide, four hundred bars later, whether price has closed through the
zone. The drawing is output; the numbers are state.

This study draws supply and demand zones where price turned. A **supply zone**
is a price band where sellers pushed price down, a **demand zone** one where
buyers pushed it up. The study extends each zone to the current bar while it
holds, and deletes it when price closes through it or it grows too old:

```openscript
version 1
study("Supply and demand zones", overlay = true, precision = 2)

leftBars  = input(5, "Pivot left bars", min = 1, max = 50)
rightBars = input(5, "Pivot right bars", min = 1, max = 50)
maxAge    = input(200, "Delete a zone after this many bars", min = 10, max = 5000)
maxZones  = input(12, "Most zones to hold", min = 1, max = 100)

// One array of objects, and four arrays of the numbers behind them.
var zones      = []
var zoneTop    = []
var zoneBottom = []
var zoneSide   = []
var zoneBar    = []

// Walked downwards, so removing element i never skips one.
for i = size(zones) - 1 to 0 step -1
    top    = element(zoneTop, i)
    bottom = element(zoneBottom, i)
    side   = element(zoneSide, i)
    age    = bar.index - element(zoneBar, i)

    // Closing through a zone breaks it; a wick into it is the zone working.
    broken = side > 0 ? close > top : close < bottom

    if broken or age > maxAge
        draw.delete(element(zones, i))
        remove(zones, i)
        remove(zoneTop, i)
        remove(zoneBottom, i)
        remove(zoneSide, i)
        remove(zoneBar, i)
        continue

    draw.setTo(element(zones, i), time, bottom)
    draw.setTooltip(element(zones, i), (side > 0 ? "Supply" : "Demand") +
                    ", " + text(age, 0) + " bars old")

pivotUp   = pivotHigh(high, leftBars, rightBars)
pivotDown = pivotLow(low, leftBars, rightBars)

// A supply zone runs from the turning bar's high down to the top of its body,
// the wick where price was turned away. A demand zone mirrors it below.
if not isNone(pivotUp) and size(zones) < maxZones
    zoneHigh = high[rightBars]
    zoneLow  = max(open[rightBars], close[rightBars])
    push(zones, draw.box(time[rightBars], zoneHigh, time, zoneLow,
                         color = red, fillColor = red, text = "Supply"))
    push(zoneTop, zoneHigh)
    push(zoneBottom, zoneLow)
    push(zoneSide, 1)
    push(zoneBar, bar.index - rightBars)

if not isNone(pivotDown) and size(zones) < maxZones
    zoneHigh = min(open[rightBars], close[rightBars])
    zoneLow  = low[rightBars]
    push(zones, draw.box(time[rightBars], zoneHigh, time, zoneLow,
                         color = lime, fillColor = lime, text = "Demand"))
    push(zoneTop, zoneHigh)
    push(zoneBottom, zoneLow)
    push(zoneSide, -1)
    push(zoneBar, bar.index - rightBars)
```


Three details make it hold up on a long chart. Subtracting two bar indexes is
safe inside one run, which is why the age is measured with `bar.index` while
every anchor uses `time`. Each box's right edge moves to `time`, never past it.
And every zone has a way to die: broken, too old, or never created once the list
is full.

The study declares no plot at all. Nothing it produces is one value per bar.

## Paths with draw.polyline

A polyline is the shape for a path with more than two points, which would
otherwise be one `draw.line` per segment. This study joins the last dozen swing
points into one path and replaces the path as each new swing forms:

```openscript
version 1
study("Swing path", overlay = true, precision = 2)

leftBars  = input(5, "Pivot left bars", min = 1, max = 50)
rightBars = input(5, "Pivot right bars", min = 1, max = 50)
points    = input(12, "Points in the path", min = 3, max = 100)

pivotUp   = pivotHigh(high, leftBars, rightBars)
pivotDown = pivotLow(low, leftBars, rightBars)

var pathTimes  = []
var pathPrices = []
var path       = none

swing = isNone(pivotUp) ? pivotDown : pivotUp

if not isNone(swing)
    push(pathTimes, time[rightBars])
    push(pathPrices, swing)

    // Trim both arrays together, so they stay the same length.
    if size(pathTimes) > points
        shift(pathTimes)
        shift(pathPrices)

    // One object whose path is replaced, not one line per segment.
    if isNone(path)
        path = draw.polyline(pathTimes, pathPrices, color = purple, width = 2)
    else
        draw.setPoints(path, pathTimes, pathPrices)
```

Pass `closed = true` with a `fillColor` and the same call draws a filled shape,
which is how a script shades a triangle or a wedge:

```openscript
if bar.isLast
    draw.polyline([time[30], time[15], time], [low[30], high[15], low],
                  color = teal, closed = true, fillColor = teal, opacity = 0.1)
```

## Common mistakes

| Symptom | Cause | Fix |
|---|---|---|
| Lines move to the wrong place when older history loads | Anchored on `bar.index` | Anchor on `time`, which does not move |
| The study gets slower the longer the chart is open, or stops with OS5010 | Create and forget | Cap the list, or keep one object and move it |
| A line marks a bar exactly `rightBars` too late | Anchored at `time` on the bar that reported the pivot | Anchor at `time[rightBars]` |
| OS4005 long after a delete | The handle was not cleared when the object was deleted | Set the handle to `none` beside the `draw.delete` |
| OS3011 on a setter | The setter does not take that kind of object | Check the setter table: only lines have a style, only labels and boxes have text |
| OS2015 on an empty array | Nothing tells the compiler what the array holds | Push to it, or write `var zones: array<box> = []` |
| A box's right edge lands differently on each timeframe | A guessed bar length | Move the right edge to `time`, or use a line with `extendRight` |
| A drawing vanishes on the forming bar and never comes back | A handle kept in a `live var` | Use `var`, so the handle rolls back with the object |
| The box hides the candles | A fill that is too strong | Lower `opacity` |
| A box's fill cannot be seen | A faded fill colour dimmed again by `opacity` | Pass a plain colour and set the strength with `opacity` |
| A vertical line crosses the whole pane | An extended line whose two anchors share a time | Anchor the line at two different times |
| A study that resets each session draws nothing on the /trading chart | It resets on `session.isFirstBar`, which has no value there | Test for a new IST date, as the examples here do |

**Related.** [Labels and shapes](/script/visuals/labels-and-shapes) for text
plates and markers, [Tables](/script/visuals/tables) for numbers pinned to a
corner, [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds)
for a zone that has no price extent, [Colors](/script/visuals/colors) for line,
border and fill colours, and the [drawing reference](/script/reference/drawing)
for every `draw` call.


## Tables

Source: https://openalgo.in/script/visuals/tables

Almost everything a study produces is a value per bar, and a value per bar is a
plot. A table is for the rest: **readings that describe the current moment
rather than a history.** The latest RSI (relative strength index, a 0 to 100
momentum reading) and ATR (average true range, the typical size of one bar's
move), the trend on three timeframes, where price sits in today's range. None of
these has a shape on a time axis; each is read at a glance.

A table is also the one output that stays where you put it. It is pinned to a
corner of the pane, so unlike a stack of labels it does not move when price
moves. This page covers declaring a grid with `table()`, writing it with
`cell()`, when to write it, and how to align and colour it.

## A complete dashboard

```openscript
version 1
study("Dashboard", overlay = true)

rsiLen   = input(14, "RSI length", min = 2, max = 200)
atrLen   = input(14, "ATR length", min = 1, max = 200)
lookback = input(20, "Range lookback", min = 2, max = 500)
corner   = input("topRight", "Corner",
                 options = ["topLeft", "topRight", "bottomLeft", "bottomRight"])

// Declared once, at the top level, before the first bar.
panel = table("Readings", 7, 2, position = corner,
              textColor = silver, bgColor = fade(black, 25))

// Every reading is computed on every bar, outside the if below.
oscillator  = rsi(close, rsiLen)
atrPercent  = atr(atrLen) / close * 100
top         = highest(high, lookback)
bottom      = lowest(low, lookback)
rangePct    = top > bottom ? (close - bottom) / (top - bottom) * 100 : none
volumeRatio = volume / sma(volume, lookback)
trend       = ema(close, 20) > ema(close, 50)

fn show(value, decimals) => isNone(value) ? "warming up" : text(value, decimals)

zone = isNone(oscillator) ? "warming up" :
       (oscillator > 70 ? "overbought" : (oscillator < 30 ? "oversold" : "neutral"))
zoneColor = isNone(oscillator) ? silver :
            (oscillator > 70 ? red : (oscillator < 30 ? lime : silver))

// The panel shows one state, the current one, so it is written on the newest bar.
if bar.isLast
    cell(panel, 0, 0, chart.symbol, textColor = white)
    cell(panel, 0, 1, chart.interval, textColor = white, align = "right")

    cell(panel, 1, 0, "Trend")
    cell(panel, 1, 1, isNone(trend) ? "warming up" : (trend ? "up" : "down"),
         textColor = isNone(trend) ? silver : (trend ? lime : red), align = "right")

    cell(panel, 2, 0, "RSI")
    cell(panel, 2, 1, show(oscillator, 1), textColor = zoneColor, align = "right")

    cell(panel, 3, 0, "Zone")
    cell(panel, 3, 1, zone, textColor = zoneColor, align = "right")

    cell(panel, 4, 0, "ATR, percent of price")
    cell(panel, 4, 1, show(atrPercent, 2), align = "right")

    cell(panel, 5, 0, "Position in " + text(lookback, 0) + " bar range")
    cell(panel, 5, 1, isNone(rangePct) ? "warming up" : text(rangePct, 0) + " percent",
         align = "right")

    cell(panel, 6, 0, "Volume against average")
    cell(panel, 6, 1, show(volumeRatio, 2), align = "right",
         textColor = volumeRatio > 2 ? orange : silver)
```


The screenshot shows a panel like this one on a 15 minute NSE chart. "Trend" is
whether the 20 bar EMA (exponential moving average) is above the 50 bar one,
"Position in 20 bar range" is where the close sits between the lowest low and
the highest high of the last 20 bars (0 at the low, 100 at the high), and the
last row compares this bar's volume with its 20 bar average.

Three habits in that script make a good panel, and the rest of this page
explains each:

1. **The grid is declared at the top level**, once, with a fixed size.
2. **The readings are computed at the top level** and only the writing sits
   inside the `if`. A stateful call such as `rsi()` advances only on the bars
   where it runs, so one moved inside `if bar.isLast` would see a single bar and
   return nothing. The compiler warns about that with
   [OS8001](/script/errors/warnings#os8001).
3. **The cells are written on the newest bar only**, through one `show` helper
   that says "warming up" rather than leaving a blank or inventing a zero.

The first row reads `chart.symbol` and `chart.interval`, which the /trading
chart supplies. It does not yet supply `chart.exchange` or `chart.lotSize`,
so there both are absent: a cell built from either is blank, and `show` would
say "warming up" for ever.

## Declaring a grid

`table(title, rows, cols)` declares a grid and returns the table you write into.
The optional arguments set where it sits and how it looks:

| Argument | Takes | Default |
|---|---|---|
| `title` | `string`, the grid's name. It is not drawn on the chart | Required |
| `rows` | A whole number of rows | Required |
| `cols` | A whole number of columns | Required |
| `position` | `"topLeft"`, `"topRight"`, `"bottomLeft"` or `"bottomRight"` | `"topRight"` |
| `textColor` | The default text colour for every cell | `none`, the chart's default |
| `bgColor` | The background behind the whole grid | `none` |
| `borderWidth` | Border thickness, `0` for none | `0` |

**`table()` must be at the top level.** Like `plot()`, `fill()` and
`level()`, a table is part of the study's fixed shape: the pane has to know
what room to reserve before the first bar runs. A `table()` inside an `if`, a
loop or a function is [OS3006](/script/errors/arguments#os3006):

```openscript
if bar.isLast
    panel = table("Readings", 2, 2)
```

You never hide a table by wrapping it in a branch. You hide it by writing no
cells, as [switching a table off](#clearing-and-switching-a-table-off) shows.

For the same reason, every argument of `table()` is settled before the first
bar. Each must be a literal, arithmetic over literals, or an `input()`; a value
that depends on bar data is [OS3003](/script/errors/arguments#os3003):

```openscript
rows  = bar.index > 100 ? 4 : 2
panel = table("Readings", rows, 2)
```

Make `position` an input, as the dashboard does. It costs one line, and which
corner is free depends on the reader's chart, not on your study. An input works
for the grid's colours too: `bgColor = input(black, "Panel colour")`.

`table()` returns a table object, the same object on every bar. It can be named,
kept and passed to a function, and it is never deleted: the grid lives as long as
the study does.

### One grid per study

A study may declare several grids and the compiler accepts it, but a chart pane
has room for one: **the chart draws the first grid a study declares.** A second
`table()` compiles and its cells are written, yet nothing appears for it, and no
diagnostic says so yet. Declare one grid and give it the rows you need. If you
want two panels, write two studies and put them in different corners.

## Writing cells

`cell(t, row, col, text)` writes one cell of the grid `t` on this bar. Its
optional arguments style that one cell:

| Argument | Takes | Default |
|---|---|---|
| `textColor` | The cell's text colour | `none`, the grid's `textColor` |
| `bgColor` | The cell's background | `none`, the grid's `bgColor` |
| `align` | `"left"`, `"center"` or `"right"` | `"left"` |

Unlike `table()`, `cell` may appear anywhere: inside an `if`, a loop or a
function. All of its arguments, colours and alignment included, are read on
every bar, so a cell can change colour with the reading it shows.

Rows and columns count from zero, so a grid declared with 4 rows and 2 columns
has rows 0 to 3 and columns 0 and 1. Writing outside the grid stops the script
with [OS4004](/script/errors/runtime#os4004), so keep the declared size and the
rows you write in step. A second write to the same cell on the same bar replaces
the first.

A helper function keeps a long panel short, because a table object can be passed
to a function like any other value:

```openscript
fn row(t, r, name, value) =>
    cell(t, r, 0, name)
    cell(t, r, 1, value, align = "right")

panel = table("Levels", 3, 2, position = "bottomRight")

if bar.isLast
    row(panel, 0, "High", text(high, 2))
    row(panel, 1, "Low", text(low, 2))
    row(panel, 2, "Close", text(close, 2))
```

### Numbers in cells

The text of a cell is a `string`, and OpenScript never converts a number to text
on its own, so passing a number is [OS3011](/script/errors/arguments#os3011):

```openscript
panel = table("Readings", 2, 2)
cell(panel, 0, 0, close)
```

Convert with `text()`: `text(value, decimals)` writes a fixed number of
decimals. How it handles an absent value decides what a warming-up panel shows:

| You write | When the value is absent |
|---|---|
| `text(value, 2)` | The result is absent, and the cell is blank |
| `text(value)` | The result is the string `"none"` |
| `show(value, 2)` with the helper | The cell says `warming up` |

A blank cell hides that the study has not started, and a zero would invent a
number. Saying so is the only honest option, and one helper makes it consistent
across the whole panel:

```openscript
fn show(value, decimals) => isNone(value) ? "warming up" : text(value, decimals)
```

## Write on the newest bar

**Cells do not carry over from one bar to the next.** At the start of every run
of a bar, the engine empties every grid, and the chart shows what the newest bar
wrote. Three consequences follow:

- **Write inside `if bar.isLast`.** Writing the panel on every bar of a fifty
  thousand bar chart is fifty thousand writes to show the last one. `bar.isLast`
  is true only on the newest bar.
- **A cell written only under some condition disappears on the bars where the
  condition is false.** If the condition is false on the newest bar, the panel
  is empty. Write every cell you want to see on the newest bar.
- **Nothing is ever left over.** A panel that writes five rows on one bar and
  three on the next shows three.

The same rule makes a table safe while the newest bar is still forming. That bar
runs again on every update, the grid is emptied each time, and the panel is
rewritten from scratch rather than piling up.

## Alignment and numbers that line up

The rule that makes a panel readable: **labels left, numbers right.** A
right-aligned column puts the last digit of every number at the same edge, so
with the same number of decimals the units line up and a longer number reads as
a bigger one. In a left-aligned column 9.50 and 11.25 start at the same place and
their decimal points do not line up. `align` defaults to `"left"`, so pass
`align = "right"` on every value cell.

Inside one cell, `str.repeat()` turns a number into a bar drawn with
characters, which is often quicker to read than the number itself:

```openscript
version 1
study("Strength meter", overlay = true)

barLen = input(10, "Meter width", min = 4, max = 40)

meter = table("Strength", 2, 2, position = "bottomRight", textColor = silver)

oscillator = rsi(close, 14)
strength   = isNone(oscillator) ? none : floor(oscillator / 100 * barLen)

if bar.isLast
    cell(meter, 0, 0, "RSI")
    cell(meter, 0, 1, isNone(strength) ? "" :
                      str.repeat("|", strength) + str.repeat(".", barLen - strength),
         textColor = oscillator > 70 ? red : (oscillator < 30 ? lime : aqua))

    cell(meter, 1, 0, "Value")
    cell(meter, 1, 1, isNone(oscillator) ? "warming up" : text(oscillator, 1),
         align = "right")
```

With the RSI at 64 and a width of 10, the meter reads `||||||....`. `floor()`
is not decoration: `str.repeat` needs a whole number of copies, and given 6.4 it
stops the script with [OS4003](/script/errors/runtime#os4003).

`str.padLeft()` and `str.padRight()` pad text to a number of characters.
They do not line numbers up in a cell: the chart draws cells in a proportional
font, where a space is narrower than a digit, and `align` is what lines a column
up. Padding is for text that must have a fixed number of characters, such as a
zero-padded hour: `str.padLeft("9", 2, "0")` is `"09"`.

## Colour in a table

Colour is set at three levels, each overriding the one above it:

| Level | Set by | Applies to | Can change per bar |
|---|---|---|---|
| The grid | `table(..., textColor = ..., bgColor = ...)` | Every cell that sets nothing itself | No, fixed before the first bar |
| The cell | `cell(..., textColor = ..., bgColor = ...)` | That cell | Yes |
| Neither | Leaving the arguments out | The chart's own default | Not applicable |

So a grid's colours are constants or inputs, and a colour that follows the data,
such as red for overbought, goes on the cell.

Give the grid a translucent background rather than a solid one.
`bgColor = fade(black, 25)` keeps the candles behind the panel faintly visible,
which matters because the panel sits over the price pane; the same colour at full
strength punches a rectangular hole in the chart.

Colour in a cell is information, so spend it on the cell that carries the reading
and not on the label beside it. A panel where every cell is coloured is a panel
where nothing stands out. [Colors](/script/visuals/colors) covers colours that
read well on both light and dark charts.

## Headers and merged cells

There is no cell span in version 1: every cell occupies exactly one row and one
column. For a header across a row, put the text in the first column, leave the
rest blank, and give the whole row one background so it reads as a block:

```openscript
panel = table("Bias", 4, 3)

if bar.isLast
    cell(panel, 0, 0, "Higher timeframes", textColor = white, bgColor = fade(navy, 40))
    cell(panel, 0, 1, "", bgColor = fade(navy, 40))
    cell(panel, 0, 2, "", bgColor = fade(navy, 40))
```

For a value that needs the width of two columns, design the grid with fewer,
wider columns. A grid has a fixed shape, so the clean fix for a value that does
not fit is usually a different shape. In return, every cell has exactly one
address, and `cell(panel, r, c, ...)` means the same thing on every bar.

## Clearing and switching a table off

`clear()` empties every cell of a grid that has been written so far on this
bar. Since the grid starts every bar empty anyway, you need it only to discard
what the script has already written on the current bar and write something else
instead, such as replacing a full panel with one message:

```openscript
panel = table("Readings", 3, 2)

if bar.isLast
    cell(panel, 0, 0, "Close")
    cell(panel, 0, 1, text(close, 2), align = "right")
    cell(panel, 1, 0, "Volume")
    cell(panel, 1, 1, text(volume, 0), align = "right")

    if not chart.isIntraday
        clear(panel)
        cell(panel, 0, 0, "Intraday charts only")
```

Switching a whole table off works the same way as hiding it: **write no cells.**
The `table()` call cannot sit in an `if`, but the writes can.

One detail decides whether that leaves the corner clean. The chart draws the
grid at its declared size whether or not anything was written, so a grid
declared with a `bgColor` or a `borderWidth` still shows as an empty block when
it holds no text. For a panel that can be switched off, leave the grid's own
background at `none` and give the background to the cells you write:

```openscript
panel      = table("Readings", 1, 2)
showPanel  = input(true, "Show the panel")
shade      = fade(black, 25)
oscillator = rsi(close, 14)

// With showPanel off, nothing is written and nothing is drawn.
if bar.isLast and showPanel
    cell(panel, 0, 0, "RSI", bgColor = shade)
    cell(panel, 0, 1, text(oscillator, 1), bgColor = shade, align = "right")
```

## A higher timeframe grid

The case a table is best at: several timeframes, one row each, each row saying
the same thing about a different interval. This panel shows the EMA trend on
three intervals the reader chooses:

```openscript
version 1
study("Timeframe bias", overlay = true)

fastLen = input(20, "Fast length", min = 1, max = 500)
slowLen = input(50, "Slow length", min = 1, max = 500)
tfA     = input("15", "Timeframe 1", kind = "interval")
tfB     = input("60", "Timeframe 2", kind = "interval")
tfC     = input("1D", "Timeframe 3", kind = "interval")

grid = table("Bias", 4, 2, position = "topRight", bgColor = fade(black, 20))

// Each read waits for that timeframe's bar to close, so it never repaints.
biasA = req.timeframe(tfA, ema(close, fastLen) > ema(close, slowLen))
biasB = req.timeframe(tfB, ema(close, fastLen) > ema(close, slowLen))
biasC = req.timeframe(tfC, ema(close, fastLen) > ema(close, slowLen))

fn word(b) => isNone(b) ? "warming up" : (b ? "up" : "down")
fn tint(b) => isNone(b) ? silver : (b ? lime : red)

if bar.isLast
    cell(grid, 0, 0, "Timeframe", textColor = white)
    cell(grid, 0, 1, "Bias", textColor = white, align = "right")

    cell(grid, 1, 0, tfA)
    cell(grid, 1, 1, word(biasA), textColor = tint(biasA), align = "right")

    cell(grid, 2, 0, tfB)
    cell(grid, 2, 1, word(biasB), textColor = tint(biasB), align = "right")

    cell(grid, 3, 0, tfC)
    cell(grid, 3, 1, word(biasC), textColor = tint(biasC), align = "right")
```

`word` and `tint` both test `isNone()` first, and that is not caution for its
own sake. An absent condition takes the false branch, so `b ? "up" : "down"`
would print "down" on every row until the first higher timeframe bar closed. A
panel that says "down" when it means "not known yet" is worse than no panel.
[Higher timeframes](/script/data/higher-timeframes) explains `req.timeframe()`
and why its default mode never repaints.

## What a table is not for

| You want | Use | Because |
|---|---|---|
| A value per bar | `plot()` | A table shows one state, not a history |
| An event on a bar | `signal()` | A marker is attached to the bar it happened on |
| A running log of values | `print()` | The log takes a value per bar and draws nothing |
| A caption on a shape | `draw.label()` or a box's own `text` | It belongs with the thing it describes |
| Fifty rows of history | A backtest report or the log | A chart corner is too small to read it |

The last row is the one people push against. A table can be declared with fifty
rows and filled with the last fifty bars, and it will work, and it will be
unreadable at the size a chart corner allows. Charts are for shapes over time;
a list belongs in the [print log](/script/writing/debugging) or a
[backtest report](/script/strategies/reading-a-report).

## Common mistakes

| Symptom | Cause | Fix |
|---|---|---|
| OS3006 on the `table()` line | Declared inside an `if`, a loop or a function | Declare at the top level and guard the `cell` writes instead |
| OS3003 on the `table()` line | A size, corner or colour that depends on bar data | Use a literal or an input; put per-bar colour on the cell |
| OS3011 on a `cell` call | A number passed where text is expected | `text(value, decimals)` |
| OS4004 on a `cell` call | A row or column outside the declared grid | Declare enough rows, or check the index |
| The panel is empty | Cells written under a condition that is false on the newest bar | Write every cell inside `if bar.isLast` |
| A reading is blank | `text(value, decimals)` of an absent value | Use a `show` helper that says "warming up" |
| Every row says "down" on a fresh chart | An absent condition took the false branch | Test `isNone` first and say so |
| The chart is slow with a table on it | Cells written on every bar of history | Write inside `if bar.isLast` |
| The panel hides the candles under it | A solid `bgColor` | `fade(black, 25)` or similar |
| A second panel never appears | The chart draws only the first grid a study declares | Declare one grid, or split the study in two |
| Numbers do not line up | Cells are left aligned by default | `align = "right"` on the value column |
| An empty block stays in the corner with the panel switched off | The grid has a `bgColor` or a border, which is drawn at the declared size | Put the background on the cells you write instead |
| A cell is blank, or says "warming up" for ever, on the /trading chart | It reads `chart.exchange` or `chart.lotSize`, which the chart does not supply yet | Leave those readings out of a panel meant for the chart |
| OS4003 on a `str.repeat` meter | A count that is not a whole number | Round it down with `floor` first |

**Related.** [Labels and shapes](/script/visuals/labels-and-shapes) for the stack
of labels a table replaces, [Lines and boxes](/script/visuals/lines-and-boxes)
for output that belongs on the chart rather than in a corner,
[Colors](/script/visuals/colors) for cell colours,
[Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds) for
stating a regime in colour, and the reference entries `table()` and
`cell()`.


# Inputs and settings

## Inputs

Source: https://openalgo.in/script/inputs/inputs

An input turns a number, a colour or a choice written in your script into a setting a reader can change without opening the source. Every `input()` call in an OpenScript (also called OpenAlgo Script) file becomes one row of the study's settings dialog in /trading, and one field in the Backtest and Strategies panels when the script is a strategy. This page covers every kind of input, the control each one becomes, how bounds and defaults protect the calculation, how saved values are kept, and what the compiler refuses.

```openscript
version 1

study("Simple average", overlay = true, precision = 2)

len = input(20,    "Length", min = 2, max = 500)
src = input(close, "Source")

plot(sma(src, len), "Average", aqua, width = 2)
```

Two lines of inputs, two rows in the settings dialog, and a study that works on any instrument at any interval without anyone editing it.


## One call, three things

Each `input()` call does three things at once:

1. It assigns a name the script reads, exactly like any other assignment.
2. It builds one row of the settings dialog.
3. It names a slot where the reader's value is kept, so a value they typed comes back when they open /trading tomorrow.

**The default comes first, before the title, and it fixes the input's type.** `input(20, ...)` is a number input because `20` is a number, and `input(true, ...)` is a tick box because `true` is a bool. There is no type argument to get wrong.

**The title is the second argument.** It is the row's label. If you leave it out, the row is labelled with the variable's name, so write one anyway: `len` is a fine name in source and a poor label in a dialog. Write the title as a string literal on the line. The compiler reads it from there and does not evaluate expressions to build it, so a title such as `"Length " + "(bars)"` is ignored and the row falls back to the variable's name.

## The kinds, and the control each becomes

There is one function. The kind of control follows the type of the default, and a `kind` argument separates the kinds that share a string default.

| Written as | Kind | The script gets | In the /trading settings dialog |
|---|---|---|---|
| `input(14, "Length")` | number | `number` | A number box with up and down arrows |
| `input(true, "Show the band")` | switch | `bool` | A tick box |
| `input("note", "Label text")` | text | `string` | A text box |
| `input("ema", "Average", options = ["sma", "ema"])` | choice | `string` | A menu of the listed values |
| `input(aqua, "Band colour")` | colour | `color` | A colour swatch |
| `input(close, "Source")` | source | `series number` | A menu of price series |
| `input("1h", "Bias interval", kind = "interval")` | interval | `string` | A menu of intervals |
| `input("2025-01-01 09:15", "Anchor", kind = "time")` | time | `number` | A text box for a date and time |

Three more kinds are planned and not in this version: `kind = "symbol"` (an instrument picker), `kind = "price"` (a price set by clicking the chart) and `kind = "session"` (two clock fields). Writing one is refused:

```openscript
window = input("0915-1530", "Trading window", kind = "session")
plot(close, "Close")
```

Until they arrive, use a text input and check what you get: a symbol as text for `req.symbol()`, a window as text for `session.isIn()`.

### Number

The workhorse. `min`, `max` and `step` shape the control.

```openscript
atrLen = input(14,   "ATR length",     min = 1,   max = 200, step = 1)
mult   = input(3.0,  "Band, in ATR",   min = 0.5, max = 20,  step = 0.1)
risk   = input(5000, "Risk per trade, in rupees", min = 1)
plot(atr(atrLen) * mult, "Band width")
plot(risk, "Risk")
```

In the settings dialog, the arrows beside the box move the value by `step` (1 when you give none) and stop at `min` and `max`. You can also type a value. `step` does not restrict what is typed: a multiplier with `step = 0.1` still accepts `2.35`.

Give `min` and `max` to every number that feeds a length. A length must be a whole number of 1 or more, and a fractional or zero length is refused rather than rounded, because a length of 14.5 is a bug. A typed value outside the bounds is refused when the study runs, with OS6019 naming the setting and the bound, so the calculation never sees it.

### Switch

A bool default renders as a tick box. Use it for the optional half of a study.

```openscript
showBand = input(true,  "Show the band")
paint    = input(false, "Recolour the candles")

basis = sma(close, 20)
upper = basis + 2 * stdev(close, 20)

plot(showBand ? upper : none, "Upper band", aqua)
barColor(paint ? (close > basis ? lime : red) : none)
```

Notice what the switch controls: the drawing, not the calculation. A `plot` cannot sit inside an `if` (OS3006), so a switch hides a plot by giving it the absent value `none`, which draws a gap. And a switch must not skip a stateful call such as `sma()`, because a call that does not run on a bar does not advance its state, which is warning OS8001 and a broken line. Compute at the top level, then let the switch decide what is drawn.

### Text

A string default with no `options` and no `kind` is a text box: the right control for a label, a note on a drawing, or an instrument symbol until the picker arrives.

```openscript
benchmark = input("NIFTY", "Benchmark symbol")
bench = req.symbol(benchmark, chart.interval, close, exchange = "NSE_INDEX", mode = "developing")
plot(close / bench, "Relative strength")
```

### Choice

Add `options` and the same string default becomes a menu. The options are strings, and the default has to be one of them, or the dialog would open with nothing chosen (OS3018).

```openscript
maType = input("ema", "Average type",
               options = ["sma", "ema", "wma", "rma", "hma", "vwma"])

basis = ma(close, 20, type = maType)
plot(basis, "Basis")
```

`ma()` exists so that a choice can switch the shape of a study without a `switch` over six branches. A list of numbers is refused (OS3011), so offer numbers as strings and convert them with `toNumber()`.

### Colour

A colour default renders as a colour swatch that opens the browser's colour picker.

```openscript
upColor   = input(lime, "Rising colour")
downColor = input(red,  "Falling colour")

hist = macd(close, 12, 26, 9)[2]
plot(hist, "Histogram", hist > 0 ? upColor : downColor, style = "histogram")
```

The swatch has no opacity control, so a colour chosen there keeps the transparency the script's default had. Declare a colour input when the colour carries meaning the reader may want to restate, such as a long side against a short side. For a line whose colour is only decoration, you do not need one: the Style tab already lets the reader recolour every plot. [Settings and style](/script/inputs/settings-and-style) explains how a colour input and a plot's Style colour become one setting.

### Source

A price series default renders as a menu of price series, and the script gets a series it uses exactly like `close`.

```openscript
src = input(hlc3, "Source")
plot(ema(src, 20), "EMA 20")
```

The /trading menu lists Open, High, Low, Close, Hl2, Hlc3 and Ohlc4. The language also accepts `volume` as a source default, but the /trading menu does not list it, so read volume directly rather than through a source input.

### Interval

`kind = "interval"` renders a menu of intervals. The script gets a string, which it passes to a [higher timeframe read](/script/data/higher-timeframes).

```openscript
biasTf = input("1D", "Bias interval", kind = "interval")
bias   = req.timeframe(biasTf, ema(close, 20))
plot(bias, "Bias", style = "step")
```

In /trading the menu starts with **Chart interval**, followed by the intervals your data feed serves, such as `1m`, `5m`, `15m`, `1h` and `D`, and the input's current value if the feed does not list it. The script receives exactly the string you pick, and a read accepts only the forms on [Timeframes](/script/data/timeframes#how-a-timeframe-is-written):

> **Some entries in the /trading menu are not timeframes a read accepts. **Chart interval** hands the script an empty string, and **D** is the feed's name for daily, where the language writes `1D` (likewise **W** and **M**, where it writes `1W` and `1M`). A read given any of these stops with OS6001 when the study loads. For a daily read, keep `"1D"` as the input's default: the menu shows the input's current value as an entry of its own when the feed does not list it.**

The repaint mode of a read is never an input. `mode = "confirmed"`, `"developing"` or `"lookahead"` is written as a literal, because a setting would let a reader change the honesty of a study without reading it.

### Time

`kind = "time"` takes a date and a time. In the /trading dialog it is a text box with the hint `YYYY-MM-DD HH:MM`. The value is stored as the text you typed, and the script receives a timestamp in UTC milliseconds, converted once before the first bar, ready to compare with `time`.

```openscript
// Read as a UTC clock in /trading: 03:45 UTC is 09:15 IST.
anchor = input("2025-01-02 03:45", "Anchor, UTC", kind = "time")
started = time >= anchor
background(started ? fade(aqua, 95) : none)
```

> **In /trading today the text is converted as a **UTC** clock, not in the chart's timezone, on the chart and in the Backtest panel alike. `2025-01-02 09:15` is 09:15 UTC, which is 14:45 IST. Subtract 5 hours 30 minutes from an IST time when you type it, and put "UTC" in the title so the reader knows. The Strategies panel refuses to run a script with a time input. [Sessions and time](/script/data/sessions-and-time) has more on time in /trading.**

## Where input() may appear

**At the top level of the file, and nowhere else.** Not inside an `if`, a loop or a function. The dialog is built once, before the first bar, from the `input()` calls the compiler can see, so a row that existed on some bars and not others would have nothing for a saved value to attach to:

```openscript
version 1

study("Band", overlay = true)

useBand = input(true, "Use the band")
if useBand
    bandLen = input(20, "Band length")
```

Declare the input at the top level and read its name inside the block instead.

**The default must be fixed before the first bar**: a literal, arithmetic over literals, or another input. A default computed from bar data is OS3003:

```openscript
lookback = input(round(close / 100), "Lookback")
plot(sma(close, lookback), "Average")
```

An `input()` may also be the value of a declaration option, which is how a reader changes something the declaration decides, and it may be written inside the expression of a [higher timeframe read](/script/data/higher-timeframes#what-the-expression-means-inside-a-read):

```openscript
version 1

// A study in its own pane, where precision sets the decimals on the scale.
study("Daily RSI", precision = input(1, "Decimals", min = 0, max = 8), range = [0, 100])

dailyRsi = req.timeframe("1D", rsi(close, input(14, "RSI length", min = 2)))
plot(dailyRsi, "Daily RSI", purple, style = "step")
```

The input inside the read follows the dialog like any other. The one in the declaration is a different case in /trading today: the chart reads declaration options when it loads the study, at their defaults, so changing the Decimals row does not change the drawing there. [Settings and style](/script/inputs/settings-and-style#what-the-declaration-decides) lists what follows the dialog.

## Names, titles and saved values

/trading keeps one value per input for each study on a chart, and files it under a **key**: the name the input is assigned to, or its title when it is assigned to no name.

```openscript
len = input(20, "Length")               // filed under len
var start = input(0, "Starting count")  // filed under start
plot(sma(close, len), "Average")
plot(start, "Start")
```

The key survives every edit that does not rename it. Inserting an input above another, deleting one, or reordering them leaves every saved value where the reader put it. Renaming the variable is the one edit that loses a saved value, because the row it was saved for is gone, and the study comes back on the default.

Because a key must be unique, the compiler checks it:

| Code | When |
|---|---|
| OS3017 | Two inputs share a title |
| OS3022 | An input with no name has a title that spells another input's name |
| OS3021 | An input with no name has no title at all |
| OS3024 | An input with no name has an empty title |

```openscript
version 1

// Assigned to no name and given no title: nothing to file the value under.
study("Range", precision = input(2))
plot(high - low, "Range")
```

A named input is different: it has a key already, so an empty title is read as no title and the row is labelled with the name. `len = input(14, "")` is the same row as `len = input(14)`.

## var in front of an input

`var total = input(0, "Starting count")` is an ordinary [persistent variable](/script/language/persistence) whose first value is the setting. It is set once, on the first bar, and keeps whatever the script puts in it afterwards, which is how a running count starts from a setting:

```openscript
var tally = input(0, "Starting count")
tally += 1
plot(tally, "Bars so far")
```

The price is that the name is no longer the setting itself. A later line may change it, so it is not fixed before the first bar, and it cannot be a declaration option (OS3003) or be read inside a higher timeframe read (OS6003). Write the plain form when you want the setting, and `var` when you want a value that starts there.

## Groups and tooltips

Every kind also accepts these arguments:

| Argument | Type | Default | Does |
|---|---|---|---|
| `group` | `string` | `""` | A heading the input belongs under |
| `tooltip` | `string` | `""` | Help text for the input: the unit, and what moving it does |
| `inline` | `string` | `""` | Planned: rows sharing a value sit on one line |
| `confirm` | `bool` | `false` | Planned: ask for this value when the study is added |

`group` and `tooltip` are carried in the compiled script. In /trading today, the study settings dialog lists the inputs in the order they appear in the source, without group headings and without tooltips. The input forms in the Backtest and Strategies panels show the tooltip when you rest the pointer on an input's label. The compiler accepts `inline` and `confirm`, and they have no effect yet.

So make the labels carry the meaning. **Name the unit in the label when it is not obvious**: "Band width, in ATR" and "Flat this many minutes after the open" need no tooltip. "Multiplier" and "Threshold" need one and will still be misread. Keep grouping anyway, in the order a reader works: calculation first, then what is drawn, then anything about trading.

```openscript
version 1

study("Range breakout", overlay = true, precision = 2)

len = input(20, "Lookback, in bars", min = 2, max = 500,
            tooltip = "Bars the breakout level is measured over. " +
                      "Longer is slower and gives fewer, cleaner signals.")

stopMult = input(2.0, "Stop distance, in ATR", min = 0.2, max = 20, group = "Risk",
                 tooltip = "Distance from the breakout level to the stop, " +
                           "in average true range.")

showStop = input(true, "Draw the stop", group = "Display")
stopTint = input(red,  "Stop colour",   group = "Display")

level20  = highest(high, len)[1]
atrValue = atr(14)

plot(level20, "Breakout level", aqua, width = 2)
plot(showStop ? level20 - stopMult * atrValue : none, "Stop", stopTint)
```

## When a value is checked

A value passes three checks, and which one catches a mistake matters, because only the first two catch it before the study draws anything.

| When | What is checked | On failure |
|---|---|---|
| Compile | The declaration itself: placement, a fixed default, a default inside `options`, a title, a unique key | OS3007, OS3003, OS3018, OS3017, OS3021, OS3022, OS3024. The script does not compile |
| Load | The reader's saved value against the input's type, `min`, `max` and `options` | OS6019, naming the setting and the rule. The study does not run |
| Each bar | A legal setting that becomes an illegal argument, such as a length computed down to zero | OS4003. The study stops on that bar |

**A saved value that fails the check stops the study rather than falling back to the default.** The alternative looks friendlier and is a trap: a study that quietly used its default would come back on the chart under its own name, drawing numbers the reader never configured, with nothing on screen to say so. The usual cause is an edit that tightened a bound. Open the settings, correct the value or press **Defaults**, and press **Ok**.

Meanwhile the dialog still opens, and the study's declared shape uses the declared default for any value that fails, so the row you need to correct is always reachable.

An input that is declared and never read is warning OS8018: the row appears, the reader changes it, and nothing happens, which is worse than the setting not existing.

## Inputs in the Backtest and Strategies panels

A strategy's inputs are set in two more places, with one shared form, so the two accept exactly the same values:

- The **Backtest** panel, under **Settings**, which opens by itself for a strategy that declares inputs.
- The **Strategies** panel, in the form where you deploy a strategy.

In that form a switch is a `true` or `false` menu, a choice is a menu of its options, and every other input is a box that shows the script's default as a hint. **A box left empty uses the script's own default**, rather than zero or an empty string, and only the values you change are sent. In the Backtest panel the values apply to the next run and never reach a deployed strategy; in the Strategies panel a running strategy reads them when it starts, so stop it and start it again to apply a change.

One difference from the chart's settings dialog: a number box holding a value outside the input's `min` and `max`, or text that is not a number, is dropped rather than sent, so that run uses the script's default without saying so. Check the box holds what you meant before you trust a result.


The values in the strategy's own declaration, such as its capital, order size, commission and slippage, are shown in the Backtest panel under **Declared by the script** and are not offered as inputs. Edit the script to change them. [Backtesting](/script/strategies/backtesting) and [Sandbox and live](/script/strategies/sandbox-and-live) cover the two panels.

## Choosing defaults

A default is not a placeholder. Most readers never change it, so the default is the study for almost everyone who loads it.

- **Use the value you actually use.** If you trade the study with a length of 34, ship 34.
- **Make it valid on the first chart it lands on.** A default that assumes an intraday interval breaks on a daily chart, and one that assumes volume breaks on an index, which has none.
- **Watch the warmup.** A call that needs 200 bars is absent on the first 199, so a default of 200 on a chart holding 300 bars draws almost nothing and looks broken. [Warmup](/script/language/warmup) explains how warmups add up.
- **Set bounds to the range where the study still means something**, not to the range a number can hold. `min = 2, max = 500` on a lookback says more than `min = 1` alone, and stops a reader typing 50000 and waiting.
- **Default a switch to `true` for a feature the study is about**, and to `false` for anything that paints over the reader's chart, such as recoloured candles or a shaded background.

## What belongs in the dialog

**An input the dialog cannot show is a setting the reader can never change.** The dialog is built entirely from the `input()` calls the compiler can see before the first bar. Nothing else in a script becomes a row, so a number written in the middle of a calculation is a number no reader will change without editing the source.

The working rule: **every number, colour or choice in a script is either an input or a deliberate constant.** When you fix a value, say in a comment why. A reader who finds `14` with no comment assumes you forgot; a reader who finds a sentence saying the value is part of the definition moves on.

The opposite matters as much. Some things must not be inputs even though the dialog could show them: the mode of a higher timeframe read, the declaration's `onUnconfirmed`, anything that changes what the study is allowed to know. Those belong in the source, where a review can see them. [Repainting](/script/data/repainting) explains why.

## Common mistakes

| Symptom | Code | Fix |
|---|---|---|
| `input()` inside an `if` or a function | OS3007 | Move it to the top level and read the name inside the block |
| A default computed from bar data | OS3003 | Use a literal, or an input for the thing the default depended on |
| Two rows with one title | OS3017 | Rename one; the title is part of the key |
| A row with no name and no title | OS3021 | Give it a title written as a string literal |
| A row with no name and an empty title | OS3024 | Give the title something to say |
| A title that spells another input's name | OS3022 | Retitle it, or rename the other input |
| A choice whose default is not in its list | OS3018 | Add the default to `options`, or pick a listed value |
| A list of numbers as `options` | OS3011 | Write the options as strings |
| A `var` holding an input used as a declaration option | OS3003 | Drop the `var` |
| A planned kind such as `"symbol"` | OS2001 | Use a text input for now |
| A saved value outside the bounds | OS6019 | Correct it in the settings dialog, or press Defaults |
| A row nobody reads | OS8018, a warning | Use the name, or delete the input |
| A length reaching zero on some bar | OS4003 | Set `min = 1` on the input so the value never gets there |

## Worked example: one study, a complete dialog

```openscript
version 1

study("Bands", overlay = true, precision = 2)

// Calculation. Ungrouped, because these are the rows a reader adjusts most.
len = input(20, "Length, in bars", min = 2, max = 500,
            tooltip = "Bars in the basis average and in the deviation.")
src = input(hlc3, "Source")

mult = input(2.0, "Band width, in standard deviations",
             min = 0.1, max = 5, step = 0.1, group = "Bands")
maType = input("sma", "Basis type", group = "Bands",
               options = ["sma", "ema", "wma", "rma", "hma", "vwma"])

showBands = input(true,           "Show the bands", group = "Display")
bandTint  = input(aqua,           "Band colour",    group = "Display")
shadeTint = input(fade(aqua, 92), "Shade colour",   group = "Display")
basisTint = input(orange,         "Basis colour",   group = "Display")

// Computed on every bar, whatever the switch says.
basis = ma(src, len, type = maType)
dev   = mult * stdev(src, len)

// The switch hides the bands by handing their plots none, never by wrapping
// plot() in an if: plot is fixed before the first bar.
upper = showBands ? basis + dev : none
lower = showBands ? basis - dev : none

plot(basis, "Basis", basisTint, width = 2)
upperPlot = plot(upper, "Upper", bandTint)
lowerPlot = plot(lower, "Lower", bandTint)

// A fill stops wherever one of its plots is absent, so the switch hides the
// shade too. The shade has a colour input of its own, and a colour picked for
// it keeps the transparency of its default.
fill(upperPlot, lowerPlot, shadeTint)
```

Eight rows, and nothing in the calculation that a reader might reasonably want different is locked away in the source.

**Related:** [Settings and style](/script/inputs/settings-and-style), [input() reference](/script/reference/input), [Plots](/script/visuals/plots), [Higher timeframes](/script/data/higher-timeframes), [OS3xxx Arguments](/script/errors/arguments)


## Settings and style

Source: https://openalgo.in/script/inputs/settings-and-style

Every study you put on a chart in /trading gets a settings dialog, a legend row and a place in the Indicators dialog, and your script declares none of it. The dialog is generated from two things the compiler fixes before the first bar: your `input()` calls, and the plots the study draws. This page walks through that dialog as it appears in /trading, explains what a reader can change without you writing a line, covers the declaration options of an OpenScript (also called OpenAlgo Script) study, and says what /trading keeps when you come back.

```openscript
version 1

study("Two averages", overlay = true, precision = 2)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)

plot(ema(close, fastLen), "Fast", aqua,   width = 2)
plot(ema(close, slowLen), "Slow", orange, width = 2)
```

Two inputs and two plots give a dialog with two number rows on its Inputs tab, and on its Style tab one row for each line, with its colour, opacity, thickness and line style. The script asked for none of the Style tab.


## Opening the settings dialog

There are three ways in:

- **The legend row.** Each study on the chart has a row in the chart's legend, with buttons to hide the study, open its settings and remove it. A study written in OpenScript also has a braces button, which opens its source in the **Scripts** panel.
- **The Indicators dialog.** Under **On this chart**, **Active** lists every study on the chart, each with **Settings** and **Remove**.
- **The Objects panel.** On the right-hand toolbar, **Objects** lists what is on the chart, and each study there offers **Hide**, **Settings** and **Remove**.


The dialog carries the study's title at the top, two tabs, **Inputs** and **Style**, and three buttons at the bottom: **Defaults** on the left, **Cancel** and **Ok** on the right.

- **Ok** applies what you changed and closes the dialog.
- **Cancel**, the close button, the Escape key or a click outside the dialog closes it without applying anything.
- **Defaults** puts the fields back to the script's defaults. Nothing is applied until you press **Ok**.

A tab with nothing on it is greyed out, so a study with no inputs opens straight on its Style tab.

## The Inputs tab

The Inputs tab has one row per `input()` call, in the order they appear in the source, with the input's title as the label and a control chosen by the input's kind:

| Kind of input | Control |
|---|---|
| Number | A number box, with arrows that move by the input's `step` and stop at its `min` and `max` |
| Switch (`true` or `false`) | A tick box |
| Choice (`options = [...]`) | A menu of the listed values |
| Source (`close`, `hlc3` and so on) | A menu of Open, High, Low, Close, Hl2, Hlc3 and Ohlc4 |
| Interval (`kind = "interval"`) | A menu of Chart interval and the intervals your data feed serves |
| Colour | A colour swatch |
| Text | A text box |
| Time (`kind = "time"`) | A text box with the hint `YYYY-MM-DD HH:MM`, read as a UTC clock in /trading today |

The dialog does not show group headings or tooltips today, so write labels that say what a row is and in which unit. [Inputs](/script/inputs/inputs) covers every kind, its bounds and the values it hands the script.

## The Style tab

The Style tab has one row per `plot()`, labelled with the plot's title. You write none of it.


Each row has a tick box and an appearance button that shows the line's current colour and style. The tick box shows or hides that one plot: unticking it sets the plot's opacity to zero, and ticking it brings it back at full opacity. The appearance button opens a panel with:

- **A palette** of greys and colours. Picking one sets the colour and closes the panel.
- **A plus sign**, for any other colour from the browser's colour picker.
- **Opacity**, a slider from 0 to 100 percent.
- **Thickness**, one of 1, 2, 3 or 4 pixels.
- **Line style**: solid, dashed or dotted.

Two consequences save a script work.

**A script never writes style for the reader's benefit.** Write the colour and width that make the study readable on the day you ship it, and stop there. A trader who wants a thicker or dashed line changes it on the Style tab, and the change is kept. A script that tried to offer every option as an input would duplicate controls the reader already has.

**Style is a preference, and a value is a computation.** If a change affects a number, it belongs in an input. If it only affects how the same number looks, the Style tab already handles it.

What the Style tab does **not** offer in /trading:

- **The plot's style.** Whether a plot is a line, a step line, a histogram, columns or an area is decided by its `style` argument in the script.
- **Rows for anything but plots.** `level()`, `fill()`, `background()`, `barColor()` and tables have no Style rows. If a reader should be able to recolour one of those, give it a colour input, as the next section shows.

## A colour with a meaning

Sometimes a colour carries meaning: the long side against the short side, one band against another. For those, declare a colour input and pass it to the plot.

```openscript
version 1

study("Stop", overlay = true, precision = 2)

atrLen = input(10,  "ATR length", min = 1, max = 200)
mult   = input(3.0, "Stop distance, in ATR", min = 0.5, max = 20)

longTint  = input(lime, "Long stop colour",  group = "Colours")
shortTint = input(red,  "Short stop colour", group = "Colours")

atrValue = atr(atrLen)
var dir  = 1
var stop = none

prevStop = stop

if close > orElse(prevStop, low)
    dir = 1
else if close < orElse(prevStop, high)
    dir = -1

stop = dir == 1 ? hl2 - mult * atrValue : hl2 + mult * atrValue

// Two plots rather than one plot with a per-bar colour: each colour input is
// passed to a plot as it is, which makes it the same setting as that plot's
// Style colour, and the line breaks where the stop switches sides.
plot(dir ==  1 ? stop : none, "Stop, long",  longTint,  width = 2)
plot(dir == -1 ? stop : none, "Stop, short", shortTint, width = 2)
```

**A colour input passed to a plot is the same setting as that plot's Style colour.** The Inputs tab shows it under your title, "Long stop colour", and the Style tab shows it on the plot's row, and changing either changes both. So a reader never ends up with two colour controls for one line that disagree. This holds only when the input is passed as it is: a colour computed from an input, such as `fade(longTint, 50)` or `up ? longTint : shortTint`, is not tied to the Style row.

For a level or a shade, which have no Style row, a colour input is the only way to let the reader restyle it. Pass the input straight to the call:

```openscript
version 1

study("Shaded range", overlay = true, precision = 2)

len       = input(20, "Lookback, in bars", min = 2, max = 500)
lineTint  = input(aqua,           "Range colour", group = "Colours")
shadeTint = input(fade(aqua, 90), "Shade colour", group = "Colours")
midTint   = input(gray,           "Midline colour", group = "Colours")

top    = highest(high, len)
bottom = lowest(low, len)

topPlot    = plot(top,    "Range high", lineTint, style = "step")
bottomPlot = plot(bottom, "Range low",  lineTint, style = "step")
fill(topPlot, bottomPlot, shadeTint)
level(0, "Zero", midTint)
```

> **On the /trading chart, a fill follows a colour input only when the input is passed to `fill()` as it is, as above. A shade colour computed from an input, such as `fade(shadeTint, 50)`, is drawn instead in the colour of the first plot passed to `fill()`, at 12 percent opacity. The swatch has no opacity control, so give a shade colour input a transparent default, and a colour the reader picks keeps that transparency.**

## A colour that changes per bar

The colour argument of `plot()` takes either one colour or a colour that varies per bar. Pass an expression that varies, and the chart draws each bar in its own colour:

```openscript
version 1

study("Momentum histogram", precision = 4)

upTint   = input(lime, "Rising colour")
downTint = input(red,  "Falling colour")

m = macd(close, 12, 26, 9)

level(0, "Zero", gray)

plot(m[0], "MACD",   aqua)
plot(m[1], "Signal", orange)
plot(m[2], "Histogram", m[2] > 0 ? upTint : downTint, style = "histogram")
```

When the script decides the colour on every bar, build that colour from colour inputs, as above, and the reader keeps control of both colours through rows you named. A per-bar colour built from bare colour names is one the reader cannot change at all.

Four helpers cover nearly every colour you need to build:

| Call | Does |
|---|---|
| `rgb()` | Red, green and blue channels from 0 to 255, fully opaque |
| `rgba()` | The same with an alpha from 0 to 1, where 1 is opaque |
| `fade()` | The same colour at a given **transparency** in percent, where 100 is invisible |
| `withAlpha()` | The same colour at a given **opacity** from 0 to 1 |

`fade` takes transparency and `withAlpha` takes opacity, and mixing them up draws something invisible. `fade(aqua, 92)` is the usual way to write a light shade. [Colors](/script/visuals/colors) has the rest.

## Paint the reader did not ask for

`background()` and `barColor()` paint whole bars rather than drawing a plotted line, so they have no Style rows and the reader cannot turn them off there. Anything that paints over the reader's candles or the whole pane needs a switch of your own:

```openscript
version 1

study("Session shading", overlay = true)

shade = input(true,  "Shade the opening minutes")
paint = input(false, "Recolour the candles")
tint  = input(aqua,  "Shade colour", group = "Colours")

opening = session.isIn("0915-0930", "Asia/Kolkata")

// An absent colour leaves the bar alone, which is how a paint switches itself
// off. It is not an error.
background(shade and opening ? fade(tint, 88) : none)
barColor(paint ? (close > open ? lime : red) : none)
```

Default a paint switch to `false` unless the study is about the paint. A study that recolours the instrument's candles the moment it is added has overwritten something the reader may have set up on purpose. [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds) has more.

## What the declaration decides

The declaration is where a study says how it wants to be placed and formatted. Every option is fixed before the first bar, because the pane, the scale and the legend exist before any bar runs.

| Option | Type | Default | Decides |
|---|---|---|---|
| `title` | `string` | required | The name on the legend row, in the Indicators dialog and at the top of the settings dialog |
| `short` | `string` | the title | A shorter legend name, for a host that shows one. The /trading legend shows the full title |
| `overlay` | `bool` | `false` | `true` draws on the price pane, `false` gives the study its own pane below the price |
| `precision` | `number` | `4` | Decimals on the study's own pane, 0 to 10. A study drawn over the price keeps the instrument's own price formatting |
| `format` | `string` | `"price"` | `"price"`, `"percent"` or `"volume"` formatting on the study's pane |
| `range` | `array<number>` | none | `[min, max]` to fix the study pane's scale, as in `[0, 100]` |
| `scale` | `string` | `"right"` | `"right"`, `"left"` or `"none"`. The /trading chart places each line by the `scale` argument of its own `plot()`, so set it there |
| `group` | `string` | `""` | The category shown beside the study in the Indicators dialog. /trading shows OpenScript when you leave it empty |
| `onUnconfirmed` | `bool` | `false` | Whether signals, alerts and orders may fire on a bar that is still forming. See [Repainting](/script/data/repainting) |

In the Indicators dialog, every saved script that compiles is listed by its title under **My scripts**, and its `group` appears beside it when you point at the row.


```openscript
version 1

study("Bounded oscillator", precision = 2, range = [0, 100], group = "Momentum")

len = input(14, "Length", min = 2, max = 200)

level(70, "Overbought", red)
level(50, "Middle", gray)
level(30, "Oversold", lime)

plot(rsi(close, len), "RSI", purple, width = 2)
```

`range = [0, 100]` is worth writing on any bounded oscillator. Without it the pane rescales to whatever the data did, so 70 stops meaning overbought and the reader compares a line against a moving frame. The range takes two numbers, lowest first:

```openscript
version 1

study("Backwards", range = [100, 0])
plot(rsi(close, 14), "RSI")
```

`precision` belongs on the declaration and almost never on a plot. On a plot drawn over the price pane it would reformat the instrument's own price scale, which is rarely what anyone wants, and the compiler warns with OS8007:

```openscript
version 1

study("Average", overlay = true)
plot(sma(close, 20), "Average", precision = 1)
```

The full list of options for `study()` and `strategy()` is in the [declarations reference](/script/reference/declarations).

> **An `input()` can be written as a declaration option, such as `precision = input(2, "Decimals")`, or as a plot's `width` or `style`. In /trading today, the chart reads those at their defaults when it loads the study, so changing such a row in the settings dialog does not change the drawing. Colour inputs, level styles and a pane's `range` do follow the dialog.**

## What /trading keeps

/trading keeps each chart's studies, with their settings, and brings them back when you return. What it keeps for a study:

| Kept | Filed under |
|---|---|
| The value of each input you changed | The input's key: the name it is assigned to, or its title when it has no name |
| Each Style change | The plot's position in the file: the first plot, the second plot, and so on |
| A colour input's value | The input's key, whichever tab you changed it on |

The study itself is tied to its script's file name, not to one version of the text. When you edit and save the script, the study keeps its saved settings.

Because values are filed by name and position, some edits reach the reader:

- **Renaming an input's variable** loses the reader's saved value for it, and the study comes back on the default. Renaming only its title keeps the value, unless the input is assigned to no name, because then the title is its key.
- **Renaming a plot's title** keeps its Style changes, because they are filed by position.
- **Moving a plot above another**, or deleting one before it, hands the saved Style changes to whichever plot now sits in that position.
- **Tightening a bound** can make a saved value invalid. The study then stops with OS6019, naming the setting and the rule it broke, rather than quietly using the default. Open the settings, correct the value or press **Defaults**, and press **Ok**.

Choose names and titles once, before anyone else loads the study, and treat a later rename as a change your readers will see.

## Worked example: a study that gives away everything worth giving

```openscript
version 1

study("Channel", overlay = true, precision = 2)

len = input(20,   "Length, in bars", min = 2, max = 500)
src = input(hlc3, "Source")

mult = input(2.0, "Width, in ATR", min = 0.2, max = 10, step = 0.1, group = "Channel")

showChannel = input(true,           "Show the channel", group = "Colours")
upperTint   = input(aqua,           "Upper colour",     group = "Colours")
lowerTint   = input(aqua,           "Lower colour",     group = "Colours")
basisTint   = input(orange,         "Basis colour",     group = "Colours")
shadeTint   = input(fade(aqua, 94), "Shade colour",     group = "Colours")

basis = ema(src, len)
band  = mult * atr(len)

// Computed on every bar; the switch hands the edge plots none to hide them,
// and the shade stops wherever its plots are absent.
upper = showChannel ? basis + band : none
lower = showChannel ? basis - band : none

plot(basis, "Basis", basisTint, width = 2)
upperPlot = plot(upper, "Upper", upperTint)
lowerPlot = plot(lower, "Lower", lowerTint)
fill(upperPlot, lowerPlot, shadeTint)
```

What the reader gets: three number and source rows, a switch and four colours on the Inputs tab, and on the Style tab a colour, opacity, thickness and line style for each of the three lines. What the script did not have to write: a single thickness input, a line style input, or any code to save and restore any of it.

**Related:** [Inputs](/script/inputs/inputs), [Plots](/script/visuals/plots), [Fills](/script/visuals/fills), [Colors](/script/visuals/colors), [Declarations reference](/script/reference/declarations), [Repainting](/script/data/repainting)


# Alerts

## Alerts from scripts

Source: https://openalgo.in/script/alerts/overview

An alert is how a script tells you about a condition while you are looking somewhere else: a crossover on a five-minute SBIN chart, a break of the opening range on NIFTY futures, an RSI turning back from an extreme. This page covers the three calls OpenScript (also called OpenAlgo Script) gives you for it, `alert()`, `signal()` and the planned `notify()`: what each one takes, exactly when it fires, how to build a message that carries the numbers you need, and how the three differ.

What happens after an alert fires depends on where the script runs. For the /trading page, including what works there in this release, read [Alerts in /trading](/script/alerts/alerts-in-trading).

## A first alert

A complete study that draws two moving averages and raises an alert on each crossover:

```openscript
version 1

study("EMA cross alerts", overlay = true, precision = 2)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)

fast = ema(close, fastLen)
slow = ema(close, slowLen)

plot(fast, "Fast EMA", aqua,   width = 2)
plot(slow, "Slow EMA", orange, width = 2)

if crossUp(fast, slow)
    alert(chart.symbol + ": fast EMA crossed above slow at " + text(close, 2),
          id = "cross-up", title = "EMA cross up")

if crossDown(fast, slow)
    alert(chart.symbol + ": fast EMA crossed below slow at " + text(close, 2),
          id = "cross-down", title = "EMA cross down")
```

Three things are worth seeing before any detail:

- **The condition is an ordinary `if`.** There is no separate function for declaring a condition. The `if` you would have written anyway is the condition the alert watches.
- **The message is computed on the bar that fired.** `text(close, 2)` is that bar's close, so the alert says "crossed above slow at 812.45" rather than only "crossed".
- **Each alert has an `id` and a `title`.** The `id` is the alert's permanent name; the title is the heading a person reads. Both are explained below.

By the rules of the language, this study raises one alert on the bar where a crossing is confirmed (the bar has closed), and nothing for the crossings already in the chart's history.

> **On the /trading chart in this release**
The /trading chart checks each bar for a script's alerts once, when the bar first reaches the chart. During trading hours a bar arrives with its first tick, before it has closed, so an `alert()` waiting for the close (every alert, unless the file opts out) has nothing to report yet, and the chart does not look at that bar again. Such an alert can still fire for a bar that arrives late, after its time has passed, but you cannot rely on it. To be told about a script's condition on /trading today, plot the condition and put a study alert on that plot, as [Alerts on a script condition](/script/alerts/alerts-in-trading#alerts-on-a-script-condition) shows. A strategy deployed from the Strategies panel is not affected: it runs on the OpenAlgo server and writes its alerts to the run's log.

## The three calls at a glance

| | `alert()` | `signal()` | `notify()` |
|---|---|---|---|
| What it does | Sends a message about this bar | Draws a marker on this bar | Sends a message to a named channel |
| Where it shows | Off the chart, wherever the platform delivers it | On the chart, on the bar | A channel the platform has configured |
| On the bars already in history | Fires for none of them | Draws on every past bar that matched | Not applicable |
| On a bar that is still forming | Waits for the bar to close, unless the file sets `onUnconfirmed = true` | The same as `alert()` | Not applicable |
| Status in version 0.5.0 | Available | Available | Planned, refused by the compiler with OS2020 |

A marker is a record of what the script saw, drawn back over the whole history so you can judge the rule by eye. An alert is a message about the bar in front of you. Most useful studies want both, and each costs one line.

## alert()

`alert(message, id = "", title = "", frequency = "oncePerBar")` returns nothing.

| Argument | Type | Default | What it is |
|---|---|---|---|
| `message` | `string` | required | The text the fired alert carries, worked out on the bar that fired |
| `id` | `string` | `""` | The alert's stable name. Always give one |
| `title` | `string` | `""` | A short heading shown above, or in place of, the message |
| `frequency` | `string` | `"oncePerBar"` | How often the same alert may fire: `"oncePerBar"`, `"once"` or `"everyUpdate"` |

`id`, `title` and `frequency` are fixed before the first bar runs; only `message` is read on every bar. `title` and `frequency` may each be a literal or an `input()`. Write `id` as a plain string literal such as `"cross-up"`: an `id` from an `input()`, or one joined with `+`, does not count as a fixed name (see [The id is a promise](#the-id-is-a-promise)). A title built from bar data is refused:

```openscript
alert("Crossed", id = "cross", title = "Crossed at " + text(close, 2))
```

An `alert()` call may stand anywhere a statement may: at the top level, inside an `if` or a `for`, or inside the body of a function you define with `fn`. At the top level with no guard it fires on every confirmed bar, which is occasionally what you want and usually is not.

### The condition is the if around it

The compiler turns each `alert()` call into one **watched condition**: a named entry the platform checks as new bars arrive. The condition's test, called its **predicate**, is the chain of `if` guards that leads to the call. Nesting works exactly as reading the file suggests: an alert two branches deep has both guards in its predicate, joined by `and`.

```openscript
hh = highest(high, 20)[1]

if session.isIn("0915-1530")
    if close > hh
        alert("Twenty bar breakout", id = "breakout")
```

The predicate of that alert is "the bar is inside 09:15 to 15:30, and the close is above the previous twenty-bar high". Because it is built from the source, it can never describe a condition the script does not actually test.

> **Compute running values such as `highest()` at the top level and only test them inside the `if`. A stateful call written inside a branch advances only on the bars where that branch runs, and the compiler warns about it with OS8001.**

## Building the message

A message is an ordinary `string` expression, evaluated on the bar the predicate accepted, so every value in it is that bar's value. There is no placeholder syntax in the language: you build the text with `+` and `text()`. Three rules decide what you can put in it.

**There is no automatic conversion between numbers and strings.** Adding a number to a string is an error, not a sentence:

```openscript
alert("Close " + close, id = "close-note")
```

Numbers reach a message through `text()`, which has two forms:

- `text(x, 2)` writes `x` with exactly two decimals, rounding halves away from zero. This is the form for a price.
- `text(x)` writes the value with every digit it carries, so a price can come out as `101.24199999999999`. Keep it for whole numbers, and for values you do not want rounded.

**Absence spreads through `+`.** An absent value (`none`, see [Absent values](/script/language/absent-values)) makes the whole expression absent, because `"a" + none` is `none`. The two forms of `text()` treat an absent value differently, and so do the usual fixes:

| Written | When `r` is 63.28 | When `r` is absent |
|---|---|---|
| `text(r)` | `63.28` | `none`, the four letters |
| `text(r, 1)` | `63.3` | Absent, so the whole message is absent |
| `isNone(r) ? "not ready" : text(r, 1)` | `63.3` | `not ready` |
| `text(orElse(r, 0), 1)` | `63.3` | `0.0` |

Most of the time the `if` takes care of this: an alert whose condition tests `r` only fires on bars where `r` has a value. It matters for the parts of the message the condition does not test, which are often the values that are absent during [warmup](/script/language/warmup), after a gap, or on an index with no volume:

```openscript
r  = rsi(close, 14)
rv = relativeVolume(20)

// crossUp needs r, so r has a value whenever this fires.
// rv is not part of the condition, so say what to write when it is absent.
if crossUp(r, 50)
    alert("RSI crossed 50 at " + text(r, 1) + ", relative volume "
          + (isNone(rv) ? "not available" : text(rv, 2)),
          id = "rsi-50", title = "RSI crossed 50")
```

Where a script's message does come out absent, the /trading chart shows the alert's `title` in its place. That is one more reason to give every alert a title.

**Pick the ingredients from what the bar knows.** Everything here is a fact any script can read, where the host states it:

| Want in the message | Write |
|---|---|
| The instrument | `chart.symbol`, and `orElse(chart.exchange, "")`: the /trading chart does not state the exchange, and an absent part would make the whole message absent |
| The chart's interval | `chart.interval` |
| The bar's price | `text(close, 2)` |
| A computed value | `text(atr(14), 2)` |
| The bar's time, in the chart's time zone | `date.format(time, "yyyy-MM-dd HH:mm")` |
| Which side fired | a conditional: `up ? "Long" : "Short"` |
| The open position, in a strategy | `text(pos.size)`, `text(pos.avgPrice, 2)` |

A message that carries enough to act on without opening the chart:

```openscript
version 1

study("RSI extremes", precision = 2, range = [0, 100])

len = input(14, "RSI length", min = 2, max = 200)
hi  = input(70, "Overbought", min = 50, max = 100)
lo  = input(30, "Oversold",   min = 0,  max = 50)

r = rsi(close, len)

level(hi, "Overbought", red)
level(lo, "Oversold",   lime)
plot(r, "RSI", purple, width = 2)

// Test the change, not the state: the bar RSI leaves a zone.
leftHigh = not isNone(r) and r < hi and orElse(r[1], 0) >= hi
leftLow  = not isNone(r) and r > lo and orElse(r[1], 100) <= lo

if leftHigh
    alert(chart.symbol + " " + chart.interval + ": RSI left overbought at "
          + text(r, 1) + ", price " + text(close, 2)
          + ", " + date.format(time, "yyyy-MM-dd HH:mm"),
          id = "rsi-left-high", title = "RSI left overbought")

if leftLow
    alert(chart.symbol + " " + chart.interval + ": RSI left oversold at "
          + text(r, 1) + ", price " + text(close, 2)
          + ", " + date.format(time, "yyyy-MM-dd HH:mm"),
          id = "rsi-left-low", title = "RSI left oversold")
```

`date.format()` reads the timestamp in the chart's time zone, so the time in the message agrees with the time under the bar on the chart's axis.

When both directions share one alert, a conditional picks the word:

```openscript
up   = crossUp(ema(close, 9), ema(close, 21))
down = crossDown(ema(close, 9), ema(close, 21))

if up or down
    alert((up ? "Long" : "Short") + " signal on " + chart.symbol + " at " + text(close, 2),
          id = "ema-flip", title = "EMA flip")
```

## When an alert fires

An alert obeys the rule every signal and order obeys: **it does not fire on a bar that is still forming.** A call made on a forming bar is held until the bar is confirmed (closed), and if the condition that produced it is no longer true at the close, it never fires at all.

Take a 5-minute chart of NIFTY futures. At 10:31 the price pokes above the twenty-bar high; by the 10:35 close it has slipped back below. The condition was true for a few seconds and false when the bar finished, so no alert is sent. A level that is touched and rejected has not been broken, and an alert that said it had would be noise. The full story is on [Realtime and confirmation](/script/language/realtime-and-confirmation).

Two more facts about firing catch people out once:

- **Adding a study to a chart fires nothing for the history already on it.** An alert is a statement about now. A study added at noon that sent four hundred alerts for the morning would bury the one that mattered.
- **An absent condition counts as false.** An alert inside an `if` whose test is absent during [warmup](/script/language/warmup) does not fire. That is correct, and it is also the most common reason a new alert seems dead.

### frequency

`frequency` decides how often one alert may fire.

| Value | Means | Use it for |
|---|---|---|
| `"oncePerBar"` | At most one alert per bar. The default | Almost everything |
| `"once"` | The first time only, for the life of this study on the chart | A one-off level, a note at the session open |
| `"everyUpdate"` | On every run of the bar, tick by tick. Requires `onUnconfirmed = true` | Watching a level while the bar forms, accepting the noise |

`"everyUpdate"` without `onUnconfirmed = true` in the declaration is refused at compile time rather than quietly downgraded:

```openscript
version 1

study("Every update")

if close > open
    alert("Rising", id = "rising", frequency = "everyUpdate")

plot(close, "Close")
```

A file opts in to acting on a forming bar by declaring `onUnconfirmed = true`. From then on the script is responsible for its own guards: add `and bar.isConfirmed` to any alert that should still wait for the close.

```openscript
version 1

study("Fast breakout alerts", overlay = true, onUnconfirmed = true)

upper = highest(high, 20)[1]
plot(upper, "Twenty bar high", aqua)

// The file now acts on a forming bar, so it guards itself:
// this fires once, on the bar's close.
if crossUp(close, upper) and bar.isConfirmed
    alert("Closed above the twenty bar high at " + text(close, 2), id = "breakout")

// This one watches the level tick by tick while the bar is still forming.
if close > upper
    alert("Trading above the twenty bar high at " + text(close, 2),
          id = "above-high", frequency = "everyUpdate")
```

Setting `onUnconfirmed` also makes the compiler warn (OS8002) about any higher timeframe read in the file, because a forming bar reading a coarser bar is where [repainting](/script/data/repainting) comes from.

> ****The /trading chart keeps no `frequency`.** It judges a script's alerts at most once per bar, so `"once"` and `"everyUpdate"` behave as `"oncePerBar"` there. When you want one alert per session or per day, hold a [`var`](/script/language/persistence) flag in the script and test it in the condition, as the opening range example below does.**

## The id is a promise

`id` is the stable name of a watched condition. The platform files each firing under it, and the name has to survive the edits you make to the script later. On /trading the `id` is also how repeats of the same alert are recognised: a desktop notification for an alert that fires again replaces the previous one instead of stacking a new one beside it.

With no `id`, the compiler names the alert after its line in the file, such as `alert@10`, and warns with OS8008. That derived name changes the moment a line is inserted above the call. An `id` taken from an `input()`, or joined together with `+`, gets the same treatment and the same warning, because only a plain string literal is fixed before the first bar.

```openscript
if crossUp(close, sma(close, 50))
    alert("Crossed the 50 bar average")
```

Two alerts in one file may not share an `id`:

```openscript
if crossUp(close, sma(close, 50))
    alert("Crossed up", id = "cross")
if crossDown(close, sma(close, 50))
    alert("Crossed down", id = "cross")
```

Keep ids short, lowercase and hyphenated, and name the event rather than the number:

| id | Verdict |
|---|---|
| `"cross-up"` | Good. Short, stable, says what happened |
| `"rsi-left-high"` | Good. Still true when the thresholds change |
| `"rsi-below-30"` | Poor. Wrong the day the input is set to 25 |
| no id at all | Poor. Derived from the line, so an edit above it renames the alert |

`title` is not a substitute for `id`. The title is text for a person and you may change it at will; the id is a key, and renaming it makes it a different alert.

## signal(): a marker on the bar

`signal(text, color = none, at = "above", shape = "label")` draws a named marker on the current bar. It is the whole of shape drawing in OpenScript, and it is covered in depth on [Labels and shapes](/script/visuals/labels-and-shapes).

| Argument | What it takes |
|---|---|
| `text` | The marker's text, read per bar, so it can carry values: `"UP " + text(close, 2)` |
| `color` | A colour, or `none` for the chart's default |
| `at` | `"above"`, `"below"` or `"price"` |
| `shape` | `"label"`, `"arrowUp"`, `"arrowDown"`, `"triangleUp"`, `"triangleDown"`, `"circle"`, `"square"`, `"diamond"`, `"cross"` or `"flag"` |

```openscript
if crossUp(ema(close, 9), ema(close, 21))
    signal("BUY", color = lime, shape = "arrowUp", at = "below")
```

Like `alert()`, a signal waits for the bar to close unless the file sets `onUnconfirmed = true`. Unlike `alert()`, it draws on every bar of the history where its condition held, which is what lets you check a rule by scrolling back through a month of NSE bars. `color`, `at` and `shape` are fixed before the first bar, so each must be a literal or an `input()`; only the text changes from bar to bar.

## notify(): planned

`notify(message, channel)` is specified as a way for a script to send a message to a channel the platform has already configured, by name. It is **planned and not in version 0.5.0**, and the compiler refuses it with OS2020:

```openscript
if crossUp(close, sma(close, 50))
    notify("Crossed the 50 bar average", "desk")
```

Until it arrives, the only delivery a script declares is an `alert()`. Where the message goes after that (a sound, a desktop notification, a messaging channel) is the platform's decision, made outside the script, so the same file compiles and runs everywhere. On /trading, see [How a firing reaches you](/script/alerts/alerts-in-trading#how-a-firing-reaches-you).

## Complete examples

### A breakout alert

A channel of the previous twenty bars' high and low, with an alert on the bar the close first leaves it. `crossUp()` and `crossDown()` test the change, so a strong run fires once at the break rather than on every bar above the line.

```openscript
version 1

study("Channel breakout alerts", overlay = true, precision = 2)

len = input(20, "Lookback, in bars", min = 2, max = 200)

upper = highest(high, len)[1]
lower = lowest(low, len)[1]

plot(upper, "Channel high", aqua,   width = 2, style = "step")
plot(lower, "Channel low",  orange, width = 2, style = "step")

if crossUp(close, upper)
    alert(chart.symbol + " closed above its " + text(len) + " bar high at "
          + text(close, 2), id = "channel-break-up", title = "Channel break up")

if crossDown(close, lower)
    alert(chart.symbol + " closed below its " + text(len) + " bar low at "
          + text(close, 2), id = "channel-break-down", title = "Channel break down")
```

### An alert and a marker, once per session

The high and low of the first minutes of each session, with one alert and one marker for the first break of either side. On an NSE chart the session opens at 09:15, so the default range is 09:15 to 09:30. The session's first bar is `session.isFirstBar` where the host states session hours; the /trading chart does not in this release, so the study falls back to the first bar of each IST day, which on NSE is the same bar.

```openscript
version 1

study("Opening range break", overlay = true, precision = 2)

rangeMinutes = input(15, "Opening range, in minutes", min = 1, max = 240)

// The session's first bar, or the first bar of each IST day where the host
// states no session hours.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

var openTime  = none
var rangeHigh = none
var rangeLow  = none
var broken    = false

if newSession
    openTime  = time
    rangeHigh = high
    rangeLow  = low
    broken    = false

elapsed = isNone(openTime) ? none : time - openTime
forming = not isNone(elapsed) and elapsed < rangeMinutes * 60000

if forming and not newSession
    rangeHigh = max(rangeHigh, high)
    rangeLow  = min(rangeLow, low)

// broken is a var, so this block runs at most once per session.
if not forming and not broken and not isNone(rangeHigh)
    if close > rangeHigh
        broken = true
        signal("BREAK UP", shape = "triangleUp", at = "below")
        alert(chart.symbol + " broke the opening range high at "
              + text(close, 2), id = "or-break-up", title = "Range break up")
    else if close < rangeLow
        broken = true
        signal("BREAK DOWN", shape = "triangleDown", at = "above")
        alert(chart.symbol + " broke the opening range low at "
              + text(close, 2), id = "or-break-down", title = "Range break down")

plot(rangeHigh, "Range high", aqua,   width = 2, style = "step")
plot(rangeLow,  "Range low",  orange, width = 2, style = "step")
```

The `broken` flag does the work `frequency` cannot: `"oncePerBar"` limits an alert to one per bar, and this study wants one per session. A `var` is how a script remembers "already handled". Because a `var` is rolled back before each re-run of a forming bar, the flag behaves the same on the chart as it does in a backtest.

### A multi-condition alert

Several conditions, each given a name, joined with `and` in one `if`. This one looks for a pullback in an uptrend: price above a rising 50-bar EMA, RSI turning up through 40, volume at least one and a half times its average, and the bar inside 09:30 to 15:00 so the noisy first and last minutes of the session are left out.

```openscript
version 1

study("Trend pullback alerts", overlay = true, precision = 2)

trendLen  = input(50,  "Trend EMA length", min = 5, max = 400)
dipLevel  = input(40,  "RSI dip level", min = 10, max = 60)
volFactor = input(1.5, "Volume above its average by", min = 1, max = 5)

trend = ema(close, trendLen)
r     = rsi(close, 14)
rv    = relativeVolume(20)

// Four conditions, each named, so the alert reads as a sentence.
upTrend  = close > trend and trend > trend[5]
bounced  = crossUp(r, dipLevel)
volumeOk = orElse(rv, 0) >= volFactor
inHours  = session.isIn("0930-1500")

plot(trend, "Trend EMA", orange, width = 2)

if upTrend and bounced and volumeOk and inHours
    signal("DIP", shape = "triangleUp", at = "below")
    alert(chart.symbol + " " + chart.interval + ": RSI bounced to " + text(r, 1)
          + " in an uptrend, close " + text(close, 2)
          + ", volume " + text(orElse(rv, 0), 1) + " times its average",
          id = "trend-dip", title = "Pullback in an uptrend")
```

Naming each condition keeps the `if` readable and makes each one easy to check on its own. When the alert does not fire and you want to know which part is false, copy that condition into a small study without `overlay = true`, which draws in a pane of its own, and plot it as `upTrend ? 1 : 0`. Only one of the four, `bounced`, is a change; the other three are states that filter it. An alert built only from states would fire on every bar they all hold.

### Alerts in a strategy

`alert()` works in a strategy file too, and the message can read the position:

```openscript
version 1

strategy("EMA cross with alerts", overlay = true, precision = 2)

fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow)
    buy(qty = 1)
    alert("Long entry signal at " + text(close, 2) + ", buying 1", id = "entry", title = "Entry")

if crossDown(fast, slow) and pos.isLong
    close()
    alert("Exit signal: closing the long entered at " + text(pos.avgPrice, 2), id = "exit", title = "Exit")

plot(fast, "Fast EMA", aqua)
plot(slow, "Slow EMA", orange)
```

The alert describes the order being sent, not a fill. By default a market order fills at the next bar's open, so on the bar that raises the exit alert the long is still open, and `pos.avgPrice` still reads the price it was entered at.

A strategy added to the /trading chart is drawn like a study, and the chart judges its alerts the same way it judges a study's (see the warning near the top of this page). A strategy deployed from the Strategies panel runs on the OpenAlgo server rather than on the chart. It sends nothing while it replays history at the start; after that, each alert it raises is written as a line in that run's log, such as `Alert exit: Exit Exit signal: closing the long entered at 812.45`, rather than being sent anywhere. See [Sandbox and live](/script/strategies/sandbox-and-live).

## What goes wrong

| Symptom | Cause | Fix |
|---|---|---|
| A study's alert does not fire on the /trading chart | In this release the chart checks each bar only as it arrives, before a waiting alert can fire | Plot the condition and put a study alert on it: [Alerts on a script condition](/script/alerts/alerts-in-trading#alerts-on-a-script-condition) |
| Nothing fires, ever, anywhere | The condition is absent during warmup and false afterwards, or it is never true | Plot the condition as `cond ? 1 : 0` from a study of its own and look at the line |
| Fires on every bar of a trend | The condition tests a state, not a change | Test the change: `crossUp()`, a comparison with `[1]`, or a `var` flag |
| Fired, and then the bar closed the other way | `onUnconfirmed = true` without a `bar.isConfirmed` guard | Remove `onUnconfirmed`, or add the guard |
| Nothing fired for the morning's crossings | The study was added after them. History fires nothing | Expected. Look at the markers from `signal()` to judge the past |
| The message shows only the title | Part of the message was absent on that bar, often a `text(x, 2)` of an absent `x` | Give that part a fallback with `isNone()` or `orElse()`, or use one-argument `text(x)` |
| `"a" + 5` refused with OS2003 | No automatic conversion between string and number | `"a" + text(5)` |
| OS8008 warning | The alert has no fixed `id` | Write the id out as a plain string literal |
| OS3017 | Two alerts share an `id` | Rename one |
| `"everyUpdate"` refused with OS3009 | It needs `onUnconfirmed = true` in the declaration | Set it, or use `"oncePerBar"` |
| A `"once"` alert fires on more than one bar on the chart | The chart keeps no `frequency` | Hold a `var` flag and test it in the condition |

More cases are on [Troubleshooting](/script/writing/troubleshooting). For the codes quoted here, see [Names and types](/script/errors/names-and-types#os2003), [Arguments](/script/errors/arguments#os3009) and [Warnings](/script/errors/warnings#os8008).

**Related.** [Alerts in /trading](/script/alerts/alerts-in-trading), [Realtime and confirmation](/script/language/realtime-and-confirmation), [Labels and shapes](/script/visuals/labels-and-shapes), [Persistence](/script/language/persistence), [Repainting](/script/data/repainting), [Alerts and logging reference](/script/reference/alerts-and-logging)


## Alerts in /trading

Source: https://openalgo.in/script/alerts/alerts-in-trading

This page is about alerts as you meet them on the /trading page of OpenAlgo: setting a price alert with one right-click, putting an alert on a study's line (including a study you wrote in OpenScript), what an `alert()` in your script does on the chart in this release, how each firing reaches you, and how to read and manage everything in the **Alerts** panel on the right-hand toolbar. For writing `alert()` calls in a script, see [Alerts from scripts](/script/alerts/overview).

> **Alerts run in your browser**
Alerts are checked by the chart that is open in /trading, so an alert fires only while /trading is open in a browser tab. The tab may sit behind other windows: a chart with an Active alert keeps fetching bars while it is hidden. Close the tab and nothing fires until you open it again. What did fire is kept on the OpenAlgo server, in the Log tab, for 90 days.

## Four kinds of alert

| Kind | What it watches | How you create it | Listed in the Alerts tab |
|---|---|---|---|
| Price alert | The instrument's price against a level or a channel | Right-click the price pane, or the **Alerts** button on the chart toolbar | Yes |
| Study alert | One plot of a study on the chart, built-in or your own script, against a value | Right-click the study's line, or the **Alerts** button | Yes |
| Drawing alert | A level of a drawing, such as a trend line | Right-click the drawing, or the **Alerts** button | Yes |
| Script alert | An `alert()` call in an OpenScript study on the chart | Nothing to create: it is watched once the study is on the chart | No. Any firing appears in the Log tab |

The first three are alerts you set on the chart, and each has a row in the Alerts tab with its own settings. The fourth is written into the script itself: its condition and message come from the code. In this release a script alert rarely fires on the chart (see [Alerts from a script](#alerts-from-a-script)), so to be told about a condition your script computes, use a study alert on a plot of it, as [Alerts on a script condition](#alerts-on-a-script-condition) shows.

## A price alert in one click

1. Right-click the price pane at the level you want to watch.
2. Choose **Create price alert at** followed by the price, for example `Create price alert at 812.45`.

The alert is made there and then, with no form in between. A toast confirms it, such as `Alert set: SBIN crossing 812.45`, and a line appears across the chart at the level. The price is rounded to the instrument's tick size, so a point picked between two ticks becomes a price the instrument can actually trade at.

A right-click alert takes these settings, and every one can be changed afterwards with **Edit** on its row:

| Setting | Value |
|---|---|
| Condition | Crossing |
| Evaluate | Intrabar touch |
| Repeat | Only once |
| Expiration | Two months from now |
| When it fires | Sound and Desktop notification |
| Alert name | Written from the alert, such as `SBIN crossing 812.45` |


To move a price alert, drag its line. The new price is rounded to the tick, and a name the page wrote for you follows the new price; a name you typed yourself is left alone.

## The Create alert dialog

Click **Alerts** on the chart toolbar, beside **Indicators**, to open **Create alert** for the chart in that pane. The line under the title names the instrument, such as `SBIN on this chart`. The same dialog opens as **Edit alert** from the **Edit** action on a row of the Alerts panel, with **Save** in place of **Create**.

The dialog reads top to bottom as a sentence: what to watch, when to fire, when to stop, what to call it and how to tell you.

### Condition

**What to watch** picks the source:

| What to watch | Then choose | Value |
|---|---|---|
| Price | Nothing more | A price, seeded with the latest close |
| Study plot | **Study** (numbered as in the chart legend, such as `1: RSI`) and **Plot** | A value in the plot's own units, seeded with the plot's latest value |
| Drawing level | **Drawing** (each drawing by its tool and a number) and **Level** | None: the drawing supplies its own level on every bar |

The list also offers **Candle condition**, but the dialog has no control yet for picking which candle pattern to watch, so saving one stops at "Choose a candle condition."

**Condition** then decides what counts as a hit:

| Condition | Fires when the watched value |
|---|---|
| Crossing | Moves from one side of the level to the other, in either direction |
| Crossing up | Moves from at or below the level to above it |
| Crossing down | Moves from at or above the level to below it |
| Greater than | Is above the level |
| Less than | Is below the level |
| Entering channel | Moves from outside the band between **Lower** and **Upper** to inside it |
| Leaving channel | Moves from inside that band to outside it |

The two channel conditions replace the single value box with **Lower** and **Upper**. Greater than and Less than test a state rather than a crossing: with Repeat set to Every time they fire again on every bar the value stays beyond the level, so pair them with Only once unless you want to hear about it again.

### Trigger

| Control | Choices |
|---|---|
| Repeat | **Only once**: fire the first time, then show as Fired. **Every time**: keep watching after each firing, firing at most once per bar |
| Evaluate | **On bar close** or **Intrabar touch** |

The hint under Evaluate says what each one costs. **On bar close**: "Fires on a confirmed bar. A wick that is later revised will not fire it." **Intrabar touch**: "Fires the moment price touches, including on a wick that final history may not keep." On bar close is the same rule an OpenScript `alert()` follows by default. Intrabar touch is quicker and noisier, and it is what every new alert starts with.

### Expiration

Tick **Expires** and pick a date and time, or leave it unticked for an alert that watches until you remove it ("Open-ended: this alert keeps watching until you remove it."). A new alert starts ticked, two months ahead. The time is on the chart's own clock, and the hint underneath names the time zone, the same one as the chart's time axis.

### Alert name

The first box is the name. Leave it empty and the name is written for you from the alert, such as `RSI crossing up 30`; the box shows that name as its placeholder. The second box is an optional **Message**.

A message can carry values filled in at the moment the alert fires. Type a placeholder in double braces. The first five in the table are also shown as buttons under the box, and clicking one adds it to the end of the message.

| Placeholder | Fills in |
|---|---|
| `{{ticker}}` | The instrument, as the chart names it. `{{symbol}}` works too |
| `{{exchange}}` | The exchange it trades on |
| `{{interval}}` | The chart's timeframe. `{{timeframe}}` works too |
| `{{price}}` | The value that met the condition |
| `{{close}}` | The fired bar's close |
| `{{open}}` | The fired bar's open |
| `{{high}}` | The fired bar's high |
| `{{low}}` | The fired bar's low |
| `{{volume}}` | The fired bar's volume, as a whole number |
| `{{time}}` | The time of the bar the alert fired on |
| `{{timenow}}` | The moment it was delivered |

For example, `{{ticker}} crossed {{price}}, close {{close}} on {{interval}}` arrives as a sentence you can act on from a notification. Prices are written with the instrument's own decimals. The two times are written like `2026-09-23 10:35`, on your computer's clock rather than the chart's. A placeholder spelled wrong is left exactly as you typed it, and so is one the chart has no value for: a bar that carries no volume keeps `{{volume}}` rather than claiming zero.

Below the message, **Active as soon as it is saved** is ticked by default. Untick it to save the alert in the Stopped state.

### When it fires

Four boxes choose how you are told, per alert:

| Box | Ticked for a new alert | What it needs |
|---|---|---|
| Sound | Yes | Nothing |
| Desktop notification | Yes | Your browser's permission, which the page asks for when you create or save an alert |
| Telegram | No | "Needs the bot running and your account linked" |
| WhatsApp | No | "Needs a paired device" |

Sound and the desktop notification never leave your machine. Telegram and WhatsApp send the message out, and each must be set up on its own page in OpenAlgo first. See [How a firing reaches you](#how-a-firing-reaches-you).

### Saving

Click **Create** (or **Save**). If something is missing, the dialog says what in red and does not close:

| Message | What to do |
|---|---|
| Enter a price to watch. | Type a price in the value box |
| Enter a value to watch. | Type a value for the study plot |
| A channel needs both of its bounds. | Fill in Lower and Upper |
| A channel needs two different bounds. | Make Lower and Upper differ |
| Choose a study. This chart has none, or the one chosen has been removed. | Add a study to the chart, or pick another |
| Choose a plot from that study. | Pick a plot (see [Study alerts](#study-alerts-including-on-your-own-scripts) for which entries are plots) |
| Drawings are still loading. Try again in a moment. | Wait a moment and click again |
| Draw something on the chart first, then alert on its level. | Draw first, then open the dialog |
| Choose which level of that drawing to watch. | Pick a level |
| Enter an expiry date and time, or switch the expiry off. | Complete the date and time, or untick Expires |

## Study alerts, including on your own scripts

A study alert watches one plotted line against a value. It works the same on a built-in study and on a study you wrote in OpenScript, because a saved script sits on the chart like any other study.

The quickest way is to right-click the study's line and choose **Create study alert**. The alert is made at once, with the condition Crossing and the value the line had at the bar under the pointer, and otherwise the same settings as a right-click price alert. Open **Edit** on its row to change the condition or the value.

From the dialog, set **What to watch** to **Study plot**, then pick the **Study** and the **Plot**. For a study written in OpenScript the Plot list shows the keys the chart gives each `plot()`: `p0` for the first `plot()` in the file, `p1` for the second, and so on. The list also shows other columns the script produces, such as `openscript:alert:0` for its first `alert()`. Those are not plots, and picking one stops at "Choose a plot from that study." Right-clicking the line is the surest way to get the plot you mean. The value is in the plot's own units: an RSI threshold of 70 is 70, and it is not rounded to the instrument's tick.


A study alert set to **Intrabar touch** reads the plot as it moves during the bar; set **On bar close** to judge only the value the bar closed with.

An alert on a study you later remove from the chart can no longer be checked. Its row says why, and it is dropped the next time the chart restores its alerts.

## Drawing alerts

Right-click a drawing and choose **Create drawing alert**. When a drawing cannot carry an alert the entry is greyed out, and hovering it tells you why. From the dialog, choose **Drawing level**, then the **Drawing** and which **Level** of it to watch. There is no value to type: a trend line is at a price on every bar, and that price is the level.

## Alerts from a script

A study that calls `alert()` needs no setup to be watched. Save it in the Scripts panel and put it on the chart with **Apply to chart** there, or from the **Indicators** dialog. Each `alert()` in it becomes a condition the chart checks as new bars arrive, starting from the next new bar.

> **Script alerts do not fire reliably on the chart in this release**
The chart checks each new bar for a script's alerts once, at the moment the bar first reaches the chart, and does not look at that bar again after it closes. During trading hours a bar arrives with its first tick, and an `alert()` waits for its bar to close unless the file sets `onUnconfirmed = true`, so at that moment it has nothing to report. It fires only for a bar that arrives late, after its time has passed. A file that does set `onUnconfirmed = true` fires only when its condition already holds on the first update of a new bar, with the message worked out from that update. Until this is fixed, use the route in [Alerts on a script condition](#alerts-on-a-script-condition), which works with every delivery channel.

When a script alert does fire on the chart:

- a toast shows the script's message, or its title when the message is absent on that bar;
- the alert sound plays, and a desktop notification appears if the /trading tab is hidden and the browser allows notifications;
- a row is added to the **Log** tab, with the title, the message and the symbol.

A few things set script alerts apart from the alerts you create on the chart:

- **They are not in the Alerts tab**, so they have no Stop, Edit or Delete. To silence them, remove the study from the chart, or take the `alert()` out of the script and save it.
- **Delivery is fixed** at Sound and Desktop notification. There is no box to tick for Telegram or WhatsApp.
- **Nothing fires for history.** Adding the study, or changing its settings, recalculates the past without sending anything. Only bars that arrive afterwards are checked.
- **At most once per bar.** The chart checks each `alert()` once for each new bar, so the `frequency` values `"once"` and `"everyUpdate"` behave as `"oncePerBar"` here. See [frequency](/script/alerts/overview#frequency).
- **The id names the alert.** A repeat of the same `id` replaces its previous desktop notification rather than stacking another beside it. Give every alert a fixed `id` and a `title`, as [The id is a promise](/script/alerts/overview#the-id-is-a-promise) explains.

A strategy deployed from the Strategies panel runs on the OpenAlgo server rather than on the chart, so the limit above does not apply to it. It sends nothing while it replays history at the start; after that, each alert it raises is written as a line in that run's log, and it is not sent to this panel, the sound or any channel.

## Alerts on a script condition

The dependable way to be told about something your script computes is to plot the condition as 1 or 0 and put a study alert on that plot. Set to **On bar close**, the study alert is judged on the closed bar, the same rule a script's `alert()` follows. It also has everything the dialog offers (a name, message placeholders, an expiry, repeat) and can go to Telegram or WhatsApp as well as the sound and the desktop notification.

```openscript
version 1

study("Breakout flag", precision = 0, range = [0, 1])

upper = highest(high, 20)[1]
broke = crossUp(close, upper)

// 1 on the bar the close breaks the twenty bar high, 0 on every other bar.
plot(broke ? 1 : 0, "Breakout", aqua, style = "step")
```

1. Save the study and add it to the chart.
2. Click **Alerts** on the chart toolbar to open **Create alert**.
3. Set **What to watch** to **Study plot**, pick this study, and pick plot `p0`.
4. Set **Condition** to **Crossing up** and type `0.5` as the value, so the alert fires on the bar the flag steps from 0 to 1.
5. Set **Evaluate** to **On bar close** and **Repeat** to **Every time**.
6. Tick the channels you want under **When it fires**, and click **Create**.

During warmup `broke` is absent, and an absent condition takes the second branch of `? :`, so the plot reads 0 there rather than leaving a gap. To watch several conditions, give each its own `plot()` (they become `p0`, `p1` and so on) and set one study alert per plot.

## The Alerts panel

Click **Alerts** on the right-hand toolbar, between **Objects** and **Scripts**. The panel opens beside the chart, so you can keep it open while you watch the prices it is waiting for.


- **The header** reads **Alerts**, with the pane it describes underneath, such as `Pane 1 · NSE:SBIN`. With several panes on screen it describes the pane you are working in.
- **The menu** at the right of the header holds **Start all**, **Stop all** and **Remove all alerts**.
- **Two tabs**, **Alerts** and **Log**, each with a count.
- **A search box** under the tabs (Search alerts, or Search log) that matches the name, the message and the symbol.
- On the Alerts tab, **a sort** by **Status**, **Name** or **Recently fired**. On the Log tab, a **Clear** button in its place.

There is no New button. You create an alert where the price is, from the chart, and the panel shows what is already watching.

### The Alerts tab

One row per alert on this pane. Each row shows:

- the alert's name, and the time it last fired (only the time when that was today, the date and time otherwise);
- its message, or when it has none, what it is waiting for, such as `Crossing up 812.45`;
- the symbol and timeframe it was made on, such as `SBIN · 5m`;
- its state.

| State | Means |
|---|---|
| Active | Watching |
| Fired | A Repeat Only once alert that has fired. It stays in the list, and its line is removed from the chart |
| Stopped | Paused by you, or saved with Active as soon as it is saved unticked |
| Expired | Its expiry time has passed. It stays in the list, and its line is removed from the chart |

Sorting by Status puts Active first, then Fired, Stopped and Expired.

A row can read Active and still not be checked at this moment. It then carries one more line saying why. The common ones:

| Line | Why | What to do |
|---|---|---|
| Switch to 5m to evaluate this alert | An alert is checked only on the timeframe it was made on. A price alert's line still shows on other timeframes | Switch the pane back to that timeframe |
| Instrument context differs | The pane now shows another symbol | Switch back to the symbol the alert was made on |
| Alerts are paused | The chart is in bar replay, a workspace is still loading, or the chart has no data | Leave replay or wait; alerts resume on their own |

Hover a row for its three actions: **Stop** (or **Start** on a stopped alert), **Edit**, which opens the Edit alert dialog, and **Delete**.

### The Log tab

One row per firing, newest first, from every pane and every chart. Each row shows:

- the alert's name and a time: for a row added while the page is open, the time of the bar it fired on; for a row read back from the server, when it was recorded;
- the message, with its placeholders filled in;
- the symbol, and the price at which it fired when the firing carried one;
- on rows read back from the server, the channels that accepted the message: `sound`, `notification`, `telegram`, `whatsapp`.

Rows added while the page is open show no channels. After a reload, a row with no channels listed is an alert that fired and reached nobody, which is a different thing from an alert that never fired.

The log is kept on the OpenAlgo server for 90 days, so firings survive a closed tab. When /trading opens, the page reads back the latest 200, and it holds up to 200 firings while it stays open, dropping the oldest first. **Clear** empties the log on the server as well; if the server does not agree, a toast says "The log could not be cleared. It will be back on the next reload."

## How a firing reaches you

Every firing shows a toast on the page with the alert's message (or its name, when there is no message) and is written to the Log. The alert's **When it fires** boxes then decide the rest:

| Channel | What you get |
|---|---|
| Sound | Two short rising tones from the /trading tab |
| Desktop notification | Your operating system's notification, titled with the symbol and the alert's name, with the message as its text. Shown only while the /trading tab is hidden, because a visible page already has the toast. Clicking it brings the chart forward |
| Telegram | The symbol, the name and the message, sent through OpenAlgo's Telegram bot to your linked account |
| WhatsApp | The same text, sent to your paired device |

The outward channels are tried together, and each is tried once. When one refuses, a toast says so in the server's own words, for example "The alert fired, but Telegram did not take it." followed by the reason, and it is not retried. The alert has already fired by then, and the Log row records which channels accepted it.

## Managing alerts

| To | Do this |
|---|---|
| Change an alert | **Edit** on its row, then **Save** |
| Move a price alert | Drag its line on the chart |
| Pause and resume one | **Stop** and **Start** on its row |
| Pause or resume all | **Stop all** or **Start all** in the panel menu |
| Delete one | **Delete** on its row, or hover its line on the chart and press Delete or Backspace |
| Delete all on the pane | **Remove all alerts** in the panel menu |
| Silence a script's alerts | Remove the study from the chart |
| Clear the history | **Clear** on the Log tab |

Pressing Delete over the chart removes a drawing first when one is selected or under the pointer, and an alert's line only when nothing else claims the key.

Alerts belong to the chart pane they were made on and are saved with that chart in your browser, so they come back when you reopen /trading. A Repeat Only once alert that has fired comes back as Fired, not armed again, so it does not fire twice for the same price.

**Related.** [Alerts from scripts](/script/alerts/overview), [Realtime and confirmation](/script/language/realtime-and-confirmation), [The editor](/script/getting-started/the-editor), [Plots](/script/visuals/plots), [Sandbox and live](/script/strategies/sandbox-and-live), [Troubleshooting](/script/writing/troubleshooting)


# Strategies

## Overview

Source: https://openalgo.in/script/strategies/overview

A strategy is an OpenScript file (OpenScript is also called OpenAlgo Script) that draws like a study and can also place orders. This page covers what changes when you declare `strategy()` instead of `study()`, the options only a strategy has, what happens to an order on every bar, where a strategy runs in /trading, and which parts of the strategy surface run in version 0.5.0. Read it before the other strategy pages: they all build on the loop described here.

## A first strategy

Here is a complete strategy. It trades a 9 and 21 bar EMA cross on whatever chart it is added to: an NSE stock, an index future on NFO or a contract on MCX.

```openscript
version 1
strategy("EMA cross, traded", overlay = true, precision = 2,
         capital = 500000, qty = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)

fast = ema(close, fastLen)
slow = ema(close, slowLen)

// Both signals are computed at the top level, so each call sees every bar.
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy(tag = "entry")
else if goFlat and pos.isLong
    close()

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)
plot(pos.isFlat ? none : pos.avgPrice, "Entry", fade(silver, 40), style = "step")
```

Compared with a study that only marks the cross, three things were added:

- **A size.** `qty = 1` in the declaration is the size every order uses unless it names its own.
- **A position guard.** `pos.isFlat` and `pos.isLong` stop the script entering again on every bar the condition stays true.
- **A decision about which branch wins.** `else if` means the two orders can never be sent on the same bar.

The last plot is a habit worth forming early. It draws the average entry price while a position is open and a gap while flat, because `pos.avgPrice` is absent when there is no position. A strategy whose position is visible on the chart is a strategy whose bugs are visible on the chart.

On the /trading chart a strategy runs against the same simulated fill model the Backtest panel uses, and places nothing. A **fill** is an order being executed at a price, and each simulated fill is marked on the bar it happened on:


## One word separates a study from a strategy

A file carries exactly one declaration, right after the `version` line. `study(...)` declares a script that draws. `strategy(...)` declares a script that draws and can also place orders. It accepts every option `study()` accepts and adds the trading options below, so one file holds the drawing and the trading, computed once, from the same numbers. The indicator on your chart and the rules in your backtest cannot drift apart, because they are the same lines.

The order functions exist only in a strategy. Call one from a study and the compiler refuses it with OS7001, naming the declaration to change:

```openscript
version 1
study("EMA cross, marked", overlay = true)

if crossUp(ema(close, 9), ema(close, 21))
    buy(qty = 1)
```

## The strategy options

These are the options `strategy()` adds to the ones every study has (`title`, `short`, `overlay`, `precision`, `format`, `range`, `scale`, `group` and `onUnconfirmed`):

| Option | Default | Accepts | Controls |
|---|---|---|---|
| `capital` | `100000` | a number | Starting equity for the backtest |
| `currency` | `""` | a string | The label money is shown with in the report |
| `qty` | `1` | a number | The order size used when an order names none |
| `qtyType` | `"units"` | `"units"`, `"lots"`, `"cash"`, `"equityPercent"` | The unit `qty` is counted in |
| `product` | `"intraday"` | `"intraday"`, `"overnight"` | The product every order is sent with |
| `fillOn` | `"nextOpen"` | `"nextOpen"`, `"close"` | Where a market order decided on a bar is filled |
| `slippage` | `0` | a number | Ticks of adverse slippage on every market and stop fill |
| `commission` | `0` | a number | The charge, in the unit `commissionType` names |
| `commissionType` | `"perTrade"` | `"perTrade"`, `"perUnit"`, `"percent"` | How `commission` is applied |
| `pyramiding` | `1` | a whole number, 1 or more | How many entries one direction may hold |
| `closeOnSessionEnd` | `false` | `true` or `false` | Flatten at the session close |

A **tick** is the smallest step the instrument's price can move, such as 0.05 rupees, and **slippage** is the difference between the price you expected and the one you got. **Pyramiding** means adding to a position you already hold.

Every option value must be fixed before the first bar: a literal, arithmetic over literals, or an `input()` call. The settings dialog and the legend are built before any data arrives, so an option that depended on a bar would have nothing to be built from, and the compiler refuses it with OS3003.

Two defaults are deliberately set against you. `fillOn = "nextOpen"` because a decision made from a bar's close cannot be filled at that same close in the real market. `pyramiding = 1` because a script that adds to a position by accident reports a return its stated rules never earned.

> **What /trading does with these options in 0.5.0**
- `qtyType = "cash"` and `qtyType = "equityPercent"` are accepted by the compiler and refused by the Backtest panel before the run starts: a backtest fills in units and keeps no running equity to size against.
- `qtyType = "lots"` has a known backtest defect with closing orders, and the Strategies panel refuses to start a strategy that counts in anything but `"units"`. Count in units, as [Position and sizing](/script/strategies/position-and-sizing) shows.
- `closeOnSessionEnd = true` is accepted and not acted on by the backtest or by a deployment: a position is carried past the session close. Write the exit in the script, as [Exits and brackets](/script/strategies/exits-and-brackets) shows.

## A strategy keeps its own books

One rule sits under every strategy page, so it is worth stating before any call:

> **A strategy never places an order worked out as a difference against the account's position. Every order states its own side and its own quantity, and everything a strategy knows about its position comes from its own fills.**

An account position is held per contract, not per strategy. A trade you placed by hand, a second strategy on the same contract, or this same script started twice all land in the same row. An order that read that row and sent the difference would be computing against somebody else's trade: two strategies on one NIFTY future would keep undoing each other all session, and a strategy that found your manual position already in place would never enter, report no trade, and leave its stop resting against a position that is not its own.

So `pos.size`, `pos.avgPrice` and the rest are folded from this strategy's own settled fills. No call returns the account's quantity. `pos.isShared`, which is planned, will report that the account's position in this contract is shared with something else, such as a manual trade or another strategy, as a yes or no that a human should look into.

## A marker is not an order

These three calls look alike in a file and do entirely different things:

| Call | What it does | Can it be refused | Moves money | When it happens |
|---|---|---|---|---|
| `signal()` | Draws one named marker on the bar | No | No | When the bar is confirmed |
| `alert()` | Raises one watched condition for the host to deliver | No | No | When the bar is confirmed |
| `buy()` | Sends one order to the order destination | Yes | Yes | Placed when the bar is confirmed, filled later |

A bar is **confirmed** once it has closed and its prices can no longer change. A marker is a statement about the chart, and nothing can stop it being drawn. An order is a request, and it can be refused: for an absent price or size (OS7002), a size of zero or less (OS7004), a price off the tick (OS7006), a resting order with no price (OS7007), an entry beyond the pyramiding limit (OS7008), cancelling a tag that is not working (OS7009), a stop or target on the wrong side of an open position (OS7010), two opposite orders on one bar (OS7013), or a close larger than what it closes (OS7017). [Orders](/script/strategies/orders) lists every refusal with its usual cause.

> **A refused order stops the script**
In version 0.5.0 a refused order stops the run at the bar it happened on. Nothing that bar decided is sent, and no later bar executes. In the Backtest panel the report then holds only the trades made before it, and the panel does not show the error, so a run with far fewer trades than the chart suggests is worth checking for one. The guards below are what keep a strategy from ever reaching one.

## What happens on every bar

A strategy has no main function. The file is the body of a loop, and for each bar, oldest first, the engine runs every top-level statement from the first line to the last. For a strategy, one execution of one bar goes like this:

| Step | What happens |
|---|---|
| 1 | The bar's `open`, `high`, `low`, `close`, `volume` and `time`, and the [bar facts](/script/reference/bar), are filled in |
| 2 | Every fill the destination has reported is folded into the strategy's own ledger, so `pos.size`, `pos.avgPrice` and the rest describe what is actually held now |
| 3 | Inputs are read from the settings dialog |
| 4 | The script runs, top to bottom, once |
| 5 | Every plot, fill, level, table cell and drawing is published, whether the bar is confirmed or not |
| 6 | If the bar is confirmed, every marker, alert and order the script asked for is applied. If it is still forming, they are discarded |

Step 6 surprises people, so be exact about it. When `buy(qty = 1)` runs, nothing is sent. The call records what it was asked to do, and the record is applied at the end of the bar, and only if the bar is confirmed. On a bar that is still forming, the record is thrown away and rebuilt on the next update. A condition that was true halfway through a bar and false when it closed never places an order.

That is why an order function returns nothing: there is no order yet to hand back. A script that needs to act on its own order reads `pos.size` on a later bar, where the fill is a fact. With the default `fillOn = "nextOpen"`, an order decided at the close of one bar fills at the next bar's open, and the position is already in place when that next bar runs:

```openscript
version 1
strategy("Reading back a fill", overlay = true, capital = 500000)

goLong = crossUp(ema(close, 9), ema(close, 21))

if goLong and pos.isFlat
    buy(qty = 1, tag = "entry")

// The previous bar's reading, taken on its own line so it is recorded on
// every bar. Written inside the "and" below, it would only be recorded on
// the bars where that side runs.
wasLong = pos.isLong[1]

// True on the first bar the position is held: the order was decided on the
// bar before and filled at this bar's open.
justFilled = pos.isLong and not wasLong

if justFilled
    signal("FILLED AT " + text(pos.avgPrice, 2), at = "below")
```

Two more consequences of the loop catch people once:

**The newest bar runs many times.** While a bar is forming it is executed again on every update. Before each run the engine restores every [persistent](/script/language/persistence) value to what it held at the end of the previous bar, so running the forming bar ten times gives the same answer as running it once. That is what makes the chart and a backtest of the same data agree. [Realtime and confirmation](/script/language/realtime-and-confirmation) covers it in full.

**Acting inside a bar is opt-in.** `onUnconfirmed = true` in the declaration lifts the deferral, and from then on the script writes its own confirmation guard, or it places an order on every update of the bar:

```openscript
version 1
strategy("Intrabar, guarded", overlay = true, onUnconfirmed = true)

goLong = crossUp(ema(close, 9), ema(close, 21))

if goLong and bar.isConfirmed and pos.isFlat
    buy(qty = 1)
```

## The guards every strategy needs

A strategy is mostly the same code as the study plus a handful of guards. Learn them as a set: leaving one out produces a backtest that looks fine and is not.

| Guard | Written as | Why |
|---|---|---|
| Warmup | `not isNone(x)` | A window that has not filled yet has no value (it is [absent](/script/language/absent-values)), and an order given an absent price or size is refused (OS7002) |
| Position | `pos.isFlat`, `pos.isLong` | Without it a one-lot idea enters again on every bar the condition holds |
| One branch | `else if` | Two opposite orders on one bar are refused (OS7013) |
| Trading hours | `session.isIn()` with a named zone | Keeps entries inside the hours you mean, such as 09:30 to 15:00 on NSE |
| Top level | signals computed before the `if` | A stateful call such as `crossUp()` inside a branch only advances on the bars the branch runs (warning OS8001) |

**Warmup** is the run of early bars before an indicator has enough history to give a value. The last guard applies to `and` as well: its right side is not evaluated when its left side is false, so write `goLong = crossUp(fast, slow)` at the top level and test `goLong and pos.isFlat`, rather than putting the call inside the condition.

`session.isOpen` is planned. Until it lands, `session.isIn()` with a window you write is the trading-hours guard. Name the zone, `"Asia/Kolkata"` for Indian markets: the chart states its timezone to the script, but the Backtest panel does not, and there a window with no zone has no value on any bar, so the strategy never trades.

```openscript
version 1
strategy("Breakout, guarded", overlay = true, precision = 2,
         capital = 500000, qty = 1, product = "intraday")

length = input(20, "Breakout lookback", min = 2, max = 500)

// The window as it stood before this bar, so this bar's own high cannot be
// the level it is breaking.
breakoutLevel = highest(high, length)[1]

ready    = not isNone(breakoutLevel)
inHours  = session.isIn("0930-1500", "Asia/Kolkata")
breakout = close > breakoutLevel

if ready and inHours and pos.isFlat and breakout
    buy(tag = "entry")
else if pos.isLong and not inHours
    close()

plot(breakoutLevel, "Breakout level", aqua, style = "step")
```

A strategy deployed from the Strategies panel cannot read the clock this way in version 0.5.0: the runner refuses to start a script that calls `session.isIn()` or any `date.*` function on an Indian instrument. [Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today) shows a trading window built from arithmetic on `time` that works in all three places.

## Where a strategy runs in /trading

The same compiled strategy runs in three places, and the numbers it computes do not change between them. What changes is where its orders go.

| Where | Orders go to | Money |
|---|---|---|
| On the chart | A simulated fill model in your browser. Nothing is placed | None |
| The Backtest panel | The same fill model, over history | None |
| A deployment from the Strategies panel, while OpenAlgo is in analyzer mode | Sandbox trading (analyzer mode in OpenAlgo) | None |
| A deployment, while OpenAlgo is in live mode | Your broker, through OpenAlgo's own order path | Yours |

**Nothing in a script decides where its orders go.** A deployment sends through the platform's order path, and that path follows OpenAlgo's analyzer setting, which is one setting for the whole platform, made outside the Strategies panel. There is no call, option or input that chooses the destination, and no call that reports it, so a script cannot behave differently once it is live and the run you tested in the sandbox is the run that goes to market.

The Strategies panel shows the platform's current mode in its header, and its start button names the destination it is about to use:


The runner that executes a deployment supports a subset of the language in version 0.5.0: quantities in units only, no `exit()` or `order.bracket()`, and no calendar or session reads. It refuses anything else before the first order, and [Sandbox and live](/script/strategies/sandbox-and-live) lists each refusal with its fix.

A strategy that wants its stop or target drawn plots it like any other value. The chart shows what happened; the destination decides what happens.

## What runs in version 0.5.0

The strategy surface is designed in full and partly built. The names that are not built yet are still in the language, and calling one is refused at the call with OS2020, so you find out where you wrote it.

| Runs today | Planned |
|---|---|
| `buy()`, `sell()`, `close()`, `exit()`, `cancel()`, `cancelAll()` | Reading an order back: `order.working()`, `order.pending`, `order.status()`, `order.filled()` and the rest |
| `order.place()`, `order.reverse()`, `order.bracket()` | Sizing helpers: `order.qtyForRisk()`, `order.qtyForCash()`, `order.roundToLot()` |
| `pos.size`, `pos.isLong`, `pos.isShort`, `pos.isFlat`, `pos.avgPrice` | `pos.barsHeld`, `pos.openProfit`, `pos.equity` and the other position and run figures |
| The declaration options above | Legs and books: every `leg.*` and `book.*` call, and `order.oco()` and `order.modify()` |

Some behaviour is not modelled yet, and each page says where it matters:

- A stop or target set with `exit()` or `order.bracket()` is not filled on the chart or in a backtest, and a deployment refuses to start a script that calls either.
- `closeOnSessionEnd` does not flatten a position at the session close.
- Sizing in `"cash"` or `"equityPercent"` is refused by the backtest.
- Five refusals are catalogued and not raised yet: a size that is not a whole number of lots (OS7005), an order needing more capital than the strategy has (OS7011), an order outside the session (OS7012), a rejection by the destination (OS7014) and a strategy with no destination (OS7015).

## Two shapes a strategy can take

Every script on this page trades one instrument, the one on its chart, and opens and closes it on its own signals. That is the **per-leg** shape, and `buy()`, `sell()` and `close()` are its short spelling. The language also defines a shape that enters several contracts **as a unit**, such as the two legs of a short straddle on NIFTY weekly options, and manages them with one combined stop. That shape is planned; [Legs and books](/script/strategies/multi-leg-and-books) describes it.

## Mistakes that look like results

| Symptom | Cause | Fix |
|---|---|---|
| The position grows every bar the condition is true | No position guard | Add `and pos.isFlat`, or raise `pyramiding` on purpose |
| The backtest stops partway, with far fewer trades than the chart shows | A refused order, most often OS7008: a second entry with `pyramiding = 1` | Guard entries with `pos.isFlat` |
| No trades at all in the Backtest panel | A trading window written without a zone, such as `session.isIn("0930-1500")` | Name the zone: `session.isIn("0930-1500", "Asia/Kolkata")` |
| The backtest is much better than the account | `fillOn = "close"`, no slippage, no commission | Keep the defaults, then add real costs |
| Orders appear on history and not on the forming bar | The condition is true inside the bar and false at its close | Nothing to fix: that is the deferral working |
| A stop plotted on the chart never exits the backtest | Levels from `exit()` are not filled in 0.5.0 | Test the level in the script, as [Exits and brackets](/script/strategies/exits-and-brackets) shows |

**Related.** [Orders](/script/strategies/orders), [Exits and brackets](/script/strategies/exits-and-brackets), [Position and sizing](/script/strategies/position-and-sizing), [Costs and fills](/script/strategies/costs-and-fills), [Backtesting](/script/strategies/backtesting), [Sandbox and live](/script/strategies/sandbox-and-live), [Your first strategy](/script/getting-started/first-strategy), [Strategy orders reference](/script/reference/strategy)


## Orders

Source: https://openalgo.in/script/strategies/orders

This page covers every call a strategy uses to place, name, cancel and close an order: `buy()`, `sell()`, `close()`, `exit()`, `cancel()`, `cancelAll()` and the [order namespace](/script/reference/orders). You need it as soon as a strategy does more than enter at the market and flatten on the opposite signal: a limit entry that waits for a pullback, a stop entry above an opening range, a partial exit, a reversal.

## A complete example

This strategy bids for a pullback in an uptrend with a limit order, cancels the bid if it has not filled within a few bars, and exits at the market when the trend turns. It works on any instrument; on an NSE stock it trades one share.

```openscript
version 1
strategy("Pullback limit", overlay = true, precision = 2,
         capital = 500000, qty = 1)

offsetAtr = input(0.5, "Bid this far below the close, in ATR", min = 0.1, max = 5)
waitBars  = input(3,   "Cancel the bid after this many bars", min = 1, max = 50)

atrValue = atr(14)
trend    = ema(close, 50)
upTrend  = close > trend

// Every price that reaches an order is rounded to the tick and tested for
// absence first.
wanted   = close - offsetAtr * atrValue
bidPrice = isNone(wanted) ? none : roundToTick(wanted)

// The price and bar of the resting bid, none while nothing rests.
var restingAt = none
var placedAt  = none

// Once the bid has filled there is nothing resting any more.
if not pos.isFlat
    restingAt = none
    placedAt  = none

stale = not isNone(placedAt) and bar.index - placedAt >= waitBars

if stale
    cancel("pullback")
    restingAt = none
    placedAt  = none
else if upTrend and pos.isFlat and isNone(placedAt) and not isNone(bidPrice)
    buy(limit = bidPrice, tag = "pullback")
    restingAt = bidPrice
    placedAt  = bar.index
else if pos.isLong and not upTrend
    close()

plot(trend, "Trend", orange, width = 2)
plot(restingAt, "Resting bid", aqua, style = "step")
```

Read the three branches in order. The cancel is tested first, so on the bar a bid goes stale the script cancels it and does not immediately place another at a stale price. The single `if` chain also means no two orders from this script can ever go out on the same bar. And the plot draws the price that was actually sent, held in a `var` (a variable that keeps its value from one bar to the next), not a price recomputed from today's volatility.

A backtest lists every trade the run made:


## The order calls

Everything below works only in a `strategy()` file; in a study the compiler refuses it with OS7001.

| Call | For | Status |
|---|---|---|
| `buy(qty, limit, stop, tag)` | Enter or add to a long position | Runs |
| `sell(qty, limit, stop, tag)` | Enter or add to a short position, or reduce a long | Runs |
| `close(tag, qty)` | Flatten the position, or the part one tag entered | Runs |
| `exit(tag, qty, limit, stop, profit, loss)` | Set the position's stop and target | Accepted, not acted on in /trading yet; see [Exits and brackets](/script/strategies/exits-and-brackets) |
| `cancel(tag)` | Cancel a working order that has not filled | Runs |
| `cancelAll()` | Cancel every working order this strategy placed | Runs |
| `order.place(side, qty, type, price, trigger, tag)` | The general form, for a script that computes its side | Runs |
| `order.reverse(qty, tag)` | Close the position and open the same size the other way | Runs |
| `order.bracket(tag, profit, loss)` | Set the stop and target as distances from the entry | Accepted, not acted on in /trading yet; see [Exits and brackets](/script/strategies/exits-and-brackets) |
| `order.working()`, `order.pending` | Whether a tag is working, and how many orders are | Planned |
| `order.status()`, `order.filled()`, `order.avgFill()`, `order.id()`, `order.rejection()` | Reading one order back from the strategy's own ledger | Planned |
| `order.modify()`, `order.oco()` | Changing a working order in place, and one-cancels-other | Planned |

Six bare names cover almost every script, and the `order` namespace holds the rest. A planned name is refused at the call with OS2020, so a script cannot compile around one by accident.

## Default or absent

Leave an argument out and you get its default: `buy()` uses the declaration's `qty`, and `buy()` with neither price is a market order. Pass an argument whose value comes out absent (no value on this bar) and you get something else entirely: the order is refused with OS7002, naming the argument, and the run stops.

That difference is deliberate. `buy(stop = lowest(low, 20))` on bar 5 is not a market order at a price nobody chose; it is refused, because the 20-bar window has not filled yet. The fix is a guard, computed once at the top level:

```openscript
version 1
strategy("Stop entry, guarded", overlay = true, precision = 2, qty = 1)

rangeHigh = highest(high, 20)[1]
trendUp   = close > ema(close, 50)
trigger   = isNone(rangeHigh) ? none : roundToTick(rangeHigh)

if not isNone(trigger) and trendUp and pos.isFlat
    buy(stop = trigger, tag = "breakout")
```

That script shows the guard and nothing else. It still has a flaw the section on working orders below fixes: every flat bar places another stop order, because a working order is not a position. `pyramiding` does not catch it either, because it counts filled entries: in a backtest the stops resting at that level trigger together when price reaches it, and the position comes out several times the size the script meant.

## Market, limit, stop and stop-limit

There is one entry function per direction, and the kind of order is decided by which prices you pass. A **limit** order buys at its price or lower (sells at its price or higher). A **stop** order waits until the market reaches its trigger price, then becomes a market order. A **stop-limit** waits for the trigger, then rests as a limit.

| `limit` | `stop` | Kind | In a backtest it fills |
|---|---|---|---|
| absent | absent | Market | At the next bar's open, or at this bar's close with `fillOn = "close"`, worsened by the slippage |
| given | absent | Limit | Once a bar trades beyond the limit, at the limit, or at the open when the bar opens beyond it. No slippage |
| absent | given | Stop | Once a bar reaches the trigger, at the trigger, or at the open when the bar gaps through it, worsened by the slippage |
| given | given | Stop-limit | Once the trigger is reached and a bar then trades beyond the limit. Until then it rests as a limit |

An order with a price is first tested against the bar after the one that placed it. A limit that the bar's low only touches is not filled, because touching a price is not proof your order was reached in the queue. [Costs and fills](/script/strategies/costs-and-fills#resting-orders-limits-and-stops) covers these rules.

The trader's decision is direction; the price is a qualifier on it. You decide to buy, and whether you buy at the market or wait for a pullback is the next thought.

Two rules apply to every price you pass:

- **A price must fall on a tick.** A limit between two ticks cannot exist at the exchange, so it is refused with OS7006, naming the instrument, its tick and the price. The engine does not round it for you, because that would move the order off the level your script computed. Round it yourself with `roundToTick()`.
- **`roundToTick()` is absent when the host has stated no tick size.** An order given the absent result is refused with OS7002. Test the rounded price once and use the result everywhere, as the examples on this page do.

Here is a stop entry placed once a session, above the high of the first fifteen minutes (09:15 to 09:30), and cancelled at 11:00 if it has not triggered. Every clock test names the zone, because the Backtest panel does not state the chart's timezone to the script, and a window with no zone has no value there:

```openscript
version 1
strategy("Opening range stop entry", overlay = true, precision = 2,
         capital = 500000, qty = 1, product = "intraday")

// A new IST date is a new session.
newDay   = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")
forming  = session.isIn("0915-0930", "Asia/Kolkata")
canEnter = session.isIn("0930-1100", "Asia/Kolkata")
lateDay  = not session.isIn("0915-1500", "Asia/Kolkata")

var rangeHigh = none
var placed    = false
var working   = false

if newDay
    rangeHigh = none
    placed    = false

if forming
    rangeHigh = isNone(rangeHigh) ? high : max(rangeHigh, high)

trigger = isNone(rangeHigh) ? none : roundToTick(rangeHigh)

// A filled stop is a position, not a working order.
if not pos.isFlat
    working = false

if working and not canEnter
    cancel("orb")
    working = false
else if canEnter and not placed and pos.isFlat and not isNone(trigger)
    buy(stop = trigger, tag = "orb")
    placed  = true
    working = true
else if pos.isLong and lateDay
    close()

plot(rangeHigh, "Range high", aqua, style = "step")
```

A deployment from the Strategies panel cannot read the clock like this in version 0.5.0; [Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today) explains why, and what works instead.

## One position, and no order crosses zero

`buy(qty)` adds to the position, `sell(qty)` subtracts from it, and `close()` flattens it. All three count this strategy's own settled fills and nothing else.

`sell()` does not mean "close a long". It means "subtract", which closes a long if one is open and keeps going into a short if the quantity is larger. To flatten, say so with `close()`.

**No order crosses zero.** In a strategy that counts in units, an instruction that would take the position from long to short is sent as two orders: one that closes the outgoing position and one that opens the new one. Each carries its own position reference, so a fill that arrives late can still say which position it belongs to.

```openscript
version 1
strategy("Two orders, not one", overlay = true, qty = 1)

if bar.index == 100
    buy(qty = 5, tag = "long")

// Long 5 here, so this sends two orders: sell 5 to close, then sell 3 to open
// a short of 3.
if bar.index == 110
    sell(qty = 8, tag = "flip")

if bar.index == 120
    close()
```

**Direction comes from the function, never from the sign of the quantity.** A negative quantity is a calculation that went the wrong way, and a quantity of zero is never what a script means; both are refused with OS7004. Sizing towards a target position from `pos.size` is fine, because `pos.size` describes nothing but this strategy:

```openscript
version 1
strategy("Target three units", overlay = true, qty = 1)

wantedSize = 3 - pos.size

if wantedSize > 0
    buy(qty = wantedSize)
else if wantedSize < 0
    sell(qty = -wantedSize)
```

## One leg in version 0.5.0

A strategy trades legs, and a leg is one contract. In version 0.5.0 a file declares no legs, so it has exactly one: the instrument on its chart. Every order acts on it and none of them names it. The `leg` argument that every order call accepts is there for the planned [multi-leg strategies](/script/strategies/multi-leg-and-books), and writing it today is refused with OS3023, whatever you pass:

```openscript
version 1
strategy("A leg that does not exist", overlay = true, qty = 1)

goLong = crossUp(ema(close, 9), ema(close, 21))

if goLong and pos.isFlat
    buy(qty = 1, leg = "main")
```

The fix is to take the argument out.

## Tags: naming an order

An order is named by a **tag**, a string your script chooses. A tag is how a later bar cancels an order that is still working, how `close(tag = ...)` picks out one part of a position, and what the trade list and every refusal message quote back at you.

The script chooses the name rather than the engine because an order function places nothing at the moment it runs: the request is applied at the end of the bar, and only if the bar is confirmed. There is no order yet to have an identifier, and a tag is a name the script already knows.

**What a tag argument means is written in its default.**

| Kind | Default | Calls | Naming nothing |
|---|---|---|---|
| A **label** | `""` | `buy()`, `sell()`, `exit()`, `order.place()`, `order.reverse()`, `order.bracket()` | Ordinary: the tag rides along to the destination and the report |
| A **reference** | required, or `none` | `cancel()`, `close()` | A mistake: it names something the strategy must already have |

A `close` whose tag no order in the file is placed with can never close anything, so the compiler refuses it with OS7016 before any bar runs. It is almost always a typo:

```openscript
version 1
strategy("A typo in a tag", overlay = true, qty = 1)

fast = ema(close, 9)
slow = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy(tag = "entry")
else if goFlat and pos.isLong
    close(tag = "entyr")
```

`cancel` is checked while the run is going instead, because only the run knows what is working: cancelling a tag with no working order is refused with OS7009, and the run stops. Closing a tag that has already flattened is not an error. It sends nothing and says nothing, which is what makes a `close(tag = "entry")` safe to write on every bar of an exit condition.

Two habits pay for themselves: give every order a tag, even when the script has only one, and make the tag describe the intention (`"entry"`, `"pullback"`, `"reversal"`) rather than the bar it was placed on.

## When the next signal arrives and an order is still working

A **working** order is one that has been placed and has neither filled nor been cancelled, such as a limit waiting for its price. This is the case that separates a strategy that survives real market conditions from one that does not. The language fixes three facts and leaves the fourth to you:

1. **A working order is not a position.** `pos.size` counts settled fills. A resting limit changes nothing in `pos.*` until it fills.
2. **Nothing is cancelled for you.** A new `buy()` does not replace a working `buy()`. If both fill, you hold both, even with `pyramiding = 1`, because the pyramiding limit counts filled entries and neither had filled when it was placed.
3. **Two opposite orders on one bar are refused**, both of them, with OS7013 naming both lines. Source order is an accident of layout, so neither is honoured. Two orders on the same side are not this: they are two orders.
4. **What happens to the old order is a trading decision**, and yours to write.

There are three sane policies. Write the script so a reader can tell which one it uses:

| Policy | Written as | Suits |
|---|---|---|
| Cancel and replace | `cancel(tag)` then place the new order, on the same bar or the next | A resting order that tracks a moving level |
| First come, first served | Remember that an order is working and place nothing new until it fills or is cancelled | An entry taken at its price or not at all |
| Age out | Count the bars an order has been working and cancel it | A signal that goes stale, like the complete example above |

`order.working()` and `order.pending` will answer "is this order still working" from the ledger. Both are planned, so in version 0.5.0 a script keeps that fact itself in a [`var`](/script/language/persistence), set when the order is placed and cleared when the position opens or the order is cancelled. The pullback and opening range examples above both do exactly that.

`cancelAll()` is for the moments a script has lost confidence in everything it has working: the session ending, a risk switch turned off in the inputs. It cancels working orders only, and does not close a position; an order that has filled is not working any more. With nothing working it sends nothing and refuses nothing, so it is safe to call on any bar.

## Exiting

| To | Call | Notes |
|---|---|---|
| Flatten the position | `close()` | Whatever is held, long or short |
| Flatten part of it | `close(qty = n)` | `n` is positive whichever way the position points |
| Flatten the part one tag entered | `close(tag = "runner")` | The tag must be one an order in the file is placed with |
| Leave at a stop or a target | A rule that tests the level and calls `close()` | See [Exits and brackets](/script/strategies/exits-and-brackets) |

**A close is measured against what is left to close**: what has settled, less everything already on its way out. Two bare closes on one bar send one order between them, and a close on the bar after one the destination has not answered yet sends nothing. That is what stops `close()` under `if pos.size > 0` from sending the whole position again on every bar while a slow destination is still working the first one.

**A quantity written on a close is held to a ceiling.** `close(qty = 5)` against a position of 3 would flatten it and open a short under a call named `close`, so it is refused with OS7017, naming what you asked for and what is left. A close with no quantity asks for whatever is there and cannot be wrong, which is why `close(tag = "runner")` on a flattened tag is silent while `close(tag = "runner", qty = 1)` on it is refused. If a scale-out can fire twice on one position, guard it rather than sizing it and hoping. This one takes half off at a first target fixed when the position opens, and closes the rest on the opposite cross:

```openscript
version 1
strategy("Scale out", overlay = true, precision = 2,
         capital = 500000, qty = 2)

atrValue = atr(14)
fast     = ema(close, 9)
slow     = ema(close, 21)
goLong   = crossUp(fast, slow)
goFlat   = crossDown(fast, slow)

// The first target, fixed once the position is open rather than recomputed
// from each bar's ATR.
var target = none
// One scale out per position, not one per bar above the target.
var scaled = false

if pos.isFlat
    target = none
    scaled = false
else if isNone(target) and not isNone(atrValue)
    target = pos.avgPrice + 2 * atrValue

if goLong and pos.isFlat
    buy(qty = 2, tag = "entry")
else if pos.isLong and goFlat
    close()
else if pos.isLong and not scaled and high > target
    // abs because pos.size is signed and an order quantity never is.
    half = floor(abs(pos.size) / 2)
    if half > 0
        close(qty = half)
        scaled = true

plot(target, "First target", lime, style = "step")
```

## Reversing

There are three ways, and they are not the same trade:

| Way | What happens | Use it when |
|---|---|---|
| `order.reverse()` | One decision, two orders: close the position, open the same size the other way | A stop-and-reverse system that is never flat |
| `sell(qty = abs(pos.size) + newQty)` | One instruction the engine splits into two orders, because no order crosses zero | The new size differs from the old, in a strategy counting in units |
| `close()`, then `sell()` on a later bar | Two decisions, with at least one bar flat between them | The reversal deserves a second look |

`order.reverse()` says in one call what the other two spell out, and a reader does not have to check any arithmetic to see that the size is unchanged:

```openscript
version 1
strategy("Stop and reverse", overlay = true, precision = 2,
         capital = 500000, qty = 1, pyramiding = 1)

factor = input(3.0, "Band width, in ATR", min = 0.5, max = 20)
atrLen = input(10,  "ATR length", min = 1, max = 200)

// supertrend returns [line, direction]: -1 while the trend is up, 1 while
// it is down.
bands   = supertrend(factor, atrLen)
band    = bands[0]
dir     = bands[1]
prevDir = dir[1]

// Both readings must exist. On the first bar with a direction the previous
// one is absent, and != against an absent value reads as a change.
flipped = not isNone(dir) and not isNone(prevDir) and dir != prevDir

if flipped and pos.isFlat
    if dir == -1
        buy(tag = "long")
    else
        sell(tag = "short")
else if flipped
    order.reverse(tag = "reversal")

plot(dir == -1 ? band : none, "Stop, long",  lime, width = 2)
plot(dir == 1  ? band : none, "Stop, short", red,  width = 2)
```

## When the script computes its side

`order.place()` is the general form, for a script whose direction comes out of a calculation rather than out of two branches. `side` is `"buy"` or `"sell"`, and `type` is `"market"`, `"limit"`, `"stop"` or `"stopLimit"`. The type and the prices must agree: `"limit"` takes `price`, `"stop"` takes `trigger`, `"stopLimit"` takes both, and `"market"` takes neither. A type that names a limit or a stop without the price it needs is refused with OS7007 rather than filled in from the bar's close, since the report would say "limit" and the fill would say "market". A value written outside either list is a compile error (OS3008).

```openscript
version 1
strategy("Signed model", overlay = true, capital = 500000, qty = 1)

score = ema(close, 9) - ema(close, 21)
side  = isNone(score) ? "" : (score > 0 ? "buy" : "sell")

if side != "" and pos.isFlat
    order.place(side, 1, tag = "model")
else if pos.isLong and side == "sell"
    close()
else if pos.isShort and side == "buy"
    close()
```

Use the bare functions where the direction is written in the source, and `order.place` where it is not. A reader can see a `buy()` without running anything; a reader of `order.place(side, ...)` has to work out what `side` holds.

## Refusals

Every refused order reports a code and a reason, naming the line that placed it. In version 0.5.0 a refusal while the run is going also stops the run at that bar: nothing the bar decided is sent, and no later bar executes. The Backtest panel then reports only the trades made before the refusal, and does not show the error itself. Codes marked "At compile time" are caught before any bar runs.

| Code | Means | Usual cause | In 0.5.0 |
|---|---|---|---|
| OS7001 | An order call in a study | The declaration says `study` | At compile time |
| OS7002 | An order argument is absent | A price or size from a window that has not warmed up | Raised |
| OS7003 | An order call inside a request expression | A function that places an order, passed to `req.timeframe()` | At compile time |
| OS7004 | Quantity is zero or negative | A size computed from a difference that went the wrong way | Raised |
| OS7005 | Quantity is not a whole number of lots | Units passed to an instrument that trades in lots | Not raised yet |
| OS7006 | Price is not on a tick | A limit computed as a percentage and never rounded | Raised |
| OS7007 | A resting order has no price | `order.place` with `type = "limit"` and no `price` | Raised |
| OS7008 | The entry was refused by pyramiding | No position guard on the entry | Raised |
| OS7009 | No working order has that tag | Cancelling an order that has already filled or ended | Raised |
| OS7010 | A stop or target on the wrong side of the position | A stop above a long's entry | Raised |
| OS7011 | The order needs more capital than the strategy has | Fixed sizing against a small `capital` | Not raised yet |
| OS7012 | The instrument is outside its session | An order at a time the exchange is closed | Not raised yet |
| OS7013 | Two opposite orders on one bar | Two independent `if` blocks that can both be true | Raised |
| OS7014 | The destination rejected the order | A product or margin the account cannot trade | Not raised yet |
| OS7015 | The strategy has no order destination | Nothing configured to receive orders | Not raised yet |
| OS7016 | A close names a tag nothing places | A typo in a tag | At compile time |
| OS7017 | A close states more than it is closing | A scale-out fired twice | Raised |
| OS3023 | An order names a leg | `leg = ...` written in version 0.5.0 | At compile time |

OS7008 is the pyramiding limit doing its job. Silently building a position the declaration forbade would report a return the stated rules never earned, so the order is refused instead. The [order error pages](/script/errors/orders) give a before and after for each code.

## Pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| A `sell` went short instead of flattening | `sell` subtracts, it does not close | `close()` |
| Two entries where the script meant one | Guarded on `pos.isFlat` alone while a resting order was still working | Remember the working order in a `var`, as the examples do |
| The run stops with OS7009 | `cancel` on an order that has already filled | Clear the "working" flag when the position opens |
| The run stops with OS7013 | Two `if` blocks placing opposite orders on one bar | One `if` chain with `else if` |
| No orders at all in the Backtest panel | A clock test written without a zone, such as `session.isIn("0930-1100")` | Name the zone: `session.isIn("0930-1100", "Asia/Kolkata")` |
| OS3023 on every order | A `leg` argument | Take it out; the order acts on the chart's instrument |
| Orders appear on history and not on the forming bar | The condition is true inside the bar and false at its close | Nothing to fix: orders wait for the bar to confirm |

**Related.** [Overview](/script/strategies/overview), [Exits and brackets](/script/strategies/exits-and-brackets), [Position and sizing](/script/strategies/position-and-sizing), [Costs and fills](/script/strategies/costs-and-fills), [Reading the books](/script/strategies/reading-the-books), [Strategy orders reference](/script/reference/strategy), [order.* reference](/script/reference/orders)


## Exits and brackets

Source: https://openalgo.in/script/strategies/exits-and-brackets

This page covers every way a strategy gets out of a position: a stop and a target, a trailing stop, one-cancels-other, exits on the clock, and the protective levels the language defines with `exit()`, `order.bracket()` and the planned `leg.*` and `book.*` calls. You need it for any strategy whose exit is a price level rather than the opposite signal.

A **stop** (stop-loss) closes a losing position at a price you chose in advance. A **target** closes a winning one at a price you chose in advance. A **bracket** is the two together, attached to one entry.

> **What /trading does with exit() and order.bracket() in 0.5.0**
The compiler accepts both calls, and neither protects a position in /trading yet. On the chart and in the Backtest panel the levels they set are never filled, so a bracketed trade stays open until the script itself closes it. The Strategies panel refuses to start a script that calls either one. Until that changes, write every stop and target as a rule the script tests on each bar, as the runnable examples on this page do.

## A complete example

An intraday breakout on 15-minute bars. The entry fixes a stop two ATRs below and a target three ATRs above the decision bar's close, the script tests both on every bar, and a clock rule takes the position off before the close. **ATR** (average true range) is the average size of a bar's move, so a level set in ATRs is wider on a volatile instrument and tighter on a quiet one.

```openscript
version 1
strategy("Breakout with a stop and a target", overlay = true, precision = 2,
         capital = 500000, qty = 1,
         product = "intraday", pyramiding = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

length     = input(20,  "Breakout lookback", min = 2, max = 500)
stopMult   = input(2.0, "Stop, in ATR",   min = 0.2, max = 20)
targetMult = input(3.0, "Target, in ATR", min = 0.2, max = 40)

atrValue      = atr(14)
breakoutLevel = highest(high, length)[1]

// roundToTick is absent while atr is still warming up, or when the host
// states no tick size.
stopPrice   = roundToTick(close - stopMult * atrValue)
targetPrice = roundToTick(close + targetMult * atrValue)

ready    = not isNone(breakoutLevel) and not isNone(stopPrice) and not isNone(targetPrice)
breakout = close > breakoutLevel
inHours  = session.isIn("0930-1500", "Asia/Kolkata")
lateDay  = not session.isIn("0915-1500", "Asia/Kolkata")

// Held in var, so the levels tested and plotted are the ones set at entry.
var entryStop   = none
var entryTarget = none

// Clear the last trade's levels before the entry below can set new ones.
if pos.isFlat
    entryStop   = none
    entryTarget = none

stopHit   = pos.isLong and low <= entryStop
targetHit = pos.isLong and high >= entryTarget

if ready and inHours and pos.isFlat and breakout
    entryStop   = stopPrice
    entryTarget = targetPrice
    buy(tag = "entry")
else if stopHit or targetHit or (pos.isLong and lateDay)
    close()

plot(breakoutLevel, "Breakout level", aqua, style = "step")
plot(entryStop,   "Stop",   red,  style = "step")
plot(entryTarget, "Target", lime, style = "step")
```

Notice where the levels come from: the close of the bar the decision was made on, while the entry fills at the next bar's open. The gap between the two is real, and the report shows it rather than hiding it by recomputing the stop from the fill. The exit fills at the open after the bar that touched a level, which on most days is worse than the level itself; that is the honest cost of a rule you write rather than a level someone holds.

The two tests need no `isNone` guard of their own. While the position is flat, `pos.isLong` is false and the levels are absent; a comparison with an absent value is absent, and an absent condition takes the false branch, so neither test can fire. The clock tests name the zone because the Backtest panel does not state the chart's timezone to the script.

## A level someone holds, or a rule you write

Every exit is one of two things, and confusing them is the most expensive mistake on this page:

| | A level | A rule in the script |
|---|---|---|
| Written as | `exit()`, `order.bracket()`, and the planned `leg.*` and `book.*` levels | `if ... close()` |
| Checked | By whoever holds the level, against the bar's range | Once per bar, where you wrote it |
| Acts | On the bar the level is reached, at the level | On the bar the condition is true, at the next fill point |
| Sends | A stop order or a limit order at the level | An ordinary market order |
| Keeps protecting if the strategy stops running | Yes, once it rests at the destination | No |
| In /trading, 0.5.0 | Not acted on | Runs |

A level is a promise someone else keeps. A rule is a promise you keep, checked when the script runs. A strategy whose exit is "when the trend reading turns" can only use a rule, because nobody but your script knows what the trend reading is. A stop that is the difference between a bad day and a ruinous one is what a level is for, once levels are acted on; until then, write it as a rule and watch the positions a paused strategy leaves behind.

## exit() and order.bracket()

This is how the language attaches a bracket to a position. Everything in this section is accepted by the compiler and checked as described, with the limits the callout at the top of the page sets out.

**A position carries at most one stop and one target at a time.** `exit()` sets them. Calling it again replaces them rather than adding a second pair, and the last call to run on a bar is the one in force.

`exit()` takes its levels as absolute prices (`stop`, `limit`) or as distances from the entry in the instrument's own price units (`loss`, `profit`). `order.bracket()` is the distance form on its own. A distance travels as a distance, because the entry it is measured from is a fill the destination knows before your script does. That makes the distance form the natural partner of a market entry:

```openscript
version 1
strategy("Crossover with a bracket", overlay = true,
         capital = 500000, qty = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

atrMult = input(2.0, "Stop, in ATR", min = 0.5, max = 10)

ef = ema(close, 9)
es = ema(close, 21)
a  = atr(14)

goLong = crossUp(ef, es)
goFlat = crossDown(ef, es)
sized  = not isNone(a)

// The entry and its bracket leave together, from one condition, so there is
// never a bar on which the position exists and its stop does not.
if goLong and pos.isFlat and sized
    buy(tag = "long")
    order.bracket(tag = "long", loss = a * atrMult, profit = a * atrMult * 2)
else if goFlat and pos.isLong
    close()

plot(ef, "Fast", aqua)
plot(es, "Slow", orange)
plot(pos.isFlat ? none : pos.avgPrice, "Entry", silver, style = "step")
```

On the /trading chart and in the Backtest panel this strategy exits only on the opposite cross, because the bracket is not filled there, and the Strategies panel will not start it.

Four rules apply to both calls:

- **One side, one form.** Giving an absolute price and a distance for the same side is refused at compile time with OS3010, because the two would have to be reconciled and any rule for that would surprise somebody. A stop as a price and a target as a distance is fine.
- **An absent level is refused.** `exit()` is an order call, so a level whose value comes out absent is OS7002 and stops the run. Test the level first.
- **The right side of the position.** A stop belongs below a long's entry and a target above it, and the other way round for a short. A price on the wrong side is refused with OS7010. It is only checked against an open position, so an entry and its bracket on the same bar, before the entry has filled, are the ordinary shape and are not refused. A level exactly at the entry is allowed.
- **The tag is a label.** It tells the destination which entry the levels protect. A bracket whose tag matches no order is not refused.

```openscript
version 1
strategy("Two ways to say one stop", overlay = true, qty = 1)

goLong = crossUp(ema(close, 9), ema(close, 21))

if goLong and pos.isFlat
    buy(tag = "entry")
    exit(tag = "entry", stop = close - 10, loss = 10)
```

## One-cancels-other

**One-cancels-other** (OCO) means two orders are linked so that when one fills, the other is cancelled. A bracket needs no such pair kept in step: the position carries one stop and one target as levels rather than two independent resting orders, and reaching either one closes the position. The other level then has nothing to act on, so neither of the two failures that ruin a hand-built bracket can happen: both filling on a gap, or one surviving after the other has closed the trade.

The general case, one arbitrary order cancelling another, is `order.oco()`, which is planned. Until it lands, write the cancel yourself: when one of your orders fills (the position changes), `cancel()` the other tag, guarding on the fact that it is still working as [Orders](/script/strategies/orders#when-the-next-signal-arrives-and-an-order-is-still-working) shows. A stop and a target written as rules, like the complete example above, need no cancel at all: there is only ever one `close()`.

## Trailing stops

A **trailing stop** follows the best price the position has seen and stays a fixed distance behind it, so it locks in more of a winning move the further the move runs. The language's trailing stop is `leg.trail()`, which is planned. There is no `trail` argument on `exit()`: a trail is a rule checked on every bar rather than one price an order can rest at, and one rule with one spelling is easier to hold in your head.

When it lands, `leg.trail(name, distance, activateAt)` will work like this:

- `distance` is in the instrument's price units and is positive.
- The trail activates when the position's profit per unit first reaches `activateAt`, measured from the average entry price. With `activateAt` left out, it activates on the first fill.
- Once active it keeps the best price seen since activation: the highest high for a long, the lowest low for a short, from confirmed bars, and the last price on a bar still forming.
- The level is the best price minus `distance` for a long, plus `distance` for a short, and **it only ever moves in the position's favour**. It never retreats.
- Where a position has both a stop and an active trail, the more protective of the two is in force, and `leg.stopPrice()` reads that level back.

Until then, a trail is a rule you write with [`var`](/script/language/persistence). This one follows the highest high since entry, three ATRs behind, and never lets the level fall:

```openscript
version 1
strategy("Trailing stop in the script", overlay = true, precision = 2,
         capital = 500000, qty = 1, pyramiding = 1, product = "overnight")

trailAtr = input(3.0, "Trail this far behind, in ATR", min = 0.2, max = 20)

atrValue = atr(14)
fast     = ema(close, 9)
slow     = ema(close, 21)
goLong   = crossUp(fast, slow)

var bestHigh  = none
var trailStop = none

if pos.isFlat
    bestHigh  = none
    trailStop = none
else if pos.isLong
    bestHigh  = isNone(bestHigh) ? high : max(bestHigh, high)
    candidate = bestHigh - trailAtr * atrValue
    // The ratchet: the level only ever rises.
    if not isNone(candidate)
        trailStop = isNone(trailStop) ? candidate : max(trailStop, candidate)

stopped = pos.isLong and low <= trailStop

if goLong and pos.isFlat
    buy(tag = "entry")
else if stopped
    close()

plot(trailStop, "Trailing stop", red, width = 2, style = "step")
plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
```

## Exiting on the clock

Intraday strategies on NSE and NFO have to be flat before 15:30. Three facts decide how you write that.

**`closeOnSessionEnd` is not acted on yet.** The option is accepted in the declaration and means "flatten at the session close", and in version 0.5.0 nothing flattens: a backtest carries the position into the next session, and so does a deployment. Write the exit in the script.

**An exit decided on the last bar fills in the next session.** With the default `fillOn = "nextOpen"`, a `close()` decided on the session's last bar fills at the next bar's open, which is the next session's first bar. To be flat by the close, decide on a bar that leaves another bar to fill in. `session.isIn()` tests the time each bar starts at: a bar is inside `"0915-1500"` when it starts at or after 09:15 and before 15:00. So `not session.isIn("0915-1500", "Asia/Kolkata")` is first true on the bar that starts at 15:00, and on 15-minute bars the close it sends fills at the 15:15 open.

**Each place in /trading reads the clock differently.** The chart reads it in the chart's timezone. The Backtest panel reads it only where the script names the zone, as every example here does. `session.isFirstBar` and `session.isLastBar` have no value in either, because /trading does not state the instrument's session hours to the script yet. And the Strategies panel refuses to start a script that calls `session.isIn()` or any `date.*` function on an Indian instrument, so a deployed strategy needs a window built from arithmetic on `time`: [Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today) shows one.

| Exit kind | Written with | Good for |
|---|---|---|
| A cutoff time | `not session.isIn("0915-1500", "Asia/Kolkata")` | Flat before the close, at an interval-independent time |
| Minutes in the trade | `time` minus a `var` set when the position opens | A rule stated in clock time |
| Bars held | `bar.index` minus a `var` set when the position opens | A horizon in bars; `pos.barsHeld` is planned |
| A weekday | `date.dayOfWeek()` with the zone named | A weekly rule, such as flat before a weekly expiry |
| The session's last bar | `session.isLastBar` with `fillOn = "close"` | A host that states session hours; not /trading today |

This strategy gives up on a trade that is not in profit after two hours, never holds more than sixty bars, and is flat before the close:

```openscript
version 1
strategy("Give up on a trade that has not worked", overlay = true, precision = 2,
         capital = 500000, qty = 1, product = "intraday")

holdMinutes = input(120, "Give up after this many minutes", min = 5, max = 1440)
barsCap     = input(60,  "Never hold more than this many bars", min = 2, max = 500)

fast    = ema(close, 9)
slow    = ema(close, 21)
goLong  = crossUp(fast, slow)
lateDay = not session.isIn("0915-1500", "Asia/Kolkata")

// When the position was opened, and on which bar; none while flat. The
// first bar held is the bar the entry filled at the open of.
var enteredAt  = none
var enteredBar = none

if pos.isFlat
    enteredAt  = none
    enteredBar = none
else if isNone(enteredBar)
    enteredAt  = time
    enteredBar = bar.index

// time is in milliseconds, so 60000 of it is one minute.
heldMinutes = isNone(enteredAt) ? none : (time - enteredAt) / 60000
barsHeld    = isNone(enteredBar) ? none : bar.index - enteredBar
notWorking  = pos.isLong and close <= pos.avgPrice

// One chain, so no exit can be sent on the same bar as the entry.
if goLong and pos.isFlat and not lateDay
    buy(tag = "entry")
else if pos.isLong and lateDay
    close()
else if notWorking and heldMinutes >= holdMinutes
    close()
else if pos.isLong and barsHeld >= barsCap
    close()

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
background(pos.isFlat ? none : fade(aqua, 94))
```

The comparisons with `heldMinutes` and `barsHeld` need no `isNone` test: an ordered comparison with an absent value is absent, and an absent condition takes the false branch.

## Levels the engine will hold

The language defines protective levels at three scopes. In version 0.5.0 only the bracket set with `exit()` and `order.bracket()` exists, and /trading does not act on it yet; the rest are planned, and calling one is refused with OS2020.

| Scope | Level | Call | Status |
|---|---|---|---|
| One position (a leg) | Stop and target | `exit()`, `order.bracket()` | Accepted, not acted on in /trading yet |
| One position (a leg) | Standing stop and target | `leg.stop()`, `leg.target()` | Planned |
| One position (a leg) | Trailing stop | `leg.trail()` | Planned |
| The whole strategy (the book) | Combined stop and target, in money | `book.stop()`, `book.target()` | Planned |
| The whole strategy (the book) | A profit floor that activates then advances | `book.lockProfit()` | Planned |
| The whole strategy (the book) | Every stop to its own entry | `book.trailStopsToEntry()` | Planned |
| The session | Entries only inside a window | `book.entryWindow()` | Planned |
| The session | Square off at a time | `book.exitAt()` | Planned |
| The session | Square off before expiry | `book.squareOffAtExpiry()` | Planned |
| The session | A daily loss limit | `book.dailyLoss()` | Planned |
| The session | Flatten at the close | `closeOnSessionEnd = true` | Accepted, not acted on yet |

Unlike `exit()`, the planned levels are not order calls: passing `none` to one removes the level rather than being refused, because removing a stop is something a script means to do. The book levels, and why a combined stop belongs only to a strategy that enters its legs as a unit, are on [Legs and books](/script/strategies/multi-leg-and-books).

### When a held level is tested

These are the language's rules for levels the engine holds, and they arrive with the planned levels above.

1. Every level is tested once per bar, after the script's own statements, in a fixed order: the daily loss limit; the exit time, then the session close, then the expiry square-off; the combined stop, then the combined target; the profit floor; the move of every stop to entry; then each leg in declaration order, its stop, then its target, then its trail. A rule that squares the book off ends the sequence for that bar.
2. On a confirmed bar a level is reached when the bar's range reaches it: `low <= level` for a long's stop, `high >= level` for a long's target, and the reverse for a short. On a bar still forming, only the last price is tested, and the test is taken again when the bar closes.
3. **When one bar's range contains both the stop and the target, the stop is taken.** A bar is four prices and no path, and assuming the better of the two is how a backtest invents money.
4. A stop sends a stop order at its level and a target a limit order at its level, so a backtest fills at the level rather than at the next open. When the bar opens beyond the level, the fill is at the open. Slippage applies to the stop and not to the target.

### The named events

When the held levels land, every transition one causes will be recorded as a named event with the bar's time, the leg, the level and the value that crossed it, so that after a bad day the log says which rule fired rather than only that the position closed.

| Event | Recorded when |
|---|---|
| `legStopHit`, `legTargetHit` | A leg's stop or target was reached and the leg closed |
| `trailActivated`, `trailAdvanced` | A leg's trailing stop switched on, or moved in the leg's favour |
| `combinedStopHit`, `combinedTargetHit` | The book's profit reached the combined stop or target and the book was squared off |
| `lockProfitActivated`, `lockProfitFloorAdvanced`, `lockProfitTriggered` | The profit floor came into being, moved up a step, or was hit |
| `trailToEntryActivated` | Every leg's stop moved to its own entry |
| `sessionEndSquareOff`, `exitTimeSquareOff`, `expirySquareOff` | The book was flattened at the session close, at the exit time, or before a contract's expiry |
| `dailyLossHit` | The day's loss reached the limit, and no entry is taken for the rest of the day |
| `entryRefused` | An entry was refused by the direction filter, the entry window or a daily loss already hit |
| `fillAfterTerminal` | A fill arrived after its order had already ended |

Events reach the run's record and the log, not the chart, and no call reads one: a script that branched on its own stop having fired would be deciding twice what the rule already decided once.

## Pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| A bracketed trade never exits in the backtest | `exit()` and `order.bracket()` levels are not filled in 0.5.0 | Test the levels in the script and `close()`, as the complete example does |
| The Strategies panel will not start the strategy | It calls `exit()` or `order.bracket()` | Replace them with rules the script tests |
| The run stops with OS7002 at the entry | A stop or target taken from `atr` before it warmed up | Test the level with `isNone` before the entry |
| The run stops with OS7010 | A stop moved above a long's entry | Stop below a long, target above it |
| The plotted stop drifts after entry | The plot reads a level recomputed every bar | Hold the level set at entry in a `var` and plot that |
| An intraday position is carried overnight | `closeOnSessionEnd` alone, or an exit decided on the last bar | Exit on a cutoff time that leaves a bar to fill in |
| No exits on the clock in the Backtest panel | A clock test written without a zone | Name the zone: `session.isIn("0915-1500", "Asia/Kolkata")` |

**Related.** [Orders](/script/strategies/orders), [Position and sizing](/script/strategies/position-and-sizing), [Legs and books](/script/strategies/multi-leg-and-books), [Costs and fills](/script/strategies/costs-and-fills), [Sessions and time](/script/data/sessions-and-time), [Sandbox and live](/script/strategies/sandbox-and-live), [Strategy orders reference](/script/reference/strategy), [leg.* reference](/script/reference/legs)


## Position and sizing

Source: https://openalgo.in/script/strategies/position-and-sizing

This page covers two questions every strategy answers on every bar: what am I holding, and how big should the next order be. It explains the [pos.* facts](/script/reference/position), the units an order's size is counted in, how to trade whole lots of an index future or an MCX contract with `chart.lotSize`, and how to size by the money you are prepared to lose or by how much the instrument moves.

## A complete example

This strategy trades a set number of lots of whatever future is on the chart. It works out the size in units from the lot size the host states, so the same file trades NIFTY futures, BANKNIFTY futures or an MCX contract without a change, and trades single shares on an NSE stock where the lot is one.

```openscript
version 1
strategy("Lots on a futures chart", overlay = true, precision = 2,
         capital = 1000000, qtyType = "units",
         product = "intraday", pyramiding = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

lots = input(1, "Lots per trade", min = 1, max = 50)

// chart.lotSize is absent, not 1, when the host has not stated a lot size.
lotUnits = max(orElse(chart.lotSize, 1), 1)
orderQty = lots * lotUnits

fast   = ema(close, 9)
slow   = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy(qty = orderQty, tag = "entry")
else if goFlat and pos.isLong
    close()

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
plot(pos.isFlat ? none : pos.avgPrice, "Entry", fade(silver, 40), style = "step")
```

If the exchange's lot size for the contract on the chart is 75 units, `lots = 2` sends a buy of 150 units, and `close()` sells exactly what is held. Why this file counts in units rather than declaring `qtyType = "lots"` is explained under [Where a size comes from](#where-a-size-comes-from).

Where the lot size comes from depends on where the strategy runs in /trading. The Backtest panel and a deployment from the Strategies panel use the lot size OpenAlgo holds for the instrument. The chart does not state one to the script yet, so on the chart `chart.lotSize` is absent, this file falls back to one unit per lot, and the chart's fill markers show one unit where the backtest shows a lot.

## What a strategy can read about itself

The `pos` namespace answers two questions: what is held right now, and how the run has gone so far. Every entry is a per-bar fact folded from **this strategy's own settled fills**, never from the account's position, for the reason [the overview](/script/strategies/overview) gives.

| Name | When flat | Means | Status |
|---|---|---|---|
| `pos.size` | `0` | Net position in units, positive long and negative short | Runs |
| `pos.isLong`, `pos.isShort`, `pos.isFlat` | `false`, `false`, `true` | The sign of `pos.size`, spelled out | Runs |
| `pos.avgPrice` | absent | Average price of the open position | Runs |
| `pos.entryTime` | absent | When the current position was opened | Planned |
| `pos.barsHeld` | absent | Bars since it was opened, `0` on the entry bar | Planned |
| `pos.entries` | `0` | How many entries make up the current position | Planned |
| `pos.openProfit`, `pos.openProfitPercent` | absent | Unrealised profit, in money and as a percentage of cost, marked to this bar's close | Planned |
| `pos.maxProfit`, `pos.maxLoss` | absent | The best and worst this position has been | Planned |
| `pos.isShared` | either | The account's position in this contract is shared with something else | Planned |
| `pos.equity`, `pos.netProfit`, `pos.tradeCount` | a number | Capital plus profit, realised profit, and closed trades so far | Planned |
| `pos.winRate`, `pos.profitFactor`, `pos.maxDrawdown` | a number | Run statistics | Planned |

Four rules about that table matter more than the table.

**`pos.avgPrice` is absent while flat, and `pos.size` is zero while flat.** They look inconsistent and are not. Zero is the true size of a flat position, so adding `pos.size` to something gives the right answer. Zero is not a price, and `close > pos.avgPrice` while flat would take a branch that looks correct and means nothing. [Absence](/script/language/absent-values) propagates through that comparison, the branch is not taken, and the mistake cannot happen.

**Every figure reflects settled fills, not intentions.** A market order decided on one bar fills at the next bar's open, and `pos.*` changes from that next bar. A resting limit or stop order changes nothing until it fills. So `pos.isFlat` is a complete entry guard for market orders, and not for resting ones: while a limit waits, the strategy is still flat, and a second order goes through. Remember the working order yourself, as [Orders](/script/strategies/orders) shows, until `order.working()` and `order.pending` land.

**Open profit is marked to the bar's close.** Not to a bid or an ask. Until `pos.openProfit` lands you can compute the same figure yourself, as the panel below does.

**`pos.isShared` stays a yes or no.** It will say that the account's position in this contract is shared: another strategy or a manual trade is in the same contract as this one. No call turns it into a number, because a script that could read the account's quantity would size against it.

Here is a panel that puts the position on the chart, using only what runs today. A strategy whose state is visible is a strategy you can debug without a print log.

```openscript
version 1
strategy("Position panel", overlay = true, precision = 2,
         capital = 500000, qty = 1)

panel = table("Position", 4, 2, position = "topRight", textColor = silver)

fast   = ema(close, 9)
slow   = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy(tag = "entry")
else if goFlat and pos.isLong
    close()

// Open profit by hand, marked to the close, until pos.openProfit lands.
pointValue = orElse(chart.pointValue, 1)
openProfit = pos.isFlat ? none : (close - pos.avgPrice) * pos.size * pointValue

// One place decides what an absent reading looks like.
fn show(value, decimals) => isNone(value) ? "flat" : text(value, decimals)

// Only on the newest bar: a panel shows one state.
if bar.isLast
    cell(panel, 0, 0, "Size")
    cell(panel, 0, 1, text(pos.size))
    cell(panel, 1, 0, "Average")
    cell(panel, 1, 1, show(pos.avgPrice, 2))
    cell(panel, 2, 0, "Open profit")
    cell(panel, 2, 1, show(openProfit, 0), textColor = orElse(openProfit, 0) >= 0 ? lime : red)
    cell(panel, 3, 0, "Lot size")
    cell(panel, 3, 1, show(chart.lotSize, 0))

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
```

[Tables](/script/visuals/tables) covers the table calls this panel uses.

## Where a size comes from

The declaration sets the default size and the unit it is counted in. An order that names no quantity uses the default; an order that names one overrides it, in the same unit.

| `qtyType` | `qty = 2` means | Backtest panel, 0.5.0 | Strategies panel, 0.5.0 |
|---|---|---|---|
| `"units"` | Two units: two shares, or two units of a contract | Filled as written | Runs |
| `"lots"` | Two lots, that is `2 * chart.lotSize` units | Converted through the instrument's lot size, with the closing defect below | Refused before it starts |
| `"cash"` | Two units of currency, turned into units at the fill price | Refused before the run, with OS6021 | Refused before it starts |
| `"equityPercent"` | Two percent of current equity, turned into units at the fill price | Refused before the run, with OS6021 | Refused before it starts |

A backtest fills in units and keeps no running equity to size against, so it cannot convert cash or a percentage of equity, and says so with OS6021 before any bar runs rather than filling the number as written. The runner behind the Strategies panel sends only a quantity the script states in units.

> **Lots and closing orders in the 0.5.0 backtest**
Under `qtyType = "lots"` the orders you size are converted correctly, but a close whose size the engine works out for you is not. A bare `close()`, a tagged `close(tag = ...)` and `order.reverse()` all work their size out in units, and the 0.5.0 backtest converts that size from lots a second time. With a lot size of 50, closing a two-lot position (100 units) sells 100 lots, 5,000 units, and leaves the strategy short 4,900.

Count in units and compute the size from `chart.lotSize`, as the complete example does: that is also the only unit a deployment accepts. If you keep `qtyType = "lots"` for a backtest, flatten with a close that states its size in lots, such as `close(qty = lots)`. Do not flatten with `sell(qty = lots)`: under lots that opens a separate short beside the long, and the trade list then shows both as open trades that never close.

Pick the unit that matches how you describe the trade to yourself, and use it everywhere. A script whose declaration says lots and whose orders pass unit counts is a script that will one day trade seventy-five times too much.

## Instruments that trade in lots

Index futures and options on NFO, and most MCX contracts, cannot be traded in single units. The exchange trades them in lots, and an order for anything that is not a whole number of lots is rejected. Three chart facts tell a script what it needs:

| Fact | Name | Absent when |
|---|---|---|
| Units in one lot | `chart.lotSize` | The host has not stated one |
| Money per one point of price, per unit | `chart.pointValue` | The host has not stated one |
| Smallest price step | `chart.tickSize` | The host has not stated one |

**Absent is not 1.** When the host has not stated a lot size, `chart.lotSize` is absent, and absence propagates through arithmetic, so a size computed from it is absent too and the order is refused with OS7002. Decide once what a missing lot size means in your script: `max(orElse(chart.lotSize, 1), 1)` treats it as one unit. In /trading that fallback matters on the chart, which states no lot size. The Backtest panel states the lot size OpenAlgo holds for the instrument, and where OpenAlgo holds none it runs on a lot of 1 and a tick of 0.05 and says so in the line under the report's figures.

**Round down to whole lots yourself.** A quantity that is not a whole number of lots is catalogued as OS7005, and in version 0.5.0 nothing raises it: the order is sent as written and the destination is left to reject it. `order.roundToLot()` is planned. Until it lands, round with arithmetic:

```openscript
version 1
strategy("Whole lots", overlay = true, qtyType = "units")

wantedUnits = input(170, "Units wanted", min = 1)

lotUnits = max(orElse(chart.lotSize, 1), 1)

// Down, to a whole number of lots: 170 units at a lot of 75 is 150.
wholeLots = floor(wantedUnits / lotUnits) * lotUnits

fast   = ema(close, 9)
slow   = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

// Fewer units wanted than one lot rounds down to nothing, and a size of
// zero would be refused (OS7004), so it is tested.
if goLong and pos.isFlat and wholeLots > 0
    buy(qty = wholeLots, tag = "entry")
else if goFlat and pos.isLong
    close()
```

Round down unless you mean up. A size rounded up is a position larger than the script asked for, and if every entry rounds up, every entry is too large in the same direction for the life of the run.

**Money needs the point value.** `chart.pointValue` is the money one unit makes or loses when the price moves by one. For a cash equity it is 1, and profit is simply price change times shares. For a contract quoted with a multiplier it is that multiplier, and leaving it out of a risk calculation is how a script ends up risking the multiplier times what it meant to. Multiply by `orElse(chart.pointValue, 1)` wherever money meets a price distance.

## Sizing by capital at risk

This is how most traders actually describe size: not "two lots" but "I am willing to lose five thousand on this trade". Turn that sentence into a quantity and the stop distance sets the size, so a wide stop buys fewer units and every trade risks about the same.

```openscript
version 1
strategy("Risk the same amount every time", overlay = true, precision = 2,
         capital = 1000000, qtyType = "units",
         product = "overnight", pyramiding = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

riskAmount = input(5000, "Money risked per trade", min = 100)
stopMult   = input(2.0,  "Stop, in ATR", min = 0.2, max = 20)
maxLots    = input(10,   "Never more than this many lots", min = 1, max = 500)

atrValue = atr(14)
fast     = ema(close, 9)
slow     = ema(close, 21)
goLong   = crossUp(fast, slow)
goFlat   = crossDown(fast, slow)

lotUnits   = max(orElse(chart.lotSize, 1), 1)
pointValue = orElse(chart.pointValue, 1)

stopDistance = stopMult * atrValue
stopPrice    = roundToTick(close - stopDistance)

// Money at risk over money lost per unit, rounded down to whole lots and
// capped, because as the stop distance shrinks the division grows without
// limit.
rawUnits = stopDistance > 0 ? riskAmount / (stopDistance * pointValue) : none
units    = isNone(rawUnits) ? none : min(floor(rawUnits / lotUnits), maxLots) * lotUnits

canTrade = not isNone(units) and units > 0 and not isNone(stopPrice)

// The stop the size was computed from, held for the life of the position.
var entryStop = none

if pos.isFlat
    entryStop = none

stopHit = pos.isLong and low <= entryStop

if goLong and pos.isFlat and canTrade
    entryStop = stopPrice
    buy(qty = units, tag = "entry")
else if stopHit or (goFlat and pos.isLong)
    close()

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
plot(entryStop, "Stop", red, style = "step")
```

The comparison `stopDistance > 0` does two jobs. It keeps the division away from zero, and because an ordered comparison with an absent value is absent and an absent condition takes the false branch, it also covers the warmup bars where `atr()` has no value yet.

The stop is a rule the script tests on each bar rather than a level set with `exit()`, because /trading does not act on `exit()` levels in 0.5.0; [Exits and brackets](/script/strategies/exits-and-brackets) explains.

This risks a fixed amount of money rather than a percentage of equity, because `pos.equity` is planned. `order.qtyForRisk()`, also planned, will do the division in one call and return `none` when the entry and the stop are equal.

Three things this sizing does not protect you from. It assumes the exit happens at the stop price, while a rule exits at the next bar's open after the bar that reached the stop, and a gap can open well beyond it, so the loss taken is often larger than the amount. It sizes one trade, not ten correlated trades taken the same morning. And it is a division, so it needs the cap.

## Sizing by volatility

Risk sizing asks how far away the stop is. Volatility sizing asks how much the instrument moves in a day, and aims for the same money move whatever you trade. It is what lets one strategy run across a bank stock, a NIFTY future and an MCX contract without the fastest one dominating the equity curve.

```openscript
version 1
strategy("Constant volatility exposure", overlay = true, precision = 2,
         capital = 1000000, qtyType = "units",
         product = "overnight", pyramiding = 1)

targetMove = input(5000, "Target daily move, in money", min = 100)
atrLen     = input(20,   "ATR length", min = 2, max = 200)

atrValue   = atr(atrLen)
lotUnits   = max(orElse(chart.lotSize, 1), 1)
pointValue = orElse(chart.pointValue, 1)

// What one unit moves in money on an average bar, and how many units make
// the target move.
movePerUnit = atrValue * pointValue
rawUnits    = movePerUnit > 0 ? targetMove / movePerUnit : none
units       = isNone(rawUnits) ? none : floor(rawUnits / lotUnits) * lotUnits

trend   = ema(close, 50)
upTrend = close > trend
canSize = not isNone(units) and units > 0

if upTrend and pos.isFlat and canSize
    buy(qty = units, tag = "entry")
else if not upTrend and pos.isLong
    close()

plot(trend, "Trend", orange, width = 2)
plot(units, "Units the model wants", aqua, overlay = false)
```

Run it on a daily chart for "a day" to mean a day: `atr()` measures the average range of the chart's own bars.

## Sizing helpers

Four helpers will turn a sentence about money into a quantity. All four are planned in version 0.5.0.

| Helper | Returns | For |
|---|---|---|
| `order.qtyForCash()` | Units | "Put two lakh into this" |
| `order.qtyForEquityPercent()` | Units | "Put ten percent of the account into this" |
| `order.qtyForRisk()` | Units, or `none` when entry and stop are equal | "Lose no more than this if I am wrong" |
| `order.roundToLot()` | Units | Round to a whole number of lots, down unless told otherwise |

The first two size the position and say nothing about what it can lose; the third sizes the loss and lets the position be whatever that implies. They answer different questions, and a strategy described in terms of risk should not be sized in terms of exposure because the exposure version is one line shorter.

## Adding to a position

`pyramiding` in the declaration is how many entries one direction may hold. The default is 1, and an entry beyond the limit is refused with OS7008, which stops the run. When adding is the intent, say so in both places: raise the limit, and count the entries in the guard, so the script never relies on the refusal to stop it. `pos.entries` is planned, so the count below is kept in a `var`:

```openscript
version 1
strategy("Add on strength", overlay = true, precision = 2,
         capital = 1000000, qtyType = "units", pyramiding = 3)

lots = input(1, "Lots per add", min = 1, max = 50)

lotUnits = max(orElse(chart.lotSize, 1), 1)
trend    = ema(close, 50)
upTrend  = close > trend
newHigh  = high > highest(high, 20)[1]

var entries   = 0
var lastAddAt = none

if pos.isFlat
    entries   = 0
    lastAddAt = none

// A fresh high and five bars of separation, not simply "still above the
// average", which would add on every bar of the move.
spaced = isNone(lastAddAt) or bar.index - lastAddAt >= 5

if upTrend and newHigh and spaced and entries < 3
    buy(qty = lots * lotUnits, tag = "add")
    entries   = entries + 1
    lastAddAt = bar.index
else if not upTrend and pos.isLong
    close()

plot(trend, "Trend", orange)
plot(pos.isFlat ? none : pos.avgPrice, "Average", fade(silver, 40), style = "step")
```

`pos.avgPrice` moves as you add, which is the point of plotting it: a stop measured from the average of three entries is a different stop from one measured from the first.

> **How the 0.5.0 report marks a scale-in**
The report's equity curve marks a trade at the size it ended up at, and at its final average price, from the bar it first opened. A position built in three adds is therefore marked, before the second add, on units it did not yet hold. For a strategy that adds as the price rises, like this one, that shows a drawdown the account never had, and the report's maximum drawdown is taken from the same curve. A partial close is misread the same way: after it settles the trade is still marked at its full size, so the part already closed is counted twice in the open profit. The realised profit is unaffected.

## Pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| Two entries where the script meant one | A resting order was still working while `pos.isFlat` was true | Remember the working order in a `var` |
| A lots strategy ends up hugely short in the backtest | `close()` under `qtyType = "lots"` | Count in units from `chart.lotSize`, or flatten with `close(qty = lots)` |
| The backtest refuses to start with OS6021 | `qtyType = "cash"` or `"equityPercent"` | Count in units |
| The Strategies panel will not start the strategy | Any `qtyType` other than `"units"` | Count in units from `chart.lotSize` |
| The chart's markers show one unit where the backtest shows a lot | The chart states no lot size, so `chart.lotSize` falls back to 1 there | Nothing to fix; read sizes from the Backtest panel |
| Every order is refused with OS7002 | A size computed from `chart.lotSize` while it is absent | `max(orElse(chart.lotSize, 1), 1)` |
| The size explodes on quiet days | Risk sizing with no cap as the stop distance shrinks | Cap the size |
| The run stops with OS7008 | An entry beyond `pyramiding` | Guard entries, or raise `pyramiding` on purpose |
| A future sized as if one point were one rupee | `chart.pointValue` left out of the arithmetic | Multiply by `orElse(chart.pointValue, 1)` |

**Related.** [Overview](/script/strategies/overview), [Orders](/script/strategies/orders), [Exits and brackets](/script/strategies/exits-and-brackets), [Legs and books](/script/strategies/multi-leg-and-books), [Costs and fills](/script/strategies/costs-and-fills), [pos.* reference](/script/reference/position), [chart.* reference](/script/reference/chart)


## Legs and books

Source: https://openalgo.in/script/strategies/multi-leg-and-books

This page covers the multi-leg model of OpenScript: **legs**, each one contract a strategy trades, and the **book**, all of a strategy's legs taken together, with the rules that manage them. It is the model for option structures such as a short straddle or strangle on NIFTY or BANKNIFTY weekly options, where two or more contracts only make sense together and the risk belongs to the combination rather than to any one leg.

> **Planned in version 0.5.0**
Every `leg.*` and `book.*` name on this page is planned. In version 0.5.0 a strategy trades one instrument, the one on its chart, and a call to any `leg.*` or `book.*` name is refused where you wrote it with OS2020. This page describes the design so you can plan for it, and the last section shows what you can build today.

## Why multi-leg positions need their own model

Take a short straddle: sell the at-the-money call and the at-the-money put of the same NIFTY expiry, where at the money means the strike nearest the index's current level. If the index rises 150 points, the call loses and the put gains. If it falls, the reverse. The position makes money when the index stays near the strike and time passes, and loses when it moves far in either direction. That profile belongs to the pair, not to either leg.

A stop placed on each leg on its own gets this wrong in both directions. It fires on moves the combination absorbed, stopping out the losing leg on a day the other leg was paying for it, and it misses the losses that build slowly across both legs at once. **For a position like this, measure the stop on the sum.** Two single-instrument strategies running side by side are not a substitute: they are two strategies that each see half the position.

## A short straddle, as it will be written

Here is the design for a NIFTY short straddle entered once a day after the open, with a combined stop, a combined target, a profit floor, a clock exit and a square-off before expiry. It is shown so you can see the shape of the language; the compiler refuses it today with OS2020.

```openscript
version 1
strategy("NIFTY short straddle", precision = 2,
         capital = 1000000, qtyType = "lots", qty = 1,
         product = "intraday")

underlying = input("NIFTY", "Underlying")
lots       = input(1, "Lots per leg", min = 1, max = 50)

// Both legs are described, not named: the nearest expiry, at the money.
// The host resolves each description to one contract before the first bar.
leg.relative("ce", underlying, "option", expiryRank = 0, strikeOffset = 0,
             right = "call", side = "sell", qty = lots)
leg.relative("pe", underlying, "option", expiryRank = 0, strikeOffset = 0,
             right = "put", side = "sell", qty = lots)

// Rules the engine holds for the whole book, in money.
book.stop(6000)
book.target(9000)
book.lockProfit(4000, 2000, step = 2000, advance = 1500)

// Rules about the clock.
book.exitAt("1515")
book.squareOffAtExpiry(15)
book.dailyLoss(15000)

entryTime = session.isIn("0920-1000")

var enteredToday = false

if session.isFirstBar
    enteredToday = false

// One decision sends both legs their declared side and quantity.
if entryTime and not enteredToday and not book.isOpen
    book.enter(tag = "straddle")
    enteredToday = true

plot(book.profit, "Book profit", aqua, width = 2)
```

Read it top to bottom: two legs declared once, the rules that manage the book set once, and a single entry decision. There is no exit in the script at all. The combined stop, the combined target, the profit floor, the 15:15 exit and the expiry square-off are levels the engine holds and tests on every bar, in a fixed order, and each one records a named event when it fires.

## Legs

A **leg** is one contract a strategy trades, named by a string you choose. A file declares its legs once, at the top level, before the first bar. A file that declares none has exactly one leg, the instrument on its chart, which is every strategy in version 0.5.0.

There are two ways to declare one:

| Call | Declares |
|---|---|
| `leg.fixed(name, symbol, exchange, product, qty, side)` | A leg on a contract named outright, such as one futures contract |
| `leg.relative(name, underlying, kind, expiryRank, expiryCycle, strikeOffset, right, reference, exchange, product, qty, side)` | A leg on a contract described relative to an underlying: nearest expiry, at the money, call |

A relative leg is described field by field:

| Field | Holds | Default |
|---|---|---|
| `underlying` | The instrument the contract derives from, such as NIFTY | required |
| `kind` | `"future"` or `"option"` | required |
| `expiryRank` | `0` for the nearest expiry, `1` for the one after it | `0` |
| `expiryCycle` | Which series, where an exchange lists more than one (weekly and monthly) | the exchange's default series |
| `strikeOffset` | Strikes away from the money: `0` at the money, positive above, negative below | `0` |
| `right` | `"call"` or `"put"`, and absent for a future | none |
| `reference` | The price the offset is measured from | the underlying's price when the leg is resolved |
| `exchange`, `product`, `qty`, `side` | The leg's own bookkeeping: where it trades, the product, its size and whether the book buys or sells it | the chart's exchange, the declaration's product and size, `"buy"` |

The rules for declaring a leg follow from it being part of the strategy's fixed shape, like a plot. The compiler will apply them when legs land; today any `leg.*` call is refused with OS2020 before they are reached.

- **Top level only.** A leg inside an `if` or a function is refused with OS3006. You cannot hide a leg on some bars; declare it and decide per bar whether to send it an order.
- **Fixed before the first bar.** Every argument must be a literal, arithmetic over literals, or an `input()` call. A bar-dependent one is OS3003.
- **One name, one leg.** Two legs with the same name are OS3017, because every later call keys on the name.
- **A future has no right or strike.** `right` or `strikeOffset` given with `kind = "future"` is OS3010.

**The engine never builds a symbol.** A symbol format built for one exchange means nothing on another, so the description goes to the host and a resolved contract comes back. A description the host cannot resolve is OS6007, before the first bar, and the strategy does not start.

**A relative leg resolves once.** This is how you avoid closing a position you do not hold. A leg described as "nearest expiry, at the money, call" resolves on a quiet morning to one strike, and the entry carries that contract. If the description were resolved again at the exit, after NIFTY had moved 200 points, "at the money" would name a different strike: the closing order would go to a contract the strategy never held, and the one it does hold would stay open with nothing managing it. So the contract is fixed for the run, and five calls read it back:

| Call | Returns |
|---|---|
| `leg.symbol()` | The resolved contract, which is what the orders carried |
| `leg.exchange()` | The exchange the orders were sent to |
| `leg.product()` | The product actually sent |
| `leg.expiry()` | The contract's expiry, absent for a contract with none |
| `leg.strike()` | The contract's strike, absent for a contract with none |

Print them on the first bar of a strategy and the log answers "what did it actually trade" without anyone having to work out what was at the money that morning.

## Two shapes: as a unit, or per leg

A strategy takes one of two shapes, and the calls you use decide which.

| Shape | Entered and exited with | Suits |
|---|---|---|
| As a unit | `book.enter()`, `book.exit()` | A position whose legs only make sense together: straddles, strangles, spreads |
| Per leg | `leg.enter()`, `leg.exit()`, and their short spellings `buy()`, `sell()`, `close()`, `exit()`, `order.place()`, `order.reverse()` | Legs that open and close on their own signals |

`book.enter(tag)` sends every declared leg its declared side and quantity in one decision, and `book.exit(tag)` closes every open leg. `leg.enter(name, side, qty, limit, stop, tag)` and `leg.exit(name, qty, limit, stop, tag)` act on one leg at a time, filtered by `book.direction()`, which limits entries to `"long"`, `"short"` or `"both"`. Every single-instrument strategy in version 0.5.0 is already the per-leg shape, written the short way.

**Why a combined stop fits only one shape.** `book.profit` is measured from the last moment the book was flat. In a strategy that enters as a unit, that moment is the start of the current trade, because the book goes flat between trades, so a combined stop is a stop on that trade: "square off when this straddle is six thousand down". In a per-leg strategy the book may never be flat: one leg closes as another opens and a third has been running since Tuesday. The window would start at a moment no rule chose and no reader could name, and a stop on an arbitrary window is worse than none, because it looks like a stop.

So the language refuses the combination rather than defining it. When books land, these two rules will be checked at compile time:

- A file that calls `book.enter()` or `book.exit()` and also any per-leg entry or exit is refused.
- A file that calls `book.stop()`, `book.target()`, `book.lockProfit()` or `book.trailStopsToEntry()` without `book.enter()` is refused, with the fix naming `leg.stop()` and `leg.target()`.

Both hold in a one-leg file too. One rule that is always true is easier to hold in your head than one with an exception, and a script that grows a second leg later would otherwise change meaning on the day it grew it.

## Reading the legs

In a file with one leg, `pos.size`, `pos.avgPrice` and the other single-position facts describe the position. In a file that declares more than one leg, those facts are refused at compile time: adding a quantity of one contract to a quantity of another is not a position in anything, and averaging two average prices gives a price at which nothing traded. Each leg is read by name instead:

| Call | Reads | When the leg is flat |
|---|---|---|
| `leg.size()` | Signed units this strategy holds in the leg | `0` |
| `leg.isOpen()` | Whether the leg holds a position | `false` |
| `leg.avgPrice()` | Average price of the leg's open position | absent |
| `leg.entryTime()` | When the leg's current position was opened | absent |
| `leg.profit()` | The leg's open profit in money, marked to this bar's close | `0` |
| `leg.stopPrice()`, `leg.targetPrice()` | The stop and target actually in force | absent when none is set |

A leg has no equivalent of `pos.isLong`, `pos.barsHeld`, `pos.maxProfit` or `pos.maxLoss`: the sign of `leg.size()` answers the first, `leg.entryTime()` the second, and a `var` the script keeps the other two. `pos.equity`, `pos.netProfit`, `pos.tradeCount` and `pos.isShared` are money, counts and a yes or no, which do add across legs, so they read the whole strategy in every file.

## The book

The **book** is every leg a strategy has declared, taken together. It reads back three facts:

| Name | Reads |
|---|---|
| `book.profit` | The book's profit in money: every leg's open profit plus everything realised since the book was last flat |
| `book.dayProfit` | The same, measured from this session's open |
| `book.isOpen` | Whether any leg holds a position |

And it holds these rules. Each call sets a rule that stays in force until it is replaced, and passing `none` removes it.

| Call | Rule |
|---|---|
| `book.stop()` | Square off every leg when the book's profit falls to minus `amount` |
| `book.target()` | Square off every leg when the book's profit reaches `amount` |
| `book.lockProfit()` | Once profit reaches `activateAt`, keep a floor at `lock`, raised by `advance` for every further `step`; square off if profit falls to the floor |
| `book.trailStopsToEntry()` | Move every leg's stop to its own entry once the book is `at` in profit |
| `book.entryWindow()` | Allow new entries only inside a window such as `"0920-1100:12345"` |
| `book.exitAt()` | Square off every leg at a time such as `"1515"`, in the chart's timezone |
| `book.squareOffAtExpiry()` | Square off a leg this many minutes before its contract expires |
| `book.dailyLoss()` | Square off, and take no more entries today, once today's loss reaches `amount` |

A window that does not parse, a time that is not four digits, and a direction other than `"long"`, `"short"` or `"both"` are each refused with OS3008. `step` and `advance` go together or not at all; one without the other is OS3009.

**The profit floor, worked through.** `book.lockProfit(4000, 2000, step = 2000, advance = 1500)` does nothing until the book is 4,000 up. At that moment a floor exists at 2,000. For every further 2,000 of profit the floor rises by 1,500: at 6,000 it stands at 3,500, at 8,000 at 5,000. The floor never moves down. If the book's profit falls to the floor, every leg is squared off. Read as a sentence: once I am four thousand up I will not give back more than two thousand of it, and for every two thousand further I raise that line by fifteen hundred.

Two notes that each save a day. `book.dailyLoss()` tests `book.dayProfit`, measured from this session's open, so the limit means today and not the whole backtest. And `book.squareOffAtExpiry()` is what keeps a strategy from holding a weekly option into settlement, a calendar event no price rule can see coming.

The order in which the book's rules and each leg's levels are tested on a bar, and the named events each one records, are on [Exits and brackets](/script/strategies/exits-and-brackets#when-a-held-level-is-tested).

## A short strangle with a stop on each leg

The straddle above measures its stop on the sum. A common alternative on a short strangle puts a stop on each leg, measured on that leg's own premium, and moves both stops to cost once the book is in profit. The language supports both; know which one you are writing, because a stop per leg will take the losing leg off on a day the other leg was paying for it. Per-leg levels are not entries or exits, so they sit alongside `book.enter()`. This is the design; it is refused today with OS2020.

```openscript
version 1
strategy("BANKNIFTY short strangle", precision = 2,
         capital = 1000000, qtyType = "lots", qty = 1,
         product = "intraday")

underlying = input("BANKNIFTY", "Underlying")
lots       = input(1,  "Lots per leg", min = 1, max = 50)
stopPct    = input(30, "Stop per leg, percent of its premium", min = 5, max = 200)

// Two strikes out of the money on each side.
leg.relative("ce", underlying, "option", strikeOffset = 2,
             right = "call", side = "sell", qty = lots)
leg.relative("pe", underlying, "option", strikeOffset = -2,
             right = "put", side = "sell", qty = lots)

book.trailStopsToEntry(3000)
book.exitAt("1515")
book.squareOffAtExpiry(15)

entryTime = session.isIn("0920-1000")

var enteredToday = false

if session.isFirstBar
    enteredToday = false

if entryTime and not enteredToday and not book.isOpen
    book.enter(tag = "strangle")
    enteredToday = true

// A short leg's stop sits above the premium it was sold at, set once the leg
// has filled and its average price is known.
if leg.isOpen("ce") and isNone(leg.stopPrice("ce"))
    leg.stop("ce", roundToTick(leg.avgPrice("ce") * (1 + stopPct / 100)))

if leg.isOpen("pe") and isNone(leg.stopPrice("pe"))
    leg.stop("pe", roundToTick(leg.avgPrice("pe") * (1 + stopPct / 100)))

plot(book.profit, "Book profit", aqua, width = 2)
plot(leg.profit("ce"), "Call leg", orange)
plot(leg.profit("pe"), "Put leg", teal)
```

## What you can build in version 0.5.0

Until legs and books land, three patterns cover most of what options traders want from a script.

**Watch the combination with a study.** A study can read two option contracts with `req.symbol()` and plot their combined premium, so you see the straddle's value on one line, with a gap rather than a false number on any bar where one leg has no price. [Other instruments](/script/data/other-instruments) explains the read, and [Example scripts](/script/getting-started/example-scripts#6-combined-premium) walks through a complete combined-premium study.

**Trade one leg from its own chart.** Add a strategy to an option's chart and it trades that contract, in whole lots from `chart.lotSize`, with every fill in its own books and a backtest that runs. This one sells the option once a day after 09:20, with a stop and a target measured from the premium it actually sold at, and exits before the close. **Premium** is the option's price, and a short option makes money as the premium falls:

```openscript
version 1
strategy("Short option, premium stop", precision = 2,
         capital = 1000000, qtyType = "units",
         product = "intraday", pyramiding = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

lots      = input(1,  "Lots", min = 1, max = 50)
stopPct   = input(30, "Stop, percent of the entry premium", min = 1, max = 500)
targetPct = input(50, "Target, percent of the entry premium", min = 1, max = 99)

lotUnits = max(orElse(chart.lotSize, 1), 1)

// Every clock test names the zone, so it holds in the Backtest panel as well
// as on the chart. A new IST date is a new session.
newDay    = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")
entryTime = session.isIn("0920-1000", "Asia/Kolkata")
lateDay   = not session.isIn("0915-1500", "Asia/Kolkata")

var doneToday = false

if newDay
    doneToday = false

// Measured from the fill: pos.avgPrice is the premium actually sold at.
stopLevel   = pos.isShort ? roundToTick(pos.avgPrice * (1 + stopPct / 100)) : none
targetLevel = pos.isShort ? roundToTick(pos.avgPrice * (1 - targetPct / 100)) : none

hit    = pos.isShort and high >= stopLevel
banked = pos.isShort and low <= targetLevel

if entryTime and pos.isFlat and not doneToday
    sell(qty = lots * lotUnits, tag = "premium")
    doneToday = true
else if pos.isShort and (hit or banked or lateDay)
    close()

plot(stopLevel,   "Stop",   red,  style = "step")
plot(targetLevel, "Target", lime, style = "step")
plot(pos.isShort ? pos.avgPrice : none, "Premium sold", fade(silver, 40), style = "step")
```

The entry window is tested against the time each bar starts, and its end is exclusive, so make it wider than the chart's interval: on a 15-minute chart no bar starts inside `"0920-0930"`, while `"0920-1000"` holds the 09:30 and 09:45 bars. The size counts in units computed from the lot size, for the reason [Position and sizing](/script/strategies/position-and-sizing#where-a-size-comes-from) gives. The stop and target are rules the script tests, because /trading does not act on `exit()` levels in 0.5.0. To deploy it from the Strategies panel, replace the clock tests with a window built from arithmetic on `time`, as [Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today) shows, because the runner refuses to start a script that reads the calendar.

**Manage two legs from one chart, with limits.** A strategy on one leg's chart can read the other leg with `req.symbol()`, manage the combined premium, trade its own leg, and raise an `alert()` for the other leg that you route yourself. The combined stop then measures the right thing, but only the chart's leg is in the strategy's books, the other leg's fills are not, and the version 0.5.0 backtest refuses such a strategy before the first bar with OS6006, because a backtest is given the chart's own bars and cannot supply another instrument's. Treat it as a bridge until `book.enter()` lands, not as a two-leg strategy.

**Related.** [Overview](/script/strategies/overview), [Orders](/script/strategies/orders), [Exits and brackets](/script/strategies/exits-and-brackets), [Position and sizing](/script/strategies/position-and-sizing), [Reading the books](/script/strategies/reading-the-books), [leg.* reference](/script/reference/legs), [book.* reference](/script/reference/books)


## Costs and fills

Source: https://openalgo.in/script/strategies/costs-and-fills

This page covers the two assumptions every backtest makes about money: the price each order filled at, and what each fill cost. You need it before you trust any result, because a backtest without costs describes a market in which trading is free, and no such market exists.

Two terms run through the page. A **fill** is one order being executed: a buy that opens a position is one fill and the sell that closes it is another. A **round trip** is the pair, from flat back to flat. **Slippage** is the gap between the price your rule saw and the price you actually got.

The shorter the holding period, the more this matters. Take a strategy that makes two round trips a day, five lakh a side. The charges on one round trip come to roughly two hundred and thirty rupees, and one tick of slippage on each of the two fills adds more on top. Five hundred round trips a year is something like a lakh and a half of cost. An idea whose gross edge is three lakh a year is a good idea; the same idea with the costs left out looks like a great one. The cost model is the part of a strategy that decides whether the rest of it was worth writing.

## A costed strategy

Every cost setting lives in the `strategy()` declaration. This is the EMA cross from the [overview](/script/strategies/overview) with fills and costs stated rather than left at their defaults:

```openscript
version 1

// 0.023 percent per fill: an illustrative intraday equity cost stack, divided
// between the two fills of a round trip. Take your own rates from your own
// contract note, and write down where the number came from.
strategy("Costed EMA cross", overlay = true, precision = 2,
         capital = 500000, qty = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "percent", commission = 0.023)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 2, max = 500)

fast = ema(close, fastLen)
slow = ema(close, slowLen)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy()
else if goFlat and pos.isLong
    close()

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
// The price the position was actually filled at, while one is open. Where it
// sits away from the close that triggered the entry, that gap is the fill model.
plot(pos.isFlat ? none : pos.avgPrice, "Filled at", silver, style = "step")
```

Run it from the Backtest panel and open the **Settings** section: under **Declared by the script** the panel lists the capital, order size, pyramiding, commission, slippage and fill rule it ran with. They are shown and not offered. To change one, edit the `strategy()` line and run again. [Backtesting](/script/strategies/backtesting) walks through the panel.

## The options that set fills and costs

| Option | Default | Accepts | What it decides |
|---|---|---|---|
| `fillOn` | `"nextOpen"` | `"nextOpen"`, `"close"` | The price a market order decided on a bar is filled at |
| `slippage` | `0` | a number of ticks, zero or more | How much worse than that price every market or stop fill is |
| `commission` | `0` | a number, zero or more | The amount charged, in the unit `commissionType` names |
| `commissionType` | `"perTrade"` | `"perTrade"`, `"perUnit"`, `"percent"` | What `commission` is measured against |
| `capital` | `100000` | a number | The starting equity the report's return is measured against |
| `qty`, `qtyType` | `1`, `"units"` | see [Position and sizing](/script/strategies/position-and-sizing) | How many units each order carries |

Leaving any of these at its default is a decision, even when it does not feel like one. Zero slippage and zero commission is the decision that trading is free.

A negative `slippage` or `commission` compiles, but the backtest refuses to start with it (OS6021), because a negative cost would pay the strategy for trading.

## Where a market order fills

`fillOn` names the price a market order is filled at when the script decides on it at a bar's close.

| `fillOn` | Decision made on | Filled at | What it assumes about you |
|---|---|---|---|
| `"nextOpen"` (default) | The close of bar `i` | The open of bar `i + 1` | You acted on a closed bar and took whatever the market opened at |
| `"close"` | The close of bar `i` | The close of bar `i` | You could trade at the same close your rule was computed from |

The default is `"nextOpen"` because a decision made from a bar's close cannot be filled at that same close in the real market. By the time the close is a number you can compare with a moving average, the bar is over. A backtest that fills there credits you with a price that was only knowable after the last moment you could have traded at it. On an instrument that gaps, the difference between one bar's close and the next bar's open is often larger than every charge on this page put together.

In both cases the trade list and the chart mark the entry on the bar after the decision, because that is the first bar that runs holding the position. Only the price differs.

`"close"` is not forbidden, because there are honest uses for it: an instrument with a closing auction you can genuinely take part in, or a rule whose inputs all come from bar `i - 1`, so that filling at bar `i`'s close looks ahead at nothing. If you set it, write a comment saying which case you are in. If you cannot name one, you are inflating your results.

## Resting orders: limits and stops

A market order has one fill price and the table above names it. An order with a price, such as `buy(limit = ...)`, `sell(stop = ...)` or `order.place()` with `type = "limit"`, `"stop"` or `"stopLimit"`, rests until the market reaches it, which may happen inside a bar the script never sees the inside of. A bar is only four prices, and nothing in it says whether the high came before the low, so the backtest decides those fills against the strategy rather than in its favour:

| Order | Fills when | Fills at | Slippage |
|---|---|---|---|
| Limit | The bar trades **through** the price. A bar whose high only touches a sell limit, or whose low only touches a buy limit, does not fill it | The limit price, or the bar's open when the bar opens already beyond the limit, which is the better price | None: a limit fills at its own price or better, never worse |
| Stop | The bar reaches the trigger | The trigger, made worse by the declared slippage | Yes |
| Stop, on a gap | The bar **opens** beyond the trigger | That open, made worse by the declared slippage | Yes |
| Stop limit | The bar reaches the trigger **and** trades through the limit | As a limit | None |

Being touched is not the same as being traded through. A limit resting at the exact extreme of a bar is the order that most often does not fill in practice, because other orders at that price were in the queue first. A stop does not fill at its trigger when the market gaps past it: it fills at the first price actually available, which on the day it matters most is a long way from the trigger. And a stop limit whose trigger is reached but whose limit is not traded through keeps resting as a plain limit, so it can fail to fill at all. That is what the order is, not a defect: the position it was meant to close stays open.

An order that fills inside a bar is only known to have filled once that bar is complete, so the trade list and the chart record it on the next bar, at the price it filled at.

## Stops and targets set as levels

`exit()` and `order.bracket()` attach a stop and a target to a position as price levels.

> **Levels in release 0.5.0**
The compiler accepts `exit()` and `order.bracket()`, but the 0.5.0 backtest does not fill the levels they set: the position simply stays open past them. The OpenAlgo strategy runner refuses to start a script that calls either one. Until they land, write a stop or a target as a rule the script tests on each close, as the example further down does. [Exits and brackets](/script/strategies/exits-and-brackets) covers both forms.

When level fills arrive, they are planned to follow the same conservative rules as the resting orders above: a level fills at its own price, or at the open when the bar opens beyond it; the stop pays slippage and the target does not; and when one bar's range contains both the stop and the target, the stop is taken, because nothing in a bar says which came first.

A rule-based stop is also the honest baseline for comparison. It exits at the next bar's open after the close that broke the level, which is the conservative reading. If a strategy's results depend heavily on whether its stop is a level or a rule, its returns are mostly a claim about fill quality, and fill quality is the thing you control least.

## Slippage

`slippage` is a number of ticks of adverse slippage applied to every market fill and every stop fill, and never to a limit. Adverse means it always works against you: a buy fills higher and a sell fills lower. With a tick of 0.05 and `slippage = 2`, a buy that would fill at a next open of 104.00 fills at 104.10, and the sell that closes it at an open of 109.00 fills at 108.90.

It is counted in ticks rather than in money or percent because a tick is the unit the instrument actually moves in. The tick size comes from the instrument: `chart.tickSize` in a script, and in the Backtest panel the tick size the platform holds for the symbol. When the platform has no tick size for an instrument, the panel runs with a tick of 0.05 and says so under the figures, because the slippage charged then rests on that assumption.

How to choose the number, in order of how much it matters:

| Ask | Then |
|---|---|
| How wide is the spread when you actually trade? | At least half the spread, in ticks, on each fill |
| How large is your order against the visible depth? | Add a tick for each extra price level you would have to take |
| When do your signals fire? | The first and last minutes of the 09:15 to 15:30 session are the widest, so a strategy that trades there pays more |
| How fast does the instrument move? | A fast instrument moves between your decision and your fill even with no spread |

Then run the only test that matters: turn it up. A declaration option can be wired to an `input()`, and the Backtest panel then offers it as a field you can change between runs without editing the script:

```openscript
version 1

strategy("Slippage sensitivity", overlay = true, precision = 2,
         capital = 500000, qty = 1,
         fillOn = "nextOpen",
         slippage = input(1, "Slippage, ticks per fill", min = 0, max = 20),
         commissionType = "percent", commission = 0.023)

fast = ema(close, 9)
slow = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy()
else if goFlat and pos.isLong
    close()

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
```

Run it at one tick, then at two, then at four. The rule of thumb this produces is worth more than any single number: **if the edge dies between one tick and two, it was never an edge.** It was the backtest reading a price nobody would have given you.

> **A cost wired to an input is resolved when the run starts, so the run uses the field's value. The **Declared by the script** list reads only numbers written directly in the declaration, so it shows `0` for an option wired to an input. The input field is the one that counts.**

## Commission

| `commissionType` | `commission` means | Charged on every fill as | Suits |
|---|---|---|---|
| `"perTrade"` | A flat amount | `commission` | A flat fee per order |
| `"perUnit"` | An amount per unit | `commission` times the units filled | A per share or per contract charge |
| `"percent"` | A percentage of traded value | `commission / 100` times price times units | Everything that scales with turnover |

Every one of the three is charged **per fill**, and a round trip is two fills. That settles the one place a reasonable reader can take the words two ways: `commissionType = "perTrade"` with `commission = 20` charges 20 on the entry and 20 on the exit, 40 per round trip. A commission of `0` charges nothing at all.

Each fill's charge is rounded once, to the paisa, and added to the trade that fill belongs to. A trade's **Net** in the trade list is its gross result less those charges, and a trade still open at the last bar has paid its entry charge already.

## The full cost stack

What a real market charges on a round trip is rarely one of the three spellings. It is a mixture of a flat fee, percentages, a tax on the other charges and a tax that applies to one side only. In the order it usually appears on an Indian contract note:

| Charge | Base | Side | Notes |
|---|---|---|---|
| Brokerage | Turnover, or a flat fee per order | Both | Often the smaller of a percentage and a cap |
| Exchange transaction charge | Turnover | Both | Set by the exchange, varies by segment |
| Clearing charge | Turnover | Both | Small, and easy to forget entirely |
| Regulator turnover fee | Turnover | Both | Small, same |
| Tax on services | The charges above, not the turnover | Both | A tax on a tax base, so it compounds the others |
| Securities transaction tax | Turnover, premium or settlement value, by segment | Often one side only | The largest single line for many intraday strategies |
| Stamp duty | Turnover | The buy side, in most segments | Varies by state and segment |

**The rates below are illustrative.** They have the right shape and they are not your rates. Take yours from your own contract note, which is the only document that knows your plan, your segment and your state.

A worked round trip: buy five lakh of an NSE stock intraday and sell it the same day, so turnover is 5,00,000 a side and 10,00,000 in total.

| Line | Rate used | Base | Amount |
|---|---|---|---|
| Brokerage | 0.03 percent, capped at 20 per order | 5,00,000 a side | 40.00 |
| Exchange transaction charge | 0.00325 percent | 10,00,000 | 32.50 |
| Regulator turnover fee | 0.0001 percent | 10,00,000 | 1.00 |
| Tax on services | 18 percent | 73.50 of charges | 13.23 |
| Securities transaction tax | 0.025 percent | 5,00,000, sell side only | 125.00 |
| Stamp duty | 0.003 percent | 5,00,000, buy side only | 15.00 |
| **Round trip total** | | | **226.73** |

Two readings of that total, and both are useful:

- **As a percentage of turnover:** 226.73 on 10,00,000 is about 0.0227 percent per fill. Rounded up, that is `commissionType = "percent", commission = 0.023`, the setting in the first example.
- **As money per round trip:** about 227, or about 113 per fill, which is `commissionType = "perTrade", commission = 113` when the order size is stable enough for a flat figure to be honest.

Write the source of the number beside it. A cost setting with no note of where it came from is a number nobody dares change, which means it will be wrong for years.

### What a single number cannot capture

**One-sided taxes.** A percentage applied to every fill charges the sell-side tax on the buy as well. Over a round trip the total comes out right; per trade it is smeared across both sides. That is fine for an equity curve and wrong for a question like "what does one extra entry cost me", so answer that question with the arithmetic above.

**Caps and tiers.** A brokerage plan that is the smaller of a percentage and a cap is not a percentage. Work out which side of the cap your typical order sits on, use that, and check again when your size changes. A strategy that grows into its cap gets quietly cheaper, and one that shrinks out of it gets quietly dearer.

## Refuse a trade that cannot pay for itself

The most valuable thing a cost model does is not correcting the equity curve after the fact. It is stopping the trade. A strategy that knows what a round trip costs can decline the trades whose expected move does not clear it, and that filter is often worth more than any change to the entry rule.

```openscript
version 1

strategy("Only trades that can pay for themselves", overlay = true, precision = 2,
         capital = 500000, qty = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "percent", commission = 0.023)

costPercent = input(0.046, "Round trip cost, percent of one side", min = 0, max = 2)
slipTicks   = input(2,     "Ticks given up per round trip", min = 0, max = 40)
edgeMult    = input(3.0,   "Target must be this many times the cost", min = 1, max = 20)
targetMult  = input(2.0,   "Target, in ATR", min = 0.2, max = 20)

atrValue = atr(14)
tick     = orElse(chart.tickSize, 0)

// The cost of a round trip expressed in price, so it can be compared with a
// move in price. Charges scale with the price; slippage does not.
costInPrice = close * costPercent / 100 + slipTicks * tick
target      = targetMult * atrValue

// An ordered comparison against an absent value is absent, and an absent
// condition takes the false branch, so this one test also covers warmup.
worthIt = target > edgeMult * costInPrice

fast   = ema(close, 9)
slow   = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

var targetLevel = none
var stopLevel   = none

if goLong and worthIt and pos.isFlat
    buy()
    targetLevel = close + target
    stopLevel   = close - atrValue

// The target and the stop as rules tested on each close. They fill at the next
// bar's open, which is the conservative reading, and they run the same way in a
// backtest and in a deployed strategy.
hitTarget = close >= targetLevel
hitStop   = close <= stopLevel

if pos.isLong and (goFlat or hitTarget or hitStop)
    close()
    targetLevel = none
    stopLevel   = none

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)

// The two quantities the filter compares, on their own pane, so a run that takes
// no trades explains itself at a glance.
plot(target, "Target", lime, overlay = false)
plot(edgeMult * costInPrice, "Cost hurdle", red, overlay = false)
```

The last two plots are the part to keep. When a costed strategy stops trading, the first question is whether the entry rule stopped firing or the cost filter started refusing, and two lines on a pane answer it without a single print statement.

## A costs panel

The Backtest panel has no figure for the total charged: the charges are inside every trade's **Net** and inside **Net profit**, never shown on their own. To see them, put an estimate on the chart. A script can count its own round trips from `pos.size`: a round trip ends on the bar the position returns to flat, and on that bar `pos.size[1]` and `pos.avgPrice[1]` still describe the position that just closed.

```openscript
version 1

strategy("Cost panel", overlay = true, precision = 2,
         capital = 500000, qty = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "percent", commission = 0.023)

costPercent = input(0.046, "Round trip cost, percent of one side", min = 0, max = 2)

fast = ema(close, 9)
slow = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy()
else if goFlat and pos.isLong
    close()

var roundTrips = 0
var costPaid   = 0.0

// The bar a position returns to flat. The value one bar back is the position
// that has just closed, with its size and its average price.
if not bar.isFirst and pos.size[1] != 0 and pos.size == 0
    roundTrips += 1
    costPaid += abs(pos.size[1]) * pos.avgPrice[1] * costPercent / 100

panel = table("Costs", 3, 2, position = "bottomRight", textColor = silver)

// Written only on the newest bar: a panel shows one state.
if bar.isLast
    cell(panel, 0, 0, "Round trips")
    cell(panel, 0, 1, text(roundTrips))
    cell(panel, 1, 0, "Estimated cost paid")
    cell(panel, 1, 1, text(costPaid, 0))
    cell(panel, 2, 0, "Average per round trip")
    cell(panel, 2, 1, roundTrips > 0 ? text(costPaid / roundTrips, 0) : "no round trips")

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
```

The estimate counts charges only. Slippage is already inside the fill prices, so it is in **Net profit** and not in this panel.

Set the estimate beside the report's **Net profit**. Costs as a share of what the idea earned before costs, that is costs divided by net profit plus costs, is the figure to watch. Below about a fifth, the strategy owns its returns. Above a half, you are running a business whose main customer is the cost stack, and the fix is fewer and larger trades rather than a better entry.

## What still differs from the real account

Even a fully costed backtest is a model. These are the gaps that remain, so you recognise them when the account underperforms the report:

| Gap | Why it exists | What to do |
|---|---|---|
| Queue position | A backtest does not know how many orders were ahead of yours at a price | Treat resting limit fills as optimistic |
| Partial fills | A backtest fills the whole order or none of it | Size below the visible depth |
| Market impact | Your own order moves the price, and the historical bars did not contain it | Trade smaller than you think you can |
| Rejections | Margin, product and permission refusals happen in a real account and never in history | Watch the order book of a deployed strategy, and size for the worst point |
| Carry and funding | Holding overnight costs money that a bar series does not show | Account for it outside the strategy, per position per night |
| Missed bars | A strategy that was not running took no trades | Compare trade lists, not just curves |

None of these is an argument against backtesting. They are the reason a backtest result is a hypothesis and [sandbox trading](/script/strategies/sandbox-and-live) is the test of it, which is why the same script runs in both without being rewritten.

## Pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| Wonderful equity curve, poor real results | Costs left at zero | Fill in the stack, then run again |
| The edge halves when slippage goes from one tick to two | The edge was fill quality | Trade a slower version of the idea |
| Entries sit exactly at the close that triggered them | `fillOn = "close"` | Leave the default unless you can name the honest case |
| A stop level the script set never exits the backtest | Levels set with `exit()` are not filled in 0.5.0 | Write the stop as a rule tested on each close |
| Cost per round trip looks twice what you expected | `"perTrade"` is charged on every fill, and a round trip is two | Halve the figure, or use the per fill amount |
| Your rates changed and the backtest still charges the old ones | The cost is a bare number with no note of where it came from | Wire it to an `input()` with a comment on its source |

**Related.** [Backtesting](/script/strategies/backtesting), [Reading a report](/script/strategies/reading-a-report), [Orders](/script/strategies/orders), [Exits and brackets](/script/strategies/exits-and-brackets), [Position and sizing](/script/strategies/position-and-sizing), [Declarations reference](/script/reference/declarations)


## Backtesting

Source: https://openalgo.in/script/strategies/backtesting

A backtest runs a strategy over the bars of the chart you are looking at, one bar at a time, oldest first, exactly as the chart runs a study, and reports what the strategy would have done. This page covers running one from the **Backtest** panel in the /trading page of OpenAlgo: what to pick, how far back to reach, how much history the first trade needs before it means anything, and which parts of a strategy the 0.5.0 backtest does not model yet.

There is no separate backtest mode in the language and no backtest-only function. The file you backtest is the file you later [deploy](/script/strategies/sandbox-and-live), and the only thing that differs between the two is where the orders go. In a backtest they go to a fill model over history, and nothing is sent anywhere.

## A strategy to backtest

A backtest needs a saved script that declares `strategy()`. A `study()` places no orders, so it has no trades, no equity curve and no report. This one is complete: save it from the Scripts panel, open an NSE stock or an index future on a 5 minute chart, and it runs.

```openscript
version 1

// Costs: one tick of slippage on every fill, and 0.023 percent of the traded
// value per fill, an illustrative intraday equity cost stack. Use your own.
strategy("EMA cross, costed", overlay = true, precision = 2,
         capital = 500000, qty = input(1, "Quantity", min = 1),
         fillOn = "nextOpen", slippage = 1,
         commissionType = "percent", commission = 0.023)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 2, max = 500)

fast = ema(close, fastLen)
slow = ema(close, slowLen)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy()
else if goFlat and pos.isLong
    close()

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)
```

Three choices in that declaration are worth copying into every strategy you test:

- **The costs are filled in before the first run.** Slippage and commission change every figure in the report, and a strategy you have already seen a clean equity curve for is a strategy you will argue with when the costs arrive. [Costs and fills](/script/strategies/costs-and-fills) covers choosing them.
- **The quantity is an input.** `qty = input(...)` hands the order size to whoever runs the script, so the Backtest panel offers it as a field. A script that writes a number, `qty = 5`, fixes the size and no panel can change it. A script that says nothing trades 1 unit.
- **The fill rule is the default.** `fillOn = "nextOpen"` fills a decision made at one bar's close at the next bar's open, which is the price you could actually have had.

## Running it from the Backtest panel

Open **Backtest** from the toolbar on the right-hand edge of /trading. It sits between **Scripts** and **Strategies**, in the order the work happens: write, test, then run.


| Part of the panel | What it does |
|---|---|
| Header | Shows the symbol and interval of the chart the run is of, or **No chart** |
| **Strategy** | Lists your saved strategies, and only strategies. With none saved it reads **No strategies saved** |
| **From** and **To** | The range of bars the run covers |
| **Run backtest** | Starts a run with the current dates and inputs. It reads **Running** while one is going |
| **Settings** | The script's inputs, and what its `strategy()` line declares. Opens itself when the script has inputs; **show** and **hide** fold it |

**The instrument and the interval are the chart's, not the panel's.** There is no symbol box. A run is always of the instrument and interval on the chart beside it, read at the moment the run starts, so to backtest something else, change the chart. A backtest of something other than what you are looking at is the one result you would misread.

**Three things start a run on their own:** choosing a strategy in the list, changing the chart's instrument, and changing the chart's interval. Each of those changes what a run is of, and a report left on screen would otherwise describe something the chart no longer shows. Nothing else starts one. Edits to the dates and to the inputs wait for **Run backtest**, so you can make several before you mean any of them.

You can also start from the editor. In the Scripts panel, the **Apply to chart** button (the play icon beside the script's name) adds its plots to the chart and hands it to the Backtest panel, which opens and runs it over the chart's history. For a strategy that is what applying it means: the chart draws and does not trade, so the trades come from the run.

When a run finishes, its fills are marked on the chart, and the figures, the equity curve and the trade list appear under the controls. [Reading a report](/script/strategies/reading-a-report) goes through every one of them.

A run that cannot go ahead says why, in a box under the controls. A script that does not compile lists its first five diagnostics with their line and code. A run refused before its first bar shows the code and the reason, for example OS6021 for a quantity stated in cash. A range with no bars, or with too many, says that instead.


## The date range

When the panel opens, the range ends today and reaches back by interval, because a range that is right for one interval is wrong for another:

| Chart interval | Default reach | Roughly how many bars |
|---|---|---|
| Minutes | Two months | About 40 sessions: 15,000 bars at 1 minute, 3,000 at 5 minutes |
| Seconds | Two months | At the finest second intervals that is more than the ceiling below, so shorten it |
| Hours | Two years | About 3,500 bars at 1 hour |
| Daily | Two years | About 500 bars |
| Weekly | Five years | About 260 bars |
| Monthly | Ten years | About 120 bars |

Once you type a date yourself, the range stops following the interval. A range you chose on purpose is not rewritten when you next change the timeframe.

A run covers at most **100,000 bars**. An NSE session from 09:15 to 15:30 is 375 minutes, so the ceiling is a little more than a year of 1 minute bars. A longer range is refused before anything runs, with a message saying how many bars it held: shorten the range, or use a larger interval.

| Interval | Bars in one NSE session | 100,000 bars is about |
|---|---|---|
| 1 minute | 375 | 266 sessions |
| 5 minutes | 75 | 1,300 sessions |
| 15 minutes | 25 | 4,000 sessions |
| 1 hour | 7 | 14,000 sessions |

### Choosing a range on purpose

The range decides what the result is a statement about. The calendar matters less than three other things.

**Count trades, not days.** A five year daily run of a strategy that trades twice a year is ten trades, and ten trades is an anecdote. Aim for a range that produces at least a hundred closed trades and treat anything under thirty as a sketch. If a hundred trades needs ten years of daily bars, test the idea on a finer interval or not at all.

**Cover more than one regime.** A range should contain at least one strong trend, one long sideways stretch and one fast fall, because those are the three shapes a strategy can be wrong in. A long-only run over a rising market tells you that the strategy was long during a rise.

**Hold something back.** Decide before you look at any result which part of the range you will not tune on. Developing on the first two thirds and checking on the last third is a common split, and any split chosen in advance is better than the best split chosen afterwards.

Two practical points. End the range on a bar that has closed, so no trade depends on a bar that was still moving. And keep the range fixed while you compare versions of a script: a change to the range and a change to the script in the same step means neither one is measured.

### Choosing the interval

The interval is the resolution of every decision in the file, and the backtest sees four prices per bar and nothing inside them.

| Interval | What one bar hides | Where the honesty risk is |
|---|---|---|
| 1 to 5 minutes | Seconds. Spread and queue position dominate | Costs: a small edge per trade is eaten by the spread |
| 15 minutes to 1 hour | The path of price inside the bar | Stops and targets that both sit inside one bar |
| Daily | The whole session | A gap through a stop, filled far from its trigger |
| Weekly and longer | Weeks | Too few trades to say anything |

**A coarser interval is not a slower version of a finer one.** A 15 minute script run on 1 hour bars is a different strategy with the same source: its averages span four times the time and its signal count falls. Compare two intervals as two strategies. To read a coarser interval from a finer chart, use `req.timeframe()`.

## Settings: inputs and what the script declares

The **Settings** section has two halves.

**Inputs.** Every `input()` the script declares is a field, labelled with the input's own label. A true or false input is a two-way list, an input with `options` is a list of those options, and a number field carries the input's `min`, `max` and `step`. A field left empty uses the script's own default, and so does a number outside the input's own `min` and `max`: the panel drops it rather than sending it. A change takes effect on the next run, so press **Run backtest** after editing. These values are for testing on this chart only: they never reach a strategy that is running on the server, whose inputs are set under **Strategies**. A script with no inputs says so and suggests the line that would make a number adjustable.

**Declared by the script.** The capital, order size, pyramiding, commission, slippage and fill rule from the `strategy()` line, shown so you know what the figures rest on. They are shown and not offered: to change one, edit the script.

Under the settings, a line headed **Order size** says where the size comes from:

| The script | The panel says |
|---|---|
| Wires `qty` to an input, as above | The size comes from a setting below, so you choose it |
| Writes a number other than 1, such as `qty = 5` | The script sets the size and it cannot be changed here |
| Says nothing about size, or writes `qty = 1` | It trades 1 unit, which is also the default, and the line `qty = input(1, "Quantity", min = 1)` would hand the choice to you |

State quantities in units. A strategy sized with `qtyType = "cash"` or `"equityPercent"` is refused by the 0.5.0 backtest before its first bar, and the OpenAlgo strategy runner sends only quantities stated in units. On an NFO future or option, a quantity in units is the number of units, so one lot of a contract whose lot size is 75 is `qty = 75`. [Position and sizing](/script/strategies/position-and-sizing) covers sizing from `chart.lotSize`.

### The instrument's own facts

The run reads the instrument's tick size and lot size from the platform's own record of the symbol. The tick size is what a tick of `slippage` is worth and what `chart.tickSize` answers; the lot size is what `chart.lotSize` answers and what converts a quantity stated in lots. The line under the figures states both, for example **Tick 0.05, lot 1**. When the platform holds no record for an instrument, the run uses a tick of 0.05 and a lot of 1 and the line says so, because a guessed tick size makes every slippage charge wrong without anything else looking wrong. Money is shown in rupees, to two decimal places, and each charge is rounded to the paisa.

## What runs where

| Where | What it does | Where orders go |
|---|---|---|
| Backtest panel | Runs the strategy over the chart's history, in your browser, on a background thread where the browser allows one, so the chart keeps drawing | A fill model over the bars. Nothing is sent to a broker |
| The chart | Draws the strategy's plots when it is applied or added from the indicators list, run against the same simulated fills the backtest uses, over the bars the chart has loaded | Nowhere: the chart draws and does not trade |
| Strategies panel | Runs a deployment as a process on the server, bar by bar as bars close | The platform's own order path: the sandbox in analyzer mode, your broker in live mode |

All three run the same compiled program, so the values a script computes from the same bars agree between them. What differs is where the orders go and, in release 0.5.0, which parts of a script each place supports. [Sandbox and live](/script/strategies/sandbox-and-live) covers the third row and lists what the server runner needs.

## Warmup: how much history the first trade needs

A function that needs `k` bars returns the absent value until `k` bars exist, and absence carries through arithmetic, through comparisons and into the branch that would have placed the order. An absent condition takes the false branch, so during warmup no order is placed. That protects you automatically. What it does not do is tell you when warmup ended, and you need that number to choose a range. [Warmup](/script/language/warmup) covers the rule in full.

Warmups add up along a chain. `sma(ema(close, 10), 10)` has no value until bar 18: the inner average is absent until bar 9, and the outer one needs ten present values after that.

Take the entry condition apart, write down each term's warmup, and take the largest:

| Term in the entry | First bar with a value |
|---|---|
| `ema(close, 200)` | bar 199 |
| `atr(14)` | bar 13 |
| `rsi(close, 14)` | bar 14 |
| `highest(high, 20)[1]` | bar 20 |
| The signal line of `macd(close, 12, 26, 9)` | bar 33 |

There are three kinds of warmup, and the reference gives only the first: each call's own length, the extra bars a `[n]` lookback adds, and state your file builds up in a `var` over time. The third is the one that gets missed, because it is in your file and nowhere else.

A file whose entry reads `ema(close, 200)` is warm at bar 199. On a 15 minute chart that is eight sessions in. A useful default: take the file's warmup in bars, add a fifth for the lookbacks you forgot, and round up to a whole session.

### Two ways to make the first trade honest

The Backtest panel trades every bar in the range, so the warmup bars are inside it. The first way is to reach back further than you want to trade and keep the strategy out of the market until your own window starts. Two time inputs make the window a setting:

```openscript
version 1

strategy("Traded window", overlay = true, precision = 2,
         capital = 500000, qty = 1)

// Dates, not times of day: the Backtest panel gives the run no time zone, so a
// written time of day would be read as UTC rather than as Indian time.
tradeFrom = input("2025-01-01", "Trade from",      kind = "time")
tradeTo   = input("2026-01-01", "Stop trading on", kind = "time")

fast = ema(close, 20)
slow = ema(close, 200)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

// Bars before tradeFrom are loaded and computed, they are simply not traded.
// With From set far enough back, the 200 bar average is already warm on the
// first bar inside the window, which makes its first trade comparable with its
// last.
inWindow = time >= tradeFrom and time < tradeTo

if inWindow and goLong and pos.isFlat
    buy()

if pos.isLong and (goFlat or not inWindow)
    close()

plot(slow, "Slow", orange, width = 2)
background(inWindow ? none : fade(silver, 92))
```

Set **From** a few weeks before **Trade from** and **To** on or after **Stop trading on**, and the grey background shows the bars that were computed and not traded. Time inputs are for backtesting only: the strategy runner refuses to start a script that declares one, so take the window out before you [deploy](/script/strategies/sandbox-and-live).

The second way is to state the guard in the script, which is worth doing anyway because the chart then shows the bar the strategy became honest on rather than leaving you to infer it from the first marker:

```openscript
version 1

strategy("Guarded entry", overlay = true, precision = 2,
         capital = 500000, qty = 1)

trend    = ema(close, 200)
breakout = highest(high, 20)[1]
strength = rsi(close, 14)

// Every value the entry reads, tested once. Absence would already have skipped
// the entry, because an absent condition takes the false branch. The guard
// exists so the chart can show where the strategy became warm.
warm = not isNone(trend) and not isNone(breakout) and not isNone(strength)

if warm and pos.isFlat and close > breakout and strength > 55 and close > trend
    buy()

if pos.isLong and close < trend
    close()

plot(trend, "Trend", aqua, width = 2)
plot(breakout, "Breakout level", orange, style = "step")
background(warm ? none : fade(silver, 92))
```

## What the 0.5.0 backtest does not model yet

Some parts of a strategy compile and are not acted on by the backtest in this release. Most of them fail quietly, with a report that looks normal, so know them before you read one. Two are refused before the run starts, with the reason shown in the panel:

| In the script | In a 0.5.0 backtest | What to do |
|---|---|---|
| A stop or target set with `exit()` or `order.bracket()` | Not filled | Write the stop as a rule tested on each close. [Costs and fills](/script/strategies/costs-and-fills) shows one |
| `closeOnSessionEnd = true` | Not acted on: a position is carried past the close | Close it in the script as well |
| A calendar read with no time zone, such as `date.hour(time)` or `session.isIn("0915-1530")` | Absent on every bar, because the panel does not state the chart's time zone to the run, so a condition built on it is never true | Pass the zone: `date.hour(time, "Asia/Kolkata")`, `session.isIn("0915-1530", "Asia/Kolkata")` |
| `session.isFirstBar`, `session.isLastBar` | No value, so never true: the run is given no session boundaries | Anchor on a time window you write with an explicit zone |
| A day, week or month read, such as `req.timeframe("1D", close)` | Absent on every bar, because the run has no time zone to group days in | Filter on an intraday read such as `"1h"`, or test the strategy drawn on the chart |
| `chart.interval`, `chart.intervalMinutes`, `chart.isIntraday` | Absent: the panel does not state the chart's interval to the run | Take a length in bars as an input |
| Another instrument, read with `req.symbol()` | The run is refused before it starts | Backtest on the instrument itself; an intraday `req.timeframe()` read of the chart's own instrument works |
| `qtyType = "lots"` | Entries are converted from lots to units, but an exit that sizes itself, such as `close()`, is not: it sends the position's unit count as a number of lots, sells many times what is held and opens a large position the other way | Use `qtyType = "units"` |
| `qtyType = "cash"` or `"equityPercent"` | The run is refused before it starts | Use `qtyType = "units"` |

One more behaviour is worth knowing. An order the strategy is not allowed to place, such as a second entry while one is open and `pyramiding` is `1`, is an error that stops the script at that bar (OS7008). Nothing after that bar is placed or filled: the trade list ends there, and the equity curve carries whatever position was open, marked to every later close, to the end of the range. The Backtest panel does not show the error, so a run that shows far fewer trades than the chart suggests, or ends on one long open trade, is worth checking for this first. Guarding every entry with `pos.isFlat`, or with the side you mean to add to, keeps a strategy from reaching one. [Orders](/script/strategies/orders) lists the refusals.

## Reproducing a run

The panel keeps the latest run on screen and stores nothing. To reproduce a result later you need what it was a run of:

| Fact | Why it changes the answer |
|---|---|
| The script, as saved | The exact rules that ran |
| Symbol and exchange | Which prices |
| Interval | The resolution of every decision |
| From and To | Which bars |
| Every input you changed | The parameters the rules ran with |
| Tick size and lot size, from the line under the figures | Every money figure |

Restore those, run again, and compare the trade list rather than the summary. Two runs with the same net profit and different trade lists are not the same run.

The language makes a rerun a check rather than a new experiment. It has no random number function, and the same compiled program over the same bars, with the same inputs and the same instrument facts, produces the same numbers every time. If two runs that match on every row of the table above disagree, the bars changed: an adjusted history or a revised bar moves a result, and none of it is in the script.

## What a run cannot tell you

A run tells you what a fixed set of rules did over a fixed set of bars. It cannot tell you whether the rules will keep working, whether you chose them because they fitted those bars, or whether you would have held the position through the drawdown in the middle. Reading the report well is a separate skill, and it is the next page.

## Mistakes that produce a beautiful, wrong result

| Mistake | What it looks like | Fix |
|---|---|---|
| Trading from the first bar of the range | The first trades fire on half-warm values | Load warmup bars before the traded window |
| `fillOn = "close"` | Every entry at the price that triggered it | Leave the default |
| Zero costs | A dense intraday script prints money | Set slippage and commission before reading anything |
| A stop set with `exit()` | Losses run far past the stop level the script set | Write the stop as a rule in 0.5.0 |
| One regime | A long-only strategy over a rising market | Extend or move the range |
| Tuned on the whole range | Every parameter at a local peak | Hold a section back before tuning |
| Too few trades | A 22 trade run with a 68 percent win rate | Longer range, finer interval, or drop the idea |

**Related.** [Costs and fills](/script/strategies/costs-and-fills), [Reading a report](/script/strategies/reading-a-report), [Reading the books](/script/strategies/reading-the-books), [Sandbox and live](/script/strategies/sandbox-and-live), [Warmup](/script/language/warmup), [Your first strategy](/script/getting-started/first-strategy)


## Reading a report

Source: https://openalgo.in/script/strategies/reading-a-report

A backtest report is a trade list and an equity curve, with a handful of figures derived from them. This page goes through everything the Backtest panel in /trading shows after a run: what each figure measures, which ones flatter a strategy while saying nothing, and how to decide whether the difference between two runs is an improvement or luck.

Read in this order: the curve, then the trades, then the summary figures, and never the other way round. The figures are what a strategy says about itself. The curve and the trade list are what it did.

## A strategy to read

Any strategy produces a report. This one also draws its open trade's result in a pane of its own, marked to each close the same way the report's equity curve marks it, so the chart shows the path of every trade that the summary compresses into one number:

```openscript
version 1

strategy("EMA cross, with its open result", overlay = true, precision = 2,
         capital = 500000, qty = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "percent", commission = 0.023)

fast = ema(close, 9)
slow = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy()
else if goFlat and pos.isLong
    close()

// The open trade's result in money, before charges, marked to this bar's close
// the way the equity curve marks it. Absent while flat, so the pane shows a gap
// between trades rather than a zero.
openResult = pos.isFlat ? none : (close - pos.avgPrice) * pos.size

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
plot(openResult, "Open result", silver, style = "histogram", overlay = false)
```

Run it from the Backtest panel on a 5 minute chart of an NSE stock. The report appears under the panel's controls.


## Every figure in the panel

The eight tiles are all folded from the run's own fills: the orders this strategy placed and what they filled at. Nothing in them comes from your account.

| Tile | What it measures | Counted over |
|---|---|---|
| **Net profit** | The sum of every closed trade's result after its charges. Green when it is zero or above, red below | Closed trades |
| **Return** | Net profit as a percentage of the `capital` the declaration states, 100,000 when it states none | Closed trades |
| **Trades** | How many trades closed | Closed trades |
| **Win rate** | Winning trades divided by winning plus losing trades. A trade whose net is exactly zero counts as neither. Shows `-` when nothing has closed | Closed trades |
| **Profit factor** | The winning trades' gross profit divided by the losing trades' gross loss, both taken before charges. Shows `-` when no trade lost, or when the losing trades lost nothing before charges | Closed trades |
| **Expectancy** | Net profit divided by the number of closed trades: the average result of one trade, in money. `0.00` when nothing has closed | Closed trades |
| **Max drawdown** | The deepest fall of equity below its own running peak, in money, shown as a negative number | The equity curve, every bar |
| **Max run-up** | The largest rise of equity above its own running low, in money | The equity curve, every bar |

A trade wins or loses on its net result after charges. A trade whose gross move was positive and whose charges took it under counts as a loss, so on a strategy with thin trades the win rate falls as soon as costs are filled in, which is the point of filling them in. **Gross** in this page means before charges and **net** means after them.

Under the equity chart, one line states what the figures rest on: how many fills were marked on the chart, how many bars the run covered and how long it took, and the tick size and lot size of the instrument, for example **Tick 0.05, lot 1**. When the platform holds no tick or lot size for the instrument, the line says the run assumed a tick of 0.05 and a lot of 1, and that every figure in money rests on those.

### Open trades at the last bar

A trade still open when the run reaches its last bar is not closed by the report. The panel says how many there were, and the trade list shows **open** in its Exit column. Net profit and every tile derived from it leave the open trade out, because its profit has not been realised. The equity curve includes it: its entry charge was paid on the bar it opened, and its open result is marked to every close.

When the run ends holding a position, a box headed **Position now** shows it: the side and size, the entry price, and its result marked to the latest price of the instrument, with a tag saying whether that price is still arriving (**Live**), is the last one received (**Last known**), or has not arrived yet (**No price yet**). It is a position nobody holds. The chart draws and does not trade, so nothing is held at your broker because of it. To trade the strategy, deploy it under **Strategies**, as [Sandbox and live](/script/strategies/sandbox-and-live) describes.

## The equity curve and the drawdown

Under the tiles, a chart draws two panes on one time axis. The top pane is equity. The bottom pane is drawdown, drawn as an area below zero, so looking straight down from a peak finds the trough under it.

**Equity is the declared capital, plus the gross result of every closed trade, less every charge paid, plus the open trade's result marked to this bar's close.** Two details decide its shape:

- **All of a trade's charges land on the bar it opened**, the exit charge included, and its gross result lands on the bar it closed. While it is open, its result is in the curve as open profit, marked to each close. So the curve steps down by the whole round trip's cost the moment a trade opens.
- **It is marked bar by bar, not trade by trade.** A position that is 40,000 down in the middle of a week that ends flat shows that fall, because the curve is marked at every close. A curve drawn only from closed trades would not show it at all.

It is also marked to the close, not to the price you could have got out at. On an instrument with a wide spread that understates every drawdown by roughly half a spread per unit held.

The curve is this strategy's alone. It starts at the declared `capital`, counts only this strategy's fills, and knows nothing about your other strategies or the margin your account is carrying.

What to look for before reading any figure:

| What you see | What it usually means |
|---|---|
| One steep section carrying the whole run | The result is one period, not a strategy |
| Steps of equal height | A fixed size and a fixed target. Check the size was realistic |
| A long flat stretch | The rules stopped firing. Find out which regime that was |
| Smooth to the point of unreality | Look for `fillOn = "close"` or a higher timeframe read that looks ahead |
| A curve that only rises when the market rises | The strategy is long exposure with extra steps |

### How drawdown is measured, and why it matters

A drawdown is the fall from the highest equity the run has reached so far to the lowest point after it, before a new high. The panel's **Max drawdown** is the deepest such fall, measured on every bar, including open positions, in money.

That definition has two choices in it, and a report that does not say which it made cannot be compared with another:

| Choice | This panel | The other way | Effect |
|---|---|---|---|
| What is marked | Every bar, open positions included | Closed trades only | Closed-trade drawdown is never larger, and often much smaller |
| How it is stated | Money | Percent of the preceding peak | Percent shrinks late drawdowns on a growing account |

A worked illustration of how much the first choice matters. Three trades, starting equity 1,00,000:

| Trade | Worst point while open | Closed at |
|---|---|---|
| 1 | -18,000 | +6,000 |
| 2 | -4,000 | -3,000 |
| 3 | -22,000 | +14,000 |

Measured on closed trades only, the worst fall is 3,000: from 1,06,000 after the first trade to 1,03,000 after the second. Measured bar by bar, it is at least 25,000: from that 1,06,000 peak down to 81,000 at the worst point of the third trade. Same trades, same net profit of 17,000, and two numbers that lead to two different decisions about position size. The bar-by-bar figure is the honest one, because it is the number you would have been looking at while it was happening, and that is the number that decides whether a strategy gets switched off.

To read it as a percentage, divide it by the peak equity just before it, which is the top pane's height at the start of the deepest valley in the bottom pane.

### Time under water

Depth is half of a drawdown. The other half is how long it lasted. A 12 percent drawdown that recovers in nine days and one that takes seven months are the same number and very different experiences. Read the widest valley in the drawdown pane, from where it leaves zero to where it returns, alongside the deepest one.

## Win rate against expectancy

Win rate on its own is close to meaningless, because it says nothing about size. The figure that means something is expectancy, the average result of a trade:

```text
expectancy = winRate * averageWin - (1 - winRate) * averageLoss
```

where `averageLoss` is a positive number. The panel computes it as net profit over closed trades, which is the same figure whenever no trade scratched at exactly zero. Three strategies with the same expectancy of 400 a trade:

| Strategy | Win rate | Average win | Average loss | Expectancy | Longest losing run to expect |
|---|---|---|---|---|---|
| A | 75% | 1,200 | 2,000 | 400 | Short |
| B | 50% | 2,400 | 1,600 | 400 | Moderate |
| C | 25% | 6,400 | 1,600 | 400 | Long |

All three make the same money per trade over a large enough sample, and they are not interchangeable. C spends most of its life losing, so it needs a size small enough that ten losses in a row is an inconvenience, and a trader who will still place the eleventh. A has the opposite problem: its losses are larger than its wins, so one run of bad luck undoes many wins.

Two derived numbers are worth having beside it:

- **Profit factor** is on the panel. Above 1 means the winners' gross beat the losers' gross. It is taken before charges, so a run can show a profit factor above 1 and still lose money once costs are paid: read it beside **Net profit**. It is a useful shape check and fragile on small samples, because one large win moves it a long way. On fewer than fifty trades, treat it as a description rather than a measurement.
- **Payoff ratio**, average win over average loss, is not on the panel. Work it out from the trade list. It tells you which of the three shapes above you are holding, and so what a normal bad week looks like.

## The trade list

Under the chart, **Trades** lists every trade the run made, one row per trade, in the order they opened.


| Column | Holds |
|---|---|
| **Side** | `long` or `short` |
| **Entry** | The average price the trade was entered at, after slippage |
| **Exit** | The average price it was closed at, or **open** for a trade still held at the last bar |
| **Net** | The trade's result after its charges, green at zero or above and red below |

A trade is one position from flat back to flat. A strategy that adds to a position before closing it has one row for the whole position, entered at the average of its entries. [Reading the books](/script/strategies/reading-the-books) shows how the list is built from the orders underneath it.

The summary figures are all averages, and an average describes a set of trades badly, because a minority of trades usually carries the result. Five tests, in order of how often they change someone's mind:

1. **Remove the best five trades.** If the run is still profitable, the result does not rest on a handful of trades. If not, it is five lucky trades with a long tail of noise attached. Do the same with the worst five to see how concentrated the risk is.
2. **Compare the median trade with the mean.** A median far below the mean says a few large wins are doing the work.
3. **Look at the largest win as a share of net profit.** Above about a third, the result is one trade.
4. **Count the longest run of losses.** Then ask whether you would have kept the strategy running through it. A strategy you would have switched off has an expectancy of zero, whatever the report says.
5. **Split the trades by anything that is not the rules.** Long against short, by weekday, by time of day, by month. If the whole edge lives in one bucket, you have a filter you have not written down.

## The marks on the chart

Every fill of the run is marked on the price, on the bar the run records it on, from the same list of fills the trade list is built from. The marks of a long trade sit below the bars and the marks of a short trade sit above them, so the two ends of a round trip are on the same side of the price. The colour is the order's side: green for a buy, red for a sell.

| Label | Mark | What the fill did |
|---|---|---|
| **Long** `+1` | Green arrow up, below the bar | Opened a long |
| **Exit long** `-1` | Red arrow up, below the bar | Closed a long |
| **Short** `-1` | Red arrow down, above the bar | Opened a short |
| **Exit short** `+1` | Green arrow down, above the bar | Closed a short |

The signed number is the size of the fill in units, plus for a buy and minus for a sell. A reversal is two fills on one bar, an exit and an entry, so it draws two marks there. A new run replaces the previous run's marks rather than adding to them, and a run that made no trades clears them, so what is on the chart is always the latest run. If the chart has not finished loading its bars, the panel says it had no price series to mark.

## Numbers that flatter a strategy while meaning nothing

| Figure | What it hides | Read it next to |
|---|---|---|
| **Return** over a short range | That a lucky quarter is still a quarter | **Trades** and the length of the range |
| **Win rate** | Trade size | **Expectancy** and the payoff ratio |
| **Profit factor** on 30 trades | Sampling noise | The result with the best five trades removed |
| **Net profit** | Position size, which you chose | Net profit against the capital actually at risk |
| **Expectancy** | The distribution | The median trade and the largest win's share |
| The largest winning trade | Nothing. It is a lottery result | Whether the run survives without it |
| Percent of profitable months | Size again: eleven small wins and one ruinous loss is 92 percent | The worst month |
| A result since a start date you chose | That the start date was chosen after seeing the data | The same run started a year earlier and a year later |

One comparison deserves its own paragraph. Run the strategy once with slippage and commission at zero and read **Expectancy**: that is the average trade before costs. **If it is smaller than what one round trip costs**, the strategy is not marginal, it does not exist. An average trade of 180 before costs against a round trip that costs 200 means every improvement you find will be inside the cost model. Check this first, because it saves weeks. [Costs and fills](/script/strategies/costs-and-fills) shows how to work out the cost of a round trip.

## Telling a real improvement from noise

This is the part of comparing two runs that people get wrong.

### Change one thing

Both runs use the same symbol, exchange, interval, range, inputs and cost settings, except the single thing under test. If you changed a parameter and extended the range in the same step, you have measured nothing, and the only fix is to run it again. The Backtest panel keeps only the latest run on screen, so write down the figures and the trade list of the first before you run the second.

### The unit of evidence is a trade, not a day

A run that covers four years is not four years of evidence. It is however many trades it took. Sixty trades is sixty observations, whether they arrived over a month or a decade.

### The noise band, with arithmetic

Take the Net column of the trade list, leaving out any trade marked **open**, compute its mean and its standard deviation, and divide the deviation by the square root of the number of trades. That is the standard error of the mean trade: roughly how far the measured average sits from the true one by luck alone.

A concrete case. Run A: 120 trades, mean trade 420, standard deviation 3,800.

```text
standard error = 3800 / sqrt(120) = 347
```

So run A's true mean trade lies somewhere around 420 plus or minus about 700 at two standard errors: roughly from -280 to 1,120. Run B comes back with a mean trade of 700 over a similar number of trades. The difference is 280, smaller than the error on either run taken alone. There is no evidence here. Run B is not better, it is differently lucky.

How large does a difference have to be? Compare it with the standard error of the difference, which for two independent runs of similar spread is about 1.4 times one run's standard error, close to 500 here. Anything under about 1,000 per trade is inside the noise at two standard errors. With a noisy strategy and a hundred trades, only a very large improvement is detectable at all, and the way to make a smaller one measurable is more trades, not more confidence.

### When the two runs overlap, compare the trades that differ

Most changes do not alter every trade. If 92 of 100 trades are identical in both runs, the comparison is really about eight trades, and the whole-sample standard error is far too generous a test. Pair the runs instead: list the trades that differ, take the difference in result for each, and ask whether that set of differences has a mean away from zero. Everything the two runs share cancels out, which makes a paired comparison much more sensitive.

A change that alters no trade is not an improvement. It is a preference.

### Four checks that are not arithmetic

- **Is the improvement a plateau or a spike?** Run the neighbouring parameter values too. A length of 21 that beats 20 and 22 by a wide margin is a fit to this history. A length of 21 on a broad region that all works about equally well is a finding.
- **Does it survive the section you held back?** An improvement that appears on the tuning section and vanishes on the held-back one describes the tuning section.
- **Does it survive double the slippage?** If it disappears, what you improved was the cost assumption.
- **Was it the twentieth thing you tried?** Twenty tests at a one in twenty threshold produce one impressive result from pure noise, on average. Count your tests honestly and raise the bar as the count rises.

### Symptoms and their usual causes

| Symptom | Likely cause | Test |
|---|---|---|
| A large gain from a tiny parameter change | Fitting to one period | Run the neighbours |
| Improvement in one year only | Regime, not edge | Split the range by year |
| Win rate up, net profit down | The change cut winners short | Compare payoff ratios |
| More trades and a better average | Usually a cost or fill assumption | Double the slippage |
| Better on the tuning section only | Overfitting | Run the held-back section |
| Both runs identical except two trades | Nothing was measured | Pair the differing trades |

**Related.** [Backtesting](/script/strategies/backtesting), [Costs and fills](/script/strategies/costs-and-fills), [Reading the books](/script/strategies/reading-the-books), [Sandbox and live](/script/strategies/sandbox-and-live), [Exits and brackets](/script/strategies/exits-and-brackets)


## Reading the books

Source: https://openalgo.in/script/strategies/reading-the-books

Every strategy keeps its own books: a record of each order it placed, what each order filled at, and the position those fills add up to. This page covers where those books show up in /trading, what the ledger underneath them holds, why the same fill can be reported twice without being counted twice, and what a script can read about its own orders and position today.

The rule behind all of it is short. **A strategy's position and profit are folded from its own settled fills, and from nothing else.** No call reads your account's position, and no order is ever computed as a difference against it.

## A script that reads its own books

A strategy can put what it believes about its own position on the chart. This panel uses only calls that run in release 0.5.0:

```openscript
version 1

strategy("Books panel", overlay = true, precision = 2,
         capital = 500000, qty = 1)

fast = ema(close, 9)
slow = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy(tag = "entry")
else if goFlat and pos.isLong
    close(tag = "entry")

// One place that decides what an absent reading looks like. A blank cell hides
// that there is nothing, and a zero invents a number.
fn show(value, decimals) => isNone(value) ? "none" : text(value, decimals)

side     = pos.isLong ? "long" : (pos.isShort ? "short" : "flat")
openMove = pos.isFlat ? none : (close - pos.avgPrice) * pos.size

// A fact the ledger will answer once order reads land, kept by the script
// meanwhile: how many bars the position changed on.
var fills = 0
if not bar.isFirst and pos.size != pos.size[1]
    fills += 1

panel = table("Books", 5, 2, position = "topRight", textColor = silver)

// Written only on the newest bar. A panel shows one state.
if bar.isLast
    cell(panel, 0, 0, "Side")
    cell(panel, 0, 1, side)
    cell(panel, 1, 0, "Position, units")
    cell(panel, 1, 1, text(pos.size))
    cell(panel, 2, 0, "Average price")
    cell(panel, 2, 1, show(pos.avgPrice, 2))
    cell(panel, 3, 0, "Open result")
    cell(panel, 3, 1, show(openMove, 0))
    cell(panel, 4, 0, "Bars the position changed on")
    cell(panel, 4, 1, text(fills))

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
plot(pos.isFlat ? none : pos.avgPrice, "Average price", silver, style = "step")
```

`pos.size` is the net position in units, positive long and negative short, and `0` when flat. `pos.avgPrice` is the average price of the open position and absent while flat, which is why the panel passes it through `show()` rather than straight to `text()`. Both move only when a fill settles, never when an order is merely sent.

## Where the books show up

| Where | What you see | Built from |
|---|---|---|
| Backtest panel | The trade list, and the marks on the chart | The run's own ledger of orders and fills |
| Strategies panel, **Activity** | **Orders**, **Trades** and **Positions** tabs | The platform's own order book, trade book and position book, narrowed to this deployment's orders |
| Inside the script | `pos.size`, `pos.avgPrice`, `pos.isLong`, `pos.isShort`, `pos.isFlat` | The strategy's own settled fills |

The same idea runs through all three. A strategy is judged on what it did, which is what filled, and not on what it asked for or on what the account happens to hold.

## Every strategy keeps its own books

An account holds one position per contract, and that position can have several owners: a strategy, a second strategy on the same contract, and a trade you placed by hand. A report built from the account's position would be a report about somebody else's trades as much as your own. So each strategy keeps its own ledger, and everything the language says about a position is a sum over that ledger.

Two consequences follow, and both are deliberate:

- **Two strategies on one contract each see their own position.** If one goes long two lots while another goes short two lots, the account is flat and both strategies correctly believe they have a trade on. Each order states its own side and quantity outright, so the two trade their own plans and the account nets them. Neither ever sends an order to "correct" itself towards the account, which is how two strategies would otherwise spend a session undoing each other.
- **Nothing in a script reads the account's quantity.** A script that could would be computing against whoever else is trading that contract. The planned `pos.isShared` will report, as a yes or no, whether the account's position in a contract is shared with something other than this strategy, and that is the only fact about the account the language intends to give.

## The ledger

The ledger holds one row per order placed. A row is added when the order is sent, and after that it changes only when the destination (the sandbox, your broker, or the backtest's fill model) sends a report about that order. Nothing is ever filled in by guesswork, so the sequence that produced a position can be replayed and audited rather than inferred. You do not see the ledger directly: the trade list, the chart marks and the `pos.*` values are all built from it.

| Field | Holds |
|---|---|
| `intentId` | The engine's own key for the order, unique within the run |
| `orderRef` | The destination's own order id, exactly as given, `""` until the destination answers |
| `tag` | The tag the script placed the order with, `""` when it named none |
| `leg` | The leg the order belongs to, `""` for a strategy that trades one instrument. [Legs and books](/script/strategies/multi-leg-and-books) covers legs |
| `positionRef` | The position this order settles against, below |
| `instrument` | The contract actually sent: its symbol and exchange |
| `product` | The product actually sent, which may be a different word from the one the script declared |
| `side`, `qty`, `type`, `price`, `trigger` | The order as it left the engine |
| `status` | The folded status, below |
| `filledQty` | The cumulative filled quantity, never a change |
| `avgFillPrice` | The destination's average price over `filledQty`, absent while nothing has filled |
| `rejection` | The destination's own rejection text, `""` when there is none |
| `placedAt`, `updatedAt` | When the order was sent, and when a report last changed the row |

The product and the contract are recorded as sent rather than as declared, because a position reconciled against a word nobody sent is reconciled against something nobody traded.

### Statuses

| Status | Means | Ends the order |
|---|---|---|
| `placed` | Sent, and the destination has not answered yet | No |
| `working` | Accepted by the destination and not completely filled | No |
| `triggerPending` | Accepted and waiting for its trigger price | No |
| `filled` | The whole quantity is filled | Yes |
| `cancelled` | Ended by a cancellation | Yes |
| `rejected` | Refused, carrying the destination's own text | Yes |
| `expired` | Ended without filling, by the destination's own rule | Yes |

A status only moves forward, from `placed` through `working` or `triggerPending` to one of the four that end an order, and an ended order never changes status again. `placed` is the engine's own word; every other status comes from the destination.

### Position references, and why a reversal is two orders

Every order carries a position reference. One is created when the strategy goes from flat to holding, and it ends when that position returns to zero through settled fills. A fill always settles the position its own order names, never whichever position is current, so a fill that arrives late cannot be applied to the position that replaced the one it belonged to.

That is why no order crosses zero. `order.reverse()` on a long position is two orders: one that closes the long and one that opens the short, each with its own reference. In a backtest the chart shows both on the same bar, an **Exit long** mark and a **Short** mark. The OpenAlgo strategy runner still sends a reversal to the platform as the single net order it adds up to, and splits the fill back across the two positions afterwards.

An instruction that orders nothing adds no row: a stop or target set with `exit()` or `order.bracket()` and a cancellation with `cancel()` name a position or an order without becoming one.

## A fill can be reported twice without being counted twice

A destination's report about an order is cumulative: it restates the order's whole life so far, not what changed since the last report. Reports repeat, cross in flight and arrive late. A connection that reconnects resends its last reports, and a destination unsure whether you heard it says it again. None of that is a fault.

An engine that added up each report's quantity would double a fill and report a position the strategy never held. The ledger folds a report instead:

1. **The filled quantity is the larger** of what the row holds and what the report says, so a repeated or stale report changes nothing.
2. **The average price is the destination's**, taken only when the filled quantity grew. The engine never averages two averages of its own.
3. **The status moves forward or not at all.** A report about an order that has already ended cannot change its status, but a fill it carries still counts: when a cancellation and a fill cross in flight, the shares really traded, and a ledger that dropped them would hide a position the strategy holds.

Three practical consequences, in the order they reach a strategy:

- **The position moves by what newly settled, once.** `pos.size` changes by exactly the quantity a report adds, however many times that report arrives, so reading the position is always safe.
- **A partial fill is a state, not an event.** The backtest fills every order whole, but a real destination can leave an order at `working` with part of its quantity filled for as long as it takes. Guard on the position rather than on the idea that an order is either untouched or done.
- **Never count anything by counting reports.** When the planned order reads arrive, `order.filled()` will return the running total, not a change: to know what filled on one bar, take the difference from the previous bar.

## From the ledger to the trade list

The Backtest panel does not show ledger rows. It shows the trades built from them. A **trade** is one position from flat back to flat:

- Its **Entry** is the average price over every fill that built the position, and its **Exit** the average over every fill that closed it.
- A strategy that adds to a position before closing it, with `pyramiding` above `1`, has one trade for the whole position, with the combined size at the average entry.
- Its charges are the charges of every fill that belongs to it, and its **Net** is its gross result less those charges.
- A position still open at the last bar is a trade marked **open**, with no exit.

Each fill is also one mark on the chart, on the bar the run records it on, so a round trip is an entry mark and an exit mark. [Reading a report](/script/strategies/reading-a-report) covers both.

> **Changing size inside a trade, and the equity curve**
A trade holds one entry price and one size for the whole position, so the equity curve values it at its final size and average entry from the bar it first opened. A strategy that adds to a position is therefore shown, on the bars before the later entries, holding units it did not hold yet, and its drawdown reads worse than it was. A strategy that closes part of a position is shown still holding the whole of it until the trade ends. Net profit and the trade list are not affected, because they are folded from the fills.

## Reading the books from a script

### What runs in release 0.5.0

| Call | When flat | Means |
|---|---|---|
| `pos.size` | `0` | Net position in units, positive long and negative short |
| `pos.isLong`, `pos.isShort`, `pos.isFlat` | flat is `true` | The sign of `pos.size`, spelled out |
| `pos.avgPrice` | absent | The average price of the open position |

These are enough for the guards every strategy needs, and for any fact you can derive yourself. The panel above works out the open result from the average price, and counts position changes by comparing `pos.size` with its value on the previous bar.

### What is planned

The rest of the ledger's reads are planned. A planned call is refused where you write it, with OS2020, so a script cannot compile around one by accident:

```openscript
version 1
strategy("Planned read", overlay = true)

if crossUp(ema(close, 9), ema(close, 21))
    buy(tag = "entry")

plot(order.filled("entry"), "Filled so far")
```

| Planned call | Will read |
|---|---|
| `order.status()` | An order's folded status, from the table above |
| `order.filled()` | Its cumulative filled quantity, `0` before the first fill |
| `order.avgFill()` | Its average fill price, absent before the first fill |
| `order.working()`, `order.pending` | Whether an order is still working and unfilled, and how many are |
| `order.id()` | The destination's own order id |
| `order.rejection()` | The destination's own rejection text |
| `pos.entryTime`, `pos.barsHeld`, `pos.entries` | When the position opened, bars since, and how many entries built it |
| `pos.openProfit`, `pos.maxProfit`, `pos.maxLoss` | The open result in money, and the best and worst the position has seen |
| `pos.equity`, `pos.netProfit`, `pos.tradeCount` | The strategy's own equity, realised profit and closed trade count |

Until they land, keep what you need in a [`var`](/script/language/persistence), set when you place an order and cleared when the position changes.

### Tags that name something

A tag on `close()` or `cancel()` names an order the script placed. Three rules keep that honest:

- **A close whose tag no order in the file is placed with is refused when the script compiles**, with OS7016. It is almost always a typo. A `cancel()` with such a tag is not checked in this release, so spell those with care:

```openscript
version 1
strategy("A mistyped tag", overlay = true)

goLong = crossUp(ema(close, 9), ema(close, 21))
goFlat = crossDown(ema(close, 9), ema(close, 21))

if goLong and pos.isFlat
    buy(tag = "entry")
if goFlat and pos.isLong
    close(tag = "entyr")
```

- **A close on a tag that holds nothing right now sends nothing and says nothing**, which is what makes closing the same tag twice safe to write.
- **A close with a `qty` larger than what is left is refused** at the bar, with OS7017, because no order crosses zero. Guard a scale-out on `pos.size`, or leave the quantity out and let the close send what is there.

## The books of a deployed strategy

A strategy deployed from the **Strategies** panel keeps books at the platform, and the panel shows them. Press **Activity** on a deployment's row to open them, and **Hide** to fold them away.

| Tab | Columns |
|---|---|
| **Orders** | Symbol, Side, Qty, Price, Status |
| **Trades** | Symbol, Side, Qty, Price, At |
| **Positions** | Symbol, Net, Avg, P&L |

**Refresh** reads the tab again, and the tabs also read themselves again whenever an order is placed, changed or cancelled anywhere on the platform. A tab with nothing to show says so, for example **No orders from this strategy yet**. A tab that cannot be read says why in place, without hiding the other two.

These are the platform's own books, narrowed to this deployment. Every order a running deployment places carries the deployment's own id as its strategy tag, and the **Orders** and **Trades** tabs are the platform's order book and trade book filtered on that tag. Two deployments of the same script, on two instruments or two intervals, therefore keep separate books. The statuses are the platform's own words, such as `complete`, `open`, `trigger pending`, `rejected` and `cancelled`.

The books are read from the side the platform is set to now: the sandbox's books while OpenAlgo is in analyzer mode, your broker's while it is in live mode, and the tabs read themselves again when that setting changes. Orders a deployment placed on the other side are in the other side's books, so after switching modes its earlier activity is not in these tabs until you switch back.

**Positions is weaker than the other two, and it says so.** A position is held per contract and carries no strategy, so the tab lists the contracts this deployment traded, and a row may include size another strategy or a manual order opened. It tells you what you are in because of this strategy, not what the strategy is worth.

The line on the deployment's row, **Flat** or the side, size, symbol and average price, is read from the same narrowed position book. The profit beside it is the deployment's own, from the platform's record of this deployment's orders, and it includes profit already taken on positions the strategy has since closed, which is why a figure can appear beside **Flat**, with a note saying where it came from.

## Reconciling the books with the account

Once a day, compare what a deployment's row says it holds with your account's own positions. They should agree, except for whatever another strategy or a manual trade holds in the same contract. The day they differ for any other reason is the day to find out from the panel rather than from a statement.

Two situations make them differ by design:

- **A run that restarts begins flat.** A deployment started again, by you or after the platform restarts, replays recent history without sending anything and holds nothing, whatever the account holds from before. Its books start empty. [Sandbox and live](/script/strategies/sandbox-and-live) covers what to do about a position it does not know about.
- **Stop closes the run's own position, not the account's.** It sends one order for exactly what this run's books hold, so a second strategy's position in the same contract is left alone.

## Pitfalls

| Symptom | Cause | Fix |
|---|---|---|
| A fill counted twice | Adding up reports instead of reading the total | Read the position, which has folded every repeat |
| OS7016 on a `close` | The tag is one no order in the file is placed with, usually a typo | Use the entry's tag, or leave the tag out to close the whole position |
| OS7017 on a `close` | The quantity is larger than what is left, usually a scale-out firing twice | Guard on `pos.size`, or leave the quantity out |
| A late fill applied to the wrong trade | Expecting fills to settle the current position | They settle their own position; a reversal is two orders |
| The strategy's position disagrees with the account's | Something else trades that contract, or the run restarted flat | Check the **Positions** tab and the account, then decide who owns the difference |
| A planned read refused with OS2020 | `order.*` reads and most `pos.*` facts are planned in 0.5.0 | Keep the fact in a `var` |
| Drawdown looks worse than the trades suggest | A position that was added to is valued at its final size from its first bar | Read the trade list for the result |

**Related.** [Orders](/script/strategies/orders), [Reading a report](/script/strategies/reading-a-report), [Legs and books](/script/strategies/multi-leg-and-books), [Position and sizing](/script/strategies/position-and-sizing), [Sandbox and live](/script/strategies/sandbox-and-live), [pos.* reference](/script/reference/position), [order.* reference](/script/reference/orders)


## Sandbox and live

Source: https://openalgo.in/script/strategies/sandbox-and-live

A backtest says what a strategy would have done. A deployment runs it: a process on the OpenAlgo server executes the same compiled script on each bar as it closes and sends real orders through the platform's own order path. This page covers deploying a strategy from the **Strategies** panel in /trading, running it in sandbox trading (analyzer mode in OpenAlgo) first, what changes when it runs live, what the strategy runner needs from a script in release 0.5.0, and how to pause, stop and restart one without losing track of a position.

## A strategy ready to deploy

The strategy runner in release 0.5.0 runs a subset of what the backtest runs. This file stays inside it: its quantity is in units, its stop is a rule the script tests on each close, and it reads no calendar and no session boundary.

```openscript
version 1

// Written for the 0.5.0 strategy runner: units, a stop written as a rule, and no
// calendar or session reads, which the runner does not answer yet. The costs
// are for the backtest; a deployment pays whatever the market charges.
strategy("EMA cross, deployable", overlay = true, precision = 2,
         capital = 500000, qty = input(1, "Quantity, units", min = 1),
         product = "intraday",
         fillOn = "nextOpen", slippage = 1,
         commissionType = "percent", commission = 0.023)

fastLen = input(9,  "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 2, max = 500)
maxLoss = input(1.0, "Close a long this far below entry, percent", min = 0.1, max = 20)

fast = ema(close, fastLen)
slow = ema(close, slowLen)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

// The stop, from the strategy's own entry price. pos.avgPrice is absent while
// flat, so the test below is only ever true while a position is open.
stopLevel = pos.avgPrice * (1 - maxLoss / 100)
tooFar    = close <= stopLevel

if goLong and pos.isFlat
    buy()

if pos.isLong and (goFlat or tooFar)
    close()

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)
plot(pos.isFlat ? none : pos.avgPrice, "Entry", silver, style = "step")
plot(stopLevel, "Stop", red, style = "step")
```

Backtest it first, from the Backtest panel, until you believe the report. Then deploy it in sandbox trading and let it run for long enough to compare its trades with the backtest's.

## Where a strategy runs

| Where | Bars | Orders go to | Money |
|---|---|---|---|
| Backtest panel | History, all of it closed | A fill model over the bars, in your browser | None |
| A deployment, in analyzer mode | Each bar as it closes | OpenAlgo's sandbox | None |
| A deployment, in live mode | Each bar as it closes | Your broker, through OpenAlgo | Yours |

The script and its inputs are the same in all three. What differs is where the orders go, and **nothing in a script decides that.** A deployment sends its orders through OpenAlgo's own order path, the same path every other surface of the platform uses, and that path reads the platform's analyzer setting before anything else:

- While OpenAlgo is in **analyzer mode**, a deployment's orders go to the sandbox. The sandbox checks each order against its own funds and margin, fills it against the latest quote for the instrument, and keeps its own orders, trades and positions. No money moves.
- While OpenAlgo is in **live mode**, the same orders go to your broker.

There is no call, no declaration option, no input and no field in the deployment form that chooses the destination, and a script cannot ask which one it is running against. That is deliberate. A strategy that behaved differently once it was live would be a strategy nobody had tested, and the sandbox run would stop being evidence about the live one.

> **The setting is platform-wide**
Analyzer mode is one setting for the whole platform, made outside the Strategies panel. Turning it off moves every running deployment to live at once: a deployment holding nothing follows the platform and sends its next order to your broker. Before you switch, pause or stop every deployment you do not mean to trade live.

A deployment that is holding a position when the setting changes does not follow it. Its next order goes to the new destination, the run sees that the destination changed under an open position, stops, and writes in its log that the position was opened at the earlier destination and must be checked and closed by a person. Change the setting while your deployments are flat.

## Deploying from the Strategies panel

Open **Strategies** from the toolbar on the right-hand edge of /trading. It sits after **Backtest**, in the order the work happens.


The header shows the platform's current destination as a badge, **Analyzer** or **Live**, and how many deployments are running. Hover the badge and it says where a running strategy's orders go.

1. **Press Deploy a strategy.** It is available once you have saved at least one strategy; a study plots and places no orders, so only a strategy is offered.
2. **Choose the Strategy**, then the instrument. The form starts on the chart's own instrument and interval. Type in the instrument box to search the platform's instrument list for the chosen exchange, then pick the **Exchange**, the **Interval** your broker serves, and the **Product**.
3. **Set the inputs.** Every `input()` the script declares is a field, as in the Backtest panel. A field left empty runs on the script's own default.
4. **Press Deploy.** The deployment appears as a row, stopped.
5. **Press Start in sandbox.** In live mode the same button reads **Start live**. The row turns to **running**, with how long it has been running and the server process it runs in.

| Product | Offered on | Means |
|---|---|---|
| `MIS` | Every exchange | Intraday |
| `CNC` | Cash exchanges, such as NSE and BSE | Delivery |
| `NRML` | Derivative exchanges, such as NFO, BFO and MCX | Carried on margin |

The product chosen here is the product every order is sent as, whatever word the script's `product` option uses, and the form starts on `MIS`. A script written to carry a position overnight, with `product = "overnight"`, therefore runs as intraday unless you pick `CNC` or `NRML` yourself. The form offers only the products the chosen exchange takes, so you cannot pick one its venue does not have.

**A deployment is a strategy on one instrument at one interval.** The same strategy can be deployed on as many instruments and intervals as you like, and each deployment runs, holds, keeps its books and stops on its own. Changing the instrument or the interval in a deployment's settings makes a second deployment rather than moving the first, so a strategy you are already running is left where it is. Deploying the same strategy twice on the same instrument and interval is refused, because that is one strategy running twice on one instrument.

Each row carries:

| Control | What it does |
|---|---|
| **Start in sandbox** or **Start live** | Starts the deployment. Needs an instrument to be set |
| **Pause** | Ends the run and leaves its position exactly where it is |
| **Stop** | Closes what the run is holding, then ends it. Asks first |
| **Settings** | Opens the deployment's instrument, product and inputs. A running strategy reads its inputs when it starts, so pause it and start it again to apply a change |
| **Activity** | Opens the deployment's **Orders**, **Trades** and **Positions**. [Reading the books](/script/strategies/reading-the-books) covers them |
| Remove (the bin icon) | Removes the deployment and its schedule, after asking. Available only while it is not running. The strategy stays in the editor, and anything it traded stays in your books |

With more than six deployments, a search box finds one by strategy or instrument. The list refreshes itself whenever an order moves anywhere on the platform, and every fifteen seconds in case a run ended on its own.

## What the runner needs from a script

The server does not compile anything. It runs the compiled program the editor stores beside the script each time a save compiles with no errors; a save with errors removes the stored program, so the program always matches the script as saved. Pressing Start on a script with no compiled program is refused in the panel, with a message telling you to open it in the chart and save it once the console shows no errors.

The server runs that program on `openscript`, the Python library of the language, rather than on the engine in your browser. A script that calls something the server's engine does not implement yet is refused when the run loads, and the log names the diagnostic code.

When a run starts, the runner checks the program before it sends anything. In release 0.5.0 it refuses a script that:

| The script | Why the runner refuses it | What to do |
|---|---|---|
| Calls `exit()` or `order.bracket()` | A stop and a target have to go out as one protected pair, and the runner cannot send that yet. Sending the entry alone would leave a position with nothing protecting it | Write the stop as a rule tested on each close, as above |
| Sizes by `qtyType = "lots"`, `"cash"` or `"equityPercent"` | It sends only a quantity the script states in units | Use `qtyType = "units"`, and size F&O orders in units of the lot |
| Reads `session.isFirstBar` or `session.isLastBar` | The runner works out no session boundaries. It refuses the first itself, and the server's engine does not have the second yet | Anchor on something the bars carry |
| Reads the calendar: any `date.*` call, `date.format()` or `session.isIn()` | The runner reads a clock only in UTC and an Indian instrument's calendar is IST, so every such call would answer nothing and the strategy would never act on one | Keep calendar rules out of a deployed script in this release |
| Declares an input with `kind = "time"` | The same: a written time would be read under the wrong calendar | The same |

A refused run ends at once and sends nothing. The row goes back to **stopped**, and the reason, naming the script, is in the run's log on the server.

`closeOnSessionEnd` is not acted on by the runner either, so an intraday strategy that must be flat by the close needs its own exit, and without a calendar read that exit cannot be a time of day in this release. Watch the end of the first sessions, and close anything left open yourself.

> **Intraday positions and the square-off**
In analyzer mode the sandbox squares off `MIS` positions on its own at a set time before the close (15:15 for NSE, BSE and NFO by default), and after that time it refuses `MIS` orders that would open or add to a position until the next session. That square-off is not one of the deployment's orders, so the deployment's books still show the position, and its next exit would be an order in the other direction. Pause or stop a deployment that is holding an `MIS` position before the square-off time, or deploy it with `CNC` or `NRML`. A live account may square off intraday positions in the same way: check your own account's rule.

## How a run starts, and what it does on each bar

**It replays recent history and begins flat.** A strategy needs history before it can say anything, so the first thing a run does is load the last five days of bars for the deployment's instrument and interval, and execute them in order. Nothing is sent for them. Replaying yesterday's signals as orders would trade yesterday on today's money, so the run begins holding nothing and its log says so: an exit written for a position the history would have opened has nothing to exit.

The window runs from five calendar days before today up to now: three or four earlier sessions and whatever of today has traded so far. That is enough warmup for some files and not for others:

| Interval | Bars replayed, roughly | Warm at the start for |
|---|---|---|
| 1 minute | 1,100 to 1,900 | Almost anything |
| 5 minutes | 225 to 375 | A 200 bar average |
| 15 minutes | 75 to 125 | A 50 bar average, not a 200 bar one |
| 1 hour | 21 to 35 | Short lookbacks only |
| Daily | 3 to 5 | Almost nothing |

A run keeps what it computes, so a file that is not warm at the start becomes warm as bars arrive. Until then its conditions are absent and it simply does not trade. A daily strategy reading a 200 bar average would not trade for most of a year, so deploy it on a finer interval or not at all.

**Orders go out when a bar closes.** The newest bar is executed again on every update while it is still forming, with the strategy's state restored before each run, so running it ten times gives the same answer as running it once. Orders are sent only from the execution that confirms the bar, which is exactly what the backtest did. A script that declares `onUnconfirmed = true` has its intrabar orders held back, and the log says so once. Where the platform's tick stream is available the run closes each bar on it the moment it ends; otherwise it looks for the closed bar in history, checking at least every fifteen seconds and more often around each bar's close.

**The orders are the platform's own kinds.** A market order is sent as `MARKET`, a limit as `LIMIT`, a stop as `SL-M` and a stop-limit as `SL`, with the deployment's product, and every order carries the deployment's own id as its strategy tag, which is what the **Activity** books are filtered on. A reversal, which the engine keeps as two orders, reaches the platform as the one net order it adds up to.

**Nothing is taken back.** An order the platform refuses is recorded as rejected with the platform's own reason, nothing behind it on that bar is sent, and the run carries on. If an earlier order on the same bar had already gone out, the position is half moved: the run stops, says so in capitals in the log, and sends nothing to undo it, so check the position yourself. An error in the script, such as a second entry beyond `pyramiding`, stops the run at that bar and nothing further is sent. Both are written to the log with the bar and the reason.

**Edits reach a run only when it starts.** A run loads the compiled program and the saved inputs when it starts. Saving a new version of the script, or changing a deployment's inputs, changes nothing in a run that is already going: pause it and start it again to pick the change up. The restart replays history and begins flat, like any start.

## Sandbox against backtest

Run in sandbox trading for at least a week and at least twenty fills, then compare its trades with a backtest of the same days. They should differ in fill prices and in nothing else. A difference in **which** trades were taken is a bug to find before you go live, and it usually has one of these causes:

| Difference | Cause |
|---|---|
| The deployment missed the first trades of the week | It started flat after replaying history, or was not yet warm |
| An exit the backtest took is missing | The position it would have closed was opened before the run started, so the run holds nothing to exit |
| A trade fired on a bar the backtest shows no signal on | The bar the run acted on differed from history's version of it. The log says so, once, when a closed bar from the tick stream disagrees with history |
| Fill prices are worse by a steady amount | Slippage in the real market, or between the bar's close and the order's arrival. Revisit the `slippage` you backtested with |
| A position the backtest carried was closed at 15:15 | The sandbox's own square-off of an `MIS` position, which the deployment's books do not see |

What a sandbox run cannot show you is the market's side of a fill: queue position, partial fills, the impact of your own size, and the refusals a real account meets for margin, permissions or price bands. [Costs and fills](/script/strategies/costs-and-fills) lists those gaps.

## Pause, Stop and the position

Pausing and stopping are different things, and the difference is the position.

- **Pause** ends the run and leaves its position exactly where it is, at the sandbox or at your broker. The position becomes yours to manage. Pause costs nothing, so it is one press.
- **Stop** closes what the run is holding and then ends it. It asks first, naming the strategy, the instrument, and the size and result of what it is about to close, and offers **Close and stop**, **Pause instead** and **Cancel**. It sends one market order for exactly what this run's own books hold, in the opposite direction, so another strategy's position in the same contract is left alone. It then waits for that order to fill. If the close is refused or does not complete in time, the run stays running and still holding, and the panel says so, because a run that exited on a close it did not manage would leave a position nothing is watching.

Stop spends a spread and cannot be undone. Use Pause to change a parameter or to look at what a strategy is doing, and Stop when you are finished with it.

### A run outlives the page

A deployment is a process on the server. Closing the browser stops nothing, and the panel is a view of what is already happening. If the panel cannot reach the runner it says the list may be out of date, and anything already running is still running.

When OpenAlgo itself restarts, it puts back every deployment that was running. A run whose process survived is taken over as it is. A run that has to be started again replays history and begins flat, like any start. A deployment you paused or stopped is not put back.

### The position a run does not know about

A run starts flat. It does not look at the account and adopt what is there, because a position in the account has no entry logic attached to it: the script's stop and its exit would be managing a trade its rules never took. So if a deployment was holding a position when it was paused, or when a restart started it again, the account still holds that position and the new run does not. Its next exit has nothing to exit, and its next entry opens a new position beside the old one.

Before starting a deployment again after it held a position:

1. Check the account's position in the contract, and the deployment's **Positions** tab.
2. Decide who owns it. Either close it yourself, or take it over by hand and leave the strategy to start flat.
3. Cancel any working orders the earlier run left behind, unless you mean to keep one.
4. Only then start the deployment.

Nothing in the language does this for you. It is a decision, so it belongs to a person.

### Schedules

The runner can also hold a schedule for a deployment: a start time and an optional stop time in IST, on chosen days of the week, skipping days the deployment's exchange is closed. The Strategies panel has no controls for one yet, and removing a deployment removes its schedule. A scheduled stop pauses: it ends the run and leaves any position where it is, so a stop time has to fall after the strategy is already flat.

## The run's log

Each run writes its own log file on the server, in the `log/strategies` folder of the OpenAlgo installation, named after the deployment and the time the run started, in IST. The Strategies panel does not show it. It records, in plain sentences:

- the start, with the strategy, instrument, exchange and interval, and a line saying orders go through the platform's own order path;
- which inputs were set from the deployment's saved values;
- the history replay and how many bars it covered;
- which destination the orders are going to, analyzer or live, once the first order is accepted;
- every order sent, with its side, quantity, instrument, order kind, product and the platform's order id;
- every alert the script raises;
- every refusal, error and stop, naming the bar and the reason.

When a deployment goes back to **stopped** on its own, the log says why.

## Pre-flight checklist

Work through this before a strategy runs with real money. Every item can be checked.

**The script**

1. It starts with `version 1`, compiles with no errors, and you have read every warning.
2. It stays inside what the runner needs: quantities in units, no `exit()` or `order.bracket()`, no calendar or session reads, no time inputs.
3. Every entry has an exit, including one that does not depend on the entry signal reversing, such as a loss limit written as a rule.
4. Every entry is guarded by the position, such as `pos.isFlat`, so no signal can enter twice.
5. It is warm within the history the runner replays at your interval, or you accept its first sessions as warmup.
6. On an F&O contract, the quantity in units is a whole number of lots.

**The numbers**

7. A backtest over a range covering more than one regime, with at least a hundred closed trades.
8. Slippage and commission set, and the result survives double the slippage.
9. The average trade before costs comfortably larger than the cost of a round trip.
10. Maximum drawdown, measured bar by bar, is one you can sit through.
11. At least a week and twenty fills in sandbox trading, and its trade list agrees with a backtest of the same days except for fill prices.

**The account**

12. The instrument is one your account is permitted to trade, at the product the deployment names.
13. The tick and lot size the Backtest panel reported are the instrument's real ones.
14. There is margin for the size at the worst point of the backtest, not the average one.
15. Nothing else trades the same contract, or you know how you will tell the positions apart.
16. You know how to close the position by hand, without the strategy.

**The operations**

17. Every deployment you do not mean to trade live is paused or stopped before analyzer mode is turned off, and every deployment is flat when you switch.
18. You know what happens to a position when a run is paused, stopped or restarted, and you have decided what you will do.
19. You know where the run's log is and how to read it.
20. The first live day is on the smallest size the instrument allows, and somebody watches the first session.

**Related.** [Backtesting](/script/strategies/backtesting), [Reading a report](/script/strategies/reading-a-report), [Reading the books](/script/strategies/reading-the-books), [Costs and fills](/script/strategies/costs-and-fills), [Exits and brackets](/script/strategies/exits-and-brackets), [Realtime and confirmation](/script/language/realtime-and-confirmation)


# Writing scripts

## Style guide

Source: https://openalgo.in/script/writing/style-guide

This page covers how to lay out, name and comment an OpenScript file (OpenScript is also called OpenAlgo Script) so that a reader can go from the top to the bottom once and know what the script does and why. It matters because a trading script is read far more often than it is written: by you when a number looks wrong on a Monday morning, by the person you share it with, and by anyone deciding whether to trust a strategy with money.

The rules here are conventions, not grammar. The compiler accepts a badly named, badly ordered file. What it cannot do is explain it.

## What the language already decides

OpenScript settles most of the arguments a style guide usually exists to settle, so you do not have to.

| Rule | What happens otherwise |
|---|---|
| Indent with spaces only | A tab in the indentation is [OS1002](/script/errors/syntax#os1002) |
| One statement per line | A `;` is [OS1007](/script/errors/syntax#os1007) |
| Every line of a block has the same indentation | One line out by a single space is [OS1003](/script/errors/syntax#os1003) |
| A block is the lines indented more deeply than its header | There are no braces and no `end` keyword to place |
| Comments start with `//` and run to the end of the line | `/*` is [OS1026](/script/errors/syntax#os1026): there are no block comments |

The language also defines one canonical layout, and the formatter in the `openalgo-script` package's editor tools writes it. A formatter moves whitespace and nothing else, so laying a file out again never changes what it means. The canonical layout is:

- four spaces per block level;
- a continuation line (a statement carried onto the next line) eight spaces past the line that began the statement;
- one space between two tokens, with no space inside brackets, before a comma or around a dot;
- a comment after code two spaces clear of it;
- at most one blank line in a row, and exactly one line ending at the end of the file.

Because the canonical layout puts one space around `=`, a formatter does not keep columns you align by hand. The Scripts panel on the /trading page does not reformat a file for you in this release, so write in this layout as you go. Every example on this page is written in it. Let the layout decide the spacing and put your effort where it cannot help: the names you choose, the order you put things in, what you pull out into a function, and what you say in a comment.

A good rule to write to is the one the language itself follows: a rule exists because of a reason, and the reason is worth one sentence. A reader who knows why a line is written a certain way keeps it that way. A reader who only knows that it is written that way deletes it the first time it is inconvenient.

## The shape of a file

Every script has the same sections in the same order. The order is not arbitrary. A script runs top to bottom once per bar, and a name has to be assigned before it is read, so a file that reads naturally is also a file that compiles. See the [execution model](/script/language/execution-model) for the details.

| Order | Section | Holds | Why it sits here |
|---|---|---|---|
| 1 | Header comment | What the script does, what it needs, what it does not do | The first thing a reader sees should be prose, not code |
| 2 | `version 1` | The language version | It must be the first line that is not blank and not a comment |
| 3 | Declaration | `study(...)` or `strategy(...)` | Exactly one, and it fixes the pane, the precision and, for a strategy, the cost model |
| 4 | `limits(...)` | A raised loop budget or history depth, when the defaults are not enough | It must be the statement straight after the declaration. See [Limits](/script/writing/limits) |
| 5 | Inputs | Every `input()` call | Top level only, and a reader wants the knobs before the machinery |
| 6 | Functions | `fn` declarations | A function may be declared after it is called, so put them here or at the end: pick one |
| 7 | Data reads | `req.timeframe()` and `req.symbol()` | They are the script's outside dependencies and belong together |
| 8 | Calculations | Library calls and arithmetic | Unconditional, at the top level, so every stateful call advances on every bar |
| 9 | State | `var` declarations and the blocks that update them | After the values they are computed from |
| 10 | Decisions | The `if` blocks that turn numbers into conclusions | After the numbers, before the output |
| 11 | Outputs | `plot()`, `fill()`, `level()`, `table()` | Top level only, and where a reader looks first when a line is wrong |
| 12 | Events and paint | `signal()`, `alert()`, `barColor()`, `background()`, drawings, orders | Last, because they are consequences |

Sections 11 and 12 can be interleaved where a marker belongs next to the decision that raises it. What does not read well is a plot in the middle of the calculations and another after the orders.

Here is the whole shape in one short file. It works on any instrument: an NSE stock, a NIFTY future on NFO, or crude oil on MCX.

```openscript
// Deviation bands around a simple average, with a marker on the bar the source
// closes above the upper band.
//
// Needs: only the chart's own bars. No volume and no other instrument.
// Does not: place orders or say anything about direction.

version 1

study("Deviation bands", overlay = true, precision = 2)

length = input(20, "Length, in bars", min = 2, max = 500)
widthDev = input(2.0, "Band width, in standard deviations", min = 0.1, max = 10)
src = input(close, "Source")

basis = sma(src, length)
dev = widthDev * stdev(src, length)
upper = basis + dev
lower = basis - dev

plot(basis, "Basis", orange, width = 2)
upperPlot = plot(upper, "Upper", silver)
lowerPlot = plot(lower, "Lower", silver)
fill(upperPlot, lowerPlot, fade(silver, 92))

// A break is an event on one bar, so it is a marker rather than a plotted
// column that would have to be absent on every other bar.
if crossUp(src, upper)
    signal("BREAK UP")
```

Two small things in that file are deliberate. The basis plot's handle is not kept, because nothing uses it, while the two band handles are kept because `fill()` names them. And the input titles are full phrases with units in them, because the settings dialog is the only documentation most users of a script ever read.


## Naming

The convention, which the compiler does not enforce, is `camelCase` for names and functions and `UPPER_SNAKE` for values the script treats as constants. Beyond that, these rules earn their keep.

| Rule | Instead of | Write | Because |
|---|---|---|---|
| Say what the number is, not its type | `n`, `val`, `x2` | `length`, `stopDistance`, `rangeWidth` | Every one of them is a `number`, so the name is the only information |
| Put the unit in the name | `hold` | `holdMinutes` | A number that is sometimes minutes and sometimes milliseconds is a bug waiting for a busy expiry day |
| A bool reads as a claim about this bar | `flag`, `check` | `forming`, `isReady`, `broken` | `if forming` reads as English; `if flag` reads as nothing |
| A length input ends in `Len` or `Length` | `fast`, `slow` | `fastLen`, `slowLen` | Then `fast` and `slow` are free for the averages themselves |
| A plot handle is named for its plot | `p1`, `p2` | `upperPlot`, `lowerPlot` | The only thing a handle is for is being named by `fill` |
| Loop indices may be short | `elementIndex` | `i`, `j` | A three line loop body gives the index all the context it needs |
| Fixed constants get `UPPER_SNAKE` | `msPerMinute` | `MS_PER_MINUTE` | It marks the value as fixed by arithmetic rather than by the user |

```openscript
MS_PER_MINUTE = 60000

holdMinutes = input(30, "Hold, in minutes", min = 1, max = 375)
holdMs = holdMinutes * MS_PER_MINUTE

plot(holdMs, "Hold, in milliseconds")
```

### Names you cannot use

Two kinds of name are refused, and both are [OS2002](/script/errors/names-and-types#os2002).

**A library name.** `close`, `ema()`, `aqua`, `plot()` and every other name in the [reference](/script/reference/technical-analysis) live in the outermost scope, so assigning to one is an error. This catches people more often than it sounds, because many library names are ordinary words: `variance()`, `count()`, `change()`, `mix()`, `sign()`, `median()` and `chop()` are all taken.

```openscript
variance = stdev(close, 20) * stdev(close, 20)
```

When the compiler stops you, add a word that says what the value is: `varianceValue`, `barCount`, `priceChange`.

**A second declaration of a name that already exists outside.** Assigning to a top-level name from inside an `if` or a loop is allowed: it updates that name, which is usually what you want. What the compiler refuses is a new name that would hide the outer one: a `var` of the same name inside a block, a loop counter or a function parameter named like a top-level value, or a function body that assigns to a top-level name. That is an error, not a warning, because the most expensive bug in a per-bar script is a value that is right in one place and stale in another, and two variables with one name is the shortest path there.

```openscript
len = 20

fn helper(src) =>
    len = 9  // a second len inside the function
    sma(src, len)

plot(helper(close), "Helper")
```

Rename the inner one, here to `innerLen`. See [Variables and scope](/script/language/variables-and-scope).

## Ordering inside a section

Within the calculations, order by dependency and then by importance. A reader should be able to stop at any line and have already seen everything the lines below it use.

```openscript
stopMult = input(2.0, "Stop, in ATR", min = 0.2, max = 20)
targetMult = input(3.0, "Target, in ATR", min = 0.2, max = 40)

// Each line uses only what is above it, and the values that matter come last.
atrValue = atr(14)
stopDistance = stopMult * atrValue
targetDistance = targetMult * atrValue

plot(stopDistance, "Stop distance")
plot(targetDistance, "Target distance")
```

Group by subject, not by kind of call. Three lines that compute a stop belong together even if one is a library call and two are arithmetic. Blank lines between groups are free, and they are the cheapest readability there is.

Functions may be declared after they are called, because the compiler collects every `fn` before it checks any body. Use that freedom once: put every function in one place, either straight under the inputs or at the end of the file, and do it the same way in every script you write.

## When to extract a function

Extract a function when one of these three is true, and not otherwise. See [User functions](/script/language/functions) for the rules of `fn`.

**1. The same expression appears twice.** Two copies are two places to fix when it is wrong, and the second one is always the one that gets missed.

**2. A line needs a sentence to explain it, and the sentence is a name.** This is the most common reason and the most underused.

```openscript
// Before: correct, and the reader has to decode it every time.
z = (close - sma(close, 20)) / stdev(close, 20)
plot(z, "Z score")
```

```openscript
// After: the name is the explanation, and the formula is read once.
fn zscore(src, len) =>
    m = sma(src, len)
    s = stdev(src, len)
    (src - m) / s

z = zscore(close, 20)
plot(z, "Z score")
```

**3. You want a second, independent copy of some per-bar state.** State belongs to each call site, not to the function, so a stateful helper called in two places keeps two separate counters. That is what makes a helper reusable at all.

```openscript
fn barsSinceTrue(cond) =>
    var n = none
    if cond
        n = 0
    else if not isNone(n)
        n = n + 1
    n

sinceUp = barsSinceTrue(close > open)  // its own counter
sinceHigh = barsSinceTrue(high > high[1])  // a separate counter

plot(sinceUp, "Bars since an up bar")
plot(sinceHigh, "Bars since a higher high")
```

Do not extract when the function would take six arguments to avoid repeating two lines, when the body is one library call with the arguments renamed, or when all it does is hide a number that should have been an input.

You cannot extract a function that calls itself. Recursion is [OS2005](/script/errors/names-and-types#os2005), because the state of every call site is laid out before the first bar runs. Write a loop instead.

```openscript
// Refused where it is declared, whether or not anything calls it.
fn countdown(n) => n <= 0 ? 0 : countdown(n - 1)
```

One more constraint shapes extraction: a parameter may not be named after a name at the file's top level or after a library name (OS2002 again). Helper parameters therefore tend to be short and generic, such as `src`, `len` and `cond`, which is fine, because a helper's parameters take their meaning from the call site.

## Comments

**Comment why, not what.** The reader can see what a line does; the language is small enough that every line says what it does. What the reader cannot see is the alternative you rejected and the reason.

```openscript
// Bad: says what the line already says.
// Compute the 20 bar exponential moving average of the close.
e = ema(close, 20)
plot(e, "EMA 20")
```

```openscript
// Good: says what the reader could not have known.
// Computed at the top level on purpose. Inside the branch that uses it, the
// average would advance only on the bars that branch ran on, which is warning
// OS8001 and a line with holes in it.
e = ema(close, 20)
plot(e, "EMA 20")
```

The places where a why comment nearly always pays for itself:

| Where | What to say |
|---|---|
| An `orElse()` | What is absent, on which bars, and what would happen without the fallback |
| A `var` | Why the value has to survive from one bar to the next |
| A `live var` | Why counting updates is the intent, since it makes the chart and a backtest differ |
| A pivot or any lagged read | How many bars late the value is, and that the lag is real rather than a bug |
| A `mode` on a higher timeframe read | Which of the three readings the script takes, and why |
| A guard on `bar.isConfirmed` or `bar.isLast` | What would happen on the still-forming bar without it |
| A fixed number | Where it came from, or why it is not an input |
| An order that looks accidental | That reading a `var` before it is reassigned is how the previous bar's value is obtained |

The header comment is the one exception to "why, not what": it is the place that says what. Write three short paragraphs in this order: what the script draws or trades, what it needs from the chart (volume, a session, another instrument, a particular timeframe), and what it deliberately does not do. Write it before the code, and you find out whether you know what you are building. [Sharing scripts](/script/writing/sharing-scripts) shows a full header.

There are no block comments. An unterminated one would swallow the rest of the file and report its error at the last line, so the form does not exist. Comment out a region by putting `//` in front of each line.

## Formatting

- **Four spaces per level**, the canonical amount.
- **Keep lines under about eighty characters.** A continuation line must be indented more deeply than the first line of its statement ([OS1028](/script/errors/syntax#os1028) otherwise), so a wrapped line is never ambiguous.
- **Break a long call at its named arguments**, one group per line. A call that needs four lines is telling you it has four ideas in it.
- **Blank lines separate sections, not statements.** A blank line inside a three line group is noise.
- **Never write a line whose only purpose is to be clever.** A nested ternary three levels deep is legal, and nobody reads it correctly the first time. Give the inner choice a name.

```openscript
up = close > open
down = close < open
strong = volume > sma(volume, 20)

// Before: legal, and nobody reads it correctly the first time.
tone = up ? (strong ? lime : green) : (down ? (strong ? red : maroon) : gray)
barColor(tone)
```

```openscript
up = close > open
down = close < open
strong = volume > sma(volume, 20)

// After: two names, three short lines, one obvious reading.
upTone = strong ? lime : green
downTone = strong ? red : maroon
tone = up ? upTone : down ? downTone : gray
barColor(tone)
```

A long declaration broken at its named arguments, in the canonical layout:

```openscript
version 1

strategy("EMA cross, NIFTY futures", overlay = true, precision = 2,
        capital = 500000, qtyType = "lots", qty = 1,
        fillOn = "nextOpen", slippage = 1,
        commissionType = "perTrade", commission = 20)

fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow)
    buy()

if crossDown(fast, slow)
    close()
```

## Shapes to avoid

| Shape | What goes wrong | Write instead |
|---|---|---|
| A stateful call inside an `if` | It advances only on the bars the branch runs, and is absent on the rest ([OS8001](/script/errors/warnings#os8001)) | Compute it at the top level, use it inside the branch |
| A `plot` inside an `if` | [OS3006](/script/errors/arguments#os3006): the set of plotted columns is fixed before bar 0 | `plot(cond ? value : none, "Title")` |
| A `var` holding `bar.index` | Every index shifts when more history loads | Store `time` and compare timestamps |
| `x = 0` then `x = x[1] + 1` as a counter | `x[1]` is absent on bar 0, absence spreads, and the series stays absent for ever | `var x = 0` then `x = x + 1` |
| `live var` because it sounded faster | The chart and a backtest now disagree by design ([OS8011](/script/errors/warnings#os8011)) | Plain `var`, unless counting updates is the intent |
| A number typed into the calculations | Nobody can tune it without editing the script | An `input()` with a title, a minimum and a maximum |
| A name assigned and never read | It still runs on every bar and suggests something depends on it ([OS8010](/script/errors/warnings#os8010)) | Delete the line |
| The same data read written twice | Two requests against the host's ceiling ([OS5006](/script/errors/limits#os5006)) | Read once, name it, reuse the name |

> **The error reference lists a warning for a persistent value holding a bar index, [OS8014](/script/errors/warnings#os8014), but the compiler in version 0.5.0 does not raise it yet: it does not follow a bar index into a `var`. Until it does, the rule above is yours to keep.**

## Treat warnings as part of the style

An OS8xxx warning never stops a script, which makes it tempting to leave in place. Do not. Every warning describes a shape that is almost always a mistake, and in the rare case where it is not, one comment saying so costs nothing. A file that compiles with no warnings tells its reader that everything unusual in it was meant. The full list is on the [warnings](/script/errors/warnings) page.

**Related.** [Debugging](/script/writing/debugging), [Profiling and speed](/script/writing/profiling), [Limits](/script/writing/limits), [Testing scripts](/script/writing/testing), [Sharing scripts](/script/writing/sharing-scripts), [Example scripts](/script/getting-started/example-scripts)


## Debugging

Source: https://openalgo.in/script/writing/debugging

This page shows how to take an OpenScript study or strategy (OpenScript is also called OpenAlgo Script) that produces a wrong number, or no number at all, and find the first bar where it goes wrong and the line that causes it. You need it the first time a line on your chart has a hole in it, a signal fires where it should not, or a strategy's backtest does something you cannot explain.

## Why there is no stepper

A script is the body of a loop that runs once per bar, top to bottom, oldest bar first, over a chart that may hold tens of thousands of bars. By the time you look at the chart the loop has already finished. There is no breakpoint to set and no variable inspector to open, because there is nothing left running to attach one to. See the [execution model](/script/language/execution-model).

So debugging in OpenScript is not stepping. It is making the script show what it did, on every bar or on one chosen bar, and reading that back. There are five ways to make it show you.

| Way | Shows | Best for |
|---|---|---|
| A `plot()` | One number per bar, drawn | Finding the bar a value goes wrong on |
| `background()` or `barColor()` | One fact per bar, painted behind or on the candles | Finding which bars are absent, or which bars a branch ran on |
| A `table()` | A grid of values written on the newest bar | Watching many values at once |
| `draw.label()` | Text pinned to one bar at one price | Freezing the state of a chosen bar where you can see it |
| `print()` | A line in the script's log, with the bar's time | A trace over many bars, in a host that shows a log |

> **The /trading page has no view of the script's log in this release. A `print()` call compiles and runs, but its lines appear nowhere on the page. Everything else on this page works in /trading. [Print and the log](#print-and-the-log) covers `print` for when you run a script where a log is shown.**

Here is the most useful single debugging line in the language. It shades every bar where a value is absent (has no value, see [Absent values](/script/language/absent-values)), and it usually shows the cause at a glance: a block of shading at the left edge is warmup, a stripe in the middle is a gap in the data or a branch that did not run.

```openscript
value = rsi(close, 14)

// Shade every bar where the value is absent.
background(isNone(value) ? fade(red, 85) : none)
plot(value, "RSI")
```

## Start with three questions

Three questions, asked in this order, solve most problems.

1. **Is the value absent, or is it wrong?** These are different bugs with different causes. Absence usually comes from warmup, a gap in the data, or a branch that did not run. A wrong number usually comes from arithmetic, an off-by-one lookback, or state updated in the wrong order.
2. **On which bar does it first go wrong?** Not "it looks wrong on the right of the chart". The first bar. Everything after the first wrong bar is a consequence.
3. **What did the inputs to that line hold on that bar?** Once you have the bar, put everything that feeds the line on the chart for that bar, and check it against arithmetic you do by hand.

## Make a value visible

### Plot it

The fastest look at any intermediate value is a plot. Two things get in the way, and each has a one line fix.

**A bool cannot be plotted.** There is no conversion from `bool` to `number`, so write it out. That is also a chance to tell "false" apart from "absent", which is the distinction that matters.

```openscript
r = rsi(close, 14)
hot = r > 70

// Three states: 1 for true, 0 for false and a gap for absent. Plotting
// orElse(hot, false) instead would draw a confident zero through the warmup.
plot(isNone(hot) ? none : (hot ? 1 : 0), "debug: hot", fuchsia, style = "step")
```

**A debug plot on a price overlay flattens the price scale.** A value of 50 on a pane where NIFTY trades near 25,000 squashes everything. Put the debug plot on the other scale, or debug in a separate copy of the study with `overlay = false`.

```openscript
version 1

study("Bands, with a debug line", overlay = true, precision = 2)

basis = sma(close, 20)
dev = 2 * stdev(close, 20)

plot(basis, "Basis", orange)
// On the left scale, so a value near 50 does not flatten a price near 25000.
plot(dev, "debug: deviation", fuchsia, scale = "left")
```

Delete debug plots before you share a script. Each one takes a legend row and a row in the settings dialog, and a plot the compiler can see is absent on every bar earns warning [OS8009](/script/errors/warnings#os8009).

### Paint the bars

`background()` and `barColor()` answer "which bars" faster than any plot, because you read them without looking at a scale. The absent-value line at the top of this page is one use. The same trick shows which bars a branch ran on. Here it marks the bars of the NSE opening range, 09:15 to 09:30:

```openscript
var rangeHigh = none
forming = session.isIn("0915-0930")
// The range's first bar: inside the window, and the bar before was not.
starts = forming and not orElse(forming[1], false)

// Declared before the if, so the background call below can read it.
taken = false
if forming
    rangeHigh = starts ? high : max(orElse(rangeHigh, high), high)
    taken = true

background(taken ? fade(aqua, 90) : none)
plot(rangeHigh, "Opening range high", aqua, style = "step")
```

`taken` is declared before the `if` on purpose. A name first assigned inside a block belongs to that block, so assigning it only inside would put it out of reach of the `background` call ([OS2001](/script/errors/names-and-types#os2001)). See [Variables and scope](/script/language/variables-and-scope).

### Write a debug panel

A table is the closest thing to a variable inspector. Declare it at the top level, because the pane reserves room for it before bar 0. Write the cells on the newest bar (`bar.isLast`): the chart shows the cells the newest bar wrote, so cells written only on an older bar never appear, and writing them on every bar is wasted work.

```openscript
version 1

study("Bands, with a debug panel", overlay = true, precision = 2)

length = input(20, "Length", min = 2, max = 500)
widthDev = input(2.0, "Band width", min = 0.1, max = 10)
showPanel = input(true, "Show the debug panel")

basis = sma(close, length)
dev = widthDev * stdev(close, length)
upper = basis + dev

// Declared at the top level. A table inside an if is OS3006, for the same
// reason a plot is: the grid is part of the study's fixed shape.
panel = table("Debug", 6, 2, position = "topRight",
        textColor = silver, bgColor = fade(black, 25))

// One place that decides what an absent value looks like. A blank cell hides
// that the study is warming up, and a zero invents a number.
fn show(value, decimals) => isNone(value) ? "absent" : text(value, decimals)

if showPanel and bar.isLast
    cell(panel, 0, 0, "bar.index")
    cell(panel, 0, 1, text(bar.index, 0))
    cell(panel, 1, 0, "bar.updates")
    cell(panel, 1, 1, text(bar.updates, 0))
    cell(panel, 2, 0, "confirmed")
    cell(panel, 2, 1, bar.isConfirmed ? "yes" : "no")
    cell(panel, 3, 0, "basis")
    cell(panel, 3, 1, show(basis, 4))
    cell(panel, 4, 0, "dev")
    cell(panel, 4, 1, show(dev, 4))
    cell(panel, 5, 0, "upper")
    cell(panel, 5, 1, show(upper, 4))

plot(basis, "Basis", orange, width = 2)
```

`bar.updates` (how many times the newest bar has run) and `bar.isConfirmed` (whether the bar has closed) are in that panel on purpose. During market hours the newest bar is still forming and runs again on every update, and those two facts explain most reports of "it worked in the backtest". See [Realtime and confirmation](/script/language/realtime-and-confirmation).

> **The /trading chart draws the first table a study declares. If your study already has a table, add the debug rows to it rather than declaring a second one.**

## Step through a script

Stepping through a per-bar script means one of three things, and they are worth keeping apart. All three use labels, because a label stays on the bar it was drawn on and you can read it on the chart.

**Stepping through the bars.** Pick a short window of bars, shade it, and label each bar in it with the values you care about. Widen or move the window until you see the bar where a value changes in a way you did not expect. This is the real equivalent of a stepper.

```openscript
fromBar = input(240, "Trace from bar", min = 0)
toBar = input(245, "Trace to bar", min = 0)

basis = sma(close, 20)
dev = 2 * stdev(close, 20)

fn show(value, decimals) => isNone(value) ? "absent" : text(value, decimals)

inWindow = bar.index >= fromBar and bar.index <= toBar
if inWindow
    draw.label(time, high,
            text(bar.index, 0) + ": basis " + show(basis, 2) + ", dev " + show(dev, 2),
            color = fade(black, 20))

background(inWindow ? fade(aqua, 90) : none)
plot(basis, "Basis")
```

**Stepping through the lines of one bar.** Put the intermediate values on one chosen bar, numbered in source order. Execution is strictly top to bottom with no callbacks, so a numbered list of intermediates in source order is a complete record of that bar.

```openscript
watchBar = input(-1, "Label this bar index, -1 for none")

basis = sma(close, 20)
dev = 2 * stdev(close, 20)
upper = basis + dev

fn show(value, decimals) => isNone(value) ? "absent" : text(value, decimals)

if bar.index == watchBar
    draw.label(time, high,
            "1 close " + show(close, 2) + " | 2 basis " + show(basis, 4) +
            " | 3 dev " + show(dev, 4) + " | 4 upper " + show(upper, 4) +
            " | 5 previous upper " + show(upper[1], 4),
            color = fade(black, 20))

plot(upper, "Upper")
```

**Watching the state carried between bars.** Show a `var` before and after the block that updates it. Reading a `var` before it is reassigned gives the value the previous bar left, which is exactly what you want to see. See [Persistence](/script/language/persistence).

```openscript
fromBar = input(240, "Trace from bar", min = 0)
toBar = input(245, "Trace to bar", min = 0)

long = close > ema(close, 50)
lo = lowest(low, 10)

var stop = none
before = stop  // what the previous bar left
if long
    stop = max(orElse(stop, lo), lo)

fn show(value) => isNone(value) ? "absent" : text(value, 2)

if bar.index >= fromBar and bar.index <= toBar
    draw.label(time, low, "stop in " + show(before) + ", out " + show(stop),
            color = fade(black, 20))

plot(stop, "Trailing stop", red, style = "step")
```

A label is anchored to a time and a price, so it stays on its bar when more history loads and every bar index shifts. It also lasts until the script deletes it, so a condition that is true on a thousand bars leaves a thousand labels. Keep the window small, or call `draw.deleteAll()` at the top of the bar while you experiment. `draw.count()` tells you how many objects the script holds. See [Lines and boxes](/script/visuals/lines-and-boxes).

## Break on a condition

There is no breakpoint, so a break becomes a guard around a dump. State the condition as a bool at the top level, then hang the output off it.

```openscript
jumpPct = input(0.5, "Report a one bar move larger than this, in percent", min = 0)

basis = sma(close, 20)

// As a percentage, so one setting works on a stock near 500 and on NIFTY near 25000.
moved = not isNone(basis) and not isNone(basis[1]) and abs(basis - basis[1]) / basis[1] * 100 > jumpPct

if moved
    draw.label(time, high,
            "jump from " + text(basis[1], 2) + " to " + text(basis, 2) +
            ", close " + text(close, 2),
            color = fade(red, 20))

background(moved ? fade(red, 70) : none)
plot(basis, "Basis")
```

Note both `isNone()` tests. Without them the comparison is absent during warmup, an absent condition takes the false branch, and the break never fires on the bars most likely to hold the problem.

> **A guard written without thinking about absence fails exactly where you need it.**

## Find the first bar that goes wrong

This is the method the rest of the page serves.

### The first-offender pattern

Keep one persistent value. Record the first bar where your value disagrees with one you trust, and mark it once. Everything after that bar is downstream.

```openscript
version 1

study("Where does it first disagree", precision = 6)

tolerance = input(0.000001, "Tolerance", min = 0)

mine = myCalculation(close, 20)
reference = sma(close, 20)

// Both must be present before a comparison means anything. With one absent the
// comparison is absent, the branch is skipped, and the first real disagreement
// after warmup would be missed or misreported.
comparable = not isNone(mine) and not isNone(reference)
disagrees = comparable and abs(mine - reference) > tolerance

// A time rather than a bar index: loading more history renumbers every bar,
// and a bar's time never moves.
var firstBadTime = none

if disagrees and isNone(firstBadTime)
    firstBadTime = time
    // Drawn once, on the first disagreement only.
    draw.label(time, high,
            "first disagreement, bar " + text(bar.index, 0) +
            ": mine " + text(mine, 8) + ", reference " + text(reference, 8),
            color = fade(red, 20))

panel = table("First offender", 1, 2, position = "topRight", textColor = silver)
if bar.isLast
    cell(panel, 0, 0, "first disagreement")
    cell(panel, 0, 1, isNone(firstBadTime) ? "none" : date.format(firstBadTime, "yyyy-MM-dd HH:mm"))

plot(mine, "Mine", aqua)
plot(reference, "Reference", orange)
plot(comparable ? mine - reference : none, "Difference", fuchsia, scale = "left")

fn myCalculation(src, len) =>
    total = 0.0
    for i = 0 to len - 1
        total += src[i]
    total / len
```

The table tells you whether there is a disagreement at all and when, and the label sits on the bar itself. The bar index goes into the label, because within one run it is the number you type into a `watchBar` input, but it is not kept, for the reason in the comment.

### Bisect the script

When the first-offender pattern tells you the bar but not the line, halve the script instead of staring at it.

1. Take the wrong output and the bar where it first goes wrong.
2. Plot or label the value halfway up the chain of lines that feed it, on that bar.
3. If the halfway value is right, the bug is below it. If it is wrong, the bug is above it. Repeat.

Four rounds cover a chain of sixteen lines, which is longer than most scripts have. It beats reading, because reading finds the bugs you can imagine and bisection finds the one that is actually there.

### Bisect the inputs

If the value is wrong for one setting and right for another, the shortest path is often the input rather than the code. Set the length to 2, or to 1 if the function allows it, and work the arithmetic out by hand. Most off-by-one bugs in a lookback are visible at length 2 and invisible at length 20.

## Six bugs that look like other bugs

| Symptom | Usual cause | Confirm it by |
|---|---|---|
| The line never draws | The plotted value is absent on every bar ([OS8009](/script/errors/warnings#os8009) when the compiler can see it), or a name never received what it was meant to hold | `background(isNone(value) ? fade(red, 85) : none)` |
| The line has a hole in the middle | One absent bar spread through the arithmetic, or a stateful call sits inside a branch that did not run ([OS8001](/script/errors/warnings#os8001)) | Label the inputs on the first bar of the hole |
| The line is flat near the left edge, then correct | Warmup hidden by an `orElse(x, 0)` that draws a confident zero | Remove the `orElse` and look for the gap |
| A counter stays absent for ever | A plain `x = 0` followed by `x = x[1] + 1`: `x[1]` is absent on bar 0 and absence spreads to every bar after | Replace it with `var x = 0` and `x = x + 1` |
| Right in the backtest, different during the session | The forming bar: a `live var`, `onUnconfirmed = true`, or a higher timeframe read in `"developing"` or `"lookahead"` mode | Put `bar.updates` and `bar.isConfirmed` in a debug panel, and search the file for `mode =` |
| The numbers change when more history loads | A stored `bar.index`, or a value that depends on where the chart's history starts, such as `cum()` or a `var` counter started on bar 0 | Search the file for `bar.index` in a `var`, and anchor running values to a session or a date |

The [Troubleshooting](/script/writing/troubleshooting) page lists many more symptoms with their fixes, and [Repainting](/script/data/repainting) covers values that change after the fact.

## Read the diagnostics

Every problem the compiler finds carries a stable code, a line, a column, a message and a fix. In the Scripts panel on the /trading page, every save (Ctrl+S) compiles the script, and the console under the editor lists each diagnostic: its code, its line and column, the line of source with the spot underlined, the message and the fix. The console button in the status bar at the bottom of the panel opens it and shows how many there are. When you get a code, look it up in the [error reference](/script/errors/overview): each entry explains the cause and shows a before and after pair that is often your exact situation in four lines.


Warnings deserve the same attention. [OS8001](/script/errors/warnings#os8001), [OS8010](/script/errors/warnings#os8010), [OS8012](/script/errors/warnings#os8012) and [OS8015](/script/errors/warnings#os8015) each describe a shape that is nearly always a bug, and clearing one is cheaper than the afternoon of debugging it would otherwise cost.

A problem found while the script runs, such as a loop that runs out of budget or an array read past its end, is not in the console, because the console reports what the compiler found when you saved. The study stops on that bar instead. On the /trading page, the Objects panel marks a stopped study with Error, and if it stops as you add it to the chart, a notification shows the code, the message and the fix. See [Limits](/script/writing/limits) for the codes you meet this way.

## Print and the log

`print()` writes one line to the script's log, with the bar's time attached. The language defines it so that a host can show a trace over many bars. The /trading page does not show the log in this release, so on that page use the labels and tables above. When you run a script where the log is shown, three rules apply.

**Build the string with `text()`.** `+` joins two strings and does nothing else, so adding a number to a string is [OS2003](/script/errors/names-and-types#os2003):

```openscript
r = rsi(close, 14)
print("rsi " + r)
```

Convert the number first. There are two forms, and the difference matters during warmup. `text(x)` with no decimals accepts any value and writes an absent one as the word `none`. `text(x, decimals)` is for a number you know is present: given an absent value it returns an absent string, and a string joined to an absent value is absent, so the whole line would be lost.

```openscript
r = rsi(close, 14)

// text(x) with no decimals accepts any value and writes an absent one as
// the word none, so the line is never lost to absence.
print("bar " + text(bar.index, 0) + " rsi " + text(r) + " close " + text(close, 2))
```

**Guard it.** An unguarded `print` writes one line per bar. A host limits how fast the log may fill and says how many lines it dropped, but a trace you have to scroll through is barely better than none. Print a window of bars:

```openscript
fromBar = input(240, "Trace from bar", min = 0)
toBar = input(260, "Trace to bar", min = 0)

basis = sma(close, 20)
dev = 2 * stdev(close, 20)

if bar.index >= fromBar and bar.index <= toBar
    print("bar " + text(bar.index, 0) +
            " time " + date.format(time, "yyyy-MM-dd HH:mm") +
            " close " + text(close, 2) +
            " basis " + text(basis) +
            " dev " + text(dev))

plot(basis, "Basis")
```

**Know when the line is written.** Like `signal()`, `alert()` and orders, `print` waits for the bar to be confirmed. On a forming bar the script runs again on every update, and only the bar's final run writes its line, so you get one line per bar, not one per update. A script that sets `onUnconfirmed = true` in its declaration opts out of that wait, and then every run of the forming bar writes a line. Add `and bar.isConfirmed` to the guard if you want one line per bar in that case.

## The debugging loop

1. **Hold the data still.** Debug on bars that do not change under you: outside market hours, or in a backtest over a fixed date range in the Backtest panel. A moving dataset turns a reproducible bug into a mystery.
2. **Reproduce it once.** Know the symptom precisely: which plot, which bar, what value you expected.
3. **Find the first wrong bar** with the first-offender pattern.
4. **Show the state of that bar** with a label or a debug panel.
5. **Form one hypothesis** and change one thing.
6. **Run it again and compare.** If the change did nothing, put it back before trying the next one. Two guesses at once is how one small bug becomes two.
7. **Write the check that would have caught it**, while you still remember what it was. See [Testing scripts](/script/writing/testing).

**Related.** [Testing scripts](/script/writing/testing), [Troubleshooting](/script/writing/troubleshooting), [Profiling and speed](/script/writing/profiling), [Limits](/script/writing/limits), [Style guide](/script/writing/style-guide), [Warmup](/script/language/warmup), [Absent values](/script/language/absent-values)


## Limits

Source: https://openalgo.in/script/writing/limits

This page lists every limit an OpenScript script (OpenScript is also called OpenAlgo Script) runs inside: how many loop turns a bar may take, how large an array or a string may grow, how many drawings a script may hold, and the rest. For each one it gives the value, says whether your script can change it, and explains what to do when you reach it. You need it when a study stops with an OS5xxx error, and before you write a script that loops heavily or keeps a lot of state.

## Every limit at a glance

The values below are the ones the engine in `openalgo-script` 0.5.0 applies when the host sets nothing else, which is how scripts run on the /trading page. Two of them, the loop budget and the array ceiling, are fixed by the language itself. The others are the engine's defaults, and a host is allowed to set its own.

| Limit | Value | Can the script change it | Checked | Reported as |
|---|---|---|---|---|
| Loop turns per bar, all loops together | 2,000,000 | Yes, with `limits(loops = n)` | While the bar runs | [OS5001](/script/errors/limits#os5001) |
| Retained history depth | Every bar on the chart | Yes, with `limits(history = n)` | While the bar runs | [OS4002](/script/errors/runtime#os4002) |
| Elements in one array | 1,000,000 | No | While the bar runs | [OS5002](/script/errors/limits#os5002) |
| Characters in one string | 100,000 | No | While the bar runs | [OS5008](/script/errors/limits#os5008) |
| Drawing objects held at once | 10,000 | No | When an object is created | [OS5010](/script/errors/limits#os5010) |
| Nesting of expressions, and of blocks, in the source | 128 levels | No | When the file compiles | [OS5005](/script/errors/limits#os5005) |
| Depth of nested function calls | 64 | No | When the program loads | [OS5005](/script/errors/limits#os5005) |
| Data requests (`req.timeframe()` and `req.symbol()` reads) | No ceiling unless the host sets one | No | When the program loads | [OS5006](/script/errors/limits#os5006) |
| Instructions in the compiled program | No ceiling unless the host sets one | No | When the program compiles or loads | [OS5009](/script/errors/limits#os5009) |
| State regions (the stored state of stateful calls) | No ceiling unless the host sets one | No | When the program compiles or loads | [OS5004](/script/errors/limits#os5004) |
| Time per bar | No clock unless the host sets one | No | While the bar runs | [OS5007](/script/errors/limits#os5007) |
| Largest value `limits()` may ask for | No ceiling unless the host sets one | No | When the program loads | [OS5003](/script/errors/limits#os5003) |
| Lines written by `print()` | The host's rate limit. The /trading page does not show the log in this release | No | As lines are written | The host says how many lines it dropped |
| Bars the script runs over | The host's. The /trading Backtest panel runs up to 100,000 | No | Before the run | The Backtest panel asks for a shorter range |

Each row has its own section below. The [OS5xxx error page](/script/errors/limits) carries the exact message, the placeholders it is filled with and a before and after example for every code.

## Two rules behind every limit

A limit in OpenScript is a number you can see, with a reason you can read and, where it makes sense, a line you can write to change it. Two rules follow, and they hold for every row above.

- **A limit that is exceeded is reported, never absorbed.** A loop that runs out of budget stops the bar and says so. It does not break out of the loop and carry on, because a loop that ran two million times and then quietly stopped produces a plausible wrong number, and a plausible wrong number is worse than no number.
- **A host that will not spend what a script asks for says so.** It refuses when the program loads, with [OS5003](/script/errors/limits#os5003), naming the option, the value asked for and the most it allows. It never caps the value silently, because a script that ran under a smaller budget than it asked for would produce numbers its author never asked for and could not reproduce.

When a bar stops on a limit, the study stops there. On the /trading page, the Objects panel marks the study with Error, and if it stops as you add it to the chart, a notification shows the code, the message and the fix.

## The limits() line

Two limits belong to the script, and both are set in one place: a `limits()` line straight after the declaration.

```openscript
version 1

study("Close clustering", precision = 0)
limits(loops = 4_000_000)  // 2,500 closes compared pairwise is about 3.1 million turns

windowLen = input(2500, "Closes compared", min = 2, max = 2500)
bandTicks = input(4, "Band, in ticks", min = 1, max = 100)

var closes: array<number> = []
push(closes, close)
if size(closes) > windowLen
    shift(closes)

band = bandTicks * orElse(chart.tickSize, 0.05)
closeCount = size(closes)

// The pairwise scan runs on the newest bar only: its answer describes the
// present, and running it on every bar would repeat the same work thousands
// of times.
pairs = 0
if bar.isLast
    for i = 0 to closeCount - 1
        for j = i + 1 to closeCount - 1
            if abs(element(closes, i) - element(closes, j)) <= band
                pairs += 1

plot(bar.isLast ? pairs : none, "Close pairs inside the band")
```

Without the `limits()` line, that scan stops on the newest bar with OS5001 on any chart holding more than about 2,000 bars, because 2,500 closes compared pairwise take about 3.1 million loop turns. With it, the bar completes. The comment says why the budget is raised, which is the part a reader needs.

The rules are short, and the compiler enforces them:

| Rule | Code when broken | Why |
|---|---|---|
| `limits()` is optional | None | Most scripts never need it: none of the example scripts does |
| It appears at most once | [OS3014](/script/errors/arguments#os3014) | Two would need a rule for which one wins |
| It is the statement straight after `study()` or `strategy()` | [OS3014](/script/errors/arguments#os3014) | A reader sees a raised budget without searching, and the engine knows it before bar 0 |
| Its values are literal whole numbers | [OS3015](/script/errors/arguments#os3015) | A budget computed from data, or set from an `input()`, cannot be known before the run starts |
| Its options are `loops` and `history` | [OS3002](/script/errors/arguments#os3002) | Nothing else is the script's to set |

```openscript
version 1

study("Heavy")

len = input(20, "Length")
limits(loops = 5_000_000)
plot(sma(close, len), "Average")
```

```openscript
limits(loops = 5_000 * 1_000)
```

Underscores in a number are allowed and make large budgets readable: `4_000_000` is four million.

## The loop budget

**Every turn of every loop, added up over all the loops that run during one bar, counts against one per-bar budget. The default is 2,000,000.**

Going over it is [OS5001](/script/errors/limits#os5001). The message names the budget in force and the line of the loop that was running when it ran out, and its fix suggests a budget twice the current one, rounded up (4,000,000 for the default). The bar stops, and the study stops with it.

Three details are deliberate:

- **Per bar, not per loop.** A script with one nested loop is treated the same as a script with ten loops one after another. A per-loop budget would let a script with twenty loops do twenty times the work of a script with one.
- **Reset every bar.** A long chart is never, by itself, a reason to fail.
- **Stop rather than break out.** See the two rules above.

The budget exists because a script runs inside a chart in your browser, and a loop whose exit condition is never met would freeze the page. The most common way to reach it is not a big loop but a `while` with no bound:

```openscript
i = 0
total = 0.0

// Before: nothing in the body changes i, so while the condition is true this
// never ends, and the bar stops with OS5001.
while close[i] > close[i + 1]
    total += close[i]

plot(total, "Total")
```

```openscript
i = 0
total = 0.0

// After: a bound and an increment. The cap is a decision you can defend, and
// the loop cannot spin.
while i < 500 and close[i] > close[i + 1]
    total += close[i]
    i += 1

plot(total, "Sum of the latest rising run")
```

When you reach it, ask which of two things is true. Either the loop has a bug, and you fix the exit condition, or the script genuinely needs more, and you raise the budget in one line with a comment saying why. Before raising it, read [Profiling and speed](/script/writing/profiling): a script that needs millions of turns on every bar is usually rebuilding from scratch something it could carry forward, and the rewrite is both faster and shorter than a raised budget.

## The retained history depth

`x[n]` reads the value of `x` as it stood `n` bars ago. By default the engine keeps the whole history of every series it needs, so any depth works and [OS4002](/script/errors/runtime#os4002) never appears. See [Bars and history](/script/language/bars-and-history).

`limits(history = n)` tells the engine to keep only the last `n` bars of history, which bounds memory on a very long chart. Reads up to `x[n]` then work, and a read deeper than that is OS4002. That is different from reading before the chart begins, and the difference is the point of the error:

- `x[n]` where `n` is greater than `bar.index` is **absent**. The value never existed, so absence is the truthful answer.
- `x[n]` deeper than the retained depth is an **error**. The value existed and the engine threw it away. Treating that as absent would hide a real bug behind a plausible gap.

```openscript
version 1

study("A week ago")
limits(history = 375)  // the deepest read below is [375]

// 75 five minute bars per NSE session, 09:15 to 15:30, so 375 bars is a week.
weekAgo = close[375]
plot(close - weekAgo, "Change over 375 bars", aqua)
```

The error's fix suggests a depth equal to the index that failed. If the script reads deeper somewhere else, that read fails next, so set `history` to the deepest index the whole script reads rather than to the number in the first message.

An index that is not a whole number, or is negative, is a different error, [OS4001](/script/errors/runtime#os4001), raised on the bar that reads it: there is no half a bar ago, and reading the future is not available at any price.

## The array ceiling

**An array holds at most 1,000,000 elements.** Going over it is [OS5002](/script/errors/limits#os5002), naming the array and the size it reached. `limits()` does not raise it in version 1, because an array that large is almost always a window that is never trimmed rather than a real need.

Decide how much of the past you need and drop the rest as you go:

```openscript
var window: array<number> = []

push(window, close)
if size(window) > 500
    shift(window)

plot(avg(window), "Mean of the last 500 closes", aqua)
```

A neighbouring error has the same root: an index outside an array is [OS4004](/script/errors/runtime#os4004), an error rather than absence, because an array has an extent your script chose. See [Collections](/script/language/collections).

## The string ceiling

**A string holds at most 100,000 characters.** Going over it is [OS5008](/script/errors/limits#os5008), with the length it reached in the message. The check happens before the string is built, so `str.repeat()` asked for a huge result stops cleanly rather than exhausting memory.

The usual way to reach it is text accumulated into a persistent string, a piece per bar, that nothing ever trims.

```openscript
// Before: grows by one piece on every bar, for ever, and stops with OS5008
// once the chart holds enough bars.
var closesText = ""
closesText += text(close, 2) + ", "

panel = table("Latest closes", 1, 1, position = "bottomLeft")
if bar.isLast
    cell(panel, 0, 0, closesText)
```

```openscript
// After: keep the pieces, trim to what you show, join only those.
var closePieces: array<string> = []
push(closePieces, text(close, 2))
if size(closePieces) > 10
    shift(closePieces)

panel = table("Latest closes", 1, 1, position = "bottomLeft")
if bar.isLast
    cell(panel, 0, 0, str.join(closePieces, ", "))
```

## Drawing objects held at once

**A script holds at most 10,000 drawing objects at once.** A drawing lasts until the script deletes it. When a script tries to create one more than the ceiling, the bar stops with [OS5010](/script/errors/limits#os5010), naming the number it would have reached. The oldest drawing is never dropped to make room: a study that is right on the right of the chart and quietly wrong on the left is worse than one that stops.

The fix is to delete what you no longer want and bound the set:

```openscript
maxZones = input(50, "Zones kept", min = 1, max = 500)

var zones: array<box> = []

breakout = crossUp(close, highest(high, 20)[1])
if breakout
    push(zones, draw.box(time, high, time + 3600000, low))

// Keep the newest maxZones boxes: delete the oldest drawing and remove its
// element together, so the array and the chart never disagree.
if size(zones) > maxZones
    draw.delete(element(zones, 0))
    shift(zones)

plot(draw.count(), "Objects held")
```

`draw.count()` reports how many objects the script holds. Put it in a debug panel while you develop: a count that rises for ever is a leak, and you see it in a minute rather than in an hour. See [Lines and boxes](/script/visuals/lines-and-boxes).


## Nesting and call depth

The source may nest expressions, and separately blocks, **128 levels** deep. Going deeper is [OS5005](/script/errors/limits#os5005) when the file compiles. The ceiling keeps the compiler inside a bounded stack so that no file can stop the page, and it is far above anything a person writes by hand: generated source is the usual way to reach it.

Function calls may nest **64 deep** at run time, a function calling a function calling a function. A program that would go deeper is refused with the same code when it loads. Recursion, a function calling itself, is not allowed at all ([OS2005](/script/errors/names-and-types#os2005)), so the depth of any program is known before the first bar.

If you meet either, the fix is also the readable change: give the inner part a name. The same habit helps long before any ceiling, as in this nesting of four levels.

```openscript
a = close > open
b = volume > 0
c = high > high[1]
d = low < low[1]

// Before: legal, and hard to follow.
code = a ? b ? c ? d ? 1 : 2 : 3 : 4 : 5
plot(code, "Bar code")
```

```openscript
a = close > open
b = volume > 0
c = high > high[1]
d = low < low[1]

// After: the same value, with the inner choice named.
inner = c ? (d ? 1 : 2) : 3
code = a ? (b ? inner : 4) : 5
plot(code, "Bar code")
```

## Data requests

Each `req.timeframe()` or `req.symbol()` read is a separate series kept in step with the chart's bars: a `req.timeframe()` read of the chart's own instrument is built from the chart's bars, and a `req.symbol()` read is fetched by the host. A host may set a ceiling on how many reads one file makes; the /trading page sets none. A file with more is refused with [OS5006](/script/errors/limits#os5006) when it loads, naming the count and the ceiling. It is refused rather than having the extra requests dropped, because a dropped request is a plot that quietly turns absent. A read written inside another read counts as one more request.

The fix is nearly always to stop asking twice for the same thing. Two reads with the same arguments are still two requests, so read once, name the result and reuse the name.

```openscript
// Before: the same read written twice is two requests.
plot(req.timeframe("1D", high), "Previous day high", orange, style = "step")
brokeOut = crossUp(close, req.timeframe("1D", high))
background(brokeOut ? fade(orange, 80) : none)
```

```openscript
// After: one request, named, and the name used twice.
prevDayHigh = req.timeframe("1D", high)  // the last completed day's high
plot(prevDayHigh, "Previous day high", orange, style = "step")
brokeOut = crossUp(close, prevDayHigh)
background(brokeOut ? fade(orange, 80) : none)
```

Delete reads whose results you do not use: an unused read still costs a whole series, and the unread name earns warning [OS8010](/script/errors/warnings#os8010). See [Higher timeframes](/script/data/higher-timeframes) and [Other instruments](/script/data/other-instruments).

## Program size and state regions

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 [OS5009](/script/errors/limits#os5009). You will not reach it by writing a study by hand. A file that size is nearly always repeated blocks that a function would collapse, or generated source.

A host may also cap the number of **state regions**, the stored state of every stateful call such as `ema()`, counted per call path. The count multiplies when several functions each call the next more than once, and a program over the cap is [OS5004](/script/errors/limits#os5004), naming the two functions where the multiplication happened.

```openscript
fn inner(src) => ema(src, 20)
fn outer(src) => inner(src) - inner(src[1])

// Before: two calls of outer, each calling inner twice, so four separate
// averages are kept and updated on every bar.
v = outer(close) + outer(hlc3)
plot(v, "Four averages")
```

```openscript
fn inner(src) => ema(src, 20)

// After: one call per source at the top level, and the previous bar's value
// read from the name's history. Two averages, and the same numbers once
// both have warmed up.
closeAvg = inner(close)
typicalAvg = inner(hlc3)
v = (closeAvg - closeAvg[1]) + (typicalAvg - typicalAvg[1])
plot(v, "Two averages")
```

## Time per bar

The loop budget counts turns, not time, so a script can be well inside it and still be slow. A host running many strategies can give each bar a wall clock budget so that one script cannot starve the rest. A bar that takes longer is [OS5007](/script/errors/limits#os5007), naming the bar, the time it took and the budget. There is no `limits()` option for it. The engine as shipped reads no clock at all unless the host asks it to, because a default time limit would let the same script pass on a fast machine and fail on a slow one.

The usual cause is work recomputed over the whole history on every bar:

```openscript
// Before: on bar 40,000 this loop runs 40,001 times.
total = 0.0
for i = 0 to bar.index
    total += close[i]

plot(total / bar.count, "Mean since the first bar")
```

```openscript
// After: one addition per bar. The mean is the same apart from rounding in
// the last digits, because the closes are added in a different order.
var total = 0.0
total += close

plot(total / bar.count, "Mean since the first bar")
```

`cum()` is this running total ready made. [Profiling and speed](/script/writing/profiling) covers the pattern in full.

## How many bars you get

The number of bars a script sees is the host's decision, not the language's. On the /trading page a study runs over the bars the chart has loaded, and the Backtest panel runs over the date range you pick, up to 100,000 bars. There is no setting in the script for how far back it may look and no bar at which a script starts for real: a script runs on bar 0 exactly as it runs on bar 40,000, and warmup is expressed entirely through the absent value. See [Warmup](/script/language/warmup).

Two things follow in practice.

- **`bar.index` is a position in the bars the engine was given, not an address.** Loading more history shifts every index, so a stored index points at something that moved. Store `time` instead: a bar's time does not move.
- **How much history you get is a question about the data, not the script.** If a study needs 200 bars of warmup and the chart holds 300, most of the chart is warmup. Say in the script's header how much history it needs. See [Sharing scripts](/script/writing/sharing-scripts).

## When you reach a limit

| You see | First ask | Then do |
|---|---|---|
| [OS5001](/script/errors/limits#os5001) | Does this loop end on every bar? | Fix the exit condition; if it is right, raise `loops` and comment why |
| [OS5007](/script/errors/limits#os5007) | Does any loop's length depend on `bar.index`? | Carry the value forward in a `var`, or use `cum()` |
| [OS4002](/script/errors/runtime#os4002) | Is the deep read intended? | Set `limits(history = n)` to the depth the message suggests |
| [OS5002](/script/errors/limits#os5002) | Is this array a window or a log? | Trim on push; a window needs a fixed length |
| [OS5010](/script/errors/limits#os5010) | Does every object I draw ever get deleted? | Delete the oldest as you create the newest, and remove its array element with it |
| [OS5008](/script/errors/limits#os5008) | Am I building text on every bar for output shown once? | Keep the pieces in an array, trim it, and join on `bar.isLast` |
| [OS5009](/script/errors/limits#os5009) | Are there repeated blocks? | Extract a function |
| [OS5004](/script/errors/limits#os5004) | Does a function call another more than once on several paths? | Call the inner one once at the top level and pass the name down |
| [OS5005](/script/errors/limits#os5005) | Is this expression readable? | Name the inner part |
| [OS5006](/script/errors/limits#os5006) | Is any request a duplicate? | Read once, reuse the name, delete unused reads |
| [OS5003](/script/errors/limits#os5003) | Do I really need this budget? | Lower it, or run the file where the host allows more |

**Related.** [Profiling and speed](/script/writing/profiling), [Debugging](/script/writing/debugging), [Testing scripts](/script/writing/testing), [Style guide](/script/writing/style-guide), [OS5xxx Limits errors](/script/errors/limits), [Bars and history](/script/language/bars-and-history)


## Profiling and speed

Source: https://openalgo.in/script/writing/profiling

This page shows how to find which part of a slow OpenScript script (OpenScript is also called OpenAlgo Script) is costing the time, how to measure it rather than guess, and how to fix it. You need it when a study takes noticeably long to draw on a chart with a long history, when a backtest over a year of 5 minute bars crawls, or when a bar stops with a budget error from the [Limits](/script/writing/limits) page.

## The cost model

A script is the body of a loop that runs once per bar. Every top-level line runs on every bar, so the total cost of a script is its cost per bar multiplied by the number of bars. A study that takes a tenth of a millisecond per bar takes five seconds over 50,000 bars, which is more than two and a half years of 5 minute NIFTY bars. There is no part of a script that runs once and is free, and there is nothing you can move "outside the loop", because there is no outside. See the [execution model](/script/language/execution-model).

Two consequences follow, and most speed work is one of them.

- **A line whose cost grows with `bar.index` turns a linear script into a quadratic one.** 50,000 bars each scanning up to 50,000 bars is over a billion operations.
- **A line that recomputes from scratch what it could carry forward pays the whole cost again on every bar**, when the honest cost of the update was one addition.

## What costs what

This is a model, not a benchmark. Engines differ in raw speed and must not differ in results, so trust the order and treat the ratios as rough.

| Work | Cost per bar | Notes |
|---|---|---|
| Reading a bar field (`close`, `high`, `time`) | Lowest | The engine fills these before the bar's code runs |
| Arithmetic, a comparison, a ternary | Very low | Plain operations on numbers |
| Reading history, `close[5]` | Very low | A direct read, not a search |
| A smoothing library call (`ema()`, `rma()`, `atr()`) | Low and constant | Each carries its previous value forward, so the length does not change the cost per bar |
| A windowed library call (`sma()`, `highest()`, `stdev()`) | Grows with the length | The window is read afresh on every bar, so `sma(close, 200)` adds 200 values per bar. That keeps the value exact, and the work is done inside the engine rather than as turns of your own loop |
| A user function call | Low | One frame, and one set of stored state per call site |
| A `for` loop of `n` turns | `n` times the body | The body's cost is what matters; the loop itself is cheap |
| Building a string | Moderate | Joining allocates, and doing it on every bar for text shown once is waste |
| Creating or changing a drawing | Moderate, and it lasts | The object lives until you delete it |
| A higher timeframe or other instrument read | Paid per read | A whole second series is built or fetched and kept in step with the chart |
| A loop whose length grows with `bar.index` | Ruinous | The quadratic case, which this page mostly exists for |

## Measure the work

**A script cannot time itself, and that is deliberate.** `chart.now()` is the chart's clock as the host supplies it, and it is the only clock a script can read during a bar. It is fixed for reproducibility, so the same script over the same bars gives the same result every time, and a value fixed for reproducibility is not a stopwatch. There is no random source either, for the same reason.

So you measure **work** from inside the script and **time** from outside it. They answer different questions.

| Measurement | How | Tells you |
|---|---|---|
| Loop turns per bar | Count them into a `var`, show the count in a table on the last bar | Whether a loop is the problem, and by how much |
| Worst bar | Keep a running maximum of the per-bar count | Whether the cost is spread out or concentrated |
| Calls to a helper | Count entries the same way | Whether a function runs more often than you thought |
| Drawings held | `draw.count()` in a debug panel | Whether drawings pile up instead of being deleted |
| Data requests | Count the `req.` lines by eye | Whether you are near a host's request ceiling ([OS5006](/script/errors/limits#os5006)) |
| Time for the whole run | How long the chart takes to draw, or the backtest to finish | Whether the total is acceptable at all |

### Count the loop turns

This measurement settles most arguments. It costs a few lines and turns "the loop is probably fine" into a number.

```openscript
version 1

study("Loop turn counter", precision = 0)

length = input(20, "Window", min = 2, max = 500)

var totalTurns = 0
var worstBar = 0

perBar = 0
total = 0.0

for i = 0 to length - 1
    perBar += 1
    total += close[i]

totalTurns += perBar
if perBar > worstBar
    worstBar = perBar

panel = table("Loop turns", 3, 2, position = "topRight", textColor = silver)
if bar.isLast
    cell(panel, 0, 0, "turns in total")
    cell(panel, 0, 1, text(totalTurns, 0))
    cell(panel, 1, 0, "worst single bar")
    cell(panel, 1, 1, text(worstBar, 0))
    cell(panel, 2, 0, "bars")
    cell(panel, 2, 1, text(bar.count, 0))

plot(total / length, "Mean", aqua)
```

Compare the worst bar with the loop budget of 2,000,000 turns per bar. A worst bar in the thousands is fine. A worst bar in the hundreds of thousands is doing something structurally wrong even though it has hit no limit, and the next section is probably why.

Know what the budget does and does not protect you from. It stops a runaway loop from freezing the page. It does not stop a script from being slow: a loop that runs 50,000 turns on every bar is well inside the budget and is still 50,000 times more work than the one addition it should have been.

## The quadratic trap

This is the most common cause of a slow script, and it always looks reasonable on the day it is written.

```openscript
// Before: the mean of every close since the first bar. On bar 40,000 this
// loop runs 40,001 times, and it ran 40,000 times on the bar before.
total = 0.0
for i = 0 to bar.index
    total += close[i]

plot(total / bar.count, "Mean since the first bar", aqua)
```

Over 50,000 bars that is more than a billion turns, and a host that sets a time budget per bar stops it with [OS5007](/script/errors/limits#os5007). The fix is to carry the answer forward instead of rebuilding it:

```openscript
// After: one addition per bar. The mean is the same apart from rounding in
// the last digits, because the closes are added in a different order.
var total = 0.0
total += close

plot(total / bar.count, "Mean since the first bar", aqua)
```

The library has this shape ready made as `cum()`, a running total from the first bar, and using it is better still: a library function has a stated warmup and an exactly specified result, and your own accumulator has neither until you test it.

The general rule: **if a loop's length depends on `bar.index`, the value it computes can almost certainly be written as an update.** Ask what changed since the previous bar. Usually exactly one value arrived and at most one left.

## Rolling windows: add one, drop one

The same idea applies to a fixed window. The saving is smaller, but the shape is worth knowing because it extends to statistics the library does not have.

```openscript
len = input(20, "Length", min = 1, max = 500)

// Before: len additions on every bar.
total = 0.0
for i = 0 to len - 1
    total += close[i]

plot(total / len, "Mean, by loop")
```

```openscript
len = input(20, "Length", min = 1, max = 500)

// After: two operations per bar whatever len is. The value leaving the window
// is close[len], the bar just before the window's oldest bar.
var running = 0.0
running += close
if bar.index >= len
    running -= close[len]

// Absent until the window is full, so the warmup matches sma rather than
// reporting a mean of however many bars have arrived.
mean = bar.index >= len - 1 ? running / len : none
plot(mean, "Mean, by update")
```

Before you write that, check whether the library already has it. `sma()`, `sum()`, `highest()`, `lowest()`, `stdev()`, `median()`, `percentile()`, `correlation()` and the rest are specified with exact warmups and exact arithmetic, and a hand-written copy is one more thing to test. Write a window by hand when the statistic is genuinely not in the [reference](/script/reference/series), not to save a call.

A running total has one accuracy caution that a fresh sum does not: subtracting a value added thousands of bars ago lets small floating point errors build up, so the result drifts from a fresh sum in the last few decimals as the chart grows. For most uses this is not a practical problem, but it is why the library's own windowed functions take the sum fresh over the window on every bar instead: `sma()` pays `len` additions per bar so that its value never drifts.

## Loops inside loops

Nesting multiplies, and the multiplication is easy to underestimate.

| Shape | Turns per bar | Over 50,000 bars |
|---|---|---|
| `for i = 0 to 19` | 20 | 1,000,000 |
| `for i = 0 to 19` inside `for j = 0 to 19` | 400 | 20,000,000 |
| `for i = 0 to 99` inside `for j = 0 to 99` | 10,000 | 500,000,000 |
| `for i = 0 to bar.index` | up to 50,000 | more than 1,000,000,000 |

Three changes fix nearly every nested loop.

**Hoist what does not change**, and **halve the work when the relationship is symmetric.** Anything computed from an input, a `chart.` fact or this bar's values is the same on every turn, so read it once before the loop. A `for` loop reads its bounds once, when it starts, so a bound on the outer loop costs nothing extra, but the inner loop starts again on every outer turn. And comparing every pair once rather than twice turns 400 turns into 190.

```openscript
var levels: array<number> = []
push(levels, close)
if size(levels) > 100
    shift(levels)

// Before: the inner loop reads size(levels) again for every outer turn, the
// tick size is read and defaulted on every inner turn, and every pair is
// compared twice.
nearest = none
for i = 0 to size(levels) - 1
    for j = 0 to size(levels) - 1
        gap = abs(element(levels, i) - element(levels, j)) / orElse(chart.tickSize, 0.05)
        if i != j and (isNone(nearest) or gap < nearest)
            nearest = gap

plot(nearest, "Nearest pair, in ticks")
```

```openscript
var levels: array<number> = []
push(levels, close)
if size(levels) > 100
    shift(levels)

// After: both read once per bar, and each pair visited once.
levelCount = size(levels)
tick = orElse(chart.tickSize, 0.05)
nearest = none
for i = 0 to levelCount - 1
    for j = i + 1 to levelCount - 1
        gap = abs(element(levels, i) - element(levels, j)) / tick
        if isNone(nearest) or gap < nearest
            nearest = gap

plot(nearest, "Nearest pair, in ticks")
```

**Leave early.** `break` leaves the innermost loop and `continue` skips to its next turn. A scan looking for the first match should stop at it.

```openscript
var highs: array<number> = []
push(highs, high)
if size(highs) > 100
    shift(highs)

// Stop at the first match instead of scanning the rest.
found = none
for i = 0 to size(highs) - 1
    if element(highs, i) > close
        found = i
        break

plot(found, "Oldest stored high above the close")
```

## Do not compute the same thing twice

**Every call site keeps its own state.** Two identical calls in two places are two independent sets of state, each updated on every bar. That rule is what makes a stateful helper reusable, and it is also what makes a copied line cost double. See [User functions](/script/language/functions).

```openscript
// Before: three call sites, three MACD calculations on every bar.
plot(macd(close, 12, 26, 9)[0], "MACD", aqua)
plot(macd(close, 12, 26, 9)[1], "Signal", orange)
plot(macd(close, 12, 26, 9)[2], "Histogram", gray, style = "histogram")
```

```openscript
// After: one call site, one calculation, three reads of the array it returns.
m = macd(close, 12, 26, 9)
plot(m[0], "MACD", aqua)
plot(m[1], "Signal", orange)
plot(m[2], "Histogram", gray, style = "histogram")
```

That is why `macd()` returns an array rather than being three separate functions: three names would be three call sites, and the shared smoothing would be computed three times per bar.

The same applies with more force to data reads, where the cost is a whole second series kept in step with the chart. Two identical `req.timeframe("1D", high)` calls are two reads: make one, name it, and use the name everywhere. [Limits](/script/writing/limits#data-requests) has the full example.

## Do newest-bar work on the newest bar

A panel shows one state, the current one. Writing it on all 50,000 bars to display the last one is 50,000 wasted writes.

```openscript
atrValue = atr(14)

panel = table("Now", 2, 2, position = "topRight", textColor = silver)

if bar.isLast
    cell(panel, 0, 0, "Close")
    cell(panel, 0, 1, text(close, 2))
    cell(panel, 1, 0, "ATR 14")
    cell(panel, 1, 1, isNone(atrValue) ? "warming up" : text(atrValue, 2))
```

During market hours the newest bar runs again on every update and rewrites the same cells, and persistent values are restored before each run, so nothing piles up. See [Realtime and confirmation](/script/language/realtime-and-confirmation).

Be careful about what you put behind that guard. Cells, labels and boxes that describe the present are fine. A **calculation** is not: a stateful call inside a branch advances only on the bars the branch runs, which behind `bar.isLast` means one bar, so its result is absent everywhere else ([OS8001](/script/errors/warnings#os8001)). That is why `atrValue` above is computed at the top level. Calculate unconditionally, display conditionally.

## Short-circuiting, and its trap

`and` and `or` evaluate their right side only when it can change the answer, so putting the cheapest test first is free speed:

```openscript
threshold = input(25000.0, "Level")
inSession = session.isIn("0915-1530")

// The cheap tests come first, so the comparison runs only where it can matter.
if inSession and close > threshold
    signal("ABOVE")
```

The trap: when the right side holds a stateful call and is skipped on some bar, that call's state does not advance and its series is absent on that bar. The compiler warns about it with [OS8001](/script/errors/warnings#os8001):

```openscript
threshold = input(25000.0, "Level")
inSession = session.isIn("0915-1530")

if inSession and highest(high, 200) > threshold
    signal("HIGH")
```

So short-circuiting is a speed technique for pure tests only. Anything whose value you also plot, or whose state has to track every bar, is computed at the top level first:

```openscript
threshold = input(25000.0, "Level")
inSession = session.isIn("0915-1530")

extreme = highest(high, 200)  // advances on every bar
if inSession and extreme > threshold
    signal("HIGH")

plot(extreme, "200 bar high", aqua)
```

## Memory

Speed is usually the complaint, but memory is what ends a session badly. Four things drive it.

**Retained history.** The engine keeps a history for a top-level name only when the program actually reads that name's history with `[]`, so most names cost one value rather than one per bar. By default that history reaches back to the first bar. `limits(history = n)` bounds it. See [Limits](/script/writing/limits#the-retained-history-depth).

**Arrays.** An array appended to on every bar and never trimmed grows with the chart until it reaches the 1,000,000 element ceiling ([OS5002](/script/errors/limits#os5002)). Trim as you push:

```openscript
var window: array<number> = []
push(window, close)
if size(window) > 500
    shift(window)

plot(avg(window), "Mean of the last 500 closes")
```

**Drawings.** Each lasts until the script deletes it, up to 10,000 at once ([OS5010](/script/errors/limits#os5010)). Delete a zone when price closes through it or it goes stale, and keep `draw.count()` in a debug panel while you develop.

**Strings.** Text appended to a persistent string on every bar is the one shape that grows without bound by accident, and it ends at [OS5008](/script/errors/limits#os5008). Keep the pieces in an array, trim it to the rows you show, and join only those.

## The order to work in

1. **Get it right first.** A faster script that computes a different number is not an optimisation. Have a value you trust before you change anything.
2. **Measure.** Count loop turns, calls and drawings. Two minutes of counting beats an hour of rewriting the wrong line.
3. **Fix the structure, not the details.** Nearly every real improvement is one of three changes: remove a loop whose length grows with the bar index, carry a value forward instead of rebuilding it, or stop computing the same thing twice. Tweaking one expression is rarely worth the change.
4. **Measure again, and check the numbers did not move.** Run whatever comparison told you the script was right, on the same bars. The [difference plot](/script/writing/testing#compare-two-implementations) is the standard way.
5. **Stop when it is fast enough.** A study that draws in under a second on the chart you actually use is finished, whatever the counters say.

**Related.** [Limits](/script/writing/limits), [Debugging](/script/writing/debugging), [Testing scripts](/script/writing/testing), [Style guide](/script/writing/style-guide), [Series functions](/script/reference/series), [Persistence](/script/language/persistence)


## Testing scripts

Source: https://openalgo.in/script/writing/testing

This page shows how to prove that an OpenScript script (OpenScript is also called OpenAlgo Script) computes what you think it computes, on the bars you think it computes it, and how to decide honestly whether a strategy is ready for real orders. Every check here is a short script or a few lines you add to one, and each catches a different kind of mistake.

## Why a chart script needs testing

A script is short, so it feels as though it cannot hide much. It can. Four properties of running once per bar make a wrong script look right.

- **It runs tens of thousands of times.** A bug that fires on one bar in a thousand fires fifty times over 50,000 bars, and every one of them is off the edge of the screen.
- **Warmup is out of sight.** The bars where a value is absent are at the left of the chart, where nobody scrolls, and a fallback of zero looks like data. See [Warmup](/script/language/warmup).
- **The newest bar behaves differently from every other bar.** During market hours it runs again on every update, and it is the only bar you ever watch.
- **The failure is money.** A study that is slightly wrong is a nuisance. A strategy that is slightly wrong is a position in NIFTY futures you did not mean to hold.

Testing here means five checks and a checklist. Do the first four checks before you trust a number, the fifth whenever you replace a calculation, and the checklist at the end before you trust an order.

## Check a value against a hand calculation

Pick one bar, put the inputs to a line on the chart, do the arithmetic yourself and compare. This is the only check that shows the value is right at all; everything else shows it is right in the same way everywhere.

```openscript
version 1

// A probe, not a study. Run it once against bars you can read off the
// chart, then delete it.

study("Hand check, mean of three", overlay = true, precision = 2)

checkAt = input(-1, "Check this bar index, -1 for none", min = -1)

mean = sma(close, 3)

// Written out for exactly three terms on purpose. A probe with a loop in it is
// a second implementation of the thing under test, with its own bugs, and two
// wrong answers can agree. The terms are added oldest first, the order the
// library adds a window in, so the two results can match to the last digit.
if bar.index == checkAt
    byHand = (close[2] + close[1] + close) / 3
    draw.label(time, high,
            "closes " + text(close[2], 2) + ", " + text(close[1], 2) + ", " + text(close, 2) +
            " | library " + text(mean) + " | by hand " + text(byHand) +
            " | difference " + text(mean - byHand),
            color = fade(black, 20))

plot(mean, "Mean of three", aqua)
```

Set **Check this bar index** to a bar after the first two, and the label appears on that bar with every number you need. `text()` with no decimals writes each value in full, so nothing is hidden by rounding.

You can demand an exact match, because the language does. All arithmetic is 64-bit floating point with round-to-nearest-even, in the order the source writes it, and an engine may not reorder or fuse operations. `round()` takes halves away from zero. Arrays are always walked in index order. There is no randomness, and the only clock a script can read during a bar, `chart.now()`, is a value the host fixes. Every engine must produce the same result to the last bit, so a disagreement between your arithmetic and the script's is a real disagreement.

Check three bars, not one: an early bar just after warmup ends, a bar in the middle, and a bar on a session boundary or a gap, such as the 09:15 bar after a weekend. Those are the three places the arithmetic differs for different reasons.

When your arithmetic and the script disagree, work out which is wrong before changing anything. The usual causes:

| Cause | Example |
|---|---|
| Adding in a different order | `(close + close[1] + close[2]) / 3` can differ from `sma(close, 3)` in the last digit, because floating point addition depends on order and the library adds a window oldest first |
| An off-by-one in a lookback | `close[len]` where you meant `close[len - 1]` |
| A window that includes the current bar when you assumed it did not | `sma()` over `len` bars includes this bar |
| Population against sample standard deviation | `stdev()` divides by `len` by default; `sample = true` divides by `len - 1` |
| Remainder against modulo | `-7 % 3` is `-1`, because `%` takes the sign of the left side; `mod(-7, 3)` is `2`, because `mod()` takes the sign of the right |

## Check the warmup

**Warmup is a promise, not a hint.** A warmup of "bar `len - 1`" means the call returns no value on bars 0 to `len - 2` and a value from bar `len - 1` onward, on every engine, with no bar of slack. That makes it testable, and a warmup one bar out is a genuine defect.

The probe below works for any value. Swap the line that computes `value` for the one you want to test.

```openscript
version 1

study("Warmup probe", precision = 4)

length = input(14, "Length", min = 2, max = 200)

value = rsi(close, length)

// The first bar the value exists on. isNone(firstBar) keeps it at the first:
// without that guard this would record the most recent bar with a value.
var firstBar = none
if isNone(firstBar) and not isNone(value)
    firstBar = bar.index

panel = table("Warmup", 3, 2, position = "topLeft", textColor = silver)

if bar.isLast
    cell(panel, 0, 0, "first bar with a value")
    cell(panel, 0, 1, isNone(firstBar) ? "never" : text(firstBar, 0))
    cell(panel, 1, 0, "documented warmup")
    cell(panel, 1, 1, text(length, 0))  // rsi's first value is on bar len
    cell(panel, 2, 0, "bars on the chart")
    cell(panel, 2, 1, text(bar.count, 0))

plot(value, "RSI", purple)
```

Compare the first cell with the "first value" line in the function's [reference](/script/reference/technical-analysis) entry. The ones worth remembering:

| Call | First bar with a value | Why it is not what you might guess |
|---|---|---|
| `sma()`, `ema()`, `highest()`, `stdev()` over `len` | `len - 1` | `len` values exist once bar `len - 1` has arrived |
| `rsi()` over `len` | `len` | It uses `len` changes, and a change needs two bars |
| `change()`, `crossUp()` | 1 | Both read the previous bar |
| `mom()`, `roc()` over `len` | `len` | The same extra bar, for the same reason |
| `macd(src, fast, slow, signal)` | Element 0 at `max(fast, slow) - 1`; elements 1 and 2 at `max(fast, slow) + signal - 2` | The signal line is an average of the MACD line, so it starts `signal - 1` bars later |
| `atr()` over `len` | `len - 1` | `trueRange()` on bar 0 is `high - low`, the one deliberate exception to absence spreading |
| `barsSince()`, `valueWhen()` | The first bar the condition is true | Absent before that, not zero: zero would mean "it happened on this bar" |
| `sma(ema(close, 10), 10)` | 18 | Warmups add up, because an absent source makes an absent result |

This probe catches two mistakes nothing else does: a value that is absent for ever because a stateful call sits in a branch that never runs ([OS8001](/script/errors/warnings#os8001)), and a value that starts one bar too early, which is the mark of a hand-written calculation that looks ahead.

## Check the forming bar

During market hours the newest bar runs again on every update. Before each run, the engine restores every `var`, including the contents of arrays, to what it held at the end of the previous bar. **So running the forming bar ten times gives the same answer as running it once.** A script behaves this way unless it deliberately says otherwise. See [Realtime and confirmation](/script/language/realtime-and-confirmation).

Test that yours does. This probe counts both things at once. Its `live var` line raises warning [OS8011](/script/errors/warnings#os8011) on purpose: here the difference the warning describes is the measurement.

```openscript
version 1

study("Forming bar probe", precision = 0)

// A var is restored before each run of the forming bar, so it counts bars.
var barsSeen = 0
barsSeen += 1

// A live var is not restored, so it counts runs.
live var runs = 0
runs += 1

panel = table("Forming bar", 4, 2, position = "bottomRight", textColor = silver)

if bar.isLast
    cell(panel, 0, 0, "bars counted, var")
    cell(panel, 0, 1, text(barsSeen, 0))
    cell(panel, 1, 0, "runs counted, live var")
    cell(panel, 1, 1, text(runs, 0))
    cell(panel, 2, 0, "bar.updates")
    cell(panel, 2, 1, text(bar.updates, 0))
    cell(panel, 3, 0, "this bar is confirmed")
    cell(panel, 3, 1, bar.isConfirmed ? "yes" : "no")

plot(barsSeen, "Bars", aqua)
```

On history the two counters agree. During the session, `barsSeen` keeps counting bars while `runs` climbs with every update. If a counter in your own script behaves like `runs` when you meant it to behave like `barsSeen`, it is a `live var`, and the chart and a backtest of the same bars will disagree.

The other half of the forming bar is what a script is allowed to do on it. `signal()`, `alert()`, `print()` and orders wait until the bar is confirmed, unless the declaration sets `onUnconfirmed = true`. If the condition is no longer true when the bar closes, they never happen at all. Test this before you rely on it: a strategy that acts on the close of a bar in a backtest and on a touch in the middle of a bar in real trading is not the same strategy.

## Hold the bars still

Everything above assumes the data holds still. Make it: a test on bars that keep arriving is an anecdote. In the Backtest panel on the /trading page, pick a date range that has already ended and keep it for every run you compare. Change one thing at a time, the script or the inputs, never both.

A result is reproducible when you can name three things: the script revision, the inputs, and the bars. The Scripts panel keeps no revision history in this release: each save replaces the file, and the server keeps only the previous save as a backup. So keep a copy of the exact text you tested. See [The editor](/script/getting-started/the-editor).

A light habit gets most of the value: keep a folder per script holding the bars you tested against, the settings you used and the output you checked by hand. Run it again after every edit. The first time it catches a change you did not intend, it has paid for itself.

If you work with the `openalgo-script` or `openscript` libraries directly, the language's own conformance suite (the shared set of test cases every engine must pass) is the model to copy. One case is one folder, and every byte of input lives in it: the case never names a symbol for a runner to fetch, never opens a network connection and never reads the wall clock.

```text
cases/
  my-bands/
    warmup/
      case.json
      script.os
      bars.csv
      expected.csv
      settings.json
      notes.md
```

```json
{
  "id": "my-bands/warmup",
  "category": "semantics",
  "profile": "core",
  "languageVersion": 1,
  "description": "The upper band is absent on bars 0 to 18 and present from bar 19.",
  "asserts": ["values"],
  "tolerance": { "abs": 0, "rel": 0 }
}
```

Three fields do real work. `languageVersion` is pinned, so the case is compiled the same way for ever. `asserts` names only the outputs the case checks, so a change to drawings cannot break a case about warmup. `tolerance` defaults to exact, because engines that disagree on a decimal have a defect, not a rounding difference. A case whose script calls `chart.now()` also fixes that clock with a `now` field. [Your own engine](/script/integrate/conformance) describes the full format.

## Compare two implementations

When you replace a calculation, for speed or for clarity, the test is not that the new one looks right. It is that both produce the same numbers on every bar, and that when they do not, you know the first bar where they part. That is the [first-offender pattern](/script/writing/debugging#the-first-offender-pattern), and it is the standard way to check any optimisation.

Keep the old calculation in the file, plot the difference, run it, and only then delete the old one.

```openscript
fn myFasterMean(src, len) =>
    var running = 0.0
    running += src
    if bar.index >= len
        running -= src[len]
    bar.index >= len - 1 ? running / len : none

mine = myFasterMean(close, 20)
reference = sma(close, 20)

comparable = not isNone(mine) and not isNone(reference)
plot(comparable ? mine - reference : none, "Difference", fuchsia, scale = "left")
```

A difference line flat at zero across the whole chart is a stronger statement than any number of spot checks, and it takes one look. A running total like this one can drift from a fresh sum in the last few decimals over a long chart; [Profiling and speed](/script/writing/profiling#rolling-windows-add-one-drop-one) explains why, and the difference plot is how you see whether it matters.

## Before you trust a strategy with money

Studies mislead. Strategies cost. Work down these lists, and treat any row you cannot answer as a no.

### The numbers

| Check | How |
|---|---|
| The calculation matches a hand calculation on three bars | [Check a value against a hand calculation](#check-a-value-against-a-hand-calculation) |
| Every warmup matches the documented one | [Check the warmup](#check-the-warmup) |
| The script gives the same answer however often the forming bar runs | [Check the forming bar](#check-the-forming-bar) |
| The result is reproducible from fixed bars and fixed settings | [Hold the bars still](#hold-the-bars-still) |
| No warning is outstanding | Save, and read the console under the editor. [OS8001](/script/errors/warnings#os8001), [OS8009](/script/errors/warnings#os8009), [OS8011](/script/errors/warnings#os8011), [OS8012](/script/errors/warnings#os8012) and [OS8015](/script/errors/warnings#os8015) each describe a shape that is nearly always a bug |

### The honesty

| Check | Why it matters |
|---|---|
| No higher timeframe read uses `mode = "lookahead"` | That mode reads a higher timeframe bar's final value from its first lower timeframe bar. It repaints history, permanently and by design |
| A `"developing"` read is guarded, or accepted knowingly | It includes the higher timeframe bar still forming, so its value on the newest bars moves until that bar closes |
| `onUnconfirmed` is not set, or every use is guarded by `bar.isConfirmed` | Acting on an unconfirmed bar is where repainting comes from |
| `fillOn` is `"nextOpen"` | A decision made from a bar's close cannot be filled at that same close in a real market, which is why it is the default |
| Every pivot's lag is accounted for | `pivotHigh()` and `pivotLow()` report a pivot `right` bars after it formed, the first bar on which it is knowable |
| No `var` holds a bar index | Loading more history renumbers every bar. Store `time` instead |

See [Repainting](/script/data/repainting) for the whole subject.

### The cost model

| Check | Why |
|---|---|
| `slippage` is set to something you would actually pay | It defaults to zero, which is nobody's market |
| `commission` and `commissionType` match what you actually pay | A strategy with many small trades lives or dies here. See [Costs and fills](/script/strategies/costs-and-fills) |
| `qtyType` and `qty` mean what you think | `"units"`, `"lots"`, `"cash"` and `"equityPercent"` are four different position sizes, and one NFO lot is many units |
| The result survives doubling the costs | If it does not, the edge was the cost model |

### The robustness

| Check | Why |
|---|---|
| It still works on neighbouring input values | A result that exists only at length 14 and vanishes at 13 and 15 is a coincidence you have fitted |
| It works on bars you did not look at while building it | Hold some back from the start, and do not peek at them twice |
| It works on more than one instrument, or you know why it does not | A rule that only works on one symbol is a claim about that symbol |
| The trade count is large enough to mean anything | Three good trades is a story, not a result |
| The worst losing run is one you could sit through | The number that ends most strategies is the drawdown, not the average trade |

### The operations

| Check | Why |
|---|---|
| It is flat when you expect it to be | Test the script's own square-off on a real session end, 15:30 on NSE and NFO. `closeOnSessionEnd` is accepted and not acted on in version 0.5.0, so the exit has to be a rule in the script: see [Exiting on the clock](/script/strategies/exits-and-brackets#exiting-on-the-clock) |
| It behaves on a day with a gap, a halt or a missing bar | Absence reaches a plot as a gap; make sure it reaches your decisions as "do nothing" |
| It has run in sandbox trading (analyzer mode in OpenAlgo), on real market data, long enough to see every branch | The Strategies panel starts a run in sandbox while OpenAlgo is in analyzer mode. Run there first, then live. See [Sandbox and live](/script/strategies/sandbox-and-live) |
| You know what it does when a data read fails | `req.isReady()` and `req.error()` let a script say "not yet" instead of guessing |


## What testing does not cover

The language's conformance suite tests the compiler's diagnostics and the engines' output. It deliberately does not test speed, memory, the look of a chart or the wording of a message. Nor does anything on this page. Those matter, but a test is not what fixes them: see [Profiling and speed](/script/writing/profiling) and [Limits](/script/writing/limits).

And no test says whether a strategy is a good idea. It says whether the script does what you told it to. Keeping those two apart is most of the discipline.

**Related.** [Debugging](/script/writing/debugging), [Profiling and speed](/script/writing/profiling), [Backtesting](/script/strategies/backtesting), [Reading a report](/script/strategies/reading-a-report), [Sharing scripts](/script/writing/sharing-scripts), [Warmup](/script/language/warmup)


## Troubleshooting

Source: https://openalgo.in/script/writing/troubleshooting

This page takes the symptom you are looking at, such as a blank pane, a line that stops short, a signal that fires on every bar or a strategy that stops trading, and gives you the cause and the fix. It covers OpenScript studies and strategies in the /trading page (OpenScript is also called OpenAlgo Script).

Entries are grouped by where the symptom shows up, not by which part of the language is involved, because you know what you are looking at and not yet what caused it. Each has a **Cause** and a **Fix**.

## Two habits first

Two habits solve more problems than this whole page.

**Plot the thing you are unsure about.** A `bool` becomes `cond ? 1 : 0`, and a suspicion becomes a line you can look at. A flat zero means false; a gap means absent, and the two have completely different causes.

```openscript
fast = ema(close, 9)
slow = ema(close, 21)
up = fast > slow

// A gap on the left is warmup. A flat zero is a condition that is false.
plot(isNone(up) ? none : (up ? 1 : 0), "debug: up", fuchsia, style = "step")
```

**Read the warnings.** OS8xxx warnings stop nothing, and each describes a shape that is legal and almost never what the author meant. Every save in the Scripts panel compiles the script, and the console under the editor lists the warnings with the errors.


## Nothing is drawn

### My study draws nothing at all

**Cause.** Usually one of three things: there is no `plot()` in the file, the value passed to `plot` is absent on every bar, or the study is in its own pane and you are looking at the price pane. When the compiler can see that a plotted value is absent on every bar, it raises warning [OS8009](/script/errors/warnings#os8009), "this plot can never draw", so check the warnings first.

**Fix.** Confirm the file has a `plot`. Then plot the raw input to your calculation rather than the result, and work forward until the line disappears. If the study belongs over the candles, say so in the declaration: `study("Name", overlay = true)`. See [Plots](/script/visuals/plots).


### My line starts late on the left

**Cause.** This is warmup, and it is working. A function that needs `k` bars has no value until `k` bars exist, and an absent value reaching a plot draws a gap rather than a zero. There is no separate warmup phase: the whole of it is the absent value.

**Fix.** Nothing, if the length is what you wanted. Warmups add up, so `sma(ema(close, 10), 10)` is absent until bar 18, and every function's first bar is stated in its reference entry. If you genuinely want a number during warmup, ask for one:

```openscript
// A fixed 50 on the first 14 bars. Only do this when a neutral reading is
// genuinely what you mean: it is data you invented.
plot(orElse(rsi(close, 14), 50), "RSI, 50 during warmup")
```

See [Warmup](/script/language/warmup).

### My line has holes in the middle of the chart

**Cause.** Something in the calculation went absent on those bars, and absence spreads through arithmetic all the way to the plot. The usual sources, most frequent first:

| Source | Why it is absent |
|---|---|
| A division by zero | `up / down` where `down` is 0 on that bar gives an absent value, not an error |
| A stateful call inside a branch | The call did not run on those bars, so its series is absent there |
| `volume` on a bar whose data states none | Absent, not zero, so anything computed from it is absent on that bar |
| A price missing from the data | A windowed function is absent while any bar in its window is |
| A maths call with no real answer | `sqrt()` below zero, `log()` at or below zero |

**Fix.** Find which term went absent by plotting the terms one at a time. For the branch case the compiler has already told you, with [OS8001](/script/errors/warnings#os8001): compute the call at the top level and use the result inside the branch.

```openscript
trending = close > sma(close, 50)
e = none

// Holes on every bar that is not trending.
if trending
    e = ema(close, 20)

plot(e, "EMA 20", aqua)
```

```openscript
trending = close > sma(close, 50)

// No holes: the average advances on every bar.
e = ema(close, 20)
if trending
    signal("TREND")

plot(e, "EMA 20", aqua)
```

See [Absent values](/script/language/absent-values).

### My table is blank, or half of it is

**Cause.** A cell written with an absent value is blank, just as a plot of an absent value is a gap. Also, the chart shows only the cells the newest bar wrote: a `cell()` call that did not run on the newest bar leaves its cell empty.

**Fix.** Convert deliberately, so a reader can tell warmup from a value: `isNone(v) ? "warming up" : text(v, 2)`, or `text(v)`, which writes an absent value as the word `none`. Declare the `table()` at the top level and write its cells on the newest bar, with `if bar.isLast`. The /trading chart draws the first table a study declares, so keep one table per study. See [Tables](/script/visuals/tables).

### The compiler refuses my plot inside an if

**Cause.** [OS3006](/script/errors/arguments#os3006). `plot()`, `fill()`, `level()` and `table()` define the fixed shape of the study, and that shape has to be known before bar 0 so the chart can build a legend, an axis and a settings dialog. A plot inside a branch would exist on some bars and not others. An `input()` inside a branch is refused for the same kind of reason, with [OS3007](/script/errors/arguments#os3007).

```openscript
trending = close > sma(close, 50)
ema20 = ema(close, 20)

if trending
    plot(ema20, "EMA 20", aqua)
```

**Fix.** Move it to the top level and hide it on the bars you want hidden with the absent value.

```openscript
trending = close > sma(close, 50)
ema20 = ema(close, 20)

// One column, absent on the bars where it is not wanted.
plot(trending ? ema20 : none, "EMA 20", aqua)
```

## The chart looks wrong

### The price axis changed format when I added my study

**Cause.** `precision` and `format` on a `plot()` set the formatting of the price scale that plot uses. On a study drawn over the price pane, that scale is the instrument's own axis, so the study reformats the chart underneath it. The compiler warns with [OS8007](/script/errors/warnings#os8007).

**Fix.** Set `precision` on the declaration, which applies to the study, rather than on a plot drawn over the price pane.

```openscript
version 1

study("Average", overlay = true, precision = 2)

average = sma(close, 20)
plot(average, "Average", orange)
```

### My plot is a sloping line between two daily values

**Cause.** A higher timeframe value changes once per daily bar and stays constant across every intraday bar inside it. Drawn as an ordinary line, the chart joins yesterday's reading to today's with a slope, suggesting intraday values that were never read.

**Fix.** Use `style = "step"`. A step plot says what the data says: the value held, then changed.

```openscript
version 1

study("Daily bias", overlay = true)

biasAverage = req.timeframe("1D", ema(close, 20))
plot(biasAverage, "Daily EMA 20", orange, width = 2, style = "step")
```


See [Higher timeframes](/script/data/higher-timeframes).

### My colour made everything invisible

**Cause.** `fade()` takes **transparency**, not opacity, as a percentage. `fade(aqua, 90)` is nearly invisible and `fade(aqua, 10)` is nearly solid. The two conventions are opposites, and a script that guesses wrong draws nothing you can see.

**Fix.** Use `fade()` when you are thinking "how see-through", and `withAlpha()` with a value from 0 to 1 when you are thinking "how solid". `fade(c, 90)` and `withAlpha(c, 0.1)` describe the same colour from the two sides.

```openscript
// Both give a faint aqua: 90 percent see-through, 10 percent solid.
faint = fade(aqua, 90)
alsoFaint = withAlpha(aqua, 0.1)

background(close > open ? faint : alsoFaint)
```

A colour channel written as a literal outside its range, such as `rgb(300, 0, 0)`, is refused by the compiler with [OS3004](/script/errors/arguments#os3004) rather than clamped. See [Colors](/script/visuals/colors).

## Values, names and warmup

### My running total is absent on every bar

**Cause.** A plain assignment is recomputed from scratch on every bar. A name that reads its own previous value through `[1]` reads an absent value on bar 0, the addition spreads that absence, and the series stays absent for ever after.

```openscript
// Absent on bar 0, and absent for ever after.
barCount = 0
barCount = barCount[1] + 1
plot(barCount, "Bars")
```

**Fix.** Use `var`, the language's way of saying "keep this from one bar to the next". See [Persistence](/script/language/persistence).

```openscript
// 1, 2, 3, and so on.
var barCount = 0
barCount = barCount + 1
plot(barCount, "Bars")
```

### My counter counts updates instead of bars during the session

**Cause.** You used `live var`. An ordinary `var` is restored before each run of the forming bar, which is what makes running the newest bar ten times give the same answer as running it once. `live var` opts out of that on purpose, and the compiler says so with warning [OS8011](/script/errors/warnings#os8011).

**Fix.** Use `var`. Keep `live var` for the one case it exists for, counting updates within a bar on purpose, and expect the chart and a backtest to differ when you do. See [Realtime and confirmation](/script/language/realtime-and-confirmation).

### My comparison is neither true nor false

**Cause.** If either side of `<`, `<=`, `>` or `>=` is absent, the result is absent, not false. So `a > b` being false does not mean `a <= b` is true: during warmup both are absent and both branches are skipped. A condition that is absent takes the false branch.

**Fix.** That is the correct behaviour, and it keeps `not (a > b)` equal to `a <= b` for every input. When you need to know, ask with `isNone()`, or with `==` and `!=`, which never return absent. An ordered comparison with `none` written on one side is absent on every bar, which is warning [OS8012](/script/errors/warnings#os8012):

```openscript
missing = close > none
plot(isNone(missing) ? 1 : 0, "Always absent")
```

### The compiler says a name is not defined, and I can see it three lines up

**Cause.** One of two rules. Either the name was first assigned inside a block, so it belongs to that block and is invisible outside it, or the name is read above the line that assigns it. The file runs top to bottom on every bar, so order matters. This is [OS2001](/script/errors/names-and-types#os2001).

```openscript
volatile = high - low > atr(14)

if volatile
    scratch = high - low  // declared inside the block

plot(scratch, "Scratch")  // not visible here
```

**Fix.** Assign it at the top level before you read it. Functions are the one exception: an `fn` may be called before its declaration appears.

```openscript
volatile = high - low > atr(14)

scratch = none  // declared at the top level
if volatile
    scratch = high - low  // updates the existing name

plot(scratch, "Scratch")
```

See [Variables and scope](/script/language/variables-and-scope).

### The compiler says the name already exists

**Cause.** [OS2002](/script/errors/names-and-types#os2002). A second declaration of a name that already exists outside is an error: a `var` of the same name inside a block, a loop counter or function parameter named like a top-level value, or a function body that assigns to a top-level name. So is assigning to a library name such as `close`, `ema()`, `aqua` or `level()`, because the library lives in the outermost scope. Many library names are ordinary words: `variance`, `count`, `change`, `level` and `median` are all taken.

**Fix.** Rename yours. The message names the line of the other declaration, or says it is built in, so you can see what you collided with.

### My values changed when the chart loaded more history

**Cause.** One of two things. You stored `bar.index` in a `var`: it is a position in the bars the engine was given, not a fixed address, so loading more history renumbers every bar and the stored number now points somewhere else. Or a value depends on where the history starts, such as `cum()` or a `var` counter that began on bar 0: more history is a different starting point, so a different total.

**Fix.** Store `time` and compare timestamps, because a bar's time never moves. Anchor a running value to something that does not move either, such as the start of a session or a date, rather than to the first bar on the chart.

> **The error reference lists a warning for a stored bar index, [OS8014](/script/errors/warnings#os8014), but the compiler in version 0.5.0 does not raise it yet: it does not follow a bar index into a `var`.**

### Warmup quietly changed an answer

**Cause.** An `if` whose condition can be absent takes the false branch during warmup, so a name the block assigns keeps whatever it held before, and those bars sit off the left edge where nobody looks.

**Fix.** Decide what warmup means and write it down: test `isNone(cond)` explicitly, or give the name a starting value above the `if` that you are happy to see on warmup bars.

> **The error reference lists a warning for this shape, [OS8004](/script/errors/warnings#os8004), but the compiler in version 0.5.0 does not raise it yet: it does not yet follow which names a branch on a possibly absent condition assigns.**

## Signals and alerts

### My signal fires on every bar of a trend

**Cause.** The condition tests a **state**, not a **change**. `fast > slow` is true on every bar of an uptrend, so it marks every bar.

**Fix.** Test the moment it changes. `crossUp()` is true on exactly the bar where `a` was at or below `b` and is now above. Where the event is not a crossing, compare with the previous bar.

```openscript
fast = ema(close, 9)
slow = ema(close, 21)
up = fast > slow

// Once, on the crossing bar.
if crossUp(fast, slow)
    signal("BUY")

// The same idea by hand, for a condition that is not a crossing.
if up and not orElse(up[1], false)
    signal("TURNED UP")
```

### My signal never fires

**Cause.** Four candidates, most frequent first:

1. The condition is absent rather than false, and an absent condition takes the false branch. This is warmup, a missing `volume`, or a division by zero.
2. The bar is still forming. `signal()`, `alert()` and orders wait until the bar is confirmed, and if the condition is no longer true when the bar closes they never happen at all.
3. The condition is never true. If it is constant, the compiler says so with warning [OS8017](/script/errors/warnings#os8017).
4. A guard above it does something you did not intend, such as an ordered comparison against `none`.

**Fix.** Plot the condition as `cond ? 1 : 0` and look at the line, as in [Two habits first](#two-habits-first).

### My alert never arrives

**Cause.** Everything in the previous entry applies, and two things are specific to alerts on the /trading page. Nothing fires for bars that were already on the chart when the study was added, because an alert is a statement about now. And in this release the chart checks a script's `alert()` once for each new bar, at the moment that bar first arrives. During market hours a bar arrives with its first tick, before it has closed, while the alert waits for its bar to close, so at that moment it has nothing to report and the chart does not look at that bar again. The alert fires only for a bar that reaches the chart already closed, which you cannot rely on while the market is open.

**Fix.** Plot the condition as 1 or 0 and put a study alert on that plot: open **Create alert** from the chart's **Alerts** button, set **What to watch** to **Study plot**, pick the study and its plot, and set **Evaluate** to **On bar close**. [Alerts on a script condition](/script/alerts/alerts-in-trading#alerts-on-a-script-condition) walks through it. Keep a fixed `id` on every `alert()` in the script as well: without one its identity comes from its line number, which moves when you edit the file, and the compiler warns with [OS8008](/script/errors/warnings#os8008).

```openscript
fast = ema(close, 9)
slow = ema(close, 21)
crossed = crossUp(fast, slow)

// The line a study alert can watch: 1 on the crossing bar, 0 otherwise.
plot(crossed ? 1 : 0, "Fast crossed above slow", fuchsia, style = "step")

if crossed
    alert("Fast crossed above slow at " + text(close, 2), id = "cross-up")
```

### My marker sits on the wrong side of the bar

**Cause.** The call did not say where the marker goes, so it took the default, `at = "above"`. The side is never worked out from what the marker says.

**Fix.** Say which: `at = "above"`, `"below"` or `"price"`, and choose a `shape`. The value must be written as a literal or come from an `input()`, because a marker's look is fixed before bar 0; a value that changes per bar is [OS3003](/script/errors/arguments#os3003).

```openscript
fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow)
    signal("BUY", at = "below", shape = "arrowUp")
```

## Higher timeframe and other instruments

### My higher timeframe read is empty

**Cause.** Work down this list:

| Code | Means |
|---|---|
| [OS6001](/script/errors/data#os6001) | The timeframe string is not a timeframe. Minutes are a number in a string, `"5"` or `"60"`; a day is `"1D"` |
| [OS6002](/script/errors/data#os6002) | The request is finer than the chart. Folding cannot invent bars that were never loaded |
| [OS6015](/script/errors/data#os6015) | The request is not a whole multiple of the chart's interval, such as `"7"` on a 5 minute chart |
| [OS6007](/script/errors/data#os6007) | The host does not know that symbol on that exchange |
| [OS6008](/script/errors/data#os6008) | The instrument returned no bars over the range the chart covers |
| [OS6009](/script/errors/data#os6009) | The request failed: a connection, permission or quota problem in the host |

Beyond those, a `req.symbol()` read is simply absent until the host answers, which is not instant.

**Fix.** Check `req.isReady()` before acting on the value, and `req.error()` for the reason when a read failed. The unit letters are case sensitive: `"1M"` is one month and `"1m"` is one minute.

```openscript
niftyDaily = req.symbol("NIFTY", "1D", close, exchange = "NSE_INDEX")

// req.error() is an empty string until something goes wrong.
failure = req.error(niftyDaily)
panel = table("NIFTY read", 1, 1, position = "bottomRight")
if bar.isLast and failure != ""
    cell(panel, 0, 0, "NIFTY read failed: " + failure)

plot(req.isReady(niftyDaily) ? niftyDaily : none, "NIFTY daily close", orange, style = "step")
```

See [Other instruments](/script/data/other-instruments).

### My markers move when I reload the chart

**Cause.** The study repaints: what it showed on a bar while that bar was live differs from what it shows for the same bar on history. The usual causes are a higher timeframe read with `mode = "lookahead"`, which gives every chart bar the final value of its higher timeframe bar, and `onUnconfirmed = true` in the declaration, which lets signals and orders act on a bar that is still forming. A `mode = "developing"` read also moves on the newest bars until the higher timeframe bar closes.

**Fix.** `mode = "confirmed"` is the default and the only mode that never repaints. A `"lookahead"` read raises warning [OS8005](/script/errors/warnings#os8005). If you set `onUnconfirmed = true` deliberately, guard every decision with `bar.isConfirmed`, and expect warning [OS8002](/script/errors/warnings#os8002) on every higher timeframe read in the file. The /trading legend does not mark a repainting study in this release, so these warnings in the console are your notice. See [Repainting](/script/data/repainting).

## Strategies, orders and backtests

### My backtest and the running strategy disagree

**Cause.** Usually not a bug. A backtest decides fills from bars, and a running strategy gets the fills the market gives it. The candidates, in the order worth checking:

| Cause | What to look at |
|---|---|
| Fill timing | `fillOn` defaults to `"nextOpen"`: the backtest fills a decision at the next bar's open, because a decision made from a bar's close cannot be filled at that same close |
| Costs | `slippage`, `commission` and `commissionType` are what the backtest charges on every fill; set them to what you actually pay |
| A `live var` | It is not restored, so it counts updates while running and bars in a backtest |
| `onUnconfirmed = true` | A running strategy acts on a bar that is still forming; the backtest only ever saw it closed |
| A repainting read | A `"lookahead"` read knows each higher timeframe bar's final value on history and not while it forms |
| A stop and a target in one bar | A backtest cannot see the path inside a bar, as the entry below explains |

**Fix.** Read the declaration first: most of those are options on one line. Leave `fillOn = "nextOpen"` alone unless you can say why the other is honest for your market. See [Costs and fills](/script/strategies/costs-and-fills).

### My strategy did nothing when I ran it

**Cause.** In order of likelihood:

1. **It is not running.** Each deployment in the Strategies panel shows whether it is running or stopped, and a run can stop on its own, on an error or a refused order.
2. **It is trading in sandbox.** A running strategy sends orders through OpenAlgo's own order path, so while OpenAlgo is in analyzer mode its orders go to sandbox trading (analyzer mode in OpenAlgo), not to your account. The Strategies panel header says **Live** or **Analyzer**, and the start button says **Start live** or **Start in sandbox**.
3. **The condition has not been true since it started.** Orders wait for a confirmed bar, and a strategy acts only on bars that arrive after it starts.
4. **The instrument was outside its trading session.** Nothing in the script checks this for you.

**Fix.** Check the deployment's row and the mode in the Strategies panel header. To check the logic itself, open the script in the Scripts panel and press **Apply to chart**: for a strategy, that runs a backtest over the chart's history and marks every fill on the price. On the chart and in the Backtest panel, guard entries to the session with `session.isIn()` and a named zone, such as `session.isIn("0915-1530", "Asia/Kolkata")`. A deployed strategy cannot read the clock that way, because the Strategies panel refuses a script that calls `session.*` or `date.*` on an Indian instrument, so there build the window from arithmetic on `time`. `closeOnSessionEnd = true` is accepted and not acted on in version 0.5.0, so a strategy that must be flat at the close needs its own exit. See [Sandbox and live](/script/strategies/sandbox-and-live) and [Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today).

> **[OS7012](/script/errors/orders#os7012) (outside the session) is in the error reference, but nothing raises it in version 0.5.0: nothing compares the bar's time with the instrument's session before an order is sent. Guard the session yourself.**

### My strategy stopped on an order error

**Cause.** An order the script placed broke a rule, and the strategy stopped on that bar with an OS7xxx error. In a backtest this looks like a run that trades for a while and then does nothing more. The code says which rule:

| Code | Means | Usual reason |
|---|---|---|
| [OS7002](/script/errors/orders#os7002) | An order argument is absent | A stop or a quantity computed from a window that has not filled yet |
| [OS7004](/script/errors/orders#os7004) | The quantity is zero or negative | A sizing formula rounded down to 0 |
| [OS7008](/script/errors/orders#os7008) | The entry was refused by pyramiding | A second entry in the same direction while `pyramiding` allows one |
| [OS7013](/script/errors/orders#os7013) | Two opposite orders on one bar | An exit and an entry, or a buy and a sell, from two conditions that can both be true |
| [OS7016](/script/errors/orders#os7016) | A close names a tag nothing places | A typo in a `close` tag, reported when the file compiles |
| [OS7017](/script/errors/orders#os7017) | A close asks for more than is left to close | A `qty` on `close()` larger than what the position or tag still holds, less anything already working against it |

**Fix.** For an absent argument, guard the call rather than defaulting the value, because an order is the one place where doing nothing quietly is worse than stopping loudly. This entry sizes each trade so the stop risks a fixed amount, and does nothing until every number exists:

```openscript
version 1

strategy("Sized from the stop", overlay = true, qtyType = "units", qty = 1)

riskAmount = input(5000, "Amount risked per trade", min = 1)

fast = ema(close, 9)
slow = ema(close, 21)
crossed = crossUp(fast, slow)
stop = lowest(low, 20)
// The zone is named, so the window also holds in the Backtest panel.
inSession = session.isIn("0915-1530", "Asia/Kolkata")

// Absent until the 20 bar window fills, and zero when the stop is too wide
// for the amount risked. Either way the entry below does nothing.
distance = close - stop
qty = distance > 0 ? floor(riskAmount / distance) : none

// The script tests its own stop: the 0.5.0 backtest does not fill a stop
// set with exit().
var stopLevel = none

if pos.isLong and close < stopLevel
    close()
else if crossed and inSession and pos.isFlat and not isNone(qty) and qty > 0
    buy(qty = qty)
    stopLevel = stop
```

For [OS7013](/script/errors/orders#os7013), make the conditions exclusive with `else if`, so at most one order is placed per bar. Compute the crossings at the top level so both keep advancing on every bar:

```openscript
version 1

strategy("Add on strength", overlay = true, qty = 1, pyramiding = 2)

fast = ema(close, 9)
slow = ema(close, 21)
up = crossUp(fast, slow)
weak = crossDown(fast, slow)
newHigh = close > highest(high, 20)[1]

// One order per bar at most. Written as separate ifs, a bar that is both weak
// and a new high would place close() and buy() together, which is OS7013.
if weak and not pos.isFlat
    close()
else if up and pos.isFlat
    buy()
else if newHigh and pos.size == 1
    buy()
```

For [OS7016](/script/errors/orders#os7016), fix the spelling so the tag on `close()` matches the one on the entry:

```openscript
fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow)
    buy(qty = 1, tag = "entry")

if crossDown(fast, slow)
    close(tag = "entyr")
```

For [OS7017](/script/errors/orders#os7017), leave the quantity off `close()` and it closes whatever is left, or guard a partial exit on `pos.size` so it cannot fire twice on one position. The engine will not send a smaller number for you: that would be a quantity you did not write.

> **[OS7005](/script/errors/orders#os7005) (a quantity that is not a whole number of lots) and [OS7011](/script/errors/orders#os7011) (an order larger than the capital) are in the error reference, and nothing raises them in version 0.5.0. Round NFO and MCX quantities to the lot size yourself, using `chart.lotSize`.**

### My strategy stopped after its first entry

**Cause.** `pyramiding` defaults to 1: one entry in each direction. An entry condition that stays true, such as `fast > slow`, asks for a second entry on the next bar while the first is still open, and that is [OS7008](/script/errors/orders#os7008). The strategy stops on that bar rather than quietly ignoring the order.

**Fix.** Test the position, which also makes the intent readable. Raise `pyramiding` in the declaration only if you really mean to add to a position.

```openscript
fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow) and pos.isFlat
    buy(qty = 1)

if crossDown(fast, slow) and not pos.isFlat
    close()
```

### My stop was hit in real trading and not in the backtest

**Cause.** A backtest sees bars, not ticks. The order in which a bar made its high and its low, and the path between them, is not in the data. A stop and a target that both sit inside one bar's range cannot be resolved from that bar, and the fill model has to choose.

**Fix.** Do not treat a backtest as a tick-accurate simulation of what happens inside a bar. Test the same rule on a shorter interval, where each bar hides less of the path, and size the stop so that being wrong about the path inside one bar does not decide the result. See [Backtesting](/script/strategies/backtesting).

## Numbers and performance

### My indicator disagrees with another implementation's numbers

**Cause.** Two implementations of the same named indicator often differ in three places: how a smoothed average is seeded, whether a standard deviation divides by the window length or one less, and how halves are rounded. All three are fixed in OpenScript and stated per function.

**Fix.** Check the three. `ema()` is seeded on bar `len - 1` with the simple average of those `len` values. `stdev()` and `variance()` divide by `len`, the population form, and take `sample = true` for the other. `round()` takes halves away from zero. Then check the warmup: an implementation that starts a bar earlier or later has a different first value, and so a different smoothed series for ever after.

### My loop ran out of budget

**Cause.** [OS5001](/script/errors/limits#os5001). Every turn of every loop in one bar counts against a budget of 2,000,000 per bar. The bar stops rather than breaking out of the loop, because a loop cut short produces a plausible wrong number.

**Fix.** Either the exit condition is wrong, which the named line shows you, or the script genuinely needs more, and you raise the budget in one place with `limits(loops = n)` straight after the declaration. See [Limits](/script/writing/limits#the-loop-budget).

Also check for the descending loop that never runs: `for i = 9 to 0` runs zero times and needs `step -1`. The compiler warns with [OS8015](/script/errors/warnings#os8015).

```openscript
total = 0.0
for i = 9 to 0 step -1
    total += close[i]

plot(total, "Sum of the last 10 closes")
```

### My script is slow, or a bar timed out

**Cause.** Almost always recomputation: a loop that walks the whole history on every bar, so the work grows with the square of the chart's length. A host that sets a time budget per bar stops it with [OS5007](/script/errors/limits#os5007).

**Fix.** Keep a running value in a `var` and update it per bar, or use the library functions that already do the work: `highest()`, `sum()`, `cum()`, `barsSince()` and `valueWhen()`.

```openscript
// Before: 200 turns of a loop on every bar.
total = 0.0
for i = 0 to 199
    total += close[i]

plot(total, "Sum of 200 closes, by loop")
```

```openscript
// After: one library call. It still adds 200 closes on each bar, but inside
// the engine rather than as turns of your own loop.
plot(sum(close, 200), "Sum of 200 closes")
```

See [Profiling and speed](/script/writing/profiling).

### The compiler rejects a character I cannot see

**Cause.** [OS1001](/script/errors/syntax#os1001). Outside a string or a comment, the language accepts ASCII letters, digits, spaces, newlines and its own punctuation, and nothing else. A non-breaking space or a curly quotation mark pasted from a web page or a document is invisible in every editor, and refusing it where it sits stops it causing a baffling error three tokens later.

**Fix.** The message names the plain character to use instead. Related refusals from the same family: a tab in the indentation ([OS1002](/script/errors/syntax#os1002), indent with spaces), a semicolon ([OS1007](/script/errors/syntax#os1007), put the second statement on its own line), `!` (write `not`), `&&` and `||` (write `and` and `or`), and `^` (write `pow(a, b)`).

```openscript
bullish = close > open && volume > 0
```

## Still stuck

Try three things before anything else. Read the warnings, because they describe exactly the shapes that produce puzzling behaviour. Plot the intermediate value, because a gap and a flat zero look the same in your head and completely different on a chart. And look the code up in the [error reference](/script/errors/overview), where every code has a cause, a fix and a before and after example. [Debugging](/script/writing/debugging) walks through finding the exact bar and line that goes wrong.

**Related.** [Debugging](/script/writing/debugging), [Reading an error](/script/errors/overview), [Warmup](/script/language/warmup), [Absent values](/script/language/absent-values), [Repainting](/script/data/repainting), [FAQ](/script/resources/faq), [Glossary](/script/resources/glossary)


## Sharing scripts

Source: https://openalgo.in/script/writing/sharing-scripts

This page shows how to package an OpenScript script (OpenScript is also called OpenAlgo Script) so that someone who did not write it can use it correctly, and how to version it so that nobody's chart, backtest or running strategy changes underneath them. You need it the first time you give a study to a colleague, post a strategy for others, or come back to your own script after six months.

## What sharing means here

A script is a plain text `.oscript` file. Sharing it means giving someone that text with enough around it that they can use it without asking you questions. Someone using the /trading page pastes it into a new script in the Scripts panel. There is no store to submit to and no approval step, which puts the whole job of being usable on the file and what travels with it.


What you share is a package, not only code:

| Part | Needed | Holds |
|---|---|---|
| The script file, `.oscript` | Yes | The code, with a header comment |
| A README | Yes | What it does, what it needs, what it does not do, and the inputs |
| A licence | Yes | What others may do with it. Without one, legally, nothing |
| A changelog | From the second version | What changed, and whether the numbers moved |
| Test bars and settings | Strongly recommended | The data and inputs your published numbers came from |
| A version number | Yes | In the file name, in the header, and in the study's title |

## The header comment

The first thing in the file, before `version 1`, is prose. This is the one place in a script where a comment says **what** rather than why, because it is the only part most readers look at before running it. See the [Style guide](/script/writing/style-guide#comments) for comments everywhere else.

```openscript
// Deviation bands, version 1.2.0
//
// A simple moving average with bands a chosen number of standard deviations
// above and below it, and a marker on the bar the source closes above the
// upper band.
//
// Needs: only the chart's own bars. No volume, no other instrument and no
// session information.
// Warmup: the bands are absent until bar length - 1, which at the default
// length of 20 is the first 19 bars of the chart.
// Repaints: no. There is no higher timeframe read, and the marker waits for
// the bar to close.
// Built for: any instrument and interval. Tested on daily and 5 minute bars
// of a liquid NSE index future.
// Does not: place orders, size a position or say anything about direction.
//
// Licence: Apache-2.0. See LICENSE beside this file.

version 1

study("Deviation bands 1.2", overlay = true, precision = 2, group = "Volatility")

length = input(20, "Length, in bars", min = 2, max = 500,
        tooltip = "Bars in both the average and the deviation")
widthDev = input(2.0, "Band width, in standard deviations", min = 0.1, max = 10,
        group = "Bands")
src = input(close, "Source")

basis = sma(src, length)
dev = widthDev * stdev(src, length)
upper = basis + dev
lower = basis - dev

plot(basis, "Basis", orange, width = 2)
upperPlot = plot(upper, "Upper", silver)
lowerPlot = plot(lower, "Lower", silver)
fill(upperPlot, lowerPlot, fade(silver, 92))

if crossUp(src, upper)
    signal("BREAK UP")
```

Each line of that header does a specific job.

- **The version is in the header and in the title.** The title is the name in the chart legend and in the Indicators dialog, so someone with two versions of a study on one chart can tell them apart without opening the settings. (`short` sets a shorter legend name for a host that shows one; the /trading chart shows the title.)
- **"Needs" is a compatibility statement.** A study that needs `volume` draws nothing on an instrument whose data carries none, and the reader should learn that from the header rather than from an empty pane.
- **"Warmup" is stated in bars and at the default setting.** Warmup is exact in this language, so it can be stated exactly. See [Warmup](/script/language/warmup).
- **"Repaints" is stated even when the answer is no.** The answer can be checked from the source, because a read that repaints has to name its mode on the line that does it. Saying so saves every reader that check. See [Repainting](/script/data/repainting).
- **"Built for"** tells a reader whether they are the intended user.
- **"Does not"** prevents most misunderstandings, and it is the line everybody leaves out.

The `group` and `tooltip` arguments of `input()` matter more than they look. The settings dialog is the only documentation many users will ever read, so write titles as full phrases with units, group related rows under a heading, and put what a title is too short to say in a tooltip. The /trading study settings dialog does not show group headings or tooltips in this release, so the title has to carry the meaning on its own there; the input forms of the Backtest and Strategies panels show the tooltip when you rest the pointer on an input's label. The `group` on `study()` is different: it is the category a picker files the study under. In the /trading Indicators dialog your own scripts are listed together under **My scripts**, and the group is the label shown beside a script's name when you point at it. See [Settings and style](/script/inputs/settings-and-style).


## Versioning

Use three numbers, and give them the meanings a reader of a trading script needs.

| Part | Increase it when | Examples |
|---|---|---|
| Major | **The numbers change** for the same inputs on the same bars | Switching a deviation from the population to the sample divisor; changing a window to exclude the current bar; fixing a wrong formula |
| Minor | Behaviour is added, but existing numbers do not move | A new optional plot; a new input whose default reproduces the old behaviour; a new alert |
| Patch | Nothing you can observe changes | A comment, a rename, a faster calculation that gives identical output on every bar |

The test for a major version is mechanical: run the old and the new version over the same fixed bars with the same settings and plot the difference, as in [Testing scripts](/script/writing/testing#compare-two-implementations). If the difference is not a flat zero on every bar, it is a major version, whatever the change looked like.

This is the same promise the language makes about itself: a fix that changes a number is a version change, because a chart that silently redraws itself after an update is worse than one that is slightly wrong in a documented way.

```openscript
length = input(20, "Length, in bars", min = 2, max = 500)
widthDev = input(2.0, "Band width, in standard deviations", min = 0.1, max = 10)

// 1.0.0: the population divisor.
dev = widthDev * stdev(close, length)
plot(dev, "Deviation")
```

```openscript
length = input(20, "Length, in bars", min = 2, max = 500)
widthDev = input(2.0, "Band width, in standard deviations", min = 0.1, max = 10)

// 2.0.0: the sample divisor. Every band value moves, so this is a new major
// version and not an edit to 1.x.
dev = widthDev * stdev(close, length, sample = true)
plot(dev, "Deviation")
```

If both readings have real users, the kinder answer is neither a fork nor a silent change: add an input, keep the old default, and ship it as a minor version.

```openscript
length = input(20, "Length, in bars", min = 2, max = 500)
widthDev = input(2.0, "Band width, in standard deviations", min = 0.1, max = 10)

// 1.1.0: both readings, and the old one is still the default.
sampleDev = input(false, "Use the sample divisor",
        tooltip = "Off reproduces version 1.0.0 exactly")

dev = widthDev * stdev(close, length, sample = sampleDev)
plot(dev, "Deviation")
```

## A shared version never changes

**Once a version is shared, its file never changes again.** Not for a typo in a comment, not for a one character fix, not because nobody has downloaded it yet. A change becomes a new version number.

This is not ceremony. It is the only thing that keeps these true:

- **Results stay reproducible.** A number someone quotes from a chart or a backtest is only checkable if the exact file that produced it still exists. A shared file that changes while keeping its name breaks that link, and every number anyone quoted from it becomes impossible to check. The Scripts panel on the /trading page keeps no revision history, only the previous save as a backup, so keeping old versions is your job. See [The editor](/script/getting-started/the-editor).
- **A bug report can be answered.** "Version 1.2.0 on these bars gives 41.7" is a report you can act on. "The latest version gives 41.7" is not, if the latest version has been three different files.
- **Nothing changes under someone's running strategy.** A person running version 1.2.0 with a position open needs 1.2.0 to stay exactly what it was until they choose to move.

In practice:

- Put the version in the file name, in the header, and in the declaration's title. Each is visible in a different place.
- Keep old versions available. Someone is running one, and their alternative is to stop trusting their own results.
- If a version is dangerous, mark it withdrawn in the changelog and say why, rather than deleting it. A file that vanishes leaves the people who have it no way to find out what was wrong.
- A file you have given to one person is shared. The rule is about whether anyone else has it, not how many.

## The changelog

One entry per version, newest first, and each entry answers one question before anything else: **did the numbers move?**

```text
## 2.0.0

Numbers changed. The deviation now uses the sample divisor, so every band
value differs from 1.1.0. Run any backtest that used this study again.

## 1.1.0

Numbers unchanged with default settings. Adds a "Use the sample divisor"
input, off by default, which reproduces 1.0.0 exactly.
Adds an alert on a close outside the upper band.

## 1.0.0

First release.
```

Three kinds of change cover everything, in this order of importance: numbers changed, behaviour changed, appearance changed. Someone deciding whether to upgrade a study that a strategy depends on needs the first line and nothing else.

## The README

The README carries what does not fit in the header. A workable template:

**What it does.** Two or three sentences in the language a trader uses rather than the language the code uses. Say what the lines on the chart mean, not which functions produced them.

**The inputs.** A table, because a settings dialog is a list and a list does not explain how rows relate.

| Input | Default | Range | Means |
|---|---|---|---|
| Length, in bars | 20 | 2 to 500 | Bars in both the average and the deviation |
| Band width, in standard deviations | 2.0 | 0.1 to 10 | Distance from the average to each band |
| Source | `close` | Any price | Which price the average is computed from |

**What it needs.** Volume, a session, another instrument, a minimum history, a particular timeframe. Each of these turns into an empty pane or a wrong number when it is missing, and each is invisible in the code to someone who does not read the whole file. An NFO options study that reads the underlying index is a good example of a need worth stating.

**Warmup.** How many bars before it draws, as a formula in the inputs, plus what that is at the defaults.

**Whether it repaints, and how.** There are three honest answers. A script whose higher timeframe reads use the default `"confirmed"` mode and that takes no action on an unconfirmed bar does not repaint. A script reading a forming higher timeframe bar (`mode = "developing"`) shows values on the newest bars that move until that higher timeframe bar closes. A script using `mode = "lookahead"` repaints history by design. The /trading legend does not mark a repainting study in this release, so the README is where a reader learns it.

**What it does not do.** The shortest section, and the one that prevents the most disappointment.

**How to reproduce the published numbers.** The bars, the settings and the instrument facts (such as the lot size for an F&O contract) you used, ideally as files beside the script. This is the same material as a test case, so build it once. See [Testing scripts](/script/writing/testing#hold-the-bars-still).

**Known limitations.** Where it is wrong, where it is untested, and what you would not use it for. A limitation you disclose is part of the documentation. A limitation someone else discovers is a bug report and a lost reader.

## A licence

A script shared with no licence grants nobody any rights, whatever you intended. Careful people will not use it, and anyone who uses it anyway does so without permission. Choose a licence, name it in the header and put its full text beside the script as a `LICENSE` file.

There are two broad families:

- **A permissive licence** lets anyone use, modify and redistribute the code, including inside something closed, usually asking only that the notice travels with it.
- **A copyleft licence** requires that derived work is shared under the same terms. That keeps derived work open and, in exchange, keeps it out of closed products.

The two OpenScript libraries, `openalgo-script` on npm (JavaScript and TypeScript) and `openscript` on PyPI (Python), are released under Apache 2.0, a permissive licence, so that any platform can embed the language. A script is not a language, so that reasoning does not automatically carry over to your work. Pick the family that matches what you want to happen to it.

Two related points that are not about licences. A strategy is not investment advice, and a line in the README saying what it is and is not is worth writing. And if your script was built from someone else's shared script, say so and honour their terms: plain text files exist so that where code came from can be seen.

## Before you share: the checklist

| Check | Why |
|---|---|
| The debug harness is gone | Debug plots, panels, labels and prints are noise on someone else's chart, and a debug plot that is always absent earns [OS8009](/script/errors/warnings#os8009) |
| The file compiles with no warnings | Every OS8xxx warning describes a shape that is nearly always a bug, and a clean file tells a reader that anything unusual was meant |
| The `version 1` line is present | Without it the file is compiled as the newest version, which is the one thing that can change under it ([OS8003](/script/errors/warnings#os8003)) |
| Every input has a title, and a range where one makes sense | The settings dialog is the documentation most users read |
| Nothing is typed in that should be an input | A symbol, an exchange, a session window, a date, a lot size |
| No credentials, API keys, account numbers or broker identifiers appear anywhere | A script is a text file that travels |
| The numbers can be reproduced from the bars and settings in the package | [Testing scripts](/script/writing/testing#hold-the-bars-still) |
| The header states needs, warmup, repainting and what it does not do | These four answer most of the questions you would otherwise be asked |
| The version is in the file name, the header and the legend | Three places, each seen in a different context |
| The licence file is present and the header names it | See above |

A file with no version line still compiles, with a warning:

```openscript
study("No version line")
plot(close, "Close")
```

Here is the last of a debug harness on its way out. It goes to the test folder beside the script, not to the study every reader loads.

```openscript
basis = sma(close, 20)
dev = 2 * stdev(close, 20)

// Remove before sharing: a probe, and a debug plot nobody else wants.
watchBar = input(-1, "Label this bar index, -1 for none")
if bar.index == watchBar
    draw.label(time, high, "basis " + text(basis) + ", dev " + text(dev))
plot(dev, "debug: deviation", fuchsia, scale = "left")

plot(basis, "Basis", orange)
```

See [Debugging](/script/writing/debugging) for what a harness is for.

## After sharing

**When a bug is reported, ask for three things:** the version, the settings and the bars. With those you can reproduce it exactly, because nothing else in the language varies: there is no randomness, no clock reading during a bar other than `chart.now()`, which the host fixes, and no arithmetic that differs between engines. A report that cannot be reproduced from those three is about the host or the data, and that is worth knowing too.

**When someone says it repaints**, the answer is in the source and takes one line to give. A higher timeframe read names its mode on the line that makes it, the default mode never repaints, and `onUnconfirmed = true` can only appear on the declaration line. Point at the line.

**When you want to change it**, go back to the top of this page. The change is a new version, the changelog says whether the numbers moved, and the old file stays where it is.

**Related.** [Style guide](/script/writing/style-guide), [Testing scripts](/script/writing/testing), [Debugging](/script/writing/debugging), [Limits](/script/writing/limits), [Inputs](/script/inputs/inputs), [Example scripts](/script/getting-started/example-scripts), [Libraries](/script/language/libraries)


# Reference

## Keywords

Source: https://openalgo.in/script/reference/keywords

OpenScript, also called OpenAlgo Script, reserves thirty-six words. Each one has a fixed job in the grammar, so none of them can be the name of a variable, a function or a parameter. This page lists every reserved word with the form it appears in, what it does and a working example, followed by the two words that are part of the grammar without being reserved: `version` and `limits`.

Read it once to learn which names are off limits. Come back to an entry when the compiler reports `OS1019` (a reserved word used as a name), or when you want the exact rule for a loop, a branch or a declaration.

## All reserved words at a glance

| Group | Words |
|---|---|
| Declarations | `study`, `strategy` |
| Control flow | `if`, `else`, `for`, `to`, `step`, `in`, `while`, `break`, `continue`, `switch`, `case`, `default` |
| Functions | `fn`, `return` |
| Persistence | `var`, `live` |
| Logic | `and`, `or`, `not` |
| Literals | `true`, `false`, `none` |
| Type names | `number`, `string`, `bool`, `color`, `series`, `array` |
| Reserved for a later version | `as`, `import`, `is`, `map`, `matrix`, `type` |

The six words in the last row do nothing in version 1. They are reserved now so that giving them a meaning later cannot break a script that used one as a variable name: a script that compiles under version 1 keeps compiling, and keeps producing the same numbers, under every later release.

Here is a complete study that uses most of the everyday keywords. It counts how many of the last ten bars closed above their open, and remembers the best count seen so far.

```openscript
version 1
study("Up bars in the last ten", precision = 0)

// A user function: how many of the last len bars closed above their open.
fn upBars(len: number) =>
    total = 0
    for i = 0 to len - 1
        if isNone(close[i])
            continue
        if close[i] > open[i]
            total += 1
    total

var best = 0
recent = upBars(10)
if not isNone(recent) and recent > best
    best = recent

plot(recent, "Up bars", aqua, style = "histogram")
plot(best, "Most so far", orange)
```

## Where a reserved word may appear

A reserved word is never a name. The table shows what that rules out, and the one place a reserved word is still legal.

| Written as | Allowed | What happens |
|---|---|---|
| A variable name, such as `type = "long"` | No | `OS1019`, and the fix suggests a name that is free |
| A parameter of your own function, such as `fn f(color = red)` | No | `OS1019`, because the body refers to a parameter by name |
| The name of your own function, such as `fn step() => 1` | No | `OS1019` |
| A named argument label, such as `plot(x, "X", color = aqua)` | Yes | A label is matched against the called function's parameter list and is never looked up as a name |
| Text inside a string, such as `"type"` | Yes | A string is text, not code |

```openscript
type = "long"
```

The label rule is why library calls can use `color` and `step` as argument names and you can still write them:

```openscript
version 1
study("Labels are not names", overlay = true)

stepSize = input(0.5, "Step size", step = 0.1)
plot(close + stepSize, "Offset close", color = aqua)
```

Do not confuse a reserved word with a built-in name. `close`, `ema`, `plot` and `aqua` are not reserved: they are ordinary names in the global scope. Assigning to one tries to declare a second name with the same spelling, and the language has no shadowing (one name hiding another), so the error is `OS2002` rather than `OS1019`.

```openscript
close = 5
```

> **`bool` and `number` are type names, so they cannot be called as functions. The conversions are spelled `toBool()` and `toNumber()`. Writing `bool(x)` or `number(s)` is `OS1019`, and the fix names the working spelling.**

## Declarations

### `study`

**Form:** `study("Title", options...)`, the file's declaration.

Declares a file that computes and draws but places no orders. Every file carries exactly one declaration; write it directly under the `version` line. Calling an order function such as `buy()` from a study is `OS7001`. Every option is described on the [Declarations](/script/reference/declarations) page.

```openscript
version 1
study("RSI", precision = 2, range = [0, 100])

level(70, "Overbought", red)
level(30, "Oversold", lime)
plot(rsi(close, 14), "RSI", purple)
```

### `strategy`

**Form:** `strategy("Title", options...)`, the file's declaration.

Declares a file that plots and also places orders. It accepts every option `study` accepts and adds the trading options: starting capital, order size, product, fills, costs and pyramiding. A file with no declaration is `OS2007`, and a second declaration is `OS2008`.

```openscript
version 1
strategy("EMA cross", overlay = true, capital = 500000)

fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow)
    buy()
if crossDown(fast, slow)
    close()

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
```

## Control flow

### `if`

**Form:** `if condition`, a block header.

Runs the indented block below it when the condition is `true`. The condition must be a `bool` or `none`; a number or a string is `OS2011`, because the language has no truthiness (no rule that treats `0` or `""` as false). A condition that is `none`, such as a comparison during warmup, takes the false branch. That is the one place where an absent value is absorbed rather than passed on, because execution has to go somewhere.

```openscript
r = rsi(close, 14)
if r > 70
    signal("OVERBOUGHT")
plot(r, "RSI")
```

### `else`

**Form:** `else`, or `else if condition`, a block header.

The alternative branch of an `if`. `else if` is two words on one line and does not add indentation, so a chain of conditions stays flat instead of drifting to the right.

```openscript
if close > open
    barColor(lime)
else if close < open
    barColor(red)
else
    barColor(gray)
```

### `for`

**Form:** `for i = start to end`, optionally with `step n`, or `for item in array`.

The two loop forms. The counted form includes both ends, so `for i = 0 to 9` runs ten times. If the end is below the start and the step is positive (or the other way round), the body does not run at all, and the compiler warns with `OS8015` when it can see that; a range is never reversed for you. An absent start, end or step stops the script with `OS4013` rather than running the loop zero times. The loop variable belongs to the loop and may not be assigned in the body (`OS2006`).

```openscript
total = 0.0
for i = 0 to 9
    total += close[i]
plot(total / 10, "Ten bar mean")
```

Every iteration of every loop counts against a per-bar budget of 2,000,000 iterations. See [`limits`](#limits) to raise it.

### `to`

**Form:** `for i = start to end`.

Separates the two bounds of a counted loop. Both ends are inclusive.

```openscript
higher = 0
for i = 1 to 20
    if high[i] > high
        higher += 1
plot(higher, "Bars of the last 20 with a higher high than this bar")
```

### `step`

**Form:** `step n`, the optional third clause of a counted `for`.

Sets the increment. It defaults to `1`, and a descending loop has to say `step -1`. A step of `0` is `OS3004`, because it is the one loop that could never finish. Walking an array from the end with `step -1` is the standard way to remove elements while you loop, since removing one shifts every element after it.

```openscript
var zones: array<number> = []
push(zones, close)
for i = size(zones) - 1 to 0 step -1
    if element(zones, i) < close * 0.95
        remove(zones, i)
plot(size(zones), "Zones kept")
```

`step` is also a named argument of `input()` and `psar()`. That is correct code, because an argument label is not a name.

### `in`

**Form:** `for item in array`.

Introduces the array form of `for`. It visits the elements from index `0` up to the size measured when the loop starts, so elements appended during the loop are not visited. `in` is not a membership test: use `indexOf(arr, v) != -1` for that.

```openscript
levels = [22000.0, 22500.0, 23000.0]
above = 0
for lvl in levels
    if close > lvl
        above += 1
plot(above, "Levels below price")
```

### `while`

**Form:** `while condition`, a block header.

Repeats its block while the condition holds, checking it before each pass. The condition follows the same rule as `if`: a `bool`, or `none`, which ends the loop. It shares the per-bar loop budget with every other loop; running past the budget is `OS5001`, which stops the script rather than quietly breaking out of the loop.

```openscript
prices = [100.0, 250.0, 400.0]
i = 0
while i < size(prices) and prices[i] < close
    i += 1
plot(i, "Levels below the close")
```

The `and` in the condition matters: when `i` reaches the size of the array, the left side is `false`, so `prices[i]` is never read past the end.

### `break`

**Form:** `break`, a statement.

Leaves the innermost `for` or `while` at once. It is the only way out of a counted loop early, since the loop variable cannot be assigned. Outside a loop it is `OS1009`.

```openscript
firstUp = -1
for i = 0 to 20
    if close[i] > open[i]
        firstUp = i
        break
plot(firstUp, "Bars since the last up bar")
```

### `continue`

**Form:** `continue`, a statement.

Skips the rest of this pass and moves to the next iteration of the innermost loop. Outside a loop it is `OS1009`.

```openscript
total = 0.0
for i = 0 to 19
    if isNone(volume[i])
        continue
    total += volume[i]
plot(total, "Volume over 20 bars")
```

### `switch`

**Form:** `switch subject`, or a bare `switch`, a block header.

A statement that picks one arm. The value form compares a subject with each `case`; the condition form, with no subject, takes the first arm whose condition is true. Arms do not fall through. A name that the arms set must be declared before the `switch`, because a name first assigned inside an arm belongs to that arm and cannot be read after it.

```openscript
mode = input("fast", "Mode", options = ["fast", "slow", "verySlow"])

len = 14
switch mode
    case "fast"
        len = 9
    case "slow", "verySlow"
        len = 21

plot(sma(close, len), "SMA")
```

There is no expression form of `switch`. To choose a value, use the ternary `cond ? a : b`.

### `case`

**Form:** `case value`, `case valueA, valueB`, or `case condition` in a bare `switch`.

Opens one arm of a `switch`. A `case` may list several values separated by commas, all of the subject's type. Its block ends at the next `case` or `default`. A `case` outside a `switch` is `OS1017`.

```openscript
r = rsi(close, 14)
zone = 0
switch
    case r > 70
        zone = 1
    case r < 30
        zone = -1
plot(zone, "RSI zone")
```

### `default`

**Form:** `default`, the last arm of a `switch`.

Runs when no `case` matched. It is optional and must come last; a `default` followed by a `case` is `OS1017`. With no `default` and no match, nothing runs.

```openscript
r = rsi(close, 14)
zone = "mid"
switch
    case r > 70
        zone = "high"
    case r < 30
        zone = "low"
    default
        zone = "mid"
plot(zone == "high" ? 1 : zone == "low" ? -1 : 0, "RSI zone")
```

## Functions

### `fn`

**Form:** `fn name(parameters) => expression`, or `fn name(parameters) =>` followed by an indented block whose last expression is the result.

Declares a user function. Parameters may carry a type annotation and a default. The rules follow from the per-bar model, where the whole file runs once per bar:

- A function is declared at the top level of the file. Declaring one inside a block is `OS1023`.
- A function is not a value: it cannot be stored in a variable or passed as an argument.
- A function may not call itself, directly or through a cycle (`OS2005`). Write a loop instead.
- A function may be called above the line that declares it.
- State inside a function, a `var` or a call such as `ema()`, belongs to each call site separately, so two calls in two places keep two separate states.

```openscript
version 1
study("Z-score", precision = 2)

fn zscore(src, len) =>
    m = sma(src, len)
    s = stdev(src, len)
    (src - m) / s

plot(zscore(close, 20), "Z-score of the close", aqua)
level(0, "Mean", gray)
```

### `return`

**Form:** `return expression`, or a bare `return`.

Leaves a function at once with that value, or with `none` when bare. A function whose last statement is an expression returns it without `return`, so the keyword is needed only to leave early.

```openscript
fn clampTo(x, lo, hi) =>
    if x < lo
        return lo
    if x > hi
        return hi
    x

plot(clampTo(rsi(close, 14), 30, 70), "RSI held between 30 and 70")
```

## Persistence

### `var`

**Form:** `var name = initial`, optionally `var name: type = initial`.

Declares a value that is set once, on the first bar the line is reached, and then keeps whatever it holds from bar to bar. Without `var`, a name is computed afresh on every bar. The initial value is required: `var total` alone is `OS1011`, and the fix is `var total = none`.

```openscript
var barCount = 0
barCount += 1
plot(barCount, "Bars so far")
```

On the newest bar of a moving chart, a `var` is restored before each re-run of that bar to what it held at the end of the previous bar, so running the bar ten times gives the same answer as running it once. See [Persistence](/script/language/persistence).

### `live`

**Form:** `live var name = initial`.

Declares a persistent value that does not roll back when the newest bar runs again. Use it only when counting the updates of a moving bar is the point. A script that uses it gives different numbers on a moving chart than in a backtest over the same bars, which is why the compiler reminds you with warning `OS8011`.

```openscript
live var updates = 0
updates += 1
plot(updates, "Executions so far")
```

## Logic

### `and`

**Form:** `a and b`, a binary operator.

True when both sides are true. It uses three-valued logic, where `none` means unknown, and it short-circuits: the right side runs only when the left side is not `false`. `&&` does not exist; writing it is `OS1001`, and the fix names `and`.

```openscript
avgVolume = sma(volume, 20)
strong = close > open and volume > avgVolume
barColor(strong ? lime : none)
```

A call that keeps state, such as `ema()`, on the right of an `and` does not advance on a bar where the right side is skipped, and the compiler warns with `OS8001`. Compute such a call on its own line first, then combine the results, as `avgVolume` is above.

### `or`

**Form:** `a or b`, a binary operator.

True when either side is true, with the same three-valued logic. The right side runs only when the left side is not `true`. `||` does not exist.

```openscript
// On bar 0 bar.isFirst is true, so time[1], which is absent there, is never read.
newDay = bar.isFirst or not date.isSameDay(time, time[1])
background(newDay ? fade(aqua, 90) : none)
```

### `not`

**Form:** `not x`, a unary operator.

Boolean negation. `not none` is `none`. `!` is not an operator; `!cond` is `OS1001` and the fix names `not cond`.

```openscript
insideBar = high < high[1] and low > low[1]
barColor(not insideBar ? fade(gray, 60) : yellow)
```

## Literals

### `true`

**Form:** `true`, a literal of type `bool`.

One of the two boolean values. It is not the number `1` and does not mix with numbers.

```openscript
e = ema(close, 20)
upCross = crossUp(close, e)
downCross = crossDown(close, e)

var inTrend = false
if upCross
    inTrend = true
else if downCross
    inTrend = false
background(inTrend ? fade(lime, 90) : none)
```

### `false`

**Form:** `false`, a literal of type `bool`.

The other boolean value. It is not the number `0`.

```openscript
hideBand = input(false, "Hide the band")
upper = sma(close, 20) + 2 * stdev(close, 20)
plot(hideBand ? none : upper, "Upper band")
```

### `none`

**Form:** `none`, the absent value.

The value that means "there is nothing here": warmup, a division by zero, a bar with no data. It belongs to every type, so a `series number` may hold `none` on any bar. It passes through arithmetic and through `<`, `<=`, `>` and `>=`, but `==` and `!=` always answer `true` or `false`, so `x == none` works as a test. Plotting `none` leaves a gap. See [Absent values](/script/language/absent-values).

```openscript
ema20 = ema(close, 20)
trending = ema20 > ema(close, 50)
plot(trending ? ema20 : none, "EMA 20 while trending", aqua)
```

## Type names

### `number`

**Form:** `number`, in a type annotation.

The one numeric type: a finite 64-bit floating point value. There is no separate integer type, so a length, a bar count and a price are all `number`. A place that needs a whole number, such as a length or an index, refuses a fractional one with an error rather than rounding it for you.

```openscript
fn band(src: series number, len: number = 20, mult: number = 2) =>
    sma(src, len) + mult * stdev(src, len)

plot(band(close), "Upper band", aqua)
```

### `string`

**Form:** `string`, in a type annotation.

Text, as a sequence of Unicode code points. A string literal uses double or single quotes, which mean the same thing. There is no implicit conversion: `"count: " + 5` is `OS2003`, and the fix is `"count: " + text(5)`.

```openscript
fn tag(prefix: string, value: number) => prefix + " " + text(value, 2)

panel = table("Last bar", 1, 2, position = "bottomRight")
if bar.isLast
    cell(panel, 0, 0, tag("Close", close))
    cell(panel, 0, 1, tag("Range", high - low))
```

### `bool`

**Form:** `bool`, in a type annotation.

The boolean type, holding `true` or `false` and nothing else. The conversion to it is `toBool()`, not `bool(x)`.

```openscript
fn mostlyUp(cond: bool, len: number) => count(cond, len) > len / 2

plot(mostlyUp(close > open, 10) ? 1 : 0, "Mostly up bars")
```

### `color`

**Form:** `color`, in a type annotation.

The colour type: red, green, blue and alpha (opacity). A colour is written as a named colour such as `aqua`, as hex such as `#ff8800`, or built with a function such as `rgb()` or `fade()`. The word is also the name of an argument on `plot()`, `fill()`, `level()` and the drawing calls, and that is legal because a label is not a name.

```openscript
fn tint(hot: bool) => hot ? red : silver

barColor(tint(volume > 2 * sma(volume, 20)))
```

### `series`

**Form:** `series T`, in a type annotation.

Marks a value that carries one entry per bar. On a function parameter it says that `[]` inside the function reads the history of whatever the caller passed, so a helper such as a change over one bar works for any source. The compiler also infers this from how the parameter is used, so the annotation documents intent rather than being required.

```openscript
fn barChange(src: series number) => src - src[1]

plot(barChange(hlc3), "Change in typical price")
```

### `array`

**Form:** `array<T>`, in a type annotation.

Names an array type. `T` is `number`, `string`, `bool`, `color`, or an object type such as `line`, `box` or `table`. An array of arrays is not a type in version 1 (`OS2019`). An array is built with a bracket literal, and an empty one needs its element type from an annotation.

```openscript
var closes: array<number> = []
push(closes, close)
if size(closes) > 50
    shift(closes)
plot(avg(closes), "Mean of the last 50 closes")
```

## Reserved for a later version

These six words are reserved and do nothing in version 1. Using any of them as a name is `OS1019`.

### `as`

Intended for naming an import, alongside `import`. Nothing in version 1 accepts it.

```openscript
as = "alias"
```

### `import`

Intended for importing a user library file. Version 1 has no imports; a script is one file.

```openscript
import = 1
```

### `is`

Reserved for a later version. No construct in version 1 uses it, so do not plan around any particular meaning.

```openscript
is = true
```

### `map`

Intended for a keyed collection, `map<K, V>`, with `string` and `number` keys and iteration in insertion order so a script stays deterministic. Until then, keep two arrays side by side and index them together.

```openscript
map = 0
```

### `matrix`

Intended for a two-dimensional numeric container with the row and column operations that correlation and regression studies need.

```openscript
matrix = 0
```

### `type`

Intended for user-declared record types and the field access that goes with them. Its absence is why `draw.polyline()` takes two parallel arrays, of times and of prices, rather than one array of points.

```openscript
type = "long"
```

## Positional words

Two words belong to the grammar without being reserved. Each has a fixed place near the top of a file.

### `version`

**Form:** `version 1`, the first line of the file that is not blank and not a comment.

Declares the language version the file was written for. Version 1 is the only version today. Anything other than a comment or a blank line above it is `OS1021`.

The line is optional, but write it. Without it, the compiler warns with `OS8003` and compiles the file with the newest version it implements, so the file's meaning is tied to whichever release reads it. With it, the file is read by the version 1 rules for good: a script that compiles under version 1 compiles under every later release and produces the same numbers.

```openscript
version 1
study("Pinned to version 1", overlay = true)
plot(ema(close, 21), "EMA 21", orange)
```

### `limits`

**Form:** `limits(loops = n, history = n)`, on the line immediately after the declaration.

Sets the run's budgets. Both options are optional.

| Option | What it sets | Default |
|---|---|---|
| `loops` | The number of loop iterations allowed per bar, summed over every loop that runs on that bar | 2,000,000 |
| `history` | How many bars of each series the engine keeps for `[]` to read | Every bar of the data |

`limits` appears at most once and must sit directly under `study(...)` or `strategy(...)`; anywhere else is `OS3014`. Its values must be literal numbers, not inputs or expressions (`OS3015`). A host that will not run a budget you ask for says so with `OS5003` rather than quietly lowering it. It does not change the ceiling of 1,000,000 elements per array.

Raise `loops` only for a script whose loops genuinely run past two million iterations on one bar. Set `history` to keep memory bounded over a long run when you know how far back the script reads; reading further back than you kept is `OS4002`.

```openscript
version 1
study("Deep lookback")
limits(history = 500)

hits = 0
for i = 1 to 200
    if close[i] > close
        hits += 1
plot(hits, "Of the last 200 bars, closes above this one")
```

Neither `version` nor `limits` is reserved, and the compiler accepts either as an ordinary variable name further down a file. Pick another name anyway: a reader expects both words to mean the lines described here.

Related: [Operators](/script/reference/operators), [Types](/script/reference/types), [Declarations](/script/reference/declarations), [Script structure](/script/language/script-structure), [Control flow](/script/language/control-flow), [Functions](/script/language/functions).


## Operators

Source: https://openalgo.in/script/reference/operators

This page is the complete list of operator marks in OpenScript: arithmetic, comparison, logic, the ternary, history and member access, and the assignment forms. For each one it gives the types it accepts, the type it produces and what it does when an operand is `none`, the absent value.

Operators are where a script's arithmetic meets its warmup bars (the first bars, where an indicator does not have enough data yet), so the absence rules below decide what your first plotted bars look like. If you are learning the language, read the precedence table and the section on absent operands; the rest is reference.

```openscript
version 1
study("Body strength", precision = 2)

body     = close - open                          // binary minus
barRange = high - low
share    = barRange > 0 ? body / barRange : none // the ternary skips a zero range
avgVol   = sma(volume, 20)
heavy    = volume > 1.5 * avgVol and not isNone(share)

plot(share * 100, "Body as a percent of range", aqua, style = "histogram")
plot(heavy ? share * 100 : none, "On heavy volume", orange, style = "histogram")
level(0, "Zero", gray)
```

## Precedence and associativity

Operators on a higher row bind more tightly. Each row lists its associativity, which decides how a chain of operators from the same row groups.

| Level | Operators | Associativity | Notes |
|---|---|---|---|
| 1 | `(expr)`, `f(args)`, `a[i]`, `a.b` | Left | Grouping, call, history or element, member |
| 2 | unary `-`, unary `+`, `not` | Right | |
| 3 | `*`, `/`, `%` | Left | |
| 4 | binary `+`, binary `-` | Left | `+` also joins strings |
| 5 | `<`, `<=`, `>`, `>=` | None | Cannot be chained |
| 6 | `==`, `!=` | None | Cannot be chained |
| 7 | `and` | Left | Short-circuits |
| 8 | `or` | Left | Short-circuits |
| 9 | `cond ? a : b` | Right | Only the chosen arm runs |

```openscript
a = 2
b = 3
c = 4
x = a + b * c                 // a + (b * c), which is 14
y = -a % b                    // (-a) % b, which is -2
z = close > open ? "up" : close < open ? "down" : "flat"   // groups to the right
plot(x + y, "Sum, which is 12")
plot(str.length(z), "Length of the direction word")
```

Assignment is not in the table. `=` and the compound forms such as `+=` are statements, not operators, and they are listed [below](#assignment).

### How each associativity reads

| Associativity | Operators | `a op b op c` means |
|---|---|---|
| Left | `*`, `/`, `%`, binary `+`, binary `-`, `and`, `or` | `(a op b) op c` |
| Right | unary `-`, unary `+`, `not`, the ternary | `not not x` is `not (not x)`; `p ? a : q ? b : c` is `p ? a : (q ? b : c)` |
| None | `<`, `<=`, `>`, `>=`, `==`, `!=` | Not allowed: `a < b < c` is `OS1008`. Write `a < b and b < c` |

A comparison and an equality are on different levels, so `a < b == true` is legal: the comparison runs first. Two operators from the same non-chaining level are the only form refused.

## Grouping, calls, history and members

### `(` and `)`: grouping and calls

Parentheses group an expression to override precedence, at no cost at run time. After a name, they hold a call's arguments.

```openscript
mid = (high + low) / 2
plot(mid, "Midpoint")
```

A call takes positional arguments, named arguments, or positional followed by named. The compiler checks every call before the first bar runs:

| Mistake | Error |
|---|---|
| Too many arguments | `OS3001` |
| A required argument left out | `OS3012` |
| A named argument that does not exist | `OS3002`, and the message lists the names that do |
| The same argument given twice | `OS3013` |
| A positional argument after a named one | `OS3005` |
| An argument of the wrong type | `OS3011` |

### `[` and `]`: history, elements and array literals

One pair of brackets does three jobs, and the compiler always knows which from what is on the left.

| Written | When | Means | Result |
|---|---|---|---|
| `src[n]` | `src` is a series | The value `n` bars ago; `src[0]` is `src` | The series' value type |
| `arr[i]` | `arr` is an array | Element `i`, counting from 0 | The element type |
| `[a, b, c]` | At the start of an expression | An array literal | `array<T>` |

```openscript
prev  = close[1]              // history: the previous bar's close
var recent: array<number> = []
push(recent, close)
if size(recent) > 20
    shift(recent)
first = recent[0]             // element: the oldest value kept
plot(close - prev, "Change")
plot(first, "Oldest of the last 20")
```

A value has history, and so accepts `[n]`, when it is a built-in series such as `close`, a name declared at the top level of the file, a call that returns a series, or a series parameter of your own function. Anything else is `OS2004`: give the value a name at the top level and read that name.

| History case | Result |
|---|---|
| `n` is more than the bars so far | `none`, not an error and not the oldest bar |
| `n` is `none` | `none` |
| `n` is negative or not a whole number | `OS4001` when the bar runs |
| `n` is deeper than the retained history | `OS4002`, naming the depth that was kept |

An array index outside `0` to `size - 1`, or one that is not a whole number, is `OS4004`. It is an error rather than an absent value because an array has a size you chose, so an index past it is a mistake. When one line does both jobs, the explicit forms `history()` and `element()` say which is meant.

### `.`: member access

Reads a member of a namespace, such as `bar.isConfirmed`, `chart.lotSize` or `date.hour(time)`. The namespaces are `bar`, `chart`, `session`, `date`, `str`, `math`, `pos`, `order`, `leg`, `book`, `draw` and `req`. A member that does not exist is `OS2009`, and the fix suggests the nearest name.

```openscript
if bar.isConfirmed and close > high[1]
    signal("BREAKOUT")
```

### `,`: separator

Separates the arguments of a call, the elements of an array literal and the values of a `case`. A comma at the end of a line continues the statement onto the next line, which is how a long call is spread over several lines.

```openscript
levels = [22000.0, 22500.0, 23000.0]
plot(close, "Close",
     color = aqua,
     width = 2)
plot(size(levels), "Levels")
```

## Unary operators

### Unary `-` and `+`

Unary `-` negates a number. Unary `+` returns it unchanged. Both take a `number` and give a `number`; given `none`, both give `none`. A negative literal such as `-2` is this operator applied to `2`, and it parses as you expect inside an argument list.

```openscript
drop = -change(close)
plot(drop, "Fall since the last bar")
```

### `not`

Boolean negation: `bool` to `bool`, and `not none` is `none`. It is right associative, so `not not x` is legal. `!` is not an operator; `!cond` is `OS1001`, and the fix names `not`.

```openscript
if not bar.isConfirmed
    background(fade(yellow, 90))
```

## Arithmetic

| Operator | Operands | Result | With a zero or an absent operand |
|---|---|---|---|
| `*` | `number * number` | `number` | `none` if either side is absent, even `none * 0` |
| `/` | `number / number` | `number` | `none` when the divisor is zero, including `0 / 0` |
| `%` | `number % number` | `number`, the remainder of truncated division | `none` when the divisor is zero |
| `+` | `number + number`, or `string + string` | `number`, or the joined `string` | `none` if either side is absent |
| `-` | `number - number` | `number` | `none` if either side is absent |

A `number` in OpenScript is always finite. An operation with no finite result produces `none` instead of infinity, so a bad bar draws a gap rather than a spike and never poisons an average.

### `*` multiplication

```openscript
atrPercent = atr(14) / close * 100
plot(atrPercent, "ATR as a percent of price")
```

### `/` division

Division by zero is `none`, not an error, because one bad bar must not stop a study that is correct over fifty thousand others. You do not need a guard to avoid a crash; the plot simply has a gap on that bar. Reach for `orElse()` or a ternary only when you want a definite value there instead of a gap.

```openscript
gain  = rma(max(change(close), 0), 14)
loss  = rma(max(-change(close), 0), 14)
ratio = gain / loss            // none on a bar where the average loss is 0
plot(ratio, "Average gain over average loss")
```

### `%` remainder

The remainder of truncated division, so its sign follows the left operand: `-7 % 3` is `-1`. The library's `mod()` is the floored remainder, whose sign follows the right operand, so `mod(-7, 3)` is `2`. The two agree whenever the right operand is positive, which covers wrapping an index or a bar count.

```openscript
everyFifth = bar.index % 5 == 0
background(everyFifth ? fade(silver, 92) : none)
```

### `+` addition and concatenation

Adds two numbers or joins two strings, and nothing else. `"a" + 5` is `OS2003`: there is no implicit conversion anywhere in the language. Convert explicitly with `text()`. Joining follows the absence rule too, so `"a" + none` is `none`; to write an absent value into text on purpose, use `text(none)`, which is the string `"none"`.

```openscript
panel = table("Last close", 1, 1, position = "bottomRight")
message = "Close at " + text(close, 2)
if bar.isLast
    cell(panel, 0, 0, message)
```

### `-` subtraction

```openscript
body = close - open
plot(body, "Body", style = "histogram")
```

There is no exponent operator. Write `pow()`: `pow(x, 2)`.

## Comparison

### `<`, `<=`, `>`, `>=`: ordering

| Operands | Result |
|---|---|
| `number` against `number`, or `string` against `string` | `bool`, or `none` when either side is absent |

Strings compare by Unicode code point, which is the same on every machine and in every locale. It is not an alphabetical sort for people.

If either side is `none`, the result is `none`, not `false`. This keeps `a > b` and `a <= b` exact opposites on every bar. During warmup both are `none`, so neither branch runs, instead of one of them running on a guess.

```openscript
r = rsi(close, 14)
if r > 70
    background(fade(red, 92))
if r <= 30
    background(fade(lime, 92))
plot(r, "RSI", purple)
```

Comparisons cannot be chained: `a < b < c` is `OS1008`, and the fix is `a < b and b < c`.

### `==` and `!=`: equality

| Operands | Result |
|---|---|
| Two values of the same type, or any value against `none` | `bool`, never `none` |

Equality is the one place absence does not spread. `none == none` is `true`, `none == 5` is `false` and `5 != none` is `true`, so `x == none` is a working test, the same as `isNone()`.

That rule has a consequence worth seeing once. On bar 0, `dir[1]` is `none`, so `dir != dir[1]` is `true` there. The example guards the first bar so a flip is reported only between two real readings:

```openscript
dir  = close > open ? 1 : -1
flip = not isNone(dir[1]) and dir != dir[1]
if flip
    signal("FLIP")
plot(dir, "Direction")
```

| Comparing | Rule |
|---|---|
| Two values of different types | `OS2003` |
| Two colours | Equal when all four channels match |
| Two arrays, or two drawing objects | Equal only when they are the same one. `arrayEqual()` compares array contents |
| Two plot, fill or level handles | Not allowed |

## Logic

### `and`, `or`, `not`

`and` and `or` combine `bool` values with three-valued logic, where `none` means unknown. Both short-circuit: the right side runs only when it can still change the answer.

| `a` | `b` | `a and b` | `a or b` |
|---|---|---|---|
| `true` | `true` | `true` | `true` |
| `true` | `false` | `false` | `true` |
| `true` | `none` | `none` | `true` |
| `false` | any | `false` | `b` |
| `none` | `true` | `none` | `true` |
| `none` | `false` | `false` | `none` |
| `none` | `none` | `none` | `none` |

An unknown side is absorbed only when the other side settles the answer alone: a `false` under `and`, a `true` under `or`. Both operators give the same value with their sides swapped, so order never changes a result. Order does decide what runs:

| Expression | Left side is | Right side runs |
|---|---|---|
| `a and b` | `false` | No |
| `a and b` | `true` or `none` | Yes |
| `a or b` | `true` | No |
| `a or b` | `false` or `none` | Yes |

```openscript
// time[1] is absent on bar 0, but bar.isFirst is true there, so it is never read.
newDay = bar.isFirst or not date.isSameDay(time, time[1])
background(newDay ? fade(aqua, 90) : none)
```

A call that keeps state, such as `ema()` or a function holding a `var`, does not advance on a bar where it is skipped, and it is absent for that bar. The compiler warns with `OS8001`. Compute such a call on its own line first, then combine the results. `&&` and `||` do not exist; the operators are the words.

## The ternary

### `?` and `:`

**Form:** `cond ? a : b`. The condition is a `bool` or `none`. Both arms have the same type, or one arm is `none`; arms of different types are `OS2012`. Only the chosen arm runs. A `none` condition takes the second arm, the same rule as `if`.

```openscript
ema20 = ema(close, 20)
trending = ema20 > ema(close, 50)
plot(trending ? ema20 : none, "EMA 20 while trending", aqua)
```

The ternary is how you hide a plot on some bars: `plot()` is top level only, and wrapping it in an `if` is `OS3006`. It is also the place for a guard, such as a value you only want on some bars. Keep stateful calls out of the arms for the same reason as with `and` and `or`: an arm that is not chosen does not advance, and the compiler warns with `OS8001`.

## Assignment

Assignment is a statement, never an expression, so `if x = 5` is `OS1006` with the fix "write `==` to compare".

| Form | Means |
|---|---|
| `name = expression` | Declare the name in this scope, or update it if it already exists in an enclosing scope |
| `name += expression` | `name = name + expression` |
| `name -= expression` | `name = name - expression` |
| `name *= expression` | `name = name * expression` |
| `name /= expression` | `name = name / expression` |
| `name %= expression` | `name = name % expression` |

The compound forms obey every rule of the long form, including absence: `x += none` leaves `x` absent. A name's type is fixed by the first assignment that gives it a definite value, and assigning another type later is `OS2003`.

```openscript
version 1
study("Running volume")

var total = 0.0
total += orElse(volume, 0)      // one absent bar would otherwise make the total absent for good
plot(total, "Cumulative volume", silver, style = "area")
```

## What each operator does with `none`

| Operator | With an absent operand |
|---|---|
| Unary `-`, unary `+` | `none` |
| `*`, `/`, `%`, `+`, `-` | `none` if either side is absent |
| `+` on strings | `none` if either side is absent |
| `<`, `<=`, `>`, `>=` | `none` if either side is absent |
| `==`, `!=` | Never absent. `none == none` is `true` |
| `not` | `none` |
| `and`, `or` | See the three-valued table |
| `? :` condition | Takes the second arm |
| `? :` arm | Whatever the chosen arm gives, absent included |
| `src[n]` with `n` absent | `none` |

`none * 0` is `none`, not zero: the operand was unknown, not zero. One uniform rule means an absent value travels to the plot, where it draws a gap you can see, instead of turning into a number that looks right.

## Operators that do not exist

Each of these is refused when the script compiles. The table gives the error you see and what to write instead.

| Written | Error | Write instead |
|---|---|---|
| `!x` | `OS1001` | `not x` |
| `a && b`, `a \|\| b` | `OS1001` | `a and b`, `a or b` |
| `x ^ y`, `x ** y` | `OS1001` | `pow(x, y)` |
| `x++` | `OS1001` | `x += 1` |
| `x--` | `OS1022` | `x -= 1` |
| Bitwise `&`, `\|`, `~` | `OS1001` | Nothing; there are no bitwise operators in version 1 |
| `<<`, `>>` | `OS1008`, read as two comparisons | Nothing; there are no shift operators |
| `a < b < c` | `OS1008` | `a < b and b < c` |
| `a; b` on one line | `OS1007` | One statement per line |
| `{ ... }` | `OS1001` | Indentation. There are no braces |
| `/* ... */` | `OS1026` | `//` on each line |

## Functions that spell out an operator

Where an operator could be misread by a person, the library has a named function that says exactly what is meant.

| Function | Always means |
|---|---|
| `history()` | History, `n` bars back |
| `element()` | Element `i` of an array |
| `pow()` | Raise to a power |
| `mod()` | Remainder whose sign follows the divisor, unlike `%` |
| `arrayEqual()` | Arrays with equal contents, unlike `==` |
| `isNone()` | The same test as `x == none` |
| `orElse()` | The value when present, a fallback when absent |

## Index of marks

| Mark | Level | Means |
|---|---|---|
| `(` `)` | 1 | Grouping, or a call's arguments |
| `[` `]` | 1 | History, element access, or an array literal |
| `.` | 1 | Member of a namespace |
| `-` unary, `+` unary, `not` | 2 | Negation, identity, boolean not |
| `*` `/` `%` | 3 | Multiply, divide, remainder |
| `+` `-` | 4 | Add or join strings, subtract |
| `<` `<=` `>` `>=` | 5 | Ordering, absent if either side is. `<` and `>` also enclose the element type in `array<number>` |
| `==` `!=` | 6 | Equality, never absent |
| `and` | 7 | Conjunction |
| `or` | 8 | Disjunction |
| `?` `:` | 9 | The ternary. `:` also introduces a type annotation, as in `var x: number = 0` |
| `=` | Statement | Assignment. Also a named argument (`color = aqua`) and a parameter default (`len = 20`) |
| `+=` `-=` `*=` `/=` `%=` | Statement | Compound assignment |
| `,` | None | Separates arguments, elements and `case` values |
| `//` | None | A comment to the end of the line |
| `\` | None | Continues a statement on the next line |
| `=>` | None | Separates a function's parameters from its body |

Related: [Keywords](/script/reference/keywords), [Types](/script/reference/types), [Operators in the language guide](/script/language/operators), [Absent values](/script/language/absent-values), [Bars and history](/script/language/bars-and-history), [General functions](/script/reference/general).


## Types

Source: https://openalgo.in/script/reference/types

Every value in an OpenScript script has a type, and the compiler knows it before the first bar runs. This page lists every type in version 1, the three qualifiers that say when a value is known (fixed when the script compiles, fixed by the settings dialog, or new on every bar), and the annotations you can write to state a type yourself.

Types matter because OpenScript never converts one into another behind your back. `"count: " + 5` does not quietly become `"count: 5"`, and a number is never a condition. Knowing the list below turns most compile errors into something you can predict.

```openscript
version 1
study("Typed helpers", overlay = true)

lookback = input(20, "Lookback", min = 2, max = 200)   // number, fixed by the settings dialog
tint     = input(aqua, "Colour")                        // color, fixed by the settings dialog

fn band(src: series number, len: number, mult: number = 2) =>
    sma(src, len) + mult * stdev(src, len)

var highs: array<number> = []                          // an array of numbers
var marks: array<label> = []                           // an array of drawing objects

upper = band(close, lookback)                          // series number
push(highs, high)
if size(highs) > lookback
    shift(highs)

if close > upper
    push(marks, draw.label(time, high, "Above " + text(upper, 2)))
if size(marks) > 10
    draw.delete(element(marks, 0))
    shift(marks)

plot(upper, "Upper band", tint)
plot(max(highs), "Highest kept high", silver)
```

## The type list

| Type | Holds | Written as | Can be annotated |
|---|---|---|---|
| `number` | One finite number | `42`, `3.14`, `0xFF`, `1_000` | Yes |
| `string` | Text, as Unicode code points | `"BUY"`, `'BUY'` | Yes |
| `bool` | `true` or `false` | `true`, `false` | Yes |
| `color` | Red, green, blue and alpha (opacity) | `aqua`, `#ff8800`, `#ff880080` | Yes |
| `none` | The absent value | `none` | No, it belongs to every type |
| `series T` | One `T` per bar | No literal | Yes, as `series number` and so on |
| `array<T>` | An ordered, resizable list of one type | `[1, 2, 3]` | Yes |
| `line`, `label`, `box`, `polyline`, `table` | An object the script creates and keeps | Returned by the drawing calls and `table()` | Yes |
| `plot`, `fill`, `level` | A declaration handle, fixed before bar 0 | Returned by `plot()`, `plotCandles()`, `fill()` and `level()` | No |

There is no integer type and no date type, and you cannot declare a type of your own in version 1; the word `type` is reserved for that later. Some library tables show a parameter as `any`: that is a library function accepting several types, not a type you can write.

## Value types

### number

A `number` is a 64-bit floating point value that is always finite. It is the only numeric type, so a length, a bar count, a lot size and a price are all `number`, and no conversion between them exists to get wrong.

Where a whole number is required, such as a lookback length, an array index or a loop step, a fractional value is refused rather than rounded for you. The compiler reports `OS3004` when it can see the value; a value computed on a bar stops the script when that bar runs (`OS4003` for a length, `OS4004` for an array index). Round it yourself with `floor()` or `round()`, where a reader can see which way it goes.

An operation with no finite result, such as `1 / 0` or `sqrt(-1)`, gives `none` instead of infinity or not-a-number.

```openscript
len = input(21, "Slow length", min = 2, max = 500)
halfLen = floor(len / 2)          // 10, not 10.5, and the choice is visible
plot(sma(close, len), "Slow", orange)
plot(sma(close, halfLen), "Fast", aqua)
```

Number literals:

| Literal | Value |
|---|---|
| `42`, `3.14`, `.5` | Decimal, a leading digit is optional |
| `1_000_000` | Underscores group digits and mean nothing |
| `2.5e-4` | An exponent |
| `0xFF` | Hexadecimal, 255 |
| `010` | Ten. There is no octal form |

Time is a `number` too: milliseconds since 1 January 1970, in UTC. That is why `time` can be subtracted, compared and stored like any number, and why the [date functions](/script/reference/date) exist to read it as a calendar.

### string

Text, as a sequence of Unicode code points. Double and single quotes mean the same thing, so a string holding one kind needs no escape: `'He said "go"'`. The escapes are `\\`, `\"`, `\'`, `\n`, `\t`, `\r`, `\0` and `\uXXXX` with four hex digits; any other is `OS1005`. A string cannot run past the end of its line (`OS1004`); join pieces with `+` across lines instead.

`+` joins two strings, and `<`, `<=`, `>`, `>=` compare them by code point, the same on every machine. Everything else is in the [string functions](/script/reference/string).

```openscript
panel = table("Symbol", 1, 1, position = "bottomRight")
symbolLine = "NIFTY" + " " + text(close, 2)
if bar.isLast
    cell(panel, 0, 0, symbolLine)
```

### bool

`true` or `false`, and nothing else. A `bool` is not a number: `0` is not false, `1` is not true, and `""` is not false. A condition must be a `bool` or `none`; anything else is `OS2011`. To turn a condition into a number, write `cond ? 1 : 0`, or count it over a window with `count()`.

```openscript
upBar = close > open
plot(count(upBar, 20), "Up bars in the last 20")
```

### color

Red, green, blue and alpha, where alpha is the opacity. Write one of the nineteen named colours such as `aqua` or `orange`, a hex literal `#rrggbb` or `#rrggbbaa`, or build one with `rgb()`, `rgba()`, `fade()`, `withAlpha()` or `mix()`. Two colours are equal when all four channels match.

```openscript
tint = close > open ? lime : fade(red, 40)
barColor(tint)
```

See [Colors](/script/reference/color) for every named colour and function.

### none

The absent value, written `none`. It is the only value of its own type, and it also belongs to every other type, so a `series number` can hold `none` on any bar and a `string` name can hold it too. It is what a moving average holds before it has enough bars, what `close[1]` is on the first bar, and what a division by zero gives.

It passes through arithmetic and ordered comparison, `==` and `!=` always answer `true` or `false`, and a condition that is `none` takes the false branch. See [Absent values](/script/language/absent-values) and the functions `isNone()` and `orElse()`.

## Qualifiers: when a value is known

A type says what a value is. A qualifier says when it is settled, and some arguments accept only a value settled before the first bar.

| Qualifier | Settled | Examples |
|---|---|---|
| Constant | When the script compiles | Literals, arithmetic over literals, named colours, `math.pi`, `math.e`, and `rgb()`, `rgba()`, `fade()`, `withAlpha()`, `alpha()`, `mix()` over constants |
| Input | Before bar 0, by the settings dialog | A call to `input()`, and a name bound directly to one |
| Series | Afresh on every bar | `close`, `bar.index`, `ema(close, 9)`, any name computed from bar data |

Each level can stand wherever a later one is accepted: a constant works anywhere an input or a series does, and an input works anywhere a series does. The reverse is refused.

| Place | Accepts | Refused with |
|---|---|---|
| Options of `study()` and `strategy()` | A constant, or an input used as it is | `OS3003` |
| Arguments marked "Fixed before the first bar" in a reference table, such as a table's corner or a marker's shape | A constant, or an input used as it is | `OS3003` |
| `limits(loops = ..., history = ...)` | A literal number only | `OS3015` |
| Everything else | Any of the three | |

> **A name bound to a literal is not a constant to the compiler. `corner = "topLeft"` followed by `table("T", 1, 1, position = corner)` is `OS3003`. Write the literal in place, or make it a setting with `corner = input("topLeft", "Corner")`, which is accepted.**

An input must also be used as it is. A fixed field holds a value or a reference to one setting, so an expression computed from an input, such as `input(2, "Decimals") + 1` or `fade(tint, 50)` where `tint` is an input, is refused there. In release 0.5.0 that refusal is reported as `OS6018`, a message about a program that failed verification; the cause is the expression, and the fix is to make the setting itself hold the value the field needs.

```openscript
corner = input("topLeft", "Corner", options = ["topLeft", "topRight", "bottomLeft", "bottomRight"])
panel  = table("Last close", 1, 2, position = corner)   // an input is fixed before bar 0
if bar.isLast
    cell(panel, 0, 0, "Close")
    cell(panel, 0, 1, text(close, 2))
```

### Series and broadcast

A series is the per-bar history of a value: `series number` is one number per bar. Read bare, it gives this bar's value; `[n]` gives the value `n` bars back.

A plain value used where a series is expected is broadcast: treated as that same value on every bar. A series used where a plain value is expected means this bar's value. That is why `ema(close, 9)` works whether the length is a literal, an input or a series. Broadcast is the only automatic conversion in the language, and it changes no value.

A value has history, and accepts `[n]`, in four cases:

1. It is a built-in series, such as `open`, `close`, `volume`, `time` or `bar.index`.
2. It is a name declared at the top level of the file.
3. It is a call to a function that returns a series, such as `ema(close, 9)`.
4. It is a series parameter of your own function, where `[n]` reads the history of whatever the caller passed.

Anything else is `OS2004`. The facts in the [`chart` namespace](/script/reference/chart) are the clearest case: they are fixed for the whole run, so `chart.tickSize[1]` asks a question with no different answer and is refused.

```openscript
body = close - open                              // top level, so it has history
plot(body - body[1], "Change in body size")
```

An `input()` is a single value, with one exception: a source input such as `input(close, "Source")` returns a `series number`, because what the user picks is a series.

## Arrays

`array<T>` is an ordered, resizable list whose elements share one type. The element type is `number`, `string`, `bool`, `color`, or an object type: `line`, `label`, `box`, `polyline` or `table`.

| Rule | Detail |
|---|---|
| Not an element type | A series, a declaration handle or another array: `array<array<number>>` is `OS2019` |
| Empty literal | Takes its type from an annotation or from the first `push`, `unshift`, `insert` or `set` into it; with neither it is `OS2015` |
| Mixed literal | `["RSI", 14]` is `OS2013`; keep two arrays side by side |
| Assignment | Copies the reference, not the contents: two names, one array. `copy()` makes an independent array |
| `==` | True only for the same array. `arrayEqual()` compares contents |
| Index out of range | `OS4004` |
| Size | At most 1,000,000 elements (`OS5002`) |

```openscript
var window: array<number> = []
push(window, close)
if size(window) > 20
    shift(window)
plot(avg(window), "Mean of the last 20 closes", aqua)
```

A function with more than one output returns an `array<number>` of this bar's outputs, in the order its reference entry documents. The array is never absent and never changes length; each element has its own warmup and is `none` until it is ready.

```openscript
m = macd(close, 12, 26, 9)
plot(m[0], "MACD", aqua)
plot(m[1], "Signal", orange)
plot(m[2], "Histogram", gray, style = "histogram")
```

See [Collections](/script/reference/collections) for every array function.

## Drawing objects

`line`, `label`, `box`, `polyline` and `table` are runtime objects. The script creates them as bars arrive, keeps them, changes them and deletes them. An object is a reference, like an array: assigning it to a second name gives two names for one object, and `==` asks whether two names hold the same object.

An object lives from the bar that created it until the bar that deletes it with `draw.delete()` or `draw.deleteAll()`. Dropping every name that refers to it does not delete it; the chart keeps drawing it. A `table()` is never deleted; `clear()` empties its cells.

```openscript
var marks: array<label> = []
if crossUp(close, sma(close, 50))
    push(marks, draw.label(time, low, "Up", textColor = lime))
if size(marks) > 20
    draw.delete(element(marks, 0))
    shift(marks)
```

## Declaration handles

`plot`, `fill` and `level` are declaration handles. A call to `plot()`, `plotCandles()`, `fill()` or `level()` declares a fixed part of the study, a column, a band or a line, once, before bar 0. The handle it returns is that declaration, and it exists only for the compiler.

A handle can be named at the top level and passed to `fill()`, and nothing else. It cannot be held in a `var`, stored in an array, passed to or returned from your own function, compared, or used in arithmetic (`OS2003`). Its type name cannot be written in an annotation (`OS2016`).

```openscript
b = bollinger(close, 20, 2)
upper = plot(b[1], "Upper", aqua)
lower = plot(b[2], "Lower", aqua)
fill(upper, lower, color = fade(aqua, 88))
```

| Operation | `plot`, `fill`, `level` | `line`, `label`, `box`, `polyline`, `table` |
|---|---|---|
| Name at the top level | Yes | Yes |
| Name inside a block or a function | No | Yes |
| Hold in a `var` | No | Yes |
| Hold in an array | No | Yes |
| Pass to your own function, or return from one | No | Yes |
| Compare with `==` and `!=` | No | Yes, as identity |
| Read history with `[n]` | No | No |
| Arithmetic or a condition | No | No |

## Calls that return nothing

A call that acts rather than computes, such as `signal()`, `print()`, `background()` or `draw.delete()`, returns no value at all. That is not `none`: there is nothing to keep, so write the call as a statement on its own line.

## User types

Version 1 has no user-declared record types. Where a record would hold several fields, keep one array per field and index them together, as `draw.polyline()` does with its arrays of times and prices.

## Type annotations

The compiler infers every type, so annotations are optional. Write one to document intent, or where there is nothing to infer from, as with an empty array or a `var` that starts as `none`.

| Where | Form |
|---|---|
| A `var` declaration | `var name: T = initial` |
| A function parameter | `name: T`, or `name: T = default` |

An annotation is `series` in front of a value type, `array<T>`, or an object type.

```openscript
newDay = bar.isFirst or not date.isSameDay(time, time[1])
var dayHigh: series number = none
if newDay or high > dayHigh
    dayHigh = high
plot(dayHigh, "High of the day so far", style = "step")
```

```openscript
fn band(src: series number = close, len: number = 20) => sma(src, len)

basis = band()
var hits: array<number> = []
var lastSide: color = gray
if close > basis
    push(hits, close)
    lastSide = lime
else if close < basis
    lastSide = red
barColor(lastSide)
plot(size(hits), "Closes above the band so far")
```

Annotations do not go on a plain assignment (`x: number = 5` is a syntax error) or on a function's result: a function's type is inferred from its body. An annotation naming a type that does not exist, such as `integer` or `plot`, is `OS2016`, and a value that does not match its annotation is `OS2003`.

## How a name gets its type

A name's type is fixed by the first assignment that gives it a definite value, and assigning another type later is `OS2003`, however far apart the two lines are. `none` carries no type, so `var stop = none` waits for the first real value to fix it.

```openscript
var stop = none               // no type yet
if close > open
    stop = low                // number, from here on
plot(stop, "Stop")
```

A name that never receives a definite value is absent on every bar. Plotting it draws nothing, and the compiler warns with `OS8009`.

## Conversions

There is no implicit conversion between any two types. The conversions are four calls:

| Call | Takes | Gives |
|---|---|---|
| `text(x)` | Any value | `string`; `text(none)` is `"none"` |
| `text(x, decimals)` | `number` | `string` with fixed decimals |
| `toNumber(s)` | `string` | `number`, or `none` when the text is not a number |
| `toBool(x)` | `bool` or `none` | `bool`; `none` becomes `false` |

See `text()`, `toNumber()` and `toBool()` on the [General](/script/reference/general) page.

## Type errors you are likely to meet

| Code | Means | Usual fix |
|---|---|---|
| `OS2003` | Two types do not mix, or a name changed type | Convert with `text`, `toNumber` or `toBool`, or use a second name |
| `OS2004` | The value has no history | Name it at the top level of the file and read that name |
| `OS2011` | A condition is not a `bool` | Write the test out: `x > 0`, `isNone(x)`, `s != ""` |
| `OS2012` | The ternary's arms have different types | Make them agree, or use `none` for the empty arm |
| `OS2013` | An array literal mixes types | Split it into two arrays |
| `OS2015` | An empty array has no element type | Annotate it: `var hits: array<number> = []` |
| `OS2016` | An annotation names no type | Use `number`, `string`, `bool`, `color`, `array<T>` or an object type |
| `OS2019` | That type cannot be an array element | Use a value or object type |
| `OS3003` | A value that must be fixed before bar 0 depends on bar data | Use a literal or an `input()` |
| `OS3004` | A whole number was required | Wrap it in `floor()` or `round()` |

Related: [Types and values](/script/language/types-and-values), [Absent values](/script/language/absent-values), [Collections](/script/language/collections), [Objects and methods](/script/language/objects-and-methods), [Keywords](/script/reference/keywords), [Operators](/script/reference/operators).


## Declarations

Source: https://openalgo.in/script/reference/declarations

Every OpenScript file opens with the same set-up: the `version` line, one declaration, and an optional `limits` block. The declaration is either `study()`, for a script that computes and draws, or `strategy()`, for one that also places orders. Its options name the script, choose its pane and axis, and for a strategy set the money, the order size and the costs a backtest uses.

This page documents every option of both declarations with its type, its default and the values it accepts, and says what the /trading page does with each one in this release. Come here when you set up a new script, or when a backtest result depends on a default you did not choose.

```openscript
version 1
strategy("NIFTY futures EMA cross", overlay = true, precision = 2,
         capital = 500000, currency = "INR",
         qty = 1, qtyType = "lots", product = "intraday",
         fillOn = "nextOpen", slippage = 2,
         commission = 20, commissionType = "perTrade",
         pyramiding = 1)

fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow)
    buy()
if crossDown(fast, slow) and not pos.isFlat
    close(qty = 1)          // see qtyType for why a lots strategy states the quantity

plot(fast, "Fast EMA", aqua)
plot(slow, "Slow EMA", orange)
```

## The top of a file

| Line | Required | Rule |
|---|---|---|
| Comments | No | Comments and blank lines may come first |
| `version 1` | Recommended | The first line that is not blank and not a comment. Anything else before it is `OS1021` |
| `study(...)` or `strategy(...)` | Yes | Exactly one per file. None is `OS2007`, a second is `OS2008`. Write it directly under `version` |
| `limits(...)` | No | At most once, on the line immediately after the declaration (`OS3014`) |

```openscript
// Change over the last 100 bars.
version 1
study("Hundred bar change", precision = 2)
limits(history = 500)

plot(close - close[100], "Change over 100 bars", aqua)
```

## The version line

**Form:** `version 1`.

Declares which language version the file was written for. Version 1 is the only one today.

The line is optional and worth writing every time. A file without it is compiled with the newest language version the compiler implements, and the compiler warns with `OS8003`. A file with it is read by the version 1 rules for good: a script that compiles under version 1 compiles under every later release and produces the same numbers. A later version may add keywords, functions, options and types; it never changes what an existing construct means.

## Rules for every option

Both declarations are read once, before the first bar, because the legend, the axis and the settings dialog are built before any bar runs. So every option value must be fixed before bar 0:

| Written as | Accepted |
|---|---|
| A literal: `overlay = true`, `precision = 2` | Yes |
| Arithmetic over literals: `capital = 5 * 100000` | Yes |
| A bare `input()`, or a name bound to one: `precision = input(2, "Decimals")` | Yes |
| An expression computed from an input: `precision = input(2, "Decimals") + 1` | No. In release 0.5.0 this is reported as `OS6018`, a message about a program that failed verification; the cause is the expression |
| Anything that depends on bar data: `overlay = close > open` | No, `OS3003` |

The first argument is the title and may be written positionally or as `title = "..."`. Every other option is written by name. The compiler checks each one like a function argument:

| Mistake | Error |
|---|---|
| No title | `OS3012` |
| An option that does not exist, such as `capital` on a `study` | `OS3002`, listing the ones that do |
| A value of the wrong type, such as `overlay = 1` | `OS3011` |
| A string outside the accepted set, such as `scale = "middle"` | `OS3008`, listing the accepted values |
| A whole number outside its range, such as `precision = 11` or `pyramiding = 0` | `OS3004` |
| A `range` that is not two numbers, lowest first | `OS3016` |

A setting that a user changes in the settings dialog reaches a declaration option through its input. That is how a study lets a user choose its own decimals, or a strategy lets a user choose its capital, without editing the script.

```openscript
version 1
strategy("Sized by a setting", overlay = true,
         capital = input(500000, "Starting capital", min = 10000),
         qty = input(75, "Units per trade", min = 1))

trend = sma(close, 50)
if crossUp(close, trend)
    buy()
if crossDown(close, trend)
    close()
```

## study()

**Form:** `study(title, short, overlay, precision, format, range, scale, group, onUnconfirmed)`

Declares a script that computes and draws but places no orders. Calling an order function, or reading the `pos` namespace, in a study file is `OS7001`.

| Option | Type | Default | Accepted values |
|---|---|---|---|
| `title` | `string` | Required | Any text. First positional argument |
| `short` | `string` | The title | Any text |
| `overlay` | `bool` | `false` | `true` or `false` |
| `precision` | `number` | `4` | A whole number from 0 to 10 |
| `format` | `string` | `"price"` | `"price"`, `"percent"`, `"volume"` |
| `range` | `array<number>` | `none` | `[min, max]`, with `min` below `max` |
| `scale` | `string` | `"right"` | `"right"`, `"left"`, `"none"` |
| `group` | `string` | `""` | Any text |
| `onUnconfirmed` | `bool` | `false` | `true` or `false` |

### title

The script's name, shown in the chart legend and in the chart's indicator picker. It is the only required option. (The Scripts panel lists your scripts by file name, not by title.)

```openscript
version 1
study("Distance from the 50 EMA", precision = 2)
plot(close - ema(close, 50), "Close minus EMA 50", aqua, style = "histogram")
level(0, "Zero", gray)
```

### short

A shorter name, meant for a legend where space is tight. It is recorded with the compiled study for a host (the application that runs the script) that shows it. The /trading chart in this release shows the `title` in its legend and does not use `short`.

```openscript
version 1
study("Volume weighted average price, reset daily", short = "VWAP", overlay = true)

newDay = bar.isFirst or not date.isSameDay(time, time[1])
plot(vwapAnchor(hlc3, newDay), "VWAP", orange)
```

### overlay

Chooses the pane. `true` draws on the price chart, over the candles, which suits moving averages, bands and levels. `false`, the default, gives the study its own pane below the price, which suits oscillators and volume.

With `overlay = true`, Bollinger Bands drawn over the candles of a BHEL 15 minute chart:


With `overlay` left at `false`, a relative strength index in its own pane below the price:


```openscript
version 1
study("EMA 21", overlay = true)
plot(ema(close, 21), "EMA 21", orange)
```

### precision

How many decimals the study's axis and legend show, a whole number from 0 to 10. Use `2` for rupee prices and oscillators, `0` for counts.

```openscript
version 1
study("Up bars in 20", precision = 0)
plot(count(close > open, 20), "Up bars")
```

**Remarks.** `precision` and `format` apply to the plots drawn in the study's own pane. On an overlay study the plots sit on the price axis, which keeps the instrument's own formatting. A plot can set its own `precision` and `format` arguments, which take priority over the study's.

### format

How the axis and the crosshair write values:

| Value | Writes |
|---|---|
| `"price"` | A plain number with `precision` decimals |
| `"percent"` | The value followed by a percent sign. It does not multiply by 100, so plot `150` to see `150%` |
| `"volume"` | An abbreviated quantity, such as `1.25K`, `3.40M` or `1.00B` |

```openscript
version 1
study("Relative volume", format = "percent", precision = 0)
plot(volume / sma(volume, 20) * 100, "Volume as a percent of its mean")
```

The example multiplies the ratio by 100 itself, so a bar with one and a half times its average volume reads `150%`.

### range

Fixes the study pane's scale to `[min, max]` instead of fitting it to the data. Use it for a bounded oscillator so the pane does not rescale as values move. The default, `none`, lets the pane fit its data.

```openscript
version 1
study("RSI 14", precision = 2, range = [0, 100])
level(70, "Overbought", red)
level(30, "Oversold", lime)
plot(rsi(close, 14), "RSI", purple)
```

### scale

Which price scale the study is measured against: `"right"`, the default, `"left"`, or `"none"` to draw on a scale of its own with no axis shown. On an overlay, `"none"` lets a series with very different values, such as volume, share the price pane without squashing the candles.

```openscript
version 1
study("Volume on price", overlay = true, scale = "none")
plot(volume, "Volume", fade(gray, 50), style = "column")
```

### group

A category for the chart's indicator picker, so related scripts sit together. When you leave it empty, the /trading chart lists the script under OpenScript.

```openscript
version 1
study("High of the day", overlay = true, group = "Intraday")

newDay = bar.isFirst or not date.isSameDay(time, time[1])
var dayHigh = none
if newDay
    dayHigh = high
else if high > dayHigh
    dayHigh = high
plot(dayHigh, "High of the day so far", aqua, style = "step")
```

### onUnconfirmed

Whether signals, alerts and orders may happen on a bar that is still forming. By default they may not: a `signal()`, an `alert()` or an order call on the moving newest bar is held until the bar closes, and dropped if its condition no longer holds by then. That keeps a chart of history identical to what it showed at the time.

Setting `onUnconfirmed = true` lets them happen within the bar. Your script then guards itself with `bar.isConfirmed` where it needs to, and the compiler warns with `OS8002` on every higher timeframe read in the file, because that combination is where repainting (a past signal changing after the fact) comes from. See [Realtime and confirmation](/script/language/realtime-and-confirmation).

```openscript
version 1
study("Intrabar breakout", overlay = true, onUnconfirmed = true)

prevHigh = highest(high, 20)[1]
if close > prevHigh
    signal("BREAKING OUT")
plot(prevHigh, "20 bar high", aqua, style = "step")
```

## strategy()

**Form:** `strategy(title, ...every study option..., capital, currency, qty, qtyType, product, fillOn, slippage, commission, commissionType, pyramiding, closeOnSessionEnd)`

Declares a script that plots and places orders. It takes every `study()` option above, with the same defaults, plus the trading options below. One file both draws and trades, so the numbers you see and the numbers you trade are computed once. See [Strategies overview](/script/strategies/overview).

| Option | Type | Default | Accepted values |
|---|---|---|---|
| `capital` | `number` | `100000` | Starting equity for the backtest |
| `currency` | `string` | `""` | A label for money |
| `qty` | `number` | `1` | The order size when an order names none |
| `qtyType` | `string` | `"units"` | `"units"`, `"lots"`, `"cash"`, `"equityPercent"` |
| `product` | `string` | `"intraday"` | `"intraday"`, `"overnight"` |
| `fillOn` | `string` | `"nextOpen"` | `"nextOpen"`, `"close"` |
| `slippage` | `number` | `0` | Ticks of adverse slippage on every fill |
| `commission` | `number` | `0` | The cost, in the unit `commissionType` names |
| `commissionType` | `string` | `"perTrade"` | `"perTrade"`, `"perUnit"`, `"percent"` |
| `pyramiding` | `number` | `1` | A whole number, 1 or more |
| `closeOnSessionEnd` | `bool` | `false` | `true` or `false` |


### capital

The equity the backtest starts with. The report measures returns and drawdowns against it. The backtest does not model margin, so `capital` does not limit the size of an order: a position worth more than the capital still fills.

```openscript
version 1
strategy("Five lakh start", capital = 500000)

trend = sma(close, 20)
if crossUp(close, trend)
    buy()
if crossDown(close, trend)
    close()
```

### currency

A label for money, such as `"INR"`. It changes no number. It is recorded with the compiled strategy; the backtest report in this release labels money with the instrument's own currency, which the host supplies (the /trading Backtest panel uses INR), rather than with this option.

```openscript
version 1
strategy("Rupee report", currency = "INR")

trend = ema(close, 50)
if crossUp(close, trend)
    buy()
if crossDown(close, trend)
    close()
```

### qty

The order size used when an order call such as `buy()` does not name one. Its unit is set by `qtyType`.

```openscript
version 1
strategy("Fifty shares a trade", qty = 50)

trend = ema(close, 20)
if crossUp(close, trend)
    buy()
if crossDown(close, trend)
    close()
```

### qtyType

What the number in `qty`, and in an order's own `qty`, means.

| Value | `qty = 1` means | In a backtest |
|---|---|---|
| `"units"` | One unit: one share, or one unit of a contract. The default | Filled as written |
| `"lots"` | One lot, of `chart.lotSize` units. The natural choice for NFO futures and options and for MCX | Converted to units with the instrument's lot size; refused when the host states no lot size |
| `"cash"` | An amount of money, converted to a size at the fill price | Refused with `OS6021` in this release |
| `"equityPercent"` | A percentage of current equity | Refused with `OS6021` in this release |

The backtest refuses cash and equity sizing because it keeps no running equity to size against, and it says so rather than filling the number as written.

> **In release 0.5.0, a bare `close()` in a strategy declared with `qtyType = "lots"` sends the position's size in units as though it were a count of lots. On a contract whose lot is more than one unit, a backtest then sells far more than it holds and opens a large opposite position. Until this is fixed, close a lots strategy with the number of lots you hold, `close(qty = n)`, and guard it with `not pos.isFlat`: a close that states a quantity while the strategy is flat stops the run with `OS7017`. A strategy sized in units has no such problem.**

```openscript
version 1
strategy("Two lots a trade", qty = 2, qtyType = "lots")

trend = ema(close, 20)
if crossUp(close, trend)
    buy()
if crossDown(close, trend) and not pos.isFlat
    close(qty = 2)
```

### product

Whether positions are meant to close within the session, `"intraday"`, or may be carried to another session, `"overnight"`. It is recorded on every order the strategy sends. A backtest fills the same way under either value and does not square off an intraday position for you; see [closeOnSessionEnd](#closeonsessionend) for that.

```openscript
version 1
strategy("Positional swing", product = "overnight", qty = 50)

fast = ema(close, 20)
slow = ema(close, 50)
if crossUp(fast, slow)
    buy()
if crossDown(fast, slow)
    close()
```

### fillOn

Where an order decided on a bar is filled. `"nextOpen"`, the default, fills at the next bar's open, which is what the real market allows: a decision made from a bar's close cannot also be filled at that same close. `"close"` fills at the deciding bar's close, which is optimistic, so choose it only when you know your execution can do that.

```openscript
version 1
strategy("Honest fills", fillOn = "nextOpen")

trend = ema(close, 20)
if crossUp(close, trend)
    buy()
if crossDown(close, trend)
    close()
```

### slippage

Ticks of adverse slippage applied to every market and stop fill: a buy fills higher and a sell lower by this many ticks of the instrument's tick size. On a contract whose tick is 0.05, `slippage = 2` moves each fill by 0.10 against you. A limit order is never slipped. Set it before you read any result.

```openscript
version 1
strategy("Two ticks of slippage", slippage = 2)

trend = ema(close, 20)
if crossUp(close, trend)
    buy()
if crossDown(close, trend)
    close()
```

### commission

The cost charged on each fill, in the unit that `commissionType` names. The default is `0`, which makes every trade free and flatters every result.

```openscript
version 1
strategy("Twenty rupees an order", commission = 20, commissionType = "perTrade")

trend = ema(close, 20)
if crossUp(close, trend)
    buy()
if crossDown(close, trend)
    close()
```

### commissionType

What the `commission` number counts.

| Value | `commission` is | A round trip of 10 units, in at 102 and out at 104, costs |
|---|---|---|
| `"perTrade"` | A flat amount per order. The default | `commission = 20` costs 40: 20 on the entry, 20 on the exit |
| `"perUnit"` | An amount per unit traded | `commission = 2` costs 40: 2 on each of 10 units, twice |
| `"percent"` | A percentage of the traded value: `0.03` means 0.03 percent | `commission = 0.03` costs 0.62: 0.03 percent of 1,020 plus 1,040 |

```openscript
version 1
strategy("Percent of turnover", commission = 0.03, commissionType = "percent")

trend = ema(close, 20)
if crossUp(close, trend)
    buy()
if crossDown(close, trend)
    close()
```

### pyramiding

The most entries allowed in one direction at once. The default, `1`, means one entry. An entry beyond the limit is not quietly skipped: the backtest stops at that bar with `OS7008`. So a strategy that can signal again while it is already in a position checks the position before it enters, as the example does with `pos.size`.

```openscript
version 1
strategy("Add on strength", pyramiding = 3)

breakout = close > highest(high, 20)[1]
if breakout and pos.size < 3
    buy()
if close < lowest(low, 10)[1]
    close()
```

### closeOnSessionEnd

Asks for any open position to be flattened at the session's close, for an intraday strategy that must not carry a position overnight. It is recorded in the compiled strategy, but in release 0.5.0 the backtest does not act on it, so close explicitly before the session ends.

Two things decide how you do that:

- The exact test for the last bar is `session.isLastBar`, which needs the host to state the instrument's session hours. The /trading chart and Backtest panel do not state them in this release, so there it is `none`.
- With the default `fillOn = "nextOpen"`, an order decided on the session's last bar fills at the next bar's open, which is the next session's first bar, so the position is carried overnight anyway.

A window you name avoids both. The example starts closing at 15:15, so the exit fills at the next bar's open while the session is still trading, and it names the zone so the window is read in Indian time even where the host states no timezone.

```openscript
version 1
strategy("Flat by the close", overlay = true, product = "intraday", closeOnSessionEnd = true)

lateSession = session.isIn("1515-1530", "Asia/Kolkata")
fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow) and not lateSession
    buy()
if crossDown(fast, slow) or lateSession
    close()
```

## The limits block

**Form:** `limits(loops = n, history = n)`, on the line immediately after the declaration.

Sets the run's budgets. Both options are optional, and the block itself is optional.

| Option | Sets | Default |
|---|---|---|
| `loops` | Loop iterations allowed per bar, summed across every loop that runs on that bar | 2,000,000 |
| `history` | Bars of each series kept for `[n]` to read | Every bar of the data |

The rules:

- It appears at most once, directly under `study(...)` or `strategy(...)`. Anywhere else is `OS3014`.
- Its values are literal numbers, not inputs or expressions (`OS3015`), because the budget is settled before the program is loaded and a reader should see it in the file.
- An option it does not have, such as `depth`, is `OS3002`.
- Running past the loop budget stops the script with `OS5001` rather than breaking out of the loop and plotting a plausible wrong number.
- Reading further back than `history` keeps is `OS4002`, which names the depth that was kept.
- A host that will not run the budget you ask for says so with `OS5003` instead of quietly lowering it.
- Arrays stay capped at 1,000,000 elements whatever `limits` says.

Raise `loops` only when a script's loops genuinely run past two million iterations on one bar. Set `history` when you know how far back the script reads and want memory to stay bounded over a long run.

```openscript
version 1
study("Two hundred bar scan")
limits(history = 500)

hits = 0
for i = 1 to 200
    if close[i] > close
        hits += 1
plot(hits, "Of the last 200 bars, closes above this one")
```

See [Limits](/script/writing/limits) for the budgets a host enforces and how to stay inside them.

Related: [Keywords](/script/reference/keywords), [Types](/script/reference/types), [Script structure](/script/language/script-structure), [Inputs](/script/reference/input), [Costs and fills](/script/strategies/costs-and-fills), [Position and sizing](/script/strategies/position-and-sizing).


## Price and volume

Source: https://openalgo.in/script/reference/price-and-volume

These are the values a script reads without declaring anything: the bar's four prices, its volume and open interest, four averaged prices built from them, and the instant the bar opened. Each one is a `series number`: it has a value on every bar and accepts `[n]` to read an earlier bar, so `close[1]` is the previous bar's close.

Almost every study starts here, so it is worth knowing two things before you use them. On the newest bar of a moving chart, `high`, `low`, `close` and `volume` are still changing. And `volume` and `oi` are absent, not zero, on a bar for which the host (the application that runs the script and feeds it bars, such as the /trading page) supplies none.

```openscript
version 1
study("Bar anatomy", precision = 2)

body      = close - open
upperWick = high - max(open, close)
lowerWick = min(open, close) - low

plot(body, "Body", close >= open ? lime : red, style = "histogram")
plot(upperWick, "Upper wick", silver)
plot(-lowerWick, "Lower wick", gray)
```

| Value | On a bar still forming |
|---|---|
| `open` | Fixed at the first trade of the interval |
| `high` | Can only rise |
| `low` | Can only fall |
| `close` | The last traded price, so it moves both ways |
| `volume` | Grows |
| `time` | Fixed: the bar's opening instant |
| Anything read with `[1]` or older | Fixed for good |

## Prices of the bar

### open

```
open: series number
```

First value: bar 0

The price of the first trade in the bar's interval. On a 5 minute NIFTY futures chart, the 09:15 bar's `open` is the first traded price of the session, which is why gap studies compare it with the previous bar's close.

```openscript
version 1
study("Opening gap", precision = 2)

gap = open - close[1]
plot(gap, "Gap from the previous close", gap >= 0 ? lime : red, style = "histogram")
```

**Remarks.** `open` is fixed once the bar's first trade prints, so a condition built only from `open` and older bars does not change while the bar forms.

**See also.** `close`, `ohlc4`, `session.isFirstBar`

### high

```
high: series number
```

First value: bar 0

The highest traded price in the bar's interval. On the forming bar it can only rise, and it is final once the bar is confirmed.

```openscript
version 1
study("Twenty bar high", overlay = true)

prevHigh = highest(high, 20)[1]
plot(prevHigh, "Highest high of the previous 20 bars", aqua, style = "step")
if close > prevHigh
    signal("BREAKOUT")
```

**Remarks.** Take the window's high from the previous bar, `highest(high, 20)[1]`, when you test a breakout. The current bar's own high is always inside its own window, so `close > highest(high, 20)` can never be true.

**See also.** `low`, `highest()`, `hl2`

### low

```
low: series number
```

First value: bar 0

The lowest traded price in the bar's interval. On the forming bar it can only fall.

```openscript
version 1
study("Swing low stop", overlay = true)

stopLevel = lowest(low, 10)[1]
plot(stopLevel, "Lowest low of the previous 10 bars", red, style = "step")
```

**See also.** `high`, `lowest()`, `hl2`

### close

```
close: series number
```

First value: bar 0

The bar's closing price. On a bar that is still forming it is the latest traded price, so it moves up and down until the bar closes. It is the usual source for most indicators.

```openscript
version 1
study("Close and its mean", overlay = true)

plot(close, "Close", silver)
plot(sma(close, 20), "SMA 20", orange)
```

**Remarks.** `close` is the one name with two meanings. Read bare, it is this price. Written as a call, `close()` is the order that flattens a strategy's position, documented at `close()`. The compiler tells them apart by the brackets. In a study, `close()` is `OS7001`.

A condition on `close` can be true and then false within one forming bar. Signals, alerts and orders wait for the bar to close by default, so they act only on the final value. Read `close[1]` when you want a value that is already settled.

**See also.** `open`, `hlc3`, `bar.isConfirmed`

## Volume and open interest

### volume

```
volume: series number
```

First value: bar 0

The quantity traded during the bar: shares for an NSE or BSE equity, contracts or units for a derivative. On the forming bar it grows with every trade.

```openscript
version 1
study("Volume and its mean", format = "volume")

showVolume = chart.hasVolume != false     // true when the host says yes or says nothing
avgVolume = sma(volume, 20)
plot(showVolume ? volume : none, "Volume", close >= open ? fade(lime, 40) : fade(red, 40), style = "column")
plot(showVolume ? avgVolume : none, "20 bar mean", orange)
```

**Remarks.** `volume` is absent, not zero, on a bar for which the host supplies no volume. Zero is a real reading that means nobody traded; a volume nobody reported is a different fact. Anything computed from an absent `volume` is absent too, so a volume study draws a gap there rather than a flat line. Whether an index such as the NIFTY 50 arrives with no volume or with zeros depends on the data source, so use the futures contract when you need volume for an index.

`chart.hasVolume` says whether the host supplies volume at all, but a host may leave it unstated, and the /trading chart does in this release. That is why the example writes `chart.hasVolume != false`: it hides the volume only when the host says there is none.

**See also.** `chart.hasVolume`, `vwap()`, `obv()`, `relativeVolume()`

### oi

```
oi: series number
```

First value: bar 0

Open interest: the number of futures or options contracts outstanding at the end of the bar. It is what lets you read positioning on NFO and MCX contracts, which price and volume alone cannot show.

```openscript
version 1
study("Open interest change", format = "volume")

oiChange = oi - oi[1]
plot(oiChange, "Change in open interest", oiChange >= 0 ? aqua : orange, style = "histogram")
```

**Remarks.** `oi` is absent where the host supplies no open interest, which is the normal case for a cash equity or an index, and then the example draws nothing. Open interest is a level at a moment, while volume is a flow over the bar, so when bars are combined into a coarser timeframe, the coarser bar's open interest is the last reading inside it, never the sum.

The classic reading: price rising with open interest rising suggests new positions being built, and price rising with open interest falling suggests existing short positions being closed.

**See also.** `chart.hasOpenInterest`, `volume`, `change()`

## Averaged prices

Four combinations of the bar's prices, provided so you do not write the arithmetic by hand each time. Each has a value on bar 0 whenever the prices do.

### hl2

```
hl2: series number
```

First value: bar 0

The bar's midpoint, `(high + low) / 2`. It ignores where the bar opened and closed, so it suits studies that care about the range, such as channel midlines.

```openscript
version 1
study("Midpoint trend", overlay = true)
plot(ema(hl2, 21), "EMA of the midpoint", aqua)
```

**See also.** `hlc3`, `ohlc4`, `supertrend()`

### hlc3

```
hlc3: series number
```

First value: bar 0

The typical price, `(high + low + close) / 3`. It is what `vwap()` averages by default and what `cci()` measures, and it is a steadier source than `close` alone.

```openscript
version 1
study("Typical price mean", overlay = true)
plot(ema(hlc3, 21), "EMA of the typical price", orange)
```

**See also.** `hl2`, `hlcc4`, `vwap()`

### ohlc4

```
ohlc4: series number
```

First value: bar 0

The average of all four prices, `(open + high + low + close) / 4`. It smooths out a bar whose close landed at an extreme.

```openscript
version 1
study("Average price", overlay = true)
plot(sma(ohlc4, 10), "SMA of the average price", silver)
```

**See also.** `hlc3`, `hlcc4`

### hlcc4

```
hlcc4: series number
```

First value: bar 0

The close-weighted average, `(high + low + close + close) / 4`. It counts the close twice, so it follows the close more closely than `hlc3` while still using the range.

```openscript
version 1
study("Close weighted mean", overlay = true)
plot(ema(hlcc4, 20), "EMA of the close weighted price", aqua)
```

**See also.** `hlc3`, `ohlc4`, `close`

## Time of the bar

### time

```
time: series number
```

First value: bar 0

The instant the bar opened, as a number of milliseconds since 1 January 1970 in UTC. Because it is an ordinary number, you can subtract two times to get an elapsed duration, compare a bar's time with a date you built, or store it to find a bar again later.

```openscript
version 1
study("Minutes since the day's first bar", precision = 0)

newDay = bar.isFirst or not date.isSameDay(time, time[1])
var dayStart = none
if newDay
    dayStart = time

plot((time - dayStart) / 60000, "Minutes since the day's first bar", aqua, style = "step")
```

**Remarks.** The number is the same everywhere; turning it into a date or a clock time depends on a timezone. The [date functions](/script/reference/date) read it in the chart's timezone by default, which is Asia/Kolkata on the /trading chart, so `date.hour(time)` of the first NSE bar is 9 and `date.minute(time)` is 15.

Store `time`, not `bar.index`, when you want to recognise a bar later. A bar index shifts when more history loads; a time does not.

**See also.** `date.format()`, `date.hour()`, `chart.now()`, `session.isIn()`

### timeClose (planned, not available yet)

```
timeClose: series number
```

First value: bar 0

The instant the bar's interval ends, in UTC milliseconds. It is planned: until it arrives, add the interval to `time`, as `time + chart.intervalMinutes * 60000` on an intraday chart.

## Related

[Bar state](/script/reference/bar), [Chart facts](/script/reference/chart), [Bars and history](/script/language/bars-and-history), [Absent values](/script/language/absent-values), [Realtime and confirmation](/script/language/realtime-and-confirmation).


## bar.*

Source: https://openalgo.in/script/reference/bar

A script runs once per bar, oldest bar first. The `bar` namespace tells the script where it is in that run: the bar's position in the data, whether it is the first or the newest bar, and, on the newest bar of a moving chart, whether the bar has closed and how many times it has been executed.

These facts matter most at the two edges of the chart. At the left edge, `bar.isFirst` and `bar.index` help you seed state and skip warmup (the first bars, where an indicator does not have enough data yet). At the right edge, `bar.isLast` lets you write a table once, and `bar.isConfirmed` separates a bar that has finished from one that is still forming.

```openscript
version 1
study("Run position", overlay = true)

panel = table("Run", 3, 2, position = "topRight")

if bar.isLast
    cell(panel, 0, 0, "Bars loaded")
    cell(panel, 0, 1, text(bar.count))
    cell(panel, 1, 0, "Newest bar confirmed")
    cell(panel, 1, 1, bar.isConfirmed ? "yes" : "still forming")
    cell(panel, 2, 0, "Executions of this bar")
    cell(panel, 2, 1, text(bar.updates))
```

Every entry on this page is a series with a value from bar 0. Four of them are worked out by the engine from the data it was given. The other four describe the execution itself, and come from the host, the application that drives the script (such as the /trading chart): whether it added a bar or updated the newest one, and whether that bar has closed.

| Worked out from the data | Stated by the host about each execution |
|---|---|
| `bar.index`, `bar.count`, `bar.isFirst`, `bar.isLast` | `bar.isConfirmed`, `bar.isRealtime`, `bar.isNew`, `bar.updates` |

What those four read in the two usual cases:

| | `bar.isNew` | `bar.isConfirmed` | `bar.isRealtime` | `bar.updates` |
|---|---|---|---|---|
| Every bar of a history load, and every bar of a backtest | true | true | false | 1 |
| The newest bar of a chart following the market, as it forms | true on its first run, then false | false until its interval has elapsed, then true | true | 1, 2, 3 and on |

## Position in the data

### bar.index

```
bar.index: series number
```

First value: bar 0

The zero-based position of the bar being computed within the data the chart loaded. The oldest bar is 0, the next is 1, and so on. Use it to skip a warmup period or to act on every nth bar.

```openscript
version 1
study("Warmup mask", overlay = true)

sma50 = sma(close, 50)
warm = bar.index >= 100
plot(warm ? sma50 : none, "SMA 50, hidden for the first 100 bars", orange)
```

**Remarks.** `bar.index` is a position in the loaded data, not a permanent address. Loading more history shifts every index by the number of bars added. To remember a particular bar, store its `time`, which never moves.

**See also.** `bar.count`, `bar.isFirst`, `time`

### bar.count

```
bar.count: series number
```

First value: bar 0

How many bars have been seen so far, including this one: `bar.index + 1`. It is the natural divisor for a mean taken over the whole run.

```openscript
version 1
study("Mean close since the first bar", overlay = true)
plot(cum(close) / bar.count, "Mean of every close so far", silver)
```

**See also.** `bar.index`, `cum()`

### bar.isFirst

```
bar.isFirst: series bool
```

First value: bar 0

True on the oldest bar in the data, the one where `bar.index` is 0, and false on every other bar. Use it to seed a value, or to protect a read of `[1]`, which is absent on that bar.

```openscript
version 1
study("New day marker", overlay = true)

// bar.isFirst is true on bar 0, so or never evaluates time[1] there.
newDay = bar.isFirst or not date.isSameDay(time, time[1])
background(newDay ? fade(aqua, 90) : none)
```

**Remarks.** `bar.isFirst` is about the data, not the market. The oldest bar loaded is often in the middle of a session; `session.isFirstBar` is the test for the first bar of each trading session, where the host states the session's hours.

**See also.** `bar.index`, `session.isFirstBar`, `bar.isLast`

### bar.isLast

```
bar.isLast: series bool
```

First value: bar 0

True on the newest bar the chart has loaded. A table or a summary label shows only the current state, so write it when `bar.isLast` is true instead of on every bar of the history.

```openscript
version 1
study("Last close panel", overlay = true)

panel = table("Last close", 1, 2, position = "bottomRight")
if bar.isLast
    cell(panel, 0, 0, chart.symbol)
    cell(panel, 0, 1, text(close, 2))
```

**Remarks.** On a moving chart the newest bar is executed again on every update, so `bar.isLast` stays true across those updates until a new bar appears.

**See also.** `bar.isConfirmed`, `table()`, `cell()`

## State of the bar

### bar.isConfirmed

```
bar.isConfirmed: series bool
```

First value: bar 0

True when the bar's interval has elapsed and its values will not change again. It is true for every historical bar, and for the newest bar once its time is up. It is the flag a script uses to refuse to act on a bar that is still forming.

```openscript
version 1
study("Confirmed breakout", overlay = true, onUnconfirmed = true)

prevHigh = highest(high, 20)[1]
if bar.isConfirmed and close > prevHigh
    signal("BREAKOUT")
plot(prevHigh, "20 bar high", aqua, style = "step")
```

**Remarks.** By default a script does not need this guard for signals, alerts and orders: they wait for the bar to close anyway. The guard matters when the declaration sets `onUnconfirmed = true`, as the example does, or when you draw or write a table that should only change on a closed bar.

**See also.** `bar.isRealtime`, `bar.updates`, `signal()`

### bar.isRealtime

```
bar.isRealtime: series bool
```

First value: bar 0

True when a feed of the latest market data is driving updates to the bar, and false while the bars come from a one-time load of history. Only the newest bar can be realtime; every bar of a backtest has it false.

```openscript
version 1
study("Feed status", overlay = true)

panel = table("Feed", 1, 1, position = "topRight")
if bar.isLast
    cell(panel, 0, 0, bar.isRealtime ? "Following the market" : "History only")
```

**See also.** `bar.isConfirmed`, `bar.isNew`

### bar.isNew

```
bar.isNew: series bool
```

First value: bar 0

True when the latest update added a new bar, and false when it replaced the values of a bar that was already there. Every historical bar has it true, because each one arrived once. On a moving chart, the first execution of each new bar has `bar.isNew` true and the later updates of that bar have it false.

```openscript
version 1
study("Update anatomy", precision = 0)

plot(bar.updates, "Executions of this bar", silver)
plot(bar.isNew ? 1 : 0, "Added by the last update", aqua, style = "column")
```

**See also.** `bar.updates`, `bar.isRealtime`

### bar.updates

```
bar.updates: series number
```

First value: bar 0

How many times this bar has been executed, counting from 1. A historical bar runs once. The newest bar of a moving chart runs again on every update, and this number rises with each one.

```openscript
version 1
study("Updates per bar", precision = 0)

live var updatesSeen = 0
updatesSeen += 1
plot(bar.updates, "Executions of this bar", silver)
plot(updatesSeen, "Executions since the chart opened", aqua)
```

**Remarks.** `bar.updates` counts executions, not trades in the market; it is mostly a diagnostic. A plain `var` is restored before each re-execution of the newest bar, so counting updates yourself needs `live var`, as above, and the compiler warns with `OS8011` to remind you that such a count differs between a moving chart and a backtest.

**See also.** `bar.isNew`, `bar.isConfirmed`

## Related

[Execution model](/script/language/execution-model), [Realtime and confirmation](/script/language/realtime-and-confirmation), [Persistence](/script/language/persistence), [Price and volume](/script/reference/price-and-volume), [session.*](/script/reference/session).


## chart.*

Source: https://openalgo.in/script/reference/chart

The `chart` namespace answers questions about what the script is running on: which instrument, on which exchange, at what interval, in which timezone, and with what contract arithmetic (tick size, lot size, point value). A script that reads these facts instead of typing numbers in can work unchanged on an NSE stock, an NFO index future and an MCX contract, wherever the host states them.

Not every host states every fact. The /trading chart in this release passes the script only the symbol, the interval, the tick size and the timezone, so `chart.exchange`, `chart.lotSize`, `chart.instrumentType`, `chart.hasVolume` and the rest read as `none` there. The table under [Where the facts come from](#where-the-facts-come-from) shows what each part of /trading states, and each entry below says what to do where its fact is missing.

Every entry except `chart.now()` is a single value, fixed for the whole run. None of them has a history, so `chart.tickSize[1]` is `OS2004`: the answer could not have been different one bar ago.

## Where the facts come from

Every fact here comes from the host, the application that runs the script, and any of them may be missing. A fact the host does not state reads as `none`, never as a guess, so a script can tell "one lot is 75 units" from "nobody said".

In this release the /trading page states these facts:

| Fact | On the /trading chart | In the /trading Backtest panel |
|---|---|---|
| `chart.symbol` | Yes | Yes |
| `chart.exchange` | `none` | Yes |
| `chart.interval`, and `chart.intervalMinutes` and `chart.isIntraday` worked out from it | Yes | `none` |
| `chart.timezone` | Yes, `"Asia/Kolkata"` unless the chart is set otherwise | `none` |
| `chart.tickSize` | Yes | Yes |
| `chart.lotSize`, `chart.pointValue`, `chart.currency` | `none` | Yes |
| `chart.instrumentType`, `chart.hasVolume`, `chart.hasOpenInterest` | `none` | `none` |
| `chart.now()` | Yes | `none` |

So test a fact with `isNone()`, or give it a fallback with `orElse()`, before a calculation depends on it. The example below shows each fact, or "not stated" where the host gave none.

```openscript
version 1
study("Instrument facts", overlay = true)

fn shown(s: string) => isNone(s) ? "not stated" : s
fn shownNumber(x: number) => isNone(x) ? "not stated" : text(x)
fn yesNo(b: bool) => isNone(b) ? "not stated" : b ? "yes" : "no"

panel = table("Instrument", 7, 2, position = "topRight")

if bar.isLast
    cell(panel, 0, 0, "Symbol")
    cell(panel, 0, 1, shown(chart.symbol))
    cell(panel, 1, 0, "Exchange")
    cell(panel, 1, 1, shown(chart.exchange))
    cell(panel, 2, 0, "Type")
    cell(panel, 2, 1, shown(chart.instrumentType))
    cell(panel, 3, 0, "Interval")
    cell(panel, 3, 1, shown(chart.interval))
    cell(panel, 4, 0, "Tick size")
    cell(panel, 4, 1, shownNumber(chart.tickSize))
    cell(panel, 5, 0, "Lot size")
    cell(panel, 5, 1, shownNumber(chart.lotSize))
    cell(panel, 6, 0, "Volume supplied")
    cell(panel, 6, 1, yesNo(chart.hasVolume))
```


## The instrument

### chart.symbol

```
chart.symbol: string
```

First value: n/a

The instrument's symbol as the host names it, such as `"RELIANCE"` or `"SBIN"` for NSE equities. It is `none` when the host names none. Use it in labels and tables, or to adapt a script to one instrument.

```openscript
version 1
study("Symbol label", overlay = true)

if bar.isLast
    draw.label(time, high, chart.symbol + " " + text(close, 2), textColor = white)
```

**See also.** `chart.exchange`, `req.symbol()`

### chart.exchange

```
chart.exchange: string
```

First value: n/a

The exchange the instrument trades on, as the host codes it: for example `"NSE"` or `"BSE"` for cash equities, `"NFO"` for NSE futures and options, `"MCX"` for commodities, or `none` where the host does not say, as on the /trading chart. It is also the default exchange of `req.symbol()`, so a read of another instrument looks on the same exchange unless you name one.

```openscript
version 1
study("Exchange tag", overlay = true)

isDerivative = chart.exchange == "NFO" or chart.exchange == "MCX"
background(isDerivative ? fade(purple, 95) : none)
```

**Remarks.** The /trading chart does not state the exchange in this release, so there the value is `none` and both comparisons above are `false`: `==` never returns `none`, which keeps the example safe to run anywhere.

**See also.** `chart.symbol`, `chart.instrumentType`

### chart.instrumentType

```
chart.instrumentType: string
```

First value: n/a

What kind of instrument the chart shows. It is one of seven strings: `"equity"`, `"future"`, `"option"`, `"index"`, `"currency"`, `"commodity"` or `"other"`, or `none` when the host does not say. Branch on it when a study should behave differently on, say, an index and its future.

```openscript
version 1
study("Index or not", overlay = true)

isIndex = chart.instrumentType == "index"
// Weight the average by volume except on an index, which has none to weight by.
weighted = vwma(close, 20)
plain = sma(close, 20)
plot(isIndex ? plain : weighted, "Mean of 20 bars", isIndex ? orange : aqua)
```

**Remarks.** Pair this check with `chart.hasVolume` and `chart.hasOpenInterest` rather than assuming what each type supplies. The /trading page does not state the instrument type in this release.

**See also.** `chart.hasVolume`, `chart.optionType`

### chart.currency

```
chart.currency: string
```

First value: n/a

The currency label the host uses for money on this instrument, such as `"INR"`. It is a label for reports and tables; it changes no number.

```openscript
version 1
study("Value of one lot", overlay = true)

panel = table("Contract", 1, 2, position = "bottomRight")
lotValue = close * chart.lotSize
if bar.isLast
    cell(panel, 0, 0, "One lot, in " + orElse(chart.currency, "the instrument's money"))
    cell(panel, 0, 1, isNone(lotValue) ? "lot size not stated" : text(lotValue, 0))
```

**See also.** `chart.pointValue`, `chart.lotSize`

## Contract arithmetic

### chart.tickSize

```
chart.tickSize: number
```

First value: n/a

The instrument's smallest price step, such as 0.05 for many NSE contracts. It is `none` when the host has not said, rather than a guessed small number, so a script sizing a stop in ticks can tell "one tick is 0.05" from "nobody said".

```openscript
version 1
study("Stop twenty ticks below the low", overlay = true)

ticks = input(20, "Stop distance in ticks", min = 1)
stopLevel = low - orElse(chart.tickSize, 0.05) * ticks
plot(roundToTick(stopLevel), "Stop", red, style = "step")
```

**Remarks.** Anything derived from an absent tick size is absent too, which is why `roundToTick()` returns `none` in that state instead of an unrounded price that looks rounded. The example supplies a fallback with `orElse()` for the distance, and the plot is still absent if the host gives no tick size at all. Both the /trading chart and its Backtest panel state the tick size.

**See also.** `roundToTick()`, `roundToStep()`

### chart.lotSize

```
chart.lotSize: number
```

First value: n/a

How many units make up one lot. On NFO futures and options and on MCX, orders are placed in whole lots, and lot sizes are set by the exchange and revised from time to time, so read this value rather than typing a number into a script. It is `none` when the host has not said, which on /trading means on the chart: only the Backtest panel states it.

```openscript
version 1
study("Exposure of one lot", precision = 0)

lotValue = close * chart.lotSize
plot(lotValue, "Value of one lot", aqua)
```

**Remarks.** The /trading Backtest panel states the lot size, from the platform's instrument record. The /trading chart does not in this release, so there the example draws nothing, which is the honest answer: without a lot size there is no lot value. A strategy that sizes in lots can declare `qtyType = "lots"`, and then every order quantity is a count of lots of this size. See [Declarations](/script/reference/declarations#qtytype).

**See also.** `order.roundToLot()`, `chart.pointValue`

### chart.pointValue

```
chart.pointValue: number
```

First value: n/a

The money one point of price is worth for one unit of the instrument. For a cash equity it is normally 1: a one-rupee move is one rupee per share. It is `none` when the host does not know it. Multiply by the lot size to get the value of a point for a whole lot.

```openscript
version 1
study("Rupees per point, per lot", precision = 0)

perLot = chart.pointValue * orElse(chart.lotSize, 1)
atrMoney = atr(14) * perLot
plot(atrMoney, "Average true range in money, per lot", orange)
```

**Remarks.** The /trading Backtest panel states a point value of 1. The /trading chart does not state one in this release, so there the example draws nothing.

**See also.** `chart.lotSize`, `chart.currency`, `atr()`

## What the host supplies

### chart.hasVolume

```
chart.hasVolume: bool
```

First value: n/a

True when the host states that it supplies volume for this instrument, false when it states that it does not, and `none` when it says neither. Where it is false, `volume` is absent on every bar. Test this once instead of testing the value on every bar.

```openscript
version 1
study("Volume if available", format = "volume")

// Hide the column only when the host says there is no volume.
showVolume = chart.hasVolume != false
plot(showVolume ? volume : none, "Volume", fade(aqua, 40), style = "column")
```

**Remarks.** The /trading page does not state this fact in this release, so it is `none` there. `chart.hasVolume ? volume : none` would then hide the volume that is actually present; `chart.hasVolume != false` does not, because `none != false` is `true`.

**See also.** `volume`, `chart.hasOpenInterest`

### chart.hasOpenInterest

```
chart.hasOpenInterest: bool
```

First value: n/a

True when the host states that it supplies open interest for this instrument, as it can for futures and options, false when it states that it does not, and `none` when it says neither. Where it is false, `oi` is absent.

```openscript
version 1
study("Open interest if available", format = "volume")

showOi = chart.hasOpenInterest != false
plot(showOi ? oi : none, "Open interest", purple)
```

**See also.** `oi`, `chart.hasVolume`

## The chart's interval and clock

### chart.interval

```
chart.interval: string
```

First value: n/a

The chart's interval as the host names it: a count and a unit such as `"1m"`, `"5m"` or `"1h"`, a bare number of minutes such as `"60"`, or a letter such as `"D"`, `"W"` or `"M"`, which is how the /trading chart names its daily, weekly and monthly intervals. The unit letter is case sensitive: `"1M"` is a month and `"1m"` is a minute.

```openscript
version 1
study("Interval stamp", overlay = true)

if bar.isLast
    draw.label(time, high, chart.symbol + ", " + chart.interval, textColor = silver)
```

**See also.** `chart.intervalMinutes`, `req.timeframe()`

### chart.intervalMinutes

```
chart.intervalMinutes: number
```

First value: n/a

The chart's interval in minutes: 5 on `"5m"`, 60 on `"1h"` or `"60"`, 1440 on `"1D"`. It is `none` for an interval with no fixed length in minutes, such as a month, and `none` for an interval named with a bare letter, such as `"D"`. Use it to turn a duration into a count of bars.

```openscript
version 1
study("One hour of bars", overlay = true)

barsPerHour = chart.isIntraday ? max(1, round(60 / chart.intervalMinutes)) : 1
plot(sma(close, barsPerHour), "Mean of the last hour", aqua)
```

**Remarks.** A count derived this way assumes no bars are missing. Inside a session that holds; across a session break or a holiday it does not, so measure elapsed time with `time` where that matters.

**See also.** `chart.isIntraday`, `chart.interval`

### chart.isIntraday

```
chart.isIntraday: bool
```

First value: n/a

True when the chart's interval is shorter than one day, and false for a day or longer. Like `chart.intervalMinutes`, it is `none` when the interval is not stated or is named with a bare letter such as `"D"`, and a condition that is `none` takes the false branch. Session tools such as an opening range only make sense on an intraday chart, so this is the natural guard for them.

```openscript
version 1
study("Daily VWAP on intraday charts", overlay = true)

newDay = bar.isFirst or not date.isSameDay(time, time[1])
dayVwap = vwapAnchor(hlc3, newDay)
plot(chart.isIntraday ? dayVwap : none, "VWAP from the day's first bar", orange)
```

**See also.** `chart.intervalMinutes`, `vwapAnchor()`

### chart.timezone

```
chart.timezone: string
```

First value: n/a

The timezone the chart's time axis is labelled in, as an IANA name (the standard `Area/City` form) such as `"Asia/Kolkata"` for Indian exchanges. Every [date function](/script/reference/date) and `session.isIn()` reads time in this zone unless you pass another, so calendar fields agree with the axis you are looking at.

```openscript
version 1
study("Zone check", overlay = true)

panel = table("Clock", 2, 2, position = "bottomRight")
if bar.isLast
    cell(panel, 0, 0, "Chart zone")
    cell(panel, 0, 1, orElse(chart.timezone, "not stated"))
    cell(panel, 1, 0, "Newest bar opened")
    cell(panel, 1, 1, date.format(time, "dd MMM HH:mm"))
```

**Remarks.** The /trading Backtest panel states no timezone in this release. There a date or session call that relies on the default returns `none`; pass the zone explicitly, as in `date.hour(time, "Asia/Kolkata")`, in a strategy you backtest.

**See also.** `date.hour()`, `session.isIn()`

### chart.now()

```
chart.now() -> number
```

First value: n/a

The chart's wall clock, as UTC milliseconds. It is the only clock a script can read while a bar runs; everything else is a function of the bars. Use it to ask how old the newest bar is, not to compute anything on history.

```openscript
version 1
study("Bar age", precision = 0)

ageMinutes = (chart.now() - time) / 60000
plot(bar.isLast ? ageMinutes : none, "Minutes since the newest bar opened", silver)
```

**Remarks.** It is a call rather than a value because it is the one thing in the language that is not fixed for the run. The host decides what it returns, so a test can fix it and a script that uses it can still be reproduced. A backtest has no wall clock: in the /trading Backtest panel it is `none`.

**See also.** `time`, `bar.isRealtime`

## Planned

These facts are named in the language and not available in this release; using one is `OS2020`. Each will read from the instrument the host supplies.

### chart.expiry (planned, not available yet)

```
chart.expiry: number
```

First value: n/a

The expiry instant of a futures or options contract, in UTC milliseconds, so a script can count the days or bars left before expiry.

### chart.strike (planned, not available yet)

```
chart.strike: number
```

First value: n/a

The strike price of an options contract, for studies that compare an option's premium with how far the underlying is from its strike.

### chart.optionType (planned, not available yet)

```
chart.optionType: string
```

First value: n/a

Whether an options contract is a call or a put: `"call"`, `"put"`, or `""` for an instrument that is not an option or where the host has not said.

### chart.isReplay (planned, not available yet)

```
chart.isReplay: bool
```

First value: n/a

True when the chart's bars are being replayed one at a time rather than loaded whole, so a script can tell a replay from a history load.

## Related

[Price and volume](/script/reference/price-and-volume), [session.*](/script/reference/session), [date.*](/script/reference/date), [Other instruments](/script/data/other-instruments), [Timeframes](/script/data/timeframes), [Position and sizing](/script/strategies/position-and-sizing).


## session.*

Source: https://openalgo.in/script/reference/session

A trading session is what an exchange opens and closes: 09:15 to 15:30 IST for NSE and BSE equities and for NFO futures and options, longer hours for MCX. The `session` namespace tells a script where each bar sits in that session, so an opening range, a daily reset or a square-off before the close follows the exchange's hours rather than the calendar.

Three members work today, and they get their answers from two different places. The host in the table is the application that runs the script, such as the /trading page.

| Member | Answers from | Where it is `none` |
|---|---|---|
| `session.isFirstBar`, `session.isLastBar` | The instrument's own session hours, which the host states | Wherever the host states no session hours, which includes the /trading chart and Backtest panel in this release |
| `session.isIn()` | A window of clock times you write in the script | Wherever no timezone is known; name one with the `zone` argument to be safe |

The rest of the namespace is planned and listed at the end of this page.

So on the /trading page today, build session logic on `session.isIn()`, and use the first two where the host states the hours, with a fallback for where it does not. The example below needs nothing from the host except a timezone: it holds the high and low of the first fifteen minutes of each NSE day.

```openscript
version 1
study("Opening range", overlay = true, precision = 2)

inRange = session.isIn("0915-0930")
rangeStarts = inRange and not orElse(inRange[1], false)

var rangeHigh = none
var rangeLow = none
if rangeStarts
    rangeHigh = high
    rangeLow = low
else if inRange
    rangeHigh = max(rangeHigh, high)
    rangeLow = min(rangeLow, low)

plot(rangeHigh, "Range high", aqua, style = "step")
plot(rangeLow, "Range low", orange, style = "step")
```

`rangeStarts` is true on the first bar inside the window: the bar is in it and the bar before was not. `orElse()` turns the absent `inRange[1]` of the very first bar into `false`.

> **Reset per-day state on the start of the session, not on a change of date, wherever the host lets you. A session and a calendar day line up for a 09:15 to 15:30 session, but a session that runs past midnight is one session and two dates, and a holiday is one date and no session.**

## Session boundaries

### session.isFirstBar

```
session.isFirstBar: series bool
```

First value: bar 0

True on the first bar of each trading session and false on every other bar. It is the bar to reset anything that is measured per session: the day's high and low, a running volume, a count of trades.

```openscript
version 1
study("Session high and low", overlay = true)

// The session's own first bar where the host states session hours,
// otherwise the first bar of each calendar day.
firstBar = orElse(session.isFirstBar, bar.isFirst or not date.isSameDay(time, time[1]))

var dayHigh = none
var dayLow = none
if firstBar
    dayHigh = high
    dayLow = low
else
    dayHigh = max(dayHigh, high)
    dayLow = min(dayLow, low)

plot(dayHigh, "Session high", lime, style = "step")
plot(dayLow, "Session low", red, style = "step")
```

**Remarks.** It is the first bar delivered inside the session's hours, so a session that opened late still has a first bar. The oldest bar of the chart counts as a first bar too when the data starts in the middle of a session, which makes the first session on the chart a partial one. A bar outside the session's hours has `false`.

It comes from the session hours in the instrument's record, read in the instrument's timezone. When the host states no session for the instrument, the value is `none`, and an `if` on it never runs. That is why the example wraps it in `orElse()`: on the /trading page, which states no session hours in this release, the example falls back to a change of calendar day, which is the same thing for an NSE session.

**See also.** `session.isLastBar`, `bar.isFirst`, `vwap()`

### session.isLastBar

```
session.isLastBar: series bool
```

First value: bar 0

True on the last bar of each session's schedule. It is worked out from the scheduled close, not from the arrival of the next bar, so it is known while that bar is still running. On a 5 minute NSE chart it is the 15:25 bar. On a day when trading stops early, the scheduled last bar never arrives, so no bar of that day has it true.

```openscript
version 1
strategy("Intraday only", overlay = true, product = "intraday", fillOn = "close")

// The session's last bar where the host states session hours,
// otherwise the last fifteen minutes of the NSE day.
squareOff = orElse(session.isLastBar, session.isIn("1515-1530", "Asia/Kolkata"))

fast = ema(close, 9)
slow = ema(close, 21)

if crossUp(fast, slow) and not squareOff
    buy()
if crossDown(fast, slow) or squareOff
    close()
```

**Remarks.** Waiting for the next session's first bar to flatten is too late: by then the position has been carried overnight. Watch the fill rule too. With the default `fillOn = "nextOpen"`, an order decided on the last bar fills at the next bar's open, which is the next session's first bar. The example declares `fillOn = "close"` so the exit fills at the close of the bar that decided it. The other way is to decide earlier, with a window such as `session.isIn("1515-1530")`, and keep the default fill.

It needs the chart's interval as well as the session hours, to know which bar slot is last. When the host does not state both, the value is `none`, which is why the example falls back to a window. The window names its zone because the /trading Backtest panel states no timezone.

**See also.** `session.isFirstBar`, `close()`, `bar.isLast`

## Windows you name

### session.isIn()

```
session.isIn(spec: string, zone?: string = chart.timezone) -> series bool
```

| Parameter | Type | Default |
|---|---|---|
| spec | string | required |
| zone | string | chart.timezone |

First value: bar 0

True when the bar falls inside a window of clock times you write, such as `"0915-1000"` for the first forty-five minutes of the NSE session or `"1430-1530"` for the last hour. Use it to trade only part of the day, to shade a period, or to hold a range while it forms. It needs nothing from the host except a timezone, so it works on every chart.

```openscript
version 1
study("Entry window", overlay = true)

window = input("0930-1445", "Entry window")
inWindow = session.isIn(window)

fast = ema(close, 9)
slow = ema(close, 21)
crossed = crossUp(fast, slow)

if inWindow and crossed
    signal("BUY")
background(inWindow ? none : fade(gray, 92))
```

The `spec` string is `"HHMM-HHMM"`, with an optional list of days after a colon.

| Spec | Means |
|---|---|
| `"0915-1530"` | Every day, from 09:15 up to 15:30 |
| `"0915-1530:12345"` | The same window, Monday to Friday only |
| `"0900-2330"` | A long day window, such as an MCX session |
| `"2300-0500"` | An overnight window: an end before the start crosses midnight |
| `"0915-0915"` | An empty window that matches nothing, not a full day |

**Remarks.** The window starts at the first time and stops before the second: a bar that opens at 15:30 is outside `"0915-1530"`, and the 15:25 bar is inside. The test uses the bar's opening time, `time`. Two windows written back to back, such as `"0915-1200"` and `"1200-1530"`, cover every minute exactly once. An end of `2400` means midnight at the end of the day.

Days are numbered 1 for Monday through 7 for Sunday, the same as `date.dayOfWeek()`. For a window that crosses midnight, the day list names the day the window opened on: `"2300-0100:1"` covers Monday 23:00 to Tuesday 01:00.

The times are read in the chart's timezone unless `zone` names another IANA zone (the standard `Area/City` form), such as `"Asia/Kolkata"`. Where no timezone is known, as in the /trading Backtest panel in this release, the result is `none` unless you pass `zone`. A zone the host does not know stops the script with `OS6005`; abbreviations such as `"IST"` are not zone names.

A spec that does not follow the form above, such as `"9:15-15:30"`, is not caught by the compiler and matches no bar: the result is `none`, so a condition built on it never holds. Check the spelling when a window never lights up.

**See also.** `session.isFirstBar`, `date.hour()`, `chart.timezone`

## Planned

These session facts are named in the language and not available in this release; using one is `OS2020`. Each will be worked out from the instrument's session hours.

### session.isOpen (planned, not available yet)

```
session.isOpen: series bool
```

First value: bar 0

True when the bar falls inside the instrument's own trading session, so a strategy can refuse to place an order outside market hours.

### session.startTime (planned, not available yet)

```
session.startTime: series number
```

First value: the session's first bar

The instant the bar's session opened, in UTC milliseconds, for measuring time since the open. Until then, store `time` in a `var` on the session's first bar.

### session.endTime (planned, not available yet)

```
session.endTime: series number
```

First value: the session's first bar

The instant the bar's session is scheduled to close, in UTC milliseconds, for measuring the time left before the close.

### session.barIndex (planned, not available yet)

```
session.barIndex: series number
```

First value: bar 0

The bar's position within its session, 0 on the session's first bar. Until then, count bars in a `var` that resets on the session's first bar.

### session.nextOpen (planned, not available yet)

```
session.nextOpen: series number
```

First value: bar 0

The instant the next session opens, in UTC milliseconds.

### session.isHoliday() (planned, not available yet)

```
session.isHoliday(t: number) -> bool
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |

First value: n/a

Whether a date is a trading holiday, once the host supplies an exchange holiday calendar. Until then, a holiday shows up in the data as a day with no bars.

## Related

[Sessions and time](/script/data/sessions-and-time), [date.*](/script/reference/date), [bar.*](/script/reference/bar), [chart.*](/script/reference/chart), [Price and volume](/script/reference/price-and-volume), [Exits and brackets](/script/strategies/exits-and-brackets).


## date.*

Source: https://openalgo.in/script/reference/date

Every instant in OpenScript is a plain `number`: milliseconds since 1 January 1970, in UTC. That is what `time` holds for each bar. The `date` namespace turns such a number into calendar and clock fields (a year, a weekday, an hour), turns fields back into a number, rounds an instant back to the start of its day, week or month, and writes it as text.

You need these functions whenever a rule depends on the calendar: a weekly anchor, a label with the bar's date, a filter on the first half hour of trading. For rules that follow the exchange's hours, the [session functions](/script/reference/session) are usually the better tool.

```openscript
version 1
study("Week and month anchors", overlay = true)

newWeek  = date.startOfWeek(time) != date.startOfWeek(time[1])
newMonth = date.month(time) != date.month(time[1])

weekVwap = vwapAnchor(hlc3, newWeek)
plot(weekVwap, "Weekly VWAP", aqua)
background(newMonth ? fade(orange, 85) : none)

if newMonth
    draw.label(time, high, date.format(time, "MMM yyyy"), textColor = orange)
```

On the first bar, `time[1]` is absent, so both comparisons are `true` there (`!=` never returns `none`): the first bar on the chart starts a week and a month of its own.

## The timezone every field is read in

A timestamp is the same number everywhere, but its calendar fields depend on a timezone: 03:45 UTC is 09:15 in India. Every function on this page reads a timestamp in the chart's timezone, `chart.timezone`, unless you pass a `zone` argument. The /trading chart's zone is `"Asia/Kolkata"`, so `date.hour(time)` of the first NSE bar is 9, matching the labels on the chart's own axis.

| `zone` argument | Result |
|---|---|
| Left out | The chart's timezone, or `none` when the host (the application running the script) states no timezone |
| An IANA name (the standard `Area/City` form) such as `"Asia/Kolkata"` or `"Europe/London"`, or `"UTC"` | That zone |
| An abbreviation such as `"IST"`, or a name the host does not know | `OS6005` when the bar runs, which stops the script |

> **The /trading Backtest panel states no timezone in this release, so in a backtest every call on this page that leaves out `zone` returns `none`. In a strategy you backtest, pass the zone: `date.hour(time, "Asia/Kolkata")`.**

A zone is always a name, never a fixed offset, because an offset is wrong for half the year anywhere that moves its clocks. Where clocks do move, a wall clock time that was skipped resolves to the instant it would have been, and a time that happened twice resolves to the first of the two. India does not move its clocks, so none of this affects an Indian chart.

None of these functions has a warmup: given a timestamp that is present and a timezone, each gives a value on bar 0. Given `none`, such as `time[1]` on the first bar, each gives `none`.

## Calendar fields

### date.year()

```
date.year(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

The calendar year of the timestamp, such as 2025. Use it to split a study by year or to mark where one begins.

```openscript
version 1
study("Year marker", overlay = true)

newYear = not isNone(time[1]) and date.year(time) != date.year(time[1])
if newYear
    draw.label(time, high, text(date.year(time)), textColor = silver)
```

**Remarks.** The `not isNone(time[1])` guard keeps the first bar on the chart from counting as the start of a year.

**See also.** `date.month()`, `date.dayOfYear()`

### date.month()

```
date.month(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

The month of the timestamp, 1 for January through 12 for December.

```openscript
version 1
study("Quarter ends", overlay = true)

quarterEndMonth = date.month(time) % 3 == 0
background(quarterEndMonth ? fade(purple, 94) : none)
```

**See also.** `date.startOfMonth()`, `date.day()`

### date.day()

```
date.day(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

The day of the month, 1 to 31.

```openscript
version 1
study("First trading day of the month", overlay = true)

firstDay = not isNone(time[1]) and date.month(time) != date.month(time[1])
if firstDay
    signal("DAY " + text(date.day(time)), at = "below")
```

**Remarks.** The marker shows which date the month's first trading day fell on, such as `DAY 2` when the 1st was a holiday. To ask whether two bars fall on the same day, use `date.isSameDay()` rather than comparing day numbers: bars a month apart can both fall on the 12th.

**See also.** `date.isSameDay()`, `date.month()`

### date.dayOfWeek()

```
date.dayOfWeek(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

The day of the week, 1 for Monday through 7 for Sunday. Monday is 1 so that the trading week is one unbroken range: `date.dayOfWeek(time) <= 5` is a weekday test.

```openscript
version 1
study("One weekday", overlay = true)

chosen = input(4, "Day to shade, 1 is Monday", min = 1, max = 7)
background(date.dayOfWeek(time) == chosen ? fade(aqua, 92) : none)
```

**Remarks.** The same numbering is used by the day list of `session.isIn()`.

**See also.** `session.isIn()`, `date.startOfWeek()`

### date.dayOfYear()

```
date.dayOfYear(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

The day of the year, 1 on 1 January through 365, or 366 in a leap year.

```openscript
version 1
study("Day of the year", precision = 0)
plot(date.dayOfYear(time), "Day of the year", silver, style = "step")
```

**See also.** `date.weekOfYear()`, `date.year()`

### date.weekOfYear()

```
date.weekOfYear(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

The ISO week number: weeks start on Monday, and week 1 is the week that holds the year's first Thursday. So a week that straddles the new year belongs to the year holding most of it, and no year has a week 0. Monday 29 December 2025, for example, is in week 1 of 2026.

```openscript
version 1
study("Week numbers", overlay = true)

newWeek = date.weekOfYear(time) != date.weekOfYear(time[1])
if newWeek
    draw.label(time, low, "W" + text(date.weekOfYear(time)), textColor = gray)
```

**See also.** `date.startOfWeek()`, `date.dayOfWeek()`

## Clock fields

### date.hour()

```
date.hour(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

The hour of the timestamp on a 24 hour clock, 0 to 23, in the chart's timezone unless `zone` names another.

```openscript
version 1
study("Morning and afternoon", overlay = true)

morning = date.hour(time) < 12
background(morning ? fade(yellow, 94) : fade(blue, 96))
```

**Remarks.** Where a rule follows the exchange's hours, prefer `session.isIn()`, or `session.isFirstBar` and `session.isLastBar` where the host states the session's hours. A window reads more plainly than a pair of hour and minute tests, and it does not change meaning when the chart's interval changes.

**See also.** `date.minute()`, `session.isIn()`

### date.minute()

```
date.minute(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

The minute of the hour, 0 to 59. Combined with `date.hour()`, it places a bar on the clock.

```openscript
version 1
study("First half hour", overlay = true)

openingHalfHour = date.hour(time) == 9 and date.minute(time) < 45
background(openingHalfHour ? fade(aqua, 90) : none)
```

**See also.** `date.hour()`, `date.second()`

### date.second()

```
date.second(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

The second of the minute, 0 to 59. On a chart of whole minutes it is 0 on every bar; it matters on a chart with bars shorter than a minute, or for a timestamp that did not come from a bar.

```openscript
version 1
study("Seconds past the minute", precision = 0)

wallSecond = date.second(chart.now())
plot(bar.isLast ? wallSecond : none, "Seconds past the minute on the wall clock", silver)
```

**See also.** `date.minute()`, `chart.now()`

## Building and rounding a timestamp

### date.from()

```
date.from(year: number, month: number, day: number, hour?: number = 0, minute?: number = 0, second?: number = 0, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| year | number | required |
| month | number | required |
| day | number | required |
| hour | number | 0 |
| minute | number | 0 |
| second | number | 0 |
| zone | string | chart.timezone |

First value: bar 0

Builds a timestamp from calendar fields, read as a wall clock time in the chart's timezone unless `zone` names another. Use it for a fixed date, such as the start of a period you want to study.

```openscript
version 1
study("Since 1 January 2025", overlay = true)

start = date.from(2025, 1, 1, 9, 15)
plot(time >= start ? close : none, "Close since the start date", aqua)
```

**Remarks.** Every field must be a whole number, or the result is `none`. A field outside its normal range is not refused; it rolls over into the next unit. Month 13 is January of the next year, day 0 is the last day of the month before, and hour 25 is 01:00 the next day. Keep each field in its normal range unless that roll-over is what you want.

**See also.** `date.format()`, `input()`

### date.startOfDay()

```
date.startOfDay(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

Midnight at the start of the timestamp's day, in the chart's timezone. Subtract it from `time` to get how far into the day a bar is.

```openscript
version 1
study("Minutes since midnight", precision = 0)

minutesIntoDay = (time - date.startOfDay(time)) / 60000
plot(minutesIntoDay, "Minutes since midnight", silver)
```

**Remarks.** A day is not a session. For a session that runs past midnight, reset state on the session's first bar instead; see `session.isFirstBar`.

**See also.** `date.startOfWeek()`, `date.isSameDay()`

### date.startOfWeek()

```
date.startOfWeek(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

Midnight at the start of the Monday of the timestamp's week. Comparing it with the previous bar's value is a clean test for the first bar of a new week.

```openscript
version 1
study("Weekly VWAP", overlay = true)

newWeek = date.startOfWeek(time) != date.startOfWeek(time[1])
plot(vwapAnchor(hlc3, newWeek), "VWAP from the week's first bar", aqua)
```

**See also.** `date.startOfMonth()`, `vwapAnchor()`

### date.startOfMonth()

```
date.startOfMonth(t: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| zone | string | chart.timezone |

First value: bar 0

Midnight on the first day of the timestamp's month.

```openscript
version 1
study("Monthly VWAP", overlay = true)

newMonth = date.startOfMonth(time) != date.startOfMonth(time[1])
plot(vwapAnchor(hlc3, newMonth), "VWAP from the month's first bar", orange)
```

**See also.** `date.startOfWeek()`, `date.month()`

### date.isSameDay()

```
date.isSameDay(a: number, b: number, zone?: string = chart.timezone) -> bool
```

| Parameter | Type | Default |
|---|---|---|
| a | number | required |
| b | number | required |
| zone | string | chart.timezone |

First value: bar 0

True when two timestamps fall on the same calendar day in the chart's timezone. It compares the whole date, so two bars a month apart are never the same day. When either timestamp is absent, the answer is `none`.

```openscript
version 1
study("New calendar day", overlay = true)

// bar.isFirst guards bar 0, where time[1] is absent.
newDay = bar.isFirst or not date.isSameDay(time, time[1])
background(newDay ? fade(aqua, 88) : none)
```

**Remarks.** For an NSE session, the first bar of a new calendar day is the session's first bar, which makes this the usual stand-in for `session.isFirstBar` on a host that states no session hours. On a session that runs past midnight the date changes in the middle of trading, so there it is not a stand-in.

**See also.** `session.isFirstBar`, `date.startOfDay()`

## Writing a timestamp as text

### date.format()

```
date.format(t: number, pattern: string, zone?: string = chart.timezone) -> string
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| pattern | string | required |
| zone | string | chart.timezone |

First value: bar 0

Writes a timestamp as text following a pattern, for labels, tables and messages. The pattern uses a small, closed set of placeholders; every other character is copied as written.

```openscript
version 1
study("Date stamp", overlay = true)

panel = table("Last bar", 1, 2, position = "bottomRight")
if bar.isLast
    cell(panel, 0, 0, "Opened")
    cell(panel, 0, 1, date.format(time, "EEE dd MMM yyyy, HH:mm"))
```

| Placeholder | Writes |
|---|---|
| `yyyy` | Four digit year |
| `MM` | Two digit month |
| `dd` | Two digit day |
| `HH` | Two digit hour, 24 hour clock |
| `mm` | Two digit minute |
| `ss` | Two digit second |
| `MMM` | Three letter month, such as `Mar` |
| `EEE` | Three letter weekday, such as `Fri` |

| Pattern | Writes, for 09:15 on Friday 14 March 2025 |
|---|---|
| `"yyyy-MM-dd"` | `2025-03-14` |
| `"dd MMM yyyy"` | `14 Mar 2025` |
| `"EEE HH:mm"` | `Fri 09:15` |
| `"HH:mm:ss"` | `09:15:00` |
| `"EEE dd MMM yyyy, HH:mm"` | `Fri 14 Mar 2025, 09:15` |

**Remarks.** Month and weekday names are English and the same on every machine, so a label never changes with the viewer's language settings. The pattern is read from left to right taking the longest placeholder at each point, so `MMM` is a month name and is not read as `MM` followed by `M`. Anything that is not a placeholder is copied as it is: `"MMMM"` writes `MarM`, and a lone `M`, `d` or `yy` is copied as those letters.

**See also.** `text()`, `cell()`, `draw.label()`

## Planned

### date.add() (planned, not available yet)

```
date.add(t: number, unit: string, count: number, zone?: string = chart.timezone) -> number
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| unit | string | required |
| count | number | required |
| zone | string | chart.timezone |

First value: bar 0

Calendar arithmetic that respects month lengths and clock changes, such as adding one month to a timestamp. It is planned, and using it is `OS2020`. Until it arrives, add a fixed number of milliseconds for days, hours and minutes, and build month boundaries with `date.from()`.

## Related

[Sessions and time](/script/data/sessions-and-time), [session.*](/script/reference/session), [chart.*](/script/reference/chart), [Price and volume](/script/reference/price-and-volume), [Strings](/script/reference/string).


## Technical analysis

Source: https://openalgo.in/script/reference/technical-analysis

This page is the reference for every indicator built into OpenScript, also called OpenAlgo Script: sixty-eight functions from the simple moving average to the Ichimoku cloud. Fifty-five of them work in version 0.5.0; the other thirteen are named in the language and marked Planned, so you can see what is coming. Each entry tells you what the indicator measures, how traders read it, the exact arithmetic where it matters, and the first bar on which it has a value. Every example is a complete script: paste it into the Scripts panel of the /trading page, save it, and press **Apply to chart**.

You need this page whenever a study or strategy reads price through an indicator. Indicators are where most scripts start, and most surprises in a new script (a line that starts late, a crossing that never fires, a band that is missing on the left of the chart) come from the details written here.

## How to read an entry

Each entry opens with a short description. Below it the compiler fills in the signature, a table of parameters with their types and defaults, the return type and the **first value**: the first bar on which the call can return a number, counting the oldest bar loaded on the chart as bar 0. Below those facts you find a working example, remarks and links to related entries.

A few rules hold for every function on the page.

- **Warmup is exact.** Before its first value a call returns `none`, the absent value, and a plot shows a gap there rather than a zero. After it, the call has a value on every bar whose inputs are present, except where the arithmetic would divide by zero (a window with no range, for example); the remarks of each entry name those cases. Warmups add up when you feed one indicator into another: `sma(ema(close, 10), 10)` has its first value on bar 18, because the inner average starts on bar 9 and the outer one then needs ten values. See [Warmup](/script/language/warmup) and [Absent values](/script/language/absent-values).
- **Absence spreads through a window.** If any bar inside a window is absent, that bar's result is absent. Only `sumSkip()`, `avgSkip()` and `countPresent()` skip absent values, and they say so in their names.
- **A length is a whole number of 1 or more.** Any other value, such as `0` or `2.5`, stops the script on the first bar that uses it with run-time error [OS4003](/script/errors/runtime#os4003). Take a length from `input()` so the user can change it, and keep it fixed while the script runs: if a computed length changes between bars, a window function such as `sma()` starts its window again and is absent until the new window fills.
- **Every indicator call keeps its own state.** An `ema` remembers its running value from one bar to the next, which is why almost every entry carries a Keeps state badge. The state belongs to the place the call is written, so each call in your file is a separate indicator. Assign an indicator to a name once and reuse the name rather than writing the same call twice.
- **Compute indicators at the top level, on every bar.** A call that keeps state only advances on the bars where it actually runs. Inside an `if` block, in the side of a `condition ? a : b` choice that is not taken, or on the right of an `and` or `or` that has already decided its answer, it skips bars and draws a different line. The compiler warns about all three with OS8001:

```openscript
trend = 0.0
if close > open
    trend = ema(close, 20)
```

The fix is to compute first and decide afterwards:

```openscript
version 1
study("Compute first, decide after", overlay = true)

avg20 = ema(close, 20)
plot(close > open ? avg20 : none, "EMA 20 on up bars", aqua)
```

> **Three calls on this page, `alma()`, `chop()` and `hv()`, take an exponential or a logarithm. Those two operations can differ in the last binary digit from one computer to another, so these three readings carry no bit-for-bit guarantee across platforms. The difference is far below anything a chart shows.**

## Functions that return several values

Some indicators produce more than one line: MACD has a line, a signal and a histogram. These functions return an `array<number>` holding this bar's values in a fixed order. Read each value by its position with `[0]`, `[1]` and so on.

```openscript
version 1
study("MACD, three lines", precision = 2)

m = macd(close, 12, 26, 9)

plot(m[0], "MACD", aqua)
plot(m[1], "Signal", orange)
plot(m[2], "Histogram", gray, style = "histogram")
```

The array always has the same length, even during warmup. Each position carries its own warmup and holds `none` until it is reached, so `m[1]` is never an out-of-range error on an early bar.

| Function | `[0]` | `[1]` | `[2]` | `[3]` | `[4]` |
|---|---|---|---|---|---|
| `macd()` | MACD line | Signal | Histogram | | |
| `ppo()` | PPO line | Signal | Histogram | | |
| `stoch()` | %K | %D | | | |
| `stochRsi()` | %K | %D | | | |
| `bollinger()` | Basis | Upper | Lower | | |
| `keltner()` | Basis | Upper | Lower | | |
| `donchian()` | Upper | Middle | Lower | | |
| `supertrend()` | Line | Direction | | | |
| `psar()` | Stop | Direction | | | |
| `adx()` | ADX | +DI | -DI | | |
| `aroon()` | Up | Down | | | |
| `ichimoku()` | Conversion | Base | Span A | Span B | Lagging |

> **`donchian()` puts the upper line first and the middle second. `bollinger()` and `keltner()` put the basis first. Check the table when you switch between them.**

In `m[1]` the brackets pick an element of the array, not a past bar. To read an element's value on an earlier bar, give it a name first; a top-level name has history, and `[1]` on it means one bar ago.

```openscript
version 1
study("Signal line rising", precision = 2)

m   = macd(close)
sig = m[1]

plot(sig, "Signal", sig > sig[1] ? lime : red, width = 2)
```

Writing both steps on one expression is an error, because an element has no history of its own:

```openscript
st = supertrend(3, 10)
flipped = st[1] != st[1][1]
```

Name the element first, as `sig = m[1]` does above, and read its history from the name.

## Choosing a moving average

Every average smooths price and every average lags it. They differ in how they trade smoothness for speed.

| You want | Use | Trade-off |
|---|---|---|
| The plain average everyone means by "the 200 day" | `sma()` | Equal weights; reacts slowly and drops old bars abruptly |
| A faster average that never fully forgets | `ema()` | Weight `2 / (len + 1)` on the newest bar |
| The smoothing inside RSI, ATR and ADX | `rma()` | Weight `1 / len`; slower than an `ema` of the same length |
| Recent bars to count more, in a straight line | `wma()` | Newest bar weighted `len`, oldest weighted 1 |
| Much less lag at the same length | `hma()` | Can overshoot at turns; longer warmup |
| An `ema` with its lag partly removed | `dema()`, `tema()` | Longer warmup; can overshoot |
| Heavily traded bars to count more | `vwma()` | Needs volume |
| A tunable balance of lag and smoothness | `alma()` | Two extra settings to choose |
| The end point of a fitted trend line | `linreg()` | Follows straight trends closely; jumps at turns |
| A light four-bar smoothing with no length | `swma()` | Fixed at four bars |
| Let the user choose from the settings dialog | `ma()` | Only the six common types |

## Moving averages

### sma()

```
sma(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The simple moving average: the arithmetic mean of the last `len` values of `src`, every bar weighted equally. It is the baseline average and the one traders mean by "the 50 day" and "the 200 day" on a daily chart.

```openscript
version 1
study("50 and 200 day averages", overlay = true)

sma50  = sma(close, 50)
sma200 = sma(close, 200)

plot(sma50, "SMA 50", aqua)
plot(sma200, "SMA 200", orange, width = 2)

if crossUp(sma50, sma200)
    signal("GOLDEN CROSS", color = lime, at = "below", shape = "triangleUp")
if crossDown(sma50, sma200)
    signal("DEATH CROSS", color = red, at = "above", shape = "triangleDown")
```

**Remarks.** A 200 bar average has no value until bar 199, which on a daily NSE chart is roughly ten months of sessions, so load enough history before you judge the line. The sum is taken afresh over the window on every bar, oldest value first, and divided once by `len`. The cost per bar grows with `len` but not with the length of the chart. An absent value anywhere in the window makes that bar's average absent.

**See also.** `ema()`, `wma()`, `ma()`, `sum()`

### ema()

```
ema(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The exponential moving average gives the newest value a weight of `2 / (len + 1)` and the running average the rest, so recent bars count more and old bars fade away without ever dropping out abruptly. It reacts to a new move sooner than `sma()` of the same length, which makes it the usual choice for crossover systems.

```openscript
version 1
study("EMA 9 and 21", overlay = true)

fastLen = input(9, "Fast", min = 1, max = 200)
slowLen = input(21, "Slow", min = 2, max = 400)

fast = ema(close, fastLen)
slow = ema(close, slowLen)

fastPlot = plot(fast, "EMA fast", aqua, width = 2)
slowPlot = plot(slow, "EMA slow", orange, width = 2)
fill(fastPlot, slowPlot, colorUp = fade(lime, 88), colorDown = fade(red, 88))

if crossUp(fast, slow)
    signal("BUY", color = lime, at = "below", shape = "arrowUp")
if crossDown(fast, slow)
    signal("SELL", color = red, at = "above", shape = "arrowDown")
```

The picture shows a 20 and 50 bar variant of this study on a daily SBIN chart, with the band shaded the same way and each crossing labelled Golden cross or Death cross in place of an arrow:


**Remarks.** The average is seeded on bar `len - 1` with the simple average of the first `len` values, and is absent before that. It does not start from the first close and drift into shape, so the line you see is correct from its first point. Each later bar computes `value * weight + previous * (1 - weight)`. With `len` of 9 the newest close carries 20 percent of the weight. If the source is absent on a bar after the seed, that bar's result is absent and the running value is held, so the next present bar carries on where the last one left off.

**See also.** `sma()`, `rma()`, `dema()`, `tema()`, `crossUp()`

### wma()

```
wma(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The weighted moving average weights the last `len` values in a straight line: the newest value counts `len` times, the one before it `len - 1` times, down to 1 for the oldest. At the same length it turns sooner than both `sma()` and `ema()`, and it forgets a bar completely once the bar leaves the window.

```openscript
version 1
study("WMA against SMA", overlay = true)

len = input(20, "Length", min = 1, max = 500)

plot(wma(close, len), "WMA", lime, width = 2)
plot(sma(close, len), "SMA", fade(silver, 30))
```

**Remarks.** The weighted sum is divided once by `len * (len + 1) / 2`, the total of the weights. With `len` of 20 the newest bar carries 20 of 210 parts, just under 10 percent. `hma()` is built from three of these averages.

**See also.** `sma()`, `hma()`, `swma()`

### rma()

```
rma(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The running moving average, often called Wilder's smoothing: an exponential average whose newest value gets a weight of `1 / len`. It is the smoothing inside `rsi()`, `atr()` and `adx()`, and you reach for it when you rebuild or adapt one of those indicators yourself.

```openscript
version 1
study("RSI built from rma", precision = 2, range = [0, 100])

len = input(14, "Length", min = 1, max = 200)

delta = change(close)
gain  = max(delta, 0)
loss  = max(-delta, 0)

avgGain = rma(gain, len)
avgLoss = rma(loss, len)
manual  = avgLoss == 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss)

plot(manual, "RSI by hand", purple, width = 2)
plot(rsi(close, len), "rsi()", fade(orange, 40))
```

The two lines in this example lie on top of each other, because this is how `rsi()` is defined.

**Remarks.** Seeded like `ema()`: on bar `len - 1`, with the simple average of the first `len` values. Each later bar computes `(previous * (len - 1) + value) / len`. A weight of `1 / len` is the same decay as an `ema` of length `2 * len - 1`, so `rma(x, 14)` moves about as slowly as `ema(x, 27)`. Keep that in mind when you compare the two.

**See also.** `ema()`, `rsi()`, `atr()`, `adx()`

### hma()

```
hma(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len + round(sqrt(len)) - 2`

The Hull moving average combines weighted averages so that most of the lag cancels out: it follows price closely while staying smooth. Traders often colour it by its slope and treat a change of slope as a change of trend.

```openscript
version 1
study("Hull average", overlay = true)

len = input(55, "Length", min = 2, max = 500)
h = hma(close, len)

plot(h, "HMA", h > h[1] ? lime : red, width = 2)
```

**Remarks.** The recipe is `wma(2 * wma(src, half) - wma(src, len), round(sqrt(len)))`, where `half` is `floor(len / 2)` held at a minimum of 1. The first value arrives on bar `len + round(sqrt(len)) - 2`, bar 60 for a length of 55. Because it removes lag by extrapolating, it can overshoot price at sharp turns.

**See also.** `wma()`, `ema()`, `ma()`

### dema()

```
dema(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `2 * len - 2`

The double exponential moving average subtracts an average's own lag once: `2 * ema - ema(ema)`. It hugs price more closely than `ema()` of the same length.

```openscript
version 1
study("DEMA against EMA", overlay = true)

len = input(20, "Length", min = 1, max = 300)

plot(ema(close, len), "EMA", fade(silver, 30))
plot(dema(close, len), "DEMA", aqua, width = 2)
```

**Remarks.** The second average is fed the first one's output, absent bars included, so it seeds on the first `len` values the first one produced. That gives the first value on bar `2 * len - 2`, bar 38 for a length of 20.

**See also.** `ema()`, `tema()`

### tema()

```
tema(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `3 * len - 3`

The triple exponential moving average applies the same lag correction twice: `3 * e1 - 3 * e2 + e3`, where `e1` is the `ema` of the source, `e2` the `ema` of `e1` and `e3` the `ema` of `e2`. It is the fastest of the exponential family and the most prone to overshoot.

```openscript
version 1
study("Triple EMA trend", overlay = true)

len = input(20, "Length", min = 1, max = 200)
t = tema(close, len)

plot(t, "TEMA", t > t[1] ? teal : maroon, width = 2)
```

**Remarks.** Three chained averages need three warmups, so the first value is on bar `3 * len - 3`, bar 57 for a length of 20. `trix()` measures the rate of change of the same triple smoothing.

**See also.** `dema()`, `ema()`, `trix()`

### vwma()

```
vwma(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The volume weighted moving average weights each value by the volume traded on its bar, so a heavily traded bar pulls the average more than a quiet one. When it sits above `sma()` of the same length, the higher closes came on heavier volume.

```openscript
version 1
study("VWMA against SMA", overlay = true)

len = input(20, "Length", min = 1, max = 500)

v = vwma(close, len)
s = sma(close, len)

vPlot = plot(v, "VWMA", orange, width = 2)
sPlot = plot(s, "SMA", fade(silver, 30))
fill(vPlot, sPlot, colorUp = fade(lime, 85), colorDown = fade(red, 85))
```

**Remarks.** It is the window sum of `src * volume` divided by the window sum of `volume`. It is absent where any bar in the window has no volume, and where the volume in the window adds up to zero. An index itself, such as NIFTY 50, trades no volume, so there is nothing to weight by on an index chart: chart the index future on NFO instead.

**See also.** `sma()`, `vwap()`, `volume`

### swma()

```
swma(src: series number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |

First value: bar 3

The symmetric weighted moving average is a fixed four-bar smoothing with weights 1, 2, 2 and 1. It has no length to set. Use it to take the jitter out of an oscillator or a noisy series while adding only a bar and a half of lag.

```openscript
version 1
study("Smoothed RSI", precision = 2, range = [0, 100])

r = rsi(close, 14)

plot(r, "RSI", fade(purple, 60))
plot(swma(r), "RSI, smoothed", purple, width = 2)
level(70, "Overbought", fade(red, 40))
level(30, "Oversold", fade(lime, 40))
```

**Remarks.** The result is `(w3 + 2 * w2 + 2 * w1 + w0) / 6`, where `w0` is this bar's value and `w3` the value three bars back. It needs four values, so it starts on bar 3 of its source: on `rsi(close, 14)`, which starts on bar 14, it starts on bar 17.

**See also.** `wma()`, `sma()`

### alma()

```
alma(src: series number, len: number, offset?: number = 0.85, sigma?: number = 6) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |
| offset | number | 0.85 |
| sigma | number | 6 |

First value: bar `len - 1`

The Arnaud Legoux moving average weights the window with a bell curve whose peak you place. `offset` moves the peak between the oldest bar (0) and the newest (1), and `sigma` sets how narrow the bell is. It lets you choose your own balance between responsiveness and smoothness.

```openscript
version 1
study("ALMA", overlay = true)

len    = input(21, "Length", min = 1, max = 500)
offset = input(0.85, "Offset", min = 0, max = 1, step = 0.05)
sigma  = input(6.0, "Sigma", min = 0.5, max = 20)

plot(alma(close, len, offset, sigma), "ALMA", aqua, width = 2)
```

**Remarks.** The peak sits at position `offset * (len - 1)`, counting 0 as the oldest bar in the window. Each weight is `exp(-(gap * gap) / (2 * spread * spread))`, where `gap` is the distance from the peak and `spread` is `len / sigma`, and the weighted sum is divided by the total of the weights. A larger `sigma` narrows the bell so fewer bars near the peak carry the weight; a smaller one widens it toward an equal-weight average. The default offset of 0.85 leans toward recent bars.

**See also.** `wma()`, `ema()`

### linreg()

```
linreg(src: series number, len: number, offset?: number = 0) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |
| offset | number | 0 |

First value: bar `len - 1`

The linear regression value: fit a least squares straight line through the last `len` values and return the line's value on this bar. It tracks a steady trend closely, and the line's slope tells you how fast the trend is moving.

```openscript
version 1
study("Regression line and slope", overlay = true)

len = input(50, "Length", min = 2, max = 500)

fitted = linreg(close, len)
slope  = fitted - linreg(close, len, 1)

plot(fitted, "Regression", slope > 0 ? lime : red, width = 2)
```

**Remarks.** The fit places the oldest bar of the window at 0 and this bar at `len - 1`. `offset` reads the same fitted line `offset` bars back without fitting it again, so `linreg(src, len) - linreg(src, len, 1)` is the slope per bar, as the example uses it. A length of 1 has no line to fit and returns `none` on every bar.

**See also.** `sma()`, `correlation()`

### ma()

```
ma(src: series number, len: number, type?: string = "sma") -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |
| type | string | "sma" (one of "sma", "ema", "wma", "rma", "hma", "vwma") |

First value: the named type's

One call for six averages, chosen by name: `"sma"`, `"ema"`, `"wma"`, `"rma"`, `"hma"` or `"vwma"`. Feed `type` from an `input()` with those options and the user can switch the average from the study's settings dialog without editing the script.

```openscript
version 1
study("Switchable average", overlay = true)

kind = input("ema", "Average", options = ["sma", "ema", "wma", "rma", "hma", "vwma"])
len  = input(20, "Length", min = 1, max = 500)

plot(ma(close, len, kind), "Average", aqua, width = 2)
```

**Remarks.** The result and its first value are exactly those of the named average. A literal type that is not one of the six is error [OS3008](/script/errors/arguments#os3008) when you compile. A type that only arrives while the script runs and names none of the six gives `none` on every bar rather than silently drawing a different average. Each type keeps its own state, so switching type starts the new average from its own seed.

**See also.** `sma()`, `ema()`, `keltner()`, [Inputs](/script/inputs/inputs)

### kama() (planned, not available yet)

```
kama(src: series number, len: number, fast?: number = 2, slow?: number = 30) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |
| fast | number | 2 |
| slow | number | 30 |

First value: bar `len`

Kaufman's adaptive moving average will speed its smoothing up when price moves steadily in one direction and slow it down when price chops, between the `fast` and `slow` limits.

### zlema() (planned, not available yet)

```
zlema(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The zero lag exponential moving average will be an exponential average with most of its lag removed, so that it follows price more closely than `ema()` of the same length.

### vidya() (planned, not available yet)

```
vidya(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `2 * len - 1`

The variable index dynamic average will adjust its smoothing to relative volatility, moving faster when the market is active and slower when it is quiet.

## Trend

Trend indicators answer two questions: which way is the market going, and how strongly. `supertrend()` and `psar()` draw a trailing stop line and report which way the trend runs. `adx()` and `aroon()` measure strength, `ichimoku()` draws a complete trend frame, and `chop()` tells trending from sideways.

### supertrend()

```
supertrend(factor?: number = 3, atrLen?: number = 10) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| factor | number | 3 |
| atrLen | number | 10 |

First value: element 0 at bar `atrLen`, element 1 at bar `atrLen`

A trailing band set a multiple of the average true range away from the bar's midpoint. In an uptrend the line trails below price and only rises; in a downtrend it trails above and only falls. When the close crosses through the line, it flips to the other side. It returns `[line, direction]`, and `direction` is `-1` while the line is below price (long) and `1` while it is above (short).

```openscript
version 1
study("Supertrend", overlay = true)

factor = input(3.0, "Factor", min = 0.5, max = 10, step = 0.5)
atrLen = input(10, "ATR length", min = 1, max = 100)

st   = supertrend(factor, atrLen)
band = st[0]
dir  = st[1]

plot(dir == -1 ? band : none, "Up trend", lime, width = 2)
plot(dir == 1 ? band : none, "Down trend", red, width = 2)

if dir == -1 and dir[1] == 1
    signal("LONG", color = lime, at = "below", shape = "triangleUp")
if dir == 1 and dir[1] == -1
    signal("SHORT", color = red, at = "above", shape = "triangleDown")
```

A Supertrend with the same settings on a BHEL 15 minute chart, from a study that also shades between the line and the candles and labels each flip BUY or SELL in place of a triangle:


The example plots the line twice, once per direction, so each flip leaves a gap instead of a vertical jump across the candles. The flip tests compare with `dir[1]`, which is absent on the line's first bar; `==` against an absent value is false, so no flip is marked there.

**Remarks.** The raw bands are `hl2 + factor * atr(atrLen)` and `hl2 - factor * atr(atrLen)`. From bar to bar the lower band may only rise, unless the previous close fell below it, and the upper band may only fall, unless the previous close rose above it.

The line starts on the upper band, so on a rising chart the first values can read short (`1`) until the first close above that band flips it. While it follows the upper band it stays there as long as the close is at or below that band; while it follows the lower band it stays as long as the close is at or above it. An exact touch therefore does not flip it.

The first value arrives one bar after `atr()` has one, on bar `atrLen`, because the bands need a previous band and a previous close to trail against.

**See also.** `psar()`, `atr()`, `barColor()`, `signal()`

### psar()

```
psar(start?: number = 0.02, step?: number = 0.02, max?: number = 0.2) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| start | number | 0.02 |
| step | number | 0.02 |
| max | number | 0.2 |

First value: element 0 at bar 1, element 1 at bar 1

The parabolic stop and reverse: a stop that starts at the last swing extreme and moves toward price a little faster each time the trend makes a new extreme. When price reaches the stop, the trend is taken to have reversed and the stop jumps to the other side. It returns `[sar, direction]`, with `direction` `-1` while long and `1` while short, the same convention as `supertrend()`.

```openscript
version 1
study("Parabolic SAR", overlay = true)

p   = psar(0.02, 0.02, 0.2)
sar = p[0]
dir = p[1]

plot(dir == -1 ? sar : none, "SAR, long", lime, style = "lineWithMarkers")
plot(dir == 1 ? sar : none, "SAR, short", red, style = "lineWithMarkers")

if dir == -1 and dir[1] == 1
    signal("SAR LONG", color = lime, at = "below")
if dir == 1 and dir[1] == -1
    signal("SAR SHORT", color = red, at = "above")
```

**Remarks.** It is seeded on bar 1: the direction is up if bar 1 closed above bar 0, and the stop starts at bar 0's low when up or its high when down. The acceleration starts at `start`.

On each later bar, in this order:

1. The stop moves toward the extreme: `stop + acceleration * (extreme - stop)`.
2. If a long stop is now above the bar's low (or a short stop below its high), the direction flips, the stop jumps to the extreme the old trend reached, and the acceleration returns to `start`.
3. Otherwise, a new extreme (a higher high while long, a lower low while short) raises the acceleration by `step`, up to `max`.

The stop is reported as computed: it is not pulled back outside the previous two bars' range, as some published versions do. A study that wants that clamp writes it itself.

**See also.** `supertrend()`, `atr()`, [Exits and brackets](/script/strategies/exits-and-brackets)

### adx()

```
adx(diLen?: number = 14, adxLen?: number = 14) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| diLen | number | 14 |
| adxLen | number | 14 |

First value: element 1 and 2 at bar `diLen`, element 0 at bar `diLen + adxLen - 1`

The average directional index and its two directional indicators. `+DI` measures how much of recent movement was upward and `-DI` how much was downward, each as a percentage of the true range. ADX measures how far apart they are, smoothed: the strength of the trend, whichever way it runs. It returns `[adx, plusDI, minusDI]`.

```openscript
version 1
study("ADX and DI", precision = 2)

diLen  = input(14, "DI length", min = 1, max = 100)
adxLen = input(14, "ADX smoothing", min = 1, max = 100)

a = adx(diLen, adxLen)

plot(a[0], "ADX", orange, width = 2)
plot(a[1], "+DI", lime)
plot(a[2], "-DI", red)
level(25, "Trending", fade(gray, 40), "dotted")
level(20, "Weak", fade(gray, 60), "dotted")
```

An ADX above about 25 and rising is commonly read as a trending market, and one below 20 as a weak or sideways one. ADX says how strong the trend is, not which way it runs: for the direction, compare the two DI lines. `+DI` above `-DI` says buyers own the move.

**Remarks.** Upward movement is `high - high[1]` and downward movement is `low[1] - low`; on each bar only the larger one counts, and only if it is positive. Both movements and the true range are smoothed with `rma()` over `diLen`, and each DI is `smoothed movement / smoothed range * 100`. ADX is the `rma()` over `adxLen` of `abs(+DI - -DI) / (+DI + -DI) * 100`. The two DI lines start on bar `diLen` (bar 14 by default) because movement needs the previous bar, and ADX starts on bar `diLen + adxLen - 1` (bar 27). The DI values are absent where the smoothed range is zero, and the ratio inside ADX counts as 0 on a bar where both DI values are zero.

**See also.** `aroon()`, `chop()`, `rma()`, `trueRange()`

### aroon()

```
aroon(len?: number = 14) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 14 |

First value: both elements at bar `len`

Aroon measures how recently the window's highest high and lowest low were set, as a percentage. Aroon up is 100 when this bar set the high and falls toward 0 as the high ages; Aroon down does the same for the low. It returns `[up, down]`. A strong uptrend keeps Aroon up near 100 and Aroon down low.

```openscript
version 1
study("Aroon", precision = 2, range = [0, 100])

len = input(25, "Length", min = 1, max = 200)

ar = aroon(len)

plot(ar[0], "Aroon up", lime, width = 2)
plot(ar[1], "Aroon down", red, width = 2)
level(70, "Strong", fade(gray, 40), "dotted")
level(30, "Weak", fade(gray, 40), "dotted")
```

**Remarks.** Each line is `100 * (len - bars since the extreme) / len`, measured over a window of `len + 1` bars so that an extreme set exactly `len` bars ago still counts and a reading of 0 is possible. That extra bar is why the first value is on bar `len`. When two bars share the extreme, the more recent one counts. The Aroon oscillator is simply `ar[0] - ar[1]`.

**See also.** `adx()`, `highestBars()`, `lowestBars()`

### ichimoku()

```
ichimoku(convLen?: number = 9, baseLen?: number = 26, spanLen?: number = 52) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| convLen | number | 9 |
| baseLen | number | 26 |
| spanLen | number | 52 |

First value: element by element at bar `convLen - 1`, `baseLen - 1`, `baseLen - 1`, `spanLen - 1`, `baseLen - 1`

The Ichimoku cloud: five lines that together describe trend, support and momentum. The conversion and base lines are the midpoints of the highest high and lowest low over 9 and 26 bars. Span A is the average of those two, span B is the 52 bar midpoint, and the space between the spans forms the cloud. The lagging line is the close. It returns `[conversion, base, spanA, spanB, lagging]`.

```openscript
version 1
study("Ichimoku cloud", overlay = true)

ich = ichimoku(9, 26, 52)

plot(ich[0], "Conversion", aqua)
plot(ich[1], "Base", maroon)
spanA = plot(ich[2], "Span A", lime, offset = 26)
spanB = plot(ich[3], "Span B", red, offset = 26)
fill(spanA, spanB, colorUp = fade(lime, 85), colorDown = fade(red, 85))
plot(ich[4], "Lagging", gray, offset = -26)
```

The spans come back on the bar they are computed on, not shifted. The plot's `offset` draws them 26 bars forward and the lagging line 26 bars back, which is how the cloud is traditionally shown. If you change `baseLen`, change the offsets to match. To compare today's close with the part of the cloud drawn above today's bar, read the spans 26 bars back:

```openscript
version 1
study("Close above the cloud", overlay = true)

ich   = ichimoku()
spanA = ich[2]
spanB = ich[3]

cloudTop = max(spanA[26], spanB[26])

background(close > cloudTop ? fade(lime, 92) : none)
```

**Remarks.** Each value has its own first bar: conversion on bar `convLen - 1`, base, span A and lagging on bar `baseLen - 1`, and span B on bar `spanLen - 1` (bars 8, 25 and 51 with the defaults). Span A is the average of the conversion and base values, not a midpoint over a window of its own. Returning the spans unshifted means you can compare them with anything else in the script directly.

**See also.** `donchian()`, `plot()`, [Fills](/script/visuals/fills)

### chop()

```
chop(len?: number = 14) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 14 |

First value: bar `len`

The choppiness index, a 0 to 100 reading of whether the window trended or went sideways. It compares the distance price travelled bar by bar with the range it covered overall: a market that went straight up travels about its range and reads low, while one that went back and forth travels many times its range and reads high. Use it as a filter to switch between trend-following and range-trading rules.

```openscript
version 1
study("Choppiness", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)
c = chop(len)

plot(c, "Choppiness", silver, width = 2)
level(61.8, "Choppy", fade(red, 40))
level(38.2, "Trending", fade(lime, 40))
```

**Remarks.** The reading is `100 * log10(sum of true range / (highest high - lowest low)) / log10(len)`. Readings above 61.8 are commonly taken as choppy and below 38.2 as trending. Each true range here needs the previous close, so the first value is on bar `len` rather than `len - 1`. The result is absent when the range or the travelled distance is not above zero, and for a length of 1.

**See also.** `adx()`, `trueRange()`, `atr()`

## Oscillators and momentum

Oscillators turn price into a bounded or centred reading that is easy to compare with fixed levels: overbought and oversold, above or below zero. They are usually drawn in their own pane below the price chart, so the examples leave `overlay` at its default of `false`.

An oscillator that measures change needs two bars for its first change, so it carries one extra bar of warmup. `rsi()` with a length of 14 needs 14 changes, and its first value is on bar 14, not bar 13.

### rsi()

```
rsi(src: series number, len?: number = 14) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 14 |

First value: bar `len`

The relative strength index: a 0 to 100 reading of how one-sided the recent changes were. It averages the up moves and the down moves separately and compares them. Readings above 70 are traditionally overbought and below 30 oversold, and many traders watch the 50 line as the boundary between bullish and bearish momentum.

```openscript
version 1
study("RSI", precision = 2, range = [0, 100])

len = input(14, "Length", min = 1, max = 200)
r = rsi(close, len)

plot(r, "RSI", purple, width = 2)
level(70, "Overbought", fade(red, 40))
level(50, "Middle", fade(gray, 60))
level(30, "Oversold", fade(lime, 40))

if crossUp(r, 30)
    alert("RSI crossed back above 30", id = "rsi-oversold-exit")
```


**Remarks.** Each change `src - src[1]` is split into a gain and a loss, each is smoothed with `rma()` over `len`, and the result is `100 - 100 / (1 + averageGain / averageLoss)`. When the average loss is zero, including a window where price never moved, the result is 100. The example under `rma()` rebuilds it step by step. RSI of RSI, or RSI of any series, works the same way: pass the series as `src`.

**See also.** `stochRsi()`, `mfi()`, `cmo()`, `rma()`, `level()`

### stoch()

```
stoch(len?: number = 14, smoothK?: number = 1, smoothD?: number = 3) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 14 |
| smoothK | number | 1 |
| smoothD | number | 3 |

First value: element 0 at bar `len + smoothK - 2`, element 1 at bar `len + smoothK + smoothD - 3`

The stochastic oscillator places the close inside the window's range: 100 at the highest high, 0 at the lowest low. It returns `[k, d]`, where %K is the position smoothed over `smoothK` bars and %D is %K smoothed over `smoothD` bars. With `smoothK` of 1 you get the fast stochastic; 3 gives the common slow stochastic.

```openscript
version 1
study("Slow stochastic", precision = 2, range = [0, 100])

s = stoch(14, 3, 3)
k = s[0]
d = s[1]

plot(k, "%K", aqua, width = 2)
plot(d, "%D", orange)
level(80, "Overbought", fade(red, 40))
level(20, "Oversold", fade(lime, 40))

if crossUp(k, d) and k < 20
    signal("K UP", color = lime, at = "below", shape = "triangleUp")
```

**Remarks.** The raw position is `100 * (close - lowest low) / (highest high - lowest low)` over `len` bars, using the bars' own highs and lows rather than the highest and lowest close. %K is `sma()` of that over `smoothK` and %D is `sma()` of %K over `smoothD`. With `stoch(14, 3, 3)`, %K starts on bar 15 and %D on bar 17. The position is absent on a bar where the window's high equals its low.

**See also.** `stochRsi()`, `williamsR()`, `donchian()`

### stochRsi()

```
stochRsi(src: series number, rsiLen?: number = 14, stochLen?: number = 14, smoothK?: number = 3, smoothD?: number = 3) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| rsiLen | number | 14 |
| stochLen | number | 14 |
| smoothK | number | 3 |
| smoothD | number | 3 |

First value: element 0 at bar `rsiLen + stochLen + smoothK - 2`

The stochastic RSI applies the stochastic position test to `rsi()` instead of to price: where does today's RSI sit within its own recent range? It moves faster than RSI and reaches its extremes far more often, so it suits short-term timing. It returns `[k, d]`.

```openscript
version 1
study("Stochastic RSI", precision = 2, range = [0, 100])

s = stochRsi(close, 14, 14, 3, 3)

plot(s[0], "%K", aqua, width = 2)
plot(s[1], "%D", orange)
level(80, "High", fade(red, 40))
level(20, "Low", fade(lime, 40))
```

**Remarks.** The window's high and low are taken from the RSI values themselves, over `stochLen` bars. %K starts on bar `rsiLen + stochLen + smoothK - 2` and %D follows `smoothD - 1` bars later: bars 29 and 31 with the defaults. When RSI makes a new high for the window on bar after bar, the raw reading sits at 100 (and at 0 for a run of new lows), so %K can stay pinned at an extreme through a steady trend. The raw reading is absent when RSI has not moved across the window, since its high then equals its low.

**See also.** `rsi()`, `stoch()`

### williamsR()

```
williamsR(len?: number = 14) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 14 |

First value: bar `len - 1`

Williams %R is the stochastic position on an inverted scale: 0 when the close is at the window's highest high and -100 at its lowest low. Readings above -20 are commonly called overbought and below -80 oversold.

```openscript
version 1
study("Williams %R", precision = 2, range = [-100, 0])

len = input(14, "Length", min = 1, max = 200)

plot(williamsR(len), "%R", red, width = 2)
level(-20, "Overbought", fade(red, 40))
level(-80, "Oversold", fade(lime, 40))
```

**Remarks.** It is computed as `-100 * (highest high - close) / (highest high - lowest low)`, from the distance below the window high. It reads levels, not changes, so the first value is on bar `len - 1`. It is absent where the window's high equals its low.

**See also.** `stoch()`, `stochRsi()`

### cci()

```
cci(len?: number = 20) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 20 |

First value: bar `len - 1`

The commodity channel index measures how far the typical price, `hlc3`, sits from its own average, in units of its usual deviation. Readings beyond +100 or -100 mark a price that has moved unusually far from its mean, which traders read either as the start of a strong move or as stretched, depending on context.

```openscript
version 1
study("CCI", precision = 2)

len = input(20, "Length", min = 2, max = 200)
c = cci(len)

plot(c, "CCI", orange, width = 2)
level(100, "+100", fade(red, 40))
level(0, "Zero", fade(gray, 60))
level(-100, "-100", fade(lime, 40))
```

**Remarks.** The reading is `(typical - sma(typical, len)) / (0.015 * mean deviation)`, where the mean deviation is the average absolute distance of the window's typical prices from their mean. It uses the mean absolute deviation, not the standard deviation: the 0.015 constant is calibrated for it, and a standard deviation would change every reading while still drawing a plausible line. The first value is on bar `len - 1`.

**See also.** `hlc3`, `stdev()`, `bbPercent()`

### macd()

```
macd(src: series number, fast?: number = 12, slow?: number = 26, signal?: number = 9) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| fast | number | 12 |
| slow | number | 26 |
| signal | number | 9 |

First value: element 0 at bar `max(fast, slow) - 1`, elements 1 and 2 at bar `max(fast, slow) + signal - 2`

Moving average convergence divergence: the gap between a fast and a slow `ema()` of the source. It returns `[macd, signal, histogram]`: the gap itself, an `ema()` of the gap, and the difference between the two. The line crossing its signal and the histogram crossing zero are the classic momentum turns.

```openscript
version 1
study("MACD", precision = 2)

fastLen   = input(12, "Fast", min = 1, max = 200)
slowLen   = input(26, "Slow", min = 2, max = 400)
signalLen = input(9, "Signal", min = 1, max = 100)

m    = macd(close, fastLen, slowLen, signalLen)
line = m[0]
sig  = m[1]
hist = m[2]

plot(hist, "Histogram", hist >= 0 ? fade(lime, 40) : fade(red, 40), style = "histogram")
plot(line, "MACD", aqua, width = 2)
plot(sig, "Signal", orange)
level(0, "Zero", fade(gray, 60))

if crossUp(line, sig)
    signal("MACD UP", color = lime, at = "below", shape = "triangleUp")
```

**Remarks.** The line is `ema(src, fast) - ema(src, slow)`. The signal average is fed the line from its first value, so it seeds on the first `signal` values the line produced, and the histogram is `line - signal` as reported. With the defaults the line starts on bar 25 and the signal and histogram on bar 33. The line is in price units, so a stock near 3,000 shows a much larger MACD than one near 300; use `ppo()` to compare them. Give the signal line a name other than `signal`: that is a library function, and assigning to it is error [OS2002](/script/errors/names-and-types#os2002).

**See also.** `ppo()`, `ema()`, `crossUp()`

### ppo()

```
ppo(src: series number, fast?: number = 12, slow?: number = 26, signal?: number = 9) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| fast | number | 12 |
| slow | number | 26 |
| signal | number | 9 |

First value: as `macd`

The percentage price oscillator is MACD expressed as a percentage of the slow average. Because it is scaled by price, a stock near 3,000 and one near 300 can be compared on the same axis, and so can the same stock years apart. It returns `[ppo, signal, histogram]`.

```openscript
version 1
study("PPO", precision = 2)

p    = ppo(close, 12, 26, 9)
hist = p[2]

plot(hist, "Histogram", hist >= 0 ? fade(lime, 40) : fade(red, 40), style = "histogram")
plot(p[0], "PPO", aqua, width = 2)
plot(p[1], "Signal", orange)
level(0, "Zero", fade(gray, 60))
```

**Remarks.** The line is `100 * (ema(src, fast) - ema(src, slow)) / ema(src, slow)`; the signal and histogram are formed exactly as in `macd()`, with the same first values.

**See also.** `macd()`, `roc()`

### mom()

```
mom(src: series number, len?: number = 10) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 10 |

First value: bar `len`

Momentum: the change in the source over a fixed distance, `src - src[len]`. Positive means price is higher than it was `len` bars ago, and the size says by how much, in price units.

```openscript
version 1
study("Momentum", precision = 2)

len = input(10, "Length", min = 1, max = 200)
m = mom(close, len)

plot(m, "Momentum", m >= 0 ? lime : red, style = "histogram")
level(0, "Zero", fade(gray, 60))
```

**Remarks.** The first value is on bar `len`, the first bar that has a value `len` bars behind it. It is not smoothed; `tsi()` is a smoothed relative of it and `roc()` expresses the same change as a percentage.

**See also.** `roc()`, `change()`, `tsi()`

### roc()

```
roc(src: series number, len?: number = 9) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 9 |

First value: bar `len`

Rate of change: the change over `len` bars as a percentage of the older value. On a daily chart, `roc(close, 250)` is roughly the one-year return in percent, since NSE and BSE trade about 250 sessions a year.

```openscript
version 1
study("Rate of change", precision = 2)

len = input(9, "Length", min = 1, max = 500)

plot(roc(close, len), "ROC", lime, width = 2)
level(0, "Zero", fade(gray, 60))
```

**Remarks.** The reading is `100 * (src - src[len]) / src[len]`, first available on bar `len`. It is absent where the older value is zero.

**See also.** `mom()`, `ppo()`, `trix()`

### cmo()

```
cmo(src: series number, len?: number = 9) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 9 |

First value: bar `len`

The Chande momentum oscillator: the sum of the up moves minus the sum of the down moves over the window, divided by their total, on a scale from -100 to 100. It is not smoothed, so a turn shows on the bar it happens. Readings beyond +50 or -50 are commonly read as strong momentum.

```openscript
version 1
study("Chande momentum", precision = 2, range = [-100, 100])

len = input(9, "Length", min = 1, max = 200)

plot(cmo(close, len), "CMO", aqua, width = 2)
level(50, "+50", fade(red, 40))
level(0, "Zero", fade(gray, 60))
level(-50, "-50", fade(lime, 40))
```

**Remarks.** The reading is `100 * (rise - fall) / (rise + fall)`, where `rise` and `fall` are the window sums of the up and down changes. It is 100 when every change in the window was up and -100 when every change was down. It is absent when price did not change at all across the window. The first value is on bar `len`.

**See also.** `rsi()`, `mom()`

### tsi()

```
tsi(src: series number, longLen?: number = 25, shortLen?: number = 13) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| longLen | number | 25 |
| shortLen | number | 13 |

First value: bar `longLen + shortLen - 1`

The true strength index smooths the bar-to-bar change twice, then divides by the size of the change smoothed the same way. The result, between -100 and 100, is a clean reading of the direction of momentum without the noise of `mom()`.

```openscript
version 1
study("True strength index", precision = 2)

t   = tsi(close, 25, 13)
sig = ema(t, 7)

plot(t, "TSI", teal, width = 2)
plot(sig, "Signal", orange)
level(0, "Zero", fade(gray, 60))
```

**Remarks.** The reading is `100 * ema(ema(change, longLen), shortLen) / ema(ema(abs(change), longLen), shortLen)`, with the long length applied first. The first value is on bar `longLen + shortLen - 1`, bar 37 with the defaults. A signal line is not part of the call; the example makes one with `ema()`.

**See also.** `mom()`, `ema()`, `macd()`

### trix()

```
trix(src: series number, len?: number = 18) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 18 |

First value: bar `3 * len - 2`

TRIX is the one-bar percentage change of a triple exponential average. The triple smoothing filters out short swings, so TRIX turns only on sustained changes of direction. The values are small, so give the study a few extra decimals.

```openscript
version 1
study("TRIX", precision = 4)

len = input(18, "Length", min = 1, max = 100)
t = trix(close, len)

plot(t, "TRIX", fuchsia, width = 2)
plot(ema(t, 9), "Signal", orange)
level(0, "Zero", fade(gray, 60))
```

**Remarks.** It is `100 * (e3 - e3[1]) / e3[1]`, where `e3` is `ema(ema(ema(src, len), len), len)`. That is the percentage change of the average, not the change of its logarithm. The first value is on bar `3 * len - 2`, bar 52 for a length of 18.

**See also.** `tema()`, `roc()`

### dpo()

```
dpo(src: series number, len?: number = 21) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 21 |

First value: bar `len + floor(len / 2)`

The detrended price oscillator removes the trend from price so that shorter cycles stand out. It subtracts a moving average taken from about half a window ago, and the peaks and troughs that remain show the rhythm of the swings. Measure the bars between its peaks to estimate a cycle length.

```openscript
version 1
study("Detrended price", precision = 2)

len = input(21, "Length", min = 2, max = 200)

plot(dpo(close, len), "DPO", silver, width = 2)
level(0, "Zero", fade(gray, 60))
```

**Remarks.** The reading is `src - sma(src, len)[floor(len / 2) + 1]`: this bar's value less the simple average as it stood `floor(len / 2) + 1` bars ago. It is reported on the current bar, not drawn back in time. The first value is on bar `len + floor(len / 2)`, bar 31 for a length of 21.

**See also.** `sma()`, `linreg()`

### ultimateOsc()

```
ultimateOsc(len1?: number = 7, len2?: number = 14, len3?: number = 28) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len1 | number | 7 |
| len2 | number | 14 |
| len3 | number | 28 |

First value: bar `max(len1, len2, len3)`

The ultimate oscillator blends buying pressure over three windows, 7, 14 and 28 bars by default, so that no single length dominates. It reads from 0 to 100, with 70 and 30 as the usual overbought and oversold levels.

```openscript
version 1
study("Ultimate oscillator", precision = 2, range = [0, 100])

u = ultimateOsc(7, 14, 28)

plot(u, "UO", orange, width = 2)
level(70, "Overbought", fade(red, 40))
level(30, "Oversold", fade(lime, 40))
```

**Remarks.** Buying pressure is `close - min(low, previous close)` and the bar's range is `max(high, previous close) - min(low, previous close)`. At each length the window sum of pressure is divided by the window sum of range, and the three ratios are blended as `100 * (4 * short + 2 * middle + long) / 7`, the shortest window weighted most. Both terms need the previous close, so the first value is on bar `max(len1, len2, len3)`, bar 28 by default.

**See also.** `stoch()`, `rsi()`

### awesomeOsc()

```
awesomeOsc(fast?: number = 5, slow?: number = 34) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| fast | number | 5 |
| slow | number | 34 |

First value: bar `max(fast, slow) - 1`

The awesome oscillator is the difference between a 5 bar and a 34 bar simple average of the bar midpoint, `hl2`. It is drawn as a histogram, coloured by whether each bar is higher than the one before.

```openscript
version 1
study("Awesome oscillator", precision = 2)

ao = awesomeOsc(5, 34)

plot(ao, "AO", ao > ao[1] ? lime : red, style = "histogram")
level(0, "Zero", fade(gray, 60))
```

**Remarks.** The reading is `sma(hl2, fast) - sma(hl2, slow)`, first available on bar `max(fast, slow) - 1`, bar 33 by default.

**See also.** `macd()`, `sma()`, `hl2`

### fisher() (planned, not available yet)

```
fisher(len?: number = 9) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 9 |

First value: bar `len`

The Fisher transform will reshape the close's position in its recent range so that extremes stand out sharply, returning `[fisher, trigger]`.

### rvi() (planned, not available yet)

```
rvi(src: series number, len?: number = 10) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 10 |

First value: bar `len + 3`

The relative vigor index will measure where the close sits inside each bar's range, smoothed, returning `[rvi, signal]`.

### coppock() (planned, not available yet)

```
coppock(src: series number, roc1?: number = 14, roc2?: number = 11, wmaLen?: number = 10) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| roc1 | number | 14 |
| roc2 | number | 11 |
| wmaLen | number | 10 |

First value: bar `max(roc1, roc2) + wmaLen - 1`

The Coppock curve will be a weighted average of two rates of change, a slow momentum turn traditionally read on monthly charts of an index.

## Volatility and bands

Volatility measures how far an instrument moves. You use it to size stops and positions, to set bands around price, and to spot quiet periods that often come before large moves.

### trueRange()

```
trueRange() -> series number
```

First value: bar 0

True range is the bar's full range including any gap from the previous close: the largest of `high - low`, `abs(high - previous close)` and `abs(low - previous close)`. It is the building block of `atr()`, and on NSE, where most gaps happen at the 09:15 open, it captures the overnight move that `high - low` misses.

```openscript
version 1
study("True range and gaps", precision = 2)

tr     = trueRange()
gapped = tr > high - low

plot(tr, "True range", gapped ? orange : silver, style = "column")
```

When the true range is larger than the bar's own range, the previous close lay outside this bar: price gapped. The example colours those bars orange.

**Remarks.** On the chart's first bar there is no previous close, so the result there is `high - low`. This is the one deliberate exception in the library to absence spreading from a missing value: the bar's own range is a true statement about that bar, and it lets `atr()` start on bar `len - 1`.

**See also.** `atr()`, `natr()`, `chop()`

### atr()

```
atr(len?: number = 14) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 14 |

First value: bar `len - 1`

The average true range is the `rma()` of `trueRange()`: the typical distance this instrument moves in one bar, in its own price units. It is the working measure for stop distances, target distances and position sizes.

```openscript
version 1
study("ATR stop levels", overlay = true)

len  = input(14, "ATR length", min = 1, max = 100)
mult = input(2.0, "Multiple", min = 0.5, max = 10, step = 0.5)

a = atr(len)

plot(close - mult * a, "Long stop", fade(red, 30), style = "step")
plot(close + mult * a, "Short stop", fade(lime, 30), style = "step")
```

**Remarks.** Because true range has a value on bar 0, the first value is on bar `len - 1`. The value is in price units, rupees for an NSE stock and index points for an index future, so a 2 ATR stop on a NIFTY future and on a stock are very different amounts. To size a position so every trade risks the same amount, divide the amount by the stop distance; see [Position and sizing](/script/strategies/position-and-sizing).

**See also.** `natr()`, `trueRange()`, `supertrend()`, `keltner()`

### natr()

```
natr(len?: number = 14) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 14 |

First value: bar `len - 1`

The normalised average true range is `atr()` as a percentage of the close. It lets you compare volatility between instruments at different prices, or the same instrument across years.

```openscript
version 1
study("ATR percent", precision = 2)

hot = input(3.0, "High volatility above, percent", min = 0.1, max = 20)
n   = natr(14)

plot(n, "ATR %", n > hot ? orange : aqua, width = 2)
level(hot, "Threshold", fade(gray, 50))
```

**Remarks.** The reading is `100 * atr(len) / close`, with the same first value as `atr()`.

**See also.** `atr()`, `hv()`

### stdev()

```
stdev(src: series number, len: number, sample?: bool = false) -> series number
stdev(arr: array<number>) -> number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |
| sample | bool | false |
| arr | array<number> | required |

First value: bar `len - 1`

The standard deviation of the last `len` values: how widely they are spread around their mean, in the source's own units. By default it divides by `len` (the population form, which is what the band indicators use); pass `sample = true` to divide by `len - 1`. A second form takes an array and returns the standard deviation of its elements.

```openscript
version 1
study("Z-score", precision = 2)

len = input(20, "Length", min = 2, max = 500)

mean = sma(close, len)
dev  = stdev(close, len)
z    = dev > 0 ? (close - mean) / dev : none

plot(z, "Z-score", aqua, width = 2)
level(2, "+2", fade(red, 40))
level(0, "Mean", fade(gray, 60))
level(-2, "-2", fade(lime, 40))
```

The z-score says how many standard deviations the close sits from its average. The array form works on any list you build:

```openscript
version 1
study("Spread of the last five closes", precision = 2)

closes = [close, close[1], close[2], close[3], close[4]]

plot(stdev(closes), "Deviation of five closes", silver)
```

**Remarks.** The series form computes the mean first and then the squared distances from it, in two passes, which stays accurate on prices where the values are large and the spread is small. The first value is on bar `len - 1`. The array form always uses the population divisor and returns `none` for an empty array or one holding an absent element.

**See also.** `variance()`, `bollinger()`, `hv()`, [Collections](/script/language/collections)

### variance()

```
variance(src: series number, len: number, sample?: bool = false) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |
| sample | bool | false |

First value: bar `len - 1`

The variance of the last `len` values: the square of `stdev()`. It is in squared units (rupees squared for a price), which makes it hard to read on a chart but useful in calculations that add or compare spreads. `sample = true` switches to the `len - 1` divisor.

```openscript
version 1
study("Variance, population and sample", precision = 4)

len = input(20, "Length", min = 2, max = 500)

plot(variance(close, len), "Population", silver)
plot(variance(close, len, sample = true), "Sample", orange)
```

**Remarks.** It is computed in two passes, the mean first and then the squared deviations from it, so it never comes out negative. The first value is on bar `len - 1`. With `sample = true` and a length of 1 there is nothing to divide by, and the result is absent.

**See also.** `stdev()`, `covariance()`

### hv()

```
hv(src: series number, len?: number = 20, periodsPerYear?: number = 252) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 20 |
| periodsPerYear | number | 252 |

First value: bar `len`

Historical volatility: the standard deviation of the bar-to-bar log returns, annualised. It is the realised counterpart of an option's implied volatility, so comparing the two on NIFTY or BANKNIFTY tells you whether options are pricing more or less movement than the index has actually shown. On Indian index options the implied side of that comparison comes from Black-76 pricing off the synthetic future; `hv()` gives you the realised side.

```openscript
version 1
study("Historical volatility", precision = 2)

len     = input(20, "Length", min = 2, max = 500)
periods = input(252, "Bars per year", min = 1)

plot(hv(close, len, periods) * 100, "HV %", purple, width = 2)
```

**Remarks.** The reading is `stdev(log(src / src[1]), len) * sqrt(periodsPerYear)`, with the population deviation. It is a proportion, not a percentage: 0.18 means 18 percent a year, so the example multiplies by 100 at the plot. `periodsPerYear` is the number of the chart's bars in a year: 252 for daily bars, and for intraday NSE bars the bars in the 09:15 to 15:30 session times the sessions, for example `75 * 252` (18900) on a 5-minute chart. A log return needs the previous bar, so the first value is on bar `len`.

**See also.** `stdev()`, `natr()`, `log()`

### bollinger()

```
bollinger(src: series number, len?: number = 20, mult?: number = 2) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 20 |
| mult | number | 2 |

First value: all elements at bar `len - 1`

Bollinger Bands: a simple moving average with bands a multiple of the standard deviation above and below it. The bands widen when price becomes volatile and narrow when it calms. It returns `[basis, upper, lower]`.

```openscript
version 1
study("Bollinger Bands", overlay = true)

len  = input(20, "Length", min = 2, max = 500)
mult = input(2.0, "Deviations", min = 0.5, max = 5, step = 0.5)

bb = bollinger(close, len, mult)

plot(bb[0], "Basis", orange)
upper = plot(bb[1], "Upper", aqua)
lower = plot(bb[2], "Lower", aqua)
fill(upper, lower, fade(aqua, 92))
```

Bollinger Bands with the same settings on a BHEL 15 minute chart, from the [Bollinger Bands](/script/getting-started/example-scripts#bollinger-bands) study in Example scripts, which draws the bands in blue and labels each close that crosses outside a band:


**Remarks.** The basis is `sma(src, len)`, and the bands are `basis + mult * stdev(src, len)` and `basis - mult * stdev(src, len)`, with the population deviation. All three values start on bar `len - 1`.

**See also.** `bbWidth()`, `bbPercent()`, `keltner()`, `stdev()`

### bbWidth()

```
bbWidth(src: series number, len?: number = 20, mult?: number = 2) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 20 |
| mult | number | 2 |

First value: bar `len - 1`

Bollinger band width: the distance between the bands divided by the basis. It turns the bands' shape into a single number, so a squeeze, the narrow and quiet stretch that often comes before a large move, shows up as a low.

```openscript
version 1
study("Band width squeeze", precision = 4)

len      = input(20, "Length", min = 2, max = 500)
lookback = input(120, "Squeeze lookback", min = 10, max = 1000)

w       = bbWidth(close, len, 2)
squeeze = w <= lowest(w, lookback)

plot(w, "Band width", squeeze ? orange : aqua, width = 2)
```

**Remarks.** The reading is `(upper - lower) / basis`, computed from the bands exactly as `bollinger()` reports them, so a study that plots both agrees to the last digit. A value of 0.05 means the bands are 5 percent of the basis apart. The first value is on bar `len - 1`.

**See also.** `bollinger()`, `bbPercent()`, `keltner()`

### bbPercent()

```
bbPercent(src: series number, len?: number = 20, mult?: number = 2) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | 20 |
| mult | number | 2 |

First value: bar `len - 1`

Percent B says where the source sits between the Bollinger Bands: 0 at the lower band, 0.5 at the basis and 1 at the upper band. It goes above 1 or below 0 when price closes outside the bands.

```openscript
version 1
study("Percent B", precision = 2)

pb = bbPercent(close, 20, 2)

plot(pb, "%B", aqua, width = 2)
level(1, "Upper band", fade(red, 40))
level(0.5, "Basis", fade(gray, 60))
level(0, "Lower band", fade(lime, 40))
```

**Remarks.** The reading is `(src - lower) / (upper - lower)`, from the bands as reported. It is absent on a bar where the bands meet: when every value in the window is equal, or when `mult` is 0. The first value is on bar `len - 1`.

**See also.** `bollinger()`, `bbWidth()`, `stoch()`

### keltner()

```
keltner(len?: number = 20, mult?: number = 2, atrLen?: number = 10, maType?: string = "ema") -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 20 |
| mult | number | 2 |
| atrLen | number | 10 |
| maType | string | "ema" |

First value: all elements at bar `max(len, atrLen) - 1`

Keltner channels: a moving average of the close with bands a multiple of the average true range above and below it. Where Bollinger Bands widen with the spread of closes, Keltner channels widen with how far the bars actually travel, which makes them steadier. It returns `[basis, upper, lower]`.

```openscript
version 1
study("Keltner channel and squeeze", overlay = true)

len  = input(20, "Length", min = 2, max = 500)
mult = input(1.5, "ATR multiple", min = 0.5, max = 5, step = 0.25)

kc = keltner(len, mult, 10, "ema")
bb = bollinger(close, len, 2)

plot(kc[0], "Basis", orange)
upper = plot(kc[1], "Upper", teal)
lower = plot(kc[2], "Lower", teal)
fill(upper, lower, fade(teal, 92))

squeezed = bb[1] < kc[1] and bb[2] > kc[2]
background(squeezed ? fade(yellow, 90) : none)
```

The background marks a squeeze: the Bollinger Bands sitting inside the Keltner channel. During warmup either side of the comparison is absent, the condition takes the false branch, and nothing is painted.

**Remarks.** The basis is the average of the close over `len`, of the type `maType` names: `"sma"`, `"ema"`, `"wma"`, `"rma"`, `"hma"` or `"vwma"`, as in `ma()`. The width is `atr(atrLen)`, and the bands are `basis + mult * atr` and `basis - mult * atr`. All three values start on bar `max(len, atrLen) - 1`. The compiler does not check `maType`; a name that is not one of the six leaves all three values absent on every bar.

**See also.** `bollinger()`, `atr()`, `ma()`, `donchian()`

### donchian()

```
donchian(len?: number = 20) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 20 |

First value: all elements at bar `len - 1`

Donchian channels: the highest high and lowest low of the last `len` bars, with the midpoint between them. A close beyond the previous bar's channel is a breakout, the basis of many trend-following systems. It returns `[upper, basis, lower]`, upper first.

```openscript
version 1
study("Donchian breakout", overlay = true)

len = input(20, "Length", min = 2, max = 500)

dc    = donchian(len)
upper = dc[0]
lower = dc[2]

plot(upper, "Upper", lime, style = "step")
plot(dc[1], "Middle", fade(gray, 40), style = "step")
plot(lower, "Lower", red, style = "step")

if close > upper[1]
    signal("BREAKOUT", color = lime, at = "below", shape = "triangleUp")
if close < lower[1]
    signal("BREAKDOWN", color = red, at = "above", shape = "triangleDown")
```

**Remarks.** The window includes the current bar, so the close can never be above this bar's upper line. Test a breakout against the previous bar's channel, `upper[1]`, as the example does. The middle value is `(upper + lower) / 2`. All three start on bar `len - 1`.

**See also.** `highest()`, `lowest()`, `keltner()`, `ichimoku()`

### massIndex() (planned, not available yet)

```
massIndex(len?: number = 25) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 25 |

First value: bar `len + 17`

The mass index will measure how much the high to low range is expanding, as a warning that a trend may be about to reverse.

## Volume

Volume indicators ask whether traders are backing a price move with size. They need the traded volume of each bar, which the host (the application running the script, such as the /trading chart) supplies with the price. An index itself, such as NIFTY 50 or BANKNIFTY, trades no volume, so these functions have nothing to measure on an index chart: chart the index future on NFO when you need volume for an index. On a bar whose volume is absent, every function in this section is absent too.

### vwap()

```
vwap(src?: series number = hlc3) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | hlc3 |

First value: the session's first bar

The volume weighted average price since the session opened: the average price paid for every unit traded today. Intraday traders use it as the day's fair value: price above VWAP favours buyers, below favours sellers. It restarts on the first bar of each trading session, found from the session hours the host states for the instrument (09:15 IST for NSE and NFO), not at midnight.

```openscript
version 1
study("Session VWAP", overlay = true)

v = vwap()

plot(v, "VWAP", orange, width = 2)
barColor(close > v ? lime : close < v ? red : none)
```

> **In this release the /trading chart does not state the instrument's session hours to the script, so there `vwap()` has no session to start from and is absent on every bar: the study above draws nothing. See [Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today).**

No Indian session runs past midnight IST, so a new IST date is a new session. Anchoring `vwapAnchor()` to that gives the same average, and it works on the /trading chart today:

```openscript
version 1
study("Day VWAP by the IST date", overlay = true)

// A new trading day: the first bar on the chart, or a bar on a different
// IST date from the bar before it.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

dayVwap = vwapAnchor(hlc3, newDay)

plot(dayVwap, "Day VWAP", orange, width = 2)
barColor(close > dayVwap ? lime : close < dayVwap ? red : none)
```

**Remarks.** It is `sum(src * volume) / sum(volume)` over the session so far, with `src` defaulting to `hlc3`. Both running totals restart on the session's first bar before that bar is added, so the first bar of each session is the first bar of the new average. On a daily or longer chart every bar is its own session and the result equals `src`; the compiler does not warn about that yet. A bar with absent data gives an absent result and leaves the totals as they were.

**See also.** `vwapAnchor()`, `vwma()`, `session.isFirstBar`, [Sessions and time](/script/data/sessions-and-time)

### vwapAnchor()

```
vwapAnchor(src: series number, resetWhen: series bool) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| resetWhen | series bool | required |

First value: the first bar `resetWhen` is true

The same volume weighted average, restarted on every bar where `resetWhen` is true. Anchor it to the start of a week, a month, an expiry, a result announcement or any condition you can write.

```openscript
version 1
study("Weekly and monthly VWAP", overlay = true)

newWeek  = date.startOfWeek(time) != date.startOfWeek(time[1])
newMonth = date.month(time) != date.month(time[1])

plot(vwapAnchor(hlc3, newWeek), "Weekly VWAP", aqua, width = 2)
plot(vwapAnchor(hlc3, newMonth), "Monthly VWAP", fuchsia, width = 2)
```

The conditions compare this bar's week and month with the previous bar's, so a week whose Monday is a market holiday still resets on its first trading day.

**Remarks.** The result is absent until `resetWhen` is first true, because there is no anchor to measure from. On an anchor bar both totals are reset before the bar's own price and volume are added, so the anchor bar opens the new average. On the chart's first bar `time[1]` is absent and `!=` is true, so the example's first average starts there.

**See also.** `vwap()`, `date.startOfWeek()`, `date.month()`

### obv()

```
obv() -> series number
```

First value: bar 0, seeded 0

On balance volume: a running total that adds the bar's whole volume when the close rises and subtracts it when the close falls. The level means little on its own; what matters is its direction and whether it confirms price. A price high that on balance volume does not confirm is a warning.

```openscript
version 1
study("On balance volume", format = "volume")

o = obv()

plot(o, "OBV", teal, width = 2)
plot(ema(o, 20), "OBV average", fade(orange, 30))
```

**Remarks.** The total starts at 0 on the chart's first bar, which has no earlier close to compare with. An unchanged close adds nothing. An absent bar gives an absent result and leaves the total where it was.

**See also.** `ad()`, `pvt()`, `cum()`

### ad()

```
ad() -> series number
```

First value: bar 0

The accumulation distribution line: a running total of volume weighted by where the close sat inside the bar. A close at the high adds the whole volume, a close at the low subtracts it, and a close in the middle adds nothing. A rising line says the closes are landing near the highs.

```openscript
version 1
study("Accumulation distribution", format = "volume")

plot(ad(), "A/D", lime, width = 2)
```

**Remarks.** Each bar adds `((close - low) - (high - close)) / (high - low) * volume`. A bar with no range, where high equals low, adds 0 rather than ending the total. The total starts at 0 before the first bar.

**See also.** `adOsc()`, `cmf()`, `obv()`

### adOsc()

```
adOsc(fast?: number = 3, slow?: number = 10) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| fast | number | 3 |
| slow | number | 10 |

First value: bar `max(fast, slow) - 1`

The accumulation distribution oscillator, often called the Chaikin oscillator: a fast `ema()` of the `ad()` line minus a slow one. It dates the turns in accumulation, crossing above zero when buying pressure picks up.

```openscript
version 1
study("A/D oscillator", format = "volume")

osc = adOsc(3, 10)

plot(osc, "A/D oscillator", osc >= 0 ? lime : red, style = "histogram")
level(0, "Zero", fade(gray, 60))
```

**Remarks.** It is `ema(ad, fast) - ema(ad, slow)`, taken over the running total rather than the per-bar term. The first value is on bar `max(fast, slow) - 1`, bar 9 by default.

**See also.** `ad()`, `cmf()`, `macd()`

### cmf()

```
cmf(len?: number = 20) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 20 |

First value: bar `len - 1`

Chaikin money flow: the accumulation over the window as a fraction of the volume traded in it. It runs from -1 to 1; readings above zero say closes have been landing in the upper half of their bars on the volume that mattered.

```openscript
version 1
study("Chaikin money flow", precision = 3)

c = cmf(20)

plot(c, "CMF", c >= 0 ? lime : red, style = "histogram")
level(0.05, "Buying", fade(lime, 50), "dotted")
level(-0.05, "Selling", fade(red, 50), "dotted")
```

**Remarks.** It is the window sum of the `ad()` per-bar term divided by the window sum of volume, first available on bar `len - 1`. The plus and minus 0.05 lines are a common threshold, not part of the definition.

**See also.** `ad()`, `mfi()`

### mfi()

```
mfi(len?: number = 14) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 14 |

First value: bar `len`

The money flow index is `rsi()` computed on money flow, the typical price times volume, instead of on price. It reads from 0 to 100, with 80 and 20 as the usual overbought and oversold levels.

```openscript
version 1
study("Money flow index", precision = 2, range = [0, 100])

m = mfi(14)

plot(m, "MFI", purple, width = 2)
level(80, "Overbought", fade(red, 40))
level(20, "Oversold", fade(lime, 40))
```

**Remarks.** Each bar's flow is `hlc3` times volume. The flow counts on the rising side when the typical price rose from the previous bar and on the falling side when it fell; an unchanged typical price counts on neither. The result is `100 - 100 / (1 + rising / falling)` over window sums, not smoothed averages, and it is 100 when the falling sum is zero. The first value is on bar `len`.

**See also.** `rsi()`, `cmf()`

### pvt()

```
pvt() -> series number
```

First value: bar 1, seeded 0

The price volume trend: a running total of volume weighted by the percentage change of the close. Unlike `obv()`, which adds the whole volume for any rise, a small rise adds a small share and a large rise a large one.

```openscript
version 1
study("Price volume trend", format = "volume")

p = pvt()

plot(p, "PVT", olive, width = 2)
plot(ema(p, 21), "PVT average", fade(orange, 30))
```

**Remarks.** Each bar adds `(close - close[1]) / close[1] * volume` to a total that starts at 0. The first bar has no change behind it and is absent; the second bar already carries its own term.

**See also.** `obv()`, `roc()`

### eom()

```
eom(len?: number = 14) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 14 |

First value: bar `len`

Ease of movement: how far the bar's midpoint moved per unit of volume, averaged over `len` bars. A large positive reading means price rose easily on light volume; a reading near zero means it took heavy volume to move price at all.

```openscript
version 1
study("Ease of movement", precision = 2)

scale = input(100000, "Display scale", min = 1)
e     = eom(14) * scale

plot(e, "EOM", e >= 0 ? lime : red, width = 2)
level(0, "Zero", fade(gray, 60))
```

**Remarks.** Each bar's term is `(hl2 - hl2[1]) * (high - low) / volume`, and the result is its `sma()` over `len`, first available on bar `len`. No scaling constant is applied, so on a liquid NSE stock, where volume runs to lakhs of shares, the raw reading is very small. Multiply it at the plot to bring it into a readable range, as the example does with an input.

**See also.** `forceIndex()`, `hl2`

### forceIndex()

```
forceIndex(len?: number = 13) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 13 |

First value: bar `len`

The force index multiplies each bar's change in close by its volume and smooths the result: a big move on big volume is a strong force. Crossings of zero mark shifts between buying and selling pressure.

```openscript
version 1
study("Force index", format = "volume")

f = forceIndex(13)

plot(f, "Force", f >= 0 ? lime : red, style = "histogram")
level(0, "Zero", fade(gray, 60))
```

**Remarks.** It is the `ema()` over `len` of `(close - close[1]) * volume`. The first change is on bar 1, so the first value is on bar `len`. The values are large, so the example uses the study's volume format for the axis.

**See also.** `eom()`, `obv()`, `ema()`

### relativeVolume()

```
relativeVolume(len?: number = 20) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| len | number | 20 |

First value: bar `len - 1`

Relative volume: this bar's volume divided by its average volume over the last `len` bars. A reading of 2 means twice the normal volume. Use it to confirm breakouts and to spot unusual activity. On an intraday chart, the opening bars at 09:15 naturally trade far more than midday bars, so compare like with like or use a daily chart.

```openscript
version 1
study("Confirmed breakout", overlay = true)

len   = input(20, "Lookback", min = 2, max = 500)
ratio = input(1.8, "Volume multiple", min = 1, max = 10)

hi = highest(high, len)[1]
rv = relativeVolume(len)

plot(hi, "Breakout level", orange, style = "step")

if close > hi and rv > ratio
    signal("BREAK", color = lime, at = "below", shape = "triangleUp")
```

**Remarks.** It is `volume / sma(volume, len)`, first available on bar `len - 1`. The average includes the current bar.

**See also.** `vwma()`, `highest()`, `volume`

### nvi() (planned, not available yet)

```
nvi() -> series number
```

First value: bar 1

The negative volume index will keep a running total of price changes on bars where volume fell, following the idea that informed traders act on quiet days.

### pvi() (planned, not available yet)

```
pvi() -> series number
```

First value: bar 1

The positive volume index will keep a running total of price changes on bars where volume rose.

### klinger() (planned, not available yet)

```
klinger(fast?: number = 34, slow?: number = 55, signal?: number = 13) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| fast | number | 34 |
| slow | number | 55 |
| signal | number | 13 |

First value: bar `slow + signal - 2`

The Klinger oscillator will compare volume force with the trend of each bar, returning `[klinger, signal]`.

### cvd() (planned, not available yet)

```
cvd() -> series number
```

First value: bar 0

Cumulative volume delta will keep a running total of buying minus selling volume, once hosts supply the trade-by-trade data inside each bar.

### volumeProfile() (planned, not available yet)

```
volumeProfile(rows?: number = 24, from?: number = none) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| rows | number | 24 |
| from | number | none |

First value: the anchor bar

The volume profile will total the volume traded at each price level since an anchor bar, for a histogram drawn sideways against the price axis.

## Pattern and swing

Swing points mark the turns of the market: the highs and lows that define support, resistance and the structure of a trend. The dedicated swing function, `zigzag()`, is planned. Until it arrives, build swings from `pivotHigh()` and `pivotLow()`, which confirm a turn after a set number of bars on each side:

```openscript
version 1
study("Swing highs and lows", overlay = true)

left  = input(5, "Bars to the left", min = 1, max = 50)
right = input(5, "Bars to the right", min = 1, max = 50)

ph = pivotHigh(high, left, right)
pl = pivotLow(low, left, right)

var lastHigh = none
var lastLow  = none

if not isNone(ph)
    lastHigh = ph
if not isNone(pl)
    lastLow = pl

plot(lastHigh, "Last swing high", fade(red, 30), style = "step")
plot(lastLow, "Last swing low", fade(lime, 30), style = "step")
```

A pivot is known only `right` bars after it forms, so the levels step in late by that many bars. That delay is the honest cost of confirming a swing, not a fault. [Series functions](/script/reference/series) documents the pivot functions in full.

### zigzag() (planned, not available yet)

```
zigzag(src: series number, deviation: number) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| deviation | number | required |

First value: first confirmed swing

Zigzag will connect swing highs and lows that are confirmed once price reverses by at least `deviation` percent, filtering out smaller moves.

## Related

[Series functions](/script/reference/series) for `highest()`, `crossUp()`, `change()` and the other helpers these indicators are built from. [Warmup](/script/language/warmup) and [Absent values](/script/language/absent-values) for why a line starts where it does. [Plotting](/script/reference/plotting) for drawing a result, [Higher timeframes](/script/data/higher-timeframes) for running any indicator on a coarser interval, and [Inputs](/script/inputs/inputs) for making lengths and multipliers adjustable.


## Series functions

Source: https://openalgo.in/script/reference/series

A series is a value with one entry per bar, such as `close`, `volume` or any name you assign at the top level of a script. The functions on this page read a series **across** bars rather than on one bar: the highest high of the last 20 bars, whether a fast average has just crossed a slow one, how many bars have passed since a breakout, the low at the last swing. Nearly every study and strategy uses a few of them, and many of the indicators on [Technical analysis](/script/reference/technical-analysis) are built from them.

Each one replaces a lookback you would otherwise write by hand with the history operator `[n]`, and each states the exact bar on which it first has a value. Use this page to pick the right function and to understand what it returns on the first bars of a chart, on a bar where an input is missing, and on a tie. [Bars and history](/script/language/bars-and-history) covers the history operator itself.

```openscript
version 1
study("Twenty bar breakout", overlay = true, precision = 2)

len = input(20, "Lookback, in bars", min = 2, max = 200)

// The high and low of the previous len bars. The [1] leaves this bar out,
// so a close above upper is a close above every earlier high in the window.
upper = highest(high, len)[1]
lower = lowest(low, len)[1]

breakUp   = crossUp(close, upper)
breakDown = crossDown(close, lower)

// The level of the most recent upside break, and how many bars ago it was.
lastBreak = valueWhen(breakUp, upper)
barsAfter = barsSince(breakUp)

plot(upper, "Upper", lime, style = "step")
plot(lower, "Lower", red, style = "step")
plot(barsAfter <= 10 ? lastBreak : none, "Recent breakout level", orange, style = "step")

if breakUp
    signal("BREAK UP", at = "below", shape = "triangleUp")
if breakDown
    signal("BREAK DOWN", at = "above", shape = "triangleDown")
```

On a 15 minute chart of an NSE stock, the 09:15 to 15:30 session is 25 bars, so a lookback of 25 is a rolling window of exactly one session. The breakout level shows on the breakout bar and the ten bars after it, and then disappears, because `barsAfter <= 10` is false after that and the plot receives `none`.

## Rules every function here follows

| Rule | What it means for your script |
|---|---|
| The window includes this bar | `highest(high, 20)` covers this bar and the 19 before it. Add `[1]` to compare against the bars before this one |
| Warmup is exact | Warmup is the run of bars at the start of the chart before a call has enough history. Before the bar shown under **First value**, a call returns `none`, the absent value, and a plot draws a gap |
| One absent bar makes a window absent | If any bar in the window is absent, the result is absent. `sumSkip()`, `avgSkip()` and `countPresent()` are the exceptions, and `count()` counts an absent condition as not true |
| A length is a whole number of 1 or more | Any other value stops the script on that bar |
| Each call keeps its own state | Compute at the top level of the script, on every bar |

### The window includes this bar

A window of length `len` is this bar and the `len - 1` bars before it. That matters most for breakouts. `close > highest(high, 20)` can never be true, because the window holds this bar's own high and a close is never above its own bar's high. Compare against the window that ended on the previous bar instead: `close > highest(high, 20)[1]`. The `[1]` costs one bar of warmup, so the first value moves from bar 19 to bar 20.

### First values and absence

The **First value** line of each entry counts the oldest bar on the chart as bar 0. "Bar `len - 1`" means the call is absent on bars 0 to `len - 2` and has a value from bar `len - 1` on. Warmups add up when you feed one call into another: `highest(ema(close, 10), 20)` first has a value on bar 9 + 19 = bar 28. [Warmup](/script/language/warmup) shows how to count a chain.

Absence inside a window spreads to the result. A study that reads another instrument, or a value you set to `none` on some bars on purpose, produces a gap for as long as the absent bar sits inside the window. When you want the window to pass over absent bars, use the three functions under [Totals that skip absent bars](#totals-that-skip-absent-bars).

### Lengths

A length (`len`, `left`, `right`, `n`) must be a whole number of 1 or more. A fractional, zero or negative length stops the script on that bar with run-time error [OS4003](/script/errors/runtime#os4003), naming the function, the argument and the value it received. It is never rounded for you, because a length of 14.5 is a mistake in the script. When a length is computed, make it whole and keep it at 1 or above:

```openscript
version 1
study("Half the setting")

len  = input(21, "Length", min = 2, max = 200)
half = max(1, round(len / 2))

plot(highest(high, half), "High over half the window", lime)
```

A length can be an `input()` or even a series that changes from bar to bar. When the length changes, the call starts its window again: it is absent until it has seen `len` bars at the new length, and then continues normally. `highest(high, n)` with `n` stepping from 20 to 30 is absent for 29 bars, starting on the bar of the step, and has a value again on the 30th.

### Where to call them

Every function on this page keeps state between bars, so each carries a Keeps state badge. The state belongs to the place the call is written: two calls of `highest(high, 20)` in two places are two separate windows.

A call that does not run on a bar does not see that bar. So a call inside an `if` block skips the bars where the condition was false, and its window is no longer "the last 20 bars" but "the last 20 bars on which the block ran". The compiler warns about it with [OS8001](/script/errors/warnings#os8001):

```openscript
trending = close > ema(close, 50)
if trending
    // Runs only on trending bars, so the window leaves the other bars out.
    prevTop = highest(high, 20)[1]
    if close > prevTop
        signal("BREAKOUT", at = "below", shape = "triangleUp")
```

Compute first, on every bar, and decide afterwards:

```openscript
version 1
study("Compute first, decide after", overlay = true)

trending = close > ema(close, 50)
prevTop  = highest(high, 20)[1]

if trending and close > prevTop
    signal("BREAKOUT", at = "below", shape = "triangleUp")
```

[Execution model](/script/language/execution-model) explains in full how each call keeps its own state.

## Window highs and lows

### highest()

```
highest(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The largest value of `src` over the last `len` bars, this bar included. Use it for a breakout level, the top of a channel, or the high of a range you want to trade out of.

```openscript
version 1
study("Previous 20 bar high", overlay = true)

prevHigh = highest(high, 20)[1]
plot(prevHigh, "Previous 20 bar high", lime, style = "step")

if close > prevHigh
    barColor(lime)
```

**Remarks.** The window includes this bar, so compare a close against `highest(high, len)[1]`, as above, not against `highest(high, len)`. If any bar in the window is absent, the result is absent for as long as that bar stays in the window.

**See also.** `lowest()`, `highestBars()`, `donchian()`

### lowest()

```
lowest(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The smallest value of `src` over the last `len` bars, this bar included. Use it for the bottom of a range, a support level, or a stop below recent lows.

```openscript
version 1
study("Stop under the last ten lows", overlay = true)

stopLine = lowest(low, 10)
plot(stopLine, "Ten bar low", red, style = "step")

if close < lowest(low, 10)[1]
    signal("BELOW THE RANGE", at = "above", shape = "arrowDown")
```

**Remarks.** A stop taken from `lowest` moves down as well as up. For a trailing stop that only rises, keep it in a `var` and raise it with `max()`; [Persistence](/script/language/persistence) shows the pattern.

**See also.** `highest()`, `lowestBars()`, `donchian()`

### highestBars()

```
highestBars(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

How many bars ago the highest value in the window was set: `0` when this bar set it, up to `len - 1` when the oldest bar in the window did. Use it to ask how fresh a high is, or to find the bar a high was made on.

```openscript
version 1
study("Fresh 50 bar high", overlay = true)

age = highestBars(high, 50)
plot(highest(high, 50), "50 bar high", lime, style = "step")

if age == 0
    signal("NEW HIGH", at = "above", shape = "circle")
```

**Remarks.** A tie goes to the most recent bar: when the same high is touched twice inside the window, the count is to the later touch. The result is a whole number of bars, so it works as an offset: `close[highestBars(high, 50)]` is the close on the bar that set the high, and `time[highestBars(high, 50)]` is that bar's time, which is where a drawing anchored to the high belongs.

**See also.** `highest()`, `lowestBars()`, `barsSince()`

### lowestBars()

```
lowestBars(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

How many bars ago the lowest value in the window was set: `0` when this bar set it. Use it to anchor a label or a line at the low of a range.

```openscript
version 1
study("Where the 20 bar low was", overlay = true)

age      = lowestBars(low, 20)
lowTime  = time[age]
lowPrice = low[age]

if bar.isLast
    draw.label(lowTime, lowPrice, "20 bar low", color = red)
```

**Remarks.** Ties go to the most recent bar, as with `highestBars()`. During the first `len - 1` bars the result is absent, and so is any value read through it, such as `low[age]` above.

**See also.** `lowest()`, `highestBars()`, `draw.label()`

## Change and direction

### change()

```
change(src: series number) -> series number
change(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar 1

How much `src` has moved: `src - src[1]` with one argument, and `src - src[len]` with two. Use it for a bar's change, a change over a week of daily bars, or as the input to another function.

```openscript
version 1
study("Change over one bar and five", precision = 2)

plot(change(close), "Change on the bar", gray, style = "histogram")
plot(change(close, 5), "Change over five bars", aqua)
```

**Remarks.** On a daily NSE chart, five bars is a trading week. The one argument form has its first value on bar 1; the two argument form has it on bar `len`, because it needs the bar `len` bars back. For a percentage, divide by the earlier value, `change(close, 5) / close[5] * 100`, or use `roc()`.

**See also.** `mom()`, `roc()`, `rising()`, `history()`

### rising()

```
rising(src: series number, len: number) -> series bool
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len`

True when `src` went up on each of the last `len` bars: every one of the last `len` one bar changes was above zero. Use it to confirm that an average or an oscillator is climbing, not just above a level.

```openscript
version 1
study("Direction of the average", overlay = true)

ema20 = ema(close, 20)
up    = rising(ema20, 3)
down  = falling(ema20, 3)

plot(ema20, "EMA 20", orange)

if up
    barColor(lime)
else if down
    barColor(red)
```

**Remarks.** The test is strict: a bar on which `src` did not change breaks the run, so a flat value is neither rising nor falling. The first value is on bar `len`, one bar later than a window of `len` values, because `len` changes need `len + 1` bars. The example computes both tests before the `if`: written inside the `else if`, `falling` would only see the bars where `rising` was false, and the compiler warns with OS8001.

**See also.** `falling()`, `change()`, `count()`

### falling()

```
falling(src: series number, len: number) -> series bool
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len`

True when `src` went down on each of the last `len` bars. Use it to spot a pullback, a fading oscillator or a weakening average.

```openscript
version 1
study("Pullback in an uptrend", overlay = true)

trendUp  = close > ema(close, 50)
pullback = falling(close, 3)

if trendUp and pullback
    signal("PULLBACK", at = "below", shape = "triangleUp")
```

**Remarks.** Strict in the same way as `rising()`: an unchanged bar ends the run. Absent on the first `len` bars, so an `if` on it simply does not run there.

**See also.** `rising()`, `change()`

## Crossings

### crossUp()

```
crossUp(a: series number, b: series number) -> series bool
```

| Parameter | Type | Default |
|---|---|---|
| a | series number | required |
| b | series number | required |

First value: bar 1

True on the bar where `a` moves above `b`: on the previous bar `a` was at or below `b`, and on this bar it is above. Use it for a moving average crossover, a price crossing a level, or an oscillator leaving an oversold zone.

```openscript
version 1
study("EMA 9 and 21 cross", overlay = true)

fast = ema(close, 9)
slow = ema(close, 21)

plot(fast, "EMA 9", aqua)
plot(slow, "EMA 21", orange)

if crossUp(fast, slow)
    signal("BUY", at = "below", shape = "arrowUp")
```

"At or below, then above" means two lines that touch and then separate count as one crossing. Here is a close crossing a fixed level of 102:

| Bar | `close` | `crossUp(close, 102)` |
|---|---|---|
| 1 | 101 | false |
| 2 | 102 | false, the close only touched the level |
| 3 | 103 | true, it was at the level and is now above |

**Remarks.** Either argument can be a fixed number: `crossUp(rsi(close, 14), 30)`. The first value is on bar 1. The result is absent when either side is absent on this bar or on the previous one, which happens during an indicator's warmup, so the first crossing a script can see comes one bar after both sides have values.

**See also.** `crossDown()`, `cross()`, `valueWhen()`

### crossDown()

```
crossDown(a: series number, b: series number) -> series bool
```

| Parameter | Type | Default |
|---|---|---|
| a | series number | required |
| b | series number | required |

First value: bar 1

True on the bar where `a` moves below `b`: on the previous bar `a` was at or above `b`, and on this bar it is below. Use it for a bearish crossover, a price losing a level, or an oscillator leaving an overbought zone.

```openscript
version 1
study("RSI leaves overbought", precision = 2)

r = rsi(close, 14)
plot(r, "RSI 14", purple)
level(70, "Overbought", red)

if crossDown(r, 70)
    signal("EXIT", at = "above", shape = "arrowDown")
```

**Remarks.** The mirror of `crossUp()`, with the same rule for touching: a value that falls to exactly `b` has not crossed yet, and crosses on the bar it goes below.

**See also.** `crossUp()`, `cross()`

### cross()

```
cross(a: series number, b: series number) -> series bool
```

| Parameter | Type | Default |
|---|---|---|
| a | series number | required |
| b | series number | required |

First value: bar 1

True when `a` crosses `b` in either direction on this bar. Use it when the direction does not matter, such as an alert whenever price crosses VWAP.

```openscript
version 1
study("Price crosses VWAP", overlay = true)

// The day's VWAP, restarted on the first bar of each IST day.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")
v = vwapAnchor(hlc3, newDay)
plot(v, "VWAP", orange)

if cross(close, v)
    signal("X", at = "price", shape = "circle")
    alert("Price crossed VWAP", id = "vwap-cross")
```

**Remarks.** `cross(a, b)` is the same as `crossUp(a, b) or crossDown(a, b)`. When you later need to know which way it went, test the two directions separately. The example anchors the average by date because `vwap()` restarts on the session's first bar, which needs session hours the /trading chart does not state in this release.

**See also.** `crossUp()`, `crossDown()`, `alert()`

## Counting and remembering

### barsSince()

```
barsSince(cond: series bool) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| cond | series bool | required |

First value: first bar `cond` is true

How many bars have passed since `cond` was last true: `0` on a bar where it is true, `1` on the bar after, and so on. Use it to act within a few bars of an event, or to measure how long a quiet spell has lasted.

```openscript
version 1
study("Bars since a wide range bar", precision = 0)

wide = (high - low) > 2 * atr(14)
plot(barsSince(wide), "Bars since a wide bar", teal, style = "column")
```

**Remarks.** The result is absent, not zero, until `cond` has been true at least once, because zero would read as "it happened on this bar". A test such as `barsSince(breakout) <= 5` is therefore absent before the first breakout, and an `if` on it does not run, which is the answer you want. After that, a bar where `cond` is absent counts as a bar where it was not true, so the count keeps rising.

**See also.** `valueWhen()`, `count()`, `highestBars()`

### valueWhen()

```
valueWhen(cond: series bool, src: T, occurrence?: number = 0) -> T
```

| Parameter | Type | Default |
|---|---|---|
| cond | series bool | required |
| src | T | required |
| occurrence | number | 0 |

First value: the `occurrence + 1` th true bar

The value `src` had on the most recent bar where `cond` was true, held until `cond` is true again. With `occurrence = 1` it reaches one event further back, to the true bar before that one. Use it to remember a price at an event: the low at the last crossover, the high of the last breakout bar.

```openscript
version 1
study("Low at the last two crossovers", overlay = true)

fast    = ema(close, 9)
slow    = ema(close, 21)
crossed = crossUp(fast, slow)

lastLow  = valueWhen(crossed, low)
priorLow = valueWhen(crossed, low, 1)

plot(lastLow, "Low at the last cross", lime, style = "step")

if crossed and lastLow > priorLow
    signal("HIGHER LOW", at = "below")
```

**Remarks.** `occurrence = 0` is the most recent true bar, `1` the one before it, and so on. The result is absent until `cond` has been true `occurrence + 1` times. An absent `cond` counts as not true. `occurrence` must be a whole number, 0 or more; a fraction or a negative number gives `none` on every bar rather than stopping the script.

> **Numbers only in this release**
The signature accepts any type for `src`, but in release 0.5.0 `valueWhen` returns a value only when `src` is a number. With a `bool` or a `string` it compiles and gives `none` on every bar. Remember a number instead, such as `1` for up and `0` for down, and compare it.

**See also.** `barsSince()`, `pivotLow()`, [Persistence](/script/language/persistence)

### count()

```
count(cond: series bool, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| cond | series bool | required |
| len | number | required |

First value: bar `len - 1`

How many of the last `len` bars `cond` was true on, this bar included, from `0` to `len`. Use it to measure how persistent a condition is: up bars in a window, closes above an average, bars with heavy volume.

```openscript
version 1
study("Up bars in the last 20", precision = 0)

upBars = count(close > open, 20)
plot(upBars, "Up bars", lime, style = "column")
level(10, "Half", gray)
```

**Remarks.** An absent condition counts as not true rather than making the result absent. So `count(close > close[1], 20)` has a value from bar 19 even though its condition is absent on bar 0. Divide by `len` and multiply by 100 for a percentage of the window.

**See also.** `barsSince()`, `sum()`, `rising()`

### history()

```
history(src: T, n: number) -> T
```

| Parameter | Type | Default |
|---|---|---|
| src | T | required |
| n | number | required |

First value: bar `n`

`src` as it stood `n` bars ago: the call form of `src[n]`. It takes an expression directly, so `history(close - open, 1)` is the previous bar's body without naming it first.

```openscript
version 1
study("Body against the previous body", precision = 2)

plot(close - open, "Body", aqua, style = "histogram")
plot(history(close - open, 1), "Previous body", orange)
```

**Remarks.** The first value is on bar `n`. Unlike the offset inside `[]`, `n` is a length: a whole number of 1 or more, so `history(close, 0)` stops the script with [OS4003](/script/errors/runtime#os4003) where `close[0]` simply reads this bar. [Bars and history](/script/language/bars-and-history) covers when to prefer a named value.

> **Numbers only in this release**
In release 0.5.0 `history` returns a value only when `src` is a number. On a `bool`, a `string` or an array it compiles and gives `none` on every bar. For a `bool` or a `string`, name the value at the top level and use the operator instead: `flag[1]` works where `history(flag, 1)` does not.

**See also.** `change()`, `valueWhen()`

## Running totals

### cum()

```
cum(src: series number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |

First value: bar 0

The running total of `src` from the first bar. Use it for a line that accumulates for the whole chart, such as a score of up bars against down bars.

```openscript
version 1
study("Up bars minus down bars", precision = 0)

score = close > open ? 1 : close < open ? -1 : 0
plot(cum(score), "Running score", aqua)
```

**Remarks.** An absent bar gives an absent result on that bar and leaves the total where it was; the next present bar carries on from there. The total never restarts. For a total that restarts every session at 09:15, keep it in a `var` and reset it on the session's first bar, as [Persistence](/script/language/persistence) shows: `session.isFirstBar` where the host states session hours, and a new IST date on the /trading chart, which does not.

**See also.** `sum()`, `sumSkip()`, `obv()`

### sum()

```
sum(src: series number, len: number) -> series number
sum(arr: array<number>) -> number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |
| arr | array<number> | required |

First value: bar `len - 1`

The total of `src` over the last `len` bars, this bar included. A second form, `sum(arr)`, totals every element of an array. The compiler picks the form from the arguments you pass.

```openscript
version 1
study("Net move over 20 bars", precision = 2)

move = close - open
plot(sum(move, 20), "Sum of bodies, 20 bars", aqua)
```

The array form reads every element once:

```openscript
version 1
study("Average of three levels", overlay = true)

levels = [22000.0, 22500.0, 23000.0]
plot(sum(levels) / size(levels), "Average level", orange)
```

**Remarks.** In the window form, one absent bar in the window makes the total absent; use `sumSkip()` to pass over absent bars. In the array form, an empty array totals `0` and an array holding an absent element totals `none`. A single series with no length matches neither form:

```openscript
plot(sum(close), "Total")
```

**See also.** `cum()`, `sumSkip()`, `count()`, `avg()`

## Totals that skip absent bars

Every other window function on this page gives `none` when any bar in its window is absent. These three pass over absent bars instead, and say so in their names. Use them for a series that is absent on some bars by design: a value that only exists on the first bar of each session, a reading taken only on up bars, or another instrument's data with gaps in it.

### sumSkip()

```
sumSkip(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The total of the present values of `src` over the last `len` bars, passing over absent bars. A window with no present values totals `0`.

```openscript
version 1
study("Up moves and down moves", precision = 2)

upMove   = close > open ? close - open : none
downMove = close < open ? open - close : none

plot(sumSkip(upMove, 20), "Up moves in 20 bars", lime)
plot(sumSkip(downMove, 20), "Down moves in 20 bars", red)
```

**Remarks.** The first value is on bar `len - 1`, even when the early bars are all absent. Because an empty window totals `0`, a zero can mean "nothing present" as well as "the values added to zero"; check `countPresent()` when the difference matters.

**See also.** `sum()`, `avgSkip()`, `countPresent()`

### avgSkip()

```
avgSkip(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The mean of the present values of `src` over the last `len` bars, passing over absent bars. It divides by the number of bars that had a value, not by `len`.

```openscript
version 1
study("Average up body and down body", precision = 2)

upBody   = close > open ? close - open : none
downBody = close < open ? open - close : none

plot(avgSkip(upBody, 50), "Average up body", lime)
plot(avgSkip(downBody, 50), "Average down body", red)
```

**Remarks.** A window with no present values has no mean, so the result is absent there. Read it beside `countPresent()`: an average of three bars out of fifty is a much weaker number than an average of forty.

**See also.** `sma()`, `sumSkip()`, `countPresent()`

### countPresent()

```
countPresent(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

How many of the last `len` bars had a value for `src`, from `0` to `len`. Use it to see how much data an `avgSkip()` or `sumSkip()` reading was built from.

```openscript
version 1
study("Opening gaps in the window", precision = 2)

window = input(250, "Window, in bars", min = 2, max = 5000)

// The session's first bar, or of the IST day where no session hours are stated.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

// A gap exists only on the first bar of each session and is absent elsewhere.
gap = newSession ? open - close[1] : none

plot(avgSkip(gap, window), "Average opening gap", orange)
plot(countPresent(gap, window), "Session opens in the window", silver)
```

**Remarks.** On a 15 minute NSE chart, 250 bars is ten sessions, so the second line reads about 10. A count well below what you expect points to missing data rather than a quiet market.

**See also.** `avgSkip()`, `sumSkip()`, `isNone()`

## Swing pivots

A pivot is a swing high or swing low: a bar that stands above, or below, a set number of bars on each side of it. Pivots anchor divergences, trendlines and supply and demand zones. The chart below shows a supply and demand study on a 15 minute NSE chart: it finds each zone with `pivotHigh()` and `pivotLow()`, five bars on each side, and draws it with `draw.box()`.


### pivotHigh()

```
pivotHigh(src: series number, left: number, right: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| left | number | required |
| right | number | required |

First value: bar `left + right`

The value of a swing high: a bar whose `src` is higher than each of the `left` bars before it and each of the `right` bars after it. The value is reported on the bar `right` bars after the pivot, which is the first bar on which the pivot is known, and the result is absent on every other bar.

```openscript
version 1
study("Swing highs and lows", overlay = true)

// Five bars on each side. Each pivot is known five bars after it forms.
ph = pivotHigh(high, 5, 5)
pl = pivotLow(low, 5, 5)

// offset draws the marker back on the pivot bar without changing the bar on
// which the value became known.
plot(ph, "Swing high", red, style = "lineWithMarkers", offset = -5)
plot(pl, "Swing low", lime, style = "lineWithMarkers", offset = -5)
```

**Remarks.** The comparison is strict on both sides, so a run of equal highs holds no pivot. The report comes `right` bars late on purpose: a value placed back on the pivot bar would be a value no script could have had at that time, and the study would look better on history than it can ever be on the latest bar. To draw at the pivot, shift the plot with `offset` set to minus `right`, or anchor a drawing object at `time[right]` and `high[right]`. A plot's `offset` must be a fixed number or a single `input()`, so write the number, as above, rather than an expression such as `-right`. A strategy that acts on a pivot acts `right` bars after it.

**See also.** `pivotLow()`, `highestBars()`, `zigzag()`

### pivotLow()

```
pivotLow(src: series number, left: number, right: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| left | number | required |
| right | number | required |

First value: bar `left + right`

The value of a swing low: a bar whose `src` is lower than each of the `left` bars before it and each of the `right` bars after it, reported `right` bars later and absent on every other bar.

```openscript
version 1
study("Last swing low as support", overlay = true)

pl      = pivotLow(low, 3, 3)
support = valueWhen(not isNone(pl), pl)

plot(support, "Last swing low", lime, style = "step")

if crossDown(close, support)
    signal("SUPPORT BROKEN", at = "above", shape = "arrowDown")
```

**Remarks.** Because the result is absent between pivots, hold the last one with `valueWhen()`, as above, or in a `var`. The line steps to each new swing low `right` bars after the low was made.

**See also.** `pivotHigh()`, `lowestBars()`, `valueWhen()`

## Window statistics

These five describe the distribution of values inside a window. Each is computed fresh over the window on every bar, and each is absent while any bar in the window is absent.

### median()

```
median(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

The middle value of `src` over the last `len` bars. With an even `len` it is the mean of the two middle values. Use it as a centre line that one extreme bar cannot drag far.

```openscript
version 1
study("Median and mean close", overlay = true)

plot(median(close, 21), "Median 21", orange)
plot(sma(close, 21), "Mean 21", aqua)
```

**Remarks.** A single outlier, such as a result day spike, moves the mean by its full size divided by `len` but barely moves the median. `median(src, len)` is exactly `percentile(src, len, 50)`.

**See also.** `percentile()`, `sma()`

### percentile()

```
percentile(src: series number, len: number, p: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |
| p | number | required |

First value: bar `len - 1`

The value below which `p` percent of the window falls, from `p = 0`, the window's lowest value, to `p = 100`, its highest. Between two values it interpolates in a straight line. Use it for a threshold that adapts to the instrument, such as "a range larger than 80 percent of recent bars".

```openscript
version 1
study("Bar range against its own history", precision = 2)

barRange = high - low
plot(barRange, "Bar range", silver, style = "column")
plot(percentile(barRange, 100, 80), "80th percentile", red)
plot(percentile(barRange, 100, 20), "20th percentile", lime)
```

**Remarks.** The window's values are sorted, and the result sits at position `p / 100 * (len - 1)` in that order. For the four values 10, 20, 30 and 40, the 25th percentile is at position 0.75, three quarters of the way from 10 to 20, which is 17.5. A `p` outside 0 to 100 gives `none`.

**See also.** `median()`, `percentRank()`

### percentRank()

```
percentRank(src: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| src | series number | required |
| len | number | required |

First value: bar `len - 1`

Where this bar's value stands in its own window, as a percentage: the share of the last `len` values, this one included, that are at or below it. `100` means nothing in the window is higher. Use it to rank today's close, range or volume against recent history on a common 0 to 100 scale.

```openscript
version 1
study("Close rank over a year", precision = 0, range = [0, 100])

plot(percentRank(close, 250), "Rank of the close", teal)
level(90, "Top tenth", red)
level(10, "Bottom tenth", lime)
```

**Remarks.** On a daily chart, 250 bars is about one year of NSE sessions. Because this bar counts itself, the lowest possible reading is `100 / len`, not 0: in a window of 4 the lowest value reads 25. Equal values count as at or below, so a close that ties the window's high reads 100.

**See also.** `percentile()`, `highest()`

### correlation()

```
correlation(a: series number, b: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| a | series number | required |
| b | series number | required |
| len | number | required |

First value: bar `len - 1`

The correlation of `a` and `b` over the last `len` bars, from `-1` (they move in opposite directions) through `0` (no linear relation) to `1` (they move together). Use it to see how closely a stock is following its index, or whether two instruments are still paired.

```openscript
version 1
study("Correlation with NIFTY", precision = 2, range = [-1, 1])

benchName     = input("NIFTY", "Benchmark symbol")
benchExchange = input("NSE_INDEX", "Benchmark exchange")
len           = input(60, "Window, in bars", min = 2, max = 500)

// "developing" reads the benchmark's bar at the same instant as the chart's
// bar. The default, "confirmed", would hand back the benchmark's previous
// bar, and the two returns below would be one bar apart.
bench = req.symbol(benchName, chart.interval, close, exchange = benchExchange, mode = "developing")

// Correlate bar to bar returns rather than prices: two prices that both
// rose over the window correlate strongly even when their bar to bar moves
// do not.
stockRet = change(close) / close[1]
benchRet = change(bench) / bench[1]

plot(correlation(stockRet, benchRet, len), "Correlation", aqua)
level(0, "Zero", gray)
```

**Remarks.** This is the population correlation: the `covariance()` divided by the product of the two population standard deviations, each of which divides by `len` rather than `len - 1`. When either series is flat across the window its spread is zero, and the result is `none`.

Pairing two instruments bar by bar needs both values from the same bar. A read at the chart's own interval in the default `"confirmed"` mode only ever holds bars that have closed, and at the open of a chart bar the other instrument's bar at the same instant has not closed yet, so the read is one bar behind the chart. `mode = "developing"` removes that lag. On the bars already on the chart it gives each bar's final value, and on the newest bar it moves with the market, exactly as the chart's own `close` does. [Higher timeframes](/script/data/higher-timeframes#the-mode) explains the modes.

Any bar the benchmark has not supplied is absent and leaves a gap until it has left the window. On a daily chart in /trading, write `"1D"` as the timeframe in place of `chart.interval`. [Other instruments](/script/data/other-instruments) covers both.

**See also.** `covariance()`, `req.symbol()`, `stdev()`

### covariance()

```
covariance(a: series number, b: series number, len: number) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| a | series number | required |
| b | series number | required |
| len | number | required |

First value: bar `len - 1`

The population covariance of `a` and `b` over the last `len` bars: the average product of their distances from their own means. Positive when they move together, negative when they move apart. Its size is in the units of `a` times the units of `b`, so read the sign more than the number; `correlation()` is the scaled version.

```openscript
version 1
study("Beta against NIFTY", precision = 2)

benchName     = input("NIFTY", "Benchmark symbol")
benchExchange = input("NSE_INDEX", "Benchmark exchange")
len           = input(120, "Window, in bars", min = 2, max = 1000)

// The benchmark's bar at the same instant as the chart's, as in the
// correlation example above.
bench = req.symbol(benchName, chart.interval, close, exchange = benchExchange, mode = "developing")

stockRet = change(close) / close[1]
benchRet = change(bench) / bench[1]

// Beta: how far the stock moved, on average, for each unit the index moved.
beta = covariance(stockRet, benchRet, len) / covariance(benchRet, benchRet, len)

plot(beta, "Beta", orange)
level(1, "Moves with the index", gray)
```

**Remarks.** The covariance of a series with itself, `covariance(x, x, len)`, is the population variance of `x`, the square of `stdev(x, len)`. Population form means it divides by `len`, not `len - 1`.

**See also.** `correlation()`, `variance()`, `stdev()`

## Related

[Bars and history](/script/language/bars-and-history), [Warmup](/script/language/warmup), [Absent values](/script/language/absent-values), [Execution model](/script/language/execution-model), [Technical analysis](/script/reference/technical-analysis), [Math](/script/reference/math), [Other instruments](/script/data/other-instruments)


## Math

Source: https://openalgo.in/script/reference/math

The functions on this page work on one number at a time: rounding a price to the instrument's tick, sizing a quantity in whole lots, taking a logarithm for returns, holding a value inside a range. They read no history and keep no state, so each has a value from bar 0 whenever its arguments do. The everyday ones are bare names, such as `abs()`, `round()` and `max()`. The ones a trading script needs less often, trigonometry among them, sit under the `math` namespace: `math.sin()`, `math.pi` and the rest.

What sets these apart is their behaviour at the edges. This page states where each function gives `none`, the absent value, instead of a number, how halves round, and how `mod()` differs from the `%` operator. Those details decide whether a stop lands on a valid price and whether a study survives one bad bar.

```openscript
version 1
study("Strike and stop levels", overlay = true, precision = 2)

strikeGap = input(50, "Strike interval", min = 0.05)
atrMult   = input(1.5, "Stop distance, in ATRs", min = 0.1, max = 10)

// The at-the-money strike: the close rounded to the nearest strike interval.
atm = roundToStep(close, strikeGap)

// A stop below the close, on a price the exchange accepts.
stopPrice = roundToTick(close - atrMult * atr(14))

// How far away the stop is, as a percentage of the close.
riskPct = (close - stopPrice) / close * 100

plot(atm, "ATM strike", orange, style = "step")
plot(stopPrice, "Stop", red, style = "step")

if bar.isLast
    print("ATM " + text(atm, 0) + ", stop " + text(stopPrice, 2) + ", risk " + text(riskPct, 1) + "%")
```

NIFTY options near the money are listed every 50 points, so the default gives the at-the-money strike; set the interval to 100 for BANKNIFTY. The stop is placed on a whole number of ticks. On a chart whose instrument has no known tick size, `roundToTick()` returns `none` and the stop line is simply not drawn.

## Rules every function here follows

| Rule | What it means for your script |
|---|---|
| There is no integer type | A length, a bar count, a quantity and a price are all `number`, so nothing needs converting |
| A number is always finite | An operation with no finite real answer gives `none`, never infinity: `1 / 0`, `sqrt(-1)`, `log(0)`, an overflow |
| Absence passes through | An absent argument gives an absent result: `max(close, none)` is `none` |
| A malformed argument stops the script | `round(x, 2.5)` and `round(x, -2)` stop the script on that bar with [OS4003](/script/errors/runtime#os4003) |
| No state and no warmup | Each call reads only this bar's arguments, so there is no run of early bars without a value: the first value is on bar 0 |

Each parameter is typed `number`, and a series number is accepted too: `abs(close - open)` is computed on every bar. A `bool` is refused, because a condition is not a number: `abs(close > open)` is error `OS3011`.

The line between the rows that give `none` and the row that stops is the line between "the answer does not exist" and "the question was malformed". A division by zero on one flat bar must not end a study that is right on fifty thousand others, so it gives `none` and the plot shows a gap. A count of 2.5 decimal places can never be right, so the script stops on that bar, and on a chart the study is marked as errored with a message naming the call, the argument and the value it received. [Runtime errors](/script/errors/runtime) shows where that message appears.

### Results at the edges

| Expression | Result | Why |
|---|---|---|
| `round(2.5)` | `3` | Halves round away from zero |
| `round(-2.5)` | `-3` | The same rule below zero |
| `floor(-2.5)` | `-3` | Toward negative infinity |
| `ceil(-2.5)` | `-2` | Toward positive infinity |
| `trunc(-2.5)` | `-2` | Toward zero |
| `mod(-7, 3)` | `2` | Takes the sign of the divisor |
| `-7 % 3` | `-1` | Takes the sign of the left operand |
| `mod(5, 0)` | `none` | No finite answer |
| `1 / 0` | `none` | No finite answer |
| `sqrt(-1)` | `none` | No real answer |
| `log(0)` | `none` | No finite answer |
| `pow(10, 400)` | `none` | Too large to be a finite number |
| `pow(-8, 1 / 3)` | `none` | No real answer |
| `exp(1000)` | `none` | Too large to be a finite number |
| `math.asin(2)` | `none` | Outside -1 to 1 |
| `max(close, none)` | `none` | Absence passes through |

> **`exp()`, `log()`, `log10()`, `math.log2()`, `pow()`, `math.hypot()` and the trigonometric functions can differ in the last binary digit from one computer to another. OpenScript does not yet fix one exact method for them, so these results carry no bit-for-bit guarantee across platforms. The difference is far below anything a chart shows. `sqrt()`, the rounding functions and every other function on this page give identical results everywhere.**

## Sign and size

### abs()

```
abs(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The size of `x` without its sign: `abs(-3)` is `3`. Use it for a candle's body regardless of colour, a distance between two prices, or the size of a move in either direction.

```openscript
version 1
study("Doji bars", overlay = true)

body     = abs(close - open)
barRange = high - low

if barRange > 0 and body <= 0.1 * barRange
    signal("DOJI", at = "above", shape = "diamond")
```

**Remarks.** An absent `x` gives `none`.

**See also.** `sign()`, `max()`

### sign()

```
sign(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

`-1` when `x` is negative, `0` when it is zero and `1` when it is positive. Use it to turn a move into a direction and count directions.

```openscript
version 1
study("Direction of the close", precision = 0)

dir = sign(change(close))
plot(dir, "Direction", silver, style = "column")
plot(sum(dir, 10), "Net direction over 10 bars", aqua)
```

**Remarks.** An unchanged close gives `0`, so it adds nothing to the net count above. An absent `x` gives `none`.

**See also.** `abs()`, `change()`, `count()`

### min()

```
min(a: number, b: number) -> number
min(arr: array<number>) -> number
```

| Parameter | Type | Default |
|---|---|---|
| a | number | required |
| b | number | required |
| arr | array<number> | required |

First value: bar 0

The smaller of `a` and `b`. A second form, `min(arr)`, gives the smallest element of an array. The compiler picks the form from the arguments you pass.

```openscript
version 1
study("Wicks", precision = 2)

upperWick = high - max(open, close)
lowerWick = min(open, close) - low

plot(upperWick, "Upper wick", red, style = "column")
plot(lowerWick, "Lower wick", lime, style = "column")
```

**Remarks.** Either argument absent gives `none`. The two value form takes exactly two arguments; for three, nest the calls: `min(min(a, b), c)`, or put the values in an array. `min([3, 9, 4])` is `3`. An empty array has no smallest element, so it gives `none`, and so does an array holding an absent element. For the smallest value of a series over several bars, use `lowest()`.

**See also.** `max()`, `clamp()`, `lowest()`

### max()

```
max(a: number, b: number) -> number
max(arr: array<number>) -> number
```

| Parameter | Type | Default |
|---|---|---|
| a | number | required |
| b | number | required |
| arr | array<number> | required |

First value: bar 0

The larger of `a` and `b`. A second form, `max(arr)`, gives the largest element of an array. Use the two value form for the top of a candle body, a floor under a computed value, or a stop that only moves one way.

```openscript
version 1
study("Stop that only rises", overlay = true)

candidate = close - 2 * atr(14)

var stop = none
if isNone(stop)
    stop = candidate
else
    stop = max(stop, candidate)

plot(stop, "Rising stop", red, style = "step")
```

**Remarks.** Either argument absent gives `none`, which is why the example seeds `stop` separately: `max(none, candidate)` would stay `none` for ever. The array form follows the same rules as `min()`: an empty array or an absent element gives `none`. For the largest value of a series over several bars, use `highest()`.

**See also.** `min()`, `highest()`, [Persistence](/script/language/persistence)

### clamp()

```
clamp(x: number, lo: number, hi: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |
| lo | number | required |
| hi | number | required |

First value: bar 0

`x` held inside the range `lo` to `hi`: `lo` when `x` is below it, `hi` when `x` is above it, and `x` itself otherwise. Use it to keep a computed quantity, a weight or a ratio within sensible bounds.

```openscript
version 1
study("Quantity for a fixed risk", precision = 0)

capital = input(500000, "Capital")
riskPct = input(1, "Risk per trade, %", min = 0.1, max = 5)
maxQty  = input(1000, "Largest quantity", min = 1)

riskPerShare = 2 * atr(14)
rawQty       = floor(capital * riskPct / 100 / riskPerShare)

plot(clamp(rawQty, 1, maxQty), "Quantity", aqua, style = "column")
```

**Remarks.** Keep `lo` at or below `hi`. Nothing checks the order, and with the two reversed the result is never `x`: it is `lo` when `x` is below `lo` and `hi` otherwise, so `clamp(5, 10, 1)` is `10` and `clamp(20, 10, 1)` is `1`. Any absent argument gives `none`.

**See also.** `min()`, `max()`, `order.qtyForRisk()`

## Rounding

Six ways to make a number whole, or a multiple of something:

| Function | Rounds | `2.5` | `-2.5` |
|---|---|---|---|
| `floor()` | Down, toward negative infinity | `2` | `-3` |
| `ceil()` | Up, toward positive infinity | `3` | `-2` |
| `trunc()` | Toward zero | `2` | `-2` |
| `round()` | To the nearest, halves away from zero | `3` | `-3` |
| `roundToStep()` | To the nearest multiple of a step | | |
| `roundToTick()` | To the nearest multiple of the instrument's tick size | | |

Round when the rounded number is the thing you mean: a quantity in whole lots, a strike, an order price. When only the display should change, format the number with `text(x, decimals)` and keep full precision in the calculation.

### floor()

```
floor(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The largest whole number at or below `x`. Use it when you must not round up past a limit, such as the number of whole lots a sum of money can buy.

```openscript
version 1
study("Lots the capital buys", precision = 0)

capital  = input(500000, "Capital")
lotUnits = orElse(chart.lotSize, 1)

// Whole lots only: floor never rounds up past what the capital covers.
lots = floor(capital / (close * lotUnits))

plot(lots, "Lots at full value", aqua, style = "column")
```

**Remarks.** Below zero, `floor` moves away from zero: `floor(-2.5)` is `-3`. `trunc()` gives `-2`. `chart.lotSize` is `none` when the host has not supplied it, which is why the example falls back to 1 unit with `orElse()`.

**See also.** `ceil()`, `trunc()`, `order.roundToLot()`

### ceil()

```
ceil(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The smallest whole number at or above `x`. Use it when a count must cover everything, such as the lots needed to hedge a holding.

```openscript
version 1
study("Lots to hedge a holding", precision = 0)

shares   = input(1200, "Shares held", min = 1)
lotUnits = input(250, "Lot size of the future", min = 1)

// Round up: the hedge must cover every share, even if the last lot is part used.
plot(ceil(shares / lotUnits), "Lots needed", orange, style = "column")
```

**Remarks.** Below zero, `ceil` moves toward zero: `ceil(-2.5)` is `-2`.

**See also.** `floor()`, `round()`

### round()

```
round(x: number) -> number
round(x: number, decimals: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |
| decimals | number | required |

First value: bar 0

`x` rounded to the nearest whole number, or with a second argument to `decimals` places after the point. A value exactly halfway rounds away from zero: `round(2.5)` is `3` and `round(-2.5)` is `-3`.

```openscript
version 1
study("RSI, rounded", precision = 1)

r = rsi(close, 14)
plot(round(r, 1), "RSI to one decimal", purple)

if bar.isLast
    print("RSI " + text(r, 1) + ", nearest whole number " + text(round(r)))
```

**Remarks.** `decimals` must be a whole number, 0 or more; a fraction or a negative number stops the script with [OS4003](/script/errors/runtime#os4003). Numbers are stored in binary, and most decimal fractions are not exact there: `1.005` is held as a value a hair below 1.005, so `round(1.005, 2)` is `1`. Rounding changes the number that later lines compute with; when you only want fewer digits on screen, use `text(x, decimals)` instead.

**See also.** `roundToStep()`, `trunc()`, `text()`

### trunc()

```
trunc(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

`x` with its fraction removed, rounding toward zero: `trunc(1.7)` is `1` and `trunc(-1.7)` is `-1`. Use it when a value should shrink toward zero on both sides alike.

```openscript
version 1
study("Whole ATRs from the average", precision = 0)

avg20    = ema(close, 20)
distance = (close - avg20) / atr(14)

// trunc treats both sides alike: 1.7 ATRs above and 1.7 below both count
// as one whole ATR.
plot(trunc(distance), "Whole ATRs", aqua, style = "column")
```

**Remarks.** For values at or above zero, `trunc` and `floor()` agree; they differ only below zero.

**See also.** `floor()`, `ceil()`

### roundToStep()

```
roundToStep(x: number, step: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |
| step | number | required |

First value: bar 0

`x` rounded to the nearest multiple of the step, with halves going away from zero. Use it for strikes, round-number levels, or a price grid of your own.

```openscript
version 1
study("Nearest round number", overlay = true)

gap = input(100, "Round to the nearest", min = 0.05)
plot(roundToStep(close, gap), "Nearest round number", orange, style = "step")
```

**Remarks.** With a step of 50, `22437` rounds to `22450` and the halfway value `22425` rounds up to `22450` as well. The step must be above zero; a step of zero or below gives `none`. To always round down to the step, write `floor(x / gap) * gap`.

**See also.** `roundToTick()`, `round()`

### roundToTick()

```
roundToTick(price: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| price | number | required |

First value: bar 0

`price` rounded to the nearest multiple of the instrument's tick size, `chart.tickSize`. Use it for any price you place an order at or draw as a level, so that it is a price the exchange accepts.

```openscript
version 1
study("Limit price below the close", overlay = true)

offsetPct  = input(0.5, "Distance below the close, %", min = 0.1, max = 5)
limitPrice = roundToTick(close * (1 - offsetPct / 100))

plot(limitPrice, "Limit price", aqua, style = "step")
```

**Remarks.** With a tick size of 0.05, `roundToTick(101.23)` is `101.25`. When the host has not supplied a tick size, the result is `none` rather than the unrounded price, because a price that looks rounded and is not would be rejected later with a less helpful message. An order given an absent price is refused with [OS7002](/script/errors/orders#os7002), which names the argument.

**See also.** `roundToStep()`, `chart.tickSize`, [Orders](/script/strategies/orders)

## Powers, roots and logarithms

### sqrt()

```
sqrt(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The square root of `x`, and `none` when `x` is below zero. Use it to scale a volatility over time, or in any formula built on a variance.

```openscript
version 1
study("Expected move from implied volatility", overlay = true)

iv   = input(14, "Implied volatility, %", min = 1, max = 200)
days = input(7, "Calendar days to expiry", min = 1, max = 365)

// One standard deviation of movement over the period, from an annual figure.
move = close * iv / 100 * sqrt(days / 365)

plot(close + move, "Upper expected", red)
plot(close - move, "Lower expected", lime)
```

**Remarks.** A variance computed by hand, such as the mean of squares less the square of the mean, can come out a hair below zero on a run of nearly equal prices. That is binary rounding, not data. Guard it with `sqrt(max(v, 0))` so one bar does not leave a gap. `sqrt` gives the same result on every platform.

**See also.** `pow()`, `stdev()`, `hv()`

### pow()

```
pow(x: number, y: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |
| y | number | required |

First value: bar 0

`x` raised to the power `y`, and `none` where the result is not a finite real number. Use it for compounding, annualising a return, or any exponent that is not a whole square.

```openscript
version 1
study("Annualised return", precision = 1)

n = input(20, "Period, in daily bars", min = 1, max = 500)

// The return over n sessions, compounded up to a year of about 250 sessions.
growth = close / close[n]
annual = (pow(growth, 250 / n) - 1) * 100

plot(annual, "Annualised return, %", aqua)
level(0, "Zero", gray)
```

**Remarks.** `pow(10, 400)` is too large and gives `none`, and so does `pow(-8, 1 / 3)`, which has no real answer. `pow(0, 0)` is `1`. There is no `^` operator, because it reads as a power to some readers and as something else to others; the compiler stops on it and suggests `pow`:

```openscript
cube = 2 ^ 3
```

**See also.** `exp()`, `sqrt()`, `log()`

### exp()

```
exp(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

`e` raised to the power `x`, the inverse of `log()`. Use it to turn a result computed on logarithms back into a price.

```openscript
version 1
study("Geometric mean of the close", overlay = true)

// The average of the logarithms, turned back into a price with exp.
geo = exp(sma(log(close), 20))

plot(geo, "Geometric mean 20", orange)
plot(sma(close, 20), "Arithmetic mean 20", aqua)
```

**Remarks.** A result too large to be finite, such as `exp(1000)`, gives `none`.

**See also.** `log()`, `pow()`, `math.e`

### log()

```
log(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The natural logarithm of `x`, and `none` when `x` is zero or below. Use it for log returns, which add up across bars, or for a log scale of price.

```openscript
version 1
study("Log return", precision = 4)

ret = log(close) - log(close[1])
plot(ret * 100, "Log return, %", aqua, style = "histogram")
plot(sum(ret, 20) * 100, "20 bar log return, %", orange)
```

**Remarks.** Log returns add: the sum of the last 20 one bar log returns equals `log(close / close[20])`, which plain percentage returns do not. `log(0)` and `log(-1)` give `none`.

**See also.** `exp()`, `log10()`, `math.log2()`

### log10()

```
log10(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The base ten logarithm of `x`, with the same rule as `log()`: `none` at zero or below. Use it to read a quantity in orders of magnitude.

```openscript
version 1
study("Turnover in powers of ten", precision = 2)

// Rupee turnover of the bar: 5 is one lakh, 7 is one crore.
turnover = close * volume
plot(log10(turnover), "Turnover, log10", teal)
level(5, "One lakh", gray)
level(7, "One crore", gray)
```

**Remarks.** `log10(1000)` is `3`. On an instrument without volume, such as an index, `volume` is absent and so is the line.

**See also.** `log()`, `math.log2()`

### math.log2()

```
math.log2(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The base two logarithm of `x`: how many times you double 1 to reach `x`. Use it to count doublings or halvings.

```openscript
version 1
study("Doublings since the first bar", precision = 2)

var firstClose = close
plot(math.log2(close / firstClose), "Doublings", aqua)
level(1, "Doubled", gray)
```

**Remarks.** `math.log2(8)` is `3`. Like `log()`, it gives `none` at zero or below.

**See also.** `log()`, `log10()`

### math.e

```
math.e: number
```

First value: bar 0

The number e, about 2.718281828, the base of the natural logarithm. It is a value, not a call: `math.e()` is error `OS2010`.

```openscript
version 1
study("Continuous growth path", overlay = true)

ratePct = input(12, "Growth per year, %", min = 0, max = 100)

var start = close
years = bar.index / 250

// Continuous compounding on a daily chart of about 250 sessions a year.
plot(start * pow(math.e, ratePct / 100 * years), "Growth path", orange)
```

**Remarks.** `pow(math.e, x)` is the same as `exp(x)`, and `exp()` is the shorter way to write it.

**See also.** `exp()`, `log()`, `math.pi`

## Remainders

### mod()

```
mod(a: number, b: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| a | number | required |
| b | number | required |

First value: bar 0

The remainder of `a` divided by `b`, taking the sign of `b`: `a - b * floor(a / b)`. Use it to wrap a value into a cycle, such as the minutes of a session into half hours or an angle into 0 to 360.

```openscript
version 1
study("Half hours from the open", overlay = true)

// Minutes since 09:15, read in the chart's timezone.
sinceOpen = date.hour(time) * 60 + date.minute(time) - (9 * 60 + 15)

// True on the bars that open at 09:15, 09:45, 10:15 and every half hour
// after, on a 1, 5 or 15 minute chart.
halfHour = mod(sinceOpen, 30) == 0
background(halfHour ? fade(silver, 90) : none)
```

`mod` and the `%` operator agree whenever `b` is positive and `a` is at or above zero. They differ when a sign is negative, and both exist on purpose:

| Expression | Value | Sign follows |
|---|---|---|
| `mod(7, 3)` | `1` | |
| `7 % 3` | `1` | |
| `mod(-7, 3)` | `2` | The divisor, `3` |
| `-7 % 3` | `-1` | The left operand, `-7` |
| `mod(7, -3)` | `-2` | The divisor, `-3` |
| `7 % -3` | `1` | The left operand, `7` |

**Remarks.** Use `mod` to wrap a value that can go below zero: `mod(angle, 360)` always lands from 0 up to 360, so `mod(-90, 360)` is `270`, where `-90 % 360` stays at `-90`. `mod(a, 0)` gives `none`, as does `a % 0`. Fractions work: `mod(7.5, 2)` is `1.5`.

**See also.** `floor()`, [Operators](/script/language/operators)

## Trigonometry

Angles are in radians: a full turn is `2 * math.pi`. Convert with `math.toRadians()` and `math.toDegrees()`. Trigonometry is rare in trading scripts; its main uses are cycles, slope angles and squashing an unbounded value into a fixed range.

### math.pi

```
math.pi: number
```

First value: bar 0

The number pi, about 3.141592654: half a turn in radians. It is a value, not a call.

```openscript
version 1
study("Reference cycle", precision = 2, range = [-1, 1])

period = input(20, "Cycle length, in bars", min = 2, max = 500)

// One full turn is 2 * pi radians, so this wave repeats every period bars.
plot(math.sin(2 * math.pi * bar.index / period), "Sine", aqua)
```

**Remarks.** `math.toDegrees(math.pi)` is `180`.

**See also.** `math.sin()`, `math.toRadians()`, `math.e`

### math.sin()

```
math.sin(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The sine of an angle `x` in radians, between -1 and 1. Use it for a smooth wave, or a weight that rises and falls over a fixed span.

```openscript
version 1
study("Mid-session weight", precision = 2, range = [0, 1])

// Minutes since 09:15, as a share of the 375 minute NSE session:
// 0 at the open and 1 at 15:30.
minutesIn = date.hour(time) * 60 + date.minute(time) - (9 * 60 + 15)
position  = clamp(minutesIn / 375, 0, 1)

// Half a sine wave across the session: 0 at both ends, 1 at midday.
plot(math.sin(math.pi * position), "Weight", aqua)
```

**Remarks.** `math.sin(math.pi / 2)` is `1`. The hour and minute are read in the chart's timezone, so on an NSE chart the clock is IST.

**See also.** `math.cos()`, `math.asin()`, `math.pi`

### math.cos()

```
math.cos(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The cosine of an angle `x` in radians, between -1 and 1. Use it to build weights shaped like an arch, or a cycle a quarter turn from a sine.

```openscript
version 1
study("Hann weighted average", overlay = true)

len = input(20, "Length", min = 3, max = 200)

// Weights shaped like one arch of a cosine: small at both ends of the window
// and largest in the middle.
total  = 0.0
weight = 0.0
for i = 0 to len - 1
    w = 0.5 - 0.5 * math.cos(2 * math.pi * (i + 1) / (len + 1))
    total  += w * close[i]
    weight += w

plot(total / weight, "Hann average", orange)
```

**Remarks.** `math.cos(0)` is `1`. The loop above reads `close[i]`, which is absent until there are `len` bars behind it, so the average has its first value on bar `len - 1`.

**See also.** `math.sin()`, `math.acos()`, `wma()`

### math.tan()

```
math.tan(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The tangent of an angle `x` in radians: the rise per unit across of a line at that angle. Use it to turn an angle into a slope.

```openscript
version 1
study("Angle to points per bar", precision = 2)

angle = input(45, "Angle, in degrees", min = -89, max = 89)

// On a chart scaled so that one ATR up matches one bar across, a line at
// this angle rises this many points per bar.
plot(math.tan(math.toRadians(angle)) * atr(14), "Points per bar", aqua)
```

**Remarks.** An angle drawn on a chart depends on how the chart is stretched, so a slope in degrees only means something once you fix the scale, as the ATR does here. `math.tan(math.pi / 4)` gives `0.9999999999999999` rather than exactly `1`, because `math.pi` is a binary number a hair away from the true pi. Round with `round()` before you compare a trigonometric result with an exact value.

**See also.** `math.atan()`, `math.toRadians()`

### math.asin()

```
math.asin(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The angle, in radians, whose sine is `x`, from `-math.pi / 2` to `math.pi / 2`. Gives `none` when `x` is outside -1 to 1. Use it to turn a ratio between -1 and 1 back into an angle.

```openscript
version 1
study("Close position as an angle", precision = 0, range = [-90, 90])

// Where the close sits in the bar's range: -1 at the low, 1 at the high.
barRange = high - low
position = barRange > 0 ? (2 * close - high - low) / barRange : none

plot(math.toDegrees(math.asin(clamp(position, -1, 1))), "Angle", aqua)
```

**Remarks.** Keep the `clamp()` even when the ratio should be in range by construction. A binary rounding error can put it a hair outside -1 to 1, and then the result is `none` for that bar.

**See also.** `math.sin()`, `math.acos()`, `clamp()`

### math.acos()

```
math.acos(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The angle, in radians, whose cosine is `x`, from `0` to `math.pi`. Gives `none` when `x` is outside -1 to 1.

```openscript
version 1
study("Angle between range and volume", precision = 0, range = [0, 180])

// A correlation is the cosine of the angle between the two windows once each
// has had its mean removed. acos turns it back into that angle:
// 0 degrees moves together, 90 unrelated, 180 opposite.
rho = correlation(high - low, volume, 50)

// clamp guards against a rounding error that lands a hair outside -1 to 1.
plot(math.toDegrees(math.acos(clamp(rho, -1, 1))), "Angle, degrees", aqua)
level(90, "Unrelated", gray)
```

**Remarks.** `math.acos(-1)` is `math.pi` and `math.acos(1)` is `0`. As with `math.asin()`, keep the `clamp()` on a ratio that should be in range by construction. The example needs an instrument with volume.

**See also.** `math.cos()`, `math.asin()`, `correlation()`

### math.atan()

```
math.atan(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

The angle, in radians, whose tangent is `x`, from `-math.pi / 2` to `math.pi / 2`. Every number has one, so it never gives `none` for a present `x`. Use it to squash a value that can grow without limit into a fixed range.

```openscript
version 1
study("Bounded z-score", precision = 2, range = [-1, 1])

z = (close - sma(close, 50)) / stdev(close, 50)

// atan maps any number into -pi / 2 to pi / 2, so this line stays inside
// -1 to 1 however far the close runs from its average.
plot(2 / math.pi * math.atan(z), "Bounded z", aqua)
level(0, "Zero", gray)
```

**Remarks.** `math.atan(1)` is `math.pi / 4`, 45 degrees. For an angle from a rise and a run, where the run can be zero or negative, use `math.atan2()`.

**See also.** `math.atan2()`, `math.tan()`, `math.toDegrees()`

### math.atan2()

```
math.atan2(y: number, x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| y | number | required |
| x | number | required |

First value: bar 0

The angle, in radians, of the point `(x, y)` seen from the origin, from `-math.pi` to `math.pi`. Note the order: `y` comes first. Unlike `math.atan(y / x)`, it tells the four quarters of the circle apart and works when `x` is zero.

```openscript
version 1
study("Phase of a 20 bar cycle", precision = 0, range = [-180, 180])

period = 20
turn   = 2 * math.pi * bar.index / period

// Read the last 20 closes as one wave, cos(turn - phase). The correlation of
// such a wave with the cosine is cos(phase), and with the sine is sin(phase),
// so atan2 of the pair gives the phase back, in the right quarter of the circle.
s = correlation(close, math.sin(turn), period)
c = correlation(close, math.cos(turn), period)

plot(math.toDegrees(math.atan2(s, c)), "Phase, degrees", aqua)
```

On a close that swings in a clean 20 bar cycle, the line holds steady at that cycle's phase: a wave that peaks 60 degrees after the reference reads 60 on every bar. A line that drifts means the cycle is a little longer or shorter than 20 bars, and one that jumps about means there is no clear 20 bar cycle to read.

| Call | Degrees |
|---|---|
| `math.atan2(1, 1)` | `45` |
| `math.atan2(1, -1)` | `135` |
| `math.atan(1 / -1)` | `-45`, the wrong quarter for the point `(-1, 1)` |

**Remarks.** Two absent arguments, or one, give `none`.

**See also.** `math.atan()`, `math.hypot()`

### math.hypot()

```
math.hypot(x: number, y: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |
| y | number | required |

First value: bar 0

`sqrt(x * x + y * y)`, the length of the line from the origin to `(x, y)`, computed without overflowing on large values. Use it to combine two readings measured on the same scale into one distance.

```openscript
version 1
study("Unusual bar, in move and volume", precision = 2)

len = input(50, "Window, in bars", min = 5, max = 500)

// Two z-scores: how many standard deviations this bar's move and this bar's
// volume sit from their own averages over the window.
move  = change(close)
zMove = (move - sma(move, len)) / stdev(move, len)
zVol  = (volume - sma(volume, len)) / stdev(volume, len)

// The straight line distance from an ordinary bar, where both are zero.
// A bar can reach 3 through either reading alone or through both together.
plot(math.hypot(zMove, zVol), "Distance from an ordinary bar", aqua)
level(3, "Unusual", red)
```

**Remarks.** `math.hypot(3, 4)` is `5`. A z-score (a distance from the average counted in standard deviations) puts a price move and a volume on one scale, which is what makes the distance meaningful; adding a raw move in rupees to a raw volume in shares would not be. Either argument absent gives `none`, so on an instrument without volume the line is not drawn.

**See also.** `math.atan2()`, `sqrt()`, `stdev()`

### math.toDegrees()

```
math.toDegrees(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

Converts an angle from radians to degrees: `math.toDegrees(math.pi)` is `180`. Use it to show an angle in the unit people read.

```openscript
version 1
study("Slope angle", precision = 1, range = [-90, 90])

len = input(20, "Length", min = 2, max = 200)

// The regression line's rise per bar, measured in ATRs so the angle does not
// depend on the instrument's price.
line  = linreg(close, len)
slope = (line - line[1]) / atr(14)

plot(math.toDegrees(math.atan(slope)), "Slope angle", aqua)
level(0, "Flat", gray)
```

**Remarks.** The conversion is `x * 180 / math.pi`, multiplied first and divided second.

**See also.** `math.toRadians()`, `math.atan()`

### math.toRadians()

```
math.toRadians(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

Converts an angle from degrees to radians: `math.toRadians(180)` is `math.pi`. Every trigonometric function takes radians, so convert an angle a reader types in degrees before you use it.

```openscript
version 1
study("Shifted cycle", precision = 2, range = [-1, 1])

period   = input(20, "Cycle length, in bars", min = 2, max = 500)
phaseDeg = input(90, "Phase shift, in degrees", min = 0, max = 360)

turn = 2 * math.pi * bar.index / period
plot(math.sin(turn), "Cycle", aqua)
plot(math.sin(turn + math.toRadians(phaseDeg)), "Shifted cycle", orange)
```

**Remarks.** The conversion is `x * math.pi / 180`, multiplied first and divided second.

**See also.** `math.toDegrees()`, `math.sin()`

## Hyperbolic functions

The three hyperbolic functions are named in the language and planned for a later release. Calling one today is error `OS2020`. Until they arrive, `exp()` builds them: `(exp(x) - exp(-x)) / 2` is the hyperbolic sine.

### math.sinh() (planned, not available yet)

```
math.sinh(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

Will return the hyperbolic sine of `x`, `(exp(x) - exp(-x)) / 2`.

### math.cosh() (planned, not available yet)

```
math.cosh(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

Will return the hyperbolic cosine of `x`, `(exp(x) + exp(-x)) / 2`.

### math.tanh() (planned, not available yet)

```
math.tanh(x: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| x | number | required |

First value: bar 0

Will return the hyperbolic tangent of `x`, which squashes any number into the range -1 to 1. For a bounded oscillator today, `math.atan()` does a similar job, as its example shows.

## No random numbers

OpenScript has no random number function, in the `math` namespace or anywhere else. A script run twice over the same bars must give the same numbers, or two backtests could not be compared and a result could not be reproduced. For the same reason the only reading of a clock during a bar is `chart.now()`, whose value the host supplies.

## Related

[Operators](/script/language/operators), [Absent values](/script/language/absent-values), [Types and values](/script/language/types-and-values), [Series functions](/script/reference/series), [General](/script/reference/general), [Position and sizing](/script/strategies/position-and-sizing)


## General

Source: https://openalgo.in/script/reference/general

Two small groups of functions that every kind of script uses. `isNone()` and `orElse()` deal with `none`, the absent value that a moving average holds during its warmup (the first bars, before it has enough data) and that `close[1]` holds on the first bar. `text()`, `toNumber()` and `toBool()` are the only ways to turn a value of one type into another, because OpenScript never converts types on its own.

Reach for this page when a plot starts later than you expected, when a running total goes blank, or when the compiler reports `OS2003` because a number met a string.

```openscript
version 1
study("RSI with a readable label", precision = 2)

r = rsi(close, 14)

// Warmup is visible: shade the bars where RSI has no value yet.
background(isNone(r) ? fade(gray, 90) : none)

plot(r, "RSI", purple)
plot(orElse(r, 50), "RSI, held at 50 during warmup", fade(silver, 50))

panel = table("RSI", 1, 1, position = "topRight")
if bar.isLast
    cell(panel, 0, 0, isNone(r) ? "RSI warming up" : "RSI " + text(r, 1))
```

## Absent values

A value is absent, `none`, when there is nothing to report: the first bars of an indicator, a read before the first bar, a division by zero, a volume the host (the application running the script) does not supply. Absence passes through arithmetic, so one absent input makes the result absent, and a plot of an absent value draws a gap. The two functions below let you test for it and replace it. See [Absent values](/script/language/absent-values) for the full rules.

### isNone()

```
isNone(x: any) -> bool
```

| Parameter | Type | Default |
|---|---|---|
| x | any | required |

First value: bar 0, whatever its arguments

True when the value is absent, and false for any present value of any type. It is the same test as `x == none`; use whichever reads better.

```openscript
version 1
study("Warmup shading", overlay = true)

slow = sma(close, 200)
background(isNone(slow) ? fade(gray, 90) : none)
plot(slow, "SMA 200", orange)
```

**Remarks.** `isNone` always answers `true` or `false`, from bar 0, whatever its argument. That is why it is safe as a guard on the left of `and`: in `not isNone(x) and x > 5`, the comparison runs only when `x` is present.

Order matters for which side runs, not for the answer. `not isNone(x) and x > 5` and `x > 5 and not isNone(x)` give the same result; `isNone(x) and x > 5` is never true however you write it.

**See also.** `orElse()`, `bar.isFirst`

### orElse()

```
orElse(x: T, fallback: T) -> T
```

| Parameter | Type | Default |
|---|---|---|
| x | T | required |
| fallback | T | required |

First value: as soon as the earlier of `x` or `fallback` is present

Returns `x` when it is present and `fallback` when it is absent. Both must be the same type: `orElse(close, "none")` is `OS3011`. Use it where a missing value should count as something definite, such as a zero in a running total, or a label to show when the host did not state a fact.

```openscript
version 1
study("Cumulative volume", format = "volume")

var total = 0.0
// Without orElse, one bar with no volume would make the total absent for good.
total += orElse(volume, 0)
plot(total, "Volume since the first bar", silver, style = "area")
```

**Remarks.** The result is present as soon as either argument is. A fallback hides warmup, which is the point in a running total and a trap in a signal: `orElse(rsi(close, 14), 50)` plots a flat 50 for the first bars, and a rule that trades on it treats those bars as real readings. Keep the absent value where a decision depends on it.

**See also.** `isNone()`, `sumSkip()`, `avgSkip()`

## Conversions

OpenScript has no implicit conversion between types. `"RSI " + 55` is `OS2003`, `1 + true` is `OS2003`, and a number is never a condition. These three functions are the conversions there are.

| Call | From | To | When the input cannot be converted |
|---|---|---|---|
| `text(x)` | Any value | `string` | Never fails. `text(none)` is the string `"none"` |
| `text(x, decimals)` | `number` | `string` | `none` when `x` is absent |
| `toNumber(s)` | `string` | `number` | `none` when the text is not a number |
| `toBool(x)` | `bool` or `none` | `bool` | Never fails. `none` becomes `false` |

### text()

```
text(x: any) -> string
text(x: number, decimals: number) -> string
```

| Parameter | Type | Default |
|---|---|---|
| x | any | required |
| decimals | number | required |

First value: bar 0

Turns a value into text, for a label, a table cell or an alert message. With one argument it takes any value; with a second, `decimals`, it writes a number with exactly that many digits after the point.

```openscript
version 1
study("Close and ATR", overlay = true)

a = atr(14)
if bar.isLast
    msg = chart.symbol + " closed at " + text(close, 2) + ", ATR " + text(a, 2)
    draw.label(time, high, msg, textColor = white)
```

| Value | `text(x)` writes |
|---|---|
| A number | The shortest digits that read back as the same number: `100`, `1234.5678`, `0.05`, `0.3333333333333333`. Exponent form only below 0.000001 or from 10 to the 21st up, such as `1e-7` or `1e21` |
| A `bool` | `"true"` or `"false"` |
| A string | The string itself |
| A colour | Hex with alpha, such as `"#ff0000ff"` for `red` |
| `none` | `"none"` |

`text(x, decimals)` rounds halves away from zero and always writes plain digits, never an exponent: `text(1234.5, 0)` is `"1235"`, `text(-2.5, 0)` is `"-3"` and `text(2.5, 2)` is `"2.50"`. `decimals` must be a whole number.

**Remarks.** The two forms treat `none` differently. `text(none)` is the four letters `"none"`, which is useful while debugging. `text(x, 2)` with an absent `x` is `none`, and so is any string it is joined to with `+`, so guard a message during warmup, as the page's first example does with `isNone()`.

A decimal that looks like a half is not always stored as one. `1.005` is held as a number a hair below it, so `text(1.005, 2)` is `"1.00"`, not `"1.01"`.

**See also.** `str.padLeft()`, `date.format()`, `cell()`

### toNumber()

```
toNumber(s: string) -> number
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |

First value: bar 0

Reads a number out of a string, and gives `none` when the string is not a number. Its most common use is a setting typed as text, such as a list of price levels in one input.

```openscript
version 1
study("Distance to the nearest level", precision = 2)

raw = input("22000, 22500, 23000", "Levels, separated by commas")

var levels: array<number> = []
if bar.isFirst
    for part in str.split(raw, ",")
        value = toNumber(part)
        if not isNone(value)
            push(levels, value)

nearest = none
for lvl in levels
    if isNone(nearest) or abs(close - lvl) < abs(close - nearest)
        nearest = lvl

plot(close - nearest, "Distance to the nearest level")
```

| Text | `toNumber` gives |
|---|---|
| `"12.5"`, `" 12.5 "` | `12.5`. Spaces at either end are ignored |
| `"-3"`, `"+3"`, `".5"`, `"12."` | `-3`, `3`, `0.5`, `12` |
| `"2.5e3"` | `2500` |
| `"12.5%"`, `"1,234"`, `"0x10"`, `"1_000"` | `none` |
| `""`, `"NaN"`, `"Infinity"` | `none` |

**Remarks.** It takes a `string`: `toNumber(close)` is `OS3011`. Always test the result with `isNone()` before you rely on it, as the example does, because a typing slip in a setting gives `none` rather than an error.

**See also.** `text()`, `str.split()`, `str.trim()`

### toBool()

```
toBool(x: any) -> bool
```

| Parameter | Type | Default |
|---|---|---|
| x | any | required |

First value: bar 0, whatever its arguments

Turns a `bool` that may be absent into a definite one: `none` becomes `false`, and `true` and `false` stay as they are. Use it where a condition computed during warmup must read as a plain `false` rather than unknown.

```openscript
version 1
study("Trend flag", overlay = true)

ema50 = ema(close, 50)
above = toBool(close > ema50)    // false, not none, while the EMA warms up

panel = table("Trend", 1, 2, position = "topRight")
if bar.isLast
    cell(panel, 0, 0, "Above the 50 EMA")
    cell(panel, 0, 1, text(above))
```

**Remarks.** The difference shows wherever `none` would otherwise travel: `not (close > ema50)` is `none` during warmup, while `not toBool(close > ema50)` is `true`. Choose deliberately, because a warmup bar that reads as `false` is a claim about the market that nobody measured.

It is not a way to read a number as a condition; there is no truthiness (no rule that treats `0` as false) in the language. In release 0.5.0 the compiler accepts a number or a string here, and the result is `false` whatever the value, so `toBool(1)` is `false`. Write the comparison you mean, such as `n != 0`. The call is spelled `toBool` because `bool` is a type name and cannot be called: `bool(x)` is `OS1019`.

**See also.** `isNone()`, `text()`

## Related

[Absent values](/script/language/absent-values), [Types and values](/script/language/types-and-values), [Types](/script/reference/types), [Operators](/script/reference/operators), [Strings](/script/reference/string).


## Strings

Source: https://openalgo.in/script/reference/string

Text is how a script talks to a person: the words in a table cell, the message an alert sends, the note on a label. In OpenScript a piece of text is a **string**, written between double quotes: `"NIFTY"`. This page documents the eighteen `str.*` functions that measure, search, cut, split, pad and change strings (sixteen available now and two planned), and the few rules every one of them follows.

Two conversions you will use beside them on almost every line live on the [General](/script/reference/general) page: `text()` turns any value into a string, and `toNumber()` reads a number back out of one.

```openscript
version 1
study("Readings panel", overlay = true)

panel = table("Readings", 4, 2, position = "topRight")

r = rsi(close, 14)
a = atr(14)

// One helper decides what a missing reading looks like, in one place.
fn show(value, decimals) => isNone(value) ? "warming up" : text(value, decimals)

// A long option symbol is cut to 14 characters so the panel stays narrow.
name = str.length(chart.symbol) > 14 ? str.substring(chart.symbol, 0, 14) + ".." : chart.symbol

// A ten character meter: one # for every 10 points of RSI.
filled = isNone(r) ? 0 : round(r / 10)
meter  = str.repeat("#", filled) + str.repeat(".", 10 - filled)

if bar.isLast
    cell(panel, 0, 0, name)
    cell(panel, 0, 1, chart.interval, align = "right")
    cell(panel, 1, 0, "RSI 14")
    cell(panel, 1, 1, show(r, 1), align = "right")
    cell(panel, 2, 0, "ATR 14")
    cell(panel, 2, 1, show(a, 2), align = "right")
    cell(panel, 3, 0, "Strength")
    cell(panel, 3, 1, meter)
```

The panel is written only on the newest bar because it shows one state, the current one. [Tables](/script/visuals/tables) explains that pattern in full. The examples on this page show their results in a table cell like this one, because a cell is something you can see on the chart.

## Rules every string function follows

**Text and numbers do not mix by themselves.** `"RSI " + 55` is error [OS2003](/script/errors/names-and-types#os2003), not the string `"RSI 55"`. Convert the number first: `"RSI " + text(55)`. Use `text(x, decimals)` when the number is for display, because it fixes the number of decimals without changing the value your calculation keeps.

```openscript
line = "RSI " + rsi(close, 14)
```

**Absent in, absent out.** A value is **absent** (the value `none`) when it does not exist yet, such as an RSI in its first bars. Every function on this page returns `none` when a string it was given is `none`, and `+` does the same. `text(x, 2)` of an absent `x` is absent too, so `"RSI " + text(r, 1)` is absent during warmup and a cell written with it stays blank. The one-argument form differs: `text(none)` is the string `"none"`. Decide what a missing value should read as, as the `show` helper above does. [Absent values](/script/language/absent-values) covers the idea in full.

**Positions count characters from 0.** The first character is at position 0. A character here is a Unicode code point, so the rupee sign counts as one: `"₹100"` has a length of 4 and the `1` is at position 1. Wherever a function takes a range, the start is included and the end is not.

**Counts and positions are whole numbers.** A position, width or count that is negative or has a fractional part stops the study at that bar with [OS4003](/script/errors/runtime#os4003). Wrap a computed count in `round()` or `floor()` before you pass it.

**Case is converted the same way everywhere.** `str.upper()` and `str.lower()` do not depend on the language settings of the machine, so a script produces the same text on every computer.

**Strings compare by code point.** `<` and `>` order two strings character by character by their Unicode number, so every capital letter sorts before every small letter: `"Z" < "a"` is true. `sort()` uses the same order for an array of strings.

**No warmup, and a ceiling.** None of these functions has a warmup: given present strings, each gives a value on bar 0. A string holds at most 100,000 characters; building a longer one stops the study at that bar with [OS5008](/script/errors/limits#os5008). The usual cause is a `var` string that grows by one line every bar. Keep the lines in an array and trim it instead, as `str.join()` shows.

## At a glance

| Function | Returns | Does |
|---|---|---|
| `str.length()` | `number` | Counts the characters |
| `str.contains()` | `bool` | Tests whether one string appears in another |
| `str.startsWith()` | `bool` | Tests the start of a string |
| `str.endsWith()` | `bool` | Tests the end of a string |
| `str.indexOf()` | `number` | Finds the first position of a part, or `-1` |
| `str.upper()` | `string` | Capital letters |
| `str.lower()` | `string` | Small letters |
| `str.trim()` | `string` | Removes whitespace from both ends |
| `str.substring()` | `string` | Cuts out a range of characters |
| `str.replace()` | `string` | Replaces the first occurrence |
| `str.replaceAll()` | `string` | Replaces every occurrence |
| `str.split()` | `array<string>` | Splits at a separator |
| `str.join()` | `string` | Joins an array with a separator |
| `str.padLeft()` | `string` | Pads on the left to a width |
| `str.padRight()` | `string` | Pads on the right to a width |
| `str.repeat()` | `string` | Repeats a string |
| `str.format()` | `string` | Planned: fills a template |
| `str.match()` | `bool` | Planned: tests a pattern |

## Inspecting a string

### str.length()

```
str.length(s: string) -> number
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |

First value: bar 0

The number of characters in `s`. Use it to check that a text input is not empty, or to keep a label short enough for the space it has, as the Readings panel above does with a long option symbol.

```openscript
note  = str.trim(input("", "Note to show"))
panel = table("Note", 1, 1, position = "bottomLeft")

// Write the cell only when the reader typed something.
if bar.isLast and str.length(note) > 0
    cell(panel, 0, 0, note)
```

**Remarks.** The rupee sign `₹` counts as one character, like any letter. The empty string `""` has a length of 0.

**See also.** `str.substring()`, `str.trim()`, `str.padLeft()`

### str.contains()

```
str.contains(s: string, part: string) -> bool
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| part | string | required |

First value: bar 0

True when `part` appears anywhere inside `s`, and false when it does not. The test is exact and case sensitive: `"NIFTY"` does not contain `"nifty"`.

```openscript
// A symbol with BANK in its name, such as BANKNIFTY or HDFCBANK, gets a wider stop.
isBank   = str.contains(chart.symbol, "BANK")
atrValue = atr(14)
plot(isBank ? atrValue * 1.5 : atrValue, "Stop distance")
```

**Remarks.** Containment can match more than you meant: `"BANKNIFTY"` contains `"NIFTY"`. When the part must be at a known end, use `str.startsWith()` or `str.endsWith()`. An empty `part` is contained in every string. To ignore case, convert both sides with `str.upper()` first.

**See also.** `str.startsWith()`, `str.endsWith()`, `str.indexOf()`

### str.startsWith()

```
str.startsWith(s: string, part: string) -> bool
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| part | string | required |

First value: bar 0

True when `s` begins with `part`. It tells `"NIFTY"` apart from `"BANKNIFTY"`, which `str.contains()` cannot.

```openscript
isNiftyContract = str.startsWith(chart.symbol, "NIFTY")
background(isNiftyContract ? fade(aqua, 94) : none)
```

**Remarks.** Case sensitive. An empty `part` is a prefix of every string.

**See also.** `str.endsWith()`, `str.contains()`, `chart.symbol`

### str.endsWith()

```
str.endsWith(s: string, part: string) -> bool
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| part | string | required |

First value: bar 0

True when `s` ends with `part`. OpenAlgo writes a futures contract as the underlying, the expiry and `FUT` (`BANKNIFTY24APR24FUT`), and an option as the underlying, the expiry, the strike and `CE` or `PE` (`NIFTY28MAR2420800CE`), so a suffix test is a quick way to tell what is on the chart.

```openscript
sym = chart.symbol
n   = str.length(sym)

// An option symbol ends in its strike and then CE or PE, as in NIFTY28MAR2420800CE.
// Asking for a digit before those two letters keeps out a stock such as RELIANCE.
strikeBefore = n >= 3 ? not isNone(toNumber(str.substring(sym, n - 3, n - 2))) : false

kind = strikeBefore and str.endsWith(sym, "CE") ? "Call option" :
       strikeBefore and str.endsWith(sym, "PE") ? "Put option" :
       str.endsWith(sym, "FUT") ? "Future" : "Stock or index"

panel = table("Contract", 1, 1)
if bar.isLast
    cell(panel, 0, 0, kind)
```

**Remarks.** A suffix alone can mislead: `RELIANCE` and `BAJFINANCE` end in `CE` as well. That is why the example checks for a digit before the suffix. Case sensitive. An empty `part` is a suffix of every string. `chart.instrumentType` reports the kind of instrument directly on a host that supplies it.

**See also.** `str.startsWith()`, `str.substring()`, `chart.instrumentType`

### str.indexOf()

```
str.indexOf(s: string, part: string) -> number
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| part | string | required |

First value: bar 0

The position of the first occurrence of `part` in `s`, counting from 0, or `-1` when it does not occur. Pair it with `str.substring()` to cut a string at a separator.

```openscript
spec  = input("NSE:SBIN", "Instrument, as EXCHANGE:SYMBOL")
colon = str.indexOf(spec, ":")

// Everything after the colon, or the whole text when there is no colon.
symbolPart = colon >= 0 ? str.substring(spec, colon + 1) : spec

panel = table("Instrument", 1, 1)
if bar.isLast
    cell(panel, 0, 0, symbolPart)
```

**Remarks.** Not found is `-1`, a definite answer rather than `none`, so compare the result with `-1` or `>= 0`. The position counts code points. An empty `part` is found at 0. To split at every separator in one call, use `str.split()`.

**See also.** `str.contains()`, `str.substring()`, `str.split()`, `indexOf()`

## Changing case and whitespace

### str.upper()

```
str.upper(s: string) -> string
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |

First value: bar 0

`s` with every letter in capitals. Use it to show a name in one consistent style, or to compare two strings without caring about case.

```openscript
watch = input("sbin", "Symbol to highlight")

// Symbols are written in capitals, so a name typed in small letters still matches.
background(str.upper(str.trim(watch)) == chart.symbol ? fade(yellow, 90) : none)
```

**Remarks.** The conversion is fixed by the language rather than by the language settings of the machine, so it gives the same result everywhere. Letters outside the English alphabet convert too: `str.upper("école")` is `"ÉCOLE"`.

**See also.** `str.lower()`, `str.trim()`

### str.lower()

```
str.lower(s: string) -> string
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |

First value: bar 0

`s` with every letter in small letters, on the same fixed terms as `str.upper()`. Lower-case the text a reader typed before you compare it, so `Buy`, `BUY` and `buy` all match.

```openscript
side  = input("Buy", "Side")
isBuy = str.lower(str.trim(side)) == "buy"
plot(isBuy ? low : high, "Reference price")
```

**Remarks.** For a choice from a fixed list, a menu input with `options` is better than free text, because the reader cannot mistype it. See `input()`.

**See also.** `str.upper()`, `str.trim()`, `input()`

### str.trim()

```
str.trim(s: string) -> string
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |

First value: bar 0

`s` with the whitespace at both ends removed: spaces, tabs, line breaks and the other Unicode whitespace characters, such as the no-break space. Whitespace inside the string stays. Trim anything a person typed before you compare it or show it.

```openscript
note  = str.trim(input("  watch 22,500  ", "Note"))
panel = table("Note", 1, 1, position = "bottomLeft")

// The brackets sit right against the text: the outer spaces are gone, the inner one stays.
if bar.isLast
    cell(panel, 0, 0, "[" + note + "]")
```

**Remarks.** The set removed is the Unicode whitespace set and nothing else, and it is the same on every machine. A zero-width space and a byte order mark are not whitespace and stay. `toNumber()` ignores the same whitespace at either end of a number.

**See also.** `str.lower()`, `toNumber()`, `str.replaceAll()`

## Cutting and replacing

### str.substring()

```
str.substring(s: string, from: number, to?: number = none) -> string
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| from | number | required |
| to | number | none |

First value: bar 0

The part of `s` from position `from` up to, but not including, position `to`. Leave out `to` to take everything from `from` to the end. Use it to read fixed fields out of a string, such as the hours and minutes of a session window.

```openscript
window     = input("0915-1530", "Trading window")
openHour   = toNumber(str.substring(window, 0, 2))
openMinute = toNumber(str.substring(window, 2, 4))

minutesIn = (date.hour(time) - openHour) * 60 + date.minute(time) - openMinute
plot(minutesIn, "Minutes since the window opened")
```

**Remarks.** A `to` past the end is cut back to the end, so `str.substring("SBIN", 0, 20)` is `"SBIN"`. A `from` at or after `to` gives the empty string. A negative or fractional position stops the study at that bar with [OS4003](/script/errors/runtime#os4003). To test whether a bar is inside a window, `session.isIn()` reads the `"0915-1530"` text for you; cut the string yourself only when you need the numbers.

**See also.** `str.indexOf()`, `str.split()`, `str.length()`

### str.replace()

```
str.replace(s: string, find: string, with: string) -> string
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| find | string | required |
| with | string | required |

First value: bar 0

`s` with the first occurrence of `find` replaced by `with`. Later occurrences are left alone. It is handy for filling one placeholder in a message template.

```openscript
template = "{symbol}: fast average crossed above slow"
if crossUp(ema(close, 9), ema(close, 21))
    alert(str.replace(template, "{symbol}", chart.symbol), id = "cross-up")
```

**Remarks.** When `find` does not occur, `s` comes back unchanged. An empty `find` matches at position 0, so `with` is added to the front. To replace every occurrence, use `str.replaceAll()`.

**See also.** `str.replaceAll()`, `str.format()`, `alert()`

### str.replaceAll()

```
str.replaceAll(s: string, find: string, with: string) -> string
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| find | string | required |
| with | string | required |

First value: bar 0

`s` with every occurrence of `find` replaced by `with`, working from left to right. Use it to clean text before you read it, such as removing the commas from a number written the Indian way.

```openscript
typed = input("1,00,000", "Capital, in rupees")

// toNumber cannot read the commas, so remove every one of them first.
capital = toNumber(str.replaceAll(typed, ",", ""))

panel = table("Capital", 1, 1)
if bar.isLast
    cell(panel, 0, 0, isNone(capital) ? "Not a number" : text(capital, 0))
```

**Remarks.** The text you insert is not searched again, so `str.replaceAll("aaa", "a", "aa")` is `"aaaaaa"` and cannot run for ever. When `find` does not occur, `s` comes back unchanged.

**See also.** `str.replace()`, `toNumber()`, `str.trim()`

## Splitting and joining

### str.split()

```
str.split(s: string, separator: string) -> array<string>
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| separator | string | required |

First value: bar 0

Splits `s` at every occurrence of `separator` and returns the pieces as an array of strings. It is the way to accept a list in a single text input, such as a set of price levels.

```openscript
version 1
study("Nearest level", overlay = true)

levelText = input("22000,22250,22500,22750", "Levels, comma separated")

parts = str.split(levelText, ",")
nearest = none
for part in parts
    value = toNumber(part)
    if not isNone(value) and (isNone(nearest) or abs(close - value) < abs(close - nearest))
        nearest = value

plot(nearest, "Nearest level", orange, style = "step")
```

**Remarks.** The separator itself is not kept. When it does not occur, the result is one element holding the whole string. Two separators side by side give an empty piece between them, so `"a,,b"` splits into three parts. An empty separator splits the string into single characters. `toNumber()` ignores spaces around each number, so `"22000, 22250"` reads as well as `"22000,22250"`, and an empty or unreadable piece becomes `none`, which the loop above skips.

**See also.** `str.join()`, `toNumber()`, `size()`, `element()`

### str.join()

```
str.join(parts: array<string>, separator: string) -> string
```

| Parameter | Type | Default |
|---|---|---|
| parts | array<string> | required |
| separator | string | required |

First value: bar 0

Joins the strings in `parts` into one string, with `separator` between each pair. It is the reverse of `str.split()`, and the tidy way to build a line from several fields.

```openscript
fields = [chart.symbol, text(open, 2), text(high, 2), text(low, 2), text(close, 2)]

panel = table("Last bar", 1, 1)
if bar.isLast
    cell(panel, 0, 0, str.join(fields, " | "))
```

**Remarks.** An empty array joins to the empty string. An absent element is written as the word `none`, so a field that is still warming up shows as that word rather than making the whole line disappear. To show a running list without growing one huge string, keep the pieces in a `var` array, trim it, and join only what you show:

```openscript
// The last five closes, oldest first.
var lines: array<string> = []
push(lines, text(close, 2))
if size(lines) > 5
    shift(lines)

panel = table("Recent closes", 1, 1)
if bar.isLast
    cell(panel, 0, 0, str.join(lines, ", "))
```

**See also.** `str.split()`, `push()`, `shift()`

## Padding and repeating

Padding makes a string a fixed number of characters long. Its most dependable use is a zero in front of a number, as in a clock time written `2:05`. Spaces only line text up in a font where every character has the same width, and table cells on the chart are drawn in a font where they do not. To line up a column of numbers in a table, give its cells `align = "right"` with `cell()` instead.

### str.padLeft()

```
str.padLeft(s: string, width: number, fill?: string = " ") -> string
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| width | number | required |
| fill | string | " " |

First value: bar 0

`s` with `fill` added on the left until it is `width` characters long. Use it to give a number a fixed count of digits, such as the minutes of a clock time.

```openscript
// Time left in the 09:15 to 15:30 session, written as h:mm.
minutesIn = (date.hour(time) - 9) * 60 + date.minute(time) - 15
left      = max(375 - minutesIn, 0)
clock     = text(floor(left / 60)) + ":" + str.padLeft(text(left % 60), 2, "0")

panel = table("Session clock", 1, 1)
if bar.isLast
    cell(panel, 0, 0, "Left in the session " + clock)
```

**Remarks.** A string already `width` characters or longer comes back unchanged; nothing is cut off. `fill` defaults to a space and may be longer than one character, in which case it repeats and is cut to fit: `str.padLeft("7", 5, "ab")` is `"abab7"`. An empty `fill` adds nothing. A negative or fractional `width` stops the study at that bar with [OS4003](/script/errors/runtime#os4003).

**See also.** `str.padRight()`, `text()`, `cell()`

### str.padRight()

```
str.padRight(s: string, width: number, fill?: string = " ") -> string
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| width | number | required |
| fill | string | " " |

First value: bar 0

`s` with `fill` added on the right until it is `width` characters long. Use it to give a text meter a fixed length, however much of it is filled.

```openscript
r = rsi(close, 14)
filled = isNone(r) ? 0 : round(r / 10)

// Ten characters every time: one # per 10 points of RSI, then dots to fill the rest.
meter = str.padRight(str.repeat("#", filled), 10, ".")

panel = table("RSI meter", 1, 1)
if bar.isLast
    cell(panel, 0, 0, meter)
```

**Remarks.** Follows the same rules as `str.padLeft()`: a longer string is not cut, and `fill` repeats to fit, so `str.padRight("ab", 5, "xy")` is `"abxyx"`.

**See also.** `str.padLeft()`, `str.repeat()`

### str.repeat()

```
str.repeat(s: string, n: number) -> string
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| n | number | required |

First value: bar 0

`n` copies of `s` joined together. Its main use is a bar drawn out of characters inside a table cell, which shows a size at a glance without a pane of its own.

```openscript
r = rsi(close, 14)
filled = isNone(r) ? 0 : round(r / 5)

panel = table("RSI bar", 1, 1)
if bar.isLast
    cell(panel, 0, 0, "RSI " + str.repeat("|", filled) + str.repeat(".", 20 - filled))
```

**Remarks.** `n` must be a whole number of 0 or more; `str.repeat("ab", 0)` is `""`. A negative or fractional `n` stops the study at that bar with [OS4003](/script/errors/runtime#os4003), so round a computed count first, as above. A very large `n` can pass the 100,000 character ceiling ([OS5008](/script/errors/limits#os5008)).

**See also.** `str.padRight()`, `round()`, `cell()`

## Planned

### str.format() (planned, not available yet)

```
str.format(template: string, values: array<string>) -> string
```

| Parameter | Type | Default |
|---|---|---|
| template | string | required |
| values | array<string> | required |

First value: bar 0

Will substitute values into a template, so `"{0} at {1}"` with two values becomes one string. Until it ships, join the pieces with `+` and `text()`, or fill named placeholders with `str.replace()` and `str.replaceAll()`.

### str.match() (planned, not available yet)

```
str.match(s: string, pattern: string) -> bool
```

| Parameter | Type | Default |
|---|---|---|
| s | string | required |
| pattern | string | required |

First value: bar 0

Will test a string against a pattern, once the language defines a pattern syntax that every engine reads the same way. Until then, `str.contains()`, `str.startsWith()` and `str.endsWith()` cover the common tests.

## Putting it together

An alert message is text a person reads on a phone, away from the chart, so it should carry the numbers that caused it. Build it from `text()` with fixed decimals, and make sure every part has a value, so a message is never lost to an absent field:

```openscript
version 1
study("Cross alert", overlay = true)

fast = ema(close, 9)
slow = ema(close, 21)

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)

if crossUp(fast, slow)
    alert(chart.symbol + " " + chart.interval
          + ": fast crossed above slow at " + text(close, 2)
          + ", gap " + text(fast - slow, 2),
          id = "cross-up", title = "EMA cross")
```

On the bar of a cross both averages have values, so every `text(x, 2)` in the message is present.

## Related

[Types and values](/script/language/types-and-values), [Absent values](/script/language/absent-values), [General](/script/reference/general), [Tables](/script/visuals/tables), [Alerts from scripts](/script/alerts/overview), [Collections](/script/reference/collections), [Inputs](/script/reference/input).


## Colors

Source: https://openalgo.in/script/reference/color

Every line, band, marker, shaded bar and table cell a script draws takes a colour. This page is the reference for the colour values the language gives you: the nineteen named colours, the functions that build a colour from its channels, the two that make a colour transparent, the one that reads transparency back, and the one that blends two colours into a scale. For the design side, such as how much transparency a fill needs and which colours read on light and dark charts, see the [Colors](/script/visuals/colors) guide.

```openscript
version 1
study("Trend colours", overlay = true)

fast     = ema(close, 9)
slow     = ema(close, 21)
atrValue = atr(14)

f = plot(fast, "Fast EMA", aqua)
s = plot(slow, "Slow EMA", orange)

// A light green band while the fast average is above the slow one, red below.
fill(f, s, colorUp = fade(lime, 85), colorDown = fade(red, 85))

// Candles grow stronger in colour the further price stretches from the slow average,
// measured in ATRs and capped at one ATR.
stretch = clamp(abs(close - slow) / atrValue, 0, 1)
tint    = close > slow ? lime : red
barColor(mix(silver, tint, stretch))
```

Three ideas are in that script: named colours (`aqua`, `orange`, `lime`, `red`, `silver`), transparency with `fade()`, and a colour that follows a value with `mix()`. During warmup `stretch` is absent, so the blend is absent too, and an absent bar colour leaves the candle its own colour.

## Writing a colour

A colour is a value of type `color`: red, green and blue channels from 0 to 255, and an **alpha**, its opacity, from 0 (invisible) to 1 (fully opaque). There are three ways to write one.

| Form | Example | Notes |
|---|---|---|
| A named colour | `aqua` | Nineteen names, all fully opaque |
| A hex literal | `#ff8800` or `#ff880080` | Six hex digits, or eight with an alpha byte |
| A function | `rgb(255, 136, 0)` | Built from channels, or from another colour |

A hex literal has exactly six or eight digits, and upper and lower case mean the same. The optional last pair is the alpha as a byte, so `80` (128) is an alpha of about 0.5. The short three-digit form is not a colour:

```openscript
plot(close, "Close", #f80)
```

Colours support `==` and `!=`: two colours are equal when all four channels match, so `orange == rgb(255, 165, 0)` is true. They support no arithmetic; `red + blue` is [OS2003](/script/errors/names-and-types#os2003). To see a colour's exact value while debugging, `text()` writes it as `#rrggbbaa`: `text(fade(aqua, 88))` is `"#00ffff1f"`.

None of the names or functions on this page has a warmup. A colour built from present values exists on bar 0, and a function given an absent argument returns an absent colour. What an absent colour does depends on where it goes: [Where colours go](#where-colours-go) below has the details.

## The nineteen named colours

Each name is a built-in value of type `color` at full opacity, written bare with no prefix. The channel values are fixed by the language, so `aqua` is the same colour on every engine that runs your script. Because they are values rather than keywords, a name cannot be assigned to:

```openscript
aqua = #00e5ff
```

| Name | Hex | Name | Hex |
|---|---|---|---|
| `aqua` | `#00ffff` | `navy` | `#000080` |
| `black` | `#000000` | `olive` | `#808000` |
| `blue` | `#0000ff` | `orange` | `#ffa500` |
| `brown` | `#a52a2a` | `pink` | `#ffc0cb` |
| `fuchsia` | `#ff00ff` | `purple` | `#800080` |
| `gray` | `#808080` | `red` | `#ff0000` |
| `green` | `#008000` | `silver` | `#c0c0c0` |
| `lime` | `#00ff00` | `teal` | `#008080` |
| `maroon` | `#800000` | `white` | `#ffffff` |
| | | `yellow` | `#ffff00` |

> **`green` is a dark, half-strength green. The bright green most charts use for a rising bar is `lime`.**

## Neutrals

### black

```
black: color
```

First value: bar 0

Pure black. Faded, it makes a table background that lets the price pane show through; opaque, it is the text colour for a light cell.

```openscript
panel = table("Last close", 1, 2, bgColor = fade(black, 25), textColor = white)

if bar.isLast
    cell(panel, 0, 0, "Close")
    cell(panel, 0, 1, text(close, 2))
```

**Remarks.** Opaque black disappears on a dark chart. Use it for backgrounds with `fade()`, or for text on a light fill.

**See also.** `white`, `gray`, `table()`

### gray

```
gray: color
```

First value: bar 0

A mid grey, halfway between black and white, and the default colour of a `level()`. It reads on both light and dark charts, so it suits reference lines that should be seen but not noticed. The name is spelled `gray`.

```openscript
plot(mom(close, 10), "Momentum", aqua)
level(0, "Zero", gray)
```

**See also.** `silver`, `level()`

### silver

```
silver: color
```

First value: bar 0

A light grey. Use it for a secondary line, a neutral state between two coloured ones, or the start of a colour scale that blends towards a stronger colour.

```openscript
plot(sma(close, 50), "SMA 50", silver)
plot(sma(close, 200), "SMA 200", fade(silver, 50))
```

**See also.** `gray`, `mix()`

### white

```
white: color
```

First value: bar 0

Pure white. The usual text colour on a dark table cell or label plate.

```openscript
panel = table("Symbol", 1, 1, bgColor = fade(navy, 20))

if bar.isLast
    cell(panel, 0, 0, chart.symbol, textColor = white)
```

**Remarks.** White text vanishes on a light chart background, so give it a dark cell or plate to sit on.

**See also.** `black`, `cell()`, `draw.label()`

## Reds, oranges and yellows

### red

```
red: color
```

First value: bar 0

Pure red. The conventional colour for a falling bar, a bearish signal and a stop level.

```openscript
stopLine = close - 2 * atr(14)
plot(stopLine, "Two ATR stop", red, style = "step")
```

**See also.** `maroon`, `lime`, `fade()`

### maroon

```
maroon: color
```

First value: bar 0

A dark red, half the strength of `red`. Use it for a second bearish shade, such as a support line that sits under a brighter stop.

```openscript
plot(lowest(low, 20)[1], "Low of the previous 20 bars", maroon, style = "step")
```

**See also.** `red`, `brown`

### brown

```
brown: color
```

First value: bar 0

A muted red-brown. It gives a long, slow line a colour of its own without competing with the brighter lines around it.

```openscript
plot(sma(close, 200), "SMA 200", brown, width = 2)
```

**See also.** `maroon`, `orange`, `olive`

### orange

```
orange: color
```

First value: bar 0

A warm orange. It stands out on light and dark charts alike and is a common choice for the slower of two averages.

```openscript
plot(ema(close, 9), "Fast EMA", aqua)
plot(ema(close, 21), "Slow EMA", orange)
```

**See also.** `yellow`, `brown`, `aqua`

### yellow

```
yellow: color
```

First value: bar 0

Pure yellow. Faded, it makes a highlight behind a bar, such as the first bar of each session at 09:15 on NSE.

```openscript
// The first bar of each IST day, which on NSE is the session's first bar.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")
background(newDay ? fade(yellow, 80) : none)
```

**Remarks.** Opaque yellow is hard to read on a light chart. Use it faded, or for markers on a dark chart. The example tests the date rather than `session.isFirstBar`, which needs session hours the /trading chart does not state in this release.

**See also.** `orange`, `background()`, `session.isFirstBar`

### pink

```
pink: color
```

First value: bar 0

A light, soft pink. It works well for a band that should be visible without dominating the candles.

```openscript
b = bollinger(close, 20, 2)
upper = plot(b[1], "Upper band", pink)
lower = plot(b[2], "Lower band", pink)
fill(upper, lower, fade(pink, 85))
```

**See also.** `fuchsia`, `fill()`

## Greens

### lime

```
lime: color
```

First value: bar 0

Pure, bright green. The conventional colour for a rising bar, a bullish signal and a breakout.

```openscript
barColor(close > high[1] ? lime : none)
```

**See also.** `green`, `red`, `barColor()`

### green

```
green: color
```

First value: bar 0

A dark green, half the strength of `lime`. Use it for a second bullish shade, or for a volume column that should not glare.

```openscript
plot(volume, "Volume", close >= open ? green : maroon, style = "column")
```

**See also.** `lime`, `teal`, `olive`

### olive

```
olive: color
```

First value: bar 0

A dark yellow-green. It suits a neutral secondary line that should stay in the background.

```openscript
plot(wma(close, 30), "WMA 30", olive)
```

**See also.** `green`, `brown`

### teal

```
teal: color
```

First value: bar 0

A dark blue-green. A calm colour for a line you read often but do not want to shout.

```openscript
plot(ema(hl2, 34), "EMA 34 of the midpoint", teal)
```

**See also.** `aqua`, `green`, `navy`

## Blues and purples

### aqua

```
aqua: color
```

First value: bar 0

Bright cyan. It reads clearly on both light and dark charts and is the most common colour for a study's main line or band.

```openscript
plot(ema(close, 20), "EMA 20", aqua, width = 2)
```

**See also.** `teal`, `blue`, `fade()`

### blue

```
blue: color
```

First value: bar 0

Pure, deep blue. A strong colour for one important line, such as the session VWAP.

```openscript
// The day's VWAP, restarted on the first bar of each IST day.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")
plot(vwapAnchor(hlc3, newDay), "VWAP", blue, width = 2)
```

**Remarks.** Pure blue is dark and can be hard to see on a dark chart; `aqua` is the brighter choice there. The example anchors the average itself because `vwap()` restarts on the session's first bar, which needs session hours the /trading chart does not state in this release; there `vwap()` has no value.

**See also.** `aqua`, `navy`, `vwapAnchor()`, `vwap()`

### navy

```
navy: color
```

First value: bar 0

A very dark blue. It reads well on a light chart and as a dark cell or label background with white text.

```openscript
plot(hlc3, "Typical price", navy)
```

**Remarks.** On a dark chart navy is close to invisible as a line. Use it for fills and backgrounds there instead.

**See also.** `blue`, `white`

### purple

```
purple: color
```

First value: bar 0

A dark purple. A traditional colour for an oscillator such as the RSI, which sits in its own pane.

```openscript
plot(rsi(close, 14), "RSI", purple)
level(70, "Overbought", red)
level(30, "Oversold", lime)
```

**See also.** `fuchsia`, `level()`

### fuchsia

```
fuchsia: color
```

First value: bar 0

Bright magenta. It is rarely used for lines, which is what makes it good for a rare event you want to catch the eye, such as a large opening gap.

```openscript
// The day's first bar, found by its IST date, and its gap from the last close.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")
gapPercent = (open - close[1]) / close[1] * 100
if newDay and abs(gapPercent) > 1
    signal("GAP", color = fuchsia)
```

**See also.** `purple`, `pink`, `signal()`

## Building a colour from channels

### rgb()

```
rgb(r: number, g: number, b: number) -> color
```

| Parameter | Type | Default |
|---|---|---|
| r | number | required |
| g | number | required |
| b | number | required |

First value: bar 0

A fully opaque colour from its red, green and blue channels, each a whole number from 0 to 255. Use it for a colour the nineteen names do not cover.

```openscript
plot(ema(close, 50), "EMA 50", rgb(255, 136, 0), width = 2)
```

**Remarks.** A channel written as a number that is outside 0 to 255, or not whole, is refused when the script compiles:

```openscript
plot(close, "Close", rgb(300, 0, 0))
```

A channel computed from data is not refused in this release: it is rounded to a whole number and held inside 0 to 255, so a red channel that works out at 300 quietly becomes 255. A later release may stop the study instead, with [OS4009](/script/errors/runtime#os4009). Do not rely on the silent limit: bound a computed channel yourself with `clamp()`, so the decision is visible in the script.

**See also.** `rgba()`, `mix()`, `clamp()`

### rgba()

```
rgba(r: number, g: number, b: number, a: number) -> color
```

| Parameter | Type | Default |
|---|---|---|
| r | number | required |
| g | number | required |
| b | number | required |
| a | number | required |

First value: bar 0

The same as `rgb()` with a fourth argument, the alpha, from 0 (invisible) to 1 (opaque). `rgba(r, g, b, a)` is the same colour as `withAlpha(rgb(r, g, b), a)`.

```openscript
plot(sma(close, 20), "SMA 20", rgba(0, 150, 255, 0.6))
```

**Remarks.** The three channels follow the rules of `rgb()`. A computed alpha below 0 or above 1 is clamped to the nearest end.

**See also.** `rgb()`, `withAlpha()`, `fade()`

### hsl() (planned, not available yet)

```
hsl(h: number, s: number, l: number) -> color
```

| Parameter | Type | Default |
|---|---|---|
| h | number | required |
| s | number | required |
| l | number | required |

First value: bar 0

Will build a colour from hue, saturation and lightness, which makes it easy to step through related colours by changing one number. Until it ships, use `rgb()` or blend two colours with `mix()`.

## Transparency

The two transparency functions count in opposite directions, and guessing wrong draws something invisible. Keep this table in mind:

| Call | Argument | 0 means | The top of the range means |
|---|---|---|---|
| `fade()` `(color, percent)` | Transparency, 0 to 100 | Unchanged | `100`: invisible |
| `withAlpha()` `(color, a)` | Alpha, 0 to 1 | Invisible | `1`: opaque |
| `rgba()` `(r, g, b, a)` | Alpha, 0 to 1 | Invisible | `1`: opaque |
| `alpha()` `(color)` | Reads the alpha, 0 to 1 | Invisible | `1`: opaque |

For an opaque colour the two are the same idea from opposite ends: `fade(aqua, 88)` and `withAlpha(aqua, 0.12)` are the same colour.

> **The settings dialog also has an **Opacity** row for every plot, on its Style tab. That row counts opacity, the same direction as `withAlpha()` in percent: 100 there is fully visible. It is the reverse of `fade()`, where 100 is invisible.**

### fade()

```
fade(color: color, percent: number) -> color
```

| Parameter | Type | Default |
|---|---|---|
| color | color | required |
| percent | number | required |

First value: bar 0

The same colour made `percent` transparent, where 0 leaves it unchanged and 100 makes it invisible. It is the call most scripts reach for when shading a fill or a background.

```openscript
b = bollinger(close, 20, 2)
upper = plot(b[1], "Upper", aqua)
lower = plot(b[2], "Lower", aqua)
fill(upper, lower, fade(aqua, 88))
```

**Remarks.** `fade` scales the alpha the colour already has: the result's alpha is `alpha(color) * (100 - percent) / 100`. On an opaque colour that is simply `(100 - percent) / 100`, but fading twice compounds, so `fade(fade(aqua, 50), 50)` has an alpha of 0.25, not 0.5. Apply `fade` once, to an opaque colour, or use `withAlpha()` to set an exact alpha.

A computed percent outside 0 to 100 is not refused. The resulting alpha is kept within 0 to 1, so a percent above 100 gives an invisible colour and a negative one makes the colour more opaque. Hold a computed percent in range with `clamp()`.

As a guide, a fill between two plots wants roughly `fade(c, 85)` to `fade(c, 95)`, and a whole-bar `background()` more than that.

**See also.** `withAlpha()`, `alpha()`, `fill()`, `background()`

### withAlpha()

```
withAlpha(color: color, a: number) -> color
```

| Parameter | Type | Default |
|---|---|---|
| color | color | required |
| a | number | required |

First value: bar 0

The same colour with its alpha set to `a`, from 0 (invisible) to 1 (opaque), whatever alpha it had before. Reach for it when you compute an opacity from data, or when a colour may already be partly transparent.

```openscript
r = rsi(close, 14)
// Readings far from 50 are drawn solidly; readings near 50 fade to 20 percent opacity.
strength = clamp(abs(r - 50) / 50, 0.2, 1)
plot(r, "RSI", withAlpha(purple, strength), width = 2)
```

**Remarks.** It sets the alpha rather than scaling it, so `withAlpha(fade(aqua, 88), 1)` is plain `aqua` again. A computed `a` outside 0 to 1 is clamped.

**See also.** `fade()`, `alpha()`, `rgba()`

### alpha()

```
alpha(color: color) -> number
```

| Parameter | Type | Default |
|---|---|---|
| color | color | required |

First value: bar 0

The alpha of a colour, from 0 (invisible) to 1 (opaque). Use it when a colour comes out of a calculation and you need to know how visible it is, for example to keep a blended line from fading away altogether.

```openscript
r = rsi(close, 14)

// Blend from a faint grey near 50 to a solid purple at the extremes.
weight = clamp(abs(r - 50) / 30, 0, 1)
shade  = mix(fade(silver, 90), purple, weight)

// A line nobody can see is no use: keep it at least 40 percent opaque.
lineColour = isNone(shade) ? none : alpha(shade) < 0.4 ? withAlpha(shade, 0.4) : shade
plot(r, "RSI", lineColour, width = 2)
```

**Remarks.** Every named colour has an alpha of 1. A hex alpha byte is divided by 255, so `alpha(#ff880080)` is about 0.502, and `alpha(fade(aqua, 88))` is 0.12. The alpha of an absent colour is absent.

**See also.** `withAlpha()`, `fade()`, `mix()`

## Blending

### mix()

```
mix(a: color, b: color, weight: number) -> color
```

| Parameter | Type | Default |
|---|---|---|
| a | color | required |
| b | color | required |
| weight | number | required |

First value: bar 0

A blend of two colours: `weight` 0 gives `a`, 1 gives `b`, and 0.5 gives the colour halfway between. Compute the weight from a value and you have a colour scale, such as candles that grow redder as volume rises.

```openscript
version 1
study("Volume heat", overlay = true)

hottest = input(3.0, "Volume multiple that counts as hot", min = 1.5, max = 10)

rv = relativeVolume(20)

// Volume at its average gives 0, volume at the hot multiple gives 1.
// The clamp keeps one enormous bar from pushing the weight past red.
weight = clamp((rv - 1) / (hottest - 1), 0, 1)

// Absent during warmup, which leaves the candles their own colour.
barColor(mix(silver, red, weight))
```

**Remarks.** All four channels are blended, alpha included. Red, green and blue are then rounded to whole numbers, halves away from zero, so `mix(red, blue, 0.3)` is `#b3004dff`. A weight outside 0 to 1 is not refused: the blend carries on past the far colour and each channel is then clamped to its range, which is rarely the colour you meant. Clamp the weight first, as above. An absent weight gives an absent colour.

**See also.** `gradient()`, `clamp()`, `barColor()`

### gradient() (planned, not available yet)

```
gradient(value: number, from: number, to: number, colorFrom: color, colorTo: color) -> color
```

| Parameter | Type | Default |
|---|---|---|
| value | number | required |
| from | number | required |
| to | number | required |
| colorFrom | color | required |
| colorTo | color | required |

First value: bar 0

Will place a value between two bounds and return the matching colour between two colours, the colour scale above in one call. Until it ships, compute the weight with `clamp()` and blend with `mix()`, as the Volume heat example shows.

## Where colours go

A constant colour and a colour computed per bar go in the same argument. Some surfaces fix their colour before the first bar and accept only a constant: a named colour, a hex literal, a colour built from literals such as `fade(red, 40)`, or a colour `input()` passed in as it is. A colour that changes from bar to bar there is [OS3003](/script/errors/arguments#os3003).

| Surface | Takes a colour per bar |
|---|---|
| `plot()` `color` | Yes |
| `fill()` `color`, `colorUp`, `colorDown` | Yes |
| `barColor()` and `background()` | Yes, the usual case |
| `cell()` `textColor` and `bgColor` | Yes, read each time the cell is written |
| `table()` `textColor` and `bgColor` | No, fixed before the first bar |
| `level()` `color` | No, fixed before the first bar |
| `signal()` `color` | No, fixed before the first bar |

**An absent colour switches the paint off.** `barColor(none)` leaves the candle its own colour and `background(none)` leaves the bar unshaded. That is how a conditional colour turns itself off, and it is not an error:

```openscript
risky = atr(14) > 2 * atr(100)
background(risky ? fade(red, 92) : none)
```

A plot is different: a bar whose colour is absent is drawn in the plot's own colour, the one on the Style tab of the settings dialog. To leave a gap in a plot, make its value absent instead.

Letting the reader choose a colour is one line: `input(aqua, "Line colour")` puts a colour swatch in the settings dialog. The swatch picks red, green and blue only; a colour chosen there keeps the alpha of the input's default, so `input(fade(aqua, 50), "Band colour")` stays half transparent whatever the reader picks. See `input()`.

## Related

[Colors guide](/script/visuals/colors), [Plots](/script/visuals/plots), [Fills](/script/visuals/fills), [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds), [Types and values](/script/language/types-and-values), [Plotting](/script/reference/plotting), [Inputs](/script/reference/input).


## Collections

Source: https://openalgo.in/script/reference/collections

An **array** is an ordered list of values held under one name, such as `[22000, 22250, 22500]`. A series already remembers the past (`close[20]` is the close twenty bars ago, with no container at all), so an array is for the jobs a series cannot do: a list you sort or trim, a set of levels that grows and shrinks as price creates and breaks them, the drawings a study will come back to, or one number per session rather than one per bar. This page documents every function that reads, changes, copies, orders and summarises an array, and the rules they all share.

```openscript
version 1
study("Opening bar range", precision = 2)

sessions = input(10, "Sessions to average", min = 2, max = 60)

// The session's first bar, or the first bar of each IST day where the host
// states no session hours, as on the /trading chart.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

// One number per session, the range of its first bar (09:15 on NSE),
// kept for the whole run and trimmed to the last few sessions.
var ranges: array<number> = []

if newSession
    push(ranges, high - low)
    if size(ranges) > sessions
        shift(ranges)

plot(avg(ranges), "Average opening bar range", aqua)
plot(newSession ? high - low : none, "This session's opening bar", orange, style = "column")
```

`newSession` is `session.isFirstBar` where the host states the instrument's session hours. The /trading chart does not in this release, so there a new IST date marks the first bar instead, which for an NSE session is the same bar. The `var` keeps the array from bar to bar, `push()` adds this session's number, `shift()` drops the oldest, and `avg()` summarises what is left. Until the first session's opening bar, the array is empty and its average is absent, so nothing is drawn.

## Rules every array follows

**One element type.** An array can grow and shrink, and every element has the same type, written in its type as `array<number>`, `array<string>` and so on. The element may be a `number`, `string`, `bool` or `color`, a drawing (`line`, `label`, `box`, `polyline`) or a `table`. A series, the handle that `plot()` or `fill()` returns, and another array cannot be elements, so `array<array<number>>` is [OS2019](/script/errors/names-and-types#os2019). Keep a grid in one flat array instead, and find a cell at `row * columns + column`. A literal that mixes types, such as `["RSI", 14]`, is [OS2013](/script/errors/names-and-types#os2013).

**An empty literal needs its type.** `[]` takes its element type from an annotation, `var hits: array<number> = []`, or from the first `push()`, `unshift()`, `insert()` or `set()` into it. With neither it is [OS2015](/script/errors/names-and-types#os2015). Write the annotation; it is the clearest documentation the next reader gets.

```openscript
var hits = []
```

**An array is a reference.** `b = a` gives two names for one array, so a change through `b` shows through `a`. `copy()` makes an independent array. For the same reason `a == b` asks whether two names hold the same array; `arrayEqual()` compares what they hold.

**Indexes run from 0 to `size - 1`.** `arr[i]` and `element(arr, i)` read element `i`. An index outside that range, or with a fractional part, stops the study at that bar with [OS4004](/script/errors/runtime#os4004), naming the index and the size. That is the opposite of `close[500]` on bar 7, which is simply absent: a past bar that never existed is a missing value, while an index outside an array the script built is a mistake in the script. On an array, `[]` always means element access, never history.

**Two lifetimes.** An array made by a plain assignment is built again on every bar, which suits scratch work such as a sorted copy. An array declared with `var` (a variable that keeps its value from one bar to the next) is made once and lasts the whole run. On the newest, still-forming bar, a `var` array's contents are restored before each update is executed, so pushing once per bar pushes once per bar, not once per tick. [Persistence](/script/language/persistence) explains the rollback.

**Limits.** An array holds at most 1,000,000 elements; one more stops the study at that bar with [OS5002](/script/errors/limits#os5002). The usual cause is a `var` array that is pushed to on every bar and never trimmed. None of the functions on this page has a warmup: each works on the array as it stands on the bar being executed.

## At a glance

| Function | Returns | Does |
|---|---|---|
| `size()` | `number` | Counts the elements |
| `element()` | the element | Reads element `i`, the same as `arr[i]` |
| `indexOf()` | `number` | Finds the first index of a value, or `-1` |
| `arrayEqual()` | `bool` | Compares two arrays element by element |
| `set()` | nothing | Writes element `i` |
| `push()` | nothing | Adds to the end |
| `pop()` | the element | Removes and returns the last element |
| `unshift()` | nothing | Adds to the front |
| `shift()` | the element | Removes and returns the first element |
| `insert()` | nothing | Adds before index `i` |
| `remove()` | the element | Removes and returns element `i` |
| `clear()` | nothing | Empties an array, or every cell of a table |
| `copy()` | `array` | An independent copy |
| `slice()` | `array` | A new array holding part of this one |
| `sort()` | nothing | Sorts in place |
| `reverse()` | nothing | Reverses in place |
| `avg()` | `number` | The mean of every element |

The functions that change an array (set, push, pop, shift, unshift, insert, remove, clear, sort, reverse) change it in place and return no new array.

## Reading

### size()

```
size(arr: array<E>) -> number
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |

First value: bar 0

The number of elements in the array. Test it before you read or remove an element from an array that may be empty, and use it to trim a window to a fixed length.

```openscript
var closes: array<number> = []
push(closes, close)
if size(closes) > 50
    shift(closes)

plot(size(closes), "Closes held")
```

**Remarks.** A new empty array has a size of 0. The last element is at `size(arr) - 1`.

**See also.** `element()`, `push()`, `shift()`

### element()

```
element(arr: array<E>, i: number) -> E
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| i | number | required |

First value: bar 0

Element `i` of the array, counting from 0. `arr[i]` is the same call written shorter. Functions with several outputs, such as `macd()`, return an array, and `element` reads one output from it.

```openscript
m = macd(close, 12, 26, 9)

plot(element(m, 0), "MACD", aqua)
plot(element(m, 1), "Signal", orange)
plot(m[2], "Histogram", gray, style = "histogram")
```

**Remarks.** An index outside `0` to `size - 1`, or with a fractional part, stops the study at that bar with [OS4004](/script/errors/runtime#os4004). On a name that holds an array, `[]` reads an element and never an earlier bar. Where a line mixes the two meanings, `element(arr, i)` says which one you mean. The array returned by a multi-output function always has the same length, and each element is absent until its own warmup is over.

**See also.** `size()`, `set()`, `indexOf()`

### indexOf()

```
indexOf(arr: array<E>, v: E) -> number
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| v | E | required |

First value: bar 0

The index of the first element equal to `v`, or `-1` when no element is. With two arrays kept side by side, it turns one into a lookup table for the other.

```openscript
names  = ["sma", "ema", "wma"]
lens   = [50, 21, 30]
choice = input("ema", "Average", options = ["sma", "ema", "wma"])

// Each average type has its own length, found by position.
slot = indexOf(names, choice)
len  = slot >= 0 ? element(lens, slot) : 20

plot(ma(close, len, choice), "Average")
```

**Remarks.** Not found is `-1`, a definite answer, so compare the result with `-1` or `>= 0`. The search uses `==`, so an absent element is found by searching for `none`, and two drawings match only when they are the same drawing. The search walks the array from the front, one element at a time.

**See also.** `element()`, `str.indexOf()`, `arrayEqual()`

### arrayEqual()

```
arrayEqual(a: array<E>, b: array<E>) -> bool
```

| Parameter | Type | Default |
|---|---|---|
| a | array<E> | required |
| b | array<E> | required |

First value: bar 0

True when two arrays have the same length and equal elements in the same order. It compares contents, where `==` on two arrays only asks whether they are the same array.

```openscript
// Three closes in a row, each higher than the one before.
lastThree = [close[2], close[1], close]
ordered   = copy(lastThree)
sort(ordered, "asc")

barColor(arrayEqual(lastThree, ordered) ? lime : none)
```

**Remarks.** Each pair of elements is compared with `==`, so an absent element equals an absent element. Two arrays of drawings are equal when they hold the same drawings in the same order.

**See also.** `copy()`, `sort()`, `indexOf()`

## Writing and growing

### set()

```
set(arr: array<E>, i: number, v: E) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| i | number | required |
| v | E | required |

First value: bar 0

Writes `v` into element `i`, replacing what was there. It changes the array in place; the size stays the same.

```openscript
// Up bars counted by weekday, Monday first.
var ups: array<number> = [0, 0, 0, 0, 0, 0, 0]

day = date.dayOfWeek(time) - 1
if close > open
    set(ups, day, element(ups, day) + 1)

plot(element(ups, day), "Up bars on this weekday so far")
```

**Remarks.** `i` must already exist: writing past the end is [OS4004](/script/errors/runtime#os4004), not a way to grow the array. Use `push()` or `insert()` to add elements.

**See also.** `element()`, `push()`, `insert()`

### push()

```
push(arr: array<E>, v: E) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| v | E | required |

First value: bar 0

Adds `v` to the end of the array. It is the usual way to collect values as they happen, one per event.

```openscript
var swingHighs: array<number> = []

ph = pivotHigh(high, 5, 5)
if not isNone(ph)
    push(swingHighs, ph)

last = size(swingHighs) > 0 ? element(swingHighs, size(swingHighs) - 1) : none
plot(last, "Latest swing high", red, style = "step")
```

**Remarks.** A `var` array that is pushed to on every bar grows for the whole run. Trim it with `shift()` once it holds as many elements as you need, or it reaches the 1,000,000 element limit on a long chart.

**See also.** `pop()`, `unshift()`, `shift()`

### pop()

```
pop(arr: array<E>) -> E
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |

First value: bar 0

Removes the last element and returns it. With `push()` it makes a stack: the most recently added value comes off first.

```openscript
var supports: array<number> = []

pl = pivotLow(low, 5, 5)
if not isNone(pl)
    push(supports, pl)

// A close below the newest support removes it, and the one before takes over.
if size(supports) > 0 and close < element(supports, size(supports) - 1)
    pop(supports)

current = size(supports) > 0 ? element(supports, size(supports) - 1) : none
plot(current, "Current support", lime, style = "step")
```

**Remarks.** On an empty array it stops the study at that bar with [OS4004](/script/errors/runtime#os4004), so guard it with `size(arr) > 0`. You may use the returned value or ignore it.

**See also.** `push()`, `shift()`, `remove()`

### unshift()

```
unshift(arr: array<E>, v: E) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| v | E | required |

First value: bar 0

Adds `v` to the front of the array, so element 0 is always the newest value.

```openscript
var recent: array<number> = []
unshift(recent, close)
if size(recent) > 5
    pop(recent)

// recent[0] is this bar's close, recent[4] the close four bars ago.
plot(size(recent) == 5 ? recent[0] - recent[4] : none, "Change over four bars")
```

**Remarks.** Every existing element moves up one index. For a window with the oldest value first, use `push()` and `shift()` instead.

**See also.** `shift()`, `push()`, `insert()`

### shift()

```
shift(arr: array<E>) -> E
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |

First value: bar 0

Removes the first element and returns it. Paired with `push()`, it keeps a rolling window: push the new value, then shift the oldest once the window is full.

```openscript
len = input(20, "Window", min = 2, max = 500)

var window: array<number> = []
push(window, close)
if size(window) > len
    shift(window)

plot(avg(window), "Mean of the window", aqua)
```

**Remarks.** On an empty array it stops the study at that bar with [OS4004](/script/errors/runtime#os4004). For a plain moving average, `sma()` is shorter and exact about its warmup; keep a window by hand when you need something the library does not compute, such as a trimmed mean.

**See also.** `push()`, `pop()`, `slice()`

### insert()

```
insert(arr: array<E>, i: number, v: E) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| i | number | required |
| v | E | required |

First value: bar 0

Adds `v` before index `i`, moving that element and everything after it up by one. Use it to keep an array in order as values arrive.

```openscript
// The ten widest bars seen so far, widest first.
var widest: array<number> = []
barRange = high - low

i = 0
while i < size(widest) and element(widest, i) >= barRange
    i += 1

if i < 10
    insert(widest, i, barRange)
    if size(widest) > 10
        pop(widest)

// Drawn once ten bars have been seen, so the line is always the tenth widest.
plot(size(widest) == 10 ? element(widest, 9) : none, "Tenth widest bar so far")
```

**Remarks.** `i` may be from 0 to `size(arr)`; inserting at `size(arr)` adds to the end, like `push()`. Anything larger is [OS4004](/script/errors/runtime#os4004).

**See also.** `remove()`, `push()`, `unshift()`

### remove()

```
remove(arr: array<E>, i: number) -> E
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| i | number | required |

First value: bar 0

Removes element `i` and returns it, moving everything after it down by one. It is how a script drops a level that has been broken or has grown too old.

```openscript
var levels: array<number> = []
var born: array<number> = []

ph = pivotHigh(high, 5, 5)
if not isNone(ph)
    push(levels, ph)
    push(born, bar.index)

// Count down, so removing element i never skips the element after it.
for i = size(levels) - 1 to 0 step -1
    if close > element(levels, i) or bar.index - element(born, i) > 100
        remove(levels, i)
        remove(born, i)

plot(size(levels), "Unbroken swing highs")
```

**Remarks.** An index outside `0` to `size - 1` is [OS4004](/script/errors/runtime#os4004). When a loop removes elements, count down with `step -1`: removing element `i` renumbers every element after it, and a loop counting up would skip one. Keep side-by-side arrays in step by removing the same index from each, as above.

**See also.** `insert()`, `pop()`, `shift()`

### clear()

```
clear(arr: array<E>) -> nothing
clear(t: table) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| t | table | required |

First value: bar 0

Empties an array, leaving it with a size of 0. Called with a `table()` instead, it empties every cell of the table, so the panel can be rebuilt from scratch.

```openscript
// The closes of the current session only, from its first bar. newSession is
// explained under the first example on this page.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))
var sessionCloses: array<number> = []
if newSession
    clear(sessionCloses)
push(sessionCloses, close)

plot(avg(sessionCloses), "Mean close of the session so far", orange)
```

**Remarks.** The array itself survives, so every name that refers to it sees it empty. The table form is covered with an example in [Tables](/script/visuals/tables).

**See also.** `size()`, `table()`, `cell()`

## Copying, slicing and ordering

### copy()

```
copy(arr: array<E>) -> array<E>
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |

First value: bar 0

A new array with the same elements, independent of the original: changing one does not change the other. Copy before you sort, so the original keeps its order.

```openscript
len = input(21, "Window", min = 3, max = 500)

var window: array<number> = []
push(window, close)
if size(window) > len
    shift(window)

// sort works in place, so sort a copy and leave the window in bar order.
ranked = copy(window)
sort(ranked, "asc")
middle = ranked[floor(size(ranked) / 2)]

plot(size(window) == len ? middle : none, "Median of the window", orange)
```

**Remarks.** The copy is a new array, but the elements themselves are not duplicated. For numbers, strings, colours and bools that makes no difference. A copy of an array of drawings holds the same drawings, not new ones, so moving a line through the copy moves the line on the chart. For a median over bars, `median()` does this in one call with an exact warmup.

**See also.** `slice()`, `sort()`, `arrayEqual()`

### slice()

```
slice(arr: array<E>, from: number, to: number) -> array<E>
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| from | number | required |
| to | number | required |

First value: bar 0

A new array holding the elements from index `from` up to, but not including, index `to`. The original is not changed.

```openscript
version 1
study("Trimmed mean", overlay = true)

len  = input(20, "Window", min = 5, max = 200)
trim = input(2, "Values dropped from each end", min = 0, max = 10)

var window: array<number> = []
push(window, close)
if size(window) > len
    shift(window)

// The mean without the most extreme closes at either end.
ranked = copy(window)
sort(ranked, "asc")
ready = size(window) == len and trim * 2 < len
plot(ready ? avg(slice(ranked, trim, len - trim)) : none, "Trimmed mean", aqua, width = 2)
```

**Remarks.** A `to` past the end is cut back to the end, and a `from` at or after `to` gives an empty array. Negative or fractional bounds stop the study at that bar with [OS4003](/script/errors/runtime#os4003).

**See also.** `copy()`, `sort()`, `str.substring()`

### sort()

```
sort(arr: array<E>, order: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |
| order | string | required (one of "asc", "desc") |

First value: bar 0

Sorts the array in place, `"asc"` for smallest first or `"desc"` for largest first. Numbers sort by value, strings by Unicode code point (every capital letter before every small letter), and `false` before `true`.

```openscript
names = ["TCS", "INFY", "HDFCBANK", "RELIANCE"]
sort(names, "asc")

// Shows HDFCBANK, INFY, RELIANCE, TCS
panel = table("Watchlist", 1, 1)
if bar.isLast
    cell(panel, 0, 0, str.join(names, ", "))
```

**Remarks.** It returns nothing and changes the array you pass, so sort a `copy()` when the original order still matters. Absent elements go after every present value when sorting `"asc"`, and so come first with `"desc"`. `order` is written as a literal, or taken from a menu `input()`; any other literal is [OS3008](/script/errors/arguments#os3008).

**See also.** `reverse()`, `copy()`, `median()`, `percentile()`

### reverse()

```
reverse(arr: array<E>) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<E> | required |

First value: bar 0

Reverses the order of the elements in place, so the first becomes the last.

```openscript
var swings: array<string> = []

ph = pivotHigh(high, 3, 3)
if not isNone(ph)
    push(swings, text(ph, 2))
    if size(swings) > 3
        shift(swings)

panel = table("Swing highs", 1, 1)
if bar.isLast
    newestFirst = copy(swings)
    reverse(newestFirst)
    cell(panel, 0, 0, "Newest first: " + str.join(newestFirst, ", "))
```

**Remarks.** Like `sort()`, it returns nothing and changes the array it is given.

**See also.** `sort()`, `copy()`, `unshift()`

## Statistics

### avg()

```
avg(arr: array<number>) -> number
```

| Parameter | Type | Default |
|---|---|---|
| arr | array<number> | required |

First value: bar 0

The mean of every element in an array of numbers. Use it when the values you are averaging are not one per bar, such as one reading per session, or when you keep a window by hand.

```openscript
// The session's first bar, or of the IST day where no session hours are stated.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))
var gaps: array<number> = []
if newSession and not bar.isFirst
    push(gaps, abs(open - close[1]))
    if size(gaps) > 20
        shift(gaps)

plot(avg(gaps), "Average opening gap, last 20 sessions", aqua)
```

**Remarks.** An empty array has no mean, so `avg` of it is absent, and one absent element makes the whole mean absent. Four more summaries have an array form beside their usual one, and the compiler picks the form from the argument: `sum()`, `min()`, `max()` and `stdev()` (population standard deviation). `sum` of an empty array is 0, while `min`, `max` and `stdev` of one are absent. For a mean over the last `len` bars of a series, `sma()` is the direct call.

**See also.** `sum()`, `min()`, `max()`, `stdev()`, `sma()`

## Looping over an array

`for value in arr` visits the elements in index order, from 0 to the size measured when the loop starts. Elements added during the loop are not visited, and if the array shrinks past the loop's position, the loop stops. To walk by index, write `for i = 0 to size(arr) - 1`; to remove as you go, count down with `step -1`, as `remove()` shows.

```openscript
levels = [22000.0, 22250.0, 22500.0]

passed = 0
for mark in levels
    if close > mark
        passed += 1

plot(passed, "Levels below the close")
```

Every loop iteration on a bar counts against a budget of 2,000,000 per bar. A script that passes it stops the study at that bar with [OS5001](/script/errors/limits#os5001); one that genuinely needs more raises the budget with a `limits(loops = ...)` line straight after its declaration. See [Control flow](/script/language/control-flow) for every loop form.

## Maps and matrices

The array is the only collection in this release. `map` and `matrix` are reserved words, kept for key-value maps and two-dimensional numeric grids in a later language version, so neither can be used as a name today ([OS1019](/script/errors/syntax#os1019)). Until they arrive, two arrays kept side by side with `indexOf()` do the work of a small map, and one flat array indexed as `row * columns + column` does the work of a grid.

## Related

[Collections guide](/script/language/collections), [Persistence](/script/language/persistence), [Types](/script/reference/types), [Control flow](/script/language/control-flow), [Strings](/script/reference/string), [Series functions](/script/reference/series), [Lines and boxes](/script/visuals/lines-and-boxes).


## Inputs

Source: https://openalgo.in/script/reference/input

An input is a value the person using a study can change without opening the script: a length, a multiplier, which price to read, which average to use, a colour, a higher timeframe, an anchor date. Every call to `input()` does three things at once. It gives the script a value to read, it builds one row of the study's settings dialog, and it names the slot the reader's choice is saved under, so the choice comes back when the chart is opened again.

There is one function, and the kind of row it builds follows the type of the default you give it. A `kind` argument picks the two kinds a type alone cannot, and an `options` list turns a text row into a menu.

```openscript
version 1
study("Configurable average", overlay = true)

src    = input(close, "Source")
len    = input(20, "Length", min = 2, max = 500)
maType = input("ema", "Average type",
               options = ["sma", "ema", "wma", "rma", "hma", "vwma"])

bySlope   = input(true, "Colour by slope")
upColor   = input(lime, "Rising")
downColor = input(red, "Falling")

m = ma(src, len, maType)

plot(m, "Average", bySlope and m < m[1] ? downColor : upColor, width = 2)
```

Six settings, and a study that works on any instrument and any interval without anyone editing it. Nothing in the script branches on `maType`: `ma()` takes the type name directly, which is why the menu's options are the type names.

## The settings dialog in /trading

On the /trading page you open a study's settings from its row in the chart legend. The dialog has two tabs:

- **Inputs** holds one row per `input()` call, in the order the script declares them.
- **Style** holds rows the chart adds for every plot without the script declaring anything: its colour, opacity, thickness, line style and plot style.

**Defaults** at the bottom puts every row back to the script's defaults, and **Ok** applies the changes.


## The kinds at a glance

| Kind | Written as | Row in the settings dialog | The script gets |
|---|---|---|---|
| Number | `input(14, "Length")` | A number field with up and down arrows | `number` |
| Switch | `input(true, "Show the band")` | A tick box | `bool` |
| Text | `input("", "Note")` | A text field | `string` |
| Menu | `input("ema", "Type", options = [...])` | A drop-down of the listed values | `string` |
| Colour | `input(aqua, "Band colour")` | A colour swatch | `color` |
| Source | `input(close, "Source")` | A drop-down of the price series | a series, one value per bar |
| Timeframe | `input("1D", "Bias timeframe", kind = "interval")` | A drop-down of intervals | `string` |
| Time | `input("2025-01-01", "Anchor", kind = "time")` | A text field for a date and time | `number`, a timestamp |
| Symbol, planned | `kind = "symbol"` | An instrument picker | `string` |
| Price, planned | `kind = "price"` | A price set by clicking the chart | `number` |
| Session, planned | `kind = "session"` | Two clock fields | `string` |

## The function

### input()

```
input(value: any, title?: string, min?: number, max?: number, step?: number, options?: array<string>, kind?: string, group?: string = "", tooltip?: string = "", inline?: string, confirm?: bool) -> any
```

| Parameter | Type | Default |
|---|---|---|
| value | any | required |
| title | string | optional |
| min | number | optional |
| max | number | optional |
| step | number | optional |
| options | array<string> | optional |
| kind | string | optional (one of "interval", "time", "symbol", "price", "session") |
| group | string | "" |
| tooltip | string | "" |
| inline | string | optional |
| confirm | bool | optional |

First value: bar 0

Declares one setting and returns its value. The first argument is the default, and its type decides the kind of row; the second is the row's label. The value is known before the first bar and is the same on every bar, so you can use it anywhere a plain value of its type fits.

```openscript
len  = input(20, "Length", min = 2, max = 500)
mult = input(2.0, "Band width, in standard deviations", min = 0.5, max = 5, step = 0.1)

basis = sma(close, len)
dev   = mult * stdev(close, len)

plot(basis, "Basis", orange)
plot(basis + dev, "Upper", aqua)
plot(basis - dev, "Lower", aqua)
```

**Remarks.** The settings dialog is built once, before the first bar, from the `input()` calls the compiler can see. Every rule about `input()` follows from that:

- It is written at the top level of the file, outside any block: as an assignment, inside a larger expression, as a declaration option such as `study("B", precision = input(2, "Decimals"))`, or inside the expression of a `req.timeframe()` read. Inside an `if`, a loop or a function it is [OS3007](/script/errors/arguments#os3007).
- The default is fixed before any data arrives: a literal, arithmetic over literals, a colour built from literals such as `fade(aqua, 50)`, or, for a source, one of the price series. A default computed from bar data is [OS3003](/script/errors/arguments#os3003).
- The title is written as a string literal on the line. When the input is assigned to a name, the title can be left out and defaults to that name.
- An input has no warmup: its value exists on bar 0.

**See also.** `ma()`, `req.timeframe()`, `table()`, [Declarations](/script/reference/declarations)

## Kinds of input

### Number

A number default builds a number field with up and down arrows. It is the input for a length, a multiplier, a threshold or a quantity. `min` and `max` bound the value, and `step` sets how far one click of an arrow moves it.

```openscript
atrLen   = input(14, "ATR length", min = 1, max = 200, step = 1)
stopMult = input(2.0, "Stop, in ATR", min = 0.5, max = 10, step = 0.1)

plot(close - stopMult * atr(atrLen), "Long stop", red, style = "step")
```

Set `min` on every length. The arrows stop at `min` and `max`, but a reader can still type a number past them, and the dialog saves it as typed. The study then refuses to load, with [OS6019](/script/errors/data#os6019) naming the setting and the bound, before it can draw a wrong line. Without the bound, a length of 0 reaches the indicator and stops the study at run time with [OS4003](/script/errors/runtime#os4003) instead.

`step` shapes the arrows only. A reader who types 2.35 into a field with `step = 0.1` keeps 2.35.

### Switch

A `true` or `false` default builds a tick box. Use it for the optional part of a study, such as a band, a fill or recoloured candles.

```openscript
showBands = input(true, "Show the bands")

b = bollinger(close, 20, 2)
plot(showBands ? b[1] : none, "Upper", aqua)
plot(showBands ? b[2] : none, "Lower", aqua)
```

A switch gates what is drawn, not what is computed. `plot()` cannot sit inside an `if` ([OS3006](/script/errors/arguments#os3006)), so a switch hides a plot by passing `none`, which draws nothing on that bar. Keep indicator calls such as `bollinger` at the top level, outside any branch, so they see every bar; the compiler warns when one sits in a branch.

### Text

A string default with no `options` and no `kind` builds a text field: for a label, a note, or a value the script reads itself, such as a list of price levels.

```openscript
note  = input("Watch the 22,500 level", "Note")
panel = table("Note", 1, 1, position = "bottomLeft")

if bar.isLast
    cell(panel, 0, 0, str.trim(note))
```

Text is read exactly as it is typed, so trim it with `str.trim()` and turn numbers in it into numbers with `toNumber()`. When the value must be one of a few fixed words, use a menu instead: the reader cannot mistype it.

### Menu

Add `options`, a list of strings, and the same string default builds a drop-down over exactly those values. The default must be one of them.

```openscript
corner = input("topRight", "Panel corner",
               options = ["topLeft", "topRight", "bottomLeft", "bottomRight"])

panel = table("Last close", 1, 2, position = corner)

if bar.isLast
    cell(panel, 0, 0, chart.symbol)
    cell(panel, 0, 1, text(close, 2), align = "right")
```

A default outside the list is caught when the script compiles, because the drop-down would open with nothing selected:

```openscript
maType = input("ema", "Average type", options = ["sma", "wma"])
```

The options must be strings: a list of numbers is [OS3011](/script/errors/arguments#os3011). A saved value that is not in the list, for example after you remove an option from the script, stops the study from loading with [OS6019](/script/errors/data#os6019). Menus pair naturally with arguments that accept a fixed set of names, such as the `type` of `ma()` and the `position` of `table()`.

### Colour

A colour default builds a colour swatch. The Style tab already gives every plot its own colour row, so declare a colour input when one choice should drive several things at once, or when the script computes with the colour.

```openscript
tint = input(aqua, "Band colour")

b = bollinger(close, 20, 2)
upper = plot(b[1], "Upper", tint)
lower = plot(b[2], "Lower", tint)
fill(upper, lower, fade(tint, 90))
```

Pass the input straight to a plot, as above, and that plot's colour row on the Style tab and your row on the Inputs tab become one setting: change either and both follow. The default may be a named colour, a hex literal or a colour built from literals.

The swatch picks red, green and blue only; it has no opacity control. A colour chosen there keeps the opacity of the default, so `input(fade(aqua, 50), "Band colour")` stays half transparent whatever colour the reader picks. Apply any other transparency in the script, as `fade(tint, 90)` does for the fill. See [Colors](/script/reference/color).

### Source

A price series as the default builds a drop-down of the series the study can read. In /trading it lists the seven price series: `open`, `high`, `low`, `close`, `hl2`, `hlc3` and `ohlc4`. The script gets a series, one value per bar, and uses it exactly like `close`.

```openscript
src = input(hlc3, "Source")
plot(ema(src, 20), "EMA 20", aqua)
```

Use one of those seven as the default. `volume` is accepted as a source too, but the /trading drop-down does not list it. Other bar values, such as `oi`, `hlcc4` or `time`, compile as a default, but the study then refuses to load with [OS6019](/script/errors/data#os6019).

### Timeframe

`kind = "interval"` with a string default builds a drop-down of intervals. The script gets a timeframe string, ready for `req.timeframe()`.

```openscript
version 1
study("Higher timeframe bias", overlay = true)

biasTf  = input("1D", "Bias timeframe", kind = "interval")
biasLen = input(20, "Bias length", min = 2, max = 200)

bias = req.timeframe(biasTf, ema(close, biasLen))

plot(bias, "Higher timeframe EMA", orange, width = 2, style = "step")
barColor(isNone(bias) ? none : close > bias ? fade(lime, 40) : fade(red, 40))
```

A timeframe is a count and a unit: `"5m"`, `"1h"`, `"1D"`, `"1W"`, `"1M"`. The units are case sensitive, so `"1M"` is a month and `"1m"` a minute, and a bare number counts minutes, so `"60"` and `"1h"` are the same. A timeframe finer than the chart's own stops the study from loading with [OS6002](/script/errors/data#os6002), because bars that were never loaded cannot be invented. [Higher timeframes](/script/data/higher-timeframes) covers the read itself.

In /trading the drop-down lists **Chart interval** first, then the intervals your broker serves (for example `1m`, `5m`, `15m`, `1h` and `D`), and it keeps the study's current value in the list even when the broker does not name it.

> **Two of those entries are not timeframes the language reads in this release. **Chart interval** is an empty value, and `D` is the broker's spelling of a day, which the language writes `"1D"`. Choosing either stops the study from loading with [OS6001](/script/errors/data#os6001). Pick a minute or hour entry, or keep a day as the script's default, `"1D"`.**

### Time

`kind = "time"` with a date string builds a text field for a date and time; in /trading the empty field shows the pattern `YYYY-MM-DD HH:MM`. Write the default as `"YYYY-MM-DD HH:MM"`, or as `"YYYY-MM-DD"` for midnight at the start of that day. The script gets a **timestamp**, the same kind of number as `time` (milliseconds since 1 January 1970, UTC), so the two compare directly.

```openscript
version 1
study("Anchored VWAP from a date", overlay = true)

anchorTime = input("2025-01-01", "Anchor date", kind = "time")

// True on the first bar at or after the anchor, and on no other bar.
isAnchor = time >= anchorTime and orElse(time[1], 0) < anchorTime

anchored = vwapAnchor(hlc3, isAnchor)
plot(time >= anchorTime ? anchored : none, "Anchored VWAP", orange, width = 2)
```

This is the one kind whose saved value and returned value differ. The saved value is the text, a clock reading, so a layout saved in one timezone opens at the same clock time in another. The returned value is the timestamp a script needs for comparing with `time`. Text that is not a date stops the study from loading with [OS6019](/script/errors/data#os6019).

Which timezone the text is read in is up to the host. The language intends the chart's own timezone, but the /trading page in this release reads it as UTC: `"2025-01-01 09:15"` there means 09:15 UTC, which is 14:45 in India. That is why the example gives a date alone. Midnight UTC is 05:30 in India, before the 09:15 open, so the anchor lands on the first bar of that day either way.

### Planned kinds

Three more kinds are named in the language and not available in this release. Writing one is [OS2001](/script/errors/names-and-types#os2001), which says the kind is not defined:

```openscript
window = input("0915-1530", "Trading window", kind = "session")
```

| Kind | Will build | Until then |
|---|---|---|
| `kind = "symbol"` | An instrument picker | A text input holding the symbol, passed to `req.symbol()` |
| `kind = "price"` | A price the reader sets by clicking the chart | A number input |
| `kind = "session"` | Two clock fields | A text input holding a window such as `"0915-1530"`, passed to `session.isIn()` |

```openscript
window = input("0915-1530", "Trading window")
background(session.isIn(window) ? none : fade(gray, 90))
```

## Arguments

| Argument | Kinds | Default | Means |
|---|---|---|---|
| first, the default | every kind | required | The starting value; its type decides the kind |
| `title` | every kind | the name assigned | The row's label, and the second positional argument |
| `min`, `max` | number | none | The range a saved value must fall in |
| `step` | number | none | How far one click of an arrow moves |
| `options` | text | none | The list of values, all strings; supplying it makes a menu |
| `kind` | text | none | `"interval"` or `"time"`; `"symbol"`, `"price"` and `"session"` are planned |
| `group` | every kind | `""` | A heading to group rows under |
| `tooltip` | every kind | `""` | Help text for the row |
| `inline` | every kind | none | Planned: rows sharing a value sit on one line |
| `confirm` | every kind | none | Planned: ask for the value when the study is added |

`inline` and `confirm` are accepted by the compiler in this release and have no effect yet.

`title`, `group`, `tooltip`, `options` and `kind` are written on the line itself, as literals (joining two literals with `+` is fine). The same text held in a name first is [OS3003](/script/errors/arguments#os3003), because the dialog is built before any line of the script runs.

`group` and `tooltip` travel with the input for any host that shows them. The /trading dialog in this release lists the rows in the order the script declares them and does not yet show group headings or tooltips, so order your `input()` calls the way a reader should meet them, and put a unit in the title when it matters, as in `"Stop, in ATR"`:

```openscript
stopLen = input(14, "ATR length", group = "Risk", min = 1, max = 200,
                tooltip = "Bars of average true range the stop is measured in. "
                        + "A longer length moves the stop less often.")
stopMult = input(2.0, "Stop, in ATR", group = "Risk", min = 0.5, max = 10)

plot(close - stopMult * atr(stopLen), "Stop", red, style = "step")
```

## How a setting is saved and checked

A saved value is filed under **the name the input is assigned to**, or under its **title** when it is assigned to no name, as in `study("B", precision = input(2, "Decimals"))`. So you can reorder, add and delete inputs without losing anyone's settings, and you can change the title of an assigned input freely. Renaming the variable, or the title of an input assigned to no name, is the edit that drops the saved value. Because the name or title is a key, the compiler holds every input to a few rules:

| Mistake | Code |
|---|---|
| Two inputs with the same title | [OS3017](/script/errors/arguments#os3017) |
| An unnamed input whose title spells another input's name | [OS3022](/script/errors/arguments#os3022) |
| An unnamed input with no title | [OS3021](/script/errors/arguments#os3021) |
| An unnamed input whose title is `""` | [OS3024](/script/errors/arguments#os3024) |
| A menu default not in `options` | [OS3018](/script/errors/arguments#os3018) |
| An input that nothing reads | [OS8018](/script/errors/warnings#os8018), a warning |

```openscript
fast = input(9, "Length")
slow = input(21, "Length")
plot(ema(close, fast) - ema(close, slow), "Gap")
```

When the study loads, each saved value is checked against its input: the type, `min` and `max`, the menu's `options`, the source list and a readable date. A value that fails stops the study with [OS6019](/script/errors/data#os6019), naming the setting and the rule. The study does not quietly fall back to the default, because a chart drawing numbers from settings the reader did not choose, with nothing on screen to say so, is worse than a clear message. Fix the value in the dialog, or press **Defaults**.

## var in front of an input

`len = input(14, "Length")` makes `len` another name for the setting. `var tally = input(0, "Start")` is different: it is an ordinary `var` (a variable that keeps its value from one bar to the next) whose starting value is the setting, and later lines may change it.

```openscript
var tally = input(0, "Start counting from")
tally += 1
plot(tally, "Bars counted")
```

Because a `var` can change from bar to bar, it no longer counts as a fixed setting. Reading it inside a higher timeframe read is [OS6003](/script/errors/data#os6003), where the plain form works. Write the plain form when you want the setting itself.

## Related

[Inputs guide](/script/inputs/inputs), [Settings and style](/script/inputs/settings-and-style), [Declarations](/script/reference/declarations), [Colors](/script/reference/color), [Higher timeframes](/script/data/higher-timeframes), [Strings](/script/reference/string).


## Plotting

Source: https://openalgo.in/script/reference/plotting

This page is the reference for the six calls that draw what a study computes:

- `plot()` draws a line, a histogram or another style of one value per bar.
- `plotCandles()` draws candles built from four values of your own.
- `level()` draws a horizontal reference line.
- `fill()` shades the band between two plotted lines.
- `background()` shades the whole height of a bar, behind everything else.
- `barColor()` recolours the instrument's own candles.

Almost every study in OpenScript (also called OpenAlgo Script) uses some of them. The compiler enforces a few of their rules, such as which calls must sit at the top level of the file, so read the first sections before you write your first study.

A Supertrend study on a BHEL 15 minute chart is built from two of them. `plot()` draws the line twice, green while the trend is up and red while it is down, and `fill()` shades between the line and the middle of each candle's body. The BUY and SELL labels on each flip come from `signal()`:


Here is one study that uses five of the six:

```openscript
version 1
study("Band regime", overlay = true, precision = 2)

len  = input(20,  "Length", min = 2, max = 500)
mult = input(2.0, "Deviations", min = 0.1, max = 5)

// bollinger returns three values: b[0] is the basis, b[1] the upper band, b[2] the lower.
b = bollinger(close, len, mult)

// Two named plots, so the fill below can shade between them.
upper = plot(b[1], "Upper", fade(aqua, 40))
lower = plot(b[2], "Lower", fade(aqua, 40))
plot(b[0], "Basis", orange, width = 2)
fill(upper, lower, color = aqua, opacity = 0.08)

// Yesterday's high, read from the daily bars that have closed.
level(req.timeframe("1D", high), "Previous day high", fade(silver, 30))

// Per-bar paint: a colour of none leaves that bar alone.
barColor(close > b[1] ? lime : close < b[2] ? red : none)
background(session.isIn("0915-0930") ? fade(yellow, 92) : none)
```

## Where each call may appear

The **top level** of a file is everything written directly in it, outside any `if`, loop or function body. Four of these calls declare part of the study's fixed shape: the columns in its legend, the rows of its settings dialog, its bands and its levels. That shape has to exist before the first bar runs, so those four calls are top level only. The two paint calls are per-bar output and may appear anywhere.

| Call | Returns | Where it may appear | Read on every bar |
|---|---|---|---|
| `plot()` | a `plot` handle | Top level only | The value and the colour |
| `plotCandles()` | a `plot` handle | Top level only | The four prices and the colours |
| `level()` | a `level` handle | Top level only | The price |
| `fill()` | a `fill` handle | Top level only | The colours |
| `background()` | nothing | Anywhere | The colour |
| `barColor()` | nothing | Anywhere | The colour |

A top-level-only call inside an `if`, a loop or a function body is error `OS3006`. You never need one there: to hide a plot, a level or a band on some bars, give it `none` on those bars.

```openscript
ema20 = ema(close, 20)
trending = adx(14, 14)[0] > 25
if trending
    plot(ema20, "EMA 20", aqua)
```

```openscript
// Compute the average on every bar, then choose per bar what to draw.
ema20 = ema(close, 20)
trending = adx(14, 14)[0] > 25
plot(trending ? ema20 : none, "EMA 20", aqua)
```

Compute an indicator at the top level and choose what to draw afterwards. An indicator called inside an `if` or inside one arm of `? :` only advances on the bars where that branch runs, and the compiler warns with `OS8001`.

## Handles

`plot()`, `plotCandles()`, `level()` and `fill()` each return a **declaration handle**: a name for the column, line or band they declared. A handle exists only while the script is compiled and has no value on any bar. You may name it at the top level, directly from the call, and pass it to `fill()`, which is the only call that takes one. Everything else is refused:

| Doing this with a handle | Error |
|---|---|
| Arithmetic, such as `p + 1` | `OS2003` |
| Keeping it in a `var` | `OS2003` |
| Passing it to a function of your own | `OS2003` |
| Putting it in an array | `OS2019` |
| Reading a past value with `[]` | `OS2004` |

```openscript
p = plot(close, "Close")
q = p + 1
```

Drawing objects are the opposite kind of thing: `draw.line()` and its siblings return ordinary values you keep and change as bars arrive. See [Drawing objects](/script/reference/drawing).

## Absence draws a gap

A value that is `none` never draws as zero. A plot breaks its line, a candle is not drawn, a level disappears, a band stops, and a bar given no colour keeps its own. That is why warmup needs no special code: an indicator is `none` until it has enough bars, so its line simply starts later. See [Absent values](/script/language/absent-values).

## Arguments fixed before the first bar

The parameter tables below mark every argument that is **fixed before the first bar**. The chart builds the legend, the axes and the settings dialog from these arguments before any bar runs, so each one must be written at the call as one of:

| Accepted | Example |
|---|---|
| A literal | `width = 2`, `style = "step"` |
| Arithmetic over literals | `offset = -2 * 3` |
| A colour built from literals | `fade(gray, 55)` |
| An `input()`, at the call or held in a name of its own | `width = input(2, "Width")` |

Anything that depends on bar data is error `OS3003`. So is a name that holds a plain value: `w = 2` followed by `width = w` is refused, so write the `2` at the call or make it an input. Arithmetic on an input, such as `offset = -rightBars`, is refused too; declare the input with the value you want instead.

The fixed arguments are a plot's title, width, style, offset, `overlay`, `precision`, `format` and `scale`; a level's title, colour, style and width; and a band's `opacity` and `overlay`. The value you draw is read on every bar, and so is the colour of `plot()`, `plotCandles()` and `fill()`.

## Plotted columns

### plot()

```
plot(value: series number, title: string, color?: color = none, width?: number = 1.5, style?: string = "line", offset?: number = 0, overlay?: bool = none, precision?: number = none, format?: string = none, scale?: string = "right") -> plot
```

| Parameter | Type | Default |
|---|---|---|
| value | series number | required |
| title | string | required |
| color | color | none |
| width | number | 1.5 |
| style | string | "line" (one of "line", "lineWithMarkers", "step", "area", "histogram", "column") |
| offset | number | 0 |
| overlay | bool | none |
| precision | number | none |
| format | string | none (one of "price", "percent", "volume") |
| scale | string | "right" (one of "right", "left", "none") |

First value: bar 0

Draws one number per bar as a column: a line, a histogram, an area or one of the other styles below. The number comes from whatever you computed; `plot` computes nothing itself. The title is required and names the column in the legend and in the settings dialog, where a user can restyle it.

```openscript
version 1
study("MACD", precision = 2)

m = macd(close, 12, 26, 9)

level(0, "Zero", fade(gray, 55), style = "solid")

// Declared first, so it sits under the two lines.
plot(m[2], "Histogram", color = m[2] > 0 ? fade(lime, 30) : fade(red, 30), style = "histogram")
plot(m[0], "MACD", aqua, width = 2)
plot(m[1], "Signal", orange)
```

The six styles:

| `style` | Draws | Use it for |
|---|---|---|
| `"line"` | A line joining consecutive values. The default | A value that changes every bar |
| `"lineWithMarkers"` | The same line with a mark at every value | Sparse values, such as pivots, that are absent on most bars |
| `"step"` | A flat segment per bar, jumping where the value changes | A value that changes only now and then: a daily read, an opening range, a stop |
| `"area"` | A line with the region to the axis filled | One quantity whose level is the story |
| `"histogram"` | A bar from zero to the value | A signed quantity, read above and below zero |
| `"column"` | A bar from the axis to the value | A quantity that is never negative, such as volume |

A value read once a day and drawn as a line slopes from one reading to the next, and every point on the slope is a number the script never had. Draw it as a step:

```openscript
version 1
study("Previous day high", overlay = true, precision = 2)
plot(req.timeframe("1D", high), "Previous day high", aqua, width = 2, style = "step")
```

**Colour.** One argument takes both a fixed colour and a colour that changes per bar. Pass `aqua` and the plot is always aqua; pass an expression such as `hist > 0 ? lime : red` and each bar gets its own. Leave it out and the host, the application drawing the chart such as the /trading page, picks the next colour from its palette. See [Colors](/script/reference/color).

**Offset.** `offset` moves where the column is drawn, never what it holds. A positive offset draws values to the right, into the space past the newest bar; a negative one draws them back over history, which is how a pivot, known only some bars later, is drawn on the bar it formed on:

```openscript
version 1
study("Pivot highs", overlay = true, precision = 2)

// A pivot is reported 5 bars after it forms, so draw it 5 bars back.
ph = pivotHigh(high, 5, 5)
plot(ph, "Pivot high", orange, style = "lineWithMarkers", offset = -5)
```

The offset is a whole number fixed before the first bar, so `offset = -rightBars` on an input is refused. When the pivot's right side is an input, declare a second input with the negative default, or write the literal as above.

**Pane and axis.** A plot lands in the pane the declaration chose. `overlay = true` on one plot moves just that column onto the price pane. `scale` picks the axis within the pane:

| `scale` | Means |
|---|---|
| `"right"` | The right-hand price axis. The default |
| `"left"` | A second axis on the left, for a series in different units on the same pane |
| `"none"` | No axis, for a column whose size would flatten everything else |

`precision` (decimals, a whole number) and `format` (`"price"`, `"percent"` or `"volume"`) on a plot format the **axis** the plot maps to, not that one line, because an axis is shared. On a plot drawn over the price pane that reformats the instrument's own axis; in a study declared with `overlay = true`, the compiler warns about it with `OS8007`. Set them on the [declaration](/script/reference/declarations) of a study with its own pane instead. Formatting is display only: `format = "percent"` divides nothing by a hundred and `precision = 2` rounds nothing; use `round()` to change a value.

**Remarks.** A plot has a value from bar 0, and its line starts wherever its value first exists. The title has no default, so `plot(x)` is error `OS3012`. Plots are drawn in the order they are declared, so declare a histogram before the lines that should sit on top of it. A stop that changes sides is best drawn as two plots, each `none` while the other is in use, so the line never cuts vertically through the candles on the flip bar.

**See also.** `plotCandles()`, `fill()`, `level()`, [Plots](/script/visuals/plots), [Colors](/script/visuals/colors)

### plotCandles()

```
plotCandles(open: series number, high: series number, low: series number, close: series number, title: string, colorUp?: color = #00ff00ff, colorDown?: color = #ff0000ff, wickColor?: color = none, borderColor?: color = none) -> plot
```

| Parameter | Type | Default |
|---|---|---|
| open | series number | required |
| high | series number | required |
| low | series number | required |
| close | series number | required |
| title | string | required |
| colorUp | color | #00ff00ff |
| colorDown | color | #ff0000ff |
| wickColor | color | none |
| borderColor | color | none |

First value: bar 0

Draws bar-shaped output from four series of your own: a candle per bar with a body from open to close and wicks to the high and low. The candle takes `colorUp` where its close is at or above its open and `colorDown` where it is below. Use it for smoothed candles, or for a coarser timeframe's candle drawn over a finer chart.

```openscript
version 1
study("Averaged candles", precision = 2)

// Each candle opens halfway through the previous one and closes at the
// average of its own four prices, which smooths out the noise.
var avgOpen = none
avgClose = ohlc4
avgOpen = isNone(avgOpen) ? (open + close) / 2 : (avgOpen + avgClose[1]) / 2
avgHigh = max(high, max(avgOpen, avgClose))
avgLow  = min(low, min(avgOpen, avgClose))

plotCandles(avgOpen, avgHigh, avgLow, avgClose, "Averaged", colorUp = teal, colorDown = maroon)
```

**Remarks.** Where the sources are `none`, as during warmup, no candle is drawn. The handle is a `plot`, so `fill()` can name it; a band drawn to it follows its close. `wickColor` and `borderColor` default to `none`, which draws the wick and the border in the body's own colour for that bar; set them only to override that. Drawn over the price pane, faded colours keep the instrument's own candles readable underneath. `plotCandles` draws its own candles; to recolour the instrument's candles, use `barColor()`.

**See also.** `plot()`, `req.timeframe()`, `barColor()`, [Plots](/script/visuals/plots)

## Reference lines

### level()

```
level(price: series number, title?: string = "", color?: color = #808080ff, style?: string = "dashed", width?: number = 1) -> level
```

| Parameter | Type | Default |
|---|---|---|
| price | series number | required |
| title | string | "" |
| color | color | #808080ff |
| style | string | "dashed" (one of "solid", "dashed", "dotted") |
| width | number | 1 |

First value: bar 0

Draws one horizontal line straight across the study's pane at one price: 70 and 30 on an oscillator, zero under a histogram, yesterday's high on a price chart. A level takes no column, has no history and shows no value in the legend, which is what makes it the right tool for a threshold and the wrong one for a measurement.

```openscript
version 1
study("RSI with zones", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)

level(70, "Overbought", fade(red, 40))
level(50, "Middle", fade(gray, 70), style = "dotted")
level(30, "Oversold", fade(lime, 40))

plot(rsi(close, len), "RSI", purple, width = 2)
```

The price may be computed from the data. It is read on every bar, and **the line drawn is the one from the last bar executed**. So a price that exists on only one bar usually draws nothing: hold it in a `var` instead.

```openscript
version 1
study("Session open", overlay = true, precision = 2)

// The session's first bar, or the first bar of the IST day where the chart
// states no session hours, as /trading does in this release.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

var sessionOpen = none
if newSession
    sessionOpen = open

// Held, so the newest bar has a price to draw.
level(sessionOpen, "Session open", fade(aqua, 25), style = "solid", width = 2)
```

**Remarks.** An absent price on the last bar draws no line, which is the way to switch a level off, for example `pos.isFlat ? none : pos.avgPrice` in a strategy. A level cannot be one end of a `fill()` (error `OS3020`); plot the value instead, fully transparent if the line itself should not show. When you want to see where a value used to be, it is not a level: draw it with `plot(x, "Title", style = "step")`. Give every level a title, since it is the line's only label.

**See also.** `plot()`, `fill()`, [Levels](/script/visuals/levels), [Declarations](/script/reference/declarations)

## Bands

### fill()

```
fill(plotA: plot, plotB: plot, color?: color = none, colorUp?: color = none, colorDown?: color = none, opacity?: number = 1, overlay?: bool = none) -> fill
```

| Parameter | Type | Default |
|---|---|---|
| plotA | plot | required |
| plotB | plot | required |
| color | color | none |
| colorUp | color | none |
| colorDown | color | none |
| opacity | number | 1 |
| overlay | bool | none |

First value: bar 0

Shades the region between two plots. A band turns "which line is higher, and by how much" into a colour a reader takes in at a glance, which is why it suits a volatility band, a moving average cross, or an oscillator shaded to its midline. Its first two arguments are the handles that `plot()` or `plotCandles()` returned, not values.

```openscript
version 1
study("EMA cross, shaded", overlay = true, precision = 2)

fast = ema(close, 9)
slow = ema(close, 21)

pFast = plot(fast, "Fast", aqua, width = 2)
pSlow = plot(slow, "Slow", orange, width = 2)

// pFast comes first, so colorUp paints where the FAST average is higher.
fill(pFast, pSlow, colorUp = fade(lime, 85), colorDown = fade(red, 85))
```

| Colour arguments | The band is |
|---|---|
| None of the three | `plotA`'s colour, faded to twelve percent |
| `color` | That colour on both sides |
| `colorUp` and `colorDown` | `colorUp` where `plotA` is above `plotB`, `colorDown` where it is below |

Giving `color` together with `colorUp` or `colorDown` is error `OS3010`. Passing a value where a handle belongs is error `OS3020`:

```openscript
pClose = plot(close, "Close")
fill(pClose, open)
```

To shade to a fixed value, which a `level()` cannot do, plot the value as a column and make it invisible. `fade()` takes a transparency in percent, so `fade(c, 100)` is fully transparent.

```openscript
version 1
study("RSI shaded to 50", precision = 2, range = [0, 100])

pRsi = plot(rsi(close, 14), "RSI", purple, width = 2)
pMid = plot(50, "Midline", fade(gray, 100))
fill(pRsi, pMid, colorUp = fade(lime, 86), colorDown = fade(red, 86))
```

**Remarks.** A band stops wherever either of its plots is `none` and resumes where both return, so it inherits their warmup with no code from you. `opacity` is a dimmer from 0 to 1 that multiplies whatever transparency the colours already have. It defaults to 1 and is fixed before the first bar; dimming with both `opacity` and `fade()` compounds, so pick one. A band and the two plots it names must end up in the same pane, and the compiler does not check it: give both plots the same `overlay` and `offset`, and the band the same `overlay`. On the /trading chart in this release a band takes one colour: a colour computed per bar, such as `color = squeezed ? orange : none`, is not applied bar by bar, and the band is drawn in `plotA`'s colour faded instead. To switch a band off on some bars there, make one of its plots `none` on those bars.

**See also.** `plot()`, `level()`, `fade()`, [Fills](/script/visuals/fills)

## Paint

### background()

```
background(color: color) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| color | color | required |

First value: bar 0

Shades the full height of this bar's column, behind the candles, plots and everything else in the pane the study draws in. It is the surface for a fact about a bar that has no price: the first fifteen minutes of the session, an expanded volatility regime, a study that is still warming up.

```openscript
version 1
study("Opening window", overlay = true)

window = input("0915-0930", "Shade this window")

background(session.isIn(window) ? fade(silver, 90) : none)
```

**Remarks.** `background(none)` leaves the bar unshaded, which is how a conditional wash switches itself off, so the call rarely needs an `if` around it. If a script calls it more than once on a bar, the last call wins, and a last call with `none` clears the shade. It covers the whole bar, so keep colours faint: `fade(c, 90)`, which is 90 percent transparent, is a good place to start. Paint is recomputed on every update of the newest bar and is never deferred the way `signal()` and `alert()` are; guard it with `bar.isConfirmed` if only settled bars should be shaded. A zone with a top and a bottom is not a background; draw it with `draw.box()`.

**See also.** `barColor()`, `fade()`, `session.isIn()`, [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds)

### barColor()

```
barColor(color: color) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| color | color | required |

First value: bar 0

Recolours the instrument's own candles on this bar. A candle's colour already tells the reader whether it closed up or down, so repaint it only to show something they cannot read off the candle, such as the trend or which side of a stop price is on, and pass `none` on the bars that do not matter.

```openscript
version 1
study("Trend regime", overlay = true, precision = 2)

paint = input(true, "Recolour the candles")

fast = ema(close, 20)
slow = ema(close, 50)
up = fast > slow

// Three states: during warmup up is none, and the bar keeps its colour.
tint = isNone(up) ? none : (up ? lime : red)
barColor(paint ? tint : none)

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
```

**Remarks.** `up ? lime : red` alone would paint every warmup bar red, because an absent condition takes the false branch; test `isNone()` first, as above. Within one script the last call on a bar wins, including a last call with `none`. The candles belong to the chart, not to your study: when several studies on one chart call `barColor`, only one study's colours are drawn and the others' are not, so give users an input to switch your colouring off, as the example does. Like `background()`, it is recomputed on every update of the newest bar.

**See also.** `background()`, `plotCandles()`, [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds)

## Related

[Visuals overview](/script/visuals/overview), [Plots](/script/visuals/plots), [Levels](/script/visuals/levels), [Fills](/script/visuals/fills), [Colors reference](/script/reference/color), [Drawing objects](/script/reference/drawing), [Tables](/script/reference/tables), [Declarations](/script/reference/declarations).


## Drawing objects

Source: https://openalgo.in/script/reference/drawing

A plot is one value per bar. A drawing object is a shape with anchors of its own: a trendline between two swing lows, a box over the 09:15 opening range, a label beside the newest bar, a path through the last dozen swings. The `draw` namespace creates these objects, changes them as bars arrive and deletes them when they are no longer wanted. This page is the reference for all twenty `draw` functions in OpenScript (also called OpenAlgo Script), and for the handful of rules that decide whether a drawing study stays fast on a chart of fifty thousand bars.

Here is the pattern most drawing studies follow: create an object once, move it on later bars, and keep a capped list of old ones.

```openscript
version 1
study("Opening range boxes", overlay = true, precision = 2)

rangeMinutes = input(15, "Opening range, in minutes", min = 1, max = 240)
keepSessions = input(5,  "Sessions to keep", min = 1, max = 60)

// The session's first bar, or the first bar of each IST day where the host
// states no session hours, as on the /trading chart.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

var openTime  = none
var rangeHigh = none
var rangeLow  = none
var zone      = none
var zones     = []

if newSession
    openTime  = time
    rangeHigh = high
    rangeLow  = low
    zone      = none

elapsed = isNone(openTime) ? none : time - openTime
forming = not isNone(elapsed) and elapsed < rangeMinutes * 60000

if forming
    rangeHigh = max(rangeHigh, high)
    rangeLow  = min(rangeLow, low)
    if isNone(zone)
        // Created once per session...
        zone = draw.box(openTime, rangeHigh, time, rangeLow, color = aqua, fillColor = aqua, opacity = 0.08)
        push(zones, zone)
        // ...and the oldest deleted once there are more than you keep.
        if size(zones) > keepSessions
            draw.delete(shift(zones))
    else
        draw.setBounds(zone, openTime, rangeHigh, time, rangeLow)
else if not isNone(zone)
    // The range is final: only the right edge follows the session.
    draw.setTo(zone, time, rangeLow)
```


The examples on this page that reset once per session find the session's first bar with the `newSession` line above. `session.isFirstBar` needs the instrument's session hours, which the /trading chart does not state in this release, so there it has no value; a new IST date marks the same bar for an NSE, BSE or MCX session.

## How drawing objects work

**Anchors are a time and a price.** Every point is a timestamp in UTC milliseconds, usually `time` or `time[n]`, and a price on the scale of the pane the study draws in. An object is never anchored to a bar index, so it stays where you put it when the chart loads more history and every index shifts. In a study with its own pane, the "price" is a reading on that pane's scale, such as an RSI value.

**They may be created anywhere.** Unlike `plot()`, every `draw` call may appear inside an `if`, a loop or a function.

**They are ordinary values.** `draw.line()`, `draw.label()`, `draw.box()` and `draw.polyline()` return a `line`, `label`, `box` or `polyline`. You can keep one in a `var`, hold many in an array, pass one to a function and compare one with `none`. Two names for the same object are one object, and `==` tests identity.

**They live until you delete them.** Dropping the last name that refers to an object does not remove it: the chart keeps drawing it until `draw.delete()` or `draw.deleteAll()` does. Each host, the application running the script such as the /trading page, sets a ceiling on how many objects one script may hold; the engine's default, which the /trading chart uses, is 10,000. Creating one more stops the script on that bar with `OS5010`, rather than quietly dropping the oldest. So decide how each object ends before you write the line that creates it:

| Lifecycle | How it is written | Objects on the chart | Use it for |
|---|---|---|---|
| One object, moved | Create while the `var` is `none`, then call setters | One per thing drawn | Something that always exists: a level, a range, a tag |
| A capped list | `push()` on create, `draw.delete(shift(list))` over the cap | At most the cap | One object per event: zones, pivots, breakouts |
| Create and forget | A bare `draw.line(...)` on an event | One per event, for ever | Only when the event count is small and known |

**The newest bar is rolled back.** On a moving chart the newest bar runs again on every update, and before each run the set of objects is restored to what it was at the end of the previous bar, exactly as `var` values are. A script that creates a label on the newest bar gets one label, not one per update. Keep handles in `var`, never in `live var`: a `live var` survives the rollback, so after the next update it holds a handle to an object the rollback removed, and changes made through it draw nothing.

**A deleted object stays deleted.** A setter called on an object that has been deleted is error `OS4005`, which stops the script on that bar. A setter given `none` does nothing, and so does `draw.delete()` given `none` or an object already deleted. So assign `none` to the name on the same lines that delete the object, test `isNone()` before changing it, and when objects live in an array, remove the element as well: deleting the object does not.

**Absent values draw nothing.** An object whose anchor has no time or no price is not drawn, and a colour of `none` is fully transparent rather than a default colour.

**Objects are written, not read.** There is no call that asks an object where it is. When a script needs the numbers later, to see whether price has closed through a zone, it keeps them itself in its own `var`s or arrays beside the handles.

### Which setter takes which object

Each setter takes only the kinds of object that have the property it writes. Passing another kind is error `OS3011` when the script is compiled.

| Call | line | label | box | polyline |
|---|---|---|---|---|
| `draw.setFrom()`, `draw.setTo()`, `draw.setBounds()` | yes | | yes | |
| `draw.setAt()` | | yes | | |
| `draw.setPoints()` | | | | yes |
| `draw.setExtend()`, `draw.setStyle()` | yes | | | |
| `draw.setText()`, `draw.setTextColor()`, `draw.setTooltip()` | | yes | yes | |
| `draw.setFillColor()` | | | yes | yes |
| `draw.setWidth()` | yes | | yes | yes |
| `draw.setColor()`, `draw.delete()` | yes | yes | yes | yes |

```openscript
tag = draw.label(time, high, "high")
draw.setFrom(tag, time, low)
```

## Creating objects

### draw.line()

```
draw.line(t1: number, p1: number, t2: number, p2: number, color?: color = gray, width?: number = 1, style?: string = "solid", extendLeft?: bool = false, extendRight?: bool = false) -> line
```

| Parameter | Type | Default |
|---|---|---|
| t1 | number | required |
| p1 | number | required |
| t2 | number | required |
| p2 | number | required |
| color | color | gray |
| width | number | 1 |
| style | string | "solid" (one of "solid", "dashed", "dotted") |
| extendLeft | bool | false |
| extendRight | bool | false |

First value: bar 0

Creates a straight line between two anchors, `(t1, p1)` and `(t2, p2)`, and returns it. Use it for trendlines, a line joining two pivots, or a horizontal line that starts and stops rather than crossing the whole pane. `extendLeft` and `extendRight` continue it past its anchors to the edge of the pane.

```openscript
version 1
study("Pivot low trendline", overlay = true, precision = 2)

rightBars = input(5, "Pivot right bars", min = 1, max = 50)

pl = pivotLow(low, 5, rightBars)

var prevTime  = none
var prevPrice = none
var trend     = none

if not isNone(pl)
    // A pivot is reported rightBars bars late, so anchor it where it formed.
    pivotTime = time[rightBars]
    if not isNone(prevTime)
        if isNone(trend)
            trend = draw.line(prevTime, prevPrice, pivotTime, pl, color = lime, width = 2, extendRight = true)
        else
            draw.setBounds(trend, prevTime, prevPrice, pivotTime, pl)
    prevTime  = pivotTime
    prevPrice = pl
```

**Remarks.** Anchor a pivot at `time[rightBars]`, the bar it formed on, not at `time`, the bar it was reported on. An extended line needs no upkeep: its anchors fix the slope and the chart draws the rest. To reach into the empty space past the newest bar, extend the line rather than computing a future timestamp: `time - time[1]` is the bar length inside a session but the whole overnight gap on a session's first bar.

**See also.** `draw.setBounds()`, `draw.setExtend()`, `draw.setStyle()`, [Lines and boxes](/script/visuals/lines-and-boxes)

### draw.label()

```
draw.label(t: number, p: number, text: string, color?: color = none, textColor?: color = white, align?: string = "center", tooltip?: string = "") -> label
```

| Parameter | Type | Default |
|---|---|---|
| t | number | required |
| p | number | required |
| text | string | required |
| color | color | none |
| textColor | color | white |
| align | string | "center" |
| tooltip | string | "" |

First value: bar 0

Creates a plate of text anchored at a time and a price, and returns it. A label is an object you own: use it for a caption that belongs at a point, such as a pivot's price or the current reading beside the newest bar. For a marker on the bar where an event happened, `signal()` is simpler, because a signal has no handle to manage.

```openscript
version 1
study("Pivot labels", overlay = true, precision = 2)

rightBars = input(5,   "Pivot right bars", min = 1, max = 50)
padding   = input(0.5, "Padding, in ATR",  min = 0, max = 5)
keep      = input(30,  "Labels to keep",   min = 1, max = 500)

pivotUp   = pivotHigh(high, 5, rightBars)
pivotDown = pivotLow(low, 5, rightBars)

// Padding measured in the instrument's own volatility clears the bar on
// a stock at 250 and on an index future at 25,000 alike.
pad = atr(14) * padding

var tags = []

if not isNone(pivotUp)
    push(tags, draw.label(time[rightBars], pivotUp + pad, text(pivotUp, 2), color = red, textColor = white))

if not isNone(pivotDown)
    push(tags, draw.label(time[rightBars], pivotDown - pad, text(pivotDown, 2), color = lime, textColor = black))

// A pivot high and a pivot low can land on one bar, so trim until the list fits.
while size(tags) > keep
    draw.delete(shift(tags))
```

**Remarks.** A label is placed at the price you give it; there is no pixel offset anywhere in the language, so pad with a multiple of `atr()` to clear the candle. `color` is the plate and defaults to `none`, which draws no plate at all: the text then sits straight on the chart in `textColor`, white by default, so give a label a plate colour or a text colour that reads on your chart. `align` decides which part of the plate sits on the anchor's time: `"center"` (the default) centres it, `"left"` puts the plate's left edge there so it extends to the right, and `"right"` puts its right edge there so it extends to the left. Keep the caption short and put detail in `tooltip`, which shows while the pointer rests on the label. `text()` of an absent value is the string `"none"`, so guard captions built from values that may still be warming up. A label per bar is the commonest way to make a chart slow: a value on every bar is a `plot()`.

**See also.** `draw.setAt()`, `draw.setText()`, `signal()`, [Labels and shapes](/script/visuals/labels-and-shapes)

### draw.box()

```
draw.box(t1: number, p1: number, t2: number, p2: number, color?: color = none, fillColor?: color = none, opacity?: number = 0.12, width?: number = 1, text?: string = "", textColor?: color = white, tooltip?: string = "") -> box
```

| Parameter | Type | Default |
|---|---|---|
| t1 | number | required |
| p1 | number | required |
| t2 | number | required |
| p2 | number | required |
| color | color | none |
| fillColor | color | none |
| opacity | number | 0.12 |
| width | number | 1 |
| text | string | "" |
| textColor | color | white |
| tooltip | string | "" |

First value: bar 0

Creates a rectangle between two corners, `(t1, p1)` and `(t2, p2)`, and returns it. A box is the shape for a price band over a stretch of time: a supply or demand zone, an opening range, the range of a mother bar. It can carry a caption inside it and a tooltip.

```openscript
version 1
study("Inside bar zones", overlay = true, precision = 2)

keep = input(10, "Zones to keep", min = 1, max = 100)

var zones = []

// An inside bar trades within the previous bar's range.
insideBar = high < high[1] and low > low[1]

if insideBar
    zone = draw.box(time[1], high[1], time, low[1], color = orange, fillColor = orange, text = "inside", textColor = orange)
    push(zones, zone)
    if size(zones) > keep
        draw.delete(shift(zones))
```

**Remarks.** `color` is the border and `fillColor` the inside; both default to `none`, which draws nothing, so a box needs at least one of them to be seen. `opacity` dims the fill and defaults to `0.12`, faint enough to leave the candles readable. The caption is drawn in `textColor`, white by default. A box has no extend argument, so a box that should reach the current bar has its right edge moved there with `draw.setTo()` on each bar, which is one cheap call against one object. A regime with no top or bottom, such as "the first fifteen minutes", belongs in `background()`, not in a box.

**See also.** `draw.setBounds()`, `draw.setText()`, `draw.setFillColor()`, [Lines and boxes](/script/visuals/lines-and-boxes)

### draw.polyline()

```
draw.polyline(times: array<number>, prices: array<number>, color?: color = gray, width?: number = 1, closed?: bool = false, fillColor?: color = none, opacity?: number = 0.12) -> polyline
```

| Parameter | Type | Default |
|---|---|---|
| times | array<number> | required |
| prices | array<number> | required |
| color | color | gray |
| width | number | 1 |
| closed | bool | false |
| fillColor | color | none |
| opacity | number | 0.12 |

First value: bar 0

Creates one path through many points and returns it. The points come as two arrays of the same length, one of times and one of prices, paired by index. With `closed = true` the path returns to its first point, and with a `fillColor` it becomes a filled shape: a wedge, a triangle, an outline around a range.

```openscript
version 1
study("Last ten bars outline", overlay = true, precision = 2)

var outline = none

if bar.isLast
    times  = []
    prices = []
    // Along the highs, oldest first, then back along the lows.
    for i = 9 to 0 step -1
        push(times, time[i])
        push(prices, high[i])
    for i = 0 to 9
        push(times, time[i])
        push(prices, low[i])
    // One outline for the life of the chart: created once, then reshaped
    // as each new bar arrives.
    if isNone(outline)
        outline = draw.polyline(times, prices, color = purple, closed = true, fillColor = purple, opacity = 0.1)
    else
        draw.setPoints(outline, times, prices)
```

**Remarks.** The path is copied when the call runs. Pushing to the arrays afterwards changes nothing on the chart; `draw.setPoints()` is how a path changes. Keep the two arrays the same length: trim both together. A point whose time or price is `none` is a gap: the path is drawn in separate pieces on either side of it, and on the /trading chart a path with a gap is drawn open and unfilled, whatever `closed` and `fillColor` say.

**See also.** `draw.setPoints()`, `push()`, [Lines and boxes](/script/visuals/lines-and-boxes)

## Moving objects

### draw.setFrom()

```
draw.setFrom(obj: line or box, t: number, p: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | line or box | required |
| t | number | required |
| p | number | required |

First value: bar 0

Moves the first anchor of a line or box to a new time and price. The second anchor stays where it is.

```openscript
version 1
study("Session high line", overlay = true, precision = 2)

// The session's first bar, or of the IST day where no session hours are stated.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

var hiPrice = none
var hiTime  = none
var hiLine  = none

if newSession or isNone(hiPrice) or high > hiPrice
    hiPrice = high
    hiTime  = time

if isNone(hiLine)
    hiLine = draw.line(hiTime, hiPrice, time, hiPrice, color = orange, style = "dashed")
else
    // The start follows the bar that made the high; the end follows this bar.
    draw.setFrom(hiLine, hiTime, hiPrice)
    draw.setTo(hiLine, time, hiPrice)
```

**See also.** `draw.setTo()`, `draw.setBounds()`

### draw.setTo()

```
draw.setTo(obj: line or box, t: number, p: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | line or box | required |
| t | number | required |
| p | number | required |

First value: bar 0

Moves the second anchor of a line or box to a new time and price. It is the everyday call for a shape whose right edge follows the newest bar.

```openscript
version 1
study("Session open line", overlay = true, precision = 2)

// The session's first bar, or of the IST day where no session hours are stated.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

var sessionOpen = none
var openLine    = none

if newSession
    // One line at a time: the previous session's is removed.
    if not isNone(openLine)
        draw.delete(openLine)
    sessionOpen = open
    openLine    = draw.line(time, open, time, open, color = aqua, width = 2)
else if not isNone(openLine)
    // The start stays at the session's first bar; the end follows this bar.
    draw.setTo(openLine, time, sessionOpen)
```

**See also.** `draw.setFrom()`, `draw.setBounds()`

### draw.setBounds()

```
draw.setBounds(obj: line or box, t1: number, p1: number, t2: number, p2: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | line or box | required |
| t1 | number | required |
| p1 | number | required |
| t2 | number | required |
| p2 | number | required |

First value: bar 0

Moves both anchors of a line or box in one call. Use it when both ends of a shape change on the same bar, such as a range whose top, bottom and extent all move.

```openscript
version 1
study("Twenty bar range", overlay = true, precision = 2)

top    = highest(high, 20)
bottom = lowest(low, 20)

var rangeBox = none
if not isNone(top)
    if isNone(rangeBox)
        rangeBox = draw.box(time[19], top, time, bottom, color = silver, fillColor = silver, opacity = 0.06)
    else
        draw.setBounds(rangeBox, time[19], top, time, bottom)
```

**See also.** `draw.setFrom()`, `draw.setTo()`, `highest()`

### draw.setAt()

```
draw.setAt(label: label, t: number, p: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| label | label | required |
| t | number | required |
| p | number | required |

First value: bar 0

Moves a label to a new time and price. Together with `draw.setText()`, it keeps one label beside the newest bar for the life of the chart instead of creating a new one on every bar.

```openscript
version 1
study("VWAP tag", overlay = true, precision = 2)

// The day's VWAP, restarted on the first bar of each IST day. vwap() restarts
// on the session's first bar, which the /trading chart cannot find without
// session hours, so this anchors it by date.
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")
v = vwapAnchor(hlc3, newDay)
plot(v, "VWAP", orange, width = 2)

var tag = none
if bar.isLast
    if isNone(tag)
        tag = draw.label(time, v, "VWAP " + text(v, 2), color = fade(orange, 25), textColor = black)
    else
        draw.setAt(tag, time, v)
        draw.setText(tag, "VWAP " + text(v, 2))
```

**Remarks.** Only a label has a single anchor; lines and boxes move with `draw.setFrom()`, `draw.setTo()` and `draw.setBounds()`.

**See also.** `draw.label()`, `draw.setText()`

### draw.setPoints()

```
draw.setPoints(polyline: polyline, times: array<number>, prices: array<number>) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| polyline | polyline | required |
| times | array<number> | required |
| prices | array<number> | required |

First value: bar 0

Replaces the whole path of a polyline with new arrays of times and prices. A polyline keeps its own copy of the points it was given, so this call is the only way its shape changes.

```openscript
version 1
study("Swing path", overlay = true, precision = 2)

rightBars = input(5,  "Pivot right bars", min = 1, max = 50)
points    = input(12, "Points in the path", min = 3, max = 100)

pivotUp   = pivotHigh(high, 5, rightBars)
pivotDown = pivotLow(low, 5, rightBars)

var pathTimes  = []
var pathPrices = []
var path       = none

swing = isNone(pivotUp) ? pivotDown : pivotUp

if not isNone(swing)
    push(pathTimes, time[rightBars])
    push(pathPrices, swing)
    // Trim both arrays together, so they stay the same length.
    if size(pathTimes) > points
        shift(pathTimes)
        shift(pathPrices)
    if isNone(path)
        path = draw.polyline(pathTimes, pathPrices, color = purple, width = 2)
    else
        draw.setPoints(path, pathTimes, pathPrices)
```

**See also.** `draw.polyline()`, `shift()`

### draw.setExtend()

```
draw.setExtend(line: line, left: bool, right: bool) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| line | line | required |
| left | bool | required |
| right | bool | required |

First value: bar 0

Sets whether a line continues past its first anchor to the left edge of the pane and past its second anchor to the right edge. It changes what `extendLeft` and `extendRight` said when the line was created.

```openscript
version 1
study("Support until broken", overlay = true, precision = 2)

pl = pivotLow(low, 5, 5)

var support      = none
var supportPrice = none
var broken       = false

if not isNone(pl)
    if not isNone(support)
        draw.delete(support)
    supportPrice = pl
    broken       = false
    support      = draw.line(time[5], pl, time, pl, color = lime, extendRight = true)
else if not isNone(support) and not broken and close < supportPrice
    // Broken: stop the ray at the breaking bar and grey it out.
    broken = true
    draw.setExtend(support, false, false)
    draw.setTo(support, time, supportPrice)
    draw.setColor(support, gray)
```

**See also.** `draw.line()`, `draw.setTo()`

## Styling and text

### draw.setColor()

```
draw.setColor(obj: line, label, box or polyline, color: color) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | line, label, box or polyline | required |
| color | color | required |

First value: bar 0

Changes the colour of a line, or the border of a box, the plate of a label or the stroke of a polyline. Any object kind is accepted.

```openscript
version 1
study("Last price line", overlay = true, precision = 2)

var lastLine = none
if bar.isLast
    if isNone(lastLine)
        lastLine = draw.line(time[1], close, time, close, style = "dotted", extendLeft = true, extendRight = true)
    else
        draw.setBounds(lastLine, time[1], close, time, close)
    draw.setColor(lastLine, close >= open ? lime : red)
```

**See also.** `draw.setFillColor()`, `draw.setTextColor()`, [Colors](/script/reference/color)

### draw.setFillColor()

```
draw.setFillColor(obj: box or polyline, color: color) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | box or polyline | required |
| color | color | required |

First value: bar 0

Changes the colour inside a box or a polyline. Use it to let a zone say something about price, for example whether the close is above it, inside it or below it.

```openscript
version 1
study("First bar break", overlay = true, precision = 2)

// The session's first bar, or of the IST day where no session hours are stated.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

var zone   = none
var top    = none
var bottom = none

if newSession
    if not isNone(zone)
        draw.delete(zone)
    top    = high
    bottom = low
    zone   = draw.box(time, high, time, low, color = aqua, fillColor = aqua, opacity = 0.1)
else if not isNone(zone)
    draw.setTo(zone, time, bottom)
    // Green above the session's first bar, red below it, aqua inside.
    draw.setFillColor(zone, close > top ? lime : close < bottom ? red : aqua)
```

**Remarks.** The box's `opacity`, set when it was created, still dims the new colour.

**See also.** `draw.box()`, `draw.setColor()`

### draw.setTextColor()

```
draw.setTextColor(obj: label or box, color: color) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | label or box | required |
| color | color | required |

First value: bar 0

Changes the colour of the text in a label or a box. Pair it with `draw.setColor()` when a label's plate changes, so the text stays readable on it.

```openscript
version 1
study("Current reading", overlay = true, precision = 2)

oscillator = rsi(close, 14)
pad        = atr(14)

fn show(value, decimals) => isNone(value) ? "warming up" : text(value, decimals)

var tag = none
if bar.isLast
    caption = "RSI " + show(oscillator, 1)
    plate   = isNone(oscillator) ? gray : (oscillator > 70 ? red : (oscillator < 30 ? lime : silver))
    // White reads on the red and green plates, black on grey and silver.
    ink     = oscillator > 70 or oscillator < 30 ? white : black
    if isNone(tag)
        tag = draw.label(time, high + pad, caption, color = plate, textColor = ink, tooltip = "14 bar RSI")
    else
        draw.setAt(tag, time, high + pad)
        draw.setText(tag, caption)
        draw.setColor(tag, plate)
        draw.setTextColor(tag, ink)
```

**Remarks.** `rsi()` and `atr()` are computed at the top level and only used inside the `if`. Called inside it, they would advance only on the newest bar and have no history. While the RSI is still `none`, the comparisons in `ink` are `none` too and take the false branch, so the text is black on the grey plate.

**See also.** `draw.setColor()`, `draw.setText()`

### draw.setWidth()

```
draw.setWidth(obj: line, box or polyline, width: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | line, box or polyline | required |
| width | number | required |

First value: bar 0

Changes the thickness of a line, of a box's border or of a polyline's stroke. A heavier line says "follow this one", so it is a way to single out the newest object in a list.

```openscript
version 1
study("Latest zone in bold", overlay = true, precision = 2)

var zones = []

if high < high[1] and low > low[1]
    // The previous newest zone goes back to a thin border.
    if size(zones) > 0
        draw.setWidth(element(zones, size(zones) - 1), 1)
    push(zones, draw.box(time[1], high[1], time, low[1], color = orange, width = 3))
    if size(zones) > 10
        draw.delete(shift(zones))
```

**See also.** `draw.setStyle()`, `element()`

### draw.setStyle()

```
draw.setStyle(obj: line, style: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | line | required |
| style | string | required (one of "solid", "dashed", "dotted") |

First value: bar 0

Changes a line to `"solid"`, `"dashed"` or `"dotted"`. Only a line has a style. A common use is to keep a level solid while it is in play and dash it once it has been reached.

```openscript
version 1
study("Target line", overlay = true, precision = 2)

ema20 = ema(close, 20)
band  = atr(14)

var target = none
var goal   = none
var hit    = false

// A new target two ATR above the close on each cross of the average.
if crossUp(close, ema20)
    if not isNone(target)
        draw.delete(target)
    goal   = close + 2 * band
    hit    = false
    target = draw.line(time[1], goal, time, goal, color = lime, extendRight = true)
else if not isNone(target) and not hit and high >= goal
    // Reached: keep it as a record, dashed and stopped at this bar.
    hit = true
    draw.setStyle(target, "dashed")
    draw.setExtend(target, false, false)
    draw.setTo(target, time, goal)
```

**See also.** `draw.line()`, `draw.setWidth()`

### draw.setText()

```
draw.setText(obj: label or box, text: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | label or box | required |
| text | string | required |

First value: bar 0

Changes the caption of a label, or the text written inside a box. Use it to keep a caption in step with the numbers it describes.

```openscript
version 1
study("Session range caption", overlay = true, precision = 2)

// The session's first bar, or of the IST day where no session hours are stated.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

var zone      = none
var startTime = none
var hi        = none
var lo        = none

if newSession
    if not isNone(zone)
        draw.delete(zone)
    startTime = time
    hi        = high
    lo        = low
    zone      = draw.box(time, high, time, low, color = teal, fillColor = teal, opacity = 0.06, textColor = silver)
else if not isNone(zone)
    hi = max(hi, high)
    lo = min(lo, low)
    draw.setBounds(zone, startTime, hi, time, lo)
    draw.setText(zone, "range " + text(hi - lo, 2))
```

**See also.** `draw.setTooltip()`, `text()`, `str.format()`

### draw.setTooltip()

```
draw.setTooltip(obj: label or box, text: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | label or box | required |
| text | string | required |

First value: bar 0

Sets the detail shown while the pointer rests on a label or box. A tooltip costs nothing on screen, so it is where the numbers behind an object belong, leaving the caption to say what the object is.

```openscript
version 1
study("Pivot tooltips", overlay = true, precision = 2)

ph = pivotHigh(high, 5, 5)

var tags = []
if not isNone(ph)
    tag = draw.label(time[5], ph, "PH", color = red, textColor = white)
    draw.setTooltip(tag, "Pivot high " + text(ph, 2) + " at " + date.format(time[5], "yyyy-MM-dd HH:mm"))
    push(tags, tag)
    if size(tags) > 20
        draw.delete(shift(tags))
```

**See also.** `draw.setText()`, `date.format()`

## Deleting and counting

### draw.delete()

```
draw.delete(obj: line, label, box or polyline) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| obj | line, label, box or polyline | required |

First value: bar 0

Removes one object from the chart. Any kind of object is accepted. After the call the handle is stale: assign `none` to the name on the same lines, so no later setter reaches a deleted object.

```openscript
version 1
study("Cross marker", overlay = true, precision = 2)

sma50 = sma(close, 50)

var marker = none

if crossUp(close, sma50)
    if not isNone(marker)
        draw.delete(marker)
    marker = draw.label(time, low, "cross", color = lime, textColor = black)

// Remove the marker once price falls back, and forget the handle with it.
if not isNone(marker) and close < sma50
    draw.delete(marker)
    marker = none
```

**Remarks.** A setter given a deleted object is error `OS4005`, which stops the script on that bar and names the bar the object was deleted on. Deleting an object that sits in an array leaves the element in the array; `draw.delete(shift(list))` deletes the oldest object and removes it from the list in one line. When removing several elements in a loop, walk the list downwards, `for i = size(list) - 1 to 0 step -1`, so a removal never skips the element after it.

**See also.** `draw.deleteAll()`, `shift()`, `remove()`

### draw.deleteAll()

```
draw.deleteAll() -> nothing
```

First value: bar 0

Removes every object this script has created. It is a reset: use it when the whole picture is out of date, or on the newest bar in a study that draws only a small set of objects for the current state.

```openscript
version 1
study("Last three pivot highs", overlay = true, precision = 2)

ph = pivotHigh(high, 5, 5)

var pivotTimes  = []
var pivotPrices = []
if not isNone(ph)
    push(pivotTimes, time[5])
    push(pivotPrices, ph)
    if size(pivotTimes) > 3
        shift(pivotTimes)
        shift(pivotPrices)

if bar.isLast
    // A small picture of the current state: clear it and draw it again.
    draw.deleteAll()
    for i = 0 to size(pivotTimes) - 1
        draw.label(element(pivotTimes, i), element(pivotPrices, i), text(element(pivotPrices, i), 2), color = red)
```

**Remarks.** Calling it on every bar and redrawing works but wastes effort: it rebuilds every object on every bar of history to show the state of the last one. Every handle the script still holds is stale afterwards, so set the names you keep to `none` or clear the arrays that hold them.

**See also.** `draw.delete()`, `draw.count()`

### draw.count()

```
draw.count() -> number
```

First value: bar 0

The number of objects this script currently holds on the chart. It is the health check for a drawing study: a count that keeps climbing as more history loads is the sign of a create-and-forget object that needs a cap.

```openscript
version 1
study("Object count", overlay = true, precision = 2)

var zones = []
if high < high[1] and low > low[1]
    push(zones, draw.box(time[1], high[1], time, low[1], fillColor = orange))
    if size(zones) > 25
        draw.delete(shift(zones))

panel = table("Objects", 1, 2, position = "bottomLeft")
if bar.isLast
    cell(panel, 0, 0, "Objects held")
    cell(panel, 0, 1, text(draw.count(), 0), align = "right")
```

**See also.** `draw.deleteAll()`, `table()`

## Related

[Lines and boxes](/script/visuals/lines-and-boxes), [Labels and shapes](/script/visuals/labels-and-shapes), [Visuals overview](/script/visuals/overview), [Plotting](/script/reference/plotting), [Tables](/script/reference/tables), [Persistence](/script/language/persistence), [Realtime and confirmation](/script/language/realtime-and-confirmation), [Collections](/script/language/collections).


## Tables

Source: https://openalgo.in/script/reference/tables

Most of what a study computes is a value per bar, and a value per bar is a `plot()`. A table is for the rest: the current RSI, ATR and volume in one glance, the trend on three timeframes, today's range, the symbol and interval. OpenScript (also called OpenAlgo Script) declares a table with `table()`, a grid pinned to a corner of the pane, and fills it with `cell()`. The grid stays in its corner while the chart scrolls and zooms, and it sits over whatever candles are behind it.

```openscript
version 1
study("Readings", overlay = true, precision = 2)

corner = input("bottomRight", "Corner", options = ["topLeft", "topRight", "bottomLeft", "bottomRight"])

// Declared once, at the top level, like a plot.
panel = table("Readings", 4, 2, position = corner, textColor = silver, bgColor = fade(black, 25))

// Every reading is computed on every bar, outside the if below.
r = rsi(close, 14)
a = atr(14)
volumeRatio = volume / sma(volume, 20)

fn show(value, decimals) => isNone(value) ? "warming up" : text(value, decimals)

// Written on the newest bar only: the panel shows one state, the current one.
if bar.isLast
    cell(panel, 0, 0, chart.symbol, textColor = white)
    cell(panel, 0, 1, chart.interval, textColor = white, align = "right")
    cell(panel, 1, 0, "RSI 14")
    cell(panel, 1, 1, show(r, 1), textColor = r > 70 ? red : r < 30 ? lime : silver, align = "right")
    cell(panel, 2, 0, "ATR 14")
    cell(panel, 2, 1, show(a, 2), align = "right")
    cell(panel, 3, 0, "Volume against its average")
    cell(panel, 3, 1, show(volumeRatio, 2), align = "right")
```


## How a table works

| | `table()` | `cell()` |
|---|---|---|
| Where it may appear | Top level only | Anywhere: inside an `if`, a loop or a function |
| When it happens | Once, before the first bar | On every bar where the call runs |
| Its arguments | All fixed before the first bar | All read on the bar it runs |
| Returns | A `table`, the same object on every bar | Nothing |

**A grid starts every bar empty.** What the chart shows is the cells written on the newest bar, and a cell written on an earlier bar does not carry over. That is why a panel is written inside `if bar.isLast` (`bar.isLast` is true only on the newest bar): writing it on every bar of a long history is thousands of writes to show the last one. On a moving chart the newest bar runs again on every update and the grid is rewritten each time, so nothing piles up.

**Compute at the top level, write inside the `if`.** An indicator such as `rsi()` keeps state and advances only on the bars where its call runs. Moved inside `if bar.isLast`, it would see one bar and return `none`, and the compiler warns with `OS8001`.

**Cells hold text.** The text argument is a `string`, and a number passed there is error `OS3011`, so convert it with `text()`. A text that is `none` leaves the cell blank. Because a condition that is `none` takes the false branch, `b ? "up" : "down"` says "down" on every bar before `b` has a value; test `isNone()` first and say "warming up".

**Addresses start at zero.** A grid of 4 rows and 2 columns has rows 0 to 3 and columns 0 to 1. Writing outside the grid is error `OS4004`, which stops the script on that bar.

**Colour has three levels.** The grid's `textColor` and `bgColor` apply to every cell that says nothing else; a cell's own `textColor` and `bgColor` override them for that cell; leave both out and the chart's defaults apply. Give the grid a translucent background, such as `fade(black, 25)`, so the candles behind it stay faintly visible.

**On the /trading chart, one grid per study is drawn.** A study may declare several grids and the compiler accepts them, but the chart draws only the first one declared; the others compile, are written to and never appear. The grid's title is not shown on the chart either. Declare one grid with the rows you need, and write a second study for a second panel.

## Declaring a grid

### table()

```
table(title: string, rows: number, cols: number, position?: string = "topRight", textColor?: color = none, bgColor?: color = none, borderWidth?: number = 0) -> table
```

| Parameter | Type | Default |
|---|---|---|
| title | string | required |
| rows | number | required |
| cols | number | required |
| position | string | "topRight" (one of "topLeft", "topRight", "bottomLeft", "bottomRight") |
| textColor | color | none |
| bgColor | color | none |
| borderWidth | number | 0 |

First value: bar 0

Declares a grid of `rows` by `cols` cells pinned to one corner of the study's pane, and returns the table that `cell()` writes into. The title names the grid; the size and corner are part of the study's fixed shape, which is why the call is top level only and every argument is settled before the first bar.

```openscript
version 1
study("Timeframe bias", overlay = true)

tfA = input("15m", "Timeframe 1", kind = "interval")
tfB = input("1h",  "Timeframe 2", kind = "interval")
tfC = input("1D",  "Timeframe 3", kind = "interval")

grid = table("Bias", 4, 2, position = "topRight", bgColor = fade(black, 20), borderWidth = 1)

// Each row changes when that timeframe's bar closes, and not before.
// A timeframe finer than the chart's stops the study with OS6002, so run
// this on a chart of 15 minutes or less.
biasA = req.timeframe(tfA, ema(close, 20) > ema(close, 50))
biasB = req.timeframe(tfB, ema(close, 20) > ema(close, 50))
biasC = req.timeframe(tfC, ema(close, 20) > ema(close, 50))

fn word(b) => isNone(b) ? "warming up" : (b ? "up" : "down")
fn tint(b) => isNone(b) ? silver : (b ? lime : red)

if bar.isLast
    cell(grid, 0, 0, "Timeframe", textColor = white)
    cell(grid, 0, 1, "Bias", textColor = white, align = "right")
    cell(grid, 1, 0, tfA)
    cell(grid, 1, 1, word(biasA), textColor = tint(biasA), align = "right")
    cell(grid, 2, 0, tfB)
    cell(grid, 2, 1, word(biasB), textColor = tint(biasB), align = "right")
    cell(grid, 3, 0, tfC)
    cell(grid, 3, 1, word(biasC), textColor = tint(biasC), align = "right")
```

A `table()` inside an `if`, a loop or a function is error `OS3006`:

```openscript
if bar.isLast
    panel = table("Readings", 2, 2)
```

Its arguments are fixed before the first bar, so write each one at the call as a literal, a colour such as `fade(black, 25)`, or an `input()`. An argument that depends on bar data is error `OS3003`, and so is a size held in a name, such as `n = 3` followed by `table("Readings", n, 2)`.

**Remarks.** `rows` and `cols` are whole numbers; a fraction is error `OS3004`. Make `position` an `input()` with the four corners as options, since which corner is free depends on the reader's chart. The table is an ordinary value: you can name it, keep it and pass it to a function of your own, and it is never deleted. Writing no cells leaves every cell blank, but the grid keeps its size, its border and its own `bgColor`, so a grid with a background still shows as an empty block. For a panel the reader can switch off, leave the grid's `bgColor` and `borderWidth` out, set `bgColor` on the cells instead, and guard the writes with `if bar.isLast and showPanel`. A table with fifty rows of history works and is unreadable at the size of a chart corner; a list belongs in a report, not on the chart.

**See also.** `cell()`, `clear()`, `bar.isLast`, [Tables](/script/visuals/tables)

## Writing cells

### cell()

```
cell(t: table, row: number, col: number, text: string, textColor?: color = none, bgColor?: color = none, align?: string = "left") -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| t | table | required |
| row | number | required |
| col | number | required |
| text | string | required |
| textColor | color | none |
| bgColor | color | none |
| align | string | "left" (one of "left", "center", "right") |

First value: bar 0

Writes one cell of the grid `t` at `row` and `col` on this bar, with its own text colour, background and alignment. The text is a `string`; the colours and the alignment are read on the bar, so a cell can change colour with the reading it shows.

```openscript
version 1
study("Average ladder", overlay = true, precision = 2)

ladder = table("Averages", 5, 3, position = "bottomLeft", textColor = silver, bgColor = fade(black, 25))

e9   = ema(close, 9)
e21  = ema(close, 21)
e50  = ema(close, 50)
e200 = ema(close, 200)

// One row of the ladder: labels left, numbers right.
fn row(t, r, name, value) =>
    above = not isNone(value) and close > value
    cell(t, r, 0, name)
    cell(t, r, 1, isNone(value) ? "warming up" : text(value, 2), align = "right")
    cell(t, r, 2, isNone(value) ? "" : (above ? "above" : "below"), textColor = isNone(value) ? silver : (above ? lime : red), align = "right")
    above

if bar.isLast
    // A header: the same background across the row reads as one block.
    header = fade(navy, 40)
    cell(ladder, 0, 0, "Average", textColor = white, bgColor = header)
    cell(ladder, 0, 1, "Value", textColor = white, bgColor = header, align = "right")
    cell(ladder, 0, 2, "Close is", textColor = white, bgColor = header, align = "right")
    row(ladder, 1, "EMA 9", e9)
    row(ladder, 2, "EMA 21", e21)
    row(ladder, 3, "EMA 50", e50)
    row(ladder, 4, "EMA 200", e200)
```

Because only the cells written on the newest bar show, a grid whose number of rows changes from bar to bar needs no clean-up: write the rows that apply and the rest stay blank.

```openscript
version 1
study("Averages under the close", overlay = true, precision = 2)

names  = ["EMA 9", "EMA 21", "EMA 50"]
values = [ema(close, 9), ema(close, 21), ema(close, 50)]

board = table("Close is above", 3, 1, position = "topLeft")

if bar.isLast
    slot = 0
    for i = 0 to size(names) - 1
        if not isNone(element(values, i)) and close > element(values, i)
            cell(board, slot, 0, element(names, i))
            slot += 1
```

**Remarks.** `align` is `"left"`, `"center"` or `"right"`; put labels left and numbers right, so the digits line up. Writing the same cell twice on a bar keeps the last write. `clear()` empties every cell written so far on this bar, for a script that builds its grid in more than one pass and wants to start again. There is no merged cell: for a header across a row, write the text in the first column and give every cell in the row the same `bgColor`. `str.padLeft()` and `str.repeat()` help line text up inside a cell, or draw a small meter from characters.

**See also.** `table()`, `clear()`, `text()`, `str.format()`, [Tables](/script/visuals/tables)

## What a table is not for

| You want | Use | Because |
|---|---|---|
| A value on every bar | `plot()` | A table shows one state, not a history |
| An event on one bar | `signal()` | A marker stays on the bar where it happened |
| A caption on a shape | `draw.label()`, or a box's own text | It belongs beside the thing it describes |
| A regime over a stretch of bars | `background()` | A regime belongs to bars, not to a corner |

## Related

[Tables](/script/visuals/tables), [Visuals overview](/script/visuals/overview), [Plotting](/script/reference/plotting), [Drawing objects](/script/reference/drawing), [Strings](/script/reference/string), [bar.*](/script/reference/bar), [Debugging](/script/writing/debugging).


## Alerts and logging

Source: https://openalgo.in/script/reference/alerts-and-logging

A study computes on every bar, but most of what it finds is only worth something if it reaches a person: a crossover on a five minute SBIN chart, a break of the opening range on a NIFTY future, a value you are debugging. OpenScript (also called OpenAlgo Script) has four calls for that. `alert()` sends a message when a condition holds, `signal()` puts a marker on the bar, `print()` writes a line to the script's log, and `notify()`, which is planned, will send a message to a named channel. This page is the reference for all four.

```openscript
version 1
study("EMA cross, marked and alerted", overlay = true, precision = 2)

fast = ema(close, 9)
slow = ema(close, 21)

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)

if crossUp(fast, slow)
    signal("BUY", color = lime, at = "below", shape = "triangleUp")
    alert(chart.symbol + " fast EMA crossed above slow at " + text(close, 2), id = "cross-up", title = "EMA cross up")
    print("cross up, close " + text(close, 2))

if crossDown(fast, slow)
    signal("SELL", color = red, at = "above", shape = "triangleDown")
    alert(chart.symbol + " fast EMA crossed below slow at " + text(close, 2), id = "cross-down", title = "EMA cross down")
    print("cross down, close " + text(close, 2))
```


## Four calls compared

| | `signal()` | `alert()` | `print()` | `notify()` |
|---|---|---|---|---|
| Produces | A marker on the bar | A message about the bar | A line in the script's log | A message to a named channel |
| On the /trading page | Drawn on the chart | A notification on the page, and a row in the Log tab of the Alerts panel, when it fires; while the market is open it may not fire, as [Alerts](#alerts) explains | Not shown in this release | Planned |
| On the history already loaded | Drawn on every past bar that matched | Fires for none of them | Written for every bar that ran it | Planned |
| On a bar still forming | Waits for the close | Waits for the close | Waits for the close | Planned |

All four return nothing. The three available today may appear anywhere: at the top level, inside an `if` or a loop, or inside a function. The condition is the `if` you write around the call; there is no separate call for declaring one.

## Waiting for the bar to close

On a moving chart the newest bar runs again on every update. By default a `signal()`, an `alert()` and a `print()` on that bar are held back until the bar closes, and if the condition that produced them is no longer true by then, they never happen at all. A price that pokes through a level for ten seconds and falls back has not broken it, and an alert that said it had would be noise.

A study opts out by setting `onUnconfirmed = true` in its [declaration](/script/reference/declarations). The calls then run on every update of the forming bar, the script guards whatever should still wait with `bar.isConfirmed`, and the compiler warns with `OS8002` about every `req.timeframe()` and `req.symbol()` read in the file, because that pair is where repainting comes from. [Realtime and confirmation](/script/language/realtime-and-confirmation) covers the whole rule.

```openscript
version 1
study("Fast alerts", overlay = true, onUnconfirmed = true)

prevHigh = highest(high, 20)[1]

// Fires on every update of a forming bar that trades above the level.
if high > prevHigh
    alert("Traded above the 20 bar high", id = "above-high", frequency = "everyUpdate")

// The marker still waits for the close, because the script says so.
if bar.isConfirmed and close > prevHigh
    signal("CLOSE ABOVE", at = "below", shape = "arrowUp")
```

## Alerts

### alert()

```
alert(message: string, id?: string = "", title?: string = "", frequency?: string = "oncePerBar") -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| message | string | required |
| id | string | "" |
| title | string | "" |
| frequency | string | "oncePerBar" (one of "oncePerBar", "once", "everyUpdate") |

First value: bar 0

Declares a watched condition and the message it sends. The condition is the chain of `if` guards that reaches the call, and the message is evaluated on the bar where they held, so every value in it is that bar's value. The host, the application running the study such as the /trading page, watches it as bars arrive and raises the alert on each new bar where the guards hold; the script polls nothing.

On the /trading page an alert is watched as soon as its study is on the chart, and each firing shows as a notification and is recorded in the Log tab of the Alerts panel. In this release, though, the chart judges a script's alerts once, when a bar first arrives. During market hours a bar arrives with its first tick, before it has closed, so an alert that waits for the close (every alert, unless the study sets `onUnconfirmed = true`) has nothing to report yet, and the chart does not look at that bar again. Such an alert fires only for a bar that reaches the chart already closed. To be told reliably, plot the condition as 1 or 0 and create a study alert on that plot from the chart's **Create alert** dialog, with **Study plot** as the source, as [Alerts on a script condition](/script/alerts/alerts-in-trading#alerts-on-a-script-condition) shows.

```openscript
version 1
study("RSI extremes", precision = 2, range = [0, 100])

len = input(14, "RSI length", min = 2, max = 200)
hi  = input(70, "Overbought", min = 50, max = 100)

r = rsi(close, len)

level(hi, "Overbought", fade(red, 40))
plot(r, "RSI", purple, width = 2)

// The change, not the state: fires on the bar RSI drops back below the line.
if crossDown(r, hi)
    alert(chart.symbol + " " + chart.interval + ": RSI left overbought at " + text(r, 1) + ", close " + text(close, 2) + ", " + date.format(time, "yyyy-MM-dd HH:mm"), id = "rsi-left-high", title = "RSI left overbought")
```

**The message.** `message` is a string built on the bar. `+` joins two strings and nothing else, so a number goes in through `text()`; `"RSI " + r` is error `OS2003`. A value that is `none` makes the whole joined string `none`, while `text(none)` is the string `"none"`, so build a message that cannot come out absent, with `text()` or `orElse()` around the parts that may be missing. On the /trading chart an alert whose message comes out absent shows its `title` instead. `date.format()` writes the bar's time in the chart's timezone, so it matches the time on the axis.

**The id.** `id` is the alert's permanent name. A host keys everything about the alert by it, such as a user's subscription or the record of when it fired. Write it as a short literal, such as `"cross-up"`, and keep it when you edit the script: renaming it makes a different alert. With no `id`, or an `id` taken from an `input()`, the compiler derives a name from the call's position and warns with `OS8008`, because inserting a line above the call would change that name. Two alerts in a file with the same `id` are error `OS3017`. `title` is a heading for people, and you may change it freely. Like `id` and `frequency`, it is fixed before the first bar: write it at the call as a literal, because a title built from bar data, or held in a name, is error `OS3003`.

**Frequency.**

| `frequency` | Fires | Use it for |
|---|---|---|
| `"oncePerBar"` | At most once per bar. The default | Almost everything |
| `"once"` | The first time only, for the life of this study on the chart | A one-off note |
| `"everyUpdate"` | On every run of the forming bar. Requires `onUnconfirmed = true`, or error `OS3009` | Watching a level tick by tick, accepting the noise |

```openscript
if close > open
    alert("Rising", id = "rising", frequency = "everyUpdate")
```

On the /trading chart in this release every alert fires at most once per bar, whatever its `frequency` says: the chart checks each condition once for each new bar and has nowhere to keep the setting. For one alert per session, hold a `var` flag and test it in the condition. The example finds each session's first bar with `session.isFirstBar` where the host states session hours, and with a new IST date where it does not, which includes the /trading chart:

```openscript
version 1
study("First bar break", overlay = true, precision = 2)

// On NSE a new IST date is a new session, so it stands in for
// session.isFirstBar where the host states no session hours.
newSession = orElse(session.isFirstBar, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))

var rangeHigh = none
var alerted   = false

if newSession
    rangeHigh = high
    alerted   = false

// alerted is a var, so this fires at most once per session.
if not newSession and not alerted and close > rangeHigh
    alerted = true
    alert(chart.symbol + " closed above its first bar high at " + text(close, 2), id = "first-bar-break", title = "First bar break")
```

**Remarks.** Adding a study to a chart fires nothing for the bars already loaded: an alert is a statement about now. Nested guards join with `and`, so an `alert()` two `if`s deep has both conditions. A condition that is `none` takes the false branch, so an alert guarded by a comparison stays quiet during warmup; if an alert never fires, plot its condition as `cond ? 1 : 0` and look at the line. If the line shows the condition held and the /trading chart still sent nothing, that is the limit described under the entry above, and the same plotted line is what a study alert can watch. Test a change, with `crossUp()`, `crossDown()` or a comparison with `[1]`, rather than a state, or the alert fires on every bar the state lasts. Routing, retries and where a message is delivered belong to the host; see [Alerts in /trading](/script/alerts/alerts-in-trading).

**See also.** `signal()`, `text()`, `date.format()`, [Alerts from scripts](/script/alerts/overview)

### notify() (planned, not available yet)

```
notify(message: string, channel: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| message | string | required |
| channel | string | required |

First value: bar 0

Will send a message to a channel the platform has already configured, named by `channel`, rather than as an alert. It is planned and not part of version 0.5.0; until it arrives, `alert()` is how a script sends a message.

## Markers

### signal()

```
signal(text: string, color?: color = none, at?: string = "above", shape?: string = "label") -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| text | string | required |
| color | color | none |
| at | string | "above" (one of "above", "below", "price") |
| shape | string | "label" (one of "label", "arrowUp", "arrowDown", "triangleUp", "triangleDown", "circle", "square", "diamond", "cross", "flag") |

First value: bar 0

Puts a named marker on this bar: a label, an arrow, a triangle or another shape, above the bar, below it or on the bar itself. It is how a study shows where a rule fired, and because markers are drawn on every past bar that matched, it is also how you judge the rule over the whole history.

```openscript
version 1
study("Pivot markers", overlay = true, precision = 2)

ph = pivotHigh(high, 5, 5)
pl = pivotLow(low, 5, 5)

// A text of none means no marker, so no if is needed. A pivot is known
// only 5 bars after it forms, so each marker sits on the bar that confirmed
// it, 5 bars right of the pivot itself.
signal(isNone(ph) ? none : "PH " + text(ph, 2), color = red, at = "above", shape = "arrowDown")
signal(isNone(pl) ? none : "PL " + text(pl, 2), color = lime, at = "below", shape = "arrowUp")
```

| `at` | The marker sits |
|---|---|
| `"above"` | Above the bar. The default |
| `"below"` | Below the bar |
| `"price"` | On the bar itself |

`shape` is one of `"label"` (the default), `"arrowUp"`, `"arrowDown"`, `"triangleUp"`, `"triangleDown"`, `"circle"`, `"square"`, `"diamond"`, `"cross"` and `"flag"`.

**Fixed and per-bar arguments.** Only `text` is read on each bar. `color`, `at` and `shape` are part of the marker's declaration and are fixed before the first bar, so each must be written at the call as a literal or an `input()`; a value that depends on bar data, such as `at = up ? "below" : "above"`, is error `OS3003`. Write two calls instead, one per side, and state `at` on every call: the side is never inferred from the marker's text.

**Remarks.** Each call site gives at most one marker per bar, so a loop that signals once per element marks the bar once, with the last text; one mark per element calls for `draw.label()`. Markers are rebuilt from the script on every run, so there is nothing to delete. Like `alert()`, a signal on a forming bar waits for the close unless the study sets `onUnconfirmed = true`. On the /trading chart, `"above"` and `"below"` are measured from the candle, and the marker's text is drawn in its own `color`. Keep the text to a word or two; detail belongs in an alert message or a label's tooltip. To put a mark on the pivot bar itself rather than on the bar that confirmed it, draw it with `draw.label()` at `time[5]`. `text()` of a value still warming up is the string `"none"`, so guard it as the example does.

**See also.** `alert()`, `draw.label()`, `crossUp()`, [Labels and shapes](/script/visuals/labels-and-shapes)

## The log

### print()

```
print(value: any) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| value | any | required |

First value: bar 0

Writes one line to the script's log with the bar's time attached, and draws nothing. It takes a value of any type. Use it for a trace over a range of bars, or to record the first bar where a value goes wrong, when a plot or a table is not the right way to look.

```openscript
version 1
study("Trace a window", precision = 2)

fromBar = input(240, "Trace from bar", min = 0)
toBar   = input(260, "Trace to bar", min = 0)

basis = sma(close, 20)
dev   = stdev(close, 20)

plot(basis, "Basis", orange)

// A window of bars, not every bar: an unguarded print writes a line per bar.
if bar.index >= fromBar and bar.index <= toBar
    print("bar " + text(bar.index, 0) + " " + date.format(time, "yyyy-MM-dd HH:mm") + " close " + text(close, 2) + " basis " + text(basis) + " dev " + text(dev))
```

**Building the line.** To combine words and numbers, build one string. `+` joins strings only, so `"rsi " + r` is error `OS2003`:

```openscript
r = rsi(close, 14)
print("rsi " + r)
```

`text(x)` with one argument accepts any value and writes an absent one as `none`, so a trace line is never lost to absence. `text(x, decimals)` fixes the decimals, but is itself `none` when `x` is, so use it for a number you know is present.

**Remarks.** Like `signal()` and `alert()`, a `print` on a forming bar waits for the bar to close, so you get one line per bar rather than one per update; with `onUnconfirmed = true` every run of the forming bar writes a line, and `and bar.isConfirmed` on the guard brings it back to one. The host limits how fast the log may fill, and a host that drops lines says how many it dropped. The /trading page in this release does not display the script log: the Log tab of its Alerts panel lists alerts that fired, and the console under the script editor shows compiler diagnostics. There, show a value with a `plot()`, a `table()` or a `draw.label()`; an application built on the [JavaScript library](/script/integrate/javascript) receives each printed line with the bar that wrote it.

**See also.** `text()`, `date.format()`, `bar.index`, [Debugging](/script/writing/debugging)

## Related

[Alerts from scripts](/script/alerts/overview), [Alerts in /trading](/script/alerts/alerts-in-trading), [Realtime and confirmation](/script/language/realtime-and-confirmation), [Labels and shapes](/script/visuals/labels-and-shapes), [Debugging](/script/writing/debugging), [Strings](/script/reference/string), [Repainting](/script/data/repainting).


## Requests

Source: https://openalgo.in/script/reference/request

A study sees the bars of its own chart. The `req` functions let it see further: a daily value on a five minute chart, the NIFTY index beside a stock, two option legs added into one premium. `req.timeframe()` reads an expression computed on a coarser interval of the chart's own instrument, `req.symbol()` reads one computed on another instrument, and `req.isReady()` and `req.error()` tell the script whether the answer has arrived. Two more, `req.candle()` and `req.events()`, are planned. This page is the reference for all six in OpenScript (also called OpenAlgo Script).

```openscript
version 1
study("Relative strength", precision = 4)

indexName     = input("NIFTY", "Index")
indexExchange = input("NSE_INDEX", "Index exchange")

// The index's close at the chart's own interval. "developing" pairs each
// chart bar with the index bar at the same time (see The mode, below).
idx = req.symbol(indexName, chart.interval, close, exchange = indexExchange, mode = "developing")

// Absent until the index answers, so the line simply starts then.
ratio = close / idx

plot(ratio, "Close over index", aqua, width = 2)
plot(sma(ratio, 20), "Average of the ratio", fade(silver, 40))

// Red if the read failed, grey while it is still loading.
background(req.error(idx) != "" ? fade(red, 90) : (req.isReady(idx) ? none : fade(gray, 94)))
```

## How a read works

A read takes an expression and computes it as a program of its own over the requested bars. Inside the expression, `open`, `high`, `low`, `close`, `volume` and `time` are the requested instrument's, at the requested interval, and every indicator reads those bars: `req.timeframe("1D", ema(close, 20))` is a 20 day average of daily closes, not a longer average of five minute closes. The result is then lined up with the chart's bars by timestamp, one value per chart bar, and it has the type of the expression: a number, a `bool` for a comparison, a `string` for text.

History counts differently inside and outside the read:

| Expression | `[1]` counts | On a 5 minute chart at 11:20 it holds |
|---|---|---|
| `req.timeframe("1D", high)` | | Yesterday's high: the last day that closed |
| `req.timeframe("1D", high[1])` | Days | The high of the day before yesterday |
| `req.timeframe("1D", high, mode = "developing")` | | Today's high so far |
| `req.timeframe("1D", high)[1]` | Chart bars | Yesterday's high again: what the read held at 11:15 |


Inside the expression you may write literals and `input()` calls, and read a name from the rest of the file only when that name holds an `input()`. Any other name is error `OS6003`, because a name computed on the chart's own bars has no meaning on the requested bars. That includes a name that holds a plain number, such as `n = 20`: write the `20` inside the expression instead. A `var` that starts from an input counts as computed too, because a later line may change it.

```openscript
myAtr = atr(14)
wide  = req.timeframe("1D", high - low > myAtr)
plot(wide ? 1 : 0, "Wide day")
```

A read computes a value; it does not act or draw. An order call inside the expression is error `OS7003`, and a drawing, marker or alert call is error `OS3006`. The symbol and the timeframe are part of what the host, the application running the script such as the /trading page, fetches, so they too are fixed before the first bar: write each as a literal or an `input()`, because one that depends on bar data is error `OS3003`.

## The mode

**`mode` decides what a read is allowed to know on a bar where the coarser bar has not finished.** It is the argument that makes a repainting study impossible to write by accident, because the default never repaints and the other two have to be written out.

| `mode` | On a chart bar inside an unfinished coarse bar, it returns | Repaints | First value |
|---|---|---|---|
| `"confirmed"` | The last coarse bar that closed, held until the next one closes. The default | Never | Once the first coarse bar has closed |
| `"developing"` | The coarse bar so far | On the newest bars, while the coarse bar forms | Once the first coarse bar has begun |
| `"lookahead"` | The coarse bar's final value | On history, always | Wherever the coarse bar exists |

On a five minute chart reading `req.timeframe("1D", high)`, a bar at 11:00 today holds yesterday's high in `"confirmed"`, today's high so far in `"developing"`, and today's final high in `"lookahead"`, which no one could have known at 09:15. Only the first is a number a rule could have acted on at 11:00, so for a timeframe coarser than the chart's it is the only mode a signal, an alert or an order should be built on. A backtest of a rule that reads `"lookahead"` looks excellent and means nothing.

### Reading at the chart's own interval

A read at the chart's own interval, usually of another instrument, follows the same rule, and the result surprises people. On a five minute chart, the other instrument's 11:00 bar has not closed when the chart's 11:00 bar opens, so a `"confirmed"` read gives the 11:00 chart bar the other instrument's 10:55 bar: every value is one bar behind. Where the other instrument has no bar at all, a `"confirmed"` read keeps holding an older value rather than going absent.

To pair each chart bar with the other instrument's bar at the same time, as a ratio, a spread or a combined premium needs, write `mode = "developing"`. On history each chart bar then gets the other instrument's bar at the same time, a chart bar with no counterpart is absent, and only the value on the newest bar can change, until that bar closes, just as the chart's own `close` does.

| On a five minute chart, at the 11:00 bar | `mode = "confirmed"` | `mode = "developing"` |
|---|---|---|
| The other instrument traded at 11:00 | Its 10:55 bar | Its 11:00 bar |
| The other instrument has no 11:00 bar | An older bar, held | Absent |

### Warnings

The compiler warns about a `"lookahead"` read with `OS8005`. A `"developing"` read carries no warning: the mode written on the line is its disclosure. A file that sets `onUnconfirmed = true` gets warning `OS8002` on every read in the file, whatever its mode or interval, because a forming chart bar that reads another bar still forming can be withdrawn twice over. [Repainting](/script/data/repainting) covers the subject in full.

## Timeframes

A timeframe is a count and a unit, written as a string. The unit letters are case sensitive.

| Written | Means |
|---|---|
| `"1m"`, `"5m"`, `"15m"` | Minutes |
| `"1h"`, `"4h"` | Hours |
| `"1D"` | One day |
| `"1W"` | One week |
| `"1M"` | One month. `"1m"` is one minute |
| `"60"` | A bare number is minutes, so this is `"1h"` |

Take a timeframe from the reader with `input("1D", "Timeframe", kind = "interval")`. A string the language does not recognise is error `OS6001`. Two more rules depend on the chart, so they are checked when the study loads, and a study that breaks either is refused before its first bar: a timeframe finer than the chart's is `OS6002`, because a daily chart does not contain the five minute bars that made it, and an intraday timeframe that is not a whole multiple of the chart's, such as `"45m"` on a `"30m"` chart, is `OS6015`. Day, week and month reads are grouped by the calendar in the instrument's timezone and are exempt from the multiple rule. [Timeframes](/script/data/timeframes) covers intervals in full.

## When a read fails

A read that cannot be answered is never an empty series, which would look exactly like an instrument that did not trade. It stays absent on every bar, `req.isReady()` stays false, and `req.error()` returns a sentence naming the problem. The rest of the study keeps drawing.

| Code | When |
|---|---|
| `OS6007` | The host does not know the symbol on that exchange |
| `OS6008` | The instrument resolved, but has no bars over the chart's range |
| `OS6009` | The data source refused or did not answer; the sentence carries the host's own reason |
| `OS6014` | The feed does not store that interval for the instrument; the sentence lists the intervals it has |
| `OS6012` | A day, week or month read, and the host stated no timezone for the chart's instrument |

A few problems stop the whole study instead, when it loads and before its first bar, so nothing is drawn:

| Code | When |
|---|---|
| `OS6002` | A timeframe finer than the chart's |
| `OS6015` | An intraday timeframe that is not a whole multiple of the chart's |
| `OS6006` | The host cannot serve reads of other instruments at all |
| `OS5006` | The file makes more reads than the host allows |

Each distinct symbol, exchange and timeframe is one series the host fetches and keeps in step with the chart, so make one read per series, give it a name and reuse the name. Arithmetic on reads you already have is free: the midpoint of yesterday's range is `(prevHigh + prevLow) / 2`, not a third read.

## Reads in /trading

| Where the script runs | `req.timeframe()` | `req.symbol()` |
|---|---|---|
| On the chart, as a study | Folded from the chart's bars. Day, week and month reads use the chart's timezone, Asia/Kolkata unless changed in the chart settings | Fetched through the chart's own data feed |
| In the Backtest panel | Intraday reads work. Day, week and month reads are absent on every bar, because the run is not told the chart's timezone | The run is refused before its first bar with `OS6006` |

The /trading chart does not tell a study its instrument's exchange, so `chart.exchange` is `none` there. A `req.symbol()` call that names no `exchange` is fetched on the chart's own exchange, which suits a peer stock on the same exchange; name `exchange` whenever the other instrument trades elsewhere, such as `NIFTY` on `NSE_INDEX` from an NSE stock chart.

/trading calls its daily interval `D`, which is not a timeframe the language reads, so on a daily chart a read that passes `chart.interval` stops the study with `OS6001`. Write `"1D"` there instead.

## Reading another timeframe

### req.timeframe()

```
req.timeframe(timeframe: string, expr: T, mode?: string = "confirmed") -> T
```

| Parameter | Type | Default |
|---|---|---|
| timeframe | string | required |
| expr | T | required |
| mode | string | "confirmed" (one of "confirmed", "developing", "lookahead") |

First value: first bar the mode allows, section 15.3

Reads an expression computed on a coarser interval of the chart's own instrument, and returns its value on each chart bar. Use it for a daily trend filter on an intraday chart, yesterday's high and low as levels, or the hourly RSI beside the five minute one.

```openscript
version 1
study("Previous day levels", overlay = true, precision = 2)

// "confirmed" is the default: these are yesterday's numbers, and they never repaint.
prevHigh  = req.timeframe("1D", high)
prevLow   = req.timeframe("1D", low)
prevClose = req.timeframe("1D", close)

// Derived, not a fourth read.
prevMid = (prevHigh + prevLow) / 2

pHigh = plot(prevHigh, "Previous high", aqua, width = 2, style = "step")
pLow  = plot(prevLow, "Previous low", orange, width = 2, style = "step")
plot(prevClose, "Previous close", silver, style = "step")
plot(prevMid, "Previous mid", fade(silver, 50), style = "step")
fill(pHigh, pLow, color = aqua, opacity = 0.05)
```

A picture of the finished daily candle, drawn across history, is the one honest use of `"lookahead"`. The compiler warns on each read, and the study must never be used for a decision:

```openscript
version 1
study("Daily candle", overlay = true)

o = req.timeframe("1D", open,  mode = "lookahead")
h = req.timeframe("1D", high,  mode = "lookahead")
l = req.timeframe("1D", low,   mode = "lookahead")
c = req.timeframe("1D", close, mode = "lookahead")

plotCandles(o, h, l, c, "Daily", colorUp = fade(lime, 60), colorDown = fade(red, 60))
```

**Remarks.** A `"confirmed"` read has no value until the first coarse bar has closed, plus the warmup of its expression: `req.timeframe("1D", ema(close, 20))` needs twenty closed days, so on an intraday chart its line starts about a month in. Draw a value that changes once per coarse bar with `style = "step"`. The mode must be written as a literal: an `input()` there is error `OS3003`, so no one can change how honest the study is from its settings dialog. A comparison with a read is `none` during warmup, and `none` takes the false branch of an `if`; write `not isNone(bias) and close > bias` when the difference between "no" and "not known yet" matters to you.

**See also.** `req.symbol()`, `plotCandles()`, `input()`, [Higher timeframes](/script/data/higher-timeframes), [Repainting](/script/data/repainting)

## Reading another instrument

### req.symbol()

```
req.symbol(symbol: string, timeframe: string, expr: T, exchange?: string = chart.exchange, mode?: string = "confirmed") -> T
```

| Parameter | Type | Default |
|---|---|---|
| symbol | string | required |
| timeframe | string | required |
| expr | T | required |
| exchange | string | chart.exchange |
| mode | string | "confirmed" (one of "confirmed", "developing", "lookahead") |

First value: as above, plus the host's answer

Reads an expression computed on another instrument, at the timeframe you name, and returns its value on each chart bar. Use it to measure a stock against the NIFTY index, a future against its spot, or to add two option legs into one premium. When you pair bars at the chart's own interval, pass `mode = "developing"`, as the examples here do; [Reading at the chart's own interval](#reading-at-the-chart-s-own-interval) explains why. Symbols are written as OpenAlgo writes them: `SBIN` on `NSE`, `NIFTY` on `NSE_INDEX`, a NIFTY future or option contract on `NFO`.

```openscript
version 1
study("Combined premium", precision = 2)

// Type the two contract symbols into the study's settings.
callLeg = input("", "Call leg")
putLeg  = input("", "Put leg")
legs    = input("NFO", "Legs exchange")
lots    = input(1, "Lots", min = 1, max = 100)
// chart.lotSize describes the chart's instrument, not the legs: enter the
// contract's current lot size here.
lotSize = input(1, "Units in one lot", min = 1)

callPrice = req.symbol(callLeg, chart.interval, close, exchange = legs, mode = "developing")
putPrice  = req.symbol(putLeg, chart.interval, close, exchange = legs, mode = "developing")

// If either leg has no bar at this instant, the sum is none, not half a position.
premium = callPrice + putPrice

plot(premium, "Combined premium", orange, width = 2)
plot(premium * lots * lotSize, "Position value", aqua, scale = "left")
```

**Remarks.** The read is absent until the host has fetched the other instrument's bars; when they arrive the study is calculated again over its whole history, so a line that appears a moment late did not repaint. With `mode = "developing"`, a chart bar with no counterpart, because the other instrument did not trade then, keeps different hours or had a holiday, is absent, and arithmetic with it is absent too. Hold a last value deliberately with `valueWhen()` if that is what you want, never with `orElse(read, 0)`, which would draw a zero price. `chart.lotSize`, `chart.tickSize` and the rest of `chart.*` describe the chart's instrument, never the one you read, so take the other instrument's lot size as an input and check the current one for your contract. `exchange` defaults to `chart.exchange`; on /trading, see [Reads in /trading](#reads-in-trading). The `mode` argument works exactly as in `req.timeframe()`. A dedicated instrument picker for inputs is planned; until then the symbol is a text input.

**See also.** `req.isReady()`, `req.error()`, `req.timeframe()`, [Other instruments](/script/data/other-instruments)

## Request status

### req.isReady()

```
req.isReady(read: any) -> series bool
```

| Parameter | Type | Default |
|---|---|---|
| read | any | required |

First value: bar 0

True once the host has answered the read you pass in, and false while it is still being fetched. Pass the name you assigned the read to. Use it to show the reader that a study is loading, rather than leaving an empty pane that looks broken.

```openscript
version 1
study("Loading shade", precision = 2)

peer = req.symbol("RELIANCE", chart.interval, close, exchange = "NSE", mode = "developing")

plot(close / peer, "Close over RELIANCE", aqua)

// Grey until the other instrument's bars have arrived.
background(req.isReady(peer) ? none : fade(gray, 94))
```

**Remarks.** Ready means answered, not present. A `req.timeframe()` read of the chart's own bars is ready from the first bar, even while its value is still `none` during warmup, so test `isNone()` for the value and `req.isReady` for the answer. When a read fails, `req.error()` says why: test it first, as the example under that entry does.

**See also.** `req.error()`, `req.symbol()`, `isNone()`

### req.error()

```
req.error(read: any) -> series string
```

| Parameter | Type | Default |
|---|---|---|
| read | any | required |

First value: bar 0

The reason a read failed, as a sentence, or `""` when nothing went wrong, including while the read is still loading. Use it to tell the reader what to fix: a misspelt symbol, a missing interval, an unreachable data source.

```openscript
version 1
study("Peer status", precision = 2)

peerName     = input("RELIANCE", "Compare with")
peerExchange = input("NSE", "Its exchange")

peer = req.symbol(peerName, chart.interval, close, exchange = peerExchange, mode = "developing")
why  = req.error(peer)

plot(close / peer, "Close over peer", aqua)

panel = table("Status", 1, 2, position = "topRight", textColor = silver)
if bar.isLast
    cell(panel, 0, 0, peerName)
    cell(panel, 0, 1, why != "" ? why : (req.isReady(peer) ? "ready" : "loading"), textColor = why != "" ? red : (req.isReady(peer) ? lime : silver))
```

**Remarks.** The sentence names the problem, for example "The host does not know NOPE on NSE." for an unknown symbol (`OS6007`), or "The host did not supply a timezone for SBIN." for a daily read with no timezone (`OS6012`). Where the host gave its own reason, the sentence carries it. The code itself is not part of the string; the table under [When a read fails](#when-a-read-fails) lists the codes. A failed read leaves only itself absent: everything in the study that does not depend on it keeps drawing, which is why a comparison study should keep its own instrument's plots out of the read.

**See also.** `req.isReady()`, `table()`, [Other instruments](/script/data/other-instruments)

## Planned

### req.candle() (planned, not available yet)

```
req.candle(timeframe: string, mode?: string) -> array<number>
```

| Parameter | Type | Default |
|---|---|---|
| timeframe | string | required |
| mode | string | optional |

First value: as above

Will return a whole coarser bar at once, its open, high, low and close as one array, so a higher timeframe candle can be drawn with one read instead of four. It is planned and not part of version 0.5.0; until then, make one `req.timeframe()` read per price and pass them to `plotCandles()`.

### req.events() (planned, not available yet)

```
req.events(kind: string) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| kind | string | required |

First value: host dependent

Will read scheduled corporate events for the chart's instrument, such as dividends and splits, as a series. It is planned and not part of version 0.5.0.

## Related

[Higher timeframes](/script/data/higher-timeframes), [Other instruments](/script/data/other-instruments), [Repainting](/script/data/repainting), [Timeframes](/script/data/timeframes), [chart.*](/script/reference/chart), [Absent values](/script/language/absent-values), [Backtesting](/script/strategies/backtesting).


## Strategy orders

Source: https://openalgo.in/script/reference/strategy

This page documents the six calls a strategy uses to trade: `buy()` and `sell()` to enter, `close()` to flatten, `exit()` to attach a stop and a target, and `cancel()` and `cancelAll()` to withdraw orders that have not filled. Most OpenScript strategies (OpenScript is also called OpenAlgo Script) need nothing else. All six compile and run in version 0.5.0; the one with a caveat is `exit()`, whose levels are not filled yet, as its entry explains.

Two words recur below. A **fill** is the execution of an order at a price, reported back by the order's **destination**, whatever receives it: the simulated venue of a backtest or of the chart, or OpenAlgo for a deployed strategy. A **resting** order is a limit or stop order that waits for the market to reach its price, and it is **working** until it fills or is cancelled.

All six work only in a file declared with `strategy()`. In a `study()` file the compiler refuses them with OS7001 and names the declaration to change. The general forms in the `order` namespace, `order.place()`, `order.reverse()` and `order.bracket()`, are on the [order.* page](/script/reference/orders).

## A complete example

A breakout strategy for 15-minute bars on an NSE stock or an NFO future. While the trend is up it rests a buy stop order just above the high of the last 20 bars and sends a protective stop with it. It withdraws the order if the trend turns before it fills. From the first bar that opens at 15:00, it withdraws anything still working and closes any position.

```openscript
version 1
strategy("Stop entry with a bracket", overlay = true, precision = 2,
         capital = 500000, qty = 1, product = "intraday",
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

length   = input(20,  "Breakout lookback", min = 2, max = 500)
stopMult = input(2.0, "Stop, in ATR", min = 0.2, max = 20)

atrValue  = atr(14)
rangeHigh = highest(high, length)[1]
trendUp   = close > ema(close, 50)

// Every price an order sees is on a tick and tested for absence first.
trigger   = isNone(rangeHigh) ? none : roundToTick(rangeHigh)
stopPrice = isNone(trigger) ? none : roundToTick(trigger - stopMult * atrValue)
ready     = not isNone(trigger) and not isNone(stopPrice)

// The zone is written out so the windows also work in the Backtest panel.
inHours = session.isIn("0930-1430", "Asia/Kolkata")
lateDay = not session.isIn("0915-1500", "Asia/Kolkata")

// The script remembers its own resting order and the stop it sent.
var working   = false
var entryStop = none

if not pos.isFlat
    working = false
if pos.isFlat and not working
    entryStop = none

// One chain, so no two orders from this script can go out on the same bar.
if lateDay and working
    cancelAll()
    working = false
else if pos.isLong and (lateDay or low <= entryStop)
    close()
else if working and not trendUp
    cancel("breakout")
    working = false
else if ready and inHours and trendUp and pos.isFlat and not working
    entryStop = stopPrice
    buy(stop = trigger, tag = "breakout")
    exit(tag = "breakout", stop = entryStop)
    working = true

plot(trigger, "Entry trigger", aqua, style = "step")
plot(entryStop, "Protective stop", red, style = "step")
plot(pos.isFlat ? none : pos.avgPrice, "Average price", fade(silver, 40), style = "step")
```

Three things in it are there because of how version 0.5.0 behaves:

- **The stop is also tested by the script** (`low <= entryStop`). The stop sent with `exit()` is handed on as an instruction, and neither the backtest nor the chart fills it, so without the script's own test no trade in a backtest would ever stop out.
- **The time windows name their zone**, `"Asia/Kolkata"`. The Backtest panel does not pass the chart's time zone to a run, so `session.isIn()` with no zone has no value on any bar of a backtest, and a strategy guarded by it never trades. [Backtesting](/script/strategies/backtesting) lists the other things the panel does not supply yet.
- **It is for the chart and the Backtest panel.** The Strategies panel refuses to start a strategy that calls `exit()`, and one that reads the clock with `session.isIn()` on an Indian instrument. [Sessions and time](/script/data/sessions-and-time#sessions-and-the-clock-in-trading-today) shows a time window that works there too.

The fills of a strategy are marked on the chart where they happened, as in this run of another strategy on a 15-minute NSE chart:


## What every order call shares

| Rule | What it means for you |
|---|---|
| Nothing is sent at the call | The call records a request. The request is applied at the end of the bar, and only once the bar is confirmed, meaning its interval has ended (unless the declaration sets `onUnconfirmed = true`), so a condition that was true halfway through a bar and false at its close places nothing. That is also why every order call returns nothing: there is no order yet to hand back. |
| Position facts follow fills | `pos.size` and the rest change when a fill arrives, not when you call `buy()`. With the default `fillOn = "nextOpen"`, a market order decided on one bar fills at the next bar's open, and the position shows from that bar. |
| Left out is not absent | An argument you leave out takes its default: `buy()` uses the declaration's `qty` and, with no price, is a market order. An argument you write whose value comes out [absent](/script/language/absent-values) (`none`, no value on this bar) is refused with OS7002, naming the argument. |
| A refusal stops the run | A refused order stops the script on the bar it happened, on the chart and in a backtest alike. Nothing that bar decided is sent, including orders from lines that ran before the refused one, and no later bar runs. |
| No opposite orders on one bar | A buy and a sell decided on the same bar are refused with OS7013. A `close()` of a long position is a sell, so `close()` and `buy()` on one bar are refused as well (with OS7008 first, when the buy would also exceed `pyramiding`). Write your conditions as one `if` and `else if` chain. Two orders on the same side are ordinary. |
| One leg | A file in version 0.5.0 trades exactly one instrument, the one on its chart. The `leg` argument that `buy()`, `sell()`, `close()` and `exit()` accept is for the planned [multi-leg strategies](/script/reference/legs), and writing it today is refused with OS3023. |

**Whether a tag must name something is written in its default.** A tag is a name you give an order. A tag that defaults to `""` is a **label**: it rides along to the destination and to the trade list, and it names nothing that has to exist. `buy()`, `sell()` and `exit()` take labels. A tag that is required, or that defaults to `none`, is a **reference**: it names orders the strategy has already placed. `cancel()` requires one and `close()` defaults to `none`, and a tag there that names no order is a mistake the language reports. [Tags: naming an order](/script/strategies/orders#tags-naming-an-order) explains the habit of giving every order a tag.

```openscript
version 1
strategy("A leg that does not exist", overlay = true, qty = 1)

goLong = crossUp(ema(close, 9), ema(close, 21))

if goLong and pos.isFlat
    buy(leg = "main")
```

## Entering

`buy()` and `sell()` share one shape. The kind of order is decided by which prices you pass:

| `limit` | `stop` | Order | Fills when |
|---|---|---|---|
| left out | left out | Market | At the next fill point `fillOn` names |
| given | left out | Limit | Price trades at the limit or better |
| left out | given | Stop | Price trades through the trigger |
| given | given | Stop-limit | Price trades through the trigger, then the order rests as a limit |

Every price must fall on a tick of the instrument, or the order is refused with OS7006. Round with `roundToTick()`, which is absent when the host has stated no tick size, and test the result before you use it.

A backtest decides resting orders against the bar's open, high, low and close, and it decides against the strategy where the bar cannot say: a limit fills only when price trades beyond it (touching it is not enough), and a stop that the bar opens beyond fills at the open, not at the trigger. [Costs and fills](/script/strategies/costs-and-fills) has the details.

### buy()

```
buy(qty?: number, limit?: number = none, stop?: number = none, tag?: string = "", leg?: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| qty | number | optional |
| limit | number | none |
| stop | number | none |
| tag | string | "" |
| leg | string | optional |

First value: bar 0

Enters or adds to a long position. Without a price it is a market order; with `limit`, `stop` or both it rests until the market reaches it. Leave `qty` out to use the declaration's `qty`, counted in the declaration's `qtyType`.

```openscript
version 1
strategy("Buy two lots on a cross", overlay = true, qty = 1)

lots = input(2, "Lots", min = 1, max = 50)

// chart.lotSize is absent, not 1, when the host states no lot size.
lotUnits = max(orElse(chart.lotSize, 1), 1)

fast   = ema(close, 9)
slow   = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy(qty = lots * lotUnits, tag = "entry")
else if goFlat and pos.isLong
    close(tag = "entry")
```

**Remarks.** `buy` adds to the position; it does not replace a working `buy`. A new call while an earlier resting order is still working leaves two orders working, and if both fill you hold both. Guard market entries on `pos.isFlat`, and remember a resting order yourself in a [`var`](/script/language/persistence) (a variable that keeps its value from one bar to the next) until `order.working()` lands.

The declaration's `pyramiding` (default `1`) caps how many entries one direction may hold. An entry placed while the strategy already holds that many filled entries on that side is refused with OS7008; orders still waiting to fill are not counted. A quantity of zero or below is refused with OS7004, because the direction comes from the function you call and never from the sign of the quantity.

**See also.** `sell()`, `close()`, `exit()`, `order.place()`, `pos.isFlat`

### sell()

```
sell(qty?: number, limit?: number = none, stop?: number = none, tag?: string = "", leg?: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| qty | number | optional |
| limit | number | none |
| stop | number | none |
| tag | string | "" |
| leg | string | optional |

First value: bar 0

Enters or adds to a short position, or reduces a long one. `sell` means "subtract from the position": against a long it closes the long first, and a quantity larger than the long carries on into a short. To flatten, use `close()`, which never goes past zero.

```openscript
version 1
strategy("Short below the trend", overlay = true, precision = 2, qty = 1)

fast     = ema(close, 9)
slow     = ema(close, 21)
goShort  = crossDown(fast, slow)
coverNow = crossUp(fast, slow)

if goShort and pos.isFlat
    sell(tag = "short")
else if coverNow and pos.isShort
    close(tag = "short")

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
```

**Remarks.** No single order crosses zero. When a `sell` would take a long position through zero, it is sent as two orders: one that closes the long and one that opens the short, each carrying its own position reference, so a late fill can say which position it belongs to. Under `qtyType = "units"` the split is exact: long 5, `sell(qty = 8)` sends a sell of 5 and a sell of 3. The declaration's `product` (`"intraday"` or `"overnight"`) travels with every order; whether an account may carry a short position overnight is for the destination to decide, not the language.

**See also.** `buy()`, `close()`, `order.reverse()`, `pos.isShort`

## Exiting

### close()

```
close(tag?: string = none, qty?: number = none, leg?: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | none |
| qty | number | none |
| leg | string | optional |

First value: bar 0

Flattens the position, or the part of it one tag entered. With no arguments it closes whatever is held, long or short. With `qty` it closes that many and leaves the rest. With `tag` it closes only the part that orders placed with that tag opened.

```openscript
version 1
strategy("Core and runner", overlay = true, precision = 2,
         qty = 1, pyramiding = 2)

fast     = ema(close, 9)
slow     = ema(close, 21)
goLong   = crossUp(fast, slow)
goFlat   = crossDown(fast, slow)
atrValue = atr(14)

// Absent while flat, because pos.avgPrice is.
target = pos.isFlat ? none : pos.avgPrice + 3 * atrValue

if goLong and pos.isFlat
    buy(qty = 2, tag = "core")
    buy(qty = 1, tag = "runner")
else if goFlat and pos.isLong
    close()
else if pos.isLong and high >= target
    // Safe on every bar: once the core is off, this sends nothing.
    close(tag = "core")

plot(target, "Target for the core", lime, style = "step")
```

**Remarks.** A close is measured against what is left to close: what has filled, less everything already working against it. Two bare closes on one bar send one order between them, and a close on the bar after one the destination has not answered sends nothing, so `close()` under `if pos.isLong` never sends the position twice. A close on a flat position, or on a tag whose part has already been closed, sends nothing and says nothing.

The same rule covers a resting order that reduces the position. While a sell stop for the whole position waits under a long, the position is already spoken for and `close()` sends nothing. To get out early, `cancel()` the resting order and close on the next bar, once the cancellation has been confirmed.

When the declaration counts in units (the default), a quantity you write may not exceed what is left: `close(qty = 5)` against a position of 3 is refused with OS7017, naming what you asked for and what is left, because it would flatten the position and open the opposite one under a call named close. A tag that no order in the file is placed with is refused before the first bar with OS7016, which almost always means a typo.

Under `qtyType = "lots"` the 0.5.0 backtest converts the size of a bare or tagged `close()` from lots a second time, so it sells far more than the position holds. Count in units and size from `chart.lotSize`, as [Where a size comes from](/script/strategies/position-and-sizing#where-a-size-comes-from) explains.

```openscript
version 1
strategy("A typo in a tag", overlay = true, qty = 1)

goLong = crossUp(ema(close, 9), ema(close, 21))
goFlat = crossDown(ema(close, 9), ema(close, 21))

if goLong and pos.isFlat
    buy(tag = "entry")
else if goFlat and pos.isLong
    close(tag = "entyr")
```

`close` read bare, without brackets, is the bar's closing price, `close`. The compiler tells the two apart by the brackets.

**See also.** `sell()`, `exit()`, `order.reverse()`, `pos.size`

### exit()

```
exit(tag?: string = "", qty?: number = none, limit?: number = none, stop?: number = none, profit?: number = none, loss?: number = none, leg?: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | "" |
| qty | number | none |
| limit | number | none |
| stop | number | none |
| profit | number | none |
| loss | number | none |
| leg | string | optional |

First value: bar 0

Sets the position's protective stop and profit target. Give the levels as prices (`stop`, `limit`) or as distances from the entry price in the instrument's own price units (`loss`, `profit`). A position carries at most one stop and one target, and calling `exit` again replaces them. A stop and a target sent together like this are called a **bracket**.

In version 0.5.0 the levels are not filled: the chart and the Backtest panel hand them on and never act on them, and the Strategies panel refuses to start a strategy that calls `exit`. So this example sends the levels with the entry and also tests them itself, which is what closes its trades today:

```openscript
version 1
strategy("Stop and target at entry", overlay = true, precision = 2, qty = 1)

atrValue = atr(14)
goLong   = crossUp(ema(close, 9), ema(close, 21))

stopPrice   = roundToTick(close - 2 * atrValue)
targetPrice = roundToTick(close + 4 * atrValue)
ready       = not isNone(stopPrice) and not isNone(targetPrice)

// The levels sent with the entry, kept so the script can test them as well.
var stopLevel   = none
var targetLevel = none

if goLong and pos.isFlat and ready
    buy(tag = "entry")
    exit(tag = "entry", stop = stopPrice, limit = targetPrice)
    stopLevel   = stopPrice
    targetLevel = targetPrice
else if pos.isLong and (low <= stopLevel or high >= targetLevel)
    close()

plot(pos.isFlat ? none : stopLevel, "Stop", red, style = "step")
plot(pos.isFlat ? none : targetLevel, "Target", lime, style = "step")
```

The distance form suits a market entry, whose fill price your script does not know yet. The levels travel as distances and are measured from the fill:

```openscript
atrValue = atr(14)

if crossUp(ema(close, 9), ema(close, 21)) and pos.isFlat and not isNone(atrValue)
    buy(tag = "entry")
    exit(tag = "entry", loss = 2 * atrValue, profit = 4 * atrValue)
```

**Remarks.** Four rules apply:

- **One side, one form.** A price and a distance for the same side, such as `stop` with `loss`, is refused before the first bar with OS3010. A stop as a price and a target as a distance is fine.
- **An absent level is refused.** `exit` is an order call, so a level whose value is absent is OS7002. The whole bar's orders go with it, including an entry placed on the line above, so test levels before you use them, as both examples do.
- **The right side of the position.** A stop belongs below a long and above a short, a target the other way round. A level on the wrong side of an open position's average price is refused with OS7010. It is checked only against a position that is open, so an entry and its bracket on the same bar are the ordinary shape.
- **The tag is a label.** It tells the destination which entry the levels protect. A tag that matches no order is not refused.

There is no trailing stop argument: the language's trailing stop is `leg.trail()`, which is planned. [Exits and brackets](/script/strategies/exits-and-brackets) covers exits in full, including a trailing stop written in the script.

**See also.** `order.bracket()`, `close()`, `leg.stop()`, `leg.target()`

## Cancelling

### cancel()

```
cancel(tag: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | required |

First value: bar 0

Cancels the working order placed with a tag, before it fills. If more than one working order carries the tag, all of them are cancelled. Use it for a resting limit or stop order that has gone stale, or to replace an order at a new price.

```openscript
version 1
strategy("Stale bid", overlay = true, precision = 2, qty = 1)

waitBars = input(3, "Cancel the bid after this many bars", min = 1, max = 50)

wanted  = close - atr(14)
bid     = isNone(wanted) ? none : roundToTick(wanted)
upTrend = close > ema(close, 50)

// The bar the bid was placed on, none while nothing rests.
var placedAt = none
if not pos.isFlat
    placedAt = none

stale = not isNone(placedAt) and bar.index - placedAt >= waitBars

if stale
    cancel("bid")
    placedAt = none
else if upTrend and pos.isFlat and isNone(placedAt) and not isNone(bid)
    buy(limit = bid, tag = "bid")
    placedAt = bar.index
else if pos.isLong and not upTrend
    close()
```

**Remarks.** The tag is a reference: cancelling a tag with no working order, because its order has already filled or was never placed, is refused with OS7009 and stops the run. Clear your own record of a working order when the position opens, as the example does, so the script never cancels an order that has already become a position.

An order and its cancellation may be decided on the same bar. To replace an order, call `cancel` before you place the replacement, or give the replacement a new tag: in a backtest a cancellation withdraws every working order carrying its tag, including one placed earlier on the same bar.

A fill can race a cancellation at a real exchange. When it does, the fill is still counted in the position, and the strategy holds what traded. `cancel` never closes a position.

**See also.** `cancelAll()`, `order.working()`, `order.modify()`

### cancelAll()

```
cancelAll() -> nothing
```

First value: bar 0

Cancels every working order this strategy placed. Reach for it when the script no longer wants anything resting: the session ending, or a risk switch turned off in the settings.

```openscript
version 1
strategy("Nothing working after 15:00", overlay = true, precision = 2, qty = 1)

enabled   = input(true, "Trading enabled")
lateDay   = not session.isIn("0915-1500", "Asia/Kolkata")
standDown = lateDay or not enabled

rangeHigh = highest(high, 20)[1]
trigger   = isNone(rangeHigh) ? none : roundToTick(rangeHigh)

var working = false
if not pos.isFlat
    working = false

if standDown and working
    cancelAll()
    working = false
else if standDown and pos.isLong
    close()
else if not standDown and pos.isFlat and not working and not isNone(trigger)
    buy(stop = trigger, tag = "breakout")
    working = true
```

**Remarks.** `cancelAll` refuses nothing, so it is safe to call when nothing is working. It cancels orders only and never closes a position: an order that has filled is not working any more.

To cancel everything and be flat, pair it with `close()`. The two may run on the same bar, with one exception: a working order that reduces the position, such as a sell stop under a long, counts as already on its way out until its cancellation is confirmed, so a `close()` on the same bar sends nothing for that part. Close on the next bar in that case.

**See also.** `cancel()`, `close()`, `order.pending`

## Related

[Orders](/script/strategies/orders), [Exits and brackets](/script/strategies/exits-and-brackets), [Position and sizing](/script/strategies/position-and-sizing), [Overview](/script/strategies/overview), [order.*](/script/reference/orders), [pos.*](/script/reference/position), [Declarations](/script/reference/declarations), [Glossary](/script/resources/glossary).


## pos.*

Source: https://openalgo.in/script/reference/position

The `pos` namespace is how a strategy reads its own state: how much it holds, which way, and at what average price. Once the planned entries land it will also say how long the position has been held, what it is making and how the run has gone. Every strategy needs at least `pos.isFlat` or `pos.size`, because a guard on the position is what stops an entry being sent again on every bar its condition stays true.

Every `pos` fact is built from **this strategy's own fills**, the executions reported back for the orders it sent. None of them is read from the account's position, which can hold another strategy's trades or a trade you placed by hand. [A strategy keeps its own books](/script/strategies/overview#a-strategy-keeps-its-own-books) explains why that rule is worth having. All of them are strategy only: in a `study()` file they are refused with OS7001.

## A complete example

A strategy that is always in the market after the first crossover of two averages: long while the fast average is above the slow one, short while it is below. The first cross opens a position and every later cross reverses it. It shades the background by direction, draws the average price while a position is open, and writes the position into a small table in the top right corner on the newest bar. It uses only the five `pos` facts that run in version 0.5.0.

```openscript
version 1
strategy("Long, short or flat", overlay = true, precision = 2,
         capital = 500000, qty = 1)

fast = ema(close, 9)
slow = ema(close, 21)
up   = crossUp(fast, slow)
down = crossDown(fast, slow)

if up and pos.isFlat
    buy(tag = "long")
else if down and pos.isFlat
    sell(tag = "short")
else if up and pos.isShort
    order.reverse(tag = "long")
else if down and pos.isLong
    order.reverse(tag = "short")

shade = pos.isLong ? fade(lime, 92) : (pos.isShort ? fade(red, 92) : none)
background(shade)
plot(pos.isFlat ? none : pos.avgPrice, "Average price", silver, style = "step")

panel = table("Position", 2, 2, position = "topRight", textColor = silver)
if bar.isLast
    cell(panel, 0, 0, "Size")
    cell(panel, 0, 1, text(pos.size))
    cell(panel, 1, 0, "Average")
    cell(panel, 1, 1, pos.isFlat ? "flat" : text(pos.avgPrice, 2))
```

## What runs and what is planned

| Name | When flat | Means | Status |
|---|---|---|---|
| `pos.size` | `0` | Net position in units, positive long, negative short | Runs |
| `pos.isLong`, `pos.isShort`, `pos.isFlat` | `false`, `false`, `true` | The sign of `pos.size`, spelled out | Runs |
| `pos.avgPrice` | absent | Average price of the open position | Runs |
| `pos.entryTime`, `pos.barsHeld`, `pos.entries` | absent, absent, `0` | When the position opened, for how many bars, from how many entries | Planned |
| `pos.openProfit`, `pos.openProfitPercent` | absent | Unrealised profit, in money and in percent of cost | Planned |
| `pos.maxProfit`, `pos.maxLoss` | absent | The best and worst this position has been | Planned |
| `pos.isShared` | either | Whether the account holds more of this contract than the strategy | Planned |
| `pos.equity`, `pos.netProfit`, `pos.tradeCount` | a number | Capital plus profit, realised profit, closed trades | Planned |
| `pos.winRate`, `pos.profitFactor`, `pos.maxDrawdown` | a number | Run statistics | Planned |

**Unrealised** profit is what an open position would make if it were closed at the current price; **realised** profit is what closed trades actually made. A planned name is refused where you write it with OS2020, so a script cannot compile around one by accident. [Computing the planned figures today](#computing-the-planned-figures-today) shows how to work out the per-position ones yourself.

Three rules hold for every entry on this page:

- **Fills, not intentions.** An order placed on this bar changes nothing here until it fills. With the default `fillOn = "nextOpen"`, a market order decided on one bar shows in `pos` from the next bar. A resting limit or stop order changes nothing until it fills, so `pos.isFlat` alone does not stop a second resting order from being placed.
- **Absent is not zero.** A price is absent while flat, because zero is a price and `close > pos.avgPrice` against zero would take a branch that looks correct. A size is zero while flat, because zero is the true size and adding it to something gives the right answer.
- **Marked to the close.** The planned profit figures will value an open position at this bar's close, not at a bid or an ask.

## The position

### pos.size

```
pos.size: series number
```

First value: bar 0, `0` when flat

The strategy's net position in units: positive when long, negative when short and `0` when flat. It counts what the fills reported, which is units. When a declaration counts orders in lots, the destination converts each order to units where it knows the lot size, so in the Backtest panel a two-lot position in a contract of 75 units reads `150`.

```openscript
version 1
strategy("Position in a pane", overlay = false, precision = 0, qty = 2)

fast   = ema(close, 9)
slow   = ema(close, 21)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy(tag = "entry")
else if goFlat and pos.isLong
    close()

plot(pos.size, "Position, in units", aqua, style = "step")
```

**Remarks.** Sizing towards a target from `pos.size` is sound, because it counts nothing but this strategy's own fills. Take the direction from the function you call, never from the sign of the quantity, since a quantity of zero or below is refused with OS7004:

```openscript
wantedSize = 3 - pos.size

if wantedSize > 0
    buy(qty = wantedSize)
else if wantedSize < 0
    sell(qty = -wantedSize)
```

Because `pos.size` counts fills only, an order still waiting to fill is not in it. In a backtest a market order has always filled by the next bar; against a slower destination, remember what you sent in a [`var`](/script/language/persistence) until its fill arrives.

**See also.** `pos.isLong`, `pos.isShort`, `pos.isFlat`, `chart.lotSize`

### pos.isLong

```
pos.isLong: series bool
```

First value: bar 0

True while the strategy holds a long position, the same as `pos.size > 0`. Use it to guard an exit so that it only runs when there is something to exit.

```openscript
version 1
strategy("Shade the long", overlay = true, qty = 1)

fast   = ema(close, 20)
slow   = ema(close, 50)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy(tag = "entry")
else if goFlat and pos.isLong
    close()

background(pos.isLong ? fade(lime, 92) : none)
```

**See also.** `pos.isShort`, `pos.isFlat`, `pos.size`

### pos.isShort

```
pos.isShort: series bool
```

First value: bar 0

True while the strategy holds a short position, the same as `pos.size < 0`. This example sells short on a downward cross and covers when price rises two ATRs (average true ranges, a measure of how far price typically moves in a bar) above the average entry price.

```openscript
version 1
strategy("Cover the short", overlay = true, precision = 2, qty = 1)

atrValue = atr(14)
goShort  = crossDown(ema(close, 9), ema(close, 21))

// A stop two ATRs above the short's average price, absent while flat.
coverAt = pos.isShort ? pos.avgPrice + 2 * atrValue : none

if goShort and pos.isFlat
    sell(tag = "short")
else if pos.isShort and high >= coverAt
    close()

plot(coverAt, "Cover above", red, style = "step")
```

**See also.** `pos.isLong`, `pos.avgPrice`, `sell()`

### pos.isFlat

```
pos.isFlat: series bool
```

First value: bar 0

True while the strategy holds nothing, the same as `pos.size == 0`. It is the entry guard almost every strategy starts with: without it, an entry is sent again on every bar its condition stays true.

```openscript
version 1
strategy("One position at a time", overlay = true, qty = 1)

trend    = ema(close, 50)
crossUpT = crossUp(close, trend)
crossDnT = crossDown(close, trend)

// Without pos.isFlat, a buy would go out on every bar the condition holds.
if crossUpT and pos.isFlat
    buy(tag = "trend")
else if crossDnT and pos.isLong
    close()
```

**Remarks.** In a backtest and on the chart, `pos.isFlat` is a complete guard for market entries, because a market order decided on one bar has filled before the next bar runs. It does not guard a resting limit or stop order: while one waits, the strategy is still flat. Remember a resting order in a [`var`](/script/language/persistence), as [Orders](/script/strategies/orders#when-the-next-signal-arrives-and-an-order-is-still-working) shows, until `order.working()` lands.

**See also.** `pos.isLong`, `pos.isShort`, `order.pending`

### pos.avgPrice

```
pos.avgPrice: series number
```

First value: absent while flat

The average price of the open position, and absent while flat. When a strategy adds to a position the average moves, which is why a stop measured from it is a different stop from one measured from the first entry.

This example adds to a long position on new 20-bar highs, at most twice, and stops the whole position out two ATRs below its average price. `pyramiding = 2` in the declaration allows the second entry in the same direction.

```openscript
version 1
strategy("Stop from the average", overlay = true, precision = 2,
         qty = 1, pyramiding = 2)

atrValue = atr(14)
trendUp  = close > ema(close, 50)
newHigh  = high > highest(high, 20)[1]

// Absent while flat, so the comparison below cannot be true then.
stopLevel = pos.isFlat ? none : pos.avgPrice - 2 * atrValue

// Counted by the script, so it never relies on the pyramiding refusal.
var adds = 0
if pos.isFlat
    adds = 0

if pos.isLong and low <= stopLevel
    close()
else if trendUp and newHigh and adds < 2
    buy(tag = "add")
    adds = adds + 1

plot(stopLevel, "Stop from the average", red, style = "step")
plot(pos.isFlat ? none : pos.avgPrice, "Average price", fade(silver, 40), style = "step")
```

**Remarks.** Plotting `pos.isFlat ? none : pos.avgPrice` draws the entry price while a position is open and a gap while flat, which makes a strategy's state visible on the chart. The average is taken over the fills that make up the position, at the prices the destination reported, so any slippage is already in it.

**See also.** `pos.size`, `pos.openProfit`, `pos.entries`

## The current position

These will describe the position the strategy holds now. They are planned, and each will be absent while flat except `pos.entries`, which will be `0`.

### pos.entryTime (planned, not available yet)

```
pos.entryTime: series number
```

First value: absent while flat

The time the current position was opened, as a timestamp like `time`. It will let a strategy measure how long it has held a position in clock time rather than in bars.

### pos.barsHeld (planned, not available yet)

```
pos.barsHeld: series number
```

First value: absent while flat

How many bars the current position has been held, `0` on the entry bar. It is the natural input for an exit after a set number of bars.

### pos.entries (planned, not available yet)

```
pos.entries: series number
```

First value: bar 0

How many entries make up the current position, for a rule such as "add at most three times". It will be `0` while flat.

### pos.openProfit (planned, not available yet)

```
pos.openProfit: series number
```

First value: absent while flat

The open position's unrealised profit in money, valued at this bar's close.

### pos.openProfitPercent (planned, not available yet)

```
pos.openProfitPercent: series number
```

First value: absent while flat

The same unrealised profit as a percentage of the position's cost, so one threshold can be used across instruments at very different prices.

### pos.maxProfit (planned, not available yet)

```
pos.maxProfit: series number
```

First value: absent while flat

The best unrealised profit the current position has reached since it opened, in money. With `pos.openProfit` it answers "how much of the move did I give back".

### pos.maxLoss (planned, not available yet)

```
pos.maxLoss: series number
```

First value: absent while flat

The worst unrealised loss the current position has reached since it opened, in money. It is the figure to watch when judging whether a stop is too wide.

## The account

### pos.isShared (planned, not available yet)

```
pos.isShared: series bool
```

First value: bar 0

True when the account's position in a contract this strategy holds is larger than the strategy's own: another strategy or a manual trade is in the same contract. It stays a yes or no. No call will return the account's quantity as a number, because a script that could read it would size against somebody else's trade.

## The run so far

These will read the whole run of the strategy. They are planned. A finished backtest already shows net profit, closed trades, win rate, profit factor, maximum drawdown and the equity curve in the Backtest panel's report; what is planned is reading them from inside the script, bar by bar.


### pos.equity (planned, not available yet)

```
pos.equity: series number
```

First value: bar 0

The declaration's `capital` plus everything realised and the open position's unrealised profit. It is this strategy's equity, not your account balance.

### pos.netProfit (planned, not available yet)

```
pos.netProfit: series number
```

First value: bar 0

Profit realised since the run began, in money, from closed trades only.

### pos.tradeCount (planned, not available yet)

```
pos.tradeCount: series number
```

First value: bar 0

How many trades have closed since the run began.

### pos.winRate (planned, not available yet)

```
pos.winRate: series number
```

First value: bar 0

The share of closed trades that made money.

### pos.profitFactor (planned, not available yet)

```
pos.profitFactor: series number
```

First value: bar 0

Gross profit divided by gross loss over the closed trades: above 1 means the winners made more than the losers lost.

### pos.maxDrawdown (planned, not available yet)

```
pos.maxDrawdown: series number
```

First value: bar 0

The largest fall in equity from a peak to a later low so far in the run.

## Computing the planned figures today

The per-position figures are a few lines of [`var`](/script/language/persistence) each. This strategy adds up to three times on fresh highs and keeps the planned figures itself, valued at the close the way the planned entries will be, then shows them in a table:

```openscript
version 1
strategy("Position figures by hand", overlay = true, precision = 2,
         capital = 500000, qty = 1, pyramiding = 3)

slow    = ema(close, 21)
trendUp = close > slow
newHigh = high > highest(high, 20)[1]
goFlat  = crossDown(ema(close, 9), slow)

pointValue = orElse(chart.pointValue, 1)

var entryBar  = none
var entryTime = none
var entries   = 0
var bestOpen  = none
var worstOpen = none

// Reset while flat; start the clock on the first bar the position holds.
if pos.isFlat
    entryBar  = none
    entryTime = none
    entries   = 0
    bestOpen  = none
    worstOpen = none
else if isNone(entryBar)
    entryBar  = bar.index
    entryTime = time

barsHeld   = isNone(entryBar) ? none : bar.index - entryBar
minutesIn  = isNone(entryTime) ? none : (time - entryTime) / 60000
openProfit = pos.isFlat ? none : (close - pos.avgPrice) * pos.size * pointValue
openPct    = pos.isFlat ? none : openProfit / (abs(pos.size) * pos.avgPrice * pointValue) * 100

if not isNone(openProfit)
    bestOpen  = isNone(bestOpen) ? openProfit : max(bestOpen, openProfit)
    worstOpen = isNone(worstOpen) ? openProfit : min(worstOpen, openProfit)

// entries counts the orders sent into this position.
if trendUp and newHigh and entries < 3
    buy(tag = "add")
    entries = entries + 1
else if goFlat and pos.isLong
    close()

fn show(value, decimals) => isNone(value) ? "flat" : text(value, decimals)

panel = table("Position", 6, 2, position = "topRight", textColor = silver)
if bar.isLast
    cell(panel, 0, 0, "Bars held")
    cell(panel, 0, 1, show(barsHeld, 0))
    cell(panel, 1, 0, "Minutes held")
    cell(panel, 1, 1, show(minutesIn, 0))
    cell(panel, 2, 0, "Entries")
    cell(panel, 2, 1, text(entries))
    cell(panel, 3, 0, "Open profit")
    cell(panel, 3, 1, show(openProfit, 0))
    cell(panel, 4, 0, "Open profit, percent")
    cell(panel, 4, 1, show(openPct, 2))
    cell(panel, 5, 0, "Best and worst")
    cell(panel, 5, 1, isNone(bestOpen) ? "flat" : text(bestOpen, 0) + " / " + text(worstOpen, 0))
```

`openProfit` here is valued at the close from the average fill price, so it includes slippage and leaves out commission. The run figures (equity, realised profit, trade count and the statistics) need the price of every fill, which a script cannot read until `order.avgFill()` lands; read them from the backtest report instead.

> **More than one leg**
When [multi-leg strategies](/script/reference/legs) land, the twelve entries that describe one position (from `pos.size` to `pos.maxLoss` in the table above) will be refused before the first bar in a file that declares more than one leg, because adding a quantity of one contract to a quantity of another is not a position in anything. Each leg will be read by name with `leg.size()`, `leg.avgPrice()` and their siblings. The money and count entries add up across legs and will read the whole strategy in every file.

## Related

[Position and sizing](/script/strategies/position-and-sizing), [Reading the books](/script/strategies/reading-the-books), [Orders](/script/strategies/orders), [Strategy orders](/script/reference/strategy), [order.*](/script/reference/orders), [leg.*](/script/reference/legs), [Reading a report](/script/strategies/reading-a-report).


## order.*

Source: https://openalgo.in/script/reference/orders

The `order` namespace holds the order calls a strategy reaches for less often than the six on [Strategy orders](/script/reference/strategy): a general form for a script that computes its side, a reversal in one call, a stop and target given as distances, the calls that will read an order back, and the helpers that will turn an amount of money into a quantity. Three of them run in version 0.5.0 and the rest are planned.

Everything here works only in a `strategy()` file; in a study it is refused with OS7001. A planned name is refused where you write it with OS2020, so you find out at the line that uses it.

## A complete example

A model whose direction comes out of a calculation: the gap between a fast and a slow average decides the side, `"buy"` or `"sell"`. While flat, the strategy enters on the model's side with `order.place()` and attaches a stop and a target as distances with `order.bracket()`. When the side turns against the position it closes, and on the next bar it enters the other way. Each turn of the model is traded once, so after a stop-out it waits for the next turn.

```openscript
version 1
strategy("Computed side, bracketed", overlay = true, precision = 2,
         capital = 500000, qty = 1, pyramiding = 1,
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

atrMult = input(2.0, "Stop, in ATR", min = 0.5, max = 10)

fast     = ema(close, 9)
slow     = ema(close, 21)
atrValue = atr(14)

// "buy" above the slow average, "sell" below it, "" while the averages warm up.
score = fast - slow
side  = isNone(score) ? "" : (score > 0 ? "buy" : "sell")

// The side of the last entry, so each turn of the model is traded once.
var lastSide = ""

if pos.isFlat and side != "" and side != lastSide and not isNone(atrValue)
    order.place(side, 1, tag = "model")
    order.bracket(tag = "model", loss = atrMult * atrValue, profit = 2 * atrMult * atrValue)
    lastSide = side
else if (pos.isLong and side == "sell") or (pos.isShort and side == "buy")
    close()

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
plot(pos.isFlat ? none : pos.avgPrice, "Average price", fade(silver, 40), style = "step")
```

A reader of `order.place(side, ...)` has to work out what `side` holds, so use it where the direction really is computed, and `buy()` or `sell()` where it is written in the source. In version 0.5.0 the chart and the Backtest panel do not fill a bracket, so there this strategy exits only when the side turns; and the Strategies panel refuses to start a strategy that calls `order.bracket()`.

## What runs and what is planned

| Call | For | Status |
|---|---|---|
| `order.place()` | Place an order whose side and type are values | Runs |
| `order.reverse()` | Close the position and open the other way, in one decision | Runs |
| `order.bracket()` | Set the stop and target as distances from the entry | Runs, with the caveat above |
| `order.modify()`, `order.oco()` | Change a working order in place; cancel one order when another fills | Planned |
| `order.working()`, `order.pending` | Whether a tagged order is working; how many are | Planned |
| `order.status()`, `order.filled()`, `order.avgFill()`, `order.id()`, `order.rejection()` | Read one order back | Planned |
| `order.qtyForCash()`, `order.qtyForEquityPercent()`, `order.qtyForRisk()`, `order.roundToLot()` | Turn money or risk into a quantity | Planned |

## Placing and changing orders

### order.place()

```
order.place(side: string, qty: number, type?: string = "market", price?: number = none, trigger?: number = none, tag?: string = "", leg?: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| side | string | required (one of "buy", "sell") |
| qty | number | required |
| type | string | "market" (one of "market", "limit", "stop", "stopLimit") |
| price | number | none |
| trigger | number | none |
| tag | string | "" |
| leg | string | optional |

First value: bar 0

The general order call, for a script whose side comes out of a calculation rather than out of two branches. `side` is `"buy"` or `"sell"` and `type` is `"market"`, `"limit"`, `"stop"` or `"stopLimit"`. It keeps a script from writing its entry block twice, once for each direction, and it is the only order call that states the type outright.

This example rests a stop-limit order above the high of the last 20 bars: it triggers when price trades through the high, and then buys at no more than 0.2 percent above it.

```openscript
version 1
strategy("Stop-limit above the range", overlay = true, precision = 2, qty = 1)

rangeHigh = highest(high, 20)[1]
trigger   = isNone(rangeHigh) ? none : roundToTick(rangeHigh)
ceiling   = isNone(trigger) ? none : roundToTick(trigger * 1.002)
ready     = not isNone(trigger) and not isNone(ceiling)
goFlat    = crossDown(close, ema(close, 20))
lateDay   = not session.isIn("0915-1500", "Asia/Kolkata")

var working = false
if not pos.isFlat
    working = false

// A stop-limit that gapped past its ceiling may never fill, so it is withdrawn
// from 15:00 and placed afresh the next morning.
if working and lateDay
    cancel("range")
    working = false
else if pos.isLong and (goFlat or lateDay)
    close()
else if ready and pos.isFlat and not working and not lateDay
    order.place("buy", 1, type = "stopLimit", price = ceiling, trigger = trigger, tag = "range")
    working = true
```

**Remarks.** The type and the prices must agree:

| `type` | `price` | `trigger` |
|---|---|---|
| `"market"` | left out | left out |
| `"limit"` | the limit | left out |
| `"stop"` | left out | the trigger |
| `"stopLimit"` | the limit it rests at once triggered | the trigger |

A type missing the price it needs is refused with OS7007 when the call runs. A `side` or `type` written as a value outside its list is refused before the first bar with OS3008. `qty` is required here, and it follows the same rules as in `buy()`: zero or below is OS7004, and a value that comes out absent is OS7002.

An order on the side that reduces the position, such as a resting sell stop under a long, counts as already on its way out while it works. A bare `close()` then sends nothing for the part it covers, and if the order later triggers it takes the position off. To exit some other way, `cancel()` it first and close on the next bar, once the cancellation has been confirmed.

```openscript
version 1
strategy("A side that does not exist", overlay = true, qty = 1)

if crossUp(close, ema(close, 20))
    order.place("long", 1, tag = "entry")
```

**See also.** `buy()`, `sell()`, `cancel()`

### order.reverse()

```
order.reverse(qty?: number = none, tag?: string = "", leg?: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| qty | number | none |
| tag | string | "" |
| leg | string | optional |

First value: bar 0

Closes the position and opens one the other way, in one decision. With `qty` left out the new position is the same size as the old one; with `qty` it is that size. It is the call a stop-and-reverse system, which is always in the market once it has started, is written with.

This example uses `supertrend()`, a trend-following band that flips from one side of price to the other when the trend turns, and reverses the position on every flip:

```openscript
version 1
strategy("Stop and reverse", overlay = true, precision = 2,
         capital = 500000, qty = 1, pyramiding = 1)

factor = input(3.0, "Band width, in ATR", min = 0.5, max = 20)
atrLen = input(10,  "ATR length", min = 1, max = 200)

// supertrend returns [line, direction]: -1 while the trend is up, 1 while down.
bands = supertrend(factor, atrLen)
band  = bands[0]
dir   = bands[1]

// != treats an absent value as a value, so the first bar with a direction
// counts as a flip and opens the first position.
flipped = not isNone(dir) and dir != dir[1]

if flipped and pos.isFlat
    if dir == -1
        buy(tag = "long")
    else
        sell(tag = "short")
else if flipped
    order.reverse(tag = "reversal")

plot(dir == -1 ? band : none, "Stop, long",  lime, width = 2)
plot(dir == 1  ? band : none, "Stop, short", red,  width = 2)
```

**Remarks.** A reversal is always two orders, because no order crosses zero: one closes the outgoing position and one opens the replacement, each with its own position reference, so a late fill can say which position it settles. Long 2, `order.reverse(qty = 5)` sends a sell of 2 and a sell of 5 and leaves the strategy short 5. On a flat position it sends nothing.

`order.reverse` sizes both halves itself in units. Under `qtyType = "lots"` the 0.5.0 backtest converts those sizes from lots a second time, the same defect as `close()`; count in units, as [Where a size comes from](/script/strategies/position-and-sizing#where-a-size-comes-from) explains.

**See also.** `close()`, `sell()`, `pos.size`

### order.bracket()

```
order.bracket(tag?: string = "", profit?: number = none, loss?: number = none, leg?: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | "" |
| profit | number | none |
| loss | number | none |
| leg | string | optional |

First value: bar 0

Sets the position's stop and target as distances from the entry price, in the instrument's own price units. It is the distance form of `exit()` on its own, and the natural partner of a market entry, whose fill price your script does not know yet.

```openscript
version 1
strategy("Pullback with a bracket", overlay = true, precision = 2, qty = 1)

atrValue = atr(14)
trendUp  = close > ema(close, 50)
pullback = crossUp(close, ema(close, 20))
sized    = not isNone(atrValue)

if trendUp and pullback and pos.isFlat and sized
    buy(tag = "pullback")
    order.bracket(tag = "pullback", loss = 1.5 * atrValue, profit = 3 * atrValue)
else if pos.isLong and not trendUp
    close()
```

**Remarks.** A position carries one stop and one target, so calling `order.bracket` or `exit()` again replaces the pair rather than adding a second one. A distance whose value is absent is refused with OS7002, and that takes the whole bar's orders with it, including the entry above it. The tag is a label that tells the destination which entry the levels protect; a tag that matches no order is not refused.

In version 0.5.0 the chart and the Backtest panel do not fill a bracket, so in this example every trade closes when the trend turns. The Strategies panel refuses to start a strategy that calls `order.bracket`. Until both change, write the stop and target as rules the script tests, as [Exits and brackets](/script/strategies/exits-and-brackets) shows.

**See also.** `exit()`, `leg.stop()`, `leg.trail()`

### order.modify() (planned, not available yet)

```
order.modify(tag: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | required |

First value: bar 0

Will change a working order's price or quantity in place, named by its tag, instead of cancelling it and placing a new one. Until it lands, `cancel()` the order and place the replacement: call `cancel` first on the bar, or give the replacement a new tag, because a cancellation withdraws every working order carrying its tag.

### order.oco() (planned, not available yet)

```
order.oco(tagA: string, tagB: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| tagA | string | required |
| tagB | string | required |

First value: bar 0

Will link two working orders so that when one fills, the other is cancelled: one-cancels-other. A bracket's stop and target already relate this way, since reaching one closes the position the other protects; this is the general case for any two orders. Until it lands, `cancel()` the other tag yourself when the position changes.

## Reading an order back

These seven will read the strategy's own **ledger**: the record of every order it placed and every fill reported for it. They never ask the destination directly: they read what the strategy has already recorded from the destination's answers, so a fill reported twice is still counted once. All seven are planned. Until they land, a strategy remembers its own working orders in a [`var`](/script/language/persistence), as the examples on [Strategy orders](/script/reference/strategy) do.

Three rules will apply to all of them:

- **A tag that names nothing reads empty.** Reading is how a script finds out, so a tag with no order reads as that entry's empty value instead of being refused: `0` from `order.filled()`, `""` from `order.id()` and `order.rejection()`, absent from `order.avgFill()`. Acting on such a tag, as `cancel()` does, is still refused with OS7009.
- **Finished orders stay readable.** An order that has filled, been cancelled, rejected or expired keeps its record, which is when `order.avgFill()` and `order.rejection()` have something to say.
- **The newest order wins.** Where several orders carry one tag, the reads read the most recently placed.

An order's status is one of these words:

| Status | Means | Final |
|---|---|---|
| `placed` | Sent, and the destination has not answered yet | No |
| `working` | Live at the destination and not completely filled | No |
| `triggerPending` | Accepted and waiting for its trigger price | No |
| `filled` | The whole quantity has filled | Yes |
| `cancelled` | Ended by a cancellation | Yes |
| `rejected` | Refused, with the destination's own reason | Yes |
| `expired` | Ended without filling, by the destination's own rule | Yes |

A status only moves forward, and a final status never changes. A fill that arrives after a cancellation, which happens when a cancellation races a fill at the exchange, is still counted in the filled quantity while the status stays `cancelled`.

### order.working() (planned, not available yet)

```
order.working(tag: string) -> series bool
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | required |

First value: bar 0

Will be true while an order with that tag is live and not completely filled. It is the guard for "place this order only if the last one is not still resting", and the check to make before `cancel()`.

### order.pending (planned, not available yet)

```
order.pending: series number
```

First value: bar 0

Will count the orders this strategy has live and not completely filled. `order.pending == 0` is the guard that stops a second entry while a resting order waits, which `pos.isFlat` alone cannot do.

### order.id() (planned, not available yet)

```
order.id(tag: string) -> series string
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | required |

First value: bar 0

Will read the destination's own order id for that tag, as a string, and `""` until the destination has answered. It is the reference to quote when you ask your broker about one order.

### order.status() (planned, not available yet)

```
order.status(tag: string) -> series string
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | required |

First value: bar 0

Will read the status of the order with that tag, as one of the words in the table above.

### order.filled() (planned, not available yet)

```
order.filled(tag: string) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | required |

First value: bar 0

Will read how much of the order has filled so far, `0` before the first fill. It is a running total and only rises. To find what filled on this bar, take the difference from the previous bar yourself.

### order.avgFill() (planned, not available yet)

```
order.avgFill(tag: string) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | required |

First value: decided by the data

Will read the order's average fill price over everything filled so far, as the destination computed it, and absent before the first fill.

### order.rejection() (planned, not available yet)

```
order.rejection(tag: string) -> series string
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | required |

First value: bar 0

Will read the destination's own reason for rejecting the order, word for word, and `""` when there is none. It is what a table on the chart shows when a trader asks why an entry did not happen.

## Sizing helpers

These will turn a sentence about money into a quantity. All four are planned. They will round down unless told otherwise, because a size rounded up is a position larger than the script asked for, and the error repeats with every entry.

### order.qtyForCash() (planned, not available yet)

```
order.qtyForCash(cash: number, price?: number = close) -> number
```

| Parameter | Type | Default |
|---|---|---|
| cash | number | required |
| price | number | close |

First value: bar 0

Will return the number of whole units that `cash` buys at `price`, which defaults to this bar's close: "put two lakh into this".

### order.qtyForEquityPercent() (planned, not available yet)

```
order.qtyForEquityPercent(percent: number, price?: number = close) -> number
```

| Parameter | Type | Default |
|---|---|---|
| percent | number | required |
| price | number | close |

First value: bar 0

Will return the number of whole units that `percent` of the strategy's current equity buys at `price`: "put ten percent of the capital into this".

### order.qtyForRisk() (planned, not available yet)

```
order.qtyForRisk(risk: number, entry: number, stop: number) -> number
```

| Parameter | Type | Default |
|---|---|---|
| risk | number | required |
| entry | number | required |
| stop | number | required |

First value: bar 0

Will return the number of whole units for which being stopped out, from `entry` to `stop`, costs `risk` in money. It will return `none` when `entry` and `stop` are equal, which can happen during warmup, and the order that receives the absent quantity is then refused with OS7002.

### order.roundToLot() (planned, not available yet)

```
order.roundToLot(qty: number, direction?: string = "down", leg?: string) -> number
```

| Parameter | Type | Default |
|---|---|---|
| qty | number | required |
| direction | string | "down" (one of "up", "down") |
| leg | string | optional |

First value: bar 0

Will round a quantity to a whole number of the instrument's lots, down unless `direction = "up"`. It is how a size computed in units becomes one the exchange accepts for an NFO or MCX contract.

## Sizing today

Until the helpers land, the same arithmetic is a few lines. This strategy sizes each trade so that being stopped out loses about a fixed amount of money, caps the money it commits, rounds down to whole lots, and exits at the stop it sized for or on the opposite cross:

```openscript
version 1
strategy("Risk sizing by hand", overlay = true, precision = 2,
         capital = 1000000, qtyType = "units", pyramiding = 1)

riskAmount = input(5000,   "Money lost if the stop is hit", min = 100)
maxCash    = input(500000, "Never commit more than this much", min = 1000)
stopAtr    = input(2.0,    "Stop, in ATR", min = 0.5, max = 10)

atrValue   = atr(14)
lotUnits   = max(orElse(chart.lotSize, 1), 1)
pointValue = orElse(chart.pointValue, 1)
goLong     = crossUp(ema(close, 9), ema(close, 21))
goFlat     = crossDown(ema(close, 9), ema(close, 21))

// The stop sits stopAtr ATRs below the close the entry is decided on.
stopDistance = stopAtr * atrValue
stopNow      = isNone(stopDistance) ? none : roundToTick(close - stopDistance)

// The size the risk allows, capped by the money committed, then rounded down
// to whole lots: order.qtyForRisk, order.qtyForCash and order.roundToLot.
riskUnits = stopDistance > 0 ? floor(riskAmount / (stopDistance * pointValue)) : none
cashUnits = floor(maxCash / (close * pointValue))
units     = isNone(riskUnits) ? none : floor(min(riskUnits, cashUnits) / lotUnits) * lotUnits

// The stop this trade was sized for, kept while the position is open.
var stopLevel = none
if pos.isFlat
    stopLevel = none

if pos.isLong and (low <= stopLevel or goFlat)
    close()
else if goLong and pos.isFlat and not isNone(stopNow) and not isNone(units) and units > 0
    buy(qty = units, tag = "entry")
    stopLevel = stopNow

plot(stopLevel, "Stop the size was computed for", red, style = "step")
```

The loss at the stop is about `riskAmount` rather than exactly it: the entry fills at the next bar's open rather than at the close the size was computed from, and the exit is decided on the bar that reaches the stop and fills at the following bar's open, which can be beyond the stop. When the risk allows less than one lot, the size rounds down to zero and the script, correctly, takes no trade; raise the amount for a contract with a large lot. [Position and sizing](/script/strategies/position-and-sizing) works through sizing by risk, by volatility and by lots in full.

## Related

[Orders](/script/strategies/orders), [Exits and brackets](/script/strategies/exits-and-brackets), [Reading the books](/script/strategies/reading-the-books), [Position and sizing](/script/strategies/position-and-sizing), [Strategy orders](/script/reference/strategy), [pos.*](/script/reference/position), [leg.*](/script/reference/legs).


## leg.*

Source: https://openalgo.in/script/reference/legs

A **leg** is one contract a strategy trades. The `leg` namespace is how a strategy will declare more than one: the call and the put of a straddle on NIFTY weekly options, the near and far months of a futures spread on NFO or MCX, a future hedged with an option. It will let a script name a contract outright or describe it ("nearest expiry, at the money, call"), read back the contract the host picked, hold one position per leg, and put a stop, a target and a trailing stop on each.

**Every entry on this page is planned.** In version 0.5.0 a strategy has exactly one leg, the instrument on its chart, and every order acts on it without naming it. Calling any `leg.*` name is refused where you write it with OS2020. This page documents what each call will do, so you can see where the language is going and design around it, and shows what to write today.

A few option terms recur below. The **underlying** is the instrument a future or option is based on, such as the NIFTY index. The **expiry** is the day the contract ends. The **strike** is the price an option is written at, and the option **at the money** is the one whose strike is nearest the underlying's current price. The [Glossary](/script/resources/glossary) has the rest.

## One leg today

Every strategy you write in version 0.5.0 is a one-leg strategy. To trade an option, find the option in symbol search, open its own chart and add the strategy there.


This one sells the option on the chart once a day, on a bar that opens between 09:20 and 09:35 (on a 15-minute chart, the 09:30 bar). It stops out if the premium rises 30 percent above the entry, and closes from the first bar that opens at 15:00:

```openscript
version 1
strategy("Sell the option on the chart", overlay = true, precision = 2,
         capital = 500000, qtyType = "units", product = "intraday",
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

lots    = input(1,  "Lots", min = 1, max = 50)
stopPct = input(30, "Stop, percent above the entry premium", min = 5, max = 300)

// The zone is written out so the windows also work in the Backtest panel.
zone        = "Asia/Kolkata"
lotUnits    = max(orElse(chart.lotSize, 1), 1)
entryWindow = session.isIn("0920-0935", zone)
lateDay     = not session.isIn("0915-1500", zone)
newDay      = bar.isFirst or not date.isSameDay(time, time[1], zone)

var doneToday = false
if newDay
    doneToday = false

// Absent while flat, so the stop test below cannot fire then.
stopLevel = pos.isShort ? roundToTick(pos.avgPrice * (1 + stopPct / 100)) : none

if pos.isShort and (lateDay or high >= stopLevel)
    close()
else if entryWindow and pos.isFlat and not doneToday
    sell(qty = lots * lotUnits, tag = "premium")
    doneToday = true

plot(stopLevel, "Stop", red, style = "step")
```

The size is counted in units from the lot size, and the stop is a rule the script tests, for the reasons on [Strategy orders](/script/reference/strategy). On the chart the lot size is not stated to the script yet, so there `chart.lotSize` is absent and this file falls back to one unit per lot; the Backtest panel uses the lot size OpenAlgo holds for the instrument.

Every order call except `cancel()` and `cancelAll()` accepts a `leg` argument, and in version 0.5.0 writing it is refused with OS3023, whatever it names: a file that declares no leg has no name the argument could refer to. Take the argument out and the order acts on the chart's instrument.

```openscript
version 1
strategy("Naming a leg too early", overlay = true, qty = 1)

goLong = crossUp(ema(close, 9), ema(close, 21))

if goLong and pos.isFlat
    buy(leg = "fut")
```

To read a second instrument today, for a signal or a combined premium, use `req.symbol()`; [Other instruments](/script/data/other-instruments) shows how, and the [book.* page](/script/reference/books#what-you-can-write-today) measures a two-leg premium from one chart.

## The shape a declared leg will take

Declared, the same idea gets a name that every later call uses: here a leg on the near-month NIFTY future, entered on its own signal, with a standing stop and a trailing stop the engine holds. The block below is the planned shape, and version 0.5.0 refuses it with OS2020.

```openscript
version 1
strategy("Trail behind the move", overlay = true, precision = 2,
         capital = 500000, qty = 1, qtyType = "lots", product = "intraday")

stopMult    = input(2.0, "Initial stop, in ATR", min = 0.2, max = 20)
trailAtr    = input(3.0, "Trail this far behind, in ATR", min = 0.2, max = 20)
activateAtr = input(1.0, "Start trailing after this much profit, in ATR", min = 0.1, max = 20)

leg.relative("fut", "NIFTY", "future", expiryRank = 0, exchange = "NFO")

atrValue = atr(14)
goLong   = crossUp(ema(close, 9), ema(close, 21))
ready    = not isNone(atrValue)

if goLong and ready and not leg.isOpen("fut")
    leg.enter("fut", tag = "entry")
    leg.stop("fut", roundToTick(close - stopMult * atrValue))
    leg.trail("fut", trailAtr * atrValue, activateAt = activateAtr * atrValue)

plot(leg.stopPrice("fut"), "Stop in force", red, width = 2, style = "step")
```

Three rules will hold once legs land:

- **Every order names its leg.** In a file with one leg the `leg` argument defaults to it and is never written. In a file with more than one, leaving it out is OS3012, because there is no leg the engine could pick, and a name that is not declared is OS3008, with the declared names in the message.
- **One position per leg.** Each leg holds its own position, and `pos.size` and the other single-position facts are refused in a file with more than one leg: adding a quantity of one contract to a quantity of another is not a position in anything. Each leg is read by name with `leg.size()` and its siblings.
- **Two shapes, never mixed.** A strategy enters its legs one at a time on their own signals, with `leg.enter()` and `leg.exit()`, or all together as a unit with `book.enter()`. `buy()`, `sell()`, `close()` and `exit()` are the per-leg calls written the short way. The [book.* page](/script/reference/books#two-shapes) explains why a file uses one shape or the other.

## Declaring legs

A leg is declared once, at the top level, before the first bar. The set of contracts a strategy trades is part of its fixed shape, like its plots, so a declaration inside an `if`, a loop or a function is refused with OS3006. You cannot hide a leg by passing `none`: declare it, and decide on each bar whether to send it an order.

- Every argument must be fixed before the first bar: a literal, arithmetic over literals, or an `input()`. A value that depends on the bar is OS3003.
- Two legs with the same name are OS3017, because the name is what every later call uses to find the leg.
- The engine never parses a symbol and never builds one. A symbol format belongs to one market; the description goes to the host, and the host sends back one real contract.

### leg.fixed() (planned, not available yet)

```
leg.fixed(name: string, symbol: string, exchange?: string = chart.exchange, product?: string, qty?: number, side?: string = "buy") -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |
| symbol | string | required |
| exchange | string | chart.exchange |
| product | string | optional |
| qty | number | optional |
| side | string | "buy" (one of "buy", "sell") |

First value: bar 0

Will declare a leg on a contract named outright, by the symbol your host knows it by, with its exchange, product, default quantity and default side. Use it when the contract is known in advance and does not roll over to a new expiry, such as one particular future or a stock.

### leg.relative() (planned, not available yet)

```
leg.relative(name: string, underlying: string, kind: string, expiryRank?: number = 0, expiryCycle?: string = none, strikeOffset?: number = 0, right?: string = none, reference?: number = none, exchange?: string = chart.exchange, product?: string, qty?: number, side?: string = "buy") -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |
| underlying | string | required |
| kind | string | required (one of "future", "option") |
| expiryRank | number | 0 |
| expiryCycle | string | none |
| strikeOffset | number | 0 |
| right | string | none (one of "call", "put") |
| reference | number | none |
| exchange | string | chart.exchange |
| product | string | optional |
| qty | number | optional |
| side | string | "buy" (one of "buy", "sell") |

First value: bar 0

Will declare a leg on a contract described relative to an underlying, such as "the nearest NIFTY weekly expiry, two strikes above the money, the call", which the host turns into one real contract before the first bar. A description the host cannot resolve is OS6007, and the strategy does not start.

**Remarks.** The fields of a description:

| Field | Holds |
|---|---|
| `underlying` | The instrument the contract derives from, such as an index or a stock, passed to the host as written |
| `kind` | `"future"` or `"option"`. Required, because it decides which of the other fields apply |
| `expiryRank` | `0` for the nearest expiry, `1` for the one after it, and so on |
| `expiryCycle` | Which series, where the exchange lists more than one, such as weekly and monthly; left out means the exchange's default series |
| `strikeOffset` | Strikes away from the money: `0` at the money, `2` two strikes above, `-2` two below |
| `right` | `"call"` or `"put"`, and left out for a future |
| `reference` | The price the offset is measured from; left out means the underlying's price at the moment the host resolves the contract |

`name`, `exchange`, `product`, `qty` and `side` are the leg's own bookkeeping, not part of the description. Giving `right` or `strikeOffset` with `kind = "future"` is OS3010.

**A relative contract resolves once.** Say a leg describes "nearest expiry, at the money, call" and enters on a quiet morning at one strike. If the description were evaluated again at the exit, after the index has moved a hundred points, "at the money" would name a different strike, and the strategy would send a closing order for a contract it never held while the one it does hold stays open. So the description is resolved once, before the first bar, and every order for the rest of the run carries that one contract. `leg.symbol()` reads it back.

## The resolved contract

These will report the contract the host picked and what the orders actually carried. They are fixed for the run rather than per bar, so print them once on the first bar of a strategy and the log answers "what did it actually trade" without anyone reasoning about what was at the money that morning.

### leg.symbol() (planned, not available yet)

```
leg.symbol(name: string) -> string
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: bar 0

Will return the contract the leg resolved to, as the symbol its orders carried. It is the name to reconcile against a broker statement.

### leg.exchange() (planned, not available yet)

```
leg.exchange(name: string) -> string
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: bar 0

Will return the exchange the leg's orders were sent to.

### leg.product() (planned, not available yet)

```
leg.product(name: string) -> string
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: bar 0

Will return the product the leg's orders were actually sent with, after any translation the destination applies, which is what a statement can be matched against.

### leg.expiry() (planned, not available yet)

```
leg.expiry(name: string) -> number
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: bar 0

Will return the resolved contract's expiry, and absent for a contract with no expiry, such as a stock.

### leg.strike() (planned, not available yet)

```
leg.strike(name: string) -> number
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: bar 0

Will return the resolved option's strike, and absent for a contract with none.

## A leg's position

These will read one leg's own position, built from that leg's fills. In a one-leg file they say what the matching `pos` facts say. A leg will have no equivalent of `pos.isLong`, `pos.barsHeld`, `pos.maxProfit` or `pos.maxLoss`: the sign of `leg.size()` answers the first, `leg.entryTime()` the second, and the other two are a [`var`](/script/language/persistence) the script keeps.

### leg.size() (planned, not available yet)

```
leg.size(name: string) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: bar 0, `0` when flat

Will return the signed number of units this strategy holds in the leg: positive long, negative short, `0` when flat.

### leg.avgPrice() (planned, not available yet)

```
leg.avgPrice(name: string) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: absent while the leg is flat

Will return the average price of the leg's open position, and absent while the leg is flat, for the same reason `pos.avgPrice` is.

### leg.entryTime() (planned, not available yet)

```
leg.entryTime(name: string) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: absent while the leg is flat

Will return the time the leg's current position was opened, and absent while the leg is flat.

### leg.profit() (planned, not available yet)

```
leg.profit(name: string) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: bar 0, `0` when flat

Will return the leg's open profit in money, valued at this bar's close, and `0` while the leg is flat.

### leg.isOpen() (planned, not available yet)

```
leg.isOpen(name: string) -> series bool
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: bar 0

Will be true while the leg holds a position. It is the per-leg entry guard, in place of `pos.isFlat`.

## Entering and exiting one leg

### leg.enter() (planned, not available yet)

```
leg.enter(name: string, side?: string, qty?: number, limit?: number = none, stop?: number = none, tag?: string = "") -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |
| side | string | optional (one of "buy", "sell") |
| qty | number | optional |
| limit | number | none |
| stop | number | none |
| tag | string | "" |

First value: bar 0

Will send one order that enters one leg on its own signal, on the leg's declared side and quantity unless you give others, as a market, limit or stop order by the same rules as `buy()`. An entry that the book's direction filter or entry window refuses is recorded rather than sent.

### leg.exit() (planned, not available yet)

```
leg.exit(name: string, qty?: number = none, limit?: number = none, stop?: number = none, tag?: string = "") -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |
| qty | number | none |
| limit | number | none |
| stop | number | none |
| tag | string | "" |

First value: bar 0

Will send one order that exits one leg, all of it or `qty` of it, at the market or resting at `limit` or `stop`.

## Levels on a leg

A **level** is a price the engine watches for you: it is tested once per bar, after your script's own statements, and when it is reached the engine closes the leg. Passing `none` as a level removes it; unlike an order argument, an absent level is not refused. A leg carries at most one stop and one target at a time, and `exit()` and `leg.stop()` are two ways of setting the same stop: the last call to run on a bar is the one in force.

### leg.stop() (planned, not available yet)

```
leg.stop(name: string, price: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |
| price | number | required |

First value: bar 0

Will set a standing stop that closes the leg when its price reaches `price` against the position, replacing any stop in force.

### leg.target() (planned, not available yet)

```
leg.target(name: string, price: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |
| price | number | required |

First value: bar 0

Will set a standing target that closes the leg when its price reaches `price` in favour of the position, replacing any target in force.

### leg.trail() (planned, not available yet)

```
leg.trail(name: string, distance: number, activateAt?: number = none) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |
| distance | number | required |
| activateAt | number | none |

First value: bar 0

Will set a trailing stop that follows the best price the leg has seen, `distance` behind it, and only ever moves in the leg's favour. It is the language's one trailing stop: there is no trail argument on `exit()` or `order.bracket()`.

**Remarks.** How the trail will behave, exactly:

- `distance` is in the leg's own price units and is positive.
- The trail activates when the leg's profit per unit first reaches `activateAt`: the last price minus the average entry price for a long leg, the reverse for a short. With `activateAt` left out it activates on the leg's first fill.
- Once active it keeps the best price since activation: the highest for a long leg and the lowest for a short, from the bar's high or low on a confirmed bar, and from the last price on a bar still forming.
- Its level is the best price minus `distance` for a long leg, plus `distance` for a short. It never moves back.
- Where a leg has both a stop and an active trail, the more protective of the two is in force: the higher for a long, the lower for a short.

Until it lands, a trail is a few lines of [`var`](/script/language/persistence); [Trailing stops](/script/strategies/exits-and-brackets#trailing-stops) has one.

### leg.stopPrice() (planned, not available yet)

```
leg.stopPrice(name: string) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: absent when no stop is in force

Will return the stop level actually in force on the leg, whichever call set it, and absent when there is none. While a trail is active and tighter than the stop, it returns the trail's level, so plot this rather than your own variable to see what is really protecting the position.

### leg.targetPrice() (planned, not available yet)

```
leg.targetPrice(name: string) -> series number
```

| Parameter | Type | Default |
|---|---|---|
| name | string | required |

First value: absent when no target is in force

Will return the target level actually in force on the leg, and absent when there is none.

## How levels on a leg will be tested

These are the language's rules for the levels the engine holds, and they arrive with the calls above.

| Rule | Detail |
|---|---|
| When | Once per bar, after the script's own statements. The [book rules](/script/reference/books#when-the-rules-are-tested) are tested first, then each leg in declaration order: its stop, then its target, then its trail (activated, then advanced, then tested) |
| Reached | On a confirmed bar, when the bar's range reaches the level: `low <= level` for a long's stop and a short's target, `high >= level` for a long's target and a short's stop. On a bar still forming, against the last price only, and tested again when the bar closes |
| Both in one bar | When one bar's range contains both the stop and the target, the stop is taken. A bar is four prices and no path, and assuming the better outcome is how a backtest invents money |
| Fill | A stop sends a stop order at its level and a target a limit order at its level, so a backtest fills at the level. When the bar opens beyond the level, the fill is at the open |
| Costs | The declaration's slippage applies to a stop and not to a target |
| Order rules | The exit is an ordinary order: it is recorded like any other and obeys the tick rule, so a level off the tick is OS7006 |

Every change a level causes is recorded as a named event with the bar's time, the leg, the level and the price that crossed it: `legStopHit`, `legTargetHit`, `trailActivated` and `trailAdvanced`. Events reach the run's record and the log. No call reads one, because a script that branched on its own stop having fired would be deciding twice what the rule already decided once.

## Related

[Legs and books](/script/strategies/multi-leg-and-books), [Exits and brackets](/script/strategies/exits-and-brackets), [Orders](/script/strategies/orders), [Other instruments](/script/data/other-instruments), [book.*](/script/reference/books), [Strategy orders](/script/reference/strategy), [pos.*](/script/reference/position), [Glossary](/script/resources/glossary).


## book.*

Source: https://openalgo.in/script/reference/books

A strategy's **book** is every leg it has declared, taken together (a leg is one contract the strategy trades; see [leg.*](/script/reference/legs)). The `book` namespace will manage that whole position as one: enter all the legs in one decision, stop out on their combined profit, lock in profit as it grows, take entries only inside a time window, **square off** (close every leg) at a set time or before expiry, and stop trading for the day after a set loss.

It matters most for option structures on NFO. A short straddle or strangle is two options sold together, a call and a put, each hedging the other. Stopping each leg on its own is the classic way to take two losses on a day the pair was doing its job. **Measure the stop on the sum, never on a leg.** The book is the language's place for rules measured on the sum.

**Every entry on this page is planned.** In version 0.5.0 a strategy trades one leg, the instrument on its chart, and calling any `book.*` name is refused where you write it with OS2020. This page documents what each call will do, and shows the same rules written by hand for the one leg you can trade today.

## What you can write today

Most of the book rules can be written for a single position in a few lines. This strategy enters at most one long position a day, on a crossover inside a 09:30 to 11:00 window, and applies three rules measured on the position's profit in money: a money stop, a profit lock that activates and then advances, and a time exit from 15:00.

```openscript
version 1
strategy("Book rules by hand", overlay = true, precision = 2,
         capital = 500000, qtyType = "units", product = "intraday",
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

lots       = input(1,    "Lots", min = 1, max = 50)
stopMoney  = input(6000, "Stop, in money", min = 100)
activateAt = input(4000, "Lock profit once up this much", min = 100)
lockAt     = input(2000, "Then keep at least this much", min = 0)
stepMoney  = input(2000, "Raise the floor for every further", min = 100)
advance    = input(1500, "Raise it by", min = 0)

// The zone is written out so the windows also work in the Backtest panel.
zone       = "Asia/Kolkata"
lotUnits   = max(orElse(chart.lotSize, 1), 1)
pointValue = orElse(chart.pointValue, 1)

inWindow = session.isIn("0930-1100", zone)
exitTime = not session.isIn("0915-1500", zone)
newDay   = bar.isFirst or not date.isSameDay(time, time[1], zone)
goLong   = crossUp(ema(close, 9), ema(close, 21))

// The position's profit in money, valued at the close.
profit = pos.isFlat ? none : (close - pos.avgPrice) * pos.size * pointValue

var peak         = none
var lockFloor    = none
var enteredToday = false

if newDay
    enteredToday = false

// The floor appears once the peak reaches activateAt, and rises one advance
// for every further step the peak reaches. The peak never falls, so neither
// does the floor.
if pos.isFlat
    peak      = none
    lockFloor = none
else if not isNone(profit)
    peak = isNone(peak) ? profit : max(peak, profit)
    if peak >= activateAt
        lockFloor = lockAt + floor((peak - activateAt) / stepMoney) * advance

stopHit = profit <= -stopMoney
lockHit = not isNone(lockFloor) and profit <= lockFloor

if pos.isLong and (exitTime or stopHit or lockHit)
    close()
else if goLong and inWindow and pos.isFlat and not enteredToday
    buy(qty = lots * lotUnits, tag = "entry")
    enteredToday = true

// The two money levels, drawn as prices.
perPoint = pos.isFlat ? none : pos.size * pointValue
plot(pos.isFlat ? none : pos.avgPrice - stopMoney / perPoint, "Money stop", red, style = "step")
plot(isNone(lockFloor) ? none : pos.avgPrice + lockFloor / perPoint, "Profit floor", lime, style = "step")
```

The money amounts suit one lot of a NIFTY future on a 15-minute chart; scale them to your instrument, because on a single share of a stock they are never reached and every trade ends at the time exit. Remember too that the window only admits a trade on a bar where the crossover happens, so on a quiet day it trades nothing.

Two differences from the planned rules are worth knowing. A rule written in the script acts on the bar its condition is true and fills at the next fill point, so it exits at the next bar's open rather than at the level. And two rules cannot be written exactly by hand yet: the daily loss limit needs realised profit, which a script cannot read until `pos.netProfit` or `order.avgFill()` lands, and the expiry square-off needs the contract's expiry, which `chart.expiry` will supply and which is planned too.

To measure a two-leg structure on its sum today, open one leg's chart and read the other with `req.symbol()`. This study, put on the call's chart, draws the combined premium of a straddle (the call and the put at the same strike and expiry):

```openscript
version 1
study("Straddle premium", precision = 2)

putLeg = input("", "The put's symbol")

// "developing" pairs each chart bar with the put's bar at the same time; the
// default would hand back the put's previous bar.
putPrice = req.symbol(putLeg, chart.interval, close, mode = "developing")
premium  = close + putPrice

plot(premium, "Combined premium", orange, width = 2)
```

The line stays empty until you type the put's symbol into the study's settings, and on any bar where the put has no price, because the sum of a price and an absent value is absent. `mode = "developing"` is what makes the sum a sum of one instant: a read in the default mode at the chart's own interval is one bar behind, as [Other instruments](/script/data/other-instruments#which-bar-a-read-gives-you) explains. The read uses the chart's own exchange unless you name another. A strategy can trade the leg on its chart and stop it on that sum: script 12 in [Example scripts](/script/getting-started/example-scripts) is written that way, and that page explains what it still needs from /trading before it trades. The Backtest panel holds only the chart's own bars, so it refuses any script that reads another instrument, before the first bar, with OS6006; run it on the chart instead.

## The shape a book will take

Declared, a short strangle on NIFTY weekly options (a call two strikes above the money and a put two strikes below it, both sold) becomes two legs and a handful of rules. The block below is the planned shape, and version 0.5.0 refuses it with OS2020.

```openscript
version 1
strategy("Short strangle, one book", precision = 2,
         capital = 500000, qty = 1, qtyType = "lots", product = "intraday",
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

lots = input(1, "Lots per leg", min = 1, max = 50)

leg.relative("ce", "NIFTY", "option", expiryRank = 0, strikeOffset = 2,
             right = "call", exchange = "NFO", side = "sell", qty = lots)
leg.relative("pe", "NIFTY", "option", expiryRank = 0, strikeOffset = -2,
             right = "put", exchange = "NFO", side = "sell", qty = lots)

// Session rules.
book.entryWindow("0920-1030:12345")
book.exitAt("1500")
book.squareOffAtExpiry(15)
book.dailyLoss(15000)

// Combined rules, measured on the sum of both legs.
book.stop(6000)
book.target(9000)
book.lockProfit(4000, 2000, step = 2000, advance = 1500)
book.trailStopsToEntry(3000)

newDay = bar.isFirst or not date.isSameDay(time, time[1], "Asia/Kolkata")

var enteredToday = false
if newDay
    enteredToday = false

if not enteredToday and not book.isOpen
    book.enter(tag = "strangle")
    enteredToday = true

plot(book.profit, "Book profit", aqua, width = 2)
```

Each rule call sets a level that stays in force until it is replaced, so writing the rules at the top level, where they run on every bar, is the ordinary shape. Passing `none` removes a rule; unlike an order argument, an absent level is not refused.

## Two shapes

A strategy takes one of two shapes, and the shape decides what a combined rule means.

| Shape | Entered with | Legs open and close | Combined rules |
|---|---|---|---|
| **As a unit** | `book.enter()` and `book.exit()` | Together, in one decision | Allowed, and measured from the trade's start |
| **Per leg** | `leg.enter()` and `leg.exit()`, or `buy()`, `sell()`, `close()`, `exit()`, `order.place()` and `order.reverse()`, which are the same calls written the short way | Each on its own signal | Refused |

`book.profit` is measured from the last moment the book was flat. In a strategy entered as a unit that moment is the start of the current trade, because the book is flat between trades, so a combined stop is a stop on that trade. In a per-leg strategy the book may never be flat, and a combined stop would measure from a moment no rule chose and no reader could name, which is worse than no stop at all, because it looks like one. So two refusals will be made before the first bar:

- A file that calls `book.enter()` or `book.exit()` and also any per-leg entry or exit is refused.
- A file that calls `book.stop()`, `book.target()`, `book.lockProfit()` or `book.trailStopsToEntry()` without `book.enter()` is refused, and the fix names `leg.stop()` and `leg.target()`.

Both hold in a one-leg file too, so a script does not change meaning on the day it grows a second leg. A per-leg strategy uses a stop, a target and a trail on each leg, and the session rules below, which are measured from things a reader can name.

## Entering and exiting as a unit

### book.enter() (planned, not available yet)

```
book.enter(tag?: string = "") -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | "" |

First value: bar 0

Will enter the whole book in one decision: one order per declared leg, each on the leg's declared side and quantity. It is the entry for a structure whose legs only make sense together, such as a straddle.

### book.exit() (planned, not available yet)

```
book.exit(tag?: string = "") -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| tag | string | "" |

First value: bar 0

Will exit the whole book in one decision: one order for every leg that holds a position.

## Combined rules

These are measured on `book.profit`, in money, and each squares off every leg when it fires. They belong to a book entered as a unit.

### book.stop() (planned, not available yet)

```
book.stop(amount: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| amount | number | required |

First value: bar 0

Will square off every leg when the book's profit falls to `-amount`. `book.stop(6000)` means "take the whole structure off if this trade is six thousand down".

### book.target() (planned, not available yet)

```
book.target(amount: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| amount | number | required |

First value: bar 0

Will square off every leg when the book's profit reaches `amount`.

### book.lockProfit() (planned, not available yet)

```
book.lockProfit(activateAt: number, lock: number, step?: number = none, advance?: number = none) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| activateAt | number | required |
| lock | number | required |
| step | number | none |
| advance | number | none |

First value: bar 0

Will protect profit once it arrives: nothing happens until the book's profit first reaches `activateAt`, then a floor exists at `lock`, and with `step` and `advance` the floor rises by `advance` for every further `step` of profit reached. When the profit falls to the floor, every leg is squared off. `step` and `advance` are given together or not at all; one without the other is OS3009.

**Remarks.** Read `book.lockProfit(4000, 2000, step = 2000, advance = 1500)` as "once I am four thousand up, I keep at least two thousand of it, and for every further two thousand I reach, I raise that line by fifteen hundred":

| Best profit reached so far | Floor |
|---|---|
| Below 4,000 | None yet |
| 4,000 | 2,000 |
| 6,000 | 3,500 |
| 8,000 | 5,000 |
| 10,000 | 6,500 |

In general the floor stands at `lock + n * advance`, where `n` is the largest whole number for which the profit has reached `activateAt + n * step`. The floor never moves down. Waiting for `activateAt` keeps the lock from acting on a trade that never got going, and a floor that never falls keeps it from turning into a looser second stop halfway through a good day.

### book.trailStopsToEntry() (planned, not available yet)

```
book.trailStopsToEntry(at: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| at | number | required |

First value: bar 0

Will move every leg's stop to that leg's own entry price once the book is `at` in profit, turning a winning structure's stops into break-even stops.

## Session rules

These gate entries and square off on the clock. Unlike the combined rules, they suit both shapes.

### book.direction() (planned, not available yet)

```
book.direction(filter: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| filter | string | required (one of "long", "short", "both") |

First value: bar 0

Will restrict which sides an entry may take: `"long"`, `"short"` or `"both"`. An entry on a side the filter excludes is refused and recorded, not sent.

### book.entryWindow() (planned, not available yet)

```
book.entryWindow(spec: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| spec | string | required |

First value: bar 0

Will allow new entries only inside a window written `"HHMM-HHMM"` with an optional list of weekdays, 1 for Monday to 7 for Sunday: `"0920-1030:12345"` is 09:20 to 10:30, Monday to Friday, the same form `session.isIn()` takes. A window that does not parse is OS3008.

### book.exitAt() (planned, not available yet)

```
book.exitAt(time: string) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| time | string | required |

First value: bar 0

Will square off every leg at a time written `"HHMM"` in the chart's time zone, such as `"1500"` to be flat half an hour before the NSE close. A time that is not four digits is OS3008.

### book.squareOffAtExpiry() (planned, not available yet)

```
book.squareOffAtExpiry(minutesBefore?: number = 0) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| minutesBefore | number | 0 |

First value: bar 0

Will square off a leg `minutesBefore` minutes before its contract expires, so a strategy never holds an option or future into settlement, an event no price rule can see coming.

### book.dailyLoss() (planned, not available yet)

```
book.dailyLoss(amount: number) -> nothing
```

| Parameter | Type | Default |
|---|---|---|
| amount | number | required |

First value: bar 0

Will square off every leg and take no new entry for the rest of the day once the day's loss reaches `amount`. It tests `book.dayProfit`, which is measured from this session's open, so the limit means today and not the whole run.

**Remarks.** The end-of-day square-off is not a book call. It is the declaration's `closeOnSessionEnd` option, one spelling of one rule. In version 0.5.0 the option is accepted and not yet acted on, so write the exit in the script as well; [Exiting on the clock](/script/strategies/exits-and-brackets#exiting-on-the-clock) shows how.

## Reading the book

### book.profit (planned, not available yet)

```
book.profit: series number
```

First value: bar 0

Will read the book's profit in money: the open profit of every leg plus everything the strategy realised since the book was last flat. It is what the combined rules measure.

### book.dayProfit (planned, not available yet)

```
book.dayProfit: series number
```

First value: bar 0

Will read the same profit measured from this session's open, which is what `book.dailyLoss()` tests.

### book.isOpen (planned, not available yet)

```
book.isOpen: series bool
```

First value: bar 0

Will be true while any leg holds a position. `not book.isOpen` is the entry guard for a book entered as a unit.

## When the rules are tested

Every rule is evaluated once per bar, after the script's own statements for that bar have run, in this fixed order. The order is part of the language, so two engines running the same script close the same positions on the same bar.

1. The daily loss limit.
2. The exit time, then the end-of-day square-off, then the expiry square-off.
3. The combined stop, then the combined target.
4. The profit lock: activate the floor, then advance it, then test it.
5. The move of every stop to its entry.
6. Each leg in declaration order: its stop, then its target, then its trail, as the [leg.* page](/script/reference/legs#how-levels-on-a-leg-will-be-tested) describes.

A rule that squares the book off ends the sequence for that bar: the rules below it have nothing left to act on. The book rules come before the leg rules so that when a combined limit takes the whole book off, the record names the rule that did it.

## The named events

Every change a rule causes will be recorded as a named event, with the bar's time, the leg where there is one, the rule's level and the value that crossed it. After a bad day the log then says which rule fired, not only that the position closed.

| Event | Recorded when |
|---|---|
| `combinedStopHit` | The book's profit fell to the combined stop and the book was squared off |
| `combinedTargetHit` | The book's profit reached the combined target and the book was squared off |
| `lockProfitActivated` | The book's profit first reached `activateAt` and a floor now exists |
| `lockProfitFloorAdvanced` | The floor moved up a step |
| `lockProfitTriggered` | The book's profit fell to the floor and the book was squared off |
| `trailToEntryActivated` | Every leg's stop was moved to its own entry |
| `sessionEndSquareOff` | `closeOnSessionEnd` flattened the book at the session's close |
| `exitTimeSquareOff` | `book.exitAt()` flattened the book at its time |
| `expirySquareOff` | A leg was closed because its contract was about to expire |
| `dailyLossHit` | The day's loss reached the limit; the book is off and no entry is taken for the rest of the day |
| `entryRefused` | An entry was refused by the direction filter, the entry window or a daily loss already hit, naming which |

To read a log after a bad day, find the last square-off event of the day first: it names the rule and carries the level, so you see at once whether the level was the one you meant. Then read back to the last `entryRefused` to see why entries stopped. A `combinedStopHit` with no leg stop on the same bar is exactly right, because the book rule took the position off and the leg rules had nothing left to act on. Events reach the run's record and the log; no call reads one, because a script that branched on its own stop having fired would be deciding twice what the rule already decided once.

## Related

[Legs and books](/script/strategies/multi-leg-and-books), [Exits and brackets](/script/strategies/exits-and-brackets), [Other instruments](/script/data/other-instruments), [leg.*](/script/reference/legs), [pos.*](/script/reference/position), [Strategy orders](/script/reference/strategy), [Sessions and time](/script/data/sessions-and-time).


# Errors

## Reading an error

Source: https://openalgo.in/script/errors/overview

Every time you open or save a script in /trading, the compiler checks it and reports what it found as **diagnostics**: numbered errors and warnings, each tied to a line of your script. This page shows how to read one, where each part comes from, and what the code alone tells you before you read another word. It applies to every script written in OpenScript (also called OpenAlgo Script), study or strategy, and it ends with the eight code ranges and the page that documents each one.

## A diagnostic in the console

Here is a small RSI study with a slip on its last line. It computes the RSI into `r`, then plots a second RSI whose length is misspelt: `lenght` where the input is called `len`.

```openscript
// RSI with overbought and oversold levels, drawn in its own pane.
version 1

study("RSI", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)
r = rsi(close, len)

level(70, "Overbought", fade(red, 40))
level(50, "Middle", fade(gray, 60))
level(30, "Oversold", fade(lime, 40))

plot(rsi(close, lenght), "RSI", purple, width = 2)
```

Press Ctrl+S. The status bar at the bottom of the panel turns red and reads "1 error, so it will not run yet", and the console button at its left end shows 2. Click that button to open the **console**, the drawer under the editor, and it lists the two diagnostics, a warning and then an error:

```text
OS8010  line 7, column 1
r = rsi(close, len)
^
r is assigned at line 7 and never read.
Fix: Use the value, or delete the line.

OS2001  line 13, column 17
plot(rsi(close, lenght), "RSI", purple, width = 2)
                ^^^^^^
lenght is not defined at this point in the file.
Fix: Assign lenght above this line, move this line below its assignment, or correct the spelling to len.
```


The screenshot shows this script in the Scripts panel with the console open. The number of the line with the first error, 13, is red in the gutter, so you can find the line without counting.

The two diagnostics point at one slip. The script already holds the RSI in `r`, so the fix for both is to plot `r`. Save the corrected script below and the status bar reads "Ready", the console says "Nothing to report.", and the study can go on the chart:

```openscript
// RSI with overbought and oversold levels, drawn in its own pane.
version 1

study("RSI", precision = 2, range = [0, 100])

len = input(14, "Length", min = 2, max = 200)
r = rsi(close, len)

level(70, "Overbought", fade(red, 40))
level(50, "Middle", fade(gray, 60))
level(30, "Oversold", fade(lime, 40))

plot(r, "RSI", purple, width = 2)
```

## The parts of a diagnostic

The console shows the first five parts below for every diagnostic. The last two, the cause and the stage, are on the code's entry in these pages.

| Part | In the example | What it tells you |
|---|---|---|
| Code | `OS2001` | What kind of problem this is. The same mistake carries the same code in every release |
| Position | line 13, column 17 | Where the problem starts. The carets (`^`) under the copy of your line mark the exact characters |
| Message | lenght is not defined at this point in the file. | What went wrong, with the names and values from your script filled in |
| Fix | Assign lenght above this line, ... | What to change. Every diagnostic names a concrete action on your source |
| Severity | Error, shown in red | Whether it stops the script. An error does; a warning, shown in amber, never does |
| Cause | Under "What it means" on the code's entry | Why the rule exists and what usually leads to it, with a before and after example |
| Stage | `check`, a badge beside the code | When the problem was found, and so whether anything ran before it |

Three details make the parts easier to use.

- **The message is a template.** The error catalogue, the list of every code the language defines, stores each message with blanks, such as `{name} is not defined at this point in the file.`, and the compiler fills the blanks from your script. The console always shows the filled message. The range pages show the template, with each blank written as the word that names it.
- **The fix is yours to apply.** It names the replacement, the line to move or the call to wrap. The /trading editor shows it as text and never changes your script by itself.
- **A suggested name is the closest match, not a certainty.** When a fix offers a spelling, such as "correct the spelling to len", the compiler picked the known name nearest to what you typed. For a typo it is usually right. For a name that is simply wrong it is a guess, so read it before you take it.

## Errors and warnings

| | Error | Warning |
|---|---|---|
| Codes | OS1xxx to OS7xxx | OS8xxx |
| Colour in the console | Red | Amber |
| Status bar | "1 error, so it will not run yet" | "Ready, with 1 warning" |
| Stops the script | Yes. A script with a compile error cannot be applied to the chart or backtested. An error raised while a bar runs stops the script on that bar | Never |
| What to do | Fix it and save again | Read it. It describes code that runs and is almost never what you meant |

A script with errors is still saved when you press Ctrl+S, so nothing you typed is lost. It has no compiled program until it compiles, though, so it cannot be applied to a chart, backtested or deployed until the errors are fixed and it is saved again. See [The editor](/script/getting-started/the-editor#checking-and-the-console) for the console, the status bar and saving.

Warnings are documented on [OS8xxx Warnings](/script/errors/warnings). Two worth meeting early are [OS8001](/script/errors/warnings#os8001), a stateful call (one that keeps its own state from bar to bar, such as `ema()`) inside an `if`, and [OS8010](/script/errors/warnings#os8010), a name assigned and never read.

## The stage: when a problem is found

Every code belongs to one stage, shown as a badge beside it on the range pages. The stage says how far the script got before the problem was found. The **engine** is the part of OpenScript that runs a compiled script bar by bar, and the **host** is the application that feeds it, which on the /trading page is OpenAlgo.

| Stage | What is happening | Codes raised here | Where you see it |
|---|---|---|---|
| `lex` | Reading the characters of the file | Part of OS1xxx | The console, when you open or save the script |
| `parse` | Reading statements, blocks and brackets | Most of OS1xxx, and one OS5xxx limit | The console, when you open or save the script |
| `check` | Names, types, scope and how each call is written, before any bar runs | OS2xxx, OS3xxx, OS8xxx, and a few OS6xxx and OS7xxx | The console, when you open or save the script |
| `runtime` | Running one bar | OS4xxx, and parts of OS5xxx, OS6xxx and OS7xxx | When the script runs, on the chart or in a backtest |
| `host` | The host answering the engine: data, budgets and order destinations | Most of OS6xxx, and parts of OS5xxx and OS7xxx | Mostly when the script is loaded or runs. [OS6018](/script/errors/data#os6018) can also appear in the console |

The first three stages finish before the first bar. An error there means nothing ran and nothing was drawn, and it costs you one edit. The language moves as many mistakes as it can into those stages: a study that calls `buy()` is refused at `check` with [OS7001](/script/errors/orders#os7001), so it can never place an order on any bar, and a table written with 2.5 rows is refused at `check` with [OS3004](/script/errors/arguments#os3004).

A `runtime` error depends on the data. The same script can run cleanly on one NSE stock and stop on bar 30,000 of another. When it happens, the script stops on that bar: the bar's work is undone and no later bar runs. The engine never skips the bar and carries on, because a gap that nobody explains looks exactly like a gap the script meant.

A runtime error is not in the console, which reports what the compiler found when you saved, and it is not written on the chart either. On the /trading page:

- When you add a study that stops, a notice shows the code, the message and the fix.
- The **Objects** panel on the right-hand toolbar lists the study with the status Error. That is also where to look when a study already on the chart stops later, for example when a new bar arrives.

[Debugging](/script/writing/debugging) shows how to make values visible and find the first bar where a script goes wrong.

## The eight ranges

The first digit after `OS` says which kind of problem the diagnostic describes. It does not say how serious it is: that is the severity.

| Range | Kind | Covers | Severity | Codes |
|---|---|---|---|---|
| [OS1xxx](/script/errors/syntax) | Syntax | The text is not a program: characters, layout and grammar | Error | 29 |
| [OS2xxx](/script/errors/names-and-types) | Names and types | The program parses, and a name or a type does not work out | Error | 20 |
| [OS3xxx](/script/errors/arguments) | Arguments | A call or an option is wrong where it is written | Error | 24 |
| [OS4xxx](/script/errors/runtime) | Runtime | A bar produced a value the engine cannot act on | Error | 13 |
| [OS5xxx](/script/errors/limits) | Limits | A budget ran out: loops, memory, size or time | Error | 10 |
| [OS6xxx](/script/errors/data) | Data | Bars, instruments, timeframes and the answers to data requests | Error | 23 |
| [OS7xxx](/script/errors/orders) | Orders | An order could not be placed as written | Error | 19 |
| [OS8xxx](/script/errors/warnings) | Warnings | The script compiles and runs, and something in it is probably not meant | Warning | 19 |

Version 0.5.0 catalogues 157 codes. What each range means for your next move:

- **[OS1xxx Syntax errors](/script/errors/syntax).** Something in the text is not part of the language: a tab in the indentation, a semicolon, `&&` for `and`, a bracket that is never closed. Nothing has run. The fix is on the line reported, or on the line where the bracket or block it names was opened.
- **[OS2xxx Names and types](/script/errors/names-and-types).** The text is a program and its meaning does not work out: a name read before it is assigned or misspelt, a name declared twice, a number added to a string, `[]` on a value that keeps no history. Nothing has run.
- **[OS3xxx Arguments](/script/errors/arguments).** A call is wrong where it stands: too many arguments, an unknown argument name, a `plot` inside an `if`, a per-bar value in a setting that is fixed before the first bar. Nothing has run.
- **[OS4xxx Runtime errors](/script/errors/runtime).** A bar ran and produced something the engine cannot act on: an array index past the end, a history index that is not a whole number, a change to a drawing that was already deleted. This is the first range that depends on the data.
- **[OS5xxx Limits](/script/errors/limits).** A budget ran out: loop iterations on one bar, the size of an array, the size of the compiled program, the time one bar may take. [Limits](/script/writing/limits) lists every budget.
- **[OS6xxx Data](/script/errors/data).** The data is not what the script asked for: an unknown timeframe, a request finer than the chart, an instrument or exchange OpenAlgo does not know, bars out of order.
- **[OS7xxx Orders](/script/errors/orders).** An order could not be placed as written: an absent price, a quantity of zero, a price that is not on the instrument's tick, a bracket on the wrong side of the entry.
- **[OS8xxx Warnings](/script/errors/warnings).** The script runs. Read them anyway.

Numbers inside a range are given out in the order codes were added, not grouped by topic, and a code is never renumbered or reused. Searching for `OS2001` finds the same explanation in this release and in every later one. The range pages group related codes under headings so you can read them by topic.

## Working through a list of diagnostics

**Start with the earliest line.** One slip often produces several messages. This fragment is missing one `)` on its first line:

```openscript
x = max(close,
    open
plot(x, "X")
```

Pasted under a declaration, it gives five errors and a warning. The bracket is never closed ([OS1012](/script/errors/syntax#os1012)), so the compiler reads the `plot` line as more of the same statement: a continuation line that is not indented ([OS1028](/script/errors/syntax#os1028)), a third argument with no comma before it ([OS1014](/script/errors/syntax#os1014)), a `max` call with three arguments ([OS3001](/script/errors/arguments#os3001)), and an `x` read before its assignment has finished ([OS2001](/script/errors/names-and-types#os2001)). With no separate `plot` line left, `x` is never read either, which is the warning ([OS8010](/script/errors/warnings#os8010)). Close the bracket and all six go away.

**Fix, save, read again.** The editor checks when you save, not while you type, so after each fix press Ctrl+S and read the console afresh. Later messages often vanish with an earlier one.

**Fix errors first, then read the warnings.** A warning never stops the script, but each one describes code that is almost always a mistake.

**OS6018 beside another error.** In version 0.5.0 a few check errors, such as [OS3003](/script/errors/arguments#os3003) and [OS2005](/script/errors/names-and-types#os2005), bring [OS6018](/script/errors/data#os6018) ("The compiled program is malformed") onto the same line. Its message is long and technical; you can ignore it. Fix the other error and OS6018 goes with it.

**OS6018 on its own.** Its message then says the fault is in the compiler and asks you to report it together with the script. One case is known in version 0.5.0: a setting fixed before the first bar that does arithmetic on an input, such as `precision = input(2, "Decimals") + 1` or `width = w + 1` where `w` is an input. Pass the input on its own and put the arithmetic in its default: `precision = input(3, "Decimals")`.

**Look up the code.** Every code is on its range page with its message, a plain explanation of what usually causes it, the fix, and a before and after example. The before block is the shortest code that raises the code. It may use names such as `fast`, `trending` or a function `band` that a real script would define above it, so pasted on its own it also reports [OS2001](/script/errors/names-and-types#os2001) for those names. The after block is the same code, fixed. For problems described by what you see rather than by a code, use [Troubleshooting](/script/writing/troubleshooting).

## Specific codes and broader codes

Some codes take one case away from a broader code so that their fix can be precise. A condition that is a number could be reported as a general type mismatch, but it has its own code, [OS2011](/script/errors/names-and-types#os2011), whose fix tells you to write the test out. The broader code still covers every other case.

| Code | Takes this case | From |
|---|---|---|
| [OS1005](/script/errors/syntax#os1005) | An unknown escape sequence in a string | [OS1004](/script/errors/syntax#os1004) |
| [OS1027](/script/errors/syntax#os1027) | A colour literal that is not six or eight hexadecimal digits, such as `#fff` | [OS1001](/script/errors/syntax#os1001) |
| [OS1028](/script/errors/syntax#os1028) | A continuation line that is not indented past its statement | [OS1003](/script/errors/syntax#os1003) |
| [OS1029](/script/errors/syntax#os1029) | A name written against a number, such as `2fast` | [OS1001](/script/errors/syntax#os1001) |
| [OS2011](/script/errors/names-and-types#os2011) | A condition that is not a bool | [OS2003](/script/errors/names-and-types#os2003) |
| [OS2012](/script/errors/names-and-types#os2012) | Ternary arms of different types | [OS2003](/script/errors/names-and-types#os2003) |
| [OS2013](/script/errors/names-and-types#os2013) | An array literal that mixes types | [OS2003](/script/errors/names-and-types#os2003) |
| [OS2019](/script/errors/names-and-types#os2019) | A type that cannot be an array element | [OS2016](/script/errors/names-and-types#os2016) |
| [OS2020](/script/errors/names-and-types#os2020) | A name that is planned and not implemented yet | [OS2001](/script/errors/names-and-types#os2001) |
| [OS3019](/script/errors/arguments#os3019) | A plot handle where a drawing object belongs | [OS3011](/script/errors/arguments#os3011) |
| [OS3020](/script/errors/arguments#os3020) | Something other than two plots passed to `fill()` | [OS3011](/script/errors/arguments#os3011) |
| [OS3022](/script/errors/arguments#os3022) | An input whose title is another input's name | [OS3017](/script/errors/arguments#os3017) |
| [OS3023](/script/errors/arguments#os3023) | An order that names a leg in a file with no legs | [OS3008](/script/errors/arguments#os3008) |
| [OS3024](/script/errors/arguments#os3024) | An input written in place with an empty title | [OS3021](/script/errors/arguments#os3021) |

**Related.** [The editor](/script/getting-started/the-editor), [Debugging](/script/writing/debugging), [Troubleshooting](/script/writing/troubleshooting), [Limits](/script/writing/limits), [Glossary](/script/resources/glossary)


## OS1xxx Syntax errors

Source: https://openalgo.in/script/errors/syntax

Syntax errors are the first thing the compiler looks for. They come from the two stages that run before anything else: the lexer (`lex`), which reads the characters of your file into words, numbers and symbols, and the parser (`parse`), which reads those into statements and blocks. An OS1xxx code means the text itself is not a program yet, so no name has been looked up, no bar has run and nothing has been drawn. The fix is always local: the line reported, or the line where the bracket or block it names was opened.

Most syntax errors come from three places. Text copied from a web page, a chat message or a document brings invisible spaces and curly quotes with it. Operators typed out of habit, such as `&&` or `!`, are spelled as words in OpenScript. And indentation is how the language marks a block, so a line one space out of place changes the structure.

## What well-formed code looks like

This study follows every layout rule on this page. It marks the high of the NSE opening range, the first fifteen minutes from 09:15 to 09:30, and signals the bar whose close first moves above it. Paste it into a new script to have a known-good reference beside your own.

```openscript
version 1
study("Opening range breakout", overlay = true)

// A comment runs from // to the end of its line.
var rangeHigh = none
inRange = session.isIn("0915-0930")
rangeStarts = inRange and not orElse(inRange[1], false)

// A block is the lines indented under its header, four spaces per level.
if rangeStarts
    rangeHigh = high
else if inRange
    rangeHigh = max(rangeHigh, high)

// An operator at the end of a line continues the statement on the next.
breakout = not isNone(rangeHigh) and close > rangeHigh and
        close[1] <= rangeHigh[1]

plot(rangeHigh, "Range high", orange)
if breakout
    signal("Breakout")
```

The rules it follows, and the code you get for breaking each one:

| Rule | Code when broken |
|---|---|
| `version 1` is the first line that is not blank or a comment | [OS1021](#os1021) |
| One statement per line, and no `;` anywhere | [OS1007](#os1007), [OS1018](#os1018) |
| Indent blocks with spaces, the same amount on every line of a block | [OS1002](#os1002), [OS1003](#os1003) |
| A header such as `if` has an indented body under it | [OS1010](#os1010) |
| A continued line is indented past the line its statement began on | [OS1028](#os1028) |
| Comments start with `//` | [OS1026](#os1026), [OS1001](#os1001) |
| Logic is written `and`, `or` and `not` | [OS1001](#os1001) |
| One comparison per expression | [OS1008](#os1008) |
| Every `(` and `[` is closed by its own kind of bracket | [OS1012](#os1012), [OS1013](#os1013) |

## Characters and literals

Outside strings and comments, the lexer accepts only plain ASCII letters and digits, the space, the newline and the language's own punctuation. Inside a string or a comment any character is fine, so `"Target ₹"` and `// rupee target` both work. When [OS1001](#os1001) refuses a character, its message names the replacement from this table:

| You wrote | Write instead |
|---|---|
| `!` | `not` |
| `&&` | `and` |
| `\|\|` | `or` |
| `^` or `**` | `pow(a, b)` |
| `++` | `a += 1` |
| `{` or `}` | indentation, which is how a block is written |
| A no-break space, an em space or another wide space | a plain space |
| A tab between two words on a line | a plain space |
| A curly quotation mark, single or double | a straight quote |
| A letter with an accent in a name | the plain ASCII spelling |
| `#`, `$` or `@` outside a colour | nothing: delete it, or move the text into a string |

### OS1001 Unexpected character

Unexpected character {char}. {suggestion}

Fix: Delete the character or replace it with the plain ASCII spelling the message names: a plain space for a non-breaking space, a straight quote for a typographic one, not for !, and for &&, or for ||, pow(a, b) for ^.

You typed a character that the language does not use outside a string or a comment. Often you cannot see it: a no-break space or a curly quotation mark arrives with code copied from a web page, a chat message or a document, and it looks exactly like a normal space or quote. The rest are operators typed out of habit, such as `!`, `&&`, `||`, `^`, `**` and `++`, or braces around a block. The message quotes the character and names the replacement. For an invisible or unusual character it also gives the Unicode number, such as U+00A0, and for spaces, tabs and quotation marks a name such as "a no-break space". A tab at the start of a line is [OS1002](#os1002) instead.

When the character is invisible, delete the text around the reported column and type it again by hand. For a pasted string, retyping its two quotes is usually enough. A `#` typed to start a comment lands here too: comments start with `//`.

### OS1027 Malformed colour literal

{written} is not a colour: a colour literal is # and six or eight hexadecimal digits.

Fix: Give it six hexadecimal digits, or eight where the last two are the alpha byte. A named colour or rgb(r, g, b) says the same thing without the digits.

A `#` followed by hexadecimal digits is a colour, and it needs exactly six digits, as in `#ff8800`, or eight, where the last two set the transparency, as in `#ff880080`. The short forms `#fff` and `#ff88` are not accepted, and neither is a character outside `0` to `9` and `a` to `f` (capitals `A` to `F` are fine). Because the colour is dropped from the line, the console usually shows [OS1022](#os1022) beside this error; correcting the colour clears both. If hex digits are not what you want, a named colour such as `orange` or `rgb()` says the same thing.

This is the case of [OS1001](#os1001) for colours. It has its own code because deleting the `#`, the advice OS1001 gives, would throw away a colour you nearly had right.

### OS1029 A name written against a number

{written} is neither a number nor a name: the number literal ends at {number}, and a name cannot begin with a digit.

Fix: Write the value in a form the language has: a decimal literal, or 0x and hexadecimal digits. There is no binary form and no octal form, and an underscore separates digit groups only where a digit follows it. Where a number and a name were meant as two things, put an operator between them.

A name cannot start with a digit, and a number cannot have letters attached to it. The usual causes are a variable called something like `2fast`, a unit typed after a quantity (`10k`, `5m`), a binary or octal literal (`0b1011`, `0o17`), and an underscore at the end of a number (`1_000_`). Numbers are written in decimal, such as `1_000_000` or `2.5e-4`, or in hexadecimal with `0x`, and an underscore may only sit between two digits.

Rename the variable so it starts with a letter (`fast2`), and write quantities in full: `10000`, not `10k`. When you meant a number followed by a name, put an operator between them.

### OS1004 Unterminated string literal

This string literal opens with {quote} and the line ends before a matching {quote}.

Fix: Close the string with a matching {quote} before the end of the line, and join text across lines with + and a continuation.

A string opens and closes on the same line, with the same kind of quote: `"BUY"` or `'BUY'`. This error is reported at the opening quote when the line ends before a matching one, which almost always means the closing quote is missing. The unclosed string also swallows the `)` of the call it sits in, so [OS1012](#os1012) usually appears beside it and goes away when the quote is added.

A string cannot span lines. To build a long message, close each piece and join them with `+`, ending the line with the `+` so the statement continues.

### OS1005 Unknown escape sequence

{sequence} is not an escape sequence.

Fix: Double the backslash to write one literally, or use one of the escapes the language defines.

Inside a string, a backslash starts an escape sequence: `\n` for a new line, `\t` for a tab, `\r`, `\0`, `\\` for a backslash, `\"` and `\'` for quotes, and `\u` followed by exactly four hexadecimal digits for any character, such as `\u20b9` for the rupee sign. A backslash followed by anything else is refused rather than guessed at. It most often comes from a file path or a pattern pasted with single backslashes: write each backslash as `\\`.

### OS1026 Block comment

{marker} does not open or close a comment. A comment is written // and runs to the end of its line.

Fix: Write // instead, and give every line of a commented region its own //, which every editor does with one keystroke.

OpenScript has line comments only: `//` starts one and it runs to the end of the line. `/*` and `*/` are not comment markers, so the compiler reports them where they sit instead of letting an unclosed comment hide the rest of the file. To comment out several lines, put `//` at the start of each one.

### OS1007 Semicolon

; is not part of the language.

Fix: Delete the ; and put the second statement on its own line.

A statement ends at the end of its line, so there is nothing for a semicolon to do, and a semicolon at the end of a line is refused as well as one between two statements. Delete it, and give each statement a line of its own.

## Indentation and blocks

A block is the lines indented under a header: `if`, `else`, `for`, `while`, `switch`, `case`, `default`, or a function whose body starts on the next line. The block ends at the first line indented as far as its header, or less. Indent with spaces, four per level by convention. Blank lines and lines that hold only a comment never affect a block, so they may sit at any indentation.

### OS1002 Tab in indentation

This line is indented with a tab.

Fix: Replace the leading tabs with spaces. Four spaces per level is the convention and the formatter's output.

The line is indented with a tab. A tab is drawn at a different width in every editor, so a block marked with tabs could mean one thing on your screen and another on someone else's, and the language accepts spaces only. In the /trading editor the Tab key moves the focus out of the editor rather than typing anything, so a tab almost always arrives in pasted code. Replace each leading tab with four spaces.

### OS1003 Indentation does not match this block

This line is indented {found} spaces; the block opened at line {line} is indented {expected}.

Fix: Indent this line to {expected} spaces to keep it in the block, or to {line}'s own indentation to end the block here.

Every line of one block must start at exactly the same column, and a single space more or less counts. The message says how many spaces this line has, how many its block has, and which line opened the block. Line it up with the lines above it to keep it in the block, or with the header to end the block there.

A continued statement, one that carries on from the line above because of an open bracket or a trailing operator, follows a different rule: see [OS1028](#os1028).

### OS1028 A continuation line is not indented past its statement

A continuation line must be indented more deeply than the line its statement began on; line {line} is indented {statement} and this line is indented {found}.

Fix: Indent this line further than the {statement} spaces on line {line}. Four more is the convention and the formatter's output.

A statement continues onto the next line when a bracket is still open, when the line ends with an operator, a comma, `?`, `:` or `=`, or when it ends with a backslash. The continued line must be indented further than the line the statement began on, so that nobody reading the file can mistake it for a new statement. Indent it; any amount deeper than the first line works, and lining it up under the opening bracket reads well.

This is the case of [OS1003](#os1003) for continued lines. It has its own code because a continuation opens no block, so the block rule's message would describe a block that is not there.

### OS1010 Block header with no body

{header} opens a block, and the next line is not indented more deeply.

Fix: Indent the body under the header, or delete the header line if the body is genuinely empty.

A header line promises an indented body, and the next line is not indented more deeply. This is nearly always a body that lost its indentation when it was pasted. Indent the body under the header.

`if`, `else`, the loops, `case` and `default` have no one-line form, so even a body of one statement goes on its own indented line. A function can be written on one line by putting its body after `=>`: `fn barRange() => high - low`. Blank and comment lines do not count as a body.

### OS1016 else does not follow an if

This else is indented {found} spaces and the nearest if is indented {expected}.

Fix: Line the else up with its if, at {expected} spaces.

An `else` belongs to the `if` at exactly its own indentation. At any other indentation there is no `if` for it to pair with, and the compiler does not guess which one you meant. Line the `else` up with its `if`. An `else if` is written as two words on one line and is not indented further than the `if`.

An `else` with no `if` above it at all, such as one written after a `for` block, is reported the same way. When the `else` is indented by a stray space or two, [OS1003](#os1003) usually appears on the same line; lining it up clears both.

## Brackets and expressions

### OS1012 Bracket is never closed

The {bracket} opened at line {line} is never closed.

Fix: Add the matching closer at the end of the argument list on line {line}.

An open `(` or `[` carries the statement onto the following lines, so when its closer is missing the compiler reads everything after it as part of one long statement. The error is reported at the opening bracket, which is where to look. It usually brings a crowd of other errors from the lines below, such as a continuation that is not indented or a missing comma; add the closer and they go with it. [Reading an error](/script/errors/overview#working-through-a-list-of-diagnostics) shows one missing bracket producing six diagnostics.

### OS1013 Mismatched closing bracket

Found {found} where {expected} was expected, closing the {opener} opened at line {line}.

Fix: Change {found} to {expected}, which is what closes the {opener} opened at line {line}.

A call closes with `)`, and a history index or an array closes with `]`. This error means a bracket was closed with the other kind, such as `sum(values]` or `close[1)`. The message names the bracket it found, the one it expected and the line the opener is on. Change the closer to match its opener.

### OS1014 Missing comma between arguments

Two arguments run together; a comma is missing before {token}.

Fix: Put a comma between the two arguments.

Two values sit side by side inside a call with no comma between them. It usually comes from editing a call: a comma deleted by accident, as in `plot(ema(close, 9) "EMA", aqua)`, or a named argument added without one, as in `plot(close, "C" color = aqua)`. Put the comma back. The compiler reports it rather than guessing where the comma should go.

### OS1022 Expression expected

An expression was expected after {token}.

Fix: Supply the missing operand, or delete the trailing {token}.

A line ends with something that needs more after it (an operator, a comma, `=` or an opening bracket) and nothing usable follows. It is usually a half-finished edit, or a stray `+` or `,` left at the end of a line. Supply the missing value, or delete the trailing symbol.

It also appears beside another error on the same line when that error removed part of the expression, as with a malformed colour ([OS1027](#os1027)). Fix the other error first.

### OS1015 Ternary with no second arm

This ? has no matching :

Fix: Give the ternary both arms, and use none for the arm that should draw nothing.

The conditional operator, `condition ? a : b`, always produces a value, so it needs both of its arms. A one-armed version is usually a plot meant to draw on some bars only. Use `none` for the arm that should draw nothing: `plot(ready ? value : none, "Value", aqua)`. A plot draws a gap wherever its value is `none`.

### OS1008 Chained comparison

A comparison cannot be chained: {op1} is already applied before {op2}.

Fix: Split it with and, naming the middle value twice: a < b and b < c.

`30 < r < 70` reads naturally, but it can be read two ways, and a language that places orders does not guess. An expression holds at most one comparison, and that applies to `==` and `!=` as much as to `<` and `>`. Split it with `and`, writing the middle value twice: `30 < r and r < 70`.

The console usually shows [OS2003](/script/errors/names-and-types#os2003) on the same line as well, because the first comparison produces a `bool` and the second then compares that `bool` with a number. Splitting the comparison clears both.

### OS1018 More than one statement on a line

Unexpected {token} after the end of this statement.

Fix: Put {token} and what follows it on its own line, or supply the operator that was meant to join them.

The compiler finished reading a statement and found more on the same line. Either an operator is missing between two values (`x = close open`), there is one closing bracket too many (`ema(close, 9))`), or two statements share a line. Add the operator you meant, delete the extra bracket, or move the second statement to its own line.

### OS1006 Assignment used as a condition

= assigns a value, and a condition needs a comparison.

Fix: Write == to compare, or move the assignment to its own line above the if.

`=` stores a value and `==` compares two. A condition needs a comparison, so `if len = 14` is refused instead of quietly storing 14 and treating the test as true. The same applies to a `while` condition. Write `==`, or, if you did mean to store a value, do it on its own line above the `if`.

## Statements and names

### OS1009 break or continue outside a loop

{word} is only valid inside a for or a while body.

Fix: Move it inside the loop body, or write return to leave a function early.

`break` leaves the innermost `for` or `while` loop, and `continue` skips to its next pass. Outside a loop body there is nothing for either to act on. To leave a function early, use `return`. To skip some work on some bars, put that work under an `if`; plots stay at the top level and take `none` on the bars they should skip.

### OS1011 var with no initial value

var {name} has no initial value.

Fix: Give it a starting value; var {name} = none is the empty start.

`var` declares a value that keeps its content from one bar to the next, and its starting value is part of the declaration, so no bar can ever read it empty by accident. Write `var total = 0` to start from zero, or `var runningHigh = none` to start with no value and fill it on a later bar. See [Persistence](/script/language/persistence).

### OS1017 case or default in the wrong place

{word} is only valid inside a switch, and default must be its last arm.

Fix: Move the arm inside the switch block, and put default after every case.

`case` and `default` are the arms of a `switch` and exist only inside one, indented under it. `default` catches everything the cases above it did not, so it must be the last arm: a `case` after it could never run. Move the arms under their `switch`, with `default` at the bottom.

### OS1019 Reserved word used as a name

{word} is a reserved word and cannot be used as a name.

Fix: Rename it; {suggestion} keeps the meaning and is not reserved.

Reserved words belong to the language and cannot name a variable, a function or a function parameter. The ones people reach for most often are `color`, `step`, `type`, `in` and `number`. A few, such as `type`, `map` and `import`, are reserved for features planned for later versions, so that adding those features cannot break a script written today. The fix suggests a name that keeps your meaning, such as `colorValue`. The full list is on [Keywords](/script/reference/keywords).

A named argument label is not a variable, so `plot(x, "X", color = aqua)` is fine. Writing `bool(x)` or `number(s)` to convert a value lands here too, because the conversions are `toBool()` and `toNumber()`.

### OS1020 Incomplete for header

A for header needs = start to end or in array; found {token}.

Fix: Write for i = 0 to size(values) - 1 for a counted loop, or for v in values to visit elements.

A `for` loop has two forms and no others. A counted loop names a start and an end, `for i = 0 to 9`, optionally followed by `step`, and runs with both ends included. A loop over an array names the array, `for price in prices`. A comma between the bounds, or a header with no `to`, is this error. See [Control flow](/script/language/control-flow).

### OS1021 The version declaration is not first

version must be the first line that is not blank and not a comment; line {line} came before it.

Fix: Move the version line to the top of the file, above the study or strategy declaration.

`version 1` must be the first line of the file, above the `study()` or `strategy()` declaration. Only blank lines and comments may come before it. The version is read before anything else so that the script is always compiled by the right version of the language. Move the line to the top.

### OS1023 A function declared inside a block

fn {name} is declared inside a block, and a function is declared at the top level of the file.

Fix: Move the whole declaration out to the top level of the file, and call {name} from inside the block.

Functions are declared at the top level of the file, never inside an `if`, a loop, a `switch` arm or another function. Move the whole `fn` declaration out to the top level and call it from inside the block. A function may be called on a line above its declaration, so it can sit anywhere at the top level. See [User functions](/script/language/functions).

### OS1024 Assignment to an indexed element

An assignment writes to a name, and this target is an index into {name}.

Fix: Change an array element with set({name}, i, v), or assign to a plain name: the past of a series is computed and never written.

There is no assignment into brackets. On an array, change an element with `set()`: `set(prices, 0, close)`. On a series such as `close`, `[1]` reads a bar the engine has already computed, and a past bar can never be rewritten. If you want a value of your own, assign it to a plain name.

### OS1025 Assignment to a member

An assignment writes to a name, and this target is the member {member} of {name}.

Fix: Assign to a plain name. Version 1 has no member assignment: a dot reads a member, and the members a script can reach are facts and functions the library and the host supply.

A dot reads a member of a namespace such as `chart`, `bar` or `session`, and every member is a fact supplied by the library, the instrument or the position. A script cannot write to any of them, and version 1 has no user-defined records whose fields you could set. Assign the value to a plain name of your own: `tick = 0.05`, not `chart.tickSize = 0.05`. When the member does not exist either, [OS2009](/script/errors/names-and-types#os2009) appears beside this error, as it does for the example below: `chart` has a `tickSize` member, not `tickStep`.

**Related.** [Reading an error](/script/errors/overview), [Script structure](/script/language/script-structure), [Keywords](/script/reference/keywords), [Operators](/script/language/operators), [Control flow](/script/language/control-flow), [OS2xxx Names and types](/script/errors/names-and-types)


## OS2xxx Names and types

Source: https://openalgo.in/script/errors/names-and-types

OS2xxx codes come from the checker, the `check` stage that runs after the file has been read and before the first bar. By then the text is a well-formed program, and what fails is its meaning: a name read before it is assigned or spelt differently from its assignment, the same name declared twice, a number where a condition belongs, `[]` on a value that keeps no history. Nothing has run yet, so these are still cheap to fix: the change is on the line reported, or on the line the message points to.

Two ideas explain most of this range. First, names follow the order of the file: a script runs top to bottom on every bar, so a name must be assigned above the line that reads it, and a name first assigned inside a block stays inside that block. Second, types never convert by themselves: a number is not a string and `0` is not `false`. When you mean a conversion you write it: `text()` turns a number into a string, `toNumber()` reads a number from a string, and a comparison such as `count > 0` turns a number into a `bool`.

## A script that gets names and types right

This study marks bars on any NSE or MCX instrument where volume jumps above a multiple of its recent average. Every name is assigned before it is read, every condition is a bool, and the number in the marker is turned into text with `text()`.

```openscript
version 1
study("Volume spike", overlay = false)

len = input(20, "Average length")
mult = input(2.0, "Spike multiple")

// Assigned at the top level, above every line that reads it.
avgVolume = sma(volume, len)
spike = volume > mult * avgVolume

plot(volume, "Volume", gray, style = "histogram")
plot(avgVolume, "Average volume", orange)

// A condition is a bool, and text is built with text().
if spike and not spike[1]
    signal("Spike " + text(volume / avgVolume, 1) + "x")
```

The rules it follows, and the code you get for breaking each one:

| Rule | Code when broken |
|---|---|
| A name is assigned above the line that reads it, in the same block or an enclosing one | [OS2001](#os2001) |
| One variable per name: no second declaration inside a function, and no reuse of a built-in name | [OS2002](#os2002) |
| A name keeps the type of its first value, and types never mix on their own | [OS2003](#os2003) |
| A condition is a `bool` | [OS2011](#os2011) |
| `[]` reads the history of a series (a value with one entry per bar): a built-in series, a top-level name, or a call that returns a series | [OS2004](#os2004) |
| One `study()` or `strategy()` declaration per file | [OS2007](#os2007), [OS2008](#os2008) |

## Names and scope

A name is declared by its first assignment. At the top level it belongs to the whole file from that line on. Inside an `if`, a loop, a `switch` arm or a function body it belongs to that block. Assigning inside an `if` or a loop to a name that already exists above updates that name, so there is only ever one variable with a given name. See [Variables and scope](/script/language/variables-and-scope).

### OS2001 Name is not defined here

{name} is not defined at this point in the file.

Fix: Assign {name} above this line, move this line below its assignment, or correct the spelling to {suggestion}.

The name is not known on the line that reads it. There are three usual causes. It is misspelt, and the fix suggests the closest name the compiler knows, as with `lenght` for `len`. It is assigned further down the file, and since the script runs top to bottom on every bar, a line cannot read a name that has not been assigned yet. Or it was first assigned inside an `if`, a loop or a function and is read outside it, where it does not exist: assign it at the top level first, even as `none`, and let the block update it.

A prefix in front of a library function also lands here, as in `ta.ema(close, 9)`, because the whole dotted name is unknown: everyday functions are written bare, `ema(close, 9)`. Your own functions are the one exception to the order rule: an `fn` may be called on a line above its declaration.

### OS2002 The name already exists in an enclosing scope

{name} is already declared at line {line}, so a second one cannot be declared here.

Fix: Rename this one, or drop the inner declaration and let the assignment update the {name} at line {line}.

OpenScript never has two variables with one name. A function body can read a name from the file but not declare one of its own with the same name, so `len = 9` inside a function, when the file already has a `len`, is refused, and so is a parameter called `len`. Built-in names such as `close`, `high`, `ema`, `aqua` and `plot` count as declared already: `close = 5`, `ema = 9` or a parameter called `high` all land here, and the message then gives the earlier line as `built-in`.

Rename the new name. Inside an `if` or a loop, assigning to a name from above is not this error: it updates that name, which is usually what you wanted.

### OS2006 Assignment to a loop variable

{name} is this loop's variable and cannot be assigned inside the body.

Fix: Use break to leave early, or keep a separate name for the value the body changes.

A `for` loop owns its variable: the counter in `for i = 0 to 9`, or the element in `for price in prices`. The body may read it but not assign to it, because a loop whose counter the body can move no longer runs the number of times its header says. To leave the loop early, use `break`. To work with a changed value, copy it into a name of your own first.

### OS2009 Unknown member of a namespace

{namespace} has no member named {member}.

Fix: Use one of {namespace}'s members; {suggestion} is the closest match to what was written.

A namespace such as `bar`, `chart`, `session`, `date`, `str`, `math`, `pos`, `order` or `draw` holds a fixed set of members, and the one written is not among them. It is a typo or a member of a different namespace. The fix names the closest member, which is right for a small slip (`session.isFirst` suggests `isFirstBar`) and only a guess for a bigger one (`chart.lot` suggests `chart.now`, when the member wanted is `chart.lotSize`). The reference lists every member of every namespace.

### OS2010 This name is not a function

{name} is {type}, not a function, so it cannot be called.

Fix: Remove the argument list to read the value, or call the function that was meant: {suggestion}.

Built-in values such as `volume`, `high` and `bar.index` are read bare, without brackets. An argument list after one usually means a function was remembered under the wrong name: `volume(20)` for the 20-bar average volume is `sma(volume, 20)`. Remove the brackets to read the value, or call the function you meant. The function the fix names is only the closest spelling to the value you wrote, so it is often unrelated: for `volume(20)` the console suggests `blue`. Look up the function you meant in the reference.

### OS2020 Name is planned, not implemented

{name} is planned and is not implemented in this version.

Fix: Compute what {name} would give from names the library implements today, or take the line out until a version implements it. Do not reach for the nearest name that compiles: a neighbour computes something else, and a plot that quietly changes meaning is worse than one that refuses to compile.

The name is part of the language and is not implemented in this release. It is spelt correctly, and no line above it would help. The reference marks such names as Planned. Examples in version 0.5.0 include `kama()`, `session.isOpen` and the multi-leg `leg.fixed()`.

Compute the value from functions that exist today, or take the line out until a release implements it. Do not swap in a function with a similar name just because it compiles: it computes something else, and a plot that quietly changes meaning is worse than one that refuses to compile.

## Types

Every value has a type: `number`, `string`, `bool`, `color`, an array, or one of the object types. A name takes its type from its first definite value and keeps it. See [Types and values](/script/language/types-and-values).

### OS2003 Types do not match

{leftType} and {rightType} do not mix here.

Fix: Convert explicitly: text(x) for a string, or text(x, decimals) to fix the decimals. Where no conversion applies, write the value in the type the line needs, or give the second value a name of its own. A plot, fill or level handle converts to nothing: leave it named at the top level, pass it to fill(), and use draw.line() or draw.box() where the script needs something it can keep.

Two values whose types do not fit together meet in one expression. The language never converts on its own: `"count: " + 5` is refused because `+` joins two strings or adds two numbers, never one of each, and `1 + true` is refused because a `bool` is not a number. Convert explicitly: `text()` turns a number into text (`text(x, 2)` with two decimals), `toNumber()` reads a number from a string, and `toBool()` turns an absent value into `false`.

The same code covers a name whose type changes. `len = 14` followed later by `len = "fourteen"` is refused, because `len` became a number on its first line. A first value of `none` fixes no type, which is how `var stop = none` can later hold a price. It also covers a plot handle used as a number: `p = plot(close, "Close")` names the plot itself, not its value, so `p + 1` has nothing to add. Keep the value in a name of its own and plot that.

### OS2011 A condition must be a bool

This condition is {type}; a condition must be bool or none.

Fix: Write the test out: {name} > 0 for a count, isNone({name}) for absence, {name} != "" for a string.

`if`, `while`, `not`, both sides of `and` and `or`, and the `?` of a ternary need a `bool`, or an absent value, which takes the false branch. No rule turns a number or a string into true or false, so write the test you mean: `hitCount > 0` for a count, `mode != ""` for a string, `isNone()` for absence. `if volume` is the same mistake; write `if volume > 0`.

This is the case of [OS2003](#os2003) for conditions, with a fix that names the test to write.

### OS2012 The two arms of the ternary have different types

The arms of this ? : are {leftType} and {rightType}.

Fix: Make both arms the same type with text() or toNumber(), or use none for the empty arm.

The two arms of `condition ? a : b` produce one value, so they must be the same type. `none` fits either arm, and it is how an arm says there is nothing here, as in `plot(ready ? value : none, "Value")`. Otherwise make both arms produce the same kind of value, for example two strings, or convert one arm with `text()` or `toNumber()`.

### OS2013 An array literal mixes types

This array holds {firstType} at index 0 and {otherType} at index {index}.

Fix: Make every element {firstType}, or keep two arrays and index them together.

An array holds elements of one type, which is what lets `size()`, `sum()`, `avg()` and `sort()` mean one thing. A literal such as `["RSI", 14, "EMA", 9]` mixes strings and numbers, and it is usually two lists that belong side by side: keep one array of names and one of lengths, and index them together.

## History

### OS2004 This value has no history

{expr} has no history, so [] cannot read a past value of it.

Fix: If the value is a per-bar number computed inside a block or left as a temporary, assign it at the top level of the file and read the history of that top level name. If it is a declaration handle, a runtime object or a library fact that is not a series, no name gives it a history: assign what you want to look back at to a top level name of its own, and read that.

`[1]` reads the value a name had one bar ago, and the engine keeps that history only for values it records on every bar: built-in series such as `close` and `volume`, names assigned at the top level of the file, calls that return a series (so `ema(close, 9)[1]` works), and series parameters of your own functions. Everything else keeps no history: a name first assigned inside an `if` or a loop, a bracketed expression such as `(close - open)[1]`, a plot handle, a drawing object, and a fixed fact about the instrument such as `chart.lotSize`.

Assign the value to a top-level name and read that name's history: `body = close - open`, then `body[1]`. See [Bars and history](/script/language/bars-and-history).

## Arrays and annotations

An annotation states a type after a colon, as in `var hits: array<number> = []` or `fn band(src: series number)`. Annotations are optional; the compiler checks them when you write them.

### OS2015 The element type of this empty array is unknown

An empty array literal needs its element type from an annotation or from a first use.

Fix: Annotate the declaration, var hits: array<number> = [], or put the first element in with push(), unshift(), insert() or set() and let the type be read from that call.

An empty array literal, `[]`, has no elements to take its type from. The compiler looks for an annotation, or for the first `push()`, `unshift()`, `insert()` or `set()` in the file that puts an element into the array, and uses that. With neither, it does not guess. Annotate the declaration, `var hits: array<number> = []`, or add the call that fills it.

### OS2016 Unknown type in an annotation

{type} is not a type.

Fix: Use number, string, bool, color, array<T>, or an object type (line, label, box, polyline, table), with series in front for a per-bar value.

The types you can write in an annotation are `number`, `string`, `bool`, `color`, `array<T>` and the object types `line`, `label`, `box`, `polyline` and `table`, with `series` in front for a value that changes per bar. There is no `int` or `float`: a length, a bar count and a price are all `number`. `plot`, `fill` and `level` cannot be written in an annotation either, because a plot handle can never be stored, passed or returned.

### OS2019 This type cannot be an array element

array<{type}> is not a type: {type} cannot be an array element.

Fix: Use an element type an array holds: number, string, bool, color, line, label, box, polyline or table. A plot cannot be kept anywhere, so declare each plot at the top level and give it its own name.

An array element is a `number`, a `string`, a `bool`, a `color`, or a `line`, `label`, `box`, `polyline` or `table` object. An array of arrays, an array of series and an array of plots are all refused. To keep several plots, declare each one at the top level with its own name. To keep several values per entry, keep one array per value and index them together. This is the case of [OS2016](#os2016) for array elements.

## Functions

### OS2005 Recursive call

{name} calls itself: {cycle}.

Fix: Rewrite it as a loop, or split the work into two functions that do not call each other.

A function cannot call itself, directly or through other functions, and the message names the cycle, such as `a calls b calls a`. Each place in the file that calls a function keeps its own state for `var` and for stateful calls such as `ema()`, set up before the first bar, and a recursive call would need an unknown number of them. Rewrite the work as a `for` or `while` loop. In version 0.5.0 the console also shows [OS6018](/script/errors/data#os6018) on the function's line, with a long technical message; it clears when the recursion does.

### OS2014 A function cannot be used as a value

{name} is a function, and version 1 has no function values.

Fix: Call {name} with its arguments and use the value it returns.

In version 1 a function is not a value: you cannot assign `ema` to a name, keep it in an array or pass it to another function, as in `sma(ema, 9)`. Call it with its arguments and use what it returns. To let the user choose between moving averages, pass a string input to `ma()`, which takes the kind of average as its third argument.

### OS2017 A function with this name is already declared

{name} is already declared as a function at line {line}.

Fix: Rename one of them, or give the one function a default argument that covers both uses.

A file has one function per name. Two `fn` declarations with the same name would make each call's meaning depend on its arguments, so the second is refused. Rename one of them, or merge the two by giving the extra parameter a default value: `fn band(src, len = 20) => sma(src, len)` covers both `band(close)` and `band(close, 50)`.

### OS2018 Duplicate parameter name

{name} appears twice in this parameter list.

Fix: Rename the second parameter, so every name in the list appears once.

Every parameter of a function needs its own name, because the body refers to parameters by name and a named argument in a call picks one by name. Rename the repeated parameter.

## The declaration

Every script has exactly one declaration, `study()` or `strategy()`, and by convention it sits directly under `version 1`. It gives the chart the title, the pane and the settings it needs before the first bar. See [Declarations](/script/reference/declarations).

### OS2007 The file has no declaration

A file needs one study() or strategy() declaration before any other statement.

Fix: Add study("Name") as the first statement, or strategy("Name") if the file places orders.

The file has no `study()` or `strategy()` line. Without one there is no title for the legend and no entry for the indicator list. Add `study("Name")` under `version 1`, or `strategy("Name")` if the script will place orders.

### OS2008 More than one declaration

This file already declares {kind} at line {line}.

Fix: Delete this declaration and move the options you wanted onto the one at line {line}.

The file declares twice. A strategy accepts every option a study does, so a file that seems to need both is a strategy: keep the `strategy()` line, move any options from the `study()` line onto it, and delete the `study()` line. See [Strategies overview](/script/strategies/overview).

**Related.** [Reading an error](/script/errors/overview), [Variables and scope](/script/language/variables-and-scope), [Types and values](/script/language/types-and-values), [Absent values](/script/language/absent-values), [Bars and history](/script/language/bars-and-history), [User functions](/script/language/functions), [Collections](/script/language/collections), [OS3xxx Arguments](/script/errors/arguments)


## OS3xxx Arguments

Source: https://openalgo.in/script/errors/arguments

OS3xxx codes come from the checker, the `check` stage that runs before the first bar, and every one of them is about a call site: the way a function, a declaration, an input or `limits()` is called on one line. The script has parsed and its names resolve. What is wrong is the number of arguments, an argument's name, a value the parameter does not accept, or a call written somewhere it cannot be. Nothing has run yet.

A large part of this range protects the study's fixed shape. Before the first bar the chart builds the legend, the pane and its scale, the settings dialog and the list of plots, so everything that feeds them must be known before any data arrives: declaration options, a plot's title and style, every `input()`, the size and corner of a table. Per-bar values are welcome everywhere else, including a plot's colour.

## A script whose calls are all well formed

This study draws a Bollinger-style band on any chart, NIFTY futures or an NSE stock alike, and raises an alert on the bar where the close crosses above the upper edge.

```openscript
version 1
study("Band breakout", overlay = true, precision = 2)

len = input(20, "Length", min = 2)
mult = input(2.0, "Deviations")
showBand = input(true, "Show the band")

basis = sma(close, len)
dev = mult * stdev(close, len)

// Positional arguments first, named arguments after them.
upper = plot(showBand ? basis + dev : none, "Upper", aqua)
lower = plot(showBand ? basis - dev : none, "Lower", aqua)
plot(basis, "Basis", orange, width = 2)

// fill takes the two plot handles.
fill(upper, lower, color = fade(aqua, 88))

// Per-bar decisions use calls that may sit anywhere.
if crossUp(close, basis + dev)
    alert("Close above the upper band", id = "upperBreak")
```

A **handle** is what `plot()`, `plotCandles()`, `fill()` and `level()` return: a name for that part of the study, such as `upper` above, which `fill()` takes to find its two plots. Where each kind of call may be written:

| Call | May be written | Otherwise |
|---|---|---|
| `plot`, `plotCandles`, `fill`, `level`, `table` | At the top level only | [OS3006](#os3006) |
| `input` | At the top level only | [OS3007](#os3007) |
| `limits` | Once, as the first statement after the declaration | [OS3014](#os3014) |
| `signal`, `alert`, `background`, `barColor`, `cell`, `print`, the `draw` functions, order calls | Anywhere, including inside `if`, loops and functions | |

## Argument lists

Arguments are positional or named. Positional arguments come first, in the order of the signature; named arguments follow, in any order, using the parameter names the reference shows. Each parameter is filled once, and a parameter with a default may be left out.

### OS3001 Wrong number of arguments

{name} takes {expected} arguments and {found} were given.

Fix: Pass the arguments the signature names: {signature}.

The call passes more arguments than the function takes. The fix prints the signature, with a `?` after each parameter that may be left out, so you can see what the function expects. A common cause is an argument that belongs to a neighbouring call, such as a plot's colour typed inside the indicator it plots: `plot(ema(close, 9, aqua), "EMA")` gives `ema()` three arguments. Move it to the call it belongs to. A call with too few arguments is [OS3012](#os3012) instead.

### OS3002 Unknown named argument

{name} has no argument called {argument}.

Fix: Use one of {names}; {suggestion} is the closest to what was written.

Named arguments are matched against the parameter list by exact spelling, and a name that is not on the list would otherwise be silently ignored. The fix lists the names that exist and the closest one to what you wrote. Two frequent causes are British spelling (the parameter is `color`, not `colour`) and capitals: names are case sensitive, so `colorup` is not `colorUp`.

### OS3005 Positional argument after a named one

A positional argument cannot follow a named one.

Fix: Name this argument too, or move it in front of the first named argument.

Once an argument is named, the arguments after it must be named too, because their position no longer says which parameter they fill. Name the argument, or move it in front of the first named one.

### OS3013 Argument given twice

{argument} is given twice at this call site.

Fix: Delete one of the two, keeping the value that was meant.

One parameter is filled twice, either by position and then by name, as in `ema(close, 9, len = 21)`, or by the same name written twice. Either way a reader cannot tell which value wins, so the call is refused. Delete the one you did not mean.

### OS3012 A required argument is missing

{name} requires {argument}, which has no default.

Fix: Pass {argument} at the call site, as the example shows: {example}.

A parameter with no default was left out, and there is no value the engine could sensibly invent for it. The usual ones are a title and a source: `study()` and `plot(close)` need a title, as in `plot(close, "Close")`, and an indicator needs its source, as in `rsi(close)` rather than `rsi()`. Most indicators need a length as well, as in `ema(close, 9)`; the reference shows which parameters have a default. The fix shows the call with its required arguments.

### OS3011 Argument has the wrong type

{name}'s {argument} is {expected}; {found} was given.

Fix: Convert the value with text(), toNumber() or toBool(), or pass an expression of type {expected}.

The argument's type is not the one the parameter takes: text where a number goes, as in `ema(close, "9")`, a number where a title goes, or a number where a colour goes. Nothing converts on its own. The one allowance is that a plain value may be passed where a series is expected, and it is then read as that same value on every bar, which is why `crossUp(close, 22500)` works: the level 22500 becomes a series that is 22500 on every bar. Pass a value of the right type, or convert it with `text()`, `toNumber()` or `toBool()`.

## Values a parameter accepts

### OS3004 Argument is not a valid whole number

{name}'s {argument} must be a whole number {range}; {found} was given.

Fix: Pass a whole number inside {range}, and wrap a computed value in floor() or round().

Some arguments count things, so they must be whole numbers inside a fixed range: a table's rows and columns (1 or more), a cell's row and column (0 or more), a plot's `precision` (0 to 10) and `offset`, an `rgb()` channel (0 to 255), and a loop's `step`, which must not be 0 because that loop could never finish. When you write the number in the script, the checker tests it and refuses a fraction rather than rounding it, because 2.5 rows is a mistake in the script. Round a value you compute with `floor()` or `round()`.

Only the arguments listed above are tested before the first bar. A whole number the script computes, such as the length in `sma(close, len / 2)`, is tested when the bar runs instead, as runtime error [OS4003](/script/errors/runtime#os4003). In version 0.5.0 that includes an indicator's length written as a number: `sma(close, 14.5)` compiles, and the study stops on its first bar with OS4003.

### OS3008 The value is not valid for this parameter

{argument} accepts {values}; {found} is not one of them.

Fix: Use one of {values}; {suggestion} is the closest to what was written.

The parameter takes one value from a fixed list, because each value selects a different behaviour, and the one written is not on the list. Without this check it would be ignored or replaced by a default without a word. The message lists every accepted value and suggests the closest. Common cases are a plot's `style` (`"line"`, `"step"`, `"histogram"` and others), a table's `position` (`"topRight"`, not `"top_right"`), and a strategy's `qtyType`, which is `"units"`, `"lots"`, `"cash"` or `"equityPercent"`: count NFO futures and options in `"lots"` and NSE cash shares in `"units"`. A strategy's `product` is `"intraday"` or `"overnight"`.

### OS3010 Two arguments that cannot both be given

{first} and {second} set the same thing two ways.

Fix: Keep one of the two: an absolute price or a distance from the entry, one colour for the whole band or a colour for each side, and on a leg described as a future neither right nor strikeOffset.

Two arguments that set the same thing were both given, and any rule for choosing between them would surprise somebody, so the call is refused. On `exit()`, a target is either an absolute `limit` price or a `profit` distance from the entry, and a stop is either an absolute `stop` price or a `loss` distance: give one of each pair, not both. On `fill()`, a band takes one `color` for both sides, or `colorUp` and `colorDown` for each side, never both kinds. Keep the one you meant.

### OS3019 A declaration handle in an object argument

{name}'s {argument} is {expected}; {found} is a declaration handle, which has no value at run time.

Fix: Pass an object the script created with draw.line(), draw.box() or draw.label(), and change a plot's own appearance through the arguments of the plot call instead.

`plot()`, `plotCandles()`, `fill()` and `level()` return a handle: a name for that part of the study, which `fill()` uses to find two plots. It is fixed before the first bar and is not an object you can change later. `cell()` and the drawing setters such as `draw.setColor()` take an object the script created while bars ran: a table, or a line, label, box or polyline.

To change a plot's colour bar by bar, give the plot call a per-bar colour: `plot(close, "Close", color = close > open ? lime : red)`. To draw something you can move or restyle later, create it with `draw.line()`, `draw.box()` or `draw.label()`. This is the case of [OS3011](#os3011) for plot handles.

### OS3020 fill needs two declared plots

fill's {argument} is {found}; it takes a plot declared by plot() or plotCandles().

Fix: Plot both edges at the top level, name each one, and pass the two names to fill().

`fill()` shades the area between two plots already on the chart, so its first two arguments are plot handles: the names you gave two `plot()` or `plotCandles()` calls. A bare expression such as `basis + dev` is not a plot, and neither is a `level()`. Plot both edges at the top level, name each one, and pass the two names, as the example at the top of this page does. See [Fills](/script/visuals/fills). This is the case of [OS3011](#os3011) for `fill()`.

## Fixed before the first bar

The chart builds a study's legend, pane, axis and settings dialog once, before the first bar runs. Everything that feeds them must be known at that moment: a literal such as `2`, arithmetic on literals such as `1 + 1`, or an `input()`, which is read from the settings before the first bar. The reference marks each such parameter as fixed before the first bar in its parameter table.

| Written as | Accepted |
|---|---|
| `precision = 2` or `precision = 1 + 1` | Yes |
| `precision = input(2, "Decimals")` | Yes |
| `dp = input(2, "Decimals")`, then `precision = dp` | Yes |
| `var dp = 2`, then `precision = dp` | No, [OS3003](#os3003): a `var` can change on later bars |
| `precision = round(close / 1000)` | No, [OS3003](#os3003): it depends on the bar |
| `precision = input(2, "Decimals") + 1` | No. In version 0.5.0 this reports only [OS6018](/script/errors/data#os6018); put the arithmetic in the default instead: `input(3, "Decimals")` |

### OS3003 This option must be a constant

{option} is read once, before the first bar, so it cannot depend on bar data.

Fix: Use a literal, or make it tunable with an input(): {option} = input(2, "{option}").

The argument feeds something that is built before the first bar, and the value you wrote can change from bar to bar. This covers every option of `study()` and `strategy()`, a plot's title, width and style, a signal's `color`, `at` and `shape`, a table's title, size and corner, and an alert's `id`, `title` and `frequency`. Use a literal, or an `input()` so the user can change it in the settings dialog. A name assigned from an input works too, as in `dp = input(2, "Decimals")` followed by `precision = dp`, but a `var` does not, because a `var` can change on later bars. The table above shows each form.

The fix's sample input always uses 2 as its default; write the default that suits the option, such as `input(true, "Overlay")` for `overlay`. A plot's `color` is not on this list, so a colour chosen per bar is fine. In version 0.5.0 the console also shows [OS6018](/script/errors/data#os6018) on the same line, with a long technical message; it goes away when this error is fixed.

### OS3006 This call must be at the top level

{name} defines part of the study's fixed shape and cannot appear inside {construct}.

Fix: Move the call to the top level and hide it per bar by passing none: plot(cond ? value : none, ...). Inside a request expression, read the value first and draw with it afterwards. A leg is not hidden by passing none: declare it at the top level and decide per bar whether to send it an order.

`plot`, `plotCandles`, `fill`, `level` and `table` declare the fixed shape of the study: its columns, bands, horizontal lines and grids. The legend and the settings dialog list them before the first bar, so they cannot sit inside an `if`, a loop, a `switch` arm or a function body, where they would exist on some bars and not others. To show a plot on some bars only, keep it at the top level and give it `none` on the others: `plot(trending ? ema20 : none, "EMA 20", aqua)`. A plot draws a gap wherever its value is `none`.

The same code covers a drawing or an alert inside the expression of a `req.timeframe()` or `req.symbol()` read. That expression is evaluated on the other timeframe's or instrument's bars, where there is no bar of this chart to draw on. Read the value first, then draw or alert with it on the next line.

### OS3007 input() must be at the top level

input() builds one row of the settings dialog, which is read once before the first bar.

Fix: Move the input() to the top level and use the name it assigns inside the block.

Each `input()` is one row of the settings dialog, and the dialog exists before any bar runs, so a row cannot appear or vanish with the data, and a saved setting needs a row that is always there. Put every `input()` at the top level of the file, including the ones used only inside a function or an `if`, and read its name inside the block. See [Inputs](/script/inputs/inputs).

### OS3009 This option needs another option to be set

{option} = {value} requires {required}.

Fix: Set {required} in the declaration, or choose a value of {option} that stands on its own.

Some options only mean something together. An alert with `frequency = "everyUpdate"` fires on every update of the bar that is still forming, but by default signals, alerts and orders act only on confirmed bars, so on its own that frequency would never have an update to fire on. Add `onUnconfirmed = true` to the declaration, or use the default frequency, `"oncePerBar"`. See [Realtime and confirmation](/script/language/realtime-and-confirmation).

### OS3016 range must be a low and a high

range is [{low}, {high}]; it takes two numbers and the first must be below the second.

Fix: Write two numbers, lowest first: range = [0, 100].

The `range` option of a declaration fixes the scale of the study's own pane, and it is written as two numbers in square brackets, the lower first: `range = [0, 100]` for an oscillator such as RSI. A reversed pair such as `[100, 0]`, a list with one number or two equal numbers leave no scale to draw. A bare number with no brackets, `range = 50`, is [OS3011](#os3011) instead.

## limits()

`limits()` sets two of the engine's budgets for one script: `loops`, the number of loop turns it may run on one bar, and `history`, how many past bars the engine keeps. See [Limits](/script/writing/limits).

### OS3014 limits() is in the wrong place

limits() appears at most once, immediately after the declaration; this one is at line {line}.

Fix: Move the limits() line directly under study(...) or strategy(...), and merge two calls into one.

`limits()` is the first statement after the `study()` or `strategy()` declaration (blank lines and comments may sit between them), and it appears only once, so that anyone reading the file sees its budgets at the top. Move it up, and merge two calls into one: `limits(loops = 5_000_000, history = 5000)`.

### OS3015 limits() takes literal numbers

limits() is read before the first bar, so {option} must be a literal number.

Fix: Write the number: limits(loops = 50_000_000).

The engine sets aside room for these budgets before the first bar runs, so each one must be a number written out in full, such as `5_000_000`. Even arithmetic on numbers, such as `1000 * 1000`, is refused, and an `input()` is refused too, because a budget a settings dialog could change is a budget nobody can see by reading the script.

## Names and settings keys

Plots, levels, inputs and alerts are identified by name. The legend and the settings dialog label rows with them, saved settings are stored under them, and an alert subscription is kept under the alert's `id`. That is why each must be unique, and why an input needs a name or a title to be stored under. The rule is per kind: a plot and a level may share a title, but two plots may not.

### OS3017 Two of these share a name

{kind} names must be unique in a file; {name} is also used at line {line}.

Fix: Rename one of them so each name appears once.

Two plots, two levels, two inputs or two alerts in one file share a name. Their rows in the legend and the settings dialog would be indistinguishable, and one would overwrite the other's saved settings. Two alerts with one `id` would leave a subscription attached to whichever of the two was kept. Rename one of them so each name appears once, such as `"EMA fast"` and `"EMA slow"`.

### OS3018 The default is not in the options list

This input's default {default} is not one of options {values}.

Fix: Add {default} to options, or make the default one of the listed values.

An input with `options` is a dropdown, and its default is the entry selected when the settings dialog opens, so the default must be one of the options. Check the spelling and the capitals: `"Fast"` and `"fast"` are different strings. The options themselves are strings.

### OS3021 An input written in place has no title

An input() assigned to no name is named by its title, and this one has no title written as a string literal.

Fix: Give it a title written as a string literal: input(2, "Precision").

An input written directly inside another call, rather than assigned to a name, has no name to store the user's setting under, so its title is used as its key as well as its label. That title must be a string written in quotes inside the call; a name holding a string does not count. Give it one, `input(2, "Precision")`, or assign the input to a name first and use the name.

### OS3024 An input written in place has an empty title

An input() assigned to no name is named by its title, and this one's title is empty.

Fix: Give the title something to say: input(2, "Precision").

This is [OS3021](#os3021) with a title that is present but empty. An empty string can neither label the row nor serve as its key, and a second input written the same way would share that empty key. Give the title something to say. An input assigned to a name may have an empty title, because the name labels the row, so `len = input(14, "")` compiles.

### OS3022 Two inputs share a settings key

This input is keyed by its title {name}, which is already the name of the input at line {line}.

Fix: Give this one a title of its own, or rename the input at line {line}.

An input assigned to a name is stored under that name, and an input written in place is stored under its title. Here the title of one input spells the name of another, so both rows would share one stored setting and nothing would decide which row gets it. Give the in-place input a title of its own, or rename the other input. This is the case of [OS3017](#os3017) for settings keys.

## Orders

### OS3023 An order names a leg this file does not declare

{name} was given a {argument}, and this file declares none.

Fix: Leave {argument} out. A file that declares no leg has exactly one, and every order acts on it.

An order call was given a `leg` argument, but the file declares no legs. A strategy with no legs trades exactly one instrument, the one on the chart, and every order acts on it, so a leg name there names nothing. Remove the `leg` argument. Declaring legs with `leg.fixed()` or `leg.relative()` for multi-leg option positions is planned and not available in version 0.5.0; see [Legs and books](/script/strategies/multi-leg-and-books).

**Related.** [Reading an error](/script/errors/overview), [Declarations](/script/reference/declarations), [Inputs](/script/inputs/inputs), [Plots](/script/visuals/plots), [Fills](/script/visuals/fills), [Limits](/script/writing/limits), [OS2xxx Names and types](/script/errors/names-and-types), [OS4xxx Runtime errors](/script/errors/runtime)


## OS4xxx Runtime errors

Source: https://openalgo.in/script/errors/runtime

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](/script/strategies/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](/script/strategies/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](/script/errors/arguments#os3004) for some whole-number arguments, such as the rows of a `table()`, and [OS3008](/script/errors/arguments#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.

```openscript
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.

| Code | What it catches | In version 0.5.0 |
|---|---|---|
| [OS4001](#os4001) | A history index that is fractional or negative | Raised |
| [OS4002](#os4002) | A history read deeper than `limits(history = n)` keeps | Raised |
| [OS4003](#os4003) | A length, count or position that is fractional, or below what the function accepts | Raised |
| [OS4004](#os4004) | An array index outside the array | Raised, and also covers OS4006 and OS4008 |
| [OS4005](#os4005) | A setter on a deleted drawing object | Raised |
| [OS4006](#os4006) | Taking an element from an empty array | Not raised yet: OS4004, or absent |
| [OS4007](#os4007) | A reversed or out of range slice | Not raised yet: the slice is shortened, or OS4003 for a negative bound |
| [OS4008](#os4008) | A table cell outside the table | Not raised yet: OS4004 |
| [OS4009](#os4009) | A colour channel out of range | Not raised yet: the channel is clamped |
| [OS4010](#os4010) | A calendar field out of range | Not raised yet: the date rolls over |
| [OS4011](#os4011) | A string position outside the string | Not raised yet: a shorter or empty string |
| [OS4012](#os4012) | A computed name outside the accepted set | Not raised yet: absent, or a fallback, depending on the call |
| [OS4013](#os4013) | A loop bound that is absent | Raised |

## History reads

`x[n]` reads the value of `x` as it stood `n` bars ago. See [Bars and history](/script/language/bars-and-history).

### OS4001 History index is not usable

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

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

`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.

### OS4002 History index is deeper than the retained depth

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

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

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](/script/writing/limits#the-retained-history-depth).

## Whole numbers and names

### OS4003 A whole number was required here

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

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

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.

### OS4012 That value is not one of the accepted names

{argument} accepts {values}; {found} was computed on this bar.

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

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](/script/errors/arguments#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.

## Arrays

An array holds the elements your script put into it, numbered from 0 to `size - 1`. See [Collections](/script/language/collections).

### OS4004 Array index out of range

Index {index} is outside {name}, which holds {size} elements.

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

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](#os4006)), and so does a `cell()` written outside a table's grid, with the index given as a row and column pair ([OS4008](#os4008)).

### OS4006 The array is empty

{name} cannot take an element from an empty array.

Fix: Test size(arr) > 0 before the call.

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](#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.

### OS4007 Slice range is invalid

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

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

`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](#os4003). Clamp both bounds yourself, as the fix below shows, so the script does not depend on any of that.

## Drawing objects and tables

### OS4005 The drawing object no longer exists

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

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

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](/script/visuals/lines-and-boxes).

### OS4008 Table cell is outside the table

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

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

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](#os4004), whose message gives the row and column pair and the number of cells. Declare the table with the shape you write. See [Tables](/script/visuals/tables).

## Colours, dates and strings

### OS4009 Colour channel is out of range

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

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

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](/script/errors/arguments#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](/script/visuals/colors).

### OS4010 Calendar field is out of range

{field} is {found}; it runs {range}.

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

`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](/script/data/sessions-and-time).

### OS4011 String position is outside the string

Position {index} is outside a string of {length} characters.

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

`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.

## Loops

### OS4013 A loop bound is absent

This loop's {bound} is absent on this bar.

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

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](/script/language/control-flow) and [Absent values](/script/language/absent-values).

**Related.** [Reading an error](/script/errors/overview), [OS5xxx Limits](/script/errors/limits), [Debugging](/script/writing/debugging), [Bars and history](/script/language/bars-and-history), [Absent values](/script/language/absent-values), [Warmup](/script/language/warmup)


## OS5xxx Limits

Source: https://openalgo.in/script/errors/limits

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.

| When | Codes | What happens |
|---|---|---|
| When the script compiles | [OS5005](#os5005) for nesting in the source | Shown in the console under the editor. Nothing runs |
| When the program loads, before bar 0 | [OS5003](#os5003), [OS5004](#os5004), [OS5006](#os5006), [OS5009](#os5009), and [OS5005](#os5005) for call depth | The 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 runs | [OS5001](#os5001), [OS5002](#os5002), [OS5007](#os5007), [OS5008](#os5008), [OS5010](#os5010) | The run stops at that bar, as for any [runtime error](/script/errors/runtime#when-they-appear) |

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](#os5003), [OS5004](#os5004), [OS5006](#os5006), [OS5007](#os5007) and [OS5009](#os5009) come only from hosts that set their own, such as a server running many strategies for many people, or your own integration.

| Budget | Default | Changed by the script | Code |
|---|---|---|---|
| Loop turns per bar, all loops together | 2,000,000 | Yes, `limits(loops = n)` | [OS5001](#os5001) |
| Elements in one array | 1,000,000 | No | [OS5002](#os5002) |
| Characters in one string | 100,000 | No | [OS5008](#os5008) |
| Drawing objects held at once | 10,000 | No | [OS5010](#os5010) |
| Nesting in the source, and nested calls | 128 levels, and 64 calls | No | [OS5005](#os5005) |
| Time per bar, state regions, data requests, program size, `limits()` values | No ceiling unless the host sets one | No | [OS5007](#os5007), [OS5004](#os5004), [OS5006](#os5006), [OS5009](#os5009), [OS5003](#os5003) |

[Limits](/script/writing/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.

```openscript
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

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

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

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](/script/language/control-flow).

### OS5007 The bar took too long

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

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

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](/script/writing/profiling).

## Memory: arrays, strings and drawings

### OS5002 The array is too large

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

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

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.

### OS5008 The string is too long

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

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

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.

### OS5010 Too many drawing objects

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

Fix: 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.

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](/script/visuals/lines-and-boxes).

## Program shape: nesting, state and size

### OS5005 Nesting is too deep

{construct} is nested {found} deep and the ceiling is {max}.

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

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](/script/errors/names-and-types#os2005)), so the call depth of any program is known before the first bar.

### OS5004 The program needs more state regions than the engine allows

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

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

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](/script/language/functions).

### OS5009 The program is too large

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

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

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.

## Host ceilings

### OS5003 The host refused this limits() value

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

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

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.

### OS5006 Too many outstanding data requests

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

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

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](/script/data/higher-timeframes).

**Related.** [Limits](/script/writing/limits), [Profiling and speed](/script/writing/profiling), [OS4xxx Runtime errors](/script/errors/runtime), [Reading an error](/script/errors/overview), [Control flow](/script/language/control-flow), [Collections](/script/language/collections)


## OS6xxx Data

Source: https://openalgo.in/script/errors/data

This page covers the OS6xxx codes of OpenScript (also called OpenAlgo Script): the errors about the data a script is given and the data it asks for. They cover timeframes written in a request, other instruments, the bars the engine receives, the facts about an instrument, the compiled program a host loads, and the settings of a backtest run. Many of them are not mistakes in your script at all but answers from the host or faults in the data, and the explanations below say so wherever that is the case, so you know whether to change the script or look elsewhere.

## When they appear

| When | Codes | What happens |
|---|---|---|
| When the script compiles | [OS6001](#os6001), [OS6003](#os6003) | Shown in the console under the editor. Nothing runs |
| When the program loads, before bar 0 | [OS6001](#os6001) for a timeframe from an input, [OS6002](#os6002), [OS6004](#os6004), [OS6006](#os6006), [OS6012](#os6012), [OS6015](#os6015) to [OS6019](#os6019) | The study or run is refused whole and nothing is drawn. On a chart, /trading shows the code and the message in a notice |
| When the bars arrive, or while they run | [OS6010](#os6010), [OS6011](#os6011), [OS6005](#os6005) | The run stops at that bar, as for any [runtime error](/script/errors/runtime#when-they-appear) |
| When the host answers a request | [OS6007](#os6007), [OS6008](#os6008), [OS6009](#os6009), [OS6014](#os6014) | Nothing stops. The read is absent, and `req.error()` returns the message |
| Before a backtest's first bar | [OS6020](#os6020), [OS6021](#os6021), [OS6023](#os6023), and [OS6022](#os6022) on a replay | The run is refused and nothing is computed. Of these, only OS6021 can appear in the Backtest panel, which shows the code and the message |
| Not raised in version 0.5.0 | [OS6013](#os6013) | The shape it describes is refused earlier, when the script compiles |

The **host** named throughout this page is the application the engine runs inside. It supplies the bars and the facts about the instrument, and answers requests for other data. In OpenAlgo it is the /trading page for charts and backtests, and the strategy runner on the OpenAlgo server for a deployed strategy.

The request answers deserve a note of their own. A read that the host cannot answer does not stop the study: everything that does not depend on it keeps drawing, the read is absent, and `req.error()` on the read's name returns the message with the host's reason. A study that shows that reason, as the example below does, never leaves you guessing at an empty line. See [Other instruments](/script/data/other-instruments).

## A request written to avoid them

This study colours the bars by the daily trend on an intraday chart. The timeframe comes from an input, everything computed on the daily bars is inside the request expression, and a table in the corner says whether the daily data arrived.

```openscript
version 1
study("Daily trend filter", overlay = true)

tf = input("1D", "Higher timeframe", kind = "interval")
len = input(20, "Average length", min = 1, max = 200)

// Everything computed on the daily bars goes inside the request expression,
// and the timeframe comes from an input, so both are fixed before bar 0
// (OS6003, OS6013).
dailyAvg = req.timeframe(tf, ema(close, len))
dailyUp = req.timeframe(tf, close > ema(close, len))

plot(dailyAvg, "Daily average", orange, style = "step")
barColor(isNone(dailyUp) ? none : (dailyUp ? lime : red))

// A read the host could not answer is absent, and req.error says why
// (OS6007, OS6008, OS6009, OS6014).
why = req.error(dailyAvg)
status = table("Status", 1, 1, position = "topRight")
if bar.isLast
    cell(status, 0, 0, why != "" ? why : "Daily data ready")
```

## Timeframes

A timeframe is written as a string: a count and a unit, or a bare number of minutes. See [Timeframes](/script/data/timeframes) and [Higher timeframes](/script/data/higher-timeframes).


### OS6001 Unknown timeframe

{value} is not a timeframe.

Fix: Write a count and a unit, or a bare number of minutes: "5m", "1h", "1D", "1W", "60".

The units are case sensitive:

| Unit | Means | Examples |
|---|---|---|
| `m` | Minutes | `"1m"`, `"5m"` |
| `h` | Hours | `"1h"` |
| `D` | Days | `"1D"`, `"2D"` |
| `W` | Weeks | `"1W"` |
| `M` | Months | `"1M"`, `"3M"` |
| none | Minutes | `"15"`, `"60"` |

So `"1M"` is one month and `"1m"` is one minute, and `"60"` and `"1h"` are the same timeframe. Anything else is refused: a word such as `"hourly"`, a lower-case day or week such as `"1d"`, or `"D"` with no count. A timeframe written in the source is checked when the script compiles; one that comes from an `input()` is checked when the study loads, with the same code.

One case catches people out on /trading. The chart's daily interval is named `D`, which is not a timeframe the language reads, so `req.timeframe(chart.interval, close)` compiles and is then refused with this code when the study loads on a daily chart. Write `"1D"` in the request instead of passing `chart.interval`.

### OS6002 The requested timeframe is lower than the chart's

The chart is {chart} and the request asks for {requested}.

Fix: Request {chart} or higher, or change the chart's interval to the lower one and fold upwards instead.

Folding bars upward works: twelve 5 minute bars make an hour. Folding downward cannot, because the chart was never given the prices inside its own bars, and a study that invented them would repaint. So a request for an interval finer than the chart's, such as `"5"` on a 60 minute chart, is refused when the study loads, naming both intervals. If you need 5 minute detail, put the chart on 5 minutes and read the coarser interval from there.

### OS6015 The requested timeframe does not fold into the chart's

{requested} is not a whole multiple of {chart}.

Fix: Request a multiple of {chart}, for example {suggestion}.

An intraday request is built by counting chart bars into groups, so its interval must be a whole multiple of the chart's. On a 5 minute chart, `"15"` and `"60"` fold cleanly; on a 30 minute chart, `"45"` does not, because 45 minutes is one and a half chart bars. Day, week and month requests are built from the calendar and the session instead, so they are exempt. The study is refused when it loads, and the fix names an interval that works.

### OS6014 The feed does not offer this timeframe

The host has no {timeframe} data for {symbol}; it offers {available}.

Fix: Request one of {available}, or derive the interval you want from a lower one the host does serve.

A timeframe can be well formed and still be one the data source does not store for this instrument, such as 3 minute bars from a feed that keeps 1 and 5 minute bars. The host answers with the intervals it does serve, and the message lists them. Like the other request answers on this page, it stops nothing: the read is absent and `req.error()` carries the message. Request one of the listed intervals, or a coarser one that folds from them.

### OS6013 The request changed after the first bar

This request asked for {first} on bar 0 and for {found} on bar {bar}.

Fix: Compute the symbol and the timeframe from literals or inputs, not from bar data.

The symbol and the timeframe of a request are settled once, before the first bar, so that the host can fetch each series once and keep it in step with the chart. A request whose identity changed from bar to bar would need a new fetch on a bar that had already been drawn.

**Not raised yet.** In version 0.5.0 nothing raises OS6013, because nothing can reach it: a timeframe or a symbol computed from bar data, like the one in the example below, is refused when the script compiles, with [OS3003](/script/errors/arguments#os3003). Take the timeframe from a literal or from an `input()` with `kind = "interval"`, as the fix does.

## Request expressions

### OS6003 A per-bar name inside a request expression

{name} is computed on this chart's bars, so it has no meaning on the requested ones.

Fix: Move the calculation inside the request expression, or pass a constant: a literal or an input().

The expression passed to `req.timeframe()` or `req.symbol()` is computed on the requested bars, in their own time: daily bars for a `"1D"` read, the other instrument's bars for a symbol read. A value computed on the chart's own bars, such as a 20 bar average of 5 minute closes, has no meaning on a daily bar, so the compiler refuses it.

In version 0.5.0 the only names from the rest of the file the expression can read are inputs, the names an `input()` assigns. Every other name is refused, even one that holds a plain number:

```openscript
k = 20
d = req.timeframe("1D", sma(close, k))
plot(d, "Daily average")
```

Write numbers and arithmetic inside the expression instead, such as `sma(close, 2 * 10)`, or make the number an input. Move any calculation inside the expression, so it is computed on the requested bars, as the example at the top of this page does with `ema(close, len)`, where `len` is an input.

## Instruments and their facts

### OS6007 Unknown symbol or exchange

The host does not know {symbol} on {exchange}.

Fix: Correct the symbol, and name the exchange it trades on when it is not the chart's: req.symbol("SYMBOL", "1D", close, exchange = "EXCHANGE").

The host resolves every symbol a script names against the instruments it can serve. When it does not know the symbol on the exchange named, or on the chart's own exchange when the read names none, the read is refused with this code rather than returned empty, because an empty series looks exactly like an instrument that did not trade. The rest of the study keeps drawing, the read is absent, and `req.error()` returns the message.

Check the spelling and the exchange. In OpenAlgo an index has an exchange code of its own: NIFTY is read with `exchange = "NSE_INDEX"`, not `"NSE"`, and an option or future on it with `exchange = "NFO"`. See [Other instruments](/script/data/other-instruments).

### OS6008 The request returned no bars

{symbol} at {timeframe} returned no bars over the range this chart covers.

Fix: Move the chart's range into the period the instrument traded, or request a symbol that covers it.

The host found the instrument and had no bars for it over the range the chart covers. A futures or options contract that has expired, one that had not been listed yet, and a range older than the stored history all end here. As with OS6007, the read is absent, `req.error()` carries the message, and the rest of the study keeps drawing.

For NFO and MCX contracts, which expire on a fixed schedule, read the contract that was trading over the chart's range, or move the chart into the period the contract traded.

### OS6009 The request failed

The host could not fetch {symbol} at {timeframe}: {reason}.

Fix: Act on {reason} in the host: it is a connection, permission or quota problem. Where the value can be derived from the chart's own bars, derive it and drop the request.

The host knows the instrument and could not get its bars: the data source refused, did not answer, or limited how often it may be asked. The message carries the host's own reason, and that reason is the thing to act on: it describes a connection, permission or quota problem, not a problem in your script. The read is absent and `req.error()` carries the message.

If the value can be worked out from the chart's own bars, such as the day's high on an intraday chart, derive it and drop the request, as the fix below does with `session.isFirstBar`. That fix needs the instrument's session hours, which the /trading chart does not state in this release, so there reset on a new IST date instead:

```openscript
newDay = isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")

var dayHigh = none
if newDay
    dayHigh = high
else
    dayHigh = max(dayHigh, high)

plot(dayHigh, "Day high so far", aqua, style = "step")
```

### OS6012 An instrument fact is not known

The host did not supply {fact} for {symbol}.

Fix: Supply {fact} in the host's instrument record, or stop depending on it: round with a number the script chooses rather than chart.tickSize.

Tick size, lot size, the trading session and the timezone come from the host's record of the instrument, not from the bars. In version 0.5.0 the engine raises this code when it loads a program and that record cannot be read: a session stated without the timezone it is measured in, a timezone the calendar does not know, or session hours not written as `HH:MM`. It is a problem in the host's record, not in your script.

A fact the host simply leaves out does not raise this code. `chart.tickSize` or `chart.lotSize` reads as absent, `roundToTick()` returns absent, and an order priced or sized from them stops with [OS7002](/script/errors/orders#os7002). On the /trading chart `chart.lotSize` is absent, so a study that works in money takes the lot size from an `input()`, as the fix below does. You can also meet this code's message without an error: a daily, weekly or monthly read on a host that states no timezone is absent, and `req.error()` returns this message for it.

### OS6005 Unknown timezone

{value} is not a timezone this host knows.

Fix: Use a full area and location name from the host's list, or leave the argument out to use the chart's own timezone.

A timezone is named by area and location, such as `"Asia/Kolkata"` or `"UTC"`, from the host's timezone database. An abbreviation such as `"IST"` is not a timezone name, because several abbreviations mean different offsets in different parts of the world, and a session test cannot carry that ambiguity. An unknown name stops the study on the first bar that calls the function.

Write `"Asia/Kolkata"` for Indian exchanges, or leave the argument out to use the chart's own timezone. See [Sessions and time](/script/data/sessions-and-time).

## The bars the engine is given

### OS6010 The engine was given no bars

There are no bars for {symbol} at {timeframe}, so the script cannot run.

Fix: Choose an instrument and interval that have history, or widen the chart's date range until bars exist.

A script is the body of a loop over the chart's bars, and with no bars there is nothing to run. The engine says so rather than drawing an empty pane, which would look like a script that computed nothing. The message names the symbol and the interval.

It means the instrument has no history at that interval in the range loaded: a contract before it was listed, an interval the chart has no data for, or a date range with no trading in it. Choose an instrument and interval that have history, or widen the range.

### OS6011 The bars are not in order

Bar {index} is dated {time}, which is not after bar {previous}.

Fix: Reload the history; if the same bar repeats, the feed is sending duplicates and the host has to sort and deduplicate the bars before the engine runs.

Everything in the language assumes that bar times strictly increase: the history operator, warmup and every session test depend on it. When the host hands over a bar whose time is not after the one before it, a duplicate or a bar out of order, the engine refuses it rather than computing quietly wrong values, and names the bar.

This is a problem in the data feed or the host, not in your script. Reload the history. If the same bar repeats, the feed is sending duplicates, and the host has to sort and deduplicate the bars before the engine runs.

## Loading a compiled program

A **compiled program** is what the compiler turns your source into: a list of instructions the engine runs, stored as data. The chart and the Backtest panel compile your source afresh each time they run it. Saving a script in the Scripts panel also stores its compiled program beside the source, and that stored program is what a strategy deployed from the Strategies panel runs on the OpenAlgo server.

Three of the codes in this group, [OS6004](#os6004), [OS6016](#os6016) and [OS6018](#os6018), are about a stored program rather than your source, and the cure for them is the same: open the script and save it again, so it is recompiled by the compiler you have now. A deployment refused this way says so in its log and asks you to do exactly that. See [The editor](/script/getting-started/the-editor#saving).

### OS6004 The library manifest disagrees with the program

Entry {index} of the program names {name} with {arity} arguments; this engine's manifest has {manifest}.

Fix: Recompile the script against this engine's library, or run the program on an engine with the library version it was compiled against.

A compiled program carries its own table of the library functions it calls, with the number of arguments each takes, and the engine checks every entry against its own library when it loads the program. A mismatch means the program was compiled against a different version of the library, and running it could compute wrong numbers before anything noticed.

You meet this only when a compiled program moves between versions, for example a program stored before an upgrade. Recompile the script from its source with the engine you run it on.

### OS6006 The engine lacks a capability the program requires

This program requires {tag} and this engine does not have it.

Fix: Run the program on an engine that has {tag}, or remove the feature that needs it: the tag names it.

A compiled program lists the capabilities it needs, such as `orders` for a strategy that places orders or `req.symbol` for a read of another instrument, and an engine that lacks one refuses the program when it loads, naming the capability. Refusing before the first bar is better than meeting an instruction the engine cannot run halfway through the chart.

In the /trading Backtest panel this is the refusal a strategy meets when it reads another instrument with `req.symbol()`: a backtest is given only the chart's own bars and cannot fetch another instrument's. The same read works on the chart. Remove the feature the message names, or run the program where it is available.

### OS6016 The compiled format version is not one this engine implements

This program is in compiled format {found} and this engine implements format {max}; a different major number is a different format, whether it is higher or lower.

Fix: Run the program on an engine that implements format {found}, or recompile the source with a compiler that emits format {max}.

The compiled program is a versioned data format, 1.1 in this release. An engine loads a program whose major number (the part before the dot) matches its own, whatever the minor number after it, and refuses one whose major number differs, older or newer, rather than guessing at instructions it cannot read. The message gives both numbers.

Recompile the script from its source with the compiler that matches the engine: in /trading, open the script and save it.

### OS6017 The program's language version is not one this engine implements

This program was compiled from language version {found}, and this engine implements {versions}.

Fix: Upgrade the engine to one that implements language version {found}, or recompile the source against a version it has.

The first line of a script, `version 1`, names its language version, and the program compiled from it carries that number. An engine runs every language version it implements exactly as before, and refuses one it does not have rather than running it approximately, because a saved script has to keep producing the same numbers. Version 0.5.0 implements language version 1.

A program compiled from a newer language version needs a newer engine, or a recompile against a version this engine has.

### OS6018 The compiled program is malformed

The program failed verification at {location}: {reason}.

Fix: Recompile the script from its source; a program that fails verification came from a broken compiler or was edited after it was written, and neither is repairable by hand.

Before an engine runs a program, it checks that the program is well formed: every instruction points at something that exists, and every field has the shape the format requires. A program that fails is refused whole, before any bar, and the message says where it failed. A program straight from the compiler passes, so a failure means the stored program was damaged or edited after it was compiled. Recompile it from the source.

In version 0.5.0 the compiler itself can also report OS6018 on a line, beside another error such as [OS2005](/script/errors/names-and-types#os2005) for a function that calls itself, or [OS3003](/script/errors/arguments#os3003). Fix the other error and it goes with it. If OS6018 is the only diagnostic on an unchanged script, the fault is in the compiler, and its message asks you to report it with the script. See [Reading an error](/script/errors/overview).

### OS6019 A host setting fails the input's validation

The host supplied {value} for {key}, and {validation}.

Fix: Correct the value in the settings dialog, or widen the input's own min, max or options so the value is allowed.

Every `input()` validates what the settings supply: the type, a number's `min` and `max`, and membership of an `options` list. When a setting falls outside those rules, for example a length of 0 saved before the script added `min = 1`, the study is refused when it loads, rather than quietly running with the default, because a settings dialog that ignores what you typed is worse than one that tells you the value is out of range. The message names the input, the value and the rule it broke.

Correct the value in the study's settings, or widen the input's own rules so the value is allowed. See [Inputs](/script/inputs/inputs).

## Backtest runs

A backtest states everything it is carried out under before its first bar, and refuses a setting it cannot carry out rather than producing a figure that means something else. In the Backtest panel a refused run shows the code and the message, and nothing is computed, so correcting the setting and running again costs one run. See [Backtesting](/script/strategies/backtesting).

The Backtest panel fetches exactly the bars of the date range you pick, reports on all of them, and supplies no charge schedule of its own, so of the four codes below only [OS6021](#os6021) can appear there. The other three come from backtests you run through the library. See [Backtesting API](/script/integrate/backtesting-api).

### OS6020 The report window holds no bars

The window from {from} to {to} holds none of the {count} bars supplied.

Fix: Widen the window until it covers bars, or supply the bars it covers. Both bounds are inclusive and are compared against the times of the bars supplied rather than against a calendar, so a window that falls inside a gap in the data is empty however wide it looks.

A backtest run through the library can report on a date window narrower than the bars it was given. When that window holds none of those bars, such as a range entirely before the loaded history or one that falls inside a gap in the data, there is nothing to report, and the run is refused rather than shown as a flat equity curve that would say nothing happened.

Widen the window, or choose one that overlaps the loaded bars. Both ends are inclusive and are compared with the bars' own times, not with the calendar, so a window that falls inside a gap is empty however wide it looks. In the Backtest panel an empty date range stops earlier, with the panel's own message that no bars came back for that range.

### OS6021 A run setting cannot be applied as stated

{setting} cannot be applied: {problem}.

Fix: State the setting so it can be carried out: declare a charge line after every line it is levied on, supply the tick size a slippage in ticks is measured in, write down the reason a tolerance needs a bound, or bring a bound past the cap back inside it. Nothing has been computed at the point this is refused, so correcting the setting and running again costs one run.

A setting can be well formed and still impossible to carry out on this run. In version 0.5.0 the common cause is sizing: a backtest fills in units and keeps no running equity, so `qtyType = "cash"` and `qtyType = "equityPercent"` are refused, and so is `qtyType = "lots"` on an instrument whose lot size is not known. Slippage stated in ticks when there is no tick size to measure a tick in is refused the same way, and so is a charge levied on another charge that is declared after it. The message names the setting and the reason.

Count in units, or in lots on an instrument that states its lot size. In the Backtest panel only the refusal of `"cash"` and `"equityPercent"` can happen: the panel reads the tick and lot size from OpenAlgo's record of the instrument, and when it has none it runs with a tick of 0.05 and a lot of 1 and says so under the report, so check that line before trusting a result counted in lots. See [Position and sizing](/script/strategies/position-and-sizing) and [Costs and fills](/script/strategies/costs-and-fills).

### OS6022 The bars are not the bars the record was made from

The bars supplied hash to {found}, and the record was made from {expected}.

Fix: Replay against the bars the record names. Where the revision is the point, make a second record over the revised bars and compare the two runs, rather than overwriting one run with the other under one name.

A saved run record names the exact bars it was made from by a hash, so a replay can prove it is repeating the same run. Bars do get revised: a feed corrects a price, a session is extended, a split is applied to history. When the bars supplied now hash differently from the ones in the record, the replay is refused rather than reporting the old figures over new data.

You meet this only when you replay records through the library. Replay against the bars the record names, or, where the revision is the point, make a new record over the revised bars and compare the two runs. See [Backtesting API](/script/integrate/backtesting-api).

### OS6023 Two cost models are stated at once

A charge schedule was supplied, and the declaration states a commission of {commission} in {commissionType}.

Fix: Supply the schedule and leave the declaration's commission at its default of zero, or state the commission in the declaration and supply no schedule. A schedule is the one of the two that can carry a floor, a cap, a charge levied on a charge, and a cost that falls on one side of the trade only.

A strategy's own `commission` and a charge schedule supplied by the host running the backtest describe the same money. Applied together they would charge it twice, and applied one at a time they would charge whichever an engine happened to prefer, which is a rule nobody wrote down. So a run that has both is refused before the first bar. The message gives the commission the declaration states.

Keep one of the two: leave `commission` at its default of 0 when a schedule is supplied, or supply no schedule. A schedule can say more, such as a floor, a cap, a charge on another charge, or a cost on one side of the trade only. The Backtest panel supplies no schedule, so there the declaration's `commission` always applies and this code never appears.

**Related.** [Timeframes](/script/data/timeframes), [Higher timeframes](/script/data/higher-timeframes), [Other instruments](/script/data/other-instruments), [Sessions and time](/script/data/sessions-and-time), [Backtesting](/script/strategies/backtesting), [Reading an error](/script/errors/overview)


## OS7xxx Orders

Source: https://openalgo.in/script/errors/orders

This page covers the OS7xxx codes of OpenScript (also called OpenAlgo Script): the refusals a strategy meets when an order cannot be placed as written. An order is the one place in the language where quietly doing something else would cost real money, so every doubtful order is refused loudly instead of being adjusted: an absent price is not replaced by the close, a price between two ticks is not rounded, and an entry the declaration forbids is not added to the position. Each refusal points at a line you can fix, and most have a standard guard that keeps a strategy from ever reaching them.

## When they appear

| When | Codes | What happens |
|---|---|---|
| When the script compiles | [OS7001](#os7001), [OS7003](#os7003), [OS7016](#os7016) | Shown in the console under the editor. The strategy cannot run until it is fixed |
| When an order is placed on a bar | [OS7002](#os7002), [OS7004](#os7004), [OS7006](#os7006) to [OS7010](#os7010), [OS7013](#os7013), [OS7017](#os7017) | The run stops at that bar, and nothing further is sent |
| Not raised in version 0.5.0 | [OS7005](#os7005), [OS7011](#os7011), [OS7012](#os7012), [OS7014](#os7014), [OS7015](#os7015), [OS7018](#os7018), [OS7019](#os7019) | Reserved for checks that arrive later. Each entry says what happens today |

A refusal while the run is going stops it at that bar in every place a strategy runs:

- **On the chart**, where a strategy's plots and trades are simulated in the browser, the strategy draws nothing and /trading shows the code and the message in a notice, as for any [runtime error](/script/errors/runtime#when-they-appear).
- **In the Backtest panel**, the report lists only the trades made before the refusal, the equity curve runs flat from there to the end, and the panel does not show the error. A backtest with far fewer trades than the chart suggests is worth checking for one first. See [Backtesting](/script/strategies/backtesting).
- **In a deployment** from the Strategies panel, sandbox trading (analyzer mode in OpenAlgo) or live, the run stops at that bar, sends nothing further, and writes the code and the bar to the run's log on the server. See [Sandbox and live](/script/strategies/sandbox-and-live).

## A strategy written to avoid them

This strategy enters on an EMA cross, sets a protective stop at the previous 20 bar low, and exits on the opposite cross or a close below the stop. Every guard in it is there because of one of the codes below.

```openscript
version 1
strategy("EMA cross, guarded", overlay = true)

qty = input(1, "Quantity", min = 1)
fast = ema(close, 9)
slow = ema(close, 21)
up = crossUp(fast, slow)
down = crossDown(fast, slow)

// The low of the previous 20 bars, rounded onto the tick (OS7006). It is
// absent on the first bars, and on a chart with no tick size (OS7002).
swingLow = roundToTick(lowest(low, 20)[1])

var stopAt = none

// Enter only when flat, so pyramiding never refuses an entry (OS7008).
// Entry and exit sit in one if chain, so they never run on the same bar.
if up and pos.isFlat and not isNone(swingLow)
    buy(qty = qty, tag = "cross")
    // A protective stop goes below a long entry (OS7010).
    exit(tag = "cross", stop = swingLow)
    stopAt = swingLow
else if pos.isLong and (down or close < stopAt)
    // No quantity: close takes whatever is left to close (OS7017).
    close(tag = "cross")

plot(fast, "Fast EMA", aqua)
plot(slow, "Slow EMA", orange)
plot(pos.isLong ? stopAt : none, "Stop", red, style = "step")
```

The crosses are computed at the top level, above the `if`, because a stateful call such as `crossDown()` inside a branch only advances on the bars where the branch runs (warning [OS8001](/script/errors/warnings#os8001)). The stop is also tested by the script itself, because the version 0.5.0 backtest does not fill a stop set with `exit()`; see [Exits and brackets](/script/strategies/exits-and-brackets).

## Where orders can be placed

### OS7001 Only a strategy can do that

{name} is available only in a file declared with strategy().

Fix: Change study(...) on line {line} to strategy(...), or replace {name} with signal("...") to mark the bar without trading.

The order calls, `buy()`, `sell()`, `exit()`, `close()`, `cancel()` and the `order.*` functions, and the `pos.*` values need a position to act on and a report to write to. Only a file declared with `strategy()` has those, so a study that uses one is refused when it compiles, and a study can never place an order on any bar.

Change `study(...)` to `strategy(...)` when the script is meant to trade. When you only want to mark the bar on the chart, keep the study and use `signal()` instead. See [Strategies overview](/script/strategies/overview).

### OS7003 An order function inside a request expression

{name} inside a request expression would place an order from another instrument's bars.

Fix: Read the value with the request, and place the order at the top level from the result.

The expression inside `req.timeframe()` or `req.symbol()` runs on other bars, in their own time: the daily bars of a `"1D"` read, or another instrument's bars. An order placed there would have no instrument, no moment and no price of its own, and it would fire once per bar of a series the chart never shows.

Read the value you need with the request, and place the order at the top level of the strategy from the result, as the fix below does.

## Order arguments

### OS7002 An order argument is absent

{name}'s {argument} is absent on this bar.

Fix: Guard the call with isNone({argument}), or supply a fallback with orElse() where one is genuinely correct.

An order argument that comes out absent is refused rather than defaulted, because an order is the one place where quietly doing nothing, or something else, is worse than stopping. The usual source is warmup: a stop, a limit or a quantity computed from an indicator that has no value on the first bars, such as `lowest()` over 20 bars before 20 bars exist. A missing instrument fact does it too: `roundToTick()` is absent where the host states no tick size, and `chart.lotSize` is absent on the /trading chart.

Leaving an argument out is different: `buy()` with no quantity uses the declaration's `qty`. Test a computed value with `isNone()` before the order call, as the example above does with `swingLow`, or give it a fallback with `orElse()` only where a fallback is genuinely correct. See [Absent values](/script/language/absent-values).

### OS7004 Order quantity is zero or negative

{name} was given a quantity of {qty}.

Fix: Pass a positive quantity, guard the call with a size test, and use sell() to go the other way.

The direction of an order comes from the function, `buy()` or `sell()`, never from the sign of the quantity. A negative quantity is a calculation that went the wrong way, and a quantity of zero is never what a script means, so both stop the run. The usual cause is sizing towards a target, `target - pos.size`, on a bar where the target has already been reached or passed.

Test the size before the call, and use `sell()` to go the other way. See [Position and sizing](/script/strategies/position-and-sizing).

### OS7005 Quantity is not a multiple of the lot size

{symbol} trades in lots of {lot}, and {qty} is not a multiple of it.

Fix: Size in lots: declare qtyType = "lots" and pass the lot count, or round a computed size with order.roundToLot().

NFO futures and options, and MCX contracts, trade in lots: an order must be a whole multiple of the contract's lot size, and the exchange rejects anything else. This code is planned to refuse such a quantity in the engine too, so a backtest never reports a trade that could not have happened.

**Not raised yet.** In version 0.5.0 nothing compares an order's quantity with the lot size, so `buy(qty = 100)` on a contract whose lot is 75 units is sent as written. Size in lots yourself: declare `qtyType = "lots"` and pass a number of lots, as the fix below does (with a lot of 75, `buy(qty = 2)` is 150 units). The fix line also names `order.roundToLot()`, which is planned and does not compile in this release; until it arrives, round a computed quantity down to whole lots with `floor(qty / lot) * lot`.

### OS7006 Price is not on a tick

{symbol} ticks at {tick}, and {price} does not fall on one.

Fix: Round to the tick before passing the price: round(price / chart.tickSize) * chart.tickSize.

Every instrument trades in steps of its tick size, and a limit or stop price between two ticks cannot exist at the exchange. The engine does not round it for you, because moving the order off the level your script computed would change the result, and in a backtest the change would often be in your favour. A price computed as a percentage, such as `close * 1.013`, is the usual cause.

Round the price onto the tick with `roundToTick()` before the call, as the example above does. Where the host states no tick size, `roundToTick()` is absent, so test the rounded price once with `isNone()` and use it everywhere. See [Orders](/script/strategies/orders).

### OS7007 A resting order has no price

A {type} order needs {argument}, and none was given.

Fix: Pass {argument}, or leave the type as market and let the order fill at the next price.

A limit or stop order rests at a price level, so it needs that level. `order.place()` with `type = "limit"` needs `price`, `type = "stop"` needs `trigger`, and `type = "stopLimit"` needs both. The engine does not fill in the bar's close, because that would turn the order into a market order under another name, and the report would say "limit" about a fill the script never asked for.

Pass the price the type needs, or use `type = "market"` and let the order fill at the next price.

## The position and the bar

### OS7008 The entry was refused by pyramiding

This strategy allows {max} entries in one direction and already holds {found}.

Fix: Raise pyramiding in the declaration, or test pos.size before entering again.

`pyramiding` in the declaration says how many entries in one direction a position may hold at once, and it is 1 unless you set it. An entry beyond that stops the run, rather than quietly building a bigger position than the declaration allows and reporting a return the stated rules never earned. The usual cause is an entry condition that stays true for several bars with no position guard.

A close and a new entry in the same direction on one bar cause it too: an order fills after the bar that places it, so when the entry is placed the position has not closed yet and still counts. Guard entries with `pos.isFlat`, as the example above does, or raise `pyramiding` when adding to a position is the plan. See [Declarations](/script/reference/declarations#pyramiding).

### OS7010 A bracket price is on the wrong side of the entry

A {side} entry at {entry} cannot take a {leg} at {price}.

Fix: Put the stop below a long entry and the limit above it, and swap the two for a short.

A stop protects a position and a target takes profit, so their sides are fixed by the position's direction. For a long, the stop goes below the average entry price and the target above it; for a short, the other way round. A stop on the wrong side would fill at once and turn every trade into an instant loss that looks like a strategy result.

The check is made against an open position, so an entry and its stop placed on the same bar, before the entry has filled, are the ordinary shape and are not refused, and a level exactly at the entry is allowed. See [Exits and brackets](/script/strategies/exits-and-brackets).

### OS7011 The order needs more capital than the strategy has

This order needs {required} and the strategy has {available}.

Fix: Size from equity with qtyType = "equityPercent", or test pos.equity before entering.

A backtest that could spend money it does not have would report returns nobody could have earned. This code is planned to refuse an order that needs more capital than the strategy has left, and to record the refusal so the equity curve stays honest.

**Not raised yet.** In version 0.5.0 nothing compares an order's cost with the strategy's capital, so `buy(qty = 100)` at a price near 100 fills in full under `capital = 1000`. Keep quantity times price within the `capital` you declared yourself. Neither route the fix names works in this release: the backtest refuses `qtyType = "equityPercent"` with [OS6021](/script/errors/data#os6021), and `pos.equity` is planned. In the Backtest panel count in units or lots.

### OS7012 The instrument is outside its session

{symbol} is outside its trading session at {time}.

Fix: Guard entries with session.isOpen, and set closeOnSessionEnd = true to flatten at the close.

An exchange works orders only during its session, 09:15 to 15:30 IST for NSE equities and NFO contracts. This code is planned to refuse an order placed outside the session, rather than hold it until the open and fill it at a price the script never saw.

**Not raised yet.** In version 0.5.0 nothing checks the session before an order is sent. Guard entries yourself with `session.isIn()`, as the example below does, and name the zone, as in `session.isIn("0915-1530", "Asia/Kolkata")`: the /trading Backtest panel states no timezone, so there a window without one is absent and the guard never lets an entry through. `session.isOpen`, which the fix line names, is planned and does not compile in this release. The declaration's `closeOnSessionEnd = true` is accepted but not yet acted on either, so close an intraday position yourself before 15:30, as [Exiting on the clock](/script/strategies/exits-and-brackets#exiting-on-the-clock) shows. See [Sessions and time](/script/data/sessions-and-time).

### OS7013 Two opposite orders on one bar

{first} and {second} were both placed on bar {bar}.

Fix: Make the conditions exclusive with else if, or place the exit on this bar and the entry on the next.

When one bar places both a buy and a sell, there is no fair way to choose between them: which one comes first in the file is an accident of layout, and "the last one wins" would change silently when someone reorders two blocks. So neither is placed, the run stops, and the message names both calls with their lines, and the bar. It happens with two separate `if` blocks whose conditions can be true on the same bar, such as a `buy()` on an EMA cross and a `sell()` on an RSI level, as in the example below.

Make the conditions exclusive with `else if`, as the fix does, or place the sell on this bar and the buy on the next. Two orders on the same side are not this error, and neither is a `close()` or an `exit()` beside a `buy()` or a `sell()`: the code is about `buy()` and `sell()` only.

### OS7017 A close states more than it is closing

close was given a quantity of {qty}, and {part} has {held} left to close.

Fix: Leave the quantity out and close() flattens what is left, or size the part from pos.size and keep the quantity at or under {held}.

A close can never send more than is left to close, because an order that went past zero would flatten the position and open the opposite one under a call named `close`. What is left is what the position, or the part of it the tag names, holds, less any orders already on their way out. `close(qty = 5)` against a position of 1 stops the run, naming both numbers.

Leave the quantity out and `close()` closes whatever is left, which cannot be wrong, or size a partial exit from `pos.size`. A close with no quantity on a tag that holds nothing sends nothing and is not an error.

## Tags

A **tag** is the name you give an order with `tag = "..."`, such as `buy(qty = 1, tag = "breakout")`. Later calls use it to say which order or which part of the position they mean.

### OS7009 Unknown order tag

There is no working order tagged {tag}.

Fix: Use the tag the order was placed with, or cancelAll() where the script means every order it has working.

`cancel()` acts on a working order: one placed and not yet filled, cancelled or expired. A tag that names no working order means the script has lost track of its orders, most often because the order has already filled. Ignoring the call would leave the strategy believing an order is still out, so the run stops.

Use the tag the order was placed with, keep your own record of whether the order is still working, or call `cancelAll()` when you mean every working order.

### OS7016 A close names a tag nothing places

No order in this file is placed with the tag {tag}.

Fix: Use the tag the entry was placed with, or leave the tag out to flatten the whole leg.

`close()` with a tag closes the part of the position that orders placed with that tag opened. When no order anywhere in the file is placed with that tag, the close can never close anything: it would send nothing on every bar and say nothing, while the strategy believes it has flattened. That is almost always a typo, so the compiler refuses it.

Use the tag the entry was placed with, or leave the tag out to close the whole position. A tag the script computes is not checked, because the compiler cannot know its value.

## The destination

The destination is where orders go: the simulator in the Backtest panel, or sandbox trading (analyzer mode in OpenAlgo) or a live account when a strategy is deployed. The last four codes are about the conversation between the engine and the destination, and none is raised in version 0.5.0.

### OS7014 The destination rejected the order

The order destination rejected {name}: {reason}.

Fix: Act on {reason}: it comes from the destination, not from the script, and the same order will be rejected again until the account or the order changes.

The order left the strategy well formed and the destination refused it: a product the account cannot trade, not enough margin, or a symbol the account has no permission for. The reason comes from the destination, not from the script, and the same order will be refused again until the account or the order changes.

**Not raised yet.** In version 0.5.0 a refusal that comes back is recorded against the order as rejected, with the destination's own reason, but no diagnostic points at the line that placed it. See [Sandbox and live](/script/strategies/sandbox-and-live).

### OS7015 The strategy has no order destination

This strategy placed an order and the host supplied no destination.

Fix: Connect a destination in the host, or run the file as a study(): replace buy() with signal("BUY").

A strategy needs somewhere for its orders to go. This code is planned for a host that runs a strategy with nowhere to send orders, which would compute a position nobody ever took.

**Not raised yet.** In version 0.5.0 nothing raises OS7015. An engine given no order route at all refuses a strategy when it loads, with [OS6006](/script/errors/data#os6006) naming `orders`. In /trading a strategy always has a destination: the simulator when you backtest it, and the one its deployment names when it runs.

### OS7018 A frame names an order this strategy did not place

The frame names intent {intent}, and this strategy holds no such order.

Fix: Answer with the intent id the engine sent. A destination's own reference is carried in the frame's reference field, where the engine records it and never parses it, and it is not what an answer is addressed by.

A destination reports on an order by the id the engine gave it when the order was sent. This code is for a report naming an order this strategy never placed, such as a destination answering for another strategy's order or for a run that has already ended. It is a problem in the host, not in your script.

**Not raised yet.** In version 0.5.0 such a report is refused and the refusal is recorded, but no diagnostic is raised. It concerns you only if you build your own host on the library: answer with the id the engine sent. See [Host interface](/script/integrate/host-interface).

### OS7019 A fill was reported with no price

The frame reports {qty} filled for intent {intent}, and no average fill price.

Fix: Report the average fill price the destination computed over the cumulative quantity, on every frame that reports a quantity greater than the last one. A frame carrying no new quantity needs no price.

A report that says more quantity has filled must also carry the average fill price, because a position needs a price as well as a size before it has an average entry, a profit or an equity point. A report with a quantity and no price is refused whole.

**Not raised yet.** In version 0.5.0 such a report is refused and recorded, but no diagnostic is raised. Like [OS7018](#os7018), it concerns hosts built on the library: report the destination's average fill price over the whole filled quantity on every report that adds quantity.

**Related.** [Orders](/script/strategies/orders), [Exits and brackets](/script/strategies/exits-and-brackets), [Position and sizing](/script/strategies/position-and-sizing), [Backtesting](/script/strategies/backtesting), [Sandbox and live](/script/strategies/sandbox-and-live), [Reading an error](/script/errors/overview)


## OS8xxx Warnings

Source: https://openalgo.in/script/errors/warnings

This page covers the OS8xxx codes of OpenScript (also called OpenAlgo Script): the warnings. A warning never stops anything. The script compiles, runs and draws exactly as written. What a warning says is that a line has a shape whose behaviour is well defined and almost never what its author wanted: an average that only advances on some bars, a study that shows values it could not have known, an alert that will stop firing when you edit the file. Reading each one takes a moment, and ignoring one is how a chart ends up quietly wrong.

## Where they appear

Warnings come from the compiler, so you see them when you save a script in the Scripts panel, before anything runs. Each one is listed in the console under the editor with its line, its message and its fix, and the status bar reads "Ready, with 1 warning" instead of "Ready". A script with warnings can still be applied to a chart, backtested and deployed. See [The editor](/script/getting-started/the-editor#checking-and-the-console).


Five of the nineteen codes are reserved for checks the compiler does not make yet: [OS8004](#os8004), [OS8006](#os8006), [OS8013](#os8013), [OS8014](#os8014) and [OS8019](#os8019). Their entries say so, and describe the shape to avoid by hand until the warning arrives.

## A script with nothing to warn about

This EMA cross study is written the way the warnings below ask for: the stateful calls run on every bar, the version line is there, every input is read, and the alert has a fixed id.

```openscript
version 1
study("Clean EMA cross", overlay = true)

fastLen = input(9, "Fast length", min = 1, max = 200)
slowLen = input(21, "Slow length", min = 1, max = 400)

// Stateful calls at the top level, so they advance on every bar (OS8001).
fast = ema(close, fastLen)
slow = ema(close, slowLen)
crossed = crossUp(fast, slow)
trending = fast > slow

// Hide a value with none instead of computing it inside a branch.
plot(trending ? fast : none, "Fast EMA in an uptrend", aqua)
plot(slow, "Slow EMA", orange)

// A fixed id keeps an alert subscription attached when lines move (OS8008).
if crossed
    alert("Fast EMA crossed above the slow EMA", id = "emaCrossUp")
```

## The file

### OS8003 No version declaration

This file declares no language version; it was compiled as version {version}.

Fix: Add version {version} as the first line of the file.

The first line of a script, `version 1`, names the language version it is written in. A file that names its version is read by that version's rules for ever, even after newer versions ship. A file without one is read by the newest version the compiler has, which is the one thing that can change under it. In this release the file still compiles and runs, as version 1.

Add `version 1` as the first line. Every complete example in this documentation starts with it. See [Script structure](/script/language/script-structure).

### OS8013 Deprecated

{name} is deprecated since language version {version}; {replacement} does the same thing.

Fix: Replace {name} with {replacement}; the two compute the same values.

When a function or an option turns out to be a mistake, it is never removed and never changes meaning, because a saved script must keep producing the same numbers. It is marked deprecated instead, and this warning names its replacement, which computes the same values.

**Not raised yet.** In version 0.5.0 no name in the library is deprecated, so this warning never appears. The example below uses placeholder names to show its shape.

## Where a call runs

### OS8001 A stateful call inside a branch

{name} advances only on the bars where this branch runs, and is absent on the rest.

Fix: Compute it unconditionally at the top level and use the result inside the branch.

Stateful calls, such as `ema()`, `sma()`, `rma()`, `crossUp()` and any user function that keeps a `var`, carry state from one bar to the next. When such a call sits inside an `if` branch, or in one arm of a ternary, it runs only on the bars where that branch runs: its state advances only on those bars, and it is absent on the rest. An EMA computed only on trending bars is not the chart's EMA; it is an average of whichever bars happened to trend.

Compute the call unconditionally at the top level, give it a name, and use the name inside the branch, as the example at the top of this page does. See [Execution model](/script/language/execution-model) and [User functions](/script/language/functions).

### OS8004 A branch on an absent condition changes a value used later

{condition} can be absent, and this block assigns {name}, which is read at line {line}.

Fix: Decide what warmup means: test isNone({condition}) explicitly, or give {name} a starting value above the if.

A condition that is absent takes the false branch. During warmup, while an indicator in the condition has no value yet, a block under `if rsi(close, 14) > 70` does not run, so a name it assigns keeps whatever it held before. Those bars sit off the left edge of the chart, which is why the shape can change an answer without anyone noticing.

**Not raised yet.** In version 0.5.0 the checker does not follow names assigned under a possibly absent condition, so this shape compiles without a warning. The before block below is refused for a different reason in this release: `zone` is first assigned inside the `if`, so it does not exist after the block ([OS2001](/script/errors/names-and-types#os2001)). The shape the warning describes needs a name that already exists above the `if`, such as a `var`. Decide what warmup means yourself: give the name its starting value above the `if`, as the fix does, or test the condition with `isNone()`. See [Warmup](/script/language/warmup).

### OS8011 live var makes live and backtest differ

{name} is a live var, so it keeps its value across the updates of the moving bar.

Fix: Use var unless counting intrabar updates is the actual intent; keep live var only for that.

A `var` keeps its value from one bar to the next, and on the forming bar it is rolled back before each update, so the script sees every bar once, exactly as a backtest does. A `live var` opts out: it keeps its value across the updates of the forming bar as well, which is what you need to count ticks or updates within a bar. A script that uses one gives different numbers on a real-time chart and in a backtest of the same data, by design.

Use `var` unless counting intrabar updates is the point of the script. See [Persistence](/script/language/persistence) and [Realtime and confirmation](/script/language/realtime-and-confirmation).

### OS8014 A persistent value holds a bar index

{name} keeps a bar index across bars, and every index shifts when more history loads.

Fix: Store time instead and compare timestamps; the bar's time does not move.

`bar.index` is a position in the bars the engine was given, not a fixed address. When more history loads, every bar is renumbered, so a `var` that stored a bar index and compares it later is comparing against a number that has moved.

**Not raised yet.** In version 0.5.0 the checker does not follow a bar index into a persistent value, so this compiles without a warning. Store `time` instead, as the fix does: a bar's time never changes. See [Bars and history](/script/language/bars-and-history).

## Repainting

A study repaints when a value it showed on a past bar changes later. See [Repainting](/script/data/repainting).

### OS8002 A higher timeframe read with onUnconfirmed

This file sets onUnconfirmed = true and reads {timeframe}; together they repaint.

Fix: Drop onUnconfirmed = true, or guard every use of the read with bar.isConfirmed.

`onUnconfirmed = true` lets a script act inside a bar that is still forming. A higher timeframe read is itself incomplete until its own bar closes, so acting on it intrabar acts on a value that will change: the chart redraws when the higher bar closes, and a real-time run and a backtest of the same data disagree.

The warning appears on each higher timeframe read in a file that sets `onUnconfirmed = true`, and it stays even when you guard the uses with `bar.isConfirmed`, as a standing reminder of the combination. Drop `onUnconfirmed` unless the script really needs to act within the bar. See [Realtime and confirmation](/script/language/realtime-and-confirmation).

### OS8005 A lookahead read

This read uses {mode}, so the study shows values the bar it is drawn on could not have known.

Fix: Drop the mode to take the default, which never repaints, unless the study is deliberately a study of what the higher bar went on to do.

A higher timeframe read with `mode = "lookahead"` gives each chart bar the final value of the higher bar it falls in, including bars before that higher bar closed. At 10:00 the study shows the day's high that was only known at 15:30. It is the strongest form of repainting there is, and it makes a backtest look far better than anything that could be traded.

Remove the mode to use the default, which never repaints, unless the study is deliberately about what the higher bar went on to do. See [Higher timeframes](/script/data/higher-timeframes).

## Plots and alerts

### OS8006 A session average on a session-length bar

A session anchored average resets each session, and each bar of {interval} is a whole session, so it equals its source.

Fix: Use an intraday interval for a session anchored average, or plot the source directly and delete the call.

A session anchored average such as `vwap()` starts again at the first bar of each session and accumulates through the day. On a daily or longer chart every bar is a whole session, so the average covers one bar and equals that bar's own price: the plot adds nothing, while its legend suggests it does.

**Not raised yet.** In version 0.5.0 the checker does not compare the call with the chart's interval, so this compiles without a warning. Use a session average on an intraday chart, such as 5 or 15 minute bars across the 09:15 to 15:30 session. On the /trading chart, `vwap()` has no value in this release, because the chart does not state the session hours it restarts on; anchor the average by date with `vwapAnchor()`, as in `vwapAnchor(hlc3, isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata"))`.

### OS8007 A plot sets the price pane's own formatting

{title} sets {option} while drawing over the price pane, which reformats the instrument's own axis.

Fix: Set precision and format in the declaration, for the whole study, or drop them from a plot drawn over the price pane.

`precision` and `format` on a plot set how its price scale is labelled. A plot drawn over the price pane shares the instrument's own scale, so setting them there reformats the axis that the candles and every other series on the pane are read against, which is almost never what was meant.

Set `precision` and `format` in the declaration, for the whole study, or leave them off a plot drawn over price. See [Settings and style](/script/inputs/settings-and-style).

### OS8008 An alert with no fixed id

This alert has no fixed id, so its identity is derived from its position at line {line}.

Fix: Give the alert a stable id of your own: alert("text", id = "emaCross").

An alert subscription is kept under the alert's id. Without a fixed `id`, the id is derived from the alert's position in the file, so inserting a line above it changes the id, and the subscription quietly stops firing. An id taken from an `input()` warns as well, because it could change whenever someone edits the settings.

Give every `alert()` an `id` string of its own, written as a literal, as the example at the top of this page does. See [Alerts from scripts](/script/alerts/overview).

### OS8009 This plot can never draw

{title} plots a value that is absent on every bar.

Fix: Plot the value that was meant, or delete the plot and its settings row.

A plot whose value is `none` on every bar still takes a legend row, a settings entry and a place on the scale, and draws nothing. It is usually a placeholder that was left behind, or a name that was never given the value it was meant to hold.

Plot the value you meant, or delete the plot.

## Code that does nothing

### OS8010 A name is never read

{name} is assigned at line {line} and never read.

Fix: Use the value, or delete the line.

A name that is assigned and never read still costs work on every bar, and it tells the next reader that something depends on it. It is usually what is left of a calculation that was replaced. A plot's handle counts too: `p = plot(close, "Close")` warns unless `p` is passed to `fill()`.

Use the value, or delete the line.

### OS8012 An ordered comparison against none is always absent

This {op} has none on one side, so it is absent on every bar.

Fix: Test absence with isNone(x), or with x == none, both of which are always true or false.

The ordered comparisons `<`, `>`, `<=` and `>=` give absent when either side is absent, and a comparison against `none` has an absent side on every bar. So the condition is absent on every bar, takes the false branch every time, and the block never runs.

To ask whether a value is absent, use `isNone()` or `x == none`, which are always true or false. See [Absent values](/script/language/absent-values).

### OS8015 This loop never runs

The loop starts at {start}, ends at {end} and steps {step}, so the body never runs.

Fix: Add step -1 to count down, or swap the bounds to count up.

A `for` loop counts from its start to its limit in steps of its step, which is 1 unless you write one. `for i = 9 to 0` starts above its limit and so runs zero times: a loop never reverses by itself, because a loop that could reverse is a loop that could run for ever by accident.

Write `step -1` to count down, or swap the bounds to count up. See [Control flow](/script/language/control-flow).

### OS8016 Unreachable code

Line {line} follows a return that always runs, so it never executes.

Fix: Delete the unreachable lines, or move them above the return.

`return` leaves a function at once, so lines after a `return` that always runs never execute. The usual cause is an early exit that lost its `if`.

Delete the unreachable lines, or move them above the `return`. See [User functions](/script/language/functions).

### OS8017 The condition is constant

This condition is {value} on every bar.

Fix: Restore the test that was meant, or delete the branch that never runs.

An `if` or `else if` whose condition is the literal `true` or `false` has the same answer on every bar, so one of its branches can never run. It is usually a test pinned while debugging and left behind.

In version 0.5.0 the warning covers only a bare `true` or `false`. A condition that is just as constant but written as an expression, such as `if 1 > 2` or `if not true`, compiles without it, so look for those yourself.

Restore the condition you meant, or delete the branch that never runs. See [Debugging](/script/writing/debugging).

### OS8018 An input is never used

The input {title} is declared at line {line} and never read.

Fix: Use the name the input assigns, or delete the input() and its dialog row.

Every `input()` adds a field to the study's settings dialog. When the script never reads the name the input assigns, you can change the field and nothing happens, which is worse than the field not being there at all.

Use the input, or delete it. See [Inputs](/script/inputs/inputs).

### OS8019 A deleted object is still held

{name} still holds the {kind} deleted at line {line}.

Fix: Assign none to the name on the same path as the delete, and where the object came out of an array, remove the element as well as deleting the object.

Deleting a drawing object with `draw.delete()` does not clear the name or the array element that refers to it: both still hold the deleted object. The next setter that reaches it stops the run with [OS4005](/script/errors/runtime#os4005), often many bars after the line that caused it.

**Not raised yet.** In version 0.5.0 the checker does not follow a reference to a deleted object, so this compiles without a warning. Assign `none` to the name next to the delete, and when the object came out of an array, remove its element with `shift()` or `remove()` as well, as the fix does. See [Lines and boxes](/script/visuals/lines-and-boxes).

**Related.** [Reading an error](/script/errors/overview), [The editor](/script/getting-started/the-editor), [Repainting](/script/data/repainting), [Execution model](/script/language/execution-model), [Style guide](/script/writing/style-guide), [OS4xxx Runtime errors](/script/errors/runtime)


# Integrate

## Two libraries

Source: https://openalgo.in/script/integrate/overview

This section is for developers who want OpenScript, also called OpenAlgo Script, inside their own financial portal, trading terminal or research tool. It covers the two libraries the language ships as, what each one holds, which pieces your platform keeps, and the order in which to adopt them. Read this page first: it decides which of the other Integrate pages you need.

OpenScript is not a feature locked inside OpenAlgo. OpenAlgo itself is built on the same two libraries described here: the /trading page compiles, draws and backtests scripts with `openalgo-script`, and OpenAlgo's server runs a deployed strategy with `openscript`. Nothing it does with them is closed to you.

## The two libraries

| | `openalgo-script` | `openscript` |
|---|---|---|
| Registry | npm | PyPI |
| Language | JavaScript and TypeScript (types included) | Python |
| Runtime | Any modern browser; Node.js 22 or newer on a server | Python 3.12 or newer |
| Holds | The compiler, the engine, the backtest, six headless editor functions (functions that return data and draw nothing), a chart adapter and a drop-in editor adapter | An engine that runs compiled programs. No compiler |
| Runtime dependencies | None | None (the Python standard library only) |
| Licence | Apache 2.0 | Apache 2.0 |
| Version | 0.5.0 | 0.5.0 |

```bash
npm install openalgo-script
pip install openscript
```

The two are released together at the same version. The source, the specification and the conformance suite are in the project repository at [github.com/marketcalls/openscript](https://github.com/marketcalls/openscript).

Apache 2.0 was chosen on purpose. A platform can embed the language in a commercial product without publishing its own source, which is what a shared language needs.

## A first run

This is the whole loop, compile, load, run and read, in one file. Save the script as `ema-cross.os`:

```openscript
version 1
study("EMA cross", overlay = true)

fast = input(9, "Fast", min = 1)
slow = input(21, "Slow", min = 2)

ef = ema(close, fast)
es = ema(close, slow)
plot(ef, "Fast EMA", aqua)
plot(es, "Slow EMA", orange)

if crossUp(ef, es)
    signal("BUY", color = green, at = "below", shape = "arrowUp")
    alert("Fast EMA crossed above slow EMA", id = "cross-up", title = "Cross up")
if crossDown(ef, es)
    signal("SELL", color = red, shape = "arrowDown")
```

Then run it with Node.js 22 or newer, after `npm install openalgo-script`:

```js title="first-run.mjs"
import { readFileSync } from "node:fs";
import { sourceFile, parse, check, emit, DiagnosticBag, load, renderDiagnostics } from "openalgo-script";

// 1. Compile: source text in, a compiled program (plain data) out.
const text = readFileSync("ema-cross.os", "utf8");
const file = sourceFile("ema-cross.os", text);
const bag = new DiagnosticBag();
const { program } = emit(file, check(file, parse(file, bag), bag), bag);
if (bag.hasErrors || program === undefined) {
  throw new Error(renderDiagnostics(file, bag.ordered()));
}

// 2. Load: verify the program and resolve its inputs.
const loaded = load(program, { settings: { fast: 9, slow: 21 } });
if (!loaded.ok) throw new Error(`${loaded.diagnostic.code}: ${loaded.diagnostic.message}`);

// 3. Run: one record per bar, oldest first. These stand in for your own data:
//    one NSE session of 5 minute bars from 09:15 IST, times in UTC milliseconds.
const sessionOpen = Date.UTC(2025, 0, 6, 3, 45);
const bars = Array.from({ length: 75 }, (_, i) => {
  const close = 820 + 6 * Math.sin(i / 8);
  return { time: sessionOpen + i * 300_000, open: close - 0.4, high: close + 0.9, low: close - 1.1, close, volume: 12_000 };
});
const { bars: results, diagnostic } = loaded.engine.run(bars);
if (diagnostic) throw new Error(`${diagnostic.code} on line ${diagnostic.span.line}: ${diagnostic.message}`);

// 4. Read: every plot is a channel, one value per bar, null where it has none yet.
const last = results.at(-1);
for (const plot of program.outputs.plots) {
  console.log(plot.title, last.columns[plot.channel]);
}
```

It prints the two averages on the last bar of the session:

```text
Fast EMA 823.0657170289549
Slow EMA 823.1290870928198
```

Nothing was drawn, fetched or sent anywhere: the library computed numbers from the bars it was handed, and everything else is yours. [JavaScript library](/script/integrate/javascript) takes each of the four steps apart.

## The one line that divides the work

**OpenScript owns anything that produces a number. Your platform owns anything that produces a pixel, a database row or a process.**

Indicator values, order fills, the position, profit and loss, stops, targets and trailing, the equity curve, drawdown and win rate are language semantics. They are identical everywhere the language runs. Bars, your symbology, your market's charges, your storage, your job scheduling and your report page are yours, because a platform in another market has different answers to every one of them.

That line is what makes this promise possible: **a script backtested in a trader's browser produces the same trades as the same script backtested on your servers.** A server backtest whose numbers disagree with the trader's own chart is worse than none, because there are then two answers and no way to tell which is wrong.

## How a script becomes numbers

```text
script text  ->  compiled program (plain data)  ->  engine  ->  values, markers, alerts and orders
```

The compiler does not emit JavaScript or Python. It emits a [compiled program](/script/integrate/compiled-program): a versioned data structure holding a list of instructions and the tables that describe the study, its inputs and its outputs. An engine runs a script by walking that list, one bar at a time.

Compiling and running are separate steps and can happen on different machines, which decides where your costs land:

| Work | Where it usually runs | What it costs you |
|---|---|---|
| Compiling a script | The trader's browser, in milliseconds | Nothing on your servers |
| Drawing a study on a chart | The trader's browser | Nothing beyond the page you already serve |
| Backtesting over bars already on the chart | The trader's browser | Nothing on your servers |
| A strategy running while nobody is watching | Your server | A process per running strategy |
| A backtest over a range too long to send to a browser | Your server | A job |

Most of what your users do never reaches your servers. Store a compiled program keyed by its hash and compile again only when the script changes.

## What each piece is, and which way it depends

The JavaScript library is one package with an entry point per tier, so a consumer who wants only the compiler never loads an adapter.

| Import | Is | Depends on |
|---|---|---|
| `openalgo-script` | The compiler, the engine and the backtest | Nothing |
| `openalgo-script/editor` | Six headless language functions: highlight, complete, diagnose, hover, signature and format | The compiler |
| `openalgo-script/adapters/charts` | Turns a compiled study into an indicator for openalgo-charts, the OpenAlgo charting engine | The compiler, and the chart as an optional peer dependency |
| `openalgo-script/adapters/codemirror` | The drop-in editor adapter: wires the six functions into a popular open-source editor component | The editor functions, and that component as an optional peer dependency |

Dependencies point one way. The compiler knows no chart and no editor; only an adapter knows two worlds at once. That makes an adapter the piece a platform replaces rather than patches: a platform with its own chart writes its own chart adapter and keeps everything else, and a platform with its own editor does the same on that side. Nothing in the package names a browser global, so every file loads in a web worker and on a server.

The Python engine is a separate package in another language. It runs a program compiled elsewhere, which is what lets a Python-only production server run strategies without a JavaScript runtime in its image.

## Adopting it one step at a time

Each row is useful on its own, and nobody has to take the next one.

| You want | You add | Roughly |
|---|---|---|
| Scripts that produce numbers | `openalgo-script`, and the six things of the host interface | An afternoon |
| Those studies on your chart | The [chart adapter](/script/integrate/charts-adapter), and a chart | Days. Little more if you already use openalgo-charts |
| Traders writing scripts in your app | The [editor functions](/script/integrate/editor-integration), with your own text component or the drop-in adapter | Days |
| Backtests your traders can trust | The [backtesting API](/script/integrate/backtesting-api), your bars and your charge schedule | Nothing extra to install: it is in the first row's package |
| Traders trading from it | The order half of the [host interface](/script/integrate/host-interface), wired to your order API | About a week |
| Strategies on a Python server | The [Python engine](/script/integrate/python-engine) | Days |
| The language on your own stack | Your own engine for the compiled program, held to the [conformance suite](/script/integrate/conformance) | Weeks |

The last row is the one that makes OpenScript a standard rather than a library. A platform that will not run anyone else's interpreter reads the compiled program format, writes its own engine and passes the suite, and its traders' scripts are then the same scripts as everyone else's.

## What your platform supplies

Whatever you take, you supply six things, and a trading platform already has all of them:

1. **Bars**: open, high, low, close, volume and time, oldest first.
2. **Instrument facts**: tick size, lot size, session, timezone and the rest of the instrument record.
3. **More bars on request**, for another instrument or another timeframe, when scripts read them.
4. **Somewhere to draw**.
5. **Somewhere to send orders**, if scripts may trade.
6. **Somewhere to save settings**.

No instrument naming scheme, exchange rule or broker concept appears in the language. **A symbol is opaque to the engine**: it never parses one, never builds one and hands it back exactly as it received it. A relative contract, such as the at-the-money call of the nearest NFO expiry, resolves once at the start of a run, and every later action uses that resolved identity, so an exit never names a different contract from the one entered. [Host interface](/script/integrate/host-interface) gives the exact shape of each duty and what happens when you cannot answer.

## What the design guarantees

| Guarantee | Why it holds |
|---|---|
| Nothing turns text into code | A compiled program is data. The engine walks an instruction list and never calls a string evaluator or a function builder, so it runs under a content security policy with no `unsafe-eval` |
| A script cannot reach anything | It can only do what the instruction set exposes. There is no network, no file system and no access to the page or process it runs in |
| A runaway script stops | The engine owns the loop, so loop, instruction, memory and time budgets are counters inside it |
| One failing script takes nothing else down | Loading and running never throw. A failure is a diagnostic with a code and a source line, returned for that script alone |
| Same program, same bars, same numbers | Arithmetic order, rounding and every library function's accumulation order are specified, so every conforming engine agrees to the last bit. The one exception in 0.5.0 is the transcendental functions, such as `exp()`, `pow()` and the trigonometric family, which still use the platform's own maths library and can differ in the last bit between machines |
| No runtime dependencies | Both packages declare none, and the project's build checks every import against that |

Those properties are why a platform can run many customers' scripts in one process, which a design that generates code cannot offer. A content security policy is the set of rules a web page sends the browser about what it may run; `unsafe-eval` is the permission that lets a page turn text into code, and the engine never needs it.

On a server you can make the first guarantee hold in your own process as well. Start the process that runs the engine with the runtime switch that refuses the string evaluator and the function builder:

```bash
node --disallow-code-generation-from-strings server.mjs
```

The switch refuses those two names in that one process and nothing wider. A child process gets its own options, so set it on every process you start, and keep anything that evaluates user text away from the engine's process. In a browser, a content security policy without `unsafe-eval` does the same job. If you run the engine in a web worker, serve the worker as a file from your own origin rather than building it from a blob, because a policy that allows scripts from your origin refuses a worker built from a blob URL.

## Where 0.5.0 stands

Stated plainly, so nothing on this list surprises you later:

- **Studies are the finished surface.** Plots, fills, levels, markers, bar colours, backgrounds, drawing objects, tables, alerts and reads of other timeframes and instruments all run, on a chart and headless.
- **The backtest does not model everything, and says which.** A stop or target attached with `exit()` or `order.bracket()` does not fill yet. A quantity stated in cash or as a percentage of equity is refused before the first bar rather than filled. A strategy that scales in is charted at the size it ended up entering, which can overstate its drawdown. A script cannot read its own equity during a run.
- **The Python engine runs less than the JavaScript one.** It has no drawing objects, tables or reads of other data, and no array functions, `print()`, date functions or a few chart and session facts. A script that needs one is refused at load, naming what is missing; [Python engine](/script/integrate/python-engine#what-this-engine-runs-and-what-it-refuses) has the list.
- **The portability claim is still being tested.** The JavaScript and Python engines agree to the last bit on every case they both run, but both were written in the same repository. No engine written by anyone else has run the conformance suite yet, and no case yet asserts a per-bar indicator value.
- **Some library names are planned.** The compiler refuses a planned name with [OS2020](/script/errors/names-and-types#os2020) where it is written.

[Release notes](/script/resources/release-notes) carries the full list, release by release.

## Where to go next

| You are building | Read |
|---|---|
| Anything in JavaScript or TypeScript | [JavaScript library](/script/integrate/javascript) |
| Studies on openalgo-charts | [Chart adapter](/script/integrate/charts-adapter) |
| A script editor | [Editor integration](/script/integrate/editor-integration) |
| Backtests from code | [Backtesting API](/script/integrate/backtesting-api) |
| A Python server | [Python engine](/script/integrate/python-engine) |
| Your own host | [Host interface](/script/integrate/host-interface) |
| Your own engine | [Compiled program](/script/integrate/compiled-program), then [Your own engine](/script/integrate/conformance) |

**Related.** [Introduction](/script/getting-started/introduction), [Execution model](/script/language/execution-model), [JavaScript library](/script/integrate/javascript), [Python engine](/script/integrate/python-engine), [Compiled program](/script/integrate/compiled-program), [Host interface](/script/integrate/host-interface), [Glossary](/script/resources/glossary)


## JavaScript library

Source: https://openalgo.in/script/integrate/javascript

This page covers the core of `openalgo-script`, the npm package that holds the OpenScript compiler and engine: compiling a script, loading the program it produces, running it over bars, following a live bar as it forms, and reading every output back. It is the page to read before the chart adapter, the editor functions or the backtest, because all three are built on these calls.

Everything here runs the same way in a browser tab, in a web worker and in Node.js. The core imports no package and touches no browser global.

## Install

```bash
npm install openalgo-script
```

| Fact | Value |
|---|---|
| Module format | ECMAScript modules only. Use `import`, not `require` |
| Server runtime | Node.js 22 or newer |
| Types | TypeScript declarations ship in the package; no separate types package |
| Runtime dependencies | None |
| Entry points | `openalgo-script`, `openalgo-script/editor`, `openalgo-script/adapters/charts`, `openalgo-script/adapters/codemirror` |

## A complete run

The script is the EMA cross from [Two libraries](/script/integrate/overview), saved as `ema-cross.os`:

```openscript
version 1
study("EMA cross", overlay = true)

fast = input(9, "Fast", min = 1)
slow = input(21, "Slow", min = 2)

ef = ema(close, fast)
es = ema(close, slow)
plot(ef, "Fast EMA", aqua)
plot(es, "Slow EMA", orange)

if crossUp(ef, es)
    signal("BUY", color = green, at = "below", shape = "arrowUp")
    alert("Fast EMA crossed above slow EMA", id = "cross-up", title = "Cross up")
if crossDown(ef, es)
    signal("SELL", color = red, shape = "arrowDown")
```

Most hosts wrap the four compiler stages in one helper. This is the one the rest of the Integrate pages use:

```js title="compile.mjs"
import { sourceFile, parse, check, emit, DiagnosticBag } from "openalgo-script";

/** Source text in; a compiled program, or the reasons there is none, out. */
export function compile(name, text) {
  const file = sourceFile(name, text);
  const bag = new DiagnosticBag();
  const checked = check(file, parse(file, bag), bag);
  const { program } = emit(file, checked, bag);
  if (bag.hasErrors || program === undefined) {
    return { ok: false, file, diagnostics: bag.ordered() };
  }
  return { ok: true, file, program, diagnostics: bag.ordered() };
}
```

And this runs the script over two NSE sessions of 5 minute bars and reads back a plot and every marker:

```js title="run.mjs"
import { readFileSync } from "node:fs";
import { load } from "openalgo-script";
import { compile } from "./compile.mjs";

const compiled = compile("ema-cross.os", readFileSync("ema-cross.os", "utf8"));
if (!compiled.ok) throw new Error(compiled.diagnostics.map((d) => d.message).join("\n"));
const { program, file } = compiled;

const loaded = load(program, { source: file, settings: { fast: 9, slow: 21 } });
if (!loaded.ok) throw new Error(`${loaded.diagnostic.code}: ${loaded.diagnostic.message}`);
const engine = loaded.engine;

// Your own bars, oldest first. Here: two NSE sessions of 5 minute bars.
const bars = [];
for (let day = 0; day < 2; day++) {
  const open = Date.UTC(2025, 0, 6 + day, 3, 45); // 09:15 IST
  for (let i = 0; i < 75; i++) {
    const close = 820 + 6 * Math.sin((day * 75 + i) / 8);
    bars.push({ time: open + i * 300_000, open: close - 0.4, high: close + 0.9, low: close - 1.1, close, volume: 12_000 });
  }
}

const run = engine.run(bars);
if (run.diagnostic) throw new Error(run.diagnostic.message);

// Plots: one column per plot, read by channel.
const fast = program.outputs.plots.find((p) => p.title === "Fast EMA");
console.log("Fast EMA on the last bar:", engine.column(fast.channel).at(-1));

// Markers: a marker channel holds its text on the bars where signal() fired.
for (const marker of program.outputs.markers) {
  run.bars.forEach((bar, i) => {
    const text = bar.columns[marker.channel];
    if (text !== null) console.log(new Date(bars[i].time).toISOString(), text, marker.position, marker.shape);
  });
}
```

It prints the fast average on the last bar, then every marker with its time, position and shape, one marker declaration at a time:

```text
Fast EMA on the last bar: 816.7267522123716
2025-01-06T07:50:00.000Z BUY below arrowUp
2025-01-07T05:45:00.000Z BUY below arrowUp
2025-01-06T05:35:00.000Z SELL above arrowDown
2025-01-06T09:55:00.000Z SELL above arrowDown
2025-01-07T07:55:00.000Z SELL above arrowDown
```

The `SELL` marker sits `above` the bar because its `signal()` call names no position and `above` is the default. The rest of this page explains each call in that file.

## Compiling

Compilation is four stages, and each is a function you can call on its own:

| Call | Takes | Gives |
|---|---|---|
| `sourceFile(name, text)` | A file name for messages, and the raw text | A `SourceFile`: the text normalised (a byte order mark dropped, CRLF turned into LF), with line and column lookups |
| `parse(file, sink)` | The source file and a diagnostic sink | The syntax tree. It recovers around a malformed statement, so later stages still run |
| `check(file, script, sink)` | The file and the tree | A `CheckedScript`: names resolved, types checked, warmup tracked |
| `emit(file, checked, sink, options?)` | The file and the checked script | `{ program, gaps }`: the [compiled program](/script/integrate/compiled-program), and anything that stopped a faithful program being produced, in which case `program` is `undefined` |

A `DiagnosticBag` is the sink every stage reports into. Read it with `all`, `errors`, `warnings`, `hasErrors` and `ordered()`, which sorts everything into the order a reader walks the file.

> **Decide whether a compile succeeded from `bag.hasErrors`, not from whether `program` is defined. The emitter can still hand back a program for a file the checker refused, and such a program must never be stored or run. A file with warnings and no errors is a good program.**

### Reading a diagnostic

Every diagnostic carries the same fields, wherever it was raised:

| Field | Holds |
|---|---|
| `code` | The stable catalogue code, such as `OS2001` |
| `severity` | `"error"` or `"warning"` |
| `stage` | Which stage raised it |
| `title` | The catalogue's short name for the code |
| `message` | The sentence, with the names from your file filled in |
| `fix` | What to change, from the same catalogue |
| `autofix` | Whether an editor may apply the fix without asking the trader a question |
| `span` | `offset`, `length`, `line` and `column` in the normalised text |
| `values` | The values that filled the message, for a host that writes its own wording |

`renderDiagnostics(file, diagnostics)` prints them the way a terminal shows them, with the line and a caret under the fault:

```js
import { renderDiagnostics } from "openalgo-script";
import { compile } from "./compile.mjs";

const result = compile("typo.os", `version 1
study("Typo")
plot(emaa(close, 9), "EMA")
`);
if (!result.ok) {
  for (const d of result.diagnostics) {
    console.log(`${d.severity} ${d.code} at ${d.span.line}:${d.span.column}: ${d.message}`);
    console.log(`  fix: ${d.fix}`);
  }
  console.log(renderDiagnostics(result.file, result.diagnostics));
}
```

```text
error OS2001 at 3:6: emaa is not defined at this point in the file.
  fix: Assign emaa above this line, move this line below its assignment, or correct the spelling to ema.
typo.os

3 | plot(emaa(close, 9), "EMA")
  |      ^^^^
OS2001: emaa is not defined at this point in the file.
Fix: Assign emaa above this line, move this line below its assignment, or correct the spelling to ema.
```

The first two lines are the loop's own; the rest is `renderDiagnostics`. The `values` of that diagnostic are `{ name: "emaa", suggestion: "ema" }`, which is what a host needs to offer the correction as a one-click fix.

The [Errors](/script/errors/overview) section documents every code. An editor that shows errors while the trader types calls `diagnose` from the editor entry point instead, which runs the same stages: see [Editor integration](/script/integrate/editor-integration).

### Store the program, not the compile

A compiled program is kilobytes and it is cacheable. Compile once per saved revision of a script, store the result keyed by its hash, and load it wherever it has to run:

```js title="store.mjs"
import { readFileSync } from "node:fs";
import { canonicalise, programHash, sourceHash, loadText } from "openalgo-script";
import { compile } from "./compile.mjs";

const compiled = compile("ema-cross.os", readFileSync("ema-cross.os", "utf8"));
if (!compiled.ok) throw new Error(compiled.diagnostics.map((d) => d.message).join("\n"));
const { program, file } = compiled;

// What you store beside the script: the canonical text and both hashes.
const stored = {
  sourceHash: sourceHash(file.text),  // identifies the text the trader wrote; equals program.source.hash
  programHash: programHash(program),  // identifies the program an engine runs
  program: canonicalise(program),     // the bytes that travel, and that the hash covers
};

// Later, in another process or on another machine:
const loaded = loadText(stored.program, { settings: { fast: 5 } });
console.log(loaded.ok); // true
```

`canonicalise` writes the one canonical text of a program: sorted keys, no whitespace, fixed number and string spellings. `programHash` and `sourceHash` are SHA-256 hashes written as `sha256:` and 64 hexadecimal digits. Record both beside a chart, a backtest or a running strategy and you can prove months later that an engine upgrade did not change a result. The [Python engine](/script/integrate/python-engine) is handed exactly this canonical text.

> **`sourceHash` hashes exactly the text you give it. The program's own `source.hash` is taken over the normalised text, with a byte order mark dropped and CRLF line endings turned into LF, so hash `file.text` from the compile rather than the raw file. On a file saved with CRLF line endings the two differ.**

## Loading

`load(program, options)` verifies a program in full before a single bar runs, then resolves its inputs. It never throws. It answers either `{ ok: true, engine, inputs }` or `{ ok: false, diagnostic }`, so a program that cannot run says so before your chart has drawn anything.

| Option | What it is |
|---|---|
| `settings` | The stored input values, keyed by input key. Leave it out to run every input at its default |
| `host` | What your platform supplies: the instrument record, the chart clock, an order route and a provider for other instruments' bars. See below |
| `source` | The `SourceFile`, so a runtime diagnostic can carry an offset as well as a line |
| `limits` | Any of the engine's budgets you want to change, as a partial object |
| `clock` | A function returning the current time in milliseconds. With `limits.ms` set, it turns on the per-bar time budget |
| `time` | How a `"time"` input's stored wall clock text becomes an instant. Without it the text is read as UTC |

### Settings and input keys

An input's key is the name it was assigned to: `fast = input(9, "Fast")` is keyed `fast`. An input that no variable receives is keyed by its title. That covers an input written straight into a call, such as `ema(close, input(9, "Length"))`, keyed `Length`, and one written as a declaration option, such as `strategy("S", qty = input(1, "Quantity"))`, keyed `Quantity` (there `qty =` names the option, not a variable). The keys a program declares are in `program.inputs`, each with its kind, label, default, bounds and options, which is everything a settings dialog needs.

A stored value that fails its input's type, `min`, `max` or `options` is refused at load with [OS6019](/script/errors/data#os6019), naming the key. It is not quietly replaced with the default, because a settings dialog that ignores what the user typed is worse than one that says the value is out of range. A stored key the program no longer declares is ignored, so removing an input and putting it back keeps the user's value.

Changing a setting is a new load. Settings are read once, before bar 0, because declaration options may be written from inputs and those are fixed before the first bar.

### The host

The `host` option is the whole of what the engine reads from your platform. Every field is optional:

```js
const loaded = load(program, {
  source: file,
  settings: { fast: 9, slow: 21 },
  host: {
    instrument: {
      symbol: "SBIN", exchange: "NSE", interval: "5", timezone: "Asia/Kolkata",
      tickSize: 0.05, lotSize: 1, currency: "INR", instrumentType: "equity",
      hasVolume: true, session: { start: "09:15", end: "15:30", days: [1, 2, 3, 4, 5] },
    },
    now: Date.now(),                 // what chart.now() answers
    route: (effect, bar) => { /* send effect.intents to your order path */ },
    requestBars: (query) => undefined, // bars for another instrument: see Host interface
  },
});
```

What each field turns on is decided at load, not discovered on bar four thousand:

| Field left out | What happens |
|---|---|
| `instrument` | Every `chart.tickSize`, `chart.lotSize` and other instrument fact reads as absent. With no `session`, the per-bar session facts are absent too, and `vwap()` never starts. A `session` stated without a `timezone` is refused at load with [OS6012](/script/errors/data#os6012), because a wall clock window with no zone is not a window |
| `route` | The engine has no `orders` capability. A strategy is refused at load with [OS6006](/script/errors/data#os6006) naming it; every study still runs |
| `requestBars` | The engine has no `req.symbol` capability, so a script that calls `req.symbol()` is refused at load. `req.timeframe()` needs no provider: the engine folds the chart's own bars |

[Host interface](/script/integrate/host-interface) is the full contract: every field of the instrument record, how to answer a request, and how orders and their fills travel.

### Other loaders

| Call | Use it when |
|---|---|
| `loadText(text, options)` | The program arrives as text from storage or the network. The text must be the canonical encoding; any other spelling is refused with OS6018, because the hash you recorded was taken over canonical bytes |
| `verify(program, { capabilities, limits })` | You want to refuse a bad program when it arrives rather than when it is first drawn. `capabilitiesFor(hasOrderRoute, hasRequestProvider)` gives the capability list an engine with that host would have, and `DEFAULT_LIMITS` the default budgets |

## Bars

The engine takes one plain object per bar, oldest first:

```json
{
  "time": 1736135100000,
  "open": 820.4,
  "high": 821.5,
  "low": 819.3,
  "close": 821.1,
  "volume": 12000,
  "oi": null
}
```

| Field | Rule |
|---|---|
| `time` | The bar's **open** instant, whole milliseconds since the Unix epoch, UTC. 09:15 IST is 03:45 UTC |
| `open`, `high`, `low`, `close` | Numbers. A price your feed does not have is `null`, never zero and never carried forward |
| `volume` | Optional. Leave it out or write `null` when you do not know it. `0` means you know nobody traded |
| `oi` | Optional open interest, for NFO and MCX contracts. A level, not a flow |

The engine derives `hl2`, `hlc3`, `ohlc4` and `hlcc4` itself, with a fixed order of operations, so do not supply them.

Two things are refused rather than run on. A run over no bars is [OS6010](/script/errors/data#os6010). A bar whose `time` is not strictly after the one before it is [OS6011](/script/errors/data#os6011), naming that bar. The engine never sorts, deduplicates or repairs what it is given, so either one is a fix on your side.

## Running over history

`engine.run(bars, states?)` runs the whole dataset in one call and returns `{ bars, diagnostic }`, one result per bar. It stops at the first bar that fails. The optional `states` array gives each bar's state (below); without it every bar is history.

An engine keeps every bar it has been given. A second `run` on the same engine continues after the last bar rather than starting again, so handing it the same history twice is refused with [OS6011](/script/errors/data#os6011) at its first bar. To start over, for a new instrument, a new interval or a corrected history, call `load` again and use the new engine. `engine.barCount` says how many bars an engine holds.

Each bar's result:

| Field | Holds |
|---|---|
| `index` | Which bar, counting from 0 |
| `columns` | One value per channel, in channel order. `null` where nothing wrote the channel on this bar |
| `applied` | Whether the bar was decided, so its markers, alerts and orders took effect |
| `effects` | The order calls the bar applied, each with the order intents it became |
| `frames` | What the order updates delivered since the previous bar did to the strategy's ledger |
| `alerts` | The alerts this bar raised |
| `diagnostic` | The failure that stopped the bar, when one did |

The engine never throws. A failing bar returns its diagnostic with a catalogue code and a source line, the bar's columns stay as they were, and `engine.failed` becomes true: a stopped script stays stopped until you load it again. One broken script cannot take anything else in your process down.

## Reading the outputs

Everything a script declares is in `program.outputs`, fixed before bar 0, and each declaration names the channel its per-bar value travels in:

| Output | Declared in | Per-bar value |
|---|---|---|
| Plots | `outputs.plots`: `key`, `title`, `type`, `channel`, `color`, `width`, `lineStyle`, `scale` and more | A number, or `null` for a gap |
| Fills | `outputs.fills`: `between` (the two plot keys), `colorUp`, `colorDown`, `opacity` | None of their own: a band is drawn from its two plots' columns. A band coloured per bar also has `colorUpChannel` and `colorDownChannel` |
| Levels | `outputs.levels`: `title`, `channel`, `color`, `lineStyle`, `lineWidth` | The price on each bar. A chart draws the line at the last bar's |
| Markers | `outputs.markers`: `channel`, `position`, `shape`, `color` | The marker's text on the bars where `signal()` fired |
| Alerts | `outputs.alerts`: `key`, `title`, `condChannel`, `messageChannel`, `frequency` | Raised alerts arrive on the bar result |
| Bar colour and background | `outputs.barColor`, `outputs.background` | A colour, or `null` to leave the bar alone |
| Tables | `outputs.tables`: `position`, `rows`, `cols`, `options` | `engine.tables()` gives each grid as the last bar left it |
| Drawing objects | Not declared: the set grows and shrinks as bars arrive | `engine.drawings()` gives every live object, oldest first |

`engine.column(channel)` returns a whole channel over every bar run so far, which is what a chart wants. A colour in a column is an object, `{ tag: "color", r, g, b, a }`: red, green and blue from 0 to 255 and alpha from 0 to 1. In the declarations of `program.outputs` the same colour is written as four numbers, `[r, g, b, a]`.

**Absence is `null` at the boundary.** A plot that has not warmed up yet is `null`, not zero, and a chart draws a gap. There is no warmup length to trim: the line starts on the bar its value stops being absent. [Warmup](/script/language/warmup) explains the rule from the script's side.

The [chart adapter](/script/integrate/charts-adapter) does all of this mapping for openalgo-charts. Read the outputs yourself when you draw on another chart or send the values somewhere else.

## A live bar

A chart's newest bar keeps changing until its interval ends. The engine handles that with two calls and one piece of state:

| Call | Means |
|---|---|
| `engine.append(bar, state)` | A new bar has opened. Runs it once |
| `engine.update(bar, state)` | The newest bar changed. Rolls the engine back to the start of that bar, then runs it again |
| `state.isConfirmed` | The bar's interval has elapsed. True for every bar of history |
| `state.isRealtime` | A realtime feed is driving this bar. False for every bar of history |

```js title="live.mjs"
import { readFileSync } from "node:fs";
import { load } from "openalgo-script";
import { compile } from "./compile.mjs";

// A fresh engine: it has run no bars yet.
const { program, file } = compile("ema-cross.os", readFileSync("ema-cross.os", "utf8"));
const { engine } = load(program, { source: file });

const open = Date.UTC(2025, 0, 6, 3, 45); // 09:15 IST
const bar = (i, close) => ({ time: open + i * 300_000, open: close, high: close + 1, low: close - 1, close, volume: 5_000 });

// History first: 40 closed bars.
engine.run(Array.from({ length: 40 }, (_, i) => bar(i, 820 - i * 0.5)));

// A new 5 minute bar opens: append it once.
let r = engine.append(bar(40, 801), { isConfirmed: false, isRealtime: true });

// Every tick inside it: update the same bar. The engine rolls back to the start
// of the bar first, so ten updates give the answer one update would.
r = engine.update(bar(40, 809), { isConfirmed: false, isRealtime: true });
r = engine.update(bar(40, 830), { isConfirmed: false, isRealtime: true });
// r.columns has the new averages; r.applied is false and r.alerts is empty.

// The interval elapses: confirm it. Only now do markers, alerts and orders apply.
r = engine.update(bar(40, 830), { isConfirmed: true, isRealtime: true });
// r.applied is true, and r.alerts holds the cross-up alert:
// { key: "cross-up", title: "Cross up", message: "Fast EMA crossed above slow EMA", bar: 40, time: ... }
```

Three rules make a live chart agree with a backtest of the same bars:

- **Update the bar that moved; never append a tick as a new bar.** A tick appended as a bar is a bar that never existed: every average counts it, the history operator shifts, warmup ends early, and nothing raises an error. The study on the trader's chart quietly stops being the study a backtest computes.
- **Plots redraw on every update; markers, alerts and orders wait.** A condition that was true halfway through a bar and false at its close places no order and fires no alert, because the execution that saw it was rolled back. A script can opt out with `onUnconfirmed = true` in its declaration; see [Realtime and confirmation](/script/language/realtime-and-confirmation).
- **An alert fires only on a realtime bar.** Adding a study to a chart that already holds history fires nothing for those bars, so a history load with `isRealtime` false raises no alerts.

A `live var` in a script is the one value that keeps its update-to-update state instead of rolling back, and a script that uses one is not reproducible by design.

## Budgets

The engine counts its own loop, so a runaway script stops rather than freezing the tab or the server. `DEFAULT_LIMITS` holds the defaults, and `load(program, { limits })` changes any of them:

| Limit | Default | Guards |
|---|---|---|
| `arrayElements` | 1,000,000 | Elements in one array ([OS5002](/script/errors/limits#os5002)) |
| `stringLength` | 100,000 | Characters (Unicode code points) in one string |
| `drawingObjects` | 10,000 | Lines, labels, boxes and polylines a script holds at once |
| `frames` | 64 | Nested function calls |
| `steps` | `null`: worked out from the program | Instructions one bar may execute. Left at `null`, the engine computes the most a bar of this program can possibly execute and uses that, so a host rarely sets it |
| `ms` | `null`: no time limit | Wall clock milliseconds per bar. Takes effect only with the `clock` option |
| `loops`, `history`, `instructions`, `states`, `requests` | `null`: no ceiling | Ceilings on what a program may ask for: the loop budget and retained history its `limits` line states (OS5003), its instruction count (OS5009), its state regions (OS5004) and its reads of other data (OS5006). A program over a ceiling is refused at load, never silently capped |

A script's own `limits` line sets its loop budget and retained history; [Limits](/script/writing/limits) covers them from the script's side.

## Running it safely on a server

Run the engine in its own worker process, not on the thread that handles requests: an engine pass over a long history is pure computation with no pause in it. Treat a long run as a job, return an identifier at once and report progress on a channel you already keep open, because a proxy's read timeout will end a request long before a multi-year backtest does. Start that process with `--disallow-code-generation-from-strings`, as [Two libraries](/script/integrate/overview#what-the-design-guarantees) explains.

The engine takes one object per bar rather than columnar arrays. That costs roughly three times the memory of typed arrays over a decade of one minute bars, which matters for a very long backtest inside a browser tab and not on a server. Build the bar objects straight from your data response rather than parsing into one shape and copying into another, so you hold one copy.

**Related.** [Two libraries](/script/integrate/overview), [Chart adapter](/script/integrate/charts-adapter), [Editor integration](/script/integrate/editor-integration), [Backtesting API](/script/integrate/backtesting-api), [Host interface](/script/integrate/host-interface), [Compiled program](/script/integrate/compiled-program), [Execution model](/script/language/execution-model)


## Chart adapter

Source: https://openalgo.in/script/integrate/charts-adapter

The chart adapter draws a compiled study on openalgo-charts, the OpenAlgo charting engine. One call, `descriptorFor`, turns a compiled program into the chart's indicator descriptor: the legend row, the settings dialog, the plots and everything else the script declares. The chart then calls back into the adapter whenever it needs values, and the adapter runs the engine. This page covers wiring it up, the options you pass, how a live bar and a settings change reach the study, and what the chart does not draw in 0.5.0.

The adapter computes nothing itself. Every value on the chart is the engine's, so the chart and a backtest of the same script cannot disagree.

## Install

```bash
npm install openalgo-script openalgo-charts
```

The chart is an optional peer dependency of `openalgo-script`, and the adapter never imports it. A host that wants only the language installs no chart. The chart's repository is [github.com/marketcalls/openalgo-charts](https://github.com/marketcalls/openalgo-charts).

## A study on a chart

The script is the EMA cross from [JavaScript library](/script/integrate/javascript). This module runs in the browser page that holds the chart, and uses the `compile` helper from that page:

```js title="chart.mjs"
import { createChart, registerIndicator } from "openalgo-charts";
import { descriptorFor } from "openalgo-script/adapters/charts";
import { compile } from "./compile.mjs";

// `bars` and `source` are yours: the instrument's bars and the saved script text.
// 1. The chart and the instrument's own candles. Chart bar times are UTC seconds.
const chart = createChart(document.getElementById("chart"));
chart.addSeries("candlestick").setData(bars);

// 2. Compile the trader's script.
const compiled = compile("ema-cross.os", source);
if (!compiled.ok) throw new Error(compiled.diagnostics.map((d) => `${d.code} line ${d.span.line}: ${d.message}`).join("\n"));

// 3. Turn it into an indicator descriptor and register it with the chart.
const descriptor = descriptorFor(compiled.program, {
  source: compiled.file,
  category: "My scripts",
  instrument: {
    exchange: "NSE",
    lotSize: 1,
    hasVolume: true,
    session: { start: "09:15", end: "15:30", days: [1, 2, 3, 4, 5] },
  },
});
registerIndicator(descriptor);

// 4. Add it, with the settings stored for this instance.
const study = chart.addIndicator(descriptor.id, { fast: 9, slow: 21 });
```

`study` is the chart's own handle: `study.setSettings({ fast: 5 })` recomputes with a new setting and `study.remove()` takes it off. From here the chart owns the study. It calls the descriptor when bars load, when the newest bar moves and when a setting changes, and the adapter answers each call from the engine.

In TypeScript, one line checks the descriptor against the chart's own type at your build, and fails to compile if the two have drifted apart:

```js
// TypeScript
import type { IndicatorDescriptor } from "openalgo-charts";
const checked: IndicatorDescriptor = descriptorFor(compiled.program);
```

## What the descriptor holds

Everything a script declares is fixed before its first bar, so the descriptor can build the legend and the settings dialog before any value exists:

| Script | Descriptor |
|---|---|
| The title and `group` in `study()` | `name` and `category` |
| `overlay = true` or `false` | `placement`: `"onchart"` on the price pane, `"pane"` in a pane of its own |
| `range` in `study()` | `range`: the pane's fixed scale |
| Each `input()` | One row of `inputs`: the settings dialog, with each input's label, default, bounds, options and group |
| Each `plot()` and `plotCandles()` | One entry of `plots`, with its style, colour, width, scale and price format |
| Each `fill()` | One entry of `fills`, a band between two plots |
| Each `level()` | `levels`, drawn at the last bar's price |
| Each `signal()` | `markers`: every marker the last calculation produced |
| `barColor()` and `background()` | `barColors` and `background`: one colour per bar, `null` for none |
| `table()` and `cell()` | `table`: the grid as the last executed bar left it |
| `draw.line()`, `draw.box()` and the other drawing objects | `draws`: every object the script currently holds |
| Each `alert()` | One entry of `alerts`, the conditions a user can subscribe to |
| A read of another instrument with `req.symbol()` | `attach`: the lifecycle that fetches its bars |

A member is present only when the script declares something for it, so a study with no table has no `table` hook and costs the chart nothing for one.

**The id is the source hash.** `descriptor.id` defaults to `openscript:` followed by the script's source hash. A saved chart layout stores the id and the settings, so the same script restores to the same study, and an edited script does not silently inherit the settings of the study it replaced. A host that manages its own script identities passes `id`.

## Options

`descriptorFor(program, options)` takes what the chart and the program cannot tell the adapter:

| Option | What it is |
|---|---|
| `id` | The registry id. Defaults to the source hash, as above |
| `category` | The picker category, used when the script's own `group` is empty |
| `settings` | The stored settings, for declaration options written with `input()`. See below |
| `instrument` | Instrument facts the chart does not hold: the exchange, the lot size, `hasVolume`, the session, and anything else in the [instrument record](/script/integrate/host-interface#instrument-facts) |
| `markerColor` | The colour of a marker whose script named none. Defaults to a neutral grey |
| `source` | The `SourceFile`, so a diagnostic carries an offset as well as a line |
| `orders` | Where a strategy's orders go, as a route function. See [Strategies on a chart](#strategies-on-a-chart) |
| `simulateOrders` | Run a strategy against the backtest's simulated destination. Off by default |
| `limits`, `clock` | The engine's budgets, as in [JavaScript library](/script/integrate/javascript#budgets) |
| `resolveTime` | Turns a `"time"` input's stored wall clock text into UTC seconds, given the text and the chart's timezone. Without it the text is read as UTC |

### Instrument facts come from two places

On every calculation the chart hands the adapter what it knows: the symbol, the interval, its timezone, the chart clock for `chart.now()`, and the tick size from the price pane. Everything else comes from the `instrument` option, and the adapter merges the two.

**State the session.** A chart holds an interval and a timezone, and no exchange calendar. `session.isFirstBar`, `session.isLastBar` and every study anchored to them, `vwap()` among them, read the session in the instrument record. A host that states no session gets those facts absent on every bar, which is honest and leaves a VWAP study with an empty pane. For NSE and BSE equities and NFO contracts the session is `09:15` to `15:30`, Monday to Friday, in `Asia/Kolkata`.

## Settings, and when to build again

Some parts of the descriptor are values fixed when it is built; others are functions the chart calls with its current settings each time.

| Resolved when `descriptorFor` runs | Resolved again on every call |
|---|---|
| `id`, `name`, `category`, `placement`, `inputs`, `plots`, `fills`, `alerts` | `calc`, `calcTail`, `range`, `levels`, `barColors`, `background`, `markers`, `table`, `draws`, `attach` |

Most settings changes need nothing from you: `study.setSettings` reaches the second column at once. The first column matters only for a declaration option a script writes from an input, such as `study("Bands", precision = input(2, "Decimals"))` or a plot width written `width = input(2, "Width")`. Those are part of the declared shape, so they read the `settings` you passed to `descriptorFor`. A host that keeps one descriptor per study instance passes that instance's stored settings and builds the descriptor again when the user changes one of them.

A plot colour taken from a colour input is the one exception in the first column: the plot also carries the input's key as its `colorKey`, and openalgo-charts restyles the line from each instance's own settings with no rebuild.

A stored value the engine would refuse, such as a precision of 99 against an input declared `max = 8`, reads as the input's default in the declared shape, so the settings dialog still opens and the user can correct it. The calculation itself still stops with [OS6019](/script/errors/data#os6019) naming the key and the bound.

## How the chart drives the engine

| The chart calls | The adapter does |
|---|---|
| `calc(bars, settings, store, ctx)` | Loads the program into a fresh engine and runs every bar. Used on the first draw, on a settings change and whenever the history changes |
| `calcTail(bars, settings, fromIndex, previous, store, ctx)` | Re-runs the bar that moved with `update` and appends any bar after it. Used as a live bar forms |

The tail path is refused rather than trusted. It runs only when the engine it holds was loaded with the same settings and has executed exactly the bars before the tail, checked by the first and last bar times. Anything else returns nothing and the chart falls back to a full `calc`, because splicing a tail onto a history that changed underneath it draws a plausible wrong study.

Every bar of history is handed over as confirmed and not realtime. The newest bar takes its state from the chart, so markers, alerts and orders wait for it to close, exactly as in [JavaScript library](/script/integrate/javascript#a-live-bar). The adapter keeps each instance's engine in the store the chart gives that instance. `release(store)` drops it and closes any fetch still in flight; for a study that reads another instrument, the lifecycle the chart attaches calls it when the study is detached.

## When a study cannot run

The engine never throws, and a chart's calculation has nowhere to put a failure except an exception. So the adapter throws a `ChartAdapterError` carrying the engine's diagnostic whole:

| `error.name` | Raised when | What the user can do |
|---|---|---|
| `IndicatorInputError` | The program was refused before any bar ran: a setting ([OS6019](/script/errors/data#os6019)), a limit, or a capability the host did not give ([OS6006](/script/errors/data#os6006)) | For a setting, correct it in the study's dialog; the chart treats this name as an input error the user can fix. A limit or a capability is the host's to fix |
| `OpenScriptError` | The program ran and stopped on a bar | Fix the script. `error.diagnostic.span.line` says where |

`error.diagnostic` has the catalogue code, the message, the fix and the source position, the same record [JavaScript library](/script/integrate/javascript#reading-a-diagnostic) describes. Show those rather than a generic failure.

## Reading another instrument

A study that calls `req.symbol()`, such as a stock's ratio to the NIFTY index, needs bars the chart does not hold. The descriptor of such a study carries an `attach` lifecycle, and the chart fetches through the bars provider you register:

```js
chart.setBarsProvider(({ symbol, exchange, interval, from, to, signal }) =>
  fetchBars(symbol, exchange, interval, from, to, signal),
);
```

`fetchBars` is your own call to your data source, returning chart bars (times in UTC seconds) for that instrument and range. The sequence is:

1. The first calculation runs before anything is fetched. The read is absent, `req.isReady()` is false, and the rest of the study draws.
2. The lifecycle asks your provider for the range the chart covers, extended back by the warmup the read needs and rounded to whole bars of the requested interval, so a new fetch happens only when a bar of that interval closes.
3. When the bars arrive the chart recomputes, and the read has values.

While a wider fetch is in flight, the bars already fetched keep serving, so the line does not break each time a bar of the requested interval closes. A provider that rejects is reported as [OS6009](/script/errors/data#os6009) carrying your provider's own message, which the script can read with `req.error()` and the chart shows as the study's data status; everything that does not depend on the read keeps drawing. A read of the chart's own instrument at another timeframe, `req.timeframe()`, needs no provider: the engine folds the chart's own bars.

## Strategies on a chart

A strategy places orders, and a chart has nowhere to send them. So a strategy handed to the adapter with no destination is refused at load with [OS6006](/script/errors/data#os6006) naming the `orders` capability. That refusal is deliberate: a chart that quietly swallowed a strategy's orders while drawing its plots would show a strategy the user believes is running.

Two ways to draw one:

| Option | What happens |
|---|---|
| `simulateOrders: true` | The strategy runs against the same simulated destination the [backtest](/script/integrate/backtesting-api) uses. Its plots, legend and settings work, its position is right, and the entries and exits on the price are the same fills as the backtest report of the same script. Nothing is sent anywhere |
| `orders: route` | Your own route function receives each order and its intents. Wins over `simulateOrders`. See [Host interface](/script/integrate/host-interface#orders) |

```js
const descriptor = descriptorFor(strategyProgram, {
  simulateOrders: true,
  instrument: { exchange: "NSE", lotSize: 1, hasVolume: true },
});
```

A chart draws a strategy; it does not report one. The simulated destination prices fills against the chart's tick size and uses neutral money settings, and nothing a chart draws reads a profit figure. Run the [backtest](/script/integrate/backtesting-api) for the report.

## Who owns the candles

Several studies can share a pane, and the instrument's candles are one object. So only one study's `barColor()` is drawn: **the study latest in the chart's own study order that paints**. The rule follows the order the user sees in the legend and changes only when the user adds, removes or reorders a study, so the candles never flicker between two colourings while both studies recompute. openalgo-charts applies it for you: of the studies that paint, the one added last owns the candles, and removing or hiding it gives the bars their own colours back. A host drawing on another chart applies the rule with `candleOwner`:

```js
import { candleOwner } from "openalgo-script/adapters/charts";

// Your studies in legend order, oldest first. A study that paints carries its barColors hook.
const owner = candleOwner([
  { id: "ema-cross", barColors: undefined },
  { id: "trend-paint", barColors: trendDescriptor.barColors },
  { id: "rsi" },
]);
// owner is "trend-paint": draw its bar colours and ignore any other study's.
```

Backgrounds need no such rule. Every colour carries its own alpha, and two translucent backgrounds compose.

## What the chart does not draw in 0.5.0

The compiled program carries all of a script's outputs; a few have no field on the chart's descriptor to land in, and are left out rather than approximated:

| Script feature | On the chart |
|---|---|
| A `fill()` whose colour changes per bar | Drawn in its first plot's colour, faded. The chart's band takes one colour |
| A table's title | Not shown. The chart's grid has no heading |
| A second `table()` in one study | Only the first grid is drawn |
| An alert's `frequency` | Every alert is checked once per new bar, which is `"oncePerBar"` |

Each of these is a fact about this chart's descriptor, not about the language. Another host reading the same compiled program may draw all of it.

## Writing an adapter for another chart

If your platform has its own chart, write your own adapter and keep everything else. The chart adapter is the only module that knows both worlds, which makes it the piece to replace rather than patch. Everything it does is on [JavaScript library](/script/integrate/javascript): load the program, run the bars, read `program.outputs` and the channels, and map each output onto your chart's own series, bands, markers and grids. The rules worth copying from this one:

- Convert bar times at the boundary. openalgo-charts counts in seconds and the engine in milliseconds; a study comparing `time` against an anchor is otherwise out by a factor of a thousand and still draws.
- Keep absence as absence. A `null` in a column is a gap, never a zero.
- Re-run a moving bar with `update`, never by appending it again.
- Hand over the whole set of drawing objects after every run and replace what you drew before. There is no add or remove event to track.

**Related.** [JavaScript library](/script/integrate/javascript), [Host interface](/script/integrate/host-interface), [Backtesting API](/script/integrate/backtesting-api), [Editor integration](/script/integrate/editor-integration), [Visuals overview](/script/visuals/overview), [Other instruments](/script/data/other-instruments), [Realtime and confirmation](/script/language/realtime-and-confirmation)


## Editor integration

Source: https://openalgo.in/script/integrate/editor-integration

This page is for a platform putting an OpenScript editor in front of its traders. The language ships the intelligence an editor needs as six pure functions, text in and data out, with no user interface and no DOM: `highlight`, `complete`, `diagnose`, `hover`, `signature` and `format`. The text component, the panel around it, the theme, saving and the apply button stay yours. This page covers each function, what it costs to call, and the drop-in adapter for teams that would rather not wire the six by hand.

None of the six is written separately from the language. Highlighting is the real lexer. Diagnostics are the compiler's own, with their messages and fixes taken from the error catalogue. Completions and hover text come from the standard library manifest and the specification's own tables, and the defaults a signature shows are the defaults the compiler applies. A word added to the language is coloured, completed and explained on the day it is added, with no change to your editor.

> **The Scripts panel in the /trading page uses the language's own highlighting and lists the compiler's diagnostics in its console, with each one's line, code and fix. It does not offer completion, hover cards or signature help in this release. Those three functions are here for your own editor.**

The Scripts panel console, below, lists the same diagnostics `diagnose` returns for a file: each one's code, line and column, message and fix, warnings (here OS8010, a value assigned and never read) as well as errors.


## A first editor

This module turns a script into highlighted HTML, lists its problems, and applies a completion. It runs in a browser or in Node.js:

```js title="editor.mjs"
import { highlight, diagnose, complete } from "openalgo-script/editor";
import { normaliseSource } from "openalgo-script";

const escape = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

/** The script as HTML: one span per piece, one class per kind. */
export function toHtml(source) {
  return highlight(source)
    .map((p) => (p.kind === "whitespace" ? escape(p.text) : `<span class="os-${p.kind}">${escape(p.text)}</span>`))
    .join("");
}

/** The rows of a problems panel, straight from the compiler. */
export function problems(source) {
  return diagnose(source).map((d) => ({
    line: d.span.line,
    column: d.span.column,
    code: d.code,
    severity: d.severity,
    message: d.message,
    fix: d.fix,
  }));
}

/** Accept a completion row: replace the span it names, never just insert at the cursor. */
export function accept(source, row) {
  const { offset, length } = row.replace;
  return source.slice(0, offset) + row.insert + source.slice(offset + length);
}

// Offsets index the normalised text, so normalise the buffer once on the way in.
const buffer = normaliseSource('version 1\r\nstudy("EMA", overlay = true)\r\nplot(em');
const rows = complete(buffer, buffer.length); // [{ label: "ema", insert: "ema", kind: "function", ... }]
const next = accept(buffer, rows[0]);         // ends with "plot(ema"
console.log(problems(next));                  // OS3012, OS1012 and OS2014: the unfinished call, each with its fix
```

Style the classes once. The set of kinds is closed, so the theme never needs a rule for a word the language adds later.

## highlight

```text
highlight(source) -> [{ kind, span, text }, ...]
highlightLines(source) -> [{ line, pieces }, ...]
```

Every piece of the file, in order, **covering every character exactly once**. No piece is empty, none overlaps its neighbour, and joining their texts gives you the file back. That is the property to rely on: a highlighter that drops one character draws everything after it on that line one column to the left, and the caret stops sitting where the text is.

`highlightLines` gives the same pieces grouped by line, one entry per line including the blank ones, with no line ending inside any piece. Use it when you render a line at a time; it saves you deciding which line a run of whitespace across a line ending belongs to.

| Kind | Is |
|---|---|
| `keyword` | A reserved word of the language |
| `builtin` | A name in the standard library, such as `ema`, `close` or `aqua` |
| `name` | Any other name, including the ones the script declares |
| `number` | A number literal |
| `string` | A string literal |
| `color` | A hexadecimal colour literal such as `#ff8800` |
| `comment` | A comment |
| `operator` | A mark that computes something, such as `+` or `>=` |
| `punctuation` | A mark that groups or separates, such as `(` or `,` |
| `whitespace` | Spaces and the layout between tokens |
| `unknown` | Source the lexer took no token from |

A named colour such as `aqua` is a library value, so it arrives as `builtin`. To paint it in its own colour, ask `hover` for its channels. `unknown` does not mean wrong: `diagnose` is what says whether something is a mistake.

**Comments come back from here.** The parser has no use for a comment, so the lexer emits no token for one, and a host usually ends up recovering them from the gaps. Ask `highlight` for pieces of kind `comment` instead; scanning for `//` yourself goes wrong on a `//` inside a string.

**Offsets index the normalised text.** A byte order mark is dropped and CRLF becomes LF before anything reads a file. Normalise your buffer once with `normaliseSource` from the core, or draw from each piece's own `text` and never index your buffer at all.

## diagnose

```text
diagnose(source) -> [Diagnostic, ...]
```

Every diagnostic a compile of that text produces, in the order a reader walks the file. It is the same `Diagnostic` record the compiler produces anywhere: `code`, `severity`, `message`, `fix`, `span` and the `values` that filled the message. [JavaScript library](/script/integrate/javascript#reading-a-diagnostic) lists every field.

**It is the whole compiler, not a subset.** Some codes are raised only when the program is emitted, so an editor that stopped after the type checker would show a clean file and then have the apply button refused with a code the panel never mentioned.

**A file that does not parse still answers usefully**, because that is the normal state of a file someone is typing into. A lexical mistake costs its own character, a statement that will not parse costs its own line, and the lines around it are still checked, so a trader fixing three mistakes sees all three at once.

**What it costs.** A finished ninety line study takes well under a millisecond, about a third to half of one, and the same file with a bracket left open halfway down, the state a file is in the moment someone types one, takes about a millisecond. The project's build holds both to a budget (1.5 and 4 milliseconds), so a change that made the editor several times slower fails there before it reaches you. Call it on a debounce of a few tens of milliseconds and it will not show in a frame.

## complete

```text
complete(source, offset) -> [{ label, insert, kind, detail, summary, planned, refusal, replace }, ...]
```

What may be written at that position: the library's functions and values, the names the file has declared and still has in scope, the named arguments of the call being written, and the members of a namespace after a dot.

| Field | Holds |
|---|---|
| `label` | What the row shows |
| `insert` | What accepting it writes |
| `kind` | `function`, `value`, `namespace`, `member`, `variable` or `argument` |
| `detail` | The signature, such as `ema(src: series number, len: number) -> series number` |
| `summary` | The name's one-line description |
| `planned` | True for a name the library declares and this release does not implement |
| `refusal` | For a planned name, the compiler's own sentence refusing it |
| `replace` | The span the row replaces: the word already typed, or an empty span at the cursor |

Four rules, each of which is a bug if you ignore it:

- **Replace `replace`, do not insert at the cursor.** Otherwise a trader who types `em` and accepts `ema` gets `emema`.
- **The list is already filtered, exactly.** Names are case sensitive. Run a fuzzy matcher over the list and you will offer `EMA`, which does not compile.
- **A name is offered from the end of the statement that declares it**, because that is where the compiler lets you use it, and a name declared inside a block is offered only inside that block.
- **Planned names come last and are marked.** Grey the row and show `refusal`, or drop it. Writing one is [OS2020](/script/errors/names-and-types#os2020).

Reserved words are not offered. The core exports them as `RESERVED_WORDS` if your editor wants to add them where it judges them useful.

## hover

```text
hover(source, offset) -> { kind, span, name, signatures, summary, warmup, type, declaredAt, colour, planned, refusal } | undefined
```

What the word under the pointer is. `kind` is `library`, `declared` or `keyword`, and nothing comes back for a space, an operator, a number or a string. Take this file:

```openscript
version 1
study("RSI", overlay = false)
r = rsi(close, 14)
plot(r, "RSI", purple)
```

With the pointer on `rsi` in the third line, `hover` answers:

```json
{
  "kind": "library",
  "span": { "offset": 44, "length": 3, "line": 3, "column": 5 },
  "name": "rsi",
  "signatures": ["rsi(src: series number, len?: number = 14) -> series number"],
  "summary": "0 to 100 reading of how one-sided the last `len` changes were",
  "warmup": "bar `len`",
  "type": "series number",
  "planned": false
}
```

`span` is the word to underline. Fields with nothing to say, such as `declaredAt` for a library name, are left out.

| The word is | You get |
|---|---|
| A library name | Every signature, its one-line description, and `warmup`: the first bar it can have a value on |
| A name the script declared | The `type` the checker worked out, and `declaredAt`, the span where it was declared, for a jump to definition |
| A named colour | `colour`: its red, green, blue and alpha, for a swatch |
| A reserved word | `kind: "keyword"` with its name and span, and no description |

Three things a hover cannot give you, because the text does not exist in a form it can read:

| Missing | Why, and what to do |
|---|---|
| An explanation of a reserved word | Each word is explained in prose where the language uses it, not in a table. Link the word to the [keywords reference](/script/reference/keywords) |
| A description per signature | A name with several signatures lists them all with one description, the first one's |
| A description per parameter | `signature` gives each parameter's name, type, default and accepted values, and no sentence |

## signature

```text
signature(source, offset) -> { name, of, signature, parameters, active, summary, planned, refusal, span } | undefined
```

The call being written, and which parameter the cursor is in. It works on a call that is still being typed, which is the only time it is asked: the call is found from the brackets, not from a syntax tree that does not exist yet.

| Field | Holds |
|---|---|
| `name`, `signature`, `summary` | The call, its full signature and its description |
| `of` | `library`, or `declared` for a function the file defines itself |
| `parameters` | Each parameter's `name`, `type`, `required`, `defaultText` and accepted `values` |
| `active` | The index of the parameter the cursor is in, or `-1` past the last one, which is a call with too many arguments |
| `span` | Where the call's name is |

**The default shown is the default the compiler applies**, for an ordinary library call and for the output declarations, `plot()`, `plotCandles()`, `fill()`, `level()`, `signal()`, `alert()`, `input()`, `table()`, `barColor()` and `background()`, whose optional arguments become fields of the compiled program. Inside `plot(close, ` it reports `color` defaulting to `none`, `width` to `1.5` and `style` to `"line"`, the values the compiler writes. A written label decides the active parameter, because a named argument may sit anywhere after the positional ones. While a call is unfinished its overload is chosen by the number of arguments alone; once the arguments are written, `diagnose` reports any call that resolves to something other than what was meant.

In 0.5.0 `signature` returns nothing inside `study(` or `strategy(`: the two declaration lines have no entry for it to read. Link those to the [declarations reference](/script/reference/declarations) instead.

## format

```text
format(source) -> source, laid out
```

The language's canonical layout: indentation, spacing around operators and after commas, where a comment sits, blank lines.

```text
before:  basis=sma(close,20)
after:   basis = sma(close, 20)
```

**Formatting never changes what a script computes, and that is checked rather than promised.** The project lays out every example and every test script again, compiles both texts and requires identical compiled programs. On top of that, every call lexes its own output and compares it with the tokens that went in; if anything moved, your source comes back untouched. A formatting rule that is wrong therefore does nothing, which is the only acceptable way for it to fail on a strategy holding a position.

**A source that does not parse comes back unchanged**, byte for byte, rather than laid out as far as it could be. Line breaks inside a statement stay where the writer put them.

## What stays yours

The text component and its caret. The panel, the gutter, the squiggles and the theme. Debouncing, and when to format. Saving, revisions and the apply button. Which diagnostics to show, and where. All of that is design, and it is yours for the same reason your chart is: a language package with opinions about it is a package nobody can embed.

## The drop-in editor adapter

For a team using the popular open-source editor component this adapter targets, `openalgo-script/adapters/codemirror` wires the six functions into it in a few lines. The component is an optional peer dependency and nothing in the adapter imports it, so installing `openalgo-script` pulls in no editor at all.

| Export | Is |
|---|---|
| `openscriptStream` | A line tokenizer for the component's stream language support: highlighting, a line at a time |
| `openscriptCompletion` | A completion source |
| `openscriptLint` | A linter source: the compiler's diagnostics, with the fix under each message and the code as its source |
| `openscriptHoverTooltip(render)` | A hover tooltip source. You pass the renderer |
| `openscriptSignatureTooltip(render)` | A signature tooltip that follows the cursor inside a call's brackets. You pass the renderer |
| `formatDocument` | An editor command that formats the whole document and keeps the caret where it was |
| `hoverLines`, `signatureLines` | What each tooltip says, as an ordered list of lines, for your renderer |
| `diagnosticsFor(text)` | The linter's answer for a text, without a view |
| `documentOf(text)`, `normalisedOffset(document, offset)` | The offset translation the adapter does, for a host that wires some pieces itself: the normalised text, and how an offset maps between it and the editor's own document |
| `HIGHLIGHT_TOKENS`, `COMPLETION_TYPES` | The mapping from the language's kinds to the component's style and completion type names |

```js
import {
  openscriptStream, openscriptCompletion, openscriptLint,
  openscriptHoverTooltip, openscriptSignatureTooltip, formatDocument,
  hoverLines, signatureLines,
} from "openalgo-script/adapters/codemirror";

// StreamLanguage, autocompletion, linter, hoverTooltip, showTooltip and keymap are
// the editor component's own exports, imported from its own packages.

// The markup is yours: the adapter draws nothing.
const panel = (lines) => {
  const dom = document.createElement("div");
  dom.className = "os-tooltip";
  for (const line of lines) dom.append(Object.assign(document.createElement("div"), { textContent: line }));
  return { dom };
};

export const openscript = [
  StreamLanguage.define(openscriptStream),
  autocompletion({ override: [openscriptCompletion] }),
  linter(openscriptLint),
  hoverTooltip(openscriptHoverTooltip((held) => panel(hoverLines(held)))),
  showTooltip.compute(["doc", "selection"], openscriptSignatureTooltip((held) => panel(signatureLines(held)))),
  keymap.of([{ key: "Shift-Alt-f", run: formatDocument }]),
];
```

**Nothing in the package draws.** Both tooltips take the markup as a required parameter with no default, which is what lets every file in the package load in a worker and on a server. What a tooltip says is still the compiler's: `hoverLines` and `signatureLines` give you the lines, and only the element around them is yours.

**Offsets are translated for you.** Every span the language produces indexes the normalised text, while the component holds the document exactly as it was typed, CRLF line endings included. The adapter maps between the two, so a squiggle below the first line of a file with CRLF line endings lands under the text it is about.

**Highlighting a line at a time loses nothing.** The component asks for one line at a time and `highlight` reads a whole file, but nothing in the language crosses a line ending, and the project measured both over thousands of lines of scripts and malformed input without one piece differing.

What the adapter narrows, compared with calling the six functions yourself:

| Narrowing | Instead |
|---|---|
| The component looks style names up at run time, so no type check catches a name its theme does not know | Spread `HIGHLIGHT_TOKENS` with your theme's own names, or call `highlight` and draw your own decorations |
| The completion list is filtered exactly and the component is told not to filter it again | Call `complete` and filter the rows your own way before handing them over |
| The signature tooltip appears whenever the cursor is inside a call, with no key to open or dismiss it | Call `signature` and show it on the gesture your product uses |
| Formatting replaces the whole document, and a document with CRLF line endings comes back with LF | Call `format` and apply the result the way your product wants |

A platform with its own editor writes its own adapter in the same shape and keeps everything else: the six functions are the supported path, not a fallback.

**Related.** [JavaScript library](/script/integrate/javascript), [Two libraries](/script/integrate/overview), [The editor](/script/getting-started/the-editor), [Reading an error](/script/errors/overview), [Style guide](/script/writing/style-guide), [Chart adapter](/script/integrate/charts-adapter)


## Backtesting API

Source: https://openalgo.in/script/integrate/backtesting-api

This page covers backtesting from code with `openalgo-script`: one call that runs a compiled strategy over your bars against a simulated order destination and returns a **run record**. It is the same backtest the Backtest panel in the /trading page runs in the browser, so a report your server produces agrees with the one a trader sees. Read it to build a backtest service, a batch job over many instruments, or a report page of your own.

The run record is the product, not a number printed at the end. It carries the program, the bars, the settings you chose, every order, every fill, the ledger and the report, so a result can be checked again months later and handed to another engine as a test case.

## A complete backtest

The strategy states its own costs, so the report is costed from the first run:

```openscript
version 1
strategy("EMA cross, costed", overlay = true, capital = 500000,
         qty = input(10, "Quantity", min = 1),
         fillOn = "nextOpen", slippage = 1,
         commissionType = "perTrade", commission = 20)

fastLen = input(9, "Fast", min = 1)
slowLen = input(21, "Slow", min = 2)
fast = ema(close, fastLen)
slow = ema(close, slowLen)
goLong = crossUp(fast, slow)
goFlat = crossDown(fast, slow)

if goLong and pos.isFlat
    buy()
else if goFlat and pos.isLong
    close()

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
```

This file runs it over ten NSE sessions of 5 minute bars. `compile` is the helper from [JavaScript library](/script/integrate/javascript), and `sampleBars` stands in for your stored history:

```js title="backtest.mjs"
import { readFileSync } from "node:fs";
import { backtest, settingsFor } from "openalgo-script";
import { compile } from "./compile.mjs";
import { sampleBars } from "./bars.mjs";

const source = readFileSync("ema-cross-costed.os", "utf8");
const compiled = compile("ema-cross-costed.os", source);
if (!compiled.ok) throw new Error(compiled.diagnostics.map((d) => d.message).join("\n"));

// The instrument the money is priced under.
const contract = {
  symbol: "SBIN", exchange: "NSE", currency: "INR",
  tickSize: 0.05, lotSize: 1, pointValue: 1, digits: 2,
};

const result = backtest(compiled.program, sampleBars(), settingsFor(contract), {
  sourceText: compiled.file.text, // the normalised text the program was compiled from
  instrument: {
    interval: "5", timezone: "Asia/Kolkata", instrumentType: "equity", hasVolume: true,
    session: { start: "09:15", end: "15:30", days: [1, 2, 3, 4, 5] },
  },
});
if (!result.ok) throw new Error(`${result.diagnostic.code}: ${result.diagnostic.message}`);

const { summary, trades } = result.record.report;
console.log(`${summary.tradeCount} closed trades, net ${summary.netProfit.toFixed(2)} ${summary.currency}`);
console.log(`charges ${summary.charges.toFixed(2)}, win rate ${summary.winRate === null ? "none" : (summary.winRate * 100).toFixed(1) + "%"}`);
console.log(`max drawdown ${summary.maxDrawdown.toFixed(2)} at ${new Date(summary.maxDrawdownAt).toISOString()}`);
for (const t of trades) {
  console.log(t.side, t.units, t.entryPrice, "->", t.exitPrice, "net", t.netProfit.toFixed(2));
}
```

```js title="bars.mjs"
/** Ten NSE sessions of 5 minute bars, 09:15 to 15:30 IST, on a 0.05 tick. A stand-in for your stored history. */
export function sampleBars() {
  const tick = (x) => Math.round(x * 20) / 20;
  const bars = [];
  let price = 800;
  let n = 0;
  for (let day = 0; day < 10; day++) {
    const open = Date.UTC(2025, 0, 6 + day + 2 * Math.floor(day / 5), 3, 45); // skip weekends
    for (let i = 0; i < 75; i++, n++) {
      const o = price;
      price = tick(price + 3 * Math.sin(n / 17) + 1.2 * Math.cos(n / 5));
      bars.push({ time: open + i * 300_000, open: o, high: tick(Math.max(o, price) + 0.5), low: tick(Math.min(o, price) - 0.5), close: price, volume: 10_000, oi: null });
    }
  }
  return bars;
}
```

It prints:

```text
6 closed trades, net 3847.00 INR
charges 240.00, win rate 100.0%
max drawdown -262.00 at 2025-01-15T05:30:00.000Z
long 10 817.25 -> 883.55 net 623.00
long 10 818 -> 887.05 net 650.50
long 10 816.0999999999999 -> 886.05 net 659.50
long 10 819.6 -> 885.35 net 617.50
long 10 816.4 -> 887.4 net 670.00
long 10 817 -> 883.65 net 626.50
```

Each trade paid 40 in charges: 20 on the entry and 20 on the exit. The prices are raw binary64 numbers, `816.0999999999999` among them, because nothing in a report is rounded; round when you print.

`backtest` answers `{ ok: true, record }`, or `{ ok: false, diagnostic }` when the run cannot be carried out at all. It throws in one case only: a `sourceText` that is not the text the program was compiled from, which is a mistake in the calling code rather than a run that failed.

## The call

```text
backtest(program, bars, settings, options?) -> { ok: true, record } | { ok: false, diagnostic }
```

| Argument | What it is |
|---|---|
| `program` | A compiled program whose declaration is `strategy()` |
| `bars` | Your bars, oldest first, in the shape of [JavaScript library](/script/integrate/javascript#bars): `time` in UTC milliseconds, prices, and `volume` and `oi` as numbers or `null` |
| `settings` | Everything you decided about this run. Build it with `settingsFor` |
| `options` | What the record carries beside the run, below |

Every bar you hand over executes. The report window decides which of them the report is about.

### The contract

The contract is the instrument the money is priced under, and the one part of the settings you must state:

| Field | Means |
|---|---|
| `symbol`, `exchange` | The identity, carried into every order row. Opaque to the engine |
| `currency` | The currency every money figure is in, such as `"INR"` |
| `tickSize` | The price increment. Slippage is counted in ticks |
| `lotSize` | Units per lot. Only a quantity stated in lots reads it |
| `pointValue` | Money per point of price per unit. `1` for an equity; set it for a contract whose point is worth more |
| `digits` | Decimal places a fill's charges are rounded to, half to even, once per fill |

On an NFO future or option a quantity is stated in units, so one lot of a contract whose lot size is 75 is `qty = 75` in the script. [Position and sizing](/script/strategies/position-and-sizing) covers sizing from `chart.lotSize`.

### Settings

`settingsFor(contract, chosen?)` fills every other setting with the absence of a choice. Override any of them in `chosen`:

| Setting | Default | Means |
|---|---|---|
| `range` | The whole of the bars supplied | The report window: `{ from, to }`, both inclusive, UTC milliseconds, `null` for an open end |
| `costs` | `null` | A charge schedule you supply. `null` charges what the script's `strategy()` line declares |
| `fill` | `DEFAULT_FILL` | How a resting limit or stop order is decided against a bar |
| `inputs` | None overridden | Input values, keyed by input key |
| `now` | `null` | The fixed value `chart.now()` answers |
| `tolerance` | `EXACT` | How closely a later comparison must agree. A bound that is not zero needs a `reason` |

```js
const settings = settingsFor(contract, {
  range: { from: Date.UTC(2025, 0, 13, 3, 45), to: null }, // report from the second week
  inputs: { Quantity: 25, fastLen: 5 },
});
```

Input keys follow the rule on [JavaScript library](/script/integrate/javascript#settings-and-input-keys): `fastLen` is the name its input was assigned to, and `Quantity` is the title of the input written inside the `strategy()` line, which no name receives. A key the program does not declare is ignored.

**The report window is not the bars.** Every bar you supply runs, oldest first, so the strategy's averages warm up before the window opens. A bar before the window is warmup: its orders are real, a position opened on it is carried into the window, and it contributes no point to the equity curve. A window that holds no bar is refused with [OS6020](/script/errors/data#os6020) rather than reported as a flat line. Supply history before the window to cover the longest warmup in the script; [Backtesting](/script/strategies/backtesting#warmup-how-much-history-the-first-trade-needs) explains how much.

**The default fill policy is conservative on purpose.** A limit order fills only where the bar traded through its price, not where the bar merely touched it, so an order resting exactly at the day's low is not credited with a fill nobody can prove. A stop that the market gapped through fills at the open, the price a trader would actually have got, not at its trigger. The policy carries its own version so a record made today replays under today's rules even after they change.

### Costs you supply

Leave `costs` at `null` and the run charges what the script declares: its `commission`, `commissionType` and `slippage`. To charge your platform's real schedule instead, supply one. Each line is charged per fill, on the side it names:

| Line field | Means |
|---|---|
| `name` | The line's name in the breakdown |
| `base` | `"turnover"` (a fraction of price times units), `"units"` (money per unit), `"order"` (money per fill) or `"charges"` (a fraction of the lines named in `of`) |
| `side` | `"buy"`, `"sell"` or `"both"`. A tax on one side is charged exactly on that side |
| `rate` | The fraction, or the money amount, for the base |
| `min`, `max` | Optional floor and cap in money, or `null` |
| `of` | For `base: "charges"`, the lines this one is levied on |

The schedule itself carries `currency`, `digits`, `slippageTicks` and `source: "supplied"`. A cash equity trade in India stacks brokerage, a transaction tax on the sell side, exchange charges, GST on brokerage and exchange charges, and stamp duty on the buy side:

```js
// Illustrative rates. Take the current figures from your broker's and the exchange's published schedules.
const costs = {
  currency: "INR", digits: 2, slippageTicks: 1, source: "supplied",
  lines: [
    { name: "brokerage", base: "order", side: "both", rate: 20, min: null, max: null, of: [] },
    { name: "stt", base: "turnover", side: "sell", rate: 0.00025, min: null, max: null, of: [] },
    { name: "exchange", base: "turnover", side: "both", rate: 0.0000297, min: null, max: null, of: [] },
    { name: "gst", base: "charges", side: "both", rate: 0.18, min: null, max: null, of: ["brokerage", "exchange"] },
    { name: "stamp", base: "turnover", side: "buy", rate: 0.00003, min: null, max: null, of: [] },
  ],
};
const result = backtest(program, bars, settingsFor(contract, { costs }));
```

`chargeFor(schedule, fill, contract)` returns one fill's breakdown, line by line, for a costs panel. Each line is computed unrounded and only the fill's total is rounded, once, to `digits`, so adding the printed lines by hand can land a fraction of the last digit away from the total. [Costs and fills](/script/strategies/costs-and-fills) covers choosing the numbers.

### What is refused before the first bar

Nothing has been computed when these are found, so a refusal costs one run rather than a report nobody can explain:

| Refused | Code |
|---|---|
| A supplied schedule while the script also declares a commission: the same money charged twice | [OS6023](/script/errors/data#os6023) |
| A schedule that cannot be evaluated against the contract | [OS6021](/script/errors/data#os6021) |
| A quantity in cash or a percentage of equity: a backtest fills in units and works out no running equity to size against | OS6021 |
| A quantity in lots when the contract states no lot size | OS6021 |
| A comparison tolerance with a bound and no reason | OS6021 |
| A report window holding none of the bars | [OS6020](/script/errors/data#os6020) |
| A setting that fails its input's rules | [OS6019](/script/errors/data#os6019) |

A failure on a bar during the run does not refuse the run: it is recorded in the record's `diagnostics` with the bar it happened on.

### Options

| Option | Means |
|---|---|
| `sourceText` | The script's own text, as the compiler normalised it: pass `compiled.file.text`. It is checked against the program's source hash, and text that does not match throws. Needed for the record to become a conformance case |
| `instrument` | The instrument facts beside the contract: `interval`, `timezone`, `instrumentType`, `hasVolume`, `hasOpenInterest` and `session`. The session is what `session.isLastBar` and every session-anchored call read. A record becomes a conformance case only when this states `hasVolume` |
| `form` | `"inline"` (the default) carries the bars in the record. `"referenced"` carries only their hash, count and first and last times, for bars you keep in your own store |

## The run record

| Field | Holds |
|---|---|
| `recordVersion` | The record format's version, 4 in 0.5.0 |
| `engine` | The engine's name and version |
| `languageVersion` | The language version the program was compiled under |
| `program`, `programHash` | The compiled program itself, and its hash |
| `source`, `sourceText` | The source hash, line count and file name, and the text when you supplied it |
| `settings` | Every setting of the run, defaults included |
| `instrument` | The instrument record the engine was handed |
| `bars` | The bars inline, or their hash and range when referenced |
| `frames` | Every order update the simulated destination sent |
| `fills` | Every fill, in order: side, units, price, bar and the position it moved |
| `orders` | The ledger: one row per order with its status, filled quantity and average price |
| `diagnostics` | Anything raised during the run, with the bar it was raised on |
| `report` | The figures, below |

### The report

| Part | Holds |
|---|---|
| `summary` | One flat object of figures: net profit, gross profit and loss, charges, return, trade counts, wins, losses and scratches, win rate, average win and loss, expectancy and its standard error, profit factor, maximum drawdown and run-up with their percentages and times, longest drawdown, average bars held, bars in the market and bar count |
| `trades` | Every trade: side, units, entry and exit bar, time and price, bars held, gross and net profit, charges, the best and worst open profit on the way, and whether it is still open |
| `equity` | One point per bar inside the window: realised profit, charges, open profit, cash, equity, exposure, drawdown and run-up |
| `monthly` | Net profit, return and trade count per calendar month |
| `markers` | Every entry and exit, with its bar, time, side, units and price, for drawing on a chart |
| `analysis` | Long and short trades apart, the largest win and loss, and the longest runs of wins and losses |

A few conventions that decide how you print them:

- **A percentage is a fraction.** `returnPercent` of `0.0077` is 0.77 percent; multiplying by a hundred is your display's job.
- **Drawdown is negative.** `maxDrawdown` of `-262` is a fall of 262 from the peak, and `maxDrawdownPercent` is negative too.
- **A summary figure that points at a bar carries its time**, such as `maxDrawdownAt`, because loading more history moves every bar index. Trades, markers and equity points carry both the bar index and the time; store and compare the time.
- **Some figures are `null` rather than zero**, where zero would be a claim the run cannot make: `winRate` when no trade decided anything, `profitFactor` when there was no losing trade, `averageBarsHeld` when nothing closed, and the times of a drawdown or run-up that never happened.
- **A trade wins or loses after charges.** A trade whose gross profit its charges ate is a loser.
- **Nothing in the report is rounded** except each fill's charges. Round only when you display.

[Reading a report](/script/strategies/reading-a-report) explains what each figure tells a trader.

## Keeping a run reproducible

A result nobody can reproduce is an anecdote. The record makes reproduction a check rather than a hope:

```js title="reproduce.mjs"
import { readFileSync, writeFileSync } from "node:fs";
import { backtest, settingsFor, recordToJson, recordFromJson, replay, rerun, runBytes, compareRuns } from "openalgo-script";
import { compile } from "./compile.mjs";
import { sampleBars } from "./bars.mjs";

// The same program, bars and contract as backtest.mjs.
const compiled = compile("ema-cross-costed.os", readFileSync("ema-cross-costed.os", "utf8"));
const { program, file } = compiled;
const bars = sampleBars();
const contract = { symbol: "SBIN", exchange: "NSE", currency: "INR", tickSize: 0.05, lotSize: 1, pointValue: 1, digits: 2 };
const before = backtest(program, bars, settingsFor(contract), { sourceText: file.text }).record;

// Store the whole record. It is the product of the run.
writeFileSync("run-1.json", recordToJson(before));

// Months later: read it back and check it.
const stored = recordFromJson(readFileSync("run-1.json", "utf8"));
const money = replay(stored);    // the money folded again from the stored fills
const again = rerun(stored);     // the program executed again over the stored bars
console.log(money.ok && money.report.summary.netProfit === stored.report.summary.netProfit); // true
console.log(again.ok && runBytes(again.record) === runBytes(stored));                       // true

// Change one thing and compare.
const after = backtest(program, bars, settingsFor(contract, { inputs: { fastLen: 5 } }), { sourceText: file.text }).record;
const comparison = compareRuns(before, after);
console.log(comparison.comparable, comparison.differences); // true [ { what: "inputs", ... } ]
```

| Call | Does |
|---|---|
| `recordToJson(record)`, `recordFromJson(text)` | The record as text and back. `recordFromJson` answers `null` for JSON that is not a record, or is a record from a newer release than yours; text that is not JSON at all throws, so wrap it when the text comes from outside |
| `replay(record, bars?)` | Folds the money again from the stored fills, with no engine: the report, recomputed |
| `rerun(record, bars?)` | Executes the stored program again over the stored bars. On the same engine the result is bit-identical, and `runBytes` is what you compare |
| `compareRuns(before, after)` | Puts two records side by side |
| `caseFilesFrom(record, identity)` | Turns a record into the files of a conformance case. See [Your own engine](/script/integrate/conformance#harvesting-a-case-from-a-run) |

A record made with `form: "referenced"` holds no bars, so hand the same bars back to `replay` and `rerun`; bars that hash differently are refused with OS6022.

`compareRuns` answers five things. `comparable` is false when the two runs were over different bars or a different contract, because subtracting money from two different studies is arithmetic with no meaning. `differences` names every setting that differs even when the pair is comparable, because the reason a run improved is as often a setting somebody forgot they changed as the change they meant to test. `deltas` gives each summary figure before, after and the change. `separation` is the difference in expectancy measured in standard errors, and `sharedTrades` counts trades that opened on the same bar on the same side in both: a separation near zero is noise, however good the new net profit looks.

Three habits make a result reproducible:

- **Pin the script revision.** Store the program hash with the run, so editing the script can never change a result already produced.
- **Prefer stored history to a fresh fetch.** A data vendor may revise a bar, and a run over refetched bars can differ from Monday to Tuesday for reasons nobody can see afterwards.
- **Record the cost settings with the run.** Charges change. A run under last year's schedule is not wrong, but it is not comparable with one under this year's unless both are on the record.

## A backtest service

A backtest over years of minute bars is pure computation with no pause in it. Three rules shape the service around it:

- **A run is a job, not a request.** Start it, return an identifier, report progress on a channel you already keep open, and let the client fetch the record by identifier. Any proxy in front of you has a read timeout, and a trader who sees a gateway error while the run carries on behind it is the worst version of this.
- **The computation runs in its own process**, never on the thread that answers requests.
- **Nothing large travels in a request body.** Records are fetched by identifier.

Put the honest limit of any backtest in your own interface as well: a backtest assumes the fills it models. It cannot know that your order would have moved the price, that the spread was wider than the bar suggests, or that the exchange was slow that morning. Modelled costs are an estimate, and modelled slippage is a guess with a number attached.

## What the 0.5.0 backtest does not model

- **A bracket's stop and target do not fill.** A strategy that attaches them with `exit()` or `order.bracket()` runs, but those levels are never filled. Manage exits in the script with `close()`.
- **Quantities in cash or a percentage of equity are refused**, as above. State quantities in units or lots.
- **A trade whose size changes is marked at one size.** The equity curve marks a trade at the size it ended up entering, from the bar it opened. A strategy that adds to a position is therefore charted deeper than the account went, and its drawdown figures are overstated; a partial close is marked at the full size afterwards, so its open profit is counted twice. Realised profit is right in both cases, because it is folded from the fills.
- **A script cannot read its own equity mid-run.** `pos.equity` and the other account figures are planned.

## Frames you supply

`backtestSupplied(program, bars, settings, frames, options?)` runs the same way against order updates somebody else supplied rather than a simulated destination: nothing is priced off a bar, no order rests, and an order the frames say nothing about stays where its placement left it. It is how a conformance case asserts the ledger against input no engine chose, and how you reproduce what a real destination answered. A supplied frame has the shape of a record's own `frames`: `afterBar`, the bar after which it arrived; `intent`, which order it is about, counting the run's orders from 1; `status`; the cumulative `filledQty` and `avgFillPrice`; and optionally `orderRef`, `text` and `time`. Handing a record's own frames back to `backtestSupplied` reproduces that record's report. What each status word means is on [Host interface](/script/integrate/host-interface#orders).

To drive a strategy bar by bar against your own order path instead, use the engine directly with an order route: [Host interface](/script/integrate/host-interface#orders) in JavaScript, and [Python engine](/script/integrate/python-engine#running-a-strategy) on a Python server.

**Related.** [Backtesting](/script/strategies/backtesting), [Reading a report](/script/strategies/reading-a-report), [Costs and fills](/script/strategies/costs-and-fills), [JavaScript library](/script/integrate/javascript), [Host interface](/script/integrate/host-interface), [Your own engine](/script/integrate/conformance)


## Python engine

Source: https://openalgo.in/script/integrate/python-engine

`openscript` on PyPI is an OpenScript engine written in pure Python. It runs a compiled program on a server where no JavaScript runtime is available, which for a production trading server is the ordinary case. This page covers installing it, getting a program to it, running bars through it one at a time, handling the bar that is still forming, and running a strategy whose orders go through your own order path.

It holds **no compiler**. A program is compiled wherever the JavaScript library runs, in the trader's browser, a build step or a small service, and handed to this engine as data. The two engines are held to each other by the conformance suite, and a disagreement between them blocks a release.

## Install

```bash
pip install openscript
```

| Fact | Value |
|---|---|
| Python | 3.12 or newer |
| Dependencies | None. The standard library only, and not the parts of it that would stop two runs agreeing: no network, threads, randomness or locale inside the package |
| Licence | Apache 2.0 |
| Version | 0.5.0, released together with `openalgo-script` |
| Code generation | None. No string evaluator, no statement executor, no import by a computed name and no objects loaded out of bytes, so many people's scripts can run in one process |

## Getting a program to Python

The program travels as its **canonical text**. Compile where the compiler runs, write the canonical encoding, store it keyed by its hash, and hand that text to Python. This costs one compile per saved revision of a script, not one per run.

The study for this page, `two-bar-mean.os`:

```openscript
version 1
study("Two bar mean", overlay = true)

len = input(2, "Length", min = 1)
mean = sma(close, len)

if close > mean
    signal("UP")

plot(mean, "Mean", aqua)
```

Compiled with `openalgo-script` in Node.js, using the `compile` helper from [JavaScript library](/script/integrate/javascript):

```js title="build.mjs"
import { readFileSync, writeFileSync } from "node:fs";
import { canonicalise, programHash } from "openalgo-script";
import { compile } from "./compile.mjs";

const compiled = compile("two-bar-mean.os", readFileSync("two-bar-mean.os", "utf8"));
if (!compiled.ok) throw new Error(compiled.diagnostics.map((d) => d.message).join("\n"));

// The canonical text is what the Python engine loads, and what programHash covers.
writeFileSync("program.json", canonicalise(compiled.program), "utf8");
console.log(programHash(compiled.program));
```

**It must be the canonical text**, not text that merely parses to the same program. The hash you record a run against was taken over those exact bytes, so a pretty-printed copy is refused at load with OS6018. `canonicalise` in either library writes the one accepted spelling.

## A complete run

```python title="run_study.py"
"""Run a compiled OpenScript study over bars, in Python."""
import pathlib

from openscript.adapter.serving import Serving
from openscript.contracts import Bar, BarState
from openscript.run import load_text
from openscript.verify import capabilities

CLOSED = BarState(is_new=True, is_confirmed=True, is_realtime=False, updates=1.0)
MOVING = BarState(is_new=False, is_confirmed=False, is_realtime=True, updates=2.0)
INSTRUMENT = {"symbol": "NIFTY", "exchange": "NSE_INDEX", "timezone": "Asia/Kolkata", "tickSize": 0.05}

# The canonical text the compiler emitted, stored as data. Nothing here compiles.
text = pathlib.Path("program.json").read_text(encoding="utf-8")

library = Serving()
loaded = load_text(text, {"len": 2}, library, capabilities=capabilities())
if not loaded.ok:
    refused = loaded.diagnostic
    raise SystemExit(f"{refused.code} at {refused.line}:{refused.column}")
run = loaded.run

start = 1735703100000  # 1 January 2025, 09:15 IST, in UTC milliseconds
closes = [100.0, 102.0, 101.0, 105.0]
previous = None
for index, close in enumerate(closes):
    bar = Bar(time=float(start + index * 60000), open=close, high=close, low=close, close=close)
    library.at_bar({"high": bar.high, "low": bar.low, "close": bar.close,
                    "previousClose": previous, "volume": bar.volume,
                    "isSessionFirst": index == 0}, index == 0)
    result = run.execute_bar(index, bar, CLOSED, supplied=len(closes), instrument=INSTRUMENT)
    if not result.ok:
        raise SystemExit(f"bar {index}: {result.diagnostic.code}")
    print(index, result.columns, result.applied_channels)
    previous = close

# The newest bar is still moving: execute it twice at the same index.
for close in (106.0, 104.0):
    bar = Bar(time=float(start + 4 * 60000), open=close, high=close, low=close, close=close)
    library.at_bar({"high": close, "low": close, "close": close, "previousClose": previous,
                    "volume": bar.volume, "isSessionFirst": False}, False)
    result = run.execute_bar(4, bar, MOVING, supplied=5, instrument=INSTRUMENT)
    print("moving", close, result.columns, result.applied_channels)
```

```text
0 [None, None] [1]
1 [101.0, 'UP'] [1]
2 [101.5, None] [1]
3 [103.0, 'UP'] [1]
moving 106.0 [105.5, 'UP'] []
moving 104.0 [104.5, None] []
```

Column 0 is the plot and column 1 the marker. Bar 0 has no mean yet, so the plot is `None`: warmup is absence, not zero. The last line is the one to understand. The second execution of bar 4 gives the mean of 105 and 104, not of 106 and 104, because the engine rolled the bar back before running it again. And `applied_channels` is empty on both executions of that bar, because nobody has confirmed it: the marker is computed but not committed.

## Loading

```python
from openscript.run import load, load_text
```

`load_text(text, settings, library, limits, capabilities, read_time)` reads the canonical text and then does everything `load` does. `load(raw, ...)` takes a program object built in the same process, which never was text and has nothing to be canonical about. Both return a `LoadResult` with `.run`, `.diagnostic` and the property `.ok`; exactly one of the first two is `None`.

| Argument | What it is |
|---|---|
| `text` or `raw` | The canonical text, or the program object |
| `settings` | The stored input values, keyed by input key. `{}` or `None` runs the defaults |
| `library` | The library seam: a `Serving` instance, below. `None` is a library with nothing in it |
| `limits` | `openscript.budget.EngineLimits`. Leave the default, `DEFAULT_LIMITS`, unless you have a reason |
| `capabilities` | The capability tags this run serves, from `capabilities(...)` |
| `read_time` | How a written date becomes an instant, for a `time` input. Without it the date is read as UTC |

**The library is a seam, not an import.** The machine asks the library for its manifest at load and for each call during a bar. `Serving` from `openscript.adapter.serving` joins the engine's library tables to that seam. Build one per run: `Serving()` for a study, `Serving(ledger)` for a strategy.

**Capabilities are what this run serves.** A program needing a tag missing from the list is refused at load, naming it, rather than run with a hole in it. `capabilities()` gives the machine's own tags; `capabilities("orders")` adds the tag a strategy needs.

### When a load is refused

A Python `Diagnostic` carries `.code`, `.line`, `.column`, `.severity` and `.values`, and **no message text**. The wording lives in the error catalogue, the same JSON file the compiler and these pages are built from. It ships in the `openalgo-script` npm package as `node_modules/openalgo-script/spec/errors.json`; copy it beside your Python code. Look the code up there and fill in the values, or show the code and link it to the [Errors](/script/errors/overview) pages:

```python
import json
import pathlib
import re

# The error catalogue: the same file the compiler and these pages are built from.
CATALOGUE = {entry["code"]: entry
             for entry in json.loads(pathlib.Path("errors.json").read_text(encoding="utf-8"))["entries"]}

def sentence(diagnostic):
    """The catalogue's message and fix for a diagnostic, with its values filled in."""
    entry = CATALOGUE[diagnostic.code]
    fill = lambda match: str(diagnostic.values.get(match.group(1), match.group(0)))
    return re.sub(r"\{(\w+)\}", fill, entry["message"]), re.sub(r"\{(\w+)\}", fill, entry["fix"])
```

For `{"len": 0}` against an input declared `min = 1`, that gives "The host supplied 0 for len, and the minimum is 1." with its fix.

| What happened | Code |
|---|---|
| The program calls a function this engine's library does not hold | [OS6004](/script/errors/data#os6004) |
| The program needs a capability this run does not serve | [OS6006](/script/errors/data#os6006) |
| The text is not the canonical encoding, or the program fails verification | [OS6018](/script/errors/data#os6018) |
| A stored setting fails its input's rules | [OS6019](/script/errors/data#os6019) |
| The compiled format version, or the language version, is not one this engine has | [OS6016](/script/errors/data#os6016), [OS6017](/script/errors/data#os6017) |

Four of those mean "this engine does not have that" rather than "the program is wrong": OS6004, OS6006, OS6016 and OS6017, each naming the missing thing. A host that can fall back to another engine, such as the JavaScript engine in a Node.js worker, reads those four differently from the rest.

## What this engine runs, and what it refuses

The Python engine is built for strategies and for studies that produce numbers. It does not carry a chart's drawing surface, and in 0.5.0 some of the library is still missing from it. Each gap is refused at load, by name, never answered with an empty value:

| Script uses | In the Python engine |
|---|---|
| Plots, fills, levels, markers, bar colour, background, alerts | Run. The values arrive in the bar result |
| Orders, positions and the strategy ledger | Run, with `capabilities("orders")` and `Serving(ledger)` |
| User functions, loops, `var`, and array literals read by index | Run |
| Moving averages, oscillators and the other indicators, the maths, string and colour functions | Run |
| Array functions such as `push()`, `size()`, `sort()` and `avg()` | Refused: OS6004 naming the function |
| `print()` | Refused: OS6004 naming `print` |
| The `date` functions, such as `date.hour()` and `date.dayOfWeek()` | Refused: OS6004 naming the function |
| `session.isIn()`, `session.isLastBar`, `chart.intervalMinutes`, `chart.isIntraday` | Refused: OS6004 naming the function |
| Drawing objects such as `draw.line()` | Refused: OS6006 naming `objects` |
| `table()` | Refused: OS6006 naming `tables` |
| `req.timeframe()` and `req.symbol()` | Refused: OS6006 naming the tag. The engine is handed no other bars |

Run a script that needs any of those on the JavaScript engine, which serves them all. `capabilities()` lists `arrays` even though the array functions are missing, so check a script against this table, or load it once, before you deploy it to a Python server.

## The bar cycle

```python
result = run.execute_bar(index, bar, state, supplied=None, instrument=None, now=ABSENT)
```

One call is one execution of one bar, all eleven steps of the bar cycle in order.

| Argument | What it means |
|---|---|
| `index` | Which bar, counting from 0. **The same index twice is a re-execution of that bar, not a new one** |
| `bar` | `Bar(time, open, high, low, close, volume, oi)`. `time` is the bar's open instant in UTC milliseconds. A price or volume you do not have is left absent, never zero and never carried forward |
| `state` | `BarState(is_new, is_confirmed, is_realtime, updates)`: the four facts only the side that built the bar knows |
| `supplied` | How many bars you have supplied. It decides `bar.isLast` and nothing else |
| `instrument` | The instrument record as a dictionary, the same fields as on [Host interface](/script/integrate/host-interface#instrument-facts). The `chart` namespace and tick rounding read it |
| `now` | The fixed value `chart.now()` answers. Absent unless you state one |

Three of those decide more than they look like they do:

- **`is_confirmed` decides whether markers, alerts and orders happen at all.** They apply only on a confirmed bar, unless the script declared `onUnconfirmed = true`. A condition that was true halfway through a bar and false at its close places no order.
- **`is_realtime` separates history from the bar in front of you**, and no engine can work it out. Alerts are raised only on a realtime bar, so a backtest that passes `False` throughout raises none, which is right for a backtest and wrong for a live runner that forgets to set it.
- **`supplied` left out makes every bar the last**, because the engine takes it as `index + 1`. That is what a realtime feed wants. A run over a dataset of known size passes the total.

### Before every execution: at_bar

`Serving.at_bar(facts, first)` states the facts a bar-reading library call needs and no call context carries. Call it before every execution:

| Fact | Value |
|---|---|
| `high`, `low`, `close` | This bar's prices |
| `previousClose` | The previous bar's close, `None` on the first bar |
| `volume` | This bar's volume, or absent |
| `isSessionFirst` | Whether this bar opens a trading session, by your own calendar. For NSE, the 09:15 IST bar |

The second argument is whether this is bar 0. **This is the one place a forgotten line produces a study that runs and is wrong.** A fact you do not state is absent, so a study built on it draws an empty line, `trueRange()` and everything built on it among them, and nothing says why.

### What comes back

| Field | Holds |
|---|---|
| `.index` | The bar this was |
| `.columns` | One value per channel, by channel index. `None` where nothing wrote the channel |
| `.applied_channels` | The deferred channels (markers and alert conditions) this execution committed. Empty on a bar that is not decided |
| `.applied` | The order calls a decided bar left behind, in the order the bar made them. Each has `.name`, `.arguments` and `.position` |
| `.alerts` | `Alert(key, title, message, bar, time)`, raised only on a decided realtime bar |
| `.diagnostic` | What stopped the bar, or `None`. Read `.ok` |

`.columns` always carries every channel, so a line redraws on every tick of a moving bar; `.applied_channels` says which of the marker and alert channels count. Draw the line, and commit the marker only when its channel is in `.applied_channels`.

**A bar that fails stops there.** Its later steps do not run, its result's `.columns` is empty, and `.diagnostic` has the code and position. The failure never escapes as an exception, so a process running many scripts gets a diagnostic about one of them and nothing else. The run is still loaded afterwards; whether a failed bar ends it is your decision.

## The bar that is still forming

A bar that has not closed is executed again every time its price changes, and the later execution must give what the first would have given had the bar arrived at that price once. The engine makes that true on one condition: **you pass the same index.**

On the first execution of bar `i` the engine records its state; on every later execution of the same index it restores that record first. Give each tick a new index instead and you have appended bars that never existed: every stateful call counts each tick as a bar, the history operator shifts, warmup ends early, and the study on the chart stops being the study a backtest computes. Nothing raises.

A `live var` in a script is the one exception, by design: it keeps its value across re-executions, and a script that uses one is not reproducible.

The same rollback is exposed for a re-execution the engine cannot see coming, such as a chart replay or a bar your feed corrected:

```python
mark = run.checkpoint()          # before executing bar k
...                              # bars k, k + 1, k + 2 executed
run.restore(mark)                # everything goes back
result = run.execute_bar(k, bar, state, supplied=total)
```

`restore` puts back every value cell, every library state region and every object under them in one copy, so two cells that held one array still hold one array. It leaves `live var` cells as they are now. A checkpoint is an in-process object for re-executing bars, not a saved run.

## Running a strategy

A strategy's orders are records, not calls. An order function in a script leaves a record and the engine does nothing with it; your host places the order, and learns what became of it from the frames your destination sends back. The strategy's position is folded from those frames by its own ledger. It is never read from the account, because an account position is shared with every other strategy and every manual trade on that contract.

The order of the moments between two bars is fixed:

1. Deliver every frame your destination sent since the last bar: `ledger.deliver(frame)`.
2. Fold them: `ledger.settle()`.
3. Execute the bar.
4. Place what the bar decided, from `result.applied`.

A driver that folded after the bar would let a script react inside the bar its own order was sent in, and one that folded during the bar would give two executions of a forming bar two different positions to read.

The strategy, compiled to `orb.json` the same way as above:

```openscript
version 1
strategy("Opening range breakout", overlay = true, qty = 75, product = "intraday")

var rangeHigh = none
if session.isFirstBar
    rangeHigh = high

breakout = not isNone(rangeHigh) and close > rangeHigh
if breakout and pos.isFlat
    buy()

plot(rangeHigh, "Range high", orange)
```

```python title="run_strategy.py"
"""Run a compiled OpenScript strategy bar by bar, sending its orders through your own code."""
import pathlib

from openscript.adapter.ordering import options_for
from openscript.adapter.serving import Serving
from openscript.contracts import Bar, BarState
from openscript.run import load_text
from openscript.strategy import IntentBar, Ledger, OrderFrame
from openscript.verify import capabilities

INSTRUMENT = {"symbol": "NIFTY25JANFUT", "exchange": "NFO", "timezone": "Asia/Kolkata",
              "tickSize": 0.05, "lotSize": 75, "hasVolume": True,
              "session": {"start": "09:15", "end": "15:30", "days": [1, 2, 3, 4, 5]}}
CLOSED = BarState(is_new=True, is_confirmed=True, is_realtime=False, updates=1.0)

ledger = Ledger()                 # the strategy's own record of what it sent and what filled
library = Serving(ledger)         # the library, with the ledger behind every pos call
text = pathlib.Path("orb.json").read_text(encoding="utf-8")
loaded = load_text(text, {}, library, capabilities=capabilities("orders"))
if not loaded.ok:
    raise SystemExit(f"{loaded.diagnostic.code} at {loaded.diagnostic.line}:{loaded.diagnostic.column}")
run = loaded.run

# Size the ledger from the declaration, read through the run so input references are resolved.
declared = {name: run.declaration(("meta", "strategy", name))
            for name in ("qty", "qtyType", "product", "pyramiding", "capital")}
ledger.options = options_for(declared, INSTRUMENT)

start = 1735703100000  # 1 January 2025, 09:15 IST
bars = [Bar(time=float(start + i * 300000), open=23500.0 + i, high=23510.0 + i * 3,
            low=23490.0 + i, close=23505.0 + i * 3, volume=1000.0) for i in range(8)]

waiting = []     # frames your destination sent since the last bar
previous = None
for index, bar in enumerate(bars):
    # 1 and 2. Deliver what arrived since the last bar, then fold it.
    for frame in waiting:
        ledger.deliver(frame)
    ledger.settle()
    waiting = []

    # 3. Execute the bar.
    library.at_bar({"high": bar.high, "low": bar.low, "close": bar.close,
                    "previousClose": previous, "volume": bar.volume,
                    "isSessionFirst": index == 0}, index == 0)
    result = run.execute_bar(index, bar, CLOSED, supplied=len(bars), instrument=INSTRUMENT)
    if not result.ok:
        raise SystemExit(f"bar {index}: {result.diagnostic.code}")

    # 4. Place what the bar decided.
    appended = len(ledger.rows())
    for effect in result.applied:
        placed = ledger.place(effect.name, effect.arguments,
                              IntentBar(index=index, time=bar.time), effect.position)
        if placed.refusal is not None:
            ledger.discard(appended)   # a refusal takes back everything this bar appended
            break
        for intent in placed.intents:
            print("send", intent.intent_id, intent.side, intent.qty, intent.qty_type, intent.instrument.symbol)
            # Stand-in for your order path: fill at once. A real destination answers later.
            waiting.append(OrderFrame(intent_id=intent.intent_id, status="filled",
                                      filled_qty=intent.qty, avg_fill_price=bar.close,
                                      order_ref=f"R{intent.intent_id}", time=bar.time))
    print(index, "position", ledger.size())
    previous = bar.close
```

The breakout fires on bar 2, the order is sent after that bar closes, the fill is folded before bar 3, and the position reads 75 from bar 3 on: one lot of the NIFTY future, stated in units.

**The ledger must be in the library.** `Serving(ledger)` is what serves the `pos` namespace and the order calls. Built without one, a strategy is refused at load naming the first position or order call it makes (OS6004), rather than running as a study that quietly trades nothing.

**Size the ledger from the declaration through the run.** A declaration may state its quantity or capital with an `input()`, so read each field with `run.declaration(...)`, which resolves the input, rather than off the raw program. `options_for` turns that and the instrument record into the ledger's options before bar 0.

**An intent carries its unit.** `intent.qty` is counted in `intent.qty_type`, as the script declared it, and is never multiplied by a lot size here: the lot is your venue's fact and your symbology is yours, so an engine converting it would send a quantity nobody asked for. Each intent also carries `.intent_id`, `.kind`, `.side`, `.order_type`, `.tag`, `.instrument`, `.product` and `.bar`.

**Frames are cumulative.** Every `OrderFrame` restates the whole life of one order: `intent_id`, `status`, the total `filled_qty` so far, the `avg_fill_price` of all of it, and optionally `order_ref`, `sent_instrument`, `sent_product`, `time` and `text`. A repeated frame, two frames that crossed in flight and a reconnecting session that resends its last frames are therefore harmless. A partial fill is a `working` frame with a non-zero `filled_qty`. The fold, the status words and what a host must report are on [Host interface](/script/integrate/host-interface#orders).

What a run made in money is not the ledger's job. The report is folded from the fills after the fact, so a stored run can be reported again with no engine present; the JavaScript [backtest](/script/integrate/backtesting-api) is where that report comes from today.

## What the engine leaves to you

- **Scheduling.** Nothing here calls `execute_bar`. When a run starts and stops, and what an exchange calendar says about today, are yours. The engine has no clock of its own.
- **Process isolation.** A run is an object in your process. The engine never lets a script's failure escape and stops a runaway loop, but a run occupies its worker until it returns. Run one strategy per process, and decide what happens when one dies.
- **Persistence.** A run holds its state in memory and writes nothing. A process that restarts loads the program again and executes the bars again from the beginning.
- **Data.** Bars exist because you supply them, one at a time and in order.
- **The destination.** An intent leaves through your code and a frame arrives through `ledger.deliver`, which is the only way in. The engine knows no broker and hands a symbol back exactly as it received it.
- **The compiler and the chart.** Compile with the JavaScript library; draw with the [chart adapter](/script/integrate/charts-adapter).

## The conformance adapter

The package is also a command line, the adapter the [conformance suite](/script/integrate/conformance) runs it through:

```bash
python -m openscript --describe
python -m openscript <case-directory>
python -m openscript --actual <case-directory>
```

```json
{"engineOnly":true,"languageVersions":[1],"name":"openscript","profile":"strategy","schemaVersion":"1.1","version":"0.5.0"}
```

`--describe` states what the engine claims. The other two run one case directory and read one JSON object on standard input, `{"program": "<the canonical program text>"}`, because the engine has no compiler to turn the case's `script.os` into a program itself: the first compares against the case's expected files, the second prints what the engine computed so two engines can be compared directly.

**Related.** [Two libraries](/script/integrate/overview), [JavaScript library](/script/integrate/javascript), [Compiled program](/script/integrate/compiled-program), [Host interface](/script/integrate/host-interface), [Backtesting API](/script/integrate/backtesting-api), [Your own engine](/script/integrate/conformance), [Sandbox and live](/script/strategies/sandbox-and-live)


## Compiled program

Source: https://openalgo.in/script/integrate/compiled-program

The compiler does not produce JavaScript or Python. It produces a **compiled program**: one plain data object holding a list of instructions and the tables an engine needs, in a documented, versioned format. Every engine, the JavaScript library, the Python engine and any engine you write, runs a script by walking that list one bar at a time. This page describes the format for anyone who stores programs, moves them between machines, reads them for a debugger, or implements an engine.

You do not need this page to use the libraries. You need it to understand what you are storing, why a program is safe to run from an untrusted author, and what an engine of your own must do.

## A script and its program

```openscript
version 1
study("Two bar mean", overlay = true)

len = input(2, "Length", min = 1)
mean = sma(close, len)
var hits = 0

if close > mean
    hits = hits + 1
    signal("UP")

plot(mean, "Mean", aqua)
```

The compiler turns that into this program, shown laid out for reading:

```json
{
  "openscript": { "format": "1.1", "language": 1 },
  "requires": ["core.1"],
  "compiler": { "name": "openscript", "version": "0.5.0" },
  "source": {
    "hash": "sha256:b17e0f0cb4173846316ce2c59f7ee22ce1f7b031eaca75e27596dd4456b2a493",
    "lines": 13,
    "file": "two-bar-mean.os"
  },
  "meta": {
    "kind": "study", "title": "Two bar mean", "short": "Two bar mean",
    "overlay": true, "precision": 4, "format": "price", "range": null,
    "scale": "right", "group": "", "onUnconfirmed": false
  },
  "limits": { "loops": 2000000, "history": null },
  "lib": {
    "manifest": 1,
    "functions": [{ "name": "sma", "arity": 2, "state": true, "effect": "none" }]
  },
  "inputs": [
    {
      "key": "len", "kind": "number", "label": "Length", "default": ["n", 2],
      "min": 1, "max": null, "step": null, "options": null,
      "group": "", "tooltip": null, "slot": 0
    }
  ],
  "channels": [
    { "id": 0, "type": "number", "defer": false, "once": true },
    { "id": 1, "type": "string", "defer": true, "once": false }
  ],
  "outputs": {
    "plots": [
      {
        "key": "p0", "title": "Mean", "type": "line", "channel": 0,
        "color": [0, 255, 255, 1], "colorChannel": null, "width": 1.5,
        "lineStyle": "solid", "offset": 0, "overlay": null, "scale": "right",
        "precision": null, "priceFormat": null, "ohlc": null
      }
    ],
    "fills": [],
    "levels": [],
    "markers": [
      { "key": "m0", "channel": 1, "position": "above", "shape": "label", "color": null, "textColor": null }
    ],
    "tables": [],
    "alerts": [],
    "barColor": null,
    "background": null
  },
  "consts": [["z", null], ["b", false], ["b", true], ["n", 0], ["n", 1], ["s", "UP"]],
  "series": [{ "id": 0, "kind": "bar", "field": "close", "name": "close" }],
  "frame": { "slots": 2 },
  "cells": [{ "id": 0, "kind": "var", "name": "hits" }],
  "states": [{ "id": 0, "fn": 0 }],
  "functions": [],
  "callSites": [],
  "loops": [],
  "code": [
    ["SLOAD", 0], ["LOAD", 0], ["CALL_LIB", 0, 2, 0], ["STORE", 1],
    ["CELL_INIT", 0, 7], ["CONST", 3], ["STOREC", 0],
    ["SLOAD", 0], ["LOAD", 1], ["GT"], ["JUMP_FALSE", 17],
    ["LOADC", 0], ["CONST", 4], ["ADD"], ["STOREC", 0],
    ["CONST", 5], ["EMIT", 1],
    ["LOAD", 1], ["EMIT", 0],
    ["HALT"]
  ],
  "requests": [],
  "debug": {
    "pos": [[0, 5, 12], [1, 5, 19], [2, 5, 8], [3, 5, 1], [4, 6, 1], [5, 6, 12], [6, 6, 1], [7, 8, 4], [8, 8, 12], [9, 8, 10], [10, 8, 1], [11, 9, 12], [12, 9, 19], [13, 9, 17], [14, 9, 5], [15, 10, 12], [16, 10, 5], [17, 12, 6], [18, 12, 1]],
    "fnPos": [],
    "names": { "slots": ["len", "mean"], "cells": ["hits"], "series": ["close"], "channels": ["Mean", "UP"] },
    "retain": false
  }
}
```

Read back against the source, the instruction list is the script line by line:

```text
 0  SLOAD 0            line 5   push close
 1  LOAD 0                      push len, which the engine wrote into slot 0 before the bar
 2  CALL_LIB 0, 2, 0            sma(close, len), 2 arguments, using state region 0
 3  STORE 1                     mean
 4  CELL_INIT 0, 7     line 6   var hits: first bar only, otherwise jump to 7
 5  CONST 3                     0
 6  STOREC 0                    hits = 0
 7  SLOAD 0            line 8   close
 8  LOAD 1                      mean
 9  GT                          close > mean
10  JUMP_FALSE 17               absent or false: skip the branch
11  LOADC 0            line 9   hits
12  CONST 4                     1
13  ADD
14  STOREC 0                    hits = hits + 1
15  CONST 5            line 10  "UP"
16  EMIT 1                      the marker channel
17  LOAD 1             line 12  mean
18  EMIT 0                      the plot channel
19  HALT
```

There is no instruction for `input()`: the engine writes the input's value into its slot before each bar. There is no instruction for `study()` or for the plot's declaration either: both are tables read once, before the first bar. And there is no warmup anywhere. On bar 0, `sma` has seen one value of the two it needs and returns absent, the comparison with an absent value is absent, and the branch is not taken. The line starts on the bar its value stops being absent.

## Why data and not code

A compiled program is a list of instructions an engine walks, and nothing in the system evaluates text: no `eval`, no function built from a string, no generated source loaded anywhere. That one decision is where the platform properties come from:

- **It runs under a strict content security policy.** A browser needs no `unsafe-eval`, and a security team has nothing to approve.
- **A script cannot reach anything.** It can only do what the instruction set exposes. There is no instruction for the network, the file system or the process around it, so a script shared by a stranger can draw a wrong line and nothing more.
- **The budgets are real.** The engine owns the loop, so the loop budget and the memory and time limits are counters inside it rather than hopes about a script's behaviour.
- **An engine needs no compiler.** Its core is a loop over forty-one instructions, not a second implementation of the language, so it can be written in any language a platform already uses. The standard library and the strategy runtime are the larger part of the work; [Your own engine](/script/integrate/conformance) sizes it.
- **Programs are cacheable and portable.** Compile once, store the result, run it in a browser today and on a server tomorrow.

## The top level

A program is one object with these fields. An empty table is written as an empty array, never left out.

| Field | Holds |
|---|---|
| `openscript` | The compiled format version and the language version |
| `requires` | The capability tags an engine must have |
| `compiler` | Who emitted it, for a bug report. An engine never reads it |
| `source` | The source hash, the line count and the file name |
| `meta` | The declaration: study or strategy, and every option |
| `limits` | The loop budget per bar and the retained history depth |
| `lib` | The library functions the program calls, in first-use order |
| `inputs` | One entry per `input()`, in source order |
| `channels` | The per-bar output channels |
| `outputs` | Plots, fills, levels, markers, tables, alerts, bar colour and background |
| `consts` | The constant pool |
| `series` | The series registers: every value whose history the program reads |
| `frame`, `cells`, `states` | Slot count, persistent `var` cells, and library state regions |
| `functions`, `callSites` | User function bodies, and one entry per distinct call path |
| `loops` | One entry per loop, so a spent budget can name the loop's line |
| `code` | The per-bar instruction list, ending in `HALT` |
| `requests` | Reads of another timeframe or instrument |
| `debug` | Source positions for every instruction, and the names of slots, cells, registers and channels |

### Two versions, two jobs

`openscript.format` versions the **format**: field names, the instruction set, the encoding. It is `"1.1"` in 0.5.0. `openscript.language` versions **meaning**: which front end parsed the source, and which behaviour of each library function an engine must apply. An engine selects library behaviour by the program's `language`, never by the newest it has, so a saved script never changes its numbers. The two move independently, because a new field and a corrected calculation have nothing to do with each other.

### Capability tags

`requires` lists what a program actually needs, and an engine checks it at load:

| Tag | Required when the program |
|---|---|
| `core.1` | Always: the instruction set |
| `arrays` | Uses an array literal, reads an array element by index, or calls an array function |
| `functions` | Declares a user function |
| `loops` | Contains a loop |
| `orders` | Is a strategy that places, modifies or cancels an order |
| `objects` | Creates a line, label, box or polyline |
| `tables` | Declares a table |
| `alerts` | Declares an alert |
| `req.timeframe` | Reads another timeframe of the chart's own instrument |
| `req.symbol` | Reads another instrument |

A compiler emits every tag a program needs and no other. So an engine that implements everything except orders runs every study and refuses exactly the strategies, with a message naming the missing tag, not a vague "too new".

### The declaration

`meta` is the `study()` or `strategy()` line, evaluated at compile time: `kind`, `title`, `short`, `overlay`, `precision`, `format`, `range`, `scale`, `group` and `onUnconfirmed`. A strategy adds a `strategy` object. For the costed EMA cross on [Backtesting API](/script/integrate/backtesting-api), whose declaration states its capital, fill rule, slippage and commission and takes its quantity from an input:

```json
{
  "capital": 500000, "currency": "", "qty": { "input": "Quantity" }, "qtyType": "units",
  "product": "intraday", "fillOn": "nextOpen", "slippage": 1,
  "commission": 20, "commissionType": "perTrade", "pyramiding": 1, "closeOnSessionEnd": false
}
```

**Every field is written with its effective value, defaults included.** An engine needs no table of defaults, and a default that changes in a later language version cannot change an old program, because the old program carries the old value in writing.

**An option written with an `input()` is carried as a reference**, `{ "input": "<key>" }`, as `qty` is above. An engine resolves every reference once at load, from the host's settings, before bar 0. That is how a tunable quantity, precision or plot colour reaches a declaration that is otherwise fixed before the first bar.

### Inputs

Each entry of `inputs` has a `key` (the settings key: the name the input was assigned to, or its title where no name received it), a `kind` (`number`, `bool`, `string`, `color`, `source`, `interval`, `time` or `select`), a `label`, a `default`, `min`, `max`, `step`, `options`, `group`, `tooltip`, and the `slot` the value is written into before every bar.

With no stored value, an input takes its `default`. A stored value is used when it passes validation, which is exact: the right type, inside `min` and `max`, and one of the `options` where there are any. A stored value that fails refuses the load with [OS6019](/script/errors/data#os6019) rather than falling back quietly to the default. An input value is never absent, because `input()` cannot declare `none` as its default.

### Channels and outputs

Everything a script shows for a bar leaves the machine through a **channel**: one value per bar, written by the one `EMIT` instruction. A plot's value, a per-bar plot colour, a level's price, a marker's text, an alert's condition and message, a bar colour and a pane background are all channels. A channel nothing wrote is absent, which the host sees as a gap in a plot, no marker, no alert, or a bar left its own colour.

Each channel says whether it is `defer`red, held back on a bar that is still forming (markers and alert conditions), and whether it is `once`: written exactly once on every path, which is how a plot column is guaranteed a value or an explicit absence on every bar.

`outputs` holds the declared shape the channels feed, fixed before bar 0 because a legend, a settings dialog and a pane must exist before the first bar runs. Tables and drawing objects are the exception: a table's cells are written by library calls against a handle, and a line or box is an object in the heap that a script creates once and changes over many bars, because both would otherwise need an unbounded number of channels.

### Values and constants

A value on the machine is one of six things:

| Tag | Holds |
|---|---|
| absent | Nothing: `none` in a script, `null` when it reaches a host |
| number | A finite binary64 number: the standard 64-bit floating point number most languages call a double |
| bool | `true` or `false` |
| string | A sequence of Unicode code points |
| color | Red, green and blue as whole numbers from 0 to 255, alpha from 0 to 1 |
| reference | An array, a table or a drawing object |

**A number is always finite.** An arithmetic result that is not finite becomes absent, checked after every single operation. Negative zero is turned into positive zero. Absence is a separate tag, never a sentinel number, so it cannot leak into arithmetic by accident.

The constant pool, `consts`, holds every literal as a `[tag, value]` pair: `["z", null]` for absent, `["b", true]`, `["n", 14]`, `["s", "BUY"]` and `["c", [255, 136, 0, 1]]` for a colour. Entries 0, 1 and 2 are always absent, `false` and `true`.

### Series, slots, cells and state

| Region | Holds | Lifetime |
|---|---|---|
| Series registers | The per-bar history of a value the program reads with `[n]`, and every built-in bar field it reads | Across bars |
| Frame slots | Every other name, input values and loop counters | One execution of a bar |
| Cells | One per `var` or `live var` | Across bars |
| State regions | The private state of each stateful library call site, such as an average's window | Across bars |

The compiler gives a name a register only when the program reads its history, which changes memory and never numbers. State is allocated per call site, so `sma(close, 20)` written twice keeps two independent windows, and a helper function called from two places keeps separate state for each.

### Reads of other data

A `req.timeframe()` or `req.symbol()` read compiles to two halves: an entry in `requests`, settled before bar 0, and a series register the engine fills with the read's value on each bar. Each entry holds `read` (`"timeframe"` or `"symbol"`), `symbol` and `exchange` (`null` for the chart's own), `timeframe`, `mode` (`confirmed`, `developing` or `lookahead`), `series` (the register), `warmup` (how many requested bars the expression needs) and `body`: the expression compiled to run over the requested bars. `req.timeframe("1D", sma(close, 5))` compiles to an entry with `read: "timeframe"`, `symbol: null`, `timeframe: "1D"`, `mode: "confirmed"` and `warmup: 4`. Because every read is known at load, a host can fetch them all in parallel before the first bar. [Host interface](/script/integrate/host-interface#bars-for-another-instrument-or-timeframe) covers the host's side.

## The instruction set

Forty-one instructions, deliberately small and dull: one way to do each thing, no shorthand for two others. Each is an array whose first element is the opcode's name, followed by its operands.

| Group | Instructions | Does |
|---|---|---|
| Constants and stack | `CONST`, `DUP`, `POP` | Push a constant; duplicate or discard the top value |
| Slots | `LOAD`, `STORE` | Read and write the current frame's slots |
| Cells | `CELL_INIT`, `LOADC`, `STOREC` | Initialise a `var` once; read and write it |
| Series | `SLOAD`, `SSTORE`, `HIST`, `HISTP` | Read and write a register; read its value `n` bars back |
| Arithmetic | `ADD`, `SUB`, `MUL`, `DIV`, `MOD`, `NEG` | Binary64 arithmetic; `ADD` also joins two strings |
| Comparison | `LT`, `LE`, `GT`, `GE`, `EQ`, `NE` | Ordering and equality |
| Logic | `NOT`, `AND`, `OR`, `AND_SHORT`, `OR_SHORT` | Three-valued logic, with the short circuit as its own instruction |
| Control | `JUMP`, `JUMP_FALSE`, `TICK`, `FOR_INIT`, `FOR_NEXT` | Branches and loops; `TICK` charges the loop budget |
| Arrays | `ARRAY`, `ELEM` | Build an array; read an element |
| Calls | `CALL_LIB`, `CALL_FN`, `RET` | Call a library function or a user function; return |
| Output | `EMIT` | Write a channel for this bar |
| Termination | `HALT` | End the bar |

The rules that give the language its behaviour live in a handful of these:

- **Absence propagates through arithmetic and ordering**, so `close > none` is absent, not `false`.
- **Equality is total.** `EQ` and `NE` always answer `true` or `false`, which is how a script can ask whether something is absent at all.
- **`and` and `or` are three-valued**: `false and absent` is `false`, `true or absent` is `true`, and the rest of the combinations with absence are absent.
- **A branch treats absence as false.** `JUMP_FALSE` is the one place absence is absorbed, because execution has to go somewhere.
- **A history read before the first bar is absent**, while an array index outside the array is an error, [OS4004](/script/errors/runtime#os4004): a history has no value there, an array has an extent the script chose.
- **Every loop body starts with `TICK`**, and every backward jump must land on one. That single rule means no loop can run without charging the budget, and when the budget is spent the engine raises [OS5001](/script/errors/limits#os5001) naming the loop's line.
- **An order call carries one extra argument**, the names of the arguments the script wrote. `buy()` means "the quantity I declared", while `buy(qty = x)` with `x` absent is a sizing calculation that has not warmed up, refused with [OS7002](/script/errors/orders#os7002). Without the names, the two would be the same call.

## Before the first bar: verification

An engine must verify a program in full before it runs a single bar, and refuse one that fails with OS6018, naming the instruction or field. Verification checks the structure and every index into every table; that every opcode exists with the right operand count; that every jump lands inside its own list; that every list ends in `HALT` or `RET`; that the stack depth agrees on every path, never goes below zero and is zero at `HALT`; that every backward jump lands on a `TICK`; that every `once` channel is written exactly once on every path; that the capabilities and library entries match the engine's; and that every input reference names a declared input.

A verified program cannot underflow its stack, jump out of bounds, address a slot that does not exist or loop without charging the budget. Every failure left is a script error with a source line, which is the only kind of failure a trader should ever see. `verify` in the JavaScript library runs the same checks on their own.

## One bar, eleven steps

| Step | What the engine does |
|---|---|
| 1. Restore | If this bar has run before, restore the state saved at the end of the previous bar |
| 2. Truncate | Cut every register's history back to this bar, discarding what a previous run of it wrote |
| 3. Clear | Empty the stack, the slots, the channels, the table cells, the pending effects and the loop counter |
| 4. Fill bar registers | Write the host's bar, the derived prices and the bar facts; write each read's value |
| 5. Fill inputs | Write each input's resolved value into its slot |
| 6. Execute | Run `code` from instruction 0 to `HALT` |
| 7. Close the registers | Append this bar's value to every register's history |
| 8. Publish the columns | Hand every channel's value to the host, `null` where absent |
| 9. Decide about effects | On a confirmed bar, or when the script set `onUnconfirmed`, apply the deferred channels and the pending order calls; otherwise discard them |
| 10. Trim history | Drop register entries older than the retained depth, when one is set |
| 11. Checkpoint | Save the state, if the engine is moving on to the next bar |

Steps 1 and 2 are what make a forming bar idempotent: executed ten times, it gives the answer it would have given once. Steps 8 and 9 are the line between drawing and acting. A line is redrawn on every update; a marker, an alert or an order waits for the bar to close, and an order function returns absent when it is called, because inventing an order id for something that may never exist would be a lie.

An error in step 6 stops the bar: steps 7 to 11 do not run, and the engine reports the code and source position rather than carrying a half-executed state into the next bar.

### Rollback and replay

The state saved at step 11 holds every cell, every library state region, the objects reachable from them with sharing preserved, the strategy's position and orders, and each register's history length. Restoring it before re-running a bar is the rollback rule, and `live var` cells are the single exception: they keep their current value, which is what `live var` means.

The same mechanism serves three purposes. It makes a forming bar idempotent, it lets a debugger step backwards, and it makes a replay exact: restoring the state from the end of bar `j` and running bars `j + 1` to `k` gives bar `k` exactly, bit for bit, what the original run gave it.

## Determinism

Two engines running the same program over the same bars must produce the same output to the last bit, on every machine. So an engine may not:

- reorder, reassociate or fuse floating point operations, use extended precision, flush subnormal numbers to zero, or change the rounding mode;
- compute a library function in any order other than the one the specification fixes for it, because a moving average is a sum and a sum has an order;
- use the platform's own maths library for `exp`, `log`, `pow` and the trigonometric functions once the portable reference algorithm exists. **None is written yet**, so those calls and the indicators built on them, `alma()`, `hv()` and `chop()` among them, carry no cross-engine guarantee in the last bit in 0.5.0;
- read randomness, the wall clock (except the host-supplied `chart.now()`), the locale or the environment's timezone;
- let a script observe hash table order or concurrency.

Number-to-text conversion is specified too: `text(x)` writes the shortest decimal that reads back to the same number, and `text(x, d)` rounds halves away from zero to exactly `d` decimals.

## The canonical text and the hashes

A program travels as its **canonical encoding**: UTF-8 with no byte order mark, no whitespace between tokens, object keys sorted by Unicode code point, every number in the shortest decimal form that reads back to the same binary64 value, and strings escaping only what they must.

```text
{"callSites":[],"cells":[{"id":0,"kind":"var","name":"hits"}],"channels":[{"defer":false,"id":0,"once":true,"type":"number"},...
```

Two hashes identify a result:

| Hash | Taken over | Identifies |
|---|---|---|
| Source hash | The script's UTF-8 text after normalisation: a byte order mark dropped and CRLF turned into LF | The text the trader wrote. It is in the program as `source.hash` |
| Program hash | The canonical encoding of the program | The exact program an engine ran |

```js title="hashes.mjs"
import { readFileSync } from "node:fs";
import { canonicalise, programHash, sourceHash, normaliseSource, loadText } from "openalgo-script";
import { compile } from "./compile.mjs";

const source = readFileSync("two-bar-mean.os", "utf8");
const { program } = compile("two-bar-mean.os", source);

const text = canonicalise(program);                                   // the bytes to store and send
const id = programHash(program);                                      // "sha256:" and 64 hex digits
console.log(sourceHash(normaliseSource(source)) === program.source.hash); // true
console.log(loadText(text).ok);                                       // true
console.log(loadText(JSON.stringify(program, null, 2)).ok);           // false: OS6018, not the canonical spelling
```

`sourceHash` hashes exactly the text it is given, so normalise the source first, or hash the `file.text` your compile produced; on a file saved with CRLF line endings the raw text hashes differently.

Record both hashes beside a chart, a backtest and a running strategy. An engine upgrade never changes a stored result, and the two hashes are how you prove it. Text read from outside the process must be exactly the canonical encoding; an engine refuses any other spelling, because the hash you recorded names those bytes and no others.

## Versions and compatibility

**A minor format version may only add.** It may add a field whose absence changes no number, a table reachable only from a new field, a capability tag, or metadata. It may not add or change an instruction, change the encoding, add a required field or change a default. Any new field whose absence would change a number must come with a capability tag, which is what lets an older engine safely ignore fields it does not know. Format 1.1 added `requests`, and a program with a read names `req.timeframe` or `req.symbol` in `requires`, so a 1.0 engine refuses it by tag rather than drawing an empty line.

**A major version may change anything**, and is a different format that shares a name. It still cannot change what an existing program computes: a program carries its language version, and that is bound for life.

An engine loading a program refuses at the first failure, in this order, and every refusal names what is missing:

| Step | Refused with |
|---|---|
| The text is not the canonical encoding | OS6018 |
| The format's major version is one the engine does not implement | OS6016 |
| A required capability tag is missing | OS6006 |
| The language version is one the engine has no library behaviour for | OS6017 |
| A library entry disagrees with the engine's manifest in name, arguments, state or effect | OS6004 |
| The program exceeds the engine's instruction, state region or call depth ceilings | OS5009, OS5004, OS5005 |
| The program fails verification | OS6018 |

A newer minor version loads in an older engine, and an older one loads in a newer engine with any table it lacks read as empty. **A program that ran yesterday runs today and produces the same numbers**, whatever engine, version or machine runs it.

## Errors an engine raises

| Code | Raised when |
|---|---|
| [OS3004](/script/errors/arguments#os3004) | A loop step is zero, or an argument value is out of range |
| [OS4001](/script/errors/runtime#os4001) | A history offset is negative or not a whole number |
| [OS4002](/script/errors/runtime#os4002) | A history offset reaches past the retained depth |
| [OS4004](/script/errors/runtime#os4004) | An array index is outside the array |
| [OS4013](/script/errors/runtime#os4013) | A `for` loop's start, limit or step is absent |
| [OS5001](/script/errors/limits#os5001) | The per-bar loop budget is spent |
| OS5003, OS5004, OS5005, OS5009 | The program asks for more than the host or engine allows |
| OS6004, OS6006, OS6016, OS6017, OS6018 | The load refusals above |
| [OS6019](/script/errors/data#os6019) | A host setting fails an input's validation |
| [OS7002](/script/errors/orders#os7002) | An order argument the script wrote is absent |

A load refusal names the instruction index or the field, because the failure is in the program. An error during a bar carries the source line and column from `debug.pos`, and a spent loop budget names the loop's own line rather than whatever instruction happened to be running.

## Becoming a chart

An engine computes columns; drawing them is a separate job. The program's tables map one to one onto a chart: `meta` onto the study's name, pane and scale, `inputs` onto the settings dialog, `outputs` onto series, bands, levels, markers, grids and alerts, the heap's drawing objects onto free drawings handed over whole after every bar, and `requests` onto the host's fetches. The [chart adapter](/script/integrate/charts-adapter) is that mapping for openalgo-charts. One rule is part of the format rather than any chart: only one study may colour the instrument's candles, the latest in the host's own study order that paints them.

**Related.** [Two libraries](/script/integrate/overview), [JavaScript library](/script/integrate/javascript), [Python engine](/script/integrate/python-engine), [Host interface](/script/integrate/host-interface), [Your own engine](/script/integrate/conformance), [Execution model](/script/language/execution-model), [Absent values](/script/language/absent-values)


## Host interface

Source: https://openalgo.in/script/integrate/host-interface

A **host** is whatever already owns the data and the account: a charting product, a trading terminal, a backtest service, a research notebook. The engine assumes none of them. It asks the host a small, fixed set of questions, and this page is that contract: the exact shape of each answer, when it is read, and what happens when you cannot give it. It applies whichever engine you run, the JavaScript library, the Python engine or one of your own.

On OpenAlgo there are two hosts. The /trading page hosts charts and backtests: it supplies the bars of the chart, the settings dialog, alert delivery and some of the instrument record. In this release the chart states the symbol, interval, tick size and timezone, and the Backtest panel states no interval, timezone or session; [chart.*](/script/reference/chart#where-the-facts-come-from) lists which fact each one states. A deployed strategy is hosted by OpenAlgo's server, which runs it with the Python engine and sends its orders either to sandbox trading (analyzer mode in OpenAlgo) or to your broker account, whichever the platform is set to.

## The six duties

| Duty | You supply | Read | Optional |
|---|---|---|---|
| 1. Bars | Open, high, low, close, volume and time, oldest first | On every execution of a bar | No |
| 2. Instrument facts | The instrument record | Once, at load | No |
| 3. Bars on request | Another instrument's bars, or another timeframe's | At load, answered before or between bars | Yes |
| 4. Bar state | Whether a bar is confirmed and whether a realtime feed is driving it | On every execution of a bar | No |
| 5. Orders | A destination for order intents, and frames reporting what became of them | At the end of a decided bar, and between bars | Yes |
| 6. Settings | A stored value per input key | Once, at load | Yes |

A drawing surface and the chart clock for `chart.now()` complete the list; the [chart adapter](/script/integrate/charts-adapter) covers drawing.

**Nothing reaches a running bar.** An execution reads the saved state, the bar and the settings, and nothing else. A frame that arrives, a request answered, a setting changed: each takes effect between bars or on a fresh load, never during one. That is what makes a replay exact and a backtest reproducible.

**An optional duty is declared at load, not discovered on bar four thousand.** A host with no order route gives its engine no `orders` capability, and a strategy is refused at load with [OS6006](/script/errors/data#os6006) naming it. A host with no provider for other instruments gives no `req.symbol` capability, and a script that reads one is refused the same way.

**When you cannot answer, there are three outcomes and never a fourth**: the absent value, which a script can test with `isNone()`; a catalogued error carrying your own words for the reason; or a refusal at load. A guess is never one of them. A tick size invented as `0.01`, a zero written for an unknown volume, a price carried forward from the previous bar: each produces a number that looks computed, and nothing downstream can tell that it is not.

## A complete host

This study compares a stock with the NIFTY index, so it exercises four duties at once: bars, instrument facts, a read of another instrument, and bar state with an alert.

```openscript
version 1
study("Strength against the index", overlay = false)

indexSymbol = input("NIFTY", "Index")
indexClose = req.symbol(indexSymbol, "5", close, exchange = "NSE_INDEX", mode = "developing")
ratio = close / indexClose
average = sma(ratio, 20)

plot(ratio, "Ratio", teal)
plot(average, "Ratio average", orange)

if crossUp(ratio, average)
    alert("Outperforming the index", id = "outperform", title = "Relative strength")
```

The host, in JavaScript, with `compile` from [JavaScript library](/script/integrate/javascript):

```js title="host.mjs"
import { readFileSync } from "node:fs";
import { load } from "openalgo-script";
import { compile } from "./compile.mjs";

// Stand-ins for your own data layer: 5 minute bars from 09:15 IST, times in UTC milliseconds.
const sessionOpen = Date.UTC(2025, 0, 6, 3, 45);
const makeBars = (base, drift) => Array.from({ length: 75 }, (_, i) => {
  const close = base * (1 + drift * i + 0.002 * Math.sin(i / 6));
  return { time: sessionOpen + i * 300_000, open: close, high: close * 1.0005, low: close * 0.9995, close, volume: 25_000 };
});
const stockBars = makeBars(820, -0.0002);
const indexBars = makeBars(23500, 0);

const host = {
  // Duty 2: the instrument record, read once at load.
  instrument: {
    symbol: "SBIN", exchange: "NSE", interval: "5", timezone: "Asia/Kolkata",
    tickSize: 0.05, lotSize: 1, currency: "INR", instrumentType: "equity",
    hasVolume: true, hasOpenInterest: false,
    session: { start: "09:15", end: "15:30", days: [1, 2, 3, 4, 5] },
  },
  // The chart clock, which chart.now() answers.
  now: Date.UTC(2025, 0, 6, 10, 0),
  // Duty 3: bars for another instrument, asked once per read, at load.
  requestBars(query) {
    if (query.read === "symbol" && query.instrument === "NIFTY" && query.exchange === "NSE_INDEX") {
      return { bars: indexBars };
    }
    if (query.read === "symbol") return { refused: { code: "OS6007" } };
    return undefined; // the chart's own instrument at another interval: let the engine fold it
  },
};

const { program, file } = compile("relative-strength.os", readFileSync("relative-strength.os", "utf8"));
// Duty 6: the settings stored for this instance, keyed by input key.
const loaded = load(program, { source: file, host, settings: { indexSymbol: "NIFTY" } });
if (!loaded.ok) throw new Error(`${loaded.diagnostic.code}: ${loaded.diagnostic.message}`);
const engine = loaded.engine;

// Duties 1 and 4: history first, every bar confirmed and not realtime.
engine.run(stockBars.slice(0, 74));

// Then the newest bar, live: forming, then closed.
const last = stockBars[74];
engine.append(last, { isConfirmed: false, isRealtime: true });
const closed = engine.update({ ...last, close: last.close * 1.01, high: last.close * 1.01 }, { isConfirmed: true, isRealtime: true });
console.log(closed.columns.slice(0, 2), closed.alerts);
// closed.alerts: [{ key: "outperform", title: "Relative strength", message: "Outperforming the index", bar: 74, time: ... }]
```

The alert fires once, on the live bar, when it closes. The 74 bars of history raised none, because an alert is raised only on a bar the host says a realtime feed is driving, and the forming execution raised none, because nothing is decided until the bar is confirmed.

## Bars

### The shape

| Field | Type | Means |
|---|---|---|
| `time` | number | The bar's **open** instant, whole milliseconds since the Unix epoch, UTC. 09:15 IST is 03:45 UTC |
| `open`, `high`, `low`, `close` | number | The bar's prices. A price you do not have is absent, never zero and never carried forward |
| `volume` | number | Only where you have it. Absent and zero are different facts |
| `oi` | number | Open interest, only where you have it. A level, not a flow: a coarser bar takes the last value, never the sum |

`time` is the open instant because the open is how a bar can be identified while it is still forming. A feed that stamps bars by their close converts once, in the host.

### The order they arrive in

- **Oldest first.** Position 0 is the oldest bar you supplied, and that position is `bar.index`.
- **`time` strictly increases.** Two bars with one timestamp are not two bars.
- **Spacing need not be uniform.** Sessions have gaps, and a host that pads a gap with invented bars is inventing trades.
- **The engine will not repair anything.** It does not adjust, round, resample, deduplicate or reorder what you hand it. Identical bars give identical numbers; different bars were never going to agree.

The engine works out `hl2`, `hlc3`, `ohlc4` and `hlcc4` itself, with a fixed order of operations, so never supply them. A midpoint computed by the host can differ from the engine's in the last bit.

### A missing volume is not a zero volume

**Zero is a reading:** you were watching and nobody traded. **Absent means nobody stated it:** anything computed from it is absent, a volume study draws a gap, and a script can test for it. Never write `0` for a volume you do not know, which draws a confident flat line across the part of the chart where you knew nothing; and never write an absent volume for a real zero, which breaks every running total across a quiet bar. Whether an instrument reports volume at all is a separate fact, `hasVolume`, below.

### The newest bar moves

You may hand the newest bar back with new values. That is an **update**, not a new bar, and the engine re-executes it from the state it saved at the end of the previous bar, so ten updates give the answer one would.

- An update never changes a bar's `time`. A new `time` is a new bar.
- An update may change `high`, `low`, `close` and `volume`. `open` may change only before the bar's first execution.
- A confirmed bar is never revised.
- A correction to a bar **older** than the newest is not an update. Start the run again from bar 0 with the corrected data.

### When you cannot answer

| Situation | What happens |
|---|---|
| No bars at all | [OS6010](/script/errors/data#os6010). An empty pane with no message would look like a study that drew nothing |
| A bar whose time does not follow the one before it | [OS6011](/script/errors/data#os6011), naming that bar. The run stops there; bars before it stand |
| A price or volume you do not have | The absent value |
| Fewer bars than the script's warmup needs | Not an error. The study is absent until it has enough bars, and draws from the first bar it can |
| A feed that is behind | Not an error. The engine runs over what it has, and later bars arrive as updates |

## Instrument facts

### The record

Twelve facts, read once at load and constant for the whole run:

| Fact | Type | Required | What a script sees when you leave it out | Read by |
|---|---|---|---|---|
| `symbol` | string | No | Absent in the JavaScript engine; the Python engine answers `""` | `chart.symbol` |
| `exchange` | string | No | Absent | `chart.exchange` |
| `interval` | string | No | Absent, and the two derived facts with it | `chart.interval` |
| `timezone` | string | **With a session** | Absent | `chart.timezone` and every calendar and session call |
| `tickSize` | number | No | Absent | `chart.tickSize`, `roundToTick()` |
| `lotSize` | number | No | Absent | `chart.lotSize`, `order.roundToLot()` |
| `pointValue` | number | No | Absent | `chart.pointValue` |
| `currency` | string | No | Absent | `chart.currency` |
| `instrumentType` | string | No | Absent | `chart.instrumentType` |
| `hasVolume` | bool | **Yes** | Absent, but a conforming host never leaves it out: it is the one fact every host must state | `chart.hasVolume` |
| `hasOpenInterest` | bool | No | Absent | `chart.hasOpenInterest` |
| `session` | object | No | Absent, and every per-bar session fact with it | The `session` namespace |

For a NIFTY future on NFO:

```json
{
  "symbol": "NIFTY25JANFUT",
  "exchange": "NFO",
  "interval": "5",
  "timezone": "Asia/Kolkata",
  "tickSize": 0.05,
  "lotSize": 75,
  "pointValue": 1,
  "currency": "INR",
  "instrumentType": "future",
  "hasVolume": true,
  "hasOpenInterest": true,
  "session": { "start": "09:15", "end": "15:30", "days": [1, 2, 3, 4, 5] }
}
```

The spellings:

- `interval` is a timeframe string; a bare number is minutes, so `"60"` and `"1h"` are both one hour and both give `chart.intervalMinutes` of 60. `chart.interval` hands the text back exactly as you wrote it.
- `timezone` is a zone name from the standard timezone database, such as `Asia/Kolkata`, never a fixed offset, which is silently wrong for half the year anywhere with a seasonal clock change.
- `instrumentType` is one of `"equity"`, `"future"`, `"option"`, `"index"`, `"currency"`, `"commodity"` or `"other"`.
- `tickSize` and `lotSize` are positive. Zero is not a tick size.

**Two facts are derived, and you must not supply them.** `chart.intervalMinutes` and `chart.isIntraday` are worked out from `interval`, so they can never disagree with it.

**Why exactly one fact is required.** Every other fact has an honest answer for "nobody said": absent, which a script can test. `hasVolume` does not, because an instrument that never reports volume and one whose figures are late produce the same empty column. A tick size is absent rather than a guessed `0.05` for the same reason: a script sizing a stop in ticks has to tell "the smallest increment is five paise" from "nobody said".

### The session

```json
"session": { "start": "09:15", "end": "15:30", "days": [1, 2, 3, 4, 5] }
```

`start` and `end` are wall clock times, `"HH:MM"`, read in the instrument's `timezone`. `days` numbers Monday as 1 through Sunday as 7. `"24:00"` is midnight at the end of the day, and an `end` earlier than its `start` crosses midnight, which an overnight session needs. MCX, for instance, trades from 09:00 into the late evening.

The session earns its place through the scheduled close: `session.isLastBar` is true on the last bar of the schedule even when trading stopped early, so a strategy that must be flat by 15:30 acts on it rather than on the appearance of a new bar, which arrives too late.

**A stated session is checked against itself at load**, and refused with [OS6012](/script/errors/data#os6012) naming what is missing when it has no `timezone`, a time not spelled `"HH:MM"` (`"9:15"` is the one a host writes first), or a `days` entry outside 1 to 7. A host that states no session at all is not refused: that is the honest record of a schedule it does not hold, and the per-bar session facts are then absent.

**A session study is only as good as the session.** `vwap()` restarts at the session's first bar, and `session.isFirstBar` and `session.isLastBar` are derived from the window. A host that holds a schedule and does not state it gets every one of them absent on every bar, with nothing on the chart to say why.

Two limits of version 1: **one window per instrument**, so an instrument with a break states the enclosing window and a script that must know about the break tests its own window with `session.isIn()`; and **no holiday calendar**, so a holiday is simply a day with no bars.

**State every fact you have.** Withholding the tick size gives a script an absent value it can test. Withholding the session or its timezone removes a whole family of per-bar facts, and the result on screen is an empty pane.

## Bars for another instrument or timeframe

A script may read an expression computed on another instrument with `req.symbol()`, or on another timeframe of the chart's own instrument with `req.timeframe()`. The engine folds the chart's own bars for a timeframe read, so a host need not serve one. A read of another instrument it never can, so that is this duty.

### The request

Every read in the program is known at load, so the engine asks about each one then, once, and never discovers a new one during a bar. That is what lets you fetch in parallel and cache by instrument and timeframe.

| Field | Means |
|---|---|
| `id` | The engine's handle for this read. Every answer and refusal is about it |
| `read` | `"symbol"` for another instrument, `"timeframe"` for the chart's own instrument at another interval |
| `instrument` | The identity to resolve: the one the script named, or the chart's own on a timeframe read. Opaque |
| `exchange` | Where it trades: the one the script named, or the chart's own when it named none |
| `timeframe` | A timeframe string such as `"5"`, `"60"` or `"1D"` |
| `mode` | `"confirmed"`, `"developing"` or `"lookahead"`: whether the newest requested bar may be one still forming |
| `warmup` | How many requested bars of history the expression needs before its first value, or `null` when no number is known |

Every identity is already resolved when it reaches you, so you resolve an identity and never apply a language default. One absence is deliberate: a `"symbol"` read whose identity was meant to come from a setting that holds nothing. The chart's own instrument is never substituted there; refuse it.

**The range is yours to work out.** A request carries no dates, because the engine has been handed no bars when it asks. Cover the chart's own range, extended backwards by `warmup` requested bars and forward to the end of the requested bar the newest chart bar falls in. `warmup` is a floor, not a promise: history that starts later than it asks for gives a read that is absent for longer, never a wrong number.

**One request per read**, not per instrument. Two reads of the same instrument at the same timeframe are two requests; answer both, from one fetch if you cache.

### The answer

| Answer | Means |
|---|---|
| `{ bars }` | Your own bars for that instrument at that timeframe, oldest first, in the shape of duty 1. Never padded, extended or synthesised |
| `{ pending: true }` | Still fetching. The read is absent and `req.isReady()` is false; when the bars arrive, load again and recalculate over the whole history |
| `{ refused }` | You cannot answer. Below |
| Nothing | You do not serve this read. On a timeframe read the engine folds the chart's own bars; on another instrument it is OS6007 |

An answer arriving after the run started is a fresh load and a full recalculation, never a splice into a run already past the bars it would have changed.

### Refusing

**A refusal is reported, never an empty answer**, because an empty series looks exactly like an instrument that did not trade.

| Code | You are saying |
|---|---|
| [OS6007](/script/errors/data#os6007) | You do not know that instrument on that exchange |
| [OS6008](/script/errors/data#os6008) | You resolved it and have nothing over the range the chart covers |
| [OS6009](/script/errors/data#os6009) | Your source refused or did not answer. Carry its own words in `reason` |
| [OS6014](/script/errors/data#os6014) | You do not serve that timeframe for that instrument. List the ones you do in `available` |
| [OS6015](/script/errors/data#os6015) | The intraday timeframe is not a whole multiple of the chart's, so it cannot be folded |
| [OS5006](/script/errors/limits#os5006) | The script makes more reads than you allow. Raised at load, from the ceiling you state |

The reason reaches the script through `req.error()`, and the study keeps drawing everything that does not depend on the failed read. Carry your source's words unchanged: "the data subscription does not cover this instrument" is actionable, "the request failed" is not. History that starts after the warmup is not OS6008: serve what you have.

### When the study goes away

A request belongs to the run that made it. A run ends when the study is removed, recompiled, reloaded after a settings change, or moved to another instrument or interval, and its outstanding requests are cancelled. Stop what work you can, and **never deliver an answer to a run that has ended**: a stale answer applied to the successor puts a line on the chart that no current script asked for. Cancellation is reported to nobody.

## Bar state

The eight bar facts split cleanly. **The host states the four that are about the delivery**, which no array of bars can reveal: `bar.isNew`, `bar.isConfirmed`, `bar.isRealtime` and `bar.updates`. **The engine derives the four that are about the dataset**: `bar.index`, `bar.count`, `bar.isFirst` and `bar.isLast`. A fact the engine can compute is never also stated by the host, because two sources for one number can disagree and no rule would say which wins.

In the JavaScript engine, `append` and `update` are `isNew` and the update count, so you pass only `isConfirmed` and `isRealtime`. The Python engine takes all four in its `BarState`.

| | `isNew` | `isConfirmed` | `isRealtime` | `updates` |
|---|---|---|---|---|
| A history load, every bar | true | true | false | 1 |
| A live bar as it forms | true, then false | false until its interval has elapsed, then true | true | 1, 2, 3 and on |

**Confirmation is one way.** A bar you confirmed is never revised and never handed back unconfirmed: markers, alerts and orders are applied on confirmation, and an order cannot be unplaced.

**A realtime feed carries ticks, not bars.** Turning ticks into bars is the host's job, and the engine is handed the result. Bar boundaries are a venue and session question you already answer when you draw a chart, and reading a feed from inside a bar would break replay. Two hosts that aggregate ticks differently hand the engine different bars and get different numbers; what the engine guarantees is that identical bars give identical numbers.

When you cannot tell whether the newest bar has closed, state it unconfirmed and confirm it when the next bar arrives: one bar late is the safe direction. With no realtime feed at all, state `isRealtime` false and confirm every bar. Replaying stored bars as if live, state what is true of the replay.

## Orders

This duty is for a host that lets scripts trade. The engine states what the strategy decided; your platform makes the order and reports back what happened. The engine never reads the account's position, because a position on a contract is shared with every other strategy and every manual trade on it. A strategy's position is folded from the orders it sent and the frames you report.

### What the engine hands over

An **order intent**, at the end of a confirmed bar, through your route. It is not an order: a condition that was true halfway through a bar and false at its close produces none.

| Field | Means |
|---|---|
| `intentId` | Unique within the run. Every frame about this order carries it back |
| `kind` | `"place"`, `"cancel"` or `"bracket"` |
| `instrument` | The resolved identity, `{ symbol, exchange }`. Never a symbol the engine assembled |
| `side` | `"buy"` or `"sell"`. Absent on a cancellation, and on a bracket, whose side is the position's own |
| `qty` | The quantity, in the unit `qtyType` names |
| `qtyType` | The script's own unit, passed through untranslated. A quantity the engine worked out from fills, as a flattening order's is, is in units |
| `type` | `"market"`, `"limit"`, `"stop"` or `"stopLimit"`, following the prices given |
| `limit`, `trigger` | The limit price and the stop trigger, where there are any |
| `target`, `stop` | A bracket's target and stop, as prices |
| `profit`, `loss` | A bracket's target and stop as distances from the entry, where the script stated them that way |
| `tag` | The script's own label, `""` when it named none. A cancellation names the tag it cancels |
| `product` | The script's `product` option, such as `"intraday"`, passed through untranslated |
| `positionRef` | The position this order belongs to. `0` on a cancellation, and on a bracket set while there is nothing to protect |
| `bar` | The index and open time of the bar whose close decided it |

Four things hosts get wrong:

- **Translate `product` yourself.** A product name is a venue's own word, and only you know the venue. Report what you actually sent in the frame, so both are on the record when they differ.
- **Never multiply a quantity by a lot size the engine did not state.** On NFO, one lot of a contract whose lot size is 75 is a quantity of 75 units; the engine passes the unit it was given, and converting is yours.
- **A bracket is an instruction, not an implementation.** Rest orders at the destination or watch the market yourself; the engine learns what happened only from frames. A trailing stop is never part of a bracket: when one is hit, you receive an ordinary exit order.
- **A distance stays a distance.** On the bar that places an entry and its bracket together nothing has filled yet, so a `profit` or `loss` is measured from the entry's fill, which you see first.

### What you report back

An **order frame**: a cumulative snapshot of one order as your destination describes it.

| Field | Means |
|---|---|
| `intentId` | Which intent this is about. A frame the engine does not recognise is ignored |
| `status` | One word from the vocabulary below |
| `filledQty` | **Cumulative** filled quantity since the order was sent |
| `avgFillPrice` | The average price of that whole quantity. Absent while nothing has filled |
| `orderRef` | Your destination's own reference. Recorded and shown, never parsed |
| `sentInstrument`, `sentProduct` | What you actually sent, after your own translation |
| `time` | Your destination's timestamp for this frame, UTC milliseconds |
| `text` | Your destination's own words for a rejection or cancellation, unparaphrased |
| `seq` | Your destination's sequence number for this order, where it has one |

**Frames are cumulative, not deltas.** Every frame restates the whole life of one order. A reconnecting session that resends its last frames, a repeated frame and two frames that cross in flight are then all harmless; under a delta scheme each of them is a phantom fill.

| Status | Means | Ends the order | You may send it |
|---|---|---|---|
| `placed` | Sent, and nothing has come back yet | No | No: it is the engine's own |
| `working` | Live at the destination, not completely filled | No | Yes |
| `triggerPending` | Accepted, waiting for its trigger price | No | Yes |
| `filled` | The whole quantity is filled | Yes | Yes |
| `cancelled` | Ended by a cancellation | Yes | Yes |
| `rejected` | Refused, with the destination's own text | Yes | Yes |
| `expired` | Ended without filling, by the destination's own rule | Yes | Yes |

**A partial fill is a quantity, not a status**: a `working` frame with a non-zero `filledQty`. Map your destination's own words onto this vocabulary, and **report a status you cannot map as the nearest word that does not end the order**, with the destination's words in `text`. Reporting an unknown state as terminal tells a strategy an order is dead and frees it to place another while the first may still be live. A fill that arrives after a cancellation was acknowledged is still folded.

Report at least every frame that changes an order's status or filled quantity. A host that reports only terminal frames conforms, but a script waiting for a working order to clear then waits blind.

### When a frame takes effect

Deliver frames whenever your destination speaks, one at a time, through the engine's intake: `engine.deliver(frame)` in JavaScript, `ledger.deliver(frame)` in Python. The engine folds them at the next bar boundary, before that bar runs, so every position fact is constant for the length of an execution and a forming bar sees the same position on every update. The intake takes one frame, returns nothing, and is the only way in.

```js title="orders.mjs"
import { readFileSync } from "node:fs";
import { load } from "openalgo-script";
import { compile } from "./compile.mjs";

const outbox = [];   // intents to send, filled by the route during a bar
const inbox = [];    // frames your destination sent back, delivered between bars

const host = {
  instrument: {
    symbol: "NIFTY25JANFUT", exchange: "NFO", interval: "5", timezone: "Asia/Kolkata",
    tickSize: 0.05, lotSize: 75, currency: "INR", instrumentType: "future", hasVolume: true,
    session: { start: "09:15", end: "15:30", days: [1, 2, 3, 4, 5] },
  },
  // Called at the end of a decided bar, once per order call, with the intents it became.
  route(effect, bar) {
    for (const intent of effect.intents) outbox.push(intent);
  },
};

const { program } = compile("orb.os", readFileSync("orb.os", "utf8"));
const loaded = load(program, { host });
if (!loaded.ok) throw new Error(`${loaded.diagnostic.code}: ${loaded.diagnostic.message}`);
const engine = loaded.engine;

// Stand-in for your order API: acknowledges, then fills at once.
function send(intent, price, time) {
  inbox.push({ intentId: intent.intentId, status: "working", filledQty: 0, avgFillPrice: null, orderRef: `R${intent.intentId}`, time });
  inbox.push({ intentId: intent.intentId, status: "filled", filledQty: intent.qty, avgFillPrice: price, orderRef: `R${intent.intentId}`, time });
}

for (const bar of bars) {
  // Between bars: deliver every frame that arrived. They are folded before the bar runs.
  for (const frame of inbox.splice(0)) engine.deliver(frame);

  const result = engine.append(bar);
  if (result.diagnostic) throw new Error(result.diagnostic.message);

  // After the bar: send what it decided.
  for (const intent of outbox.splice(0)) send(intent, bar.close, bar.time);
}
console.log(engine.orders()); // the strategy's own ledger: one row per order, with status and fills
```

`orb.os` is the opening range breakout from [Python engine](/script/integrate/python-engine#running-a-strategy), and `bars` are your own. Each bar's result also reports `frames`: what the frames delivered before it did to the ledger.

### When you cannot answer

| Situation | What happens |
|---|---|
| Your engine has no `orders` capability | [OS6006](/script/errors/data#os6006) at load, naming it |
| The connection dropped and no status is available | The order keeps its last recorded status. **Silence is not a fill and not a cancellation.** The engine does not guess, retry or place a replacement |
| A frame about an intent the engine does not know | Ignored |
| A fill the strategy never asked for | Ignored by the strategy's ledger. Report it as an account event |
| Your destination refused the order | A `rejected` frame with its own text. The ledger row records both |

## Settings

A map from each input's key to a value, **per instance**: the same script added twice to one chart is two instances with two maps, which is what lets one be a 14 period reading and the other a 50.

**The key is the input's name, never its position.** `len = input(14, "Length")` is keyed `len`. An input no name receives, such as one written inside a declaration, is keyed by its title. A positional key survives no edit at all: inserting an input above another would move a stored value onto the wrong row, and both being numbers, nothing would say so. Two inputs cannot share a key; the compiler refuses the file instead.

| Input kind | Stored as |
|---|---|
| `number` | A number |
| `bool` | `true` or `false` |
| `string`, `select` | A string. A `select` value must be one of the declared options |
| `color` | `#rrggbbaa`, eight lower case hexadecimal digits |
| `source` | One of `open`, `high`, `low`, `close`, `hl2`, `hlc3`, `ohlc4`, `volume` |
| `interval` | A timeframe string |
| `time` | A wall clock string in the chart's zone, so a saved layout restores to the same wall clock in another zone |

Settings are read once, at load. **Changing one is a new load**, and the run starts again from bar 0, because a declaration may take a value from an input and a declaration is fixed before the first bar. A stored value that fails its input's type, bounds or options is [OS6019](/script/errors/data#os6019) and the program does not run; it never falls back to the default, because a dialog that silently ignores what the user typed is worse than one that says the value is out of range. A stored key the program no longer declares is kept and ignored, so removing an input and putting it back keeps the user's value.

The style rows no script declares, a plot's colour, thickness, line style and visibility, are your own storage; nothing in the compiled program describes them. A host that cannot store settings conforms, and says so rather than showing a dialog that appears to save.

## Identity: a symbol is opaque

**The engine never parses an instrument identity.** It compares identities for equality and hands them back unchanged: it never splits one on a separator, changes its case, builds one from parts, or infers an underlying, an expiry, a strike or a right from one. A naming scheme built around one market's derivatives means nothing on another, and a parsing rule in the language would make every renamed contract a compiler release. Your symbology is yours, which makes you the only participant that can own it correctly.

A script names a contract by what it is, and you resolve the description to whatever your symbology calls it. **A relative contract is resolved once, at the start of a run**, and every later bar request, order, report line and restart uses that resolved identity. "The at-the-money call of the nearest NFO expiry" is a different contract at the exit than at the entry if the price moved or the expiry rolled, and an exit that re-resolved would open a second position in a contract nobody chose while leaving the first one open. So persist the resolved identity with the run, treat a new expiry or a new trading day as a new run, and refuse a run whose description you cannot resolve at its start. The script-side surface for describing relative contracts, `leg.relative()` among it, is planned in 0.5.0; the host's side of the rule is fixed now.

## A conforming host

A host that implements this interface can say each of these, and someone else can check it:

1. **Bars**: open, high, low, close, volume and time, oldest first, strictly increasing, unadjusted and unreordered.
2. **No substitutions**: never a zero for an unknown volume, never absence for a real zero, never a default tick size, never a price carried forward.
3. **Instrument facts**: `hasVolume` always; every other fact when you have it; a session with its timezone and correct spellings.
4. **Bar state**: the delivery facts on every execution, none of the derived ones, bars built from the feed by you, and confirmation never withdrawn.
5. **Optional duties declared at load**, with your ceilings, so a program needing more is refused at load: OS6006 for a capability, OS5003 for a `limits` value, OS5006 for requests.
6. **Requests** answered or refused with a catalogue code and your source's words, never with an empty answer; covering the range; cancelled when a run ends.
7. **Orders**: `intentId` on every frame, cumulative frames through the intake, the status vocabulary or a mapping onto it, never an unknown state reported as terminal, and a rejection's own text.
8. **Settings** stored per input key, or a plain statement that they are not.
9. **Identity** treated as opaque, and relative contracts resolved once.
10. **What you do not do, declared** rather than discovered.

And what a host may not assume: that the engine will repair the bars; that absence and zero are interchangeable; that a script produces a symbol; that settings can change mid-run; that an order happened when the script called the function; that the engine tracks the account; that the engine calls back into your code during a bar; or that the engine runs in any particular language, process or machine. The [conformance suite](/script/integrate/conformance) tests an engine through a host's interface: a host that can serve a case directory through its own interface, and reproduce the expected output, has shown its side of duties 1 to 4 and 6 matches this page.

**Related.** [Two libraries](/script/integrate/overview), [JavaScript library](/script/integrate/javascript), [Python engine](/script/integrate/python-engine), [Chart adapter](/script/integrate/charts-adapter), [Compiled program](/script/integrate/compiled-program), [Other instruments](/script/data/other-instruments), [Sessions and time](/script/data/sessions-and-time), [Realtime and confirmation](/script/language/realtime-and-confirmation)


## Your own engine

Source: https://openalgo.in/script/integrate/conformance

This page is for a platform that will not run somebody else's interpreter in its hot path, which is a reasonable position. Because a compiled program is data rather than code, there is a format to implement instead of a runtime to embed, and an engine for it can be written in whatever language your infrastructure already speaks. This page covers what you implement, what you do not, how the conformance suite proves your engine agrees with every other one, and what a passing result does and does not let you claim.

A conforming engine is one that passes the suite. There is no other definition: reading the specification carefully is not one.

## What you implement, and what you do not

| You implement | Where it is specified | How hard |
|---|---|---|
| The instruction set: the program's shape, the machine, forty-one instructions, the bar cycle, rollback, absence, determinism and versioning | The compiled program specification. [Compiled program](/script/integrate/compiled-program) is the tour | Mechanical once read |
| The standard library, each function in its specified accumulation order | The standard library specification, checked against the published vectors below | Careful work |
| The strategy runtime: the ledger, the fold of order frames, protective levels and the order they are evaluated in | The standard library specification's strategy sections, and [Host interface](/script/integrate/host-interface#orders) | The hard part |

**You do not implement the compiler.** You consume compiled programs and never parse OpenScript. The language can gain syntax without you changing anything, because the format is the contract and it has its own version, which moves far more slowly than the language. The [Python engine](/script/integrate/python-engine) is built exactly this way: it has no compiler and runs programs the JavaScript library emitted.

The specification lives in the project's repository, [github.com/marketcalls/openscript](https://github.com/marketcalls/openscript), beside the suite. It is written to be implementable from the documents alone. Where two competent implementers could reasonably choose differently, that is a defect in the documents, and the project wants to hear about it while there is still time to fix it.

**Roughly what it costs.** Weeks, not days, and most of it is the strategy runtime. Budget for the suite finding things: a first run that passes everything usually means the suite was not wired up correctly.

### The two mistakes that lose money

A wrong instruction draws a wrong line. A wrong strategy runtime loses money, silently and only in production. Two hazards catch almost everyone:

- **Frames are cumulative, not deltas.** A destination reports an order as a running total, and the same frame can arrive twice or out of order. Folding a repeat as a new fill double counts a position.
- **A fill can arrive after a terminal status.** A cancellation races a fill and the destination acknowledges the cancellation first. An engine that treats a terminal order as closed forever loses that fill, and the account holds a position the strategy does not know it has. Everything after that is confidently wrong: the ledger, the profit and loss, the protective levels, and the exit that will never be sent.

Each of these has more than one defensible answer, and only one of them is the answer every engine shares. Follow the specification exactly rather than reasoning from first principles.

### Arithmetic is part of the contract

Every operation is IEEE-754 binary64 with round-to-nearest-even, in the order the instructions give. Do not reassociate, fuse a multiply and an add, use extended precision, flush subnormals to zero or vectorise a sum into a different order. Where a formula can be written two ways, the standard library documents fix which one, because arithmetic that is mathematically equal is not numerically equal, and your users will find the difference before you do. A library function's result is defined by its specified accumulation order: an incremental rolling sum is not bit-identical to a fresh sum over the window, and is allowed only where the specification defines it.

## Check your library against the vectors first

Before a single case, you can check each library function on its own. The repository publishes a **vector file** for each arithmetic function, in its `spec/vectors/library` folder: a file of inputs and the exact outputs the reference engine produced for them, named by the function and its argument count, such as `sma-2.json`. An `index.json` beside them lists every file, and every function that has none and why: colours, strings, array operations, drawing calls, host and ledger reads and the calendar functions are checked other ways, and `pow()` is held out because its last bit comes from the platform's maths library.

Every number in a vector file is a binary64 bit pattern: sixteen lower case hexadecimal digits, big-endian. A case is a run of `bars` bars with one column per argument and one per output; a cell is `null` for absent, and each output column's `warmup` is the index of its first bar with a value. The `holes`, `short` and `absent-args` cases check what a function does with a gap, too little history and an argument that has no value yet.

```python title="vectors.py"
"""Check a moving average written in another codebase against the published vectors, bit for bit."""
import json
import pathlib
import struct

def decode(cell):
    """A cell is null (absent) or sixteen hex digits: the binary64 bits, big-endian."""
    return None if cell is None else struct.unpack(">d", bytes.fromhex(cell))[0]

def bits(value):
    return None if value is None else struct.pack(">d", value).hex()

def my_sma(window, length):
    """Your implementation. The specified order: oldest to newest, then divide."""
    if len(window) < length or any(v is None for v in window[-length:]):
        return None
    total = 0.0
    for v in window[-length:]:
        total += v
    return total / length

vectors = json.loads(pathlib.Path("sma-2.json").read_text(encoding="utf-8"))
for case in vectors["cases"]:
    if case["gaps"]:
        continue  # reaches an open gap in the specification: not held to it
    src = [decode(c) for c in case["args"][0]["values"]]
    lengths = [decode(c) for c in case["args"][1]["values"]]
    expected = case["outputs"][0]["values"]
    for i in range(case["bars"]):
        got = my_sma(src[: i + 1], int(lengths[i])) if lengths[i] is not None else None
        if bits(got) != expected[i]:
            print(f"{case['id']}: bar {i} expected {expected[i]}, got {bits(got)}")
            break
    else:
        print(f"{case['id']}: all {case['bars']} bars match")
```

Run against the published `sma-2.json`, it prints a line such as `full-0: all 80 bars match` for each of the seven cases. Compare bit patterns, never floats. A case whose `gaps` list is not empty reaches a part of the specification that fixes no answer yet, and you are not held to it.

## The conformance suite

The suite is a directory of cases. A case is a script, its input bars and the expected output, with a stated comparison rule so that "matches" means something exact. It tests two things and keeps them apart: **a compiler** (source in, diagnostics or a program out) and **an engine** (a program and bars in, output out). An engine with no compiler runs the engine half and says so.

It does not test speed, memory, the look of a chart or the wording of a message.

### A case on disk

One case is one directory, and every byte of its input is in it. A case never names a symbol for a runner to fetch, never reads a date range from anywhere, never opens a network connection and never reads the clock, which is why it reproduces on a laptop with no connection, on a build machine in another country, and in five years.

```text
cases/
  order/
    buy/
      case.json
      script.os
      bars.csv
      instrument.json
      backtest.json
      frames.csv
      expected.json
      notes.md
```

| File | Required | Holds |
|---|---|---|
| `case.json` | Yes | What the case is, what it asserts, and any tolerance |
| `script.os` | Yes | The source text, always under this name |
| `bars.csv` | For an engine case | The input bars, in full |
| `expected.csv` | For per-bar values | One column per asserted channel, one row per bar |
| `expected.json` | For everything else | Diagnostics, drawings, tables, orders, trades, the performance summary, log lines |
| `instrument.json` | No | The instrument record. Defaults below |
| `settings.json` | No | Values for the script's inputs. Absent means every default |
| `backtest.json` | For a strategy case | The money digits, a supplied charge schedule and the report window |
| `bars.<name>.csv` | No | A second bar series, for a read of another timeframe or instrument |
| `ticks.csv` | No | Updates inside the newest bar, for a case about the forming bar |
| `frames.csv` | No | Order frames delivered between bars, for a case about the ledger |
| `notes.md` | No | Why the case exists and what it defends against |

A runner reads no other file.

```json title="case.json"
{
  "id": "order/buy",
  "category": "strategy",
  "profile": "strategy",
  "languageVersion": 1,
  "description": "A strategy that enters long with buy on a crossing of two averages, sized from the distance to its stop, and flattens with close on the crossing back produces the recorded ledger, trades and summary.",
  "asserts": ["diagnostics", "orders", "trades", "performance"],
  "tolerance": { "abs": 0, "rel": 0, "reason": null }
}
```

| Field | Means |
|---|---|
| `id` | The directory path, repeated so a moved directory is caught |
| `category` | One of the categories below |
| `profile` | `core`, `chart` or `strategy` |
| `languageVersion` | The version the script compiles under, always pinned |
| `description` | One sentence, printed when the case fails |
| `asserts` | The channels it checks: any of `diagnostics`, `values`, `markers`, `fills`, `levels`, `barColors`, `background`, `table`, `drawings`, `alerts`, `orders`, `trades`, `performance`, `log` |
| `now` | The fixed value of `chart.now()`, required when the script calls it |
| `tolerance` | The comparison rule below. Absent means exact |

A case asserts only the channels it names, so a change to drawings cannot break a case about absence, and the case that does fail points at what changed.

### The input files

`bars.csv` has a header and one row per bar, oldest first: `time` is the open time in UTC milliseconds, strictly increasing; prices are written in the shortest decimal that reads back to the intended binary64 value; an absent price or volume is written `none`; and an extra column is an error, so a typo in a header cannot silently drop an input.

```text title="bars.csv"
time,open,high,low,close,volume
1748736000000,99.7,101.1,98.8,100,1000
1748739600000,101.08,102.48,100.18,101.38,1025
```

Without an `instrument.json`, a case runs under these deliberately boring defaults, so a case about something else is not accidentally about sessions:

```json
{
  "symbol": "TEST", "exchange": "TEST", "interval": "60", "timezone": "UTC",
  "tickSize": 0.01, "lotSize": 1, "hasVolume": true,
  "session": { "start": "00:00", "end": "24:00", "days": [1, 2, 3, 4, 5, 6, 7] }
}
```

`frames.csv` supplies order frames the way `bars.csv` supplies bars, so a case asserts the fold against input no engine chose. `afterBar` is the bar after whose execution the frame arrives, folded before the next one. `intent` is an ordinal, 1 for the first order the run placed, which the runner maps to your engine's own ids. `filledQty` is cumulative. This file illustrates the format; it is not one of the shipped cases:

```text title="frames.csv"
afterBar,intent,status,filledQty,avgFillPrice,orderRef,text,time
0,1,working,0,none,R1,,1735689600500
1,1,filled,25,101.5,R1,,1735693200750
1,1,filled,25,101.5,R1,,1735693200750
2,1,filled,40,101.75,R1,,none
```

Those four rows are a working frame, a fill, the same fill repeated, and a quantity that rose after the order had ended, which are exactly the two hazards above.

`backtest.json` holds what a strategy's report was folded under and the script never states: the money `digits`, the charge schedule the host supplied (or `null` for the script's own), and the report `range`. It is required of every strategy case, because a digit count nobody stated is a figure two engines round differently.

```json title="backtest.json"
{ "digits": 2, "costs": null, "range": { "from": null, "to": null } }
```

### The expected files

`expected.csv` holds per-bar values, one row per input bar. No shipped case asserts per-bar values yet, so this excerpt illustrates the format, with rows 2 to 18 left out:

```text title="expected.csv"
bar,ema20,signal
0,none,
1,none,
19,100.4375,
20,100.6390625,BUY
```

`bar` repeats the row index so a dropped row is caught where it was dropped. An absent value is `none`, never an empty field; an empty field means an event channel produced nothing on that bar. Numbers are the shortest decimal that reads back exactly, never rounded for readability, and a colour is `#rrggbbaa` in lower case.

`expected.json` holds ordered lists of flat objects. A diagnostic is compared on its `code`, `line`, `column` and `severity` only, never its wording, so the catalogue can keep improving its messages. An order is a ledger row compared on the fields the case names. `performance` is one flat object of summary figures, each defined by an exact formula over the trades, the bar closes and the run's capital, point value, currency, digits and window, including where the honest answer is not a number: `winRate`, `profitFactor`, `averageBarsHeld`, `maxDrawdownAt` and `maxRunUpAt` are `null` rather than zero when there is nothing to divide by or nothing happened.

### Categories and profiles

| Category | Needs a compiler | Asserts |
|---|---|---|
| `lexical`, `syntax`, `static` | Yes | Diagnostics from tokenising, parsing and checking |
| `warning` | Yes | A warning, and that compilation still succeeded |
| `rejection` | Yes | That something is refused, with a given code |
| `semantics` | No | Per-bar values: persistence, scope, control flow, absence |
| `numerics` | No | Per-bar values against an independently written reference |
| `surface` | No | Markers, fills, levels, bar colours, backgrounds, tables, drawing objects |
| `time` | No | Values derived from time, sessions and instrument facts |
| `external` | No | Reads of another timeframe or instrument, served from case files |
| `intrabar` | No | Output after the forming bar is replayed from `ticks.csv` |
| `strategy` | No | Orders, fills, position, trades and performance |
| `runtime` | No | A raised error and the bar it was raised on |
| `limits` | No | Behaviour at and past a declared limit |
| `program` | No | The compiled program itself, round tripped |
| `log` | No | The log stream |

| Profile | Covers | Lets you claim |
|---|---|---|
| `core` | The compiler categories, `semantics`, `numerics`, `runtime`, `limits`, `log`, `program` | Compiles and runs the language with correct numbers |
| `chart` | `core`, plus `surface`, `time` and `external` | Also produces everything a chart draws |
| `strategy` | `chart`, plus `strategy` | Also places orders and produces a backtest report |

Profiles are cumulative. An engine with no compiler reports itself `engineOnly` beside its profile and is not handed the compiler categories. A case outside your claimed profile is skipped, and a skipped case is never a pass.

**Some calls carry no cross-engine guarantee yet.** The transcendental functions, `exp`, `log`, `log10`, `log2`, `pow`, `hypot` and the trigonometric family, and the indicators built on them, `alma()`, `hv()` and `chop()`, have no portable reference algorithm written down. No case may assert a value that reaches one, and an engine is told plainly which calls those are.

## Running the suite

The suite and its runner are in the project's repository:

```bash
git clone https://github.com/marketcalls/openscript
cd openscript
npm install
npm run build

# Your engine against the expected files, writing the result document to a file
node scripts/run-suite.mjs --adapter path/to/your-adapter.mjs --out result.json

# Your engine against the reference engine, case by case, exactly
node scripts/run-suite.mjs --against path/to/your-adapter.mjs
```

`--cases <dir>` walks another suite root and `--timeout <ms>` bounds one invocation. The one-line summary goes to standard error, so standard output is the result document and nothing else. The exit code is the verdict.

### Your adapter

Your engine takes part through an **adapter**: a program the runner starts once per case, never once for the whole suite, so a crash or a hang costs one case rather than every result. It answers three invocations, each with one JSON object on standard output:

| Invocation | Writes |
|---|---|
| `adapter --describe` | Your engine's identity: `name`, `version`, `profile`, `languageVersions` and `schemaVersion` |
| `adapter <case-directory>` | One case result: its outcome and, on a failure, the first difference |
| `adapter --actual <case-directory>` | What your engine computed for the channels the case asserts, with no comparison, so the runner can compare two engines itself |

The runner starts every adapter with Node.js, so an engine in another language ships a small JavaScript file that starts the real engine and relays its output. The Python engine does exactly that, and compiles `script.os` with the reference compiler on the way, handing the engine the canonical program text on standard input:

```json
{"engineOnly":true,"languageVersions":[1],"name":"openscript","profile":"strategy","schemaVersion":"1.1","version":"0.5.0"}
```

### Comparing numbers

**A comparison is bit-exact unless the case declares otherwise.** Two correct engines computing the same expression over the same inputs, under the arithmetic rules above, have no licence to differ by one bit.

```text
compare(actual, expected, abs, rel):
    1. expected absent and actual absent          -> pass
    2. exactly one of them absent                 -> fail
    3. actual is not a finite number              -> fail (reported as nonFinite)
    4. normalise negative zero to zero on both sides
    5. identical binary64 bits                    -> pass
    6. abs == 0 and rel == 0                      -> fail
    7. |actual - expected| <= max(abs, rel * |expected|) -> pass
    8. otherwise                                  -> fail
```

Absence is compared first and never numerically: a value one bar early is a defect however small it is. A tolerance uses `max`, not a sum, so exactly one bound is in force at any magnitude and a failure can name which it broke. A case that needs slack declares it with a `reason`, which is required whenever a bound is not zero, and the suite caps any tolerance at a relative `1e-9` and an absolute `1e-12`. When two engines are compared against each other the tolerance is always zero, whatever the case says: a tolerance exists only to absorb an outside reference's different accumulation order.

Strings compare as exact sequences of code points, colours channel by channel as bytes, times as exact integers, and ordered lists by length first and then element by element.

### Outcomes and the result document

| Outcome | Means |
|---|---|
| `pass` | Every asserted channel matched |
| `fail` | A channel did not match. The first difference is reported: channel, column, bar, expected, actual and the bound it broke |
| `nonFinite` | The engine produced infinity or not-a-number, which is always a defect |
| `error` | The case could not be run: a crash, a hang, a timeout, or a malformed case |
| `unsupported` | The engine does not implement the feature, which it names |
| `skipped` | The case is outside the claimed profile. Never a pass |

A run with any `fail`, `nonFinite`, `error`, or `unsupported` inside the claimed profile does not pass. The result document records the suite revision, your engine's identity, the platform the runner ran on, one row per case and a summary. The rows below show the three shapes a row takes; the second and third are illustrations, since no shipped case asserts an indicator value or a drawing yet:

```json
{
  "suiteRevision": "0.5.0",
  "engine": { "name": "my-engine", "version": "1.0.0", "profile": "strategy" },
  "languageVersions": [1],
  "schemaVersion": "1.1",
  "platform": "(operating system, processor and runtime version)",
  "startedAt": 1735689600000,
  "cases": [
    { "id": "order/buy", "outcome": "pass", "durationMs": 41 },
    {
      "id": "ta/momentum/rsi", "outcome": "fail", "channel": "values", "column": "rsi14", "bar": 41,
      "expected": "68.21847374634196", "actual": "68.21847374634194", "bound": "exact", "difference": "1.4210854715202004e-14"
    },
    { "id": "draw/polyline", "outcome": "unsupported", "feature": "draw.polyline" }
  ],
  "summary": { "total": 3, "pass": 1, "fail": 1, "nonFinite": 0, "error": 0, "unsupported": 1, "skipped": 0 }
}
```

The failing row is the shape to expect: two values one unit apart in the last bit, which a tolerance would have hidden, and which is exactly the disagreement the suite exists to find. `bound` names what the failure broke: `exact` for a case with no tolerance, `abs` or `rel` for one that declares a bound, and `absence` when one side was absent.

## Two engines disagreeing is a release blocker

A backtest that disagrees with the chart is worthless, and so is the chart. So a disagreement between two engines stops a release: it becomes a defect report naming both engines and the first differing bar, somebody decides which engine is right **by reading the specification**, not by preferring the engine written first, and if the specification does not decide it, the specification is fixed first and the engine second. A case reproducing the disagreement is then added.

**A case is never edited to make an engine pass.** The legitimate responses to a failure are to fix the engine, to fix the specification and then the engine, or to show with a reviewed explanation that the case itself was wrong. Loosening a tolerance is not on the list. Cases are added over time and practically never removed, so a result names the suite revision it was run against.

## Harvesting a case from a run

A strategy case is harvested from a real run, not written by hand, so it asserts what an engine did over bars that existed rather than what somebody believed a run does. `caseFilesFrom` turns a [backtest](/script/integrate/backtesting-api) record into the files of a case, returning their text and writing nothing:

```js title="harvest.mjs"
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { backtest, settingsFor, caseFilesFrom } from "openalgo-script";
import { compile } from "./compile.mjs";
import { sampleBars } from "./bars.mjs";

// The costed EMA cross, its bars and its contract, from Backtesting API.
const { program, file } = compile("ema-cross-costed.os", readFileSync("ema-cross-costed.os", "utf8"));
const contract = { symbol: "SBIN", exchange: "NSE", currency: "INR", tickSize: 0.05, lotSize: 1, pointValue: 1, digits: 2 };

const result = backtest(program, sampleBars(), settingsFor(contract), {
  sourceText: file.text, // required: a case holds script.os
  instrument: { interval: "5", timezone: "Asia/Kolkata", hasVolume: true, // hasVolume is required too
                session: { start: "09:15", end: "15:30", days: [1, 2, 3, 4, 5] } },
});
const made = caseFilesFrom(result.record, {
  id: "strategy/ema-cross-costed",
  description: "An EMA cross on NSE 5 minute bars produces the recorded ledger, trades and summary.",
});
if (!made.ok) throw new Error(made.reason);

const dir = "cases/strategy/ema-cross-costed";
mkdirSync(dir, { recursive: true });
for (const [name, text] of Object.entries(made.files)) {
  writeFileSync(`${dir}/${name}`, text); // case.json, script.os, bars.csv, expected.json, ...
}
```

A record can become a case only when it carries the script's own text, checked against the program's source hash, and the instrument record including `hasVolume`. A record that cannot make a whole case makes none: a directory missing one file would fail on an engine that did nothing wrong.

## What a passing result means

**It means** that at suite revision R, your engine at version V ran every case in profile P and produced the recorded output for all of them, at the tolerances the cases declare, with no network and no clock, and that anyone can rerun the same revision against the same build and get the same report. That is a strong claim: your engine agrees with every other passing engine on everything the suite covers, to the bit.

**It does not mean** correctness on anything the suite does not cover; correctness in any financial sense, since engines that follow a specification together are wrong together; robustness against hostile input; performance; security, which depends on your isolation rather than your arithmetic; fitness for trading real money; an endorsement, since the project certifies nobody; or anything about another revision or another profile.

A conformance badge carries four things and is not valid without all four: the engine and its version, the suite revision, the profile, and a link to the published result document.

## Where the suite stands

Stated plainly, because a green run reads as wide as the reader imagines it:

- **What it reaches today:** the compiler's diagnostics from tokenising, parsing and checking; the runtime errors; behaviour at a declared limit; and strategies, through their ledger, trades and performance summary, including partial fills, rejections, cancellations, expiries and a fill after a terminal status.
- **What it does not reach yet:** no case asserts a per-bar indicator value, so the `semantics` and `numerics` categories are specified but not yet exercised. Two engines can agree on every case and still disagree on what a moving average is, which is why the library vectors above matter. No case yet supplies a host's own charge schedule, a repeated frame or two frames in the wrong order, more than one entry in a direction, or more than one instrument.
- **Who has run it:** the JavaScript and Python engines agree to the last bit on every case they both run, and the build stops on any disagreement. The Python engine has no compiler, so the compiler cases are skipped for it, and the cases the two share are the strategy cases plus those about loops, limits and stored settings. It also lacks the array functions and the log, and cases that would reach those are held back until it has them, so its agreement does not cover them. Both engines were written in the same repository, so their agreement is evidence about that repository rather than about the specification. **No engine written by anyone else has passed the suite yet.** If you are building one, the project would rather work with you than have you find the gaps alone.

**Related.** [Compiled program](/script/integrate/compiled-program), [Host interface](/script/integrate/host-interface), [Python engine](/script/integrate/python-engine), [Backtesting API](/script/integrate/backtesting-api), [Two libraries](/script/integrate/overview), [Testing](/script/writing/testing)


# Resources

## Glossary

Source: https://openalgo.in/script/resources/glossary

This page defines the terms the OpenScript documentation uses, in alphabetical order. OpenScript, also called OpenAlgo Script, borrows words from both programming and trading, and a few of them carry a narrower meaning here than in everyday use. When a word on another page stops you, look it up here, then follow the link at the end of the entry to the page that explains the rule behind it.

Definitions are one or two sentences. Two terms carry two meanings (a fill and a range), and both meanings are listed.

## The vocabulary in one script

Most of the language vocabulary appears in this short study. It draws the highest high of the 20 bars before the current one, marks the bar where the close breaks above that level, and then holds a stop at the low of the same 20 bars until the close falls back below the stop. It works on any instrument and any interval, for example a 5-minute chart of an NSE stock.

```openscript
version 1
study("Range breakout", overlay = true, precision = 2)

length = input(20, "Lookback", min = 2)

hi = highest(high, length)[1]
lo = lowest(low, length)[1]

var inTrade = false
var stop = none

if not inTrade and close > hi
    inTrade = true
    stop = lo
    signal("BUY", lime, at = "below", shape = "arrowUp")
    alert("Close broke above the range", id = "rangeBreak")
else if inTrade and close < stop
    inTrade = false
    stop = none

plot(hi, "Upper", aqua)
plot(inTrade ? stop : none, "Stop", red, style = "step")
```

Reading it with the glossary's words:

| In the script | Term |
|---|---|
| `version 1` | Version declaration |
| `study(...)` | Declaration |
| `input(20, "Lookback", min = 2)` | Input, which builds one row of the settings dialog |
| `highest(high, length)[1]` | A stateful call, then the history operator |
| `var inTrade = false` | Persistence |
| `var stop = none` | The absent value |
| The indented lines under `if` | A block |
| `signal(...)` | A marker |
| `alert(..., id = "rangeBreak")` | An alert, named by its id |
| `plot(...)` | A plotted column, named by its title |
| `inTrade ? stop : none` | A ternary whose absent arm draws a gap |

## A

**Absent value.** The value that means "there is no value here", written `none`. It belongs to every type, it propagates through arithmetic and ordered comparison, and a plot draws a gap where it appears rather than a zero. See [Absent values](/script/language/absent-values).

**Alert.** A condition a script raises with `alert()`, together with the message to send when it holds on a confirmed bar. See [Alerts from scripts](/script/alerts/overview).

**Analyzer mode.** See sandbox trading.

**Anchor.** The time and price a drawing object is attached to. Anchoring to a time rather than to a bar index keeps an object where you put it when more history loads and every index shifts. See [Lines and boxes](/script/visuals/lines-and-boxes).

**Argument.** A value passed at a call. Arguments may be positional, named, or positional followed by named, as in `plot(fast, "Fast", aqua, width = 2)`.

**Arm.** One `case` or `default` branch of a `switch`, or one of the two outcomes of a ternary. Arms of a `switch` do not fall through, and a name first assigned inside an arm does not exist outside it. See [Control flow](/script/language/control-flow).

**Array.** An ordered, resizable list whose elements all have one type, written `array<number>`, `array<string>` and so on. An array is a reference: assigning one name to another gives two names for one array, and `copy()` makes an independent one. See [Collections](/script/language/collections).

**At the money.** An option whose strike is the one nearest the current price of its underlying, such as the NIFTY strike closest to the index level. Strikes away from it are in the money or out of the money, depending on which side they lie and whether the option is a call or a put.

**Autofix.** A mark on some catalogue entries saying the fix is mechanical and needs no decision from you, such as a tab in the indentation, a semicolon, a missing comma or a missing version line. An editor built on the library can apply such a fix for you. The /trading editor shows the fix as text under the diagnostic and leaves the change to you. See [The editor](/script/getting-started/the-editor).

## B

**Backtest.** A run of a strategy over past bars that produces orders, trades, an equity curve and a report, under the costs the strategy declares. See [Backtesting](/script/strategies/backtesting).

**Bar.** One interval of price data: an open, a high, a low, a close, a volume and a time, plus open interest where the host supplies it. On a 5-minute NSE chart, the 09:15 to 15:30 IST session holds 75 bars. The script's body runs once per bar.

**Bar index.** The zero-based position of a bar in the data the chart holds, oldest first, read as `bar.index`. It is a position in the loaded data, not a permanent address, so it shifts when more history loads.

**Bare name.** A library name with no namespace prefix, such as `ema`, `highest`, `plot` or `aqua`. Everyday functions are bare and the long tail lives in namespaces.

**Black-76.** The option pricing model for an option on a futures or forward price. Indian index and stock options are priced with Black-76 off the synthetic future, not with a model built on the spot price.

**Block.** The lines indented under a header line such as `if`, `for` or `case`. Indentation is spaces only, every line of one block has exactly the same indentation, and there are no braces. See [Script structure](/script/language/script-structure).

**Book.** A planned feature: a group of legs managed as one position, with a combined stop, target, daily loss limit and square-off rules, through the `book.*` functions such as `book.stop()`. It is not available in version 0.5.0. See [Legs and books](/script/strategies/multi-leg-and-books).

**Bracket.** A target, a stop or both attached to an open position, set with `exit()` or `order.bracket()`. In version 0.5.0 a backtest does not fill either level, so test an exit rule in a backtest with an explicit `close()`. See [Exits and brackets](/script/strategies/exits-and-brackets).

**Broadcast.** The automatic treatment of a plain value as that same value on every bar, which is how `ema(close, 9)` accepts the literal `9` where a series would also be allowed. It is the only automatic widening in the language and it never changes a value.

**Budget.** A per-bar ceiling on work, most often the loop budget of 2,000,000 iterations per bar, which you raise deliberately with a `limits(loops = ...)` line. See [Limits](/script/writing/limits).

## C

**Catalogue.** The single list of every diagnostic OpenScript can raise, each with its code, message, cause, fix and a before and after example. The editor and these pages read the same catalogue. See [Reading an error](/script/errors/overview).

**Chart contract.** What a compiled study hands the chart: its plotted columns, shaded bands, levels, markers, tables, drawings, background, bar colours, alerts and settings inputs. Also called the descriptor.

**Check.** The stage that resolves names, types, scope and every call before any bar runs. Most mistakes are caught here rather than on a bar. In the /trading editor this happens every time you save.

**Code.** The stable identifier on every diagnostic, of the form `OS` followed by four digits, such as OS2002. A code is never reused and never renumbered. See [Reading an error](/script/errors/overview).

**Colour literal.** A named colour such as `aqua` or `orange`, or a hex colour such as `#ff8800`, or `#ff880080` with an alpha byte for transparency. See [Colors](/script/visuals/colors).

**Column.** One plotted series on the chart, created by one `plot()` call and named by its title.

**Commission.** The brokerage and charges a backtest deducts, declared on `strategy()` per trade, per unit or as a percentage of the traded value. See [Costs and fills](/script/strategies/costs-and-fills).

**Compiled program.** The plain data the compiler produces and an engine runs: a list of instructions and a few tables. Nothing turns text into executable code at any point. See [Compiled program](/script/integrate/compiled-program).

**Condition.** A `bool` or absent expression used by `if`, `while`, a ternary, a `switch` arm or an alert. An absent condition takes the false branch.

**Confirmed bar.** A bar whose interval has ended and which will not change again, read as `bar.isConfirmed`. Every historical bar is confirmed; the newest bar becomes confirmed when its interval ends. See [Realtime and confirmation](/script/language/realtime-and-confirmation).

**Conformance suite.** The public set of cases (a script, its input bars and the expected output) that every OpenScript engine must reproduce exactly. See [Your own engine](/script/integrate/conformance).

**Continuation line.** A line that carries on the statement above it, because a bracket is still open, because the previous line ended in an operator or a comma, or because it ended in a backslash. It must be indented more deeply than the line the statement began on.

**Crossover.** The bar on which one series moves from at or below another to above it, which `crossUp()` detects. `crossDown()` detects the opposite move and `cross()` either one. All three need the previous bar, so they first have a value on bar 1.

## D

**Declaration.** The `study(...)` or `strategy(...)` statement every file carries exactly one of, as its first statement after the version line. It sets the title, the pane, the precision and, for a strategy, the capital, the order size and the costs. See [Declarations](/script/reference/declarations).

**Deferred call.** A `signal()`, `alert()` or order call made on a bar that is still forming. It waits for the bar to close, and if the condition that produced it is no longer true by then, it never happens.

**Deployment.** One strategy running on the OpenAlgo server for one instrument and interval, with its own settings, position and log, created with Deploy a strategy in the Strategies panel of /trading. One script can have several deployments at once. A deployment is a server process, so closing the browser does not stop it. See [Sandbox and live](/script/strategies/sandbox-and-live).

**Descriptor.** See chart contract.

**Determinism.** The rule that the same compiled program over the same bars produces the same output on every engine, every time. There is no randomness, and a script reads the clock only through `chart.now()`.

**Diagnostic.** One error or warning: a code, a line, a column, the span of text it is about, a message and a fix.

**Drawdown.** A fall in a strategy's equity from its highest point to a later low. The maximum drawdown in a backtest report is the deepest such fall over the run. See [Reading a report](/script/strategies/reading-a-report).

**Drawing object.** A line, label, box or polyline created through the `draw` namespace, such as `draw.line()`. It stays on the chart until the script deletes it, and the script can move and restyle it over time.

## E

**Element access.** Reading one element of an array with `arr[i]` or `element()`. The compiler tells it apart from the history operator by the type of the value on the left.

**Engine.** A program that runs a compiled program bar by bar. The JavaScript library and the Python engine are two engines, and they must agree to the last decimal. See [Two libraries](/script/integrate/overview).

**Equity.** Starting capital plus realised and unrealised profit. Reading it from inside a script, as `pos.equity`, is planned and not available in version 0.5.0.

**Equity curve.** The account's equity plotted bar by bar over a backtest. Drawdown and run-up are both measured on it. See [Reading a report](/script/strategies/reading-a-report).

**Error.** A diagnostic in the ranges OS1xxx to OS7xxx. An error found before the first bar stops compilation. An error raised while a bar runs stops the run at that bar: earlier bars keep what they drew, and nothing is drawn from that bar on.

**Exchange.** The venue an instrument trades on, read as `chart.exchange` in the host's own naming where the host states it (the /trading chart does not in this release). OpenAlgo uses NSE and BSE for equities, NFO for index and stock futures and options, MCX for commodities and NSE_INDEX for the NSE indices.

**Expectancy.** The average net result of one closed trade in a backtest: net profit divided by the number of closed trades. A positive expectancy means the strategy made money per trade on average, after charges.

**Expiry.** The day a futures or options contract ends. Reading the chart instrument's expiry, as `chart.expiry`, is planned and not available in version 0.5.0.

## F

**Fill (a band).** The shaded region between two plotted columns, created with `fill()` at the top level. See [Fills](/script/visuals/fills).

**Fill (an order).** The execution of an order at a price. Where a backtest fills a market order is set by the declaration's `fillOn` option: `"nextOpen"`, the default, or `"close"`. See [Costs and fills](/script/strategies/costs-and-fills).

**File scope.** The scope holding every name assigned at the top level of a file and every `fn` declared in it.

**Fixed shape.** The set of plots, fills, levels, tables and inputs, which the chart must know before the first bar so it can build the legend, the axis and the settings dialog. This is why those calls must be at the top level.

**Frequency.** The `alert()` argument that decides how often one alert may fire: `"oncePerBar"` (the default), `"once"` or `"everyUpdate"`.

**Front end.** The part of the compiler that reads one language version. Every past front end is kept, and the file's version line chooses which one reads it.

**Function.** A named calculation declared with `fn` at the top level. Functions cannot be nested, cannot call themselves, and are not values in version 1. See [User functions](/script/language/functions).

## G

**Gap.** What an absent value draws: a plot breaks its line, a fill stops, a level is not drawn, a bar keeps its own colour, a table cell is blank.

**Global scope.** The outermost scope, holding the standard library and the built-in series such as `close`. Assigning to one of those names is an error.

**Group.** A heading that gathers rows in the settings dialog, set with the `group` argument of `input()`. The declaration also takes a `group` option, a category a host may list the study under.

## H

**Handle.** The value a `plot()` call returns, so that `fill()` can name the two plots it shades. A handle exists only at compile time: it cannot be stored in a `var`, put in an array or passed to a function.

**Higher timeframe read.** A value computed on a coarser interval and sampled onto the chart's bars, written `req.timeframe(timeframe, expr)` with `req.timeframe()`. See [Higher timeframes](/script/data/higher-timeframes).

**History operator.** `x[n]`, the value of a series `n` bars back. Reading past the start of the data gives the absent value, never a clamped value or a zero. See [Bars and history](/script/language/bars-and-history).

```openscript
prev = close[1]       // the previous bar's close, absent on the first bar
move = close - prev   // absent on the first bar too
plot(move, "Change from the previous close")
```

**Host.** The application an engine runs inside. It supplies the bars, the instrument facts, the settings dialog, alert delivery and the destination for orders. In OpenAlgo the host is the /trading page for studies and backtests, and the strategy runner on the OpenAlgo server for a deployed strategy. See [Host interface](/script/integrate/host-interface).

## I

**id.** The stable name of an alert, given with `id = "..."` on `alert()`. The host keeps track of an alert by its id, so it has to survive edits to the script. An alert without one is named after the line it is on, which changes when you insert a line above it, and the compiler warns with OS8008.

**Idempotent in the bar.** The property that running the forming bar twice gives the same answer as running it once, which the rollback rule provides.

**Identifier.** A name in the source: an ASCII letter or underscore followed by ASCII letters, digits and underscores. Identifiers are case sensitive, so `fast` and `Fast` are two names.

**Input.** A setting declared with `input()` at the top level, which builds one row of the settings dialog. The default value comes first because its type decides the kind of field. See [Inputs](/script/inputs/inputs).

**Instrument fact.** Something the host knows about the instrument, read through the `chart` namespace: tick size, lot size, point value, exchange, instrument type. A fact the host has not supplied is absent rather than guessed.

**Interval.** The chart's bar length, read as `chart.interval` as a timeframe string such as `"1D"`, and as `chart.intervalMinutes` in minutes, which is absent on a daily, weekly or monthly chart. See [Timeframes](/script/data/timeframes).

**IST.** India Standard Time, five and a half hours ahead of UTC. NSE and BSE equity and derivative sessions run from 09:15 to 15:30 IST. A bar's `time` is stored in UTC, and the calendar and session calls read it in the chart's timezone.

## L

**Leg.** A planned feature: one instrument a multi-leg strategy trades, declared with `leg.fixed()` or `leg.relative()`, for positions such as a straddle on NFO index options. In version 0.5.0 a strategy trades one instrument, the chart's. See [Legs and books](/script/strategies/multi-leg-and-books).

**Level.** A fixed horizontal reference line in the study's pane, created with `level()` at the top level, such as 70 and 30 on an RSI. See [Levels](/script/visuals/levels).

**Limits.** The optional `limits()` statement immediately after the declaration. It raises the per-bar loop budget (`loops`) or the retained history depth (`history`), and its arguments must be literal numbers. See [Limits](/script/writing/limits).

**Live mode.** See sandbox trading.

**Live var.** A `var` that does not roll back when the forming bar runs again, so it counts every update within the bar. It makes a chart and a backtest of the same data disagree by design, and raises warning OS8011. See [Realtime and confirmation](/script/language/realtime-and-confirmation).

**Lookahead.** The higher timeframe read mode that uses a coarser bar's final value from inside that bar. It repaints history by design, must be written out, and raises warning OS8005. See [Repainting](/script/data/repainting).

**Lookback.** How many bars a calculation reads, such as the 20 in `sma(close, 20)`. The reference calls it the length, `len`, and it must be a whole number.

**Lot.** The number of units that trade together, read as `chart.lotSize` where the host states it: the /trading Backtest panel does, and the /trading chart does not in this release. NFO futures and options and MCX contracts trade in whole lots, and a strategy declared with `qtyType = "lots"` counts its orders in lots, though the Strategies panel runs only a strategy that counts in units. See [Position and sizing](/script/strategies/position-and-sizing).

## M

**Marker.** A symbol drawn on one bar by `signal()`, with its text, a shape, a colour and a position above, below or at the price. See [Labels and shapes](/script/visuals/labels-and-shapes).

**Message.** The string an alert carries, worked out on the bar the alert fired. Absence propagates through joining strings with `+`, so a message joined from an absent value is itself absent. `text()` of an absent value is the word `none`.

**Mode.** The argument on a higher timeframe read that decides what the read may know: `"confirmed"` (the default), `"developing"` or `"lookahead"`. Only `"confirmed"` never repaints.

**Moving bar.** The newest bar of a chart receiving the latest market data, which runs again on every update until its interval ends. Also called the forming bar or the unconfirmed bar.

## N

**Namespace.** A prefix holding part of the library's long tail: `bar`, `chart`, `session`, `date`, `str`, `math`, `pos`, `order`, `draw` and `req`, with `leg` and `book` planned.

**Net position.** The one position a strategy holds in the chart's instrument, positive when long and negative when short, read as `pos.size`.

**none.** See absent value.

## O

**OCO.** One cancels the other: two orders where the fill of one cancels the other, typically a target and a stop. The general form, `order.oco()`, is planned.

**Offset.** The `plot()` argument that shifts where a column is drawn, never what it contains. A positive offset draws the last values past the newest bar.

**onUnconfirmed.** The declaration option that allows signals, alerts and orders on a bar that is still forming. It is off by default, and turning it on makes the script responsible for its own `bar.isConfirmed` checks.

**Open interest.** The number of futures or options contracts outstanding at the end of the bar, read as `oi`. It is absent where the host supplies none, which `chart.hasOpenInterest` tells you.

**Overlay.** The declaration option `overlay = true`, which draws the study over the price rather than in a pane of its own.

**Overload.** More than one form of one function, told apart by the number or type of arguments, as in `sum(values)` for an array and `sum(close, 20)` for a rolling total.

## P

**Pane.** A drawing area on the chart. A study is drawn over the price pane or given its own pane below it.

**Per-bar execution model.** The rule that the whole file is the body of a loop that runs once per bar, top to bottom, oldest bar first, with no main function and no event handler. See [Execution model](/script/language/execution-model).

**Persistence.** A value that carries from one bar to the next, declared with `var`. It is a different idea from history, which reads the past. See [Persistence](/script/language/persistence).

```openscript
var barsSeen = 0          // set to 0 once, on the first bar
barsSeen = barsSeen + 1   // 1, 2, 3, ... because the value carries forward
plot(barsSeen, "Bars seen", aqua)
```

**Placeholder.** A named slot in an error message, such as the name of your variable, which the compiler fills in from your script.

**Planned.** A name the language defines that is not available in this release. The reference marks it with a Planned badge, and using it is error OS2020. See [Release notes](/script/resources/release-notes) for what is planned.

**Plot.** One column of one value per bar, declared at the top level with `plot()` and drawn according to its style, width, colour and offset. Every plot needs a title. See [Plots](/script/visuals/plots).

**Point value.** The money one point of price movement is worth for one unit of the instrument, read as `chart.pointValue`. It is absent when the host has not supplied it.

**Precision.** The number of decimals on a study's axis and legend, set on the declaration.

**Price source.** One of the per-bar prices a study can read: `open`, `high`, `low`, `close`, or a blend the engine works out for you: `hl2` is `(high + low) / 2`, `hlc3` is `(high + low + close) / 3`, `ohlc4` averages all four prices and `hlcc4` counts the close twice.

**Predicate.** The chain of conditions that leads to an `alert()` call. The compiler turns it into the watched condition the host evaluates.

**Product.** The strategy option choosing `"intraday"` or `"overnight"` treatment of a position, the same distinction an Indian trading account makes between an intraday position and one carried forward.

**Profit factor.** Gross profit divided by gross loss over the closed trades of a backtest. Above 1 the winners earned more than the losers gave back. It is empty when no trade lost money.

**Propagation.** The rule that an absent operand makes the result absent. It holds for arithmetic, joining strings and ordered comparison, and not for equality.

```openscript
prev = close[1]           // absent on the first bar
plus = prev + 1           // absent wherever prev is absent
isUp = close > prev       // absent there too, not false
noPrev = prev == none     // always true or false, never absent
plot(plus, "Previous close plus one")
barColor(isUp ? lime : none)
background(noPrev ? fade(gray, 90) : none)
```

**Pyramiding.** The strategy option that caps how many entries may add up in one direction. It defaults to 1, and an entry past the cap is refused with OS7008.

## R

**Range (of codes).** One block of a thousand error codes, OS1xxx to OS8xxx, saying what kind of thing went wrong, never how serious it is.

**Range (of a pane).** The declaration option `range = [min, max]`, which fixes a study pane's scale, as in `[0, 100]` for an oscillator.

**Repaint.** To redraw history differently after the fact, so that what the chart shows today is not what it showed at the time. It comes from a `"developing"` or `"lookahead"` read, or from letting a bar that is still forming leave a mark: `onUnconfirmed = true` acts on it, and a `live var` counts its updates. See [Repainting](/script/data/repainting).

**Report.** The summary a backtest produces: net profit, drawdown, win rate, the trade list, the equity curve and the rest. See [Reading a report](/script/strategies/reading-a-report).

**Reserved word.** A word the language keeps for itself and will not accept as a name, including several reserved now for features planned in later versions. See [Keywords](/script/reference/keywords).

**Rollback.** The rule that before the forming bar runs again, every persistent value is restored to what it held at the end of the previous bar. It is what makes a chart and a backtest over the same data agree.

**Run-up.** A climb in a strategy's equity from a low point to a later high. The maximum run-up in a backtest report is the largest such climb, shown beside the maximum drawdown.

**Runtime.** The stage that runs a bar. A runtime error, such as a fractional length reaching `sma()` (OS4003), stops the run at the bar that raised it.

## S

**Sandbox trading.** Running a strategy on the latest market data against a simulated account rather than a real one, so nothing touches real money. In OpenAlgo this is analyzer mode. Where a deployed strategy's orders go follows the mode OpenAlgo is in, which is set elsewhere on the site and shown on the Strategies panel: in analyzer mode they go to the sandbox, in live mode to your trading account. Nothing in a script can choose. See [Sandbox and live](/script/strategies/sandbox-and-live).

**Scope.** Where a name can be seen: global, file, or one block. A name is declared by its first assignment in a scope, and assigning to a name that already exists in an enclosing scope updates it. See [Variables and scope](/script/language/variables-and-scope).

**Series.** The per-bar history of a value, written `series number`, `series bool` and so on. Reading it bare gives this bar's value and `[n]` gives the value `n` bars back. See [Types and values](/script/language/types-and-values).

**Session.** The instrument's trading hours as the host defines them, such as 09:15 to 15:30 IST for NSE equities and derivatives. The `session` namespace reports the first bar, the last bar and whether a bar falls in a window you state. The first and last bar need the host to supply the hours, and are absent where it has not. See [Sessions and time](/script/data/sessions-and-time).

**Settings dialog.** The panel of one row per `input()`, plus the style rows the host adds for every plot. It is built once, before the first bar, which is why inputs must be at the top level. See [Settings and style](/script/inputs/settings-and-style).

**Severity.** Whether a diagnostic is an error or a warning.

**Shadowing.** Declaring a name in an inner scope when the same name already exists in an enclosing one. OpenScript refuses it with error OS2002, because two variables with one name is the shortest path to a value that is right in one place and stale in another.

**Short-circuit.** The rule that `and` and `or` evaluate their right side only when it can still change the answer. A stateful call skipped this way does not advance on that bar.

**Signal.** A named marker on one bar, written with `signal()`. It is drawn on the chart rather than delivered like an alert.

**Slippage.** Ticks of adverse price movement a backtest applies to market and stop fills, declared on `strategy()`. It always works against you: a buy fills higher and a sell lower.

**Source.** An input whose default is a price series, such as `input(close, "Source")`, which lets you choose in the settings dialog which price source a study reads.

**Square off.** Close a position completely, bringing it back to flat. Intraday positions are squared off before the session ends.

**Stage.** Where a diagnostic is raised: reading the characters, building the structure, checking, running a bar, or the host answering. The stage tells you when you find out.

**State slot.** The storage a stateful call keeps between bars. It belongs to the place in the source where the call is written, which is what makes one helper function reusable in several places, and why a function cannot call itself.

**Stateful call.** A call that keeps values between bars, such as `ema()` or `rma()`, or a user function containing `var`. Inside a branch it advances only on the bars where the branch runs, which raises warning OS8001.

**Step.** A plot style, `style = "step"`, that holds a value flat until it changes. It is the honest way to draw a value that updates once per coarser bar.

**Straddle.** A call and a put on the same underlying, strike and expiry, bought or sold together, usually at the money. A study can plot the combined premium of the two; trading both legs from one strategy is planned. See [Legs and books](/script/strategies/multi-leg-and-books).

**Strategy.** A file declared with `strategy()`: a study that can also place orders, so the numbers you plot and the numbers you trade are the same numbers. See [Strategies overview](/script/strategies/overview).

**Strike.** The price at which an option can be exercised. Reading the chart instrument's strike, as `chart.strike`, is planned and not available in version 0.5.0.

**Study.** A file declared with `study()`: a calculation and what it draws on the chart, with no ability to place an order. Also called an indicator.

**Subject.** The value a `switch` compares each `case` against. A `switch` with no subject takes the first `case` whose condition is true.

**Synthetic future.** The futures price implied by a call and a put on the same strike and expiry: the strike plus the call premium minus the put premium. It is the forward price the option market itself is using, which is why Indian options are priced off it with Black-76.

## T

**Table.** A grid pinned to a corner of the pane, declared at the top level with `table()` and filled bar by bar with `cell()`. See [Tables](/script/visuals/tables).

**Tag.** A label on an order, such as `buy(tag = "entry")`, which a later `close()` or `cancel()` can refer to.

**Ternary.** `cond ? a : b`. Both arms must have the same type, or one may be `none`, and only the arm taken is evaluated.

**Tick.** The instrument's smallest price step, read as `chart.tickSize`. It is absent when the host has not supplied it, and `roundToTick()` rounds a price to it.

**Timeframe string.** A count and a unit, such as `"5m"`, `"60"`, `"1h"`, `"1D"`, `"1W"` or `"1M"`. A bare number is minutes. The unit letters are case sensitive: `"1M"` is one month and `"1m"` is one minute. See [Timeframes](/script/data/timeframes).

**Title.** The name of a study, a plot, a level or an input, used in the legend, the settings dialog and the saved layout. Titles must be unique within a file.

**Top level.** The outermost indentation of a file, where the declaration, `input()`, `plot()`, `fill()`, `level()`, `table()` and `fn` must appear.

**Trade.** One position from the fill that opens it to the fill that brings it back to flat. Adding to a position adds entries to the same trade, and a reversal is two trades.

**Trailing stop.** A stop that follows the price as a trade moves in your favour and never moves back. The trailing rules of the `leg` namespace are planned; in version 0.5.0 you keep the trailing level yourself in a `var` and close the position when the price crosses it.

**Truthiness.** Treating a number or a string as true or false. It does not exist in OpenScript: `if 1` is error OS2011, and a condition must be a `bool` or absent.

## U

**Unconfirmed bar.** A bar whose interval has not ended, so its values can still change. Signals, alerts and orders wait for it to close by default.

## V

**var.** The keyword that declares a value initialised once, on the first bar that reaches it, and kept from bar to bar after that. See [Persistence](/script/language/persistence).

**Version declaration.** The line `version 1` at the top of a file, which fixes the language version that reads it for good. Without it the file is read by the newest version and the compiler warns with OS8003. See [Script structure](/script/language/script-structure).

## W

**Warmup.** The bars at the start of the data on which a calculation cannot produce a value yet, shown as the absent value. `sma(close, 20)` first has a value on bar 19. Every function states its warmup exactly. See [Warmup](/script/language/warmup).

**Warning.** A diagnostic in the OS8xxx range. It stops nothing, and describes a shape that is valid but almost never what the author meant. See [OS8xxx Warnings](/script/errors/warnings).

**Watched condition.** What one `alert()` call becomes in the chart contract: its id, title, message and the conditions that lead to it. In /trading, the chart that shows the study checks it while the page is open, with nothing more for you to set up, and raises a notification when it holds. In this release the chart judges each bar once, when it first arrives, so during market hours an alert that waits for the bar to close may not fire; a study alert on a plotted condition is the dependable route. See [Alerts in /trading](/script/alerts/alerts-in-trading).

**Whole number.** A number with no fractional part, which lengths and array indices require. A fractional length is refused rather than rounded, because a length of 14.5 is a mistake in the script and rounding it would hide the mistake.

**Win rate.** The share of a backtest's closed trades that made money after charges. A trade that nets exactly zero counts as neither a win nor a loss.

Related: [FAQ](/script/resources/faq), [Execution model](/script/language/execution-model), [Absent values](/script/language/absent-values), [Reading an error](/script/errors/overview).


## FAQ

Source: https://openalgo.in/script/resources/faq

This page answers the questions that come up in your first week with OpenScript, also called OpenAlgo Script. Each answer is short on purpose and ends with a link to the page that explains it properly. If a word in an answer is new to you, the [Glossary](/script/resources/glossary) defines it.

## Getting started

### What is OpenScript?

An open trading language for writing a study (an indicator that draws on the chart) or a strategy (a study that also places orders) once, then plotting it, backtesting it and trading it with the same numbers in all three places. It runs in the /trading page of OpenAlgo, and it is also published as two Apache 2.0 libraries: `openalgo-script` on npm for JavaScript and TypeScript, and `openscript` on PyPI for Python. See [Introduction](/script/getting-started/introduction).

### What does a script look like?

This is a complete study. It draws two moving averages over the price and marks the bar where the fast one crosses above the slow one. It works on any instrument and any interval.

```openscript
version 1
study("EMA cross", overlay = true, precision = 2)

fastLen = input(9, "Fast length", min = 1, max = 500)
slowLen = input(21, "Slow length", min = 1, max = 500)

fast = ema(close, fastLen)
slow = ema(close, slowLen)

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)

if crossUp(fast, slow)
    signal("BUY", lime, at = "below", shape = "arrowUp")
```

Twelve complete scripts, from this one to an options premium strategy, are on [Example scripts](/script/getting-started/example-scripts).

### Do I need to install anything?

Not to use it in OpenAlgo. Open the /trading page, open the Scripts panel and start a new script. The compiler runs in your browser, and there is no build step between saving a script and putting it on a chart. To run OpenScript inside your own application you install one of the two libraries. See [Quickstart](/script/getting-started/quickstart) and [Two libraries](/script/integrate/overview).


### Where do I start reading?

[Quickstart](/script/getting-started/quickstart), then [Your first strategy](/script/getting-started/first-strategy). When you want the rules rather than the tour, read [Execution model](/script/language/execution-model) and [Absent values](/script/language/absent-values): those two pages explain most of the language.

### How do I see what is wrong with my script?

Save it. In the /trading editor, Ctrl+S saves and compiles in one step, and a script that does not compile is still saved. The status bar under the editor then says Ready, Ready with a count of warnings, or how many errors stop the script from running. The console button at the left of the status bar opens the console, which lists each diagnostic with its code, its line and column, the line itself with the problem marked, the message and the fix.

The editor does not complete names or show help on hover in this release, so keep the reference open beside it. See [The editor](/script/getting-started/the-editor).

### Do I have to write `version 1` at the top?

No, but always do. Without it the file is compiled with the newest language version the compiler has, which is the one thing that could change under you, and the compiler warns with OS8003. See [Script structure](/script/language/script-structure).

### Will my script keep working after an update?

A script that declares `version 1` keeps compiling under the version 1 rules in every later release, because the compiler keeps every past version of the language. A release can still correct how a library function computes. When one does, the [Release notes](/script/resources/release-notes) list it under the changes a script can observe: 0.5.0, for example, changed what `text()` and `round()` give in a few extreme cases that no ordinary price reaches.

### What is the difference between a study and a strategy?

A study only calculates and draws. A strategy is a study that can also place orders, declared with `strategy(...)` instead of `study(...)`. The language, the file format and every drawing call are the same. In /trading, applying a study from the Scripts panel adds it to the chart, while applying a strategy runs a backtest over the chart's history and marks its trades. See [Strategies overview](/script/strategies/overview).

## The language

### What types are there?

`number`, `string`, `bool` and `color`, each either a plain value or a `series` that has one value per bar, plus arrays such as `array<number>`. There is no separate integer type: a length, a bar count and a price are all `number`. `none`, the absent value, belongs to every type. See [Types and values](/script/language/types-and-values).

### Why does `"RSI " + r` not work?

Nothing converts itself in OpenScript. A string and a number do not mix, so the compiler stops you with OS2003:

```openscript
r = rsi(close, 14)
label = "RSI " + r
```

Convert the number yourself with `text()`, which also takes a number of decimals:

```openscript
r = rsi(close, 14)
if crossUp(r, 50)
    signal("RSI " + text(r, 2))
plot(r, "RSI")
```

A silent conversion would have to pick a format for you, and a message or an alert that shows the wrong number of decimals, or `none` where you expected a price, is a bug you find late. See [Types and values](/script/language/types-and-values).

### Why is `if 1` an error?

A condition must be a `bool` or absent. There is no truthiness (treating a number or a string as true or false), so there is no rule about which values count as true to remember or to get wrong. `if 1` is OS2011. A trading script that quietly treats a zero as false has a bug nobody finds until it costs money.

### How do I write a block?

With indentation, using spaces. There are no braces, no `end` and no semicolons. Every line of one block carries exactly the same indentation, and four spaces is the convention. A tab is OS1002 and a line out by one space is OS1003. See [Script structure](/script/language/script-structure).

### Why can I not write `a < b < c`?

Because its two plausible readings disagree, and a script that places orders should not have to guess which one you meant. Write `a < b and b < c`. The chained form is OS1008. See [Operators](/script/language/operators).

### Where are `&&`, `||`, `!` and `^`?

They do not exist. The words are `and`, `or` and `not`, and the power function is `pow()`. Typing one of the missing operators gives OS1001, and the message names the replacement.

### Can a function call itself?

No. A function's state belongs to the place it is called from, so recursion would need an unbounded pile of state on every bar. Write a loop instead. A function that calls itself is OS2005. See [User functions](/script/language/functions).

### Why can I not declare a variable with a name that already exists outside the block?

Assigning to a name that already exists updates it, from any block. Declaring a second variable with the same name inside a block, for example with `var`, could only be a mistake, because two variables with one name is the shortest path to a value that is right in one place and stale in another. The compiler refuses it with OS2002:

```openscript
count = 0
if close > open
    var count = 1
```

Drop the `var` to update the outer `count`, or pick a new name. See [Variables and scope](/script/language/variables-and-scope).

## Values, absence and warmup

### What is `none`?

The absent value: there is no value here. You write it bare, it belongs to every type, and every rule about how it behaves is fixed by the language rather than left to chance. See [Absent values](/script/language/absent-values).

### Is `none` the same as zero?

No, and this is the most important answer on this page. `none + 1` is `none`, `none * 0` is `none`, and an absent value reaching a plot draws a gap rather than a zero.

### Why is `none > 5` not false?

If it were false, then `a > b` and `a <= b` could both be false, and a script that takes one branch and assumes the other is its opposite would go the wrong way during warmup, on bars off the left edge of the screen where nobody looks. So an ordered comparison with an absent side is absent, and an `if` on an absent condition does not run its block (it runs the `else` block, if there is one): during warmup `if r > 70` and `if r <= 70` both skip theirs. Equality is the exception: `x == none` is always true or false.

### How do I test for absence?

`isNone()`, or `x == none`. Equality always answers true or false, never absent, which is what makes the question askable. To replace an absent value with a fallback, use `orElse()`.

### Why does my line start part way along the chart?

Warmup: the first bars on which a calculation cannot have a value yet. A calculation that needs `k` bars is absent until `k` bars exist, so `sma(close, 20)` first has a value on bar 19. Every reference entry states the first bar its function has a value on. See [Warmup](/script/language/warmup).

### How do I get a number during warmup?

`orElse(x, fallback)`. Use it deliberately: a value you substitute on the first bars is data you invented. See [Warmup](/script/language/warmup).

### What is `close[1]` on the first bar?

Absent. Nothing is clamped to the start of the data, because a clamped value looks like real data and is not. See [Bars and history](/script/language/bars-and-history).

### When do I need `var`?

When a value has to carry forward from one bar to the next. A plain assignment is worked out again from scratch on every bar. This study counts consecutive bars that closed higher than the bar before, and starts again from zero on any bar that did not:

```openscript
version 1
study("Up-close streak")

var streak = 0

if close > close[1]
    streak = streak + 1
else
    streak = 0

plot(streak, "Consecutive higher closes", aqua, style = "histogram")
```

`var streak = 0` runs once, on the first bar. On every later bar `streak` starts from the value the bar before left it with. On the first bar `close[1]` is absent, so the condition is absent and the `else` block sets the streak to zero. See [Persistence](/script/language/persistence).

### What is the difference between `var` and `[1]`?

`[1]` is history: it reads the value a series had one bar ago. `var` is persistence: it keeps a value and carries it forward. They answer different questions and are often confused.

### What does `live var` do, and should I use it?

It is a `var` that does not roll back when the newest bar runs again on an update, so it keeps counting within the bar. Use it only when counting those updates is the point, because a script that uses one reports different numbers on the chart than in a backtest of the same data, and the compiler warns with OS8011. See [Realtime and confirmation](/script/language/realtime-and-confirmation).

## Plotting and drawing

### Why can I not put `plot` inside an `if`?

The chart needs the full set of plots before the first bar, to build the legend, the axis and the settings dialog. To hide a plot on some bars, plot `none` on them instead. A plot inside a block is OS3006. See [Plots](/script/visuals/plots).

```openscript
trend = ema(close, 50)
plot(close > trend ? trend : none, "Trend while price is above it", lime)
```

### Why does every plot need a title?

The title is the plot's name in the legend, in the settings dialog and in the saved layout, so it is required and must be unique in the file. `plot(x)` without one is OS3012, and two plots with one title is OS3017.

### How do I draw an arrow on a bar?

`signal(text, shape = "arrowUp", at = "below")`. One call covers every marker shape, and it may sit inside an `if`. See [Labels and shapes](/script/visuals/labels-and-shapes).

### How do I draw a line between two points and move it later?

With the `draw` namespace: `draw.line()`, `draw.label()`, `draw.box()`, `draw.polyline()` and the setters that move and restyle them. Objects are anchored to a time and a price, so they stay where you put them when more history loads. See [Lines and boxes](/script/visuals/lines-and-boxes).

### Is there a limit on how many objects I can draw?

The language fixes no number: the host sets the ceiling, and the engine's default, which the /trading chart keeps, is 10,000 objects held at once. A script that would create one more stops with OS5010 rather than having the oldest quietly deleted. Delete objects you no longer need.

### How do I colour the candles themselves?

`barColor()`, on any bar, anywhere in the script, including inside an `if`. `barColor(none)` leaves the bar its own colour, which is how a condition switches the colouring off. See [Bar colouring and backgrounds](/script/visuals/bar-coloring-and-backgrounds).

### How do I put a value on its own scale, or shift it forward?

The `scale` and `offset` arguments of `plot()`. An offset moves where the plot is drawn, never what it contains. See [Plots](/script/visuals/plots).

## Data, sessions and other instruments

### How do I read the daily close on an intraday chart?

`req.timeframe("1D", close)`. By default it reads only days that have closed, so during today's session it gives the previous session's close, which is usually what you want for a pivot or a gap. See [Higher timeframes](/script/data/higher-timeframes).

```openscript
prevClose = req.timeframe("1D", close)
plot(prevClose, "Previous session close", gray, style = "step")
```

### Will that repaint?

Not in the default mode. `mode = "confirmed"` reads only higher timeframe bars that have closed and never repaints (redraws history differently after the fact). The other two modes must be written out in the source. `"lookahead"` raises warning OS8005 on its line. `"developing"` raises no warning, so treat it as a deliberate choice: it shows the coarser bar as it stands, and that value keeps changing until the bar closes. See [Repainting](/script/data/repainting).

### Why is my higher timeframe read empty?

It is still warming up, the data has not arrived, or the host has not supplied what the read needs. Warmup is counted in the requested bars, so `req.timeframe("1D", sma(close, 20))` is absent until twenty daily bars have closed, which on an intraday chart needs about a month of history. A daily, weekly or monthly read also needs the instrument's timezone from the host. `req.isReady()` and `req.error()` tell you whether the answer has arrived and, if the host refused, why.

### Can I read another instrument?

Yes, with `req.symbol()`. This reads the NIFTY index on the chart's own interval, for example to compare a stock with the index:

```openscript
nifty = req.symbol("NIFTY", chart.interval, close, exchange = "NSE_INDEX", mode = "developing")
plot(nifty, "NIFTY")
```

`mode = "developing"` pairs each chart bar with the index bar at the same time. The default, `"confirmed"`, hands back only bars that have closed, which at the chart's own interval is the index's previous bar, so a comparison would mix two different bars. The value is absent until the host supplies the bars. Without `exchange`, the read uses the chart's own exchange. See [Other instruments](/script/data/other-instruments).

### How do I work with the 09:15 to 15:30 session?

`session.isIn()` tells you whether a bar falls inside a window you state, such as `session.isIn("0915-1530")`, and `bar.isFirst or not date.isSameDay(time, time[1])` finds the first bar of each day. Both read the bar's time in the chart's timezone. `session.isFirstBar` and `session.isLastBar` also need the instrument's trading hours.

All of these come from facts the host supplies, and a fact the host has not supplied makes the read absent rather than guessed. In this release the /trading chart supplies its timezone but not the trading hours, so `session.isFirstBar` is absent there, and a new IST date, `isNone(time[1]) or not date.isSameDay(time, time[1], "Asia/Kolkata")`, is the test to use for the first bar of an NSE day. A backtest run from the Backtest panel is given neither, so there a session or calendar read that relies on the chart's timezone is absent, and so is a daily read. Name the zone, as in `session.isIn("0915-1530", "Asia/Kolkata")`, and the read works in a backtest too. See [Sessions and time](/script/data/sessions-and-time).

## Alerts

### How do I raise an alert?

Put `alert(message, id = "...")` inside the `if` that describes the condition. There is no separate function to declare a condition: the condition is the `if` you would have written anyway. In /trading, once the study is on a chart, the chart checks the condition as new bars arrive, shows a notification when it fires, and the Alerts panel keeps a log of every firing. Alerts are checked by the chart that is open, so they fire only while /trading is open.

In this release the chart judges a script's alert once, when a bar first arrives, and during market hours that is before the bar has closed, so an alert that waits for the close may not fire at all. To be told reliably, plot the condition as 1 or 0 and create a study alert on that plot from the chart's **Create alert** dialog, with **Study plot** as what to watch. See [Alerts from scripts](/script/alerts/overview) and [Alerts on a script condition](/script/alerts/alerts-in-trading#alerts-on-a-script-condition).

### Why does my alert not fire on the bar where I can see it should?

Because that bar is still forming. Alerts, signals and orders wait until the bar closes, and if the condition is no longer true by then they never fire. That is what stops an alert from firing on a cross that is gone a minute later. On the /trading chart there is a second reason, the one in the answer above: the chart has already judged the bar before it closed.

### Why did adding the study not fire alerts for all the past bars?

By design. An alert is a statement about now, and hundreds of alerts for history would bury the one that matters.

### Why does my alert need an `id`?

The id is the alert's stable name, which the host uses to keep track of it. Without one, the compiler names the alert after the line the call is on, which changes the moment you insert a line above it, and it warns with OS8008.

## Strategies

### How do I turn a study into a strategy?

Change `study(` to `strategy(` and add orders. `strategy()` takes every option `study()` takes, so the plotted numbers and the traded numbers are the same numbers, computed once. See [Your first strategy](/script/getting-started/first-strategy).

```openscript
version 1
strategy("EMA cross", overlay = true, capital = 500000, qtyType = "units")

// One lot, counted in units. chart.lotSize is absent, not 1, where the host
// states no lot size, as on the /trading chart.
lotUnits = max(orElse(chart.lotSize, 1), 1)

fast = ema(close, 9)
slow = ema(close, 21)

plot(fast, "Fast", aqua, width = 2)
plot(slow, "Slow", orange, width = 2)

if crossUp(fast, slow) and pos.isFlat
    buy(qty = lotUnits)

if crossDown(fast, slow) and pos.isLong
    close()
```

The size is one lot, stated in units, which is how NFO futures and MCX contracts are best sized in this release: the Backtest panel states the lot size, the chart does not (so there the example trades one unit), and the Strategies panel runs only a strategy that counts in units. See [Position and sizing](/script/strategies/position-and-sizing).

### Why did my order fill at the next bar's open?

Because `fillOn` defaults to `"nextOpen"`. A decision made from a bar's close cannot be filled at that same close in the real market, and a backtest whose default is optimistic is a backtest that misleads you. `fillOn = "close"` fills at the signal bar's close instead; use it knowing it flatters the result. See [Costs and fills](/script/strategies/costs-and-fills).

### Can I hold a long and a short position at the same time?

Not in version 0.5.0. A strategy holds one net position in the chart's instrument, positive when long and negative when short, read as `pos.size`. Positions made of several legs, such as a straddle or a hedge, are planned through the `leg` and `book` functions. See [Legs and books](/script/strategies/multi-leg-and-books).

### How do I size a position?

With `qty` on the order or the declaration, counted in the unit `qtyType` names: `"units"` or `"lots"`. To risk a fixed amount per trade, work the size out yourself from the distance to your stop, round it down, and then actually exit at that stop:

```openscript
version 1
strategy("Risk-sized entry", overlay = true, precision = 2,
         capital = 500000, qtyType = "units", qty = 1)

riskAmount = input(5000, "Rupees at risk per trade", min = 100)

fast = ema(close, 9)
slow = ema(close, 21)
crossedUp = crossUp(fast, slow)
crossedDown = crossDown(fast, slow)
swingLow = lowest(low, 10)

perUnit = close - swingLow
units = perUnit > 0 ? floor(riskAmount / perUnit) : none
canTrade = not isNone(units) and units > 0

var stopLevel = 0.0

if crossedUp and pos.isFlat and canTrade
    buy(qty = units)
    stopLevel = swingLow

if pos.isLong and (close < stopLevel or crossedDown)
    close()

plot(fast, "Fast", aqua)
plot(slow, "Slow", orange)
plot(pos.isLong ? stopLevel : none, "Stop", red, style = "step")
```

The stop is the lowest low of the last 10 bars, fixed at the entry. The two crossings are worked out at the top level, before the `if` lines, because a crossing call inside the right side of `and` or `or` would skip bars and the compiler would warn with OS8001. The exit is checked on each bar's close and fills at the next open, so a gap through the stop can lose more than the amount you set. A stop set with `exit()` is not used here because a backtest does not fill one in version 0.5.0.

The sizing helpers `order.qtyForRisk()`, `order.qtyForCash()`, `order.qtyForEquityPercent()` and `order.roundToLot()` are planned, and a backtest refuses a quantity counted in `"cash"` or `"equityPercent"` in this release. See [Position and sizing](/script/strategies/position-and-sizing).

### Can a strategy read its own profit or equity?

Not yet. `pos.size`, `pos.avgPrice`, `pos.isFlat`, `pos.isLong` and `pos.isShort` work today. The money figures, such as `pos.equity`, `pos.netProfit` and `pos.openProfit`, are planned and are refused with OS2020. The backtest report shows the run's net profit, equity curve and drawdown once it finishes. See [Reading a report](/script/strategies/reading-a-report).

### Why was my order refused?

The OS7xxx code on the diagnostic says which rule it broke, and the message gives the numbers involved, such as the quantity or the price. See [OS7xxx Orders](/script/errors/orders).

### Does a strategy trade with real money?

It depends on the mode OpenAlgo is in, not on the script. A strategy you deploy from the Strategies panel sends its orders through OpenAlgo's own order path: to the sandbox while OpenAlgo is in analyzer mode (sandbox trading), and to your trading account while it is in live mode. The mode is set elsewhere on the site. The Strategies panel shows it in its header, Analyzer or Live, and the start button reads Start in sandbox or Start live. Nothing in a script can choose the mode.

Backtest first, then run the strategy in sandbox trading (analyzer mode in OpenAlgo), then decide. See [Sandbox and live](/script/strategies/sandbox-and-live).

## Errors, warnings and limits

### What does a code like OS2002 mean?

The first digit is the kind of problem: OS1xxx syntax, OS2xxx names and types, OS3xxx arguments, OS4xxx runtime, OS5xxx limits, OS6xxx data, OS7xxx orders, OS8xxx warnings. Every diagnostic carries a message and a fix. See [Reading an error](/script/errors/overview).


### Do I have to fix the warnings?

Nothing stops if you do not. But every OS8xxx warning describes something that is valid and almost never what the author meant, so in practice: yes, read each one.

### What does error OS2020 mean?

You used a name the language defines but this release does not implement yet. The reference marks such names with a Planned badge, and [Release notes](/script/resources/release-notes) lists what is planned.

### Is there a limit on loops?

2,000,000 iterations per bar by default, counted across every loop the bar runs, and raised in one line with `limits(loops = ...)` directly after the declaration. See [Limits](/script/writing/limits).

### My script is slow. What is the usual cause?

A loop that walks the whole history on every bar. Keep a running value in a `var` instead, or use the library function that already does it, such as `sum()` or `highest()`. See [Profiling and speed](/script/writing/profiling).

### Where is the full list of errors?

The Errors section, one page per range, starting at [Reading an error](/script/errors/overview). The editor and those pages read the same catalogue, so they never disagree.

## These pages and the libraries

### Are the examples on these pages tested?

Yes. Every OpenScript example on these pages is checked with the real compiler, the same version the /trading editor uses, and every signature, default and warmup in the reference is read from that compiler rather than typed in.

### Can I use OpenScript outside OpenAlgo?

Yes. The JavaScript library (`openalgo-script` on npm) compiles and runs scripts, draws them on a chart and backtests strategies. The Python engine (`openscript` on PyPI) runs compiled programs on a server. Both are Apache 2.0, and neither needs another package to run. See [Two libraries](/script/integrate/overview).

### Can I write my own engine?

Yes. The compiled program is a documented data format and the conformance suite says what any engine must reproduce. See [Compiled program](/script/integrate/compiled-program) and [Your own engine](/script/integrate/conformance).

### Can an AI assistant write OpenScript?

Yes, if you give it the reference. The whole documentation is published as one markdown file for exactly this. See [Using AI assistants](/script/resources/ai-assistants).

### What works today, and what is still planned?

See [Release notes](/script/resources/release-notes), which summarises every release and the roadmap.

Related: [Glossary](/script/resources/glossary), [Troubleshooting](/script/writing/troubleshooting), [Reading an error](/script/errors/overview), [Example scripts](/script/getting-started/example-scripts).


## Using AI assistants

Source: https://openalgo.in/script/resources/ai-assistants

An AI assistant can write OpenScript, also called OpenAlgo Script, well if it has the reference in front of it, and badly if it works from memory. OpenScript is a young language with its own rules, so an assistant left to guess tends to borrow names and habits from other languages, and the result does not compile. This page covers the two files this documentation publishes for assistants, how to hand them over, an instruction that keeps the assistant inside the language, and how to check what comes back in the /trading editor. Everything here works with any assistant that accepts an attached file or can read a web page.

## Two files for assistants

Every time this documentation is built, two plain text files are generated from the same pages you are reading.

| File | Address | What it holds | Use it when |
|---|---|---|---|
| Complete reference | [openscript-reference.md](https://openalgo.in/script/openscript-reference.md) | Every page of this documentation as one markdown file: the guides, every reference entry with its signature, parameter table and first bar with a value, every error with its message and fix, and every example | You want the assistant to have everything at once |
| Map | [llms.txt](https://openalgo.in/script/llms.txt) | A short index: one line per page with its title, its address and a one-sentence summary, and a link to the complete reference | The assistant can read web pages and should fetch only what it needs |

Both files are written for machines as much as for people, so they drop what an assistant cannot use. Screenshots are left out, callouts become plain text under a short label, and a link to a reference entry becomes the entry's name in code format. A reference entry that is planned rather than available carries "(planned, not available yet)" in its heading, which is what lets an assistant avoid it.

The complete reference opens with the version it describes, for example `# OpenScript 0.5.0 complete reference`, and every page inside it carries a `Source:` line with that page's address on this site. The map follows the llms.txt convention: a title, a one-line summary, then sections of links with a sentence each.

## A worked example

Here is the whole loop in miniature. You give the assistant the complete reference and the instruction from the next section, then ask:

```text
Write a study for a 5-minute NSE chart that draws a 9 and a 21 period EMA,
and marks the bar where the 9 crosses above the 21 while RSI(14) is above 50.
I also want an alert on that condition.
```

A good answer is one complete script that follows every rule in the instruction:

```openscript
version 1
study("EMA cross with RSI filter", overlay = true, precision = 2)

fastLen = input(9, "Fast EMA", min = 1, max = 200)
slowLen = input(21, "Slow EMA", min = 2, max = 400)
rsiLen = input(14, "RSI length", min = 2, max = 100)

fast = ema(close, fastLen)
slow = ema(close, slowLen)
r = rsi(close, rsiLen)

plot(fast, "Fast EMA", aqua, width = 2)
plot(slow, "Slow EMA", orange, width = 2)

if crossUp(fast, slow) and r > 50
    signal("BUY", lime, at = "below", shape = "arrowUp")
    alert("Fast EMA crossed above slow EMA with RSI above 50", id = "emaCrossRsi")
```

Notice what it did not do: no invented function names, no second declaration, a title on every plot, the stateful calls (`ema()` and `rsi()`, which keep values from bar to bar) worked out at the top level rather than inside the `if`, and an `id` on the alert so its name survives later edits. You then paste it into the /trading editor, as described below, and save it: the status bar reads Ready when it compiles.

## Giving the assistant the documentation

There are two ways, and which one fits depends on what your assistant can do.

**Attach the file.** Download [openscript-reference.md](https://openalgo.in/script/openscript-reference.md) and attach it to the conversation, the way you would attach any document. This works with any assistant that accepts file attachments, including one that cannot browse the web, and it guarantees the assistant is reading the version you downloaded. The file holds the whole documentation, well over a megabyte of text, which is more than some assistants accept. If yours refuses it, use the map to find the pages your task needs and paste those instead.

**Point the assistant at the address.** If your assistant can read web pages, give it the map:

```text
Before you write any OpenScript, read https://openalgo.in/script/llms.txt
and then the complete reference it links to, or at least the pages that
cover what I am asking for.
```

The map lets the assistant fetch only the pages a task needs, for example the Strategies pages for an order question, or a single reference page for one function.

> **Start a new conversation for each script, or at least repeat the instruction below. Assistants drift back to habits from other languages over a long conversation, and the reminder costs one paste.**

Download a fresh copy when the version changes. The first line of the complete reference names the OpenScript version it describes, and [Release notes](/script/resources/release-notes) tells you when a new version arrives.

## What to tell the assistant

Paste this instruction at the start of the conversation, together with the reference. Each rule prevents a mistake assistants make often.

```text
You are writing OpenScript (also called OpenAlgo Script), the trading language
of the /trading page in OpenAlgo. Use the OpenScript reference I have given you
as the only source of truth for names, arguments and behaviour.

Rules for every script:
1. The first line is exactly: version 1
2. The file has exactly one declaration. Use study("Title", ...) for a script
   that only calculates and draws, and strategy("Title", ...) for one that
   places orders. Never both.
3. Use only functions, values and argument names that appear in the reference.
   Check every name against the reference before you use it. Never invent a
   name or borrow one from another language. An entry marked "planned, not
   available yet" cannot be used.
4. Every plot() has a title as its second argument, and no two plots share a
   title.
5. plot, fill, level, input and table go at the top level only, never inside
   if, for, switch or a function. To hide a plot on some bars, plot none there.
6. Indent blocks with four spaces. No tabs, no braces, no semicolons.
7. Use and, or, not and pow(). There is no &&, ||, ! or ^.
8. Nothing converts itself: use text() to put a number into a string.
9. The absent value is none. Test it with isNone() or == none, and replace it
   with orElse(). close[1] is none on the first bar.
10. Work out stateful calls such as ema(), rsi() and atr() at the top level,
    then use the results inside if blocks.
11. Use var for a value that must carry from one bar to the next.
12. Give every alert() an id.
13. For a strategy on contracts that trade in lots, such as NFO futures and
    options or MCX, declare qtyType = "lots".
14. In a strategy, exit with close() when your own condition says so. A
    backtest does not fill the target or stop set by exit() or
    order.bracket() in this version.

Answer with one complete script in a single code block. After it, list every
assumption you made, such as the interval, the instrument and the session.
```

The first four rules are the ones that matter most. `version 1` fixes the language version the script is read by. One declaration is a hard rule of the language. Checking names against the reference is what stops invented functions, which are the most common failure. And a missing plot title is an error, so it is worth a rule of its own.

## Checking what comes back

Never trust generated code because it looks right. Paste it into the editor and let the compiler read it.

1. Open the /trading page, open the Scripts panel and create a new script.
2. Paste the assistant's script into the editor, replacing the starter text.
3. Save with Ctrl+S. Every save compiles the script, and a script that does not compile is still saved.
4. Read the status bar under the editor. It says Ready, Ready with a count of warnings, or how many errors stop the script from running.
5. Open the console with the button at the left of the status bar. It lists each diagnostic with its code, its line and column, the line itself with the problem marked, the message and the fix.


The editor does not complete names or show help on hover in this release, so the reference is where you check a name the assistant used.

These are the mistakes assistants make most often, and the diagnostic that catches each one:

| What the assistant wrote | Diagnostic | What to do |
|---|---|---|
| No `version 1` line | OS8003 (warning) | Add `version 1` as the first line |
| A name that does not exist: an invented function, a namespace prefix borrowed from elsewhere, or `na` for an absent value | OS2001 | Find the real name in the reference; absence is `none` |
| A name the reference marks planned | OS2020 | Rewrite without it; see the release notes for what is available |
| An argument name that does not exist, such as `colour` | OS3002 | The fix lists every real argument name and the closest one |
| `plot(x)` with no title | OS3012 | Add a title as the second argument |
| Two plots with the same title | OS3017 | Give each plot its own title |
| A `plot` or an `input` inside an `if` | OS3006, OS3007 | Move it to the top level; plot `none` to hide it |
| An order call such as `buy()` in a study | OS7001 | Change `study(` to `strategy(` |
| Two declarations | OS2008 | Keep one |
| `&&`, `^` or braces | OS1001 | Use `and`, `pow()` and indentation |
| A semicolon at the end of a line | OS1007 | Remove it |
| A tab in the indentation | OS1002 | Indent with spaces |
| A number added to a string | OS2003 | Wrap the number in `text()` |
| `:=` to change a variable | OS1018 | Write `x = 2`; assignment is always `=` |
| A function that calls itself | OS2005 | Write a loop |
| A stateful call inside a branch | OS8001 (warning) | Work it out at the top level and use the result in the branch |

When a diagnostic appears, copy its code, its message and the line it points at back to the assistant, and ask it to fix only that. The message is written to be acted on, and the full explanation of every code is in the Errors section, starting at [Reading an error](/script/errors/overview).

One caution about OS2001. Its fix offers the library name spelled most like the one that failed, which is a spelling guess, not a translation: for `na` it offers `ma()`, a moving average, when the absent value is `none`. Check the suggestion against the reference before you or the assistant accept it.

Here is what that looks like on the most common case, a plot without a title:

```openscript
plot(ema(close, 20))
```

The fix is one argument:

```openscript
plot(ema(close, 20), "EMA 20", orange)
```

## What the diagnostics cannot catch

A script that compiles with no errors is a script the language accepts, not a script that does what you asked. Check the behaviour yourself before you rely on it.

- **Read every warning.** Warnings do not stop a script, and every OS8xxx warning describes something that is valid and almost never intended.
- **Put it on a chart and look.** Apply the study to a chart and compare its values on a few bars with a built-in study from the indicators dialog, or with values you work out by hand.
- **Check where the lines start.** A line that begins later than you expect is warmup, which is correct. A line that begins at bar 0 when it should not may mean the assistant used `orElse()` to invent values.
- **Check higher timeframe reads.** `req.timeframe()` reads only closed bars unless the script writes `mode = "developing"` or `mode = "lookahead"`. If the assistant wrote either, ask why: both can repaint. See [Repainting](/script/data/repainting).
- **Check the declaration options.** `onUnconfirmed = true` lets signals, alerts and orders act on a bar that is still forming. An assistant should not set it unless you asked for that.
- **Check the market assumptions.** Session times in IST, the interval, lot sizes and the exchange are easy for an assistant to assume wrongly. The list of assumptions the instruction asks for is where to look.
- **Check anything that depends on time of day.** Session and calendar reads, such as `session.isIn()`, `session.isFirstBar` or a daily `req.timeframe()`, come out absent where the host has not supplied the facts they need, and a condition built on them then never holds. Put the script on a chart and confirm those conditions fire where you expect. See [Sessions and time](/script/data/sessions-and-time).

For a strategy, the order of work matters more than the code:

1. Backtest it from the Backtest panel, with realistic commission and slippage declared. See [Backtesting](/script/strategies/backtesting).
2. Read the report and the trade list, and check a few trades against the chart. See [Reading a report](/script/strategies/reading-a-report).
3. With OpenAlgo in analyzer mode, deploy it from the Strategies panel. The panel header reads Analyzer and the start button reads Start in sandbox, so its orders go to sandbox trading (analyzer mode in OpenAlgo). Let it run on the latest market data until you have seen it trade. See [Sandbox and live](/script/strategies/sandbox-and-live).
4. Only then decide whether to run it with live orders.

> **Never start a generated strategy while OpenAlgo is in live mode without having run it in sandbox trading first. Where orders go is set by OpenAlgo's mode, not by the script, and the start button then reads Start live. An assistant cannot see your account, your margin or the instrument's real lot size, and a strategy that compiles can still send orders you did not intend.**

Related: [The editor](/script/getting-started/the-editor), [Reading an error](/script/errors/overview), [Troubleshooting](/script/writing/troubleshooting), [Release notes](/script/resources/release-notes).


## Release notes

Source: https://openalgo.in/script/resources/release-notes

This page summarises every release of OpenScript, also called OpenAlgo Script, newest first, and then the roadmap. Read it when you want to know whether something works in the version you have, or whether an upgrade changes anything your scripts can see. The current version is **0.5.0**, and both libraries carry it: `openalgo-script` on npm (JavaScript and TypeScript) and `openscript` on PyPI (Python) are released together, at the same version, under Apache 2.0.

The version is 0.5.0 rather than 1.0 on purpose. The studies surface is finished and is the part to build on. Strategies run and backtest on one instrument, with limits stated below, and multi-leg strategies are designed but not built.

## What works in 0.5.0

| Area | Status | Notes |
|---|---|---|
| Studies: plots, levels, fills, colours, markers, bar colouring, backgrounds | Works | The finished part of the language |
| Tables and drawing objects (lines, labels, boxes, polylines) | Works | A study that declares two tables draws only the first |
| Inputs and the settings dialog | Works | A plot's `style` chosen through an `input()` is drawn in its default style |
| Higher timeframe and other instrument reads | Works | `req.candle()` and `req.events()` are planned |
| Alerts and markers from scripts | Works | `alert()` and `signal()` work; `notify()` is planned. The /trading chart judges a script's alert once, when a bar first arrives, so during market hours an alert that waits for the close may not fire there: see [Alerts in /trading](/script/alerts/alerts-in-trading) |
| Editor functions: highlighting, completion, hover, signature help, diagnostics, formatting | Works, in the library | For anyone building an editor. The /trading editor uses the highlighting and the compiler's diagnostics on every save; it has no completion, hover or signature help in this release |
| Strategies on one instrument | Works | `buy()`, `sell()`, `exit()`, `close()`, `cancel()`, `cancelAll()`, `order.place()`, `order.bracket()` and `order.reverse()`. A backtest does not fill the levels `exit()` and `order.bracket()` set |
| Position facts | Partly | `pos.size`, `pos.avgPrice`, `pos.isFlat`, `pos.isLong` and `pos.isShort` work; the money figures such as `pos.equity` and `pos.netProfit` are planned |
| Backtest and report | Works, with stated limits | See [Known limits](#known-limits-in-0-5-0) |
| A strategy drawn on a chart | Works | New in 0.5.0 |
| Order sizing helpers and order status | Planned | `order.qtyForRisk()`, `order.roundToLot()`, `order.status()`, `order.pending` and the rest of that group |
| Multi-leg positions | Planned | Every `leg.*` and `book.*` name |
| Maps, matrices, user types and imports | Reserved | The words are reserved for a later language version |
| Python engine | Published | Runs compiled programs on a server; holds no compiler, and has no arrays and no log yet |

A planned name is refused where you write it, with error OS2020, so you find out in the editor rather than on a chart. The reference marks each one with a Planned badge.

```openscript
profitSoFar = pos.netProfit
```

Everything in the next study works in 0.5.0. It reads the previous session's high and low onto an intraday chart, draws them as steps and shows them in a table. By default a daily read takes only days that have closed, so on a 5-minute NSE chart during today's session these are yesterday's levels.

```openscript
version 1
study("Previous day levels", overlay = true, precision = 2)

pdh = req.timeframe("1D", high)
pdl = req.timeframe("1D", low)

plot(pdh, "Previous day high", green, style = "step")
plot(pdl, "Previous day low", red, style = "step")

panel = table("Levels", 2, 2, position = "topRight")

if bar.isLast
    cell(panel, 0, 0, "PDH")
    cell(panel, 0, 1, text(pdh, 2))
    cell(panel, 1, 0, "PDL")
    cell(panel, 1, 1, text(pdl, 2))
```

### Planned names in 0.5.0

These names are part of the language's design and are refused with OS2020 in this release.

| Group | Planned names |
|---|---|
| Position | `pos.barsHeld`, `pos.entries`, `pos.entryTime`, `pos.equity`, `pos.isShared`, `pos.maxDrawdown`, `pos.maxLoss`, `pos.maxProfit`, `pos.netProfit`, `pos.openProfit`, `pos.openProfitPercent`, `pos.profitFactor`, `pos.tradeCount`, `pos.winRate` |
| Orders | `order.avgFill()`, `order.filled()`, `order.id()`, `order.modify()`, `order.oco()`, `order.pending`, `order.qtyForCash()`, `order.qtyForEquityPercent()`, `order.qtyForRisk()`, `order.rejection()`, `order.roundToLot()`, `order.status()`, `order.working()` |
| Legs and books | Every `leg.*` name, such as `leg.fixed()` and `leg.relative()`, and every `book.*` name, such as `book.stop()` and `book.dailyLoss()` |
| Session | `session.isOpen`, `session.startTime`, `session.endTime`, `session.nextOpen`, `session.barIndex`, `session.isHoliday()` |
| Instrument | `chart.expiry`, `chart.strike`, `chart.optionType`, `chart.isReplay` |
| Indicators | `coppock()`, `cvd()`, `fisher()`, `kama()`, `klinger()`, `massIndex()`, `nvi()`, `pvi()`, `rvi()`, `vidya()`, `volumeProfile()`, `zigzag()`, `zlema()` |
| Requests | `req.candle()`, `req.events()` |
| Other | `notify()`, `timeClose`, `date.add()`, `str.format()`, `str.match()`, `gradient()`, `hsl()`, `math.cosh()`, `math.sinh()`, `math.tanh()` |

### Known limits in 0.5.0

Stated here so you do not find them inside a report you have already believed.

- **A bracket does not fill in a backtest.** `exit()` and `order.bracket()` compile and reach the order destination, but the backtest does not yet fill the target or the stop they set. To test an exit in a backtest, write the condition yourself and call `close()`.
- **Cash and equity-percent sizing is refused in a backtest.** A strategy declared with `qtyType = "cash"` or `"equityPercent"` is refused, because the backtest works out no running equity to size against. Use `"units"` or `"lots"`.
- **A strategy that adds to a position shows a deeper drawdown than it had.** The equity curve marks a trade at its final size from the bar it first opened, so a strategy that scales in is reported as having risked more than it did. The realised profit is right.
- **Fills are modelled from four prices, not a path.** A limit fills only where the bar traded through it, and a stop that the price gapped past fills at the open.
- **A script cannot read its own profit or equity while it runs.** The report shows them after the run.
- **Five order refusals are not raised yet**: an order quantity that is not a multiple of the lot size (OS7005), an order that needs more capital than the strategy has (OS7011), an order outside the session (OS7012), a rejection by the destination reported against the script's line (OS7014), and a strategy with nowhere to send orders (OS7015; such a strategy is refused when it loads, with OS6006, instead). Until they are, size in whole lots, stay within the capital you declared and keep entries inside the session yourself.
- **A second table is dropped.** A chart pane draws one table, so a study that declares two draws the first and says nothing about the second.
- **A plot style cannot be a setting.** `plot(..., style = input(...))` compiles and draws the default style whatever the setting says.
- **The Python engine has no arrays and no log yet.** A program that uses arrays or `print()` is refused by the Python engine, and runs in the JavaScript library.

## Unreleased

Changes since 0.5.0 that are not in a published version yet. None of them changes how a script compiles or what it computes; they concern engine authors.

- The conformance suite (the shared set of test cases every engine must reproduce) gained twenty three cases for its core profile, the smallest set of features an engine can claim. They cover syntax, static checks, runtime errors and limits. Before them, an engine that implemented nothing could pass that profile, because every existing case belonged to the strategy profile and was skipped.
- An engine with no compiler is no longer handed cases about compiler diagnostics.
- What the suite still does not check, said plainly: no case asserts a value on a bar, so two engines can agree on every case and still disagree on what a moving average is.

## 0.5.0

**A strategy can be drawn on a chart.** Until this release a chart could draw a study but not a strategy. The chart adapter can now run a strategy against the same simulated order destination the backtest uses, so its plots draw, its legend and settings dialog work, and its position is right. The entries and exits you see on the price and the trades in the backtest report of the same script are one answer, not two. A host enables it explicitly; without it, a strategy with nowhere to send orders is still refused with OS6006.

**The Python engine is on PyPI.** `pip install openscript` installs the engine that runs a compiled program: Apache 2.0, no dependencies, Python 3.12 or newer. It holds no compiler, so a program is compiled where the JavaScript library runs and handed to it as data. The two packages are released together at the same version.

**The backtest report says more.**

- Run-up: the largest climb in equity, as money and as a percentage, and when it happened, beside the existing maximum drawdown. Every point of the equity curve carries its run-up too.
- A trade analysis: closed trades split into long and short, each side with its own net profit and win rate; the largest win and largest loss after charges; and the longest runs of winning and losing trades, counted in the order the trades closed.
- Every figure in the summary now has a written formula, so an engine written by someone else can reproduce it.

**Changes a script can observe.** Each is a correction, and each is small. A stored backtest that touched one of them reproduces to a slightly different number after the upgrade; nothing else changes.

- `text(x, decimals)` writes the shortest digits that read back to the same value at every size. Only whole numbers at or beyond about nine quadrillion after scaling are affected; no price moves by a digit.
- `round(x, decimals)` scales by the exact power of ten. A value rounded to 23 decimals can move by one unit in the last place; no other count of decimals changes.
- Comparing two strings with `<` and sorting an array of strings both order by Unicode code point. Only a comparison between a character outside the basic plane and one from U+E000 upward changes its answer.
- `str.trim()` and `toNumber()` use a fixed list of whitespace characters. The byte order mark (U+FEFF) is no longer removed, and the next-line character (U+0085) now is.

**Multi-leg strategies are scoped and planned.** A strategy trades one instrument today. The design for several legs, `leg.fixed()` and `leg.relative()` to declare what each leg trades, `leg.*` rules to manage each one and `book.*` rules across all of them, is written and marked planned, and the roadmap now says how it will be built. Two planned names were renamed while nothing could use them: the `arm` parameter of `leg.trail()` and `book.lockProfit()` is now `activateAt`, and the events `trailArmed` and `lockProfitArmed` are now `trailActivated` and `lockProfitActivated`.

**Six missing capabilities are now recorded as planned**, so they are on the plan rather than unnoticed: an account-level drawdown halt, a cap on position size, a cap on orders per session, a halt after a run of losing sessions, reading past trades by index, and a script stating whether it runs on every update or only on closed bars.

**The documentation uses sandbox trading throughout** for the mode that trades against a simulated account.

**For integrators.**

- A program compiled at an earlier minor version of the compiled format now loads in a newer engine; a table the older program lacks reads as empty.
- `loadText` loads a program that arrives as text, and refuses one that is not in the canonical encoding with OS6018.
- How a number becomes text is now one written rule, with published test cases, and the arithmetic of every library function is published as test vectors another engine can load. `pow` is held out, because it gives different last bits on different runtimes.
- A backtest run record now carries the script's source text and the instrument facts, so a stored run can be turned into a conformance case.
- The conformance suite has its first cases, a runner and an adapter for each engine. The Python engine agrees with the JavaScript library on every strategy case, to the last bit, including partial fills, refusals, cancellations and expiries.
- `exp`, `log`, `pow`, the trigonometric functions and the indicators built on them compute what they always did, but carry no cross-engine guarantee in the last bit until a portable algorithm is written.
- Still true, and said here: no engine written outside this project has run the suite yet.

## 0.4.0

**Backtesting arrives, and a run is a document.** `backtest(program, bars, settings)` runs a compiled strategy over a range of bars against a simulated order destination and returns a run record: the program, the hash of its source, the bars or a hash naming them, the settings, every order update, every fill, the ledger, the diagnostics and the report. Nothing in it is specific to one engine.

- **Replay and rerun.** `replay(record)` rebuilds the report from the record's own fills without running a bar, and must match. `rerun(record)` runs the record again and compares the bytes. A replay over bars that have since been revised is refused with OS6022 rather than reporting old figures over new data.
- **Comparing two runs.** `compareRuns` puts two records side by side. Runs over different bars or contracts are reported as not comparable. A changed program over the same bars is comparable, every other difference is named (a changed setting is as often the reason for an improvement as the change you meant to test), and a separation figure says whether the difference in average trade is larger than the noise.
- **The report**: a trade list, an equity curve, drawdown, a month-by-month table, win rate and expectancy with its standard error. A trade runs from the fill that takes the position off flat to the fill that returns it, so adding to a position adds entries to one trade and a reversal is two trades. A trade that nets exactly zero counts as neither a win nor a loss.
- **The report window.** Every bar supplied runs; only the bars inside the window are reported. Bars before it are warmup, and a position opened there is carried in with its charges paid. A window holding none of the bars is refused with OS6020.
- **Costs.** Slippage is counted in ticks and always works against you, on market and stop fills and never on limit fills. A host with its own charge schedule supplies it; a strategy that declares a commission gets a schedule of one line from it; stating both is refused with OS6023.
- **Quantities.** Lots are converted through the instrument's lot size, and lots on an instrument with no lot size are refused. Cash and equity-percent quantities are refused.

Four wrong numbers were fixed before release: a quantity in lots was filled as a raw count of units (a strategy sizing in lots of 65 traded one sixty-fifth of what it asked for); a negative commission was credited instead of refused; a month's return was divided by equity it had already earned; and a profit factor could go negative, which is now reported as empty instead.

The money figures of the `pos` namespace stayed planned in this release, so a script still cannot branch on its own equity.

## 0.3.0

**The editor half: six functions, text in and data out.** These are what every editor feature of OpenScript is built from, and none of them is written by hand: each one asks the compiler.

- `highlight` colours every character of a file exactly once, using the language's own tables, so a new function is coloured the day it is added.
- `diagnose` runs the whole compiler and returns its errors and warnings with the catalogue's message and fix. It answers usefully on a half-typed file, and it is fast enough to run on every keystroke: about a third of a millisecond on a heavy file and about a millisecond on a file with a bracket left open.
- `format` lays a file out in the one standard layout and never changes what it means; every example is formatted, both versions are compiled, and the two compiled programs are compared.
- `complete` offers library names, your own names in scope, the named arguments of the call you are writing and the members of a namespace after a dot. Planned names are offered last, marked with the OS2020 sentence, so you learn they are planned before you use them.
- `hover` shows what a name is: the library's one-line description, the type of your own names, the channels of a colour.
- `signature` shows the call you are writing, which argument you are on, and each parameter's type and the default the compiler actually applies.

The functions are published as the `openalgo-script/editor` entry point, with a drop-in adapter that wires them into a common browser text editor component and draws nothing itself. They are for anyone building an editor: the /trading editor uses the highlighter and the compiler's diagnostics, and does not offer completion, hover or signature help in this release.

## 0.2.0

**The studies surface, finished.** A script compiles in a browser tab in milliseconds and computes, bar by bar, the same numbers everywhere. One hundred and one independently written studies compile, load and run, and five of them match arithmetic taken from the specification alone, bit for bit, warmups included. The chart adapter turns a compiled study into what a chart draws. (At the time, the advice was to upgrade for studies and not to expect a backtest or an editor yet: those arrived in 0.4.0 and 0.3.0.)

**The library runs.**

- Sixty-three names that compiled but could not run now do, among them `dema()`, `tema()`, `vwma()`, `alma()`, `linreg()`, `ma()`, `psar()`, `adx()`, `aroon()`, `ichimoku()`, `stoch()`, `stochRsi()`, `cci()`, `williamsR()`, `keltner()`, `vwap()`, `vwapAnchor()`, the `date.*` calendar calls and `session.isIn()`.
- Every documented default now reaches the call. Before this, `atr()`, `rsi(close)` and twenty-seven other calls written with their defaults compiled and drew nothing.
- Fifty-nine names that cannot run yet are marked planned and refused where you write them, with OS2020, instead of failing when the study loads.
- `toBool(x)` and `toNumber(s)` replace two spellings that clashed with reserved words and could never be called.

**Higher timeframe and other instrument reads run**, in three modes. `"confirmed"`, the default, takes the last coarser bar that closed and never repaints; `"developing"` shows the coarser bar as it stands; `"lookahead"` uses its final value from its first bar, which is why it repaints. `req.timeframe("1D", high)` is therefore the previous completed day's high. A read's warmup is counted in the requested bars. A timeframe finer than the chart is OS6002, one that is not a whole multiple of it is OS6015, and one that is not a timeframe at all is OS6001. When the host refuses a read, the read is absent, `req.error()` carries the host's reason, and the rest of the study keeps drawing.

**Everything a study produces reaches the chart**: markers, bar colours, backgrounds, tables, alerts, drawing objects and reads of other instruments.

- Drawing objects are created, moved, restyled and deleted as bars arrive. Changes made on a bar that is still forming are undone when it runs again, so a chart does not gain a copy per update. Changing an object the script already deleted is OS4005, and the host's ceiling on objects is OS5010.
- Alerts fire for the present and never for history, so adding a study to a chart with two years of bars fires nothing. `"once"` fires once for the life of the study, `"oncePerBar"` once per bar, and `"everyUpdate"` on every update. An alert's message carries the text worked out on the bar that fired it.
- Markers no longer appear and disappear on a bar that is still forming.

**Inputs got simpler.** An `input()` may be written in a declaration option, such as `precision = input(2, "Places")`, inside a larger expression, and inside a read's expression. An input written in place is keyed by its title, so a stored setting stays on its row when you add or reorder inputs; an input with no title is OS3021, an empty title OS3024, and a title that repeats another input's name OS3022. `var len = input(...)` now works as a running value that starts from the setting.

**The compiler says more.** OS8001, the warning for a stateful call that does not run on every bar, now also covers ternary arms, the right side of `and` and `or`, `else if` conditions and later `case` arms, so a file that compiled clean before can show it now. Two alerts sharing an id are OS3017. A `draw` setter given an object that has no such property is OS3011. A colour built from constants, such as `fade(red, 50)`, is accepted wherever a fixed colour is required.

**The order rules are enforced.** Eight order refusals that were documented and raised by nothing are now raised at the call that breaks them, before anything is sent: an absent order argument (OS7002), a zero or negative quantity (OS7004), a price not on a tick (OS7006), a limit or stop order with no price (OS7007), an entry past the pyramiding limit (OS7008), a cancel naming no working order (OS7009), a bracket on the wrong side of the entry (OS7010) and two opposite orders on one bar (OS7013).

- A `close()` naming a tag no order in the file uses is OS7016, and a close stating more than is held is OS7017.
- No order crosses zero: a sell larger than the long position is sent as two orders, one closing and one opening.
- `pos.avgPrice` averages only the positions on the side the strategy holds.
- A strategy's position is worked out from its own fills, never from the account's position in the contract, which may belong to someone else as well.
- The `leg` argument of an order is refused with OS3023, since no file can declare a leg yet.

**Bad data is refused instead of computed on.** No bars is OS6010, a bar whose time does not follow the one before is OS6011, and an instrument record that contradicts itself (a session with no timezone, for example) is OS6012.

Versions 0.1.0-alpha.0 and 0.1.0-alpha.1 were earlier previews that parsed scripts and computed nothing.

## 0.1.0-alpha.1

The first release published by the automated pipeline rather than by hand. It fixed a test runner problem that found no tests on older JavaScript runtimes and reported success anyway, raised the supported JavaScript runtime to version 22, and made the build report both the package version and the compiled program format version.

## 0.1.0-alpha.0

The first publication. **It parsed scripts and computed nothing**: a lexer, a parser and diagnostics with a code, a line, a column and a fix. It told you whether a script was well formed, and could not calculate a moving average, draw or place an order. Behind it were the language specification, a 143-entry error catalogue, the compiled program format, the host interface, a conformance suite design and twelve example scripts, all of which parsed cleanly.

## Roadmap

The project moves in phases, and a phase is finished when its test passes, not when its code is written. No dates are promised here.

### Built

- **The language, the compiler and the bar engine.** Names, types, warmup, history and persistence, with no code generated from text anywhere, and budgets that stop a runaway script.
- **The whole visual surface.** Plots, markers, colours, tables, drawing objects, alerts, and reads of higher timeframes and other instruments.
- **Reproducible backtests.** A stored run reports again to the same figures and runs again to the same bytes, and two runs can be compared well enough to tell an improvement from noise. A check runs this on every build.

### In progress

- **The editor.** The six editor functions are built and published. The /trading editor uses the highlighter and the diagnostics; completion, hover and signature help in it, and a language server that would give desktop code editors the same help, are not written.
- **A second engine and running strategies on a server.** The Python engine is published and agrees with the JavaScript library on every strategy case in the suite. The design runs strategies on a server, never in a browser tab, because closing a tab is not a decision anyone makes about their positions. It adds process isolation per strategy, scheduling against exchange calendars, a log per script, and sandbox trading by default with live orders only as a deliberate act.
- **Alerts.** An alert is evaluated by the chart that is open, so it fires while the chart is open and stops when the chart is closed. Evaluating alerts on a server, so they fire with nothing open, is a phase of its own and is not being built yet.

### Planned

- **An open standard.** A conformance suite anyone can run against an engine written without reading this implementation, and the test that finishes this phase: an engine in a third programming language, written from the specification alone, passing the suite. With it: a converter for scripts written in other chart scripting languages, a versioned compiled format with a compatibility promise, and a conformance badge.
- **Multi-leg strategies and their risk rules.** Declaring legs, fixed contracts or ones described relative to the market such as the at-the-money NIFTY call of the nearest expiry, and managing them as a book: a stop and target on the combined profit, a profit lock, moving every leg's stop to its entry, entry windows, an exit time, a daily loss limit and squaring off at expiry. A combined position has risk that belongs to the combination, not to any one leg, which is why two single-leg strategies are not a substitute. Four questions come first: where the instrument list comes from, what "at the money" means to the tick, whether a leg may be added after the first bar, and what a book-level stop does to a leg that cannot be traded.
- **The six recorded capabilities** from 0.5.0: an account-level drawdown halt, a position size cap, a cap on orders per session, a halt after losing sessions, reading past trades by index, and choosing whether a script runs on every update or only on closed bars.
- **Language features reserved for a later version.** `map` and `matrix` collections, `import` of a shared library file, `type` for user record types, functions as values, and an expression form of `switch`. The words are reserved now so that adding them cannot break a script written today.

### What the project promises from version 1

- **A saved script keeps compiling.** A file declares its language version, and the compiler keeps every past version of the language.
- **Every error is documented**, with a code, a message, a cause and a fix, and the build fails on a code that is not.
- **Engines agree.** A disagreement between the two engines on the conformance suite blocks a release.

Related: [FAQ](/script/resources/faq), [Two libraries](/script/integrate/overview), [Your own engine](/script/integrate/conformance), [Legs and books](/script/strategies/multi-leg-and-books).

