Futures

Futures streams provide price data for exchange-traded futures contracts. These streams enable onchain protocols to build markets around futures price exposure. Coverage currently includes commodities — energy and precious metals — described below.

Because futures contracts expire, Data Streams does not publish one feed per contract. Instead, each instrument is represented by three generic feeds that always track the current front, second, and third contract. This is explained in Rotation methodology below.

Developers are responsible for choosing the appropriate feed and ensuring that the operation and performance of their choice matches expectations. For more information, see the Developer Responsibilities guidance.

Schema

Futures streams use the Futures (v14) schema.

The v14 schema includes consensus mid, bid, and ask prices, contract expiration and first day of notice, the tracked contract month, market status, and a staleness measure. It also includes continuous roll price fields — goldmanRollPrice, interpolatedGoldmanRollPrice, and currentBusinessDay — described below.

Continuous roll price fields

In addition to the raw generic feed prices above — which switch entirely to the next contract at the first notice date — Chainlink also publishes continuous price series built using alternative rolling methodologies, for integrators who want a smoothed price instead of a hard cutover:

FieldDescription
goldmanRollPriceUses the Goldman Roll (GSCI methodology): shifts 20% of the position to the next contract each business day, over business days 5–9 of the month preceding delivery
interpolatedGoldmanRollPriceInterpolated Goldman Roll price. Reported as 0 when the feed is not within an active roll window
currentBusinessDayCurrent business day count within the active roll window, so integrators can implement the Goldman Roll methodology themselves

For the complete field list and definitions, see the Futures (v14) schema.

Implementation

Futures streams are consumed like any other Data Streams report: you fetch the latest report for a feed ID and decode it with the SDK. The only difference is the report schema version, which is v14 for futures streams.

The examples below fetch and decode a live NY-UKOIL/USD futures report from the testnet API. Each SDK auto-detects or explicitly targets the v14 schema.

// FuturesFetch.go — Fetch and decode a Futures (v14) report with the Go SDK.
//
// Usage:
//   export API_KEY="..."
//   export API_SECRET="..."
//   go run FuturesFetch.go 0x000ebfe7b7560e1866ef91d4607ce5ee7474382794e5ec755f38b70b1f30e647
package main

import (
	"context"
	"fmt"
	"os"
	"time"

	streams "github.com/smartcontractkit/data-streams-sdk/go/v2"
	feed "github.com/smartcontractkit/data-streams-sdk/go/v2/feed"
	report "github.com/smartcontractkit/data-streams-sdk/go/v2/report"
	v14 "github.com/smartcontractkit/data-streams-sdk/go/v2/report/v14"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "Usage: go run FuturesFetch.go [FeedID]\n")
		os.Exit(1)
	}
	feedIDInput := os.Args[1]

	apiKey := os.Getenv("API_KEY")
	apiSecret := os.Getenv("API_SECRET")
	if apiKey == "" || apiSecret == "" {
		fmt.Fprintf(os.Stderr, "API_KEY and API_SECRET environment variables must be set\n")
		os.Exit(1)
	}

	cfg := streams.Config{
		ApiKey:    apiKey,
		ApiSecret: apiSecret,
		RestURL:   "https://api.testnet-dataengine.chain.link",
		Logger:    streams.LogPrintf,
	}

	client, err := streams.New(cfg)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
		os.Exit(1)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	var feedID feed.ID
	if err := feedID.FromString(feedIDInput); err != nil {
		fmt.Fprintf(os.Stderr, "Invalid feed ID format '%s': %v\n", feedIDInput, err)
		os.Exit(1)
	}

	reportResponse, err := client.GetLatestReport(ctx, feedID)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to get latest report: %v\n", err)
		os.Exit(1)
	}

	// Decode the v14 (Futures) report
	decodedReport, err := report.Decode[v14.Data](reportResponse.FullReport)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to decode report: %v\n", err)
		os.Exit(1)
	}

	d := decodedReport.Data

	fmt.Printf("\nDecoded V14 Report for Stream ID %s:\n", feedIDInput)
	fmt.Println("------------------------------------------")
	fmt.Printf("Mid Price             : %s\n", d.MidPrice.String())
	fmt.Printf("Bid Price             : %s\n", d.BidPrice.String())
	fmt.Printf("Ask Price             : %s\n", d.AskPrice.String())
	fmt.Printf("Expiry Time           : %d\n", d.ExpiryTime.Unix())
	fmt.Printf("First Day of Notice   : %d\n", d.FirstDayOfNotice.Unix())
	fmt.Printf("Last Seen TimestampNs : %d\n", d.LastSeenTimestampNs.UnixNano())
	fmt.Printf("Market Status         : %d\n", d.MarketStatus)
	fmt.Printf("Contract Month        : %d\n", d.ContractMonth)
	fmt.Printf("Goldman Roll Price    : %s\n", d.GoldmanRollPrice.String())
	fmt.Printf("Current Business Day  : %d\n", d.CurrentBusinessDay)
	fmt.Printf("Interp Goldman Roll   : %s\n", d.InterpolatedGoldmanRollPrice.String())
	fmt.Println("------------------------------------------")
}

