Financial Resilience Simulator: Measure Your Ability to Withstand Economic Shocks
number bidir. ; graphe éditorial (bande d’érosion,
* valeurs en bout de courbe, point terracotta unique) ; hiérarchie
* résultats ; en-tête filet doré ; .is-three par classe.
* – v1.2 : support bilingue (attribut lang, data-lang, table I18N JS).
* —————————————————————————
*/
if ( ! defined( ‘ABSPATH’ ) ) { return; }
if ( ! function_exists( ‘eco3_simu_register_assets’ ) ) :
/* =========================================================================
* 1. ASSETS (enqueue conditionnel via le shortcode)
* ========================================================================= */
add_action( ‘wp_enqueue_scripts’, ‘eco3_simu_register_assets’ );
function eco3_simu_register_assets() {
wp_register_style( ‘eco3-simu’, false, array(), ‘1.4’ );
wp_add_inline_style( ‘eco3-simu’, eco3_simu_css() );
wp_register_script( ‘eco3-simu’, ”, array(), ‘1.4’, true );
wp_add_inline_script( ‘eco3-simu’, eco3_simu_js() );
}
/* =========================================================================
* 2. SHORTCODE
* ========================================================================= */
add_shortcode( ‘eco3min_simulateur’, ‘eco3_simu_shortcode’ );
function eco3_simu_shortcode( $atts ) {
$atts = shortcode_atts( array(
‘type’ => ‘interets-composes’,
‘lang’ => ‘fr’,
), $atts, ‘eco3min_simulateur’ );
wp_enqueue_style( ‘eco3-simu’ );
wp_enqueue_script( ‘eco3-simu’ );
$lang = ( strtolower( $atts[‘lang’] ) === ‘en’ ) ? ‘en’ : ‘fr’;
return eco3_simu_markup( sanitize_title( $atts[‘type’] ), $lang );
}
/* =========================================================================
* 3. MARKUP PAR TYPE + LANGUE
* ========================================================================= */
function eco3_simu_markup( $type, $lang ) {
switch ( $type ) {
case ‘interets-composes’:
if ( $lang === ‘en’ ) {
return
What time does to your capital — and what it doesn’t guarantee
Adjust the assumptions. The tool plots the nominal projection and its real purchasing power: the terracotta band is what inflation removes.
€
€
%
yrs
%
Assumption −1.5%/yr (Dalbar QAIB methodology — contested: other studies find a smaller gap). Describes the observed gap between fund returns and the return actually captured by the average investor — not an individual fate.
Nominal projection
Constant return, on a spreadsheet
After behavior gap
Captured return < headline
Real purchasing power
Conditional projection, non-predictive · assumptions entered by the user · constant return, annual compounding, end-of-period contributions · Eco3min — educational tool, neither advice nor a recommendation.
HTML;
}
return
Ce que le temps fait à votre capital — et ce qu’il n’en garantit pas
Modifiez les hypothèses. L’outil trace la projection nominale et son pouvoir d’achat réel : la bande terracotta est ce que l’inflation retire.
€
€
%
ans
%
Hypothèse −1,5 %/an (méthodologie Dalbar QAIB — contestée : d’autres études situent l’écart plus bas). Décrit l’écart observé entre rendement des fonds et rendement réellement capté par l’investisseur moyen, pas une fatalité individuelle.
Projection nominale
Rendement constant, sur tableur
Après écart comportemental
Rendement capté < affiché
Pouvoir d’achat réel
Projection conditionnelle, non prédictive · hypothèses saisies par l’utilisateur · rendement supposé constant, capitalisation annuelle, versements en fin de période · Eco3min — outil pédagogique, ni conseil ni recommandation.
HTML;
case ‘capacite-endettement’:
if ( $lang === ‘en’ ) {
return
The ratio says “yes” — your remaining budget says whether you hold
The debt-to-income ratio (HCSF cap 35%) is necessary but not sufficient. At an equal ratio, it is the remaining budget that separates a robust situation from a fragile one.
Budget-structure observation, non-predictive · assumptions entered by the user · the HCSF 35% cap is a regulatory reference, not an approval threshold · Eco3min — educational tool, neither advice nor a recommendation.
HTML;
}
return
Le ratio dit « oui » — le reste à vivre dit si vous tenez
Le taux d’endettement (plafond HCSF 35 %) est nécessaire mais pas suffisant. À ratio égal, c’est le reste à vivre qui sépare une situation robuste d’une situation fragile.
Observation de structure budgétaire, non prédictive · hypothèses saisies par l’utilisateur · le plafond HCSF 35 % est un repère réglementaire, pas un seuil d’octroi · Eco3min — outil pédagogique, ni conseil ni recommandation.
HTML;
case ‘resilience-financiere’:
if ( $lang === ‘en’ ) {
return
A positive balance doesn’t tell you how long you’d hold
Adjust your flows and the shock. This tool measures no return — it measures your resistance duration: how many months your safety savings absorb the shock before rupture.
Budget-structure stress observation, non-predictive · assumptions entered by the user · the 3–6 month buffer is a commonly cited reference, not a norm · Eco3min — educational tool, neither advice nor a recommendation.
HTML;
}
return
Un solde positif ne dit pas combien de temps vous tenez
Modifiez vos flux et le choc. L’outil ne mesure pas un rendement — il mesure votre durée de résistance : combien de mois votre épargne de sécurité absorbe le choc avant rupture.
Observation de structure budgétaire sous choc, non prédictive · hypothèses saisies par l’utilisateur · le matelas de 3–6 mois est une référence courante, pas une norme · Eco3min — outil pédagogique, ni conseil ni recommandation.
HTML;
default:
return ‘
' . esc_html( $type ) . '.‘;
}
}
/* =========================================================================
* 4. CSS
* ========================================================================= */
function eco3_simu_css() {
return =v) return steps[k]*mag; }
return 10*mag;
}
// —- i18n —-
var I18N = {
fr: {
loc:’fr-FR’, symAfter:true,
yr:function(n){ return n+’ an’+(n>1?’s’:”); },
leg:{nominal:’Nominal’, behav:’Après écart comportemental’, real:’Pouvoir d\u2019achat réel’, erode:’Érodé par l\u2019inflation’},
delta:function(p){ return ‘\u2212’+p+’ % vs nominal’; },
breakdown:function(o){
return ‘Total versé sur ‘+o.yrs+’ ans : ‘+o.versed+’‘
+’ · Intérêts générés (nominal) : ‘+o.interets+’‘
+’ · Pouvoir d\u2019achat érodé par l\u2019inflation : \u2212’+o.erode+’‘
+(o.dalbar?’ · Écart comportemental : \u2212’+o.behav+’‘:”);
},
cap:{
leg:{enc:’Crédits en cours’, neu:’Nouvelle mensualité’, rav:’Reste à vivre’},
marker:’Plafond HCSF 35 %’,
perso:function(v){ return v+’ /personne’; },
breakdown:function(o){
return ‘Revenus : ‘+o.revenus+’/mois · Mensualités totales : ‘+o.charges+’ (taux d\u2019endettement ‘+o.ratio+’ %) · Reste à vivre : ‘+o.reste+’/mois (‘+o.perso+’)’;
}
},
res:{
leg:{normal:’Normal’, shock:’Sous choc’, ref:’Repère 3–6 mois’},
month:function(n){ return n+’ mois’; },
infinite:’∞’,
runwayUnit:function(v){ return v+’ mois’; },
delta:function(s){ return ‘sous choc : ‘+s+’/mois’; },
breakdown:function(o){
return ‘Régime normal : solde ‘+o.solde+’/mois · Sous choc (revenus −’+o.cr+’ %, charges +’+o.cc+’ %) : ‘+o.soldechoc+’/mois‘+(o.rupture?(‘ · votre épargne (‘+o.ep+’) couvre ‘+o.runway+’‘):(‘ · aucune rupture sous ce choc’));
}
}
},
en: {
loc:’en-IE’, symAfter:false,
yr:function(n){ return n+’ yr’+(n>1?’s’:”); },
leg:{nominal:’Nominal’, behav:’After behavior gap’, real:’Real purchasing power’, erode:’Eroded by inflation’},
delta:function(p){ return ‘\u2212’+p+’% vs nominal’; },
breakdown:function(o){
return ‘Total contributed over ‘+o.yrs+’ yrs: ‘+o.versed+’‘
+’ · Interest generated (nominal): ‘+o.interets+’‘
+’ · Purchasing power eroded by inflation: \u2212’+o.erode+’‘
+(o.dalbar?’ · Behavior gap: \u2212’+o.behav+’‘:”);
},
cap:{
leg:{enc:’Existing loans’, neu:’New payment’, rav:’Remaining budget’},
marker:’HCSF cap 35%’,
perso:function(v){ return v+’ /person’; },
breakdown:function(o){
return ‘Income: ‘+o.revenus+’/mo · Total payments: ‘+o.charges+’ (debt-to-income ‘+o.ratio+’%) · Remaining budget: ‘+o.reste+’/mo (‘+o.perso+’)’;
}
},
res:{
leg:{normal:’Normal’, shock:’Under shock’, ref:’3–6-month reference’},
month:function(n){ return n+’ mo’; },
infinite:’∞’,
runwayUnit:function(v){ return v+’ months’; },
delta:function(s){ return ‘under shock: ‘+s+’/mo’; },
breakdown:function(o){
return ‘Normal regime: balance ‘+o.solde+’/mo · Under shock (income −’+o.cr+’%, costs +’+o.cc+’%): ‘+o.soldechoc+’/mo‘+(o.rupture?(‘ · your savings (‘+o.ep+’) cover ‘+o.runway+’‘):(‘ · no rupture under this shock’));
}
}
}
};
// ——– module : intérêts composés
function initInteretsComposes(root){
var lang = (root.getAttribute(‘data-lang’) === ‘en’) ? ‘en’ : ‘fr’;
var T = I18N[lang];
var EUR = new Intl.NumberFormat(T.loc,{style:’currency’,currency:’EUR’,maximumFractionDigits:0});
function fmt(v){ return EUR.format(Math.max(0, Math.round(v))); }
function shortEur(v){
v = Math.max(0, v);
if(lang === ‘en’){
if(v>=1e6) return ‘€’+(v/1e6).toFixed(v>=1e7?0:1)+’M’;
if(v>=1e3) return ‘€’+Math.round(v/1e3)+’k’;
return ‘€’+Math.round(v);
}
if(v>=1e6) return (v/1e6).toFixed(v>=1e7?0:1).replace(‘.’,’,’)+’ M€’;
if(v>=1e3) return Math.round(v/1e3)+’ k€’;
return Math.round(v)+’ €’;
}
var binds = {};
root.querySelectorAll(‘[data-bind]’).forEach(function(el){
var k = el.getAttribute(‘data-bind’); (binds[k] = binds[k] || []).push(el);
});
var outs = {};
root.querySelectorAll(‘[data-out]’).forEach(function(el){ outs[el.getAttribute(‘data-out’)] = el; });
var behavCard = root.querySelector(‘[data-card=”behavioral”]’);
var resultsBox = root.querySelector(‘[data-results]’);
var svg = root.querySelector(‘[data-chart]’);
var legend = root.querySelector(‘[data-legend]’);
function readVal(k, def){
var els = binds[k]; if(!els) return def;
var src = els.filter(function(e){return e.type===’number’;})[0] || els[0];
return num(src, def);
}
function syncFrom(el, k){
var els = binds[k]; if(!els) return;
var n = els.filter(function(e){return e.type===’number’;})[0];
var r = els.filter(function(e){return e.type===’range’;})[0];
if(!n || !r) return;
if(el.type === ‘range’){ n.value = r.value; }
else { r.value = Math.min(parseFloat(r.max), Math.max(parseFloat(r.min), num(n, parseFloat(r.value)))); }
}
function compute(){
var P = Math.max(0, readVal(‘capital’, 0));
var m = Math.max(0, readVal(‘mensuel’, 0));
var r = Math.max(0, readVal(‘taux’, 0));
var yrs = Math.max(1, Math.min(60, Math.round(readVal(‘duree’, 1))));
var infl = Math.max(0, readVal(‘inflation’, 0));
var dalbar = !!(binds.dalbar && binds.dalbar[0].checked);
var rEff = dalbar ? Math.max(0, r – 1.5) : r;
var nominal = fv(P, m, r, yrs);
var behavioral = fv(P, m, rEff, yrs);
var real = deflate(behavioral, infl, yrs);
var versed = P + m*Math.round(yrs*12);
outs.nominal.textContent = fmt(nominal);
if (outs.behavioral) outs.behavioral.textContent = fmt(behavioral);
outs.real.textContent = fmt(real);
var erodePct = nominal>0 ? Math.round((1 – real/nominal)*100) : 0;
if (outs.delta) outs.delta.textContent = erodePct>0 ? T.delta(erodePct) : ”;
if (behavCard) behavCard.hidden = !dalbar;
if (resultsBox) resultsBox.classList.toggle(‘is-three’, dalbar);
outs.breakdown.innerHTML = T.breakdown({
yrs: yrs,
versed: fmt(versed),
interets: fmt(nominal – versed),
erode: fmt(nominal – deflate(nominal, infl, yrs)),
behav: fmt(nominal – behavioral),
dalbar: dalbar
});
drawChart({P:P,m:m,r:r,rEff:rEff,yrs:yrs,infl:infl,dalbar:dalbar});
}
function drawChart(p){
var W=720,H=360, padL=62,padR=104,padT=20,padB=34;
var x0=padL, x1=W-padR, y0=H-padB, y1=padT;
var nom=[], rea=[], beh=[];
for(var y=0; ymaxV) maxV=d[1]; }); });
maxV = niceMax(maxV);
function X(yr){ return x0 + (yr/p.yrs)*(x1-x0); }
function Y(v){ return y0 – (v/maxV)*(y0-y1); }
function pts(a){ return a.map(function(d){ return X(d[0]).toFixed(1)+’ ‘+Y(d[1]).toFixed(1); }); }
function path(a){ return a.map(function(d,i){ return (i?’L’:’M’)+X(d[0]).toFixed(1)+’ ‘+Y(d[1]).toFixed(1); }).join(‘ ‘); }
var s=”, ticks=4;
for(var t=0;t‘;
s+=’
}
[0, Math.round(p.yrs/2), p.yrs].forEach(function(yr){
s+=’
});
s+=’
s+=’
if(p.dalbar) s+=’
s+=’
var nv=nom[nom.length-1][1], rv=rea[rea.length-1][1], ex=X(p.yrs), ny=Y(nv), ry=Y(rv);
s+=’
s+=’
var lift = (Math.abs(ny-ry)‘+shortEur(nv)+’‘;
s+=’
svg.innerHTML=s;
var L=T.leg;
var leg=’‘+L.nominal+’‘;
if(p.dalbar) leg+=’‘+L.behav+’‘;
leg+=’‘+L.real+’‘;
leg+=’‘+L.erode+’‘;
legend.innerHTML=leg;
}
root.addEventListener(‘input’, function(e){
var el = e.target;
var b = el.getAttribute && el.getAttribute(‘data-bind’);
if(b && el.type !== ‘checkbox’) syncFrom(el, b);
compute();
});
root.addEventListener(‘change’, compute);
compute();
}
// ——– module : capacité d’endettement (multi-sorties, composition)
function initCapaciteEndettement(root){
var lang = (root.getAttribute(‘data-lang’) === ‘en’) ? ‘en’ : ‘fr’;
var T = I18N[lang].cap, loc = I18N[lang].loc;
var EUR = new Intl.NumberFormat(loc,{style:’currency’,currency:’EUR’,maximumFractionDigits:0});
function fmt(v){ return EUR.format(Math.round(v)); }
function pct(v){ return (Math.round(v*10)/10).toLocaleString(loc); }
function ratioStr(v){ return lang===’en’ ? pct(v)+’%’ : pct(v)+’ %’; }
var binds = {};
root.querySelectorAll(‘[data-bind]’).forEach(function(el){
var k = el.getAttribute(‘data-bind’); (binds[k] = binds[k] || []).push(el);
});
var outs = {};
root.querySelectorAll(‘[data-out]’).forEach(function(el){ outs[el.getAttribute(‘data-out’)] = el; });
var barWrap = root.querySelector(‘[data-bar]’);
var legend = root.querySelector(‘[data-legend]’);
var realCard = root.querySelector(‘.eco3-simu__stat–real’);
function readVal(k, def){
var els = binds[k]; if(!els) return def;
var src = els.filter(function(e){return e.type===’number’;})[0] || els[0];
var v = parseFloat(src.value); return isFinite(v) ? v : def;
}
function syncFrom(el, k){
var els = binds[k]; if(!els) return;
var n = els.filter(function(e){return e.type===’number’;})[0];
var r = els.filter(function(e){return e.type===’range’;})[0];
if(!n || !r) return;
if(el.type === ‘range’){ n.value = r.value; }
else { var v=parseFloat(n.value); v=isFinite(v)?v:parseFloat(r.value);
r.value = Math.min(parseFloat(r.max), Math.max(parseFloat(r.min), v)); }
}
function mensualite(montant, tauxAnnuel, annees){
var i = tauxAnnuel/100/12, n = Math.round(annees*12);
if(n0 ? charges/revenus*100 : 0;
var reste = revenus – charges;
var perso = reste/pers;
outs.ratio.textContent = ratioStr(ratio);
outs.reste.textContent = fmt(reste);
if(outs.resteperso) outs.resteperso.textContent = T.perso(fmt(perso));
if(realCard) realCard.classList.toggle(‘is-neg’, reste0 ? revenus : 1;
var wEnc = Math.max(0, encours)/base*100;
var wNew = Math.max(0, mNew)/base*100;
var wRav = Math.max(0, reste)/base*100;
var totalCredit = wEnc + wNew;
if(totalCredit > 100){ var sc=100/totalCredit; wEnc*=sc; wNew*=sc; wRav=0; }
var seg=”;
if(wEnc>0) seg+=’
‘;
if(wNew>0) seg+=’
‘;
if(wRav>0) seg+=’
‘;
barWrap.innerHTML =
‘
‘
+ ‘
‘
+ ‘
‘;
var L=T.leg;
legend.innerHTML =
‘‘+L.enc+’‘
+ ‘‘+L.neu+’‘
+ ‘‘+L.rav+’‘;
}
root.addEventListener(‘input’, function(e){
var el = e.target; var b = el.getAttribute && el.getAttribute(‘data-bind’);
if(b) syncFrom(el, b); compute();
});
compute();
}
// ——– module : résilience financière (trajectoire, normal vs choc)
function initResilienceFinanciere(root){
var lang = (root.getAttribute(‘data-lang’) === ‘en’) ? ‘en’ : ‘fr’;
var T = I18N[lang].res, loc = I18N[lang].loc;
var EUR = new Intl.NumberFormat(loc,{style:’currency’,currency:’EUR’,maximumFractionDigits:0});
function fmt(v){ return EUR.format(Math.round(Math.abs(v))); }
function sfmt(v){ return (v>=0?’+’:’\u2212′)+fmt(v); }
function shortEur(v){ v=Math.max(0,v); if(v>=1e6) return (lang===’en’?’\u20ac’:”)+(v/1e6).toFixed(1)+(lang===’en’?’M’:’ M\u20ac’); if(v>=1e3) return (lang===’en’?’\u20ac’:”)+Math.round(v/1e3)+(lang===’en’?’k’:’ k\u20ac’); return (lang===’en’?’\u20ac’:”)+Math.round(v)+(lang===’en’?”:’ \u20ac’); }
var binds={}; root.querySelectorAll(‘[data-bind]’).forEach(function(el){var k=el.getAttribute(‘data-bind’);(binds[k]=binds[k]||[]).push(el);});
var outs={}; root.querySelectorAll(‘[data-out]’).forEach(function(el){outs[el.getAttribute(‘data-out’)]=el;});
var svg=root.querySelector(‘[data-chart]’), legend=root.querySelector(‘[data-legend]’);
var realCard=root.querySelector(‘.eco3-simu__stat–real’);
function readVal(k,def){ var els=binds[k]; if(!els) return def; var s=els.filter(function(e){return e.type===’number’;})[0]||els[0]; var v=parseFloat(s.value); return isFinite(v)?v:def; }
function syncFrom(el,k){ var els=binds[k]; if(!els) return; var n=els.filter(function(e){return e.type===’number’;})[0],r=els.filter(function(e){return e.type===’range’;})[0]; if(!n||!r) return; if(el.type===’range’){n.value=r.value;} else {var v=parseFloat(n.value); v=isFinite(v)?v:parseFloat(r.value); r.value=Math.min(parseFloat(r.max),Math.max(parseFloat(r.min),v));} }
function niceMax(v){ if(v=v) return st[k]*mag; } return 10*mag; }
function compute(){
var revenus=Math.max(0,readVal(‘revenus’,0)), charges=Math.max(0,readVal(‘charges’,0)), dettes=Math.max(0,readVal(‘dettes’,0)), epargne=Math.max(0,readVal(‘epargne’,0));
var cr=Math.max(0,readVal(‘chocrev’,0)), cc=Math.max(0,readVal(‘choccharges’,0));
var soldeNormal = revenus – charges – dettes;
var revChoc = revenus*(1-cr/100), chChoc = charges*(1+cc/100);
var soldeChoc = revChoc – chChoc – dettes;
var deficit = -soldeChoc;
var rupture = deficit > 0;
var runway = rupture ? epargne/deficit : Infinity;
outs.solde.textContent = sfmt(soldeNormal);
outs.runway.textContent = rupture ? T.runwayUnit(Math.round(runway*10)/10) : T.infinite;
if(outs.soldechoc) outs.soldechoc.textContent = T.delta(sfmt(soldeChoc));
if(realCard) realCard.classList.toggle(‘is-neg’, rupture && runway maxV)maxV=d[1];});}); maxV=niceMax(maxV||1000);
function X(m){ return x0+(m/horizon)*(x1-x0); }
function Y(v){ return y0-(v/maxV)*(y0-y1); }
function path(a){ return a.map(function(d,i){return (i?’L’:’M’)+X(d[0]).toFixed(1)+’ ‘+Y(d[1]).toFixed(1);}).join(‘ ‘); }
var s=”, ticks=4;
for(var t=0;t
[0, Math.round(horizon/2), horizon].forEach(function(m){ s+=’
if(6‘; s+=’
s+=’
s+=’
if(p.rupture && p.runway‘; s+=’
svg.innerHTML=s;
legend.innerHTML=’‘+T.leg.normal+’‘
+’‘+T.leg.shock+’‘
+’‘+T.leg.ref+’‘;
}
root.addEventListener(‘input’, function(e){ var el=e.target; var b=el.getAttribute&&el.getAttribute(‘data-bind’); if(b) syncFrom(el,b); compute(); });
compute();
}
function boot(){
document.querySelectorAll(‘.eco3-simu[data-eco3-simu]’).forEach(function(root){
if(root.dataset.e3sInit) return; root.dataset.e3sInit=’1′;
switch(root.getAttribute(‘data-eco3-simu’)){
case ‘interets-composes’: initInteretsComposes(root); break;
case ‘capacite-endettement’: initCapaciteEndettement(root); break;
case ‘resilience-financiere’: initResilienceFinanciere(root); break;
// futurs types : ajouter un case + un init dédié
}
});
}
if(document.readyState===’loading’) document.addEventListener(‘DOMContentLoaded’, boot);
else boot();
})();
ECO3JS;
}
endif;
Last updated — 28 May 2026
Disclaimer – Financial Information: The analyses, commentary, and content published on eco3min.fr are provided for informational and educational purposes only. They do not constitute investment advice or a solicitation to buy or sell financial instruments. Past performance is not indicative of future results. All investment decisions involve risk and are the sole responsibility of the reader.
