optimize.portfolio_v1 <- function(
R,
constraints,
optimize_method=c("DEoptim","random","ROI","ROI_old","pso","GenSA"),
search_size=20000,
trace=FALSE, ...,
rp=NULL,
momentFUN='set.portfolio.moments_v1'
)
{
optimize_method=optimize_method[1]
tmptrace=NULL
start_t<-Sys.time()
call <- match.call()
if (is.null(constraints) | !is.constraint(constraints)){
stop("you must pass in an object of class constraints to control the optimization")
}
R <- checkData(R)
N = length(constraints$assets)
if (ncol(R)>N) {
R=R[,names(constraints$assets)]
}
T = nrow(R)
out=list()
weights=NULL
dotargs <-list(...)
if(!is.function(momentFUN)){
momentFUN<-match.fun(momentFUN)
}
.mformals <- dotargs
.mformals$R <- R
.mformals$constraints <- constraints
mout <- try((do.call(momentFUN,.mformals)) ,silent=TRUE)
if(inherits(mout,"try-error")) {
message(paste("portfolio moment function failed with message",mout))
} else {
dotargs <- c(dotargs,mout)
}
normalize_weights <- function(weights){
if(!is.null(constraints$min_sum) | !is.null(constraints$max_sum)){
if(!is.null(constraints$max_sum) & constraints$max_sum != Inf ) {
max_sum=constraints$max_sum
if(sum(weights)>max_sum) { weights<-(max_sum/sum(weights))*weights }
}
if(!is.null(constraints$min_sum) & constraints$min_sum != -Inf ) {
min_sum=constraints$min_sum
if(sum(weights)<min_sum) { weights<-(min_sum/sum(weights))*weights }
}
}
return(weights)
}
if(optimize_method=="DEoptim"){
stopifnot("package:DEoptim" %in% search() || requireNamespace("DEoptim",quietly = TRUE) )
if(hasArg(itermax)) itermax=match.call(expand.dots=TRUE)$itermax else itermax=N*50
NP = round(search_size/itermax)
if(NP<(N*10)) NP <- N*10
if(NP>2000) NP=2000
if(!hasArg(itermax)) {
itermax<-round(search_size/NP)
if(itermax<50) itermax=50
}
if(hasArg(parallel)) parallel=match.call(expand.dots=TRUE)$parallel else parallel=TRUE
if(!isTRUE(parallel) && 'package:foreach' %in% search()){
foreach::registerDoSEQ()
}
DEcformals <- formals(DEoptim::DEoptim.control)
DEcargs <- names(DEcformals)
if( is.list(dotargs) ){
pm <- pmatch(names(dotargs), DEcargs, nomatch = 0L)
names(dotargs[pm > 0L]) <- DEcargs[pm]
DEcformals$NP <- NP
DEcformals$itermax <- itermax
DEcformals[pm] <- dotargs[pm > 0L]
if(!hasArg(strategy)) {
strategy=6
DEcformals$strategy=strategy
}
if(!hasArg(reltol)) {
reltol=.000001
DEcformals$reltol=reltol
}
if(!hasArg(steptol)) {
steptol=round(N*1.5)
DEcformals$steptol=steptol
}
if(!hasArg(c)) {
tmp.c=.4
DEcformals$c=tmp.c
}
if(!hasArg(storepopfrom)) {
storepopfrom=1
DEcformals$storepopfrom=storepopfrom
}
if(isTRUE(parallel) && 'package:foreach' %in% search()){
if(!hasArg(parallelType)) {
parallelType=2
DEcformals$parallelType=parallelType
}
if(!hasArg(packages)) {
packages <- names(sessionInfo()$otherPkgs)
DEcformals$packages <- packages
}
}
}
if(isTRUE(trace)) {
tmptrace=trace
assign('.objectivestorage', list(), as.environment(.storage))
trace=FALSE
}
upper = constraints$max
lower = constraints$min
if(hasArg(rpseed)){
seed <- match.call(expand.dots=TRUE)$rpseed
DEcformals$initialpop <- seed
rpseed <- FALSE
} else {
rpseed <- TRUE
}
if(hasArg(rpseed) & isTRUE(rpseed)) {
if(hasArg(eps)) eps=match.call(expand.dots=TRUE)$eps else eps = 0.01
rpconstraint<-constraint(assets=length(lower), min_sum=constraints$min_sum-eps, max_sum=constraints$max_sum+eps,
min=lower, max=upper, weight_seq=generatesequence())
rp <- random_portfolios_v1(rpconstraints=rpconstraint,permutations=NP)
DEcformals$initialpop=rp
}
controlDE <- do.call(DEoptim::DEoptim.control,DEcformals)
minw = try(DEoptim::DEoptim( constrained_objective_v1 , lower = lower[1:N] , upper = upper[1:N] , control = controlDE, R=R, constraints=constraints, nargs = dotargs , ...=...))
if(inherits(minw,"try-error")) { minw=NULL }
if(is.null(minw)){
message(paste("Optimizer was unable to find a solution for target"))
return (paste("Optimizer was unable to find a solution for target"))
}
if(isTRUE(tmptrace)) trace <- tmptrace
weights = as.vector( minw$optim$bestmem)
weights <- normalize_weights(weights)
names(weights) = colnames(R)
out = list(weights=weights, objective_measures=constrained_objective_v1(w=weights,R=R,constraints,trace=TRUE)$objective_measures,out=minw$optim$bestval, call=call)
if (isTRUE(trace)){
out$DEoutput=minw
out$DEoptim_objective_results<-try(get('.objectivestorage',envir=.storage),silent=TRUE)
rm('.objectivestorage',envir=.storage)
}
}
if(optimize_method=="random"){
if(missing(rp) | is.null(rp)){
rp<-random_portfolios_v1(rpconstraints=constraints,permutations=search_size)
}
if (isTRUE(trace)) out$random_portfolios<-rp
if ("package:foreach" %in% search() & !hasArg(parallel)){
ii <- 1
rp_objective_results<-foreach::foreach(ii=1:nrow(rp), .errorhandling='pass') %dopar% constrained_objective_v1(w=rp[ii,],R,constraints,trace=trace,...=dotargs)
} else {
rp_objective_results<-apply(rp, 1, constrained_objective_v1, R=R, constraints=constraints, trace=trace, ...=dotargs)
}
if(isTRUE(trace)) out$random_portfolio_objective_results<-rp_objective_results
search<-vector(length=length(rp_objective_results))
for (i in 1:length(search)) {
if (isTRUE(trace)) {
search[i]<-ifelse(try(rp_objective_results[[i]]$out),rp_objective_results[[i]]$out,1e6)
} else {
search[i]<-as.numeric(rp_objective_results[[i]])
}
}
if (isTRUE(trace)) {
min_objective_weights<- try(normalize_weights(rp_objective_results[[which.min(search)]]$weights))
} else {
min_objective_weights<- try(normalize_weights(rp[which.min(search),]))
}
out$weights<-min_objective_weights
out$objective_measures<-try(constrained_objective_v1(w=min_objective_weights,R=R,constraints,trace=TRUE)$objective_measures)
out$call<-call
}
if(optimize_method == "ROI_old"){
print("ROI_old is going to be depricated.")
roi.result <- ROI::ROI_solve(x=constraints$constrainted_objective, constraints$solver)
weights <- roi.result$solution
names(weights) <- colnames(R)
out$weights <- weights
out$objective_measures <- roi.result$objval
out$call <- call
}
if(optimize_method == "ROI"){
bnds <- list(lower = list(ind = seq.int(1L, N), val = as.numeric(constraints$min)),
upper = list(ind = seq.int(1L, N), val = as.numeric(constraints$max)))
moments <- list(mean=rep(0, N))
alpha <- 0.05
target <- NA
for(objective in constraints$objectives){
if(objective$enabled){
if(!any(c(objective$name == "mean", objective$name == "var", objective$name == "CVaR")))
stop("ROI only solves mean, var, or sample CVaR type business objectives, choose a different optimize_method.")
if(objective$name == "mean"){
moments[[objective$name]] <- try(as.vector(apply(R, 2, "mean", na.rm=TRUE)), silent=TRUE)
} else {
moments[[objective$name]] <- try(eval(as.symbol(objective$name))(R), silent=TRUE)
}
target <- ifelse(!is.null(objective$target),objective$target, target)
alpha <- ifelse(!is.null(objective$alpha), objective$alpha, alpha)
lambda <- ifelse(!is.null(objective$risk_aversion), objective$risk_aversion, 1)
}
}
plugin <- ifelse(any(names(moments)=="var"), "quadprog", "glpk")
if(plugin == "quadprog") ROI_objective <- ROI::Q_objective(Q=2*lambda*moments$var, L=-moments$mean)
if(plugin == "glpk") ROI_objective <- ROI::L_objective(L=-moments$mean)
Amat <- rbind(rep(1, N), rep(1, N))
dir.vec <- c(">=","<=")
rhs.vec <- c(constraints$min_sum, constraints$max_sum)
if(!is.na(target)) {
Amat <- rbind(Amat, moments$mean)
dir.vec <- c(dir.vec, "==")
rhs.vec <- c(rhs.vec, target)
}
if(try(!is.null(constraints$groups), silent=TRUE)){
if(sum(constraints$groups) != N)
stop("Number of assets in each group needs to sum to number of total assets.")
n.groups <- length(constraints$groups)
if(!all(c(length(constraints$cLO),length(constraints$cLO)) == n.groups) )
stop("Number of group constraints exceeds number of groups.")
Amat.group <- matrix(0, nrow=n.groups, ncol=N)
k <- 1
l <- 0
for(i in 1:n.groups){
j <- constraints$groups[i]
Amat.group[i, k:(l+j)] <- 1
k <- l + j + 1
l <- k - 1
}
if(is.null(constraints$cLO)) cLO <- rep(-Inf, n.groups)
if(is.null(constraints$cUP)) cUP <- rep(Inf, n.groups)
Amat <- rbind(Amat, Amat.group, -Amat.group)
dir.vec <- c(dir.vec, rep(">=", (n.groups + n.groups)))
rhs.vec <- c(rhs.vec, constraints$cLO, -constraints$cUP)
}
if(any(names(moments)=="CVaR")) {
Rmin <- ifelse(is.na(target), 0, target)
ROI_objective <- ROI::L_objective(c(rep(0,N), rep(1/(alpha*T),T), 1))
Amat <- cbind(rbind(1, 1, moments$mean, coredata(R)), rbind(0, 0, 0, cbind(diag(T), 1)))
dir.vec <- c(">=","<=",">=",rep(">=",T))
rhs.vec <- c(constraints$min_sum, constraints$max_sum, Rmin ,rep(0, T))
if(try(!is.null(constraints$groups), silent=TRUE)){
zeros <- matrix(0, nrow=n.groups, ncol=(T+1))
Amat <- rbind(Amat, cbind(Amat.group, zeros), cbind(-Amat.group, zeros))
dir.vec <- c(dir.vec, rep(">=", (n.groups + n.groups)))
rhs.vec <- c(rhs.vec, constraints$cLO, -constraints$cUP)
}
}
opt.prob <- ROI::OP(objective=ROI_objective,
constraints=ROI::L_constraint(L=Amat, dir=dir.vec, rhs=rhs.vec),
bounds=bnds)
roi.result <- ROI::ROI_solve(x=opt.prob, solver=plugin)
weights <- roi.result$solution[1:N]
names(weights) <- colnames(R)
out$weights <- weights
out$out <- roi.result$objval
out$call <- call
}
if(optimize_method=="pso"){
stopifnot("package:pso" %in% search() || requireNamespace("pso",quietly = TRUE) )
if(hasArg(maxit)) maxit=match.call(expand.dots=TRUE)$maxit else maxit=N*50
controlPSO <- list(trace=FALSE, fnscale=1, maxit=1000, maxf=Inf, abstol=-Inf, reltol=0)
PSOcargs <- names(controlPSO)
if( is.list(dotargs) ){
pm <- pmatch(names(dotargs), PSOcargs, nomatch = 0L)
names(dotargs[pm > 0L]) <- PSOcargs[pm]
controlPSO$maxit <- maxit
controlPSO[pm] <- dotargs[pm > 0L]
if(!hasArg(reltol)) controlPSO$reltol <- .0001
if(!hasArg(s)) {
s <- N*10
controlPSO$s<-s
}
if(!hasArg(maxit.stagnate)) {
maxit.stagnate <- controlPSO$s
controlPSO$maxit.stagnate <- maxit.stagnate
}
if(hasArg(trace) && try(trace==TRUE,silent=TRUE)) controlPSO$trace <- TRUE
if(hasArg(trace) && isTRUE(trace)) {
controlPSO$trace <- TRUE
controlPSO$trace.stats=TRUE
}
}
upper <- constraints$max
lower <- constraints$min
minw = try(pso::psoptim( par = rep(NA, N), fn = constrained_objective_v1 , R=R, constraints=constraints,
lower = lower[1:N] , upper = upper[1:N] , control = controlPSO))
if(inherits(minw,"try-error")) { minw=NULL }
if(is.null(minw)){
message(paste("Optimizer was unable to find a solution for target"))
return (paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector( minw$par)
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
out = list(weights=weights,
objective_measures=constrained_objective_v1(w=weights,R=R,constraints,trace=TRUE)$objective_measures,
out=minw$value,
call=call)
if (isTRUE(trace)){
out$PSOoutput=minw
}
}
if(optimize_method=="GenSA"){
stopifnot("package:GenSA" %in% search() || requireNamespace("GenSA",quietly = TRUE) )
if(hasArg(maxit)) maxit=match.call(expand.dots=TRUE)$maxit else maxit=N*50
controlGenSA <- list(maxit = 5000, threshold.stop = NULL, temperature = 5230,
visiting.param = 2.62, acceptance.param = -5, max.time = NULL,
nb.stop.improvement = 1e+06, smooth = TRUE, max.call = 1e+07,
verbose = FALSE)
GenSAcargs <- names(controlGenSA)
if( is.list(dotargs) ){
pm <- pmatch(names(dotargs), GenSAcargs, nomatch = 0L)
names(dotargs[pm > 0L]) <- GenSAcargs[pm]
controlGenSA$maxit <- maxit
controlGenSA[pm] <- dotargs[pm > 0L]
if(hasArg(trace) && try(trace==TRUE,silent=TRUE)) controlGenSA$verbose <- TRUE
}
upper <- constraints$max
lower <- constraints$min
if(!is.null(rp)) par = rp[,1] else par = rep(1/N, N)
minw = try(GenSA::GenSA( par=par, lower = lower[1:N] , upper = upper[1:N], control = controlGenSA,
fn = constrained_objective_v1 , R=R, constraints=constraints))
if(inherits(minw,"try-error")) { minw=NULL }
if(is.null(minw)){
message(paste("Optimizer was unable to find a solution for target"))
return (paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(minw$par)
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
out = list(weights=weights,
objective_measures=constrained_objective_v1(w=weights,R=R,constraints,trace=TRUE)$objective_measures,
out=minw$value,
call=call)
if (isTRUE(trace)){
out$GenSAoutput=minw
}
}
end_t<-Sys.time()
message(c("elapsed time:",end_t-start_t))
out$constraints<-constraints
out$data_summary<-list(first=first(R),last=last(R))
out$elapsed_time<-end_t-start_t
out$end_t<-as.character(Sys.time())
class(out)<-c(paste("optimize.portfolio",optimize_method,sep='.'),"optimize.portfolio")
return(out)
}
.onLoad <- function(lib, pkg) {
if(!exists('.storage'))
.storage <<- new.env()
}
optimize.portfolio <- optimize.portfolio_v2 <- function(
R,
portfolio=NULL,
constraints=NULL,
objectives=NULL,
optimize_method=c("DEoptim","random","ROI","pso","GenSA", "Rglpk", "osqp", "mco", "CVXR", "cvxr", ...),
search_size=20000,
trace=FALSE, ...,
rp=NULL,
momentFUN='set.portfolio.moments',
message=FALSE
)
{
if(inherits(portfolio, "portfolio.list")){
n.portf <- length(portfolio)
opt.list <- vector("list", n.portf)
for(i in 1:length(opt.list)){
if(message) cat("Starting optimization of portfolio ", i, "\n")
opt.list[[i]] <- optimize.portfolio(R=R,
portfolio=portfolio[[i]],
constraints=constraints,
objectives=objectives,
optimize_method=optimize_method,
search_size=search_size,
trace=trace,
...=...,
rp=rp,
momentFUN=momentFUN,
message=message)
}
out <- combine.optimizations(opt.list)
return(out)
}
if(inherits(portfolio, "regime.portfolios")){
regime.switching <- TRUE
regime <- portfolio$regime
if(index(last(R)) %in% index(regime)){
regime.idx <- as.numeric(regime[index(last(R))])[1]
portfolio <- portfolio$portfolio.list[[regime.idx]]
} else {
warning("Dates in regime and R do not match, defaulting to first portfolio")
regime.idx <- 1
portfolio <- portfolio$portfolio.list[[regime.idx]]
}
} else {
regime.switching <- FALSE
}
if(inherits(portfolio, "mult.portfolio.spec")){
R <- proxy.mult.portfolio(R=R, mult.portfolio=portfolio)
portfolio <- portfolio$top.portfolio
}
if(length(optimize_method) == 2) optimize_method <- optimize_method[2] else optimize_method <- optimize_method[1]
tmptrace <- NULL
start_t <- Sys.time()
call <- match.call()
if (!is.null(portfolio) & !is.portfolio(portfolio)){
stop("you must pass in an object of class 'portfolio' to control the optimization")
}
if(!is.null(constraints)){
if(inherits(constraints, "v1_constraint")){
if(is.null(portfolio)){
tmp_portf <- portfolio.spec(assets=constraints$assets)
}
message("constraint object passed in is a 'v1_constraint' object, updating to v2 specification")
portfolio <- update_constraint_v1tov2(portfolio=tmp_portf, v1_constraint=constraints)
}
if(!inherits(constraints, "v1_constraint")){
portfolio <- insert_constraints(portfolio=portfolio, constraints=constraints)
}
}
if(!is.null(objectives)){
portfolio <- insert_objectives(portfolio=portfolio, objectives=objectives)
}
R <- checkData(R)
N <- length(portfolio$assets)
if (ncol(R) > N) {
R <- R[,names(portfolio$assets)]
}
T <- nrow(R)
out <- list()
weights <- NULL
constraints <- get_constraints(portfolio)
moment_name = momentFUN
if(!is.function(momentFUN)){
momentFUN <- match.fun(momentFUN)
}
.formals <- formals(momentFUN)
.formals <- modify.args(formals=.formals, arglist=list(...), dots=TRUE)
if(optimize_method %in% c("ROI", "quadprog", "glpk", "symphony", "ipop")){
obj_names <- unlist(lapply(portfolio$objectives, function(x) x$name))
if(any(obj_names %in% c("CVaR", "ES", "ETL"))){
.formals <- modify.args(formals=.formals, arglist=list(ROI=TRUE), dots=TRUE)
}
}
if("R" %in% names(.formals)) .formals <- modify.args(formals=.formals, arglist=NULL, R=R, dots=FALSE)
if("portfolio" %in% names(.formals)) .formals <- modify.args(formals=.formals, arglist=NULL, portfolio=portfolio, dots=FALSE)
.formals$... <- NULL
mout <- try(do.call(momentFUN, .formals), silent=TRUE)
if(inherits(mout, "try-error")) {
message(paste("portfolio moment function failed with message", mout))
} else {
dotargs <- mout
}
normalize_weights <- function(weights){
if(!is.null(constraints$min_sum) | !is.null(constraints$max_sum)){
if(!is.null(constraints$max_sum) & constraints$max_sum != Inf & constraints$max_sum != 0) {
max_sum=constraints$max_sum
if(sum(weights)>max_sum) { weights<-(max_sum/sum(weights))*weights }
}
if(!is.null(constraints$min_sum) & constraints$min_sum != -Inf & constraints$min_sum != 0) {
min_sum=constraints$min_sum
if(sum(weights)<min_sum) { weights<-(min_sum/sum(weights))*weights }
}
}
return(weights)
}
if(optimize_method == "DEoptim"){
stopifnot("package:DEoptim" %in% search() || requireNamespace("DEoptim",quietly = TRUE))
if(hasArg(itermax)) itermax=match.call(expand.dots=TRUE)$itermax else itermax=N*50
NP <- round(search_size/itermax)
if(NP < (N * 10)) NP <- N * 10
if(NP > 2000) NP <- 2000
if(!hasArg(itermax)) {
itermax <- round(search_size / NP)
if(itermax < 50) itermax <- 50
}
if(hasArg(parallel)) parallel <- match.call(expand.dots=TRUE)$parallel else parallel <- TRUE
if(!isTRUE(parallel) && 'package:foreach' %in% search()){
foreach::registerDoSEQ()
}
DEcformals <- formals(DEoptim::DEoptim.control)
DEcargs <- names(DEcformals)
if( is.list(dotargs) ){
pm <- pmatch(names(dotargs), DEcargs, nomatch = 0L)
names(dotargs[pm > 0L]) <- DEcargs[pm]
DEcformals$NP <- NP
DEcformals$itermax <- itermax
DEcformals[pm] <- dotargs[pm > 0L]
if(!hasArg(strategy)) {
strategy=6
DEcformals$strategy=strategy
}
if(!hasArg(reltol)) {
reltol=0.000001
DEcformals$reltol=reltol
}
if(!hasArg(steptol)) {
steptol=round(N*1.5)
DEcformals$steptol=steptol
}
if(!hasArg(c)) {
tmp.c=0.4
DEcformals$c=tmp.c
}
if(!hasArg(storepopfrom)) {
storepopfrom=1
DEcformals$storepopfrom=storepopfrom
}
if(isTRUE(parallel) && 'package:foreach' %in% search()){
if(!hasArg(parallelType)) {
parallelType=2
DEcformals$parallelType=parallelType
}
if(!hasArg(packages)) {
packages <- names(sessionInfo()$otherPkgs)
DEcformals$packages <- packages
}
}
}
if(hasArg(traceDE)) traceDE=match.call(expand.dots=TRUE)$traceDE else traceDE=TRUE
DEcformals$trace <- traceDE
if(isTRUE(trace)) {
tmptrace <- trace
assign('.objectivestorage', list(), envir=as.environment(.storage))
trace=FALSE
}
upper <- constraints$max
lower <- constraints$min
if((constraints$max_sum - constraints$min_sum) < 0.02){
message("Leverage constraint min_sum and max_sum are restrictive,
consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01")
}
if(!is.null(rp)){
rp_len <- min(nrow(rp), NP)
seed <- rp[1:rp_len,]
DEcformals$initialpop <- seed
} else{
if(hasArg(rp_method)) rp_method=match.call(expand.dots=TRUE)$rp_method else rp_method="sample"
if(hasArg(fev)) fev=match.call(expand.dots=TRUE)$fev else fev=0:5
rp <- random_portfolios(portfolio=portfolio, permutations=(NP+1), rp_method=rp_method, eliminate=FALSE, fev=fev)
DEcformals$initialpop <- rp
}
controlDE <- do.call(DEoptim::DEoptim.control, DEcformals)
minw = try(DEoptim::DEoptim( constrained_objective, lower=lower[1:N], upper=upper[1:N], control=controlDE, R=R, portfolio=portfolio, env=dotargs, normalize=FALSE, fnMap=function(x) fn_map(x, portfolio=portfolio)$weights), silent=TRUE)
if(inherits(minw, "try-error")) {
message(minw)
minw=NULL
}
if(is.null(minw)){
message(paste("Optimizer was unable to find a solution for target"))
return (paste("Optimizer was unable to find a solution for target"))
}
if(isTRUE(tmptrace)) trace <- tmptrace
weights <- as.vector(minw$optim$bestmem)
print(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(w=weights, R=R, portfolio, trace=TRUE, normalize=FALSE, env=dotargs)$objective_measures
out <- list(weights=weights, objective_measures=obj_vals, opt_values=obj_vals, out=minw$optim$bestval, call=call)
if (isTRUE(trace)){
out$DEoutput <- minw
out$DEoptim_objective_results <- try(get('.objectivestorage',envir=.storage),silent=TRUE)
rm('.objectivestorage',envir=.storage)
}
}
if(optimize_method=="random"){
if((constraints$max_sum - constraints$min_sum) < 0.02){
message("Leverage constraint min_sum and max_sum are restrictive,
consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01")
}
if(missing(rp) | is.null(rp)){
if(hasArg(rp_method)) rp_method=match.call(expand.dots=TRUE)$rp_method else rp_method="sample"
if(hasArg(eliminate)) eliminate=match.call(expand.dots=TRUE)$eliminate else eliminate=TRUE
if(hasArg(fev)) fev=match.call(expand.dots=TRUE)$fev else fev=0:5
rp <- random_portfolios(portfolio=portfolio, permutations=search_size, rp_method=rp_method, eliminate=eliminate, fev=fev)
}
if (isTRUE(trace)) out$random_portfolios <- rp
if ("package:foreach" %in% search() & !hasArg(parallel)){
ii <- 1
rp_objective_results <- foreach::foreach(ii=1:nrow(rp), .errorhandling='pass') %dopar% constrained_objective(w=rp[ii,], R=R, portfolio=portfolio, trace=trace, env=dotargs, normalize=FALSE)
} else {
rp_objective_results <- apply(rp, 1, constrained_objective, R=R, portfolio=portfolio, trace=trace, normalize=FALSE, env=dotargs)
}
if(isTRUE(trace)) out$random_portfolio_objective_results <- rp_objective_results
search <- vector(length=length(rp_objective_results))
for (i in 1:length(search)) {
if (isTRUE(trace)) {
search[i] <- ifelse(try(rp_objective_results[[i]]$out), rp_objective_results[[i]]$out,1e6)
} else {
search[i] <- as.numeric(rp_objective_results[[i]])
}
}
if (isTRUE(trace)) {
min_objective_weights <- try(normalize_weights(rp_objective_results[[which.min(search)]]$weights))
} else {
min_objective_weights <- try(normalize_weights(rp[which.min(search),]))
}
out$weights <- min_objective_weights
obj_vals <- try(constrained_objective(w=min_objective_weights, R=R, portfolio=portfolio, trace=TRUE, normalize=FALSE, env=dotargs)$objective_measures)
out$objective_measures <- obj_vals
out$opt_values <- obj_vals
out$call <- call
}
roi_solvers <- c("ROI", "quadprog", "glpk", "symphony", "ipop")
if(optimize_method %in% roi_solvers){
if(hasArg(control)) control=match.call(expand.dots=TRUE)$control else control=NULL
moments <- list(mean=rep(0, N))
alpha <- 0.05
if(!is.null(constraints$return_target)){
target <- constraints$return_target
} else {
target <- NA
}
lambda <- 1
valid_objnames <- c("HHI", "mean", "var", "sd", "StdDev", "CVaR", "ES", "ETL")
for(objective in portfolio$objectives){
if(objective$enabled){
if(!(objective$name %in% valid_objnames)){
stop("ROI only solves mean, var/StdDev, HHI, or sample ETL/ES/CVaR type business objectives, choose a different optimize_method.")
}
arguments <- objective$arguments
if(!is.null(arguments$clean)) clean <- arguments$clean else clean <- "none"
if(!is.null(arguments[["p"]])) alpha <- arguments$p else alpha <- alpha
if(alpha > 0.5) alpha <- (1 - alpha)
if(clean != "none") moments$cleanR <- Return.clean(R=R, method=clean)
if(objective$name == "mean"){
if(!is.null(mout$mu)){
moments[["mean"]] <- as.vector(mout$mu)
} else {
moments[["mean"]] <- try(as.vector(apply(Return.clean(R=R, method=clean), 2, "mean", na.rm=TRUE)), silent=TRUE)
}
} else if(objective$name %in% c("StdDev", "sd", "var")){
if(!is.null(mout$sigma)){
moments[["var"]] <- mout$sigma
} else {
moments[["var"]] <- try(var(x=Return.clean(R=R, method=clean), na.rm=TRUE), silent=TRUE)
}
} else if(objective$name %in% c("CVaR", "ES", "ETL")){
moments[[objective$name]] <- ""
} else {
moments[[objective$name]] <- try(eval(as.symbol(objective$name))(Return.clean(R=R, method=clean)), silent=TRUE)
}
target <- ifelse(!is.null(objective$target), objective$target, target)
lambda <- ifelse(!is.null(objective$risk_aversion), objective$risk_aversion, lambda)
if(!is.null(objective$conc_aversion)) lambda_hhi <- objective$conc_aversion else lambda_hhi <- NULL
if(!is.null(objective$conc_groups)) conc_groups <- objective$conc_groups else conc_groups <- NULL
}
}
if("var" %in% names(moments)){
if(optimize_method == "ROI"){
solver <- "quadprog"
} else {
solver <- optimize_method
}
if(!is.null(constraints$turnover_target) | !is.null(constraints$ptc) | !is.null(constraints$leverage)){
if(!is.null(constraints$turnover_target) & !is.null(constraints$ptc)){
warning("Turnover and proportional transaction cost constraints detected, only running optimization for turnover constraint.")
constraints$ptc <- NULL
}
if(!is.null(constraints$turnover_target) & is.null(constraints$ptc)){
qp_result <- gmv_opt_toc(R=R, constraints=constraints, moments=moments, lambda=lambda, target=target, init_weights=portfolio$assets, solver=solver, control=control)
weights <- qp_result$weights
obj_vals <- qp_result$obj_vals
out <- list(weights=weights, objective_measures=obj_vals, opt_values=obj_vals, out=qp_result$out, call=call)
}
if(!is.null(constraints$ptc) & is.null(constraints$turnover_target)){
qp_result <- gmv_opt_ptc(R=R, constraints=constraints, moments=moments, lambda=lambda, target=target, init_weights=portfolio$assets, solver=solver, control=control)
weights <- qp_result$weights
obj_vals <- qp_result$obj_vals
out <- list(weights=weights, objective_measures=obj_vals, opt_values=obj_vals, out=qp_result$out, call=call)
}
if(!is.null(constraints$leverage)){
qp_result <- gmv_opt_leverage(R=R, constraints=constraints, moments=moments, lambda=lambda, target=target, solver=solver, control=control)
weights <- qp_result$weights
obj_vals <- qp_result$obj_vals
out <- list(weights=weights, objective_measures=obj_vals, opt_values=obj_vals, out=qp_result$out, call=call)
}
} else {
if(hasArg(maxSR)) maxSR=match.call(expand.dots=TRUE)$maxSR else maxSR=FALSE
if(maxSR){
target <- max_sr_opt(R=R, constraints=constraints, moments=moments, lambda_hhi=lambda_hhi, conc_groups=conc_groups, solver=solver, control=control)
tmp_moments_mean <- moments$mean
moments$mean <- rep(0, length(moments$mean))
}
roi_result <- gmv_opt(R=R, constraints=constraints, moments=moments, lambda=lambda, target=target, lambda_hhi=lambda_hhi, conc_groups=conc_groups, solver=solver, control=control)
weights <- roi_result$weights
obj_vals <- roi_result$obj_vals
if(maxSR){
port.mean <- as.numeric(sum(weights * tmp_moments_mean))
names(port.mean) <- "mean"
obj_vals$mean <- port.mean
}
out <- list(weights=weights, objective_measures=obj_vals, opt_values=obj_vals, out=roi_result$out, call=call)
}
}
if(length(names(moments)) == 1 & "mean" %in% names(moments)) {
if(optimize_method == "ROI"){
solver <- "glpk"
} else {
solver <- optimize_method
}
if(!is.null(constraints$max_pos) | !is.null(constraints$leverage)) {
roi_result <- maxret_milp_opt(R=R, constraints=constraints, moments=moments, target=target, solver=solver, control=control)
weights <- roi_result$weights
obj_vals <- roi_result$obj_vals
out <- list(weights=weights, objective_measures=obj_vals, opt_values=obj_vals, out=roi_result$out, call=call)
} else {
roi_result <- maxret_opt(R=R, constraints=constraints, moments=moments, target=target, solver=solver, control=control)
weights <- roi_result$weights
obj_vals <- roi_result$obj_vals
out <- list(weights=weights, objective_measures=obj_vals, opt_values=obj_vals, out=roi_result$out, call=call)
}
}
if( any(c("CVaR", "ES", "ETL") %in% names(moments)) ) {
if(optimize_method == "ROI"){
solver <- "glpk"
} else {
solver <- optimize_method
}
if(hasArg(ef)) ef=match.call(expand.dots=TRUE)$ef else ef=FALSE
if(hasArg(maxSTARR)) maxSTARR=match.call(expand.dots=TRUE)$maxSTARR else maxSTARR=TRUE
if(ef) meanetl <- TRUE else meanetl <- FALSE
tmpnames <- c("CVaR", "ES", "ETL")
idx <- which(tmpnames %in% names(moments))
if(length(moments) == 2 & all(moments$mean != 0) & ef==FALSE & maxSTARR){
target <- mean_etl_opt(R=R, constraints=constraints, moments=moments, alpha=alpha, solver=solver, control=control)
meanetl <- TRUE
}
if(!is.null(constraints$max_pos)) {
roi_result <- etl_milp_opt(R=R, constraints=constraints, moments=moments, target=target, alpha=alpha, solver=solver, control=control)
weights <- roi_result$weights
obj_vals <- list()
if(meanetl) obj_vals$mean <- sum(weights * moments$mean)
obj_vals[[tmpnames[idx]]] <- roi_result$out
out <- list(weights=weights, objective_measures=obj_vals, opt_values=obj_vals, out=roi_result$out, call=call)
} else {
roi_result <- etl_opt(R=R, constraints=constraints, moments=moments, target=target, alpha=alpha, solver=solver, control=control)
weights <- roi_result$weights
obj_vals <- list()
if(meanetl) obj_vals$mean <- sum(weights * moments$mean)
obj_vals[[tmpnames[idx]]] <- roi_result$out
out <- list(weights=weights, objective_measures=obj_vals, opt_values=obj_vals, out=roi_result$out, call=call)
}
}
out$moment_values = list(momentFun = moment_name,mu = mout$mu, sigma = mout$sigma)
optimize_method <- "ROI"
}
if(optimize_method=="pso"){
stopifnot("package:pso" %in% search() || requireNamespace("pso",quietly = TRUE) )
if(hasArg(maxit)) maxit=match.call(expand.dots=TRUE)$maxit else maxit=N*50
controlPSO <- list(trace=FALSE, fnscale=1, maxit=1000, maxf=Inf, abstol=-Inf, reltol=0)
PSOcargs <- names(controlPSO)
if( is.list(dotargs) ){
pm <- pmatch(names(dotargs), PSOcargs, nomatch = 0L)
names(dotargs[pm > 0L]) <- PSOcargs[pm]
controlPSO$maxit <- maxit
controlPSO[pm] <- dotargs[pm > 0L]
if(!hasArg(reltol)) controlPSO$reltol <- .000001
if(hasArg(trace) && try(trace==TRUE,silent=TRUE)) controlPSO$trace <- TRUE
if(hasArg(trace) && isTRUE(trace)) {
controlPSO$trace <- TRUE
controlPSO$trace.stats=TRUE
}
}
upper <- constraints$max
lower <- constraints$min
minw <- try(pso::psoptim( par = rep(NA, N), fn = constrained_objective, R=R, portfolio=portfolio, env=dotargs,
lower = lower[1:N] , upper = upper[1:N] , control = controlPSO))
if(inherits(minw,"try-error")) { minw=NULL }
if(is.null(minw)){
message(paste("Optimizer was unable to find a solution for target"))
return (paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector( minw$par)
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(w=weights, R=R, portfolio=portfolio, trace=TRUE, env=dotargs)$objective_measures
out <- list(weights=weights,
objective_measures=obj_vals,
opt_values=obj_vals,
out=minw$value,
call=call)
if (isTRUE(trace)){
out$PSOoutput=minw
}
}
if(optimize_method=="GenSA"){
stopifnot("package:GenSA" %in% search() || requireNamespace("GenSA",quietly = TRUE) )
if(hasArg(maxit)) maxit=match.call(expand.dots=TRUE)$maxit else maxit=N*50
controlGenSA <- list(maxit = 5000, threshold.stop = NULL, temperature = 5230,
visiting.param = 2.62, acceptance.param = -5, max.time = NULL,
nb.stop.improvement = 1e+06, smooth = TRUE, max.call = 1e+07,
verbose = FALSE)
GenSAcargs <- names(controlGenSA)
if( is.list(dotargs) ){
pm <- pmatch(names(dotargs), GenSAcargs, nomatch = 0L)
names(dotargs[pm > 0L]) <- GenSAcargs[pm]
controlGenSA$maxit <- maxit
controlGenSA[pm] <- dotargs[pm > 0L]
if(hasArg(trace) && try(trace==TRUE,silent=TRUE)) controlGenSA$verbose <- TRUE
}
upper <- constraints$max
lower <- constraints$min
if(!is.null(rp)) par = rp[,1] else par = rep(1/N, N)
minw = try(GenSA::GenSA( par=par, lower = lower[1:N] , upper = upper[1:N], control = controlGenSA,
fn = constrained_objective , R=R, portfolio=portfolio, env=dotargs))
if(inherits(minw,"try-error")) { minw=NULL }
if(is.null(minw)){
message(paste("Optimizer was unable to find a solution for target"))
return (paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(minw$par)
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(w=weights, R=R, portfolio=portfolio, trace=TRUE, env=dotargs)$objective_measures
out = list(weights=weights,
objective_measures=obj_vals,
opt_values=obj_vals,
out=minw$value,
call=call)
if (isTRUE(trace)){
out$GenSAoutput=minw
}
}
if (optimize_method == "Rglpk") {
stopifnot("package:Rglpk" %in% search() ||
requireNamespace("Rglpk", quietly = TRUE))
valid_objnames <- c("mean", "CVaR", "ES", "ETL")
target <- -Inf
alpha <- 0.05
reward <- FALSE
risk <- FALSE
for (objective in portfolio$objectives) {
if (objective$enabled) {
if (!(objective$name %in% valid_objnames)) {
stop("Rglpk only solves mean and ETL/ES/CVaR type business objectives,
choose a different optimize_method.")
}
alpha <- ifelse(!is.null(objective$arguments[["p"]]),
objective$arguments[["p"]], alpha
)
target <- ifelse(!is.null(objective$target), objective$target, target)
reward <- ifelse(objective$name == "mean", TRUE, reward)
risk <- ifelse(objective$name %in% valid_objnames[2:4], TRUE, risk)
arguments <- objective$arguments
}
}
alpha <- ifelse(alpha > 0.5, 1 - alpha, alpha)
target <- max(target, constraints$return_target, na.rm = TRUE)
max_sum <- constraints$max_sum
min_sum <- constraints$min_sum
max_sum <- ifelse(is.infinite(max_sum), 9999.0, max_sum)
min_sum <- ifelse(is.infinite(min_sum), -9999.0, min_sum)
upper <- constraints$max
lower <- constraints$min
upper[which(is.infinite(upper))] <- 9999.0
lower[which(is.infinite(lower))] <- 9999.0
if ((max_sum - min_sum) < 0.02) {
message("Leverage constraint min_sum and max_sum are restrictive,
consider relaxing. e.g. 'full_investment' constraint
should be min_sum=0.99 and max_sum=1.01")
}
if (is.null(constraints$max_pos) & is.null(constraints$group_pos)) {
if (reward & !risk) {
Rglpk.obj <- colMeans(R)
Rglpk.mat <- rbind(rep(1, N), rep(1, N))
Rglpk.dir <- c(">=", "<=")
Rglpk.rhs <- c(min_sum, max_sum)
Rglpk.bound <- list(
lower = list(
ind = 1:N,
val = lower
),
upper = list(
ind = 1:N,
val = upper
)
)
if (!is.null(constraints$groups)) {
if (!all(c(
length(constraints$cLO),
length(constraints$cUP)
)
== length(constraints$groups))) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- rep(0, N)
temp[constraints$groups[[i]]] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp, temp)
Rglpk.dir <- c(Rglpk.dir, ">=", "<=")
Rglpk.rhs <- c(
Rglpk.rhs,
constraints$cLO[i],
constraints$cUP[i]
)
}
rm(temp)
}
if (!is.infinite(target)) {
Rglpk.mat <- rbind(Rglpk.mat, colMeans(R))
Rglpk.dir <- c(Rglpk.dir, ">=")
Rglpk.rhs <- c(Rglpk.rhs, target)
}
Rglpk.result <- try(Rglpk::Rglpk_solve_LP(
obj = Rglpk.obj,
mat = Rglpk.mat,
dir = Rglpk.dir,
rhs = Rglpk.rhs,
bound = Rglpk.bound,
types = rep("C", N),
max = TRUE
))
if (inherits(Rglpk.result, "try-error")) Rglpk.result <- NULL
if (is.null(Rglpk.result)) {
message(paste("Optimizer was unable to find a solution for target"))
return(paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(Rglpk.result$solution[1:N])
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(
w = weights, R = R, portfolio = portfolio,
trace = TRUE, env = dotargs
)$objective_measures
out <- list(
weights = weights,
objective_measures = obj_vals,
opt_values = obj_vals,
out = Rglpk.result$optimum,
call = call
)
if (isTRUE(trace)) {
out$Rglpkoutput <- Rglpk.result
}
}
else if (risk & !reward) {
Rglpk.obj <- c(
rep(0, N),
rep(-1 / alpha / T, T),
-1
)
Rglpk.mat <- rbind(
c(rep(1, N), rep(0, T + 1)),
c(rep(1, N), rep(0, T + 1))
)
Rglpk.dir <- c(">=", "<=")
Rglpk.rhs <- c(min_sum, max_sum)
Rglpk.bound <- list(
lower = list(
ind = c(1:N, N + T + 1),
val = c(lower, -Inf)
),
upper = list(
ind = c(1:N, N + T + 1),
val = c(upper, Inf)
)
)
if (!is.null(constraints$groups)) {
if (!all(c(
length(constraints$cLO),
length(constraints$cUP)
)
== length(constraints$groups))) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- rep(0, N + T + 1)
temp[constraints$groups[[i]]] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp, temp)
Rglpk.dir <- c(Rglpk.dir, ">=", "<=")
Rglpk.rhs <- c(
Rglpk.rhs,
constraints$cLO[i],
constraints$cUP[i]
)
}
rm(temp)
}
if (!is.infinite(target)) {
Rglpk.mat <- rbind(Rglpk.mat, colMeans(R))
Rglpk.dir <- c(Rglpk.dir, ">=")
Rglpk.rhs <- c(Rglpk.rhs, target)
}
Rglpk.mat <- rbind(
Rglpk.mat,
cbind(
matrix(R, T),
diag(1, T),
rep(1, T)
)
)
Rglpk.dir <- c(Rglpk.dir, rep(">=", T))
Rglpk.rhs <- c(Rglpk.rhs, rep(0, T))
Rglpk.result <- try(Rglpk::Rglpk_solve_LP(
obj = Rglpk.obj,
mat = Rglpk.mat,
dir = Rglpk.dir,
rhs = Rglpk.rhs,
bound = Rglpk.bound,
types = rep("C", N + T + 1),
max = TRUE
))
if (inherits(Rglpk.result, "try-error")) Rglpk.result <- NULL
if (is.null(Rglpk.result)) {
message(paste("Optimizer was unable to find a solution for target"))
return(paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(Rglpk.result$solution[1:N])
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(
w = weights, R = R, portfolio = portfolio,
trace = TRUE, env = dotargs
)$objective_measures
out <- list(
weights = weights,
objective_measures = obj_vals,
opt_values = obj_vals,
out = Rglpk.result$optimum,
call = call
)
if (isTRUE(trace)) {
out$Rglpkoutput <- Rglpk.result
}
}
else if (risk & reward) {
Rglpk.obj <- c(
rep(0, N),
rep(-1 / alpha / T, T),
-1,
0
)
Rglpk.mat <- rbind(
c(rep(1, N), rep(0, T + 1), -min_sum),
c(rep(1, N), rep(0, T + 1), -max_sum)
)
Rglpk.dir <- c(">=", "<=")
Rglpk.rhs <- c(0, 0)
Rglpk.mat <- rbind(
Rglpk.mat,
cbind(diag(1, N), matrix(0, N, T + 1), -lower),
cbind(diag(1, N), matrix(0, N, T + 1), -upper)
)
Rglpk.dir <- c(Rglpk.dir, rep(">=", N), rep("<=", N))
Rglpk.rhs <- c(Rglpk.rhs, rep(0, 2 * N))
Rglpk.bound <- list(
lower = list(
ind = c(1:N, N + T + 1),
val = c(rep(-Inf, N), -Inf)
),
upper = list(
ind = c(1:N, N + T + 1),
val = c(rep(Inf, N), Inf)
)
)
if (!is.null(constraints$groups)) {
if (!all(c(
length(constraints$cLO),
length(constraints$cUP)
)
== length(constraints$groups))) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- c(rep(0, N + T + 1), -constraints$cLO[i])
temp[constraints$groups[[i]]] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp)
Rglpk.dir <- c(Rglpk.dir, ">=")
Rglpk.rhs <- c(Rglpk.rhs, 0)
temp <- c(rep(0, N + T + 1), -constraints$cUP[i])
temp[constraints$groups[[i]]] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp)
Rglpk.dir <- c(Rglpk.dir, "<=")
Rglpk.rhs <- c(Rglpk.rhs, 0)
}
rm(temp)
}
if (!is.infinite(target)) {
Rglpk.mat <- rbind(
Rglpk.mat,
c(rep(0, N + T + 1)),
target
)
Rglpk.dir <- c(Rglpk.dir, "<=")
Rglpk.rhs <- c(Rglpk.rhs, 1)
}
Rglpk.mat <- rbind(
Rglpk.mat,
c(colMeans(R), rep(0, T + 2))
)
Rglpk.dir <- c(Rglpk.dir, "==")
Rglpk.rhs <- c(Rglpk.rhs, 1)
Rglpk.mat <- rbind(
Rglpk.mat,
cbind(
matrix(R, T),
diag(1, T),
rep(1, T),
rep(0, T)
)
)
Rglpk.dir <- c(Rglpk.dir, rep(">=", T))
Rglpk.rhs <- c(Rglpk.rhs, rep(0, T))
Rglpk.result <- try(Rglpk::Rglpk_solve_LP(
obj = Rglpk.obj,
mat = Rglpk.mat,
dir = Rglpk.dir,
rhs = Rglpk.rhs,
bound = Rglpk.bound,
types = rep("C", N + T + 2),
max = TRUE
))
if (inherits(Rglpk.result, "try-error")) Rglpk.result <- NULL
if (is.null(Rglpk.result)) {
message(paste("Optimizer was unable to find a solution for target"))
return(paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(Rglpk.result$solution[1:N] / Rglpk.result$solution[N + T + 2])
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(
w = weights, R = R, portfolio = portfolio,
trace = TRUE, env = dotargs
)$objective_measures
out <- list(
weights = weights,
objective_measures = obj_vals,
opt_values = obj_vals,
out = Rglpk.result$optimum,
call = call
)
if (isTRUE(trace)) {
out$Rglpkoutput <- Rglpk.result
}
}
}
else {
if (reward & !risk) {
Rglpk.obj <- c(colMeans(R), rep(0, N))
Rglpk.mat <- rbind(
c(rep(1, N), rep(0, N)),
c(rep(1, N), rep(0, N))
)
Rglpk.dir <- c(">=", "<=")
Rglpk.rhs <- c(min_sum, max_sum)
Rglpk.bound <- list(
lower = list(
ind = 1:N,
val = lower
),
upper = list(
ind = 1:N,
val = upper
)
)
if (!is.null(constraints$groups)) {
if (!all(c(
length(constraints$cLO),
length(constraints$cUP)
)
== length(constraints$groups))) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- rep(0, 2 * N)
temp[constraints$groups[[i]]] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp, temp)
Rglpk.dir <- c(Rglpk.dir, ">=", "<=")
Rglpk.rhs <- c(
Rglpk.rhs,
constraints$cLO[i],
constraints$cUP[i]
)
}
if (!is.null(constraints$group_pos)) {
if (length(constraints$group_pos)
!= length(constraints$groups)) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- rep(0, 2 * N)
temp[constraints$groups[[i]] + N] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp)
Rglpk.dir <- c(Rglpk.dir, "==")
Rglpk.rhs <- c(
Rglpk.rhs,
constraints$group_pos[i]
)
}
}
rm(temp)
}
if (!is.infinite(target)) {
Rglpk.mat <- rbind(Rglpk.mat, c(colMeans(R), rep(0, N)))
Rglpk.dir <- c(Rglpk.dir, ">=")
Rglpk.rhs <- c(Rglpk.rhs, target)
}
if (!is.null(constraints$max_pos)) {
Rglpk.mat <- rbind(Rglpk.mat, c(rep(0, N), rep(1, N)))
Rglpk.dir <- c(Rglpk.dir, "<=")
Rglpk.rhs <- c(Rglpk.rhs, constraints$max_pos)
Rglpk.mat <- rbind(
Rglpk.mat,
cbind(diag(1, N), diag(-upper, N)),
cbind(diag(1, N), diag(-lower, N))
)
Rglpk.dir <- c(Rglpk.dir, rep("<=", N), rep(">=", N))
Rglpk.rhs <- c(Rglpk.rhs, rep(0, 2 * N))
}
Rglpk.result <- try(Rglpk::Rglpk_solve_LP(
obj = Rglpk.obj,
mat = Rglpk.mat,
dir = Rglpk.dir,
rhs = Rglpk.rhs,
bound = Rglpk.bound,
types = c(rep("C", N), rep("B", N)),
max = TRUE
))
if (inherits(Rglpk.result, "try-error")) Rglpk.result <- NULL
if (is.null(Rglpk.result)) {
message(paste("Optimizer was unable to find a solution for target"))
return(paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(Rglpk.result$solution[1:N])
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(
w = weights, R = R, portfolio = portfolio,
trace = TRUE, env = dotargs
)$objective_measures
out <- list(
weights = weights,
objective_measures = obj_vals,
opt_values = obj_vals,
out = Rglpk.result$optimum,
call = call
)
if (isTRUE(trace)) {
out$Rglpkoutput <- Rglpk.result
}
}
else if (risk & !reward) {
Rglpk.obj <- c(
rep(0, N),
rep(-1 / alpha / T, T),
-1,
rep(0, N)
)
Rglpk.mat <- rbind(
c(rep(1, N), rep(0, T + 1 + N)),
c(rep(1, N), rep(0, T + 1 + N))
)
Rglpk.dir <- c(">=", "<=")
Rglpk.rhs <- c(min_sum, max_sum)
Rglpk.bound <- list(
lower = list(
ind = c(1:N, N + T + 1),
val = c(constraints$min, -Inf)
),
upper = list(
ind = c(1:N, N + T + 1),
val = c(constraints$max, Inf)
)
)
if (!is.null(constraints$groups)) {
if (!all(c(
length(constraints$cLO),
length(constraints$cUP)
)
== length(constraints$groups))) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- rep(0, 2 * N + T + 1)
temp[constraints$groups[[i]]] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp, temp)
Rglpk.dir <- c(Rglpk.dir, ">=", "<=")
Rglpk.rhs <- c(
Rglpk.rhs,
constraints$cLO[i],
constraints$cUP[i]
)
}
if (!is.null(constraints$group_pos)) {
if (length(constraints$group_pos)
!= length(constraints$groups)) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- rep(0, 2 * N + T + 1)
temp[constraints$groups[[i]] + N + T + 1] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp)
Rglpk.dir <- c(Rglpk.dir, "==")
Rglpk.rhs <- c(
Rglpk.rhs,
constraints$group_pos[i]
)
}
}
rm(temp)
}
if (!is.infinite(target)) {
Rglpk.mat <- rbind(
Rglpk.mat,
c(colMeans(R), rep(0, N + T + 1))
)
Rglpk.dir <- c(Rglpk.dir, ">=")
Rglpk.rhs <- c(Rglpk.rhs, target)
}
if (!is.null(constraints$max_pos)) {
Rglpk.mat <- rbind(
Rglpk.mat,
c(rep(0, N + T + 1), rep(1, N))
)
Rglpk.dir <- c(Rglpk.dir, "<=")
Rglpk.rhs <- c(Rglpk.rhs, constraints$max_pos)
Rglpk.mat <- rbind(
Rglpk.mat,
cbind(
diag(1, N),
matrix(0, N, T + 1),
diag(-upper, N)
),
cbind(
diag(1, N),
matrix(0, N, T + 1),
diag(-lower, N)
)
)
Rglpk.dir <- c(Rglpk.dir, rep("<=", N), rep(">=", N))
Rglpk.rhs <- c(Rglpk.rhs, rep(0, 2 * N))
}
Rglpk.mat <- rbind(
Rglpk.mat,
cbind(
matrix(R, T),
diag(1, T),
rep(1, T),
matrix(0, T, N)
)
)
Rglpk.dir <- c(Rglpk.dir, rep(">=", T))
Rglpk.rhs <- c(Rglpk.rhs, rep(0, T))
Rglpk.result <- try(Rglpk::Rglpk_solve_LP(
obj = Rglpk.obj,
mat = Rglpk.mat,
dir = Rglpk.dir,
rhs = Rglpk.rhs,
bound = Rglpk.bound,
types = c(rep("C", N + T + 1), rep("B", N)),
max = TRUE
))
if (inherits(Rglpk.result, "try-error")) Rglpk.result <- NULL
if (is.null(Rglpk.result)) {
message(paste("Optimizer was unable to find a solution for target"))
return(paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(Rglpk.result$solution[1:N])
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(
w = weights, R = R, portfolio = portfolio,
trace = TRUE, env = dotargs
)$objective_measures
out <- list(
weights = weights,
objective_measures = obj_vals,
opt_values = obj_vals,
out = Rglpk.result$optimum,
call = call
)
if (isTRUE(trace)) {
out$Rglpkoutput <- Rglpk.result
}
}
else if (risk & reward) {
Rglpk.obj <- c(
rep(0, N),
rep(-1 / alpha / T, T),
-1,
0,
rep(0, N)
)
Rglpk.mat <- rbind(
c(
rep(1, N), rep(0, T + 1),
-min_sum, rep(0, N)
),
c(
rep(1, N), rep(0, T + 1),
-max_sum, rep(0, N)
)
)
Rglpk.dir <- c(">=", "<=")
Rglpk.rhs <- c(0, 0)
Rglpk.mat <- rbind(
Rglpk.mat,
cbind(diag(1, N), matrix(0, N, T + 1), -lower, diag(0, N)),
cbind(diag(1, N), matrix(0, N, T + 1), -upper, diag(0, N))
)
Rglpk.dir <- c(Rglpk.dir, rep(">=", N), rep("<=", N))
Rglpk.rhs <- c(Rglpk.rhs, rep(0, 2 * N))
Rglpk.bound <- list(
lower = list(
ind = c(1:N, N + T + 1),
val = rep(-Inf, N + 1)
),
upper = list(
ind = c(1:N, N + T + 1),
val = rep(Inf, N + 1)
)
)
if (!is.null(constraints$groups)) {
if (!all(c(
length(constraints$cLO),
length(constraints$cUP)
)
== length(constraints$groups))) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- c(rep(0, N + T + 1), -constraints$cLO[i], rep(0, N))
temp[constraints$groups[[i]]] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp)
Rglpk.dir <- c(Rglpk.dir, ">=")
Rglpk.rhs <- c(Rglpk.rhs, 0)
temp <- c(rep(0, N + T + 1), -constraints$cUP[i], rep(0, N))
temp[constraints$groups[[i]]] <- 1
Rglpk.mat <- rbind(Rglpk.mat, temp)
Rglpk.dir <- c(Rglpk.dir, "<=")
Rglpk.rhs <- c(Rglpk.rhs, 0)
}
if (!is.null(constraints$groups_pos)) {
if (length(constraints$groups_pos)
!= length(constraints$groups)) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- c(rep(0, N + T + 2), rep(1, N))
Rglpk.mat <- rbind(Rglpk.mat, temp)
Rglpk.dir <- c(Rglpk.dir, "==")
Rglpk.rhs <- c(Rglpk.rhs, constraints$groups_pos[i])
}
}
rm(temp)
}
if (!is.infinite(target)) {
Rglpk.mat <- rbind(
Rglpk.mat,
c(rep(0, N + T + 1)),
target,
rep(0, N)
)
Rglpk.dir <- c(Rglpk.dir, "<=")
Rglpk.rhs <- c(Rglpk.rhs, 1)
}
Rglpk.mat <- rbind(
Rglpk.mat,
c(colMeans(R), rep(0, T + N + 2))
)
Rglpk.dir <- c(Rglpk.dir, "==")
Rglpk.rhs <- c(Rglpk.rhs, 1)
if (!is.null(constraints$max_pos)) {
Rglpk.mat <- rbind(
Rglpk.mat,
c(rep(0, N + T + 2), rep(1, N))
)
Rglpk.dir <- c(Rglpk.dir, "<=")
Rglpk.rhs <- c(Rglpk.rhs, constraints$max_pos)
Rglpk.mat <- rbind(
Rglpk.mat,
cbind(
diag(1, N),
matrix(0, N, T + 2),
diag(-upper - 9999, N)
),
cbind(
diag(1, N),
matrix(0, N, T + 2),
diag(-lower + 9999, N)
)
)
Rglpk.dir <- c(Rglpk.dir, rep("<=", N), rep(">=", N))
Rglpk.rhs <- c(Rglpk.rhs, rep(0, N), rep(0, N))
}
Rglpk.mat <- rbind(
Rglpk.mat,
cbind(
matrix(R, T),
diag(1, T),
rep(1, T),
matrix(0, T, N + 1)
)
)
Rglpk.dir <- c(Rglpk.dir, rep(">=", T))
Rglpk.rhs <- c(Rglpk.rhs, rep(0, T))
Rglpk.result <- try(Rglpk::Rglpk_solve_LP(
obj = Rglpk.obj,
mat = Rglpk.mat,
dir = Rglpk.dir,
rhs = Rglpk.rhs,
bound = Rglpk.bound,
types = c(rep("C", N + T + 2), rep("B", N)),
max = TRUE
))
if (inherits(Rglpk.result, "try-error")) Rglpk.result <- NULL
if (is.null(Rglpk.result)) {
message(paste("Optimizer was unable to find a solution for target"))
return(paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(Rglpk.result$solution[1:N] / Rglpk.result$solution[N + T + 2])
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(
w = weights, R = R, portfolio = portfolio,
trace = TRUE, env = dotargs
)$objective_measures
out <- list(
weights = weights,
objective_measures = obj_vals,
opt_values = obj_vals,
out = Rglpk.result$optimum,
call = call
)
if (isTRUE(trace)) {
out$Rglpkoutput <- Rglpk.result
}
}
}
}
if (optimize_method == "osqp") {
stopifnot("package:osqp" %in% search() ||
requireNamespace("osqp", quietly = TRUE))
valid_objnames <- c(
"mean", "sigma", "StdDev", "var", "volatility",
"sd"
)
reward <- FALSE
risk <- FALSE
target <- -Inf
for (objective in portfolio$objectives) {
if (objective$enabled) {
if (!(objective$name %in% valid_objnames)) {
stop("osqp only solves mean and sd type business objectives,
choose a different optimize method.")
}
target <- ifelse(!is.null(objective$target), objective$target, target)
reward <- ifelse(objective$name == "mean", TRUE, reward)
risk <- ifelse(objective$name %in% valid_objnames[2:6], TRUE, risk)
}
}
target <- max(target, constraints$return_target, na.rm = TRUE)
max_sum <- constraints$max_sum
min_sum <- constraints$min_sum
max_sum <- ifelse(is.infinite(max_sum), 9999.0, max_sum)
min_sum <- ifelse(is.infinite(min_sum), -9999.0, min_sum)
upper <- constraints$max
lower <- constraints$min
upper[which(is.infinite(upper))] <- 9999.0
lower[which(is.infinite(lower))] <- 9999.0
if ((max_sum - min_sum) < 0.02) {
message("Leverage constraint min_sum and max_sum are restrictive,
consider relaxing. e.g. 'full_investment' constraint
should be min_sum=0.99 and max_sum=1.01")
}
if (xor(risk, reward)) {
if (reward) {
osqp.P <- diag(0, N)
osqp.q <- -colMeans(R)
}
else {
osqp.P <- cov(R)
osqp.q <- rep(0, N)
}
osqp.A <- rep(1, N)
osqp.l <- min_sum
osqp.u <- max_sum
osqp.rnames <- c("sum")
osqp.A <- rbind(osqp.A, diag(1, N))
osqp.l <- c(osqp.l, lower)
osqp.u <- c(osqp.u, upper)
osqp.rnames <- c(
osqp.rnames,
rep("box", N)
)
if (!is.null(constraints$groups)) {
if (!all(c(
length(constraints$cLO),
length(constraints$cUP)
)
== length(constraints$groups))) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- rep(0, N)
temp[constraints$groups[[i]]] <- 1
osqp.A <- rbind(osqp.A, temp)
osqp.l <- c(osqp.l, constraints$cLO[i])
osqp.u <- c(osqp.u, constraints$cUP[i])
}
rm(temp)
osqp.rnames <- c(
osqp.rnames,
rep(
"group",
length(constraints$groups)
)
)
}
if (!is.infinite(target)) {
osqp.A <- rbind(osqp.A, colMeans(R))
osqp.l <- c(osqp.l, target)
osqp.u <- c(osqp.u, Inf)
osqp.rnames <- c(osqp.rnames, "target")
}
rownames(osqp.A) <-
names(osqp.u) <-
names(osqp.l) <- osqp.rnames
colnames(osqp.A) <- colnames(R)
osqp.result <- try(osqp::solve_osqp(
P = osqp.P,
q = osqp.q,
A = osqp.A,
l = osqp.l,
u = osqp.u,
pars = osqp::osqpSettings(verbose = FALSE)
))
if (inherits(osqp.result, "try-error")) osqp.result <- NULL
if (is.null(osqp.result)) {
message(paste("Optimizer was unable to find a solution for target"))
return(paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(osqp.result$x)
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(
w = weights, R = R, portfolio = portfolio,
trace = TRUE, env = dotargs
)$objective_measures
out <- list(
weights = weights,
objective_measures = obj_vals,
opt_values = obj_vals,
out = osqp.result$info$obj_val,
call = call
)
if (isTRUE(trace)) {
out$osqpoutput <- osqp.result
}
}
else {
osqp.P <- cbind(rbind(cov(R), rep(0, N)), rep(0, N + 1))
osqp.q <- rep(0, N + 1)
osqp.rnames <- c()
osqp.A <- c(rep(1, N), -min_sum)
osqp.A <- rbind(osqp.A, c(rep(-1, N), max_sum))
osqp.l <- c(0, 0)
osqp.u <- c(Inf, Inf)
osqp.rnames <- c("min.sum", "max.sum")
osqp.A <- rbind(
osqp.A,
cbind(diag(1, N), -lower)
)
osqp.A <- rbind(
osqp.A,
cbind(diag(-1, N), upper)
)
osqp.l <- c(osqp.l, rep(0, 2 * N))
osqp.u <- c(osqp.u, rep(Inf, 2 * N))
osqp.rnames <- c(osqp.rnames, rep("Box", 2 * N))
if (!is.null(constraints$groups)) {
if (!all(c(
length(constraints$cLO),
length(constraints$cUP)
)
== length(constraints$groups))) {
stop("Please assign group constraint")
}
for (i in 1:length(constraints$groups)) {
temp <- rep(0, N)
temp[constraints$groups[[i]]] <- 1
osqp.A <- rbind(
osqp.A,
c(temp, -constraints$cLO[i])
)
osqp.A <- rbind(
osqp.A,
c(-temp, constraints$cUP[i])
)
osqp.l <- c(osqp.l, 0, 0)
osqp.u <- c(osqp.u, Inf, Inf)
}
osqp.rnames <- c(osqp.rnames, rep(
"Group",
2 * length(constraints$groups)
))
rm(temp)
}
if (!is.infinite(target)) {
osqp.A <- rbind(osqp.A, c(rep(0, N), target))
osqp.l <- c(osqp.l, -Inf)
osqp.u <- c(osqp.u, 1)
osqp.rnames <- c(osqp.rnames, "target")
}
osqp.A <- rbind(osqp.A, c(colMeans(R), 0))
osqp.l <- c(osqp.l, 1)
osqp.u <- c(osqp.u, 1)
osqp.A <- rbind(osqp.A, c(rep(0, N), 1))
osqp.l <- c(osqp.l, 0)
osqp.u <- c(osqp.u, Inf)
osqp.rnames <- c(osqp.rnames, rep("Shrinkage", 2))
rownames(osqp.A) <-
names(osqp.u) <-
names(osqp.l) <- osqp.rnames
colnames(osqp.A) <- c(colnames(R), "Shrinkage")
osqp.result <- try(osqp::solve_osqp(
P = osqp.P,
q = osqp.q,
A = osqp.A,
l = osqp.l,
u = osqp.u,
pars = osqp::osqpSettings(verbose = FALSE)
))
if (inherits(osqp.result, "try-error")) osqp.result <- NULL
if (is.null(osqp.result)) {
message(paste("Optimizer was unable to find a solution for target"))
return(paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(osqp.result$x[1:N] / osqp.result$x[N + 1])
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(
w = weights, R = R, portfolio = portfolio,
trace = TRUE, env = dotargs
)$objective_measures
out <- list(
weights = weights,
objective_measures = obj_vals,
opt_values = obj_vals,
out = osqp.result$info$obj_val,
call = call
)
if (isTRUE(trace)) {
out$osqpoutput <- osqp.result
}
}
}
if (optimize_method == "mco") {
mco.fn <- function(w) {
mco.return <- FALSE
mco.risk <- FALSE
mco.lambda <- FALSE
mco.alpha <- 0.05
for (objective in portfolio$objectives) {
if (objective$enabled) {
mco.return <- ifelse(objective$name == "mean",
t(w) %*% colMeans(R),
mco.return
)
mco.risk <- switch(
objective$name,
"ES" = 1,
"CVaR" = 1,
"TVaR" = 1,
"sigma" = 2,
"volitility" = 2,
"StdDev" = 2,
"sd" = 2
)
mco.risk <- ifelse(is.null(mco.risk), FALSE, mco.risk)
mco.alpha <- switch(
is.null(objective$arguments[["p"]]) + 1,
objective$arguments[["p"]],
mco.alpha
)
mco.alpha <- ifelse(mco.alpha > 0.5, 1 - mco.alpha, mco.alpha)
mco.lambda <- ifelse(is.null(objective$risk_aversion),
mco.lambda,
objective$risk_aversion
)
}
}
mco.portfolio <- R %*% w
if (mco.risk == 1) {
mco.VaR <- quantile(mco.portfolio, mco.alpha)
mco.output.risk <- -mean(mco.portfolio[which(mco.portfolio <= mco.VaR)])
}
if (mco.risk == 2) {
mco.output.risk <- sd(mco.portfolio)
if (mco.lambda) {
mco.output.risk <- mco.output.risk - mco.lambda * mean(mco.portfolio)
}
}
if (mean(mco.portfolio) < 0) {
return(Inf)
}
if (mco.return) {
if (mco.risk) {
return(mco.output.risk / mean(mco.portfolio))
}
return(-mean(mco.portfolio))
}
return(mco.output.risk)
}
mco.idim <- N
mco.odim <- 1
mco.constraints <- function(w) {
result <- 0
result <- result - 999 * max(sum(w) > constraints$max_sum, 0)
result <- result - 999 * max(constraints$min_sum > sum(w), 0)
result <- result -
999 * max(sum(w != 0) > constraints$max_pos, 0, na.rm = TRUE)
result <- result -
999 * max(t(w) %*% colMeans(R) < constraints$return_target,
0,
na.rm = TRUE
)
result <- result -
999 * max(constraints$div_target > (1 - sum(w^2)),
0,
na.rm = TRUE
)
if (!is.null(constraints$groups)) {
for (i in 1:length(constraints$groups)) {
result <- result -
999 * max(sum(w[constraints$groups[[i]]]) > constraints$cUP[i],
0,
na.rm = TRUE
)
result <- result -
999 * max(constraints$cLO[i] > sum(w[constraints$groups[[i]]]),
0,
na.rm = TRUE
)
}
if (!is.null(constraints$group_pos)) {
for (i in 1:length(constraints$groups)) {
temp <- w[constraints$groups[[i]]]
result <- result -
999 * max(sum(temp != 0) > constraints$group_pos[i],
0,
na.rm = TRUE
)
}
}
}
return(result)
}
if (!is.null(constraints$groups)) {
if (!all(c(
length(constraints$cLO),
length(constraints$cUP)
)
== length(constraints$groups))) {
stop("Please assign group constraint")
}
if (!is.null(constraints$group_pos)) {
if (length(constraints$group_pos)
!= length(constraints$groups)) {
stop("Please assign group constraint")
}
}
}
mco.result <- try(mco::nsga2(
fn = mco.fn,
idim = mco.idim,
odim = mco.odim,
constraints = mco.constraints,
cdim = 1,
lower.bounds = constraints$min,
upper.bounds = constraints$max
))
if (inherits(mco.result, "try-error")) mco.result <- NULL
if (is.null(mco.result)) {
message(paste("Optimizer was unable to find a solution for target"))
return(paste("Optimizer was unable to find a solution for target"))
}
weights <- as.vector(mco.result$par[1, ])
weights <- normalize_weights(weights)
names(weights) <- colnames(R)
obj_vals <- constrained_objective(
w = weights, R = R, portfolio = portfolio,
trace = TRUE, env = dotargs
)$objective_measures
out <- list(
weights = weights,
objective_measures = obj_vals,
opt_values = obj_vals,
out = mco.result$value[1, 1],
call = call
)
if (isTRUE(trace)) {
out$mcooutput <- mco.result
}
}
cvxr_solvers <- c("CBC", "GLPK", "GLPK_MI", "OSQP", "CPLEX", "SCS", "ECOS", "GUROBI", "MOSEK")
if (optimize_method == "CVXR" || optimize_method == "cvxr" || optimize_method %in% cvxr_solvers) {
stopifnot("package:CVXR" %in% search() || requireNamespace("CVXR", quietly = TRUE))
if(optimize_method == "CVXR"|| optimize_method == "cvxr") cvxr_default=TRUE else cvxr_default=FALSE
X <- as.matrix(R)
wts <- CVXR::Variable(N)
z <- CVXR::Variable(T)
zeta <- CVXR::Variable(1)
t <- CVXR::Variable(1)
target = -Inf
reward <- FALSE
risk <- FALSE
risk_ES <- FALSE
risk_CSM <- FALSE
risk_HHI <- FALSE
maxSR <- FALSE
maxSTARR <- FALSE
ESratio <- FALSE
CSMratio <- FALSE
alpha <- 0.05
lambda <- 1
valid_objnames <- c("mean", "var", "sd", "StdDev", "ES", "CVaR", "ETL", "CSM", "HHI", "hhi")
for (objective in portfolio$objectives) {
if (objective$enabled) {
if (!(objective$name %in% valid_objnames)) {
stop("CVXR only solves mean, var/sd/StdDev and ETL/ES/CVaR/CSM/EQS type business objectives,
choose a different optimize_method.")
}
alpha <- ifelse(!is.null(objective$arguments[["p"]]), objective$arguments[["p"]], alpha)
lambda <- ifelse(!is.null(objective$risk_aversion), objective$risk_aversion, lambda)
target <- ifelse(!is.null(objective$target), objective$target, target)
reward <- ifelse(objective$name == "mean", TRUE, reward)
risk <- ifelse(objective$name %in% valid_objnames[2:4], TRUE, risk)
risk_ES <- ifelse(objective$name %in% valid_objnames[5:7], TRUE, risk_ES)
risk_CSM <- ifelse(objective$name %in% valid_objnames[8:8], TRUE, risk_CSM)
if(objective$name %in% valid_objnames[9:11]){
risk_HHI <- TRUE
lambda_hhi <- objective$conc_aversion
}
arguments <- objective$arguments
}
}
if(alpha > 0.5) alpha <- (1 - alpha)
mean_value <- mout$mu
sigma_value <- mout$sigma
if(hasArg(ef)) ef=match.call(expand.dots=TRUE)$ef else ef=FALSE
if(ef){
reward=FALSE
mean_idx <- which(unlist(lapply(portfolio$objectives, function(x) x$name)) == "mean")
return_target <- portfolio$objectives[[mean_idx]]$target
} else return_target=NULL
if(reward & !risk & !risk_ES & !risk_CSM & !risk_HHI){
obj <- -t(mean_value) %*% wts
constraints_cvxr = list()
tmpname = "mean"
} else if(!reward & risk & !risk_ES & !risk_CSM & !risk_HHI){
obj <- CVXR::quad_form(wts, sigma_value)
constraints_cvxr = list()
tmpname = "StdDev"
} else if(!reward & risk & !risk_ES & !risk_CSM & risk_HHI){
obj <- CVXR::quad_form(wts, sigma_value) + lambda_hhi * CVXR::cvxr_norm(wts, 2) **2
constraints_cvxr = list()
tmpname = "HHI"
} else if(reward & risk & !risk_ES & !risk_CSM & !risk_HHI){
if(hasArg(maxSR)) maxSR=match.call(expand.dots=TRUE)$maxSR
if(!maxSR){
obj <- CVXR::quad_form(wts, sigma_value) - (t(mean_value) %*% wts) / lambda
constraints_cvxr = list()
tmpname = "optimal value"
} else {
obj <- CVXR::quad_form(wts, sigma_value)
constraints_cvxr = list(t(mean_value) %*% wts == 1, sum(wts) >= 0)
tmpname = "Sharpe Ratio"
}
} else if(risk_ES & !risk_CSM){
if(reward){
if(hasArg(maxSTARR)) maxSTARR=match.call(expand.dots=TRUE)$maxSTARR else maxSTARR=TRUE
if(hasArg(ESratio)) maxSTARR=match.call(expand.dots=TRUE)$ESratio else maxSTARR=maxSTARR
}
if(maxSTARR){
obj <- zeta + (1/(T*alpha)) * sum(z)
constraints_cvxr = list(z >= 0,
z >= -X %*% wts - zeta,
t(mean_value) %*% wts == 1,
sum(wts) >= 0)
tmpname = "ES ratio"
} else {
obj <- zeta + (1/(T*alpha)) * sum(z)
constraints_cvxr = list(z >= 0, z >= -X %*% wts - zeta)
tmpname = "ES"
}
} else if(!risk_ES & risk_CSM){
if(reward){
if(hasArg(CSMratio)) CSMratio=match.call(expand.dots=TRUE)$CSMratio else CSMratio=TRUE
}
if(CSMratio){
obj <- zeta + (1/(alpha* sqrt(T))) * t
constraints_cvxr = list(z >= 0,
z >= -X %*% wts - zeta,
t(mean_value) %*% wts == 1,
sum(wts) >= 0,
t >= CVXR::p_norm(z, p=2))
tmpname = "CSM ratio"
} else {
obj <- zeta + (1/(alpha* sqrt(T))) * t
constraints_cvxr = list(z >= 0, z >= -X %*% wts - zeta,
t >= CVXR::p_norm(z, p=2))
tmpname = "CSM"
}
} else {
stop("Wrong multiple objectives. CVXR only solves mean, var, or simple ES/CSM type business objectives, please reorganize the objectives.")
}
if(!maxSR & !maxSTARR & !CSMratio) weight_scale=1 else weight_scale=sum(wts)
if(!maxSR & !maxSTARR & !CSMratio){
if(!is.null(constraints$max_sum) & !is.infinite(constraints$max_sum) & constraints$max_sum - constraints$min_sum <= 0.001){
constraints_cvxr = append(constraints_cvxr, sum(wts) == constraints$max_sum)
} else{
if(!is.null(constraints$max_sum)){
max_sum <- ifelse(is.infinite(constraints$max_sum), 9999.0, constraints$max_sum)
constraints_cvxr = append(constraints_cvxr, sum(wts) <= max_sum)
}
if(!is.null(constraints$min_sum)){
min_sum <- ifelse(is.infinite(constraints$min_sum), -9999.0, constraints$min_sum)
constraints_cvxr = append(constraints_cvxr, sum(wts) >= min_sum)
}
}
}
upper <- constraints$max
lower <- constraints$min
if(!all(is.infinite(upper))){
upper[which(is.infinite(upper))] <- 9999.0
constraints_cvxr = append(constraints_cvxr, wts <= upper * weight_scale)
}
if(!all(is.infinite(lower))){
lower[which(is.infinite(lower))] <- -9999.0
constraints_cvxr = append(constraints_cvxr, wts >= lower * weight_scale)
}
i = 1
for(g in constraints$groups){
constraints_cvxr = append(constraints_cvxr, sum(wts[g]) >= constraints$cLO[i] * weight_scale)
constraints_cvxr = append(constraints_cvxr, sum(wts[g]) <= constraints$cUP[i] * weight_scale)
i = i + 1
}
if(!is.null(constraints$return_target)){
constraints_cvxr = append(constraints_cvxr, t(mean_value) %*% wts >= constraints$return_target * weight_scale)
}
if(!is.null(return_target)){
constraints_cvxr = append(constraints_cvxr, t(mean_value) %*% wts >= return_target)
}
if(!is.null(constraints$B)){
constraints_cvxr = append(constraints_cvxr, t(constraints$B) %*% wts <= constraints$upper)
constraints_cvxr = append(constraints_cvxr, t(constraints$B) %*% wts >= constraints$lower)
}
if(!is.null(constraints$turnover_target)){
if(is.null(constraints$weight_initial)){
weight_initial <- rep(1/N, N)
} else {
weight_initial <- constraints$weight_initial
}
if(tmpname == "StdDev"){
if(is.null(constraints$turnover_penalty)){
sigma_value_penalty = nearPD(sigma_value)$mat
} else {
sigma_value_penalty = sigma_value + diag(constraints$turnover_penalty, N)
}
obj <- CVXR::quad_form(wts - weight_initial, sigma_value_penalty) + 2 * t(wts - weight_initial) %*% sigma_value %*% weight_initial + t(weight_initial) %*% sigma_value %*% weight_initial
}
constraints_cvxr = append(constraints_cvxr, sum(abs(wts - weight_initial)) <= constraints$turnover_target)
}
prob_cvxr <- CVXR::Problem(CVXR::Minimize(obj), constraints = constraints_cvxr)
if(cvxr_default){
if((risk || maxSR) && !risk_HHI){
result_cvxr <- CVXR::solve(prob_cvxr, solver = "OSQP", ... = ...)
} else {
result_cvxr <- CVXR::solve(prob_cvxr, solver = "SCS", ... = ...)
}
} else {
result_cvxr <- CVXR::solve(prob_cvxr, solver = optimize_method, ... = ...)
}
cvxr_wts <- result_cvxr$getValue(wts)
if(maxSR | maxSTARR | CSMratio) cvxr_wts <- cvxr_wts / sum(cvxr_wts)
cvxr_wts <- as.vector(cvxr_wts)
cvxr_wts <- normalize_weights(cvxr_wts)
names(cvxr_wts) <- colnames(R)
obj_cvxr <- list()
if(reward & !risk & !risk_ES & !risk_CSM & !risk_HHI){
obj_cvxr[[tmpname]] <- -result_cvxr$value
} else if(!reward & risk & !risk_ES & !risk_CSM & !risk_HHI){
obj_cvxr[[tmpname]] <- sqrt(result_cvxr$value)
} else if(!reward & risk & !risk_ES & !risk_CSM & risk_HHI){
obj_cvxr[["StdDev"]] <- sqrt(t(cvxr_wts) %*% sigma_value %*% cvxr_wts)
obj_cvxr[[tmpname]] <- (result_cvxr$value - t(cvxr_wts) %*% sigma_value %*% cvxr_wts)/lambda_hhi
} else if(!maxSR & !maxSTARR & !CSMratio){
obj_cvxr[[tmpname]] <- result_cvxr$value
if(reward & risk){
obj_cvxr[["mean"]] <- cvxr_wts %*% mean_value
obj_cvxr[["StdDev"]] <- sqrt(t(cvxr_wts) %*% sigma_value %*% cvxr_wts)
}
} else {
obj_cvxr[["mean"]] <- cvxr_wts %*% mean_value
if(maxSR){
obj_cvxr[["StdDev"]] <- sqrt(t(cvxr_wts) %*% sigma_value %*% cvxr_wts)
obj_cvxr[[tmpname]] <- obj_cvxr[["mean"]] / obj_cvxr[["StdDev"]]
} else if(maxSTARR){
obj_cvxr[["ES"]] <- result_cvxr$value / sum(result_cvxr$getValue(wts))
obj_cvxr[[tmpname]] <- obj_cvxr[["mean"]] / obj_cvxr[["ES"]]
} else if(CSMratio){
obj_cvxr[["CSM"]] <- result_cvxr$value / sum(result_cvxr$getValue(wts))
obj_cvxr[[tmpname]] <- obj_cvxr[["mean"]] / obj_cvxr[["CSM"]]
}
}
if(ef) obj_cvxr[["mean"]] <- cvxr_wts %*% mean_value
out = list(weights = cvxr_wts,
objective_measures = obj_cvxr,
opt_values=obj_cvxr,
out = obj_cvxr[[tmpname]],
call = call,
solver = result_cvxr$solver,
moment_values = list(momentFun = moment_name,mu = mout$mu, sigma = mout$sigma))
optimize_method = "CVXR"
}
end_t <- Sys.time()
if(message) message(c("elapsed time:", end_t-start_t))
out$portfolio <- portfolio
if(trace) out$R <- R
out$data_summary <- list(first=first(R), last=last(R))
out$elapsed_time <- end_t - start_t
out$end_t <- as.character(Sys.time())
if(regime.switching){
out$regime <- regime.idx
}
class(out) <- c(paste("optimize.portfolio", optimize_method, sep='.'), "optimize.portfolio")
return(out)
}
optimize.portfolio.rebalancing_v1 <- function(R,constraints,optimize_method=c("DEoptim","random","ROI"), search_size=20000, trace=FALSE, ..., rp=NULL, rebalance_on=NULL, training_period=NULL, rolling_window=NULL)
{
stopifnot("package:foreach" %in% search() || requireNamespace("foreach",quietly=TRUE))
start_t<-Sys.time()
call <- match.call()
if(optimize_method=="random"){
if(is.null(rp))
rp<-random_portfolios(rpconstraints=constraints,permutations=search_size)
} else {
rp=NULL
}
if(hasArg(trailing_periods)) {
trailing_periods=match.call(expand.dots=TRUE)$trailing_periods
rolling_window <- trailing_periods
}
if(is.null(training_period)) {if(nrow(R)<36) training_period=nrow(R) else training_period=36}
if (is.null(rolling_window)){
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
ep <- ep.i[1]
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
optimize.portfolio(R[1:ep,],constraints=constraints,optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
}
} else {
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
optimize.portfolio(R[(ifelse(ep-rolling_window>=1,ep-rolling_window,1)):ep,],constraints=constraints,optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
}
}
names(out_list)<-index(R[ep.i])
end_t<-Sys.time()
message(c("overall elapsed time:",end_t-start_t))
class(out_list)<-c("optimize.portfolio.rebalancing")
return(out_list)
}
optimize.portfolio.rebalancing <- function(R, portfolio=NULL, constraints=NULL, objectives=NULL, optimize_method=c("DEoptim", "random", "ROI", "CVXR"), search_size=20000, trace=FALSE, ..., rp=NULL, rebalance_on=NULL, training_period=NULL, rolling_window=NULL)
{
stopifnot("package:foreach" %in% search() || requireNamespace("foreach",quietly=TRUE))
stopifnot("package:iterators" %in% search() || requireNamespace("iterators",quietly=TRUE))
if(inherits(portfolio, "portfolio.list")){
n.portf <- length(portfolio)
opt.list <- vector("list", n.portf)
for(i in 1:length(opt.list)){
if(hasArg(message)) message=match.call(expand.dots=TRUE)$message else message=FALSE
if(message) cat("Starting optimization of portfolio ", i, "\n")
opt.list[[i]] <- optimize.portfolio.rebalancing(R=R,
portfolio=portfolio[[i]],
constraints=constraints,
objectives=objectives,
optimize_method=optimize_method,
search_size=search_size,
trace=trace,
...=...,
rp=rp,
rebalance_on=rebalance_on,
training_period=training_period,
rolling_window=rolling_window)
}
out <- combine.optimizations(opt.list)
class(out) <- "opt.rebal.list"
return(out)
}
if(inherits(portfolio, "mult.portfolio.spec")){
R <- proxy.mult.portfolio(R=R, mult.portfolio=portfolio)
portfolio <- portfolio$top.portfolio
}
call <- match.call()
start_t<-Sys.time()
if (!is.null(portfolio) & !is.portfolio(portfolio)){
stop("you must pass in an object of class 'portfolio' to control the optimization")
}
if(hasArg(message)) message=match.call(expand.dots=TRUE)$message else message=FALSE
if(hasArg(trailing_periods)) {
trailing_periods=match.call(expand.dots=TRUE)$trailing_periods
rolling_window <- trailing_periods
}
if(!is.null(constraints)){
if(inherits(constraints, "v1_constraint")){
if(is.null(portfolio)){
tmp_portf <- portfolio.spec(assets=constraints$assets)
}
message("constraint object passed in is a 'v1_constraint' object, updating to v2 specification")
portfolio <- update_constraint_v1tov2(portfolio=tmp_portf, v1_constraint=constraints)
}
if(!inherits(constraints, "v1_constraint")){
portfolio <- insert_constraints(portfolio=portfolio, constraints=constraints)
}
}
if(!is.null(objectives)){
portfolio <- insert_objectives(portfolio=portfolio, objectives=objectives)
}
call <- match.call()
if(length(optimize_method) == 2) optimize_method <- optimize_method[2] else optimize_method <- optimize_method[1]
if(optimize_method=="random"){
if(hasArg(rp_method)) rp_method=match.call(expand.dots=TRUE)$rp_method else rp_method="sample"
if(hasArg(eliminate)) eliminate=match.call(expand.dots=TRUE)$eliminate else eliminate=TRUE
if(hasArg(fev)) fev=match.call(expand.dots=TRUE)$fev else fev=0:5
if(is.null(rp))
if(inherits(portfolio, "regime.portfolios")){
rp <- rp.regime.portfolios(regime=portfolio, permutations=search_size, rp_method=rp_method, eliminate=eliminate, fev=fev)
} else {
rp <- random_portfolios(portfolio=portfolio, permutations=search_size, rp_method=rp_method, eliminate=eliminate, fev=fev)
}
} else {
rp = NULL
}
if(is.null(training_period) & !is.null(rolling_window))
training_period <- rolling_window
if(is.null(training_period)) {if(nrow(R)<36) training_period=nrow(R) else training_period=36}
turnover_idx <- which(sapply(portfolio$constraints, function(x) x$type == "turnover"))
if(length(turnover_idx) > 0){
turnover_idx <- turnover_idx[1]
if(is.null(portfolio$constraints[[turnover_idx]]$weight_initial)){
portfolio$constraints[[turnover_idx]]$weight_initial <- rep(1/length(portfolio$assets), length(portfolio$assets))
}
if (is.null(rolling_window)){
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
ep <- ep.i[1]
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
opt <- optimize.portfolio(R[1:ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
portfolio$constraints[[turnover_idx]]$weight_initial <- opt$weights
opt
}
} else {
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
opt <- optimize.portfolio(R[(ifelse(ep-rolling_window>=1,ep-rolling_window,1)):ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
portfolio$constraints[[turnover_idx]]$weight_initial <- opt$weights
opt
}
}
} else {
if (is.null(rolling_window)){
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
ep <- ep.i[1]
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
optimize.portfolio(R[1:ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
}
} else {
ep.i<-endpoints(R,on=rebalance_on)[which(endpoints(R, on = rebalance_on)>=training_period)]
out_list<-foreach::foreach(ep=iterators::iter(ep.i), .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
optimize.portfolio(R[(ifelse(ep-rolling_window>=1,ep-rolling_window,1)):ep,], portfolio=portfolio, optimize_method=optimize_method, search_size=search_size, trace=trace, rp=rp, parallel=FALSE, ...=...)
}
}
}
names(out_list)<-index(R[ep.i])
end_t <- Sys.time()
elapsed_time <- end_t - start_t
if(message) message(c("overall elapsed time:", end_t-start_t))
out <- list()
out$portfolio <- portfolio
out$R <- R
out$call <- call
out$elapsed_time <- elapsed_time
out$opt_rebalancing <- out_list
class(out) <- c("optimize.portfolio.rebalancing")
return(out)
}
optimize.portfolio.parallel <- function(R,
portfolio,
optimize_method=c("DEoptim","random","ROI","pso","GenSA","CVXR"),
search_size=20000,
trace=FALSE, ...,
rp=NULL,
momentFUN='set.portfolio.moments',
message=FALSE,
nodes=4)
{
stopifnot("package:foreach" %in% search() || requireNamespace("foreach",quietly=TRUE))
optimize_method=optimize_method[1]
start_t <- Sys.time()
call <- match.call()
opt_out_list <- foreach::foreach(1:nodes, .errorhandling='pass', .packages='PortfolioAnalytics') %dopar% {
optimize.portfolio(R=R, portfolio=portfolio,
optimize_method=optimize_method,
search_size=search_size, trace=trace,
rp=rp, momentFUN=momentFUN, parallel=FALSE,
...=...)
}
end_t <- Sys.time()
elapsed_t <- end_t - start_t
if(message) message(c("overall elapsed time:", elapsed_t))
out <- list()
out$optimizations <- opt_out_list
out$call <- call
out$elapsed_time <- elapsed_t
class(out) <- c("optimize.portfolio.parallel")
return(out)
}