The Projection Space#

The by-contract projection of the PA_UK_S model.

The Space is parameterized by point_id, so Projection[1] is an ItemSpace projecting model point 1:

>>> Projection[1].result_cf()          # the worked-example scenario
>>> Projection.point_id = 2            # the same contract, probability-weighted

t counts months from the annuity start date, 1-based: t = 1 is the first projected month and t = 0 is the month-zero state used as the base case of every recursion (lives_if(0, life) = 1, cum_annuity_pp(0, kind) = 0).

Input data

Inputs are external files: plain CSVs living in the model folder’s parent directory, products/pension_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 PA_UK_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, reached here through the data Reference:

Reference

Cells

File

model_point_file

data.model_point_table()

model_point_table.csv

mort_table_file

data.mort_table()

mort_table.csv

Naming

Cells names follow lifelib’s basiclife.BasicTerm_S and savings.CashValue_SE wherever those models have an analogue, and follow SPIA_US_S — this library’s U.S. payout chassis — wherever the two products share machinery, so that the same concept has the same name on both sides of the Atlantic. The technical notes use compact actuarial symbols instead. The mapping is:

Notes symbol

Cells

Meaning

t

(the cells argument)

Month from the start date

y = ceil(t/12)

policy_year(t)

Policy year containing month t

(none)

duration(t)

Completed policy years, y - 1

(none)

duration_mth(t)

Months elapsed at end of month t

P

purchase_price()

Purchase price

x_a, x_d

age_at_entry(life)

Entry ages (ALB), life 1 or 2

(none)

age(t, life)

Attained age (ALB)

(none)

sex(life)

Sex of each covered life

theta_a, theta_d

rating_factor(life)

Enhanced/impaired multiplier

delta

dependant_pct()

Dependant’s percentage

(dependant_present)

is_joint()

Contract covers a dependant

overlap

overlap()

Dependant runs during guarantee

A(1)

annual_income_init()

Starting annualized income

A(y)

annual_income(y)

Annualized income in year y

g

escalation_rate()

Fixed escalation rate

escalation_type

escalation_type()

level / fixed / rpi_catchup / lpi5

I(k)

rpi_index(k)

RPI reference level at anniv. k

peak

rpi_peak(k)

Running peak of the index

m

payment_freq()

Payments per year

timing

payment_timing()

arrears or advance

T

is_payment_mth(t)

Month t is a payment date

(payment point)

payment_surv_mth(t)

Month survival is measured at

inst(t)

annuity_pp(t)

Scheduled instalment

n

guarantee_mths()

Guarantee period in months

C(t) = 1{t <= n}

certain_floor(t)

Annuity-certain floor indicator

l_a(t), l_d(t)

lives_if(t, life)

Survival probability

d_a(t), d_d(t)

lives_death(t, life)

Death density

d_last(t)

lives_death_last(t)

Last-death density

(none)

lives_if_last(t)

At least one covered life alive

max(C, l_a)

payment_factor(t)

Annuitant payment factor

(none)

payment_factor_life(t)

l_a alone, before the floor

w(t)

overlap_gate(t)

Dependant-stream availability

delta(1-l_a)l_d w

dependant_factor(t)

Dependant payment factor

G(t)

cum_annuity_pp(t, kind)

Cumulative scheduled instalments

v

vp_pct()

Value-protection percentage

vp_basis

vp_basis()

first_death or last_survivor

VPbal(t)

vp_balance(t)

Value-protection balance

h(t)

mths_since_payment(t)

Complete months since last payment

proportion

proportion()

Proportionate final payment

(none)

next_payment_mth(t)

The next scheduled payment month

q_x (table)

mort_rate_base(t, life)

Base-table annual rate

alpha

annuitant_adj

Population-to-annuitant factor

f(x)

improve_rate(t, life)

Improvement rate at the age

(1-f)^(c-c0)

improve_factor(t, life)

Cumulative improvement factor

q_rated

mort_rate(t, life)

Annual rate after all factors

q_m

mort_rate_mth(t, life)

