SPB Git
9.3 KB · 118 lines python
1"""Tests du chapitre 47 (cas 3, 12 logements à Montréal — données synthétiques) : identités, valeurs citées dans le texte,2reproductibilité du Monte-Carlo. Tolérances déclarées : montants ±1 $ (valeurs citées au dollar), taux ±1e-6, MC ±1 $ (même graine)."""3import sys, pathlib4import numpy as np, pytest5sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))6from traite import cas3, finance as fin7import ch47_dossier as ch89@pytest.fixture(scope="module")10def D():11    return ch.dossier(n_mc=10_000, graine=ch.GRAINE)1213# ----------------------------------------------------------------------------- identités1415def test_identites_revenus_depenses(D):16    b = D["base"]17    assert b["rbp_contrat"] == pytest.approx(D["etat"]["loyer_mensuel_contrat"].sum() * 12)18    assert b["rbe"] == pytest.approx(b["rbp_contrat"] * (1 - 0.039) + 9360)19    assert b["rne"] == pytest.approx(b["rbe"] - b["depenses_hors_reserve"] - b["reserve"])20    det = b["detail"]21    assert det.loc[det["poste"] == "Taxes municipales", "normalise"].iloc[0] == pytest.approx(2_200_000 * 0.004631)22    assert det.loc[det["poste"].str.startswith("Gestion"), "normalise"].iloc[0] == pytest.approx(0.04 * b["rbe"])2324def test_capitalisation_et_dcf(D):25    b, tx, d, h = D["base"], D["taux"], D["dcf"], D["hyp"]26    assert tx["n"] == 4027    assert D["valeur_capitalisation"] == pytest.approx(b["rne"] / tx["mediane"])28    p = D["projection"]29    flux = p.loc[p["annee"] <= 10, "flux_avant_dette"].tolist()30    va = sum(cf / (1 + h["taux_actualisation"]) ** (t + 1) for t, cf in enumerate(flux)) + d["vt_nette"] / (1 + h["taux_actualisation"]) ** 1031    assert d["valeur"] == pytest.approx(va)32    assert d["vt_brute"] == pytest.approx(d["rne_T1"] / h["taux_terminal"]) and d["vt_nette"] == pytest.approx(d["vt_brute"] * 0.975)33    # taux implicite de première année de la valeur DCF et TRI non levier = taux d'actualisation34    assert D["financement_dcf"]["tri_sans_levier"] == pytest.approx(h["taux_actualisation"], abs=1e-8)3536def test_financement_identites(D):37    f = D["financement"]; h = f["hypotheque"]38    assert f["contrainte"] == "RCD" and f["pret"] == pytest.approx(f["pret_rcd"])39    assert f["rcd"] == pytest.approx(1.20) and f["rpv"] <= 0.7540    assert h.taux_periodique == pytest.approx((1 + 0.0412 / 2) ** (1 / 6) - 1)41    assert h.service_dette_annuel == pytest.approx(12 * h.paiement)42    assert h.solde(480) == pytest.approx(0.0, abs=1e-6)43    ff = D["financement_final"]44    assert fin.van(ff["flux_fonds_propres"], ff["tri_fonds_propres"]) == pytest.approx(0.0, abs=1e-3)   # VAN nulle au TRI4546def test_projection_vectorisee_reproduit_cas3(D):47    """La projection vectorisée (Monte-Carlo) doit coïncider exactement avec cas3.projection_dcf aux paramètres de base."""48    h, b, e = D["hyp"], D["base"], D["etat"]49    one = lambda x: np.array([x])50    proj = ch.projeter_vectorise(e, h, b, one(h["taux_inoccupation_pertes"]), one(h["croissance_loyers_marche"]), one(h["rotation_annuelle"]), one(h["croissance_depenses"]), np.ones((1, 3)))51    p = D["projection"]52    assert np.allclose(proj["rne"][0], p["rne"].values, rtol=1e-12)53    assert np.allclose(proj["flux"][0], p.loc[p["annee"] <= 10, "flux_avant_dette"].values, rtol=1e-12)54    v = ch.valeur_vectorisee(proj, one(h["taux_actualisation"]), one(h["taux_terminal"]), h["couts_vente"])55    assert v["valeur"][0] == pytest.approx(D["dcf"]["valeur"], rel=1e-12)56    assert D["tornade"]["V_base"].iloc[0] == pytest.approx(D["dcf"]["valeur"], rel=1e-9)5758def test_droits_mutation_montreal():59    assert ch.droits_mutation_montreal(2_060_000) == pytest.approx(41_325.50)60    assert ch.droits_mutation_montreal(62_900) == pytest.approx(314.50)61    assert ch.droits_mutation_montreal(0) == 0.06263# ----------------------------------------------------------------------------- valeurs citées dans ch47.tex (tolérance ±1 $ sauf indication)6465def test_valeurs_citees(D):66    b, tx, d, f, rec, cu = D["base"], D["taux"], D["dcf"], D["financement"], D["reconciliation"], D["comparaison_unite"]67    assert b["rbp_contrat"] == pytest.approx(165_540, abs=1) and b["rbp_marche"] == pytest.approx(186_036, abs=1)68    assert b["rbe"] == pytest.approx(168_443.94, abs=0.01)69    assert b["depenses_hors_reserve"] == pytest.approx(70_385.19, abs=0.01) and b["reserve"] == pytest.approx(8_103, abs=0.01)70    assert b["rne"] == pytest.approx(89_955.75, abs=0.01)71    assert tx["mediane"] == pytest.approx(0.04415, abs=1e-6) and tx["moyenne"] == pytest.approx(0.0440375, abs=1e-6) and tx["ponderee_rne"] == pytest.approx(0.043319, abs=1e-6)72    assert tx["q1"] == pytest.approx(0.041525, abs=1e-6) and tx["q3"] == pytest.approx(0.046375, abs=1e-6)73    assert D["valeur_capitalisation"] == pytest.approx(2_037_502.89, abs=0.01)74    assert d["valeur"] == pytest.approx(2_208_565.91, abs=0.01) and d["va_flux"] == pytest.approx(662_409.91, abs=0.01) and d["va_terminale"] == pytest.approx(1_546_156.00, abs=0.01)75    assert d["vt_brute"] == pytest.approx(3_193_167.52, abs=0.01) and d["rne_T1"] == pytest.approx(159_658.38, abs=0.01) and d["part_terminale"] == pytest.approx(0.7001, abs=1e-4)76    assert f["pret_rcd"] == pytest.approx(1_475_959.45, abs=0.01) and f["pret_rpv"] == pytest.approx(1_656_424.43, abs=0.01)77    assert f["service_dette"] == pytest.approx(74_963.13, abs=0.01) and f["constante"] == pytest.approx(0.050789, abs=1e-6) and f["hypotheque"].paiement == pytest.approx(6_246.93, abs=0.01)78    assert f["hypotheque"].solde(60) == pytest.approx(1_394_739.19, abs=0.01)79    assert D["financement_dcf"]["tri_fonds_propres"] == pytest.approx(0.1122, abs=1e-4) and D["financement_dcf"]["point_mort"] == pytest.approx(0.927, abs=1e-3)80    assert cu["indication"] == pytest.approx(1_870_902.67, abs=0.01) and cu["prix_par_logement_retenu"] == pytest.approx(155_908.56, abs=0.01)81    assert cu["mrb_mediane"] == pytest.approx(11.94, abs=1e-9) and cu["indication_mrb"] == pytest.approx(1_976_547.60, abs=0.01)82    assert cu["ols"]["coef"]["ln_loyer"] == pytest.approx(1.1707, abs=1e-4) and cu["ols"]["coef"]["taille"] == pytest.approx(0.0079, abs=1e-4)83    assert rec["valeur_ponderee"] == pytest.approx(2_064_054.90, abs=0.01) and rec["valeur_finale"] == 2_060_00084    assert rec["ratio_valeur_role"] == pytest.approx(0.9364, abs=1e-4) and rec["mrb_contrat"] == pytest.approx(12.44, abs=0.01) and rec["taux_implicite"] == pytest.approx(0.04367, abs=1e-5)85    ff = D["financement_final"]86    assert ff["mise_de_fonds"] == pytest.approx(584_040.55, abs=0.01) and ff["rpv"] == pytest.approx(0.7165, abs=1e-4) and ff["tri_fonds_propres"] == pytest.approx(0.1383, abs=1e-4)87    assert D["libre_de_baux"]["rne"] == pytest.approx(108_864.54, abs=0.01) and D["libre_de_baux"]["valeur"] == pytest.approx(2_465_788.04, abs=0.01)88    sc = D["scenarios"]89    assert sc.loc[sc["hausse_pb"] == 200, "service_dette"].iloc[0] == pytest.approx(95_920.20, abs=0.01) and sc.loc[sc["hausse_pb"] == 300, "rcd_6"].iloc[0] == pytest.approx(1.222, abs=1e-3)90    aph = D["aph"]91    assert aph.loc[aph["amortissement_ans"] == 40, "pret"].iloc[0] == pytest.approx(1_610_137.58, abs=0.01)9293def test_monte_carlo_citations_et_reproductibilite(D):94    mc = D["mc"]; st = mc["stats"]; pr = mc["probabilites"]95    assert mc["n"] == 10_000 and len(mc["valeur"]) == 10_00096    assert st["q05"] < st["q25"] < st["mediane"] < st["q75"] < st["q95"]97    assert st["mediane"] == pytest.approx(2_191_607.87, abs=1) and st["q05"] == pytest.approx(1_735_194.21, abs=1) and st["q95"] == pytest.approx(2_876_902.35, abs=1)98    assert st["moyenne"] == pytest.approx(2_233_505.68, abs=1) and st["ecart_type"] == pytest.approx(350_442.57, abs=1)99    assert pr["rcd1_sous_1.20"] == pytest.approx(0.4936, abs=1e-4) and pr["rcd1_sous_1.00"] == 0.0 and pr["rcd6_choc200_sous_1.20"] == pytest.approx(0.0140, abs=1e-4)100    assert pr["V_sous_cap"] == pytest.approx(0.3094, abs=1e-4) and pr["V_sous_role"] == pytest.approx(0.5118, abs=1e-4)101    # même graine → résultats identiques ; graine différente → distribution voisine (±1,5 % sur la médiane)102    mc2 = ch.monte_carlo({k: D[k] for k in ["etat", "hyp", "base", "financement", "valeur_capitalisation"]}, 10_000, ch.GRAINE)103    assert np.array_equal(mc2["valeur"], mc["valeur"])104    mc3 = ch.monte_carlo({k: D[k] for k in ["etat", "hyp", "base", "financement", "valeur_capitalisation"]}, 10_000, 7)105    assert mc3["stats"]["mediane"] == pytest.approx(st["mediane"], rel=0.015)106    # la médiane du RCD de première année est au seuil 1,20 (prêt calibré au seuil)107    assert float(np.median(mc["rcd"][:, 0])) == pytest.approx(1.20, abs=0.02)108109def test_exercices(D):110    ex = ch.exercices(D, D["comparaison_unite"], D["reconciliation"])111    assert ex["ex1"]["rne"] == pytest.approx(88_207.65, abs=0.01) and ex["ex1"]["valeur"] == pytest.approx(1_997_908.27, abs=0.01)112    assert ex["ex2"]["rne"] == pytest.approx(88_949.51, abs=0.01)113    assert ex["ex3"]["V_q1"] == pytest.approx(2_166_303.49, abs=0.01) and ex["ex3"]["V_q3"] == pytest.approx(1_939_746.68, abs=0.01)114    assert ex["ex4_rot10"]["valeur"] == pytest.approx(2_140_916.43, abs=0.01) and ex["ex4_rot25"]["valeur"] == pytest.approx(2_231_654.72, abs=0.01)115    assert ex["ex5"]["pret"] == pytest.approx(1_283_814.77, abs=0.01) and ex["ex5"]["contrainte"] == "RCD"116    assert ex["ex6"]["pret"] == pytest.approx(1_610_137.58, abs=0.01) and ex["ex6"]["prime_pct"] == pytest.approx(0.051, abs=1e-6)117    assert ex["ex7"]["p_rcd1_sous_1.20"] == pytest.approx(0.9262, abs=1e-4) and ex["ex7"]["mediane"] == pytest.approx(2_108_706.66, abs=1)118
Files · 8e522ed3d370f600dee36015720c0600cbb26a05
Menu

Theme