Book trades from FpML
You'll read FpML confirmations into a book as your own side of the trade, with each trade discounting under its counterparty's CSA, and see what couldn't be read.
Before you start
- helixio installed: see installing.
- Your confirmations as text, in FpML 5 confirmation view. The example states one inline: a five-year EUR swap written for these docs, between two fictional banks.
- Your own party's id or LEI, as the documents state it.
- If you want trades discounted under their CSAs: a counterparty for each one, keyed by an id the documents state for it. See counterparties and CSA.
The FpML products the reader books are listed under supported products.
Steps
- Collect the documents as
(label, text)pairs. The label names a document in the refusals, so a file name works well. - Read them with
Book.from_fpml(documents, party=...), naming your own party by its id or LEI. Withoutparty, the first party in each document is yours. The trade id follows the party. - Pass
counterparties=with your counterparty master, so each trade discounts under its counterparty's CSA. - Read the refusals, which come back beside the book: one row per trade or document that couldn't be booked.
- If you'd rather stop at the first problem, pass
strict=True: it raises and books nothing.
The comments in the program match these steps.
Rust
use helixrs::fpml::{PartySelector, ReadOptions};
use helixrs::{Book, Counterparties, Counterparty, CsaSpec, market_data_needs};
// 1. The documents are (label, text) pairs. The label names a document in the
// refusals, so a file name is a good one.
const CONFIRMATION: &str = r#"<?xml version="1.0" encoding="utf-8"?>
<dataDocument xmlns="http://www.fpml.org/FpML-5/confirmation" fpmlVersion="5-14">
<trade>
<tradeHeader>
<partyTradeIdentifier>
<partyReference href="party1" />
<tradeId tradeIdScheme="https://example.com/trade-id">HX-IRS-0001</tradeId>
</partyTradeIdentifier>
<partyTradeIdentifier>
<partyReference href="party2" />
<tradeId tradeIdScheme="https://example.com/trade-id">HX-IRS-0002</tradeId>
</partyTradeIdentifier>
<tradeDate>2026-09-29</tradeDate>
</tradeHeader>
<swap>
<swapStream>
<payerPartyReference href="party1" />
<receiverPartyReference href="party2" />
<calculationPeriodDates id="floatingCalcPeriodDates">
<effectiveDate>
<unadjustedDate>2026-10-01</unadjustedDate>
<dateAdjustments>
<businessDayConvention>NONE</businessDayConvention>
</dateAdjustments>
</effectiveDate>
<terminationDate>
<unadjustedDate>2031-10-01</unadjustedDate>
<dateAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCenters id="primaryBusinessCenters">
<businessCenter>EUTA</businessCenter>
</businessCenters>
</dateAdjustments>
</terminationDate>
<calculationPeriodDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</calculationPeriodDatesAdjustments>
<calculationPeriodFrequency>
<periodMultiplier>6</periodMultiplier>
<period>M</period>
<rollConvention>1</rollConvention>
</calculationPeriodFrequency>
</calculationPeriodDates>
<paymentDates>
<calculationPeriodDatesReference href="floatingCalcPeriodDates" />
<paymentFrequency>
<periodMultiplier>6</periodMultiplier>
<period>M</period>
</paymentFrequency>
<payRelativeTo>CalculationPeriodEndDate</payRelativeTo>
<paymentDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</paymentDatesAdjustments>
</paymentDates>
<resetDates id="resetDates">
<calculationPeriodDatesReference href="floatingCalcPeriodDates" />
<resetRelativeTo>CalculationPeriodStartDate</resetRelativeTo>
<fixingDates>
<periodMultiplier>-2</periodMultiplier>
<period>D</period>
<dayType>Business</dayType>
<businessDayConvention>NONE</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
<dateRelativeTo href="resetDates" />
</fixingDates>
<resetFrequency>
<periodMultiplier>6</periodMultiplier>
<period>M</period>
</resetFrequency>
<resetDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</resetDatesAdjustments>
</resetDates>
<calculationPeriodAmount>
<calculation>
<notionalSchedule>
<notionalStepSchedule>
<initialValue>25000000.00</initialValue>
<currency currencyScheme="http://www.fpml.org/coding-scheme/external/iso4217">EUR</currency>
</notionalStepSchedule>
</notionalSchedule>
<floatingRateCalculation>
<floatingRateIndex>EUR-EURIBOR-Reuters</floatingRateIndex>
<indexTenor>
<periodMultiplier>6</periodMultiplier>
<period>M</period>
</indexTenor>
</floatingRateCalculation>
<dayCountFraction>ACT/360</dayCountFraction>
</calculation>
</calculationPeriodAmount>
</swapStream>
<swapStream>
<payerPartyReference href="party2" />
<receiverPartyReference href="party1" />
<calculationPeriodDates id="fixedCalcPeriodDates">
<effectiveDate>
<unadjustedDate>2026-10-01</unadjustedDate>
<dateAdjustments>
<businessDayConvention>NONE</businessDayConvention>
</dateAdjustments>
</effectiveDate>
<terminationDate>
<unadjustedDate>2031-10-01</unadjustedDate>
<dateAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</dateAdjustments>
</terminationDate>
<calculationPeriodDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</calculationPeriodDatesAdjustments>
<calculationPeriodFrequency>
<periodMultiplier>1</periodMultiplier>
<period>Y</period>
<rollConvention>1</rollConvention>
</calculationPeriodFrequency>
</calculationPeriodDates>
<paymentDates>
<calculationPeriodDatesReference href="fixedCalcPeriodDates" />
<paymentFrequency>
<periodMultiplier>1</periodMultiplier>
<period>Y</period>
</paymentFrequency>
<payRelativeTo>CalculationPeriodEndDate</payRelativeTo>
<paymentDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</paymentDatesAdjustments>
</paymentDates>
<calculationPeriodAmount>
<calculation>
<notionalSchedule>
<notionalStepSchedule>
<initialValue>25000000.00</initialValue>
<currency currencyScheme="http://www.fpml.org/coding-scheme/external/iso4217">EUR</currency>
</notionalStepSchedule>
</notionalSchedule>
<fixedRateSchedule>
<initialValue>0.024</initialValue>
</fixedRateSchedule>
<dayCountFraction>30E/360</dayCountFraction>
</calculation>
</calculationPeriodAmount>
</swapStream>
</swap>
</trade>
<party id="party1">
<partyId partyIdScheme="http://www.fpml.org/coding-scheme/external/iso17442">HXDEMO00000000000001</partyId>
<partyName>Helixio Demo Bank</partyName>
</party>
<party id="party2">
<partyId partyIdScheme="http://www.fpml.org/coding-scheme/external/iso17442">EXCPTY00000000000001</partyId>
<partyName>Example Counterparty Bank</partyName>
</party>
</dataDocument>
"#;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let documents = [("hx-irs-0001.xml", CONFIRMATION)];
// 2. Read them as your own party. The first party in a document is the
// default; `PartySelector::Id` picks one by its id or LEI, and the trade
// id follows that party.
for party in ["HXDEMO00000000000001", "EXCPTY00000000000001"] {
let options = ReadOptions { party: PartySelector::Id(party.to_string()), ..ReadOptions::default() };
let read = Book::from_fpml(&documents, &options, None);
for trade in read.book.trades() {
println!(
"as {party} booked {} {} x{}",
trade.id,
trade.instrument.product_name(),
trade.quantity
);
}
}
// 3. Give the counterparty master, so each trade discounts under its
// counterparty's CSA. Here the other side posts USD collateral.
let master = Counterparties::new().with(Counterparty::new("EXCPTY00000000000001", CsaSpec::new("USD-SOFR")));
let options = ReadOptions { counterparties: Some(&master), ..ReadOptions::default() };
let read = Book::from_fpml(&documents, &options, None);
let needs = market_data_needs(&read.book.trades()[0].instrument)?.needs();
let discount = needs.iter().find(|(role, _)| role == "discount").expect("a discount curve").1.clone();
println!("discounts on {discount}");
// 4. Read the refusals. A document that can't be read is one refusal row,
// and the others still book.
let with_broken = [("broken.xml", "<dataDocument"), documents[0]];
let read = Book::from_fpml(&with_broken, &ReadOptions::default(), None);
println!("booked {}", read.book.trades().len());
for refusal in &read.refusals {
println!("refused {}: {}", refusal.source, refusal.error);
}
// 5. Or read strictly: the first refusal fails and nothing is booked.
let raised = Book::from_fpml_strict(&with_broken, &ReadOptions::default(), None).is_err();
println!("strict read raises {}", if raised { "yes" } else { "no" });
Ok(())
}Python
import helixio as hlx
# 1. Collect the documents as (label, text) pairs. The label names a document
# in the refusals, so a file name is a good one.
CONFIRMATION = """<?xml version="1.0" encoding="utf-8"?>
<dataDocument xmlns="http://www.fpml.org/FpML-5/confirmation" fpmlVersion="5-14">
<trade>
<tradeHeader>
<partyTradeIdentifier>
<partyReference href="party1" />
<tradeId tradeIdScheme="https://example.com/trade-id">HX-IRS-0001</tradeId>
</partyTradeIdentifier>
<partyTradeIdentifier>
<partyReference href="party2" />
<tradeId tradeIdScheme="https://example.com/trade-id">HX-IRS-0002</tradeId>
</partyTradeIdentifier>
<tradeDate>2026-09-29</tradeDate>
</tradeHeader>
<swap>
<swapStream>
<payerPartyReference href="party1" />
<receiverPartyReference href="party2" />
<calculationPeriodDates id="floatingCalcPeriodDates">
<effectiveDate>
<unadjustedDate>2026-10-01</unadjustedDate>
<dateAdjustments>
<businessDayConvention>NONE</businessDayConvention>
</dateAdjustments>
</effectiveDate>
<terminationDate>
<unadjustedDate>2031-10-01</unadjustedDate>
<dateAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCenters id="primaryBusinessCenters">
<businessCenter>EUTA</businessCenter>
</businessCenters>
</dateAdjustments>
</terminationDate>
<calculationPeriodDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</calculationPeriodDatesAdjustments>
<calculationPeriodFrequency>
<periodMultiplier>6</periodMultiplier>
<period>M</period>
<rollConvention>1</rollConvention>
</calculationPeriodFrequency>
</calculationPeriodDates>
<paymentDates>
<calculationPeriodDatesReference href="floatingCalcPeriodDates" />
<paymentFrequency>
<periodMultiplier>6</periodMultiplier>
<period>M</period>
</paymentFrequency>
<payRelativeTo>CalculationPeriodEndDate</payRelativeTo>
<paymentDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</paymentDatesAdjustments>
</paymentDates>
<resetDates id="resetDates">
<calculationPeriodDatesReference href="floatingCalcPeriodDates" />
<resetRelativeTo>CalculationPeriodStartDate</resetRelativeTo>
<fixingDates>
<periodMultiplier>-2</periodMultiplier>
<period>D</period>
<dayType>Business</dayType>
<businessDayConvention>NONE</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
<dateRelativeTo href="resetDates" />
</fixingDates>
<resetFrequency>
<periodMultiplier>6</periodMultiplier>
<period>M</period>
</resetFrequency>
<resetDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</resetDatesAdjustments>
</resetDates>
<calculationPeriodAmount>
<calculation>
<notionalSchedule>
<notionalStepSchedule>
<initialValue>25000000.00</initialValue>
<currency currencyScheme="http://www.fpml.org/coding-scheme/external/iso4217">EUR</currency>
</notionalStepSchedule>
</notionalSchedule>
<floatingRateCalculation>
<floatingRateIndex>EUR-EURIBOR-Reuters</floatingRateIndex>
<indexTenor>
<periodMultiplier>6</periodMultiplier>
<period>M</period>
</indexTenor>
</floatingRateCalculation>
<dayCountFraction>ACT/360</dayCountFraction>
</calculation>
</calculationPeriodAmount>
</swapStream>
<swapStream>
<payerPartyReference href="party2" />
<receiverPartyReference href="party1" />
<calculationPeriodDates id="fixedCalcPeriodDates">
<effectiveDate>
<unadjustedDate>2026-10-01</unadjustedDate>
<dateAdjustments>
<businessDayConvention>NONE</businessDayConvention>
</dateAdjustments>
</effectiveDate>
<terminationDate>
<unadjustedDate>2031-10-01</unadjustedDate>
<dateAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</dateAdjustments>
</terminationDate>
<calculationPeriodDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</calculationPeriodDatesAdjustments>
<calculationPeriodFrequency>
<periodMultiplier>1</periodMultiplier>
<period>Y</period>
<rollConvention>1</rollConvention>
</calculationPeriodFrequency>
</calculationPeriodDates>
<paymentDates>
<calculationPeriodDatesReference href="fixedCalcPeriodDates" />
<paymentFrequency>
<periodMultiplier>1</periodMultiplier>
<period>Y</period>
</paymentFrequency>
<payRelativeTo>CalculationPeriodEndDate</payRelativeTo>
<paymentDatesAdjustments>
<businessDayConvention>MODFOLLOWING</businessDayConvention>
<businessCentersReference href="primaryBusinessCenters" />
</paymentDatesAdjustments>
</paymentDates>
<calculationPeriodAmount>
<calculation>
<notionalSchedule>
<notionalStepSchedule>
<initialValue>25000000.00</initialValue>
<currency currencyScheme="http://www.fpml.org/coding-scheme/external/iso4217">EUR</currency>
</notionalStepSchedule>
</notionalSchedule>
<fixedRateSchedule>
<initialValue>0.024</initialValue>
</fixedRateSchedule>
<dayCountFraction>30E/360</dayCountFraction>
</calculation>
</calculationPeriodAmount>
</swapStream>
</swap>
</trade>
<party id="party1">
<partyId partyIdScheme="http://www.fpml.org/coding-scheme/external/iso17442">HXDEMO00000000000001</partyId>
<partyName>Helixio Demo Bank</partyName>
</party>
<party id="party2">
<partyId partyIdScheme="http://www.fpml.org/coding-scheme/external/iso17442">EXCPTY00000000000001</partyId>
<partyName>Example Counterparty Bank</partyName>
</party>
</dataDocument>
"""
documents = [("hx-irs-0001.xml", CONFIRMATION)]
# 2. Read them as your own party. The first party in a document is the default;
# `party=` picks one by its id or LEI, and the trade id follows that party.
for party in ("HXDEMO00000000000001", "EXCPTY00000000000001"):
book, _ = hlx.Book.from_fpml(documents, party=party)
for trade_id, instrument, quantity in book.trades():
print(f"as {party} booked {trade_id} {instrument.product()} x{quantity:g}")
# 3. Give the counterparty master, so each trade discounts under its
# counterparty's CSA. Here the other side posts USD collateral.
master = [hlx.Counterparty("EXCPTY00000000000001", "USD-SOFR")]
book, _ = hlx.Book.from_fpml(documents, counterparties=master)
needs = dict(book.trades()[0][1].market_data_needs())
print(f"discounts on {needs['discount']}")
# 4. Read the refusals. A document that can't be read is one refusal row, and
# the others still book.
book, refusals = hlx.Book.from_fpml([("broken.xml", "<dataDocument"), *documents])
rows = refusals.to_dict()
print(f"booked {len(book.trades())}")
for source, reason in zip(rows["source"], rows["reason"]):
print(f"refused {source}: {reason}")
# 5. Or read strictly: the first refusal raises and nothing is booked.
try:
hlx.Book.from_fpml([("broken.xml", "<dataDocument"), *documents], strict=True)
raised = False
except ValueError:
raised = True
print(f"strict read raises {'yes' if raised else 'no'}")as HXDEMO00000000000001 booked HX-IRS-0001 VanillaSwap x1
as EXCPTY00000000000001 booked HX-IRS-0002 VanillaSwap x1
discounts on EUR-CSA-USD
booked 1
refused broken.xml: not well-formed XML: the document does not have a root node
strict read raises yes
Check it worked
- Each trade is booked once, under the trade id your party gives it.
- With the master, the EUR swap facing a counterparty that posts USD discounts on
EUR-CSA-USD. - The malformed document is the only refusal, and the good one still books.
- The strict read raises.
Supported products
| FpML product | Books as |
|---|---|
swap |
a single-currency or cross-currency swap, including stubs, step schedules, compounding, principal exchanges and FX-linked notionals |
swaption |
a European or Bermudan swaption; a cancelable swap books as a callable swap |
capFloor |
a cap, a floor, or a collar |
fra |
a forward rate agreement |
bulletPayment |
a single payment |
fxSingleLeg |
an FX spot, forward or non-deliverable forward |
fxSwap |
an FX swap |
fxOption |
a European FX option, non-deliverable options included |
strategy |
each of its FX components |
creditDefaultSwap |
a single-name or index CDS |
creditDefaultSwapOption |
an option on a CDS |
bondOption |
an option on a bond from your security master (Rust only, see below) |
repo |
a term repo (Rust only, see below) |
equityOption |
a European equity option |
returnSwap |
an equity total return swap |
Any other product, and any element that would change a trade's economics but isn't mapped, is refused by name.
A bond option and a repo name their bond, so the reader looks it up in a security master: in
Rust, pass one as ReadOptions::securities. Python's Book.from_fpml has no such argument yet,
so in Python both come back as refusals.
Related
- Counterparties and CSA, the reference for the counterparty master.
- How trades are built, for why the reader refuses rather than defaults.
- Value a book and collect failures, for what to do with the book next.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.