Active with benchmark

Task

Optimize the trade given various utilities and some simple constraints for the case where there are expected returns and a benchmark.

Preparation

  • vector of asset prices
  • vector of expected returns
  • variance matrix for the assets
  • information on the benchmark
  • current portfolio (if it exists)
  • Portfolio Probe

You need the prices at which assets trade, and a variance matrix of the asset returns.  You also need an expected return for each asset in the universe.

The benchmark either needs to be an asset in the variance, or you need the asset weights of the benchmark (and all of the assets to be in the variance).

The holdings of the current portfolio need to be in a vector with names that are the asset identifiers.

You also need to have the Portfolio Probe package loaded into your R session:

require(PortfolioProbe)

If you don’t have Portfolio Probe, see “Demo or Buy”.

Doing the example

You need to have the package loaded into your R session:

require(pprobeData)

Doing it

We’ll do a few optimizations:

  • Maximize benchmark-relative information ratio
  • Maximize a  benchmark-relative mean-variance utility
  • Maximize expected return given a maximum tracking error
  • Maximize the information ratio given a maximum tracking error

Preliminaries

The inputs we need in order to get our optimal portfolio are:

  • vector of prices at which the assets may be traded
  • variance matrix of the asset returns
  • vector of expected returns
  • desired value of the new portfolio
  • appropriate constraints
  • current portfolio (optional)
  • information on the benchmark

In the examples we assume that the benchmark is not an asset in the variance matrix.  See the “Further Details” section for the alternative approach.

prices

We start by naming the vector of prices that we want to use:

priceVector <- xassetPrices[251,]

These are the prices at the close of the last trading day of 2006. The first few values are:

> head(priceVector)
 XA101  XA103  XA105  XA107  XA108  XA111 
 33.56  72.25  74.39 192.06   5.91  15.98

The requirement for the prices is that it be a vector of positive numbers with names (that are the asset identifiers).

expected returns

We can get the MACD signal value for the same time point as the prices to use as the expected returns:

> expRet <- xaMACD[251,] / 100
> head(expRet)
       XA101        XA103        XA105 
 0.009178291  0.009114743  0.011844481 
       XA107        XA108        XA111 
 0.008825741  0.014268342 -0.003350204

current portfolio

We create an object to serve as the current portfolio:

curPortfol <- (1:10) * 1000
names(curPortfol) <- colnames(xassetPrices)[1:10]

What is expected is a numeric vector of the number of units of each asset in the portfolio. The names of the vector are the identifiers of the assets that are used in the price vector and the variance matrix.

> curPortfol
XA101 XA103 XA105 XA107 XA108 XA111 XA113 XA115 
 1000  2000  3000  4000  5000  6000  7000  8000 
XA120 XA126 
 9000 10000

portfolio value

The value of the portfolio that we should specify is the current value of the existing portfolio adjusted by whatever cash flow is desired. Here we assume we want to add $20,000 to the portfolio:

cashFlow <- 20000
grossVal <- as.numeric(valuation(curPortfol, 
   priceVector, collapse=TRUE)) + cashFlow

We get the value of the current portfolio assuming the prices we are using and then add the cash flow. (The as.numeric is merely for cosmetic reasons to make the result simpler.) We end up with:

> grossVal
[1] 3033430

The gross value of the portfolio that we want is slightly more than $3 million.

create benchmark weight vector

For the example, we will create a weight vector that equally weights all of the assets in the universe:

benwt <- rep(1/350, 350)
names(benwt) <- rownames(xaLWvar06)

The first few values are:

> head(benwt)
      XA101       XA103       XA105       XA107 
0.002857143 0.002857143 0.002857143 0.002857143 
      XA108       XA111 
0.002857143 0.002857143

The vector of benchmark weights need not have all of the assets that are in the variance matrix nor be in any particular order.  It is mandatory, however, that all of the assets in the benchmark weight vector be in the variance.

Optimization: benchmark-relative information ratio

In this case we want to maximize the quantity: expected return relative to the benchmark divided by the tracking error.

opBenInfo <- trade.optimizer(priceVector, 
   variance=xaLWvar06, expected.return=expRet,
   existing=curPortfol, gross=grossVal, long.only=TRUE, 
   port.size=10, benchmark="EqualWt", 
   bench.weights=list(EqualWt=benwt))

We use the bench.weights argument to  provide the benchmark weights and the name to use for the benchmark.  For active portfolios this is the preferred method as is explained below in the “Further Details” section.

The benchmark argument is required to actually put the benchmark into the utility.  The bench.weights argument merely makes a benchmark available, it does not use it.

This call does not specify the utility, and hence uses the default which is to maximize the information ratio.  When a benchmark is specified this will be the benchmark-relative information ratio.

Optimization:  benchmark-relative mean-variance utility

We need to decide on a risk aversion when we use a mean-variance utility.

