US Treasury Yield Curve Slope Signal

A reproducible specification for a level–trend regime signal using GS10 and FEDFUNDS (monthly, FRED).

Abstract

This document formalises a rule-based signal derived from the US Treasury yield curve slope, defined as the difference between the 10-year constant-maturity Treasury rate (GS10) and the effective federal funds rate (FEDFUNDS). The slope is computed at a monthly frequency and paired with a 3-month moving-average trend to classify regimes relevant for risk assets—Bullish, Neutral, Bearish—using transparent thresholds and a simple steepening test.

1. Data

Both series are converted to numeric, aligned to a monthly index (MS), and forward-filled within each series prior to merging on Date. Analysis is performed on percentages (level rates).

2. Methodology

Data construction note: Constant‑maturity yields are estimates constructed by the U.S. Treasury; they can be revised and occasionally suffer missing values (e.g., holidays). The signal relies only on the slope; including the level of real yields and term premium would provide more nuance.

  1. Monthly alignment: reindex each series to month-start (MS) over its span and forward-fill missing months.
  2. Merge & spread: create a panel with columns Value_GS10, Value_FEDFUNDS; compute the slope
    Yield_Spreadt = GS10t − FEDFUNDSt
  3. Trend proxy: compute a short-horizon moving average as a steepening/flattening proxy
    Yield_Spread_Trendt = Mean(Yield_Spreadt−2:t) (3 months)
  4. Extremeness (rolling z‑score): compute a rolling z‑score of the slope to measure statistical extremeness
    Zt = (Yield_Spreadt − Mean(Yield_Spread)) / Std(Yield_Spread) (rolling window)
    Default window: 60 months (5 years). Shorter windows adapt faster; longer windows reduce noise.
  5. Classification: apply deterministic rules (for a silver-risk orientation) using level and steepening tests:
    • Bullish: Yield_Spreadt ≤ 0% (curve flat/inverted).
    • Bearish: Yield_Spreadt > 1.5% or a sustained steepening condition (e.g., Yield_Spreadt > Yield_Spread_Trendt for multiple months). Use the rolling z‑score to flag unusually steep configurations.
    • Neutral: 0 < Yield_Spreadt ≤ 0.5% or 0.5 < Yield_Spreadt ≤ 1.5% with Yield_Spreadt ≤ Yield_Spread_Trendt.

The neutral bands recognise that mildly positive slopes can be benign unless accompanied by steepening momentum.

3. Model Interpretation

The slope (term spread) is commonly interpreted as a summary of (i) expected future short rates and (ii) compensation for duration risk (term premia). In the academic literature, the slope has been shown to contain predictive information about future economic activity and recession risk, but it is not a precise timing tool.

Interpretation heuristics

  • Inversion (slope ≤ 0): historically associated with elevated recession risk at 6–18 month horizons; interpret as a risk-warning rather than a trigger for immediate action.
  • Re-steepening: can occur via bull steepening (short rates falling) during deteriorating growth; persistence filters (multi-month confirmation) matter.
  • Very steep slopes: often align with early-cycle expansions, but may also reflect higher term premia; consider adding real-yield levels and a term-premium estimate.

Using z‑scores and persistence

  • Rolling z‑score: interpret Z-scores as “extremeness” rather than direction. Large absolute values indicate unusual configurations relative to recent history.
  • Sustained steepening rule: require multiple months of steepening (and/or a z‑score threshold) before regime flips to reduce noise from data revisions and calendar effects.

Academic anchors (selected)

4. Implementation Notes (Python)

def fetch_fred_series_df(series_id):
    s = fred.get_series(series_id)
    if s is None or s.empty:
        return pd.DataFrame(columns=['Series_ID','title','Date','Value'])
    df = s.reset_index(); df.columns = ['Date','Value']
    try:
        meta = fred.get_series_info(series_id); title = getattr(meta, 'title', None)
    except Exception:
        title = None
    df['Series_ID'] = series_id; df['title'] = title
    df['Date'] = pd.to_datetime(df['Date']); df['Value'] = pd.to_numeric(df['Value'], errors='coerce')
    return df[['Series_ID','title','Date','Value']]

