3

I have written a simple script for modelling stock prices using Geometric Brownian Motion. The time series I am downloading are daily adjusted closing prices. My aim is to be able to change the prediction period and all other variables.

However, I am trying to understand the price prediction differences between calculating the mu (average returns) and sigma (variance) using a linear or standard methods versus using a log approach. The log approach consistently generates a higher predicted stock price. My code is below.

I have scavenged the internet and read whatever I could find. There are several helpful articles on this forum as well, such as here and here. But nothing really covers my question.

My question is, which approach is the most appropriate?

(I am using Python 3.)

from math import log, e
import matplotlib.pyplot as plt
from pandas_datareader import data
from datetime import date, timedelta
import datetime

stock = 'AAPL' # Enter the name of the stock start = '2015/1/1' apple = data.DataReader(stock, 'yahoo', start)

This is the 'normal' way of calculating mu and sigma

close = apple[:]['Adj Close'] mu = (close[-1]/close[1])** (252.0/len(close)) - 1 sigma = (close/close.shift(1)-1)[1:].std()*np.sqrt(252)

This is the 'log' way of calculating mu and sigma

apple['log_price'] = np.log(apple['Adj Close']) apple['log_return'] = apple['log_price'].diff() mu = apple.log_return.sum() -1 sigma = np.std(apple.log_price)

s0 = close[-1] T = 18/12 delta_t = 0.001 num_reps = 1000 steps = T/delta_t plt.figure(figsize=(15,10)) closing_prices = [] for j in range(num_reps): price_path = [s0] st = s0 for i in range(int(steps)): drift = (mu - 0.5 * sigma2) * delta_t diffusion = sigma * np.sqrt(delta_t) * np.random.normal(0, 1) st = st*e(drift + diffusion) price_path.append(st) closing_prices.append(price_path[-1]) plt.plot(price_path) plt.ylabel('stock price',fontsize=15) plt.xlabel('steps',fontsize=15) plt.axhline(y = s0, color = 'r', linestyle = '-') # print latest price TW plt.show()

mean_end_price = round(np.mean(closing_prices),2) print("Expected price in 12 months: $", str(mean_end_price))

twhale
  • 331
  • 1
  • 9
  • "The log approach consistently generates a higher predicted stock price" because your log drift is higher than your normal drift due to the way you calculate mu for log vs normal which is pointed out in the answer below. – amdopt Jun 25 '20 at 18:56

1 Answers1

5

The drift in your code is:

drift = (mu - 0.5 * sigma**2) * delta_t

So I assume you are using the Geometric Brownian Motion to simulate your stock price, not just plain Brownian motion. Therefore your model is Lognormal, not Normal. Also, I assume that the time series that you're downloading is daily closing prices.

The solution to the GBM model is the following (below $\delta t:=(t_i)-(t_{i-1})$ & $Z\sim~N(0,1)$):

$$ ln(S_{t_i}) - ln(S_{t_{i-1}}) = (\mu - 0.5\sigma^2)\delta t+\sigma\sqrt{\delta t}Z $$

Note that the log-returns above are distributed normally:

$$ln(S_{t_i}) - ln(S_{t_{i-1}}) \sim N\left(\tilde{\mu} := (\mu - 0.5\sigma^2)\delta t;\tilde{\sigma}:=\sqrt{\delta t}\sigma\right)$$

If we're using daily time series, then $\delta t = \frac{1}{260}$.

If you wanna calibrate the above model based on historical daily data, your task is to calibrate $\tilde{\mu}$ and $\tilde{\sigma}$:

$$(i): \tilde{\mu}=\frac{1}{n}\sum_{i=1}^{n} ln\left( \frac{S_{t_i}}{S_{t_{i-1}}}\right)$$

$$ (ii): \tilde{\sigma}^2 = \frac{1}{n-1} \sum_{i=1}^{n} \left( \left[ ln\left( \frac{S_{t_i}}{S_{t_{i-1}}}\right) - \tilde{\mu} \right]^2\right) $$

Note that in (i) above, you computed $\tilde{\mu}$ and NOT $\mu$. In your simulation, you need $\mu$, so first do the following:

$$ \sigma = \tilde{\sigma}*\frac{1}{\sqrt{\delta t}}=\tilde{\sigma}*\sqrt{260} $$

$$\mu = \frac{1}{\delta t}*\tilde{\mu}+0.5{\sigma}^2=260*\tilde{\mu}+0.5{\sigma}^2$$

