Labor Market Turnover Differential
A reproducible, monthly signal that gauges labor‑market tightness by comparing worker‑initiated quits to employer‑initiated layoffs & discharges. Built from FRED JOLTS series using robust z‑scores and clear regime rules.
Abstract
We compute the monthly Turnover Differential as the quits rate minus the layoffs & discharges rate. Intuition: a rising gap (quits ≫ layoffs) reflects worker confidence and tight conditions; a shrinking or negative gap signals loosening. We standardise the differential with a robust rolling z‑score and map it, alongside the level, into categorical regimes (Bullish, Neutral, Bearish) for downstream macro & markets use.
1. Data (FRED Identifiers)
- JTSQUR: Quits Rate (%), total nonfarm
- JTSLDR: Layoffs & Discharges Rate (%), total nonfarm
Both are monthly. We coerce to a common monthly index at Month‑Start ("MS") and forward‑fill within month to handle publication alignment.
Data source overview (what each series measures)
- JTSQUR (Quits rate) — the share of employed workers who voluntarily separate from their job in a given month. Often interpreted as a revealed-preference measure of worker confidence and outside options (job-to-job transitions).
- JTSLDR (Layoffs & discharges rate) — the share of employed workers whose separations are employer-initiated (layoffs or discharges) in a given month. Often interpreted as a measure of firm-level retrenchment or demand weakness.
Release cadence & lag: JOLTS is released monthly with roughly a one‑month lag (e.g., January data released in early March). This means the signal is not a high-frequency “nowcast” input; it is best used as a slow-moving confirmation signal within the labor pillar.
Revisions & benchmarking: JOLTS estimates are revised as part of the BLS annual revision process (including seasonal-factor updates and benchmarking). Recent years of data can change materially, so historical backtests should treat the series as real-time noisy and revision-prone rather than fixed truth.
Optional auxiliary series (recommended for v1.1+ extensions):
- JTSJOL (Job openings level) and UNEMPLOY (Unemployment level) — can be combined into an unemployed-per-opening ratio (UNEMPLOY / JTSJOL) as a direct slack / tightness proxy.
- JTSHIR (Hires rate) — can be used to separate worker confidence (quits) from employer hiring appetite (hires), and to compute alternative turnover composites.
# Fetch & align (fredapi)
q = fred.get_series("JTSQUR").to_frame("QuitsRate")
l = fred.get_series("JTSLDR").to_frame("LayoffRate")
q.index = pd.to_datetime(q.index); l.index = pd.to_datetime(l.index)
turnover_raw_df = (
pd.concat([q, l], axis=1).sort_index()
.resample("MS").last().ffill().reset_index()
.rename(columns={"index": "Date"})
)
2. Signal Construction
The level differential (percentage points) is:
We then compute a robust z‑score using a rolling window with median and MAD, guarding against outliers and short histories.
Default window \(W=60\) months with a minimum of 24. If history is shorter, the window adapts: \(W = \max(24, \lfloor0.8\cdot N\rfloor)\).
def _robust_z(s, window=60, min_window=24):
s = pd.to_numeric(s, errors="coerce").astype(float)
n = s.notna().sum()
w = max(min_window, window if n >= window else int(n * 0.8) if n else min_window)
med = s.rolling(w, min_periods=min_window).median()
mad = (s - med).abs().rolling(w, min_periods=min_window).median()
return (s - med) / (mad.replace(0, np.nan) * 1.4826)
Methodology amendment (v1.1)
- Data latency: treat the signal as a lagged labor-tightness confirmation input; do not interpret short-term shifts as real-time turning points.
- Benchmarking revisions: maintain a small revision-aware “transition band” (or require sustained confirmation) to reduce false regime flips caused by later revisions.
- Emphasize quits: optionally up-weight quits relative to layoffs in the level composite to reflect that quits often carry more information about worker outside options than layoffs do about demand at the margin:
\( x^{pp}_t = lpha\, ext{QuitsRate}_t - ext{LayoffRate}_t \) with
α> 1 (e.g., 1.25–1.50) tested via backtest. - Add tightness ratio: incorporate unemployed per job opening (UNEMPLOY / JTSJOL). High values indicate slack; falling values indicate tightening. Use as a modifier or confirmation filter.
- Momentum via moving z-score: compute a rolling z-score on the 3–6 month change in the composite (and/or on the tightness ratio) to better capture turning momentum without over-reacting to one-month noise.
3. Regime Classification
We combine information from the level (pp) and the robust z‑score into three regimes. Neutral “bands” prevent over‑trading on noise:
- Level neutral band (pp): [-0.20, 0.50]
- Z neutral band: [-0.50, 0.50]
def _classify(row):
lvl, z = row["Turnover_Diff_pp"], row["Turnover_Diff_z"]
if pd.isna(lvl) or pd.isna(z): return "Neutral"
if (lvl < -0.20) or (z < -0.50): return "Bearish"
if (lvl > 0.50) and (z > 0.50): return "Bullish"
return "Neutral"
Thresholds are configurable via level_neutral_band and z_neutral_band.
4. Outputs & Metadata
# Core columns
["Date","QuitsRate","LayoffRate",
"Turnover_Diff_pp","Turnover_Diff_z","Turnover_Regime"]
# Attached metadata
attrs["series"] = {"quits": "JTSQUR", "layoffs": "JTSLDR"}
attrs["generated_at"] = datetime.utcnow().isoformat() + "Z"
attrs["rules"] = {
"level_neutral_band_pp": (-0.20, 0.50),
"z_neutral_band": (-0.50, 0.50),
"definition": "Bullish if quits ≫ layoffs; Bearish if layoffs ≥ quits and/or negative momentum."
}
For reporting, the last five years can be rendered as an HTML table for dashboards or emails.
5. Data Handling & Validation
- Frequency alignment: resample to Month‑Start and forward‑fill within month to ensure synchronous comparisons.
- Short‑history safety: adaptive robust z‑window prevents unstable scaling during early sample periods.
- NaN policy: coercion to numeric; z‑scores undefined where MAD = 0 are set to NaN and naturally mapped to Neutral.
- Preview hooks: print/display last rows of the raw and final data frames for quick inspection.
6. Model Interpretation (how to read the signal)
What the level means
- Turnover_Diff_pp > 0: quits exceed layoffs, consistent with a “tight” labor market where workers have outside options and firms are not broadly shedding labor.
- Turnover_Diff_pp near 0: tighter and looser forces are balanced; interpret as “late‑cycle” or “transitional” unless other labor indicators confirm.
- Turnover_Diff_pp < 0: layoffs exceed quits, consistent with a loosening labor market (often an early warning for rising unemployment risk).
What the robust z-score means
The robust z-score measures how unusual the current level is relative to its recent history (rolling median/MAD). It is best interpreted as extremeness, not “causality”:
- z > +0.5: tighter-than-normal vs history (worker confidence unusually strong and/or layoffs unusually low).
- z between −0.5 and +0.5: typical / noisy range; avoid over-interpreting.
- z < −0.5: looser-than-normal vs history (quits weak and/or layoffs elevated).
How to use it inside a macro model
- As a labor-tightness confirmation: treat “Bullish” as confirming labor strength (supportive for growth/consumption, wage pressure risk); treat “Bearish” as confirming deterioration (higher recession risk).
- Combine with vacancy/unemployment tightness: the unemployed-per-opening ratio (UNEMPLOY / JTSJOL) is a direct slack proxy linked to search-and-matching theory and Beveridge-curve dynamics. Use it to validate whether quits/layoffs are signaling the same tightness direction.
- Look for sustained shifts: because JOLTS is lagged and revised, prefer sustained moves (multi‑month) and cross-confirmation rather than reacting to one print.
Academic anchors (why these variables matter)
- Search & matching / Beveridge curve: tightness is often summarized by the vacancy–unemployment relationship; shifts can reflect changes in matching efficiency and flows.
- Worker flows (quits/layoffs): quits and layoffs are core separations margins; they help decompose labor-market dynamics beyond the unemployment rate.
- Tightness and wage growth: recent work emphasizes quits and vacancies-per-searcher as informative tightness measures for wage inflation.
Selected references
- Barlevy, G. (2024). “The Shifting Reasons for Beveridge Curve Shifts.” Journal of Economic Perspectives.
- Davis, S. J., Faberman, R. J., & Haltiwanger, J. (2011/2012). “Labor Market Flows in the Cross Section and Over Time.” (JOLTS-based worker flows).
- Domash, A., & Summers, L. H. (2022). “How Tight are U.S. Labor Markets?” (tightness indicators and u/v measures).
- Heise, S. et al. (NY Fed Staff Reports, 2024). “Wage Growth and Labor Market Tightness.” (quits and vacancies per effective searcher).
- BLS JOLTS Technical Note & annual revisions notices (data definitions, benchmarking, and revision windows).
7. Implementation Notes (Python)
# ➊ Fetch & harmonise monthly series (fredapi)
# ➋ Compute level differential in percentage points
# ➌ Apply robust rolling z-score with adaptive window
# ➍ Classify regimes with level & z neutral bands
# ➎ Persist metadata & (optional) HTML table for last 5y
8. Governance & Change Control
- Review thresholds and windows semi‑annually with backtests around turning points.
- Document rationale for any rule changes (e.g., neutral bands, window length).
- Version outputs and store data‑quality logs for auditability.