Skip to content

Commit

Permalink
typos
Browse files Browse the repository at this point in the history
  • Loading branch information
Reckziegel committed Aug 16, 2023
1 parent bad549a commit 6bab00a
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 72 deletions.
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
^\.github$
^codecov\.yml$
^cran-comments\.md$
^CRAN-SUBMISSION$
3 changes: 3 additions & 0 deletions CRAN-SUBMISSION
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Version: 0.1.0
Date: 2023-07-27 21:45:01 UTC
SHA: bad549aabedebd983d9fc00884262e14bb5d091e
74 changes: 47 additions & 27 deletions R/epo.R
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
#' Enhanced Portfolio Optimization (EPO)
#'
#' Computes the optimal portfolio allocation using the EPO method with full-investment
#' constraint.
#' Computes the optimal portfolio allocation using the EPO method.
#'
#' @param x A data-set with asset returns. It should be a \code{tibble}, a \code{xts}
#' or a \code{matrix}.
#' @param signal A \code{double} with the investor's belief's (signals, forecasts).
#' @param method A \code{character}. One of: `simple` or `anchored`.
#' @param signal A \code{double} vector with the investor's belief's (signals, forecasts).
#' @param lambda A \code{double} with the investor's risk-aversion preference.
#' @param method A \code{character}. One of: `"simple"` or `"anchored"`.
#' @param w A \code{double} between \code{0} and \code{1}. The shrinkage level
#' increases from 0 to 1.
#' @param anchor A \code{double} with the investor anchor (benchmark), in which
#' @param anchor A \code{double} vector with the anchor (benchmark) in which
#' the allocation should not deviate too much from. Only used when `method = "anchored"`.
#' @param normalize A \code{boolean} indicating whether the allocation should be
#' normalized to sum \code{1} (full-investment constraint). The default is `normalize = TRUE`.
#' @param endogenous A \code{boolean} indicating whether the risk-aversion parameter
#' should be considered endogenous (only used when `method = "anchored"`).
#' The default is `endogenous = TRUE`.
#'
#' @return The optimal allocation vector.
#' @export
Expand All @@ -24,13 +29,13 @@
#' ##################
#'
#' # Traditional Mean-Variance Analysis
#' epo(x = x, signal = s, method = "simple", w = 0)
#' epo(x = x, signal = s, lambda = 10, method = "simple", w = 0)
#'
#' # 100% Shrinkage
#' epo(x = x, signal = s, method = "simple", w = 1)
#' epo(x = x, signal = s, lambda = 10, method = "simple", w = 1)
#'
#' # 50% Classical MVO and 50% Shrinkage
#' epo(x = x, signal = s, method = "simple", w = 0.5)
#' epo(x = x, signal = s, lambda = 10, method = "simple", w = 0.5)
#'
#' ####################
#' ### Anchored EPO ###
Expand All @@ -39,57 +44,59 @@
#' benchmark <- rep(0.25, 4) # 1/N Portfolio
#'
#' # Traditional Mean-Variance Analysis
#' epo(x = x, signal = s, method = "anchored", w = 0.0, anchor = benchmark)
#' epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.0, anchor = benchmark)
#'
#' # 100% on the Anchor portfolio
#' epo(x = x, signal = s, method = "anchored", w = 1.0, anchor = benchmark)
#' epo(x = x, signal = s, lambda = 10, method = "anchored", w = 1.0, anchor = benchmark)
#'
#' # Somewhere between the two worlds
#' epo(x = x, signal = s, method = "anchored", w = 0.5, anchor = benchmark)
epo <- function(x, signal, method = c("simple", "anchored"), w, anchor = NULL) {
#' epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.5, anchor = benchmark)
epo <- function(x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE) {

UseMethod("epo", x)

}

#' @rdname epo
#' @export
epo.default <- function(x, signal, method = c("simple", "anchored"), w, anchor = NULL) {
epo.default <- function(x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE) {

rlang::abort("`x` must be a tibble, xts or a matrix.")

}

#' @rdname epo
#' @export
epo.tbl <- function(x, signal, method = c("simple", "anchored"), w, anchor = NULL) {
epo.tbl <- function(x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE) {

epo_(x = tbl_to_mtx(x), signal = signal, method = method, w = w, anchor = anchor)
epo_(x = tbl_to_mtx(x), signal = signal, lambda = lambda, method = method, w = w, anchor = anchor, normalize = normalize, endogenous = endogenous)

}

#' @rdname epo
#' @export
epo.xts <- function(x, signal, method = c("simple", "anchored"), w, anchor = NULL) {
epo.xts <- function(x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE) {

epo_(x = as.matrix(x), signal = signal, method = method, w = w, anchor = anchor)
epo_(x = as.matrix(x), signal = signal, lambda = lambda, method = method, w = w, anchor = anchor, normalize = normalize, endogenous = endogenous)

}

#' @rdname epo
#' @export
epo.matrix <- function(x, signal, method = c("simple", "anchored"), w, anchor = NULL) {
epo.matrix <- function(x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE) {

epo_(x = x, signal = signal, method = method, w = w, anchor = anchor)
epo_(x = x, signal = signal, lambda = lambda, method = method, w = w, anchor = anchor, normalize = normalize, endogenous = endogenous)

}

#' @keywords internal
epo_ <- function(x, signal, method = c("simple", "anchored"), w, anchor = NULL) {
epo_ <- function(x, signal, lambda, method = c("simple", "anchored"), w, anchor = NULL, normalize = TRUE, endogenous = TRUE) {

# Error Handling
assertthat::assert_that(assertthat::is.string(method))
assertthat::assert_that(assertthat::is.number(lambda))
assertthat::assert_that(assertthat::is.number(w))
assertthat::assert_that(assertthat::is.flag(normalize))

if (NCOL(signal) > 1) {
signal <- matrix(signal)
Expand Down Expand Up @@ -127,15 +134,23 @@ epo_ <- function(x, signal, method = c("simple", "anchored"), w, anchor = NULL)
# The simple EPO
if (method == "simple") {

.epo <- inv_shrunk_cov %*% signal # equation 16
.epo <- (1 / lambda) * inv_shrunk_cov %*% signal # equation 16

# The anchored EPO
} else if (method == "anchored") {

# footnote 13
.gamma <- c((sqrt(t(a) %*% cov_tilde %*% a) / sqrt(t(s) %*% inv_shrunk_cov %*% cov_tilde %*% inv_shrunk_cov %*% s)))
# equation 17
.epo <- inv_shrunk_cov %*% (((1 - w) * .gamma * s) + ((w * I %*% V %*% a)))
if (endogenous) {

# footnote 13
.gamma <- c((sqrt(t(a) %*% cov_tilde %*% a) / sqrt(t(s) %*% inv_shrunk_cov %*% cov_tilde %*% inv_shrunk_cov %*% s)))
# equation 17
.epo <- inv_shrunk_cov %*% (((1 - w) * .gamma * s) + ((w * I %*% V %*% a)))

} else {

.epo <- inv_shrunk_cov %*% (((1 - w) * (1 / lambda) * s) + ((w * I %*% V %*% a)))

}

# Error
} else {
Expand All @@ -144,8 +159,13 @@ epo_ <- function(x, signal, method = c("simple", "anchored"), w, anchor = NULL)

}

# Full-Investment constraint
.epo <- as.double(.epo / sum(.epo))
if (normalize) {

# Force full-investment constraint
.epo <- as.double(.epo / sum(.epo))

}

.epo

}
Expand Down
14 changes: 7 additions & 7 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ knitr::opts_chunk$set(
<!-- badges: start -->

[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) [![R-CMD-check](https://github.com/Reckziegel/epo/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/Reckziegel/epo/actions/workflows/R-CMD-check.yaml) [![Codecov test coverage](https://codecov.io/gh/Reckziegel/epo/branch/main/graph/badge.svg)](https://app.codecov.io/gh/Reckziegel/epo?branch=main)

[![CRAN status](https://www.r-pkg.org/badges/version/epo)](https://CRAN.R-project.org/package=epo)
<!-- badges: end -->

The Enhanced Portfolio Optimization (EPO) method, described in Pedersen, Babu and Levine (2021), proposes a unifying theory on portfolio optimization. Employing Principal Component Analysis (PCA), the EPO method ranks portfolios based on their variance, from the most to the least important principal components. Notably, the least important principal components emerge as "problem portfolios", primarily due to their low *estimated* risk, leading to the underestimation of their *true* risks. These portfolios offer high expected returns (*ex-ante*) and low realized Sharpe Ratios (*ex-post*), underscoring the challenges faced when using them through standard approaches.
Expand Down Expand Up @@ -59,13 +59,13 @@ s <- colMeans(x) # it could be any signal
##################
# Traditional Mean-Variance Analysis
epo(x = x, signal = s, method = "simple", w = 0)
epo(x = x, signal = s, lambda = 10, method = "simple", w = 0)
# 100% Shrinkage
epo(x = x, signal = s, method = "simple", w = 1)
epo(x = x, signal = s, lambda = 10, method = "simple", w = 1)
# 50% Classical MVO and 50% Shrinkage
epo(x = x, signal = s, method = "simple", w = 0.5)
epo(x = x, signal = s, lambda = 10, method = "simple", w = 0.5)
####################
### The Anchored EPO
Expand All @@ -74,13 +74,13 @@ epo(x = x, signal = s, method = "simple", w = 0.5)
benchmark <- rep(0.25, 4) # 1/N Portfolio
# Traditional Mean-Variance Analysis
epo(x = x, signal = s, method = "anchored", w = 0.0, anchor = benchmark)
epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.0, anchor = benchmark)
# 100% on the Anchor portfolio
epo(x = x, signal = s, method = "anchored", w = 1.0, anchor = benchmark)
epo(x = x, signal = s, lambda = 10, method = "anchored", w = 1.0, anchor = benchmark)
# 50% on Mean-Variance Analysis and 50% on the Anchor Portfolio
epo(x = x, signal = s, method = "anchored", w = 0.5, anchor = benchmark)
epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.5, anchor = benchmark)
```

## References
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](h
[![R-CMD-check](https://github.com/Reckziegel/epo/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/Reckziegel/epo/actions/workflows/R-CMD-check.yaml)
[![Codecov test
coverage](https://codecov.io/gh/Reckziegel/epo/branch/main/graph/badge.svg)](https://app.codecov.io/gh/Reckziegel/epo?branch=main)

[![CRAN
status](https://www.r-pkg.org/badges/version/epo)](https://CRAN.R-project.org/package=epo)
<!-- badges: end -->

The Enhanced Portfolio Optimization (EPO) method, described in Pedersen,
Expand Down Expand Up @@ -87,15 +88,15 @@ s <- colMeans(x) # it could be any signal
##################

# Traditional Mean-Variance Analysis
epo(x = x, signal = s, method = "simple", w = 0)
epo(x = x, signal = s, lambda = 10, method = "simple", w = 0)
#> [1] 0.1914569 0.9894828 -0.3681779 0.1872382

# 100% Shrinkage
epo(x = x, signal = s, method = "simple", w = 1)
epo(x = x, signal = s, lambda = 10, method = "simple", w = 1)
#> [1] 0.2352863 0.3659986 0.1375249 0.2611902

# 50% Classical MVO and 50% Shrinkage
epo(x = x, signal = s, method = "simple", w = 0.5)
epo(x = x, signal = s, lambda = 10, method = "simple", w = 0.5)
#> [1] 0.223281853 0.564005906 -0.009868083 0.222580324

####################
Expand All @@ -105,15 +106,15 @@ epo(x = x, signal = s, method = "simple", w = 0.5)
benchmark <- rep(0.25, 4) # 1/N Portfolio

# Traditional Mean-Variance Analysis
epo(x = x, signal = s, method = "anchored", w = 0.0, anchor = benchmark)
epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.0, anchor = benchmark)
#> [1] 0.1914569 0.9894828 -0.3681779 0.1872382

# 100% on the Anchor portfolio
epo(x = x, signal = s, method = "anchored", w = 1.0, anchor = benchmark)
epo(x = x, signal = s, lambda = 10, method = "anchored", w = 1.0, anchor = benchmark)
#> [1] 0.25 0.25 0.25 0.25

# 50% on Mean-Variance Analysis and 50% on the Anchor Portfolio
epo(x = x, signal = s, method = "anchored", w = 0.5, anchor = benchmark)
epo(x = x, signal = s, lambda = 10, method = "anchored", w = 0.5, anchor = benchmark)
#> [1] 0.2374674 0.4557503 0.1004711 0.2063111
```

Expand Down
93 changes: 73 additions & 20 deletions man/epo.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 6bab00a

Please sign in to comment.