Bear Market Similarity Analysis using Nasdaq 100 Index Data

Nasdaq, S&P 500, Dow Jones extend losses as markets price in more aggressive Fed.

No matter how long you’ve been putting your money to work in the stock market, it’s been a challenging past five months. Since the year began, the widely followed Dow Jones Industrial Average has dipped as much as 15% from its all-time closing high. The benchmark S&P 500 has fared modestly worse, with a peak-to-trough intraday decline that briefly touched 20%.

But it’s the growth-stock-driven Nasdaq Composite (^IXIC -3.17%) that’s been taken to the woodshed. In the six months following its record-closing high, the Nasdaq declined by as much as 31%, which firmly places the index in a bear market.

Let’s quantify similarities between the current market conditions and the following six historical bear market events in which the stock price fell by 20% or more from its high price:

  • 1987 Black Monday
  • 1990 Recession
  • 2000 Dot-Com Bubble
  • 2008 Financial Crisis
  • 2020 COVID-19 Pandemic
  • 2021-2022 Bear Market

We will analyze historical bear markets using the Nasdaq 100 Index data in Python.

Let’ set the working directory YOURPATH and install/import Python libraries

!pip install fastdtw

import os
import datetime as dt
import itertools
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import pandas_datareader.data as web
from sklearn import preprocessing
from sklearn.metrics.pairwise import cosine_similarity
from fastdtw import fastdtw
data_dir = “YOURPATH”
os.makedirs(data_dir, exist_ok=True)

Let’s get the historical data of Nasdaq 100 index(^NDX) from Yahoo Finance and save the data

nasdaq_100 = web.DataReader(‘^NDX’, ‘yahoo’, start=’1980-01-01′, end=’2022-05-28′)

print(nasdaq_100.shape)

nasdaq100_file_path = f”{data_dir}/nasdaq100.csv”
nasdaq_100.to_csv(nasdaq100_file_path)
nasdaq_100 = pd.read_csv(nasdaq100_file_path, index_col=”Date”, parse_dates=True)
nasdaq_100.head(3)

(9241, 6)
Nasdaq 100 historical data table

Let’s plot the historical price

fig, ax = plt.subplots(figsize=(15, 8))
ax.plot(nasdaq_100.index, nasdaq_100[“Close”])
ax.grid(axis=”y”)
ax.set_title(“Nasdaq 100 historical price”)
ax.set_xlabel(“Date”)
ax.set_ylabel(“Price”)
plt.show()

Nasdaq 100 historical price

We consider the following 6 bear market situations

ts_black_monday = nasdaq_100.loc[“1987-10-05″:”1987-10-26”,
“Close”] / nasdaq_100.loc[“1987-10-05”, “Close”]
ts_1990_recession = nasdaq_100.loc[“1990-07-16″:”1990-10-11”,
“Close”] / nasdaq_100.loc[“1990-07-16”, “Close”]
ts_dotcom = nasdaq_100.loc[“2000-03-27″:”2002-10-07”,
“Close”] / nasdaq_100.loc[“2000-03-27”, “Close”]
ts_financial_crisis = nasdaq_100.loc[“2008-06-05″:”2009-03-09”,
“Close”] / nasdaq_100.loc[“2008-06-05”, “Close”]
ts_covid19_pandemic = nasdaq_100.loc[“2020-02-19″:” 2020-03-20″,
“Close”] / nasdaq_100.loc[“2020-02-19”, “Close”]
ts_current_bear = nasdaq_100.loc[“2021-11-19”: “2022-05-24”,
“Close”] / nasdaq_100.loc[“2021-11-19”, “Close”]
ts_current_bear

Date
2021-11-19    1.000000
2021-11-22    0.988393
2021-11-23    0.983913
2021-11-24    0.987599
2021-11-26    0.966949
                ...   
2022-05-18    0.719729
2022-05-19    0.716550
2022-05-20    0.714136
2022-05-23    0.726123
2022-05-24    0.710167
Name: Close, Length: 128, dtype: float64

Let’s plot these events

