Labour Market Momentum

Composite signal tracking labour market strength via jobless claims and payroll momentum.

Abstract

The Labour Market Momentum signal measures shifts in employment strength by combining jobless claims trends with nonfarm payroll growth. Weekly claims are inverted (higher claims = weaker momentum), while payroll growth is directly positive. The composite reveals cycles of Tightening, Neutral, and Softening labour dynamics.

1. Data

ICSA and CCSA are resampled to monthly frequency to align with PAYEMS.

2. Transformations

  1. Compute year‑on‑year (YoY) percent change for all three series.
  2. For PAYEMS, also compute the 3‑month difference to capture short‑term hiring momentum:
PAYEMS\_{3mDiff,t} = PAYEMS_t − PAYEMS_{t−3}

This highlights inflection points in employment growth.

3. Normalisation

z\_{rob}(X)_t = \dfrac{X_t − \mathrm{median}(X)_{t,w}}{1.4826·\mathrm{MAD}(X)_{t,w}}
Rolling window w = 36 months, minimum 18. Median‑based z‑scaling mitigates noise from data revisions.

4. Composite Construction

Jobless claims are inverted (rising claims = negative signal) before combining with payroll momentum:

Labor\_{Momentum,t} = \tfrac{1}{3}\big(z(-ICSA\_{YoY,t}) + z(-CCSA\_{YoY,t}) + z(PAYEMS\_{3mDiff,t})\big)

5. Regime Mapping

If\ C_t > 0.75 → \textit{Tightening};\quad |C_t| ≤ 0.75 → \textit{Neutral};\quad C_t < −0.75 → \textit{Softening}

6. 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))

d = df_labor.copy()

for col in ["ICSA","CCSA","PAYEMS"]:
    d[f"{col}_YoY"] = d[col].pct_change(12)

d["PAYEMS_3mDiff"] = d["PAYEMS"].diff(3)

d["Z_ICSA"]  = robust_z(-d["ICSA_YoY"])
d["Z_CCSA"]  = robust_z(-d["CCSA_YoY"])
d["Z_PAYEMS"] = robust_z(d["PAYEMS_3mDiff"])

d["Labor_Momentum"] = d[["Z_ICSA","Z_CCSA","Z_PAYEMS"]].mean(axis=1)

hi, lo = 0.75, -0.75

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

d["Labor_Regime"] = d["Labor_Momentum"].apply(_regime)

df_sig_labor = d
display(df_sig_labor.tail())

7. Interpretation

8. Limitations