AI For Trading: Quiz: Rate of Returns Over Multiple Periods (36)

Rate of Returns Over Multiple Periods

Numpy.cumsum and Numpy.cumprod

You've just leared about active returns and passive returns. Another important concept related to returns is "Cumulative returns" which is defined as the returns over a time period. You can read more about rate of returns here!

There are two ways to calcualte cumulative returns, depends on how the returns are calculated. Let's take a look at an example.

import numpy as np
import pandas as pd
from datetime import datetime

dates = pd.date_range(datetime.strptime('1/1/2016', '%m/%d/%Y'), periods=12, freq='M')
start_price, stop_price = 0.24, 0.3
abc_close_prices = np.arange(start_price, stop_price, (stop_price - start_price)/len(dates))

abc_close = pd.Series(abc_close_prices, dates)
abc_close
2016-01-31    0.240
2016-02-29    0.245
2016-03-31    0.250
2016-04-30    0.255
2016-05-31    0.260
2016-06-30    0.265
2016-07-31    0.270
2016-08-31    0.275
2016-09-30    0.280
2016-10-31    0.285
2016-11-30    0.290
2016-12-31    0.295
Freq: M, dtype: float64

Here, we have the historical prices for stock ABC for 2016. We would like to know the yearly cumulative returns for stock ABC in 2016 using time-weighted method, assuming returns are reinvested. How do we do it? Here is the formula:

Assume the returns over n successive periods are:

$$ r_1, r_2, r_3, r_4, r_5, ..., r_n $$

The cumulative return of stock ABC over period n is the compounded return over period n:

\( (1 + r_1)(1 + r_2)(1 + r_3)(1 + r_4)(1 + r_5)...(1 + r_n) - 1\)

First, let's calculate the returns of stock ABC.


print("abc_close:")
print(abc_close)

print("abc_close shift:")
print(abc_close.shift(1))

returns = abc_close / abc_close.shift(1) - 1
returns
abc_close:
2016-01-31    0.240
2016-02-29    0.245
2016-03-31    0.250
2016-04-30    0.255
2016-05-31    0.260
2016-06-30    0.265
2016-07-31    0.270
2016-08-31    0.275
2016-09-30    0.280
2016-10-31    0.285
2016-11-30    0.290
2016-12-31    0.295
Freq: M, dtype: float64
abc_close shift:
2016-01-31      NaN
2016-02-29    0.240
2016-03-31    0.245
2016-04-30    0.250
2016-05-31    0.255
2016-06-30    0.260
2016-07-31    0.265
2016-08-31    0.270
2016-09-30    0.275
2016-10-31    0.280
2016-11-30    0.285
2016-12-31    0.290
Freq: M, dtype: float64

2016-01-31         NaN
2016-02-29    0.020833
2016-03-31    0.020408
2016-04-30    0.020000
2016-05-31    0.019608
2016-06-30    0.019231
2016-07-31    0.018868
2016-08-31    0.018519
2016-09-30    0.018182
2016-10-31    0.017857
2016-11-30    0.017544
2016-12-31    0.017241
Freq: M, dtype: float64

The cumulative return equals to the product of the daily returns for the n periods.
That's a very long formula. Is there a better way to calculate this.

The answer is yes, we can use numpy.cumprod().

For example, if we have the following time series: 1, 5, 7, 10 and we want the product of the four numbers. How do we do it? Let's take a look!

lst = [1,5,7,10]
np.cumprod(lst)
array([  1,   5,  35, 350])

The last element in the list is 350, which is the product of 1, 5, 7, and 10.

OK, let's use numpy.cumprod() to get the cumulative returns for stock ABC

(returns + 1).cumprod()[len(returns)-1] - 1
0.22916666666666652

The cumulative return for stock ABC in 2016 is 22.91%.

The other way to calculate returns is to use log returns.

The formula of log return is the following:

$$ LogReturn = ln(\frac{P_t}{P_t - 1}) $$

The cumulative return of stock ABC over period n is the compounded return over period n:

$$ \sum_{i=1}^{n} r_i = r_1 + r_2 + r_3 + r_4 + ... + r_n $$

Let's see how we can calculate the cumulative return of stock ABC using log returns.

First, let's calculate log returns.

log_returns = (np.log(abc_close).shift(-1) - np.log(abc_close)).dropna()
log_returns.head()
2016-01-31    0.020619
2016-02-29    0.020203
2016-03-31    0.019803
2016-04-30    0.019418
2016-05-31    0.019048
Freq: M, dtype: float64

The cumulative sum equals to the sum of the daily returns for the n periods which is a very long formula.

To calculate cumulative sum, we can simply use numpy.cumsum().

Let's take a look at our simple example of time series 1, 5, 7, 10.

lst = [1,5,7,10]
np.cumsum(lst)
array([ 1,  6, 13, 23])

The last element is 23 which equals to the sum of 1, 5, 7, 10

OK, let's use numpy.cumsum() to get the cumulative returns for stock ABC

cum_log_return = log_returns.cumsum()[len(log_returns)-1]
np.exp(cum_log_return) - 1
0.22916666666666696

The cumulative return for stock ABC in 2016 is 22.91% using log returns.

Quiz: Arithmetic Rate of Return

Now, let's use cumprod() and cumsum() to calculate average rate of return.

For consistency, let's assume the rate of return is calculated as \( \frac{P_t}{P_t - 1} - 1 \)

Arithmetic Rate of Return:

$$ \frac{1}{n} \sum_{i=1}^{n} r_i = \frac{1}{n}(r_1 + r_2 + r_3 + r_4 + ... + r_n) $$

import quiz_tests

def calculate_arithmetic_rate_of_return(close):
    """
    Compute returns for each ticker and date in close.

    Parameters
    ----------
    close : DataFrame
        Close prices for each ticker and date

    Returns
    -------
    arithmnetic_returns : Series
        arithmnetic_returns at the end of the period for each ticker

    """
    # TODO: Implement Function
    print(close)
    print(close.shift(1))

    returns = close / close.shift(1) - 1
    print(returns)      

    arithmetic_returns = returns.cumsum(axis=0).iloc[returns.shape[0]-1]/returns.shape[0]
    print(arithmetic_returns)

    return arithmetic_returns

quiz_tests.test_calculate_arithmetic_rate_of_return(calculate_arithmetic_rate_of_return)
                    ZUM        NTNW           LKD          VFR         WOH
2001-07-13  21.05081048 17.01384381   10.98450376  11.24809343 12.96171273
2001-07-14  15.63570259 14.69054309   11.35302769 475.74195118 11.95964043
2001-07-15 482.34539247 35.20258059 3516.54167823  66.40531433 13.50396048
2001-07-16  10.91893302 17.90864387   24.80126542  12.48895419 10.52435923
2001-07-17  10.67597197 12.74940144   11.80525758  21.53903949 19.99766037
2001-07-18  11.54549538 23.98146843   24.97476306  36.03196210 14.30433232
                    ZUM        NTNW           LKD          VFR         WOH
2001-07-13          nan         nan           nan          nan         nan
2001-07-14  21.05081048 17.01384381   10.98450376  11.24809343 12.96171273
2001-07-15  15.63570259 14.69054309   11.35302769 475.74195118 11.95964043
2001-07-16 482.34539247 35.20258059 3516.54167823  66.40531433 13.50396048
2001-07-17  10.91893302 17.90864387   24.80126542  12.48895419 10.52435923
2001-07-18  10.67597197 12.74940144   11.80525758  21.53903949 19.99766037
                   ZUM        NTNW          LKD         VFR         WOH
2001-07-13         nan         nan          nan         nan         nan
2001-07-14 -0.25723988 -0.13655355   0.03354944 41.29534136 -0.07731018
2001-07-15 29.84897463  1.39627496 308.74483411 -0.86041737  0.12912763
2001-07-16 -0.97736283 -0.49126900  -0.99294726 -0.81192839 -0.22064647
2001-07-17 -0.02225135 -0.28808672  -0.52400584  0.72464717  0.90013092
2001-07-18  0.08144677  0.88098779   1.11556274  0.67286764 -0.28469971
ZUM     4.77892789
NTNW    0.22689225
LKD    51.39616553
VFR     6.83675173
WOH     0.07443370
Name: 2001-07-18, dtype: float64
Tests Passed

为者常成,行者常至