Skip to main content
Fixed Income · Part 1 of 5

Yield Curve Monitor

The yield curve has predicted every US recession since 1955. OpenClaw checks it automatically every morning using free FRED data — and alerts you the day it inverts.

FRED Free API 2s10s Spread Inversion Alert Daily Brief
⚠️
Yield curve data is for educational monitoring only. An inverted yield curve is a historical pattern — it is not a guaranteed recession predictor. Not financial advice.

What Is the Yield Curve?

The US government borrows money by selling Treasury bonds. It sells bonds that mature (pay back) in different timeframes — 2 years, 5 years, 10 years, 30 years. Each has its own interest rate, called a yield.

The yield curve is simply a line connecting all those yields from shortest to longest. Normally the line slopes upward — you get paid more interest for lending longer, because you're taking more risk. It makes sense: if you lend the government money for 30 years instead of 2 years, you want more compensation for that extra risk.

Here's what normal and inverted curves look like:

NORMAL CURVE (healthy economy): % 5.0│ ●─── 30yr 4.5│ ●────────── 4.0│ ●────────── 3.5│ ●────────── └──────────────────────────────────── 2yr 5yr 10yr 30yr INVERTED CURVE (recession warning): % 5.5│ ●────────── 5.0│ ●────────── 4.5│ ●────────── 4.2│ ●─── 30yr └──────────────────────────────────── 2yr 5yr 10yr 30yr

Why Does Inversion Matter?

When short-term rates are higher than long-term rates, it tells us something important: the bond market thinks things will get worse before they get better. Investors are rushing into long-term bonds as a safe haven — driving long-term yields down — while short-term rates stay high because the Federal Reserve hasn't cut yet.

The 2s10s spread (2-year yield minus 10-year yield) is the most-watched measure. When it goes negative — inversion — it has preceded every US recession since the 1950s, typically by 6–24 months.

Historical Inversions & Recessions

Inversion Year Recession Followed
1978 1980 recession
1988 1990 recession
2000 2001 recession
2006 2008–09 financial crisis
2019 2020 recession
2022–2023 Ongoing monitoring

Note: Past inversions are historical data, not guarantees of future recessions.

What OpenClaw Does

OpenClaw fetches the 2-year and 10-year Treasury yields from FRED every morning. It calculates the spread, plots the full curve shape, and sends you a briefing. If the curve inverts — or if the spread crosses a threshold you've set — you get an alert.

You set up your fi-config.yaml once, and OpenClaw handles the rest. No API keys to juggle, no spreadsheet gymnastics, no manual checking. Just automated insights delivered to your inbox.

Getting Your Free FRED API Key

FRED (Federal Reserve Economic Data) publishes Treasury yields for free. Getting started is simple:

Step-by-Step Setup

  1. Visit fred.stlouisfed.org
  2. Create a free account (30 seconds)
  3. Click My AccountAPI Keys"Request API Key"
  4. Copy your key into fi-config.yaml

Test Your Connection

Install the required library and verify FRED is working:

# Install the required library
pip install fredapi pandas pyyaml

# Verify it works
python3 -c "
from fredapi import Fred
import os
fred = Fred(api_key='your_key_here')
print('10-year yield:', fred.get_series('DGS10').iloc[-1], '%')
print('✅ FRED connection working')
"

The Yield Curve Config

Save this to fi-config.yaml in your OpenClaw project directory:

# fi-config.yaml — yield curve section
fred:
  api_key: "your_fred_api_key_here"

yield_curve:
  maturities:
    - { label: "2-Year",  series: "DGS2"  }
    - { label: "5-Year",  series: "DGS5"  }
    - { label: "10-Year", series: "DGS10" }
    - { label: "30-Year", series: "DGS30" }
  alerts:
    inversion: true           # Alert when 2yr yield > 10yr yield
    spread_threshold: -0.25   # Alert if 2s10s drops below -0.25%
    steepening_move: 0.50     # Alert if spread widens 0.50% in a week

alerts:
  email: "you@example.com"
  daily_digest: true

Replace your_fred_api_key_here with the key you just requested. Replace you@example.com with your actual email.

The Yield Curve Script

This is the core OpenClaw yield curve monitor. It fetches Treasury yields from FRED, calculates the spread, and checks alert conditions:

# fi_yield_curve.py — OpenClaw Yield Curve Monitor
# Fetches Treasury yields from FRED and checks for inversion

import yaml
from fredapi import Fred
import pandas as pd
from datetime import datetime, timedelta

# ── Step 1: Load your config ──────────────────────────────────────
def load_config(path: str = "fi-config.yaml") -> dict:
    with open(path) as f:
        return yaml.safe_load(f)

cfg  = load_config()
fred = Fred(api_key=cfg["fred"]["api_key"])
YC   = cfg["yield_curve"]

print(f"🦞 OpenClaw Yield Curve Monitor")
print(f"   {datetime.now().strftime('%Y-%m-%d %H:%M')}")
print("─" * 45)

# ── Step 2: Fetch current yields from FRED ────────────────────────
def fetch_yields() -> dict:
    """
    Fetches the latest available yield for each maturity.
    FRED updates daily — we take the most recent non-null value.
    """
    yields = {}
    for item in YC["maturities"]:
        series = fred.get_series(item["series"], observation_start="2020-01-01")
        latest = series.dropna().iloc[-1]   # most recent value
        yields[item["label"]] = round(float(latest), 3)
        print(f"   {item['label']:<10} {latest:.3f}%")
    return yields

