Esc

    The API is under testing and may change before release.

    Report a book's risk

    You'll put trades in a book, see which risk measures the book can answer, and report its value, DV01 and par ladder in one call, reading one bucket of the ladder back.

    Before you start

    • helixio installed: see installing.
    • A market marked from quotes. A par ladder needs the curve's calibrating quotes, so mark it with with_recipe_quotes or a solve that keeps its ladder, not from bare factors.
    • The trades you hold. This guide books two SOFR swaps: a five-year receiver and a two-year payer.

    Steps

    1. Mark the market from the SOFR swap quotes with with_recipe_quotes.
    2. Book the trades from their template, add them to a Book, and attach the market.
    3. List what the book can answer with available_measures. A par measure appears only when every curve the trades read carries the quotes it was marked from.
    4. Ask for value, DV01, par DV01 and the par ladder in one report call, which prices the book once and reads every measure from that one pass.
    5. Pick a bucket out of the ladder by its label, the tenor of the quote it moves.

    The comments in the program match these steps.

    Rust
    use chrono::NaiveDate;
    use helixrs::presets;
    use helixrs::{Book, CollateralSpec, MarketContext, QuoteSheet, Side, Termination, report_records, template};
    
    type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
    
    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<()> {
        // Step 1: mark the market at the close from the SOFR swap quotes.
        let close = date(SOFR_CLOSE_DATE);
        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)?;
    
        // Step 2: put two swaps in a book and attach the market: receive 4.50% for five
        // years on 10 million, pay 4.70% for two years on 20 million.
        let sofr = template("USD-SOFR-OIS")?;
        let receiver = sofr
            .swap(close)?
            .with_maturity("5Y".parse::<Termination>()?)?
            .with_notional(10_000_000.0)
            .with_fixed_rate(0.0450)
            .build()?;
        let payer = sofr
            .swap(close)?
            .with_maturity("2Y".parse::<Termination>()?)?
            .with_notional(20_000_000.0)
            .with_fixed_rate(0.0470)
            .with_side(Side::Payer)
            .build()?;
        let book = Book::new().add("RCV-5Y", receiver, 1.0).add("PAY-2Y", payer, 1.0).attach_market(market);
    
        // Step 3: list the measures this book can answer on this market.
        let names: Vec<&str> = book.available_measures(close)?.iter().map(|preset| preset.name()).collect();
        println!("measures  {}", names.join(", "));
    
        // Step 4: report value, DV01, par DV01 and the par ladder in one call.
        let rows = report_records(&book.report(
            &[presets::NPV, presets::DV01, presets::PAR_DV01, presets::PAR_LADDER],
            close,
        )?);
        let measures = rows["measure"].text().expect("text");
        let labels = rows["label"].text().expect("text");
        let values = rows["value"].numbers().expect("numbers");
        for ((measure, label), value) in measures.iter().zip(labels).zip(values) {
            println!("{measure:<10} {label:<4} {value:>14.2}");
        }
    
        // Step 5: read one bucket back: the five-year quote's row of the ladder.
        let ladder: Vec<(&str, f64)> = measures
            .iter()
            .zip(labels)
            .zip(values)
            .filter(|((measure, _), _)| measure.as_str() == "PAR_LADDER")
            .map(|((_, label), value)| (label.as_str(), *value))
            .collect();
        let bucket = |tenor: &str| ladder.iter().find(|(label, _)| *label == tenor).map(|(_, v)| *v).expect("a ladder row");
        println!("5Y bucket {:.2}", bucket("5Y"));
    
        Ok(())
    }
    Python
    from datetime import date
    
    import helixio as hlx
    
    # Step 1: mark the market at the close from the SOFR swap quotes.
    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,
        },
    }
    market = hlx.MarketContext(SOFR_CLOSE_DATE).with_recipe_quotes("USD", USD_SOFR_CLOSE)
    
    # Step 2: put two swaps in a book and attach the market: receive 4.50% for five
    # years on 10 million, pay 4.70% for two years on 20 million.
    sofr = hlx.templates.get("USD-SOFR-OIS")
    receiver = sofr.swap(SOFR_CLOSE_DATE).with_maturity("5Y").with_notional(10_000_000.0).with_fixed_rate(0.0450).build()
    payer = (
        sofr.swap(SOFR_CLOSE_DATE)
        .with_maturity("2Y")
        .with_notional(20_000_000.0)
        .with_fixed_rate(0.0470)
        .with_side("payer")
        .build()
    )
    book = hlx.Book().add("RCV-5Y", receiver, 1.0).add("PAY-2Y", payer, 1.0)
    book.attach_market(market)
    
    # Step 3: list the measures this book can answer on this market.
    names = book.available_measures(as_of=SOFR_CLOSE_DATE).to_dict()["name"]
    print(f"measures  {', '.join(names)}")
    
    # Step 4: report value, DV01, par DV01 and the par ladder in one call.
    report = book.report(
        [hlx.risk.NPV, hlx.risk.rates.DV01, hlx.risk.rates.PAR_DV01, hlx.risk.rates.PAR_LADDER],
        as_of=SOFR_CLOSE_DATE,
    ).to_dict()
    for measure, label, value in zip(report["measure"], report["label"], report["value"]):
        print(f"{measure:<10} {label:<4} {value:>14.2f}")
    
    # Step 5: read one bucket back: the five-year quote's row of the ladder.
    ladder = {label: value for measure, label, value in zip(report["measure"], report["label"], report["value"]) if measure == "PAR_LADDER"}
    print(f"5Y bucket {ladder['5Y']:.2f}")
    measures  NPV, DV01, DV01_BUCKETED, DV01_BY_CURVE, DV01_LADDER, PAR_DV01, PAR_LADDER
    NPV                 -134623.92
    DV01                   -618.09
    PAR_DV01               -596.60
    PAR_LADDER 1M             0.15
    PAR_LADDER 3M            -0.01
    PAR_LADDER 1Y             3.59
    PAR_LADDER 2Y          3801.64
    PAR_LADDER 3Y             5.20
    PAR_LADDER 4Y             7.07
    PAR_LADDER 5Y         -4414.23
    PAR_LADDER 7Y             0.00
    PAR_LADDER 10Y            0.00
    PAR_LADDER 15Y            0.00
    PAR_LADDER 20Y            0.00
    PAR_LADDER 30Y            0.00
    5Y bucket -4414.23

    Check it worked

    • The ladder has one row per quote the curve was marked from, labelled by its tenor.
    • The ladder's rows sum to PAR_DV01.
    • The receiver's risk sits on the 5Y quote and is negative: it loses when rates rise. The payer's sits on the 2Y quote and is positive.
    • DV01 and PAR_DV01 differ. DV01 moves the curve's own node rates; PAR_DV01 moves the quotes the curve was marked from, so it reads in the units a desk hedges in.
    • If a par measure is missing from available_measures, or a report refuses it, the curve was pinned without its ladder: mark it from quotes instead.
    • Python reference, for every preset in hlx.risk and the columns a report returns.
    • Provenance, for why a curve without its ladder has no par risk.
    • Books carry their market, for attaching a market and valuing a book across days.

    From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.