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.

Calculating ROI comparison...

'; analysisDataForPdf = null; if (downloadPdfBtn) downloadPdfBtn.disabled = true; // Get inputs const holdingPeriod = getElNumVal('reitsmroiHoldingPeriod', 10); // Real Estate Inputs const rePurchasePrice = getElNumVal('reitsmroiREPurchasePrice', 0); const reDownPaymentPct = getElNumVal('reitsmroiREDownPaymentPct', 20) / 100; const reOtherUpfrontCosts = getElNumVal('reitsmroiREOtherUpfrontCosts', 0); const reLoanRate = getElNumVal('reitsmroiRELoanRate', 0) / 100; const reLoanTerm = getElNumVal('reitsmroiRELoanTerm', 30); const rePropertyTaxes = getElNumVal('reitsmroiREPropertyTaxes', 0); const rePropertyInsurance = getElNumVal('reitsmroiREPropertyInsurance', 0); const reMaintenance = getElNumVal('reitsmroiREMaintenance', 0); const reMonthlyRentalIncome = getElNumVal('reitsmroiRERentalIncome', 0); const reVacancyRate = getElNumVal('reitsmroiREVacancyRate', 0) / 100; const reMgmtFeesPct = getElNumVal('reitsmroiREMgmtFeesPct', 0) / 100; const reAppreciationRate = getElNumVal('reitsmroiREAppreciation', 0) / 100; const reSellingCostsPct = getElNumVal('reitsmroiRESellingCostsPct', 0) / 100; // Stock Market Inputs const stockInitialInvestment = getElNumVal('reitsmroiStockInitialInvestment', 0); const stockAnnualContrib = getElNumVal('reitsmroiStockAnnualContrib', 0); const stockAnnualReturn = getElNumVal('reitsmroiStockAnnualReturn', 0) / 100; const stockExpenseRatio = getElNumVal('reitsmroiStockExpenseRatio', 0) / 100; // Validations let errors = []; if (holdingPeriod <= 0) errors.push("Investment Horizon must be positive."); if (rePurchasePrice <= 0 && stockInitialInvestment <=0) errors.push("Either Real Estate Purchase Price or Stock Initial Investment must be positive."); // Add more specific validations as needed... if (errors.length > 0) { analysisResultsDiv.innerHTML = `
Input Errors:
    ${errors.map(e => `
  • ${e}
  • `).join('')}