opBenMeanVar <- trade.optimizer(priceVector, 
   variance=xaLWvar06, expected.return=expRet,
   existing=curPortfol, gross=grossVal, long.only=TRUE, 
   port.size=10, benchmark="EqualWt", 
   bench.weights=list(EqualWt=benwt), 
   utility="mean-variance", risk.aversion=10)

The differences from the optimization above is that we specified the utility and the risk aversion.

Optimization: expected return given a maximum tracking error

Here we want to maximize the expected return while constraining the tracking error to be no more than some quantity, 6% in this case.

opBenExpRetTC <- trade.optimizer(priceVector, 
   variance=xaLWvar06, expected.return=expRet,
   existing=curPortfol, gross=grossVal, long.only=TRUE, 
   port.size=10, bench.constraint=c(EqualWt=.06^2/252), 
   bench.weights=list(EqualWt=benwt), 
   utility="maximum return")

We have specified the utility, and the benchmark constraint which is in terms of the variance.  Note that there is no benchmark argument (though it would do essentially the same optimization).

Optimization: information ratio given a maximum tracking error

Here we want to maximize the information ratio (not relative to the benchmark) with a maximum constraint on the tracking error.

opBenInfoTC <- trade.optimizer(priceVector, 
   variance=xaLWvar06, expected.return=expRet,
   existing=curPortfol, gross=grossVal, long.only=TRUE, 
   port.size=10, bench.constraint=c(EqualWt=.06^2/252), 
   bench.weights=list(EqualWt=benwt), 
   utility="information ratio")

All we’ve done is change the utility specification to "information ratio".  We would get the same thing by leaving out the utility specification since maximizing the information ratio is the default.

If we added the specification benchmark="EqualWt", then we would be maximizing the benchmark-relative information ratio with the tracking error constraint.

Print results

The resulting object is printed like:

> opBenInfoTC
$new.portfolio
XA141 XA182 XA195 XA199 XA317 XA412 XA420 XA481 
 7043  5650  4553 32479  3379  7416  4942  5734 
XA715 XA893 
 3874  8847 

$trade
 XA101  XA103  XA105  XA107  XA108  XA111  XA113 
 -1000  -2000  -3000  -4000  -5000  -6000  -7000 
 XA115  XA120  XA126  XA141  XA182  XA195  XA199 
 -8000  -9000 -10000   7043   5650   4553  32479 
 XA317  XA412  XA420  XA481  XA715  XA893 
  3379   7416   4942   5734   3874   8847 

$results
objective   negutil      cost   penalty 
-3.855436 -3.855436  0.000000  0.000000 

$converged
[1] FALSE

$objective.utility
[1] "information ratio"

$alpha.values
        A0 
0.02491865 

$var.values
           V0 V0 -- EqualWt 
 4.177363e-05  1.428571e-05 

$utility.values
[1] -3.855436

$existing
XA101 XA103 XA105 XA107 XA108 XA111 XA113 XA115 
 1000  2000  3000  4000  5000  6000  7000  8000 
XA120 XA126 
 9000 10000 

$violated
NULL

$timestamp
[1] "Wed Sep 26 10:12:29 2012"
[2] "Wed Sep 26 10:12:40 2012"

$call
trade.optimizer(prices = priceVector, variance = xaLWvar06, expected.return = expRet, 
    existing = curPortfol, gross = grossVal, long.only = TRUE, 
    port.size = 10, bench.constraint = c(EqualWt = 0.06^2/252), 
    bench.weights = list(EqualWt = benwt), utility = "information ratio")

The first two components are the new (optimal) portfolio and the trade to achieve that. There are some additional components to the object that are not shown.

Explanation

Optimization strategy

Maximizing expected return with a tracking error constraint is the easiest of these to do in practice.  This is because we don’t need the variance and the expected returns to be on the same scale.  We merely need to decide what (expected) tracking error we are willing to tolerate. (Easiest isn’t necessarily best.)

The mean-variance formulation assumes either that the variance and expected returns are matched in scale, or that the risk aversion takes the mismatch into account.  Maximizing the information ratio assumes that the scales are matched.

Technical details

portfolio value

It is mandatory that the value of the resulting portfolio be specified. For long-only portfolios it is sufficient to state the desired gross value. The actual value of the portfolio will (usually) be slightly less than the specification:

> format(grossVal, nsmall=2, big.mark=",")
[1] "3,033,430.00"
> grossVal - as.numeric(valuation(opBenInfo, collapse=TRUE))
[1] 214.42

utility

If both expected returns and variance are given, then the default utility is to maximize the information ratio.  If expected returns are not given, then the default is to minimize variance (or if a benchmark is specified, tracking error).

other output components

One component of the output to pay special attention to is ‘violated‘ — this states which constraints, if any, are violated. You want this to be NULL.

It is probably not important whether ‘converged‘ is TRUE or FALSE. The optimization is likely to be good enough with or without convergence.

Further Details

Summary

You can see more about the optimization with the summary of the object:

