###############################################################################1# R (https://r-project.org/) Numeric Methods for Optimization of Portfolios2#3# Copyright (c) 2004-2021 Brian G. Peterson, Peter Carl, Ross Bennett, Kris Boudt4#5# This library is distributed under the terms of the GNU Public License (GPL)6# for full details see the file COPYING7#8# $Id: charts.DE.R 3378 2014-04-28 21:43:21Z rossbennett34 $9#10###############################################################################1112# Note that many of these functions were provided by Kris Boudt and modified13# only slightly to work with this package1415#' Statistical Factor Model16#'17#' Fit a statistical factor model using Principal Component Analysis (PCA)18#'19#' @details20#' The statistical factor model is fitted using \code{prcomp}. The factor21#' loadings, factor realizations, and residuals are computed and returned22#' given the number of factors used for the model.23#'24#' @param R xts of asset returns25#' @param k number of factors to use26#' @param \dots additional arguments passed to \code{prcomp}27#' @return28#' #' \describe{29#' \item{factor_loadings}{ N x k matrix of factor loadings (i.e. betas)}30#' \item{factor_realizations}{ m x k matrix of factor realizations}31#' \item{residuals}{ m x N matrix of model residuals representing idiosyncratic32#' risk factors}33#' }34#' Where N is the number of assets, k is the number of factors, and m is the35#' number of observations.36#' @export37statistical.factor.model <- function(R, k=1, ...){38if(!is.xts(R)){39R <- try(as.xts(R))40if(inherits(R, "try-error")) stop("R must be an xts object or coercible to an xts object")41}42# dimensions of R43m <- nrow(R)44N <- ncol(R)4546# checks for R47if(m < N) stop("fewer observations than assets")48x <- coredata(R)4950# Make sure k is an integer51if(k <= 0) stop("k must be a positive integer")52k <- as.integer(k)5354# Fit a statistical factor model using Principal Component Analysis (PCA)55fit <- prcomp(x, ...=...)5657# Extract the betas58# (N x k)59betas <- fit$rotation[, 1:k]6061# Compute the estimated factor realizations62# (m x N) %*% (N x k) = (m x k)63f <- x %*% betas6465# Compute the residuals66# These can be computed manually or by fitting a linear model67tmp <- x - f %*% t(betas)68b0 <- colMeans(tmp)69res <- tmp - matrix(rep(b0, m), ncol=N, byrow=TRUE)7071# Compute residuals via fitting a linear model72# tmp.model <- lm(x ~ f)73# tmp.beta <- coef(tmp.model)[2:(k+1),]74# tmp.resid <- resid(tmp.model)75# all.equal(t(tmp.beta), betas, check.attributes=FALSE)76# all.equal(res, tmp.resid)7778# structure and return79# stfm = *st*atistical *f*actor *m*odel80structure(list(factor_loadings=betas,81factor_realizations=f,82residuals=res,83m=m,84k=k,85N=N),86class="stfm")87}888990#' Center91#'92#' Center a matrix93#'94#' This function is used primarily to center a time series of asset returns or95#' factors. Each column should represent the returns of an asset or factor96#' realizations. The expected value is taken as the sample mean.97#'98#' x.centered = x - mean(x)99#'100#' @param x matrix101#' @return matrix of centered data102#' @export103center <- function(x){104if(!is.matrix(x)) stop("x must be a matrix")105n <- nrow(x)106p <- ncol(x)107meanx <- colMeans(x)108x.centered <- x - matrix(rep(meanx, n), ncol=p, byrow=TRUE)109x.centered110}111112##### Single Factor Model Comoments #####113114#' Covariance Matrix Estimate115#'116#' Estimate covariance matrix using a single factor statistical factor model117#'118#' @details119#' This function estimates an (N x N) covariance matrix from a single factor120#' statistical factor model with k=1 factors, where N is the number of assets.121#'122#' @param beta vector of length N or (N x 1) matrix of factor loadings123#' (i.e. the betas) from a single factor statistical factor model124#' @param stockM2 vector of length N of the variance (2nd moment) of the125#' model residuals (i.e. idiosyncratic variance of the stock)126#' @param factorM2 scalar value of the 2nd moment of the factor realizations127#' from a single factor statistical factor model128#' @return (N x N) covariance matrix129covarianceSF <- function(beta, stockM2, factorM2){130# Beta of the stock with the factor index131beta = as.numeric(beta)132133N = length(beta)134135# Idiosyncratic variance of the stock136stockM2 = as.numeric(stockM2)137138if(length(stockM2) != N) stop("dimensions do not match for beta and stockM2")139140# Variance of the factor141factorM2 = as.numeric(factorM2)142143# Coerce beta to a matrix144beta = matrix(beta, ncol = 1)145146# Compute estimate147# S = (beta %*% t(beta)) * factorM2148S = tcrossprod(beta) * factorM2149D = diag(stockM2)150return(S + D)151}152153#' Coskewness Matrix Estimate154#'155#' Estimate coskewness matrix using a single factor statistical factor model156#'157#' @details158#' This function estimates an (N x N^2) coskewness matrix from a single factor159#' statistical factor model with k=1 factors, where N is the number of assets.160#'161#' @param beta vector of length N or (N x 1) matrix of factor loadings162#' (i.e. the betas) from a single factor statistical factor model163#' @param stockM3 vector of length N of the 3rd moment of the model residuals164#' @param factorM3 scalar of the 3rd moment of the factor realizations from a165#' single factor statistical factor model166#' @return (N x N^2) coskewness matrix167coskewnessSF <- function(beta, stockM3, factorM3){168# Beta of the stock with the factor index169beta = as.numeric(beta)170N = length(beta)171172# Idiosyncratic third moment of the stock173stockM3 = as.numeric(stockM3)174175if(length(stockM3) != N) stop("dimensions do not match for beta and stockM3")176177# Third moment of the factor178factorM3 = as.numeric(factorM3)179180# Coerce beta to a matrix181beta = matrix(beta, ncol = 1)182183# Compute estimate184# S = ((beta %*% t(beta)) %x% t(beta)) * factorM3185S = (tcrossprod(beta) %x% t(beta)) * factorM3186D = matrix(0, nrow=N, ncol=N^2)187for(i in 1:N){188col = (i - 1) * N + i189D[i, col] = stockM3[i]190}191return(S + D)192}193194#' Cokurtosis Matrix Estimate195#'196#' Estimate cokurtosis matrix using a single factor statistical factor model197#'198#' @details199#' This function estimates an (N x N^3) cokurtosis matrix from a statistical200#' factor model with k factors, where N is the number of assets.201#'202#' @param beta vector of length N or (N x 1) matrix of factor loadings203#' (i.e. the betas) from a single factor statistical factor model204#' @param stockM2 vector of length N of the 2nd moment of the model residuals205#' @param stockM4 vector of length N of the 4th moment of the model residuals206#' @param factorM2 scalar of the 2nd moment of the factor realizations from a207#' single factor statistical factor model208#' @param factorM4 scalar of the 4th moment of the factor realizations from a209#' single factor statistical factor model210#' @return (N x N^3) cokurtosis matrix211cokurtosisSF <- function(beta, stockM2, stockM4, factorM2, factorM4){212# Beta of the stock with the factor index213beta = as.numeric(beta)214N = as.integer(length(beta))215216# Idiosyncratic second moment of the stock217stockM2 = as.numeric(stockM2)218219if(length(stockM2) != N) stop("dimensions do not match for beta and stockM2")220221# Idiosyncratic fourth moment of the stock222stockM4 = as.numeric(stockM4)223224if(length(stockM4) != N) stop("dimensions do not match for beta and stockM4")225226# Second moment of the factor227factorM2 = as.numeric(factorM2)228229# Fourth moment of the factor230factorM4 = as.numeric(factorM4)231232# Compute estimate233# S = ((beta %*% t(beta)) %x% t(beta) %x% t(beta)) * factorM4234S = (tcrossprod(beta) %x% t(beta) %x% t(beta)) * factorM4235D = .residualcokurtosisSF(NN=N, sstockM2=stockM2, sstockM4=stockM4, mfactorM2=factorM2, bbeta=beta)236return(S + D)237}238239# Wrapper function to compute the residual cokurtosis matrix of a statistical240# factor model with k = 1.241# Note that this function was orignally written in C++ (using Rcpp) by242# Joshua Ulrich and re-written using the C API by Ross Bennett243#' @useDynLib "PortfolioAnalytics"244.residualcokurtosisSF <- function(NN, sstockM2, sstockM4, mfactorM2, bbeta){245# NN : integer246# sstockM2 : vector of length NN247# sstockM4 : vector of length NN248# mfactorM2 : double249# bbeta : vector of length NN250251if(!is.integer(NN)) NN <- as.integer(NN)252if(length(sstockM2) != NN) stop("sstockM2 must be a vector of length NN")253if(length(sstockM4) != NN) stop("sstockM4 must be a vector of length NN")254if(!is.double(mfactorM2)) mfactorM2 <- as.double(mfactorM2)255if(length(bbeta) != NN) stop("bbeta must be a vector of length NN")256257.Call('residualcokurtosisSF', NN, sstockM2, sstockM4, mfactorM2, bbeta, PACKAGE="PortfolioAnalytics")258}259260##### Multiple Factor Model Comoments #####261262#' Covariance Matrix Estimate263#'264#' Estimate covariance matrix using a statistical factor model265#'266#' @details267#' This function estimates an (N x N) covariance matrix from a statistical268#' factor model with k factors, where N is the number of assets.269#'270#' @param beta (N x k) matrix of factor loadings (i.e. the betas) from a271#' statistical factor model272#' @param stockM2 vector of length N of the variance (2nd moment) of the273#' model residuals (i.e. idiosyncratic variance of the stock)274#' @param factorM2 (k x k) matrix of the covariance (2nd moment) of the factor275#' realizations from a statistical factor model276#' @return (N x N) covariance matrix277covarianceMF <- function(beta, stockM2, factorM2){278# Formula for covariance matrix estimate279# Sigma = beta %*% factorM2 %*% beta**T + Delta280# Delta is a diagonal matrix with the 2nd moment of residuals on the diagonal281282# N = number of assets283# k = number of factors284285# Use the dimensions of beta for checks of stockM2 and factorM2286# beta should be an (N x k) matrix287if(!is.matrix(beta)) stop("beta must be a matrix")288N <- nrow(beta)289k <- ncol(beta)290291# stockM2 should be a vector of length N292stockM2 <- as.numeric(stockM2)293if(length(stockM2) != N) stop("dimensions do not match for beta and stockM2")294295# factorM2 should be a (k x k) matrix296if(!is.matrix(factorM2)) stop("factorM2 must be a matrix")297if((nrow(factorM2) != k) | (ncol(factorM2) != k)){298stop("dimensions do not match for beta and factorM2")299}300301# Compute covariance matrix302S <- beta %*% tcrossprod(factorM2, beta)303D <- diag(stockM2)304return(S + D)305}306307#' Coskewness Matrix Estimate308#'309#' Estimate coskewness matrix using a statistical factor model310#'311#' @details312#' This function estimates an (N x N^2) coskewness matrix from a statistical313#' factor model with k factors, where N is the number of assets.314#'315#' @param beta (N x k) matrix of factor loadings (i.e. the betas) from a316#' statistical factor model317#' @param stockM3 vector of length N of the 3rd moment of the model residuals318#' @param factorM3 (k x k^2) matrix of the 3rd moment of the factor319#' realizations from a statistical factor model320#' @return (N x N^2) coskewness matrix321coskewnessMF <- function(beta, stockM3, factorM3){322# Formula for coskewness matrix estimate323# Phi = beta %*% factorM3 %*% (beta**T %x% beta**T) + Omega324# %x% is the kronecker product325# Omega is the (N x N^2) matrix matrix of zeros except for the i,j elements326# where j = (i - 1) * N + i, which is corresponding to the expected third327# moment of the idiosyncratic factors328329# N = number of assets330# k = number of factors331332# Use the dimensions of beta for checks of stockM2 and factorM2333# beta should be an (N x k) matrix334if(!is.matrix(beta)) stop("beta must be a matrix")335N <- nrow(beta)336k <- ncol(beta)337338# stockM3 should be a vector of length N339stockM3 <- as.numeric(stockM3)340if(length(stockM3) != N) stop("dimensions do not match for beta and stockM3")341342# factorM3 should be an (k x k^2) matrix343if(!is.matrix(factorM3)) stop("factorM3 must be a matrix")344if((nrow(factorM3) != k) | (ncol(factorM3) != k^2)){345stop("dimensions do not match for beta and factorM3")346}347348# Compute coskewness matrix349beta.t <- t(beta)350S <- (beta %*% factorM3) %*% (beta.t %x% beta.t)351D <- matrix(0, nrow=N, ncol=N^2)352for(i in 1:N){353col <- (i - 1) * N + i354D[i, col] <- stockM3[i]355}356return(S + D)357}358359#' Cokurtosis Matrix Estimate360#'361#' Estimate cokurtosis matrix using a statistical factor model362#'363#' @details364#' This function estimates an (N x N^3) cokurtosis matrix from a statistical365#' factor model with k factors, where N is the number of assets.366#'367#' @param beta (N x k) matrix of factor loadings (i.e. the betas) from a368#' statistical factor model369#' @param stockM2 vector of length N of the 2nd moment of the model residuals370#' @param stockM4 vector of length N of the 4th moment of the model residuals371#' @param factorM2 (k x k) matrix of the 2nd moment of the factor372#' realizations from a statistical factor model373#' @param factorM4 (k x k^3) matrix of the 4th moment of the factor374#' realizations from a statistical factor model375#' @return (N x N^3) cokurtosis matrix376cokurtosisMF <- function(beta , stockM2 , stockM4 , factorM2 , factorM4){377378# Formula for cokurtosis matrix estimate379# Psi = beta %*% factorM4 %*% (beta**T %x% beta**T %x% beta**T) + Y380# %x% is the kronecker product381# Y is the residual matrix.382# see Asset allocation with higher order moments and factor models for383# definition of Y384385# N = number of assets386# k = number of factors387388# Use the dimensions of beta for checks of stockM2 and factorM2389# beta should be an (N x k) matrix390if(!is.matrix(beta)) stop("beta must be a matrix")391N <- nrow(beta)392k <- ncol(beta)393394# stockM2 should be a vector of length N395stockM2 <- as.numeric(stockM2)396if(length(stockM2) != N) stop("dimensions do not match for beta and stockM2")397398# stockM4 should be a vector of length N399stockM4 <- as.numeric(stockM4)400if(length(stockM4) != N) stop("dimensions do not match for beta and stockM4")401402# factorM2 should be a (k x k) matrix403if(!is.matrix(factorM2)) stop("factorM2 must be a matrix")404if((nrow(factorM2) != k) | (ncol(factorM2) != k)){405stop("dimensions do not match for beta and factorM2")406}407408# factorM4 should be a (k x k^3) matrix409if(!is.matrix(factorM4)) stop("factorM4 must be a matrix")410if((nrow(factorM4) != k) | (ncol(factorM4) != k^3)){411stop("dimensions do not match for beta and factorM4")412}413414# Compute cokurtosis matrix415beta.t <- t(beta)416S <- (beta %*% factorM4) %*% (beta.t %x% beta.t %x% beta.t)417# betacov418betacov <- as.numeric(beta %*% tcrossprod(factorM2, beta))419# Compute the residual cokurtosis matrix420D <- .residualcokurtosisMF(NN=N, sstockM2=stockM2, sstockM4=stockM4, bbetacov=betacov)421return( S + D )422}423424# Wrapper function to compute the residual cokurtosis matrix of a statistical425# factor model with k > 1.426# Note that this function was orignally written in C++ (using Rcpp) by427# Joshua Ulrich and re-written using the C API by Ross Bennett428#' @useDynLib "PortfolioAnalytics"429.residualcokurtosisMF <- function(NN, sstockM2, sstockM4, bbetacov){430# NN : integer, number of assets431# sstockM2 : numeric vector of length NN432# ssstockM4 : numeric vector of length NN433# bbetacov : numeric vector of length NN * NN434435if(!is.integer(NN)) NN <- as.integer(NN)436if(length(sstockM2) != NN) stop("sstockM2 must be a vector of length NN")437if(length(sstockM4) != NN) stop("sstockM4 must be a vector of length NN")438if(length(bbetacov) != NN*NN) stop("bbetacov must be a vector of length NN*NN")439440.Call('residualcokurtosisMF', NN, sstockM2, sstockM4, bbetacov, PACKAGE="PortfolioAnalytics")441}442443##### Extract Moments #####444445#' Covariance Estimate446#'447#' Extract the covariance matrix estimate from a statistical factor model448#'449#' @param model statistical factor model estimated via450#' \code{\link{statistical.factor.model}}451#' @param \dots not currently used452#' @return covariance matrix estimate453#' @seealso \code{\link{statistical.factor.model}}454#' @author Ross Bennett455#' @export456extractCovariance <- function(model, ...){457if(!inherits(model, "stfm")) stop("model must be of class 'stfm'")458459# Extract elements from the model460beta <- model$factor_loadings461f <- model$factor_realizations462res <- model$residuals463m <- model$m464k <- model$k465N <- model$N466467# Residual moments468denom <- m - k - 1469stockM2 <- colSums(res^2) / denom470471# Factor moments472factorM2 <- cov(f)473474# Compute covariance estimate475if(k == 1){476out <- covarianceSF(beta, stockM2, factorM2)477} else if(k > 1){478out <- covarianceMF(beta, stockM2, factorM2)479} else {480# invalid k481message("invalid k, returning NULL")482out <- NULL483}484return(out)485}486487#' Coskewness Estimate488#'489#' Extract the coskewness matrix estimate from a statistical factor model490#'491#' @param model statistical factor model estimated via492#' \code{\link{statistical.factor.model}}493#' @param \dots not currently used494#' @return coskewness matrix estimate495#' @seealso \code{\link{statistical.factor.model}}496#' @author Ross Bennett497#' @export498extractCoskewness <- function(model, ...){499if(!inherits(model, "stfm")) stop("model must be of class 'stfm'")500501# Extract elements from the model502beta <- model$factor_loadings503f <- model$factor_realizations504res <- model$residuals505m <- model$m506k <- model$k507N <- model$N508509# Residual moments510denom <- m - k - 1511stockM3 <- colSums(res^3) / denom512513# Factor moments514# f.centered <- center(f)515# factorM3 <- M3.MM(f.centered)516factorM3 <- PerformanceAnalytics::M3.MM(f)517518# Compute covariance estimate519if(k == 1){520# Single factor model521out <- coskewnessSF(beta, stockM3, factorM3)522} else if(k > 1){523# Multi-factor model524out <- coskewnessMF(beta, stockM3, factorM3)525} else {526# invalid k527message("invalid k, returning NULL")528out <- NULL529}530return(out)531}532533#' Cokurtosis Estimate534#'535#' Extract the cokurtosis matrix estimate from a statistical factor model536#'537#' @param model statistical factor model estimated via538#' \code{\link{statistical.factor.model}}539#' @param \dots not currently used540#' @return cokurtosis matrix estimate541#' @seealso \code{\link{statistical.factor.model}}542#' @author Ross Bennett543#' @export544extractCokurtosis <- function(model, ...){545if(!inherits(model, "stfm")) stop("model must be of class 'stfm'")546547# Extract elements from the model548beta <- model$factor_loadings549f <- model$factor_realizations550res <- model$residuals551m <- model$m552k <- model$k553N <- model$N554555# Residual moments556denom <- m - k - 1557stockM2 <- colSums(res^2) / denom558stockM4 <- colSums(res^4) / denom559560# Factor moments561factorM2 <- cov(f)562# f.centered <- center(f)563# factorM4 <- M4.MM(f.centered)564factorM4 <- PerformanceAnalytics::M4.MM(f)565566# Compute covariance estimate567if(k == 1){568# Single factor model569out <- cokurtosisSF(beta, stockM2, stockM4, factorM2, factorM4)570} else if(k > 1){571# Multi-factor model572out <- cokurtosisMF(beta, stockM2, stockM4, factorM2, factorM4)573} else {574# invalid k575message("invalid k, returning NULL")576out <- NULL577}578return(out)579}580581582583