In this assignment we looked at the assumption of normal distributed return of stock indices. We also tried to fit the binomial model and Black-Scholes model to real data and compare with market data. The B&S model gave better results than the binomial model.
Data was downloaded from the Tel Aviv Stock Exchange website, 3 months of daily prices for the TA 35 index and one month of daily prices for a call option on the index with a strike price of 1400. The risk free rate was taken from a government bond.
Unfortunately the Yahoo finance API isn’t working so I had to manually download the data which is attached.
A model that could use to model the behavior of the index over time could be a geometrical Brownian motion. Here we assume that the natural log of the returns is normally distributed. \[\ln\Big(\frac{S_{t}}{S_{0}}\Big)\sim\mathcal{N}(\mu t,\sigma^{2}t)\] Where \(\mu\) is the drift parameter and \(\sigma\) is the diffusion parameter. This follows the notion of Brownian motion that independent increments are normally distributed with parameters that a proportional to the size of the increment (time in our case).
This is a realistic model since it models percentage changes and not absolute changes in price which makes sense with how market prices work. The expected returns of GBM are independent of the value of the process (stock price), which agrees with what we would expect in reality. A GBM process only assumes positive values, just like real stock prices.
However, GBM is not a completely realistic model, in particular it falls short of reality in the following points:
The continuous returns were calculated as the following \[r_{t}=\ln\Big(\frac{P_{t}}{P_{t-1}}\Big)=\ln(P_{t})-\ln(P_{t-1})\]
# loading data download from TASE
ta35 <- read.csv("ta35.csv")
# converting to ts
ta35_ts <- xts(ta35$close, order.by=as.Date(ta35$date, "%d/%m/%Y"))
names(ta35_ts) <- "TA35"
## plotting stock price
dygraph(ta35_ts, main = "TA35 Index Price")%>%dyOptions(axisLineWidth = 1.5, fillGraph = TRUE, drawGrid = TRUE)
We can see that even in a short 3 month time period there is volatility in the index.
# Denote n the number of time periods:
n <- nrow(ta35)
# Compute continuously compounded daily returns
ta35_return <- log(ta35$close[2:n]) - log(ta35$close[1:(n-1)])
## qqplotting
p <- ggplot(data.frame(ta35_return), aes(sample=ta35_return))+stat_qq() + geom_abline(intercept=mean(ta35_return),slope=sd(ta35_return))
ggplotly(p)
A Q-Q plot is a scatterplot created by plotting two sets of quantiles against one another. If both sets of quantiles came from the same distribution, we should see the points forming a line that’s roughly straight. As can be seen the plot above, the continous returns does roughly line up on the straight line.
For the binomial model what we need to know is \(u,d,r,C_{u},C_{d,}E\). we also need for \(d<r<u\) to be true. The annual risk free rate is 0.1%. We don’t need to know the true probability of these changes since we will use the risk free probability measure: \[C=\frac{\tilde{p}\cdot C_{u}+(1-\tilde{p})C_{d}}{1+r},\tilde{p}=\frac{r-d}{u-d}\] Unlike the Black-Scholes Model we don’t need to estimate the volatility here.
I used \[u=\exp(\sigma\cdot(\sqrt{T/n})-1\] and \[d=\exp(-\sigma\cdot(\sqrt{T/n})-1\] in order to approximate the binomial model to the log-normal Black-Scholes model. The volatility is estimated from the data and \(n=1\).
# loading data and calulating time in days
one_month <- read.csv("one_month.csv")
one_month$date <- as.Date(one_month$date, "%d/%m/%Y")
one_month$exp_day <- as.Date("28/7/2017","%d/%m/%Y")
one_month$T <- as.integer(one_month$exp_day-one_month$date)/365
# cont' risk free rate (from annual rate)
rf <- log(1+0.001)
# volatility of returns - this is daily turned to annual
sigma <- sqrt(var(ta35_return)*365)
# function to calculate price under the binomial model
binom_option <- function(E,S,sigma,rf,T)
{
# fixing for term of option
u <- exp(sigma*sqrt(T))-1
d <- exp(-sigma*sqrt(T))-1
r_t <- exp(T*rf)-1
p_tilde <- (r_t-d)/(u-d)
c_u <- max(S*(u+1)-E,0)
c_d <- max(S*(d+1)-E,0)
C <- (p_tilde*c_u + (1-p_tilde)*c_d)/(1+r_t)
#print(c(u1,d1,r_t,p_tilde,c_u,p_tilde*c_u,S))
return(C*100)
}
one_month$binom <- rep(0,nrow(one_month))
for (i in 1:nrow(one_month))
{one_month$binom[i] <- round(binom_option(1400,one_month$s[i],sigma,rf,one_month$T[i]))}
For the Black-Scholes Model we need to calculate the volatility of continous returns and the continous risk free rate. We’ll then use the Black-Scholes formula to get a theoretical price of the options and compare it to the observed market price. I will use the Black-Scholes formula: \[C=S\cdot\mathcal{N}(d_{1})-E\cdot e^{-r_{c}T}\cdot N(d_{2})\] where \[d_{1}=\frac{\ln(S/E)+(r_{c}+\frac{\sigma^{2}}{2})\cdot T}{\sigma\sqrt{T}},d_{2}=d_{1}-\sigma\cdot\sqrt{T}\] Under the following assumptions:
# Function for calculating Black-Scholes Call Option Value
blackscholes <- function(S, X, rf, T, sigma) {
d1 <- (log(S/X)+(rf+sigma^2/2)*T)/(sigma*sqrt(T))
d2 <- d1 - sigma * sqrt(T)
return(S*pnorm(d1) - X*exp(-rf*T)*pnorm(d2))
}
| Date | Time | TA35 | C_1400 | BS | Binom | BS.Market.Ratio | Binom.Market.Ratio |
|---|---|---|---|---|---|---|---|
| 2017-06-07 | 0.14 | 1,427 | 3,602 | 3,725 | 3,963 | 103.41% | 110.02% |
| 2017-06-06 | 0.14 | 1,424 | 3,420 | 3,497 | 3,808 | 102.25% | 111.35% |
| 2017-06-05 | 0.15 | 1,427 | 3,830 | 3,765 | 4,017 | 98.30% | 104.88% |
| 2017-06-04 | 0.15 | 1,428 | 4,041 | 3,811 | 4,062 | 94.31% | 100.52% |
| 2017-06-01 | 0.16 | 1,421 | 3,295 | 3,402 | 3,790 | 103.25% | 115.02% |
| 2017-05-29 | 0.16 | 1,419 | 3,565 | 3,339 | 3,770 | 93.66% | 105.75% |
| 2017-05-28 | 0.17 | 1,419 | 3,436 | 3,307 | 3,754 | 96.25% | 109.25% |
| 2017-05-25 | 0.18 | 1,423 | 3,783 | 3,670 | 4,067 | 97.01% | 107.51% |
| 2017-05-24 | 0.18 | 1,419 | 3,408 | 3,374 | 3,844 | 99.00% | 112.79% |
| 2017-05-23 | 0.18 | 1,419 | 3,620 | 3,431 | 3,898 | 94.78% | 107.68% |
| 2017-05-22 | 0.18 | 1,422 | 3,846 | 3,604 | 4,044 | 93.71% | 105.15% |
| 2017-05-21 | 0.19 | 1,422 | 3,286 | 3,678 | 4,112 | 111.93% | 125.14% |
| 2017-05-18 | 0.19 | 1,412 | 3,286 | 3,087 | 3,650 | 93.94% | 111.08% |
| 2017-05-17 | 0.20 | 1,420 | 4,190 | 3,566 | 4,058 | 85.11% | 96.85% |
| 2017-05-16 | 0.20 | 1,433 | 4,750 | 4,464 | 4,750 | 93.98% | 100.00% |
| 2017-05-15 | 0.20 | 1,432 | 4,475 | 4,456 | 4,755 | 99.58% | 106.26% |
| 2017-05-14 | 0.21 | 1,432 | 4,475 | 4,420 | 4,740 | 98.77% | 105.92% |
| 2017-05-11 | 0.21 | 1,423 | 4,128 | 3,841 | 4,328 | 93.05% | 104.84% |
| 2017-05-10 | 0.22 | 1,420 | 4,402 | 3,702 | 4,224 | 84.10% | 95.96% |
| 2017-05-09 | 0.22 | 1,429 | 4,474 | 4,277 | 4,681 | 95.60% | 104.63% |
| 2017-05-08 | 0.22 | 1,424 | 3,930 | 3,952 | 4,440 | 100.56% | 112.98% |
We can see that the binomial model’s results were much less accurate, it is a more simplistic model. Also, I might have estimated errors when estimated the volatility since I only used 3 months - trade off between the bias of using a longer period and variance of using less data.
On the other hand we can see that the Black-Scholes price is fairly close to the market price. All parameters of the model are observed and known except for the volatility. The volatility needs to estimated. I can assume that the volatility expected by investors is different from the one I estimated. The volatility implied by market prices under the Black-Scholes model is called the implied volatility.
I will now try to numerically find the implied volatility that best fits the market prices observed (I assume that it is constant during the month). I numerically found the volatility that fulfills: \[\min_{\tilde{\sigma}}\sum_{i=1}^{n}\Big(\frac{abs[observed_{i}-model(\tilde{\sigma})_{i}]}{n}\Big)\]
# looking for implied volatility
grid <- seq(0.01,0.2,0.001)
stat <- rep(0,length(grid))
for ( i in 1:length(grid))
{
stat[i] <- mean(abs(one_month$c_1400-blackscholes(one_month$s,1400, rf,one_month$T,grid[i])*100))
}
# best solution
imp <- grid[which(stat==min(stat))]
estimated <- sigma
imp
## [1] 0.104
estimated
## [1] 0.09753878
We can see the investors expected a larger volatility than I estimated, interesting.