Task
Optimize the trade given various utilities and some simple constraints for the case where there are expected returns and no benchmark.
Preparation
- vector of asset prices
- vector of expected returns
- variance matrix for the assets
- 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 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
xaLWvar06
variance matrix from “Returns to variance matrix” examplexaMACD
matrix from “Compute a technical indicator” example to use for expected returnspprobeData
package
You need to have the package loaded into your R session:
require(pprobeData)
Doing it
We’ll do a few optimizations:
- Maximize the information ratio
- Maximize a mean-variance utility
- Maximize expected return given a maximum volatility
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)
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.
Optimization: information ratio
We’re now ready to do an optimization. The only constraint that we impose besides the gross value and being long-only is that no more than 10 assets may be in the portfolio.
opMaxInfo <- trade.optimizer(priceVector, variance=xaLWvar06, expected.return=expRet, existing=curPortfol, gross=grossVal, long.only=TRUE, port.size=10)
We are not specifying what utility to use, so it will use the default which is to maximize the information ratio.
Optimization: mean-variance utility
If we are using a mean-variance utility, we need to decide what risk aversion to use.
opMeanVar <- trade.optimizer(priceVector, variance=xaLWvar06, expected.return=expRet, existing=curPortfol, gross=grossVal, long.only=TRUE, port.size=10, utility="mean-variance", risk.aversion=10)
We’ve added two arguments to the previous optimization. We use the utility
argument to state the form of utility to use, and we use the risk.aversion
argument to state the risk aversion that we want to use.
Note that the form of the utility is: expected return minus risk aversion times variance. Some have a one-half in the last term.
Optimization: maximum return with volatility constraint
An often convenient form of optimization is to constrain the volatility to some maximum value and then maximize the expected return. Here we constrain volatility to 8%.
opMaxExpVC <- trade.optimizer(priceVector, variance=xaLWvar06, expected.return=expRet, existing=curPortfol, gross=grossVal, long.only=TRUE, port.size=10, utility="maximum return", var.constraint=.08^2/252)
We need to translate our 8% volatility into the scale of the variance, which is daily.
Print results
The resulting object is printed like:
> opMaxExpVC $new.portfolio XA199 XA280 XA298 XA351 XA420 XA481 XA802 XA891 35808 12235 3939 345 5388 2847 1366 6415 XA893 XA980 9164 4593 $trade XA101 XA103 XA105 XA107 XA108 XA111 XA113 -1000 -2000 -3000 -4000 -5000 -6000 -7000 XA115 XA120 XA126 XA199 XA280 XA298 XA351 -8000 -9000 -10000 35808 12235 3939 345 XA420 XA481 XA802 XA891 XA893 XA980 5388 2847 1366 6415 9164 4593 $results objective negutil cost penalty -0.02012791 -0.02012791 0.00000000 0.00000000 $converged [1] TRUE $objective.utility [1] "maximum return" $alpha.values A0 0.02012791 $var.values V0 2.539682e-05 $utility.values [1] -0.02012791 $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] "Tue Sep 25 09:52:46 2012" [2] "Tue Sep 25 09:52:55 2012" $call trade.optimizer(prices = priceVector, variance = xaLWvar06, expected.return = expRet, existing = curPortfol, gross = grossVal, long.only = TRUE, port.size = 10, utility = "maximum return", var.constraint = 0.08^2/252)
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
The optimization with the volatility constraint is the easiest 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) volatility we are willing to tolerate.
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(opMaxExpVC, collapse=TRUE)) [1] 18.99
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.
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
You can see more about the optimization with the summary of the object:
> summary(opMaxExpVC) $results objective negutil cost penalty -0.02012791 -0.02012791 0.00000000 0.00000000 $objective.utility [1] "maximum return" $alpha.values A0 0.02012791 $var.values V0 2.539682e-05 $number.of.assets existing trade 10 20 new open 10 10 close universe.total 10 350 tradable select.universe 350 350 positions.notrade 0 $opening.positions [1] "XA199" "XA280" "XA298" "XA351" "XA420" [6] "XA481" "XA802" "XA891" "XA893" "XA980" $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 3033411 3033411 3033411 0 $valuation.trade gross net long short 6046841.01 19981.01 3033411.01 3013430.00 $valuation.trade.fraction.of.gross gross net long short 1.993413023 0.006586977 1.000000000 0.993413023
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.
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.
See also
Navigate
- Back to “Optimize Trades”
- Back to the top level of “Portfolio Probe Cookbook”