Path: blob/master/R/charts.efficient.frontier.R
1433 views
1#' Chart the efficient frontier and risk-return scatter2#'3#' Chart the efficient frontier and risk-return scatter of the assets for4#' \code{optimize.portfolio} or \code{efficient.frontier} objects5#'6#' @details7#' For objects created by optimize.portfolio with 'DEoptim', 'random', or 'pso'8#' specified as the optimize_method:9#' \itemize{10#' \item The efficient frontier plotted is based on the the trace information (sets of11#' portfolios tested by the solver at each iteration) in objects created by12#' \code{optimize.portfolio}.13#' }14#'15#' For objects created by optimize.portfolio with 'ROI' specified as the16#' optimize_method:17#' \itemize{18#' \item The mean-StdDev or mean-ETL efficient frontier can be plotted for optimal19#' portfolio objects created by \code{optimize.portfolio}.20#'21#' \item If \code{match.col="StdDev"}, the mean-StdDev efficient frontier is plotted.22#'23#' \item If \code{match.col="ETL"} (also "ES" or "CVaR"), the mean-ETL efficient frontier is plotted.24#' }25#'26#' Note that \code{trace=TRUE} must be specified in \code{\link{optimize.portfolio}}27#'28#' GenSA does not return any useable trace information for portfolios tested at29#' each iteration, therfore we cannot extract and chart an efficient frontier.30#'31#' By default, the tangency portfolio (maximum Sharpe Ratio or modified Sharpe Ratio)32#' will be plotted using a risk free rate of 0. Set \code{rf=NULL} to omit33#' this from the plot.34#'35#' @param object object to chart.36#' @param \dots passthru parameters to \code{\link{plot}}37#' @param optimize_method the optimize method to get the efficient frontier38#' @param match.col string name of column to use for risk (horizontal axis).39#' \code{match.col} must match the name of an objective measure in the40#' \code{objective_measures} or \code{opt_values} slot in the object created41#' by \code{\link{optimize.portfolio}}.42#' @param n.portfolios number of portfolios to use to plot the efficient frontier.43#' @param xlim set the x-axis limit, same as in \code{\link{plot}}.44#' @param ylim set the y-axis limit, same as in \code{\link{plot}}.45#' @param cex.axis numerical value giving the amount by which the axis should be magnified relative to the default.46#' @param element.color provides the color for drawing less-important chart elements, such as the box lines, axis lines, etc.47#' @param main a main title for the plot.48#' @param RAR.text string name for risk adjusted return text to plot in the legend.49#' @param rf risk free rate. If \code{rf} is not null, the maximum Sharpe Ratio or modified Sharpe Ratio tangency portfolio will be plotted.50#' @param tangent.line TRUE/FALSE to plot the tangent line.51#' @param cex.legend numerical value giving the amount by which the legend should be magnified relative to the default.52#' @param chart.assets TRUE/FALSE to include the assets.53#' @param labels.assets TRUE/FALSE to include the asset names in the plot.54#' \code{chart.assets} must be \code{TRUE} to plot asset names.55#' @param pch.assets plotting character of the assets, same as in \code{\link{plot}}.56#' @param cex.assets numerical value giving the amount by which the asset points and labels should be magnified relative to the default.57#' @author Ross Bennett, Xinran Zhao58#' @rdname chart.EfficientFrontier59#' @export60chart.EfficientFrontier <- function(object, ...){61UseMethod("chart.EfficientFrontier")62}6364#' @rdname chart.EfficientFrontier65#' @method chart.EfficientFrontier optimize.portfolio.CVXR66#' @export67chart.EfficientFrontier.optimize.portfolio.CVXR <- function(object, ..., optimize_method='CVXR', match.col="ES", n.portfolios=25, xlim=NULL, ylim=NULL, cex.axis=0.8, element.color="darkgray", main="Efficient Frontier", RAR.text="SR", rf=0, tangent.line=TRUE, cex.legend=0.8, chart.assets=TRUE, labels.assets=TRUE, pch.assets=21, cex.assets=0.8){68if(!inherits(object, "optimize.portfolio.CVXR")) stop("object must be of class optimize.portfolio.CVXR")6970portf <- object$portfolio71R <- object$R72if(is.null(R)) stop(paste("Not able to get asset returns from", object))73wts <- object$weights74objectclass <- class(object)[1]7576# objnames <- unlist(lapply(portf$objectives, function(x) x$name))77# if(!(match.col %in% objnames)){78# stop("match.col must match an objective name")79# }8081# get the optimal return and risk metrics82xtract <- extractStats(object=object)83columnames <- names(xtract)84if(!(("mean") %in% columnames)){85# we need to calculate the mean given the optimal weights86opt_ret <- applyFUN(R=R, weights=wts, FUN="mean")87} else {88opt_ret <- xtract["mean"]89}90# get the match.col column91mtc <- pmatch(match.col, columnames)92if(is.na(mtc)) {93mtc <- pmatch(paste(match.col,match.col,sep='.'), columnames)94}95if(is.na(mtc)){96# if(is.na(mtc)) stop("could not match match.col with column name of extractStats output")97opt_risk <- applyFUN(R=R, weights=wts, FUN=match.col)98} else {99opt_risk <- xtract[mtc]100}101102# get the data to plot scatter of asset returns103asset_ret <- scatterFUN(R=R, FUN="mean")104asset_risk <- scatterFUN(R=R, FUN=match.col)105rnames <- colnames(R)106107if(match.col == "StdDev"){108frontier <- meanvar.efficient.frontier(portfolio=portf, R=R, n.portfolios=n.portfolios, ...=...)109rar <- "SR"110}111if(match.col %in% c("ETL", "ES", "CVaR")){112frontier <- meanetl.efficient.frontier(portfolio=portf, R=R, n.portfolios=n.portfolios, ...=...)113rar <- "STARR"114}115if(match.col =="CSM"){116frontier <- meancsm.efficient.frontier(portfolio=portf, R=R, n.portfolios=n.portfolios, ...=...)117rar <- "CSMratio"118}119120# data points to plot the frontier121x.f <- frontier[, match.col]122y.f <- frontier[, "mean"]123124# Points for the Sharpe Ratio ((mu - rf) / StdDev) or STARR ((mu - rf) / ETL)125if(!is.null(rf)){126sr <- (y.f - rf) / (x.f)127idx.maxsr <- which.max(sr)128srmax <- sr[idx.maxsr]129}130131# set the x and y limits132if(is.null(xlim)){133xlim <- range(c(x.f, asset_risk))134# xlim[1] <- xlim[1] * 0.8135xlim[1] <- 0136xlim[2] <- xlim[2] * 1.15137}138if(is.null(ylim)){139ylim <- range(c(y.f, asset_ret))140# ylim[1] <- ylim[1] * 0.9141ylim[1] <- 0142ylim[2] <- ylim[2] * 1.1143}144145# plot the efficient frontier line146plot(x=x.f, y=y.f, ylab="Mean", xlab=match.col, main=main, xlim=xlim, ylim=ylim, axes=FALSE, ...)147148# Add the global minimum variance or global minimum ETL portfolio149points(x=x.f[1], y=y.f[1], pch=16)150151if(chart.assets){152# risk-return scatter of the assets153points(x=asset_risk, y=asset_ret, pch=pch.assets, cex=cex.assets)154if(labels.assets) text(x=asset_risk, y=asset_ret, labels=rnames, pos=4, cex=cex.assets)155}156157# plot the optimal portfolio158points(opt_risk, opt_ret, col="blue", pch=16) # optimal159text(x=opt_risk, y=opt_ret, labels="Optimal",col="blue", pos=4, cex=0.8)160if(!is.null(rf)){161# Plot tangency line and points at risk-free rate and tangency portfolio162if(tangent.line) abline(rf, srmax, lty=2)163points(0, rf, pch=16)164points(x.f[idx.maxsr], y.f[idx.maxsr], pch=16)165# text(x=x.f[idx.maxsr], y=y.f[idx.maxsr], labels="T", pos=4, cex=0.8)166# Add lengend with max Sharpe Ratio and risk-free rate167legend("topleft", paste(RAR.text, " = ", signif(srmax,3), sep = ""), bty = "n", cex=cex.legend)168legend("topleft", inset = c(0,0.05), paste("rf = ", signif(rf,3), sep = ""), bty = "n", cex=cex.legend)169}170axis(1, cex.axis = cex.axis, col = element.color)171axis(2, cex.axis = cex.axis, col = element.color)172box(col = element.color)173}174175#' @rdname chart.EfficientFrontier176#' @method chart.EfficientFrontier optimize.portfolio.ROI177#' @export178chart.EfficientFrontier.optimize.portfolio.ROI <- function(object, ..., optimize_method='ROI', match.col="ES", n.portfolios=25, xlim=NULL, ylim=NULL, cex.axis=0.8, element.color="darkgray", main="Efficient Frontier", RAR.text="SR", rf=0, tangent.line=TRUE, cex.legend=0.8, chart.assets=TRUE, labels.assets=TRUE, pch.assets=21, cex.assets=0.8){179if(!inherits(object, "optimize.portfolio.ROI")) stop("object must be of class optimize.portfolio.ROI")180181portf <- object$portfolio182R <- object$R183if(is.null(R)) stop(paste("Not able to get asset returns from", object))184wts <- object$weights185objectclass <- class(object)[1]186187# objnames <- unlist(lapply(portf$objectives, function(x) x$name))188# if(!(match.col %in% objnames)){189# stop("match.col must match an objective name")190# }191192# get the optimal return and risk metrics193xtract <- extractStats(object=object)194columnames <- names(xtract)195if(!(("mean") %in% columnames)){196# we need to calculate the mean given the optimal weights197opt_ret <- applyFUN(R=R, weights=wts, FUN="mean")198} else {199opt_ret <- xtract["mean"]200}201# get the match.col column202mtc <- pmatch(match.col, columnames)203if(is.na(mtc)) {204mtc <- pmatch(paste(match.col,match.col,sep='.'), columnames)205}206if(is.na(mtc)){207# if(is.na(mtc)) stop("could not match match.col with column name of extractStats output")208opt_risk <- applyFUN(R=R, weights=wts, FUN=match.col)209} else {210opt_risk <- xtract[mtc]211}212213# get the data to plot scatter of asset returns214asset_ret <- scatterFUN(R=R, FUN="mean")215asset_risk <- scatterFUN(R=R, FUN=match.col)216rnames <- colnames(R)217218if(match.col %in% c("ETL", "ES", "CVaR")){219frontier <- meanetl.efficient.frontier(portfolio=portf, R=R, n.portfolios=n.portfolios, ...=...)220rar <- "STARR"221}222if(match.col == "StdDev"){223frontier <- meanvar.efficient.frontier(portfolio=portf, R=R, n.portfolios=n.portfolios, ...=...)224rar <- "SR"225}226# data points to plot the frontier227x.f <- frontier[, match.col]228y.f <- frontier[, "mean"]229230# Points for the Sharpe Ratio ((mu - rf) / StdDev) or STARR ((mu - rf) / ETL)231if(!is.null(rf)){232sr <- (y.f - rf) / (x.f)233idx.maxsr <- which.max(sr)234srmax <- sr[idx.maxsr]235}236237# set the x and y limits238if(is.null(xlim)){239xlim <- range(c(x.f, asset_risk))240# xlim[1] <- xlim[1] * 0.8241xlim[1] <- 0242xlim[2] <- xlim[2] * 1.15243}244if(is.null(ylim)){245ylim <- range(c(y.f, asset_ret))246# ylim[1] <- ylim[1] * 0.9247ylim[1] <- 0248ylim[2] <- ylim[2] * 1.1249}250251# plot the efficient frontier line252plot(x=x.f, y=y.f, ylab="Mean", xlab=match.col, main=main, xlim=xlim, ylim=ylim, axes=FALSE, ...)253254# Add the global minimum variance or global minimum ETL portfolio255points(x=x.f[1], y=y.f[1], pch=16)256257if(chart.assets){258# risk-return scatter of the assets259points(x=asset_risk, y=asset_ret, pch=pch.assets, cex=cex.assets)260if(labels.assets) text(x=asset_risk, y=asset_ret, labels=rnames, pos=4, cex=cex.assets)261}262263# plot the optimal portfolio264points(opt_risk, opt_ret, col="blue", pch=16) # optimal265text(x=opt_risk, y=opt_ret, labels="Optimal",col="blue", pos=4, cex=0.8)266if(!is.null(rf)){267# Plot tangency line and points at risk-free rate and tangency portfolio268if(tangent.line) abline(rf, srmax, lty=2)269points(0, rf, pch=16)270points(x.f[idx.maxsr], y.f[idx.maxsr], pch=16)271# text(x=x.f[idx.maxsr], y=y.f[idx.maxsr], labels="T", pos=4, cex=0.8)272# Add lengend with max Sharpe Ratio and risk-free rate273legend("topleft", paste(RAR.text, " = ", signif(srmax,3), sep = ""), bty = "n", cex=cex.legend)274legend("topleft", inset = c(0,0.05), paste("rf = ", signif(rf,3), sep = ""), bty = "n", cex=cex.legend)275}276axis(1, cex.axis = cex.axis, col = element.color)277axis(2, cex.axis = cex.axis, col = element.color)278box(col = element.color)279}280281#' @rdname chart.EfficientFrontier282#' @method chart.EfficientFrontier optimize.portfolio283#' @export284chart.EfficientFrontier.optimize.portfolio <- function(object, ..., match.col="ES", n.portfolios=25, xlim=NULL, ylim=NULL, cex.axis=0.8, element.color="darkgray", main="Efficient Frontier", RAR.text="SR", rf=0, tangent.line=TRUE, cex.legend=0.8, chart.assets=TRUE, labels.assets=TRUE, pch.assets=21, cex.assets=0.8){285# This function will work with objects of class optimize.portfolio.DEoptim,286# optimize.portfolio.random, and optimize.portfolio.pso287288if(inherits(object, "optimize.portfolio.GenSA")){289stop("GenSA does not return any useable trace information for portfolios tested, thus we cannot extract an efficient frontier.")290}291292if(!inherits(object, "optimize.portfolio")) stop("object must be of class optimize.portfolio")293294portf <- object$portfolio295R <- object$R296if(is.null(R)) stop(paste("Not able to get asset returns from", object))297wts <- object$weights298299# get the stats from the object300xtract <- extractStats(object=object)301columnames <- colnames(xtract)302303# Check if match.col is in extractStats output304if(!(match.col %in% columnames)){305stop(paste(match.col, "is not a column in extractStats output"))306}307308# check if 'mean' is in extractStats output309if(!("mean" %in% columnames)){310stop("mean is not a column in extractStats output")311}312313# get the stats of the optimal portfolio314optstats <- xtract[which.min(xtract[, "out"]), ]315opt_ret <- optstats["mean"]316opt_risk <- optstats[match.col]317318# get the data to plot scatter of asset returns319asset_ret <- scatterFUN(R=R, FUN="mean")320asset_risk <- scatterFUN(R=R, FUN=match.col)321rnames <- colnames(R)322323# get the data of the efficient frontier324frontier <- extract.efficient.frontier(object=object, match.col=match.col, n.portfolios=n.portfolios, ...=...)325326# data points to plot the frontier327x.f <- frontier[, match.col]328y.f <- frontier[, "mean"]329330# Points for the Sharpe or Modified Sharpe Ratio331if(!is.null(rf)){332sr <- (y.f - rf) / (x.f)333idx.maxsr <- which.max(sr)334srmax <- sr[idx.maxsr]335}336337# set the x and y limits338if(is.null(xlim)){339xlim <- range(c(x.f, asset_risk))340# xlim[1] <- xlim[1] * 0.8341xlim[1] <- 0342xlim[2] <- xlim[2] * 1.15343}344if(is.null(ylim)){345ylim <- range(c(y.f, asset_ret))346# ylim[1] <- ylim[1] * 0.9347ylim[1] <- 0348ylim[2] <- ylim[2] * 1.1349}350351# plot the efficient frontier line352plot(x=x.f, y=y.f, ylab="Mean", xlab=match.col, main=main, xlim=xlim, ylim=ylim, axes=FALSE, ...)353354# Add the global minimum variance or global minimum ETL portfolio355points(x=x.f[1], y=y.f[1], pch=16)356357if(chart.assets){358# risk-return scatter of the assets359points(x=asset_risk, y=asset_ret, pch=pch.assets, cex=cex.assets)360if(labels.assets) text(x=asset_risk, y=asset_ret, labels=rnames, pos=4, cex=cex.assets)361}362363# plot the optimal portfolio364points(opt_risk, opt_ret, col="blue", pch=16) # optimal365text(x=opt_risk, y=opt_ret, labels="Optimal",col="blue", pos=4, cex=0.8)366if(!is.null(rf)){367# Plot tangency line and points at risk-free rate and tangency portfolio368if(tangent.line) abline(rf, srmax, lty=2)369points(0, rf, pch=16)370points(x.f[idx.maxsr], y.f[idx.maxsr], pch=16)371# text(x=x.f[idx.maxsr], y=y.f[idx.maxsr], labels="T", pos=4, cex=0.8)372# Add lengend with max Sharpe Ratio and risk-free rate373legend("topleft", paste(RAR.text, " = ", signif(srmax,3), sep = ""), bty = "n", cex=cex.legend)374legend("topleft", inset = c(0,0.05), paste("rf = ", signif(rf,3), sep = ""), bty = "n", cex=cex.legend)375}376axis(1, cex.axis = cex.axis, col = element.color)377axis(2, cex.axis = cex.axis, col = element.color)378box(col = element.color)379}380381382#' Chart weights along an efficient frontier383#'384#' This function produces a stacked barplot of weights along an efficient frontier.385#'386#' @param object object of class \code{efficient.frontier} or \code{optimize.portfolio}.387#' @param \dots passthru parameters to \code{barplot}.388#' @param colorset color palette or vector of colors to use.389#' @param n.portfolios number of portfolios to extract along the efficient frontier.390#' @param by.groups TRUE/FALSE. If TRUE, the group weights are charted.391#' @param match.col string name of column to use for risk (horizontal axis). Must match the name of an objective.392#' @param main title used in the plot.393#' @param cex.lab the magnification to be used for x-axis and y-axis labels relative to the current setting of 'cex'.394#' @param cex.axis the magnification to be used for sizing the axis text relative to the current setting of 'cex', similar to \code{\link{plot}}.395#' @param cex.legend the magnification to be used for sizing the legend relative to the current setting of 'cex', similar to \code{\link{plot}}.396#' @param legend.labels character vector to use for the legend labels.397#' @param element.color provides the color for drawing less-important chart elements, such as the box lines, axis lines, etc.398#' @param legend.loc NULL, "topright", "right", or "bottomright". If legend.loc is NULL, the legend will not be plotted.399#' @author Ross Bennett400#' @rdname chart.EF.Weights401#' @export402chart.EF.Weights <- function(object, ...){403UseMethod("chart.EF.Weights")404}405406407#' @rdname chart.EF.Weights408#' @method chart.EF.Weights efficient.frontier409#' @export410chart.EF.Weights.efficient.frontier <- function(object, ..., colorset=NULL, n.portfolios=25, by.groups=FALSE, match.col="ES", main="", cex.lab=0.8, cex.axis=0.8, cex.legend=0.8, legend.labels=NULL, element.color="darkgray", legend.loc="topright"){411# using ideas from weightsPlot.R in fPortfolio package412413if(!inherits(object, "efficient.frontier")) stop("object must be of class 'efficient.frontier'")414415if(is.list(object)){416# Objects created with create.EfficientFrontier will be a list of 2 elements417frontier <- object$frontier418} else {419# Objects created with extractEfficientFrontier will only be an efficient.frontier object420frontier <- object421}422423424# get the columns with weights425cnames <- colnames(frontier)426wts_idx <- grep(pattern="^w\\.", cnames)427wts <- frontier[, wts_idx]428429if(by.groups){430constraints <- get_constraints(object$portfolio)431groups <- constraints$groups432if(is.null(groups)) stop("group constraints not in portfolio object")433if(!is.null(groups)){434groupfun <- function(weights, groups){435# This function is to calculate weights by group given the group list436# and a matrix of weights along the efficient frontier437ngroups <- length(groups)438group_weights <- rep(0, ngroups)439for(i in 1:ngroups){440group_weights[i] <- sum(weights[groups[[i]]])441}442group_weights443}444wts <- t(apply(wts, 1, groupfun, groups=groups))445}446}447448# return along the efficient frontier449# get the "mean" column450mean.mtc <- pmatch("mean", cnames)451if(is.na(mean.mtc)) {452mean.mtc <- pmatch("mean.mean", cnames)453}454if(is.na(mean.mtc)) stop("could not match 'mean' with column name of extractStats output")455456# risk along the efficient frontier457# get the match.col column458mtc <- pmatch(match.col, cnames)459if(is.na(mtc)) {460mtc <- pmatch(paste(match.col,match.col,sep='.'),cnames)461}462if(is.na(mtc)) stop("could not match match.col with column name of extractStats output")463464# compute the weights for the barplot465pos.weights <- +0.5 * (abs(wts) + wts)466neg.weights <- -0.5 * (abs(wts) - wts)467468# Define Plot Range:469ymax <- max(rowSums(pos.weights))470ymin <- min(rowSums(neg.weights))471range <- ymax - ymin472ymax <- ymax + 0.005 * range473ymin <- ymin - 0.005 * range474dim <- dim(wts)475range <- dim[1]476xmin <- 0477if(is.null(legend.loc)){478xmax <- range479} else {480xmax <- range + 0.3 * range481}482483# set the colorset if no colorset is passed in484if(is.null(colorset))485colorset <- 1:dim[2]486487# plot the positive weights488barplot(t(pos.weights), col = colorset, space = 0, ylab = "",489xlim = c(xmin, xmax), ylim = c(ymin, ymax),490border = element.color, cex.axis=cex.axis,491axisnames=FALSE, ...)492493if(!is.null(legend.loc)){494if(legend.loc %in% c("topright", "right", "bottomright")){495# set the legend information496if(is.null(legend.labels)){497if(by.groups){498legend.labels <- names(groups)499if(is.null(legend.labels)) legend.labels <- constraints$group_labels500} else {501legend.labels <- gsub(pattern="^w\\.", replacement="", cnames[wts_idx])502}503}504legend(legend.loc, legend = legend.labels, bty = "n", cex = cex.legend, fill = colorset)505}506}507# plot the negative weights508barplot(t(neg.weights), col = colorset, space = 0, add = TRUE, border = element.color,509cex.axis=cex.axis, axes=FALSE, axisnames=FALSE, ...)510511512# Add labels513ef.return <- frontier[, mean.mtc]514ef.risk <- frontier[, mtc]515n.risk <- length(ef.risk)516n.labels <- 6517M <- c(0, ( 1:(n.risk %/% n.labels) ) ) * n.labels + 1518# use 3 significant digits519axis(3, at = M, labels = signif(ef.risk[M], 3), cex.axis=cex.axis)520axis(1, at = M, labels = signif(ef.return[M], 3), cex.axis=cex.axis)521522# axis labels and titles523mtext(match.col, side = 3, line = 2, adj = 0.5, cex = cex.lab)524mtext("Mean", side = 1, line = 2, adj = 0.5, cex = cex.lab)525mtext("Weight", side = 2, line = 2, adj = 0.5, cex = cex.lab)526# add title527title(main=main, line=3)528# mtext(main, adj = 0, line = 2.5, font = 2, cex = 0.8)529box(col=element.color)530}531532#' @rdname chart.EF.Weights533#' @method chart.EF.Weights optimize.portfolio534#' @export535chart.EF.Weights.optimize.portfolio <- function(object, ..., colorset=NULL, n.portfolios=25, by.groups=FALSE, match.col="ES", main="", cex.lab=0.8, cex.axis=0.8, cex.legend=0.8, legend.labels=NULL, element.color="darkgray", legend.loc="topright"){536# chart the weights along the efficient frontier of an objected created by optimize.portfolio537538if(!inherits(object, "optimize.portfolio")) stop("object must be of class optimize.portfolio")539540frontier <- extractEfficientFrontier(object=object, match.col=match.col, n.portfolios=n.portfolios)541chart.EF.Weights(object=frontier, colorset=colorset, ...,542match.col=match.col, by.groups=by.groups, main=main, cex.lab=cex.lab,543cex.axis=cex.axis, cex.legend=cex.legend,544legend.labels=legend.labels, element.color=element.color,545legend.loc=legend.loc)546}547548#' @rdname chart.EfficientFrontier549#' @method chart.EfficientFrontier efficient.frontier550#' @export551chart.EfficientFrontier.efficient.frontier <- function(object, ..., match.col="ES", n.portfolios=NULL, xlim=NULL, ylim=NULL, cex.axis=0.8, element.color="darkgray", main="Efficient Frontier", RAR.text="SR", rf=0, tangent.line=TRUE, cex.legend=0.8, chart.assets=TRUE, labels.assets=TRUE, pch.assets=21, cex.assets=0.8){552if(!inherits(object, "efficient.frontier")) stop("object must be of class 'efficient.frontier'")553554# get the returns and efficient frontier object555R <- object$R556frontier <- object$frontier557558# get the column names from the frontier object559cnames <- colnames(frontier)560561# get the "mean" column562mean.mtc <- pmatch("mean", cnames)563if(is.na(mean.mtc)) {564mean.mtc <- pmatch("mean.mean", cnames)565}566if(is.na(mean.mtc)) stop("could not match 'mean' with column name of efficient frontier")567568# get the match.col column569mtc <- pmatch(match.col, cnames)570if(is.na(mtc)) {571mtc <- pmatch(paste(match.col,match.col,sep='.'),cnames)572}573if(is.na(mtc)) stop("could not match match.col with column name of efficient frontier")574575if(chart.assets){576# get the data to plot scatter of asset returns577asset_ret <- scatterFUN(R=R, FUN="mean")578asset_risk <- scatterFUN(R=R, FUN=match.col)579rnames <- colnames(R)580} else {581asset_ret <- NULL582asset_risk <- NULL583}584585# set the x and y limits586if(is.null(xlim)){587xlim <- range(c(frontier[, mtc], asset_risk))588# xlim[1] <- xlim[1] * 0.8589xlim[1] <- 0590xlim[2] <- xlim[2] * 1.15591}592if(is.null(ylim)){593ylim <- range(c(frontier[, mean.mtc], asset_ret))594# ylim[1] <- ylim[1] * 0.9595ylim[1] <- 0596ylim[2] <- ylim[2] * 1.1597}598599if(!is.null(rf)){600sr <- (frontier[, mean.mtc] - rf) / (frontier[, mtc])601idx.maxsr <- which.max(sr)602srmax <- sr[idx.maxsr]603}604605# plot the efficient frontier line606plot(x=frontier[, mtc], y=frontier[, mean.mtc], ylab="Mean", xlab=match.col, main=main, xlim=xlim, ylim=ylim, axes=FALSE, ...)607608# Add the global minimum variance or global minimum ETL portfolio609points(x=frontier[1, mtc], y=frontier[1, mean.mtc], pch=16)610611if(chart.assets){612# risk-return scatter of the assets613points(x=asset_risk, y=asset_ret, pch=pch.assets, cex=cex.assets)614if(labels.assets) text(x=asset_risk, y=asset_ret, labels=rnames, pos=4, cex=cex.assets)615}616617if(!is.null(rf)){618# Plot tangency line and points at risk-free rate and tangency portfolio619if(tangent.line) abline(rf, srmax, lty=2)620points(0, rf, pch=16)621points(frontier[idx.maxsr, mtc], frontier[idx.maxsr, mean.mtc], pch=16)622# text(x=frontier[idx.maxsr], y=frontier[idx.maxsr], labels="T", pos=4, cex=0.8)623# Add legend with max Risk adjusted Return ratio and risk-free rate624legend("topleft", paste(RAR.text, " = ", signif(srmax,3), sep = ""), bty = "n", cex=cex.legend)625legend("topleft", inset = c(0,0.05), paste("rf = ", signif(rf,3), sep = ""), bty = "n", cex=cex.legend)626}627axis(1, cex.axis = cex.axis, col = element.color)628axis(2, cex.axis = cex.axis, col = element.color)629box(col = element.color)630}631632#' Plot multiple efficient frontiers633#'634#' Overlay the efficient frontiers of multiple portfolio objects on a single plot.635#'636#' @param R an xts object of asset returns637#' @param portfolio_list list of portfolio objects created by638#' \code{\link{portfolio.spec}} and combined with \code{\link{combine.portfolios}}639#' @param type type of efficient frontier, see \code{\link{create.EfficientFrontier}}640#' @param n.portfolios number of portfolios to extract along the efficient frontier.641#' This is only used for objects of class \code{optimize.portfolio}642#' @param match.col string name of column to use for risk (horizontal axis).643#' Must match the name of an objective.644#' @param search_size passed to optimize.portfolio for type="DEoptim" or type="random".645#' @param main title used in the plot.646#' @param cex.axis the magnification to be used for sizing the axis text relative to the current setting of 'cex', similar to \code{\link{plot}}.647#' @param element.color provides the color for drawing less-important chart elements, such as the box lines, axis lines, etc.648#' @param legend.loc location of the legend; NULL, "bottomright", "bottom", "bottomleft", "left", "topleft", "top", "topright", "right" and "center".649#' @param legend.labels character vector to use for the legend labels.650#' @param cex.legend The magnification to be used for sizing the legend relative to the current setting of 'cex', similar to \code{\link{plot}}.651#' @param xlim set the x-axis limit, same as in \code{\link{plot}}.652#' @param ylim set the y-axis limit, same as in \code{\link{plot}}.653#' @param \dots passthrough parameters to \code{\link{plot}}.654#' @param chart.assets TRUE/FALSE to include the assets.655#' @param labels.assets TRUE/FALSE to include the asset names in the plot.656#' @param pch.assets plotting character of the assets, same as in \code{\link{plot}}.657#' @param cex.assets A numerical value giving the amount by which the asset points and labels should be magnified relative to the default.658#' @param col vector of colors with length equal to the number of portfolios in \code{portfolio_list}.659#' @param lty vector of line types with length equal to the number of portfolios in \code{portfolio_list}.660#' @param lwd vector of line widths with length equal to the number of portfolios in \code{portfolio_list}.661#' @author Ross Bennett662#' @export663chart.EfficientFrontierOverlay <- function(R, portfolio_list, type, n.portfolios=25, match.col="ES", search_size=2000, main="Efficient Frontiers", cex.axis=0.8, element.color="darkgray", legend.loc=NULL, legend.labels=NULL, cex.legend=0.8, xlim=NULL, ylim=NULL, ..., chart.assets=TRUE, labels.assets=TRUE, pch.assets=21, cex.assets=0.8, col=NULL, lty=NULL, lwd=NULL){664# create multiple efficient frontier objects (one per portfolio in portfolio_list)665if(!inherits(portfolio_list, "portfolio.list")) stop("portfolio_list must be passed in as a list")666if(length(portfolio_list) == 1) warning("Only one portfolio object in portfolio_list")667# store in out668out <- list()669for(i in 1:length(portfolio_list)){670if(!is.portfolio(portfolio_list[[i]])) stop("portfolio in portfolio_list must be of class 'portfolio'")671out[[i]] <- create.EfficientFrontier(R=R, portfolio=portfolio_list[[i]], type=type, n.portfolios=n.portfolios, match.col=match.col, search_size=search_size)672}673# get the data to plot scatter of asset returns674asset_ret <- scatterFUN(R=R, FUN="mean")675asset_risk <- scatterFUN(R=R, FUN=match.col)676rnames <- colnames(R)677678# set the x and y limits679if(is.null(xlim)){680xlim <- range(asset_risk)681# xlim[1] <- xlim[1] * 0.8682xlim[1] <- 0683xlim[2] <- xlim[2] * 1.15684}685if(is.null(ylim)){686ylim <- range(asset_ret)687# ylim[1] <- ylim[1] * 0.9688ylim[1] <- 0689ylim[2] <- ylim[2] * 1.1690}691692# plot the assets693plot(x=asset_risk, y=asset_ret, xlab=match.col, ylab="Mean", main=main, xlim=xlim, ylim=ylim, axes=FALSE, type="n", ...)694axis(1, cex.axis = cex.axis, col = element.color)695axis(2, cex.axis = cex.axis, col = element.color)696box(col = element.color)697698if(chart.assets){699# risk-return scatter of the assets700points(x=asset_risk, y=asset_ret, pch=pch.assets, cex=cex.assets)701if(labels.assets) text(x=asset_risk, y=asset_ret, labels=rnames, pos=4, cex=cex.assets)702}703704# set some basic plot parameters705if(is.null(col)) col <- 1:length(out)706if(is.null(lty)) lty <- 1:length(out)707if(is.null(lwd)) lwd <- rep(1, length(out))708709for(i in 1:length(out)){710tmp <- out[[i]]711tmpfrontier <- tmp$frontier712cnames <- colnames(tmpfrontier)713714# get the "mean" column715mean.mtc <- pmatch("mean", cnames)716if(is.na(mean.mtc)) {717mean.mtc <- pmatch("mean.mean", cnames)718}719if(is.na(mean.mtc)) stop("could not match 'mean' with column name of extractStats output")720721# get the match.col column722mtc <- pmatch(match.col, cnames)723if(is.na(mtc)) {724mtc <- pmatch(paste(match.col, match.col, sep='.'),cnames)725}726if(is.na(mtc)) stop("could not match match.col with column name of extractStats output")727# Add the efficient frontier lines to the plot728lines(x=tmpfrontier[, mtc], y=tmpfrontier[, mean.mtc], col=col[i], lty=lty[i], lwd=lwd[i])729}730if(!is.null(legend.loc)){731if(is.null(legend.labels)){732legend.labels <- paste("Portfolio", 1:length(out), sep=".")733}734legend(legend.loc, legend=legend.labels, col=col, lty=lty, lwd=lwd, cex=cex.legend, bty="n")735}736return(invisible(out))737}738739740#' Overlay the efficient frontiers of different minRisk portfolio objects on a single plot.741#'742#' @param R an xts object of asset returns743#' @param portfolio same constrained portfolio created by \code{\link{portfolio.spec}}744#' @param risk_type type of risk that you want to compare745#' @param n.portfolios number of portfolios to extract along the efficient frontier.746#' This is only used for objects of class \code{optimize.portfolio}747#' @param match.col string name of column to use for portfolio object.748#' Must match the name of an objective.749#' @param guideline show the risk difference and mean difference between efficient frontiers750#' @param plot_type define the plot_type, default is "l"751#' @param main title used in the plot.752#' @param cex.axis the magnification to be used for sizing the axis text relative to the current setting of 'cex', similar to \code{\link{plot}}.753#' @param element.color provides the color for drawing less-important chart elements, such as the box lines, axis lines, etc.754#' @param legend.loc location of the legend; NULL, "bottomright", "bottom", "bottomleft", "left", "topleft", "top", "topright", "right" and "center".755#' @param legend.labels character vector to use for the legend labels.756#' @param cex.legend The magnification to be used for sizing the legend relative to the current setting of 'cex', similar to \code{\link{plot}}.757#' @param xlim set the x-axis limit, same as in \code{\link{plot}}.758#' @param ylim set the y-axis limit, same as in \code{\link{plot}}.759#' @param \dots passthrough parameters to \code{\link{plot}}.760#' @param chart.assets TRUE/FALSE to include the assets.761#' @param labels.assets TRUE/FALSE to include the asset names in the plot.762#' @param pch.assets plotting character of the assets, same as in \code{\link{plot}}.763#' @param cex.assets A numerical value giving the amount by which the asset points and labels should be magnified relative to the default.764#' @param col vector of colors with length equal to the number of portfolios in \code{portfolio_list}. Add two more to customize guideline color.765#' @param lty vector of line types with length equal to the number of portfolios in \code{portfolio_list}. Add two more to customize guideline type.766#' @param lwd vector of line widths with length equal to the number of portfolios in \code{portfolio_list}. Add two more to customize guideline width.767#' @author Xinran Zhao768#' @export769chart.EfficientFrontierCompare <- function(R, portfolio, risk_type, n.portfolios=25, match.col=c("StdDev", "ES"), guideline=NULL, main="Efficient Frontiers", plot_type = "l", cex.axis=0.5, element.color="darkgray", legend.loc=NULL, legend.labels=NULL, cex.legend=0.8, xlim=NULL, ylim=NULL, ..., chart.assets=TRUE, labels.assets=TRUE, pch.assets=21, cex.assets=0.8, col=NULL, lty=NULL, lwd=NULL){770# show digits771options(scipen = 999)772773# store in out774out <- create.EfficientFrontier(R=R, portfolio=portfolio, type="mean-risk", risk_type=risk_type, compare_port = match.col, n.portfolios = n.portfolios, ...)775n.p = dim(out$frontier)[1]776m.p = dim(out$frontier)[2]777rnames <- colnames(R)778779# set the x and y limits780if(is.null(xlim)){781xlim <- c(0, 0)782xlim[1] <- out$frontier[1,1] * 0.7783xlim[2] <- out$frontier[n.p, 1] * 1.2784}785if(is.null(ylim)){786ylim <- c(0, 0)787ylim[1] <- out$frontier[1,2] * 0.7788ylim[2] <- out$frontier[n.p, 2] * 1.2789}790791# plot the assets792plot(x=1, y=1, xlab=risk_type, ylab="Mean", main=main, xlim=xlim, ylim=ylim, axes=FALSE, type="n", ...)793axis(1, cex.axis = cex.axis, col = element.color)794axis(2, cex.axis = cex.axis, col = element.color)795box(col = element.color)796797if(is.null(guideline)) guideline <- ifelse(length(match.col) == 2, TRUE, FALSE)798if(guideline){799# set some basic plot parameters800if(is.null(col) | length(col) == length(match.col)) col <- c(1:length(match.col), 1, 1)801if(is.null(lty) | length(lty) == length(match.col)) lty <- c(1:length(match.col), 3, 3)802if(is.null(lwd) | length(lwd) == length(match.col)) lwd <- c(rep(1, length(match.col)), 1, 1)803} else {804if(is.null(col)) col <- 1:length(match.col)805if(is.null(lty)) lty <- 1:length(match.col)806if(is.null(lwd)) lwd <- rep(1, length(match.col))807}808809# get the "mean" column810cnames <- colnames(out$frontier)811mean.mtc <- pmatch("mean", cnames)812if(is.na(mean.mtc)) {813mean.mtc <- pmatch("mean.mean", cnames)814}815if(is.na(mean.mtc)) stop("could not match 'mean' with column name of extractStats output")816817for(i in 1:length(match.col)){818# get the match.col column819mtc <- pmatch(match.col[i], cnames)820if(is.na(mtc)) stop("could not match match.col with column name of extractStats output")821# Add the efficient frontier lines to the plot822lines(x=out$frontier[, mtc], y=out$frontier[, mean.mtc], col=col[i], lty=lty[i], lwd=lwd[i], type = plot_type, ...)823}824825# legend826if(!is.null(legend.loc)){827legend.loc = "bottomright"828}829if(is.null(legend.labels)){830legend.labels <- paste("min", match.col, "Portfolio")831}832if(guideline){833lines(x=c(out$frontier[1,1], out$frontier[1,m.p]), y = rep(out$frontier[1,2], 2), lty=lty[3], col=col[3], lwd=lwd[3])834points(x=c(out$frontier[1,1], out$frontier[1,m.p]), y = rep(out$frontier[1,2], 2), pch=pch.assets, cex=cex.assets)835x_diff = abs(out$frontier[,1] - out$frontier[1,m.p])836x_index = min(abs(out$frontier[,1] - out$frontier[1,m.p]))837lines(x=c(out$frontier[which(x_diff == x_index),1], out$frontier[1,m.p]), y = c(out$frontier[which(x_diff == x_index),2], out$frontier[1,2]), lty=lty[4], col=col[4], lwd=lwd[4])838points(x=c(out$frontier[which(x_diff == x_index),1], out$frontier[1,m.p]), y = c(out$frontier[which(x_diff == x_index),2], out$frontier[1,2]), pch=pch.assets, cex=cex.assets)839if(labels.assets){840text(out$frontier[1,1], out$frontier[1,2], labels = paste("(", round(out$frontier[1,1], 4), ",", round(out$frontier[1,2], 4), ")"), pos = 1, cex = cex.assets)841text(out$frontier[1,m.p], out$frontier[1,2], labels = paste("(", round(out$frontier[1,m.p], 4), ",", round(out$frontier[1,2], 4), ")"), pos = 4, cex = cex.assets)842text(out$frontier[which(x_diff == x_index),1], out$frontier[which(x_diff == x_index),2], labels = paste("(", round(out$frontier[which(x_diff == x_index),1], 4), ",", round(out$frontier[which(x_diff == x_index),2], 4), ")"), pos = 2, cex = cex.assets)843}844legend.labels <- append(legend.labels, paste("% Decrease in Risk =", round((out$frontier[1,m.p] - out$frontier[1,1]) * 100 / out$frontier[1,1], 2)))845legend.labels <- append(legend.labels, paste("% Increase in Return =", round((out$frontier[which(x_diff == x_index),2] - out$frontier[1,2]) * 100 / out$frontier[1,2], 2)))846}847legend("bottomright", legend=legend.labels, col=col, lty=lty, lwd=lwd, cex=cex.legend, bty="n")848return(invisible(out))849}850851###############################################################################852# R (https://r-project.org/) Numeric Methods for Optimization of Portfolios853#854# Copyright (c) 2004-2023 Brian G. Peterson, Peter Carl, Ross Bennett, Kris Boudt, Xinran Zhao855#856# This library is distributed under the terms of the GNU Public License (GPL)857# for full details see the file COPYING858#859# $Id$860#861###############################################################################862863864