To model the price distribution of a stock, Black Scholes assumes a random walk. For our purpose, the equation detailing the process is not important. It’s sufficient to note that the process has a deterministic component and a random component.
- Drift (deterministic)
This is the expected continuously compounded return of the stock. You might wonder where we get the “expected return”. Like CAPM says the expected return is a function of how volatile the stock is. The more risky, the higher expected return.
The key insight in the arbitrage pricing framework is that in the context of replication, the drift of the stock is assumed to be the risk-free rate. This might sound crazy but the key qualifier is “in the context of replication”.
When you are trying to price an option, you don’t actually care what the expected return is of the stock because you are basing the price on an offsetting strategy that sterilizes the effect of the stock price. If the offsetting strategy matches the profit of the call option, then have constructed a long/short portfolio without risk. The payoff of that portfolio only needs to be discounted by the risk-free rate.
In this replication context, the drift is simply the annualized risk-free rate minus the volatility drag
- Volatility (random) While the drift tells us the expected return of the stock, we know that the actual return will be a randomly sampled draw from the volatility distribution centered around a mean volatility, σ. We assume a lognormal distribution because:
- the returns are compounded
- stock prices are bounded by zero
Simulating Brownian Motion
In words:
logreturns ~ a normally distributed random variable with mean = drift and standard deviation = volatility scaled to root(time)
The implementation equation:
Monte Carlo simulation: Brownian motion
This video by Bionic Turtle shows how to implement a Brownian Motion diffusion process
My python script to generate daily logreturns:
#function to create brownian motion lognormal diffusion #random logreturn = drift + volatility * random number #random logreturn = (r - 0.5 * sigma^2) * dt + sigma * sqrt(dt) * z def brownian_motion_log_returns(number_of_periods, mu, sigma): dt = 1/252 sqrt_dt = np.sqrt(dt) log_returns = np.zeros(number_of_periods) log_returns[0] = np.random.normal(0, sqrt_dt) for i in range(1, number_of_periods): log_returns[i] = (mu - 0.5 * sigma ** 2) * dt + sigma * sqrt_dt * np.random.normal() return log_returns
With a stream of logreturns, it is simple to generate prices:
def brownian_motion_stock_price(day_quantity, mu, annual_vol, spot_price): #returns a dataframe with logreturn and price columns df = pd.DataFrame() log_returns = brownian_motion_log_returns(day_quantity, mu, annual_vol) price_series = [spot_price] for i in range(1, day_quantity): price_series.append(price_series[i - 1] * np.exp(log_returns[i])) #assign log returns and price to dataframe df['log_return'] = log_returns df['price'] = price_series return df
The Monte Carlo just requires generating many price paths:
#create a function that creates n price paths def monte_carlo_simulation(num_paths, day_quantity, mu, annual_vol, spot_price): #returns a dataframe with monte carlo price paths paths_df = pd.DataFrame() for i in range(num_paths): paths_df['path_' + str(i)] = brownian_motion_stock_price(day_quantity, mu, annual_vol, spot_price)['price'] return paths_df