{"spec_id":"frontier-efficient","library":"echarts","language":"javascript","code":"// anyplot.ai\n// frontier-efficient: Efficient Frontier for Portfolio Optimization\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-09-02\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: a 5-asset universe (annualized mean return / volatility) ---------\nconst assetNames = [\n  \"US Equities\",\n  \"Intl Equities\",\n  \"Real Estate\",\n  \"Corp Bonds\",\n  \"Commodities\",\n];\nconst mu = [0.11, 0.09, 0.08, 0.045, 0.07];\nconst vol = [0.18, 0.2, 0.16, 0.06, 0.24];\nconst corr = [\n  [1.0, 0.65, 0.35, -0.1, 0.15],\n  [0.65, 1.0, 0.3, -0.05, 0.2],\n  [0.35, 0.3, 1.0, 0.05, 0.1],\n  [-0.1, -0.05, 0.05, 1.0, -0.05],\n  [0.15, 0.2, 0.1, -0.05, 1.0],\n];\nconst nAssets = mu.length;\nconst cov = corr.map((row, i) => row.map((c, j) => c * vol[i] * vol[j]));\nconst riskFreeRate = 0.025;\n\n// --- Small linear-algebra helpers (Gauss-Jordan inverse, matrix/vector ops) --\nfunction invertMatrix(matrix) {\n  const n = matrix.length;\n  const aug = matrix.map((row, i) => [\n    ...row,\n    ...Array.from({ length: n }, (_, j) => (i === j ? 1 : 0)),\n  ]);\n  for (let col = 0; col < n; col++) {\n    let pivotRow = col;\n    for (let r = col + 1; r < n; r++) {\n      if (Math.abs(aug[r][col]) > Math.abs(aug[pivotRow][col])) pivotRow = r;\n    }\n    [aug[col], aug[pivotRow]] = [aug[pivotRow], aug[col]];\n    const pivot = aug[col][col];\n    for (let j = 0; j < 2 * n; j++) aug[col][j] /= pivot;\n    for (let r = 0; r < n; r++) {\n      if (r === col) continue;\n      const factor = aug[r][col];\n      for (let j = 0; j < 2 * n; j++) aug[r][j] -= factor * aug[col][j];\n    }\n  }\n  return aug.map((row) => row.slice(n));\n}\nconst matVec = (m, v) => m.map((row) => row.reduce((s, x, j) => s + x * v[j], 0));\nconst dot = (a, b) => a.reduce((s, x, i) => s + x * b[i], 0);\n\n// --- Mean-variance frontier (closed-form Merton solution) -------------------\nconst invCov = invertMatrix(cov);\nconst ones = mu.map(() => 1);\nconst invCovOnes = matVec(invCov, ones);\nconst invCovMu = matVec(invCov, mu);\nconst scalarA = dot(ones, invCovOnes);\nconst scalarB = dot(ones, invCovMu);\nconst scalarC = dot(mu, invCovMu);\nconst scalarD = scalarA * scalarC - scalarB * scalarB;\n\nconst minVarReturn = scalarB / scalarA;\nconst minVarRisk = Math.sqrt(1 / scalarA);\n\nconst frontierReturnMax = Math.max(...mu) * 1.4;\nconst frontierPoints = [];\nconst frontierSteps = 80;\nfor (let i = 0; i <= frontierSteps; i++) {\n  const r = minVarReturn + ((frontierReturnMax - minVarReturn) * i) / frontierSteps;\n  const variance = (scalarA * r * r - 2 * scalarB * r + scalarC) / scalarD;\n  const risk = Math.sqrt(Math.max(variance, 0));\n  frontierPoints.push([risk * 100, r * 100]);\n}\n\n// Tangency (max Sharpe ratio) portfolio.\nconst excessInvCov = matVec(invCov, mu.map((m) => m - riskFreeRate));\nconst excessSum = excessInvCov.reduce((s, x) => s + x, 0);\nconst tangencyWeights = excessInvCov.map((x) => x / excessSum);\nconst tangencyReturn = dot(tangencyWeights, mu);\nconst tangencyRisk = Math.sqrt(dot(tangencyWeights, matVec(cov, tangencyWeights)));\n\nconst cmlSlope = (tangencyReturn - riskFreeRate) / tangencyRisk;\nconst cmlMaxRisk = frontierPoints[frontierPoints.length - 1][0] / 100;\nconst capitalMarketLine = [\n  [0, riskFreeRate * 100],\n  [cmlMaxRisk * 100, (riskFreeRate + cmlSlope * cmlMaxRisk) * 100],\n];\n\n// --- Random long-only portfolios (uniform over the 5-asset simplex) --------\nfunction makeLcg(seed) {\n  let state = seed >>> 0;\n  return () => {\n    state = (1103515245 * state + 12345) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(42);\nconst portfolioCount = 400;\nconst randomPortfolios = [];\nfor (let p = 0; p < portfolioCount; p++) {\n  const draws = Array.from({ length: nAssets }, () => -Math.log(Math.max(rand(), 1e-9)));\n  const drawSum = draws.reduce((s, x) => s + x, 0);\n  const weights = draws.map((x) => x / drawSum);\n  const portfolioReturn = dot(weights, mu);\n  const portfolioRisk = Math.sqrt(dot(weights, matVec(cov, weights)));\n  const sharpe = (portfolioReturn - riskFreeRate) / portfolioRisk;\n  randomPortfolios.push([portfolioRisk * 100, portfolioReturn * 100, sharpe]);\n}\nconst sharpeValues = randomPortfolios.map((d) => d[2]);\nconst sharpeMin = Math.min(...sharpeValues);\nconst sharpeMax = Math.max(...sharpeValues);\n\n// --- Init ---------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option ---------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"frontier-efficient · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 16,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  legend: {\n    data: [\n      \"Efficient Frontier\",\n      \"Capital Market Line\",\n      \"Min-Variance Portfolio\",\n      \"Max-Sharpe Portfolio\",\n    ],\n    top: 64,\n    textStyle: { color: t.inkSoft, fontSize: 15 },\n    itemWidth: 22,\n    itemHeight: 12,\n  },\n  grid: { left: 110, right: 190, top: 140, bottom: 90 },\n  xAxis: {\n    type: \"value\",\n    name: \"Risk — Annualized Volatility (%)\",\n    nameLocation: \"middle\",\n    nameGap: 42,\n    min: 0,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: \"{value}%\" },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Expected Return — Annualized (%)\",\n    nameLocation: \"middle\",\n    nameGap: 60,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: \"{value}%\" },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  visualMap: {\n    type: \"continuous\",\n    seriesIndex: 0,\n    dimension: 2,\n    min: sharpeMin,\n    max: sharpeMax,\n    orient: \"vertical\",\n    right: 16,\n    top: \"middle\",\n    itemWidth: 18,\n    itemHeight: 220,\n    text: [\"High Sharpe\", \"Low Sharpe\"],\n    textStyle: { color: t.inkSoft, fontSize: 13 },\n    inRange: { color: t.seq },\n  },\n  tooltip: {\n    trigger: \"item\",\n    formatter: (p) =>\n      Array.isArray(p.value) && p.value.length >= 3\n        ? `Risk: ${p.value[0].toFixed(1)}%<br/>Return: ${p.value[1].toFixed(1)}%<br/>Sharpe: ${p.value[2].toFixed(2)}`\n        : `${p.seriesName}<br/>Risk: ${p.value[0].toFixed(1)}%<br/>Return: ${p.value[1].toFixed(1)}%`,\n  },\n  series: [\n    {\n      name: \"Random Portfolios\",\n      type: \"scatter\",\n      data: randomPortfolios,\n      symbolSize: 7,\n      itemStyle: { opacity: 0.45 },\n    },\n    {\n      name: \"Efficient Frontier\",\n      type: \"line\",\n      data: frontierPoints,\n      showSymbol: false,\n      smooth: true,\n      lineStyle: { color: t.ink, width: 4 },\n      itemStyle: { color: t.ink },\n      z: 3,\n    },\n    {\n      name: \"Capital Market Line\",\n      type: \"line\",\n      data: capitalMarketLine,\n      showSymbol: false,\n      lineStyle: { color: t.ink, width: 2.5, type: \"dashed\" },\n      itemStyle: { color: t.ink },\n      z: 2,\n    },\n    {\n      name: \"Min-Variance Portfolio\",\n      type: \"scatter\",\n      data: [[minVarRisk * 100, minVarReturn * 100]],\n      symbol: \"diamond\",\n      symbolSize: 24,\n      itemStyle: { color: t.ink, borderColor: t.pageBg, borderWidth: 2 },\n      label: {\n        show: true,\n        formatter: \"Min Variance\",\n        position: \"bottom\",\n        distance: 10,\n        color: t.ink,\n        fontSize: 14,\n      },\n      z: 4,\n    },\n    {\n      name: \"Max-Sharpe Portfolio\",\n      type: \"scatter\",\n      data: [[tangencyRisk * 100, tangencyReturn * 100]],\n      symbol: \"pin\",\n      symbolSize: 34,\n      itemStyle: { color: t.ink, borderColor: t.pageBg, borderWidth: 2 },\n      label: {\n        show: true,\n        formatter: \"Max Sharpe\",\n        position: \"top\",\n        distance: 8,\n        color: t.ink,\n        fontSize: 14,\n      },\n      z: 4,\n    },\n  ],\n});\n"}