<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Monte_carlo on LibreLeo: Financial Freedom for Globally Mobile Investors</title><link>https://libreleo.com/tags/monte_carlo/</link><description>Tools, math, and lived experience for expats building wealth across borders. Passive portfolios and active income from a Dubai-based trader.</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Copyright © 2026 | All rights reserved</copyright><lastBuildDate>Mon, 22 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://libreleo.com/tags/monte_carlo/index.xml" rel="self" type="application/rss+xml"/><item><title>Monte Carlo Retirement Calculator</title><link>https://libreleo.com/calculators/monte-carlo-retirement-calculator/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://libreleo.com/calculators/monte-carlo-retirement-calculator/</guid><description>Interactive Monte Carlo simulator. 10,000 scenarios by default, up to 100,000. Four portfolios, two withdrawal strategies, log-normal compounding, fat-tail mode. Run your real retirement numbers.</description><content:encoded><![CDATA[
  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow" style="background-color: #1e3a5f"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    
  </span>

  <span
    
      style="color: #ffffff"
    
    ><strong>Want to understand the methodology?</strong> Learn why Monte Carlo simulation matters and how it models market volatility in our <strong><a href="/posts/monte-carlo-simulation-retirement-planning/" >Complete Guide to Monte Carlo Retirement Planning</a></strong>.</span>
</div>


<h2 class="relative group">Interactive Calculator
    <div id="interactive-calculator" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#interactive-calculator" aria-label="Anchor">#</a>
    </span>
    
</h2>

<div class="monte-carlo-calculator" id="monte-carlo-app">
  
  <div class="mc-header">
    <h2>🎲 Monte Carlo Retirement Calculator</h2>
    <p class="mc-subtitle">Simulate your financial future with 10,000 scenarios (up to 100,000)</p>
  </div>

  
  <div class="mc-section">
    <h3>1. Choose Your Portfolio</h3>
    <div class="portfolio-grid">
      <div class="portfolio-card" data-portfolio="1">
        <div class="portfolio-header">
          <input type="radio" name="portfolio" id="p1" value="1" checked>
          <label for="p1">
            <strong>Dividend-Focused</strong>
            <span class="badge">Original</span>
          </label>
        </div>
        <div class="portfolio-details">
          <p class="portfolio-desc">Heavy dividend tilt with growth</p>
          <ul class="portfolio-assets">
            <li>VTI: 35% • SCHG: 15%</li>
            <li>SCHD: 30% • SGOV: 20%</li>
          </ul>
          <div class="portfolio-stats">
            <span>ER: 0.0525%</span>
            <span class="exp-return-label" data-portfolio="1">Exp Return: —</span>
          </div>
        </div>
      </div>

      <div class="portfolio-card recommended" data-portfolio="2">
        <div class="portfolio-header">
          <input type="radio" name="portfolio" id="p2" value="2">
          <label for="p2">
            <strong>Classic Three-Fund</strong>
            <span class="badge recommended-badge">⭐ Recommended</span>
          </label>
        </div>
        <div class="portfolio-details">
          <p class="portfolio-desc">Ultimate simple, diversified portfolio</p>
          <ul class="portfolio-assets">
            <li>VTI: 54% • VXUS: 26%</li>
            <li>BND: 20%</li>
          </ul>
          <div class="portfolio-stats">
            <span>ER: 0.0404%</span>
            <span class="exp-return-label" data-portfolio="2">Exp Return: —</span>
          </div>
        </div>
      </div>

      <div class="portfolio-card" data-portfolio="3">
        <div class="portfolio-header">
          <input type="radio" name="portfolio" id="p3" value="3">
          <label for="p3">
            <strong>Golden Butterfly</strong>
            <span class="badge">All-Weather</span>
          </label>
        </div>
        <div class="portfolio-details">
          <p class="portfolio-desc">All market conditions with gold</p>
          <ul class="portfolio-assets">
            <li>VTI: 30% • VXUS: 10%</li>
            <li>SHY: 20% • TLT: 20% • GLD: 20%</li>
          </ul>
          <div class="portfolio-stats">
            <span>ER: 0.1560%</span>
            <span class="exp-return-label" data-portfolio="3">Exp Return: —</span>
          </div>
        </div>
      </div>

      <div class="portfolio-card" data-portfolio="4">
        <div class="portfolio-header">
          <input type="radio" name="portfolio" id="p4" value="4">
          <label for="p4">
            <strong>Modern Bogleheads</strong>
            <span class="badge">TIPS & REITs</span>
          </label>
        </div>
        <div class="portfolio-details">
          <p class="portfolio-desc">Enhanced diversification + inflation</p>
          <ul class="portfolio-assets">
            <li>VTI: 40% • VXUS: 20%</li>
            <li>VNQ: 10% • VTIP: 15% • BND: 15%</li>
          </ul>
          <div class="portfolio-stats">
            <span>ER: 0.0485%</span>
            <span class="exp-return-label" data-portfolio="4">Exp Return: —</span>
          </div>
        </div>
      </div>
    </div>
  </div>

  
  <div class="mc-section">
    <h3>2. Set Your Parameters</h3>
    <div class="input-grid">
      <div class="input-group">
        <label for="initial-value">
          Starting Portfolio Value
          <span class="help-text">How much are you starting with?</span>
        </label>
        <div class="input-with-prefix">
          <span class="prefix">$</span>
          <input type="number" id="initial-value" value="1000000" min="10000" step="10000">
        </div>
      </div>

      <div class="input-group">
        <label for="withdrawal-rate">
          Annual Withdrawal Rate
          <span class="help-text">% of portfolio value</span>
        </label>
        <div class="input-with-suffix">
          <input type="number" id="withdrawal-rate" value="3.0" min="1" max="10" step="0.1">
          <span class="suffix">%</span>
        </div>
      </div>

      <div class="input-group">
        <label for="withdrawal-strategy">
          Withdrawal Strategy
          <span class="help-text">How withdrawals adjust over time</span>
        </label>
        <select id="withdrawal-strategy">
          <option value="constant">Constant Dollar (Traditional SWR)</option>
          <option value="dynamic">Dynamic Spending (Vanguard)</option>
        </select>
      </div>

      <div class="input-group">
        <label for="years">
          Simulation Duration
          <span class="help-text">Years until you turn 100</span>
        </label>
        <div class="input-with-suffix">
          <input type="number" id="years" value="50" min="10" max="70" step="5">
          <span class="suffix">years</span>
        </div>
      </div>

      <div class="input-group">
        <label for="advisor-fee">
          Additional Fees (optional)
          <span class="help-text">Robo-advisor or financial advisor fee</span>
        </label>
        <div class="input-with-suffix">
          <input type="number" id="advisor-fee" value="0" min="0" max="2" step="0.05">
          <span class="suffix">%</span>
        </div>
      </div>
    </div>

    <div class="advanced-options">
      <button type="button" class="toggle-advanced" id="toggle-advanced">
        <span class="toggle-text">⚙️ Advanced Options</span>
        <span class="arrow">▼</span>
      </button>
      <div class="advanced-content" id="advanced-content" style="display: none;">
        <div class="input-grid">
          <div class="input-group">
            <label for="simulations">
              Number of Simulations
              <span class="help-text">More = more accurate (slower)</span>
            </label>
            <select id="simulations">
              <option value="1000">1,000 (Fast)</option>
              <option value="10000" selected>10,000 (Recommended)</option>
              <option value="50000">50,000 (Accurate)</option>
              <option value="100000">100,000 (Very Accurate)</option>
            </select>
          </div>

          <div class="input-group">
            <label for="inflation">
              Inflation Rate
              <span class="help-text">Expected annual inflation</span>
            </label>
            <div class="input-with-suffix">
              <input type="number" id="inflation" value="2.5" min="0" max="10" step="0.1">
              <span class="suffix">%</span>
            </div>
          </div>

          <div class="input-group checkbox-group">
            <label>
              <input type="checkbox" id="fat-tails">
              <span>Enable Fat-Tail Mode</span>
              <span class="help-text">Better models black swan events</span>
            </label>
          </div>

          <div class="input-group" id="dynamic-floor-group" style="display: none;">
            <label for="dynamic-floor">
              Dynamic Floor
              <span class="help-text">% below inflation-adjusted spending</span>
            </label>
            <div class="input-with-suffix">
              <input type="number" id="dynamic-floor" value="2.5" min="0" max="20" step="0.5">
              <span class="suffix">%</span>
            </div>
          </div>

          <div class="input-group" id="dynamic-ceiling-group" style="display: none;">
            <label for="dynamic-ceiling">
              Dynamic Ceiling
              <span class="help-text">% above inflation-adjusted spending</span>
            </label>
            <div class="input-with-suffix">
              <input type="number" id="dynamic-ceiling" value="5.0" min="0" max="20" step="0.5">
              <span class="suffix">%</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  
  <div class="mc-section">
    <button type="button" class="btn-primary btn-large" id="run-simulation">
      <span class="btn-text">🚀 Run Simulation</span>
      <span class="btn-loading" style="display: none;">⏳ Running...</span>
    </button>
  </div>

  
  <div class="mc-section results-section" id="results-section" style="display: none;">
    <h3>📊 Simulation Results</h3>

    
    <div class="summary-grid">
      <div class="summary-card success">
        <div class="card-header">Median Outcome</div>
        <div class="card-value" id="median-value">$0</div>
        <div class="card-subtitle" id="median-real">$0 real</div>
      </div>

      <div class="summary-card warning">
        <div class="card-header">Worst Case (5th %ile)</div>
        <div class="card-value" id="p5-value">$0</div>
        <div class="card-subtitle" id="p5-real">$0 real</div>
      </div>

      <div class="summary-card info">
        <div class="card-header">Best Case (95th %ile)</div>
        <div class="card-value" id="p95-value">$0</div>
        <div class="card-subtitle" id="p95-real">$0 real</div>
      </div>

      <div class="summary-card" id="depletion-card">
        <div class="card-header">Depletion Risk</div>
        <div class="card-value" id="depletion-prob">0.0%</div>
        <div class="card-subtitle">Chance of running out</div>
      </div>
    </div>

    
    <p class="growth-context" id="growth-context">
      Without withdrawals, your median outcome would be
      <strong id="no-wd-median">$0</strong>
      (<span id="no-wd-median-real">$0 real</span>).
      Your withdrawals cost about
      <strong id="wd-cost">$0</strong>
      of median compound growth over the horizon. <em>That's the real price of retirement spending.</em>
    </p>

    
    <div class="chart-container">
      <h4>Portfolio Growth Over Time</h4>
      <p class="chart-caption">
        The dashed brass line is the <strong>median path with no withdrawals</strong> — your portfolio's pure growth potential against the same market shocks. The gap between it and the solid green median is what your spending costs you, year by year.
      </p>
      <canvas id="growth-chart"></canvas>
    </div>

    <div class="chart-container">
      <h4>Distribution of Final Values</h4>
      <canvas id="distribution-chart"></canvas>
    </div>

    
    <div class="results-table">
      <h4>Detailed Metrics</h4>
      <table>
        <thead>
          <tr>
            <th>Metric</th>
            <th>Nominal Value</th>
            <th>Real Value (Today's $)</th>
          </tr>
        </thead>
        <tbody id="results-table-body">
        </tbody>
      </table>
    </div>

    
    <div class="risk-metrics">
      <h4>Risk Analysis</h4>
      <div class="metrics-grid">
        <div class="metric-item">
          <span class="metric-label">Median Max Drawdown</span>
          <span class="metric-value" id="median-drawdown">0%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">Worst Drawdown (95th %ile)</span>
          <span class="metric-value" id="worst-drawdown">0%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">Sharpe Ratio</span>
          <span class="metric-value" id="sharpe-ratio">0.00</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">Sortino Ratio</span>
          <span class="metric-value" id="sortino-ratio">0.00</span>
        </div>
      </div>
    </div>

    
    <div class="risk-metrics">
      <h4>S&P 500 Benchmark Comparison</h4>
      <div class="metrics-grid">
        <div class="metric-item">
          <span class="metric-label">S&P 500 Median (Nominal)</span>
          <span class="metric-value" id="sp500-median">$0</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">S&P 500 Median (Real)</span>
          <span class="metric-value" id="sp500-median-real">$0</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">Beat S&P 500 (Nominal)</span>
          <span class="metric-value" id="beat-sp500">0%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">Beat S&P 500 (Real)</span>
          <span class="metric-value" id="beat-sp500-real">0%</span>
        </div>
      </div>
    </div>

    
    <div class="risk-metrics">
      <h4>Fee Impact Analysis</h4>
      <div class="metrics-grid">
        <div class="metric-item">
          <span class="metric-label">Blended Expense Ratio</span>
          <span class="metric-value" id="blended-er">0.00%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">Total Annual Fee</span>
          <span class="metric-value" id="total-fee">0.00%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">Fee Drag (Total)</span>
          <span class="metric-value" id="fee-drag">0.0%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">Cost of Fees</span>
          <span class="metric-value" id="fee-cost">$0</span>
        </div>
      </div>
    </div>

    
    <div class="results-table">
      <h4>Withdrawal Analysis (First 10 Years)</h4>
      <table>
        <thead>
          <tr>
            <th>Year</th>
            <th>Average</th>
            <th>Median</th>
            <th>5th %ile</th>
            <th>95th %ile</th>
          </tr>
        </thead>
        <tbody id="withdrawal-table-first">
        </tbody>
      </table>
    </div>

    <div class="results-table">
      <h4>Withdrawal Analysis (Last 10 Years)</h4>
      <table>
        <thead>
          <tr>
            <th>Year</th>
            <th>Average</th>
            <th>Median</th>
            <th>5th %ile</th>
            <th>95th %ile</th>
          </tr>
        </thead>
        <tbody id="withdrawal-table-last">
        </tbody>
      </table>
    </div>

    
    <div class="risk-metrics">
      <h4>Goal Probability Analysis</h4>
      <p style="margin-bottom: 1rem; color: var(--mc-text-secondary);">Probability of reaching target by end of simulation:</p>
      <div class="metrics-grid">
        <div class="metric-item">
          <span class="metric-label">$1.5M</span>
          <span class="metric-value" id="goal-1500000">0%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">$2M</span>
          <span class="metric-value" id="goal-2000000">0%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">$3M</span>
          <span class="metric-value" id="goal-3000000">0%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">$5M</span>
          <span class="metric-value" id="goal-5000000">0%</span>
        </div>
        <div class="metric-item">
          <span class="metric-label">$10M</span>
          <span class="metric-value" id="goal-10000000">0%</span>
        </div>
      </div>
    </div>

    
    <div class="export-buttons">
      <button type="button" class="btn-secondary" id="export-csv">
        📥 Export to CSV
      </button>
      <button type="button" class="btn-secondary" id="copy-results">
        📋 Copy Results
      </button>
    </div>
  </div>

  
  <details class="mc-methodology">
    <summary>📐 Methodology &amp; assumptions</summary>
    <div class="mc-methodology-body">
      <h4>Engine</h4>
      <ul>
        <li><strong>Monthly Geometric Brownian Motion</strong> per asset, log-normal compounding (<code>value &times; exp(r)</code>) so portfolios can't go below zero.</li>
        <li><strong>Cholesky-decomposed correlation matrix</strong> drives correlated monthly returns across the assets in the chosen portfolio.</li>
        <li><strong>Fat-tail mode</strong> swaps Normal for Student's t (df=5) with variance correction.</li>
        <li><strong>Annual rebalance</strong> at year-end. Per-asset withdrawals are taken at target weights, which has a monthly partial-rebalance side effect.</li>
      </ul>
      <h4>What the metrics actually measure</h4>
      <ul>
        <li><strong>Median / 5th / 95th percentile</strong> — distribution of terminal portfolio values across paths.</li>
        <li><strong>Depletion risk</strong> — share of paths where terminal value &lt; $1.</li>
        <li><strong>Sharpe / Sortino</strong> — per-path risk-adjusted return computed from each simulation's gross monthly portfolio returns (annualized), then averaged across paths. <em>Not</em> a cross-path outcome-dispersion proxy.</li>
        <li><strong>Max drawdown</strong> — peak-to-trough decline of portfolio value <em>including withdrawals</em>. For retirees this conflates market loss with normal liquidation; expect drawdowns to climb over a long horizon even in benign markets.</li>
        <li><strong>P(beat S&amp;P 500)</strong> — the SP500 path shares VTI's correlated monthly shock (empirical ρ≈0.98) so the comparison happens in the same market state, not parallel universes.</li>
      </ul>
      <h4>Assumptions you should know about</h4>
      <ul>
        <li>Expected returns and volatilities are <strong>conservative empirical estimates</strong> (2000-2025 monthly data for US/intl equities, bonds, REITs, TIPS, gold). Bond and TIPS volatility have been bumped to ~4.5-5% to match empirical, not the textbook 3%.</li>
        <li><strong>No taxes.</strong> Withdrawals are pre-tax. Your real spending power depends on your jurisdiction.</li>
        <li><strong>No cash buffer mechanic.</strong> Real retirees often hold 1-2 years cash; not modelled. Biases outcomes slightly worse than reality for disciplined defenders.</li>
        <li><strong>Asset correlations are historical.</strong> Equity/long-bond correlation, in particular, has trended positive since 2022; the simulator uses a benign-decade prior. Treat Golden Butterfly's downside numbers as optimistic.</li>
      </ul>
      <p class="mc-methodology-note">
        Plan from the 5th percentile, not the median. Stack a regime-change haircut on top before sizing your spending. Monte Carlo is a stress test, not a forecast.
      </p>
    </div>
  </details>
</div>


<style>
:root {
  --mc-bg-primary: #ffffff;
  --mc-bg-secondary: #f9fafb;
  --mc-border: #e5e7eb;
  --mc-text-primary: #1f2937;
  --mc-text-secondary: #6b7280;
  --mc-card-bg: #ffffff;
  --mc-card-selected-bg: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
  --mc-card-recommended-bg: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
  --mc-primary-color: #2563eb;
  --mc-success-color: #22c55e;
  --mc-warning-color: #f59e0b;
  --mc-danger-color: #ef4444;
  --mc-input-bg: #ffffff;
  --mc-input-border: #d1d5db;
  --mc-button-bg: #2563eb;
  --mc-button-text: #ffffff;
  --mc-button-hover-bg: #1d4ed8;
}

.dark {
  --mc-bg-primary: #1a202c;
  --mc-bg-secondary: #2d3748;
  --mc-border: #4a5568;
  --mc-text-primary: #e5e7eb;
  --mc-text-secondary: #9ca3af;
  --mc-card-bg: #2d3748;
  --mc-card-selected-bg: linear-gradient(135deg, #1e3a5f 0%, #2d4a7c 100%);
  --mc-card-recommended-bg: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%);
  --mc-primary-color: #3b82f6;
  --mc-success-color: #10b981;
  --mc-warning-color: #f59e0b;
  --mc-danger-color: #ef4444;
  --mc-input-bg: #1a202c;
  --mc-input-border: #4a5568;
  --mc-button-bg: #3b82f6;
  --mc-button-text: #ffffff;
  --mc-button-hover-bg: #2563eb;
}

.monte-carlo-calculator {
  max-width: 1200px;
  margin: 2rem auto;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  background: var(--mc-bg-primary);
  color: var(--mc-text-primary);
}

.mc-header {
  text-align: center;
  margin-bottom: 2rem;
}

.mc-header h2 {
  font-size: 2rem;
  margin-bottom: 0.5rem;
  color: var(--mc-text-primary);
}

.mc-subtitle {
  color: var(--mc-text-secondary);
  font-size: 1.1rem;
}

.mc-section {
  border-radius: 12px;
  padding: 2rem;
  margin-bottom: 1.5rem;
}

.mc-section h3 {
  margin-top: 0;
  margin-bottom: 1.5rem;
  color: var(--mc-text-primary);
  font-size: 1.5rem;
}

 
.portfolio-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
}

