tret and one for the sum of the portfolio weights as before. The boundary values for each
parameter stay the same:
In [ 62 ]: cons = ({‘type’: ‘eq’, ‘fun’: lambda x: statistics(x)[ 0 ] - tret},
{‘type’: ‘eq’, ‘fun’: lambda x: np.sum(x) - 1 })
bnds = tuple(( 0 , 1 ) for x in weights)
For clarity, we define a dedicated function min_func for use in the minimization
procedure. It merely returns the volatility value from the statistics function:
In [ 63 ]: def min_func_port(weights):
return statistics(weights)[ 1 ]
When iterating over different target return levels (trets), one condition for the
minimization changes. That is why the conditions dictionary is updated during every loop:
In [ 64 ]: %%time
trets = np.linspace(0.0, 0.25, 50 )
tvols = []
for tret in trets:
cons = ({‘type’: ‘eq’, ‘fun’: lambda x: statistics(x)[ 0 ] - tret},
{‘type’: ‘eq’, ‘fun’: lambda x: np.sum(x) - 1 })
res = sco.minimize(min_func_port, noa * [1. / noa,], method=‘SLSQP’,
bounds=bnds, constraints=cons)
tvols.append(res[‘fun’])
tvols = np.array(tvols)
Out[64]: CPU times: user 4.35 s, sys: 4 ms, total: 4.36 s
Wall time: 4.36 s
Figure 11-13 shows the optimization results. Crosses indicate the optimal portfolios given
a certain target return; the dots are, as before, the random portfolios. In addition, the figure
shows two larger stars: one for the minimum volatility/variance portfolio (the leftmost
portfolio) and one for the portfolio with the maximum Sharpe ratio:
In [ 65 ]: plt.figure(figsize=( 8 , 4 ))
plt.scatter(pvols, prets,
c=prets / pvols, marker=‘o’)
# random portfolio composition
plt.scatter(tvols, trets,
c=trets / tvols, marker=‘x’)
# efficient frontier
plt.plot(statistics(opts[‘x’])[ 1 ], statistics(opts[‘x’])[ 0 ],
‘r*’, markersize=15.0)
# portfolio with highest Sharpe ratio
plt.plot(statistics(optv[‘x’])[ 1 ], statistics(optv[‘x’])[ 0 ],
‘y*’, markersize=15.0)
# minimum variance portfolio
plt.grid(True)
plt.xlabel(‘expected volatility’)
plt.ylabel(‘expected return’)
plt.colorbar(label=‘Sharpe ratio’)