0

I have already successfully calculated my rolling correlations in my xts object with

x <- cbind(market_return,stock_returns)
rollcor_3year <- rollapplyr(
    x, width=width_cor,function(x) cor(x[,1],x[,-1],
    use="pairwise.complete.obs"),by.column=FALSE)

The correlation was later used to calculate rolling Betas.

Now I found the function CAPM.beta from the PerformanceAnalytics package and I wonder why I cannot use

beta <- rollapplyr(x,width=width_cor,function(x) CAPM.beta(x[,1],x[,-1]),by.column=FALSE)

or

beta <- rollapplyr(stock_returns,width=width_cor,CAPM.beta,Rb=market_return,by.column=FALSE)

directly.

With both functions it starts to calculate but doesnt stop...

It would be nice to see if I get the same betas out of the predefined function, but apparently it does not work like that. What did I do wrong?

jackbauer
  • 155
  • 2
  • 14

2 Answers2

1

Actually, the PerformanceAnalytics function will give the same results but it takes longer to do it. The code below uses sample data for 2017-01-01 to now with AMZN and XOM as stocks and SPY as a proxy for market returns. A window of 40 trading days is used in the rolling calculations. The rolling beta value is calculated using the CAPM.beta and BetaCoVariance functions from PerformanceAnalytics and by three methods which directly calculate the covariance matrix and then taking the ratio of the pairwise covariances to the market variance. The results from the methods are displayed to show they are the same. microbenchmark from the microbenchmark package is used to measure execution times for all methods. The direct calculations is one to two orders of magnitude faster.

  library(xts)
  library(quantmod)
  library(PerformanceAnalytics)
  library(microbenchmark)
#
#  get price time histories and calculate returns
#  use SPY as proxy for S&P 500; SPY should be first symbol in assets
#
  assets <- c("SPY", "AMZN", "XOM")   
  getSymbols( assets, from = "2017-01-01", auto.assign = TRUE)

  asset_prices <- xts()
  asset_prices <- Reduce(f=function(x,y) {y_sym=eval(as.name(y));  merge(x,y_sym[,paste0(y,".Adjusted")])},
                         x = assets, init=asset_prices) 
  asset_returns <- diff.xts(asset_prices, arithmetic = FALSE, na.pad=FALSE)-1

  market_return <- asset_returns$SPY.Adjusted
  stock_returns <- asset_returns[,-1] 

#
#  calculate rolling beta with a 40 trading-day window using CAPM.beta.roll
#  For this amount of data and calculating daily betas (by = 1), calculation should take 5-10 seconds
#
  width_cor = 40
  CAPM.beta_roll <- rollapply(data=stock_returns, FUN=CAPM.beta, Rb= market_return, Rf = 2.5/252, 
                       width = width_cor, by = 1, align = "right", by.column=TRUE)

#
#  calculate rolling beta with a 40 trading-day window by calculating the covariance matrix and taking ratio of two elements
#  For this amount of data and calculating daily betas (by = 1), calculation should be very quick
#
  CovVar <- function(Ra, Rb) {R = merge.xts(Rb, Ra, join="inner"); cv=cov(x=R);  
                               cv[1,-1]/cv[1,1,drop=TRUE]}
  CovVar_roll <- rollapplyr(data=stock_returns, width=width_cor,
                            FUN= CovVar,  Rb = market_return, by.column=FALSE)

#
#  since rollapply does not apply the window to Rb, it is done in CovVar for each time window
#  CovVar1 is a faster version which passes the merged market and stock return to cov directly
#  Its single argument R must be the merged data matrix R
#
  CovVar1 <- function(R){  cv=cov(x=R); cv[-1,1]/cv[1,1]}
  CovVar1_roll <- rollapplyr(data=merge(market_return, stock_returns), width=width_cor,
                             FUN= CovVar1,  by.column=FALSE)

  #
  #  CovVar2 is a faster version which passes the merged market and stock return to cov directly and 
  #  calculates the covariances only between the market returns and stock_returns.  For a small number of stocks,
  #  this is less efficient than calculating the entire covariance for a single matrix as in CovVar1 but it should become more 
  #  efficient for a larger number of stocks.
  #  Its single argument R must be the merged data matrix R
  #
  CovVar2 <- function(R){  cv = cov(R[,1], R );  cv[,-1]/cv[1,1] }
  CovVar2_roll <- rollapplyr(data=merge(market_return, stock_returns), width=width_cor,
                             FUN= CovVar2,  by.column=FALSE)

#
# Compare to verify that results are the same 
#
  print(tail(merge(CAPM.beta_roll, CovVar_roll, CovVar1_roll, CovVar2_roll )))
#
#  Compare execution times for four above methods and third method using BetaCovariance function from PerformanceAnalytics
#  This should take 25-35 seconds to run
#

  elapsed_times <- microbenchmark(
                  CAPM.beta_roll = rollapplyr(data=stock_returns, width=width_cor,
                                              FUN= CAPM.beta, Rb=market_return,by.column=FALSE),
                  BetaCoVar_roll = rollapplyr(data=stock_returns, width=width_cor,
                                               FUN= BetaCoVariance, Rb=market_return,by.column=FALSE),
                  CovVar_roll = rollapplyr(data=stock_returns, width=width_cor,
                                           FUN= CovVar,  Rb = market_return, by.column=FALSE),
                  CovVar1_roll = rollapplyr(data=merge(market_return, stock_returns), width=width_cor,
                                             FUN= CovVar1,  by.column=FALSE),
                  CovVar2_roll = rollapplyr(data=merge(market_return, stock_returns), width=width_cor,
                                             FUN= CovVar2,  by.column=FALSE),
                   times = 3)

