Plotting Financial Data With Python: Efficient Frontier (2 assets)

The main idea behind the Efficient Frontier is that the overall risk (volatility) of a portfolio may not be equal to the sum of the risk of its components so some combinations are better than others. In this post we’re going to visualize the optimal weights of 2 given assets in a hypothetical portfolio.

This is the fourth part of the “Plotting Financial Data With Python” series and it’s better if you read it in chronological order:

  1. Part 1 - History
  2. Part 2 - Variance
  3. Part 3 - Comparing Returns
  4. Part 4 - Efficient Frontier (2 Assets) (you are here)
  5. Part 5 - Efficient Frontier (N Assets)

Components

Let’s compare IBM and Disney by using the script from the previous post:

$ python compare.py IBM DIS

IBM and DIS chart

It looks like both of those stocks have made a lot of money for their shareholders but it’s unclear who will be better off in the long run. Let’s analyze both stocks in order to make a few assumptions based on their historical performance.

Stats

Here are some stats for IBM:

  • Monthly mean return: 0.76%
  • Standard deviation of monthly return: 7.51%

Here are the same stats tor DIS:

  • Monthly mean return: 0.83%
  • Standard deviation of monthly return: 7.25%

Those stocks look pretty close in terms of risk and return so which one should we choose? Would it be a better idea to split the portfolio 5050? Let’s run a few calculations to find out.

Diversified Portfolio

So what are the mean return of the 5050 portfolio? It’s just a weighted average of its components’ return:

\( r_p = r_1 w_1 + r_2 w_2 \)

Where:

\( r_p \) = portfolio mean return

\( r_1, r_2 \) = return of the portfolio components

\( w_1, w_2 \) = weights of the portfolio components

0.76% * 50% + 0.83% * 50% = 0.80%

Not bad, but what about the risk? Here is the formula:

\( σ_p^2 = w_1^2 σ_1^2 + w_2^2 σ_2^2 + 2 w_1 w_2 cov(1, 2) \)

In our case covariance is 0.00207, so:

  • Portfolio variance = 0.5^2 * 0.0751^2 + 0.5^2 * 0.0725^2 + 2 * 0.5 * 0.5 * 0.00207 = 0.003759065
  • Portfolio standard deviation = sqrt(Portfolio Variance) = 6.13%

It looks like the 5050 split is not a bad idea after all, we would get the same return with significantly less risk but what about the other possible combinations? How much would we get if we were ready to accept more risk? Is it possible to decrease the risk? Plotting many different combinations on a graph might give us a good picture of how diversification works and helps us to make the right choice.

Plotting Portfolios

Let’s plot a lot of different asset combinations in order to find out how they affect portfolio volatility and expected return. A scatter plot is a good tool for showing that kind of data. Here is the script, you can call it frontier2.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import sys
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

import alpha_vantage

def show_frontier(symbol1, symbol2, interval='MONTHLY'):
    returns1 = alpha_vantage.get_stock_returns_history(symbol1, interval)
    mean_returns1 = np.mean(returns1)
    variance1 = np.var(returns1)
    standard_deviation1 = np.sqrt(variance1)

    print('Mean returns (%s) = %f' % (symbol1, mean_returns1))
    print('Variance (%s) = %f' % (symbol1, variance1))
    print('Standard Deviation (%s) = %f' % (symbol1, standard_deviation1))

    returns2 = alpha_vantage.get_stock_returns_history(symbol2, interval)
    mean_returns2 = np.mean(returns2)
    variance2 = np.var(returns2)
    standard_deviation2 = np.sqrt(variance2)

    print('Mean returns (%s) = %f' % (symbol2, mean_returns2))
    print('Variance (%s) = %f' % (symbol2, variance2))
    print('Standard Deviation (%s) = %f' % (symbol2, standard_deviation2))

    covariance = np.cov(returns1, returns2)[0][1]
    print('Covariance = %f' % covariance)

    weights = []

    for n in range(0, 101):
        weights.append((1 - 0.01 * n, 0 + 0.01 * n))

    returns = []
    standard_deviations = []

    portfolio_50_50_standard_deviation = None
    portfolio_50_50_returns = None

    for w1, w2 in weights:
        returns.append(w1 * mean_returns1 + w2 * mean_returns2)

        variance = w1**2 * standard_deviation1**2 + w2**2 * standard_deviation2**2 + 2 * w1 * w2 * covariance
        standard_deviation = np.sqrt(variance)
        standard_deviations.append(standard_deviation)

        plt.scatter(standard_deviations[-1], returns[-1], color='blue')

        if w1 == 0.5 and w2 == 0.5:
            portfolio_50_50_standard_deviation = standard_deviations[-1]
            portfolio_50_50_returns = returns[-1]

    plt.scatter(portfolio_50_50_standard_deviation, portfolio_50_50_returns, marker='x', color='red', alpha=1)
    plt.text(portfolio_50_50_standard_deviation, portfolio_50_50_returns, '50/50', fontsize=9)

    x_padding = np.average(standard_deviations) / 25
    plt.xlim(min(standard_deviations) - x_padding, max(standard_deviations) + x_padding)

    y_padding = np.average(returns) / 25
    plt.ylim(min(returns) - y_padding, max(returns) + y_padding)

    plt.gca().set_xticklabels(['{:.2f}%'.format(x*100) for x in plt.gca().get_xticks()])
    plt.gca().set_yticklabels(['{:.2f}%'.format(y*100) for y in plt.gca().get_yticks()])

    plt.title('Efficient Frontier (%s and %s)' % (symbol1, symbol2))

    plt.show()

show_frontier(sys.argv[1], sys.argv[2])

Now let’s run this script in order to see the scatter plot:

python frontier2.py IBM DIS

Efficient Frontier

As you can see, the 5050 portfolio is on the bottom half of this chart. That’s not very good because it means that there is another portfolio that has more returns with the same risks. Usually, it’s better to ignore the bottom half of the efficient frontier and pick a portfolio from the top half that best suits your risk tolerance. Many young people prefer high returns and it might be a good idea if your investment horizon is long enough but the people who are close to retirement age are usually not comfortable with the idea of waiting for another 20-30 years in order to recover from a possible loss caused by holding too many high-risk assets.

Conclusion

Now we have a script that allows us to find an optimal combination of 2 different assets. That’s a good start but it’s more practical to be able to include more assets and that’s what we’re going to do in the next post.