Pricing European options by Monte Carlo¶
Problem¶
Price a one-year European call and put, at the money (\(S_0 = K = 100\)), with \(r = 5\%\) and \(\sigma = 20\%\), by simulating 10,000 prices at maturity, and compare with the closed form.
Method¶
Under the risk-neutral measure the price at maturity is log-normal with drift \(r\):
and the same for the put. Only one draw per path is needed, because the payoff depends on the final price alone.
Code¶
clear all, clc
%% 1
n = 10000;
T = 1;
sigma = 0.2;
S_0 = 100;
K = 100;
r = 0.05;
[call_price, put_price] = F_MonteCarlo(S_0, K, r, sigma, T, n)
function [C, P] = F_MonteCarlo(S_0, K, r, sigma, T, n)
% European call/put pricing by Monte Carlo, simulating the price at maturity S_T
% with a GBM under the risk-neutral measure Q (drift = r, not mu).
% INPUT: S_0 current price, K strike, r rate, sigma vol, T maturity, n simulations.
% OUTPUT: C call price, P put price.
Z = randn(n, 1); % one shock per simulation
S_T = S_0 * exp((r - 0.5*sigma^2)*T + sigma*sqrt(T)*Z); % price at maturity (p. 227)
payoff_call = max(S_T - K, 0); % call payoff at maturity
payoff_put = max(K - S_T, 0); % put payoff at maturity
C = exp(-r*T) * mean(payoff_call); % discounted mean = call price
P = exp(-r*T) * mean(payoff_put); % discounted mean = put price
end
import numpy as np
# %% function F_MonteCarlo
def F_MonteCarlo(S_0, K, r, sigma, T, n):
# European call/put pricing by Monte Carlo, simulating the price at maturity S_T
# with a GBM under the risk-neutral measure Q (drift = r, not mu).
Z = np.random.randn(n) # one shock per simulation
S_T = S_0 * np.exp((r - 0.5 * sigma**2) * T + sigma * np.sqrt(T) * Z) # price at maturity
payoff_call = np.maximum(S_T - K, 0) # call payoff at maturity
payoff_put = np.maximum(K - S_T, 0) # put payoff at maturity
C = np.exp(-r * T) * np.mean(payoff_call) # discounted mean = call price
P = np.exp(-r * T) * np.mean(payoff_put) # discounted mean = put price
return C, P
# %% 1
n = 10000
T = 1
sigma = 0.2
S_0 = 100
K = 100
r = 0.05
call_price, put_price = F_MonteCarlo(S_0, K, r, sigma, T, n)
print("call_price =", call_price)
print("put_price =", put_price)
Two variable names restored
In the saved version of this script, two lines had lost their variable name and read
= 100; and = 0.05;, so the file stopped with a syntax error at line 9. They are
restored here as K = 100; and r = 0.05;, the names the call to F_MonteCarlo on the
next line requires. Nothing else is changed.
Results¶
| Monte Carlo, 10,000 draws | Black–Scholes | |
|---|---|---|
| Call | 10.3871 | 10.4506 |
| Put | 5.5059 | 5.5735 |
One simulated run, random seed 0. With 10,000 draws the standard error of the call price is about 0.15, so the gap from the closed form is within one standard error.
Takeaways¶
- Monte Carlo converges slowly: the error falls as \(1/\sqrt{n}\), so one more correct digit costs a hundred times more simulations.
- Its strength is flexibility, not speed: the same code prices any payoff that depends on the final price, and with full paths it handles path-dependent ones, as on the next page.
- Pricing uses the risk-free rate as drift, not the expected return of the stock: that is what makes the discounted expectation a price.