Docs·Delta DSL Script·Library

Statistics & regression

Rolling stdev/variance/dev, correlation, normalize, percentrank/percentile/median, linreg, polyreg2 (+ stderr), zscore. The stats helpers that anchor every quant indicator.

The statistics family is what separates a "moving average" indicator from a "z-score-of-deviation-from-the-regression-fit" indicator. Every function is rolling — it operates over a period-bar window ending at the current bar, returning a series of the same length.

Rolling dispersion

stdev(src, period)

Rolling standard deviation over period bars. Uses population divisor (÷ period), not sample (÷ period − 1), so two consecutive calls with period=p and period=p+1 differ by one bar's contribution rather than by a Bessel correction.

Delta DSL
sigma = stdev(close, 20)

variance(src, period)

Rolling variance — stdev(src, period)². Slightly faster than pow(stdev(...), 2) because it avoids the sqrt.

dev(src, period)

Mean absolute deviation around the rolling SMA: Σ|src − sma(src, p)| / p. This is what CCI uses internally (with its × 0.015 constant baked in).

Delta DSL
mad = dev(close, 20)

Normalisation

normalize(src, fromMin, fromMax, toMin=0, toMax=1)

Map src from [fromMin, fromMax] into [toMin, toMax] linearly. Any of the four bounds may be a series — the most common use is mapping an oscillator into the visible price range so it overlays the candle pane:

Delta DSL
@pane "overlay"

r = rsi(close, 14)
priceLow  = lowest(low,  200)
priceHigh = highest(high, 200)

rsiOverlay = normalize(r, 0, 100, priceLow, priceHigh)
plotLine(rsiOverlay, color="#F0B90B", width=1.5)

When fromMin == fromMax the output is toMin (avoids divide-by-zero).

Ranks & percentiles

percentrank(src, period)

Rank of src[i] within its rolling window as a 0-100 percentile. 100 means the current bar is the maximum of the window; 0 means the minimum.

Delta DSL
rank = percentrank(close, 50)
plotShape(rank > 95, low, shape="circle", color="#0ECB81")    // top 5% breakouts

percentile(src, period, percent)

Linearly-interpolated percentile of the rolling window. percent is [0, 100]. percentile(close, 50, 50) is the rolling 50-bar median.

median(src, period)

Shortcut for percentile(src, period, 50).

Delta DSL
mid = median(close, 50)
plotLine(mid, color="rgba(240,185,11,0.6)", width=1)

Correlation & regression

correlation(a, b, period)

Pearson correlation coefficient over a rolling window. Range [−1, +1]. Use to gauge how tightly two series move together.

Delta DSL
@pane "below"
@input length = input.int(20, "Length", minval=5, maxval=200)

corr = correlation(close, volume, length)
paneRange(-1, 1)
paneHline( 0,    color="rgba(255,255,255,0.2)")
paneLine(corr,  color="#F0B90B", width=1.5)

When the rolling stdev of either series is zero (a flat segment), correlation is mathematically undefined and the function returns NaN.

linreg(src, period, offset=0)

Linear-regression value at the END of the window. offset=0 is the newest bar (right edge of the lookback); positive offset is older bars; negative offset projects FORWARD past the right edge.

Delta DSL
fit  = linreg(close, 50, 0)        // current-bar fit
proj = linreg(close, 50, -3)       // 3-bar forward projection

polyreg2(src, period, offset=0)

Quadratic (degree-2) polynomial regression — fits y ≈ c₀ + c₁·x + c₂·x² to the last period bars by closed-form Cramer's-rule solve. Same offset semantics as linreg. Catches mild curvature (acceleration / deceleration of the trend) where a straight line would miss it.

Delta DSL
fit  = polyreg2(close, 50, 0)
plotLine(fit, color="#F0B90B", width=2)

polyreg2_stderr(src, period)

Standard error of the quadratic regression — sqrt(Σ(y − fit)² / period). Use as the band offset for a regression channel:

