spb/traite-evaluation-qc
Public
1"""Tests du chapitre 30 — flux actualisés et valeur terminale (cas 3, données synthétiques).2Identités : VAN(TRI) = 0 ; VT = RNE_{T+1}/R_T × (1 − c_v) ; DCF = Σ VA + VA(VT) ; cohérence Gordon (DCF = RNE_1/(r − g)3sous croissance constante, quel que soit T) ; flux après dette = flux − SD ; valeurs citées dans manuscript/chapters/ch30.tex."""4import math, pytest5from traite import finance as fin6from cases import ch30_dcf as c78@pytest.fixture(scope="module")9def R():10 return c.base()1112# ------------------------------------------------------------------ valeurs citées (tolérances déclarées)1314def test_valeurs_citees_dcf(R):15 d = R["dcf"]; h = R["hyp"]16 assert d["valeur"] == pytest.approx(2_208_566, abs=1) # valeur DCF17 assert d["va_flux"] == pytest.approx(662_410, abs=1)18 assert d["va_terminale"] == pytest.approx(1_546_156, abs=1)19 assert d["vt_brute"] == pytest.approx(3_193_168, abs=1)20 assert d["vt_nette"] == pytest.approx(3_113_338, abs=1)21 assert d["rne_T1"] == pytest.approx(159_658, abs=1)22 assert d["part_terminale"] == pytest.approx(0.700, abs=0.001)23 assert R["valeur_capitalisation"] == pytest.approx(2_037_503, abs=1)24 assert R["taux"]["mediane"] == pytest.approx(0.04415, abs=1e-6)25 assert R["base"]["rne"] == pytest.approx(89_956, abs=1)26 assert h["taux_actualisation"] == 0.0725 and abs(h["taux_terminal"] - 0.05) < 1e-12 and h["couts_vente"] == 0.025 and h["horizon_ans"] == 102728def test_tableau_dcf_identites(R):29 t = c.tableau_dcf(R); h = R["hyp"]; T = h["horizon_ans"]30 tt = t[t["annee"] <= T]31 assert len(t) == T + 132 # RNE = RBE − dépenses − réserve ; flux = RNE − CAPEX ; VA = flux × facteur ; Σ VA = va_flux33 assert (tt["rne"] - (tt["rbe"] - tt["depenses"] - tt["reserve"])).abs().max() < 1e-634 assert (tt["flux_avant_dette"] - (tt["rne"] - tt["capex"])).abs().max() < 1e-635 assert (tt["va"] - tt["flux_avant_dette"] * (1 + h["taux_actualisation"]) ** (-tt["annee"])).abs().max() < 1e-636 assert tt["va"].sum() == pytest.approx(R["dcf"]["va_flux"], rel=1e-12)37 assert tt["va_cumulee"].iloc[-1] == pytest.approx(R["dcf"]["va_flux"], rel=1e-12)38 assert tt["capex"].sum() == pytest.approx(85_000 + 40_000 + 120_000)39 assert (t["rbp"] <= t["rbp_marche"] + 1e-6).all()4041def test_valeur_terminale(R):42 d = R["dcf"]; h = R["hyp"]43 vt_b, vt_n = fin.valeur_terminale(d["rne_T1"], h["taux_terminal"], h["couts_vente"])44 assert vt_b == pytest.approx(d["rne_T1"] / h["taux_terminal"]) and vt_n == pytest.approx(vt_b * (1 - h["couts_vente"]))45 assert d["va_terminale"] == pytest.approx(vt_n * fin.facteur_va(h["taux_actualisation"], h["horizon_ans"]), rel=1e-12)46 # actualiser à T+1 au lieu de T serait une erreur de (1+r)^-1 : on vérifie l'ampleur citée (≈ 104 519 $)47 erreur = vt_n * fin.facteur_va(h["taux_actualisation"], h["horizon_ans"]) - vt_n * fin.facteur_va(h["taux_actualisation"], h["horizon_ans"] + 1)48 assert erreur == pytest.approx(104_519, abs=1)49 assert d["valeur"] == pytest.approx(d["va_flux"] + d["va_terminale"], rel=1e-12)5051# ------------------------------------------------------------------ TRI / VAN5253def test_van_au_tri_nulle_sans_levier(R):54 sl = c.dcf_sans_levier(R)55 assert sl["tri"] == pytest.approx(R["hyp"]["taux_actualisation"], abs=1e-9) # acheter à V_0 rapporte exactement r56 assert abs(sl["van_au_taux"]) < 1e-457 assert abs(fin.van(sl["flux"], sl["tri"])) < 1e-458 assert sl["mirr"] == pytest.approx(R["hyp"]["taux_actualisation"], abs=1e-9) # MIRR(r, r) = r quand VAN(r) = 059 assert 9.0 < sl["delai_recuperation"] < 10.06061def test_levier(R):62 lv = c.flux_levier(R); f = R["financement"]; h = R["hyp"]; T = h["horizon_ans"]63 hyp = f["hypotheque"]64 assert lv["service_dette"] == pytest.approx(hyp.paiement * 12)65 assert lv["mise_de_fonds"] == pytest.approx(R["dcf"]["valeur"] - f["pret"])66 assert lv["solde_final"] == pytest.approx(hyp.solde(T * 12))67 tab = lv["tableau"]68 assert (tab["flux_apres_dette"] - (tab["flux_avant_dette"] - tab["service_dette"])).abs().max() < 1e-669 assert tab["flux_fonds_propres"].iloc[-1] == pytest.approx(tab["flux_apres_dette"].iloc[-1] + R["dcf"]["vt_nette"] - lv["solde_final"])70 assert abs(fin.van(lv["flux"], lv["tri"])) < 1e-4 # VAN(TRI) = 071 assert lv["changements_signe"] == 5 # flux non conventionnels (CAPEX 2027, 2032)72 assert lv["tri"] == pytest.approx(0.1122, abs=0.0005)73 assert lv["mirr"] == pytest.approx(0.1061, abs=0.0005)74 assert h["financement"]["taux"] < lv["mirr"] < lv["tri"] # MIRR entre taux de financement et TRI ici75 assert lv["tri"] > h["taux_actualisation"] # levier positif76 assert lv["mise_de_fonds"] == pytest.approx(732_606, abs=1) and lv["solde_final"] == pytest.approx(1_295_148, abs=1)77 assert lv["rcd_1"] == pytest.approx(1.20, abs=1e-6)78 # composition semestrielle : i_m = (1 + i/2)^(1/6) − 1 < i/1279 assert lv["taux_mensuel"] == pytest.approx((1 + h["financement"]["taux"] / 2) ** (1 / 6) - 1) and lv["taux_mensuel"] < h["financement"]["taux"] / 1280 assert lv["taux_effectif_annuel_hyp"] == pytest.approx((1 + h["financement"]["taux"] / 2) ** 2 - 1)8182# ------------------------------------------------------------------ cohérence DCF ↔ capitalisation directe (Gordon)8384@pytest.mark.parametrize("T", [1, 5, 10, 25, 60])85def test_proposition_gordon(T):86 v = c.verification_gordon(noi1=100_000, r=0.0725, g=0.035, T=T)87 assert v["V_dcf"] == pytest.approx(v["V_gordon"], rel=1e-10)88 # la part de la valeur terminale décroît avec T et vaut ((1+g)/(1+r))^T89 assert v["part_terminale"] == pytest.approx(((1 + 0.035) / (1 + 0.0725)) ** T, rel=1e-9)9091def test_gordon_avec_couts_de_vente_brise_l_egalite():92 v = c.verification_gordon(noi1=100_000, r=0.0725, g=0.035, T=10, couts_vente=0.025)93 assert v["V_dcf"] < v["V_gordon"]94 assert v["V_gordon"] - v["V_dcf"] == pytest.approx(0.025 * v["V_gordon"] * ((1 + 0.035) / (1 + 0.0725)) ** 10, rel=1e-9)9596def test_taux_implicites(R):97 ti = c.taux_implicites(R); h = R["hyp"]98 assert ti["R_implicite_dcf"] == pytest.approx(R["base"]["rne"] / R["dcf"]["valeur"])99 assert ti["R_implicite_dcf"] == pytest.approx(0.0407, abs=0.0001)100 assert ti["g_implicite_terminal"] == pytest.approx(h["taux_actualisation"] - h["taux_terminal"])101 assert ti["croissance_rne_annualisee"] == pytest.approx(0.0590, abs=0.0005)102 assert ti["tri_si_paye_V_cap"] == pytest.approx(0.0825, abs=0.0005)103 assert ti["tri_si_paye_V_cap"] > h["taux_actualisation"] # payer moins que V_0 rapporte plus que r104 assert ti["ecart_dcf_cap"] == pytest.approx(171_063, abs=1)105 assert fin.taux_gordon(h["taux_actualisation"], h["croissance_loyers_marche"]) == pytest.approx(0.0375)106107# ------------------------------------------------------------------ sensibilité et scénarios108109def test_sensibilite_monotone(R):110 s = c.sensibilite_taux_terminal(R)111 assert (s["valeur"].diff().dropna() < 0).all() # V décroît en R_T112 assert s.loc[(s["taux_terminal"] - 0.05).abs() < 1e-9, "valeur"].iloc[0] == pytest.approx(R["dcf"]["valeur"])113 assert s["valeur"].iloc[0] == pytest.approx(2_481_417, abs=1) and s["valeur"].iloc[-1] == pytest.approx(2_006_893, abs=1)114 S = c.surface_r_Rt(R)115 assert (S.diff().dropna() < 0).values.all() and (S.T.diff().dropna() < 0).values.all() # V décroît en r et en R_T116 G = c.surface_r_g(R)117 assert (G.T.diff().dropna() > 0).values.all() # V croît avec g_marché118119def test_tornade_et_scenarios(R):120 t = c.tornade(R)121 assert t["intrant"].iloc[0].startswith("Croissance des loyers de marché")122 assert (t["amplitude"].diff().dropna() <= 1e-9).all()123 assert t["amplitude"].iloc[0] == pytest.approx(368_235, abs=1)124 s = c.scenarios(R).set_index("scenario")125 assert s.loc["Base", "valeur"] == pytest.approx(R["dcf"]["valeur"])126 assert s.loc["Base", "tri_sans_levier_prix_base"] == pytest.approx(0.0725, abs=1e-9)127 assert s.loc["Encadrement serré", "valeur"] == pytest.approx(1_912_046, abs=1)128 assert s.loc["Hausse des taux", "valeur"] == pytest.approx(1_947_889, abs=1)129 assert s.loc["Encadrement serré", "tri_sans_levier_prix_base"] < 0.0725 and s.loc["Hausse des taux", "tri_sans_levier_prix_base"] < 0.0725130 assert s.loc["Hausse des taux", "rcd_min"] < 1.2131132def test_exercices_coherence():133 e = c.exercices()134 assert e["ex1"]["R"] == pytest.approx(0.0375)135 assert e["ex2"]["va_10"] - e["ex2"]["va_11_erronee"] == pytest.approx(e["ex2"]["ecart"])136 f5 = e["ex3"]137 assert f5["valeur"] == pytest.approx(f5["va_flux"] + f5["va_vt"])138 assert f5["van_prix"] < 0 and f5["tri_prix"] < 0.07139 assert e["ex4"]["i_m_can"] < e["ex4"]["i_m_us"] and e["ex4"]["paiement_can"] < e["ex4"]["paiement_us"]140 for T, v in e["ex5"].items():141 assert v["V_dcf"] == pytest.approx(v["V_gordon"], rel=1e-10)142 assert e["ex6"]["valeur"] == pytest.approx(1_986_966, abs=1)143 assert abs(fin.van(e["ex7"]["flux"], e["ex7"]["tri"])) < 1e-4144