This vignette mirrors a typical situation faced by a junior buy-side analyst: a small office asset is brought to the investment committee, and the analyst is asked to:
load a standardised configuration (preset_default) into the DCF engine,
compute the main project and equity metrics (IRR, NPV, equity multiple),
compare simple debt structures (bullet vs amortising),
document credit metrics (DSCR, forward LTV),
and finally draft a concise investment memo for the managing director.
The numerical example follows the teaching case developed by Karl Delattre (CNAM ICH, Financement immobilier privé, 2020), and is encoded in the preset_default configuration shipped with the package.
The objective is not to build a full-blown investment committee paper, but to show a transparent, reproducible workflow from YAML assumptions to a stylised investment note.
In this fictional example, the junior analyst receives the following mandate:
“We are looking at a fully-let office asset with stable rents and limited capex over a 5-year hold. Use the preset_default assumptions, run the DCF, compare a bullet and an amortising loan at 30% LTV, and prepare a short note summarising returns, leverage, and key risks.”
We translate this request into three tasks:
Load the preset_default configuration and run the DCF engine.
Extract and visualise the most important indicators for investors (unlevered project IRR, leveraged equity IRR/NPV, equity cash-flows).
Document basic lender-side indicators (DSCR, forward LTV) and synthesise them into a short narrative memo.
We first read the preset_default.yml file from inst/extdata. The configuration encodes pricing, rental assumptions, indexation, capex, debt parameters and covenant guardrails.
## List of 27
## $ purchase_year : int 2020
## $ horizon_years : int 5
## $ index_rate : num 0.01
## $ opex_inflation_rate : num 0.01
## $ capex_inflation_rate : num 0.01
## $ entry_yield : num 0.065
## $ acq_cost_rate : num 0.075
## $ exit_yield_spread_bps : int -150
## $ exit_transaction_costs :List of 2
## $ ltv_base : chr "price_di"
## $ capitalized_fees : logi FALSE
## $ arrangement_fee_pct : num 0
## $ disc_method : chr "wacc"
## $ disc_rate_wacc :List of 3
## $ disc_rate_wacc_capm :List of 7
## $ disc_rate_risk_premium :List of 4
## $ disc_rate_yield_plus_growth:List of 2
## $ opex_sqm : int 60
## $ ltv_init : num 0.3
## $ rate_annual : num 0.02
## $ extra_amort_pct : num 0
## $ scr_ratio : num 0.35
## $ leasing_cost_pct : num 0.15
## $ leases :List of 1
## $ hurdle_equity_irr : num 0.055
## $ hurdle_project_irr : num 0.055
## $ hurdle_equity_npv : num 0
At this stage the analyst does not need to modify the configuration: the point of the exercise is precisely to show what the “plain vanilla” default profile looks like.
We now pass the configuration to run_case(), which:
## [1] "pricing" "all_equity" "leveraged" "comparison" "cashflows"
## [6] "config"
For convenience, we will keep direct references to the main components:
## $price_ht
## [1] 3076923
##
## $acq_cost
## [1] 230769.2
##
## $price_di
## [1] 3307692
In this example, the asset is priced as follows:
The DCF and the debt sizing use price_di as the financing base.
The financing configuration encodes the initial loan-to-value and the debt sizing convention:
## $ltv_base
## [1] "price_di"
##
## $ltv_init
## [1] 0.3
##
## $debt_init
## [1] 992307.7
##
## $equity_init
## [1] 2315385
##
## $capitalized_fees
## [1] FALSE
##
## $arrangement_fee_pct
## [1] 0
##
## $disc_method
## [1] "wacc"
##
## $disc_rate
## [1] 0.0424
##
## $disc_detail
## NULL
For the preset_default:
the LTV base is the “droits inclus” price (ltv_base = “price_di”),
initial LTV is (cfg_finance$ltv_init),
initial debt is therefore (cfg_finance$debt_init)
EUR,
initial equity ticket is (cfg_finance$equity_init)
EUR.
We can summarise the capital structure at origination in a small table:
## # A tibble: 4 × 2
## item amount
## <chr> <dbl>
## 1 Acquisition price (DI) 3307692.
## 2 Initial debt 992308.
## 3 Initial equity 2315385.
## 4 Initial LTV 0.3
From the perspective of the underlying real estate project, the key outputs are the unlevered project IRR and NPV, based on the free cash-flow profile and terminal resale value.
## # A tibble: 6 × 11
## year net_operating_income capex opex free_cash_flow sale_proceeds
## <int> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0 0 0 0 -3307692. 0
## 2 1 200000 0 0 200000 0
## 3 2 202000 0 0 202000 0
## 4 3 204020 0 0 204020 0
## 5 4 0 309090. 61818. -370908. 0
## 6 5 197715. 29657. 0 4082810. 3914752.
## # ℹ 5 more variables: discount_factor <dbl>, discounted_cash_flow <dbl>,
## # asset_value <dbl>, acquisition_price <dbl>, discounted_cf <dbl>
The last period combines:
For a quick visual check, the analyst can plot free cash-flows and sale proceeds over the life of the investment.
The unlevered metrics are stored directly in the all_equity object:
## $irr_project
## [1] 0.05924442
##
## $npv_project
## [1] 253371.2
In words:
the unlevered project IRR is 5.92%,
the unlevered project NPV at the chosen discount rate is 253,371 EUR.
For a junior analyst, this provides the first sanity check:
The comparison$summary table aggregates key metrics for three scenarios:
## # A tibble: 3 × 7
## scenario irr_equity npv_equity irr_project npv_project min_dscr
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 all_equity 0.0592 253371. 0.0592 253371. NA
## 2 debt_bullet 0.0755 351661. 0.0592 253371. -3.11
## 3 debt_amort 0.0682 314738. 0.0592 253371. -0.294
## # ℹ 1 more variable: max_ltv_forward <dbl>
From this table, the analyst can read:
To prepare an investment memo, it is often useful to reformat the table in a more readable way:
## # A tibble: 3 × 8
## scenario irr_equity npv_equity irr_project npv_project min_dscr
## <chr> <chr> <chr> <chr> <chr> <dbl>
## 1 all_equity 5.92% 253,371 5.92% 253,371 NA
## 2 debt_bullet 7.55% 351,661 5.92% 253,371 -3.12
## 3 debt_amort 6.82% 314,738 5.92% 253,371 -0.294
## # ℹ 2 more variables: max_ltv_forward <dbl>, max_ltv_fwd <chr>
The detailed debt schedules for the bullet and amortising structures are stored in comparison$details:
## # A tibble: 6 × 8
## year debt_draw interest amortization payment arrangement_fee outstanding_debt
## <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0 992308. 0 0 0 0 992308.
## 2 1 0 19846. 0 1.98e4 0 992308.
## 3 2 0 19846. 0 1.98e4 0 992308.
## 4 3 0 19846. 0 1.98e4 0 992308.
## 5 4 0 19846. 0 1.98e4 0 992308.
## 6 5 0 19846. 992308. 1.01e6 0 0
## # ℹ 1 more variable: loan_init <dbl>
## # A tibble: 6 × 8
## year debt_draw interest amortization payment arrangement_fee outstanding_debt
## <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0 992308. 0 0 0 0 992308.
## 2 1 0 19846. 190680. 210526. 0 801627.
## 3 2 0 16033. 194494. 210526. 0 607134.
## 4 3 0 12143. 198384. 210526. 0 408750.
## 5 4 0 8175 202351. 210526. 0 206398.
## 6 5 0 4128. 206398. 210526. 0 0
## # ℹ 1 more variable: loan_init <dbl>
These tables show, year by year:
The credit ratios (DSCR, interest coverage, forward LTV, debt yield) are available in the ratios tables. This is what will matter for the lender and for covenant discussions.
## # A tibble: 6 × 3
## year dscr ltv_forward
## <int> <dbl> <dbl>
## 1 0 NA 0.248
## 2 1 10.1 0.246
## 3 2 10.2 0.243
## 4 3 10.3 -0.803
## 5 4 -3.11 0.251
## 6 5 0.195 NA
## # A tibble: 6 × 3
## year dscr ltv_forward
## <int> <dbl> <dbl>
## 1 0 NA 0.248
## 2 1 0.950 0.198
## 3 2 0.959 0.149
## 4 3 0.969 -0.331
## 5 4 -0.294 0.0522
## 6 5 0.939 NA
For visual comparison, we can stack the two paths and plot DSCR and forward LTV over time (excluding year 0):
The plots make two points very clear for the analyst:
Under the bullet structure, DSCR is extremely comfortable (high coverage), with a moderate forward LTV path.
Under the amortising structure, leverage is lower but debt service is heavier, which can push DSCR closer to – or even below – typical covenant guardrails, especially if NOI underperforms.
The leveraged$cashflows table stores, among other columns, the equity cash flow (equity_cf) series used to compute the leveraged IRR:
## # A tibble: 6 × 9
## year free_cash_flow discount_factor payment interest outstanding_debt
## <int> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0 -3307692. 1 0 0 992308.
## 2 1 200000 1.04 19846. 19846. 992308.
## 3 2 202000 1.09 19846. 19846. 992308.
## 4 3 204020 1.13 19846. 19846. 992308.
## 5 4 -370908. 1.18 19846. 19846. 992308.
## 6 5 4082810. 1.23 1012154. 19846. 0
## # ℹ 3 more variables: arrangement_fee <dbl>, debt_draw <dbl>, equity_cf <dbl>
The sign convention is:
A simple bar chart gives the analyst an immediate view of the equity profile:
## $irr_equity
## [1] 0.07545018
##
## $npv_equity
## [1] 351661.3
For documentation purposes, the analyst can also recompute the equity multiple using the helper provided by the package:
## [1] 1.336641
In narrative form:
These three indicators are typically the core of the buy-side decision.
In many investment-committee settings, the junior analyst is expected not only to assess the project on an all-equity basis and under a single leverage profile, but also to explore how equity performance and credit risk evolve when leverage and repayment structures change.
In this section, we keep the same real-estate cash-flow
profile as in the base case (the Delattre teaching example encoded
in preset_default.yml), and vary only the financing
structure around four canonical variants:
The aim is to build a compact comparison grid of equity IRRs, NPVs and basic credit indicators (DSCR, forward LTV) across these financing cases.
We reuse the YAML configuration already loaded as
cfg_default, and normalise it exactly as
run_case() does. This provides a consistent bundle of
inputs for the DCF engine.
We define a small scenario grid, then loop over it using compare_financing_scenarios(). For the pure 100% equity case, we simply reuse the all_equity metrics that have already been computed.
For each scenario, we either:
reuse the already-computed all-equity metrics (for 100% equity),
or call compare_financing_scenarios() with the appropriate LTV and interest rate, and extract the bullet or amortising line from its summary table.
## # A tibble: 4 × 11
## scenario_id label ltv rate structure irr_equity npv_equity irr_project
## <chr> <chr> <dbl> <dbl> <chr> <dbl> <dbl> <dbl>
## 1 eq_100 100% … 0 0 all_equi… 0.0592 253371. 0.0592
## 2 ltv30_bullet_2 30% L… 0.3 0.02 bullet 0.0755 351661. 0.0592
## 3 ltv70_bullet_3 70% L… 0.7 0.03 bullet 0.122 380329. 0.0592
## 4 ltv70_amort_2_5 70% L… 0.7 0.025 amort 0.0860 364940. 0.0592
## # ℹ 3 more variables: npv_project <dbl>, min_dscr <dbl>, max_ltv_fwd <dbl>
Finally, we format the comparison table for direct inclusion in an investment note or slide deck.
## # A tibble: 4 × 11
## scenario_id label ltv rate structure irr_equity npv_equity irr_project
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 eq_100 100% … 0% n/a all_equi… 5.92% 253,371 5.92%
## 2 ltv30_bullet_2 30% L… 30% 2.0% bullet 7.55% 351,661 5.92%
## 3 ltv70_bullet_3 70% L… 70% 3.0% bullet 12.24% 380,329 5.92%
## 4 ltv70_amort_2_5 70% L… 70% 2.5% amort 8.60% 364,940 5.92%
## # ℹ 3 more variables: npv_project <chr>, min_dscr <dbl>, max_ltv_fwd <chr>
Before writing the memo, it is useful to consolidate the most important figures in a single table that can be copy-pasted into a presentation or internal note.
## # A tibble: 15 × 2
## item value
## <chr> <chr>
## 1 Acquisition price (DI) 3,307,692
## 2 Initial LTV 30.0%
## 3 Unlevered project IRR 5.92%
## 4 Unlevered project NPV 253,371
## 5 Leveraged equity IRR (30% LTV, bullet) 7.55%
## 6 Leveraged equity NPV (30% LTV, bullet) 351,661
## 7 Leveraged equity IRR (70% LTV, bullet) 7.55%
## 8 Leveraged equity NPV (70% LTV, bullet) 351,661
## 9 Leveraged equity IRR (70% LTV, amortising) 6.82%
## 10 Leveraged equity NPV (70% LTV, amortising) 314,738
## 11 Minimum DSCR (bullet) -3.115
## 12 Maximum forward LTV (bullet) 25.1%
## 13 Minimum DSCR (amortising) -0.294
## 14 Maximum forward LTV (amortising) 19.8%
## 15 Equity multiple (bullet) 1.34
The junior analyst can now translate the table into a short, structured commentary. The text below is only a template; in practice, it can be refined and expanded depending on the audience:
Deal summary. The asset is acquired for 3,307,692
EUR “droits inclus”.
The financing structure assumes an initial LTV of 30.0%, corresponding
to an opening loan of 992,308 EUR and an initial equity ticket of
2,315,385 EUR.
Unlevered performance. On an all-equity basis, the
5-year DCF yields an unlevered project IRR of 5.92% and an NPV of
253,371 EUR at the chosen discount rate.
The project is therefore marginally value-creating before leverage, with
most of the value coming from the terminal resale.
Leveraged performance.
For the 30% LTV, bullet loan structure, the leveraged equity IRR reaches 7.55%, for an equity NPV of 351,661 EUR and an equity multiple of about 1.34x. Leverage thus adds a moderate but meaningful uplift to equity returns compared to the all-equity case.
For the 70% LTV, bullet loan structure, the leveraged equity IRR increases to 7.55%, with an NPV of 351,661 EUR. The higher leverage leads to significantly higher returns, but at the cost of increasing the minimum DSCR to -3.115x and the maximum forward LTV to 25.1%.
For the 70% LTV, amortising loan structure, the leveraged equity IRR is 6.82% and the NPV is 314,738 EUR. The amortisation schedule leads to lower leverage, making the debt more manageable but resulting in a lower equity IRR. Minimum DSCR is -0.294x, and maximum forward LTV is 19.8%.
Credit profile.
The bullet debt structure remains the most risky from a credit perspective, with the minimum DSCR dipping below typical covenants in year 5, at -3.115x. The maximum forward LTV also rises to 25.1%, but remains below 65%, which is acceptable for most lenders.
The amortising structure is slightly more conservative. The DSCR remains above 1x for the entire investment horizon, and the forward LTV is also much lower, reaching 19.8%. This structure therefore offers a better balance between risk and return.
Key sensitivities and risks. Given the relatively
short hold period and the importance of the terminal value, returns are
sensitive to exit yield assumptions and potential softening of market
pricing at year 5. Rental cash-flows are stable under the preset, but
adverse reversion at lease expiry or higher vacancy at exit would
directly impact both unlevered and leveraged performance.
From a lender’s standpoint, the main residual risk is therefore
valuation risk at exit rather than income shortfall during the life of
the loan.
This narrative, combined with the tables and charts above, forms a
compact yet complete junior-analyst-level investment note. Because every
number is directly generated from run_case(cfg_default),
the memo is fully reproducible and can be stress-tested by adjusting the
YAML configuration or by applying scenario shocks (rental growth, exit
yields, LTV, interest rates) in separate notebooks.