Delta DSL
@input length   = input.int(50, "Length", minval=10, maxval=400)
@input widthMul = input.float(2.0, "Width × stderr", minval=0.5, maxval=4.0, step=0.1)

mid    = polyreg2(close, length, 0)
stderr = polyreg2_stderr(close, length)
upper  = mid + stderr * widthMul
lower  = mid - stderr * widthMul

plotLine(mid,   color="#F0B90B", width=2)
plotLine(upper, color="rgba(240,185,11,0.5)")
plotLine(lower, color="rgba(240,185,11,0.5)")
plotBand(upper, lower, color="rgba(240,185,11,0.04)")

Z-score

zscore(src, period)

Rolling z-score: (src − sma(src, p)) / stdev(src, p). Says "how many standard deviations away from the rolling mean is the current bar?". NaN where the local stdev is zero (perfectly flat segment).

Delta DSL
@pane "below"

z = zscore(close, 50)
paneRange(-4, 4)
paneHline( 2, color="rgba(246,70,93,0.6)")
paneHline(-2, color="rgba(14,203,129,0.6)")
paneHline( 0, color="rgba(255,255,255,0.2)")

paneLine(z, color="#F0B90B", width=1.5)
plotShape(z > 2,  high, shape="arrowDown", color="#F6465D", size=8)
plotShape(z < -2, low,  shape="arrowUp",   color="#0ECB81", size=8)

Patterns

Bollinger bands from scratch

The built-in bb_* functions wrap this — but it's a useful exercise:

Delta DSL
@input length = input.int(20, "Length")
@input mult   = input.float(2.0, "Std Dev")

mid   = sma(close, length)
sigma = stdev(close, length)

upper = mid + sigma * mult
lower = mid - sigma * mult

plotLine(upper, color="rgba(240,185,11,0.5)")
plotLine(mid,   color="#F0B90B")
plotLine(lower, color="rgba(240,185,11,0.5)")
plotBand(upper, lower, color="rgba(240,185,11,0.04)")

"Statistically significant" breakout filter

Delta DSL
@input zThresh = input.float(2.5, "Z threshold", minval=1.0, maxval=5.0, step=0.1)

z = zscore(close, 50)
breakout = abs(z) > zThresh

plotShape(breakout and z > 0, high, shape="arrowUp",   color="#0ECB81")
plotShape(breakout and z < 0, low,  shape="arrowDown", color="#F6465D")

Regression channel with optional fork

Delta DSL
@input mode = input.string("linear", "Mode", options="linear,quadratic")
@input len  = input.int(60, "Length")

mid  = na
stde = na

if mode == "linear"
  mid := linreg(close, len, 0)
  // For linear, we approximate stderr by 1.5× rolling stdev — a
  // cheap fallback that approximates the channel width without
  // computing a separate linear-regression stderr.
  stde := stdev(close, len) * 1.5
else
  mid  := polyreg2(close, len, 0)
  stde := polyreg2_stderr(close, len)
end

upper = mid + stde * 2
lower = mid - stde * 2

plotLine(mid,   color="#F0B90B", width=2)
plotLine(upper, color="rgba(240,185,11,0.5)")
plotLine(lower, color="rgba(240,185,11,0.5)")

Performance notes

  • Every function in this family is O(n × period) in the worst case — each bar walks its window. For long histories with period > 200, this is the dominant cost.
  • stdev / variance use a running-sum optimisation so they stay close to O(n).
  • polyreg2 solves a 3×3 system per bar via Cramer's rule — a fixed amount of arithmetic, so it's O(n) overall, faster than naive least-squares.
  • correlation, linreg, percentile walk the window per bar.
  • The whole-script loop budget is 5 000 000 iterations (see Pitfalls). For a 50-bar polyreg2 over 5 000 bars that's well under the cap.

Next

  • Color helpersrgb, rgba, withAlpha, gradient.
  • Drawing — rendering the series this page produces.
  • Recipes — full statistical indicators end-to-end.