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

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.

Emacs 29.3 (Org mode 9.6.15)