How a market is built
This page explains what happens between a desk's quotes and a market you can price on: why a sheet is written the way a desk writes it, what a recipe knows, how a curve carries a year-end turn, what a data source is asked for, and why a book holds its own market. The reference pages in this section tell you what to call; this one tells you why it works that way.
The by-hand programs below read their quotes from the desk sheets; load them once and each program runs as shown.
Quote sheets and recipes
The front of a rates curve and the back of it are quoted in different instruments. For US dollar SOFR a desk sees deposits at the very front, swaps that each cover one policy meeting, quarterly futures quoted as prices, and overnight indexed swaps by tenor out to thirty years. Each instrument is dated by its own conventions, and each tells you about a different stretch of the curve.
A sheet records the quotes the way a desk writes them: under the template each one is quoted on,
at a locator in that template's own terms. A tenor such as "5Y" for deposits and swaps, a
decision date for meeting swaps, a contract code such as "MAR27" for futures, and a window such
as "MAR27 - JUN27" for forward swaps.
A recipe belongs to a collateral key: USD for trades collateralised in dollars, or
KRW-CSA-USD for won trades collateralised in dollars. It lists the curves that key needs, each in
segments from the short end out, with the template that quotes each segment and the role it plays.
The recipe keys catalog lists every key that ships.
A curve solved from twelve quotes has twelve pillars, one at the date each quoted instrument
reaches, and the solve makes every one of those instruments worth nothing at its quote. That is why
pricing each quote back on the solved market is the check that the market says what the sheet
said. For an overnight index collateralised in its own currency, the rate a coupon fixes on and the
rate collateral earns are the same rate, so the USD recipe solves one curve that both discounts
dollar cashflows and projects SOFR. Other keys solve several curves, and the recipe records which
segment solves which.
with_recipe_quotes does all of this in one call; the
quote sheets and recipes page shows that short way. The program
below takes the same ladder apart: it builds each rung by hand, solves them, and prints whether the
result is the very market the one-call version builds.
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()?);
let ladder = QuoteSheet::from_rows(USD_SOFR_LADDER.iter().copied())?;
// The recipe binds each quote to the instrument it quotes, unsolved.
let base = MarketContext::as_of(close);
let rungs = base.recipe_rungs(&usd, &ladder)?;
for rung in &rungs {
println!("{} {}", rung.pillar, rung.instrument.product_name());
}
// Solving the rungs pins the same curves, under the same roles.
let by_hand = base.with_curve_helpers(&rungs)?;
let short_way = base.with_recipe_quotes(&usd, &ladder)?;
let read = |market: &MarketContext| -> Outcome<f64> {
let (curve, _) = market.curve(&"discount(USD)".parse()?).expect("the USD curve");
Ok(curve.view::<f64>().discount(date((2031, 10, 1)))?)
};
let discount_5y = read(&by_hand)?;
println!("5Y discount factor {discount_5y:.12}");
println!(
"same market as with_recipe_quotes {}",
if by_hand == short_way { "yes" } else { "no" }
);
Ok(())
}import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_LADDER
# The recipe binds each quote to the instrument it quotes, unsolved.
base = hlx.MarketContext(SOFR_CLOSE_DATE)
rungs = base.recipe_rungs("USD", USD_SOFR_LADDER)
for rung in rungs:
print(f"{rung.pillar()} {rung.instrument().product()}")
# Solving the rungs pins the same curves, under the same roles.
by_hand = base.with_curve_helpers(rungs)
short_way = base.with_recipe_quotes("USD", USD_SOFR_LADDER)
discount_5y = by_hand.curve("discount(USD)").discount(SOFR_CLOSE_DATE.replace(year=2031))
print(f"5Y discount factor {discount_5y:.12f}")
print(f"same market as with_recipe_quotes {'yes' if by_hand == short_way else 'no'}")2026-10-08 MoneyMarketDeposit
2026-12-10 OvernightIndexedSwap
2027-06-16 RateFuture
2027-09-15 RateFuture
2028-10-10 OvernightIndexedSwap
2031-10-08 OvernightIndexedSwap
2036-10-08 OvernightIndexedSwap
5Y discount factor 0.791631240666
same market as with_recipe_quotes yes
Curve helpers
When no shipped recipe describes the curve you need, you build it from rungs yourself. Each rung is an instrument on a template, struck at its quote and placed at the date it carries information to: a deposit at its maturity, a meeting swap at the next decision, a future at the end of its reference quarter, a swap at its final payment.
Rungs are grouped by the curve each one solves. A rung on an overnight index solves the currency's discount curve and that index's projection as one curve; a rung on a term index solves that index's projection.
Turns are the reason window rungs exist. At a year end, banks shrink their balance sheets to report them, and overnight funding briefly costs more. A desk quotes that premium as a rate over the few days across the year end. A smooth curve would spread the premium over months on either side. A curve that carries the turn needs a pillar where the turn starts and one where it ends: the curve holds its forward flat between neighbouring pillars, so the days between those two carry the turn's rate and nothing else, and the turn shows as a step up and back down.
The curve helpers page lists the rung kinds, and build a curve from your own rungs walks through doing it.
Data sources
A desk's market data lives elsewhere: a vendor terminal, a database of closes, a file a middle-office process writes. Which quotes a book needs depends on the book: the curves its trades discount and project on, through their recipes, and the fixings its coupons have already reset against. A data source removes the work of writing that list by hand for every book.
A source answers a demand. For one date, the demand names every template a recipe needs quotes on (the whole template, because a recipe describes a curve's shape, not a list of points), every print a valuation reads, every FX spot the book converts through, and any curve no recipe builds, which no source can be asked for.
Fetching is three steps: work out the demand, ask the source, and install the answer. A source leaves out what it doesn't hold rather than failing. Installing solves the recipes' curves from the answered quotes and adds the answered prints to the history, which is the same solve as marking a market from a sheet, so a fetched market and a sheet-marked market built from the same numbers are the same market. Anything the demand named and the answer didn't carry is a gap, and a snapshot with gaps is refused, naming every gap at once. A refused fetch changes nothing: the book keeps the market it had.
How a source plugs in, and why the market asks rather than the caller, is covered in how market data reaches a market. The data sources page fetches the short way. The program below installs the same answer by hand, then prices a seasoned trade on it and on the one-call market, so you can see the two values agree.
use chrono::NaiveDate;
use helixio_docs_sheets::{SOFR_CLOSE_DATE, Sheet, USD_SOFR_LADDER, USD_SOFR_PRINTS};
use helixrs::{
Book, CollateralSpec, FixingKey, FixingStore, Locator, MarketContext, MarketData, QuoteSheet,
StripQuotes, Termination, 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 strip(sheet: Sheet, template: &str) -> Outcome<Vec<(Locator, f64)>> {
sheet
.iter()
.filter(|(t, _, _)| *t == template)
.map(|(_, locator, quote)| Ok((Locator::parse(locator)?, *quote)))
.collect()
}
fn main() -> Outcome<()> {
let close = date(SOFR_CLOSE_DATE);
let seasoned = template("USD-SOFR-OIS")?
.swap(date((2026, 9, 24)))?
.with_maturity("5Y".parse::<Termination>()?)?
.with_notional(50_000_000.0)
.with_fixed_rate(0.0466)
.build()?;
let book = Book::new().add("SOFR-5Y", seasoned.clone(), 1.0);
// An offline market still says what the book would need.
let offline = MarketContext::as_of(close);
let demand = offline.demand(&book)?;
println!("strips {}, fixings {}", demand.strips.len(), demand.fixings.len());
// The answer, built from the desk's sheet: every pillar of every strip named,
// and the prints.
let mut strips = Vec::new();
for asked in &demand.strips {
strips.push(StripQuotes {
template: asked.template.clone(),
observed: close,
entries: strip(USD_SOFR_LADDER, &asked.template)?,
});
}
let fixings = USD_SOFR_PRINTS
.iter()
.map(|(series, day, value)| ((*series).to_owned(), date(*day), *value))
.collect();
let data = MarketData {
strips,
fixings,
fx: Vec::new(),
};
// Installing the answer solves the curves the recipe names and pins the prints.
let market = offline.with_market_data(&demand, &data)?;
let npv = seasoned.npv(&market)?;
println!("npv {npv:.2}");
// The same answer through `with_recipe_quotes` and pinned prints is the same
// market, so the value agrees bit for bit.
let sheet = QuoteSheet::from_rows(USD_SOFR_LADDER.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 direct = MarketContext::as_of(close)
.with_fixings(FixingStore::from_observations_checked(&prints)?)
.with_recipe_quotes(&CollateralSpec::Currency("USD".parse()?), &sheet)?;
println!("direct {:.2}", seasoned.npv(&direct)?);
Ok(())
}import datetime as dt
import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_LADDER, USD_SOFR_PRINTS
seasoned = (
hlx.templates.get("USD-SOFR-OIS")
.swap(dt.date(2026, 9, 24))
.with_maturity("5Y")
.with_notional(50_000_000.0)
.with_fixed_rate(0.0466)
.build()
)
book = hlx.Book().add("SOFR-5Y", seasoned, 1.0)
# An offline market still says what the book would need.
offline = hlx.MarketContext(SOFR_CLOSE_DATE)
demand = offline.demand(book)
print(f"strips {len(demand.strips.to_dict()['template'])}, fixings {len(demand.fixings.to_dict()['date'])}")
# The answer, built from the desk's sheet: every pillar of every strip named,
# and the prints.
data = hlx.MarketData(
strips=[
(template, SOFR_CLOSE_DATE, list(USD_SOFR_LADDER[template].items()))
for template in demand.strips.to_dict()["template"]
],
fixings=list(USD_SOFR_PRINTS),
)
# Installing the answer solves the curves the recipe names and pins the prints.
market = offline.with_market_data(demand, data)
npv = seasoned.npv(market)
print(f"npv {npv:.2f}")
# The same answer through `with_recipe_quotes` and pinned prints is the same
# market, so the value agrees bit for bit.
direct = (
hlx.MarketContext(SOFR_CLOSE_DATE)
.with_fixings(USD_SOFR_PRINTS)
.with_recipe_quotes("USD", USD_SOFR_LADDER)
)
print(f"direct {seasoned.npv(direct):.2f}")strips 4, fixings 3
npv -102966.25
direct -102966.25
Books carry their market
A desk asks its book the same questions every day, about the same trades, against a market that changes. Passing the market into every call invites passing the wrong one: yesterday's curves, or a market missing the prints a seasoned trade needs. A book that carries its market, and is asked only for a date, reads the right snapshot every time. A date with no snapshot is refused, and the refusal leaves the book's market as it was; the dated market explains how those snapshots are kept.
The book's value is just its trades' values on that snapshot, weighted by quantity. Its DV01 is the change in that value for a one basis point rise in rates; a receiver of fixed loses when rates rise, so a book that receives fixed on more duration than it pays has a negative DV01. Each trade values in its own currency, and a book with trades in several currencies sums them only in a reporting currency you give it, converting each at the snapshot's spot.
The books carry their market page values a book the short way. The program below sums the same book trade by trade, by hand, and prints the book's own value beside the sum.
use chrono::NaiveDate;
use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE, USD_SOFR_PRINTS};
use helixrs::{
Book, CollateralSpec, FixingKey, FixingStore, MarketContext, QuoteSheet, Side,
Termination, 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 sofr = template("USD-SOFR-OIS")?;
let seasoned = sofr
.swap(date((2026, 9, 24)))?
.with_maturity("5Y".parse::<Termination>()?)?
.with_notional(50e6)
.with_fixed_rate(0.0466)
.build()?;
let fresh = sofr
.swap(close)?
.with_maturity("2Y".parse::<Termination>()?)?
.with_notional(25e6)
.with_side(Side::Payer)
.with_fixed_rate(0.0455)
.build()?;
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 market = MarketContext::as_of(close)
.with_fixings(FixingStore::from_observations_checked(&prints)?)
.with_recipe_quotes(&CollateralSpec::Currency("USD".parse()?), &sheet)?;
let book = Book::new()
.add("SOFR-5Y-RCV", seasoned, 1.0)
.add("SOFR-2Y-PAY", fresh, 1.0)
.attach_market(market.clone());
// Each trade against the date's snapshot, times its quantity.
let snapshot = market.on(close);
let mut total = 0.0;
for trade in book.trades() {
let value = trade.instrument.npv(&snapshot)? * trade.quantity;
println!("{:<12} {value:.2}", trade.id);
total += value;
}
println!("{:<12} {total:.2}", "sum");
println!("{:<12} {:.2}", "book npv", book.npv(close)?);
Ok(())
}import datetime as dt
import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE, USD_SOFR_PRINTS
sofr = hlx.templates.get("USD-SOFR-OIS")
seasoned = sofr.swap(dt.date(2026, 9, 24)).with_maturity("5Y").with_notional(50e6).with_fixed_rate(0.0466).build()
fresh = sofr.swap(SOFR_CLOSE_DATE).with_maturity("2Y").with_notional(25e6).with_side("payer").with_fixed_rate(0.0455).build()
market = hlx.MarketContext(SOFR_CLOSE_DATE).with_fixings(USD_SOFR_PRINTS).with_recipe_quotes("USD", USD_SOFR_CLOSE)
book = hlx.Book().add("SOFR-5Y-RCV", seasoned, 1.0).add("SOFR-2Y-PAY", fresh, 1.0)
book.attach_market(market)
# Each trade against the date's snapshot, times its quantity.
snapshot = market.on(SOFR_CLOSE_DATE)
total = 0.0
for trade_id, instrument, quantity in book.trades():
value = instrument.npv(snapshot) * quantity
print(f"{trade_id:<12} {value:.2f}")
total += value
print(f"{'sum':<12} {total:.2f}")
print(f"{'book npv':<12} {book.npv(as_of=SOFR_CLOSE_DATE):.2f}")SOFR-5Y-RCV -103160.20
SOFR-2Y-PAY 18970.81
sum -84189.39
book npv -84189.39
Equity spots and vols
The rates part of an equity market is the curve the trade's currency discounts on, built the same way as any other recipe curve. The equity part is a spot price per share and, for options, a vol surface quoted on the listed expiries and strikes. A desk marks both from the exchange close; the examples state them as data, because no published close sits behind them.
Between strikes the surface interpolates linearly in vol; between expiries it interpolates linearly in total variance, so the interpolation adds no calendar arbitrage the quotes don't already have; beyond every edge it is flat. It is the same listed-option surface the bond future option page uses, keyed by an equity name instead of a contract. The equity spots and vols page lists its arguments, and load a vol grid loads one.
See also
- Source:
crates/helixrs/src/market/store.rs,crates/helixrs/src/trading.rs - Related pages:
- Quote sheets and recipes, Curve helpers, Data sources, Books carry their market and Equity spots and vols, the reference pages for this section.
- The dated market, for snapshots across several dates.
- How market data reaches a market, for the provider design.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.