finance-query v3.0.0

Futures#

The FuturesContract handle provides quote, chart, and history data for futures contracts. Quotes are served by Yahoo Finance (keyless — no feature flag, no API key) on the default route, the same way Yahoo resolves equity quotes. Polygon.io remains available as an alternate quote source, and CFTC serves Commitments of Traders positioning data (see Commitments of Traders below).

Getting a Handle#

Providers::builder().build() with no .route() call already serves futures quotes — Yahoo is the default for every capability. Yahoo resolves futures symbols the same way it resolves equities (e.g. "ES=F" for the E-mini S&P 500):

rust · no_run
use finance_query::{Interval, Providers, TimeRange};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let providers = Providers::builder().build().await?;
    let contract = providers.futures("ES=F");
    let quote = contract.quote().await?;
    let chart = contract.chart(Interval::OneDay, TimeRange::OneMonth).await?;
    let history = contract.history(TimeRange::OneMonth).await?;
    println!("{}: {:?} ({} candles)", quote.symbol, quote.price, chart.candles.len() + history.candles.len());
    Ok(())
}

Alternative: Polygon#

Route Capability::FUTURES to Provider::Polygon for an alternate quote source, or as a fallback behind Yahoo. Polygon uses its own contract-ticker format (e.g. "ES" rather than "ES=F") and set POLYGON_API_KEY in your environment before calling build():

rust · no_run feature=polygon
use finance_query::{Capability, Provider, Providers};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let providers = Providers::builder()
        .route(Capability::FUTURES, [Provider::Polygon, Provider::Yahoo])
        .build()
        .await?;
    let contract = providers.futures("ES");
    let quote = contract.quote().await?;
    println!("{}: {:?}", quote.symbol, quote.price);
    Ok(())
}

Quote#

quote() returns a FuturesQuote with the current contract price and metadata.

FuturesQuote Fields:

FieldTypeDescription
symbolStringContract ticker symbol (e.g., "ESM26")
nameOption<String>Human-readable contract name
underlyingOption<String>Underlying asset (e.g., "S&P 500")
exchangeOption<String>Exchange where the contract trades
expiration_dateOption<String>Contract expiry as YYYY-MM-DD
priceOption<f64>Current contract price
changeOption<f64>Price change
change_percentOption<f64>Price change as a percentage (e.g. 9.62 for 9.62%)
open_interestOption<u64>Number of outstanding contracts
volumeOption<u64>Session volume in contracts
timestampOption<i64>Unix timestamp of the last update, in seconds

This field-verification helper compiles as a real test, so the table above cannot drift from the type:

rust · runnable
use finance_query::FuturesQuote;

// `FuturesQuote` is #[non_exhaustive] outside the crate, so construct via
// serde. With live data: `providers.futures("ES=F").quote().await?`.
let quote: FuturesQuote = serde_json::from_value(serde_json::json!({
    "symbol": "ESM26",
    "name": "E-mini S&P 500 Jun 2026",
    "underlying": "S&P 500",
    "exchange": "CME",
    "expiration_date": "2026-06-19",
    "price": 5432.25,
    "change": 12.50,
    "change_percent": 0.23,
    "open_interest": 1_850_000_u64,
    "volume": 920_000_u64,
    "timestamp": 1_718_000_000_i64,
}))
.unwrap();

fn verify_futures_quote_fields(q: FuturesQuote) {
    let _: String = q.symbol;
    let _: Option<String> = q.name;
    let _: Option<String> = q.underlying;
    let _: Option<String> = q.exchange;
    let _: Option<String> = q.expiration_date;
    let _: Option<f64> = q.price;
    let _: Option<f64> = q.change;
    let _: Option<f64> = q.change_percent;
    let _: Option<u64> = q.open_interest;
    let _: Option<u64> = q.volume;
    let _: Option<i64> = q.timestamp;
}
verify_futures_quote_fields(quote.clone());

println!("symbol = {}", quote.symbol);
println!("price = {:?}", quote.price);
println!("open_interest = {:?}", quote.open_interest);
recorded outputcargo soothfast docs capture
symbol = ESM26
price = Some(5432.25)
open_interest = Some(1850000)
checked claims
FuturesQuoteverified current

Chart#

chart(interval, range) returns OHLCV candles for the requested interval and time range.

History#

history(range) is a convenience wrapper around chart that picks a sensible default interval for the given range via TimeRange::default_interval.

Indicators & Risk#

indicators(interval, range) / indicator(kind, interval, range) (requires the indicators feature) and risk(interval, range) (requires the risk feature) compute directly from this handle's own chart data:

rust · no_run feature=risk
use finance_query::indicators::Indicator;
use finance_query::{Interval, Providers, TimeRange};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let providers = Providers::builder().build().await?;
    let contract = providers.futures("ES=F");

    let summary = contract
        .indicators(Interval::OneDay, TimeRange::ThreeMonths)
        .await?;
    if let Some(rsi) = summary.rsi_14 {
        println!("RSI(14): {:.2}", rsi);
    }

    let rsi_21 = contract
        .indicator(Indicator::Rsi(21), Interval::OneDay, TimeRange::ThreeMonths)
        .await?;

    let risk = contract.risk(Interval::OneDay, TimeRange::OneYear).await?;
    println!("VaR 95%:      {:.2}%", risk.var_95 * 100.0);
    println!("Max Drawdown: {:.2}%", risk.max_drawdown * 100.0);
    Ok(())
}

risk takes no benchmark parameter — beta is always None, since futures contracts have no natural benchmark to compare against.

Commitments of Traders#

commitments_of_traders() (cftc feature) fetches weekly CFTC positioning data for this contract — see the CFTC provider page for symbol resolution, field meanings, and a full example. It's a separate operation from quote(): CFTC has no price data at all, so route FUTURES to [Provider::Cftc, Provider::Yahoo] (or Provider::Polygon) to get both quotes and positioning from one handle.

Provider Reference#

built with cargo soothfast docs build source