D07-F01-A04 / Complete engineering topic

Wilder RMA: Build the Smoothing Engine Behind RSI and ATR

Derive, implement, and verify an SMA-seeded Wilder moving average without hiding initialization or state.

D07 · TECHNICAL INDICATORS
D07-F01-A04Canonical / Tested / Open
D07 / D07-F01

Suppose two systems both report a “14-period Wilder average,” yet their first values disagree and the difference persists. The recurrence may be correct in both systems. The mismatch can come from the seed, warm-up alignment, missing rows, or rounded state.

That is why Wilder's Moving Average—often called RMA, Wilder smoothing, a modified moving average, or sometimes SMMA—needs a complete contract rather than a one-line formula.

In this tutorial we will calculate an SMA-seeded RMA, implement it in Python and TypeScript, compare it with an equal-period EMA, and test the operational details that determine reproducibility. The method is historically associated with J. Welles Wilder's 1978 indicator systems; the institutional record for his original book establishes that source context.

What RMA gives a practitioner and a builder

For a practitioner, RMA provides a smoother causal view of an observed series. For a builder, it provides an O(1)O(1)-state recurrence used inside later indicators. Official TC2000 methodology identifies Wilder smoothing in both Wilder's RSI and traditional ATR.

By the end, you will be able to:

  • derive alpha from the declared period;
  • distinguish warm-up, seed, and recursive updates;
  • explain why a period-14 RMA is not a period-14 EMA;
  • preserve full-precision state across updates and restarts;
  • verify two language implementations against one fixture.

The only mathematical prerequisite is the Simple Moving Average (SMA), which we use once to initialize the state.

Intuition: keep most of yesterday, accept a fraction of today

Let Rt1R_{t-1} be the prior RMA and xtx_t the current observation. With period nn, the update is:

Rt=Rt1+1n(xtRt1)R_t=R_{t-1}+\frac{1}{n}(x_t-R_{t-1})

Read the formula from the inside out:

  1. Measure the gap between the current value and the prior state.
  2. Take one nn-th of that gap.
  3. Add the adjustment to the prior state.

The equivalent weighted form is:

Rt=n1nRt1+1nxtR_t=\frac{n-1}{n}R_{t-1}+\frac1n x_t

Those coefficients are non-negative and sum to one. The new state must therefore lie between the prior state and current value. That convex-bound property is useful in tests.

See one exact update

In the canonical example, the prior state is 11.666711.6667, the current value is 1515, and the period is 33. Notice that the new state moves exactly one third of the gap:

One period-three RMA update

The full-precision calculation is:

R4=353+13(15353)=1159R_4=\frac{35}{3}+\frac13\left(15-\frac{35}{3}\right) =\frac{115}{9}

Only the label is rounded. The fraction—or its full binary floating-point equivalent—continues into the next update.

Initialization is part of the algorithm

The recurrence needs a prior state. Our canonical finance-style policy waits for nn valid observations, then uses their SMA:

Rn=x1+x2++xnnR_n=\frac{x_1+x_2+\cdots+x_n}{n}

Outputs before observation nn are null. After the seed exists, every new observation advances the recurrence once.

This is consistent with maintained classic Wilder implementations. TA-Lib's RSI source accumulates an initial period average and describes subsequent Wilder updates as multiplying the prior value by period - 1, adding today's value, and dividing by period.

A first-value seed is also possible:

R1=x1R_1=x_1

It starts immediately but produces a different early path. Neither choice should be hidden behind a library default. Period, seed, warm-up alignment, and reset policy must travel with the result.

RMA versus EMA: the period labels are not interchangeable

A common finance-style EMA converts span mm to:

αEMA=2m+1\alpha_{\text{EMA}}=\frac{2}{m+1}

RMA uses:

αRMA=1n\alpha_{\text{RMA}}=\frac1n

At equal labels m=n>1m=n>1, the RMA alpha is smaller. It reacts more slowly. To give an EMA the same alpha as an nn-period RMA:

