The price is a mean
The inputs are those of case studies 2.4.6, 2.6.5 and 5.6.5. The stock costs dollars, the strike is , the expiry years. The interest rate is and the volatility , both annual decimals.
Case study 5.6.5 wrote the price at expiry as , with standard normal, and . Case study 2.6.5 priced the call as an integral over weighted by . That integral is an expected value: the mean of the discounted payoff
So the call's price is . Draw independent copies of , compute each , and average. By the law of large numbers (lesson 5.7.2) the average settles on .
Rule: Monte Carlo price
are independent standard normal draws. The method needs no formula for the price, only a way to draw . That is why desks use it for payoffs that have no formula.
Try it
The first line is the Black–Scholes formula of case study 2.4.6. The second is case study 2.6.5's integral by quad, from the corner where the call starts to pay. The upper limit 12 stands in for : the weight is negligible there.
Then 10,000 seeded draws and their average, the $6.2713 of the hook.
The payoff is lopsided
Before measuring the error of the average, look at what is being averaged. The call pays nothing when , which has probability . When it pays, the payoff can be large.
The same integrals give its spread. , so the standard deviation of one discounted payoff is
That is about 1.49 times the price. The skewness (lesson 5.3.3) is 1.83: a long right tail.
The seeded sample looks the same. Of its 10,000 payoffs, 0.4903 are zero, the median is $0.3174, the largest is $66.43 and the sample skewness is 1.78. One payoff is nothing like normal; the central limit theorem (lesson 5.7.3) is about their mean.
Try it
quad integrates , and against . The first line prints , , , its ratio to the price, and the skewness from the three moments.
The second line describes the seeded sample: the share of zero payoffs, the median, the largest payoff and the skewness.
The standard error and a 95% interval
The average of independent payoffs has standard deviation (lesson 5.7.2): for . On a real problem is unknown, since a formula for it would usually give the price too.
So estimate it from the same draws, with the sample standard deviation , dividing by (lesson 5.5.2). By the central limit theorem (lesson 5.7.3), is close to normal. So it lies within 1.96 standard errors of about 95% of the time.
Rule: Standard error and 95% interval
is the sample standard deviation of the discounted payoffs. For a share, is close to , and this is the standard error of case study 4.7.5.
For the seeded run, , so . The interval runs from 6.0885 to 6.4541 and covers 6.3710. The miss, , is 1.07 standard errors below the price: ordinary, as the hook said.
Try it
Y.std(ddof=1) divides by . The first line prints the estimate, and the standard error; the second the 95% interval.
The last line is the miss, the miss in standard errors, and the standard error from the exact .
What one more digit costs
Carry the same seeded run on to a million draws. The first 10,000 are the ones above.
| draws | 100 | 1,000 | 10,000 | 100,000 | 1,000,000 |
|---|---|---|---|---|---|
| estimate | 5.1066 | 6.5055 | 6.2713 | 6.3685 | 6.3765 |
| standard error | 0.8348 | 0.2924 | 0.0933 | 0.0299 | 0.0095 |
Every interval covers 6.3710. Each tenfold step divides the standard error by about (lesson 5.7.3); case study 4.7.5 saw the same slope for a probability.
To pin the price to the cent with 95% confidence, a half-width of 0.01, needs : about 3.44 million draws. A tenth of a cent needs about 344 million. Each extra digit costs a hundred times the draws.
One seeded run's estimate and its 95% interval as the draws grow. The readouts give the estimate and the half-width ; the band holds 6.3710 throughout and narrows like .
Try it
One array of a million payoffs; Y[:N] keeps the first N. Each line prints , the estimate, the standard error, and whether the 95% interval covers the price.
The last loop gives the draws for half-widths of 0.01 and 0.001.
When the 95% can be trusted
One run covering the price proves little. Repeat the whole simulation many times and count how often the interval covers 6.3710.
| draws | 20 | 100 | 2,000 |
|---|---|---|---|
| runs | 10,000 | 10,000 | 1,000 |
| interval covers 6.3710 | 0.9034 | 0.9402 | 0.946 |
| interval wholly below 6.3710 | 0.0873 | 0.0446 | 0.025 |
| interval wholly above 6.3710 | 0.0093 | 0.0152 | 0.029 |
With 2,000 draws the coverage is close to 95%, and the misses fall about evenly on each side. With 20 draws it is only 90%, and most misses are low.
The lopsided payoff is the reason. The mean of 20 payoffs still has skewness about (lesson 5.7.3).
A small sample often misses the rare large payoffs. Then both its average and its come out too small: the interval sits low and is too narrow.
The interval needs enough draws for the theorem to apply, and more when the payoff is lopsided.
Try it
For each , every row of Z is one complete simulation. The columns are , the share of intervals covering the price, the share lying wholly below it and the share wholly above.
The last line is the skewness of a mean of 20 payoffs.
Antithetic draws
The standard error is , so there are two ways to shrink it: more draws, or a smaller . Methods that take the second way are called variance reduction. The simplest uses antithetic draws.
If is standard normal, so is . Draw values of and use each twice, as and as .
A high gives a large payoff and its mirror a small one, so the two payoffs of a pair are negatively correlated. Their average varies less than two independent payoffs would.
With 10,000 payoffs in 5,000 pairs, one seeded run gives $6.3704 with standard error 0.0694. Plain draws give about 0.0947 for the same 10,000 payoffs. The correlation within a pair was ; the variance of a pair's average is , with that correlation.
Two traps come with the method.
- The standard error must come from the pairs. The 10,000 payoffs are not independent, so over all of them, 0.0942 here, is wrong. Use the 5,000 pair averages: their sample standard deviation over .
- It helps only when the payoff moves one way with . A straddle pays , high at both ends. Its two payoffs in a pair have correlation : near duplicates. An estimate from pairs then has times the variance of one from plain draws, so antithetic draws make it worse.
Try it
call and straddle compute discounted payoffs for an array of draws. The first line is the antithetic estimate, its standard error from the 5,000 pair averages, and the correlation within a pair.
The second line is the wrong standard error, treating all 10,000 payoffs as independent. The last is the straddle's correlation within a pair and .
On the desk
Try it
mc_call returns the estimate, the standard error and the share of draws that pay; drift replaces in the exponent. bs_call is the formula.
The three lines are the strike of 150, then the drift of 8% beside its exact value, then . There the standard error is 0 up to rounding.
A Monte Carlo price with its error
Write mc_call(S0, K, T, r, sigma, n, seed, antithetic=False). It returns the pair (price, se) for a European call, as in this case study.
- Draw with
np.random.default_rng(seed).standard_normal(...), and set with and . - Without antithetic draws:
ndraws,pricethe mean of the discounted payoffs,setheir sample standard deviation (dividing by ) over . - With
antithetic=True: drawn // 2values and average the payoffs of and in each pair. Computepriceandsefrom then // 2pair averages. - Raise
ValueErrorif , if , or is negative, if , or if antithetic draws get an odd or fewer than 4.
The tests check this case study's seeded run (6.2713, standard error 0.0933) and its antithetic run (6.3704 and 0.0694). They also check the scale, , and a deep out-of-the-money strike.
See the tests
import math
BS = 6.371028 # Black–Scholes price of the shared call (case study 2.4.6)
def test_seeded_run():
"""Seed 575 with 10,000 draws gives 6.2713 with standard error 0.0933, as in the case study"""
price, se = mc_call(100, 100, 0.5, 0.03, 0.2, 10_000, 575)
expect_close(price, 6.271264328239705, what="price", rel=1e-9)
expect_close(se, 0.09327231109608519, what="standard error (sample sd with n - 1, over sqrt(n))", rel=1e-9)
def test_closes_on_black_scholes():
"""With 400,000 draws the estimate is within 3 standard errors of 6.3710"""
price, se = mc_call(100, 100, 0.5, 0.03, 0.2, 400_000, 11)
assert abs(price - BS) <= 3 * se, f"expected a price within 3 standard errors ({3 * se:.4f}) of {BS}, got {price:.4f}"
def test_standard_error_size():
"""The standard error is close to 9.4683 / sqrt(n), the payoff's standard deviation over sqrt(n)"""
_, se = mc_call(100, 100, 0.5, 0.03, 0.2, 400_000, 12)
expect_close(se, 9.4683 / math.sqrt(400_000), what="standard error with 400,000 draws", rel=0.02)
def test_four_times_the_draws():
"""Four times the draws halve the standard error"""
small = mc_call(100, 100, 0.5, 0.03, 0.2, 10_000, 3)[1]
large = mc_call(100, 100, 0.5, 0.03, 0.2, 40_000, 3)[1]
expect_close(large / small, 0.5, what="ratio of standard errors for 40,000 and 10,000 draws", rel=0.03)
def test_antithetic():
"""Antithetic pairs, seed 5753 and 10,000 payoffs: 6.3704 with standard error 0.0694, from the 5,000 pair averages"""
price, se = mc_call(100, 100, 0.5, 0.03, 0.2, 10_000, 5753, antithetic=True)
expect_close(price, 6.370379263137437, what="antithetic price", rel=1e-9)
expect_close(se, 0.0694189616918534, what="antithetic standard error", rel=1e-9)
plain = mc_call(100, 100, 0.5, 0.03, 0.2, 10_000, 5753)[1]
assert se < plain, f"expected the antithetic standard error {se:.4f} to be below the plain one {plain:.4f}"
def test_no_volatility():
"""With sigma = 0 every payoff is 100 - 100e^(-0.015) = 1.4888, and the standard error is 0"""
price, se = mc_call(100, 100, 0.5, 0.03, 0.0, 1_000, 1)
expect_close(price, 100 - 100 * math.exp(-0.015), what="price with sigma = 0", rel=1e-9)
assert abs(se) < 1e-9, f"expected a standard error of 0, got {se}"
def test_at_expiry():
"""With T = 0 the call is worth max(S0 - K, 0): 5 for S0 = 105 and K = 100"""
price, se = mc_call(105, 100, 0, 0.03, 0.2, 1_000, 1)
expect_close(price, 5.0, what="price at expiry", rel=1e-12)
assert abs(se) < 1e-9, f"expected a standard error of 0, got {se}"
def test_deep_out_of_the_money():
"""Strike 150: the standard error is more than 30% of the price with 10,000 draws"""
price, se = mc_call(100, 150, 0.5, 0.03, 0.2, 10_000, 5754)
assert price > 0, f"expected a positive price, got {price}"
assert se / price > 0.3, f"expected a relative error above 0.3, got {se / price:.3f}"
def test_bad_inputs():
"""S0 <= 0, a negative sigma or T, n below 2, or an odd n with antithetic draws raises ValueError"""
cases = [
("S0 = 0", lambda: mc_call(0, 100, 0.5, 0.03, 0.2, 100, 1)),
("sigma = -0.2", lambda: mc_call(100, 100, 0.5, 0.03, -0.2, 100, 1)),
("T = -1", lambda: mc_call(100, 100, -1, 0.03, 0.2, 100, 1)),
("n = 1", lambda: mc_call(100, 100, 0.5, 0.03, 0.2, 1, 1)),
("odd n with antithetic", lambda: mc_call(100, 100, 0.5, 0.03, 0.2, 101, 1, antithetic=True)),
]
for name, call in cases:
try:
call()
except ValueError:
continue
raise AssertionError(f"expected ValueError for {name}")
Summary card
- The call's price is a mean, ; the average of simulated discounted payoffs settles on it (lesson 5.7.2).
- Its standard error is , with the sample standard deviation of the payoffs; here , so 10,000 draws give about 0.095.
- is a 95% interval (lesson 5.7.3) once is large enough for the lopsided payoff. Coverage was 90% at 20 draws and close to 95% at 2,000.
- One more digit costs 100 times the draws: the cent needs about 3.44 million.
- Antithetic pairs , cut the standard error when the payoff moves one way with : 0.0694 against 0.0947 for 10,000 payoffs. They hurt a straddle. Take the standard error from the pair averages.
- Report the error, the draws and the seed; simulate with ; expect large relative errors deep out of the money.