Financial Stress Index (STLFSI4)

A reproducible, weekly risk-conditions signal derived from FRED: STLFSI4 (St. Louis Fed Financial Stress Index, 4th rev.). v1.1 adds an optional pillar view (volatility / credit / liquidity) plus a confidence flag when component series are stale. No plots.

FRED STLFSI4 Weekly (W‑FRI) Robust Z Regime Classification Pillar Decomposition (Optional) Confidence Flag

Abstract

We convert the St. Louis Fed’s composite financial stress index into a weekly risk-on / risk-off classifier. The series is harmonised to a Friday weekly cadence and forward-filled to remove minor publication gaps. We compute a robust z-score on the level to quantify deviations from its long-run distribution, then classify regimes using transparent rules that combine absolute level with relative z-score.

1) Data Sources (Composite + Pillars)

1.1 Primary composite (baseline)

We operate at a weekly Friday cadence ("W-FRI") and forward-fill within week to ensure continuity. STLFSI4 itself is a composite built from multiple market indicators; we treat it as the authoritative headline stress level.

# Fetch & align (fredapi)
stlfsi = fred.get_series("STLFSI4")
df_stress = pd.DataFrame(stlfsi, columns=["STLFSI4"])
df_stress = df_stress.resample("W-FRI").last().ffill()
print("✅ STLFSI4 data retrieved."); print(df_stress.tail())

1.2 Optional “pillar” components (for interpretability)

To make drivers explicit, we optionally source representative sub-series and map them into three pillars. These components are typically near real-time, but can suffer outages, late updates, or discontinuations (especially interbank-spread series).

Pillar Series (FRED) What it captures Notes
Volatility VIXCLS Equity implied volatility (“risk aversion / uncertainty” proxy). Daily close; resampled to W-FRI.
Credit BAMLH0A0HYM2 (alt: BAMLC0A0CM) Corporate credit risk premium (high yield OAS; investment grade OAS as fallback). Daily close; resampled to W-FRI.
Liquidity TEDRATE Interbank/treasury funding stress proxy (TED spread). Marked “DISCONTINUED” on FRED; when stale, pillar confidence is reduced and/or a replacement spread should be substituted.
# Optional pillars (fredapi) -> resample weekly (W-FRI)
vix = fred.get_series("VIXCLS")                 # volatility
hy_oas = fred.get_series("BAMLH0A0HYM2")        # credit
ted = fred.get_series("TEDRATE")                # liquidity (may be discontinued)

df_pillars = pd.DataFrame({"VIXCLS": vix, "HY_OAS": hy_oas, "TEDRATE": ted})
df_pillars = df_pillars.resample("W-FRI").last()

Design intent: the headline classifier remains STLFSI4; pillars are used to explain why stress is changing, and to generate a staleness-aware confidence flag.

2) Robust Z-Score on Level

To measure relative tightness/looseness versus history, we compute a robust z-score using median and MAD over a rolling window. Given weekly data, we use a 3‑year target window (156 weeks) with a minimum of 52 weeks; if history is shorter, we adapt to 80% of available observations but never below 52.

\( z_t = \dfrac{X_t - \text{median}_W(X)}{1.4826\,\text{MAD}_W(X)} \)

Window choice: \(W = \max\{52, \min(156, \lfloor 0.8\,N \rfloor)\}\), where \(N\) is non-missing sample size.

def robust_z(series, window=156, min_window=52):
    s = series.astype(float)
    w = max(min_window, window if s.notna().sum() >= window else int(max(min_window, s.notna().sum()*0.8)))
    med = s.rolling(w, min_periods=min_window).median()
    mad = (s - med).abs().rolling(w, min_periods=min_window).median()
    return (s - med) / (1.4826 * mad.replace(0, np.nan))

df_stress["Stress_z"] = robust_z(df_stress["STLFSI4"], window=156, min_window=52)

2b) Pillar Contributions & Confidence (v1.1, optional)

When pillar series are enabled, we compute a robust z-score for each pillar and aggregate them into an explainable “pillar stress” score. Pillars are oriented so that higher values imply more stress.

  • Volatility pillar: higher VIXCLS → higher stress.
  • Credit pillar: wider OAS (BAMLH0A0HYM2) → higher stress.
  • Liquidity pillar: wider TED (TEDRATE) → higher stress (but series may be discontinued).

We publish both the pillar z-scores and a simple contribution view. A default equal-weight aggregation is used unless explicitly overridden (e.g., w_vol=0.33, w_credit=0.33, w_liq=0.34).

# Robust z per pillar (same robust_z as above)
df_pillars["VIX_z"] = robust_z(df_pillars["VIXCLS"])
df_pillars["Credit_z"] = robust_z(df_pillars["HY_OAS"])
df_pillars["Liq_z"] = robust_z(df_pillars["TEDRATE"])

weights = {"VIX_z": 1/3, "Credit_z": 1/3, "Liq_z": 1/3}
df_pillars["PillarStress_z"] = sum(df_pillars[k] * w for k, w in weights.items())

# Contributions (simple share of weighted z)
for k, w in weights.items():
    df_pillars[f"contrib_{k}"] = (df_pillars[k] * w) / df_pillars["PillarStress_z"].replace(0, np.nan)

Staleness-aware confidence flag

Because component feeds can stall (outages, vendor maintenance, holidays, discontinued series), we attach a confidence flag based on how recently each pillar updated relative to the weekly anchor date.

