{"spec_id":"indicator-sma","library":"echarts","language":"javascript","code":"// anyplot.ai\n// indicator-sma: Simple Moving Average (SMA) Indicator Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\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\nconst numPeriods = 460; // ~1.8 trading years of business days\nconst dates = [];\nconst cursor = new Date(\"2023-01-02T00:00:00Z\");\nwhile (dates.length < numPeriods) {\n  const day = cursor.getUTCDay();\n  if (day !== 0 && day !== 6) dates.push(new Date(cursor));\n  cursor.setUTCDate(cursor.getUTCDate() + 1);\n}\n\n// Three regimes so the SMAs actually cross: a steady climb (SMA200 builds\n// history), a sustained slide (death cross: fast SMAs fall below SMA200),\n// then a recovery rally (golden cross: fast SMAs climb back above SMA200).\nlet price = 140;\nconst close = dates.map((_, i) => {\n  let drift;\n  if (i < 220) drift = 0.32; // climb\n  else if (i < 340) drift = -0.34; // slide\n  else drift = 0.4; // recovery rally\n  const noise = (rand() - 0.5) * 3.6;\n  price = Math.max(30, price + drift + noise);\n  return Number(price.toFixed(2));\n});\n\nfunction sma(values, window) {\n  return values.map((_, i) => {\n    if (i < window - 1) return null;\n    let sum = 0;\n    for (let j = i - window + 1; j <= i; j++) sum += values[j];\n    return Number((sum / window).toFixed(2));\n  });\n}\n\nconst smaShort = sma(close, 20);\nconst smaMedium = sma(close, 50);\nconst smaLong = sma(close, 200);\n\nconst toSeries = (values) => dates.map((date, i) => [date.getTime(), values[i]]);\nconst closeSeries = toSeries(close);\nconst smaShortSeries = toSeries(smaShort);\nconst smaMediumSeries = toSeries(smaMedium);\nconst smaLongSeries = toSeries(smaLong);\n\n// Flag SMA20/SMA200 crossovers (golden cross = short overtakes long) to\n// annotate with a markLine — an ECharts-distinctive touch that also makes\n// the crossover behavior the spec calls out easy to read at a glance.\nconst crossovers = [];\nfor (let i = 1; i < dates.length; i++) {\n  if (smaShort[i - 1] === null || smaLong[i - 1] === null || smaShort[i] === null || smaLong[i] === null) continue;\n  const prevDiff = smaShort[i - 1] - smaLong[i - 1];\n  const currDiff = smaShort[i] - smaLong[i];\n  if (prevDiff <= 0 && currDiff > 0) {\n    crossovers.push({ time: dates[i].getTime(), label: \"Golden Cross\" });\n  } else if (prevDiff >= 0 && currDiff < 0) {\n    crossovers.push({ time: dates[i].getTime(), label: \"Death Cross\" });\n  }\n}\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  color: t.palette,\n  title: {\n    text: \"indicator-sma · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  legend: {\n    data: [\"Close\", \"SMA 20\", \"SMA 50\", \"SMA 200\"],\n    top: 56,\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n  },\n  tooltip: { trigger: \"axis\" },\n  grid: { left: 90, right: 50, top: 120, bottom: 70 },\n  xAxis: {\n    type: \"time\",\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    scale: true,\n    name: \"Price (USD)\",\n    nameLocation: \"middle\",\n    nameGap: 60,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      name: \"Close\",\n      type: \"line\",\n      data: closeSeries,\n      showSymbol: false,\n      lineStyle: { color: t.ink, width: 1.75, opacity: 0.75 },\n      itemStyle: { color: t.ink, opacity: 0.75 },\n      z: 1,\n    },\n    {\n      name: \"SMA 20\",\n      type: \"line\",\n      data: smaShortSeries,\n      showSymbol: false,\n      connectNulls: false,\n      lineStyle: { color: t.palette[0], width: 3 },\n      itemStyle: { color: t.palette[0] },\n      z: 3,\n      markLine: {\n        symbol: \"none\",\n        animation: false,\n        label: { color: t.inkSoft, fontSize: 12, formatter: \"{b}\" },\n        lineStyle: { color: t.inkSoft, type: \"dashed\", width: 1.5 },\n        data: crossovers.map((c) => ({ name: c.label, xAxis: c.time })),\n      },\n    },\n    {\n      name: \"SMA 50\",\n      type: \"line\",\n      data: smaMediumSeries,\n      showSymbol: false,\n      connectNulls: false,\n      lineStyle: { color: t.palette[1], width: 2.5 },\n      itemStyle: { color: t.palette[1] },\n      z: 2,\n    },\n    {\n      name: \"SMA 200\",\n      type: \"line\",\n      data: smaLongSeries,\n      showSymbol: false,\n      connectNulls: false,\n      lineStyle: { color: t.palette[2], width: 2.5 },\n      itemStyle: { color: t.palette[2] },\n      z: 2,\n    },\n  ],\n});\n"}