The purpose of this vignette is to demonstrate a sample of portfolio optimization problems that can be solved by using the ROI package. This vignette is based on joint work with Florian Schwendinger and Ronald Hochreiter which was presented at RFinance 2016, Chicago, USA, May 2016.
If previously no ROI version was installed, one should at least install the two plugins ROI.plugin.glpk
, ROI.plugin.alabama
and ROI.plugin.quadprog
to run the examples below.
rm(list = ls())
## NOTE: (Rglpk needs libglpk-dev on Debian/Ubuntu ("sudo apt-get install libglpk-dev"))
if (!require("ROI")) install.packages("ROI"); library("ROI")
if (!require("ROI.plugin.glpk")) install.packages("ROI.plugin.glpk"); library("ROI.plugin.glpk")
if (!require("ROI.plugin.quadprog")) install.packages("ROI.plugin.quadprog"); library("ROI.plugin.quadprog")
if (!require("ROI.plugin.alabama")) install.packages("ROI.plugin.alabama"); library("ROI.plugin.alabama")
Several R functions are created to implement the typical objectives and constraints used for portfolio optimization. All functions require a data.frame
r_mat
of returns. The mathematical formulation of the objectives and constraints is presented below. The default optimization in ROI is minimization. Hence for all maximization problems the argument maximum = TRUE
in the OP()
function must be specified.
Name | Arguments | Objectives | Max/Min | Type |
---|---|---|---|---|
reward_objective |
(r_mat) |
Portfolio return/reward | max | LP |
markowitz_objective |
(r_mat) |
Variance of portfolio | min | QP |
mad_objective |
(r_mat) |
Mean Absolute Deviation | min | LP |
downside_var_objective |
(r_mat) |
Lower semi-variance of portfolio | min | QP |
downside_mad_objective |
(r_mat) |
Lower semi-mean absolute deviation | min | LP |
cvar_objective |
(r_mat, alpha, probs = NULL) |
Conditional Value-at-Risk | min | LP |
minimax_young_objective |
(r_mat) |
Minimax portfolio | max | LP |
quadratic_utility_objective |
(r_mat, lambda = 2) |
Quadratic Utility | max | LP |
sharpe_objective |
(r_mat, rf = 0) |
Sharpe ratio | max | QP |
omega_objective |
(r_mat, tau = 0) |
Omega | max | LP |
Name | Arguments | Constraints | Type |
---|---|---|---|
budget_constraint |
(r_mat, dir = "==", rhs = 1) |
Budget constraint,i.e., sum of the portfolio equals budget B (default B=rhs =1) |
LP |
group_constraint |
(r_mat, index, coef.index = 1, dir = "==", rhs) |
Group constraint applied only the the index elements of \(x\) |
LP |
turnover_constraint |
(r_mat, x0 = NULL, dir = "<=", rhs = 100) |
Constraint on the turnover from some initial weights \(x_0\) | LP |
cardinality_constraint |
(r_mat, dir = "<=", rhs = 100) |
Cardinality | MILP |
reward_constraint |
(r_mat, dir = ">=", rhs = 0) |
Target return constraint | LP |
markowitz_constraint |
(r_mat, dir = "<=", rhs = 1000) |
Constraint on variance of portfolio \(x^{\top}Qx \leq q^2\) | QP |
cvar_constraint |
(r_mat, alpha, probs = NULL, dir = "<=", rhs = 100) |
Constraints on the conditional Value-at-Risk | LP |
We consider a portfolio of assets with random returns. We denote the portfolio choice vector by \(x\) and by \(r\) the vector of random returns. \(N\) denotes the number of assets in the portfolio while \(S\) is the number of scenarios in the scenarios set.
\[ \max_{x \in \mathbb{R}^N} \hat \mu^\top x \] where \(\hat \mu\) is the vector of estimated mean asset returns.
In ROI we define a function which returns a linear objective is L_objective
(note that the minus turns the maximization into a minimization problem):
function(r_mat) {
reward_objective <- L_objective(colMeans(r_mat))
objective <-list(objective = objective)
}
(see Example 1, Example 12, Example 13).
The objective of the minimum of the variance portfolio is given by: \[\min_{x \in \mathbb{R}^N} x^\top Q x\] \[Q = \mathbb{C}ov(r)\]
function(r_mat) {
markowitz_objective <- Q_objective(Q = 2 * cov(r_mat),
objective <-L = rep(0, NCOL(r_mat)))
list(objective = objective)
}
The minimization of the mean absolute deviation can be written as a linear problem. \[ \begin{eqnarray*} \min_{x\in \mathbb{R}^N, y\in \mathbb{R}^S, z\in \mathbb{R}^S}&& \frac{1}{S} \sum_{s = 1}^S (y_s + z_s)\\ s.t.&&y_s - z_s = (r_{s} - \hat \mu)^\top x,\\ && y_s \geq 0, z_s \geq 0. \end{eqnarray*} \] Note that the bounds \(y_s\geq 0\) and \(z_s\geq 0\) must not be additionally specified as these are the default in ROI.
function(r_mat){
mad_objective <- colnames(r_mat)
x.names <- NCOL(r_mat)
N <- nrow(r_mat)
S <- colMeans(r_mat)
mu <- cbind(sweep(as.matrix(r_mat), 2, mu), - diag(S), diag(S))
Amat <- c(x.names,
var.names <-paste0("y_mad_aux", seq_len(S)),
paste0("z_mad_aux", seq_len(S)))
L_constraint(L = Amat, dir = rep("==", S),
constraint <-rhs = rep(0, S),
names = var.names)
L_objective(L = c(rep(0, N), rep(1/S, 2 * S)))
objective <-
list(objective = objective, constraint = constraint)
}
(See Example 4)
A downside risk measure is the lower semi-variance, which is the expected squared deviation from the mean, calculated over those points that are no greater than the mean. Only returns that are below the mean contribute to the portfolio risk. Let \(r_s\) denote the vector of random returns of the \(N\) assets in scenario \(s\). The problem can be formulated as (using the auxiliary variable \(z\in \mathbb{R}^S\)): \[\min_{x\in \mathbb{R}^N} \frac{1}{S} \sum_{s = 1}^S \mathrm{max}(\hat\mu^\top x - r_s^\top x, 0)^2.\] The problem can be reformulated as a QP: \[ \begin{eqnarray*} \min_{x\in \mathbb{R}^N, z\in \mathbb{R}^S}&& \frac{1}{S} \sum_{s = 1}^S z_s^2\\ s.t.&&z_s \geq (\hat\mu - r_s)^\top x)\\ && z_s \geq 0. \end{eqnarray*} \]
function(r_mat, x.names = NULL) {
downside_var_objective <- colnames(r_mat)
x.names <- NCOL(r_mat)
N <- NROW(r_mat)
S <- colMeans(r_mat)
mu <- cbind(sweep(as.matrix(r_mat), 2, mu), diag(S))
Amat <- c(x.names, paste0("z_dvar_aux", seq_len(S)))
var.names <-
L_constraint(L = Amat, dir = rep(">=", S),
constraint <-rhs = rep(0, S),
names = var.names)
Q_objective(
objective <-Q = 2 * diag(c(rep(1e-05, N), rep(1/S, S))),
L = rep(0, N + S))
list(objective = objective, constraint = constraint)
}
(see Example 5).
Let \(r_s\) denote the vector of random returns of the \(N\) assets in scenario \(s\). The downside risk measure given by the expected absolute deviation from the mean, calculated over those points that are no greater than the mean is given by: \[\frac{1}{S} \sum_{s = 1}^S \vert \mathrm{max}(\hat\mu^\top x - r_s^\top x, 0) \vert\] The minimization of this risk measure can be reformulated as a linear problem: \[ \begin{eqnarray*} \min_{x\in \mathbb{R}^N, z\in \mathbb{R}^S}&& \frac{1}{S} \sum_{s = 1}^S z_s,\\ s.t.&& z_s \geq (\hat\mu - r_s)^\top x\\ && z_s \geq 0. \end{eqnarray*} \]
function(r_mat){
downside_mad_objective <- colnames(r_mat)
x.names <- NCOL(r_mat)
N <- NROW(r_mat)
S <- colMeans(r_mat)
mu <- cbind(sweep(as.matrix(r_mat), 2, mu), diag(S))
Amat <- c(x.names, paste0("z_dmad_aux", seq_len(S)))
var.names <-
L_constraint(L = Amat, dir = rep(">=", S),
constraint <-rhs = rep(0, S),
names = var.names)
L_objective(L = c(rep(0, N), rep(1/S, S)))
objective <-
list(objective = objective, constraint = constraint)
}
(see Example 6).
The conditional value at risk/expected shortfall is the average of the losses that exceed the \(\alpha\)-quantile of the loss distribution, also called \(\alpha\) Value-at-Risk (VaR). Defining the loss distribution as minus of the return of the portfolio, the problem can be formulated as follows: \[ \begin{eqnarray*} \min_{x\in \mathbb{R}^N, z\in \mathbb{R}^S, \gamma\in \mathbb{R}}& \gamma + \frac{1}{(1-\alpha)} \sum_{s = 1}^S p_s z_s\\ s.t.\ &z_s \geq - r_s^\top x - \gamma,\\ &z_s \geq 0, \end{eqnarray*} \] where VaR is a minimizer of the function over \(\gamma\) and \(p=(p_1, \dots, p_S)\) is a vector of probabilities associated with the scenarios.
function(r_mat, alpha, probs = NULL) {
cvar_objective <- colnames(r_mat)
x.names <- NCOL(r_mat)
N <- NROW(r_mat)
S <- colMeans(r_mat)
mu <-if (is.null(probs)) probs <- rep(1/S, S)
if (alpha < 0.5) alpha <- 1 - alpha
cbind(as.matrix(r_mat), diag(S), 1)
Amat <- c(x.names, paste0("z_cvar_aux", seq_len(S)), "gamma")
var.names <-
## set bounds for gamma (-Inf, Inf)
ROI::V_bound(li = c(N + S + 1), lb = c( -Inf),
bnds <-ui = c(N + S + 1), ub = c( Inf))
L_constraint(L = Amat, dir = rep(">=", S),
constraint <-rhs = rep(0, S),
names = var.names)
L_objective(c(rep(0, N), probs/(1 - alpha), 1))
objective <-
list(objective = objective, constraint = constraint, bounds = bnds)
}
Note that if no value is specified for \(p_1, \cdots, p_S\) the default is equal weights.
(see Example 7, Example 14).
The optimal portfolio is defined as that one that minimizes the maximum loss over all past historical periods, subject to a restriction on the minimum acceptable average return across all observed periods of time. The minimax portfolio maximizes the minimum gain and can be seen as a limiting case of CVaR for \(\alpha \rightarrow 1\). Let \(M_p=\min_s r_s^\top x\) denote the minimum gain of the portfolio. \[ \begin{eqnarray*} \max_{x \in \mathbb{R}^N, M_p\in \mathbb{R}}&& M_p\\ s.t.&% r_s^\top x - M_p \geq 0. \end{eqnarray*} \] Young, M.R., 1998. A minimax portfolio selection rule with linear programming solution. Management science, 44(5), pp.673-683.
function(r_mat){
minimax_young_objective <- colnames(r_mat)
x.names <- NCOL(r_mat)
N <- NROW(r_mat)
S <- cbind(as.matrix(r_mat), -1)
Amat <-
ROI::V_bound(li = c(N + 1), lb = c( -Inf),
bnds <-ui = c(N + 1), ub = c( Inf))
L_constraint(L = Amat, dir=rep(">=", S), rhs=rep(0, S),
constraint <-names=c(x.names, "mp_aux"))
L_objective(c(rep(0, N), 1))
objective <-
list(objective = objective, constraint = constraint, bounds = bnds)
}
(see Example 8).
Let \(\lambda\) denote the risk aversion parameter. Typical risk aversion parameters lie between 2 and 4. Then the quadratic utility is given by: \[\begin{eqnarray*} \max_{x\in\mathbb{R}^N}&& \hat \mu^\top x - \frac{\lambda}{2} x^\top Q x \end{eqnarray*}\]
function(r_mat, lambda = 2){
quadratic_utility_objective <- Q_objective(Q = - lambda * cov(r_mat),
objective <-L = colMeans(r_mat))
list(objective = objective)
}
(see Example 9).
The Omega Ratio is a risk-return performance measure of an investment asset, portfolio, or strategy. The Omega Ratio, introduced in 2002 by Keating and Shadwick, is defined as the probability weighted ratio of gains versus losses for some threshold return target \(\tau\). \[\Omega(\tilde r) = \frac{\int_\tau^{+\infty}\left(1 - F(r)\right)\mathrm{d}r}{\int_{-\infty}^\tau F(r)\mathrm{d}r} \] where \(F(r)\) denotes the cumulative distribution function of \(\tilde r\). The maximization of \(\Omega(\tilde r)\) can be formulated as an linear problem (note that the budget normalization constraint and the target return constraint are already part of the model formulation). \[\begin{eqnarray*} \max_{y\in \mathbb{R}^N, u\in \mathbb{R}^S, z\in \mathbb{R}}& \hat \mu^\top y - \tau z\\ s.t.\ & u_s \geq \tau - r_s^\top y , \\ & u_i\geq 0,\\ & \mathit{1}^\top u = 1,\\ & \mathit{1}^\top y = z,\\ & \hat \mu^\top y \geq \tau z,\\ & z \geq 0 . \end{eqnarray*}\] The Omega optimal portfolio is given by \(x^* = y^* /z^*\), where \(z\) is the homogenizing variable. If not explicitly specified, the default for the threshold is \(\tau = 0\).
function(r_mat, tau = 0){
omega_objective <-## variables y_1, ... y_N, u_1, .., u_S, z
NCOL(r_mat)
N <- NROW(r_mat)
S <- colMeans(r_mat)
mu <-
rbind(cbind(as.matrix(r_mat), diag(S), 0),# u_s >= tau - r_s'y
Amat <-c(rep(0, N), rep(1, S), 0), # sum(u) = 1
c(rep(1, N), rep(0, S), -1), # sum(y) = z
c(mu, rep(0, S), - tau),
c(rep(0, N), rep(0, S), 1)) # mu'y >= tau * z
c(paste0("y_omega_aux", seq_len(N)),
var.names <-paste0("u_omega_aux", seq_len(S)),
"z_omega")
L_constraint(L = Amat,
constraint <-dir = c(rep(">=", S), "==", "==", ">=", ">"),
rhs = c(rep(tau, S), 1, 0, 0, 1e-05),
names=var.names)
L_objective(L = c(mu, rep(0, S), -tau))
objective <-
list(objective = objective, constraint = constraint)
## x* <- y*/z*
}
(see Example 11).
Can be set by the argument bounds
in OP()
. Note that the default in ROI is \(0 \leq x_i \leq \infty\).
\[\sum_{i=1}^N x_i = B \qquad \sum_{i=1}^N x_i \leq B_u \qquad \sum_{i=1}^N x_i \geq B_l.\] Budget normalization constraint is obtained for B = 1 (default).
function(r_mat, dir = "==", rhs = 1) {
budget_constraint <- colnames(r_mat)
x.names <-L_constraint(L = rep(1, NCOL(r_mat)),
dir = dir, rhs = rhs, names = x.names)
}
Group constraints are linear constraints which apply to only some elements of the portfolio choice vector \(x\): \[ ax_i + bx_j = c \] where the \(=\) can be replaced by inequalities.
function(r_mat, index, coef.index = 1, dir = "==", rhs) {
group_constraint <-## index = (i, j)
## coef.index = c(a,b)
## rhs = c
colnames(r_mat)
x.names <- NCOL(r_mat)
N <- rep(0, N)
L <- coef.index
L[index] <-L_constraint(L = L, dir = dir, rhs = rhs, names = x.names)
}
(see Example 1, Example 12).
For a target turnover \(L\) and some initial weights \(x_0\), the turnover contraint is \[\sum_{i=1}^N \vert x_i - x_{i0}\vert \leq L.\] This can be reformulated in terms of auxiliary variables \(y^+, y^- \in \mathbb{R}^{+N}\) such that \[\begin{eqnarray*} &&y^+_i- y_i^- = x_i - x_{0i}\\ &&\sum_{i=1}^N (y_i^+ + y_i^-)\leq L\\ &&y^+ \geq 0, \, y^-\geq 0 \end{eqnarray*}\] If no initial weights are specified, the default is equal weights. Note: does not work out of the box with Omega and Sharpe objectives.
function(r_mat, x0 = NULL, dir = "<=", rhs = 100) {
turnover_constraint <- colnames(r_mat)
x.names <- NCOL(r_mat)
N <- NROW(r_mat)
S <-if (is.null(x0)) x0 <- rep(1/N, N)
cbind(diag(N), - diag(N), diag(N))
Amat <- c(x.names,
var.names <-paste0("y_plus_aux", seq_len(N)),
paste0("y_minus_aux", seq_len(N)))
rbind(L_constraint(L = Amat, dir = rep("==", N), rhs = x0,
names = var.names),
L_constraint(c(rep(0, N), rep(1, N), rep(1, N) ),
dir = dir, rhs = rhs, names = var.names))
}
(see Example 12).
The cardinality constraint assigns bounds \(P_{min}\) and \(P_{max}\) on the number of assets in a portfolio. Binary auxiliary variables \(z\in \mathbb{R}^N\) are introduced such that \(P_{min} \leq \sum_{i=1}^N z_i\leq P_{max}\) and \(x_i - z_i \leq 0\).
function(r_mat, dir = "<=", rhs = 100) {
cardinality_constraint <- colnames(r_mat)
x.names <- NCOL(r_mat)
N <- cbind(diag(N), -diag(N))
Amat <- c(x.names, paste0("z_card_aux", seq_len(N)))
var.names <-cat("Variable types for z_card_aux must be set to binary.\n")
rbind(L_constraint(L = Amat, dir = rep("<=", N),
rhs = rep(0, N), names = var.names),
L_constraint(L = c(rep(0, N), rep(1, N)), dir = dir,
rhs = rhs, names = var.names))
}
(see Example 14).
Also typical objective functions can be used as constraints. For example, the expected return must not fall below a value \(\tau\): \[\hat\mu^\top x \geq \tau\] Note: does not work with Sharpe or Omega (it is included in both Sharpe and Omega maximization problems).
function(r_mat, dir = ">=", rhs = 0) {
reward_constraint <- colnames(r_mat)
x.names <-L_constraint(L = colMeans(r_mat), dir = dir,
rhs = rhs, names = x.names)
}
Typically constraints of the form \(x^\top Q x \leq q^2\). Note however that this is a quadratic constraint and needs appropriate solvers (e.g., in ROI.plugin.alabama). Note: does not work with Omega.
function(r_mat, dir = "<", rhs = 1000) {
markowitz_constraint <- colnames(r_mat)
x.names <- NCOL(r_mat)
N <-Q_constraint(Q = 2 * cov(r_mat), L = rep(0, N),
dir = dir, rhs = rhs, names=x.names)
}
(see Example 13).
Typically CVaR\((x, \alpha) \leq q\). Note: does not work with Omega and Sharpe.
function(r_mat, alpha, probs = NULL, dir = "<=", rhs = 100) {
cvar_constraint <- colnames(r_mat)
x.names <- NCOL(r_mat)
N <- NROW(r_mat)
S <-if (alpha < 0.5) alpha <- 1 - alpha
if (is.null(probs)) probs <- rep(1/S, S)
cbind(as.matrix(r_mat), diag(S), 1)
Amat <- c(x.names, paste0("z_cvar_aux", seq_len(S)), "gamma")
var.names <-# set bounds for gama
ROI::V_bound(li = c(N + S + 1), lb = c( -Inf),
bnds <-ui = c(N + S + 1), ub = c( Inf))
rbind(L_constraint(L = Amat, dir = rep(">=", S),
rhs=rep(0, S), names = var.names),
L_constraint(c(rep(0, N), rep(1 / ((1 - alpha) * S), S), 1),
dir = dir, rhs = rhs, names = var.names))
}
(see Example 12).
For illustration purposes daily log returns for the past 180 days of the 30 companies included in the 2018 Dow Jones Industrial Average will be used.
if (!require("quantmod")) install.packages("quantmod"); library("quantmod")
c("MMM", "AXP", "AAPL", "BA", "CAT", "CVX", "CSCO", "KO", "DIS", "DOW",
Tickers <-"XOM", "GS", "HD", "IBM", "INTC", "JNJ", "JPM",
"MCD", "MRK", "MSFT", "NKE", "PFE", "PG",
"TRV", "UNH", "VZ", "V", "WMT", "WBA")
"cache/cached_portfolio_data.rda"
cached_file <-if ( file.exists(cached_file) ) {
load(cached_file)
else {
} function(x) {
download_tickers <- function(e) {
download_ticker_error_handler <-warning(paste(x, 'not found'))
return(NA)
}tryCatch(getSymbols(x, from = Sys.Date() - 180, auto.assign = FALSE),
error = download_ticker_error_handler)
} lapply(Tickers, download_tickers)
Historicals <-
do.call("cbind",lapply(Historicals, function(x) x[, 6]))
prices <-
as.data.frame((log(lag(prices)) - log(prices))[-1, ])
djia2018 <-dir.create(dirname(cached_file), showWarnings = FALSE)
save(Historicals, prices, djia2018, file = cached_file)
}
if (!require("quantmod")) install.packages("quantmod"); library("quantmod")
c("MMM", "AXP", "AAPL", "BA", "CAT", "CVX", "CSCO", "KO", "DIS", "DOW",
Tickers <-"XOM", "GS", "HD", "IBM", "INTC", "JNJ", "JPM",
"MCD", "MRK", "MSFT", "NKE", "PFE", "PG",
"TRV", "UNH", "UTX", "VZ", "V", "WMT", "WBA")
function(x) {
download_tickers <- function(e) {
download_ticker_error_handler <-warning(paste(x, 'not found'))
return(NA)
}tryCatch(getSymbols(x, from = Sys.Date() - 180, auto.assign = FALSE),
error = download_ticker_error_handler)
} lapply(Tickers, download_tickers)
Historicals <-
do.call("cbind",lapply(Historicals, function(x) x[, 6]))
prices <-
as.data.frame((log(lag(prices)) - log(prices))[-1, ]) djia2018 <-
We consider a portfolio of assets with random returns. We denote the portfolio choice vector by \(x\) and by \(r\) the vector random returns. \(N\) denotes the number of assets in the portfolio while \(S\) is the number of scenarios in the scenarios set.
The following optimization problem: \[ \begin{eqnarray*} \max_{x \in \mathbb{r}^N}&& \hat \mu^\top x\\ \sum_{x=1}^N x_i &=& 1\\ x_i &\geq& 0\\ x_3 + x_{17} &\leq& 0.5 \end{eqnarray*} \] can be set up easily in ROI:
OP(objective = reward_objective(djia2018)$objective,
lp <-constraints = rbind(budget_constraint(djia2018),
group_constraint(djia2018, index = c(3, 17), dir = "==", rhs = 0.5)),
maximum = T)
(Note that the bounds can be omitted as this is the default in ROI). To perfom the optimization, the ROI_solve()
function is called:
ROI_solve(lp, solver = "glpk")) (sol <-
## Optimal solution found.
## The objective value is: -5.048051e-04
solution(sol)
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted
## 0.5 0.0 0.5 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
OP(objective = markowitz_objective(djia2018)$objective,
lp <-constraints = rbind(budget_constraint(djia2018)))
(Note that the bounds can be omitted as this is the default in ROI). To perfom the optimization, the ROI_solve()
function is called:
ROI_solve(lp, solver = "quadprog")) (sol <-
## Optimal solution found.
## The objective value is: 6.210559e-05
round(solution(sol), 3)
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted
## 0.000 0.000 0.000 0.000 0.000 0.041 0.000 0.071 0.000 0.000 0.000 0.029 0.000 0.000 0.000 0.175 0.000 0.190 0.035 0.000 0.000 0.000 0.108 0.000 0.016 0.177 0.000 0.158 0.000
OP(objective = markowitz_objective(djia2018)$objective,
lp <-constraints = rbind(budget_constraint(djia2018),
reward_constraint(djia2018, rhs = 0.001)))
To perfom the optimization, the ROI_solve()
function is called:
ROI_solve(lp, solver = "quadprog")) (sol <-
## No optimal solution found.
## The solver message was: Constraints are inconsistent, no solution.
## The objective value is: NA
round(solution(sol), 3)
## [1] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
The minimum MAD portfolio is given by:
OP(objective = mad_objective(djia2018)$objective,
lp <-constraints = rbind(mad_objective(djia2018)$constraint,
budget_constraint(djia2018),
use.names = TRUE))
(Note that the bounds can be omitted as this is the default in ROI). To perfom the optimization, the ROI_solve()
function is called:
ROI_solve(lp, solver = "glpk")) (sol <-
## Optimal solution found.
## The objective value is: 6.145726e-03
The optimal weights are:
round(solution(sol)[seq_len(NCOL(djia2018))], 3)
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted
## 0.000 0.000 0.000 0.010 0.000 0.072 0.000 0.203 0.000 0.000 0.000 0.000 0.000 0.044 0.000 0.045 0.000 0.178 0.000 0.000 0.000 0.000 0.065 0.000 0.000 0.258 0.000 0.126 0.000
The minimum lower semi-variance portfolio is given by:
downside_var_objective(djia2018)
tmp <- OP(objective = tmp$objective,
lp <-constraints = rbind(tmp$constraint,
budget_constraint(djia2018),
use.names = TRUE))
To perfom the optimization, the ROI_solve()
function is called:
ROI_solve(lp, solver = "quadprog")) (sol <-
## Optimal solution found.
## The objective value is: 3.259502e-05
The optimal weights are:
round(solution(sol)[seq_len(NCOL(djia2018))], 3)
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted
## 0.000 0.000 0.000 0.000 0.000 0.096 0.000 0.058 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.189 0.000 0.186 0.052 0.000 0.000 0.000 0.089 0.000 0.000 0.159 0.000 0.172 0.000
The portfolio with minimum lower semi-absolute deviation and minimum 0.001 target return is given by:
downside_mad_objective(djia2018)
tmp <- OP(objective = tmp$objective,
lp <-constraints = rbind(tmp$constraint,
budget_constraint(djia2018),
reward_constraint(djia2018, rhs = 0.001),
use.names = TRUE))
To perfom the optimization, the ROI_solve()
function is called:
ROI_solve(lp, solver = "glpk")) (sol <-
## No optimal solution found.
## The solver message was: No feasible solution exists.
## The objective value is: 6.349332e-03
round(solution(sol)[seq_len(NCOL(djia2018))], 3)
## [1] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
The portfolio with minimum 95% conditional value at risk can be obtained as follows:
cvar_objective(djia2018, alpha = 0.95)
tmp <- OP(objective = tmp$objective,
lp <-constraints = rbind(tmp$constraint,
budget_constraint(djia2018),
use.names = TRUE),
bounds = tmp$bounds)
To perfom the optimization, the ROI_solve()
function is called:
ROI_solve(lp, solver = "glpk")) (sol <-
## Optimal solution found.
## The objective value is: 1.679488e-02
round(solution(sol)[seq_len(NCOL(djia2018))], 3)
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted
## 0.000 0.000 0.000 0.000 0.000 0.068 0.000 0.000 0.000 0.000 0.000 0.000 0.036 0.000 0.000 0.000 0.000 0.040 0.374 0.000 0.000 0.000 0.049 0.000 0.000 0.112 0.000 0.320 0.000
The solution for 99% CVaR is given by:
cvar_objective(djia2018, alpha = 0.99)
tmp99 <- OP(objective = tmp99$objective,
lp99 <-constraints = rbind(tmp99$constraint,
budget_constraint(djia2018),
use.names = TRUE),
bounds = tmp$bounds)
ROI_solve(lp99, solver = "glpk") sol99 <-
round(solution(sol99)[seq_len(NCOL(djia2018))], 3)
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted
## 0.000 0.000 0.000 0.059 0.028 0.020 0.000 0.000 0.000 0.000 0.000 0.000 0.018 0.000 0.000 0.000 0.000 0.000 0.356 0.000 0.000 0.000 0.000 0.000 0.060 0.114 0.000 0.345 0.000
The 95% or the 99% VaR are a by-product of the optimization problem and can be extracted from the solution
c("0.95" = sol$solution["gamma"],
VaR <-"0.99" = sol99$solution["gamma"])
VaR
## 0.95.gamma 0.99.gamma
## 0.01599057 0.01695138
minimax_young_objective(djia2018)
tmp <- OP(objective = tmp$objective,
lp <-constraints = rbind(tmp$constraint,
budget_constraint(djia2018),
use.names = TRUE),
bounds = tmp$bounds,
maximum = T)
To perfom the optimization, the ROI_solve()
function is called:
ROI_solve(lp, solver = "glpk")) (sol <-
## Optimal solution found.
## The objective value is: -1.695138e-02
round(solution(sol)[seq_len(NCOL(djia2018))], 3)
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted
## 0.000 0.000 0.000 0.059 0.028 0.020 0.000 0.000 0.000 0.000 0.000 0.000 0.018 0.000 0.000 0.000 0.000 0.000 0.356 0.000 0.000 0.000 0.000 0.000 0.060 0.114 0.000 0.345 0.000
quadratic_utility_objective(djia2018)
tmp <- OP(objective = tmp$objective,
lp <-constraints = budget_constraint(djia2018),
bounds = V_bound(li = seq_len(NCOL(djia2018)),
lb = rep(-1, NCOL(djia2018))),
maximum = T)
ROI_solve(lp, solver = "quadprog")) (sol <-
## Optimal solution found.
## The objective value is: 2.073909e-02
round(solution(sol)[seq_len(NCOL(djia2018))], 3)
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted
## 5.247 -0.096 -0.248 -1.000 -1.000 2.337 -1.000 -1.000 1.187 -1.000 -1.000 0.232 2.325 -1.000 -1.000 0.676 -1.000 -1.000 -1.000 -1.000 -1.000 2.405 -1.000 0.562 3.251 0.122 -1.000 -1.000 -1.000
omega_objective(djia2018)
tmp <- OP(objective = tmp$objective,
lp <-constraints = tmp$constraint,
maximum = T)
ROI_solve(lp, solver = "glpk")) (sol <-
## Optimal solution found.
## The objective value is: 3.656264e-04
The optimal solution is given by \(x^* = y^* / z^*\).
solution(sol)
sol_omega <- round(sol_omega[1:30]/sol_omega["z_omega"], 3)
x_opt <-names(x_opt) <-colnames(djia2018)
x_opt
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted <NA>
## 1.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.003
\[\begin{eqnarray*} \max_{x\in\mathbb{R}^N}& \hat\mu^\top x \\ s.t.& \mathrm{CVaR}(x, 0.95) \leq 0.02 \\ & x_i \geq -1\\ & x_2 + x_{10} + x_{20}\leq 0.5 \\ & \sum_{i=1}^N \vert x_i - x_{0i} \vert \leq 0.5 \end{eqnarray*}\] Given that the constraints have more (auxiliary) variables than the objective, first the constraints are defined:
OP(maximum = TRUE)
lp <-
constraints(lp) <- rbind(
budget_constraint(djia2018),
group_constraint(djia2018, index = c(2, 10, 20), dir = "<=", rhs = 0.5),
turnover_constraint(djia2018, dir = "<=", rhs = 0.5),
cvar_constraint(djia2018, alpha = 0.95, rhs = 0.05),
use.names = TRUE)
Next, the vector of the linear objective is filled with zeros to match the dimension of the constraints:
c(terms(reward_objective(djia2018)$objective)$L)
obj <-objective(lp) <- c(obj, double(ncol(constraints(lp)) - length(obj)))
## Error in (function (classes, fdef, mtable) : unable to find an inherited method for function 'constraints' for signature '"OP"'
ROI_solve(lp, solver = "glpk")) (sol <-
## Error in ROI_solve(lp, solver = "glpk"): objective is missing, with no default
solution(sol)[1:30]
## y_omega_aux1 y_omega_aux2 y_omega_aux3 y_omega_aux4 y_omega_aux5 y_omega_aux6 y_omega_aux7 y_omega_aux8 y_omega_aux9 y_omega_aux10 y_omega_aux11 y_omega_aux12 y_omega_aux13 y_omega_aux14 y_omega_aux15 y_omega_aux16 y_omega_aux17 y_omega_aux18 y_omega_aux19 y_omega_aux20 y_omega_aux21 y_omega_aux22 y_omega_aux23 y_omega_aux24 y_omega_aux25 y_omega_aux26 y_omega_aux27 y_omega_aux28 y_omega_aux29 u_omega_aux1
## 1.320703389 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 0.004619163
\[\begin{eqnarray*} \max_{x\in\mathbb{R}^N}&& \hat\mu^\top x \\ s.t.&& x^{\top}Qx \leq 0.1 \\ &&\sum_{i=1}^N x_i =1\\ && x_i \geq 0 \end{eqnarray*}\]
OP(maximum = T)
p <-
constraints(p) <- rbind(
markowitz_constraint(djia2018, dir = "<=", rhs = 0.5^2),
budget_constraint(djia2018),
use.names = TRUE)
objective(p) <- reward_objective(djia2018)$objective
When using the alabama solver starting values need to be chosen. A good strategy is to use several starting values and compare the results.
lapply(1:10, function(x) runif(length(objective(p))))
mstart <- lapply(mstart, function(s) ROI_solve(p, solver = "alabama", start = s))
solus <- which.max(sapply(solus, solution, type = "objval"))
best_solution <-round(solution(solus[[best_solution]]), 3)
## MMM.Adjusted AXP.Adjusted AAPL.Adjusted BA.Adjusted CAT.Adjusted CVX.Adjusted CSCO.Adjusted KO.Adjusted DIS.Adjusted DOW.Adjusted XOM.Adjusted GS.Adjusted HD.Adjusted IBM.Adjusted INTC.Adjusted JNJ.Adjusted JPM.Adjusted MCD.Adjusted MRK.Adjusted MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted UNH.Adjusted VZ.Adjusted V.Adjusted WMT.Adjusted WBA.Adjusted
## 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
\[\begin{eqnarray*} \min_{x\in \mathbb{R}^N, z\in \mathbb{R}^S, \gamma\in \mathbb{R}}& &\gamma + \frac{1}{(1-0.99)} \sum_{s = 1}^S p_s z_s\\ s.t.&&z_s \geq - r_s^\top x - \gamma,\\ &&z_s \geq 0,\\ && \sum_{i=1}^N u_i\leq 6,\\ && x_i - u_i \leq 0. \end{eqnarray*}\] where \(u\in \mathbb{R}^N\)are binary auxiliary variables.
cvar_objective(djia2018, 0.99)
tmp <- OP()
lp <-
constraints(lp) <- rbind(
$constraint,
tmpbudget_constraint(djia2018),
cardinality_constraint(djia2018, dir = "<=", rhs = 6),
use.names = TRUE
)
## Variable types for z_card_aux must be set to binary.
c((tmp$objective)$L)
obj <-objective(lp) <- c(obj, double(NCOL(constraints(lp)) - length(obj)))
## Error in (function (classes, fdef, mtable) : unable to find an inherited method for function 'constraints' for signature '"OP"'
types(lp) <- rep("C", NCOL(constraints(lp) ))
## Error in (function (classes, fdef, mtable) : unable to find an inherited method for function 'constraints' for signature '"OP"'
types(lp)[grep("z_card_aux", constraints(lp)$names)] <- "B"
## Error in (function (classes, fdef, mtable) : unable to find an inherited method for function 'constraints' for signature '"OP"'
ROI_solve(lp, solver = "glpk")) (sol <-
## Error in ROI_solve(lp, solver = "glpk"): objective is missing, with no default
round(solution(sol)[1:30], 3)
## y_omega_aux1 y_omega_aux2 y_omega_aux3 y_omega_aux4 y_omega_aux5 y_omega_aux6 y_omega_aux7 y_omega_aux8 y_omega_aux9 y_omega_aux10 y_omega_aux11 y_omega_aux12 y_omega_aux13 y_omega_aux14 y_omega_aux15 y_omega_aux16 y_omega_aux17 y_omega_aux18 y_omega_aux19 y_omega_aux20 y_omega_aux21 y_omega_aux22 y_omega_aux23 y_omega_aux24 y_omega_aux25 y_omega_aux26 y_omega_aux27 y_omega_aux28 y_omega_aux29 u_omega_aux1
## 1.321 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.005