---
title: "Debugging"
description: "A practical workflow for finding out why a wrun indicator is wrong: make visible what you cannot see, read the Console, isolate one output, read the numbers at…"
order: 119
section: "faq"
---

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

# Debugging

A practical workflow for finding out why a wrun indicator is wrong: make visible what you cannot see, read the Console, isolate one output, read the numbers at the Console prompt, then check the usual suspects. Work it in order.

Your indicator compiles and runs but the signal is wrong, or it draws nothing, or a line is mysteriously flat. There is no `console.log` (the module runs in a sandbox with no console), but there is a debug log that prints in the editor's Console, a prompt that reads the last run's numbers, and one fact that makes indicators easy to debug: every value the module computes can be an output.

## 1. Emit what you cannot see

The fastest debugger is an output or a log line. You cannot step through the bar loop, but you can make any intermediate value visible on every bar. Three forms:

- **A `lower` line** shows the shape of a value over every bar. Seeing it usually tells you immediately whether it is doing what you think: pinned at zero, flatlining, a spike where there should be none.
- **A `none` output** is a probe: computed and written every bar, never drawn, and readable by name at the Console prompt (step 4). It keeps the picture clean while you check a number.
- **The debug log** prints text: a string slot named `debug`, written in `onBar()`, prints each bar's line in the editor's Console.

```typescript sample=faq-debug-output
param("len", 14, { min: 2, max: 200 });
// The value under suspicion, drawn in its own pane so its shape is visible on every bar.
output("range_pct", line, lower, { unit: "%", color: "#ef4444", description: "high minus low as a percent of close" });
output("smooth", line, lower, { unit: "%", color: "#f59e0b", description: "EMA of the range" });
// A probe: computed every bar and readable at the Console prompt, never drawn. Delete it when done.
output("raw_spread", none, lower, { description: "debug: high minus low before the percent" });

let ema = new Ema(14);

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

function onBar(): void {
  const close = bar.close();
  const spread = bar.high() - bar.low();
  const rangePct = close > 0.0 ? (spread / close) * 100.0 : NaN;
  if (isNaN(rangePct)) return;
  out_range_pct(rangePct);
  out_smooth(ema.update(rangePct));
  out_raw_spread(spread);
}
```

The debug log is the indicator's `print()`. Declare `string("debug", { max_bytes })`, build a line with the allocation-free `sb_*` builder, and send it with `str_debug_sb()` (or send a finished string with `str_debug(text)`). Write it only on the bars worth reading: a bar that writes nothing prints nothing.

```typescript sample=faq-debug-log
param("len", 20, { min: 2, max: 200, description: "Bars in the volume average" });
input("volume", ohlcv.volume);
output("ratio", line, lower, { description: "Volume over its average" });
// The debug log: a string slot named exactly debug. Each non-empty line prints in the editor's Console.
string("debug", { max_bytes: 128 });

let average = new Sma(20);

function onStart(): void {
  average = new Sma(i32(p_len()));
}

function onBar(): void {
  const volume = bar.volume();
  const mean = average.update(volume);
  if (isNaN(mean) || mean <= 0.0) return;
  const ratio = volume / mean;
  out_ratio(ratio);
  // Log only the bars worth reading. The builder allocates nothing, so logging never grows memory.
  if (ratio > 3.0) {
    sb_clear();
    sb_text("spike ratio=");
    sb_f64(ratio, 2);
    sb_text(" volume=");
    sb_f64(volume, 0);
    str_debug_sb();
  }
}
```

After a **Run**, the Console prints one line per bar whose slot is not empty, oldest bar first, after the bar's time in ISO 8601 UTC:

```text
2026-09-30T14:00:00.000Z spike ratio=3.41 volume=1824
2026-09-30T19:00:00.000Z spike ratio=4.07 volume=2210
```

Every run rebuilds the lines, so they always belong to the run on screen, and the Console keeps the newest 400 ("… N earlier print lines truncated to keep the console fast"). The lines print before the chart draws, so they are there even when drawing fails. Build the text with `sb_*`, not with `+` or `toString()`: text built that way allocates on every bar, and memory that grows once the bars start stops the run.

When you need the exact number on the chart rather than in the Console, a string slot and a `render.label` print it on the newest bar. It switches the file to the second runtime contract, which **Run** derives for you:

```typescript sample=faq-debug-readout
output("range_pct", line, lower, { unit: "%", color: "#ef4444" });
output("tag_x", none, lower, { description: "This bar's open time, the label's x" });
string("readout", { max_bytes: 32 });
// One label, on the newest bar that wrote the slot: the exact number, read off the chart.
render.label("range_readout", { x: "tag_x", y: "range_pct", text: "readout", color: "#f59e0b", size: 12 });

function onBar(): void {
  const close = bar.close();
  const rangePct = close > 0.0 ? ((bar.high() - bar.low()) / close) * 100.0 : NaN;
  if (isNaN(rangePct)) return;
  out_range_pct(rangePct);
  out_tag_x(bar.time());
  sb_clear();
  sb_text("range% = ");
  sb_f64(rangePct, 2);
  str_readout_sb();
}
```

This works for any expression. Lift the part you doubt into a variable, declare an output or a log line for it, and read the answer instead of guessing. Delete the probe and the log when you are done: whoever adds a published indicator gets everything it declares.

## 2. Read the Console

If **Run** stops before the chart changes, the message is in the editor's Console, tagged with the stage that produced it, and one problem hides the ones behind it. The status bar's problem counter (its tooltip reads **Open Problems**) opens the Console, and clicking a row jumps to its line:

