The Projection Space#

The by-contract projection of MYGA_US_S.

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

>>> Projection[1].result_cf()          # the anchor cell
>>> Projection.point_id = 2            # or switch the default

Input data

Inputs are external files: plain CSVs living in the model folder’s parent directory, products/fixed_deferred_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 MYGA_US_S folder without its parent’s CSVs produces a model that reads and then fails on first evaluation.

The readers and the filename References live on the sibling Data Space, reached here through the data Reference, so each file is read once per model rather than once per model point:

Reference (on Data)

Cells

File

model_point_file

data.model_point_table()

model_point_table.csv

mort_table_file

data.mort_table()

mort_table.csv

surr_charge_file

data.surr_charge_table()

surr_charge_table.csv

surr_charge_age_cap_file

data.surr_charge_age_cap_table()

surr_charge_age_cap.csv

rate_scenario_file

data.rate_scenario()

rate_scenario.csv

withdrawal_file

data.withdrawal_table()

withdrawal_table.csv

mva_factor_file

data.mva_factor_table()

mva_factor_table.csv

Projection basis

t counts policy months, 1-based, with t = 0 the issue instant carrying the premium, the acquisition commission, the premium tax and the initial branch of every recursion. policy_year(t) = ceil(t / 12) is the contract year, so anniversaries fall at t = 12, 24, ... and the five-year guarantee period ends at t = 60.

Within a month: the free-withdrawal counters roll, the guarantee-period boundary is applied, the elective withdrawal is taken and the tax basis updated — all at the beginning of the month (BOM) — then interest is credited, the Model #805 floor rolls forward and decrements are applied in the order annuitization, mortality, surrender at the end of the month (EOM) [std], with every decrement benefit valued on the post-crediting account value.

Twelve monthly factors (1 + i_cr)^(1/12) reproduce the declared effective annual rate exactly, so av_pp(12) = premium_pp() * (1 + i_cr). Do not additionally compound daily; the discretization affects only the placement of interest within a month.

Naming

Cells names follow lifelib’s basiclife.BasicTerm_S and savings.CashValue_SE wherever those models have an analogue — pols_* for policy counts, av_* for account values, plural nouns for cash flows, *_rate for rates, *_pp for per-contract amounts, *_at(t, timing) for a quantity read at a point inside the month. The technical notes use compact actuarial symbols instead. The mapping is:

Notes symbol

Cells

Meaning

t

duration_mth(t)

Elapsed policy months

y = ceil(t/12)

policy_year(t)

Contract year containing month t

y - 1

duration(t)

Completed contract years

x

age_at_entry

Issue age (ANB)

x + y - 1

age(t)

Attained age in month t

n

guar_period

Guarantee period in years

(none)

policy_term

Years from issue to maturity_age

(none)

proj_len

Last projection month

P

premium_pp

Single purchase payment

i_cr(t)

credit_rate(t)

Declared credited rate, eff. annual

f(t) - 1

credit_rate_mth(t)

Monthly credited rate

s_ren

renewal_spread

Renewal declaration spread (Reference)

i_nf

mgsv_rate

GMSV accumulation rate (2.80%)

g - 1

mgsv_rate_mth

Monthly GMSV rate

i_stat

mgsv_rate_statutory(cmt5)

Model #805 indexed rate

GMIR

gmir

Guaranteed minimum interest rate

MR(t)

market_rate(t)

Market competitor rate

AV(t)

av_pp(t)

Account value at end of month t

AV(t-1), AV’(t), AV(t)

av_pp_at(t, timing)

BEF_WD / BEF_INV / EOM

l(t) x AV(t)

av_at(t, timing)

In-force weighted account value

(interest credited)

inv_income_pp(t)

Interest credited in month t

(cumulative interest)

interest_credited_pp(t)

Interest credited to date

MGSV(t)

mgsv_pp(t)

Model #805 floor at end of month t

c(t)

mgsv_charge_pp(t)

Monthly slice of the contract charge

d(t)

mgsv_wd_deduct_pp(t)

Withdrawal deducted from the floor

FWB(y)

free_wd_base(y)

Free-withdrawal base at the anniversary

0.10 x FWB(y)

free_wd_allow(y)

Allowance for contract year y

FW(t)

free_wd_avail(t)

Unused allowance at BOM of month t

(FW after W(t))

free_wd_remain(t)

Unused allowance after the withdrawal

W(t)

wd_pp(t)

Gross amount removed from the AV

(free part of W)

wd_free_pp(t)

Free portion of W(t)

E(t) on a withdrawal

wd_excess_pp(t)

Withdrawal exposed to charge and MVA

E(t) on a surrender

surr_excess_pp(t)

Surrender exposed to charge and MVA

sc(y)

surr_charge_rate(t)

Surrender charge rate in force

sc_clock(t)

surr_charge_year(t)

Year within the current schedule

(schedule key)

surr_charge_id(t)

initial / renewal / none

C(t)

surr_charge_pp(t)

Charge on a full surrender

C(t) on a withdrawal

wd_charge_pp(t)

Charge on a partial withdrawal

mu(t)

mva_rate(t)

MVA rate, signed

M(t)

mva_pp(t)

Capped MVA on a full surrender

M(t) on a withdrawal

wd_mva_pp(t)

Capped MVA on a partial withdrawal

cap(.)

mva_pp_on(t, base)

The capped MVA on any currency base

T(t)

mva_term(t)

MVA duration in years

i0

mva_ref_yield_locked(t)

Reference yield locked at period start

it

mva_ref_yield(t)

Reference yield at month t

Phi(t)

