Methodik des Simulators
Technische Transparenz zu Pfaden, Perzentilen und erweiterten Werkzeugen.
Zuletzt aktualisiert: Mai 2026
1. Überblick: Was der Simulator leistet
My FIRE Simulator projiziert Pläne für finanzielle Unabhängigkeit (FIRE) mit tausenden monatlichen Pfaden (Monte-Carlo oder historisch), konfigurierbaren Steuern und Entnahmestrategien. Der numerische Kern ist in C++ geschrieben und läuft in Ihrem Browser als WebAssembly (WASM) in einem Web Worker: Ihre Hauptplan-Daten werden nicht an unsere Server gesendet, um zu rechnen.
Diese Seite beschreibt die Logik für Nutzer, die den Motor verstehen — oder gedanklich prüfen — möchten. Sie ersetzt keine professionelle Finanzberatung.
2. Architektur im Browser
Der technische Ablauf des Hauptsimulators:
- Oberfläche (HTML + JavaScript): liest das Formular, prüft Phasen, Steuern und Portfolio; erzeugt ein Konfigurations-JSON (
store.js). - Web Worker (
simulation_worker.js): empfängt das JSON und delegiert an das passende WASM-Binary, ohne die UI zu blockieren. - WASM-Module aus C++ kompiliert: Hauptsimulation, inverse Ziele, Heatmap und SWR-Suche.
- Ergebnisse: Perzentile, Diagramme (Chart.js), Jahrestabelle und Erbschaftshistogramm werden clientseitig gerendert.
Die schnelle Rechner auf der Landingpage („Berechne dein Ziel“) ist die Ausnahme: nur deterministisches JavaScript mit Zinseszins, ohne WASM, für eine sofortige Antwort.
3. Haupt-Simulationsmotor
Quelldatei: src-wasm/simulate.cpp → Worker-WASM. Standardmäßig 5.000 unabhängige Iterationen (iterations im Payload), jeweils gleiche Konfiguration, unterschiedliche Zufallspfade.
Zeitliche Granularität
Die Simulation schreitet monatlich über den definierten Horizont voran. Jeden Monat: Einzahlungen und Entnahmen nach Phasen, Portfolio-Rendite, Inflation, Gebühren, Steuern und Stressereignissen, falls konfiguriert.
Statistische Ausgaben
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: interpolierte Perzentile des Kapitals (nominal und real) pro Monat und am Ende.
- Erfolgswahrscheinlichkeit / Ruinrisiko: Anteil der Pfade, die das Kapital vor dem Horizont aufbrauchen.
- Steueraufschlüsselung (P50): kumulierte Gebühren und Steuern im Medianszenario bei dynamischer Steuerberechnung.
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. Datenquellen: individuell vs historisch
Individuell (reines Monte-Carlo)
Erzeugt zufällige Monatsrenditen mit Normalverteilung (Mittelwert mu, Volatilität sigma) plus konstanter Inflation.
Historisch (Block-Sampling)
Nutzt public/js/data/historical_series.js (Jordà–Schularick–Taylor, moderne Ära 1950–2020). Aufeinanderfolgende Jahresblöcke mit koordinierten realen Renditen.
- Regionen: USA, Europa (Deutschland-Proxy vor 1999), Japan, Vereinigtes Königreich.
- Panic-Taste (1870): nur bei historischer Quelle — verlängert den Zeitraum.
- Währungsabsicherung (Hedged): nur historisch — passt FX an, wenn Ihre Währung von der Serie abweicht.
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. Portfolio, Volatilität und Glidepath
Im individuellen Modus schätzt die UI mu und sigma mit Markowitz-Heuristiken (calculatePortfolioStats).
Der Glidepath interpoliert Gewichte monatlich. Stressereignisse wenden Schocks in gewählten Jahren an.
| 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. Besteuerung und Kapital-Buckets
Die dynamische Besteuerung liegt in src-wasm/simulate.cpp und läuft nur im vollen Monatsloop (needsDynamicCalc): nicht-feste Entnahme, historische Datenquelle, aktive Steuern, konfigurierte Tranches oder FIFO. Der Plan-JSON enthält generalCapital, taxFreeCapital, deferredCapital, taxStrategy (average | fifo), withdrawalOrder, taxBrackets[], flatTaxRate und latentGainsPct (anfängliche Steuerkostenbasis des General-Buckets).
… sind englisch. JSON (store.js) und C++ nutzen weiterhin spanische Schlüssel (z. B. generalCapital in der Doku = capGeneral im Code).Drei Kapital-Buckets
- Allgemein (Broker): Einzahlungen in
cashFlowPhasesmitaccount: "general". Einziger Bucket mit modellierter Steuerkostenbasis (Durchschnitt oder FIFO). - Steuerfrei (libre):
account: "libre". Bei Entnahmen wird 100 % Gewinn angenommen (ratio = 1.0); kein FIFO. - Gestundet (diferido):
account: "diferido". Gleiche Steuerlogik wie libre (voller Gewinn bei Entnahme).
Entnahmereihenfolge (withdrawalOrder)
- Optimal (Standard): feste Reihenfolge
general → libre → diferido(Code: Indizes 0 → 2 → 1). - Anteilig (
prorated): verteilt die Netto-Entnahme nach Kapitalgewicht; reicht ein Bucket nicht, geht der Rest an den nächsten in derselben Reihenfolge.
Durchschnittsmodus (taxStrategy: "average")
Es gibt eine aggregierte Steuerkostenbasis (generalCostBasis) im General-Bucket. Bei Entnahme ist der steuerpflichtige Gewinnanteil proportional: ratio = (Marktwert − Kosten) / Marktwert. Steuer als Flatrate (flatTaxRate) oder progressive Tranches (taxBrackets mit max und rate). Gewinne sammeln sich in annualGainYTD und werden jedes Kalenderjahr (alle 12 Monate) zurückgesetzt. Der Motor berechnet die nötige Brutto-Entnahme für ein Ziel-Netto (Brutto ↔ Netto pro Tranche).
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-Modus (taxStrategy: "fifo")
Nur für den Bucket general. Jede Einzahlung erzeugt ein Lot { cost, units } in einer deque; ein Indexpreis (indexPrice) wird monatlich mit Portfolio-Rendite und Broker-Gebühr aktualisiert. Verkäufe verbrauchen Lots nach FIFO: Gewinn = Marktwert des Lots − Kosten; Steuer auf diesen Gewinn (Tranches oder Flatrate). Bei mehr als 120 Lots werden die 12 ältesten zu einem Lot zusammengeführt (Speicherlimit).
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. Entnahmestrategien
Die Strategie wird als withdrawalStrategy gesendet: fixed (Standard), guyton oder vpw. Geplante Entnahmen kommen als negative Werte in cashFlowPhases[].val und werden im dynamischen Monatsloop verarbeitet.
Fix (fixed)
Jeden Monat mit geplanter Entnahme: withdrawal = baseWithdrawals[t] × withdrawalInflationFactor. Mit adjustFlowsForInflation wird withdrawalInflationFactor einmal jährlich mit (1 + Jahresinflation) multipliziert (wie Einzahlungen mit generalInflationFactor). Entspricht der klassischen inflationsindexierten Entnahmeregel.
Guyton–Klinger (guyton)
UI-Parameter: gkThreshold (%) und gkAdjustment (%). Der Motor setzt initialGKWithdrawalRate = geplante Jahresentnahme / Gesamtkapital beim ersten Entnahme-Monat. Jedes Januar: (1) Inflation auf die Ausgaben nur wenn totalCapital ≥ capitalAtYearStart; (2) Vergleich der aktuellen Entnahmerate mit der Anfangsrate — über rate × (1 + Schwelle) wird der Faktor mit (1 − Anpassung) multipliziert; darunter rate × (1 − Schwelle) mit (1 + Anpassung).
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 — Variable Percentage Withdrawal (vpw)
Parameter vpwRate (erwartete Jahresrate, z. B. 4 %). Jedes Januar (oder beim Entnahmestart) wird eine Annuität auf dem lebenden Kapital neu berechnet: Restjahre remainingYears = (months − t + 1) / 12; Tilgungsrate pmtRate = vpwRate / (1 − (1+vpwRate)−remainingYears) (bei 0: 1/remainingYears). Monatsentnahme = totalCapital × pmtRate / 12. Steuerlich mit isVPWMeta (brutto-orientiert, kein fixes Netto).
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. Erweiterte Werkzeuge
Die drei Tools im Panel «Erweiterte Werkzeuge» (public/js/controllers/simulation.js) lesen nicht das Hauptformular: sie bauen eigene Payloads und rufen den Web Worker (simulation_worker.js) mit goals, heatmap und swr auf.
Ziele (simulate_goals.cpp)
mode: "time": Wie viele Jahre bistargetmitinitialund monatlicher Sparratemonthly? Bis 80 Jahre pro Pfad; sonst Rückgabe 80.mode: "money": Welche konstante monatliche Sparrate (in heutigen Euro) inyearsnötig ist?- Immer 5.000 Iterationen. Reale Renditen: Custom log-normal minus Inflation; historisch zufälliger Startjahr-Bootstrap und sequenzielle Serie aus
historical_series.js. - Perzentile «pessimistisch = schlechter»: Zeit-Modus P10 = 90. Perzentil der Jahre; Geld-Modus P10 = höhere Sparrate.
Heatmap (simulate_heatmap.cpp)
- Vereinfachtes Modell vs. Hauptmotor: keine Phasen, Steuern, Glidepath, Schulden, GK/VPW.
- Raster: Renteneintrittsalter (
ageMin…ageMax,ageStep) × monatliche Ausgaben (expMin…expMax,expStep). Pro Zelle: fixes Kapital, Horizont95 − Alter, Jahresausgaben mit Inflation, Jahresrendite (MC oder historischer Index-Lauf). - 5.000 Läufe pro Zelle; Erfolg = Kapital > 0.
rate = Erfolge / 5000 × 100. - UI-Limit:
Zeilen × Spalten ≤ 120; Mindest-Schritt Ausgaben 10 €, Altersschritt ≥ 1.
SWR — sichere Entnahmerate
public/js/api.jsklont den Haupt-Payload, erzwingtwithdrawalStrategy: fixed, ohne Extra-Phasen, Schulden, Krisen, Hypotheken.- Worker: Binärsuche 1–15 % (7 Schritte); jeder Test eine monatliche Entnahmephase =
(initialCapital × rate/100) / 12überyears. - Jeder Test ruft
simulate_wasmauf. Ziel: ≥ 95 % ohne Ruin ((iterations − bankruptcies) / iterations). - Keine künftigen Einzahlungen, Renten oder Sondereinkommen — nur «heute in Rente mit inflationsindexierter fester Entnahme».
simulate_goals.cpp, simulate_heatmap.cpp, simulation_worker.js → handleSWR.9. Was auf dem Server läuft
E-Mail-Benachrichtigungen sind in der aktuellen Oberfläche nicht vorhanden.
- Google-Konto / Supabase: Pläne, Kommentare, Links.
- KI-Berater: Zusammenfassung an Gemini.
- Kontaktformular (Formspree) und Simple Analytics.
- Optionale anonyme Fehlerprotokolle.
10. Einschränkungen
Every simulation simplifies reality. It is important to know the model's assumptions:
- Die Zukunft kann schlechter sein als historisches P10.
- Steuerrecht und Gebühren ändern sich.
- Bildungswerkzeug, keine Anlageempfehlung.