← All posts

Which Commodities Have the Strongest Momentum? Rotation Backtest in Python

Backtest a top-two monthly momentum rotation across commodity ETFs against an equal-weight basket and DBC.

What's the question?

Commodity markets do not move as one asset class. Gold can rise while energy falls. Natural gas can crash while agricultural commodities remain stable. Base metals can behave like a growth signal, while precious metals behave like a defensive store of value.

Momentum is a direct way to test whether that rotation can be exploited. The rule is not based on a view about supply, inventories, central banks, or geopolitics. It asks whether the strongest recent commodity exposures continue to lead over the next month. An AI momentum leadership test uses a similar top-ranked monthly rotation on a basket of AI stocks.

The question is whether a simple momentum rotation across liquid commodity ETFs outperforms an equal-weight commodity basket and a broad commodity benchmark.

The approach

The universe is GLD, SLV, USO, UNG, DBA, DBB, and DBC. It covers gold, silver, oil, natural gas, agriculture, base metals, and broad commodities. Built from SEC EDGAR public filings and market data, the backtest uses five years of daily prices and daily returns.

  1. Pull five years of daily adjusted prices and daily returns
  2. Convert daily returns into complete monthly returns
  3. Rank ETFs each month by prior six-month price return
  4. Hold the top two ETFs equal-weighted for the next month
  5. Compare the strategy with an equal-weight basket and DBC

The momentum signal is shifted by one month. This avoids using the same month's return both to rank the ETFs and to measure the portfolio result.

Code

import xfinlink as xfl
import pandas as pd

xfl.set_api_key("YOUR_API_KEY")  # free at https://xfinlink.com/signup

tickers = ["GLD", "SLV", "USO", "UNG", "DBA", "DBB", "DBC"]
prices = xfl.prices(tickers, period="5y", fields=["adj_close", "return_daily"])

price_daily = prices.pivot_table(index="date", columns="ticker", values="adj_close").dropna()
return_daily = prices.pivot_table(index="date", columns="ticker", values="return_daily").dropna()

monthly_prices = price_daily.resample("ME").last()
monthly_returns = (1 + return_daily).resample("ME").prod() - 1

signal = monthly_prices.pct_change(6).shift(1)
dates = signal.dropna().index.intersection(monthly_returns.index)

strategy_returns = []
for dt in dates:
    leaders = signal.loc[dt].sort_values(ascending=False).head(2).index
    strategy_returns.append(monthly_returns.loc[dt, leaders].mean())

strategy = pd.Series(strategy_returns, index=dates)
equal_weight = monthly_returns.loc[dates, tickers].mean(axis=1)

print((1 + strategy).prod(), (1 + equal_weight).prod())

Full script with formatting and visualisation: commodity-momentum-rotation-python.py

Output

Commodity momentum rotation cumulative return compared with equal-weight and DBC benchmarks
=== Commodity Momentum Rotation Backtest ===
Universe: GLD, SLV, USO, UNG, DBA, DBB, DBC
Sample: 2022-01-31 to 2026-05-31 (53 monthly rebalances)
Signal: prior 6-month price return; portfolio: top 2 equal-weighted ETFs

Portfolio comparison:
Top-2 momentum          return=+20.3%  vol=29.5%  max_drawdown=-35.5%  positive_months=62.3%
Equal commodity basket  return=+13.5%  vol=15.4%  max_drawdown=-23.3%  positive_months=62.3%
DBC broad commodity     return=+10.9%  vol=15.9%  max_drawdown=-23.0%  positive_months=56.6%

Most frequent momentum leaders:
GLD  selected in 31 months
SLV  selected in 26 months
USO  selected in 19 months
UNG  selected in 13 months
DBA  selected in  9 months
DBC  selected in  5 months
DBB  selected in  3 months

Latest signal ranking:
USO  trailing_6m_return=+102.7%
SLV  trailing_6m_return=+51.5%
DBC  trailing_6m_return=+35.9%
GLD  trailing_6m_return=+15.1%
DBB  trailing_6m_return=+13.3%
DBA  trailing_6m_return= +6.3%
UNG  trailing_6m_return=-20.0%

What this tells us

Momentum improved returns in this commodity universe. The top-two strategy returned 20.3% annualized, compared with 13.5% for the equal-weight basket and 10.9% for DBC. The positive-month rate was also higher than DBC, at 62.3% versus 56.6%.

The improvement came with a clear cost. Volatility rose to 29.5%, almost double the equal-weight basket. Maximum drawdown also worsened to -35.5%. That is the signature of commodity momentum: it can catch strong trends, but it concentrates in the most volatile contracts when they are moving.

Gold and silver dominated the leader counts. GLD was selected in 31 months and SLV in 26 months. USO also appeared often, but natural gas was selected only 13 months despite its large swings. The latest signal shows the same concentration problem: USO had the strongest six-month return, but that does not make it low risk.

So what?

Commodity momentum is useful for identifying leadership, not for controlling risk by itself. A top-two ranking can improve return, but position sizing has to account for volatility, drawdown, and the tendency for energy ETFs to reverse sharply.

The practical implementation is to separate the signal from the allocation. Use six-month momentum to identify candidates, then cap position weights by volatility or expected shortfall. In commodities, the strongest trend can also be the largest source of portfolio damage.

Built with xfinlink — free financial data API for Python.