{"spec_id":"indicator-sma","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// indicator-sma: Simple Moving Average (SMA) Indicator Chart\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG random walk) ------------------------\nfunction lcg(seed) {\n  let state = seed;\n  return function next() {\n    state = (state * 1103515245 + 12345) % 2147483648;\n    return state / 2147483648;\n  };\n}\nconst rand = lcg(42);\n\nconst numDays = 320;\nconst dates = [];\nconst close = [];\nlet price = 148;\nlet cursor = new Date(2023, 0, 2);\nwhile (dates.length < numDays) {\n  const dow = cursor.getDay();\n  if (dow !== 0 && dow !== 6) {\n    dates.push(new Date(cursor));\n    // regime shift: sustained uptrend, then a pullback — gives the SMAs\n    // something to cross over\n    const drift = dates.length < 170 ? 0.0009 : -0.0004;\n    const shock = (rand() - 0.5) * 0.03;\n    price *= 1 + drift + shock;\n    close.push(price);\n  }\n  cursor.setDate(cursor.getDate() + 1);\n}\n\nfunction sma(values, period) {\n  return values.map((_, i) => {\n    if (i < period - 1) return null;\n    let sum = 0;\n    for (let j = i - period + 1; j <= i; j++) sum += values[j];\n    return sum / period;\n  });\n}\n\nconst smaShort = sma(close, 20);\nconst smaMedium = sma(close, 50);\nconst smaLong = sma(close, 200);\n\n// Golden-cross / death-cross emphasis: color each Close segment by whether\n// price sits above (bullish, brand green) or below (bearish, semantic-red\n// anchor) the medium SMA — a Chart.js `segment` feature that turns the\n// crossover signal into the chart's visual focal point.\nfunction crossState(i) {\n  const s = smaMedium[i];\n  if (s == null) return \"above\";\n  return close[i] >= s ? \"above\" : \"below\";\n}\nfunction hexToRgba(hex, alpha) {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\nconst bullish = t.palette[0]; // #009E73 brand green\nconst bearish = t.palette[4]; // #AE3030 matte-red semantic anchor\nconst bullishFill = hexToRgba(bullish, 0.14);\nconst bearishFill = hexToRgba(bearish, 0.1);\n\nconst labels = dates.map((d) =>\n  d.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\", year: \"2-digit\" }),\n);\n\n// --- Title (fontsize scales down when the descriptive prefix pushes past the\n// 67-char baseline) ----------------------------------------------------------\nconst title = \"TechCorp Stock · indicator-sma · javascript · chartjs · anyplot.ai\";\nconst titleFontSize = title.length > 67 ? Math.max(15, Math.round((22 * 67) / title.length)) : 22;\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    labels,\n    datasets: [\n      {\n        label: \"Close\",\n        data: close,\n        borderColor: bullish,\n        backgroundColor: bullishFill,\n        borderWidth: 2,\n        pointRadius: 0,\n        tension: 0,\n        // Fill the gap between Close and SMA 50 (dataset index 2) rather than\n        // down to the axis bottom — a narrow, legible band that visualizes\n        // the crossover spread instead of a heavy full-height area.\n        fill: 2,\n        segment: {\n          borderColor: (ctx) => (crossState(ctx.p0DataIndex) === \"below\" ? bearish : bullish),\n          backgroundColor: (ctx) => (crossState(ctx.p0DataIndex) === \"below\" ? bearishFill : bullishFill),\n        },\n      },\n      {\n        label: \"SMA 20\",\n        data: smaShort,\n        borderColor: t.palette[1],\n        backgroundColor: t.palette[1],\n        borderWidth: 2.5,\n        pointRadius: 0,\n        tension: 0,\n        fill: false,\n      },\n      {\n        label: \"SMA 50\",\n        data: smaMedium,\n        borderColor: t.palette[2],\n        backgroundColor: t.palette[2],\n        borderWidth: 2.5,\n        pointRadius: 0,\n        tension: 0,\n        fill: false,\n      },\n      {\n        label: \"SMA 200\",\n        data: smaLong,\n        borderColor: t.palette[3],\n        backgroundColor: t.palette[3],\n        borderWidth: 3,\n        pointRadius: 0,\n        tension: 0,\n        fill: false,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    interaction: { mode: \"nearest\", intersect: false },\n    plugins: {\n      title: {\n        display: true,\n        text: title,\n        color: t.ink,\n        font: { size: titleFontSize, weight: \"500\" },\n      },\n      legend: {\n        position: \"top\",\n        align: \"end\",\n        labels: { color: t.ink, font: { size: 16 }, boxWidth: 24, boxHeight: 3 },\n      },\n    },\n    scales: {\n      x: {\n        ticks: { color: t.inkSoft, font: { size: 14 }, maxTicksLimit: 10, autoSkip: true },\n        grid: { display: false },\n        title: { display: true, text: \"Date\", color: t.ink, font: { size: 18 } },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Price (USD)\", color: t.ink, font: { size: 18 } },\n      },\n    },\n  },\n});\n"}