Your first trade
In about ten minutes you'll price an interest rate swap from start to finish: you'll turn a desk's quotes into a market, book a five-year swap, value it, find the rate that makes it worth nothing, and put it in a book that reports its risk.
Before you start
You need helixio installed in Rust or Python; Install helixio and check it runs takes a couple of minutes. You don't need any market data: the program below carries the twelve swap quotes it uses.
It helps to know what an interest rate swap is. One side pays a fixed rate, the other pays a floating rate that follows an index. Here the index is SOFR, the US dollar overnight rate, compounded over each period.
Steps
1. Run the program
Copy this program into a file and run it. Its comments are numbered; each step below takes one numbered part and the lines it prints.
use chrono::NaiveDate;
use helixrs::presets;
use helixrs::{
Book, CollateralSpec, MarketContext, QuoteSheet, Termination, report_records, template,
};
type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
/// The close you mark the market at, and the desk's SOFR swap quotes at it.
const SOFR_CLOSE_DATE: (i32, u32, u32) = (2026, 10, 1);
const USD_SOFR_CLOSE: &[(&str, &str, f64)] = &[
("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),
];
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);
// Step 1: mark the market. The USD recipe knows that a SOFR swap discounts and
// projects on one curve, and solves that curve so every quote prices to par.
let usd = CollateralSpec::Currency("USD".parse()?);
let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
let market = MarketContext::as_of(close).with_recipe_quotes(&usd, &sheet)?;
for (role, key) in market.contents() {
println!("{role:<11} {key}");
}
let (discount, _) = market.curve(&"discount(USD)".parse()?).expect("the USD curve");
let pillars = discount.parameter_count();
println!("pillars {pillars}");
// Step 2: draft a swap. The template holds what a desk never restates:
// calendars, day counts, payment lag, the floating index. You say what is
// particular to this trade: its start, its length, its size and its rate.
let sofr = template("USD-SOFR-OIS")?;
let swap = sofr
.swap(close)?
.with_maturity("5Y".parse::<Termination>()?)?
.with_notional(10_000_000.0)
.with_fixed_rate(0.0450)
.build()?;
let dates = swap.contract_dates();
let product = swap.product_name().to_string();
println!("product {product}");
println!("effective {}", dates[0].1);
println!("termination {}", dates[1].1);
// Step 3: value it, and read the rate that would make it worth nothing.
let npv = swap.npv(&market)?;
let par = swap.par_rate(&market)?;
println!("npv {npv:.2}");
println!("par rate {par:.8}");
// Step 4: let the market set the rate. A swap struck at par is worth nothing
// on the market it was struck on.
let fair = sofr
.swap(close)?
.with_maturity("5Y".parse::<Termination>()?)?
.with_notional(10_000_000.0)
.at_par(&market)?;
let fair_npv = fair.npv(&market)?;
println!("struck at {:.8}", fair.par_rate(&market)?);
println!("its npv {:.2}", fair_npv.abs());
// Step 5: put it in a book. The book carries its market, so its reports only
// need the date to value at.
let book = Book::new().add("SOFR-5Y", swap, 1.0).attach_market(market);
let total = book.npv(close)?;
let rows = report_records(&book.report(&[presets::DV01], close)?);
let dv01: f64 = rows["value"].numbers().expect("numbers").iter().sum();
println!("book npv {total:.2}");
println!("book dv01 {dv01:.2}");
Ok(())
}from datetime import date
import helixio as hlx
# The close you mark the market at, and the desk's SOFR swap quotes at it.
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,
},
}
# Step 1: mark the market. The USD recipe knows that a SOFR swap discounts and
# projects on one curve, and solves that curve so every quote prices to par.
market = hlx.MarketContext(SOFR_CLOSE_DATE).with_recipe_quotes("USD", USD_SOFR_CLOSE)
for role, key in market.contents():
print(f"{role:<11} {key}")
pillars = market.curve("discount(USD)").parameter_count()
print(f"pillars {pillars}")
# Step 2: draft a swap. The template holds what a desk never restates:
# calendars, day counts, payment lag, the floating index. You say what is
# particular to this trade: its start, its length, its size and its rate.
sofr = hlx.templates.get("USD-SOFR-OIS")
swap = (
sofr.swap(SOFR_CLOSE_DATE)
.with_maturity("5Y")
.with_notional(10_000_000.0)
.with_fixed_rate(0.0450)
.build()
)
dates = dict(swap.contract_dates())
print(f"product {swap.product()}")
print(f"effective {dates['effective']}")
print(f"termination {dates['termination']}")
# Step 3: value it, and read the rate that would make it worth nothing.
npv = swap.npv(market)
par = swap.par_rate(market)
print(f"npv {npv:.2f}")
print(f"par rate {par:.8f}")
# Step 4: let the market set the rate. A swap struck at par is worth nothing
# on the market it was struck on.
fair = sofr.swap(SOFR_CLOSE_DATE).with_maturity("5Y").with_notional(10_000_000.0).at_par(market)
fair_npv = fair.npv(market)
print(f"struck at {fair.par_rate(market):.8f}")
print(f"its npv {abs(fair_npv):.2f}")
# Step 5: put it in a book. The book carries its market, so its reports only
# need the date to value at.
book = hlx.Book().add("SOFR-5Y", swap, 1.0)
book.attach_market(market)
total = book.npv(as_of=SOFR_CLOSE_DATE)
rows = book.report([hlx.risk.rates.DV01], as_of=SOFR_CLOSE_DATE).to_dict()
dv01 = sum(rows["value"])
print(f"book npv {total:.2f}")
print(f"book dv01 {dv01:.2f}")It prints:
discount USD
projection USD-SOFR
pillars 12
product OvernightIndexedSwap
effective 2026-10-05
termination 2031-10-05
npv -92888.14
par rate 0.04710000
struck at 0.04710000
its npv 0.00
book npv -92888.14
book dv01 -4537.06
2. Mark the market (part 1)
A quote sheet maps a template, here USD-SOFR-OIS, to quotes by tenor. The USD recipe knows
which curves those quotes build: for SOFR, one curve both discounts US dollar cashflows and
projects SOFR fixings. with_recipe_quotes solves that curve so every quote on the sheet prices
back to par. That's why you see one discount entry, one projection entry, and twelve pillars, one
per quote.
3. Draft a swap from its template (part 2)
A template holds what a desk never restates for each trade: calendars, day counts, payment lag and the floating index. You only say what's particular to this trade: when it starts, how long it runs, its size and its fixed rate. Starting on the close date means starting at spot, two business days later, which is why the swap is effective on 5 October.
4. Value it (part 3)
The swap receives fixed by default. Its value is negative because it receives 4.50% while the market's five-year rate is higher. That market rate is the swap's par rate, and it equals the sheet's five-year quote: a five-year swap starting at spot is one of the instruments the curve was solved to reprice.
5. Strike it at par (part 4)
Instead of choosing a rate, let the market choose: at_par strikes the swap at its par rate. A
trade struck at par is worth nothing on the market it was struck on, so its value prints as
zero.
6. Put it in a book (part 5)
A book holds trades and, once you attach one, their market. After that its reports only need the date to value at. Here it reports its value and its DV01, the change in value for a one basis point rise in rates.
What you built
You went from twelve quotes to a valued, risk-reported trade:
- The market has a USD discount curve and a SOFR projection, with one pillar per quote.
- The par rate equals the sheet's five-year quote. If yours differs, the swap's dates don't match the quote's, usually because it didn't start at spot.
- The swap struck at par is worth nothing, the book is worth exactly what its one swap is worth, and the DV01 is negative, because a receiver of fixed loses when rates rise.
Next steps
- The mental model names each piece you just used and how they fit.
- Price a swap from today's quotes does the same job with your own quotes.
- Overnight indexed swap lists every argument the swap takes.
- Books carry their market values a book on more than one date.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.