diff --git a/frontend/src/pages/Plateformes.jsx b/frontend/src/pages/Plateformes.jsx index d7380e1..1bd11b6 100644 --- a/frontend/src/pages/Plateformes.jsx +++ b/frontend/src/pages/Plateformes.jsx @@ -904,6 +904,76 @@ export default function Plateformes() { return { brut: xirr(flowsBrut), net: xirr(flowsNet), estime, cutoff }; }, [chartRows, allRembs, allReinvests, selectedYear, reinvestCumulParInv, capRembParInv]); + /* ── XIRR "attendu" (théorique) — mêmes flux réels que ci-dessus, mais les prêts + non soldés sont valorisés via leur échéancier prévisionnel (simul_remboursements) + projeté jusqu'à leur terme, plutôt qu'un unique flux "capital restant racheté + aujourd'hui". Reproduit la logique des taux affichés par les plateformes elles- + mêmes (ex. "Mon taux de rendement interne" chez Bienprêter, "X %/an" chez La + Première Brique) : optimiste, suppose que toutes les échéances prévues seront + honorées à la date prévue — y compris pour les prêts en_retard/procédure (le + KPI "Capital en risque" signale déjà ce risque séparément). + Fallback : si un prêt non soldé n'a aucune échéance future en base (schéma + manquant), on retombe sur la valorisation "capital restant au cutoff" comme + pour le XIRR réel, pour ne jamais sous-évaluer un prêt faute de données. ── */ + const plateformeXirrAttendu = useMemo(() => { + const cutoff = selectedYear ? `${selectedYear}-12-31` : today(); + const ids = new Set(chartRows.map(r => r.id)); + const flowsBrut = []; + const flowsNet = []; + for (const r of chartRows) { + if (!r.date_souscription) continue; + flowsBrut.push({ amount: -r.montant_investi, date: r.date_souscription }); + flowsNet.push({ amount: -r.montant_investi, date: r.date_souscription }); + } + for (const rv of allReinvests) { + if (!ids.has(rv.investissement_id)) continue; + const d = rv.date_reinvestissement?.slice(0, 10); + if (!d || d > cutoff) continue; + flowsBrut.push({ amount: -(rv.montant || 0), date: d }); + flowsNet.push({ amount: -(rv.montant || 0), date: d }); + } + for (const rb of allRembs) { + if (!ids.has(rb.investissement_id)) continue; + const d = rb.date_remb?.slice(0, 10); + if (!d || d > cutoff) continue; + flowsBrut.push({ amount: (rb.capital || 0) + (rb.cashback || 0) + (rb.interets_bruts || 0), date: d }); + flowsNet.push({ amount: rb.net_recu || 0, date: d }); + } + // Échéances futures prévues, groupées par investissement + const futureSimulParInv = {}; + for (const sr of allSimulRembs) { + if (!ids.has(sr.investissement_id)) continue; + const d = sr.date_prevue?.slice(0, 10); + if (!d || d <= cutoff) continue; + if (!futureSimulParInv[sr.investissement_id]) futureSimulParInv[sr.investissement_id] = []; + futureSimulParInv[sr.investissement_id].push({ date: d, capital: sr.capital_prevu || 0, interets: sr.interets_prevus || 0 }); + } + for (const r of chartRows) { + const capInv = r.montant_investi + (reinvestCumulParInv[r.id] || 0); + const capRemb = capRembParInv[r.id] || 0; + const capRestant = Math.max(0, capInv - capRemb); + if (capRestant <= 0.01) continue; + const echeances = futureSimulParInv[r.id]; + if (echeances && echeances.length) { + for (const e of echeances) { + if (e.capital <= 0 && e.interets <= 0) continue; + const rate = getRatesForYear(e.date); + const pfu = rate ? (rate.prelev_sociaux + rate.impot_revenu) / 100 : 0.3; + flowsBrut.push({ amount: e.capital + e.interets, date: e.date }); + flowsNet.push({ amount: e.capital + e.interets * (1 - pfu), date: e.date }); + } + } else { + // Pas d'échéancier prévisionnel disponible : fallback identique au XIRR réel + flowsBrut.push({ amount: capRestant, date: cutoff }); + flowsNet.push({ amount: capRestant, date: cutoff }); + } + } + const byDate = (a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0); + flowsBrut.sort(byDate); + flowsNet.sort(byDate); + return { brut: xirr(flowsBrut), net: xirr(flowsNet) }; + }, [chartRows, allRembs, allReinvests, allSimulRembs, selectedYear, reinvestCumulParInv, capRembParInv, pfuRates]); + const netMode = displayMode === 'net'; const prevYearLabel = selectedYear ? Number(selectedYear) - 1 : new Date().getFullYear() - 1; @@ -1558,7 +1628,9 @@ export default function Plateformes() { {/* 7 — XIRR */} {(() => { const rendement = netMode ? plateformeXirr.net : plateformeXirr.brut; + const rendementAttendu = netMode ? plateformeXirrAttendu.net : plateformeXirrAttendu.brut; const cutoffLabel = selectedYear ? `31/12/${selectedYear}` : "aujourd'hui"; + const periodLabel = selectedYear ? `Sur l'année ${selectedYear}` : "Depuis le début"; const xirrExplication = `XIRR (taux de rendement interne actualisé) : taux annualisé calculé à partir de l'ensemble des flux réels datés ` + `des prêts de cette sélection (versements initiaux, réinvestissements, remboursements ${netMode ? 'nets, après fiscalité' : 'bruts, avant fiscalité'}). ` + @@ -1566,6 +1638,15 @@ export default function Plateformes() { (plateformeXirr.estime ? `Certains prêts ne sont pas encore soldés : leur capital restant dû est ajouté en flux final, valorisé au ${cutoffLabel} — c'est donc une estimation, pas un taux définitif.` : 'Tous les prêts de cette sélection sont soldés : taux définitif.'); + const xirrAttenduExplication = + `XIRR attendu (théorique) : comme le taux de rendement interne affiché par les plateformes de crowdlending ` + + `(ex. "Mon taux de rendement interne" chez Bienprêter, le %/an affiché chez La Première Brique), ce calcul suppose ` + + `que toutes les échéances prévues (capital + intérêts) des prêts encore en cours seront honorées exactement à la ` + + `date prévue par l'échéancier — contrairement au XIRR ci-dessus, qui valorise prudemment le capital restant dû ` + + `"comme racheté au ${cutoffLabel}", sans compter les intérêts futurs.\n\n` + + `C'est donc une projection optimiste : elle ne tient pas compte d'un éventuel retard ou défaut de paiement ` + + `(voir le KPI "Capital en risque" pour ce risque). Si un prêt n'a pas d'échéancier prévisionnel en base, il est ` + + `valorisé de la même façon que dans le XIRR réel.`; return (