Amélioration du calcul XIRR
This commit is contained in:
@@ -872,24 +872,34 @@ export default function InvestissementDetail() {
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
// Rendement réel annualisé (XIRR) — calculé uniquement si l'opération est remboursée
|
||||
// Rendement annualisé (XIRR) — calcul exact une fois le prêt intégralement remboursé.
|
||||
// Sinon (en cours / en retard / procédure), estimation basée sur les remboursements déjà
|
||||
// versés + une valorisation du capital restant dû à la date du jour (comme si ce capital
|
||||
// était "racheté" aujourd'hui) — donne un taux annualisé qui tient compte du rythme réel
|
||||
// des versements, contrairement à un simple ratio intérêts perçus / capital / durée.
|
||||
let rendementReelBrut = null;
|
||||
let rendementReel = null;
|
||||
if (inv.statut === 'rembourse' && remb.length > 0 && inv.date_souscription) {
|
||||
const rendementEstime = inv.statut !== 'rembourse';
|
||||
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 }));
|
||||
// Valorisation à date du capital pas encore remboursé (uniquement pour l'estimation en cours)
|
||||
const valorisationAuj = rendementEstime && capRestant > 0 ? [{ amount: capRestant, date: todayStr }] : [];
|
||||
// Brut : capital + cashback + intérêts bruts (avant fiscalité)
|
||||
const cashflowsBrut = [
|
||||
{ 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 })),
|
||||
...valorisationAuj,
|
||||
];
|
||||
rendementReelBrut = xirr(cashflowsBrut);
|
||||
// Net : net_recu (après prélèvements sociaux + impôt sur le revenu)
|
||||
// Net : net_recu (après prélèvements sociaux + impôt sur le revenu) — le capital restant
|
||||
// valorisé n'est, lui, pas soumis à fiscalité (ce n'est pas un gain, juste le principal)
|
||||
const cashflows = [
|
||||
{ amount: -inv.montant_investi, date: inv.date_souscription },
|
||||
...reinvOutflows,
|
||||
...remb.map(r => ({ amount: r.net_recu, date: r.date_remb })),
|
||||
...valorisationAuj,
|
||||
];
|
||||
rendementReel = xirr(cashflows);
|
||||
}
|
||||
@@ -961,18 +971,20 @@ export default function InvestissementDetail() {
|
||||
</div>
|
||||
{(() => {
|
||||
const rendement = netMode ? rendementReel : rendementReelBrut;
|
||||
const baseTip = netMode ? 'XIRR sur flux nets réels (après fiscalité)' : 'XIRR sur flux bruts (avant fiscalité)';
|
||||
const titleTip = rendement !== null
|
||||
? (netMode ? 'XIRR sur flux nets réels (après fiscalité)' : 'XIRR sur flux bruts (avant fiscalité)')
|
||||
: inv.statut !== 'rembourse'
|
||||
? 'Disponible uniquement sur les investissements remboursés'
|
||||
: 'Données insuffisantes';
|
||||
? baseTip + (rendementEstime ? ' — estimation : capital restant dû valorisé à la date du jour' : '')
|
||||
: 'Nécessite au moins un remboursement enregistré';
|
||||
return (
|
||||
<div className="kpi" title={titleTip}>
|
||||
<div className="label">Rendement annualisé — {netMode ? 'Net' : 'Brut'}</div>
|
||||
<div className="label">
|
||||
Rendement annualisé — {netMode ? 'Net' : 'Brut'}
|
||||
{rendementEstime && rendement !== null ? <span className="text-muted"> (estimé)</span> : ''}
|
||||
</div>
|
||||
<div className="value" style={{ color: rendement !== null ? (rendement >= 0 ? 'var(--success)' : 'var(--danger)') : 'var(--text-muted)' }}>
|
||||
{rendement !== null
|
||||
? `${(rendement * 100).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} %`
|
||||
: inv.statut === 'rembourse' ? '—' : 'En cours…'}
|
||||
: remb.length === 0 ? 'Aucun remboursement' : '—'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user