The Projection Space#

The by-policy projection of the ULB_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 anchor cell
>>> Projection.point_id = 2            # the accumulation cell, no withdrawals

t counts policy months, 1-based. The notes index the unit fund UF(t) at the end of month t with UF(0) = P; the library indexes av_pp() at the start, so av_pp(t) is the notes’ UF(t-1) — the column their worked-example table prints first — and the notes’ UF(t) is av_pp_at(t, "AFT_WD"), which equals av_pp(t + 1).

Input data

Inputs are external files: plain CSVs living in the model folder’s parent directory, products/unit_linked_bond/, 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 ULB_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

surr_table_file

data.surr_table()

surr_table.csv

Naming

Cells names follow lifelib’s savings.CashValue_SE and basiclife.BasicTerm_S wherever those models have an analogue, and the account-value vocabulary the U.S. models in this library settled on — av_pp_at(t, timing) for the fund read at a point inside the month, check_av_roll_fwd for its identity. The technical notes use compact symbols instead. The mapping is:

Notes symbol

Cells

Meaning

t

(the cells argument)

Policy month

y = ceil(t/12)

policy_year(t)

Policy year containing month t

a

age(t)

Attained age (ALB)

(none)

duration(t)

Completed policy years, y - 1

(none)

duration_mth(t)

Months elapsed at end of month t

P

premium()

Single premium

UF(t-1)

av_pp(t)

Unit fund at the start of month t

UF_g(t), UF’(t), UF(t)

av_pp_at(t, timing)

The fund inside month t

(per segment)

av_per_segment_pp(t)

The fund divided by the segments

g

fund_return

Annual gross fund return

g_m

fund_return_mth()

Its monthly equivalent

G$(t)

fund_growth_pp(t)

Gross return credited in month t

t_pf

tax_provision_rate()

Tax provision rate

TX(t)

tax_provision_pp(t)

Tax provision deducted

c, c_m

amc_rate(), amc_rate_mth()

Annual management charge

AMC$(t)

amc_pp(t)

AMC collected in month t

f, f_m

further_costs_rate()

Fund-borne further costs

FC$(t)

further_costs_pp(t)

Further costs borne in month t

u

db_uplift()

Death-benefit uplift, 1.001

W(t)

wd_pp(t)

Withdrawal cancelled at EOM

(cap)

wd_cap_pp(t)

Rolling 12-month withdrawal cap

(pattern)

wd_pattern()

none / allowance_5pct / custom

AC(t)

adviser_charge_pp(t)

Ongoing adviser charge

GC(t)

gmdb_charge_pp(t)

GMDB rider charge

G(t)

gmdb_guarantee_pp(t)

GMDB guaranteed amount

gmdb_flag

gmdb_flag()

Rider elected

DS(t)

death_strain_pp(t)

Non-unit cost per death

q_a, q_m(t)

mort_rate(t), mort_rate_mth(t)

Mortality rates

w_base(y)

surr_rate_base(t)

Table surrender rate

M_perf(t)

perf_factor(t)

Performance lapse multiplier

M_allow(y)

allow_factor(t)

Allowance-exhaustion step

w_ann(y,t), w_m(t)

surr_rate(t), surr_rate_mth(t)

Surrender rates applied

l(t-1)

pols_if(t)

In force at the start of month t

l(t)

pols_if_at(t, timing)

BEF_DECR / BEF_SURR / AFT_DECR

(none)

pols_death(t)

Deaths in month t

(none)

pols_surr(t)

Surrenders at the end of month t

(none)

pols_maturity(t)

In force when the projection ends

CumWD(n)

wd_cum_pp(t)

Cumulative withdrawals + charges

CumAllow(n)

allowance_cum_pp(t)

Cumulative 5% allowable element

ExcessGain(n)

excess_gain_pp(t)

Excess-event gain, policyholder side

E(t)

expenses(t)

Acquisition + maintenance

(none)

inflation_factor(t)

