Task: Populate the interest rates family

Table of Contents

This page documents a task in the Populate the ACME books with a realistic trade population — the remaining work story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

ACME's books hold interest rate trades: vanilla and overnight index swaps, swaptions, caps and floors, forward rate agreements, cross-currency swaps and the rest of the ten routed types. The family carries 42% of the population and it is the curve backbone every other class discounts on. The planner draws the trade types from the catalogue and the mix from Trade Population Weights.

2. Status

Field Value
State DONE
Parent story Populate the ACME books with a realistic trade population — the remaining work
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-10-25

3. Acceptance

  • Every interest rate trade type the catalogue routes to rate_instrument books a trade. Met for nine of the ten. The ten include the inflation swap, which this task hands to the inflation family; the nine book.
  • The three unrouted types, RateDigitalOption, SwaptionStraddle and ForwardVolatilityAgreement, are routed or named out of scope. All three are named out of scope, each with the reason measured against ORE's own schema.
  • The class share of the population matches its weight within tolerance, and every routed type appears at least once. Half met. Every routed type appears at least once in a twelve trade population. The share is 27% against a 42% weight, and cannot be met until the other families are populated: it is a property of the whole population and not of one class.
  • The family exports as the ORE portfolio the engine reads, and reverse-maps without loss. Met at the exporter, and not yet through the whole path. Two cases prove a generated trade writes its instrument, legs and index and that one without a fact row writes nothing; a live population writes twelve fact rows for twelve trades. What is not proven is the portfolio the ORE service gathers for a run.
  • The report that prices the family runs against its discount and forward curves, or the task states which pricing it cannot prove yet. Deferred with the reason stated: the shell drives no report run, so no pricing was proven.

4. Plan

4.1. What the tree already holds

The work is smaller than the family's size suggests, because three layers are already in place.

  • The routing. trade_type_routing.hpp sends ten types to rate_instrument: Swap, CrossCurrencySwap, ForwardRateAgreement, CapFloor, Swaption, FlexiSwap, BalanceGuaranteedSwap, CallableSwap, KnockOutSwap and InflationSwap. Routing decides which array a trade type may carry, so those ten can book a rate instrument today.
  • The writer, service side. write_instrument_batch already writes rate_instruments, swap_legs, swap_leg_amounts and swap_leg_rates. No trading service change is needed to store a swap.
  • The vocabulary. The legs draw on codes that are seeded: twelve day count fractions, seven business day conventions, nine payment frequencies, 33 benchmark rates and the currency set.

What is missing is the synthetic side: a rate payload, a planner and a writer arm. It has neither.

4.2. Decisions

  • D1 — the ten. They are the routed ten, and InflationSwap is among them. Booking it here as a rate-shaped swap keeps the count true and stops this task and the inflation task from both claiming it; the inflation task adds inflation_swap_instruments and the inflation market data on top.
  • D2 — the leg shape. Every routed type books a rate_instruments header with two swap_legs, and each leg one swap_leg_amount and one swap_leg_rate: a fixed leg against a floating one, whatever the type. The plan originally said the specialised arrays could stay empty until a pricing need justified them, and an exporter test proved that wrong — the store rebuilds an instrument from the header and one fact row, so the writer states the fact row its trade type needs. The fields that fact row carries are the ones the type requires and no more: a settlement lag, an exercise style, a barrier, a reference security.
  • D3 — the unrouted types. RateDigitalOption, SwaptionStraddle and ForwardVolatilityAgreement appear in the routing table not at all, so a request carrying any instrument array for them is refused as misrouted. Two are rate products whose option data the instrument_option arrays already hold, so routing them to rate_instrument is a table change; the third is named out of scope here, and the reason is recorded.
  • D4 — CashPosition. The weights page gives it 1.5% of the class, and the catalogue holds the trade type, but no instrument table routes it and it is not a rate derivative. It is named out of scope, and the weight it carries leaves the class share this task can meet. That has to be stated rather than silently absorbed.

4.3. Steps

