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();
}
});