Mark a daily close
You'll keep one market across days: mark each close, publish the prints as they arrive, value trades on each day's own curves, and drop dates you no longer need.
Before you start
- helixio installed: see installing.
- Each day's quote sheet, and the fixings as they're published.
Steps
- Mark the first close with
with_fixingsandwith_recipe_quotes. - The next day, move the market with
on(next_day)and mark that day's sheet. The first day's snapshot stays. - When a print is published, pin the full set again with
with_fixings. Only the new print gets a new version, stamped with the day it became known. - Value on any date with
market.on(date), and drop a date withwithout(date).
The comments in the program match these steps.
Rust
use chrono::NaiveDate;
use helixrs::{CollateralSpec, FixingKey, FixingStore, MarketContext, QuoteSheet, Termination, template};
type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
/// Your first close, its SOFR swap quotes, and the prints you held 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),
];
const USD_SOFR_PRINTS: &[(&str, (i32, u32, u32), f64)] = &[
("USD-SOFR", (2026, 9, 28), 0.0390),
("USD-SOFR", (2026, 9, 29), 0.0388),
("USD-SOFR", (2026, 9, 30), 0.0390),
];
fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
}
/// The sheet with every quote moved by `shift`.
fn sheet(shift: f64) -> Outcome<QuoteSheet> {
let shifted = USD_SOFR_CLOSE.iter().map(|&(template, locator, quote)| (template, locator, quote + shift));
Ok(QuoteSheet::from_rows(shifted)?)
}
/// The prints you held at the close, and any published since.
fn prints(since: &[(&str, (i32, u32, u32), f64)]) -> Outcome<FixingStore> {
let observations: Vec<(FixingKey, NaiveDate, f64)> = USD_SOFR_PRINTS
.iter()
.chain(since)
.map(|(index, day, value)| (FixingKey::Typed((*index).to_owned()), date(*day), *value))
.collect();
Ok(FixingStore::from_observations_checked(&observations)?)
}
fn joined(dates: &[NaiveDate]) -> String {
dates.iter().map(NaiveDate::to_string).collect::<Vec<_>>().join(", ")
}
fn main() -> Outcome<()> {
let close = date(SOFR_CLOSE_DATE);
let next_day = date((2026, 10, 2));
let usd = CollateralSpec::Currency("USD".parse()?);
// Mark the first close. The next day, move the market on and mark that day's
// sheet, here every quote a basis point higher. The first snapshot is kept.
let market = MarketContext::as_of(close)
.with_fixings(prints(&[])?)
.with_recipe_quotes(&usd, &sheet(0.0)?)?
.on(next_day)
.with_recipe_quotes(&usd, &sheet(0.0001)?)?;
println!("dates {}", joined(&market.dates()));
println!("as of {}", market.reference_date());
// The 1 October print is published on the second day. Pin the full set
// again: only the new print gets a new version, known from the second day.
let market = market.with_fixings(prints(&[("USD-SOFR", SOFR_CLOSE_DATE, 0.0391)])?);
let versions = market.fixing_history().versions();
for (index, day, known, _, _) in &versions {
println!("{index} {day} known {known}");
}
// Value one swap on each date's own curves.
let swap = template("USD-SOFR-OIS")?
.swap(close)?
.with_maturity("5Y".parse::<Termination>()?)?
.with_notional(10_000_000.0)
.with_fixed_rate(0.0465)
.build()?;
let first = swap.npv(&market.clone().on(close))?;
let second = swap.npv(&market)?;
println!("npv on {close} {first:.2}");
println!("npv on {next_day} {second:.2}");
// Drop a date you no longer need. The others are left exactly as they were.
let trimmed = market.clone().without(close);
println!("dates {}", joined(&trimmed.dates()));
println!("npv on {next_day} {:.2}", swap.npv(&trimmed)?);
Ok(())
}Python
import datetime as dt
import helixio as hlx
# Your first close, its SOFR swap quotes, and the prints you held at it.
SOFR_CLOSE_DATE = dt.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,
},
}
USD_SOFR_PRINTS = [
("USD-SOFR", dt.date(2026, 9, 28), 0.0390),
("USD-SOFR", dt.date(2026, 9, 29), 0.0388),
("USD-SOFR", dt.date(2026, 9, 30), 0.0390),
]
NEXT_DAY = dt.date(2026, 10, 2)
# Mark the first close. The next day, move the market on and mark that day's
# sheet, here every quote a basis point higher. The first snapshot is kept.
next_sheet = {t: {k: v + 0.0001 for k, v in q.items()} for t, q in USD_SOFR_CLOSE.items()}
market = (
hlx.MarketContext(SOFR_CLOSE_DATE)
.with_fixings(USD_SOFR_PRINTS)
.with_recipe_quotes("USD", USD_SOFR_CLOSE)
.on(NEXT_DAY)
.with_recipe_quotes("USD", next_sheet)
)
print(f"dates {', '.join(d.isoformat() for d in market.dates())}")
print(f"as of {market.as_of()}")
# The 1 October print is published on the second day. Pin the full set again:
# only the new print gets a new version, known from the second day.
market = market.with_fixings([*USD_SOFR_PRINTS, ("USD-SOFR", SOFR_CLOSE_DATE, 0.0391)])
history = market.fixing_history().to_dict()
for index, day, known in zip(history["index"], history["date"], history["known_on"]):
print(f"{index} {day} known {known}")
# Value one swap on each date's own curves.
swap = (
hlx.templates.get("USD-SOFR-OIS")
.swap(SOFR_CLOSE_DATE)
.with_maturity("5Y")
.with_notional(10_000_000.0)
.with_fixed_rate(0.0465)
.build()
)
first = swap.npv(market.on(SOFR_CLOSE_DATE))
second = swap.npv(market)
print(f"npv on {SOFR_CLOSE_DATE} {first:.2f}")
print(f"npv on {NEXT_DAY} {second:.2f}")
# Drop a date you no longer need. The others are left exactly as they were.
trimmed = market.without(SOFR_CLOSE_DATE)
print(f"dates {', '.join(d.isoformat() for d in trimmed.dates())}")
print(f"npv on {NEXT_DAY} {swap.npv(trimmed):.2f}")dates 2026-10-01, 2026-10-02
as of 2026-10-02
USD-SOFR 2026-09-28 known 2026-10-01
USD-SOFR 2026-09-29 known 2026-10-01
USD-SOFR 2026-09-30 known 2026-10-01
USD-SOFR 2026-10-01 known 2026-10-02
npv on 2026-10-01 -26539.47
npv on 2026-10-02 -30774.05
dates 2026-10-02
npv on 2026-10-02 -30774.05
Check it worked
dates()lists every day you marked.- Each print is known from the day it was published, so the first day still sees only what it knew.
- The same swap values differently on each day's curves.
- After
without, the remaining dates value exactly as before.
Related
- The dated market, for why snapshots and print history work this way.
- Quote sheets and recipes, for each day's sheet.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.