Calculating portfolio returns in Python

In this post we will learn to calculate the portfolio returns in Python. Since we are not aware of any modules that perform such calculations we will perform this calculation manually.

Calculating portfolio returns using the formula

A portfolio return is the weighted average of individual assets in the portfolio.

Here is what we need

  • Asset symbols that make up our portfolio
  • Price data for the assets
  • weights of assets
  • Calculating the weighted average of our assets returns
  • Adding them to get the portfolio returns

Lets first load the modules

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import pandas_datareader as web

We will invest in the following assets

  • Aggregate Bonds ETF (BND) 10%
  • Small Cap ETF (VB) 20%
  • Developed markets ETF (VEA) 25%
  • S&P 500 ETF (VOO) 25%
  • Emerging Markets ETF (VWO) 20%

So lets assign our assets to the symbols variable.

symbols = ['VOO','VEA', 'VB', 'VWO','BND']

Next we download the price data for the assets.

price_data = web.get_data_yahoo(symbols,
                               start = '2013-01-01',
                               end = '2018-03-01')

Now that we have the price data lets look at the head of this data.

print(price_data.head())
## Attributes       High               ...       Adj Close           
## Symbols           BND         VB    ...             VOO        VWO
## Date                                ...                           
## 2013-01-02  83.940002  83.209999    ...      117.851250  38.347870
## 2013-01-03  83.930000  83.690002    ...      117.745369  38.120152
## 2013-01-04  83.779999  83.919998    ...      118.239380  38.187622
## 2013-01-07  83.760002  83.639999    ...      117.921799  37.858707
## 2013-01-08  83.849998  83.610001    ...      117.568985  37.546669
## 
## [5 rows x 30 columns]

But we just need the Adjusted Closing price for our returns calculations. So lets select that columns.

price_data = price_data['Adj Close']
print(price_data.head())
## Symbols           BND         VB        VEA         VOO        VWO
## Date                                                              
## 2013-01-02  71.491119  75.985794  29.420095  117.851250  38.347870
## 2013-01-03  71.278122  75.940125  29.124334  117.745369  38.120152
## 2013-01-04  71.388893  76.515572  29.288649  118.239380  38.187622
## 2013-01-07  71.337738  76.287193  29.140768  117.921799  37.858707
## 2013-01-08  71.405899  76.141068  28.984674  117.568985  37.546669

We can see that pandas has sorted our columns alphabetically so we need to align our weights correctly to the column names.

w = [0.1,0.2,0.25,0.25,0.2]

A quick check to see if our weights add to one.

print(sum(w))
## 1.0

Now we will calculate the asset returns in our portfolio.

ret_data = price_data.pct_change()[1:]
print(ret_data.head())
## Symbols          BND        VB       VEA       VOO       VWO
## Date                                                        
## 2013-01-03 -0.002979 -0.000601 -0.010053 -0.000898 -0.005938
## 2013-01-04  0.001554  0.007578  0.005642  0.004196  0.001770
## 2013-01-07 -0.000717 -0.002985 -0.005049 -0.002686 -0.008613
## 2013-01-08  0.000955 -0.001915 -0.005357 -0.002992 -0.008242
## 2013-01-09 -0.000358  0.004319  0.004818  0.003001  0.005840

Next we can calculate the weighted returns of our assets.

weighted_returns = (w * ret_data)
print(weighted_returns.head())
## Symbols          BND        VB       VEA       VOO       VWO
## Date                                                        
## 2013-01-03 -0.000298 -0.000120 -0.002513 -0.000225 -0.001188
## 2013-01-04  0.000155  0.001516  0.001410  0.001049  0.000354
## 2013-01-07 -0.000072 -0.000597 -0.001262 -0.000671 -0.001723
## 2013-01-08  0.000096 -0.000383 -0.001339 -0.000748 -0.001648
## 2013-01-09 -0.000036  0.000864  0.001205  0.000750  0.001168

Next the portfolio returns are simply the sum of the weighted returns of the assets. So lets add the rows.

port_ret = weighted_returns.sum(axis=1)
# axis =1 tells pandas we want to add
# the rows

Lets plot the histogram of the returns.

fig = plt.figure()
ax1 = fig.add_axes([0.1,0.1,0.8,0.8])
ax1.hist(port_ret, bins = 60)
ax1.set_xlabel('Portfolio returns')
ax1.set_ylabel("Freq")
ax1.set_title("Portfolio Returns calculated manually")
plt.show(); 

