Calculating Bull/Bear Markets in Pandas

Viewed 534

I was recently given a challenge of calculating the presence of Bull/Bear markets using the values of -1, 1 to denote which one is which.

It is straight forward enough to do this with a for-loop but I know this is the worst way to do these things and it's better to use numpy/pandas methods if possible. However, I'm not seeing an easy way to do it.

Any ways of how to do this, maybe using changes of +/- 20% from current place to determine which regime you're in.

Here's a sample dataframe:

dates = pd.date_range(start='1950-01-01', periods=25000)
rand  = np.random.RandomState(42)

vals    = np.zeros(25000)
vals[0] = 15

for i in range(1, 25000):
    vals[i] = vals[i-1] + rand.normal(0, 1)

df = pd.DataFrame(vals, columns=['Price'], index=dates)

The plot of these prices looks like this:

enter image description here

Anyone have any recommendations to calculate what regime the current point is in?

If you have to use a for loop then that's fine.

2 Answers

I think this might work:

import numpy as np
vals = np.random.normal(0, 1, 25000)
vals[0] = 15
vals = np.cumsum(vals)

Here is a solution using the S&P 500 index from Yahoo! Finance (ticker ^GSPC):

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import yfinance as yf
import requests_cache
session = requests_cache.CachedSession()

df = yf.download('^GSPC', session=session)
df = df[['Adj Close']].copy()

df['dd'] = df['Adj Close'].div(df['Adj Close'].cummax()).sub(1)
df['ddn'] = ((df['dd'] < 0.) & (df['dd'].shift() == 0.)).cumsum()
df['ddmax'] = df.groupby('ddn')['dd'].transform('min')
df['bear'] = (df['ddmax'] < -0.2) & (df['ddmax'] < df.groupby('ddn')['dd'].transform('cummin'))
df['bearn'] = ((df['bear'] == True) & (df['bear'].shift() == False)).cumsum()

bears = df.reset_index().query('bear == True').groupby('bearn')['Date'].agg(['min', 'max'])
print(bears)

df['Adj Close'].plot()
for i, row in bears.iterrows():
    plt.fill_between(row, df['Adj Close'].max(), alpha=0.25, color='r')
plt.gca().yaxis.set_major_formatter(plt.matplotlib.ticker.StrMethodFormatter('{x:,.0f}'))
plt.ylabel('S&P 500 Index (^GSPC)')
plt.title('S&P 500 Index with Bear Markets (> 20% Declines)')

plt.savefig('bears.png')
plt.show()

Here are the bear markets in data frame bears:

             min        max
bearn                      
1     1956-08-06 1957-10-21
2     1961-12-13 1962-06-25
3     1966-02-10 1966-10-06
4     1968-12-02 1970-05-25
5     1973-01-12 1974-10-02
6     1980-12-01 1982-08-11
7     1987-08-26 1987-12-03
8     2000-03-27 2002-10-08
9     2007-10-10 2009-03-06
10    2020-02-20 2020-03-20
11    2022-01-04 2022-06-15

Here is a plot:

Bear markets

Edit: I think this is an improvement from my first solution since ^GSPC provides a longer time series and bear markets are not typically dividend-adjusted.

Related