Using CAPM to Evaluate the Performance of Listed Investment Companies with R | by David Harris | Towards Data Science

The return on an investment should compensate an investor for the time value of the capital they have invested, as well as the risk that some, or all, of their investment may be lost. The Capital Asset Pricing Model (CAPM) is widely used in finance as a means of determining the level of compensation an investor should expect to receive from an investment given the level of risk associated with holding that particular asset rather than holding a “risk-free” asset, such as sovereign government bonds.
Idiosyncratic (Unsystematic) risk — Refers to risk that is unique to that particular asset. Idiosyncratic risk can be reduced through diversification and maintaining a well-constructed portfolio (Investopedia 2019a).
Systematic risk — Refers to market-wide risk, which cannot be reduced through portfolio diversification. Although an investor can build a portfolio that limits their exposure to systematic risk, the CAPM theory suggests this will come with a trade-off of the returns they can expect to receive (Investopedia 2019b).
Since it is possible to diversify investments in such a way as to eliminate idiosyncratic risk, the CAPM assumes returns on investments do not compensate an investor for holding this type of risk. Therefore it is essential that an investor diversifies their investments to avoid carrying risk that they will not be compensated for*.
[* how this diversification can be done is beyond the scope of this article, however, in a future article I will discuss Modern Portfolio Theory which enables the development of a portfolio of assets designed to eliminate idiosyncratic risk through asset diversification and obtain an appropriate level of exposure to systematic risk based on personal risk tolerance preferences.]
The CAPM methodology describes the relationship between the expected return of an asset and its exposure to systematic risk. To calculate the expected return of an asset given its risk, by way of CAPM, the following equation is used:
By manipulating this equation, we can utilise financial asset data and regression analysis to evaluate firstly whether the CAPM theory holds up in practice and secondly to evaluate the performance of particular financial assets.
This methodology follows from Jensen (1968) in which the performance of US mutual funds was systematically examined to determine whether any were able to “outperform the market” (Brooks 2008, pp. 67–81).
Listed Investment Companies (LICs) — A LIC is similar to a Managed Mutual Fund, however, investors are able to buy and sell shares in a LIC just like ordinary shares on a stock exchange. As such, historical pricing data is readily available online and can be easily sourced to conduct our analysis. Generally, the aim of holding LIC securities is to gain access to the skills and expertise of the LIC managers who utilise active investment strategies to outperform a defined benchmark. For holding LIC securities, investors are charged a management fee in the range of 1.0–1.5% per annum. It is also not uncommon for LICs to charge performance fees when returns are above the defined benchmark, typically 10–20% of the return above that of the benchmark (Firstlinks 2016).
Passive Exchange Traded Funds (ETFs) — Unlike the actively managed LICs, the aim of a passively managed ETF is to replicate the performance of defined benchmark, such as a market index (ASXETFS.com 2019). The fees an investor can expect to pay for holding a passive ETF are generally just a fraction of those charged by actively managed LICs. For example, our analysis uses the Vanguard Australian Shares Index ETF (VAS) which seeks to track the return of the S&P/ASX 300 Index before taking into account fees, which as of May 2020 are 0.1% per annum (Vanguard 2020).
Why would an investor prefer an LIC over a passive ETF? — Using the CAPM terminology introduced above, the main reason for an investor choosing a LIC investment over a passive ETF is the belief that the active investment strategies employed by the LIC managers will yield “alpha” returns (alpha > 0) whilst minimising risk exposure.
The following sections will utilise the CAPM methodology of Jensen (1968) to examine the historical performance of Australian LICs by obtaining estimates for the alpha and beta parameters of each security. Further, we will use our findings to evaluate the potential for building portfolios to replicate the returns of the LIC assets using a combination of a passive index tracking exchange traded fund (ETF), namely the Vanguard Australian Shares Index ETF (VAS), and Australian Commonwealth Government bonds.
The first order of business for our analysis is to obtain the required data. We have decided to use monthly returns data for the past 5 years as this is a common timeframe and frequency for calculating CAPM parameters*, however, the analysis and code can be easily adapted to accommodate alternate timeframes and frequency. For our analysis we have used returns data from 2015–06–01 through 2020–03–01.
[* this is currently the timeframe and frequency used for the beta calculation on Yahoo (Au)]
LIC List — A list of the 118 LICs currently trading on the ASX was obtained from https://www.asxlics.com, imported to R as a data frame and cleaned.
# IMPORT AND TIDY LIC LIST #################################LICs <- read.csv("https://www.asxlics.com/uploads/csv/20200401-lics.csv", header = TRUE)n <- nrow(LICs)LICs <- LICs[c(2:n), 1:3]lic.colnames <- c("Code", "Company", "Market Cap")names(LICs) <- lic.colnamesticker <- as.character(LICs[,1])row.names(LICs) <- ticker
Risk-free rate data — Sovereign government bonds are widely used in finance as “risk-free” assets (Investopedia 2020b). For our analysis, we will be using the yield data on Australian Commonwealth Government bonds with 5 years to maturity*. This data can be sourced from the Reserve Bank of Australia’s webpage**.
[* the 5-year timeframe was chosen simply as it paired nicely with the 5 year timeframe selected for data collection.]
[** prior to importing this data into R, we quickly formatted the dates to YYYY-MM-DD in Excel.]
# IMPORT AND TIDY RISK-FREE RATE DATA ######################Rf <- import("f2.1-data.csv") ## need to have manually formatted dates to YYYY-MM-DD in Exceln <- nrow(Rf)Rf <- Rf[c(12:n), c(1, 4)]Rf <- Rf[!apply(Rf == "", 1, all),]Rf$V1 <- as.Date(Rf$V1)Rf$V4 <- as.numeric(Rf$V4)Rf$V4 <- ((1+(Rf$V4/100))^(1/12)-1)Rf <- xts(Rf$V4, order.by = Rf$V1)names(Rf) <- c("Rf")
Benchmark Index Data — We have elected to use pricing data for the passively managed Vanguard Australian Shares Index ETF (VAS) as a proxy for the market returns. The fund seeks to track the return of the S&P/ASX 300 Index, therefore we would expect to obtain comparable results if we were to explicitly use direct price data of that market index or, to a lesser extent, another index such as the All Ordinaries or S&P/ASX 200. The reason for using the Vanguard ETF data is that it will enable us to readily produce replication portfolios following our initial CAPM modelling. Historical price data for VAS was obtained from Yahoo Finance. We use “Adj. Close” price data as this data series is adjusted for dividend distributions and enables our analysis to consider the dividend cashflows an investor would have been entitled to, as well as the capital gains return.
# IMPORT AND TIDY BENCHMARK DATA ###########################Rb <- read.csv("https://query1.finance.yahoo.com/v7/finance/download/VAS.AX?period1=1430265600&period2=1588118400&interval=1mo&events=history")n <- nrow(Rb)Rb <- Rb[c(1:n-1), c(1,6)]Rb$Date <- as.Date(Rb[, 1])Rb <- xts(Rb$`Adj.Close`, order.by = Rb$Date)names(Rb) <- c("Rb")Rb$Rb <- Return.calculate(Rb$Rb, method = "log")
LIC Data — The list of LICs was then used to obtain historical pricing data for each from Yahoo Finance. Again “Adj. Close” price data is used. Using R, the data was compiled into a single xts object along with the risk-free data and benchmark data, then trimmed.
# IMPORT AND TIDY LIC DATA #################################url_f <- "https://query1.finance.yahoo.com/v7/finance/download/"url_e <- ".AX?period1=1430265600&period2=1588118400&interval=1mo&events=history"n <- nrow(LICs)data <- merge(Rf, Rb)k <- nrow(data)for(i in 1:n){  url_temp_ch <- as.character(LICs[i,1])  url_temp <- paste(url_f, url_temp_ch, url_e, sep = "")  Ra_temp <- data.frame(rep(NA, k))  try(Ra_temp <- read.csv(url_temp, na.strings = c("null")), silent = T)  n_temp <- nrow(Ra_temp)  try(Ra_temp <- Ra_temp[c(1:n_temp-1), c(1,6)], silent = T)    if(is.na(Ra_temp[1, 1]) != TRUE){    Ra_temp$Date <- as.Date(Ra_temp[, 1])    Ra_temp <- xts(Ra_temp$`Adj.Close`, order.by = Ra_temp$Date)    header <- as.character(LICs[i,1])    names(Ra_temp) <- header    Ra_temp[, 1] <- Return.calculate(Ra_temp[, 1], method = "log")    data <- merge(data, Ra_temp)    rm(Ra_temp)  }  else if(is.na(Ra_temp[1, 1]) == TRUE){    data_temp <- data    data_temp$Rf <- rep(data_temp[1, 2], k)    data_temp <- data_temp$Rf    header <- as.character(LICs[i,1])    names(data_temp) <- header    data <- merge(data, data_temp)    rm(data_temp)  }}n <- nrow(data)data <- data[complete.cases(data[1:n, c(1, 2)]),]LIC.list <- names(data)names(data) <- LIC.listn <- ncol(data)LIC.list <- LIC.list[3:n]
Generating the CAPM Variables — As indicated above, the CAPM regression analysis requires us to calculate the “Excess Returns” for each LIC and the “Market Risk Premium”. These are calculated and added to a new data frame called “capm.data” in case we wish to export and save the data.
# GENERATE CAPM VARIABLES ##################################n <- ncol(data)capm.data <- as.xts(merge(data$Rf, data$Rb-data$Rf))names(capm.data) <- c("Rf", "mrp")for(i in 3:n){  Ra.Er_temp <- as.xts(data[, i]-data$Rf)  header <- as.character(names(data))  header <- paste(header, ".Er", sep = "")  names(Ra.Er_temp) <- header[i]  capm.data <- merge(capm.data, Ra.Er_temp)}n <- ncol(capm.data)LICs$Code <- LIC.list
Having compiled our data, we are now ready to calculate the CAPM parameters alpha and beta. To do so, we use a series of linear regressions with the “Excess Returns” of our LICs as our dependent variables and the “Market Risk Premium” as our explanatory variable. It is important to note that not all of our LICs have data for the entire 5 year period, and as such, some of the parameters will be less precisely estimated given the fewer data points available*. The below code calculates the parameters alpha and beta, as well as using a t-test function to produce p-values so we can have some idea of how accurately the parameters have been estimated.
[* again, one may prefer to adjust the timeframe and frequency to alleviate this issue.]
# LOAD t-TEST FUNCTION #####################################ttest <- function(reg, coefnum, val){  co <- coef(summary(reg))  tstat <- (co[coefnum,1]-val)/co[coefnum,2]  2 * pt(abs(tstat), reg$df.residual, lower.tail = FALSE)}# CALCULATE CAPM PARAMETERS ################################n <- ncol(capm.data)capm.para <- data.frame()for(i in 3:n){  try(    capm <- lm(capm.data[, i] ~ capm.data$mrp)  , silent = T)  para.temp <- data.frame(rep(0, 4))  try(para.temp <- capm$coefficients, silent = T)  para.temp <- as.data.frame(para.temp)  para.temp <- as.data.frame(transpose(para.temp))  try(para.temp[1, 3] <- ttest(capm, 1, 0), silent = T)  try(para.temp[1, 4] <- ttest(capm, 2, 1), silent = T)  names(para.temp) <- c("alpha", "beta", "alpha(0) ~ Pr(>|t|)", "beta(1) ~ Pr(>|t|)")  row.names(para.temp) <- as.character(LICs[i-2,1])  capm.para[i-2, 1:4] <- para.temp[1, 1:4]  try(rm(capm), silent = T)  rm(para.temp)}row.names(LICs) <- LICs$CodeLICs <- merge(LICs, capm.para, by.x = 1, by.y = 0, all.x = TRUE, all.y = TRUE)
For beta, we have simply tested the hypothesis that our estimated value is indifferent from 1, that is that the LIC security tends to move in line with the market as a whole*.
[* this test and subsequent p-value is not overly relevant to our particular line of analysis, however, the t-test can be modified to test whether is indifferent from any particular value so could be useful if we chose to drill our analysis down a bit deeper into the individual LICs and how they have historically reacted to broader market movements.]
For alpha, our hypothesis is that our estimated value is indifferent from zero, indicating that we have not found the LIC managers to be outperforming the market, or evidence to suggest the CAPM does not hold based on the data we have collected. Therefore we simply need to check whether we have obtained a positive and statistically significant alpha value for any of the LICs.
# CHECK FOR POSITIVE AND SIGNIFICANT ALPHA RETURNS #########LICs$alpha.rtns <- ifelse(LICs$`alpha(0) ~ Pr(>|t|)`<= 0.05 & LICs$alpha > 0.0, "TRUE", "FALSE")
Our results show that over this period no LIC achieved a statistically significant (at the 95% confidence level), and positive alpha return. In fact, there was only the one LIC to have an estimated alpha value statistically different from zero, and in that case, the alpha value was -0.0051, indicating that this LIC had under-performed the market relative to the systematic risk exposure. If we widen our “significance” cut-off to the 90% confidence level, we only find one additional LICs with a significant alpha value, again it is estimated to be negative indicating under-performance. We can, therefore, conclude that our analysis was unable to find evidence to suggest that actively managed LICs yielded returns for investors that were significantly different from the returns of the broader market, relative to the risk exposure.
Since we have now seen that the LICs have not outperformed the market by delivering statistically significant higher returns, we are able to make portfolios combining the risk-free asset and the market tracking ETF that replicate the return of the LIC with a lower risk exposure.­ From our findings regarding alpha returns above, we know that the expected return of our replication portfolio and those of the LIC securities will not be statistically different from one-another*. Therefore, if we can show our replication portfolios exhibit less variation in returns when compared to the relative LIC, we can be confident that these portfolios deliver comparable returns to an investor for less risk expose.
[* it is also worth reminding ourselves at this point that we have not accounted for any management or performance fees that may be charged by the LICs.]
We first need to calculate the standard deviation of the LIC excess returns using the data series produced earlier of each LIC.
# CALCULATE SD(x) FOR LICs #################################k <- ncol(data)-2sd.temp <- as.numeric(vector())er.list <- names(capm.data)n <- nrow(er.list)er.list <- er.list[3:(k+2)]for(i in 1:k){  sd.temp[i] <- STDEV(capm.data[, er.list[i]])}sd.temp <- as.data.frame(as.numeric(sd.temp))row.names(sd.temp) <- LIC.listnames(sd.temp) <- c("SD(ER_at)")LICs <- merge(LICs, sd.temp, by.x = 1, by.y = 0, all.x = TRUE, all.y = TRUE)
Next, we need to calculate the excess returns for our replication portfolios by first calculating the historical return for each period, then subtracting the risk-free rate. From this, the standard deviations of the replication portfolio excess returns can be calculated.
# CALCULATE SD(x) FOR REP. PORT. ###########################k <- nrow(data)j <- nrow(LICs)sd.temp <- as.numeric(vector())for(i in 1:j){  beta.temp <- as.data.frame(rep(LICs[i, 5], k))  rep.port.temp <- beta.temp  Rf.temp <- as.numeric(data$Rf)  rep.port.temp <- add_column(rep.port.temp, Rf.temp, .after = 100)  rtn.temp <- as.data.frame(data[, 2])  rep.port.temp <- add_column(rep.port.temp, rtn.temp, .after = 100)  names(rep.port.temp) <- c("Beta", "Rf", "Rtn")  port.temp <- (1-rep.port.temp$Beta)*rep.port.temp$Rf+rep.port.temp$Beta*rep.port.temp$Rtn  rep.port.temp <- add_column(rep.port.temp, port.temp, .after = 100)  names(rep.port.temp) <- c("Beta", "Rf", "Rtn", "Port. Rtn")  rep.port.temp$`Port. Rtn` <- as.numeric(unlist(rep.port.temp$`Port. Rtn`))  rep.port.temp$Rtn <- as.numeric(unlist(rep.port.temp$Rtn))  rep.port.temp$Exc.Port.Rtn <- as.numeric(unlist(rep.port.temp$`Port. Rtn`-rep.port.temp$Rf))  sd.temp[i] <- STDEV(rep.port.temp[, 5])}LICs$"SD(ER_pt)" <- sd.temp
Finally, we check whether the standard deviation of the returns from our replication portfolios is lower than that of the LIC securities, indicating less risk exposure.
# COMPARE SD(x) PERFORMANCE ################################LICs$'Lower Rep. Port. Risk?' <- ifelse(LICs$`SD(ER_pt)` <= LICs$`SD(ER_at)`, "TRUE", "FALSE")
The results show that for each LIC in our analysis we have been able to produce a replication portfolio, combining a passive index tracking ETF and risk-free assets that will produce a comparable rate of return while exposing an investor to less risk.
We have shown through this analysis that over our sample period (2015–06–01 through 2020–03–01) we fail to reject our hypothesis that LIC managers were unable consistently deliver returns above those offered by holding a replication portfolio of a passive index tracking ETF and risk-free assets. Further, investors holding LIC securities are being charged a higher management fee, and in some cases, a performance fee, to hold these securities with the belief that the expertise of their LIC manager will deliver the highest possible return for the lowest possible risk exposure. Yet we have seen that by holding a replication portfolio, investors will also be able to reduce their risk exposure.
It is important to note that, as with all financial assets, past performance is not always a reliable indicator of future performance. The analysis above has used 5 years of monthly price data, using different timeframes and periods will likely yield different results. I have posted this article not with the intention of providing financial advice, but to share the methodology and coding for others to replicate with their own preferences.
Thanks for reading all the way to the end of the article! I’d love to hear any comments about the above. Feel free to leave a message, or reach out to me through LinkedIn.
# Packages #############################pacman::p_load(pacman, expss, jtools, NCmisc, PerformanceAnalytics, purrr, quantmod, rio, stats, tibble, tidyquant, utils, xts, zoo)
Note from Towards Data Science’s editors: While we allow independent authors to publish articles in accordance with our rules and guidelines, we do not endorse each author’s contribution. You should not rely on an author’s works without seeking professional advice. See our Reader Terms for details.
ASXETFS.com (2019) — https://www.asxetfs.com [accessed 08/05/2020]
ASXLICS.com (2019) — https://www.asxlics.com [accessed 08/05/2020]
Brooks, C 2008, Introductory Econometrics for Finance, Second Edition, Cambridge University Press
Firstlinks (2016) — https://www.firstlinks.com.au/understanding-lic-fee-structures [accessed 08/05/2020]
Investopedia (2019a) — https://www.investopedia.com/terms/u/unsystematicrisk.asp [accessed 08/05/2020]
Investopedia (2019b) — https://www.investopedia.com/terms/s/systematicrisk.asp [accessed 08/05/2020]
Investopedia (2020a) — https://www.investopedia.com/terms/c/capm.asp [accessed 08/05/2020]
Investopedia (2020b) — https://www.investopedia.com/terms/r/riskfreeasset.asp [accessed 08/05/2020]
Reserve Bank of Australia (2020) — https://www.rba.gov.au/statistics/tables/#interest-rates [accessed 08/05/2020]
Vanguard (2020) — https://www.vanguardinvestments.com.au/retail/ret/investments/product.html#/fundDetail/etf/portId=8205/?overview [accessed 08/05/2020]
Yahoo Finance (2020) — https://au.finance.yahoo.com [accessed 08/05/2020]