def staleness_days(series):
    return (series.index.max() - series.dropna().index.max()).days if series.dropna().size else 10_000

# Example rule-of-thumb thresholds (tune to your pipeline):
# 0–7d: OK, 8–21d: stale, >21d: critical (or discontinued)
stale = {
  "vol": staleness_days(df_pillars["VIXCLS"]) > 7,
  "credit": staleness_days(df_pillars["HY_OAS"]) > 7,
  "liq": staleness_days(df_pillars["TEDRATE"]) > 21
}
df_pillars["FSI_Confidence"] = (
  "HIGH" if not any(stale.values())
  else "MED" if sum(stale.values()) == 1
  else "LOW"
)

If pillars are disabled, confidence defaults to HIGH so long as STLFSI4 updated on schedule.

3) Regime Classification

We combine absolute level and relative z-score to form weekly regimes:

  • Bearish (risk‑off): STLFSI4 > 0 or z > +0.5 → tightening / elevated stress
  • Bullish (risk‑on): STLFSI4 < 0 and z < −0.5 → loose conditions
  • Neutral: otherwise (including any NaNs)
def classify_stress(row, level_thresh=0.0, z_lo=-0.5, z_hi=0.5):
    lvl, z = row["STLFSI4"], row["Stress_z"]
    if np.isnan(lvl) or np.isnan(z):
        return "Neutral"
    if (lvl > level_thresh) or (z > z_hi):
        return "Bearish"
    if (lvl < level_thresh) and (z < z_lo):
        return "Bullish"
    return "Neutral"

df_stress["FinancialStress_Signal"] = df_stress.apply(classify_stress, axis=1)

Thresholds can be tuned based on backtests or risk appetite.

4) Outputs & Columns

# Core columns
["STLFSI4","Stress_z","FinancialStress_Signal",
 # Optional interpretability columns
 "VIXCLS","HY_OAS","TEDRATE","VIX_z","Credit_z","Liq_z","PillarStress_z","FSI_Confidence"]

Console prints echo the tail of the aligned series and the final signal view. No plots are generated.

5) Data Handling & Validation

6) Interpretation & Use (Model Interpretation)

6.1 What the headline level means

STLFSI4 is standardized around a long-run average. In practice, positive values indicate above-average financial stress/tightness, while negative values indicate below-average stress/looser conditions. Treat the level as a “headline” barometer of broad market stress rather than a single-market signal. (St. Louis Fed STLFSI4 documentation; see References.)

6.2 How to interpret the robust z-score

The robust z-score is a within-history extremeness measure. Use it to distinguish: (i) modest tightness/looseness (near 0) vs (ii) statistically unusual stress/ease (large magnitude). Because the z-score uses rolling median/MAD, it is less sensitive to crisis outliers than mean/std.

  • z > +0.5: above-normal stress relative to the recent multi-year distribution (watch for “risk-off” behaviour).
  • z < −0.5: below-normal stress / easy conditions (often compatible with “risk-on” positioning).
  • Near thresholds: expect whipsaw risk; prefer sustained confirmation (e.g., 2–4 weekly closes) if you use this for regime switching.

6.3 Pillars: interpreting drivers, not just the label (v1.1)

When enabled, pillar contributions provide a decomposition into volatility (risk aversion), credit (risk premium / intermediation capacity), and liquidity (funding stress). This aligns with the academic treatment of financial stress as a multi-dimensional phenomenon captured by co-movement across spreads, volatility, and liquidity measures.

  • Volatility-led stress: spikes in implied volatility can be sharp and mean-reverting; use for tactical risk management (VIX literature).
  • Credit-led stress: widening spreads often carry higher macro predictive content, especially via “excess bond premium” dynamics.
  • Liquidity-led stress: widening funding spreads can signal market-functioning risk; treat as high-severity when confirmed.

6.4 Confidence flag: how to use it

FSI_Confidence is a data-quality indicator, not a market call. If confidence is LOW, interpret pillar narratives cautiously and default to the headline STLFSI4 regime until components resume updating. This addresses real-time outage risk in VIX/credit-spread/funding-spread feeds.

6.5 Practical usage

  • Strategic: use sustained “Bearish” readings as a headwind to cyclical risk exposure, not as a timing tool in isolation.
  • Tactical: use rapid increases (level + z) and volatility-led surges for short-horizon risk controls.
  • Macro integration: interpret stress jointly with growth/inflation conditions; the literature links severe stress episodes to downturn risk, but not every stress episode produces a recession.

References (selected)

  1. St. Louis Fed / FRED: STLFSI4 series notes and description (method and component categories).
  2. St. Louis Fed FRED Blog (2022): “The St. Louis Fed’s Financial Stress Index, version 4”.
  3. Hakkio, C. S., & Keeton, W. R. (2009). “Financial stress: what is it, how can it be measured, and why does it matter?” Federal Reserve Bank of Kansas City, Economic Review.
  4. Cardarelli, R., Elekdag, S., & Lall, S. (2009/2010). “Financial Stress, Downturns, and Recoveries / Financial Stress and Economic Contractions.” IMF Working Paper / related journal version.
  5. Whaley, R. E. (2000). “The Investor Fear Gauge.” (VIX interpretation).
  6. Gilchrist, S., & Zakrajšek, E. (2012). “Credit Spreads and Business Cycle Fluctuations.” American Economic Review.