Index a contract price to a commodity price series

A contract that follows a commodity index needs two published prices and one division. Narwhal returns monthly physical price series with the unit, the currency and the period, so the adjustment between a base month and a reset month can be computed in code.

Read the index months

curl, copper, August and September
curl -s "https://api.narwhalapi.com/v1/commodities/physical/series/worldbank-cmo-copper/observations?start=2026-08-01&end=2026-10-01" \
  -H "Authorization: Bearer $NARWHAL_API_KEY"
Response
{
  "series_id": "worldbank-cmo-copper",
  "currency": "USD",
  "unit": "metric_ton",
  "unit_key": "tonne",
  "unit_quantity": "1",
  "attribution": "The World Bank: World Bank Commodity Price Data (The Pink Sheet), CC BY 4.0",
  "observations": [
    {
      "period_start": "2026-08-01",
      "period_end": "2026-08-31",
      "price": "14326",
      "revision": 1
    },
    {
      "period_start": "2026-09-01",
      "period_end": "2026-09-30",
      "price": "14474",
      "revision": 1
    }
  ],
  "next_cursor": null
}

Each observation is one month. period_start and period_end give the month, price is a decimal string, and revision is 1 for these rows. The unit and currency sit on the response: USD per metric_ton. start is inclusive and end is exclusive, and the two must be sent together, so end=2026-10-01 returns September and stops.

GET /v1/commodities/physical/series lists the series you can index to. It took no filter for the full list and returned 93 series. Narrow it with commodity or category, for example commodity=copper, and read the description before you pick one. For copper it says Grade A cathodes at an exchange settlement price.

Calculate the indexed price adjustment

A price clause usually names a base month, a reset month and a rule. The adjustment is the index at the reset month divided by the index at the base month. The new price is the contract price times that ratio. The contract price and the months are yours. The series supplies only the index.

  1. Fetch the months The script requests January to September 2026 and keys each price by the first seven characters of period_start.
  2. Divide the reset month by the base month With a base month of 2026-03 and a reset month of 2026-09, the ratio is 14474 over 12529, so the index change is 15.52 percent.
  3. Apply it to the contract price A contract price of 8000.00 USD per tonne becomes 9241.92 after the adjustment.
  4. Try an averaging rule Many clauses average several months. The script also averages June, July and August, 13807.00, against March. The change is 10.20 percent and the new price is 8816.03.
    Python, copper indexation
    import os
    import requests
    
    HEAD = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}
    r = requests.get(
        "https://api.narwhalapi.com/v1/commodities/physical/series/worldbank-cmo-copper/observations",
        params={"start": "2026-01-01", "end": "2026-10-01"},
        headers=HEAD,
        timeout=15,
    )
    r.raise_for_status()
    body = r.json()
    price = {o["period_start"][:7]: float(o["price"]) for o in body["observations"]}
    
    base_month, reset_month = "2026-03", "2026-09"
    contract_price = 8000.00  # your own contract price per tonne, in the series currency
    
    ratio = price[reset_month] / price[base_month]
    print(f"{body['currency']} per {body['unit']}: {price[base_month]:g} -> {price[reset_month]:g}")
    print(f"index change {100 * (ratio - 1):.2f}%  new contract price {contract_price * ratio:.2f}")
    
    # Variant: the average of the three months before the reset month
    window = ["2026-06", "2026-07", "2026-08"]
    average = sum(price[m] for m in window) / len(window)
    ratio3 = average / price[base_month]
    print(f"3-month average {average:.2f}  change {100 * (ratio3 - 1):.2f}%  "
          f"new contract price {contract_price * ratio3:.2f}")
RuleIndex value usedChange against 2026-03New price on 8000.00
Reset month, 2026-091447415.52%9241.92
Average of 2026-06 to 2026-0813807.0010.20%8816.03

The rule changes the answer by more than five percentage points, so write the rule into the contract and into the code in the same words.

Write the clause so the code can follow it

Code can apply a clause only when every term is a value. Settle these terms before you run anything against the data.

  • The series_id, for example worldbank-cmo-copper, so both sides read the same published series.
  • The base month and the reset month, each written as a month such as 2026-03.
  • The rule for the reset value: one month, or an average of named months.
  • The rounding of the ratio and of the new price, and the currency of the contract price.
  • Any floor, cap or pass-through share. These belong to the contract, and the API does not supply them. Apply them after the ratio, in your code, in the order the clause lists them.

Which months can I index to?

Only months the series has published. The series record for copper shows coverage_start 1960-01-01 and coverage_end 2026-09-30, so September 2026 was the last month available when this was written. A reset month after coverage_end has no price yet. Check for it in code and hold the adjustment instead of using the previous month in silence. Add a line to the log that says which month was missing and when the adjustment was applied, so the delay is visible to both parties.

Read /latest on the series for the newest observation alone, which is a quick way to learn whether the reset month has been published. Its response carries the same period_start, period_end, price and revision fields.

What should I store with the adjusted price?

Store the series_id, the two period_start values, both prices, the revision and the attribution text from the response. The attribution field names the publisher and the licence, and a contract file that cites an index should keep it.

Does the series price in my currency?

The copper series is in USD per metric ton. Index in USD and convert the final amount if the contract pays in another currency, or follow get a commodity price in your currency for converted quotes.

Can I index to a daily price instead?

The monthly series is the one used here. Daily spot prices live on a different family of routes, described on the spot commodities API. A monthly clause should use the monthly series, because mixing a daily price with a monthly base month changes what the ratio means.

Published .

Next steps