RSI seems simple until you try to implement it correctly. The formula everyone knows: RSI = 100 - (100 / (1 + RS)), where RS = Average Gain / Average Loss. The part that's almost always wrong in open-source implementations: how you calculate "Average Gain" and "Average Loss."

The wrong way (simple moving average)

// WRONG - This is what most tutorials show
const avgGain = gains.slice(-14).reduce((a, b) => a + b, 0) / 14;
const avgLoss = losses.slice(-14).reduce((a, b) => a + b, 0) / 14;

This produces RSI values that don't match TradingView, MT4, or any professional platform.

The correct way (Wilder's Exponential Smoothing)

Wilder's method works like this:

  • Initial average gain/loss: simple average of the first 14 periods
  • Every subsequent period: avgGain = (prevAvgGain × 13 + currentGain) / 14

This is a specific form of exponential smoothing with alpha = 1/14. The smoothed values "remember" more history, making Wilder's RSI slightly smoother and slower to react than a naive implementation.

let avgGain = gains.slice(0, 14).reduce((a, b) => a + b, 0) / 14;
let avgLoss = losses.slice(0, 14).reduce((a, b) => a + b, 0) / 14;

for (let i = 14; i < gains.length; i++) {
  avgGain = (avgGain * 13 + gains[i]) / 14;
  avgLoss = (avgLoss * 13 + losses[i]) / 14;
  rsiValues[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
}

ADX: the hardest indicator to implement correctly

ADX is significantly more complex than RSI. It involves three sequential calculations:

  1. True Range (TR): max(high - low, |high - prevClose|, |low - prevClose|) — the inter-candle gap terms (|high - prevClose| and |low - prevClose|) are the part most implementations miss. Without them, gapped markets (Crypto on weekends) produce incorrect ATR and ADX values.
  1. Directional Movement (+DM / -DM): upMove = high - prevHigh, downMove = prevLow - low. If upMove > 0 and upMove > downMove: +DM = upMove, else 0. If downMove > 0 and downMove > upMove: -DM = downMove, else 0.
  1. Wilder Smoothing on TR, +DM, -DM: Uses the same smoothing as RSI: smoothedTR = prevSmoothedTR - (prevSmoothedTR / 14) + currentTR.
  1. +DI / -DI: +DI = (smoothed+DM / smoothedTR) × 100.
  1. DX: |+DI - -DI| / (+DI + -DI) × 100.
  1. ADX: Wilder smoothing on DX values over 14 periods.

When you implement all six steps correctly, the ADX value matches TradingView's reading to within rounding error. When any step is wrong — particularly the inter-candle gap terms in TR — the ADX diverges, especially on assets with significant overnight gaps or weekend crypto moves.

---

Mustafa Kamal Hossain

Mustafa Kamal Hossain

Founder & Principal Engineer at Manfi. Passionate about Laravel, SaaS architecture, and high-performance engineering.