ATR calculation from MT5 candles: true range and Wilder smoothing

5 min readMetaKit

Two charts can display different ATR values for the same symbol and period without either price feed being broken. They may use different smoothing, different starting history, or a candle that is still forming. Before using Average True Range in an application, define exactly which series you intend to calculate.

This guide calculates a specified Wilder-style ATR from MT5 candle data. It focuses on indicator arithmetic and reproducibility. It does not choose a stop multiplier, forecast a direction, or promise an exact match with every terminal indicator. The existing trailing-stop guide covers an application of volatility; this article supplies the calculation contract that an implementation needs first.

True range includes the previous close

MetaTrader's ATR documentation describes true range using the current high and low together with the preceding close. Express the calculation as:

TR = max(high - low, abs(high - previous close), abs(low - previous close))

Consider a synthetic previous close of 1.1000 and a new bar with high 1.1040 and low 1.1020. The ordinary high-low range is 0.0020. The distances from the previous close are 0.0040 and 0.0020, so true range is 0.0040. Ignoring the gap halves the input to the indicator in this example.

Those figures are price units. With an explicitly chosen EURUSD pip convention of 0.0001, the true range is 40 pips. ATR is not inherently a pip value, dollar amount or percentage. Converting it requires an instrument-specific unit definition; the pip-value guide explains why decimal digits and pips should not be treated as interchangeable.

Define the average and its seed

For the variant here, take the arithmetic mean of the first period true ranges as the initial ATR. For each subsequent true range, use:

next ATR = ((period - 1) × previous ATR + current TR) / period

The TA-Lib ATR implementation documents Wilder smoothing and an initial average. This article's small JavaScript implementation is independent example code; it does not reproduce TA-Lib's complete lookback, compatibility or numerical implementation options.

A rolling simple average replaces the oldest observation on every step. The recursive calculation above instead carries information forward through the previous ATR. An exponential average using a coefficient of 2 / (period + 1) is another different calculation. Naming all three “ATR 14” does not make their outputs identical.

For a short synthetic period of three, true ranges of 3, 4 and 3 produce an initial ATR of 10 / 3, approximately 3.3333. If the next true range is 5, the recursive result is 35 / 9, approximately 3.8889. A three-value rolling average of 4, 3 and 5 would instead be 4.0.

A calculation with explicit warm-up values

This function accepts chronological, finalized high/low/close bars. It uses the first bar only to supply a previous close, so the first output appears at index period. Earlier values are null, meaning insufficient input rather than zero volatility.

function wilderAtr(bars, period = 14) {
  if (!Number.isSafeInteger(period) || period < 1 ||
      !Array.isArray(bars) || bars.length < period + 1) {
    throw new Error("Need a positive period and at least period + 1 bars");
  }
  for (const bar of bars) {
    if (!bar || ![bar.high, bar.low, bar.close].every(Number.isFinite) ||
        bar.low <= 0 || bar.high < bar.low ||
        bar.close < bar.low || bar.close > bar.high) {
      throw new Error("Invalid positive-price OHLC sample");
    }
  }
  const result = Array(bars.length).fill(null);
  let seed = 0;
  let atr = 0;
  for (let i = 1; i < bars.length; i++) {
    const b = bars[i], previous = bars[i - 1].close;
    const tr = Math.max(b.high - b.low,
      Math.abs(b.high - previous), Math.abs(b.low - previous));
    if (i <= period) seed += tr;
    if (i === period) atr = seed / period;
    else if (i > period) atr = (atr * (period - 1) + tr) / period;
    if (i >= period) {
      if (!Number.isFinite(atr)) throw new Error("ATR overflow");
      result[i] = atr;
    }
  }
  return result;
}

For example, an initial bar closing at 100 followed by high/low/close bars (102,99,101), (104,100,103), (103,100,101) and (106,102,105) generates the true ranges used above. These unit-scale prices are synthetic arithmetic fixtures, not forex quotations. The calculation was checked for the seed, next recursive value, period one, constant prices and invalid inputs.

Collect enough finalized history

MetaKit exposes GET /v1/accounts/{id}/candles with a symbol, timeframe and optional range/count parameters. Its current response contains data and truncated, with a 5,000-candle cap. There is no public ATR endpoint in the current route contract: fetch the candles and calculate your chosen indicator in your application.

The current no-range path includes the latest bar. Determine whether that bar is complete before using it in a closed-bar series. Preserve its opening timestamp and the observation time; do not describe a partial H1 candle as a finalized hourly observation. The historical-data walkthrough covers retrieval rather than indicator parity.

Fetch more than the minimum if you need a stable comparison with a long-running recursive series. Starting the recursion from a different historical point changes the carried state. If you persist an ATR checkpoint, store the previous close, last processed bar, period, smoothing convention and source identity together. Recalculate when historical bars change instead of appending onto an incompatible seed.

Explain differences before changing the formula

When comparing an application result with a chart, first match the exact account symbol and timeframe. Then compare finalized OHLC inputs, gap handling, seed, first output index and smoothing rule. Finally check whether both values refer to the same completed bar. A difference at this stage is a diagnostic, not proof that one provider is wrong.

Do not repair missing candles by inventing flat bars merely to keep the array regular. That inserts zero ranges and changes volatility. Treat session closures and unexplained missing observations separately. Likewise, duplicated boundary bars can count the same range twice unless collection is reconciled before calculation.

Keep sufficient precision during the recursion, then round only for display. A label such as “Wilder ATR, H1, period 14, closed bars, seed from this dataset” makes the output reproducible. If you normalize by price, call that a separately defined percentage series and specify the denominator.

For integration details, use the MetaKit API documentation. ATR describes the chosen historical price variation; it is not a guaranteed future range or a substitute for checking costs, contract constraints and execution conditions.

Sources and verification

Verified 20 October 2026 against MetaKit's candle route and serializer. All worked paths are synthetic; no live indicator comparison was performed.