Core Concepts

na and Color

Working with missing values in kScript: the na helpers (isna, nz, fixnan), how NaN behaves, and building colors with color.rgb and color.new.

Core Concept 4 min read

Why na matters

na is kScript's "no value here." You meet it constantly: an indicator hasn't warmed up yet, a find matched nothing, a source has no row on this bar. The important rule is that na is contagious in arithmetic: any expression touching an na becomes na. Add 5 to a not-yet-warmed SMA and you get na, and an na value plots as a gap, not a zero. So the skill is detecting na and filling it before it poisons a calculation or blanks a line.

These helpers do that:

HelperReturnsUse it to
isna(x)booleanbranch on whether a value is missing
isnan(x)booleancheck for NaN specifically (treated as na)
isnum(x)booleanthe inverse: is this a usable number
nz(x, fallback)numberreplace a missing x with a fallback
fixnan(series)serieshold the last good value forward over gaps

NaN and na compare equal, so a NaN that sneaks in from bad math is caught by the same na checks.

Filling and forward-holding

nz(x, fallback) is the everyday tool: if x is na, you get fallback, otherwise you get x. It is how you keep a plot continuous or keep arithmetic finite.

fixnan(series) is the series-level cousin: wherever the series is na, it substitutes the most recent non-na value, so a gappy series becomes a stepped, continuous one. Reach for it when an upstream feed has holes you'd rather hold flat than drop.

scripts/probes/lang-nacolor/na_color_happy.ks
//@version=2
define(title="na and Color Happy Path", position="offchart", axis=true)

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
timeseries closeSeries = trade.close
timeseries slow = sma(source=closeSeries, period=20)
timeseries fixed = fixnan(source=slow)

var propagated = slow + 5
var filled = nz(propagated, closeSeries)
var checks = (isna(propagated) ? 1 : 0) + (isnan(NaN) ? 1 : 0) + (isnum(closeSeries) ? 1 : 0) + ((NaN == na) ? 1 : 0)
var baseColor = color.rgb(r=37, g=99, b=235)
var softColor = color.new(color=baseColor, transp=35)

plotLine(value=filled + checks, colors=[baseColor], width=2, label=["nz"], desc=["nz fills na propagated arithmetic"])
plotLine(value=nz(fixed, closeSeries), colors=[softColor], width=2, label=["fixnan"], desc=["fixnan returns a plottable series"])

Walking the key lines:

  • var propagated = slow + 5 is deliberately na for the first 20 bars: the SMA hasn't warmed up, and adding 5 to na stays na. That is the contagion in action.
  • nz(propagated, closeSeries) patches those early bars with the close, so the line is continuous from bar 0 instead of starting blank at bar 20.
  • fixnan(slow) produces a version of the SMA with no internal gaps.

What you'll see: two continuous lines from the first bar onward, no leading gap, because every potentially-na value has a fallback.

Building colors

Colors are first-class values. Two constructors cover most needs:

  • color.rgb(r, g, b) builds a color from red, green, blue channels, each 0-255.
  • color.new(color, transp) takes an existing color and applies transparency (0 opaque, 100 fully transparent).

Store the result in a var and pass it in a plot's colors=[...] array, exactly as the example above does with baseColor and a 35%-transparent softColor. For the named-constant palette (red, teal, ...) see Color Constants; for the full color API see Color Functions.

Channel values are validated

Channels must stay in 0-255. An out-of-range channel is caught and fails loudly rather than wrapping or clamping silently:

scripts/probes/lang-nacolor/invalid_color_boundary.ks
//@version=2
define(title="Invalid Color Boundary", position="offchart", axis=true)

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
var badColor = color.rgb(r=300, g=0, b=0)

plotLine(value=trade.close, colors=[badColor], width=2, label=["Bad color"], desc=["invalid color boundary"])

Here r=300 triggers color.rgb.r must be between 0 and 255 at 5:16. If a color call errors, check that every channel is within 0-255.