Rental Yield & ROI Calculator

Rental Yield & ROI Calculator

Property & Purchase Details

Income & Operating Expenses

Annual Operating Expenses

Enter 0 if self-managed.

Financing & Sale Assumptions (Optional)

Financing Details

Enter 100% for an all-cash purchase.

Sale Assumptions (for Total ROI calculation)

Rental Yield & ROI Analysis

Your analysis will appear here.

Total Profit: ${formatCurrency(sale.totalProfit)}

`; html += `

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

`; html += `

Annualized Total ROI: ${sale.annualizedTotalROI_pct.toFixed(2)}%

`; html += `
`; } if(data.projectionTable.length > 0) { html += `

Year-by-Year Projection (Illustrative):

`; html += `
`; html += ``; data.projectionTable.forEach(row => { html += ``; }); html += `
YearProperty ValueLoan BalanceEquityCash FlowCum. Cash Flow
${row.year} ${formatCurrency(row.propertyValue)}${formatCurrency(row.loanBalance)} ${formatCurrency(row.equity)}${formatCurrency(row.cashFlow)} ${formatCurrency(row.cumulativeCashFlow)}
`; } html += `

This analysis is illustrative and uses average assumptions. It does not account for taxes (income or capital gains), inflation on expenses/rent beyond initial inputs, or unexpected major repairs. 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 inputs = data.inputs; const calc = data.calculations; const sale = data.totalROIAnalysis; 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, valueColor = [52,73,94]) { if (y > doc.internal.pageSize.getHeight() - 35) { doc.addPage(); y = pageMargin; } doc.setFontSize(9); doc.setFont(undefined, 'bold'); doc.setTextColor(52,73,94); 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("Rental Yield & ROI Analysis Report"); addInfo(`Report Generated: ${new Date().toLocaleString()}`); y += 5; addSectionTitle("Input Summary: Property & Purchase"); addLine("Purchase Price:", formatCurrency(inputs.purchasePrice)); addLine("Closing Costs:", formatCurrency(inputs.closingCosts)); addLine("Initial Renovations:", formatCurrency(inputs.initialRenovations)); y += 5; addSectionTitle("Input Summary: Income & Operating Expenses"); addLine("Monthly Gross Rental Income:", formatCurrency(inputs.monthlyRent)); addLine("Annual Vacancy Rate:", formatPercent(inputs.vacancyRate)); addLine("Annual Property Taxes:", formatCurrency(inputs.propertyTaxesAnnual)); addLine("Annual Property Insurance:", formatCurrency(inputs.propertyInsuranceAnnual)); addLine("Annual HOA Fees:", formatCurrency(inputs.hoaFeesAnnual)); addLine("Annual Maintenance:", formatCurrency(inputs.maintenanceAnnual)); addLine("Property Management Fees:", formatPercent(inputs.propMgmtFeesPct)); addLine("Other Annual Operating Expenses:", formatCurrency(inputs.otherOpExAnnual)); y += 5; addSectionTitle("Input Summary: Financing & Sale (If Applicable)"); addLine("Down Payment:", `${formatPercent(inputs.downPaymentPct)} (${formatCurrency(inputs.purchasePrice * inputs.downPaymentPct)})`); addLine("Loan Interest Rate (Annual):", formatPercent(inputs.loanInterestRateAnnual)); addLine("Loan Term:", `${inputs.loanTermYears} years`); if (inputs.holdingPeriod > 0) { addLine("Holding Period:", `${inputs.holdingPeriod} years`); addLine("Expected Annual Property Appreciation:", formatPercent(inputs.annualAppreciation)); addLine("Selling Costs (% of Sale Price):", formatPercent(inputs.sellingCostsPct)); } y += 10; addSectionTitle("Key Performance Indicators (First Year Operation)"); addLine("Total Initial Cash Outlay:", formatCurrency(calc.totalInitialCashOutlay)); addLine("Gross Annual Rental Income:", formatCurrency(calc.grossAnnualRent)); addLine("Effective Gross Income (EGI):", formatCurrency(calc.effectiveGrossIncome)); addLine("Total Annual Operating Expenses:", formatCurrency(calc.totalAnnualOperatingExpenses)); addLine("Net Operating Income (NOI):", formatCurrency(calc.noi), [44, 62, 80], [38, 166, 154]); addLine("Gross Rental Yield:", formatPercent(calc.grossRentalYield_pct/100), [44, 62, 80], [38, 166, 154]); addLine("Net Rental Yield (Cap Rate):", formatPercent(calc.netRentalYieldCapRate_pct/100), [44, 62, 80], [38, 166, 154]); if (calc.loanAmount > 0) { addLine("Annual Debt Service (Mortgage):", formatCurrency(calc.annualDebtService)); } addLine("Annual Pre-Tax Cash Flow:", formatCurrency(calc.preTaxCashFlowAnnual), [44, 62, 80], [38, 166, 154]); addLine("Cash-on-Cash ROI (Pre-Tax):", formatPercent(calc.cashOnCashROI_pct/100), [44, 62, 80], [142, 68, 173]); y += 10; if (sale) { addSectionTitle(`Total Return Analysis (Over ${inputs.holdingPeriod} Years)`); addLine("Projected Sale Price:", formatCurrency(sale.projectedSalePrice)); addLine("Net Cash From Sale:", formatCurrency(sale.netCashFromSale)); addLine("Total Profit:", formatCurrency(sale.totalProfit), [44, 62, 80], [142, 68, 173]); addLine("Total ROI:", formatPercent(sale.totalROI_pct/100), [44, 62, 80], [142, 68, 173]); addLine("Annualized Total ROI:", formatPercent(sale.annualizedTotalROI_pct/100), [44, 62, 80], [142, 68, 173]); y += 10; } if (data.projectionTable.length > 0) { addSectionTitle("Year-by-Year Projection (Illustrative)"); const tableH = ["Yr", "Prop.Val", "Loan Bal.", "Equity", "Cash Flow", "Cum. CF"]; const tableCW = [30, 80, 80, 80, 70, 80]; const tableFData = data.projectionTable.map(r => [ r.year, formatCurrency(r.propertyValue,0,0), formatCurrency(r.loanBalance,0,0), formatCurrency(r.equity,0,0), formatCurrency(r.cashFlow,0,0), formatCurrency(r.cumulativeCashFlow,0,0) ]); addTable(tableH, tableFData, tableCW); } addInfo("This analysis is illustrative and uses average assumptions. It does not account for taxes (income or capital gains), inflation on expenses/rent beyond initial inputs, or unexpected major repairs. 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} - Rental Yield & ROI Calculator`, pageMargin, doc.internal.pageSize.getHeight() - 15); } doc.save('Rental_Yield_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