`; if (downloadPdfBtn) downloadPdfBtn.disabled = true; return; } // --- Real Estate Calculations --- const reDownPaymentAmount = rePurchasePrice * reDownPaymentPct; const reLoanAmount = rePurchasePrice - reDownPaymentAmount; const reTotalInitialCashOutlay = reDownPaymentAmount + reOtherUpfrontCosts; const reMonthlyMortgagePayment = calculateMortgagePayment(reLoanAmount, reLoanRate, reLoanTerm); const reAnnualMortgagePayment = reMonthlyMortgagePayment * 12; const reProjection = []; let currentPropertyValue = rePurchasePrice; let remainingLoanBalance = reLoanAmount; let cumulativeRECashFlow = 0; let totalPrincipalPaid = 0; for (let year = 1; year <= holdingPeriod; year++) { const yearStartValue = currentPropertyValue; const yearStartLoanBalance = remainingLoanBalance; const annualGrossRental = reMonthlyRentalIncome * 12; const vacancyLoss = annualGrossRental * reVacancyRate; const effectiveGrossIncome = annualGrossRental - vacancyLoss; const managementFees = effectiveGrossIncome * reMgmtFeesPct; // Based on EGI const operatingExpenses = rePropertyTaxes + rePropertyInsurance + reMaintenance + managementFees; const noi = effectiveGrossIncome - operatingExpenses; let annualInterestPaid = 0; let annualPrincipalPaid = 0; if (remainingLoanBalance > 0) { for (let month = 1; month <= 12; month++) { const interestForMonth = remainingLoanBalance * (reLoanRate / 12); const principalForMonth = reMonthlyMortgagePayment - interestForMonth; annualInterestPaid += interestForMonth; annualPrincipalPaid += principalForMonth; remainingLoanBalance -= principalForMonth; if (remainingLoanBalance < 0) remainingLoanBalance = 0; } } totalPrincipalPaid += annualPrincipalPaid; const cashFlowThisYear = noi - reAnnualMortgagePayment; cumulativeRECashFlow += cashFlowThisYear; currentPropertyValue *= (1 + reAppreciationRate); reProjection.push({ year, propertyValue: currentPropertyValue, loanBalance: remainingLoanBalance, equity: currentPropertyValue - remainingLoanBalance, noi, cashFlow: cashFlowThisYear, cumulativeCashFlow: cumulativeRECashFlow }); } const reSalePrice = currentPropertyValue; // Value at end of holding period const reSellingCostsAmount = reSalePrice * reSellingCostsPct; const reNetProceedsFromSaleBeforeLoan = reSalePrice - reSellingCostsAmount; const reFinalLoanBalance = remainingLoanBalance; // From end of loop const reNetCashFromSale = reNetProceedsFromSaleBeforeLoan - reFinalLoanBalance; const reTotalProfit = reNetCashFromSale + cumulativeRECashFlow - reTotalInitialCashOutlay; const reTotalROI = reTotalInitialCashOutlay > 0 ? (reTotalProfit / reTotalInitialCashOutlay) : 0; const reAnnualizedROI = reTotalInitialCashOutlay > 0 ? (Math.pow(1 + reTotalROI, 1 / holdingPeriod) - 1) : 0; // --- Stock Market Calculations --- const stockProjection = []; let stockPortfolioValue = stockInitialInvestment; let totalStockContributions = stockInitialInvestment; let totalStockFeesPaid = 0; for (let year = 1; year <= holdingPeriod; year++) { const yearStartBalance = stockPortfolioValue; if (year > 1) { stockPortfolioValue += stockAnnualContrib; totalStockContributions += stockAnnualContrib; } const grossReturn = stockPortfolioValue * stockAnnualReturn; stockPortfolioValue += grossReturn; const fees = stockPortfolioValue * stockExpenseRatio; stockPortfolioValue -= fees; totalStockFeesPaid += fees; stockProjection.push({ year, startBalance: yearStartBalance, contribution: (year === 1 ? stockInitialInvestment : stockAnnualContrib), grossReturn, feesPaid: fees, endBalance: stockPortfolioValue }); } const stockTotalProfit = stockPortfolioValue - totalStockContributions; const stockTotalROI = totalStockContributions > 0 ? (stockTotalProfit / totalStockContributions) : 0; const stockAnnualizedROI = totalStockContributions > 0 ? (Math.pow(1 + stockTotalROI, 1 / holdingPeriod) - 1) : 0; analysisDataForPdf = { inputs: { holdingPeriod, rePurchasePrice, reDownPaymentPct, reOtherUpfrontCosts, reLoanRate, reLoanTerm, rePropertyTaxes, rePropertyInsurance, reMaintenance, reMonthlyRentalIncome, reVacancyRate, reMgmtFeesPct, reAppreciationRate, reSellingCostsPct, stockInitialInvestment, stockAnnualContrib, stockAnnualReturn, stockExpenseRatio }, realEstate: { totalInitialCashOutlay: reTotalInitialCashOutlay, totalProfit: reTotalProfit, totalROI_pct: reTotalROI * 100, annualizedROI_pct: reAnnualizedROI * 100, finalValue: reSalePrice, netCashFromSale: reNetCashFromSale, // Corrected: Use the defined variable cumulativeCashFlow: cumulativeRECashFlow, projection: reProjection }, stockMarket: { totalInvested: totalStockContributions, totalProfit: stockTotalProfit, totalROI_pct: stockTotalROI * 100, annualizedROI_pct: stockAnnualizedROI * 100, finalValue: stockPortfolioValue, totalFeesPaid: totalStockFeesPaid, projection: stockProjection } }; displayAnalysisResults(analysisDataForPdf); if (downloadPdfBtn) downloadPdfBtn.disabled = false; if (pdfButtonContainer) pdfButtonContainer.style.display = 'block'; } function displayAnalysisResults(data) { if (!analysisResultsDiv || !data) return; let html = `

ROI Comparison Summary (Over ${data.inputs.holdingPeriod} Years):

`; html += `
`; // Real Estate Summary Item html += `

Real Estate Investment

`; html += `

Total Initial Cash Outlay: ${formatCurrency(data.realEstate.totalInitialCashOutlay)}

`; html += `

Projected Property Value at Sale: ${formatCurrency(data.realEstate.finalValue)}

`; html += `

Net Cash From Sale (after loan & costs): ${formatCurrency(data.realEstate.netCashFromSale)}

`; html += `

Cumulative Cash Flow (Rental): ${formatCurrency(data.realEstate.cumulativeCashFlow)}

`; html += `

Total Profit: ${formatCurrency(data.realEstate.totalProfit)}

`; html += `

Total ROI: ${data.realEstate.totalROI_pct.toFixed(2)}%

`; html += `

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

`; html += `
`; // Stock Market Summary Item html += `

Stock Market Investment

`; html += `

Total Cash Invested: ${formatCurrency(data.stockMarket.totalInvested)}

`; html += `

Projected Portfolio Value at End: ${formatCurrency(data.stockMarket.finalValue)}

`; html += `

Total Fees Paid (Expense Ratio): ${formatCurrency(data.stockMarket.totalFeesPaid)}

`; html += `

Total Profit: ${formatCurrency(data.stockMarket.totalProfit)}

`; html += `

Total ROI: ${data.stockMarket.totalROI_pct.toFixed(2)}%

`; html += `

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