# Geometric brownian motion small timesteps high volatility

I'm trying to generate some sample geometric brownian motion paths for an asset which is traded 24/7 without interruption and is highly volatile (upwards to 150% implied volatility on options markets).

I'm currently using this script: https://stackoverflow.com/a/13203189/5433929

I would like to generate sample paths with arbitrary resolution like a minute, an hour, a day, or anything between these. My understanding is that if I assume an annualized volatility of 150% for example, and I want to generate an hourly GBM using the script I linked, I will need to convert this 150% in to an hourly volatility, which is done by taking 1.5/sqrt(number of hours in a year). However when I do this, the sample paths generated over for example a 30 days time horizon are not realistic at all. Depending on the drift parameter, it eithers goes almost in a straight line up, varies with the typical GBM features within an extremely tight range of +/- 0.1% not at all representative of an asset with such high volatility.

What am I doing wrong here?

EDIT:

Due to demand from @Kermittfrog, I'm pasting in my specific script.

def generateGBM(T, mu, sigma, S0, dt):
'''
Generate a geometric brownian motion time series. Shamelessly copy pasted from here: https://stackoverflow.com/a/13203189

Params:

T: time horizon
mu: drift
sigma: percentage volatility
S0: initial price
dt: size of time steps

Returns:

t: time array
S: time series
'''
N = round(T/dt)
t = np.linspace(0, T, N)
W = np.random.standard_normal(size = N)
W = np.cumsum(W)*np.sqrt(dt) ### standard brownian motion ###
X = (mu-0.5*sigma**2)*t + sigma*W
S = S0*np.exp(X) ### geometric brownian motion ###
return t, S


I'm doing this with the following parameters:

#Initial reference market price
INITIAL_PRICE = 1100
#The desired annualized volatility
ANNUALIZED_VOL = 1.5
#The annual drift of the geometric brownian motion
DRIFT = 0.04
#The time horizon in days
TIME_HORIZON = 30
#The size of the time steps in days (20 minutes here)
TIME_STEPS_SIZE = 0.0138889


and scaling down annualized volatility and drift by dividing by the square root of the number of timesteps there would be in a year

N_timesteps = 365/dt
sigma_timestep = sigma/np.sqrt(N_timesteps)
mu = DRIFT/np.sqrt(N_timesteps)


The result is plots that don't really look anything like what one would expect from a 150% volatility asset over a period of a month, unless my expectations are really wrong

• You keep the vol at 150% sigma=1.50, but you set the time increment parameter to something like dt = 1 / 365 / 24 or whatever idea of #trading days / year and #trading hours / day you have. Then, your hourly drift is m = (mu - 0.5 * sigma**2)*dt, and your hourly random increment is e = sigma * sqrt(dt) * np.random.randn(). Thus, the next hour's simulated price given the price right now is S * exp(m + e). You can of course neatly vectorise all that. HTH? Jul 15, 2021 at 13:50
• Hey @Kermittfrog. For what it's worth I don't have a prescribed drift, I'm just using arbitrary drifts and volatility, with the only constraint that the annualized volatility should be high like > 100%. So you're saying that in the GBM formula, sigma is in fact an annualized volatility already? When I just pass sigma as the the desired annualized vol of eg 150%, I just get a sample path that goes to zero extremely quickly (within 10-15 days, which definitely doesn't seem to be a realistic), which doesn't correspond to any realistic price path so something must be wrong right? Jul 15, 2021 at 18:34
• Without knowing your specific implementation it is hard to argue. In most of such cases, it is a typo in the code or a misconception of the model that lead to strange results. Could you paste your code? Jul 15, 2021 at 20:11
• @Kermittfrog The implementation is exactly the one I linked to in my original post, I'm just changing the inputs but I can edit my question to make it self contained Jul 16, 2021 at 10:44
• @Kermittfrog added my code in there Jul 16, 2021 at 10:56

Given your code, the following will yield what you are after:

t,S = generateGBM(TIME_HORIZON/365, DRIFT, ANNUALIZED_VOL, INITIAL_PRICE, 1/365/24/3)


As all inputs are annualized, you must also think in units of year fractions: The time horizon is 30 days over 365 days, and the time step size, being 20 minutes, is one year over 365 * 24 * 3 (there are three 20-minutes intervals in an hour).

If, on the other hand, you want to work with daily inputs, you can run:

t,S = generateGBM(TIME_HORIZON, DAILY_DRIFT, DAILY_VOL, INITIAL_PRICE, 1/24/3)


and you can transform annual vol and annual drift into daily counterparts as

DAILY_VOL   = np.sqrt(1/365) * ANNUALIZED_VOL
DAILY_DRIFT =         1/365  * DRIFT


HTH?

• This solves it entirely, thanks a lot for your help. Jul 28, 2021 at 15:59