Each step ends in a state that can be checked on its own.

  1. The payload. Add rate_terms to trade_population_plan.hpp: the currency, both legs' convention codes and economics, the notional, and the start and maturity dates. Add it to the trade_terms variant. It carries codes and numbers only, so nothing is invented downstream.
  2. The scope. The plan needs the currencies and the floating indices to draw from, resolved from refdata by the caller, as commodities already are. Add them to trade_generation_scope.
  3. The planner. rate_trade_planner in the registry under interest_rates, cycling the routed types over the scope's currencies and indices, and banding notional and tenor from the scope.
  4. The writer. Fill rate_instruments, swap_legs, swap_leg_amounts and swap_leg_rates from rate_terms in the population writer.
  5. The routing. Route RateDigitalOption and SwaptionStraddle to rate_instrument, and record ForwardVolatilityAgreement and CashPosition as out of scope with their reasons. Resolved the other way: routing alone does not make a type bookable, because a product with no fact row cannot be rebuilt for export. All three are out of scope, each with the reason stated in the Notes.
  6. The profile. Give ACME's seed profile a rate population, so a provisioning run produces the family without a hand edit.
  7. The proof. Run a generation, then measure: every routed type present at least once, every trade with two legs and their economics, the class share against the weight, and the export and reverse map.
  8. The record. Measurements, Result, and the gaps this task cannot close.

5. Measurements

Measured on 2026-10-25 against a recreated database and a full provisioning run.

What Value Command Commit
The build exit 0 compass build 1abe6da879
The codegen gate no drift check_component_drift.py --all 1abe6da879
The cmake list gate exit 0 regenerate_cmake_component_files.py --all --check 1abe6da879
The provisioning run 9 of 9 steps completed compass shell -l ...acme...ores 1abe6da879
Rate trades booked 12, across 9 of the routed types select trade_type_code, count(*) from ores_trading_rate_instruments_tbl 1abe6da879
Legs, amounts and rates 24, 24, 24 select count(*) from ores_trading_swap_legs_tbl 1abe6da879
Floating indices drawn GBP-LIBOR-3M, GBP-LIBOR-6M, GBP-SONIA, the book's own currency select distinct floating_index_code ... 1abe6da879
Rate roles fixed 12, spread 12 select rate_role, count(*) from ores_trading_swap_leg_rates_tbl 1abe6da879
Fact rows 12, one per trade: vanilla 4, fra 2, cap floor 1, swaption 1, callable 1, knock out 1, flexi 1, guaranteed 1 one count per fact table, summed: select count(*) from ores_trading_vanilla_swap_instruments_tbl and the seven others 1abe6da879
Provenance of the population synthetic and system.new_record on 44 of 44 trades select origin, change_reason_code, count(*)... 1abe6da879
The class share rates 12 of 44 (27%), bonds 32 of 44 (73%) select count(*) ... 1abe6da879
The exporter, with a fact row the trade writes SwapData, its index and its legs ores.ore.core.tests 1abe6da879
The exporter, without one nothing but the trade type ores.ore.core.tests 1abe6da879
The ORE suite 475,447 assertions in 385 cases, all passing ores.ore.core.tests 1abe6da879
The synthetic suite 1459 assertions in 66 cases, all passing ores.synthetic.core.tests 1abe6da879

6. Notes

6.1. Two defects the live runs found

Both were found by a provisioning run and neither by a compile.

  • The leg group. The first run failed on ores_trading_swap_legs_tbl_leg_group_check: the column accepts null or the groups a credit linked swap states, and the writer stated "main". An ordinary swap leaves it null.
  • The wrong catalogue. The second run failed with Invalid floating_index_type: AMERIBOR. A leg states the FpML name of an index, GBP-SONIA, and the store holds a closed set of those; the scope was loading benchmark rate names instead. It now loads the floating index types and the planner prefers the indices of the book's own currency.

6.2. A rate trade without a fact row does not export

The plan said the specialised fact arrays could stay empty until a pricing need justified them. That was wrong, and an exporter test caught it.

