# Language reference

> The precise rules of Flowscope Script, from lexical structure and declarations to types, operators, evaluation order, null semantics and limits.

This page defines the language. It is written to be exact rather than gentle; for
explanations and worked examples, start with the [guides](/docs/scripting/guides).
Every built-in function, constant and record is in the
[built-in catalog](/docs/scripting/reference/catalog), every compiler message in
[diagnostics](/docs/scripting/reference/diagnostics).

Syntax is shown in a compact notation: `name` stands for an identifier, `expr` for
an expression, `T` for a type and `...` for repetition.

## Source files

A script is a single UTF-8 text of at most 1 000 000 bytes. Its first line is the
header:

```flowscope
script "Funding watch"
```

The string is the display name; `script.setTitle(text)` replaces it at run time.
Everything after the header is a sequence of **top-level declarations**. There are
no statements at top level, no imports and no other entry point.

## Lexical structure

### Comments

A comment starts with `//` and runs to the end of the line. There are no block
comments.

### Lines and statements

Statements and declarations end at the end of the line. There are no semicolons and
exactly one statement per line.

A long expression continues on the next line when the line ends with a binary
operator, `?`, `:`, `,`, `(`, `[` or `{`. A line must not begin with an operator, `?`
or `:`.

```flowscope
let body = (chart.close ?? 0.0) - (chart.open ?? 0.0)
let score = body / context.tickSize +
  (chart.volume ?? 0.0) / 1000.0
```

Inside parentheses, brackets and grouped declaration blocks, newlines between
elements are allowed.

### Identifiers

An identifier starts with an ASCII letter or `_` and continues with letters, digits
or `_`. Identifiers are case-sensitive. User type names start with an upper-case
letter. Built-in namespace names (`ta`, `math`, `str`, `color`, `time`, `input`,
`plot`, `fill`, `data`, `context`, `entities`, `log`, `strategy`, …) are predefined.

### Keywords

```text
script   data     input    state    setup    plot     fill
window   pane     alert    type     fn       on
let      if       else     match    for      in       return
true     false    null
```

`self` names the receiver inside methods of a `type`. `strategy(...)` at top level is
the [strategy declaration](/docs/scripting/strategies/declaration).

### Number literals

| Form | Type | Examples |
|---|---|---|
| Digits without a decimal point | `int` (64-bit signed) | `0`, `14`, `1_000_000` |
| Digits with a decimal point | `float` (64-bit IEEE 754) | `1.5`, `0.25`, `1_000.0` |

`_` may separate digit groups. A float literal needs digits on both sides of the
point. A leading `-` is the unary minus operator.

### String literals

Strings are enclosed in double quotes and may not span lines. Escapes: `\n`, `\t`,
`\"` and `\\`; any other is an error. There is no interpolation; use
`str.format("{0} / {1:.2}", a, b)`.

### Boolean and null literals