> summary(opBenExpRetTC)
$results
  objective     negutil        cost     penalty 
-0.02846031 -0.02846031  0.00000000  0.00000000 

$objective.utility
[1] "maximum return"

$alpha.values
        A0 
0.02846031 

$var.values
           V0 V0 -- EqualWt 
 6.459429e-05  1.428571e-05 

$number.of.assets
         existing             trade 
               10                20 
              new              open 
               10                10 
            close    universe.total 
               10               351 
         tradable   select.universe 
              350               351 
positions.notrade 
                0 

$opening.positions
 [1] "XA176" "XA182" "XA195" "XA199" "XA412"
 [6] "XA481" "XA665" "XA678" "XA715" "XA893"

$closing.positions
 [1] "XA101" "XA103" "XA105" "XA107" "XA108"
 [6] "XA111" "XA113" "XA115" "XA120" "XA126"

$value.limits
        lower   upper
gross 3033127 3033430
net   3033127 3033430
long  3033127 3033430
short       0       0

$valuation.new.portfolio
  gross     net    long   short 
3033321 3033321 3033321       0 

$valuation.trade
     gross        net       long      short 
6046751.37   19891.37 3033321.37 3013430.00 

$valuation.trade.fraction.of.gross
     gross        net       long      short 
1.99344238 0.00655762 1.00000000 0.99344238

This has some pieces that are also in the print method, but new information as well. We see that all of the current portfolio was sold off — a trade to make the broker happy.

Benchmark specification

The examples give information about the benchmark via the bench.weights argument.  In the case where expected returns are given, this is the preferred method because:

  • it is maybe, possibly slightly less hassle
  • it ensures that the benchmark is effectively included into the variance
  • it eliminates the possibility of a subtle problem

The (apparently) best way of including a benchmark into a variance matrix is to use the benchmark weights to do the calculations so that the benchmark covariances are all consistent with the rest of the variance matrix.

The alternative to using the bench.weights argument is to use a variance matrix that has the benchmark as an asset.  In the examples, we could have used the xaLWvar06EqWt variance matrix from “Add benchmark to variance matrix”.  The changes we would need to make (besides not giving bench.weights) would be using the benchmark name in this variance whereever the benchmark is named.

The subtle problem that can arise with this approach is that the expected return of the benchmark might not be consistent with the expected returns of its constituents.  All of the “regular” assets need to have expected returns but benchmarks don’t need to be included in the expected returns vector.

When bench.weights is given, it computes the expected return of the benchmark given the constituent weights and expected returns.  When bench.weights is not given and the benchmark is not an asset in the expected returns, then the benchmark expected return is assumed (silently) to be zero. The expected return of the benchmark matters in the optimization.

Checking your work

It is easy to be unsure that the optimization is doing as intended.  Here we take a closer look at the results so we can be assured of what is being done.

tracking error and volatility

The var.values component of the result of an optimization gives variance values for the optimal portfolio.  Since our variance matrix is from daily data, we annualize variances by multiplying by 252.  Here are the annualized volatilities and tracking errors for our example optimizations:

> sqrt(252 * opBenInfo$var.values)
V0 -- EqualWt 
   0.06440552 
> sqrt(252 * opBenMeanVar$var.values)
V0 -- EqualWt 
    0.2968157 
> sqrt(252 * opBenExpRetTC$var.values)
           V0 V0 -- EqualWt 
    0.1275843     0.0600000 
> sqrt(252 * opBenInfoTC$var.values)
           V0 V0 -- EqualWt 
     0.102601      0.060000

The elements with name "V0" are volatilities and the elements named "V0 -- EqualWt" are tracking errors (that is, volatilities relative to the benchmark).

The first two only involve the tracking error (as we should expect).  The last two involve both the volatility and tracking error (as we should expect).  These latter two that constrain the tracking error are at the constraint.

utility

Here we check the utility of three out of four of the examples.

> opBenInfo$alpha.values / sqrt(opBenInfo$var.values)
A0 -- EqualWt 
     6.220451 
> opBenInfo$utility.values
[1] -6.220451
> opBenExpRetTC$alpha.values
        A0 
0.02846031 
> opBenExpRetTC$utility.values
[1] -0.02846031
> opBenInfoTC$alpha.values / sqrt(opBenInfoTC$var.values["V0"])
      A0 
3.855436 
> opBenInfoTC$utility.values
[1] -3.855436

The utilities have the opposite sign of our calculations because the optimization algorithm minimizes the negative utility (which is equivalent to maximizing utility).

Troubleshooting

  • The variance matrix needs to contain all of the assets that are in the price vector. It can have additional assets — except for benchmarks, these will be ignored. The order of the assets in the variance does not matter.
  • All of the prices need to be in the same currency. You have to check that — the code has no way of knowing.
  • It will still work if the object given as the prices is a one-column or one-row matrix. But it will complain about other matrices.  The same is true for expected returns.
  • Note the comments above about benchmark expected returns.

See also

Navigate