{"spec_id":"drawdown-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// drawdown-basic: Drawdown Chart\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-08-24\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 90, right: 60, bottom: 70, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\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 gaussian() {\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_DAYS = 756; // ~3 years of trading days\nconst START_DATE = new Date(2023, 0, 2);\nconst dailyDrift = 0.0003;\nconst dailyVol = 0.011;\n\nconst dates = [];\nconst values = [];\nlet nav = 100;\nfor (let i = 0; i < N_DAYS; i++) {\n  const day = new Date(START_DATE);\n  day.setDate(day.getDate() + Math.floor((i / 5) * 7) + (i % 5));\n  dates.push(day);\n  if (i > 0) nav *= 1 + dailyDrift + dailyVol * gaussian();\n  values.push(nav);\n}\n\n// Drawdown as percentage decline from the running maximum.\nlet runningMax = values[0];\nconst drawdowns = values.map((v) => {\n  runningMax = Math.max(runningMax, v);\n  return ((v - runningMax) / runningMax) * 100;\n});\n\n// Maximum drawdown: the trough with the deepest decline.\nlet troughIdx = 0;\nfor (let i = 1; i < drawdowns.length; i++) {\n  if (drawdowns[i] < drawdowns[troughIdx]) troughIdx = i;\n}\n// Peak preceding the trough (last point where drawdown was ~0 before the trough).\nlet peakIdx = troughIdx;\nfor (let i = troughIdx; i >= 0; i--) {\n  if (drawdowns[i] >= -1e-9) {\n    peakIdx = i;\n    break;\n  }\n  peakIdx = i;\n}\n// Recovery: first point after the trough where drawdown returns to zero.\nlet recoveryIdx = -1;\nfor (let i = troughIdx; i < drawdowns.length; i++) {\n  if (drawdowns[i] >= -1e-9) {\n    recoveryIdx = i;\n    break;\n  }\n}\n\nconst data = dates.map((date, i) => ({ date, drawdown: drawdowns[i] }));\n\n// Key statistics required by spec: max drawdown %, duration, and recovery time.\nconst maxDDDuration = troughIdx - peakIdx;\nconst recoveryTime = recoveryIdx >= 0 ? recoveryIdx - troughIdx : null;\n\n// --- Scales -------------------------------------------------------------------\nconst x = d3\n  .scaleTime()\n  .domain(d3.extent(data, (d) => d.date))\n  .range([0, iw]);\nconst y = d3\n  .scaleLinear()\n  .domain([d3.min(drawdowns) * 1.15, 0])\n  .nice()\n  .range([ih, 0]);\n\n// --- SVG mount ----------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Y gridlines (drawdown %) --------------------------------------------------\ng.append(\"g\")\n  .selectAll(\"line\")\n  .data(y.ticks(6))\n  .join(\"line\")\n  .attr(\"x1\", 0)\n  .attr(\"x2\", iw)\n  .attr(\"y1\", (d) => y(d))\n  .attr(\"y2\", (d) => y(d))\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\n\n// --- Drawdown area fill (semi-transparent loss red, fading toward zero) -------\nsvg\n  .append(\"defs\")\n  .append(\"linearGradient\")\n  .attr(\"id\", \"drawdown-fill-gradient\")\n  .attr(\"x1\", \"0%\")\n  .attr(\"y1\", \"0%\")\n  .attr(\"x2\", \"0%\")\n  .attr(\"y2\", \"100%\")\n  .call((gradient) => {\n    gradient.append(\"stop\").attr(\"offset\", \"0%\").attr(\"stop-color\", t.palette[4]).attr(\"stop-opacity\", 0.06);\n    gradient.append(\"stop\").attr(\"offset\", \"100%\").attr(\"stop-color\", t.palette[4]).attr(\"stop-opacity\", 0.38);\n  });\n\nconst area = d3\n  .area()\n  .x((d) => x(d.date))\n  .y0(y(0))\n  .y1((d) => y(d.drawdown))\n  .curve(d3.curveLinear);\n\ng.append(\"path\").datum(data).attr(\"d\", area).attr(\"fill\", \"url(#drawdown-fill-gradient)\");\n\n// --- Drawdown line --------------------------------------------------------------\nconst line = d3\n  .line()\n  .x((d) => x(d.date))\n  .y((d) => y(d.drawdown))\n  .curve(d3.curveLinear);\n\ng.append(\"path\")\n  .datum(data)\n  .attr(\"d\", line)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[4])\n  .attr(\"stroke-width\", 2.5)\n  .attr(\"stroke-linejoin\", \"round\");\n\n// --- Zero baseline --------------------------------------------------------------\ng.append(\"line\")\n  .attr(\"x1\", 0)\n  .attr(\"x2\", iw)\n  .attr(\"y1\", y(0))\n  .attr(\"y2\", y(0))\n  .attr(\"stroke\", t.ink)\n  .attr(\"stroke-width\", 1.5);\n\n// --- Recovery marker (new-high crossing back through zero) ----------------------\nif (recoveryIdx >= 0) {\n  g.append(\"circle\")\n    .attr(\"cx\", x(data[recoveryIdx].date))\n    .attr(\"cy\", y(0))\n    .attr(\"r\", 6)\n    .attr(\"fill\", t.pageBg)\n    .attr(\"stroke\", t.palette[0])\n    .attr(\"stroke-width\", 2.5);\n\n  g.append(\"text\")\n    .attr(\"x\", x(data[recoveryIdx].date))\n    .attr(\"y\", y(0) - 16)\n    .attr(\"text-anchor\", \"middle\")\n    .attr(\"fill\", t.palette[0])\n    .style(\"font-size\", \"14px\")\n    .style(\"font-weight\", \"600\")\n    .text(\"Recovery from max drawdown\");\n}\n\n// --- Maximum drawdown marker + annotation ----------------------------------------\ng.append(\"circle\")\n  .attr(\"cx\", x(data[troughIdx].date))\n  .attr(\"cy\", y(drawdowns[troughIdx]))\n  .attr(\"r\", 7)\n  .attr(\"fill\", t.palette[4])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 2);\n\nconst troughLabel = g.append(\"g\").attr(\"transform\", `translate(${x(data[troughIdx].date)},${y(drawdowns[troughIdx])})`);\nconst labelBelow = y(drawdowns[troughIdx]) < ih * 0.35;\nconst labelDy = labelBelow ? 34 : -20;\ntroughLabel\n  .append(\"text\")\n  .attr(\"x\", 0)\n  .attr(\"y\", labelDy)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .style(\"font-weight\", \"700\")\n  .text(`Max drawdown: ${drawdowns[troughIdx].toFixed(1)}%`);\n\n// --- Axes ------------------------------------------------------------------------\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(8).tickFormat(d3.timeFormat(\"%b %Y\")));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6).tickFormat((d) => `${d}%`));\n\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.grid);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\nxAxis.selectAll(\".tick line\").remove();\nyAxis.selectAll(\".tick line\").remove();\n\n// --- Axis labels -------------------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 56)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Date\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -64)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Drawdown from Peak (%)\");\n\n// --- Title ----------------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 46)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"drawdown-basic · javascript · d3 · anyplot.ai\");\n\n// --- Key statistics callout (spec-required: max DD %, duration, recovery time) ---\nconst recoveryText = recoveryTime !== null ? `${recoveryTime} days` : \"not yet recovered\";\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 72)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"15px\")\n  .text(\n    `Max drawdown: ${drawdowns[troughIdx].toFixed(1)}%  ·  Duration: ${maxDDDuration} trading days  ·  Recovery: ${recoveryText}`,\n  );\n"}