{"spec_id":"indicator-sma","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// indicator-sma: Simple Moving Average (SMA) Indicator Chart\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-02\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// Small LCG PRNG (the browser has no seeded RNG) feeding a Box-Muller\n// transform, so the daily returns look like real market noise.\nlet seed = 42;\nfunction lcgRandom() {\n  seed = (seed * 1664525 + 1013904223) % 4294967296;\n  return seed / 4294967296;\n}\nfunction gaussian() {\n  const u1 = 1 - lcgRandom();\n  const u2 = lcgRandom();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst PERIODS = 300;\nconst dates = [];\nlet cursor = Date.UTC(2023, 0, 2);\nwhile (dates.length < PERIODS) {\n  const weekday = new Date(cursor).getUTCDay();\n  if (weekday !== 0 && weekday !== 6) dates.push(cursor);\n  cursor += 24 * 3600 * 1000;\n}\n\nconst DRIFT = 0.0004;\nconst VOLATILITY = 0.012;\nconst closes = [];\nlet price = 148;\nfor (let i = 0; i < PERIODS; i++) {\n  price *= 1 + DRIFT + VOLATILITY * gaussian();\n  closes.push(Math.round(price * 100) / 100);\n}\n\nfunction sma(values, windowSize) {\n  return values.map((_, i) => {\n    if (i < windowSize - 1) return null;\n    let sum = 0;\n    for (let j = i - windowSize + 1; j <= i; j++) sum += values[j];\n    return Math.round((sum / windowSize) * 100) / 100;\n  });\n}\n\nconst sma20 = sma(closes, 20);\nconst sma50 = sma(closes, 50);\nconst sma200 = sma(closes, 200);\n\nconst closeSeries = dates.map((d, i) => [d, closes[i]]);\nconst sma20Series = dates.map((d, i) => [d, sma20[i]]);\nconst sma50Series = dates.map((d, i) => [d, sma50[i]]);\nconst sma200Series = dates.map((d, i) => [d, sma200[i]]);\n\n// Golden-cross / death-cross detection (SMA 50 vs SMA 200) — the spec's\n// headline application. Only real crossovers found in the generated series\n// are annotated, so the callout always matches what the lines actually do.\nconst crossovers = [];\nfor (let i = 1; i < PERIODS; i++) {\n  const prev50 = sma50[i - 1];\n  const prev200 = sma200[i - 1];\n  const cur50 = sma50[i];\n  const cur200 = sma200[i];\n  if (prev50 == null || prev200 == null || cur50 == null || cur200 == null) continue;\n  if (prev50 <= prev200 && cur50 > cur200) {\n    crossovers.push({ date: dates[i], type: \"golden\", label: \"Golden Cross\" });\n  } else if (prev50 >= prev200 && cur50 < cur200) {\n    crossovers.push({ date: dates[i], type: \"death\", label: \"Death Cross\" });\n  }\n}\n\n// --- Chart -----------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"line\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"indicator-sma · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    type: \"datetime\",\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    crosshair: { color: t.grid, dashStyle: \"Dash\" },\n    // Distinctive Highcharts feature: declarative plotLines spotlight the\n    // real golden-cross / death-cross moments found in the data above.\n    plotLines: crossovers.map((c) => ({\n      value: c.date,\n      color: c.type === \"golden\" ? t.palette[0] : t.amber,\n      dashStyle: \"Dash\",\n      width: 2,\n      zIndex: 5,\n      label: {\n        text: c.label,\n        rotation: 0,\n        y: 16,\n        x: 6,\n        style: { color: t.ink, fontSize: \"12px\", fontWeight: \"600\" },\n      },\n    })),\n  },\n  yAxis: {\n    title: {\n      text: \"Closing Price (USD)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n    margin: 14,\n  },\n  tooltip: {\n    shared: true,\n    // Distinctive Highcharts feature: custom formatter reports each SMA's\n    // delta versus the Close price, not just the raw line values.\n    formatter: function () {\n      const closePoint = this.points.find((p) => p.series.name === \"Close\");\n      const lines = [`<b>${Highcharts.dateFormat(\"%b %e, %Y\", this.x)}</b>`];\n      this.points.forEach((p) => {\n        let delta = \"\";\n        if (closePoint && p.series.name !== \"Close\") {\n          const deltaPct = ((closePoint.y - p.y) / p.y) * 100;\n          delta = ` <span style=\"color:${t.inkSoft}\">(${deltaPct >= 0 ? \"+\" : \"\"}${deltaPct.toFixed(1)}% vs Close)</span>`;\n        }\n        lines.push(`<span style=\"color:${p.color}\">●</span> ${p.series.name}: <b>$${p.y.toFixed(2)}</b>${delta}`);\n      });\n      return lines.join(\"<br/>\");\n    },\n  },\n  plotOptions: {\n    series: { animation: false, marker: { enabled: false } },\n  },\n  series: [\n    { name: \"Close\", data: closeSeries, lineWidth: 2.5, color: t.palette[0], zIndex: 4 },\n    { name: \"SMA 20\", data: sma20Series, lineWidth: 1.5, dashStyle: \"Solid\", color: t.palette[1], zIndex: 3 },\n    { name: \"SMA 50\", data: sma50Series, lineWidth: 1.5, dashStyle: \"ShortDash\", color: t.palette[2], zIndex: 2 },\n    { name: \"SMA 200\", data: sma200Series, lineWidth: 1.75, dashStyle: \"LongDash\", color: t.palette[3], zIndex: 1 },\n  ],\n});\n"}