Libraries

Sharing code between Indicators is publishing a package: the unit of reuse on the registry is a whole Indicator, addressed as @scope/name, that anyone installs…

Sharing code between Indicators is publishing a package: the unit of reuse on the registry is a whole Indicator, addressed as @scope/name, that anyone installs, mounts on a chart, or feeds into another package as a metric composition input. Inside one workspace, shared code is an ordinary AssemblyScript module beside src/indicator.ts, imported by relative path. kScript (legacy) had a third thing, a library() header imported as @owner/name and frozen into every consumer at save; that cross-package source import is not in Indicators yet, and this page is what the two existing forms give you instead. The registry side (dry run, publish, versions, visibility, deleting) is Publishing.

A shared module in the workspace

Put functions, classes, and constants in a second file under src/ and import it by path. The build compiles every file src/indicator.ts reaches; the only rule is that declarations (param, input, output, and the rest) live in src/indicator.ts, because the sheet is derived from the entry file alone (a declaration elsewhere is the named build error code-first declarations must live in src/indicator.ts).

The kScript library("norm") example as src/lib/norm.ts:

// A shared helper module: plain AssemblyScript functions and a class, no declarations.
export function zscore(sample: f64, mean: f64, dev: f64): f64 {
  if (dev == 0.0) return 0.0;
  return (sample - mean) / dev;
}

// +1 stretched up, -1 stretched down, 0 neutral.
export function regime(z: f64, hi: f64, lo: f64): f64 {
  if (z > hi) return 1.0;
  if (z < lo) return -1.0;
  return 0.0;
}

export class Band {
  top: f64;
  bottom: f64;

  constructor(top: f64, bottom: f64) {
    this.top = top;
    this.bottom = bottom;
  }

  contains(price: f64): bool {
    return price <= this.top && price >= this.bottom;
  }
}

And the consumer, src/indicator.ts, which owns the data, the TA objects, and the declarations, and imports the shared logic beside the shipped ./sdk/ta:

import { input, line, lower, ohlcv, output, param } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_regime, out_z } from "./gen/outputs";
import { p_period } from "./gen/params";
import { Sma, Stdev } from "./sdk/ta";
import { Band, regime, zscore } from "./lib/norm";

param("period", 20, { min: 2, max: 400 });
input("close", ohlcv.close);
output("z", line, lower, { color: "#4f8cff", description: "Z-score of the close" });
output("regime", line, lower, { color: "#f59e0b", description: "+1 stretched up, -1 down, 0 inside the band" });

let sma = new Sma(20);
let stdev = new Stdev(20);
let z: f64 = NaN;
let band = new Band(0.0, 0.0);

export function init(): void {
  sma = new Sma(i32(p_period()));
  stdev = new Stdev(i32(p_period()));
}

export function state(): i32 {
  const close = in_close();
  const mean = sma.update(close);
  const dev = stdev.update(close);
  z = zscore(close, mean, dev);
  band = new Band(mean + dev, mean - dev);
  return isNaN(mean) || isNaN(dev) ? 0 : 1;
}

export function finalize(): void {
  out_z(z);
  out_regime(band.contains(z) ? 0.0 : regime(z, 2.0, -2.0));
  emitRow();
}

export function reset(): void {
  sma.reset();
  stdev.reset();
  z = NaN;
}

Both files build with om indicator build, and the exported package carries the compiled module and the sheet, not the source: the helper is part of your workspace, kept beside src/indicator.ts. Types travel too: Band is a class the consumer constructs, the port of a kScript shared type. What the kScript placement rules were for (aliased calls inside a timeseries initializer) has no counterpart, because there are no series expressions: a function is a function everywhere.

The new Band(...) on every bar allocates, and the module's runtime never frees; keep one Band and update its fields when the history is long (Collections).

Two shared-module rules the compiler enforces for you: an import that collides with a local name is a compile error (no silent precedence), and a shared function calling its sibling always gets its own sibling. Errors inside the helper carry the helper's own file, line, and column.

What a library was for, as a package

The rest of what a kScript library offered maps onto packages:

kScript libraryIndicator package
import "@you/norm"om install @you/norm, then mount it or read its metric
import "@you/[email protected]" pininstalls pin the exact version and hash; alerts lock to what they were armed on
a shared typea class in a workspace module; across packages, values travel as metrics
a consumer reusing a computed seriesa metric composition input: { "source": "metric", "metric": "wrun/@you/norm/z" } in a hand-written sheet feeds one package's output into another (Script definition)
draft resolves live in the editorom indicator install --replace and the editor's Run rebuild from the current source
publish an immutable versionom publish; versions are permanent (Publishing)

Not in Indicators yet. import "@scope/lib" from another package's source at build time. Cross-package reuse is a metric today (the consumer reads the published package's computed output) or a copy of the module into src/lib; package-to-package source imports are product work.

om install @you/norm
om indicator install ./my-indicator --replace