The previous article argued that lifecycle modelling should move from event labels to state transitions: before state plus instruction produces after state.
This article makes that concrete. First, the two types that sit at the centre of every lifecycle event — Trade and TradeState. Then, the nine primitives that change them.
Trade and TradeState
A Trade extends TradableProduct. It carries everything that makes a financial transaction what it is: the product definition with its economic terms and payouts, the parties, the trade date, the identifier, the execution details, and the contract details. This is where the eight payout types from the previous articles live.
A TradeState wraps a Trade at a specific point in its life:
type TradeState: trade Trade (1..1) state State (0..1) resetHistory Reset (0..*) transferHistory TransferState (0..*) observationHistory ObservationEvent (0..*) valuationHistory Valuation (0..*)
Six fields. Four of them are histories.
tradeis the product and economic terms at this point in time.staterecords whether the trade is closed, and why — Active, Terminated, Novated, Cancelled.resetHistoryrecords observed values (rates, prices, fixings).transferHistoryrecords asset movements (cash settlements, securities deliveries).observationHistoryrecords external events (credit events, corporate actions).valuationHistoryrecords valuations over time.
The key distinction: the four histories record lifecycle outcomes without modifying the product definition. When a reset records that SOFR was 5.12% on 28 Jun 2026, the InterestRatePayout.rateSpecification still says “3M SOFR.” The reset is a history entry, not a product amendment. When a transfer records a settled payment, the contractual payment terms are untouched.
A concrete example. A USD 100M fixed-float IRS is executed on 15 March 2026. Two months later, it is partially terminated to USD 80M. Later, a floating rate is observed. Three TradeStates exist:
TradeState₁ (15 Mar 2026 — Execution) trade: IRS { notional: USD 100M, fixed 3.50%, 3M SOFR, Party A ↔ Party B } state: empty (active)TradeState₂ (15 May 2026 — Partial Termination) trade: IRS { notional: USD 80M, fixed 3.50%, 3M SOFR, Party A ↔ Party B } state: empty (active)TradeState₃ (28 Jun 2026 — Reset) trade: IRS { notional: USD 80M, fixed 3.50%, 3M SOFR, Party A ↔ Party B } state: empty (active) resetHistory: [ Reset { resetValue: 0.0512, resetDate: 2026-06-28 } ]
TradeState₁ → TradeState₂: the product changed (notional reduced). TradeState₂ → TradeState₃: the product is identical, but resetHistory grew. That is the difference between a product-changing event and an observation event. The four histories on TradeState are where lifecycle outcomes accumulate without touching the product.
Every lifecycle event creates a new TradeState. The current snapshot is the last one in the chain. The mechanism for producing each new state from the previous one is a set of nine primitive operators. Each follows the same pattern:
The before TradeState carries the trade as it was. The after TradeState is the result. Nothing is overwritten — the before state persists in the lineage.
The connector is Instruction:
type Instruction: primitiveInstruction PrimitiveInstruction (0..1) before TradeState (0..1)
Two fields. primitiveInstruction says what to do — which of the nine primitives to apply. before says on what — a reference to the TradeState being acted upon. Split is the special case: its breakdown carries nested primitive instructions, one per branch, because each branch of a split can have its own quantity change, party change, or other primitives applied.
The Nine Primitives
The nine primitives are the fields inside PrimitiveInstruction. Each has a Create_ function that takes a before TradeState and instruction parameters, producing one or more after TradeStates:
type PrimitiveInstruction: execution ExecutionInstruction (0..1) quantityChange QuantityChangeInstruction (0..1) termsChange TermsChangeInstruction (0..1) partyChange PartyChangeInstruction (0..1) exercise ExerciseInstruction (0..1) contractFormation ContractFormationInstruction (0..1) reset ResetInstruction (0..1) transfer TransferInstruction (0..1) split SplitInstruction (0..1)
Execution is the only primitive with no before-state — it creates a trade from nothing. Split is the only primitive that produces multiple after-states.
Execution. Instantiates a new trade. Takes a product definition, parties, trade date, and identifier, and produces the first TradeState. No before-state because nothing existed before.
QuantityChange. Modifies the notional or quantity of a trade. A partial termination reduces it. An increase raises it. The instruction specifies the new quantity. The function validates the result — a partial termination cannot reduce to zero (that would be a full termination), and an increase must be positive.
TermsChange. Alters the product terms: a rate change, a maturity extension, a spread adjustment. The function produces a new TradeState with the amended product. The original TradeState remains — the change is additive, not destructive.
PartyChange. Switches a counterparty. Used in novation (original party replaced), clearing (party replaced by CCP), and assignment.
Exercise. Triggers an embedded option. For a swaption, the underlier product — a fully-defined IRS — becomes a live trade on the exercise date. For a callable bond, the bond is redeemed early.
ContractFormation. Attaches legal agreements to the trade. A trade may be executed without a signed master agreement. When the agreement is executed, ContractFormation links it.
Reset. Records an observed value — a floating rate, an equity price, an FX fixing. This does not change the product definition. It records the observation in TradeState.resetHistory. The product said “3M SOFR, fixed 2BD before period start.” The reset records: on 28 Jun 2026, SOFR was 5.12%.
Transfer. Moves assets between parties: cash settlements, securities deliveries, collateral movements. The instruction specifies the asset, the quantity, the settlement date, and the delivery method.
Split. Copies a trade into multiple identical trades. Used in allocation (a block trade is split into allocated portions), partial novation (the trade is split into retained and novated portions), and compression.
Composing Primitives into Business Events
A BusinessEvent combines one or more primitives into a recognisable lifecycle event:
type BusinessEvent extends EventInstruction: // inherited from EventInstruction: intent EventIntentEnum (0..1) eventDate date (0..1) effectiveDate date (0..1) instruction Instruction (0..*) // added by BusinessEvent: eventQualifier string (0..1) after TradeState (0..*)
The eventQualifier is populated by qualification functions — the event equivalent of the product qualification functions from Part 19. Where product qualification inspects payout structure, event qualification inspects primitive composition.
Qualify_PartialTermination checks: is there exactly one QuantityChange? Did the quantity decrease but not reach zero? If so, the qualifier is PartialTermination. Qualify_Novation checks: is there a Split? Are there QuantityChange instructions on each branch that sum to the original? Is there a PartyChange on exactly one branch?
The qualifier is derived from the primitives, not stored independently. The primitives are the evidence. The qualifier is the conclusion.
Partial Novation: A Concrete Example
A partial novation occurs when one party to a trade transfers part of its position to a new counterparty while retaining the rest. Party B has a USD 100M IRS with Party A. Party B wants to transfer USD 30M of that position to Party C — perhaps because Party C can manage the risk more cheaply, or because Party B is reducing its exposure to Party A. The remaining USD 70M stays with Party B.
One trade becomes two. One counterparty changes on one portion. The original notional is preserved in aggregate. This is a partial novation — not a full novation (where B exits entirely) and not a termination (where the notional is reduced without a replacement party).
The Split creates two branches from one trade. One QuantityChange reduces Branch A to 70M. Another QuantityChange reduces Branch B to 30M, and a PartyChange replaces Party B with Party C. The qualifier “Novation” is not stored by the user — it is derived by inspecting the primitives.
Without the state-transition view, a system stores eventType = PartialNovation and hopes downstream consumers understand. With it, the system stores exactly what changed — which primitives, on which trade, producing which result. An AI agent can explain: the trade was split into two branches, quantities were adjusted to 70M and 30M, and the counterparty on the 30M branch was replaced.
Product Qualification and Event Qualification – The Same Philosophy
As discussed in the previous articles, a product is not what its label says it is, but instead a product is what its payout structure says it is. The event model applies the same principle:
| Product Model | Event Model | |
|---|---|---|
| The label says | “IRS” | “PartialNovation” |
| The structure says | Two IRPs, same currency, one fixed one floating, recurring paymentDates | Split + QuantityChange + PartyChange |
| Auditable? | Run the qualification function | Inspect the primitives |
Structure determines product meaning. State transition determines event meaning. The same philosophy, applied to both halves of the model.
Conclusion
Nine primitives. Each with a Create_ function. Each taking a before-state and producing an after-state. Business events compose primitives into recognisable lifecycle events, qualified by inspecting the composition – not by trusting a label.
The next article looks at what surrounds those events: the lineage that connects states across time, the workflow that governs operational processing, and the most important primitive distinction – reset versus transfer.

