Menu Home

Modelling Asset Returns with Brownian Motion

The geometric Brownian motion model allows you to generate a series of prices for an asset. It is a type of stochastic process that follows a Brownian motion path with a drift.

Stochastic processes are a concept from probability theory which are used to model the change in a seemingly random process over time. Brownian motion is a stochastic process, originally used by physicists to describe the seemingly random movement of particles within a liquid or gas. Geometric Brownian Motion builds on this by also incorporating the drift factor. These differ from auto-regressive models where you want future prices to take into account past prices using techniques such as ARMA or GARCH.

It's a far more complicated topic then can be described adequately in a single paragraph, but for further background, refer to here for a more complete overview.

In order to build models of the prices of an asset, we need to think of our asset's price as a process, where the return of an an asset over a time period is analogous to a process moving a set distance \Delta S (price) over a time period \Delta t.

Depending on the model used, and it's associated parameters, you can emulate a number of different behaviours in an asset's price movement. The most fundamental of these is an asset that exhibits a mean rate of return \mu, with volatility \sigma. Where the rate of return between two time periods can be defined as:

R = \frac{S_t - S_{t-1}}{S_{t-1}}

However, it is more usually defined in terms of log returns:

R = log\left(\frac{S_t}{S_{t-1}}\right)

There are two models which are typically used for modelling returns with Brownian motion. The first of these is the discrete Brownian motion model, which can be used for generating a series of asset prices.

\frac{\Delta S}{S} = \mu \Delta t + \sigma \sqrt{\Delta t} \epsilon

By substituting the return calculation \frac{S_t - S_{t-1}}{S_{t-1}}, we can derive the iterative formula for calculating our asset price S at time t given S_{t-1}

S_t = S_{t-1}\mu t + S_{t-1} \sigma \sqrt{\Delta t} \epsilon + S_{t-1}

This can them be implemented in Python as follows, where delta is the duration in days between each generated price, and sigma and mu are annualised values - i.e. delta = 1.0 generates daily prices.

import numpy as np


DAYS_PER_YEAR = 252.0


def generate_bm_prices(periods, start_price, mu, sigma, delta):
    t = delta / DAYS_PER_YEAR
    prices = np.zeros(periods)
    epsilon_sigma_t = np.random.normal(0, 1, periods) * sigma * np.sqrt(t)
    prices[0] = start_price
    for i in range(1, len(prices)):
        prices[i] = prices[i-1] * mu * t + \
                    prices[i-1] * epsilon_sigma_t[i-1] + \
                    prices[i-1]
    return prices

The second of these is the geometric Brownian motion model. Here the logarithm of the random component of our returns follow a Brownian motion.

This is usually given in it's closed form solution:

S_t = S_0 e^{((\mu-\frac{\sigma^2}{2})t + W_t \sigma)}

W_t is a Weiner process, where the following relationship holds:

W_t - W_{t-1} = \epsilon \sqrt{\Delta t}

Where \epsilon \in N(0, 1), i.e. it is a normally distributed random variable. Therefore to simulate the path of a GBM process, we can use the following model:

S_t = S_{t-1} e^{\left((\mu-\frac{\sigma^2}{2})\Delta t + \epsilon \sqrt{\Delta t} \sigma\right)}

This can be re-written in the more familiar form where we measure log returns of our asset:

\log\left({\frac{S_t}{S_{t-1}}}\right) = (\mu-\frac{\sigma^2}{2})\Delta t + \epsilon \sqrt{\Delta t} \sigma

The below Python code implements this model (I have a fully vectorised version here):

def generate_gbm_prices(periods, start_price, mu, sigma, delta):
    t = delta / DAYS_PER_YEAR
    prices = np.zeros(periods)
    epsilon_sigma_t = np.random.normal(0, 1, periods-1) * sigma * np.sqrt(t)
    prices[0] = start_price
    for i in range(1, len(prices)):
        prices[i] = prices[i-1] * \
                    np.exp((mu - 0.5 * sigma**2) * t +
                           epsilon_sigma_t[i-1])
    return prices

By plotting each of the above price series, we can illustrate the slight differences between the prices series generated by each of these models. The differences are most pronounced around 60-70 days in the chart - where you can see the blue lines in addition to the green due to deviations in price.

Furthermore, in order to validate our GBM model, we can run multiple GBM simulations and plot the volatility and return that they exhibit. As our models produce daily returns, but we are providing annualised values for our returns and volatility, we need to convert them using the following formulas (remember we're using log returns, so we simply multiply our returns for \mu):

\mu_A = \mu_d . 252

\sigma_A = \sigma_d .\sqrt{252}

We run 10,000 simulations using the below function.

import math

import matplotlib.pyplot as plt
import numpy as np


def run_multiple_simulations(simulation_count, periods, start_price, mu,
                             sigma, days_per_period):
    np.random.seed(10)
    annualised_days = 252.0
    sigmas = []
    mus = []
    for i in range(0, simulation_count):
        prices = gbm.generate_gbm_prices(periods, start_price, mu, sigma,
                                         days_per_period)
        returns = calculate_log_returns(prices)
        mus.append((1.0+returns.mean())**annualised_days - 1.0)
        sigmas.append(returns.std() * math.sqrt(annualised_days))

    plt.subplot(211)
    plt.hist(mus)
    plt.subplot(212)
    plt.hist(sigmas)
    plt.show()


def lag(data, empty_term=0.):
    lagged = np.roll(data, 1)
    lagged[0] = empty_term
    return lagged


def calculate_log_returns(pnl):
    lagged_pnl = lag(pnl)
    returns = np.log(pnl / lagged_pnl)

    # All values prior to our position opening in pnl will have a
    # value of inf. This is due to division by 0.0
    returns[np.isinf(returns)] = 0.
    # Additionally, any values of 0 / 0 will produce NaN
    returns[np.isnan(returns)] = 0.
    return returns

As we have selected a large value for \sigma in the above simulation, we get a wide ranging return (\mu). However, from the histograms it produces we can see that the values are broadly in line with what we expect.

Finally, as realistic as some of the generated series appear, there are two significant shortfalls with the data produced by these models.

  1. They assume asset returns are normally distributed. There is an abundance of evidence when looking at real asset returns that suggests otherwise. Extreme events happen far more frequently in the markets then a normal distribution would suggest is possible.
  2. Asset prices are not continuous, they exhibit jumps (for instance between the exchange closing price and subsequent open), whereas geometric Brownian motion assumes that they are continuous.

Providing you bear these caveats in mind, geometric Brownian motion is a useful tool to have at your disposal for generating simulated data sets to model different assets. As touched on earlier if you already have real data to work with, there are more appropriate models, but the above should enable you to easily generate test data to play with.

As usual, all code for these examples is available here.

Categories: Python

Tagged as:

conor

6 replies

    1. Thanks Stuart. Nothing planned just yet, but it's good to know the post hit the spot. Great blog btw - nice to see someone else coming from a CS background.

  1. Hi,

    Thank you very much for your excellent article and code. I am doing a project at school which requires me to model stock prices using Geometric Brownian Motion. You save my day.

    Best regards,

Leave a Reply

Your email address will not be published. Required fields are marked *