Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/R/extractstats.R
1433 views
1
###############################################################################
2
# R (https://r-project.org/) Numeric Methods for Optimization of Portfolios
3
#
4
# Copyright (c) 2004-2023 Brian G. Peterson, Peter Carl, Ross Bennett, Kris Boudt, Xinran Zhao
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$
10
#
11
###############################################################################
12
13
#' utility function to replace awkward named from unlist
14
#' @param rnames character vector of names to check for cleanup
15
name.replace <- function(rnames){
16
rnames<-gsub("objective_measures.",'',rnames)
17
matchvec<-c('mean.mean','median.median','ES.ES','ETL.ETL','CVaR.ES','ES.MES','ETL.MES','CVaR.MES','VaR.MVaR','maxDrawdown.maxDrawdown','sd.sd','StdDev.StdDev')
18
for(str in matchvec){
19
pos<-pmatch(str,rnames)
20
if(!is.na(pos)){
21
switch(str,
22
mean.mean = {rnames[pos]<-'mean'},
23
median.median = {rnames[pos]<-'median'},
24
CVaR.MES =, CVaR.ES = {rnames[pos]<-'CVaR'},
25
ES.MES =, ES.ES = {rnames[pos]<-'ES'},
26
ETL.MES =, ETL.ETL = {rnames[pos]<-'ETL'},
27
VaR.MVaR = {rnames[pos]<-'VaR'},
28
maxDrawdown.maxDrawdown = {rnames[pos]<-'maxDrawdown'},
29
sd.sd=, StdDev.StdDev = {rnames[pos]<-'StdDev'},
30
#pamean={rnames[pos]<-'mean'}
31
)
32
}
33
}
34
return(rnames)
35
}
36
37
##### extractStats #####
38
39
#' extract some stats and weights from a portfolio run via \code{optimize.portfolio}
40
#'
41
#' This function will dispatch to the appropriate class handler based on the
42
#' input class of the optimize.portfolio output object.
43
#'
44
#' For \code{optimize.portfolio} objects:
45
#'
46
#' In general, \code{extractStats} will extract the values objective measures
47
#' and weights at each iteration of a set of weights. This is the case for the
48
#' DEoptim, random portfolios, and pso solvers that return trace information.
49
#' Note that \code{trace=TRUE} must be specified in \code{optimize.portfolio}
50
#' to return the trace information.
51
#'
52
#' For \code{optimize.portfolio.pso} objects, this function will extract the
53
#' weights (swarm positions) from the PSO output and the out values
54
#' (swarm fitness values) for each iteration of the optimization.
55
#' This function can be slow because we need to run \code{constrained_objective}
56
#' to calculate the objective measures on the transformed weights.
57
#'
58
#' For \code{optimize.portfolio.rebalancing} objects:
59
#'
60
#' The \code{extractStats} function will return a list of the objective measures
61
#' and weights at each rebalance date for \code{optimize.portfolio.rebalancing}
62
#' objects. The objective measures and weights of each iteration or permutation
63
#' will be returned if the optimization was done with DEoptim, random portfolios,
64
#' or pso. This could potentially result in a very large list object where each
65
#' list element has thousands of rows of at each rebalance period.
66
#'
67
#' The output from the GenSA solver does not store weights evaluated at each iteration
68
#' The GenSA output for trace.mat contains nb.steps, temperature, function.value, and current.minimum
69
#'
70
#' @param object list returned by optimize.portfolio
71
#' @param prefix prefix to add to output row names
72
#' @param ... any other passthru parameters
73
#' @seealso \code{\link{optimize.portfolio}}
74
#' @aliases extractStats extractStats.optimize.portfolio.DEoptim
75
#' extractStats.optimize.portfolio.parallel extractStats.optimize.portfolio.random
76
#' extractStats.optimize.portfolio.ROI extractStats.optimize.portfolio.pso
77
#' extractStats.optimize.portfolio.GenSA
78
#' @export
79
extractStats <- function (object, prefix=NULL, ...){
80
UseMethod('extractStats')
81
}
82
83
#' @method extractStats optimize.portfolio.DEoptim
84
85
#' @export
86
extractStats.optimize.portfolio.DEoptim <- function(object, prefix=NULL, ...) {
87
if(!inherits(object, "optimize.portfolio.DEoptim")) stop("object must be of class optimize.portfolio.DEoptim")
88
89
# Check if object$DEoptim_objective_results is null, the user called optimize.portfolio with trace=FALSE
90
if(is.null(object$DEoptim_objective_results)) stop("DEoptim_objective_results is null, trace=TRUE must be specified in optimize.portfolio")
91
92
# first pull out the optimal portfolio
93
trow<-c(unlist(object$objective_measures),out=object$out,object$weights)
94
#colnames(trow)<-c(colnames(unlist(object$objective_measures)),'out',names(object$weights))
95
result<-trow
96
l = length(object$DEoptim_objective_results)
97
nobj<-length(unlist(object$DEoptim_objective_results[[1]]$objective_measures))
98
result=matrix(nrow=l,ncol=(nobj+length(object$weights))+1)
99
ncols<-ncol(result)
100
101
for (i in 1:l) {
102
if(!is.atomic(object$DEoptim_objective_results[[i]])) {
103
result[i,1:nobj]<-unlist(object$DEoptim_objective_results[[i]]$objective_measures)
104
result[i,(nobj+1)]<-object$DEoptim_objective_results[[i]]$out
105
result[i,(nobj+2):ncols]<-object$DEoptim_objective_results[[i]]$weights
106
}
107
}
108
109
rnames<-c(names(unlist(object$DEoptim_objective_results[[1]]$objective_measures)),'out',paste('w',names(object$weights),sep='.'))
110
rnames<-name.replace(rnames)
111
colnames(result)<-rnames
112
rownames(result) = paste(prefix,"DE.portf", index(object$DEoptim_objective_results), sep=".")
113
#rownames(result) = paste("DE.portf.", index(result), sep="")
114
return(result)
115
}
116
117
#' @method extractStats optimize.portfolio.ROI
118
119
#' @export
120
extractStats.optimize.portfolio.ROI <- function(object, prefix=NULL, ...) {
121
if(!inherits(object, "optimize.portfolio.ROI")) stop("object must be of class optimize.portfolio.ROI")
122
trow <- c(object$out, object$weights)
123
objmeas <- extractObjectiveMeasures(object)
124
objnames <- names(objmeas)
125
obj <- unlist(objmeas)
126
result <- c(obj, trow)
127
rnames<-c(objnames, 'out', paste('w', names(object$weights), sep='.'))
128
#print(result)
129
#print(rnames)
130
names(result)<-rnames
131
return(result)
132
}
133
134
#' @method extractStats optimize.portfolio.CVXR
135
136
#' @export
137
extractStats.optimize.portfolio.CVXR <- function(object, prefix=NULL, ...) {
138
if(!inherits(object, "optimize.portfolio.CVXR")) stop("object must be of class optimize.portfolio.CVXR")
139
trow <- c(object$out, object$weights)
140
objmeas <- extractObjectiveMeasures(object)
141
objnames <- names(objmeas)
142
obj <- unlist(objmeas)
143
result <- c(obj, trow)
144
rnames<-c(objnames, 'out', paste('w', names(object$weights), sep='.'))
145
#print(result)
146
#print(rnames)
147
names(result)<-rnames
148
return(result)
149
}
150
151
#' @method extractStats optimize.portfolio.pso
152
153
#' @export
154
extractStats.optimize.portfolio.pso <- function(object, prefix=NULL, ...){
155
if(!inherits(object, "optimize.portfolio.pso")) stop("object must be of class optimize.portfolio.pso")
156
157
# Check if object$PSOoutput is null, the user called optimize.portfolio with trace=FALSE
158
if(is.null(object$PSOoutput)) stop("PSOoutput is null, trace=TRUE must be specified in optimize.portfolio")
159
160
R <- object$R
161
portfolio <- object$portfolio
162
163
normalize_weights <- function(weights){
164
# normalize results if necessary
165
if(!is.null(constraints$min_sum) | !is.null(constraints$max_sum)){
166
# the user has passed in either min_sum or max_sum constraints for the portfolio, or both.
167
# we'll normalize the weights passed in to whichever boundary condition has been violated
168
# NOTE: this means that the weights produced by a numeric optimization algorithm like DEoptim
169
# might violate your constraints, so you'd need to renormalize them after optimizing
170
# we'll create functions for that so the user is less likely to mess it up.
171
172
# NOTE: need to normalize in the optimization wrapper too before we return, since we've normalized in here
173
# In Kris' original function, this was manifested as a full investment constraint
174
if(!is.null(constraints$max_sum) & constraints$max_sum != Inf ) {
175
max_sum=constraints$max_sum
176
if(sum(weights)>max_sum) { weights<-(max_sum/sum(weights))*weights } # normalize to max_sum
177
}
178
179
if(!is.null(constraints$min_sum) & constraints$min_sum != -Inf ) {
180
min_sum=constraints$min_sum
181
if(sum(weights)<min_sum) { weights<-(min_sum/sum(weights))*weights } # normalize to min_sum
182
}
183
184
} # end min_sum and max_sum normalization
185
return(weights)
186
}
187
188
# get the constraints for min_sum and max_sum normalization
189
constraints <- get_constraints(object$portfolio)
190
191
# optimal portfolio
192
# trow <- c(unlist(object$objective_measures), out=object$out, object$weights)
193
194
# get the weights of each iteration from PSOoutput
195
psoweights <- do.call(rbind, lapply(object$PSOoutput$stats$x, t))
196
197
# need to normalize so that psoweights are valid portfolios
198
psoweights <- t(apply(psoweights, 1, normalize_weights))
199
200
# bind the optimal weights to psoweights
201
psoweights <- rbind(object$weights, psoweights)
202
203
# get swarm fitness values (i.e. out value of the function evaluated with the swarm positions)
204
tmpout <- unlist(object$PSOoutput$stats$f)
205
206
# combine the optimal out value to the vector of out values
207
tmpout <- c(object$out, tmpout)
208
209
# run constrained_objective on the weights to get the objective measures in a matrix
210
stopifnot("package:foreach" %in% search() || suppressMessages(requireNamespace("foreach",quietly = TRUE)))
211
i <- 1
212
obj <- foreach::foreach(i=1:nrow(psoweights), .inorder=TRUE, .combine=rbind, .errorhandling='remove') %dopar% {
213
unlist(constrained_objective(w=psoweights[i,], R=R, portfolio=portfolio, trace=TRUE)$objective_measures)
214
}
215
objnames <- name.replace(colnames(obj))
216
result <- cbind(obj, tmpout, psoweights)
217
colnames(result) <- c(objnames, "out", paste('w',names(object$weights),sep='.'))
218
rownames(result) <- paste(prefix, "pso.portf", index(tmpout), sep=".")
219
return(result)
220
}
221
222
#' @method extractStats optimize.portfolio.GenSA
223
224
#' @export
225
extractStats.optimize.portfolio.GenSA <- function(object, prefix=NULL, ...) {
226
if(!inherits(object, "optimize.portfolio.GenSA")) stop("object must be of class optimize.portfolio.GenSA")
227
228
# Check if object$GenSAoutput is null, the user called optimize.portfolio with trace=FALSE
229
if(is.null(object$GenSAoutput)) stop("GenSAoutput is null, trace=TRUE must be specified in optimize.portfolio")
230
231
trow<-c(out=object$out, object$weights)
232
obj <- unlist(object$objective_measures)
233
result <- c(obj, trow)
234
235
rnames <- name.replace(names(result))
236
names(result) <- rnames
237
return(result)
238
}
239
240
#' @method extractStats optimize.portfolio.invol
241
242
#' @export
243
extractStats.optimize.portfolio.invol <- function(object, prefix=NULL, ...) {
244
if(!inherits(object, "optimize.portfolio.invol")) stop("object must be of class optimize.portfolio.invol")
245
trow<-c(out=object$out, object$weights)
246
247
obj <- unlist(object$objective_measures)
248
result <- c(obj, trow)
249
250
rnames <- name.replace(names(result))
251
names(result) <- rnames
252
return(result)
253
}
254
255
#' @method extractStats optimize.portfolio.eqwt
256
257
#' @export
258
extractStats.optimize.portfolio.eqwt <- function(object, prefix=NULL, ...) {
259
if(!inherits(object, "optimize.portfolio.eqwt")) stop("object must be of class optimize.portfolio.eqwt")
260
trow<-c(out=object$out, object$weights)
261
262
obj <- unlist(object$objective_measures)
263
result <- c(obj, trow)
264
265
rnames <- name.replace(names(result))
266
names(result) <- rnames
267
return(result)
268
}
269
270
#' @method extractStats optimize.portfolio.rebalancing
271
272
#' @export
273
extractStats.optimize.portfolio.rebalancing <- function(object, prefix=NULL, ...) {
274
if(!inherits(object, "optimize.portfolio.rebalancing")) stop("object must be of class optimize.portfolio.rebalancing")
275
276
if(inherits(object$portfolio, "regime.portfolios")){
277
return(extractStatsRegime(object, prefix=prefix))
278
} else {
279
return(lapply(object$opt_rebal, extractStats, ...))
280
}
281
}
282
283
# Helper function for extractStats.optimize.portfolio.rebalancing
284
# with regime switching.
285
# If I have N different regimes and N different portfolios, then
286
# extractStats should return a list of length N where each element
287
# contains the extractStats output for a given regime
288
extractStatsRegime <- function(object, prefix=NULL){
289
tmp.regimes <- unlist(lapply(object$opt_rebalancing, function(x) x$regime))
290
unique.regimes <- unique(tmp.regimes)
291
292
# Initialize a list to hold the optimize.portfolio objects for each regime
293
out.list <- vector("list", length(unique.regimes))
294
names(out.list) <- paste("regime", 1:length(unique.regimes), sep=".")
295
296
# Outer loop over each regime
297
for(i in 1:length(unique.regimes)){
298
# Get the index for each regime
299
tmp.idx <- which(tmp.regimes == unique.regimes[i])
300
301
# Initialize a temporary list to store the extractStats output for each
302
# unique regime
303
tmp <- vector("list", length(tmp.idx))
304
305
# Nested loop over each optimize.portfolio object of the corresponding regime
306
for(j in 1:length(tmp)){
307
tmp[[j]] <- extractStats(object$opt_rebalancing[[tmp.idx[j]]], prefix=prefix)
308
}
309
out.list[[i]] <- tmp
310
}
311
out.list
312
}
313
314
#' @method extractStats optimize.portfolio.parallel
315
316
#' @export
317
extractStats.optimize.portfolio.parallel <- function(object,prefix=NULL,...) {
318
resultlist<-object
319
l = length(resultlist)
320
result=NULL
321
for (i in 1:l) {
322
if(is.null(result)) result<-extractStats(resultlist[[i]])
323
else result <- rbind(result,extractStats(resultlist[[i]]))
324
}
325
326
rownames(result) = paste("par", index(result), rownames(result), sep=".")
327
return(result)
328
}
329
330
#' @method extractStats optimize.portfolio.random
331
332
#' @export
333
extractStats.optimize.portfolio.random <- function(object, prefix=NULL, ...){
334
# This just flattens the $random_portfolio_objective_results part of the object
335
if(!inherits(object, "optimize.portfolio.random")) stop("object must be of class optimize.portfolio.random")
336
337
# Check if object$random_portfolio_objective_results is null, the user called optimize.portfolio with trace=FALSE
338
if(is.null(object$random_portfolio_objective_results)) stop("random_portfolio_objective_results is null, trace=TRUE must be specified in optimize.portfolio")
339
340
OptimResults<-object
341
342
l = length(OptimResults$random_portfolio_objective_results)
343
nobj<-length(unlist(OptimResults$random_portfolio_objective_results[[1]]$objective_measures))
344
result=matrix(nrow=l,ncol=(nobj+length(OptimResults$weights))+1)
345
ncols<-ncol(result)
346
347
for (i in 1:l) {
348
if(!is.atomic(OptimResults$random_portfolio_objective_results[[i]])) {
349
result[i,1:nobj]<-unlist(OptimResults$random_portfolio_objective_results[[i]]$objective_measures)
350
result[i,(nobj+1)]<-OptimResults$random_portfolio_objective_results[[i]]$out
351
result[i,(nobj+2):ncols]<-OptimResults$random_portfolio_objective_results[[i]]$weights
352
}
353
}
354
355
rnames<-c(names(unlist(OptimResults$random_portfolio_objective_results[[1]]$objective_measures)),'out',paste('w',names(OptimResults$weights),sep='.'))
356
rnames<-name.replace(rnames)
357
colnames(result)<-rnames
358
rownames(result) = paste(prefix,"rnd.portf", index(OptimResults$random_portfolio_objective_results), sep=".")
359
360
return(result)
361
}
362
363
#' @method extractStats opt.list
364
365
#' @export
366
extractStats.opt.list <- function(object, ...){
367
# get the stats of each optimization in a list
368
# each element in the list is an optimize.portfolio object
369
stats_list <- vector("list", length(object))
370
for(i in 1:length(stats_list)){
371
stats_list[[i]] <- extractStats(object[[i]])
372
}
373
return(stats_list)
374
}
375
376
#' @method extractStats opt.rebal.list
377
378
#' @export
379
extractStats.opt.rebal.list <- function(object, ...){
380
# get the stats of each optimization in a list
381
# each element in the list is an optimize.portfolio.rebalancing object
382
stats_list <- vector("list", length(object))
383
for(i in 1:length(stats_list)){
384
stats_list[[i]] <- extractStats(object[[i]])
385
}
386
return(stats_list)
387
}
388
389
##### extractWeights #####
390
391
#' Extract weights from a portfolio run via \code{optimize.portfolio} or \code{optimize.portfolio.rebalancing}
392
#'
393
#' This function will dispatch to the appropriate class handler based on the
394
#' input class of the optimize.portfolio or optimize.portfolio.rebalancing output object
395
#'
396
#' @param object list returned by optimize.portfolio
397
#' @param \dots any other passthru parameters
398
#' @seealso \code{\link{optimize.portfolio}}, \code{\link{optimize.portfolio.rebalancing}}
399
#' @export
400
extractWeights <- function (object, ...){
401
UseMethod('extractWeights')
402
}
403
404
#' @method extractWeights optimize.portfolio
405
406
#' @export
407
extractWeights.optimize.portfolio <- function(object, ...){
408
if(!inherits(object, "optimize.portfolio")){
409
stop("object must be of class 'optimize.portfolio'")
410
}
411
return(object$weights)
412
}
413
414
#' @method extractWeights optimize.portfolio.rebalancing
415
416
#' @export
417
extractWeights.optimize.portfolio.rebalancing <- function(object, ...){
418
if(!inherits(object, "optimize.portfolio.rebalancing")){
419
stop("Object passed in must be of class 'optimize.portfolio.rebalancing'")
420
}
421
rebal_object <- object$opt_rebal
422
numColumns = length(rebal_object[[1]]$weights)
423
numRows = length(rebal_object)
424
425
result <- matrix(nrow=numRows, ncol=numColumns)
426
427
for(i in 1:numRows)
428
result[i,] = unlist(rebal_object[[i]]$weights)
429
430
colnames(result) = names(unlist(rebal_object[[1]]$weights))
431
rownames(result) = names(rebal_object)
432
result = as.xts(result, dateFormat="Date")
433
return(result)
434
}
435
436
#' @method extractWeights summary.optimize.portfolio.rebalancing
437
438
#' @export
439
extractWeights.summary.optimize.portfolio.rebalancing <- function(object, ...){
440
object$weights
441
}
442
443
#' @method extractWeights opt.list
444
445
#' @export
446
extractWeights.opt.list <- function(object, ...){
447
# get the optimal weights of each optimization in a list
448
weights_list <- list()
449
for(i in 1:length(object)){
450
weights_list[[i]] <- object[[i]]$weights
451
}
452
453
# get/set the names in the object
454
opt_names <- names(object)
455
if(is.null(opt_names)) opt_names <- paste("opt", 1:length(object))
456
457
# get the names of each element in weights_list
458
weights_names <- unlist(lapply(weights_list, names))
459
460
# unique names in weights_names
461
names_unique <- unique(weights_names)
462
463
# create a matrix of zeros to fill in with weights later
464
weights_mat <- matrix(0, nrow=length(weights_list), ncol=length(names_unique),
465
dimnames=list(opt_names, names_unique))
466
for(i in 1:length(weights_list)){
467
pm <- pmatch(x=names(weights_list[[i]]), table=names_unique)
468
weights_mat[i, pm] <- weights_list[[i]]
469
}
470
return(weights_mat)
471
}
472
473
#' @method extractWeights opt.rebal.list
474
475
#' @export
476
extractWeights.opt.rebal.list <- function(object, ...){
477
# get the optimal weights of each optimization in a list
478
# each element in the list is an optimize.portfolio.rebalancing object
479
weights_list <- vector("list", length(object))
480
for(i in 1:length(weights_list)){
481
weights_list[[i]] <- extractWeights(object[[i]])
482
}
483
return(weights_list)
484
}
485
486
##### extractObjectiveMeasures #####
487
488
#' Extract the objective measures
489
#'
490
#' This function will extract the objective measures from the optimal portfolio
491
#' run via \code{optimize.portfolio}
492
#'
493
#' @param object list returned by optimize.portfolio
494
#' @return list of objective measures
495
#' @seealso \code{\link{optimize.portfolio}}
496
#' @author Ross Bennett
497
#' @export
498
extractObjectiveMeasures <- function(object){
499
UseMethod("extractObjectiveMeasures")
500
}
501
502
#' @method extractObjectiveMeasures optimize.portfolio
503
504
#' @export
505
extractObjectiveMeasures.optimize.portfolio <- function(object){
506
if(!inherits(object, "optimize.portfolio")) stop("object must be of class 'optimize.portfolio'")
507
# objective measures returned as $objective_measures for all other solvers
508
out <- object$objective_measures
509
return(out)
510
}
511
512
#' @method extractObjectiveMeasures optimize.portfolio.rebalancing
513
514
#' @export
515
extractObjectiveMeasures.optimize.portfolio.rebalancing <- function(object){
516
if(!inherits(object, "optimize.portfolio.rebalancing")) stop("object must be of class 'optimize.portfolio.rebalancing'")
517
518
if(inherits(object$portfolio, "regime.portfolios")){
519
result <- extractObjRegime(object)
520
} else {
521
rebal_object <- object$opt_rebal
522
num.columns <- length(unlist(extractObjectiveMeasures(rebal_object[[1]])))
523
num.rows <- length(rebal_object)
524
result <- matrix(nrow=num.rows, ncol=num.columns)
525
for(i in 1:num.rows){
526
result[i,] <- unlist(extractObjectiveMeasures(rebal_object[[i]]))
527
}
528
colnames(result) <- name.replace(names(unlist(extractObjectiveMeasures(rebal_object[[1]]))))
529
rownames(result) <- names(rebal_object)
530
result <- as.xts(result)
531
}
532
return(result)
533
}
534
535
# Helper function for extractObjectiveMeasures.optimize.portfolio.rebalancing
536
# with regime switching.
537
# If I have N different regimes and N different portfolios, then
538
# extractObjectiveMeasures should return a list of length N where each element
539
# contains the objective measures for a given regime
540
extractObjRegime <- function(object){
541
tmp.regimes <- unlist(lapply(object$opt_rebalancing, function(x) x$regime))
542
unique.regimes <- sort(unique(tmp.regimes))
543
#print(tmp.regimes)
544
#print(unique.regimes)
545
546
# Initialize a list to hold the objective measures for each regime
547
out.list <- vector("list", length(unique.regimes))
548
names(out.list) <- paste("regime", unique.regimes, sep=".")
549
550
# Outer loop over each regime
551
for(i in 1:length(unique.regimes)){
552
# Get the index for each regime
553
tmp.idx <- which(tmp.regimes == unique.regimes[i])
554
555
# Initialize a temporary list to store the objective measures for each
556
# unique regime
557
tmp <- vector("list", length(tmp.idx))
558
559
# Nested loop over each optimize.portfolio object of the corresponding regime
560
for(j in 1:length(tmp)){
561
tmp[[j]] <- unlist(object$opt_rebalancing[[tmp.idx[j]]]$objective_measures)
562
}
563
# rbind the objective measures and convert to an xts object
564
#obj <- xts(do.call(rbind, tmp), as.Date(names(tmp.idx)))
565
obj <- do.call(rbind, tmp)
566
colnames(obj) <- name.replace(colnames(obj))
567
obj <- xts(obj, as.Date(names(tmp.idx)))
568
# insert the objective measures into the list
569
out.list[[unique.regimes[i]]] <- obj
570
}
571
out.list
572
}
573
574
#' @method extractObjectiveMeasures summary.optimize.portfolio.rebalancing
575
576
#' @export
577
extractObjectiveMeasures.summary.optimize.portfolio.rebalancing <- function(object){
578
object$objective_measures
579
}
580
581
#' @method extractObjectiveMeasures opt.list
582
583
#' @export
584
extractObjectiveMeasures.opt.list <- function(object){
585
# The idea is that these portfolios opt.list may have different objectives.
586
# Need a function to evaluate *all* objective measures for each portfolio.
587
# Challenges:
588
# - allow for different R objects across portfolios
589
# - Done
590
# - detect and remove duplicate objectives
591
# - Done based on name and objective type
592
# - handle duplicate objective names, but different arguments (i.e. different p for ES)
593
# - TODO
594
# - risk budget objectives need to be entered last
595
# - Done
596
if(!inherits(object, "opt.list")) stop("object must be of class 'opt.list'")
597
# Get the names of the list
598
opt.names <- names(object)
599
if(is.null(opt.names)) opt.names <- paste("portfolio", 1:length(object))
600
601
# Use the objectives from the first element and use as the basis for comparison
602
base <- sapply(object[[1]]$portfolio$objectives, function(x) paste(class(x)[1], x$name, sep="."))
603
604
# Get the objective name and type from each portfolio
605
obj_list <- lapply(object, function(x) sapply(x$portfolio$objectives, function(u) paste(class(u)[1], u$name, sep=".")))
606
607
# If all the objective names are identical, simply extract the objective measures
608
# and build the objective_measures matrix
609
if(all(sapply(obj_list, function(u) identical(x=base, y=u)))){
610
obj_list <- list()
611
# Get the objective_measures from each element
612
for(i in 1:length(object)){
613
tmp <- unlist(object[[i]]$objective_measures)
614
names(tmp) <- name.replace(names(tmp))
615
obj_list[[opt.names[i]]] <- tmp
616
}
617
obj_names <- unique(unlist(lapply(obj_list, names)))
618
obj_mat <- matrix(NA, nrow=length(obj_list), ncol=length(obj_names),
619
dimnames=list(opt.names, obj_names))
620
for(i in 1:length(obj_list)){
621
pm <- pmatch(x=names(obj_list[[i]]), table=obj_names)
622
obj_mat[i, pm] <- obj_list[[i]]
623
}
624
out <- obj_mat
625
} else {
626
# The objectives across portfolios are not identical, we will build an
627
# objectives list with *all* the objectives and recalculate the objective_measures
628
629
# Initialize a tmp.obj list to store all of the objectives from each
630
tmp.obj <- list()
631
tmp.budget <- list()
632
633
# Step 1: Loop through object and get the objectives from each portfolio
634
for(i in 1:length(object)){
635
tmp.portf <- object[[i]]$portfolio
636
for(j in 1:length(tmp.portf$objectives)){
637
if(inherits(tmp.portf$objectives[[j]], "risk_budget_objective")){
638
# tmp.budget <- c(tmp.budget, tmp.portf$objectives[[j]])
639
num.budget <- length(tmp.budget) + 1
640
tmp.budget[[num.budget]] <- tmp.portf$objectives[[j]]
641
} else {
642
# tmp.obj <- c(tmp.obj, tmp.portf$objectives[[j]])
643
num.obj <- length(tmp.obj) + 1
644
tmp.obj[[num.obj]] <- tmp.portf$objectives[[j]]
645
}
646
} # end inner loop of objectives
647
} # end outer loop of object
648
649
# This will make sure that "risk_budget_objectives" are entered last, but doesn't
650
# address duplicate names with different arguments in the arguments list
651
# e.g. different arguments for p, clean, etc.
652
tmp.obj <- c(tmp.obj, tmp.budget)
653
654
# Remove any duplicates
655
# The last objective will be the one that is kept
656
out.obj <- list()
657
obj.names <- sapply(tmp.obj, function(x) paste(x$name, class(x)[1], sep="."))
658
if(any(duplicated(obj.names))){
659
idx <- which(!duplicated(obj.names, fromLast=TRUE))
660
for(i in 1:length(idx)){
661
out.obj[[i]] <- tmp.obj[[idx[i]]]
662
}
663
}
664
665
# Loop through object and insert the new objectives list into each portfolio
666
# and run constrained_objective on each portfolio to extract the
667
# objective_measures for each portfolio
668
out <- list()
669
for(i in 1:length(object)){
670
object[[i]]$portfolio$objectives <- tmp.obj
671
tmp.weights <- object[[i]]$weights
672
tmp.R <- object[[i]]$R
673
tmp.portf <- object[[i]]$portfolio
674
tmp <- unlist(constrained_objective(w=tmp.weights, R=tmp.R, portfolio=tmp.portf, trace=TRUE)$objective_measures)
675
names(tmp) <- name.replace(names(tmp))
676
out[[opt.names[i]]] <- tmp
677
}
678
out <- do.call(rbind, out)
679
}
680
return(out)
681
}
682
683
#' @method extractObjectiveMeasures opt.rebal.list
684
685
#' @export
686
extractObjectiveMeasures.opt.rebal.list <- function(object, ...){
687
# get the optimal weights of each optimization in a list
688
# each element in the list is an optimize.portfolio.rebalancing object
689
obj_list <- vector("list", length(object))
690
for(i in 1:length(obj_list)){
691
obj_list[[i]] <- extractObjectiveMeasures(object[[i]])
692
}
693
return(obj_list)
694
}
695
696
##### extractGroups #####
697
698
#' Extract the group and/or category weights
699
#'
700
#' This function extracts the weights by group and/or category from an object
701
#' of class \code{optimize.portfolio}. Group constraints or category_labels must
702
#' be specified for this to return group constraints.
703
#'
704
#' @param object object of class \code{optimize.portfolio}
705
#' @param ... passthrough parameters. Not currently used
706
#' @return a list with two elements
707
#' \describe{
708
#' \item{weights: }{Optimal set of weights from the \code{optimize.portfolio} object}
709
#' \item{category_weights: }{Weights by category if category_labels are supplied in the \code{portfolio} object}
710
#' \item{group_weights: }{Weights by group if group is a constraint type}
711
#' }
712
#' @author Ross Bennett
713
#' @export
714
extractGroups <- function(object, ...){
715
if(!inherits(object, "optimize.portfolio")) stop("object must be of class 'optimize.portfolio'")
716
717
# Check category_labels in portfolio object
718
category_labels <- object$portfolio$category_labels
719
720
# Get the constraints to check for group constraints
721
constraints <- get_constraints(object$portfolio)
722
723
groups <- constraints$groups
724
725
cat_weights <- NULL
726
group_weights <- NULL
727
728
if(!is.null(category_labels)){
729
cat_names <- names(category_labels)
730
ncats <- length(category_labels)
731
cat_weights <- rep(0, ncats)
732
for(i in 1:ncats){
733
cat_weights[i] <- sum(object$weights[category_labels[[i]]])
734
}
735
names(cat_weights) <- cat_names
736
}
737
738
if(!is.null(groups)){
739
n.groups <- length(groups)
740
group_weights <- rep(0, n.groups)
741
for(i in 1:n.groups){
742
group_weights[i] <- sum(object$weights[groups[[i]]])
743
}
744
names(group_weights) <- constraints$group_labels
745
}
746
return(list(weights=object$weights,
747
category_weights=cat_weights,
748
group_weights=group_weights)
749
)
750
}
751
752
753