Value a book and collect what failed
You'll value every trade in a book that can be valued, and get a list of the ones that couldn't, each with the market input it was missing, instead of stopping at the first failure.
Before you start
- helixio installed: see installing.
- A market and the trades you want to value.
Steps
- Build the market once.
- Value each trade in a book of its own, so one trade's failure can't stop the others.
- Catch the missing-input error (
MissingMarketInputErrorin Python; in Rust, a pricing error whosemissing_input()is set) and keep the trade id with what was missing. Let any other error stop the run: it means something other than missing data is wrong.
The comments in the program match these steps.
Rust
use chrono::NaiveDate;
use helixrs::{AnyInstrument, Book, CollateralSpec, MarketContext, QuoteSheet, Termination, template};
/// The close you value 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 swap(code: &str, traded: NaiveDate, rate: f64) -> Result<AnyInstrument, Box<dyn std::error::Error>> {
Ok(template(code)?
.swap(traded)?
.with_maturity("5Y".parse::<Termination>()?)?
.with_notional(1e6)
.with_fixed_rate(rate)
.build()?)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let close = date(SOFR_CLOSE_DATE);
// Three trades: a fresh SOFR swap, a EUR swap, and a SOFR swap traded a week
// before the close, which has already reset.
let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
let market = MarketContext::as_of(close).with_recipe_quotes(&CollateralSpec::Currency("USD".parse()?), &sheet)?;
let trades = [
("SOFR-5Y", swap("USD-SOFR-OIS", close, 0.0466)?),
("ESTR-5Y", swap("EUR-ESTR-OIS", close, 0.0200)?),
("SEASONED", swap("USD-SOFR-OIS", date((2026, 9, 24)), 0.0466)?),
];
// Value each trade in its own book. A missing input is kept; any other
// error still stops the run, because it means something else is wrong.
let mut valued = Vec::new();
let mut refused = Vec::new();
for (trade_id, instrument) in &trades {
let book = Book::new().add(*trade_id, instrument.clone(), 1.0).attach_market(market.clone());
match book.npv(close) {
Ok(value) => valued.push((*trade_id, value)),
Err(error) => match error.missing_input() {
Some((kind, key)) => refused.push((*trade_id, format!("{kind}({key})"))),
None => return Err(error.into()),
},
}
}
let valued_trades = valued.len();
println!("valued {valued_trades} of {} trades", trades.len());
for (trade_id, what) in &refused {
println!("refused {trade_id:<9} missing {what}");
}
Ok(())
}Python
import datetime as dt
import helixio as hlx
# The close you value at, and the desk's SOFR swap quotes 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,
},
}
# Three trades: a fresh SOFR swap, a EUR swap, and a SOFR swap traded a week
# before the close, which has already reset.
market = hlx.MarketContext(SOFR_CLOSE_DATE).with_recipe_quotes("USD", USD_SOFR_CLOSE)
sofr, estr = hlx.templates.get("USD-SOFR-OIS"), hlx.templates.get("EUR-ESTR-OIS")
trades = [
("SOFR-5Y", sofr.swap(SOFR_CLOSE_DATE).with_maturity("5Y").with_notional(1e6).with_fixed_rate(0.0466).build()),
("ESTR-5Y", estr.swap(SOFR_CLOSE_DATE).with_maturity("5Y").with_notional(1e6).with_fixed_rate(0.0200).build()),
("SEASONED", sofr.swap(dt.date(2026, 9, 24)).with_maturity("5Y").with_notional(1e6).with_fixed_rate(0.0466).build()),
]
# Value each trade in its own book. A missing input is caught and kept; any
# other error still stops the run, because it means something else is wrong.
valued, refused = [], []
for trade_id, instrument in trades:
book = hlx.Book().add(trade_id, instrument, 1.0)
book.attach_market(market)
try:
valued.append((trade_id, book.npv(as_of=SOFR_CLOSE_DATE)))
except hlx.MissingMarketInputError as error:
refused.append((trade_id, f"{error.kind}({error.key})"))
valued_trades = len(valued)
print(f"valued {valued_trades} of {len(trades)} trades")
for trade_id, what in refused:
print(f"refused {trade_id:<9} missing {what}")valued 1 of 3 trades
refused ESTR-5Y missing discount(EUR)
refused SEASONED missing fixing(USD-SOFR@2026-09-28)
Check it worked
- The trades the market covers are valued.
- Each refused trade names its missing input in the market's own spelling, such as
discount(EUR)for a curve orfixing(USD-SOFR@2026-09-28)for a print. - Add the missing curve or print and run again; the trade moves from refused to valued.
Related
- Errors, the reference for every error class and what it names.
- Books carry their market, for valuing a whole book at once.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.