#!/usr/bin/env python3 """Chapitre 30 — Flux actualisés et valeur terminale : exemple intégral du cas 3 (12 logements, Montréal, DONNÉES SYNTHÉTIQUES). Tous les chiffres cités dans manuscript/chapters/ch30.tex et solutions/ch30.tex proviennent de ce script, qui n'appelle que la bibliothèque commune (traite.cas3, traite.finance). Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch30_dcf.py Les fonctions sont importées par les scripts de figures (analysis/python/figures/ch30_*.py) et par analysis/python/tests/test_ch30.py.""" from __future__ import annotations import sys, copy, pathlib import numpy as np, pandas as pd sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) from traite import cas3, finance as fin ROOT = pathlib.Path(__file__).resolve().parents[3] # ----------------------------------------------------------------------------- cœur du cas 3 def base() -> dict: """Tout le cas 3 (RNE normalisé, taux extraits, projection 11 ans, DCF, capitalisation directe, financement).""" return cas3.tout() def tableau_dcf(r: dict) -> pd.DataFrame: """Tableau DCF complet : projection + facteur d'actualisation + valeur actuelle + VA cumulée (années 1..T, puis T+1).""" h = r["hyp"]; T = h["horizon_ans"]; taux = h["taux_actualisation"] p = r["projection"].copy() p["facteur"] = [fin.facteur_va(taux, int(t)) if t <= T else np.nan for t in p["annee"]] p["va"] = p["flux_avant_dette"] * p["facteur"] p["va_cumulee"] = p["va"].cumsum() return p def dcf_sans_levier(r: dict) -> dict: """Flux de l'investisseur non endetté qui paie V_0 : TRI, VAN au taux d'actualisation, délai de récupération, MIRR.""" d = r["dcf"]; h = r["hyp"] flux = [-d["valeur"]] + list(d["flux"]); flux[-1] += d["vt_nette"] taux_r = h["taux_actualisation"] return {"flux": flux, "tri": fin.tri(flux), "van_au_taux": fin.van(flux, taux_r), "delai_recuperation": fin.delai_recuperation(flux), "mirr": fin.tri_modifie(flux, taux_r, taux_r)} def taux_implicites(r: dict) -> dict: """Lecture croisée DCF / capitalisation directe : taux implicite du DCF, croissance implicite, TRI si l'on paie la valeur par capitalisation directe, croissance annualisée du RNE sur l'horizon.""" d = r["dcf"]; h = r["hyp"]; p = r["projection"]; T = h["horizon_ans"] rne1 = float(p.loc[p["annee"] == 1, "rne"].iloc[0]); rneT1 = float(p.loc[p["annee"] == T + 1, "rne"].iloc[0]) R_med = r["taux"]["mediane"]; taux_r = h["taux_actualisation"]; Rt = h["taux_terminal"] V_cap = r["valeur_capitalisation"] flux_cap = [-V_cap] + list(d["flux"]); flux_cap[-1] += d["vt_nette"] return {"R_implicite_dcf": rne1 / d["valeur"], "g_implicite_gordon_R_med": taux_r - R_med, "g_implicite_terminal": taux_r - Rt, "croissance_rne_annualisee": (rneT1 / rne1) ** (1 / T) - 1, "croissance_rbp_annualisee": (float(p.loc[p["annee"] == T + 1, "rbp"].iloc[0]) / float(p.loc[p["annee"] == 1, "rbp"].iloc[0])) ** (1 / T) - 1, "tri_si_paye_V_cap": fin.tri(flux_cap), "ecart_dcf_cap": d["valeur"] - V_cap, "ecart_dcf_cap_pct": d["valeur"] / V_cap - 1, "V_gordon_g_marche": fin.capitalisation_directe(rne1, fin.taux_gordon(taux_r, h["croissance_loyers_marche"])), "V_cap_au_taux_terminal": fin.capitalisation_directe(rne1, Rt)} def flux_levier(r: dict) -> dict: """Flux après service de la dette (financement de traite.cas3.financement) et TRI des fonds propres avec levier. Les CAPEX sont supportés par les fonds propres : la série passée à rendement_fonds_propres_levier est le flux avant dette (RNE − CAPEX).""" d = r["dcf"]; f = r["financement"]; h = r["hyp"]; T = h["horizon_ans"] hyp = f["hypotheque"] res = fin.rendement_fonds_propres_levier(d["flux"], d["valeur"], hyp, d["vt_nette"]) apres_dette = fin.flux_apres_financement(d["flux"], hyp) rne = r["projection"].loc[r["projection"]["annee"] <= T, "rne"].tolist() tab = pd.DataFrame({"annee": range(1, T + 1), "rne": rne, "capex": [c for c in (r["projection"].loc[r["projection"]["annee"] <= T, "capex"])], "flux_avant_dette": d["flux"], "service_dette": [hyp.service_dette_annuel] * T, "flux_apres_dette": apres_dette, "rcd": [x / hyp.service_dette_annuel for x in rne]}) tab["flux_fonds_propres"] = res["flux"][1:] signes = int(sum(1 for a, b in zip(res["flux"][:-1], res["flux"][1:]) if a * b < 0)) mirr = fin.tri_modifie(res["flux"], h["financement"]["taux"], h["taux_actualisation"]) return {**res, "tableau": tab, "service_dette": hyp.service_dette_annuel, "changements_signe": signes, "mirr": mirr, "produit_net_revente": d["vt_nette"] - res["solde_final"], "cash_on_cash_1": fin.rendement_cash_on_cash(apres_dette[0], res["mise_de_fonds"]), "rcd_1": fin.rcd(rne[0], hyp.service_dette_annuel), "rcd_min_horizon": min(tab["rcd"]), "taux_effectif_annuel_hyp": fin.taux_effectif_annuel(h["financement"]["taux"], 2), "taux_mensuel": hyp.taux_periodique, "taux_mensuel_si_composition_mensuelle": h["financement"]["taux"] / 12, "levier": "positif" if res["tri"] > r["hyp"]["taux_actualisation"] else "négatif"} # ----------------------------------------------------------------------------- sensibilité def valeur_avec(hyp_mod: dict, r: dict) -> dict: """Recalcule projection + DCF avec un dictionnaire d'hypothèses modifié (les données d'état locatif/dépenses sont inchangées).""" proj = cas3.projection_dcf(r["etat"], r["dep"], hyp_mod) return cas3.valeur_dcf(proj, hyp_mod) | {"projection": proj} def modifier(hyp: dict, **kw) -> dict: h = copy.deepcopy(hyp) for k, v in kw.items(): if k == "capex_facteur": h["capex"] = {a: m * v for a, m in h["capex"].items()} else: h[k] = v return h def sensibilite_taux_terminal(r: dict, grille=(0.0425, 0.045, 0.0475, 0.05, 0.0525, 0.055, 0.0575)) -> pd.DataFrame: rows = [] for Rt in grille: v = valeur_avec(modifier(r["hyp"], taux_terminal=Rt), r) rows.append({"taux_terminal": Rt, "vt_brute": v["vt_brute"], "vt_nette": v["vt_nette"], "va_terminale": v["va_terminale"], "valeur": v["valeur"], "part_terminale": v["part_terminale"], "ecart_pct_base": v["valeur"] / r["dcf"]["valeur"] - 1}) return pd.DataFrame(rows) def surface_r_Rt(r: dict, taux=(0.0625, 0.065, 0.0675, 0.07, 0.0725, 0.075, 0.0775, 0.08, 0.0825), Rt=(0.0425, 0.045, 0.0475, 0.05, 0.0525, 0.055, 0.0575)) -> pd.DataFrame: """Surface V(r, R_T) ; lignes = r, colonnes = R_T.""" M = np.zeros((len(taux), len(Rt))) for i, ri in enumerate(taux): for j, Rj in enumerate(Rt): M[i, j] = valeur_avec(modifier(r["hyp"], taux_actualisation=ri, taux_terminal=Rj), r)["valeur"] return pd.DataFrame(M, index=list(taux), columns=list(Rt)) def surface_r_g(r: dict, taux=(0.0625, 0.065, 0.0675, 0.07, 0.0725, 0.075, 0.0775, 0.08, 0.0825), g=(0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05)) -> pd.DataFrame: """Surface V(r, g) où g est la croissance des loyers de marché (R_T inchangé).""" M = np.zeros((len(taux), len(g))) for i, ri in enumerate(taux): for j, gj in enumerate(g): M[i, j] = valeur_avec(modifier(r["hyp"], taux_actualisation=ri, croissance_loyers_marche=gj), r)["valeur"] return pd.DataFrame(M, index=list(taux), columns=list(g)) def tornade(r: dict) -> pd.DataFrame: """Variation de la valeur pour un choc bas/haut de chaque intrant, les autres étant à leur valeur de base.""" h = r["hyp"]; V0 = r["dcf"]["valeur"] chocs = [ ("Taux d'actualisation r (−50 / +50 pb)", {"taux_actualisation": h["taux_actualisation"] - 0.005}, {"taux_actualisation": h["taux_actualisation"] + 0.005}), ("Taux terminal R_T (−25 / +25 pb)", {"taux_terminal": h["taux_terminal"] - 0.0025}, {"taux_terminal": h["taux_terminal"] + 0.0025}), ("Croissance des loyers de marché (2,5 / 4,5 %)", {"croissance_loyers_marche": 0.025}, {"croissance_loyers_marche": 0.045}), ("Croissance des loyers contractuels (2,0 / 4,0 %)", {"croissance_loyers_contrat": 0.020}, {"croissance_loyers_contrat": 0.040}), ("Rotation annuelle (5 / 25 %)", {"rotation_annuelle": 0.05}, {"rotation_annuelle": 0.25}), ("Inoccupation et pertes (2,9 / 4,9 %)", {"taux_inoccupation_pertes": 0.029}, {"taux_inoccupation_pertes": 0.049}), ("CAPEX (−25 / +25 %)", {"capex_facteur": 0.75}, {"capex_facteur": 1.25}), ("Croissance des dépenses (2,0 / 4,0 %)", {"croissance_depenses": 0.020}, {"croissance_depenses": 0.040}), ("Coûts de vente (1,5 / 3,5 %)", {"couts_vente": 0.015}, {"couts_vente": 0.035}), ] rows = [] for nom, bas, haut in chocs: vb = valeur_avec(modifier(h, **bas), r)["valeur"]; vh = valeur_avec(modifier(h, **haut), r)["valeur"] rows.append({"intrant": nom, "valeur_bas": vb, "valeur_haut": vh, "delta_bas": vb - V0, "delta_haut": vh - V0, "amplitude": abs(vh - vb)}) return pd.DataFrame(rows).sort_values("amplitude", ascending=False).reset_index(drop=True) def scenarios(r: dict) -> pd.DataFrame: """Trois scénarios narratifs : base ; encadrement serré des loyers ; hausse des taux. Deux lectures : (i) la valeur que conclurait l'évaluateur sous le scénario ; (ii) le sort de l'investisseur qui a payé la valeur de base et financé avec le prêt de base (TRI sans levier et TRI des fonds propres réalisés si le scénario se matérialise). Dans « Hausse des taux », le prêt de base est supposé renouvelé au taux initial + 100 pb dès l'origine (approximation du renouvellement, cf. ch. 31).""" h = r["hyp"]; V_base = r["dcf"]["valeur"]; f_base = r["financement"] defs = { "Base": ({}, 0.0), "Encadrement serré": ({"croissance_loyers_contrat": 0.020, "rotation_annuelle": 0.08, "croissance_loyers_marche": 0.030}, 0.0), "Hausse des taux": ({"taux_actualisation": h["taux_actualisation"] + 0.0075, "taux_terminal": h["taux_terminal"] + 0.005}, 0.01), } rows = [] for nom, (kw, choc_taux_hyp) in defs.items(): hm = modifier(h, **kw); v = valeur_avec(hm, r); p = v["projection"] hyp_pret = fin.Hypotheque(f_base["pret"], h["financement"]["taux"] + choc_taux_hyp, h["financement"]["amortissement_mois"]) flux_sl = [-V_base] + list(v["flux"]); flux_sl[-1] += v["vt_nette"] lev = fin.rendement_fonds_propres_levier(v["flux"], V_base, hyp_pret, v["vt_nette"]) rows.append({"scenario": nom, "r": hm["taux_actualisation"], "R_T": hm["taux_terminal"], "g_contrat": hm["croissance_loyers_contrat"], "g_marche": hm["croissance_loyers_marche"], "rotation": hm["rotation_annuelle"], "taux_hyp": hyp_pret.taux_nominal, "rne_1": float(p.loc[p["annee"] == 1, "rne"].iloc[0]), "rne_T1": v["rne_T1"], "vt_nette": v["vt_nette"], "valeur": v["valeur"], "part_terminale": v["part_terminale"], "ecart_pct_base": v["valeur"] / V_base - 1, "service_dette": hyp_pret.service_dette_annuel, "rcd_min": min(x / hyp_pret.service_dette_annuel for x in p.loc[p["annee"] <= hm["horizon_ans"], "rne"]), "tri_sans_levier_prix_base": fin.tri(flux_sl), "tri_fonds_propres_prix_base": lev["tri"], "flux_fp_min": min(lev["flux"][1:-1])}) return pd.DataFrame(rows) # ----------------------------------------------------------------------------- vérification de la proposition DCF = capitalisation directe (Gordon) def verification_gordon(noi1: float = 100_000.0, r: float = 0.0725, g: float = 0.035, T: int = 10, couts_vente: float = 0.0) -> dict: """Sous croissance constante g et R_T = r − g, le DCF à horizon T redonne exactement NOI_1/(r − g), quel que soit T.""" noi = fin.projeter_croissance(noi1, g, T + 1) res = fin.dcf(noi[:T], r, noi_suivant=noi[T], taux_terminal=fin.taux_gordon(r, g), couts_vente=couts_vente) return {"V_dcf": res.valeur, "V_gordon": fin.capitalisation_directe(noi1, fin.taux_gordon(r, g)), "part_terminale": res.va_terminale / res.valeur, "T": T} # ----------------------------------------------------------------------------- exercices (solutions/ch30.tex) def exercices() -> dict: out = {} # 30.1 Gordon out["ex1"] = {"R": fin.taux_gordon(0.0725, 0.035), "V": fin.capitalisation_directe(100_000, fin.taux_gordon(0.0725, 0.035)), "g_implicite": 0.0725 - 0.04415, "r_implicite": 0.04415 + 0.035} # 30.2 valeur terminale vtb, vtn = fin.valeur_terminale(159_658.38, 0.05, 0.025) out["ex2"] = {"vt_brute": vtb, "vt_nette": vtn, "va_10": vtn * fin.facteur_va(0.0725, 10), "va_11_erronee": vtn * fin.facteur_va(0.0725, 11), "ecart": vtn * fin.facteur_va(0.0725, 10) - vtn * fin.facteur_va(0.0725, 11), "vt_nette_55": fin.valeur_terminale(159_658.38, 0.055, 0.025)[1]} # 30.3 DCF 5 ans flux5 = [62_000, 64_000, 20_000, 68_000, 70_000]; noi6 = 72_000 res5 = fin.dcf(flux5, 0.07, noi_suivant=noi6, taux_terminal=0.05, couts_vente=0.03) prix = 1_300_000 f5 = [-prix] + flux5; f5[-1] += res5.valeur_terminale_nette out["ex3"] = {"va_flux": res5.va_flux, "vt_brute": res5.valeur_terminale_brute, "vt_nette": res5.valeur_terminale_nette, "va_vt": res5.va_terminale, "valeur": res5.valeur, "van_prix": fin.van(f5, 0.07), "tri_prix": fin.tri(f5), "part_terminale": res5.va_terminale / res5.valeur} # 30.4 taux hypothécaire canadien i = 0.0412 out["ex4"] = {"i_m_can": fin.taux_mensuel_canadien(i), "i_m_us": i / 12, "eff_can": fin.taux_effectif_annuel(i, 2), "eff_us": fin.taux_effectif_annuel(i, 12), "paiement_can": fin.Hypotheque(1_000_000, i, 300).paiement, "paiement_us": fin.Hypotheque(1_000_000, i, 300, composition=12).paiement} out["ex4"]["ecart_annuel"] = (out["ex4"]["paiement_us"] - out["ex4"]["paiement_can"]) * 12 # 30.5 cohérence Gordon out["ex5"] = {T: verification_gordon(90_000, 0.0725, 0.035, T) for T in (5, 10, 20)} out["ex5_cv"] = verification_gordon(90_000, 0.0725, 0.035, 10, couts_vente=0.025) # 30.6 variante du cas 3 r = base() v6 = valeur_avec(modifier(r["hyp"], taux_actualisation=0.0775, taux_terminal=0.055), r) out["ex6"] = {"valeur": v6["valeur"], "va_flux": v6["va_flux"], "va_terminale": v6["va_terminale"], "vt_nette": v6["vt_nette"], "part_terminale": v6["part_terminale"], "ecart_pct": v6["valeur"] / r["dcf"]["valeur"] - 1, "R_implicite": float(v6["projection"].loc[0, "rne"]) / v6["valeur"]} # 30.7 TRI avec levier (données du cas 3) lev = flux_levier(r) out["ex7"] = {"flux": lev["flux"], "tri": lev["tri"], "mirr": lev["mirr"], "changements_signe": lev["changements_signe"], "mise": lev["mise_de_fonds"], "solde_final": lev["solde_final"], "van_8": fin.van(lev["flux"], 0.08), "van_12": fin.van(lev["flux"], 0.12)} # 30.8 écart DCF / capitalisation directe out["ex8"] = taux_implicites(r) return out # ----------------------------------------------------------------------------- corps de tableaux LaTeX (collés dans ch30.tex) def _n(x, dec=0): """Nombre brut pour une colonne S de siunitx (point décimal, pas de séparateur).""" return f"{x:.{dec}f}" def latex_tables(): r = base(); h = r["hyp"]; T = h["horizon_ans"]; t = tableau_dcf(r); d = r["dcf"] print("% --- tab:30:projection (revenus et dépenses, années 1-11)") for _, x in t.iterrows(): print(f"{int(x.annee)} & {int(x.exercice)} & {_n(x.rbp)} & {_n(x.rbp_marche)} & {_n(x.rbe)} & {_n(x.depenses)} & {_n(x.reserve)} & {_n(x.rne)} \\\\") print("% --- tab:30:dcf (flux, facteur, VA)") for _, x in t[t.annee <= T].iterrows(): print(f"{int(x.annee)} & {_n(x.rne)} & {_n(x.capex)} & {_n(x.flux_avant_dette)} & {_n(x.facteur, 6)} & {_n(x.va)} & {_n(x.va_cumulee)} \\\\") print("% --- tab:30:levier") lv = flux_levier(r) for _, x in lv["tableau"].iterrows(): print(f"{int(x.annee)} & {_n(x.rne)} & {_n(x.flux_avant_dette)} & {_n(x.service_dette)} & {_n(x.flux_apres_dette)} & {_n(x.rcd, 2)} & {_n(x.flux_fonds_propres)} \\\\") print("% --- tab:30:sens-rt") for _, x in sensibilite_taux_terminal(r).iterrows(): print(f"{_n(x.taux_terminal*100, 2)} & {_n(x.vt_brute)} & {_n(x.vt_nette)} & {_n(x.va_terminale)} & {_n(x.valeur)} & {_n(x.part_terminale*100, 1)} & {_n(x.ecart_pct_base*100, 1)} \\\\") print("% --- tab:30:tornade") for _, x in tornade(r).iterrows(): print(f"{x.intrant} & {_n(x.valeur_bas)} & {_n(x.valeur_haut)} & {_n(x.delta_bas)} & {_n(x.delta_haut)} & {_n(x.amplitude)} \\\\") print("% --- tab:30:scenarios (colonnes = scénarios)") s = scenarios(r).set_index("scenario") lignes = [("Taux d'actualisation $r$ (\\%)", "r", 2, 100), ("Taux terminal $R_T$ (\\%)", "R_T", 2, 100), ("Croissance des loyers contractuels (\\%)", "g_contrat", 1, 100), ("Croissance des loyers de marché (\\%)", "g_marche", 1, 100), ("Rotation annuelle (\\%)", "rotation", 0, 100), ("Taux hypothécaire (\\%)", "taux_hyp", 2, 100), ("$\\NOI_{11}$ (\\$)", "rne_T1", 0, 1), ("Valeur terminale nette (\\$)", "vt_nette", 0, 1), ("Valeur $V_0$ (\\$)", "valeur", 0, 1), ("Écart à la base (\\%)", "ecart_pct_base", 1, 100), ("Part de la valeur terminale (\\%)", "part_terminale", 1, 100), ("Service de la dette (\\$)", "service_dette", 0, 1), ("RCD minimal sur l'horizon", "rcd_min", 2, 1), ("TRI sans levier si payé \\dollars{2208566} (\\%)", "tri_sans_levier_prix_base", 2, 100), ("TRI des fonds propres si payé \\dollars{2208566} (\\%)", "tri_fonds_propres_prix_base", 2, 100), ("Flux des fonds propres le plus bas (\\$)", "flux_fp_min", 0, 1)] for lab, col, dec, mult in lignes: print(lab + " & " + " & ".join(_n(s.loc[sc, col] * mult, dec) for sc in ["Base", "Encadrement serré", "Hausse des taux"]) + " \\\\") print("% --- surface V(r,R_T) en milliers") S = surface_r_Rt(r) for ri, row in S.iterrows(): print(_n(ri*100, 2) + " & " + " & ".join(_n(v/1000, 0) for v in row.values) + " \\\\") # ----------------------------------------------------------------------------- impression def _d(x): return f"{x:,.0f}".replace(",", " ") + " $" def _p(x, d=2): return f"{x*100:.{d}f}".replace(".", ",") + " %" def main(): pd.set_option("display.width", 250); pd.set_option("display.max_columns", 40); pd.set_option("display.float_format", lambda x: f"{x:,.4f}") r = base(); h = r["hyp"]; b = r["base"]; tx = r["taux"]; d = r["dcf"] print("=" * 100); print("CHAPITRE 30 — CAS 3 (DONNÉES SYNTHÉTIQUES) : FLUX ACTUALISÉS"); print("=" * 100) print("\n== Hypothèses (data/synthetic/cas3_montreal_hypotheses_dcf.json)") for k in ["date_evaluation", "horizon_ans", "taux_inoccupation_pertes", "croissance_loyers_contrat", "croissance_loyers_marche", "rotation_annuelle", "croissance_depenses", "taux_actualisation", "taux_terminal", "couts_vente", "capex", "autres_revenus_annuels"]: print(f" {k:32s} {h[k]}") print(f" financement : {h['financement']}") print("\n== Année 1 (RNE normalisé, cf. ch. 27-28)") print(f" RBP contractuel {_d(b['rbp_contrat'])} ; RBP marché {_d(b['rbp_marche'])} ; écart {_d(b['ecart_marche'])} ({_p(b['ecart_marche']/b['rbp_marche'],1)} sous le marché)") print(f" Inoccupation et pertes {_p(b['taux_vacance_pertes'],1)} = {_d(b['vacance_pertes'])} ; autres revenus {_d(b['autres_revenus'])} ; RBE {_d(b['rbe'])}") print(f" Dépenses hors réserve {_d(b['depenses_hors_reserve'])} ; réserve {_d(b['reserve'])} ; RNE {_d(b['rne'])} ({_d(b['rne_par_logement'])}/log.) ; ratio dépenses {_p(b['ratio_depenses'],1)}") print("\n== Taux extraits des 40 comparables (ch. 29)") print(f" n={tx['n']} ; médiane {_p(tx['mediane'],3)} ; moyenne {_p(tx['moyenne'],3)} ; pondérée RNE {_p(tx['ponderee_rne'],3)} ; Q1 {_p(tx['q1'],3)} ; Q3 {_p(tx['q3'],3)} ; min {_p(tx['min'],2)} ; max {_p(tx['max'],2)}") print(f" Valeur par capitalisation directe au taux médian : {_d(r['valeur_capitalisation'])}") print("\n== Tableau DCF complet (années 1-10 + année 11 pour la VT)") t = tableau_dcf(r) print(t[["annee", "exercice", "rbp", "rbp_marche", "rbe", "depenses", "reserve", "rne", "capex", "flux_avant_dette", "facteur", "va", "va_cumulee"]].to_string(index=False)) print("\n== Valeur terminale et valeur") print(f" RNE_11 {_d(d['rne_T1'])} ; R_T {_p(h['taux_terminal'])} ; VT brute {_d(d['vt_brute'])} ; coûts de vente {_p(h['couts_vente'],1)} = {_d(d['vt_brute']-d['vt_nette'])} ; VT nette {_d(d['vt_nette'])}") print(f" Facteur (1+r)^-10 = {fin.facteur_va(h['taux_actualisation'], 10):.6f} ; VA de la VT {_d(d['va_terminale'])} ; VA des flux {_d(d['va_flux'])}") print(f" VALEUR DCF = {_d(d['valeur'])} ; part de la VT {_p(d['part_terminale'],1)} ; par logement {_d(d['valeur']/12)}") ti = taux_implicites(r) print("\n== Lecture croisée DCF / capitalisation directe") for k, v in ti.items(): print(f" {k:32s} {(_p(v,3) if abs(v) < 1 else _d(v))}") sl = dcf_sans_levier(r) print("\n== Investisseur non endetté payant V_0") print(f" TRI {_p(sl['tri'],4)} (= r par construction) ; VAN(r) {sl['van_au_taux']:.6f} $ ; délai de récupération {sl['delai_recuperation']:.2f} ans ; MIRR(r,r) {_p(sl['mirr'],4)}") lv = flux_levier(r); f = r["financement"] print("\n== Financement (traite.cas3.financement) et flux après dette") print(f" Prêt max RPV 75 % {_d(f['pret_rpv'])} ; prêt max RCD 1,20 {_d(f['pret_rcd'])} ; prêt retenu {_d(f['pret'])} (contrainte {f['contrainte']}) ; RPV effectif {_p(f['rpv'])}") print(f" Taux nominal {_p(h['financement']['taux'])} comp. semestrielle → mensuel {_p(lv['taux_mensuel'],5)} (vs {_p(lv['taux_mensuel_si_composition_mensuelle'],5)} si mensuelle) ; effectif annuel {_p(lv['taux_effectif_annuel_hyp'],4)}") print(f" Paiement mensuel {_d(f['hypotheque'].paiement)} ; service de la dette {_d(lv['service_dette'])} ; constante {_p(f['constante'],3)} ; RCD année 1 {lv['rcd_1']:.3f} ; RCD min sur l'horizon {lv['rcd_min_horizon']:.3f}") print(f" Mise de fonds {_d(lv['mise_de_fonds'])} ; solde après 120 paiements {_d(lv['solde_final'])} ; produit net de revente {_d(lv['produit_net_revente'])}") print(lv["tableau"].to_string(index=False)) print(f" Flux des fonds propres : {[round(x) for x in lv['flux']]}") print(f" Changements de signe {lv['changements_signe']} ; TRI fonds propres {_p(lv['tri'],3)} ; MIRR (fin. {_p(h['financement']['taux'])}, réinv. {_p(h['taux_actualisation'])}) {_p(lv['mirr'],3)} ; cash-on-cash an 1 {_p(lv['cash_on_cash_1'],2)} ; levier {lv['levier']}") print("\n== Sensibilité au taux terminal") print(sensibilite_taux_terminal(r).to_string(index=False)) print("\n== Surface V(r, R_T)"); print(surface_r_Rt(r).round(0).to_string()) print("\n== Surface V(r, g_marché)"); print(surface_r_g(r).round(0).to_string()) print("\n== Tornade"); print(tornade(r).to_string(index=False)) print("\n== Scénarios"); print(scenarios(r).to_string(index=False)) print("\n== Vérification de la proposition (croissance constante, R_T = r − g)") for T in (5, 10, 30): print(f" T={T:2d} : {verification_gordon(T=T)}") print("\n== Exercices") for k, v in exercices().items(): print(f" {k}: {v}") if __name__ == "__main__": latex_tables() if "--latex" in sys.argv else main()