In Step 2, we defined parameters used for this recipe, such as the considered timeframe, the risky assets we wanted to use for building the portfolio, and the number of simulations. An important thing to note here is that we also ran RISKY_ASSETS.sort(), to sort the list alphabetically. This matters when interpreting the results as when downloading data from Yahoo Finance using the yfinance library, the obtained prices are ordered alphabetically, not as specified in the provided list. Having downloaded the stock prices, we calculated simple returns using the pct_change method of a pandas DataFrame, and dropped the first row containing NaNs.
For evaluating the potential portfolios, we needed the average (expected) annual return and the corresponding covariance matrix. We obtained them by using the mean() and cov() methods of the DataFrame. We also annualized both metrics by multiplying them by 252 (the average number of trading days in a year).
In Step 5, we calculated the random portfolio weights. Following the assumptions of the MPT (refer to the chapter introduction for reference), the weights needed to be positive and sum up to 1. To achieve this, we first generated a matrix of random numbers (between 0 and 1), using np.random.random. The matrix was of size N_SIMULATIONS x N_ASSETS. To make sure the weights totaled 1, we divided each row of the matrix by its sum.
In Step 6, we calculated the portfolio metrics—returns and standard deviation. To calculate the expected annual portfolio returns, we had to multiply the weights by the previously calculated annual averages. For the standard deviations, we had to use the following formula: , where is the vector of weights and is the historical covariance matrix. To calculate the standard deviation, we iterated over all the simulated portfolios, using a for loop.
For this example, we assumed that the risk-free rate was 0%, so the Sharpe ratio of the portfolio could be calculated as portfolio returns/portfolio volatility. Another possible approach would be to calculate the average annual risk-free rate over 2018, and to use the portfolio excess returns for calculating the ratio.
The last three steps led to visualizing the results. First, we put all the relevant metrics into a pandas DataFrame. Second, we created an array of expected returns from the sample. To do so, we used np.linspace, with the min and max values from the calculated portfolio returns. We rounded the numbers to two decimals, to make the calculations smoother. For each expected return, we found the minimum observable volatility. In cases where there was no match, as can happen with equally spread points on the linear space, we skipped that point.
In the very last step, we plotted the simulated portfolios, the individual assets, and the approximated Efficient Frontier in one plot. The shape of the frontier was a bit jagged, which can be expected when using only simulated values that are not that frequent in some extreme areas. Additionally, we colored the dots representing the simulated portfolios by the value of the Sharpe ratio.