Monte Carlo · Return processes
Filtered Historical Simulation (variance-targeted GJR-GARCH)
fhs_gjr_garch_vt
The default. It is the GJR-GARCH filter with one change to how the model is fitted: the long-run variance is pinned to the estimation window rather than estimated jointly with everything else. That change is small to describe and large in effect.
What variance targeting does
A GJR-GARCH(1,1) fit has four free parameters. One of them, , sets the level the process reverts to. Estimating it from the same likelihood as the dynamics makes the long-run level sensitive to a handful of extreme days, because those days carry most of the likelihood.
Variance targeting removes it from the optimisation and solves for it instead, so the model's unconditional variance equals the sample variance of the estimation window by construction:
Only , and are then estimated, by Gaussian quasi-maximum likelihood with a multi-start SLSQP search. The level is an identity rather than a fitted quantity, and the shape of the clustering is what the likelihood is spent on.
Why it is the default
Rolling-origin testing compares simulated dispersion against what actually happened after each origin date. Across 17 candidate generators, this one came closest to realised volatility at every horizon measured:
| Horizon | This process | Previous default |
|---|---|---|
| 1 day | +1.8% | +26.8% |
| 1 month | +2.3% | +45.9% |
| 3 months | -2.1% | +48.4% |
| 1 year | -3.0% | +55.8% |
Positive means the simulation produced more volatility than actually occurred. The previous default was over-dispersed at every horizon, which makes a plan look riskier than the history supports.
What this does not mean
Being the best-calibrated candidate is not the same as being validated. Under the published acceptance policy this process is insufficient_evidence, not accepted, and the accepted model set is empty. At horizons beyond one day the panel does not carry enough independent observations to qualify any model, which is a property of the available calendar rather than a verdict on this one.
Every probability this engine reports stays conditional on the selected model, the frozen optimizer-time inputs, the effective configuration, the engine version and the seed. None of them is a calibrated forecast, and a lower volatility error does not make one.
History requirement and the disclosed fallback
A GJR fit needs about 1,260 jointly aligned trading days, roughly five years, across every holding. A portfolio containing a recent listing or a young fund will not have that.
When you do not name a process, a portfolio that cannot meet the requirement is not rejected. The run falls back to the stationary block bootstrap and says so: the result carries a generator_fallback block naming the substitution and the sessions available against the sessions required, and the PDF names it in the return-model label. The substitute is a retired process whose dispersion is materially wider than realised, so treat those numbers accordingly.
If you do name this process explicitly and the history is short, the request is rejected with mc_insufficient_history_for_process rather than silently substituted. Asking for a specific model and receiving a different one is worse than an error.