Academy/Strategy Code Breakdown/RSI Mean Reversion — Code Walkthrough
Strategy Code BreakdownLesson 2

RSI Mean Reversion — Code Walkthrough

Walk through the RSI calculation step by step: first-pass averages, Wilder's smoothing, and the oversold/overbought signal logic.

11 minute read
4 key takeaways

RSI Mean Reversion — Code Walkthrough

RSI (Relative Strength Index) was developed by J. Welles Wilder in 1978. This strategy buys when RSI dips into "oversold" territory and sells when it becomes "overbought" — betting that extreme moves will revert to the mean.

Step 1 — Configuration

typescript
export interface RSIConfig {
  rsiPeriod: number;           // lookback window (default 14)
  oversoldThreshold: number;   // buy signal below this (default 30)
  overboughtThreshold: number; // sell signal above this (default 70)
  stopLossPct: number;         // 0.05 = -5% stop loss
  takeProfitPct: number;       // 0.15 = +15% take profit
}

export const defaultConfig: RSIConfig = {
  rsiPeriod: 14,
  oversoldThreshold: 30,
  overboughtThreshold: 70,
  stopLossPct: 0.05,
  takeProfitPct: 0.15,
};

Wilder's original parameters (14, 30, 70) are used. The 30/70 thresholds are not magic numbers — they were chosen so signals fire ~5% of the time in trending markets, balancing frequency with quality.

Step 2 — calculateRSI: Two Phases of Calculation

typescript
export function calculateRSI(closes: number[], period = 14): number[] {
  const rsi: number[] = new Array(period).fill(NaN); // no RSI for first N bars
  let avgGain = 0;
  let avgLoss = 0;

  // PHASE 1: Simple average over first 'period' bars
  for (let i = 1; i <= period; i++) {
    const change = closes[i] - closes[i - 1];
    if (change > 0) avgGain += change;
    else avgLoss += Math.abs(change);
  }
  avgGain /= period;
  avgLoss /= period;

Why Fill NaN for the First 14 Bars?

RSI requires 14 previous closes to compute. Before that, the value is mathematically undefined — not zero. Returning NaN makes it explicit so the signal generator can skip those bars safely.

typescript
  // PHASE 2: Wilder's Smoothed Moving Average from bar N onward
  for (let i = period; i < closes.length; i++) {
    const change = closes[i] - closes[i - 1];
    const gain = change > 0 ? change : 0;
    const loss = change < 0 ? Math.abs(change) : 0;

    // Wilder's smoothing: (prev_avg × 13 + today) / 14
    avgGain = (avgGain * (period - 1) + gain) / period;
    avgLoss = (avgLoss * (period - 1) + loss) / period;

    const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
    rsi.push(100 - 100 / (1 + rs));
  }
  return rsi;
}

Wilder's smoothing is a variation of EMA with multiplier 1/period instead of 2/(period+1). It gives more weight to history, making RSI less "jumpy" than a plain EMA approach. The formula 100 − 100/(1+RS) maps the gain/loss ratio to 0–100, approaching 100 when there are no losses and 0 when there are no gains.

Step 3 — generateSignals: Threshold Crossings

typescript
export function generateSignals(closes, config) {
  const rsiValues = calculateRSI(closes, config.rsiPeriod);

  return rsiValues.map((rsi, i) => {
    if (isNaN(rsi)) return { index: i, signal: 0, rsi }; // skip warmup period

    if (rsi < config.oversoldThreshold)   return { index: i, signal: 1, rsi };  // BUY
    if (rsi > config.overboughtThreshold) return { index: i, signal: -1, rsi }; // SELL
    return { index: i, signal: 0, rsi };                                          // HOLD
  });
}

Limitation: Level-Based, Not Crossing-Based

This signal fires every bar RSI is below 30, not just when it first crosses. In a deep oversold condition you'd get repeated BUY signals. Production strategies often check the "first cross" by comparing RSI[i-1] vs RSI[i] across the threshold, similar to MACD crossover logic.

Key Takeaways
  • RSI measures the speed and magnitude of recent price changes on a 0–100 scale
  • First 14 bars use a simple average gain/loss; subsequent bars use Wilder's smoothing
  • RSI < 30 = oversold (potential reversal up); RSI > 70 = overbought (potential reversal down)
  • Signal fires on the threshold crossing, not the extreme level

Open RSI Strategy in Lab

Clone the RSI strategy and run a live backtest