Expense inflation factor

(unit cancellations)

claims(t, kind)

Gross benefit outgo by kind

(none)

unit_releases(t)

Units cancelled to fund benefits

(none)

withdrawals(t)

Withdrawal outgo

AMC$ x l

amc_charges(t)

AMC margin collected

FC$ x l

further_costs(t)

Further costs, a pass-through

TX x l

tax_provisions(t)

Tax provision, a pass-through

NUCF(t)

net_cf(t)

Non-unit cash flow, income positive

Four names needed care.

UF is the unit fund, and the library calls a policyholder-owned fund av_pp_at(). The house name wins, because one concept must not carry two names across the library — but the docstrings and the result_cf columns keep the unit vocabulary the product is actually discussed in, and av_pp(t) is documented everywhere as the notes’ UF(t-1).

G is used twice in the notes: G$(t) is the gross fund return credited in the month and G(t) the GMDB guaranteed amount. They become fund_growth_pp() and gmdb_guarantee_pp(); nothing here is called G.

net_cf on this product is the non-unit cash flow, not a gross liability total. That is not a departure from the library convention but the consequence of the product: every benefit is funded by cancelling the policyholder’s own units, so a gross presentation adds the same money to both sides. The gross flows are still published — claims_death, claims_surrender, withdrawals and unit_releases are result_cf columns — and check_unit_funding() asserts that they net exactly against the fund.

pols_maturity is borrowed from the term models and, as in WOL_UK_S, means something different: a bond has no maturity date, so this is the population still in force when the projection ends. It pays nothing and exists only so the roll-forward closes in the last month.

The unit fund recursion, and the order the charges come in

Per policy, within month t:

UF_g(t) = UF(t-1) x (1 + g_m(1 - t_pf))          growth, net of the tax provision
UF'(t)  = UF_g(t) x (1 - c_m - f_m)              AMC and further costs
UF(t)   = UF'(t) - W(t) - AC(t) - GC(t)          unit cancellations at end of month

The order matters and is a listed pitfall. The annual management charge accrues daily through the unit price, so it is levied on the post-growth, pre-cancellation fund. Charging it on UF(t-1) instead, or after the withdrawal, moves the margin by about half a month’s growth or withdrawal — small in one month and systematic over decades. av_pp_at() exposes all three points so the ordering is inspectable rather than buried in one expression.

What is margin and what is a pass-through

Only two of the four amounts that leave the unit fund are insurer income:

Amount

Insurer income

Why

AMC

yes

the charge for managing the contract

GMDB rider charge

yes

the price of the guarantee

Further costs

no

fund-borne expenses, paid on to the fund

Tax provision

no

collected in-price, paid on as tax

Adviser charges

no

post-RDR pass-throughs facilitated by cancelling units

Booking the pass-throughs as margin is the notes’ second-listed pitfall and would overstate the year-one non-unit result by more than the AMC itself — the tax provision alone is about 97% of the AMC on the anchor cell, and the further costs another 10%. further_costs() and tax_provisions() are therefore published as their own result_cf columns and are not in net_cf(), so the exclusion is visible.

The death strain is the uplift, not the death benefit

The sum assured is u x UF, of which UF is funded by cancelling the policyholder’s own units. The non-unit cost per death is therefore only

DS(t) = (u - 1) UF(t) + max(0, G(t) - u UF(t)) 1{gmdb}

— a tenth of a percent of the fund on the composite, plus any in-the-money guarantee. The uplift is a parameter and never a literal: 100.1% against 101% is a tenfold difference in death strain, which the notes list as a pitfall and model point 5 exercises.

The 5% allowance is policyholder tax machinery, not a product feature

allowance_cum_pp() and excess_gain_pp() track the cumulative 5% tax-deferred allowance and the excess-event gain it produces when exceeded. Neither generates an insurer cash flow. The allowance never caps what can be withdrawn — the product cap is the rolling 7.5% of wd_cap_pp() — and treating it as a product feature is a listed pitfall. It is carried because it drives behaviour: the allowance-exhaustion step in allow_factor() raises surrender from policy year 21, once twenty years of allowance have been drawn.