mva_factor_geometric(a, b, tau) Geometric MVA factor

F_s

mva_duration_factor(t)

Declared-differential duration factor

P_accum@GMIR

prem_accum_gmir_pp(t)

Premium less prior withdrawals at the GMIR

s_adm

mva_admin_spread

Geometric MVA expense adder (Reference)

SV(t)

surr_value_pp(t)

Gross surrender value before the floor

SB(t)

surr_benefit_pp(t)

Surrender benefit paid

gp_end(t)

gp_end(t)

Months left in the guarantee period

(the 30-day window)

in_gp_window(t)

True in the guarantee-period-end window

basis(t)

tax_basis_pp(t)

IRC 72 investment in the contract

(taxable part of W)

taxable_wd_pp(t)

Income-first taxable amount

l(t-1)

pols_if(t)

In-force at the start of month t

l(0)

pols_if_init

In-force at issue

l(t)

pols_if_at(t, “AFT_DECR”)

In-force at the end of month t

(intra-month l)

pols_if_at(t, timing)

BEF_DECR / BEF_MORT / BEF_LAPSE / AFT_DECR

q(t)

mort_rate_mth(t)

Monthly mortality rate

(annual q_x)

mort_rate(t)

Annual mortality at the attained age

w(t)

lapse_rate_mth(t)

Monthly total surrender rate

w_annual(t)

lapse_rate(t)

Annual total surrender rate

Base(y)

lapse_rate_base(t)

Prescribed base lapse by contract year

G

gmir_factor

VM-22 GMIR factor

Market(t)

lapse_dyn_market(t)

Dynamic-lapse market term

Rate(t)

lapse_dyn_rate(t)

Dynamic-lapse rate term

Phi_MVA(t)

mva_lapse_factor_at(t)

MVA factor gating the dynamic term

BF

lapse_buffer

Dynamic-lapse buffer factor (Reference)

X

lapse_dyn_exponent(t)

Dynamic-lapse exponent

a(t)

annuitization_rate(t)

Monthly annuitization election rate

l a

pols_annuitization(t)

Annuitization elections

l (1-a) q

pols_death(t)

Deaths

l (1-a)(1-q) w

pols_lapse(t)

Full surrenders

(none)

pols_maturity(t)

Deemed-maturity annuitizations

P at t = 0

premiums(t)

Premium income

(premium credited)

prem_to_av_pp(t)

Premium credited to the account value

W + M - C

withdrawals(t)

Withdrawal payments

(ledger benefit lines)

claims(t, kind) claim_pp(t, kind) claims_from_av(t, kind) claims_over_av(t, kind)

Benefit outgo by kind Benefit per contract by kind Account value released by a claim Benefit paid above the account value

0.02 x P

commissions(t)

Acquisition commission

(50/12) x 1.025^(y-1)

expenses(t)

Maintenance expense

premium tax

premium_taxes(t)

Premium tax

NetCF(t)

net_cf(t)

Net cash flow

Six names needed care, and all six are collisions the notes themselves carry.

d(t) in these notes is the withdrawal deducted from the Model #805 floor in processing step 7, not deaths as in Term_US_A; it becomes mgsv_wd_deduct_pp(), leaving pols_death() unambiguous. c(t) is the monthly slice of the annual contract charge, not conversions, and becomes mgsv_charge_pp(). E(t) names two different currency bases — the excess of a withdrawal over the free allowance and the excess of the whole account value over it at a full surrender — which wd_excess_pp() and surr_excess_pp() separate; the same symbol is expenses in Term_US_A, which here is expenses(). X is the dynamic-lapse exponent here and the premium tax in Term_US_A, split into lapse_dyn_exponent() and premium_taxes(). T(t) is the MVA duration in years while t is the policy month, so it becomes mva_term(). And the floor itself carries three labels in the sources — MGSV in this library, “GMSV” in the specimen [S11], “MGV” in the fixed-indexed annuity notes — one concept, named mgsv_pp() throughout.

Two further names carry a library-wide convention that differs from the notes’ own indexing, and in both cases the notes’ quantity survives under a second name rather than being dropped.

pols_if() is the count in force at the start of month t, matching Term_US_A (pols_if(1) == pols_if_init()) and savings.CashValue_SE, and it is the weight applied to that same month’s cash flows — withdrawals(), expenses() and the pols_if column of result_cf() now reconcile row by row, which they did not while pols_if was the closing count. The notes’ end-of-month l(t) is unchanged and unmoved; it is read as pols_if_at(t, "AFT_DECR"), and pols_if(t + 1) == pols_if_at(t, "AFT_DECR") in every month but the last, where pols_maturity() empties the block.

lapse_rate() is the annual total surrender rate and lapse_rate_mth() the monthly one, matching the mort_rate() / mort_rate_mth() pair and the eight other models that spell it that way. The notes write the monthly rate w(t) and the annual rate w_annual(t), so the pairing here is the notes’ own — only the suffixes move.

Three rows of the table above are References, not Cells, because the notes give each as a single assumption number rather than a function of t: s_ren is renewal_spread, the spread in i_cr^ren(t) = max(GMIR, MR(t) - s_ren) (0.00% on the base run, 1.00% in the notes’ scenario [std]); BF is lapse_buffer, the 50 bp band inside which the dynamic-lapse response is exactly zero; and s_adm is mva_admin_spread, the 25 bp administrative-expense adder in the geometric MVA’s denominator, which is 0 in Voya’s variant [S3][S4]. Rebasing any of the three is an assignment to the Reference, with no formula change. P_accum@GMIR, by contrast, is a recursion and so a Cells: prem_accum_gmir_pp() is the level the gmir_floor cap holds AV + M above [S13].

