Esc

    The API is under testing and may change before release.

    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

    1. Mark the first close with with_fixings and with_recipe_quotes.
    2. The next day, move the market with on(next_day) and mark that day's sheet. The first day's snapshot stays.
    3. 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.
    4. Value on any date with market.on(date), and drop a date with without(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.
    • 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.