2m+1=1n\frac{2}{m+1}=\frac1n m=2n1m=2n-1

The TC2000 ATR methodology documents this conversion. A 14-period RMA recurrence matches an EMA alpha commonly labeled span 27—but only when the seed and output alignment also match.

The following visual holds the period label and SMA seed constant. The RMA uses alpha 1/31/3, while the EMA uses alpha 1/21/2:

Period-three RMA compared with period-three EMA

Both begin at 35/335/3. By observation six, the RMA is 1198/811198/81, or 14.7901, while the equal-period EMA is 95/695/6, or 15.8333.

The complete data contract

A numeric array is sufficient for a small function, but a reproducible financial series needs more:

ConcernCanonical rule
OrderingUnique, strictly increasing timestamp or sequence key
ValueFinite number; zero is valid
IdentityStable series identifier
UnitStable currency, range, return points, count, or other declared unit
BasisOne adjustment, FX, inflation, or constituent methodology
PeriodPositive integer
SeedSMA of first period observations
Missing valueReject; never silently turn into zero
Missing rowNo update in observation-space mode
RoundingPresentation only
CorrectionReplay the dependent suffix

Fixed-alpha RMA decays per accepted observation, not per wall-clock hour. An overnight gap and a one-minute gap both represent one update if each contributes one row. A time-aware smoother is a different algorithm.

Similarly, mixing adjusted and unadjusted prices inside one state history is invalid. The resulting jump is a data-basis break, not market movement.

Walk through the canonical fixture

Use period 3 and the synthetic values:

10, 13, 12, 15, 14, 1810,\ 13,\ 12,\ 15,\ 14,\ 18
ObservationValueExact RMADisplayState
110nullWarming
213nullWarming
31235/335/311.6667SMA seed
415115/9115/912.7778Recursive
514356/27356/2713.1852Recursive
6181198/811198/8114.7901Recursive

The first two values only contribute to the seed sum. Observation three creates the first numeric state. Each later state depends on the prior full-precision RMA.

Use the interactive causal playground to step through the same values. Change the period to alter alpha and warm-up, then switch to first-value initialization to see which differences come from the seed rather than the recurrence. The static comparison above remains the complete fallback when scripts or motion are unavailable.

A real index window: observations versus derived arithmetic

A historical example is useful here only if its evidence boundary is explicit. Nasdaq Trader's year-to-date file reports these Nasdaq Composite Index closing values. The close column is observed provider data. The RMA and EMA columns are author-derived from those six values with period 3, the same three-observation SMA seed, one update per listed session, and no intermediate rounding.

Session dateNasdaq observed closeDerived RMA, alpha 1/3Derived equal-period EMA, alpha 1/2
2026-03-0222,748.86
2026-03-0322,516.69
2026-03-0422,807.4822,691.010022,691.0100
2026-03-0522,748.9922,710.336722,720.0000
2026-03-0622,387.6822,602.784422,553.8400
2026-03-0922,695.9522,633.839622,624.8950

Six Nasdaq Composite observations with author-derived RMA and EMA paths

The common seed is 22,691.01. When the observed close falls to 22,387.68 on 2026-03-06, the equal-period EMA moves farther because alpha (1/2) is larger than the RMA's (1/3). Both then move back toward the 2026-03-09 observation. This visual demonstrates smoothing mechanics; it does not show causation, predictive value, indicator superiority, or a tradable strategy. The extract does not establish the file's historical publication timestamp or revision chain.

Provider context: Nasdaq Trader Daily Market Files, Nasdaq data definitions, and the 2026 year-to-date CSV, retrieved 2026-07-22T23:23:41Z.

Translate the contract into code

The reference Python core is intentionally small:

Python
alpha = 1.0 / period
seed_sum = 0.0
rma = None

