- Calculate the cumulative total price return for each of the 60
stocks as well as the TSX Composite Index from the beginning of the
period to the end. - Calculate the daily standard deviation of returns
for each of the 60 stocks as well as the TSX Composite Index (annualize
this number using the √N rule). - Create a scatterplot in R plotting the
standard deviation vs. the return for the year to date period for each
of the 61 time series, with standard deviation on the x-axis.
Use a
different point style for the TSX Composite Index to highlight the index
vs. the individual stocks.
tsx_members <- read.csv("a1_tsx60memb.csv")
tsx_prices <- read.csv("a1_tsx60membpx.csv")
tsx_composite <- read.csv("a1_sptsxpx.csv")%>%
rename(Date = Dates, Price=S.P.TSX.Composite.Index.Level)
# Reshape the data to a long format
tsx_prices_long <- tsx_prices %>%
pivot_longer(cols = -Date, names_to = "Ticker", values_to = "Price")
tsx_prices_long$Date <- as.Date(tsx_prices_long$Date)
tsx_composite$Date <- as.Date(tsx_composite$Date)
# cumulative returns
cumulative_returns <- tsx_prices_long %>%
group_by(Ticker) %>%
summarize(cumulative_return = (last(Price) - first(Price)) / first(Price) * 100)
cumulative_returns %>%
kbl(caption = "Cumulative Returns of S&P/TSX 60 Stocks") %>%
kable_styling(full_width = F, position = "center", bootstrap_options = c("striped", "hover")) %>%
scroll_box(height = "300px") | Ticker | cumulative_return |
|---|---|
| ABX | -29.7738033 |
| AEM | -23.0848153 |
| ARX | -9.6542727 |
| ATD.B | -4.3185299 |
| BAM.A | 3.4700315 |
| BB | -7.5233023 |
| BBD.B | 45.6081081 |
| BCE | -10.9717344 |
| BHC | 10.6461087 |
| BMO | 6.5862894 |
| BNS | -7.3024055 |
| CCL.B | 9.6442274 |
| CCO | 11.9735756 |
| CM | -0.0898619 |
| CNQ | -2.5797989 |
| CNR | 11.3926480 |
| CP | 19.7182484 |
| CPG | -20.0598802 |
| CSU | 30.9432324 |
| CTC.A | -0.5663825 |
| CVE | -0.6568144 |
| DOL | -4.4839326 |
| ECA | 0.6406523 |
| EMA | -12.5026489 |
| ENB | -11.4228856 |
| FM | -9.3576966 |
| FNV | -16.0849009 |
| FTS | -6.1717549 |
| G | -14.5972138 |
| GIB.A | 25.4795724 |
| GIL | -4.8490846 |
| HSE | 16.7748918 |
| IMO | 3.0141844 |
| IPL | -8.2280903 |
| K | -30.3571429 |
| L | -1.5891529 |
| MFC | -8.5057471 |
| MG | -1.1748252 |
| MRU | 1.1636544 |
| NA. | 4.5964126 |
| NTR | 6.2898551 |
| OTEX | 17.4391923 |
| POW | -8.9068826 |
| PPL | -2.6678329 |
| QSR | -4.1858679 |
| RCI.B | 7.1632330 |
| RY | 0.6896552 |
| SAP | -10.9824014 |
| SJR.B | -7.8431373 |
| SLF | 0.7575758 |
| SNC | -7.5013207 |
| SU | 14.7127909 |
| T | 2.1101498 |
| TD | 6.7019400 |
| TECK.B | -12.7817319 |
| TRI | 6.0091324 |
| TRP | -9.9189627 |
| WCN | 18.2575931 |
| WN | -6.7694001 |
| WPM | -19.7991392 |
# daily returns and annualized standard deviation
daily_sd <- tsx_prices_long %>%
arrange(Date) %>%
group_by(Ticker) %>%
mutate(daily_return = (Price - lag(Price)) / lag(Price)*100) %>%
summarize(annualized_sd = sd(daily_return, na.rm = TRUE) * sqrt(252))
# Combine cumulative returns and standard deviations
tsx_data <- cumulative_returns %>%
left_join(daily_sd, by = "Ticker")
# Now add the TSX Composite Index
tsx_composite_return <- (last(tsx_composite$Price) - first(tsx_composite$Price)) / first(tsx_composite$Price) * 100
# standard deviation for the TSX Composite Index
tsx_composite_sd <- tsx_composite %>%
arrange(Date) %>%
mutate(daily_return = (Price - lag(Price)) / lag(Price)*100) %>%
summarize(annualized_sd = sd(daily_return, na.rm = TRUE) * sqrt(252))
# Combine stock data with the TSX Composite Index data
tsx_index_data <- data.frame(
Ticker = "TSX Composite",
cumulative_return = tsx_composite_return,
annualized_sd = tsx_composite_sd$annualized_sd)
tsx_final_data <- bind_rows(tsx_data, tsx_index_data)
tsx_final_data %>%
kbl(caption = "Combined Data of S&P/TSX Stocks and Index") %>%
kable_styling(full_width = F, position = "center", bootstrap_options = c("striped", "hover")) %>%
scroll_box(height = "300px") | Ticker | cumulative_return | annualized_sd |
|---|---|---|
| ABX | -29.7738033 | 25.121116 |
| AEM | -23.0848153 | 26.099193 |
| ARX | -9.6542727 | 30.004314 |
| ATD.B | -4.3185299 | 21.953643 |
| BAM.A | 3.4700315 | 17.476855 |
| BB | -7.5233023 | 38.498023 |
| BBD.B | 45.6081081 | 40.012873 |
| BCE | -10.9717344 | 10.477512 |
| BHC | 10.6461087 | 45.848530 |
| BMO | 6.5862894 | 9.885924 |
| BNS | -7.3024055 | 10.987753 |
| CCL.B | 9.6442274 | 20.766257 |
| CCO | 11.9735756 | 31.718613 |
| CM | -0.0898619 | 10.088510 |
| CNQ | -2.5797989 | 26.151464 |
| CNR | 11.3926480 | 17.256581 |
| CP | 19.7182484 | 18.328382 |
| CPG | -20.0598802 | 41.040571 |
| CSU | 30.9432324 | 26.380750 |
| CTC.A | -0.5663825 | 20.694332 |
| CVE | -0.6568144 | 40.416859 |
| DOL | -4.4839326 | 21.565915 |
| ECA | 0.6406523 | 36.408975 |
| EMA | -12.5026489 | 15.861490 |
| ENB | -11.4228856 | 25.356114 |
| FM | -9.3576966 | 50.754969 |
| FNV | -16.0849009 | 20.653859 |
| FTS | -6.1717549 | 13.577737 |
| G | -14.5972138 | 28.475413 |
| GIB.A | 25.4795724 | 13.909277 |
| GIL | -4.8490846 | 33.880666 |
| HSE | 16.7748918 | 28.499753 |
| IMO | 3.0141844 | 21.492078 |
| IPL | -8.2280903 | 18.130873 |
| K | -30.3571429 | 37.514344 |
| L | -1.5891529 | 14.510301 |
| MFC | -8.5057471 | 15.259372 |
| MG | -1.1748252 | 28.181916 |
| MRU | 1.1636544 | 16.838633 |
| NA. | 4.5964126 | 11.118090 |
| NTR | 6.2898551 | 25.303495 |
| OTEX | 17.4391923 | 24.277977 |
| POW | -8.9068826 | 11.924285 |
| PPL | -2.6678329 | 19.165443 |
| QSR | -4.1858679 | 21.227851 |
| RCI.B | 7.1632330 | 14.668132 |
| RY | 0.6896552 | 10.718949 |
| SAP | -10.9824014 | 17.966009 |
| SJR.B | -7.8431373 | 18.132636 |
| SLF | 0.7575758 | 13.977093 |
| SNC | -7.5013207 | 16.997317 |
| SU | 14.7127909 | 21.693145 |
| T | 2.1101498 | 9.975501 |
| TD | 6.7019400 | 10.069219 |
| TECK.B | -12.7817319 | 34.493222 |
| TRI | 6.0091324 | 18.867391 |
| TRP | -9.9189627 | 20.351396 |
| WCN | 18.2575931 | 15.430204 |
| WN | -6.7694001 | 13.589204 |
| WPM | -19.7991392 | 22.626394 |
| TSX Composite | -0.2888414 | 12.893525 |
ggplot(tsx_final_data, aes(x = annualized_sd, y = cumulative_return, label = Ticker)) +
geom_point(aes(shape = ifelse(Ticker == "TSX Composite", "Index", "Stock"),
color = ifelse(Ticker == "TSX Composite", "Index", "Stock")), size = 3) +
scale_shape_manual(values = c("Index" = 16, "Stock" = 17)) +
scale_color_manual(values = c("Index" = "#4584b6", "Stock" = "#ffde57")) +
labs(title = "Standard Deviation vs. Return for TSX Stocks and Composite Index",
x = "Annualized Standard Deviation",
y = "Cumulative Return (%)",
color = "Legend",
shape = "Legend") +
theme_minimal(base_size = 15) +
geom_text(aes(label = Ticker),
hjust = 0.5,
vjust = -1,
size = 3,
check_overlap = TRUE) +
theme(legend.position = "top",
legend.box = "horizontal")
In your a1_tsx60memb data frame, create a new column
that assigns securities to a group based on market capitalization.
Create three groups - one for securities with less than $20B in market
cap, one for securities with a market cap between $20B and $50B, and one
for securities with a market cap above $50B. You may name the groups
anything, but “Small”, “Mid”, and “Large” seems reasonable.
Use the
aggregate() function in base R (or an equivalent function from
the CRAN package data.table) to calculate the total weight of securities
in each of your three market cap groups. Plot it in a bar chart.
Create another bar chart but use the GICS Sectors as your grouping
variable.
tsx_members <- tsx_members %>%
mutate(market_cap_group = case_when(
Market.Cap < 20000000000 ~ "Small",
Market.Cap >= 20000000000 & Market.Cap <= 50000000000 ~ "Mid",
Market.Cap > 50000000000 ~ "Large"
))
# total weight for each market cap group
market_cap_weight <- aggregate(Weight ~ market_cap_group, data = tsx_members, sum)
# bar chart
p <- ggplot(market_cap_weight, aes(x = market_cap_group, y = Weight)) +
geom_bar(stat = "identity", fill = "#4584b6") +
labs(title = "Total Weight by Market Cap Group", x = "Market Cap Group", y = "Total Weight") +
theme_minimal()+
theme(axis.text.x = element_blank())
interactive_plot <- ggplotly(p)%>%
layout(hoverlabel = list(bgcolor = "#ffde57", font = list(color = "black")))
interactive_plot# total weight for each GICS Sector
sector_weight <- aggregate(Weight ~ GICS.Sector, data = tsx_members, sum)
# a bar chart for GICS sectors
p_weight <- ggplot(sector_weight, aes(x = GICS.Sector, y = Weight)) +
geom_bar(stat = "identity", fill = "#ffde57") +
labs(title = "Total Weight by GICS Sector", x = "GICS Sector", y = "Total Weight") +
theme_minimal() +
theme(axis.text.x = element_blank())
interactive_plot_weight <- ggplotly(p_weight)%>%
layout(hoverlabel = list(bgcolor = "#4584b6", font = list(color = "white")))
interactive_plot_weight
The beta of a security is a risk measure that gauges the degree
of market sensitivity of that security (high beta stocks have higher
market risk, and vice versa) and is generally the slope of a regression
line for the returns of that security regressed on the returns on the
benchmark index.
For the securities in this sample, calculate the
market beta using the TSX Composite Index as the benchmark (you will
need to run 60 linear regressions and extract the slope coefficient from
each). Use either a loop or preferably an apply() function to accomplish
this.
Plot a barplot of the betas of the 60 stocks in your sample
over the sample period. Also plot the R-Squared of the 60 regressions in
a separate barplot. How effective is a one-factor model such as this in
describing the returns?
# daily returns for stocks
tsx_prices_long <- tsx_prices_long %>%
arrange(Date) %>%
group_by(Ticker) %>%
mutate(daily_return = (Price - lag(Price)) / lag(Price) * 100) %>%
filter(!is.na(daily_return))
# daily returns for TSX Composite Index
tsx_composite <- tsx_composite %>%
arrange(Date) %>%
mutate(daily_return = (Price - lag(Price)) / lag(Price) * 100) %>%
filter(!is.na(daily_return))
# A function to compute beta and R-squared for each stock
calculate_beta <- function(stock_returns, benchmark_returns) {
model <- lm(stock_returns ~ benchmark_returns)
beta <- coef(model)[2]
r_squared <- summary(model)$r.squared
return(c(beta = beta, r_squared = r_squared))}
# Data frame for beta and R-squared
beta_results <- tsx_prices_long %>%
group_by(Ticker) %>%
summarize(
beta_r_squared = list(calculate_beta(daily_return, tsx_composite$daily_return))
) %>%
unnest_wider(beta_r_squared)
# Rename the columns for clarity
colnames(beta_results) <- c("Ticker", "Beta", "R_Squared")
# Plot the Betas
p_beta <- ggplot(beta_results, aes(x = reorder(Ticker, Beta), y = Beta)) +
geom_bar(stat = "identity", fill = "#4584b6") +
labs(title = "Betas of the 60 Stocks", x = "Stock Ticker", y = "Beta") +
theme_minimal() +
theme(axis.text.x = element_blank())
interactive_plot_beta <- ggplotly(p_beta)%>%
layout(hoverlabel = list(bgcolor = "#ffde57", font = list(color = "black")))
interactive_plot_beta# Plot the R-squared values
p_r_squared <- ggplot(beta_results, aes(x = reorder(Ticker, R_Squared), y = R_Squared)) +
geom_bar(stat = "identity", fill = "#ffde57") +
labs(title = "R-Squared of the 60 Regressions", x = "Stock Ticker", y = "R-Squared") +
theme_minimal() +
theme(axis.text.x = element_blank())
interactive_r_squared <- ggplotly(p_r_squared)%>%
layout(hoverlabel = list(bgcolor = "#4584b6", font = list(color = "white")))
interactive_r_squared
Calculate the covariance matrix of the 60 securities in the
TSX 60 Index, and use this data to estimate the volatility of:
•
The equally weighted portfolio of all 60 securities, • The the TSX
Weighted portfolio (use the weights provided in the a1_tsx60memb.csv
file) • A portfolio of all 60 securities where the weight is inversely
proportional to the beta you estimated in Problem 3 according to the
following weight function: \[\omega_i=\dfrac{\dfrac{1}{\beta_i}}{\sum_{i=1}^{N}\dfrac{1}{\beta_i}}\]
• An equally weighted portfolio containing only securities in the
Financials GICS sector. • Equally weighted portfolios for each of your
three market capitalization groups. Create a data frame containing the
volatility for each of these 7 portfolios and plot them together in a
barchart.
# a pivot table of daily returns
tsx_returns_wide <- tsx_prices_long %>%
select(Date, Ticker, daily_return) %>%
pivot_wider(names_from = Ticker, values_from = daily_return)
# covariance matrix for the stocks (excluding the Date)
cov_matrix <- cov(tsx_returns_wide[, -1], use = "pairwise.complete.obs")# Equally weighted portfolio
equal_weights <- rep(1/60, 60)
equal_portfolio_variance <- t(equal_weights) %*% cov_matrix %*% equal_weights
equal_portfolio_sd <- sqrt(equal_portfolio_variance)# Extract the weights from the members data
tsx_weights <- tsx_members$Weight / 100
tsx_portfolio_variance <- t(tsx_weights) %*% cov_matrix %*% tsx_weights
tsx_portfolio_sd <- sqrt(tsx_portfolio_variance) # weights inversely proportional to beta
inverse_beta_weights <- 1 / beta_results$Beta
inverse_beta_weights <- inverse_beta_weights / sum(inverse_beta_weights)
# Inversely proportional to beta portfolio
inv_beta_portfolio_variance <- t(inverse_beta_weights) %*% cov_matrix %*% inverse_beta_weights
inv_beta_portfolio_sd <- sqrt(inv_beta_portfolio_variance) # Filter for Financials sector
financials_tickers <- tsx_members %>%
filter(GICS.Sector == "Financials") %>%
pull(Ticker)
financials_tickers <- na.omit(financials_tickers)
# Subset the covariance matrix for Financials tickers
financials_cov_matrix <- cov_matrix[financials_tickers, financials_tickers]
# Equal weights for Financials sector (after removing NA)
financials_weights <- rep(1/length(financials_tickers), length(financials_tickers))
# Financials portfolio variance
financials_portfolio_variance <- t(financials_weights) %*% financials_cov_matrix %*% financials_weights
financials_portfolio_sd <- sqrt(financials_portfolio_variance)# Small Cap Portfolio
small_cap_tickers <- tsx_members %>%
filter(market_cap_group == "Small") %>%
pull(Ticker)
# Remove the invalid ticker from small_cap_tickers
small_cap_tickers <- small_cap_tickers[small_cap_tickers %in% rownames(cov_matrix)]
small_cap_weights <- rep(1/length(small_cap_tickers), length(small_cap_tickers))
small_cap_cov_matrix <- cov_matrix[small_cap_tickers, small_cap_tickers]
small_cap_portfolio_variance <- t(small_cap_weights) %*% small_cap_cov_matrix %*% small_cap_weights
small_cap_portfolio_sd <- sqrt(small_cap_portfolio_variance)
# Mid Cap Portfolio
mid_cap_tickers <- tsx_members %>%
filter(market_cap_group == "Mid") %>%
pull(Ticker)
mid_cap_weights <- rep(1/length(mid_cap_tickers), length(mid_cap_tickers))
# Remove the invalid tickers from mid_cap_tickers
mid_cap_tickers <- mid_cap_tickers[mid_cap_tickers %in% rownames(cov_matrix)]
mid_cap_cov_matrix <- cov_matrix[mid_cap_tickers, mid_cap_tickers]
mid_cap_weights <- rep(1 / length(mid_cap_tickers), length(mid_cap_tickers))
mid_cap_portfolio_variance <- t(mid_cap_weights) %*% mid_cap_cov_matrix %*% mid_cap_weights
mid_cap_portfolio_sd <- sqrt(mid_cap_portfolio_variance)
# Large Cap Portfolio
large_cap_tickers <- tsx_members %>%
filter(market_cap_group == "Large") %>%
pull(Ticker)
large_cap_weights <- rep(1/length(large_cap_tickers), length(large_cap_tickers))
large_cap_cov_matrix <- cov_matrix[large_cap_tickers, large_cap_tickers]
large_cap_portfolio_variance <- t(large_cap_weights) %*% large_cap_cov_matrix %*% large_cap_weights
large_cap_portfolio_sd <- sqrt(large_cap_portfolio_variance)# a data frame with the volatilities
volatility_data <- data.frame(
Portfolio = c("Equally Weighted", "TSX Weighted", "Inverse Beta Weighted",
"Financials Sector", "Small Cap", "Mid Cap", "Large Cap"),
Volatility = c(equal_portfolio_sd, tsx_portfolio_sd, inv_beta_portfolio_sd,
financials_portfolio_sd, small_cap_portfolio_sd, mid_cap_portfolio_sd, large_cap_portfolio_sd))# ggplot bar chart
p <- ggplot(volatility_data, aes(x = Portfolio, y = Volatility)) +
geom_bar(stat = "identity", fill = "#4584b6") +
labs(title = "Volatility of Different Portfolios", x = "Portfolio", y = "Volatility (Standard Deviation)") +
theme_minimal() +
theme(axis.text.x = element_blank())
interactive_plot <- ggplotly(p) %>%
layout(hoverlabel = list(bgcolor = "#ffde57", font = list(color = "black")))
interactive_plot
Install the package quantmod and download prices of the
S&P 500 using the function getSymbols() (ticker“ˆGSPC”).
Calculate the daily returns and compare against a normal distribution
using a qqnorm() and qqline() plot.
# Download prices of the S&P 500 using getSymbols()
getSymbols("^GSPC", src = "yahoo", from = "2010-01-01", to = Sys.Date())## [1] "GSPC"
# daily returns- Using Adjusted Close prices to account for dividends and stock splits
sp500_prices <- Ad(GSPC)
daily_returns <- diff(log(sp500_prices))
daily_returns <- na.omit(daily_returns)
# a Q-Q plot to compare against a normal distribution
qqnorm(daily_returns, main = "Q-Q Plot of Daily Returns of S&P 500")
qqline(daily_returns, col = "red") Md Mahmudul
Hasan
MQIM 3760573
Faculty of Management
University of New Brunswick
mahmudul.hasan@unb.ca