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:
| Helper | Returns | Use it to |
|---|---|---|
isna(x) | boolean | branch on whether a value is missing |
isnan(x) | boolean | check for NaN specifically (treated as na) |
isnum(x) | boolean | the inverse: is this a usable number |
nz(x, fallback) | number | replace a missing x with a fallback |
fixnan(series) | series | hold 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.
//@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 + 5is deliberatelynafor the first 20 bars: the SMA hasn't warmed up, and adding5tonastaysna. 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, each0-255.color.new(color, transp)takes an existing color and applies transparency (0opaque,100fully 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:
//@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.