1##### GMV and QU QP Function #####2#' GMV/QU QP Optimization3#'4#' This function is called by optimize.portfolio to solve minimum variance or5#' maximum quadratic utility problems6#'7#' @param R xts object of asset returns8#' @param constraints object of constraints in the portfolio object extracted with \code{get_constraints}9#' @param moments object of moments computed based on objective functions10#' @param lambda risk_aversion parameter11#' @param target target return value12#' @param lambda_hhi concentration aversion parameter13#' @param conc_groups list of vectors specifying the groups of the assets.14#' @param solver solver to use15#' @param control list of solver control parameters16#' @author Ross Bennett17gmv_opt <- function(R, constraints, moments, lambda, target, lambda_hhi, conc_groups, solver="quadprog", control=NULL){18stopifnot("package:ROI" %in% search() || requireNamespace("ROI", quietly=TRUE))19plugin <- paste0("ROI.plugin.", solver)20stopifnot(paste0("package:", plugin) %in% search() || requireNamespace(plugin, quietly=TRUE))2122# Check for cleaned returns in moments23if(!is.null(moments$cleanR)) R <- moments$cleanR2425# Number of assets26N <- ncol(R)2728# Check for a target return constraint29if(!is.na(target)) {30# If var is the only objective specified, then moments$mean won't be calculated31if(all(moments$mean==0)){32tmp_means <- colMeans(R)33} else {34tmp_means <- moments$mean35}36} else {37tmp_means <- rep(0, N)38target <- 039}40Amat <- tmp_means41dir.vec <- "=="42rhs.vec <- target43meq <- 14445# Set up initial A matrix for leverage constraints46Amat <- rbind(Amat, rep(1, N), rep(-1, N))47dir.vec <- c(dir.vec, ">=",">=")48rhs.vec <- c(rhs.vec, constraints$min_sum, -constraints$max_sum)4950# Add min box constraints51Amat <- rbind(Amat, diag(N))52dir.vec <- c(dir.vec, rep(">=", N))53rhs.vec <- c(rhs.vec, constraints$min)5455# Add max box constraints56Amat <- rbind(Amat, -1*diag(N))57dir.vec <- c(dir.vec, rep(">=", N))58rhs.vec <- c(rhs.vec, -constraints$max)5960# Applying box constraints61lb <- constraints$min62ub <- constraints$max6364bnds <- ROI::V_bound(li=seq.int(1L, N), lb=as.numeric(lb),65ui=seq.int(1L, N), ub=as.numeric(ub))6667# Include group constraints68if(try(!is.null(constraints$groups), silent=TRUE)){69n.groups <- length(constraints$groups)70Amat.group <- matrix(0, nrow=n.groups, ncol=N)71for(i in 1:n.groups){72Amat.group[i, constraints$groups[[i]]] <- 173}74if(is.null(constraints$cLO)) cLO <- rep(-Inf, n.groups)75if(is.null(constraints$cUP)) cUP <- rep(Inf, n.groups)76Amat <- rbind(Amat, Amat.group, -Amat.group)77dir.vec <- c(dir.vec, rep(">=", (n.groups + n.groups)))78rhs.vec <- c(rhs.vec, constraints$cLO, -constraints$cUP)79}8081# Add the factor exposures to Amat, dir.vec, and rhs.vec82if(!is.null(constraints$B)){83t.B <- t(constraints$B)84Amat <- rbind(Amat, t.B, -t.B)85dir.vec <- c(dir.vec, rep(">=", 2 * nrow(t.B)))86rhs.vec <- c(rhs.vec, constraints$lower, -constraints$upper)87}8889# quadprog cannot handle infinite values so replace Inf with .Machine$double.xmax90# This is the strategy used in ROI91# Amat[ is.infinite(Amat) & (Amat <= 0) ] <- -.Machine$double.xmax92# Amat[ is.infinite(Amat) & (Amat >= 0) ] <- .Machine$double.xmax93# rhs.vec[is.infinite(rhs.vec) & (rhs.vec <= 0)] <- -.Machine$double.xmax94# rhs.vec[is.infinite(rhs.vec) & (rhs.vec >= 0)] <- .Machine$double.xmax9596# Remove the rows of Amat and elements of dir.vec and rhs.vec where rhs.vec is Inf or -Inf97Amat <- Amat[!is.infinite(rhs.vec), ]98dir.vec <- dir.vec[!is.infinite(rhs.vec)]99rhs.vec <- rhs.vec[!is.infinite(rhs.vec)]100101# Set up the quadratic objective102if(!is.null(lambda_hhi)){103if(length(lambda_hhi) == 1 & is.null(conc_groups)){104ROI_objective <- ROI::Q_objective(Q=2*lambda*(moments$var + lambda_hhi * diag(N)), L=-moments$mean) # ROI105#Dmat <- 2*lambda*(moments$var + lambda_hhi * diag(N)) # solve.QP106#dvec <- moments$mean # solve.QP107} else if(!is.null(conc_groups)){108# construct the matrix with concentration aversion values by group109hhi_mat <- matrix(0, nrow=N, ncol=N)110vec <- 1:N111for(i in 1:length(conc_groups)){112tmpI <- diag(N)113tmpvec <- conc_groups[[i]]114zerovec <- setdiff(vec, tmpvec)115for(j in 1:length(zerovec)){116tmpI[zerovec[j], ] <- rep(0, N)117}118hhi_mat <- hhi_mat + lambda_hhi[i] * tmpI119}120ROI_objective <- ROI::Q_objective(Q=2*lambda*(moments$var + hhi_mat), L=-moments$mean) # ROI121#Dmat <- 2 * lambda * (moments$var + hhi_mat) # solve.QP122#dvec <- moments$mean # solve.QP123}124} else {125ROI_objective <- ROI::Q_objective(Q=2*lambda*moments$var, L=-moments$mean) # ROI126#Dmat <- 2 * lambda * moments$var # solve.QP127#dvec <- moments$mean # solve.QP128}129# set up the optimization problem and solve130opt.prob <- ROI::OP(objective=ROI_objective,131constraints=ROI::L_constraint(L=Amat, dir=dir.vec, rhs=rhs.vec),132bounds=bnds)133result <- try(ROI::ROI_solve(x=opt.prob, solver=solver, control=control), silent=TRUE)134135# result <- try(solve.QP(Dmat=Dmat, dvec=dvec, Amat=t(Amat), bvec=rhs.vec, meq=meq), silent=TRUE)136if(inherits(x=result, "try-error")) stop(paste("No solution found:", result))137138weights <- result$solution[1:N]139names(weights) <- colnames(R)140out <- list()141out$weights <- weights142out$out <- result$objval143obj_vals <- list()144# Calculate the objective values here so that we can use the moments$mean145# and moments$var that might be passed in by the user. This will avoid146# the extra call to constrained_objective147if(!all(moments$mean == 0)){148port.mean <- as.numeric(sum(weights * moments$mean))149names(port.mean) <- "mean"150obj_vals[["mean"]] <- port.mean151# faster and more efficient way to compute t(w) %*% Sigma %*% w152port.sd <- sqrt(sum(crossprod(weights, moments$var) * weights))153names(port.sd) <- "StdDev"154obj_vals[["StdDev"]] <- port.sd155} else {156# faster and more efficient way to compute t(w) %*% Sigma %*% w157port.sd <- sqrt(sum(crossprod(weights, moments$var) * weights))158names(port.sd) <- "StdDev"159obj_vals[["StdDev"]] <- port.sd160}161out$obj_vals <- obj_vals162# out$out <- result$objval # ROI163# out$call <- call # need to get the call outside of the function164return(out)165}166167##### Maximize Return LP Function #####168#' Maximum Return LP Optimization169#'170#' This function is called by optimize.portfolio to solve maximum return171#'172#' @param R xts object of asset returns173#' @param constraints object of constraints in the portfolio object extracted with \code{get_constraints}174#' @param moments object of moments computed based on objective functions175#' @param target target return value176#' @param solver solver to use177#' @param control list of solver control parameters178#' @author Ross Bennett179maxret_opt <- function(R, moments, constraints, target, solver="glpk", control=NULL){180stopifnot("package:ROI" %in% search() || requireNamespace("ROI",quietly = TRUE))181plugin <- paste0("ROI.plugin.", solver)182stopifnot(paste0("package:", plugin) %in% search() || requireNamespace(plugin, quietly=TRUE))183184# Check for cleaned returns in moments185if(!is.null(moments$cleanR)) R <- moments$cleanR186187N <- ncol(R)188# Applying box constraints189# maxret_opt needs non infinite values for upper and lower bounds190lb <- constraints$min191ub <- constraints$max192if(any(is.infinite(lb)) | any(is.infinite(ub))){193warning("Inf or -Inf values detected in box constraints, maximum return194objectives must have finite box constraint values.")195ub[is.infinite(ub)] <- max(abs(c(constraints$min_sum, constraints$max_sum)))196lb[is.infinite(lb)] <- 0197}198bnds <- ROI::V_bound(li=seq.int(1L, N), lb=as.numeric(lb),199ui=seq.int(1L, N), ub=as.numeric(ub))200201# set up initial A matrix for leverage constraints202Amat <- rbind(rep(1, N), rep(1, N))203dir.vec <- c(">=","<=")204rhs.vec <- c(constraints$min_sum, constraints$max_sum)205206# check for a target return207if(!is.na(target)) {208Amat <- rbind(Amat, moments$mean)209dir.vec <- c(dir.vec, "==")210rhs.vec <- c(rhs.vec, target)211}212213# include group constraints214if(try(!is.null(constraints$groups), silent=TRUE)){215n.groups <- length(constraints$groups)216Amat.group <- matrix(0, nrow=n.groups, ncol=N)217for(i in 1:n.groups){218Amat.group[i, constraints$groups[[i]]] <- 1219}220if(is.null(constraints$cLO)) cLO <- rep(-Inf, n.groups)221if(is.null(constraints$cUP)) cUP <- rep(Inf, n.groups)222Amat <- rbind(Amat, Amat.group, -Amat.group)223dir.vec <- c(dir.vec, rep(">=", (n.groups + n.groups)))224rhs.vec <- c(rhs.vec, constraints$cLO, -constraints$cUP)225}226227# Add the factor exposures to Amat, dir.vec, and rhs.vec228if(!is.null(constraints$B)){229t.B <- t(constraints$B)230Amat <- rbind(Amat, t.B, -t.B)231dir.vec <- c(dir.vec, rep(">=", 2 * nrow(t.B)))232rhs.vec <- c(rhs.vec, constraints$lower, -constraints$upper)233}234235# set up the linear objective236ROI_objective <- ROI::L_objective(L=-moments$mean)237# objL <- -moments$mean238239# set up the optimization problem and solve240opt.prob <- ROI::OP(objective=ROI_objective,241constraints=ROI::L_constraint(L=Amat, dir=dir.vec, rhs=rhs.vec),242bounds=bnds)243roi.result <- try(ROI::ROI_solve(x=opt.prob, solver=solver, control=control), silent=TRUE)244if(inherits(roi.result, "try-error")) stop(paste("No solution found:", roi.result))245246# roi.result <- Rglpk_solve_LP(obj=objL, mat=Amat, dir=dir.vec, rhs=rhs.vec, bounds=bnds)247248# The Rglpk solvers status returns an an integer with status information249# about the solution returned: 0 if the optimal solution was found, a250#non-zero value otherwise.251if(roi.result$status$code != 0) {252message(roi.result$status$msg$message)253stop("No solution")254return(NULL)255}256257weights <- roi.result$solution[1:N]258names(weights) <- colnames(R)259out <- list()260out$weights <- weights261out$out <- roi.result$objval262obj_vals <- list()263# Calculate the objective values here so that we can use the moments$mean264# that might be passed in by the user. This will avoid265# the extra call to constrained_objective266port.mean <- -roi.result$objval267names(port.mean) <- "mean"268obj_vals[["mean"]] <- port.mean269out$obj_vals <- obj_vals270# out$call <- call # need to get the call outside of the function271return(out)272}273274##### Maximize Return MILP Function #####275#' Maximum Return MILP Optimization276#'277#' This function is called by optimize.portfolio to solve maximum return278#' problems via mixed integer linear programming.279#'280#' @param R xts object of asset returns281#' @param constraints object of constraints in the portfolio object extracted with \code{get_constraints}282#' @param moments object of moments computed based on objective functions283#' @param target target return value284#' @param solver solver to use285#' @param control list of solver control parameters286#' @author Ross Bennett287maxret_milp_opt <- function(R, constraints, moments, target, solver="glpk", control=NULL){288stopifnot("package:ROI" %in% search() || requireNamespace("ROI",quietly = TRUE))289plugin <- paste0("ROI.plugin.", solver)290stopifnot(paste0("package:", plugin) %in% search() || requireNamespace(plugin, quietly=TRUE))291292# Check for cleaned returns in moments293if(!is.null(moments$cleanR)) R <- moments$cleanR294295# Number of assets296N <- ncol(R)297298# Maximum number of positions (non-zero weights)299max_pos <- constraints$max_pos300min_pos <- 1301302# Upper and lower bounds on weights303LB <- as.numeric(constraints$min)304UB <- as.numeric(constraints$max)305306# Check for target return307if(!is.na(target)){308# We have a target309targetcon <- rbind(c(moments$mean, rep(0, N)),310c(-moments$mean, rep(0, N)))311targetdir <- c("<=", "==")312targetrhs <- c(Inf, -target)313} else {314# No target specified, just maximize315targetcon <- NULL316targetdir <- NULL317targetrhs <- NULL318}319320# weight_sum constraint321Amat <- rbind(c(rep(1, N), rep(0, N)),322c(rep(1, N), rep(0, N)))323324# Target return constraint325Amat <- rbind(Amat, targetcon)326327# Bounds and position limit constraints328Amat <- rbind(Amat, cbind(-diag(N), diag(LB)))329Amat <- rbind(Amat, cbind(diag(N), -diag(UB)))330Amat <- rbind(Amat, c(rep(0, N), rep(-1, N)))331Amat <- rbind(Amat, c(rep(0, N), rep(1, N)))332333dir <- c("<=", ">=", targetdir, rep("<=", 2*N), "<=", "<=")334rhs <- c(1, 1, targetrhs, rep(0, 2*N), -min_pos, max_pos)335336# Include group constraints337if(try(!is.null(constraints$groups), silent=TRUE)){338n.groups <- length(constraints$groups)339Amat.group <- matrix(0, nrow=n.groups, ncol=N)340k <- 1341l <- 0342for(i in 1:n.groups){343j <- constraints$groups[i]344Amat.group[i, k:(l+j)] <- 1345k <- l + j + 1346l <- k - 1347}348if(is.null(constraints$cLO)) cLO <- rep(-Inf, n.groups)349if(is.null(constraints$cUP)) cUP <- rep(Inf, n.groups)350zeros <- matrix(data=0, nrow=nrow(Amat.group), ncol=ncol(Amat.group))351Amat <- rbind(Amat, cbind(Amat.group, zeros), cbind(-Amat.group, zeros))352dir <- c(dir, rep(">=", (n.groups + n.groups)))353rhs <- c(rhs, constraints$cLO, -constraints$cUP)354}355356# Add the factor exposures to Amat, dir, and rhs357if(!is.null(constraints$B)){358t.B <- t(constraints$B)359zeros <- matrix(data=0, nrow=nrow(t.B), ncol=ncol(t.B))360Amat <- rbind(Amat, cbind(t.B, zeros), cbind(-t.B, zeros))361dir <- c(dir, rep(">=", 2 * nrow(t.B)))362rhs <- c(rhs, constraints$lower, -constraints$upper)363}364365# Only seems to work if I do not specify bounds366# bnds <- ROI::V_Bound(li=seq.int(1L, 2*m), lb=c(as.numeric(constraints$min), rep(0, m)),367# ui=seq.int(1L, 2*m), ub=c(as.numeric(constraints$max), rep(1, m)))368bnds <- NULL369370# Set up the types vector with continuous and binary variables371types <- c(rep("C", N), rep("B", N))372373# Set up the linear objective to maximize mean return374ROI_objective <- ROI::L_objective(L=c(-moments$mean, rep(0, N)))375376# Set up the optimization problem and solve377opt.prob <- ROI::OP(objective=ROI_objective,378constraints=ROI::L_constraint(L=Amat, dir=dir, rhs=rhs),379bounds=bnds, types=types)380roi.result <- try(ROI::ROI_solve(x=opt.prob, solver=solver, control=control), silent=TRUE)381if(inherits(roi.result, "try-error")) stop(paste("No solution found:", roi.result))382383# Weights384weights <- roi.result$solution[1:N]385names(weights) <- colnames(R)386387# The out object is returned388out <- list()389out$weights <- weights390out$out <- roi.result$objval391392obj_vals <- list()393port.mean <- -roi.result$objval394names(port.mean) <- "mean"395obj_vals[["mean"]] <- port.mean396out$obj_vals <- obj_vals397return(out)398}399400##### Minimize ETL LP Function #####401#' Minimum ETL LP Optimization402#'403#' This function is called by optimize.portfolio to solve minimum ETL problems.404#'405#' @param R xts object of asset returns406#' @param constraints object of constraints in the portfolio object extracted with \code{get_constraints}407#' @param moments object of moments computed based on objective functions408#' @param target target return value409#' @param alpha alpha value for ETL/ES/CVaR410#' @param solver solver to use411#' @param control list of solver control parameters412#' @author Ross Bennett413etl_opt <- function(R, constraints, moments, target, alpha, solver="glpk", control=NULL){414stopifnot("package:ROI" %in% search() || requireNamespace("ROI",quietly = TRUE))415plugin <- paste0("ROI.plugin.", solver)416stopifnot(paste0("package:", plugin) %in% search() || requireNamespace(plugin, quietly=TRUE))417418# Check for cleaned returns in moments419if(!is.null(moments$cleanR)) R <- moments$cleanR420421N <- ncol(R)422T <- nrow(R)423# Applying box constraints424LB <- c(as.numeric(constraints$min), rep(0, T), -1)425UB <- c(as.numeric(constraints$max), rep(Inf, T), 1)426bnds <- ROI::V_bound(li=seq.int(1L, N+T+1), lb=LB,427ui=seq.int(1L, N+T+1), ub=UB)428429# Add this check if mean is not an objective and return is a constraints430if(!is.na(target)){431if(all(moments$mean == 0)){432moments$mean <- colMeans(R)433}434} else {435moments$mean <- rep(0, N)436target <- 0437}438439Amat <- cbind(rbind(1, 1, moments$mean, coredata(R)), rbind(0, 0, 0, cbind(diag(T), 1)))440dir.vec <- c(">=","<=",">=",rep(">=",T))441rhs.vec <- c(constraints$min_sum, constraints$max_sum, target ,rep(0, T))442443if(try(!is.null(constraints$groups), silent=TRUE)){444n.groups <- length(constraints$groups)445Amat.group <- matrix(0, nrow=n.groups, ncol=N)446for(i in 1:n.groups){447Amat.group[i, constraints$groups[[i]]] <- 1448}449if(is.null(constraints$cLO)) cLO <- rep(-Inf, n.groups)450if(is.null(constraints$cUP)) cUP <- rep(Inf, n.groups)451zeros <- matrix(0, nrow=n.groups, ncol=(T+1))452Amat <- rbind(Amat, cbind(Amat.group, zeros), cbind(-Amat.group, zeros))453dir.vec <- c(dir.vec, rep(">=", (n.groups + n.groups)))454rhs.vec <- c(rhs.vec, constraints$cLO, -constraints$cUP)455}456# Add the factor exposures to Amat, dir, and rhs457if(!is.null(constraints$B)){458t.B <- t(constraints$B)459zeros <- matrix(data=0, nrow=nrow(t.B), ncol=(T+1))460Amat <- rbind(Amat, cbind(t.B, zeros), cbind(-t.B, zeros))461dir.vec <- c(dir.vec, rep(">=", 2 * nrow(t.B)))462rhs.vec <- c(rhs.vec, constraints$lower, -constraints$upper)463}464465ROI_objective <- ROI::L_objective(c(rep(0,N), rep(1/(alpha*T),T), 1))466opt.prob <- ROI::OP(objective=ROI_objective,467constraints=ROI::L_constraint(L=Amat, dir=dir.vec, rhs=rhs.vec),468bounds=bnds)469roi.result <- try(ROI::ROI_solve(x=opt.prob, solver=solver, control=control), silent=TRUE)470if(inherits(x=roi.result, "try-error")) stop(paste("No solution found:", roi.result))471472weights <- roi.result$solution[1:N]473names(weights) <- colnames(R)474out <- list()475out$weights <- weights476out$out <- roi.result$objval477es_names <- c("ES", "ETL", "CVaR")478es_idx <- which(es_names %in% names(moments))479obj_vals <- list()480# Calculate the objective values here so that we can use the moments$mean481# and moments$var that might be passed in by the user. This will avoid482# the extra call to constrained_objective483if(!all(moments$mean == 0)){484port.mean <- as.numeric(sum(weights * moments$mean))485names(port.mean) <- "mean"486obj_vals[["mean"]] <- port.mean487port.es <- roi.result$objval488names(port.es) <- es_names[es_idx]489obj_vals[[es_names[es_idx]]] <- port.es490} else {491port.es <- roi.result$objval492names(port.es) <- es_names[es_idx]493obj_vals[[es_names[es_idx]]] <- port.es494}495out$obj_vals <- obj_vals496return(out)497}498499##### Minimize ETL MILP Function #####500#' Minimum ETL MILP Optimization501#'502#' This function is called by optimize.portfolio to solve minimum ETL problems503#' via mixed integer linear programming.504#'505#' @param R xts object of asset returns506#' @param constraints object of constraints in the portfolio object extracted with \code{get_constraints}507#' @param moments object of moments computed based on objective functions508#' @param target target return value509#' @param alpha alpha value for ETL/ES/CVaR510#' @param solver solver to use511#' @param control list of solver control parameters512#' @author Ross Bennett513etl_milp_opt <- function(R, constraints, moments, target, alpha, solver="glpk", control=NULL){514stopifnot("package:ROI" %in% search() || requireNamespace("ROI",quietly = TRUE))515plugin <- paste0("ROI.plugin.", solver)516stopifnot(paste0("package:", plugin) %in% search() || requireNamespace(plugin, quietly=TRUE))517518# Check for cleaned returns in moments519if(!is.null(moments$cleanR)) R <- moments$cleanR520521# Number of rows522n <- nrow(R)523524# Number of columns525m <- ncol(R)526527max_sum <- constraints$max_sum528min_sum <- constraints$min_sum529LB <- constraints$min530UB <- constraints$max531max_pos <- constraints$max_pos532min_pos <- 1533moments_mean <- as.numeric(moments$mean)534535# A benchmark can be specified in the parma package.536# Leave this in and set to 0 for now537benchmark <- 0538539# Check for target return540if(!is.na(target)){541# We have a target542targetcon <- c(moments_mean, rep(0, n+2))543targetdir <- "=="544targetrhs <- target545} else {546# No target specified, just maximize547targetcon <- NULL548targetdir <- NULL549targetrhs <- NULL550}551552# Set up initial A matrix553tmpAmat <- cbind(-coredata(R),554matrix(-1, nrow=n, ncol=1),555-diag(n),556matrix(benchmark, nrow=n, ncol=1))557558# Add leverage constraints to matrix559tmpAmat <- rbind(tmpAmat, rbind(c(rep(1, m), rep(0, n+2)),560c(rep(1, m), rep(0, n+2))))561562# Add target return to matrix563tmpAmat <- rbind(tmpAmat, as.numeric(targetcon))564565# This step just adds m rows to the matrix to accept box constraints in the next step566tmpAmat <- cbind(tmpAmat, matrix(0, ncol=m, nrow=dim(tmpAmat)[1]))567568# Add lower bound box constraints569tmpAmat <- rbind(tmpAmat, cbind(-diag(m), matrix(0, ncol=n+2, nrow=m), diag(LB)))570571# Add upper bound box constraints572tmpAmat <- rbind(tmpAmat, cbind(diag(m), matrix(0, ncol=n+2, nrow=m), diag(-UB)))573574# Add rows cardinality constraints575tmpAmat <- rbind(tmpAmat, cbind(matrix(0, ncol=m + n + 2, nrow=1), matrix(-1, ncol=m, nrow=1)))576tmpAmat <- rbind(tmpAmat, cbind(matrix(0, ncol=m + n + 2, nrow=1), matrix(1, ncol=m, nrow=1)))577578# Set up the rhs vector579rhs <- c( rep(0, n), min_sum, max_sum, targetrhs, rep(0, 2*m), -min_pos, max_pos)580581# Set up the dir vector582dir <- c( rep("<=", n), ">=", "<=", targetdir, rep("<=", 2*m), "<=", "<=")583584if(try(!is.null(constraints$groups), silent=TRUE)){585n.groups <- length(constraints$groups)586Amat.group <- matrix(0, nrow=n.groups, ncol=m)587for(i in 1:n.groups){588Amat.group[i, constraints$groups[[i]]] <- 1589}590if(is.null(constraints$cLO)) cLO <- rep(-Inf, n.groups)591if(is.null(constraints$cUP)) cUP <- rep(Inf, n.groups)592zeros <- matrix(0, nrow=n.groups, ncol=(m + n + 2))593tmpAmat <- rbind(tmpAmat, cbind(Amat.group, zeros), cbind(-Amat.group, zeros))594dir <- c(dir, rep(">=", (n.groups + n.groups)))595rhs <- c(rhs, constraints$cLO, -constraints$cUP)596}597598# Add the factor exposures to Amat, dir, and rhs599if(!is.null(constraints$B)){600t.B <- t(constraints$B)601zeros <- matrix(data=0, nrow=nrow(t.B), ncol=(m + n + 2))602tmpAmat <- rbind(tmpAmat, cbind(t.B, zeros), cbind(-t.B, zeros))603dir <- c(dir, rep(">=", 2 * nrow(t.B)))604rhs <- c(rhs, constraints$lower, -constraints$upper)605}606607# Linear objective vector608ROI_objective <- ROI::L_objective(c( rep(0, m), 1, rep(1/n, n) / alpha, 0, rep(0, m)))609610# Set up the types vector with continuous and binary variables611types <- c( rep("C", m), "C", rep("C", n), "C", rep("B", m))612613bnds <- ROI::V_bound( li = 1L:(m + n + 2 + m), lb = c(LB, -1, rep(0, n), 1, rep(0, m)),614ui = 1L:(m + n + 2 + m), ub = c(UB, 1, rep(Inf, n), 1, rep(1, m)))615616# Set up the optimization problem and solve617opt.prob <- ROI::OP(objective=ROI_objective,618constraints=ROI::L_constraint(L=tmpAmat, dir=dir, rhs=rhs),619bounds=bnds, types=types)620roi.result <- try(ROI::ROI_solve(x=opt.prob, solver=solver, control=control), silent=TRUE)621if(inherits(roi.result, "try-error")) stop(paste("No solution found:", roi.result))622623# The Rglpk solvers status returns an an integer with status information624# about the solution returned: 0 if the optimal solution was found, a625#non-zero value otherwise.626if(roi.result$status$code != 0) {627message("Undefined Solution")628return(NULL)629}630631weights <- roi.result$solution[1:m]632names(weights) <- colnames(R)633out <- list()634out$weights <- weights635out$out <- roi.result$objval636es_names <- c("ES", "ETL", "CVaR")637es_idx <- which(es_names %in% names(moments))638obj_vals <- list()639# Calculate the objective values here so that we can use the moments$mean640# and moments$var that might be passed in by the user.641if(!all(moments$mean == 0)){642port.mean <- as.numeric(sum(weights * moments$mean))643names(port.mean) <- "mean"644obj_vals[["mean"]] <- port.mean645port.es <- roi.result$objval646names(port.es) <- es_names[es_idx]647obj_vals[[es_names[es_idx]]] <- port.es648} else {649port.es <- roi.result$objval650names(port.es) <- es_names[es_idx]651obj_vals[[es_names[es_idx]]] <- port.es652}653out$obj_vals <- obj_vals654return(out)655}656657##### minimize variance or maximize quadratic utility with turnover constraints #####658#' GMV/QU QP Optimization with Turnover Constraint659#'660#' This function is called by optimize.portfolio to solve minimum variance or661#' maximum quadratic utility problems with turnover constraint662#'663#' @param R xts object of asset returns664#' @param constraints object of constraints in the portfolio object extracted with \code{get_constraints}665#' @param moments object of moments computed based on objective functions666#' @param lambda risk_aversion parameter667#' @param target target return value668#' @param init_weights initial weights to compute turnover669#' @param solver solver to use670#' @param control list of solver control parameters671#' @author Ross Bennett672gmv_opt_toc <- function(R, constraints, moments, lambda, target, init_weights, solver="quadprog", control=NULL){673# function for minimum variance or max quadratic utility problems674stopifnot("package:corpcor" %in% search() || requireNamespace("corpcor",quietly = TRUE))675stopifnot("package:ROI" %in% search() || requireNamespace("ROI", quietly = TRUE))676plugin <- paste0("ROI.plugin.", solver)677stopifnot(paste0("package:", plugin) %in% search() || requireNamespace(plugin, quietly=TRUE))678679# Check for cleaned returns in moments680if(!is.null(moments$cleanR)) R <- moments$cleanR681682# Modify the returns matrix. This is done because there are 3 sets of683# variables 1) w.initial, 2) w.buy, and 3) w.sell684R0 <- matrix(0, ncol=ncol(R), nrow=nrow(R))685returns <- cbind(R, R0, R0)686V <- cov(returns)687688# number of assets689N <- ncol(R)690691# initial weights for solver692if(is.null(init_weights)) init_weights <- rep(1/ N, N)693694# check for a target return constraint695if(!is.na(target)) {696# If var is the only objective specified, then moments$mean won't be calculated697if(all(moments$mean==0)){698tmp_means <- colMeans(R)699} else {700tmp_means <- moments$mean701}702} else {703tmp_means <- rep(0, N)704target <- 0705}706Amat <- c(tmp_means, rep(0, 2*N))707dir <- "=="708rhs <- target709meq <- N + 1710711# Amat for initial weights712# Amat <- cbind(diag(N), matrix(0, nrow=N, ncol=N*2))713Amat <- rbind(Amat, cbind(diag(N), -1*diag(N), diag(N)))714rhs <- c(rhs, init_weights)715dir <- c(dir, rep("==", N))716717# Amat for turnover constraints718Amat <- rbind(Amat, c(rep(0, N), rep(-1, N), rep(-1, N)))719rhs <- c(rhs, -constraints$turnover_target)720dir <- c(dir, ">=")721722# Amat for positive weights723Amat <- rbind(Amat, cbind(matrix(0, nrow=N, ncol=N), diag(N), matrix(0, nrow=N, ncol=N)))724rhs <- c(rhs, rep(0, N))725dir <- c(dir, rep(">=", N))726727# Amat for negative weights728Amat <- rbind(Amat, cbind(matrix(0, nrow=N, ncol=2*N), diag(N)))729rhs <- c(rhs, rep(0, N))730dir <- c(dir, rep(">=", N))731732# Amat for full investment constraint733Amat <- rbind(Amat, rbind(c(rep(1, N), rep(0,2*N)), c(rep(-1, N), rep(0,2*N))))734rhs <- c(rhs, constraints$min_sum, -constraints$max_sum)735dir <- c(dir, ">=", ">=")736737# Amat for lower box constraints738Amat <- rbind(Amat, cbind(diag(N), diag(0, N), diag(0, N)))739rhs <- c(rhs, constraints$min)740dir <- c(dir, rep(">=", N))741742# Amat for upper box constraints743Amat <- rbind(Amat, cbind(-diag(N), diag(0, N), diag(0, N)))744rhs <- c(rhs, -constraints$max)745dir <- c(dir, rep(">=", N))746747# include group constraints748if(try(!is.null(constraints$groups), silent=TRUE)){749n.groups <- length(constraints$groups)750Amat.group <- matrix(0, nrow=n.groups, ncol=N)751zeros <- matrix(0, nrow=n.groups, ncol=N)752for(i in 1:n.groups){753Amat.group[i, constraints$groups[[i]]] <- 1754}755if(is.null(constraints$cLO)) cLO <- rep(-Inf, n.groups)756if(is.null(constraints$cUP)) cUP <- rep(Inf, n.groups)757Amat <- rbind(Amat, cbind(Amat.group, zeros, zeros))758Amat <- rbind(Amat, cbind(-Amat.group, zeros, zeros))759dir <- c(dir, rep(">=", (n.groups + n.groups)))760rhs <- c(rhs, constraints$cLO, -constraints$cUP)761}762763# Add the factor exposures to Amat, dir, and rhs764if(!is.null(constraints$B)){765t.B <- t(constraints$B)766zeros <- matrix(0, nrow=nrow(t.B), ncol=ncol(t.B))767Amat <- rbind(Amat, cbind(t.B, zeros, zeros))768Amat <- rbind(Amat, cbind(-t.B, zeros, zeros))769dir <- c(dir, rep(">=", 2 * nrow(t.B)))770rhs <- c(rhs, constraints$lower, -constraints$upper)771}772773# Remove the rows of Amat and elements of rhs.vec where rhs is Inf or -Inf774Amat <- Amat[!is.infinite(rhs), ]775rhs <- rhs[!is.infinite(rhs)]776dir <- dir[!is.infinite(rhs)]777778ROI_objective <- ROI::Q_objective(Q=corpcor::make.positive.definite(2*lambda*V),779L=rep(-tmp_means, 3))780781opt.prob <- ROI::OP(objective=ROI_objective,782constraints=ROI::L_constraint(L=Amat, dir=dir, rhs=rhs))783784roi.result <- try(ROI::ROI_solve(x=opt.prob, solver=solver, control=control), silent=TRUE)785if(inherits(roi.result, "try-error")) stop(paste("No solution found:", roi.result))786787wts <- roi.result$solution788wts.final <- wts[1:N]789790weights <- wts.final791names(weights) <- colnames(R)792out <- list()793out$weights <- weights794out$out <- roi.result$objval795obj_vals <- list()796# Calculate the objective values here so that we can use the moments$mean797# and moments$var that might be passed in by the user.798if(!all(moments$mean == 0)){799port.mean <- as.numeric(sum(weights * moments$mean))800names(port.mean) <- "mean"801obj_vals[["mean"]] <- port.mean802# faster and more efficient way to compute t(w) %*% Sigma %*% w803port.sd <- sqrt(sum(crossprod(weights, moments$var) * weights))804names(port.sd) <- "StdDev"805obj_vals[["StdDev"]] <- port.sd806} else {807# faster and more efficient way to compute t(w) %*% Sigma %*% w808port.sd <- sqrt(sum(crossprod(weights, moments$var) * weights))809names(port.sd) <- "StdDev"810obj_vals[["StdDev"]] <- port.sd811}812out$obj_vals <- obj_vals813return(out)814}815816##### minimize variance or maximize quadratic utility with proportional transactioncosts constraints #####817#' GMV/QU QP Optimization with Proportional Transaction Cost Constraint818#'819#' This function is called by optimize.portfolio to solve minimum variance or820#' maximum quadratic utility problems with proportional transaction cost constraint821#'822#' @param R xts object of asset returns823#' @param constraints object of constraints in the portfolio object extracted with \code{get_constraints}824#' @param moments object of moments computed based on objective functions825#' @param lambda risk_aversion parameter826#' @param target target return value827#' @param init_weights initial weights to compute turnover828#' @param solver solver to use829#' @param control list of solver control parameters830#' @author Ross Bennett831gmv_opt_ptc <- function(R, constraints, moments, lambda, target, init_weights, solver="quadprog", control=NULL){832# function for minimum variance or max quadratic utility problems833# modifying ProportionalCostOpt function from MPO package834stopifnot("package:corpcor" %in% search() || requireNamespace("corpcor", quietly = TRUE))835stopifnot("package:ROI" %in% search() || requireNamespace("ROI", quietly = TRUE))836plugin <- paste0("ROI.plugin.", solver)837stopifnot(paste0("package:", plugin) %in% search() || requireNamespace(plugin, quietly=TRUE))838839# Check for cleaned returns in moments840if(!is.null(moments$cleanR)) R <- moments$cleanR841842# proportional transaction costs843ptc <- constraints$ptc844845# Modify the returns matrix. This is done because there are 3 sets of846# variables 1) w.initial, 2) w.buy, and 3) w.sell847R0 <- matrix(0, ncol=ncol(R), nrow=nrow(R))848returns <- cbind(R, R0, R0)849V <- cov(returns)850851# number of assets852N <- ncol(R)853854# initial weights for solver855if(is.null(init_weights)) init_weights <- rep(1/ N, N)856857# Check for a target return constraint858if(!is.na(target)) {859# If var is the only objective specified, then moments$mean won't be calculated860if(all(moments$mean==0)){861tmp_means <- colMeans(R)862} else {863tmp_means <- moments$mean864}865} else {866tmp_means <- rep(0, N)867target <- 0868}869Amat <- c(tmp_means, rep(0, 2 * N))870dir <- "=="871rhs <- 1 + target872meq <- 1873874# separate the weights into w, w^+, and w^-875# w - w^+ + w^- = 0876Amat <- rbind(Amat, cbind(diag(N), -diag(N), diag(N)))877rhs <- c(rhs, init_weights)878dir <- c(dir, rep("==", N))879meq <- N + 1880881# w+ >= 0882Amat <- rbind(Amat, cbind(diag(0, N), diag(N), diag(0, N)))883rhs <- c(rhs, rep(0, N))884dir <- c(dir, rep(">=", N))885886# w- >= 0887Amat <- rbind(Amat, cbind(diag(0, N), diag(0, N), diag(N)))888rhs <- c(rhs, rep(0, N))889dir <- c(dir, rep(">=", N))890891# 1^T w + tcb^T w^+ + tcs^T w^- >= min_sum892Amat <- rbind(Amat, c(rep(1, N), ptc, ptc))893rhs <- c(rhs, constraints$min_sum)894dir <- c(dir, ">=")895896# 1^T w + tcb^T w^+ + tcs^T w^- <= max_sum897Amat <- rbind(Amat, c(rep(-1, N), -ptc, -ptc))898rhs <- c(rhs, -constraints$max_sum)899dir <- c(dir, ">=")900901# -(1 + tcb)^T w^+ + (1 - tcs)^T w^- >= 0902Amat <- rbind(Amat, c(rep(0, N), -(1 + ptc), (1 - ptc)))903rhs <- c(rhs, 0)904dir <- c(dir, ">=")905906# Amat for lower box constraints907Amat <- rbind(Amat, cbind(diag(N), diag(N), diag(N)))908rhs <- c(rhs, constraints$min)909dir <- c(dir, rep(">=", N))910911# Amat for upper box constraints912Amat <- rbind(Amat, cbind(-diag(N), -diag(N), -diag(N)))913rhs <- c(rhs, -constraints$max)914dir <- c(dir, rep(">=", N))915916# include group constraints917if(try(!is.null(constraints$groups), silent=TRUE)){918n.groups <- length(constraints$groups)919Amat.group <- matrix(0, nrow=n.groups, ncol=N)920for(i in 1:n.groups){921Amat.group[i, constraints$groups[[i]]] <- 1922}923if(is.null(constraints$cLO)) cLO <- rep(-Inf, n.groups)924if(is.null(constraints$cUP)) cUP <- rep(Inf, n.groups)925Amat <- rbind(Amat, cbind(Amat.group, Amat.group, Amat.group))926Amat <- rbind(Amat, cbind(-Amat.group, -Amat.group, -Amat.group))927dir <- c(dir, rep(">=", (n.groups + n.groups)))928rhs <- c(rhs, constraints$cLO, -constraints$cUP)929}930931# Add the factor exposures to Amat, dir, and rhs932if(!is.null(constraints$B)){933t.B <- t(constraints$B)934Amat <- rbind(Amat, cbind(t.B, t.B, t.B))935Amat <- rbind(Amat, cbind(-t.B, -t.B, -t.B))936dir <- c(dir, rep(">=", 2 * nrow(t.B)))937rhs <- c(rhs, constraints$lower, -constraints$upper)938}939d <- c(-tmp_means, rep(0, 2 * N))940941# Remove the rows of Amat and elements of rhs where rhs is Inf or -Inf942Amat <- Amat[!is.infinite(rhs), ]943rhs <- rhs[!is.infinite(rhs)]944dir <- dir[!is.infinite(rhs)]945946ROI_objective <- ROI::Q_objective(Q=corpcor::make.positive.definite(2*lambda*V), L=d)947948opt.prob <- ROI::OP(objective=ROI_objective,949constraints=ROI::L_constraint(L=Amat, dir=dir, rhs=rhs))950roi.result <- try(ROI::ROI_solve(x=opt.prob, solver=solver, control=control), silent=TRUE)951if(inherits(roi.result, "try-error")) stop(paste("No solution found:", roi.result))952953wts <- roi.result$solution954weights <- wts[1:N]955names(weights) <- colnames(R)956out <- list()957out$weights <- weights958out$out <- roi.result$objval959obj_vals <- list()960# Calculate the objective values here so that we can use the moments$mean961# and moments$var that might be passed in by the user.962if(!all(moments$mean == 0)){963port.mean <- as.numeric(sum(weights * moments$mean))964names(port.mean) <- "mean"965obj_vals[["mean"]] <- port.mean966# faster and more efficient way to compute t(w) %*% Sigma %*% w967port.sd <- sqrt(sum(crossprod(weights, moments$var) * weights))968names(port.sd) <- "StdDev"969obj_vals[["StdDev"]] <- port.sd970} else {971# faster and more efficient way to compute t(w) %*% Sigma %*% w972port.sd <- sqrt(sum(crossprod(weights, moments$var) * weights))973names(port.sd) <- "StdDev"974obj_vals[["StdDev"]] <- port.sd975}976out$obj_vals <- obj_vals977return(out)978}979980##### minimize variance or maximize quadratic utility with leverage constraints #####981#' GMV/QU QP Optimization with Turnover Constraint982#'983#' This function is called by optimize.portfolio to solve minimum variance or984#' maximum quadratic utility problems with a leverage constraint985#'986#' @param R xts object of asset returns987#' @param constraints object of constraints in the portfolio object extracted with \code{get_constraints}988#' @param moments object of moments computed based on objective functions989#' @param lambda risk_aversion parameter990#' @param target target return value991#' @param solver solver to use992#' @param control list of solver control parameters993#' @author Ross Bennett994gmv_opt_leverage <- function(R, constraints, moments, lambda, target, solver="quadprog", control=NULL){995# function for minimum variance or max quadratic utility problems996stopifnot("package:corpcor" %in% search() || requireNamespace("corpcor",quietly = TRUE))997stopifnot("package:ROI" %in% search() || requireNamespace("ROI", quietly = TRUE))998plugin <- paste0("ROI.plugin.", solver)999stopifnot(paste0("package:", plugin) %in% search() || requireNamespace(plugin, quietly=TRUE))10001001# Check for cleaned returns in moments1002if(!is.null(moments$cleanR)) R <- moments$cleanR10031004# Modify the returns matrix. This is done because there are 3 sets of1005# variables 1) w.initial, 2) w.buy, and 3) w.sell1006R0 <- matrix(0, ncol=ncol(R), nrow=nrow(R))1007returns <- cbind(R, R0, R0)1008V <- cov(returns)10091010# number of assets1011N <- ncol(R)10121013# check for a target return constraint1014if(!is.na(target)) {1015# If var is the only objective specified, then moments$mean won't be calculated1016if(all(moments$mean==0)){1017tmp_means <- colMeans(R)1018} else {1019tmp_means <- moments$mean1020}1021} else {1022tmp_means <- rep(0, N)1023target <- 01024}1025Amat <- c(tmp_means, rep(0, 2*N))1026dir <- "=="1027rhs <- target1028# meq <- N + 110291030# separate the weights into w, w+, and w-1031# w - w+ + w- = 01032Amat <- rbind(Amat, cbind(diag(N), -1*diag(N), diag(N)))1033rhs <- c(rhs, rep(0, N))1034dir <- c(dir, rep("==", N))10351036# Amat for leverage constraints1037Amat <- rbind(Amat, c(rep(0, N), rep(-1, N), rep(-1, N)))1038rhs <- c(rhs, -constraints$leverage)1039dir <- c(dir, ">=")10401041# Amat for positive weights1042Amat <- rbind(Amat, cbind(matrix(0, nrow=N, ncol=N), diag(N), matrix(0, nrow=N, ncol=N)))1043rhs <- c(rhs, rep(0, N))1044dir <- c(dir, rep(">=", N))10451046# Amat for negative weights1047Amat <- rbind(Amat, cbind(matrix(0, nrow=N, ncol=2*N), diag(N)))1048rhs <- c(rhs, rep(0, N))1049dir <- c(dir, rep(">=", N))10501051# Amat for full investment constraint1052Amat <- rbind(Amat, rbind(c(rep(1, N), rep(0,2*N)),1053c(rep(-1, N), rep(0,2*N))))1054rhs <- c(rhs, constraints$min_sum, -constraints$max_sum)1055dir <- c(dir, ">=", ">=")10561057# Amat for lower box constraints1058Amat <- rbind(Amat, cbind(diag(N), diag(0, N), diag(0, N)))1059rhs <- c(rhs, constraints$min)1060dir <- c(dir, rep(">=", N))10611062# Amat for upper box constraints1063Amat <- rbind(Amat, cbind(-diag(N), diag(0, N), diag(0, N)))1064rhs <- c(rhs, -constraints$max)1065dir <- c(dir, rep(">=", N))10661067# include group constraints1068if(try(!is.null(constraints$groups), silent=TRUE)){1069n.groups <- length(constraints$groups)1070Amat.group <- matrix(0, nrow=n.groups, ncol=N)1071zeros <- matrix(0, nrow=n.groups, ncol=N)1072for(i in 1:n.groups){1073Amat.group[i, constraints$groups[[i]]] <- 11074}1075if(is.null(constraints$cLO)) cLO <- rep(-Inf, n.groups)1076if(is.null(constraints$cUP)) cUP <- rep(Inf, n.groups)1077Amat <- rbind(Amat, cbind(Amat.group, zeros, zeros))1078Amat <- rbind(Amat, cbind(-Amat.group, zeros, zeros))1079dir <- c(dir, rep(">=", (n.groups + n.groups)))1080rhs <- c(rhs, constraints$cLO, -constraints$cUP)1081}10821083# Add the factor exposures to Amat, dir, and rhs1084if(!is.null(constraints$B)){1085t.B <- t(constraints$B)1086zeros <- matrix(0, nrow=nrow(t.B), ncol=ncol(t.B))1087Amat <- rbind(Amat, cbind(t.B, zeros, zeros))1088Amat <- rbind(Amat, cbind(-t.B, zeros, zeros))1089dir <- c(dir, rep(">=", 2 * nrow(t.B)))1090rhs <- c(rhs, constraints$lower, -constraints$upper)1091}10921093# Remove the rows of Amat and elements of rhs.vec where rhs is Inf or -Inf1094Amat <- Amat[!is.infinite(rhs), ]1095rhs <- rhs[!is.infinite(rhs)]1096dir <- dir[!is.infinite(rhs)]10971098ROI_objective <- ROI::Q_objective(Q=corpcor::make.positive.definite(2*lambda*V),1099L=rep(-tmp_means, 3))11001101opt.prob <- ROI::OP(objective=ROI_objective,1102constraints=ROI::L_constraint(L=Amat, dir=dir, rhs=rhs))11031104roi.result <- try(ROI::ROI_solve(x=opt.prob, solver=solver, control=control), silent=TRUE)1105if(inherits(roi.result, "try-error")) stop(paste("No solution found:", roi.result))11061107wts <- roi.result$solution1108wts.final <- wts[1:N]11091110weights <- wts.final1111names(weights) <- colnames(R)1112out <- list()1113out$weights <- weights1114out$out <- roi.result$objval1115obj_vals <- list()1116# Calculate the objective values here so that we can use the moments$mean1117# and moments$var that might be passed in by the user.1118if(!all(moments$mean == 0)){1119port.mean <- as.numeric(sum(weights * moments$mean))1120names(port.mean) <- "mean"1121obj_vals[["mean"]] <- port.mean1122# faster and more efficient way to compute t(w) %*% Sigma %*% w1123port.sd <- sqrt(sum(crossprod(weights, moments$var) * weights))1124names(port.sd) <- "StdDev"1125obj_vals[["StdDev"]] <- port.sd1126} else {1127# faster and more efficient way to compute t(w) %*% Sigma %*% w1128port.sd <- sqrt(sum(crossprod(weights, moments$var) * weights))1129names(port.sd) <- "StdDev"1130obj_vals[["StdDev"]] <- port.sd1131}1132out$obj_vals <- obj_vals1133return(out)1134}11351136# This function uses optimize() to find the target return value that1137# results in the maximum starr ratio (mean / ES).1138# returns the target return value1139mean_etl_opt <- function(R, constraints, moments, alpha, solver, control){1140# create a copy of the moments that can be modified1141tmp_moments <- moments11421143# Find the maximum return1144if(!is.null(constraints$max_pos)){1145max_ret <- maxret_milp_opt(R=R, constraints=constraints, moments=moments, target=NA, solver=solver, control=control)1146} else {1147max_ret <- maxret_opt(R=R, moments=moments, constraints=constraints, target=NA, solver=solver, control=control)1148}1149max_mean <- as.numeric(-max_ret$out)11501151# Find the minimum return1152tmp_moments$mean <- -1 * moments$mean1153if(!is.null(constraints$max_pos)){1154min_ret <- maxret_milp_opt(R=R, constraints=constraints, moments=tmp_moments, target=NA, solver=solver, control=control)1155} else {1156min_ret <- maxret_opt(R=R, constraints=constraints, moments=tmp_moments, target=NA, solver=solver, control=control)1157}1158min_mean <- as.numeric(min_ret$out)11591160# use optimize() to find the target return value that maximizes sharpe ratio1161opt <- try(optimize(f=starr_obj_fun, R=R, constraints=constraints,1162solver=solver, moments=moments, alpha=alpha,1163lower=min_mean, upper=max_mean, control=control,1164maximum=TRUE, tol=.Machine$double.eps),1165silent=TRUE)1166if(inherits(opt, "try-error")){1167stop(paste("Objective function failed with message\n", opt))1168return(NULL)1169}1170return(opt$maximum)1171}11721173# Function to calculate the starr ratio.1174# Used as the objective function for optimize()1175starr_obj_fun <- function(target_return, R, constraints, moments, alpha, solver, control){1176if(!is.null(constraints$max_pos)){1177opt <- etl_milp_opt(R=R, constraints=constraints, moments=moments,1178target=target_return, alpha=alpha, solver=solver,1179control=control)1180} else {1181opt <- etl_opt(R=R, constraints=constraints, moments=moments,1182target=target_return, alpha=alpha, solver=solver,1183control=control)1184}1185weights <- matrix(opt$weights, ncol=1)1186opt_mean <- sum(weights * moments$mean)1187opt_etl <- as.numeric(opt$out)1188starr <- opt_mean / opt_etl1189return(starr)1190}119111921193# This was my implementation of a binary search for the maximum starr ratio1194# target return. Better to use optimize() in R rather than my method. -Ross Bennett1195# mean_etl_opt <- function(R, constraints, moments, target, alpha, solver="glpk", tol=.Machine$double.eps^0.5, maxit=50){1196# # This function returns the target mean return that maximizes mean / etl (i.e. starr)1197#1198# # if all(moments$mean == 0) then the user did not specify mean as an objective,1199# # and we just want to return the target mean return value1200# if(all(moments$mean == 0)) return(target)1201#1202# fmean <- matrix(moments$mean, ncol=1)1203#1204# # can't use optimize.portfolio here, this function is called inside1205# # optimize.portfolio and will throw an error message about nesting too deeply1206#1207# # Find the maximum return1208# if(!is.null(constraints$max_pos)){1209# max_ret <- maxret_milp_opt(R=R, constraints=constraints, moments=moments, target=NA, solver=solver)1210# } else {1211# max_ret <- maxret_opt(R=R, moments=moments, constraints=constraints, target=NA, solver=solver)1212# }1213# max_mean <- as.numeric(-max_ret$out)1214#1215# # Find the starr at the maximum etl portfolio1216# if(!is.null(constraints$max_pos)){1217# ub_etl <- etl_milp_opt(R=R, constraints=constraints, moments=moments, target=max_mean, alpha=alpha, solver=solver)1218# } else {1219# ub_etl <- etl_opt(R=R, constraints=constraints, moments=moments, target=max_mean, alpha=alpha, solver=solver)1220# }1221# ub_weights <- matrix(ub_etl$weights, ncol=1)1222# ub_mean <- as.numeric(t(ub_weights) %*% fmean)1223# ub_etl <- as.numeric(ub_etl$out)1224# # starr at the upper bound1225# ub_starr <- ub_mean / ub_etl1226# if(is.infinite(ub_starr)) stop("Inf value for STARR, objective value is 0")1227#1228# # cat("ub_mean", ub_mean, "\n")1229# # cat("ub_etl", ub_etl, "\n")1230# # cat("ub_starr", ub_starr, "\n")1231#1232# # Find the starr at the minimum etl portfolio1233# if(!is.null(constraints$max_pos)){1234# lb_etl <- etl_milp_opt(R=R, constraints=constraints, moments=moments, target=NA, alpha=alpha, solver=solver)1235# } else {1236# lb_etl <- etl_opt(R=R, constraints=constraints, moments=moments, target=NA, alpha=alpha, solver=solver)1237# }1238# lb_weights <- matrix(lb_etl$weights)1239# lb_mean <- as.numeric(t(lb_weights) %*% fmean)1240# lb_etl <- as.numeric(lb_etl$out)1241#1242# # starr at the lower bound1243# lb_starr <- lb_mean / lb_etl1244# # if(is.infinite(lb_starr)) stop("Inf value for STARR, objective value is 0")1245#1246# # set lb_starr equal to 0, should this be a negative number like -1e6?1247# # the lb_* values will be 0 for a dollar-neutral strategy so we need to reset the values1248# if(is.na(lb_starr) | is.infinite(lb_starr)) lb_starr <- 01249#1250# # cat("lb_mean", lb_mean, "\n")1251# # cat("lb_etl", lb_etl, "\n")1252# # cat("lb_starr", lb_starr, "\n")1253#1254# # want to find the return that maximizes mean / etl1255# i <- 11256# while((abs(ub_starr - lb_starr) > tol) & (i < maxit)){1257# # bisection method to find the maximum mean / etl1258#1259# # print(i)1260# # cat("ub_starr", ub_starr, "\n")1261# # cat("lb_starr", lb_starr, "\n")1262# # print("**********")1263# # Find the starr at the mean return midpoint1264# new_ret <- (lb_mean + ub_mean) / 21265# if(!is.null(constraints$max_pos)){1266# mid <- etl_milp_opt(R=R, constraints=constraints, moments=moments, target=new_ret, alpha=alpha, solver=solver)1267# } else {1268# mid <- etl_opt(R=R, constraints=constraints, moments=moments, target=new_ret, alpha=alpha, solver=solver)1269# }1270# # print(mid)1271# mid_weights <- matrix(mid$weights, ncol=1)1272# mid_mean <- as.numeric(t(mid_weights) %*% fmean)1273# mid_etl <- as.numeric(mid$out)1274# mid_starr <- mid_mean / mid_etl1275# # the mid_* values MIGHT be 0 for a dollar-neutral strategy so we need to reset the values1276# # if(is.na(mid_starr) | is.infinite(mid_starr)) mid_starr <- 01277# # tmp_starr <- mid_starr1278#1279# # cat("mid_mean", mid_mean, "\n")1280# # cat("mid_etl", mid_etl, "\n")1281# # cat("mid_starr", mid_starr, "\n")1282#1283# if(mid_starr > ub_starr){1284# # if mid_starr > ub_starr then mid_starr becomes the new upper bound1285# ub_mean <- mid_mean1286# ub_starr <- mid_starr1287# new_ret <- (lb_mean + ub_mean) / 21288# if(!is.null(constraints$max_pos)){1289# mid <- etl_milp_opt(R=R, constraints=constraints, moments=moments, target=new_ret, alpha=alpha, solver=solver)1290# } else {1291# mid <- etl_opt(R=R, constraints=constraints, moments=moments, target=new_ret, alpha=alpha, solver=solver)1292# }1293# mid_weights <- matrix(mid$weights, ncol=1)1294# mid_mean <- as.numeric(t(mid_weights) %*% fmean)1295# mid_etl <- as.numeric(mid$out)1296# mid_starr <- mid_mean / mid_etl1297# # the mid_* values MIGHT be 0 for a dollar-neutral strategy so we need to reset the values1298# # if(is.na(mid_starr) | is.infinite(mid_starr)) mid_starr <- 01299# } else if(mid_starr > lb_starr){1300# # if mid_starr > lb_starr then mid_starr becomes the new lower bound1301# lb_mean <- mid_mean1302# lb_starr <- mid_starr1303# new_ret <- (lb_mean + ub_mean) / 21304# if(!is.null(constraints$max_pos)){1305# mid <- etl_milp_opt(R=R, constraints=constraints, moments=moments, target=new_ret, alpha=alpha, solver=solver)1306# } else {1307# mid <- etl_opt(R=R, constraints=constraints, moments=moments, target=new_ret, alpha=alpha, solver=solver)1308# }1309# mid_weights <- matrix(mid$weights, ncol=1)1310# mid_mean <- as.numeric(t(mid_weights) %*% fmean)1311# mid_etl <- as.numeric(mid$out)1312# mid_starr <- mid_mean / mid_etl1313# # the mid_* values MIGHT be 0 for a dollar-neutral strategy so we need to reset the values1314# # if(is.na(mid_starr) | is.infinite(mid_starr)) mid_starr <- 01315# }1316# i <- i + 11317# }1318# return(new_ret)1319# }1320132113221323# Function to calculate the sharpe ratio.1324# Used as the objective function for optimize()1325sharpe_obj_fun <- function(target_return, R, constraints, moments, lambda_hhi=NULL, conc_groups=NULL, solver="quadprog", control=control){1326opt <- gmv_opt(R=R, constraints=constraints, moments=moments, lambda=1,1327target=target_return, lambda_hhi=lambda_hhi,1328conc_groups=conc_groups, solver=solver, control=control)1329weights <- opt$weights1330# opt_mean <- as.numeric(t(weights) %*% matrix(moments$mean, ncol=1))1331opt_mean <- sum(weights * moments$mean)1332# opt_sd <- as.numeric(sqrt(t(weights) %*% moments$var %*% weights))1333# faster and more efficient way to compute t(w) %*% Sigma %*% w1334opt_sd <- sqrt(sum(crossprod(weights, moments$var) * weights))1335opt_sr <- opt_mean / opt_sd1336return(opt_sr)1337}13381339# This function uses optimize() to find the target return value that1340# results in the maximum sharpe ratio (mean / sd).1341# returns the target return value1342max_sr_opt <- function(R, constraints, moments, lambda_hhi, conc_groups, solver, control){1343# create a copy of the moments that can be modified1344tmp_moments <- moments13451346# Find the maximum return1347max_ret <- maxret_opt(R=R, moments=moments, constraints=constraints,1348target=NA, solver="glpk", control=control)1349max_mean <- as.numeric(-max_ret$out)13501351# Find the minimum return1352tmp_moments$mean <- -1 * moments$mean1353min_ret <- maxret_opt(R=R, moments=tmp_moments, constraints=constraints,1354target=NA, solver="glpk", control=control)1355min_mean <- as.numeric(min_ret$out)13561357# use optimize() to find the target return value that maximizes sharpe ratio1358opt <- try(optimize(f=sharpe_obj_fun, R=R, constraints=constraints,1359solver=solver, lambda_hhi=lambda_hhi,1360conc_groups=conc_groups, moments=moments, control=control,1361lower=min_mean, upper=max_mean,1362maximum=TRUE, tol=.Machine$double.eps),1363silent=TRUE)1364if(inherits(opt, "try-error")){1365stop(paste("Objective function failed with message\n", opt))1366return(NULL)1367}1368return(opt$maximum)1369}137013711372# This was my implementation of a binary search for the maximum sharpe ratio1373# target return. Better to use optimize() in R rather than my method. -Ross Bennett1374# max_sr_opt <- function(R, constraints, moments, lambda, target, lambda_hhi, conc_groups, solver="quadprog", tol=.Machine$double.eps^0.5, maxit=50){1375# # This function returns the target mean return that maximizes mean / sd (i.e. sharpe ratio)1376#1377# # get the forecast mean from moments1378# fmean <- matrix(moments$mean, ncol=1)1379#1380# # Find the maximum return1381# max_ret <- maxret_opt(R=R, moments=moments, constraints=constraints, target=NA)1382# max_mean <- as.numeric(-max_ret$out)1383#1384# # Calculate the sr at the maximum mean return portfolio1385# ub_weights <- matrix(max_ret$weights, ncol=1)1386# ub_mean <- max_mean1387# ub_sd <- as.numeric(sqrt(t(ub_weights) %*% moments$var %*% ub_weights))1388# # sr at the upper bound1389# ub_sr <- ub_mean / ub_sd1390#1391# # Calculate the sr at the miminum var portfolio1392# tmpmoments <- moments1393# tmpmoments$mean <- rep(0, length(moments$mean))1394# lb_sr <- gmv_opt(R=R, constraints=constraints, moments=tmpmoments, lambda=1, target=NA, lambda_hhi=lambda_hhi, conc_groups=conc_groups, solver=solver)1395# lb_weights <- matrix(lb_sr$weights)1396# lb_mean <- as.numeric(t(lb_weights) %*% fmean)1397# lb_sd <- as.numeric(sqrt(t(lb_weights) %*% moments$var %*% lb_weights))1398# # sr at the lower bound1399# lb_sr <- lb_mean / lb_sd1400#1401# # cat("lb_mean:", lb_mean, "\n")1402# # cat("ub_mean:", ub_mean, "\n")1403# # print("**********")1404#1405# # want to find the return that maximizes mean / sd1406# i <- 11407# while((abs(ub_sr - lb_sr) > tol) & (i < maxit)){1408# # bisection method to find the maximum mean / sd1409#1410# # Find the starr at the mean return midpoint1411# new_ret <- (lb_mean + ub_mean) / 21412# mid <- gmv_opt(R=R, constraints=constraints, moments=tmpmoments, lambda=1, target=new_ret, lambda_hhi=lambda_hhi, conc_groups=conc_groups, solver=solver)1413# mid_weights <- matrix(mid$weights, ncol=1)1414# mid_mean <- as.numeric(t(mid_weights) %*% fmean)1415# mid_sd <- as.numeric(sqrt(t(mid_weights) %*% moments$var %*% mid_weights))1416# mid_sr <- mid_mean / mid_sd1417# # tmp_sr <- mid_sr1418#1419# # print(i)1420# # cat("new_ret:", new_ret, "\n")1421# # cat("mid_sr:", mid_sr, "\n")1422# # print("**********")1423#1424# if(mid_sr > ub_sr){1425# # if mid_sr > ub_sr then mid_sr becomes the new upper bound1426# ub_mean <- mid_mean1427# ub_sr <- mid_sr1428# new_ret <- (lb_mean + ub_mean) / 21429# mid <- gmv_opt(R=R, constraints=constraints, moments=tmpmoments, lambda=1, target=new_ret, lambda_hhi=lambda_hhi, conc_groups=conc_groups, solver=solver)1430# mid_weights <- matrix(mid$weights, ncol=1)1431# mid_mean <- as.numeric(t(mid_weights) %*% fmean)1432# mid_sd <- as.numeric(sqrt(t(mid_weights) %*% moments$var %*% mid_weights))1433# mid_sr <- mid_mean / mid_sd1434# } else if(mid_sr > lb_sr){1435# # if mid_sr > lb_sr then mid_sr becomes the new lower bound1436# lb_mean <- mid_mean1437# lb_sr <- mid_sr1438# new_ret <- (lb_mean + ub_mean) / 21439# mid <- gmv_opt(R=R, constraints=constraints, moments=tmpmoments, lambda=1, target=new_ret, lambda_hhi=lambda_hhi, conc_groups=conc_groups, solver=solver)1440# mid_weights <- matrix(mid$weights, ncol=1)1441# mid_mean <- as.numeric(t(mid_weights) %*% fmean)1442# mid_sd <- as.numeric(sqrt(t(mid_weights) %*% moments$var %*% mid_weights))1443# mid_sr <- mid_mean / mid_sd1444# }1445# i <- i + 11446# }1447# return(new_ret)1448# }144914501451###############################################################################1452# R (https://r-project.org/) Numeric Methods for Optimization of Portfolios1453#1454# Copyright (c) 2004-2021 Brian G. Peterson, Peter Carl, Ross Bennett, Kris Boudt1455#1456# This library is distributed under the terms of the GNU Public License (GPL)1457# for full details see the file COPYING1458#1459# $Id$1460#1461###############################################################################146214631464