---
title: "Data types"
description: "The types an Indicator is built from. The file is AssemblyScript, TypeScript syntax over fixed-width numbers, so every value has a declared width and the…"
order: 12
section: "core-concepts"
---

<!-- source: docs/indicators/core-concepts/data-types.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Data types

The types an Indicator is built from. The file is AssemblyScript, TypeScript
syntax over fixed-width numbers, so every value has a declared width and
the compiler names a mismatch before anything runs. Where kScript (legacy)
inferred one `number` type and carried `na`, `color`, and `TimeSeries` as
values, an Indicator has two numeric widths, a boolean, module-internal
strings, fixed-size arrays, and classes, and a missing value is simply
`NaN`.

## Primitive types

### `f64`

A 64-bit float: every param, input, and output crosses the host boundary
as one. Prices, volumes, scores, ratios, and decisions (`0.0` or `1.0`)
are all `f64`. A literal with a decimal point is an `f64`; a whole-number
literal is not, so annotate or write the point.

```text
let price: f64 = 45000.5;
let volume: f64 = 1000.0;   // 1000 alone would be an i32
let value: f64 = NaN;       // "no value yet"
```

### `i32`

A 32-bit integer: a period, a ring-buffer cursor, a loop counter, the
`state()` return value, and what `Cross.update()` answers. Params arrive as
`f64`, so a period becomes an integer with an explicit cast:
`new Sma(i32(p_period()))`. Integer division truncates: `7 / 2` on two
`i32` values is `3`.

```text
let period: i32 = 20;
let cursor: i32 = 0;
const bars = i32(p_period());   // f64 to i32, explicitly
```

### `bool`

`true` or `false`, from comparisons (`<`, `>`, `==`, `!=`) and logical
operators (`&&`, `||`, `!`). A `bool` never leaves the module on its own: a
decision becomes an output by turning it into a number, usually with a
ternary.

```text
const isUptrend: bool = fast > slow;
out_is_uptrend(isUptrend ? 1.0 : 0.0);
```

### `string`

Text lives inside the module: labels you build for a text renderer, keys in
a `Map`, comparisons of two literals. Strings never become params, inputs,
or outputs; the only way out is a declared string slot written in
`finalize()` (`functions/plotting.md`). Building a string allocates, and
the module never frees memory, so per-bar text goes through the
allocation-free line builder (`sb_text`, `sb_f64`) rather than `+`.
Concatenating a number needs `.toString()`: `"close " + close` is refused
with `Type 'f64' is not assignable to type 'String'`.

### `NaN`, not `na`

There is no missing-value type. A missing `f64` is `NaN`: every TA class
returns it until its window is warm, and writing it to an output draws a
gap. Test it with `isNaN(x)`; `x == NaN` is always false. Arithmetic with
`NaN` stays `NaN`, exactly like kScript's `na` contagion
(`na-and-scalar-types.md`).

```text
out_filtered(condition ? value : NaN);   // a gap where the condition fails
```

## Core Indicator types

### The per-bar value

kScript's central type was `TimeSeries`: a whole history you indexed with
`[0]`, `[1]`, `[n]`. An Indicator has no such type. An input is a reader
function that returns this bar's value, and the host calls `state()` once
per bar, oldest first. History is whatever you keep:

- a **remembered value** (`prevClose`) for `[1]`;
- a **TA class** (`Sma`, `Ema`, `Rsi`, ...) for anything windowed, since
  each one keeps its own window;
- a **ring buffer** (`StaticArray<f64>` plus a cursor) when you need the
  last N values yourself.

Inputs are read-only by construction (`in_close()` returns a number), and
they are read in `state()` only. The full model is
`core-concepts/execution-model.md`.

### `StaticArray<f64>`

A fixed-size array allocated once, the workhorse for windows. Size it from
a param's declared `max` at module scope or in `init()`, never per bar.
`Array<f64>` (growable) and `Map<K, V>` exist too (`collections.md`).

```text
const MAX_BARS = 200;
const window = new StaticArray<f64>(MAX_BARS);
```

### `class`

Your own typed structs with methods, the counterpart of kScript's `type`
(`user-defined-types.md`).

## Input and configuration types

### Params are numbers

Every `param` is an `f64` with a literal default and an optional `min` /
`max`. There is no `select`, `boolean`, `color`, or `string` input type:

| kScript input type | Indicator form |
| --- | --- |
| `number`, `slider` | `param("period", 20, { min: 1, max: 200 })` |
| `boolean` | `param("show_open", 1, { min: 0, max: 1 })`, tested as `!= 0.0` |
| `select` with options | a numeric index param (`0` = line, `1` = bar) and a `switch` in the code |
| `color` | none: color is declared on the output, box, or segment |
| `string` (a symbol) | none: a venue is a pinned declaration, `input(..., { symbol, exchange })` |

### Data sources are members

kScript named a source with a string (`source("funding_rate", ...)`). An
Indicator names it as a member of a source namespace, and the field is
part of the reference:

| Identifier | Description |
| --- | --- |
| `ohlcv.close` (and `open`, `high`, `low`, `volume`) | Price and volume |
| `funding.rate_close` (and `rate_open`, `predicted_close`, ...) | Funding rates |
| `liquidations.liquidations` | Liquidation volume, optionally by `side` |
| `oi.close` | Open interest |
| `trades.volume` with `side: "BUY"` or `"SELL"` | Side-split trade volume |
| `time.bar_open_sec` | The bar's open time in epoch seconds |

The complete catalog, celled classes included, is `data-sources.md`.

