{"spec_id":"indicator-ema","library":"echarts","language":"javascript","code":"// anyplot.ai\n// indicator-ema: Exponential Moving Average (EMA) Indicator Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Simple LCG so the sample data is reproducible without Math.random().\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\nconst PERIODS = 120;\nconst SHORT_PERIOD = 12;\nconst LONG_PERIOD = 26;\n\nconst startDate = new Date(Date.UTC(2026, 0, 5));\nconst dates = [];\nfor (let i = 0; i < PERIODS; i++) {\n  const dt = new Date(startDate.getTime() + i * 86400000);\n  dates.push(\n    `${dt.getUTCFullYear()}-${String(dt.getUTCMonth() + 1).padStart(2, \"0\")}-${String(dt.getUTCDate()).padStart(2, \"0\")}`,\n  );\n}\n\n// Daily close price: a random walk with three drift regimes — an opening\n// pullback, then a rally, then a second pullback — so the EMA lines produce\n// both a golden cross (bullish) and a death cross (bearish) naturally rather\n// than by construction.\nconst close = [148.5];\nfor (let i = 1; i < PERIODS; i++) {\n  let drift;\n  if (i < PERIODS * 0.3) drift = -0.4;\n  else if (i < PERIODS * 0.7) drift = 0.45;\n  else drift = -0.35;\n  const noise = (rand() - 0.5) * 3.2;\n  close.push(Math.max(close[i - 1] + drift + noise, 5));\n}\n\nfunction ema(values, period) {\n  const k = 2 / (period + 1);\n  const out = new Array(values.length).fill(null);\n  const seedAvg = values.slice(0, period).reduce((a, b) => a + b, 0) / period;\n  out[period - 1] = seedAvg;\n  for (let i = period; i < values.length; i++) {\n    out[i] = values[i] * k + out[i - 1] * (1 - k);\n  }\n  return out;\n}\n\nconst emaShort = ema(close, SHORT_PERIOD);\nconst emaLong = ema(close, LONG_PERIOD);\n\n// Crossover points: short EMA moving from below to above the long EMA is a\n// golden cross (bullish); above to below is a death cross (bearish).\nconst goldenCrosses = [];\nconst deathCrosses = [];\nfor (let i = LONG_PERIOD; i < PERIODS; i++) {\n  const prevDiff = emaShort[i - 1] - emaLong[i - 1];\n  const diff = emaShort[i] - emaLong[i];\n  if (prevDiff <= 0 && diff > 0) goldenCrosses.push({ coord: [dates[i], emaShort[i]], name: \"Golden Cross\" });\n  if (prevDiff >= 0 && diff < 0) deathCrosses.push({ coord: [dates[i], emaShort[i]], name: \"Death Cross\" });\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-ema · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  tooltip: {\n    trigger: \"axis\",\n    axisPointer: { type: \"cross\" },\n    valueFormatter: (value) => `$${Number(value).toFixed(2)}`,\n  },\n  legend: {\n    data: [\"Close\", `EMA (${SHORT_PERIOD})`, `EMA (${LONG_PERIOD})`],\n    top: 66,\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 16 },\n    itemWidth: 22,\n    itemHeight: 12,\n  },\n  grid: {\n    left: 90,\n    right: 60,\n    top: 130,\n    bottom: 90,\n    containLabel: true,\n  },\n  xAxis: {\n    type: \"category\",\n    data: dates,\n    boundaryGap: false,\n    name: \"Date\",\n    nameLocation: \"middle\",\n    nameGap: 50,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 13,\n      rotate: 45,\n      interval: (index) => index % 10 === 0,\n    },\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: 13, formatter: (v) => `$${v}` },\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: close,\n      symbol: \"none\",\n      lineStyle: { width: 3.5, color: t.palette[0] },\n      itemStyle: { color: t.palette[0] },\n      z: 3,\n    },\n    {\n      name: `EMA (${SHORT_PERIOD})`,\n      type: \"line\",\n      data: emaShort,\n      symbol: \"none\",\n      lineStyle: { width: 1.75, color: t.palette[2] },\n      itemStyle: { color: t.palette[2] },\n      z: 2,\n      markPoint: {\n        symbolSize: 40,\n        label: { show: false },\n        data: [\n          ...goldenCrosses.map((p) => ({ ...p, symbol: \"triangle\", itemStyle: { color: t.palette[7] } })),\n          ...deathCrosses.map((p) => ({ ...p, symbol: \"triangle\", symbolRotate: 180, itemStyle: { color: t.palette[4] } })),\n        ],\n      },\n    },\n    {\n      name: `EMA (${LONG_PERIOD})`,\n      type: \"line\",\n      data: emaLong,\n      symbol: \"none\",\n      lineStyle: { width: 1.75, color: t.palette[3] },\n      itemStyle: { color: t.palette[3] },\n      z: 2,\n    },\n  ],\n});\n"}