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.
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(())
}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, . 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 the futures price, the strike, the vol, the time to expiry and the discount factor:
A paid premium is worth ; a futures-style premium is worth , 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 |
|---|---|---|---|
| the future's price | MarketContext::with_future_price(contract, price)? |
MarketContext.with_future_price |
|
| the option vol | MarketContext::with_future_option_vols(contract, surface) |
MarketContext.with_future_option_vols |
|
| , , , 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;
npvis per contract, timescontracts.
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.