Rate futures
This page lists what you need to price a listed short-term interest rate future: the booking call and its arguments, the fair price and convexity formulas, and the defaults you get. A rate future is a listed contract on a three-month or one-month rate, quoted as 100 less the rate.
Quick example
The example reads its quotes from the desk sheets; load them once and it runs as shown.
This books ten March 2028 SOFR three-month contracts bought at 95.40, reads their fair price and the position's value, then prices them again with Hull-White convexity.
use chrono::NaiveDate;
use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_LADDER};
use helixrs::{
AnyInstrument, AnyTemplate, CollateralSpec, MarketContext, PricingRiskFactors,
QuoteSheet, RateFuture, futures_convexity, futures_price, template,
};
type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
}
fn position(short: bool) -> Outcome<RateFuture> {
let AnyTemplate::Future(futures) = template("USD-SOFR-3M-FUT")? else {
unreachable!("a futures template")
};
let future = RateFuture::for_contract(&futures, 2028, 3, 10.0, 95.40)?;
Ok(if short { future.short() } else { future })
}
/// The fair price and the convexity adjustment, read on the market's resolved view.
fn price(future: &RateFuture, market: &MarketContext) -> Outcome<(f64, f64)> {
let instrument = AnyInstrument::RateFuture(future.clone());
Ok(market.priced(&instrument.risk_factors()?, |view| {
Ok((futures_price(future, view)?, futures_convexity(future, view)?))
})?)
}
fn main() -> Outcome<()> {
let close = date(SOFR_CLOSE_DATE);
let market = MarketContext::as_of(close)
.with_recipe_quotes(
&CollateralSpec::Currency("USD".parse()?),
&QuoteSheet::from_rows(USD_SOFR_LADDER.iter().copied())?,
)?
.with_hull_white("USD".parse()?, 0.03, 0.01);
// Ten March 2028 contracts, bought at 95.40.
let long = position(false)?;
let instrument = AnyInstrument::RateFuture(long.clone());
for (name, day) in instrument.contract_dates() {
println!("{name:<14} {day}");
}
// The fair price is 100 less the forward rate over the reference quarter; the
// position is worth the price move times contracts and the point value.
let (fair, _) = price(&long, &market)?;
let npv = instrument.npv(&market)?;
println!("fair price {fair:.6}");
println!("npv {npv:.2}");
// Under Hull-White the futures rate sits above the forward, so the convexity
// adjusted price is lower than the forward price. The adjustment reads
// directly as a rate, and it is the whole gap between the two prices.
let (adjusted, convexity) = price(&long.clone().with_hull_white_convexity(), &market)?;
println!("adjusted price {adjusted:.6}");
println!("convexity bp {:.4}", convexity * 1e4);
Ok(())
}import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_LADDER
market = (
hlx.MarketContext(SOFR_CLOSE_DATE)
.with_recipe_quotes("USD", USD_SOFR_LADDER)
.with_hull_white("USD", 0.03, 0.01)
)
futures = hlx.templates.get("USD-SOFR-3M-FUT")
# Ten March 2028 contracts, bought at 95.40.
position = futures.rate_future_contract(2028, 3, contracts=10.0, traded_price=95.40)
for name, day in position.contract_dates():
print(f"{name:<14} {day}")
# The fair price is 100 less the forward rate over the reference quarter; the
# position is worth the price move times contracts and the point value.
fair = hlx.rates.futures_price(position, market)
npv = position.npv(market)
print(f"fair price {fair:.6f}")
print(f"npv {npv:.2f}")
# Under Hull-White the futures rate sits above the forward, so the convexity
# adjusted price is lower than the forward price. The adjustment reads
# directly as a rate, and it is the whole gap between the two prices.
modelled = position.with_hull_white_convexity()
adjusted = hlx.rates.futures_price(modelled, market)
convexity = hlx.rates.futures_convexity(modelled, market)
print(f"adjusted price {adjusted:.6f}")
print(f"convexity bp {convexity * 1e4:.4f}")accrual_start 2028-03-15
accrual_end 2028-06-21
fair price 95.277505
npv -3062.38
adjusted price 95.263478
convexity bp 1.4026
With the model switched on, the fair price drops below the forward price: that gap is the convexity adjustment.
How it's priced
The fair price is , where the futures rate is the forward plus the convexity adjustment, . A position of contracts bought at is worth
with the contract's value per index point. A three-month SOFR contract compounds the overnight rate in arrears over its reference period, from , the first day not yet fixed, to its end , with accrual . Under Hull-White with mean reversion and volatility its futures rate is
with , and . The first two terms are the convexity of a term rate fixing at ; the third is the variance the rate accrues while it fixes. So is positive, and it grows with and with the time to the contract. The gap between the forward price and the adjusted price is the adjustment, in price points: . Why daily margining creates that gap is covered in how rates products work.
API
| Symbol | Meaning | Rust | Python |
|---|---|---|---|
| the contract | a listed contract | RateFuture::for_contract |
Template.rate_future_contract |
| short | the other side | RateFuture::short |
short=True |
| the fair price | futures_price inside MarketContext::priced |
rates.futures_price |
|
| the model | MarketContext::with_hull_white |
MarketContext.with_hull_white |
|
| modelled | Hull-White convexity | RateFuture::with_hull_white_convexity |
Instrument.with_hull_white_convexity |
| the convexity exponent | HullWhiteParameters::compounded_convexity_exponent |
models.HullWhite.compounded_convexity_exponent |
|
| the contract's dates and the model's duration factors | inside RateFuture::with_hull_white_convexity |
inside Instrument.with_hull_white_convexity |
|
| the adjustment, as a rate | futures_convexity inside MarketContext::priced |
rates.futures_convexity |
|
| the position's value | AnyInstrument::npv |
Instrument.npv |
Template.rate_future_contract takes:
| Argument | Type | Default | Meaning |
|---|---|---|---|
year, month |
int |
required | the contract's delivery month |
contracts |
float |
required | the number of contracts |
traded_price |
float |
required | the price you traded at |
short |
bool |
False |
book the short side; Rust .short() |
convexity_adjustment |
float or None |
None |
a fixed adjustment, as a rate, in place of a model |
In Rust, RateFuture::for_contract(template, year, month, contracts, traded_price) takes the same
values positionally.
Defaults and limits
- Side: a contract is booked long. Pass
short=Truein Python, or call.short()in Rust, for the other side. - Convexity: none is added unless you ask for it. The Hull-White adjustment needs the model on
the market (
with_hull_white) and on the instrument (with_hull_white_convexity). - Contracts: codes, reference periods and the value per point come from the listed contract; see listed futures and rates conventions.
- Forwards: the fair price is one reading of the forward over the reference quarter; the forward itself is read on reading a curve.
See also
- Source:
crates/helixrs/src/instruments/rates.rs,crates/helixrs/src/market/pricers.rs - Related pages:
- Listed futures, for contract codes and periods.
- Curve helpers, for futures as curve rungs.
- Hull-White, for calibrating the model.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.