The fund runs out, and the projection ends there

A 5% withdrawal against a 5% gross return is not sustainable once the tax provision and the charges are taken: the fund drifts down and, on the deterministic base run, reaches zero inside the annuitant’s lifetime. wd_pp() caps the withdrawal at what the fund can pay, so the fund never goes negative, and proj_len() ends the projection at fund_exhaust_mth() — a bond with no units has no liability, no margin and nothing left to project. That is a product fact worth seeing rather than an artefact to hide: the anchor cell’s fund is exhausted around policy year 30, and every margin the insurer was counting on stops there.

Behaviour is the whole valuation

Every margin line is proportional to the unit fund and to persistency, and a surrender costs nothing at the point of exit — the surrender value is the bid value of units, cancelled — while truncating the entire future AMC stream. Two dynamic overlays sit on the base table, both [std] and both inert in the deterministic base run:

  • perf_factor(), min(2, 1 + 2 max(0, g_ref - R_12m)), raising surrender after poor performance. The base run has R_12m = g_ref by construction, so it is 1; return_shock moves the trailing return without disturbing the fund path, which is what makes the multiplier testable in isolation.

  • allow_factor(), a step to 1.5 from policy year 21, when the tax allowance is spent.

There is no interest-sensitive dynamic lapse and no paid-up state: the product is single premium, so there is no premium obligation to stop.

Cells Descriptions#

model_point()[source]#

The selected model point as a Series.

age_at_entry()[source]#

The issue age of the selected model point, age last birthday.

ALB is chosen to index directly into the single-year-of-age table used as the [std] mortality proxy. The anchor documents quote contractual age limits next birthday; the difference is immaterial to a product whose death strain is a tenth of a percent of the fund.

sex()[source]#

The sex (M / F) of the selected model point.

lives()[source]#

single. Joint last-death bonds are out of scope in this composite.

premium()[source]#

P: the single premium, net of any set-up adviser charge.

Top-ups are excluded from the base projection: a top-up is a new model point with its own premium, allowance clock and segments [std].

n_segments()[source]#

The number of identical segments the bond is written in; 100 [std].

Per-segment values are bond values divided by this. Modelling at bond level is exact only while all segments stay identical - a segment surrender breaks the symmetry - and the composite keeps bond-level modelling and notes the approximation.

db_uplift()[source]#

u: the death benefit as a multiple of the bid value of units; 1.001.

A parameter and never a literal: 100.1% against 101% is a tenfold difference in death strain, which is the whole non-unit cost of mortality on this product. Model point 5 is the anchor cell at 101% and exists to make that visible.

amc_rate()[source]#

c: the annual management charge, 1.00% [std].

A snapshot of a discretionary element: per-fund charge rate cards are not published, and the insurer’s provisions allow the charge to be increased. It is the insurer’s only material income line, so its level sets the whole result.

further_costs_rate()[source]#

f: the fund-borne further costs, 0.10% [std].

Reduces the unit fund and is not insurer income - it is paid on as a fund expense. Booking it as margin is a listed pitfall.

tax_provision_rate()[source]#

t_pf: the life-fund tax provision taken in-price, 20% of gross return [std].

Collected through the unit price and paid on as corporation tax, so it is neutral to the insurer’s margin in this model. The real I-E position has timing differences - income as received, realised gains at the next charge date, an annual deemed disposal, settlement on full surrender - and base differences including expense relief and the minimum profits test, all of which create insurer-side tax strain or float that this proxy does not capture.

wd_pattern()[source]#

none, allowance_5pct or custom: the regular withdrawal pattern.

allowance_5pct is the anchor: 5% of premium a year, taken monthly. That is the pattern every fetched key features document leads with, it sits inside the 7.5% product cap, and adviser charges consume the same tax allowance - so rational take-up gravitates to 5% inclusive of charges.

