Housing Lead
Composite leading indicator for the housing cycle using permits, housing starts momentum, and mortgage‑rate headwinds.
Abstract
The Housing Lead signal blends forward indicators of residential activity (building permits and housing starts) with the financing constraint captured by the 30‑year mortgage rate. Momentum in permits (6‑month annualised) and starts (3‑month annualised) are positively related to future construction, while higher mortgage rates are a drag. Robust z‑scores and fixed weights produce a stable, interpretable composite with regimes: Expanding, Neutral, and Contracting.
1. Data
- PERMIT — Building Permits: New Private Housing Units (FRED:
PERMIT), monthly, SAAR. - HOUST — Housing Starts: Total: New Privately Owned (FRED:
HOUST), monthly, SAAR. - MORTGAGE30US — 30‑Year Fixed Rate Mortgage Average (FRED:
MORTGAGE30US), weekly average; resampled to month‑end.
Ensure consistent frequency (month‑end) before constructing the composite.
2. Transformations
- Permits momentum: six‑month annualised growth
PERMIT\_{6mAnn,t} = (PERMIT_t / PERMIT_{t−6})^2 − 1
- Starts momentum: three‑month annualised growth
HOUST\_{3mAnn,t} = (HOUST_t / HOUST_{t−3})^4 − 1
- Mortgage headwind: use the level of MORTGAGE30US; higher rates are negative for activity.
3. Normalisation
z\_{rob}(X)_t = \dfrac{X_t − \mathrm{median}(X)_{t,w}}{1.4826·\mathrm{MAD}(X)_{t,w}}
with rolling window w = 48 months (minimum 18). Robust to outliers and structural level shifts.
4. Composite Construction
Weighting emphasises forward permit signals, then starts, with a smaller penalty from mortgage rates:
Housing\_{Lead,t} = 0.45·z(PERMIT\_{6mAnn,t}) + 0.35·z(HOUST\_{3mAnn,t}) + 0.20·z(−MORTGAGE30US_t)
5. Regime Mapping
If\ C_t > 0.75 → \textit{Expanding};\quad |C_t| ≤ 0.75 → \textit{Neutral};\quad C_t < −0.75 → \textit{Contracting}
6. Implementation (Python)
import pandas as pd
import numpy as np
def robust_z(s, win=48, 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_housing.copy()
d["PERMIT_6mAnn"] = (d["PERMIT"] / d["PERMIT"].shift(6))**2 - 1.0
d["HOUST_3mAnn"] = (d["HOUST"] / d["HOUST"].shift(3))**4 - 1.0
d["Z_PERMIT"] = robust_z(d["PERMIT_6mAnn"])
d["Z_HOUST"] = robust_z(d["HOUST_3mAnn"])
d["Z_MORT"] = robust_z(-d["MORTGAGE30US"])
d["Housing_Lead"] = (0.45*d["Z_PERMIT"] + 0.35*d["Z_HOUST"] + 0.20*d["Z_MORT"])
hi, lo = 0.75, -0.75
def _regime(v):
if pd.isna(v): return np.nan
return "Expanding" if v > hi else ("Contracting" if v < lo else "Neutral")
d["Housing_Regime"] = d["Housing_Lead"].apply(_regime)
df_sig_housing = d
display(df_sig_housing.tail())
7. Interpretation
- Expanding: permitting and starts momentum outweigh mortgage headwinds; pipeline growth likely.
- Neutral: mixed signals; housing activity near trend.
- Contracting: weak permits/starts and/or high mortgage rates point to housing slowdown.
8. Limitations
- PERMIT and HOUST are volatile and subject to revisions; use multi‑month windows to reduce noise.
- MORTGAGE30US is weekly and may require careful resampling (e.g., month‑end last).
- Fixed thresholds (±0.75) are heuristic; calibration may be warranted for portfolio applications.