Story: Rework the FX instruments to the instrument table rule
Table of Contents
This page documents a story in Sprint 28. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
1. Goal
Rework forwards, swaps, vanilla, barrier, digital, Asian, accumulator and variance products to the instrument table rule of the trade shape investigation (section 14), as the bonds were reworked, with the data-oriented review deciding the tables.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent sprint | Sprint 28 |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-17 |
3. Acceptance
- The family header's trade_type check lists exactly the family's codes, and each product fact table checks its own.
- Every instrument row is keyed by its trade and pinned to the anchor's trade type and party by a composite foreign key.
- The family's trade types route to their tables through the trade-type catalogue, not hand-written code.
- The ORE round trip stays lossless across the family's corpus.
4. Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
| Carry the FX money digits across the ORE boundary | DONE | 2026-10-16 | 2026-10-16 | Finding 1 of the FX review. Every FX money leaf in the ORE binding is a plain float, so the document's digits are lost before the mapper runs and all 35 exact_decimal call sites fall back to from_double. Make the family's money members lexical leaves through scripts/reapply_ore_lexical_form.py, write them back with set_exact, and prove the digits survive. |
| Give the FX accumulator range bounds their own table | DONE | 2026-10-16 | 2026-10-18 | Finding 2 of the FX review. fx_accumulator_instrument.range_bounds_json holds the ORE RangeBounds list as JSON text in a column, because the list has no child table. Give the child its own model and table keyed by the trade, map it in both directions, and delete the column. |
| Agree the FX continuous columns with their domain types | DONE | 2026-10-18 | 2026-10-18 | Finding 3 of the FX review. fx_variance_swap_instrument.strike is numeric(38, 12) at rest with a double in the domain and the entity. A variance strike is a continuous quantity, so move the column to double precision and check every other FX continuous column agrees with its domain type. |
| Batch the FX service reads onto the repository query | DONE | 2026-10-18 | Finding 4 of the FX review. The seven FX services issue 21 per-key single reads where the repository already declares a batch read by key vector. The shape is seven of seven services, so decide whether the fix belongs on a shared service base or on each service, and land it for the family. | |
| Turn the FX soft references into foreign keys | DONE | 2026-10-18 | 2026-10-18 | Finding 5 of the FX review. The seven FX tables declare no foreign key. Twenty columns carry a soft reference to a named table, deferred to PR 4 by the models' own prose. Tighten them into real keys, or record the reason each one stays soft. |
| Close the money-column gate over the FX deviations | DONE | 2026-10-18 | 2026-10-18 | The enforcement gap of the FX review. check_money_decimal_columns.py cannot see a continuous quantity whose name reads as money, a money value read through a binary float, or money typed text, and both of its baselines are blank. Teach the check the shapes the family exposes, seed the baselines, and correct the exact-numbers page, which still claims ten columns in the continuous direction. |
| Decide the FX discriminators and the doubled vocabularies | DONE | 2026-10-18 | 2026-10-18 | Findings 6, 8 and 9 of the FX review. The barrier and digital instruments encode a choice as nullable columns with no discriminator; fx_vanilla_option_instrument states three exercise styles in prose and admits two in its check; and four more closed vocabularies are stated both in a table and in a check. Decide each one and enforce it, or record why it stays. |
| Carry the barrier levels' digits | DONE | 2026-10-16 | 2026-10-16 | Finding 1 of the FX review, second half. The barrier level vector is the one money page the digits unit left as a bulk of floats: barrierData_Levels_t.Level is xsd::vector<float>, so every barrier level in every family crosses the ORE boundary as the nearest float. Teach the reapply script the vector shape, then write the levels with set_exact. |
| Give the FX family typed arrays, not a variant | DONE | 2026-10-17 | 2026-10-17 | The FX family returns fx_instrument_variant: one aggregate that carries an entity. The atomised entity forbids it. Replace it with one typed array per product in a carrier struct, delete the variant, and route the mapper, the carrier, the exporter and the import handler through the arrays. No behaviour change and no schema change; it unblocks the range bounds unit. |
5. Decisions
- A closed vocabulary is stated in its lookup table, not in a check. Five FX vocabularies were stated twice. The foreign key now validates the code, so the check is the copy that was removed; the lookup is what a reader edits and what the store enforces.
- An exercise style admits the whole ORE set. The vanilla option's check refused Bermudan while its prose and the lookup both admitted it. The check goes with the other duplicates, so the set is the lookup's: European, Bermudan and American.
- A single barrier's level sits in the column its direction names. An Up style or a cap engages the upper level, a Down style or a floor the lower one, and the other column stays null. The columns were positional, so an UpAndOut option could not be stated without inventing a lower level.
- A product that states two levels is the exception, and its trade type says so. The directional check names FxBarrierOption alone; the double and generic barriers hold a level per side, and a double barrier must state both.
- The digital option requires what each code engages and tolerates the rest. The one synthetic generator fills a strike and both barriers on its single row, so a check that refused another code's field would refuse that row.
- The family was reviewed on 2026-10-16, and the review is recorded as Investigation: FX family data model review. It found that the per-type tables already meet the explicit-variation check, so a family header and per-product fact tables are not required by the checklist.
- The units below carry the deviations the review found. If the header and the fact tables are still wanted for another reason, record that reason here and add the acceptance it serves.
- The units run digits first, because that unit moves the goldens.
- The nullable-string convention is not a family finding. The trading component holds 91 columns whose domain member cannot carry a NULL; the FX family carries 19 of them. A component task owns the fix.
- The family carries typed arrays, not a variant.
fx_instrument_variantis an aggregate that carries an entity, and The atomised entity forbids it: the wire carries one typed array per part, and the array an element sits in states its type, so no element and no carrier needs a discriminator. The legacy carrier is deleted, and the range bound is one more typed array beside the instrument arrays, not a field inside an aggregate. - The other three variants carry the same debt.
equity_instrument_variant,credit_fact_variantandrates_fact_variantare the same shape. They are out of scope here and want their own story.
6. Out of scope
- The ten FX trade-type codes that route to no instrument table. The catalogue records them as not yet covered.
- The ORE binding generator. The digits unit patches the generated binding through the reapply script; changing the generator is a different work item.
- The FX reference-data entities themselves.
- The equity, credit and rates variants, which are the same defect in other families.
- The pipeline-wide move onto
instrument_batch. This story removes the FX variant only; the importer, the exporter and the planner keep their per-family carriers for the other families.