The Projection Space#
The by-contract projection of the DIA_US_S model.
The Space is parameterized by point_id, so Projection[1] is an ItemSpace
projecting model point 1:
>>> Projection[1].result_annual() # the worked-example anchor cell
>>> Projection.point_id = 8 # or switch the default
t counts months from issue, and it is 0-based
t = 0, 1, 2, …, proj_len() are policy months from issue, indexed exactly as the
technical notes index them. t = 0 is the issue month and a real projected month, not
a recursion base case: the first premium is received at its start, deaths occur during
it, and the maintenance expense accrues in it. This is a deliberate departure from the
1-based t of Term_US_A and SPIA_US_S, and the reason is that
the notes’ T is a month index in the 0-based scheme — the anchor cell’s income
start month is T = 240 and its premiums fall at months 0 and 60. Renumbering to a
1-based grid would make T = 240 mean the 241st month and quietly move every option
window, premium date and payment date by one.
Two consequences to hold on to:
lives_if(t, life)is the probability of being alive at the start of montht— equivalently, of having survivedtelapsed months,lives_if(0) = 1. That is the same function of elapsed time asSPIA_US_S.lives_if, so the name carries its meaning across unchanged.lives_death(t, life) = lives_if(t) - lives_if(t + 1)— deaths during montht. In the 1-based chassis the same quantity isl(t-1) - l(t), because monthtspans elapsed[t-1, t)there and[t, t+1)here.
Input data
Inputs are external files: plain CSVs living in the model folder’s parent directory,
products/deferred_income_annuity/, read at run time rather than stored inside the
model. The model folder therefore holds nothing but formulas — no _data/, no IOSpec,
no embedded values — so a diff of the model shows logic changes only, and an input can
be edited or swapped without rewriting the model. This follows annuallife.TradLife_A;
contrast basiclife.BasicTerm_S, which keeps its inputs inside the model through
modelx’s IOSpec machinery.
The consequence worth knowing: the model is not portable on its own. Copying the
DIA_US_S folder without its parent’s CSVs produces a model that reads
and then fails on first evaluation.
Each table has a filename Reference and a reader Cells, both on
Data:
Reference |
Cells |
File |
|---|---|---|
model_point_file |
data.model_point_table() |
model_point_table.csv |
premium_schedule_file |
data.premium_schedule_table() |
premium_schedule.csv |
mort_table_file |
data.mort_table() |
mort_table.csv |
improvement_scale_file |
data.improvement_scale() |
improvement_scale.csv |
payout_factor_file |
data.payout_factor_table() |
payout_factor_table.csv |
rop_factor_file |
data.rop_factor_table() |
rop_factor_table.csv |
Naming
Cells names follow lifelib’s basiclife.BasicTerm_S and savings.CashValue_SE
wherever those models have an analogue, and — for everything in the income phase — they
follow SPIA_US_S exactly, because the payout phase of a DIA is a
single premium immediate annuity. The technical notes use compact actuarial symbols
instead. The mapping is:
Notes symbol |
Cells |
Meaning |
|---|---|---|
t |
(the cells argument) |
Policy month from issue |
y = floor(t/12) + 1 |
policy_year(t) |
Policy year containing t |
(none) |
duration(t) |
Completed policy years |
(none) |
duration_mth(t) |
Months elapsed at start of t |
x, x_2 |
age_at_entry(life) |
Issue ages (ANB), life 1 or 2 |
x(t) |
age(t, life) |
Attained age (ANB) |
(none) |
sex(life) |
Sex of each covered life |
(none) |
is_joint() |
Two covered lives |
market_type |
market_type() |
NQ / TradIRA / RothIRA / QLAC |
f |
income_form() |
LO / LO_ROP / CR / IR / PC |
(f, payout part) |
payout_form() |
The payout stream f implies |
db_form |
db_form() |
ROP or NONE in deferral |
n |
certain_period() |
Elected certain period, years |
T |
income_start_mth() |
Income start month in force |
(T at issue) |
income_start_mth_init() |
Income start month at issue |
(T for a premium at t) |
income_start_mth_at(t) |
Start month a premium is priced to |
m |
payment_freq() |
Payments per year |
pay_timing |
payment_timing() |
arrears / advance |
c |
cola_rate() |
Fixed compound COLA rate |
(1+c)^floor((t-T)/12) |
cola_factor(t) |
COLA escalation factor |
delta / survivor_pct |
survivor_pct() |
Survivor percentage |
reduction_trigger |
reduction_trigger() |
either / primary |
P_k |
premium_pp(t) |
Premium paid in month t |
(the schedule) |
premium_by_mth() |
{month: premium} mapping |
CP(t) |
cum_premium_pp(t) |
Cumulative premiums, eq (7) |
B(t) |
annual_income(t) |
Guaranteed annual income, eq (8) |
l(t) |
lives_if(t, life) |
Survival to the start of month t |
q(t) |
mort_rate_mth(t, life) |
Monthly death probability |
(annual q) |
mort_rate(t, life) |
Annual rate after improvement |
q_x (table) |
mort_rate_base(t, life) |
Base-table annual rate |
2012 (in q_x^(2012+n)) |
mort_table_year |
Base year of the shipped table |
G2_x |
improvement_rate(t, life) |
Generational improvement rate |
(A/E) |
mort_ae_factor |
A/E factor, 1.00 [std] |
omega |
omega_age |
Limiting age, 120 |
(d_i) |
lives_death(t, life) |
Deaths during month t |
l_last(t) |
lives_if_last(t) |
At least one life alive |
d_last(t) |
lives_death_last(t) |
Last-death density |
(in force) |
pols_if(t) |
Contracts in force |
i_p, v |
pricing_rate |
Pricing interest rate, 4.75% |
v^d |
pricing_disc_factor(yrs) |
Pricing discount factor |
L |
expense_load |
Expense and profit load, 6.0% |
d_k = (T - t_k)/12 |
deferral_yrs(t) |
Deferral of a premium at t |
_d p_x |
surv_to_income_start(t) |
Survival over the deferral |
a^(m)_y(f; i) |
annuity_factor(…) |
Payout APV, eq (2) generalized |
(same, at T) |
payout_factor() |
Payout factor in force |
(same, computed) |
payout_factor_calc() |
Payout factor by eq (2) |
A_rop(x, d; i) |
rop_factor(t) |
ROP factor in force, eq (3) |
(same, computed) |
rop_factor_calc(t) |
ROP factor by the mid-band rule |
a_def(x, d, f; i) |
annuity_factor_def(t) |
Deferred APV, eq (1) |
pr(x, d, f) |
purchase_rate(t) |
Purchase rate, eq (4) |
n_g (pricing) |
guarantee_mths_pricing() |
n_g in the kernel, eq (5) |
(“three iterations”) |
pricing_iters |
Fixed points taken on eq (5) |
n_g (realized) |
guarantee_yrs() |
CP(T)/B(T), eq (5) |
n_R |
certain_mths_refund() |
Derived refund period, months |
n_eff |
certain_mths_eff() |
Effective certain months |
C(t) |
certain_floor(t) |
Certain-floor indicator |
L(t), L_pay(t) |
payment_factor_life(t) |
Life-contingent payment factor |
Phi(t) |
payment_factor(t) |
max(C(t), L(t)) |
inst(t) |
annuity_pp_sched(t) |
Scheduled instalment |
(inst as payable) |
annuity_pp(t) |
Instalment after a blackout |
(inst after commutation) |
annuity_pp_paid(t) |
inst * (1 - theta_cum * C) |
G(t) |
cum_annuity_pp(t) |
Cumulative scheduled instalments |
RG(t) |
refund_balance(t) |
Remaining refund balance |
phase(t) |
phase(t) |
DEFERRAL / PAYOUT / TERMINATED |
(t >= T) |
is_payout(t) |
In the income phase |
T (payment dates) |
is_payment_mth(t) |
Month t is a payment date |
(payment point) |
payment_surv_mth(t) |
Elapsed month survival is at |
Baa(t_e) |
baa_yield |
Moody’s Baa yield at t_e |
s_adj |
adjust_spread |
Repricing spread, 100 bp |
i_e |
repricing_rate() |
Start-date repricing rate, eq (10) |
B’/B |
adjust_income_factor() |
Repricing factor, eq (11) |
t_e |
adjust_mth() |
Start-date exercise month |
T’ |
adjust_start_mth() |
New income start month |
t_a |
accel_mth() |
Acceleration exercise month |
n_a |
accel_months |
Payments accelerated, 6 |
Cost(t_a) |
accel_cost() |
Acceleration cost, eq (12) |
accel_blackout(t) |
accel_blackout(t) |
Payments suspended |
(Phi, frozen at t_a) |
accel_payment_factor(t) |
Weight on an accelerated payment |
t_c |
commute_mth() |
Commutation exercise month |
r_ref(t) - r_ref(0) |
ref_rate_shift |
Reference-rate rise since issue |
i_c(t) |
commute_disc_rate(t) |
Commutation rate, eq (13) |
m_c |
commute_margin |
Commutation margin, 50 bp |
CV(t) |
commuted_value(t) |
Commuted value, eq (14) |
theta_cum(t) |
commute_frac_cum(t) |
Cumulative commuted fraction |
(l_last(t_c)) |
commute_weight(t) |
Cohort a commutation extinguishes |
E[COMM(t)] |
commutations(t) |
Commutation paid to the owner |
Limit(year) |
qlac_premium_limit |
QLAC premium limit, $210,000 |
qlac_room(t) |
qlac_room(t) |
Remaining QLAC premium room |
(age 85 outside date) |
qlac_max_start_age |
Latest QLAC income start age |
(QLAC conditions) |
qlac_flags() |
QLAC compliance failures |
(premium income) |
premiums(t) |
l(t) * premium paid at t |
(deferral DB) |
claims(t, “DEATH”) |
Return of CP(t) on death |
(refund benefit) |
claims(t, “REFUND”) |
Cash-refund lump sum |
(per contract) |
claim_pp(t, kind) |
Claim amount per contract |
E[income] |
annuity_payments(t) |
Expected income outgo |
e, g |
expense_maint, inflation_rate |
Maintenance expense and escalation |
E[EXP(t)] |
expenses(t) |
Maintenance expense |
(total outgo) |
liability_outgo(t) |
Gross liability outgo |
(none) |
liability_cf(t) |
Net stream, outgo positive |
(none) |
net_cf(t) |
Premiums less outgo |
Seven names in that table needed care.
l and L differ in the notes only by case — the survival probability versus the
life-contingent payment factor — so they become lives_if and
payment_factor_life, exactly as in SPIA_US_S. L is also the
expense and profit load in equation (4); that one is the Reference expense_load.
B is the guaranteed annual income and B(t)/m is one instalment; the notes
never name the instalment, so it takes the chassis name annuity_pp_sched. Note that
annual_income(t) is indexed by month, not by policy year as in
SPIA_US_S, because a DIA’s income level changes when a premium is paid
and premiums fall on months.
n_g is two different quantities in the notes and they must not be conflated.
guarantee_mths_pricing() is the guarantee period fed into the pricing kernel by
equation (5) — a fixed point, since n_g = CP(T)/B and B depends on n_g.
guarantee_yrs() is the realized value once B is known, the 3.3891 years the
worked example quotes. And certain_mths_eff() — the projection’s certain floor —
is zero on a cash-refund contract, because a cash refund is a lump sum at death, not
a stream of guaranteed payments. Equation (5) is a pricing approximation for both refund
forms; only installment refund actually pays a certain stream.
theta appears nowhere in the DIA notes. theta_cum, the cumulative commuted
fraction, is inherited from the immediate-annuity notes that these notes incorporate by
reference for the whole income phase; the DIA notes write equations (13) and (14) with
no symbol for the fraction at all. The cells keeps the chassis name,
commute_frac_cum(), so that the two models read alike.
Two cohort weights carry no notes symbol because the notes have no need of one: a SPIA
is already in payment and its whole model point reaches the income phase together.
surv_to_payout() is l_last(T), the cohort that ever reaches the income phase
and therefore the only cohort a guarantee period can pay; commute_weight() is
l_last(t_c), the sub-cohort still alive to exercise a commutation and therefore the
only cohort whose guarantee a commutation can extinguish. They are the same idea
applied at two dates, and getting either of them wrong moves the guaranteed leg without
moving anything else.
CP(T) is the DIA-specific change to the payout chassis and the notes’ own listed
pitfall: the refund base is cumulative premiums, not the initial premium. Where
SPIA_US_S writes premium_pp(), this model writes
cum_premium_pp(income_start_mth()).
The self-checks follow the library-wide check_* convention, which is
CashValue_SE.check_av_roll_fwd()’s: each check takes no argument and returns a
bool covering every projected t, so one test can call the same name across every
model in the library, and each carries a companion check_*_resid(t) returning the
signed float residual at a single t for the debugging session that follows a failure.
The bool is defined in terms of the residual, never the other way round.
check_lives_roll_fwd(), check_income_roll_fwd(),
check_payment_factor() and check_commutation_value() are the four here; there
is no check_av_roll_fwd, because there is no account value to roll forward.
Two pricing bases: tabulated and computed
The notes’ worked example is priced off illustrative factors it states outright —
a^(12)_80(cash refund, female; 4.75%) = 8.60, A_rop(60, 20) = 0.157900,
A_rop(65, 15) = 0.180200 — declaring them “mutually consistent illustrative values,
not table lookups”. Both readings are shipped, and which one applies is a model point
column:
factor_basis = "table"payout_factor()androp_factor()read the shipped factor tables, andannuity_factor_def()is the notes’ equation (1) literally,v^d * _d p_x * a^(m). Model points 1, 2 and 4. This is the basis that reproduces the worked example. It is single-life only: equation (1) factorizes the deferral survival out of the payout APV, which a joint status does not permit.factor_basis = "formula"payout_factor_calc()computes equation (2) androp_factor_calc()the notes’ mid-band approximation of equation (3), both off the shipped mortality table, andannuity_factor_def()values the whole deferred stream in one pass so joint forms work. Model points 3 and 5 onwards.
The shipped mortality table is [std], built so that the two bases agree where the
notes say they should: its rates over ages 60-84 are band-constant values reproducing
the notes’ five-year survival anchors exactly, and the geometric continuation past age
85 is calibrated so that equation (2) at the income start age returns 8.600 — the notes’
own illustrative payout factor. Where they then disagree is A_rop(60, 20); see
below.
The A_rop(60, 20) divergence is shipped, not resolved
The notes say A_rop was “computed from the same survival anchors with a mid-band
death-timing approximation”. Run that recipe and the twenty-year factor comes out at
0.161605, not the 0.157900 the notes print, while the fifteen-year factor comes
out at 0.180241 against a printed 0.180200 — agreement to four decimal places.
One of the two is arithmetically consistent with the stated recipe and the other is not.
Rather than choose, the model ships both: model point 1 takes the printed factors from
rop_factor_table.csv and reproduces the worked example, model point 3 is otherwise
identical and computes them. A test pins the gap open.
The purchase-rate rounding is a [std] convention, and it is worth 3.6 cents
The worked example computes B_1 = 100,000 x 0.321765 = $32,176.50 from a purchase
rate printed to six decimals. At full precision pr_1 = 0.3217647 and the same
premium buys $32,176.47; over both slices the difference is 3.6 cents on a
$44,259.65 income. The model point column purchase_rate_dp carries the
convention — 6 on point 1, blank (full precision) on point 2 — so the worked example
reproduces to the cent without the rounding being hidden.
The certain period is a floor, not a second stream
payment_factor(t) = max(certain_floor(t), payment_factor_life(t)) is inherited from
the payout chassis and is the notes’ first-listed payout pitfall. During the certain
period the full, unreduced instalment is payable regardless of survival; an additive
construction would pay 1 + L and silently double the guarantee. The max also
reproduces, with no extra logic, the rule that a survivor reduction falling inside a
guarantee period is deferred to the end of that period.
There is no lapse decrement, and adding one is a defect
“Lapse and surrender: exactly zero, at all durations. Not conservatism — there is no
surrender benefit to elect [R9][R13]. A nonzero lapse assumption in a DIA model is a
defect, not a margin.” There is no lapse_rate, no pols_lapse, no surrender cash
flow row and no account value roll-forward in this model, and none should be added:
the liability is a schedule of income slices keyed on (premium, purchase date, income
start date, income option), not a balance.
Cells Descriptions#
- age_at_entry(life)[source]#
x: issue age (ANB) of the primary (life = 1) or joint (life = 2) annuitant.
Age nearest birthday [std]: MassMutual states contract issue age on an ANB basis [S2], prescribed VM-22 payout mortality is ANB [R9], and the 2012 IAM Basic and Period tables were developed ANB [R15][REG-R59].
- reduction_trigger()[source]#
trig: whether the reduction is triggered by either death or the primary’s.
A switch, not a parameter: the two conventions coincide on the primary’s death and differ only on the secondary’s [S1][S2][S4].
- income_form()[source]#
f: the income option elected at issue and immutable thereafter [S1][S2][S4].
LOlife only;LO_ROPlife only with a 100% return-of-purchase-payments death benefit in deferral, the unbundled Pacific Life variant [S4];CRlife with cash refund;IRlife with installment refund;PClife with period certain.
- payout_form()[source]#
The payout stream the elected form implies: LO, CR, IR or PC.
LO_ROPcollapses toLOhere because its return of premium is a deferral death benefit carried bydb_form(); after the income start date it is a pure life annuity [S4]. The unbundling of the death benefit from the payout form is the product spec’s chosen parameterization, so that the two mortality exposures move independently.
- db_form()[source]#
The deferral-phase death benefit:
ROPorNONE.100% of cumulative premiums, no interest, lump sum, on every option except Life Only and Joint Life Only [S1][S2][S3][S4][R13 SS3.I(1)(a)]. This is the product’s central pricing fork: setting it to
NONEremovesA_ropfrom the numerator of equation (4) and buys strictly more income for the same premium.
- certain_period()[source]#
n: the elected certain period in years, 10-30 on PC forms [S2]; 0 otherwise.
- payment_timing()[source]#
Whether instalments fall in arrears (the [std] default) or in advance.
No retrieved DIA document states the convention; arrears matches
products/immediate_annuity/product-spec.mdso that one payout chassis serves both products.
- cola_rate()[source]#
c: the fixed compound annual increase, 0 or 1%-4%, elected at issue [S2][S3][S4].
Every observed option is a fixed compound escalator applied on each anniversary of the income start date, not a CPI adjustment - the notes list treating it as index-linked among the product’s pitfalls.
- issue_year()[source]#
The calendar year of issue [std]; the anchor of the generational projection.
The improvement exponent is
calendar_year(t) - mort_table_year. The chassis calls its counterpartannuity_yearbecause a SPIA’s annuity date is its issue date; here the two are twenty years apart, so the generational anchor has to be named for the one that matters.
- mort_basis()[source]#
Whether mortality is the notes’ anchor curve or the generational projection.
anchor [std]: the shipped table is used as a period table, which is what reproduces the worked example - its survival anchors are a single non-generational curve. generational: the notes’ prescribed construction
q^(2012+k) = q^(2012) * (1 - G2_x)^k[R9][REG-R59] is applied with the shipped illustrative improvement scale. Both are shipped because the notes prescribe the second and verify against the first.
- factor_basis()[source]#
Whether the pricing factors are table lookups or formula computations.
table:
payout_factor()androp_factor()read the notes’ illustrative factors andannuity_factor_def()is equation (1) literally - the basis that reproduces the worked example, single-life only. formula: both are computed from the shipped mortality table by equations (2) and (3). See the Space docstring.
- purchase_rate_dp()[source]#
Decimal places the purchase rate is rounded to, or None for full precision.
[std]. The worked example computes
Bfrom a six-decimal purchase rate; carrying full precision instead moves the anchor cell’s annual income by 3.6 cents. Blank on the model point returns -1, meaning no rounding: modelx forbids a cells returningNone, so every “not set” on this Space is a negative sentinel.
Premiums paid to any other contract intended to be a QLAC [R1 (q)(2)(ii)(B)].
The QLAC dollar limit is reduced by premiums paid to any other QLAC under any 401(a), 403(a), 403(b), 408 or governmental 457(b) arrangement; the issuer may rely on the owner’s written representation [R5 (h)(2)].
The selected contract’s premium slices, ordered by payment month.
A DIA is a flexible-premium contract - “any number of premiums during the deferral period” [S2][S3][S4][R13] - so the schedule is a separate long table rather than a fixed set of model point columns.
The premium schedule collapsed to a
{month: amount}mapping.Built once per contract so that
premium_pp()is a dictionary lookup rather than a DataFrame filter in every one of the projected months.
P_k: the premium per contract paid at the start of month t; 0 if none falls.
CP(t): cumulative premiums paid per contract, equation (7).
CP(t) = CP(t-1) + sum of P_k at t,CP(-1) = 0. This is both the deferral death benefit base and - att = T- the refund base. For a flexible-premium DIA the refund base is CP(T), not the initial premium, which is one of the notes’ listed pitfalls [S2][S4].
- duration_mth(t)[source]#
Months elapsed from issue at the start of month t; equal to t.
tis 0-based here, so the identity is trivial - the cells exists so that the monthly models share one vocabulary.
- horizon_mths()[source]#
Months from issue to the limiting age of the youngest covered life.
t/12 + x_i > omega_agefor every covered life is the payout chassis’ stop rule; stopping on the primary’s age alone would truncate a younger joint annuitant’s tail.
- proj_len()[source]#
The last projected month index:
truns 0, 1, …,proj_len().The mortality horizon, or the end of the guarantee period if that is later. Both are age- or contract-driven rather than derived from the projection they bound, so
proj_len()never depends on the survival path.
- income_start_mth_init()[source]#
T at issue: the income start month elected at issue,
13 <= T <= 360[S2][S4].Tis a month index, so the income start date is the start of monthT, exactlyT/12years after issue. Under arrears the first instalment falls one payment period later - atm = 12, at the end of monthT.
- adjust_mth()[source]#
t_e: the month a start-date adjustment is exercised; -1 if it never is.
One-time, deferral only, excluded on Life Only and Joint Life Only [S1][S3][S4]. The incidence of the exercise - the notes’ 1.5% p.a. take-up with its 60/40 direction split, rate multipliers and health-selection overlay - is a cohort- splitting behavioural construction and is not implemented; the exercise is named deterministically on the model point instead, and is off by default.
- adjust_start_mth()[source]#
T’: the new income start month after an adjustment; -1 if there is none.
|T' - T| <= 60months,T'at least 13 months after the last premium, and inside the maximum deferral and the maximum income start age [S1][S2][S3][S4].
- income_start_mth()[source]#
T in force: the adjusted start month if one is exercised, else the elected one.
- income_start_mth_at(t)[source]#
The income start month a premium paid in month t is priced to.
A premium bought before the adjustment was priced to the original date; one bought after it is priced to the new date [R13 SS3.B(1)(b)].
- repricing_rate()[source]#
i_e = Baa(t_e) - s_adj: the rate a start-date adjustment is repriced at, eq (10).
The disclosed recipe keys on Moody’s Seasoned Baa Corporate Bond Yield at the request date less a contractual interest-rate-change adjustment [S1][S2]; the adjustment is named but never quantified in any retrieved source, so the 100 bp spread is [std]. The base run sets
baa_yieldso thati_e = i_p- no rate movement since issue - because the notes supply no Baa value [std].
- accel_mth()[source]#
t_a: the month payment acceleration is exercised; -1 if it never is.
Payout phase, attained age 59 1/2 or over, nonqualified, monthly frequency [S1][S2][S3][S4]. The notes’ 2% p.a. take-up among eligible contracts is a cohort-splitting construction and is not implemented; the exercise is named deterministically on the model point and is off by default.
- commute_mth()[source]#
t_c: the month a commutation is exercised; -1 if never (extended case only).
Offered by one of the four composite sources [S4][S5] and prohibited on a QLAC after the required beginning date [R1 (q)(1)(iv)]. The notes’ 1.5% p.a. take-up is not implemented, for the same reason as the other two options.
- commute_frac()[source]#
The fraction of the remaining guaranteed payments commuted; up to 100% [S4][R13].
- mort_rate_base(t, life)[source]#
The base-table annual mortality rate for
lifein the year containing month t.The shipped table is an illustrative [std] annuitant table, not the 2012 IAM Basic table the notes prescribe: licensed tables may not be embedded, and the Basic table is in any case printed in no source this library holds - A-821 prints the loaded Period Table only [REG-R153]. Its rates over ages 60-84 are band-constant values that reproduce the notes’ five-year survival anchors exactly. Rates at and above the limiting age are 1.
- improvement_rate(t, life)[source]#
G2_x: the generational improvement rate for
lifein the year containing t.The shipped scale is an illustrative [std] stand-in for Projection Scale G2 and is read only when
mort_basis() == "generational". Scaling it is the notes’ first listed sensitivity: Scale G2 is fixed and dated, and the 2020-2024 payout experience study says it has over-projected improvement [R15][REG-R61].
- mort_rate(t, life)[source]#
The annual mortality rate applied to
lifein the year containing month t.On the generational basis the notes’ construction
q_x^(2012+k) = q_x^(2012) * (1 - G2_x)^k[R9][REG-R59], withk = calendar_year(t) - mort_table_year, so each attained age in each future calendar year uses its own improved rate. On the anchor basis the table is used unprojected, which is what the worked example’s survival curve is.mort_ae_factoris the experience A/E overlay, [std] at 1.00 - the notes set the deferral-phase A/E at 1.00 and give no number for the payout phase, the 2020-2024 study being cited without a factor [R15][REG-R61].
- mort_rate_mth(t, life)[source]#
q(t) = 1 - (1 - q_annual)^(1/12): the probability of death during month t [std].
- lives_if(t, life)[source]#
l(t): the probability that
lifeis alive at the start of month t.l(0) = 1andl(t+1) = l(t) * (1 - q(t)), equation (9) - the only decrement in the product. There is no lapse and no annuitization decrement: VM-22’s standard-projection lapse section is “not applicable” to contracts with no account value or surrender benefit, and annuitization is prescribed at 0% [R9]. Returns 0 forlife = 2on a single-life contract.
- lives_death(t, life)[source]#
d(t) = l(t) - l(t+1): the deaths of
lifeduring month t.Note the index. Month
tspans elapsed months[t, t+1)on this 0-based grid, so the density is a forward difference; the 1-based chassis writes the same quantity asl(t-1) - l(t).
- lives_if_last(t)[source]#
l_last(t): the probability at least one covered life is alive at the start of t.
l_1 + l_2 - l_1*l_2on a joint contract, under joint-life independence [std];l_1on a single-life contract, which is why no separatel_alivecells is needed.
- lives_death_last(t)[source]#
d_last(t) = l_last(t) - l_last(t+1): the last-death density in month t.
Also the death that terminates the income stream and triggers the refund, and - on a joint contract, where the first death leaves the contract running on the option chosen at issue [S2] - the death that triggers the deferral death benefit.
- pols_if(t)[source]#
Contracts in force at the start of month t:
pols_if_init * l_last(t).The notes weight the maintenance expense by
l(t)alone. The payout chassis instead usesmax(C(t), l_alive(t)), so that a certain-period obligation running on after the last death still carries expense; the two notes files differ here and this model follows its own. They coincide on every form without a certain floor, the anchor cell included.
- pricing_disc_factor(yrs)[source]#
v^yrs on the pricing interest rate i_p [std].
Contractual, not a valuation discount: this is the kernel that turns a premium into guaranteed income. No DIA source discloses a pricing rate - the Compact relieves the insurer of disclosing the deferral-period basis altogether [R13 SS1.B(1)(a)] - so 4.75% is a modeling assumption, readable as a long-duration general-account portfolio yield net of default costs.
- deferral_yrs(t)[source]#
d_k = (T - t)/12: the remaining deferral in years of a premium paid in month t.
- surv_to_income_start(t)[source]#
_d p_x(t): survival of the primary life from month t to the income start month.
A ratio on the single survival curve anchored at issue, which is how the notes derive
_15p_65 = _20p_60 / _5p_60 = 0.715000/0.975000 = 0.733333.
- annuity_factor(start_mth, guar_mths, int_rate, from_mth)[source]#
The APV at month
from_mthof $1 per annum of income beginning atstart_mth.Equation (2) generalized. The k-th instalment is certain while
k * 12/mdoes not exceedguar_mthsand life-contingent after that, the payment factor being the samemax(C, L)the projection uses, with the survival probabilities taken conditional on being alive atfrom_mth:a = sum over k of (1/m) * v^((s_k - from_mth)/12) * max(C_k, L_k)
Called with
from_mth = start_mthit isa^(m)_y(f; i), the payout factor at the income start age; called withfrom_mth = tit isa_def(x(t), d, f; i)directly, which is what makes the joint forms work. Equation (15)’s expansiona_def^(both) + s * sum_i a_def^(only i)is algebraically this sum with the either trigger, so no separate joint kernel is needed. The convertible variant, equation (16), is not implemented; the base is non-convertible [std].The certain instalments are weighted by the probability that the income phase is reached at all - see
surv_to_payout(). Without that weight the deferred factor would treat a guarantee period as payable to an annuitant who died in the deferral, and equation (1)’sv^d * _d p_x * a^(m)would not be recovered.
- guarantee_mths_pricing()[source]#
n_g in months as fed into the pricing kernel, equation (5).
n_g = CP(T) / BputsBon both sides, and the notes’ listed pitfall is “failing to iterate leaves a systematic bias in the derived guarantee period and hence in the income”. Resolved here by fixed-point iteration from a life-only start, the notes’ own prescription (“three iterations from a life-only start are ample”) -pricing_iterscarries the count. The equation (5) approximation is used for both refund forms [std]; the exact cash-refund factor of equation (6) is not implemented.
- payout_factor_calc()[source]#
a^(m) at the income start age computed from the shipped table, equation (2).
- payout_factor()[source]#
a^(m)_{x+d}(f; i_p): the payout annuity factor in force.
On the table basis the notes’ illustrative value - 8.60 for the anchor cell’s
a^(12)_80(cash refund, female; 4.75%)- read from payout_factor_table.csv [std]; on the formula basispayout_factor_calc(). No insurer publishes payout factors for this product and the Compact does not require the basis to be disclosed at all [R13 SS1.B(1)(a)].
- rop_factor_calc(t)[source]#
A_rop for a premium paid in month t, by the notes’ mid-band approximation.
Equation (3) is a monthly sum; the notes state their illustrative factors were “computed from the same survival anchors with a mid-band death-timing approximation”, which is what this implements: deaths in each five-year band of the deferral, discounted from the midpoint of the band. Zero when the contract carries no deferral death benefit, which is the
1{db_form = ROP}indicator of (4).The exposure is the last-death curve
l_last, not the primary life’s. Equation (3) is written on a single lifexand the notes never restate it for a joint contract, but the benefit this factor has to pay for is the oneclaims()projects, and on a joint contract that attaches to the last death - “if one annuitant dies in deferral the contract continues on the option chosen at issue” [S2], so the return of premium falls due only when the contract ends. Pricing the primary life’s death density instead would charge for an exposure 2.5 times the one projected (0.161605 against 0.064105 on the joint model point) and understate the income the premium buys by about 12.5% - the notes’ own listed pitfall that the deferral death benefit’s cost “belongs in the purchase rate (4) and in the projected cash flows”, read as requiring the same benefit in both. On a single-life contractl_lastis the primary curve, so this changes nothing on the worked example.
- rop_factor(t)[source]#
A_rop(x(t), d; i_p): the return-of-premium factor for a premium paid in month t.
On the table basis the notes’ illustrative values from rop_factor_table.csv [std]; on the formula basis
rop_factor_calc(). The two disagree on the twenty-year slice - 0.157900 printed against 0.161605 computed - while agreeing to four decimals on the fifteen-year slice; both readings are shipped and a test pins the gap open. See the Space docstring.
- annuity_factor_def(t)[source]#
a_def(x(t), d, f; i_p): the APV at month t of $1 p.a. of income from T, eq (1).
On the table basis, equation (1) literally,
v^d * _d p_x * a^(m). That factorization is single-life only - a joint status does not separate into a deferral survival times a payout APV - so a joint model point must use the formula basis, where the whole deferred stream is valued in one pass byannuity_factor().
- purchase_rate(t)[source]#
pr(x(t), d, f): annual income bought per $1 of premium paid in month t, eq (4).
pr = [ (1 - L) - 1{ROP} * A_rop ] / a_def- the equivalence principle applied per premium, which is the core of the product: income is additive across slices, each priced at the annuitant’s attained age and the remaining deferral at its own payment date [R13 SS3.B(1)(b)][S3]. Nothing accumulates and there is no balance to roll forward. The whole kernel is [std]: no purchase-rate table was obtained and none is published.purchase_rate_dprounds the result, 6 on the worked example’s model point; see the Space docstring.
- adjust_income_factor()[source]#
B’/B on a start-date adjustment, equation (11).
Actuarial equivalence at the exercise month [std]:
B' = B(t_e) * a_def(x(t_e), (T - t_e)/12, f; i_e) / a_def(x(t_e), (T' - t_e)/12, f; i_e)
a_defdecreases in the deferral, so deferring the date raises the payment and advancing it lowers it, matching Pacific Life’s statement [S4][S5]. Two refinements the disclosed recipe does not mention are flagged rather than modeled, as the notes require: the return-of-premium exposure changes with the deferral length, andCPis unchanged so the derived guarantee period shifts.
- annual_income(t)[source]#
B(t): the guaranteed annual income purchased to date, before COLA, equation (8).
B(t) = B(t-1) + sum of P_k * pr(x(t_k), (T - t_k)/12, f),B(-1) = 0, plus the repricing factor of equation (11) in the month a start-date adjustment is exercised. Income is additive across slices and each slice is fully guaranteed from the moment its premium is paid [R13 SS3.H(1)].
- guarantee_yrs()[source]#
n_g realized: the derived guarantee period in years, equation (5).
CP(T) / B(T)on the refund forms - 150,000/44,259.65 = 3.3891 years on the anchor cell, so its cash-refund guarantee is exhausted during the fourth payment year, at attained age about 83.4. The elected period on a PC form, zero on a life only form. Distinct fromguarantee_mths_pricing(), which is the fixed point the kernel uses beforeBis known.
- is_payout(t)[source]#
Whether month t is in the income phase,
t >= T.Deaths in months
t < Tare deferral-phase deaths attracting the return of premium; deaths in monthst >= Tare payout-phase deaths attracting the form’s refund benefit.
- is_payment_mth(t)[source]#
Whether an instalment falls in month t.
Arrears: the k-th instalment falls at the end of month
T + k*(12/m) - 1, so the first lands at the end of monthTwhenm = 12- one payment period after the income start date, which is why policy year 21 of the anchor cell carries the first twelve payments. Advance: the k-th falls at the start of monthT + (k-1)*(12/m), one full payment period earlier.
- payment_surv_mth(t)[source]#
The elapsed month at which survival is measured for the instalment falling in t.
Arrears: the end of month t, which on this 0-based grid is elapsed month
t + 1. Advance: the start of month t, elapsed montht. Using end-of-period survival for advance payments understates the liability by about one period’s mortality per payment.
- cola_factor(t)[source]#
(1+c)^floor((t-T)/12): the COLA escalation applying in month t.
Fixed compound, on each anniversary of the income start date, elected at issue and irrevocable [S2][S3][S4]. One in the deferral and in the first payout year.
- annuity_pp_sched(t)[source]#
inst(t) = B(t)/m * (1+c)^floor((t-T)/12): the scheduled instalment per contract.
Zero outside payment months. Scheduled, not paid: an acceleration blackout or a commutation may suppress it.
- cum_annuity_pp(t)[source]#
G(t): cumulative scheduled instalments per contract through the end of month t.
A deterministic as-if-alive schedule - instalments payable while any covered life is alive follow the deterministic escalation path - so the refund balance needs no path simulation.
- certain_mths_refund()[source]#
n_R: the certain period an installment refund derives, in months.
Payments “continue in the same amount and frequency until they equal the purchase payments” [S2], so
n_Ris the offset of the first payment month at which cumulative instalments reachCP(T). Searched rather than closed so that a COLA path still resolves, and bounded by the mortality horizon so that it never depends onproj_len(), which depends on it. [std]: the month grid rounds the guarantee up to a whole instalment rather than trimming the last one, which is what the notes’ “exactly a certain-and-life annuity with n_g = CP(T)/B” framing implies; the payout chassis trims instead.
- certain_mths_eff()[source]#
n_eff: the effective certain period in months, by payout form.
12 * non a period certain form;n_Ron installment refund; zero on cash refund and life only. A cash refund is a lump-sum shortfall paid at death, not a stream of guaranteed payments - equation (5) treats it as certain-and-life for pricing only, and carrying that into the projection would pay the guarantee twice.
- payment_factor_life(t)[source]#
L(t): the life-contingent payment factor for the instalment falling in month t.
Survival is measured at
payment_surv_mth(), not at t. By form and trigger:single life: L = l_1 joint, trig = either: L = l_1*l_2 + delta*(l_1 + l_2 - 2*l_1*l_2) joint, trig = primary: L = l_1 + delta*(1 - l_1)*l_2
The either form pays the full instalment while both are alive and delta while exactly one is; the primary form pays in full while the primary lives whatever the joint annuitant’s status. The two coincide on the primary’s death and differ only on the secondary’s, which is why the trigger is a switch and not a footnote. Zero before the income start date.
- surv_to_payout()[source]#
l_last(T): the probability that the income phase is ever reached.
The one place the payout chassis cannot be carried across unexamined. A SPIA is already in payment, so
SPIA_US_Sweights its certain floor by nothing:C(t)is a bare indicator. A DIA’s guarantee period only begins if the contract reaches the income start date - an annuitant who dies in the deferral receives the return-of-premium benefit and no instalment is ever paid - so on an expected basis the certain instalments carry this factor. Without it a period certain or installment refund contract would pay its guarantee with probability one and overstate the liability by the whole deferral mortality. The DIA notes inheritL_payand its “certain inside a guarantee period” wording from the immediate-annuity notes without adjusting for the deferral; this is that adjustment, and it is the same weight equation (1) applies through_d p_x.
- payment_factor(t)[source]#
Phi(t) = max(C(t) * l_last(T), L(t)): the master payment factor.
The
maxmakes the certain period an annuity-certain floor rather than an additional stream: during the guarantee the full unreduced instalment is payable regardless of survival after the income start date, and themaxprevents paying1 + L. An additive construction silently doubles the guarantee - the payout chassis’ first-listed pitfall - and, because the floor pays the unreduced instalment, it also reproduces with no extra flag the rule that a survivor reduction inside a guarantee period is deferred to the end of that period [S1][S3]. Thel_last(T)weight on the floor is the DIA’s own; seesurv_to_payout().
- refund_balance(t)[source]#
RG(t) = max(0, CP(T) - income scheduled through the end of month t-1).
The
t-1is the arrears convention: an instalment due at the end of the death month has not been paid, so a death betweenTandT + 1/myields a cash refund of the fullCP(T). On advance timing an instalment paid at the start of the death month has been paid, so the balance is measured attthere or the lump sum is overstated by one instalment.
- accel_blackout(t)[source]#
Whether payments are suspended in month t by a payment acceleration.
Six monthly payments are made in one sum at
t_aand no payment is made for the following five months [S1][S4]. The feature is expressly “not a liquidity feature” [S2] - a timing shift, not a withdrawal - and modelling it as a withdrawal is one of the notes’ listed pitfalls.
- accel_cost()[source]#
Cost(t_a): the economic cost of a payment acceleration, equation (12).
(B/m) * sum over j = 1..n_a-1 of [ 1 - v^(j/m) * _{j/m} p_x(t_a) ]. Accelerated payments are made unconditionally, so the insurer forgoes the survivorship and interest discount on payments 2 throughn_a. Small but real, and the reason the feature is capped in uses and gated at age 59 1/2 - the gate itself being driven by the IRC SS72(q) 10% additional tax [R8][REG-R55]. Reported, not added to the cash flows: the cost is already implicit in paying them early throughaccel_payment_factor(). Only the mortality element of it surfaces in the projected cash flows, because they are undiscounted - on the anchor configuration 435.11 per exercising contract here against 153.52 of extra lifetime income outgo, the difference being the interest termv^(j/m)that an undiscounted projection has nowhere to put.
- accel_payment_factor(t)[source]#
Phi for an instalment scheduled in month t but paid in the acceleration lump sum.
The six instalments are paid in one sum at
t_a[S1][S4], so the five that are pulled forward are paid unconditionally: their survivorship is frozen at the date the lump sum falls -payment_factor_life(t_a), the same weight the first instalment of the block carries - instead of being measured at their own scheduled dates. That frozen weight is the mortality element equation (12) prices; the interest element of (12) does not appear in an undiscounted projection.A guaranteed instalment is untouched. Its weight is already
C(t) * l_last(T), independent of survival after the income start date, so accelerating it forgoes no survivorship - which is why themaxis taken against the certain floor exactly aspayment_factor()does, and why equation (12)’s cost is zero inside a guarantee period and strictly positive outside it. Zero when no acceleration is exercised.
- commute_frac_cum(t)[source]#
theta_cum(t): the cumulative commuted fraction applying in month t.
Zero before the exercise month and
commute_fracfrom it, the exercise falling at the start of the month and so reaching that month’s own instalment - the notes’ processing order puts option exercises ahead of the income payment. It multipliesC(t)inannuity_pp_paid(), so it reaches guaranteed instalments only: if the annuitant is alive when the would-be guarantee period ends, income resumes until death, the life-contingent tail not being commuted [S4]. Applying it to that tail contradicts every retrieved contract.
- commute_weight(t)[source]#
l_last(t_c): the expected size of the cohort a commutation extinguishes in month t.
The one weight the payout chassis has no need for, and the mirror image of
surv_to_payout(). A commutation is exercised by an owner who is alive att_c[S4], so it can only extinguish the guaranteed instalments of thel_last(t_c)sub-cohort. The rest of the cohort that reached the income phase -l_last(T) - l_last(t_c), contracts whose annuitant died betweenTandt_c- keeps its guarantee running to the beneficiary and is unaffected. On the shipped commutation model point those two weights are0.715000and0.680338, andl_last(t_c)is the one this model applies: applying the commutation to all of the first would cancel 4.85% more guaranteed liability than the commuted value pays for.Zero outside a guarantee period, before the exercise month, and on a contract that never commutes. Bounded above by
payment_factor()becauset_c >= T, so the surviving expected instalment is never negative.
- commute_disc_rate(t)[source]#
i_c(t) = i_p + max(0, r_ref(t) - r_ref(0)) + m_c: the commutation rate, eq (13).
One-sided by construction - it rises with rates and does not fall - implementing the Compact’s stated intent that the adjustment “reduce interest risk in the event of rising interest rate after issue” and its required disclosure that “the higher the interest rate the lower the commuted value” [R13 SS3.F(7)]. The actual contractual formula is not published anywhere [S4][S5], so this is [std] and [unverified];
ref_rate_shiftis a flat scalar because a reference-rate path would be another input table.
- commuted_value(t)[source]#
CV(t): the commuted value of the remaining guaranteed payments, equation (14).
sum over j in J_g(t) of (B/m) * (1 + i_c(t))^(-(j - t)/12), whereJ_gis the set of remaining guaranteed payment months. Zero on a form with no guaranteed stream: a cash refund is a lump sum at death, not a schedule of payments, so there is nothing to commute. Non-zero only in the exercise month.
- commutations(t)[source]#
E[COMM(t)]: the commutation payment made to the owner in month t.
CV(t) * l_last(t): the commuted value is paid to the cohort alive to exercise, which is the samel_last(t_c)weightcommute_weight()removes from the guaranteed instalments. The two must agree, andcheck_commutation_value()asserts that they do - discounted ati_c, the value paid out equals the value suppressed. Zero in the base run - commutation exists only in the extended case [S4][S5] - and prohibited on a QLAC after the required beginning date other than a rescission period not exceeding 90 days [R1 (q)(1)(iv)][R2 SS202(a)(4)].
- annuity_pp(t)[source]#
The instalment per contract payable in month t, before commutation and survival.
Equal to
annuity_pp_sched()except during an acceleration blackout, where the payment has already been made in one sum at the exercise month.
- annuity_pp_paid(t)[source]#
inst(t) * (1 - theta_cum(t) * C(t)): the instalment a commuting contract gets.
A commutation reduces guaranteed instalments only; the life-contingent payments after the guarantee period are untouched, which is what makes the tail resume. Per contract, and only for a contract that actually commuted - which is a sub-cohort, not the whole model point, so
annuity_payments()applies it throughcommute_weight()rather than to the expected instalment as a whole.
- annuity_payments(t)[source]#
E[income] in month t: the instalment weighted over the cohorts entitled to it.
Two cohort splits sit on top of the chassis’
inst(t) * Phi(t), and both are weights rather than per-contract adjustments:E[income](t) = pols * [ inst(t) * (Phi(t) - w_c(t)) not commuted + inst_paid(t) * w_c(t) ] commuted
w_c(t)iscommute_weight(), thel_last(t_c)sub-cohort that was alive to exercise the commutation;inst_paidis that sub-cohort’s reduced instalment. Multiplyinginst_paidby the whole ofPhiinstead - the obvious reading of the chassis formula - would cancel the guarantee of contracts whose annuitant died betweenTandt_cand could not have commuted.In the exercise month of a payment acceleration the following
n_a - 1instalments are added ataccel_payment_factor(), the survivorship frozen at the lump sum’s own date - they are paid whether or not the annuitant survives to their scheduled dates, which is precisely the mortality element equation (12) measures - and the months they would have fallen in pay nothing.
- claim_pp(t, kind)[source]#
The claim amount per contract in month t, by
kind."DEATH"- the deferral death benefit, 100% of cumulative premiums paid without interest as a lump sum at the end of the month of death [S1][S2][S3][S4][R13 SS3.I(1)(a)], zero once the income phase starts and zero on a no-death-benefit form."REFUND"- the cash-refund shortfallrefund_balance(), zero on any form other than cash refund, because installment refund and period certain deliver their guarantee as continued payments through the certain floor rather than as a lump sum.
- claims(t, kind=None)[source]#
Expected claim outgo in month t;
kind=Nonetotals every kind.Weighted by
lives_death_last(), the death that ends the contract: on a joint contract the first death leaves the contract running on the option chosen at issue [S2], so both the deferral death benefit and the refund attach to the last death. The two kinds are mutually exclusive by construction -"DEATH"is zero from the income start month and"REFUND"is zero before it.
Premium income in month t:
l(t) * sum of P_k at t, received at the start.Deterministic schedule with no attrition [std]: DIA subsequent premiums are wholly discretionary and no source publishes a distribution of them. Dump-in risk differs structurally from a fixed deferred annuity’s - each premium is priced at then-current rates, so there is no rate guarantee to select against unless the contract guarantees paid-up rates for future premiums [R13 SS1.B(1)(h)], which is off in the base [std].
- expenses(t)[source]#
E[EXP(t)] = (e/12) * (1+g)^(y-1) * l(t): maintenance expense in month t.
$50 per contract per year escalated at 2.5%: VM-22 prescribes exactly that for individual Payout Annuity Reserving Category contracts, plus 7 basis points on a present-value base for contracts without an account value [R9]; adopting the prescribed figure as the best estimate is [std], and the 7 bp element is not implemented because this model computes no present-value base.
- liability_outgo(t)[source]#
Total gross liability outgo in month t, outgo positive.
Income payments, death and refund benefits, any commutation, and maintenance expense. There is no surrender cash flow row, and none should be added - the contract has no cash surrender value at any time before the income start date and none after [S1][S2][R13 SS3.P].
- liability_cf(t)[source]#
The net liability cash flow in month t, outgo positive:
-net_cf(t).Across this library
liability_cfis the net stream carried under the sign the notes that print one use, andnet_cfis the same stream income-positive; the invariantnet_cf(t) == -liability_cf(t)holds in every model that publishes both, and a conventions test asserts it.The distinction that matters here: the notes’ purpose statement projects “premiums in; deferral death benefits, income…” , so premium income belongs in the net stream. That makes this product differ from the
SPIA_US_Spayout chassis it otherwise follows, where the single premium is a pricing input and never a projected cash flow, so there gross outgo and the net stream coincide. The gross outgo alone isliability_outgo().
- net_cf(t)[source]#
Net cash flow to the insurer in month t: premium income less liability outgo.
The sign convention of
Term_US_A.net_cf. Unlike the immediate-annuity chassis this product does carry premium income in the projection, because premiums are flexible and fall throughout the deferral - so premium income nets here, andliability_cf()is this stream negated rather than the gross outgoliability_outgo().
- qlac_room(t)[source]#
qlac_room(t) = Limit - CP(t) - premiums paid to any other intended QLAC.
$200,000 as enacted, indexed with a base period of the calendar quarter beginning July 1 2022 and increments rounded to the next lowest multiple of $10,000; $210,000 for 2026 [R1 (q)(2)(ii), (q)(4)(ii)(A)][R2 SS202(a)(2)][R3][S4]. There is no percentage-of-account-balance limit: SECURE 2.0 SS202(a)(1) directed its elimination and the codified text has no percentage test, so a 25% rule, a $125,000 or $130,000 cap and an RMD age of 70 1/2 are all superseded arithmetic.
- qlac_flags()[source]#
The QLAC conditions this contract fails; an empty list when it complies.
The overlay generates no cash flows of its own - it caps premiums, restricts forms, constrains
T, disables features and raises compliance flags. Loss of QLAC status changes the owner’s RMD position, not the insurer’s liability cash flows. An excess premium ends QLAC status on the date paid unless returned by the end of the following calendar year; any other failure voids status retroactively to purchase [R1 (q)(4)(i)(B), (q)(4)(iii)(A)]. Empty on a contract that is not a QLAC.
- check_lives_roll_fwd_resid(t)[source]#
Roll-forward residual in month t, over both lives, plus the last-survivor identity.
sum_i [ l_i(t) - d_i(t) - l_i(t+1) ]and, on a joint contract,l_last(t) - [l_1 + l_2 - l_1*l_2]. Zero to floating point; non-zero if the independence construction and the per-life recursion ever disagree. The signed float at a singlet, for the debugging session that follows acheck_lives_roll_fwd()failure.
- check_lives_roll_fwd()[source]#
Truewhen the in-force roll-forward closes in every projected month.No argument and a bool, the library-wide
check_*shape taken fromCashValue_SE.check_av_roll_fwd(): one call coverst = 0 .. proj_len(), so the same name can be asserted across every model in the library.check_lives_roll_fwd_resid()carries the signed residual at a singlet. Mortality is the only decrement here, so a failure means the last-survivor construction and the per-life recursion have come apart.
- check_income_roll_fwd_resid(t)[source]#
B(t) - B(t-1)*adj - P_k*prin month t: the income roll-forward residual, eq (8).Zero to floating point. Non-zero would mean income appearing from somewhere other than a premium slice or the one permitted repricing - and nothing else can move it: there is no credited rate, no index, no charge and no balance in this product. The signed float at a single
t;check_income_roll_fwd()is the bool.
- check_income_roll_fwd()[source]#
Truewhen income moves only by a premium slice or the permitted repricing.No argument and a bool over every projected
t, the library-widecheck_*shape;check_income_roll_fwd_resid()returns the signed residual at onet. The tolerance is absolute rather than relative becauseBis a currency amount that only ever steps, never accrues.
- check_payment_factor_resid(t)[source]#
Phi(t) - max(C(t)*l_last(T), L(t))in month t: the double-count residual.Zero by construction. Computed anyway because an additive floor -
C + Linstead ofmax(C, L)- is the payout chassis’ first-listed pitfall and would show up here asmin(C, L).check_payment_factor()is the bool over everyt.
- check_payment_factor()[source]#
Truewhen Phi is the max of the two legs, never their sum, in every month.No argument and a bool, the library-wide
check_*shape. The comparison is exact rather than tolerant: the identity is arithmetical, so any residual at all is a defect and not a rounding artefact.
- check_commutation_value_resid(t)[source]#
PV_ic(instalments suppressed) - commutations(t)in month t: the exchange residual.A commutation is an exchange, not a benefit: the owner gives up guaranteed instalments and receives their present value at
i_c. On an expected basis the two legs must therefore carry the same cohort weight, and this residual is what catches it if they do not.The suppressed leg is read out of
annuity_payments()itself rather than re-derived - what the projection would have paid,inst(s) * Phi(s), less what it does pay - so weighting the suppression byl_last(T)while paying the commuted value tol_last(t_c)shows up here as a leak equal to the difference: 6,898.37 on the shipped commutation model point. The acceleration block is added back before the comparison so that the two options do not contaminate each other, though the contract’s own six-month interlocks make that combination unreachable [S4]. Zero to floating point at the exercise month, and zero everywhere else because both legs are.check_commutation_value()is the bool over everyt.
- check_commutation_value()[source]#
Truewhen the commutation exchange closes in every projected month.No argument and a bool, the library-wide
check_*shape;check_commutation_value_resid()returns the signed leak at onet. TriviallyTrueon a contract that never commutes, which is the base run - the residual is identically zero away from the exercise month.
- lives_if_ann(y)[source]#
l at the end of policy year y:
lives_if_last(12*y).The annual display’s survival column. Policy year
yspans months12(y-1) .. 12y-1, so surviving it means being alive at the start of month12y.
- claims_ann(y, kind=None)[source]#
Expected claim outgo over policy year y; the notes’
E[DB]when kind=”DEATH”.The monthly cash flows summed over the twelve months of the year. Because
CPis constant through a year whose premium falls at its start, this collapses to(l(y-1) - l(y)) * CP(y)on the annual grid - which is exactly how the worked example computes it.
- annuity_payments_ann(y)[source]#
The notes’
E[income]: expected income at the end of policy year y.An annual-payment display approximation, not the sum of the year’s monthly instalments: one year’s income
Bescalated for COLA and weighted by survival to the end of the year. The model runs monthly; this cells exists so that the notes’ annual display table can be reproduced and asserted directly. Zero in a policy year that ends before the income start month.
- result_pols()[source]#
Result table of survival probabilities and payment factors, indexed by month t.
- result_annual()[source]#
The technical notes’ annual display table, indexed by policy year.
One row per policy year with the columns the worked example prints: the attained age at the start of the year, the premium received at its start,
CP,B, the survival probability at the end of the year, the expected deferral death benefit paid during it, and the expected income at its end on the annual-payment display approximation. The projection itself is monthly; this is the display grid.