### Placement is per output

kScript placed the whole script `onchart` or `offchart` in `define()`. An
Indicator places each output: `overlay` (the price pane) or `lower` (its
own pane), as the third argument of `output(...)`.

## Visual and plotting types

### Color

A color is a string literal in a declaration: `#rrggbb`, `#rrggbbaa`,
`rgb()`, `hsl()`, or a named color on an output. It is never a runtime
value; there is no color variable, and a per-bar color is a data-only
output indexing a declared `colors` palette through `color_by`
(`color-constants.md`).

```text
output("fast", line, overlay, { color: "#FF6B35" });
output("mid", line, overlay, { color_by: "regime", colors: ["#ef4444", "#22c55e"] });
```

### Plot and shape kinds

The second argument of `output(...)` is the plot kind: `line`, `bar`,
`area`, `histogram`, `candle`, `shape`, `scatter`, or `none` (data-only).
A `shape` output draws a mark at its value; the mark's kind is the host's
default, and `render.shape` picks one of `circle`, `cross`,
`triangle_up`, `triangle_down`, `diamond`, `arrow_up`, `arrow_down`,
`flag`, `square` (`functions/plotting.md`).

## Practical examples

### Colors for multi-line plots

kScript shared one color array across plots with `colorIndex`. An
Indicator declares each line's color on its output, and the palette lives
in the file's declarations:

```typescript
import { input, line, ohlcv, output, overlay } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_sma10, out_sma20, out_sma50 } from "./gen/outputs";
import { Sma } from "./sdk/ta";

input("close", ohlcv.close);
output("sma10", line, overlay, { color: "#FF6B35", width: 2, description: "10-period simple moving average" });
output("sma20", line, overlay, { color: "#3B82F6", width: 2, description: "20-period simple moving average" });
output("sma50", line, overlay, { color: "#10B981", width: 2, description: "50-period simple moving average" });

let sma10 = new Sma(10);
let sma20 = new Sma(20);
let sma50 = new Sma(50);
let v10: f64 = NaN;
let v20: f64 = NaN;
let v50: f64 = NaN;

export function init(): void {}

export function state(): i32 {
  const close = in_close();
  v10 = sma10.update(close);
  v20 = sma20.update(close);
  v50 = sma50.update(close);
  // Each line starts when its own window is warm: the shorter averages draw first.
  return isNaN(v10) ? 0 : 1;
}

export function finalize(): void {
  out_sma10(v10);
  out_sma20(v20);
  out_sma50(v50);
  emitRow();
}

export function reset(): void {
  sma10.reset();
  sma20.reset();
  sma50.reset();
  v10 = NaN;
  v20 = NaN;
  v50 = NaN;
}
```

The three periods are fixed here, so `init()` has nothing to read; the
row is ready as soon as the fastest average is, and the slower two write
`NaN` (a gap) until their own windows fill.

### Conditional plotting with `NaN`

Combine a condition with `NaN` to draw a value only when the condition
holds, the exact shape of kScript's `condition ? value : na`:

```typescript
import { histogram, input, line, lower, ohlcv, output, overlay, param } from "./sdk/declare";
import { in_close, in_volume } from "./gen/inputs";
import { emitRow, out_filtered, out_volume } from "./gen/outputs";
import { p_limit } from "./gen/params";

param("limit", 50, { min: 0, max: 1000000, description: "Volume a bar must exceed to be drawn" });
input("close", ohlcv.close);
input("volume", ohlcv.volume);
output("filtered", line, overlay, { color: "#dc2626", width: 2, description: "Close, only on bars whose volume exceeds the limit" });
output("volume", histogram, lower, { color: "#94a3b8", description: "Volume" });

let limit: f64 = 50.0;
let close: f64 = NaN;
let volume: f64 = NaN;

export function init(): void {
  limit = p_limit();
}

export function state(): i32 {
  close = in_close();
  volume = in_volume();
  return 1;
}

export function finalize(): void {
  // NaN draws nothing: the line breaks wherever the condition fails, and the volume pane still draws.
  out_filtered(volume > limit ? close : NaN);
  out_volume(volume);
  emitRow();
}

export function reset(): void {
  close = NaN;
  volume = NaN;
}
```

Writing `NaN` to one output leaves the row ready (the other outputs still
draw); returning `0` from `state()` would abstain the whole row. Both are
correct; pick by whether anything on the row is meaningful.

## Type conversion and indexing

kScript exposed a candle as six indexed columns (`0` timestamp through `5`
volume). An Indicator declares one input per field it reads (`ohlcv.open`,
`ohlcv.high`, `ohlcv.low`, `ohlcv.close`, `ohlcv.volume`) and reads the
timestamp from its own source, `time.bar_open_sec` (seconds, not
milliseconds). There is no `priceIndex` argument anywhere: a class takes
the number you hand it. Casts are explicit (`i32(x)`, `f64(n)`) and
indexing a number is refused (`type-system.md` lists the messages).

## Best practices

- **Name by what it is.** `prevClose`, `windowHigh`, `barsSeen`: a module-
  level variable's name should say what it carries across bars.
- **Annotate module-level state.** `let value: f64 = NaN;` and
  `let cursor: i32 = 0;` read as documentation and stop an integer literal
  from silently making a variable an `i32`.
- **Keep decisions as outputs.** A `bool` you want to draw or alert on is a
  `0.0` / `1.0` output declared `none`; the sheet turns it into a look.
- **Let `NaN` mean missing.** Never write `0` for "not ready"; a zero is
  drawn and alerted on as a real value.