Expected output

Running any of the examples above against a live NY-UKOIL/USD futures feed produces a decoded report similar to the following (values are illustrative and change with each report):

Decoded V14 Report for Stream ID 0x000ebfe7b7560e1866ef91d4607ce5ee7474382794e5ec755f38b70b1f30e647:
------------------------------------------
Valid From Timestamp  : 1789593821
Observations Timestamp: 1789593821
Native Fee            : 132954556310247
LINK Fee              : 29292627326579709
Expires At            : 1792185821
Mid Price             : 105584000000000000000
Bid Price             : 105539000000000000000
Ask Price             : 105629000000000000000
Expiry Time           : 1790740800
First Day of Notice   : 1790740800
Last Seen TimestampNs : 1789592399813000000
Market Status         : 1
Contract Month        : 11
Goldman Roll Price    : 100574999999999990000
Current Business Day  : 11
Interp Goldman Roll   : 0
------------------------------------------

The prices above are 18-decimal fixed-point integers. For example, Mid Price 105584000000000000000 equals 105.584 USD. Contract Month 11 is November, and Market Status 1 means the market is closed.

Decoded fields

The v14 report decodes to the following fields. Prices are 18-decimal fixed-point integers; timestamps are Unix epoch seconds unless noted.

FieldTypeMeaning
midPrice, bidPrice, askPriceint192Consensus mid, bid, and ask prices (18 decimals)
expiryTimeuint64Contract expiration, Unix seconds
firstDayOfNoticeuint64First day of notice, Unix seconds
lastSeenTimestampNsuint64Last provider update, nanoseconds
marketStatusuint32Market status: 0 Unknown, 1 Closed, 2 Open
contractMonthuint32Contract month, 1 (Jan) to 12 (Dec)
goldmanRollPriceint192Goldman Roll continuous price (18 decimals)
currentBusinessDayuint32Current business day in the roll window
interpolatedGoldmanRollPriceint192Interpolated Goldman Roll price; 0 outside a roll window

Implementation patterns

Futures streams differ from spot streams in a few ways that affect how you consume them:

  • Use marketStatus, not timestamps, to gate trading. marketStatus is the authoritative signal for whether the market is open. Do not infer market state from lastSeenTimestampNs or other timestamps; they only indicate when data was last recorded. See Market Hours for the value mapping per feed.
  • Monitor lastSeenTimestampNs for staleness. Futures are sourced from a single provider per feed. If updates stop arriving, the price can go stale without an obvious price change. Pause trading or restrict liquidations when the timestamp lags your freshness threshold.
  • Expect a price discontinuity at each roll. When the front contract expires, the feed re-maps to the next contract, which can trade at a different price. Do not treat this step as a data error. If your application needs a smooth series, build it from the continuous roll fields (goldmanRollPrice, interpolatedGoldmanRollPrice) or construct your own off-chain.
  • Do not hardcode contract-month spacing. The gap between the front, second, and third feeds varies by instrument (one month for energy, up to a few months for precious metals). Read contractMonth from the report rather than assuming monthly spacing.
  • Roll early if you are expiry-sensitive. In the final days of a contract, liquidity migrates to the next contract while the front feed still tracks the expiring one, which can increase volatility. If your application is sensitive to this, roll ahead of expiry using the second feed.

Consuming the three generic feeds

