{"spec_id":"indicator-macd","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// indicator-macd: MACD Technical Indicator Chart\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-05\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// Small LCG for reproducible pseudo-randomness (Math.random() is not seeded)\nlet seed = 42;\nfunction nextRandom() {\n  seed = (seed * 1664525 + 1013904223) % 4294967296;\n  return seed / 4294967296;\n}\n\nfunction ema(values, period) {\n  const k = 2 / (period + 1);\n  const result = [];\n  let prev = values[0];\n  values.forEach((value, i) => {\n    prev = i === 0 ? value : value * k + prev * (1 - k);\n    result.push(prev);\n  });\n  return result;\n}\n\nconst periods = 120;\nconst startDate = new Date(\"2024-03-01T00:00:00Z\");\nconst dates = [];\nconst closes = [];\nlet price = 182;\nfor (let i = 0; i < periods; i++) {\n  const d = new Date(startDate);\n  d.setUTCDate(d.getUTCDate() + i);\n  dates.push(`${d.getUTCMonth() + 1}/${d.getUTCDate()}`);\n  price += (nextRandom() - 0.47) * 3.2;\n  closes.push(price);\n}\n\nconst emaFast = ema(closes, 12);\nconst emaSlow = ema(closes, 26);\nconst macdLine = emaFast.map((v, i) => v - emaSlow[i]);\nconst signalLine = ema(macdLine, 9);\nconst histogram = macdLine.map((v, i) => v - signalLine[i]);\nconst zeroLine = dates.map(() => 0);\n\n// Find the strongest crossover (largest ensuing histogram swing) to give the\n// chart a focal point instead of pure data display.\nfunction strongestCrossover(hist) {\n  let best = null;\n  for (let i = 1; i < hist.length; i++) {\n    const bullish = hist[i - 1] <= 0 && hist[i] > 0;\n    const bearish = hist[i - 1] >= 0 && hist[i] < 0;\n    if (!bullish && !bearish) continue;\n    let strength = 0;\n    for (let j = i; j < Math.min(hist.length, i + 15); j++) {\n      if ((bullish && hist[j] < 0) || (bearish && hist[j] > 0)) break;\n      strength = Math.max(strength, Math.abs(hist[j]));\n    }\n    if (!best || strength > best.strength) best = { index: i, bullish, strength };\n  }\n  return best;\n}\nconst crossover = strongestCrossover(histogram);\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart -------------------------------------------------------------------\nconst bullColor = t.palette[0]; // #009E73 brand green — positive histogram / gain\nconst bearColor = t.palette[4]; // #AE3030 matte red — negative histogram / loss (semantic anchor)\nconst macdColor = t.palette[2]; // #4467A3 blue — MACD line\nconst signalColor = t.palette[3]; // #BD8233 ochre — signal line\n\n// Native Chart.js plugin (no external dependency) marking the strongest\n// bullish/bearish crossover — a distinctive, library-idiomatic way to give\n// the chart a storytelling focal point beyond generic combo-chart usage.\nconst crossoverPlugin = {\n  id: \"crossoverAnnotation\",\n  afterDatasetsDraw(chart) {\n    const meta = chart.getDatasetMeta(2); // MACD line dataset\n    const point = meta.data[crossover.index];\n    if (!point) return;\n    const { ctx, chartArea } = chart;\n    const markerColor = crossover.bullish ? bullColor : bearColor;\n    ctx.save();\n    ctx.beginPath();\n    ctx.arc(point.x, point.y, 9, 0, Math.PI * 2);\n    ctx.lineWidth = 2.5;\n    ctx.strokeStyle = markerColor;\n    ctx.stroke();\n\n    const label = crossover.bullish ? \"Strongest bullish crossover\" : \"Strongest bearish crossover\";\n    const labelBelow = point.y - 24 < chartArea.top;\n    const labelY = labelBelow ? point.y + 26 : point.y - 16;\n    let align = \"center\";\n    if (point.x < chartArea.left + 90) align = \"left\";\n    else if (point.x > chartArea.right - 90) align = \"right\";\n    ctx.font = \"600 15px sans-serif\";\n    ctx.fillStyle = t.ink;\n    ctx.textAlign = align;\n    ctx.fillText(label, point.x, labelY);\n    ctx.restore();\n  },\n};\n\nnew Chart(canvas, {\n  type: \"bar\",\n  data: {\n    labels: dates,\n    datasets: [\n      {\n        type: \"bar\",\n        label: \"Histogram\",\n        data: histogram,\n        backgroundColor: histogram.map((v) => (v >= 0 ? bullColor : bearColor)),\n        borderWidth: 0,\n        barPercentage: 1.0,\n        categoryPercentage: 0.9,\n      },\n      {\n        type: \"line\",\n        label: \"Zero line\",\n        data: zeroLine,\n        borderColor: t.inkSoft,\n        borderWidth: 1.5,\n        borderDash: [6, 4],\n        pointRadius: 0,\n        pointHoverRadius: 0,\n      },\n      {\n        type: \"line\",\n        label: \"MACD (12, 26)\",\n        data: macdLine,\n        borderColor: macdColor,\n        backgroundColor: macdColor,\n        borderWidth: 3,\n        pointRadius: 0,\n        pointHoverRadius: 0,\n        tension: 0.15,\n      },\n      {\n        type: \"line\",\n        label: \"Signal (9)\",\n        data: signalLine,\n        borderColor: signalColor,\n        backgroundColor: signalColor,\n        borderWidth: 2.5,\n        pointRadius: 0,\n        pointHoverRadius: 0,\n        tension: 0.15,\n      },\n    ],\n  },\n  plugins: [crossoverPlugin],\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    interaction: { mode: \"index\", intersect: false },\n    plugins: {\n      title: {\n        display: true,\n        text: \"indicator-macd · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { bottom: 14 },\n      },\n      subtitle: {\n        display: true,\n        text: \"12-day / 26-day EMA convergence-divergence, 9-day signal EMA\",\n        color: t.inkSoft,\n        font: { size: 13, style: \"italic\" },\n        padding: { bottom: 14 },\n      },\n      legend: {\n        position: \"top\",\n        labels: {\n          color: t.ink,\n          font: { size: 14 },\n          boxWidth: 24,\n          usePointStyle: true,\n          filter: (item) => item.text !== \"Zero line\",\n        },\n      },\n    },\n    scales: {\n      x: {\n        ticks: { color: t.inkSoft, font: { size: 14 }, maxTicksLimit: 12, autoSkip: true },\n        grid: { display: false },\n        title: { display: true, text: \"Trading Date\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"MACD Value ($)\", color: t.ink, font: { size: 16 } },\n      },\n    },\n  },\n});\n"}