Markovitz Portfolio Optimization in a simple example

In [1]:
import matplotlib.pyplot as plt
import pandas_datareader.data as web
import numpy as np
import pandas as pd

start = pd.datetime(2013,1,1,0,0,0,0) 
In [4]:
import numpy as np
import pandas as pd
import pandas_datareader.data as web
import matplotlib.pyplot as plt

#list of stocks in portfolio
stocks = ['AAPL','AMZN','MSFT','GOOG']
 
#download daily price data for each of the stocks in the portfolio
data = web.DataReader(stocks,data_source='yahoo',start=start)['Adj Close']
#data can be stored by pd.Dataframe.to_csv(data,'data.csv')
#data = pd.DataFrame.from_csv('data.csv')

#convert daily stock prices into daily returns
returns = -data.pct_change()

 
#calculate mean daily return and covariance of daily returns
mean_daily_returns = returns.mean()
cov_matrix = returns.cov()
 
#set number of runs of random portfolio weights
num_portfolios = 25000
 
#set up array to hold results
#We have increased the size of the array to hold the weight values for each stock
results = np.zeros((4+len(stocks)-1,num_portfolios))
 
for i in range(num_portfolios):
    #select random weights for portfolio holdings
    weights = np.array(np.random.random(4))
    #rebalance weights to sum to 1
    weights /= np.sum(weights)
 
    #calculate portfolio return and volatility
    portfolio_return = np.sum(mean_daily_returns * weights) * 252
    portfolio_std_dev = np.sqrt(np.dot(weights.T,np.dot(cov_matrix, weights))) * np.sqrt(252)
 
    #store results in results array
    results[0,i] = portfolio_return
    results[1,i] = portfolio_std_dev
    #store Sharpe Ratio (return / volatility) - risk free rate element excluded for simplicity
    results[2,i] = results[0,i] / results[1,i]
    #iterate through the weight vector and add data to results array
    for j in range(len(weights)):
        results[j+3,i] = weights[j]
 
#convert results array to Pandas DataFrame
results_frame = pd.DataFrame(results.T,columns=['ret','stdev','sharpe',stocks[0],stocks[1],stocks[2],stocks[3]])
 
#locate position of portfolio with highest Sharpe Ratio
max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()]
#locate positon of portfolio with minimum standard deviation
min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()]
%matplotlib inline
#create scatter plot coloured by Sharpe Ratio
plt.scatter(results_frame.stdev,results_frame.ret,c=results_frame.sharpe,cmap='RdYlBu',s=1)
plt.xlabel('Volatility')
plt.ylabel('Returns')
plt.colorbar()
#plot red star to highlight position of portfolio with highest Sharpe Ratio
plt.scatter(max_sharpe_port[1],max_sharpe_port[0],marker=(5,1,0),color='r',s=100)
#plot green star to highlight position of minimum variance portfolio
plt.scatter(min_vol_port[1],min_vol_port[0],marker=(5,1,0),color='g',s=100)
Out[4]:
<matplotlib.collections.PathCollection at 0x7f94991c9748>
In [5]:
data.plot()
Out[5]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f949cd00208>
In [6]:
returns
Out[6]:
AAPL AMZN GOOG MSFT
Date
2017-12-08 NaN NaN NaN NaN
2017-12-07 0.000295 0.001902 0.005901 0.019843
2017-12-06 0.001831 0.006415 0.012174 -0.003516
2017-12-05 -0.003728 0.009355 0.012991 0.014375
2017-12-04 -0.000943 0.006675 0.006437 0.006251
2017-12-01 -0.007362 -0.025045 -0.011505 -0.039221
2017-11-30 -0.004677 -0.012389 -0.011127 0.001068
2017-11-29 0.013791 0.013155 -0.000245 0.009861
2017-11-28 -0.021183 -0.027840 -0.025204 -0.018479
2017-11-27 -0.005894 -0.001868 -0.006492 0.011899
2017-11-24 -0.005055 0.008220 0.012901 0.007273
2017-11-22 0.000057 0.025160 0.004469 0.001802
2017-11-21 0.010402 0.014418 0.001419 -0.007340
2017-11-20 0.018251 0.011567 0.015573 0.014214
2017-11-17 -0.001000 -0.003170 -0.000697 0.001575
2017-11-16 -0.005583 -0.006558 -0.013159 -0.009709
2017-11-15 0.011806 0.009320 0.011225 0.002644
2017-11-14 -0.013366 -0.009009 -0.004986 -0.007833
2017-11-13 -0.015350 0.006747 0.000244 0.001428
2017-11-10 -0.004024 0.003383 -0.002262 0.000715
2017-11-09 -0.003321 -0.003359 -0.003103 -0.002623
2017-11-08 -0.002047 -0.003321 -0.008330 -0.005589
2017-11-07 0.008114 0.008571 0.006270 0.003430
2017-11-06 0.003203 0.002235 0.007190 -0.002373
2017-11-03 0.010043 0.008085 -0.006414 0.003907
2017-11-02 0.025449 0.015635 0.006683 0.001070
2017-11-01 0.007257 -0.008646 0.000078 0.010351
2017-10-31 -0.012883 -0.001450 0.008640 -0.000000
2017-10-30 0.013725 -0.005039 -0.000462 -0.008536
2017-10-27 0.022013 0.008912 -0.002124 0.000954
... ... ... ... ...
2013-02-12 -0.001906 0.039967 0.002759 0.005351
2013-02-11 -0.025711 0.005760 -0.002203 0.000717
2013-02-08 0.010314 -0.018429 -0.003770 0.011127
2013-02-07 0.014232 0.006566 0.014541 0.009800
2013-02-06 0.028875 -0.007647 0.004884 -0.002199
2013-02-05 -0.001071 -0.017810 0.005752 -0.005852
2013-02-04 0.033898 0.025891 0.008776 0.002182
2013-02-01 -0.025547 -0.019309 -0.021844 -0.017857
2013-01-31 -0.004122 -0.001887 0.025670 0.017186
2013-01-30 -0.002942 -0.027345 0.002461 -0.014572
2013-01-29 -0.003152 0.045498 0.000199 -0.005745
2013-01-28 0.018417 -0.060265 0.003914 0.003570
2013-01-25 0.022120 -0.028800 -0.003916 0.001075
2013-01-24 -0.024143 0.037079 -0.000716 0.008967
2013-01-23 -0.140977 0.019564 0.016852 0.000724
2013-01-22 0.017977 -0.007758 0.052097 0.016661
2013-01-18 0.009450 -0.007143 -0.002333 -0.003683
2013-01-17 -0.005360 0.006027 -0.009666 -0.000000
2013-01-16 -0.006784 0.005731 -0.005441 0.007706
2013-01-15 0.039855 -0.011044 -0.013619 -0.006287
2013-01-14 -0.032577 -0.003053 0.002317 0.011761
2013-01-11 -0.036971 0.017563 -0.023146 0.002231
2013-01-10 -0.006169 0.009704 -0.002014 0.013790
2013-01-09 0.012245 -0.003806 0.004531 -0.009070
2013-01-08 -0.015877 -0.000113 0.006530 0.005618
2013-01-07 0.002684 -0.007808 -0.001977 -0.005273
2013-01-04 -0.005917 0.034679 -0.004382 -0.001873
2013-01-03 -0.028653 0.002585 0.019377 -0.019072
2013-01-02 -0.012784 0.004527 0.000580 -0.013578
2012-12-31 0.030709 0.025028 0.021943 0.032947

