ESG Asset Allocation Simulator (Conceptual Analyzer)
Portfolio & Economic Assumptions
$
E.g., 0.2-0.4 for diversified portfolios. Affects conceptual portfolio volatility.
Asset Allocation & Assumptions (Financial & ESG)
Define asset classes, target allocations (must sum to 100%), their expected nominal performance, and conceptual ESG scores (1-10, where 10 is best).
Total Allocation: 0%
Portfolio ESG & Financial Analysis
Portfolio summary metrics will appear here.
Target Asset Allocation
Portfolio ESG Profile
Asset Class Details (Financial & ESG):
No allocations.
'; return; } const boxSize = Math.min(svg.parentElement.offsetWidth, svg.parentElement.offsetHeight, 260) || 260; svg.setAttribute('viewBox', `0 0 ${boxSize} ${boxSize}`); const radius = boxSize / 2 * 0.85; const cx = boxSize / 2; const cy = boxSize / 2; let cumulativePercent = 0; filteredAC.forEach(ac => { const percent = (ac.targetAllocation || 0) / 100; const startAngle = cumulativePercent * 2 * Math.PI - Math.PI / 2; const endAngle = (cumulativePercent + percent) * 2 * Math.PI - Math.PI / 2; const x1 = cx + radius * Math.cos(startAngle); const y1 = cy + radius * Math.sin(startAngle); const x2 = cx + radius * Math.cos(endAngle); const y2 = cy + radius * Math.sin(endAngle); const largeArcFlag = percent > 0.5 ? 1 : 0; const pathData = `M ${cx},${cy} L ${x1},${y1} A ${radius},${radius} 0 ${largeArcFlag},1 ${x2},${y2} Z`; const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", pathData); path.setAttribute("fill", ac.color || '#ccc'); path.setAttribute("stroke", "#1F2937"); path.setAttribute("stroke-width", "1"); const title = document.createElementNS("http://www.w3.org/2000/svg", "title"); title.textContent = `${ac.name}: ${ac.targetAllocation.toFixed(1)}%`; path.appendChild(title); svg.appendChild(path); cumulativePercent += percent; }); } function esgaas_renderRadarChart(esgScores, svgId) { // esgScores = {e, s, g, overall} (overall not used for radar axes) const svg = esgaas_getEl(svgId); if (!svg) return; svg.innerHTML = ''; const labels = ["Environmental (E)", "Social (S)", "Governance (G)"]; const data = [esgScores.e, esgScores.s, esgScores.g]; const numAxes = labels.length; const maxScore = 10; // Assuming scores are 0-10 const boxSize = Math.min(svg.parentElement.offsetWidth, svg.parentElement.offsetHeight, 260) || 260; svg.setAttribute('viewBox', `0 0 ${boxSize} ${boxSize}`); const radius = boxSize / 2 * 0.7; // Main radius const cx = boxSize / 2; const cy = boxSize / 2; // Draw grid lines (e.g., 3 levels: 3.33, 6.66, 10) for (let i = 1; i <= 3; i++) { const r = radius * (i / 3); let pathDataGrid = "M "; for (let j = 0; j < numAxes; j++) { const angle = (Math.PI * 2 * j / numAxes) - (Math.PI / 2); // Start at top const x = cx + r * Math.cos(angle); const y = cy + r * Math.sin(angle); pathDataGrid += `${x.toFixed(2)},${y.toFixed(2)} L `; } pathDataGrid = pathDataGrid.slice(0, -2) + " Z"; const gridPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); gridPath.setAttribute("d", pathDataGrid); gridPath.setAttribute("stroke", "#4B5563"); gridPath.setAttribute("stroke-width", "0.5"); gridPath.setAttribute("fill", "none"); svg.appendChild(gridPath); } // Draw axes lines and labels for (let i = 0; i < numAxes; i++) { const angle = (Math.PI * 2 * i / numAxes) - (Math.PI / 2); const xEnd = cx + radius * Math.cos(angle); const yEnd = cy + radius * Math.sin(angle); const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); line.setAttribute("x1", cx); line.setAttribute("y1", cy); line.setAttribute("x2", xEnd); line.setAttribute("y2", yEnd); line.setAttribute("stroke", "#6B7280"); line.setAttribute("stroke-width", "1"); svg.appendChild(line); const labelX = cx + (radius + 15) * Math.cos(angle); // Position labels outside const labelY = cy + (radius + 15) * Math.sin(angle); const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); text.setAttribute("x", labelX); text.setAttribute("y", labelY); text.setAttribute("fill", "#94A3B8"); text.setAttribute("font-size", "10"); text.setAttribute("text-anchor", angle === Math.PI / 2 || angle === -Math.PI / 2 ? "middle" : (angle > Math.PI / 2 || angle < -Math.PI / 2 ? "end" : "start")); text.setAttribute("dominant-baseline", "middle"); text.textContent = labels[i].split(' ')[0]; // Just E, S, G svg.appendChild(text); } // Draw data polygon let pathData = "M "; data.forEach((val, i) => { const normalizedVal = Math.max(0, Math.min(val, maxScore)) / maxScore; // Normalize 0-1 const angle = (Math.PI * 2 * i / numAxes) - (Math.PI / 2); const x = cx + radius * normalizedVal * Math.cos(angle); const y = cy + radius * normalizedVal * Math.sin(angle); pathData += `${x.toFixed(2)},${y.toFixed(2)} L `; }); pathData = pathData.slice(0, -2) + " Z"; // Close path const dataPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); dataPath.setAttribute("d", pathData); dataPath.setAttribute("fill", "rgba(167, 139, 250, 0.5)"); // Semi-transparent Lavender fill dataPath.setAttribute("stroke", "#A78BFA"); // Lavender stroke dataPath.setAttribute("stroke-width", "1.5"); svg.appendChild(dataPath); } // --- PDF Generation --- function esgaas_downloadPDF() { if (!esgaas_model.analysisResults) { alert("Please analyze portfolio first."); return; } if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Core PDF library (jsPDF) is not loaded.'); console.error('jsPDF library not found.'); return; } const { jsPDF: JSPDF } = window.jspdf; const doc = new JSPDF(); if (typeof doc.autoTable !== 'function') { alert('PDF Table plugin (jsPDF-AutoTable) not loaded. Tables may be missing.'); console.error('doc.autoTable is not a function.'); } let y = 15; const m = 15; const cw = doc.internal.pageSize.getWidth() - (2 * m); const res = esgaas_model.analysisResults; const inputs = res.inputsSnapshot || esgaas_model; // Use snapshot if available function addLine(text, size, style = 'normal', indent = 0, spacing = 2.5) { if (y > 275) { doc.addPage(); y = m; } doc.setFontSize(size); doc.setFont(undefined, style); const lines = doc.splitTextToSize(text, cw - indent); doc.text(lines, m + indent, y); y += (lines.length * (size * 0.35)) + spacing; } addLine(`ESG Asset Allocation Analysis: ${inputs.portfolioName || 'N/A'}`, 16, 'bold', 0, 5); addLine(`Report Date: ${new Date().toLocaleDateString()}`, 9, 'italic', 0, 5); addLine("Portfolio Summary:", 12, 'bold', 0, 3); let summaryData = [ ["Nominal Expected Return:", esgaas_formatPercent(res.portfolioNominalExpReturn)], ["Conceptual Nominal Volatility:", esgaas_formatPercent(res.portfolioNominalExpVolatility_conceptual)], ["Overall ESG Score (Conceptual):", `${esgaas_formatScore(res.portfolioEScores.overall)}/10 (${res.qualitativeESGRating})`], ["- E-Score (Weighted Avg):", `${esgaas_formatScore(res.portfolioEScores.e)}/10`], ["- S-Score (Weighted Avg):", `${esgaas_formatScore(res.portfolioEScores.s)}/10`], ["- G-Score (Weighted Avg):", `${esgaas_formatScore(res.portfolioEScores.g)}/10`], ]; if (typeof doc.autoTable === 'function') { doc.autoTable({ startY: y, body: summaryData, theme: 'plain', styles: {fontSize: 9, cellPadding: 1.2}, columnStyles: {0:{fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 6; } else { summaryData.forEach(row => addLine(`${row[0]} ${row[1]}`, 9)); y+=5; } addLine("Asset Allocation & Assumptions:", 11, 'bold', 0, 3); const head = [['Asset Class', 'Alloc. (%)', 'E(R) Nom.(%)', 'Vol. Nom.(%)', 'E', 'S', 'G']]; const body = res.assetClassesFull.map(ac => [ ac.name, (ac.targetAllocation||0).toFixed(1), (ac.nominalExpReturn||0).toFixed(1), (ac.nominalExpVolatility||0).toFixed(1), (ac.eScore||0).toFixed(0), (ac.sScore||0).toFixed(0), (ac.gScore||0).toFixed(0) ]); if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, head: head, body: body, theme: 'grid', headStyles:{fillColor:[55,65,81], fontSize:8.5}, styles:{fontSize:8, cellPadding:1.2, halign:'right'}, columnStyles: {0:{halign:'left', fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 7; } else { addLine("Asset details table cannot be generated (plugin issue).", 8, 'italic'); y+=5;} addLine("Note: This is a conceptual simulator. Expected returns, volatilities, ESG scores, and correlations are assumptions. Actual results will vary. This does not constitute investment advice.", 7, 'italic'); doc.save(`${(inputs.portfolioName || 'ESG_Portfolio').replace(/\s+/g, '_')}_Analysis.pdf`); }