Path: blob/master/R/constrained_objective.R
1433 views
###############################################################################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# TODO add examples1314# TODO add more details about the nuances of the optimization engines151617#' @rdname constrained_objective18#' @name constrained_objective19#' @export20constrained_objective_v1 <- function(w, R, constraints, ..., trace=FALSE, normalize=TRUE, storage=FALSE)21{22if (ncol(R)>length(w)) {23R=R[,1:length(w)]24}25if(!hasArg(penalty)) penalty = 1e426N = length(w)27T = nrow(R)28if(hasArg(optimize_method))29optimize_method=match.call(expand.dots=TRUE)$optimize_method else optimize_method=''30if(hasArg(verbose))31verbose=match.call(expand.dots=TRUE)$verbose32else verbose=FALSE3334# check for valid constraints35if (!is.constraint(constraints)) {36stop("constraints passed in are not of class constraint")37}3839# check that the constraints and the weighting vector have the same length40if (N != length(constraints$assets)){41warning("length of constraints asset list and weights vector do not match, results may be bogus")42}4344out=04546# do the get here47store_output <- try(get('.objectivestorage',envir=.storage),silent=TRUE)48if(inherits(store_output,"try-error")) storage=FALSE else storage=TRUE4950if(isTRUE(normalize)){51if(!is.null(constraints$min_sum) | !is.null(constraints$max_sum)){52# the user has passed in either min_sum or max_sum constraints for the portfolio, or both.53# we'll normalize the weights passed in to whichever boundary condition has been violated54# NOTE: this means that the weights produced by a numeric optimization algorithm like DEoptim55# might violate your constraints, so you'd need to renormalize them after optimizing56# we'll create functions for that so the user is less likely to mess it up.5758# NOTE: need to normalize in the optimization wrapper too before we return, since we've normalized in here59# In Kris' original function, this was manifested as a full investment constraint60# the normalization process produces much faster convergence,61# and then we penalize parameters outside the constraints in the next block62if(!is.null(constraints$max_sum) & constraints$max_sum != Inf ) {63max_sum=constraints$max_sum64if(sum(w)>max_sum) { w<-(max_sum/sum(w))*w } # normalize to max_sum65}6667if(!is.null(constraints$min_sum) & constraints$min_sum != -Inf ) {68min_sum=constraints$min_sum69if(sum(w)<min_sum) { w<-(min_sum/sum(w))*w } # normalize to min_sum70}7172} # end min_sum and max_sum normalization73} else {74# the user wants the optimization algorithm to figure it out75if(!is.null(constraints$max_sum) & constraints$max_sum != Inf ) {76max_sum=constraints$max_sum77if(sum(w)>max_sum) { out = out + penalty*(sum(w) - max_sum) } # penalize difference to max_sum78}79if(!is.null(constraints$min_sum) & constraints$min_sum != -Inf ) {80min_sum=constraints$min_sum81if(sum(w)<min_sum) { out = out + penalty*(min_sum - sum(w)) } # penalize difference to min_sum82}83}8485# penalize weights outside my constraints (can be caused by normalization)86if (!is.null(constraints$max)){87max = constraints$max88out = out + sum(w[which(w>max[1:N])]- constraints$max[which(w>max[1:N])])*penalty89}90if (!is.null(constraints$min)){91min = constraints$min92out = out + sum(constraints$min[which(w<min[1:N])] - w[which(w<min[1:N])])*penalty93}9495nargs <-list(...)96if(length(nargs)==0) nargs=NULL97if (length('...')==0 | is.null('...')) {98# rm('...')99nargs=NULL100}101102nargs<-set.portfolio.moments(R, constraints, momentargs=nargs)103104if(is.null(constraints$objectives)) {105warning("no objectives specified in constraints")106} else{107if(isTRUE(trace) | isTRUE(storage)) tmp_return<-list()108for (objective in constraints$objectives){109#check for clean bits to pass in110if(objective$enabled){111tmp_measure = NULL112multiplier = objective$multiplier113#if(is.null(objective$arguments) | !is.list(objective$arguments)) objective$arguments<-list()114switch(objective$name,115mean =,116median = {117fun = match.fun(objective$name)118nargs$x <- ( R %*% w ) #do the multivariate mean/median with Kroneker product119},120sd =,121StdDev = {122fun= match.fun(StdDev)123},124mVaR =,125VaR = {126fun= match.fun(VaR)127if(!inherits(objective,"risk_budget_objective") & is.null(objective$arguments$portfolio_method) & is.null(nargs$portfolio_method)) nargs$portfolio_method='single'128if(is.null(objective$arguments$invert)) objective$arguments$invert = FALSE129},130es =,131mES =,132CVaR =,133cVaR =,134ES = {135fun = match.fun(ES)136if(!inherits(objective,"risk_budget_objective") & is.null(objective$arguments$portfolio_method)& is.null(nargs$portfolio_method)) nargs$portfolio_method='single'137if(is.null(objective$arguments$invert)) objective$arguments$invert = FALSE138},139turnover = {140fun = match.fun(turnover) # turnover function included in objectiveFUN.R141},142{ # see 'S Programming p. 67 for this matching143fun<-try(match.fun(objective$name))144}145)146if(is.function(fun)){147.formals <- formals(fun)148onames <- names(.formals)149if(is.list(objective$arguments)){150#TODO FIXME only do this if R and weights are in the argument list of the fn151if(is.null(nargs$R) | !length(nargs$R)==length(R)) nargs$R <- R152153if(is.null(nargs$weights)) nargs$weights <- w154155pm <- pmatch(names(objective$arguments), onames, nomatch = 0L)156if (any(pm == 0L))157warning(paste("some arguments stored for",objective$name,"do not match"))158# this line overwrites the names of things stored in $arguments with names from formals.159# I'm not sure it's a good idea, so commenting for now, until we prove we need it160#names(objective$arguments[pm > 0L]) <- onames[pm]161.formals[pm] <- objective$arguments[pm > 0L]162#now add dots163if (length(nargs)) {164dargs<-nargs165pm <- pmatch(names(dargs), onames, nomatch = 0L)166names(dargs[pm > 0L]) <- onames[pm]167.formals[pm] <- dargs[pm > 0L]168}169.formals$... <- NULL170}171} # TODO do some funky return magic here on try-error172173tmp_measure = try((do.call(fun,.formals)) ,silent=TRUE)174175if(isTRUE(trace) | isTRUE(storage)) {176if(is.null(names(tmp_measure))) names(tmp_measure)<-objective$name177tmp_return[[objective$name]]<-tmp_measure178}179180if(inherits(tmp_measure,"try-error")) {181message(paste("objective name",objective$name,"generated an error or warning:",tmp_measure))182}183184# now set the new value of the objective output185if(inherits(objective,"return_objective")){186if (!is.null(objective$target) & is.numeric(objective$target)){ # we have a target187out = out + penalty*abs(objective$multiplier)*abs(tmp_measure-objective$target)188}189# target is null or doesn't exist, just maximize, or minimize violation of constraint190out = out + objective$multiplier*tmp_measure191} # end handling for return objectives192193if(inherits(objective,"portfolio_risk_objective")){194if (!is.null(objective$target) & is.numeric(objective$target)){ # we have a target195out = out + penalty*abs(objective$multiplier)*abs(tmp_measure-objective$target)196#should we also penalize risk too low for risk targets? or is a range another objective?197# # half penalty for risk lower than target198# if( prw < (.9*Riskupper) ){ out = out + .5*(penalty*( prw - Riskupper)) }199}200# target is null or doesn't exist, just maximize, or minimize violation of constraint201out = out + abs(objective$multiplier)*tmp_measure202} # univariate risk objectives203204if(inherits(objective,"turnover_objective")){205if (!is.null(objective$target) & is.numeric(objective$target)){ # we have a target206out = out + penalty*abs(objective$multiplier)*abs(tmp_measure-objective$target)207}208# target is null or doesn't exist, just maximize, or minimize violation of constraint209out = out + abs(objective$multiplier)*tmp_measure210} # univariate turnover objectives211212if(inherits(objective,"minmax_objective")){213if (!is.null(objective$min) & !is.null(objective$max)){ # we have a min and max214if(tmp_measure > objective$max){215out = out + penalty * objective$multiplier * (tmp_measure - objective$max)216}217if(tmp_measure < objective$min){218out = out + penalty * objective$multiplier * (objective$min - tmp_measure)219}220}221} # temporary minmax objective222223if(inherits(objective,"risk_budget_objective")){224# setup225226# out = out + penalty*sum( (percrisk-RBupper)*( percrisk > RBupper ),na.rm=TRUE ) + penalty*sum( (RBlower-percrisk)*( percrisk < RBlower ),na.rm=TRUE )227# add risk budget constraint228if(!is.null(objective$target) & is.numeric(objective$target)){229#in addition to a risk budget constraint, we have a univariate target230# the first element of the returned list is the univariate measure231# we'll use the univariate measure exactly like we would as a separate objective232out = out + penalty*abs(objective$multiplier)*abs(tmp_measure[[1]]-objective$target)233#should we also penalize risk too low for risk targets? or is a range another objective?234# # half penalty for risk lower than target235# if( prw < (.9*Riskupper) ){ out = out + .5*(penalty*( prw - Riskupper)) }236}237percrisk = tmp_measure[[3]] # third element is percent component contribution238RBupper = objective$max_prisk239RBlower = objective$min_prisk240if(!is.null(RBupper) | !is.null(RBlower)){241out = out + penalty * objective$multiplier * sum( (percrisk-RBupper)*( percrisk > RBupper ),na.rm=TRUE ) + penalty*sum( (RBlower-percrisk)*( percrisk < RBlower ),na.rm=TRUE )242}243# if(!is.null(objective$min_concentration)){244# if(isTRUE(objective$min_concentration)){245# max_conc<-max(tmp_measure[[2]]) #second element is the contribution in absolute terms246# # out=out + penalty * objective$multiplier * max_conc247# out = out + objective$multiplier * max_conc248# }249# }250# Combined min_con and min_dif to take advantage of a better concentration obj measure251if(!is.null(objective$min_difference) || !is.null(objective$min_concentration)){252if(isTRUE(objective$min_difference)){253# max_diff<-max(tmp_measure[[2]]-(sum(tmp_measure[[2]])/length(tmp_measure[[2]]))) #second element is the contribution in absolute terms254# Uses Herfindahl index to calculate concentration; added scaling perc diffs back to univariate numbers255max_diff <- sqrt(sum(tmp_measure[[3]]^2))/100 #third element is the contribution in percentage terms256# out = out + penalty * objective$multiplier * max_diff257out = out + penalty*objective$multiplier * max_diff258}259}260} # end handling of risk_budget objective261262} # end enabled check263} # end loop over objectives264} # end objectives processing265266if(isTRUE(verbose)) {267print('weights: ')268print(paste(w,' '))269print(paste("output of objective function",out))270print(unlist(tmp_return))271}272273if(is.na(out) | is.nan(out) | is.null(out)){274#this should never happen275warning('NA or NaN produced in objective function for weights ',w)276out<-penalty277}278279#return280if (isTRUE(storage)){281#add the new objective results282store_output[[length(store_output)+1]]<-list(out=as.numeric(out),weights=w,objective_measures=tmp_return)283# do the assign here284assign('.objectivestorage', store_output, envir=.storage)285}286if(!isTRUE(trace)){287return(out)288} else {289return(list(out=as.numeric(out),weights=w,objective_measures=tmp_return))290}291}292293#' calculate a numeric return value for a portfolio based on a set of constraints and objectives294#'295#' Function to calculate a numeric return value for a portfolio based on a set of constraints and objectives.296#' We'll try to make as few assumptions as possible and only run objectives that are enabled by the user.297#'298#' If the user has passed in either min_sum or max_sum constraints for the portfolio, or both,299#' and are using a numerical optimization method like DEoptim, and normalize=TRUE,300#' we'll normalize the weights passed in to whichever boundary condition has been violated.301#' If using random portfolios, all the portfolios generated will meet the constraints by construction.302#' NOTE: this means that the weights produced by a numeric optimization algorithm like DEoptim, pso, or GenSA303#' might violate constraints, and will need to be renormalized after optimizing.304#' We apply the same normalization in \code{\link{optimize.portfolio}} so that the weights you see have been305#' normalized to min_sum if the generated portfolio is smaller than min_sum or max_sum if the306#' generated portfolio is larger than max_sum.307#' This normalization increases the speed of optimization and convergence by several orders of magnitude in many cases.308#'309#' You may find that for some portfolios, normalization is not desirable, if the algorithm310#' cannot find a direction in which to move to head towards an optimal portfolio. In these cases,311#' it may be best to set normalize=FALSE, and penalize the portfolios if the sum of the weighting312#' vector lies outside the min_sum and/or max_sum.313#'314#' Whether or not we normalize the weights using min_sum and max_sum, and are using a numerical optimization315#' engine like DEoptim, we will penalize portfolios that violate weight constraints in much the same way316#' we penalize other constraints. If a min_sum/max_sum normalization has not occurred, convergence317#' can take a very long time. We currently do not allow for a non-normalized full investment constraint.318#' Future version of this function could include this additional constraint penalty.319#'320#' When you are optimizing a return objective, you must specify a negative multiplier321#' for the return objective so that the function will maximize return. If you specify a target return,322#' any return that deviates from your target will be penalized. If you do not specify a target return,323#' you may need to specify a negative VTR (value to reach) , or the function will not converge.324#' Try the maximum expected return times the multiplier (e.g. -1 or -10).325#' Adding a return objective defaults the multiplier to -1.326#'327#' Additional parameters for other solvers328#' (e.g. random portfolios or329#' \code{\link[DEoptim]{DEoptim.control}} or pso or GenSA330#' may be passed in via \dots331#'332#'333#' @param R an xts, vector, matrix, data frame, timeSeries or zoo object of asset returns.334#' @param w a vector of weights to test.335#' @param portfolio an object of class \code{portfolio} specifying the constraints and objectives for the optimization, see \code{\link{portfolio}}.336#' @param \dots any other passthru parameters.337#' @param trace TRUE/FALSE whether to include debugging and additional detail in the output list. The default is FALSE. Several charting functions require that \code{trace=TRUE}.338#' @param normalize TRUE/FALSE whether to normalize results to min/max sum (TRUE), or let the optimizer penalize portfolios that do not conform (FALSE)339#' @param storage TRUE/FALSE default TRUE for DEoptim with trace, otherwise FALSE. not typically user-called.340#' @param constraints a v1_constraint object for backwards compatibility with \code{constrained_objective_v1}.341#' @param env environment of moments calculated in \code{optimize.portfolio}342#' @seealso \code{\link{constraint}}, \code{\link{objective}}, \code{\link[DEoptim]{DEoptim.control}}343#' @author Kris Boudt, Peter Carl, Brian G. Peterson, Ross Bennett344#' @aliases constrained_objective constrained_objective_v1 constrained_objective_v2345#' @rdname constrained_objective346#' @export constrained_objective347#' @export constrained_objective_v2348constrained_objective <- constrained_objective_v2 <- function(w, R, portfolio, ..., trace=FALSE, normalize=TRUE, storage=FALSE, env=NULL)349{350if (ncol(R) > length(w)) {351R <- R[ ,1:length(w)]352}353if(!hasArg(penalty)) penalty <- 1e4354N <- length(w)355T <- nrow(R)356if(hasArg(optimize_method))357optimize_method <- match.call(expand.dots=TRUE)$optimize_method else optimize_method <- ''358if(hasArg(verbose))359verbose <- match.call(expand.dots=TRUE)$verbose360else verbose <- FALSE361362# initial weights363init_weights <- w364365# get the constraints from the portfolio object366constraints <- get_constraints(portfolio)367368# check for valid portfolio369if (!is.portfolio(portfolio)) {370stop("portfolio object passed in is not of class portfolio")371}372373# check that the assets and the weighting vector have the same length374if (N != length(portfolio$assets)){375warning("length of portfolio asset list and weights vector do not match, results may be bogus")376}377378out <- 0379380# do the get here381store_output <- try(get('.objectivestorage',envir=.storage), silent=TRUE)382if(inherits(store_output,"try-error")) {383storage <- FALSE384# warning("could not get .objectivestorage")385} else {386storage <- TRUE387}388389# use fn_map to normalize the weights390if(isTRUE(normalize)){391w <- fn_map(weights=w, portfolio=portfolio)$weights392# end fn_map transformation393} else {394# the user wants the optimization algorithm to figure it out395if(!is.null(constraints$max_sum) & constraints$max_sum != Inf ) {396max_sum <- constraints$max_sum397if(sum(w) > max_sum) { out <- out + penalty * (sum(w) - max_sum) } # penalize difference to max_sum398}399if(!is.null(constraints$min_sum) & constraints$min_sum != -Inf ) {400min_sum <- constraints$min_sum401if(sum(w) < min_sum) { out <- out + penalty * (min_sum - sum(w)) } # penalize difference to min_sum402}403}404405# penalize weights outside min and max box constraints (can be caused by normalization)406if (!is.null(constraints$max)){407max <- constraints$max408# Only go to penalty term if any of the weights violate max409if(any(w > max)){410out <- out + sum(w[which(w > max[1:N])] - constraints$max[which(w > max[1:N])]) * penalty411}412}413if (!is.null(constraints$min)){414min <- constraints$min415# Only go to penalty term if any of the weights violate min416if(any(w < min)){417out <- out + sum(constraints$min[which(w < min[1:N])] - w[which(w < min[1:N])]) * penalty418}419}420421# penalize weights that violate group constraints422if(!is.null(constraints$groups) & !is.null(constraints$cLO) & !is.null(constraints$cUP)){423groups <- constraints$groups424cLO <- constraints$cLO425cUP <- constraints$cUP426# Only go to penalty term if group constraint is violated427if(any(group_fail(w, groups, cLO, cUP))){428ngroups <- length(groups)429for(i in 1:ngroups){430tmp_w <- w[groups[[i]]]431# penalize for weights that are below cLO432if(sum(tmp_w) < cLO[i]){433out <- out + penalty * (cLO[i] - sum(tmp_w))434}435if(sum(tmp_w) > cUP[i]){436out <- out + penalty * (sum(tmp_w) - cUP[i])437}438}439}440} # End group constraint penalty441442# penalize weights that violate max_pos constraints443if(!is.null(constraints$max_pos)){444max_pos <- constraints$max_pos445tolerance <- .Machine$double.eps^0.5446mult <- 1447# sum(abs(w) > tolerance) is the number of non-zero assets448nzassets <- sum(abs(w) > tolerance)449if(nzassets > max_pos){450# Do we need a small multiplier term here since (nzassets - max_pos)451# will be an integer and much larger than the weight penalty terms452out <- out + penalty * mult * (nzassets - max_pos)453}454} # End position_limit constraint penalty455456# penalize weights that violate diversification constraint457if(!is.null(constraints$div_target)){458div_target <- constraints$div_target459div <- diversification(w)460mult <- 1461# only penalize if not within +/- 5% of target462if((div < div_target * 0.95) | (div > div_target * 1.05)){463out <- out + penalty * mult * abs(div - div_target)464}465} # End diversification constraint penalty466467# penalize weights that violate turnover constraint468if(!is.null(constraints$turnover_target)){469turnover_target <- constraints$turnover_target470to <- turnover(w)471mult <- 1472# only penalize if not within +/- 5% of target473if((to < turnover_target * 0.95) | (to > turnover_target * 1.05)){474# print("transform or penalize to meet turnover target")475out = out + penalty * mult * abs(to - turnover_target)476}477} # End turnover constraint penalty478479# penalize weights that violate return target constraint480if(!is.null(constraints$return_target)){481return_target <- constraints$return_target482mean_return <- port.mean(weights=w, mu=env$mu)483mult <- 1484out = out + penalty * mult * abs(mean_return - return_target)485} # End return constraint penalty486487# penalize weights that violate factor exposure constraints488if(!is.null(constraints$B)){489t.B <- t(constraints$B)490lower <- constraints$lower491upper <- constraints$upper492mult <- 1493for(i in 1:nrow(t.B)){494tmpexp <- as.numeric(t(w) %*% t.B[i, ])495if(tmpexp < lower[i]){496out <- out + penalty * mult * (lower[i] - tmpexp)497}498if(tmpexp > upper[i]){499out <- out + penalty * mult * (tmpexp - upper[i])500}501}502} # End factor exposure constraint penalty503504# Add penalty for transaction costs505if(!is.null(constraints$ptc)){506# calculate total transaction cost using portfolio$assets as initial set of weights507tc <- sum(abs(w - portfolio$assets) * constraints$ptc)508# for now use a multiplier of 1, may need to adjust this later509mult <- 1510out <- out + mult * tc511} # End transaction cost penalty512513# Add penalty for leverage exposure514# This could potentially be added to random portfolios515if(!is.null(constraints$leverage)){516if((sum(abs(w)) > constraints$leverage)){517# only penalize if leverage is exceeded518mult <- 1/100519out <- out + penalty * mult * abs(sum(abs(w)) - constraints$leverage)520}521} # End leverage exposure penalty522523# The "..." are passed in from optimize.portfolio and contain the output of524# momentFUN. The default is momentFUN=set.portfolio.moments and returns525# moments$mu, moments$sigma, moments$m3, moments$m4, etc. depending on the526# the functions corresponding to portfolio$objective$name. Would it be better527# to make this a formal argument for constrained_objective? This means that528# we completely avoid evaluating the set.portfolio.moments function. Can we529# trust that all the moments are correctly set in optimize.portfolio through530# momentFUN?531532# Add R and w to the environment with the moments533# env$R <- R534# env$weights <- w535536if(!is.null(env)){537nargs <- env538} else {539# print("calculating moments")540# calculating the moments541# nargs are used as the arguments for functions corresponding to542# objective$name called in the objective loop later543momentargs <- eval(substitute(alist(...)))544.formals <- formals(set.portfolio.moments)545.formals <- modify.args(formals=.formals, arglist=alist(momentargs=momentargs), dots=TRUE)546.formals <- modify.args(formals=.formals, arglist=NULL, R=R, dots=TRUE)547.formals <- modify.args(formals=.formals, arglist=NULL, portfolio=portfolio, dots=TRUE)548.formals$... <- NULL549# print(.formals)550nargs <- do.call(set.portfolio.moments, .formals)551}552553# We should avoid modifying nargs in the loop below.554# If we modify nargs with something like nargs$x, nargs is copied and this555# should be avoided because nargs could be large because it contains the moments.556tmp_args <- list()557558# JMU: Add all the variables in 'env' to tmp_args as names/symbols559# tmp_args[ls(env)] <- lapply(ls(env), as.name)560561if(is.null(portfolio$objectives)) {562warning("no objectives specified in portfolio")563} else{564if(isTRUE(trace) | isTRUE(storage)) tmp_return <- list()565for (objective in portfolio$objectives){566#check for clean bits to pass in567if(objective$enabled){568tmp_measure <- NULL569multiplier <- objective$multiplier570#if(is.null(objective$arguments) | !is.list(objective$arguments)) objective$arguments<-list()571switch(objective$name,572mean =,573median = {574fun = match.fun(port.mean)575# would it be better to do crossprod(w, moments$mu)?576# tmp_args$x <- ( R %*% w ) #do the multivariate mean/median with Kroneker product577},578median = {579fun = match.fun(objective$name)580tmp_args$x <- ( R %*% w ) #do the multivariate mean/median with Kroneker product581},582sd =,583var =,584StdDev = {585fun = match.fun(StdDev)586},587mVaR =,588VaR = {589fun = match.fun(VaR)590if(!inherits(objective,"risk_budget_objective") & is.null(objective$arguments$portfolio_method) & is.null(nargs$portfolio_method)) tmp_args$portfolio_method='single'591if(is.null(objective$arguments$invert)) tmp_args$invert = FALSE592},593es =,594mES =,595CVaR =,596cVaR =,597ETL=,598mETL=,599ES = {600fun = match.fun(ES)601if(!inherits(objective,"risk_budget_objective") & is.null(objective$arguments$portfolio_method) & is.null(nargs$portfolio_method)) tmp_args$portfolio_method='single'602if(is.null(objective$arguments$invert)) tmp_args$invert = FALSE603},604CSM = {}, ## xinran605turnover = {606fun = match.fun(turnover) # turnover function included in objectiveFUN.R607},608{ # see 'S Programming p. 67 for this matching609fun <- try(match.fun(objective$name))610}611)612613if(is.function(fun)){614.formals <- formals(fun)615# Add the moments from the nargs object616# nargs contains the moments, these are being evaluated617.formals <- modify.args(formals=.formals, arglist=nargs, dots=TRUE)618# Add anything from tmp_args619.formals <- modify.args(formals=.formals, arglist=tmp_args, dots=TRUE)620# Now add the objective$arguments621.formals <- modify.args(formals=.formals, arglist=objective$arguments, dots=TRUE)622# Add R and weights if necessary623if("R" %in% names(.formals)) .formals <- modify.args(formals=.formals, arglist=NULL, R=R, dots=TRUE)624if("weights" %in% names(.formals)) .formals <- modify.args(formals=.formals, arglist=NULL, weights=w, dots=TRUE)625# .formals <- modify.args(formals=.formals, arglist=tmp_args, dots=TRUE)626.formals$... <- NULL627}628629# tmp_measure <- try(do.call(fun, .formals, envir=env), silent=TRUE)630tmp_measure <- try(do.call(fun, .formals), silent=TRUE)631632if(isTRUE(trace) | isTRUE(storage)) {633# Subsitute 'StdDev' if the objective name is 'var'634# if the user passes in var as an objective name, we are actually635# calculating StdDev, so we need to change the name here.636tmp_objname <- objective$name637if(tmp_objname == "var") tmp_objname <- "StdDev"638if(is.null(names(tmp_measure))) names(tmp_measure) <- tmp_objname639tmp_return[[tmp_objname]] <- tmp_measure640}641642if(inherits(tmp_measure, "try-error")) {643message(paste("objective name", objective$name, "generated an error or warning:", tmp_measure))644}645646# now set the new value of the objective output647if(inherits(objective, "return_objective")){648if (!is.null(objective$target) & is.numeric(objective$target)){ # we have a target649out <- out + penalty*abs(objective$multiplier)*abs(tmp_measure - objective$target)650}651# target is null or doesn't exist, just maximize, or minimize violation of constraint652out <- out + objective$multiplier*tmp_measure653} # end handling for return objectives654655if(inherits(objective, "portfolio_risk_objective")){656if (!is.null(objective$target) & is.numeric(objective$target)){ # we have a target657out <- out + penalty*abs(objective$multiplier)*abs(tmp_measure - objective$target)658#should we also penalize risk too low for risk targets? or is a range another objective?659# # half penalty for risk lower than target660# if( prw < (.9*Riskupper) ){ out = out + .5*(penalty*( prw - Riskupper)) }661}662# target is null or doesn't exist, just maximize, or minimize violation of constraint663out <- out + abs(objective$multiplier)*tmp_measure664} # univariate risk objectives665666if(inherits(objective, "turnover_objective")){667if (!is.null(objective$target) & is.numeric(objective$target)){ # we have a target668out <- out + penalty*abs(objective$multiplier)*abs(tmp_measure - objective$target)669}670# target is null or doesn't exist, just maximize, or minimize violation of constraint671out <- out + abs(objective$multiplier)*tmp_measure672} # univariate turnover objectives673674if(inherits(objective, "minmax_objective")){675if (!is.null(objective$min) & !is.null(objective$max)){ # we have a min and max676if(tmp_measure > objective$max){677out <- out + penalty * objective$multiplier * (tmp_measure - objective$max)678}679if(tmp_measure < objective$min){680out <- out + penalty * objective$multiplier * (objective$min - tmp_measure)681}682}683} # temporary minmax objective684685if(inherits(objective, "risk_budget_objective")){686# setup687688# out = out + penalty*sum( (percrisk-RBupper)*( percrisk > RBupper ),na.rm=TRUE ) + penalty*sum( (RBlower-percrisk)*( percrisk < RBlower ),na.rm=TRUE )689# add risk budget constraint690if(!is.null(objective$target) & is.numeric(objective$target)){691#in addition to a risk budget constraint, we have a univariate target692# the first element of the returned list is the univariate measure693# we'll use the univariate measure exactly like we would as a separate objective694out = out + penalty*abs(objective$multiplier)*abs(tmp_measure[[1]]-objective$target)695#should we also penalize risk too low for risk targets? or is a range another objective?696# # half penalty for risk lower than target697# if( prw < (.9*Riskupper) ){ out = out + .5*(penalty*( prw - Riskupper)) }698}699percrisk = tmp_measure[[3]] # third element is percent component contribution700RBupper = objective$max_prisk701RBlower = objective$min_prisk702if(!is.null(RBupper) | !is.null(RBlower)){703out = out + penalty * objective$multiplier * sum( (percrisk-RBupper)*( percrisk > RBupper ),na.rm=TRUE ) + penalty*sum( (RBlower-percrisk)*( percrisk < RBlower ),na.rm=TRUE )704}705# if(!is.null(objective$min_concentration)){706# if(isTRUE(objective$min_concentration)){707# max_conc<-max(tmp_measure[[2]]) #second element is the contribution in absolute terms708# # out=out + penalty * objective$multiplier * max_conc709# out = out + objective$multiplier * max_conc710# }711# }712# Combined min_con and min_dif to take advantage of a better concentration obj measure713if(!is.null(objective$min_difference) || !is.null(objective$min_concentration)){714if(isTRUE(objective$min_difference)){715# max_diff<-max(tmp_measure[[2]]-(sum(tmp_measure[[2]])/length(tmp_measure[[2]]))) #second element is the contribution in absolute terms716# Uses Herfindahl index to calculate concentration; added scaling perc diffs back to univariate numbers717max_diff <- sqrt(sum(tmp_measure[[3]]^2))/100 #third element is the contribution in percentage terms718# out = out + penalty * objective$multiplier * max_diff719out = out + penalty*objective$multiplier * max_diff720}721if(isTRUE(objective$min_concentration)){722# use HHI to calculate concentration723# actual HHI724act_hhi <- sum(tmp_measure[[3]]^2)/100725# minimum possible HHI726min_hhi <- sum(rep(1/length(tmp_measure[[3]]), length(tmp_measure[[3]]))^2)/100727out <- out + penalty * objective$multiplier * abs(act_hhi - min_hhi)728}729}730} # end handling of risk_budget objective731732if(inherits(objective, "weight_concentration_objective")){733# If the user does not pass in conc_groups, the output of HHI will be a scalar734if((length(objective$conc_aversion) == 1) & is.null(objective$conc_groups)){735# treat conc_aversion as a multiplier736out <- out + penalty * objective$conc_aversion * tmp_measure737}738# If the user passes in conc_groups, the output of HHI will be a list739# The second element of the list will be the group HHI740if(length(objective$conc_aversion > 1) & !is.null(objective$conc_groups)){741if(length(objective$conc_aversion) == length(tmp_measure[[2]])){742# treat the conc_aversion vector as a multiplier per group hhi743out <- out + penalty * sum(objective$conc_aversion * tmp_measure[[2]])744}745}746} # weight concentration objective747748} # end enabled check749} # end loop over objectives750} # end objectives processing751752if(isTRUE(verbose)) {753print('weights: ')754print(paste(w,' '))755print(paste("output of objective function", out))756print(unlist(tmp_return))757}758759if(is.na(out) | is.nan(out) | is.null(out)){760#this should never happen761warning('NA or NaN produced in objective function for weights ',w)762out <- penalty763}764765#return766if (isTRUE(storage)){767#add the new objective results768store_output[[length(store_output)+1]] <- list(out=as.numeric(out), weights=w, init_weights=init_weights, objective_measures=tmp_return)769# do the assign here770assign('.objectivestorage', store_output, envir=.storage)771}772if(!isTRUE(trace)){773return(out)774} else {775return(list(out=as.numeric(out), weights=w, objective_measures=tmp_return))776}777}778779780