XIRR sur les projets
This commit is contained in:
1 parent
c9ca27b7be
commit
e401530673
1 file changed
+68
@@ -953,6 +953,15 @@ export default function InvestissementDetail() {
|
||||
let rendementReelBrut = null;
|
||||
let rendementReel = null;
|
||||
const rendementEstime = inv.statut !== 'rembourse';
|
||||
// XIRR "attendu" (théorique) — même principe que sur la page Plateformes (cf.
|
||||
// project_xirr_attendu_theorique.md) : au lieu de valoriser prudemment le capital
|
||||
// restant dû "comme racheté aujourd'hui" (sans intérêts futurs), on projette les
|
||||
// échéances futures prévues (simul_remboursements) jusqu'au terme du prêt — pour
|
||||
// matcher le taux de rendement théorique affiché par les plateformes elles-mêmes
|
||||
// (ex. "Mon taux de rendement interne" chez Bienprêter). Fallback identique au XIRR
|
||||
// réel (valorisation du capital restant au jour) si aucune échéance future en base.
|
||||
let rendementAttenduBrut = null;
|
||||
let rendementAttendu = null;
|
||||
if (remb.length > 0 && inv.date_souscription) {
|
||||
// Flux sortants supplémentaires (réinvestissements = nouveaux décaissements)
|
||||
const reinvOutflows = reinvs.map(r => ({ amount: -r.montant, date: r.date_reinvestissement }));
|
||||
@@ -975,6 +984,37 @@ export default function InvestissementDetail() {
|
||||
...valorisationAuj,
|
||||
];
|
||||
rendementReel = xirr(cashflows);
|
||||
|
||||
if (rendementEstime && capRestant > 0.01) {
|
||||
const futureEcheances = simul.filter(s =>
|
||||
s.date_prevue > todayStr && ((s.capital_prevu || 0) > 0 || (s.interets_prevus || 0) > 0)
|
||||
);
|
||||
if (futureEcheances.length) {
|
||||
const flowsBrut = [
|
||||
{ amount: -inv.montant_investi, date: inv.date_souscription },
|
||||
...reinvOutflows,
|
||||
...remb.map(r => ({ amount: (r.capital || 0) + (r.cashback || 0) + (r.interets_bruts || 0), date: r.date_remb })),
|
||||
];
|
||||
const flowsNet = [
|
||||
{ amount: -inv.montant_investi, date: inv.date_souscription },
|
||||
...reinvOutflows,
|
||||
...remb.map(r => ({ amount: r.net_recu, date: r.date_remb })),
|
||||
];
|
||||
for (const s of futureEcheances) {
|
||||
const rates = getRatesForYear(s.date_prevue);
|
||||
const pfu = rates ? (rates.prelev_sociaux + rates.impot_revenu) / 100 : 0.3;
|
||||
flowsBrut.push({ amount: (s.capital_prevu || 0) + (s.interets_prevus || 0), date: s.date_prevue });
|
||||
flowsNet.push({ amount: (s.capital_prevu || 0) + (s.interets_prevus || 0) * (1 - pfu), date: s.date_prevue });
|
||||
}
|
||||
rendementAttenduBrut = xirr(flowsBrut);
|
||||
rendementAttendu = xirr(flowsNet);
|
||||
} else {
|
||||
// Pas d'échéancier prévisionnel disponible : identique au XIRR réel, pour ne
|
||||
// jamais sous-évaluer un prêt faute de données (cf. page Plateformes).
|
||||
rendementAttenduBrut = rendementReelBrut;
|
||||
rendementAttendu = rendementReel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -1049,6 +1089,7 @@ export default function InvestissementDetail() {
|
||||
</div>
|
||||
{(() => {
|
||||
const rendement = netMode ? rendementReel : rendementReelBrut;
|
||||
const rendementAttenduAffiche = netMode ? rendementAttendu : rendementAttenduBrut;
|
||||
const xirrExplication =
|
||||
`XIRR (taux de rendement interne actualisé) : taux annualisé calculé à partir des flux réels datés ` +
|
||||
`(versement initial, réinvestissements, remboursements ${netMode ? 'nets, après fiscalité' : 'bruts, avant fiscalité'}). ` +
|
||||
@@ -1056,6 +1097,14 @@ export default function InvestissementDetail() {
|
||||
(rendementEstime
|
||||
? "Prêt pas encore soldé : le capital restant dû est ajouté en flux final, valorisé à la date du jour — c'est donc une estimation, pas un taux définitif."
|
||||
: 'Prêt intégralement remboursé : 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) encore à venir 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é ` +
|
||||
`aujourd'hui", 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. Si ce ` +
|
||||
`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 (
|
||||
<div className="kpi">
|
||||
<div className="label" style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
@@ -1078,6 +1127,25 @@ export default function InvestissementDetail() {
|
||||
? `${(rendement * 100).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} %`
|
||||
: remb.length === 0 ? 'Aucun remboursement' : '—'}
|
||||
</div>
|
||||
{rendementAttenduAffiche !== 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 : {(rendementAttenduAffiche * 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>
|
||||
);
|
||||
})()}
|
||||
|
||||
Reference in new issue
Block a user