Value a repo and its haircut
You'll book a three-month repo at 102% margin on a GC curve, value it, and read its margin position: the cash exposure, the collateral's value, what's required, the excess and the haircut.
Before you start
- helixio installed: see installing.
- SOFR quotes for the USD curve, and the GC spread to SOFR. The example states them inline.
- The collateral bond's terms, the repo's dates, rate, nominal, price and margin ratio.
Steps
- State the close and the SOFR quotes.
- Build the market with
with_recipe_quotes, then add general collateral as a repo spread curve over the discount curve withwith_repo_spread("USD", gc, "discount(USD)"). - Build the collateral bond with
products.fixed_rate_bond. - Book the repo on the
USD-GC-REPOtemplate with.repo(..., margin_ratio=1.02). The cash follows from the nominal, the price and the margin ratio. - Value it with
npv, and read the margin position withbonds.repo_margin(repo, market). - Work out the cash and the haircut from the terms, to compare.
The comments in the program match these steps.
Rust
use chrono::NaiveDate;
use helixrs::{
AnyInstrument, AnyTemplate, CollateralSpec, Curve, DayCountFraction, FixedRateBond, Frequency,
MarketContext, QuoteSheet, Repo, RepoRate, RepoSide, RepoSpread, RepoTerms, repo_margin, template,
};
type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
fn date(year: i32, month: u32, day: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
}
fn main() -> Outcome<()> {
// 1. The close and the SOFR quotes the market is built from.
let close = date(2026, 10, 1);
let usd_close = [
("USD-SOFR-OIS", "1M", 0.0391),
("USD-SOFR-OIS", "3M", 0.0402),
("USD-SOFR-OIS", "1Y", 0.0439),
("USD-SOFR-OIS", "2Y", 0.0459),
("USD-SOFR-OIS", "3Y", 0.0468),
("USD-SOFR-OIS", "4Y", 0.0469),
("USD-SOFR-OIS", "5Y", 0.0471),
("USD-SOFR-OIS", "7Y", 0.0475),
("USD-SOFR-OIS", "10Y", 0.0483),
("USD-SOFR-OIS", "15Y", 0.0494),
("USD-SOFR-OIS", "20Y", 0.0499),
("USD-SOFR-OIS", "30Y", 0.0492),
];
// 2. Build the market, with general collateral 10bp under SOFR as a repo
// spread curve over the USD discount curve.
let gc = RepoSpread::new(close, &[(date(2027, 10, 1), -0.0010)], DayCountFraction::Act360)?;
let market = MarketContext::as_of(close)
.with_recipe_quotes(&CollateralSpec::Currency("USD".parse()?), &QuoteSheet::from_rows(usd_close)?)?
.with_repo_spread("USD", Curve::RepoSpread(gc), "discount(USD)".parse()?);
// 3. The collateral: a ten-year 4.25% bond.
let bond = FixedRateBond::bullet(
"USD".parse()?,
date(2025, 8, 15),
date(2035, 8, 15),
0.0425,
Frequency::SemiAnnual,
DayCountFraction::ActActIcma,
100.0,
)?;
// 4. Book three months of repo on the USD GC template: 10 million of the bond
// at 99.5, at 4.00%, with a margin ratio of 1.02 (102% margin).
let (nominal, price, margin_ratio) = (10_000_000.0, 99.5, 1.02);
let AnyTemplate::Repo(gc_repo) = template("USD-GC-REPO")? else {
unreachable!("a repo template")
};
let start = date(2026, 10, 2);
let end = gc_repo.end_after(start, "3M".parse()?);
let terms = RepoTerms::from_template(&gc_repo, RepoSide::Repo, start, end, RepoRate::Fixed(0.0400), margin_ratio)?;
let repo = AnyInstrument::Repo(Box::new(Repo::from_collateral(terms, bond, nominal, price)?));
// 5. Value it, and read the margin position.
let margin = repo_margin(&repo, &market, None)?;
println!("npv {:.2}", repo.npv(&market)?);
for (field, value) in [
("exposure", margin.exposure),
("collateral_value", margin.collateral_value),
("required", margin.required),
("excess", margin.excess),
("haircut", margin.haircut),
] {
println!("{field:<17} {value:.6}");
}
// 6. Compare with the cash and the haircut worked out from the terms.
let cash = nominal * price / 100.0 / margin_ratio;
let haircut = 1.0 - 1.0 / margin_ratio;
println!("cash by hand {cash:.6}");
println!("haircut by hand {haircut:.6}");
Ok(())
}Python
from datetime import date
import helixio as hlx
# 1. The close and the SOFR quotes the market is built from.
SOFR_CLOSE_DATE = date(2026, 10, 1)
USD_SOFR_CLOSE = {
"USD-SOFR-OIS": {
"1M": 0.0391, "3M": 0.0402, "1Y": 0.0439, "2Y": 0.0459, "3Y": 0.0468, "4Y": 0.0469,
"5Y": 0.0471, "7Y": 0.0475, "10Y": 0.0483, "15Y": 0.0494, "20Y": 0.0499, "30Y": 0.0492,
},
}
# 2. Build the market, with general collateral 10bp under SOFR as a repo spread
# curve over the USD discount curve.
gc = hlx.curves.RepoSpreadCurve(SOFR_CLOSE_DATE, [(date(2027, 10, 1), -0.0010)])
market = (
hlx.MarketContext(SOFR_CLOSE_DATE)
.with_recipe_quotes("USD", USD_SOFR_CLOSE)
.with_repo_spread("USD", gc, "discount(USD)")
)
# 3. The collateral: a ten-year 4.25% bond.
bond = hlx.products.fixed_rate_bond(
"USD", date(2025, 8, 15), date(2035, 8, 15),
coupon=0.0425, frequency="Semiannual", day_count="ACT/ACT.ICMA",
)
# 4. Book three months of repo on the USD GC template: 10 million of the bond at
# 99.5, at 4.00%, with a margin ratio of 1.02 (102% margin).
NOMINAL, PRICE, MARGIN_RATIO = 10_000_000.0, 99.5, 1.02
repo = hlx.templates.get("USD-GC-REPO").repo(
date(2026, 10, 2), "3M", side="repo", collateral=bond, price=PRICE,
rate=0.0400, nominal=NOMINAL, margin_ratio=MARGIN_RATIO,
)
# 5. Value it, and read the margin position.
margin = hlx.bonds.repo_margin(repo, market)
print(f"npv {repo.npv(market):.2f}")
for field in ("exposure", "collateral_value", "required", "excess", "haircut"):
print(f"{field:<17} {margin[field]:.6f}")
# 6. Compare with the cash and the haircut worked out from the terms.
cash = NOMINAL * PRICE / 100.0 / MARGIN_RATIO
haircut = 1.0 - 1.0 / MARGIN_RATIO
print(f"cash by hand {cash:.6f}")
print(f"haircut by hand {haircut:.6f}")npv -2099.11
exposure 9754901.960784
collateral_value 9647684.081304
required 9950000.000000
excess -302315.918696
haircut 0.019608
cash by hand 9754901.960784
haircut by hand 0.019608
Check it worked
exposureequals the cash by hand: the collateral's value at the booked price, divided by the margin ratio.haircutis one minus one over the margin ratio. 102% margin is a 1.96% haircut, not 2%.requiredis the margin ratio times the exposure, andexcessiscollateral_valuelessrequired. A negative excess is a margin call of that size: here the bond priced off the curve is worth less than the 99.5 the cash was lent against.- To margin the collateral at a quoted price rather than off the curve, pass
price=torepo_margin.
Related
- Repo, the reference for every repo argument.
- How financing works, for why a repo is held to a margin.
- Repo spread curves, for the GC curve.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.