The Projection Space#
The by-policy projection of the Basis_DE_S model.
The Space is parameterized by point_id, so Projection[1] is an ItemSpace
projecting model point 1:
>>> Projection[1].result_cf() # the worked example's anchor cell
>>> Projection.point_id = 5 # or switch the default
Two clocks, and the argument of a cells says which
The grid is monthly and the contract is almost entirely annual, so the model runs on two clocks and a cells’ argument names the one it is on.
t counts projection months from the valuation date, 0-based: t = 0 is the
first projected month and t = proj_len() - 1 the last, with
proj_len() = 12 x proj_len_y() and proj_len_y() = omega_age() - age(0) + 1. It is
the argument of the in force, the decrements, the claims, the expenses, the commission and
the Rente instalments — everything that happens on a date.
k = proj_year(t) = t // 12 counts projection years and is the argument of
everything the contract states per Versicherungsjahr: the Beitrag, the Zuzahlung, the
four account charges, the declared rate, the Deckungskapital, the Rentenfaktor
conversion and the Überschussrente. k is the annual-step model’s own t, and
result_cf_annual().loc[k] is that model’s row k.
Policy duration at the start of projection year k is
duration_y(k) = duration_init + k completed policy years, so a new-business point
opens at duration_y(0) = 0; an in-force point opens at whatever duration it has already
run and the frame still starts at ``t = 0``. duration(t) is the same quantity read
from a month, duration_mth(t) // 12 with duration_mth(t) = 12 duration_init + t,
and policy_year(t) = duration(t) + 1 is the contractual 1-based label the AVB and the
behaviour table use. The frame opens on a policy anniversary, so a projection year is a
Versicherungsjahr and is_anniv(t) — t % 12 == 11 — is its last month.
The decrements carry the library’s two speeds: mort_rate(t) is the annual rate of
the year the month falls in and mort_rate_mth(t) = 1 - (1 - mort_rate(t))^(1/12) is
what the recursion applies, so twelve months compound back to the annual rate exactly and
pols_if(12k) is the annual-step model’s pols_if(k) to the last bit. The whole
Aufschubphase is therefore unchanged: the premium, the Zuzahlung, the commission, the
four charges, both account blocks, the fund at Rentenbeginn and the annuity struck on it
are bit-identical on all thirteen model points.
What the finer grid buys on this product is the *Rente* itself. A Rentenfaktor is
quoted in euro a month and a Leibrente is paid monthly; the annual-step model booked
twelve instalments together at the start of each payout year on the opening in-force
count, named that as a pitfall and as a stated approximation, and was generous to the year
of death by up to a full year’s annuity — 4 290,52 € of the anchor cell’s payout phase,
1,6 % of it. Here the instalment is paid to whoever is alive at the start of each month.
The Rentengarantiezeit becomes 12m guaranteed instalments beginning in the month
after the death that triggered them rather than in the following year, which moves
116,29 € onto model point 4 and 562,91 € onto model point 12; and a policy that dies in
the third month of a year now bears three twelfths of that year’s maintenance expense
rather than the whole of it.
The annuity is lifelong, so the projection runs to the end of the mortality table; in the
terminal year mort_rate is 1, the certainty falls in that year’s last month, the
last survivor dies at t = proj_len() - 1 and pols_if(proj_len()) is zero.
Input data
Inputs are external files: plain CSVs living in the model folder’s parent directory,
products/basisrente/, 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. This follows annuallife.TradLife_A; contrast basiclife.BasicTerm_S, which
keeps its inputs inside the model.
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 |
surplus_file |
data.surplus_table() |
surplus_table.csv |
rentenfaktor_file |
data.rentenfaktor_table() |
rentenfaktor_table.csv |
charge_file |
data.charge_table() |
charge_table.csv |
behaviour_file |
data.behaviour_table() |
behaviour_table.csv |
option_file |
data.option_table() |
option_table.csv |
Naming
Cells names follow lifelib’s basiclife.BasicTerm_S and savings.CashValue_SE
wherever those models have an analogue — pols_* for policy counts, plural nouns for
cash flows, *_rate for rates, *_pp for per-policy amounts, claims(t, kind)
with an uppercase kind string, av_pp_at(t, timing) and pols_if_at(t, timing)
for the within-year reads. The technical notes use compact actuarial symbols instead.
The mapping is:
Notes symbol |
Cells |
Meaning |
|---|---|---|
(none) |
model_point() |
The selected model point row |
n = omega - x(0) + 1 |
proj_len_y() |
Number of projected years |
12 n |
proj_len() |
Number of projected months |
k = t // 12 |
proj_year(t) |
Projection year of month t |
(none) |
is_anniv(t) |
Last month of a projection year |
x(k) |
age_y(k) |
Attained age in year k |
x(t) |
age(t) |
The same, read from a month |
d(k) |
duration_y(k) |
Completed policy years in year k |
d(t) |
duration(t) |
The same, read from a month |
(none) |
duration_mth(t) |
Completed policy months |
(none) |
policy_year(t) |
Contractual 1-based label |
y(k) |
cal_year_y(k) |
Calendar year in year k |
y(t) |
cal_year(t) |
The same, read from a month |
omega |
omega_age() |
Terminal age of the table |
T |
ret_y() |
Projection year of Rentenbeginn |
12 T |
ret_t() |
Month of the first instalment |
(none) |
gtd_end_t() |
Last month of the Rentengarantiezeit |
S |
beitragssumme_pp() |
Beitragssumme at inception |
P0 (1 + delta)^d(k) |
prem_base_pp(k) |
Contractual Beitrag before phi |
phi |
prem_freq_load() |
Ratenzahlungszuschlag |
P(k) |
prem_pp(k) |
Beitrag charged per paying policy |
(none) |
prem_due(t) |
True in the month it falls due |
Z(k) |
zuz_pp(k) |
Zuzahlung per paying policy |
(take-up) |
zuz_take_up(k) |
Zuzahlung utilisation rate |
(none) |
prem_total_pp(k) |
Total contribution incl. BUZ |
zill_rate x S |
alpha_total_pp() |
Zillmerised acquisition charge |
alpha(k) |
alpha_amort_pp(k) |
Its annual instalment |
alpha_z(k) |
alpha_zuz_pp(k) |
Acquisition charge on a Zuzahlung |
u(k) |
unit_cost_pp(k) |
Stueckkosten, inflating |
N(k) |
prem_to_av_pp(k) |
Premium credited after charges |
premiums(t) |
premiums(t) |
Laufende Beitraege, fund level |
zuzahlungen(t) |
zuzahlungen(t) |
Zuzahlungen, fund level |
A^p(k, .) |
av_pp_at(k, timing) |
Deckungskapital per paying policy |
A^p(k) |
av_pp(k) |
Its start-of-year value |
A^f(k, .) |
av_pu_at(k, timing) |
Premium-free block, fund level |
A(k, .) |
av_at(k, timing) |
Whole Deckungskapital, fund level |
A(k) |
av(k) |
Its start-of-year value |
(declared) |
decl_rate(k) |
Declared laufende Verzinsung |
i(k) |
cred_rate(k) |
max(gtd_rate, decl_rate(k)) |
q^t(x, y) |
mort_rate_at_age(x, y) |
First-order table rate |
(table) |
mort_rate_base(t) |
Table rate of the year of month t |
q(t) |
mort_rate(t) |
Best-estimate annual death rate |
q_mth(t) |
mort_rate_mth(t) |
Its geometric twelfth |
f(t) |
bf_rate(t) |
Beitragsfreistellung rate, annual |
l(t) |
pols_if(t) |
In force at the start of month t |
l^p(t) |
pols_paying(t) |
Premium-paying subset |
l^f(t) |
pols_paidup(t) |
Premium-free subset |
l(t)(1-q_mth) |
pols_if_at(t, timing) |
BEF_DECR / AFT_DEATH / AFT_FREEZE |
(none) |
pols_death(t) |
Expected deaths in month t |
(none) |
pols_death_paying(t) |
of which premium-paying |
(none) |
pols_death_paidup(t) |
of which premium-free |
(none) |
pols_freeze(t) |
Beitragsfreistellung transfers |
g(t) |
pols_gtd(t) |
Rentengarantiezeit continuations |
(current) |
rentenfaktor_curr() |
Aktueller Rentenfaktor at ret_age |
(options) |
rf_option_factor() |
Option reduction on the factor |
R |
rentenfaktor_applied() |
The factor actually applied |
F |
fund_at_conv() |
Fund converted at Rentenbeginn |
b(k) |
ann_bonus_rate(k) |
Ueberschussrente uplift |
a(k) |
ann_pp(k) |
Annual annuity per annuitant |
a(k) / 12 |
ann_mth_pp(t) |
The monthly Rente instalment |
(none) |
db_pp(k) |
Reserve released per paying death |
(none) |
db_pu_pp(k) |
Reserve released per paid-up death |
claims_death, _annuity, |
claims(t, kind) |
Benefit outgo by kind |
claims_survivor |
||
E(t) |
expenses(t) |
Insurer expense outgo |
C(t) |
commissions(t) |
Commission outgo |
net_cf(t) |
net_cf(t) |
Net cash flow, income positive |
liability_cf(t) |
liability_cf(t) |
The same stream, outgo positive |
Four names needed care.
av_pp_at is per premium-paying policy and av_pu_at is the premium-free block
at fund level. They are not two spellings of one quantity and they must not be
averaged into a single per-policy figure: a policy that froze at duration 5 and one that
froze at duration 15 hold different reserves, only the aggregate of the second kind is
meaningful, and collapsing the two is the third listed modeling pitfall.
av_at() is the fund-level total,
av_pp_at(k, .) x pols_paying(12 k) + av_pu_at(k, .), and it is the only one of the
three that rolls forward on mortality alone. All three take a projection year: one
premium, one set of charges and one interest credit fall per Versicherungsjahr, so the
account has nothing to say about a month.
bf_rate is the Beitragsfreistellung rate and is emphatically not lapse_rate.
There is no lapse decrement on this product and no cells of that name anywhere in the
model. A freeze is a transfer between two ledgers, not an exit: it appears in
pols_paying() and pols_paidup() and not in pols_if(), which
decrements on mortality alone.
prem_total_pp reconstructs the contribution the policyholder actually pays,
(P(t) + Z(t)) / (1 - buz_prem_share), and is a reporting cells that enters no cash
flow. The BUZ premium buys a cover this model does not project — the disability
mechanics belong to BU_DE_S — so it appears in no result_cf() column and in
net_cf() at no t. Modelling it as premium income with no benefit is the
seventeenth pitfall.
claims(t, "DEATH") is not a lump sum to a beneficiary. § 10 EStG requires everything
paid to a survivor to be paid as an annuity, so what is booked at the moment of death
is the Deckungskapital leaving this contract as the single premium of a survivor’s
annuity — itself a new liability, an immediate annuity, that this model does not
project. Reading it as a payable capital sum misreads the product, which is the tenth
pitfall.
The product is defined by prohibitions
The entitlement under a Basisrentenvertrag is nicht vererblich, nicht übertragbar,
nicht beleihbar, nicht veräußerbar and nicht kapitalisierbar. Arithmetically that
means there is no surrender value at any duration, no Kapitalwahlrecht, no
Teilkapitalauszahlung and no lump sum of any kind at any date. The one commutation
Schicht 1 does allow — the Kleinbetragsrenten-Abfindung of § 10 Abs. 1 Nr. 2 Satz 3
EStG — is left out of this model by choice and not by prohibition; model.md gives the
reasons. § 169 VVG — the Rückkaufswert, the Mindestrückkaufswert, the Stornoabzug —
is inoperative on this contract.
So this model has no ``lapse_rate``, no ``surr_rate``, no ``cv_pp``, no ``loan_pp``,
no ``withdrawals`` and no ``claims_lapse`` column. Those are structural absences, not
switched-off options, and check_no_capital() asserts the consequence at every
t: the only payments the model can make are an annuity instalment, a
Rentengarantiezeit continuation and a survivor’s single premium.
The mirror error is subtler than importing a surrender column, and it is worth naming:
computing a Rückkaufswert internally “for reference” and then flooring the
Deckungskapital at it. prem_to_av_pp() is negative in the first years of a
heavily zillmerised contract and the account is not floored, because there is no
Rückkaufswert for a floor to protect. That is why a German Deckungskapital starts
near zero.
Two ledgers, one model point
A Beitragsfreistellung under § 165 VVG survives intact on this contract and is its only behavioural exit. It stops the premium and moves the policy to the premium-free cohort, where its Deckungskapital is still credited, still pays the Stückkosten and the reserve charge, stops paying the premium charge and the Zillmerung instalment, and still converts at Rentenbeginn. It does not end the contract, release any value, change the Rentenbeginn or release any of the § 10 constraints.
The model therefore carries pols_paying() and pols_paidup() and their two
account blocks, and
pols_if(t + 1) = pols_if(t) x (1 - mort_rate_mth(t))
with bf_rate absent from the identity. check_pols_roll_fwd() asserts both that
the two ledgers sum to pols_if() and that pols_if() decrements on mortality
alone. A model point opens either entirely premium-paying or entirely
premium-free (paidup_at_init); a part-paid-up book is two model points, because
averaging the two cohorts’ reserves is the third pitfall.
No Wiederinkraftsetzung is modelled: the premium-free block is absorbing, which is conservative on premium income and is a standardization rather than a contract fact.
The declared rate is the total credited rate
cred_rate(k) = max(gtd_rate, decl_rate(k)), a maximum and not a sum. A German
laufende Verzinsung is quoted as the total rate credited to the Deckungskapital,
already including the contract’s Rechnungszins; adding the declared rate on top of the
guarantee is the sixth pitfall. The guarantee is a cohort fact fixed at conclusion and
carried on the model point as gtd_rate, so a book spanning the 2,75 % vintage of 2006
and the 1,00 % vintage of 2025 has both branches of the max live at once — model
point 8 credits its guarantee in every year while the anchor credits the declared path in
every year.
The reserve charge γ is netted inside the same step, (1 + cred_rate(k) - gamma_av),
and the Stückkosten are taken before it. γ, β, the Stückkosten and the Zillmerung
instalment are deductions from the policyholder’s account — insurer income — and the
insurer’s own outgo is the acquisition expense, the commission, the maintenance expense
and the annuity administration. Booking a charge as both is the fourth pitfall, and it is
why expenses() is invariant to beta_prem, gamma_av and zill_rate: those
three move net_cf() only through the smaller annuity the smaller fund buys at
Rentenbeginn.
The conversion at Rentenbeginn
At the start of projection year T = ret_y() — equivalently at the end of month
ret_t() - 1 — the whole fund carried out of year T - 1, grossed up by the
Schlussüberschussanteil, converts:
fund_at_conv() = av_at(T, "BEF_PREM") x (1 + terminal_bonus_rate)
ann_pp(T) = fund_at_conv() / pols_if(ret_t()) / rf_unit
x rentenfaktor_applied() x ann_freq
ann_mth_pp(t) = ann_pp(proj_year(t)) / ann_freq
There is no lump sum, no election and no notice period — this is the one date in the
contract’s life at which anything happens, and nothing happens at it that the
policyholder chooses. av(k) is zero for every k > T; at k = T itself the
published av is the pre-conversion fund, which is the number the annuity is struck on
and the one a reader of the worked example needs. check_conversion() inverts the
identity and is zero at every other k; check_av_roll_fwd() asserts that the
account is emptied at T and stays empty.
ann_pp is an annual amount and is nobody’s payment: the Rentenfaktor is quoted in
euro a month, ann_freq = 12 is the conversion from one to the other, and
ann_mth_pp() is the instalment that is actually paid — monthly in advance, from
t = ret_t(), to whoever is alive at the start of the month. The annual figure survives
as the cells the conversion and the Überschussrente are stated on, because both are
annual terms: the factor is applied once and the uplift compounds once a year, so the
twelve instalments of a payout year are equal and the thirteenth is (1 + b) times the
twelfth.
rentenfaktor_applied() = max(rentenfaktor_gtd, rentenfaktor_curr()) x
rf_option_factor(). The max is the contract’s own rule, and it means the projection
is sensitive to whichever of the two factors is higher and completely insensitive to the
other: the anchor converts at the current 31,50 € and model point 13 at its guaranteed
34,00 €. Taking the guaranteed factor when the current one is higher, or the reverse, is
the thirteenth pitfall.
The conversion basis is not the projection basis, and that is deliberate.
rentenfaktor_gtd was struck at inception on first-order DAV 2004 R with a prudential
margin and a conservative interest basis; the projection runs on the best estimate,
mort_rate(t) = mort_be_factor x mort_rate_base(t). The wedge between the two is the
payout phase’s Risikoüberschuss, and ann_bonus_rate — a teildynamische Rente —
is the mechanism that gives it back to the annuitant. ann_pp() at ret_t() is
therefore invariant to mort_be_factor while claims_annuity is not. A model that
converted on its own best-estimate mortality would abolish the wedge and with it the
whole German payout-phase surplus mechanic, which is the eleventh pitfall.
Death before Rentenbeginn pays nothing in the base design
With the survivor rider off — surv_annuity_rate = 0, which is the base design and the
anchor’s setting — a death in the Aufschubphase pays nothing: the reserve is
released as a mortality profit, because the entitlement is nicht vererblich. With the
rider on, the released reserve is payable only where an eligible survivor exists, and
claims() weights it by elig_surv_prob.
Either way the reserve leaves the fund, which is why
av_at(k + 1, "BEF_PREM") = av_at(k, "AFT_INT") x (1 - mort_rate(12 k))
holds whether or not the rider is on — at the year’s annual rate, which is exactly what
its twelve monthly rates compound to. That single identity is the arithmetic content of
nicht vererblich, and check_av_roll_fwd() asserts it. The survivor’s annuity
fraction is paid for through rf_option_factor() — a reduction in the
Rentenfaktor — rather than by scaling the death benefit, which is how a German tariff
prices the cover.
The Rentengarantiezeit is a stream, never a lump sum
A Rentengarantiezeit runs guarantee_period_y years from *Rentenbeginn*, not from
each death, so every continuation ends on the same date and pols_gtd() is a
one-line recursion ending at gtd_end_t(). On this grid the window is what the
contract says it is — 12m guaranteed monthly instalments — and a continuation
begins in the month after the death that triggered it rather than in the following year. The instalments continue only to a
permitted survivor, so each death contributes elig_surv_prob of a continuation and
where no eligible survivor exists the payments simply cease. They are never
commutable: no cash flow anywhere in this model discounts a continuation into a capital
sum. Getting either limb wrong is the fourteenth pitfall.
Modules that are off in the base run
Three constructions are implemented and are inert on the anchor, so the base run reproduces the worked example while the machinery stays visible and testable:
The survivor’s annuity,
surv_annuity_rate = 0on the anchor, which makesclaims_deathstructurally zero at everyt. Model points 3 and 12 switch it on. With it off,elig_surv_probhas no effect on any cash flow. Where it is on, the reserve released per death is the annualdb_pp(k), struck at the end of the Versicherungsjahr, so the month decides when it is released and not how much.The *Rentengarantiezeit*,
guarantee_period_y = 0on the anchor, which makesclaims_survivorstructurally zero andpols_gtdzero at everyt. Model points 4 and 12 switch it on at 10 and 20 years.The BUZ,
buz_prem_share = 0on the anchor. It is carried as a premium share and nothing else; model point 11 sets it to 0.49, the statutory boundary, andprem_total_ppis the only cells that reads it.
Both zero columns are published rather than dropped, because a column of zeros states the product fact where a missing column would only hide it.
Sign convention
net_cf() is income positive — laufende Beiträge and Zuzahlungen in, death
benefits, annuity instalments, survivor continuations, expenses and commission out —
which is the library-wide sign. liability_cf() publishes the same stream
outgo-positive, liability_cf(t) = -net_cf(t) exactly, so a Solvency II best estimate
is sum v(t) x liability_cf(t) over whatever discount curve the valuation layer
supplies. Both are columns of result_cf(), so the identity is verifiable in the
frame rather than only in prose. Unlike TD_FR_S, expenses() here does not
include the commission: the two are separate lines of the notes’ own cash flow statement
and net_cf() subtracts each once.
The shape to expect on the monthly frame is a saw-tooth: the whole Versicherungsjahr’s
Beitrag and Zuzahlung fall in the first month of each projection year and nothing else
does, so that month is strongly positive and the other eleven carry a twelfth of the
maintenance expense and are slightly negative. Summed into years by
result_cf_annual() the familiar shape returns — a large new-business strain at
k = 0, since the Zillmerung instalment is an account deduction and costs the insurer
nothing but the initial commission at 2,5 % of the Beitragssumme and the acquisition
expense both fall at inception against a single year’s contribution; then two decades of
positive accumulation-phase margin; a Rentenbeginn at which the fund converts and no
cash moves; and a long negative payout tail, now paid one instalment at a time.
Cells Descriptions#
- pols_if_init()[source]#
The number of policies the model point represents at the valuation date.
The opening value of
pols_if(), and therefore the firstpols_ifvalue ofresult_cf(). It opens entirely inpols_paying()or entirely inpols_paidup()according topaidup_at_init.
- omega_age()[source]#
The terminal age of the mortality table: the last
agein mort_table.csv.121 on the shipped [std] table, the age German annuity tables are conventionally carried to. The projection runs to it because the annuity is lifelong, and
mort_rate()returns 1.0 there, so the last survivor dies in the last month of the terminal year, att = proj_len() - 1, and there is no tail state of any kind.
- proj_len_y()[source]#
n: the number of projected years,
omega_age() - age(0) + 1.The annual coordinate of the projection, and the one the contract is written in: the declared rate, the Beitragsdynamik, the Zillmerung window, the Zuzahlung end date, the Beitragsfreistellung and the Überschussrente are all annual terms. 77 on the anchor cell — twenty-two years of Aufschubphase at attained ages 45 to 66 and fifty-five years of Rentenphase at ages 67 to 121.
This is the annual-step model’s own
proj_len(), andk = 0 ... proj_len_y() - 1indexes exactly the rows that model projected.
- proj_len()[source]#
The number of projected months,
12 x proj_len_y().The exclusive end of the frame:
result_cf()is 0-based and runst = 0 ... proj_len() - 1, solen(result_cf()) == proj_len(), which is this library’s reading ofproj_len()and is asserted by the conventions suite. 924 on the anchor cell.The last month is the last month of the terminal year, where
mort_rate()is 1: the last survivor dies att = proj_len() - 1,pols_if(proj_len()) = 0exactly and there is no tail state of any kind.
- proj_year(t)[source]#
k(t): the 0-based projection year month t falls in,
t // 12.The bridge between the model’s two clocks.
tcounts projection months from the valuation date and is the argument of the in force, the decrements, the claims, the expenses and the annuity instalments;kcounts projection years and is the argument of everything the contract states per Versicherungsjahr — the premium, the Zuzahlung, the four account charges, the declared rate, the Deckungskapital and the Überschussrente. A cells’ argument therefore says which clock it is on, and this is the only place the two meet.kis the annual-step model’s ownt, which is what makes the two comparable row by row:result_cf_annual().loc[k]is that model’s rowk.
- is_anniv(t)[source]#
True in the last month of a projection year,
t % 12 == 11.The frame opens at a policy anniversary — an in-force model point has completed
duration_initwhole policy years at the valuation date — so a projection year is a Versicherungsjahr and its last month is the month before the next anniversary. It is where everything contractually annual falls: the year’s Beitragsfreistellung, the interest credit, and the certainty that the terminal year kills the last survivor.
- age_y(k)[source]#
x(k): attained age in projection year k.
entry_age + duration_init + k. Age last birthday at conclusion (Eintrittsalter), stepping on the policy anniversary [std]: no German convention was established, and here mortality drives the annuity’s duration rather than any benefit amount, so a half-year offset is second order.
- age(t)[source]#
x(t): attained age in projection month t,
age_y(proj_year(t)).The age steps on the anniversary and not monthly, which is what lets one generational mortality surface serve every month of a Versicherungsjahr and is why the twelve months of a year share one annual death rate.
- duration_y(k)[source]#
d(k): completed policy years at the start of projection year k.
duration_init + k, so a new-business point opens atduration_y(0) = 0. The Beitragsdynamik, the Zillmerung amortisation window and the Zuzahlung end date are all keyed to this rather than tok, which is what makes an in-force model point work: model point 6 opens atduration_y(0) = 17and its premium atprem_base_pp x 1.02^17. Keying any of the three tokis the seventh pitfall.The
durindex of behaviour_table.csv is the policy year,duration_y(k) + 1.
- duration(t)[source]#
d(t): completed policy years at the start of projection month t.
duration_y(proj_year(t)). It steps on the anniversary, so the twelve months of a projection year share one policy duration and therefore one row of behaviour_table.csv.
- duration_mth(t)[source]#
Completed policy months at the start of projection month t.
12 x duration_init + t. Carried because it is the only quantity that distinguishes two model points at the same projection month — a new-business point att = 12has completed twelve policy months, an in-force point opening atduration_init = 17has completed 216 — and becauseduration(t) = duration_mth(t) // 12states in code that the policy duration is the policy month divided down.
- policy_year(t)[source]#
The contractual 1-based policy year label of month t,
duration(t) + 1.The label the AVB and behaviour_table.csv use: the first year of the contract is policy year 1 and
durationis 0 through the whole of it. Published so that a reader never has to decide which of the two a table’sdurcolumn means.
- cal_year_y(k)[source]#
y(k): calendar year at the start of projection year k.
conclusion_year + duration_init + k. Carried because the mortality basis is generational: DAV 2004 R is a Generationentafel with the improvement inside the table, somort_rate_at_age()needs a calendar year as well as an age. Two model points that reach the same attained age in different calendar years see different rates, and treating the basis as a period table is the fifteenth pitfall.
- cal_year(t)[source]#
y(t): calendar year in projection month t,
cal_year_y(proj_year(t)).Steps on the policy anniversary rather than on 1 January, which is the same convention the attained age follows and the one the annual-step model necessarily used.
- ret_y()[source]#
T: the projection year in which Rentenbeginn falls,
ret_age - age(0).22 on the anchor cell. ``T < 0`` for a model point that opens in the *Rentenphase*, in which case the conversion never occurs inside the projection,
ann_pp(0) = ann_pp_initandcheck_conversion()is vacuously true. Model point 8 hasret_y() = -3.The earliest permitted Rentenbeginn is the completion of the 62nd year of life for contracts concluded after 31 December 2011 and the 60th for earlier ones; the model reads
ret_agefrom the model point and does not enforce the floor, which is a contract-writing rule rather than a projection rule.
- ret_t()[source]#
The projection month of the first annuity instalment,
12 x ret_y().The Rentenbeginn falls at the end of the deferment: the last accumulation month is
ret_t() - 1, the fund is converted on the balance struck there, and the first of the monthly instalments is paid att = ret_t(). 264 on the anchor cell.Negative for a model point that opens in the Rentenphase, and every comparison against it is written
max(0, ret_t())for that reason.
- gtd_end_t()[source]#
The last projection month in which a Rentengarantiezeit continuation is payable.
max(0, ret_t()) + 12 x guarantee_period_y - 1. The guarantee runs from Rentenbeginn, not from each death, so every continuation ends on the same date andpols_gtd()is zero fromgtd_end_t() + 1onwards however late the death that started it. Zero-length whereguarantee_period_y = 0, in which casepols_gtd()is zero everywhere.On the monthly grid the window is what the contract says it is —
12mguaranteed monthly instalments — where the annual grid could only offermpayments of a whole year’s annuity each.
- beitragssumme_pp()[source]#
S: the contract’s Beitragssumme at inception, per policy.
The sum of the contractual laufende Beiträge over the whole premium term including the Beitragsdynamik and excluding Zuzahlungen and the Ratenzahlungszuschlag:
S = sum_{u=0}^{m-1} prem_base_pp x (1 + prem_dyn_rate)^u, m = ret_age - entry_age
and simply
prem_base_ppfor a single premium. Excluding Zuzahlungen is the conservative reading of a question no retrieved source settles, and it matters twice: S is the base of the 25 ‰ Höchstzillmersatz cap on the acquisition charge written into the account, and it is the base of the initial commission. On a long-dated contract with a Dynamik the cap binds in euro terms far above what the same percentage would allow on a short one.Struck once, at inception, from the contract’s own terms — not from the projection — so an in-force model point carries the same S it was written with.
- prem_freq_load()[source]#
phi: the Ratenzahlungszuschlag for the model point’s payment frequency.
Read from option_table.csv under
option_id = "prem_mode": 1.000 annual, 1.020 half-yearly, 1.030 quarterly, 1.050 monthly [std]. It multiplies the laufender Beitrag and nothing else — not the Zuzahlung, which carries no frequency loading because it is a single payment, and not a single premium, which is whyprem_freq_load()returns 1.0 forprem_form = "single". Applying it twice, or to the Zuzahlung, is the eighth pitfall.
- prem_base_pp(k)[source]#
The contractual laufender Beitrag in year k, before the Ratenzahlungszuschlag.
prem_base_pp x (1 + prem_dyn_rate)^duration_y(k)on theregularform: the Beitragsdynamik compounds on the base premium from inception, so it is keyed to the policy duration and not to the projection year. Forprem_form = "single"the Einmalbeitrag is paid once, atk = 0and only whereduration_init = 0, and is zero at every otherk.This is the contractual amount. What is actually charged is
prem_pp(), which applies phi and stops at Rentenbeginn.
- prem_pp(k)[source]#
P(k): the laufender Beitrag charged per premium-paying policy in year k.
prem_base_pp(k) x prem_freq_load(), taken at the start of the year (annual in advance; a fractionated mode changes the amount through the Ratenzahlungszuschlag, not the grid). Zero fromk = ret_y()— premiums stop at Rentenbeginn — and zero on a model point that opens beitragsfrei, whose whole cohort is inpols_paidup()anyway. Letting premiums run past Rentenbeginn is the seventh pitfall.A dying policy has already paid the year’s premium, because deaths fall at the end of the year:
premiums()is weighted by the openingpols_paying()and is not further multiplied by(1 - mort_rate).
- zuz_take_up(k)[source]#
The Zuzahlung utilisation rate in year k, from behaviour_table.csv.
Looked up at
dur = duration_y(k) + 1, the policy year: 0.70 at policy years 1–5, 0.85 at 6–15, 0.90 at 16+ [std], clamped to the last row beyond the table.A utilisation rate, not a contract term. The contribution the Höchstbetrag makes possible is paid out of a profit not known until the year end, so whether it is paid at all is behavioural; a model that treats the Zuzahlung as contractual has quietly set this to 1.0. Nothing in the delib corpus supports any level.
- zuz_pp(k)[source]#
Z(k): the Zuzahlung paid per premium-paying policy in year k.
zuzahlung_pp x zuz_take_up(k), taken at the start of the year alongside the laufender Beitrag and carrying no Ratenzahlungszuschlag. Zero fromk = ret_y(), zero onceduration_y(k) >= zuzahlung_end_dur, and zero on a model point that opens beitragsfrei.The Zuzahlung is the product’s signature premium form — a self-employed buyer tops the contract up out of a good year — and it is the reason
zuzahlungenis published as a column of its own rather than folded intopremiums: it is a distinct premium form on a distinct charge basis, carryingalpha_zuz_rateinstead of a share of the Zillmerung.
- prem_total_pp(k)[source]#
The total contribution the policyholder pays in year k, including the BUZ.
(prem_pp(k) + zuz_pp(k)) / (1 - buz_prem_share). A reporting cells that enters no cash flow: it appears in noresult_cf()column and innet_cf()at nok.prem_base_ppis the old-age contribution; the BUZ premium buys a cover this model does not project, and its disability mechanics belong toBU_DE_S.buz_prem_share < 0.50is the statutory invariant — the supplementary covers together must stay strictly below half the total contribution or the whole contribution loses its Sonderausgabenabzug — and model point 11 sits at 0.49, the boundary. Modelling the BUZ as premium income with no benefit is the seventeenth pitfall.
- alpha_total_pp()[source]#
The zillmerised acquisition charge written into the account, per policy.
zill_rate x beitragssumme_pp(). The Höchstzillmersatz caps it at 25 ‰ of the *Beitragssumme* for business written from 1 January 2015, reduced from 40 ‰ by the LVRG; the shipped tariffs carry the two rates and the in-force pre-2015 model points take the older one.This is a deduction from the policyholder’s *Deckungskapital*, hence insurer income — not an expense. The insurer’s own acquisition outgo is
acq_expense_ppplus the initial commission, and the German design is precisely that what the insurer pays out is sized to what it may write into the reserve.
- alpha_amort_pp(k)[source]#
alpha(k): the Zillmerung instalment struck against the account in year k.
alpha_total_pp() / zill_spread_yin equal instalments over the contract’s firstzill_spread_y = 5years of Aufschubphase — of the contract, not of the projection — and zero thereafter. An in-force model point past duration 5 therefore sees none of it at anyk: model point 6, atduration_init = 17, sees zero throughout. A single-premium contract runs the same five instalments, so the total written into its account is the same 25 ‰ of the Beitragssumme and the debit simply outlives the one premium that paid for it.Charging the whole Zillmerung in year one is the fifth pitfall. On the anchor cell the instalment is equal at
k = 0 ... 4, zero fromk = 5, and the five sum tozill_rate x beitragssumme_pp()exactly.Whether the AltZertG’s five-year spreading of acquisition and distribution costs reaches Basisrentenverträge at all was not established; the five years here are the LVRG-era German market shape and are [std].
- alpha_zuz_pp(k)[source]#
alpha_z(k): the acquisition charge on the year’s Zuzahlung, per paying policy.
alpha_zuz_rate x zuz_pp(k), charged in the year the Zuzahlung is paid [std]. A Zuzahlung is not part of the Beitragssumme and so carries no share of the Zillmerung; it carries its own single charge instead, which is the normal German treatment of a top-up. Zero where no Zuzahlung is paid.
- unit_cost_pp(k)[source]#
u(k): the Stückkosten charged to the account in year k, per policy.
unit_cost_pp x (1 + expense_infl)^k[std] — 36,00 € inflating at 1,5 % a year. Charged to both blocks: a premium-free policy keeps paying the Stückkosten and the reserve charge and stops paying beta and the Zillmerung instalment, which is the whole economic content of a Beitragsfreistellung.An account deduction, not an expense. The insurer’s own maintenance outgo is
maint_expense_ppand is a separate line ofexpenses().
- prem_to_av_pp(k)[source]#
N(k): the premium credited to the account in year k, after all four charges.
(P(k) + Z(k)) x (1 - beta_prem) - alpha_amort_pp(k) - alpha_zuz_pp(k) - unit_cost_pp(k).N(k) may be negative in the first years of a heavily zillmerised contract, and it is not floored: there is no Rückkaufswert on this product for a floor to protect, and flooring the account at an internally computed surrender value would change the early years even though nothing is ever paid. That a German Deckungskapital starts near zero is a consequence of this line and not a modelling artefact.
Zero from
k = ret_y(): there is no account in the Rentenphase.
- prem_due(t)[source]#
True in the month the year’s Beitrag falls due,
t % 12 == 0.The whole Versicherungsjahr’s contribution is taken in the first month of the projection year, with the Zuzahlung beside it. The Ratenzahlungszuschlag is the reason a fractionated mode needs no finer grid than this: a German tariff prices half-yearly, quarterly and monthly payment by loading the amount, not by moving the Versicherungsperiode, so
prem_freq_load()changes what is paid and this cells changes nothing. The account is a Deckungskapital struck per Versicherungsjahr and is credited the same annual contribution whatever the mode.
Laufende Beiträge collected in month t at fund level, an inflow.
prem_pp(proj_year(t)) x pols_paying(t)in the month the year’s premium falls due and zero in the other eleven, weighted by the premium-paying ledger and by the opening count: the premium falls at the start of the year and the year’s deaths after it, so a policy that dies during the year has already paid for it. Multiplying by(1 - mort_rate(t))here applies the death rule twice.Because the count at the start of a projection year is the annual-step model’s own
pols_paying(k), this column sums over a year to that model’s premium income exactly.
- zuzahlungen(t)[source]#
Zuzahlungen collected in month t at fund level, an inflow.
zuz_pp(proj_year(t)) x pols_paying(t)in the month the year’s premium falls due and zero in the other eleven: the Zuzahlung is a single payment made alongside the laufender Beitrag and carries no Ratenzahlungszuschlag precisely because it is not fractionated.Published as a column of its own rather than folded into
premiums()because it is a distinct premium form on a distinct charge basis, and because settingzuz_take_upto zero — which removes about two fifths of the anchor’s contribution stream — is a legitimate variant a reader should be able to see the size of.
- mort_rate_at_age(x, y)[source]#
q^t(x, y): the first-order table death rate at age x in calendar year y.
qx(x) x (1 - trend(x))^(y - mort_base_year), clipped to[0, 1]. The generational form: the improvement lives inside the basis, which is what makes DAV 2004 R a Generationentafel and what a replacement table must preserve. Two model points at the same attained age in different calendar years see different rates, and the shipped table is anchored atmort_rate_at_age(67, 2005) = 0.014000.First order, so it carries the DAV’s prudential margins. The guaranteed Rentenfaktor is struck on this basis; the projection runs on
mort_rate().
- mort_rate_base(t)[source]#
The first-order table rate applying in projection month t, an annual rate.
mort_rate_at_age(age(t), cal_year(t)). Split frommort_rate()so that the table rate and the best-estimate rate have separate names and the step between them is a single visible factor. Both are annual rates of the projection year the month falls in: the age and the calendar year step on the anniversary, so the twelve months of a year share one table rate.
- mort_rate(t)[source]#
q(t): the best-estimate annual death rate of the year month t falls in.
mort_be_factor x mort_rate_base(t), the step from the shipped first-order table to a best estimate.mort_be_factor = 0.85is [std] and is the single largest unanchored number in the payout phase.This is the library’s two-speed convention:
mort_rateis the annual rate andmort_rate_mth()the monthly rate the recursion actually applies. Reading this one into a monthly roll-forward projects twelve years of mortality in one year.The terminal age is absorbing:
mort_rate(t) = 1.0whereage(t) >= omega_age(), whatevermort_be_factorsays, so the last survivor dies in the last month of the terminal year,pols_if(proj_len()) = 0exactly and the decrement closure identity holds to the last euro. Without it the generational trend would carry the table’s own terminal rate below 1 and leave a residue in force after the end of the table.Deaths fall at the end of the month, after that month’s annuity instalment; the interest credit and the premium are annual and fall at the two ends of the year, so a dying policy has been credited its year’s interest and has paid its year’s premium.
- mort_rate_mth(t)[source]#
The monthly death rate applied in projection month t.
1 - (1 - mort_rate(t))^(1/12): the geometric twelfth of the year’s annual rate, so the twelve months of a projection year compound back to it exactly andpols_if(12k)is the annual-step model’spols_if(k)to the last bit. Dividing the annual rate by twelve instead would leave a residue that grows with the rate and is largest exactly where this product’s cash flows are — in the tail of a lifelong annuity.A rate of 1 is a certainty and is not twelfth-rooted: at the terminal age the whole cohort dies, and it dies in the last month of the year rather than a little in each, so the annuity is paid for the whole terminal year and
pols_if(proj_len()) = 0.
- bf_rate(t)[source]#
f(t): the Beitragsfreistellung rate of the year month t falls in, annual.
Read from behaviour_table.csv at
dur = policy_year(t): 4,0 % at policy years 1–5, 3,0 % at 6–10, 2,0 % at 11+ [std], clamped to the last row beyond the table.An annual rate applied once a year, not a monthly one. § 165 VVG lets the policyholder demand a Beitragsfreistellung “für den Schluss der laufenden Versicherungsperiode”, and § 12 Abs. 1 VVG makes that period the Versicherungsjahr here, so the election takes effect at the anniversary and
pols_freeze()is non-zero only in the last month of a projection year. That is also what keeps the premium-paying ledger bit-identical to the annual-step model’s at every anniversary.This is not a lapse rate and there is no lapse decrement on this product. § 165 VVG survives and is the contract’s only behavioural exit, but it removes the premium, not the policy: the rate moves policies from
pols_paying()topols_paidup()and appears nowhere inpols_if(). Treating it as a lapse is the second pitfall.Zero from
t = ret_t()— there is no premium left to stop — and zero on a single-premium contract for the same reason. Applied to the survivors of the year’s death decrement.
- pols_paying(t)[source]#
l^p(t): premium-paying policies at the start of projection month t.
pols_if_init()att = 0unless the model point opens beitragsfrei, thenl^p(t + 1) = l^p(t) x (1 - q_mth(t)) - pols_freeze(t)— the month’s death decrement, and, in the last month of a projection year only, the Beitragsfreistellung on its survivors. The weight on
premiums()andzuzahlungen(), which are due in the first month of a year, so the count they see is the count the annual-step model saw.A model point opens entirely paying or entirely premium-free. A part-paid-up book is two model points; averaging the two cohorts is the third pitfall.
- pols_paidup(t)[source]#
l^f(t): premium-free (beitragsfreie) policies at the start of projection month t.
l^f(t + 1) = l^f(t) x (1 - q_mth(t)) + pols_freeze(t). The block is absorbing: no Wiederinkraftsetzung is modelled, because none was established, which is conservative on premium income and is a standardization rather than a contract fact.A premium-free policy is still in force, still certified, still protected and still converts at Rentenbeginn. It keeps paying the Stückkosten and the reserve charge out of its own Deckungskapital and stops paying beta and the Zillmerung instalment. Both charges are annual, so the block is carried at fund level by projection year in
av_pu_at()while its head count is carried here by month.
- pols_if(t)[source]#
l(t): policies in force at the start of projection month t, both cohorts.
pols_paying(t) + pols_paidup(t), and the weight on every cash flow of the sameresult_cf()row. It obeyspols_if(t + 1) = pols_if(t) x (1 - mort_rate_mth(t))with
bf_rateabsent from the identity, because a Beitragsfreistellung is a transfer between the two ledgers and not an exit. That is the whole of what distinguishes this product’s decrement structure from a Schicht-3 annuity’s, andcheck_pols_roll_fwd()asserts it.pols_if(proj_len())is defined and is zero, becausemort_rate()is 1 in the terminal year andmort_rate_mth()places that certainty in its last month. It is read bycheck_pols_roll_fwd()and by nothing else;result_cf()stops att = proj_len() - 1.
- pols_if_at(t, timing)[source]#
The number of policies in force at a point inside projection month t.
"BEF_DECR"l(t), the start of the month, before any decrement; the same number as
pols_if()and the weight on that month’s cash flows."AFT_DEATH"l(t) x (1 - q_mth(t)), after the month’s death decrement, which falls at the end of the month. In the last month of a projection year this is the population the Beitragsfreistellung is taken from."AFT_FREEZE"l(t + 1). Numerically identical to
"AFT_DEATH", and deliberately so: the freeze moves policies betweenpols_paying()andpols_paidup()without removing any, so the total is unchanged. The two timings exist so that the processing order is readable in the code and so that the equality is a statement the model makes rather than one a reader has to reconstruct.
- pols_death_paying(t)[source]#
Expected deaths among premium-paying policies in month t:
l^p(t) x q_mth(t).Split from
pols_death_paidup()because the two cohorts release different reserves —db_pp()per policy againstdb_pu_pp()— and only the split figure can be multiplied by the right one. Both reserves are annual amounts, struck at the end of the projection year, so the twelve months of a year release the same per-policy sum and the year’s total is the annual-step model’s.
- pols_death_paidup(t)[source]#
Expected deaths among premium-free policies in month t:
l^f(t) x q_mth(t).
- pols_death(t)[source]#
Expected deaths in projection month t, both cohorts.
pols_death_paying(t) + pols_death_paidup(t), equivalentlyl(t) x q_mth(t). In the Aufschubphase a death pays nothing in the base design — the reserve is released as a mortality profit, because the entitlement is nicht vererblich — and with the survivor rider on it pays only where an eligible survivor exists. In the Rentenphase the annuity simply stops with that month’s instalment, and each death contributeselig_surv_probof a Rentengarantiezeit continuation where one is running.
- pols_freeze(t)[source]#
Policies going beitragsfrei at the end of projection month t.
l^p(t) x (1 - q_mth(t)) x f(t)in the last month of a projection year and zero in the other eleven: the Beitragsfreistellung takes effect for the end of the current Versicherungsperiode (§ 165 with § 12 Abs. 1 VVG), and it is taken on the survivors of that month’s death decrement, which is the processing order the notes set out [std]. Zero in the Rentenphase and zero on a single-premium contract.Each frozen policy carries
av_pp_at(k, "AFT_INT")of Deckungskapital with it from the paying block into the premium-free block, which is the termpols_freeze(12 k - 1) x av_pp_at(k - 1, "AFT_INT")inav_pu_at(): the freeze and the year’s interest credit fall at the same instant, the end of the Versicherungsjahr. Nothing leaves the fund, which is whycheck_av_roll_fwd()closes across a freeze.
- pols_gtd(t)[source]#
g(t): Rentengarantiezeit continuations running at the start of month t.
The guarantee runs
guarantee_period_yyears from *Rentenbeginn*, so every continuation ends on the same date:g(t) = 0 for t < max(0, ret_t()) or t > gtd_end_t() g(t) = g(t - 1) + pols_death(t - 1) x elig_surv_prob inside the window
On the monthly grid the window is
12minstalments and a continuation starts in the month after the death that triggered it, rather than in the following year.elig_surv_probis what makes this a Schicht-1 guarantee rather than a Schicht-3 one: the instalments continue only to a permitted survivor — a spouse, a registered partner, or a child while Kindergeld runs — and where none exists the payments simply cease. They are also never commutable:claims()paysann_mth_pp(t) x g(t)as a stream and nothing anywhere discounts a continuation into a capital sum.Monotone non-decreasing inside the window, and exactly zero outside it. Zero at every
twhereguarantee_period_y = 0, which is the base design and the anchor’s setting.
- decl_rate(k)[source]#
The declared laufende Verzinsung in projection year k, from surplus_table.csv.
2,60 % for
k = 0 ... 9, 2,40 % fork = 10 ... 19, 2,20 % thereafter in thebasescenario [std], clamped to the last row beyond the table.It is the total credited rate, not a spread over the *Rechnungszins*. German declared rates are quoted that way, which is why
cred_rate()is a maximum and not a sum. A scenario rather than a forecast: no declared rate specific to a Basisrente was established anywhere in the delib corpus, and the path is set above the 1,00 % Höchstrechnungszins by a plausible surplus margin and graded down.
- cred_rate(k)[source]#
i(k): the rate credited to the Deckungskapital in projection year k.
max(gtd_rate, decl_rate(k))— a maximum, not a sum.gtd_rateis the contract’s Rechnungszins, capped at the Höchstrechnungszins in force at conclusion and fixed for the whole term, so a book carries a stack of guarantee vintages and themaxpicks a different branch on each. On the anchor (1,00 %) the declared path binds at everyk; on model point 8 (2,75 %, above the whole declared path) the guarantee binds at everyk.Stacking the declared rate on top of the guarantee is the sixth pitfall and is worth a great deal over a twenty-two-year deferment. The reserve charge gamma is netted inside the same crediting step, in
av_pp_at()andav_pu_at().
- av_pp(k)[source]#
A^p(k): the Deckungskapital per premium-paying policy at the start of year k.
av_pp_initatk = 0for a point that opens premium-paying, zero for one that opens beitragsfrei (whose whole reserve is inav_pu_at()), thenav_pp_at(k - 1, "AFT_INT").Per policy, against
av_pu_at(), which is the premium-free block at fund level. The two diverge from the first freeze and must not be averaged into one per-policy figure: that is the third pitfall. Zero fromk = ret_y() + 1, the fund having become an annuity obligation.
- av_pp_at(k, timing)[source]#
A^p(k, .): the Deckungskapital per premium-paying policy inside year k.
"BEF_PREM"av_pp(), the start of the year before the premium is taken."AFT_PREM"after the year’s premium, Zuzahlung and all four charges:
av_pp_at(k, "BEF_PREM") + prem_to_av_pp(k)."AFT_INT"after interest, credited at the end of the year net of the reserve charge:
av_pp_at(k, "AFT_PREM") x (1 + cred_rate(k) - gamma_av). A policy that dies during the year has been credited a full year’s interest, because deaths fall after crediting.
All three are zero from
k = ret_y()except"BEF_PREM"atk = ret_y()itself, which is the fund the annuity is struck on.
- av_pu_at(k, timing)[source]#
A^f(k, .): the premium-free block’s Deckungskapital, at fund level.
Carried at fund level rather than per policy because a policy that froze at duration 5 and one that froze at duration 15 hold different reserves and only the aggregate is meaningful.
"BEF_PREM"av_pp_init x pols_if_init()atk = 0for a model point that opens beitragsfrei, zero otherwise; thenav_pu_at(k - 1, "AFT_INT") x (1 - q(k - 1)) + pols_freeze(12 k - 1) x av_pp_at(k - 1, "AFT_INT")— the survivors of the block, plus the reserves the year’s freezes carried across from the paying block."AFT_PREM"av_pu_at(k, "BEF_PREM") - unit_cost_pp(k) x pols_paidup(12 k). The block pays the Stückkosten and nothing else: no premium, so no beta and no Zillmerung instalment. That asymmetry is the whole economic content of a Beitragsfreistellung."AFT_INT"as for the paying block,
x (1 + cred_rate(k) - gamma_av).
- av_at(k, timing)[source]#
A(k, .): the whole Deckungskapital at fund level inside year k.
av_pp_at(k, timing) x pols_paying(12 k) + av_pu_at(k, timing), with the opening paying count as the weight at every timing, because deaths fall after interest.This is the only one of the three account cells that rolls forward on mortality alone:
av_at(k + 1, "BEF_PREM") = av_at(k, "AFT_INT") x (1 - mort_rate(12 k))
and that identity —
check_av_roll_fwd()— holds whether or not the survivor rider is on, because the reserve of a policy terminated by death leaves the fund either way: as a claim where an eligible survivor exists, as a mortality profit where none does. It is the arithmetic content of nicht vererblich. It also holds across a Beitragsfreistellung, because a freeze moves reserve between the two blocks without removing any.
- av(k)[source]#
A(k): the Deckungskapital at the start of year k, fund level.
av_at(k, "BEF_PREM"). A state variable, reported and not summed — the third column ofresult_cf()is a balance, not a cash flow, and adding it to anything is a category error.Zero for every
k > ret_y(). Atk = ret_y()itself it is the pre-conversion fund, which is the number the annuity is struck on and the one a reader following the worked example needs;fund_at_conv()grosses it up by the Schlussüberschussanteil and the account is empty from the next year onwards.
- rentenfaktor_curr()[source]#
The insurer’s aktueller Rentenfaktor at the conversion age.
Read from rentenfaktor_table.csv at
(rf_scenario_id, ret_age): euro of monthly annuity per 10 000 € of capital. 31,50 € at age 67 in thebasescenario [std]; thelowscenario runs about 12 % below it.Entirely [std]: no Rentenfaktor level, range or time series exists anywhere in the delib corpus, for this or any product. It is the single largest lever in the model, because it converts the entire accumulated fund into the entire payout-phase liability. Zero where the model point opens in the Rentenphase and no conversion occurs.
- rf_option_factor()[source]#
The multiplicative reduction in the Rentenfaktor bought by the two options.
factor("guarantee_period", guarantee_period_y) x factor("survivor", surv_annuity_rate)from option_table.csv: 1,000 for no Rentengarantiezeit, 0,995 for ten years and 0,974 for twenty; 1,000 for no survivor’s annuity and 0,930 for one at 60 % of the main annuity. All [std].A German tariff pays for both covers out of the annuity rather than by scaling the death benefit, which is why they appear here and not in
claims(). 1,000 on the anchor cell, where both options are off.
- rentenfaktor_applied()[source]#
R: the Rentenfaktor actually applied at Rentenbeginn.
max(rentenfaktor_gtd, rentenfaktor_curr()) x rf_option_factor().The
maxis the contract’s own rule and it is a genuine discontinuity: the projection is sensitive to whichever factor is higher and completely insensitive to the other. The anchor cell converts at the current 31,50 € against a guaranteed 28,00 €; model point 13 converts at its guaranteed 34,00 € against alow-scenario current 27,72 €, which is why that point exists. Taking one when the other is higher is the thirteenth pitfall. Monotone non-decreasing in both inputs.rentenfaktor_gtdwas struck at inception on first-order DAV 2004 R with a prudential margin and a conservative interest basis; it is not, and must not be, the projection’s own best-estimate basis.
- fund_at_conv()[source]#
F: the fund converted at Rentenbeginn, including the Schlussüberschussanteil.
av_at(ret_y(), "BEF_PREM") x (1 + terminal_bonus_rate), at fund level: the balance carried out of the last accumulation year, struck at the start of projection yearret_y()— equivalently at the end of monthret_t() - 1— before any annuity is paid.The Schlussüberschussanteil is allocated at this single date and at no other, which is a contract fact rather than a standardization: the contract has no earlier exit — no surrender, no capital option — so there is no earlier trigger for a terminal bonus to attach to. The 4,0 % level is [std] with nothing behind it.
Zero for a model point that opens in the Rentenphase, where no conversion occurs inside the projection.
- ann_bonus_rate(k)[source]#
b(k): the Überschussrente uplift applied at the end of payout year k.
1,0 % p.a. compounding in the
basescenario [std], read from surplus_table.csv. A teildynamische Rente: a volldynamische one would consume the whole first-order margin released in the payout phase and a konstante one none, and 1,0 % is deliberately in between.It is the mechanism that gives the conversion-basis wedge back to the annuitant — the fund is converted on first-order mortality and run off on the best estimate — so this lever and
mort_be_factorbetween them decide the payout phase’s whole economics. Both are [std] independently.
- ann_pp(k)[source]#
a(k): the annual annuity per surviving annuitant in projection year k.
Zero before Rentenbeginn; at
k = ret_y()the conversion:ann_pp(T) = fund_at_conv() / pols_if(ret_t()) / rf_unit x rentenfaktor_applied() x ann_freq
which is the cohort-average annual annuity per annuitant — exact at fund level even though the paying and premium-free cohorts arrive with different per-policy reserves — and thereafter
ann_pp(k) = ann_pp(k - 1) x (1 + ann_bonus_rate(k - 1)).pols_if(ret_t())is the count at the first payout month, which is the count at the start of payout yearT: the conversion is struck on the cohort that reaches Rentenbeginn.For a model point that opens in the Rentenphase (
ret_y() < 0) the conversion never occurs inside the projection andann_pp(0) = ann_pp_init.This is an annual amount and is not what anybody is paid:
ann_freq = 12because the Rentenfaktor is quoted in euro a month, andann_mth_pp()is the instalment. The annual figure is kept as the cells the conversion identity and the Überschussrente are stated on, because both are annual terms — the Rentenfaktor is applied once and the surplus uplift compounds once a year.
- ann_mth_pp(t)[source]#
The monthly Rente instalment per surviving annuitant in projection month t.
ann_pp(proj_year(t)) / ann_freq, paid monthly in advance fromt = ret_t()and zero before it. This is the amount the contract actually promises: a Rentenfaktor is quoted as euro of monthly annuity per 10 000 € of capital, and a Leibrente is paid monthly.This is what the monthly grid buys on this product. The annual-step model booked twelve instalments together at the start of the payout year on the opening in-force count, so a life that died in the first month of a payout year was paid the whole year; it named that as the twelfth pitfall and as a stated approximation of a monthly grid on an annual one, generous to the year of death by up to a full year’s annuity and concentrated in the high-mortality tail. Here the instalment is paid to whoever is alive at the start of each month and the approximation is gone.
The uplift still steps once a year, on the payout anniversary, because the Überschussrente is declared annually: the twelve instalments of a payout year are equal and the thirteenth is
(1 + ann_bonus_rate)times the twelfth.
- db_pp(k)[source]#
The Deckungskapital released per dying premium-paying policy in year k.
av_pp_at(k, "AFT_INT"): deaths fall after interest is credited, so the reserve released is the end-of-year one. What is paid is this amount only where the survivor rider is on and an eligible survivor exists; otherwise the whole of it is a mortality profit and nothing is paid. Seeclaims().
- db_pu_pp(k)[source]#
The Deckungskapital released per dying premium-free policy in year k.
av_pu_at(k, "AFT_INT") / pols_paidup(12 k): the premium-free block is carried at fund level, so the per-policy figure is an average over policies that froze at different durations. That average is exact for this purpose — the deaths are a uniform share of the block — and it is the only per-policy figure the block admits. Zero where the block is empty.
- claims(t, kind=None)[source]#
Benefit outgo in projection month t, by kind; the total when kind is omitted.
"DEATH"the Deckungskapital released by the month’s deaths in the Aufschubphase, weighted by
elig_surv_prob. The amount released per policy is the annualdb_pp(k)— the reserve at the end of the Versicherungsjahr, which is where the account is struck — so the twelve months of a year release the same per-policy sum and the year’s total is the annual-step model’s to the last bit. What the month decides is when it is released, not how much.Structurally zero where ``surv_annuity_rate = 0``, which is the base design and the anchor’s setting: the entitlement is nicht vererblich, so a death before Rentenbeginn pays nothing and the reserve is released as a mortality profit. Zero from
t = ret_t()in every case.Where the rider is on this is not a lump sum to a beneficiary. Everything paid to a survivor must be paid as an annuity, so what is booked is the reserve leaving this contract as the single premium of a survivor’s annuity — a new liability, an immediate annuity, that this model does not project.
"ANNUITY"ann_mth_pp(t) x pols_if(t): one monthly instalment, in advance, to the lives in force at the start of the month."SURVIVOR"ann_mth_pp(t) x pols_gtd(t): the Rentengarantiezeit stream, payable only to a permitted survivor and never commutable. Structurally zero whereguarantee_period_y = 0.
There is no fourth kind, and there can be none: no surrender value, no capital option, no partial capital payment and no commutation exist on this product.
check_no_capital()asserts that the total is exactly the sum of these three.
- expenses(t)[source]#
E(t): the insurer’s own expense outgo in projection month t, fund level.
Acquisition expense at inception (
t = 0andduration_init = 0only), then a twelfth of the annual maintenance expense per in-force policy in the Aufschubphase and a twelfth of the annual annuity administration per annuitant and per *Rentengarantiezeit* continuation in the Rentenphase, both inflating atexpense_inflfrom the valuation date. The payout phase is administratively cheaper than the accumulation phase, which is why the two rates differ.The inflation factor steps on the anniversary,
(1 + expense_infl)^proj_year(t), because an expense assumption is quoted per policy per year; the twelve months of a projection year therefore carry the same monthly amount. A policy that dies in the third month of a year now bears three twelfths of that year’s maintenance rather than the whole of it, which is the second thing the finer grid changes on this product.The commission is not in here. It is a separate line of the notes’ cash flow statement and a separate column of
result_cf(), andnet_cf()subtracts each once.Nor are the charges: beta, gamma, the Stückkosten and the Zillmerung amortisation are deductions from the policyholder’s account and hence insurer income. This cells is therefore invariant to
beta_prem,gamma_avandzill_rate, and those three movenet_cf()only through the smaller annuity that a smaller fund buys at Rentenbeginn. Booking a charge as an expense as well is the fourth pitfall.
- commissions(t)[source]#
C(t): commission outgo in projection month t, fund level.
comm_init_rate x beitragssumme_pp() x pols_if_init()at inception — the Abschlussprovision, sized to the Zillmerung cap, which is the German design in which what the insurer pays out is what it may write into the reserve — pluscomm_renew_rate x (premiums(t) + zuzahlungen(t))fromt = 1, the Bestandsprovision. Both [std]: the corpus’s only datum is a 1 575 € Abschlussprovision on one specimen quotation, and it is [unverified].The renewal commission is a percentage of a contribution, so it falls in the month the contribution does and in no other — the
t >= 1condition excludes the first year’s premium att = 0and admits every later one att = 12, 24, ..., which is exactly the annual-step model’s rule.Paid only where the model point is new business (
duration_init = 0): an in-force point’s acquisition commission was paid before the valuation date and is not a projected cash flow.
- net_cf(t)[source]#
The net liability cash flow of projection month t, income positive.
premiums + zuzahlungen - claims_death - claims_annuity - claims_survivor - expenses - commissions. The library-wide sign;liability_cf()publishes the same stream outgo-positive.expenseshere does not include the commission — the two are separate lines and each is subtracted once — and the Deckungskapital is a balance rather than a cash flow and enters nothing.prem_total_ppenters nothing either: the BUZ premium buys a cover this model does not project.check_net_cf()reconstructs this fromresult_cf()’s own published columns, which is delib’s first ruling — the headline number of a cash flow model must not be the one quantity nothing checks.
- liability_cf(t)[source]#
The same stream as
net_cf(), outgo positive:-net_cf(t)exactly.The orientation the technical notes print and the one a valuation layer consumes: a Solvency II best estimate is
sum v(t) x liability_cf(t)over the relevant risk-free term structure, plus a risk margin. Published as a column besidenet_cf()so the sign convention is verifiable in the frame rather than only in prose. This library discounts nothing and computes no reserve.
- check_net_cf_resid(t)[source]#
The cash flow statement’s residual in projection month t; zero everywhere.
Reconstructs
net_cf(t)fromresult_cf()’s own published columns —premiums + zuzahlungen - claims_death - claims_annuity - claims_survivor - expenses - commissions - net_cf— rather than from the cells that produced them, so a column added to the frame but not to
net_cf(), a mis-signed column, or a column whose cells and frame entry have drifted apart all leave a residual here.pols_ifandpols_payingare counts and are excluded from the identity by construction; the Deckungskapital is a balance on an annual clock and is not a column of this frame at all.
- check_net_cf()[source]#
True when the published cash flow statement reconciles in every projected month.
delib’s first ruling, required of every model in this library: the identity that reconstructs
net_cf(t)from the statement’s own parts, in code rather than in prose.check_net_cf_resid()gives the signed residual of the month that failed.The tolerance is
roll_fwd_tolrelative to the largest|net_cf|in the run, so it means the same thing on a 300 € contribution and on a 30 826 € one.
- check_pols_roll_fwd_resid(t)[source]#
The policy-ledger residual in projection month t; zero everywhere.
A non-negative residual, because it closes two identities at once and a signed sum could let them cancel. The first term says the two ledgers exhaust the in-force count; the second and third say the in-force count decrements on mortality alone, the last of them saying in code that the Beitragsfreistellung leaves the total untouched:
|pols_paying(t) + pols_paidup(t) - pols_if(t)| |pols_if(t + 1) - pols_if_at(t, "AFT_FREEZE")| |pols_if_at(t, "AFT_FREEZE") - pols_if(t) x (1 - mort_rate_mth(t))|
The second is the one to stare at:
bf_ratedoes not appear in it. A Beitragsfreistellung is a transfer between the ledgers and not an exit, so a model that subtracts it frompols_if()— the second pitfall — fails here, and so does a misindexed recursion that rolls forward withmort_rate_mth(t + 1). It is the monthly rate throughout: a roll-forward that used the annualmort_rate(t)here would fail in the first month of the projection.
- check_pols_roll_fwd()[source]#
True when both policy-ledger identities close in every projected month.
- check_av_roll_fwd_resid(k)[source]#
The Deckungskapital residual in projection year k; zero everywhere.
The account is an annual construction — one premium, one set of charges and one interest credit per Versicherungsjahr — so its roll-forward is stated per year and this residual takes
k, nott. Before Rentenbeginn:av_at(k + 1, "BEF_PREM") - av_at(k, "AFT_INT") x (1 - mort_rate(12 k))
the fund-level roll-forward on mortality alone, at the year’s annual rate — which is exactly what the twelve monthly rates compound to, so the identity closes on the monthly grid with the annual arithmetic unchanged. It holds across a Beitragsfreistellung, because a freeze moves reserve between the two blocks without removing any, and it holds whether or not the survivor rider is on, because the reserve of a policy terminated by death leaves the fund either way — as a claim where an eligible survivor exists, as a mortality profit where none does. That is the arithmetic content of nicht vererblich.
At
k = ret_y()the residual isav_at(k, "AFT_INT"): the conversion empties the account, so nothing is credited into it in the conversion year. After it the residual addsav(k)as well, so a Deckungskapital surviving into the Rentenphase fails here. A model that collapsed the paying and premium-free blocks into one average per-policy reserve fails at the first freeze.
- check_av_roll_fwd()[source]#
True when the Deckungskapital rolls forward and is emptied at Rentenbeginn.
- check_conversion_resid(k)[source]#
The conversion residual; zero at every k, and non-trivial only at
ret_y().Inverts the conversion identity at
T = ret_y():ann_pp(T) x pols_if(ret_t()) x rf_unit / (rentenfaktor_applied() x ann_freq) - fund_at_conv()
so it catches a factor applied per policy instead of per fund, an
ann_freqof 1 where the annuity is monthly, and arf_unitof 1 000 instead of 10 000. Zero at every otherk, which is the second thing it asserts: the fund converts exactly once, at Rentenbeginn, and there is no second conversion, no partial commutation and no re-quotation.An annual residual, because the conversion is struck once on a balance the contract defines per Versicherungsjahr; the instalments it buys are monthly and are checked by
check_annuity_roll_fwd().Vacuously zero for a model point that opens in the Rentenphase, where the conversion happened before the valuation date.
- check_no_capital_resid(t)[source]#
The nicht kapitalisierbar residual in projection month t; zero everywhere.
A non-negative residual with two limbs:
|claims(t, "DEATH")|wherever the survivor rider is off (surv_annuity_rate = 0) ort >= ret_t(). A death before Rentenbeginn pays nothing in the base design, and after Rentenbeginn the annuity simply stops; paying anything there is the ninth pitfall.|claims(t) - claims(t, "DEATH") - claims(t, "ANNUITY") - claims(t, "SURVIVOR")|, which asserts that there is no fourth kind of payment. No surrender value, no Rückkaufswert, no Kapitalwahlrecht, no Teilkapitalauszahlung and no commutation of a Rentengarantiezeit exist on this product, so the only things this model can pay are a monthly annuity instalment, a guarantee continuation and a survivor’s single premium. The Kleinbetragsrenten-Abfindung is absent too, but by standardization rather than by prohibition — Schicht 1 does permit it — so this residual records a property of this implementation and not of the statute.
Stated per month, because it is a statement about payments and payments are monthly.
The absence of a
claims_lapsecolumn, acv_ppcells and alapse_ratecells is asserted in the product’s own test module, because it is an absence and cannot be computed here.
- check_no_capital()[source]#
True when no payment other than a permitted annuity or survivor benefit is made.
- check_annuity_roll_fwd_resid(k)[source]#
The annuity and Rentengarantiezeit residual in projection year k; zero everywhere.
A non-negative residual with four limbs, stated per year because the annuity is struck once and uplifted once a year while its instalments are monthly:
inside the payout phase,
|ann_pp(k) - ann_pp(k - 1) x (1 + ann_bonus_rate(k - 1))|— the Überschussrente compounds and nothing else touches the annuity once it is struck;before it,
|ann_pp(k)|and the year’s|pols_gtd(t)|— nothing is in payment before Rentenbeginn;|12 x ann_mth_pp(t) - ann_pp(k)|over the year’s months — the twelve instalments of a payout year are equal and are exactly the annual amount, so the uplift steps on the anniversary and nowhere else;after
gtd_end_t(),|pols_gtd(t)|— the Rentengarantiezeit runs from Rentenbeginn, not from each death, so every continuation ends on the same date, and on this grid that date is the end of the12m-th instalment.
- check_annuity_roll_fwd()[source]#
True when the annuity compounds and the guarantee window closes on time.
- result_cf()[source]#
Result table of cash flows, indexed by projection month t.
pols_ifis the start-of-month count and the weight applied to every cash flow on the same row;pols_payingis the premium-paying subset and the weight on the two premium columns, which are non-zero only in the first month of a projection year. Columns 3 and 4 enternet_cfpositively and columns 5 to 9 negatively.liability_cfisnet_cfoutgo-positive.The Deckungskapital is not a column here. It is a balance on the annual clock — one premium, one set of charges and one interest credit per Versicherungsjahr — and it lives in
result_pols()beside the rates and per-policy amounts that move with it. A balance in a monthly cash flow statement would invite exactly the summation that is a category error.claims_deathis structurally zero whereversurv_annuity_rate = 0andclaims_survivorwhereverguarantee_period_y = 0; both are published rather than dropped, because a column of zeros states the product fact where a missing column would only hide it. There is no ``claims_lapse`` column and no surrender column of any name: the entitlement is nicht kapitalisierbar.The frame runs
t = 0 ... proj_len() - 1and stops, solen(result_cf())isproj_len(). Att = proj_len() - 1the last survivor dies, and there is no tail state, no maturity payment and nothing left to pay.result_cf_annual()is the same stream summed into projection years, which is the view the technical notes print.
- result_cf_annual()[source]#
result_cf()summed into projection years, indexed by k.The seven cash flow columns are summed over the twelve months of each projection year and the two counts are taken at the year’s start, which is what they are everywhere else in the model. It is a regrouping of the monthly frame and not a second projection: no cells is evaluated on a different basis to produce it.
Indexed by the projection year
krather than by the policy year, because everything annual in this model is keyed to the projection — the declared rate, the expense inflation and the Überschussrente all count from the valuation date — and becausekis the annual-step model’s ownt, soresult_cf_annual().loc[k]is that model’s rowkand the two can be read side by side.policy_yearis published as a column ofresult_pols()for a reader who needs the contractual label.The Aufschubphase rows reproduce the annual-step model’s to the last bit on the premium, the Zuzahlung and the commission, which fall on the anniversary and are weighted by a count the finer grid does not change. The annuity rows do not and are not meant to: the instalments are monthly now, and a life that dies during a payout year is no longer paid the whole of it.
- result_pols()[source]#
Result table of the annual state, indexed by projection year k.
The companion to
result_cf(), and the model’s annual view: the two policy ledgers at the start of the year and the transfers between them, the decrement and crediting rates the year is run on, the per-policy premium and Zuzahlung, the Deckungskapital of the premium-paying cohort and of the fund, and the annual annuity with the monthly instalment it is paid in.Every rate here is the annual one:
mort_rateis the year’s death rate andmort_rate_mththe twelfth the recursion applies, and the two are printed together because confusing them is the easiest way to break a monthly model of an annual product.pols_deathandpols_freezeare the year’s totals — the deaths summed over its twelve months, the freezes falling in its last — so the columns reconcile with the ledgers beside them.policy_yearis the contractual 1-based label, which isk + 1only for a model point projected from issue.