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…

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.

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.

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.

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).

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).

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 typeIndicator form
number, sliderparam("period", 20, { min: 1, max: 200 })
booleanparam("show_open", 1, { min: 0, max: 1 }), tested as != 0.0
select with optionsa numeric index param (0 = line, 1 = bar) and a switch in the code
colornone: 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:

IdentifierDescription
ohlcv.close (and open, high, low, volume)Price and volume
funding.rate_close (and rate_open, predicted_close, ...)Funding rates
liquidations.liquidationsLiquidation volume, optionally by side
oi.closeOpen interest
trades.volume with side: "BUY" or "SELL"Side-split trade volume
time.bar_open_secThe 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).

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:

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:

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.