Commodity Markets and Futures › Analyzing Futures Term Structure

Michael BrenndoerferNovember 12, 20256 min read
Quantitative FinanceAll chapters
Part 3 of 15Forwards, Futures, and Options
Chapter 16Commodity Markets and Futures › Analyzing Futures Term Structure
Reading section 7 of 12 · Analyzing Futures Term Structure · 6 minSection 7 of 12 · 6 min

Book contents

Section 7 of 12 · 6 min read

Commodity Markets and Futures › Analyzing Futures Term StructureOne section · Sections in this chapter7 / 12 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's build tools to analyze commodity futures term structures and visualize contango and backwardation conditions.

In[4]:
Code
import numpy as np

# Set random seed for reproducibility
np.random.seed(42)

We'll create a function to generate theoretical futures curves under different market conditions:

In[5]:
Code
def theoretical_futures_price(
    spot, time_to_expiry, risk_free_rate, storage_cost, convenience_yield
):
    """
    Calculate theoretical futures price using cost of carry model.

    Parameters:
    - spot: Current spot price
    - time_to_expiry: Time to delivery in years
    - risk_free_rate: Annualized risk-free rate
    - storage_cost: Annualized storage cost rate
    - convenience_yield: Annualized convenience yield rate

    Returns:
    - Theoretical futures price
    """
    net_cost = risk_free_rate + storage_cost - convenience_yield
    return spot * np.exp(net_cost * time_to_expiry)
In[6]:
Code
# Define time horizons (months to expiry)
months = np.array([1, 2, 3, 6, 9, 12, 18, 24])
times = months / 12  # Convert to years

# Current spot price
spot_price = 80  # e.g., dollars per barrel for crude oil

# Market parameters
risk_free_rate = 0.05  # 5% annual
storage_cost = 0.03  # 3% annual storage cost

# Scenario 1: Contango (low convenience yield)
low_convenience_yield = 0.02
contango_prices = [
    theoretical_futures_price(
        spot_price, t, risk_free_rate, storage_cost, low_convenience_yield
    )
    for t in times
]

# Scenario 2: Backwardation (high convenience yield)
high_convenience_yield = 0.15
backwardation_prices = [
    theoretical_futures_price(
        spot_price, t, risk_free_rate, storage_cost, high_convenience_yield
    )
    for t in times
]

# Scenario 3: Flat curve (convenience yield equals net carry cost)
balanced_convenience_yield = risk_free_rate + storage_cost
flat_prices = [
    theoretical_futures_price(
        spot_price, t, risk_free_rate, storage_cost, balanced_convenience_yield
    )
    for t in times
]

Now let's visualize the three term structure scenarios:

Out[7]:
Visualization
Line chart comparing contango, backwardation, and flat futures term structures over 24 months.
Commodity futures term structures under different market conditions. Contango (blue) shows upward-sloping prices when convenience yield is low. Backwardation (red) shows downward-sloping prices when convenience yield exceeds carrying costs. A flat curve (green) occurs when convenience yield exactly offsets net carry costs.
Out[8]:
Visualization
Grouped bar chart showing basis values for contango (blue, negative) and backwardation (red, positive) across contract maturities from 1 to 24 months.
Basis (spot minus futures) across different maturities for contango and backwardation market conditions. Negative basis indicates contango, while positive basis indicates backwardation. The magnitude of basis increases with time to expiry as carrying costs or convenience yield benefits accumulate.
In[9]:
Code
# Select a month for analysis
target_idx = 3
target_month = months[target_idx]

# Calculate basis for this horizon
basis_contango = spot_price - contango_prices[target_idx]
basis_backwardation = spot_price - backwardation_prices[target_idx]
Out[10]:
Console
Basis Analysis (Spot - Futures):
--------------------------------------------------
Spot Price: $80.00

Contango (6-month):
  Futures: $82.44
  Basis: $-2.44 (negative = contango)

Backwardation (6-month):
  Futures: $77.25
  Basis: $2.75 (positive = backwardation)

The basis values confirm our term structure shapes. In contango, the negative basis reflects futures prices above spot. In backwardation, the positive basis shows spot prices exceeding futures.

Calculating Roll Yield

Roll yield is the return generated when rolling futures positions forward. Let's calculate the annualized roll yield for different term structure conditions:

In[11]:
Code
def calculate_roll_yield(front_price, back_price, days_between):
    """
    Calculate annualized roll yield from calendar spread.

    Parameters:
    - front_price: Near-term futures price
    - back_price: Far-term futures price
    - days_between: Days between contract expirations

    Returns:
    - Annualized roll yield as a percentage
    """
    spread_return = (front_price - back_price) / front_price
    annualized = spread_return * (365 / days_between)
    return annualized * 100  # Return as percentage
In[12]:
Code
# Calculate roll yields for 1-month to 2-month roll
days_between = 30

contango_roll = calculate_roll_yield(
    contango_prices[0], contango_prices[1], days_between
)
backwardation_roll = calculate_roll_yield(
    backwardation_prices[0], backwardation_prices[1], days_between
)
flat_roll = calculate_roll_yield(flat_prices[0], flat_prices[1], days_between)
Out[13]:
Console
Annualized Roll Yield Analysis:
--------------------------------------------------
Contango:      -6.10%
Backwardation: +7.08%
Flat:          +0.00%

Negative roll yield = cost of maintaining long futures position
Positive roll yield = benefit of maintaining long futures position
Out[14]:
Visualization
Grouped bar chart comparing annualized roll yields for contango (blue, negative) and backwardation (red, positive) across consecutive contract roll periods.
Annualized roll yields across consecutive contract months for different market conditions. In contango, roll yields are negative because near-term contracts are cheaper than far-term contracts. In backwardation, roll yields are positive, and gives an additional return component for long futures positions.

The roll yield calculations highlight why commodity investors care deeply about term structure. In persistent contango, continuously rolling long futures positions erodes returns. In persistent backwardation, roll yield adds to returns.