###############################################################################1# R (https://r-project.org/) Numeric Methods for Optimization of Portfolios2#3# Copyright (c) 2004-2021 Brian G. Peterson, Peter Carl, Ross Bennett, Kris Boudt4#5# This library is distributed under the terms of the GNU Public License (GPL)6# for full details see the file COPYING7#8# $Id$9#10###############################################################################1112#' constructor for class 'objective'13#'14#' Typically called as a sub-function by the user function \code{\link{add.objective}}.15#' See main documentation there.16#'17#' @param name name of the objective which will be used to call a function, like 'ES', 'VaR', 'mean'18#' @param target univariate target for the objective, default NULL19#' @param arguments default arguments to be passed to an objective function when executed20#' @param enabled TRUE/FALSE21#' @param \dots any other passthrough parameters22#' @param multiplier multiplier to apply to the objective, usually 1 or -123#' @param objclass string class to apply, default 'objective'24#' @seealso \code{\link{add.objective}}, \code{\link{portfolio.spec}}25#' @author Brian G. Peterson26#' @export27objective<-function(name , target=NULL , arguments, enabled=TRUE , ..., multiplier=1, objclass='objective'){28if(!hasArg(name)) stop("you must specify an objective name")29if (hasArg(name)) if(is.null(name)) stop("you must specify an objective name")30if (!hasArg(arguments) | is.null(arguments)) arguments<-list()31if (!is.list(arguments)) stop("arguments must be passed as a named list")3233## now structure and return34return(structure( c(list(name = name,35target = target,36arguments=arguments,37enabled = enabled,38multiplier = multiplier39#call = match.call()40),41list(...)),42class=objclass43) # end structure44)45}464748#' check class of an objective object49#' @param x an object potentially of type 'objective' to test50#' @author Brian G. Peterson51#' @export52is.objective <- function( x ) {53inherits( x, "objective" )54}5556#' @rdname add.objective57#' @name add.objective58#' @export59add.objective_v1 <- function(constraints, type, name, arguments=NULL, enabled=TRUE, ..., indexnum=NULL)60{61if (!is.constraint(constraints)) {stop("constraints passed in are not of class constraint")}6263if (!hasArg(name)) stop("you must supply a name for the objective")64if (!hasArg(type)) stop("you must supply a type of objective to create")65if (!hasArg(enabled)) enabled=TRUE66if (!hasArg(arguments) | is.null(arguments)) arguments<-list()67if (!is.list(arguments)) stop("arguments must be passed as a named list")6869assets=constraints$assets7071tmp_objective=NULL7273switch(type,74return=, return_objective=75{tmp_objective = return_objective(name=name,76enabled=enabled,77arguments=arguments,78... = ...79)80},8182risk=, portfolio_risk=, portfolio_risk_objective =83{tmp_objective = portfolio_risk_objective(name=name,84enabled=enabled,85arguments=arguments,86...=...87)88},8990risk_budget=, risk_budget_objective=91{tmp_objective = risk_budget_objective(assets=constraints$assets,92name=name,93enabled=enabled,94arguments=arguments,95...=...96)97},98turnover = {tmp_objective = turnover_objective(name=name,99enabled=enabled,100arguments=arguments,101...=...)102},103tmp_minmax = {tmp_objective = minmax_objective(name=name,104enabled=enabled,105arguments=arguments,106...=...)107},108weight_conc=, weight_concentration =109{tmp_objective = weight_concentration_objective(name=name,110enabled=enabled,111arguments=arguments,112...=...)113},114115null =116{return(constraints)} # got nothing, default to simply returning117) # end objective type switch118if(is.objective(tmp_objective)) {119if(!hasArg(indexnum) | (hasArg(indexnum) & is.null(indexnum))) indexnum = length(constraints$objectives)+1120tmp_objective$call<-match.call()121constraints$objectives[[indexnum]]<-tmp_objective122}123return(constraints)124}125126#' General interface for adding optimization objectives, including risk, return, and risk budget127#'128#' This function is the main function for adding and updating business objectives in an object of type \code{\link{portfolio.spec}}.129#'130#' In general, you will define your objective as one of the following types: 'return', 'risk', 'risk_budget', 'quadratic utility', or 'weight_concentration'.131#' These have special handling and intelligent defaults for dealing with the function most likely to be132#' used as objectives, including mean, median, VaR, ES, etc.133#'134#' Objectives of type 'turnover' and 'minmax' are also supported.135#'136#' @param portfolio an object of type 'portfolio' to add the objective to, specifying the portfolio for the optimization, see \code{\link{portfolio}}137#' @param constraints a 'v1_constraint' object for backwards compatibility, see \code{\link{constraint}}138#' @param type character type of the objective to add or update, currently 'return','risk', 'risk_budget', 'quadratic_utility', or 'weight_concentration'139#' @param name name of the objective, should correspond to a function, though we will try to make allowances140#' @param arguments default arguments to be passed to an objective function when executed141#' @param enabled TRUE/FALSE142#' @param \dots any other passthru parameters143#' @param indexnum if you are updating a specific objective, the index number in the $objectives list to update144#' @author Brian G. Peterson and Ross Bennett145#' @aliases add.objective_v2 add.objective_v1146#' @seealso \code{\link{objective}}, \code{\link{portfolio.spec}}147#' @rdname add.objective148#' @name add.objective149#' @examples150#' data(edhec)151#' returns <- edhec[,1:4]152#' fund.names <- colnames(returns)153#' portf <- portfolio.spec(assets=fund.names)154#' # Add some basic constraints155#' portf <- add.constraint(portf, type="full_investment")156#' portf <- add.constraint(portf, type="long_only")157#'158#' # Creates a new portfolio object using portf and adds a quadratic utility159#' # objective. This will add two objectives to the portfolio object; 1) mean and160#' # 2) var. The risk aversion parameter is commonly referred to as lambda in the161#' # quadratic utility formulation that controls how much the portfolio variance162#' # is penalized.163#' portf.maxQU <- add.objective(portf, type="quadratic_utility",164#' risk_aversion=0.25)165#'166#' # Creates a new portfolio object using portf and adds mean as an objective167#' portf.maxMean <- add.objective(portf, type="return", name="mean")168#'169#' # Creates a new portfolio object using portf and adds StdDev as an objective170#' portf.minStdDev <- add.objective(portf, type="risk", name="StdDev")171#'172#' # Creates a new portfolio object using portf and adds ES as an objective.173#' # Note that arguments to ES are passed in as a named list.174#' portf.minES <- add.objective(portf, type="risk", name="ES",175#' arguments=list(p=0.925, clean="boudt"))176#'177#' # Creates a new portfolio object using portf.minES and adds a risk budget178#' # objective with limits on component risk contribution.179#' # Note that arguments to ES are passed in as a named list.180#' portf.RiskBudgetES <- add.objective(portf.minES, type="risk_budget", name="ES",181#' arguments=list(p=0.925, clean="boudt"),182#' min_prisk=0, max_prisk=0.6)183#'184#' # Creates a new portfolio object using portf.minES and adds a risk budget185#' # objective with equal component risk contribution.186#' # Note that arguments to ES are passed in as a named list.187#' portf.EqRiskES <- add.objective(portf.minES, type="risk_budget", name="ES",188#' arguments=list(p=0.925, clean="boudt"),189#' min_concentration=TRUE)190#'191#' # Creates a new portfolio object using portf and adds a weight_concentration192#' # objective. The conc_aversion parameter controls how much concentration is193#' # penalized. The portfolio concentration is defined as the Herfindahl Hirschman194#' # Index of the weights.195#' portf.conc <- add.objective(portf, type="weight_concentration",196#' name="HHI", conc_aversion=0.01)197#' @export add.objective198#' @export add.objective_v2199add.objective <- add.objective_v2 <- function(portfolio, constraints=NULL, type, name, arguments=NULL, enabled=TRUE, ..., indexnum=NULL){200if(!is.null(constraints) & inherits(constraints, "v1_constraint")){201return(add.objective_v1(constraints=constraints, type=type, name=name, arguments=arguments, enabled=enabled, ...=..., indexnum=indexnum))202}203204# This function is based on the original add.objective function, but modified205# to add objectives to a portfolio object instead of a constraint object.206if (!is.portfolio(portfolio)) {stop("portfolio passed in is not of class portfolio")}207208if (type != "quadratic_utility" & !hasArg(name)) stop("you must supply a name for the objective")209if (!hasArg(type)) stop("you must supply a type of objective to create")210if (!hasArg(enabled)) enabled=TRUE211if (!hasArg(arguments) | is.null(arguments)) arguments<-list()212if (!is.list(arguments)) stop("arguments must be passed as a named list")213214assets=portfolio$assets215216tmp_objective=NULL217218switch(type,219return=, return_objective=220{tmp_objective = return_objective(name=name,221enabled=enabled,222arguments=arguments,223... = ...224)225},226227risk=, portfolio_risk=, portfolio_risk_objective =228{tmp_objective = portfolio_risk_objective(name=name,229enabled=enabled,230arguments=arguments,231...=...232)233},234235risk_budget=, risk_budget_objective=236{tmp_objective = risk_budget_objective(assets=portfolio$assets,237name=name,238enabled=enabled,239arguments=arguments,240...=...241)242},243244turnover = {tmp_objective = turnover_objective(name=name,245enabled=enabled,246arguments=arguments,247...=...)248},249tmp_minmax = {tmp_objective = minmax_objective(name=name,250enabled=enabled,251arguments=arguments,252...=...)253},254qu=, quadratic_utility = {tmp_objective = quadratic_utility_objective(enabled=enabled, ...=...)255# quadratic_utility_objective returns a list of a return_objective and a portfolio_risk_objective256# we just need to combine it to the portfolio$objectives slot and return the portfolio257portfolio$objectives <- c(portfolio$objectives, tmp_objective)258return(portfolio)259},260weight_conc=, weight_concentration =261{tmp_objective = weight_concentration_objective(name=name,262enabled=enabled,263arguments=arguments,264...=...)265},266null =267{return(portfolio)} # got nothing, default to simply returning268) # end objective type switch269270if(is.objective(tmp_objective)) {271if(!hasArg(indexnum) | (hasArg(indexnum) & is.null(indexnum))) indexnum = length(portfolio$objectives)+1272tmp_objective$call <- match.call()273portfolio$objectives[[indexnum]] <- tmp_objective274}275return(portfolio)276}277278279# update.objective <- function(object, ...) {280# # here we do a bunch of magic to update the correct index'd objective281#282# constraints <- object283#284# if (is.null(constraints) | !is.constraint(constraints)){285# stop("you must pass in an object of class constraints to modify")286# }287#288#289# }290291292#' constructor for class return_objective293#'294#' if target is null, we'll try to maximize the return metric295#'296#' if target is set, we'll try to meet or exceed the metric, penalizing a shortfall297#'298#' @param name name of the objective, should correspond to a function, though we will try to make allowances299#' @param target univariate target for the objective300#' @param arguments default arguments to be passed to an objective function when executed301#' @param multiplier multiplier to apply to the objective, usually 1 or -1302#' @param enabled TRUE/FALSE303#' @param \dots any other passthru parameters304#' @return object of class 'return_objective'305#' @author Brian G. Peterson306#' @export307return_objective <- function(name, target=NULL, arguments=NULL, multiplier=-1, enabled=TRUE, ... )308{309if(!hasArg(target)) target = NULL310## if target is null, we'll try to maximize the return metric311if(!hasArg(multiplier)) multiplier=-1312return(objective(name=name, target=target, arguments=arguments, enabled=enabled, multiplier=multiplier,objclass=c("return_objective","objective"), ... ))313} # end return_objective constructor314315#' constructor for class portfolio_risk_objective316#'317#' if target is null, we'll try to minimize the risk metric318#' @param name name of the objective, should correspond to a function, though we will try to make allowances319#' @param target univariate target for the objective320#' @param arguments default arguments to be passed to an objective function when executed321#' @param multiplier multiplier to apply to the objective, usually 1 or -1322#' @param enabled TRUE/FALSE323#' @param \dots any other passthru parameters324#' @return object of class 'portfolio_risk_objective'325#' @author Brian G. Peterson326#' @export327portfolio_risk_objective <- function(name, target=NULL, arguments=NULL, multiplier=1, enabled=TRUE, ... )328{329if(is.null(arguments$portfolio_method)) arguments$portfolio_method="single" #use multivariate risk calcs330return(objective(name=name,target=target, arguments=arguments, multiplier=multiplier,enabled=enabled, objclass=c("portfolio_risk_objective","objective"), ... ))331} # end portfolio_risk_objective constructor332333#' constructor for class risk_budget_objective334#'335#' @param assets vector of assets to use, should come from constraints object336#' @param name name of the objective, should correspond to a function, though we will try to make allowances337#' @param target univariate target for the objective338#' @param arguments default arguments to be passed to an objective function when executed339#' @param multiplier multiplier to apply to the objective, usually 1 or -1340#' @param enabled TRUE/FALSE341#' @param \dots any other passthru parameters342#' @param min_prisk minimum percentage contribution to risk343#' @param max_prisk maximum percentage contribution to risk344#' @param min_concentration TRUE/FALSE whether to minimize concentration, default FALSE, always TRUE if min_prisk and max_prisk are NULL345#' @param min_difference TRUE/FALSE whether to minimize difference between concentration, default FALSE346#' @return object of class 'risk_budget_objective'347#' @author Brian G. Peterson348#' @export349risk_budget_objective <- function(assets, name, target=NULL, arguments=NULL, multiplier=1, enabled=TRUE, ..., min_prisk, max_prisk, min_concentration=FALSE, min_difference=FALSE )350{351if(is.null(arguments$portfolio_method)) arguments$portfolio_method="component"352353#if( is.null(RBlower) ){ RBlower = rep(-Inf,N) } ; if( is.null(RBupper) ){ RBupper = rep(Inf,N) }354nassets=length(assets)355if(hasArg(min_prisk) & hasArg(max_prisk)) {356if (length(min_prisk)>1 & length(max_prisk)>1){357if (length(min_prisk)!=length(max_prisk)) { stop("length of min_prisk and max_prisk must be the same") }358}359}360if(hasArg(min_prisk)){361if (length(min_prisk)==1) {362min_prisk <- rep(min_prisk,nassets)363names(min_prisk)<-names(assets)364}365if (length(min_prisk)!=nassets) stop(paste("length of min_prisk must be equal to 1 or the number of assets",nassets))366}367if(hasArg(max_prisk)){368if (length(max_prisk)==1) {369max_prisk <- rep(max_prisk,nassets)370names(max_prisk)<-names(assets)371}372if (length(max_prisk)!=nassets) stop(paste("length of max_prisk must be equal to 1 or the number of assets",nassets))373}374375if(!hasArg(max_prisk)) max_prisk = NULL376if(!hasArg(min_prisk)) min_prisk = NULL377378if (is.null(min_prisk) & is.null(max_prisk))379min_concentration<-TRUE380381Objective<-objective(name=name,target=target, arguments=arguments, multiplier=multiplier,enabled=enabled, objclass=c("risk_budget_objective","objective"), ... )382Objective$min_prisk = min_prisk383Objective$max_prisk = max_prisk384Objective$min_concentration<-min_concentration385Objective$min_difference<-min_difference386387return(Objective)388} # end risk_budget_objective constructor389390#' constructor for class turnover_objective391#'392#' if target is null, we'll try to minimize the turnover metric393#'394#' if target is set, we'll try to meet the metric395#'396#' @param name name of the objective, should correspond to a function, though we will try to make allowances397#' @param target univariate target for the objective398#' @param arguments default arguments to be passed to an objective function when executed399#' @param multiplier multiplier to apply to the objective, usually 1 or -1400#' @param enabled TRUE/FALSE401#' @param \dots any other passthru parameters402#' @return an objective of class 'turnover_objective'403#' @author Ross Bennett404#' @export405turnover_objective <- function(name, target=NULL, arguments=NULL, multiplier=1, enabled=TRUE, ... )406{407if(!hasArg(target)) target = NULL408## if target is null, we'll try to minimize the turnover metric409if(!hasArg(multiplier)) multiplier=1410return(objective(name=name, target=target, arguments=arguments, enabled=enabled, multiplier=multiplier,objclass=c("turnover_objective","objective"), ... ))411} # end turnover_objective constructor412413#' constructor for class tmp_minmax_objective414#'415#' This objective allows for min and max targets to be specified.416#'417#' If target is set, we'll try to meet the metric418#'419#' If target is NULL and min and max are specified, then do the following:420#'421#' If max is violated to the upside, penalize the metric. If min is violated to422#' the downside, penalize the metric. The purpose of this objective is to try423#' to meet the range between min and max424#'425#' @param name name of the objective, should correspond to a function, though we will try to make allowances426#' @param target univariate target for the objective427#' @param min minimum value428#' @param max maximum value429#' @param arguments default arguments to be passed to an objective function when executed430#' @param multiplier multiplier to apply to the objective, usually 1 or -1431#' @param enabled TRUE/FALSE432#' @param \dots any other passthru parameters433#' @return object of class 'minmax_objective'434#' @author Ross Bennett435#' @export436minmax_objective <- function(name, target=NULL, arguments=NULL, multiplier=1, enabled=TRUE, ..., min, max )437{438if(!hasArg(target)) target = NULL439## if target is null, we'll try to minimize the metric440if(!hasArg(multiplier)) multiplier=1441Objective <- objective(name=name, target=target, arguments=arguments, enabled=enabled, multiplier=multiplier,objclass=c("minmax_objective","objective"), ... )442Objective$min <- min443Objective$max <- max444return(Objective)445} # end minmax_objective constructor446447#' constructor for quadratic utility objective448#'449#' This function calls \code{\link{return_objective}} and \code{\link{portfolio_risk_objective}}450#' to create a list of the objectives to be added to the portfolio.451#'452#' @param risk_aversion risk_aversion (i.e. lambda) parameter to penalize variance453#' @param target target mean return value454#' @param enabled TRUE/FALSE, default enabled=TRUE455#' @return a list of two elements456#' \itemize{457#' \item \code{return_objective}458#' \item \code{portfolio_risk_objective}459#' }460#' @author Ross Bennett461#' @export462quadratic_utility_objective <- function(risk_aversion=1, target=NULL, enabled=TRUE){463qu <- list()464qu[[1]] <- return_objective(name="mean", target=target, enabled=enabled)465qu[[2]] <- portfolio_risk_objective(name="var", risk_aversion=risk_aversion, enabled=enabled)466return(qu)467} # end quadratic utility objective constructor468469#' Constructor for weight concentration objective470#'471#' This function penalizes weight concentration using the Herfindahl-Hirschman Index472#' as a measure of concentration.473#'474#' The \code{conc_aversion} argument can be a scalar or vector of concentration475#' aversion values. If \code{conc_aversion} is a scalar and \code{conc_groups} is476#' \code{NULL}, then the concentration aversion value will be applied to the overall477#' weights.478#'479#' If \code{conc_groups} is specified as an argument, then the concentration480#' aversion value(s) will be applied to each group.481#'482#' @param name name of concentration measure, currently only "HHI" is supported.483#' @param conc_aversion concentration aversion value(s)484#' @param conc_groups list of vectors specifying the groups of the assets. Similar485#' to \code{groups} in \code{\link{group_constraint}}486#' @param arguments default arguments to be passed to an objective function when executed487#' @param enabled TRUE/FALSE488#' @param \dots any other passthru parameters489#' @return an object of class 'weight_concentration_objective'490#' @author Ross Bennett491#' @export492weight_concentration_objective <- function(name, conc_aversion, conc_groups=NULL, arguments=NULL, enabled=TRUE, ...){493# TODO: write HHI function to be used by global solvers in constrained_objective494495# check if conc_groups is specified as an argument496if(!is.null(conc_groups)){497arguments$groups <- conc_groups498if(!is.list(conc_groups)) stop("conc_groups must be passed in as a list")499500if(length(conc_aversion) == 1){501# if conc_aversion is a scalar, replicate to the number of groups502conc_aversion <- rep(conc_aversion, length(conc_groups))503}504# length of conc_aversion must be equal to the length of conc_groups505if(length(conc_aversion) != length(conc_groups)) stop("length of conc_aversion must be equal to length of groups")506} else if(is.null(conc_groups)){507if(length(conc_aversion) != 1) stop("conc_aversion must be a scalar value when conc_groups are not specified")508}509Objective <- objective(name=name, enabled=enabled, arguments=arguments, objclass=c("weight_concentration_objective","objective"), ... )510Objective$conc_aversion <- conc_aversion511Objective$conc_groups <- conc_groups512return(Objective)513}514515#' Insert a list of objectives into the objectives slot of a portfolio object516#'517#' This is a helper function primarily for backwards compatibility to insert518#' objectives from a 'v1_constraint' object into the v2 'portfolio' object.519#'520#' @param portfolio object of class 'portfolio'521#' @param objectives list of objective objects522#' @author Ross Bennett523#' @export524insert_objectives <- function(portfolio, objectives){525# Check portfolio object526if (is.null(portfolio) | !is.portfolio(portfolio)){527stop("you must pass in an object of class portfolio")528}529530# Check that objectives is a list531if(!is.list(objectives)) stop("objectives must be passed in as a list")532533# Check that all objects in the list are of class objective534for(i in 1:length(objectives)){535if(!is.objective(objectives[[i]]))536stop("objectives must be passed in as a list and all objects in objectives must be of class 'objective'")537}538539portfolio$objectives <- objectives540return(portfolio)541}542543544