wd_rate()[source]#

The annual regular withdrawal as a fraction of the premium.

Zero on none, 5% on allowance_5pct, and the model point’s own rate on custom.

oac_rate()[source]#

The ongoing adviser charge as an annual rate on the unit value; 0 in the base run.

Post-RDR adviser charges are pass-throughs facilitated by cancelling units: they reduce the fund and consume the policyholder’s tax allowance, and they add nothing to the insurer’s non-unit cash flow. Treating them as income is a listed pitfall.

gmdb_flag()[source]#

Whether the return-of-premium death benefit rider is elected; false in the base.

With it, the death strain stops being negligible and becomes market-contingent, and the rider’s charge scale is unpublished - so the [std] cost-of-insurance form used here is a guess. Enable it only with its own sensitivity set.

av_init_pp()[source]#

The unit fund at outset; the premium on a new-business cell.

pols_if_init()[source]#

Initial number of policies in force; 1.0 on a single-policy model point.

duration(t)[source]#

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

duration_mth(t)[source]#

Months elapsed from issue 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.

Also the insurance year the tax allowance is tracked against.

age(t)[source]#

a: the attained age (ALB) in the policy year containing month t.

horizon_mths()[source]#

The mortality horizon: 12 x (omega_age - age_at_entry).

fund_exhaust_mth()[source]#

The first month at which the unit fund has been drawn to nothing.

horizon_mths() + 1 if it never is. A 5% withdrawal against a 5% gross return is not sustainable once the tax provision and the charges are taken, so on the deterministic base run the anchor cell’s fund does run out - around policy year 30 - and every margin the insurer was counting on stops there. Searched rather than solved, so that any withdrawal pattern and charge basis resolves.

proj_len()[source]#

Projection length in months: the mortality horizon, or the fund exhausting first.

A bond has no maturity date, so the horizon is a limiting age - but a bond with no units has no liability, no margin and nothing left to project, so the projection also ends when the fund does.

fund_return_mth()[source]#

g_m = (1 + g)^(1/12) - 1: the monthly gross fund return [std].

Deterministic in the base run. The insurer’s margin is proportional to the unit fund, so the liability model inherits full market beta on the margin stream: a 20% market fall cuts the margin base by about a fifth and, through perf_factor(), raises surrender at the same time.

amc_rate_mth()[source]#

c_m = c/12: the monthly annual management charge [std 1/12 accrual].

further_costs_rate_mth()[source]#

f_m = f/12: the monthly further costs [std 1/12 accrual].

mort_rate(t)[source]#

q_a: the annual best-estimate mortality rate at the attained age [std].

The shipped table rate times mort_be_factor, a crude allowance for population mortality being heavier than insured-lives experience. Nearly irrelevant to this product unless the guaranteed minimum death benefit rider is enabled.

mort_rate_mth(t)[source]#

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

surr_rate_base(t)[source]#

w_base(y): the table annual full-surrender rate in month t [std].

2 / 3 / 5 / 8 / 10 percent by band. Policy years beyond the table take its last row.

return_12m(t)[source]#

R_12m: the trailing twelve-month gross fund return driving the lapse multiplier.

fund_return + return_shock [std]. The base run leaves the shock at zero, so the trailing return equals the reference and the multiplier is 1. Keeping the shock separate from fund_return is deliberate: it moves the behavioural driver without disturbing the fund path, which is what makes the multiplier testable in isolation. A stochastic or scenario projection would replace this with a path.

perf_factor(t)[source]#

M_perf(t) = min(2, 1 + 2 max(0, g_ref - R_12m)): the performance lapse multiplier.

Poor recent performance raises surrender [std]; 1 in the deterministic base run.

allow_factor(t)[source]#

M_allow(y): the allowance-exhaustion step, 1.5 from policy year 21 [std].

