Tax-Efficient Spending Strategy Optimizer
Your Retirement Profile
How many years to project spending in retirement.
In today's dollars. Will be inflation-adjusted.
Retirement Account Details
Taxable Account
Important for capital gains calculation.
Tax-Deferred Account (e.g., 401k, Trad IRA)
Tax-Free Account (e.g., Roth IRA, Roth 401k)
Tax & Growth Assumptions
Used to adjust spending needs over time.
Avg. rate for tax-deferred withdrawals.
For gains in taxable accounts.
Spending Strategy & Projected Results
This is a simplified order. Real strategies can be more nuanced.
Your tax-efficient spending projection will appear here.
No projection data to display.
`; } html += `This projection is illustrative and uses average rates. Actual market conditions, tax laws, and personal circumstances will vary. This tool does not account for Required Minimum Distributions (RMDs) or complex tax scenarios (e.g., state taxes, tax brackets). Consult with qualified financial and tax professionals.
`; optimizationResultsDiv.innerHTML = html; } if (optimizeBtn) { optimizeBtn.addEventListener('click', runOptimizationProjection); } // --- 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 downloadReportAsPdf() { if (!jsPdfLoaded) { alert("PDF library not loaded."); return; } if (!optimizationDataForPdf) { alert("No projection data to download. Please run the projection first."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ unit: 'pt', format: 'a4', orientation: 'landscape' }); // Landscape for wider table const data = optimizationDataForPdf; const pageMargin = 30; // Adjusted for landscape const pageWidth = doc.internal.pageSize.getWidth() - 2 * pageMargin; let y = pageMargin; function addMainTitle(text) { doc.setFontSize(16); doc.setFont(undefined, 'bold'); doc.setTextColor(13, 71, 161); // Primary doc.text(text, doc.internal.pageSize.getWidth() / 2, y, { align: 'center' }); y += 30; } function addSectionTitle(text) { if (y > doc.internal.pageSize.getHeight() - 60) { doc.addPage(); y = pageMargin; } doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(25, 118, 210); // Secondary doc.text(text, pageMargin, y); y += 20; } function addLine(key, value, keyColor = [33,33,33], valueColor = [33,33,33]) { if (y > doc.internal.pageSize.getHeight() - 30) { doc.addPage(); y = pageMargin; } doc.setFontSize(9); doc.setFont(undefined, 'bold'); doc.setTextColor(keyColor[0], keyColor[1], keyColor[2]); doc.text(key, pageMargin, y); doc.setFont(undefined, 'normal'); doc.setTextColor(valueColor[0], valueColor[1], valueColor[2]); const valueText = String(value); doc.text(valueText, pageMargin + 220, y, { align: 'left', maxWidth: pageWidth - 220 - 5 }); y += 16; } function addInfo(text) { if (y > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); y = pageMargin; } doc.setFontSize(8); doc.setFont(undefined, 'italic'); doc.setTextColor(100,100,100); const splitText = doc.splitTextToSize(text, pageWidth); doc.setFillColor(245,245,245); doc.rect(pageMargin -5, y - (doc.getTextDimensions(splitText).h / 2) - 2 , pageWidth + 10, doc.getTextDimensions(splitText).h + 8, 'F'); doc.text(splitText, pageMargin, y); y += (doc.getTextDimensions(splitText).h) + 10; } function addTable(headers, tableData, columnWidths) { if (y > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); y = pageMargin; } // Check for table header space doc.setFontSize(6.5); // Even smaller font for landscape table const headerFillColor = [25, 118, 210]; const headerTextColor = [255,255,255]; const rowTextColor = [33,33,33]; doc.setFillColor(headerFillColor[0], headerFillColor[1], headerFillColor[2]); doc.setTextColor(headerTextColor[0], headerTextColor[1], headerTextColor[2]); doc.setFont(undefined, 'bold'); let currentX = pageMargin; headers.forEach((header, i) => { doc.rect(currentX, y, columnWidths[i], 16, 'F'); // Shorter header doc.text(header, currentX + 2, y + 11); // Adjust padding currentX += columnWidths[i]; }); y += 16; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); tableData.forEach((rowArray) => { if (y > doc.internal.pageSize.getHeight() - 25) { doc.addPage(); y = pageMargin; currentX = pageMargin; doc.setFillColor(headerFillColor[0], headerFillColor[1], headerFillColor[2]); doc.setTextColor(headerTextColor[0], headerTextColor[1], headerTextColor[2]); doc.setFont(undefined, 'bold'); headers.forEach((header, i) => { doc.rect(currentX, y, columnWidths[i], 16, 'F'); doc.text(header, currentX + 2, y + 11); currentX += columnWidths[i]; }); y += 16; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); } currentX = pageMargin; rowArray.forEach((cell, i) => { doc.rect(currentX, y, columnWidths[i], 14); // Shorter row const cellText = String(cell); if (cellText.toLowerCase().includes("depleted")) { doc.setTextColor(211, 47, 47); // Error color } else { doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); } const textLines = doc.splitTextToSize(cellText, columnWidths[i] - 4); doc.text(textLines, currentX + 2, y + 10); currentX += columnWidths[i]; }); doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); y += 14; }); y += 7; } addMainTitle("Tax-Efficient Spending Strategy Report"); addInfo(`Report Generated: ${new Date().toLocaleString()}`); y += 5; addSectionTitle("Input Parameters"); addLine("Retirement Age:", `${data.inputs.retirementAge} years`); addLine("Planning Horizon:", `${data.inputs.planningHorizon} years`); addLine("Initial Annual Spending:", formatCurrency(data.inputs.initialAnnualSpending)); addLine("Initial Taxable Account Balance:", formatCurrency(data.inputs.initialTaxableBalance)); addLine("Taxable Account Cost Basis:", formatCurrency(data.inputs.initialTaxableCostBasis)); addLine("Initial Tax-Deferred Account Balance:", formatCurrency(data.inputs.initialTaxDeferredBalance)); addLine("Initial Tax-Free Account Balance:", formatCurrency(data.inputs.initialTaxFreeBalance)); y+=5; addLine("Avg. Annual Investment Return:", `${data.inputs.investmentReturnRatePct.toFixed(1)}%`); addLine("Avg. Annual Inflation Rate:", `${data.inputs.inflationRatePct.toFixed(1)}%`); addLine("Ordinary Income Tax Rate (Retirement):", `${data.inputs.ordinaryIncomeTaxRatePct.toFixed(0)}%`); addLine("Long-Term Capital Gains Tax Rate:", `${data.inputs.capitalGainsTaxRatePct.toFixed(0)}%`); addLine("Withdrawal Order Strategy:", data.inputs.withdrawalOrder.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())); y += 10; addSectionTitle("Projection Summary"); addLine("Total Projected Taxes Paid:", formatCurrency(data.summary.totalTaxesPaid), [239,68,68],[239,68,68]); // Red for taxes if (data.summary.fundsDepletedYear !== null && data.summary.fundsDepletedYear <= data.inputs.planningHorizon) { const ageAtDepletion = data.inputs.retirementAge + data.summary.fundsDepletedYear -1; addLine("Funds Longevity:", `Depleted in Year ${data.summary.fundsDepletedYear} of retirement (Age ${ageAtDepletion})`,[239,68,68],[239,68,68]); } else { addLine("Funds Longevity:", `Lasted for ${data.inputs.planningHorizon} years`,[66,187,106],[66,187,106]); // Green addLine("Final Total Portfolio Value:", formatCurrency(data.summary.finalPortfolioValue)); } y += 10; if (data.projectionTable.length > 0) { addSectionTitle("Year-by-Year Spending & Tax Projection"); const tableH = ["Yr", "Age", "Spending", "Taxable W/D", "Tax-Def W/D", "Tax-Free W/D", "Taxes Paid", "Taxable End", "Tax-Def End", "Tax-Free End", "Total End"]; const tableCW = [25, 25, 55, 55, 55, 55, 55, 60, 60, 60, 65]; // Adjusted for landscape const tableFData = data.projectionTable.map(r => [ r.year, r.age, formatCurrency(r.spendingNeed,0,0), formatCurrency(r.taxableWithdrawal,0,0), formatCurrency(r.taxDeferredWithdrawal,0,0), formatCurrency(r.taxFreeWithdrawal,0,0), formatCurrency(r.taxesPaidThisYear,0,0), formatCurrency(r.taxableEnd,0,0), formatCurrency(r.taxDeferredEnd,0,0), formatCurrency(r.taxFreeEnd,0,0), formatCurrency(r.totalPortfolioEnd,0,0) + (r.notes ? ` (${r.notes.substring(0,8)})` : '') ]); addTable(tableH, tableFData, tableCW); } addInfo("This projection is illustrative and uses average rates. Actual market conditions, tax laws, and personal circumstances will vary. This tool does not account for Required Minimum Distributions (RMDs) or complex tax scenarios (e.g., state taxes, detailed tax brackets). Consult with qualified financial and tax professionals for personalized advice."); const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(7); doc.setTextColor(150); doc.text(`Page ${i} of ${pageCount} - Tax-Efficient Spending Optimizer`, pageMargin, doc.internal.pageSize.getHeight() - 15); } doc.save('Tax_Efficient_Spending_Strategy.pdf'); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', () => loadJsPdfIfNeeded(downloadReportAsPdf)); } // --- Initialization --- showTab(0); if (downloadPdfBtn) downloadPdfBtn.disabled = true; if (pdfButtonContainer) pdfButtonContainer.style.display = 'block'; loadJsPdfIfNeeded(); });