Méthodologie du simulateur
Transparence technique pour comprendre trajectoires, percentiles et outils avancés.
Dernière mise à jour : mai 2026
1. Résumé : ce que fait le simulateur
My FIRE Simulator projette des plans d'indépendance financière (FIRE) via des milliers de trajectoires mensuelles (Monte-Carlo ou historique), des impôts configurables et des stratégies de retrait. Le cœur numérique est en C++ et s'exécute dans votre navigateur en WebAssembly (WASM) dans un Web Worker : les chiffres de votre plan principal ne sont pas envoyés à nos serveurs pour le calcul.
Cette page décrit la logique pour les utilisateurs qui veulent comprendre — ou vérifier mentalement — le moteur. Elle ne remplace pas un conseil financier professionnel.
2. Architecture dans le navigateur
Le flux technique du simulateur principal :
- Interface (HTML + JavaScript) : lit le formulaire, valide phases, impôts et portefeuille ; construit un JSON de configuration (
store.js). - Web Worker (
simulation_worker.js) : reçoit le JSON et délègue au binaire WASM adapté sans bloquer l'UI. - Modules WASM compilés depuis C++ : simulation principale, objectifs inverses, carte de chaleur et recherche SWR.
- Résultats : percentiles, graphiques (Chart.js), tableau annuel et histogramme d'héritage rendus côté client.
Le calculateur rapide de la page d'accueil (« Calculez votre objectif ») est l'exception : JavaScript déterministe avec intérêts composés uniquement, sans WASM, pour une réponse instantanée.
3. Moteur principal de simulation
Fichier source : src-wasm/simulate.cpp → WASM du worker. Par défaut 5 000 itérations indépendantes (iterations dans le payload), même configuration, trajectoires aléatoires différentes.
Granularité temporelle
La simulation avance mois par mois sur l'horizon défini. Chaque mois : apports et retraits selon les phases, rendement du portefeuille, inflation, frais, impôts et événements de stress si configurés.
Sorties statistiques
In custom mode the engine generates a monthly return using Geometric Brownian Motion (GBM). The random number generator is a Mersenne Twister (mt19937) seeded from hardware (std::random_device):
R_monthly = exp( (μ − σ²/2) × Δt + σ × √Δt × Z ) − 1
where:
Δt = 1/12 (monthly step)
Z ~ N(0, 1) (standard normal distribution)
μ = expected annual portfolio return
σ = annual portfolio volatilityMonthly return — historical mode (block sampling)
In historical mode, each simulated year draws a consecutive block from the JST series. Annual returns are converted to monthly and combined by portfolio weights:
R_month_eq = (1 + R_annual_eq)^(1/12) − 1
R_month_bond = (1 + R_annual_bond)^(1/12) − 1
R_month_fx = (1 + R_annual_fx)^(1/12) − 1
R_portfolio = w_stocks × R_month_eq + w_bonds × R_month_bond + w_crypto × R_crypto_lognormal
If NOT hedged (unhedged currency):
R_final = (1 + R_portfolio) × (1 + R_month_fx) − 1Statistical outputs
- P10 / P50 / P90 : percentiles interpolés du capital (nominal et réel) chaque mois et à la fin.
- Probabilité de succès / risque de ruine : fraction de trajectoires qui épuisent le capital avant l'horizon.
- Détail fiscal (P50) : frais et impôts cumulés au scénario médian lorsque le moteur calcule les impôts dynamiquement.
Interpolated percentile calculation
We do not assume normality of final capital. Percentiles are computed with order statistics directly on the 5,000-result vectors:
index = (N − 1) × p / 100
lower = ⌊index⌋
frac = index − lower
percentile = arr[lower] × (1 − frac) + arr[lower + 1] × frac
(partial sort via std::nth_element, O(N) complexity)4. Sources de données : personnalisé vs historique
Personnalisé (Monte-Carlo pur)
Génère des rendements mensuels aléatoires avec une distribution normale (moyenne mu, volatilité sigma) plus inflation constante.
Historique (échantillonnage par blocs)
Utilise public/js/data/historical_series.js (Jordà–Schularick–Taylor, ère moderne 1950–2020). Blocs d'années consécutives avec rendements réels coordonnés.
- Régions : États-Unis, Europe (proxy Allemagne avant 1999), Japon, Royaume-Uni.
- Bouton Panique (1870) : source historique uniquement — étend la période.
- Couverture devise (Hedged) : historique uniquement — ajuste le FX si votre devise diffère de la série.
Available regions: US, Europe (Germany proxy pre-1999), Japan, United Kingdom. Default period: modern era 1950–2020. Panic button: extends to 1870+ including world wars and the Great Depression.
Block sampling algorithm
To preserve autocorrelation between consecutive years, the engine draws blocks of N years (configurable as blockSize, default 5). Each block starts at a random year in the series and advances sequentially, wrapping around:
At block start:
start_index = random in [0, series_length − 1]
Within block (consecutive years):
current_index = (start_index + offset) % series_length
When block years exhausted → new random indexCurrency hedge (Hedged): when enabled, the fx impact is ignored. Otherwise, return is adjusted: (1 + R_portfolio) × (1 + R_fx) − 1.
5. Portefeuille, volatilité et glidepath
En mode personnalisé, l'UI estime mu et sigma avec des heuristiques type Markowitz (calculatePortfolioStats).
Le glidepath interpole les poids mois par mois. Les événements de stress appliquent des chocs aux années choisies.
| Correlation | ρ |
|---|---|
| Stocks – Bonds | 0.05 |
| Stocks – Crypto | 0.20 |
| Bonds – Crypto | 0.05 |
Portfolio variance formula
μ_portfolio = w_s × μ_s + w_b × μ_b + w_c × μ_c
σ²_portfolio = w_s² × σ_s² + w_b² × σ_b² + w_c² × σ_c²
+ 2 × w_s × w_b × σ_s × σ_b × ρ_sb
+ 2 × w_s × w_c × σ_s × σ_c × ρ_sc
+ 2 × w_b × w_c × σ_b × σ_c × ρ_bcGlidepath (portfolio transition)
When enabled, stock/bond/crypto weights are linearly interpolated month by month between user-defined control points. At each month t, the engine recalculates mu and sigma with the Markowitz formula above using that month's interpolated weights.
Stress events
Stress events apply a multiplier factor to the return in the chosen month: stressFactor = 1 − drop. A 40% drop equals stressFactor = 0.6, simulating a point-in-time market crash.
6. Fiscalité et compartiments de capital
La fiscalité dynamique est dans src-wasm/simulate.cpp et ne s'exécute que dans la boucle mensuelle complète (needsDynamicCalc) : retrait non fixe, source historique, impôts, tranches ou FIFO. Le JSON inclut generalCapital, taxFreeCapital, deferredCapital, taxStrategy (average | fifo), withdrawalOrder, taxBrackets[], flatTaxRate et latentGainsPct.
… sont en anglais. Le JSON (store.js) et le C++ gardent des clés historiques en espagnol (ex. generalCapital ici = capGeneral dans le code).Trois compartiments de capital
- Général (courtier) : apports dans
cashFlowPhasesavecaccount: "general". Seul compartiment avec coût fiscal modélisé (moyenne ou FIFO). - Libre :
account: "libre". Retraits : 100 % de plus-value imposable (ratio = 1.0) ; pas de FIFO. - Différé :
account: "diferido". Même logique que libre à la sortie.
Ordre de retrait (withdrawalOrder)
- Optimal (défaut) : séquence
general → libre → diferido(indices 0 → 2 → 1). - Prorata (
prorated) : répartition selon le poids de chaque compartiment ; reliquat en cascade.
Mode moyenne (taxStrategy: "average")
Coût fiscal agrégé generalCostBasis ; plus-value proportionnelle : ratio = (valeur_marché − coût) / valeur_marché. Taux plat ou tranches taxBrackets. annualGainYTD remis à zéro chaque année civile. Le moteur calcule le brut pour un net cible.
gain_ratio = (marketValue − costBasis) / marketValue
taxable_gain = grossWithdrawal × gain_ratio
Flat tax: tax = taxable_gain × flatTaxRate
Brackets: For each bracket with (max, rate):
capacity = bracket.max − annualGainYTD
chunk = min(remainingGain, capacity)
tax += chunk × bracket.rate
Engine solves gross needed for a target net withdrawal:
gross = net / (1 − gain_ratio × marginal_rate)Mode FIFO (taxStrategy: "fifo")
Uniquement sur general : lots { cost, units } en deque, indexPrice mis à jour chaque mois. Ventes FIFO ; fusion des 12 plus anciens lots si > 120 lots.
Monthly index price update:
indexPrice *= (1 + R_portfolio) × (1 − brokerFee)
For each lot consumed (FIFO order):
lot_marketValue = units × indexPrice
cost_per_unit = original_cost / original_units
gain = (indexPrice − cost_per_unit) × units_sold
tax += apply_brackets(gain)7. Stratégies de retrait
Stratégie : withdrawalStrategy — fixed, guyton ou vpw. Retraits planifiés en valeurs négatives dans cashFlowPhases[].val.
Fixe (fixed)
withdrawal = baseWithdrawals[t] × withdrawalInflationFactor ; revalorisation annuelle si adjustFlowsForInflation (règle classique inflation-indexée).
Guyton–Klinger (guyton)
gkThreshold, gkAdjustment. initialGKWithdrawalRate au premier retrait. Chaque janvier : inflation sur les dépenses seulement si capital ≥ début d'année ; garde-fous si la taux de retrait dépasse ou descend sous les seuils par rapport au taux initial.
initialWithdrawalRate = plannedAnnualWithdrawal / totalCapital (1st withdrawal only)
Each January:
1. Prosperity rule:
If totalCapital ≥ capitalAtYearStart → apply inflation to spending
Otherwise → freeze spending (no inflation bump)
2. Guardrail rules:
currentRate = annualSpending / totalCapital
If currentRate > initialRate × (1 + threshold) → factor × (1 − adjustment) [cut]
If currentRate < initialRate × (1 − threshold) → factor × (1 + adjustment) [raise]VPW (vpw)
vpwRate ; annuité sur capital vivant : remainingYears, pmtRate = vpwRate / (1 − (1+vpwRate)−remainingYears) ; mensuel = totalCapital × pmtRate / 12. Fiscalité : isVPWMeta.
remainingYears = (totalMonths − t + 1) / 12
If vpwRate > 0:
pmtRate = vpwRate / (1 − (1 + vpwRate)^(−remainingYears))
If vpwRate = 0:
pmtRate = 1 / remainingYears
Monthly_withdrawal = totalCapital × pmtRate / 128. Outils avancés
Les trois outils « Outils avancés » (controllers/simulation.js) n'utilisent pas le formulaire principal : payloads dédiés vers le worker (goals, heatmap, swr).
Objectifs (simulate_goals.cpp)
mode: "time": années jusqu'àtarget(plafond 80 par trajectoire).mode: "money": épargne mensuelle réelle constante suryears.- 5 000 itérations ; rendements réels (Monte-Carlo ou bootstrap historique depuis
historical_series.js). - Percentiles : pessimiste = pire — temps : P10 = 90e percentile des années ; argent : P10 = épargne plus élevée.
Carte de chaleur (simulate_heatmap.cpp)
- Modèle simplifié vs moteur principal : pas de phases, impôts, glidepath, dettes ni GK/VPW.
- Grille : âge de retraite (
ageMin…ageMax, pasageStep) × dépense mensuelle (expMin…expMax, pasexpStep). Par cellule : capital fixe, horizon95 − âge, dépense annuelle revalorisée, rendement annuel (MC ou historique). - 5 000 simulations par cellule ; succès = capital > 0.
rate = succès / 5000 × 100. - Limite UI :
lignes × colonnes ≤ 120; pas dépense minimum 10 €, pas âge ≥ 1.
SWR — taux de retrait sécurisé
public/js/api.jsclone le payload mais forcewithdrawalStrategy: fixed, sans phases extra, dettes, crises ni hypothèques.- Worker : recherche dichotomique 1–15 % (7 itérations) ; phase de retrait mensuelle =
(initialCapital × taux/100) / 12suryears. - Chaque test appelle le même
simulate_wasm. Cible : ≥ 95 % sans ruine ((iterations − bankruptcies) / iterations). - Sans apports futurs, pensions publiques ni revenus extraordinaires.
simulate_goals.cpp, simulate_heatmap.cpp, simulation_worker.js (handleSWR).9. Ce qui passe par le serveur
Les notifications par e-mail ne sont pas dans l'interface actuelle.
- Compte Google / Supabase : plans, commentaires, liens.
- Conseiller IA : résumé envoyé à Gemini.
- Formulaire de contact (Formspree) et Simple Analytics.
- Journaux d'erreur anonymes optionnels.
10. Limitations
Every simulation simplifies reality. It is important to know the model's assumptions:
- L'avenir peut être pire que le P10 historique.
- La réglementation fiscale et les frais évoluent.
- Outil pédagogique, pas une recommandation d'investissement.