Manufacturers’ New Durable Goods Orders (Ex‑Transportation)
A reproducible, monthly signal derived from FRED series ADXTNO that tracks momentum in core U.S. manufacturing orders by excluding transportation volatility. We use year‑over‑year and 3‑month changes, an optional robust z‑score, and simple regime rules.
Abstract
We construct a monthly Durable Goods ex‑Transportation signal from the FRED series ADXTNO. The series is aligned to month‑end, with within‑month forward‑fill to ensure a complete monthly time series. We compute year‑over‑year and 3‑month percentage changes, optionally standardise YoY with a robust rolling z‑score, and classify regimes (Bullish, Neutral, Bearish) using transparent thresholds.
1. Data (FRED Identifier)
- ADXTNO: Manufacturers’ New Orders: Durable Goods Excluding Transportation
We work at a month‑end frequency ("M") and forward‑fill within month to align release dates.
# Fetch & align (fredapi)
durables = fred.get_series("ADXTNO")
df_durables = pd.DataFrame(durables, columns=["DurableGoods_Ex_Trans"])
df_durables = df_durables.resample("M").last().ffill()
2. Signal Construction
We compute momentum using both long‑run (YoY) and near‑term (3M) percentage changes. With \(X_t\) as the level:
df_durables["YoY_pct"] = df_durables["DurableGoods_Ex_Trans"].pct_change(12) * 100
df_durables["3M_pct"] = df_durables["DurableGoods_Ex_Trans"].pct_change(3) * 100
Optionally, we compute a robust z‑score on YoY to stabilise scaling against outliers:
Default rolling window \(W=60\) months; observations with \(\text{MAD}=0\) yield undefined z and are left as NaN.
def robust_z(series, window=60):
med = series.rolling(window).median()
mad = (series - med).abs().rolling(window).median()
return (series - med) / (1.4826 * mad.replace(0, np.nan))
df_durables["YoY_z"] = robust_z(df_durables["YoY_pct"], window=60)
3. Regime Classification
We map momentum into categorical regimes using transparent rules. Neutral bands avoid whipsaw:
- Bullish: YoY > +5% or 3M > +1%
- Bearish: YoY < −3% or 3M < −1%
- Neutral: otherwise
def classify_durables(row, yoy_hi=5, yoy_lo=-3, m3_hi=1, m3_lo=-1):
yoy, m3 = row["YoY_pct"], row["3M_pct"]
if np.isnan(yoy) or np.isnan(m3):
return "Neutral"
if (yoy > yoy_hi) or (m3 > m3_hi):
return "Bullish"
if (yoy < yoy_lo) or (m3 < m3_lo):
return "Bearish"
return "Neutral"
df_durables["DurableGoods_Signal"] = df_durables.apply(classify_durables, axis=1)
Thresholds can be tuned (e.g., based on backtests or sector sensitivity).
4. Outputs & Columns
# Core columns created
["DurableGoods_Ex_Trans","YoY_pct","3M_pct","YoY_z(optional)","DurableGoods_Signal"]
Console prints provide quick verification (tail of raw and final frames).
5. Data Handling & Validation
- Frequency alignment: resample to month‑end (
"M"),.last()for end‑of‑month value, then.ffill()to fill gaps. - NaN policy: percentage changes produce NaNs for initial lags; classification defaults to Neutral when YoY/3M are NaN.
- Robust scaling: optional YoY z‑score uses rolling median/MAD; where MAD=0, z is NaN rather than inf.
6. Interpretation & Use Cases
ADXTNO strips transportation to better capture core order momentum. Bullish regimes suggest firming manufacturing demand, supportive of cyclical risk appetite and upstream commodities; Bearish regimes indicate softening order books and potential growth downgrades. Combine with broader macro pillars (growth, inflation, financial conditions) for multi‑signal frameworks.
7. Governance & Change Control
- Re‑evaluate thresholds (+5/−3 YoY, +1/−1 3M) semi‑annually with out‑of‑sample tests.
- Version methodology changes and retain data‑quality logs.
- Document any deviations from the default window length or classification rules.