Skip to content

[QL-9.6] Tree + TrinomialTree (recombining trinomial lattice) #460

Closed

Description

@benbenbang

Batch TREE - short-rate lattice (T1/6): Tree + TrinomialTree

The recombining trinomial tree structure. Foundation for the lattice layer (Option 2, L9 Methods, EPIC-9 #71). Both absent on main.

Scope

QuantLib/ql/methods/lattices/tree.hpp + trinomialtree.{hpp,cpp} -> crates/libitofin/src/methods/lattices/.

Contract (verified against source)

  • Tree<T> (tree.hpp:50) is a CRTP base storing columns_ only; its "interface" (size(i), descendant(i,index,branch), probability(i,index,branch), underlying(i,index), branches) is a DOCUMENTATION contract (tree.hpp:34-46), not virtuals. In Rust: a small trait (or inherent methods) + const BRANCHES: usize = 3.
  • TrinomialTree (trinomialtree.hpp:41, 148+177 LOC): ctor (process: Shared<dyn StochasticProcess1D>, time_grid: TimeGrid, is_positive: bool = false); builds per-step Branching from the process expectation/variance over the grid. branches = 3; size(i), underlying(i,index) = x0 + (jMin + index)*dx(i), descendant, probability, dx(i).
  • Inner Branching (trinomialtree.hpp:66-143): k_ (middle-branch index per node), probs_[3][], tracks jMin/jMax; descendant(index,branch) = k_[index] - jMin - 1 + branch. The growable-vector build-up is the bulk.
  • Deps on main: StochasticProcess1D (stochasticprocess.rs), TimeGrid (math/timegrid.rs).

Oracle (Rust-authored)

Over a known process (e.g. OrnsteinUhlenbeckProcess or a constant-drift/vol test process) on a regular TimeGrid:

  • Per node, the 3 branch probabilities are non-negative and SUM TO 1.0.
  • dx(i) matches the trinomial spacing (sigma*sqrt(3*dt) for the standard scheme), underlying(i,index) linear in index.
  • descendant indices stay in [0, size(i+1)); the middle branch centres on the conditional mean.
  • Confirm-by-stubbing: perturb one branch probability -> the sum-to-1 assertion FAILS.

Deps / est


GATE AMENDMENT (apply)

  1. dx has a FLOOR - assert the PORTED formula, not the textbook one. QL: dxNatural = sqrt(3*variance) (trinomialtree.cpp:79) BUT a dxFloor = sqrt(3*dxFloorVar) (trinomialtree.cpp:67) can override it. Assert dx(i) against the ported formula (incl. the floor), NOT sigma*sqrt(3*dt) - else a faithful port fails its own dx check.
  2. Descendant oracle must cover ALL THREE branches (the middle-branch-centres pin leaves up/down routing uncovered, and [QL-9.8] TreeLattice + TreeLattice1D (backward induction + state prices) #462's sum-identity can't catch routing - see [QL-9.8] TreeLattice + TreeLattice1D (backward induction + state prices) #462). Assert the underlying of all three descendant nodes == conditional_mean + {-1,0,+1}*dx. Do NOT leave the per-branch routing seam to a later ticket.

Activity

  1. added a commit that references this issue on Jul 22, 2026
    f1d2d88
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions