---
title: "Quick start: author on your machine"
description: "Build, install, and read an Indicator from a terminal in five steps. The same file the chart editor compiles authors from your machine with the om CLI and bun…"
order: 10
section: "getting-started"
---

<!-- source: docs/indicators/getting-started/quick-start.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Quick start: author on your machine

Build, install, and read an Indicator from a terminal in five steps. The
same file the chart editor compiles authors from your machine with the `om`
CLI and `bun`: scaffold a workspace from a template, build it, install it,
and read its values as an ordinary metric. If you came from kScript (legacy),
this is the flow that has no equivalent there: an Indicator is a
package you hold, not a script the platform holds for you.

## Anatomy of a workspace

Every Indicator workspace has three main parts:

1. **The sheet.** `wrun/metadata.json` declares what the module reads and
   writes: params, inputs and their sources, outputs and their looks. In a
   code-first workspace it is derived from the declarations in your source;
   in a metadata-first workspace you write it by hand.
2. **The source.** `src/indicator.ts`: the four functions and the state
   between them.
3. **The generated accessors.** `src/gen/`: one reader or writer per
   declared name, regenerated before every build, so the code stays attached
   to names while the runtime passes values by position.

## Step 1: Scaffold from a template

```bash
om wrun templates --format json
om wrun create @you/my-sma ./my-sma --template sma
```

`om wrun templates` lists the starting points: `sma`, `sma-codefirst` (the
chart's New indicator seed), `sma-box` (the second starter: the same SMA
with a box shading a band around it), `vp-buy-share-codefirst` (a celled
volume-profile input with a text renderer, the worked footprint example in
`core-concepts/data-sources.md`), `polymarket-odds`, `conviction-score`,
`event-asset-divergence`, `escalation-risk`, and `hud-terminal` (a
terminal-style readout drawn with anchored label handles). Every template
scaffolds a complete workspace:

```text
om-package.json            package manifest (@scope/name, version, kind)
package.json               local build scripts
asconfig.json              compiler settings
wrun/metadata.json         the sheet (what the module reads and writes)
src/indicator.ts           your file
src/sdk/sdk.ts             raw host imports (advanced; variable-index access)
src/sdk/ta.ts              stateful TA classes, one per kScript builtin (Sma, Ema, Rsi, ...)
src/sdk/declare.ts         GENERATED declaration SDK (param/input/output/box/segment);
                           regenerated by scaffold migrations, never edit
src/gen/                   GENERATED name-attached accessors; never edit
                           (strings.ts appears when the sheet declares string slots)
scripts/wrun-prebuild.mjs  GENERATED prebuild step run by `bun run build`; never edit
.wrun-scaffold             scaffold contract version
README.md, .gitignore
```

`om wrun` migrates a workspace scaffolded by an older release onto the
current contract once, non-destructively: your source and sheet are never
touched, the SDK files and build scripts are refreshed, and the migration
is reported in the next build's warnings.

## Step 2: Read the sheet and the source

The `sma` template is the moving average with a hand-written sheet instead
of declarations. The sheet declares one param, one input, one output:

```json
{
  "id": "my-sma",
  "name": "My Simple Moving Average",
  "abi_version": "wrun-1",
  "warmup_bars": 1,
  "params": [{ "name": "period", "default": 20, "min": 1, "max": 200 }],
  "inputSources": { "close": { "source": "ohlcv", "field": "close" } },
  "inputs": [{ "index": 0, "name": "close" }],
  "outputs": [{ "index": 0, "name": "sma", "plot": "line", "panel": "overlay", "unit": "price" }]
}
```

And the source reads and writes those names through the same accessors the
chart's template uses:

```typescript
import { in_close } from "./gen/inputs";
import { emitRow, out_sma } from "./gen/outputs";
import { p_period } from "./gen/params";
import { Sma } from "./sdk/ta";

let sma = new Sma(20);
let value: f64 = NaN;

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

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

export function finalize(): void {
  out_sma(value);
  emitRow();
}

export function reset(): void {
  sma.reset();
  value = NaN;
}
```

Editing the sheet moves the SLOT inside each accessor while your source
keeps the NAME, so reordering params or inputs never changes what the module
computes. With declarations in the source the sheet is DERIVED at every
build (it records `generated_from: "declarations"` plus a `source_digest`)
and hand edits to it are refused; without declarations the hand-written
sheet is the authority, and that is the only mode for languages without an
extractor. Raw positional literals (`getFloat(0)`, `setOutput(0, ...)`) are
a build error either way; `src/sdk/sdk.ts` exists for variable-index access
only. The full grammar is in `functions/ta-library.md` and
`functions/script-definition.md`.

## Step 3: Build, install, check

```bash
om wrun build ./my-sma
om wrun install ./my-sma --replace
om wrun validate ./my-sma
```

