{"spec_id":"drawdown-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// drawdown-basic: Drawdown Chart\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-08-24\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { useXScale, useDrawingArea } from \"@mui/x-charts/hooks\";\nimport { Box, Typography } from \"@mui/material\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Simulated portfolio NAV: ~2 years of trading days (weekdays only), starting\n// at 100 with a small positive drift plus two engineered stress regimes, so\n// the drawdown story (decline, trough, recovery) reads unambiguously.\nconst START_DATE = new Date(2024, 0, 2);\nconst TRADING_DAYS = 500;\n\nconst dates = [];\nconst cursor = new Date(START_DATE);\nwhile (dates.length < TRADING_DAYS) {\n  const weekday = cursor.getDay();\n  if (weekday !== 0 && weekday !== 6) dates.push(new Date(cursor));\n  cursor.setDate(cursor.getDate() + 1);\n}\n\nlet lcgState = 42;\nconst nextRandom = () => {\n  lcgState = (lcgState * 1103515245 + 12345) % 2147483648;\n  return lcgState / 2147483648;\n};\n\n// Regime schedule: calm start -> mild correction -> calm -> sharp bear-market\n// crash (the main event) -> strong V-shaped recovery -> calm cruise. This\n// keeps the running-max drawdown story unambiguous: one dominant trough that\n// fully recovers with runway to spare before the series ends.\nconst regimeDrift = (i) => {\n  if (i < 120) return 0.0004;\n  if (i < 150) return -0.0045;\n  if (i < 260) return 0.0006;\n  if (i < 300) return -0.0105;\n  if (i < 410) return 0.0068;\n  return 0.0004;\n};\n\nlet nav = 100;\nconst navValues = dates.map((_, i) => {\n  const dailyShock = (nextRandom() - 0.5) * 0.018;\n  nav *= 1 + regimeDrift(i) + dailyShock;\n  return nav;\n});\n\nlet runningPeak = navValues[0];\nconst drawdownPct = navValues.map((value) => {\n  runningPeak = Math.max(runningPeak, value);\n  return ((value - runningPeak) / runningPeak) * 100;\n});\n\nlet troughIdx = 0;\ndrawdownPct.forEach((value, i) => {\n  if (value < drawdownPct[troughIdx]) troughIdx = i;\n});\n\nlet peakIdx = troughIdx;\nfor (let i = troughIdx; i >= 0; i -= 1) {\n  if (drawdownPct[i] === 0) {\n    peakIdx = i;\n    break;\n  }\n}\n\nlet recoveryIdx = -1;\nfor (let i = troughIdx + 1; i < drawdownPct.length; i += 1) {\n  if (drawdownPct[i] >= -0.02) {\n    recoveryIdx = i;\n    break;\n  }\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst maxDrawdownPct = drawdownPct[troughIdx];\nconst drawdownDays = Math.round((dates[troughIdx] - dates[peakIdx]) / DAY_MS);\nconst recoveryDays = recoveryIdx > -1 ? Math.round((dates[recoveryIdx] - dates[troughIdx]) / DAY_MS) : null;\n\nconst dateLabel = (date) => date.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\", year: \"numeric\" });\n\nconst statsLine =\n  `Max drawdown ${maxDrawdownPct.toFixed(1)}% · ` +\n  `Underwater ${drawdownDays}d (${dateLabel(dates[peakIdx])} → ${dateLabel(dates[troughIdx])}) · ` +\n  (recoveryDays !== null ? `Recovered in ${recoveryDays}d` : \"Not yet recovered\");\n\n// --- Overlay: shades the peak→trough and trough→recovery windows ---------\nfunction DrawdownWindow({ from, to, color, opacity }) {\n  const xScale = useXScale();\n  const { top, height } = useDrawingArea();\n  const x1 = xScale(from);\n  const x2 = xScale(to);\n  if (x1 == null || x2 == null) return null;\n  return (\n    <rect x={Math.min(x1, x2)} y={top} width={Math.abs(x2 - x1)} height={height} fill={color} fillOpacity={opacity} />\n  );\n}\n\nconst TITLE_H = 60;\nconst STATS_H = 40;\n// MUI X's y-axis `label` prop offsets itself from a hardcoded tickFontSize\n// guess rather than the tick labels' real measured width, so with \"-40%\"-style\n// negative-percent ticks it collides with the tick numbers. A hand-rotated\n// label in its own flex column sidesteps that with predictable spacing.\nconst Y_LABEL_W = 34;\n\n// --- Chart (default-exported component — the harness mounts it) ------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const chartHeight = height - TITLE_H - STATS_H;\n  const chartWidth = width - Y_LABEL_W;\n\n  return (\n    <Box\n      sx={{\n        width,\n        height,\n        bgcolor: t.pageBg,\n        boxSizing: \"border-box\",\n        display: \"flex\",\n        flexDirection: \"column\",\n        fontFamily: \"'Roboto', 'Helvetica Neue', Arial, sans-serif\",\n      }}\n    >\n      <Box sx={{ height: TITLE_H, display: \"flex\", alignItems: \"center\", pl: \"28px\" }}>\n        <Typography sx={{ color: t.ink, fontSize: 22, fontWeight: 600 }}>\n          drawdown-basic · javascript · muix · anyplot.ai\n        </Typography>\n      </Box>\n      <Box sx={{ height: STATS_H, display: \"flex\", alignItems: \"center\", pl: \"28px\" }}>\n        <Typography sx={{ color: t.inkSoft, fontSize: 15 }}>{statsLine}</Typography>\n      </Box>\n\n      <Box sx={{ display: \"flex\", flexDirection: \"row\", height: chartHeight }}>\n        <Box sx={{ width: Y_LABEL_W, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n          <Typography\n            sx={{ fontSize: 16, color: t.ink, whiteSpace: \"nowrap\", transform: \"rotate(-90deg)\" }}\n          >\n            Drawdown (%)\n          </Typography>\n        </Box>\n        <LineChart\n          width={chartWidth}\n          height={chartHeight}\n          skipAnimation\n          rightAxis=\"nav\"\n          series={[\n            {\n              id: \"drawdown\",\n              yAxisId: \"dd\",\n              data: drawdownPct,\n              label: \"Drawdown\",\n              area: true,\n              showMark: false,\n              curve: \"linear\",\n              color: t.palette[4],\n              valueFormatter: (v) => `${v.toFixed(1)}%`,\n            },\n            {\n              id: \"nav\",\n              yAxisId: \"nav\",\n              data: navValues,\n              label: \"Portfolio NAV\",\n              area: false,\n              showMark: false,\n              curve: \"linear\",\n              color: t.inkSoft,\n              valueFormatter: (v) => v.toFixed(1),\n            },\n          ]}\n          xAxis={[\n            {\n              id: \"x\",\n              data: dates,\n              scaleType: \"time\",\n              valueFormatter: (date) => date.toLocaleDateString(\"en-US\", { month: \"short\", year: \"2-digit\" }),\n              tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n            },\n          ]}\n          yAxis={[\n            {\n              id: \"dd\",\n              max: 2,\n              valueFormatter: (v) => `${v}%`,\n              tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n            },\n            {\n              id: \"nav\",\n              position: \"right\",\n              label: \"NAV\",\n              tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n              labelStyle: { fontSize: 16, fill: t.ink },\n            },\n          ]}\n          grid={{ horizontal: true }}\n          margin={{ top: 48, right: 92, bottom: 50, left: 56 }}\n          slotProps={{\n            legend: {\n              direction: \"row\",\n              position: { vertical: \"top\", horizontal: \"right\" },\n              labelStyle: { fontSize: 14, fill: t.ink },\n              itemMarkWidth: 18,\n              itemMarkHeight: 10,\n              markGap: 8,\n              itemGap: 20,\n            },\n          }}\n          sx={{\n            \"& .MuiLineElement-series-drawdown\": { strokeWidth: 2.5 },\n            \"& .MuiAreaElement-series-drawdown\": { fillOpacity: 0.55 },\n            \"& .MuiLineElement-series-nav\": { strokeWidth: 1.75, strokeDasharray: \"6 4\" },\n            \"& .MuiChartsGrid-line\": { stroke: t.grid },\n          }}\n        >\n          <DrawdownWindow from={dates[peakIdx]} to={dates[troughIdx]} color={t.amber} opacity={0.1} />\n          {recoveryIdx > -1 && (\n            <DrawdownWindow from={dates[troughIdx]} to={dates[recoveryIdx]} color={t.palette[0]} opacity={0.08} />\n          )}\n          <ChartsReferenceLine\n            y={0}\n            axisId=\"dd\"\n            lineStyle={{ stroke: t.ink, strokeDasharray: \"4 4\", strokeOpacity: 0.5 }}\n          />\n          <ChartsReferenceLine\n            x={dates[troughIdx]}\n            label={`Max drawdown ${maxDrawdownPct.toFixed(1)}%`}\n            labelAlign=\"start\"\n            lineStyle={{ stroke: t.amber, strokeWidth: 2, strokeDasharray: \"5 4\" }}\n            labelStyle={{ fill: t.amber, fontSize: 13, fontWeight: 600 }}\n          />\n          {recoveryIdx > -1 && (\n            <ChartsReferenceLine\n              x={dates[recoveryIdx]}\n              label=\"Recovered\"\n              labelAlign=\"end\"\n              lineStyle={{ stroke: t.palette[0], strokeWidth: 2, strokeDasharray: \"5 4\" }}\n              labelStyle={{ fill: t.palette[0], fontSize: 13, fontWeight: 600 }}\n            />\n          )}\n        </LineChart>\n      </Box>\n    </Box>\n  );\n}\n"}