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).
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.0isNaNfor 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;isfixnan: a bar with no liquidations readsNaNundermissing: "nan", and the held value steps only when a real reading arrives. Before the first readingheldisNaN, 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).
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.