Capex Intent
Abstract
The Capex Intent signal infers forward business investment trends from durable goods order data. It combines year‑over‑year and short‑term (3‑month annualised) growth rates for total and ex‑transport durable orders. When the ex‑transport series (ADXTNO) is present, it is prioritised. The composite detects Expansion, Neutral, or Contraction phases in capital expenditure cycles.
1. Data
- DGORDER — Manufacturers’ New Orders: Durable Goods (FRED).
- ADXTNO — Manufacturers’ New Orders: Durable Goods Ex‑Transportation (FRED, preferred).
- DGEXORD — Legacy alternate ex‑transport series (fallback).
Monthly nominal series from the U.S. Census Bureau’s M3 report (via FRED). Level data in USD millions.
2. Transformations
- YoY Growth: g_{12} = X_t / X_{t−12} − 1
- 3‑Month Annualised Growth: g_{3mAnn} = (X_t / X_{t−3})^4 − 1
The 3‑month annualised rate accelerates detection of inflection points in investment cycles.
3. Normalisation
4. Composite Construction
Weights adjust automatically depending on data availability:
# If ADXTNO (ex‑transport) is available:
Capex_Intent = 0.4·z(ADXTNO_YoY) + 0.4·z(ADXTNO_3mAnn) + 0.2·z(DGORDER_YoY)
# Otherwise:
Capex_Intent = 0.5·z(DGORDER_YoY) + 0.5·z(DGORDER_3mAnn)
This adaptive weighting ensures emphasis on core durable orders when possible, while retaining comparability across datasets.
5. Regime Classification
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_durables.copy()
ex_col = None
if "ADXTNO" in d.columns:
ex_col = "ADXTNO"
elif "DGEXORD" in d.columns:
ex_col = "DGEXORD"
def ann3(series):
return (series / series.shift(3))**4 - 1.0
target_cols = ["DGORDER"] + ([ex_col] if ex_col else [])
for col in target_cols:
d[f"{col}_YoY"] = d[col].pct_change(12)
d[f"{col}_3mAnn"] = ann3(d[col])
if ex_col:
d["Z_EX_YoY"] = robust_z(d[f"{ex_col}_YoY"])
d["Z_EX_3mAnn"] = robust_z(d[f"{ex_col}_3mAnn"])
d["Z_ALL_YoY"] = robust_z(d["DGORDER_YoY"])
d["Capex_Intent"] = (0.4*d["Z_EX_YoY"] + 0.4*d["Z_EX_3mAnn"] + 0.2*d["Z_ALL_YoY"])
d["Capex_Sources"] = f"DGORDER + {ex_col}"
else:
d["Z_ALL_YoY"] = robust_z(d["DGORDER_YoY"])
d["Z_ALL_3mAnn"] = robust_z(d["DGORDER_3mAnn"])
d["Capex_Intent"] = 0.5*d["Z_ALL_YoY"] + 0.5*d["Z_ALL_3mAnn"]
d["Capex_Sources"] = "DGORDER only"
hi, lo = 0.75, -0.75
def _regime(v):
if pd.isna(v): return np.nan
return "Expansion" if v > hi else ("Contraction" if v < lo else "Neutral")
d["Capex_Regime"] = d["Capex_Intent"].apply(_regime)
df_sig_capex = d
print(f"✅ Capex Intent built using: {d['Capex_Sources'].iloc[-1]}")
display(df_sig_capex.tail())
7. Interpretation
- Expansion: rapid durable‑goods order growth — indicative of rising capex commitments.
- Neutral: stable order momentum consistent with baseline investment pace.
- Contraction: weakening orders — early signal of softening business investment.
8. Limitations
- Durable goods data are volatile and subject to large revisions; smoothing windows mitigate but do not remove noise.
- Transportation equipment orders can distort headline DGORDER; hence preference for ADXTNO when available.
- Fixed thresholds (±0.75) are heuristic; dynamic scaling by volatility regime may improve detection.