Metodologia do simulador
Transparência técnica para perceber trajetórias, percentis e ferramentas avançadas.
Última atualização: maio de 2026
1. Resumo: o que o simulador faz
O My FIRE Simulator projeta planos de independência financeira (FIRE) com milhares de trajetórias mensais (Monte Carlo ou histórico), impostos configuráveis e estratégias de retirada. O núcleo numérico está em C++ e executa no seu navegador como WebAssembly (WASM) num Web Worker: os dados do plano principal não são enviados aos nossos servidores para calcular.
Esta página descreve a lógica para quem quer compreender — ou auditar mentalmente — o motor. Não substitui aconselhamento financeiro profissional.
2. Arquitetura no navegador
O fluxo técnico do simulador principal:
- Interface (HTML + JavaScript): lê o formulário, valida fases, impostos e carteira; constrói um JSON de configuração (
store.js). - Web Worker (
simulation_worker.js): recebe o JSON e delega no binário WASM adequado sem bloquear a UI. - Módulos WASM compilados a partir de C++: simulação principal, objetivos inversos, mapa de calor e pesquisa SWR.
- Resultados: percentis, gráficos (Chart.js), tabela anual e histograma de herança renderizados no cliente.
A calculadora rápida da landing (« Calcula a tua meta ») é a exceção: apenas JavaScript determinístico com juros compostos, sem WASM, para resposta instantânea.
3. Motor principal de simulação
Ficheiro fonte: src-wasm/simulate.cpp → WASM do worker. Por defeito 5.000 iterações independentes (iterations no payload), mesma configuração, trajetórias aleatórias distintas.
Granularidade temporal
A simulação avança mês a mês no horizonte definido. Cada mês: contribuições e retiradas segundo fases, retorno da carteira, inflação, comissões, impostos e eventos de stress se configurados.
Saídas estatísticas
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: percentis interpolados do capital (nominal e real) em cada mês e no final.
- Probabilidade de sucesso / risco de ruína: fração de trajetórias que esgotam o capital antes do horizonte.
- Detalhe fiscal (P50): comissões e impostos acumulados no cenário mediano com impostos dinâmicos.
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. Fontes de dados: personalizado vs histórico
Personalizado (Monte Carlo puro)
Gera retornos mensais aleatórios com distribuição normal (média mu, volatilidade sigma) mais inflação constante.
Histórico (amostragem por blocos)
Usa public/js/data/historical_series.js (Jordà–Schularick–Taylor, era moderna 1950–2020). Blocos de anos consecutivos com retornos reais coordenados.
- Regiões: EUA, Europa (proxy Alemanha pré-1999), Japão, Reino Unido.
- Botão Pânico (1870): só com fonte histórica — alarga o período.
- Cobrir divisa (Hedged): só histórico — ajusta FX se a sua moeda difere da 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. Carteira, volatilidade e glidepath
Em modo personalizado, a UI estima mu e sigma com heurísticas Markowitz (calculatePortfolioStats).
O glidepath interpola pesos mês a mês. Eventos de stress aplicam choques nos anos escolhidos.
| 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. Fiscalidade e buckets de capital
A fiscalidade dinâmica está em src-wasm/simulate.cpp e só corre no loop mensal completo (needsDynamicCalc): retirada não fixa, fonte histórica, impostos, escalões ou FIFO. O JSON inclui generalCapital, taxFreeCapital, deferredCapital, taxStrategy (average | fifo), withdrawalOrder, taxBrackets[], flatTaxRate e latentGainsPct.
… estão em inglês. O JSON (store.js) e o C++ mantêm chaves históricas em espanhol (ex.: generalCapital na doc = capGeneral no código).Três buckets de capital
- Geral (corretora): contribuições com
account: "general". Único bucket com custo fiscal (média ou FIFO). - Isento (libre): 100% de ganho tributável na retirada; sem FIFO.
- Diferido: mesma lógica que libre.
Ordem de retirada (withdrawalOrder)
- Ótima (padrão):
general → libre → diferido(índices 0 → 2 → 1). - Proporcional (
prorated): reparte por peso; défice passa ao seguinte.
Modo média (taxStrategy: "average")
generalCostBasis, ratio = (marketValue − custo) / marketValue, taxa fixa ou taxBrackets, annualGainYTD reinicia anualmente. Bruto para neto alvo.
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)Modo FIFO (taxStrategy: "fifo")
Só general: lotes em deque, indexPrice mensal; fusão de 12 lotes se >120.
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. Estratégias de retirada
withdrawalStrategy: fixed, guyton ou vpw. Retiradas planeadas em cashFlowPhases[].val negativos.
Fixa (fixed)
withdrawal = baseWithdrawals[t] × withdrawalInflationFactor; inflação anual com adjustFlowsForInflation.
Guyton–Klinger (guyton)
gkThreshold, gkAdjustment; initialGKWithdrawalRate; guardrails anuais conforme capital e taxa de retirada.
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; anuidade sobre capital vivo; mensal = totalCapital × pmtRate / 12; 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. Ferramentas avançadas
Três ferramentas em «Ferramentas avançadas» (controllers/simulation.js) não usam o formulário principal: payloads próprios, worker goals, heatmap, swr.
Objetivos (simulate_goals.cpp)
mode: "time": quantos anos atétargetcominitiale poupançamonthly? Até 80 anos por trajetória; senão devolve 80.mode: "money": poupança mensal real constante emyearspara atingir a meta?- Sempre 5.000 iterações. Retornos reais: custom log-normal menos inflação; histórico com bootstrap e série de
historical_series.js. - Percentis «pessimista = pior»: tempo P10 = percentil 90 dos anos; dinheiro P10 = poupança mais alta.
Mapa de calor (simulate_heatmap.cpp)
- Modelo simplificado face ao motor principal: sem fases, impostos, glidepath, dívidas nem GK/VPW.
- Grelha: idade de reforma (
ageMin…ageMax,ageStep) × despesa mensal (expMin…expMax,expStep). Por célula: capital fixo, horizonte95 − idade, despesa anual com inflação, retorno anual (MC ou histórico). - 5.000 simulações por célula; sucesso = capital > 0.
rate = sucessos / 5000 × 100. - Limite UI:
linhas × colunas ≤ 120; passo despesa mín. 10 €, passo idade ≥ 1.
SWR — taxa segura de retirada
public/js/api.jsclona o payload mas forçawithdrawalStrategy: fixed, sem fases extra, dívidas, crises ou hipotecas.- No worker: pesquisa binária 1–15% (7 passos); fase de retirada mensal =
(initialCapital × taxa/100) / 12em todoyears. - Cada teste chama o mesmo
simulate_wasm. Objetivo: ≥ 95% sem ruína ((iterations − bankruptcies) / iterations). - Sem contribuições futuras, pensões públicas nem rendimentos extraordinários.
simulate_goals.cpp, simulate_heatmap.cpp, simulation_worker.js (handleSWR).9. O que corre no servidor
Notificações por e-mail não estão na interface atual.
- Conta Google / Supabase: planos, comentários, links.
- Conselheiro IA: resumo enviado ao Gemini.
- Formulário de contacto (Formspree) e Simple Analytics.
- Registos de erro anónimos opcionais.
10. Limitações
Every simulation simplifies reality. It is important to know the model's assumptions:
- O futuro pode ser pior que o P10 histórico.
- Legislação fiscal e comissões mudam.
- Ferramenta educativa, não recomendação de investimento.