Quote sheets and recipes
This page lists what you need to turn a desk's quote sheet into a market in one call: how a sheet is written, which recipe reads it, the arguments the call takes, and what it refuses. Every later page builds its market this way.
Quick example
The example reads its quotes from the desk sheets; load them once and it runs as shown. It lists the shipped recipes, marks a USD market from every segment of the SOFR close, prices each quote back, and shows a template the recipe doesn't list being refused.
use std::collections::BTreeSet;
use chrono::NaiveDate;
use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_LADDER};
use helixrs::{CollateralSpec, MarketContext, QuoteSheet, 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 close = date(SOFR_CLOSE_DATE);
let usd = CollateralSpec::Currency("USD".parse()?);
// A bare market already knows how to build every curve helixio ships a recipe
// for. Each row is one segment of one curve: which template quotes it, and in
// which role.
let base = MarketContext::as_of(close);
let rows = base.recipes();
let (key, curve, role, template) = (
rows["key"].text().expect("text"),
rows["curve"].text().expect("text"),
rows["role"].text().expect("text"),
rows["template"].text().expect("text"),
);
let keys: BTreeSet<&str> = key.iter().map(String::as_str).collect();
println!("recipe keys {}", keys.len());
for i in 0..rows.len() {
if key[i] == "USD" {
println!("USD {:<17} {:<12} {}", curve[i], role[i], template[i]);
}
}
// The sheet names every segment the USD recipe builds from, short end first: a
// deposit at a tenor, a policy meeting at its decision date, futures at their
// contract codes (quoted as prices), and swaps at their tenors.
let ladder = QuoteSheet::from_rows(USD_SOFR_LADDER.iter().copied())?;
let market = base.clone().with_recipe_quotes(&usd, &ladder)?;
let quotes = USD_SOFR_LADDER.len();
let (discount, _) = market.curve(&"discount(USD)".parse()?).expect("the USD curve");
println!("quotes {quotes}");
println!("parameters {}", discount.parameter_count());
// Each quote became a rung: an instrument struck at that quote. The curve was
// solved so that every rung is worth nothing on it — so pricing each one back
// is the check that the market says what the sheet said.
let mut worst: f64 = 0.0;
for rung in base.recipe_rungs(&usd, &ladder)? {
let value = rung.instrument.npv(&market)?;
worst = worst.max(value.abs());
println!("{} quote {:.6}", rung.pillar, rung.quote);
}
println!("largest rung value per unit notional below 1e-12: {}", if worst < 1e-12 { "yes" } else { "no" });
// A template the recipe does not list is refused, and the refusal names the
// templates it does list, so a mistyped sheet fails where it is written.
let stray = QuoteSheet::from_rows([("USD-LIBOR-3M", "5Y", 0.05)])?;
let refused = base
.with_recipe_quotes(&usd, &stray)
.expect_err("an unlisted template must be refused");
let names_listed = refused.to_string().contains("USD-SOFR-OIS");
println!(
"refused, naming the listed USD-SOFR-OIS: {}",
if names_listed { "yes" } else { "no" }
);
Ok(())
}import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_LADDER
# A bare market already knows how to build every curve helixio ships a recipe
# for. Each row is one segment of one curve: which template quotes it, and in
# which role.
base = hlx.MarketContext(SOFR_CLOSE_DATE)
rows = base.recipes().to_dict()
keys = sorted(set(rows["key"]))
print(f"recipe keys {len(keys)}")
for key, curve, role, template in zip(rows["key"], rows["curve"], rows["role"], rows["template"]):
if key == "USD":
print(f"USD {curve:<17} {role:<12} {template}")
# The sheet names every segment the USD recipe builds from, short end first: a
# deposit at a tenor, a policy meeting at its decision date, futures at their
# contract codes (quoted as prices), and swaps at their tenors.
market = base.with_recipe_quotes("USD", USD_SOFR_LADDER)
quotes = sum(len(segment) for segment in USD_SOFR_LADDER.values())
curve = market.curve("discount(USD)")
print(f"quotes {quotes}")
print(f"parameters {curve.parameter_count()}")
# Each quote became a rung: an instrument struck at that quote. The curve was
# solved so that every rung is worth nothing on it — so pricing each one back
# is the check that the market says what the sheet said.
worst = 0.0
for rung in base.recipe_rungs("USD", USD_SOFR_LADDER):
value = rung.instrument().npv(market)
worst = max(worst, abs(value))
print(f"{rung.pillar()} quote {rung.quote():.6f}")
print(f"largest rung value per unit notional below 1e-12: {'yes' if worst < 1e-12 else 'no'}")
# A template the recipe does not list is refused, and the refusal names the
# templates it does list, so a mistyped sheet fails where it is written.
try:
base.with_recipe_quotes("USD", {"USD-LIBOR-3M": {"5Y": 0.05}})
except ValueError as error:
unlisted = str(error)
print(f"refused, naming the listed USD-SOFR-OIS: {'yes' if 'USD-SOFR-OIS' in unlisted else 'no'}")
else:
raise AssertionError("an unlisted template must be refused")recipe keys 32
USD discount deposit USD-SOFR-DEPO
USD discount policy USD-SOFR-MeetingOIS
USD discount futures USD-SOFR-3M-FUT
USD discount swaps USD-SOFR-OIS
USD projection(SOFR) telescoping
quotes 7
parameters 7
2026-10-08 quote 0.038900
2026-12-10 quote 0.038800
2027-06-16 quote 0.043500
2027-09-15 quote 0.044500
2028-10-10 quote 0.045900
2031-10-08 quote 0.047100
2036-10-08 quote 0.048300
largest rung value per unit notional below 1e-12: yes
refused, naming the listed USD-SOFR-OIS: yes
Each quote prices back to nothing on the market it marked, and the curve has one pillar per quote.
How it's priced
Each quote is the value at which its instrument is fair: a fixed rate at which a par swap is worth nothing, or the price that fixes a future's rate . Writing for the instrument's value per unit notional, struck at its quote, the solved curve satisfies
With quotes and pillars the solve is exact to its tolerance, so pricing each rung back is the check that the market says what the sheet said. Why a desk quotes the front and the back of a curve in different instruments, and why SOFR uses one curve for two roles, is covered in how a market is built.
API
| Symbol | Meaning | Rust | Python |
|---|---|---|---|
| a quote at its locator | a (template, locator, quote) row of QuoteSheet::from_rows |
{template: {locator: quote}} |
|
| the recipe for a key | MarketContext::recipe, listed by MarketContext::recipes |
MarketContext.recipe, listed by MarketContext.recipes |
|
| the rung each quote becomes | MarketContext::recipe_rungs |
MarketContext.recipe_rungs |
|
| the solved curve, pinned under its roles | MarketContext::with_recipe_quotes |
MarketContext.with_recipe_quotes |
MarketContext.with_recipe_quotes(key, quotes, ...) takes:
| Argument | Type | Default | Meaning |
|---|---|---|---|
key |
str |
required | the collateral key whose recipe reads the sheet, such as "USD" or "KRW-CSA-USD" |
quotes |
dict[str, dict[Locator, float]] |
required | the sheet: quotes by template, then by locator. In Rust, a QuoteSheet |
notional |
float |
1.0 |
the notional each rung is built on |
interpolation |
Interpolation | str |
"loglinear" |
how the solved curve interpolates between pillars |
tension |
float | None |
None |
the tension of a tension spline, when that interpolation is chosen |
end_slopes |
tuple[float, float] | None |
None |
end slopes for a spline that takes them |
tolerance |
Tolerance | None |
None |
the solver's tolerance; None uses the library's |
A locator is written the way a desk writes it on a sheet:
| Locator | Example | Used for |
|---|---|---|
| tenor | "5Y" |
deposits, swaps |
| decision date | "2026-10-28" |
meeting swaps |
| contract code | "MAR27" |
futures |
| window | "MAR27 - JUN27", "2026-12-10 - 2026-12-31" |
forward swaps over a window |
Defaults and limits
- Keys: a bare market lists every recipe helixio ships with
recipes(), before it holds a curve. The recipe keys catalog lists them. - Unlisted templates are refused: a sheet naming a template the key's recipe doesn't list
raises
ValueErrorwhere it is written, naming the templates the recipe takes. - Inspecting the rungs:
recipe_rungs(key, quotes)returns the unsolved rungs, so you can inspect, drop or add one before solving them withwith_curve_helpers.with_recipe_quotes(key, quotes)is exactlywith_curve_helpers(recipe_rungs(key, quotes)). - Futures: a futures rung carries its rate, not its price, as the quote it reprices.
See also
- Source:
crates/helixrs/src/market/store.rs,crates/helixrs/src/calibration.rs,crates/helixrs/src/statics/recipes/spec.rs - Related pages:
- How a market is built, for what a recipe knows and how the two routes compare by hand.
- Recipe keys, for every key helixio ships.
- Curve helpers, for building the rungs yourself.
- Data sources, for a sheet fetched rather than stated.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.