Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/R/portfolio.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$
10
#
11
###############################################################################
12
13
#' constructor for class portfolio
14
#'
15
#' The portfolio object is created with \code{portfolio.spec}. The portfolio
16
#' object is an S3 object of class 'portfolio' used to hold the initial asset weights,
17
#' constraints, objectives, and other information about the portfolio. The only
18
#' required argument to \code{portfolio.spec} is \code{assets}.
19
#'
20
#' The portfolio object contains the following elements:
21
#' \describe{
22
#' \item{\code{assets}}{ named vector of the seed weights}
23
#' \item{\code{category_labels}}{ character vector to categorize the assets by sector, geography, etc.}
24
#' \item{\code{weight_seq}}{ sequence of weights used by \code{\link{random_portfolios}}. See \code{\link{generatesequence}}}
25
#' \item{\code{constraints}}{ a list of constraints added to the portfolio object with \code{\link{add.constraint}}}
26
#' \item{\code{objectives}}{ a list of objectives added to the portfolio object with \code{\link{add.objective}}}
27
#' \item{\code{call}}{ the call to \code{portfolio.spec} with all of the specified arguments}
28
#' }
29
#'
30
#' @param assets number of assets, or optionally a named vector of assets specifying seed weights. If seed weights are not specified, an equal weight portfolio will be assumed.
31
#' @param name give the portfolio a name, the default name will be 'portfolio'
32
#' @param category_labels character vector to categorize assets by sector, industry, geography, market-cap, currency, etc. Default NULL
33
#' @param weight_seq seed sequence of weights, see \code{\link{generatesequence}} Default NULL
34
#' @param message TRUE/FALSE. The default is message=FALSE. Display messages if TRUE.
35
#' @return an object of class \code{portfolio}
36
#' @author Ross Bennett, Brian G. Peterson
37
#' @aliases portfolio
38
#' @seealso
39
#' \code{\link{add.constraint}},
40
#' \code{\link{add.objective}},
41
#' \code{\link{optimize.portfolio}}
42
#' @examples
43
#' data(edhec)
44
#' pspec <- portfolio.spec(assets=colnames(edhec))
45
#' pspec <- portfolio.spec(assets=10, weight_seq=generatesequence())
46
#' @export
47
portfolio.spec <- function(assets=NULL, name = 'portfolio', category_labels=NULL, weight_seq=NULL, message=FALSE) {
48
# portfolio.spec is based on the v1_constraint object, but removes
49
# constraint specification
50
if (is.null(assets)) {
51
stop("You must specify the assets")
52
}
53
54
if(!is.null(assets)){
55
# TODO FIXME this doesn't work quite right on matrix of assets
56
if(is.numeric(assets)){
57
if (length(assets) == 1) {
58
nassets = assets
59
# we passed in a number of assets, so we need to create the vector
60
if(message) message("assuming equal weighted seed portfolio")
61
assets <- rep(1 / nassets, nassets)
62
} else {
63
nassets = length(assets)
64
}
65
# and now we may need to name them
66
if (is.null(names(assets))) {
67
for(i in 1:length(assets)){
68
names(assets)[i] <- paste("Asset",i,sep=".")
69
}
70
}
71
}
72
if(is.character(assets)){
73
nassets = length(assets)
74
assetnames = assets
75
if(message) message("assuming equal weighted seed portfolio")
76
assets <- rep(1 / nassets, nassets)
77
names(assets) <- assetnames # set names, so that other code can access it,
78
# and doesn't have to know about the character vector
79
# print(assets)
80
}
81
# if assets is a named vector, we'll assume it is current weights
82
}
83
84
# If category_labels is not null then the user has passed in category_labels
85
if(!is.null(category_labels)){
86
if(!is.character(category_labels)){
87
stop("category_labels must be a character vector")
88
}
89
if(length(category_labels) != length(assets)) {
90
stop("length(category_labels) must be equal to length(assets)")
91
}
92
# Turn category_labels into a list that can be used with group constraints
93
unique_labels <- unique(category_labels)
94
tmp <- list()
95
for(i in 1:length(unique_labels)){
96
tmp[[unique_labels[i]]] <- which(category_labels == unique_labels[i])
97
}
98
category_labels <- tmp
99
}
100
101
## now structure and return
102
return(structure(
103
list(
104
name = name,
105
assets = assets,
106
category_labels = category_labels,
107
weight_seq = weight_seq,
108
constraints = list(),
109
objectives = list(),
110
call = match.call()
111
),
112
class=c("portfolio.spec","portfolio")
113
))
114
}
115
116
#' check function for portfolio
117
#'
118
#' @param x object to test for type \code{portfolio}
119
#' @author Ross Bennett
120
#' @export
121
is.portfolio <- function( x ) {
122
inherits( x, "portfolio" )
123
}
124
125
#' Regime Portfolios
126
#'
127
#' Construct a \code{regime.portfolios} object that contains a time series of
128
#' regimes and portfolios corresponding to the regimes.
129
#'
130
#' Create a \code{regime.portfolios} object to support regime switching
131
#' optimization. This object is then passed in as the \code{portfolio}
132
#' argument in \code{optimize.portfolio}. The regime is detected and the
133
#' corresponding portfolio is selected. For example, if the current
134
#' regime is 1, then portfolio 1 will be selected and used in the
135
#' optimization.
136
#'
137
#' @param regime xts or zoo object specifying the regime
138
#' @param portfolios list of portfolios created by
139
#' \code{combine.portfolios} with corresponding regimes
140
#' @return a \code{regime.portfolios} object with the following elements
141
#' \describe{
142
#' \item{regime: }{An xts object of the regime}
143
#' \item{portfolio: }{List of portfolios corresponding to the regime}
144
#' }
145
#' @author Ross Bennett
146
#' @export
147
regime.portfolios <- function(regime, portfolios){
148
if(!inherits(regime, c("xts", "zoo"))) stop("regime object must be an xts or zoo object")
149
if(!inherits(portfolios, "portfolio.list")) stop("portfolios object must be a portfolio.list object")
150
151
n.regimes <- length(unique(regime))
152
n.portfolios <- length(portfolios)
153
if(n.regimes != n.portfolios) stop("Number of portfolios must match the number of regimes")
154
155
# Check to ensure the assets in each portfolio are equal
156
for(i in 2:length(portfolios)){
157
if(!identical(portfolios[[1]]$assets, portfolios[[i]]$assets)){
158
stop("The assets in each portfolio must be identical")
159
}
160
}
161
# get the unique asset names of each portfolio
162
# asset names matter in hierarchical optimization
163
asset.names <- unique(unlist(lapply(portfolios, function(x) names(x$assets))))
164
assets <- rep(1 / length(asset.names), length(asset.names))
165
names(assets) <- asset.names
166
# structure and return
167
return(structure(list(regime=regime, portfolio.list=portfolios, assets=assets),
168
class=c("regime.portfolios", "portfolio")))
169
}
170
171