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

Programming |

Updated on

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.

Thumbnail

Contents

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)

Source Code

You can get the full source code here: https://github.com/bubelov/market-plots

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import sys
import pathlib
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

import alpha_vantage


def show_frontier(symbol1, symbol2, interval='MONTHLY'):
    returns1 = alpha_vantage.get_stock_returns_history(symbol1, interval)
    returns2 = alpha_vantage.get_stock_returns_history(symbol2, interval)

    if len(returns1) > len(returns2):
        returns1 = returns1[-len(returns2):]

    if len(returns2) > len(returns1):
        returns2 = returns2[-len(returns1):]

    mean_returns1 = np.mean(returns1)
    variance1 = np.var(returns1)
    standard_deviation1 = np.sqrt(variance1)

    #print(f'Mean returns ({symbol1}) = {mean_returns1}')
    #print(f'Variance ({symbol1}) = {variance1}')
    #print(f'Standard Deviation ({symbol1}) = {standard_deviation1}')

    mean_returns2 = np.mean(returns2)
    variance2 = np.var(returns2)
    standard_deviation2 = np.sqrt(variance2)

    #print(f'Mean returns ({symbol2}) = {mean_returns2}')
    #print(f'Variance ({symbol2}) = {variance2}')
    #print(f'Standard Deviation ({symbol2}) = {standard_deviation2}')

    correlation = np.corrcoef(returns1, returns2)[0][1]
    #print(f'Corellation = {correlation}')

    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 * standard_deviation1 * standard_deviation2 * correlation

        standard_deviation = np.sqrt(variance)
        standard_deviations.append(standard_deviation)

        plt.scatter(standard_deviations[-1], returns[-1], color='#007bff')

        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, s=320)

    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(f'Efficient Frontier ({symbol1} and {symbol2})')

    plt.xlabel('Risk')
    plt.ylabel('Return')

    pathlib.Path('img/frontier2').mkdir(parents=True, exist_ok=True)
    plt.savefig(f'img/frontier2/{symbol1}-{symbol2}.png')
    plt.close()


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.

This site doesn't have ads and the reasons are simple:

  • Most people don't want to see ads, that's not what they look for when they open web pages.
  • Ad scripts can track visitors, exposing private data to third parties.

If you found this post valuable and you wish to leave a tip, you can do it with Bitcoin:

34CXtg7c4Vbw8DZjAwFQVsrbu9eDEbTzbA