Story: IR curve follow-ups
Table of Contents
This page documents a story in Sprint 25. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
Goal
(Describe the user-visible outcome this story delivers.)
Status
| Field | Value |
|---|---|
| State | STARTED |
| Parent sprint | Sprint 25 |
| Now | Not yet started. |
| Waiting on | Nothing. |
| Next | Break the story into tasks. |
| Last touched | 2026-08-11 |
Acceptance
Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
| IR curve follow-ups | DONE | 2026-08-10 | 2026-08-10 | Two follow-ups surfaced during IR curve work: realistic-2026 seed data incompatible with index_family enum (provision party fails), and IrCurveGenerationConfigDetailDialog's hand-patched index family combo doesn't list the 16 new RFR/IBOR families. |
| Expand synthetic IR stochastic process types beyond Vasicek/CIR/Hull-White | DONE | 2026-08-07 | 2026-08-09 | Add support for further stochastic interest-rate models (BDT, Black-Karasinski, HJM, LMM, SABR, G2++, affine/QG term-structure) to ores.synthetic's yield curve process types, currently limited to Vasicek, CIR, and Hull-White. |
| Investigate tenant_type model/table drift on has_tenant_id | DONE | 2026-08-10 | 2026-08-10 | tenant_type's codegen model has no :has_tenant_id: flag, meaning current codegen would drop the tenant_id column entirely, yet the live table still has it – likely stale drift, not a clean pattern to copy. |
| Implement G2++ two-factor Gaussian short-rate process | DONE | 2026-08-07 | Implement a two-factor Gaussian short-rate engine following the data-oriented pattern of the existing hull_white_process. The second factor adds kappa2, sigma2, and correlation rho. discount_factor() has a closed-form two-factor affine bond price. Reference: QuantLib G2 class. | |
| Wire G2 (Two-Factor Gaussian) into the system: factory, seed data, config storage, Qt UI | DONE | 2026-08-07 | 2026-08-10 | PR 2 for the G2 model: expose the raw two_factor_gaussian_process engine to the system. Strongly-typed parameter structs, factory method, child DB table for G2-specific params, and dynamic Qt UI that switches the parameter form based on process_type. |
| Fix realistic_2026 theme provisioning: cross-tenant parameter_definition_id validation | DONE | 2026-08-10 | 2026-08-11 | The realistic_2026 synthetic theme fails to publish in every Acme entity: the generated insert trigger on ores_synthetic_config_process_parameter_values_tbl validates parameter_definition_id under NEW.tenant_id, but the definitions are system-tenant reference data, so the cross-tenant reference from ores_synthetic_publish_theme_from_dq_fn is rejected (Invalid parameter_definition_id). Fix: :use_system_tenant: true on the FK; regenerate, rebuild, recreate DB, re-provision. |
Decisions
- Matrix math for the multi-factor processes (affine, HJM, LMM, QG) uses
Eigen (vcpkg port
eigen3, header-only). Prior-art analysis rejectedstd::mdspan(absent from this toolchain), Boost uBLAS (legacy API, no Cholesky), glm (graphics-only) and NVIDIA matx (CUDA-first). Eigen is the modern CPU dense-algebra standard: value semantics, expression templates, LLT/LLT-with-eigen-fallback Cholesky for correlated shocks. - Exact one-step Gaussian transitions everywhere one exists (the codebase
convention, and QuantLib's own OU evolve): BK on
ln r, affine/QG on the factors. Euler is used only where no exact transition exists (LMM's rate dynamics), with its O(dt) bias measured and documented. - One generic
rate_treelattice inmath/serves every tree model: BDT fills a binomial (2 children, p = 0.5), BK a centered adaptive trinomial (Hull-White moment-matching probabilities, valid for any horizon – no clamping). The processes build the tree; the utility walks it and propagates state prices. BDT extends flat beyond the input curve rather than bounding the horizon (no new limitations at the start). - QG bond pricing uses the exponential-quadratic recursion (completion of squares over the per-tick shock, Eigen inverse + determinant). At Gamma == 0 it reduces to the affine recursion bit-for-bit – the discrete-time design anchors the two models together.
affine_term_structure_processcovers Gaussian factors only. CIR stays its own process: the square-root factor's exact transition does not fit Gaussian OU dynamics, and forcing it through would break its exact transition. Documented as a future extension.- Engine work only: none of the six processes is wired into
process_factoryyet. System-infra wiring is PR 2 (the G2 precedent). oresmd::index_familymirrors the SQL CHECK constraint onores_synthetic_ir_curve_generation_configs_tbl(22 families) as the single source of truth, andis_overnight()encodes its tenor rule (only libor/euribor are term). The seed/config side stays a plain lower-case token; the enum mismatch that broke party provisioning from realistic-2026 seed data is resolved on the oresmd side.- The dialog's index family combo stays hand-patched (all 22 families, b36839fd0) rather than enum-driven: ores.qt/synthetic already links ores.marketdata.api, but the combo lists user-facing labels that would not gain from magic_enum round-trips; both sides cite the same CHECK.
lookup_entityis architectural duplication (DQ migration precedent, commits 724253fc8 / d04000791). The drift task migrated the three IAM lookups (tenant_type, account_type, tenant_status) todomain_entitymodels and deleted the_lookup_entity.orgfiles. The lookup models were the true authority for the tenant-scoped schema; the legacy orgs were the stale duplicates.- Lookup migrations bind
:profile: simple-lookupin* Flags— never hand-authored feature flags. The profile assigns the tenancy, workspace, UUID-PK, change-reason-cache, and pagination flags the party_status blueprint uses, and regenerates with zero diff. - The tenant-scoped schema is preserved end-to-end by the domain route:
tenant_idstays in the PK and gist exclude, with the standard hardening (name unique index, security definer insert function,clock_timestamp(), system-tenant bootstrap check, notify triggers).
Out of scope
- The six engine tasks (Black-Karasinski, BDT, affine, HJM, LMM, QG) moved to the Add new stochastic processes story on 2026-08-11: their engines are merged (PR #1932) but not wired into the system, so the system wiring is tracked there, not here.