Features

Forward Monte Carlo Simulation

mc-v1.21.0

A long-only forward-planning model for NSE/BSE stock requests under an Indian direct-equity model. A versioned instrument master now separates ordinary listed shares from ETFs, REITs and InvITs, and admission refuses the latter with the modelling assumption each one breaks. The engine preserves and verifies its calibration inputs, but its output remains scenario analysis rather than a forecast; read the assumptions and limitations before leaning on the numbers.

Project your optimized portfolio forward through thousands of simulated market histories - not one average path, but the whole distribution: where 90% of outcomes land, how likely you are to hit a goal, and what a 2008-style stretch would do along the way.

Looking for the full methodology? This page is the product tour. For the engine internals - every return-generating process with its mathematics and admission checks, the drift layer, the tax and inflation overlays, and the exact definition of each reported statistic - see Monte Carlo Engine Methodology.

What it does

A completed optimization gives you one set of target weights and metrics computed on the single history that actually happened. The Monte Carlo engine asks the forward question: if markets behave like they have historically - including crashes, recoveries, and long sideways stretches - where does this portfolio end up over the next 1 to 40 years?

Each simulation draws up to 100,000 paths of daily portfolio returns, applies your cash-flow plan (SIP contributions, systematic withdrawals), estimates Indian capital-gains tax on modeled sales, applies proportional trade costs, deflates by Indian CPI, and reports the resulting wealth distribution:

  • Wealth fan chart - pointwise P5/P25/P50/P75/P95 percentiles at each month, nominal and inflation-adjusted, with a linear/log axis toggle, the goal and wealth-floor lines on their matching basis, and the engine's deterministic invested-capital schedule overlaid on the nominal view. A band edge is not one continuous path or a model-confidence interval.
  • Goal probability - P(terminal wealth ≥ your goal) on one of four explicit bases: nominal pre-tax, nominal illustrative liquidation-net, real pre-tax, or real illustrative liquidation-net. Liquidation-net deducts both modeled tax and the terminal trade cost. The engine also ships a pathwise per-month goal-attainment series with per-point Wilson intervals, rendered as the “goal probability over time” chart. When the goal basis is liquidation-net, that monthly curve monitors the pre-tax counterpart and says so, because monthly wealth is tracked pre-tax.
  • Terminal wealth distribution - a nominal pre-tax histogram plus an exceedance curve, P(ending at or above any wealth level), drawn from the engine's exact P0–P100 terminal quantile grid. Separate tiles report real and illustrative liquidation-net terminal percentiles; they are not silently overlaid on an incompatible axis.
  • Path risk - the distribution of maximum drawdown and maximum observed consecutive underwater spell computed daily insideevery path. Unrecovered spells are right-censored at the horizon, with P(unrecovered at horizon) reported beside the duration distribution. Forward 1-year VaR and CVaR are shown at 95% / 99%. VaR and CVaR have separate bootstrap confidence intervals because they are different estimators. The engine also ships dense P0–P100 quantile grids for max drawdown, longest underwater spell, and the 1-year NAV return, plus P(negative 1-year NAV return) with its Wilson interval.
  • Drawdown state by month - for each month-end, the share of paths currently underwater and the share currently at least 5%, 10%, 20%, or 30% below their running peak. This is the state right now at that month, not “ever breached by then”, so it shows how drawdown risk builds and decays over the horizon.
  • Depletion, wealth floor, and withdrawal health - terminal-zero probability is reported separately from ever-depletion, unmet withdrawals, cumulative shortfall, first shortfall year, and the chance of touching a user-defined wealth floor at the start, after a cash-flow or rebalance event, or at month-end. The floor is not tested on every intra-month market day.
  • Crisis scenarios - the same portfolio re-simulated under stressed conditions, side by side with the base run.

