String functions

Strings in an Indicator live in string slots: byte-capped channels written once per ready bar in finalize() and read by text renderers and drawing labels. They…

Strings in an Indicator live in string slots: byte-capped channels written once per ready bar in finalize() and read by text renderers and drawing labels. They never enter the module (params are numbers, inputs are numbers) and they never become metrics. On the way in, AssemblyScript's String has the JavaScript-style methods kScript (legacy) offered; on the way out, the generated sb_* builder formats numbers into a slot without allocating. This page is both halves and the one gotcha the declaration name causes.

Where strings go

Declare a slot, then send text to it from finalize():

string("note", { max_bytes: 64 });
render.text("note_mark", { y: "average", text: "note" });

The generated ./gen/strings module gives you, per slot, str_note(s) (encode and send a whole string) and str_note_sb() (send the shared line buffer), plus the builder that fills that buffer:

BuilderAppends
sb_clear()nothing: starts a new line (call it first)
sb_text(s)a string's UTF-8
sb_int(n)an integer (i64)
sb_f64(x, decimals)a number in fixed point with decimals (0..9) fraction digits, rounded half up; NaN, Inf, -Inf spelled out

A line over the slot's max_bytes is refused host-side by name (strings are never truncated); a slot not written that bar is ABSENT, which is distinct from a written empty string, and the difference is what gates render.text (an absent slot draws nothing). The senders trap by phase outside finalize().

The string name

The declaration is called string, the same word as the type. In a file that imports it, let label: string = ""; fails to compile (string is now a function). Two ways out: let the type be inferred (let label = "";), or alias the import, which the extractor still recognizes:

import { string as slot } from "./sdk/declare";
slot("note", { max_bytes: 64 });
let label: string = "";

Available methods

AssemblyScript's String matches the kScript table method for method. They work on string values in the module, in init() or per bar, and the result goes out through str_<slot>(s).

MethodSignatureDescription
splits.split(separator)splits into an array of substrings (string[])
concats.concat(other)joins two strings; + does the same
substrings.substring(start, end?)the characters between two indexes
toUpperCases.toUpperCase()uppercase copy
toLowerCases.toLowerCase()lowercase copy
trims.trim()whitespace removed from both ends (trimStart, trimEnd too)
replaces.replace(search, replaceWith)the first occurrence replaced (replaceAll for every one)
indexOfs.indexOf(search)the index of the first occurrence, -1 if absent
startsWith, endsWiths.startsWith(prefix)prefix and suffix tests
includess.includes(search)containment test
lengths.lengththe number of UTF-16 units; a property, not a call
charCodeAts.charCodeAt(i)the code unit at i
padStart, padEnds.padStart(width, fill)padding to a width

Numbers do not stringify themselves cheaply: sb_f64 and sb_int are the way to put a number in a line, and the builder is where per-bar text belongs.

Allocation

Every String method that returns a new string allocates it, and the module's runtime never frees: the sandbox compiles with a bump allocator and a 4 MiB memory ceiling, and a module that grows memory after init() is refused. Per-bar String work therefore accumulates for the whole history. The sb_* builder is allocation-free by design (one shared buffer sized to the largest slot at module start), so the rule is: String methods for one-time work in init() or at module start (a label, a market name, a template), sb_* for everything that happens per bar.

Every method in one module

A label assembled once in init() with the String methods, and per-bar text built with the sb_* builder into a mark and a live tag.

import { input, line, none, ohlcv, output, overlay, param, render, string as slot, time } from "./sdk/declare";
import { in_bar_t, in_close } from "./gen/inputs";
import { emitRow, out_average, out_bar_time, out_stretched } from "./gen/outputs";
import { p_period } from "./gen/params";
import { sb_clear, sb_f64, sb_int, sb_text, str_readout_sb, str_tag_sb } from "./gen/strings";
import { Sma } from "./sdk/ta";

param("period", 20, { min: 1, max: 200 });
input("close", ohlcv.close);
input("bar_t", time.bar_open_sec);
output("average", line, overlay, { color: "#38bdf8", width: 2, description: "Simple average" });
output("bar_time", none, overlay, { description: "Bar open in epoch seconds" });
output("stretched", none, overlay, { description: "1 while the close is over two percent from the average" });
slot("readout", { max_bytes: 48 });
slot("tag", { max_bytes: 48 });
render.text("stretch_mark", { y: "average", text: "readout", size: 10 });
render.label("average_tag", { x: "bar_time", y: "average", text: "tag", size: 11 });

let sma = new Sma(20);
let label: string = "";
let period: i32 = 20;
let value: f64 = NaN;
let close: f64 = NaN;
let barTime: f64 = NaN;
let barIndex: i32 = 0;

export function init(): void {
  period = i32(p_period());
  sma = new Sma(period);
  // One-time string work: every method from the table, on a market label.
  const raw = "  btc-usdt:perp  ";
  const parts = raw.trim().split(":");
  let name = parts[0].toUpperCase().replace("-", "/");
  if (name.startsWith("BTC") && name.endsWith("USDT") && name.indexOf("/") == 3 && name.includes("USD")) {
    name = name.concat(" ").concat(parts[1].toLowerCase());
  }
  label = name.substring(0, name.length).padEnd(12, ".");
}

export function state(): i32 {
  close = in_close();
  barTime = in_bar_t();
  value = sma.update(close);
  barIndex += 1;
  return isNaN(value) ? 0 : 1;
}

export function finalize(): void {
  const stretch = ((close - value) / value) * 100.0;
  const stretched = Math.abs(stretch) >= 2.0;
  out_average(value);
  out_bar_time(barTime);
  out_stretched(stretched ? 1.0 : 0.0);
  if (stretched) {
    // Per-bar text: the builder, no allocation.
    sb_clear();
    sb_f64(stretch, 1);
    sb_text("% at bar ");
    sb_int(barIndex);
    str_readout_sb();
  }
  sb_clear();
  sb_text(label);
  sb_text(" SMA ");
  sb_f64(value, 2);
  str_tag_sb();
  emitRow();
}

export function reset(): void {
  sma.reset();
  value = NaN;
  close = NaN;
  barTime = NaN;
  barIndex = 0;
}

label is built once and reused on every bar; reset() leaves it alone because init() owns it. The readout is written only on stretched bars, so the mark appears only there.

Caps

Per slot max_bytes is at most 4096; 64 slots per package; 64 KiB of string bytes per row; 8 MiB per run; each a named refusal (Limits).