spb/traite-evaluation-qc
Public
1#!/usr/bin/env python32"""Chapitre 31 — Financement, levier, RCD et RPV : calculs du chapitre (cas 3, 12 logements, Montréal,3DONNÉES SYNTHÉTIQUES, sauf les taux de la Banque du Canada cités en repère).4Tous les chiffres de manuscript/chapters/ch31.tex et solutions/ch31.tex proviennent de ce script, qui n'appelle que la5bibliothèque commune (traite.finance, traite.cas3) et les fonctions du chapitre 30 (cases.ch30_dcf) pour les flux.6Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch31_financement.py [--latex]7Les fonctions sont importées par analysis/python/figures/ch31_*.py et analysis/python/tests/test_ch31.py.89Aides propres au chapitre (candidates à une promotion dans traite.finance après harmonisation) :10 prime_aph(rpv, amortissement_mois) — barème SCHL immeubles collectifs (locatif ordinaire / APH Select)11 mise_de_fonds_minimale(prix) — règle fédérale 5 % / 10 % (plafond 1,5 M$) en vigueur le 15 déc. 202412 renouvellement(...) — recalcul exact du paiement sur le solde au terme, à un nouveau taux13 cout_effectif_dette(...) — TRI annuel des flux de la dette (coût effectif k)14"""15from __future__ import annotations16import sys, pathlib17import numpy as np, pandas as pd18sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))19from traite import cas3, finance as fin20from cases import ch30_dcf as c302122ROOT = pathlib.Path(__file__).resolve().parents[3]2324# Repères publics (sources/notes-C.md §4, consultés le 2026-10-31) — valeurs citées dans le texte, non des intrants du cas 325REPERES = {26 "taux_directeur": 0.0250, # BdC, 28 octobre 202627 "gdc_5ans": 0.0358, "gdc_10ans": 0.0384, # BdC Valet, 2026-10-2928 "affiche_5ans": 0.0619, # V80691335, semaine du 2026-10-2829 "assure_5ans_plus": 0.0412, # A4_RATES_MORTGAGES, V122667780, août 202630 "non_assure_5ans_plus": 0.0458, # A4_RATES_MORTGAGES, V122667786, août 202631 "mqr_plancher": 0.0525, "mqr_marge": 0.02, # BSIF, page modifiée le 2026-01-2932 "lti_multiple": 4.5, # BSIF, limites LTI33 "plafond_assurable": 1_500_000.0, "seuil_mdf": 500_000.0, # Finances Canada, 15 décembre 202434}3536# ----------------------------------------------------------------------------- aides propres au chapitre3738def prime_aph(rpv: float, amortissement_mois: int) -> float:39 """Prime d'assurance SCHL (immeubles collectifs, locatif ordinaire et APH Select, tous buts sauf construction),40 en proportion du prêt : barème par tranche de RPV + surprime de 0,25 % par tranche de 5 ans d'amortissement au-delà41 de 25 ans (sources/notes-C.md §4.4, PDF « Droits et primes » consulté le 2026-10-31)."""42 if rpv <= 0.65: base = 0.026043 elif rpv <= 0.70: base = 0.028544 elif rpv <= 0.75: base = 0.033545 elif rpv <= 0.80: base = 0.043546 elif rpv <= 0.85: base = 0.053547 elif rpv <= 0.90: base = 0.059048 else: base = 0.061549 annees = amortissement_mois / 1250 tranches = max(0, int(np.ceil((annees - 25) / 5 - 1e-9)))51 return base + 0.0025 * tranches5253def mise_de_fonds_minimale(prix: float) -> float:54 """Mise de fonds minimale d'un prêt assuré (résidentiel 1-4 logements, propriétaire-occupant) : 5 % jusqu'à 500 000 $,55 10 % de la tranche entre 500 000 $ et 1,5 M$ ; au-delà de 1,5 M$ le prêt n'est pas assurable (20 % exigés en pratique56 par la règle RPV ≤ 80 % de B-20). Règles fédérales en vigueur le 15 décembre 2024 (notes-C.md §4.3)."""57 if prix > REPERES["plafond_assurable"]:58 return 0.20 * prix59 s = REPERES["seuil_mdf"]60 return 0.05 * min(prix, s) + 0.10 * max(prix - s, 0.0)6162def cout_effectif_dette(hyp: fin.Hypotheque, annees: int) -> dict:63 """Coût effectif annuel de la dette : TRI des flux annuels de l'emprunteur (+M_0 ; −SD … ; −SD − solde) sur `annees`,64 comparé au taux effectif annuel (1 + i/2)^2 − 1 (paiements mensuels agrégés en fin d'année ⇒ TRI annuel < effectif)."""65 sd = hyp.service_dette_annuel; solde = hyp.solde(annees * 12)66 flux_emprunteur = [hyp.capital] + [-sd] * annees67 flux_emprunteur[-1] -= solde68 flux_preteur = [-x for x in flux_emprunteur]69 return {"k_annuel": fin.tri(flux_preteur), "taux_effectif": fin.taux_effectif_annuel(hyp.taux_nominal, hyp.composition),70 "flux_emprunteur": flux_emprunteur, "solde_fin": solde}7172# ----------------------------------------------------------------------------- cœur7374def base() -> dict:75 """Tout le cas 3 (via traite.cas3.tout) + flux après dette du chapitre 30."""76 r = c30.base()77 r["levier30"] = c30.flux_levier(r)78 return r7980def conventions(capital: float, i: float = 0.0412, n: int = 480) -> pd.DataFrame:81 """Comparaison des conventions canadienne (composition semestrielle) et américaine (mensuelle) sur un même prêt."""82 rows = []83 for nom, comp in (("Canada (semestrielle)", 2), ("États-Unis (mensuelle)", 12)):84 h = fin.Hypotheque(capital, i, n, composition=comp)85 rows.append({"convention": nom, "composition": comp, "taux_mensuel": h.taux_periodique, "taux_effectif": fin.taux_effectif_annuel(i, comp),86 "paiement": h.paiement, "service_dette": h.service_dette_annuel, "constante": h.constante_hypothecaire,87 "solde_60": h.solde(60), "solde_120": h.solde(120), "interets_an1": h.interets_annee(1), "capital_an1": h.capital_annee(1)})88 df = pd.DataFrame(rows)89 return df9091def tableau_extraits(h: fin.Hypotheque, periodes=(1, 2, 3, 12, 13, 60, 61, 120, 121, 240, 360, 479, 480)) -> pd.DataFrame:92 tab = pd.DataFrame(h.tableau())93 out = tab[tab["periode"].isin(periodes)].copy()94 out["part_interet"] = out["interet"] / out["paiement"]95 return out.reset_index(drop=True)9697def decomposition_annuelle(h: fin.Hypotheque) -> pd.DataFrame:98 tab = pd.DataFrame(h.tableau()); tab["annee"] = (tab["periode"] - 1) // 12 + 199 g = tab.groupby("annee").agg(interet=("interet", "sum"), capital=("capital", "sum"), paiement=("paiement", "sum"), solde_fin=("solde", "last")).reset_index()100 g["solde_debut"] = [h.capital] + g["solde_fin"].tolist()[:-1]101 return g102103def financement(r: dict) -> dict:104 """Dimensionnement du prêt du cas 3 (traite.cas3.financement) + indicateurs dérivés (ratios, point mort, coût effectif)."""105 f = r["financement"]; h = f["hypotheque"]; b = r["base"]; hyp = r["hyp"]; T = hyp["horizon_ans"]106 V = r["dcf"]["valeur"]; noi1 = b["rne"]107 cout = cout_effectif_dette(h, T)108 return {**{k: v for k, v in f.items() if k != "hypotheque"}, "hypotheque": h, "valeur": V, "rne_1": noi1,109 "paiement": h.paiement, "taux_effectif": fin.taux_effectif_annuel(h.taux_nominal, 2), "taux_mensuel_us": h.taux_nominal / 12,110 "interets_an1": h.interets_annee(1), "capital_an1": h.capital_annee(1), "solde_60": h.solde(60), "solde_120": h.solde(120),111 "rpv_max_par_rcd_sur_V": noi1 / (hyp["financement"]["rcd_min"] * h.constante_hypothecaire) / V,112 "rpv_max_par_rcd_sur_Vcap": r["taux"]["mediane"] / (hyp["financement"]["rcd_min"] * h.constante_hypothecaire),113 "R_dcf": noi1 / V, "R_median": r["taux"]["mediane"],114 "point_mort": fin.ratio_point_mort(b["depenses_totales"], h.service_dette_annuel, b["rbp_contrat"]),115 "point_mort_hors_reserve": fin.ratio_point_mort(b["depenses_hors_reserve"], h.service_dette_annuel, b["rbp_contrat"]),116 "flux_apres_dette_1": noi1 - h.service_dette_annuel, "cash_on_cash_1": (noi1 - h.service_dette_annuel) / f["mise_de_fonds"],117 "dette_sur_fp": f["pret"] / f["mise_de_fonds"], "k_annuel": cout["k_annuel"], "tri_fp": r["levier30"]["tri"], "tri_sans_levier": hyp["taux_actualisation"]}118119def levier_courant(r: dict) -> dict:120 """Vérification de R_E = (R − M f)/(1 − M) = R + (R − f) D/E sur la base DCF et sur la base capitalisation directe."""121 fi = financement(r); f = fi["constante"]; M = fi["rpv"]122 R = fi["R_dcf"]; RE_formule = (R - M * f) / (1 - M); RE_alt = R + (R - f) * fi["dette_sur_fp"]123 # sur la valeur par capitalisation directe (prix = V_cap, même prêt) :124 Vc = r["valeur_capitalisation"]; Mc = fi["pret"] / Vc; Rc = fi["rne_1"] / Vc125 return {"R": R, "f": f, "M": M, "D_sur_E": fi["dette_sur_fp"], "RE_formule": RE_formule, "RE_alt": RE_alt, "RE_direct": fi["cash_on_cash_1"],126 "R_cap": Rc, "M_cap": Mc, "RE_cap": (Rc - Mc * f) / (1 - Mc), "levier_courant": "négatif" if R < f else "positif"}127128def levier_par_rpv(r: dict, rpvs=(0.0, 0.25, 0.50, 0.60, None, 0.70, 0.75, 0.80, 0.85)) -> pd.DataFrame:129 """Effet du ratio prêt-valeur sur la mise de fonds, le service de la dette, le RCD, le rendement courant et le TRI des fonds130 propres (achat à la valeur DCF, flux du cas 3, même taux et même amortissement ; None = RPV du prêt retenu)."""131 d = r["dcf"]; hyp = r["hyp"]; fi = r["financement"]; V = d["valeur"]; T = hyp["horizon_ans"]132 noi1 = r["base"]["rne"]; cf = d["flux"]133 rows = []134 for M in rpvs:135 Mv = fi["rpv"] if M is None else M136 pret = Mv * V137 h = fin.Hypotheque(pret, hyp["financement"]["taux"], hyp["financement"]["amortissement_mois"])138 res = fin.rendement_fonds_propres_levier(cf, V, h, d["vt_nette"])139 sd = h.service_dette_annuel140 rows.append({"rpv": Mv, "pret": pret, "mise_de_fonds": V - pret, "service_dette": sd, "rcd_1": (noi1 / sd) if sd > 0 else np.inf,141 "flux_apres_dette_1": noi1 - sd, "cash_on_cash_1": (noi1 - sd) / (V - pret), "tri_fp": res["tri"],142 "flux_min": min(res["flux"][1:-1]), "retenu": M is None})143 return pd.DataFrame(rows)144145def tri_fp_courbe(r: dict, rpvs=np.linspace(0.0, 0.85, 35)) -> pd.DataFrame:146 return levier_par_rpv(r, tuple(float(x) for x in rpvs))147148def pret_maximal(r: dict) -> dict:149 fi = r["financement"]; hyp = r["hyp"]["financement"]150 return {"pret_rpv": fi["pret_rpv"], "pret_rcd": fi["pret_rcd"], "pret": fi["pret"], "contrainte": fi["contrainte"],151 "sd_max": r["base"]["rne"] / hyp["rcd_min"], "f": fi["constante"]}152153def grille_pret_max(r: dict, taux=(0.0312, 0.0412, 0.0512, 0.0612), amort=(300, 360, 480), rcd=(1.10, 1.20, 1.30)) -> pd.DataFrame:154 """Prêt maximal par le RCD selon le taux, l'amortissement et le RCD minimal, en dollars et en % de la valeur DCF."""155 noi1 = r["base"]["rne"]; V = r["dcf"]["valeur"]; rows = []156 for i in taux:157 for n in amort:158 for q in rcd:159 p = fin.pret_max_par_rcd(noi1, q, i, n)160 rows.append({"taux": i, "amortissement_mois": n, "rcd_min": q, "pret_max": p, "rpv_implicite": p / V,161 "constante": fin.Hypotheque(1.0, i, n).constante_hypothecaire})162 return pd.DataFrame(rows)163164def aph_select(r: dict, rcd_min: float = 1.10, rpv_max: float = 0.85, amorts=(480, 540, 600)) -> pd.DataFrame:165 """Variante APH Select (50 points : RPV 85 %, RCD 1,10 ; 70 points : 45 ans ; 100 points : 50 ans) sur la valeur DCF, au166 même taux de 4,12 % ; prime SCHL selon le barème (prime_aph), capitalisée ou non au prêt (ici affichée séparément)."""167 noi1 = r["base"]["rne"]; V = r["dcf"]["valeur"]; i = r["hyp"]["financement"]["taux"]; rows = []168 fi = r["financement"]169 rows.append({"formule": "Conventionnel 40 ans (RPV 75 %, RCD 1,20)", "amortissement_mois": 480, "pret_rpv": fi["pret_rpv"], "pret_rcd": fi["pret_rcd"], "pret": fi["pret"],170 "rpv": fi["pret"] / V, "service_dette": fi["service_dette"], "mise_de_fonds": V - fi["pret"], "prime_pct": np.nan, "prime": np.nan, "rcd_1": noi1 / fi["service_dette"]})171 for n in amorts:172 p_rpv = fin.pret_max_par_rpv(V, rpv_max); p_rcd = fin.pret_max_par_rcd(noi1, rcd_min, i, n); p = min(p_rpv, p_rcd)173 h = fin.Hypotheque(p, i, n); pr = prime_aph(p / V, n)174 rows.append({"formule": f"APH Select {n // 12} ans (RPV {rpv_max:.0%}, RCD {rcd_min:.2f})".replace("%", " %"), "amortissement_mois": n, "pret_rpv": p_rpv, "pret_rcd": p_rcd, "pret": p,175 "rpv": p / V, "service_dette": h.service_dette_annuel, "mise_de_fonds": V - p, "prime_pct": pr, "prime": pr * p, "rcd_1": noi1 / h.service_dette_annuel})176 return pd.DataFrame(rows)177178# ----------------------------------------------------------------------------- B-20, MQR, LTI, mise de fonds179180def regles_residentielles() -> dict:181 """Illustrations chiffrées des règles fédérales (résidentiel 1-4 logements)."""182 mqr_na = fin.taux_admissible_minimal(REPERES["non_assure_5ans_plus"]); mqr_as = fin.taux_admissible_minimal(REPERES["assure_5ans_plus"])183 mqr_bas = fin.taux_admissible_minimal(0.0300)184 prix = (400_000.0, 650_000.0, 1_000_000.0, 1_500_000.0, 1_600_000.0)185 mdf = pd.DataFrame({"prix": prix, "mise_de_fonds": [mise_de_fonds_minimale(p) for p in prix]})186 mdf["pct"] = mdf["mise_de_fonds"] / mdf["prix"]; mdf["pret"] = mdf["prix"] - mdf["mise_de_fonds"]; mdf["rpv"] = mdf["pret"] / mdf["prix"]187 # capacité d'emprunt d'un ménage : paiement admissible au MQR vs au taux contractuel (ménage fictif, 25 ans)188 revenu = 120_000.0; part_dette = 0.39 # ratio ABD indicatif, hypothèse pédagogique (non une règle citée)189 paiement_mensuel = revenu * part_dette / 12 * 0.85 # 85 % du service de la dette admissible va à l'hypothèque (reste : taxes, chauffage) — hypothèse190 def capacite(i, n=300):191 f = fin.Hypotheque(1.0, i, n).paiement192 return paiement_mensuel / f193 cap = {"revenu": revenu, "paiement_mensuel": paiement_mensuel, "pret_au_contrat": capacite(REPERES["non_assure_5ans_plus"]), "pret_au_mqr": capacite(mqr_na),194 "pret_lti": REPERES["lti_multiple"] * revenu}195 cap["reduction_mqr"] = 1 - cap["pret_au_mqr"] / cap["pret_au_contrat"]196 return {"mqr_non_assure": mqr_na, "mqr_assure": mqr_as, "mqr_taux_bas": mqr_bas, "mise_de_fonds": mdf, "capacite": cap}197198def stress_rcd(r: dict, marges=(0.0, 0.01, 0.02, 0.03)) -> pd.DataFrame:199 """Test de résistance « à la B-20 » appliqué au RCD du cas 3 : service de la dette du prêt retenu recalculé au taux contractuel200 + marge (même amortissement), RCD sur le RNE de l'année 1, et prêt maximal si le prêteur qualifiait au taux majoré."""201 fi = r["financement"]; hyp = r["hyp"]["financement"]; noi1 = r["base"]["rne"]; V = r["dcf"]["valeur"]; rows = []202 for m in marges:203 i = hyp["taux"] + m204 h = fin.Hypotheque(fi["pret"], i, hyp["amortissement_mois"])205 pmax = fin.pret_max_par_rcd(noi1, hyp["rcd_min"], i, hyp["amortissement_mois"])206 rows.append({"marge": m, "taux": i, "service_dette": h.service_dette_annuel, "rcd_1": noi1 / h.service_dette_annuel,207 "pret_max_qualifie": pmax, "rpv_qualifie": pmax / V, "constante": h.constante_hypothecaire})208 return pd.DataFrame(rows)209210# ----------------------------------------------------------------------------- renouvellement au terme211212def renouvellement(r: dict, deltas=(0.0, 0.01, 0.02, 0.03), terme_ans: int | None = None) -> dict:213 """Renouvellement exact à l'échéance du terme : solde après `terme` années, nouveau paiement sur l'amortissement résiduel214 au taux initial + delta ; service de la dette, RCD des années suivantes, flux des fonds propres et TRI (achat à la valeur215 DCF, projection de base du cas 3). Comparaison avec l'approximation du chapitre 30 (taux majoré dès l'origine)."""216 d = r["dcf"]; hyp = r["hyp"]; fi = r["financement"]; h0 = fi["hypotheque"]; T = hyp["horizon_ans"]217 terme = terme_ans or hyp["financement"]["terme_ans"]; n0 = hyp["financement"]["amortissement_mois"]218 V = d["valeur"]; mise = V - h0.capital; cf = list(d["flux"]); rne = r["projection"].loc[r["projection"]["annee"] <= T, "rne"].tolist()219 solde_terme = h0.solde(terme * 12); n_res = n0 - terme * 12220 sd0 = h0.service_dette_annuel221 rows = []; detail = {}222 for dlt in deltas:223 i2 = h0.taux_nominal + dlt224 h2 = fin.Hypotheque(solde_terme, i2, n_res)225 sd2 = h2.service_dette_annuel226 sd = [sd0] * terme + [sd2] * (T - terme)227 solde_T = h2.solde((T - terme) * 12)228 flux = [-mise] + [cf[t] - sd[t] for t in range(T)]229 flux[-1] += d["vt_nette"] - solde_T230 tri = fin.tri(flux)231 rcd = [rne[t] / sd[t] for t in range(T)]232 # approximation du ch. 30 : même prêt, taux majoré dès l'origine233 h_approx = fin.Hypotheque(h0.capital, i2, n0)234 approx = fin.rendement_fonds_propres_levier(cf, V, h_approx, d["vt_nette"])235 rows.append({"delta": dlt, "taux_renouvele": i2, "solde_terme": solde_terme, "paiement_2": h2.paiement, "service_dette_2": sd2,236 "hausse_sd": sd2 - sd0, "hausse_sd_pct": sd2 / sd0 - 1, "rcd_an_terme_plus_1": rcd[terme], "rcd_min_apres": min(rcd[terme:]),237 "rcd_an_T": rcd[-1], "flux_apres_dette_an_terme_plus_1": cf[terme] - sd2, "solde_T": solde_T, "tri_fp": tri,238 "tri_fp_approx_ch30": approx["tri"], "sd_approx_ch30": h_approx.service_dette_annuel, "interets_an_terme_plus_1": h2.interets_annee(1)})239 detail[dlt] = {"flux": flux, "rcd": rcd, "sd": sd, "hypotheque_2": h2}240 tab = pd.DataFrame(rows)241 # seuil : hausse de taux qui ramène le RCD de l'année terme+1 à 1,20 et à 1,00242 def rcd_apres(dlt):243 h2 = fin.Hypotheque(solde_terme, h0.taux_nominal + dlt, n_res); return rne[terme] / h2.service_dette_annuel244 def bissection(cible, lo=0.0, hi=0.20):245 for _ in range(200):246 mid = 0.5 * (lo + hi)247 if rcd_apres(mid) > cible: lo = mid248 else: hi = mid249 return 0.5 * (lo + hi)250 return {"tableau": tab, "detail": detail, "solde_terme": solde_terme, "n_residuel": n_res, "terme": terme,251 "delta_rcd_120": bissection(1.20), "delta_rcd_100": bissection(1.00), "taux_rcd_120": h0.taux_nominal + bissection(1.20), "taux_rcd_100": h0.taux_nominal + bissection(1.00)}252253def sensibilite_service_dette(r: dict, taux=np.arange(0.03, 0.0801, 0.0025), amorts=(300, 360, 480)) -> pd.DataFrame:254 """Service de la dette et RCD (RNE_1) du prêt retenu selon le taux et l'amortissement (pour la figure et le tableau)."""255 fi = r["financement"]; noi1 = r["base"]["rne"]; rows = []256 for n in amorts:257 for i in taux:258 h = fin.Hypotheque(fi["pret"], float(i), n)259 rows.append({"amortissement_mois": n, "taux": float(i), "service_dette": h.service_dette_annuel, "rcd_1": noi1 / h.service_dette_annuel, "constante": h.constante_hypothecaire})260 return pd.DataFrame(rows)261262def risque_levier(r: dict, sigma_actif: float = 0.08) -> dict:263 """Illustration : écart-type du rendement des fonds propres = σ_actif / (1 − M) (dette sans risque, une période)."""264 fi = r["financement"]; M = fi["rpv"]265 return {"sigma_actif": sigma_actif, "M": M, "sigma_fp": sigma_actif / (1 - M), "multiplicateur": 1 / (1 - M),266 "grille": pd.DataFrame({"rpv": [0, 0.25, 0.5, M, 0.75, 0.85], "sigma_fp": [sigma_actif / (1 - m) for m in [0, 0.25, 0.5, M, 0.75, 0.85]]})}267268# ----------------------------------------------------------------------------- exercices269270def exercices() -> dict:271 r = base(); e = {}272 # ex 1 : convention canadienne vs américaine, 850 000 $, 4,58 %, 300 mois273 e["ex1"] = conventions(850_000.0, 0.0458, 300).set_index("convention").to_dict("index")274 # ex 2 : terme vs amortissement : prêt 1 000 000 $, 4,12 %, 480 mois ; solde à 60 et 120 ; intérêts an 1 ; part du capital remboursée au terme275 h = fin.Hypotheque(1_000_000.0, 0.0412, 480)276 e["ex2"] = {"paiement": h.paiement, "sd": h.service_dette_annuel, "solde_60": h.solde(60), "solde_120": h.solde(120), "interets_an1": h.interets_annee(1),277 "capital_an1": h.capital_annee(1), "part_remboursee_60": 1 - h.solde(60) / h.capital, "interets_totaux": h.paiement * 480 - h.capital,278 "h25": fin.Hypotheque(1_000_000.0, 0.0412, 300)}279 e["ex2"]["interets_totaux_25"] = e["ex2"]["h25"].paiement * 300 - 1_000_000.0; e["ex2"]["sd_25"] = e["ex2"]["h25"].service_dette_annuel280 # ex 3 : prêt maximal : RNE 120 000 $, valeur 2 600 000 $, RPV 75 %, RCD 1,25, 4,58 %, 360 mois ; puis APH 1,10 / 85 % / 480 mois281 e["ex3"] = {"pret_rpv": fin.pret_max_par_rpv(2_600_000, 0.75), "pret_rcd": fin.pret_max_par_rcd(120_000, 1.25, 0.0458, 360),282 "f": fin.Hypotheque(1.0, 0.0458, 360).constante_hypothecaire, "pret_rcd_aph": fin.pret_max_par_rcd(120_000, 1.10, 0.0412, 480),283 "pret_rpv_aph": fin.pret_max_par_rpv(2_600_000, 0.85), "f_aph": fin.Hypotheque(1.0, 0.0412, 480).constante_hypothecaire}284 e["ex3"]["pret"] = min(e["ex3"]["pret_rpv"], e["ex3"]["pret_rcd"]); e["ex3"]["pret_aph"] = min(e["ex3"]["pret_rpv_aph"], e["ex3"]["pret_rcd_aph"])285 e["ex3"]["prime_aph"] = prime_aph(e["ex3"]["pret_aph"] / 2_600_000, 480); e["ex3"]["rpv_aph"] = e["ex3"]["pret_aph"] / 2_600_000286 # ex 4 : levier courant : R = 5,2 %, f = 6,0 % et 4,5 %, M = 0,70287 e["ex4"] = {M: {f: (0.052 - M * f) / (1 - M) for f in (0.060, 0.045)} for M in (0.0, 0.5, 0.7, 0.8)}288 # ex 5 : MQR et mise de fonds : prix 720 000 $, taux 4,58 %289 e["ex5"] = {"mdf": mise_de_fonds_minimale(720_000), "mqr": fin.taux_admissible_minimal(0.0458), "mqr_bas": fin.taux_admissible_minimal(0.0300),290 "paiement_contrat": fin.Hypotheque(720_000 - mise_de_fonds_minimale(720_000), 0.0458, 300).paiement,291 "paiement_mqr": fin.Hypotheque(720_000 - mise_de_fonds_minimale(720_000), fin.taux_admissible_minimal(0.0458), 300).paiement,292 "lti_revenu_requis": (720_000 - mise_de_fonds_minimale(720_000)) / 4.5}293 # ex 6 : renouvellement du cas 3 à +150 pb294 e["ex6"] = renouvellement(r, deltas=(0.015,))["tableau"].iloc[0].to_dict()295 # ex 7 : levier et TRI par RPV (cas 3) : 50 % et 85 %296 e["ex7"] = levier_par_rpv(r, (0.50, 0.85)).to_dict("records")297 e["ex7_k"] = financement(r)["k_annuel"]298 return e299300# ----------------------------------------------------------------------------- sortie301302def _d(x): return f"{x:,.0f}".replace(",", " ") + " $"303def _p(x, d=2): return f"{x*100:.{d}f}".replace(".", ",") + " %"304305def main():306 r = base(); fi = financement(r)307 print("=== Cas 3 — financement (traite.cas3.financement) ===")308 for k in ("valeur", "rne_1", "pret_rpv", "pret_rcd", "pret", "contrainte", "rpv", "mise_de_fonds", "paiement", "service_dette", "constante", "taux_mensuel", "taux_mensuel_us", "taux_effectif",309 "interets_an1", "capital_an1", "solde_60", "solde_120", "rcd", "flux_apres_dette_1", "cash_on_cash_1", "dette_sur_fp", "point_mort", "point_mort_hors_reserve",310 "rpv_max_par_rcd_sur_V", "rpv_max_par_rcd_sur_Vcap", "R_dcf", "R_median", "k_annuel", "tri_fp", "tri_sans_levier"):311 v = fi[k]; print(f"{k:28s} {v if not isinstance(v, float) else (round(v, 6) if abs(v) < 10 else round(v, 2))}")312 print("\n=== Conventions (prêt du cas 3) ===\n", conventions(fi["pret"]).T)313 print("\n=== Tableau d'amortissement — extraits ===\n", tableau_extraits(fi["hypotheque"]).round(2).to_string())314 dec = decomposition_annuelle(fi["hypotheque"]); print("\n=== Décomposition annuelle (années 1, 5, 10, 20, 30, 40) ===\n", dec[dec["annee"].isin([1, 5, 10, 20, 30, 40])].round(0).to_string())315 print("\n=== Levier courant ===\n", levier_courant(r))316 print("\n=== Levier par RPV ===\n", levier_par_rpv(r).round(4).to_string())317 print("\n=== Prêt maximal ===\n", pret_maximal(r))318 print("\n=== Grille prêt max (RCD) ===\n", grille_pret_max(r).round(4).to_string())319 print("\n=== APH Select ===\n", aph_select(r).round(4).to_string())320 rr = regles_residentielles(); print("\n=== Règles résidentielles ===\n", {k: v for k, v in rr.items() if k != "mise_de_fonds"}, "\n", rr["mise_de_fonds"].round(4).to_string())321 print("\n=== Stress RCD (taux + marge) ===\n", stress_rcd(r).round(4).to_string())322 ren = renouvellement(r); print("\n=== Renouvellement au terme ===\n", ren["tableau"].round(4).T.to_string(), "\nseuils :", {k: ren[k] for k in ("solde_terme", "n_residuel", "delta_rcd_120", "delta_rcd_100", "taux_rcd_120", "taux_rcd_100")})323 print("\n=== Risque ===\n", risque_levier(r)["grille"].round(4).to_string())324 s = sensibilite_service_dette(r, taux=np.array([0.03, 0.04, 0.0412, 0.05, 0.06, 0.07, 0.08])); print("\n=== Service de la dette vs taux ===\n", s.round(4).to_string())325 print("\n=== Exercices ===")326 e = exercices()327 for k, v in e.items(): print(k, v)328329if __name__ == "__main__":330 main()331