.portfolio-card {
  background: var(--mc-card-bg);
  border: 2px solid var(--mc-border);
  border-radius: 8px;
  padding: 1rem;
  cursor: pointer;
  transition: all 0.3s ease;
}

.portfolio-card:hover {
  border-color: var(--mc-primary-color);
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.portfolio-card.selected {
  border-color: var(--mc-primary-color);
  background: var(--mc-card-selected-bg);
}

.portfolio-card.recommended:not(.selected) {
  border-color: var(--mc-border);
}

.portfolio-card.recommended .recommended-badge {
  background: var(--mc-primary-color);
  color: white;
  padding: 0.125rem 0.5rem;
  border-radius: 4px;
  font-size: 0.75rem;
  margin-left: 0.5rem;
}

.portfolio-header {
  display: flex;
  align-items: center;
  margin-bottom: 0.75rem;
}

.portfolio-header input[type="radio"] {
  display: none;
}

.portfolio-header label {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1rem;
  flex: 1;
}

.badge {
  font-size: 0.75rem;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  background: var(--mc-border);
  color: var(--mc-text-primary);
  font-weight: 500;
}

.recommended-badge {
  background: var(--mc-primary-color);
  color: white;
}

.portfolio-desc {
  font-size: 0.875rem;
  color: var(--mc-text-secondary);
  margin-bottom: 0.5rem;
}

.portfolio-assets {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0;
  font-size: 0.875rem;
  color: var(--mc-text-primary);
}

.portfolio-assets li {
  margin: 0.25rem 0;
}

.portfolio-stats {
  display: flex;
  justify-content: space-between;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--mc-border);
  font-size: 0.75rem;
  color: var(--mc-text-secondary);
}

 
.input-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1.5rem;
}

.input-group {
  display: flex;
  flex-direction: column;
}

.input-group label {
  font-weight: 600;
  margin-bottom: 0.5rem;
  color: var(--mc-text-primary);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.help-text {
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--mc-text-secondary);
}

.input-with-prefix,
.input-with-suffix {
  display: flex;
  align-items: center;
  background: var(--mc-input-bg);
  border: 1px solid var(--mc-input-border);
  border-radius: 6px;
  overflow: hidden;
}

.prefix,
.suffix {
  padding: 0.75rem;
  background: var(--mc-bg-secondary);
  color: var(--mc-text-secondary);
  font-weight: 600;
}

.input-with-prefix input,
.input-with-suffix input {
  flex: 1;
  border: none;
  padding: 0.75rem;
  font-size: 1rem;
}

input[type="number"],
select {
  padding: 0.75rem;
  border: 1px solid var(--mc-input-border);
  border-radius: 6px;
  font-size: 1rem;
  background: var(--mc-input-bg);
  color: var(--mc-text-primary);
}