Monthly rate 1-(1-q)^(1/12)

(none)

mort_basis()

table or scenario run [std]

(none)

death_mth(life)

Scenario month of death [std]

omega

omega_age

Limiting age, 115

(stopping rule)

horizon_mths()

Months to the age stop rule

(none)

proj_len()

Last projected month

(none)

calendar_year(t)

Calendar year containing month t

IF(t)

pols_if(t)

Any payment obligation open

(none)

pols_if_init()

Contracts in force at t = 0

E[ANN(t)]

annuity_payments(t)

Expected annuity outgo

E[VP(t)]

claims(t, “VP”)

Value-protection lump sum

E[PROP(t)]

claims(t, “PROP”)

Proportionate final payment

E[EXP(t)]

expenses(t)

Maintenance expense

c_e, pi

expense_maint, inflation_rate

Expense level and inflation

CF(t)

liability_cf(t)

Total gross liability outgo

(none)

net_cf(t)

-liability_cf(t), insurer sign

Four names needed care.

G(t) is used for two different accumulations in the notes: the state variable table defines it as cumulative gross instalments scheduled, which is what the worked example’s G column prints, while the value-protection section says it accumulates instalments “while the annuitant is alive” on the first-death basis and “the dependant’s instalments too” on the last-survivor one. Those are different objects whenever the dependant’s stream is running, so cum_annuity_pp() takes a kind: "ANNUITANT" is the deterministic as-if-alive annuitant schedule that the first-death value-protection balance nets against, and "ALL" adds the expected dependant instalments and is the notes’ printed column.

l and L differ in the notes only by case, as in SPIA_US_S: survival probability against the payment factor. They become lives_if() and payment_factor_life(), with payment_factor() the certain-floored version.

w(t) is the overlap gate, not a lapse rate — there is no lapse on this product at all. It is spelled overlap_gate() so that nothing in the model reads as a decrement that is not one.

pols_if is not a policy count in the usual sense. It is the notes’ IF(t), the probability that any payment obligation remains — guarantee certain, annuitant alive, or dependant stream in payment — and it exists to carry the maintenance expense. The name is kept because it is what the rest of the library calls the expense weight.

Two mortality bases: table and scenario

The notes’ worked example is not a probability-weighted run. It is a scenario: “the annuitant dies in month 17; the dependant survives throughout”, evaluated at l_a = 0 from month 17 and l_d = 1 throughout. The rest of the notes projects on an expected basis. Both readings are shipped, and which one applies is a model point column — the same device SPIA_US_S uses for the same reason:

mort_basis = "table"

lives_if runs the generational recursion off the shipped mortality table and improvement scale. Model points 2, 5, 6, 7 and 8; point 2 is the worked configuration on this basis and is the run to read for a realistic cash flow shape.

mort_basis = "scenario" [std]

lives_if(t, life) is the deterministic step function 1{t < death_mth(life)}, with a blank death_mth meaning the life survives the whole projection. Model points 1, 3, 4, 9 and 10, which reproduce the worked example and its variants exactly.

The scenario switch is a [std] modelling device, not a product feature; it exists because the notes’ verification anchor is a scenario and retuning assumptions to force a probability-weighted run onto it would be dishonest.

The guarantee is a floor, not a second stream

payment_factor(t) = max(certain_floor(t), payment_factor_life(t)) is the notes’ first-listed pitfall written as one line. During the guarantee period the full instalment is payable regardless of survival, escalating as if the annuitant were alive; an additive construction would pay 1 + l_a and silently double the guarantee.

The guarantee and value protection never coexist in the representative design — the contract offers one or the other, and check_guarantee_xor() asserts no model point carries both. An engine supporting the combinable variant would have to net guarantee payments off the value-protection balance or the death benefit is paid twice.

The overlap gate

overlap = False is the representative default and means the dependant’s stream starts only at the end of the guarantee period, not at the annuitant’s death. Applying the dependant’s percentage from the death date silently converts every without-overlap policy into the more expensive with-overlap form, which is the notes’ second-listed pitfall. Model points 3 and 4 are the same contract on either side of that switch, and their cash flows differ from month 18 to month 120.