for observation, value in enumerate(numbers, start=1):
    if observation <= period:
        seed_sum += value
    if observation < period:
        output.append(None)
    elif observation == period:
        rma = seed_sum / period
        output.append(rma)
    else:
        rma += alpha * (value - rma)
        output.append(rma)

The TypeScript implementation follows the same state transitions:

TypeScript
const alpha = 1 / period;
let seedSum = 0;
let rma: number | null = null;

numbers.forEach((value, zeroBasedIndex) => {
  const observation = zeroBasedIndex + 1;
  if (observation <= period) seedSum += value;
  if (observation < period) output.push(null);
  else if (observation === period) {
    rma = seedSum / period;
    output.push(rma);
  } else {
    rma = rma + alpha * (value - rma);
    output.push(rma);
  }
});

The delivered source adds validation around these blocks. Booleans are rejected even though Python treats bool as an integer subtype. NaN, infinity, null, non-integer periods, and non-positive periods are also invalid.

Test behavior, not just example values

Both languages consume the same JSON fixture. The tests verify the canonical path within 101210^{-12}, but example parity alone is not enough.

Useful invariants include:

  • Period one is identity: alpha equals one, so output equals input.
  • Constant preservation: a constant series remains constant after seed.
  • Convex bounds: every update lies between prior state and current input.
  • Scale and translation equivariance: applying ax+ba x+b with positive aa before smoothing gives aR+baR+b.
  • Non-mutation: calculation does not alter the source array.
  • Warm-up alignment: a short series returns only null values.

These tests establish implementation correctness. They do not show that an RMA crossover predicts prices, survives transaction costs, or generalizes out-of-sample.

Operational failure modes

Rounding recursive state

If each displayed value is rounded and then reused, the error compounds. Persist and recurse with full precision.

Treating a missing row as zero

Zero is a real observation. Substituting it for missing data introduces a large artificial move. Reject or apply an explicit upstream imputation policy.

Correcting history without replay

A correction changes every dependent state. If xjx_j changes by δ\delta, the immediate RMA changes by αδ\alpha\delta. After kk unchanged updates, the remaining difference is:

αδ(1α)k\alpha\delta(1-\alpha)^k

Replay from the start or from a verified checkpoint before the correction.

Comparing vendor labels without seed details

RMA, SMMA, modified moving average, and Wilder smoothing may share the recurrence while differing in initialization or indexing. Compare alpha, seed, warm-up, and missing-data rules—not the label alone.

Reading smoothness as prediction

RMA is causal and lagging. Its visual stability is not evidence of future returns or a profitable signal.

Where to use RMA

RMA is appropriate when you need:

  • Wilder-smoothed gains and losses for RSI;
  • Wilder-smoothed true range for ATR;
  • Wilder-smoothed directional movement for DMI and ADX;
  • a simple causal baseline with constant memory;
  • parity with a platform whose RMA conventions are explicit.

For each published output, include the source identity, unit, basis, period, alpha, seed, missing policy, and rounding policy.

Summary and next topic

Wilder RMA is a fixed-alpha recursive smoother with α=1/n\alpha=1/n. Its canonical first state is an nn-observation SMA, and its period is not a hard window. Equal-period RMA and EMA differ; an EMA recurrence matches RMA only when alpha, seed, and alignment all match.

You can now calculate, implement, and verify the smoothing engine. The natural next tutorial is RSI, where the same recurrence is applied separately to gains and losses before their relative-strength ratio is mapped to a bounded oscillator.

Asset map

Asset IDTopic sectionArticle placementVideo sceneSource pathStatic fallbackStatus
RMA-V01Algorithm lifecycleInitialization3visuals/mermaid/calculation-flow.mdPseudocodeReady
RMA-V02One recursive updateIntuition4visuals/static/rma-step.svgSame assetReady
RMA-V03RMA versus EMAComparison6visuals/static/rma-vs-ema.svgSame assetReady
RMA-V04Worked example and variantsCanonical fixture2, 3, 5, 6visuals/animated/rma-playground.htmlvisuals/static/rma-vs-ema.svgReady
RMA-V05Bounded historical mechanicsHistorical example6visuals/static/nasdaq-composite-rma-example.svgHistorical tableReady
RMA-CODEImplementationCode walkthrough7implementations/python/rma.py, implementations/typescript/rma.tsArticle code blocksReady
RMA-TESTValidationTests8tests/test_rma.py, tests/rma.test.tsWorked tableReady

