3 min read

Will the Recession Ever Arrive? A Data-Driven Look at the Yield Curve and Jobless Rate

Analyzing the 2-Year/10-Year Treasury Spread, Unemployment Trends, and What They Mean for the Economy
Will the Recession Ever Arrive? A Data-Driven Look at the Yield Curve and Jobless Rate
Photo by Matthew Osborn / Unsplash

I’m always keeping an eye on economic data for clues about where the broader market and sentiment might be headed. Since I’m a visual thinker, I like to create charts—like my egg price and median house price charts. In this post, I’ll share my Python code for generating a chart that overlays the 2-year/10-year Treasury yield spread with the current jobless rate, highlighting recession periods for context.

I track this chart closely because the 2-year/10-year spread has historically predicted recessions whenever it turns negative. However, the timing of a recession remains uncertain. This is where jobless rates become a crucial indicator—when employers start shedding jobs, the likelihood of a recession increases.

So, what is this chart telling us right now? As long as jobless rates remain low, a recession is delayed. However, I’ll be watching closely now that government cuts under President Trump and Elon Musk are in motion.

Here’s the Python code to generate the same chart.

import pandas as pd
import matplotlib.pyplot as plt
from pandas_datareader.data import DataReader
from datetime import datetime

save_path = "./results/jobless-claims-2yr-10yr-recessions.png"

# Fetch 2-Year and 10-Year Treasury yield data from FRED
def fetch_treasury_spread():
    """
    Fetches the 2-Year and 10-Year Treasury yields from FRED and calculates the spread.
    Returns:
        DataFrame with date index and the treasury spread.
    """
    two_year_code = "DGS2"
    ten_year_code = "DGS10"
    start_date = "1988-01-01"
    end_date = datetime.now()
    two_year = DataReader(two_year_code, "fred", start_date, end_date)
    ten_year = DataReader(ten_year_code, "fred", start_date, end_date)
    data = pd.concat([ten_year, two_year], axis=1)
    data.columns = ["10-Year Yield", "2-Year Yield"]
    data["Spread (10Y - 2Y)"] = data["10-Year Yield"] - data["2-Year Yield"]
    return data.dropna()

# Fetch jobless rate data from FRED
def fetch_jobless_rate():
    """
    Fetches US unemployment rate data from FRED.
    Returns:
        DataFrame with date index and jobless rate.
    """
    start_date = "1988-01-01"
    end_date = datetime.now()
    jobless_rate = DataReader("UNRATE", "fred", start_date, end_date)
    jobless_rate.rename(columns={"UNRATE": "Jobless Rate"}, inplace=True)  # Fix column name
    return jobless_rate

# Fetch recession data from FRED
def fetch_recession_data():
    start_date = "1988-01-01"
    end_date = datetime.now()
    recession_data = DataReader("USREC", "fred", start_date, end_date)
    return recession_data

def get_recession_periods(recessions):
    recession_periods = []
    in_recession = False
    start_date = None
    for date, value in recessions["USREC"].items():
        if value == 1 and not in_recession:
            in_recession = True
            start_date = date
        elif value == 0 and in_recession:
            in_recession = False
            end_date = date
            recession_periods.append((start_date, end_date))
    return recession_periods

def plot_treasury_spread_with_jobless(data, jobless_rate, recession_periods):
    fig, ax1 = plt.subplots(figsize=(14, 8))
    ax1.plot(data.index, data["Spread (10Y - 2Y)"], color="blue", linewidth=1.5, label="10Y - 2Y Spread")
    ax1.axhline(0, color="red", linestyle="--", linewidth=1, alpha=0.7, label="Zero Spread")
    ax1.set_xlabel("Year", fontsize=14, labelpad=10)
    ax1.set_ylabel("Spread (%)", fontsize=14, labelpad=10, color="blue")
    ax1.tick_params(axis="y", labelcolor="blue")
    ax1.grid(visible=True, which="major", linestyle="--", linewidth=0.5, alpha=0.7)
    for start_date, end_date in recession_periods:
        ax1.axvspan(start_date, end_date, color="gray", alpha=0.3, label="Recession" if start_date == recession_periods[0][0] else "")
    ax2 = ax1.twinx()
    ax2.plot(jobless_rate.index, jobless_rate["Jobless Rate"], color="green", linewidth=1.5, label="Jobless Rate")
    ax2.set_ylabel("Jobless Rate (%)", fontsize=14, labelpad=10, color="green")
    ax2.tick_params(axis="y", labelcolor="green")
    
    lines, labels = ax1.get_legend_handles_labels()
    lines2, labels2 = ax2.get_legend_handles_labels()
    ax1.legend(lines + lines2, labels + labels2, fontsize=12, loc="upper left")

    # Add copyright text
    fig.text(0.94, 0.10, "(c) neuralmarkettrends.com", fontsize=10, color="red", ha="right", va="bottom")
  
    
    plt.title("2-Year and 10-Year Treasury Spread, Jobless Rate, and Recessions", fontsize=18, fontweight="bold", pad=20)
    plt.suptitle("Spread = (10-Year Yield - 2-Year) Yield", fontsize=12, color="gray", style="italic", y=0.91)
    plt.tight_layout()
    plt.savefig(save_path, dpi=300, bbox_inches="tight")
    plt.show()

if __name__ == "__main__":
    try:
        treasury_spread = fetch_treasury_spread()
        jobless_rate = fetch_jobless_rate()
        recession_data = fetch_recession_data()
        recession_periods = get_recession_periods(recession_data)
        plot_treasury_spread_with_jobless(treasury_spread, jobless_rate, recession_periods)
    except Exception as e:
        print(f"An error occurred: {e}")