---
title: "Your first indicator"
description: "From an empty editor to a published indicator with an alert, in six steps. Run the Moving Average starter, grow it into an EMA crossover with a mark on every…"
order: 2
section: "getting-started"
---

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

# Your first indicator

![Two moving averages with marks on each cross](/wrun/images/first-indicator.png)

**From an empty editor to a published indicator with an alert, in six
steps.** Run the Moving Average starter, grow it into an EMA crossover with
a mark on every cross, then publish it and let OpenMarket's cloud watch it
for you.

## Before you start

- **Sign in.** The editor asks a signed-out visitor to sign in, and your
  account needs a verified email.
- **Pick a market that is not CME.** Run is paused on CME markets.

## 1. Open the editor and run the starter

1. On the chart toolbar, press **Editor** (the code icon). When it is not
   pinned, it is in the **Panels** menu.
2. In the editor's Explorer, press **New indicator** and pick **Moving
   Average** under **Starters**.
3. Press **Run**. A 20-bar average draws on the chart.

The tab's first line is the language marker, `//@lang=wrun-ts`, and the
starter sits under it, a comment on every statement:

```typescript sample=sma-codefirst
// A simple moving average, declared and computed in one file. Read it top to bottom.
// Nothing is imported: every word below (param, output, Sma, bar, out_sma) is already there.

// A param is a setting the chart user can change, declared by its kind: a whole number here, with its name, its default, then the allowed range and the label the dialog shows.
param.int("period", 20, { min: 1, max: 200, label: "Period in bars" });
// An output is a per-bar number the Indicator sends back, here drawn as a line on the price panel under the label the legend shows.
// output() returns a handle; bind it with a const only when a box() or segment() needs to name it.
output("sma", line, overlay, { label: "Moving average" });

let sma = new Sma(20); // a moving-average helper that keeps its own window; onStart() rebuilds it at the chosen period

// onStart() runs once before the first bar: read each setting through its p_ reader (i32() = whole bars).
function onStart(): void { sma = new Sma(i32(p_period())); }
// onBar() runs once per bar: bar.close() is this candle's close, and out_sma() writes the output.
// Until the window holds period bars the average is NaN, and a NaN is not drawn.
function onBar(): void { out_sma(sma.update(bar.close())); }
```

- **The parts** are the ones from [What is wrun](introduction.md):
  declarations, state, `onStart()` and `onBar()`.
- **The first bars are empty.** The average is `NaN` until its window fills,
  and `NaN` draws nothing.

## 2. Swap in an EMA

Replace the file under the marker with this, and press **Run**:

```typescript sample=gs-first-step1
param("period", 20, { min: 1, max: 200, description: "EMA length in bars" });
output("avg", line, overlay, { color: "#2563eb", width: 2, description: "EMA of the close" });

let ema = new Ema(20);

function onStart(): void {
  ema = new Ema(i32(p_period()));
}

function onBar(): void {
  out_avg(ema.update(bar.close()));
}
```

- **`Ema`** weights recent bars more than `Sma`, so it turns sooner.
- **The look is declared:** `color` and `width` style the line, and each
  `description` labels a setting or a line.

## 3. Add a faster average

```typescript sample=gs-first-step2
param("fast", 9, { min: 1, max: 200, description: "Fast EMA length" });
param("slow", 21, { min: 2, max: 400, description: "Slow EMA length" });
output("fast", line, overlay, { color: "#2563eb", width: 2, description: "Fast EMA of the close" });
output("slow", line, overlay, { color: "#f97316", width: 2, description: "Slow EMA of the close" });

let fast = new Ema(9);
let slow = new Ema(21);

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

function onBar(): void {
  const close = bar.close();
  const fastValue = fast.update(close);
  const slowValue = slow.update(close);
  if (isNaN(slowValue)) return;
  out_fast(fastValue);
  out_slow(slowValue);
}
```

- **A new `param`** comes with a `p_<name>()` reader, and **a new `output`**
  with an `out_<name>()` writer.
- **`onBar()` returns early** until the slow average has a value, so both
  lines start on the same bar.

## 4. Mark every cross

```typescript sample=gs-first-final
// 1. Two settings.
param("fast", 9, { min: 1, max: 200, description: "Fast EMA length" });
param("slow", 21, { min: 2, max: 400, description: "Slow EMA length" });
// 2. Two lines on the price pane.
output("fast", line, overlay, { color: "#2563eb", width: 2, description: "Fast EMA of the close" });
output("slow", line, overlay, { color: "#f97316", width: 2, description: "Slow EMA of the close" });
// 3. A mark at the bar's low, drawn only where the gate is 1.
output("cross_up", shape, overlay, { color: "#16a34a", shape_where: "is_cross_up", description: "Fast EMA crossed above slow EMA" });
const isCrossUp = output("is_cross_up", none);
// 4. A signal the alert dialog offers once the indicator is published.
alert("crossed_up", { when: isCrossUp, message: "{{symbol}}: fast EMA crossed above the slow EMA at {{close}}", description: "Fast EMA crossed above slow EMA" });

let fast = new Ema(9);
let slow = new Ema(21);
let cross = new Cross();

function onStart(): void {
  fast = new Ema(i32(p_fast()));
  slow = new Ema(i32(p_slow()));
  cross = new Cross();
}

function onBar(): void {
  // The chart's own close for the averages, and its low to place the mark.
  const close = bar.close();
  const low = bar.low();
  const fastValue = fast.update(close);
  const slowValue = slow.update(close);
  const crossed = cross.update(fastValue, slowValue);
  if (isNaN(slowValue)) return;
  out_fast(fastValue);
  out_slow(slowValue);
  out_cross_up(low);
  out_is_cross_up(crossed == 1 ? 1.0 : 0.0);
}
```

- **`Cross`** answers `1` on the bar the fast average crosses above the slow
  one, `-1` on a cross down, `0` otherwise.
- **`is_cross_up`** (plot kind `none`) holds that decision without drawing
  it; **`cross_up`** (plot kind `shape`) draws a mark at the bar's low only
  where `shape_where` says so.
- **`alert(...)`** turns the decision into a signal the alert dialog offers
  once the indicator is published.

## 5. Change a setting

Open the indicator's settings on the chart. Each `param` is a row labelled
by its `description`: set **Fast EMA length** to 12 and **Slow EMA length**
to 26, and the chart reruns without compiling again.

## 6. Publish it and add an alert

1. Press **Publish**, type a name after `@yourname/` (for example
   `ema-cross`), and publish. The first version is 0.1.0
   ([Publishing](../functions/publishing.md)).
2. On the receipt, press **Open on chart**.
3. On that published indicator, open the alert dialog from its bell in the
   legend, pick the signal the file declared ("Fast EMA crossed above slow
   EMA"), and save.

OpenMarket's cloud evaluates the alert and notifies you when it fires
([Alerts](../functions/alerts.md)). A draft can't carry one, so publish
first.

## If something goes wrong

- **Run stops.** The Console names the problem and a squiggle marks the
  line: fix the first one and press Run again
  ([Common errors](../faq/common-errors.md)).
- **Nothing draws.** It is usually warm-up, and the Console says so:
  "Output 'sma' is NaN on all N ready bars, so nothing is drawn for it."

## Next

- **Mark the crosses down too:** a second gate on `crossed == -1` and a
  second `shape` output at the bar's high.
- **Build something real:** the [Cookbook](../cookbook/overview.md) has
  complete recipes to read and adapt.
- **Read more than candles:**
  [What you can read](../core-concepts/what-you-can-read.md).
- **See how it runs:** [Execution model](../core-concepts/execution-model.md).
