Real Estate vs. Stock Market ROI Calculator

Real Estate vs. Stock Market ROI Calculator

Define Your Investment Scenarios


Real Estate Investment Details

Closing costs, initial repairs, etc.
Enter 0 if not a rental property.
Only if it's a rental property.
Only if it's a rental property and professionally managed.
Realtor commissions, closing costs on sale.

Stock Market Investment Details

Consider matching RE Down Payment + Upfront Costs for direct comparison.
For ETFs, mutual funds, etc.

ROI Analysis & Comparison

Click "Calculate ROI Comparison" to view the analysis. Ensure all inputs are set.

Annualized ROI: ${data.stockMarket.annualizedROI_pct.toFixed(2)}%

`; html += `
`; html += `
`; // Real Estate Projection Table html += `

Real Estate Year-by-Year Projection:

`; if(data.realEstate.projection.length > 0) { html += `
`; html += ``; data.realEstate.projection.forEach(row => { html += ``; }); html += `
YearProperty ValueLoan BalanceEquityNOICash FlowCum. Cash Flow
${row.year} ${formatCurrency(row.propertyValue)}${formatCurrency(row.loanBalance)} ${formatCurrency(row.equity)}${formatCurrency(row.noi)} ${formatCurrency(row.cashFlow)}${formatCurrency(row.cumulativeCashFlow)}
`; } else { html += `

No real estate projection data to display.

`; } // Stock Market Projection Table html += `

Stock Market Year-by-Year Projection:

`; if(data.stockMarket.projection.length > 0) { html += `
`; html += ``; data.stockMarket.projection.forEach(row => { html += ``; }); html += `
YearStart BalanceContributionGross ReturnFees PaidEnd Balance
${row.year} ${formatCurrency(row.startBalance)}${formatCurrency(row.contribution,0,0)} ${formatCurrency(row.grossReturn)}${formatCurrency(row.feesPaid)} ${formatCurrency(row.endBalance)}
`; } else { html += `

No stock market projection data to display.

`; } html += `

This analysis is illustrative and uses average assumptions. It does not account for all potential costs (e.g., taxes on gains/income) or market volatility. Consult with financial advisors for personalized advice.