1246 rows × 4 columns

In [7]:
np.sqrt(cov_matrix*252)
Out[7]:
AAPL AMZN GOOG MSFT
AAPL 0.241473 0.134364 0.129676 0.137210
AMZN 0.134364 0.289654 0.184151 0.158043
GOOG 0.129676 0.184151 0.216275 0.155851
MSFT 0.137210 0.158043 0.155851 0.224991
In [8]:
mean_daily_returns
Out[8]:
AAPL    0.000609
AMZN    0.001064
GOOG    0.000775
MSFT    0.000928
dtype: float64
In [9]:
min_vol_port
Out[9]:
ret       0.198724
stdev     0.173792
sharpe    1.143458
AAPL      0.308421
AMZN      0.070936
MSFT      0.330352
GOOG      0.290291
Name: 16299, dtype: float64
In [10]:
max_sharpe_port
Out[10]:
ret       0.225219
stdev     0.184742
sharpe    1.219100
AAPL      0.127478
AMZN      0.244040
MSFT      0.173979
GOOG      0.454502
Name: 9285, dtype: float64
In [11]:
#we calculate the value of a portfolio with given weights
weights_max_sharpe = [max_sharpe_port[3],max_sharpe_port[4],max_sharpe_port[5],max_sharpe_port[6]]
weights_min_vol = [min_vol_port[3], min_vol_port[4], min_vol_port[5], min_vol_port[6]]

def portfolio_value_plot(weights,data):
    portfolio_value = [1]
    returns = -data.pct_change()
    for i in range(1,len(data.AMZN)):
        portfolio_value = portfolio_value + [portfolio_value[-1]+portfolio_value[-1]*(returns.AAPL[i]*weights[0]+returns.AMZN[i]*weights[1]+
                                                            returns.GOOG[i]*weights[2]+returns.MSFT[i]*weights[3])]
    plt.plot(portfolio_value,linewidth=0.2)
    
def portfolio_value_plot_max(weights,data):
    portfolio_value = [1]
    returns = -data.pct_change()
    for i in range(1,len(data.AMZN)):
        portfolio_value = portfolio_value + [portfolio_value[-1]+portfolio_value[-1]*(returns.AAPL[i]*weights[0]+returns.AMZN[i]*weights[1]+
                                                            returns.GOOG[i]*weights[2]+returns.MSFT[i]*weights[3])]
    plt.plot(portfolio_value,'r')
            
        
portfolio_value_plot_max(weights_max_sharpe,data)
portfolio_value_plot_max(weights_min_vol,data)

for i in range(10):
    randomweights = np.array(np.random.random(4))
    #rebalance weights to sum to 1
    randomweights /= np.sum(randomweights)
    portfolio_value_plot(randomweights,data)

plt.show()