模拟器方法论
技术透明:理解路径、百分位数与高级工具。
最后更新:2026 年 5 月
1. 概述:模拟器做什么
My FIRE Simulator 通过数千条月度路径(蒙特卡洛或历史数据)、可配置税收和取款策略,投影财务独立(FIRE)计划。数值核心用 C++ 编写,在浏览器中以 WebAssembly (WASM) 在 Web Worker 中运行:主计划数据不会发送到我们的服务器进行计算。
本页面向希望理解或在心里审查引擎逻辑的用户。不能替代专业财务建议。
2. 浏览器内架构
主模拟器的技术流程:
- 界面(HTML + JavaScript):读取表单,验证阶段、税收和投资组合;构建配置 JSON(
store.js)。 - Web Worker(
simulation_worker.js):接收 JSON 并委托给相应 WASM 二进制,不阻塞 UI。 - WASM 模块由 C++ 编译:主模拟、逆向目标、热力图和 SWR 搜索。
- 结果:百分位数、图表(Chart.js)、年度表和遗产直方图在客户端渲染。
首页快速计算器(「计算你的目标」)是例外:仅使用确定性复利 JavaScript,无 WASM,即时响应。
3. 主模拟引擎
源文件:src-wasm/simulate.cpp → Worker WASM。默认运行 5,000 次独立迭代(payload 中的 iterations),配置相同,随机路径不同。
时间粒度
模拟在定义的时间范围内按月推进。每月根据阶段应用供款和取款、组合收益、通胀、费用、税收及已配置的压力事件。
统计输出
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:每月及期末资本(名义与实际)的插值百分位。
- 成功概率 / 破产风险:在期限前耗尽资本的路径比例。
- 税收明细(P50):引擎动态计算税收时中位情景下的累计费用和税款。
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. 数据来源:自定义 vs 历史
自定义(纯蒙特卡洛)
用正态分布(均值 mu、波动率 sigma)生成随机月收益,外加恒定通胀。
历史(块抽样)
使用 public/js/data/historical_series.js(Jordà–Schularick–Taylor,现代时期 1950–2020)。抽取连续年份块,协调实际收益。
- 地区:美国、欧洲(1999 年前德国代理)、日本、英国。
- 恐慌按钮(1870):仅历史数据源——延长时期。
- 货币对冲(Hedged):仅历史——若货币与序列不同则调整汇率。
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. 投资组合、波动率与 glidepath
自定义模式下,UI 用 Markowitz 式启发法估计 mu 和 sigma(calculatePortfolioStats)。
Glidepath 按月插值权重。压力事件在选定年份施加冲击。
| 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. 税收与资本桶
动态税收逻辑位于 src-wasm/simulate.cpp,仅在需要完整月度循环时运行(needsDynamicCalc):非固定取款、历史数据源、启用税收、配置税率档或 FIFO。计划 JSON 包含 generalCapital、taxFreeCapital、deferredCapital、taxStrategy(average | fifo)、withdrawalOrder、taxBrackets[]、flatTaxRate 及 latentGainsPct(一般桶的初始税务成本基础)。
… 内的技术名称均为英文。实际 JSON(store.js)与 C++ 仍使用西班牙语历史字段(例如文档 generalCapital = 代码中的 capGeneral)。三个资本桶
- 一般(券商):在
cashFlowPhases中account: "general"供款。唯一建模税务成本的桶(平均成本或 FIFO)。 - 免税(libre):取款假定 100% 应税收益(
ratio = 1.0);无 FIFO。 - 递延(diferido):取款税务处理与 libre 相同。
取款顺序(withdrawalOrder)
- 最优(默认):固定顺序
general → libre → diferido(代码索引 0 → 2 → 1)。 - 按比例(
prorated):按各桶占总资本权重分配净取款;不足部分按相同顺序转入下一桶。
平均成本模式(taxStrategy: "average")
一般桶维护汇总 税务成本基础(generalCostBasis)。取款时应税增值按比例:ratio = (市值 − 成本) / 市值。可为单一税率(flatTaxRate)或 累进档(taxBrackets 的 max 与 rate)。年度收益累计于 annualGainYTD,每自然年(12 个月)重置。引擎计算实现目标净额的 所需毛取款。
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)FIFO 模式(taxStrategy: "fifo")
仅适用于 general 桶。每次供款在 deque 中增加批次 { cost, units };指数价格(indexPrice)每月随组合收益与券商费用更新。卖出按 FIFO 消耗批次;对实现收益征税。若批次超过 120,合并最旧 12 个以限制内存。
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. 取款策略
策略字段 withdrawalStrategy:fixed(默认)、guyton 或 vpw。计划取款在 cashFlowPhases[].val 中以负值传入,在动态月度循环中应用。
固定(fixed)
有计划取款月份:withdrawal = baseWithdrawals[t] × withdrawalInflationFactor。若 adjustFlowsForInflation 开启,每年将因子乘以 (1 + 年通胀)(与供款的 generalInflationFactor 类似)。即经典通胀调整取款规则。
Guyton–Klinger(guyton)
界面参数 gkThreshold、gkAdjustment(%)。首次取款时计算 initialGKWithdrawalRate = 计划年取款 / 总资本。每年一月:(1)仅当 totalCapital ≥ capitalAtYearStart 才对支出加通胀;(2)若当前取款率高于初始率的 (1 + 阈值),将取款因子乘以 (1 − 调整);低于 (1 − 阈值) 则乘以 (1 + 调整)。
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(预期年率,如 4%)。每年一月在 活资本 上重算年金:剩余年数 remainingYears = (months − t + 1) / 12;pmtRate = vpwRate / (1 − (1+vpwRate)−remainingYears)(为 0 时用 1/remainingYears)。月取款 = 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. 高级工具
「高级工具」面板中的三项工具(public/js/controllers/simulation.js)不读取主表单:自建载荷并调用 Web Worker(simulation_worker.js)的 goals、heatmap、swr。
目标(simulate_goals.cpp)
mode: "time":以initial和月存monthly达到target需多少年?每条路径最多模拟 80 年。mode: "money":在years内达到目标所需的恒定实际月储蓄。- 始终 5,000 次迭代。实际收益:自定义为对数正态减恒定通胀;历史为随机起始年 bootstrap 及
historical_series.js注入序列。 - 分位数约定「悲观=更差」:时间模式 P10=年份的 90 分位;金额模式 P10=更高储蓄。
热力图(simulate_heatmap.cpp)
- 相对主引擎的简化模型:无阶段、税收、glidepath、债务、GK/VPW。
- 网格:退休年龄(
ageMin…ageMax,步长ageStep)× 月支出(expMin…expMax,expStep)。每格:面板固定资本、期限95 − 年龄、年支出按通胀调整、年收益(MC 或历史逐年)。 - 每格 5,000 次模拟;成功=期末资本>0。
rate = 成功数/5000×100。 - UI 限制:
行×列 ≤ 120;最小支出步长 10,年龄步长 ≥ 1。
SWR — 安全取款率
public/js/api.js克隆主载荷并强制纯场景:withdrawalStrategy: fixed,无额外阶段、债务、危机或按揭。- Worker 内 二分搜索 1%–15%(7 次);每次测试注入单一月取款阶段 =
(initialCapital × 比率/100) / 12,覆盖整个years。 - 每次测试调用同一
simulate_wasm。目标:≥95% 路径无破产((iterations − bankruptcies) / iterations)。 - 不含未来供款、公共养老金或额外收入——仅「今日退休、通胀调整固定取款」。
simulate_goals.cpp、simulate_heatmap.cpp、simulation_worker.js(handleSWR)。9. 在服务器上运行的内容
当前界面无电子邮件通知。
- Google 账户 / Supabase:计划、评论、链接。
- AI 顾问:摘要发送至 Gemini。
- 联系表单(Formspree)和 Simple Analytics。
- 可选匿名错误日志。
10. 局限性
Every simulation simplifies reality. It is important to know the model's assumptions:
- 未来可能比历史 P10 更差。
- 税收规则和费用会变化。
- 教育工具,非投资建议。