KPI Taux attendus
This commit is contained in:
1 parent
b48731ec50
commit
5437091884
1 file changed
+101
-1
@@ -904,6 +904,76 @@ export default function Plateformes() {
|
|||||||
return { brut: xirr(flowsBrut), net: xirr(flowsNet), estime, cutoff };
|
return { brut: xirr(flowsBrut), net: xirr(flowsNet), estime, cutoff };
|
||||||
}, [chartRows, allRembs, allReinvests, selectedYear, reinvestCumulParInv, capRembParInv]);
|
}, [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 netMode = displayMode === 'net';
|
||||||
const prevYearLabel = selectedYear ? Number(selectedYear) - 1 : new Date().getFullYear() - 1;
|
const prevYearLabel = selectedYear ? Number(selectedYear) - 1 : new Date().getFullYear() - 1;
|
||||||
|
|
||||||
@@ -1558,7 +1628,9 @@ export default function Plateformes() {
|
|||||||
{/* 7 — XIRR */}
|
{/* 7 — XIRR */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const rendement = netMode ? plateformeXirr.net : plateformeXirr.brut;
|
const rendement = netMode ? plateformeXirr.net : plateformeXirr.brut;
|
||||||
|
const rendementAttendu = netMode ? plateformeXirrAttendu.net : plateformeXirrAttendu.brut;
|
||||||
const cutoffLabel = selectedYear ? `31/12/${selectedYear}` : "aujourd'hui";
|
const cutoffLabel = selectedYear ? `31/12/${selectedYear}` : "aujourd'hui";
|
||||||
|
const periodLabel = selectedYear ? `Sur l'année ${selectedYear}` : "Depuis le début";
|
||||||
const xirrExplication =
|
const xirrExplication =
|
||||||
`XIRR (taux de rendement interne actualisé) : taux annualisé calculé à partir de l'ensemble des flux réels datés ` +
|
`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é'}). ` +
|
`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
|
(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.`
|
? `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.');
|
: '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 (
|
return (
|
||||||
<div className="kpi">
|
<div className="kpi">
|
||||||
<div className="label" style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
<div className="label" style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
@@ -1593,8 +1674,27 @@ export default function Plateformes() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
<div style={{ fontSize: '0.8em', color: 'var(--text-muted)', marginTop: 5 }}>
|
||||||
Valorisé au {cutoffLabel}
|
{periodLabel}
|
||||||
</div>
|
</div>
|
||||||
|
{rendementAttendu !== null && (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 6 }}>
|
||||||
|
<span style={{
|
||||||
|
fontSize: '0.78em', fontWeight: 600, padding: '2px 8px', borderRadius: 20,
|
||||||
|
background: 'rgba(124,58,237,0.10)', color: '#7c3aed', whiteSpace: 'nowrap',
|
||||||
|
}}>
|
||||||
|
Attendu : {(rendementAttendu * 100).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} %
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="cell-tooltip tooltip-down"
|
||||||
|
data-tooltip={xirrAttenduExplication}
|
||||||
|
style={{ display: 'inline-flex', color: 'var(--text-muted)' }}
|
||||||
|
>
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
Reference in new issue
Block a user