Commodity Spread Trading: Calendars, Cracks and Crush
Commodity spread trading isolates relative prices within the energy, ag, and metals complexes instead of betting on outright direction. A calendar spread trades two expiries of the same commodity; a crack spread trades crude vs refined products; a crush spread trades soybeans vs meal and oil. These are among the oldest systematic relative-value trades in futures — and they still work when the economics of storage, refining, and processing create temporary dislocations. This article covers the main structures and how to risk-manage them.
Why spreads instead of outright
Outright commodity futures embed:
- Spot price view
- Roll yield / curve shape
- Macro risk (USD, rates, growth)
Spreads cancel much of the common factor and leave structural economics:
| Spread | Legs | Economic story |
|---|---|---|
| Calendar | M1 vs M6 same commodity | Storage, seasonality, inventory |
| Crack | Crude vs gasoline / heating oil | Refining margin |
| Crush | Soybeans vs meal + oil | Processing margin |
| Spark | Natural gas vs power | Generation margin |
| Locational | Same product, different delivery | Transport / bottleneck |
Calendar spreads
calendar = F_near - F_far
In contango, far > near; in backwardation, near > far. Calendar P&L comes from curve reshaping, not from spot level (approximately).
Drivers:
- Inventory builds → curve toward contango
- Seasonal demand (heating oil winter, gasoline summer)
- Delivery constraints into expiry
def calendar_pnl(near_entry, far_entry, near_exit, far_exit,
qty_near=1.0, qty_far=1.0) -> float:
"""Long near / short far calendar P&L."""
return qty_near * (near_exit - near_entry) - qty_far * (far_exit - far_entry)
Crack spreads
Refineries buy crude and sell gasoline and distillates. The 3:2:1 crack is the standard paper proxy:
crack_321 = 2 × gasoline + 1 × heating_oil - 3 × crude (per barrel units)
When the crack is historically wide, the market is pricing rich refining margins — possible mean-reversion if capacity can respond. When crushed, refineries cut runs and products can tighten.
Caveats for quants:
- Paper crack ≠ physical refining (location, quality, operational costs)
- Seasonal patterns are strong but crowded
- Margin and contract specs differ across legs — normalize to barrel-equivalent carefully
def crack_321(gasoline: float, heat: float, crude: float) -> float:
"""Gasoline and heat in $/gal; crude in $/bbl. Convert gal→bbl (*42)."""
return 2 * gasoline * 42 + heat * 42 - 3 * crude
Crush spreads
Soybean processors buy beans, sell meal and oil:
crush = meal_value + oil_value - bean_cost
Use exchange-standard conversion factors (bushels, tons, pounds) — wrong units are the #1 bug in ag spread backtests. Crush embeds:
- Processing capacity utilization
- Export demand for meal/oil
- Weather / crop reports (seasonality)
Risk that looks small until it is not
Spreads reduce beta to spot but concentrate idiosyncratic risk:
- Contract specs — wrong multiplier → fake P&L
- Expiry — near-leg squeezes into delivery
- Correlation breakdown — crack can move with crude in a crisis
- Liquidity — back months and products have wider markets
- Margin — exchange spread margin helps, but stress can spike requirements
Size by historical spread volatility and worst move, not by outright commodity vol. Use stress testing with "curve inversion" and "refinery outage" scenarios.
Systematic approaches
Mean-reversion on z-scored spreads:
import pandas as pd
def spread_z(spread: pd.Series, window: int = 60) -> pd.Series:
mu = spread.rolling(window).mean()
sd = spread.rolling(window).std()
return (spread - mu) / sd
# Enter when |z| > 2, exit toward 0; filter with inventory / crack seasonality
Carry overlay: prefer calendars that also earn positive roll on the held structure (futures curve).
Inventory conditionals: EIA / USDA prints change the fair value of calendars overnight — event risk, not continuous alpha.
Backtest hygiene
- Continuous contracts with explicit roll on each leg
- Same timezone for energy inventory releases
- Unit conversion tests (gal ↔ bbl, bu ↔ ton)
- Bid-ask on thin product months
- No lookahead on USDA/EIA (timestamp the print, not the day)
Event-driven engines handle multi-leg rolls more honestly than vectorized spliced series.
Key takeaways
- Commodity spreads trade structural economics, not just price direction
- Calendars, cracks, and crush are the core toolkit — get units right
- Spot beta falls; idiosyncratic and event risk rise
- Size on spread vol and stress scenarios, not outright vol
- Inventory and seasonal calendars are features — and sources of crowding
