Retirement Portfolio Withdrawal Rate Simulator
Portfolio & Withdrawal Inputs
Percentage of initial portfolio value for the first year's withdrawal.
Economic Assumptions
Before inflation.
Note: These are fixed average assumptions for an illustrative simulation. Real-world returns and inflation will vary.
Simulation Results
Click "Run Simulation" to view results. Ensure all inputs are set.
Portfolio lasted for the full ${data.inputs.simulationLength}-year simulation.
`; html += `Final Portfolio Balance: ${formatCurrency(data.summary.finalBalance)}
`; } html += `Total Amount Withdrawn: ${formatCurrency(data.summary.totalWithdrawn)}
`; html += ``;
html += `
`;
simulationResultsDiv.innerHTML = html;
}
if (runSimulationBtn) {
runSimulationBtn.addEventListener('click', runSimulation);
}
// --- PDF Download ---
function loadJsPdfIfNeeded(callback) {
if (jsPdfLoaded) { if (callback) callback(); return; }
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
script.onload = () => { jsPdfLoaded = true; console.log("jsPDF loaded dynamically."); if (callback) callback(); };
script.onerror = () => { console.error("Failed to load jsPDF."); alert("Error: Could not load PDF library."); };
document.head.appendChild(script);
}
function downloadResultsAsPdf() {
if (!jsPdfLoaded) { alert("PDF library not loaded."); return; }
if (!simulationDataForPdf) { alert("No simulation data to download. Please run a simulation first."); return; }
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ unit: 'pt', format: 'a4' });
const data = simulationDataForPdf;
const pageMargin = 40;
const pageWidth = doc.internal.pageSize.getWidth() - 2 * pageMargin;
let y = pageMargin;
function addMainTitle(text) {
doc.setFontSize(18); doc.setFont(undefined, 'bold'); doc.setTextColor(0, 77, 64); // Primary
doc.text(text, doc.internal.pageSize.getWidth() / 2, y, { align: 'center' }); y += 35;
}
function addSectionTitle(text) {
if (y > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); y = pageMargin; }
doc.setFontSize(14); doc.setFont(undefined, 'bold'); doc.setTextColor(0, 121, 107); // Secondary
doc.text(text, pageMargin, y); y += 25;
}
function addLine(key, value) {
if (y > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); y = pageMargin; }
doc.setFontSize(10);
doc.setFont(undefined, 'bold'); doc.setTextColor(33,33,33);
doc.text(key, pageMargin, y);
doc.setFont(undefined, 'normal');
const valueText = String(value);
doc.text(valueText, pageMargin + 220, y, { align: 'left', maxWidth: pageWidth - 220 - 5 });
y += 18;
}
function addInfo(text, isError = false) {
if (y > doc.internal.pageSize.getHeight() - 50) { doc.addPage(); y = pageMargin; }
doc.setFontSize(9); doc.setFont(undefined, 'italic');
doc.setTextColor(isError ? 211 : 100, isError ? 47 : 100, isError ? 47 : 100);
const splitText = doc.splitTextToSize(text, pageWidth);
doc.text(splitText, pageMargin, y);
y += (doc.getTextDimensions(splitText).h) + 10;
}
addMainTitle("Retirement Portfolio Withdrawal Simulation Report");
addInfo(`Report Generated: ${new Date().toLocaleString()}`);
y += 10;
addSectionTitle("Input Parameters");
addLine("Initial Portfolio Value:", formatCurrency(data.inputs.initialPortfolio));
addLine("Initial Annual Withdrawal Rate:", `${data.inputs.annualWithdrawalRatePct.toFixed(1)}%`);
addLine("Simulation Length:", `${data.inputs.simulationLength} years`);
addLine("Expected Avg. Annual Return:", `${data.inputs.annualReturnPct.toFixed(1)}%`);
addLine("Expected Avg. Annual Inflation:", `${data.inputs.annualInflationPct.toFixed(1)}%`);
addLine("Adjust Withdrawals for Inflation:", data.inputs.adjustForInflation ? "Yes" : "No");
y += 10;
addSectionTitle("Simulation Summary");
if (data.summary.fundsDepletedYear !== -1 && data.summary.fundsDepletedYear <= data.inputs.simulationLength) {
addLine("Portfolio Outcome:", `Depleted in Year ${data.summary.fundsDepletedYear}`, [211,47,47], [211,47,47]);
} else {
addLine("Portfolio Outcome:", `Lasted for ${data.inputs.simulationLength} years`, [56,142,60],[56,142,60]);
addLine("Final Portfolio Balance:", formatCurrency(data.summary.finalBalance));
}
addLine("Total Amount Withdrawn:", formatCurrency(data.summary.totalWithdrawn));
y += 10;
addSectionTitle("Year-by-Year Projection");
if (y > doc.internal.pageSize.getHeight() - 60) { doc.addPage(); y = pageMargin; } // Check space for table headers
const tableHeaders = ["Year", "Start Balance", "Withdrawal", "Growth", "End Balance"];
const tableColumnWidths = [40, 120, 100, 100, 120]; // Sum should be less than pageWidth
// Draw headers
doc.setFontSize(9); doc.setFillColor(0, 121, 107); doc.setTextColor(255); doc.setFont(undefined, 'bold');
let currentX = pageMargin;
tableHeaders.forEach((header, i) => {
doc.rect(currentX, y, tableColumnWidths[i], 20, 'F');
doc.text(header, currentX + 5, y + 14);
currentX += tableColumnWidths[i];
});
y += 20;
doc.setTextColor(33,33,33); doc.setFont(undefined, 'normal');
data.table.forEach(row => {
if (y > doc.internal.pageSize.getHeight() - 35) {
doc.addPage(); y = pageMargin;
// Redraw headers on new page
currentX = pageMargin;
doc.setFillColor(0, 121, 107); doc.setTextColor(255); doc.setFont(undefined, 'bold');
tableHeaders.forEach((header, i) => {
doc.rect(currentX, y, tableColumnWidths[i], 20, 'F');
doc.text(header, currentX + 5, y + 14);
currentX += tableColumnWidths[i];
});
y += 20;
doc.setTextColor(33,33,33); doc.setFont(undefined, 'normal');
}
currentX = pageMargin;
const rowData = [
row.year,
formatCurrency(row.startBalance,0,0),
formatCurrency(row.withdrawal,0,0),
formatCurrency(row.growth,0,0),
formatCurrency(row.endBalance,0,0) + (row.notes ? ` (${row.notes})` : '')
];
rowData.forEach((cell, i) => {
doc.rect(currentX, y, tableColumnWidths[i], 18);
const cellText = String(cell);
const textLines = doc.splitTextToSize(cellText, tableColumnWidths[i] - 8); // Small padding
doc.text(textLines, currentX + 4, y + 12);
currentX += tableColumnWidths[i];
});
y += 18;
});
y += 10;
addInfo("Disclaimer: This simulation uses fixed average assumptions and does not account for market volatility, sequence of returns risk, taxes, or fees. It is for illustrative and educational purposes only and should not be considered financial advice. Consult with a qualified financial advisor for personalized retirement planning.");
// Footer
const pageCount = doc.internal.getNumberOfPages();
for (let i = 1; i <= pageCount; i++) {
doc.setPage(i);
doc.setFontSize(8); doc.setTextColor(150);
doc.text(`Page ${i} of ${pageCount} - Retirement Withdrawal Simulator`, pageMargin, doc.internal.pageSize.getHeight() - 20);
}
doc.save('Retirement_Portfolio_Simulation.pdf');
}
if (downloadPdfBtn) {
downloadPdfBtn.addEventListener('click', () => loadJsPdfIfNeeded(downloadResultsAsPdf));
}
// --- Initialization ---
showTab(0);
loadJsPdfIfNeeded();
});
| Year | Start Balance | Withdrawal | Investment Growth | End Balance |
|---|---|---|---|---|
| ${row.year} | ${formatCurrency(row.startBalance)} | ${formatCurrency(row.withdrawal)} | ${formatCurrency(row.growth)} | ${formatCurrency(row.endBalance)} ${row.notes ? `(${row.notes})` : ''} |