MACD Momentum — Code Walkthrough
Read every line of the MACD strategy: EMA math, the MACD/signal/histogram pipeline, and how crossover signals are detected.
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
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
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
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
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 Value | Meaning | Action |
|---|---|---|
| +1 | Bullish crossover detected | Enter long position |
| -1 | Bearish crossover detected | Exit long / enter short |
| 0 | No crossover this bar | Hold current position |
- 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