Two quantities the notes list as model point attributes are computed here instead, because each is fixed by a rule the notes also state: av_initial is premium_pp() * (1 - load_prem_rate) with no front-end load [S5][S10][S16], and mgsv_initial is net_consideration_ratio * premium_pp() — 87.5% of gross consideration [R1 4.A(2)]. Only tax_basis_initial stays a model point column, because it genuinely varies with tax status.

Timing arguments

Account value, following CashValue_SE’s av_pp_at:

"BEF_WD"

AV(t-1), at the start of month t before the elective withdrawal.

"BEF_INV"

AV'(t), after the withdrawal and after annuitization elections, before crediting.

"EOM"

AV(t), after crediting. Equal to av_pp().

Policy counts, following CashValue_SE’s pols_if_at:

"BEF_DECR"

l(t-1), in force at the start of month t. Equal to pols_if().

"BEF_MORT"

after annuitization elections, l(t-1)(1 - a(t)).

"BEF_LAPSE"

after deaths, l(t-1)(1 - a(t))(1 - q(t)).

"AFT_DECR"

after surrenders, l(t) — the notes’ end-of-month in-force.

Benefit kind arguments are "DEATH", "LAPSE", "ANNUITIZATION" and "MATURITY". Any other value of any of these raises ValueError.

pols_maturity and the projection horizon

The technical notes state no projection horizon: a MYGA under the rollover architecture renews indefinitely, and the notes’ own base-lapse pattern is written as a repeating five-year cycle with no terminal date. The model runs to the contract anniversary at attained age maturity_age = 100 [std] — the last attained age in the sourced cap band on the renewal surrender charge, which the notes tabulate as 4% at 94, 3% at 95, 2% at 96, 1% at 97 and 0% at 98-100 [S1][S2]. The cap reaches zero at 98; 100 is where the sourced band stops, and it is past the statutory deemed maturity date of Model #805 section 8 [R1]. The survivors at that anniversary are annuitized out through pols_maturity(), zero in every month but the last, so that

pols_if(t) - pols_if(t+1) = pols_annuitization(t) + pols_death(t)
  • pols_lapse(t) + pols_maturity(t)

holds for every t — the start-of-month count opens the row, the four exits are taken during it and the next month opens on what is left. Including the last month, where the block would otherwise appear to lose lives with no cause. The name follows BasicTerm_S.pols_maturity and the construction follows Term_US_A.

Cells Descriptions#

model_point()[source]#

The selected model point as a Series.

policy_id()[source]#

The contract identifier of the selected model point.

age_at_entry()[source]#

The issue age (ANB) of the selected model point.

sex()[source]#

The sex of the selected model point, M or F.

tax_status()[source]#

Tax status of the contract: NQ, IRA, Roth or inherited.

Reported only. The base run is non-qualified [std]; no RMD module is implemented.

premium_pp()[source]#

P: the single purchase payment per contract.

pols_if_init()[source]#

l(0): in-force probability at issue, 1 for a single-contract model point.

guar_period()[source]#

n: the guarantee period in years; the surrender charge and MVA periods equal it.

declared_rate_initial()[source]#

The declared credited rate guaranteed for the initial term, 4.45% p.a. [S11].

gmir()[source]#

The guaranteed minimum interest rate, 0.25% p.a. [S11]; a floor on any declared rate.

mgsv_rate()[source]#

i_nf: the contract GMSV accumulation rate, 2.80% p.a. [S11].

The Model #805 floor accretes at this rate. It must be at least the statutory indexed rate mgsv_rate_statutory(); crediting the floor at more is permitted and simply produces a higher floor [R1 4.B].

mgsv_annual_charge()[source]#

The annual contract charge deducted from the Model #805 floor.

$0 representative [S11]; the statutory maximum is $50 p.a. [R1 4.A].

mgsv_wd_convention()[source]#

How a withdrawal reduces the Model #805 floor: gross or net_of_charges.

gross [S11] deducts W(t) itself; net_of_charges [S9] deducts the cash actually paid, W(t) + M(t) - C(t), keeping the floor higher. Both are live in the market and the difference compounds at i_nf for the rest of the contract.

renewal_architecture()[source]#

rollover (Camp A) or annual_redeclare (Camp B).

rollover starts a fresh guarantee period, surrender charge schedule and MVA period at each boundary [S1][S2][S5][S11], so the shock lapse repeats every n years. annual_redeclare redeclares the rate each anniversary with no further surrender charge and no MVA [S13], so the shock happens once.

free_wd_rule()[source]#

pct_av or interest_only.

pct_av [S10] is the representative 10%-of-base allowance; interest_only [S5][S6] is the prior contract year’s credited interest. The notes’ third value, greatest_of [S13], is not implemented: no formula is given for it.

free_wd_rate()[source]#

The free-withdrawal percentage under pct_av, 10% [S10].

free_wd_mva_exempt()[source]#

Whether the free allowance is exempt from the surrender charge and the MVA.

True is the retail MYGA convention [std] [S8][S11] and gives E(t) = AV(t) - FW(t). False is the registered-contract convention [S3][S4] and gives E(t) = AV(t), collapsing the surrender benefit to the multiplicative form max(AV(t) x (1 + mu(t) - sc(y)), MGSV(t)). The notes apply the flag to both the charge and the adjustment; in the market it strictly concerns the adjustment.

mva_family()[source]#

linear_duration, geometric or declared_differential. All implemented.

mva_cap_rule()[source]#

sym_sc, min_sc_interest, asym_sc_snfl, gmir_floor or none.

The cap, not the formula family, is the largest cross-carrier divergence in the source set, so it is a first-class model parameter. See mva_pp_on().

mva_ref_yield_at_issue()[source]#

i0 at issue: the MVA reference yield locked for the initial period, 5.00% [std].

premium_tax_rate()[source]#

Premium tax deducted from the contract, 0% [std] [S3][S16].

tax_basis_pp_init()[source]#

basis(0): the IRC 72 investment in the contract at issue [R6].

scenario_id()[source]#

The rate scenario the model point runs on, a key into rate_scenario.csv.

wd_schedule_id()[source]#

The withdrawal programme, a key into withdrawal_table.csv.

policy_term()[source]#

Contract term in years: issue to the anniversary at maturity_age [std].

proj_len()[source]#

Projection length in policy months, 12 * policy_term().

duration_mth(t)[source]#

Policy months elapsed at the end of month t. The projection index itself.

policy_year(t)[source]#

y(t) = ceil(t / 12): the contract year containing month t; 0 at issue.

duration(t)[source]#

Completed contract years at month t, policy_year(t) - 1 and 0 at issue.

age(t)[source]#

The attained age (ANB) during month t.

gp_index(t)[source]#

The index of the guarantee period containing month t: 1 for months 1..12n.

gp_start_month(t)[source]#

The month boundary at which the current guarantee period began; 0 for the first.

gp_end(t)[source]#

Months remaining in the current guarantee period.

in_gp_window(t)[source]#

True in the 30-day guarantee-period-end window [S1][S2][S5][S6].

The notes place the window in the month after a guarantee period closes, i.e. at t - 1 == 0 (mod 12n) with t > 1, so for n = 5 it is months 61, 121, 181, … In the window the full account value is available with no surrender charge and no MVA, and it is the only month in which annuitization is elected. Under annual_redeclare there is exactly one window, at the end of the initial term.

redeclare_month(t)[source]#

The month at which the credited rate in force at month t was declared.

scenario_rate(t, name)[source]#

Step-function lookup of column name in the model point’s rate scenario.

Each row of rate_scenario.csv states the level that holds from its own month until the next row of the same scenario, so a flat path is one row.

market_rate(t)[source]#

MR(t): the market competitor rate driving renewal declarations and dynamic lapse.

VM-22 defines it for a 5 to 7 year guarantee period as the 7-year Treasury rate plus a 50% A / 50% AA spread less the pricing spread [R2 6.B.5]; the model takes it as an exogenous input because the index is a state-filed variable [std].

mva_ref_yield(t)[source]#

it: the MVA reference yield at month t, from the scenario table [std].

The source design is Barclay’s US Credit Index with a state-varying formula [S8]; the model takes a scalar series rather than hard-coding an index.

credit_rate(t)[source]#

i_cr(t): the declared credited rate in force, effective annual.

The initial declared rate for the whole initial term [S11], then the renewal rule max(GMIR, MR(m) - s_ren) [std] with m the redeclaration month. The base run sets renewal_spread = 0, so the credited rate equals the competitor rate and the dynamic lapse term is exactly zero.

credit_rate_mth(t)[source]#

The monthly credited rate, (1 + i_cr(t))^(1/12) - 1.

Twelve of these factors reproduce the declared effective annual rate exactly, so the discretization moves interest only within a month. Do not compound daily as well.

mgsv_rate_mth()[source]#

The monthly GMSV accumulation rate, (1 + i_nf)^(1/12) - 1.

mgsv_rate_statutory(cmt5)[source]#

The Model #805 indexed nonforfeiture rate at a 5-year CMT level [R1 4.B].

max(0.0015, min(0.03, round(CMT5 to 1/20 of 1%) - 0.0125)). Two traps the notes call out: the floor is 15 basis points, not the widely repeated 1%; and the statute defines the minimum rate, it does not cap the contract rate — the contract must satisfy mgsv_rate() >= mgsv_rate_statutory(cmt5), which is what mgsv_rate_is_compliant() tests, not the reverse inequality.

mgsv_rate_is_compliant(cmt5)[source]#

True when the contract GMSV rate meets the statutory minimum at that CMT5 [R1 4.B].

av_pp_init()[source]#

AV(0) = P: 100% of premium credited, no front-end load [S5][S10][S16].

prem_to_av_pp(t)[source]#

Premium credited to the account value per contract; the whole premium at t = 0.

wd_scheduled_pp(t)[source]#

The gross withdrawal scheduled for month t in withdrawal_table.csv, else zero.

wd_pp(t)[source]#

W(t): the gross amount removed from the account value at BOM of month t.

Gross by construction: a contract promising a stated net check needs a gross-up solve, which is not implemented [S3]. Two sources add up here — the amount scheduled in withdrawal_table.csv, and the free-withdrawal utilization variant free_wd_util x FW(y) taken in the first month of each contract year. Base run utilization is 0 [std], so on model points other than the worked-example anchor the account value runs unbroken.

av_pp(t)[source]#

AV(t): account value per contract at the end of policy month t.

AV(t) = (AV(t-1) - W(t)) x (1 + i_cr(t))^(1/12) with AV(0) = P [S4][S5][S16]. No charges are deducted from the account value: this chassis carries no front-end load, no annual fee and no rider charges [S5][S10][S13][S16].

av_pp_at(t, timing)[source]#

Account value per contract at month t, read at the point given by timing.

"BEF_WD" is AV(t-1), "BEF_INV" is AV'(t) = AV(t-1) - W(t) and "EOM" is AV(t). At t = 0 all three are the premium.

inv_income_pp(t)[source]#

Interest credited to one contract’s account value at the end of month t.

interest_credited_pp(t)[source]#

Cumulative interest credited to one contract from issue to the end of month t.

Used by the min_sc_interest MVA cap, which limits the adjustment to the lesser of the surrender charge and the interest credited to date [S8][S9].

prem_accum_gmir_pp(t)[source]#

Premium less prior withdrawals accumulated at the GMIR.

The reference level for the gmir_floor MVA cap, under which AV + M may not fall below it [S13]; the surrender charge may still breach that level.

free_wd_base(y)[source]#

FWB(y): the free-withdrawal base fixed at the start of contract year y.

Purchase payments in year 1, the account value at the most recent anniversary thereafter [S10]. Making the base a fixed known amount at each anniversary is what lets E(t) = AV(t) - FW(t) be evaluated without a fixed point.

free_wd_allow(y)[source]#

The free-withdrawal allowance for contract year y, non-cumulative [S4][S16].

pct_av: free_wd_rate x FWB(y) [S10]. interest_only: the interest credited over the prior contract year [S5][S6], hence zero in year 1. greatest_of [S13] is not implemented and raises.

free_wd_avail(t)[source]#

FW(t): the unused free-withdrawal allowance at BOM of month t, before W(t).

Reset to the whole allowance in the first month of each contract year; carried forward within the year; never carried across an anniversary [S4][S16].

wd_free_pp(t)[source]#

The portion of W(t) covered by the free allowance, hence free of charge and MVA.

free_wd_remain(t)[source]#

The unused free-withdrawal allowance after month t’s withdrawal.

This, not free_wd_avail(), is the allowance a full surrender at the end of month t may still shelter.

wd_excess_pp(t)[source]#

E(t) for a partial withdrawal: max(0, W(t) - FW(t)) [S8][S9][S10].

The whole of W(t) when free_wd_mva_exempt is False [S3][S4].

surr_excess_pp(t)[source]#

E(t) for a full surrender at the end of month t: AV(t) - FW(t) [S8][S11].

The whole of AV(t) when free_wd_mva_exempt is False [S3][S4].

surr_charge_id(t)[source]#

The surrender charge schedule in force: initial, renewal or none.

Under rollover a fresh renewal schedule attaches at each guarantee-period boundary [S1][S2][S11]; under annual_redeclare no charge applies after the initial term [S13]. Getting this clock wrong relocates the shock lapse by years.

surr_charge_year(t)[source]#

sc_clock: the contract year within the schedule in force, 1..n.

Resets to 1 at each renewal under rollover [S1][S2][S11]. Voya and Nationwide instead run the clock from the original purchase payment date so it never restarts [S3][S4]; that variant is not carried on this chassis.

surr_charge_age_cap(t)[source]#

The attained-age cap on the surrender charge: 4% at 94 down to 0% at 98+ [S1][S2].

Returns 1.0 below the sourced age band, i.e. no cap.

surr_charge_rate(t)[source]#

sc(y): the surrender charge rate in force at month t.

9/8/7/6/5 over the initial term [S10] and 5/4/3/2/1 over a renewal term [S2], capped by the attained-age scale [S1][S2], zero once a schedule runs out, and zero in the 30-day guarantee-period-end window and before issue [S1][S2].

mva_term(t)[source]#

T(t): the MVA duration in years at the end of month t [S8].

The notes write it as (days to the end of the current contract year / 365) + whole years remaining in the MVA period. On a monthly grid those two terms collapse algebraically to (12 n k - t) / 12 — the time to the end of the current MVA period — and the same quantity serves as the geometric branch’s tau, which the notes define as days to maturity / 365.25. The two divisors differ in the sources; on exact twelfths they cannot.

mva_in_force(t)[source]#

True when an MVA applies at month t.

mu(t) = 0 unconditionally once the MVA period has expired [S8][S13][S16] and in the 30-day guarantee-period-end window [S2]. It is also zero on a death benefit [S2][S4][S8][S13][S16], on annuitization [S16], and on RMD or waiver withdrawals [S2][S5][S13]; the first two are handled where those benefits are valued and the third is not implemented.

mva_ref_yield_locked(t)[source]#

i0: the reference yield locked at the start of the current guarantee period.

The model point’s issue value for the initial term; re-locked at the prevailing reference yield at each renewal under rollover [S2][S11].

mva_ref_term_years(tau)[source]#

The whole-year maturity at which the distribution yield is read [S4].

Partial years round up, capped at the guarantee period: the Nationwide contract reads a 3-year yield for 985/365.25 = 2.69 years remaining while the exponent keeps the exact day count. With a single scalar reference yield per scenario this selection determines nothing; it is carried so a curve-valued extension has the rule in place.

mva_factor_geometric(i_deposit, i_dist, tau)[source]#

Phi: the geometric MVA factor ((1+a)/(1+b+s_adm))^tau [S3][S4].

s_adm is mva_admin_spread, 25 bp in the Nationwide contract, covering the cost of liquidating fixed-income investments and structurally biasing the adjustment against the owner; Voya’s variant sets it to zero [S3].

mva_duration_factor(t)[source]#

F_s: the declared-differential adjustment factor by years remaining [S14].

A contractual table of modified durations, read on the Ic < 6% or Ic >= 6% column and linearly interpolated between whole years, flat beyond the last row.

mva_rate(t)[source]#

mu(t): the market value adjustment rate at month t, signed and dimensionless.

linear_duration [std]: (i0 - it) x T(t) [S8][S9] — the first-order approximation of the geometric form, always in the contract holder’s disfavour when rates rise. geometric: Phi(t) - 1 [S3][S4]. declared_differential: (Ic - In) x F_s on the insurer’s own new-money rate [S14], with In taken as the market rate [std].

mva_pp_on(t, base)[source]#

M: the capped MVA amount on a currency base at month t.

Five cap rules, all first-class parameters because the cap is the largest single cross-carrier divergence in the source set:

sym_sc [std]

clamp(M_raw, -C, +C) [S2].

min_sc_interest

clamp(M_raw, -K, +K) with K = min(C, interest credited to date) [S8][S9].

asym_sc_snfl

M <= +C with no downside cap; only SB >= MGSV binds below [S12].

gmir_floor

AV + M >= premium less prior withdrawals accumulated at the GMIR [S13]. The floor is stated for a full surrender; applying it to a partial withdrawal base is an extension of the source.

none

Uncapped and fully two-sided [S3][S4].

surr_charge_pp(t)[source]#

C(t): the surrender charge on a full surrender at the end of month t.

mva_pp(t)[source]#

M(t): the capped MVA on a full surrender at the end of month t.

wd_charge_pp(t)[source]#

The surrender charge on month t’s partial withdrawal.

wd_mva_pp(t)[source]#

The capped MVA on month t’s partial withdrawal.

wd_payment_pp(t)[source]#

The cash paid on month t’s withdrawal, W(t) + M(t) - C(t).

surr_value_pp(t)[source]#

SV(t): the gross surrender value, AV(t) + M(t) - C(t), before the floor.

The composition order is account value, then MVA, then surrender charge, then the nonforfeiture floor, and it is not interchangeable: both M and C are computed on E(t) before either is deducted [S8]. Charging first and adjusting the net figure understates the adjustment by sc x |M|; flooring before the MVA silently removes the downside protection.

surr_benefit_pp(t)[source]#

SB(t): the surrender benefit actually paid, max(SV(t), MGSV(t)) [S8][S9][S12].

A binding floor is not a separate cash flow: it raises SB(t), and MGSV(t) - SV(t) is a reconciliation quantity only.

mgsv_pp_init()[source]#

MGSV(0) = 87.5% of gross consideration [R1 4.A(2)][S11].

mgsv_charge_pp(t)[source]#

c(t): the monthly slice of the annual contract charge deducted from the floor.

mgsv_wd_deduct_pp(t)[source]#

d(t): the withdrawal deducted from the Model #805 floor in month t.

W(t) under the gross convention [S11]; the cash actually paid, W(t) + M(t) - C(t), under net_of_charges [S9].

mgsv_pp(t)[source]#

MGSV(t): the Model #805 minimum guaranteed surrender value at end of month t.

MGSV(t) = [MGSV(t-1) - d(t) - c(t)] x (1 + i_nf)^(1/12) with MGSV(0) = 0.875 x P [R1 4.A][S11]. Premium tax actually paid and indebtedness are further permitted deductions, accumulated at the same rate, and are zero here.

taxable_wd_pp(t)[source]#

The income-first taxable portion of month t’s withdrawal [R6 72(e)(3)(A)].

Taxable to the extent the account value gross of surrender charge exceeds the basis. A reported quantity: it generates no insurer cash flow.

tax_basis_pp(t)[source]#

basis(t): the IRC 72 investment in the contract at the end of month t [R6].

Reduced only by the non-taxable remainder of a withdrawal. Reported, not a cash flow.

mort_rate(t)[source]#

The annual mortality rate at the attained age and sex, from mort_table.csv.

The shipped table is an illustrative Makeham curve [std], not a published basis. The prescribed basis is the 2012 IAM Basic table with Projection Scale G2 and the VM-22 Table 6.7 factors [R2 6.B.8][R9], which may not be redistributed here; swap it in by repointing Data.mort_table_file. Mortality is second-order on this product: death pays the full account value with no charge and no MVA.

mort_rate_mth(t)[source]#

q(t): the monthly mortality rate, 1 - (1 - q_x)^(1/12).

gmir_factor()[source]#

G: the VM-22 GMIR factor stepping the base lapse rate [R2 6.B.5].

1.25 at a GMIR of 1.0% or less, 1.00 up to 2.5%, 0.70 above. The representative 0.25% GMIR [S11] therefore takes the highest multiplier, which is the conservative end of the prescribed scale.

lapse_rate_base(t)[source]#

Base(y): the prescribed base lapse rate for the contract year containing month t.

VM-22 Table 6.5 mapped onto the five-year architecture as a [std] extension [R2][REG-R36]. rollover: 1% inside each guarantee period and 75% in the contract year following each expiry, repeating with period n. annual_redeclare: 1% to expiry, 75%, then 10%, 7.5% and 3% thereafter. The architecture switch, not the level, is the first-order modeling decision — Camp A creates a repeating shock, Camp B a single one.

lapse_dyn_exponent(t)[source]#

X: 2.0 during the initial surrender charge period, 2.5 at the shock and after [R2].

lapse_dyn_market(t)[source]#

Market(t): the credited-minus-competitor response of the VM-22 dynamic lapse form.

-1.25 (CR - MR)^X when the contract is competitive, zero inside the 50 bp buffer, +1.25 (MR - BF - CR)^X when it is not [R2 6.B.5]. The buffer and the power make the sensitivity strongly convex around CR = MR.

lapse_dyn_rate(t)[source]#

Rate(t): the dynamic term gated by the surrender-charge and negative-MVA haircut.

Market(t) x max(0, 1 - 5 (1 - CSV/AV)) with CSV/AV = SB(t)/AV(t) [R2 6.B.5], so the dynamic response switches off entirely once the combined haircut reaches 20%. The ITM factor is 1 for an Accumulation contract with no guaranteed living or death benefit, which the guideline states explicitly.

mva_lapse_factor_at(t)[source]#

Phi_MVA(t): the factor multiplying the dynamic term while an MVA is in force.

The VM-22 prescribed values are 0 in force and 1 expired [R2], i.e. the regulator’s view is that an in-force MVA completely neutralizes disintermediation. The model exposes the in-force value as mva_lapse_factor, defaulting to the best-estimate 0.35 [std] rather than the prescribed 0; set it to 0 for the statutory run. It is a judgement, not a calibration — sensitivity-test it.

lapse_rate(t)[source]#

w_annual(t): clamp(Base(y) G + Rate(t) Phi_MVA(t), 0.005, 0.90) [R2 6.B.5].

The annual total surrender rate, paired with lapse_rate_mth() exactly as mort_rate() is paired with mort_rate_mth().

On the base deterministic run MR = CR and renewal_spread = 0, so Market(t) = Rate(t) = 0 and this is Base(y) x 1.25: contract years 1 to 5 at 1.25% and the shock year at 90%, the 93.75% product capped.

lapse_rate_mth(t)[source]#

w(t): the monthly total surrender rate, 1 - (1 - w_annual)^(1/12).

This is the rate the monthly decrement actually applies; lapse_rate() is the annual rate it is derived from.

annuitization_rate(t)[source]#

a(t): the monthly annuitization election rate.

1.0% of in-force in each 30-day guarantee-period-end window after contract year 1 and zero elsewhere [std]. No public annuitization take-up study for deferred annuities was located; VM-22 prescribes 0% at all projection intervals for the standard projection [R2 6.B.6], which is a statutory simplification rather than an experience estimate. Set annuitization_rate_window = 0 for the statutory alternative.

pols_if(t)[source]#

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

The library-wide convention, following Term_US_A and CashValue_SE: this is the count that opens month t and the weight applied to that same month’s cash flows, so the pols_if column of result_cf() reconciles with the row it sits on. pols_if(0) = pols_if(1) = pols_if_init(), the issue instant carrying no decrement.

The notes’ end-of-month l(t) = l(t-1)(1 - a(t))(1 - q(t))(1 - w(t)) is pols_if_at(t, "AFT_DECR"); the two agree as pols_if(t + 1) in every month but proj_len(), where pols_maturity() annuitizes the survivors out and the block opens the following month empty.

pols_if_at(t, timing)[source]#

In-force at month t read at the point given by timing.

"BEF_DECR" is l(t-1), equal to pols_if(); "BEF_MORT" is after annuitization elections; "BEF_LAPSE" is after deaths; "AFT_DECR" is after surrenders and is the notes’ end-of-month l(t). The order is the notes’ l(t) = l(t-1)(1 - a)(1 - q)(1 - w) [std order].

pols_annuitization(t)[source]#

Annuitization elections in month t, taken at BOM before crediting.

pols_death(t)[source]#

Deaths in month t, on the in-force net of annuitization elections.

pols_lapse(t)[source]#

Full surrenders in month t, on the survivors of annuitization and mortality.

pols_maturity(t)[source]#

Deemed-maturity annuitizations, non-zero only at proj_len().

Not a decrement — the projection horizon runs out — but needed for the in-force roll-forward to close; see the Space docstring.

pols_decr(t, kind)[source]#

The number of contracts leaving in month t by benefit kind.

annuitization_pp(t)[source]#

The amount transferred to the payout model on an annuitization election.

The full account value AV'(t) in the 30-day window; outside it, AV'(t) less the surrender charge computed on AV'(t), with no MVA [S1][S2][S5][S16][std].

The notes name SV(t) for the non-window case, and SV(t) = AV(t) + M(t) - C(t) is composed on the post-crediting account value — but the same notes make annuitization an elective BOM transaction and set mu(t) = 0 on it. The two cannot both hold on a monthly grid, and the model resolves it at BOM: annuitizers leave before the month’s interest is credited, which is exactly what claim_from_av_pp() releases for them (AV'(t), not AV(t)). This cells is therefore the BOM analogue of SV(t) — the same composition on the account value the annuitant actually takes with them. Reading SV(t) literally would pay out one month of interest the block never credited to those contracts, and the difference (AV(t) - AV'(t)) * (1 - sc(y)) would surface as a spurious claims_over_av(). The choice is inert on every shipped model point, where a(t) is zero outside the window; a test pins it open.

No nonforfeiture floor is applied in either branch: SV(t) is the pre-floor value. Payout factors are out of scope: no retrieved product document contains an annuity rate table, so the accumulation model emits the transfer and hands the amount on.

claim_pp(t, kind)[source]#

The benefit paid per contract in month t by kind.

"DEATH" is the full account value with no charge and no MVA [S1][S2][S13], floored at the cash surrender benefit and hence at MGSV(t) [R1 6]; on the base run the floor is inert because 4.45% exceeds 2.80%, but at a renewal rate down to the GMIR it overtakes the account value, so it is tested at every duration [std]. "LAPSE" is SB(t). "ANNUITIZATION" is annuitization_pp(). "MATURITY" is the account value floored at MGSV at the projection horizon.

claim_from_av_pp(t, kind)[source]#

The account value released per contract by a claim of kind.

Equal to claim_pp() except where the Model #805 floor binds; the difference is claims_over_av(). Annuitizers leave at BOM and so release AV'(t), before the month’s interest.

premiums(t)[source]#

Premium income: the single purchase payment at t = 0.

prem_to_av(t)[source]#

Premium credited to the account value, in-force weighted.

withdrawals(t)[source]#

Withdrawal payments in month t: (W(t) + M(t) - C(t)) weighted by l(t-1).

The ledger separates the free part (wd_free_pp, never charged or adjusted) from the excess (wd_excess_pp); this line is the total cash paid. The weight is pols_if(), the start-of-month count printed on this same row of result_cf(), because the withdrawal is taken at BOM before any decrement.

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

Benefit outgo in month t, for one kind or, with kind=None, all four.

claims_from_av(t, kind)[source]#

The account value released by a claim of kind, in-force weighted.

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

Benefit paid less the account value released, claims - claims_from_av.

Signed, and normally negative on this chassis: a surrender pays out less than the account value it releases, by the surrender charge and any negative MVA. It turns positive exactly when the Model #805 floor binds and SB(t) exceeds AV(t). Either way it is a reconciliation quantity, not a cash flow of its own — a binding floor raises SB(t), it does not add a separate top-up line, and the charge and the MVA are internal accounting entries rather than ledger lines.

commissions(t)[source]#

Acquisition commission: 2.00% of premium paid at issue [std].

No retrieved document discloses MYGA commission; this is a pure modeling assumption.

premium_taxes(t)[source]#

Premium tax on the purchase payment, 0% [std] [S3][S16].

inflation_factor(t)[source]#

The expense inflation factor in the contract year containing month t.

expenses(t)[source]#

Insurer expenses: acquisition at issue and inflating maintenance monthly [std].

(50 / 12) x 1.025^(y-1) per contract per month, in-force weighted. VM-22 prescribes $35 x 1.025^(offset) per contract for business the company does not administer [R2 6.B.3]; the $50 figure is a [std] uplift of that anchor to a self-administered block. expense_acq is 0 because this chassis’s only acquisition cost is the commission.

net_cf(t)[source]#

Net cash flow in policy month t.

Interest credited, the surrender charge, the MVA and the movement of the Model #805 floor are internal accounting entries, not ledger lines: they drive AV, MGSV and the benefit amount but are never cash flows of their own. Only amounts paid to or received from the contract holder, and the insurer’s own expenses, appear here.

av_at(t, timing)[source]#

The in-force weighted account value at month t; see av_pp_at() for timing.

Each timing carries the in-force appropriate to that point in the month: l(t-1) before the withdrawal, l(t-1)(1 - a(t)) once annuitizers have left, and at the end of the month the count that survives every exit — pols_if(t + 1), which is l(t) except at proj_len(), where the deemed maturity empties the block. Using pols_if_at(t, "AFT_DECR") there would leave the matured account value standing in the block and released as a claim, and check_av_roll_fwd() would not close.

inv_income(t)[source]#

Interest credited to the whole block in month t.

wd_from_av(t)[source]#

The account value released by month t’s withdrawals, gross of charge and MVA.

av_change(t)[source]#

The change in the block’s account value over month t.

check_av_roll_fwd_resid(t)[source]#

Account value roll-forward residual in month t; zero to floating point.

AV(t) - AV(t-1) = premium in - withdrawals out + interest credited - the account value released by each of the four claim kinds. Note that the cash paid on a surrender may exceed the account value released when the Model #805 floor binds; that excess is claims_over_av() and is not part of this identity. t = 0 is the premium deposit itself and is excluded.

The signed residual, for a debugging session that needs to know where and by how much check_av_roll_fwd() failed.

check_av_roll_fwd()[source]#

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

Takes no argument and returns a bool, following CashValue_SE.check_av_roll_fwd and the library convention, so one test can call the same check across every model. check_av_roll_fwd_resid() carries the per-month signed residual, and this is abs(resid) <= check_tol_av over t = 1 .. proj_len().

check_pols_roll_fwd_resid(t)[source]#

In-force roll-forward residual in month t; zero to floating point.

pols_if(t) - pols_if(t+1) = annuitizations + deaths + surrenders + deemed maturities: the start-of-month count opens the row, the four exits are taken during it and the next month opens on what is left. In the notes’ own indexing that is l(t-1) - l(t), since pols_if() is the start-of-month count.

The signed residual; check_pols_roll_fwd() is the bool over all t.

check_pols_roll_fwd()[source]#

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

No argument, returns a bool over t = 1 .. proj_len(), matching check_av_roll_fwd() and the library convention.

result_cf()[source]#

Result table of cashflows, indexed by policy month t from 0 to proj_len().

t = 0 carries the purchase payment, the acquisition commission and the premium tax, exactly as the notes’ cash flow ledger indexes them.

The pols_if column is the start-of-month count, which is the weight applied to every cash flow on that same row, so the printed in-force reconciles with the printed flows: withdrawals(t) / wd_payment_pp(t) and expenses(t) / ((expense_maint / 12) * inflation_factor(t)) both return it.

result_pols()[source]#

Result table of in-force movements, indexed by policy month t.

pols_if opens the row (the start-of-month count, the same column result_cf() prints), the four exit columns are taken during the month, and pols_if_aft_decr is the notes’ end-of-month l(t). The closing balance is next month’s pols_if, which equals pols_if_aft_decr in every month but proj_len(), where the deemed maturity empties the block.

result_av()[source]#

Result table of per-contract account value and surrender values, indexed by t.

The first five columns are the worked example’s table; the rest are its surrender trace, available at every duration.