Academy/Strategy Code Breakdown/MACD Momentum — Code Walkthrough
Strategy Code BreakdownLesson 1

MACD Momentum — Code Walkthrough

Read every line of the MACD strategy: EMA math, the MACD/signal/histogram pipeline, and how crossover signals are detected.

12 minute read
4 key takeaways

MACD Momentum — Code Walkthrough

The MACD (Moving Average Convergence/Divergence) strategy is a classic momentum strategy. Below we walk through every function in macd-strategy.ts and explain what each line does and why.

Step 1 — Configuration Interface

typescript
export interface MACDConfig {
  fastPeriod: number;   // EMA period for the fast line (default 12)
  slowPeriod: number;   // EMA period for the slow line (default 26)
  signalPeriod: number; // EMA of MACD itself (default 9)
  stopLossPct: number;  // e.g. 0.08 = exit if trade goes -8%
  takeProfitPct: number;// e.g. 0.20 = exit when trade gains +20%
}

export const defaultConfig: MACDConfig = {
  fastPeriod: 12,
  slowPeriod: 26,
  signalPeriod: 9,
  stopLossPct: 0.08,
  takeProfitPct: 0.20,
};

The interface enforces type safety across the file. The defaults (12, 26, 9) come from Gerald Appel's original 1979 paper — they remain the industry standard starting point for daily candles.

Step 2 — calculateEMA: Exponential Moving Average

typescript
function calculateEMA(values: number[], period: number): number[] {
  const k = 2 / (period + 1);        // smoothing factor — larger period = smaller k
  const ema: number[] = [values[0]]; // seed with the first data point

  for (let i = 1; i < values.length; i++) {
    // Each EMA value blends today's price with yesterday's EMA
    ema.push(values[i] * k + ema[i - 1] * (1 - k));
  }
  return ema;
}

Why k = 2 / (period + 1)?

This formula ensures that a period-N EMA gives roughly the same weight as an N-period SMA at the center, while the exponential decay means older data contributes less and less. A 12-period EMA has k ≈ 0.154, so each new price contributes ~15% of the new value.

The loop: ema[i] = price[i] × k + ema[i-1] × (1-k). This is a one-pass O(n) calculation. No window slicing, no extra memory — just a running average that reacts faster to recent changes than a simple moving average.

Step 3 — calculateMACD: Building the Three Lines

typescript
export function calculateMACD(closes, config) {
  const fastEMA = calculateEMA(closes, config.fastPeriod);  // reacts quickly
  const slowEMA = calculateEMA(closes, config.slowPeriod);  // reacts slowly

  // MACD Line = fast EMA − slow EMA
  // Positive = fast has risen faster than slow → bullish momentum
  // Negative = fast has fallen faster than slow → bearish momentum
  const macd = fastEMA.map((f, i) => f - slowEMA[i]);

  // Signal Line = EMA of MACD (smoothed version of the momentum line)
  const signal = calculateEMA(macd, config.signalPeriod);

  // Histogram = MACD − Signal (visualises divergence between the two)
  const histogram = macd.map((m, i) => m - signal[i]);

  return { macd, signal, histogram };
}
  • MACD Line: Difference between fast and slow EMA. Positive = bullish momentum.
  • Signal Line: A smoothed (9-period EMA) version of MACD. Reduces noise.
  • Histogram: Distance between MACD and Signal. Growing histogram = momentum accelerating.

Step 4 — generateSignals: Detecting Crossovers

typescript
export function generateSignals(closes, config) {
  const { macd, signal } = calculateMACD(closes, config);

  return closes.map((_, i) => {
    if (i === 0) return { index: i, signal: 0, macd: macd[i], signalLine: signal[i] };

    // Bullish Cross: MACD was below signal, now crossed above → BUY
    const bullishCross = macd[i - 1] < signal[i - 1] && macd[i] > signal[i];

    // Bearish Cross: MACD was above signal, now crossed below → SELL
    const bearishCross = macd[i - 1] > signal[i - 1] && macd[i] < signal[i];

    const s = bullishCross ? 1 : bearishCross ? -1 : 0;
    return { index: i, signal: s, macd: macd[i], signalLine: signal[i] };
  });
}

The Crossover Logic

We compare [i-1] vs [i] for both lines. If yesterday MACD < Signal AND today MACD > Signal, a crossover just happened. This is the simplest reliable way to detect line intersections in a 1D array without floating-point comparison issues.

Signal ValueMeaningAction
+1Bullish crossover detectedEnter long position
-1Bearish crossover detectedExit long / enter short
0No crossover this barHold current position
Key Takeaways
  • EMA applies an exponentially-decaying weight so recent prices matter more
  • MACD = fast EMA − slow EMA; measures momentum direction
  • The signal line is an EMA of MACD itself, smoothing the momentum curve
  • A bullish cross occurs when MACD crosses above signal from below

Open MACD Strategy in Lab

Clone the MACD strategy and run a live backtest