0
$\begingroup$

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?

Thanks in advance.

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

$\endgroup$
7
  • 2
    $\begingroup$ 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? $\endgroup$ Jul 15, 2021 at 13:50
  • $\begingroup$ 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? $\endgroup$ Jul 15, 2021 at 18:34
  • $\begingroup$ 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? $\endgroup$ Jul 15, 2021 at 20:11
  • $\begingroup$ @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 $\endgroup$ Jul 16, 2021 at 10:44
  • $\begingroup$ @Kermittfrog added my code in there $\endgroup$ Jul 16, 2021 at 10:56

1 Answer 1

1
$\begingroup$

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?

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.