After twenty insurance years the cumulative 5% allowance is fully drawn under the anchor withdrawal pattern, and continued withdrawals then generate immediate excess-event gains - which pushes policyholders towards full surrender or advice-driven restructuring.

surr_rate(t)[source]#

w_ann: the annual full-surrender rate applied in month t.

min(cap, w_base x M_perf x M_allow). A surrender costs the insurer nothing at the point of exit - the surrender value is the bid value of units, cancelled, with no penalty - but truncates the entire future charge stream, which is why persistency rather than mortality dominates this product’s value.

surr_rate_mth(t)[source]#

w_m = 1 - (1 - w_ann)^(1/12): the monthly surrender rate [std].

av_pp(t)[source]#

UF(t-1): the unit fund per policy at the start of policy month t.

The bid value of units, and the column the notes’ worked-example table prints first. av_init_pp() at t = 1, then the end-of-month value of the previous month. Floored at zero: wd_pp() caps the withdrawal at what the fund can pay, so the fund is drawn to nothing rather than through it.

av_pp_at(t, timing)[source]#

The unit fund per policy at a point inside policy month t.

"BEF_GROWTH"

UF(t-1), the start of the month; the same as av_pp().

"AFT_GROWTH"

UF_g(t), after the gross return and the tax provision taken in price.

"AFT_CHARGE"

UF’(t), after the annual management charge and the further costs. This is the base the AMC is levied on, not UF(t-1) and not the post-withdrawal fund: the charge accrues daily through the unit price, so it sits on the post-growth, pre-cancellation fund.

"AFT_WD"

UF(t), after the end-of-month unit cancellations - withdrawal, adviser charge and rider charge. Equals av_pp(t + 1).

All four points are exposed so that the charge ordering is inspectable rather than buried in one expression; charging on the wrong one moves the margin by about half a month’s growth or withdrawal, which is small monthly and systematic over decades.

av_per_segment_pp(t)[source]#

The unit fund per segment at the start of month t: the bond value over 100.

Reported only. A policyholder’s tax outcome depends on whether whole segments are cashed or a part-surrender is taken across all of them, but the insurer’s cash flow is identical either way - both cancel the same unit value.

fund_growth_pp(t)[source]#

G$(t) = g_m x UF(t-1): the gross fund return credited in month t.

tax_provision_pp(t)[source]#

TX(t) = t_pf x G$(t): the tax provision taken in the unit price.

A pass-through, not insurer income: it reduces the unit fund and is paid on as corporation tax. On the anchor cell it is about 97% of the AMC, so counting it as margin roughly doubles the year-one non-unit result.

amc_pp(t)[source]#

AMC$(t) = c_m x UF_g(t): the annual management charge collected in month t.

Levied on the post-growth, pre-cancellation fund, because it accrues daily through the unit price. The insurer’s only material income line on this product.

further_costs_pp(t)[source]#

FC$(t) = f_m x UF_g(t): the fund-borne further costs in month t.

A pass-through, not insurer income.

wd_cap_pp(t)[source]#

The rolling twelve-month withdrawal cap: max(7.5% of UF, 7.5% of premium).

The product cap, and the only thing that actually limits what can be withdrawn - the 5% tax allowance does not. Measured against the fund after charges and against the premium, whichever is larger, and it includes the ongoing adviser charge.

wd_pp(t)[source]#

W(t): the regular withdrawal cancelled at the end of month t.

wd_rate x P / 12, then capped twice: by the product’s rolling twelve-month limit - a twelfth of it each month, and net of the ongoing adviser charge, which counts against the same limit - and by what the fund can actually pay after the adviser and rider charges. The second cap is what stops the fund going negative, and it is why the projection can end with the fund at exactly zero rather than through it.

adviser_charge_pp(t)[source]#

AC(t): the ongoing adviser charge cancelled at the end of month t.

oac_rate/12 of the fund after charges. A pass-through: it reduces the unit fund and consumes the policyholder’s tax allowance, and it is not insurer income.

