Docs·Delta DSL Script·Library

Math, format & time helpers

Element-wise math, branching helpers (iff/mask/nz/fixnan/clamp/lerp/holdSign), string formatting (tostring/padLeft/padRight), and UTC time helpers (year/month/dayofweek/hour/...).

This page documents every helper in the math, branching, format, and time families. Every function listed here:

  • Is broadcast-aware unless noted — pass a scalar OR a series, get the matching shape back.
  • Is NaN-safe — NaN inputs propagate cleanly through arithmetic; comparison with NaN returns false.
  • Is pure — no side effects, no hidden state across bars.

Element-wise math

The basic arithmetic operators (+, -, *, /, %) are already covered in the language reference. The functions below complement them.

abs(x)

Absolute value.

Delta DSL
deviation = abs(close - sma(close, 50))

floor(x), ceil(x), round(x)

Standard rounding. round uses half-away-from-zero semantics (so round(0.5) == 1, round(-0.5) == -1).

Delta DSL
ticks = floor(close * 100)        // express price in ticks of 0.01

sign(x)

Returns +1 for positive, -1 for negative, 0 for zero, NaN for NaN.

Delta DSL
direction = sign(change(close))

sqrt(x), pow(base, exp), exp(x), log(x), log10(x)

Power / exponential / logarithm. log is natural log; log10 is base-10. Negative inputs to log / sqrt produce NaN.

Delta DSL
geomeanReturn = exp(sma(log(close / close[1]), 14))

sin, cos, tan, asin, acos, atan, atan2

Trigonometry. All angles are in radians. atan2(y, x) handles quadrant correctly.

min(a, b, …), max(a, b, …)

Element-wise N-ary min / max. Any argument may be a scalar or a series; the result is a series if at least one argument is.

Delta DSL
upperBound = max(high, high[1], high[2])    // 3-bar high

For rolling min/max over a window, use highest / lowest — those are O(1) per bar, while a manual max(...) over many shifts is O(n).

Branching helpers

These are the per-bar equivalents of if / else blocks. They're how you build conditional plots, two-tone lines, and "draw only when …" markers.

iff(cond, a, b)

Vectorised if-then-else. cond is a per-bar boolean (or scalar); a and b are values selected per bar.

Delta DSL
filled = iff(close >= ma, close, na)        // close where above MA, NaN below
plotLine(filled, color="#0ECB81", width=2)  // line breaks where condition is false

When all three arguments are scalars, returns a scalar. When any one is a series, returns a series. Strings, numbers, booleans all work for the branches.

Note on per-bar colour. plotLine's color= argument is a single static string — passing a series of colours falls back to the default (the engine reads color once at draw-call time). To paint different segments in different colours, split the source into masked series and emit one plotLine per colour. See mask below.

mask(series, cond)

Pass-through where cond is true; NaN otherwise. Pair with plotLine to render a series that breaks into segments:

Delta DSL
bullSeg = mask(close, close > sma(close, 50))
bearSeg = mask(close, close < sma(close, 50))

plotLine(bullSeg, color="#0ECB81", width=2)
plotLine(bearSeg, color="#F6465D", width=2)

The renderer treats NaN as a gap — the line disappears where cond is false.

holdSign(up, down, init=0)

Sticky +1 / -1 trend tracker. Flips to +1 on every truthy up[i] and -1 on every truthy down[i]; carries the previous value forward in between. Pass init=1 to start the trend in the bull state, init=-1 for bear.

Delta DSL
ma    = sma(close, 20)
trend = holdSign(crossover(close, ma), crossunder(close, ma), 0)

bullMa = mask(ma, trend == 1)
bearMa = mask(ma, trend == -1)

plotLine(bullMa, color="#0ECB81", width=2)
plotLine(bearMa, color="#F6465D", width=2)

Without holdSign, this pattern would require a for loop walking every bar. The function is O(n) but in C-level vector form, not script-eval-time.

nz(value, fallback=0)

Replace NaN entries with fallback. Standard null-safety helper.

Delta DSL
safePnl = nz(pnl, 0)               // treat missing pnl as zero

nz accepts a scalar OR a series for both arguments.

fixnan(value)

Carry the last finite value forward across NaN gaps. Useful for sparse signals (pivots, divergences) where you want a continuous step line instead of a series of disconnected dots:

Delta DSL
ph        = pivothigh(high, 5, 5)         // NaN on non-pivot bars
phLine    = fixnan(ph)                     // step line that updates on each new pivot

plotLine(phLine, color="#F0B90B", width=1.5)

clamp(src, lo, hi)

Constrain src into [lo, hi] element-wise. Any of the three may be a series.

Delta DSL
boundedRsi = clamp(rsi(close, 14), 5, 95)        // hide noise at extremes
volScale   = clamp(volume / sma(volume, 50), 0.5, 3)

NaN inputs propagate (NaN clamps to NaN). When lo > hi, the result is lo (the lower bound wins).

lerp(a, b, t)