# 
#  Direct calculation using covariance matrix, CovVar, is 50 - 100 times faster than PerformanceAnalytics functions 
#
  print(elapsed_times)

The execution times are:

Unit: milliseconds
           expr        min         lq       mean     median         uq        max neval
 CAPM.beta_roll 3007.34309 3009.92618 3016.57905 3012.50928 3021.19703 3029.88477     3
 BetaCoVar_roll 3453.83531 3471.70954 3478.91433 3489.58377 3491.45383 3493.32390     3
    CovVar_roll   69.19571   69.57012   69.83189   69.94453   70.14999   70.35544     3
   CovVar1_roll   38.72437   39.17021   39.33052   39.61605   39.63359   39.65113     3
   CovVar2_roll   60.75020   61.08255   61.36130   61.41490   61.66684   61.91878     3 

CovVar1 is the quickest since at least for a small number of dimensions, R calculates the covariance matrix much more efficiently for a single matrix input than for an input of two matrices where it has to align the matrices. For some larger number of dimensions, the CovVar2 should be faster.

WaltS
  • 5,410
  • 2
  • 18
  • 24
  • Ah thank you very much. I assumed it would just take much much longer, but the results are indeed the same as I see. Follow up: In your functions, cv[1,-1] is the covariance for the pairwise stock and market, right? How does rollapply know that it has to iterate then only for the next stock column and keep the market column the same? – jackbauer Jul 02 '18 at 10:04
  • In CovVar, rollapply gives the function the rows of stock_returns, Ra, for the time window. But, it doesn't window Rb, market_return, so the function uses merge.xts with join = "inner" to form the matrix of returns for the dates common to Rb and Ra, i.e. the window dates for that iteration. In the second approach, CovVar_roll1, market_return is first merged with stock_returns and then given to rollapply. For both, market_return is the 1st column of the return matrix and so the 1st row and column of the cov matrix. cv[1,-1] are then covariances of stocks with market for that time window. – WaltS Jul 02 '18 at 14:09
  • Okay, do you know perhaps why in my function for the rolling correlation it is specified as `(...),function(x) cor(x[,1],x[,-1](...)`, as I have got the function from stackoverflow, but cant really grasp the concept behind it. It seems like it takes the correlation between the first column and the matrix without the last column? – jackbauer Jul 02 '18 at 16:56
  • x[,-1] removes the first column from x, not the last. cor(x[,1],x[,-1]) computes the correlation between the first column of x and the remaining columns. I've edited by answer to show this method with my first ones. The results above show that for a smaller number of stocks, its quicker to calculate the full covariance for a single matrix input. As the number of stocks increases, it should be faster to calculate covariances only between the market and the stocks. – WaltS Jul 02 '18 at 20:55
1

Here is a solution as in WaltS's answer but around 16 times faster then the CovVar2 function using the rollRegres package

library(xts)
library(quantmod)
library(PerformanceAnalytics)
library(microbenchmark)

# setup
assets <- c("SPY", "AMZN", "XOM")
getSymbols( assets, from = "2017-01-01", auto.assign = TRUE)
#R [1] "SPY"  "AMZN" "XOM"

asset_prices <- xts()
asset_prices <- Reduce(f=function(x,y) {y_sym=eval(as.name(y));  merge(x,y_sym[,paste0(y,".Adjusted")])},
                       x = assets, init=asset_prices)
asset_returns <- diff.xts(asset_prices, arithmetic = FALSE, na.pad=FALSE)-1

market_return <- asset_returns$SPY.Adjusted
stock_returns <- asset_returns[,-1]

# solution from WaltS's answer
width_cor <-  40
CovVar2 <- function(R){  cv = cov(R[,1], R );  cv[,-1]/cv[1,1] }
CovVar2_roll <- rollapplyr(
  data = merge(market_return, stock_returns), width=width_cor,
  FUN= CovVar2,  by.column=FALSE)

# rollRegres solution
library(rollRegres)
dat <- as.matrix(merge(market_return, stock_returns))
X  <- cbind(1, dat[, 1])
Ys <- dat[, -1, drop = FALSE]
roll_out <- apply(Ys, 2, function(y)
  roll_regres.fit(x = X, y = y, width = width_cor)$coefs[, 2])

# gives the same
all.equal(as.matrix(CovVar2_roll), roll_out, check.attributes = FALSE)
#R [1] TRUE

# much faster
microbenchmark(
  CovVar2 = rollapplyr(
    data = merge(market_return, stock_returns), width=width_cor,
    FUN= CovVar2,  by.column=FALSE),
  rollRegres = {
    dat <- as.matrix(merge(market_return, stock_returns))
    X  <- cbind(1, dat[, 1])
    Ys <- dat[, -1, drop = FALSE]
    roll_out <- apply(Ys, 2, function(y)
      roll_regres.fit(x = X, y = y, width = width_cor)$coefs[, 2])
  }, times = 10)
#R Unit: milliseconds
#R       expr       min        lq      mean    median        uq      max neval
#R    CovVar2 37.669941 39.086237 39.877981 39.530485 41.011374 41.71893    10
#R rollRegres  1.987162  2.036149  2.486836  2.102717  3.342224  3.73689    10
  • `rollRegress` is a brand new package for doing rolling regressions by the author of this answer. Looks promising. – WaltS Jul 03 '18 at 12:56