As pointed out in a previous blog post, the Kelly Criterion is an interesting option to decide on position sizing in portfolio selection. While the previous post looked at single stocks, I will today show how to optimize position sizes for a portfolio with multiple stocks.
At the core of my portfolio optimization is this function:
The function has three parameters.
shares is the vector of shares (position size for each stock) that is going to be estimated by optimization.
dpf is a matrix where each stock is a column and each line contains the daily stock price movements like e.g.:
maxshare option can be used to restrict the position size of a single stock to a maximum.
exp_returns <- dpf%*%shares calculates the daily (or weekly) portfolio returns.
obj is the Kelly Criterion. The higher the volatility, the larger values
obj will take. We are going to minimize the function so low values, i.e. low volatility is preferred. The next line is a trick to restrict the optimizer to values that sum to 1 (100%). If the sum of all position sizes is 1,
weight.penalty is 0 (the minimum possible value). Would the sum deviate from 1, the
weight.penalty would quickly increase. The second penalty term is similar. If a position is sized above the maximum allowed share, the penalty scores. Finally the function returns a value which the optimizer will try to minimize.
Now to feed stock data into this function I wrote a wrapper function that prepares the matrix of returns and calls
Most of the function is commented between the lines. I introduced two options:
short. The default values are, that short positions are not allowed and the returns are calculated on a weekly, not daily basis. One thing thats important for me is, that I dont use the stock returns as they are but I center them around the mean return that I expect. If I would not do this, the algorithm would always pick the historically best performing stock(s). However past performance is not a good predictor of future performance and one has to do his homework and build own (and realistic) expectations.
To test the function I select some random stocks (not a recommendation to trade these stocks).
This tells the following story: Assuming all stocks have the same expected yearly return of 3%, the long term growth rate of wealth (Kelly Criterion) is achieved by investing in the stock with a lowest volatility (here GIS). At the same time this is a hint, that the assumption that all returns are to be expected as equal is too strong. One could now play with the function to find out how high the expected return of a risky stock has to be, to be included into an existing portfolio.
I personally use this tool as a rough orientation, how stocks from my watchlist would contribute to the risk-return-profile of my portfolio. I usually use the
maxshare option and rather “flat” and moderate return expectations, because the Kelly Criterion highly favors stocks with better return expectations. E.g. if I add 1% additional expected return for GOOG, the numbers change drastically:
If find the tool to be useful because it keeps me from overbetting on very volatile stocks and gives hints, if and when risky stocks are worth to bet on.
There are a few additional points worth mentioning:
opt_portfolio_wrapper()gives the shares to achieve the Kelly-optimal portfolio selection, it does not tell, how much one should bet on the whole portfolio. Approximately this be calculated by dividing the expected portfolio return by the annualized variance. One could also adapt the
kelly()function from the previous blog post to get a number that incorporates the fact, that the return distribution is fat-tailed and non-normal.
- I excluded some of the options that my function originally had for the sake of readability. Options that I found interesting are:
- transaction costs
- “fixed shares” (e.g. my pension account is invested in index funds and bonds. When picking stocks, I might want to incorporate the fact, that I am already exposed to these securities)
getSymbols()if the historic returns should be restricted to a certain range
- transaction costs