| name | add-mc-variance-reduction |
| description | How to add a Monte Carlo variance-reduction technique to stochastic-rs-quant. Covers antithetic, control-variate, stratified, importance, quasi-MC (Halton/Sobol), and MLMC. Returns McEstimate<T> with 95% CI. |
MC variance reduction โ stochastic-rs-quant
Variance-reduction techniques in stochastic-rs-quant follow a uniform
contract: an MC estimator returns an McEstimate<T> struct carrying
(point_estimate, std_error, n_paths) from which the caller can
compute a 95 % confidence interval. New estimators ship with a
reference test asserting the variance is genuinely reduced versus
plain MC on a synthetic problem.
This SKILL covers the six classical techniques:
| Technique | Complexity | Use case |
|---|
| Antithetic | trivial | symmetric payoffs (calls / puts under GBM) |
| Control variate | low | known-mean control with high correlation |
| Stratified | medium | low-dim integrand; deterministic strata |
| Importance sampling | medium | rare events (deep OTM, default probability) |
| Quasi-MC (Halton/Sobol) | medium | low-discrepancy sequences; smooth integrands |
| MLMC (Multi-Level) | high | path-dependent + grid-discretisation error |
References: Glasserman 2003 (chapters 4 & 5 for AT/CV; 6 for IS; 7 for
QMC); Giles 2015 for MLMC.
1. The McEstimate<T> contract
#[derive(Debug, Clone)]
pub struct McEstimate<T: FloatExt> {
pub mean: T,
pub stderr: T,
pub n_paths: usize,
}
impl<T: FloatExt> McEstimate<T> {
pub fn ci_95(&self) -> (T, T) {
let z = T::from_f64_fast(1.96);
(self.mean - z * self.stderr, self.mean + z * self.stderr)
}
pub fn vr_factor(&self, baseline_stderr: T) -> T {
baseline_stderr / self.stderr
}
}
Estimators always return McEstimate<T>, never raw f64. This keeps
the variance information attached to the point estimate; callers
asking for a CI never have to chase down the stderr separately.
2. Antithetic โ the cheapest win
For an SDE driven by Z ~ N(0, 1):
pub fn price_antithetic<F>(payoff: F, n_pairs: usize, seed: u64) -> McEstimate<f64>
where F: Fn(f64) -> f64 + Sync {
let mut rng = StdRng::seed_from_u64(seed);
let mut samples = Vec::with_capacity(n_pairs);
for _ in 0..n_pairs {
let z: f64 = StandardNormal.sample(&mut rng);
let plus = payoff( z);
let minus = payoff(-z);
samples.push((plus + minus) * 0.5);
}
let n = samples.len();
let mean = samples.iter().sum::<f64>() / n as f64;
let var: f64 = samples.iter().map(|s| (s - mean).powi(2)).sum::<f64>() / (n - 1) as f64;
McEstimate { mean, stderr: (var / n as f64).sqrt(), n_paths: 2 * n }
}
Antithetic works only when the payoff is monotonic in z. For
strangles / digitals, antithetic can increase variance โ always
include the variance-reduction reference test (ยง7).
3. Control variate โ when you know E[Y]
Pick a control Y(path) whose mean E[Y] is known analytically and
which has high correlation with the payoff f(path). Then:
estimator(f) = sample_mean(f) - ฮฒ * (sample_mean(Y) - E[Y])
where ฮฒ = Cov(f, Y) / Var(Y)
The optimal ฮฒ minimises variance; estimate it from a pilot run of
n_pilot paths, then use it on the production n paths.
For European options under GBM, the natural control is the GBM
terminal price itself: Y = S_T - K * e^{-rT} has known mean = 0 if
correlation is high.
4. Quasi-MC โ Halton / Sobol
Replace StandardNormal::sample(rng) with deterministic
low-discrepancy sequences:
use sobol_rs::Sobol;
pub fn price_qmc(...) -> McEstimate<f64> {
let mut sobol = Sobol::new(d);
let mut samples = Vec::with_capacity(n);
for _ in 0..n {
let u: Vec<f64> = sobol.next().unwrap();
let z: Vec<f64> = u.iter().map(|&p| inverse_normal_cdf(p)).collect();
}
}
QMC integration error scales like (log N)^d / N rather than
N^{-1/2}, so for smooth integrands and small d the convergence is
much faster. Only the dimension d is set by the path discretisation
โ a 100-step Euler scheme is d = 100, which Sobol handles up to d โ
1000 reasonably well.
5. MLMC โ multilevel for path-dependent payoffs
For path-dependent payoffs (Asian, lookback) where the discretisation
introduces O(h^ฮฑ) bias, MLMC combines coarse + fine levels:
E[P_โ] โ E[P_0] + ฮฃ_l E[P_l - P_{l-1}]
with P_l evaluated on 2^l time steps. The number of paths
decreases geometrically with level. See mc/mlmc.rs for the workspace
implementation and Giles (2015) for the algorithmic details.
6. Common Random Numbers (CRN)
For Greeks via finite differences (delta = (P(S+h) - P(S-h)) / 2h),
share the same random draws between the bumped and base pricers.
This is the foundation of the single-pass greeks() MC override (see
greeks-pattern SKILL).
let z = sample_terminal_normals(seed, n_paths, n_steps);
let p_base = price_with_normals(s, z.view());
let p_up = price_with_normals(s + h, z.view());
let delta = (p_up - p_base) / h;
Without CRN, the finite-difference estimator's variance is the sum
of two independent sample variances; with CRN, it reduces to the
variance of the difference, often by a factor of 100ร.
7. Mandatory test: variance-reduction factor
#[test]
fn antithetic_reduces_variance() {
let plain = price_plain (..., seed=42, n=10_000);
let antithetic = price_antithetic(..., seed=42, n=5_000);
let factor = antithetic.vr_factor(plain.stderr);
assert!(
factor > 1.5,
"antithetic should reduce stderr by โฅ1.5ร, got {factor}"
);
}
Pin both seeds so the test is deterministic. The threshold (1.5ร here)
should be set conservatively but high enough that the test fails if
the technique was wired backward.
8. Anti-patterns
- Do not return a raw
f64 mean without a stderr. Always
McEstimate<T>.
- Do not apply antithetic to discontinuous payoffs (digital
options, indicator functions). Variance can go up.
- Do not estimate the optimal control-variate
ฮฒ on the
production paths and re-use the same paths for the estimator.
Pilot + production must be independent draws.
- Do not mix MC and QMC randomness in the same estimator without
a randomised-QMC scheme. Plain Sobol + plain
StandardNormal::sample
produces neither MC convergence nor QMC convergence.
- Do not skip the variance-reduction test (ยง7). It's the only
check that the technique is wired the right way around.
9. Reference impls
mc/antithetic.rs โ antithetic for European calls under GBM /
Heston.
mc/control_variate.rs โ control variate for Asian options.
mc/importance_sampling.rs โ Esscher tilt for OTM digital options.
mc/lsm.rs โ Longstaff-Schwartz for American options (uses CRN
internally).
mc/mlmc.rs โ multilevel for path-dependent payoffs.
Related SKILLs
greeks-pattern โ uses CRN for the single-pass greeks() override.
add-diffusion-process โ produces the underlying paths; seeded
constructors are mandatory for CRN.
bench-writing โ variance-reduction factor is a natural benchmark
metric.