Linear interpolation: a + (b - a) * t. Element-wise on series.

Delta DSL
midband = lerp(bb_lower(close, 20, 2), bb_upper(close, 20, 2), 0.5)

t outside [0, 1] extrapolates — there is no implicit clamp. Combine with clamp(t, 0, 1) if you want bounded interpolation.

Format helpers

Building the dynamic strings that go inside plotLabel / labelNew / paneLabel chips.

tostring(value, fmt)

Format a scalar OR a series as a string. Returns the matching shape (scalar in → string out; series in → string-array out). Combine with + for concatenation:

Delta DSL
plotLabel(time[0], close[0],
          "RSI: " + tostring(rsi(close, 14)[0], "0.0"),
          color="#F0B90B")

Format patterns

PatternEffect12.345 becomes
omittedNumber.toString() (drops trailing zeros, scientific for large/small)"12.345"
"0"Integer, half-away-from-zero"12"
"0.0"Fixed-1 decimal"12.3"
"0.00"Fixed-2 decimals (price ticks)"12.35"
"0.000000"Fixed-N up to 8 decimals"12.345000"
"#.##" / "##.##"Fixed-N decimals, trim trailing zeros"12.35"
"%"Multiply by 100, append % (decimals: 0)"1234%"
"0.0%" / "0.00%"Percent with N decimals"1234.5%"

Unknown patterns fall back to Number.toString() — typos never throw.

Delta DSL
tostring(0.5, "%")        // "50%"
tostring(0.1234, "0.00%") // "12.34%"
tostring(1.50, "##.##")   // "1.5"
tostring(NaN, "0.00")     // "NaN"

padLeft(s, width, fill=" "), padRight(s, width, fill=" ")

Pad a string to a target width. Useful for table-style HUDs inside labelNew / paneLabel:

Delta DSL
hud = "RSI: " + padLeft(tostring(r[0], "0.0"), 5) + "\n"
    + "ATR: " + padLeft(tostring(atr(high,low,close,14)[0], "0.00"), 6)

labelNew("hud", time[0], high[0], hud, anchor="above", align="left")

If s is already at least width characters long, the input is returned unchanged. Only the first character of fill is used.

Time helpers

All seven helpers accept the time series OR a scalar epoch timestamp in either seconds or milliseconds — the implementation magnitude-detects (any value below 1e11 is treated as seconds and scaled to ms internally). Output is UTC so cross-exchange comparisons stay consistent.

The time series itself carries SECONDS (engine X-axis units — same shape lineNew / boxNew / labelNew consume). Don't manually convert time before calling these helpers; they handle the rescale for you. See pitfall §12 for details.

year(t)

UTC calendar year (4-digit). 2026 for an October 2026 bar.

month(t)

UTC month, 1..12 (NOT 0-indexed — matches Pine Script semantics). January = 1.

dayofmonth(t)

UTC day of month, 1..31.

dayofweek(t)

UTC day of week, 0..6 where 0=Sun, 1=Mon, …, 6=Sat. Same indexing as JavaScript's Date.prototype.getUTCDay.

Delta DSL
// Weekday-only filter (Mon-Fri):
isWeekday = dayofweek(time) >= 1 and dayofweek(time) <= 5
plotBgColor(not isWeekday, color="rgba(255,255,255,0.05)")

hour(t), minute(t), second(t)

UTC hour 0..23, minute 0..59, second 0..59.

Delta DSL
// Highlight London open (08:00 UTC):
isLondonOpen = hour(time) == 8 and minute(time) == 0
plotShape(isLondonOpen, low - atr(high,low,close,14) * 0.5,
          shape="circle", color="#F0B90B", size=4)

weekofyear(t)

ISO 8601 week number, 1..53. Monday-start week; the week containing Thursday is week 1. Same convention as Excel, Postgres, Wikipedia.

NaN handling

Non-finite or non-positive timestamps return NaN (no crash). Filter with iff(year(time) > 0, …) if you need to gate on real timestamps.

Worked example: a multi-line HUD

Combining tostring, padLeft, time helpers, and a label slot:

Delta DSL
@name "Trading HUD"

r   = rsi(close, 14)
a   = atr(high, low, close, 14)
v20 = sma(volume, 20)

hud = "RSI:    " + padLeft(tostring(r[0],   "0.0"), 5)
    + "  ATR:  " + padLeft(tostring(a[0],   "0.00"), 7)
    + "  Vol:  " + padLeft(tostring(volume[0]/v20[0], "0.00"), 5) + "x"
    + "\n"
    + "Day:   " + tostring(dayofmonth(time[0]))
    + "  H:    " + padLeft(tostring(hour(time[0]), "0"), 2) + ":"
    + padLeft(tostring(minute(time[0]), "0"), 2)

labelNew("hud", time[0], high[0],
         hud, anchor="above", align="left",
         color="#F0B90B", bg="rgba(0,0,0,0.6)")

The HUD updates on every redraw with the latest values, anchored to the right-most bar's high.

Next