Esc

    The API is under testing and may change before release.

    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.

    Rust
    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(())
    }
    Python
    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 B0B_0 be the bond's dirty price for spot settlement, DrepoD_{\text{repo}} the repo curve, and cjc_j the coupons paid at tjt_j before the forward date TT. Buying spot and financing on repo until TT replicates the forward, so the forward dirty price is

    Fdirty=B0−∑tj<Tcj Drepo(tj)Drepo(T),F^{\text{dirty}} = \frac{B_0 - \sum_{t_j < T} c_j\, D_{\text{repo}}(t_j)}{D_{\text{repo}}(T)},

    and the clean forward price subtracts the accrued interest at TT. A forward on notional NN struck at KK pays the dirty forward less the dirty strike on its settlement date TsT_s, discounted on the currency's discount curve PP, not the repo curve:

    V=N100 (Fdirty−Kdirty) P(Ts).V = \frac{N}{100}\,\bigl(F^{\text{dirty}} - K^{\text{dirty}}\bigr)\, P(T_s).

    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
    DrepoD_{\text{repo}} the financing curve MarketContext::with_repo_spread MarketContext.with_repo_spread
    PP the currency's discount curve, read at settlement_date MarketContext::with_discount MarketContext.with_discount
    FF the forward clean price forward_price(.., PriceBasis::Clean, Some(key)) bonds.forward_price(.., repo_key=key)
    NN, KK the forward's terms BondForward::new products.bond_forward
    VV 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_date before the forward_date is 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.