{"spec_id":"indicator-rsi","library":"echarts","language":"javascript","code":"// anyplot.ai\n// indicator-rsi: RSI Technical Indicator Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Helpers -----------------------------------------------------------\nfunction hexToRgba(hex, alpha) {\n  const h = hex.replace(\"#\", \"\");\n  const r = parseInt(h.substring(0, 2), 16);\n  const g = parseInt(h.substring(2, 4), 16);\n  const b = parseInt(h.substring(4, 6), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nfunction lcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\n\nfunction nextTradingDay(date) {\n  const next = new Date(date);\n  do {\n    next.setUTCDate(next.getUTCDate() + 1);\n  } while (next.getUTCDay() === 0 || next.getUTCDay() === 6);\n  return next;\n}\n\nfunction computeRsi(closes, period) {\n  const rsi = new Array(closes.length).fill(null);\n  let avgGain = 0;\n  let avgLoss = 0;\n  for (let i = 1; i <= period; i += 1) {\n    const change = closes[i] - closes[i - 1];\n    avgGain += Math.max(change, 0);\n    avgLoss += Math.max(-change, 0);\n  }\n  avgGain /= period;\n  avgLoss /= period;\n  rsi[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n  for (let i = period + 1; i < closes.length; i += 1) {\n    const change = closes[i] - closes[i - 1];\n    const gain = Math.max(change, 0);\n    const loss = Math.max(-change, 0);\n    avgGain = (avgGain * (period - 1) + gain) / period;\n    avgLoss = (avgLoss * (period - 1) + loss) / period;\n    rsi[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n  }\n  return rsi;\n}\n\n// --- Data (in-memory, deterministic LCG-driven price walk) -------------\nconst lookback = 14;\nconst totalDays = 130;\nconst rand = lcg(42);\n\nconst dates = [new Date(\"2025-01-02T00:00:00Z\")];\nfor (let i = 1; i < totalDays; i += 1) {\n  dates.push(nextTradingDay(dates[i - 1]));\n}\n\nconst closingPrices = [64.2];\nfor (let i = 1; i < totalDays; i += 1) {\n  const cycle = Math.sin(i / 9) * 0.4;\n  const noise = (rand() - 0.5) * 1.7;\n  closingPrices.push(Math.max(10, closingPrices[i - 1] + cycle + noise));\n}\n\nconst rsiValues = computeRsi(closingPrices, lookback);\nconst rsiSeries = [];\nfor (let i = lookback; i < totalDays; i += 1) {\n  rsiSeries.push([dates[i].getTime(), rsiValues[i]]);\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[0]],\n  title: {\n    text: \"indicator-rsi · javascript · echarts · anyplot.ai\",\n    subtext: \"14-period RSI on daily closing prices · overbought > 70 · oversold < 30\",\n    left: \"center\",\n    top: 24,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n    subtextStyle: { color: t.inkSoft, fontSize: 15 },\n  },\n  grid: { left: 90, right: 150, top: 140, bottom: 90 },\n  xAxis: {\n    type: \"time\",\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    min: 0,\n    max: 100,\n    interval: 10,\n    name: \"RSI\",\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      type: \"line\",\n      name: \"RSI (14)\",\n      showSymbol: false,\n      lineStyle: { width: 3, color: t.palette[0] },\n      itemStyle: { color: t.palette[0] },\n      data: rsiSeries,\n      markArea: {\n        silent: true,\n        data: [\n          [\n            { yAxis: 70, itemStyle: { color: hexToRgba(t.palette[4], 0.1) } },\n            { yAxis: 100 },\n          ],\n          [\n            { yAxis: 0, itemStyle: { color: hexToRgba(t.inkSoft, 0.12) } },\n            { yAxis: 30 },\n          ],\n        ],\n      },\n      markLine: {\n        silent: true,\n        symbol: \"none\",\n        lineStyle: { type: \"dashed\", color: t.inkSoft, width: 1.5 },\n        label: { color: t.inkSoft, fontSize: 14, formatter: \"{b}\", position: \"end\" },\n        data: [\n          { yAxis: 70, name: \"Overbought\" },\n          { yAxis: 50, name: \"Neutral\", lineStyle: { type: \"dotted\" } },\n          { yAxis: 30, name: \"Oversold\" },\n        ],\n      },\n    },\n  ],\n});\n"}