`; analysisResultsDiv.innerHTML = html; } if (calculateBtn) { calculateBtn.addEventListener('click', runAnalysis); } // --- 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 (!analysisDataForPdf) { alert("No analysis data to download. Please run the analysis first."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ unit: 'pt', format: 'a4' }); const data = analysisDataForPdf; const pageMargin = 35; const pageWidth = doc.internal.pageSize.getWidth() - 2 * pageMargin; let y = pageMargin; function addMainTitle(text) { doc.setFontSize(16); doc.setFont(undefined, 'bold'); doc.setTextColor(44, 62, 80); doc.text(text, doc.internal.pageSize.getWidth() / 2, y, { align: 'center' }); y += 30; } function addSectionTitle(text, color = [52, 152, 219]) { if (y > doc.internal.pageSize.getHeight() - 70) { doc.addPage(); y = pageMargin; } doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(color[0], color[1], color[2]); doc.text(text, pageMargin, y); y += 20; } function addLine(key, value, keyColor = [52,73,94], valueColor = [52,73,94]) { if (y > doc.internal.pageSize.getHeight() - 35) { 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() - 45) { doc.addPage(); y = pageMargin; } doc.setFontSize(8); doc.setFont(undefined, 'italic'); doc.setTextColor(108, 117, 125); const splitText = doc.splitTextToSize(text, pageWidth); doc.setFillColor(236,240,241); 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) + 12; } function addTable(headers, tableData, columnWidths) { if (y > doc.internal.pageSize.getHeight() - 50) { doc.addPage(); y = pageMargin; } doc.setFontSize(7); const headerFillColor = [52, 152, 219]; const headerTextColor = [255,255,255]; const rowTextColor = [52,73,94]; 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], 18, 'F'); doc.text(header, currentX + 3, y + 12); currentX += columnWidths[i]; }); y += 18; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); tableData.forEach((rowArray) => { if (y > doc.internal.pageSize.getHeight() - 30) { 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], 18, 'F'); doc.text(header, currentX + 3, y + 12); currentX += columnWidths[i]; }); y += 18; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); } currentX = pageMargin; rowArray.forEach((cell, i) => { doc.rect(currentX, y, columnWidths[i], 16); const cellText = String(cell); const textLines = doc.splitTextToSize(cellText, columnWidths[i] - 6); doc.text(textLines, currentX + 3, y + 11); currentX += columnWidths[i]; }); y += 16; }); y += 8; } addMainTitle("Real Estate vs. Stock Market ROI Analysis"); addInfo(`Report Generated: ${new Date().toLocaleString()} for a ${data.inputs.holdingPeriod}-year horizon.`); y += 5; addSectionTitle("Input Summary: Common & Real Estate"); addLine("Investment Horizon:", `${data.inputs.holdingPeriod} years`); addLine("RE Purchase Price:", formatCurrency(data.inputs.rePurchasePrice)); addLine("RE Down Payment:", `${formatPercent(data.inputs.reDownPaymentPct/100,0)} (${formatCurrency(data.inputs.rePurchasePrice * data.inputs.reDownPaymentPct/100)})`); addLine("RE Other Upfront Costs:", formatCurrency(data.inputs.reOtherUpfrontCosts)); addLine("RE Loan Rate:", formatPercent(data.inputs.reLoanRate)); addLine("RE Loan Term:", `${data.inputs.reLoanTerm} years`); addLine("RE Annual Property Taxes:", formatCurrency(data.inputs.rePropertyTaxes)); addLine("RE Annual Insurance:", formatCurrency(data.inputs.rePropertyInsurance)); addLine("RE Annual Maintenance:", formatCurrency(data.inputs.reMaintenance)); addLine("RE Monthly Rental Income:", formatCurrency(data.inputs.reMonthlyRentalIncome)); addLine("RE Vacancy Rate:", formatPercent(data.inputs.reVacancyRate)); addLine("RE Mgmt Fees:", formatPercent(data.inputs.reMgmtFeesPct)); addLine("RE Annual Appreciation:", formatPercent(data.inputs.reAppreciationRate)); addLine("RE Selling Costs:", formatPercent(data.inputs.reSellingCostsPct)); y+=5; addSectionTitle("Input Summary: Stock Market"); addLine("Stock Initial Investment:", formatCurrency(data.inputs.stockInitialInvestment)); addLine("Stock Annual Contribution:", formatCurrency(data.inputs.stockAnnualContrib)); addLine("Stock Expected Annual Return:", formatPercent(data.inputs.stockAnnualReturn)); addLine("Stock Expense Ratio:", formatPercent(data.inputs.stockExpenseRatio)); y += 10; addSectionTitle("Overall ROI Summary"); addLine("RE - Total Initial Cash Outlay:", formatCurrency(data.realEstate.totalInitialCashOutlay)); addLine("RE - Total Profit:", formatCurrency(data.realEstate.totalProfit), [46,204,113]); addLine("RE - Total ROI:", formatPercent(data.realEstate.totalROI_pct/100), [46,204,113]); addLine("RE - Annualized ROI:", formatPercent(data.realEstate.annualizedROI_pct/100), [46,204,113]); y+=5; addLine("Stock - Total Cash Invested:", formatCurrency(data.stockMarket.totalInvested)); addLine("Stock - Total Profit:", formatCurrency(data.stockMarket.totalProfit), [155,89,182]); addLine("Stock - Total ROI:", formatPercent(data.stockMarket.totalROI_pct/100), [155,89,182]); addLine("Stock - Annualized ROI:", formatPercent(data.stockMarket.annualizedROI_pct/100), [155,89,182]); y += 10; if (data.realEstate.projection.length > 0) { addSectionTitle("Real Estate Year-by-Year Projection", [46,204,113]); const reH = ["Yr", "Prop.Val", "Loan Bal.", "Equity", "NOI", "Cash Flow", "Cum. CF"]; const reCW = [25, 70, 70, 70, 60, 60, 75]; const reFData = data.realEstate.projection.map(r => [ r.year, formatCurrency(r.propertyValue,0,0), formatCurrency(r.loanBalance,0,0), formatCurrency(r.equity,0,0), formatCurrency(r.noi,0,0), formatCurrency(r.cashFlow,0,0), formatCurrency(r.cumulativeCashFlow,0,0) ]); addTable(reH, reFData, reCW); } if (data.stockMarket.projection.length > 0) { addSectionTitle("Stock Market Year-by-Year Projection", [155,89,182]); const stH = ["Yr", "Start Bal.", "Contrib.", "Gross Ret.", "Fees", "End Bal."]; const stCW = [30, 90, 70, 80, 60, 90]; const stFData = data.stockMarket.projection.map(r => [ r.year, formatCurrency(r.startBalance,0,0), formatCurrency(r.contribution,0,0), formatCurrency(r.grossReturn,0,0), formatCurrency(r.feesPaid,0,0), formatCurrency(r.endBalance,0,0) ]); addTable(stH, stFData, stCW); } addInfo("This analysis is illustrative and uses average assumptions. It does not account for all potential costs (e.g., taxes on gains/income) or market volatility. Consult with financial advisors 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} - RE vs. Stock ROI Calculator`, pageMargin, doc.internal.pageSize.getHeight() - 15); } doc.save('RE_vs_Stock_ROI_Analysis.pdf'); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', () => loadJsPdfIfNeeded(downloadReportAsPdf)); } // --- Initialization --- showTab(0); if (downloadPdfBtn) downloadPdfBtn.disabled = true; if (pdfButtonContainer) pdfButtonContainer.style.display = 'block'; loadJsPdfIfNeeded(); });
Scroll to Top