{"spec_id":"histogram-returns-distribution","library":"echarts","language":"javascript","code":"// anyplot.ai\n// histogram-returns-distribution: Returns Distribution Histogram\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-02\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\nconst { width: W, height: H } = window.ANYPLOT_SIZE;\n\n// --- Data (in-memory, deterministic fixed-seed LCG) -------------------------\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\nfunction randNormal() {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst n = 252; // one trading year of daily returns\nconst returns = [];\nfor (let i = 0; i < n; i++) {\n  let r = 0.0004 + 0.011 * randNormal(); // ~0.04% mean, 1.1% daily std\n  if (rand() < 0.06) {\n    // occasional volatility-cluster shock, skewed toward downside (crash risk)\n    const sign = rand() < 0.65 ? -1 : 1;\n    r += sign * (0.02 + 0.025 * rand());\n  }\n  returns.push(r * 100); // percent\n}\n\n// --- Statistics ---------------------------------------------------------------\nconst mean = returns.reduce((a, r) => a + r, 0) / n;\nconst variance = returns.reduce((a, r) => a + (r - mean) ** 2, 0) / n;\nconst std = Math.sqrt(variance);\nconst skewness = returns.reduce((a, r) => a + ((r - mean) / std) ** 3, 0) / n;\nconst kurtosis =\n  returns.reduce((a, r) => a + ((r - mean) / std) ** 4, 0) / n - 3;\n\nfunction normalPdf(x) {\n  return (\n    (1 / (std * Math.sqrt(2 * Math.PI))) *\n    Math.exp(-0.5 * ((x - mean) / std) ** 2)\n  );\n}\n\n// --- Histogram binning (density-normalized) ----------------------------------\nconst binCount = 25;\nconst min = Math.min(...returns);\nconst max = Math.max(...returns);\nconst binWidth = (max - min) / binCount;\n\nconst counts = new Array(binCount).fill(0);\nfor (const r of returns) {\n  const idx = Math.min(binCount - 1, Math.floor((r - min) / binWidth));\n  counts[idx]++;\n}\n\nconst binCenters = counts.map((_, i) => min + (i + 0.5) * binWidth);\nconst densities = counts.map((c) => c / (n * binWidth));\nconst normalCurve = binCenters.map((x) => normalPdf(x));\n\nconst tailLo = mean - 2 * std;\nconst tailHi = mean + 2 * std;\nconst barData = binCenters.map((x, i) => ({\n  value: densities[i],\n  itemStyle: { color: x < tailLo || x > tailHi ? t.amber : t.palette[0] },\n}));\nconst categories = binCenters.map((x) => `${x.toFixed(1)}%`);\n\n// --- Title (fontsize scales with title length, 67-char baseline) ------------\nconst titleText =\n  \"Equity ETF Daily Returns · histogram-returns-distribution · javascript · echarts · anyplot.ai\";\nconst titleFontSize = Math.round(22 * Math.min(1, 67 / titleText.length));\n\n// --- Stats box (spec explicitly requires a stats text box) ------------------\nconst boxW = 380;\nconst boxH = 220;\nconst boxX = W - 60 - boxW;\nconst boxY = 160;\nconst statLines = [\n  `Observations: ${n}`,\n  `Mean: ${mean.toFixed(3)}%`,\n  `Std dev: ${std.toFixed(3)}%`,\n  `Skewness: ${skewness.toFixed(2)}`,\n  `Kurtosis: ${kurtosis.toFixed(2)}`,\n];\n\n// --- Init ---------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option ---------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: titleText,\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: titleFontSize },\n  },\n  legend: {\n    top: 66,\n    left: \"center\",\n    data: [\"Daily returns\", \"Normal fit\"],\n    itemWidth: 24,\n    itemHeight: 14,\n    textStyle: { color: t.inkSoft, fontSize: 15 },\n  },\n  tooltip: { trigger: \"axis\" },\n  grid: { left: 100, right: 60, top: 150, bottom: 110 },\n  xAxis: {\n    type: \"category\",\n    data: categories,\n    name: \"Daily return\",\n    nameLocation: \"middle\",\n    nameGap: 46,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 13, interval: 2 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Density\",\n    nameLocation: \"middle\",\n    nameGap: 60,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 13 },\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      name: \"Daily returns\",\n      type: \"bar\",\n      data: barData,\n      barCategoryGap: \"10%\",\n      z: 2,\n    },\n    {\n      name: \"Normal fit\",\n      type: \"line\",\n      data: normalCurve,\n      symbol: \"none\",\n      smooth: true,\n      lineStyle: { width: 3, color: t.palette[1] },\n      itemStyle: { color: t.palette[1] },\n      z: 3,\n    },\n  ],\n  graphic: [\n    {\n      type: \"rect\",\n      left: boxX,\n      top: boxY,\n      shape: { width: boxW, height: boxH, r: 10 },\n      style: { fill: t.elevatedBg, stroke: t.grid, lineWidth: 1 },\n    },\n    {\n      type: \"text\",\n      left: boxX + 24,\n      top: boxY + 20,\n      style: { text: \"Statistics\", fill: t.ink, fontSize: 17, fontWeight: \"bold\" },\n    },\n    ...statLines.map((line, i) => ({\n      type: \"text\",\n      left: boxX + 24,\n      top: boxY + 54 + i * 26,\n      style: { text: line, fill: t.inkSoft, fontSize: 15 },\n    })),\n    {\n      type: \"rect\",\n      left: boxX + 24,\n      top: boxY + boxH - 30,\n      shape: { width: 16, height: 16, r: 3 },\n      style: { fill: t.amber },\n    },\n    {\n      type: \"text\",\n      left: boxX + 48,\n      top: boxY + boxH - 29,\n      style: { text: \"Tail region (|z| > 2)\", fill: t.inkSoft, fontSize: 13 },\n    },\n  ],\n});\n"}