The store rebuilds a rates instrument from a header and one fact row per product: rebuild_instrument looks for the header in rate_instruments and then for a leaf in fra_instruments, vanilla_swap_instruments, or one of the seven others. A trade that states a header and legs and no leaf rebuilds nothing, and the exporter writes it as its trade type alone — no SwapData, no legs, no index. The trade books, queries and reports, and the engine would have nothing to price.

Two exporter cases now hold both sides of that. One states a header and legs and asserts the document carries no instrument; the other adds the fact row and asserts the legs and the index come out. The writer states the fact row its trade type needs, and a query over a live population counts twelve fact rows for twelve rate trades.

6.3. The inflation swap leaves this family

Its fact row states an inflation index code, and this generator has no vocabulary for one. Booking it here would have meant inventing a code the store does not hold. The inflation task owns it, and this family books the other nine.

6.4. The class share is not yet the weight

The measured share is 27%, against a weight of 42%. That is expected and not a defect: the share is a property of the whole population, and only bonds and rates are populated. Bonds carry 32 trades because four entity configs each book eight; the two families' ratio is therefore bond-led. The share can only be measured once the other six families are populated, and rebalanced then.

6.5. The three unrouted types are out of scope, and why

ORE states each of them as its own instrument: external/ore/xsd/instruments.xsd holds SwaptionStraddleData, RateDigitalOptionData and ForwardVolatilityAgreementData, and the generated portfolio reader maps all three. This repository holds no correspondings fact row: the instrument batch has no straddle, rate digital or forward volatility array, and the database has no table for any of them.

Routing a type to rate_instrument is not enough on its own, which is what the fact row finding already showed: a trade whose product has no fact row cannot be rebuilt for export, so it would book and then fail to price. Each of the three therefore needs a table, a batch array, a writer and an export mapping — a schema feature with its own task, not a line in the routing table.

6.6. What is not proven yet, and what the export did prove

  • The export, partly. Two exporter cases hold both sides: a rate trade with a header and legs and no fact row writes nothing but its trade type, and the same trade with its fact row writes SwapData, its index and its legs. A live population counts twelve fact rows for twelve rate trades. What remains unproven is the whole portfolio the ORE service gathers for a run, because no shell verb drives that path.
  • The report. The shell runs no report, so the family has not been priced against its discount and forward curves. Nothing here claims it has.
  • The class share. Measured at 27% against a 42% weight, which is expected while only bonds and rates are populated. It is a story-level check.

7. Test Scenarios

Manual QA scenarios (scaffolded via compass add test_scenario) that verify this task. Link new ones here as they're created; the scenario doc itself links back via its "Verifies task" field.

Scenario State Notes
     

8. PRs

PR Title
   

9. Review

Comment summary File Decision Notes
       

10. Result

The interest rate family books. A provisioning run completes all nine steps and writes twelve rate trades across nine of the routed types: Swap, CrossCurrencySwap, ForwardRateAgreement, CapFloor, Swaption, FlexiSwap, BalanceGuaranteedSwap, CallableSwap and KnockOutSwap. Each states a rate instrument header, two legs — one fixed against one floating — a notional, and the rate or spread that leg pays. The floating index is drawn from the tenant's floating index catalogue, filtered to the book's own currency, so a GBP book states GBP-SONIA, GBP-LIBOR-3M or GBP-LIBOR-6M.

A rate trade is a header, one product fact row and two legs. The fact row is not decoration: the store rebuilds an instrument from the header and one fact row, and the exporter test proves that without it the trade writes no instrument at all. The writer states the fact row its trade type needs, and a live population counts twelve fact rows for twelve trades.

Three things are not done, and the record says so rather than implying otherwise: the inflation swap belongs to the inflation family, which owns the inflation index vocabulary its fact row needs; RateDigitalOption, SwaptionStraddle and ForwardVolatilityAgreement are out of scope because ORE states each as its own instrument and this repository has no fact row for any of them; and the family has not been priced, because the shell drives no report run.

Emacs 29.3 (Org mode 9.6.15)