Task: Curve Template sub-config (ir_curve_generation_config)

Table of Contents

This page documents a task in the IR Rates synthetic data generation story. It captures the goal, current status, acceptance, and any notes or results.

Goal

Add a new sub-config type, codegen-driven like fx_spot_generation_config (projects/ores.synthetic/api/include/ores.synthetic.api/domain/fx_spot_generation_config.hpp, model at projects/ores.synthetic/modeling/ores.synthetic.fx_spot_generation_config.org), owned by market_data_generation_config (whose docstring already anticipated this: "vol surface, interest-rate curves later"). One config per currency+index (USD-SOFR, EUR-ESTR, …), holding the short-rate process parameters and the Curve Template: which tenors to publish at which role (short-end deposit, mid-curve FRA/future, long-end swap).

Status

Field Value
State DONE
Parent story IR Rates synthetic data generation
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-07-03

Acceptance

  • New codegen entity model + generated SQL/domain/repository/protocol layers for the sub-config, following the fx_spot_generation_config pattern.
  • Config holds: currency, index/curve name, short-rate process parameters, and an ordered list of (tenor label, instrument role) pairs forming the Curve Template.
  • Each (tenor, instrument role) entry's published rate is derived from the short-rate process's P(t,T) zero-coupon bond price (see the short-rate-process task's Acceptance) at that tenor's maturity — a simple rate for deposits, an implied forward rate between two maturities for FRAs, a par-rate solve across the template's swap cashflow dates for swaps — never an independently-noised value per tenor. This is what makes the tick batch a slice of one consistent latent curve rather than an incoherent grid.
  • Qt UI CRUD for the config is out of scope for the first pass unless trivial to add alongside the existing FxSpotRateEditor-adjacent scaffolding — flag as a follow-up if deferred.

Plan

(Implementation strategy. Written when work starts; key decisions are distilled into the parent story's * Decisions at close, but the plan itself stays — it is the historical record of what we did.)

Generalised "instrument role" into a full, reusable product catalogue rather than a narrow 3-value enum, since the same classification is useful beyond this one config:

  • asset_class_code (ores.refdata): book_status-pattern code table (fx, rates, credit, equity, commodity, inflation, bond, cross_asset).
  • instrument_code (ores.refdata): full ORE product/trade-type catalogue, sourced from ORE's own authoritative oreTradeType enumeration in external/ore/xsd/instruments.xsd (114 entries), each tagged with its asset_class, plus one ORE Studio-specific addition, Deposit (ORE has no distinct money-market-deposit trade type of its own). Both entities got dynamic-combo Qt CRUD as a side effect of codegen, wired into RefdataPlugin under a new "Classifications" submenu.
  • ir_curve_generation_config (ores.synthetic): party+config-scoped sub-config, one per currency+index, holding the short-rate process parameters (process_type=/=kappa=/=theta=/=sigma=/=initial_rate) plus ticks_per_hour=/=enabled. theta stored as a single constant (not a term-structure path) per an explicit scope decision.
  • ir_curve_template_entry (ores.synthetic): ordered (sequence_index, tenor_code, instrument_code) rows owned by a config; instrument_code is FK-validated against the new catalogue via the standard Insert-trigger Validations mechanism; tenor_code is left unvalidated, matching the existing tenor_convention_resolution precedent (no FK enforcement exists for tenor codes anywhere else in the codebase either).
  • curve_instrument_pricer (ores.analytics.quant): pure static functions deriving deposit_rate=/=fra_rate=/=swap_par_rate from discount_factor() values produced by the short-rate processes, with input validation and a Catch2 suite including an integration test against real hull_white_process=/ =vasicek_process instances.
  • ores.synthetic.service has no postgres_event_source=/=event_bus infrastructure at all (a pre-existing gap shared by sibling entities fx_spot_generation_config=/=gmm_component), so the codegen-produced NATS event-registrar files for the two new synthetic entities were deleted rather than wired — out of scope for this task to fix.
  • Qt CRUD for ir_curve_generation_config=/=ir_curve_template_entry themselves was generated by codegen as a side effect but not wired into a plugin menu, per the acceptance criteria marking it out-of-scope for this pass.

Notes

Verified end-to-end by hand via direct SQL against a freshly recreated dev database (no provisioning data available, so config rows themselves could not be inserted without a full tenant/party setup): confirmed ores_refdata_validate_instrument_code_fn — the same function ir_curve_template_entry's insert trigger calls — accepts Deposit, ForwardRateAgreement and Swap, and correctly rejects the wrong-cased DEPOSIT while listing Deposit among the valid codes.

PRs

PR Title
#1564 [refdata,synthetic,analytics.quant,qt] Curve Template sub-config, product catalogue, pricing derivations

Review

# Comment summary File Decision Notes
1 Missing RBAC permission catalogue entries for the four new entities iam_permissions_populate.sql Accepted Added read/write/delete rows for refdata::asset_class_codes, refdata::instrument_codes, synthetic::ir_curve_generation_configs, synthetic::ir_curve_template_entries, mirroring the fx_spot_generation_config/gmm_component precedent.
2 clock_timestamp() vs current_timestamp inconsistency within ores_synthetic_* schema synthetic_ir_curve_*_create.sql Declined Deliberate, matches the convention already used by every ores_refdata_* table; a conscious call-out, not an accidental inconsistency. Migrating the older synthetic tables is a separate, out-of-scope concern.
3 Non-alphabetical include/registration order (nit) registrar.cpp, LookupFetcher.hpp, RefdataPlugin.cpp Declined Functionally harmless; repo's nightly-format workflow re-sorts includes automatically on main rather than gating PRs.
4 tenor_code not FK-validated, unlike instrument_code synthetic_ir_curve_template_entries_create.sql Declined Matches the existing tenor_convention_resolution precedent — no FK enforcement exists for tenor codes anywhere else in the codebase either; a deliberate scope decision, not an oversight.
5 No domain validation on kappa/initial_rate per process_type synthetic_ir_curve_generation_configs_create.sql Partially accepted Added a CIR-specific check (initial_rate >= 0, required by CIR's square-root diffusion). Left kappa unconstrained: kappa <= 0 is a valid, intentional degenerate case (hull_white_process's target-level drift form degenerates exactly to ou_process when kappa <= 0 with constant theta).
6 Integration test asserts isfinite() only, doesn't test the claimed monotonic-consistency property curve_instrument_pricer_tests.cpp Accepted Added the actual bounds assertion (swap rate sits between the shortest and longest single-period forward rates).

Result

Delivered the full Curve Template sub-config stack: a generalised asset_class_code=/=instrument_code product catalogue (114 real ORE oreTradeType entries + 1 documented synthetic Deposit addition, 115 total) in ores.refdata with dynamic-combo Qt CRUD; ir_curve_generation_config + ordered ir_curve_template_entry rows in ores.synthetic following the fx_spot_generation_config=/ =gmm_component pattern with FK-validated instrument codes; and a curve_instrument_pricer in ores.analytics.quant deriving deposit/FRA/swap par rates from a short-rate process's discount_factor(). Local build (linux-clang-debug-make) is clean and all 73/73 ctest suites pass, including the new curve_instrument_pricer_tests suite. Qt CRUD for the two synthetic entities and actual tick-batch publishing remain out of scope per the task's own acceptance criteria (the latter is tracked as a separate BACKLOG task).

Emacs 29.3 (Org mode 9.6.15)