Toy Model
Various stocks with rewards and risks on a scale of 0-3 (0 low risk, 3 high risk) are shown below.
Stock Name |
Reward |
Risk
|
AAA |
15% |
3
|
BBB |
12% |
2
|
CCC |
4% |
0.5
|
Decision Variables
Optimization Function
Constraints
Slack Variables
Worked Solution
Dual Problem
Objective Function
Constraints
Slack Variables
Worked Solution
Interpretation of Results
In both the primal and the dual the optimal solutions, z and w respectively, are 9. This tells us that using the investment distribution given by the primal we should see a return of 9% on our initial investment.
Now, consider the variables from the optimal solution to the dual problem.
: this variable refers to the first constraint. The first constraint puts an upper bound on the amount of total risk we are willing to take on. This means
gives the increase in output for an additional unit of risk we're willing to take on. So in this case, increasing the risk we'd take by 1 increases out total return by 4%.
: this variable refers to the second constraint. Here,
. Lets consider what happens if you loosen the constraint by 0.1. In this case the increase in return would go up by 0.1%.
and
: these variables correspond with the constraints derived from
. These would be the changes in return percentage based on borrowing additional money; however, borrowing is outside the scope of this toy problem so we will not discuss this any further.
Actual Portfolio
We will use these options from TD bank.
Various BMO ETFs with rewards (3 year annualized return), risks on a scale of 1-5 (1 low risk, 5 high risk) and volatility (3 year volatility) are shown below.
Variable |
Symbol |
Stock Name |
Reward |
Risk |
Volatility
|
 |
ZXB |
BMO 2015 Corporate Bond Target Mat ETF |
-0.23% |
3 |
6.55
|
 |
ZXC |
BMO 2020 Corporate Bond Target Mat ETF |
2.63% |
3 |
7.81
|
 |
ZXD |
BMO 2025 Corporate Bond Target Mat ETF |
0.91% |
4 |
7.55
|
 |
ZAG |
BMO Aggregate Bond ETF |
2.50% |
3 |
9.31
|
 |
ZDV |
BMO Canadian Dividend ETF |
6.43% |
3 |
9.46
|
 |
ZCH |
BMO China Equity ETF |
26.73% |
3 |
13.60
|
 |
ZWB |
BMO Covered Call Canadian Banks ETF |
11.47% |
4 |
12.23
|
 |
ZWA |
BMO Covered Call DJIA Hedged to CAD ETF |
11.76% |
3 |
11.18
|
 |
ZDJ |
BMO Dow Jones Ind Avg Hdgd CAD ETF |
13.63% |
3 |
11.96
|
 |
ZEF |
BMO Emerging Market Bnd Hdgd to CAD ETF |
3.88% |
5 |
10.85
|
 |
ZRE |
BMO Equal Weight REITs ETF |
1.99% |
2 |
12.10
|
 |
ZUB |
BMO Eq Weight US Banks Hdgd to CAD ETF |
22.05% |
4 |
15.73
|
 |
ZGI |
BMO Global Infrastructure ETF |
17.37% |
3 |
10.66
|
 |
ZHY |
BMO High Yld US Corp Bd Hdgd to CAD ETF |
5.00% |
5 |
8.14
|
 |
ZJN |
BMO Junior Gas ETF |
9.75% |
4 |
21.80
|
 |
ZJG |
BMO Junior Gold ETF |
-27.79% |
5 |
42.71
|
 |
ZJO |
BMO Junior Oil ETF |
1.46% |
4 |
22.12
|
 |
ZLC |
BMO Long Corporate Bond ETF |
2.92% |
2 |
9.72
|
 |
ZFL |
BMO Long Federal Bond ETF |
-0.20% |
3 |
10.59
|
 |
ZLB |
BMO Low Volatility Canadian Equity ETF |
19.23% |
1 |
8.52
|
 |
ZCM |
BMO Mid Corporate Bond ETF |
2.67% |
5 |
7.53
|
 |
ZFM |
BMO Mid Federal Bond ETF |
0.20% |
5 |
7.92
|
 |
ZMI |
BMO Monthly Income ETF |
4.60% |
4 |
7.96
|
 |
ZDM |
BMO MSCI EAFE Hdg to CAD ETF |
18.95% |
4 |
10.56
|
 |
ZEM |
BMO MSCI Emerging Markets ETF |
8.86% |
3 |
10.97
|
 |
ZQQ |
BMO NASDAQ 100 Equity Hedged to CAD ETF |
20.36% |
5 |
12.61
|
 |
ZRR |
BMO Real Return Bond ETF |
-2.31% |
4 |
11.77
|
 |
ZUE |
BMO S&P 500 Hedged to CAD ETF |
17.36% |
4 |
11.07
|
 |
ZCN |
BMO S&P/TSX Capped Composite ETF |
9.92% |
3 |
9.16
|
 |
ZEB |
BMO S&P/TSX Equal Weight Banks ETF |
13.06% |
3 |
13.34
|
 |
ZMT |
BMO S&P/TSX EqWt Glb BM Hdgd to CAD ETF |
-4.23% |
4 |
20.63
|
 |
ZEO |
BMO S&P/TSX Equal Weight Oil&Gas ETF |
0.27% |
1 |
16.66
|
 |
ZCS |
BMO Short Corporate Bond ETF |
0.74% |
4 |
6.44
|
 |
ZFS |
BMO Short Federal Bond ETF |
-0.61% |
4 |
6.41
|
 |
ZPS |
BMO Short Provincial Bond ETF |
0.08% |
5 |
6.27
|
 |
ZST |
BMO Ultra Short-Term Bond ETF |
-0.97% |
3 |
6.78
|
LP Problem
Results
Z = 15.9009
Symbol |
Percentage Invested
|
ZXB |
0
|
ZXC |
0
|
ZXD |
0
|
ZAG |
0
|
ZDV |
0
|
ZCH |
0.15
|
ZWB |
0
|
ZWA |
0
|
ZDJ |
0
|
ZEF |
0
|
ZRE |
0
|
ZUB |
0
|
ZGI |
0.15
|
ZHY |
0
|
ZJN |
0
|
ZJG |
0
|
ZJO |
0
|
ZLC |
0
|
ZFL |
0
|
ZLB |
0.15
|
ZCM |
0
|
ZFM |
0
|
ZMI |
0
|
ZDM |
0.15
|
ZEM |
0
|
ZQQ |
0
|
ZRR |
0
|
ZUE |
0.15
|
ZCN |
0.0849481
|
ZEB |
0
|
ZMT |
0
|
ZEO |
0
|
ZCS |
0.15
|
ZFS |
0
|
ZPS |
0.0150519
|
ZST |
0
|
Solver Code
Here is the code to solve the LP program using python.
Operating System: Ubuntu 14.04 LTS
This uses a modified version PyGLPK found at https://github.com/bradfordboyle/pyglpk
Ran on python version 2.7
Both the code, called solve.py, and the datafile it uses, data.txt are shown below. Note, data.txt must be in the same directory as solve.py.
solve.py
import glpk
max_percent_per_stock = 0.15
max_volatility = 10
data_filename = 'data.txt'
the_file = open(data_filename, 'r')
lines = []
for line in the_file:
l = line.strip()
l = line.split()
lines.append(l)
# Drop first line. It's column labels.
stocks = lines[1:]
n = len(stocks)
names = [stock[0] for stock in stocks]
rewards = [float(stock[1].replace("%", "")) for stock in stocks]
risks = [stock[2] for stock in stocks]
volatilities = [float(stock[3]) for stock in stocks]
# Referencing solver code from http://tfinley.net/software/pyglpk/ex_ref.html
lp = glpk.LPX()
lp.name = '340project'
lp.obj.maximize = True
# add rows
# 1 row for first constraint
# 1 row for second constraint
# n rows for last constraint
lp.rows.add(n+2)
for row in lp.rows:
row.name = 's{0}'.format(row.index)
# put bounds on constraints
# constraint 1: 0 \leq x_n \leq 0.15 \text{ for }n=1, 2, ..., n
for row in lp.rows[:n]:
# TODO ISSUE: 0 \leq x_n \leq 0.15? Can I use None or should I use 0?
row.bounds = None, max_percent_per_stock
# constraint 2: \sum\limits{n=1}_{36} Volatility_n*x_n \leq 10
lp.rows[n].bounds = None, max_volatility
# constraint 3: \sum\limits{n=1}_{36} x_n = 1
lp.rows[n+1].bounds = 1.0, 1.0
# initial dictionary has n non-basic variables so we need n columns
lp.cols.add(n)
for col in lp.cols:
col.name = '{0}'.format(names[col.index])
col.bounds = 0, None
# set the objective function
lp.obj[:] = rewards
# build matrix of coeffs. We refer to this as matrix A in class.
matrix = []
# constraint 1: 0 \leq x_n \leq 0.15 \text{ for }n=1, 2, ..., n
for stock_num in xrange(len(stocks)):
row_of_zeros = [0 for x in xrange(n)]
row_of_zeros[stock_num] = 1
matrix += row_of_zeros
# constraint 2: \sum\limits{n=1}_{36} Volatility_n*x_n \leq 10
matrix += volatilities
# constraint 3: \sum\limits{n=1}_{36} x_n = 1
matrix += [1 for x in xrange(n)]
lp.matrix = matrix
lp.simplex()
print 'Z = %g\n' % lp.obj.value,
print '{| class="wikitable" style="text-align: center;"'
print '|-'
print '! Symbol !! Percentage Invested'
print '|-'
print '\n|-\n'.join('| %s || %g' % (c.name, c.primal) for c in lp.cols)
print '|-'
print '|}'
data.txt
Symbol Reward Risk Volatility
ZXB -0.23% 3 6.55
ZXC 2.63% 3 7.81
ZXD 0.91% 4 7.55
ZAG 2.50% 3 9.31
ZDV 6.43% 3 9.46
ZCH 26.73% 3 13.60
ZWB 11.47% 4 12.23
ZWA 11.76% 3 11.18
ZDJ 13.63% 3 11.96
ZEF 3.88% 5 10.85
ZRE 1.99% 2 12.10
ZUB 22.05% 4 15.73
ZGI 17.37% 3 10.66
ZHY 5.00% 5 8.14
ZJN 9.75% 4 21.80
ZJG -27.79% 5 42.71
ZJO 1.46% 4 22.12
ZLC 2.92% 2 9.72
ZFL -0.20% 3 10.59
ZLB 19.23% 1 8.52
ZCM 2.67% 5 7.53
ZFM 0.20% 5 7.92
ZMI 4.60% 4 7.96
ZDM 18.95% 4 10.56
ZEM 8.86% 3 10.97
ZQQ 20.36% 5 12.61
ZRR -2.31% 4 11.77
ZUE 17.36% 4 11.07
ZCN 9.92% 3 9.16
ZEB 13.06% 3 13.34
ZMT -4.23% 4 20.63
ZEO 0.27% 1 16.66
ZCS 0.74% 4 6.44
ZFS -0.61% 4 6.41
ZPS 0.08% 5 6.27
ZST -0.97% 3 6.78