Escalation, and the one path-dependent option

Four bases. level and fixed are self-explanatory; lpi5 is RPI floored at zero and capped at 5%; rpi_catchup is a ratchet: income is indexed to the running peak of the RPI reference index, so a fall in the index freezes income rather than reducing it, and later rises only bite once the index passes its previous peak. rpi_peak() carries that state across anniversaries. Resetting it each year turns the catch-up into a plain zero floor and overstates indexed income after a deflation-recovery path.

Under the deterministic 3% RPI assumption [std] the index is monotone, so the ratchet never binds and rpi_catchup degenerates to fixed 3% — model points 6 and 2 agree by construction. That is not an accident to be tidied away but the honest consequence of a deterministic inflation path: the zero floor, the ratchet and the LPI cap are all inflation options, and a deterministic path values them at intrinsic only. A market-consistent value needs stochastic inflation.

Escalation applies on the anniversary, not on payment dates: the year-2 rate does not reach the t = 12 arrears instalment, which accrued in year 1.

Value protection, and where the balance is measured

VP(t) = d(t) x max(0, v P - G(t-1)), the death benefit measured against instalments already paid. Two timing rules matter and both are the notes’ pitfalls:

  • on arrears timing the balance is G(t-1), because the instalment due at the end of the death month is never paid;

  • on advance timing an instalment paid at the start of the death month has been paid, so in an advance payment month the balance is G(t) — netting it, or the lump sum is overstated by one instalment.

On the first_death basis the death that triggers it is the annuitant’s and the balance nets the annuitant’s schedule alone. On last_survivor it is the last death, and the balance nets both streams — which, on a probability-weighted run, makes the balance an expected cumulative payment rather than a path-specific one. That approximation is stated here rather than hidden: it is exact in a scenario run, which is the basis the shipped last_survivor model point uses.

The contractual bound v + delta <= 1 on the first-death basis is asserted by check_vp_bound(); the worked configuration sits exactly on it.

There is no policyholder behaviour to model

No lapse decrement, no dynamic behaviour formulas, no surrender value at any time, no alteration of options and no premium flexibility. That is a cited product feature and the reason the liability is matching-adjustment eligible, and it is why this model has no lapse_rate of any kind. Behaviour enters only at outset, outside the projection, as basis-selection effects: voluntary annuitants self-select for longevity, which is the direction of the annuitant_adj factor below 1, and the enhanced-annuity market leaves standard-terms lives healthier on average, which is carried through rating_factor() rather than through any dynamic.

Sign convention

The notes define CF(t) as total gross liability outgo, which is liability_cf(). net_cf() is its negative, the library-wide income-positive convention, so a result_cf()["net_cf"] column can be summed or compared across every model in the library. Both are published as columns rather than one being made to stand for the other. There is no premium income in the projection at all: the purchase price is a pricing input at t = 0, not a projected cash flow.

Cells Descriptions#

model_point()[source]#

The selected model point as a Series.

purchase_price()[source]#

P: the amount applied to the annuity after any lump sum and adviser charges.

A pricing input at t = 0, not a projected cash flow: the projection carries no premium income. It enters the projection only through value protection, where the guarantee is measured against it.

age_at_entry(life)[source]#

x: the entry age of the annuitant (life = 1) or dependant (life = 2).

Age last birthday, chosen to index the shipped single-year-of-age proxy table [std].

sex(life)[source]#

The sex (M / F) of the annuitant or dependant.

rating_factor(life)[source]#

theta: the enhanced or impaired mortality multiplier [std]; 1.0 standard.

q_rated = min(1, theta x q), the simplest overlay that reprices longevity without touching contract mechanics. Insurers’ real rating structures - postcode and condition-specific factors - are not public, and a rated-age offset is the equivalent form. Whole-market enhanced quoting is mandated at the point of sale, so lives remaining on standard terms are healthier on average; the model carries that through this multiplier rather than through any behavioural dynamic.

is_joint()[source]#

True when the contract carries a dependant’s income.

dependant_pct()[source]#

delta: the dependant’s percentage of the income; zero on a single-life contract.

overlap()[source]#

Whether the dependant’s stream runs during the remaining guarantee period.

False is the representative default and means the stream starts at the end of the guarantee, not at the annuitant’s death. See overlap_gate().

annual_income_init()[source]#

A(1): the starting annualized income.

A pricing input taken from a quote: no insurer publishes an annuity rate card, so the model does not derive income from the purchase price and nothing in the projection consumes P except value protection.

payment_freq()[source]#

m: payments per year, in {12, 4, 2, 1}.

payment_timing()[source]#

Whether instalments fall in arrears or in advance.

Arrears instalments require survival at the end of the payment month; advance instalments at the start of it. Using end-of-period survival for advance payments understates the liability by roughly one period’s mortality per payment, which is material at high ages.

proportion()[source]#

Whether a proportionate final payment is made for the accrued part-period.

Arrears only. Without it - the representative default - nothing is paid for the final partial period, which is the more common contractual design.

escalation_type()[source]#

level, fixed, rpi_catchup or lpi5.

lpi5 is RPI floored at zero and capped at 5%; rpi_catchup is a ratchet on the running peak of the RPI reference index. See the Space docstring.

escalation_rate()[source]#

g: the fixed escalation rate, at most 10%; read only on the fixed basis.

guarantee_mths()[source]#

n: the guarantee period in months, 0 or 12-360.

Instalments are certain for n months, escalating as if the annuitant were alive. Mutually exclusive with value protection in the representative design.

vp_pct()[source]#

v: the value-protection percentage of the purchase price; 0 if not elected.

vp_basis()[source]#

Whether value protection pays on the first_death or the last_survivor.

On the first-death basis the trigger is the annuitant’s death and the balance nets the annuitant’s instalments; on the last-survivor basis it is the last death and the balance nets both streams. See the Space docstring for the approximation the second basis carries in a probability-weighted run.

mort_basis()[source]#

Whether the run is probability-weighted (table) or deterministic (scenario).

table runs the generational recursion off the shipped table and improvement scale; scenario [std] replaces it with the step function 1{t < death_mth(life)} so the notes’ worked example - which is a scenario, not an expectation - reproduces exactly. See the Space docstring.

death_mth(life)[source]#

The scenario month of death of life; 0 if the life survives throughout.

Read only when mort_basis() == "scenario". A blank cell in the model point table means the life never dies in the scenario and is returned as 0, since t is 1-based and a death in month 0 is not a projectable event. A death “in month 17” is decremented at the end of month 17, so lives_if(t, life) is 0 from t = 17.

start_year()[source]#

The calendar year of the annuity start date; the improvement-scale anchor.

The improvement exponent is calendar_year(t) - mort_table_year, so this is what makes the mortality basis generational rather than period.

pols_if_init()[source]#

Initial number of contracts in force; 1.0 on a single-contract model point.

duration(t)[source]#

Completed policy years at the start of month t: (t - 1) // 12.

duration_mth(t)[source]#

Months elapsed from the start date at the end of month t; equal to t.

t is 1-based, so the identity is trivial - the cells exists so the monthly models in this library share one vocabulary.

policy_year(t)[source]#

y = ceil(t/12): the policy year containing month t; 1 for t = 1..12.

age(t, life)[source]#

The attained age (ALB) of life in the policy year containing month t.

calendar_year(t)[source]#

The calendar year of the policy year containing month t.

horizon_mths()[source]#

The last month at which some covered life has not yet passed the limiting age.

12 x (omega_age - min x_i): the notes stop once t/12 + x_i > omega for every covered life, and stopping on the annuitant’s age alone would truncate a younger dependant’s tail. The notes’ other stop test, IF(t) < 1e-6, is not implemented, because a stopping rule that depends on the projection it bounds is harder to reason about than one that does not.

proj_len()[source]#

Projection length in months: the mortality horizon, or the guarantee if longer.

