Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/R/charts.RP.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
chart.Weight.RP <- function(object, ..., neighbors = NULL, main="Weights", las = 3, xlab=NULL, cex.lab = 1, element.color = "darkgray", cex.axis=0.8, colorset=NULL, legend.loc="topright", cex.legend=0.8, plot.type="line"){
14
# Specific to the output of the random portfolio code with constraints
15
if(!inherits(object, "optimize.portfolio.random")){
16
stop("object must be of class 'optimize.portfolio.random'")
17
}
18
19
if(plot.type %in% c("bar", "barplot")){
20
barplotWeights(object=object, ..., main=main, las=las, xlab=xlab, cex.lab=cex.lab, element.color=element.color, cex.axis=cex.axis, legend.loc=legend.loc, cex.legend=cex.legend, colorset=colorset)
21
} else if(plot.type == "line"){
22
23
columnnames = names(object$weights)
24
numassets = length(columnnames)
25
26
constraints <- get_constraints(object$portfolio)
27
28
if(is.null(xlab))
29
minmargin = 3
30
else
31
minmargin = 5
32
if(main=="") topmargin=1 else topmargin=4
33
if(las > 1) {# set the bottom border to accommodate labels
34
bottommargin = max(c(minmargin, (strwidth(columnnames,units="in"))/par("cin")[1])) * cex.lab
35
if(bottommargin > 10 ) {
36
bottommargin<-10
37
columnnames<-substr(columnnames,1,19)
38
# par(srt=45) #TODO figure out how to use text() and srt to rotate long labels
39
}
40
}
41
else {
42
bottommargin = minmargin
43
}
44
par(mar = c(bottommargin, 4, topmargin, 2) +.1)
45
if(any(is.infinite(constraints$max)) | any(is.infinite(constraints$min))){
46
# set ylim based on weights if box constraints contain Inf or -Inf
47
ylim <- range(object$weights)
48
} else {
49
# set ylim based on the range of box constraints min and max
50
ylim <- range(c(constraints$min, constraints$max))
51
}
52
plot(object$random_portfolios[1,], type="b", col="orange", axes=FALSE, xlab='', ylim=ylim, ylab="Weights", main=main, ...)
53
if(!any(is.infinite(constraints$min))){
54
points(constraints$min, type="b", col="darkgray", lty="solid", lwd=2, pch=24)
55
}
56
if(!any(is.infinite(constraints$max))){
57
points(constraints$max, type="b", col="darkgray", lty="solid", lwd=2, pch=25)
58
}
59
if(!is.null(neighbors)){
60
if(is.vector(neighbors)){
61
xtract=extractStats(object)
62
weightcols<-grep('w\\.',colnames(xtract)) #need \\. to get the dot
63
if(length(neighbors)==1){
64
# overplot nearby portfolios defined by 'out'
65
orderx = order(xtract[,"out"])
66
subsetx = head(xtract[orderx,], n=neighbors)
67
for(i in 1:neighbors) points(subsetx[i,weightcols], type="b", col="lightblue")
68
} else{
69
# assume we have a vector of portfolio numbers
70
subsetx = xtract[neighbors,weightcols]
71
for(i in 1:length(neighbors)) points(subsetx[i,], type="b", col="lightblue")
72
}
73
}
74
if(is.matrix(neighbors) | is.data.frame(neighbors)){
75
# the user has likely passed in a matrix containing calculated values for risk.col and return.col
76
nbweights<-grep('w\\.',colnames(neighbors)) #need \\. to get the dot
77
for(i in 1:nrow(neighbors)) points(as.numeric(neighbors[i,nbweights]), type="b", col="lightblue")
78
# note that here we need to get weight cols separately from the matrix, not from xtract
79
# also note the need for as.numeric. points() doesn't like matrix inputs
80
}
81
}
82
83
points(object$random_portfolios[1,], type="b", col="orange", pch=16) # to overprint neighbors
84
points(object$weights, type="b", col="blue", pch=16)
85
axis(2, cex.axis = cex.axis, col = element.color)
86
axis(1, labels=columnnames, at=1:numassets, las=las, cex.axis = cex.axis, col = element.color)
87
box(col = element.color)
88
}
89
}
90
91
#' @rdname chart.Weights
92
#' @method chart.Weights optimize.portfolio.random
93
#' @export
94
chart.Weights.optimize.portfolio.random <- chart.Weight.RP
95
96
chart.Scatter.RP <- function(object, ..., neighbors = NULL, return.col='mean', risk.col='ES', chart.assets=FALSE, element.color = "darkgray", cex.axis=0.8, xlim=NULL, ylim=NULL){
97
# more or less specific to the output of the random portfolio code with constraints
98
# will work to a point with other functions, such as optimize.porfolio.parallel
99
# there's still a lot to do to improve this.
100
if(!inherits(object, "optimize.portfolio.random")){
101
stop("object must be of class 'optimize.portfolio.random'")
102
}
103
R <- object$R
104
if(is.null(R)) stop("Returns object not detected, must run optimize.portfolio with trace=TRUE")
105
xtract = extractStats(object)
106
columnnames = colnames(xtract)
107
#return.column = grep(paste("objective_measures",return.col,sep='.'),columnnames)
108
return.column = pmatch(return.col,columnnames)
109
if(is.na(return.column)) {
110
return.col = paste(return.col,return.col,sep='.')
111
return.column = pmatch(return.col,columnnames)
112
}
113
#risk.column = grep(paste("objective_measures",risk.col,sep='.'),columnnames)
114
risk.column = pmatch(risk.col,columnnames)
115
if(is.na(risk.column)) {
116
risk.col = paste(risk.col,risk.col,sep='.')
117
risk.column = pmatch(risk.col,columnnames)
118
}
119
120
# if(is.na(return.column) | is.na(risk.column)) stop(return.col,' or ',risk.col, ' do not match extractStats output')
121
122
# If the user has passed in return.col or risk.col that does not match extractStats output
123
# This will give the flexibility of passing in return or risk metrics that are not
124
# objective measures in the optimization. This may cause issues with the "neighbors"
125
# functionality since that is based on the "out" column
126
if(is.na(return.column) | is.na(risk.column)){
127
return.col <- gsub("\\..*", "", return.col)
128
risk.col <- gsub("\\..*", "", risk.col)
129
warning(return.col,' or ', risk.col, ' do not match extractStats output of $objective_measures slot')
130
# Get the matrix of weights for applyFUN
131
wts_index <- grep("w.", columnnames)
132
wts <- xtract[, wts_index]
133
if(is.na(return.column)){
134
tmpret <- applyFUN(R=R, weights=wts, FUN=return.col)
135
xtract <- cbind(tmpret, xtract)
136
colnames(xtract)[which(colnames(xtract) == "tmpret")] <- return.col
137
}
138
if(is.na(risk.column)){
139
tmprisk <- applyFUN(R=R, weights=wts, FUN=risk.col)
140
xtract <- cbind(tmprisk, xtract)
141
colnames(xtract)[which(colnames(xtract) == "tmprisk")] <- risk.col
142
}
143
columnnames = colnames(xtract)
144
return.column = pmatch(return.col,columnnames)
145
if(is.na(return.column)) {
146
return.col = paste(return.col,return.col,sep='.')
147
return.column = pmatch(return.col,columnnames)
148
}
149
risk.column = pmatch(risk.col,columnnames)
150
if(is.na(risk.column)) {
151
risk.col = paste(risk.col,risk.col,sep='.')
152
risk.column = pmatch(risk.col,columnnames)
153
}
154
}
155
# print(colnames(head(xtract)))
156
157
if(chart.assets){
158
# Get the arguments from the optimize.portfolio$portfolio object
159
# to calculate the risk and return metrics for the scatter plot.
160
# (e.g. arguments=list(p=0.925, clean="boudt")
161
arguments <- NULL # maybe an option to let the user pass in an arguments list?
162
if(is.null(arguments)){
163
tmp.args <- unlist(lapply(object$portfolio$objectives, function(x) x$arguments), recursive=FALSE)
164
tmp.args <- tmp.args[!duplicated(names(tmp.args))]
165
if(!is.null(tmp.args$portfolio_method)) tmp.args$portfolio_method <- "single"
166
arguments <- tmp.args
167
}
168
# Include risk reward scatter of asset returns
169
asset_ret <- scatterFUN(R=R, FUN=return.col, arguments)
170
asset_risk <- scatterFUN(R=R, FUN=risk.col, arguments)
171
xlim <- range(c(xtract[,risk.column], asset_risk))
172
ylim <- range(c(xtract[,return.column], asset_ret))
173
} else {
174
asset_ret <- NULL
175
asset_risk <- NULL
176
}
177
178
# plot the random portfolios
179
plot(xtract[,risk.column],xtract[,return.column], xlab=risk.col, ylab=return.col, col="darkgray", axes=FALSE, xlim=xlim, ylim=ylim, ...)
180
181
# plot the risk-reward scatter of the assets
182
if(chart.assets){
183
points(x=asset_risk, y=asset_ret)
184
text(x=asset_risk, y=asset_ret, labels=colnames(R), pos=4, cex=0.8)
185
}
186
187
if(!is.null(neighbors)){
188
if(is.vector(neighbors)){
189
if(length(neighbors)==1){
190
# overplot nearby portfolios defined by 'out'
191
orderx = order(xtract[,"out"]) #TODO this won't work if the objective is anything other than mean
192
subsetx = head(xtract[orderx,], n=neighbors)
193
} else{
194
# assume we have a vector of portfolio numbers
195
subsetx = xtract[neighbors,]
196
}
197
points(subsetx[,risk.column], subsetx[,return.column], col="lightblue", pch=1)
198
}
199
if(is.matrix(neighbors) | is.data.frame(neighbors)){
200
# the user has likely passed in a matrix containing calculated values for risk.col and return.col
201
rtc = pmatch(return.col,columnnames)
202
if(is.na(rtc)) {
203
rtc = pmatch(paste(return.col,return.col,sep='.'),columnnames)
204
}
205
rsc = pmatch(risk.col,columnnames)
206
if(is.na(rsc)) {
207
rsc = pmatch(paste(risk.col,risk.col,sep='.'),columnnames)
208
}
209
for(i in 1:nrow(neighbors)) points(neighbors[i,rsc], neighbors[i,rtc], col="lightblue", pch=1)
210
}
211
}
212
213
points(xtract[1,risk.column],xtract[1,return.column], col="orange", pch=16) # overplot the equal weighted (or seed)
214
#check to see if portfolio 1 is EW object$random_portoflios[1,] all weights should be the same
215
if(!isTRUE(all.equal(object$random_portfolios[1,][1],1/length(object$random_portfolios[1,]),check.attributes=FALSE))){
216
#show both the seed and EW if they are different
217
#NOTE the all.equal comparison could fail above if the first element of the first portfolio is the same as the EW weight,
218
#but the rest is not, shouldn't happen often with real portfolios, only toy examples
219
points(xtract[2,risk.column],xtract[2,return.column], col="green", pch=16) # overplot the equal weighted (or seed)
220
}
221
## @TODO: Generalize this to find column containing the "risk" metric
222
if(length(names(object)[which(names(object)=='constrained_objective')])) {
223
result.slot<-'constrained_objective'
224
} else {
225
result.slot<-'objective_measures'
226
}
227
objcols<-unlist(object[[result.slot]])
228
names(objcols)<-name.replace(names(objcols))
229
return.column = pmatch(return.col,names(objcols))
230
if(is.na(return.column)) {
231
return.col = paste(return.col,return.col,sep='.')
232
return.column = pmatch(return.col,names(objcols))
233
}
234
risk.column = pmatch(risk.col,names(objcols))
235
if(is.na(risk.column)) {
236
risk.col = paste(risk.col,risk.col,sep='.')
237
risk.column = pmatch(risk.col,names(objcols))
238
}
239
# risk and return metrics for the optimal weights if the RP object does not
240
# contain the metrics specified by return.col or risk.col
241
if(is.na(return.column) | is.na(risk.column)){
242
return.col <- gsub("\\..*", "", return.col)
243
risk.col <- gsub("\\..*", "", risk.col)
244
# warning(return.col,' or ', risk.col, ' do not match extractStats output of $objective_measures slot')
245
opt_weights <- object$weights
246
ret <- as.numeric(applyFUN(R=R, weights=opt_weights, FUN=return.col))
247
risk <- as.numeric(applyFUN(R=R, weights=opt_weights, FUN=risk.col))
248
points(risk, ret, col="blue", pch=16) #optimal
249
text(x=risk, y=ret, labels="Optimal",col="blue", pos=4, cex=0.8)
250
} else {
251
points(objcols[risk.column], objcols[return.column], col="blue", pch=16) # optimal
252
text(x=objcols[risk.column], y=objcols[return.column], labels="Optimal",col="blue", pos=4, cex=0.8)
253
}
254
axis(1, cex.axis = cex.axis, col = element.color)
255
axis(2, cex.axis = cex.axis, col = element.color)
256
box(col = element.color)
257
}
258
259
#' @rdname chart.RiskReward
260
#' @method chart.RiskReward optimize.portfolio.random
261
#' @export
262
chart.RiskReward.optimize.portfolio.random <- chart.Scatter.RP
263
264
265
charts.RP <- function(RP, risk.col, return.col, chart.assets=FALSE, neighbors=NULL, main="Random.Portfolios", xlim=NULL, ylim=NULL, ...){
266
# Specific to the output of the random portfolio code with constraints
267
if(!inherits(RP, "optimize.portfolio.random")) stop("RP must be of class optimize.portfolio.random")
268
op <- par(no.readonly=TRUE)
269
layout(matrix(c(1,2)),heights=c(2,1.5),widths=1)
270
par(mar=c(4,4,4,2))
271
chart.Scatter.RP(object=RP, risk.col=risk.col, return.col=return.col, chart.assets=chart.assets, neighbors=neighbors, main=main, xlim=NULL, ylim=NULL, ...)
272
par(mar=c(2,4,0,2))
273
chart.Weight.RP(object=RP, main="", neighbors=neighbors, ...)
274
par(op)
275
}
276
277
278
#' @rdname plot
279
#' @method plot optimize.portfolio.random
280
#' @export
281
plot.optimize.portfolio.random <- function(x, ..., return.col='mean', risk.col='ES', chart.assets=FALSE, neighbors=NULL, xlim=NULL, ylim=NULL, main='optimized portfolio plot') {
282
charts.RP(RP=x, risk.col=risk.col, return.col=return.col, chart.assets=chart.assets, neighbors=neighbors, main=main, xlim=xlim, ylim=ylim, ...)
283
}
284
285
286
#' @rdname plot
287
#' @method plot optimize.portfolio
288
#' @export
289
plot.optimize.portfolio <- function(x, ..., return.col='mean', risk.col='ES', chart.assets=FALSE, neighbors=NULL, xlim=NULL, ylim=NULL, main='optimized portfolio plot') {
290
charts.RP(RP=x, risk.col=risk.col, return.col=return.col, chart.assets=chart.assets, neighbors=neighbors, main=main, xlim=xlim, ylim=ylim, ...)
291
}
292