## Problem data
set.seed(10)
n <- 10
SAMPLES <- 100
mu <- matrix(abs(rnorm(n)), nrow = n)
Sigma <- matrix(rnorm(n^2), nrow = n, ncol = n)
Sigma <- t(Sigma) %*% Sigma
## Form problem
w <- Variable(n)
ret <- t(mu) %*% w
risk <- quad_form(w, Sigma)
constraints <- list(w >= 0, sum(w) == 1)
## Risk aversion parameters
gammas <- 10^seq(-2, 3, length.out = SAMPLES)
ret_data <- rep(0, SAMPLES)
risk_data <- rep(0, SAMPLES)
w_data <- matrix(0, nrow = SAMPLES, ncol = n)
## Compute trade-off curve
for(i in seq_along(gammas)) {
gamma <- gammas[i]
objective <- ret - gamma * risk
prob <- Problem(Maximize(objective), constraints)
result <- psolve(prob)
check_solver_status(prob)
## Evaluate risk/return for current solution
risk_data[i] <- value(sqrt(risk))
ret_data[i] <- value(ret)
w_data[i,] <- value(w)
}Portfolio Optimization
Introduction
In this example, we solve the Markowitz portfolio problem under various constraints (Markowitz 1952; Roy 1952; Lobo, Fazel, and Boyd 2007).
We have
Portfolio optimization involves a trade-off between the expected return
Example
We construct the risk-return trade-off curve for
Note how we can obtain the risk and return by directly evaluating the value of the separate expressions:
value(risk) [,1]
[1,] 0.104144
value(ret) [,1]
[1,] 0.3489898
The trade-off curve is shown below. The
cbPalette <- brewer.pal(n = 10, name = "Paired")
p1 <- ggplot() +
geom_line(mapping = aes(x = risk_data, y = ret_data), color = "blue") +
geom_point(mapping = aes(x = sqrt(diag(Sigma)), y = mu), color = "red")
markers_on <- c(10, 20, 30, 40)
nstr <- sprintf("gamma == %.2f", gammas[markers_on])
df <- data.frame(markers = markers_on, x = risk_data[markers_on],
y = ret_data[markers_on], labels = nstr)
p1 + geom_point(data = df, mapping = aes(x = x, y = y), color = "black") +
annotate("text", x = df$x + 0.2, y = df$y - 0.05, label = df$labels, parse = TRUE) +
labs(x = "Risk (Standard Deviation)", y = "Return")
We can also plot the fraction of budget invested in each asset.
w_df <- data.frame(paste0("grp", seq_len(ncol(w_data))),
t(w_data[markers_on,]))
names(w_df) <- c("grp", sprintf("gamma == %.2f", gammas[markers_on]))
tidyW <- gather(w_df, key = "gamma", value = "fraction", names(w_df)[-1], factor_key = TRUE)
ggplot(data = tidyW, mapping = aes(x = gamma, y = fraction)) +
geom_bar(mapping = aes(fill = grp), stat = "identity") +
scale_x_discrete(labels = parse(text = levels(tidyW$gamma))) +
scale_fill_manual(values = cbPalette) +
guides(fill = "none") +
labs(x = "Risk Aversion", y = "Fraction of Budget")
Discussion
Many variations on the classical portfolio problem exist. For instance, we could allow long and short positions, but impose a leverage limit
constr <- list(p_norm(w,1) <= Lmax, sum(w) == 1)An alternative is to set a lower bound on the return and minimize just the risk. To account for transaction costs, we could add a term to the objective that penalizes deviations of CVXR with just a few alterations to the code above.
Session Info
R version 4.5.2 (2025-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Tahoe 26.3
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/Los_Angeles
tzcode source: internal
attached base packages:
[1] stats graphics grDevices datasets utils methods base
other attached packages:
[1] tidyr_1.3.2 RColorBrewer_1.1-3 ggplot2_4.0.2 CVXR_1.8.0.9207
loaded via a namespace (and not attached):
[1] gmp_0.7-5.1 generics_0.1.4 clarabel_0.11.2 slam_0.1-55
[5] lattice_0.22-9 digest_0.6.39 magrittr_2.0.4 evaluate_1.0.5
[9] grid_4.5.2 fastmap_1.2.0 rprojroot_2.1.1 jsonlite_2.0.0
[13] Matrix_1.7-4 ECOSolveR_0.6.1 backports_1.5.0 scs_3.2.7
[17] purrr_1.2.1 Rmosek_11.1.1 scales_1.4.0 codetools_0.2-20
[21] cli_3.6.5 rlang_1.1.7 Rglpk_0.6-5.1 withr_3.0.2
[25] yaml_2.3.12 otel_0.2.0 tools_4.5.2 osqp_1.0.0
[29] Rcplex_0.3-8 checkmate_2.3.4 dplyr_1.2.0 here_1.0.2
[33] gurobi_13.0-1 vctrs_0.7.1 R6_2.6.1 lifecycle_1.0.5
[37] htmlwidgets_1.6.4 cccp_0.3-3 pkgconfig_2.0.3 pillar_1.11.1
[41] gtable_0.3.6 glue_1.8.0 Rcpp_1.1.1 xfun_0.56
[45] tibble_3.3.1 tidyselect_1.2.1 knitr_1.51 dichromat_2.0-0.1
[49] highs_1.12.0-3 farver_2.1.2 htmltools_0.5.9 labeling_0.4.3
[53] rmarkdown_2.30 piqp_0.6.2 compiler_4.5.2 S7_0.2.1