is_payment_mth(t)[source]#

Whether month t is a payment date.

Arrears: t = 3, 6, 9, ... at m = 4. Advance: the k-th instalment falls one full payment period earlier, at the start of month 12(k-1)/m + 1, so t = 1, 4, 7, … At m = 12 every month is a payment month on either convention.

payment_surv_mth(t)[source]#

The month at which survival is measured for the instalment falling in month t.

Arrears: the end of month t. Advance: the end of month t - 1, because an advance instalment falls at the start of month t; 0 for the first instalment, where lives_if is 1. Using end-of-period survival for advance payments understates the liability by about one period’s mortality per payment.

rpi_index(k)[source]#

I(k): the RPI reference level at anniversary k [std]; I(0) = 1.

A deterministic (1 + rpi_rate)^k path. A deterministic path cannot value the zero floor, the catch-up ratchet or the LPI cap - all of them inflation options - and values them at intrinsic only; a market-consistent value needs stochastic inflation.

rpi_peak(k)[source]#

The running maximum of the RPI reference index through anniversary k.

The catch-up ratchet’s state, and it is path-dependent: it must persist across anniversaries. Resetting it each year turns the catch-up into a plain zero floor and overstates indexed income after a deflation-recovery path.

annual_income(y)[source]#

A(y): the annualized income in policy year y, on the annuitant’s scale.

Escalation applies at the start of the month containing the policy anniversary, so the first increase reaches t = 13 and not the t = 12 arrears instalment, which accrued in year 1. By basis:

level:        A(y) = A(1)
fixed:        A(y) = A(y-1)(1 + g)
lpi5:         A(y) = A(y-1)(1 + min(0.05, max(0, RPI)))
rpi_catchup:  A(y) = A(1) x peak(y-1) / I(0)

The catch-up form is the closed version of the ratchet pseudocode: income is indexed to the running peak of the reference index.

annuity_pp(t)[source]#

inst(t) = A(y(t))/m: the scheduled instalment per contract in month t.

Zero outside payment months. This is the annuitant’s instalment; the dependant’s is delta times it, on the same escalated income, which is what the contractual “percentage of the higher of income at death and at guarantee end” reduces to under a non-decreasing escalation path.

next_payment_mth(t)[source]#

The next scheduled payment month at or after month t; used by the proportion rule.

mths_since_payment(t)[source]#

h(t): complete months elapsed since the last payment date, measured at t - 1.

The accrual base of the proportionate final payment. On the worked configuration a death in month 17 with quarterly arrears payments at months 3, 6, … gives h = 1: one complete month since the month-15 instalment.

mort_rate_base(t, life)[source]#

The base-table annual mortality rate for life in the year containing month t.

Population mortality, times the [std] annuitant adjustment that stands in for the difference between a population table and the licensed annuitant tables. Rates at and above the limiting age are 1.

improve_rate(t, life)[source]#

f(x): the annual mortality improvement rate at life’s attained age [std].

A flat rate to age 90, tapering linearly to zero at 110 and zero above it. This stands in for the CMI Mortality Projections Model, whose software is restricted, and it materially understates the age-period-cohort structure of the real model - it exists only so the reference implementation is runnable without CMI access. The choice of long-term improvement rate is the single most sensitive judgment in UK annuity valuation, and the CMI model carries no default recommendation for it.

improve_factor(t, life)[source]#

(1 - f(x))^(c - c0): the cumulative improvement factor for life in month t.

c is the calendar year containing month t and c0 the base table’s data mid-year, so the basis is generational: each attained age in each future calendar year carries its own improved rate.

mort_rate(t, life)[source]#

q_rated: the annual mortality rate applied to life in the year containing t.

The notes’ construction, in order:

q_base  = ONS-shaped table rate x alpha
q_imp   = q_base x (1 - f(x))^(c - c0)
q_rated = min(1, theta x q_imp)

Every one of the three factors is a standardization: the table is a population proxy, the improvement scale a stand-in for a restricted model, and the rating multiplier the simplest form of an unpublished structure. The liability is a life-contingent stream with no offsetting decrements, so the level of this rate is the single largest lever on it.

