Skip to content

Euler Convergence Geometric Brownian Motion

Background

Euler discretization convergence for Geometric Brownian Motion.

Demonstrates weak and strong convergence properties of the Euler scheme for GBM approximation.

Reference: Oosterlee & Grzelak (2019). Mathematical Modeling and Computation in Finance. World Scientific.


Code

```python

-- coding: utf-8 --

""" Euler discretization convergence for Geometric Brownian Motion.

Demonstrates weak and strong convergence properties of the Euler scheme for GBM approximation.

Reference: Oosterlee & Grzelak (2019). Mathematical Modeling and Computation in Finance. World Scientific. """

import numpy as np import matplotlib.pyplot as plt

=============================================================================

1. Path Generation

=============================================================================

def generate_paths_gbm_euler(num_paths, num_steps, maturity, r, sigma, s0): """ Generate GBM paths using Euler discretization.

Parameters
----------
num_paths : int
    Number of sample paths.
num_steps : int
    Number of time steps.
maturity : float
    Time to maturity (T).
r : float
    Risk-free rate.
sigma : float
    Volatility.
s0 : float
    Initial stock price.

Returns
-------
paths : dict
    Dictionary with keys 'time', 'S1' (Euler approx), 'S2' (exact).
"""
z = np.random.normal(0.0, 1.0, (num_paths, num_steps))
w = np.zeros((num_paths, num_steps + 1))

# Approximation (Euler scheme)
s1 = np.zeros((num_paths, num_steps + 1))
s1[:, 0] = s0

# Exact solution
s2 = np.zeros((num_paths, num_steps + 1))
s2[:, 0] = s0

time = np.zeros(num_steps + 1)

dt = maturity / float(num_steps)
for i in range(num_steps):
    # Ensure samples from normal have mean 0 and variance 1
    if num_paths > 1:
        z[:, i] = (z[:, i] - np.mean(z[:, i])) / np.std(z[:, i])
    w[:, i + 1] = w[:, i] + np.sqrt(dt) * z[:, i]

    # Euler scheme
    s1[:, i + 1] = s1[:, i] + r * s1[:, i] * dt + sigma * s1[:, i] * (w[:, i + 1] - w[:, i])

    # Exact solution
    s2[:, i + 1] = s2[:, i] * np.exp((r - 0.5 * sigma ** 2.0) * dt + sigma * (w[:, i + 1] - w[:, i]))
    time[i + 1] = time[i] + dt

paths = {"time": time, "S1": s1, "S2": s2}
return paths

=============================================================================

2. Visualization

=============================================================================

def plot_sample_paths(time, s1, s2): """ Plot sample paths from Euler and exact schemes.

Parameters
----------
time : ndarray
    Time grid.
s1 : ndarray
    Euler scheme paths.
s2 : ndarray
    Exact scheme paths.
"""
plt.figure(1, figsize=(10, 6))
plt.plot(time, np.transpose(s1), 'k')
plt.plot(time, np.transpose(s2), '--r')
plt.grid()
plt.xlabel("time")
plt.ylabel("S(t)")
plt.tight_layout()

def plot_convergence(dt_vec, error_weak, error_strong): """ Plot weak and strong convergence errors.

Parameters
----------
dt_vec : ndarray
    Vector of time steps.
error_weak : ndarray
    Weak convergence errors.
error_strong : ndarray
    Strong convergence errors.
"""
plt.figure(2, figsize=(10, 6))
plt.plot(dt_vec, error_weak)
plt.plot(dt_vec, error_strong, '--r')
plt.grid()
plt.legend(['weak convergence', 'strong convergence'])
plt.xlabel("dt")
plt.ylabel("error")
plt.tight_layout()

=============================================================================

3. Main

=============================================================================

def main(): """Run convergence analysis for Euler GBM scheme.""" # ===== Sample Path Visualization ===== num_paths = 25 num_steps = 25 maturity = 1.0 r = 0.06 # Risk-free rate sigma = 0.3 # Volatility s0 = 50.0 # Initial stock price

paths = generate_paths_gbm_euler(num_paths, num_steps, maturity, r, sigma, s0)
time = paths["time"]
s1 = paths["S1"]
s2 = paths["S2"]

plot_sample_paths(time, s1, s2)

# ===== Convergence Analysis =====
num_steps_vec = range(1, 500, 1)
num_paths = 250
error_weak = np.zeros((len(num_steps_vec), 1))
error_strong = np.zeros((len(num_steps_vec), 1))
dt_vec = np.zeros((len(num_steps_vec), 1))

for idx, num_steps in enumerate(num_steps_vec):
    paths = generate_paths_gbm_euler(num_paths, num_steps, maturity, r, sigma, s0)
    # Get paths at maturity
    s1_at_t = paths["S1"][:, -1]
    s2_at_t = paths["S2"][:, -1]

    error_weak[idx] = np.abs(np.mean(s1_at_t) - np.mean(s2_at_t))
    error_strong[idx] = np.mean(np.abs(s1_at_t - s2_at_t))
    dt_vec[idx] = maturity / num_steps

print(error_strong)
plot_convergence(dt_vec, error_weak, error_strong)
plt.show()

if name == "main": main() ```

Exercises

Exercise 1. Show that the Euler scheme for GBM has weak convergence order 1 and strong convergence order 0.5.

Solution to Exercise 1

Weak convergence: expanding the exact solution to first order in \(\Delta t\) matches the Euler step, giving accumulated error \(O(\Delta t)\) over \(N = T/\Delta t\) steps (order 1). Strong convergence: the Euler scheme misses the Ito correction term \(-\frac{\sigma^2}{2}S\Delta t\). Each step has \(O(\Delta t)\) error, but pathwise accumulation as a random walk gives \(O(\sqrt{\Delta t})\) (order 0.5).


Exercise 2. For GBM with \(r = 0.06\), \(\sigma = 0.3\), \(S_0 = 50\), \(T = 1\), compute \(\mathbb{E}[S_T]\) exactly and explain why Euler can estimate the mean accurately despite low strong order.

Solution to Exercise 2

\(\mathbb{E}[S_T] = 50 e^{0.06} = \$53.09\). Euler preserves \(\mathbb{E}[S_{i+1}|S_i] = S_i(1 + r\Delta t)\), so the mean is accurately estimated. Weak convergence (order 1) governs expectations; strong convergence (order 0.5) governs individual paths. For pricing via expectations, Euler is effective even with moderate steps.


Exercise 3. Explain why normalizing random samples (\(z \leftarrow (z - \bar{z})/s_z\)) at each step improves convergence.

Solution to Exercise 3

Normalization forces exact zero mean and unit variance at each step, eliminating finite-sample bias in the drift and realized volatility. This moment matching reduces Monte Carlo variance, producing smoother convergence plots, especially with few paths.


Exercise 4. On a log-log plot, the weak error has slope 1 and the strong error has slope 0.5. If the strong error at \(\Delta t = 0.01\) is \(0.5\), estimate it at \(\Delta t = 0.001\).

Solution to Exercise 4

\(\epsilon_s \propto \sqrt{\Delta t}\). At \(\Delta t = 0.01\): \(C_s = 0.5/\sqrt{0.01} = 5\). At \(\Delta t = 0.001\): \(\epsilon_s = 5\sqrt{0.001} = 0.158\). Reducing \(\Delta t\) tenfold reduces strong error by factor \(\sqrt{10} \approx 3.16\).