{"spec_id":"indicator-macd","library":"muix","language":"javascript","code":"// anyplot.ai\n// indicator-macd: MACD Technical Indicator Chart\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-05\n\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { BarPlot } from \"@mui/x-charts/BarChart\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { ChartsTooltip } from \"@mui/x-charts/ChartsTooltip\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: 120 trading days of a synthetic closing price, MACD(12,26,9) ------\nconst PERIODS = 120;\n\n// Tiny fixed-seed LCG — the browser has no seeded Math.random().\nfunction lcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nconst rand = lcg(42);\n\n// Business-day dates starting Jan 2, 2025.\nconst dates = [];\nconst cursor = new Date(Date.UTC(2025, 0, 2));\nwhile (dates.length < PERIODS) {\n  const weekday = cursor.getUTCDay();\n  if (weekday !== 0 && weekday !== 6) {\n    dates.push(new Date(cursor));\n  }\n  cursor.setUTCDate(cursor.getUTCDate() + 1);\n}\nconst dateFormatter = new Intl.DateTimeFormat(\"en-US\", { month: \"short\", day: \"numeric\", timeZone: \"UTC\" });\nconst dateLabels = dates.map((d) => dateFormatter.format(d));\n\n// Closing price: slow oscillation (trend reversals) + noise, so MACD crosses over several times.\nconst closes = [];\nlet price = 148;\nfor (let i = 0; i < PERIODS; i++) {\n  const trend = 0.55 * Math.sin(i / 16) + 0.15 * Math.sin(i / 5.5);\n  const noise = (rand() - 0.5) * 1.4;\n  price += trend + noise;\n  closes.push(price);\n}\n\nfunction ema(values, period) {\n  const k = 2 / (period + 1);\n  const out = [values[0]];\n  for (let i = 1; i < values.length; i++) {\n    out.push(values[i] * k + out[i - 1] * (1 - k));\n  }\n  return out;\n}\n\nconst ema12 = ema(closes, 12);\nconst ema26 = ema(closes, 26);\nconst macdLine = ema12.map((v, i) => v - ema26[i]);\nconst signalLine = ema(macdLine, 9);\nconst histogram = macdLine.map((v, i) => parseFloat((v - signalLine[i]).toFixed(3)));\n\n// Most recent sign flip in the histogram = the latest MACD/signal crossover,\n// called out on the chart as a lightweight annotation.\nlet lastCrossoverIndex = null;\nfor (let i = 1; i < histogram.length; i++) {\n  if (Math.sign(histogram[i]) !== 0 && Math.sign(histogram[i]) !== Math.sign(histogram[i - 1])) {\n    lastCrossoverIndex = i;\n  }\n}\n\n// Shared numeric domain for both y-axes so histogram bars and lines stay on\n// the same visual scale (a colorMap-carrying axis can't also carry the lines'\n// explicit series colors — see the dedicated \"value-hist\" axis below).\nconst allValues = [...macdLine, ...signalLine, ...histogram];\nconst rawMax = Math.max(...allValues);\nconst rawMin = Math.min(...allValues);\nconst pad = (rawMax - rawMin) * 0.08;\nconst Y_MAX = Math.ceil((rawMax + pad) * 2) / 2;\nconst Y_MIN = Math.floor((rawMin - pad) * 2) / 2;\n\nconst MARGIN = { top: 24, right: 32, bottom: 64, left: 76 };\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width; // 1600 CSS px (landscape)\n  const H = window.ANYPLOT_SIZE.height; // 900 CSS px\n  const TITLE_H = 72;\n  const LEGEND_H = 44;\n  const chartH = H - TITLE_H - LEGEND_H;\n\n  return (\n    <Box\n      sx={{\n        width: W,\n        height: H,\n        bgcolor: t.pageBg,\n        display: \"flex\",\n        flexDirection: \"column\",\n        fontFamily: \"'Roboto', 'Helvetica Neue', Arial, sans-serif\",\n        boxSizing: \"border-box\",\n      }}\n    >\n      {/* Title */}\n      <Box sx={{ height: TITLE_H, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n        <Typography sx={{ color: t.ink, fontSize: 22, fontWeight: 600 }}>\n          indicator-macd · javascript · muix · anyplot.ai\n        </Typography>\n      </Box>\n\n      {/* Chart */}\n      <ChartContainer\n        width={W}\n        height={chartH}\n        series={[\n          {\n            type: \"bar\",\n            id: \"histogram\",\n            yAxisId: \"value-hist\",\n            data: histogram,\n            color: t.palette[4],\n          },\n          {\n            type: \"line\",\n            id: \"macd\",\n            yAxisId: \"value\",\n            data: macdLine,\n            label: \"MACD (12, 26)\",\n            color: t.palette[0],\n            showMark: false,\n            curve: \"linear\",\n          },\n          {\n            type: \"line\",\n            id: \"signal\",\n            yAxisId: \"value\",\n            data: signalLine,\n            label: \"Signal (9)\",\n            color: t.palette[1],\n            showMark: false,\n            curve: \"linear\",\n          },\n        ]}\n        xAxis={[\n          {\n            id: \"dates\",\n            scaleType: \"band\",\n            data: dateLabels,\n            categoryGapRatio: 0.15,\n            tickLabelInterval: (_value, index) => index % 10 === 0,\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n          },\n        ]}\n        yAxis={[\n          {\n            id: \"value\",\n            min: Y_MIN,\n            max: Y_MAX,\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n          },\n          {\n            // Dedicated axis for the histogram only — a colorMap-carrying axis\n            // overrides per-point colors for every series bound to it (lines\n            // included), so it must stay separate from the MACD/Signal axis.\n            id: \"value-hist\",\n            min: Y_MIN,\n            max: Y_MAX,\n            colorMap: { type: \"piecewise\", thresholds: [0], colors: [t.palette[4], t.palette[0]] },\n          },\n        ]}\n        margin={MARGIN}\n        skipAnimation\n        sx={{\n          \"& .MuiChartsGrid-line\": { stroke: t.grid },\n          \"& .MuiLineElement-root\": { strokeWidth: 2.75 },\n        }}\n      >\n        <ChartsGrid horizontal />\n        <BarPlot skipAnimation borderRadius={1} />\n        <LinePlot skipAnimation />\n        <ChartsReferenceLine\n          y={0}\n          axisId=\"value\"\n          lineStyle={{ stroke: t.ink, strokeDasharray: \"6 4\", strokeWidth: 1.5 }}\n        />\n        {lastCrossoverIndex !== null && (\n          <ChartsReferenceLine\n            x={dateLabels[lastCrossoverIndex]}\n            axisId=\"dates\"\n            label=\"Latest crossover\"\n            labelAlign=\"end\"\n            labelStyle={{ fontSize: 12, fill: t.inkSoft }}\n            lineStyle={{ stroke: t.inkSoft, strokeDasharray: \"2 4\", strokeOpacity: 0.6 }}\n          />\n        )}\n        <ChartsXAxis\n          axisId=\"dates\"\n          position=\"bottom\"\n          label=\"Trading Date\"\n          labelStyle={{ fontSize: 14, fill: t.ink }}\n          disableLine\n        />\n        <ChartsYAxis\n          axisId=\"value\"\n          position=\"left\"\n          label=\"MACD Value ($)\"\n          labelStyle={{ fontSize: 14, fill: t.ink }}\n          disableLine\n        />\n        <ChartsTooltip trigger=\"axis\" />\n      </ChartContainer>\n\n      {/* Legend */}\n      <Box sx={{ height: LEGEND_H, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", gap: \"32px\" }}>\n        <Box sx={{ display: \"flex\", alignItems: \"center\", gap: \"8px\" }}>\n          <Box sx={{ width: 24, height: 3, bgcolor: t.palette[0], borderRadius: \"2px\", flexShrink: 0 }} />\n          <Typography sx={{ color: t.inkSoft, fontSize: 14 }}>MACD Line (12, 26)</Typography>\n        </Box>\n        <Box sx={{ display: \"flex\", alignItems: \"center\", gap: \"8px\" }}>\n          <Box sx={{ width: 24, height: 3, bgcolor: t.palette[1], borderRadius: \"2px\", flexShrink: 0 }} />\n          <Typography sx={{ color: t.inkSoft, fontSize: 14 }}>Signal Line (9)</Typography>\n        </Box>\n        <Box sx={{ display: \"flex\", alignItems: \"center\", gap: \"8px\" }}>\n          <Box sx={{ width: 14, height: 14, bgcolor: t.palette[0], borderRadius: \"2px\", flexShrink: 0 }} />\n          <Typography sx={{ color: t.inkSoft, fontSize: 14 }}>Histogram (bullish)</Typography>\n        </Box>\n        <Box sx={{ display: \"flex\", alignItems: \"center\", gap: \"8px\" }}>\n          <Box sx={{ width: 14, height: 14, bgcolor: t.palette[4], borderRadius: \"2px\", flexShrink: 0 }} />\n          <Typography sx={{ color: t.inkSoft, fontSize: 14 }}>Histogram (bearish)</Typography>\n        </Box>\n      </Box>\n    </Box>\n  );\n}\n"}