{"spec_id":"frontier-efficient","library":"d3","language":"javascript","code":"// anyplot.ai\n// frontier-efficient: Efficient Frontier for Portfolio Optimization\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 110, right: 150, bottom: 90, left: 110 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data: Monte Carlo portfolio simulation over a 5-asset universe --------\nfunction lcg(seed) {\n  let state = seed;\n  return function () {\n    state = (state * 1103515245 + 12345) % 2147483648;\n    return state / 2147483648;\n  };\n}\nconst rand = lcg(42);\n\nconst assets = [\n  { name: \"Bonds\", return: 0.035, vol: 0.06 },\n  { name: \"REIT\", return: 0.07, vol: 0.18 },\n  { name: \"Intl Equity\", return: 0.08, vol: 0.19 },\n  { name: \"US Equity\", return: 0.1, vol: 0.16 },\n  { name: \"Commodities\", return: 0.05, vol: 0.22 },\n];\nconst RISK_FREE = 0.02;\n\n// One-factor market model: corr_ij = loading_i * loading_j (i != j). This\n// guarantees a valid positive-semidefinite correlation matrix without having\n// to hand-verify eigenvalues on a hand-picked 5x5 table.\nconst marketLoading = [0.05, 0.65, 0.75, 0.85, 0.55];\nfunction correlation(i, j) {\n  return i === j ? 1 : marketLoading[i] * marketLoading[j];\n}\nfunction covariance(i, j) {\n  return correlation(i, j) * assets[i].vol * assets[j].vol;\n}\n\nfunction randomWeights(n) {\n  const raw = Array.from({ length: n }, () => -Math.log(1 - rand()));\n  const sum = raw.reduce((a, b) => a + b, 0);\n  return raw.map((v) => v / sum);\n}\n\nconst N_PORTFOLIOS = 350;\nconst portfolios = Array.from({ length: N_PORTFOLIOS }, () => {\n  const w = randomWeights(assets.length);\n  let ret = 0;\n  for (let i = 0; i < assets.length; i++) ret += w[i] * assets[i].return;\n  let variance = 0;\n  for (let i = 0; i < assets.length; i++) {\n    for (let j = 0; j < assets.length; j++) variance += w[i] * w[j] * covariance(i, j);\n  }\n  const risk = Math.sqrt(variance);\n  return { risk, ret, sharpe: (ret - RISK_FREE) / risk };\n});\n\n// Efficient frontier: Pareto upper-left boundary of the simulated cloud —\n// sort by risk, keep only portfolios that beat every lower-risk portfolio's\n// return so far.\nconst byRisk = [...portfolios].sort((a, b) => a.risk - b.risk);\nconst frontier = [];\nlet bestRetSoFar = -Infinity;\nfor (const p of byRisk) {\n  if (p.ret > bestRetSoFar) {\n    frontier.push(p);\n    bestRetSoFar = p.ret;\n  }\n}\nconst minVariance = frontier[0];\nconst maxSharpe = frontier.reduce((best, p) => (p.sharpe > best.sharpe ? p : best));\n\nconst maxRisk = d3.max(portfolios, (d) => d.risk);\nconst cmlEnd = { risk: maxRisk * 1.05, ret: RISK_FREE + maxSharpe.sharpe * maxRisk * 1.05 };\n\n// --- Scales -------------------------------------------------------------\nconst x = d3\n  .scaleLinear()\n  .domain([0, maxRisk * 1.08])\n  .nice()\n  .range([0, iw]);\nconst y = d3\n  .scaleLinear()\n  .domain([RISK_FREE * 0.5, d3.max(portfolios, (d) => d.ret) * 1.08])\n  .nice()\n  .range([ih, 0]);\nconst sharpeExtent = d3.extent(portfolios, (d) => d.sharpe);\nconst color = d3.scaleSequential(d3.interpolateRgbBasis(t.seq)).domain(sharpeExtent);\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})`);\nsvg\n  .append(\"clipPath\")\n  .attr(\"id\", \"plot-area-clip\")\n  .append(\"rect\")\n  .attr(\"width\", iw)\n  .attr(\"height\", ih);\n\n// --- Axes -------------------------------------------------------------\nconst pctFormat = d3.format(\".0%\");\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(8).tickFormat(pctFormat).tickSize(-ih * 0.0));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6).tickFormat(pctFormat));\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}\n\n// gridlines (both axes, subtle — this chart mixes scatter points with line series)\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .call(d3.axisLeft(y).ticks(6).tickSize(-iw).tickFormat(\"\"))\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\ng.select(\".grid .domain\").remove();\n\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(8).tickSize(-ih).tickFormat(\"\"))\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\ng.selectAll(\".grid .domain\").remove();\n\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 60)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Portfolio Risk (annualized standard deviation)\");\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -80)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Expected Return (annualized)\");\n\n// --- Capital market line (risk-free tangent through the max-Sharpe portfolio)\nconst cmlLine = d3\n  .line()\n  .x((d) => x(d.risk))\n  .y((d) => y(d.ret));\ng.append(\"path\")\n  .datum([{ risk: 0, ret: RISK_FREE }, cmlEnd])\n  .attr(\"clip-path\", \"url(#plot-area-clip)\")\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.inkSoft)\n  .attr(\"stroke-width\", 2)\n  .attr(\"stroke-dasharray\", \"8,6\")\n  .attr(\"d\", cmlLine);\n\n// --- Random portfolio cloud, colored by Sharpe ratio -----------------------\ng.selectAll(\"circle.portfolio\")\n  .data(portfolios)\n  .join(\"circle\")\n  .attr(\"class\", \"portfolio\")\n  .attr(\"cx\", (d) => x(d.risk))\n  .attr(\"cy\", (d) => y(d.ret))\n  .attr(\"r\", 6)\n  .attr(\"fill\", (d) => color(d.sharpe))\n  .attr(\"fill-opacity\", 0.55)\n  .attr(\"stroke\", \"none\");\n\n// --- Efficient frontier curve (hero series — always brand green) ----------\nconst frontierLine = d3\n  .line()\n  .x((d) => x(d.risk))\n  .y((d) => y(d.ret))\n  .curve(d3.curveMonotoneX);\ng.append(\"path\")\n  .datum(frontier)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 4.5)\n  .attr(\"d\", frontierLine);\n\n// --- Key portfolios: minimum variance + maximum Sharpe (tangency) ---------\ng.append(\"circle\")\n  .attr(\"cx\", x(minVariance.risk))\n  .attr(\"cy\", y(minVariance.ret))\n  .attr(\"r\", 11)\n  .attr(\"fill\", t.palette[1])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 2.5);\ng.append(\"text\")\n  .attr(\"x\", x(minVariance.risk) + 18)\n  .attr(\"y\", y(minVariance.ret) + 5)\n  .attr(\"fill\", t.ink)\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 6)\n  .attr(\"stroke-linejoin\", \"round\")\n  .attr(\"paint-order\", \"stroke\")\n  .style(\"font-size\", \"15px\")\n  .text(\"Min variance\");\n\ng.append(\"path\")\n  .attr(\n    \"d\",\n    d3.symbol().type(d3.symbolStar).size(420)()\n  )\n  .attr(\"transform\", `translate(${x(maxSharpe.risk)},${y(maxSharpe.ret)})`)\n  .attr(\"fill\", t.palette[2])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 2);\ng.append(\"text\")\n  .attr(\"x\", x(maxSharpe.risk) + 20)\n  .attr(\"y\", y(maxSharpe.ret) - 24)\n  .attr(\"fill\", t.ink)\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 6)\n  .attr(\"stroke-linejoin\", \"round\")\n  .attr(\"paint-order\", \"stroke\")\n  .style(\"font-size\", \"15px\")\n  .text(\"Max Sharpe (tangency)\");\n\n// --- Legend: frontier / CML swatches ---------------------------------------\nconst legend = g.append(\"g\").attr(\"transform\", `translate(${iw - 300}, -70)`);\nconst legendRows = [\n  { label: \"Efficient frontier\", stroke: t.palette[0], dash: null, width: 4.5 },\n  { label: \"Capital market line\", stroke: t.inkSoft, dash: \"8,6\", width: 2 },\n];\nlegendRows.forEach((row, i) => {\n  legend\n    .append(\"line\")\n    .attr(\"x1\", 0)\n    .attr(\"x2\", 34)\n    .attr(\"y1\", i * 26)\n    .attr(\"y2\", i * 26)\n    .attr(\"stroke\", row.stroke)\n    .attr(\"stroke-width\", row.width)\n    .attr(\"stroke-dasharray\", row.dash);\n  legend\n    .append(\"text\")\n    .attr(\"x\", 44)\n    .attr(\"y\", i * 26 + 5)\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"14px\")\n    .text(row.label);\n});\n\n// --- Sharpe ratio color legend (vertical gradient bar) ----------------------\nconst legendGrad = svg\n  .append(\"defs\")\n  .append(\"linearGradient\")\n  .attr(\"id\", \"sharpe-gradient\")\n  .attr(\"x1\", \"0%\")\n  .attr(\"y1\", \"100%\")\n  .attr(\"x2\", \"0%\")\n  .attr(\"y2\", \"0%\");\nd3.range(0, 1.001, 0.1).forEach((stop) => {\n  legendGrad\n    .append(\"stop\")\n    .attr(\"offset\", `${stop * 100}%`)\n    .attr(\"stop-color\", color(sharpeExtent[0] + stop * (sharpeExtent[1] - sharpeExtent[0])));\n});\nconst barX = margin.left + iw + 50;\nconst barY = margin.top + 40;\nconst barH = 220;\nsvg\n  .append(\"rect\")\n  .attr(\"x\", barX)\n  .attr(\"y\", barY)\n  .attr(\"width\", 20)\n  .attr(\"height\", barH)\n  .attr(\"fill\", \"url(#sharpe-gradient)\");\nsvg\n  .append(\"text\")\n  .attr(\"x\", barX + 30)\n  .attr(\"y\", barY + 5)\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"13px\")\n  .text(sharpeExtent[1].toFixed(2));\nsvg\n  .append(\"text\")\n  .attr(\"x\", barX + 30)\n  .attr(\"y\", barY + barH)\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"13px\")\n  .text(sharpeExtent[0].toFixed(2));\nsvg\n  .append(\"text\")\n  .attr(\"x\", barX - 6)\n  .attr(\"y\", barY - 16)\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"13px\")\n  .text(\"Sharpe ratio\");\n\n// --- Title ------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 48)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"frontier-efficient · javascript · d3 · anyplot.ai\");\n"}