Goal, terminal-depletion, wealth-floor, and withdrawal-health probabilities include a common self-describing 90% Wilson interval for finite simulated paths. Those intervals are conditional on the recorded model and frozen history; they are not confidence bands for model correctness. The selected terminal basis also carries separate 90% path-bootstrap intervals for its P5, P50, and P95 wealth quantiles, shown with the terminal tiles.

Exact calibration inputs and provenance

For new optimization runs, the parent pipeline stores the exact aligned return panel and CPI series used for downstream simulation as an immutable, content-addressed artifact. Monte Carlo loads that parent artifact rather than fetching today's market history again, verifies its SHA-256 digest before use, and fails closed if the exact artifact is missing or corrupt. This keeps optimized weights and simulated history on the same data vintage.

Every result exposes an input_snapshot provenance block: source, immutable_version, sha256, path, status, rows, start, end, and cpi_source. The result badge shows the calibration end date, reuse status, and a short digest; the full fields remain available for audit. Older parents that predate immutable snapshots use a clearly labelled legacy reuse or live-refetch path and are never presented as exact parent-frozen inputs.

Results also disclose the request-schema scope gate and whether instrument subtype verification was performed, CPI age/status both at calibration and immutable replay (including a stale-now warning), return-support clipping, and requested versus effective execution batch limits. An NSE/BSE stock request is accepted under the direct-equity model, but the current request-schema gate does not independently prove that every ticker is a common share rather than another exchange-listed instrument.

How paths are generated

The default return process is a stationary block bootstrap (Politis–Romano) over your assets' full joint daily-return history. Whole cross-sectional rows are resampled in random-length blocks. Expected block length is selected automatically using the Politis–White rule, with only as a disclosed fallback. This retains the observed marginal shocks, same-day cross-asset dependence, and local ordering conditional on the selected block length. It does not by itself establish that volatility clustering has been reproduced: the Politis–White selector targets long-run-variance inference on raw returns, and raw daily returns can show little linear autocorrelation while absolute and squared returns remain dependent. Treat the selected length as a documented heuristic until block-length sensitivity has been tested. The default drift-uncertainty overlay adds a Gaussian mean perturbation, so adjusted simulated returns are not claimed to be untouched historical observations.

Six alternative processes are selectable. None of them has yet been shown to forecast better than the others, so the product does not publish a "best" model: the spread across processes is model risk you should see, not noise to be hidden. Running two and comparing the range is more informative than trusting one. Each process has its own methodology page with the full mathematics, calibration and admission rules; the engine methodology overview compares all seven side by side.

Filtered historical simulation (EWMA and GJR-GARCH)

The block bootstrap treats every historical day as equally representative of tomorrow. Filtered historical simulation instead conditions on today's volatility: it estimates each holding's conditional volatility, divides history through by it to leave standardized shocks, resamples those shocks as complete cross-asset rows, and then rebuilds volatility forward. Whole rows are resampled, so a day when everything fell together stays a day when everything falls together.

fhs_ewma uses an exponentially weighted volatility estimate. fhs_gjr_garch uses a GJR-GARCH(1,1) model, which lets losses raise future volatility more than equivalent gains – the leverage effect. Every GJR fit must pass convergence, parameter-validity, persistence, unconditional-variance and residual diagnostics; any holding whose fit fails falls back to EWMA, and the result reports which holdings fell back and why. EWMA needs roughly 3 years of shared history and GJR-GARCH roughly 5.

Both simulate in log returns, so gross returns are positive by construction and no clipping correction is needed. Because they reconstruct returns rather than replaying dated rows, they have no calendar window: inflation falls back to the disclosed constant model, and the 2008 / COVID window stresses are unavailable.

Benchmarks and comparison processes

gbm_lognormal is daily lognormal geometric Brownian motion with a Ledoit–Wolf shrinkage covariance. It exists as an analytical sanity check, deliberately not as a realism upgrade: it has no volatility clustering and no fat tails. At daily resolution, continuous-time GBM is exactly an IID Gaussian log-return model, which is why the engine is not built around it.

The fat-tailed Student- and Gaussian processes remain available for comparison. Both emit unbounded simple returns and depend on the disclosed lower-gross-return clip, so they are comparison processes rather than planning models.

A fourth family upgrades the bootstrap with regime awareness:

Regime-switching bootstrap

Real market regimes can persist for months to years. The regime process fits a 2–3 state Markov-switching model (Hamilton, 1989) to the portfolio's full daily-return calibration sample, labels each historical day by filtered state probability, then lets every simulated path carry its own regime chain: high-volatility days are drawn from the high-volatility state and lower-volatility days from their corresponding state, with realistic persistence between them.

Paths start from today's filtered regime probabilities by default. Because model parameters are estimated on the full frozen calibration window, this is not a live out-of-sample regime forecast. The bear_regime_start scenario forces every path to open in the high-volatility state - the canonical stress test for withdrawal plans, where sequence-of-returns risk does the damage.

Deterministic loss and market-mechanics stresses

Four process-independent counterfactuals cover risks that ordinary return resampling cannot represent cleanly: a permanent total loss and delisting of the largest holding, a three-month liquidity freeze with 500 bps stressed execution, five consecutive 10% lower-price-band sessions, and a 63-session cross-asset correlation spike to 0.90.

Each runs on the same sampled paths as the base so its rupee effect is paired path by path. The result records the exact target, timing, compounding, execution rule, and post-delisting reinvestment rule. No event probability is assigned: these are conditional stress tests, not likelihood or forecast claims.

The drift assumption (read this before trusting any fan chart)

Every resampling simulator centers its future on the historical average return. With roughly 23 years of usable NSE history at ~18% annualized volatility, the standard error of that mean is about

Compounded over 15–40 years, that estimation error dwarfs everything else in the simulation - it does not shrink no matter how many paths you run. FolioLab handles this three ways: per-path drift perturbation is on by default (the fan reflects parameter uncertainty, not just path noise), a drift knob lets you keep the historical mean, shrink it toward an explicit anchor, or use an explicit annual arithmetic-mean assumption. The annual input is divided by 252 to target a daily arithmetic mean; it is not a CAGR target. Results report the historical arithmetic annual mean and a separately calculated daily-rebalanced constant-weight proxy CAGR under distinct names. That proxy is descriptive calibration context, not the CAGR of a buy-and-hold portfolio. Every goal probability is labeled with the assumption behind it. Long-horizon percentiles are best read as ranges under stated assumptions, never as forecasts.

Inflation, tax, and cash flows

  • Inflation - real-wealth outputs deflate by Indian CPI aligned to the same historical days the return blocks were sampled from. The aligned CPI series and its source are frozen in the parent input artifact alongside returns. You can override with a flat annual rate; the result records whether inflation came from the snapshot, an override, or a disclosed fallback.
  • Capital-gains tax - STCG/LTCG (post-July-2024 rates by default, both editable) are realized on the sold fraction at every rebalance with the annual LTCG exemption tracked, plus a terminal-liquidation estimate. The separate illustrative liquidation-net distribution then deducts both that tax estimate and the configured terminal trade cost. This is explicitly labelled fifo_monthly_age_cohorts_v1: it approximates FIFO and holding age with monthly cohorts rather than exact broker tax lots. It remains illustrative and does not model every investor's surcharge/cess, transaction taxes and charges, loss netting, carry-forward, dividends, or off-platform gains. Results record the tax profile, rule version, effective date, rates, exemption, and holding-period model when available; legacy runs explicitly disclose when those markers were not recorded.
  • SIP / SWP - monthly contributions with annual step-up, and monthly withdrawals starting in any year. Withdrawals can stay fixed nominal, rise at a fixed annual rate, or follow simulated CPI. An SWP amount is requested cash net of the illustrative capital-gains-tax estimate and modeled withdrawal trade cost: the engine grosses up the redemption for both. If tax is disabled, a non-zero trade cost still reduces delivered spendable cash. If wealth cannot fund the requested cash plus modeled tax and cost, the unmet net cash is recorded as a withdrawal shortfall rather than silently counted as funded. Flows are tracked against a unitized NAV, so performance metrics never mistake a deposit for a return.
  • Rebalancing policy - a rebalance can return to the weights the run started with, or re-run the optimizer on what that particular simulated path has seen so far. Re-optimizing uses an expanding window: the real price history plus that path's own simulated returns to date, with no look-ahead, so it mirrors what you would actually have known at that point. Every trade it creates pays the same modeled capital-gains tax and trading cost as any other rebalance, so the mode is not free. It is available for optimizers whose allocation rule can be recomputed per path (Hierarchical Risk Parity, Inverse Volatility, Equal Weight); solver-based methods cannot be re-fitted millions of times and are refused rather than quietly run with frozen weights. You can optionally simulate the frozen-weight version over the same futures, which doubles the work and lets the result attribute the difference to the rule rather than to luck. Re-optimizing is a different question, not a validated improvement: no claim that it produces better outcomes has passed the validation harness.
  • Proportional trading cost - the configured basis-point cost applies to every modeled trade: initial investment, contributions, withdrawals, both rebalance legs, and terminal liquidation. It is still a simplified cost, not a security-level spread/impact or complete Indian charges model.

Goal and depletion definitions

A goal is evaluated against exactly one terminal series: nominal pre-tax, nominal illustrative liquidation-net, real pre-tax, or real illustrative liquidation-net. “Liquidation-net” means wealth after illustrative terminal tax and terminal trade cost; “real” additionally removes simulated inflation. With tax disabled but a non-zero trade cost, liquidation-net wealth still differs from pre-tax wealth. The goal basis is stored with the result. A liquidation-net goal is not drawn on the pre-tax fan chart because those axes are not comparable.

P(terminal depletion) means the fraction of paths whose wealth is zero at the horizon. It is terminal-only. Withdrawal plans additionally report P(ever depleted), the chance of any shortfall, the chance every scheduled withdrawal was funded, cumulative-shortfall percentiles, and first-shortfall-year percentiles. A path may deplete and later be revived by a contribution, so these measures deliberately differ.

For accumulation-only plans, exact-zero terminal depletion is hidden: under the gross-return floor it is structurally near-impossible and is not a useful solvency KPI. Configure a nominal or real wealth floor to measure planning-relevant low-wealth risk instead.

P(wealth-floor breach) is different again: it is the chance that the selected wealth basis touches or falls below the configured floor at the start (after the initial modeled trade cost), immediately after contribution, withdrawal, or rebalance events, or at month-end. It is not a terminal-only test and does not observe every intra-month daily market move.

VaR, CVaR, and uncertainty

One-year VaR is stored as a signed lower-tail return (the 5th percentile at 95%, or 1st percentile at 99%); CVaR is the signed average return beyond that cutoff. Negative values are losses. The engine computes them independently and returns separate interval blocks: var_1y_ci["95" | "99"] and cvar_1y_ci["95" | "99"]. Each CVaR bootstrap replicate recomputes both its VaR cutoff and tail mean rather than holding the cutoff fixed. The shared tail-risk CI metadata records the confidence level, bootstrap method, resampling unit, signed-tail semantics, and model/frozen-history conditional scope when supplied by the engine.

Tail estimates need enough extreme paths. The result includes the expected and observed tail counts plus a reliability warning when fewer than 25 tail observations are expected. More paths reduce Monte Carlo noise, but do not remove uncertainty in the historical return process itself. The displayed intervals cover conditional simulation/resampling uncertainty only; they do not cover model, drift, regime, tax, or inflation misspecification.

Crisis scenarios

