Esc

    The API is under testing and may change before release.

    Repo spread curves

    This page lists what you need to mark repo financing spreads: general collateral (GC) against the overnight curve, and a bond trading special against GC. It covers the arguments, how spreads chain over a base, and the difference between a key's stored factor and the composed factor a financed trade reads.

    Quick example

    This marks GC and one special, chains them over the SOFR curve, and reads the special's composed factor, checking it against the chain formula below. The example reads its quotes from the desk sheets; load them once and it runs as shown.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{
        CollateralSpec, Curve, DayCountFraction, MarketContext, QuoteSheet, RepoSpread, YieldCurve,
    };
    
    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 reference = date(SOFR_CLOSE_DATE);
        let six_months = date((2027, 4, 1));
        let accrual = |d: NaiveDate| (d - reference).num_days() as f64 / 360.0;
    
        // GC trades five basis points under SOFR at one month and ten at one year; one
        // Treasury issue trades special, thirty basis points under GC.
        let gc = RepoSpread::new(
            reference,
            &[(date((2026, 11, 2)), -0.0005), (date((2027, 10, 1)), -0.0010)],
            DayCountFraction::Act360,
        )?;
        let special = RepoSpread::new(reference, &[(date((2027, 10, 1)), -0.0030)], DayCountFraction::Act360)?;
        println!("day count      {}", gc.day_count().code());
        println!("GC at 6M       {:.8}", gc.spread(six_months)?);
    
        // The special sits over GC and GC over the SOFR discount curve; the market
        // records each base.
        let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
        let market = MarketContext::as_of(reference)
            .with_recipe_quotes(&CollateralSpec::Currency("USD".parse()?), &sheet)?
            .with_repo_spread("USGC", Curve::RepoSpread(gc.clone()), "discount(USD)".parse()?)
            .with_repo_spread("UST-2035", Curve::RepoSpread(special.clone()), "repo(USGC)".parse()?);
        let over = |key: &str| market.repo_base(key).map(ToString::to_string).unwrap_or_default();
        println!("repo(USGC) over      {}", over("USGC"));
        println!("repo(UST-2035) over  {}", over("UST-2035"));
    
        // Each key stores its own spread: its factor alone is 1/(1 + s tau). The factor a
        // trade financed on the special reads is the chain resolved for pricing, and
        // `repo_discount` reads it: the spreads along the chain add to the base in 1/P.
        let read = |id: &str| -> Outcome<f64> {
            let (curve, _) = market.curve(&id.parse()?).expect("held");
            Ok(curve.view::<f64>().discount(six_months)?)
        };
        let stored = read("repo(USGC)")?;
        let base = read("discount(USD)")?;
        let special_6m = market.repo_discount("UST-2035", six_months)?;
        println!("SOFR factor 6M       {base:.10}");
        println!("special factor 6M    {special_6m:.10}");
        // The formula, composed by hand from the stored spreads, gives the same factor.
        let composed = 1.0
            / (1.0 / base + (gc.spread(six_months)? + special.spread(six_months)?) * accrual(six_months));
        println!("by hand              {composed:.10}");
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    REFERENCE = SOFR_CLOSE_DATE
    SIX_MONTHS = dt.date(2027, 4, 1)
    
    def accrual(d: dt.date) -> float:
        return (d - REFERENCE).days / 360.0
    
    # GC trades five basis points under SOFR at one month and ten at one year; one
    # Treasury issue trades special, thirty basis points under GC.
    gc = hlx.curves.RepoSpreadCurve(
        REFERENCE, [(dt.date(2026, 11, 2), -0.0005), (dt.date(2027, 10, 1), -0.0010)]
    )
    special = hlx.curves.RepoSpreadCurve(REFERENCE, [(dt.date(2027, 10, 1), -0.0030)])
    print(f"day count      {gc.day_count()}")
    print(f"GC at 6M       {gc.spread(SIX_MONTHS):.8f}")
    
    # The special sits over GC and GC over the SOFR discount curve; the market
    # records each base.
    market = (
        hlx.MarketContext(REFERENCE)
        .with_recipe_quotes("USD", USD_SOFR_CLOSE)
        .with_repo_spread("USGC", gc, "discount(USD)")
        .with_repo_spread("UST-2035", special, "repo(USGC)")
    )
    print(f"repo(USGC) over      {market.repo_base('USGC')}")
    print(f"repo(UST-2035) over  {market.repo_base('UST-2035')}")
    
    # Each key stores its own spread: its factor alone is 1/(1 + s tau). The factor a
    # trade financed on the special reads is the chain resolved for pricing, and
    # `repo_discount` reads it: the spreads along the chain add to the base in 1/P.
    base = market.curve("discount(USD)").discount(SIX_MONTHS)
    special_6m = market.repo_discount("UST-2035", SIX_MONTHS)
    print(f"SOFR factor 6M       {base:.10f}")
    print(f"special factor 6M    {special_6m:.10f}")
    # The formula, composed by hand from the stored spreads, gives the same factor.
    composed = 1.0 / (
        1.0 / base + (gc.spread(SIX_MONTHS) + special.spread(SIX_MONTHS)) * accrual(SIX_MONTHS)
    )
    print(f"by hand              {composed:.10f}")
    day count      ACT/360
    GC at 6M       -0.00072523
    repo(USGC) over      discount(USD)
    repo(UST-2035) over  repo(USGC)
    SOFR factor 6M       0.9790816261
    special factor 6M    0.9808903021
    by hand              0.9808903021

    How it's priced

    A repo spread s(t)s(t) is simple, linear between dated pillars and flat outside them, accruing on ACT/360 with year fraction α(t0,t)\alpha(t_0, t). Declared over a base with factor PbaseP_{\text{base}}, it composes as

    1P(t)=1Pbase(t)+s(t) α(t0,t).\frac{1}{P(t)} = \frac{1}{P_{\text{base}}(t)} + s(t)\, \alpha(t_0, t).

    A spread declared over another repo key adds to that key's spread, so along the chain special, GC, SOFR:

    1Pspecial(t)=1PSOFR(t)+(sGC(t)+sspecial(t)) α(t0,t).\frac{1}{P_{\text{special}}(t)} = \frac{1}{P_{\text{SOFR}}(t)} + \big(s_{\text{GC}}(t) + s_{\text{special}}(t)\big)\, \alpha(t_0, t).

    How GC and specials trade is covered in how curves work.

    API

    Symbol Meaning Rust Python
    s(t)s(t) a repo spread RepoSpread::new with a DayCountFraction, read with RepoSpread::spread curves.RepoSpreadCurve, read with curves.RepoSpreadCurve.spread
    base the key a spread is declared over MarketContext::with_repo_spread(key, curve, over), read with MarketContext::repo_base MarketContext.with_repo_spread(key, curve, base), read with MarketContext.repo_base
    1/(1+sα)1/(1 + s\alpha) a key's own stored factor MarketContext::curve on repo(KEY) MarketContext.curve on repo(KEY)
    Pspecial(t)P_{\text{special}}(t) the composed factor a financed trade reads MarketContext::repo_discount(key, date) MarketContext.repo_discount(key, date)

    curves.RepoSpreadCurve(...):

    Argument Type Default Meaning
    reference date required the spread's reference date
    pillars list[tuple[date, float]] required (date, simple spread) pairs
    day_count str or DayCountFraction "ACT/360" the money-market day count the spread accrues on

    curves.RepoSpreadCurve.from_term_rates(base, quotes, day_count="ACT/360") builds the spread over base that reprices term repos quoted as (maturity, simple rate).

    Defaults and limits

    !!! warning "A key's stored factor is not what a trade reads" Each key stores only its own spread: read alone with curve, its factor is 1/(1+s(t) α(t0,t))1 / (1 + s(t)\, \alpha(t_0, t)). The chain composes when a repo key is resolved for pricing. Read repo_discount for the factor a trade financed on that key sees.

    • Outside the pillars: linear between pillars, flat outside them.
    • Risk: each key's pillars report risk on the repo axis, so a move in SOFR, in GC and in a special's own mark each stay on their own axis.

    See also

    • Source: crates/helixrs/src/curves/spread.rs, crates/helixrs/src/market/store.rs
    • Related pages:
      • Spread curves, for a continuously compounded spread.
      • Day counts, for ACT/360.

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