input:focus,
select:focus {
  outline: none;
  border-color: var(--mc-primary-color);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}

 
.toggle-advanced {
  background: none;
  border: none;
  color: var(--mc-primary-color);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1rem;
  padding: 0.5rem 0;
  margin-bottom: 1rem;
  text-decoration: none !important;
}

.toggle-advanced span {
  text-decoration: none !important;
}

.toggle-advanced:hover {
  opacity: 0.8;
}

.arrow {
  transition: transform 0.3s ease;
}

.toggle-advanced.active .arrow {
  transform: rotate(180deg);
}

.toggle-advanced.active,
.toggle-advanced.active span,
.toggle-advanced .toggle-text {
  text-decoration: none !important;
}

.toggle-advanced.active .toggle-text {
  text-decoration: none !important;
}

.checkbox-group label {
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
}

.checkbox-group input[type="checkbox"] {
  width: 1.25rem;
  height: 1.25rem;
  cursor: pointer;
  accent-color: var(--mc-primary-color);
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: 2px solid var(--mc-border);
  border-radius: 4px;
  background-color: var(--mc-bg);
  position: relative;
  transition: all 0.2s ease;
}

.checkbox-group input[type="checkbox"]:checked {
  background-color: var(--mc-primary-color);
  border-color: var(--mc-primary-color);
}

