avatarLee Vaughan

Summarize

The “Midterm Miracle” Investing Phenomenon

Evaluate a market buy signal with Python and yfinance

Stock market-type graphs in patriotic red, white, and blue (Leonardo AI)

Timing the stock market precisely may be elusive, but certain periods come remarkably close to being opportune. In a previous article, I delved into the “-3.5% Solution” timing strategy. Today, I want to explore another promising approach known as the “Midterm Miracle” (MtM).

The Midterm Miracle encompasses the period from October of a US Congressional midterm election year to the following June. Historically, these 9-month intervals have proven to be consistently profitable for investments in the US stock market. According to insights from billionaire investor Ken Fisher, stocks have shown gains 92% of the time during these periods and delivered higher-than-average returns.

Noteworthy publications such as the Financial Times and Forbes have reported on this phenomenon. The MtM’s success is attributed to the tendency for the president’s party to lose seats in midterm elections, resulting in increased political gridlock in Washington. More gridlock means less uncertainty around legislative actions, so investor sentiment improves.

In this Quick Success Data Science project, we’ll harness the power of Python, pandas, and the yfinance library to conduct an analysis of the MtM and gain insights into its potential as a profitable timing strategy.

The yfinance Library

The yfinance library is an open-source tool that uses Yahoo’s publicly available APIs. It’s intended for research and educational purposes, such as prototyping or downloading historical data, rather than trading real money. It offers a threaded and Pythonic way to download market data from Yahoo! Finance, but it’s not affiliated with Yahoo! Finance. You can install it using either:

conda install -c conda-forge yfinance

or

pip install yfinance

We’ll also use the pandas library for preparing the data and matplotlib for plotting. Here are the installation commands for the command line interface:

conda install matplotlib pandas

or

pip install matplotlib pandas

The Code

The following code was written and executed in JupyterLab and is described by cell.

Importing Libraries and Downloading Data

We’ll start by importing the libraries and then use yfinance to download the data. The yfinance download() method directly builds a pandas DataFrame. Pass it the symbol for the S&P 500 (^GSPC) along with a date range. In this case, we'll look at the period January 1, 1926 to June 30, 2023.

import pandas as pd
import yfinance as yf

df = yf.download('^GSPC', '1926-01-01', '2023-6-30')
display(df)
The output of the previous code (image by the author).

A quick look at the DataFrame indicates that we have 6 columns of data plus a date index. The earliest available data starts in 1927, so we won’t be able to evaluate the 1926 midterm election year.

Removing Unwanted Columns

We need only the closing price (Close) values, so we'll reassign the DataFrame with just this column. We’ll also reset the index so that the dates move to a dedicated Date column in “date aware” datetime format.

df = df['Close']
df = df.reset_index()
pd.to_datetime(df['Date'])
df.head(3)
The output of the previous code (image by the author).

Creating the Midterm Miracle DataFrame

To evaluate the MtM, we’ll need a DataFrame with just the relevant 9-month intervals. Unlike presidential election years, which can be evenly divided by four, midterm years are two years out of sync. So, all we need to do is find the years in the Date column that yield a remainder of 2 when divided by 4. The output will be a pandas Series named midterm_years.

# Filter for rows corresponding to midterm election years:
midterm_years = df[(df['Date'].dt.year % 4 == 2)]['Date'].dt.year

Next, we make a new DataFrame, df_mtm, to hold our MtM data. Then we loop through the midterm_years series and assign start and end dates for the period. This involves using pandas' built-in datetime functionality.

With these dates, we can make a temporary DataFrame, named filtered_data, from which we can extract the starting and ending values and calculate the simple return.

“Simple return” is calculated by subtracting the starting price of an investment from an ending price and then dividing by the starting price.

# Group data into 9-month periods from end Sept. of each midterm election year:
df_mtm = pd.DataFrame()
for year in midterm_years:
    start_date = pd.to_datetime(f"{year}-9-30")
    end_date = start_date + pd.DateOffset(months=9)
    filtered_data = df[(df['Date'] >= start_date) & (df['Date'] < end_date)]
    if not filtered_data.empty:
        start_price = filtered_data.iloc[0]['Close']
        end_price = filtered_data.iloc[-1]['Close']
        simple_return = round(((end_price - start_price) / start_price) * 100, 1)
        df_mtm.loc[year, 'Year'] = year
        df_mtm.loc[year, 'Start Date'] = start_date
        df_mtm.loc[year, 'End Date'] = end_date
        df_mtm.loc[year, 'Simple Return %'] = simple_return
        
