"""Tests du chapitre 29 — capitalisation directe (cas 3, données synthétiques). Identités financières + valeurs citées dans manuscript/chapters/ch29.tex et solutions/ch29.tex (tolérances déclarées).""" import sys, pathlib sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) import pytest from traite import cas3, finance as fin from ch29_capitalisation import calculer, exercices, droits_mutation ROOT = pathlib.Path(__file__).resolve().parents[3] @pytest.fixture(scope="module") def res(): return calculer() @pytest.fixture(scope="module") def ex(res): return exercices(res) # ----------------------------------------------------------------------------- identités def test_identite_capitalisation(res): assert res["V"] == pytest.approx(res["base"]["rne"] / res["R"], rel=1e-12) assert fin.taux_global_extrait(res["base"]["rne"], res["V"]) == pytest.approx(res["R"], rel=1e-12) def test_coherence_avec_cas3_partage(res): """Les chiffres du chapitre 29 coïncident avec ceux que les chapitres 30 et 47 tirent de traite.cas3.tout().""" t = cas3.tout() assert res["V"] == pytest.approx(t["valeur_capitalisation"], rel=1e-12) assert res["base"]["rne"] == pytest.approx(t["base"]["rne"], rel=1e-12) assert res["R"] == pytest.approx(t["taux"]["mediane"], rel=1e-12) def test_ellwood_egal_akerson(res): e = res["ellwood"] assert e["R_delta"] == pytest.approx(e["R_akerson"], abs=1e-12) # somme des composantes d'Akerson = taux assert sum(e["composantes"].values()) == pytest.approx(e["R_delta"], abs=1e-12) assert sum(e["composantes_marche"].values()) == pytest.approx(res["R"], abs=1e-9) # sans dette ni variation de valeur : R = Y assert e["R_sans_dette_delta0"] == pytest.approx(e["Y"], abs=1e-12) # Y implicite : réinjecté dans Ellwood, redonne le taux de marché h = fin.Hypotheque(1.0, res["hyp"]["financement"]["taux"], res["hyp"]["financement"]["amortissement_mois"]) assert fin.taux_ellwood(e["Y_implicite"], e["M"], e["f"], 1 - h.solde(120), e["n"], e["delta"]) == pytest.approx(res["R"], abs=1e-8) def test_bande_et_rcd(res): assert fin.taux_bande_investissement(res["M"], res["f"], res["re_implicite"]) == pytest.approx(res["R"], abs=1e-12) assert res["R_rcd"] == pytest.approx(1.20 * 0.75 * res["f"], rel=1e-12) # constante hypothécaire : 4,12 % composé semestriellement, 480 mois i_m = (1 + 0.0412 / 2) ** (1 / 6) - 1 assert res["f"] == pytest.approx(12 * i_m / (1 - (1 + i_m) ** -480), rel=1e-12) def test_gordon(res): g = res["gordon"] assert g["R_g_terminal"] == pytest.approx(g["r"] - g["g_terminal"], abs=1e-12) assert g["R_g_terminal"] == pytest.approx(res["hyp"]["taux_terminal"], abs=1e-12) with pytest.raises(ValueError): fin.taux_gordon(0.05, 0.05) def test_derivee_sensibilite(res): d = res["derive"]; V, R = res["V"], res["R"] assert d["dV_dR"] == pytest.approx(-V / R, rel=1e-12) # approximation linéaire vs variation exacte pour +25 pb : la linéarisation surestime la baisse assert d["var_plus25_exacte"] > d["var_plus25_lineaire"] assert d["var_plus25_exacte"] == pytest.approx(-0.0025 / (R + 0.0025), rel=1e-12) s = res["sensibilite"] assert (s["rne_p5"] / s["rne"] - 1).round(10).eq(0.05).all() assert (s["rne_m5"] / s["rne"] - 1).round(10).eq(-0.05).all() def test_droits_mutation_bareme(): assert droits_mutation(50_000) == pytest.approx(250.0) assert droits_mutation(62_900) == pytest.approx(314.5) assert droits_mutation(315_000) == pytest.approx(314.5 + 2521.0) assert droits_mutation(0) == 0.0 # ----------------------------------------------------------------------------- valeurs citées dans le texte def test_valeurs_citees_revenus(res): b = res["base"] assert b["rbp_contrat"] == pytest.approx(165_540, abs=0.5) assert b["rbp_marche"] == pytest.approx(186_036, abs=0.5) assert b["vacance_pertes"] == pytest.approx(6_456.06, abs=0.01) assert b["rbe"] == pytest.approx(168_443.94, abs=0.01) assert b["depenses_hors_reserve"] == pytest.approx(70_385.19, abs=0.01) assert b["reserve"] == pytest.approx(8_103, abs=0.01) assert b["rne"] == pytest.approx(89_955.75, abs=0.01) assert b["ratio_depenses"] == pytest.approx(0.4660, abs=5e-4) assert b["rne_par_logement"] == pytest.approx(7_496.31, abs=0.01) def test_valeurs_citees_taux(res): t = res["taux"] assert t["n"] == 40 assert t["mediane"] == pytest.approx(0.04415, abs=1e-6) assert t["moyenne"] == pytest.approx(0.044038, abs=1e-6) assert t["ponderee_rne"] == pytest.approx(0.043319, abs=1e-6) assert (t["q1"], t["q3"], t["min"], t["max"]) == pytest.approx((0.041525, 0.046375, 0.0372, 0.0547), abs=1e-6) r = res["regression"] assert r["constante"] == pytest.approx(0.04567, abs=1e-5) assert r["etat"] == pytest.approx(-0.000441, abs=1e-6) assert r["taille"] == pytest.approx(-0.000227, abs=1e-6) assert r["r2"] == pytest.approx(0.156, abs=1e-3) assert r["sigma_resid"] == pytest.approx(0.003825, abs=1e-6) assert res["mediane_proches"] == pytest.approx(0.0452, abs=1e-6) assert len(res["proches"]) == 10 and res["proches"].iloc[0]["id_vente"] == "M040" def test_valeurs_citees_valeur(res): assert res["V"] == pytest.approx(2_037_502.89, abs=0.01) assert res["V_arrondie"] == 2_040_000 assert res["V_reg"] == pytest.approx(1_969_694, abs=1) assert res["V_proches"] == pytest.approx(1_990_172, abs=1) assert res["V_q1"] == pytest.approx(1_939_747, abs=1) and res["V_q3"] == pytest.approx(2_166_303, abs=1) def test_valeurs_citees_construction(res): assert res["f"] == pytest.approx(0.050789, abs=1e-6) assert res["re_implicite"] == pytest.approx(0.02423, abs=1e-5) assert res["grille_re"][0.06] == pytest.approx(0.05309, abs=1e-5) assert res["R_rcd"] == pytest.approx(0.04571, abs=1e-5) assert res["V_rcd"] == pytest.approx(1_967_946, abs=1) g = res["gordon"] assert g["g_terminal"] == pytest.approx(0.0225, abs=1e-9) and g["R_g_tal"] == pytest.approx(0.0415, abs=1e-9) assert g["g_implicite_marche"] == pytest.approx(0.02835, abs=1e-6) e = res["ellwood"] assert e["P"] == pytest.approx(0.122504, abs=1e-6) assert e["SFF"] == pytest.approx(0.071527, abs=1e-6) assert e["delta"] == pytest.approx(0.249203, abs=1e-6) assert e["R_delta0"] == pytest.approx(0.049645, abs=1e-6) assert e["R_delta"] == pytest.approx(0.03182, abs=1e-5) assert e["R_sans_dette"] == pytest.approx(0.054675, abs=1e-6) assert e["delta_implicite"] == pytest.approx(0.076828, abs=1e-6) assert e["Y_implicite"] == pytest.approx(0.1070, abs=1e-4) assert e["V_delta"] == pytest.approx(2_826_982, abs=1) def test_valeurs_citees_multiplicateurs_coherence(res): m = res["mrb"] assert m["mediane"] == pytest.approx(13.54, abs=1e-6) and m["moyenne"] == pytest.approx(13.372, abs=1e-3) assert m["V_contrat"] == pytest.approx(2_241_412, abs=1) and m["V_marche"] == pytest.approx(2_518_927, abs=1) assert m["mrn"] == pytest.approx(22.650, abs=1e-3) and m["mrb_sujet_implicite"] == pytest.approx(12.308, abs=1e-3) assert m["cv"] > m["cv_taux"] c = res["coherence"] assert c["taxes_role"] == pytest.approx(11_925.98, abs=0.01) assert c["ecart_taxes"] == pytest.approx(880.88, abs=0.01) and c["effet_valeur"] == pytest.approx(19_952, abs=1) assert c["R_avant_reserve_equiv"] == pytest.approx(0.0481, abs=1e-4) assert c["V_si_taux_avant_reserve_sur_rne_apres"] == pytest.approx(1_869_136, abs=1) assert c["rne_marche"] == pytest.approx(108_864.54, abs=0.01) assert c["V_marche"] == pytest.approx(2_465_788, abs=1) and c["surevaluation_marche"] == pytest.approx(0.2102, abs=1e-4) mu = res["mutations"] assert mu["base"] == 2_200_000 and mu["droits"] == pytest.approx(45_460.5, abs=0.01) assert mu["droits_sur_prix"] == pytest.approx(40_763.07, abs=0.01) and mu["pct_prix"] == pytest.approx(0.0223, abs=1e-4) def test_valeurs_citees_sensibilite(res): s = res["sensibilite"].set_index("R") assert s.loc[0.0375, "rne"] == pytest.approx(2_398_820, abs=1) assert s.loc[0.0525, "rne"] == pytest.approx(1_713_443, abs=1) assert s.loc[0.045, "rne_p5"] == pytest.approx(2_098_968, abs=1) d = res["derive"] assert d["dV_25pb"] == pytest.approx(-115_374, abs=1) assert d["V_plus25"] == pytest.approx(1_928_312, abs=1) and d["V_moins25"] == pytest.approx(2_159_802, abs=1) assert d["var_plus25_exacte"] == pytest.approx(-0.0536, abs=1e-4) and d["var_plus25_lineaire"] == pytest.approx(-0.0566, abs=1e-4) def test_exercices(ex): assert ex["1"]["V"] == pytest.approx(1_288_421.05, abs=0.01) and ex["1"]["V_plus25"] == pytest.approx(1_224_000, abs=0.01) assert ex["1"]["var"] == pytest.approx(-0.05, abs=1e-9) assert ex["2"]["mediane"] == pytest.approx(0.044519, abs=1e-6) and ex["2"]["moyenne"] == pytest.approx(0.045395, abs=1e-6) assert ex["2"]["ponderee"] == pytest.approx(0.045277, abs=1e-6) and ex["2"]["V_mediane"] == pytest.approx(2_020_629, abs=1) assert ex["3"]["R_apres"] == pytest.approx(0.044118, abs=1e-6) and ex["3"]["R_avant"] == pytest.approx(0.047691, abs=1e-6) assert ex["3"]["V_errone"] == pytest.approx(1_886_214, abs=1) and ex["3"]["erreur"] == pytest.approx(-0.0749, abs=1e-4) assert ex["3"]["V_coherent_avant"] == pytest.approx(2_056_119, abs=1) assert ex["4"]["R_bande"] == pytest.approx(0.053092, abs=1e-6) and ex["4"]["R_rcd"] == pytest.approx(0.047615, abs=1e-6) assert ex["4"]["V_bande"] == pytest.approx(1_694_335, abs=1) and ex["4"]["V_rcd"] == pytest.approx(1_889_228, abs=1) assert ex["5"]["V_225"] == pytest.approx(1_799_115, abs=1) and ex["5"]["V_310"] == pytest.approx(2_167_608, abs=1) and ex["5"]["V_g4"] == pytest.approx(2_767_869, abs=1) assert ex["6"]["R_ellwood"] == pytest.approx(ex["6"]["R_akerson"], abs=1e-12) assert ex["6"]["R_ellwood"] == pytest.approx(0.031763, abs=1e-6) and ex["6"]["R_sans_dette"] == pytest.approx(0.054618, abs=1e-6) assert ex["6"]["g_equiv"] == pytest.approx(0.022565, abs=1e-6) and ex["6"]["R_gordon_equiv"] == pytest.approx(0.049935, abs=1e-6) assert ex["6"]["V"] == pytest.approx(2_832_053, abs=1) assert ex["7"]["ecart_rne"] == pytest.approx(18_908.79, abs=0.01) and ex["7"]["V_mrb_vs_cap"] == pytest.approx(0.1001, abs=1e-4) assert ex["8"]["R_24_etat2"] == pytest.approx(0.043381, abs=1e-6) and ex["8"]["R_6_etat4"] == pytest.approx(0.046594, abs=1e-6) assert ex["8"]["V_pire"] == pytest.approx(1_831_896, abs=1) and ex["8"]["V_meilleur"] == pytest.approx(2_267_792, abs=1) assert ex["8"]["var_pire"] == pytest.approx(-0.1009, abs=1e-4) and ex["8"]["var_meilleur"] == pytest.approx(0.1130, abs=1e-4) def test_figures_presentes(): for slug in ("taux-extraits", "taux-etat-taille", "surface-v", "bande-investissement", "taux-gdc"): assert (ROOT / f"assets/figures/ch29/ch29-fig-{slug}.pdf").exists(), slug