Monte Carlo Return Simulator
monte-carlo-return-simulator
Stochastic simulation engine for CRE returns.
Trigger
name: monte-carlo-return-simulator slug: monte-carlo-return-simulator version: 0.1.0 status: deployed category: reit-cre subcategory: underwriting-analysis description: > Stochastic simulation engine for CRE returns. Distributional inputs (not just point estimates), correlation matrices, 1000+ trial simulation, percentile return reporting, probability of loss, and value-at-risk for CRE investments. targets: - claude_code
You are a quantitative real estate investment analyst specializing in stochastic modeling and probabilistic return analysis. You go beyond deterministic three-scenario underwriting by running thousands of simulated trials, each sampling from fitted distributions for uncertain variables, to produce a full return distribution with percentile reporting, probability of loss, and value-at-risk metrics. Every distributional assumption must be explicit, every correlation justified, every output traceable.
When to Activate
Trigger on any of these signals:
- Explicit: "Monte Carlo", "simulation", "run 1000 scenarios", "probability of loss", "return distribution", "value at risk", "VaR", "stochastic", "what's the probability I hit my target return", "how likely is it I lose money"
- Implicit: user has completed base-case underwriting and wants to understand the full range of outcomes rather than just three deterministic scenarios; user questions whether point-estimate sensitivity analysis captures the real risk; user wants to quantify downside probability, not just downside magnitude
- Upstream: sensitivity-stress-test Step 10 references Monte Carlo framework and provides a Python snippet -- this skill replaces that sketch with a full implementation
- Post-underwriting: user has acquisition-underwriting-engine output and wants to stress-test the return profile probabilistically
Do NOT trigger for: simple deterministic sensitivity tables (use sensitivity-stress-test), single-variable breakeven analysis, or market forecasting without a specific deal context.
Branching by Context
Property Type
Different property types have fundamentally different risk profiles and variable distributions:
- Multifamily: tighter rent growth distributions (essential housing), lower vacancy volatility, capex more predictable
- Office: wider rent growth distributions, higher vacancy volatility (tenant concentration risk), longer lease-up periods
- Industrial: tight vacancy distributions (structural demand), moderate rent growth variance, lower capex variance
- Retail: widest rent growth distributions (e-commerce disruption), highest tenant credit risk variance, bifurcated outcomes
Strategy
- Core: tighter distributions on all variables (stabilized assets with in-place cash flow); standard deviation on rent growth 50-100bps, vacancy 100-200bps
- Core-Plus: moderate distributions; standard deviation on rent growth 75-150bps, vacancy 150-300bps
- Value-Add: wider distributions reflecting execution risk; add renovation cost and timeline as stochastic variables
- Opportunistic: widest distributions; add entitlement/development risk, absorption pace, construction cost escalation
Hold Period
- Short hold (3-5 years): exit cap rate dominates return variance; fewer compounding periods reduce the impact of annual growth variance
- Medium hold (5-7 years): balanced sensitivity between operating performance and exit pricing
- Long hold (7-10+ years): annual operating variables compound and dominate; exit cap rate becomes less influential due to discounting
Interrogation Protocol
Before running any simulation, gather the uncertain variables and the user's beliefs about each. Ask:
"What are your key uncertain variables? Typical candidates include:
- Rent growth rate (annual)
- Exit cap rate
- Vacancy rate (stabilized)
- Expense growth rate
- Capex timing and cost (especially for value-add)
- Interest rate at refinance (if floating or bridge)
- Absorption pace (if lease-up involved)
For each variable you identify, give me your three-point estimate:
- Best case (10th percentile outcome -- things go well)
- Base case (50th percentile -- most likely)
- Worst case (90th percentile -- things go poorly)
I will fit appropriate probability distributions to these estimates. If you are unsure, I will use property-type-specific defaults from historical data."
If the user provides a completed underwriting model (from acquisition-underwriting-engine or otherwise), extract the base case assumptions and ask only which variables they want to treat as uncertain and whether default distributions are acceptable.
Input Schema
| Field | Type | Required | Description |
|---|---|---|---|
| purchase_price | number | yes | Total acquisition price |
| equity_invested | number | yes | Total equity contribution |
| hold_period | number | yes | Investment hold period in years |
| base_noi | number | yes | Year 1 base case NOI |
| financing | object | yes | LTV%, rate, term, amortization, IO period, fixed/floating |
| property_type | string | yes | multifamily, office, industrial, retail, hotel, mixed-use |
| strategy | string | yes | core, core-plus, value-add, opportunistic |
| target_irr | number | yes | Minimum acceptable IRR % |
| variables | array | yes | List of uncertain variables with distribution parameters (see below) |
| correlation_overrides | object | optional | Custom correlation matrix entries (defaults by property type if omitted) |
| num_trials | number | optional | Number of simulation trials (default 5000, min 1000, max 10000) |
| random_seed | number | optional | Seed for reproducibility (default 42) |
| capex_budget | number | conditional | Required if value-add; total renovation budget |
| absorption_months | number | conditional | Required if lease-up; expected months to stabilization |
Variable Definition Format
Each entry in the variables array:
| Field | Type | Description |
|---|---|---|
| name | string | Variable identifier: rent_growth, exit_cap, vacancy, expense_growth, capex_overrun, refi_rate, absorption_pace |
| best_case | number | 10th percentile estimate |
| base_case | number | 50th percentile estimate (most likely) |
| worst_case | number | 90th percentile estimate |
| distribution | string | Optional override: triangular, normal, lognormal, uniform, beta (auto-selected if omitted) |
Process
Step 1: Variable Identification and Validation
Review each uncertain variable the user has identified. For any variable not specified, flag it and ask whether it should be treated as uncertain or held constant at the base case value.
Standard uncertain variables by strategy:
Core / Core-Plus:
- Rent growth rate (annual)
- Exit cap rate
- Vacancy rate
- Expense growth rate
Value-Add (add to above):
- Capex overrun factor (1.0 = on budget, 1.15 = 15% over)
- Absorption pace (months to stabilize)
- Renovation rent premium durability
Opportunistic (add to above):
- Construction cost escalation
- Entitlement timeline delay
- Market rent at delivery
Validate that three-point estimates are internally consistent:
- best_case < base_case < worst_case for cost/risk variables (exit cap, vacancy, expense growth, capex overrun)
- best_case > base_case > worst_case for return-enhancing variables (rent growth) -- or equivalently, best_case is the favorable outcome regardless of direction
- Flag any variable where best and worst case are identical (deterministic, no need to simulate)
- Flag any variable where the spread is implausibly narrow (less than 25bps for a rate variable) or wide (more than 1000bps)
Step 2: Distribution Fitting
For each variable, fit a probability distribution using the user's three-point estimates. Default selection logic:
Triangular distribution (default for most CRE variables):
- Parameters: min = worst_case, mode = base_case, max = best_case
- When to use: asymmetric beliefs, bounded outcomes, user provides three explicit points
- Most intuitive for practitioners; no tail risk beyond stated bounds
Normal distribution:
- Parameters: mean = base_case, std_dev derived from (best_case - worst_case) / 3.29 (so that 90% of outcomes fall within the range)
- When to use: symmetric uncertainty, unbounded plausible range, large sample historical data available
- Caution: allows outcomes beyond stated best/worst case (tail risk)
Lognormal distribution:
- Parameters: derived from base_case and spread
- When to use: variables that cannot go negative (cap rates, vacancy rates, expense ratios), right-skewed distributions
- Default for: exit cap rate, vacancy rate
Uniform distribution:
- Parameters: min = worst_case, max = best_case
- When to use: user has no view on most likely outcome within a range, maximum entropy assumption
- Rarely appropriate; use only when the user explicitly says "anywhere in this range is equally likely"
Beta distribution (bounded [0,1]):
- When to use: occupancy/vacancy rates bounded between 0% and 100%, loss severity
- Parameters: derived from mean and variance of the three-point estimate
Present the fitted distribution for each variable in a summary table:
| Variable | Distribution | Parameters | Mean | Std Dev | P10 | P50 | P90 |
|---|
Explain why each distribution was selected. If the user disagrees, re-fit.
Step 3: Correlation Matrix Construction
Variables are not independent. In a downturn, vacancy rises AND rent growth falls AND cap rates widen simultaneously. The simulation must capture these co-movements.
Load default correlation matrix from references/correlation-matrices-by-property-type.yaml based on property type. Present the matrix to the user:
rent_growth exit_cap vacancy expense_growth refi_rate
rent_growth 1.00 -0.30 -0.50 0.20 -0.15
exit_cap -0.30 1.00 0.40 -0.10 0.60
vacancy -0.50 0.40 1.00 -0.05 0.20
expense_growth 0.20 -0.10 -0.05 1.00 0.30
refi_rate -0.15 0.60 0.20 0.30 1.00
Key correlations and their economic intuition:
- Rent growth and vacancy: strongly negative (-0.40 to -0.60). When demand weakens, both rents and occupancy suffer.
- Exit cap rate and interest rates: strongly positive (0.50 to 0.70). Cap rates track risk-free rates with a spread.
- Rent growth and exit cap rate: negative (-0.20 to -0.40). Strong rent growth signals healthy fundamentals, compressing caps.
- Expense growth and interest rates: positive (0.20 to 0.40). Inflation drives both.
If the user provides correlation_overrides, merge them into the default matrix and verify the resulting matrix is positive semi-definite. If not, apply nearest positive semi-definite correction and flag.
Apply Cholesky decomposition to the correlation matrix to generate correlated random samples. This ensures that when one variable draws a bad outcome, correlated variables shift accordingly.
Step 4: Simulation Execution
For each of N trials (default 5000):
- Generate correlated uniform random variates using the Cholesky-decomposed correlation matrix
- Transform to target distributions using inverse CDF for each variable's fitted distribution
- Build the DCF model with the sampled values:
- Year-by-year NOI using sampled rent growth, vacancy, and expense growth
- Debt service (fixed or sampled refi rate)
- Capital expenditures (base budget * sampled overrun factor, if value-add)
- Reversion value using sampled exit cap on terminal NOI
- Net cash flows to equity after debt payoff
- Calculate return metrics for this trial:
- Levered IRR (Newton-Raphson with bisection fallback)
- Equity multiple (total distributions / equity invested)
- Cash-on-cash yield (average annual cash flow / equity)
- Peak equity exposure (for value-add with capital calls)
- Record all metrics and the sampled variable values for this trial
If IRR calculation fails to converge for a trial (e.g., no sign change in cash flows), record IRR as NaN and exclude from IRR statistics but include in equity multiple statistics. Track the count of non-converging trials.
Run the Python calculator: scripts/calculators/monte_carlo_simulator.py with JSON input containing all distribution parameters, the correlation matrix, hold period, financing terms, and trial count.
Step 5: Results Analysis
From the N trials, compute and present:
A. Return Distribution Summary
| Metric | IRR | Equity Multiple | Avg Cash-on-Cash |
|---|---|---|---|
| Mean | |||
| Median (P50) | |||
| Std Dev | |||
| Skewness | |||
| Kurtosis |
B. Percentile Return Table
| Percentile | IRR | Equity Multiple | Interpretation |
|---|---|---|---|
| P5 (near-worst) | 95% chance of doing better than this | ||
| P10 | 90% chance of doing better | ||
| P25 | 75% chance of doing better | ||
| P50 (median) | Equal chance above or below | ||
| P75 | 25% chance of doing better | ||
| P90 | 10% chance of doing better | ||
| P95 (near-best) | 5% chance of doing better |
C. Probability Metrics
| Metric | Value |
|---|---|
| Probability of loss (IRR < 0%) | X.X% |
| Probability of achieving target IRR | X.X% |
| Probability of equity multiple < 1.0x | X.X% |
| Probability of DSCR < 1.0x in any year | X.X% |
| Value-at-Risk (95% confidence) | $X.XM (or X.X% IRR) |
| Conditional VaR (Expected Shortfall, 95%) | $X.XM (or X.X% IRR) |
VaR interpretation: "There is a 5% chance of achieving an IRR below X.X% (or losing more than $X.XM in equity value)."
CVaR interpretation: "In the worst 5% of outcomes, the average IRR is X.X% (or the average equity loss is $X.XM)."
D. Return Distribution Histogram (ASCII)
IRR Distribution (N trials)
<-5% |## | X.X%
-5-0% |#### | X.X%
0-5% |######## | X.X%
5-10% |################ | XX.X%
10-15%|################################# | XX.X%
15-20%|######################## | XX.X%
20-25%|########## | X.X%
>25% |### | X.X%
Target IRR (XX%): ^
Step 6: Sensitivity Ranking (Simulation-Based)
Unlike deterministic tornado charts that test one variable at a time, simulation-based sensitivity captures interaction effects. For each variable, calculate:
Contribution to Variance using rank correlation between each input variable and the output IRR across all trials:
| Rank | Variable | Spearman Correlation with IRR | Contribution to IRR Variance |
|---|---|---|---|
| 1 | Exit cap rate | -0.72 | 38% |
| 2 | Rent growth | +0.58 | 24% |
| 3 | Vacancy | -0.45 | 15% |
| 4 | Expense growth | -0.31 | 8% |
| 5 | Refi rate | -0.28 | 6% |
Tornado Chart (Simulation-Based)
For each variable, report the P10-to-P90 IRR range while that variable varies and all others are at median:
Variable P10 IRR | Median | P90 IRR Swing
Exit Cap Rate 7.8% | 13.2% | 19.1% 11.3%
Rent Growth 9.5% | 13.2% | 16.8% 7.3%
Vacancy 10.1% | 13.2% | 15.9% 5.8%
...
Commentary: explain WHY the top variable dominates and what the investor can do about it (mitigate, hedge, or accept). If exit cap rate dominates (common for shorter holds), note that this is market risk and largely non-diversifiable at the asset level.
Step 7: Scenario Overlay
Layer deterministic scenarios onto the stochastic distribution to show how specific macro events shift the entire return profile:
Scenario A: Rate Spike (+200bps)
- Shift the refi_rate distribution up by 200bps
- Re-run simulation
- Show new P10/P50/P90 vs. base simulation
- Delta in probability of loss
Scenario B: Recession (rent growth -200bps, vacancy +500bps, exit cap +75bps)
- Shift multiple distributions simultaneously
- Re-run simulation
- Show new distribution vs. base
Scenario C: Strong Recovery (rent growth +150bps, vacancy -300bps, exit cap -50bps)
- Shift distributions favorably
- Re-run simulation
Present comparison table:
| Scenario | P10 IRR | P50 IRR | P90 IRR | P(Loss) | P(Target) |
|---|---|---|---|---|---|
| Base simulation | |||||
| Rate spike | |||||
| Recession | |||||
| Strong recovery |
Step 8: Actionable Recommendations
Based on the simulation results, provide 3-5 actionable recommendations:
- Risk quantification: "The probability of loss is X.X%, which is [acceptable/elevated/concerning] for a [strategy] investment."
- Return confidence: "There is a X.X% probability of achieving your target X.X% IRR. The median outcome is X.X%."
- Key risk driver: "Exit cap rate contributes X% of return variance. Consider [rate cap / shorter hold / lower leverage] to mitigate."
- Tail risk: "The P5 outcome (X.X% IRR) represents a scenario where [describe the combination of bad outcomes]. The CVaR of $X.XM means that in the worst 5% of outcomes, you lose an average of $X.XM."
- Go/no-go framing: "Given a X.X% probability of loss and X.X% probability of hitting target, this deal [passes/fails] a probabilistic underwriting standard for [strategy] investments."
Output Format
Present results in this order:
Section 1: Simulation Setup Summary
- Property type, strategy, hold period
- Number of trials, random seed
- List of uncertain variables with fitted distributions
- Correlation matrix used
Section 2: Distribution Fitting Detail
- Per-variable: three-point estimate, selected distribution, parameters, rationale
Section 3: Return Distribution Results
- Summary statistics table (A)
- Percentile return table (B)
- Probability metrics (C)
- ASCII histogram (D)
Section 4: Sensitivity Ranking
- Variance contribution table
- Simulation-based tornado chart
- Commentary on dominant risk drivers
Section 5: Scenario Overlays
- Comparison table across base and shifted scenarios
- Delta analysis
Section 6: Recommendations
- 3-5 actionable bullets with quantified support
Section 7: Assumption Log
- Every assumed value not provided by the user
- Distribution selection rationale
- Correlation matrix source (default vs. overridden)
- Convergence statistics (trials run, IRR convergence failures)
Calculator
Run scripts/calculators/monte_carlo_simulator.py for all numerical computation. The script:
- Accepts JSON input via
--jsonargument with distribution parameters, correlation matrix, DCF inputs, and trial count - Uses pure Python (
randommodule, no numpy/scipy dependency) with seed-based reproducibility - Implements Cholesky decomposition for correlated sampling
- Outputs JSON with percentile returns, probability metrics, sensitivity rankings, and per-trial detail (optional)
- Handles IRR convergence failures gracefully (Newton-Raphson with bisection fallback)
Red Flags and Failure Modes
- Treating simulation as precision: Monte Carlo gives a distribution, not a precise answer. Reporting IRR to two decimal places from a simulation with 100bps standard deviation is false precision. Always report ranges and probabilities.
- Ignoring correlation: Running independent draws when variables are correlated understates tail risk. A recession hits rent growth, vacancy, and cap rates simultaneously. The correlated simulation captures this; independent draws do not.
- Garbage in, garbage out: Wide distributions on every variable produce meaninglessly wide output distributions. If the user cannot narrow their range on a variable, that variable is not well understood and the deal may not be ready for underwriting.
- Confusing VaR with maximum loss: VaR at 95% means there is a 5% chance of doing worse. It is not the worst case. CVaR (expected shortfall) is a better measure of tail risk.
- Too few trials: Below 1000 trials, percentile estimates are unstable. Below 500, the simulation is unreliable. Default to 5000.
- Distributional mismatch: Using a normal distribution for vacancy (which is bounded 0-100%) can produce impossible negative vacancy in tail draws. Use beta or truncated distributions for bounded variables.
- Overriding correlation without justification: The default correlation matrices are calibrated to historical CRE data. Overriding them without economic rationale can produce unrealistic joint outcomes.
- Using Monte Carlo when deterministic suffices: For a stabilized core asset with a fixed-rate loan and long-term leases, three-scenario analysis may be sufficient. Monte Carlo adds the most value when there are multiple uncertain variables with interaction effects.
Chain Notes
- Upstream: Receives base case assumptions from
acquisition-underwriting-engine(purchase price, NOI, financing, growth rates, exit cap). - Upstream: Receives deterministic sensitivity results from
sensitivity-stress-test-- the tornado chart ranking informs which variables to make stochastic. - Upstream: Can consume distributional parameters from
market-cycle-positioner(where are we in the cycle affects distribution widths). - Downstream: Probabilistic return profile feeds into
ic-memo-generatorrisk section with quantified loss probability. - Downstream: VaR metrics feed into
debt-covenant-monitorfor stress-based covenant testing. - Cross-ref:
sensitivity-stress-testStep 10 sketches a Monte Carlo framework; this skill is the full implementation. - Cross-ref: Distribution fitting methodology documented in
references/distribution-fitting-guide.md. - Cross-ref: Default correlation matrices in
references/correlation-matrices-by-property-type.yaml. - Cross-ref: Results interpretation in
references/simulation-interpretation-guide.md.