`om wrun build` regenerates `src/gen` from the sheet (deriving the sheet
from declarations first), runs the local build (the receipt prints
`Compiler: assemblyscript (bun run build)`), checks the module statically
against the contract, and exports `dist/package`. The scaffold's local
build runs `bun install` once per workspace (the compiler) and
`bun run build` per compile; `om wrun build` drives both, and a plain
`bun run build` inside the workspace runs the same prebuild plus compile
without `om` on PATH. `om wrun install` is the same pipeline plus the local
install under `~/.openmarket/packages`, with a receipt naming the install path and
the undo (`om wrun remove @you/my-sma`). On a compile error the command
exits with the compiler's diagnostics: read them, edit, build again.

## Step 4: See a value, see it move, see it drawn

```bash
om wrun list
om metric get --metric wrun/@you/my-sma/sma:period=20 --symbol BTCUSDT --exchange BINANCE_FUTURES
om metric series --metric wrun/@you/my-sma/sma --params period=20 --symbol BTCUSDT --exchange BINANCE_FUTURES --bars 60
om chart indicator preview --type wrun/@you/my-sma/sma --bars 300
```

`om metric get` returns the newest value; `om metric series` returns one
`[barOpenSec, value]` pair per bar (oldest first, newest = the still-forming
bar) and renders a sparkline in text mode; `om chart indicator preview`
draws the draft on your active chart workspace without publishing. An
installed output is an ordinary metric id everywhere else too: watch
conditions (including `crosses_above` / `crosses_below` edges), screens,
and backtests.

## Step 5: Make it yours

Hard-coded periods are not very flexible, and one average is not much of a
study. Scaffold a second workspace from the code-first template and replace
`src/indicator.ts` with the classic off-chart study from the kScript quick
start: the difference between a fast and a slow EMA, drawn as a histogram
that is green above zero and red below. Params make the periods settings,
and a data-only `sign` output feeds the histogram's per-bar color:

```bash
om wrun create @you/ema-diff ./ema-diff --template sma-codefirst
```

```typescript
import { histogram, input, lower, none, ohlcv, output, param } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_diff, out_sign } from "./gen/outputs";
import { p_fast, p_slow } from "./gen/params";
import { Ema } from "./sdk/ta";

param("fast", 7, { min: 1, max: 200, description: "Fast period" });
param("slow", 14, { min: 2, max: 400, description: "Slow period" });
input("close", ohlcv.close);
// A histogram in its own pane, colored per bar by the sign output: entry 0 below zero, entry 1 above.
output("diff", histogram, lower, { color_by: "sign", colors: ["#dc2626", "#16a34a"], description: "Fast EMA minus slow EMA" });
output("sign", none, lower);

let fast = new Ema(7);
let slow = new Ema(14);
let diff: f64 = NaN;

export function init(): void {
  fast = new Ema(i32(p_fast()));
  slow = new Ema(i32(p_slow()));
}

export function state(): i32 {
  const close = in_close();
  diff = fast.update(close) - slow.update(close);
  return isNaN(diff) ? 0 : 1;
}

export function finalize(): void {
  out_diff(diff);
  out_sign(diff > 0.0 ? 1.0 : 0.0);
  emitRow();
}

export function reset(): void {
  fast.reset();
  slow.reset();
  diff = NaN;
}
```

The `color_by` option names another output whose per-bar value indexes the
`colors` palette (floored), so `sign` at `0` paints red and at `1` paints
green; an output cannot color itself, which is why the decision is its own
data-only output. Every param is a field in the overlay's settings dialog
on the chart and a `--params` value on your machine.

Build and install it, then read the difference back with its params:

```bash
om wrun install ./ema-diff --replace
om metric series --metric wrun/@you/ema-diff/diff --params fast=7,slow=14 --symbol BTCUSDT --exchange BINANCE_FUTURES --bars 48
```

The moment the histogram turns positive is a one-line watch on the same
metric id: params travel as a `params` object, and a condition with an edge
operator names the interval the cross is read on:

```bash
om watch create "EMA difference turned positive" --condition '{"metric":"wrun/@you/ema-diff/diff","params":{"fast":7,"slow":14},"selector":{"symbol":"BTCUSDT","exchange":"BINANCE_FUTURES","interval":"HOUR"},"op":"crosses_above","value":0}'
```

Congratulations: you have built, installed, read, and armed an Indicator
without opening a browser. Publishing from the CLI, scopes, visibility, and
deleting are in `functions/publishing.md`; every verb under its `om indicator`
spelling is `getting-started/cli.md`.

## What's next

- `core-concepts/execution-model.md`: the per-bar function, warm-up, no
  lookahead, and which host runs the file.
- `functions/moving-averages.md`: `Sma`, `Ema`, and the averages you write
  yourself.
- `functions/plotting.md`: every plot kind, panel, and styling option.
- `core-concepts/core-variables.md`: what to keep between bars and how.
