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, explicitlybool
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 failsCore 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 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).
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;andlet cursor: i32 = 0;read as documentation and stop an integer literal from silently making a variable ani32. - Keep decisions as outputs. A
boolyou want to draw or alert on is a0.0/1.0output declarednone; the sheet turns it into a look. - Let
NaNmean missing. Never write0for "not ready"; a zero is drawn and alerted on as a real value.