The SPIA_US_S Model#
Reference liability cash flow model for U.S. single premium immediate annuities.
SPIA_US_S is the executable counterpart of
products/immediate_annuity/technical-notes.md in the lifelib-products library.
It projects gross liability cash flows for a single SPIA already in payment: scheduled
instalments to the annuitant and, on a joint contract, to the surviving annuitant at a
survivor percentage; a certain-period or refund guarantee; a cash-refund lump sum on
death; optional commutation of the certain portion; and maintenance expense. There is
no premium income in the projection, no account value, no cash surrender value and
no lapse decrement — mortality is the only decrement, a position VM-22 makes
prescriptively for this reserving category [R2][REG-R36].
This product is the payout chassis of the library: the same survival-indexed payment engine serves deferred income annuities, annuitizations of deferred-annuity account values and supplementary contracts, which VM-22 places with SPIAs in a single Payout Annuity Reserving Category [R2].
Spaces. The model contains two:
DataReads the four input CSVs and holds their filename References. It takes no parameters, so each file is read once per model.
ProjectionThe by-contract projection, parameterized by
point_id:Projection[1]is an ItemSpace projecting model point 1. It reaches the input tables through itsdataReference, which resolves to the singleDataSpace.
The split matters for more than tidiness. Because Projection is parameterized,
every Projection[N] is a separate ItemSpace with its own cells cache; readers
placed there would re-read every file for every contract. In Data they are
evaluated once, however many contracts are projected.
Input data is external: CSVs in the model folder’s parent directory, read at run time rather than stored inside the model. The model folder itself holds no data, so the model and its inputs must travel together.
Projection basis. Monthly steps. t counts policy months from the annuity
date, t = 1, 2, ..., proj_len(), matching the technical notes’ own index; the
annuity date is collapsed onto the issue date, so there is no deferral period. Note
the contrast with lifelib’s CashValue_SE, whose months are 0-based: here the
month-zero state (lives_if(0, life) = 1, cum_annuity_pp(0) = 0) is the t <= 0
branch of each recursion, and every projected month is t >= 1.
proj_len() runs to the notes’ age stop rule on the youngest covered life, or
to the end of the effective certain period if that is later — stopping on the primary’s
age alone would truncate a younger joint annuitant’s tail. The age rule ends the
projection one month before any life attains the limiting age ω = 120 (696 months on
the anchor cell’s 65/62 pair, where the younger life is then 119), so the notes’ other
stop test, IF(t) < 1e-6, is not subsumed: model point 8 finishes at
pols_if(696) = 3.41e-06. A test pins the figure.
The monthly processing order follows the notes: the COLA increase applies at the start
of month 12(y-1)+1 for y >= 2; mortality is decremented at end of month;
the instalment falls at the end of the month on arrears timing (the default) and at the
start on advance timing, with survival measured at the payment point in both cases; the
cash-refund lump sum, any commutation and the maintenance expense accrue in the same
month. All cash flows are undiscounted — reserves and discounting are a separate
layer, and the only discounting in the model is contractual, inside the commuted value.
What is sourced and what is not. The contractual elements come from the composite’s
product documents: the five payout forms and the two survivor-reduction triggers, the
fixed compound COLA menu, the cash-refund and installment-refund definitions, NYL’s
premium / annualized income rule for the derived refund period, and the only
published SPIA surrender-charge schedule, 8% in contract year 2 grading to 0% from
year 10 [S1][S2][S3][S5]. Everything else is a standardization introduced for the
reference implementation, because no public source carries it: the initial income level
B(1) (no insurer publishes payout factors, so it is exogenous and unverifiable),
the mortality and improvement tables shipped here, the A/E factor of 1.084, the
commutation discount basis ([std] and [unverified]), the $60 p.a. maintenance
expense inflating at 2.5%, and the joint-life independence assumption.
This model is a mechanics demonstration, not a pricing or reserving result.
Not implemented, and named here so the omission is visible: the qualified-money overlay
(RMD limits on the certain period, the MDIB survivor cap, the post-death distribution
period — the qualified column is inert); the rate-driven dynamic commutation take-up
u(y, t), of which only the deterministic u(y) construction is built; a 10-year
CMT path (cmt10_shift is a flat scalar); the ALB/ANB conversion VM-22 supplies for
valuation-rate bucketing; the exclusion-ratio tax split, which is a policyholder
computation and generates no insurer cash flow; and every valuation layer — CARVM,
VM-22 CTE70, the 2012 IAR valuation table with its no-compound-rounding rule.
Model points. model_point_table.csv carries fifteen contracts on the same anchor
configuration — $100,000 premium, $6,000 p.a. initial income, joint primary male ANB 65
and joint annuitant female ANB 62, monthly in arrears, 3% compound COLA, survivor
percentage 2/3 — differing only in what the notes’ worked example and its “Known
modeling pitfalls” list vary: the reduction trigger, which life dies, the payout form,
arrears against advance timing, the payment frequency, the income level behind the
derived refund period, and whether the run is a deterministic scenario or a
probability-weighted table run.
Points 1 and 2 are the two columns of the worked-example table; points 3–6, 10, 11 and 13
are its traces and the pitfall cases; points 7, 8, 9 and 12 project on the shipped
mortality tables, point 8 being the anchor cell on that basis. Two points exist to hold
open a case the notes leave under-specified and would otherwise go untested: point 14 is
quarterly in advance, the frequency at which the notes’ t - 12/m survival point
and their own advance payment schedule disagree, and point 15 is the certain_only
form, where the notes’ expense formula IF(t) = max(C, l_alive) outlives the contract
that their own prose ends at n_eff. Both divergences are resolved in
Projection’s docstring and pinned by tests. A test asserts
every model point projects.
Verification. tests/test_immediate_annuity_us.py asserts every row and column of
the notes’ worked-example table — both trigger columns at t = 1, 12, 13, 14, 15, 24
and 25, to the cent — plus each of the five traces below it: the reversed death on which
the two triggers coincide, the COLA continuing after the survivor reduction, the 10-year
certain period deferring the reduction to t = 121, the cash-refund lump sum of
$93,485.00 on a death in month 14, and the derived installment-refund period of 200
months.
Example
>>> import modelx as mx
>>> model = mx.read_model("products/immediate_annuity/SPIA_US_S")
>>> model.Projection[1].result_cf()