What a Monte Carlo probability is conditional on

A Monte Carlo run in Folio Lab ends with a sentence like there is a 63% chance this portfolio is worth more than ₹1 crore in ten years. That sentence is doing two very different jobs at once, and the difference between them is the entire subject of this post.

The first job is arithmetic, and it is exact. Out of the paths the engine generated, 63% of them ended above the target. Nothing about that is uncertain. Run it again with the same seed and the same inputs and you get the same number, to the last digit.

The second job is inference, and it is not exact at all. It is the leap from 63% of simulated paths to 63% chance, and every bit of the risk in that sentence lives in the leap.

The probability is a property of a model, not a measurement of the future. It is conditional on the return process, the estimation window, the rebalancing policy, the engine version and the seed. Change any one of them and it is a different number about a different question.

The estimator is the easy part

Let WH W_HWH​ be terminal wealth at horizon H HH and w ww the target. The engine draws N NN paths and reports the sample proportion:
p^=P^(WH≥w)=1 N∑i=1 N 1 ⁣{WH(i)≥w}\widehat{p} = \widehat{\mathrm{P}}(W_H \ge w) = \frac{1}{N} \sum_{i=1}^{N} \mathbf{1}\!\left\{ W_H^{(i)} \ge w \right\}p ​=P (WH​≥w)=N1​i=1∑N​1{WH(i)​≥w}
This is a binomial proportion, so its Monte Carlo standard error is closed-form:
se(p^)=p(1−p)N\mathrm{se}(\widehat{p}) = \sqrt{\frac{p(1-p)}{N}}se(p ​)=Np(1−p)​ ​
At N=10,000 N = 10{,}000N=10,000 and p≈0.5 p \approx 0.5p≈0.5, that is se≈0.005\mathrm{se} \approx 0.005se≈0.005: a 95% interval about one percentage point wide either side. Simulation noise, in other words, is already small enough to ignore relative to everything else on this page.1

That is worth sitting with. The part of the number we can quantify precisely is the part that barely matters. The part that matters cannot be quantified from inside the simulation at all.

Five things the number is conditional on

Every probability the engine reports is conditional on a specific, recorded configuration. These are not caveats bolted on afterwards; they are inputs, and the output is a function of them.

Condition | What it fixes | What moves if you change it
Return process | The joint law paths are drawn from | Tail mass, clustering, drawdown depth
Optimizer-time inputs | Weights, frozen at the parent run | The entire allocation being simulated
Effective configuration | Horizon, contributions, tax, inflation | The wealth accounting, not the returns
Engine version | Simulation semantics | Anything, by definition
Seed | The specific draw | The last digit, and nothing structural

The second row is the one people miss. A Monte Carlo run does not re-optimize. It takes the weights a completed optimization produced, freezes them, and asks what happens next. That is a deliberate design decision: it keeps the simulation a statement about this portfolio rather than a statement about the optimizer, and it is why the two live on separate queues with separate workers.

Where the estimate ends and the model begins

The default return process is a stationary block bootstrap. It resamples contiguous blocks of historical returns, which preserves autocorrelation and cross-sectional structure inside a block and destroys it across block boundaries.

# Blocks are contiguous in time, so a 2008 October block carries
# its own correlation structure with it. What the bootstrap cannot
# do is invent a regime the sample never contained.
for path in range(n_paths):
t = 0
while t < horizon:
start = rng.integers(0, n_obs - block_len)
block = returns[start : start + block_len]
emit(block[: horizon - t])
t += block_len

The honest description of what that buys is narrow: the simulated distribution is the historical distribution, rearranged. If the sample window contains no episode like the one that actually arrives, no number of paths will produce one. Increasing N NN samples the same empirical law more finely; it does not widen it.

A bootstrap cannot extrapolate beyond its sample. Neither can a fitted parametric process, though it fails less visibly, by putting smooth tails where the data had none. Both are model risk, and neither is visible in the standard error.

Why none of this is called a forecast

Folio Lab does not describe these probabilities as calibrated forecasts, and the reason is specific rather than rhetorical: no generator here has been validated out of sample.

Calibration is a testable property. A generator is calibrated at horizon H HH if, across many rolling origins, the realised outcome falls below the predicted q qq-th quantile about q qq of the time. Testing it needs a rolling-origin harness that re-fits at each origin, holds the next H HH days out, and scores coverage against published thresholds decided before the results are seen.

That harness exists and has been run. It has not passed. The best-performing arm calibrated to roughly 3.2% against its threshold and was still rejected, and at the annual horizon the gate is not reachable by any generator we have tried, which is a statement about the study design as much as about the models.

So the language stays where the evidence is. The engine reports what its model implies. It does not claim the model is right, and it will not claim one generator beats another until something measured says so.

What would change the claim

Three things, in order of how much they would change:

A generator passing rolling-origin validation at a stated horizon. Then, and only then, the word "calibrated" becomes available, scoped to that horizon and that generator.

A validation gate whose power is understood. A test nothing can pass is not evidence of anything except the test.

Out-of-sample evidence that one rebalancing policy beats another. Currently unclaimed, deliberately.

Until then the right way to read a Folio Lab probability is as a precise answer to a question you fully specified: given this return process, these frozen weights, this configuration and this engine, what fraction of paths clears the target? That question has an exact answer, and the platform gives it to you along with everything it was conditional on.

The question you actually wanted answered is harder, and nobody has it.

Footnotes

Which is why adding paths is the cheapest and least useful lever available. Going from 10,000 to 100,000 paths shrinks the interval by a factor of 10\sqrt{10}10 ​ and changes nothing about whether the model was right. ↩

Read this on foliolab.ai