Frequently asked questions about Indicators, grouped the way the kScript (legacy) FAQ is grouped: getting started, data sources and context, technical indicators, plotting, troubleshooting, language features, performance, and the error messages people search for. Every answer names the Indicator form of the thing the kScript question was about.
Getting started
What is an Indicator? One TypeScript file. You write it in the chart's editor, Run compiles it in your browser and draws it on the chart, and the same file publishes to the registry and runs on your own machine unchanged. Every output is a number series: a line, a shape, a box coordinate, and a metric a watch, a screen, or a backtest can read. The module has no filesystem, no network, and no order capability. First steps walks through the first one.
Do I need to know TypeScript? The file is AssemblyScript: TypeScript syntax over fixed-width numbers. You need about a page of it: let x: f64 = NaN declares a number, i32(...) turns a float into a whole number, function, if, for, and class work as in TypeScript, and there is no any, no closures capturing locals, and no dynamic typing. The sma-codefirst template is 25 non-blank lines with a comment on every statement; if you can read it, you can write one. If you know kScript (legacy) or Pine, the construct table in From kScript is the fastest way in.
What is the difference between an Indicator and a kScript? A kScript calls: plotLine(...), box.new(...), alert(...). An Indicator declares what it reads and writes at the top of the file and computes numbers in four functions; the host draws from the declarations. That is what lets one file compile in the browser, run on your machine, and expose every output as a metric id. What Indicators add is the full list.
Data sources and context
What replaced currentSymbol and currentExchange? Nothing you write. An input("close", ohlcv.close) with no pin follows the market the Indicator is evaluated on: the chart's own market in the browser, the --symbol and --exchange you pass on your machine. A fixed market is a pinned input, input("btc", ohlcv.close, { symbol: "BTCUSDT", exchange: "BINANCE_FUTURES" }), honored on your machine (Data sources).
Why is the start of my line empty? The TA classes return NaN until their window fills, and a state() that returns 0 abstains the row. That is warm-up, not a bug: a 200-bar average has no value on bar 50. Load more history if the line never appears, and read Execution model for the two warm-up policies.
Can I read the previous bar? Not with an index: there is no close[1], and state() sees exactly one bar. Keep the value in a module-level variable when you see it; a window is a StaticArray<f64> you fill as a ring buffer, sized from the param's max so it is allocated once:
import { input, line, lower, ohlcv, output, overlay, param } from "./sdk/declare";
import { in_close, in_high } from "./gen/inputs";
import { emitRow, out_change, out_window_high } from "./gen/outputs";
import { p_bars } from "./gen/params";
param("bars", 5, { min: 2, max: 50, description: "Bars in the trailing window" });
input("close", ohlcv.close);
input("high", ohlcv.high);
output("change", line, lower, { description: "Close minus the previous bar's close" });
output("window_high", line, overlay, { description: "Highest high of the trailing window" });
const MAX_BARS = 50;
const highs = new StaticArray<f64>(MAX_BARS);
let n: i32 = 5;
let cursor: i32 = 0;
let count: i32 = 0;
let prevClose: f64 = NaN;
let change: f64 = NaN;
let windowHigh: f64 = NaN;
export function init(): void {
n = i32(p_bars());
}
export function state(): i32 {
const close = in_close();
// The previous close is whatever we kept from the previous call: there is no close[1] to read.
change = isNaN(prevClose) ? NaN : close - prevClose;
prevClose = close;
highs[cursor] = in_high();
cursor = (cursor + 1) % n;
if (count < n) count += 1;
if (count < n || isNaN(change)) return 0;
let h = -Infinity;
for (let i = 0; i < n; i++) if (highs[i] > h) h = highs[i];
windowHigh = h;
return 1;
}
export function finalize(): void {
out_change(change);
out_window_high(windowHigh);
emitRow();
}
export function reset(): void {
cursor = 0;
count = 0;
prevClose = NaN;
change = NaN;
windowHigh = NaN;
}Can I analyze multiple markets in one Indicator? Yes: one pinned input per market, symbol and exchange always together. The first input stays unpinned and defines the grid; the pinned ones align to it. On the chart every input reads the chart's own market, so a multi-market package is a package for your machine:
import { input, line, lower, ohlcv, output } from "./sdk/declare";
import { in_btc_close, in_close, in_eth_close } from "./gen/inputs";
import { emitRow, out_eth_btc, out_vs_btc } from "./gen/outputs";
// The first input is the grid: it follows the market the Indicator is evaluated on.
input("close", ohlcv.close);
// Pinned inputs read fixed markets; symbol and exchange always pin together.
input("btc_close", ohlcv.close, { symbol: "BTCUSDT", exchange: "BINANCE_FUTURES" });
input("eth_close", ohlcv.close, { symbol: "ETHUSDT", exchange: "BINANCE_FUTURES" });
output("eth_btc", line, lower, { description: "ETH priced in BTC" });
output("vs_btc", line, lower, { unit: "%", description: "This market's close as a percent of BTC's" });
let ethBtc: f64 = NaN;
let vsBtc: f64 = NaN;
export function init(): void {}
export function state(): i32 {
const btc = in_btc_close();
if (isNaN(btc) || btc <= 0.0) return 0;
ethBtc = in_eth_close() / btc;
vsBtc = (in_close() / btc) * 100.0;
return 1;
}
export function finalize(): void {
out_eth_btc(ethBtc);
out_vs_btc(vsBtc);
emitRow();
}
export function reset(): void {
ethBtc = NaN;
vsBtc = NaN;
}Can I write an Indicator without a data source? No. The build refuses a file with no input(...) or no output(...) by name (code-first declarations need at least one input(...) and one output(...) statement), because the first input is what defines the bar grid the module walks. A constant you want to draw is an output written to the same value on every bar of a real feed.
Can I declare inputs inside if, a loop, or a function? No. Declarations are read from the text before anything runs, so they are top-level statements of the entry file only; one inside a function is refused with input(...) declarations must be top-level statements, not inside a function, class, or expression. Every input is fetched before the first bar, so there is no such thing as a conditional subscription.
How do I read the order book? Declare a celled input over the book class, input("book", book.cells, { max_cells: 80, block_size: 10, max_depth: 40 }), and scan its [price, size, side] tuples in state() through the generated in_book_cells() and in_book_read(ptr) accessors. block_size is the venue's price-bucket width (om block-sizes lists them). Orderbook functions has the bid and ask sums as worked scans.
How does an Indicator handle data gaps? With a declared policy per input rather than interpolation. A secondary input carries its last value forward by default (missing: "carry"), delivers NaN on bars without an observation under missing: "nan", or 0 under missing: "zero". Line plots do not interpolate across NaN; a gap is a gap. When you want your own fill, do it in state():
import { input, line, liquidations, lower, ohlcv, output } from "./sdk/declare";
import { in_liqs } from "./gen/inputs";
import { emitRow, out_last_liq, out_liq } from "./gen/outputs";
input("close", ohlcv.close);
// A sparse feed: bars with no liquidation deliver NaN instead of repeating the last value.
input("liqs", liquidations.liquidations, { missing: "nan" });
output("liq", line, lower, { description: "Liquidation volume this bar, 0 on quiet bars" });
output("last_liq", line, lower, { description: "The most recent liquidation volume, carried forward" });
let liq: f64 = 0.0;
let lastLiq: f64 = NaN;
export function init(): void {}
export function state(): i32 {
const x = in_liqs();
if (isNaN(x)) {
liq = 0.0; // your own zero policy
} else {
liq = x;
lastLiq = x; // your own forward fill
}
return 1;
}
export function finalize(): void {
out_liq(liq);
out_last_liq(lastLiq);
emitRow();
}
export function reset(): void {
liq = 0.0;
lastLiq = NaN;
}On the primary input, "nan" and "zero" do more: they densify the grid, so a naturally sparse feed becomes one row per bar (Data sources).
Technical indicators
Why am I getting NaN in my calculations? Three usual causes: a TA class that has not warmed up yet, a division by zero, or an input under missing: "nan" on a bar with no observation. Check with isNaN(x) (there is no isnum; !isNaN(x) is the same test), and decide per value: abstain the row, write NaN to that output, or substitute. A two-line nz helper covers the substitution case (Best practices).
Which TA builtins exist? The whole kScript TA catalog, one stateful class per builtin in ./sdk/ta (Sma, Ema, Rsi, Atr, Bb, Macd, Stoch, Adx, Supertrend, an anchored Vwap, 52 in all), each one matching the kScript engine bar for bar. The TA library lists every class with its constructor, update() arguments, and fields.
How do I use a param as a period? Read it in init() and cast it: sma = new Sma(i32(p_period())). Params are f64; a period is i32; the cast is explicit and the compiler refuses to guess.
Plotting and visualization
How do I draw several lines on the same chart? One output(...) per line. overlay puts it on the price pane, lower in its own pane; color, width, opacity, and description are per output (Plotting).
Can I plot conditional signals? Yes: a shape output draws a mark at its value only on bars where a second, data-only output named in shape_where is nonzero. The decision is a number:
import { input, line, lower, none, ohlcv, output, overlay, param, shape } from "./sdk/declare";
import { in_close, in_low } from "./gen/inputs";
import { emitRow, out_buy, out_oversold, out_rsi } from "./gen/outputs";
import { p_len, p_level } from "./gen/params";
import { Rsi } from "./sdk/ta";
param("len", 14, { min: 2, max: 200 });
param("level", 30, { min: 5, max: 50, description: "RSI at or below this counts as oversold" });
input("close", ohlcv.close);
input("low", ohlcv.low);
output("rsi", line, lower, { color: "#a78bfa" });
// The mark sits at the output's value (this bar's low) and draws only on bars where the gate is 1.
output("buy", shape, overlay, { color: "#22c55e", shape_where: "oversold" });
output("oversold", none);
let rsi = new Rsi(14);
let level: f64 = 30.0;
let value: f64 = NaN;
let low: f64 = NaN;
export function init(): void {
rsi = new Rsi(i32(p_len()));
level = p_level();
}
export function state(): i32 {
low = in_low();
value = rsi.update(in_close());
return isNaN(value) ? 0 : 1;
}
export function finalize(): void {
out_rsi(value);
out_buy(low);
out_oversold(value <= level ? 1.0 : 0.0);
emitRow();
}
export function reset(): void {
rsi.reset();
value = NaN;
low = NaN;
}How do I change plot colors dynamically? With a palette and a decision: color_by names a data-only output, and each bar's value indexes colors (floored; a missing or out-of-range value falls back to entry 0). The kScript's colorIndex= is this exact shape:
import { input, line, none, ohlcv, output, overlay, param } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_price, out_trend } from "./gen/outputs";
import { p_fast, p_slow } from "./gen/params";
import { Sma } from "./sdk/ta";
param("fast", 10, { min: 1, max: 200 });
param("slow", 30, { min: 2, max: 400 });
input("close", ohlcv.close);
// Each bar's trend value (0 or 1) indexes the palette: red below the slow average, green above.
output("price", line, overlay, { width: 2, color_by: "trend", colors: ["#ef4444", "#22c55e"] });
output("trend", none);
let fast = new Sma(10);
let slow = new Sma(30);
let close: f64 = NaN;
let trend: f64 = 0.0;
export function init(): void {
fast = new Sma(i32(p_fast()));
slow = new Sma(i32(p_slow()));
}
export function state(): i32 {
close = in_close();
const f = fast.update(close);
const s = slow.update(close);
if (isNaN(s)) return 0;
trend = f > s ? 1.0 : 0.0;
return 1;
}
export function finalize(): void {
out_price(close);
out_trend(trend);
emitRow();
}
export function reset(): void {
fast.reset();
slow.reset();
close = NaN;
trend = 0.0;
}Can I write an output inside a conditional or a loop? The writers are ordinary calls in finalize(), so if (...) out_x(a); else out_x(b); is fine, and so is computing a value in a loop and writing it once. Each output holds one value per bar: the last write before emitRow() wins, so writing the same output in a loop is a longer way of writing it once. To draw nothing for one output on a bar, write NaN to it.
What is the difference between line, area, histogram, and scatter? They are the plot kinds an output can declare, and they change the look, not the value: line joins the points, area fills under them, histogram and bar draw a column per bar, scatter draws a point per bar, candle draws four outputs as a candle, shape draws a gated mark, and none computes without drawing. Plotting lists the options each one takes.
How does positioning work for shapes and text? A shape output draws at (this bar, its value); there is no separate location argument, so put the mark at the bar's low or high by writing that price. Text comes from string slots: render.text draws one mark per bar at (bar, a named output), render.label draws ONE label at (x, y) where x is an output in epoch seconds (the time source), and boxes and segments place themselves by bar offsets from the current bar (Drawing objects).
My line hugs the bottom of the price chart. It is a small-magnitude series drawn on the price axis. Declare it lower.
My box draws with no fill. Its color is a named color, or the opacity is 0. Use hex, rgb(), or hsl(); the default opacity is 0.2.
My mark draws on every bar. A shape output without shape_where draws wherever it has a value. Add a data-only gate output and name it in shape_where; write 0 to the gate on quiet bars.
Common issues and troubleshooting
My Indicator isn't displaying anything. What's wrong? Check, in this order: the Problems lane (a build that stopped at any stage draws nothing); whether state() ever returns 1 (a TA class that needs more bars than the chart loaded keeps it at 0); whether the output you expect is declared none; whether the chart's market serves the source the input reads (the chart says so by name); and whether a symbol or interval pin is being ignored because you are on the chart. Debugging is the full workflow.
Undefined identifier errors. Cannot find name 'out_sma' and Module 'src/gen/outputs' has no exported member mean the accessor no longer exists: you renamed or removed the declaration. Accessors are generated from the declarations, one per name, lowercased with every run of characters outside [a-z0-9_] collapsed to _: param("fast.len", ...) is p_fast_len(), input("BTC-Close", ...) is in_btc_close(). Update the import and the call. A plain variable you forgot to declare is the same message from the compiler.
How do I debug my Indicator? There is no print(). Declare the suspect value as an output, draw it in the lower pane or keep it none so it shows in the legend and as a metric, and on your machine read it as numbers with om metric series. Debugging has the workflow and the usual suspects.
Why isn't my Indicator updating in real time? The forming bar is re-evaluated on every tick from a snapshot taken after the last closed bar, and reset() is called first. A field reset() forgets is a stale value that replay reads, which looks like a line that lags or freezes. Reassign every module-level variable and call .reset() on every TA object.
Can I use null instead of NaN for missing data? No. Outputs are f64, and f64 has no null: NaN is the one value that means "nothing here", and the chart draws a gap for it. Writing 0 draws a zero. isNaN(x) is the test, and there is no null to compare against.
The chart ignores my symbol or interval pin. On the chart every input reads the chart's own market and interval; pins are honored on your machine (om wrun install, then om metric series or a watch). A pinned package is a package for your machine (Execution model).
What does the New engine badge mean? That kScript (legacy) row has a proven Indicator port. Adding it mounts the Indicator; Use kScript engine adds the kScript version instead.
Language features and syntax
Does it support switch statements? Yes, over integers, with the same fallthrough rules as TypeScript (write break). Bucket a float into a small integer first, then branch:
import { input, line, lower, ohlcv, output, param } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_score } from "./gen/outputs";
import { p_len } from "./gen/params";
import { Rsi } from "./sdk/ta";
param("len", 14, { min: 2, max: 200 });
input("close", ohlcv.close);
output("score", line, lower, { description: "-1 oversold, 0 neutral, 1 overbought" });
let rsi = new Rsi(14);
let score: f64 = 0.0;
// A switch runs over integers: bucket the value first, then branch. Each case breaks.
function zone(value: f64): i32 {
if (value <= 30.0) return 0;
if (value >= 70.0) return 2;
return 1;
}
export function init(): void {
rsi = new Rsi(i32(p_len()));
}
export function state(): i32 {
const value = rsi.update(in_close());
if (isNaN(value)) return 0;
switch (zone(value)) {
case 0:
score = -1.0;
break;
case 2:
score = 1.0;
break;
default:
score = 0.0;
break;
}
return 1;
}
export function finalize(): void {
out_score(score);
emitRow();
}
export function reset(): void {
rsi.reset();
score = 0.0;
}Are objects and arrays supported? Yes, with static types. StaticArray<f64> is the fixed-size buffer for windows, Array<f64> grows, Map exists, and a class with typed fields and methods is the struct a kScript type Zone { ... } becomes. Arrays are homogeneous, any does not exist, and allocation belongs in init() or at module start, never in state() (Collections, User-defined types).
Can an Indicator read another Indicator's values? On your machine, yes: a metric composition source, { "source": "metric", "metric": "wrun/@scope/name/output" } in a hand-written sheet, feeds an installed package's output in as an input (Data sources). It is metadata-first only (the declaration grammar refuses it by name), and the chart does not compose. On the chart, combine the logic into one file.
Can I use inputs and TA classes inside my own functions? Yes. Inputs are read in state() through in_<name>() and passed as f64 arguments; a TA class is an ordinary object you can hold in a module-level variable or pass to a function. What a function cannot do is capture a local from an enclosing function (AssemblyScript closures cannot), so shared state is module-level (User functions).
Does it support alerts? Yes, through watches on the metric. Install the package on your machine (or publish it and install it anywhere) and om watch create with a condition on wrun/@scope/name/<output>: a level (gt, lt, ...) or an edge (crosses_above, crosses_below). Alerts armed from the chart on a published Indicator exist behind a flag today. There is no alert() call in the file; the outputs are the alert surface (Alerts).
Can trades be executed from an Indicator? No. The module is sandboxed: no filesystem, no network, no order capability. A watch on the metric can carry an execute action that trades when the condition fires; that authorization lives on the watch, never in the file.
Can I show text? Through a string slot and a renderer (render.label for one tag, render.text per bar, render.table for a grid), which switches the file to the second runtime contract; outputs and params stay numbers (Drawing objects).
Can I move or delete a shape after drawing it? No. Each bar decides what it draws; a shape that should end is a when gate that turns 0. Only the forming bar re-evaluates, and there the host replaces its shapes.
How many shapes can I have? 16 boxes and 16 segments per sheet, each drawn once per bar; 32 renderers, 64 drawings, 64 string slots. Limits has every cap.
Performance and optimization
My Indicator is running slowly. How can I optimize it? The module runs once per loaded bar and once per tick, so the cost that matters is per-bar allocation. Allocate every buffer once, in init() or at module start, sized from a param's max; never new anything inside state(). Keep loops bounded by a param with a declared max. Compute a value once and keep it in a variable instead of recomputing it in finalize(). The compiled module is a few kilobytes and a 20-bar average over 5,000 bars is instant; an array that grows on every call is the one pattern that makes a long history slow.
Should I use const for constants? Yes: a module-level const MAX_BARS = 500 is a compile-time constant, the right size for a StaticArray, and it costs nothing per bar. A value that depends on a param is a module-level let assigned once in init().
Common error messages
What does "Conversion from type 'f64' to 'i32' requires an explicit cast" mean? You handed a float to something that wants a whole number: a class period, a loop bound, an array index, or a counter declared without a type (let count = 0 is an integer). Write i32(p_period()), and declare float variables as f64 (let value: f64 = 0.0). The full list of messages, each with its fix, is in Common errors.
Still have questions?
Common errors lists every message with its fix, Debugging is the workflow for an Indicator that runs but draws the wrong thing, and Limitations says what is deliberately out of scope today. For everything else, join the discussion on Discord or read the Quick reference.