The Projection Space#
The by-policy projection of the UC_FR_S model.
The Space is parameterized by point_id, so Projection[1] is an ItemSpace
projecting model point 1:
>>> Projection[1].result_av() # the worked-example table, column for column
>>> Projection[2].result_cf() # the base run, 360 months
>>> Projection.point_id = 3 # the indexee floor on the same path
t counts policy months and is 0-based: t = 0 is the issue month, period
t runs from time t to time t + 1, and the frame is t = 0 … proj_len() - 1.
Every balance is read at the end of month t after that month’s levy — so
av_euro_pp(t) is the opening euro balance of month t + 1, exactly as the notes’
worked-example table prints it.
The balances at issue — time 0, before month 0 opens, the notes’ init row — are not a
frame row. They are the *_init cells (unit_price_init(), units_init(),
av_euro_init_pp(), cum_prem_net_init(), uc_cost_basis_init(),
prem_to_av_pp()), and each month reaches its own opening balance through an opening
cells — unit_price_open(), units_open(), av_euro_open_pp() and
av_pp_at(t, "OPENING") — which is the issue value when t = 0 and the previous
month’s close thereafter. Nothing is ever indexed at t = -1.
The in-force count is read the other way round — pols_if() (t) is the count at
the start of month t, so the frame opens at pols_if(0) = pols_if_init(); see
below.
Input data
Inputs are external files: plain CSVs living in the model folder’s parent directory,
products/assurance_vie_uc/, 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
UC_FR_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 |
lapse_table_file |
data.lapse_table() |
lapse_table.csv |
plancher_rate_table_file |
data.plancher_rate_table() |
plancher_rate_table.csv |
uc_scenario_table_file |
data.uc_scenario_table() |
uc_scenario_table.csv |
Naming
Cells names follow lifelib’s savings.CashValue_SE and the account-value vocabulary
this library settled on — av_pp_at(t, timing) for the account value read at a point
inside the month, check_av_roll_fwd for its identity, _pp for a per-policy amount
and no suffix for the same amount weighted by the in-force count. The technical notes use
compact symbols instead. The mapping is:
Notes symbol |
Cells |
Meaning |
|---|---|---|
t |
(the cells argument) |
Policy month, 0-based |
y = t//12 + 1 |
policy_year(t) |
Policy year containing t |
a |
age(t) |
Attained age (ALB) |
(none) |
duration(t) |
Completed policy years |
(none) |
duration_mth(t) |
Months elapsed at start of t |
(the model point row) |
model_point() |
The selected model point |
(number of periods) |
proj_len() |
Months projected |
P, e |
premium(), prem_charge_rate() |
Single premium and charge |
P(1-e) |
prem_to_av_pp() |
Net premium allocated |
alpha |
uc_alloc() |
Share allocated to UC |
1 - alpha |
euro_alloc() |
Share allocated to euro |
p_init |
unit_price_init() |
Liquidation value at issue |
p(t) |
unit_price(t) |
Liquidation value, end of t |
p(t-1) |
unit_price_open(t) |
The same, start of t |
(scenario) |
uc_return_mth(t) |
Monthly UC return |
n_init |
units_init() |
Unit count at issue |
n(t) |
units(t) |
Unit count held, end of t |
n(t-1) |
units_open(t) |
The same, start of t |
n(t-1) c_m |
fee_units(t) |
Units cancelled by the fee |
c, c_m |
mgmt_fee_rate_uc(), mgmt_fee_rate_uc_mth() |
UC management charge |
mgmt_fee_uc(t) |
mgmt_fee_uc_pp(t) |
Charge collected, per policy |
U(t) |
av_uc_pp(t) |
UC account value |
V_init |
av_euro_init_pp() |
Euro balance at issue |
V(t) |
av_euro_pp(t) |
Euro account value, end of t |
V(t-1) |
av_euro_open_pp(t) |
The same, start of t |
i_e |
euro_credit_rate() |
Euro credited rate, net |
(1+i_e)^(1/12) |
euro_credit_factor_mth() |
Its monthly factor |
av_pp_at(t, timing) |
av_pp_at(t, timing) |
Total account value |
(x in force) |
av_at(t, timing) |
The same, weighted |
A(t), phi |
arb_amount_pp(t), arbitrage_fee_rate() |
Arbitrage and its fee |
W(t) |
wd_amount_pp(t) |
Partial surrender |
W_uc(t), W_eur(t) |
wd_uc_pp(t), wd_eur_pp(t) |
Its pro-rata split |
S_init |
cum_prem_net_init() |
Floor base at issue |
S(t) |
cum_prem_net(t) |
Floor base |
R(t) |
plancher_ratchet(t) |
Ratchet level (cliquet) |
F(t) |
plancher_amount(t) |
The floor |
K(t) |
nar(t) |
Capital sous risque |
pi(a) |
plancher_rate(t) |
Tariff / 10,000 |
pi(a)/12 |
plancher_rate_mth(t) |
Its monthly step |
K pi/12 |
plancher_charge_pp(t) |
Rider premium, per policy |
B_init |
uc_cost_basis_init() |
Levy base at issue |
B(t) |
uc_cost_basis(t) |
Prelevements sociaux base |
tau |
social_levy_rate |
17.2% |
q_a, q_m(t) |
mort_rate(t), mort_rate_mth(t) |
Mortality rates |
w_base(y) |
lapse_rate_base(t) |
Table surrender rate |
M_perf(t) |
perf_factor(t) |
Performance multiplier |
M_pl(t) |
plancher_factor(t) |
Moneyness multiplier |
w_ann(y,t), w_m(t) |
lapse_rate(t), lapse_rate_mth(t) |
Surrender rates applied |
l(t) |
pols_if(t) |
In force at the start of t |
l(t+1) |
pols_if_at(t, “AFT_DECR”) |
End of t, after decrements |
(none) |
pols_death(t), pols_lapse(t) |
Decrements in month t |
E(t) |
expenses(t) |
Acquisition + maintenance |
(benefit outgo) |
claims(t, kind) |
Gross benefit outgo by kind |
(unit and euro releases) |
av_releases(t) |
Account value released |
K x l q_m |
plancher_strain(t) |
Non-unit cost of deaths |
net_cf(t) |
net_cf(t) |
Non-unit cash flow |
Five names needed care.
mgmt_fee_uc(t) in the notes is a per-policy amount, and the library reserves the
unsuffixed name for the in-force-weighted flow. The notes’ quantity is
mgmt_fee_uc_pp(); mgmt_fee_uc() is pols_if(t) times it, and the same rule
splits plancher_charge_pp() from plancher_charge() and
arb_fee_pp() from arbitrage_fee(). The notes’ worked-example table prints the
_pp quantities; the notes’ insurer-side extraction prints the weighted ones, which is
why its year-1 management charge is 621.33 € against the table’s 630.20 €.
pols_if(t) is the in-force probability at the start of month t, so
pols_if(0) = pols_if_init() and result_cf() opens on it. It is therefore
exactly the weight carried by the flows on its own row, which is the one thing a
reader of the frame needs it to be: dividing any cash flow on row t by that row’s
pols_if recovers the per-policy amount.
This model was first written the other way round, publishing the end-of-month count
under the name pols_if while weighting each row’s flows at pols_if(t - 1).
Nothing raised and nothing went NaN — the published exposure column was simply the correct
series shifted one month, and a per-policy amount recovered from it was one month stale.
The rename fixes it and collides with nothing, because the end-of-month quantity is
still here: it is ``pols_if_at(t, “AFT_DECR”)``, the CashValue_SE timing form the
shared vocabulary prescribes, equal to pols_if(t) (1 - q_m)(1 - w_m) and to
pols_if(t + 1) everywhere the projection runs on. "BEF_DECR" and "BEF_LAPSE"
expose the two points inside the month. Every cash-flow number in result_cf() is
unchanged by the rename; only the pols_if column moved.
The account value is a stock read at the end of the month, so av_at(),
av_uc_at() and av_euro_at() weight it by pols_if_at(t, "AFT_DECR") — the
notes’ l(t + 1), the policies the balance is still carried for once the month’s
decrements have gone — which is what makes av_at(11, "BEF_DECR") = 77,330.08 x 0.968240
on the anchor cell’s last month.
net_cf on this product is the non-unit cash flow of the UC leg and the rider, not
a gross liability total and not the contract’s margin. Every benefit is funded by
cancelling units and by drawing the euro balance, so a gross presentation adds the same
money to both sides; and the euro leg’s own margin is Euro_FR_S’s output, which must
be added from outside. The gross flows are still published — claims_death,
claims_lapse, withdrawals and av_releases are result_cf columns — and
check_benefit_funding() asserts that they net exactly against the account value plus
the death strain.
withdrawals is a partial surrender, an owner election rather than a claim, so it
carries its own name and its own column and is never one of the claims kinds.
social_levy_uc is the prélèvements sociaux withheld and remitted on the UC leg.
It is a pass-through, not insurer income and not an expense, and it is published as its
own column precisely so that its exclusion from net_cf() is visible rather than
merely asserted.
The month, in the order it happens
Per policy, within month t:
p(t) = p(t-1) (1 + r_uc(t)) the liquidation value moves
V = V(t-1) (1 + i_e)^(1/12) the euro leg accrues
n = n(t-1) - n(t-1) c_m the charge cancels units
V -= A(t) ; n += A(t)(1 - phi)/p(t) the arbitrage settles
W(t) split pro rata ; n -= W_uc/p(t) ; V -= W_eur
F(t), K(t) observed on U + V the floor and the risk
plancher premium levied, euro first
decrements at end of month, deaths before surrenders
p(t-1), n(t-1) and V(t-1) above are the opening balances of month t,
which in the first month, t = 0, are the balances at issue: they are read through
unit_price_open(), units_open() and av_euro_open_pp() rather than by
indexing a month that does not exist.
Two points in that order are load-bearing and both are listed pitfalls. The management
charge is taken on the opening unit count, n(t-1), not the closing one: in a month
with an arbitrage the two differ by the arbitrage’s units, 52.28 € against 59.54 € at
t = 2 on the anchor cell. And the monthly charge rate is ``c/12``, not
1 - (1 - c)^(1/12): the insurers compound the periodic rate, so 0.25% a quarter
gives an annual factor of (1 - 0.0025)^4 = 0.99003744 rather than 1 - 1.00%.
av_pp_at() exposes "BEF_FEE", "BEF_WD", "BEF_LEVY" and "BEF_DECR"
so the ordering is inspectable rather than buried in one expression, and
check_av_roll_fwd() asserts the identity every month against independently computed
growth.
The garantie plancher
The floor F(t) follows the elected plancher_basis(): simple is the running
base of premiums net of the premium charge less partial surrenders; indexee indexes
that base at 3.50% a year and deducts the nominal withdrawal; cliquet locks in
account-value highs at each ratchet date and adjusts for a partial surrender
proportionally, because a ratchet is a value level rather than a premium tally. On the
same path the three give 94,000.00, 97,378.25 and 94,216.29 at t = 11.
The capital sous risque is min(cap, max(0, F - AV)), and everything about the rider
follows from that one expression:
it is floored at zero, so the rider costs nothing out of the money and the death strain never becomes a rebate booked as insurance profit;
it is the charge base, not the account value — the charge on the account value would be 4.6 times larger at
t = 11on the anchor cell;it is capped on the risk, not on the benefit, so the excess reduces the floor rather than truncating what the beneficiary is paid; and
it is the insurer’s cost per death, exactly, because the rest of the death benefit is the policyholder’s own account value.
An arbitrage never moves the floor. It is neither a premium nor a surrender: it moves
value between the legs and pays a fee, and check_floor_base() asserts that the floor
base is the net premium less cumulative withdrawals and nothing else.
The levy source matters to the unit count. Under euro_first the premium is taken from
the euro support and the unit count is untouched — 745.036125 units at t = 11 on the
anchor cell against 744.044774 under uc_units — which is what makes the count a
deterministic function of the event schedule alone. Where the euro balance cannot cover
the premium the remainder cancels units, which is the branch a 100%-UC allocation runs
down.
Prélèvements sociaux, and the asymmetry that is statutory
Art. L. 136-7 II, 3°, a) levies the contribution on the euro component annually, as
interest is credited; II, 3°, c) levies it on the unit-linked component only at
dénouement. So the UC leg is taxed on a gain, at surrender, partial surrender or
death, and on a loss it is zero — at t = 11 on the anchor cell the UC leg is
17,284.34 € under water and the levy is nil. Accruing it annually on the UC leg is a
listed pitfall: it would understate the account value throughout and shrink the base the
management charge is levied on. The euro leg’s annual component belongs to Euro_FR_S,
where - on the same monthly grid as this model - it lands whole in the anniversary month
beside the interest it is struck on.
Whether the plancher top-up above the account value sits inside the levy base is stated in
no retrieved document; the model puts it outside.
Behaviour
Two dynamic overlays sit on the base surrender table, both [std] and both elected per
model point through lapse_dynamic(), which is none on the worked-example anchor:
perf_factor(),min(2, 1 + 2 max(0, g_ref - R_12m)), raising surrender after poor performance. It reads a completed trailing year, so it is 1 through the first twelve months whatever the path does, which is why the anchor cell’s decrements are the flat 2% a year the notes’ worked example states.plancher_factor(), halving surrender while the floor is in the money. A policyholder holding an in-the-money guarantee has a reason not to surrender that a UK bondholder does not, because surrendering forfeits it. It is the one behavioural assumption specific to this product, it is a pure invention with no evidence behind it, and it should be the first thing a user replaces — which is why it is elected rather than wired in.
There is no paid-up state: the contract is single premium, so there is no premium obligation to stop. The 30-day renonciation is carried inside the year-1 surrender rate rather than as a separate decrement.
Cells Descriptions#
- issue_age()[source]#
The issue age of the selected model point, age last birthday.
ALB is a [std] choice. The published plancher tariffs are quoted by the insured’s attained age at the calculation date [S4 Annexe I] and are read at
age(), so the tariff steps at each policy anniversary rather than on the insured’s birthday.
- sex()[source]#
The sex (M / F) of the selected model point.
It enters the mortality assumption only. The plancher tariff is published by attained age alone and is not sex-distinct, so the sex of the life changes the insurer’s expected strain and never the price it charges - which is one half of the reason the sign of the rider’s margin at any age is genuinely unknown.
P: the single premium, before the frais sur versement.
Versements libres and versements programmes exist on every retrieved contract and are excluded from the base projection [std]: a later premium adds to
cum_prem_net()and touc_cost_basis()on the same date and changes no recursion.
- prem_charge_rate()[source]#
e: the frais sur versement, 1.00% [std].
Observed levels run from nil to a 4.50% maximum across the retrieved contracts. A non-zero mid-range level is chosen deliberately, because a zero premium charge makes the net-premium and gross-premium floor bases indistinguishable and hides the question the plancher definition turns on.
- euro_alloc()[source]#
1 - alpha: the share of the net premium allocated to the fonds en euros.
Derived rather than a column, so the two shares cannot drift apart in the input file.
- unit_price_init()[source]#
p_init: the liquidation value of the composite UC support at issue, 100.00 [std].
A scaling convention, not a fact: the unit count and the liquidation value are reciprocal, and only their product enters the liability.
- mgmt_fee_rate_uc()[source]#
c: the annual UC frais de gestion sur encours, 0.88% p.a. [std].
Anchored on the market average - France Assureurs reports an encours-weighted 0.88% on UC supports, 0.82% under gestion libre and 1.17% under gestion sous mandat - and not on the retrieved sample, which is weighted towards broker and mutual contracts and is cheaper than the market. Contract rates retrieved span 0.475% to 1.50%, a factor of three on the dominant income line, and no statutory ceiling on any French life charge appears in the retrieved texts.
- euro_credit_rate()[source]#
i_e: the annual rate credited to the euro leg, net of its own charge, 2.50%.
A [std] pointer, not a model. Taux minimum garanti, participation aux benefices, the provision pour participation aux benefices and the effet cliquet are specified and implemented in
Euro_FR_S, and the euro leg therefore produces no margin line here at all.
- arbitrage_fee_rate()[source]#
phi: the frais d’arbitrage, 0.50% of the amount switched.
The PRO BTP level; observed rates run from nil to 2%, several contracts allowing a number of free arbitrages a year. Flat-fee minima - 30 EUR by post, 15 EUR online - are administrative and do not scale, so they are not modeled.
- plancher_flag()[source]#
Whether the garantie plancher rider is elected; True on the anchor cell.
It is elected at subscription only and cannot be restarted, so it is a model point attribute rather than a switch that can turn on mid-projection. The charge is a deduction from an existing account and not a new premium, which matters for contract boundaries.
- plancher_basis()[source]#
simple,indexeeorcliquet: what the floor is measured against.simpleandindexeeare both sourced - a running base of premiums, flat or indexed at 3.50% a year.cliquetis a [std] construction: no retrieved document offers a ratchet, and its existence in the French market is unverified. It is carried so the model holds the three-way column and so the proportional-versus- nominal surrender adjustment can be asserted.
- plancher_index_rate()[source]#
The annual indexation of an
indexeefloor, 3.50%.Generali’s option 2 sets it contractually at 3.50%; PRO BTP sets it annually at the insurer’s discretion, which makes it a class-(b) element the model holds as a snapshot.
- plancher_ratchet_months()[source]#
The ratchet period of a
cliquetfloor in months, 12 [std].A one-month ratchet is the same recursion observed twelve times as often, and on the anchor path it locks in the pre-fall high: 98,476.25 against 94,216.29 at
t = 11.
- plancher_gross_basis()[source]#
Whether the floor base is gross rather than net premiums; False on the anchor.
Both bases are sourced and they differ by exactly the premium charge. Net is chosen for the anchor because it makes the floor equal the account value at issue, so the net amount at risk at issue is zero - an assertable fact rather than an accident of the premium charge; on the gross basis the rider starts 1,000 EUR in the money on a 100,000 EUR premium.
- plancher_end_age()[source]#
The attained age at which the cover ceases, 75.
The majority value; 70 and 80 are both observed. The choice matters more than it looks: on the shipped Spirica tariff the rate at 74 is 408/17 = 24 times the rate at 30, so the last five years carry a large share of the lifetime charge - and moving the cessation age to 80 runs past the last published age, which is why
plancher_rate()raises rather than extrapolates.
- plancher_cap()[source]#
The cap on the capital sous risque, 300,000 EUR.
The cap is on the risk, and any excess reduces the floor; capping the death benefit instead is a different and much cruder contract. It never binds on the anchor cell and binds precisely in the deep drawdowns where the guarantee is worth something.
- plancher_levy_source()[source]#
euro_firstoruc_units: where the plancher premium is taken from.euro_firstis the sourced design - the euro support first, then the largest UC support by cancelling units - and it is what keeps the unit count independent of the rider. Where the euro balance cannot cover the premium the remainder cancels units whatever the election, which is the branch a 100%-UC allocation runs down.
- wd_pattern()[source]#
none,one_offorprogrammed: the partial-surrender pattern.one_offtakeswd_amountin monthwd_month;programmedtakeswd_rateof the account value a year, monthly. Both are [std]: no retrieved document gives a partial-surrender pattern.programmedis the pattern the eight-year tax design encourages, and it keeps the floor base falling in step with the account.
- arb_pattern()[source]#
none,one_offorprogressive: the arbitrage pattern.progressiveis the investissement progressif design - a fixed monthly amount out of the euro fund into UC. Trigger-based options (securisation des plus-values, limitation des moins-values) are specified inproduct-spec.mdand are not implemented: they matter because they systematically move value out of UC after a rise, shrinking the management-charge base and the plancher exposure at the same time.
- lapse_dynamic()[source]#
noneorfull: whether the two behavioural multipliers are applied.noneruns the base table alone and is what the notes’ worked example states - a flat 2.00% a year through the anchor cell’s twelve months.fullappliesperf_factor()andplancher_factor(). The election exists because both multipliers are [std] inventions with no evidence behind them, and a user replacing them should be able to see the base run underneath.
- uc_return_scenario()[source]#
The id of the UC return path in uc_scenario_table.csv this model point runs on.
- pols_if_init()[source]#
l(0): the in-force probability at issue; 1.0 on a single-policy model point.
- proj_len()[source]#
The number of projected months, from the model point.
The exclusive end of the 0-based frame:
result_cf()runst = 0 … proj_len() - 1and hasproj_len()rows. 12 on the worked-example anchor and 360 on the base run. The contract is written viagere and has no maturity date, so the horizon is a modelling choice rather than a contractual one and it is a per-policy column.
- duration(t)[source]#
Completed policy years at the start of month t:
t // 12.0-based, as everywhere in lifelib: 0 through the first policy year.
- duration_mth(t)[source]#
Months elapsed from issue at the start of month t; equal to t.
tis 0-based and counts from issue, so the identity is trivial - the cells exists so the monthly models in this library share one vocabulary.
- policy_year(t)[source]#
y: the 1-based contractual policy year containing month t; 1 for t = 0..11.
duration(t) + 1. A contractual label, derived and never indexed by - it is what the policy_year column of lapse_table.csv is keyed on.
- age(t)[source]#
a: the attained age (ALB) in the policy year containing month t.
issue_age + duration(t), so the tariff steps at each policy anniversary.
- prem_to_av_pp()[source]#
P(1 - e): the premium credited to the account value, net of frais sur versement.
The whole of it is allocated between the two legs, and on the net-premium floor basis it is also the floor at issue - which is why the net amount at risk at issue,
cum_prem_net_init() - prem_to_av_pp(), is zero exactly.
- units_init()[source]#
n_init = P(1-e) alpha / p_init: the unit count bought at issue.
Unit conversion is contractually to four decimal places, au dix millieme. The model carries full precision and reports to four, because rounding the count at every cancellation is an administration-system behaviour rather than a liability one.
- cum_prem_net_init()[source]#
S_init: the floor base at issue.
P(1 - e)on the sourced net-premium basis,Pwhereplancher_gross_basis()elects the gross variant.
- uc_cost_basis_init()[source]#
B_init = P(1-e) alpha: the prelevements sociaux cost basis of the UC leg at issue.
- mgmt_fee_rate_uc_mth()[source]#
c_m = c/12: the monthly UC management charge [std 1/12 convention].
c/12and not1 - (1 - c)^(1/12). The insurers compound the periodic rate: 0.25% a quarter gives an annual factor of(1 - 0.0025)^4 = 0.99003744, not1 - 1.00%, and Suravenir’s own published table prints100 x (1 - 0.60%) = 99.4000after a year where a monthly 1/12 levy gives 99.4016. The two conventions differ in the fourth decimal of the unit count, which is exactly the precision the contract guarantees.
- euro_credit_factor_mth()[source]#
(1 + i_e)^(1/12): the monthly accrual factor of the euro leg [std].
A smoothing of an annual credit across the months of the year, and it is a [std] simplification rather than a grid artefact:
Euro_FR_Sruns on this same monthly grid and does not smooth, crediting the whole of the year’s taux servi in the anniversary month with the effet cliquet. The euro leg here is a per-model-point rate and not a model, so the twelfth-of-a-year accrual is the cheapest reading that keeps the two legs on one clock; the real crediting machinery - the participation aux benefices, the PPB and its eight-year vintage ledger - isEuro_FR_S’s. Twelve of these factors compound to exactly1 + i_e, so a full policy year of the euro leg is unaffected by the smoothing and only a mid-year exit sees it.
- uc_return_mth(t)[source]#
r_uc(t): the UC support’s return in month t, from the elected scenario.
The scenario table holds segments: a monthly return applying from
from_monthtoto_monthinclusive, both written in the model’s own 0-based months, so the first segment of every shipped scenario opens atfrom_month = 0. A month outside every segment of the elected scenario raises, rather than falling back on a last-row default - a projection that has run off the end of its scenario is not projecting anything.
- unit_price_open(t)[source]#
p(t-1): the liquidation value at the start of month t.
The close of month
t - 1, andunit_price_init()in the first month - the opening timing that keeps the price recursion off a montht = -1that does not exist.
- unit_price(t)[source]#
p(t): the liquidation value of the composite UC support at the end of month t.
Exogenous. Art. A. 132-5 makes the unit count the thing guaranteed and the value the thing that is not, so the price path is an input and never a result. Fund-level recurring costs, 1.60% a year on the market average, are inside it and accrue to the fund manager: they reduce the account value and are not insurer income.
- units_open(t)[source]#
n(t-1): the unit count held at the start of month t.
The close of month
t - 1, andunits_init()in the first month. It is the count the management charge is taken on, which is the whole point of naming it.
- fee_units(t)[source]#
n(t-1) c_m: the units cancelled by the management charge in month t.
Taken on the opening unit count. In a month with an arbitrage the opening and closing counts differ by the arbitrage’s units, so charging on the closing count overstates the fee by 52.28 EUR against 59.54 EUR at
t = 2on the anchor cell - immaterial monthly, systematic over decades, and a common source of a persistent reconciliation break against an administration system.
- mgmt_fee_uc_pp(t)[source]#
The UC management charge collected in month t, per policy, in EUR.
fee_units(t) x p(t). The dominant income line on this product, and the one whose level is a market average rather than a contractual rate.
- arb_sched_pp(t)[source]#
The gross amount scheduled to be arbitraged out of the euro leg in month t.
Before the cap at what the euro balance can actually release; see
arb_amount_pp().
- arb_amount_pp(t)[source]#
A(t): the gross amount arbitraged from the euro leg into UC in month t.
Capped at the euro balance after the month’s accrual, so a progressive arbitrage stops of its own accord once the euro support is empty rather than driving it negative. An arbitrage is neither a premium nor a surrender: it changes both legs and leaves
cum_prem_net()untouched.
- arb_fee_pp(t)[source]#
A(t) phi: the frais d’arbitrage collected in month t, per policy.
Insurer income. The amount leaving the euro leg is
A; the amount reaching the UC leg isA(1 - phi); the difference is this fee.
- av_euro_open_pp(t)[source]#
V(t-1): the euro balance at the start of month t, before the accrual.
The close of month
t - 1, andav_euro_init_pp()in the first month.
- av_euro_aft_credit_pp(t)[source]#
The euro balance after the month’s accrual and before any event.
V(t-1) x (1 + i_e)^(1/12). On the anchor cell29,700.00 x 1.025^(2/12) = 29,822.48att = 1, which is the notes’ own euro-leg check.
- units_bef_wd(t)[source]#
The unit count after the management charge and the arbitrage, before a withdrawal.
n(t-1) - fee_units(t) + arb_units(t).
- av_uc_bef_wd_pp(t)[source]#
U(t) before the withdrawal: the UC account value the pro-rata split is taken on.
- av_euro_bef_wd_pp(t)[source]#
V(t) before the withdrawal: the euro balance after the arbitrage has left it.
- wd_sched_pp(t)[source]#
The partial surrender scheduled in month t, before the cap at the account value.
- wd_amount_pp(t)[source]#
W(t): the partial surrender settled in month t, per policy.
An owner election, not a claim, which is why it has its own name and its own
result_cfcolumn. Capped at the account value, so a programmed pattern draws the contract down to nothing rather than through it. It reduces the floor base by its nominal amount, and it is the only event other than a premium that does.
- wd_uc_pp(t)[source]#
W_uc(t): the UC component of the month’s partial surrender.
Split pro rata across the supports, which is the only default stated in a retrieved contract. At
t = 5on the anchor cell the UC share is 0.80665095, so 4,033.25 EUR of the 5,000 EUR comes off the units and 966.75 EUR off the euro balance. An election that emptied the loss-making support first would changeuc_cost_basis()and therefore the prelevements sociaux.
- cum_prem_net(t)[source]#
S(t): the floor base - premiums net of the premium charge, less surrenders.
It moves on a premium or a surrender and on nothing else. An arbitrage moves value between the legs, pays a fee and leaves the guarantee untouched; letting it move the floor is a listed pitfall, and
check_floor_base()asserts against it.The opening base of the first month is
cum_prem_net_init(), the floor base at issue.
- wd_cum_pp(t)[source]#
Cumulative partial surrenders to the end of month t, accumulated independently.
Exists so
check_floor_base()can rebuild the floor base from the withdrawal series rather than from its own recursion.
- plancher_ratchet(t)[source]#
R(t): the ratchet level of a
cliquetfloor.Reduced proportionally by a partial surrender, because a ratchet is a value level rather than a premium tally - the
simplebase is reduced nominally, and on the same path att = 11the two rules give 94,216.29 and 94,000.00. Raised to the account value at each ratchet date, observed just before the plancher premium is levied.A ratchet date falls at the end of month
t, which ist + 1months from issue, so then-month ratchet fires where(t + 1) % n == 0: the first annual ratchet is the end oft = 11, the twelfth month. The opening level of the first month is the account value at issue.
- plancher_amount(t)[source]#
F(t): the floor the death benefit is guaranteed not to fall below.
simpleS(t), the running base of net premiums less surrenders.indexeeF(t-1) (1 + i_x)^(1/12) - W(t), the opening floor of the first month beingcum_prem_net_init(), the floor at issue. Indexing the running floor and then deducting the nominal withdrawal is arithmetically identical to indexing the withdrawal forward from its own date and deducting it later, which is the sources’ rule that surrenders are indexed on the same basis as the floor [S1] [S3].cliquetmax(S(t), R(t)), so the ratchet can only ever improve the sourced floor.
- plancher_rate(t)[source]#
pi(a): the annual plancher tariff at the attained age, as a rate on the risk.
The published premium per 10,000 EUR of capital sous risque, divided by 10,000:
pi(65) = 0.0196. Zero once the cover has ceased - the rider is not elected, or the attained age has reachedplancher_end_age().An attained age inside the cover but outside the table raises. That is deliberate: the tariff stops at 74 because the cover stops at 75, and an implementation that extrapolated it to a later cessation age would silently invent a price the sources do not contain.
- plancher_rate_mth(t)[source]#
pi(a)/12: the monthly step of the plancher tariff [std].
The published formula is weekly,
Pr = K x (PA / 10 000) x 1/52observed each Friday;PA/12is the same annual cost applied once against a capital sous risque observed once instead of four or five times. What is lost is the intra-month path of the net amount at risk.
- nar(t)[source]#
K(t): the capital sous risque - the net amount at risk.
min(cap, max(0, F(t) - AV)), observed once a month on the account value after the fee, the arbitrage and the withdrawal and before the levy. Zero where the rider is not elected or the cover has ceased.The
max(0, .)is not decoration. Without it the rider pays a negative charge in every rising month and the death strain turns negative, which books the gain on the units as insurance profit. And the cap is on the risk: the excess reduces the floor rather than truncating the beneficiary’s benefit.
- plancher_charge_pp(t)[source]#
The plancher premium levied in month t, per policy.
K(t) x pi(a)/12- levied on the net amount at risk and never on the account value. On the anchor cell att = 11the correct charge is 27.18 EUR; on the account value it would be 126.35 EUR, a factor of 4.6. It is nil whenever the account value is at or above the floor, which is the whole of the rider being a put.Capped at the account value it is taken from, so a levy cannot drive the contract negative. The 15-20 EUR monthly thresholds below which the real levy is deferred to the following month have no expected-value consequence and are not modeled.
- plancher_levy_eur_pp(t)[source]#
The part of the month’s plancher premium taken from the euro support.
The whole of it under
euro_firstwhile the euro balance can cover it, and nothing underuc_units.
- plancher_levy_uc_pp(t)[source]#
The part of the month’s plancher premium taken by cancelling units.
Zero under
euro_firstwhile the euro support can pay, which is what keeps the unit count a deterministic function of the event schedule. Where the euro balance runs out the remainder falls on the units whatever the election - and the count then becomes path-dependent, which is the mechanism by which a lower euro credited rate reaches the UC leg.
- units(t)[source]#
n(t): the unit count held at the end of month t.
n(t-1) - fee_units + arb_units - wd_units - plancher_levy_units, the opening count beingunits_open(). This is the quantity art. A. 132-5 makes the insurer’s commitment: every charge is a cancellation of units and never a deduction of euros, so with the plancher premium taken from the euro support the count is market-independent - with no events at all it collapses tounits_init() (1 - c_m)^(t + 1), which is the sequence the insurers publish in their own statutory tables.
- av_euro_pp(t)[source]#
V(t): the euro account value per policy at the end of month t, after the levy.
The notes’ worked-example table prints this column post-levy, so each row’s value is the next row’s opening euro balance -
av_euro_open_pp()reads it back.
- av_pp_at(t, timing)[source]#
The total account value per policy at a point inside policy month t.
"OPENING"the balance the month opens on, before the liquidation value has moved and before the euro leg accrues:
av_pp(t - 1), andprem_to_av_pp()- the premium net of the frais sur versement - in the first month,t = 0. It is the term the roll-forward check starts from."BEF_FEE"after the liquidation value has moved and the euro leg has accrued, before the management charge.
"BEF_WD"after the management charge and the arbitrage; this is the base the partial surrender is split pro rata on.
"BEF_LEVY"after the withdrawal; this is the base the `capital sous risque` is observed against.
"BEF_DECR"after the plancher premium: the end-of-month account value, and the column the notes’ worked-example table prints. It is the surrender benefit per lapse and, with
K(t)added, the death benefit.
All five points are exposed so the ordering is inspectable rather than buried in one expression.
- av_pp(t)[source]#
The total account value per policy at the end of month t.
av_pp_at(t, "BEF_DECR")under the house account-value name, and the columnresult_cf()publishes. Note that on this product it is an end-of-month value, matching the notes’ row t, and not the start-of-month reading some other models in the library give the same name.
- av_at(t, timing)[source]#
The in-force account value:
av_pp_at(t, timing) x pols_if_at(t, "AFT_DECR").The account value is a stock, so it is weighted by the count the balance is still carried for once the month’s decrements have gone — the notes’
l(t + 1)— and not by the start-of-month exposurepols_if()that weights the month’s flows. Att = 11on the anchor cell77,330.08 x 0.968240 = 74,874.07.
- av_uc_at(t)[source]#
The in-force UC account value.
The unit count at the liquidation value, weighted - arithmetic, and reproduced here without a valuation assumption of any kind. On the conventional reading this is the French statutory provision mathematique for the UC engagement, and MACSF’s notice is the one retrieved document that writes a provision mathematique recursion in units [S10 ART 11-12]. Art. R. 343-3 itself enumerates the eleven provisions and defines the provision mathematique generically; it says nothing about unites de compte, so the placement is [unverified] against any retrieved statutory or ACPR text. The plancher liability is a separate engagement and no retrieved document states how it is provisioned, so this library asserts nothing about it.
- av_euro_at(t)[source]#
The in-force euro account value.
Published for completeness; the euro engagement’s own reserve and its provision pour participation aux benefices belong to
Euro_FR_S.
- uc_cost_basis_bef_wd(t)[source]#
B(t) before the month’s withdrawal, after any investment into the UC leg.
B(t-1) + A(t)(1 - phi), the opening basis of the first month beinguc_cost_basis_init(). An arbitrage into UC is an investment and adds to the cost basis; the arbitrage fee does not, because it never reaches the support.
- uc_cost_basis(t)[source]#
B(t): the cumulative cost of the UC leg, the prelevements sociaux base.
Investments add to it; an outflow removes its pro-rata share. At
t = 5on the anchor cell it falls from 79,250.00 to 75,420.62 on a 4,033.25 EUR UC surrender.
- wd_uc_gain_pp(t)[source]#
The taxable UC gain component of the month’s partial surrender, per policy.
W_uc (1 - B / U): the amount surrendered less its pro-rata cost. Att = 5on the anchor cell4,033.25 x (1 - 79,250.00/83,469.22) = 203.87. [std]: no retrieved document sets out the arithmetic for a multisupport partial surrender.
- social_levy_wd_pp(t)[source]#
The prelevements sociaux withheld on the month’s partial surrender, per policy.
17.2% x max(0, gain)- 35.07 EUR att = 5on the anchor cell. Withheld and remitted: a pass-through, not insurer income and not an expense.
- social_levy_decr_pp(t)[source]#
The prelevements sociaux withheld per exiting policy at the month’s decrements.
The UC leg is taxed only at `denouement` - surrender, term or death - so the levy is contingent on a gain and is zero on a loss. At
t = 11on the anchor cell the UC leg is 17,284.34 EUR under water and the levy is nil, and any excess already levied year by year on the euro leg is restituted at final liquidation under art. L. 136-7 III bis. The plancher top-up above the account value is treated as outside the levy base; no retrieved document states whether it is, and the alternative reading changes the beneficiary’s net proceeds and not the insurer’s cash flow.
- 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, which is anchored so the male rate at age 65 reproduces the notes’ placeholder 1.20% a year exactly. It is an assumption and the plancher tariff is a price; their difference is the rider’s margin, and because no insurer publishes the mortality, the loading or the margin behind a tariff, the sign of that margin at any age is genuinely unknown. Sensitivity test the two independently, never as a single “plancher basis”.
- mort_rate_mth(t)[source]#
q_m = 1 - (1 - q_a)^(1/12): the monthly mortality rate [std].
Derived geometrically and not by dividing by twelve, which is what makes
[(1 - q_m)(1 - w_m)]^12 = (1 - q_a)(1 - w_a)hold exactly - the notes’ decrement check, and the only sensible test of the conversion.
- lapse_rate_base(t)[source]#
w_base(y): the table annual total-surrender rate in month t [std].
2 / 4 / 6 / 12 / 6 percent by band, the 12% falling in policy year 8. Policy years beyond the table take its last row.
- return_12m(t)[source]#
R_12m: the trailing twelve-month UC return driving the performance multiplier.
Measured over the twelve completed months ending at the start of month
t, so it needs twelve months of history and is undefined beforet = 12. Where it is undefined it returns the reference return, which makes the multiplier exactly 1 - and that is why the anchor cell’s twelve months carry the flat 2.00% a year the notes’ worked example states even though its path falls 21.97% over the year.
- perf_factor(t)[source]#
M_perf(t) = min(2, 1 + 2 max(0, g_ref - R_12m)): the performance multiplier [std].
Poor recent performance raises surrender. It is 1 on any deterministic run at the reference return, and 1 through the first twelve months of every run.
- plancher_factor(t)[source]#
M_pl(t): the plancher moneyness multiplier, 0.5 while the floor is in the money.
A policyholder holding an in-the-money guarantee has a reason not to surrender that a UK bondholder does not: surrendering forfeits it. This is the one behavioural assumption specific to this product, it is a pure [std] invention with no evidence behind it, and it should be the first thing a user replaces - which is why
lapse_dynamic()elects it rather than the model wiring it in. 1.0 wherever the rider is not elected, the cover has ceased or the units are above the floor.
- lapse_rate(t)[source]#
w_ann: the annual total-surrender rate applied in month t.
The base table alone where
lapse_dynamic()isnone, andmin(cap, w_base x M_perf x M_pl)where it isfull. A surrender costs the insurer nothing at the point of exit - the surrender value is the account value across all supports, with no exit charge - while truncating the entire future charge stream and extinguishing an in-the-money guarantee, which is why persistency dominates this product’s value and why the two effects pull in opposite directions.
- pols_if(t)[source]#
l(t): the in-force probability at the start of policy month t.
pols_if(0) = pols_if_init(), and thereafterpols_if(t) = pols_if(t-1)(1 - q_m(t-1))(1 - w_m(t-1)), deaths before surrenders [std]. It is the weight on every flow of month t and the first column ofresult_cf(), so the two agree row by row: a cash flow divided by its own row’spols_ifis the per-policy amount.The notes’
l(t + 1)— the count once month t’s decrements have gone — ispols_if_at()(t, "AFT_DECR").
- pols_if_at(t, timing)[source]#
The in-force probability at a point inside policy month t.
"BEF_DECR"the start of the month, before any decrement;
pols_if()(t)."BEF_LAPSE"after the month’s deaths and before its surrenders — the order is deaths before surrenders [std].
"AFT_DECR"the notes’
l(t + 1): the count still in force once the month’s decrements have gone. Equal topols_if(t + 1)wherever the projection runs on, and computed here directly so it also resolves in the horizon month.
The three timings are the
CashValue_SEforms the library’s shared vocabulary prescribes. They exist so that the end-of-month count has a name of its own rather than borrowing the start-of-month one, which is exactly the confusion this model shipped with before.
- claims(t, kind=None)[source]#
Gross benefit outgo in policy month t, by kind; the total when kind is omitted.
"DEATH"AV + Kper death - the account value plus the capital sous risque, which ismax(F, AV)written the way the model uses it. Att = 11on the anchor cell that is77,330.08 + 16,642.74 = 93,972.82, of which only the 16,642.74 is the insurer’s."LAPSE"AVper surrender: the account value across all supports, with no exit charge.
These are gross flows. They are published so the reader can see the whole picture, but they are not in
net_cf(), becauseav_releases()cancels them against the account value - all except the death strain. See the Space docstring.
- withdrawals(t)[source]#
Partial-surrender outgo in month t, funded from both legs pro rata.
An owner election rather than a claim, which is why it has its own name and its own
result_cfcolumn.
- av_releases(t)[source]#
The account value released to fund the month’s death and surrender benefits.
AV x (deaths + surrenders). The gross benefit outgo less this is exactly the plancher death strain, which is the only part the insurer funds from its own resources.
- prem_charge(t)[source]#
The frais sur versement collected at issue; 1,000.00 EUR on the anchor cell.
Booked in the first projected month,
t = 0, which is where the notes book it too: the issue instant is not a row of its own, so the premium charge and the acquisition expense both fall in montht = 0, weighted atpols_if(0) = 1.
- mgmt_fee_uc(t)[source]#
The UC management charge collected in month t; insurer income.
pols_if(t) x mgmt_fee_uc_pp(t), the notes’l(t)start-of-month weight. The notes’ worked-example table prints the per-policy column, which sums to 630.20 EUR over year 1; this weighted line sums to 621.33 EUR.
- plancher_charge(t)[source]#
The plancher premium collected in month t; insurer income.
Zero in every month the account value is at or above the floor, which is seven of the anchor cell’s twelve.
- plancher_strain(t)[source]#
The non-unit cost of the month’s deaths: the capital sous risque, exactly.
pols_if(t) q_m(t) K(t), the notes’l(t) q_m K(t). The whole of the account value is funded by cancelling units and by the euro balance, so the insurer’s cost per death isK(t)and nothing else.
- social_levy_uc(t)[source]#
The prelevements sociaux withheld on the UC leg in month t.
Partial surrenders plus the exits at the month’s decrements. A pass-through: withheld from the policyholder and remitted, so it is neither insurer income nor an expense and it is not in
net_cf(). It is published as its own column so that its exclusion is visible rather than merely asserted.
- expenses(t)[source]#
Acquisition and maintenance expense in month t [std].
400 EUR per policy of acquisition expense in the first projected month,
t = 0, which is the issue month - then 40 EUR per policy a year, level, taken monthly at the start-of-month exposure. No retrieved document gives an expense basis of any kind.
- net_cf(t)[source]#
The non-unit cash flow of month t, income positive.
premium charge + management charge + arbitrage fee + plancher premium - expenses - plancher death strain. Not a gross liability total, and that is a product fact rather than a departure: every benefit is funded by cancelling units and by drawing the euro balance, so a gross presentation would add the same money to both sides.Three amounts that move on this contract are deliberately not here. The prelevements sociaux are withheld and remitted. The fund-level recurring costs sit inside the liquidation value and accrue to the fund manager - adding the 1.60% to year 1 of the anchor cell would put 1,136.76 EUR against a true
net_cfof 1,262.66 EUR, both weighted at the same start-of-month exposure. (The unweighted per-policy sum is 1,152.86 EUR and is not comparable with a weightednet_cf.) And the euro leg’s credited interest is a policyholder credit whose margin isEuro_FR_S’s output: reading this stream as the contract’s total margin is a listed pitfall.
- uc_growth_pp(t)[source]#
The month’s UC investment return, per policy:
n(t-1) x (p(t) - p(t-1)).Computed from the opening unit count and the price move alone, independently of every charge, so that
check_av_roll_fwd()is an identity the recursion has to satisfy rather than a restatement of it.
- euro_interest_pp(t)[source]#
The month’s euro credited interest, per policy:
V(t-1) x ((1+i_e)^(1/12) - 1).A policyholder credit, not an insurer cash flow:
i_eis already net of the euro management charge, so the euro leg produces no margin line in this model.
- check_av_roll_fwd_resid(t)[source]#
The account value roll-forward residual in month t; zero everywhere.
AV(t) - [AV opening + UC return + euro interest - management charge - arbitrage fee - withdrawal - plancher premium], per policy, the opening balance beingav_pp_at(t, "OPENING")- the premium net of the frais sur versement in the first month. The growth terms are built from the opening unit count and the opening euro balance, so this is a genuine identity and not a restatement: charging the management fee on the closing unit count, applying last month’s price to it, forgetting that the arbitrage fee leaves the contract, or netting the withdrawal twice all show up here.
- check_av_roll_fwd()[source]#
True when the account value 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_unit_roll_fwd_resid(t)[source]#
The unit count roll-forward residual in month t; zero everywhere.
n(t) - [n(t-1)(1 - c_m) + arbitrage units - withdrawal units - levy units]. The fee term is written as a factor on the opening count rather than reusingfee_units(), so a fee taken on the closing count breaks it; and the levy term is what makes the check say something about the rider - undereuro_firstit is zero and the count is market-independent, and where the euro support cannot pay it is not.
- check_pols_roll_fwd_resid(t)[source]#
The in-force roll-forward residual in month t; zero everywhere.
pols_if(t) - pols_if_at(t, "AFT_DECR") - deaths - surrenders, the notes’l(t) - l(t+1) - deaths - surrenders. There is no maturity term: the contract is written viagere, so the projection simply stops atproj_len()with a positive in-force count rather than running the population out.
- check_floor_base_resid(t)[source]#
The floor-base residual in month t; zero everywhere.
S(t) - [S_init - cumulative withdrawals].cum_prem_net()reaches month t by its own recursion andwd_cum_pp()accumulates the withdrawals by another, so a model that let an arbitrage move the floor - the listed pitfall - would show it here as a residual of the arbitraged amount. On the anchor cell the 10,000 EUR switch att = 2leaves the floor at 99,000.00.
- check_nar_bounds_resid(t)[source]#
How far the capital sous risque falls outside
[0, cap]; zero everywhere.Signed: negative where
K(t)has gone below zero, positive where it has run past the cap. A net amount at risk that goes negative turns the rider into a rebate and books the gain on the units as insurance profit, and one that runs past the cap prices a risk the contract does not carry.
- check_benefit_funding_resid(t)[source]#
How much of the month’s benefit outgo the account value does not fund.
claims(t) - av_releases(t) - plancher_strain(t), which is zero by construction: the death benefit is written asAV + Kand the surrender benefit asAV, so the identity holds by the wayclaims()is spelled. It is published anyway, because the construction it asserts is the one thing about this product that is easy to get wrong in a different place - a model that paid the death benefit asmax(F, AV)and also released the account value, or that took the strain as the whole benefit rather than asK, would be inconsistent with these three cells and the residual would move.
- check_benefit_funding()[source]#
True when every benefit is funded by the account value plus the death strain.
- result_cf()[source]#
Result table of cashflows, indexed by policy month t = 0, 1, …, proj_len() - 1.
pols_ifis the in-force probability at the start of the month, which is the weight carried by every flow on its own row - divide a flow by it and the per-policy amount comes back.av_ppbeside it is the end-of-month account value per policy; weighted, that balance isav_at(), which usespols_if_at(t, "AFT_DECR")instead.net_cfis the non-unit stream - what accrues to the insurer on the UC leg and the rider - whileclaims_death,claims_lapse,withdrawalsandav_releasesshow the gross picture they net against.social_levy_ucis published precisely because it is not innet_cf: it is withheld from the policyholder and remitted.
- result_av()[source]#
Result table of the account value recursion, indexed by t = 0, 1, …, proj_len() - 1.
The notes’ worked-example table, column for column: the liquidation value, the unit count, the two legs, the end-of-month account value, the floor, the capital sous risque, and the two per-policy charges.