Each instrument is exposed as three feeds that always track the front, second, and third eligible contracts. The three feeds are the same schema (v14) and differ only in which contract they currently reference. To consume the curve correctly:

  • Identify the cut from contractMonth and expiryTime, not from the feed ID. The feed IDs are static and do not encode which cut a feed is. Read contractMonth and expiryTime from each report to know which contract a feed currently tracks. The feed with the nearest expiryTime is the front; the next two are the second and third.
  • The three feeds cascade on the front contract's expiry. When the front expires, the front feed takes over the second feed's contract, the second takes over the third's, and the third picks up the next eligible contract. Your integration should not hardcode which contract a feed tracks; it should re-read contractMonth/expiryTime each report.
  • Build your own roll strategy. Data Streams does not impose one. You can hold the front feed to expiry, roll early to the second feed when liquidity migrates, or construct a continuous series off-chain. The three feeds give you the visibility to implement whichever you choose.

The example below fetches all three feeds for an instrument and prints which contract each currently tracks, so you can see the curve and detect a roll:

// Read the front, second, and third feeds for an instrument.
// Replace the three feed IDs with the front/second/third feeds for your instrument.
feedIDs := []feed.ID{frontID, secondID, thirdID}

for _, id := range feedIDs {
    resp, err := client.GetLatestReport(ctx, id)
    if err != nil {
        log.Fatalf("fetch %s: %v", id, err)
    }
    decoded, err := report.Decode[v14.Data](resp.FullReport)
    if err != nil {
        log.Fatalf("decode %s: %v", id, err)
    }
    d := decoded.Data
    fmt.Printf("%s -> contract month %d, expiry %d, mid %s\n",
        id, d.ContractMonth, d.ExpiryTime.Unix(), d.MidPrice.String())
}

Notes

  • Prices are 18-decimal fixed-point integers. midPrice, bidPrice, askPrice, goldmanRollPrice, and interpolatedGoldmanRollPrice are raw integers. Divide by 10^18 to get the human-readable price. In Go and Rust these are big-number types; call .String() to display them.
  • interpolatedGoldmanRollPrice is 0 outside a roll window. Do not treat a 0 value as a price of zero; it means the feed is not currently in an active roll window.
  • contractMonth is validated to 112. The SDKs reject reports with an out-of-range month, so a successful decode guarantees a valid month.
  • The feed ID encodes the schema version. The TypeScript SDK's getReportVersion and the Go SDK's feedID.Version() derive the schema from the feed ID. Use the v14 decoder for futures feeds.

Coverage

Futures streams are currently available for the following commodities. Additional commodities and asset classes may be added based on demand.

CommodityGeneric feeds
GoldFront, second, and third feeds
SilverFront, second, and third feeds
US OilFront, second, and third feeds
UK OilFront, second, and third feeds
Natural GasFront, second, and third feeds

Naming and price representation

Feed names use the plain instrument name only (for example, "US Oil," "Gold," "Natural Gas").

Contract specifications — contract size, tick size, quotation units, and similar terms — can vary between exchanges for the same underlying instrument. Chainlink does not publish these specifications, since they belong to the exchange where a given contract is listed. If your application needs exact contract specifications, consult that exchange's own public product documentation directly.

Rotation methodology

Unlike spot instruments, futures contracts have a finite life. For example, a given month's Natural Gas contract trades for a limited window and then expires. If Data Streams published a dedicated feed per contract, integrators would need to re-point their integration to a new feed every time a contract expired.

To avoid this, each instrument is represented by three static, non-expiring generic feeds that always represent the near-term forward curve of eligible contracts:

Generic feedMeaning
Front feedTracks the nearest unexpired eligible contract
Second feedTracks the next eligible contract after the front contract
Third feedTracks the eligible contract after that

This design means:

  • Zero maintenance. Integrate the three generic feeds once. You never need to re-point feed endpoints when a contract expires — the feeds re-map internally.
  • Full visibility of the curve. Because the front, second, and third contracts are exposed simultaneously, you retain full autonomy over your own rolling logic. You can roll positions on a fixed schedule ahead of expiry, or when liquidity migrates from the front contract to the second — Data Streams does not impose a roll strategy.

How the roll works

When the front contract reaches its expiration, the generic feeds cascade forward:

  • The front feed takes over the contract previously tracked by the second feed
  • The second feed takes over the contract previously tracked by the third feed
  • The third feed begins tracking the next eligible contract in that instrument's cycle