| Stage | What it checked | Typical message |
| --- | --- | --- |
| `declarations` | the `param` / `input` / `output` / shape statements, read from the text | `option 'top' takes an output handle, not a string literal` |
| `lint` | raw positional slot literals in your source | `Raw slot literal 0 passed to getFloat(): raw positional slots rebind silently when the sheet changes` |
| `metadata` | the sheet derived from your declarations, against the schema | `Output 'price' (color_by): color_by needs 'colors' with at least 2 entries` |
| `compile` | the AssemblyScript compiler, with a hint | `Conversion from type 'f64' to 'i32' requires an explicit cast.` |
| `validate` | the built module's exports and the sandbox | `console.* is not available in an Indicator (it runs in a sandbox with no console).` |
| `compute` | the run itself, after **Run** | `AssemblyScript abort: Index out of range at ~lib/array.ts:...` |

Fix the first one and **Run** again. Every message is listed with its fix in [Common errors](common-errors.md).

A run that reaches the chart and draws nothing is usually not an error at all. It is an output that is `NaN` on every bar (`onBar()` returned before writing it, or wrote `NaN`), an output declared `none`, or a source the chart's market does not serve (the chart says which, by name). For a draft, the Console says which after the run: `Output 'range_pct' is NaN on all 480 ready bars, so nothing is drawn for it.` Step 5 covers the rest.

## 3. Isolate

When several outputs are wrong at once, stop reasoning about all of them. Reduce the module to one output: leave the others unwritten in `onBar()` (an unwritten output is `NaN`, and `NaN` draws nothing), or switch their plot to `none`, and keep only the suspect series drawn. Once that one is correct, bring the others back one at a time. This is the cheapest way to find which input poisoned a calculation downstream, because a wrong value in an indicator has exactly one place it can come from: the bar's inputs, the state carried from the previous bar, or the arithmetic between them.

## 4. Read the numbers at the Console prompt

The chart shows shapes; the Console prompt shows numbers. After a **Run**, type at the prompt ("Type an expression, an output name, or / for commands") to read the last run without compiling anything:

```text
range_pct                          the newest value
range_pct[-3]                      the value three bars back
last 20 raw_spread                 the newest 20 bars (a none output reads like any other)
at 2026-01-01T00:00Z range_pct     the value on the bar that opened at that time
rows                               the bar count, the first and last bar, the warm-up rows
params                             each param and the value the last run used
outputs                            every output and its last value
sheet                              the sheet derived from your declarations
```

A range reads over the ready bars; warm-up rows are left out. Anything else you type is an expression: it compiles into the current source, runs once per ready bar after your own code for the bar, over the chart's candles, and leaves the overlay alone, so your module-level variables and the kit are in scope. Expressions run over candles only: a strategy indicator, or one that reads another source, is refused ("The console does not run strategy Indicators; Run the tab and inspect its outputs."), and its outputs stay readable by name after a **Run**. `/help` lists the grammar, and `/clear` empties the Console.

## 5. The usual suspects

Most "wrong indicator" bugs are one of a handful of patterns. Scan this list against your symptom:

- **Line starts blank, then appears.** Warm-up. Anything with a period is `NaN` until it has enough bars, and a `NaN` output draws nothing on those bars. That leading gap is expected; if the line never appears, the chart has fewer bars loaded than the window needs.
- **Flat line.** A module-level variable assigned in `onStart()` and never updated in `onBar()`, or an output written from a different variable than the one the bar updates. Log the value per bar and check that it moves.
- **Everything is NaN.** A secondary input under `missing: "nan"` on bars without an observation, an arithmetic chain fed by one `NaN` input, or a division by zero. Probe each input as a `none` output and find the first `NaN`.
- **Wrong pane.** A percent or a count drawn `overlay` hugs the price axis floor. Declare it `lower`.
- **A mark on every bar.** A `shape` output without `shape_where`. Add the gate output.
- **The newest bar jumps around.** It is still forming: the chart re-evaluates it as live updates arrive, at most about once a second, starting each time from the module's state as it stood after the last closed bar. A bar's value settles when the bar closes.
- **Nothing at all, no error.** `onBar()` never reaches a write (a class whose period exceeds the loaded history), the only rendered output is declared `none`, or the chart's market does not serve the source (the chart names it, for example `celled source class 'tape' is not served by the browser lane yet`, or `Volume profile data is unavailable for ...`).
- **A pinned input reads `NaN` everywhere.** An index pin (`POLYGON_INDICES`) reads `NaN`, and the legend says why; any pin reads `NaN` until its first candle closes. A misspelled pin is refused instead: `Pinned market candle (...) data is unavailable for ...` ([Exchange and symbol format](../reference/symbol-format.md)).
- **An alert does not match the chart.** An alert runs the published version and the settings of the overlay it was set on, in OpenMarket's cloud, on the chart's own market; the draft in your editor is not what it evaluates. Publish the change, add the new version to the chart, and set the alert on it. A declared `alert(...)` fires on the bar its `when` output turns from zero to nonzero, so a gate that stays at `1` fires once ([Alerts](../functions/alerts.md)).

Walk these top to bottom. The fix for each is one line, and the probe from step 1 usually tells you which one you are looking at.

## See also

- [Common errors](common-errors.md) for every build and runtime message with its fix.
- [Execution model](../core-concepts/execution-model.md) for warm-up, the forming bar, and where an indicator runs.
- [Repainting](../core-concepts/repainting.md) for what can change on the forming bar and what cannot.
- [Alerts](../functions/alerts.md) for what an alert on an indicator evaluates.
