Matching Geometric Brownian Motion and Lognormal Price Simulations
Summary
The document clarifies that geometric Brownian motion can be simulated by multiplying lognormal price relatives across time steps. In the direct approach, each step’s gross price ratio should itself be lognormally distributed, with log-mean and log-volatility matching the GBM increment. The question’s implementation instead draws a lognormal variable for a quantity labeled as a return and then adds one; that changes the distribution, because adding one to a lognormal variable does not produce a lognormal price ratio.
The response gives the corresponding product formulation and parameterization for the stepwise gross returns, aligning it with the exponential GBM update. It therefore explains why the two implementations yield different simulated prices and points to the incorrect variable being transformed as the source of the discrepancy. The comparison assumes the same drift, volatility, time discretization, and simulation setup. It addresses the equivalence of these two simulation constructions, not whether GBM is a realistic model of market prices or how to calibrate its parameters.
Key ideas
- A GBM price path can be formed by multiplying lognormal gross price ratios across time steps.
- The lognormal draw should represent the gross price ratio, rather than a return that is later increased by one.
- Adding one to a lognormal variable does not preserve the lognormal distribution.
- The two simulation methods agree when their per-step log-mean and log-volatility are parameterized consistently.
- Matching the simulation formulas does not establish that GBM accurately describes real market behavior.
Tags
Full text
# Geometric Brownian Motion vs Lognormal (Monte Carlo)
# Geometric Brownian Motion vs Lognormal (Monte Carlo)
I am a bit confused. I thought that simulating stock prices via the GBM model is equivalent to drawing returns from a lognormal distribution.
In particular, consider the following Python code, which showcases both approaches I am referring to. It yields slightly different results. My questions are: Why is that so - what is the difference here?
EDIT: When I do not transform the standard deviation like I do in approach 2, both approaches yield a much more similar result. However, I was of the opinion that the lognormal standard deviation =/= the normal standard deviation and hence the transformation. What am I missing here? I appended this code below the original code (Implementation 2).
```
# IMPLEMENTATION 1
import numpy as np
# Parameters
mu = 0.05 # annual return
sigma = 0.2 # annual volatility
S0 = 100 # initial stock price
dt = 1/252 # daily time step
years = 5 # years
total_steps = 252 * years # total steps
M = 10000 # number of simulations
# Method 1: Geometric Brownian Motion
np.random.seed(42)
St1 = np.exp(
(mu - sigma ** 2 / 2) * dt
+ sigma * np.random.normal(0, np.sqrt(dt), size=(total_steps, M))
)
final_prices1 = S0 * np.cumprod(St1, axis=0)
# Method 2: Direct Log-Normal
np.random.seed(42)
# Transforming the parameters to those of the underlying normally distributed log returns
norm_sigma = np.sqrt(np.log((sigma / (1 + mu)) ** 2 + 1))
norm_mu = np.log(1 + mu) - 0.5 * norm_sigma ** 2
returns2 = np.random.lognormal(
mean=norm_mu * dt,
sigma=norm_sigma * np.sqrt(dt),
size=(total_steps, M)
) - 1
final_prices2 = S0 * np.cumprod(1 + returns2, axis=0)
# Compare statistics
print("Method 1 Final Price - Mean:", np.mean(final_prices1[-1]))
print("Method 2 Final Price - Mean:", np.mean(final_prices2[-1]))
print("Method 1 Final Price - Std:", np.std(final_prices1[-1]))
print("Method 2 Final Price - Std:", np.std(final_prices2[-1]))
# IMPLEMENTATION 2
import numpy as np
# Parameters
mu = 0.05 # annual return
sigma = 0.2 # annual volatility
S0 = 100 # initial stock price
dt = 1/252 # daily time step
years = 5 # years
total_steps = 252 * years # total steps
M = 10000 # number of simulations
# Method 1: Geometric Brownian Motion
np.random.seed(42)
St1 = np.exp(
(mu - sigma** 2 / 2) * dt
+ sigma * np.random.normal(0, np.sqrt(dt), size=(total_steps, M))
)
final_prices1 = S0 * np.cumprod(St1, axis=0)
# Method 2: Direct Log-Normal
np.random.seed(42)
norm_mu = np.log(1 + mu) - 0.5 * sigma ** 2
returns2 = np.random.lognormal(
mean=norm_mu * dt,
sigma=sigma * np.sqrt(dt),
size=(total_steps, M)
) - 1
final_prices2 = S0 * np.cumprod(1+returns2, axis=0)
# Compare statistics
print("Method 1 Final Price - Mean:", np.mean(final_prices1[-1]))
print("Method 2 Final Price - Mean:", np.mean(final_prices2[-1]))
print("Method 1 Final Price - Std:", np.std(final_prices1[-1]))
print("Method 2 Final Price - Std:", np.std(final_prices2[-1]))
```
## Answer by Ted Black (score 1)
https://quant.stackexchange.com/a/81281
Method 1 uses $$ S = S_0 \prod_{i=1}^N\exp\left( \left(\mu - \frac{1}{2}\sigma^2 \right)\Delta t + \sigma \sqrt{\Delta t} x_i \right) $$ where $x_i \in \texttt{np.random.normal}(0,\sqrt{\Delta t})$.
Method 2 should use $$ S = S_0 \prod_{i=1}^N z_i $$ where $z_i \in \texttt{np.random.lognormal}((\mu - 0.5 \sigma^2)\Delta t,\sigma \sqrt{\Delta t})$.
In your version of Method 2 you have $$ S = S_0 \prod_{i=1}^N (1+r_i) $$ where $r_i$ is what you call $\texttt{returns2}$. You model this as a lognormal variable; but clearly $z_i=1+r_i$ is a lognormal variable; and if $r_i$ is a lognormal variable, $z_i$ is not a lognormal variable.Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.