Chapter 4 Managing Portfolios

In this chapter we show how to explore and analyze data using the dataset created in Chapter @ref(#s_2Data):

At first we will learn how to full-sample optimize portfolios, then (in the next chapters) we will do the same thing in a rolling analysis and also perform some backtesting. The major workhorse of this chapter is the portfolioAnalytics-package developed by Peterson and Carl (2018).

portfolioAnalytics comes with an excellent introductory vignette vignette("portfolio_vignette") and includes more documents, detailing on the use of ROI-solvers vignette("ROI_vignette"), how to create custom moment functions vignette("custom_moments_objectives") and how to introduce CVaR-budgets vignette("risk_budget_optimization").

4.1 Introduction

SHORT INTRODUCTION TO PORTFOLIOMANAGEMENT

We start by first creating a portfolio object, before we…

4.2 Tools for Portfolio Management

4.2.1 The Portfolio Object

The portfolio object is a so-called S3-object, which means, that it has a certain class (portfolio) describing its properties, behavior and relation to other objects. Usually such an objects comes with a variety of methods. To create such an object, we reuse the stock data set that we have created in Chapter @ref(#s_2Data):

load("stocks.RData")
glimpse(stocks.final)
## Observations: 2,160
## Variables: 10
## $ symbol   <chr> "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL...
## $ date     <S3: yearmon> Jan 2000, Feb 2000, Mrz 2000, Apr 2000, Mai 2...
## $ return   <dbl> -0.07314358, 0.10481940, 0.18484208, -0.08651642, -0....
## $ adjusted <dbl> 2.489997, 2.750997, 3.259497, 2.977497, 2.015998, 2.5...
## $ volume   <dbl> 175420000, 92240400, 101158400, 62395200, 108376800, ...
## $ sp500    <dbl> -0.041753145, -0.020108083, 0.096719828, -0.030795756...
## $ Mkt.RF   <dbl> -0.0474, 0.0245, 0.0520, -0.0640, -0.0442, 0.0464, -0...
## $ SMB      <dbl> 0.0505, 0.2214, -0.1728, -0.0771, -0.0501, 0.1403, -0...
## $ HML      <dbl> -0.0045, -0.1057, 0.0794, 0.0856, 0.0243, -0.1010, 0....
## $ RF       <dbl> 0.0041, 0.0043, 0.0047, 0.0046, 0.0050, 0.0040, 0.004...
stocks.final %>% slice(1:2)
## # A tibble: 2 x 10
##   symbol date   return adjusted volume   sp500  Mkt.RF    SMB     HML
##   <chr>  <S3:>   <dbl>    <dbl>  <dbl>   <dbl>   <dbl>  <dbl>   <dbl>
## 1 AAPL   Jan ~ -0.0731     2.49 1.75e8 -0.0418 -0.0474 0.0505 -0.0045
## 2 AAPL   Feb ~  0.105      2.75 9.22e7 -0.0201  0.0245 0.221  -0.106 
## # ... with 1 more variable: RF <dbl>

For the portfolioAnalytics-package we need our data in xts-format (see @ref(#sss_112xts)) and therefore first spread the dataset returns in columns of stocks and the convert to xts using tk_xts() from the timetk-package.

returns <- stocks.final %>%
  select(symbol,date,return) %>%
  spread(symbol,return) %>%
  tk_xts(silent = TRUE)

Now its time to initialize the portfolio.spec() object passing along the names of our assets. Afterwards we print the object (most S3 obejcts come with a printing methods that nicely displays some nice information).

pspec <- portfolio.spec(assets = stocks.selection$symbol,
                        category_labels = stocks.selection$sector)
print(pspec)
## **************************************************
## PortfolioAnalytics Portfolio Specification 
## **************************************************
## 
## Call:
## portfolio.spec(assets = stocks.selection$symbol, category_labels = stocks.selection$sector)
## 
## Number of assets: 10 
## Asset Names
##  [1] "AAPL" "MSFT" "AMZN" "CSCO" "NVDA" "ORCL" "AMGN" "ADBE" "QCOM" "GILD"
## 
## Category Labels
## Information Technology : AAPL MSFT CSCO NVDA ORCL ADBE QCOM 
## Consumer Discretionary : AMZN 
## Health Care : AMGN GILD
str(pspec)
## List of 6
##  $ assets         : Named num [1:10] 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1
##   ..- attr(*, "names")= chr [1:10] "AAPL" "MSFT" "AMZN" "CSCO" ...
##  $ category_labels:List of 3
##   ..$ Information Technology: int [1:7] 1 2 4 5 6 8 9
##   ..$ Consumer Discretionary: int 3
##   ..$ Health Care           : int [1:2] 7 10
##  $ weight_seq     : NULL
##  $ constraints    : list()
##  $ objectives     : list()
##  $ call           : language portfolio.spec(assets = stocks.selection$symbol, category_labels = stocks.selection$sector)
##  - attr(*, "class")= chr [1:2] "portfolio.spec" "portfolio"

Checking the structure of the object str() we find that it contains several elements: assets which contains the asset names and initial weights that are equally distributed unless otherwise specified (e.g. portfolio.spec(assets=c(0.6,0.4))), category_labels to categorize assets by sector (or geography etc.), weight_seq (sequence of weights for later use by random_portfolios), constraints that we will set soon, objectives and the call that initialised the object. Before we go and optimize any portfolio we will show how to set contraints.

4.2.2 Constraints

Constraints define restrictions and boundary conditions on the weights of a portfolio. Constraints are defined by add.constraint specifying certain types and arguments for each type, as well as whether the constraint should be enabled or not (enabled=TRUE is the default).

4.2.2.1 Sum of Weights Constraint

Here we define how much of the available budget can/must be invested by specifying the maximum/minimum sum of portfolio weights. Usually we want to invest our entire budget and therefore set type="full_investment" which sets the sum of weights to 1. Alternatively we can set the type="weight_sum" to have mimimum/maximum weight_sum equal to 1.

pspec <- add.constraint(portfolio=pspec,
                        type="full_investment")
# print(pspec)
# pspec <- add.constraint(portfolio=pspec,type="weight_sum", min_sum=1, max_sum=1)

Another common constraint is to have the portfolio dollar-neutral type="dollar_neutral" (or equivalent formulations specified below)

# pspec <- add.constraint(portfolio=pspec,
#                         type="dollar_neutral")
# print(pspec)
# pspec <- add.constraint(portfolio=pspec, type="active")
# pspec <- add.constraint(portfolio=pspec, type="weight_sum", min_sum=0, max_sum=0)

4.2.2.2 Box Constraint

Box constraints specify upper and lower bounds on the asset weights. If we pass min and max as scalars then the same max and min weights are set per asset. If we pass vectors (that should be of the same length as the number of assets) we can specify position limits on individual stocks

pspec <- add.constraint(portfolio=pspec,
                        type="box",
                        min=0,
                        max=0.4)
# print(pspec)
# add.constraint(portfolio=pspec, 
#                         type="box", 
#                         min=c(0.05, 0, rep(0.05,8)), 
#                         max=c(0.4, 0.3, rep(0.4,8)))

Another special type of box constraints are long-only constraints, where we only allow positive weights per asset. These are set automatically, if no min and max are set or when we use type="long_only"

# pspec <- add.constraint(portfolio=pspec, type="box")
# pspec <- add.constraint(portfolio=pspec, type="long_only")

4.2.2.3 Group Constraints

Group constraints allow the user to specify constraints per groups, such as industries, sectors or geography.3 These groups can be randomly defined, below we will set group constraints for the sectors as specified above. The input arguments are the following: groupslist of vectors specifying the groups of the assets, group_labels character vector to label the groups (e.g. size, asset class, style, etc.), group_min and group_max specifying minimum and maximum weight per group, group_pos to specifying the number of non-zero weights per group (optional).

pspec <- add.constraint(portfolio=pspec,
                        type="group",
                        groups=list(pspec$category_labels$`Information Technology`, # list of vectors specifying the groups of the assets
                                 pspec$category_labels$`Consumer Discretionary`,
                                 pspec$category_labels$`Health Care`),
                        group_min=c(0.1, 0.15,0.1),
                        group_max=c(0.85, 0.55,0.4),
                        group_labels=pspec$category_labels)
# print(pspec)

4.2.2.4 Position Limit Constraint

The position limit constraint allows the user to specify limits on the number of assets with non-zero, long, or short positions. Its arguments are: max_pos which defines the maximum number of assets with non-zero weights and max_pos_long/ max_pos_short that specify the maximum number of assets with long (i.e. buy) and short (i.e. sell) positions.4

pspec <- add.constraint(portfolio=pspec, type="position_limit", max_pos=3)
# pspec <- add.constraint(portfolio=pspec, type="position_limit", max_pos_long=3, max_pos_short=3))
# print(pspec)

4.2.2.5 Diversification Constraint

The diversification constraint enables to set a minimum diversification limit by penalizing the optimizer if the deviation is larger than 5%. Diversification is defined as \(\sum_{i=1}^{N}w_i^2\) for \(N\) assets.5 Its only argument is the diversification taregt div_target.

pspec <- add.constraint(portfolio=pspec, type="diversification", div_target=0.7)
# print(pspec)

4.2.2.6 Turnover Constraint

The turnover constraint allows to specify a maximum turnover from a set of initial weights that can either be given or are the weights initially specified for the portfolio object. It is also implemented as an optimization penalty if the turnover deviates more than 5% from the turnover_target.6

pspec <- add.constraint(portfolio=pspec, type="turnover", turnover_target=0.2)
# print(pspec)

4.2.2.7 Target Return Constraint

The target return constraint allows the user to target an average return specified by return_target.

pspec <- add.constraint(portfolio=pspec, type="return", return_target=0.007)
# print(pspec)

4.2.2.8 Factor Exposure Constraint

The factor exposure constraint allows the user to set upper and lower bounds on exposures to risk factors. We will use the factor exposures that we have calculated in @(#sss_3FactorExposure). The major input is a vector or matrix B and upper/lower bounds for the portfolio factor exposure. If B is a vector (with length equal to the number of assets), lower and upper bounds must be scalars. If B is a matrix, the number of rows must be equal to the number of assets and the number of columns represent the number of factors. In this case, the length of lower and upper bounds must be equal to the number of factors. B should have column names specifying the factors and row names specifying the assets.

B <- stocks.factor_exposure %>% as.data.frame() %>% column_to_rownames("symbol")
pspec <- add.constraint(portfolio=pspec, type="factor_exposure",
                        B=B,
                        lower=c(0.8,0,-1), 
                        upper=c(1.2,0.8,0))
# print(pspec)

4.2.2.9 Transaction Cost Constraint

The transaction cost constraint enables the user to specify (porportional) transaction costs.7 Here we will assume the proportional transation cost ptc to be equal to 1%.

pspec <- add.constraint(portfolio=pspec, type="transaction_cost", ptc=0.01)
# print(pspec)

4.2.2.10 Leverage Exposure Constraint

The leverage exposure constraint specifies a maximum level of leverage. Below we set leverage to 1.3 to create a 130/30 portfolio.

pspec <- add.constraint(portfolio=pspec, type="leverage_exposure", leverage=1.3)
# print(pspec)

4.2.2.11 Checking and en-/disabling constraints

Every constraint that is added to the portfolio object gets a number according to the order it was set. If one wants to update (enable/disable) a specific constraints this can be done by the indexnum argument.

summary(pspec)
# To get an overview on the specs, their indexnum and whether they are enabled I suggest the follwoing
consts <- plyr::ldply(pspec$constraints, function(x){c(x$type,x$enabled)})
consts
pspec$constraints[[which(consts$V1=="box")]]
pspec <- add.constraint(pspec, type="box",
                        min=0, max=0.5, 
                        indexnum=which(consts$V1=="box"))
pspec$constraints[[which(consts$V1=="box")]]
# to disable constraints
pspec$constraints[[which(consts$V1=="position_limit")]]
pspec <- add.constraint(pspec, type="position_limit", enable=FALSE, # only specify argument if you do enable the constraint
                        indexnum=which(consts$V1=="position_limit"))
pspec$constraints[[which(consts$V1=="position_limit")]]

4.2.3 Objectives

For an optimal portfolio there first has to be specified what optimal in terms of the relevant (business) objective. Such objectives (target functions) can be added to the portfolio object with add.objective. With this function, the user can specify the type of objective to add to the portfolio object. Currently available are ‘return’, ‘risk’, ‘risk budget’, ‘quadratic utility’, ‘weight concentration’, ‘turnover’ and ‘minmax’. Each type of objective has additional arguments that need to be specified. Several types of objectives can be added and enabled or disabled by specifying the indexnum argument.

4.2.3.1 Portfolio Risk Objective

Here, the user can specify a risk function that should be minimized. We start by adding a risk objective to minimize portfolio variance (minimum variance portfolio). Another example could be the expected tail loss with a confidence level 0.95. Whatever function (even user defined ones are possble, the name must correspond to a function in R), necessary additional arguments to the function have to be passed as a named list to arguments. Possible functions are:

pspec <- add.objective(portfolio=pspec,
                       type='risk',
                       name='var')
pspec <- add.objective(portfolio=pspec,
                       type='risk',
                       name='ETL',
                       arguments=list(p=0.95),
                       enabled=FALSE)
# print(pspec)

4.2.3.2 Portfolio Return Objective

The return objective allows the user to specify a return function to maximize. Here we add a return objective to maximize the portfolio mean return.

pspec <- add.objective(portfolio=pspec,
                       type='return',
                       name='mean')
# print(pspec)

4.2.3.3 Portfolio Risk Budget Objective

The portfolio risk objective allows the user to specify constraints to minimize component contribution (i.e. equal risk contribution) or specify upper and lower bounds on percentage risk contribution. Here we specify that no asset can contribute more than 30% to total portfolio risk.

See the risk budget optimization vignette for more detailed examples of portfolio optimizationswith risk budgets.

pspec <- add.objective(portfolio=pspec, 
                       type="risk_budget",
                       name="var",
                       max_prisk=0.3)
pspec <- add.objective(portfolio=pspec, 
                       type="risk_budget",
                       name="ETL",
                       arguments=list(p=0.95),
                       max_prisk=0.3,
                       enabled=FALSE)
# for an equal risk contribution portfolio, set min_concentration=TRUE
pspec <- add.objective(portfolio=pspec, 
                       type="risk_budget",
                       name="ETL",
                       arguments=list(p=0.95),
                       min_concentration=TRUE,
                       enabled=FALSE)
print(pspec)
## **************************************************
## PortfolioAnalytics Portfolio Specification 
## **************************************************
## 
## Call:
## portfolio.spec(assets = stocks.selection$symbol, category_labels = stocks.selection$sector)
## 
## Number of assets: 10 
## Asset Names
##  [1] "AAPL" "MSFT" "AMZN" "CSCO" "NVDA" "ORCL" "AMGN" "ADBE" "QCOM" "GILD"
## 
## Category Labels
## Information Technology : AAPL MSFT CSCO NVDA ORCL ADBE QCOM 
## Consumer Discretionary : AMZN 
## Health Care : AMGN GILD 
## 
## 
## Constraints
## Enabled constraint types
##      - full_investment 
##      - box 
##      - group 
##      - position_limit 
##      - diversification 
##      - turnover 
##      - return 
##      - factor_exposure 
##      - transaction_cost 
##      - leverage_exposure 
## 
## Objectives:
## Enabled objective names
##      - var 
##      - mean 
##      - var 
## Disabled objective names
##      - ETL

4.2.4 Solvers

Solvers are the workhorse of our portfolio optimization framework, and there are a variety of them available to us through the portfolioAnalytics-package. I will briefly introduce the available solvers. Note that these solvers can be specified through optimize_method in the optimize.portfolio and optimize.portfolio.rebalancing method.

4.2.4.1 DEOptim

This solver comes from the R package DEoptim and is a differential evolution algorithm (a global stochastic optimization algorithm) developed by (???). The help on ?DEoptim gives many more references. There is also a nice vignette("DEoptimPortfolioOptimization") on large scale portfolio optimization using the portfolioAnalytics-package.

4.2.4.2 Random Portfolios

There are three methods to generate random portfolios contained in portfolioAnalytics:8

  1. The most flexible but also slowest method is ‘sample’. It can take leverage, box, group, and position limit constraints into account.
  2. The ‘simplex’ method is useful to generate random portfolios with the full investment and min box constraints (values for min_sum/ max_sum are ignored). Other constraints (box max, group and position limit constraints will be handled by elimination) which might leave only very few feasible portfolios. Sometimes it will also lead to suboptimal solutions.
  3. Using grid search, the ‘grid’ method only satisfies the min and max box constraints.

4.2.4.3 pso

The psoptim function from the R package pso (Bendtsen. 2012) and uses particle swarm optimization.

4.2.4.4 GenSA

The GenSA function from the R package GenSA (Gubian et al. 2018) and is based on generalized simmulated annealing (a generic probabilistic heuristic optimization algorithm)

4.2.4.5 ROI

The ROI (R Optimization Infrastructure) is a framework to handle optimization problems in R. It serves as an interface to the Rglpk package and the quadprog package which solve linear and quadratic programming problems. Available methods in the context of the portfolioAnalytics-package are given below (see section @(#sss_4Objectives) for available objectives.

  1. Maxmimize portfolio return subject leverage, box, group, position limit, target mean return, and/or factor exposure constraints on weights.
  2. Globally minimize portfolio variance subject to leverage, box, group, turnover, and/or factor exposure constraints.
  3. Minimize portfolio variance subject to leverage, box, group, and/or factor exposure constraints given a desired portfolio return.
  4. Maximize quadratic utility subject to leverage, box, group, target mean return, turnover, and/or factor exposure constraints and risk aversion parameter. (The risk aversion parameter is passed into optimize.portfolio as an added argument to the portfolio object).
  5. Minimize ETL subject to leverage, box, group, position limit, target mean return, and/or factor exposure constraints and target portfolio return.

4.2.4.6 Multicore capabilities

For large sets of assets and solvers that can parallelize, the doParallel-package provides multicore capabilities to the portfolioAnalytics package.

# require(doParallel)
# registerDoParallel()

4.3 Optimization examples

Modern portfolio theory suggests how rational investors should optimize their portfolio(s) of risky assets to take advantage of diversification effects. This is possible, because risk as opposed to return is not additive and depends very much on the pairwise comovement (correlation) between the risky assets. The concepts of modern portfolio theory go back to Markowitz (1952) considers asset returns as random variables and estimates their risk and return plainly as mean and standard deviation within the available data sample. Usually we do not introduce any constraints except the full investment constraint that constrains the portfolio weights \(w\) to sum to \(1\), but allow for unlimited shortselling. The two basic portfolio problems are either to minimize the portfolio risk \(w'\Sigma w\) given a target return \(\bar{r}\) (4.1) or maximize the portfolio return \(w'\mu\) given a target level of risk \(\bar{\sigma}\) (4.2): \[\begin{align} & \underset{w}{\text{min}} & & w'\hat{\Sigma}w \\ & \text{subject to} & & w'\hat{\mu}=\bar{r}, \\ &&& w'\mathbf{1}=1. \tag{4.1} \end{align}\] and \[\begin{align} & \underset{w}{\text{max}} & & w'\hat{\mu}\\ & \text{subject to} & & w'\hat{\Sigma}w=\bar{\sigma}, \\ &&& w'\mathbf{1}=1. \tag{4.2} \end{align}\]

The solutions to these two problems is a hyperbola that depicts the efficient frontier in the \(\mu\)-\(\sigma\)-diagram. We can plot the efficient frontier using create.EfficientFrontier9 if we specify a return (mean) and a risk (sd) objective:10

pspec <- portfolio.spec(assets = stocks.selection$symbol,
                        category_labels = stocks.selection$sector)
pspec <- add.constraint(portfolio=pspec,
                        type="full_investment") # weights sum to 1
p <- add.constraint(portfolio=p,
                        type="box",
                    min = -0.5,
                    max = +0.5)
# to create the efficient frontier 
pspec <- add.objective(portfolio=pspec, 
                       type="return",
                       name="mean") # mean
pspec <- add.objective(portfolio=pspec, 
                       type="risk",
                       name="var") # uses sd
meansd.ef <- create.EfficientFrontier(
                      R = returns,
                      portfolio = pspec,
                      type = "mean-sd",
                      n.portfolios = 25,
                      )
# summary(meansd.ef, digits=2) # to print the whole efficient frontier
# meansd.ef$frontier[1:2,] # shows the first two portfolios
chart.EfficientFrontier(meansd.ef,
                      match.col="StdDev", # which column to use for risk
                      type="l", 
                      RAR.text="Sharpe Ratio",
                      tangent.line = FALSE,
                      chart.assets=TRUE,
                      labels.assets=TRUE,xlim=c(0.03,0.20),ylim=c(0,0.055))