Credit Conditions

Composite signal of credit stress using high‑yield and BBB option‑adjusted spreads plus equity‑volatility (VIX). Outputs a smoothed index and discrete regime labels: Tightening, Neutral, Easing.

Abstract

We proxy credit conditions by combining spread‑level stress from corporate bond markets with equity‑implied volatility. Each input is robust‑normalised over a rolling window, averaged, and then exponentially smoothed to reduce transient spikes. Thresholds of ±0.75 on the final index map to tightening/easing regimes relevant for risk appetite and funding conditions.

1. Data

Resample inputs to a common frequency (month‑end recommended) before normalisation.

2. Normalisation

z\_{rob}(X)_t = \dfrac{X_t − \mathrm{median}(X)_{t,w}}{1.4826 · \mathrm{MAD}(X)_{t,w}}
with rolling window w = 36 months (minimum 18). Robust scaling reduces the impact of crisis outliers and level shifts.

3. Composite Construction

  1. Inputs: Z\_{HY}, Z\_{BBB}, Z\_{VIX}.
  2. Raw index: arithmetic mean of the three z‑scores.
  3. Smoothing: exponential moving average with span = 3 months.
Credit\_{Conditions,t} = \mathrm{EMA}_3\big(\tfrac{Z\_{HY,t} + Z\_{BBB,t} + Z\_{VIX,t}}{3}\big)

4. Regime Classification

C_t > 0.75 \Rightarrow \textit{Tightening};\quad |C_t| \le 0.75 \Rightarrow \textit{Neutral};\quad C_t < -0.75 \Rightarrow \textit{Easing}

5. Implementation (Python)

import pandas as pd
import numpy as np

def robust_z(s, win=36, min_win=18):
    x = pd.to_numeric(s, errors="coerce").astype(float)
    w = max(min_win, min(win, x.dropna().size))
    med = x.rolling(w, min_periods=min_win).median()
    mad = (x - med).abs().rolling(w, min_periods=min_win).median()
    return (x - med) / (1.4826 * mad.replace(0, np.nan))

def ema(s, span=3):
    return s.ewm(span=span, min_periods=1, adjust=False).mean()

c = df_credit_cond.copy()
c["Z_HY"]  = robust_z(c["HY_OAS"])
c["Z_BBB"] = robust_z(c["BBB_OAS"])
c["Z_VIX"] = robust_z(c["VIXCLS"])

c["Credit_Conditions_Raw"] = c[["Z_HY","Z_BBB","Z_VIX"]].mean(axis=1)
c["Credit_Conditions"] = ema(c["Credit_Conditions_Raw"], span=3)

hi, lo = 0.75, -0.75

def _regime(v):
    if pd.isna(v): return np.nan
    return "Tightening" if v > hi else ("Easing" if v < lo else "Neutral")

c["Credit_Regime"] = c["Credit_Conditions"].apply(_regime)

df_sig_credit = c

6. Interpretation

7. Assumptions & Limitations