gmdb_guarantee_pp(t)[source]#

G(t): the guaranteed minimum death benefit, if the rider is elected.

premium - cumulative withdrawals - adviser charges: a return-of-premium guarantee that erodes as the policyholder draws the fund down. Zero without the rider.

Measured before the current month’s cancellations, at wd_cum_pp(t - 1). That is the only reading that resolves: the rider charge is itself a cancellation alongside the withdrawal, so a guarantee net of the same month’s withdrawal would make the charge depend on a withdrawal that depends on the charge. Taking the start-of-month guarantee breaks the loop and matches the contractual sense, in which the amount guaranteed is what is on the record when the month begins.

gmdb_charge_pp(t)[source]#

GC(t): the rider charge cancelled at the end of month t [std].

q_m x max(0, G(t) - u UF'(t)), a cost-of-insurance form on the in-the-money part of the guarantee. The real charge scale is unpublished, so this is a guess with the right shape and no authority. Insurer income, unlike the other cancellations.

death_strain_pp(t)[source]#

DS(t): the non-unit cost per death in month t.

(u - 1) UF(t) + max(0, G(t) - u UF(t)) 1{gmdb}. The sum assured is u x UF, of which UF is funded by cancelling the policyholder’s own units, so the insurer’s cost is only the uplift - a tenth of a percent of the fund on the composite - plus any in-the-money guarantee.

wd_cum_pp(t)[source]#

CumWD: cumulative withdrawals and adviser charges to the end of month t.

The allowance-relevant total: ongoing and ad hoc adviser charges consume the same 5% tax allowance as withdrawals do.

allowance_cum_pp(t)[source]#

CumAllow: the cumulative 5% allowable element, P x min(y, 20) x 5%.

Policyholder tax machinery, not a product feature. It never caps what can be withdrawn - the product cap is wd_cap_pp() - and it generates no insurer cash flow. It is carried because it drives behaviour through allow_factor().

excess_gain_pp(t)[source]#

ExcessGain: the excess-event gain at the insurance-year end, if any.

max(0, CumWD - CumAllow), cumulative rather than per-year, so the running total is the gain reported to date. No insurer cash flow: chargeable events are settled between the policyholder and HMRC, with the insurer issuing certificates.

pols_if(t)[source]#

l(t-1): the number of policies in force at the start of policy month t.

pols_if_init() at t = 1, then the notes’ recursion l(t) = l(t-1)(1 - q_m)(1 - w_m), deaths before surrenders [std].

pols_if_at(t, timing)[source]#

The number of policies in force at a point inside policy month t.

"BEF_DECR"

the start of the month, before any decrement; pols_if().

"BEF_SURR"

after deaths, before surrenders - the processing order is death before surrender [std].

"AFT_DECR"

the notes’ l(t), the end-of-month count, and zero in the last projected month.

pols_death(t)[source]#

Deaths in policy month t, against the start-of-month in-force.

pols_surr(t)[source]#

Full surrenders at the end of policy month t, from the survivors of mortality.

pols_maturity(t)[source]#

Policies still in force when the projection ends; zero in every other month.

A bond has no maturity date, so this is either the population left at the limiting age or - on the base run - the population still holding a bond whose fund has just been drawn to nothing. It pays nothing, and it exists so the roll-forward closes.

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

Gross benefit outgo in policy month t, by kind; the total when kind is omitted.

"DEATH"

u x UF(t) per death - the full sum assured, of which all but the uplift is funded by cancelling the policyholder’s own units - or the guaranteed amount where the rider is elected and in the money, max(u x UF(t), G(t)).

"SURRENDER"

UF(t) per surrender: the bid value of units, with no penalty.

These are gross flows. They are published so the reader can see the whole picture, but they are not in net_cf(), because unit_releases() cancels them against the fund - all except the death strain. See the Space docstring.

withdrawals(t)[source]#

Withdrawal and adviser-charge outgo in month t, funded by cancelling units.

An owner election rather than a claim, which is why it has its own name and its own result_cf column.

unit_releases(t)[source]#

The unit fund cancelled to fund the month’s death and surrender benefits.

UF(t) x (deaths + surrenders). The gross benefit outgo less this is exactly the death strain, which is the only part the insurer funds from its own resources.

amc_charges(t)[source]#

The annual management charge collected in month t; insurer income.

gmdb_charges(t)[source]#

The rider charge collected in month t; insurer income, zero in the base run.

further_costs(t)[source]#

The fund-borne further costs in month t; a pass-through, not income.

Published as its own column so that its exclusion from net_cf() is visible rather than merely asserted.

tax_provisions(t)[source]#

The tax provision taken in the unit price in month t; a pass-through.

About 97% of the annual management charge on the anchor cell, which is the measure of how badly counting it as margin would mislead.

death_strain(t)[source]#

The non-unit cost of the month’s deaths: the uplift, plus any in-the-money GMDB.

inflation_factor(t)[source]#

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

expenses(t)[source]#

Acquisition and maintenance expense in month t [std].

£300 per policy at issue, then £60 per policy a year inflating at 2.5%. The margin is proportional to the fund while the expense inflates, so a small-fund cell goes margin-negative late in life - which the anchor cell does, as its fund is drawn down.

net_cf(t)[source]#

NUCF(t): the non-unit cash flow of month t, income positive.

AMC + rider charge - expenses - death strain. Not a gross liability total, and that is a product fact rather than a departure: every benefit is funded by cancelling the policyholder’s own units, so a gross presentation would add the same money to both sides. The tax provision, the further costs and the adviser charges are excluded because they are pass-throughs - the notes’ second-listed pitfall is counting them as margin.

The result is a fund-based margin stream: proportional to the unit fund and to persistency, with mortality contributing about a tenth of a percent of the fund times the mortality rate. Behaviour, not mortality, dominates the value.

check_av_roll_fwd_resid(t)[source]#

The unit fund roll-forward residual in month t; zero everywhere.

UF(t) - [UF(t-1) + growth - tax - AMC - further costs - W - AC - GC], per policy. This is the identity the charge ordering has to satisfy, and it is checked rather than assumed because the ordering is the model’s most consequential convention. Skipped once the fund has been drawn to nothing, where the withdrawal cap breaks the equality by design.

check_av_roll_fwd()[source]#

True when the unit fund roll-forward closes in every projected month.

The library-wide form of a roll-forward check: no argument, one bool over all t, so one test can call the same check across every account-value model in the library.

check_pols_roll_fwd_resid(t)[source]#

The in-force roll-forward residual in month t; zero everywhere.

pols_if(t) - pols_if(t+1) - deaths - surrenders - the final-month residual.

check_pols_roll_fwd()[source]#

True when the in-force roll-forward closes in every projected month.

check_unit_funding_resid(t)[source]#

How much of the month’s benefit outgo the unit fund does not fund.

claims(t) - unit_releases(t) - death_strain(t), which is zero: the death benefit is the unit fund plus the uplift, the surrender benefit is the unit fund exactly, and the uplift is precisely the death strain. A model that funded benefits twice - once from the fund and once from the insurer - would show it here.

check_unit_funding()[source]#

True when every benefit is funded by unit cancellation plus the death strain.

result_cf()[source]#

Result table of cashflows, indexed by policy month t.

av_pp is the unit fund at the start of the month, and pols_if the in-force count that weights every flow on the row. net_cf is the non-unit stream - what accrues to the insurer - while claims_death, claims_surrender, withdrawals and unit_releases show the gross picture they net against. further_costs and tax_provisions are published precisely because they are not in net_cf: they leave the unit fund and are paid on.

result_uf()[source]#

Result table of the unit fund recursion, indexed by policy month t.

The notes’ worked-example table, column for column: the fund at the start of the month, the gross return, the tax provision, the charges, the withdrawal, and the fund at the end.