That is the $\mu$ you should use in your simulation.

The next point is: why do you have the following line in your code?

mu = apple.log_return.sum() -1

Why subtract the 1? If your log-returns are daily, to get $\mu$, your code should read:

mu = apple.log_return.sum()/apple.log_return.count()
mu = mu*260 + 0.5*apple.log_return.var()*sqrt(260)

To get the $\mu$ in line with the formulas I described above.

If you are using GBM to simulate your stock price, you should not be using regular returns to calibrate your model: so what you call "normal" way is the wrong way to calibrate your model.

Ps: if you have daily time series to start with, you can choose one day to be your unit of time if you want to simulate with daily granularity. That will make your task significantly easier, because you can work with $\tilde{\sigma}$ and $\tilde{\mu}$ directly, rather than having to annualize them: i.e. to convert them into $\sigma$ and $\mu$.

Jan Stuller
  • 6,118
  • 2
  • 17
  • 59
  • Thank you very much for this elaborate comment. Yes, I use Geometric Brownian Motion and daily closing prices (have updated the question, thanks). If I understand your answer correctly: (1) delta_t = 0.001 becomes delta_t = 1/260 (2) use your code for mu. Is that right? (P.S. I think sqrt in your code should be np.sqrt). – twhale Jun 25 '20 at 19:17
  • (1) Yes, (2) Yes (correct, should be np.sqrt()). Ps: why do you have T=18/12? Is your time series length 18 months? – Jan Stuller Jun 25 '20 at 19:32
  • Thanks! T=18/12 because I want to predict the price 1.5 years from now. Are you using 1/260 because there are around 260 trading days in a year? – twhale Jun 25 '20 at 19:35
  • Perhaps I should use delta_t = 1 / T (with T expressed in days). – twhale Jun 25 '20 at 19:47
  • Yes I assume 260 trading days in a given year. Your $\delta t$ should be 1\260, and if you want to simulate for 1.5 years, you should have 390 time steps. Alternatively, your $\delta t$ can be equal to 1, and then your simulation parameters can simply be $\tilde{\mu}$ instead of $\mu$ and $\tilde{\sigma}$ instead of $\sigma$. You then still simulate over 390 time steps. – Jan Stuller Jun 25 '20 at 20:09
  • I am not sure I understand how to properly implement the difference between tilde_mu and mu in code in the way you describe. – twhale Jun 25 '20 at 20:18
  • It's about choosing what your unit of time is. Normally, everything in the GBM model is annualized and you assume that your unit of time is 1-year. Normally people start with the daily time-series: when you compute the average and the standard deviation of the daily log-returns, you compute the "tilde" terms. If your unit of time is 1-year, you need to "annualize" them using the formulas I provided and turn them into mu and sigma without the tilde. You can be smart though and keep your non-annualized daily parameters, and then your unit of time becomes 1-day: then $\delta t$ is just one. – Jan Stuller Jun 25 '20 at 20:25
  • What I find strange by the way is that the predicted share price s(t) mean_end_price is consistently much lower than I would expect when multiplying s(0) * (1+mu). For example for Apple: predicted price s(t) mean_end_price = $434.54 Whereas multiplying the current price s(0) by the average return (mu) gives = $ 362.73 * (1 + 0.3457) = $ 488.13 – twhale Jun 25 '20 at 20:27
  • To sum it up: if you chose your unit of time = 1 day, keep the tilde parameters. Then your sigma and mean correspond to daily moves. If you set $\delta t$ to "1" and keep daily sigma and mu (i.e. the tilde terms), then each loop in your code will evolve your model by one day. You still need 390 days to get to 1.5 years in the future. If you annualize your parameters, then your sigma and mean correspond to 1 year. That means that if you want to simulate daily evolution, you need your $\delta t$ to be 1/260, because otherwise your mean and sigma (i.e. volatility) would be too large. – Jan Stuller Jun 25 '20 at 20:29
  • Alright, okay got it now, thanks! – twhale Jun 25 '20 at 20:31
  • Re lower share price coming out of the simulation: the GBM model is log-normal. If you plot the simulation results over time (choose a percentile), the shape you get is a "cone". So it grows at a different pace than if you simply multiply by "1 + mu". – Jan Stuller Jun 25 '20 at 20:31
  • Oh yes, of course... – twhale Jun 25 '20 at 20:37