Technical Notes#
Status: Draft, 2026-08-29 (sources accessed 2026-08-29); citations re-verified against the primary documents 2026-08-30.
Scope note. These notes specify a reference liability cash-flow projection model — model name
RV_DE_S, monthly grid — for the standardized composite German klassische aufgeschobene
private Rentenversicherung defined in product-spec.md (same directory). This is not any single
insurer’s contract. [S#]/[R#] tags refer to the source list in sources.md (numbering carried from
_research/klassische_rentenversicherung.md; frozen); [REG-R#] tags refer to the cross-product
reference library references/regulatory-and-actuarial-references.md (its own frozen numbering).
std marks standardizations introduced for the reference implementation; unverified marks
claims no retrieved document confirms. Parameter values are identical to those in product-spec.md.
Cells names, model-point columns and CSV headers are English lower_snake_case; German terms of art
keep their German form in prose.
Retrieval conditions. These notes were drafted with direct HTTP egress from the build
environment blocked, on WebSearch result summaries alone and a budget exhausted after eighteen
queries on this product; that policy has since been lifted and the citations were re-verified
against the primary documents on 2026-08-30. Thirty-six of the forty-three entries in
sources.md now read Retrieved: yes — including every VVG, EStG and DeckRV section these notes
rest on, read as canonical XML with its amendment Stand recorded, and the carrier wordings whose
§ numbers appear below. Four entries were reached only in part and three not at all, and each says
which it is: treat a citation here as sound where its entry says Retrieved: yes, and as a pointer
to the instrument rather than a certificate where it does not. The consequence for these notes is
unchanged, and still specific and large: the corpus establishes this product’s mechanics
thoroughly and its levels barely at all. No charge parameter and no behavioural rate was
established at any German carrier for any year, and the Rentenfaktor range R19 R24 and the
declared rate [S15] the pass did establish are not the levels this model ships, so every number
in assumption classes (b) and (c) below, and most of class (a)’s levels, is std. What the
citations do establish is the shape of each recursion, and that is what this file is for.
Model scope and conventions#
Purpose. Project gross best-estimate liability cash flows, undiscounted — premiums in; death benefits, surrender values, the Kapitalabfindung, annuity instalments and insurer expenses out — for a single-policy model point on an expected (probability-weighted) basis, together with the two account balances that make the product what it is: the Deckungskapital and the Ansammlungsguthaben.
Out of scope, and said so rather than left to be discovered. Discounting; the statutory Deckungsrückstellung REG-R54; the Zinszusatzreserve REG-R17; the RfB and the MindZV arithmetic REG-R18 REG-R19; the Sicherungsbedarf test REG-R20; the Solvency II best estimate, risk margin, SCR and MCR REG-R6; IFRS 17 REG-R55; and all taxation — every cash flow is gross of Kapitalertragsteuer, Solidaritätszuschlag and Kirchensteuer REG-R38. Also not modeled, each for a stated reason: the Bonusrente surplus system R24; Zuzahlung, which no source names (gap 15); the survivor’s-annuity and BU riders [S10] [S4]; § 163 VVG adjustment of the guaranteed Rentenfaktor R3 R17 REG-R27; and § 169 Abs. 6 R1.
Projection frequency. Monthly grid over an annual product, so the model runs on two clocks. The contract’s own natural period is the Versicherungsjahr: the Rechnungszins is credited annually, the Überschussbeteiligung is declared annually and the Ansammlungsguthaben’s interest is credited at each policy year end and on termination [S4] § 3 Abs. 6, [S15], the § 165 paid-up value is “stated in the contract for each insurance year” R2, and Kündigung and Beitragsfreistellung both take effect “for the end of the current insurance period” R1 R2. Every one of those constructions therefore keeps the policy year as its argument. What the monthly step is for is the other half of the product: the monthly annuity [S13] R24, which the annual grid could only compress and which these notes listed as a pitfall for that reason; the Rentengarantiezeit, which guarantees
12minstalments rather thanmlumps; and the decrements, which now fall in the month they happen.What
tcounts.tindexes the policy month, counted from inception, and is 0-based:t = 0is the first policy month.duration(t) = t // 12is the 0-based policy yeark, and every annual construction in this file takes thatk;policy_year(t) = duration(t) + 1is the contractual 1-based label the input files are keyed on. The attained age and the calendar year both step on the anniversary —age(t) = issue_age + duration(t),calendar_year(t) = issue_year + duration(t)— so one generational mortality surface and one declared-rate path serve a book of mixed vintages, every policy year carrying its own attained age and calendar year REG-R14 REG-R49. A new-business model point opens att = 0. An in-force model point that has already runduration_initcomplete policy years opens att_start() = 12 × duration_init, carrying its opening balances on the model point;k_start() = duration_initis the same instant on the annual clock.is_anniv(t)ist % 12 == 11, the month the annual machinery acts in.proj_len()is the exclusive end of the frame in months, per the library-wide ruling (tests/test_model_conventions_de.py), so the frame ist = t_start() … proj_len() − 1andresult_cf().index[-1] == proj_len() − 1:proj_len_y() = omega_age − issue_age the number of policy years proj_len() = 12 × proj_len_y() the frame, in months
with
omega_age = 121std the terminal age of the shipped mortality proxy, at whichmort_rate = 1. The projection therefore ends when the annuitant cannot survive further, not at a fixed horizon: a life annuity has no term, and truncating one at, say, 40 years silently drops the tail the Rentenfaktor was priced for. For the anchor cell,proj_len_y() = 121 − 50 = 71, so the frame ist = 0 … 851and the last policy year ends at attained age 121.The Rentenbeginn is the end of the deferment period of
n = aufschub_yyears, which is the end of month12n − 1and the same instant as the start of month12n. Accumulation-phase months aret < 12n; payout-phase months aret ≥ 12n. The Kapitalabfindung is paid in month12n − 1; the first annuity instalment falls in month12n.Two speeds on each decrement.
mort_rate(t)andlapse_rate(t)return the annual rate of the policy yeartfalls in — the vectors tabulated below, flat across the year’s twelve months — andmort_rate_mth(t)andlapse_rate_mth(t), each1 − (1 − r)^(1/12), are what the recursion applies. The twelfth is geometric and neverr / 12, so twelve compound back to the year’s rate exactly andpols_ifat every anniversary is what the annual grid gave. Two rates of 1 are certainties rather than rates — the terminalq = 1of the proxy, and the § 165 cash-out — and both are placed in the anniversary month instead of being twelfth-rooted, since spreading a certainty geometrically empties the cohort eleven months early.Timing conventions std. The premium at the start of the policy year, which is what § 12 Abs. 1 VVG makes the Versicherungsperiode for this tariff, so
prem_due(t)is true in the first month of each policy year and nowhere else; charges deducted immediately after; the Rechnungszins credited on the post-premium, post-charge balance at the end of the year; the declared surplus credited to the Ansammlungsguthaben at the end of the year; death and surrender at the end of each month, deaths before surrenders; the Rentenbeginn events after the decrements of month12n − 1; and annuity instalments at the start of each payout month, monthly in advance, which is what a Rente is. The ordering of premium credit, charge deduction and interest accrual is not established by any source in this corpus [S11] and is the single most consequential std in this file (processing order, and pitfall 2).What a mid-year exit is paid. The value struck at the end of the current Versicherungsperiode — § 169 Abs. 3 VVG’s zum Schluss der laufenden Versicherungsperiode, and not the cancellation date R1 — so a policy leaving in any month of policy year
kis paidcv_pp(k)ordb_pp(k), the same amount the annual grid paid. It is the consistent reading: the Beitrag was payable in advance for the whole period, so the period’s value is what the contract earned. What the monthly grid changes is when the claim falls and how many policies are exposed to it, not what it is worth.Currency, sign and rounding. EUR throughout.
net_cf(t)is income-positive (premiums +, benefits and expenses −), with the outgo-positive orientation published asliability_cf(t) = −net_cf(t). Intermediate values at full precision; displayed cash flows to euro cents,pols_ifto six decimals std.Unisex pricing is a hard constraint, not a convention. A model point carries
sexfor the decrement — the underlying DAV tables are sex-specific raw material REG-R47 REG-R49 — andsexmust not enter the premium, the charge scale or the Rentenfaktor, sex-based differences being prohibited for contracts concluded from 21 December 2012 REG-R34.
Model point attributes#
Thirty columns. model_point_table.csv is indexed by point_id and is the one input file exempt
from the provenance rule, because a model point is a configuration rather than an assumption.
The right-hand column names the points that exercise each attribute away from its base value.
Attribute |
Type |
Meaning |
Exercised by |
|---|---|---|---|
|
int |
Key; |
all |
|
str |
Label, |
all |
|
enum {M, F} |
Decrement only; never priced REG-R34 |
2, 4, 7, 9, 11, 13 are F |
|
int |
Age last birthday at inception |
all |
|
int |
Calendar year of inception; drives the generational mortality index and fixes the guarantee vintage REG-R14 REG-R49 |
6 (2005), 14 (2017) |
|
int |
Complete policy years already elapsed; 0 = new business. Already 0-based, so the frame opens at |
6 (20), 14 (8) |
|
float |
Policies represented; |
all (1.0) |
|
enum {laufend, einmal} |
Recurring or single premium [S11] REG-R53 |
2 ( |
|
EUR p.a. |
Annual Bruttobeitrag before the frequency loading |
all but 2 |
|
EUR |
Einmalbeitrag |
2 |
|
enum {annual, half_yearly, quarterly, monthly} |
Zahlweise; keys |
3 (monthly), 4 (quarterly), 5 (half-yearly) |
|
int |
Premium-paying years from inception, a count: premiums fall in policy years |
4 (20 of 22), 5 (25 of 27) |
|
int |
Deferment in years, a count: Rentenbeginn at the end of month |
all |
|
rate |
The contract’s Rechnungszins — a model-point attribute, not a global assumption R7 REG-R14 REG-R15 |
6 (2,75 %), 14 (0,90 %) |
|
str |
Key into |
6 ( |
|
EUR/month per 10 000 € |
garantierter Rentenfaktor, fixed at inception [S8] R24 |
all (levels std) |
|
str |
Key into |
5 ( |
|
str |
Key into |
14 ( |
|
EUR |
Minimum guaranteed contract value at Rentenbeginn [S9]; 0 = not stated |
13 (60 000) |
|
enum {prem_refund, deckungskapital, max} |
2, 12 ( |
|
|
int 0/1 |
Whether the Ansammlungsguthaben is added to the death benefit R24 |
4, 12 |
|
int |
Rentengarantiezeit in years R24 [S9] [S13]; the model pays |
9 (0), 11 (5), 3/14 (15), 5/10 (20) |
|
float |
Fraction electing the Kapitalwahlrecht at Rentenbeginn [S12] R6 R21 |
9 (1.00), 2/10 (0.00), 5 (0.20) |
|
int |
Contractual policy year of Beitragsfreistellung, 1-based; 0 = never R2. The contract is paid up from policy year |
7 (10), 8 (3, trips the minimum) |
|
rate |
Dynamik annual premium increase [S4]; 0 = option off |
12 (5 %) |
|
enum {konstant, teildynamisch, volldynamisch} |
4, 11 ( |
|
|
EUR |
Opening Deckungskapital |
6, 14 |
|
EUR |
Opening Ansammlungsguthaben |
6, 14 |
|
EUR |
Premiums paid before the valuation date — the Beitragsrückgewähr base |
6, 14 |
|
EUR |
Acquisition charge already amortised before the valuation date |
6 (2 592), 14 (900) |
The fourteen model points. Point 1 is the worked example’s anchor cell. Between them the points
carry both premium forms, all four payment frequencies, two in-force cells on two legacy guarantee
vintages, both charge sets, all three death-benefit forms with and without surplus, all three payout
systems, five Rentengarantiezeit durations including zero, Kapitalwahlrecht take-ups of 0 %,
20 %, 30 % and 100 %, the Dynamik, both Beitragsfreistellung branches, and four boundary cases:
the paid-up conversion that fails the Mindestversicherungsleistung and is cashed out (8), full
commutation at Rentenbeginn (9), the guaranteed Rentenfaktor binding over a lower current one
together with a binding guar_capital_pp (13), and a Rentengarantiezeit of zero (9).
One constraint the table must satisfy and the model asserts. av_spread_pp (below) is seeded
equal to av_pp_init on an in-force point, which is exact only once the acquisition charge is
amortised under both treatments, so every in-force point has duration_init ≥ alpha_spread_years = 5
std. Points 6 and 14 open at durations 20 and 8.
State variables#
Variable |
Description |
Updated |
|---|---|---|
|
|
once per model point |
|
The bridge between the two clocks: the 0-based policy year of month |
derived |
|
Attained age in month |
annual step |
|
|
annual step |
|
Policies in force at the start of month |
monthly recursion |
|
The count the annuity instalment is paid on — the annuitised count inside the Rentengarantiezeit, survivors after it, zero before Rentenbeginn |
monthly |
|
Deckungskapital per policy at the start of policy year |
annual recursion |
|
|
within policy year |
|
Ansammlungsguthaben per policy, and its within-year points |
annual recursion |
|
The parallel Deckungskapital in which the acquisition charge is spread evenly over the first five contract years — the § 169 Abs. 3 VVG floor REG-R28 |
annual recursion |
|
|
annual recursion |
|
Conversion capital struck at Rentenbeginn |
once |
|
|
once |
|
garantierte Rente, monthly, struck at Rentenbeginn |
once |
|
Überschussrente, monthly, by payout system; steps on the anniversary |
annual |
|
The instalment actually paid in month |
monthly |
|
First-order (tariff) and second-order (best-estimate) annual mortality, flat across a policy year |
lookup |
|
The monthly rates the recursion applies, each |
derived |
|
Annual surrender rate; 0 from Rentenbeginn |
lookup |
There is no paid-up sub-population state: Beitragsfreistellung is a deterministic election at a stated policy year rather than a continuous decrement, for the reason given under Policyholder behaviour modelling. There is no Bonusrente ledger in the accumulation phase, and no survivor’s-annuity state.
Assumption inputs#
The eight input files. All inputs are external CSVs beside run.py, read once per model by
the unparameterized Data Space (the annuallife/TradLife_A layout). Every file but the model point
table carries a per-row provenance column, this library’s second ruling.
File |
Index columns |
Value columns |
|---|---|---|
|
|
the thirty model-point attributes above (provenance-exempt) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
param_table.csv holds every scalar std assumption that is neither a charge nor a rate table:
expense_acq_pp, expense_maint_pp, expense_annuity_pp, expense_claim_pp, expense_infl,
mort_be_factor, mort_base_year, omega_age, val_reserve_rate, sur_ann_rate,
sur_ann_growth, sur_ann_theta and roll_fwd_tol. Keeping them in a file rather than in
Projection References is what gives each of them its own provenance tag.
(a) Contractual and guaranteed elements (cited)#
Input |
Value / rule |
Basis |
|---|---|---|
Deckungskapital recursion |
The Sparbeitrag — the premium net of the tariff costs and the Risikobeitrag — accumulated at the Rechnungszins: “indem wir die eingezahlten Beiträge abzüglich der tariflichen Kosten und Risikobeiträge mit dem tariflichen Garantiesatz von 0,90 Prozent p. a. verzinsen” |
[S8] § 1 Abs. 2; [S11] § 27 Abs. 1; ordering std |
Rechnungszins |
A model-point attribute; 1,00 % for 2026 issues, 2,75 % and 0,90 % on the two legacy points |
|
Höchstzillmersatz |
25 ‰ of the Beitragssumme from 1 January 2015 — § 4 Abs. 1 Satz 2 DeckRV, “Der Zillmersatz darf 25 Promille der Summe aller Prämien nicht überschreiten” — 40 ‰ before, the rate at conclusion applying for the whole term (§ 4 Abs. 4) |
R7; REG-R16 REG-R20; restated by four carriers as “2,5 % der Beiträge” [S1] § 14, [S4] § 11, [S8] § 8, [S9] § 16 |
§ 169 Abs. 3 surrender floor |
At least the Deckungskapital that results from spreading the charged acquisition and distribution costs evenly over the first five contract years, “die aufsichtsrechtlichen Regelungen über Höchstzillmersätze bleiben unberührt” |
R1 at article level; REG-R28; [S1] § 12 Abs. 3, [S4] § 10 Abs. 3, [S8] § 7 Abs. 3, [S9] § 16 Abs. 4, [S11] § 34 Abs. 2 |
§ 169 Abs. 5 Stornoabzug |
Permitted only if agreed, quantified and appropriate; a deduction for unamortised acquisition costs is void |
|
§ 165 paid-up value |
Computed on the premium calculation basis, on the basis of the § 169 Abs. 3–5 surrender value, and tabulated per insurance year |
|
§ 165 minimum benefit |
Below the Mindestversicherungsleistung the contract is cashed out at the surrender value including profit shares, not made paid-up |
|
Death benefit before Rentenbeginn |
Beitragsrückgewähr (premiums paid, ohne Zinsen), the accumulated Deckungskapital, or the larger of the two; optionally plus the attributable surplus; or no benefit at all where no extension is bought |
[S8] § 1 Abs. 1; [S9] § 1 Abs. 3 for the |
Conversion capital |
Includes Überschussbeteiligung and Bewertungsreserven, subject to a minimum guaranteed contract value |
[S9] |
Conversion rule |
|
[S11] § 52 Abs. 1; [S14] § 2 Abs. 5; [S18]; R24 |
Rentenfaktor applied |
|
[S9] § 1 Abs. 1; [S14] § 2 Abs. 3 and 6; [S18]; R24 |
Bewertungsreserven |
hälftige participation under § 153 Abs. 3 Satz 2 VVG, crystallised at the transition to annuity payment — § 153 Abs. 4, “Bei Rentenversicherungen ist die Beendigung der Ansparphase der nach Absatz 3 Satz 2 maßgebliche Zeitpunkt” — and continuing in payment by contract |
|
Annuity |
Monthly in advance, for life, from Rentenbeginn — “monatlich, jeweils zum Monatsersten”; garantierte Rente plus Überschussrente, only the first guaranteed, the RfB-financed part promised “jeweils nur für ein Versicherungsjahr” |
|
Rentengarantiezeit |
Payment continues until the agreed years expire, “unabhängig davon, ob die versicherte Person diesen Termin erlebt”; commutable to a present value at the payout-phase Rechnungszins |
[S1] § 1 Abs. 4; [S4] § 1 Abs. 4 and § 10 Abs. 14; [S9] § 1 Abs. 5; R17 R24 |
Surrender in payment |
None |
|
Mortality basis |
DAV 2004 R (Aggregattafel), a Generationentafel, first order carrying safety margins over second order; carriers derive their own tables from it (NÜRNBERGER Tafel 2013R, Debeka 01/21 R) |
[S4] § 1 Abs. 6, [S5] [S6] [S7] [S16]; [S9] [S11] for the derived tables; R12 R13 REG-R47 REG-R49 |
Unisex tariff |
Sex may not enter premium or benefit for contracts from 21 December 2012 |
(b) Insurer-discretionary current elements (snapshot; revisable annually)#
Everything in this class is a declaration, not a promise: the GDV model wording says so in terms —
“Die Höhe der Überschussbeteiligung hängt von vielen Einflüssen ab, die nicht vorhersehbar und von
uns nur begrenzt beeinflussbar sind. … Die Höhe der künftigen Überschussbeteiligung kann also nicht
garantiert werden. Sie kann auch Null Euro betragen.” [S1] § 2 Abs. 7. One carrier’s rates are now
established (gap 4 closed): Bayern-Versicherung’s Überschussverteilung 2026 declares the annuity
Zinsüberschussanteil for tariff generations 2015–2025 as 3 % less the Rechnungszins before the
Rentenbeginn and 3,35 % less it during the Rentenbezug, against 2,25 % and 2,5 % for 2025 [S15] —
so the total interest credited is 3,00 % for 2026. The values below are unchanged and remain
std; they are a scenario path, not that declaration, and model.md records the divergence.
Input |
Value |
Basis |
|---|---|---|
Declared laufende Verzinsung |
2,55 % p.a. level on the |
std (i) |
Surplus rate |
|
|
Interest credited on the Ansammlungsguthaben |
at |
|
Bewertungsreserven rate at Rentenbeginn |
1,5 % of the accumulated value |
|
aktueller Rentenfaktor at Rentenbeginn |
32,00 € per month per 10 000 € at age 67 on the |
|
Überschussrente rate |
12 % of the garantierte Rente, level |
|
Überschussrente growth |
1,5 % p.a. compound on the garantierte Rente |
|
|
0,5 — half the constant increment plus half the growth rate |
(i) Anchored on the only public market averages the library has: the average laufende Verzinsung
for 2025 was 2,53 % Klassik / 2,58 % Neue Klassik, and for 2026 the sources give 2,6–2,7 %, 2,87 %
and 2,54 % — three incompatible averages REG-R53. 2,55 % sits inside the 2025 pair and is a market
average, not a carrier’s declaration. A carrier’s declaration is now available and is not this
number: Bayern-Versicherung’s Überschussverteilung 2026 credits a total of 3,00 % before the
Rentenbeginn (2025: 2,25 %) and 3,35 % during the payout phase (2025: 2,5 %) on tariff
generations 2015–2025 [S15]. The shipped path stays at 2,55 % because it is a market-average scenario
rather than one carrier’s book, and because changing it moves the worked example and the golden
tests; the divergence is recorded rather than absorbed. The declared rate is the Garantieverzinsung plus the
laufende Zinsüberschussbeteiligung, never a surplus on top of the guarantee REG-R53; that is
what the max(0, ·) in bonus_rate implements, and it is why the 2,75 % legacy point receives no
interest surplus at all — a real and important German result, not a modelling artefact.
(ii) The Ansammlungsguthaben mechanic is established in a carrier’s own definition — the surpluses
are credited to an Überschussguthaben and accumulated with interest, the interest credited at each
policy year end, on termination, and for an annuity also at the start of annuity payment or on
commutation [S15]; [S4] § 3 Abs. 6 says the same and adds that the balance is paid out on death,
surrender or commutation. Neither states the rate. Crediting the full declared rate rather than the Rechnungszins is std; the
alternative is a documented variant and moves the anchor cell’s final Ansammlungsguthaben
materially.
(iii) The mechanic is now statutory rather than restated: § 153 Abs. 3 Satz 2 VVG allots the amount
determined at termination “zur Hälfte”, and § 153 Abs. 4 makes the Beendigung der Ansparphase the
relevant moment for an annuity R4; [S4] § 3 Abs. 2 and [S15] apply it. No amount, ratio or
reserve level is established anywhere.
1,5 % of the accumulated value is a placeholder sized to be visible without dominating.
(iv) See product spec footnote 9. Chosen so that the anchor cell’s max() resolves upward (the
current factor wins) and point 13’s resolves downward (the guarantee binds), because a rule with
one branch never exercised is a rule no test covers.
(v) The three systems are established and their directions are established — the constant form is
set from a whole-period projection and falls if the insurer earns less, the fully dynamic form
adjusts annually to actual surplus development, and the partial form is a stated combination of the
two R20 R24 — and two carriers now name them in their own AVB: Garantie-PLUS-Rente /
Bonus-PLUS-Rente / Bonusrente [S4] § 3 Abs. 7, and dynamische Überschussrente /
teildynamische Bonusrente [S9] § 2 Abs. 5 c). The fully dynamic form is confirmed to ratchet —
“Die jeweils erreichte Rentenhöhe kann nicht mehr sinken” [S9] — while the level form is confirmed
not to be guaranteed, the RfB-financed part being promised “jeweils nur für ein Versicherungsjahr”
[S4]. No level, rate or split is established for any of them.
(c) Behavioural and experience assumptions (the modeller’s view)#
Every input in this class is std and none has any evidence behind it (gap 20): no German lapse rate, no Beitragsfreistellung rate, no Kapitalwahlrecht take-up rate and no market Stornoquote was returned by any search for this product.
Mortality. The first-order basis is DAV 2004 R, a Generationentafel which is the property
of the Deutsche Aktuarvereinigung, is not public and is not shipped [S8] R12 R13 REG-R49.
mort_table.csv is a std proxy with the structure the real table has and none of its values:
a sex-distinct base table q_base(sex, x) for base year 2005 — the year DAV 2004 R was intended
for new business R13 — and an age-dependent annual improvement rate improve(x), combined as
mort_rate_guar(t) = q_base(sex, x(t)) × (1 − improve(x(t)))^( calendar_year(t) − 2005 )
which is the generational form the reference library requires of any annuity proxy, built the way it
recommends: a base table times a cumulative improvement factor, anchored to Destatis’s own
Generationensterbetafeln as the free redistributable analogue REG-R49 REG-R52. The proxy is
anchored so that q_base(M, 50) = 0.002000 exactly, and that anchor is stated in the model’s
Data docstring; a substitute table must preserve it if the worked example is still to close.
improve(x) is 1,5 % p.a. below age 60, grading linearly to 0,5 % at age 100 and to zero at 110
std — a deliberate simplification of the Starttrend / Zieltrend structure the German
construction actually uses, and documented as one rather than presented as a replication REG-R49.
The second-order (best-estimate) basis is the first-order one loaded:
mort_rate(t) = mort_rate_guar(t) × mort_be_factor, mort_be_factor = 1.15 [std]
The factor is above one and that is the whole point. For an annuity, prudence means assuming mortality lower than expected, so the first-order table sits below best estimate, and the safety margin runs in two dimensions — level and trend REG-R47 REG-R49. The same table is used for the accumulation-phase death benefit, which is the German peculiarity worth naming: an annuity table prices a death benefit, so that benefit is systematically under-charged relative to a death-business basis such as DAV 2008 T REG-R48, and the Beitragsrückgewähr design exists partly because it makes the mismatch immaterial — the benefit is the premiums, not a sum insured.
Lapse (Storno). lapse_table.csv, keyed by the contractual policy year (duration,
1-based, so the lookup goes through policy_year(t) = duration(t) + 1 and the twelve months of a
policy year all read the same row), and zero from Rentenbeginn:
Duration |
1 |
2 |
3 |
4–7 |
8–11 |
12 |
13+ |
payout |
|---|---|---|---|---|---|---|---|---|
|
6,0 % |
5,0 % |
4,5 % |
4,0 % |
3,5 % |
6,0 % |
3,0 % |
0 % |
The duration-12 step is the only shaped feature and it is the one with a reason: § 20 Abs. 1 Nr. 6 EStG makes half the gain taxable only where the contract has run at least twelve years and payment falls after completion of the 62nd year of life, so German Schicht-3 surrenders are suppressed approaching duration 12 and spike at it R6 REG-R45. That is the German analogue of the eight-year threshold that drives French assurance vie behaviour, and delib models it the same way frlib does — as a duration-dependent shape with the threshold named and the level std. The level of every cell is unsourced.
Election rates. kapitalwahl_rate is a model-point attribute, base 30 % std. It is not
a behavioural formula, and the notes say why: the annuitise-or-commute decision is a tax
comparison — 18 % of each instalment at the marginal rate against half the Unterschiedsbetrag
taxed once R5 R6 REG-R41 REG-R45 — and this model computes no tax, so the rate stands in for
a calculation it does not perform. pup_year is likewise a deterministic election rather than a
rate.
Expenses (all levels std; no German carrier publishes any of them, gap 14).
Input |
Value |
Note |
|---|---|---|
Acquisition |
400,00 € per policy at issue, new business only |
|
Maintenance, accumulation |
45,00 € per policy p.a., inflating; a twelfth a month |
|
Administration, payout |
30,00 € per policy p.a., inflating; a twelfth a month |
|
Settlement |
120,00 € per death, surrender or commutation event |
|
Inflation |
2,0 % p.a., compounding in policy years and stepping on the anniversary |
Charges — and the distinction from expenses is load-bearing. A charge is a deduction the tariff
makes from the premium or the Deckungskapital; it moves money inside the contract and produces
no cash flow. An expense is the insurer’s actual outgo and is a cash flow. Confusing them is
pitfall 6. charge_table.csv is keyed by (charge_id, item) so each number carries its own
provenance tag:
Item |
|
|
Basis |
|---|---|---|---|
|
0,025 of the Beitragssumme |
0,040 |
|
|
5 |
5 |
§ 169 Abs. 3 REG-R28 |
|
0,040 of each gross premium |
0,040 |
|
|
0,0020 p.a. of the Deckungskapital |
0,0020 |
|
|
0,0030 p.a. while premium-free |
0,0030 |
|
|
0,020 of the pre-deduction value |
0,000 |
conditions R1 REG-R28; level and flat, duration-free shape std — observed forms are none [S8] [S9], a flat 250 EUR [S4], or percentages tapering to nil over the last ten years [S11] |
|
30,00 € a month |
30,00 € |
§ 165 threshold R2; level std, against 25,00 € a month at two carriers [S4] [S9] |
|
0,015 of each instalment |
0,015 |
std; recorded, not applied — see pitfall 12 |
freq_load_table.csv carries the Ratenzahlungszuschlag std (gap 14): annual 1,000 (1
instalment), half-yearly 1,020 (2), quarterly 1,030 (4), monthly 1,050 (12). No retrieved wording
publishes one; every carrier refers the amounts to the Kostenausweis nach § 2 VVG-InfoV or the
Persönlicher Vorschlag [S1] § 14 Abs. 1, [S4] § 11 Abs. 1, [S9] § 16 Abs. 1, which is why this gap
is structural.
The loading is the whole of what the Zahlweise does here, and the n_instalments column stays
documentation even on a monthly grid. § 12 Abs. 1 VVG makes the Versicherungsperiode the year
where premiums are not measured in shorter periods, the Deckungskapital this model turns on is
defined at anniversaries, and § 169 Abs. 3 pays a mid-period exit the value struck at the end of
that period — so the consistent reading is a premium payable in advance for the whole year and a
value earned for the whole year, and prem_due(t) is true in the first month of each policy year
only. Modelling the instalments would require an unearned-premium convention on mid-year exits that
no source in this corpus establishes. KLV_DE_S is the contrast and the reason this is a decision
rather than an omission: there the echt / unecht distinction turns on whether the
Versicherungsperiode is genuinely monthly, two model points differ in nothing else, and the
instalment stream is therefore in the frame.
Cash flow components and recursions#
Notation, defined once and used throughout#
Symbol |
Cells |
Meaning |
|---|---|---|
|
— |
the 0-based policy-month index, |
|
|
the 0-based policy-year index, |
|
|
attained age |
|
|
deferment years; guarantee period; commutation take-up. |
|
|
policies in force at the start of month |
|
|
the count the annuity instalment is paid on, monthly |
|
|
Deckungskapital per policy at the start of policy year |
|
|
Ansammlungsguthaben per policy at the start of policy year |
|
|
the five-year-spread parallel account, and |
|
|
gross premium per policy for policy year |
|
|
zillmered and evenly-spread acquisition charge |
|
|
premium, reserve-based and risk charges |
|
|
Sparbeitrag — the premium net of what the charges take from it |
|
|
the part of the charges the premium could not meet, taken from |
|
|
Rechnungszins; declared rate; |
|
|
death benefit paid on a death in policy year |
|
|
surrender value; the tariff value net of the Stornoabzug; the § 169 Abs. 3 floor |
|
|
first-order and best-estimate mortality, and the surrender rate — all three annual, flat across a policy year |
|
|
the monthly rates the recursion applies, each |
|
|
conversion capital; the two Rentenfaktoren and the applied one |
|
|
garantierte Rente and Überschussrente, monthly amounts stepping once a year |
Rates are per annum and dimensionless; V, A, P, D, R, K are EUR per policy; every cash
flow in result_cf() is EUR for the model point as a whole.
The Deckungskapital and the Ansammlungsguthaben#
av_pp_at(k,"BEF_PREM") = V(k)
av_pp_at(k,"AFT_PREM") = V(k) + S(k) − C(k)
int_credited_pp(k) = i × av_pp_at(k,"AFT_PREM")
av_pp_at(k,"AFT_INT") = av_pp_at(k,"AFT_PREM") + int_credited_pp(k)
bonus_credited_pp(k) = b(k) × av_pp_at(k,"AFT_PREM") + d(k) × A(k)
av_sur_pp_at(k,"AFT_INT") = A(k) + bonus_credited_pp(k)
The Ansammlungsguthaben is a second, parallel account with its own credited rate, settling at
year end and at exit R24. Its interest-surplus credit is b(k) = max(0, d(k) − i) applied to the
same base the guarantee is applied to, so the two together deliver the declared laufende Verzinsung
d(k) and never more — the arithmetic the reference library names as the commonest error in
describing a German contract REG-R53.
The § 169 Abs. 3 floor, as a difference recursion#
The two accounts differ only in the acquisition charge, so the model carries the difference rather than a second full recursion:
Δ(k0) = 0
spread_diff_pp_at(k,"AFT_INT") = ( Δ(k) + α(k) − α̃(k) ) × (1 + i)
Δ(k+1) = spread_diff_pp_at(k,"AFT_INT")
av_spread_pp_at(k,"AFT_INT") = av_pp_at(k,"AFT_INT") + spread_diff_pp_at(k,"AFT_INT")
with γ and ρ deliberately taken at the same euro amount in both accounts std, which is what
makes the difference exact. Two consequences are worth stating because they are not obvious. The
difference is large in the first five years — on the anchor cell the whole 25 ‰ is taken in the
first year against one fifth of it — and it never returns to zero, because the spread account earns the
Rechnungszins on the amounts not yet deducted. So on a zillmered tariff with a positive
Rechnungszins the § 169 Abs. 3 floor is above the tariff Deckungskapital at every duration,
not only in the first five years.
Surrender, and the Beitragsfreistellung election#
surr_charge_pp(k) = stornoabzug_rate × ( av_pp_at(k,"AFT_INT") + av_sur_pp_at(k,"AFT_INT") )
R̄(k) = av_pp_at(k,"AFT_INT") + av_sur_pp_at(k,"AFT_INT") − surr_charge_pp(k)
R̲(k) = av_spread_pp_at(k,"AFT_INT")
R(k) = max( R̄(k), R̲(k) )
A surrender in any month of policy year k is paid R(k): § 169 Abs. 3 VVG strikes the value
zum Schluss der laufenden Versicherungsperiode and not at the cancellation date R1, and the
Beitrag was payable in advance for that same period.
The floor is the § 169 Abs. 3 Deckungskapital alone, because Abs. 3 speaks of the
Deckungskapital; profit shares sit on top of the statutory minimum rather than inside it, which
is the reading § 165 Abs. 2’s “surrender value … including profit shares” R2 supports. The
alternative reading, in which the floor also carries the Ansammlungsguthaben, is not implemented
and would make the floor bind at every duration; the chosen reading lets the floor bind early and
stop binding once the surplus account has outgrown the interest residual and the Stornoabzug, so
both branches of the max() are exercised on the anchor cell alone.
Beitragsfreistellung is a deterministic election in the contractual policy year pup_year,
which is policy year k = pup_year − 1, tested at the end of the preceding year:
pup_value_pp = max( av_pp_at(pup_year−2,"AFT_INT"), av_spread_pp_at(pup_year−2,"AFT_INT") )
pup_cashout = ( pup_value_pp / 10 000 × f_g ) < min_annuity_mth
If pup_cashout is false the contract continues premium-free: P(k) = 0 from k = pup_year − 1,
the Deckungskapital is reset to pup_value_pp — the § 165 rule that the paid-up benefit is
computed on the § 169 Abs. 3–5 value, on the premium basis R2 REG-R28 — the Ansammlungsguthaben
is untouched, Δ is set to zero because the two accounts have merged, and the reserve-based charge
switches to gamma_pup_rate. The uplift is real money and it is published, as
pup_uplift(pup_year − 2) = ( pup_value_pp − av_pp_at(pup_year−2,"AFT_INT") ) × l(12(pup_year − 1)),
so that the fund-level roll-forward still closes. It is booked in the transition year
k = pup_year − 2, because that is the year whose roll-forward needs it: the uplift is the step
between that year’s zillmered end-of-year balance and the reset opening balance of policy year
k = pup_year − 1, and check_av_roll_fwd() closes in every policy year only if it is credited on
the earlier of the two. On the anchor cell it is identically zero; on point 7 it is 28,39 € at
k = 8.
If pup_cashout is true the contract is cashed out instead, the whole surviving cohort leaving
in the last month of policy year k = pup_year − 2 through the surrender decrement at R(k)
R2. That is one of the two places w(k) is 1 rather than a rate, and w^m places the certainty
in the anniversary month rather than spreading it geometrically: § 165 makes the cash-out fall at
the end of the Versicherungsperiode, not a twelfth of the way into it. No Stornoabzug is applied on the paid-up route std: Abs. 5 is drafted for a payout on
Kündigung, and here the contract continues. The alternative is a documented variant.
Decrements and the in-force recursion#
Monthly, with deaths before surrenders std inside each month, which is the annual grid’s
own order taken a twelfth at a time. Accumulation phase, t < 12n:
q^m(t) = 1 − (1 − q(t))^(1/12) the year's annual rate, geometrically twelfthed
w^m(t) = 1 − (1 − w(t))^(1/12)
pols_death(t) = l(t) × q^m(t)
pols_lapse(t) = ( l(t) − pols_death(t) ) × w^m(t)
D(k) = ( prem_refund ? prem_cum_pp(k) + P(k)
: deckungskapital ? av_pp_at(k,"AFT_INT")
: max(of the two) ) + ( db_incl_surplus ? av_sur_pp_at(k,"AFT_INT") : 0 )
claims(t,"DEATH") = D(duration(t)) × pols_death(t)
claims(t,"LAPSE") = R(duration(t)) × pols_lapse(t)
l(t+1) = l(t) − pols_death(t) − pols_lapse(t) − pols_commutation(t)
Payout phase, t ≥ 12n: mortality only, no surrender, no premium, no death benefit.
pols_death(t) = l(t) × q^m(t)
pols_lapse(t) = 0
l(t+1) = l(t) − pols_death(t)
Because (1 − q^m)^12 (1 − w^m)^12 = (1 − q)(1 − w) exactly, l(12k) at every anniversary is the
annual grid’s l(k) to the last bit. What the finer grid changes is the split of a year’s exits
between the two decrements — competing monthly rather than in a fixed annual order — not their total,
and the shift is small: 16,87 € of claims from death to lapse over the anchor cell’s whole run.
Note that D(k) uses end-of-year balances while Ď(k) in the risk charge uses start-of-year
ones; they are different quantities with deliberately similar names, and the model publishes both.
Both are annual: a death in any month of policy year k is paid D(k).
The Rentenbeginn#
All of it happens at the end of the last accumulation month t = 12n − 1 — which is the end of
policy year k = n − 1 — on the survivors of that month’s decrements:
capital_gross_pp = av_pp_at(n−1,"AFT_INT") + av_sur_pp_at(n−1,"AFT_INT")
val_reserve_pp = val_reserve_rate × capital_gross_pp
K = max( guar_capital_pp, capital_gross_pp + val_reserve_pp )
f = max( f_g, f_c )
G = K / 10 000 × f
pols_surv_rb = l(12n−1) − pols_death(12n−1) − pols_lapse(12n−1)
pols_commutation(12n−1) = κ × pols_surv_rb
pols_annuitization(12n−1) = (1 − κ) × pols_surv_rb
claims(12n−1,"COMMUTATION") = K × pols_commutation(12n−1)
l(12n) = pols_annuitization(12n−1)
f_c is read from rentenfaktor_table.csv at (rf_scenario_id, issue_age + n) — the annuitant’s
attained age at Rentenbeginn. The commuting policyholders receive K, the same capital the
annuitants convert, Bewertungsreserven included [S9]: the corpus gives no basis for paying them
less, and inventing one would be a charge no source supports.
The annuity in payment#
U(k) = sur_ann_rate × G konstant
= G × ( (1 + sur_ann_growth)^(k − n) − 1 ) volldynamisch
= θ·sur_ann_rate·G + G × ( (1 + θ·sur_ann_growth)^(k−n) − 1 ) teildynamisch, θ = 0.5
annuity_pp(t) = G + U(duration(t)) one instalment, paid in advance
a(t) = 0 for t < 12n
= pols_annuitization(12n−1) for 12n ≤ t < 12n + 12m the Rentengarantiezeit
= l(t) for t ≥ 12n + 12m
annuity_payments(t) = annuity_pp(t) × a(t)
The instalment is the product’s own unit and the model now pays it as one. The timing is
established — “Wir zahlen die Rente monatlich, jeweils zum Monatsersten” [S9] § 1 Abs. 1 — and the
annual grid these notes were first written for could only compress twelve of them into one
start-of-year payment, a std that is now gone. U steps once a year, on the anniversary,
because an Überschussrente is redeclared annually.
a(t) is the mechanic the Rentengarantiezeit consists of: inside the guarantee window the
instalment is due whether or not the annuitant is alive, so it is weighted by the annuitised
count and not by survivors R17 R24, and the window is 12m instalments. Because
l(t) ≤ pols_annuitization(12n−1) throughout the payout phase,
a(t) = max(l(t), 1{12n ≤ t < 12n+12m} × pols_annuitization(12n−1)), which is how
check_annuity_guarantee() states it. Inside the window the annual and monthly grids give the same
money to the cent, the count being fixed; they part company after it, where twelve monthly
measurements of a falling count are not one annual one, and that difference is the whole −369,14 €
the conversion moved on the anchor cell.
Expenses and the cash flow statement#
base(t) = l(t) for t < 12n, a(t) for t ≥ 12n
expenses(t) = expense_acq_pp × 1{t = t_start() and duration_init = 0}
+ ( t < 12n ? expense_maint_pp : expense_annuity_pp ) / 12
× (1+expense_infl)^duration(t) × base(t)
+ expense_claim_pp × ( pols_death(t) + pols_lapse(t) + pols_commutation(t) )
net_cf(t) = premiums(t) − claims_death(t) − claims_lapse(t) − claims_commutation(t)
− annuity_payments(t) − expenses(t)
liability_cf(t) = − net_cf(t)
The administration expense is a twelfth of the year’s amount each month rather than a monthly level of its own, so a year of it on a closed cohort is exactly the annual grid’s charge; the inflation factor steps on the anniversary, because it compounds in policy years and a twelfth-rooted inflation would be a different assumption wearing the same number. A policy leaving mid-year now bears administration only for the months it was there, which is worth 23,00 € over the anchor cell’s run.
result_cf() returns a DataFrame indexed by the 0-based month t (index.name == "t"),
contiguous from t_start() to proj_len() − 1, with these columns in this order:
pols_if, pols_annuity, premiums, claims_death, claims_lapse, claims_commutation,
annuity_payments, expenses, liability_cf, net_cf
result_cf_annual() sums it into policy years, indexed by the 1-based policy_year, which is the
view the worked example above is stated on. The account movements av, av_sur, prem_to_av,
int_credited and bonus_credited are state, not cash flows, they move once a policy year, and
they live in result_pols() with the rest of the annual state: the Sparbeitrag and the two credits
move money inside the contract and never cross the boundary, and a cash flow statement whose columns
do not all sum to its bottom line is one a reader has to know which columns to skip. The six that do
cross the boundary are premiums, the three claims_*, annuity_payments and expenses, and those
six are exactly what check_net_cf() reconciles.
The published identities#
Cells |
Clock |
Identity |
|---|---|---|
|
month |
|
|
month |
|
|
month |
|
|
year |
|
|
year |
|
|
year |
|
|
year |
|
|
scalar |
|
|
month |
|
Each has a residual companion beside it, and the residual’s argument follows its cells’ clock:
check_*_resid(t) over months for the six monthly identities, check_*_resid(k) over policy years
for the four annual ones. A monthly residual for the Deckungskapital roll-forward would first have
had to invent a monthly reserve, which is the error the two-clock split exists to make impossible.
Each check_*() takes no argument and returns a bool. check_net_cf() is required of every delib
model by the library’s first ruling and is asserted in tests/test_model_conventions_de.py.
Processing order, on two clocks#
The annual block runs once per policy year k, at its start, and is the whole of steps A1 to A6.
The monthly block runs in every month t of that year, and is steps M1 to M5. Steps A1 to A6 and
M3 run only in the accumulation phase; step A7 only at the boundary; step M4 only in the payout phase.
The annual block, for k = k0 … N_y − 1:
A1. Open the year. age_y(k) = issue_age + k, calendar_year_y(k) = issue_year + k. The opening
state is av_pp(k), av_sur_pp(k), spread_diff_pp(k), alpha_cum_pp(k) and prem_cum_pp(k);
the population is pols_if(12k).
A2. Test the phase and the elections. Accumulation if k < n, payout if k ≥ n; paid_up(k) is
true if pup_year > 0 and k ≥ pup_year − 1. If k = pup_year − 1 and the contract is
converting, av_pp(k) has already been reset to pup_value_pp by A1’s recursion and
spread_diff_pp(k) to zero; pup_uplift(pup_year − 2) — booked one year earlier, on the
transition year — records the difference.
A3. Premium in advance, decomposed. prem_pp(k), then charge_acq_pp, charge_prem_pp,
charge_admin_pp and charge_risk_pp — the last two on start-of-year balances — then
charge_from_prem_pp, charge_from_av_pp and the Sparbeitrag prem_to_av_pp.
A4. Credit the Deckungskapital and accrue the guarantee.
av_pp_at(k,"AFT_PREM") = av_pp(k) + prem_to_av_pp(k) − charge_from_av_pp(k), then
int_credited_pp(k) = int_rate_guar × av_pp_at(k,"AFT_PREM") and av_pp_at(k,"AFT_INT").
This is the std ordering — premium, then charges, then interest on what is left. The
retrieved wordings fix the decomposition ([S8] § 1 Abs. 2, [S11] § 27 Abs. 1) but none of them
fixes the within-year sequence, which stays a standardization.
A5. Credit the surplus. decl_rate(k) from the declared-rate path,
bonus_rate(k) = max(0, decl_rate(k) − int_rate_guar), then bonus_credited_pp(k) to the
Ansammlungsguthaben, at year end R24.
A6. Roll the spread account and strike the year’s values. spread_diff_pp_at(k,"AFT_INT"), hence
av_spread_pp_at(k,"AFT_INT"), the surrender value cv_pp(k) and the death benefit db_pp(k).
These are what a claim in any month of the year is paid.
A7. End of month 12n − 1 — the Rentenbeginn. Strike capital_gross_pp, add val_reserve_pp,
apply max(guar_capital_pp, ·) to get K; determine
f = max(annuity_rate_guar, annuity_rate_curr); split the survivors between
pols_commutation(12n−1) and pols_annuitization(12n−1); pay claims(12n−1,"COMMUTATION");
strike G. Both account balances go to zero.
The monthly block, for t = t_start() … N − 1:
M1. Open the month. duration(t) = t // 12 selects the year’s state; the population is
pols_if(t).
M2. Look up the year’s rates and twelfth them. mort_rate_guar(t) from the generational surface
at (sex, age(t), calendar_year(t)), mort_rate(t) = mort_rate_guar(t) × mort_be_factor,
lapse_rate(t) from the duration table at policy_year(t) (zero if duration(t) ≥ n), then
mort_rate_mth(t) and lapse_rate_mth(t), each 1 − (1 − r)^(1/12).
M3. Premium, where one is due. prem_due(t) in the first month of the policy year;
premiums(t) = prem_pp(duration(t)) × pols_if(t).
M4. Payout phase, start of the month. annuity_pp(t) = G + U(duration(t)) paid on
pols_annuity(t), which is the annuitised count inside the Rentengarantiezeit and survivors
after it.
M5. End of the month — decrements, expenses, publish. Death at mort_rate_mth(t) paying
db_pp(duration(t)); then surrender on the survivors at lapse_rate_mth(t) paying
cv_pp(duration(t)). Where duration(t) = pup_year − 2, pup_cashout is true and is_anniv(t),
the whole surviving cohort leaves through the surrender decrement. Expenses: acquisition at
t = t_start() for new business; a twelfth of the year’s maintenance or annuity administration
per policy on base(t), inflated by policy year; settlement expense on the month’s deaths,
surrenders and commutations. Then pols_if(t+1), and
net_cf(t) = premiums − claims_death − claims_lapse − claims_commutation − annuity_payments − expenses, with liability_cf(t) = −net_cf(t).
Known modeling pitfalls#
These are the specific ways an implementation of this product looks right and is wrong. Each one
becomes a test in tests/test_klassische_rentenversicherung_de.py.
Adding the declared rate on top of the guarantee. The laufende Verzinsung is the Garantieverzinsung plus the laufende Zinsüberschussbeteiligung REG-R53. Assert
int_credited_pp(k) + bonus_rate(k)·av_pp_at(k,"AFT_PREM") = decl_rate(k)·av_pp_at(k,"AFT_PREM")wheneverdecl_rate(k) ≥ int_rate_guar, and that on model point 6 — a 2,75 % vintage against a 2,55 % declaration —bonus_rate(k) = 0in every policy year whileint_credited_pp(k) > 0. A model that credits 1,00 % and a further 2,55 % overstates the anchor cell’s year-one crediting by 39 % (56,82 € against 40,82 €) and its accumulated value at Rentenbeginn by 8,5 % (63 768,69 € against 58 788,98 €), the whole of the error sitting in the Ansammlungsguthaben (12 698,26 € against 7 718,55 €).Getting the within-year order wrong. Premium, then charges, then interest on the balance std. Crediting interest before the charges, or on the opening balance only, changes year-one interest by the whole of
i × (S(0) − C(0)). Assertint_credited_pp(k)equalsint_rate_guar × av_pp_at(k,"AFT_PREM")exactly, and thatav_pp_at(k,"AFT_INT") ≠ (av_pp(k))·(1+i) + S(k)on the anchor cell.Applying only the guaranteed Rentenfaktor. The rule is
max(guaranteed, current)[S4] R24. Assertannuity_rate_appl() = 32.00on the anchor (the current factor wins) and= annuity_rate_guaron point 13 (the guarantee binds), and thatannuity_rate_appl() ≥ annuity_rate_guaron every point. A model taking the guaranteed factor alone understates the anchor’s annuity by 12,5 %.Weighting the guaranteed annuity by survivors. Inside the Rentengarantiezeit the instalment is due whether the annuitant lives or not R17 R24. Assert
pols_annuity(t) = pols_annuitization(12n−1)for12n ≤ t < 12n + 12mand= pols_if(t)after, and that the two differ att = 12n + 12m − 1on the anchor. Assert also that point 9, withrgz_years = 0, haspols_annuity(t) = pols_if(t)at every payout month. The window is12minstalments, which is what a Rentengarantiezeit guarantees.Treating Beitragsfreistellung as a lapse. They are separate decrements with different consequences: the paid-up contract keeps its guarantee vintage and its guaranteed Rentenfaktor and pays a reduced benefit; the surrendered one is gone for cash R1 R2. Assert that on point 7
pols_ifis unbroken through the paid-up yeark = pup_year − 1,prem_pp(k) = 0from it,int_rate_guaris unchanged, and that the conversion itself moves no policy:lapse_ratein policy yearpup_year − 2is the ordinary duration-9 table rate of 3,5 % and not 1, andav_pp(pup_year − 1) = pup_value_pp > av_pp_at(pup_year − 2,"AFT_INT"). What is not true is that surrender ceases: a beitragsfrei contract keeps its § 168 VVG Kündigung right, soclaims_lapsestays positive from the paid-up year on — 764,60 € in policy year 10 on point 7 — and only the § 165 cash-out branch of point 8 empties the cohort, in the anniversary month of policy yearpup_year − 1.Booking the Kostenbeitrag as an expense. The charges are internal deductions that move money inside the contract;
expensesis the insurer’s best-estimate outgo. Assertexpenses(t) ≠ charge_due_pp(duration(t)) × pols_if(t)and thatexpenses(t)is invariant tobeta_rateandgamma_rate, whileav_pp(k+1)is not. Double-counting them inflates outgo by the whole charge load and is the commonest way to make a German model look conservative.Computing the surrender value off the zillmered reserve. § 169 Abs. 3 floors it at the reserve with acquisition costs spread evenly over the first five contract years REG-R28. Assert
cv_pp(k) = max(cv_tariff_pp(k), cv_floor_pp(k)), that the floor binds in the anchor’s early years and stops binding later, and that omitting it changesclaims_lapsein the second policy year by the whole ofspread_diff_pp_at(1,"AFT_INT").Letting the Stornoabzug recover acquisition costs. § 169 Abs. 5 permits a deduction only if agreed, quantified and appropriate, and voids one for unamortised acquisition costs R1 REG-R28. Assert that
surr_charge_pp(k)is a flat percentage of the pre-deduction value and carries no duration term, and thatcv_pp(k)never falls belowcv_floor_pp(k)however largestornoabzug_rateis set.Using one mortality basis where the product uses two. The first-order basis fixes the risk charge and the guaranteed benefits; the second-order basis drives the projection REG-R47. Assert
mort_rate(t) = mort_rate_guar(t) × mort_be_factorwithmort_be_factor > 1, thatcharge_risk_ppuses the annualmort_rate_guarwhilepols_deathuses the monthlymort_rate_mthderived frommort_rate, and that swapping them changesnet_cf. Assert also that1 − (1 − mort_rate_mth(t))^12 = mort_rate(t): the twelfth is geometric, and dividing the annual rate by twelve instead would leavepols_ifabove the annual grid’s at every anniversary.Using a period mortality table. DAV 2004 R is a Generationentafel; a period-table proxy priced at an annuitisation decades ahead understates the liability by a margin that dwarfs every other assumption REG-R49. Assert
mort_rate_guardepends oncalendar_year(t)as well asage(t): on the anchor,mort_rate_guarat attained age 67 in 2043 must be strictly below the same age’s rate for a life reaching 67 in 2026.Charging the risk premium on a zero net amount at risk. With
death_benefit_form = deckungskapitalthe death benefit is the reserve, so there is nothing at risk. Assertcharge_risk_pp(k) = 0in every policy year on points 2 and 12, and thatcharge_risk_pp(k) > 0in every accumulation year on the anchor, where the benefit is the premiums paid. On that cell the net amount at risk rises to a peak of 4 587,95 € atk = 5and then declines, ending the deferment at 3 204,24 € rather than at zero: with the whole 25 ‰ zillmered out of year 1, a 4 % premium charge and a 1,00 % Rechnungszins, the Deckungskapital never overtakes the premiums paid inside seventeen years, which is precisely why Beitragsrückgewähr is real risk cover on this design and not a formality. The risk charge itself rises monotonically, from 4,37 € to 15,39 €, because first-order mortality more than triples over the deferment while the amount at risk moves by a third.Deducting the payout-phase administration charge from the annuity. The Rentenfaktor is exogenous here and already carries the tariff’s payout loading, so
annuity_admin_rateis recorded incharge_table.csvand not applied. Assertannuity_payments(t) = (G + U(duration(t))) × pols_annuity(t)exactly, with no charge term, and that the model’s payout-phaseexpensesare a twelfth of the inflated per-policyexpense_annuity_ppon the exposed count plus theexpense_claim_ppsettlement cost of that month’s deaths, and nothing else. The settlement line survives into the payout phase although the death pays no benefit, because stopping an annuity and running the Rentengarantiezeit succession is administration the insurer still performs.Paying a death benefit after Rentenbeginn. The reference model pays none:
claims_death(t) = 0for everyt ≥ 12non every model point, and the test asserts it. This is now a modelling choice, not an absence of evidence. [S4] § 1 Abs. 5 offers Beitragsrückgewähr während der Rentenzahlungszeit as an alternative to the Rentengarantiezeit — premiums paid less rider premiums less annuities already received at their inception-guaranteed level, the claim extinguishing once instalments exceed premiums — and § 3 Abs. 7 accumulates the surplus attributable to it with interest until it is paid or the claim lapses. Modelling it would add a second post-Rentenbeginn benefit path and move the worked example, so it is recorded here and deliberately left out of this pass. The other two documented mechanics are the Rentengarantiezeit [S1] [S4] [S9] and the survivor’s-annuity rider, which begins only after any guarantee period expires [S10] § 1 Abs. 3.Letting the Kapitalwahlrecht leave the account behind. Commuting policyholders receive
capital_conv_pp— the same capital annuitants convert, Bewertungsreserven included [S9]. Assertclaims_commutation(12n−1) = capital_conv_pp × kapitalwahl_rate × pols_surv_rb, that both account balances are zero fromk = n, and that on point 9 (kapitalwahl_rate = 1.00)pols_if(t) = 0and every cash flow is zero fort ≥ 12n.Forgetting that the guarantee vintage is a model-point attribute. Existing contracts keep the Rechnungszins they were written on R7 REG-R14. Assert that points 1, 6 and 14 credit 1,00 %, 2,75 % and 0,90 % respectively, and that re-running point 6 on a single global 1,00 % rate moves its Deckungskapital at
Rentenbeginnfrom 82 833,38 € to 76 439,87 €, −7,7 %, while its Ansammlungsguthaben goes the other way, 3 629,35 € to 9 292,76 €, +156 %. The conversion capital barely moves — 87 759,66 € against 87 018,62 €, −0,8 % — which is the point: a single global rate leaves the total looking almost right and puts the money in the wrong account, where it carries no guarantee.Letting
sexreach the tariff. Unisex has been compulsory since 21 December 2012 REG-R34. Assert that two model points identical but forsexproduce identicalprem_pp, identicalcharge_*except throughmort_rate_guar, and identicalannuity_rate_appl().Amortising the acquisition charge against a shrunken Beitragssumme. The § 4 DeckRV base is the sum of all premiums payable under the contract as written REG-R16, not the premiums a later Beitragsfreistellung leaves behind. Assert
alpha_total_ppon point 7 is unchanged bypup_year, and thatalpha_cum_ppnever exceedsalpha_total_pp.Truncating the payout phase. A life annuity has no term;
proj_len() = omega_age − issue_age. Assertresult_cf().index[-1] == proj_len() − 1, thatpols_if(proj_len()) = 0— one past the last row — and that the decrement closure sums topols_if_init()exactly. A 40-year horizon on the anchor cell would drop a real, if small, tail of annuity payments beyond attained age 90.
Policyholder behaviour modelling#
Every formula here is std; nothing in the corpus calibrates any of it (gap 20).
Base surrender. The duration table above, with the duration-12 step at the § 20 Abs. 1 Nr. 6 EStG twelve-year threshold R6 REG-R45. The shape is the assumption; the levels are placeholders. A German Schicht-3 projection with a lapse rate flat in duration has ignored the strongest single driver of German surrender behaviour.
No dynamic surrender. The obvious German dynamic term would key the surrender rate on the gap between a market rate and the declared laufende Verzinsung, as frlib’s euro-fund model keys it on the Livret A gap. It is not implemented here, for a reason worth stating: on this product the policyholder who surrenders forfeits a guaranteed Rentenfaktor struck on bases decades old, and the value of that forfeited option is exactly what a rate-gap formula does not capture. A model that adds a naive rate-gap term to a book of 4,00 % vintages will lapse precisely the contracts a real policyholder would never surrender.
Beitragsfreistellung as an election, not a rate. A scalar per-policy account cannot carry two sub-populations with different Deckungskapital, and splitting the account would double the accumulation-phase state for a mechanic whose rate no source establishes. The model therefore carries
pup_yearas a deterministic election on the model point, exercises both of its statutory branches (conversion, and cash-out below the Mindestversicherungsleistung) on points 7 and 8, and says here that a portfolio model needs the sub-population split this one does not have.The Kapitalwahlrecht as a take-up rate.
kapitalwahl_rateis a model-point attribute, base 30 % std. The decision it stands for is a tax comparison — the Ertragsanteil at 18 % of each instalment against half the Unterschiedsbetrag once R5 R6 REG-R41 REG-R45 — and this model computes no tax, so the rate is a stand-in for a calculation it does not perform, not an estimate of one.What is deliberately absent. No Widerruf decrement (it sits inside the year-1 lapse rate, REG-R23); no premium-default path, although § 166 VVG makes German lapse a three-way decrement in reality REG-R28 REG-R30; no Wiederinkraftsetzung [S11]; no selective-lapsation mortality loading; and no take-up modelling for the Dynamik, whose parameters are unestablished (gap 15).
Worked example#
Configuration. Model point 1, point_id = 1, policy_id = DE-RV-0001: sex = M;
issue_age = 50; issue_year = 2026; duration_init = 0, so the frame opens at t = 0;
pols_if_init = 1.0; premium_form = laufend; prem_gross_pp = 3 000,00 €;
premium_single_pp = 0,00 €; prem_freq = annual, hence freq_load = 1,000; prem_term_y = 17;
aufschub_y = 17, so the Rentenbeginn falls at the end of month t = 203 — the end of the
seventeenth policy year — at attained age 67;
int_rate_guar = 1,00 %, the 2026 vintage REG-R15; charge_id = zillmer_25;
annuity_rate_guar = 28,00 € per month per 10 000 €; rf_scenario_id = base;
decl_scenario_id = base; guar_capital_pp = 0,00 €, so the guaranteed-contract-value floor is
inoperative on this cell; death_benefit_form = prem_refund; db_incl_surplus = 0;
rgz_years = 10; kapitalwahl_rate = 0,30; pup_year = 0; dynamik_rate = 0,0000;
payout_system = konstant; av_pp_init = 0,00 €; av_sur_pp_init = 0,00 €;
prem_cum_pp_init = 0,00 €; alpha_amort_pp_init = 0,00 €. Hence proj_len_y() = 121 − 50 = 71
and proj_len() = 852, so the frame is t = 0 … 851: the accumulation phase is t = 0 … 203, the
Rentengarantiezeit covers t = 204 … 323 — a hundred and twenty guaranteed instalments — and the
survivor-weighted annuity runs from t = 324 to t = 851.
Assumptions, each tagged. Contractual and cited: the Rechnungszins i = 1,00 % p.a.
REG-R15; the Höchstzillmersatz alpha_rate = 25 ‰ of the Beitragssumme REG-R16, giving
beitragssumme_pp = 17 × 3 000,00 = 51 000,00 € and alpha_total_pp = 1 275,00 €, zillmered — taken
in full from the first year’s premium and nil thereafter; the § 169 Abs. 3 five-year spread,
alpha_spread_years = 5, giving α̃(k) = 255,00 € for k = 0 … 4 REG-R28; the § 165
Mindestversicherungsleistung test, not triggered on this cell R2; the death benefit
Beitragsrückgewähr, the premiums paid to date, premiums only [S1] R24; the conversion rule
monthly annuity = capital / 10 000 × Rentenfaktor R24; and the applied factor
max(garantiert, aktuell) [S4] R24. Insurer-discretionary current: the declared laufende
Verzinsung decl_rate = 2,55 % p.a. level on the base path std, hence
bonus_rate = max(0; 2,55 % − 1,00 %) = 1,55 % p.a. std, with the Ansammlungsguthaben
itself credited at the full 2,55 % std; the Bewertungsreserven crystallisation
val_reserve_rate = 1,5 % of the accumulated value at Rentenbeginn std [S4] R4; the
aktueller Rentenfaktor at age 67 on the base path, 32,00 € per month per 10 000 € std,
which exceeds the guaranteed 28,00 € and therefore wins the max(); and the Überschussrente
under the konstant system, sur_ann_rate = 12 % of the garantierte Rente, level std.
Charges (all levels std): beta_rate = 4,0 % of each gross premium; gamma_rate = 0,20 % p.a.
of the Deckungskapital; the Risikobeitrag ρ(k) = mort_rate_guar(12k) × max(0, prem_cum_pp(k) + prem_pp(k) − av_pp(k)); stornoabzug_rate = 2,0 % of the pre-deduction value, subject to the
§ 169 Abs. 3 floor R1 REG-R28; annuity_admin_rate = 1,5 %, recorded and not applied
(pitfall 12). Behavioural and experience (all std): first-order mortality from the shipped
generational proxy, mort_rate_guar(t) = q_base(M, x(t)) × (1 − improve(x(t)))^(τ(t) − 2005),
annual and flat across a policy year, with
the anchor q_base(M, 50) = 0,002000 and improve(x) = 1,5 % below age 60 grading to 0,5 % at 100
and to zero at 110; best-estimate mortality mort_rate(t) = mort_rate_guar(t) × 1,15; surrender
6,0 % / 5,0 % / 4,5 % / 4,0 % (durations 4–7) / 3,5 % (8–11) / 6,0 % at duration 12 / 3,0 %
thereafter, and zero from Rentenbeginn; the Kapitalwahlrecht take-up 30 %; and expenses of
400,00 € acquisition at issue, 45,00 € per policy p.a. in the accumulation phase and 30,00 € p.a. in
the payout phase, both inflating at 2,0 % p.a., plus 120,00 € per death, surrender or commutation
event. omega_age = 121 std. No Dynamik, no Beitragsfreistellung, no
guar_capital_pp floor, no behavioural modules.
The frame. The model projects months; the table below is
RV_DE_S.Projection[1].result_cf_annual(), the monthly frame summed into policy years, which is the
only view on which a year’s worth of arithmetic can be read at once. Money to the cent and pols_if
to six decimals. The index is written as the 0-based policy year k = policy_year − 1, so row k
covers months 12k … 12k + 11. Rows 0–16 are the whole accumulation phase; rows 17, 26, 27, 39, 54
and 70 sample the payout at the first annuity year, the last guaranteed year, the first
survivor-weighted year and three points down the tail. av and av_sur are the two balances at the
start of the row’s year, for the model point as a whole, taken from result_pols(); they are
state rather than cash flow and they move once a year, which is why they are not columns of
result_cf().
k |
pols_if |
av |
av_sur |
premiums |
claims_death |
claims_lapse |
claims_commutation |
annuity_payments |
expenses |
net_cf |
|---|---|---|---|---|---|---|---|---|---|---|
0 |
1.000000 |
0.00 |
0.00 |
3,000.00 |
4.88 |
158.67 |
0.00 |
0.00 |
451.10 |
2,385.34 |
1 |
0.938426 |
1,517.10 |
23.28 |
2,815.28 |
9.89 |
249.12 |
0.00 |
0.00 |
47.87 |
2,508.41 |
2 |
0.889902 |
4,032.52 |
84.53 |
2,669.71 |
15.14 |
320.11 |
0.00 |
0.00 |
45.76 |
2,288.69 |
3 |
0.848216 |
6,335.16 |
179.84 |
2,544.65 |
20.70 |
362.97 |
0.00 |
0.00 |
43.99 |
2,116.98 |
4 |
0.812600 |
8,474.57 |
306.74 |
2,437.80 |
26.62 |
445.58 |
0.00 |
0.00 |
42.92 |
1,922.68 |
5 |
0.778360 |
10,439.40 |
461.52 |
2,335.08 |
32.85 |
526.51 |
0.00 |
0.00 |
41.87 |
1,733.85 |
6 |
0.745440 |
12,238.83 |
641.09 |
2,236.32 |
39.41 |
601.95 |
0.00 |
0.00 |
40.83 |
1,554.13 |
7 |
0.713787 |
13,881.56 |
842.56 |
2,141.36 |
46.41 |
588.10 |
0.00 |
0.00 |
39.48 |
1,467.37 |
8 |
0.686908 |
15,455.93 |
1,068.70 |
2,060.72 |
53.95 |
648.43 |
0.00 |
0.00 |
38.70 |
1,319.64 |
9 |
0.660906 |
16,904.27 |
1,313.89 |
1,982.72 |
61.92 |
705.09 |
0.00 |
0.00 |
37.94 |
1,177.77 |
10 |
0.635750 |
18,232.03 |
1,575.91 |
1,907.25 |
70.35 |
758.18 |
0.00 |
0.00 |
37.18 |
1,041.55 |
11 |
0.611408 |
19,444.42 |
1,852.62 |
1,834.22 |
78.94 |
1,384.79 |
0.00 |
0.00 |
37.86 |
332.64 |
12 |
0.572603 |
20,013.45 |
2,086.42 |
1,717.81 |
87.98 |
713.03 |
0.00 |
0.00 |
34.50 |
882.30 |
13 |
0.553206 |
21,091.10 |
2,390.82 |
1,659.62 |
99.18 |
752.73 |
0.00 |
0.00 |
33.97 |
773.74 |
14 |
0.534287 |
22,078.63 |
2,706.76 |
1,602.86 |
111.26 |
790.09 |
0.00 |
0.00 |
33.43 |
668.08 |
15 |
0.515827 |
22,978.50 |
3,032.52 |
1,547.48 |
124.27 |
825.12 |
0.00 |
0.00 |
32.90 |
565.19 |
16 |
0.497806 |
23,793.00 |
3,366.34 |
1,493.42 |
138.27 |
857.84 |
8,596.26 |
0.00 |
49.65 |
−8,148.61 |
17 |
0.336143 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
862.65 |
14.36 |
−877.01 |
26 |
0.311032 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
862.65 |
17.36 |
−880.01 |
27 |
0.307034 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
782.87 |
16.14 |
−799.01 |
39 |
0.229120 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
576.71 |
15.74 |
−592.45 |
54 |
0.055062 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
128.34 |
5.64 |
−133.99 |
70 |
0.000000 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
−0.00 |
Total |
— |
— |
— |
35,986.30 |
1,022.04 |
10,688.30 |
8,596.26 |
23,115.89 |
1,646.77 |
−9,082.96 |
At the start of policy year 71 the surviving fraction is 2,15 × 10⁻⁷ and every cash flow rounds to
nil; the year is kept because result_cf().index[-1] == proj_len() − 1 is a machine-checked property
of this library, and because pols_if(852) — one past the last month — is the exact zero the closure
identity below needs.
The Total row is summed over all 852 months at full precision and then rounded, not summed from
the rounded cells, and the two differ: summing the 71 rounded annual rows gives net_cf =
−9 082,97 € against −9 082,96 €. The largest single-column gap is annuity_payments, 23 115,83 €
against 23 115,89 €; claims_death and expenses each lose two cents likewise. No rounded quantity
is re-used anywhere: every row is computed at full precision and rounded once, at the point of
display.
The shape is a long positive accumulation phase, a −8 148,61 € spike in policy year 17 where the Kapitalabfindung falls on 30 % of the survivors, and a negative annuity tail running another fifty-four years. Undiscounted, the cell collects 35 986,30 € and pays out 45 069,26 €.
What the monthly grid moved, against the annual-step model this replaced. Premiums are identical — 35 986,30 € — and so is every account balance, every surrender value and the conversion capital: the whole annual layer is bit-identical, on all fourteen model points. Three columns moved, each for a reason:
Column |
Annual grid |
Monthly grid |
Why |
|---|---|---|---|
|
23 485,03 € |
23 115,89 € |
Twelve instalments paid through the year on a cohort that loses lives, instead of twelve paid at the start of it. Inside the Rentengarantiezeit the two agree to the cent — 862,65 € a year — because the count is fixed there; the whole −369,14 € is in the survivor-weighted tail |
|
1 038,91 € |
1 022,04 € |
Deaths and surrenders now compete month by month rather than once a year |
|
10 670,70 € |
10 688,30 € |
The other side of the same shift: total exits are unchanged at every anniversary, the split is not |
|
1 669,77 € |
1 646,77 € |
A policy leaving mid-year bears administration for the months it was there |
Independent checks#
These rebuild table cells a different way — from the tariff parameters rather than from the recursion — in arithmetic a reader can follow with a calculator.
Check 1 — the first policy year, k = 0, from the tariff parameters alone. The account
arithmetic is annual and nothing here reads the model:
Beitragssumme = 17 x 3,000.00 = 51,000.00
alpha_total = 0.025 x 51,000.00 = 1,275.00 (P(0) covers it: all in year 1)
beta(0) = 0.040 x 3,000.00 = 120.00
gamma(0) = 0.0020 x 0.00 = 0.00 (the account is empty)
q*(0) = 0.002000 x (1 - 0.015)^(2026-2005)
= 0.002000 x 0.72804940 = 0.00145610
rho(0) = 0.00145610 x (0.00 + 3,000.00 - 0.00) = 4.3683
charges due = 1,275.00 + 120.00 + 0.00 + 4.3683 = 1,399.3683 (all met from the premium)
S(0) = 3,000.00 - 1,399.3683 = 1,600.6317
int_credited = 0.0100 x 1,600.6317 = 16.0063
V at year end = 1,600.6317 + 16.0063 = 1,616.6380
Delta after 0 = (0.00 + 1,275.00 - 255.00) x 1.01 = 1,030.2000
cv_floor(0) = 1,616.6380 + 1,030.2000 = 2,646.8380
cv_tariff(0) = (1,616.6380 + 24.8098) x 0.98 = 1,608.6189 (the floor wins)
Every line of that is unchanged from the annual-step model, which is the point of the two-clock split. The decrements are where the month enters, and the year’s totals are a sum of twelve:
q(0) = 0.00145610 x 1.15 = 0.00167451 annual
q^m(0) = 1 - (1 - 0.00167451)^(1/12) = 0.00013965 monthly
w(0) = table duration 1 = 0.06 annual
w^m(0) = 1 - (1 - 0.06)^(1/12) = 0.00514301 monthly
deaths, mth 0 = 1.000000 x 0.00013965 = 0.00013965
lapses, mth 0 = (1.000000 - 0.00013965) x 0.00514301 = 0.00514229
deaths, yr 0 = sum over the twelve months = 0.00162796
lapses, yr 0 = sum over the twelve months = 0.05994608
claims_death = 3,000.00 x 0.00162796 = 4.8839
claims_lapse = 2,646.8380 x 0.05994608 = 158.6676
expenses(0) = 400.00 + sum of twelve x 45.00/12 x l
+ 120.00 x 0.06157405 = 451.1043
net_cf, yr 0 = 3,000.00 - 4.8839 - 158.6676 - 451.1043 = 2,385.3442
which is the table’s first row to the cent. Two things to read off it. The twelve monthly deaths sum
to 0,001628 against the annual grid’s 0,001675 and the twelve monthly lapses to 0,059946 against
0,059900 — the competing-decrement shift, and the survivors at the anniversary are the same
0,938426 either way, because (1 − q^m)^12 (1 − w^m)^12 = (1 − q)(1 − w) exactly. And the surrender
value the leavers are paid is cv_pp(0) in every month of the year, § 169 Abs. 3 VVG striking it at
the end of the Versicherungsperiode.
The load-bearing line of the annual block is the seventh: the acquisition charge takes 42,5 % of the year-one premium, leaving a Sparbeitrag of 1 600,63 €, and the § 169 Abs. 3 floor then stands 1 030,20 € above the tariff Deckungskapital at the end of the first year. That gap is the whole of Zillmerung in one line, and it is why the floor binds through policy year 4 and stops binding in policy year 5.
Check 2 — the declared rate contains the guarantee. The two credits are struck on the same base and must together be the declared rate, never the declared rate on top of the guarantee:
guarantee 0.0100 x 1,600.6317 = 16.0063170368
surplus 0.0155 x 1,600.6317 = 24.8097914070
sum = 40.8161084438
declared 0.0255 x 1,600.6317 = 40.8161084438 identical to ten decimals
A model crediting 1,00 % and a further 2,55 % puts 56,82 € into the first year instead of 40,82 €, 39 % too much; rolled forward on the same premium and charge schedule that reaches 63 768,69 € of accumulated value at Rentenbeginn against the correct 58 788,98 €, an 8,5 % overstatement sitting entirely in the Ansammlungsguthaben (12 698,26 € against 7 718,55 €). The mirror image is model point 6 below.
Check 3 — the Rentenbeginn, rebuilt from the two balances. At the end of month t = 203 — the
end of the seventeenth and last accumulation year — the Deckungskapital stands at 51 070,4278 € per
policy and the Ansammlungsguthaben at 7 718,5532 €:
capital_gross = 51,070.4278 + 7,718.5532 = 58,788.9809
val_reserve = 0.015 x 58,788.9809 = 881.8347
K = max(0.00, 58,788.9809 + 881.8347) = 59,670.8156
f = max(28.00, 32.00) = 32.00 (the current factor wins)
G = 59,670.8156 / 10,000 x 32.00 = 190.9466 EUR a month
U = 0.12 x 190.9466 = 22.9136 EUR a month
annuity_pp = 190.9466 + 22.9136 = 213.8602 EUR a month, in advance
pols_surv_rb = 0.481648 - 0.000222 - 0.001220 = 0.480205
commutations = 0.30 x 0.480205 = 0.144061
claims_commut. = 59,670.8156 x 0.144061 = 8,596.2645
annuitisations = 0.70 x 0.480205 = 0.336143 = pols_if(204)
annuity_pay = 213.8602 x 0.336143 = 71.8877 EUR a month
x 12 months of the guarantee = 862.6523 EUR in policy year 18
matching the table at policy years 17 and 18. The instalment is the product’s own unit, and the
annual figure is now a sum of twelve of them rather than a year’s annuity paid at once; inside
the Rentengarantiezeit the count is fixed, so the year’s total is the same 862,65 € the annual grid
gave and the two models agree to the cent. They part company from policy year 28, where the count is
survivors and twelve monthly measurements of it are not one annual one. Applying the guaranteed
28,00 € instead would give G = 167,0783 €, 87,5 % of the right answer and 12,5 % of the whole payout
phase lost, the annuity scaling linearly in f.
Closure, twice. The decrements account for the whole policy — summed over all 852 months at full
precision, deaths 0,371014 + surrenders 0,484924 + commutations 0,144061 + survivors at
t = 852 — one past the last month — 0,000000 = 1,000000 exactly, against
pols_if_init() = 1,000000. The survivor term is exactly zero because mort_rate is 1 in the policy
year at attained age 120 = omega_age() − 1, which is what makes proj_len_y() = omega_age() − issue_age the right horizon: a 40-year run would strand 0,219599 of a policy at attained age 90 and
silently drop most of the tail of the payout phase. And the account rolls forward — in policy year 0,
at fund level, 0.00 + 1,600.6317 − 0.00 + 16.0063 − 99.5429 = 1,517.0951 = av(1), where 99,5429 €
is the end-of-year balance of 1 616,6380 € carried out by the 0,061574 of a policy that died or
surrendered at any point in the year. The same identity closes in every policy year, including
across the Rentenbeginn, where av_release(16) is the whole balance. That is
check_av_roll_fwd(), and it is stated per policy year: the Deckungskapital is defined at
anniversaries, so a monthly residual for it would first have had to invent a monthly reserve.
One thing the table does not show, and it is the point of the Rentengarantiezeit. In policy
year 27, the last guaranteed year, pols_annuity is 0,336143 — the count that annuitised — in every
one of its twelve months, while pols_if has fallen to 0,307366 by the last of them. The instalment
is due either way, so the year’s outgo is 862,65 € and not the roughly 800 € a survivor weighting
would give. From policy year 28 the two coincide. check_annuity_guarantee() asserts it in every
month of every model point.
Variant A — the Einmalbeitrag form (model point 2)#
A 55-year-old woman paying a single 50 000,00 € premium for a twelve-year deferment to the same
attained age 67, with death_benefit_form = deckungskapital and kapitalwahl_rate = 0.00.
k |
pols_if |
av |
av_sur |
premiums |
claims_death |
claims_lapse |
annuity_payments |
expenses |
net_cf |
|---|---|---|---|---|---|---|---|---|---|
0 |
1.000000 |
0.00 |
0.00 |
50,000.00 |
76.88 |
2,891.05 |
0.00 |
451.10 |
46,580.97 |
1 |
0.938426 |
44,310.12 |
680.01 |
0.00 |
78.45 |
2,267.01 |
0.00 |
47.87 |
−2,393.33 |
11 |
0.611187 |
31,246.12 |
5,757.88 |
0.00 |
117.65 |
2,222.96 |
0.00 |
37.85 |
−2,378.46 |
12 |
0.572308 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
1,548.54 |
22.06 |
−1,570.60 |
23 |
0.532497 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
1,433.87 |
25.74 |
−1,459.61 |
39 |
0.370668 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
982.17 |
25.57 |
−1,007.74 |
65 |
0.000285 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.77 |
0.07 |
−0.84 |
Total |
— |
— |
— |
50,000.00 |
1,128.88 |
21,374.96 |
46,844.86 |
1,908.71 |
−21,257.42 |
Totals again at full precision then rounded; the rounded-cell net_cf is −21 257,45 €. Three things
read off it. The Beitragssumme is the single premium, so alpha_total_pp is 1 250,00 € and the
first year’s Sparbeitrag is 46 750,00 € — 93,5 % of the premium against the anchor’s 53,4 %, which is
the whole economic difference between the two forms. The net amount at risk is identically zero
in every policy year, the benefit being the Deckungskapital itself, so the Risikobeitrag disappears from
the decomposition. And the conversion capital is 62 913,28 € against the anchor’s 59 670,82 €, a
garantierte Rente of 201,32 € a month, on a contract that paid 50 000,00 € once rather than
51 000,00 € over seventeen years.
Variant B — the 2,75 % legacy vintage (model point 6)#
An in-force contract written in 2005 on a 2,75 % Rechnungszins and the 40 ‰ Höchstzillmersatz,
twenty policy years elapsed, so the frame opens at t_start() = 240 — policy year 21 — and the
Rentenbeginn falls at the end of month 299, the end of policy year 25.
Its opening balances are the balances this model’s own recursion produces for the same contract run
from inception, so the cell is the continuation of a projectable contract rather than a guess.
k |
pols_if |
av |
av_sur |
premiums |
int_credited |
bonus_credited |
claims_death |
claims_lapse |
claims_commutation |
annuity_payments |
net_cf |
|---|---|---|---|---|---|---|---|---|---|---|---|
20 |
1.000000 |
61,190.90 |
3,200.00 |
2,592.00 |
1,747.81 |
81.60 |
259.29 |
2,052.23 |
0.00 |
0.00 |
210.52 |
21 |
0.965315 |
63,039.53 |
3,167.78 |
2,502.10 |
1,796.18 |
80.78 |
283.18 |
2,104.86 |
0.00 |
0.00 |
45.22 |
22 |
0.931471 |
64,758.71 |
3,134.66 |
2,414.37 |
1,841.04 |
79.93 |
308.66 |
2,153.51 |
0.00 |
0.00 |
−115.50 |
23 |
0.898434 |
66,348.31 |
3,100.58 |
2,328.74 |
1,882.41 |
79.06 |
335.83 |
2,198.15 |
0.00 |
0.00 |
−271.80 |
24 |
0.866171 |
67,807.93 |
3,065.46 |
2,245.12 |
1,920.26 |
78.17 |
364.77 |
2,238.75 |
21,974.56 |
0.00 |
−22,428.44 |
25 |
0.584254 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
2,343.02 |
−2,372.27 |
49 |
0.342195 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
1,337.26 |
−1,365.93 |
78 |
0.000000 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
−0.00 |
Total |
— |
— |
— |
12,082.32 |
9,187.70 |
399.55 |
1,551.74 |
10,747.50 |
21,974.56 |
60,594.20 |
−84,230.73 |
Two columns appear here that the anchor table omits, int_credited and bonus_credited, because
their ratio is the point. Over the five remaining accumulation years the contract is credited
9 187,70 € of guaranteed interest and 399,55 € of surplus — and every euro of that surplus is the
declared 2,55 % paid on the Ansammlungsguthaben’s own balance. Not one cent is interest surplus
on the Deckungskapital: bonus_rate = max(0; 2,55 % − 2,75 %) = 0 in every policy year, because a contract
already guaranteed more than the insurer is declaring receives no interest surplus at all. A model
with one global Rechnungszins cannot produce that row; a model that adds the declared rate to the
guarantee produces its opposite. The same cell shows the other legacy asymmetry: a garantierter
Rentenfaktor of 34,00 € struck on 2005 bases beats the current 32,00 €, so the
max(garantiert, aktuell) resolves to the guarantee; and the 40 ‰ charge set carries no
Stornoabzug, so cv_pp(20) is the undeducted 68 586,25 € against a § 169 Abs. 3 floor of
65 304,65 €, inoperative twenty years in.
What changed in these notes, and why#
Five statements written before the model existed did not survive contact with it, and were corrected
here rather than worked around in the model. (i) pup_uplift moved one row earlier, to the
transition year k = pup_year − 2: the fund-level roll-forward needs it there, or
check_av_roll_fwd() fails by the whole uplift. The amount is unchanged. (ii) Pitfall 5 no
longer asserts that the surrender claim in the paid-up row is zero: a beitragsfrei contract
keeps its § 168 VVG Kündigung right, so ordinary surrender continues after a
Beitragsfreistellung — 764,60 € in policy year 10 on point 7 — and what distinguishes the election from
a lapse is that the conversion itself moves no policy. (iii) Pitfall 1’s and pitfall 11’s magnitudes were wrong for this
parameterisation and now carry the model’s own figures: the double-credit error is 8,5 % of the
accumulated value rather than “more than half” of the Deckungskapital, and the anchor’s net amount
at risk rises to 4 587,95 € at k = 5 and ends the deferment at 3 204,24 € rather than falling
towards zero. (iv) Pitfall 15’s “more than a fifth” was wrong, and wrong in an interesting
direction: re-running point 6 on a global 1,00 % rate moves its Deckungskapital at Rentenbeginn
by −7,7 % and its Ansammlungsguthaben by +156 %, while the conversion capital moves by −0,8 %.
The vintage error is a misallocation between the two accounts, not a hole in the total, which
is why it survives a reasonableness check on the headline figure. (v) Pitfall 12 overstated the
payout-phase expense: expenses(t) there is the inflated expense_annuity_pp on the exposed
count plus the expense_claim_pp settlement cost of that month’s deaths, which is a small
percentage of the line in the first payout year and grows with mortality down the tail.
Valuation and reserve pointers#
This library publishes gross best-estimate-style liability cash flows, undiscounted, on a declared grid. The valuation layers consume them and are cited, never reproduced.
The German statutory Deckungsrückstellung. The HGB reserve of § 341f HGB, computed prospectively on the Rechnungsgrundlagen of the premium calculation REG-R54 and discounted at no more than the § 2 DeckRV rate applicable when the contract was concluded REG-R14. It is not the Solvency II best estimate: an insurer carries two liability measures, and the Überschussbeteiligung, the Zinszusatzreserve and the § 139 VAG Bewertungsreserven test all run on the HGB side REG-R14 REG-R54.
av_pp(t) × pols_if(t)is this model’s contribution to that line, not the line itself — a statutory Deckungskapital is a prospective reserve on first-order bases whileav_ppis a retrospective account roll-forward, and the two coincide only under assumptions this model does not impose.The Zinszusatzreserve. Arises where the § 5 Abs. 3 DeckRV Referenzzins falls below a contract’s tariff rate, and the § 12 MindZV Sicherungsbedarf test compares a Bundesbank month-end swap rate with the highest Rechnungszins applicable to the contract over the next fifteen years — a window that bites hardest on annuity business REG-R17 REG-R18. Model points 6 and 14, on 2,75 % and 0,90 % vintages, are exactly the cells that would carry one. Not computed.
The surplus layer. The MindZV’s 90 / 90 / 50 minima are a minimum transfer to the RfB, not a minimum payout REG-R18 REG-R10 REG-R19. This model represents the credited outcome —
decl_rateand the Ansammlungsguthaben — not the three result sources that fund it; a model of the surplus chassis itself belongs in delib’skapitallebensversicherung.Solvency II. Best estimate plus risk margin REG-R6,
BEL = Σ_t v(t) × liability_cf(t)over the stream this model publishes, with the future discretionary benefits — the surplus credit and the Bewertungsreserven crystallisation — the substance of the calculation. No risk-free curve, cost-of-capital rate or contract-boundary rule in this library was read from a retrieved instrument REG-R2 REG-R4, so every such figure would be std. And the guarantees are options: themax(guaranteed, current) Rentenfaktor[S4] is a written option on the insurer’s own future annuity tariff and the Rechnungszins floor a written interest guarantee, neither of which the deterministic path prices. A stochastic-on-deterministic run — this recursion, the crediting rule and the conversion rule re-evaluated per scenario — is what a time-value-of-options-and-guarantees calculation consumes.IFRS 17 and professional standards. A profit-participating deferred annuity would be measured under the variable fee approach REG-R55, on this same fulfilment-cash-flow engine; actuarial work sits under the DAV Fachgrundsätze and the § 141 VAG Verantwortlicher Aktuar, distinct from the MaGo’s versicherungsmathematische Funktion REG-R56 REG-R11 REG-R21.
Key sensitivities and model risks#
In rough order of leverage on a German deferred-annuity block.
The Rentenfaktor, and the fact that it is not calibrated to the shipped mortality table. The annuity amount is
K / 10 000 × f, so the whole payout phase scales linearly withf, andfis std. Market anchors now exist and the shipped values sit above them: 2025 averages of 24,33–27,18 guaranteed and 27,27–30,40 current by deferment term R24, and a current-factor average of 25,97 for 2022 R19, against a shippedbasecurrent factor of 32,00 at age 67. A user recalibrating to market would lower it, and the payout phase scales linearly with the change. The reference library warns that a model publishing a std Rentenfaktor and a std annuity table must say whether the two are consistent and which is authoritative REG-R49. They are not calibrated to each other, and the Rentenfaktor is authoritative: it fixes the benefit amount, while the mortality proxy fixes only how long that amount is paid. The model publishesannuity_due_factor()— the annuity-due present value on the shipped proxy at the guarantee interest basis — purely as a diagnostic, so the gap is visible rather than hidden. Anyone substituting a real DAV 2004 R must re-strike the Rentenfaktoren with it or accept an inconsistency the model will not flag.The declared rate and the guarantee vintage together.
bonus_rate = max(0, decl_rate − int_rate_guar)is a difference of two numbers of similar size, so a 25 bp move indecl_ratemoves the Ansammlungsguthaben’s accrual by about 16 % on the 1,00 % vintage and by all of it on the 2,75 % vintage, where the rate is already clipped at zero. The declared path is a level std scenario, not a forecast; the market’s own 2026 averages disagree with each other by 33 bp REG-R53, and one carrier’s actual 2026 declaration is 3,00 % [S15] — 45 bp above the shipped path, which on the 1,00 % vintage is a 29 % change inbonus_rate.Mortality, in two dimensions. For a deferred annuity the improvement trend matters more than the level, because the conversion happens decades out: on the anchor cell the annuitant reaches 67 in 2043, 38 improvement years after the proxy’s 2005 base. Both
q_baseandimproveare std, and the trend is the more dangerous of the two — the German construction uses a Starttrend converging to a weaker Zieltrend and the proxy uses a single age-graded rate, which is a documented simplification and not a replication REG-R49.The surrender assumption, and the option it ignores. Cumulative surrender over the anchor’s seventeen accumulation years is material, and every surrendering policy forfeits a guaranteed Rentenfaktor struck on bases that will look generous by 2043. The model’s lapse rates are unconditional; a policyholder who valued the forfeited option would surrender less. The direction of the error is therefore known and one-sided.
The Kapitalwahlrecht take-up rate. At 30 % it removes nearly a third of the annuity block at
t = 16and replaces it with a single payment. It is a pure std with no evidence (gap 20), and it substitutes for a tax comparison the model does not perform R6 REG-R45.The charge set, and the § 169 Abs. 3 floor it collides with.
alpha_rateat the statutory ceiling makes the year-one Sparbeitrag small and the early Deckungskapital correspondingly thin, but the floor then reverses most of that for surrender purposes REG-R16 REG-R28, so the two parameters must be moved together.The annuity’s timing, now that the grid can carry it. The model pays the instalment monthly in advance, which is what the Rentenfaktor quotes and what the wordings describe — “Wir zahlen die Rente monatlich, jeweils im Voraus”. Neither the exact payment date within the month nor the in-advance/in-arrears basis was established [S13] R24 (gap 19), so the in-advance reading is still std; what is no longer a modelling artefact is the frequency. The annual grid this model ran on compressed the twelve instalments into one start-of-year payment, which was generous to the payout phase by roughly half a year’s interest on one year’s annuity and by a full year of survivorship on instalments a decedent did not live to collect: on the anchor cell that compression was worth 369,14 €, 1,6 % of the payout phase, and the whole of it sat outside the Rentengarantiezeit, where the count is fixed and the two grids agree to the cent.
Everything the model does not do. No Bonusrente, no Zuzahlung, no survivor’s annuity, no § 163 VVG adjustment, no Zinszusatzreserve, no MindZV allocation, no Sicherungsbedarf test, no tax. Each is named where it belongs above; together they are the reason this is a mechanics demonstration and not a valuation.