Back to Writing The One-Line Change That Beats Brute Force: Antithetic Variates in Monte Carlo Pricing

The One-Line Change That Beats Brute Force: Antithetic Variates in Monte Carlo Pricing

There's a quiet cruelty built into Monte Carlo simulation. The error of a Monte Carlo estimate shrinks like , where is the number of simulated paths. Read that again slowly, because the consequence is harsh: to make your answer twice as precise, you don't run twice as many simulations — you run four times as many. Want one more decimal place? That's a hundredfold increase in compute.

So the obvious lever — just throw more paths at it — gets expensive fast. The interesting question is whether there's a smarter lever. A change you could make to the engine itself that buys accuracy without buying compute.

There is. And the one I want to show you is so simple it's almost insulting. It's a single sign flip. No new math libraries, no GPUs, no clever stratification scheme. Just one idea — and on the right problem it delivers the equivalent of nearly five times the paths, for free.

This post builds up to that idea deliberately. We'll price some real options, establish an honest baseline, feel the pain of the tax, and only then reveal the hero. Let's go.

Contents

The problem we'll price

To make this concrete, we'll price four contracts under the standard Black-Scholes model. Two are familiar:

  • a European call — payoff
  • a European put — payoff

And two are deliberately nasty:

  • a binary (cash-or-nothing) call — pays $1 if , otherwise nothing
  • a binary put — pays $1 if , otherwise nothing

The binaries matter for our story because their payoff is a cliff: it jumps from 0 to 1 the instant the stock crosses the strike. That discontinuity makes them notoriously twitchy to estimate — which, as you'll see, is exactly where our hero shines brightest.

Everything happens at an at-the-money baseline: , maturity year, volatility , and a risk-free rate .

We have an unfair advantage here: Black-Scholes gives us exact closed-form prices, so we always know the right answer and can measure precisely how far Monte Carlo strays.

import numpy as np
from scipy.stats import norm

