NicoSimu/Liquidite
0
1import gradio as gr2 3WEEKS_PER_MONTH = 52 / 12 # ~4.3334 5# -------------------------6# Helpers7# -------------------------8def euros(x: float) -> str:9 return f"{x:,.0f} €".replace(",", " ").replace(".", ",")10 11def pourcent(x: float, nd=2) -> str:12 return f"{x:.{nd}f} %"13 14def clamp(x: float, lo: float, hi: float) -> float:15 return max(lo, min(hi, x))16 17def f(x, default=0.0) -> float:18 try:19 return float(x)20 except Exception:21 return default22 23# ============================================================24# LOGIQUE "RECETTE" : je fixe le plafond interne -> trésorerie requise25# - Entrées : plafond interne par VL, fenêtre de stress (par défaut 52),26# loyers via taux de rendement, décote et plafond,27# financement adossé optionnel28# - Sortie : trésorerie minimale à détenir (montant + % AN)29# ============================================================30def calcul_tresorerie_minimale(31 actif_net_eur: float,32 plafond_interne_par_vl_pct_an: float,33 fenetre_stress_vl: int,34 inclure_loyers: bool,35 taux_rendement_loyers_pct: float,36 decote_loyers_pct: float,37 plafond_loyers_pct_an: float,38 inclure_financement_adosse: bool,39 mois_financement: int,40 decote_financement_pct: float,41):42 actif_net_eur = f(actif_net_eur)43 if actif_net_eur <= 0:44 return "Erreur : l'actif net doit être strictement positif."45 46 fenetre_stress_vl = max(1, int(fenetre_stress_vl))47 48 # 1) Demande maximale implicite (promesse client)49 cap = f(plafond_interne_par_vl_pct_an) / 100.050 sorties_max = actif_net_eur * cap * fenetre_stress_vl # sorties cumulées sur la fenêtre51 52 # 2) Loyers : via taux de rendement, puis prorata fenêtre, décote, et plafond53 loyers_annualises = 0.054 loyers_bruts_fenetre = 0.055 loyers_retenus = 0.056 57 if inclure_loyers:58 taux = f(taux_rendement_loyers_pct) / 100.059 loyers_annualises = max(0.0, actif_net_eur * taux)60 61 mois_fenetre = fenetre_stress_vl / WEEKS_PER_MONTH62 loyers_bruts_fenetre = loyers_annualises * (mois_fenetre / 12.0)63 64 facteur_loyers = clamp(1.0 - f(decote_loyers_pct) / 100.0, 0.0, 1.0)65 loyers_net_fenetre = loyers_bruts_fenetre * facteur_loyers66 67 # plafond des loyers comptés (anti-"liquidité magique")68 if f(plafond_loyers_pct_an) > 0:69 plafond_loyers_eur = actif_net_eur * (f(plafond_loyers_pct_an) / 100.0)70 loyers_retenus = min(loyers_net_fenetre, plafond_loyers_eur)71 else:72 loyers_retenus = loyers_net_fenetre73 74 # 3) Financement adossé (optionnel)75 financement = 0.076 if inclure_financement_adosse and inclure_loyers and loyers_annualises > 0:77 m = int(clamp(int(mois_financement), 0, 12))78 facteur_fin = clamp(1.0 - f(decote_financement_pct) / 100.0, 0.0, 1.0)79 financement = loyers_annualises * (m / 12.0) * facteur_fin80 81 # 4) Trésorerie minimale = ce qui manque pour honorer les sorties max82 tresorerie_min = max(0.0, sorties_max - loyers_retenus - financement)83 84 # 5) Ratios & contribution des loyers85 sorties_pct = (sorties_max / actif_net_eur) * 100 if actif_net_eur > 0 else 0.086 treso_pct = (tresorerie_min / actif_net_eur) * 100 if actif_net_eur > 0 else 0.087 88 # part des loyers dans le "financement" des sorties (sur la fenêtre)89 ressources = tresorerie_min + loyers_retenus + financement90 part_loyers = (loyers_retenus / ressources) * 100 if ressources > 0 else 0.091 part_fin = (financement / ressources) * 100 if ressources > 0 else 0.092 part_treso = (tresorerie_min / ressources) * 100 if ressources > 0 else 0.093 94 # textes utiles95 plafond_loyers_txt = "Aucun" if f(plafond_loyers_pct_an) == 0 else (pourcent(plafond_loyers_pct_an, 1) + " de l'actif net")96 97 out = []98 out.append(f"Actif net : {euros(actif_net_eur)}")99 out.append(f"Fenêtre de stress : {fenetre_stress_vl} VL (~{fenetre_stress_vl / WEEKS_PER_MONTH:.2f} mois)")100 out.append("")101 out.append("1) Promesse de liquidité (entrée)")102 out.append(f"- Plafond interne : {pourcent(plafond_interne_par_vl_pct_an,3)} de l'actif net PAR VL")103 out.append(f"- Sorties maximales sur la fenêtre : {euros(sorties_max)} ({pourcent(sorties_pct)} de l'actif net)")104 out.append("")105 out.append("2) Hypothèses loyers (si activé)")106 out.append(f"- Taux de rendement (loyers / actif net) : {pourcent(taux_rendement_loyers_pct,2)}")107 out.append(f"- Loyers annualisés (dérivés) : {euros(loyers_annualises)}")108 out.append(f"- Loyers bruts sur la fenêtre : {euros(loyers_bruts_fenetre)}")109 out.append(f"- Décote loyers : {pourcent(decote_loyers_pct,1)}")110 out.append(f"- Plafond des loyers comptés : {plafond_loyers_txt}")111 out.append(f"- Loyers retenus (après prudence) : {euros(loyers_retenus)}")112 out.append("")113 out.append("3) Financement adossé (si activé)")114 out.append(f"- Financement retenu : {euros(financement)}")115 out.append("")116 out.append("✅ Résultat clé (ce que tu cherchais)")117 out.append(f"- Trésorerie minimale à détenir : {euros(tresorerie_min)} ({pourcent(treso_pct)} de l'actif net)")118 out.append("")119 out.append("Poids des composantes dans la couverture des sorties (sur la fenêtre) :")120 out.append(f"- Trésorerie : {pourcent(part_treso,1)}")121 out.append(f"- Loyers futurs : {pourcent(part_loyers,1)}")122 out.append(f"- Financement adossé : {pourcent(part_fin,1)}")123 out.append("")124 out.append("Lecture simple : si ta trésorerie réelle est en-dessous de ce montant, alors ta promesse (plafond interne) n'est pas tenable sans vente d'actifs ou restrictions.")125 126 return "\n".join(out)127 128# ============================================================129# UI130# ============================================================131with gr.Blocks(title="Liquidité ELTIF — recette (plafond -> trésorerie)") as demo:132 gr.Markdown(133 "# Liquidité ELTIF — recette lisible\n"134 "*Tu fixes la promesse de rachat (plafond interne). L’outil calcule la trésorerie minimale à détenir.*\n"135 )136 137 gr.Markdown("## Paramètres du fonds")138 gr.Markdown("*Ces champs décrivent la taille du fonds et l’horizon sur lequel tu veux tester la tenue en cas de stress.*")139 140 with gr.Row():141 an = gr.Number(142 label="Actif net (EUR)",143 value=500_000_000,144 precision=0,145 info="Valeur totale du fonds. Tous les ratios se rapportent à cette base."146 )147 cap = gr.Number(148 label="Plafond interne par VL (% de l'actif net)",149 value=0.2,150 precision=3,151 info="Promesse de rachat par VL (ex : 0,20%). La trésorerie minimale sera calculée en conséquence."152 )153 fen = gr.Slider(154 label="Fenêtre de stress (nombre de VL)",155 minimum=1,156 maximum=52,157 step=1,158 value=52, # ✅ par défaut 52 comme tu veux159 info="Par défaut 52 VL (~12 mois), car c'est le test sérieux. Tu peux réduire si besoin."160 )161 162 gr.Markdown("## Loyers")163 gr.Markdown("*Au lieu d'un montant, on utilise un taux de rendement (loyers / actif net). Les loyers sont ensuite proratisés à la fenêtre, décotés, et plafonnés.*")164 165 with gr.Row():166 incl_loy = gr.Checkbox(167 label="Inclure les loyers",168 value=True,169 info="Si décoché, tu testes une version dure : seule la trésorerie compte."170 )171 taux_loy = gr.Slider(172 label="Taux de rendement des loyers (% de l'actif net par an)",173 minimum=0,174 maximum=15,175 step=0.1,176 value=7.0,177 info="Ex : 7% = loyers annualisés = 7% de l'actif net."178 )179 180 with gr.Row():181 dec_loy = gr.Slider(182 label="Décote loyers (%)",183 minimum=0,184 maximum=60,185 step=1,186 value=20,187 info="Marge de prudence (vacance, impayés, retards, renégociations)."188 )189 cap_loy = gr.Slider(190 label="Plafond des loyers comptés (% de l'actif net) — 0 = aucun",191 minimum=0,192 maximum=50,193 step=0.5,194 value=10.0,195 info="Anti-'liquidité magique' : limite la part de loyers futurs que tu acceptes de compter dans le test."196 )197 198 gr.Markdown("## Financement adossé aux loyers")199 gr.Markdown("*À activer uniquement si tu as une ligne réellement contractualisée et tirable en stress. Sinon, l’AMF considérera que ça ne vaut rien.*")200 201 with gr.Row():202 fin_on = gr.Checkbox(203 label="Inclure un financement adossé aux loyers",204 value=False,205 info="Active une ressource bancaire/financement court terme adossée aux loyers."206 )207 fin_mois = gr.Slider(208 label="Mois finançables (0..12)",209 minimum=0,210 maximum=12,211 step=1,212 value=6,213 info="Nombre de mois de loyers que tu considères monétisables via la ligne."214 )215 fin_dec = gr.Slider(216 label="Décote sur financement (%)",217 minimum=0,218 maximum=70,219 step=1,220 value=35,221 info="Décote prudente (covenants, clauses de marché, capacité réelle à tirer en crise)."222 )223 224 gr.Markdown("## Calcul")225 gr.Markdown("*Le résultat te donne la trésorerie minimale à détenir pour tenir ta promesse de rachat sur la fenêtre.*")226 227 btn = gr.Button("Calculer la trésorerie minimale à détenir")228 out = gr.Textbox(label="Résultat", lines=28)229 230 btn.click(231 calcul_tresorerie_minimale,232 inputs=[an, cap, fen, incl_loy, taux_loy, dec_loy, cap_loy, fin_on, fin_mois, fin_dec],233 outputs=out234 )235 236demo.launch()237 