In this post, I want to present a framework for formulating portfolio with targeted risk or return. The basic idea was inspired by controlling risk from a different point of view. The traditional way of controlling for portfolio risk was to apply a given set of weights to historical data to calculate historical risk. If estimated portfolio risk exceeds a threshold, we peel off allocation percentages for each asset. In this framework, I focus on constructing portfolios that target a given risk or return on a efficient risk return frontier.

First lets get some data to so we can visualize traditional portfolio optimization’s risk return characteristics. I will be using a 8 asset ETF universe.

rm(list=ls()) setInternet2(TRUE) con = gzcon(url('http://www.systematicportfolio.com/sit.gz', 'rb')) source(con) close(con) tickers = spl('EEM,EFA,GLD,IWM,IYR,QQQ,SPY,TLT') data <- new.env() getSymbols(tickers, src = 'yahoo', from = '1980-01-01', env = data, auto.assign = T) for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T) bt.prep(data, align='keep.all', dates='2000:12::')

Here are the return streams we are working with

The optimization algorithms I will employ are the following:

- Minimum Variance Portfolio
- Risk Parity Portfolio
- Equal Risk Contribution Portfolio
- Maximum Diversification Portfolio
- Max Sharpe Portfolio

To construct the risk return plane, I will put together the necessary input assumptions (correlation, return, covariance, etc). This can be done with create.historical.ia function in the SIT tool box.

#input Assumptions prices = data$prices n=ncol(prices) ret = prices/mlag(prices)-1 ia = create.historical.ia(ret,252) # 0 <= x.i <= 1 constraints = new.constraints(n, lb = 0, ub = 1) constraints = add.constraints(diag(n), type='>=', b=0, constraints) constraints = add.constraints(diag(n), type='<=', b=1, constraints) # SUM x.i = 1 constraints = add.constraints(rep(1, n), 1, type = '=', constraints)

With the above we can go ahead and input both ‘ia’ and ‘constraints’ in to the above optimization algorithms to get weights. With the weights, we can derive the portfolio risk and portfolio return. These then can be plotted on a risk return plain visually.

# create efficient frontier ef = portopt(ia, constraints, 50, 'Efficient Frontier') plot.ef(ia, list(ef), transition.map=F)

The risk return plain in the above image shows all the possible space to which a portfolio’s risk and return characteristic can reside. Anything that is beyond to the left side of the frontier do not exist (unless leverage, to which the EF will also shift leftward too). Since I am more of a visual guy, I tend to construct this risk return plain whenever I am working on new allocation algorithms. This allows me to compare with other portfolio the expected risk and return.

As you can see, each portfolio algorithm has their own set of characteristics. Note that these characteristics fluctuate across the frontier were we to frame this rolling through time. A logical extension to these risk return concepts is to construct a portfolio that aims to target ether a given risk or a given return on the frontier. To formulate this problem in SIT for the return component, simply modify the constraints as follows:

constraints = add.constraints(ia$expected.return,type='>=', b=target.return, constraints)

Note that the target.return variable is simply a variable storing the desired target return. After adding the constraint, simply run a minimum variance portfolio and you will get a target return portfolio. On the other hand, targeting risk is a bit more complicated. If you look at the efficient frontier, you will find that for a given level of risk there is two portfolios that line on it. (The sub-optimal portion of the efficient frontier is hidden). I solved for the weights using a multi optimization framework which employed both linear and quadratic (dual) optimization.

target.risk.obj<-function(ia,constraints,target.risk){ max.w = max.return.portfolio(ia, constraints) min.w = min.var.portfolio(ia, constraints) max.r = sum(max.w * ia$expected.return) min.r = sum(min.w * ia$expected.return) max.risk = portfolio.risk(max.w,ia) min.risk = portfolio.risk(min.w,ia) # If target risk exists as an efficient portfolio else # return weights of 0 if(target.risk >= min.risk | target.risk <= max.risk){ out <-optimize(f =target.return.risk.helper, interval = c(0,max.r), target.risk = target.risk, ia = ia, constraints = constraints)$minimum weight=target.return.portfolio(out)(ia,constraints) }else{ weight=rep(0,ia$n) } return(weight) }

Below is a simple backtest that takes the above assets and optimizes for the target return or target risk component. Each will run with a target of 8%.

Now the model itself requires us to specify a return or risk component. What if instead we make that a dynamic component such that we extract ether the risk or return component of a alternative sizing algorithm. Below are the performance of the dynamic risk or return component extracted from naive risk parity.

Not surprisingly, whenever we target risk, the strategy tends to become more risky. This confirms confirms risk based allocations are superior if investors are aiming to achieve low long term volatility.

Thanks for reading,

Mike

Amazing work & I love how your ran through best practice optimizations. Thank your for the contribution. I am quite new to R, and I am stuck where you call the variable “target.return” into the constraints. I don’t see the code where that gets defined, and it gets flagged as error as I try to replicate it.

Would you kindly point me in the right direction, or perhaps their is a more complete version of the code you might send me?

Thanks very much.

i think that variable is an user defined decimal. for example 0.07 if you are targeting a 7% return. sorry its been quite some time and I am not sure where I placed the code.

Hey, thanks for the quick response. I will try that. I know how it is with “old” work, but I don’t think the field has really improved on the framework since then.

yea no problem. let me know how that works out. I’d be interested in seeing updated results if you care to share

Got it to run successfully; now I just need to study SIT library to output cum performance and metrics table.