A few properties of the roll are worth calling out explicitly:

  • The roll is triggered by the front contract's expiration, as defined by the listed expiration calendar for that specific instrument. Expiration conventions differ by instrument, so rolls for different instruments happen on different dates — there is no single "roll day" across all instruments.
  • The switch happens right after the primary exchange's trading session closes on the expiration date, not at a fixed clock time across all instruments. For these commodities, that session close is typically around 17:00 ET.
  • The roll is a re-mapping, not a price adjustment. At the moment of the roll, the front feed switches from quoting the expiring contract to quoting what was previously the second contract. Because different contract months can trade at different prices, expect a price discontinuity on the generic feed at each roll. Feeds are not back-adjusted into a smooth continuous series — if your application needs a continuous series, construct it from the raw generic feeds on your end.
  • Until expiration, the front feed continues to track the expiring contract, even in its final days when liquidity has typically already migrated to the next contract. This is precisely why the second feed is exposed — so you can implement your own liquidity-based rolls ahead of expiry instead of holding the front contract to the end.

Contract eligibility varies by instrument

The front feed does not necessarily track the nearest calendar month — it tracks the nearest unexpired eligible contract, whatever month that happens to be. Each instrument has its own set of listed contract months, and for several instruments, the actively traded contracts are only a subset of what is listed:

  • Energy (US Oil, UK Oil, Natural Gas): every calendar month is listed, though liquidity is generally concentrated in nearer maturities. The generic feeds walk the full monthly strip regardless, so the front, second, and third feeds track three consecutive calendar months.
  • Precious metals (Gold, Silver): many calendar months are listed, but liquidity concentrates in a benchmark cycle. The generic feeds follow the benchmark cycle only and skip non-benchmark months, even though those contracts exist and trade.
CommodityBenchmark months
GoldFeb, Apr, Jun, Aug, Oct, Dec
SilverMar, May, Jul, Sep, Dec

The practical consequence: the gap between the front, second, and third feeds is not always one month. For Gold or Silver, consecutive generic feeds can be one to three months apart. Do not assume monthly spacing in your integration.

Worked example

Gold follows its benchmark cycle (Feb/Apr/Jun/Aug/Oct/Dec). In August 2026, before the August contract expires:

FeedContract tracked
Front feedAugust 2026
Second feedOctober 2026
Third feedDecember 2026

When the August contract expires, the front feed jumps directly to October, the second feed jumps to December, and the third feed begins tracking February 2027 — skipping the non-benchmark September and November contracts entirely, since they never appear on the generic feeds.

Integration guidance

  • Do not hardcode contract-month assumptions. The spacing between the front, second, and third feeds varies by instrument — one month for energy, up to a few months for precious metals.
  • Expect price steps at rolls. A protocol using the front feed directly for margining or liquidation should either accept the step as economically correct (the front contract genuinely changed), implement its own early roll using the second feed, or build an adjusted continuous series off-chain from the raw generic feeds.
  • Roll early to avoid volatility near expiry. In the final days of a contract's life, trading volume typically migrates to the next contract while the front feed still tracks the expiring one. Thin liquidity in the expiring contract can increase price volatility, which may unexpectedly trigger perpetual trades, liquidations, or other price-sensitive logic. If your application is sensitive to this, consider rolling ahead of expiry using the second feed.
  • Follow the listed expiration calendar. Expiration dates follow the official listed calendar of the exchange where the underlying contract trades, including holiday adjustments. Consult that exchange for the operative source of truth.

Trading hours

Futures trade during standard trading hours for each instrument, with a scheduled break each trading day. Consult the exchange where the underlying contract is listed for holiday and early-close schedules.

Each feed's marketStatus field reflects the current market status of the primary exchange venue for that instrument.

Risk considerations

Integrating protocols are responsible for implementing appropriate monitoring and risk mitigation mechanisms to ensure safe market operation.

Single-source data

Each Futures instrument is sourced from a single institutional-grade data provider per feed. While this enables high-frequency, low-latency delivery, it introduces dependency on that provider's data quality and availability. There is no redundant cross-validation between providers for a given feed.

If the underlying provider fails to deliver data, there is no fallback source, and a gap in data may not be immediately obvious from price alone. Protocols should continuously monitor feed freshness and implement safeguards — such as pausing trading or restricting liquidations — if updates stop arriving or a feed appears stale. Review the Developer Responsibilities guidance before integration.

Available streams

The table below shows all available Futures streams.

Streams Verifier Network Addresses

Expand to view supported networks and addresses required for onchain report verification

Mainnet Futures Streams

No Mainnet feeds available.

Support

For inquiries related to a data outage, contact Chainlink Labs at chainlink_data_feeds@smartcontract.com.

What's next

Get the latest Chainlink content straight to your inbox.