def bs_price(S0, E, T, sigma, r, kind):
    d1 = (np.log(S0 / E) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    df = np.exp(-r * T)
    if kind == 'call':        return S0 * norm.cdf(d1) - E * df * norm.cdf(d2)
    if kind == 'put':         return E * df * norm.cdf(-d2) - S0 * norm.cdf(-d1)
    if kind == 'binary_call': return df * norm.cdf(d2)
    if kind == 'binary_put':  return df * norm.cdf(-d2)

These four numbers — the call at 10.4506, the put at 5.5735, the binary call at 0.5323, the binary put at 0.4189 — are our ground truth.

The honest baseline: plain Monte Carlo

The recipe is the textbook one. Under the risk-neutral measure the stock follows geometric Brownian motion, and we can jump straight to the terminal price with the exact solution:

Draw a pile of standard-normal shocks , turn each into a terminal price, evaluate the payoff, average, and discount. That's the whole engine.

def payoff(ST, E, kind):
    if kind == 'call':          return np.maximum(ST - E, 0.0)
    elif kind == 'put':         return np.maximum(E - ST, 0.0)
    elif kind == 'binary_call': return (ST > E).astype(float)
    elif kind == 'binary_put':  return (ST <= E).astype(float)

def mc_price(S0, E, T, sigma, r, kind, phi):
    ST = S0 * np.exp((r - 0.5 * sigma**2) * T + sigma * np.sqrt(T) * phi)
    pf = payoff(ST, E, kind)

    disc = np.exp(-r * T) * pf
    price   = disc.mean()
    std_err = disc.std(ddof=1) / np.sqrt(len(disc))   # the number we care about
    return price, std_err

rng = np.random.default_rng(42)
phi = rng.standard_normal(100_000)

Run it with 100,000 paths and it works — the closed-form price lands comfortably inside every 95% confidence interval. A correct implementation should clear that bar, and this one does.

But "correct" is not the same as "efficient." Look at the standard errors and a pattern jumps out: the binary estimates are jittery in a way the vanilla European options aren't. That cliff in the payoff means a tiny wiggle in the simulated terminal density flips whole batches of paths from 0 to 1. The European payoffs are smooth and forgiving; the digitals are not.

And the only tool we've reached for so far is the blunt one — more paths. Here's what that tool actually costs you:

Every time you want to halve that error, you quadruple the work. We need a different lever.

A smarter lever: variance reduction

Here's the reframe. The standard error of a Monte Carlo estimate is

We've been attacking the denominator by cranking up. But there's a numerator. If we could shrink the variance of the payoffs themselves, , we'd get a tighter answer at the same path count. No extra compute. That family of tricks is called variance reduction, and it's where the real leverage lives.

Which brings us, finally, to the hero.

The reveal: antithetic variates

The idea is this. The normal distribution is perfectly symmetric: and are equally likely. So for every random shock you draw to build a price path, draw its mirror image and build a second path from that. Both paths are legitimate, unbiased risk-neutral simulations — you've changed nothing about the statistics. You've just made your sample come in mirrored pairs.

phi = rng.standard_normal(50_000)        # half as many draws...
phi_pairs = np.concatenate([phi, -phi])  # ...but mirrored into a full sample

Why on earth would that help? Because of how the two paths in a pair move. When pushes the stock up, pushes it down by the mirror amount. The payoffs of the two paths are therefore negatively correlated — one tends to be high exactly when its partner is low. And when you average two negatively correlated quantities, the swings cancel.

The payoffs of mirrored paths are negatively correlated — one tends to be high exactly when its partner is low. Average them, and the swings cancel.

The math is short enough to write out. Let and be the two payoffs in a pair. The variance of their average is

Plain Monte Carlo treating those same two draws as independent samples would give variance . So the entire difference is that term — and because the payoffs of calls, puts, and digitals are all monotone in , that covariance is strictly negative. The antithetic estimator is provably tighter. The more strongly the pair anti-correlates, the bigger the win.

That's it. That's the hero. A sign flip and a pairwise average.

The trap that almost hid the win

There's one detail that matters enormously, and it's the thing that nearly buried this result the first time I implemented it.

In code, the fix is one line — collapse the pairs before measuring:

def mc_price_antithetic(S0, E, T, sigma, r, kind, phi):
    phi_all = np.concatenate([phi, -phi])
    ST = S0 * np.exp((r - 0.5 * sigma**2) * T + sigma * np.sqrt(T) * phi_all)
    pf = payoff(ST, E, kind)

    half = len(pf) // 2
    pf = 0.5 * (pf[:half] + pf[half:])        # ← average WITHIN each pair first

    disc = np.exp(-r * T) * pf
    return disc.mean(), disc.std(ddof=1) / np.sqrt(len(disc))

The first time around I generated the mirrored paths perfectly but reported statistics over the flattened bucket — and saw essentially no improvement. It would have been easy to conclude "antithetic variates don't do much here" and move on. They do. The method was fine; the bookkeeping wasn't.

The variance reduction silently vanishes the moment you treat mirrored paths as ordinary independent samples. The estimator still looks correct. It just stops helping.

The payoff

With the pairwise average in place — and the path budget held fixed — here's what happens to the standard errors:

The European options tighten up by 23–30%. The binary options? Their standard error drops by ~54% — better than cut in half. Same compute, same number of random draws, one extra line of code.

Now translate that back into the currency we started with: paths. Because error scales as , a % reduction in standard error is worth the same as multiplying your path count by . Here's the free lunch, priced in paths:

For the digitals, a 54% reduction is equivalent to running 4.7× as many simulations. To buy that accuracy the brute-force way you'd nearly quintuple your compute bill. The sign flip gets it for nothing.

A 54% reduction in standard error is equivalent to running 4.7 times as many simulations. One sign flip. Zero extra compute.

Why the digitals win biggest

It's worth pausing on why the binaries — the contracts that gave plain Monte Carlo the most trouble — turn out to be the method's favorite customers.

The symmetry here is almost poetic. At the money ($S_0 = E$), when a shock nudges the terminal price above the strike, its mirror nudges the partner path below it — and vice versa. So a mirrored pair very often straddles the strike, with one path paying out $1 and the other paying $0. Their average is a near-deterministic $0.50, hugging the true value with almost no scatter.

The discontinuity that made digitals so noisy under plain Monte Carlo is the very feature that makes antithetic pairing so devastatingly effective on them.

The vanilla payoffs are smoother functions of , so the anti-correlation within a pair is real but milder — hence the more modest (still very welcome) 23–30% gains.

The lesson worth keeping

What I love about antithetic variates is how thoroughly the result outruns the effort. There's no sophistication here — no new theory, no exotic data structure, no hardware. Just an observation that the normal distribution is symmetric, and the discipline to average in pairs. And yet on the hardest contract in the set it delivers the punch of a fivefold compute increase.

That's the pattern I keep running into in numerical work: the biggest wins rarely come from more horsepower. They come from looking harder at the structure of the problem and exploiting a symmetry that was sitting in plain sight the whole time.

Brute force is a strategy. It's just usually the most expensive one.

Antithetic variates are also only the first rung. From here the natural next steps stack neatly:

  • Control variates — lean on a quantity whose expectation you already know (the discounted , or a vanilla call when pricing an exotic) to cancel out even more noise. They compose with antithetics.
  • Quasi-Monte Carlo — replace pseudo-random shocks with low-discrepancy Sobol' or Halton sequences and chase the convergence regime instead of .
  • Path-dependent payoffs — Asian, barrier, and lookback options, where variance reduction stops being a nice-to-have and becomes essential.

But all of those build on the same core insight: before you reach for more paths, look for the structure you can exploit for free.

Get in Touch

Working on a Monte Carlo engine that's burning more compute than it should? Want to talk through variance reduction, derivative pricing, or building reliable numerical simulations in Python?

Connect with me:

Whether it's option pricing, stochastic simulation, or squeezing more accuracy out of fewer samples, I'd love to hear what you're building.


The Monte Carlo Series:

Mathematical Foundations:


References

  1. Paul Wilmott, Introduction to Quantitative Finance, 2nd Edition, John Wiley & Sons, 2000.
  2. Peter Jäckel, Monte Carlo Methods in Financial Markets, John Wiley & Sons, 2002.
  3. Glasserman, P., Monte Carlo Methods in Financial Engineering, Springer, 2003. The definitive reference on variance reduction for pricing.
  4. Desmond J. Higham, "An Algorithmic Introduction to Numerical Simulation of Stochastic Differential Equations," SIAM Review, 43(3), 525–546, 2001.
  5. Peter E. Kloeden and Eckhard Platen, Numerical Solution of Stochastic Differential Equations, Springer, 1992.
  6. Antithetic variates. Wikipedia.
  7. Variance reduction. Wikipedia.
  8. Monte Carlo methods for option pricing. Wikipedia.

Share this article