feature

MACD

Public library indicator MACD (macd). Same source as the Chartnaut-owned row in the live indicator table.

What it plots

Pane MACD line, signal line, and histogram of their difference.

Key decisions

Piece

Why it matters

Fast/slow EMA of close

MACD = fast − slow; seed each EMA from SMA at first full period.

Signal = EMA of MACD

Needs its own buffer after the MACD line exists.

ctx.bar("histogram", …)

Histogram series uses bar/plot helper (not only ctx.plot).

Full script

meta({
  shortName: "MACD",
  kind: "pane",
  dataSource: "candles",
  category: "custom",
});

dialog({
  title: "MACD Configuration",
  description: "Configure MACD settings",
  width: 400,
  height: 450,
  resizable: false,
  showEnabledCheckbox: true,
  resetToDefaults: {
    enabled: true,
    confirmationMessage: "Are you sure you want to reset all MACD settings to their default values?",
  },
});

const fastPeriod = input.number({
  id: "fastPeriod",
  label: "Fast Period",
  default: 12,
  min: 1,
  step: 1,
});

const slowPeriod = input.number({
  id: "slowPeriod",
  label: "Slow Period",
  default: 26,
  min: 1,
  step: 1,
});

const signalPeriod = input.number({
  id: "signalPeriod",
  label: "Signal Period",
  default: 9,
  min: 1,
  step: 1,
});

const macdColor = input.color({
  id: "macdColor",
  label: "MACD Color",
  default: "#2962FF",
  allowAlpha: true,
});

const signalColor = input.color({
  id: "signalColor",
  label: "Signal Color",
  default: "#FF6D00",
  allowAlpha: true,
});

const histColor = input.color({
  id: "histColor",
  label: "Histogram Color",
  default: "#26A69A",
  allowAlpha: true,
});

const lineStyle = input.lineStyle({
  id: "lineStyle",
  label: "Line Style",
  default: LineStyle.Solid,
});

const labelsEnabled = input.boolean({
  id: "labelsEnabled",
  label: "Show Labels",
  default: true,
});

layout(
  tab({ id: "general", title: "General" },
    section({ id: "parameters", title: "Parameters" }, fastPeriod, slowPeriod, signalPeriod),
    section({ id: "display", title: "Display Options" }, labelsEnabled),
  ),
  tab({ id: "appearance", title: "Appearance" },
    section({ id: "colors", title: "Colors", columns: 2 }, macdColor, signalColor, histColor, lineStyle),
  ),
);

output.line({
  id: "macd",
  lineWidth: 2,
  colorFrom: macdColor,
  lineStyleFrom: lineStyle,
  labelFrom: labelsEnabled,
  titleWhenLabeled: "MACD",
});

output.line({
  id: "signal",
  lineWidth: 1,
  colorFrom: signalColor,
  lineStyleFrom: lineStyle,
  labelFrom: labelsEnabled,
  titleWhenLabeled: "Signal",
});

output.histogram({
  id: "histogram",
  colorFrom: histColor,
  labelFrom: labelsEnabled,
  titleWhenLabeled: "Histogram",
});

/** @param {ScriptedCtx} ctx */
export function onBar(ctx) {
  var fp = ctx.params.fastPeriod || 12;
  var sp = ctx.params.slowPeriod || 26;
  var sigP = ctx.params.signalPeriod || 9;

  var closeBuf = ctx.accum("closeBuf", [], function (prev) {
    prev.push(ctx.close);
    return prev;
  });

  var len = closeBuf.length;

  // Fast EMA
  var fastK = 2 / (fp + 1);
  var fastEma;
  if (len < fp) return;
  if (len === fp) {
    var fSum = 0;
    for (var i = 0; i < fp; i++) fSum += closeBuf[i];
    fastEma = fSum / fp;
  } else {
    var prevFast = ctx.accum("fastEma", 0, function (prev) { return prev; });
    fastEma = ctx.close * fastK + prevFast * (1 - fastK);
  }
  ctx.accum("fastEma", 0, function () { return fastEma; });

  // Slow EMA
  var slowK = 2 / (sp + 1);
  var slowEma;
  if (len < sp) return;
  if (len === sp) {
    var sSum = 0;
    for (var j = 0; j < sp; j++) sSum += closeBuf[j];
    slowEma = sSum / sp;
  } else {
    var prevSlow = ctx.accum("slowEma", 0, function (prev) { return prev; });
    slowEma = ctx.close * slowK + prevSlow * (1 - slowK);
  }
  ctx.accum("slowEma", 0, function () { return slowEma; });

  var macdVal = fastEma - slowEma;

  var macdBuf = ctx.accum("macdBuf", [], function (prev) {
    prev.push(macdVal);
    return prev;
  });

  // Signal EMA of MACD line
  var sigK = 2 / (sigP + 1);
  var signal;
  if (macdBuf.length < sigP) {
    ctx.plot("macd", macdVal);
    return;
  }
  if (macdBuf.length === sigP) {
    var sigSum = 0;
    for (var k = 0; k < sigP; k++) sigSum += macdBuf[k];
    signal = sigSum / sigP;
  } else {
    var prevSig = ctx.accum("signal", 0, function (prev) { return prev; });
    signal = macdVal * sigK + prevSig * (1 - sigK);
  }
  ctx.accum("signal", 0, function () { return signal; });

  ctx.plot("macd", macdVal);
  ctx.plot("signal", signal);
  ctx.bar("histogram", macdVal - signal);
}

Next

  1. Indicator examples

  2. Indicator series types

  3. Declaring indicator dependencies