Skip to main content
Concepts
DraftLast reviewed 2026-06-24

Valuation

trms-valuation computes mark-to-market value and risk for every confirmed deal. The engine itself is product-agnostic — it resolves a ProductValuator for the deal's product type and asset class, hands it the deal's details, a curve, and market data, and assembles the result into a Valuation record. New asset classes are added by implementing the ProductValuator SPI, not by changing the engine.

Summary vs. component-level detail

A summary valuation produces one row per deal per date: market value, clean value, accrued interest, book value, day P&L (against the prior valuation), realized and unrealized P&L, the curve used, the model name, and risk measures (Greeks) where the product has them. This is what most read paths need.

Detailed valuation goes further: it asks the valuator for calculate_components, which returns a list of ValuationDetail rows — one per cashflow, coupon, or leg, each carrying its own present value, discount factor, accrued amount, and (for floating legs) forward and fixing rates. For an interest rate swap, that means a PV line for every projected cashflow on both legs, rolled up into a leg-level summary; for a bond, a line per coupon plus one for principal redemption. The summary and the detail are designed to reconcile: component PVs should sum to the deal-level PV, and the engine logs a "residual" component when they don't, rather than letting the difference disappear silently.

The model layer: GraalPy and QuantLib

trms-python hosts a GraalPy context that lets ProductValuator implementations call into Python — specifically QuantLib — for pricing models that are impractical to reimplement natively in Java: discounted cashflow with curve bootstrapping, day-count conventions, and the kind of date arithmetic that QuantLib already gets right. This is a bridge, not a rewrite: the JVM owns orchestration, persistence, and the event-sourced write path; GraalPy owns the numerical model for the duration of a single valuation call. Keeping that boundary narrow is what lets the valuation engine stay swappable per product without dragging a Python runtime into unrelated request paths.

Cashflows feed valuation, not the other way around

Cashflows are generated and tracked independently (projected → fixed → settled, or cancelled) according to the instrument's processing group — perpetual instruments only need settlement cashflows, sharable instruments track coupons plus deal-level flows, unique instruments derive cashflows from the deal's own details. Valuation consumes whatever cashflows are currently fixed for a deal; it doesn't generate them. Conditional cashflows carry their trigger conditions in the extension payload and are evaluated during end-of-day, which is also when the EOD valuation step (POST /portfolio/valuate) runs against the full book.

A deal amended after a valuation has already been linked to a journal entry doesn't get that valuation silently rewritten — the journal stays linked to the valuation that existed when it was posted, and the amendment triggers a fresh valuation plus a reversal-and-repost in accounting. The linkage, not the number, is what's permanent.