Primary references

This tutorial is educational and does not provide investment advice.

Wilder RMA calculation flow

This diagram separates the finite warm-up from the causal recursive state.

Rendering system map…

The first numeric output is the simple-average seed. Every later output uses only prior state and the current observation, so no future data is revealed.

Takeaway: warm-up, initialization, and recurrence are three distinct states of one causal algorithm.

ReferencesPrimary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

Access date for methodology sources: 2026-07-20. Nasdaq market-statistics sources were retrieved at 2026-07-22T23:23:41Z. The executable fixture remains synthetic; the article also contains one small, attributed historical extract.

RMA-01 — New Concepts in Technical Trading Systems

  • Organization or authors: J. Welles Wilder
  • Source type: Original monograph; institutional repository record
  • Publication or effective date: 1978
  • Version: Trend Research, 141 pages, ISBN 0894590278
  • URL or DOI: University of Macedonia digital repository record
  • Accessed: 2026-07-20
  • Jurisdiction: General commodity and market technical analysis
  • Supports: Primary attribution, publication date, source scope, and the original context for RSI, true range, and directional movement systems.
  • Limitations: The repository record verifies the work and provides a licensed scan, but page-level quotations are not reproduced in this package.

RMA-02 — TA-Lib classic RSI implementation

  • Organization or authors: TA-Lib project; initial implementation credited to Mario Fortier
  • Source type: Maintained official open-source reference implementation
  • Publication or effective date: Repository history from 2000; current main branch accessed 2026-07-20
  • Version: Main branch source with 1999–2025 copyright header
  • URL or DOI: TA-Lib TA_RSI.c
  • Accessed: 2026-07-20
  • Jurisdiction: Open-source technical implementation
  • Supports: An initial period average; subsequent Wilder update by multiplying prior state by period - 1, adding the current value, and dividing by period; explicit compatibility differences in start alignment.
  • Limitations: The code smooths gains and losses inside RSI rather than exposing a standalone general-series RMA function. Its lookback and MetaStock compatibility options must not be generalized into this package's output indexing.

RMA-03 — TC2000 RSI and Wilder's RSI methodology

  • Organization or authors: TC2000 Software Company
  • Source type: Official platform indicator methodology
  • Publication or effective date: Continuously maintained help site
  • Version: Web edition accessed 2026-07-20
  • URL or DOI: RSI and Wilder's RSI
  • Accessed: 2026-07-20
  • Jurisdiction: General market technical analysis
  • Supports: Wilder smoothing as an exponential-average variant and the update previous average + (1/n) × (current - previous average).
  • Limitations: The page explains smoothing in the context of RSI and uses vendor-specific function syntax.

RMA-04 — TC2000 Average True Range methodology

  • Organization or authors: TC2000 Software Company
  • Source type: Official platform indicator methodology
  • Publication or effective date: Continuously maintained help site
  • Version: Web edition accessed 2026-07-20
  • URL or DOI: Average True Range
  • Accessed: 2026-07-20
  • Jurisdiction: General market technical analysis
  • Supports: Use of Wilder smoothing for traditional ATR and equivalence to an EMA configured with span 2n12n-1.
  • Limitations: Platform formula notation and its ATR defaults are vendor conventions, not a universal standalone RMA data contract.

RMA-05 — TradingView Pine Script RMA reference

  • Organization or authors: TradingView
  • Source type: Official language and built-in function reference
  • Publication or effective date: Pine Script version 6
  • Version: Reference manual accessed 2026-07-20
  • URL or DOI: Pine Script ta.rma reference
  • Accessed: 2026-07-20
  • Jurisdiction: Open technical documentation for a charting platform
  • Supports: The maintained RMA name, source and length interface, and current use as a charting primitive.
  • Limitations: Interactive reference content is vendor-specific and may not expose every initialization detail in static page extraction.

