University of Michigan Consumer Sentiment (UMCSENT)

A reproducible, monthly demand signal from FRED: UMCSENT. We align to month‑end, smooth the level with a 3‑month average, compute YoY and 3‑month momentum, standardise sentiment with a rolling z‑score (mean & median anchors), and classify regimes with a neutral band to reduce month‑to‑month whipsaws.

FRED UMCSENT 3M Smoothing Momentum Rolling Z (Mean+Median) Neutral Band

Abstract

We transform the University of Michigan Consumer Sentiment index into a monthly, classification‑ready demand signal. After resampling to month‑end and smoothing the level with a 3‑month average, we derive YoY and 3‑month percent changes to capture momentum. We then standardise the level with a rolling z‑score that blends mean/standard‑deviation and median/MAD anchors for robustness. The final regime (Bullish, Neutral, Bearish) is determined by combining level context (z) with momentum (YoY/3M), with an explicit neutral band to reduce noise‑driven flips.

1) Data (FRED Identifier)

We standardise to a month‑end frequency ("M") and .ffill() within month to avoid gaps around release dates.

# Fetch & align (fredapi)
umich = fred.get_series("UMCSENT")
df_sent = pd.DataFrame(umich, columns=["UMCSENT"])
df_sent = df_sent.resample("M").last().ffill()
print("✅ UMCSENT retrieved."); print(df_sent.tail())

2) Feature Engineering (Noise‑Aware)

Survey results can exhibit high month‑to‑month volatility. To reduce spurious flips driven by noise, we smooth the level with a simple 3‑month moving average before computing momentum.

\( \tilde{X}_t = \frac{1}{3}(X_t + X_{t-1} + X_{t-2}) \)
\( \text{YoY\_pct}_t = 100\times\left(\dfrac{\tilde{X}_t}{\tilde{X}_{t-12}}-1\right) \)
\( \text{3M\_pct}_t = 100\times\left(\dfrac{\tilde{X}_t}{\tilde{X}_{t-3}}-1\right) \)
# Smooth then compute momentum
df_sent["UMCSENT_3M"] = df_sent["UMCSENT"].rolling(3, min_periods=3).mean()
df_sent["YoY_pct"] = df_sent["UMCSENT_3M"].pct_change(12) * 100
df_sent["3M_pct"]  = df_sent["UMCSENT_3M"].pct_change(3)  * 100

3) Rolling Z‑Score on Level (Mean + Median Anchors)

To interpret the absolute level of sentiment relative to its own history, we compute a rolling z‑score on the smoothed level \(\tilde{X}_t\). Because surveys can have outliers and structural shifts, we blend two anchors: a conventional mean/standard‑deviation z‑score and a robust median/MAD z‑score.

\( z^{\mu}_t = \dfrac{\tilde{X}_t - \text{mean}_W(\tilde{X})}{\text{stdev}_W(\tilde{X})} \)
\( z^{\text{med}}_t = \dfrac{\tilde{X}_t - \text{median}_W(\tilde{X})}{1.4826\,\text{MAD}_W(\tilde{X})} \)
\( z_t = 0.5\,z^{\mu}_t + 0.5\,z^{\text{med}}_t \)

Default window: 120 months (min 36). If the window is not fully available, we degrade gracefully to shorter history.

def rolling_z_blend(series, window=120, min_window=36):
    s = series.astype(float)
    n = s.notna().sum()
    w = max(min_window, window if n >= window else int(max(min_window, n * 0.8)))

    mean = s.rolling(w, min_periods=min_window).mean()
    std  = s.rolling(w, min_periods=min_window).std(ddof=0)

    med = s.rolling(w, min_periods=min_window).median()
    mad = (s - med).abs().rolling(w, min_periods=min_window).median()

    z_mean = (s - mean) / std.replace(0, np.nan)
    z_med  = (s - med)  / (1.4826 * mad.replace(0, np.nan))

    return 0.5 * z_mean + 0.5 * z_med

df_sent["Level_z"] = rolling_z_blend(df_sent["UMCSENT_3M"], window=120, min_window=36)

4) Regime Classification (Neutral Band + Smoothing)

Survey results can be revised; high survey volatility means month‑to‑month flips may reflect noise rather than trend. To reduce this, we introduce a neutral band around zero and base both z‑score and momentum on the smoothed level.

  • Neutral band: |Level_z| < 0.25Neutral
  • Bullish (demand tailwind): Level_z > +0.75 and (YoY_pct > 0 or 3M_pct > +1)
  • Bearish (demand headwind): Level_z < −0.75 and (YoY_pct < 0 or 3M_pct < −1)
  • Neutral: otherwise (including any NaNs)
def classify_umich(row, z_hi=0.75, z_lo=-0.75, z_neutral=0.25, yoy_up=0.0, yoy_dn=0.0, m3_up=1.0, m3_dn=-1.0):
    z, yoy, m3 = row["Level_z"], row["YoY_pct"], row["3M_pct"]
    if np.isnan(z) or np.isnan(yoy) or np.isnan(m3):
        return "Neutral"
    if abs(z) < z_neutral:
        return "Neutral"
    if (z > z_hi) and ((yoy > yoy_up) or (m3 > m3_up)):
        return "Bullish"
    if (z < z_lo) and ((yoy < yoy_dn) or (m3 < m3_dn)):
        return "Bearish"
    return "Neutral"

df_sent["UMCSENT_Signal"] = df_sent.apply(classify_umich, axis=1)

Thresholds are tunable; in practice, widening the neutral band reduces flip frequency at the cost of responsiveness.

5) Outputs & Columns

# Core columns
["UMCSENT","UMCSENT_3M","YoY_pct","3M_pct","Level_z","UMCSENT_Signal"]

Publishing UMCSENT_3M and Level_z alongside the discrete regime makes it easier to diagnose “near‑threshold” states and interpret whether the regime is supported by a meaningful move or just small noise.

6) Data Handling & Validation

7) Model Interpretation (How to Read the Signal)

Consumer sentiment is a survey-based indicator. Academic work finds that confidence/sentiment measures can contain incremental information about future consumption growth, but the series is noisy and can be influenced by non-economic factors. Treat the regime label as a demand backdrop, not a deterministic forecast.

  • Level_z (context): positive means sentiment is high relative to its rolling history; negative means low. Near zero is “normal”.
  • Neutral band: when |Level_z| is small, the model deliberately returns Neutral (noise dominates).
  • Momentum (YoY/3M): helps timing. Divergence between level and momentum suggests transition risk (e.g., improving from depressed levels vs. rolling over from elevated levels).
  • Flip frequency: frequent flips without large moves in Level_z implies thresholds are too tight; widen z_neutral and/or raise z_hi/z_lo.
  • Real-time vs revised: for decision-grade backtests, use ALFRED vintages (what was available then), not revised history.
  • Survey method changes: step changes may reflect methodological transitions; validate against “hard” activity data before acting on a single print.

Academic & primary references (selected)

Practical caveat

Survey results can be revised; high survey volatility means month‑to‑month flips may reflect noise rather than trend. The smoothing, z‑score standardisation, and neutral band are explicit choices to prioritise stability and interpretability.