Esc

    The API is under testing and may change before release.

    Bond future options

    This page lists what you need to price a listed option on a bond future: the two constructors, the market inputs it reads, the diagnostics it reports, and an example you can run. A bond future option is an option to buy or sell a government bond future at a strike; a call on the ten-year note future pays when the future rallies above the strike.

    Quick example

    The example reads its quotes from the desk sheets; load them once and it runs as shown.

    This prices CBOT options on the December 2026 ten-year note future, struck at 112.00, with the future at 112.50 and a flat 6% vol. It reads the European value and its inputs, checks the call less the put, prices the early-exercise premium and the value of one contract, and repeats the call margined in the futures style.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{
        AnyInstrument, BondFutureOption, CollateralSpec, Currency, ExerciseStyle,
        FutureOptionVolSurface, ListedFutureId, MarketContext, OptionKind, PremiumStyle, QuoteSheet,
        VolConvention, black76, bond_future_option_diagnostics, bond_future_option_from_template,
    };
    
    type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
    
    const CONTRACT: &str = "CBOT-ZN-2026-12";
    const STRIKE: f64 = 112.0;
    
    fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let contract: ListedFutureId = CONTRACT.parse()?;
    
        // The rates curves come from the close; the future's price and its option vol
        // surface join with the market's own verbs.
        let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
        let market = MarketContext::as_of(close)
            .with_recipe_quotes(&CollateralSpec::Currency("USD".parse()?), &sheet)?
            .with_future_price(contract.clone(), 112.5)?
            .with_future_option_vols(
                contract.clone(),
                FutureOptionVolSurface::flat(close, 0.06, VolConvention::Lognormal)?,
            );
        let call = AnyInstrument::BondFutureOption(Box::new(bond_future_option_from_template(
            "CBOT-OZN",
            2026,
            12,
            STRIKE,
            OptionKind::Call,
        )?));
        let put = AnyInstrument::BondFutureOption(Box::new(bond_future_option_from_template(
            "CBOT-OZN",
            2026,
            12,
            STRIKE,
            OptionKind::Put,
        )?));
        println!(
            "future   {CONTRACT} at {:.4}",
            market.future_price(&contract).expect("pinned")
        );
        println!("options  CBOT-OZN December 2026, struck at {STRIKE:.2}");
    
        // The European value is Black-76 on the futures price, discounted to expiry:
        // a paid premium is cash today for a payoff at expiry.
        let seen = bond_future_option_diagnostics(&call, &market)?;
        println!("futures price     {:.4}", seen.futures_price);
        println!("years to expiry   {:.6}", seen.time);
        println!("discount          {:.6}", seen.discount);
        println!("european per 100  {:.6}", seen.european);
    
        // On the futures price, a call less a put is the discounted forward less the
        // discounted strike. American exercise adds a premium that is never negative,
        // and the value is per contract: a point is 1,000 dollars.
        let other = bond_future_option_diagnostics(&put, &market)?;
        let parity = seen.european - other.european;
        let value = call.npv(&market)?;
        println!("call - put        {parity:.6}");
        println!("early exercise    {:.6}", seen.early_exercise);
        println!("value, 1 contract {value:.2}");
    
        // The same option margined like its future: nothing is paid up front, so it is
        // valued undiscounted, and exercising early gains nothing.
        let margined = AnyInstrument::BondFutureOption(Box::new(BondFutureOption::new(
            contract,
            date((2026, 11, 20)),
            STRIKE,
            OptionKind::Call,
            PremiumStyle::FuturesStyle,
            ExerciseStyle::American,
            Currency::new("USD")?,
        )?));
        let style = bond_future_option_diagnostics(&margined, &market)?;
        println!("discount          {:.6}", style.discount);
        println!("european per 100  {:.6}", style.european);
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    CONTRACT = "CBOT-ZN-2026-12"
    STRIKE = 112.0
    
    # The rates curves come from the close; the future's price and its option vol
    # surface join with the market's own verbs.
    market = (
        hlx.MarketContext(SOFR_CLOSE_DATE)
        .with_recipe_quotes("USD", USD_SOFR_CLOSE)
        .with_future_price(CONTRACT, 112.5)
        .with_future_option_vols(CONTRACT, hlx.vols.FutureOptionVolSurface.flat(SOFR_CLOSE_DATE, 0.06))
    )
    call = hlx.products.bond_future_option_from_template("CBOT-OZN", 2026, 12, strike=STRIKE)
    put = hlx.products.bond_future_option_from_template("CBOT-OZN", 2026, 12, strike=STRIKE, kind="put")
    print(f"future   {CONTRACT} at {market.future_price(CONTRACT):.4f}")
    print(f"options  CBOT-OZN December 2026, struck at {STRIKE:.2f}")
    
    # The European value is Black-76 on the futures price, discounted to expiry:
    # a paid premium is cash today for a payoff at expiry.
    seen = call.diagnostics(market)
    print(f"futures price     {seen.futures_price():.4f}")
    print(f"years to expiry   {seen.time():.6f}")
    print(f"discount          {seen.discount():.6f}")
    print(f"european per 100  {seen.european():.6f}")
    
    # On the futures price, a call less a put is the discounted forward less the
    # discounted strike. American exercise adds a premium that is never negative,
    # and the value is per contract: a point is 1,000 dollars.
    other = put.diagnostics(market)
    parity = seen.european() - other.european()
    value = call.npv(market)
    print(f"call - put        {parity:.6f}")
    print(f"early exercise    {seen.early_exercise():.6f}")
    print(f"value, 1 contract {value:.2f}")
    
    # The same option margined like its future: nothing is paid up front, so it is
    # valued undiscounted, and exercising early gains nothing.
    margined = hlx.products.bond_future_option(
        CONTRACT, dt.date(2026, 11, 20), strike=STRIKE, currency="USD", premium_style="futures_style"
    )
    style = margined.diagnostics(market)
    print(f"discount          {style.discount():.6f}")
    print(f"european per 100  {style.european():.6f}")
    future   CBOT-ZN-2026-12 at 112.5000
    options  CBOT-OZN December 2026, struck at 112.00
    futures price     112.5000
    years to expiry   0.136986
    discount          0.994546
    european per 100  1.257479
    call - put        0.497273
    early exercise    0.001151
    value, 1 contract 1258.63
    discount          1.000000
    european per 100  1.264374

    The European value is Black-76 on the futures price, discounted to expiry. The call less the put is the discount factor times the forward less the strike, 0.994546×0.500.994546 \times 0.50. American exercise adds a small premium, and a contract is 1,000 dollars a point. Margined in the futures style, the option isn't discounted, so its European value is a little higher.

    How it's priced

    With FF the futures price, KK the strike, σ\sigma the vol, TT the time to expiry and D(T)D(T) the discount factor:

    c=F N(d1)−K N(d2),d1,2=ln⁡(F/K)±12σ2TσT,C−P=D(T) (F−K).c = F\,N(d_1) - K\,N(d_2), \qquad d_{1,2} = \frac{\ln(F/K) \pm \tfrac12\sigma^2 T}{\sigma\sqrt{T}}, \qquad C - P = D(T)\,(F - K).

    A paid premium is worth D(T) cD(T)\,c; a futures-style premium is worth cc, undiscounted. The early exercise premium is priced with the Barone-Adesi-Whaley approximation. Why the two premium styles value differently is covered in how bonds work.

    API

    Argument Type Default Meaning
    code, year, month str, int, int required the venue's option template and the contract month, such as "CBOT-OZN", 2026, 12
    contract, expiry str, date required the future, such as "CBOT-ZN-2026-12", and the option's expiry, when stated in full
    strike float required the strike, per 100 of the future's face
    currency str required when stated in full the premium's currency
    premium_style str required when stated in full "paid" or "futures_style"
    kind str "call" "call" or "put"
    exercise str "american" "american" or "european", when stated in full
    contracts float 1.0 the number of contracts
    point_value float 1000.0 dollars per point, when stated in full
    traded_premium float 0.0 the premium paid, netted from the value
    short bool False holds the option short

    products.bond_future_option_from_template(code, year, month, *, strike, ...) takes the venue's terms from its template; products.bond_future_option(contract, expiry, *, strike, currency, premium_style, ...) states them. In Rust, bond_future_option_from_template(code, year, month, strike, kind)? and BondFutureOption::new(..)?.

    Symbol Meaning Rust Python
    FF the future's price MarketContext::with_future_price(contract, price)? MarketContext.with_future_price
    σ\sigma the option vol MarketContext::with_future_option_vols(contract, surface) MarketContext.with_future_option_vols
    cc, D(T)D(T), TT, early exercise what the price is built from bond_future_option_diagnostics(&option, &market)? Instrument.diagnostics(market)
    Black-76 the undiscounted model price black76(F, K, σ, T, kind)? models.black76
    value per contract price times point value AnyInstrument::npv Instrument.npv

    Defaults and limits

    • Market inputs: the currency's discount curve, the future's price, and a vol surface for the contract.
    • Premium styles: CBOT pays the premium up front; Eurex and ICE margin it daily ("futures_style"), which makes it undiscounted and leaves nothing to gain from early exercise.
    • Units: the diagnostics are per 100 of the future's face; npv is per contract, times contracts.

    See also

    • Source: crates/helixrs/src/instruments/bond_derivatives.rs, crates/helixrs/src/statics/listed_future.rs, crates/helixrs/src/marketdata/future_option_vol.rs
    • Related pages:
      • How bonds work, for the premium styles.
      • Bond future, for the future and its basket.
      • Bond option, for an option on a bond itself.
      • Listed futures, for contract codes and exchange calendars.

    From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.