gs10_df = fetch_fred_series_df('GS10')
fedfunds_df = fetch_fred_series_df('FEDFUNDS')

for d in (gs10_df, fedfunds_df):
    d['Date'] = pd.to_datetime(d['Date']); d.set_index('Date', inplace=True)
    d['Value'] = pd.to_numeric(d['Value'], errors='coerce')

rng10 = pd.date_range(gs10_df.index.min(), gs10_df.index.max(), freq='MS')
rngff = pd.date_range(fedfunds_df.index.min(), fedfunds_df.index.max(), freq='MS')
GS10 = gs10_df['Value'].reindex(rng10).ffill()
FF   = fedfunds_df['Value'].reindex(rngff).ffill()

merged_yield_df = pd.concat([GS10, FF], axis=1); merged_yield_df.columns = ['Value_GS10','Value_FEDFUNDS']
merged_yield_df.reset_index(inplace=True); merged_yield_df.rename(columns={'index':'Date'}, inplace=True)

merged_yield_df['Yield_Spread'] = merged_yield_df['Value_GS10'] - merged_yield_df['Value_FEDFUNDS']
merged_yield_df['Yield_Spread_Trend'] = merged_yield_df['Yield_Spread'].rolling(3).mean()

def generate_yield_curve_signal(row):
    s = row['Yield_Spread']; t = row['Yield_Spread_Trend']
    if pd.isna(s) or pd.isna(t):
        return 'Neutral'
    if s <= 0:
        return 'Bullish'
    if s > 1.5 or s > t:
        return 'Bearish'
    if 0.5 < s <= 1.5 and s <= t:
        return 'Neutral'
    if 0 < s <= 0.5:
        return 'Neutral'
    return 'Neutral'

merged_yield_df['Yield_Curve_Signal'] = merged_yield_df.apply(generate_yield_curve_signal, axis=1)

5. Sensitivity and Limitations

6. Reproducibility

7. Applications

Useful as a macro overlay for duration/curve trades, as a conditioning variable in metals or cyclical-beta strategies, and for regime annotation in multi-asset dashboards.

8. References

4. Data Sources Overview

U.S. Treasury Constant Maturity Yields

U.S. Treasury constant-maturity yields are published by the U.S. Department of the Treasury and represent interpolated estimates of yields for standardized maturities (e.g. 2Y, 10Y, 30Y), derived from the Treasury yield curve fitted to actively traded securities.

These series are designed to provide continuity through time, but they are not direct market quotes. Revisions can occur due to curve-fitting updates, and observations may be missing on market holidays or during periods of limited issuance. As a result, they are best interpreted as smooth approximations of term structure conditions rather than precise transaction prices.

Yield Curve Slope Construction

The yield curve slope in this model is defined as the spread between long-dated and short-dated constant-maturity yields (typically 10Y minus 2Y). This transformation removes level effects and focuses attention on expectations of future growth, inflation, and monetary policy.

Academic literature emphasizes that slope measures embed both expectations of short-rate paths and term premia. Because this signal uses only the slope, it intentionally abstracts from decomposing these components; users should therefore treat it as a reduced-form macro signal rather than a structural estimate.

Real Yield and Term Premium Considerations

The model does not explicitly incorporate real yields or term premia (e.g. ACM or Kim–Wright decompositions). Research shows that real rate expectations and term premia can materially affect recession and asset-price forecasting performance. Their exclusion is a deliberate simplification to maintain robustness and transparency, but it limits interpretive nuance.

Statistical Normalisation (Rolling Z-Scores)

To contextualise slope movements through time, the model applies a rolling z-score to the slope series. This expresses the current slope relative to its own recent history, allowing identification of statistically extreme inversions or steepenings.

Rolling normalisation mitigates regime drift and long-run structural changes in interest rate levels, but it also implies that extremeness is defined relative to the chosen lookback window rather than a fixed economic threshold.

Persistence and Regime Confirmation

Regime classification requires persistence: short-lived steepenings or inversions are not sufficient to trigger regime changes. This design choice aligns with empirical findings that sustained yield curve configurations, rather than single observations, carry the strongest macroeconomic signal.