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.

1. Goal

(Describe the user-visible outcome this story delivers.)

2. Status

Field Value
State DONE
Parent sprint Sprint 25
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-09-23

3. Acceptance

4. 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.

5. Decisions

  • Matrix math for the multi-factor processes (affine, HJM, LMM, QG) uses Eigen (vcpkg port eigen3, header-only). Prior-art analysis rejected std::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_tree lattice in math/ 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_process covers 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_factory yet. System-infra wiring is PR 2 (the G2 precedent).
  • oresmd::index_family mirrors the SQL CHECK constraint on ores_synthetic_ir_curve_generation_configs_tbl (22 families) as the single source of truth, and is_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_entity is architectural duplication (DQ migration precedent, commits 724253fc8 / d04000791). The drift task migrated the three IAM lookups (tenant_type, account_type, tenant_status) to domain_entity models and deleted the _lookup_entity.org files. The lookup models were the true authority for the tenant-scoped schema; the legacy orgs were the stale duplicates.
  • Lookup migrations bind :profile: simple-lookup in * 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_id stays 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).

6. 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.

7. Result

Closed at sprint 25 close. Every task is done.

Emacs 29.3 (Org mode 9.6.15)