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 library | Indicator package |
|---|---|
import "@you/norm" | om install @you/norm, then mount it or read its metric |
import "@you/[email protected]" pin | installs pin the exact version and hash; alerts lock to what they were armed on |
a shared type | a class in a workspace module; across packages, values travel as metrics |
| a consumer reusing a computed series | a 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 editor | om indicator install --replace and the editor's Run rebuild from the current source |
| publish an immutable version | om 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