`true` and `false` have type `bool`. `null` is the absent value; its type comes from
context (see [null semantics](#null-semantics)).

### Color literals

`#RRGGBB` is an opaque colour, `#RRGGBBAA` a colour with alpha (`00` transparent to
`FF` opaque).

### Timeframe and duration literals

A positive integer followed by a unit is a `timeframe` where a timeframe is expected
and a `duration` where a duration is expected:

| Unit | Meaning | Examples |
|---|---|---|
| `s` | seconds | `30s` |
| `m` | minutes | `1m`, `15m` |
| `h` | hours | `1h`, `4h` |
| `D` | days | `1D` |
| `W` | calendar weeks | `1W` |
| `M` | calendar months | `1M` |
| `Y` | calendar years | `1Y` |

Units are case-sensitive: `1m` is one minute, `1M` one month. `60m` normalises to
`1h`. `tf("15m")` converts a string. Calendar units (`W`, `M`, `Y`) are valid for
subscriptions but not as indicator lengths. `0s` is a valid duration: `history: 0s`
means live data only.

### Sessions

A `session` is a daily UTC window, built with `time.session("09:30", "16:00")` or
`time.sessionMinutes(570, 960)`. An end before the start wraps past midnight.

## Declarations

All declarations appear at top level. `input`, `data`, `state`, `setup`, `plot`,
`fill`, `pane` and `alert` also have a **grouped form** with one declaration per line:

```flowscope
input (
  fastLength = input.int(12, title: "Fast", min: 1, max: 200)
  slowLength = input.int(26, title: "Slow", min: 2, max: 400)
)
```

### script

`script "Name"`, exactly once, first.

### input

`input name = input.kind(default, title: ..., ...)` declares a setting, fixed for a
run. Layout rows `input.tab(title:)`, `input.group(title:)` and
`input.section(title:)` are written without a binding. Each effective key (`key:` or
the binding name) must be unique. See
[inputs and visuals](/docs/scripting/guides/inputs-and-visuals).

### data

```text
data name = subscribe(data.source, exchange: ..., symbol: ..., timeframe: ..., history: ..., bucket: ..., minSize: ...)
```

Declares a subscription. Omitted `exchange`, `symbol` and `timeframe` follow the
chart. Arguments must be setup values. `minSize:` applies to `data.trades`, `bucket:`
to `data.vd` and `data.cvd`. See [data and update events](/docs/scripting/guides/data-and-events).

### setup

```text
setup name = expr
setup name = if cond expr
setup name = for k in collection { expr }
```

Computes a value once at start. The `if` form has no `else` and yields `T?`. The
`for` form builds a **family**: a read-only map from each `k` to the body's value,
such as a set of plots.

```flowscope
setup names = ["Fast", "Slow"]
setup lines = for n in names { plot.line(title: n) }
setup onBinance = if context.exchange == "binancef" 1.0
```

### state

`state name = expr` or `state name: T = expr` declares a variable initialised once and
kept across handler calls. The initialiser must be a setup expression. Handlers may
assign only to state.

### plot

`plot name = plot.kind(title: ..., color: ..., on: ..., ...)` declares a plot. Kinds:
`line`, `candle`, `bar`, `histogram`, `point`, `marker`, `arrow`, `label`, `bg`.
Feed it from handlers with `name.plot(value)` (`name.plot(o, h, l, c)` for candles
and bars). Without `on:` a plot draws on the price pane.

### fill

`fill name = fill.between(a, b, color: ...)` shades between two line plots.

### pane

`pane name = pane(title: ..., height: ...)` declares a pane under the price chart
sharing its time axis; `height` is its share of the chart (default `0.3`).

### window

```text
window name = window(title: ...) {
  panelName: chart.panel(...),
  ...
}
```

Declares a separate window with named panels: `chart.panel`, `table.new` or
`heatmap.new`. Panels are addressed as `name.panelName`. See
[custom axes and charts](/docs/scripting/guides/custom-axes-and-charts).

### alert

`alert name = alert(title: ..., onClose: ..., payload: TypeName)` declares an alert;
the binding name is its identity. Fire it from a handler with `name.trigger()` or
`name.trigger(TypeName { ... })`. Alerts fire on live data only, at most once per bar.

### type

```text
type Name {
  field: T
  ...
  fn method(params) -> T { ... }
}
```

Declares a record type, optionally with methods. Values are created with a struct
literal and copied on assignment. See
[user-defined types](/docs/scripting/guides/user-defined-types).

### fn

```text
fn name(param: T, param: T = literal) -> R { statements }
fn name(param: T) -> (R1, R2) { statements }
fn name(param: T) { statements }
```

Parameter types are required; defaults must be literals and follow required
parameters. Each call site of a function that calls `ta.*` keeps its own indicator
state. User functions cannot be called from setup expressions. See
[functions](/docs/scripting/guides/functions).

### on

```text
on sub.open { statements }
on sub.update { statements }
on sub.close { statements }
on sub.trade(name) { statements }
```

`trade` is for `data.trades` subscriptions. `data.book` and `data.volume` have no
handlers. At most one handler per subscription and event.

## Types

### Primitive types

| Type | Values | Notes |
|---|---|---|
| `int` | 64-bit signed integers | Counts, indices, keys |
| `float` | 64-bit floating point | Prices, volumes, ratios |
| `bool` | `true`, `false` | Conditions need a non-null `bool` |
| `string` | UTF-8 text | Immutable |
| `color` | RGBA colour | Literals, `color.*` |
| `time` | A point in time, UTC | `chart.time` |
| `duration` | A span of time | `2h`, `time.days(3)`; `time + duration` is a `time` |
| `timeframe` | A bar period | `4h`, `tf("15m")`, `context.timeframe` |
| `session` | A daily time window | `time.session("08:00", "16:00")` |

Arithmetic operands must have the same numeric type. Convert explicitly with
`float(x)`, `int(x)` (truncates; `null` for non-finite or out-of-range values) and
`string(x)`.

### Nullable types

`T?` is `T` or `null`. Market fields are nullable. A non-null value is accepted where
`T?` is expected; the reverse needs narrowing or `??`.

### Collections

| Type | Constructor | Behaviour |
|---|---|---|
| `T[]` | `float[](n)` | Array with capacity `n`, length 0. Adding beyond capacity stops the script. |
| `rolling<T>` | `rolling<float>(n)` | Fixed window; pushing when full drops the oldest element. |
| `map<K, V>` | `map<string, float>(n)` | Key-value store with capacity `n`. |

- Capacities must be setup values and at most 50 000.
- Setup literals `[1.0, 2.0]` and `{"a": 1}` create read-only collections.
- `float[]` and `rolling<float>` may hold `null`; other element types need non-null
  values.
- Reads (`xs[i]`, `get`, `first`, `last`, `pop`, `shift`) return `T?`.
  `xs[i] = v` requires `i < xs.len`.
- `map`, `filter` and `reduce` take lambdas; see the collections family in the
  [catalog](/docs/scripting/reference/catalog/collections).

### User types

Field types may be any value type, including nullable and other user types, but not
collections. Struct literals give every field. Records are values: assignment and
argument passing copy them.

### Several return values

A function may return a tuple `(T1, T2, ...)`: `return a, b` returns two values, and
the caller destructures them at once with `let a, b = f()`:

```flowscope
fn bands(mid: float, dev: float, mult: float) -> (float, float) {
  return mid + dev * mult, mid - dev * mult
}
```

### Subscription records {#subscription-records}

| Source | Record | Fields |
|---|---|---|
| `data.ohlcv` | `OhlcvSub` | `time open high low close volume buyVolume sellVolume buyCount sellCount trades` |
| `data.oi` | `OiSub` | `time open high low close` |
| `data.vd` | `VdSub` | `time open high low close`, bucket fields |
| `data.cvd` | `CvdSub` | `time open high low close`, bucket fields |
| `data.stat` | `StatSub` | `time sellLiq buyLiq markPrice fundingRate` |
| `data.book` | `BookSub` | Snapshot queries (`data.book.*` methods) |
| `data.volume` | `VolumeSub` | Snapshot queries (`data.profile.*` methods) |
| `data.trades` | `TradesSub` | Delivers `Trade { time, price, size, isBuy }` to `on t.trade(tr)` |

Period records have `[n]` history on their value fields, a `forming` view of the
developing period and status fields (`isNew`, `isRealtime`, `isHistory`, `isFirst`,
`isLast`, `index`). Subscription methods include `atr`, `tr`, `vwap`, `vwma`, `mfi`,
`accDist`, `change` and `delta`. Full lists are in the
[data family](/docs/scripting/reference/catalog/data).

### Constant namespaces

Enumerated arguments are namespaced constants: `linestyle.*`, `shape.*`, `anchor.*`,
`extend.*`, `align.*`, `axis.*`, `font.*`, `easing.*`, `colorspace.*`, `volbasis.*`,
`bookunit.*`, and the strategy constants `strategy.long`, `strategy.short`,
`strategy.fixed`, `strategy.cash`, `strategy.percentOfEquity`,
`strategy.commission.*` and `strategy.oca.*`.

## Operators

From lowest to highest precedence:

| Level | Operators | Meaning |
|---|---|---|
| 1 | `? :` | Conditional (right-associative) |
| 2 | `\|\|` | Logical or, short-circuit |
| 3 | `&&` | Logical and, short-circuit |
| 4 | `==` `!=` | Equality |
| 5 | `<` `<=` `>` `>=` | Ordering |
| 6 | `??` | Null coalescing (right-associative) |
| 7 | `+` `-` | Add, subtract |
| 8 | `*` `/` | Multiply, divide |
| 9 | `!` `-` (unary) | Not, negate |
| 10 | `.` `f( )` `[ ]` | Member access, call, index and history |

Rules:

- `??` binds tighter than comparisons: `x ?? 0.0 > 5.0` is `(x ?? 0.0) > 5.0`.
- `??` mixed with `&&` or `||` needs parentheses: `(x ?? false) && y`.
- There is no `%`, no bitwise operators, no `**` (use `math.pow`) and no `++`.
- `&&`, `||` and `!` take non-null `bool` operands.
- `..` appears only in `for` ranges.
- Assignment is a statement, not an expression.

## Statements

### let

```text
let name = expr
let name: T = expr
let a, b = f()
```

Binds an immutable local for the rest of the block. An annotation is needed when the
initialiser does not fix the type, for example `let x: float? = null`.

### Assignment

`target = expr`, `+=`, `-=`, `*=`, `/=`. The target must be a `state` binding, a
field path rooted in one, an element of a state collection or, inside a method,
`self` or one of its fields.

### if and else

`if cond { ... } else if cond { ... } else { ... }`. `cond` must be a non-null
`bool`; braces are required. Null checks narrow locals in the branch.

### for

```text
for x in collection { ... }
for i in a..b { ... }
for k, v in map { ... }
```

The range form counts from `a` up to but not including `b`. There is no `while`,
`break` or `continue`. Do not call `ta.*` inside a loop: each call site must run
exactly once per bar.

### match

```text
match subject {
  pattern => statement
  pattern => { statements }
}
```

Arms are tried top to bottom; one arm per line, no commas. Patterns: literals and
constants (`0`, `"bybitf"`, `bookunit.base`), comparisons (`> 0.0`, `<= -2.0`),
`null`, alternatives joined with `|` (`0 | 1 | 2`), and `_` for anything. Patterns
do not bind names or take guards. The statement form need not be exhaustive; the
expression form must be.

### return

`return`, `return expr` or `return a, b` leaves the function, method or handler.

### Expression statements

A call whose result is unused may stand alone: `p.plot(v)`, `log.info("ready")`,
`xs.push(v)`, `strategy.entry("L", strategy.long)`.

## Expressions

### Calls and named arguments

Positional arguments first, then named arguments in any order:
`ta.ema(chart.close, 20)`, `input.int(14, title: "Length", min: 1)`.

### Conditional and coalescing

`cond ? a : b` evaluates only the chosen branch. `a ?? b` yields `a` unless it is
null; `b` is evaluated only when needed.

### match expressions

```flowscope
fn regime(rate: float?) -> string {
  return match rate {
    null => "unknown"
    > 0.0005 => "crowded long"
    < -0.0003 => "crowded short"
    _ => "neutral"
  }
}
```

### Struct literals

`TypeName { field: expr, field: expr }`; every field must be given.

### Lambdas

`|x| expr` and `|acc, x| expr`, as arguments to collection methods such as `map`,
`filter` and `reduce`. The body is one expression and must be pure: no `ta.*`, no
plotting or drawing, no state assignment.

### Indexing and history

| Form | Meaning |
|---|---|
| `xs[i]` | Element `i` of a collection, `T?` |
| `sub.field[n]` | A subscription field `n` periods back, `T?` |

History applies to subscription value fields, not to locals or state; keep your own
history in a `rolling<T>`.

## Handlers and evaluation order {#handlers-and-evaluation-order}

| Handler | Runs | Own fields read |
|---|---|---|
| `on x.open` | Once when a period of `x` starts | The forming period |
| `on x.update` | When the forming period changes; once per period in history | The forming period |
| `on x.close` | Once when a period of `x` is confirmed | The period just confirmed |
| `on t.trade(tr)` | Once per trade, in time order | `tr` is the trade |

- In `x`'s own `open` and `update` handlers, `x.field` reads the forming period; in
  its `close` handler, the period just confirmed.
- In handlers of other subscriptions, `x.field` reads `x`'s last **confirmed**
  period and `x.forming.field` its developing one.

### Execution phases

1. **Setup.** Inputs, setup values, state initialisers and surfaces, once.
2. **History.** Loaded history is delivered bar by bar to the same handlers, in time
   order.
3. **New data.** On a chart, the script runs again over the history when new data
   arrives, at most every 0.75 s, so the newest bars reach the same handlers.

### Status fields

| Field | True when |
|---|---|
| `x.isHistory` | During history replay |
| `x.isRealtime` | While the forming period receives live updates |
| `x.isFirst` | On the first loaded period |
| `x.isNew` | In an update in which `x` confirmed a period |
| `x.isLast` | In an update of the newest, live period; false in close handlers |
| `x.index` | Period number from the first loaded period, from 0 |

## Setup and eval contexts

| | Setup | Eval |
|---|---|---|
| Where | `input`, `data`, `setup`, `state` initialisers, surfaces, `alert`, `strategy`, parameter defaults | Handler, function and method bodies |
| When | Once, at start | On every event |
| Inputs, setup values, `context.*`, pure helpers | yes | yes |
| Array and map literals | yes (read-only) | no; use constructors |
| Surface and plot constructors | yes | no |
| Subscription reads, `ta.*`, user `fn` calls | no | yes |
| Plotting, drawing, alerts, orders, `log.*`, state assignment | no | yes |

## Null semantics {#null-semantics}

1. Every type `T` has a nullable form `T?`. Market fields and collection reads are
   nullable.
2. Arithmetic with a `null` operand yields `null`.
3. Ordering comparisons with a `null` operand yield `false`.
4. `a == b` with one side `null` is `false`; `a != b` is then `true`.
5. `a ?? b` replaces `null` with `b`.
6. Conditions need a non-null `bool`: test for null or use `?? false`.
7. After `if x != null`, the local `x` is narrowed to `T`. Narrowing applies to
   locals and parameters, not to subscription fields, state or record fields; copy
   those to a local first.
8. A bare `null` takes its type from context; `c ? null : null` is an error.
9. Plotting `null` leaves a gap.
10. There is no `na()`, `nz()`, `?.` or postfix `!`.

## Limits {#limits}

| Limit | Value |
|---|---|
| Source size | 1 000 000 bytes |
| Inputs | 100 |
| Plots | 200 |
| Fills | 50 |
| Alerts | 50, with at most 16 payload fields |
| Subscriptions | 30 |
| User types | 128, at most 64 fields, nested at most 16 deep |
| Collection capacity | 50 000 |
| Map key length | 64 bytes |
| Indicator length (`ta.*`) | 10 000 |
| Nesting of expressions and blocks | 128 levels |
| Steps per update | 10 000 000 |
| Steps over the history replay | 100 000 000 (25 000 000 in the browser) |
| Steps of the setup (all declarations) | 2 000 000 |
| Function calls nested | 64 |
| String length | 1 MB |
| Memory held in values | 256 MB |
| Entities per pool | 5 000 |
| Table rows / columns | 5 000 / 64 |
| Heatmap cells | 250 000 |
| Log lines kept | the newest 1 000 |

Exceeding a static limit is a compile error. Exceeding a capacity, a budget or a
size at run time stops the script with an error that names it (`budget_exceeded`,
`call_depth`, `string_too_long`, `memory_exceeded`). Scripts are shared between
traders, so these limits keep any script, however written, from stalling or
crashing the terminal. See [performance](/docs/scripting/performance).

## See also

- [Built-in catalog](/docs/scripting/reference/catalog): every function, method,
  constant and record.
- [Diagnostics](/docs/scripting/reference/diagnostics): every compiler message, with a
  failing and a fixed script.
- [Guides](/docs/scripting/guides): explanations and complete examples.
