Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/R/chart.concentration.R
1433 views
1
2
# conc.type = weight or pct_contrib for risk budget optimization
3
4
#' Classic risk reward scatter and concentration
5
#'
6
#' This function charts the \code{optimize.portfolio} object in risk-return space
7
#' and the degree of concentration based on the weights or percentage component
8
#' contribution to risk.
9
#'
10
#' @param object optimal portfolio created by \code{\link{optimize.portfolio}}.
11
#' @param \dots any other passthru parameters.
12
#' @param return.col string matching the objective of a 'return' objective, on vertical axis.
13
#' @param risk.col string matching the objective of a 'risk' objective, on horizontal axis.
14
#' @param chart.assets TRUE/FALSE. Includes a risk reward scatter of the assets in the chart.
15
#' @param conc.type concentration type can be based on the concentration of weights
16
#' or concentration of percentage component contribution to risk (only works with risk
17
#' budget objective for the optimization).
18
#' @param col color palette or vector of colors to use.
19
#' @param element.color color for the border and axes.
20
#' @param cex.axis The magnification to be used for axis annotation relative to the current setting of \code{cex}.
21
#' @param xlim set the x-axis limit, same as in \code{\link{plot}}.
22
#' @param ylim set the y-axis limit, same as in \code{\link{plot}}.
23
#' @seealso \code{\link{optimize.portfolio}}
24
#' @author Peter Carl and Ross Bennett
25
#' @export
26
chart.Concentration <- function(object,
27
...,
28
return.col='mean',
29
risk.col='ES',
30
chart.assets=FALSE,
31
conc.type=c("weights", "pct_contrib"),
32
col=heat.colors(20),
33
element.color = "darkgray",
34
cex.axis=0.8,
35
xlim=NULL, ylim=NULL){
36
# check the object
37
if(!inherits(object, "optimize.portfolio")){
38
stop("object must be of class 'optimize.portfolio'")
39
}
40
41
# extract the stats
42
xtract <- try(extractStats(object), silent=TRUE)
43
if(inherits(xtract, "try-error")) {
44
message(xtract)
45
return(NULL)
46
}
47
48
# get the concentration type
49
# We can either chart the concentration of the weights or the concentration
50
# of the percentage contribution to risk for risk budget optimizations
51
conc.type <- match.arg(conc.type)
52
53
columnnames <- colnames(xtract)
54
R <- object$R
55
56
# Get the return and risk columns from xtract
57
return.column <- pmatch(return.col, columnnames)
58
if(is.na(return.column)) {
59
return.col <- paste(return.col, return.col, sep='.')
60
return.column <- pmatch(return.col, columnnames)
61
}
62
risk.column <- pmatch(risk.col, columnnames)
63
if(is.na(risk.column)) {
64
risk.col <- paste(risk.col, risk.col, sep='.')
65
risk.column <- pmatch(risk.col, columnnames)
66
}
67
68
# If the user has passed in return.col or risk.col that does not match extractStats output
69
# This will give the flexibility of passing in return or risk metrics that are not
70
# objective measures in the optimization. This may cause issues with the "neighbors"
71
# functionality since that is based on the "out" column
72
if(is.na(return.column) | is.na(risk.column)){
73
return.col <- gsub("\\..*", "", return.col)
74
risk.col <- gsub("\\..*", "", risk.col)
75
warning(return.col,' or ', risk.col, ' do not match extractStats output of $objective_measures slot')
76
# Get the matrix of weights for applyFUN
77
wts_index <- grep("w.", columnnames)
78
wts <- xtract[, wts_index]
79
if(is.na(return.column)){
80
tmpret <- applyFUN(R=R, weights=wts, FUN=return.col)
81
xtract <- cbind(tmpret, xtract)
82
colnames(xtract)[which(colnames(xtract) == "tmpret")] <- return.col
83
}
84
if(is.na(risk.column)){
85
tmprisk <- applyFUN(R=R, weights=wts, FUN=risk.col)
86
xtract <- cbind(tmprisk, xtract)
87
colnames(xtract)[which(colnames(xtract) == "tmprisk")] <- risk.col
88
}
89
columnnames = colnames(xtract)
90
return.column = pmatch(return.col,columnnames)
91
if(is.na(return.column)) {
92
return.col = paste(return.col,return.col,sep='.')
93
return.column = pmatch(return.col,columnnames)
94
}
95
risk.column = pmatch(risk.col,columnnames)
96
if(is.na(risk.column)) {
97
risk.col = paste(risk.col,risk.col,sep='.')
98
risk.column = pmatch(risk.col,columnnames)
99
}
100
}
101
102
if(chart.assets){
103
# Get the arguments from the optimize.portfolio$portfolio object
104
# to calculate the risk and return metrics for the scatter plot.
105
# (e.g. arguments=list(p=0.925, clean="boudt")
106
arguments <- NULL # maybe an option to let the user pass in an arguments list?
107
if(is.null(arguments)){
108
tmp.args <- unlist(lapply(object$portfolio$objectives, function(x) x$arguments), recursive=FALSE)
109
tmp.args <- tmp.args[!duplicated(names(tmp.args))]
110
if(!is.null(tmp.args$portfolio_method)) tmp.args$portfolio_method <- "single"
111
arguments <- tmp.args
112
}
113
# Include risk reward scatter of asset returns
114
asset_ret <- scatterFUN(R=R, FUN=return.col, arguments)
115
asset_risk <- scatterFUN(R=R, FUN=risk.col, arguments)
116
xlim <- range(c(xtract[,risk.column], asset_risk))
117
ylim <- range(c(xtract[,return.column], asset_ret))
118
} else {
119
asset_ret <- NULL
120
asset_risk <- NULL
121
}
122
123
if(conc.type == "weights"){
124
idx <- grep("w.", colnames(xtract))
125
if(length(idx) == 0) stop("weights not detected in output of extractStats")
126
tmp.x <- xtract[, idx]
127
} else if(conc.type == "pct_contrib"){
128
idx <- grep("pct_contrib", colnames(xtract))
129
if(length(idx) == 0) stop("pct_contrib not detected in output of extractStats")
130
tmp.x <- xtract[, idx]
131
}
132
# need a check to make sure that tmp.x is valid
133
134
# # Use HHI to compute the concentration of the pct_contrib_MES or concentration of weights
135
x.hhi <- apply(tmp.x, MARGIN=1, FUN="HHI")
136
# normalized HHI between 0 and 1
137
y <- (x.hhi - min(x.hhi)) / (max(x.hhi) - min(x.hhi))
138
139
op <- par(no.readonly=TRUE)
140
layout(matrix(c(1,2)),heights=c(4,1.25),widths=1)
141
par(mar=c(5,4,1,2)+.1, cex=1) # c(bottom, left, top, right)
142
143
# plot the asset in risk-return space ordered based on degree of concentration
144
plot(xtract[order(y, decreasing=TRUE), risk.column], xtract[order(y, decreasing=TRUE), return.column], xlab=risk.col, ylab=return.col, col=col, axes=FALSE, xlim=xlim, ylim=ylim, ...)
145
146
# plot the risk-reward scatter of the assets
147
if(chart.assets){
148
points(x=asset_risk, y=asset_ret)
149
text(x=asset_risk, y=asset_ret, labels=colnames(R), pos=4, cex=0.8)
150
}
151
152
axis(1, cex.axis = cex.axis, col = element.color)
153
axis(2, cex.axis = cex.axis, col = element.color)
154
box(col = element.color)
155
156
# Now plot the portfolio concentration part
157
# Add legend to bottom panel
158
par(mar=c(5,5.5,1,3)+.1, cex=0.7)
159
x <- x.hhi
160
scale01 <- function(x, low = min(x), high = max(x)) {
161
return((x - low) / (high - low))
162
}
163
164
breaks <- seq(min(x.hhi, na.rm=TRUE), max(x.hhi, na.rm=TRUE), length=(length(col)+1))
165
min.raw <- min(x, na.rm = TRUE)
166
max.raw <- max(x, na.rm = TRUE)
167
z <- seq(min.raw, max.raw, length=length(col))
168
image(z = matrix(z, ncol=1), col=col, breaks=breaks, xaxt="n", yaxt="n")
169
par(usr=c(0, 1, 0, 1)) # needed to draw the histogram correctly
170
lv <- pretty(breaks)
171
xv <- scale01(as.numeric(lv), min.raw, max.raw)
172
axis(1, at=xv, labels=sprintf("%s%%", pretty(lv)))
173
h <- hist(x, plot=FALSE, breaks=breaks)
174
hx <- scale01(breaks, min(x), max(x))
175
hy <- c(h$counts, h$counts[length(h$counts)])
176
lines(hx, hy / max(hy) * 0.95, lwd=2, type="s", col="blue")
177
axis(2, at=pretty(hy) / max(hy) * 0.95, pretty(hy))
178
title(ylab="Count")
179
title(xlab="Degree of Concentration")
180
par(op)
181
invisible(NULL)
182
}
183
184