mort_rate_mth(t, life)[source]#

q_m = 1 - (1 - q_rated)^(1/12): the monthly mortality rate [std].

lives_if(t, life)[source]#

l(t): the probability that life is alive at the end of month t.

l(0) = 1. On the table basis l(t) = l(t-1)(1 - q_m(t)), deaths being decremented at end of month. On the scenario basis [std] the survival path is the step function 1{t < death_mth(life)}, with death_mth = 0 meaning the life survives the whole projection - which is what the notes’ worked example specifies. Returns 0 for life = 2 on a single-life contract.

lives_death(t, life)[source]#

d(t) = l(t-1) - l(t): the death density of life in month t.

lives_if_last(t)[source]#

The probability that at least one covered life is alive at the end of month t.

l_a + l_d - l_a l_d under joint-life independence [std], which ignores broken-heart dependence and common lifestyle factors and so modestly overstates the expected dependant stream. On a single-life contract this is l_a.

lives_death_last(t)[source]#

d_last(t): the last-death density in month t, the last-survivor VP trigger.

certain_floor(t)[source]#

C(t) = 1{t <= n}: the annuity-certain floor indicator of the guarantee period.

payment_factor_life(t)[source]#

l_a measured at the payment point: the annuitant’s survival factor alone.

Survival is measured at payment_surv_mth(), not at t - the end of the payment month on arrears and the start of it on advance.

payment_factor(t)[source]#

max(C(t), l_a): the annuitant stream’s payment factor.

The max makes the guarantee period an annuity-certain floor rather than an additional stream: during the guarantee the full instalment is payable regardless of survival, escalating as if the annuitant were alive, and the max prevents paying 1 + l_a. An additive construction silently doubles the guarantee, which is the notes’ first-listed pitfall.

overlap_gate(t)[source]#

w(t): whether the dependant’s stream is available in month t.

1 with overlap, or once the guarantee period has run; 0 inside the guarantee without overlap. Not a decrement of any kind - there is no lapse on this product - which is why it is not called a rate. Applying the dependant’s percentage from the death date on a without-overlap contract silently converts it into the more expensive with-overlap form.

dependant_factor(t)[source]#

delta (1 - l_a) l_d w(t): the dependant stream’s payment factor.

The dependant is paid when the annuitant is dead and the dependant alive, gated by the overlap rule, both survivals measured at the payment point. Zero on a single-life contract.

cum_annuity_pp(t, kind)[source]#

G(t): cumulative scheduled instalments per contract through month t.

"ANNUITANT"

the deterministic as-if-alive annuitant schedule. This is what the first-death value-protection balance nets against, and it needs no path simulation precisely because it ignores survival.

"ALL"

the same plus the expected dependant instalments, which is the column the notes’ worked-example table prints and the balance the last-survivor basis nets against. On a probability-weighted run it is an expected cumulative payment rather than a path-specific one; in a scenario run the two coincide.

vp_balance(t)[source]#

VPbal(t) = max(0, v P - G(t)): the value-protection balance after month t.

Nets the schedule the elected basis applies to: the annuitant’s alone on first_death, both streams on last_survivor. Zero when value protection is not elected.

annuity_payments(t)[source]#

E[ANN(t)]: expected annuity outgo in month t.

inst(t) x [max(C(t), l_a) + delta (1 - l_a) l_d w(t)], scaled by pols_if_init: the annuitant stream with its guarantee floor, plus the dependant’s stream.

claims(t, kind=None)[source]#

Expected lump-sum outgo in month t, by kind; the total when kind is omitted.

"VP"

the value-protection lump sum, d(t) x VPbal. The trigger is the annuitant’s death on the first_death basis and the last death on last_survivor. The balance is measured at t - 1 on arrears timing, because the instalment due at the end of the death month is never paid; in an advance payment month it is measured at t, because an instalment paid at the start of the death month has been paid and netting it is what keeps the lump sum from being overstated by one instalment.

