Esc

    The API is under testing and may change before release.

    Repo

    This page lists what you need to book and value a repo: the template's repo call and its arguments, the GC curve it reads, classic and buy/sell-back coupons, and the margin position. A repo sells a bond for cash and buys it back later for the cash plus interest; a reverse repo is the other side, lending the cash.

    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, then repos 10 million of a ten-year 4.25% bond at 99.5 for six months at 4.00%. It values the repo and the reverse, compares a classic repo with a buy/sell-back across the bond's coupon date. To read a repo's margin position and haircut, see value a repo and its haircut.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE, USD_SOFR_PRINTS};
    use helixrs::{
        AnyInstrument, AnyTemplate, CollateralSpec, CouponTreatment, Curve, DayCountFraction,
        FixedRateBond, FixingKey, FixingStore, Frequency, MarketContext, QuoteSheet, Repo, RepoRate,
        RepoSide, RepoSpread, RepoTerms, package_value, 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 main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let start = date((2026, 10, 2));
    
        // Rates from the sheet, and general collateral 10bp under SOFR, pinned as a
        // repo spread curve over the USD discount curve.
        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 usd = "USD".parse()?;
        let bond = FixedRateBond::bullet(
            usd,
            date((2025, 8, 15)),
            date((2035, 8, 15)),
            0.0425,
            Frequency::SemiAnnual,
            DayCountFraction::ActActIcma,
            100.0,
        )?;
    
        // Six months on the USD GC template at 4.00%, on 10 million of the bond at 99.5.
        let AnyTemplate::Repo(gc_repo) = template("USD-GC-REPO")? else {
            unreachable!("a repo template")
        };
        let repo = |side, coupons, margin_ratio| -> Outcome<AnyInstrument> {
            let end = gc_repo.end_after(start, "6M".parse()?);
            let terms = RepoTerms::from_template(&gc_repo, side, start, end, RepoRate::Fixed(0.0400), margin_ratio)?;
            let built = Repo::from_collateral(terms, bond.clone(), 10_000_000.0, 99.5)?.with_coupons(coupons);
            Ok(AnyInstrument::Repo(Box::new(built)))
        };
        let borrow = repo(RepoSide::Repo, CouponTreatment::Classic, 1.0)?;
        let lend = repo(RepoSide::ReverseRepo, CouponTreatment::Classic, 1.0)?;
        println!("repo     {:.2}", borrow.npv(&market)?);
        println!("reverse  {:.2}", lend.npv(&market)?);
    
        // The bond pays a coupon inside the term. A classic repo manufactures it back
        // to the seller; a buy/sell-back folds it into the repurchase price. The cash
        // legs differ by just under the coupon; the packages, each with its collateral,
        // differ by far less.
        let classic = borrow.clone();
        let sell_back = repo(RepoSide::Repo, CouponTreatment::BuySellBack, 1.0)?;
        let coupon = 10_000_000.0 * 0.0425 / 2.0;
        let gap = sell_back.npv(&market)? - classic.npv(&market)?;
        println!("one coupon           {coupon:.2}");
        println!("cash legs differ by  {gap:.2}");
        println!(
            "packages             {:.2}  {:.2}",
            package_value(&classic, &market)?,
            package_value(&sell_back, &market)?
        );
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE, USD_SOFR_PRINTS
    
    START = dt.date(2026, 10, 2)
    
    # Rates from the sheet, and general collateral 10bp under SOFR, pinned as a
    # repo spread curve over the USD discount curve.
    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",
    )
    
    # Six months on the USD GC template at 4.00%, on 10 million of the bond at 99.5.
    def repo(side, coupons="classic", margin_ratio=1.0):
        return hlx.templates.get("USD-GC-REPO").repo(
            START, "6M", side=side, collateral=bond, price=99.5,
            rate=0.0400, nominal=10_000_000.0, coupons=coupons, margin_ratio=margin_ratio,
        )
    
    borrow, lend = repo("repo"), repo("reverse")
    print(f"repo     {borrow.npv(market):.2f}")
    print(f"reverse  {lend.npv(market):.2f}")
    
    # The bond pays a coupon inside the term. A classic repo manufactures it back
    # to the seller; a buy/sell-back folds it into the repurchase price. The cash
    # legs differ by just under the coupon; the packages, each with its collateral,
    # differ by far less.
    classic, sell_back = borrow, repo("repo", coupons="buy_sell_back")
    coupon = 10_000_000.0 * 0.0425 / 2.0
    gap = sell_back.npv(market) - classic.npv(market)
    print(f"one coupon           {coupon:.2f}")
    print(f"cash legs differ by  {gap:.2f}")
    print(f"packages             {hlx.bonds.package_value(classic, market):.2f}  {hlx.bonds.package_value(sell_back, market):.2f}")
    repo     6350.63
    reverse  -6350.63
    one coupon           212500.00
    cash legs differ by  209196.80
    packages             6350.63  6267.45

    The repo and the reverse hold the same value with opposite signs. The buy/sell-back's cash leg is worth just under one coupon more, because the buyer keeps the coupon and lowers the repurchase price to match.

    How it's priced

    Cash CC is lent against nominal NN of a bond at price PP per 100, with margin ratio mm:

    C=N P100 m.C = \frac{N\, P}{100\, m}.

    At the end date TT the cash comes back with interest at the repo rate rr over the accrual fraction α\alpha. The borrower's value is the cash received at the start date tst_s less the repayment, both discounted on the collateral's repo curve DrepoD_{\text{repo}}. Once the repo has started, only the repayment is left:

    Vrepo=C Drepo(ts)−C (1+r α) Drepo(T) before ts,Vrepo=−C (1+r α) Drepo(T) after it,Vreverse=−Vrepo.V_{\text{repo}} = C\,D_{\text{repo}}(t_s) - C\,(1 + r\,\alpha)\, D_{\text{repo}}(T) \ \text{before } t_s, \qquad V_{\text{repo}} = - C\,(1 + r\,\alpha)\, D_{\text{repo}}(T) \ \text{after it}, \qquad V_{\text{reverse}} = -V_{\text{repo}}.

    The repo curve adds the GC spread ss to the overnight curve DD in simple-rate space, 1/Drepo(t)=1/D(t)+s τ(t)1/D_{\text{repo}}(t) = 1/D(t) + s\,\tau(t), where τ(t)\tau(t) is the curve's own year fraction to tt, not the repo's accrual.

    The collateral required is mm times the exposure CC, the haircut is 1−1/m1 - 1/m, and the excess is the collateral's value less what's required. Why a repo is held to a margin is covered in how financing works.

    API

    Template.repo(start, end, *, side, collateral, price, ...) on a repo template such as templates.get("USD-GC-REPO"):

    Argument Type Default Meaning
    start date required when the cash moves
    end date or tenor str required the repurchase date, or a tenor from start; "open" books an open repo, ended on a business day's notice
    side "repo" or "reverse" required borrowing cash against the bond, or lending it
    collateral FixedRateBond required the bond
    price float required the collateral's price per 100 the cash is sized at
    rate float none the fixed repo rate; give this or spread
    spread float none a spread over the template's compounded overnight index, in place of rate; makes the repo floating
    nominal float none the bond's face; the cash follows from it, the price and the margin ratio
    cash float none the cash lent, in place of nominal
    margin_ratio float 1.0 collateral value over cash: 1.02 is 102% margin, a 1.96% haircut
    coupons "classic" or "buy_sell_back" "classic" who gets a coupon paid during the term
    repo_key str the template's GC key the repo curve the cash leg discounts on
    isin str none the collateral's ISIN: a repo curve filed under it prices first
    repo_group str none the collateral's repo group, such as a specialness bucket: tried after the ISIN, before the GC curve
    margin_basis "clean" or "dirty" the template's the price the margin ratio applies to
    interest_dates list[date] none pays interest at each date, compounding a floating repo each period
    fx_rate float none cash units per collateral unit, for a bond in another currency

    In Rust the same terms are RepoTerms::from_template(&template, side, start, end, RepoRate::Fixed(r), margin_ratio), then Repo::from_collateral(terms, bond, nominal, price) and .with_coupons(CouponTreatment::BuySellBack).

    Symbol Meaning Rust Python
    DrepoD_{\text{repo}} GC as a spread over the overnight curve RepoSpread::new, MarketContext::with_repo_spread curves.RepoSpreadCurve, MarketContext.with_repo_spread
    CC from NN cash following the collateral Repo::from_collateral Template.repo(.., nominal=)
    VV the cash leg's value AnyInstrument::npv Instrument.npv
    the package the cash leg with its collateral package_value bonds.package_value
    mm, the haircut, the excess the margin position repo_margin → RepoMargin bonds.repo_margin → dict

    bonds.repo_margin(repo, market, *, price=None) returns exposure, collateral_value, required, excess (negative is a call) and haircut. The collateral is priced off its curves, or at price per 100 if you give one.

    Defaults and limits

    • What the market needs: the currency's discount curve and a repo spread curve under the repo's key. Add the GC curve with with_repo_spread(currency, curve, "discount(USD)").
    • What the template holds: the currency, day count, settlement and the default GC key; see the repo conventions.
    • Rate: give exactly one of rate and spread.
    • Size: give exactly one of nominal and cash.
    • Haircut: the haircut is 1 - 1/margin_ratio, so 102% margin is a 1.96% haircut, not 2%.
    • Cross-currency: with fx_rate, the repo is classic, and a buy/sell-back or a package value is refused.

    See also

    • Source: crates/helixrs/src/instruments/repo.rs, crates/helixrs/src/statics/templates/repo.rs, crates/helixrs/src/market/bond_readings.rs
    • Related pages:
      • How financing works, the ideas behind repo and margin.
      • Value a repo and its haircut, step by step.
      • Repo spread curves, for DrepoD_{\text{repo}}.
      • Bond forward, for a bond carried on the same curve.
      • Repo conventions, for each template's terms.

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