Merton Process Paths (Grzelak)¶
Background¶
Generate sample paths for the Merton jump diffusion process.
This script demonstrates Monte Carlo simulation of the Merton jump diffusion model, where stock prices follow a geometric Brownian motion with superimposed random jumps. It generates and visualizes sample paths for both the log-price process and the stock price process.
Reference: Oosterlee & Grzelak (2019). Mathematical Modeling and Computation in Finance. World Scientific.
Code¶
```python
-- coding: utf-8 --¶
""" Generate sample paths for the Merton jump diffusion process.
This script demonstrates Monte Carlo simulation of the Merton jump diffusion model, where stock prices follow a geometric Brownian motion with superimposed random jumps. It generates and visualizes sample paths for both the log-price process and the stock price process.
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_merton(num_paths, num_steps, s0, T, xi_p, mu_j, sigma_j, r, sigma): """ Generate sample paths for the Merton jump diffusion process.
Parameters
----------
num_paths : int
Number of Monte Carlo paths.
num_steps : int
Number of time steps.
s0 : float
Initial stock price.
T : float
Time to maturity.
xi_p : float
Jump intensity (Poisson parameter).
mu_j : float
Mean of jump size.
sigma_j : float
Standard deviation of jump size.
r : float
Risk-free interest rate.
sigma : float
Volatility of the continuous part.
Returns
-------
paths : dict
Dictionary containing:
- 'time': time grid (ndarray of shape (num_steps+1,))
- 'X': log-price process (ndarray of shape (num_paths, num_steps+1))
- 'S': stock price process (ndarray of shape (num_paths, num_steps+1))
"""
# Create empty matrices for log-price and stock price
x = np.zeros((num_paths, num_steps + 1))
s = np.zeros((num_paths, num_steps + 1))
time = np.zeros(num_steps + 1)
dt = T / float(num_steps)
x[:, 0] = np.log(s0)
s[:, 0] = s0
# Expectation E(e^J) for J~N(mu_j, sigma_j^2)
exp_ej = np.exp(mu_j + 0.5 * sigma_j * sigma_j)
# Generate random variables
z_pois = np.random.poisson(xi_p * dt, (num_paths, num_steps))
z = np.random.normal(0.0, 1.0, (num_paths, num_steps))
j = np.random.normal(mu_j, sigma_j, (num_paths, num_steps))
for i in range(0, num_steps):
# Standardize samples to ensure mean 0 and variance 1
if num_paths > 1:
z[:, i] = (z[:, i] - np.mean(z[:, i])) / np.std(z[:, i])
# Update log-price with continuous and jump components
x[:, i + 1] = (x[:, i] + (r - xi_p * (exp_ej - 1) - 0.5 * sigma * sigma)
* dt + sigma * np.sqrt(dt) * z[:, i]
+ j[:, i] * z_pois[:, i])
time[i + 1] = time[i] + dt
s = np.exp(x)
paths = {"time": time, "X": x, "S": s}
return paths
=============================================================================¶
2. Visualization¶
=============================================================================¶
def plot_log_prices(time, x): """ Plot log-price sample paths.
Parameters
----------
time : ndarray
Time grid.
x : ndarray
Log-price paths of shape (num_paths, num_steps+1).
"""
plt.figure(1)
plt.plot(time, np.transpose(x))
plt.grid()
plt.xlabel("time")
plt.ylabel("X(t)")
plt.tight_layout()
def plot_stock_prices(time, s): """ Plot stock price sample paths.
Parameters
----------
time : ndarray
Time grid.
s : ndarray
Stock price paths of shape (num_paths, num_steps+1).
"""
plt.figure(2)
plt.plot(time, np.transpose(s))
plt.grid()
plt.xlabel("time")
plt.ylabel("S(t)")
plt.tight_layout()
=============================================================================¶
3. Main¶
=============================================================================¶
def main(): """Run Merton process path generation demonstration.""" # Parameters num_paths = 25 # Number of Monte Carlo paths num_steps = 500 # Number of time steps s0 = 100.0 # Initial stock price T = 5.0 # Time to maturity xi_p = 1.0 # Jump intensity mu_j = 0.0 # Mean of jump size sigma_j = 0.7 # Standard deviation of jump size sigma = 0.2 # Volatility r = 0.05 # Risk-free rate
# Generate paths
paths = generate_paths_merton(num_paths, num_steps, s0, T, xi_p, mu_j,
sigma_j, r, sigma)
time_grid = paths["time"]
x = paths["X"]
s = paths["S"]
# Visualize results
plot_log_prices(time_grid, x)
plot_stock_prices(time_grid, s)
plt.show()
if name == "main": main() ```
Exercises¶
Exercise 1. Write the log-price increment for one Merton time step. Identify drift, diffusion, and jump components.
Solution to Exercise 1
\(\ln S_{t+\Delta t} = \ln S_t + (r - \frac{1}{2}\sigma^2 - \lambda\bar{k})\Delta t + \sigma\sqrt{\Delta t}Z + \sum_{i=1}^n J_i\) where \(n \sim \text{Poisson}(\lambda\Delta t)\) and \(J_i \sim \mathcal{N}(\mu_J, \sigma_J^2)\).
Exercise 2. Why is exact simulation possible for the Merton model?
Solution to Exercise 2
The diffusion and jump components are independent, the diffusion is exactly GBM (log-normal), and jumps form a compound Poisson process with known distribution. Each can be sampled exactly without discretization.
Exercise 3. How do Merton paths visually differ from pure GBM paths?
Solution to Exercise 3
Merton paths show sudden vertical gaps (jumps) superimposed on smooth GBM fluctuations. The distribution has fatter tails and possibly skewness (if \(\mu_J \ne 0\)). GBM paths are continuous with no gaps.
Exercise 4. What is the compensator \(\lambda\bar{k}\) and why is it needed?
Solution to Exercise 4
\(\bar{k} = e^{\mu_J + \sigma_J^2/2} - 1\) is the expected relative jump size. Including \(-\lambda\bar{k}\) in the drift ensures \(e^{-rt}S_t\) is a martingale under the risk-neutral measure, preventing arbitrage from the expected jump return.