
Real Estate Investment
- 31 installs
- 9 repo stars
- Updated August 4, 2026
- aznatkoiny/zai-skills
real-estate-investment is a Claude skill for end-to-end real estate investment analysis, from deal screening through financial modeling to investor-ready output.
About
real-estate-investment is a skill for end-to-end real estate investment analysis, from deal screening through financial modeling to investor-ready output. A developer or investor uses it to run the numbers on a rental, build a pro forma, or calculate metrics like cap rate, cash-on-cash return, IRR, NOI, and DSCR. It routes by property type and analysis method and can output spreadsheets, decision frameworks, Python code, or full investor reports.
- Runs end-to-end real estate deal analysis: pro forma, cap rate, cash-on-cash, IRR, NOI, DSCR, equity multiple, GRM
- Routes by property type (SFR, BRRRR, house hack, multifamily, commercial, STR, land) and analysis method
- Generates spreadsheet-ready tables, decision frameworks, Python code, or investor reports
Real Estate Investment by the numbers
- 31 all-time installs (skills.sh)
- Ranked #660 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
real-estate-investment capabilities & compatibility
The skill is free; automating market data pulls needs third-party APIs (Zillow, Redfin, AirDNA, ATTOM, Rentcast, Census) named in the docs.
- Capabilities
- deal analysis · financial modeling · market analysis
- Use cases
- data analysis
- Pricing
- Free
What real-estate-investment says it does
End-to-end real estate investment analysis skill.
Comprehensive real estate investment analysis — from deal screening through financial modeling to investor-ready output.
npx skills add https://github.com/aznatkoiny/zai-skills --skill real-estate-investmentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 31 |
|---|---|
| repo stars | ★ 9 |
| Last updated | August 4, 2026 |
| Repository | aznatkoiny/zai-skills ↗ |
What it does
Analyze a real estate deal end-to-end, from pro forma and returns to stress testing and an investor report.
Who is it for?
Running the numbers on a rental, building a pro forma, computing cap rate/CoC/IRR/DSCR, and stress-testing a real estate deal.
Skip if: Non-real-estate investing, general stock trading, or transactional/legal execution of a purchase.
When should I use this skill?
A user asks to analyze a property deal, run the numbers on a rental, build a pro forma, or calculate cap rate, cash-on-cash, IRR, NOI, or DSCR.
What you get
An investor-ready analysis with a pro forma, return metrics, stress tests, and a go/no-go recommendation.
- real estate pro forma
- return-metric calculations
- sensitivity and Monte Carlo analysis
By the numbers
- 6-step analysis workflow (scope, gather, pro forma, returns, stress test, report)
- 8 core metrics in the quick reference table (NOI, cap rate, CoC, DSCR, IRR, equity multiple, GRM, break-even occupancy)
Files
Real Estate Investment Analysis
Comprehensive real estate investment analysis — from deal screening through financial modeling to investor-ready output. Covers all property types, standard and advanced metrics, code generation, and tax-aware structuring.
Analysis Workflow
Follow this 6-step process for any deal or market analysis:
1. Define scope — Identify property type, investment strategy, and target output format 2. Gather data — Collect property financials, market data, and comps (use API reference if automating) 3. Build pro forma — Construct income statement: Gross Rent → Vacancy → EGI → OpEx → NOI → Debt Service → Cash Flow 4. Calculate returns — Apply appropriate metrics (see Quick Reference below) 5. Stress test — Run sensitivity analysis, scenarios, or Monte Carlo simulation 6. Report — Generate investor-ready output with recommendations
Quick Reference — Core Metrics
| Metric | Formula | Typical Range |
|---|---|---|
| NOI | Effective Gross Income - Operating Expenses | Varies by asset |
| Cap Rate | NOI / Property Value | 4-10% (market-dependent) |
| Cash-on-Cash | Annual Pre-Tax Cash Flow / Total Cash Invested | 8-12% target |
| DSCR | NOI / Annual Debt Service | 1.2x+ (lender minimum) |
| IRR | Discount rate zeroing NPV of all cash flows | 15-20% target |
| Equity Multiple | Total Distributions / Total Capital Invested | 2.0x+ over hold |
| GRM | Property Price / Annual Gross Rent | 8-15 (lower = better) |
| Break-even Occ. | (OpEx + Debt Service) / Potential Gross Income | <85% preferred |
For complete formulas, Python code, and Excel equivalents → load references/financial-metrics.md
Property Type Router
Select the analysis framework based on property type:
| Property Type | Key Metrics | Rules of Thumb | Reference |
|---|---|---|---|
| SFR / Small Multi (1-4) | CoC, Cap Rate, DSCR | 1% rule, 50% rule, 70% rule | references/property-types.md §Residential |
| BRRRR | ARV, Rehab ROI, Refi LTV | 70% rule: Max buy = 70% ARV - repairs | references/property-types.md §Residential |
| House Hack | Effective housing cost, FHA terms | 3.5% down FHA, self-sufficiency test | references/property-types.md §Residential |
| Large Multifamily (5+) | Per-unit metrics, NOI, Cap Rate | OpEx ratio 35-45% | references/property-types.md §Commercial |
| Commercial (Office/Retail) | Per-SF metrics, lease analysis | NNN vs Gross lease impact | references/property-types.md §Commercial |
| Short-Term Rental | RevPAR, ADR, Occupancy | Revenue = ADR x Occ x 365 - fees | references/property-types.md §STR |
| Land / Development | Absorption rate, dev pro forma | Total cost vs projected value | references/property-types.md §Land |
Analysis Type Router
Select the analysis methodology based on what the user needs:
| Need | Method | Reference File |
|---|---|---|
| Run the numbers on a deal | Pro forma + core metrics | references/financial-metrics.md |
| Stress test assumptions | Sensitivity analysis (bear/base/bull) | references/advanced-analysis.md §Sensitivity |
| Model uncertainty/risk | Monte Carlo simulation | references/advanced-analysis.md §MonteCarlo |
| Syndication distributions | Waterfall modeling (GP/LP splits) | references/advanced-analysis.md §Waterfall |
| Compare/score markets | Market scoring framework | references/market-analysis.md §Scoring |
| Pull market data via API | API integration patterns | references/market-analysis.md §APIs |
| Find and adjust comps | Comparable analysis | references/market-analysis.md §Comps |
| Optimize tax impact | Depreciation, cost seg, 1031 | references/tax-strategy.md |
| Choose entity structure | LLC, LP, S-Corp comparison | references/tax-strategy.md §Entity |
Output Format Selection
Adapt output to the user's request:
Spreadsheet-ready — Generate formatted tables with formulas. Use pandas DataFrames exported to CSV/Excel. Include Excel formula equivalents for each calculation.
Decision framework — Provide structured narrative analysis with go/no-go recommendation. Include risk factors, key assumptions, and sensitivity ranges.
Code generation — Produce Python scripts using numpy-financial and pandas. Include complete, runnable pro forma models, Monte Carlo simulators, or waterfall calculators.
Investor report — Combine all three: executive summary, financial tables, risk analysis, and appendix with methodology.
Operating Expense Benchmarks by Property Type
| Property Type | OpEx Ratio (% of EGI) | Management Fee |
|---|---|---|
| Single-Family Rental | 35-50% | 8-10% |
| Small Multifamily (2-4) | 35-45% | 8-10% |
| Large Multifamily (5+) | 35-45% | 5-8% |
| Office | 35-55% | 3-5% |
| Retail (NNN) | 15-25% | 3-5% |
| Retail (Gross) | 60-80% | 3-5% |
| Industrial | 15-25% | 3-5% |
| Short-Term Rental | 50-65% | 20-25% |
Key Tax Thresholds (2025-2026)
| Strategy | Key Detail |
|---|---|
| Depreciation | Residential: 27.5yr, Commercial: 39yr (straight-line) |
| Bonus Depreciation | 100% for property placed in service Jan 20, 2025 – Dec 31, 2030 |
| Cost Segregation | Reclassify 15-40% of building into 5/7/15-yr assets |
| Section 179 | $2.5M max deduction (2025), phase-out at $4M |
| 1031 Exchange | 45-day ID period, 180-day closing, like-kind real property only |
| Opportunity Zones | Made permanent (2025), 10-year gain exclusion on QOF investment |
For complete tax analysis with IRS code references → load references/tax-strategy.md
API Quick Reference
| Provider | Best For | Pricing | Auth |
|---|---|---|---|
| Mashvisor | STR + LTR rental data | $30-$120/mo | x-api-key header |
| AirDNA | STR performance data | $12-$599/mo | Bearer token |
| ATTOM | Deep property data (155M+ properties) | $850-$2K/mo | apikey param |
| Rentcast | Rental estimates | Free-$449/mo (50 free/mo) | X-Api-Key header |
| Census Bureau | Demographics, housing | Free (API key required) | key param |
| Redfin Data Center | Market trends | Free (CSV download) | None |
For endpoint URLs, Python examples, and integration patterns → load references/market-analysis.md
Waterfall Distribution Quick Reference
Standard syndication tiers:
| Tier | IRR Hurdle | LP Share | GP Share |
|---|---|---|---|
| 1 (Return of Capital + Pref) | 0-8% | 100% | 0% |
| 2 (First Promote) | 8-12% | 90% | 10% |
| 3 (Second Promote) | 12-18% | 80% | 20% |
| 4 (Final Split) | 18%+ | 60% | 40% |
Market data: 8% pref in 40% of deals, 10% pref in 30% of deals. 85% of waterfalls use IRR hurdles.
For complete waterfall mechanics, catch-up provisions, and Python calculator → load references/advanced-analysis.md
Reference File Index
Load the appropriate reference file based on the analysis need:
| File | Contents | When to Load |
|---|---|---|
references/financial-metrics.md | 12 metrics with formulas, Python functions, Excel formulas, complete RealEstateProForma class, amortization schedules | Building a pro forma, calculating returns, generating Python/Excel models |
references/advanced-analysis.md | Sensitivity tables, Monte Carlo simulation (Python), waterfall calculator, syndication LP/GP mechanics | Stress testing deals, modeling risk, syndication analysis |
references/property-types.md | BRRRR framework, house hack analysis, commercial underwriting, STR revenue modeling, land development feasibility | Analyzing a specific property type with tailored frameworks |
references/market-analysis.md | Market scoring with 15+ indicators, 6 API integrations with Python code, comp adjustment methodology, submarket signals | Comparing markets, pulling data via APIs, running comps |
references/tax-strategy.md | Depreciation schedules, cost segregation savings, 1031 exchange rules, bonus depreciation (2025-2030), opportunity zones, entity structure comparison | Tax-optimizing a deal, choosing entity structure, planning exchanges |
Audience Adaptation
- Beginner investors: Explain metric meanings, recommend starting with the 1% rule and cash-on-cash return, walk through pro forma line by line
- Experienced investors: Skip basics, lead with IRR and equity multiple, provide code/spreadsheet output, focus on sensitivity analysis and tax optimization
- Default to expert-level analysis unless context suggests otherwise
Advanced Analysis — Sensitivity, Monte Carlo & Waterfalls
Table of Contents
- 1. Sensitivity Analysis
- 2. Monte Carlo Simulation
- 3. Waterfall Distribution Modeling
- 4. Syndication Return Calculations
---
1. Sensitivity Analysis
Key Variables to Stress Test
Interest Rates & Financing:
- Rate fluctuations affect financing costs and valuations
- Test different debt/equity combinations
Rental Income & Occupancy:
- Vacancy rates and occupancy thresholds
- Rent growth rates and lease rollover timing
Property Values & Market Conditions:
- Exit cap rate (typically most impactful variable)
- Market value fluctuations and economic cycles
Operating Expenses:
- Management fees, utilities, maintenance
- Insurance and property taxes
Capital Expenditures:
- Renovation costs, major systems replacement
- Tenant improvement allowances
Three-Scenario Framework
| Variable | Bear Case | Base Case | Bull Case |
|---|---|---|---|
| Rent Growth | 1-2% | 3% | 4-5% |
| Vacancy | 15-20% | 8-10% | 5% |
| Exit Cap Rate | +50-100 bps | Market | -50 bps |
| Interest Rate | +100 bps | Current | -50 bps |
| OpEx Growth | 4-5% | 3% | 2% |
Tornado Diagram Methodology
1. Identify 6-10 key input variables 2. Determine low, base, and high values for each 3. Run model varying one variable at a time (hold others constant) 4. Calculate range of output values (IRR or NPV) 5. Sort by total range (high - low) in descending order 6. Plot as horizontal bars showing swing from low to high
Interpretation:
- Longer bar = higher sensitivity
- Top variables require most careful validation
- Prioritize due diligence on high-impact assumptions
Break-Even Calculations
Break-Even Occupancy:
Break-Even Occupancy = (Operating Expenses + Debt Service) / Potential Gross IncomeExample:
Operating Expenses: $800,000
Annual Debt Service: $1,000,000
Potential Gross Income: $2,500,000
Break-Even = ($800,000 + $1,000,000) / $2,500,000 = 72%Industry Standards:
- Most commercial properties: 60-80%
- Lender requirement: ≤85%
- Lower break-even = larger financial cushion
Python: Basic Sensitivity Analysis
import pandas as pd
import numpy as np
def calculate_irr(rent_growth, vacancy_rate, exit_cap):
"""Simplified IRR calculation (replace with full DCF)"""
base_irr = 0.12
irr = base_irr + (rent_growth - 0.03) * 0.5 + (0.10 - vacancy_rate) * 0.3 - (exit_cap - 0.05) * 2
return irr
# Define variable ranges
variables = {
'rent_growth': np.arange(0.01, 0.06, 0.01),
'vacancy_rate': np.arange(0.05, 0.21, 0.03),
'exit_cap': np.arange(0.04, 0.08, 0.01)
}
# Base case values
base_case = {
'rent_growth': 0.03,
'vacancy_rate': 0.10,
'exit_cap': 0.05
}
# One-way sensitivity analysis
results = []
for var_name, var_range in variables.items():
for value in var_range:
params = base_case.copy()
params[var_name] = value
irr = calculate_irr(**params)
results.append({
'variable': var_name,
'value': value,
'irr': irr
})
df_sensitivity = pd.DataFrame(results)
# Calculate ranges for tornado diagram
tornado_data = df_sensitivity.groupby('variable')['irr'].agg(['min', 'max'])
tornado_data['range'] = tornado_data['max'] - tornado_data['min']
tornado_data = tornado_data.sort_values('range', ascending=False)
print(tornado_data)Python: Two-Way Sensitivity Table
import pandas as pd
import numpy as np
def calculate_npv(exit_cap, rent_growth):
base_npv = 5000000
npv = base_npv - (exit_cap - 0.05) * 10000000 + (rent_growth - 0.03) * 8000000
return npv
# Create two-way table
exit_caps = np.arange(0.04, 0.08, 0.005)
rent_growths = np.arange(0.01, 0.06, 0.005)
sensitivity_table = pd.DataFrame(
[[calculate_npv(cap, growth) for growth in rent_growths] for cap in exit_caps],
index=exit_caps,
columns=rent_growths
)
# Format as millions
print("NPV Sensitivity Table ($ Millions)")
print((sensitivity_table / 1000000).round(2))Python: Scenario Analysis
import pandas as pd
import numpy as np
def dcf_model(purchase_price, noi_year1, rent_growth, exit_cap, hold_period=5):
cash_flows = [-purchase_price]
# Project NOI
for year in range(1, hold_period + 1):
noi = noi_year1 * ((1 + rent_growth) ** year)
cash_flows.append(noi)
# Add terminal value
terminal_noi = cash_flows[-1] * (1 + rent_growth)
terminal_value = terminal_noi / exit_cap
cash_flows[-1] += terminal_value
# Calculate metrics
irr = np.irr(cash_flows)
npv = sum([cf / ((1 + 0.10) ** i) for i, cf in enumerate(cash_flows)])
return {'IRR': irr, 'NPV': npv, 'Terminal Value': terminal_value}
# Define scenarios
scenarios = {
'Bear Case': {'purchase_price': 50000000, 'noi_year1': 3000000, 'rent_growth': 0.02, 'exit_cap': 0.065},
'Base Case': {'purchase_price': 50000000, 'noi_year1': 3500000, 'rent_growth': 0.03, 'exit_cap': 0.055},
'Bull Case': {'purchase_price': 50000000, 'noi_year1': 4000000, 'rent_growth': 0.045, 'exit_cap': 0.048}
}
# Run scenarios
results = {name: dcf_model(**params) for name, params in scenarios.items()}
df_scenarios = pd.DataFrame(results).T
print(df_scenarios)Python: Break-Even Analysis
def calculate_breakeven_occupancy(operating_expenses, debt_service, potential_gross_income):
return (operating_expenses + debt_service) / potential_gross_income
# Example
op_ex = 800000
debt_service = 1000000
pgi = 2500000
beo = calculate_breakeven_occupancy(op_ex, debt_service, pgi)
print(f"Break-Even Occupancy: {beo:.1%}")
# Sensitivity on break-even
debt_scenarios = np.arange(800000, 1300000, 100000)
for ds in debt_scenarios:
beo = calculate_breakeven_occupancy(op_ex, ds, pgi)
print(f"Debt Service ${ds:,.0f}: Break-Even = {beo:.1%}")---
2. Monte Carlo Simulation
Why Monte Carlo for Real Estate
Advantages:
- Incorporates uncertainty through probability distributions
- Calculates likelihood of meeting return thresholds
- Quantifies downside risk (VaR, percentiles)
- Research shows NPV estimates can differ $500K+ from static models
Limitations:
- Requires sophisticated modeling skills
- Needs quality historical data for distributions
- "Garbage in, garbage out"
Variables to Randomize
Based on Cornell research, model these eight variables stochastically:
1. Rent growth rate (most critical) 2. Other income growth rate 3. Operating expense growth rate 4. Capital expenditures growth rate 5. Releasing costs growth rate 6. Terminal cap rate (highest impact on IRR) 7. Days vacant between leases 8. Renewal probability
Distribution Selection Guide
Normal Distribution:
- Use for: Variables with symmetric uncertainty
- Parameters: Mean (μ), standard deviation (σ)
- Applications: Rent growth (μ=3%, σ=2%), property appreciation, OpEx growth
- Caution: Can generate impossible values (e.g., negative rents)
Triangular Distribution:
- Use for: Limited data but expert judgment available
- Parameters: Minimum, most likely (mode), maximum
- Applications: Cap rates (min=4%, mode=5.5%, max=7%), construction costs, lease-up periods
- Benefits: Intuitive, prevents extreme outliers, easy to explain
Log-Normal Distribution:
- Use for: Variables that cannot be negative with right-skewed distributions
- Parameters: Mean and std dev of natural log
- Applications: Property values, extreme market movements
- Benefits: Realistic for financial variables, prevents negative values
Uniform Distribution:
- Use for: True randomness within a range
- Applications: Rarely used in real estate; perhaps timing variables
Determining Distribution Parameters
import numpy as np
# Historical rent growth data
historical_rent_growth = np.array([0.025, 0.032, 0.028, 0.041, 0.019,
0.035, 0.027, 0.038, 0.022, 0.031])
# Normal distribution parameters
mean_growth = np.mean(historical_rent_growth)
std_growth = np.std(historical_rent_growth, ddof=1) # Sample std dev
print(f"Normal: Mean={mean_growth:.2%}, Std={std_growth:.2%}")
# Triangular distribution parameters
min_growth = np.min(historical_rent_growth)
max_growth = np.max(historical_rent_growth)
mode_growth = historical_rent_growth[np.argmax(np.histogram(historical_rent_growth, bins=5)[0])]
print(f"Triangular: Min={min_growth:.2%}, Mode={mode_growth:.2%}, Max={max_growth:.2%}")Common Benchmarks:
- Rent Growth: Mean=3%, Std=2%
- Property Appreciation: Mean=3%, varies by market
- Vacancy Rates: Property-specific, 5-15% range
- Cap Rates: Vary by type; multifamily typically 4-7%
Python: Monte Carlo Framework
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
n_simulations = 10000
# Model parameters
purchase_price = 50000000
hold_period = 5
# Distribution parameters
rent_growth_mean = 0.03
rent_growth_std = 0.02
exit_cap_min = 0.045
exit_cap_mode = 0.055
exit_cap_max = 0.070
vacancy_mean = 0.08
vacancy_std = 0.03
irr_results = []
npv_results = []
for i in range(n_simulations):
# Sample variables
rent_growth = np.random.normal(rent_growth_mean, rent_growth_std)
exit_cap = np.random.triangular(exit_cap_min, exit_cap_mode, exit_cap_max)
vacancy = np.random.normal(vacancy_mean, vacancy_std)
# Constrain to realistic bounds
rent_growth = np.clip(rent_growth, -0.05, 0.15)
vacancy = np.clip(vacancy, 0.02, 0.30)
# Cash flow projection
year_1_noi = 3500000 * (1 - vacancy)
cash_flows = [-purchase_price]
for year in range(1, hold_period + 1):
noi = year_1_noi * ((1 + rent_growth) ** year)
cash_flows.append(noi)
# Terminal value
terminal_noi = cash_flows[-1] * (1 + rent_growth)
terminal_value = terminal_noi / exit_cap
cash_flows[-1] += terminal_value
# Calculate metrics
irr = np.irr(cash_flows)
npv = np.npv(0.10, cash_flows)
irr_results.append(irr)
npv_results.append(npv)
irr_results = np.array(irr_results)
npv_results = np.array(npv_results)
# Summary statistics
print("=== MONTE CARLO SIMULATION RESULTS ===")
print(f"Simulations: {n_simulations:,}")
print(f"\nIRR Statistics:")
print(f" Mean: {np.mean(irr_results):.2%}")
print(f" Median: {np.median(irr_results):.2%}")
print(f" Std Dev: {np.std(irr_results):.2%}")
print(f" 5th Percentile: {np.percentile(irr_results, 5):.2%}")
print(f" 95th Percentile: {np.percentile(irr_results, 95):.2%}")
print(f"\nNPV Statistics:")
print(f" Mean: ${np.mean(npv_results):,.0f}")
print(f" 5th Percentile: ${np.percentile(npv_results, 5):,.0f}")
print(f" 95th Percentile: ${np.percentile(npv_results, 95):,.0f}")
# Probability of meeting targets
target_irr = 0.12
prob_exceed = np.sum(irr_results >= target_irr) / n_simulations
print(f"\nP(IRR >= {target_irr:.0%}): {prob_exceed:.1%}")
# Visualize
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].hist(irr_results, bins=50, edgecolor='black', alpha=0.7)
axes[0].axvline(np.mean(irr_results), color='red', linestyle='--', label=f'Mean: {np.mean(irr_results):.2%}')
axes[0].axvline(target_irr, color='green', linestyle='--', label=f'Target: {target_irr:.0%}')
axes[0].set_xlabel('IRR')
axes[0].set_ylabel('Frequency')
axes[0].set_title('Distribution of IRR Outcomes')
axes[0].legend()
axes[1].hist(npv_results / 1000000, bins=50, edgecolor='black', alpha=0.7)
axes[1].axvline(np.mean(npv_results) / 1000000, color='red', linestyle='--')
axes[1].set_xlabel('NPV ($ Millions)')
axes[1].set_ylabel('Frequency')
axes[1].set_title('Distribution of NPV Outcomes')
plt.tight_layout()
plt.savefig('monte_carlo_results.png', dpi=300)Python: Correlated Variables
import numpy as np
# Define correlation matrix (e.g., rent growth vs exit cap)
correlation_matrix = np.array([
[1.00, -0.50], # Negative correlation
[-0.50, 1.00]
])
# Generate correlated samples
n_sims = 10000
mean = [0, 0]
samples = np.random.multivariate_normal(mean, correlation_matrix, n_sims)
# Transform to desired distributions
rent_growth = 0.03 + samples[:, 0] * 0.02 # Mean 3%, std 2%
exit_cap = 0.055 + samples[:, 1] * 0.01 # Mean 5.5%, std 1%
print(f"Correlation: {np.corrcoef(rent_growth, exit_cap)[0, 1]:.2f}")Output Interpretation
Key Metrics:
- Expected Value (Mean): Average outcome across simulations
- Median: 50th percentile
- Standard Deviation: Volatility/risk measure
- 5th Percentile: 5% chance of worse outcome (downside risk)
- 95th Percentile: Upside potential
Value at Risk (VaR):
# 5% VaR
var_5 = np.percentile(npv_results, 5)
print(f"5% VaR: ${var_5:,.0f}")
print(f"Interpretation: 5% chance NPV ≤ ${var_5:,.0f}")
# Probability of loss
prob_loss = np.sum(npv_results < 0) / len(npv_results)
print(f"P(NPV < 0): {prob_loss:.1%}")IRR Target Probabilities:
targets = [0.08, 0.10, 0.12, 0.15, 0.18]
print("IRR Target Probabilities:")
for target in targets:
prob = np.sum(irr_results >= target) / len(irr_results)
print(f" P(IRR >= {target:.0%}): {prob:.1%}")Iteration Count Guidelines
- Minimum: 1,000 iterations for basic analysis
- Recommended: 5,000-10,000 iterations for robust results
- Complex Models: 10,000+ when modeling many variables
Test for Convergence:
# Run multiple iteration counts and check stability
iteration_counts = [100, 500, 1000, 5000, 10000]
means = []
for n in iteration_counts:
temp_results = [run_simulation() for _ in range(n)]
means.append(np.mean(temp_results))
print("Mean IRR by Iteration Count:")
for n, m in zip(iteration_counts, means):
print(f"{n:>6,} iterations: {m:.4%}")Results converge when increasing iterations no longer changes mean/distribution.
---
3. Waterfall Distribution Modeling
Overview
Waterfall distribution structures determine how profits split between GPs (sponsors) and LPs (investors) through sequential tiers. Cash "cascades" through tiers—each must be filled before flowing to the next.
Purpose:
- Align GP and LP interests
- Incentivize sponsor performance through "promote"
- Provide downside protection via preferred returns
- Create clear distribution priorities
Typical 4-Tier Structure
Tier 1: Return of Capital + Preferred Return
- 100% to LPs
- First: Return original investment
- Then: Preferred return (typically 7-10%, most commonly 8%)
- No GP distributions until satisfied
Tier 2: GP Catch-Up
- Often 100% to GP
- Continues until GP receives proportional share of cumulative profits
- "Catches up" GP to agreed promote percentage
Tier 3: First Promote Split (8-12% IRR Hurdle)
- Example: 70% LP, 30% GP
- Applies after 8% IRR achieved
- Continues until next hurdle (e.g., 12% IRR)
Tier 4: Second Promote Split (12-18% IRR Hurdle)
- Example: 60% LP, 40% GP
- May have additional tiers at 15%, 18%+ IRR
Common Hurdle Structures:
| IRR Range | LP Split | GP Split | Description |
|---|---|---|---|
| < 8% | 100% | 0% | Return of capital + pref |
| 8-12% | 80% | 20% | First promote tier |
| 12-15% | 70% | 30% | Second promote tier |
| 15-18% | 65% | 35% | Third promote tier |
| 18%+ | 60% | 40% | Final promote tier |
American vs European Waterfalls
American Waterfall (Deal-by-Deal):
- GP receives carried interest before LPs have full capital return
- Calculated at individual deal/property level
- More GP-favorable; provides earlier promote access
- Most prevalent in United States
How it Works: 1. Property A sells at profit → GP receives promote immediately 2. Property B may still be held or at loss 3. GP receives promote deal-by-deal
Advantages: GP receives compensation sooner, enables longer hold periods Disadvantages: LPs may not receive full capital if later deals underperform
European Waterfall (Whole-Fund):
- GP receives no carried interest until all LPs have capital back + pref return
- Calculated at fund level, not deal-by-deal
- More LP-favorable; provides downside protection
- Common in European markets
How it Works: 1. No promote until entire fund returns all LP capital 2. Only after 100% capital + pref does GP participate in profits 3. Promotes based on aggregate fund performance
Advantages: Better LP protection Disadvantages: GP may wait years for promote, incentive to exit early
Comparison:
| Feature | American | European |
|---|---|---|
| Promote Timing | Deal-by-deal | After full fund return |
| GP Compensation | Earlier | Later |
| LP Protection | Lower | Higher |
| GP Incentive | Longer holds | Shorter holds |
| Risk to GP | Lower | Higher |
Catch-Up Provision
Purpose: Allow GP to receive 100% of distributions after pref return until GP reaches target promote percentage on cumulative basis.
Formula:
GP Catch-Up Amount = (Preferred Return × GP Target %) / (1 - GP Target %)Example: For $2M pref return and 20% target promote:
Catch-Up = $2,000,000 × 0.20 / (1 - 0.20) = $500,000Cash Flow Distribution Example:
- Total: $10,000,000
- Tier 1 (Pref): $2,000,000 to LPs (100%)
- Tier 2 (Catch-Up): $500,000 to GP (100%)
- Tier 3: $7,500,000 split 80/20
- Final GP: $500K + $1,500K = $2,000K = 20% of total
Clawback and Lookback Provisions
Clawback: Protection allowing LPs to recoup excess promote if fund fails to meet return thresholds at liquidation.
How it Works: 1. Year 3: Property sells at 15% IRR, GP receives 30% promote 2. Year 5: Fund liquidates at 10% overall IRR 3. At 10% IRR, GP only entitled to 20% promote 4. GP must return excess promote to LPs
Lookback: Re-evaluates GP compensation at end of investment period to ensure it matches actual achieved returns.
Example:
- Projected IRR at sale: 15% → GP receives 30%
- Actual fund IRR at liquidation: 11% → GP entitled to 20%
- Lookback requires GP to return 10% difference
Preferred Return: Cumulative vs Non-Cumulative
Cumulative (Most Common):
- Unpaid pref from prior periods accrues and carries forward
- Must be paid in future periods before other distributions
Example:
- 8% annual pref on $10M = $800K/year
- Year 1: Only $400K distributed
- Year 2: Must distribute $1,200K ($800K Year 2 + $400K shortfall)
Non-Cumulative (Rare):
- Pref "resets" each period
- Unpaid amounts do not carry forward
- Much less favorable to LPs
Simple vs Compound Interest
Simple Interest (Most Common):
- Unpaid pref accrues but does not compound
- Calculated on original capital only
Example:
- $10M, 8% simple pref
- Year 1: $0 distributed (owe $800K)
- Year 2: $0 distributed (owe $1,600K total)
- Unpaid pref does not earn additional pref
Compound Interest:
- Unpaid pref added to capital balance
- Future pref calculated on capital + unpaid pref
Example:
- $10M, 8% compound pref
- Year 1: $0 distributed (owe $800K)
- Year 2 balance: $10.8M
- Year 2 pref: $10.8M × 8% = $864K
- Total owed: $1,664K
Impact Comparison (5 years, no distributions):
| Structure | Year 5 Total Owed |
|---|---|
| Non-Cumulative | $800,000 |
| Cumulative, Simple | $4,000,000 |
| Cumulative, Compound | $4,693,000 |
Python: Waterfall Calculator with IRR Hurdles
import numpy as np
import numpy_financial as npf
def calculate_waterfall_irr(
lp_capital,
gp_capital,
cash_flows,
pref_rate=0.08,
hurdles=[0.08, 0.12, 0.18],
lp_splits=[0.80, 0.70, 0.60],
use_catchup=True
):
"""
Calculate waterfall distributions with IRR hurdles.
Returns: dict with distribution amounts for LP and GP
"""
total_capital = lp_capital + gp_capital
total_distributions = sum(cash_flows)
# Calculate project IRR
investment_cf = [-total_capital] + cash_flows
project_irr = npf.irr(investment_cf)
lp_distributions = 0
gp_distributions = 0
remaining_cash = total_distributions
# Tier 1: Return of Capital
if remaining_cash >= total_capital:
lp_distributions += lp_capital
gp_distributions += gp_capital
remaining_cash -= total_capital
print(f"Tier 1 - Return of Capital:")
print(f" LP: ${lp_capital:,.0f}, GP: ${gp_capital:,.0f}")
print(f" Remaining: ${remaining_cash:,.0f}\n")
else:
# Pro-rata return
lp_distributions = remaining_cash * (lp_capital / total_capital)
gp_distributions = remaining_cash * (gp_capital / total_capital)
remaining_cash = 0
return {'lp_total': lp_distributions, 'gp_total': gp_distributions, 'project_irr': -1}
# Tier 2: Preferred Return
pref_amount = lp_capital * pref_rate * len(cash_flows) # Simplified cumulative
if remaining_cash >= pref_amount:
lp_distributions += pref_amount
remaining_cash -= pref_amount
print(f"Tier 2 - Preferred Return ({pref_rate:.0%}):")
print(f" LP: ${pref_amount:,.0f}")
print(f" Remaining: ${remaining_cash:,.0f}\n")
else:
lp_distributions += remaining_cash
remaining_cash = 0
return {'lp_total': lp_distributions, 'gp_total': gp_distributions, 'project_irr': project_irr}
# Tier 3: GP Catch-Up
if use_catchup and remaining_cash > 0:
first_promote = 1 - lp_splits[0]
total_tier_1_2 = total_capital + pref_amount
catchup_target = total_tier_1_2 * first_promote / (1 - first_promote)
catchup_amount = min(catchup_target, remaining_cash)
gp_distributions += catchup_amount
remaining_cash -= catchup_amount
print(f"Tier 3 - GP Catch-Up:")
print(f" GP: ${catchup_amount:,.0f}")
print(f" Remaining: ${remaining_cash:,.0f}\n")
# Tier 4+: Promote Tiers
tier_num = 4
for i, hurdle in enumerate(hurdles):
if project_irr >= hurdle and remaining_cash > 0:
lp_split = lp_splits[i]
gp_split = 1 - lp_split
tier_lp = remaining_cash * lp_split
tier_gp = remaining_cash * gp_split
lp_distributions += tier_lp
gp_distributions += tier_gp
remaining_cash -= (tier_lp + tier_gp)
print(f"Tier {tier_num} - {hurdle:.0%} IRR Hurdle (LP:{lp_split:.0%}, GP:{gp_split:.0%}):")
print(f" LP: ${tier_lp:,.0f}, GP: ${tier_gp:,.0f}\n")
tier_num += 1
break
# Calculate IRRs
lp_irr = npf.irr([-lp_capital, lp_distributions])
gp_irr = npf.irr([-gp_capital, gp_distributions]) if gp_capital > 0 else 0
return {
'project_irr': project_irr,
'lp_total': lp_distributions,
'gp_total': gp_distributions,
'lp_irr': lp_irr,
'gp_irr': gp_irr,
'lp_percent': lp_distributions / total_distributions,
'gp_percent': gp_distributions / total_distributions
}
# Example usage
results = calculate_waterfall_irr(
lp_capital=10000000,
gp_capital=0,
cash_flows=[0, 0, 0, 0, 15000000],
pref_rate=0.08,
hurdles=[0.08, 0.12, 0.18],
lp_splits=[0.80, 0.70, 0.60],
use_catchup=True
)
print("=" * 50)
print("WATERFALL SUMMARY")
print("=" * 50)
print(f"Project IRR: {results['project_irr']:.2%}")
print(f"LP: ${results['lp_total']:,.0f} ({results['lp_percent']:.1%}), IRR: {results['lp_irr']:.2%}")
print(f"GP: ${results['gp_total']:,.0f} ({results['gp_percent']:.1%}), IRR: {results['gp_irr']:.2%}")Python: Simplified Waterfall
def simple_waterfall(total_proceeds, lp_capital, pref_rate=0.08, promote=0.20):
"""Simplified waterfall with pref return and single promote tier."""
distributions = {
'lp': {'return_of_capital': 0, 'pref_return': 0, 'profit_split': 0, 'total': 0},
'gp': {'catchup': 0, 'profit_split': 0, 'total': 0}
}
remaining = total_proceeds
# Tier 1: Return of capital
distributions['lp']['return_of_capital'] = min(lp_capital, remaining)
remaining -= distributions['lp']['return_of_capital']
if remaining <= 0:
distributions['lp']['total'] = distributions['lp']['return_of_capital']
return distributions
# Tier 2: Preferred return
pref_amount = lp_capital * pref_rate
distributions['lp']['pref_return'] = min(pref_amount, remaining)
remaining -= distributions['lp']['pref_return']
if remaining <= 0:
distributions['lp']['total'] = sum(distributions['lp'].values())
return distributions
# Tier 3: GP catch-up
total_tier_1_2 = distributions['lp']['return_of_capital'] + distributions['lp']['pref_return']
catchup_amount = total_tier_1_2 * promote / (1 - promote)
distributions['gp']['catchup'] = min(catchup_amount, remaining)
remaining -= distributions['gp']['catchup']
# Tier 4: Remaining split
distributions['lp']['profit_split'] = remaining * (1 - promote)
distributions['gp']['profit_split'] = remaining * promote
# Totals
distributions['lp']['total'] = sum(distributions['lp'].values())
distributions['gp']['total'] = sum(distributions['gp'].values())
return distributions
# Example
result = simple_waterfall(total_proceeds=15000000, lp_capital=10000000, pref_rate=0.08, promote=0.20)
print("LP Distributions:")
for key, value in result['lp'].items():
print(f" {key}: ${value:,.0f}")
print("\nGP Distributions:")
for key, value in result['gp'].items():
print(f" {key}: ${value:,.0f}")
print(f"\nGP Promote: {result['gp']['total'] / (result['lp']['total'] + result['gp']['total']):.1%}")---
4. Syndication Return Calculations
LP vs GP Splits
Typical Ownership:
- LPs: Provide 90-95% of equity
- GPs: Provide 5-10% of equity (sometimes 0%)
Distribution Priorities:
| Tier | Description | LP Split | GP Split |
|---|---|---|---|
| 1 | Return of Capital | Pro-rata | Pro-rata |
| 2 | Preferred Return | 100% | 0% |
| 3 | GP Catch-Up | 0% | 100% |
| 4 | 8-12% IRR | 80% | 20% |
| 5 | 12%+ IRR | 70% | 30% |
Example ($15M total distribution):
| Tier | Amount | LP | GP |
|---|---|---|---|
| Return of Capital | $10M | $10M | $0 |
| Pref Return 8% | $800K | $800K | $0 |
| GP Catch-Up | $200K | $0 | $200K |
| 8-12% IRR | $2M | $1.6M | $400K |
| 12%+ IRR | $2M | $1.4M | $600K |
| Total | $15M | $13.8M (92%) | $1.2M (8%) |
Promote/Carried Interest
What is a Promote? Performance-based compensation earned by GP beyond pro-rata ownership share.
Common Structures:
1. Straight Promote: All cash flow split per percentages (e.g., 70/30) 2. Single Hurdle: 100% to LPs until 8% pref, then 80/20 3. Multi-Tier IRR Hurdles:
- 8% IRR: 80/20
- 12% IRR: 70/30
- 15% IRR: 65/35
- 18% IRR: 60/40
Total GP Compensation:
Total GP Return = (GP Capital / Total Capital) × Distributions + PromoteExample:
- Sale Proceeds: $15M
- LP Capital: $9.5M (95%), GP Capital: $500K (5%)
- Pref Return: 8% ($760K to LPs)
- Promote: 20% after pref
Calculation: 1. Return of Capital: LP $9.5M, GP $500K 2. Pref to LPs: $760K 3. Remaining: $15M - $10M - $760K = $4,240K 4. Split 80/20: LP $3,392K, GP $848K 5. Total GP: $500K + $848K = $1,348K (9% of total) 6. Promote premium: 9% - 5% = 4%
Promote Ranges:
- Standard deals: 20%
- Value-add/opportunistic: 25-30%
- Development/high-risk: 30-40%
Capital Account Tracking
Formula:
Ending Capital Account = Beginning Balance
+ Capital Contributions
+ Allocated Income
- Allocated Losses
- DistributionsExample Tracking:
| Period | Contributions | Allocated Income | Distributions | Ending Balance |
|---|---|---|---|---|
| Year 0 | $1,000,000 | $0 | $0 | $1,000,000 |
| Year 1 | $0 | $50,000 | -$30,000 | $1,020,000 |
| Year 2 | $0 | $75,000 | -$40,000 | $1,055,000 |
| Year 3 | $0 | $100,000 | -$50,000 | $1,105,000 |
| Year 4 | $0 | -$20,000 | -$40,000 | $1,045,000 |
| Year 5 | $0 | $500,000 | -$1,545,000 | $0 |
Python Implementation:
import pandas as pd
class CapitalAccount:
def __init__(self, partner_name, initial_contribution):
self.partner_name = partner_name
self.balance = initial_contribution
self.transactions = [{
'year': 0,
'type': 'contribution',
'amount': initial_contribution,
'balance': initial_contribution
}]
def add_contribution(self, year, amount):
self.balance += amount
self.transactions.append({'year': year, 'type': 'contribution', 'amount': amount, 'balance': self.balance})
def allocate_income(self, year, amount):
self.balance += amount
self.transactions.append({'year': year, 'type': 'income', 'amount': amount, 'balance': self.balance})
def record_distribution(self, year, amount):
self.balance -= amount
self.transactions.append({'year': year, 'type': 'distribution', 'amount': amount, 'balance': self.balance})
def get_balance(self):
return self.balance
def get_statement(self):
return pd.DataFrame(self.transactions)
# Example
lp_account = CapitalAccount('LP-001', 1000000)
lp_account.allocate_income(1, 50000)
lp_account.record_distribution(1, 30000)
lp_account.allocate_income(2, 75000)
lp_account.record_distribution(2, 40000)
print(lp_account.get_statement())
print(f"\nBalance: ${lp_account.get_balance():,.0f}")Why Capital Accounts Matter: 1. Tax reporting (K-1 allocations) 2. Distribution rights in liquidation 3. Regulatory compliance (IRS Form 1065) 4. Investor transparency 5. Prevent negative balances (clawback risk)
Distribution Timing
Frequency:
- Monthly: Stabilized assets (multifamily, retail), requires consistent NOI
- Quarterly: Most common for syndications, balances cash flow and admin burden
- Annual: Development/heavy value-add, lower early cash flow
- Event-Driven: Upon refinance/sale, common for opportunistic deals
Timing Considerations:
1. Operating Cash Flow: Must have excess after debt service and reserves 2. Capital Events: Refi/sale proceeds distributed 30-90 days after closing 3. Tax Implications: K-1 allocations may differ from cash (phantom income risk) 4. Pref Accrual: Quarterly distributions → pref accrues quarterly
Python: Distribution Schedule
import pandas as pd
class DistributionSchedule:
def __init__(self, noi_schedule, debt_service, reserve_rate=0.10):
self.noi_schedule = noi_schedule
self.debt_service = debt_service
self.reserve_rate = reserve_rate
self.distributions = []
def calculate_distributable_cash(self, period_noi):
cash_after_debt = period_noi - self.debt_service
reserves = period_noi * self.reserve_rate
distributable = cash_after_debt - reserves
return max(distributable, 0)
def generate_quarterly_distributions(self, years=5):
quarters = years * 4
for q in range(quarters):
year = q // 4 + 1
quarter = q % 4 + 1
annual_noi = self.noi_schedule.get(year, 0)
quarterly_noi = annual_noi / 4
dist_cash = self.calculate_distributable_cash(quarterly_noi)
self.distributions.append({
'year': year,
'quarter': quarter,
'noi': quarterly_noi,
'debt_service': self.debt_service / 4,
'reserves': quarterly_noi * self.reserve_rate,
'distributable_cash': dist_cash
})
return pd.DataFrame(self.distributions)
# Example
noi_schedule = {1: 1000000, 2: 1050000, 3: 1100000, 4: 1150000, 5: 1200000}
dist_model = DistributionSchedule(noi_schedule, debt_service=600000, reserve_rate=0.10)
schedule = dist_model.generate_quarterly_distributions(years=5)
print("Quarterly Distribution Schedule:")
print(schedule.head(10))
print(f"\nTotal Distributable (5 years): ${schedule['distributable_cash'].sum():,.0f}")Best Practices: 1. Set clear expectations in operating agreement 2. Maintain reserves (never distribute below minimums) 3. Establish consistent timing (e.g., 15th of month after quarter-end) 4. Provide distribution notices with explanations 5. Coordinate year-end distributions with K-1 allocations
Financial Metrics & Pro Forma Modeling
Table of Contents
- Quick Reference Table
- Core Metrics
- Net Operating Income (NOI)
- Capitalization Rate (Cap Rate)
- Cash-on-Cash Return
- Debt Service Coverage Ratio (DSCR)
- Internal Rate of Return (IRR)
- Equity Multiple
- Gross Rent Multiplier (GRM)
- Operating Expense Ratio (OER)
- Break-even Occupancy Ratio
- Price per Unit / SF
- Pro Forma Construction
- Python Implementation
- Excel Reference
Quick Reference Table
| Metric | Formula | Typical Range |
|---|---|---|
| NOI | EGI - OpEx | Property dependent |
| Cap Rate | NOI / Property Value | 4%-10% (market dependent) |
| Cash-on-Cash | (NOI - Debt Service) / Equity | 6%-12% |
| DSCR | NOI / Debt Service | 1.2x minimum (lender requirement) |
| Equity Multiple | Total Distributions / Equity | 1.5x-3.0x (hold period dependent) |
| GRM | Price / Gross Rent | 8-15 (market dependent) |
| OER | OpEx / EGI | Multifamily: 35%-45% |
| Break-even Occupancy | (OpEx + Debt) / PGI | <85% (lender preference) |
---
Core Metrics
1. Net Operating Income (NOI)
Formula:
NOI = Effective Gross Income - Operating ExpensesWhat It Measures: Property operating performance before debt service.
Python:
def calculate_noi(effective_gross_income, operating_expenses):
"""Calculate Net Operating Income"""
return effective_gross_income - operating_expenses
# Example
noi = calculate_noi(500000, 200000)
print(f"NOI: ${noi:,.2f}") # $300,000.00Excel: =B2-C2 where B2 = EGI, C2 = OpEx
---
2. Capitalization Rate (Cap Rate)
Formula:
Cap Rate = NOI / Property ValueWhat It Measures: Unlevered yield on property; quick valuation metric.
When to Use: Compare similar properties, estimate value from NOI.
Python:
def calculate_cap_rate(noi, property_value):
"""Calculate Cap Rate"""
return noi / property_value
def estimate_value_from_cap_rate(noi, cap_rate):
"""Reverse: estimate value"""
return noi / cap_rate
# Example
cap_rate = calculate_cap_rate(300000, 4000000)
print(f"Cap Rate: {cap_rate:.2%}") # 7.50%
value = estimate_value_from_cap_rate(300000, 0.075)
print(f"Estimated Value: ${value:,.0f}")Excel: =B2/C2 where B2 = NOI, C2 = Property Value
---
3. Cash-on-Cash Return (CoC)
Formula:
CoC = (NOI - Annual Debt Service) / Total Cash InvestedWhat It Measures: Annual levered return on equity invested.
When to Use: Evaluate first-year or stabilized returns; compare leverage scenarios.
Python:
def calculate_cash_on_cash(noi, annual_debt_service, total_cash_invested):
"""Calculate Cash-on-Cash Return"""
annual_cash_flow = noi - annual_debt_service
return annual_cash_flow / total_cash_invested
# Example
coc = calculate_cash_on_cash(300000, 180000, 1000000)
print(f"Cash-on-Cash: {coc:.2%}") # 12.00%Excel: =(B2-C2)/D2 where B2 = NOI, C2 = Debt Service, D2 = Equity
---
4. Debt Service Coverage Ratio (DSCR)
Formula:
DSCR = NOI / Annual Debt ServiceWhat It Measures: Property's ability to cover debt obligations.
Typical Requirements:
- Lenders require DSCR ≥ 1.2x for stabilized properties
- DSCR = 1.0x means break-even (NOI exactly covers debt)
Python:
def calculate_dscr(noi, annual_debt_service):
"""Calculate DSCR"""
return noi / annual_debt_service
# Example
dscr = calculate_dscr(450000, 250000)
print(f"DSCR: {dscr:.2f}x") # 1.80x
# Check lender requirements
min_dscr = 1.20
if dscr >= min_dscr:
print(f"✓ Meets minimum DSCR of {min_dscr}x")
else:
print(f"✗ Below minimum DSCR of {min_dscr}x")Excel: =B2/C2 where B2 = NOI, C2 = Annual Debt Service
---
5. Internal Rate of Return (IRR)
Formula:
0 = CF₀ + CF₁/(1+IRR)¹ + CF₂/(1+IRR)² + ... + CFₙ/(1+IRR)ⁿWhat It Measures: Annualized return accounting for time value of money.
Levered vs Unlevered:
- Unlevered IRR: Based on NOI (property performance)
- Levered IRR: Based on cash flow after debt service (equity returns)
Python:
import numpy_financial as npf
def calculate_irr(cash_flows):
"""Calculate IRR from cash flow array"""
return npf.irr(cash_flows)
# Example: 5-year hold
cash_flows = [-1000000, 120000, 120000, 120000, 120000, 1320000]
irr = calculate_irr(cash_flows)
print(f"IRR: {irr:.2%}") # 15.24%
# XIRR for irregular dates
from scipy.optimize import newton
from datetime import datetime
def calculate_xirr(cash_flows, dates, guess=0.1):
"""Calculate XIRR for irregular cash flows"""
def xnpv(rate, cash_flows, dates):
min_date = min(dates)
days = [(d - min_date).days for d in dates]
return sum([cf / (1 + rate) ** (day / 365.0) for cf, day in zip(cash_flows, days)])
return newton(lambda r: xnpv(r, cash_flows, dates), guess)
# Example
cfs = [-1000000, 50000, 75000, 100000, 1200000]
dates = [
datetime(2023, 1, 1),
datetime(2023, 6, 15),
datetime(2024, 3, 20),
datetime(2024, 12, 10),
datetime(2025, 8, 1)
]
xirr = calculate_xirr(cfs, dates)
print(f"XIRR: {xirr:.2%}")Excel:
=IRR(B2:B12) # Regular periods
=XIRR(B2:B12, A2:A12) # Irregular dates---
6. Equity Multiple
Formula:
Equity Multiple = Total Cash Distributions / Initial Equity InvestmentWhat It Measures: Gross multiple of money returned (ignores timing).
Interpretation:
- 2.5x = investor receives $2.50 for every $1.00 invested
- Should be paired with IRR (2.0x over 3 years ≠ 2.0x over 10 years)
Python:
def calculate_equity_multiple(total_distributions, initial_equity):
"""Calculate Equity Multiple"""
return total_distributions / initial_equity
def calculate_equity_multiple_from_cf(cash_flows):
"""Calculate from cash flow array (cf[0] = initial investment)"""
initial_investment = abs(cash_flows[0])
total_distributions = sum(cash_flows[1:])
return total_distributions / initial_investment
# Example
em = calculate_equity_multiple_from_cf([-1000000, 120000, 120000, 120000, 120000, 1320000])
print(f"Equity Multiple: {em:.2f}x") # 1.80xExcel: =SUM(B3:B12)/ABS(B2) where B2 = initial investment, B3:B12 = distributions
---
7. Gross Rent Multiplier (GRM)
Formula:
GRM = Property Price / Gross Annual RentWhat It Measures: Quick screening metric for relative pricing.
Interpretation: Lower GRM = higher expected yield.
Python:
def calculate_grm(property_price, gross_annual_rent):
"""Calculate GRM"""
return property_price / gross_annual_rent
def estimate_value_from_grm(gross_annual_rent, market_grm):
"""Estimate value using market GRM"""
return gross_annual_rent * market_grm
# Example
grm = calculate_grm(1200000, 100000)
print(f"GRM: {grm:.2f}") # 12.00
# Compare properties
import pandas as pd
properties = pd.DataFrame({
'Property': ['A', 'B', 'C'],
'Price': [1200000, 850000, 2000000],
'Rent': [100000, 85000, 150000]
})
properties['GRM'] = properties['Price'] / properties['Rent']
print(properties)Excel: =B2/C2 where B2 = Price, C2 = Gross Annual Rent
---
8. Operating Expense Ratio (OER)
Formula:
OER = Operating Expenses / Effective Gross IncomeTypical Ranges:
- Multifamily: 35%-45% (good range: 35%-40%)
- Office: 35%-55%
- Retail: 20%-30% or 60%-80% (lease structure dependent)
- Industrial: 15%-25%
Python:
def calculate_oer(operating_expenses, effective_gross_income):
"""Calculate OER"""
return operating_expenses / effective_gross_income
def benchmark_oer(oer, property_type):
"""Benchmark against typical ranges"""
benchmarks = {
'multifamily': (0.35, 0.45),
'office': (0.35, 0.55),
'retail': (0.20, 0.30),
'industrial': (0.15, 0.25)
}
if property_type.lower() in benchmarks:
low, high = benchmarks[property_type.lower()]
if low <= oer <= high:
return f"Within range ({low:.0%}-{high:.0%})"
elif oer < low:
return f"Below range - Very efficient"
else:
return f"Above range - Review expenses"
return "Unknown property type"
# Example
oer = calculate_oer(200000, 500000)
print(f"OER: {oer:.1%}") # 40.0%
print(benchmark_oer(oer, 'multifamily'))Excel: =B2/C2 where B2 = OpEx, C2 = EGI
---
9. Break-even Occupancy Ratio
Formula:
Break-even Occupancy = (Operating Expenses + Debt Service) / Potential Gross IncomeWhat It Measures: Minimum occupancy to cover OpEx + debt.
Lender Preference: ≤85% for reasonable cushion.
Python:
def calculate_breakeven_occupancy(operating_expenses, debt_service, potential_gross_income):
"""Calculate Break-even Occupancy"""
return (operating_expenses + debt_service) / potential_gross_income
# Example
beo = calculate_breakeven_occupancy(200000, 180000, 500000)
print(f"Break-even Occupancy: {beo:.1%}") # 76.0%
# Calculate margin of safety
current_occupancy = 0.92
margin = current_occupancy - beo
print(f"Margin of Safety: {margin:.1%}") # 16.0%
# Check lender requirements
if beo <= 0.85:
print("✓ Acceptable break-even (<85%)")
else:
print(f"✗ Exceeds 85% threshold by {(beo - 0.85):.1%}")Excel: =(B2+C2)/D2 where B2 = OpEx, C2 = Debt Service, D2 = PGI
---
10. Price per Unit / SF
Formulas:
Price per Unit = Total Price / Number of Units
Price per SF = Total Price / Total Square FootageWhen to Use: Compare properties of different sizes; market benchmarking.
Python:
def calculate_price_per_unit(total_price, num_units):
"""Calculate Price per Unit"""
return total_price / num_units
def calculate_price_per_sf(total_price, total_sf):
"""Calculate Price per SF"""
return total_price / total_sf
# Example
ppu = calculate_price_per_unit(5000000, 50)
ppsf = calculate_price_per_sf(5000000, 45000)
print(f"Price per Unit: ${ppu:,.0f}") # $100,000
print(f"Price per SF: ${ppsf:,.2f}") # $111.11
# Compare multiple properties
import pandas as pd
df = pd.DataFrame({
'Property': ['A', 'B', 'C'],
'Price': [5000000, 7500000, 3200000],
'Units': [50, 75, 32],
'SF': [45000, 68000, 30000]
})
df['Price/Unit'] = df['Price'] / df['Units']
df['Price/SF'] = df['Price'] / df['SF']
print(df)Excel:
Price/Unit: =B2/C2
Price/SF: =B2/D2---
Pro Forma Construction
Standard Waterfall Structure
1. Gross Potential Rent (GPR)
+ Other Income (2%-5% of rent: parking, laundry, fees)
─────────────────────────
2. = Potential Gross Income (PGI)
3. - Vacancy & Credit Loss (5%-15% depending on property class/type)
─────────────────────────
4. = Effective Gross Income (EGI)
5. - Operating Expenses
• Property Taxes
• Insurance (0.5%-1.5% of value)
• Utilities
• Repairs & Maintenance (5%-15% of EGI)
• Property Management (3%-5% of EGI)
• Administrative
• Payroll
• Contract Services
─────────────────────────
6. = Net Operating Income (NOI)
7. - Debt Service (Principal + Interest)
─────────────────────────
8. = Cash Flow Before CapEx
9. - CapEx Reserves (5%-10% of EGI or $250-$500/unit)
─────────────────────────
10. = Net Cash FlowGrowth Assumptions
Rent Growth:
- Conservative: 2%-3% annually
- Long-run average: ~3%
- Stabilization: 1.5% Y1, 2.5% Y2, 3% Y3+
- Must be supported by market data
Expense Growth:
- Typical: 2%-3% annually
- Property taxes: 2.5%-3%
- Utilities: 3%-4% (inflation sensitive)
- Insurance: 3%-5% (volatile)
CapEx Reserves
Major Categories: Roof, HVAC, parking, plumbing, electrical, elevators, unit renovations
Common Methods:
- Percentage: 5%-10% of EGI
- Per unit: $250-$500 annually (multifamily)
- Per SF: $0.25-$0.75 (commercial)
- Component-based: Calculate replacement cycles
Note: Not included in NOI, but deducted from cash flow.
Best Practices
1. Use conservative assumptions (underestimate income, overestimate expenses) 2. Separate market rent from in-place rent 3. Model stabilization period explicitly 4. Include sensitivity analysis on key drivers 5. Support all assumptions with market data 6. Calculate multiple return metrics (IRR, EM, CoC, NPV) 7. Model actual loan terms and amortization 8. Include exit assumptions (cap rate, selling costs)
---
Python Implementation
Complete Pro Forma Class
import pandas as pd
import numpy as np
import numpy_financial as npf
class RealEstateProForma:
"""Complete real estate pro forma model"""
def __init__(self, property_params, loan_params, assumptions):
"""
Initialize pro forma
property_params: {
purchase_price, units, annual_rent_per_unit,
other_income_pct, vacancy_rate, opex_ratio
}
loan_params: {
ltv, interest_rate, amortization_years
}
assumptions: {
holding_period, rent_growth, expense_growth,
capex_pct, exit_cap_rate, selling_costs_pct
}
"""
self.property_params = property_params
self.loan_params = loan_params
self.assumptions = assumptions
self.loan_amount = property_params['purchase_price'] * loan_params['ltv']
self.equity_investment = property_params['purchase_price'] - self.loan_amount
self.build_pro_forma()
def build_pro_forma(self):
"""Build complete pro forma DataFrame"""
years = self.assumptions['holding_period']
self.df = pd.DataFrame({'Year': range(0, years + 1)})
# Year 0: Acquisition
self.df.loc[0, 'Equity Investment'] = -self.equity_investment
# Years 1+: Operations
for year in range(1, years + 1):
# Revenue
base_rent = self.property_params['annual_rent_per_unit'] * self.property_params['units']
growth_factor = (1 + self.assumptions['rent_growth']) ** (year - 1)
gross_potential_rent = base_rent * growth_factor
other_income = gross_potential_rent * self.property_params['other_income_pct']
potential_gross_income = gross_potential_rent + other_income
vacancy = potential_gross_income * self.property_params['vacancy_rate']
effective_gross_income = potential_gross_income - vacancy
# Expenses
operating_expenses = effective_gross_income * self.property_params['opex_ratio']
# NOI
noi = effective_gross_income - operating_expenses
# Debt Service
annual_debt_service = -npf.pmt(
self.loan_params['interest_rate'],
self.loan_params['amortization_years'],
self.loan_amount
)
# Cash Flow
cash_flow_before_capex = noi - annual_debt_service
capex_reserves = effective_gross_income * self.assumptions['capex_pct']
net_cash_flow = cash_flow_before_capex - capex_reserves
# Populate DataFrame
self.df.loc[year, 'Gross Potential Rent'] = gross_potential_rent
self.df.loc[year, 'Other Income'] = other_income
self.df.loc[year, 'Potential Gross Income'] = potential_gross_income
self.df.loc[year, 'Vacancy & Credit Loss'] = vacancy
self.df.loc[year, 'Effective Gross Income'] = effective_gross_income
self.df.loc[year, 'Operating Expenses'] = operating_expenses
self.df.loc[year, 'Net Operating Income'] = noi
self.df.loc[year, 'Debt Service'] = annual_debt_service
self.df.loc[year, 'Cash Flow Before CapEx'] = cash_flow_before_capex
self.df.loc[year, 'CapEx Reserves'] = capex_reserves
self.df.loc[year, 'Net Cash Flow'] = net_cash_flow
# Exit/Sale in final year
final_year = years
final_noi = self.df.loc[final_year, 'Net Operating Income']
exit_value = final_noi / self.assumptions['exit_cap_rate']
selling_costs = exit_value * self.assumptions['selling_costs_pct']
remaining_balance = self.calculate_loan_balance(final_year)
net_sale_proceeds = exit_value - selling_costs - remaining_balance
self.df.loc[final_year, 'Property Sale Value'] = exit_value
self.df.loc[final_year, 'Selling Costs'] = selling_costs
self.df.loc[final_year, 'Remaining Loan Balance'] = remaining_balance
self.df.loc[final_year, 'Net Sale Proceeds'] = net_sale_proceeds
self.df.loc[final_year, 'Total Cash Flow'] = (
self.df.loc[final_year, 'Net Cash Flow'] + net_sale_proceeds
)
# Total Cash Flow for all years
for year in range(1, final_year):
self.df.loc[year, 'Total Cash Flow'] = self.df.loc[year, 'Net Cash Flow']
self.df.loc[0, 'Total Cash Flow'] = self.df.loc[0, 'Equity Investment']
def calculate_loan_balance(self, year):
"""Calculate remaining loan balance"""
remaining_balance = -npf.fv(
self.loan_params['interest_rate'],
year,
-npf.pmt(
self.loan_params['interest_rate'],
self.loan_params['amortization_years'],
self.loan_amount
),
self.loan_amount
)
return remaining_balance
def calculate_metrics(self):
"""Calculate return metrics"""
cash_flows = self.df['Total Cash Flow'].values
irr = npf.irr(cash_flows)
total_distributions = cash_flows[1:].sum()
equity_multiple = total_distributions / abs(cash_flows[0])
npv_8 = npf.npv(0.08, cash_flows)
npv_10 = npf.npv(0.10, cash_flows)
npv_12 = npf.npv(0.12, cash_flows)
year_1_noi = self.df.loc[1, 'Net Operating Income']
year_1_cash_flow = self.df.loc[1, 'Net Cash Flow']
purchase_cap_rate = year_1_noi / self.property_params['purchase_price']
cash_on_cash_y1 = year_1_cash_flow / self.equity_investment
dscr_y1 = year_1_noi / self.df.loc[1, 'Debt Service']
return pd.Series({
'Purchase Price': self.property_params['purchase_price'],
'Equity Investment': self.equity_investment,
'Loan Amount': self.loan_amount,
'LTV': self.loan_params['ltv'],
'Purchase Cap Rate': purchase_cap_rate,
'Year 1 NOI': year_1_noi,
'Year 1 Cash Flow': year_1_cash_flow,
'Year 1 Cash-on-Cash': cash_on_cash_y1,
'Year 1 DSCR': dscr_y1,
'IRR': irr,
'Equity Multiple': equity_multiple,
'NPV @ 8%': npv_8,
'NPV @ 10%': npv_10,
'NPV @ 12%': npv_12,
'Exit Cap Rate': self.assumptions['exit_cap_rate'],
})
def create_amortization_schedule(self):
"""Create loan amortization schedule"""
years = self.loan_params['amortization_years']
rate = self.loan_params['interest_rate']
principal = self.loan_amount
payment = -npf.pmt(rate, years, principal)
periods = np.arange(1, years + 1)
interest_payments = -npf.ipmt(rate, periods, years, principal)
principal_payments = -npf.ppmt(rate, periods, years, principal)
remaining_balance = np.zeros(years)
remaining_balance[0] = principal - principal_payments[0]
for i in range(1, years):
remaining_balance[i] = remaining_balance[i-1] - principal_payments[i]
return pd.DataFrame({
'Year': periods,
'Beginning Balance': np.concatenate([[principal], remaining_balance[:-1]]),
'Payment': payment,
'Interest': interest_payments,
'Principal': principal_payments,
'Ending Balance': remaining_balance
})
def display_summary(self):
"""Print formatted summary"""
print("=" * 80)
print("REAL ESTATE PRO FORMA SUMMARY")
print("=" * 80)
metrics = self.calculate_metrics()
print("\nINVESTMENT METRICS")
print("-" * 80)
print(f"Purchase Price: ${metrics['Purchase Price']:>15,.0f}")
print(f"Loan Amount (LTV {metrics['LTV']:.1%}): ${metrics['Loan Amount']:>15,.0f}")
print(f"Equity Investment: ${metrics['Equity Investment']:>15,.0f}")
print(f"\nPurchase Cap Rate: {metrics['Purchase Cap Rate']:>15.2%}")
print(f"Year 1 Cash-on-Cash: {metrics['Year 1 Cash-on-Cash']:>15.2%}")
print(f"Year 1 DSCR: {metrics['Year 1 DSCR']:>15.2f}x")
print(f"\nLevered IRR: {metrics['IRR']:>15.2%}")
print(f"Equity Multiple: {metrics['Equity Multiple']:>15.2f}x")
print(f"\nNPV @ 8%: ${metrics['NPV @ 8%']:>15,.0f}")
print(f"NPV @ 10%: ${metrics['NPV @ 10%']:>15,.0f}")
print(f"NPV @ 12%: ${metrics['NPV @ 12%']:>15,.0f}\n")
print("\n" + "=" * 80)
print("PRO FORMA CASH FLOWS")
print("=" * 80)
display_cols = [
'Year', 'Potential Gross Income', 'Effective Gross Income',
'Operating Expenses', 'Net Operating Income', 'Debt Service',
'Net Cash Flow', 'Total Cash Flow'
]
pd.options.display.float_format = '${:,.0f}'.format
print(self.df[display_cols].to_string(index=False))
# Example Usage
if __name__ == "__main__":
property_params = {
'purchase_price': 5000000,
'units': 50,
'annual_rent_per_unit': 12000,
'other_income_pct': 0.03,
'vacancy_rate': 0.05,
'opex_ratio': 0.40
}
loan_params = {
'ltv': 0.75,
'interest_rate': 0.05,
'amortization_years': 30
}
assumptions = {
'holding_period': 10,
'rent_growth': 0.03,
'expense_growth': 0.025,
'capex_pct': 0.05,
'exit_cap_rate': 0.065,
'selling_costs_pct': 0.03
}
model = RealEstateProForma(property_params, loan_params, assumptions)
model.display_summary()
amort = model.create_amortization_schedule()
print("\n" + "=" * 80)
print("AMORTIZATION SCHEDULE (First 10 Years)")
print("=" * 80)
print(amort.head(10).to_string(index=False))Key numpy-financial Functions
import numpy_financial as npf
# 1. PMT - Loan payment
payment = npf.pmt(rate, nper, pv, fv=0, when='end')
# Example
monthly_payment = npf.pmt(0.05/12, 30*12, 200000)
print(f"Monthly Payment: ${-monthly_payment:,.2f}")
# 2. IPMT - Interest portion
interest = npf.ipmt(rate, per, nper, pv)
month_1_interest = npf.ipmt(0.05/12, 1, 30*12, 200000)
# 3. PPMT - Principal portion
principal = npf.ppmt(rate, per, nper, pv)
month_1_principal = npf.ppmt(0.05/12, 1, 30*12, 200000)
# 4. IRR - Internal rate of return
cash_flows = [-1000000, 120000, 120000, 120000, 120000, 1320000]
irr = npf.irr(cash_flows)
print(f"IRR: {irr:.2%}")
# 5. NPV - Net present value
npv = npf.npv(0.10, cash_flows)
print(f"NPV @ 10%: ${npv:,.0f}")
# 6. FV - Future value (remaining loan balance)
remaining_balance = -npf.fv(0.05/12, 60, -monthly_payment, 200000)
print(f"Balance after 5 years: ${remaining_balance:,.0f}")
# 7. PV - Present value (loan amount from payment)
loan_amount = -npf.pv(0.05/12, 30*12, 1073.64)
print(f"Loan Amount: ${loan_amount:,.0f}")---
Excel Reference
Core Financial Functions
| Python | Excel | Example | Notes |
|---|---|---|---|
npf.pmt() | =PMT(rate, nper, pv, [fv], [type]) | =PMT(5%/12, 360, 200000) | Returns negative |
npf.ipmt() | =IPMT(rate, per, nper, pv) | =IPMT(5%/12, 1, 360, 200000) | Interest portion |
npf.ppmt() | =PPMT(rate, per, nper, pv) | =PPMT(5%/12, 1, 360, 200000) | Principal portion |
npf.fv() | =FV(rate, nper, pmt, [pv]) | =FV(5%/12, 60, -1073.64, 200000) | Remaining balance |
npf.pv() | =PV(rate, nper, pmt, [fv]) | =PV(5%/12, 360, -1073.64) | Loan amount |
npf.irr() | =IRR(values, [guess]) | =IRR(B2:B12) | Array starts with CF₀ |
npf.npv() | =NPV(rate, values) | =NPV(10%, B3:B12) + B2 | Add B2 separately! |
| XIRR (scipy) | =XIRR(values, dates) | =XIRR(B2:B12, A2:A12) | Irregular dates |
| XNPV (scipy) | =XNPV(rate, values, dates) | =XNPV(10%, B2:B12, A2:A12) | Irregular NPV |
Real Estate Metrics
| Metric | Excel Formula |
|---|---|
| NOI | =B2-C2 (EGI - OpEx) |
| Cap Rate | =B2/C2 (NOI / Value) |
| Cash-on-Cash | =(B2-C2)/D2 ((NOI - Debt) / Equity) |
| DSCR | =B2/C2 (NOI / Debt Service) |
| Equity Multiple | =SUM(B3:B12)/ABS(B2) |
| GRM | =B2/C2 (Price / Rent) |
| OER | =B2/C2 (OpEx / EGI) |
| Break-even Occupancy | =(B2+C2)/D2 ((OpEx + Debt) / PGI) |
| Price/Unit | =B2/C2 (Price / Units) |
Important Excel NPV Note
Excel's NPV assumes first value is end of period 1, not period 0:
Incorrect: =NPV(10%, B2:B12)
Correct: =NPV(10%, B3:B12) + B2Amortization Schedule Template
A: Period (1 to 360)
B: Beginning Balance
B2: =Loan_Amount
B3: =E2 (previous ending balance)
C: Payment
C2: =PMT($Rate/12, $Term*12, $Loan_Amount)
D: Interest
D2: =B2*$Rate/12
E: Principal
E2: =C2-D2
F: Ending Balance
F2: =B2-E2Growth Escalations
# Compound growth
Year 1: =Base_Value
Year 2: =Base_Value * (1 + Growth_Rate)^1
Year N: =Base_Value * (1 + Growth_Rate)^(N-1)
# Using absolute/relative references
Year 1: =$B$2
Year 2: =$B$2 * (1 + $C$2)^(A3-A2)Market Analysis & Data Sources
Table of Contents
1. Market Scoring Framework 2. API Integration
3. Comparable Analysis 4. Submarket Analysis
---
Market Scoring Framework
Economic Indicators
Job Growth (BLS)
- Most critical metric for rental demand
- Data: https://www.bls.gov/emp/, https://trerc.tamu.edu/data/employment-bls/
- National projection: 5.2M jobs 2024-2034 (3.1% growth)
Population Growth (Census)
- Census API and ACS at tract/county/metro/state levels
Median Household Income (Census ACS)
- Variable: B19013 (Median Household Income)
- Updated annually at all geographic levels
Rent Growth YoY
- Sources: Zillow Rent Index (free), Redfin CSV (free), AirDNA (STR, paid), Rentcast API (50 free/month)
Supply Metrics
Building Permits
- HUD SOCDS: https://socds.huduser.gov/permits/ (1980-present, county-level)
- HUD GIS: https://hudgis-hud.opendata.arcgis.com/datasets/HUD::residential-construction-permits-by-county/about
- Census: https://www.census.gov/construction/nrc/
Construction Pipeline
- HUD Survey of Construction (SOC)
- HUD Survey of Market Absorption (SOMA)
- HUD USHMC Database (annual/quarterly/monthly)
Months of Inventory
- Formula: (Total Active Listings) / (Monthly Sales Volume)
- Sources: MLS, Redfin
Demand Metrics
Vacancy Rates
- Formula: (vacant days / available days) × 100
- Healthy range: 5-10%
- FRED: https://fred.stlouisfed.org/series/RRVRUSQ156N (Q1 1956-present)
- Census Bureau (multiple geographic levels)
Absorption Rates
- Formula: (Total SF Leased/Sold) / (Total Time Period)
- Sources: CoStar, local MLS, real estate associations
Days on Market
- Sources: MLS, Zillow, Redfin, Realtor.com
Affordability Metrics
Rent-to-Income Ratio
- Formula: (Monthly Rent) / (Gross Monthly Income)
- Standard: ≤30% (typical landlord requirement)
Price-to-Rent Ratio
- Formula: (Home Price) / (Annual Gross Rent)
- <15: Buying favored | 15-21: Balanced | >21: Renting favored
- Related: 7% rule (annual rent ≈ 7% of purchase price)
Home Price-to-Income Ratio
- Formula: (Median Home Price) / (Median Household Income)
- Combine Census income + Zillow/Redfin price data
Weighted Scoring Model
Example structure:
Job Growth: 20%
Rent Growth YoY: 20%
Population Growth: 15%
Vacancy Rate: 15%
Median Income Growth: 10%
Price-to-Rent Ratio: 10%
Building Permits: 5%
Days on Market: 5%Calculate distance between comparatives and target; weight by Gross Asset Value (GAV) for portfolios.
---
API Integration
Mashvisor API
Auth: Token via x-api-key HTTP header (JWT over HTTPS) Base: https://api.mashvisor.com/v1.1/client/ Docs: https://www.mashvisor.com/api-doc/, https://github.com/mashvisor/mashvisor-api-docs
Endpoints:
GET /airbnb-property/market-summary
Params: state, city
GET /trends/neighborhoods
GET /city/properties/{state}/{city}Python:
import requests
headers = {'x-api-key': 'YOUR_API_KEY'}
url = 'https://api.mashvisor.com/v1.1/client/airbnb-property/market-summary'
params = {'state': 'FL', 'city': 'Miami'}
response = requests.get(url, headers=headers, params=params)
data = response.json()Pricing: Contact sales (available via RapidAPI)
---
AirDNA API
Auth: Bearer Token in Authorization header Base: https://api.airdna.co Docs: https://apidocs.airdna.co/, https://airdna.redoc.ly/, https://enterprise-help.airdna.co/en/articles/8185669
Packages: Market Data, Property Valuations & Comps, Rentalizer Lead Gen, Smart Rates Data
Endpoints:
POST /rentalizer/estimate
Returns: Revenue projections, occupancy, recommended rates
GET /market/search
Returns: Market availability, classification
GET /property/availability
Returns: Availability calendar (up to 12 months)Python:
import requests
headers = {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Accept': 'application/json'
}
url = 'https://api.airdna.co/rentalizer/estimate'
payload = {
'address': '123 Main St, Miami, FL 33101',
'bedrooms': 2,
'bathrooms': 2
}
response = requests.post(url, headers=headers, json=payload)
estimate = response.json()Pricing: Contact sales; enterprise discounts for >5,000 calls/month
---
ATTOM Data API
Coverage: 155M+ U.S. residential/commercial properties, 70B rows, 9,000 attributes/property Docs: https://apitracker.io/a/attomdata, https://www.postman.com/api-evangelist/attom/documentation/4puqnzg, https://docs.deweydata.io/docs/attom
Data: Property details, tax assessments, sales history, ownership, AVMs, automated rent values, mortgage info, zoning, land use, development applications, historical imagery
Endpoints: Property Details, Property Valuation (AVM), Sales History, Tax Assessment, Mortgage/Foreclosure, Neighborhood Data
Python:
import requests
headers = {
'apikey': 'YOUR_API_KEY',
'Accept': 'application/json'
}
url = 'https://api.gateway.attomdata.com/propertyapi/v1.0.0/property/detail'
params = {
'address1': '123 Main St',
'address2': 'Miami, FL 33101'
}
response = requests.get(url, headers=headers, params=params)
property_data = response.json()Pricing: ~$500/month basic (few thousand calls); scales for bulk downloads; enterprise via consultation
---
Rentcast API
Auth: API Key via X-Api-Key header Base: https://api.rentcast.io/v1/ Docs: https://developers.rentcast.io/, https://www.postman.com/rentcast/rentcast-api/documentation/ca4yudw
Endpoints:
GET /properties
Params: city, state, limit
GET /avm/value
Returns: Property valuation estimate
GET /avm/rent
Returns: Rental estimate
GET /markets
Returns: Market trends, statisticsPython:
import requests
headers = {
'Accept': 'application/json',
'X-Api-Key': 'YOUR_API_KEY'
}
# Search properties
url = 'https://api.rentcast.io/v1/properties'
params = {'city': 'Austin', 'state': 'TX', 'limit': 20}
response = requests.get(url, headers=headers, params=params)
properties = response.json()
# Rental estimate
url = 'https://api.rentcast.io/v1/avm/rent'
params = {
'address': '123 Main St',
'city': 'Austin',
'state': 'TX',
'zipCode': '78701',
'bedrooms': 2,
'bathrooms': 2,
'squareFootage': 1200
}
response = requests.get(url, headers=headers, params=params)
rent_estimate = response.json()Pricing: Free (50 requests/month), paid tiers with higher limits, live support 7 days/week
---
Census Bureau API
Auth: Free API key via query param ?key=YOUR_API_KEY Request key: https://api.census.gov/data/key_signup.html Base URLs:
- ACS 5-Year:
https://api.census.gov/data/{year}/acs/acs5 - ACS 1-Year:
https://api.census.gov/data/{year}/acs/acs1 - Decennial:
https://api.census.gov/data/{year}/dec/sf1
Docs: https://www.census.gov/data/developers/data-sets.html, https://www.census.gov/programs-surveys/acs/data/data-via-api.html
Key Housing Variables:
B25001: Total Housing Units
B25002: Occupancy Status
B25003: Tenure (Owner/Renter)
B25004: Vacancy Status (For Rent, For Sale, etc.)
B25034: Year Structure Built
B25064: Median Gross Rent
B25077: Median Home Value
B19013: Median Household Income
B01003: Total PopulationVariable Finder: https://api.census.gov/data/2021/acs/acs5/variables.html
Python (census library):
from census import Census
from us import states
c = Census("YOUR_API_KEY")
# Median household income by state
data = c.acs5.get(
('NAME', 'B19013_001E'),
{'for': 'state:*'},
year=2021
)
# Vacancy status by tract (Miami-Dade County, FL)
data = c.acs5.get(
('NAME', 'B25004_001E', 'B25004_002E', 'B25004_003E'),
{'for': 'tract:*', 'in': 'state:12 county:086'},
year=2021
)
# Multiple housing variables for city
variables = ['NAME', 'B25001_001E', 'B25002_002E', 'B25002_003E',
'B25064_001E', 'B19013_001E']
data = c.acs5.get(
tuple(variables),
{'for': 'place:45000', 'in': 'state:12'}, # Miami, FL
year=2021
)Python (requests):
import requests
import pandas as pd
base_url = 'https://api.census.gov/data/2021/acs/acs5'
params = {
'get': 'NAME,B25001_001E,B25064_001E,B19013_001E',
'for': 'county:*',
'in': 'state:12', # Florida
'key': 'YOUR_API_KEY'
}
response = requests.get(base_url, params=params)
data = response.json()
df = pd.DataFrame(data[1:], columns=data[0])Geographic Levels: Nation, State, County, Census Tract, Block Group, Place (cities), MSA, ZCTA
Libraries: census (https://pypi.org/project/census/), pytidycensus, censusdata
Pricing: FREE
---
Redfin Data Center
Access: Manual CSV download (no API) URL: https://www.redfin.com/news/data-center/, https://www.redfin.com/news/data-center/printable-market-data/
Geographic Levels: National, Metro, State, County, City, ZIP, Neighborhood
Metrics: Median sale price, homes sold, new listings, days on market, price drops, inventory, sale-to-list ratio, pending sales, off-market sales
Updates: Weekly (Wednesdays), Monthly (3rd Friday)
Download Methods: 1. Via visualization: Select region/metro/timeframe/metric → click chart → download button 2. Via Download tab: Select region type → download complete dataset
Python (conceptual):
import requests
import pandas as pd
from io import StringIO
# UNVERIFIED - may require interactive download
url = 'https://redfin-public-data.s3.us-west-2.amazonaws.com/redfin_market_tracker/city_market_tracker.tsv000.gz'
response = requests.get(url)
df = pd.read_csv(StringIO(response.text), sep='\t', compression='gzip')
miami_data = df[df['city'] == 'Miami']Pricing: FREE
---
Comparable Analysis
Selection Criteria
1. Proximity
- Same neighborhood preferred
- Set radius/"circle" around subject
- Location differences = value differences
2. Recency
- Last 3-6 months standard
- Older sales require market conditions adjustments
3. Similarity
- Size (SF), age, style, condition, site characteristics, room count, finished area
- More alike = fewer adjustments needed
Minimum: 3 recently sold similar properties
Sources: MLS, public records, Zillow/Redfin/Realtor.com, PropStream, CoreLogic, Black Knight, ATTOM/Rentcast APIs
Adjustment Methodology
1. Transactional Adjustments
- Financing terms (seller financing, below-market rates)
- Conditions of sale (distressed, foreclosure, short sale, REO)
- Motivations (rushed sale, family transfer, estate)
2. Market Conditions
- Appreciation/depreciation between comp sale date and appraisal date
- Calculated as % change per month/quarter
3. Physical Characteristics
- Location, size/SF, lot size, age, condition, amenities, bedrooms/bathrooms, views
Adjustment Direction:
- Comp INFERIOR to subject → ADD to comp price
- Comp SUPERIOR to subject → SUBTRACT from comp price
Goal: Adjust comps to approximate value if identical to subject
Quantitative Methods
1. Paired Sales Analysis
- Find 2 similar properties differing in ONE characteristic
- Formula: (Price Difference) / (Feature Difference)
- Example: Home A (2,000 SF, $400k) vs. Home B (2,500 SF, $450k) → $50k / 500 SF = $100/SF
2. Market-Based Adjustments
- Statistical regression from local sales data
- Reflects actual market reaction
3. Cost-Based Adjustments
- Cost to create minus depreciation
- Less reliable for buyer value
Per-Square-Foot Adjustments
Calculation: (Sale Price) / (Square Footage)
Diminishing Returns:
- First 1,000 SF worth more/SF than beyond 3,000 SF
- Adjust at lower rate for additional SF
Example:
Comp: 2,000 SF, $400k → $200/SF
Subject: 2,500 SF
Option 1 (Full Rate): 2,500 × $200 = $500k
Option 2 (Marginal Rate): $400k + (500 SF × $150) = $475kMultifamily Per-Unit Adjustments
Unit Types: Studio, 1BR/1BA, 2BR/1BA, 2BR/2BA, 3BR/2BA
Process: 1. Identify unit mix for subject and comps 2. Calculate per-unit value from comp sales 3. Adjust for unit mix differences 4. Consider income potential, market rent, unit size
Example:
Comp: 10 units (5×1BR, 5×2BR), $1.5M
Subject: 10 units (3×1BR, 7×2BR)
Assume 1BR=$120k, 2BR=$180k (from paired sales)
Comp: (5×$120k) + (5×$180k) = $1.5M ✓
Subject: (3×$120k) + (7×$180k) = $1.62MAutomated Valuation Models (AVMs)
Types: 1. Comparables-Based: Mimics appraiser's sales comparison approach 2. Hedonic Models: Statistical regression (e.g., Zillow Zestimate, Redfin Estimate, ATTOM AVM)
Advantages: Speed, consistency, cost-effective, scalability, objectivity
Limitations: Data-dependent, no physical inspection, assumes average condition, market lag, less accurate for unique properties
Accuracy: Median error 5-10% in data-rich markets; higher in rural/unique/volatile markets
Providers: Zillow, Redfin, ATTOM, CoreLogic, Black Knight, Collateral Analytics, HouseCanary, ClearCapital
Best Practices:
- Use as starting point
- Compare multiple AVM sources
- Verify with recent comps
- Physical inspection for high-stakes decisions
- Adjust for known condition issues
---
Submarket Analysis
Neighborhood Signals
School Ratings
- Data: GreatSchools API (https://www.greatschools.org/api)
- Coverage: 200,000+ K-12 public/private/charter schools
- NearbySchools API for location-based search
- Scale: 1-10 (10=highest)
Crime Statistics
- NeighborhoodScout: https://www.neighborhoodscout.com/, API: https://api.locationinc.com/about-the-data (≥$5k/year)
- SpotCrime: https://spotcrime.com/ (free, Google Maps plots)
- CrimeGrade: https://crimegrade.org/ (neighborhood-level grades)
- LexisNexis Community Crime Map: https://communitycrimemap.com/
- Local police department portals
Walkability
- Walk Score API: https://www.walkscore.com/professional/api.php
- Base:
https://api.walkscore.com - Metrics: Walk Score, Transit Score, Bike Score
- Default quota: 5,000 calls/day
- Docs: https://walkscore-api.readthedocs.io/, PyPI: https://pypi.org/project/walkscore-api/
Python (Walk Score):
import requests
api_key = 'YOUR_WALKSCORE_API_KEY'
url = 'https://api.walkscore.com/score'
params = {
'format': 'json',
'address': '123 Main St Miami FL 33101',
'lat': 25.7617,
'lon': -80.1918,
'transit': 1,
'bike': 1,
'wsapikey': api_key
}
response = requests.get(url, params=params)
scores = response.json()
print(f"Walk: {scores.get('walkscore')}, Transit: {scores.get('transit', {}).get('score')}, Bike: {scores.get('bike', {}).get('score')}")Transit Access
- Walk Score Transit API, local transit authority APIs, Google Maps Distance Matrix API
Integrated Platforms
- NeighborhoodScout: All-in-one (crime, demographics, housing, schools, trends), API ≥$5k/year
- Realtor.com: GreatSchools + walkability + crime + demographics
- Zillow: GreatSchools + Walk/Transit/Bike + demographics + crime (API discontinued for new users)
- Redfin: GreatSchools + demographics + crime + walkability (CSV downloads, not neighborhood APIs)
Submarket Definition
What: Geographic area (neighborhood, zip, census tracts) with distinct characteristics (school district, zoning, demographics, housing type/age, income, walkability/transit)
Evaluation Framework:
1. Demand
- Days on market (lower=stronger), absorption rate (higher=stronger), rent/price growth YoY, vacancy rate (lower=tighter), population growth
2. Supply
- Building permits, new construction starts, inventory levels (months), conversion projects
3. Demographics
- Median income, age distribution, renter vs. owner rate, education, employment sectors
4. Amenity Access
- School ratings, Walk/Transit/Bike Score, crime rate, parks/recreation
5. Physical
- Housing stock age, property types, lot sizes, architectural styles, condition/renovation trends
6. Investment
- Price-to-rent ratio, cap rates, cash-on-cash return, rent growth, appreciation history
Scoring Model:
import pandas as pd
def score_submarket(submarket_data):
weights = {
'job_growth': 0.15,
'population_growth': 0.10,
'rent_growth_yoy': 0.15,
'median_income': 0.10,
'school_rating': 0.10,
'walk_score': 0.05,
'crime_grade': 0.10,
'vacancy_rate': 0.10, # Inverse
'days_on_market': 0.05, # Inverse
'price_to_rent': 0.10
}
normalized = normalize_metrics(submarket_data) # 0-100 scale
score = sum(normalized[k] * weights[k] for k in weights)
return score
submarkets_df['score'] = submarkets_df.apply(score_submarket, axis=1)
top_submarkets = submarkets_df.nlargest(10, 'score')Identification Techniques: 1. Geographic: Zip codes, census tracts, neighborhoods, school districts, MLS areas 2. Statistical: K-means clustering on property/demographic characteristics 3. Local Knowledge: Realtor insights, historical delineations, cultural enclaves
Use Cases: Target acquisition zones, pricing strategy, risk assessment, marketing messaging, development site selection
Property-Type Analysis Frameworks
Table of Contents
1. Residential (SFR & Small Multifamily)
- BRRRR Method Framework
- ARV Estimation Methodology
- Rules of Thumb
- House Hacking Analysis
- Rent-to-Price Ratio Benchmarks
- Expense Ratios by Property Size
2. Commercial & Large Multifamily
- Per-Unit & Per-SF Metrics
- Expense Ratios by Asset Class
- Lease Structure Impact on NOI
- Tenant Quality Analysis
- Value-Add Underwriting
- Class A/B/C Return Profiles
- Revenue Modeling
- Key AirDNA Metrics
- STR-Specific Expenses
- Regulatory Risk Checklist
- STR vs LTR Comparison
- Dynamic Pricing Tools
- Development Pro Forma
- Hard vs Soft Costs
- Absorption Rate Analysis
- Entitlement Risk Assessment
- Highest and Best Use Framework
- Cost Escalation Modeling
---
Residential (SFR & Small Multifamily)
BRRRR Method Framework
Stage-by-Stage Financial Analysis:
1. Buy: Acquire property below market value
- Calculate maximum purchase price: (ARV × 70%) - Repair Costs
- Track acquisition costs and closing fees
2. Rehab: Add value through strategic renovations
- Track rehab budget and holding costs during renovation
- Monitor labor, materials, permits
3. Rent: Stabilize with tenant income
- Project cash flow post-stabilization
- Apply 1% Rule and 50% Rule for screening
4. Refinance: Extract equity after property appreciates
- Calculate cash-back after refinancing
- Determine capital available for next deal
5. Repeat: Recycle capital into new deals
- Calculate total invested capital per property
- Track portfolio velocity
Required Calculator Functions:
- Up-front capital requirement
- Closing costs and rehab budget
- Holding costs during renovation
- Cash flow projections post-stabilization
- Cash-back estimation after refinancing
- Total invested capital per property
---
ARV Estimation Methodology
Step-by-Step Calculation:
1. Find Comparable Properties
- Recent sales within last 6 months
- Same neighborhood, similar size and condition
2. Calculate Price Per Square Foot
- Formula: ARV = Avg Cost Per SF of Comps × Subject Property SF
3. Adjust for Differences
- Account for variations in bedrooms, bathrooms, finished basements, features
4. Include Renovation Costs
- Factor cosmetic updates and significant repairs
Critical Requirements:
- Comps must be sold within last 6 months (current market conditions)
- Properties must be truly comparable (close enough for valid comparison)
- Adjust for material differences affecting value
---
Rules of Thumb
The 1% Rule
- Formula: Monthly rental income ≥ 1% of purchase price
- Purpose: Quick screening for income potential
- Limitation: Doesn't account for taxes, insurance, maintenance
- Use: Initial "back of napkin" litmus test only
The 50% Rule
- Formula: Total operating expenses = ~50% of gross rental income
- Coverage: Taxes, insurance, utilities, repairs, maintenance
- Exclusion: Does NOT include mortgage principal and interest
- Use: Quick expense estimation for single-family properties
The 70% Rule
- Formula: Maximum Purchase Price = (ARV × 70%) - Repair Costs
- Purpose: Ensures adequate profit margin for fix-and-flip
- Application: Primarily for fix-and-flip, NOT buy-and-hold
- Rationale: 30% margin covers holding costs, closing costs, realtor fees, profit
Application Sequence: 1. Use 1% Rule to screen for income potential 2. Apply 50% Rule to estimate operating expenses 3. Use 70% Rule for fix-and-flip maximum purchase price
---
House Hacking Analysis
FHA/VA Financing Advantages:
- Down payment: 3.5% (FHA) vs. 20-25% (investment property)
- Better interest rates than investment loans
- Lenders count projected rental income toward qualification
- Self-Sufficiency Test (3-4 units): 75% of rental income must cover monthly mortgage + HOA
Owner-Occupancy Requirements:
- Must live in property for minimum 1 year
- Moving out early violates loan terms and triggers penalties
Effective Housing Cost Calculation:
Effective Housing Cost = Total Mortgage Payment - Rental Income from Other UnitsTypical Financial Benefits (5-year horizon):
- Reduced annual housing costs: ~$7,700/year
- Principal reduction after 5 years: ~$26,650
- Equity position (3% appreciation): ~$106,300
---
Rent-to-Price Ratio Benchmarks
Price-to-Rent Ratio Interpretation:
- Below 15: Buying more budget-friendly than renting
- 15-21: Balanced market (both viable)
- Above 21: Renting more affordable; home prices high vs. rents
Alternative Interpretation:
- 1-15: More favorable to buy
- 16-20: Better to rent
- 21+: Much better to rent
Market Examples (by ratio):
- Lowest (favoring purchase): Detroit (8), Cleveland (11), Baltimore (13), Philadelphia (14), Chicago (15)
- Highest (favoring rental): San Jose (45), San Francisco (36)
- Regional Pattern: Lowest in South/Midwest; highest in West Coast tech hubs
For Investors: Use inverse (rent-to-price); higher = better cash flow potential
---
Expense Ratios by Property Size
Multifamily (2-4 units):
- Operating Expense Ratio (OER): 35-45% of gross rental income
- Higher efficiency than SFR due to shared systems and economies of scale
Single-Family:
- Operating expenses typically higher on percentage basis than small multifamily
- Lack of shared resources increases per-unit maintenance costs
- Higher reserve necessary (single point of failure)
Scale Effect:
- Properties with 150+ units: OER 0.58% lower than smaller properties
- Demonstrates significant economies of scale
Key Expense Categories:
- Property taxes
- Insurance
- Utilities
- Maintenance and repairs
- Property management: 8-10% of gross rent (residential)
- Vacancy allowance: 5-10% (market-dependent)
- Capital expenditures reserve
---
Commercial & Large Multifamily
Per-Unit & Per-SF Metrics
Price Per Door/Unit:
- Calculation: Total Property Price ÷ Number of Units
- Example: $2M / 20 units = $100,000 per unit
- Quick Benchmark: <$25,000 per door typically indicates good cap rate and cash flow
- Limitation: Price per door alone shouldn't be sole decision factor
Other Per-Unit Metrics:
- Rent Per Unit: Average monthly rent across all units
- Expense Per Unit: Total operating expenses ÷ units
- NOI Per Unit: Net Operating Income ÷ units
Per-Square-Foot Metrics (Office/Retail/Industrial):
- Price Per SF: Total acquisition cost ÷ total square footage
- Rent Per SF: Annual rent ÷ total leasable square footage
- Expense Per SF: Operating expenses ÷ total square footage
Application: Enables comparison across different building sizes and markets
---
Expense Ratios by Asset Class
Operating Expense Ratio = Operating Expenses ÷ Gross Revenue
Multifamily (5+ units): 35-45%
- Lower end: Newer Class A properties with efficient systems
- Higher end: Older Class B/C with deferred maintenance
Office: 35-55%
- Varies significantly based on lease structure (NNN vs. Gross)
- Class A downtown towers: typically 40-50%
Retail: 30-40% typical range (can reach 60-80% for certain formats)
- Heavily dependent on NNN vs. Gross lease structure
Industrial: 15-25%
- Lowest of all commercial property types
- Simple building systems and lower maintenance requirements
NOI Relationship: NOI % = 100% - OER
---
Lease Structure Impact on NOI
Triple Net (NNN) Lease:
- Tenant Pays: Base rent + property taxes + insurance + CAM
- Example: $20.00/SF base rent + $3.25/SF NNN expenses
- Landlord Risk: Low (tenant bears cost increases)
- NOI Impact: Base rent provides net return above all expenses
- Common In: Retail, single-tenant commercial
Full-Service Gross (FSG) Lease:
- Tenant Pays: Single all-inclusive rent rate
- Landlord Pays: All operating expenses, including utilities
- Landlord Risk: High (expense increases directly reduce NOI)
- NOI Impact: Expense increases directly reduce NOI
- Common In: Office buildings (especially older Class B/C)
Modified Gross (MG) Lease:
- Structure: Base year expense stop with tenant paying increases
- First Year: Operates like FSG lease
- Subsequent Years: Tenant pays pro-rata share of increases above base year
- Landlord Risk: Moderate (protected from increases after Year 1)
- NOI Impact: Stabilized NOI as increases passed through
- Common In: Office buildings, suburban flex space
Risk Distribution Summary:
- NNN = Lowest landlord risk, most stable NOI
- Modified Gross = Moderate landlord risk, protected after Year 1
- FSG = Highest landlord risk, variable NOI
---
Tenant Quality Analysis
Credit Ratings:
- Investment Grade: BBB-/Baa3 or higher (lowest default risk)
- Below Investment Grade: BB+/Ba1 or lower (higher risk, higher cap rates)
Impact on Property Valuation:
- Higher credit tenants = lower cap rates = higher property values
- Creditworthiness drives debt amount, interest rates, loan terms
- Building creditworthiness = Sum of tenant credit ratings + lease terms
Lease Review Checklist:
- [ ] Rent rolls and tenant mix
- [ ] Lease expiration schedule (rollover risk)
- [ ] Rent escalations (fixed % vs. CPI)
- [ ] Operating expense provisions (who pays what)
- [ ] Tenant improvement allowances
- [ ] Renewal options and terms
- [ ] Credit rating (third-party assessment)
- [ ] Lease term remaining
- [ ] Renewal probability (historical rates)
- [ ] Tenant sales (retail): Sales per SF vs. peers
- [ ] Rent as % of sales (retail): Typically 5-15%
- [ ] Co-tenancy clauses (can tenant terminate if anchor leaves?)
- [ ] Termination rights (early exit options)
---
Value-Add Underwriting
Rent Bump Projections:
ROI-Based Method:
- Unit upgrades costing $6,000 should generate $110/month premium for 22% ROI
- Calculation: ($110 × 12) ÷ $6,000 = 22%
Payback Method:
- Capital expenditures recovered within 18-24 months
- Example: $1,200 upgrade requires minimum $50/month increase
- Payback: $1,200 ÷ $50 = 24 months
Rent Growth Components: 1. Organic Growth: Baseline market rent escalation (all units) 2. Renovation Premium: Additional increase from upgrades (phased as renovations complete)
Renovation Cost Per Unit:
- Light: $6,000-$12,000 (paint, flooring, fixtures, appliances)
- Moderate: $15,000-$25,000 (kitchens, bathrooms, flooring, appliances)
- Heavy: $30,000-$50,000+ (full gut, systems upgrades)
Stabilization Timeline:
- Definition: Renovations/operational improvements complete, property reaches target occupancy, rent levels, expense ratios
- Typical Duration: 2-3 years for phased value-add programs
- Conservative Underwriting: Assume minimum 2 years to catch up under-rented units
Value-Add Pro Forma Approach: 1. Apply average renovation budget per unit (when scope identical) 2. Apply average monthly rent premium (e.g., $300/month) upon unit return to service 3. Phase renovation premium over time as renovations complete 4. Layer organic rent growth on top of all units annually 5. Model lease-up pace conservatively (not all leases expire simultaneously)
---
Class A/B/C Return Profiles
Class A Properties:
- Age: Typically built within last 15 years
- Location: CBD or densely populated urban centers in major cities
- Characteristics: Trophy properties, highest-quality finishes, top amenities, professional management, accessible via transit, high-income tenants, low vacancy
- Rents: Command highest rents in area
- Return Profile: Lower risk, more stable, lower returns; "safest" portfolio addition
- Investor Profile: Core investors, institutions, REITs seeking stable income
Class B Properties:
- Age: Typically 10-20 years old
- Location: Slightly less prime than Class A
- Characteristics: Newer and well-maintained, average/above-average finishes, fair to good visual appeal, fair parking, functional HVAC, decent management
- Rents: At-market rents
- Return Profile: Moderate risk, value creation opportunity through improvements
- Investor Profile: Value-add investors seeking 15-20% IRR
Class C Properties:
- Age: Often 20+ years old
- Location: Farther from desirable areas
- Characteristics: Functional but deferred maintenance, need updating, often lack elevators/central AC, smaller building size
- Rents: Below-market (attract tenants seeking most affordable)
- Return Profile: Higher risk, higher potential returns through repositioning
- Investor Profile: Opportunistic investors seeking 20%+ IRR, willing to take renovation/lease-up risk
- Repositioning: Can renovate C to B, but unlikely to reach A (location/size constraints)
Investment Strategy Implications:
- Class A: Core strategy, income-focused, lower leverage
- Class B: Core-plus/value-add, balanced risk-return
- Class C: Opportunistic, higher leverage, execution risk
Note: Classifications are subjective and market-relative. Class A in mid-size city may be Class B in major metro.
---
Short-Term Rental (STR)
Revenue Modeling
Core Revenue Formula:
Revenue = ADR × Occupancy Rate × 365 Days - Platform FeesWhere:
- ADR = Average Daily Rate (nightly price)
- Occupancy Rate = % of nights booked annually
Revenue Projection Example:
- ADR: $180/night
- Occupancy: 70%
- Days: 30 (monthly)
- Projected Monthly Revenue = $180 × 0.70 × 30 = $3,780
Platform Fees (2026):
- Airbnb Single-Fee Model (as of Oct 2025): Hosts pay 15.5% service fee; guests see full price
- Previous Split-Fee Model (still available for some): Hosts 3% + guests 14.1-16.5%
- Note: Independent hosts on native platform may access split-fee; PMS users automatically moved to 15.5%
- Pricing Markup to Offset 15.5% Fee: ~18.34%
---
Key AirDNA Metrics
RevPAR (Revenue Per Available Rental):
- Definition: Most important STR performance metric
- Calculation Method 1: ADR × Occupancy Rate
- Calculation Method 2: Total Revenue ÷ Total Available Nights
- Importance: Single metric accounting for both pricing power and vacancy
ADR (Average Daily Rate):
- Definition: Average nightly rate earned across all bookings
- Recent Trends: National ADR jumped 24.88% YoY (May 2024-May 2025)
- Historical Context: 30%+ annual growth from 2020-2024
Occupancy Rate:
- National Average: ~54.9% (significant seasonal variation)
- Strong Markets: 55%+ occupancy with strong ADR growth
- Target Benchmark: 60%+ for profitability
Revenue Potential:
- STRs can generate 30% more annually than LTRs
- Average monthly STR earnings: ~$4,300
- Comparison: Property renting for $2,000/month as LTR might generate $4,500/month at 80% STR occupancy ($150/night × 80% × 30 days)
2026 Profitability Targets:
- RevPAR: $11,990 annually
- Occupancy: 60%
- Gross Margin: 85%
2026 National Forecast:
- Occupancy expected to ease by 1%
- ADR forecast to strengthen by 1.5%, with further acceleration in 2027
---
STR-Specific Expenses
Furnishing & Setup:
- Initial Furnishing: $15,000-$25,000 (regular property)
- Luxury/Larger Properties: $25,000-$50,000+
- Replacement Cycle: Every 5+ years
- Includes: Furniture, decor, kitchenware, linens, towels, appliances
Cleaning:
- Small Spaces: $65 per turnover
- Large Homes (6+ bedrooms): $200+ per turnover
- Scope: Far beyond basic cleaning—laundry, staging, restocking essentials
- Variable Cost: Tied to occupancy; more bookings = more cleaning costs
Management Fees:
- Property Management: 20-25% of revenue
- Onboarding Fees: $300-$1,000 per property
- Includes: Guest communication 24/7, booking management, pricing optimization, maintenance coordination
Platform Fees:
- Airbnb Host Fee (2026): 15.5% of booking (single-fee model)
- Split-Fee Alternative: 3% host + 14-15% guest (limited availability)
Utilities:
- Higher than LTR due to guest behavior (don't optimize lights/AC/heating)
- Budget 1.5-2x normal residential usage
Supplies & Restocking:
- Ongoing: Toilet paper, paper towels, soap, shampoo, coffee, cleaning supplies
- Per-booking costs add up with high turnover
Maintenance:
- Higher frequency than LTR due to guest turnover and wear
- Immediate response required (can't wait for lease end)
- HVAC, plumbing, appliance repairs more frequent
Operating Expense Summary:
- LTR Operating Costs: ~15% of revenue
- STR Operating Costs: ~50-60% of revenue
---
Regulatory Risk Checklist
Permits & Licensing:
- [ ] Research local registration requirements
- [ ] Budget for licensing fees ($50-$500+ annually)
- [ ] Ensure compliance with display requirements (license numbers on listings)
- [ ] Timeline example: Austin effective July 1, 2026—platforms must display valid license numbers
Zoning:
- [ ] Verify zoning allows STR at specific property address
- [ ] Check if city limits STRs to certain zones
- [ ] Review primary residence requirement (60%+ of cities with STR rules now require renting only primary home)
- [ ] Check occupancy limits (days per year caps: 90-180 days common)
HOA Restrictions:
- [ ] CRITICAL: Request and read CC&Rs before purchase
- [ ] Verify current HOA rules allow STRs
- [ ] Assess risk of future HOA vote to ban STRs
- [ ] Example: Phoenix host bought condo for Airbnb; HOA voted to ban all STRs 6 months later
Tax Registration:
- [ ] Identify transient occupancy tax (TOT) or hotel tax requirements
- [ ] Determine collection and remittance requirements
- [ ] Check if platform collects/remits automatically or host must comply
Major City Restrictions:
- New York, San Francisco, Seattle: Strict occupancy limits, zoning restrictions, permit requirements
- Can make STRs financially unviable in these markets
Risk Mitigation Process: 1. Research city/county STR ordinances 2. Verify zoning allows STR at specific property address 3. Obtain and review HOA CC&Rs in full 4. Check permit/license requirements and costs 5. Understand tax obligations 6. Budget for compliance costs (legal, accounting, licenses) 7. Monitor regulatory changes (many cities tightening rules in 2025-2026)
---
STR vs LTR Comparison
Income Potential:
- STR: 30% higher annual revenue on average
- STR Monthly Average: $4,300
- Example: $2,000/month LTR vs. $4,500/month STR (at $150/night, 80% occupancy)
Operating Costs:
- LTR: ~15% of revenue
- STR: ~50-60% of revenue
Management Intensity:
- LTR: Passive after tenant placement; occasional maintenance; annual renewals
- STR: "Hospitality work, not passive investing"
- 24/7 guest communication
- Cleaning after every guest
- Constant restocking
- Immediate maintenance responses
- Dynamic pricing management
Occupancy & Stability:
- LTR: 90%+ occupancy with year-long leases; predictable income
- STR: 54.9% average occupancy; significant seasonal volatility; revenue uncertainty
Regulatory Risk:
- LTR: Minimal regulatory risk; established landlord-tenant law
- STR: High regulatory risk (changing ordinances, HOA bans, zoning restrictions, primary residence requirements)
Break-Even Analysis Factors: 1. STR restrictions are deal-breakers—verify legality before analyzing numbers 2. Use conservative occupancy assumptions with local market data (AirDNA) 3. Include all STR-specific expenses 4. Value management time honestly if self-managing 5. Model high/low seasons separately
Hybrid Strategy:
- Run as STR during peak tourist seasons
- Switch to monthly rentals during slow periods
- Maximizes income while reducing vacancy risk
Decision Matrix:
Choose STR if:
- Market has strong tourism/business travel demand
- STRs are legal and HOA permits
- Property can command premium nightly rates
- You have time for active management OR can afford 20-25% management fees
- You can absorb seasonal income volatility
Choose LTR if:
- STR restrictions prohibit or severely limit operations
- Prefer passive income and stability
- Market rental demand is strong
- Property location doesn't support premium STR rates
- You want 90%+ occupancy certainty
---
Dynamic Pricing Tools
PriceLabs:
- Launched 2014; trusted by 5,000+ hosts
- Strengths: Top-tier customization options
- Set rules based on occupancy and booking window
- Customize pricing for orphan days (single-night gaps)
- Cascading discounts and length-of-stay restrictions
- Weakness: Interface less polished
Beyond (formerly Beyond Pricing):
- Powers rates for 340,000+ listings in 7,500+ cities
- Algorithm Considers: Special events, seasonality, day of week
- Strength: Massive data set for market comparisons
Wheelhouse:
- Results: Users report ~40% profitability boost
- Updates: At least daily rate adjustments
- Strength: Real-time market and competitor data analysis
Core Algorithm Factors (across all tools):
- Real-time market demand
- Seasonality patterns
- Local events (conferences, concerts, festivals, sporting events)
- Day of week (weekday vs. weekend)
- Current occupancy rate (last-minute discounts to fill gaps)
- Competitor pricing
- Booking lead time (last-minute vs. advance)
Benefits:
- Eliminates manual pricing guesswork
- Captures demand spikes during events
- Adjusts rates as occupancy fills
- Maximizes RevPAR across season
Alternative: Airbnb Smart Pricing Tool (free, built-in, less sophisticated)
---
Land & Development
Development Pro Forma
Core Pro Forma Equation:
Total Development Cost = Land Cost + Hard Costs + Soft Costs + Financing CostsCompare to:
- Projected Sale Value (for-sale developments)
- Stabilized NOI ÷ Cap Rate (income-producing assets)
Main Components: 1. Project timelines (construction phases, absorption periods, lease-up/sell-out schedules) 2. Hard costs (physical construction) 3. Soft costs (indirect expenses) 4. Absorption rates (speed of units/space sold or leased) 5. Revenue projections (sale prices or rental income) 6. Structured financing (construction loans, mezzanine debt, equity) 7. Desired return parameters (IRR, equity multiple, profit margin targets)
---
Hard vs Soft Costs
Hard Costs (70-80% of total budget):
- Physical materials: steel, concrete, wood, glass
- Building systems: HVAC, plumbing, electrical
- Interior finishes and furnishings
- Labor: contractor and subcontractor wages
- Equipment rental
- Site work: grading, utilities, landscaping
Characteristics:
- Tangible, physical components
- Incurred during construction phase only
- Easier to estimate and finance
Soft Costs (20-30% of total budget):
- Architectural and engineering services
- Permits and regulatory fees
- Legal and financial services (attorneys, accountants)
- Insurance (builder's risk, liability)
- Consulting services (environmental, geotechnical, traffic)
- Project management and owner's representative
- Marketing and leasing costs
- Property taxes during construction
- Impact fees and development fees
Characteristics:
- Intangible, non-physical expenses
- Incurred throughout entire project lifecycle (often before construction begins)
- Difficult to finance (not tangible assets)
- Often financed through construction loan or developer equity
Financing Considerations:
- Loan costs and financing fees typically NOT capitalized into project basis
- Many developers finance soft costs through multifamily construction loans
- Soft costs precede construction, requiring upfront capital
---
Absorption Rate Analysis
Definition: Difference between occupied space at beginning and end of time period, measuring rate at which inventory is sold or leased.
Calculation:
Absorption Rate = Number of Homes/Units Sold (or Leased) in Time Period ÷ Number of Homes/Units AvailableExample:
- 50 homes sold in 3 months
- 200 homes available
- Absorption Rate = 50 ÷ 200 = 25% quarterly (or 100% annualized)
Market Indicators:
- 20% or Higher: Seller's market; high demand
- 15-20%: Neutral market
- Below 15%: Buyer's market; lower demand
Developer Application:
- High absorption rates justify new development projects
- Low absorption rates lead to slowdown in new construction
- Absorption trends guide planning for development type/amount
Key Factors Influencing Absorption:
- Economic conditions and job growth
- Population growth and demographic changes
- Interest rates and financing availability
- Seasonality and market cycles
- New construction and competitive supply
Pro Forma Integration:
- Determines sell-out or lease-up timeline
- Directly impacts cash flow waterfall modeling
- Affects carrying costs and construction loan duration
- Used to model phased delivery of inventory
---
Entitlement Risk Assessment
Legal Permissibility Criteria (First test of Highest & Best Use):
- [ ] Is proposed use legal under existing zoning?
- [ ] Compliance with building codes, environmental regulations, bylaws
- [ ] Reasonable probability of securing:
- [ ] Zoning variances or changes
- [ ] Building permits
- [ ] Environmental clearances
- [ ] Subdivision approvals
Risk Assessment Factors:
- Local Planning Climate: Pro-growth vs. slow-growth politics
- Public Opposition: NIMBY (Not In My Backyard) resistance
- Environmental Constraints: Wetlands, endangered species, contamination
- Infrastructure Capacity: Water, sewer, traffic capacity
- Approval Timeline: Uncertainty creates holding cost risk
- Cost of Entitlement: Legal fees, studies, impact fees ($10,000-$50,000+ per unit)
Risk Mitigation:
- [ ] Pre-application meetings with planning staff
- [ ] Early environmental and engineering studies
- [ ] Community outreach before formal applications
- [ ] Contingent purchase contracts (close after entitlement)
- [ ] Entitlement consultants and land use attorneys
Value Impact:
- Entitled land trades at 2-3x premium vs. unentitled
- Entitlement risk = higher return requirements (20-30% IRR)
---
Highest and Best Use Framework
Definition: The reasonably probable and legal use of vacant land or improved property that is physically possible, appropriately supported, financially feasible, and results in highest value.
The Four Criteria Framework:
1. Legal Permissibility
- Proposed use must be legal under existing zoning/regulations
- OR reasonable probability of securing entitlements (variances, rezoning)
2. Physical Possibility
- Land can physically support proposed use
- Topography, soil conditions, size, shape, access
- Utilities and infrastructure availability
3. Financial Feasibility
- Development can be completed in financially sound manner
- Projected revenues exceed costs
- Achieves market rate returns for risk level
4. Maximum Productivity
- Among all feasible options, which generates highest value/return?
- Weighs return relative to risk
- Considers absorption timelines, capital needs, exit strategy
Analysis Process: 1. Identify all legally permissible uses 2. Screen for physical possibility 3. Analyze financial feasibility of remaining options 4. Compare remaining uses for highest value/lowest risk combination
Modern Analysis Tools:
- GIS (Geographic Information Systems): Layer topography, zoning, roads, environmental constraints
- Spatial analysis of comparable developments
Risk-Adjusted Returns:
- Compare uses seeking highest return RELATIVE to risk
- Higher-risk uses (luxury condos) require higher IRR hurdles than lower-risk uses (workforce apartments)
---
Cost Escalation Modeling
Escalation Formula:
Escalation = Actual Costs - Estimated CostsCommon Approach (Oversimplified):
- General escalation: 3-5% per annum
- Based on broader inflation rates
- Problem: Assumes all trades escalate uniformly (rarely true)
Trade-Specific Approach (More Accurate):
- Each trade (concrete, steel, electrical, plumbing) escalates at different rates
- Requires trade-by-trade cost indexing
- Accounts for material supply chain disruptions, labor shortages, commodity swings
Cost Index Methods:
Input-Based Indices:
- Track changes in cost of inputs (labor and materials basket)
- Example: Engineering News Record (ENR) Building Cost Index (BCI) and Construction Cost Index (CCI)
Output-Based Indices:
- Track changes in completed project costs
- Reflect actual market pricing
Timeline-Based Escalation Modeling:
- Concrete work scheduled for Month 3: Apply 3 months of concrete cost escalation
- Electrical work scheduled for Month 9: Apply 9 months of electrical escalation
- More accurate than blanket percentage
Example:
- Concrete scheduled Month 3 → Apply 3 months escalation
- Electrical scheduled Month 9 → Apply 9 months escalation
Best Practices: 1. Use trade-specific escalation rates, not blanket percentages 2. Tie escalation to project schedule (when work occurs) 3. Build contingencies for escalation uncertainty (5-10% of hard costs) 4. Update estimates quarterly during pre-construction 5. Lock in pricing through GMP (Guaranteed Maximum Price) contracts where possible 6. Monitor commodity indices (steel, lumber, fuel) for leading indicators
---
Key Metrics Summary
Residential:
- 1% Rule: Monthly rent ≥ 1% of purchase price
- 50% Rule: Operating expenses = ~50% of gross rent
- 70% Rule: Max purchase = (ARV × 70%) - Repair Costs
- Multifamily (2-4) OER: 35-45%
- Property Management: 8-10% of gross rent
Commercial:
- Price Per Door: <$25,000 typically indicates good cap rate
- Multifamily (5+) OER: 35-45%
- Office OER: 35-55%
- Retail OER: 30-40% (can reach 60-80%)
- Industrial OER: 15-25%
STR:
- Revenue = ADR × Occupancy × 365 - Platform Fees
- Airbnb Host Fee: 15.5%
- STR Operating Costs: 50-60% of revenue
- Management Fees: 20-25% of revenue
- Target Occupancy: 60%+
- RevPAR Target (2026): $11,990 annually
Development:
- Hard Costs: 70-80% of total budget
- Soft Costs: 20-30% of total budget
- Absorption ≥20%: Seller's market
- Absorption 15-20%: Neutral market
- Absorption <15%: Buyer's market
- Cost Escalation Contingency: 5-10% of hard costs
Related skills
FAQ
Which property types does real-estate-investment support?
Single-family and small multifamily, BRRRR, house hacks, large multifamily, commercial, short-term rentals, and land or development.
What output formats can it produce?
Spreadsheet-ready tables with formulas, a decision framework with a go/no-go recommendation, runnable Python code, or a full investor report.