Scenarios re-run the full simulation with stressed sampling and show up side by side with the base run:

  • 2008 repeat / COVID repeat - at every bootstrap block restart, starts inside the named historical window receive a 5× sampling multiplier. These are repeated crisis-frequency stresses, not a one-time chronological replay.
  • Single-name blowup - one −40% one-day shock per simulated path-year hits an eligible concentrated holding (>5% weight), testing idiosyncratic risk the index never shows.
  • Bear regime start - with the regime process, every path opens in the high-volatility state.

Scenario output is conditional stress performance, not an estimate of how likely the named event is. Common random numbers pair scenario and base paths so deltas isolate the stress more cleanly. A scenario your data cannot support (e.g. a COVID window when the frozen equity history starts after 2020) is reported as skipped with the reason - never silently dropped.

Running a simulation

Three ways, all explicit - FolioLab never runs a simulation you didn't ask for:

  1. Monte Carlo Lab - the dedicated wizard on the Monte Carlo page. Pick Indian stocks (NSE / BSE), choose the optimization method whose weights get simulated, configure the simulation, and run - the optimization and the simulation are queued as one pipeline. You can also start from any past succeeded run and skip re-optimizing.
  2. On any past run - open a succeeded run's results page and hit “Run Monte Carlo”. If the run has multiple eligible methods, you must explicitly select which method's long-only weights to simulate. A single eligible method is inserted explicitly in the request. The interface does not present the highest in-sample Sharpe as a statistically preferred default.
  3. Through an AI assistant - connect Claude or any MCP client to the FolioLab MCP server and call submit_monte_carlo on a succeeded run. The paired get_monte_carlo_result tool returns the summary with ready-made interpretation sentences and the explicit list of modeling assumptions behind them, so the assistant can never quote a probability without its conditions.

Results are reproducible when the seed, configuration, engine version, and immutable input digest match. The seed alone is not a complete reproducibility claim.

The Lab's saved-run comparison puts two to four completed simulations beside each other. It labels a comparison controlled only when assets, initial weights, optimizer, calibration input, non-policy inputs, seed, and engine all match. A different seed becomes an aligned-input comparison; any other configuration drift is shown as descriptive. Separate saved artifacts are never presented as a paired out-of-sample performance test.

Plan availability

  • Free - not included.
  • Pro - 20 simulations / month (separate from your optimization and backtest quotas), up to 50,000 paths per run, on six of the seven return processes.
  • Enterprise - unlimited simulations, up to 100,000 paths per run, every return process.

Path count is chosen from a dropdown of presets - 1,000 / 5,000 / 10,000 / 25,000 / 50,000 / 100,000 - and every preset up to your plan cap is selectable; presets above the cap (or above the workload safety limit for your horizon, scenario, and asset count) are shown disabled with the reason. More paths reduce sampling noise in the reported probabilities; they do not reduce model uncertainty.

Honest limitations

  • History can only replay what happened: a future crisis worse than anything in your sample is, by construction, not in the distribution.
  • Beyond ~15 years the drift assumption dominates every other choice; treat 30-year percentiles as scenario analysis, not prediction.
  • The tax overlay is an approximate planning estimate, not a tax return. Monthly FIFO age cohorts are not exact broker lots, and the engine omits several investor-specific taxes, costs, netting, and carry-forward effects disclosed with each result.
  • Every probability is conditional on the selected model, frozen history, drift, cash-flow, inflation, tax, and cost assumptions. The rolling-origin validation harness has been built and run, and its published verdict is negative: the accepted generator set is empty, so no probability-calibration claim is made.
  • Because no generator has been accepted, the cross-generator disagreement range is withheld rather than estimated from an unqualified cohort.

Go deeper

Also relevant: Resampled MVO uses the same bootstrap machinery for weight estimation, and Rolling Walk-Forward Backtest answers the backward-looking version of this question.

Not investment advice. Past performance is not indicative of future results.