Capex Intent

Composite signal capturing capital‑expenditure momentum through durable goods orders, including ex‑transportation when available.

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

Monthly nominal series from the U.S. Census Bureau’s M3 report (via FRED). Level data in USD millions.

2. Transformations

  1. YoY Growth: g_{12} = X_t / X_{t−12} − 1
  2. 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

z_{rob}(X)_t = \dfrac{X_t − median(X)_{t,w}}{1.4826·MAD(X)_{t,w}}
Rolling window w = 48 months (minimum 18). Median absolute deviation scaling reduces distortion from outliers.

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

If\ C_t > 0.75 → Expansion;\quad |C_t| ≤ 0.75 → Neutral;\quad C_t < −0.75 → Contraction

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

This signal is designed to read like a capex leading indicator based on durable goods order momentum. It is best interpreted as a directional signal (improving vs deteriorating investment intent), not as a direct measure of realised business fixed investment.

7.1 Model Interpretation (how to read the signal)

What the levels mean

What the robust z-score means (extremeness, not causality)

The robust z-score compares the current growth rate to a rolling median and MAD window (48 months, minimum 18). It measures unusualness relative to recent history and is less sensitive to outliers than mean/standard-deviation z-scores.

How to read YoY vs 3‑month annualised growth

How to interpret ex‑transport vs headline

7.2 How to use it inside a macro model

7.3 Academic anchors (why these variables matter)

7.4 Selected references (starter list)



8. Future Enhancements (Out of Scope)

  • Volatility-aware thresholds: adapt ±0.75 bands based on rolling dispersion of the composite to reduce false signals in high-vol regimes.
  • Real (price-adjusted) capex intent: deflate orders using appropriate producer-price indices to better approximate volume dynamics.
  • Bayesian / state-space smoothing: combine headline and ex-transport in a latent-factor model to reduce noise and handle revisions.
  • Sectoral decomposition: separate defense/aircraft/IT equipment to isolate cyclically sensitive components.

9. 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.