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_variant is 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_variant and rates_fact_variant are 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.

Emacs 29.3 (Org mode 9.6.15)