Exciting Update: Version 1.0.0 is now available, introducing high-performance technical indicators and custom drawing tools. Read more
Version: 1.0.0

MACD Indicator

Calculate and render MACD line overlays and histograms on a dedicated sub-pane.

Overview

The Moving Average Convergence Divergence (MACD) is a trend-following momentum indicator showing the relationship between two moving averages of price. It consists of the MACD line, a Signal line, and a volume-like histogram.

Calculation Model

  • MACD Line: 12-period EMA - 26-period EMA.
  • Signal Line: 9-period EMA of the MACD Line.
  • Histogram: MACD Line - Signal Line.

MACD Indicator Code

Below is the full implementation of the MACD indicator from `macd.js`:

window.ChartingAPI.registerIndicator('macd', {
  name: 'MACD',
  type: 'pane',
  levels: [0],
  params: { fast: 12, slow: 26, signal: 9 },
  defaultColor: '#2196F3',
  calculate: function(bars, params) {
    const fast = params.fast || 12;
    const slow = params.slow || 26;
    const signal = params.signal || 9;
    const closes = bars.map(b => b.close);

    function calculateEma(data, period) {
      const ema = new Array(data.length).fill(null);
      if (data.length < period) return ema;
      const k = 2 / (period + 1);
      let sum = 0;
      for (let j = 0; j < period; j++) sum += data[j];
      let prev = sum / period;
      ema[period - 1] = prev;
      for (let i = period; i < data.length; i++) {
        prev = data[i] * k + prev * (1 - k);
        ema[i] = prev;
      }
      return ema;
    }

    const fastEma = calculateEma(closes, fast);
    const slowEma = calculateEma(closes, slow);

    const macdLine = new Array(closes.length).fill(null);
    for (let i = 0; i < closes.length; i++) {
      if (fastEma[i] !== null && slowEma[i] !== null) {
        macdLine[i] = fastEma[i] - slowEma[i];
      }
    }

    const signalLine = calculateEma(macdLine, signal);
    const hist = new Array(closes.length).fill(null);
    for (let i = 0; i < closes.length; i++) {
      if (macdLine[i] !== null && signalLine[i] !== null) {
        hist[i] = macdLine[i] - signalLine[i];
      }
    }

    return { line: macdLine, signal: signalLine, hist: hist };
  },
  render: function(ctx, chart, values, bounds, color) {
    const { startIndex, endIndex, toY } = bounds;

    // 1. Draw Histogram Bars
    ctx.save();
    const barW = Math.max(1, chart.candleWidth - 1);
    for (let i = startIndex; i <= endIndex; i++) {
      if (i >= chart.bars.length) break;
      const hVal = values.hist[i];
      if (hVal == null) continue;

      const x = chart.barToX(i);
      const yZero = toY(0);
      const yVal = toY(hVal);

      ctx.fillStyle = hVal >= 0 ? 'rgba(38, 166, 154, 0.5)' : 'rgba(239, 83, 80, 0.5)';
      ctx.fillRect(x - barW / 2, Math.min(yZero, yVal), barW, Math.abs(yVal - yZero));
    }
    ctx.restore();

    // 2. Draw MACD Line
    ctx.beginPath();
    ctx.strokeStyle = color || '#2196F3';
    ctx.lineWidth = 1.2;
    let started = false;
    for (let i = startIndex; i <= endIndex; i++) {
      if (i >= chart.bars.length) break;
      const v = values.line[i];
      if (v == null) { started = false; continue; }
      const x = chart.barToX(i);
      const y = toY(v);
      if (!started) { ctx.moveTo(x, y); started = true; }
      else { ctx.lineTo(x, y); }
    }
    ctx.stroke();

    // 3. Draw Signal Line
    ctx.beginPath();
    ctx.strokeStyle = '#FF9800'; // orange signal line
    ctx.lineWidth = 1.2;
    started = false;
    for (let i = startIndex; i <= endIndex; i++) {
      if (i >= chart.bars.length) break;
      const v = values.signal[i];
      if (v == null) { started = false; continue; }
      const x = chart.barToX(i);
      const y = toY(v);
      if (!started) { ctx.moveTo(x, y); started = true; }
      else { ctx.lineTo(x, y); }
    }
    ctx.stroke();
  }
});