Case study: buy-side investment memo

Package cre.dcf

2026-04-10

Purpose of this vignette

This vignette mirrors a typical buy-side analyst workflow:

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 to show a simple workflow from YAML assumptions to a short investment note.

Setting the scene: the analyst’s brief

In this fictional example, the 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:

Loading and tailoring preset_default.yml

Loading the preset configuration

We first read preset_default.yml from inst/extdata.

## 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.

Run the DCF engine

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:

Pricing and capital structure at t = 0

Purchase price and acquisition costs

## $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.

Initial LTV and capital structure

The financing configuration encodes the initial loan-to-value and the debt sizing convention:

## $ltv_base
## [1] "price_di"
## 
## $debt_type
## [1] "bullet"
## 
## $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

All-equity view: project fundamentals

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.

Cash-flow table

## # A tibble: 6 × 14
##    year     gei    noi   pbtcf net_operating_income  capex   opex free_cash_flow
##   <int>   <dbl>  <dbl>   <dbl>                <dbl>  <dbl>  <dbl>          <dbl>
## 1     0      0  0       0                        0  0          0       -3307692.
## 2     1 200000  2   e5  2   e5              200000  0          0         200000 
## 3     2 202000  2.02e5  2.02e5              202000  0          0         202000 
## 4     3 204020  2.04e5  2.04e5              204020  0          0         204020 
## 5     4  61818. 0      -3.09e5               61818. 3.09e5 61818.       -309090.
## 6     5 197715. 1.98e5  1.68e5              197715. 2.97e4     0        4121957.
## # ℹ 6 more variables: sale_proceeds <dbl>, 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.

Project IRR and NPV

The unlevered metrics are stored directly in the all_equity object:

## $irr_project
## [1] 0.06467435
## 
## $npv_project
## [1] 337536.3

The package also reports how much of present value comes from ongoing operations versus the terminal event:

## $ops_share
## [1] 0.1186896
## 
## $tv_share
## [1] 0.8813104

In words:

the unlevered project IRR is 6.47%,

the unlevered project NPV at the chosen discount rate is 337,536 EUR.

This short-hold teaching case is intentionally exit-heavy: 88.1% of present value comes from the terminal sale. From a methodological perspective, that is acceptable for a compact classroom example, but it means the analyst should discuss exit yield and disposition assumptions explicitly rather than treating the IRR as self-explanatory.

For a junior analyst, this provides the first sanity check:

Leveraged view: comparing debt structures

Summary table: all-equity vs bullet vs amortising

The comparison$summary table aggregates key metrics for three scenarios:

## # A tibble: 3 × 9
##   scenario    irr_equity npv_equity irr_project npv_project min_dscr
##   <chr>            <dbl>      <dbl>       <dbl>       <dbl>    <dbl>
## 1 all_equity      0.0647    337536.      0.0647     337536.   NA    
## 2 debt_bullet     0.0829    435826.      0.0647     337536.   10.1  
## 3 debt_amort      0.0748    398903.      0.0647     337536.    0.950
## # ℹ 3 more variables: max_ltv_forward <dbl>, ops_share <dbl>, tv_share <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 × 10
##   scenario    irr_equity npv_equity irr_project npv_project min_dscr
##   <chr>       <chr>      <chr>      <chr>       <chr>          <dbl>
## 1 all_equity  6.47%      337,536    6.47%       337,536        NA   
## 2 debt_bullet 8.29%      435,826    6.47%       337,536        10.1 
## 3 debt_amort  7.48%      398,903    6.47%       337,536         0.95
## # ℹ 4 more variables: max_ltv_forward <dbl>, ops_share <dbl>, tv_share <dbl>,
## #   max_ltv_fwd <chr>

Debt schedules

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:

Credit ratios: DSCR and forward LTV paths

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        NA    
## 5     4 NA           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     NA     
## 5     4 NA          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:

Equity cash-flows and equity multiple

Leveraged equity cash-flows

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       -309090.            1.18   19846.   19846.          992308.
## 6     5       4121957.            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:

Equity IRR, NPV and multiple

## $irr_equity
## [1] 0.08294531
## 
## $npv_equity
## [1] 435826.4

For documentation purposes, the analyst can also recompute the equity multiple using the helper provided by the package:

## [1] 1.382693

In narrative form:

These three indicators are typically the core of the buy-side decision.

Exploring alternative financing structures

In many investment-committee settings, the analyst is expected not only to assess the project on an all-equity basis and under one leverage profile, but also to test how equity performance and credit risk change with different debt structures.

In this section, we keep the same real-estate cash-flow profile as in the base case (preset_default.yml) and vary only the financing structure around four simple 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.

Normalising the configuration and rebuilding the unlevered DCF

We reuse the YAML configuration already loaded as cfg_default, and normalise it the same way as run_case(). This gives us a consistent set of inputs for the DCF engine.

Scenario grid and extraction helper

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.

Running the financing variants

For each scenario, we either:

## # 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.0647    337536.      0.0647
## 2 ltv30_bullet_2  30% L…   0.3 0.02  bullet        0.0829    435826.      0.0647
## 3 ltv70_bullet_3  70% L…   0.7 0.03  bullet        0.138     464494.      0.0647
## 4 ltv70_amort_2_5 70% L…   0.7 0.025 amort         0.0956    449105.      0.0647
## # ℹ 3 more variables: npv_project <dbl>, min_dscr <dbl>, max_ltv_fwd <dbl>

Formatting the comparison table

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… 6.47%      337,536    6.47%      
## 2 ltv30_bullet_2  30% L… 30%   2.0%  bullet    8.29%      435,826    6.47%      
## 3 ltv70_bullet_3  70% L… 70%   3.0%  bullet    13.81%     464,494    6.47%      
## 4 ltv70_amort_2_5 70% L… 70%   2.5%  amort     9.56%      449,105    6.47%      
## # ℹ 3 more variables: npv_project <chr>, min_dscr <dbl>, max_ltv_fwd <chr>

Drafting a short investment memo

Extracting key numbers

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                      6.47%    
##  4 Unlevered project NPV                      337,536  
##  5 Leveraged equity IRR (30% LTV, bullet)     8.29%    
##  6 Leveraged equity NPV (30% LTV, bullet)     435,826  
##  7 Leveraged equity IRR (70% LTV, bullet)     8.29%    
##  8 Leveraged equity NPV (70% LTV, bullet)     435,826  
##  9 Leveraged equity IRR (70% LTV, amortising) 7.48%    
## 10 Leveraged equity NPV (70% LTV, amortising) 398,903  
## 11 Minimum DSCR (bullet)                      0.195    
## 12 Maximum forward LTV (bullet)               25.1%    
## 13 Minimum DSCR (amortising)                  0.939    
## 14 Maximum forward LTV (amortising)           19.8%    
## 15 Equity multiple (bullet)                   1.38

Example narrative

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 6.47% and an NPV of 337,536 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.

Credit profile.

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.