SPB Git
24.1 KB · 331 lines python
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
Files · 960e587174484ddf8cb3579ab544c4d47de3ba3c
Menu

Theme