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.

Privacidade por conceção: só enviamos dados ao Supabase ou outros serviços descritos na política de privacidade se guardar um plano, partilhar um link ou usar o conselheiro IA.

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:

  1. Interface (HTML + JavaScript): lê o formulário, valida fases, impostos e carteira; constrói um JSON de configuração (store.js).
  2. Web Worker (simulation_worker.js): recebe o JSON e delega no binário WASM adequado sem bloquear a UI.
  3. Módulos WASM compilados a partir de C++: simulação principal, objetivos inversos, mapa de calor e pesquisa SWR.
  4. 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 volatility

Monthly 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) − 1

Statistical outputs

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.

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 index

Currency 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 – Bonds0.05
Stocks – Crypto0.20
Bonds – Crypto0.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 × ρ_bc

Glidepath (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.

Identificadores em inglês: todos os nomes técnicos em 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

Ordem de retirada (withdrawalOrder)

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")

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)
Modelos por país: aproximações educativas, não aconselhamento fiscal.

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 / 12

8. 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)

Mapa de calor (simulate_heatmap.cpp)

SWR — taxa segura de retirada

Auditar: 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.

10. Limitações

Every simulation simplifies reality. It is important to know the model's assumptions:

Abrir o simulador