---
title: "Missing values (NaN)"
description: "NaN is a wrun indicator's \"no value here\": a TA class that has not warmed up yet, a sparse source with no observation on this bar, a ratio with a zero…"
order: 17
section: "core-concepts"
---

<!-- source: docs/indicators/core-concepts/na-and-scalar-types.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Missing values (NaN)

`NaN` is a wrun indicator's "no value here": a TA class that has not
warmed up yet, a sparse source with no observation on this bar, a ratio
with a zero denominator. This page is how to test for it, fill it, hold
the last good value across it, and what it does in arithmetic and on the
chart.

## Why NaN matters

The important rule is that **`NaN` is contagious in arithmetic**: any
expression touching a `NaN` becomes `NaN`. Add `5` to a not-yet-warm SMA
and you get `NaN`, and a `NaN` written to an output draws as a gap, not a
zero. So the skill is detecting `NaN` and deciding what to do about it
before it poisons a calculation or blanks a line.

The helpers, and what they are in an indicator:

| Helper | Use it to |
| --- | --- |
| `isNaN(x)` | branch on whether a value is missing |
| `isFinite(x)` (`!isNaN(x)` when infinities are impossible) | the inverse: is this a usable number |
| `nz`: `isNaN(x) ? fallback : x` | replace a missing value with a fallback |
| `fixnan`: a module-level `held` updated only when the new value is finite | hold the last good value forward over gaps |
| `isNaN(x)`; never `x == NaN` | `NaN` is not equal to anything, itself included |

Comparisons with `NaN` are always false: `NaN > 0.0`, `NaN < 0.0`, and
`NaN == NaN` all evaluate `false`. That is why a condition built over a
warming input never fires by accident, and why `isNaN` is the only test
that works.

## Filling and forward-holding

`nz` is the everyday tool and it is one ternary: if `x` is `NaN`, take the
fallback, otherwise take `x`. It is how you keep a plot continuous or keep
arithmetic finite.

`fixnan` is the series-level cousin: wherever the series is `NaN`,
substitute the most recent finite value, so a gappy series becomes a
stepped, continuous one. In an indicator that is a module-level variable
you overwrite only when the new value is finite.

Both, plus the two ways a row can be missing, in one file. The `propagated`
output is deliberately `NaN` for the first 20 bars: the SMA has not warmed
up, and adding `5` to `NaN` stays `NaN`. `filled` patches those bars with
the close, and `held` carries the last finite liquidation reading across
quiet bars (`missing: "nan"` keeps the source honest; the holding is the
module's decision).

```typescript sample=cc-na-helpers
param("period", 20, { min: 1, max: 200, description: "SMA length" });
input("close", ohlcv.close);
input("liqs", liquidations.liquidations, { missing: "nan", description: "Liquidation volume, NaN on bars without any" });
output("propagated", line, overlay, { color: "#94a3b8", description: "SMA + 5: NaN until the window is warm, and NaN stays NaN" });
output("filled", line, overlay, { color: "rgb(37, 99, 235)", width: 2, description: "The same series with the close as a fallback while warming (nz)" });
output("held", line, lower, { color: "#7c3aed", description: "The last finite liquidation reading, held across quiet bars (fixnan)" });
const bandHi = output("band_hi", line, overlay, { color: "#2563eb66", description: "Filled series plus 1%" });
const bandLo = output("band_lo", line, overlay, { color: "#2563eb66", description: "Filled series minus 1%" });
// A box fill takes the opacity, so its color is hex, rgb(), or hsl(): a named color is refused here.
box("band", { top: bandHi, bottom: bandLo, color: "hsl(221, 83%, 53%)", opacity: 0.1, borderWidth: 0 });

let sma = new Sma(20);
let held: f64 = NaN; // the fixnan state: overwritten only by a finite value

function nz(x: f64, fallback: f64): f64 {
  return isNaN(x) ? fallback : x;
}

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

function onBar(): void {
  const close = bar.close();
  const propagated = sma.update(close) + 5.0; // NaN + 5 is NaN: the contagion
  const liqs = in_liqs();
  if (isFinite(liqs)) held = liqs; // fixnan: hold the last good value forward
  const filled = nz(propagated, close);
  out_propagated(propagated);
  out_filled(filled);
  out_held(held);
  out_band_hi(filled * 1.01);
  out_band_lo(filled * 0.99);
}
```

Walking the key lines:

- `propagated = sma.update(close) + 5.0` is `NaN` for the first 20 bars.
  Written to its output, those bars are a gap on the grey line, while the
  other outputs still draw.
- `nz(propagated, close)` patches the early bars with the close, so the
  blue line is continuous from bar 0 instead of starting blank at bar 20.
- `if (isFinite(liqs)) held = liqs;` is `fixnan`: a bar with no
  liquidations reads `NaN` under `missing: "nan"`, and the held value
  steps only when a real reading arrives. Before the first reading `held`
  is `NaN`, an honest gap rather than an invented zero.

What you'll see: a grey line that starts at bar 20 and a blue one that
starts at bar 0, a faint band around the blue one, and a stepped purple
line in the lower pane.

### Abstaining is the other option

Writing `NaN` to one output leaves the other outputs drawing. Returning
early from `onBar()` before any write leaves the whole bar blank: nothing
is drawn and no output has a value there. Use `NaN` when one line warms
slower than another; return early when nothing on the bar is meaningful
yet ([Execution model](execution-model.md)).

A color is a declaration, not a value, so `NaN` never reaches one
directly. What a `NaN` index on a `color_by` output draws, and every other
color rule, is on [Colors](../functions/colors-kit.md#per-bar-color).
