Bond forward
This page lists what you need to book and value a bond forward: the bond_forward call and its
arguments, the forward price it's struck against, and how it's valued on the repo curve. A bond
forward agrees today to buy a bond on a later date at a fixed price.
Quick example
The example reads its quotes from the desk sheets; load them once and it runs as shown.
This puts general collateral 10bp under SOFR on the USD close, reads a ten-year 4.25% bond's spot and forward clean prices for delivery on 1 April 2027, then values a forward on 10 million struck at the forward price and one struck a point above it.
use chrono::NaiveDate;
use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE, USD_SOFR_PRINTS};
use helixrs::{
AnyInstrument, BondForward, CollateralSpec, Curve, DayCountFraction, FixedRateBond, FixingKey,
FixingStore, Frequency, MarketContext, PriceBasis, QuoteSheet, RepoSpread, forward_price,
};
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 main() -> Outcome<()> {
let close = date(SOFR_CLOSE_DATE);
let delivery = date((2027, 4, 1));
let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
let prints: Vec<(FixingKey, NaiveDate, f64)> = USD_SOFR_PRINTS
.iter()
.map(|(index, day, value)| (FixingKey::Typed((*index).to_owned()), date(*day), *value))
.collect();
let gc = RepoSpread::new(close, &[(date((2027, 10, 1)), -0.0010)], DayCountFraction::Act360)?;
let market = MarketContext::as_of(close)
.with_fixings(FixingStore::from_observations_checked(&prints)?)
.with_recipe_quotes(&CollateralSpec::Currency("USD".parse()?), &sheet)?
.with_repo_spread("USD", Curve::RepoSpread(gc), "discount(USD)".parse()?);
let bond = FixedRateBond::bullet(
"USD".parse()?,
date((2025, 8, 15)),
date((2035, 8, 15)),
0.0425,
Frequency::SemiAnnual,
DayCountFraction::ActActIcma,
100.0,
)?;
// Carried on the USD GC repo curve, past the February coupon, to 1 April. The
// bond's 4.25% coupon earns more than GC costs to finance it, so the carry is
// positive and the forward clean price sits below the spot one.
let spot = forward_price(&bond, &market, date((2026, 10, 2)), PriceBasis::Clean, Some("USD"))?;
let forward = forward_price(&bond, &market, delivery, PriceBasis::Clean, Some("USD"))?;
println!("spot price {spot:.6}");
println!("forward price {forward:.6}");
let forward_on = |strike: f64| -> Outcome<f64> {
let contract = BondForward::new(bond.clone(), 10_000_000.0, strike, delivery, delivery, "USD")?;
Ok(AnyInstrument::BondForward(Box::new(contract)).npv(&market)?)
};
let at_market = forward_on(forward)?;
let above = forward_on(forward + 1.0)?;
println!(
"at the forward worth nothing to within 1e-6: {}",
if at_market.abs() < 1e-6 { "yes" } else { "no" }
);
println!("one point above {above:.2}");
// One point on 10 million of face is 100,000 at delivery, discounted to today.
Ok(())
}import datetime as dt
import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE, USD_SOFR_PRINTS
DELIVERY = dt.date(2027, 4, 1)
gc = hlx.curves.RepoSpreadCurve(SOFR_CLOSE_DATE, [(dt.date(2027, 10, 1), -0.0010)])
market = (
hlx.MarketContext(SOFR_CLOSE_DATE)
.with_fixings(USD_SOFR_PRINTS)
.with_recipe_quotes("USD", USD_SOFR_CLOSE)
.with_repo_spread("USD", gc, "discount(USD)")
)
bond = hlx.products.fixed_rate_bond(
"USD", dt.date(2025, 8, 15), dt.date(2035, 8, 15),
coupon=0.0425, frequency="Semiannual", day_count="ACT/ACT.ICMA",
)
# Carried on the USD GC repo curve, past the February coupon, to 1 April. The
# bond's 4.25% coupon earns more than GC costs to finance it, so the carry is
# positive and the forward clean price sits below the spot one.
spot = hlx.bonds.forward_price(bond, market, dt.date(2026, 10, 2), repo_key="USD")
forward = hlx.bonds.forward_price(bond, market, DELIVERY, repo_key="USD")
print(f"spot price {spot:.6f}")
print(f"forward price {forward:.6f}")
def forward_on(strike):
return hlx.products.bond_forward(
bond, notional=10_000_000.0, strike=strike,
forward_date=DELIVERY, settlement_date=DELIVERY, repo_key="USD",
)
at_market = forward_on(forward).npv(market)
above = forward_on(forward + 1.0).npv(market)
print(f"at the forward worth nothing to within 1e-6: {'yes' if abs(at_market) < 1e-6 else 'no'}")
print(f"one point above {above:.2f}")
# One point on 10 million of face is 100,000 at delivery, discounted to today.spot price 95.932686
forward price 95.824553
at the forward worth nothing to within 1e-6: yes
one point above -97908.16
The bond's 4.25% coupon earns more than GC costs to finance it, so the forward clean price sits below the spot one. Struck at the forward price, the trade is worth nothing; struck a point above, it costs the buyer about a point on 10 million of face, discounted from delivery.
How it's priced
Let be the bond's dirty price for spot settlement, the repo curve, and the coupons paid at before the forward date . Buying spot and financing on repo until replicates the forward, so the forward dirty price is
and the clean forward price subtracts the accrued interest at . A forward on notional struck at pays the dirty forward less the dirty strike on its settlement date , discounted on the currency's discount curve , not the repo curve:
Why the forward price is the spot price carried on repo is covered in how financing works.
API
products.bond_forward(bond, *, notional, strike, forward_date, settlement_date, repo_key, ...):
| Argument | Type | Default | Meaning |
|---|---|---|---|
bond |
FixedRateBond |
required | the bond to be delivered |
notional |
float |
required | the face bought forward |
strike |
float |
required | the price per 100 agreed today, clean unless dirty_strike |
forward_date |
date |
required | the price-determination date the forward price is for |
settlement_date |
date |
required | the date the payoff settles and is discounted from; on or after forward_date |
repo_key |
str |
required | the repo curve the bond is financed on, such as "USD" for USD GC |
short |
bool |
False |
sells the bond forward instead of buying it |
dirty_strike |
bool |
False |
reads strike as a dirty price |
bonds.forward_price(bond, market, forward_date, price_basis="clean", repo_key=None) returns the
fair forward price per 100, clean or dirty; without repo_key it carries on the discount curve
itself. In Rust the forward is BondForward::new in AnyInstrument::BondForward, and the price is
forward_price(bond, market, date, PriceBasis::Clean, Some(key)).
| Symbol | Meaning | Rust | Python |
|---|---|---|---|
| the financing curve | MarketContext::with_repo_spread |
MarketContext.with_repo_spread |
|
the currency's discount curve, read at settlement_date |
MarketContext::with_discount |
MarketContext.with_discount |
|
| the forward clean price | forward_price(.., PriceBasis::Clean, Some(key)) |
bonds.forward_price(.., repo_key=key) |
|
| , | the forward's terms | BondForward::new |
products.bond_forward |
| its value | AnyInstrument::npv |
Instrument.npv |
Defaults and limits
- What the market needs: the bond's discount curve and a repo spread curve under
repo_key. - Coupons: only coupons paid before delivery are taken out of the forward price. A coupon paid after delivery belongs to the buyer and stays in the price.
- Side: the forward buys the bond unless
short=True. - Dates: a
settlement_datebefore theforward_dateis refused. - Strike: clean by default. The clean and dirty prices differ by the accrued interest at the forward date, which carries no curve risk.
See also
- Source:
crates/helixrs/src/instruments/financing.rs,crates/helixrs/src/market/bond_readings.rs - Related pages:
- How financing works, for carry.
- Repo, for the financing.
- Bond TRS, for the bond's return swapped over resets.
- Fixed-rate bond, for the bond itself.
- Bond conventions and repo conventions, for each market's terms.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.