.checkbox-group input[type="checkbox"]:checked::after {
  content: '✓';
  position: absolute;
  color: white;
  font-size: 1rem;
  font-weight: bold;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.checkbox-group input[type="checkbox"]:focus {
  outline: 2px solid var(--mc-primary-color);
  outline-offset: 2px;
}

 
.btn-primary,
.btn-secondary {
  padding: 1rem 2rem;
  border: none;
  border-radius: 8px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.btn-primary {
  background: var(--mc-button-bg);
  color: var(--mc-button-text);
  width: 100%;
}

.btn-primary:hover:not(:disabled) {
  background: var(--mc-button-hover-bg);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
}

.btn-primary:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.btn-secondary {
  background: var(--mc-card-bg);
  color: var(--mc-text-primary);
  border: 1px solid var(--mc-input-border);
}

.btn-secondary:hover {
  background: var(--mc-bg-secondary);
}

.btn-large {
  font-size: 1.25rem;
  padding: 1.25rem 2.5rem;
}

 
.summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
  margin-bottom: 2rem;
}

.summary-card {
  background: var(--mc-card-bg);
  border: 2px solid var(--mc-border);
  border-radius: 8px;
  padding: 1.5rem;
  text-align: center;
}

.summary-card.success {
  border-color: var(--mc-success-color);
  background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
}

.dark .summary-card.success {
  background: linear-gradient(135deg, #064e3b 0%, #065f46 100%);
}

.summary-card.warning {
  border-color: var(--mc-warning-color);
  background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
}

.dark .summary-card.warning {
  background: linear-gradient(135deg, #78350f 0%, #92400e 100%);
}

.summary-card.info {
  border-color: var(--mc-primary-color);
  background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
}

.dark .summary-card.info {
  background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
}

.card-header {
  font-size: 0.875rem;
  color: var(--mc-text-secondary);
  margin-bottom: 0.5rem;
  font-weight: 600;
}

.card-value {
  font-size: 1.75rem;
  font-weight: 700;
  color: var(--mc-text-primary);
  margin-bottom: 0.25rem;
}

.card-subtitle {
  font-size: 0.875rem;
  color: var(--mc-text-secondary);
}

 
.chart-container {
  background: white;
  border: 1px solid var(--color-neutral-200, #e5e7eb);
  border-radius: 8px;
  padding: 1.5rem;
  margin-bottom: 1.5rem;
}

.chart-container h4 {
  margin-top: 0;
  margin-bottom: 0.5rem;
  color: var(--color-neutral-800, #1f2937);
}

.chart-caption {
  margin: 0 0 1rem;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--mc-text-secondary);
}

.chart-caption strong {
  color: #8a6f2c;
  font-weight: 600;
}

.chart-container canvas {
  max-height: 500px !important;
  height: 500px !important;
}

 
.results-table {
  background: white;
  border: 1px solid var(--color-neutral-200, #e5e7eb);
  border-radius: 8px;
  padding: 1.5rem;
  margin-bottom: 1.5rem;
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
}

thead th {
  background: var(--color-neutral-100, #f3f4f6);
  padding: 0.75rem;
  text-align: left;
  font-weight: 600;
  color: var(--color-neutral-700, #374151);
  border-bottom: 2px solid var(--color-neutral-300, #d1d5db);
}

tbody td {
  padding: 0.75rem;
  border-bottom: 1px solid var(--color-neutral-200, #e5e7eb);
}

tbody tr:hover {
  background: var(--color-neutral-50, #f9fafb);
}

 
.risk-metrics {
  background: white;
  border: 1px solid var(--color-neutral-200, #e5e7eb);
  border-radius: 8px;
  padding: 1.5rem;
  margin-bottom: 1.5rem;
}

.metrics-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1.5rem;
}

.metric-item {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.metric-label {
  font-size: 0.875rem;
  color: var(--color-neutral-600, #6b7280);
  font-weight: 500;
}

.metric-value {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--color-neutral-900, #111827);
}

 
.export-buttons {
  display: flex;
  gap: 1rem;
  justify-content: center;
  flex-wrap: wrap;
}

 
.growth-context {
  background: var(--mc-bg-secondary);
  border-left: 3px solid var(--mc-primary-color);
  padding: 1rem 1.25rem;
  margin: 0 0 2rem;
  border-radius: 0 6px 6px 0;
  color: var(--mc-text-primary);
  font-size: 0.95rem;
  line-height: 1.55;
}

.growth-context strong {
  color: var(--mc-primary-color);
  font-weight: 700;
}

.growth-context em {
  color: var(--mc-text-secondary);
  font-style: italic;
}

 
.mc-methodology {
  margin-top: 2rem;
  border: 1px solid var(--mc-border);
  border-radius: 8px;
  background: var(--mc-card-bg);
  padding: 0.75rem 1rem;
}

.mc-methodology > summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--mc-text-primary);
  padding: 0.25rem 0;
  list-style: none;
}

.mc-methodology > summary::-webkit-details-marker { display: none; }

.mc-methodology > summary::before {
  content: '▸';
  display: inline-block;
  margin-right: 0.5rem;
  transition: transform 0.2s ease;
  color: var(--mc-text-secondary);
}

.mc-methodology[open] > summary::before {
  transform: rotate(90deg);
}

.mc-methodology-body {
  padding-top: 0.75rem;
  font-size: 0.9rem;
  color: var(--mc-text-secondary);
  line-height: 1.55;
}

.mc-methodology-body h4 {
  color: var(--mc-text-primary);
  font-size: 0.95rem;
  margin: 1rem 0 0.5rem;
}

.mc-methodology-body h4:first-of-type { margin-top: 0; }

.mc-methodology-body ul {
  margin: 0 0 0.5rem 1.25rem;
  padding: 0;
}

.mc-methodology-body li {
  margin-bottom: 0.4rem;
}

.mc-methodology-body code {
  background: var(--mc-bg-secondary);
  padding: 0.05rem 0.35rem;
  border-radius: 3px;
  font-size: 0.85em;
}

.mc-methodology-note {
  margin-top: 1rem;
  padding: 0.75rem 1rem;
  background: var(--mc-bg-secondary);
  border-left: 3px solid var(--mc-primary-color);
  border-radius: 0 4px 4px 0;
  font-style: italic;
}

 
@media (max-width: 768px) {
  .portfolio-grid {
    grid-template-columns: 1fr;
  }

  .input-grid {
    grid-template-columns: 1fr;
  }

  .summary-grid {
    grid-template-columns: 1fr;
  }

  .metrics-grid {
    grid-template-columns: 1fr;
  }
}

 
@media (prefers-color-scheme: dark) {
  .mc-header h2,
  .mc-section h3,
  .card-value,
  .metric-value {
    color: var(--color-neutral-100, #f3f4f6);
  }

  .mc-subtitle,
  .portfolio-desc,
  .card-subtitle,
  .metric-label {
    color: var(--color-neutral-400, #9ca3af);
  }

  .mc-section,
  .portfolio-card,
  .summary-card,
  .chart-container,
  .results-table,
  .risk-metrics {
    background: var(--color-neutral-800, #1f2937);
    border-color: var(--color-neutral-700, #374151);
  }

  input,
  select {
    background: var(--color-neutral-700, #374151);
    color: var(--color-neutral-100, #f3f4f6);
    border-color: var(--color-neutral-600, #4b5563);
  }
}
</style>


<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>


<script>
(function() {
  'use strict';

  
  const PORTFOLIOS = {
    1: {
      name: 'Dividend-Focused Portfolio',
      assets: {
        'VTI': {arithMean: 0.10, sd: 0.17, weight: 0.35, er: 0.0003},
        'SCHG': {arithMean: 0.105, sd: 0.18, weight: 0.15, er: 0.0004},
        'SCHD': {arithMean: 0.105, sd: 0.17, weight: 0.30, er: 0.0006},
        'SGOV': {arithMean: 0.04, sd: 0.02, weight: 0.20, er: 0.0009}
      },
      correlation: [
        [1.00, 0.90, 0.80, 0.10],
        [0.90, 1.00, 0.70, 0.10],
        [0.80, 0.70, 1.00, 0.15],
        [0.10, 0.10, 0.15, 1.00]
      ]
    },
    2: {
      name: 'Classic Three-Fund Bogleheads',
      assets: {
        'VTI': {arithMean: 0.10, sd: 0.17, weight: 0.54, er: 0.0003},
        'VXUS': {arithMean: 0.08, sd: 0.18, weight: 0.26, er: 0.0007},
        'BND': {arithMean: 0.04, sd: 0.045, weight: 0.20, er: 0.0003}
      },
      correlation: [
        [1.00, 0.85, 0.15],
        [0.85, 1.00, 0.10],
        [0.15, 0.10, 1.00]
      ]
    },
    3: {
      name: 'Golden Butterfly (All-Weather)',
      assets: {
        'VTI': {arithMean: 0.10, sd: 0.17, weight: 0.30, er: 0.0003},
        'VXUS': {arithMean: 0.08, sd: 0.18, weight: 0.10, er: 0.0007},
        'SHY': {arithMean: 0.025, sd: 0.01, weight: 0.20, er: 0.0015},
        'TLT': {arithMean: 0.05, sd: 0.12, weight: 0.20, er: 0.0015},
        'GLD': {arithMean: 0.045, sd: 0.16, weight: 0.20, er: 0.0040}
      },
      correlation: [
        [1.00, 0.85, 0.10, -0.05, 0.00],
        [0.85, 1.00, 0.10, -0.05, 0.00],
        [0.10, 0.10, 1.00, 0.40, 0.05],
        [-0.05, -0.05, 0.40, 1.00, 0.10],
        [0.00, 0.00, 0.05, 0.10, 1.00]
      ]
    },
    4: {
      name: 'Modern Bogleheads (TIPS & REITs)',
      assets: {
        'VTI': {arithMean: 0.10, sd: 0.17, weight: 0.40, er: 0.0003},
        'VXUS': {arithMean: 0.08, sd: 0.18, weight: 0.20, er: 0.0007},
        'VNQ': {arithMean: 0.09, sd: 0.23, weight: 0.10, er: 0.0012},
        'VTIP': {arithMean: 0.03, sd: 0.05, weight: 0.15, er: 0.0004},
        'BND': {arithMean: 0.04, sd: 0.045, weight: 0.15, er: 0.0003}
      },
      correlation: [
        [1.00, 0.85, 0.75, 0.10, 0.15],
        [0.85, 1.00, 0.70, 0.10, 0.10],
        [0.75, 0.70, 1.00, 0.20, 0.20],
        [0.10, 0.10, 0.20, 1.00, 0.70],
        [0.15, 0.10, 0.20, 0.70, 1.00]
      ]
    }
  };

  
  function arithmeticToGeometric(arithMean, sd) {
    return arithMean - (sd * sd) / 2;
  }

  function normalRandom() {
    
    let u = 0, v = 0;
    while(u === 0) u = Math.random();
    while(v === 0) v = Math.random();
    return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
  }

  
  function studentTRandom(df) {
    
    const z = normalRandom();
    let chiSq = 0;
    for (let i = 0; i < df; i++) {
      const n = normalRandom();
      chiSq += n * n;
    }
    return z / Math.sqrt(chiSq / df);
  }

  function choleskyDecomposition(matrix) {
    const n = matrix.length;
    const L = Array(n).fill(0).map(() => Array(n).fill(0));

    for (let i = 0; i < n; i++) {
      for (let j = 0; j <= i; j++) {
        let sum = 0;
        for (let k = 0; k < j; k++) {
          sum += L[i][k] * L[j][k];
        }

        if (i === j) {
          L[i][j] = Math.sqrt(Math.max(0, matrix[i][i] - sum));
        } else {
          L[i][j] = (matrix[i][j] - sum) / (L[j][j] || 1);
        }
      }
    }

    return L;
  }

  function multiplyMatrixVector(matrix, vector) {
    return matrix.map(row =>
      row.reduce((sum, val, i) => sum + val * vector[i], 0)
    );
  }

  function percentile(arr, p) {
    const sorted = [...arr].sort((a, b) => a - b);
    const index = (p / 100) * (sorted.length - 1);
    const lower = Math.floor(index);
    const upper = Math.ceil(index);
    const weight = index % 1;

    if (lower === upper) return sorted[lower];
    return sorted[lower] * (1 - weight) + sorted[upper] * weight;
  }

  
  function runSimulation(params) {
    const portfolio = PORTFOLIOS[params.portfolioId];
    const assets = Object.values(portfolio.assets);
    const assetNames = Object.keys(portfolio.assets);
    const nAssets = assets.length;

    
    const weights = assets.map(a => a.weight);
    const geometricReturns = assets.map(a => arithmeticToGeometric(a.arithMean, a.sd));
    const stdDevs = assets.map(a => a.sd);
    const expenseRatios = assets.map(a => a.er);

    const blendedER = expenseRatios.reduce((sum, er, i) => sum + er * weights[i], 0);
    const totalFee = blendedER + params.advisorFee;
    const netReturns = geometricReturns.map(r => r - totalFee);

    
    const grossReturn = geometricReturns.reduce((sum, r, i) => sum + r * weights[i], 0);
    const portfolioNetReturn = netReturns.reduce((sum, r, i) => sum + r * weights[i], 0);

    
    
    
    
    
    
    const correlation = portfolio.correlation;
    const cholesky = choleskyDecomposition(correlation);

    
    const SP500_PROXY = {
      mean: arithmeticToGeometric(0.10, 0.16),
      sd: 0.16,
      er: 0.0003
    };
    const sp500MeanAfterFees = SP500_PROXY.mean - SP500_PROXY.er - params.advisorFee;

    
    const nMonths = params.years * 12;
    const DEPLETION_THRESHOLD = 1.0;
    const initialAnnualWithdrawal = params.initialValue * params.withdrawalRate;

    
    const finalValues = [];
    const finalSP500Values = [];
    const finalNoWithdrawalValues = []; 
    const annualValuesOverTime = [];
    const annualNoWithdrawalValuesOverTime = []; 
    const annualSP500ValuesOverTime = [];
    const annualWithdrawalsHistory = [];
    const maxDrawdowns = [];
    const sp500MaxDrawdowns = [];
    const pathSharpes = [];  
    const pathSortinos = []; 

    
    
    
    
    const SP500_VTI_CORR = 0.98;
    const SP500_INDEP_WEIGHT = Math.sqrt(1 - SP500_VTI_CORR * SP500_VTI_CORR);

    
    for (let sim = 0; sim < params.nSimulations; sim++) {
      let assetValues = weights.map(w => params.initialValue * w);
      let noWithdrawalAssetValues = weights.map(w => params.initialValue * w);
      let sp500Value = params.initialValue;
      const monthlyValues = [params.initialValue];
      const monthlyNoWithdrawalValues = [params.initialValue];
      const monthlySP500Values = [params.initialValue];
      const monthlyWithdrawals = [];
      const monthlySP500Withdrawals = [];
      const pathMonthlyReturns = []; 
      let maxValue = params.initialValue;
      let maxSP500Value = params.initialValue;
      let maxDD = 0;
      let maxSP500DD = 0;
      let lastYearSpending = initialAnnualWithdrawal;
      let lastYearSP500Spending = initialAnnualWithdrawal;
      let currentYearWithdrawal = initialAnnualWithdrawal;
      let currentYearSP500Withdrawal = initialAnnualWithdrawal;

      
      const monthlyNetReturns = netReturns.map(r => r / 12);
      const monthlyStdDevs = stdDevs.map(sd => sd / Math.sqrt(12));
      const monthlySP500Mean = sp500MeanAfterFees / 12;
      const monthlySP500Sd = SP500_PROXY.sd / Math.sqrt(12);

      
      for (let month = 0; month < nMonths; month++) {
        const yearIndex = Math.floor(month / 12);
        const monthInYear = month % 12;

        
        let monthlyReturns, monthlySP500Return;

        
        
        
        let vtiStandardizedZ;
        let tScale = 1;

        if (params.fatTails) {
          
          tScale = Math.sqrt(5 / (5 - 2));
          const z = Array(nAssets).fill(0).map(() => studentTRandom(5));
          const correlatedZ = multiplyMatrixVector(cholesky, z);
          monthlyReturns = correlatedZ.map((cz, i) => {
            const scaledZ = cz / tScale;
            return monthlyNetReturns[i] + scaledZ * monthlyStdDevs[i];
          });
          vtiStandardizedZ = correlatedZ[0] / tScale;

          const sp500IndepZ = studentTRandom(5) / tScale;
          const sp500Shock = SP500_VTI_CORR * vtiStandardizedZ
                           + SP500_INDEP_WEIGHT * sp500IndepZ;
          monthlySP500Return = monthlySP500Mean + sp500Shock * monthlySP500Sd;
        } else {
          
          const z = Array(nAssets).fill(0).map(() => normalRandom());
          const correlatedZ = multiplyMatrixVector(cholesky, z);
          monthlyReturns = correlatedZ.map((cz, i) => monthlyNetReturns[i] + cz * monthlyStdDevs[i]);
          vtiStandardizedZ = correlatedZ[0];

          const sp500IndepZ = normalRandom();
          const sp500Shock = SP500_VTI_CORR * vtiStandardizedZ
                           + SP500_INDEP_WEIGHT * sp500IndepZ;
          monthlySP500Return = monthlySP500Mean + sp500Shock * monthlySP500Sd;
        }

        
        
        
        const grossPortfolioReturn = monthlyReturns.reduce(
          (sum, r, i) => sum + r * weights[i], 0
        );
        pathMonthlyReturns.push(grossPortfolioReturn);

        
        
        
        
        
        monthlyReturns = monthlyReturns.map(r => Math.max(-1.0, Math.min(1.0, r)));
        monthlySP500Return = Math.max(-1.0, Math.min(1.0, monthlySP500Return));

        assetValues = assetValues.map((val, i) => val * Math.exp(monthlyReturns[i]));
        noWithdrawalAssetValues = noWithdrawalAssetValues.map(
          (val, i) => val * Math.exp(monthlyReturns[i])
        );
        sp500Value *= Math.exp(monthlySP500Return);

        let portfolioValue = assetValues.reduce((sum, val) => sum + val, 0);

        
        if (monthInYear === 0) {
          if (params.withdrawalStrategy === 'constant') {
            
            currentYearWithdrawal = initialAnnualWithdrawal * Math.pow(1 + params.inflation, yearIndex);
            currentYearSP500Withdrawal = currentYearWithdrawal;
          } else {
            
            if (yearIndex === 0) {
              currentYearWithdrawal = portfolioValue * params.withdrawalRate;
              currentYearSP500Withdrawal = sp500Value * params.withdrawalRate;
            } else {
              
              const rawSpending = portfolioValue * params.withdrawalRate;
              const inflationAdjustedLast = lastYearSpending * (1 + params.inflation);
              const floor = inflationAdjustedLast * (1 - params.dynamicFloor);
              const ceiling = inflationAdjustedLast * (1 + params.dynamicCeiling);
              currentYearWithdrawal = Math.max(floor, Math.min(ceiling, rawSpending));

              
              const rawSP500Spending = sp500Value * params.withdrawalRate;
              const inflationAdjustedLastSP500 = lastYearSP500Spending * (1 + params.inflation);
              const floorSP500 = inflationAdjustedLastSP500 * (1 - params.dynamicFloor);
              const ceilingSP500 = inflationAdjustedLastSP500 * (1 + params.dynamicCeiling);
              currentYearSP500Withdrawal = Math.max(floorSP500, Math.min(ceilingSP500, rawSP500Spending));
            }
            lastYearSpending = currentYearWithdrawal;
            lastYearSP500Spending = currentYearSP500Withdrawal;
          }
        }

        
        const monthlyWithdrawalAmount = currentYearWithdrawal / 12;
        const monthlySP500WithdrawalAmount = currentYearSP500Withdrawal / 12;

        const withdrawal = Math.min(monthlyWithdrawalAmount, portfolioValue);
        const sp500Withdrawal = Math.min(monthlySP500WithdrawalAmount, sp500Value);

        monthlyWithdrawals.push(withdrawal);
        monthlySP500Withdrawals.push(sp500Withdrawal);

        
        assetValues = assetValues.map((val, i) =>
          Math.max(0, val - withdrawal * weights[i])
        );
        sp500Value = Math.max(0, sp500Value - sp500Withdrawal);

        portfolioValue = assetValues.reduce((sum, val) => sum + val, 0);

        
        if (monthInYear === 11 && portfolioValue > 0) {
          assetValues = weights.map(w => portfolioValue * w);
        }
        
        
        
        if (monthInYear === 11) {
          const noWPV = noWithdrawalAssetValues.reduce((s, v) => s + v, 0);
          if (noWPV > 0) {
            noWithdrawalAssetValues = weights.map(w => noWPV * w);
          }
        }

        monthlyValues.push(portfolioValue);
        monthlyNoWithdrawalValues.push(
          noWithdrawalAssetValues.reduce((s, v) => s + v, 0)
        );
        monthlySP500Values.push(sp500Value);

        
        maxValue = Math.max(maxValue, portfolioValue);
        maxSP500Value = Math.max(maxSP500Value, sp500Value);
        const drawdown = maxValue > 0 ? (maxValue - portfolioValue) / maxValue : 0;
        const sp500Drawdown = maxSP500Value > 0 ? (maxSP500Value - sp500Value) / maxSP500Value : 0;
        maxDD = Math.max(maxDD, drawdown);
        maxSP500DD = Math.max(maxSP500DD, sp500Drawdown);
      }

      
      const annualValues = [params.initialValue];
      const annualNoWithdrawalValues = [params.initialValue];
      const annualSP500Values = [params.initialValue];
      const annualWithdrawals = [];

      for (let year = 0; year < params.years; year++) {
        const startMonth = year * 12;
        const endMonth = startMonth + 12;

        const yearWithdrawal = monthlyWithdrawals.slice(startMonth, endMonth)
          .reduce((sum, w) => sum + w, 0);
        annualWithdrawals.push(yearWithdrawal);

        annualValues.push(monthlyValues[endMonth]);
        annualNoWithdrawalValues.push(monthlyNoWithdrawalValues[endMonth]);
        annualSP500Values.push(monthlySP500Values[endMonth]);
      }

      const finalValue = monthlyValues[monthlyValues.length - 1];
      const finalSP500Value = monthlySP500Values[monthlySP500Values.length - 1];

      finalValues.push(finalValue);
      finalSP500Values.push(finalSP500Value);
      finalNoWithdrawalValues.push(
        noWithdrawalAssetValues.reduce((s, v) => s + v, 0)
      );
      annualValuesOverTime.push(annualValues);
      annualNoWithdrawalValuesOverTime.push(annualNoWithdrawalValues);
      annualSP500ValuesOverTime.push(annualSP500Values);
      annualWithdrawalsHistory.push(annualWithdrawals);
      maxDrawdowns.push(maxDD);
      sp500MaxDrawdowns.push(maxSP500DD);

      
      
      
      
      const monthlyRf = 0.03 / 12;
      const n = pathMonthlyReturns.length;
      const mMean = pathMonthlyReturns.reduce((a, b) => a + b, 0) / n;
      const mVar = pathMonthlyReturns.reduce((s, r) => s + (r - mMean) ** 2, 0) / n;
      const mStd = Math.sqrt(mVar);
      const annualizedExcess = (mMean - monthlyRf) * 12;
      const annualizedStd = mStd * Math.sqrt(12);
      pathSharpes.push(annualizedStd > 0 ? annualizedExcess / annualizedStd : 0);

      
      const downs = pathMonthlyReturns
        .map(r => r - monthlyRf)
        .filter(d => d < 0);
      const downVar = downs.reduce((s, d) => s + d * d, 0) / n; 
      const downStd = Math.sqrt(downVar) * Math.sqrt(12);
      pathSortinos.push(downStd > 0 ? annualizedExcess / downStd : Infinity);
    }

    
    const inflationAdjustor = Math.pow(1 + params.inflation, params.years);
    const finalRealValues = finalValues.map(v => v / inflationAdjustor);
    const finalSP500RealValues = finalSP500Values.map(v => v / inflationAdjustor);

    
    const sharpeRatio = pathSharpes.reduce((a, b) => a + b, 0) / pathSharpes.length;
    const finiteSortinos = pathSortinos.filter(s => Number.isFinite(s));
    const sortinoRatio = finiteSortinos.length > 0
      ? finiteSortinos.reduce((a, b) => a + b, 0) / finiteSortinos.length
      : Infinity;

    
    const sp500MedianNominal = percentile(finalSP500Values, 50);
    const sp500MedianReal = percentile(finalSP500RealValues, 50);
    const probBeatSP500Nominal = finalValues.filter((v, i) => v > finalSP500Values[i]).length / finalValues.length;
    const probBeatSP500Real = finalRealValues.filter((v, i) => v > finalSP500RealValues[i]).length / finalRealValues.length;

    
    const goals = [1_500_000, 2_000_000, 3_000_000, 5_000_000, 10_000_000];
    const goalProbabilities = {};
    goals.forEach(goal => {
      goalProbabilities[goal] = finalValues.filter(v => v >= goal).length / finalValues.length;
    });

    
    const feeImpact = Math.pow(1 + grossReturn, params.years) / Math.pow(1 + portfolioNetReturn, params.years) - 1;
    const feeCost = params.initialValue * feeImpact;

    const noWithdrawalMedian = percentile(finalNoWithdrawalValues, 50);
    const noWithdrawalMedianReal = noWithdrawalMedian / inflationAdjustor;
    const withdrawalCostMedian = noWithdrawalMedian - percentile(finalValues, 50);
    const withdrawalCostMedianReal = withdrawalCostMedian / inflationAdjustor;

    const results = {
      median: percentile(finalValues, 50),
      p5: percentile(finalValues, 5),
      p95: percentile(finalValues, 95),
      mean: finalValues.reduce((a, b) => a + b, 0) / finalValues.length,

      noWithdrawalMedian: noWithdrawalMedian,
      noWithdrawalMedianReal: noWithdrawalMedianReal,
      withdrawalCostMedian: withdrawalCostMedian,
      withdrawalCostMedianReal: withdrawalCostMedianReal,

      medianReal: percentile(finalRealValues, 50),
      p5Real: percentile(finalRealValues, 5),
      p95Real: percentile(finalRealValues, 95),
      meanReal: finalRealValues.reduce((a, b) => a + b, 0) / finalRealValues.length,

      depletionProb: finalValues.filter(v => v < DEPLETION_THRESHOLD).length / finalValues.length,

      medianDrawdown: percentile(maxDrawdowns, 50),
      worstDrawdown: percentile(maxDrawdowns, 95),

      sharpeRatio: sharpeRatio,
      sortinoRatio: sortinoRatio,

      
      sp500MedianNominal: sp500MedianNominal,
      sp500MedianReal: sp500MedianReal,
      sp500MedianDrawdown: percentile(sp500MaxDrawdowns, 50),
      probBeatSP500Nominal: probBeatSP500Nominal,
      probBeatSP500Real: probBeatSP500Real,

      
      blendedER: blendedER,
      totalFee: totalFee,
      grossReturn: grossReturn,
      portfolioNetReturn: portfolioNetReturn,
      feeImpact: feeImpact,
      feeCost: feeCost,

      
      goalProbabilities: goalProbabilities,

      valuesOverTime: annualValuesOverTime,
      noWithdrawalValuesOverTime: annualNoWithdrawalValuesOverTime,
      sp500ValuesOverTime: annualSP500ValuesOverTime,
      withdrawalsHistory: annualWithdrawalsHistory,
      finalValues: finalValues,
      finalRealValues: finalRealValues,

      portfolioName: portfolio.name
    };

    return results;
  }

  
  
  
  function computePortfolioGeoReturn(portfolio) {
    const a = Object.values(portfolio.assets);
    const w = a.map(x => x.weight);
    const sd = a.map(x => x.sd);
    const mu = a.map(x => x.arithMean);
    const corr = portfolio.correlation;

    const portArith = mu.reduce((s, m, i) => s + m * w[i], 0);
    let portVar = 0;
    for (let i = 0; i < a.length; i++) {
      for (let j = 0; j < a.length; j++) {
        portVar += w[i] * w[j] * sd[i] * sd[j] * corr[i][j];
      }
    }
    return portArith - portVar / 2;
  }

  
  const UI = {
    init() {
      this.renderExpectedReturns();
      this.bindEvents();
    },

    renderExpectedReturns() {
      document.querySelectorAll('.exp-return-label').forEach(el => {
        const id = parseInt(el.dataset.portfolio);
        const p = PORTFOLIOS[id];
        if (!p) return;
        const geo = computePortfolioGeoReturn(p);
        el.textContent = `Exp Return: ${(geo * 100).toFixed(1)}%`;
      });
    },

    bindEvents() {
      
      document.querySelectorAll('.portfolio-card').forEach(card => {
        card.addEventListener('click', (e) => {
          
          if (e.target.tagName === 'INPUT' || e.target.tagName === 'LABEL') {
            return;
          }

          const radio = card.querySelector('input[type="radio"]');
          if (radio) {
            radio.checked = true;
            
            radio.dispatchEvent(new Event('change'));
          }
        });
      });

      
      document.querySelectorAll('input[name="portfolio"]').forEach(radio => {
        radio.addEventListener('change', (e) => {
          document.querySelectorAll('.portfolio-card').forEach(c => {
            c.classList.remove('selected');
          });
          if (e.target.checked) {
            e.target.closest('.portfolio-card').classList.add('selected');
          }
        });
      });

      
      const checkedRadio = document.querySelector('input[name="portfolio"]:checked');
      if (checkedRadio) {
        checkedRadio.closest('.portfolio-card').classList.add('selected');
      }

      
      const toggleBtn = document.getElementById('toggle-advanced');
      const advancedContent = document.getElementById('advanced-content');
      toggleBtn.addEventListener('click', () => {
        const isVisible = advancedContent.style.display !== 'none';
        advancedContent.style.display = isVisible ? 'none' : 'block';
        toggleBtn.classList.toggle('active', !isVisible);
      });

      
      const withdrawalStrategySelect = document.getElementById('withdrawal-strategy');
      const dynamicFloorGroup = document.getElementById('dynamic-floor-group');
      const dynamicCeilingGroup = document.getElementById('dynamic-ceiling-group');

      withdrawalStrategySelect.addEventListener('change', (e) => {
        const isDynamic = e.target.value === 'dynamic';
        dynamicFloorGroup.style.display = isDynamic ? 'block' : 'none';
        dynamicCeilingGroup.style.display = isDynamic ? 'block' : 'none';

        
        if (isDynamic && advancedContent.style.display === 'none') {
          advancedContent.style.display = 'block';
          toggleBtn.classList.add('active');
        }
      });

      
      document.getElementById('run-simulation').addEventListener('click', () => {
        this.runSimulation();
      });

      
      document.getElementById('export-csv').addEventListener('click', () => {
        this.exportCSV();
      });

      document.getElementById('copy-results').addEventListener('click', () => {
        this.copyResults();
      });
    },

    getParams() {
      const selectedPortfolio = document.querySelector('input[name="portfolio"]:checked');
      if (!selectedPortfolio) {
        throw new Error('Please select a portfolio before running the simulation');
      }

      const withdrawalStrategy = document.getElementById('withdrawal-strategy')?.value || 'constant';
      const isDynamic = withdrawalStrategy === 'dynamic';

      
      
      const num = (id, divisor = 1) => {
        const raw = document.getElementById(id)?.value;
        const v = parseFloat(raw);
        return Number.isFinite(v) ? v / divisor : null;
      };
      const intNum = (id) => {
        const v = parseInt(document.getElementById(id)?.value);
        return Number.isFinite(v) ? v : null;
      };

      const params = {
        portfolioId: parseInt(selectedPortfolio.value),
        initialValue: num('initial-value') ?? 1000000,
        withdrawalRate: num('withdrawal-rate', 100) ?? 0.04,
        withdrawalStrategy: withdrawalStrategy,
        years: intNum('years') ?? 30,
        advisorFee: num('advisor-fee', 100) ?? 0,
        nSimulations: intNum('simulations') ?? 10000,
        inflation: num('inflation', 100) ?? 0.025,
        fatTails: document.getElementById('fat-tails')?.checked ?? false,
        dynamicFloor: isDynamic ? (num('dynamic-floor', 100) ?? 0.025) : 0,
        dynamicCeiling: isDynamic ? (num('dynamic-ceiling', 100) ?? 0.05) : 0
      };

      
      if (isNaN(params.portfolioId) || isNaN(params.initialValue) || isNaN(params.withdrawalRate)) {
        console.error('Invalid params:', params);
        throw new Error('Invalid input values detected');
      }

      return params;
    },

    async runSimulation() {
      const btn = document.getElementById('run-simulation');
      const btnText = btn.querySelector('.btn-text');
      const btnLoading = btn.querySelector('.btn-loading');

      try {
        btn.disabled = true;
        btnText.style.display = 'none';
        btnLoading.style.display = 'inline';

        
        await new Promise(resolve => setTimeout(resolve, 100));

        const params = this.getParams();
        const results = runSimulation(params);

        this.displayResults(results, params);

        
        document.getElementById('results-section').scrollIntoView({ behavior: 'smooth' });
      } catch (error) {
        console.error('Simulation error:', error);
        alert('Error running simulation: ' + error.message + '\n\nPlease check that you have selected a portfolio and all inputs are valid.\n\nCheck browser console (F12) for details.');
      } finally {
        btn.disabled = false;
        btnText.style.display = 'inline';
        btnLoading.style.display = 'none';
      }
    },

    displayResults(results, params) {
      
      document.getElementById('results-section').style.display = 'block';

      
      this.updateCard('median-value', results.median);
      this.updateCard('median-real', results.medianReal, true);
      this.updateCard('p5-value', results.p5);
      this.updateCard('p5-real', results.p5Real, true);
      this.updateCard('p95-value', results.p95);
      this.updateCard('p95-real', results.p95Real, true);

      
      document.getElementById('no-wd-median').textContent =
        this.formatCurrency(results.noWithdrawalMedian);
      document.getElementById('no-wd-median-real').textContent =
        this.formatCurrency(results.noWithdrawalMedianReal) + ' real';
      document.getElementById('wd-cost').textContent =
        this.formatCurrency(results.withdrawalCostMedian);

      
      const depletionCard = document.getElementById('depletion-card');
      const depletionProb = document.getElementById('depletion-prob');
      depletionProb.textContent = (results.depletionProb * 100).toFixed(1) + '%';

      if (results.depletionProb === 0) {
        depletionCard.classList.add('success');
        depletionCard.classList.remove('warning');
      } else if (results.depletionProb < 0.05) {
        depletionCard.classList.add('info');
        depletionCard.classList.remove('warning', 'success');
      } else {
        depletionCard.classList.add('warning');
        depletionCard.classList.remove('success', 'info');
      }

      
      this.updateTable(results, params);

      
      document.getElementById('median-drawdown').textContent =
        (results.medianDrawdown * 100).toFixed(1) + '%';
      document.getElementById('worst-drawdown').textContent =
        (results.worstDrawdown * 100).toFixed(1) + '%';
      document.getElementById('sharpe-ratio').textContent = results.sharpeRatio.toFixed(2);
      document.getElementById('sortino-ratio').textContent =
        isFinite(results.sortinoRatio) ? results.sortinoRatio.toFixed(2) : '∞';

      
      document.getElementById('sp500-median').textContent = this.formatCurrency(results.sp500MedianNominal);
      document.getElementById('sp500-median-real').textContent = this.formatCurrency(results.sp500MedianReal);
      document.getElementById('beat-sp500').textContent = (results.probBeatSP500Nominal * 100).toFixed(1) + '%';
      document.getElementById('beat-sp500-real').textContent = (results.probBeatSP500Real * 100).toFixed(1) + '%';

      
      document.getElementById('blended-er').textContent = (results.blendedER * 100).toFixed(4) + '%';
      document.getElementById('total-fee').textContent = (results.totalFee * 100).toFixed(4) + '%';
      document.getElementById('fee-drag').textContent = (results.feeImpact * 100).toFixed(1) + '%';
      document.getElementById('fee-cost').textContent = this.formatCurrency(results.feeCost);

      
      document.getElementById('goal-1500000').textContent =
        (results.goalProbabilities[1_500_000] * 100).toFixed(1) + '%';
      document.getElementById('goal-2000000').textContent =
        (results.goalProbabilities[2_000_000] * 100).toFixed(1) + '%';
      document.getElementById('goal-3000000').textContent =
        (results.goalProbabilities[3_000_000] * 100).toFixed(1) + '%';
      document.getElementById('goal-5000000').textContent =
        (results.goalProbabilities[5_000_000] * 100).toFixed(1) + '%';
      document.getElementById('goal-10000000').textContent =
        (results.goalProbabilities[10_000_000] * 100).toFixed(1) + '%';

      
      this.updateWithdrawalTables(results, params);

      
      this.drawCharts(results, params);

      
      this.currentResults = results;
      this.currentParams = params;
    },

    updateCard(id, value, isSubtitle = false) {
      const el = document.getElementById(id);
      if (isSubtitle) {
        el.textContent = this.formatCurrency(value) + ' real';
      } else {
        el.textContent = this.formatCurrency(value);
      }
    },

    formatCurrency(value) {
      return '$' + value.toLocaleString('en-US', {
        maximumFractionDigits: 0
      });
    },

    updateTable(results, params) {
      const tbody = document.getElementById('results-table-body');
      tbody.innerHTML = '';

      const rows = [
        ['Average Ending Value', results.mean, results.meanReal],
        ['Median Ending Value', results.median, results.medianReal],
        ['5th Percentile', results.p5, results.p5Real],
        ['95th Percentile', results.p95, results.p95Real]
      ];

      rows.forEach(([metric, nominal, real]) => {
        const tr = document.createElement('tr');
        tr.innerHTML = `
          <td>${metric}</td>
          <td>${this.formatCurrency(nominal)}</td>
          <td>${this.formatCurrency(real)}</td>
        `;
        tbody.appendChild(tr);
      });
    },

    updateWithdrawalTables(results, params) {
      
      const nYears = params.years;
      const withdrawalStats = [];

      for (let year = 0; year < nYears; year++) {
        const withdrawalsAtYear = results.withdrawalsHistory.map(path => path[year]);
        withdrawalStats.push({
          year: year + 1,
          average: withdrawalsAtYear.reduce((a, b) => a + b, 0) / withdrawalsAtYear.length,
          median: percentile(withdrawalsAtYear, 50),
          p5: percentile(withdrawalsAtYear, 5),
          p95: percentile(withdrawalsAtYear, 95)
        });
      }

      
      const firstTableBody = document.getElementById('withdrawal-table-first');
      firstTableBody.innerHTML = '';
      const first10 = withdrawalStats.slice(0, Math.min(10, nYears));

      first10.forEach(stat => {
        const tr = document.createElement('tr');
        tr.innerHTML = `
          <td>${stat.year}</td>
          <td>${this.formatCurrency(stat.average)}</td>
          <td>${this.formatCurrency(stat.median)}</td>
          <td>${this.formatCurrency(stat.p5)}</td>
          <td>${this.formatCurrency(stat.p95)}</td>
        `;
        firstTableBody.appendChild(tr);
      });

      
      const lastTableBody = document.getElementById('withdrawal-table-last');
      lastTableBody.innerHTML = '';
      const last10 = withdrawalStats.slice(Math.max(0, nYears - 10));

      last10.forEach(stat => {
        const tr = document.createElement('tr');
        tr.innerHTML = `
          <td>${stat.year}</td>
          <td>${this.formatCurrency(stat.average)}</td>
          <td>${this.formatCurrency(stat.median)}</td>
          <td>${this.formatCurrency(stat.p5)}</td>
          <td>${this.formatCurrency(stat.p95)}</td>
        `;
        lastTableBody.appendChild(tr);
      });
    },

    drawCharts(results, params) {
      this.drawGrowthChart(results, params);
      this.drawDistributionChart(results);
    },

    drawGrowthChart(results, params) {
      const ctx = document.getElementById('growth-chart');

      
      const years = Array.from({length: params.years + 1}, (_, i) => i);
      const p5OverTime = [];
      const p50OverTime = [];
      const p95OverTime = [];
      const noWdMedianOverTime = []; 

      for (let year = 0; year <= params.years; year++) {
        const valuesAtYear = results.valuesOverTime.map(path => path[year]);
        p5OverTime.push(percentile(valuesAtYear, 5));
        p50OverTime.push(percentile(valuesAtYear, 50));
        p95OverTime.push(percentile(valuesAtYear, 95));

        const noWdAtYear = results.noWithdrawalValuesOverTime.map(path => path[year]);
        noWdMedianOverTime.push(percentile(noWdAtYear, 50));
      }

      if (this.growthChart) {
        this.growthChart.destroy();
      }

      this.growthChart = new Chart(ctx, {
        type: 'line',
        data: {
          labels: years,
          datasets: [
            {
              label: 'Median, no withdrawals',
              data: noWdMedianOverTime,
              borderColor: 'rgba(184, 146, 58, 0.85)',  
              backgroundColor: 'rgba(184, 146, 58, 0)',
              borderWidth: 2,
              borderDash: [8, 6],
              pointRadius: 0,
              fill: false,
              tension: 0.15
            },
            {
              label: '95th Percentile',
              data: p95OverTime,
              borderColor: 'rgba(59, 130, 246, 0.8)',
              backgroundColor: 'rgba(59, 130, 246, 0.1)',
              fill: '+1'
            },
            {
              label: 'Median (50th)',
              data: p50OverTime,
              borderColor: 'rgba(16, 185, 129, 0.8)',
              backgroundColor: 'rgba(16, 185, 129, 0.1)',
              borderWidth: 3,
              fill: '+1'
            },
            {
              label: '5th Percentile',
              data: p5OverTime,
              borderColor: 'rgba(245, 158, 11, 0.8)',
              backgroundColor: 'rgba(245, 158, 11, 0.1)',
              fill: false
            }
          ]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          plugins: {
            legend: {
              position: 'top',
            },
            tooltip: {
              callbacks: {
                label: (context) => {
                  return context.dataset.label + ': ' +
                    this.formatCurrency(context.parsed.y);
                }
              }
            }
          },
          scales: {
            y: {
              beginAtZero: true,
              ticks: {
                callback: (value) => this.formatCurrency(value)
              }
            },
            x: {
              title: {
                display: true,
                text: 'Years'
              }
            }
          }
        }
      });
    },

    drawDistributionChart(results) {
      const ctx = document.getElementById('distribution-chart');

      
      const bins = 30;
      const min = Math.min(...results.finalValues);
      const max = Math.max(...results.finalValues);
      const binSize = (max - min) / bins;
      const histogram = Array(bins).fill(0);

      results.finalValues.forEach(value => {
        const binIndex = Math.min(Math.floor((value - min) / binSize), bins - 1);
        histogram[binIndex]++;
      });

      const labels = Array(bins).fill(0).map((_, i) =>
        this.formatCurrency(min + (i + 0.5) * binSize)
      );

      if (this.distributionChart) {
        this.distributionChart.destroy();
      }

      this.distributionChart = new Chart(ctx, {
        type: 'bar',
        data: {
          labels: labels,
          datasets: [{
            label: 'Frequency',
            data: histogram,
            backgroundColor: 'rgba(16, 185, 129, 0.6)',
            borderColor: 'rgba(16, 185, 129, 1)',
            borderWidth: 1
          }]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          plugins: {
            legend: {
              display: false
            }
          },
          scales: {
            y: {
              beginAtZero: true,
              title: {
                display: true,
                text: 'Number of Simulations'
              }
            },
            x: {
              title: {
                display: true,
                text: 'Final Portfolio Value'
              },
              ticks: {
                maxRotation: 45,
                minRotation: 45
              }
            }
          }
        }
      });
    },

    exportCSV() {
      if (!this.currentResults) return;

      const results = this.currentResults;
      const params = this.currentParams;

      let csv = 'Monte Carlo Retirement Simulation Results\n\n';
      csv += `Portfolio,${results.portfolioName}\n`;
      csv += `Initial Value,$${params.initialValue.toLocaleString()}\n`;
      csv += `Withdrawal Rate,${(params.withdrawalRate * 100).toFixed(1)}%\n`;
      csv += `Years,${params.years}\n`;
      csv += `Simulations,${params.nSimulations.toLocaleString()}\n\n`;

      csv += 'Metric,Nominal Value,Real Value\n';
      csv += `Median,${results.median.toFixed(0)},${results.medianReal.toFixed(0)}\n`;
      csv += `5th Percentile,${results.p5.toFixed(0)},${results.p5Real.toFixed(0)}\n`;
      csv += `95th Percentile,${results.p95.toFixed(0)},${results.p95Real.toFixed(0)}\n`;
      csv += `Mean,${results.mean.toFixed(0)},${results.meanReal.toFixed(0)}\n`;
      csv += `Depletion Probability,${(results.depletionProb * 100).toFixed(2)}%\n`;
      csv += `Median Max Drawdown,${(results.medianDrawdown * 100).toFixed(1)}%\n`;

      const blob = new Blob([csv], { type: 'text/csv' });
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'monte-carlo-results.csv';
      a.click();
    },

    copyResults() {
      if (!this.currentResults) return;

      const results = this.currentResults;
      const params = this.currentParams;

      let text = `Monte Carlo Retirement Simulation Results\n\n`;
      text += `Portfolio: ${results.portfolioName}\n`;
      text += `Initial Value: $${params.initialValue.toLocaleString()}\n`;
      text += `Withdrawal Rate: ${(params.withdrawalRate * 100).toFixed(1)}%\n\n`;
      text += `Median Ending: ${this.formatCurrency(results.median)} (${this.formatCurrency(results.medianReal)} real)\n`;
      text += `5th Percentile: ${this.formatCurrency(results.p5)} (${this.formatCurrency(results.p5Real)} real)\n`;
      text += `95th Percentile: ${this.formatCurrency(results.p95)} (${this.formatCurrency(results.p95Real)} real)\n`;
      text += `Depletion Risk: ${(results.depletionProb * 100).toFixed(1)}%\n`;

      navigator.clipboard.writeText(text).then(() => {
        alert('Results copied to clipboard!');
      });
    }
  };

  
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => UI.init());
  } else {
    UI.init();
  }
})();
</script>

<hr>

<h2 class="relative group">How to Use This Calculator
    <div id="how-to-use-this-calculator" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-to-use-this-calculator" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">Step 1: Choose Your Portfolio
    <div id="step-1-choose-your-portfolio" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#step-1-choose-your-portfolio" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Select from four professionally designed portfolios:</p>
<table>
	<thead>
			<tr>
					<th>Portfolio</th>
					<th>Best For</th>
					<th>Expected Geometric Return</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Dividend-Focused</strong></td>
					<td>Income seekers, US-focused investors</td>
					<td>8.2%</td>
			</tr>
			<tr>
					<td><strong>Three-Fund Bogleheads</strong></td>
					<td>Most investors (lowest fees, global diversification)</td>
					<td>7.4%</td>
			</tr>
			<tr>
					<td><strong>Golden Butterfly</strong></td>
					<td>Conservative investors worried about crashes</td>
					<td>5.9%</td>
			</tr>
			<tr>
					<td><strong>Modern Bogleheads</strong></td>
					<td>Inflation-conscious investors</td>
					<td>6.8%</td>
			</tr>
	</tbody>
</table>
<p>Returns are computed directly from each portfolio's asset assumptions and full covariance matrix using the portfolio-level Itô correction (μ minus half the portfolio variance). The calculator renders the same numbers live on each card.</p>

<h3 class="relative group">Step 2: Set Your Withdrawal Rate
    <div id="step-2-set-your-withdrawal-rate" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#step-2-set-your-withdrawal-rate" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>This determines how much you withdraw annually. The calculator supports two strategies:</p>
<p><strong>Constant Dollar (Traditional):</strong> Fixed inflation-adjusted withdrawals regardless of portfolio value. Simple but rigid.</p>
<p><strong>Dynamic Spending (Vanguard):</strong> Withdrawals adjust based on portfolio performance with floor/ceiling bounds. More sustainable for aggressive rates.</p>
<p><strong>Quick guide:</strong></p>
<ul>
<li>2.5-3.0% = Very conservative (50-year horizons)</li>
<li>3.0-3.5% = Moderate (40-year horizons)</li>
<li>4.0%+ = Aggressive (30-year horizons or with flexibility)</li>
</ul>

<h3 class="relative group">Step 3: Set Duration
    <div id="step-3-set-duration" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#step-3-set-duration" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Enter years until age 100 (or your planning horizon). Add 5-10 years as a buffer.</p>

<h3 class="relative group">Step 4: Add Fees (Optional)
    <div id="step-4-add-fees-optional" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#step-4-add-fees-optional" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Beyond ETF expense ratios, add any advisor or platform fees:</p>
<ul>
<li><strong>0.00%</strong> - DIY at Vanguard/Fidelity/Schwab</li>
<li><strong>0.25%</strong> - Robo-advisors</li>
<li><strong>1.00%</strong> - Traditional advisor (costs ~39% of wealth over 50 years!)</li>
</ul>

<h3 class="relative group">Step 5: Run Simulation
    <div id="step-5-run-simulation" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#step-5-run-simulation" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Click <strong>Run Simulation</strong> and wait 2-10 seconds. The default run is 10,000 scenarios. For tighter percentiles bump to 50,000 or 100,000 from Advanced Options (slower, more accurate).</p>
<hr>

<h2 class="relative group">Understanding Your Results
    <div id="understanding-your-results" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#understanding-your-results" aria-label="Anchor">#</a>
    </span>
    
</h2>
<table>
	<thead>
			<tr>
					<th>Metric</th>
					<th>What It Means</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Median Outcome</strong></td>
					<td>Most likely result (50th percentile)</td>
			</tr>
			<tr>
					<td><strong>5th Percentile</strong></td>
					<td>Your safety net. Plan around this number, not the median.</td>
			</tr>
			<tr>
					<td><strong>Depletion Risk</strong></td>
					<td>Probability of running out of money</td>
			</tr>
			<tr>
					<td><strong>Sharpe/Sortino Ratio</strong></td>
					<td>Risk-adjusted return (higher = better). Computed per simulation path from gross monthly returns, then averaged across paths.</td>
			</tr>
			<tr>
					<td><strong>Growth potential context</strong></td>
					<td>A sentence under the cards shows your median outcome <strong>without withdrawals</strong> and how much spending cost you in compound growth. Anchors the magnitude of the headline numbers.</td>
			</tr>
			<tr>
					<td><strong>Growth chart, dashed brass line</strong></td>
					<td>The median path of a parallel portfolio that compounds with the same market shocks but never withdraws. The gap between it and the solid green median is the real, year-by-year cost of your retirement spending.</td>
			</tr>
			<tr>
					<td><strong>Max Drawdown</strong></td>
					<td>Worst peak-to-trough decline. Includes the withdrawal effect, so retirement runs naturally show larger drawdowns than markets alone.</td>
			</tr>
	</tbody>
</table>
<p><strong>Depletion risk guidelines:</strong></p>
<ul>
<li>0-5% = Very safe</li>
<li>5-10% = Acceptable</li>
<li>10%+ = Consider reducing withdrawal rate</li>
</ul>
<hr>

<h2 class="relative group">Advanced Options
    <div id="advanced-options" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#advanced-options" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p><strong>Fat-Tail Mode:</strong> Layers a Student's t-distribution (df=5) on top of the default log-normal compounding to model extreme events (crashes and booms) more realistically than the bell curve alone. Recommended for conservative planning. If your plan survives fat-tail mode at a punishing withdrawal rate, your plan is genuinely robust.</p>
<p><strong>Dynamic Spending Bounds:</strong> Adjust floor (-2.5% default) and ceiling (+5% default) to control withdrawal variability.</p>
<hr>

<h2 class="relative group">Privacy &amp; Accuracy
    <div id="privacy--accuracy" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#privacy--accuracy" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>All calculations run in your browser. No data is collected or stored.</p>
<p>The engine is professional-grade:</p>
<ul>
<li><strong>Monthly Geometric Brownian Motion</strong> per asset. Returns compound via <code>value × exp(r)</code>, so portfolios mathematically cannot fall below zero.</li>
<li><strong>Cholesky-decomposed correlation matrix</strong> drives correlated monthly shocks across the assets in the chosen portfolio.</li>
<li><strong>Fat-tail mode</strong> swaps the Normal driver for a variance-corrected Student's t-distribution (df=5).</li>
<li><strong>Annual rebalance</strong> at year-end. Withdrawals are taken at target weights (this has a monthly partial-rebalance side effect, disclosed in the methodology box inside the calculator).</li>
<li><strong>Sharpe and Sortino</strong> are computed per simulation path from monthly gross portfolio returns, then averaged across paths. That's the textbook formulation, not a cross-path outcome-dispersion proxy.</li>
<li><strong>S&amp;P 500 comparison</strong> shares VTI's correlated monthly shock (empirical correlation 0.98). The &quot;probability of beating the S&amp;P&quot; is therefore measured in the same market state, not in two parallel universes.</li>
<li><strong>Expected return labels</strong> on each portfolio card are derived directly from the asset assumptions plus the full covariance matrix, not hardcoded marketing numbers.</li>
<li><strong>Parallel no-withdrawal portfolio</strong> runs alongside the main simulation using the same Cholesky-correlated monthly shocks. Its median terminal value (and its full annual path on the chart) anchors the cost of your spending. If your median outcome is $16M and the no-withdrawal median is $40M, withdrawals cost you $24M of compound growth. The dashed brass line on the growth chart is that parallel portfolio's median path over time.</li>
</ul>
<p>Open the <strong>Methodology and assumptions</strong> disclosure inside the calculator for the full set of caveats (no taxes, no cash buffer, historical correlations, the drawdown-includes-withdrawals convention).</p>
<hr>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow" style="background-color: #0f5132"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>

  <span
    
      style="color: #ffffff"
    
    ><strong>Ready to dive deeper?</strong> Our <strong><a href="/posts/monte-carlo-simulation-retirement-planning/" >Complete Guide to Monte Carlo Retirement Planning</a></strong> explains sequence of returns risk, the 4% rule limitations, and how to build safety margins into your plan.</span>
</div>


  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>Disclaimer:</strong> This calculator reflects my personal views and is for educational purposes only. It is not financial advice. Every situation is different. Always check your country's specific tax and investment rules before acting. See the full <a href="/disclaimer/" >Disclaimer</a> and <a href="/privacy/" >Privacy Policy</a> for the long version.</span>
</div>

]]></content:encoded><media:content url="https://libreleo.com/img/featured/monte-carlo-retirement-calculator.webp" medium="image"/></item><item><title>Monte Carlo Simulation for Retirement: Why Simple Calculators Get It Wrong</title><link>https://libreleo.com/posts/monte-carlo-simulation-retirement-planning/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://libreleo.com/posts/monte-carlo-simulation-retirement-planning/</guid><description>Most retirement calculators pretend the market grows at a steady 7% every year. It doesn't. Monte Carlo simulation runs thousands of possible futures so you can plan for what actually happens, not the line on a brochure.</description><content:encoded><![CDATA[<div class="lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl">
  Most retirement calculators show you a single, smooth growth line at &quot;7% per year&quot; and call it a plan. That's not a plan. Monte Carlo simulation runs thousands of possible futures against your numbers so you can see what actually happens when markets do market things.
</div>


<h2 class="relative group">The problem with average returns
    <div id="the-problem-with-average-returns" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-problem-with-average-returns" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>If someone tells you stocks return 10% on average, your brain wants to multiply your portfolio by 1.10 every year for 30 years and put a number on the screen. That number is simply ridiculous.</p>
<p>Markets don't compound in straight lines. A simple example:</p>
<ul>
<li>Year 1: +20%. Your $100 becomes $120.</li>
<li>Year 2: -10%. Your $120 becomes $108.</li>
</ul>
<p>The arithmetic average of those two years is 5%. Your actual compound growth is 3.9%. Over 30 years that gap is the difference between retiring at 55 and retiring at 62.</p>
<p>Real markets are noisier than that. Some years are +25%. Some are -35%. The order matters. Monte Carlo simulation is the only way to capture both.</p>

<h2 class="relative group">What Monte Carlo actually does
    <div id="what-monte-carlo-actually-does" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-monte-carlo-actually-does" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The name comes from the casino in Monaco, which is fitting because the whole approach is built on probability.</p>
<p>Here is the process:</p>
<ol>
<li>Define your inputs: starting balance, withdrawal rate, time horizon, asset allocation.</li>
<li>Generate a random sequence of monthly returns using each asset's historical mean and volatility.</li>
<li>Compound through the horizon. Withdraw money each year. Track the path.</li>
<li>Repeat thousands of times. Look at the distribution: how many runs survived, how many depleted, what the median outcome looked like.</li>
</ol>
<p>Each run is one possible future. Maybe you retire and immediately eat a crash. Maybe you get a lucky decade of bull market right at the start. Monte Carlo shows you the whole range so you can plan for the bad ones, not just hope for the good ones.</p>

<h2 class="relative group">Sequence of returns risk is the real fight
    <div id="sequence-of-returns-risk-is-the-real-fight" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sequence-of-returns-risk-is-the-real-fight" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>This is the part that keeps early retirees awake at night.</p>
<p>While you are still working and adding money, a crash early in your career is great. You buy cheap. Compounding loves you back.</p>
<p>After you stop working and start withdrawing, an early crash is a different beast entirely. You are selling assets to fund your life right as those assets get cheap. Every dollar you pull out at the bottom is gone. The portfolio that should have recovered now has less left to recover with.</p>
<p>Two retirees, same long-run average return, different sequence:</p>
<table>
	<thead>
			<tr>
					<th>Scenario</th>
					<th>Early returns</th>
					<th>Late returns</th>
					<th>Final balance</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Lucky</td>
					<td>+15%, +12%, +8%</td>
					<td>-10%, -5%</td>
					<td>$2.1M</td>
			</tr>
			<tr>
					<td>Unlucky</td>
					<td>-10%, -15%, -8%</td>
					<td>+20%, +15%</td>
					<td>$400K</td>
			</tr>
	</tbody>
</table>
<p>Same average. Five times the wealth gap. That is sequence of returns risk in one table, and it's exactly what Monte Carlo captures by randomizing the order of returns across thousands of paths.</p>

<h2 class="relative group">The 4% rule and where it stops being safe
    <div id="the-4-rule-and-where-it-stops-being-safe" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-4-rule-and-where-it-stops-being-safe" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>You've probably heard the 4% rule: withdraw 4% of your starting balance each year, adjust for inflation, and a 30 year retirement should hold up. It comes from the Trinity study, which used US market data from 1926 to 1992.</p>
<p>That is fine for a traditional retirement at 65. But it starts breaking when you stretch it.</p>
<p>What the simulations consistently show:</p>
<table>
	<thead>
			<tr>
					<th>Horizon</th>
					<th>Realistic safe rate</th>
					<th>Depletion risk at 4%</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>30 years</td>
					<td>3.5% to 4.0%</td>
					<td>5% to 10%</td>
			</tr>
			<tr>
					<td>40 years</td>
					<td>3.0% to 3.5%</td>
					<td>15% to 25%</td>
			</tr>
			<tr>
					<td>50 years</td>
					<td>2.5% to 3.0%</td>
					<td>30% to 45%</td>
			</tr>
	</tbody>
</table>
<p>Those aren't guarantees. They are probability distributions. The point of running thousands of scenarios is to stop asking &quot;will it work&quot; and start asking &quot;in what fraction of futures does it work, and am I OK with the rest.&quot;</p>

<h2 class="relative group">Fat tails and the fix I had to make
    <div id="fat-tails-and-the-fix-i-had-to-make" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#fat-tails-and-the-fix-i-had-to-make" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Most Monte Carlo tools, including the first version of mine, generate returns from a Normal distribution. Bell curve. Smooth. Reassuring. The problem is that real equity markets have fat tails. Extreme events happen far more often than the bell curve predicts.</p>
<p>Just since 2000:</p>
<ul>
<li>2000 to 2002: dot-com bust, Nasdaq down 78%.</li>
<li>2008: global financial crisis, S&amp;P 500 down 57%.</li>
<li>2020: COVID crash, down 34% in three weeks.</li>
</ul>
<p>A Normal distribution says crashes that severe should be once-per-century events. We've had three this century, and we're not done. Not that long ago we had liberation day and now the Iran War.</p>
<p>There's a second, deeper problem with Normal returns: arithmetically, a Normal return r and the compounding step <code>value × (1 + r)</code> can drive a portfolio below zero. That is mathematically impossible (a stock can't be worth less than nothing) but a naive simulator will happily print it.</p>
<p>The fix is to model returns as log-normal, which is what Geometric Brownian Motion actually does. Instead of <code>value × (1 + r)</code>, you compound with <code>value × exp(r)</code> where r is a normal log-return. Log-normal can't go below zero by construction, naturally produces a heavier right tail, and matches the empirical distribution of monthly equity returns much better than Normal does.</p>
<p>My calculator now uses log-normal compounding for the default mode. The fat-tail toggle layers a Student's t-distribution (df=5) on top, which gives even heavier tails for stress testing. If your plan survives the fat-tail mode at a punishing withdrawal rate, your plan is genuinely robust. If it doesn't, you've found the edge of your plan before reality finds it for you.</p>

<h2 class="relative group">Picking a withdrawal strategy
    <div id="picking-a-withdrawal-strategy" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#picking-a-withdrawal-strategy" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Monte Carlo lets you stress test the spending side too.</p>
<p><strong>Constant dollar.</strong> You withdraw a fixed inflation-adjusted amount every year regardless of what the portfolio is doing. Simple. Predictable. You will be pulling the same dollar amount out of a declining portfolio during a crash, which is exactly when you shouldn't.</p>
<p><strong>Dynamic spending (Vanguard rule).</strong> You adjust withdrawals based on portfolio performance with floors and ceilings so spending doesn't swing wildly. That's what I'm using. More sustainable at aggressive withdrawal rates. The trade-off is that you have to be willing to cut spending in bad years.</p>
<p>The simulations are consistent: at the same nominal withdrawal rate, dynamic spending often cuts depletion risk roughly in half.</p>

<h2 class="relative group">What the numbers cannot tell you
    <div id="what-the-numbers-cannot-tell-you" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-the-numbers-cannot-tell-you" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Monte Carlo is powerful. It also has blind spots.</p>
<p><strong>Regime change.</strong> The simulation assumes future volatility looks like past volatility. What if we enter a Japan-style decade of low returns? The simulator doesn't know.</p>
<p><strong>Structural shifts.</strong> AI rewriting the labor market. Demographics. Climate. None of this is in the model.</p>
<p><strong>Personal factors.</strong> Health expenses, family obligations, a roof that needs replacing. The simulator doesn't know your life.</p>
<p><strong>Taxes.</strong> Most simulators work in nominal returns. Your actual spending power depends on your tax situation, which for a Dubai-based expat looks very different from someone in London or Paris.</p>
<p>Use Monte Carlo as one input. Don't treat the 50th percentile as a forecast. Treat the 5th percentile as a planning floor.</p>

<h2 class="relative group">Build in safety margins on top
    <div id="build-in-safety-margins-on-top" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#build-in-safety-margins-on-top" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Don't just trust the simulator's 5th percentile result.</p>
<ul>
<li>Regime change buffer: -20%. Persistent low returns are a real possibility.</li>
<li>Black swan buffer: -15%. Major crisis early in retirement.</li>
<li>Fee creep buffer: -5%. Costs tend to climb over decades.</li>
</ul>
<p>If the 5th percentile outcome in the simulator says you have $1.7M, your conservative planning target after those haircuts is closer to $1.0M. If you can live on that conservatively-haircut number, you are genuinely safe. Not &quot;statistically probably fine.&quot; Safe.</p>

<h2 class="relative group">Try it yourself
    <div id="try-it-yourself" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#try-it-yourself" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Theory is one thing. Running your actual numbers through the simulator is where it stops being abstract.</p>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow" style="background-color: #1e3a5f"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    
  </span>

  <span
    
      style="color: #ffffff"
    
    ><p><strong>Try the calculator</strong></p>
<p>I built a Monte Carlo simulator that runs 10,000 scenarios by default (configurable up to 100,000) across four professionally designed portfolios. Log-normal compounding by default, fat-tail mode for stress testing, constant dollar and dynamic spending strategies, fee drag included. Cholesky-correlated monthly shocks, annual rebalance, per-path Sharpe and Sortino. A parallel no-withdrawal portfolio runs against the same shocks so you can see, on the chart and in dollars, exactly what your retirement spending costs you in compound growth.</p>
<p><strong><a href="/calculators/monte-carlo-retirement-calculator/" >Use the Monte Carlo Calculator</a></strong></p>
</span>
</div>

<p>Test different withdrawal rates. Compare portfolios. Watch how fees compound over decades. Everything runs in your browser. No accounts, no tracking, no data leaving your machine.</p>

<h2 class="relative group">The bottom line
    <div id="the-bottom-line" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-bottom-line" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Simple retirement calculators sell false precision. They give you one number and pretend to know the future. Monte Carlo is honest about uncertainty. It hands you a distribution and lets you decide how much downside you're willing to plan for.</p>
<p>You might not love seeing a 15% chance of running out of money. You would love it less at 85.</p>
<p>Plan for the 5th percentile. Hope for the median. Stay flexible enough to adjust when reality surprises you, because it will.</p>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>Disclaimer:</strong> This post reflects my personal views and is for educational purposes only. It is not financial advice. Every situation is different. Always check your country's specific tax and investment rules before acting. See the full <a href="/disclaimer/" >Disclaimer</a> and <a href="/privacy/" >Privacy Policy</a> for the long version.</span>
</div>

]]></content:encoded><media:content url="https://libreleo.com/img/featured/monte-carlo-simulation-retirement-planning.webp" medium="image"/></item></channel></rss>