Using what you already know
Simulating a call, you also simulate the stock price at expiry S_T, whose true mean S0 * exp(r * T) you know exactly. In your sample the average of S_T came out a little off, say too high. Since the call payoff and S_T move together, the call's sample mean is probably too high as well. A control variate uses this: it subtracts a multiple of the observed error of the quantity you know from the estimate of the one you want.
import numpy as np
rng = np.random.default_rng(1)
z = rng.standard_normal(2000)
s_t = 100.0 * np.exp((0.05 - 0.5 * 0.2 ** 2) + 0.2 * z) # terminal prices
call = np.maximum(s_t - 100.0, 0.0) # the payoff we want
known = 100.0 * np.exp(0.05) # E[S_T], exactly
print(round(float(s_t.mean()), 3), round(known, 3)) # the sample mean of S_T vs its true mean
print(round(float(s_t.mean() - known), 3)) # the observed error of the controlThe best multiple
How much of the control's error should you subtract? The multiple b that minimises the variance of payoff - b * control is b = cov(payoff, control) / var(control), exactly the slope of a regression of the payoff on the control (the same sums as beta in the CAPM topic). The controlled estimate is mean(payoff) - b * (mean(control) - known_mean). It is unbiased, because the term being subtracted has expectation zero.
import numpy as np
def control_coefficient(payoff, control):
dev = control - control.mean()
return float(np.sum(dev * (payoff - payoff.mean())) / np.sum(dev ** 2))
rng = np.random.default_rng(1)
z = rng.standard_normal(2000)
s_t = 100.0 * np.exp((0.05 - 0.5 * 0.2 ** 2) + 0.2 * z)
call = np.maximum(s_t - 100.0, 0.0)
known = 100.0 * np.exp(0.05)
b = control_coefficient(call, s_t)
controlled = call.mean() - b * (s_t.mean() - known)
print(round(b, 4), round(float(call.mean()), 4), round(float(controlled), 4)) # the plain mean and the corrected one# A payoff that is exactly proportional to the control is estimated without any error
import numpy as np
x = np.random.default_rng(2).standard_normal(50) + 5.0 # the control, known mean 5.0
y = 2.0 * x # the payoff
dev = x - x.mean()
b = np.sum(dev * (y - y.mean())) / np.sum(dev ** 2)
print(round(float(b), 6), round(float(y.mean() - b * (x.mean() - 5.0)), 6)) # 2.0 and exactly 10.0How much error is saved
The adjusted samples payoff - b * control are what carry the noise, so the standard error of the controlled estimate is their standard deviation (ddof=1) divided by sqrt(n). With correlation rho between payoff and control, the variance falls by a factor 1 - rho^2: a correlation of 0.9 removes 81% of it, and 0.99 removes 98%. The control costs no extra simulation, only a few extra lines.
import numpy as np
def control_variate_error(payoff, control):
dev = control - control.mean()
b = np.sum(dev * (payoff - payoff.mean())) / np.sum(dev ** 2)
return float((payoff - b * control).std(ddof=1) / np.sqrt(len(payoff)))
rng = np.random.default_rng(1)
z = rng.standard_normal(20000)
s_t = 100.0 * np.exp((0.05 - 0.5 * 0.2 ** 2) + 0.2 * z)
for strike in (80.0, 100.0, 120.0):
call = np.maximum(s_t - strike, 0.0)
plain = float(call.std(ddof=1) / np.sqrt(len(call)))
print(strike, round(plain, 4), round(control_variate_error(call, s_t), 4), round(plain / control_variate_error(call, s_t), 2))b is near 0 and nothing is gained) and its exact mean must be known (otherwise the correction is a guess). Good controls for an Asian arithmetic average are the geometric average, whose price has a formula, or the terminal price itself. You can use several controls at once, and the coefficients then come from a multiple regression.- A control variate is a simulated quantity with a known mean; subtract a multiple of its observed error from the estimate:
mean(Y) - b * (mean(X) - E[X]). - The best multiple is the regression slope
b = cov(Y, X) / var(X); the error is the standard deviation ofY - b * Xoversqrt(n). - The variance drops by
1 - rho^2; a good control is highly correlated with the payoff and has an exactly known mean, and it costs no extra simulation.
Next up: scikit-learn pipelines for returns, fitting and evaluating predictive models on return data.
Lab · what's graded
- Choose the right multiple with
control_coefficient25 pts - Correct the estimate with
control_variate_estimate25 pts - Shrink the error bar with
control_variate_error25 pts - Count the gain with
error_reduction25 pts