"PROP"

the proportionate final payment on an arrears contract that elects it: d_a(t) x (h(t) + 0.5)/(12/m) x inst(next(t)), a [std] half-month accrual for the part-period between the last payment date and the death. Zero on the representative default, where nothing is paid for the final partial period.

pols_if(t)[source]#

IF(t): the probability that any payment obligation remains in month t.

min(1, max(C(t), l_a(t)) + 1{delta>0}(1 - l_a(t)) l_d(t)) [std] - guarantee certain, annuitant alive, or dependant stream in payment. This is the weight the maintenance expense is carried on, which is why it keeps the library’s name for the expense weight even though it is not a policy count.

inflation_factor(t)[source]#

The expense inflation factor in month t: (1 + pi)^(y - 1) [std].

expenses(t)[source]#

E[EXP(t)]: maintenance expense in month t [std].

(c_e / 12)(1 + pi)^(y-1) IF(t): a round placeholder for in-payment administration, paid monthly while any payment obligation remains. No insurer publishes expense assumptions - charges are priced into the annuity rate - and acquisition cost is out of scope, the premium being single and the cost priced in. Second-order against the instalments, but the in-payment term is thirty years or more, so the inflation assumption compounds.

liability_cf(t)[source]#

CF(t): total gross liability outgo in month t, the notes’ cash flow definition.

E[ANN] + E[PROP] + E[VP] + E[EXP]. Outgo positive, the notes’ own sign. There is no premium income in the projection - the purchase price at t = 0 is a pricing input - and no surrender outgo, because the contract has no surrender value at any time.

net_cf(t)[source]#

Net cash flow to the insurer in month t: income less outgo, so -liability_cf.

Income positive, the sign convention every model in this library carries, kept even though this product has no projected income so that every model’s net_cf can be compared or summed across the library. liability_cf() carries the opposite, outgo-positive sign of the technical notes; both are published as columns of result_cf() rather than one being made to stand for the other.

check_lives_roll_fwd_resid(t)[source]#

Residual between lives_if() and an independently rebuilt survival path.

Deliberately not the telescoping identity l(t-1) - d(t) - l(t): lives_death() is defined as that difference, so the identity is identically zero whatever lives_if() returns and constrains nothing. Each life’s survival is rebuilt here from the assumptions instead, with no reference to the recursion - on the table basis as the running product prod (1 - q_m(s)), which a misindexed or mis-based recursion breaks - and the residual is the sum of the per-life differences.

check_lives_roll_fwd()[source]#

Whether the survival recursion closes at every projected month.

Takes no argument and returns a bool, the library-wide shape of a check_* cells, so one test can call the same check across every model. The signed residual of a failing month stays available as check_lives_roll_fwd_resid().

check_payment_factor_resid(t)[source]#

payment_factor(t) - max(C(t), l_a): the guarantee double-count guard.

Zero by construction. It is asserted anyway because an additive floor - C + l_a instead of max(C, l_a) - is the notes’ first-listed pitfall and would show up here as min(C, l_a).

check_payment_factor()[source]#

Whether the guarantee stays a floor rather than a second stream, at every month.

check_guarantee_xor()[source]#

Whether the contract carries at most one of a guarantee period and value protection.

The representative design offers one or the other, never both. An engine supporting the combinable variant would have to net guarantee payments off the value-protection balance, or the death benefit is paid twice - so the exclusivity is asserted rather than assumed.

check_vp_bound()[source]#

Whether v + delta <= 1 on the first-death value-protection basis.

A contractual bound, and the worked configuration sits exactly on it. It does not apply on the last-survivor basis, where the two benefits cannot both be triggered by the same death.

result_cf()[source]#

Result table of cashflows, indexed by month t.

pols_if is the probability any payment obligation remains, which is the expense weight rather than a policy count. Both signs of the net flow are published: net_cf is income-positive, the library-wide convention, and liability_cf is the technical notes’ outgo-positive CF(t).

result_pols()[source]#

Result table of survival probabilities and payment factors, indexed by month t.