yields = fetch_yields()

# ── Step 3: Calculate the key spread ─────────────────────────────
two_year  = yields.get("2-Year",  0)
ten_year  = yields.get("10-Year", 0)
spread_2s10s = round(ten_year - two_year, 3)

print(f"\n   2s10s Spread: {spread_2s10s:+.3f}%")
if spread_2s10s < 0:
    print(f"   ⚠️  INVERTED — short-term rates exceed long-term")
else:
    print(f"   ✅ Normal — curve is positively sloped")

# ── Step 4: Check alert conditions ───────────────────────────────
def check_alerts(yields: dict, spread: float) -> list:
    """Check configured alert thresholds. Returns list of alert messages."""
    alerts_cfg = YC.get("alerts", {})
    fired = []

    # Inversion alert
    if alerts_cfg.get("inversion") and spread < 0:
        fired.append({
            "type": "INVERSION",
            "message": f"2s10s spread is INVERTED at {spread:+.3f}% — 2yr ({yields['2-Year']}%) > 10yr ({yields['10-Year']}%)",
            "severity": "HIGH"
        })

    # Threshold alert
    threshold = alerts_cfg.get("spread_threshold", -0.25)
    if spread < threshold:
        fired.append({
            "type": "SPREAD_BELOW_THRESHOLD",
            "message": f"2s10s spread {spread:+.3f}% is below your threshold of {threshold:+.3f}%",
            "severity": "MEDIUM"
        })

    return fired

alerts = check_alerts(yields, spread_2s10s)
if alerts:
    print(f"\n   🚨 {len(alerts)} alert(s) triggered:")
    for a in alerts:
        print(f"   [{a['severity']}] {a['message']}")
else:
    print(f"\n   ✓ No alerts triggered today")

The Weekly Trend

Understanding whether the spread is improving or worsening is crucial. This snippet pulls the last 90 days of spread history and shows you the trend:

def fetch_spread_history(days: int = 90) -> pd.Series:
    """
    Fetches 2s10s spread history for the past N days.
    Useful for seeing whether the inversion is deepening or recovering.
    """
    start = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")

    two_yr = fred.get_series("DGS2",  observation_start=start).dropna()
    ten_yr = fred.get_series("DGS10", observation_start=start).dropna()

    # Align the two series (some days one may be missing)
    spread = (ten_yr - two_yr).dropna()
    spread.name = "2s10s_spread"

    return spread

history = fetch_spread_history(90)

# Show last 10 readings
print(f"\n📅 2s10s Spread — Last 10 trading days:")
print(f"   {'Date':<14} {'Spread':>8} {'Status'}")
print("   " + "─" * 35)
for date, val in history.tail(10).items():
    status = "⚠️  Inverted" if val < 0 else "✅ Normal"
    print(f"   {str(date.date()):<14} {val:>+7.3f}%  {status}")

# Trend check: is it getting better or worse?
recent_avg  = history.tail(5).mean()
previous_avg = history.tail(15).head(10).mean()
trend = "improving" if recent_avg > previous_avg else "worsening"
print(f"\n   5-day avg: {recent_avg:+.3f}% | 10-day prior avg: {previous_avg:+.3f}%")
print(f"   Trend: {trend.upper()}")

Sample Morning Brief Output

Here's what your OpenClaw automated brief looks like each morning:

🦞 OpenClaw Fixed Income Brief — 2025-03-14 ═══════════════════════════════════════════ YIELD CURVE SNAPSHOT 2-Year: 4.891% 5-Year: 4.612% 10-Year: 4.487% 30-Year: 4.601% 2s10s Spread: -0.404% ⚠️ INVERTED 5s30s Spread: -0.011% ⚠️ Near inversion Trend (5d vs prior 10d): Worsening ALERTS 🔴 [HIGH] 2s10s spread has been inverted for 180+ days ═══════════════════════════════════════════

This brief is automatically emailed to you every morning at 7:00 AM ET, before US market open. No intervention needed — OpenClaw handles it all.

Scheduling Daily Checks

To run your yield curve monitor automatically every weekday morning (before US market open at 9:30 AM ET), use cron:

# crontab -e
# Run every weekday at 7:00 AM local time
0 7 * * 1-5 cd /path/to/openclaw && python3 fi_yield_curve.py >> fi_brief.log 2>&1

This creates a log file fi_brief.log where you can review past briefs and debug any issues.

Tip: Adjust the time (the first 0 7) to match your timezone and preferences. The format is minute hour day month weekday.

FRED Series Reference

These are the official FRED series IDs for US Treasury yields. They update daily after market close:

Series ID Description Update Frequency
DGS3MO 3-Month Treasury Bill Rate Daily
DGS2 2-Year Treasury Constant Maturity Rate Daily
DGS5 5-Year Treasury Constant Maturity Rate Daily
DGS10 10-Year Treasury Constant Maturity Rate Daily
DGS30 30-Year Treasury Constant Maturity Rate Daily

For a complete list of all FRED series, visit fred.stlouisfed.org.