Energy‑Adjusted Metals Tailwind
Abstract
Copper prices embed both global demand impulses and cost/FX drivers. We regress copper on oil (energy input proxy) and the broad USD (pricing/FX headwind) to obtain a residual. A robust z‑score of that residual forms the Metals Tailwind index, mapped to Demand_Tailwind, Neutral, or Headwind regimes.
1. Data
This signal combines one copper price proxy with two macro controls (energy input costs and broad USD). All three series are aligned to a month-end calendar (resampled from daily) before estimation.
| Series | Provider | What it represents | Units / frequency |
|---|---|---|---|
| COPPER_YF_USD_LB Ticker: HG=F |
Yahoo Finance via yfinance |
COMEX copper futures price proxy (front-month). Used as a liquid, market-based “industrial metals” signal. | USD per lb (typical); daily, resampled to month-end |
| DCOILWTICO | FRED (EIA) | West Texas Intermediate crude oil spot price. Used as an energy input-cost proxy that co-moves with industrial cycles and cost pressures. | USD per barrel; daily, resampled to month-end |
| DTWEXBGS | FRED (Federal Reserve) | Broad trade-weighted U.S. dollar index (goods). Captures the global USD pricing/FX headwind that often co-moves inversely with USD-denominated commodity prices. | Index level; daily, resampled to month-end |
Data handling & quality notes
- Resampling: Daily observations are coerced to numeric and aligned to month-end. Prefer month-end last/close; if missing, use the last available business day in the month and record a staleness flag.
- Overlapping sample: Estimation uses only months where all three series are present (minimum 24 months).
- Revisions & restatements: FRED series may be revised or updated by the originating agency. Where feasible, keep a “last-observed” snapshot (time-stamped) to diagnose backfilled changes.
- Futures proxy caveats:
HG=Fis a front-month futures proxy; rolls, contract specs, and exchange holidays can create small discontinuities. Treat the copper series as a liquid price proxy, not a fundamental spot benchmark. - Unit comparability: Not required because the model converts residuals to a robust z-score (scale-invariant). If you need unit conversion elsewhere: USD/lb → USD/metric tonne ≈ × 2204.6226.
2. Model
Estimate a simple OLS of copper levels on oil and USD with an intercept, using all available overlapping observations (minimum 24 months). Compute the fitted value and residual:
Levels regression is chosen for simplicity and interpretability; differenced or log specifications are valid alternatives when stationarity is a concern.
3. Normalisation
4. Regime Mapping
4A. Model Interpretation
The signal is designed to separate “copper-specific” price strength from two common macro drivers: (1) energy input costs (oil) and (2) broad USD strength. The Copper_Residual is the portion of copper’s level not explained by these controls in the fitted regression, and Metals_Tailwind is a robust z-score of that residual.
- Positive tailwind (z > +0.75): copper is “rich” versus what would be expected from oil and USD. This often aligns with stronger underlying metals demand (industrial activity, inventory tightness, or supply constraints) than the controls alone would imply.
- Neutral (|z| ≤ 0.75): copper is broadly consistent with oil and USD; treat as low-conviction for the metals-demand residual.
- Headwind (z < −0.75): copper is “cheap” versus what would be expected from oil and USD, consistent with softer metals demand or adverse idiosyncratic copper dynamics.
How to use in a macro stack
- Direction: Use the regime label (Tailwind/Neutral/Headwind) as the primary state.
- Strength: Use the continuous Metals_Tailwind value to rank conviction across time or across comparable residual-style signals.
- Cross-check: When the tailwind is extreme, cross-check with other real-activity proxies (industrial production, shipping/steel activity, PMI/new orders) before making directional macro calls.
Key interpretation caveats
- Oil shocks are heterogeneous: oil price moves can reflect supply disruptions, global demand, or risk premia. The same oil move can carry different implications for copper (so residuals can spike). Prefer interpreting tailwind shifts alongside an “oil shock type” lens where possible.
- USD–commodity co-movement: broad USD strength is often negatively associated with USD-denominated commodity prices (pricing channel and global financial conditions). The USD control reduces, but does not eliminate, this common factor.
- Levels vs logs: the baseline uses levels for simplicity. If residuals appear non-stationary or heteroskedastic, consider log levels or first differences (keeping the robust z-score framework intact).
Academic & policy context (selected)
- Oil shocks and macro transmission: decomposing oil price changes into demand/supply components changes interpretation of downstream asset prices.
- USD and commodity pricing: the USD is a key common driver for globally priced commodities; USD moves affect non-US demand and local-currency prices.
- Commodity prices and global activity: measures of global real activity can help explain and forecast commodity prices; industrial metals are especially cyclical.
See references in Section 9.
5. Implementation (Python)
import pandas as pd
import numpy as np
def robust_z(s, win=60, min_win=24):
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_metals_adj.copy()
# Simple monthly OLS of copper on oil & USD (rolling residual level)
X = d[["DCOILWTICO","DTWEXBGS"]].copy()
X = X.assign(const=1.0)
y = d["COPPER_YF_USD_LB"]
mask = X.join(y).dropna().index
if len(mask) >= 24:
Xn = X.loc[mask, ["const","DCOILWTICO","DTWEXBGS"]].values
yn = y.loc[mask].values
beta = np.linalg.pinv(Xn.T @ Xn) @ (Xn.T @ yn)
d.loc[mask, "Copper_Fitted"] = (Xn @ beta)
d["Copper_Residual"] = d["COPPER_YF_USD_LB"] - d["Copper_Fitted"]
else:
d["Copper_Residual"] = np.nan
d["Metals_Tailwind"] = robust_z(d["Copper_Residual"])
hi, lo = 0.75, -0.75
def _regime(v):
if pd.isna(v): return np.nan
return "Demand_Tailwind" if v > hi else ("Headwind" if v < lo else "Neutral")
d["Metals_Regime"] = d["Metals_Tailwind"].apply(_regime)
df_sig_metals = d
display(df_sig_metals.tail())
If you previously used PCOPPUSDM, replace it with COPPER_YF_USD_LB throughout.
6. Visualisation
Plot copper vs fitted copper on the primary axis, and the residual z‑score on a secondary axis. For long‑horizon context, the standard implementation plots the last 30 years (where available).
- Primary axis: COPPER_YF_USD_LB vs Copper_Fitted
- Secondary axis: Metals_Tailwind with thresholds at
±0.75 - Optional regime shading using Metals_Regime
7. Notes & Extensions
- Specification: Consider logs and/or first differences to reduce heteroskedasticity; re‑estimate betas in a rolling window if time‑variation is expected.
- Broader metals basket: Adapt the approach to aluminum, nickel, or a composite LME index.
- FX controls: Replace broad USD with trade‑weighted EM FX for China/EM demand tilt.
- Alternative copper proxies: Substitute a spot or LME series by replacing the copper column while keeping the OLS + robust z framework intact.
8. Future Enhancements (Out of Scope)
- Volatility-aware filtering: Energy and metals prices are highly volatile and subject to geopolitical shocks. Complement the residual with volatility measures (e.g., rolling realised volatility of copper and oil; regime-dependent thresholds) to reduce noise-driven flips.
- Energy-adjusted real rates: Add an “energy-adjusted real rate” control (e.g., real yields interacted with oil/energy intensity proxies) to better capture the joint influence of financing conditions and input-cost stress on metals pricing.
- Time-varying betas: Estimate the copper~oil~USD relationship in a rolling or state-space framework to allow for structural changes (e.g., shifts in China intensity, supply constraints, or financialisation).
- Basket approach: Extend from a single copper proxy to a base-metals basket (aluminium, nickel, zinc) to reduce idiosyncratic copper supply effects.
9. References (selected)
- Kilian, L. (2009). Not All Oil Price Shocks Are Alike: Disentangling Demand and Supply Shocks in the Crude Oil Market. American Economic Review.
- Chen, Y.-C., Rogoff, K., & Rossi, B. (2010). Can Exchange Rates Forecast Commodity Prices? Quarterly Journal of Economics.
- Rees, D. M. (2023). Commodity prices and the US Dollar. BIS Working Papers.
- Ayres, J., & Nicolini, J. P. (2019). Real Exchange Rates and Primary Commodity Prices. RBA Research Workshop.
- Miranda-Pinto, J. (2023). Monetary Policy Transmission through Commodity Prices. Melbourne Institute.
- Hamilton, J. D. (2019). Measuring Global Economic Activity. NBER Working Paper.