Endowment Model Portfolio Analyzer (Conceptual Simulator)
Portfolio Basics
$
Asset Allocation & Capital Market Assumptions
Define your target asset classes, allocations, and their expected long-term performance. Illustrative defaults are provided but can be customized.
Total Allocation: 0%
Spending Rule & Projection Settings
%
%
Spending will increase by this rate annually in nominal terms if "Adjust Spending for Inflation" is checked.
Portfolio Analysis & Projections
Portfolio summary metrics will appear here.
Target Asset Allocation
Portfolio Value Projection (Nominal & Real)
Projection chart will appear here.
Year-by-Year Projection Details:
Projection table will appear here.
Projection data unavailable for chart.
'; return; } const svgWidth = Math.min(800, (empa_getEl('empaContainer_main').offsetWidth || 400) - 80); const svgHeight = 280; const m = {top: 20, right: 30, bottom: 40, left: 70}; const w = svgWidth - m.left - m.right; const h = svgHeight - m.top - m.bottom; const allYValues = projections.flatMap(p => [p.endNominalValue, p.endRealValue]).concat(initialValue); const yMin = 0; // Start y-axis at 0 for financial value charts const yMax = Math.max(...allYValues.filter(v => !isNaN(v) && v !== null)); const xMax = projections.length; // Number of years const xScale = x => (x / xMax) * w; // x is year number (1 to length) const yScale = y => h - ((y - yMin) / (yMax - yMin === 0 ? 1 : yMax - yMin)) * h; let nominalPathData = `M ${xScale(0)},${yScale(initialValue)} L ` + projections.map(p => `${xScale(p.year).toFixed(2)},${yScale(p.endNominalValue).toFixed(2)}`).join(" L"); let realPathData = `M ${xScale(0)},${yScale(initialValue)} L ` + projections.map(p => `${xScale(p.year).toFixed(2)},${yScale(p.endRealValue).toFixed(2)}`).join(" L"); container.innerHTML = ` `; } // --- PDF Generation --- function empa_downloadPDF() { if (!empa_model.analysisResults) { alert("Please analyze the 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 = empa_model.analysisResults; 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(`Endowment Model Portfolio Analysis: ${res.portfolioName || 'N/A'}`, 16, 'bold', 0, 5); addLine(`Initial Portfolio Value: ${empa_formatCurrency(res.initialValue)}`, 10); addLine(`Report Date: ${new Date().toLocaleDateString()}`, 9, 'italic', 0, 5); addLine("Summary Metrics:", 12, 'bold', 0, 3); let summaryData = [ ["Portfolio Expected Annual Return:", empa_formatPercent(res.portfolioExpReturn)], ["Illustrative Weighted Avg. Volatility:", empa_formatPercent(res.portfolioWeightedAvgVol)], ["Annual Spending Rate:", empa_formatPercent(res.spendingRate,1)], ["Assumed Inflation Rate:", empa_formatPercent(res.inflationRate,1)], ["Spending Adjusted for Inflation:", res.adjustSpendingForInflation ? 'Yes' : 'No'], ["Projection Period:", `${res.projectionPeriod} years`], ]; 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("Target Asset Allocation:", 12, 'bold', 0, 3); const allocHead = [['Asset Class', 'Allocation (%)', 'Exp. Return (%)', 'Exp. Volatility (%)']]; const allocBody = res.assetClasses.filter(ac=>(ac.allocation||0)>0).map(ac => [ac.name, ac.allocation.toFixed(1), ac.expectedReturn.toFixed(1), ac.expectedVolatility.toFixed(1)]); if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, head: allocHead, body: allocBody, theme: 'striped', headStyles: {fillColor:[55,65,81]}, styles:{fontSize:8.5}}); y = doc.lastAutoTable.finalY + 6; } else { addLine("Asset allocation table could not be generated (plugin issue).", 8, 'italic'); y+=5; } addLine(`Year-by-Year Projections (${res.projectionPeriod} Years):`, 12, 'bold', 0, 3); const projHead = [['Year', 'Start Value', 'Growth', 'Value pre-Spend', 'Spending', 'End Value (Nom.)', 'End Value (Real)']]; const projBody = res.projections.map(p => [ p.year, empa_formatCurrency(p.startNominalValue), empa_formatCurrency(p.nominalGrowthAmount), empa_formatCurrency(p.valueBeforeSpending), empa_formatCurrency(p.nominalSpendingAmount), empa_formatCurrency(p.endNominalValue), empa_formatCurrency(p.endRealValue) ]); if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, head: projHead, body: projBody, theme: 'grid', headStyles: {fillColor:[55,65,81]}, styles:{fontSize:8, cellPadding:1.2, halign:'right'}, columnStyles:{0:{halign:'center'}}}); y = doc.lastAutoTable.finalY + 7; } else { addLine("Projection table could not be generated (plugin issue).", 8, 'italic'); y+=5;} addLine("Disclaimer: This is a conceptual simulator. Expected returns, volatilities, and correlations are assumptions. Actual results will vary. Weighted average volatility is not true portfolio volatility.", 7, 'italic'); doc.save(`${(res.portfolioName || 'EndowmentModel').replace(/\s+/g, '_')}_Analysis.pdf`); }