One moving average over a raw array, row-aligned, NaN for a cell the
type cannot yet produce.
Over an array rather than a series column because most of its callers
derive their input: Keltner smooths typical price, Coppock's WMA smooths a
sum of two ROCs, the Price Oscillator subtracts two of these from each
other, Hull and TRIMA and DEMA feed one of these into another. A series
column is the special case, and movingAverageColumn is the door for
it.
Definitions
type
definition
sma
mean of the last period finite values (rollingMeanValues) — the same arithmetic as sma(), bit-for-bit on gap-free input; on this array door the window waits for contributors, like every other type
ema
α = 2/(period+1), seeded on the first sample — pond's convention, bit-for-bit what ema() / smooth('ema') gives
wma
linear weights 1…period, newest heaviest, over period(period+1)/2
smma
Wilder's (prev·(period−1) + x)/period, SMA-seeded — the wilderValues kernel RSI and ATR already run on
dema
2·EMA − EMA(EMA)
tema
3·EMA − 3·EMA(EMA) + EMA(EMA(EMA))
trima
TA-Lib's triangular: SMA(SMA(x, p), q) with p = q = (n+1)/2 for odd n, p = n/2+1, q = n/2 for even
hull
WMA(2·WMA(x, ⌊n/2⌋) − WMA(x, n), round(√n))
kama
Kaufman adaptive, TA-Lib's fast 2 / slow 30, efficiency ratio over period
zlema
EMA(2·x − x[i−lag]), lag = ⌊(period−1)/2⌋
The zlema lag floors.(period − 1)/2 is not an integer on an even
period, and the only two answers are to floor it or to interpolate
between two bars. Flooring is what every published implementation does and
is the only one that keeps the study a pure re-indexing of its input; the
cost is that zlema(10) and zlema(11) share a lag of 4 and differ only
in their EMA rate.
Warm-up — length-preserving, and where the first value lands
Every type keeps the row count and emits NaN until its definition has
enough bars. First valid index on a gap-free input, n = period:
| smaemawmasmmatrima | n−1 |
| dema | 2n−2 |
| tema | 3n−3 |
| hull | n − 2 + round(√n) |
| kama | n (the efficiency ratio needs n differences, so n+1 bars) |
| zlema | ⌊(n−1)/2⌋ + n − 1 |
Those match TA-Lib's lookbacks exactly for the seven types TA-Lib ships
(MA(matype=…)); the oracle asserts the null mask, not just the
values, so a warm-up off-by-one fails the generator.
A leading run of gaps steps the warm-up over, rather than poisoning it
— the common source of one is another study's own warm-up, and a study run
over sma(...)'s output must start late rather than come back empty
(the rsi(sma(...)) failure the property tests exist for).
sma is the one exception, deliberately. Its window counts rows, not
contributors — it emits once the window spans period rows and averages
whichever of them are finite — so a leading gap does not shift its
first value. That is sma()'s contract, the one sma∘sma pins; giving the
engine's sma a step-over would make movingAverage({ type: 'sma' }) a
second SMA that quietly disagreed with the first. Every other type in the
menu waits for period finite values and therefore shifts.
Interior gaps: which types recover and which propagate
The Wilder asymmetry, stated per type rather than averaged into a slogan.
A window kernel recovers once the gap leaves the window; a recursion has no
state to carry across a hole, so whether it recovers depends on whether the
recursion skips the missing bar or consumes it.
type
interior gap
sma
recovers — the window still spans period rows and averages the finite ones (sma()'s contract, rows not contributors)
wma
recovers — but the gap bar and the period−1 after it read NaN: a linear weight is position-specific, so dropping one cell would silently reweight the rest
trima
recovers — wma's rule (both SMA stages want a finite window; a triangular weight is positional too)
hull
recovers — wma's rule, three windows deep
ema
recovers — the recursion skips the missing bar and carries on (core's smooth('ema'))
dematemazlema
recovers — built on ema, same skip
smma
propagates to the end — Wilder consumes every bar, so a hole makes the state unknown forever (wilderValues)
kama
propagates to the end — the efficiency ratio sums period consecutive differences, and the recursion carries the result forward
Neither answer is universally right and neither is fixable in the kernel:
a caller who needs continuity across interior gaps fills before smoothing.
Cost
O(N) per type, independent of period — nothing here rescans a window.
The one that has to be written for it is wma: the definition is a
period-term dot product per bar (O(N·period)), and this runs the
running weighted sum instead, W(i) = W(i−1) − S(i−1) + period·x(i)
alongside the plain window sum S. Both accumulators are rebuilt from the
window on rows where i % period === 0 — one extra accumulation per row
amortised, the same trick and the same cadence ranged.ts uses, so the
cancellation in W − S cannot accumulate across a million bars.
The composed types pay their parts: dema two EMA passes, tema three,
trima two SMA passes, hull four WMA passes (n/2, n, and the outer
√n, plus the combine). All still O(N).
One moving average over a raw array, row-aligned,
NaNfor a cell the type cannot yet produce.Over an array rather than a series column because most of its callers derive their input: Keltner smooths typical price, Coppock's WMA smooths a sum of two ROCs, the Price Oscillator subtracts two of these from each other, Hull and TRIMA and DEMA feed one of these into another. A series column is the special case, and movingAverageColumn is the door for it.
Definitions
smaperiodfinite values (rollingMeanValues) — the same arithmetic assma(), bit-for-bit on gap-free input; on this array door the window waits for contributors, like every other typeemaα = 2/(period+1), seeded on the first sample — pond's convention, bit-for-bit whatema()/smooth('ema')giveswma1…period, newest heaviest, overperiod(period+1)/2smma(prev·(period−1) + x)/period, SMA-seeded — thewilderValueskernel RSI and ATR already run ondema2·EMA − EMA(EMA)tema3·EMA − 3·EMA(EMA) + EMA(EMA(EMA))trimaSMA(SMA(x, p), q)withp = q = (n+1)/2for oddn,p = n/2+1, q = n/2for evenhullWMA(2·WMA(x, ⌊n/2⌋) − WMA(x, n), round(√n))kamaperiodzlemaEMA(2·x − x[i−lag]),lag = ⌊(period−1)/2⌋The
zlemalag floors.(period − 1)/2is not an integer on an evenperiod, and the only two answers are to floor it or to interpolate between two bars. Flooring is what every published implementation does and is the only one that keeps the study a pure re-indexing of its input; the cost is thatzlema(10)andzlema(11)share a lag of 4 and differ only in their EMA rate.Warm-up — length-preserving, and where the first value lands
Every type keeps the row count and emits
NaNuntil its definition has enough bars. First valid index on a gap-free input,n = period:|
smaemawmasmmatrima|n−1| |dema|2n−2| |tema|3n−3| |hull|n − 2 + round(√n)| |kama|n(the efficiency ratio needsndifferences, son+1bars) | |zlema|⌊(n−1)/2⌋ + n − 1|Those match TA-Lib's lookbacks exactly for the seven types TA-Lib ships (
MA(matype=…)); the oracle asserts the null mask, not just the values, so a warm-up off-by-one fails the generator.A leading run of gaps steps the warm-up over, rather than poisoning it — the common source of one is another study's own warm-up, and a study run over
sma(...)'s output must start late rather than come back empty (thersi(sma(...))failure the property tests exist for).smais the one exception, deliberately. Its window counts rows, not contributors — it emits once the window spansperiodrows and averages whichever of them are finite — so a leading gap does not shift its first value. That issma()'s contract, the onesma∘smapins; giving the engine'ssmaa step-over would makemovingAverage({ type: 'sma' })a second SMA that quietly disagreed with the first. Every other type in the menu waits forperiodfinite values and therefore shifts.Interior gaps: which types recover and which propagate
The Wilder asymmetry, stated per type rather than averaged into a slogan. A window kernel recovers once the gap leaves the window; a recursion has no state to carry across a hole, so whether it recovers depends on whether the recursion skips the missing bar or consumes it.
smaperiodrows and averages the finite ones (sma()'s contract, rows not contributors)wmaperiod−1after it readNaN: a linear weight is position-specific, so dropping one cell would silently reweight the resttrimawma's rule (both SMA stages want a finite window; a triangular weight is positional too)hullwma's rule, three windows deepemasmooth('ema'))dematemazlemaema, same skipsmmawilderValues)kamaperiodconsecutive differences, and the recursion carries the result forwardNeither answer is universally right and neither is fixable in the kernel: a caller who needs continuity across interior gaps fills before smoothing.
Cost
O(N) per type, independent of
period— nothing here rescans a window. The one that has to be written for it iswma: the definition is aperiod-term dot product per bar (O(N·period)), and this runs the running weighted sum instead,W(i) = W(i−1) − S(i−1) + period·x(i)alongside the plain window sumS. Both accumulators are rebuilt from the window on rows wherei % period === 0— one extra accumulation per row amortised, the same trick and the same cadenceranged.tsuses, so the cancellation inW − Scannot accumulate across a million bars.The composed types pay their parts:
dematwo EMA passes,temathree,trimatwo SMA passes,hullfour WMA passes (n/2,n, and the outer√n, plus the combine). All still O(N).