Tidy method in Python

In the example given in the R post we calculated the portfolio returns using the tidy dataframe. We can also calculate the returns using a tidy method in Python. To do that we need to reshape our returns dataframe and create a new weights table. We will then join the two and calculate the portfolio returns.

First lets create a weights table.

wts_table = pd.DataFrame({'symbol':symbols,
                          'wts':[0.25,0.25,0.2,0.2,0.1]})
print(wts_table.head())
##   symbol   wts
## 0    VOO  0.25
## 1    VEA  0.25
## 2     VB  0.20
## 3    VWO  0.20
## 4    BND  0.10

Next we will transform our returns data into a tidy data. First we need to reset the index and make the Date index into a separate column.

ret_data_tidy = pd.melt(ret_data.reset_index(),
        id_vars='Date',
        var_name='symbol',
        value_name='ret')
        
print(ret_data_tidy.head())
##         Date symbol       ret
## 0 2013-01-03    BND -0.002979
## 1 2013-01-04    BND  0.001554
## 2 2013-01-07    BND -0.000717
## 3 2013-01-08    BND  0.000955
## 4 2013-01-09    BND -0.000358

We can see that the assets have been stacked on top of one another and Date has a separate column. Next we can merge our data by symbols.

ret_data_tidy_wts = pd.merge(ret_data_tidy,wts_table,on="symbol")
print(ret_data_tidy_wts.head())
##         Date symbol       ret  wts
## 0 2013-01-03    BND -0.002979  0.1
## 1 2013-01-04    BND  0.001554  0.1
## 2 2013-01-07    BND -0.000717  0.1
## 3 2013-01-08    BND  0.000955  0.1
## 4 2013-01-09    BND -0.000358  0.1

We have the data in the desired form and now we can multiply our columns to find out the weighted average.

ret_data_tidy_wts['wt_returns'] = ret_data_tidy_wts['ret'] * ret_data_tidy_wts['wts'] 
print(ret_data_tidy_wts.head())
##         Date symbol       ret  wts  wt_returns
## 0 2013-01-03    BND -0.002979  0.1   -0.000298
## 1 2013-01-04    BND  0.001554  0.1    0.000155
## 2 2013-01-07    BND -0.000717  0.1   -0.000072
## 3 2013-01-08    BND  0.000955  0.1    0.000096
## 4 2013-01-09    BND -0.000358  0.1   -0.000036

Finally we need to group our dataframe by date to calculate the daily returns on our portfolio.

port_ret_tidy = ret_data_tidy_wts.groupby("Date").sum()['wt_returns']
print(port_ret_tidy.head())
## Date
## 2013-01-03   -0.004344
## 2013-01-04    0.004484
## 2013-01-07   -0.004325
## 2013-01-08   -0.004023
## 2013-01-09    0.003951
## Name: wt_returns, dtype: float64

Now have the portfolio returns calculated in tidy format as well. We like this way since we can see which columns are getting multiplied. We can see that the results are same since the difference between the two is 0.

diff = port_ret - port_ret_tidy
print(diff.unique())
## [0.]

We can also plot the two histograms.

fig = plt.figure()
ax1 = fig.add_axes([0.1,0.1,0.8,0.8])
ax1.hist(port_ret, bins = 60) # manually calculated returns
ax1.set_xlabel('Portfolio returns')
ax1.set_ylabel("Freq")
ax1.set_title("Portfolio Returns calculated manually")
plt.show();

fig = plt.figure()
ax1 = fig.add_axes([0.1,0.1,0.8,0.8])
ax1.hist(port_ret_tidy, bins = 60) # Tidy returns
ax1.set_xlabel('Portfolio returns')
ax1.set_ylabel("Freq")
ax1.set_title("Portfolio Returns calculated in Tidy format")
plt.show();

Portfolio mean and standard deviation

Next we will calculate the portfolio mean and standard deviation, this is simple with the pandas module.

mean_ret = port_ret.mean()
std_returns = port_ret.std()
print(mean_ret)
## 0.0003768744769855518
print(std_returns)
## 0.007587147407342516

Summary

In this post we learned

  • To download asset prices in Python
  • To calculate portfolio returns
  • To transform the data into tidy format and calculate the returns