Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/R/stat.factor.model.R
1433 views
1
###############################################################################
2
# R (https://r-project.org/) Numeric Methods for Optimization of Portfolios
3
#
4
# Copyright (c) 2004-2021 Brian G. Peterson, Peter Carl, Ross Bennett, Kris Boudt
5
#
6
# This library is distributed under the terms of the GNU Public License (GPL)
7
# for full details see the file COPYING
8
#
9
# $Id: charts.DE.R 3378 2014-04-28 21:43:21Z rossbennett34 $
10
#
11
###############################################################################
12
13
# Note that many of these functions were provided by Kris Boudt and modified
14
# only slightly to work with this package
15
16
#' Statistical Factor Model
17
#'
18
#' Fit a statistical factor model using Principal Component Analysis (PCA)
19
#'
20
#' @details
21
#' The statistical factor model is fitted using \code{prcomp}. The factor
22
#' loadings, factor realizations, and residuals are computed and returned
23
#' given the number of factors used for the model.
24
#'
25
#' @param R xts of asset returns
26
#' @param k number of factors to use
27
#' @param \dots additional arguments passed to \code{prcomp}
28
#' @return
29
#' #' \describe{
30
#' \item{factor_loadings}{ N x k matrix of factor loadings (i.e. betas)}
31
#' \item{factor_realizations}{ m x k matrix of factor realizations}
32
#' \item{residuals}{ m x N matrix of model residuals representing idiosyncratic
33
#' risk factors}
34
#' }
35
#' Where N is the number of assets, k is the number of factors, and m is the
36
#' number of observations.
37
#' @export
38
statistical.factor.model <- function(R, k=1, ...){
39
if(!is.xts(R)){
40
R <- try(as.xts(R))
41
if(inherits(R, "try-error")) stop("R must be an xts object or coercible to an xts object")
42
}
43
# dimensions of R
44
m <- nrow(R)
45
N <- ncol(R)
46
47
# checks for R
48
if(m < N) stop("fewer observations than assets")
49
x <- coredata(R)
50
51
# Make sure k is an integer
52
if(k <= 0) stop("k must be a positive integer")
53
k <- as.integer(k)
54
55
# Fit a statistical factor model using Principal Component Analysis (PCA)
56
fit <- prcomp(x, ...=...)
57
58
# Extract the betas
59
# (N x k)
60
betas <- fit$rotation[, 1:k]
61
62
# Compute the estimated factor realizations
63
# (m x N) %*% (N x k) = (m x k)
64
f <- x %*% betas
65
66
# Compute the residuals
67
# These can be computed manually or by fitting a linear model
68
tmp <- x - f %*% t(betas)
69
b0 <- colMeans(tmp)
70
res <- tmp - matrix(rep(b0, m), ncol=N, byrow=TRUE)
71
72
# Compute residuals via fitting a linear model
73
# tmp.model <- lm(x ~ f)
74
# tmp.beta <- coef(tmp.model)[2:(k+1),]
75
# tmp.resid <- resid(tmp.model)
76
# all.equal(t(tmp.beta), betas, check.attributes=FALSE)
77
# all.equal(res, tmp.resid)
78
79
# structure and return
80
# stfm = *st*atistical *f*actor *m*odel
81
structure(list(factor_loadings=betas,
82
factor_realizations=f,
83
residuals=res,
84
m=m,
85
k=k,
86
N=N),
87
class="stfm")
88
}
89
90
91
#' Center
92
#'
93
#' Center a matrix
94
#'
95
#' This function is used primarily to center a time series of asset returns or
96
#' factors. Each column should represent the returns of an asset or factor
97
#' realizations. The expected value is taken as the sample mean.
98
#'
99
#' x.centered = x - mean(x)
100
#'
101
#' @param x matrix
102
#' @return matrix of centered data
103
#' @export
104
center <- function(x){
105
if(!is.matrix(x)) stop("x must be a matrix")
106
n <- nrow(x)
107
p <- ncol(x)
108
meanx <- colMeans(x)
109
x.centered <- x - matrix(rep(meanx, n), ncol=p, byrow=TRUE)
110
x.centered
111
}
112
113
##### Single Factor Model Comoments #####
114
115
#' Covariance Matrix Estimate
116
#'
117
#' Estimate covariance matrix using a single factor statistical factor model
118
#'
119
#' @details
120
#' This function estimates an (N x N) covariance matrix from a single factor
121
#' statistical factor model with k=1 factors, where N is the number of assets.
122
#'
123
#' @param beta vector of length N or (N x 1) matrix of factor loadings
124
#' (i.e. the betas) from a single factor statistical factor model
125
#' @param stockM2 vector of length N of the variance (2nd moment) of the
126
#' model residuals (i.e. idiosyncratic variance of the stock)
127
#' @param factorM2 scalar value of the 2nd moment of the factor realizations
128
#' from a single factor statistical factor model
129
#' @return (N x N) covariance matrix
130
covarianceSF <- function(beta, stockM2, factorM2){
131
# Beta of the stock with the factor index
132
beta = as.numeric(beta)
133
134
N = length(beta)
135
136
# Idiosyncratic variance of the stock
137
stockM2 = as.numeric(stockM2)
138
139
if(length(stockM2) != N) stop("dimensions do not match for beta and stockM2")
140
141
# Variance of the factor
142
factorM2 = as.numeric(factorM2)
143
144
# Coerce beta to a matrix
145
beta = matrix(beta, ncol = 1)
146
147
# Compute estimate
148
# S = (beta %*% t(beta)) * factorM2
149
S = tcrossprod(beta) * factorM2
150
D = diag(stockM2)
151
return(S + D)
152
}
153
154
#' Coskewness Matrix Estimate
155
#'
156
#' Estimate coskewness matrix using a single factor statistical factor model
157
#'
158
#' @details
159
#' This function estimates an (N x N^2) coskewness matrix from a single factor
160
#' statistical factor model with k=1 factors, where N is the number of assets.
161
#'
162
#' @param beta vector of length N or (N x 1) matrix of factor loadings
163
#' (i.e. the betas) from a single factor statistical factor model
164
#' @param stockM3 vector of length N of the 3rd moment of the model residuals
165
#' @param factorM3 scalar of the 3rd moment of the factor realizations from a
166
#' single factor statistical factor model
167
#' @return (N x N^2) coskewness matrix
168
coskewnessSF <- function(beta, stockM3, factorM3){
169
# Beta of the stock with the factor index
170
beta = as.numeric(beta)
171
N = length(beta)
172
173
# Idiosyncratic third moment of the stock
174
stockM3 = as.numeric(stockM3)
175
176
if(length(stockM3) != N) stop("dimensions do not match for beta and stockM3")
177
178
# Third moment of the factor
179
factorM3 = as.numeric(factorM3)
180
181
# Coerce beta to a matrix
182
beta = matrix(beta, ncol = 1)
183
184
# Compute estimate
185
# S = ((beta %*% t(beta)) %x% t(beta)) * factorM3
186
S = (tcrossprod(beta) %x% t(beta)) * factorM3
187
D = matrix(0, nrow=N, ncol=N^2)
188
for(i in 1:N){
189
col = (i - 1) * N + i
190
D[i, col] = stockM3[i]
191
}
192
return(S + D)
193
}
194
195
#' Cokurtosis Matrix Estimate
196
#'
197
#' Estimate cokurtosis matrix using a single factor statistical factor model
198
#'
199
#' @details
200
#' This function estimates an (N x N^3) cokurtosis matrix from a statistical
201
#' factor model with k factors, where N is the number of assets.
202
#'
203
#' @param beta vector of length N or (N x 1) matrix of factor loadings
204
#' (i.e. the betas) from a single factor statistical factor model
205
#' @param stockM2 vector of length N of the 2nd moment of the model residuals
206
#' @param stockM4 vector of length N of the 4th moment of the model residuals
207
#' @param factorM2 scalar of the 2nd moment of the factor realizations from a
208
#' single factor statistical factor model
209
#' @param factorM4 scalar of the 4th moment of the factor realizations from a
210
#' single factor statistical factor model
211
#' @return (N x N^3) cokurtosis matrix
212
cokurtosisSF <- function(beta, stockM2, stockM4, factorM2, factorM4){
213
# Beta of the stock with the factor index
214
beta = as.numeric(beta)
215
N = as.integer(length(beta))
216
217
# Idiosyncratic second moment of the stock
218
stockM2 = as.numeric(stockM2)
219
220
if(length(stockM2) != N) stop("dimensions do not match for beta and stockM2")
221
222
# Idiosyncratic fourth moment of the stock
223
stockM4 = as.numeric(stockM4)
224
225
if(length(stockM4) != N) stop("dimensions do not match for beta and stockM4")
226
227
# Second moment of the factor
228
factorM2 = as.numeric(factorM2)
229
230
# Fourth moment of the factor
231
factorM4 = as.numeric(factorM4)
232
233
# Compute estimate
234
# S = ((beta %*% t(beta)) %x% t(beta) %x% t(beta)) * factorM4
235
S = (tcrossprod(beta) %x% t(beta) %x% t(beta)) * factorM4
236
D = .residualcokurtosisSF(NN=N, sstockM2=stockM2, sstockM4=stockM4, mfactorM2=factorM2, bbeta=beta)
237
return(S + D)
238
}
239
240
# Wrapper function to compute the residual cokurtosis matrix of a statistical
241
# factor model with k = 1.
242
# Note that this function was orignally written in C++ (using Rcpp) by
243
# Joshua Ulrich and re-written using the C API by Ross Bennett
244
#' @useDynLib "PortfolioAnalytics"
245
.residualcokurtosisSF <- function(NN, sstockM2, sstockM4, mfactorM2, bbeta){
246
# NN : integer
247
# sstockM2 : vector of length NN
248
# sstockM4 : vector of length NN
249
# mfactorM2 : double
250
# bbeta : vector of length NN
251
252
if(!is.integer(NN)) NN <- as.integer(NN)
253
if(length(sstockM2) != NN) stop("sstockM2 must be a vector of length NN")
254
if(length(sstockM4) != NN) stop("sstockM4 must be a vector of length NN")
255
if(!is.double(mfactorM2)) mfactorM2 <- as.double(mfactorM2)
256
if(length(bbeta) != NN) stop("bbeta must be a vector of length NN")
257
258
.Call('residualcokurtosisSF', NN, sstockM2, sstockM4, mfactorM2, bbeta, PACKAGE="PortfolioAnalytics")
259
}
260
261
##### Multiple Factor Model Comoments #####
262
263
#' Covariance Matrix Estimate
264
#'
265
#' Estimate covariance matrix using a statistical factor model
266
#'
267
#' @details
268
#' This function estimates an (N x N) covariance matrix from a statistical
269
#' factor model with k factors, where N is the number of assets.
270
#'
271
#' @param beta (N x k) matrix of factor loadings (i.e. the betas) from a
272
#' statistical factor model
273
#' @param stockM2 vector of length N of the variance (2nd moment) of the
274
#' model residuals (i.e. idiosyncratic variance of the stock)
275
#' @param factorM2 (k x k) matrix of the covariance (2nd moment) of the factor
276
#' realizations from a statistical factor model
277
#' @return (N x N) covariance matrix
278
covarianceMF <- function(beta, stockM2, factorM2){
279
# Formula for covariance matrix estimate
280
# Sigma = beta %*% factorM2 %*% beta**T + Delta
281
# Delta is a diagonal matrix with the 2nd moment of residuals on the diagonal
282
283
# N = number of assets
284
# k = number of factors
285
286
# Use the dimensions of beta for checks of stockM2 and factorM2
287
# beta should be an (N x k) matrix
288
if(!is.matrix(beta)) stop("beta must be a matrix")
289
N <- nrow(beta)
290
k <- ncol(beta)
291
292
# stockM2 should be a vector of length N
293
stockM2 <- as.numeric(stockM2)
294
if(length(stockM2) != N) stop("dimensions do not match for beta and stockM2")
295
296
# factorM2 should be a (k x k) matrix
297
if(!is.matrix(factorM2)) stop("factorM2 must be a matrix")
298
if((nrow(factorM2) != k) | (ncol(factorM2) != k)){
299
stop("dimensions do not match for beta and factorM2")
300
}
301
302
# Compute covariance matrix
303
S <- beta %*% tcrossprod(factorM2, beta)
304
D <- diag(stockM2)
305
return(S + D)
306
}
307
308
#' Coskewness Matrix Estimate
309
#'
310
#' Estimate coskewness matrix using a statistical factor model
311
#'
312
#' @details
313
#' This function estimates an (N x N^2) coskewness matrix from a statistical
314
#' factor model with k factors, where N is the number of assets.
315
#'
316
#' @param beta (N x k) matrix of factor loadings (i.e. the betas) from a
317
#' statistical factor model
318
#' @param stockM3 vector of length N of the 3rd moment of the model residuals
319
#' @param factorM3 (k x k^2) matrix of the 3rd moment of the factor
320
#' realizations from a statistical factor model
321
#' @return (N x N^2) coskewness matrix
322
coskewnessMF <- function(beta, stockM3, factorM3){
323
# Formula for coskewness matrix estimate
324
# Phi = beta %*% factorM3 %*% (beta**T %x% beta**T) + Omega
325
# %x% is the kronecker product
326
# Omega is the (N x N^2) matrix matrix of zeros except for the i,j elements
327
# where j = (i - 1) * N + i, which is corresponding to the expected third
328
# moment of the idiosyncratic factors
329
330
# N = number of assets
331
# k = number of factors
332
333
# Use the dimensions of beta for checks of stockM2 and factorM2
334
# beta should be an (N x k) matrix
335
if(!is.matrix(beta)) stop("beta must be a matrix")
336
N <- nrow(beta)
337
k <- ncol(beta)
338
339
# stockM3 should be a vector of length N
340
stockM3 <- as.numeric(stockM3)
341
if(length(stockM3) != N) stop("dimensions do not match for beta and stockM3")
342
343
# factorM3 should be an (k x k^2) matrix
344
if(!is.matrix(factorM3)) stop("factorM3 must be a matrix")
345
if((nrow(factorM3) != k) | (ncol(factorM3) != k^2)){
346
stop("dimensions do not match for beta and factorM3")
347
}
348
349
# Compute coskewness matrix
350
beta.t <- t(beta)
351
S <- (beta %*% factorM3) %*% (beta.t %x% beta.t)
352
D <- matrix(0, nrow=N, ncol=N^2)
353
for(i in 1:N){
354
col <- (i - 1) * N + i
355
D[i, col] <- stockM3[i]
356
}
357
return(S + D)
358
}
359
360
#' Cokurtosis Matrix Estimate
361
#'
362
#' Estimate cokurtosis matrix using a statistical factor model
363
#'
364
#' @details
365
#' This function estimates an (N x N^3) cokurtosis matrix from a statistical
366
#' factor model with k factors, where N is the number of assets.
367
#'
368
#' @param beta (N x k) matrix of factor loadings (i.e. the betas) from a
369
#' statistical factor model
370
#' @param stockM2 vector of length N of the 2nd moment of the model residuals
371
#' @param stockM4 vector of length N of the 4th moment of the model residuals
372
#' @param factorM2 (k x k) matrix of the 2nd moment of the factor
373
#' realizations from a statistical factor model
374
#' @param factorM4 (k x k^3) matrix of the 4th moment of the factor
375
#' realizations from a statistical factor model
376
#' @return (N x N^3) cokurtosis matrix
377
cokurtosisMF <- function(beta , stockM2 , stockM4 , factorM2 , factorM4){
378
379
# Formula for cokurtosis matrix estimate
380
# Psi = beta %*% factorM4 %*% (beta**T %x% beta**T %x% beta**T) + Y
381
# %x% is the kronecker product
382
# Y is the residual matrix.
383
# see Asset allocation with higher order moments and factor models for
384
# definition of Y
385
386
# N = number of assets
387
# k = number of factors
388
389
# Use the dimensions of beta for checks of stockM2 and factorM2
390
# beta should be an (N x k) matrix
391
if(!is.matrix(beta)) stop("beta must be a matrix")
392
N <- nrow(beta)
393
k <- ncol(beta)
394
395
# stockM2 should be a vector of length N
396
stockM2 <- as.numeric(stockM2)
397
if(length(stockM2) != N) stop("dimensions do not match for beta and stockM2")
398
399
# stockM4 should be a vector of length N
400
stockM4 <- as.numeric(stockM4)
401
if(length(stockM4) != N) stop("dimensions do not match for beta and stockM4")
402
403
# factorM2 should be a (k x k) matrix
404
if(!is.matrix(factorM2)) stop("factorM2 must be a matrix")
405
if((nrow(factorM2) != k) | (ncol(factorM2) != k)){
406
stop("dimensions do not match for beta and factorM2")
407
}
408
409
# factorM4 should be a (k x k^3) matrix
410
if(!is.matrix(factorM4)) stop("factorM4 must be a matrix")
411
if((nrow(factorM4) != k) | (ncol(factorM4) != k^3)){
412
stop("dimensions do not match for beta and factorM4")
413
}
414
415
# Compute cokurtosis matrix
416
beta.t <- t(beta)
417
S <- (beta %*% factorM4) %*% (beta.t %x% beta.t %x% beta.t)
418
# betacov
419
betacov <- as.numeric(beta %*% tcrossprod(factorM2, beta))
420
# Compute the residual cokurtosis matrix
421
D <- .residualcokurtosisMF(NN=N, sstockM2=stockM2, sstockM4=stockM4, bbetacov=betacov)
422
return( S + D )
423
}
424
425
# Wrapper function to compute the residual cokurtosis matrix of a statistical
426
# factor model with k > 1.
427
# Note that this function was orignally written in C++ (using Rcpp) by
428
# Joshua Ulrich and re-written using the C API by Ross Bennett
429
#' @useDynLib "PortfolioAnalytics"
430
.residualcokurtosisMF <- function(NN, sstockM2, sstockM4, bbetacov){
431
# NN : integer, number of assets
432
# sstockM2 : numeric vector of length NN
433
# ssstockM4 : numeric vector of length NN
434
# bbetacov : numeric vector of length NN * NN
435
436
if(!is.integer(NN)) NN <- as.integer(NN)
437
if(length(sstockM2) != NN) stop("sstockM2 must be a vector of length NN")
438
if(length(sstockM4) != NN) stop("sstockM4 must be a vector of length NN")
439
if(length(bbetacov) != NN*NN) stop("bbetacov must be a vector of length NN*NN")
440
441
.Call('residualcokurtosisMF', NN, sstockM2, sstockM4, bbetacov, PACKAGE="PortfolioAnalytics")
442
}
443
444
##### Extract Moments #####
445
446
#' Covariance Estimate
447
#'
448
#' Extract the covariance matrix estimate from a statistical factor model
449
#'
450
#' @param model statistical factor model estimated via
451
#' \code{\link{statistical.factor.model}}
452
#' @param \dots not currently used
453
#' @return covariance matrix estimate
454
#' @seealso \code{\link{statistical.factor.model}}
455
#' @author Ross Bennett
456
#' @export
457
extractCovariance <- function(model, ...){
458
if(!inherits(model, "stfm")) stop("model must be of class 'stfm'")
459
460
# Extract elements from the model
461
beta <- model$factor_loadings
462
f <- model$factor_realizations
463
res <- model$residuals
464
m <- model$m
465
k <- model$k
466
N <- model$N
467
468
# Residual moments
469
denom <- m - k - 1
470
stockM2 <- colSums(res^2) / denom
471
472
# Factor moments
473
factorM2 <- cov(f)
474
475
# Compute covariance estimate
476
if(k == 1){
477
out <- covarianceSF(beta, stockM2, factorM2)
478
} else if(k > 1){
479
out <- covarianceMF(beta, stockM2, factorM2)
480
} else {
481
# invalid k
482
message("invalid k, returning NULL")
483
out <- NULL
484
}
485
return(out)
486
}
487
488
#' Coskewness Estimate
489
#'
490
#' Extract the coskewness matrix estimate from a statistical factor model
491
#'
492
#' @param model statistical factor model estimated via
493
#' \code{\link{statistical.factor.model}}
494
#' @param \dots not currently used
495
#' @return coskewness matrix estimate
496
#' @seealso \code{\link{statistical.factor.model}}
497
#' @author Ross Bennett
498
#' @export
499
extractCoskewness <- function(model, ...){
500
if(!inherits(model, "stfm")) stop("model must be of class 'stfm'")
501
502
# Extract elements from the model
503
beta <- model$factor_loadings
504
f <- model$factor_realizations
505
res <- model$residuals
506
m <- model$m
507
k <- model$k
508
N <- model$N
509
510
# Residual moments
511
denom <- m - k - 1
512
stockM3 <- colSums(res^3) / denom
513
514
# Factor moments
515
# f.centered <- center(f)
516
# factorM3 <- M3.MM(f.centered)
517
factorM3 <- PerformanceAnalytics::M3.MM(f)
518
519
# Compute covariance estimate
520
if(k == 1){
521
# Single factor model
522
out <- coskewnessSF(beta, stockM3, factorM3)
523
} else if(k > 1){
524
# Multi-factor model
525
out <- coskewnessMF(beta, stockM3, factorM3)
526
} else {
527
# invalid k
528
message("invalid k, returning NULL")
529
out <- NULL
530
}
531
return(out)
532
}
533
534
#' Cokurtosis Estimate
535
#'
536
#' Extract the cokurtosis matrix estimate from a statistical factor model
537
#'
538
#' @param model statistical factor model estimated via
539
#' \code{\link{statistical.factor.model}}
540
#' @param \dots not currently used
541
#' @return cokurtosis matrix estimate
542
#' @seealso \code{\link{statistical.factor.model}}
543
#' @author Ross Bennett
544
#' @export
545
extractCokurtosis <- function(model, ...){
546
if(!inherits(model, "stfm")) stop("model must be of class 'stfm'")
547
548
# Extract elements from the model
549
beta <- model$factor_loadings
550
f <- model$factor_realizations
551
res <- model$residuals
552
m <- model$m
553
k <- model$k
554
N <- model$N
555
556
# Residual moments
557
denom <- m - k - 1
558
stockM2 <- colSums(res^2) / denom
559
stockM4 <- colSums(res^4) / denom
560
561
# Factor moments
562
factorM2 <- cov(f)
563
# f.centered <- center(f)
564
# factorM4 <- M4.MM(f.centered)
565
factorM4 <- PerformanceAnalytics::M4.MM(f)
566
567
# Compute covariance estimate
568
if(k == 1){
569
# Single factor model
570
out <- cokurtosisSF(beta, stockM2, stockM4, factorM2, factorM4)
571
} else if(k > 1){
572
# Multi-factor model
573
out <- cokurtosisMF(beta, stockM2, stockM4, factorM2, factorM4)
574
} else {
575
# invalid k
576
message("invalid k, returning NULL")
577
out <- NULL
578
}
579
return(out)
580
}
581
582
583