Metodologia del simulatore
Trasparenza tecnica su traiettorie, percentili e strumenti avanzati.
Ultimo aggiornamento: maggio 2026
1. Riepilogo: cosa fa il simulatore
My FIRE Simulator proietta piani di indipendenza finanziaria (FIRE) con migliaia di traiettorie mensili (Monte Carlo o storico), tasse configurabili e strategie di prelievo. Il nucleo numerico è in C++ e gira nel browser come WebAssembly (WASM) in un Web Worker: i dati del piano principale non vengono inviati ai nostri server per il calcolo.
Questa pagina descrive la logica per chi vuole capire — o verificare mentalmente — il motore. Non sostituisce una consulenza finanziaria professionale.
2. Architettura nel browser
Il flusso tecnico del simulatore principale:
- Interfaccia (HTML + JavaScript): legge il modulo, valida fasi, tasse e portafoglio; costruisce un JSON di configurazione (
store.js). - Web Worker (
simulation_worker.js): riceve il JSON e delega al binario WASM senza bloccare l'UI. - Moduli WASM compilati da C++: simulazione principale, obiettivi inversi, heatmap e ricerca SWR.
- Risultati: percentili, grafici (Chart.js), tabella annuale e istogramma eredità renderizzati lato client.
La calcolatrice rapida della landing (« Calcola il tuo obiettivo ») è l'eccezione: solo JavaScript deterministico con interesse composto, senza WASM, per una risposta immediata.
3. Motore principale di simulazione
File sorgente: src-wasm/simulate.cpp → WASM del worker. Di default 5.000 iterazioni indipendenti (iterations nel payload), stessa configurazione, traiettorie casuali diverse.
Granularità temporale
La simulazione avanza mese per mese sull'orizzonte definito. Ogni mese: versamenti e prelievi secondo le fasi, rendimento del portafoglio, inflazione, commissioni, tasse ed eventi di stress se configurati.
Output statistici
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: percentili interpolati del capitale (nominale e reale) ogni mese e alla fine.
- Probabilità di successo / rischio rovina: frazione di traiettorie che esauriscono il capitale prima dell'orizzonte.
- Dettaglio fiscale (P50): commissioni e tasse cumulative nello scenario mediano con calcolo dinamico delle imposte.
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. Fonti dati: personalizzato vs storico
Personalizzato (Monte Carlo puro)
Genera rendimenti mensili casuali con distribuzione normale (media mu, volatilità sigma) più inflazione costante.
Storico (campionamento a blocchi)
Usa public/js/data/historical_series.js (Jordà–Schularick–Taylor, era moderna 1950–2020). Blocchi di anni consecutivi con rendimenti reali coordinati.
- Regioni: USA, Europa (proxy Germania pre-1999), Giappone, Regno Unito.
- Pulsante Panico (1870): solo con fonte storica — estende il periodo.
- Copertura valuta (Hedged): solo storico — aggiusta il FX se la valuta differisce dalla serie.
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. Portafoglio, volatilità e glidepath
In modalità personalizzata, l'UI stima mu e sigma con euristiche Markowitz (calculatePortfolioStats).
Il glidepath interpola i pesi mese per mese. Gli eventi di stress applicano shock negli anni scelti.
| 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à e bucket di capitale
La fiscalità dinamica è in src-wasm/simulate.cpp e gira solo nel loop mensile completo (needsDynamicCalc): prelievo non fisso, fonte storica, imposte, scaglioni o FIFO. Il JSON include generalCapital, taxFreeCapital, deferredCapital, taxStrategy (average | fifo), withdrawalOrder, taxBrackets[], flatTaxRate e latentGainsPct (base fiscale iniziale del bucket generale).
… sono in inglese. Il JSON (store.js) e il C++ usano ancora chiavi spagnole (es. generalCapital nella doc = capGeneral nel codice).Tre bucket di capitale
- Generale (broker): versamenti in
cashFlowPhasesconaccount: "general". Unico bucket con costo fiscale modellato (media o FIFO). - Libero:
account: "libre". Ai prelievi si assume 100% di plusvalenza imponibile (ratio = 1.0); niente FIFO. - Differito:
account: "diferido". Stessa logica fiscale del libero in uscita.
Ordine di prelievo (withdrawalOrder)
- Ottimale (default): sequenza
general → libre → diferido(indici 0 → 2 → 1). - Proporzionale (
prorated): riparte il prelievo netto per peso sul capitale totale; deficit al bucket successivo.
Modalità media (taxStrategy: "average")
Costo fiscale aggregato generalCostBasis; plusvalenza proporzionale ratio = (valore_mercato − costo) / valore_mercato. Aliquota fissa o scaglioni taxBrackets. annualGainYTD azzerato ogni anno solare. Il motore calcola il lordo per un netto obiettivo.
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)Modalità FIFO (taxStrategy: "fifo")
Solo bucket generale: lotti { cost, units } in deque, indexPrice aggiornato ogni mese. Vendite FIFO; se > 120 lotti, fusione dei 12 più vecchi.
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. Strategie di prelievo
Strategia in withdrawalStrategy: fixed, guyton o vpw. Prelievi pianificati come valori negativi in cashFlowPhases[].val nel loop dinamico.
Fissa (fixed)
withdrawal = baseWithdrawals[t] × withdrawalInflationFactor; con adjustFlowsForInflation, inflazione annua sul fattore (regola classica indicizzata).
Guyton–Klinger (guyton)
gkThreshold, gkAdjustment. initialGKWithdrawalRate al primo prelievo. Ogni gennaio: inflazione sulle spese solo se totalCapital ≥ capitalAtYearStart; guardrail se il tasso di prelievo supera o scende sotto le soglie rispetto al tasso iniziale.
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; rendita su capitale vivo: pmtRate = vpwRate / (1 − (1+vpwRate)−remainingYears); mensile = totalCapital × pmtRate / 12. Flag isVPWMeta in fiscalità.
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. Strumenti avanzati
I tre strumenti in «Strumenti avanzati» (controllers/simulation.js) non leggono il modulo principale: payload propri e worker goals, heatmap, swr.
Obiettivi (simulate_goals.cpp)
mode: "time": anni fino atarget(max 80 per traiettoria).mode: "money": risparmio mensile reale costante inyears.- 5.000 iterazioni; rendimenti reali (MC o bootstrap storico da
historical_series.js). - Percentili: pessimistico = peggiore — tempo: P10 = 90° percentile anni; denaro: P10 = risparmio più alto.
Heatmap (simulate_heatmap.cpp)
- Modello semplificato rispetto al motore principale: niente fasi, tasse, glidepath, debiti né GK/VPW.
- Griglia: età pensionamento (
ageMin…ageMax,ageStep) × spesa mensile (expMin…expMax,expStep). Per cella: capitale fisso, orizzonte95 − età, spesa annua con inflazione, rendimento annuo (MC o storico). - 5.000 simulazioni per cella; successo = capitale > 0.
rate = successi / 5000 × 100. - Limite UI:
righe × colonne ≤ 120; passo spesa min 10 €, passo età ≥ 1.
SWR — tasso di prelievo sicuro
public/js/api.jsclona il payload principale ma forza scenario puro:withdrawalStrategy: fixed, senza fasi extra, debiti, crisi o mutui.- Nel worker: ricerca binaria tra 1% e 15% (7 iterazioni); ogni test una fase di prelievo mensile =
(initialCapital × tasso/100) / 12per tuttoyears. - Ogni test chiama lo stesso
simulate_wasmdel piano principale. Obiettivo: ≥ 95% senza rovina ((iterations − bankruptcies) / iterations). - Non modella contributi futuri, pensioni pubbliche o redditi straordinari.
simulate_goals.cpp, simulate_heatmap.cpp, simulation_worker.js (handleSWR).9. Cosa avviene sul server
Le notifiche e-mail non sono nell'interfaccia attuale.
- Account Google / Supabase: piani, commenti, link.
- Consulente IA: riepilogo inviato a Gemini.
- Modulo contatti (Formspree) e Simple Analytics.
- Log di errore anonimi opzionali.
10. Limitazioni
Every simulation simplifies reality. It is important to know the model's assumptions:
- Il futuro può essere peggiore del P10 storico.
- Normativa fiscale e commissioni cambiano.
- Strumento didattico, non raccomandazione di investimento.