df_mtm = df_mtm.astype({'Year':'int'})                                                       
df_mtm.reset_index(drop=True, inplace=True)

print(df_mtm)
The df_mtm DataFrame with all the MtM years (image by the author).

Plotting the Results

To see how often this strategy produces a winner, we’ll calculate the percent of positive returns and print it. Then we’ll use pandas’ built-in plotting functionality to plot a bar chart.

# Find and print the percentage of MtM years with positive returns:
pct_positive = len(df_mtm[df_mtm['Simple Return %'] > 0]) / len(df_mtm) * 100
start = midterm_years.values[0]  # Find the first midterm year in dataset.

print(f"\nMidterm Miracle % positive outcomes since {start}: \
{pct_positive:.2f}%\n")

# Plot each year's Simple Return as a bar chart:
df_mtm.plot(kind='bar',
            x='Year', 
            y='Simple Return %', 
            color=(df_mtm['Simple Return %'] > 0).map({True: 'blue',
                                                       False: 'red'}),
            title='S&P 500 MidTerm Miracle (End September - End June)', 
            legend=False,
            ylabel='Simple Return (%)');
The Midterm Miracle has yielded positive results 87.5% of the time (image by the author).

Examining the Daily Behavior

The previous analysis revealed the outcome of the MtMs, but it didn’t provide a lot of detail. If you adopt this strategy, should you expect smooth sailing or a roller coaster ride? To determine this, we’ll need to inspect the daily closing prices for the MtM periods.

To start, we’ll make a new DataFrame (df_mtm_days) that includes rows for every day of every MtM.

# Make new DataFrame with daily data for MtM periods:
df_mtm_days = pd.DataFrame()
for year in midterm_years:
    start_date = pd.to_datetime(f"{year}-9-30")
    end_date = start_date + pd.DateOffset(months=9)
    filtered_data = df[(df['Date'] >= start_date) & (df['Date'] < end_date)]
    df_mtm_days = pd.concat([filtered_data, df_mtm_days])   

Next, we’ll plot the results. If we plot them all at once, however, the chart will be unreadable. So, we’ll write code that lets you enter a start year and see the associated MtM. To do this, we’ll plot a temporary DataFrame with just the results for the relevant period.

start_year = 1982
end_year = start_year + 1
df_temp = (df_mtm_days[(df_mtm_days['Date'].dt.year >= start_year) & 
                       (df_mtm_days['Date'].dt.year <= end_year)])
df_temp.plot(x='Date', y='Close', 
             title=f"Daily Price Change for {start_year} Midterm Miracle",
             ylabel='Daily Percent Change',
             lw=1,  
             color='k',
             grid=True);
It was up, up, and away for the 1982 MtM.

For MtM periods with sensational results, like 1942 and 1982, it’s mostly a constant climb to success. Other years, however, may have tested your resolve. MtMs in 1934, 2018, and 2002, for example, had to climb out of holes along the way.

The 1934 MtM was saved by a springtime rally.
The 2018 MtM barely eked out a win.
Things looked grim for the 2002 MtM before it rose to the occasion.
The most recent MtM had its bumps along the way.

Outcome

The pandas, matplotlib, and yfinance libraries are great tools for acquiring and analyzing financial data. In this article, we conducted a quick yet insightful analysis of the Midterm Miracle strategy, unveiling a remarkable 87.5% positive return rate since the 1930 midterm election.

Over the last 92 years, the only time this strategy suffered significant losses was during the Great Depression. While “past performance is no indication of future success,” the strategy’s consistently impressive track record warrants serious consideration from savvy investors seeking an edge in their financial endeavors.

Thank You!

Thanks for reading and please follow me for more Quick Success Data Science projects in the future.

In Plain English

Thank you for being a part of our community! Before you go:

Python Programming
Yfinance
Investing
Midterm Miracle
Pandas Dataframe
Recommended from ReadMedium