fig, ax = plt.subplots(figsize=(15, 8))
ax.plot(range(len(ts_black_monday)), ts_black_monday, alpha=1, label=”Black Monday”)
ax.plot(range(len(ts_1990_recession)), ts_1990_recession, alpha=0.5, label=”early 1990s recession”)
ax.plot(range(len(ts_dotcom)), ts_dotcom, alpha=0.5, label=”Dotcom Bubble”)
ax.plot(range(len(ts_financial_crisis)), ts_financial_crisis, alpha=0.5, label=”Financial Crisis”)
ax.plot(range(len(ts_covid19_pandemic)), ts_covid19_pandemic, alpha=1, label=”COVID-19 recession”)
ax.plot(range(len(ts_current_bear)), ts_current_bear, alpha=0.7, label=”current bear market”)
ax.scatter([211.8], [1 – 0.5246], marker=”x”, color=”red”)
ax.grid(axis=”y”)
ax.set_ylim(0, 1)
ax.set_title(“Nasdaq 100 historical bear market”)
ax.set_xlabel(“Duration(in trading days)”)
ax.set_ylabel(“Price”)
ax.legend()
plt.show()

Nasdaq 100 six historical bear markets

We can see that the average decline was 52.46% and the average duration was 211.8 trading days.

Let’s plot the decline in stock prices in each bear market normalized by its highest price

event_name_list = [“Black Monday”, “Early 1990 Recession”, “Dotcom Bubble”,
“Financial Crirsis”, “COVID19 Pandmic”, “Current Bear”]
def calculate_downside(data):
return 1 – data.min()
decrease_rate = [calculate_downside(ts_black_monday),
calculate_downside(ts_1990_recession),
calculate_downside(ts_dotcom),
calculate_downside(ts_financial_crisis),
calculate_downside(ts_covid19_pandemic),
calculate_downside(ts_current_bear)]
df_feature1 = pd.DataFrame({“Name”: event_name_list, “Decrease Rate”: decrease_rate})
df_feature1

decrease rate Nasdaq 100 6 bear markets

This table shows that Dotcom Bubble had the maximum Decrease Rate (DR) of 83%. The current bear market is clearly closer to the time of the COVID 19 pandemic with DR ~ 28%.

Let’s calculate the Decrease Speed (DS) or slope from the highest to the lowest

def calculate_slope(data):
return (1 – data.min()) / len(data)
decrease_speed = [calculate_slope(ts_black_monday),
calculate_slope(ts_1990_recession),
calculate_slope(ts_dotcom),
calculate_slope(ts_financial_crisis),
calculate_slope(ts_covid19_pandemic),
calculate_slope(ts_current_bear)]
df_feature2 = pd.DataFrame({“Name”: event_name_list, “Decrease Speed”: decrease_speed})
df_feature2

Decrease speed table

It is clear that the current bear market has DS~0.002 similar to that of the Financial Crisis.

Let’s calculate the duration (DUR) of the bear market event

duration = [len(ts_black_monday),
len(ts_1990_recession),
len(ts_dotcom),
len(ts_financial_crisis),
len(ts_covid19_pandemic),
len(ts_current_bear)]
df_feature3 = pd.DataFrame({“Name”: event_name_list, “Duration”: duration})
df_feature3

duration table

We can see that DUR(1990_recession)<DUR(current_bear)<DUR(financial_crisis).

Let’s merge the DUR and DR features

df_feature4 = pd.DataFrame({“Name”: event_name_list, “Decrease Rate”: decrease_rate, “Duration”: duration})
df_feature4

Decrease rate and duration table

Let’s apply standard scaling of these two features and plot them

decrease_rate_scaled = preprocessing.scale(decrease_rate)
duration_scaled = preprocessing.scale(duration)
fig, ax = plt.subplots(figsize=(8, 8))
for i, event in enumerate(event_name_list):
ax.scatter(decrease_rate_scaled[i], duration_scaled[i], marker=”D”, label=event)
ax.grid(linestyle=”–“)
ax.set_xlim(-2.5, 2.5)
ax.set_ylim(-2.5, 2.5)
ax.set_xlabel(“Decrease Rate”)
ax.set_ylabel(“Duration(trading days)”)
ax.legend()
plt.show()

Decrease rate vs Duration period for 6 bear markets

In fact, this X-plot measures the Euclidean distance between our 6 features in terms of (scaled) DR and DUR. It shows that current_bear is closest to 1990_recession and covid19_pandemic.

Let’s introduce a different distance metric to calculate the similarity such as the Cosine Similarity (CS)

df_feature4_1 = pd.DataFrame(
{“Name”: event_name_list, “Decrease Rate”: decrease_rate_scaled, “Duration”: duration_scaled})
similarity_list = []
for i in df_feature4_1.index:
cos_sim = cosine_similarity(df_feature4_1.loc[[i], [“Decrease Rate”, “Duration”]], df_feature4_1.loc[[5], [
“Decrease Rate”, “Duration”]])
similarity_list.append(cos_sim[0][0])
df_feature4_1[“Similarity”] = similarity_list
df_feature4_1

Combined duration and similarity table

Let’s look at the X-plot of scaled DR and DUR features

fig, ax = plt.subplots(figsize=(8, 8))
for i, event in enumerate(event_name_list):
ax.scatter(decrease_rate_scaled[i], duration_scaled[i], marker=”D”, label=event)
ax.plot([0, decrease_rate_scaled[5]], [0, duration_scaled[5]], color=”black”, alpha=0.6)
ax.plot([0, decrease_rate_scaled[4]], [0, duration_scaled[4]], color=”black”, linestyle=”dotted”, alpha=0.6)
ax.grid(linestyle=”–“)
ax.set_xlim(-2.5, 2.5)
ax.set_ylim(-2.5, 2.5)
ax.set_xlabel(“Decrease Rate”)
ax.set_ylabel(“Duration(trading days)”)
ax.legend()
plt.show()

Cosine similarity between 6 bear markets

The above table and X-plot show that current_bear is closest to covid19_pandemic and 1990_recession in terms of CS.

We can also calculate the similarity between our time-series data arrays of different lengths using the Dynamic Time Warping (DTW) library

def calc_dtw(l_sr):
dict_dtw = {}
for item1, item2 in itertools.product(l_sr, repeat=2):
distance, path = fastdtw(item1[1], item2[1])
dict_dtw[(item1[0], item2[0])] = distance
return dict_dtw
dict_dtw = calc_dtw(list(zip(event_name_list,
[ts_1990_recession,
ts_black_monday,
ts_dotcom,
ts_financial_crisis,
ts_covid19_pandemic,
ts_current_bear])))
dist_matrix = np.array(list(dict_dtw.values())).reshape(6, 6)
df_dist_matrix = pd.DataFrame(data=dist_matrix, index=event_name_list, columns=event_name_list)
plt.figure(figsize=(8, 6))
sns.heatmap(df_dist_matrix, square=True, annot=True, cmap=’Blues’)
plt.show()

DTW similarity between 6 bear markets

Let’s plot the heatmap of this similarity matrix excluding the outlier (dotcom)

event_name_list_2 = event_name_list[:2] + event_name_list[3:]
dict_dtw_2 = calc_dtw(list(zip(event_name_list_2,
[ts_1990_recession,
ts_black_monday,
ts_financial_crisis,
ts_covid19_pandemic,
ts_current_bear])))
dist_matrix_2 = np.array(list(dict_dtw_2.values())).reshape(5, 5)
df_dist_matrix_2 = pd.DataFrame(data=dist_matrix_2, index=event_name_list_2, columns=event_name_list_2)
plt.figure(figsize=(8, 6))
sns.heatmap(df_dist_matrix_2, square=True, annot=True, cmap=’Blues’)
plt.show()

Heatmap correlation similarity between 6 bear markets

We can see that covid19_pandemic, black_monday, and 1990_recession are closest to current_bear.

We have discussed the measurement of similarity between various bear markets in real time. This is a kind of pattern analysis method that is applicable to the time series of variable lengths. 

The outcome of this study is important for the investment community. Using these results, investors can gain valuable insights about maintaining risk aware market stability during periods of economic crises. In doing so, investors would be able to avoid large losses whilst hedging systemic risk of global events listed above.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: