The FIA_US_S Model#
Reference liability cash flow model for U.S. fixed indexed annuities with a GLWB.
FIA_US_S is the executable counterpart of
products/fixed_indexed_annuity/technical-notes.md in the lifelib-products
library. It projects gross liability cash flows for a single-contract model point of a
single-premium fixed indexed annuity carrying a guaranteed lifetime withdrawal benefit:
a 7% premium bonus vesting over ten years, one annual point-to-point indexed account
credited at max(0%, min(cap, index return)), a notional benefit base growing by a
guaranteed simple rollup plus 150% of realised dollar credits, a 0.95% rider charge
on that base, a lifetime withdrawal locked at the attained age of first exercise, and —
the economic centre of the product — a guaranteed income stream that survives
account-value exhaustion and pays for life.
The base contract is the deferred annuity chassis of
MYGA_US_S: the surrender-benefit composition order and the NAIC
Model #805 floor construction are the chassis’s. Everything else here is restated by
the FIA notes with its own parameters and must not be carried across from the chassis —
the account-value roll-forward is index-credit driven rather than interest-accretion
driven, the MVA is the ratio form [(1+i0)/(1+it)]^(n/12) - 1 rather than the linear
(i0 - it) x T, the death benefit is max(AV, MGV), and the lapse architecture is
rider-suppressed rather than a plain shock at surrender-charge expiry. The chassis’s
MGSV and these notes’ MGV are one quantity under two source labels; this
model uses the chassis name mgsv_pp()
throughout.
Spaces. The model contains two:
DataReads the seven input CSVs and holds their filename References. It takes no parameters, so each file is read once per model.
ProjectionThe by-contract projection, parameterized by
point_id:Projection[1]is an ItemSpace projecting model point 1. It reaches the input tables through itsdataReference, which resolves to the singleDataSpace.
The split matters for more than tidiness. Because Projection is parameterized, every
Projection[N] is a separate ItemSpace with its own cells cache; readers placed there
would re-read every file for every model point. In Data they are evaluated once,
however many contracts are projected.
Input data is external: CSVs in the model folder’s parent directory, read at run time rather than stored inside the model. The model folder itself holds no data, so the model and its inputs must travel together.
Projection basis. Monthly steps, as the _S suffix and the product assignment table
both say. t is the 0-based month index: duration(t) = t // 12 is the completed
contract years, policy_year(t) = duration(t) + 1 is the contractual label, and the
frame is t = entry_mth() … proj_len() - 1 with proj_len() = 12 * policy_term().
Every mechanic of the contract is still annual. Annual point-to-point crediting
[S2][S4][S10], the rider charge at the end of each contract year [S9], the benefit base
update [S9] and the lifetime withdrawal are all annual, and the notes make the anniversary
the single event date — so each of them happens once a year, in the anniversary month
is_anniv(t), and the contract moves no cash between anniversaries. What the finer grid
resolves is everything that is not a contractual event: mortality and surrender fall in the
month they happen, the Model #805 floor and the fixed account accrue month by month so a
mid-year death or surrender is valued on the balance it actually has, and maintenance
expense accrues where it is incurred. The excluded variants stay excluded — monthly-sum
crediting, one carrier’s monthly charge deduction, daily interim values, mid-year
withdrawal crediting are all still absent, and the indexed account is deliberately flat
between anniversaries.
Because the monthly decrement rates compound to the annual ones and every contractual step
is unmoved, every state value at an anniversary equals the annual-step model’s: account
value, benefit base, rollup base, lifetime withdrawal, Model #805 floor, phase and
pols_if at t = 12k. The cash differs, and that is the point — result_cf_annual()
sums the monthly frame into contract years and agrees exactly on pols_if, premiums,
commissions and premium_taxes, but not on the withdrawals, the claims or the
expenses.
age(t) = age_at_entry() + duration(t) is the attained age opening the contract year of
month t, so that year’s mortality reads age(t) itself; mort_rate_mth(t) converts
it for the month. The transactions at the closing anniversary are one year older:
exercise_age(t) is the age that reads the lifetime-withdrawal percentage table and
clears the minimum exercise age.
The anniversary’s processing order is the notes’ own, and every quantity that changes
inside it is exposed through a timing argument rather than being buried:
index credit and fixed interest —
av_pp_at(t, "BEF_FEE")rider charge on the opening benefit base —
av_pp_at(t, "BEF_WD")benefit base: rollup, stack, step-up —
benefit_base_pp_at(t, "BEF_WD")lifetime and excess withdrawal —
lw_pp_at(t, "BEF_WD"),wd_pp(t)charges on the excess and the proportional reduction of the guarantee —
wd_reduction_rate(t),benefit_base_pp(t)guaranteed minimum value roll —
mgsv_pp(t)phase transition including the depletion test —
phase(t)decrements —
pols_if_at(t, "AFT_DECR")
pols_if(t) is the in-force count at the start of month t, the library-wide
convention set by Term_US_S and savings.CashValue_SE, and it is the weight
carried by every cash flow reported on the same row of result_cf(). At t = 12k it
is the technical notes’ own l(k), the probability in force at the end of contract year
k, and the equality is exact. At an anniversary month it is the count that reaches
the anniversary, which is the weight that month’s withdrawal and charges carry — where the
annual grid could only weight them by the count that entered the year.
Steps 1–3 are skipped in DEPLETED and steps 1–7 in TERMINATED; step 8 is the one
that runs every month. There is no issue-instant row: the premium, the premium bonus
and the acquisition expense are beginning-of-month flows of month 0 on a new-issue
model point, alongside that month’s own activity. A model point may instead be entered in
force after entry_year() completed contract years on stated balances — which is what
the worked example does, and why result_cf() is indexed from
entry_mth() = 12 * entry_year() rather than always from zero.
Undiscounted. Like every model in this library, this one projects gross liability cash flows only; reserves and discounting are a separate layer, and the notes’ own Valuation and reserve pointers section cites AG 33, AG 35 and VM-22 rather than reproducing them. The contractual discounting that lives inside a benefit formula — the ratio-form MVA — is part of the product and stays.
What is sourced and what is not. The contractual elements come from the composite
specimen: the 0% index credit floor [S1][S4][S10][R1]; the 5.25% declared cap and 2.30%
fixed rate [S2] against the 0.25% guaranteed minimum cap [S4] and 1.00% guaranteed
minimum fixed rate [S10]; the 9.1%-to-0% surrender charge schedule and the 0-to-100%
bonus vesting vector [S5]; the 7% premium bonus [S5] and the b/(1+b) clawback [S10];
the 10% free withdrawal [S1][S3][S5][S6][S9][S10]; the ratio-form MVA and its
nonforfeiture collar [S10]; the 5.00%/2.00% guaranteed simple rollup [S2]; the 150%
stacking factor [S8][S9]; the 0.95% rider charge on the benefit base, deducted after
index credits [S9]; the lifetime withdrawal percentage bands [S3]; the cause-dependent
treatment of account-value exhaustion [S1][S5][S9]; and the Model #805 construction —
87.5% of premium excluding the bonus accumulated at a nonforfeiture rate inside the
0.15%-3% corridor, whose statutory floor is 15 basis points, not 1% [R2][R3].
Everything behavioural and expense-related is a standardization: the anniversary-only
event date; the annual step-up (no retrieved document describes an
automatic ratchet during deferral); the 1.00% flat nonforfeiture rate inside the
corridor; the insurer-favourable reading under which the guaranteed withdrawal consumes
the free withdrawal amount; the base surrender vector 2/3/4/5/6% and the three-way shock
lapse 33%/10%/5%; the rider moneyness multiplier; the locking of the payout percentage
at first exercise; the 6.0%-of-premium acquisition expense; the $80 per contract per
year maintenance expense inflating at 2.5%; the 0% premium tax; the illustrative
mortality table; and the attained age 120 projection horizon. Two crediting parameters
belong on that list rather than on the sourced one: the notes give the index-margin and
performance-trigger forms but declare no level for either, so spread_rate = 2.00%
and trigger_rate = 4.50% are illustrative [std] levels that exercise those
branches and nothing more. The cap (5.25% [S2]) and the participation rate (80%, from
[R1]’s worked min(80% x 10%, 6%) = 6%) are the sourced ones.
Exhaustion pays differently depending on its cause. On the anniversary the account
value runs out, the withdrawal requested exceeds the balance available to meet it. In
DEPLETED the insurer funds the whole shortfall, because the guarantee survives and
that stream is the product. On the TERMINATED branch it funds none of it: the
balance is gone and the rider that would have covered the rest was destroyed by the very
withdrawal being paid, [S5] treating the contract “as well as the rider” as surrendered
at that point. Paying the request in full on both branches would honour the guarantee in
the year the excess withdrawal kills it. The cap sits on the payment only — wd_pp
still carries the amount requested, so the excess still sets depletion_cause, still
drives the proportional reduction to 1 and still takes the benefit base to zero — and
wd_unfunded_pp is the part kept out of the ledger.
This model is a mechanics demonstration, not a pricing or reserving result. Replace
the assumption tables with company data before drawing any conclusion from the output.
Where the notes are silent, both readings are shipped. The MVA collar is stated on
the gross withdrawal but justified as a surrender-value test, which the worked
example only ever exercises at a full surrender. Read literally it gives a partial
withdrawal below the nonforfeiture floor no adjustment at all; read as a test on the
contract it gives the adjustment the rate produces. mva_collar_basis selects, the two
agree on the surrender path so the worked example reproduces under either, and a test
pins the gap open rather than closing it in either direction.
Not implemented. Named here so the gaps cannot be mistaken for oversights. The
monthly-sum crediting method max(f, sum_k min(R_k, c_m)), which needs a monthly
index path — a grid of months is not the same thing, and rate_scenario.csv states
index levels at anniversaries only [S4][R1]; interim values in either documented form, both
daily marks of the embedded option rather than interpolations [S10][S11]; the cap
re-declaration rule, because the notes state the target (set the cap so the one-year
call-spread cost equals the option budget) but give no option-pricing function, so the
base projection holds the snapshot scale level [R1][R6]; stochastic GLWB activation on the
h(a) incidence table, which cannot be applied to a single deterministic cell —
activation_rate() reports the
table and the base run activates at the model point’s income_start_age instead;
generational mortality projection with Scale G2, because the 2012 IAM/IAR family and the
G2 scale may not be redistributed here [REG-R59][REG-R60]; joint-life survivorship, the
notes specifying the joint payout percentage but no second-life mortality; the income
doubler, confinement and terminal illness waivers, and annuitization, all described and
put out of scope by the notes themselves; additional premium; and check_margin(),
because the notes define no margin decomposition and the model projects no asset side.
Model points. model_point_table.csv carries nine contracts on the anchor
configuration — male 62 ANB, non-qualified, $100,000 single premium, GLWB elected at
issue, income from attained age 70. Point 1 is the worked example, entered in force
at anniversary 7 on the balances the notes state there; point 2 is the same cell issued
at t = 0; point 3 is the notes’ “Where the step-up binds” block, growth mechanism (a);
point 4 is growth mechanism (b), pure stacking with only half of index credits reaching
the account value and the only non-zero fixed allocation, so it is the point that exercises
the monthly fixed-account accrual; point 5 is joint life; point 6 carries no rider, so its
shock lapse is 33%; point 7 overdraws at 105% of the maximum and so loses the guarantee at exhaustion;
point 8 takes a pre-exercise withdrawal that attracts the charge, the clawback and the
MVA; and point 9 defers income to attained age 85, past both the year the surrender charge
expires and the end of the twenty-year growth window, so its shock lapse is the 10%
rider-in-force-but-not-activated rate, its benefit base stops growing on the
contract-year-20 leg of T_g rather than on the first withdrawal, and its payout
percentage locks in the 80+ band. Between them they exercise both benefit-base growth
mechanisms, all three shock-lapse rates, both legs of the growth window, both step-up
binding cases, all four phases, both proportional-reduction denominators and both
depletion attributions, so no branch of the notes’ parameter set is dead code. A test
asserts every point projects.
Verification. tests/test_fixed_indexed_annuity_us.py asserts all sixteen rows of
the notes’ worked example table and every line of its surrender trace, to the cent and to
the eight decimals the MVA factor is displayed at; the “Where the step-up binds” variant
block on point 3; the notes’ statement that the step-up binds in contract year 1 on a
new issue with a zero index credit; the depletion arithmetic — $11,997.42 a year,
exhausted during contract year 19 at attained age 81 — and the survival of the income
stream after it; the verbatim [S9] excess-withdrawal reduction and the verbatim [S10]
clawback; the in-force and account-value roll-forwards, both through the no-argument
check_pols_roll_fwd() and check_av_roll_fwd() and through the per-month
check_*_resid(t) residuals they are built on; that every anniversary value of the
account value, the benefit base, the Model #805 floor, the lifetime withdrawal, the phase
and pols_if reproduces the annual-step model exactly, and that no contractual cash
moves between anniversaries; that the pols_if column of
result_cf() is the weight carried by the cash flows on its own row; the payment cap
on the terminating exhaustion branch; and one test per pitfall the notes state as a model
mechanic. Three of the thirteen entries in the notes’ Known modeling pitfalls list are
not model mechanics and carry no test: that the behavioural assumptions must not be
reused in a CARVM valuation, that “efficient policyholder selection” is not AG 33’s
language, and that the declared parameters are stale and state-varying are all statements
about how the projection may be used rather than about what it computes, and no
assertion can reach them. A fourth, “Interim values and index costs”, is half covered —
the interim-value structures it names are not implemented at all, while the index-cost
haircut it also names is, and is asserted to come off R(t) ahead of both the cap and
the participation rate. One line of the notes does not reproduce and is
pinned both ways instead: the surrender trace’s net proceeds of 115,741.64 is the sum of
the displayed cent-rounded components, and carried at full precision — which the notes’
own rounding convention asks for — the net is 0.55 cents lower. Every component
reproduces exactly.
Example
>>> import modelx as mx
>>> model = mx.read_model("products/fixed_indexed_annuity/FIA_US_S")
>>> model.Projection[1].result_cf() # by month
>>> model.Projection[1].result_cf_annual() # summed into contract years