RMA-06 — Nasdaq Trader Daily Market Files

  • Organization or authors: Nasdaq, Inc.; Nasdaq Trader
  • Source type: Official exchange-operated market-statistics page, field definitions, and year-to-date CSV
  • Publication or effective date: 2026 year-to-date file
  • Version: CSV retrieved 2026-07-22T23:23:41Z
  • URL or DOI: Daily Market Files, Nasdaq data definitions, and 2026 CSV
  • Accessed: 2026-07-22T23:23:41Z
  • Jurisdiction: United States market statistics
  • Supports: Nasdaq identifies the year-to-date file as containing selected index closing values and lists the Nasdaq Composite Index field; the CSV supplies the six observed Composite values dated 2026-03-02 through 2026-03-09.
  • Limitations: RMA and EMA values are author-derived, not Nasdaq-published. The small extract is not evidence of causation, prediction, profitability, point-in-time availability, revision history, or a tradable portfolio. The package does not redistribute the full source file.

Evidence and design reconciliation

Sourced facts:

  • Wilder's 1978 work is the historical source context.
  • The recurrence retains (n1)/n(n-1)/n of prior state and applies 1/n1/n to the new observation.
  • Classic Wilder-style implementations use an initial period average before recursive updates.
  • An EMA with alpha 1/n1/n, commonly labeled span 2n12n-1, shares the post-seed recurrence.
  • RSI and ATR are established applications of Wilder smoothing.
  • Nasdaq Trader's year-to-date file contains Nasdaq Composite Index values; the six dated closes reproduced in the article are provider observations.

Explicit package choices:

  • The canonical input is any homogeneous finite numeric series, not only price.
  • The first RMA is the SMA of the first period observations.
  • Pre-seed output is null and aligned one-for-one with input.
  • Missing and non-finite values are rejected rather than silently skipped or imputed.
  • Period one is accepted as the identity transformation.
  • State uses full precision; rounding is for display only.
  • Historical corrections require suffix replay from a trusted earlier state.
  • The synthetic fixture is distributed under CC0-1.0.
  • Historical-example RMA and EMA columns are author-derived with declared period, alpha, seed, alignment, and rounding rules.

Claims intentionally not made:

  • RMA is not claimed to predict prices.
  • No period is described as universally optimal.
  • Equal-period RMA and EMA are not described as equivalent.
  • Implementation parity does not establish trading profitability.
rma.ts
/** SMA-seeded Wilder moving average (RMA) for D07-F01-A04. */

export class RMAValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "RMAValidationError";
  }
}

export function calculateRma(
  values: readonly number[],
  period: number,
): Array<number | null> {
  if (!Number.isSafeInteger(period) || period < 1) {
    throw new RMAValidationError("period must be a positive integer.");
  }
  if (!Array.isArray(values)) {
    throw new RMAValidationError("values must be an array.");
  }

  const numbers = values.map((value, index) => {
    if (typeof value !== "number" || !Number.isFinite(value)) {
      throw new RMAValidationError(`values[${index}] must be a finite number.`);
    }
    return value;
  });

  const alpha = 1 / period;
  const output: Array<number | null> = [];
  let seedSum = 0;
  let rma: number | null = null;

  numbers.forEach((value, zeroBasedIndex) => {
    const observation = zeroBasedIndex + 1;
    if (observation <= period) seedSum += value;
    if (observation < period) {
      output.push(null);
    } else if (observation === period) {
      rma = seedSum / period;
      output.push(rma);
    } else {
      rma = (rma as number) + alpha * (value - (rma as number));
      output.push(rma);
    }
  });
  return output;
}
Full-height labrma playgroundOpen full screen