Technical Notes#
Status: Draft, 2026-08-29 (sources assembled); citations re-verified against the primary documents 2026-08-30.
Scope note. These notes specify a reference liability cash-flow projection model — model name
Index_DE_S, monthly grid — for the standardized composite German Indexpolice 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/indexpolice.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 that no retrieved document corroborates. 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.
The retrieval conditions govern these notes as they govern the specification. They were drafted
with direct HTTP egress blocked and the session’s search budget exhausted, from the authoring model’s
own knowledge of German practice; the citations have since been re-verified against the primary
documents, and 32 of the 38 entries in sources.md now read Retrieved: yes. Cap and quota
levels, charge levels and parts of the commercial envelope are now established for individual
carriers but not for the market, and no shipped level has been changed on the strength of one, so
class (b) and class (c) below remain std throughout — three of them now known to sit off the
retrieved evidence, as model.md records — while class (a) is cited to statutes read as canonical
XML and, for the index clauses, to two AVB read in full [S2] [S7]. What the model reproduces exactly
is the mechanics, and the two constructed Indexjahre of the research file are wired into the
shipped index path so that the mechanics are asserted against them cell by cell.
Model scope and conventions#
Purpose. Project gross best-estimate liability cash flows, undiscounted — premiums in; death, surrender and Rentenbeginn benefits out; insurer expenses — for a single-policy model point on an expected (probability-weighted) basis, together with the two state variables that make this product what it is: the Deckungskapital and the Höchststandsicherung ledger of locked-in credits.
Out of scope, and said so. Discounting; the Deckungsrückstellung and the Zinszusatzreserve REG-R14 REG-R17; Solvency II technical provisions, risk margin and SCR REG-R1 REG-R2; the RfB and the MindZV minimum-allocation arithmetic itself REG-R18 (the model consumes a declared rate, it does not derive one); the Schlussüberschussanteil and the Bewertungsreserven share REG-R9 REG-R24; the Rentenphase, which is
products/sofortrente/; Beitragsfreistellung as a decrement; Dynamik; and tax of any kind.The accumulation phase only. The projection runs from inception (or from the valuation date for an in-force point) to Rentenbeginn, where the capital falls due as a single terminal amount. Whether it is taken as a Kapitalabfindung or converted at the Rentenfaktor changes what is reported, not the terminal cash flow: either way the capital leaves the accumulation contract at the end of the last policy year,
k = proj_len_y() − 1, in the frame’s last month.Projection frequency: monthly, over a contract whose own clock is annual. The Indexjahr is twelve months, the surplus is declared once a year, the Wahlrecht is exercised once a year and the credit is struck once a year — so every one of those constructions keeps the policy year as its argument, and the model runs on two clocks. What the monthly step is for is the Indexjahr itself: its twelve monthly observations were always the mechanic, and on the annual grid they lived inside a single cells, read from a wide external table and invisible from the frame.
index_month(t),index_return_mth(t)andindex_return_capped_mth(t)now put them on the frame, one row each, so capped above and not floored below can be read month by month. The grid also dates the forfeiture — a surrender in month 7 of an Indexjahr loses that year’s credit — where the annual grid silently gave every exit the favourable year-end date. What it does not change is the settlement: the credit is struck at the year end and nowhere inside it, because that is the contract. (FRV_DE_Sis monthly for a different reason again: a unit account genuinely is priced monthly, and its charge cliff falls at month 61.)The frame is 0-based and
tcounts policy months from issue.duration(t) = t // 12is the 0-based policy yeark, and every annual construction in this file takes it; the contractual policy year is the 1-based labelk + 1and is derived, never indexed by. The attained age steps on the anniversary,age(t) = entry_age + duration(t), andis_anniv(t)ist % 12 == 11, the month the annual machinery acts in. A new-business point starts att = 0; an in-force point starts att_start() = 12 × dur_init,dur_initbeing a 0-based count of completed policy years, so the conversion is a multiplication and no model point column changed;k_start() = dur_initis the same instant on the annual clock.result_cf()is indexedt_start() … proj_len() − 1, contiguous, and its firstpols_ifvalue ispols_if_init().proj_len()is the exclusive end of the frame in months,proj_len() = 12 × proj_len_y()withproj_len_y() = ann_start_age − entry_agethe number of policy years, soresult_cf().index[-1] == proj_len() − 1— the library’s reading ofproj_len()and lifelib’s ownfor t in range(proj_len()), asserted in the conventions suite. It is not the row count of an in-force point: one atdur_init = 8publishes324 − 96 = 228rows and still reportsproj_len_y() = 27.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 — and with it the account, every Indexgutschrift, the Höchststandsicherung ledger and the guaranteed capital.The Indexjahr is aligned with the policy year std. The contractual Indexstichtag need not fall on the policy anniversary, and no carrier’s convention was established. The model has no other defensible alignment, and the misalignment would change the calibration of the Cap rather than the mechanic.
index_month(t) = t % 12 + 1is the alignment stated as arithmetic.Timing conventions std. The premium at the start of the policy year — annual in advance, 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; the premium charges deducted as the premium is credited; the reserve charge and the guaranteed interest struck on the post-premium balance, once a year; the insurer’s acquisition expense in the frame’s first month and a twelfth of the year’s maintenance each month, on the opening in-force of that month; decrements and benefits at the end of each month; the Indexjahr credit at the end of the policy year, to the survivors of all twelve months only.What a mid-year exit is paid. The policy year’s own amount: a death in any month of policy year
kis paiddb_pp(k)and a surrendercv_pp(k), both struck onav_pp_at(k, "AFT_GUAR")— the account before that year’s credits, because the payoff exists only at the Indexjahr end. The account of this tariff is defined at anniversaries and nowhere between them, so the monthly grid moves when the claim falls and how many policies are exposed to it, not what it is worth.Age basis age last birthday std, the basis the delib registry fixes for the whole library. Currency EUR. Sign:
net_cf(t)is income-positive (premiums +, benefits and expenses −), with the outgo-positive orientation published asliability_cf(t) = −net_cf(t). Rounding: full precision internally, displayed money to the cent andpols_ifto six decimals std.Unisex pricing, sex-distinct best-estimate mortality. Sex may not be a rating factor REG-R34, and it is not: no premium, charge or benefit in this model depends on
sex. It selects the best-estimate mortality row only, and mortality here is a timing assumption — the death benefit is the account value with a floor, not a sum insured — so the choice moves the result very little.
Model inputs — the external CSVs#
Inputs are external files in the model folder’s parent, read once per model in the unparameterized
Data Space (the annuallife/TradLife_A layout). Every file but model_point_table.csv carries a
final provenance column, one tag per row — delib’s second ruling, asserted by the conventions suite.
File |
Index columns |
Value columns |
|---|---|---|
|
|
the 22 model-point attributes below (exempt from |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Three index paths are shipped, all std, and their construction is stated so that a reader can rebuild them exactly rather than take them on trust:
eqidx_vol17— the broad equity price index case. Monthly returns drawn fromnumpy.random.default_rng(20260829).normal(0.0060, 0.0500, size=(40, 12)), rounded to four decimal places: monthly mean 0,60 % and monthly standard deviation 5,00 %, i.e. an arithmetic 7,2 % a year at an annualised 17,3 % — the research file’s own volatility assumption and a plausible level for a broad European equity index. The 17,3 % figure stays unverified — no index rulebook or realised- volatility series was retrieved for the EURO STOXX 50 or any other underlying — but the index itself is now a named one at a retrieved carrier [S2]. Rowsk = 8andk = 9— policy years 9 and 10 — are then overwritten with the research file’s constructed Example A and Example B (below), so that the two examples the whole mechanic turns on are reproduced by the model rather than restated in prose.houseidx_vol5— the volatility-targeted house multi-asset index case, fromnumpy.random.default_rng(20260830).normal(0.0025, 0.0144, size=(40, 12)), rounded to four decimal places: 0,25 % a month at an annualised 5,0 %, the volatility target the research file records for this design generation. The 5 % target stays unverified, and now for a stated reason: two German house multi-asset indices are named in retrieved carrier documents — R+V’s Solactive Multi Anlage Stabil Index (SOMAS) and the Stuttgarter M-A-X Multi-Asset Index [S7] [S8] — and neither publishes a volatility target or an index-level fee on the pages that describe it. The path carries a 6,00 % monthly Cap and a 100 % Partizipationsquote, because a low-volatility underlying is cheap to buy options on and that is the design’s selling point; note that the one published house-index quota is Stuttgarter’s 70 % [S8], so 100 % is generous against the single observation available.zero_path— every monthly return exactly zero, for every year. It is not a scenario; it is an instrument: it isolates the guaranteed accumulation, makes every Indexjahr credit exactly 0,00 €, and lets the Beitragsgarantie floor at Rentenbeginn be tested where it actually binds.
Example A (k = 8, policy year 9), monthly returns in per cent:
1,80 / −2,40 / 4,60 / 0,90 / −3,70 / 2,20 / 3,40 / −1,10 / 0,40 / 5,20 / −0,80 / 2,60.
Example B (k = 9, policy year 10):
6,50 / −2,10 / 5,80 / −1,90 / −2,40 / 4,20 / −3,10 / 0,60 / −2,80 / 5,10 / −1,70 / −1,20.
Model point attributes#
Twenty-two columns. The last column of the table says which model points exercise the attribute away from its base value, so that no column is carried without being tested.
Attribute |
Type |
Meaning |
Exercised by |
|---|---|---|---|
|
int |
key; model point 1 is the worked example’s anchor cell |
all |
|
str |
label, |
all |
|
enum {M, F} |
selects the best-estimate mortality row; never a rating factor REG-R34 |
3, 5, 7, 10, 12 |
|
int |
age last birthday at inception; |
3–13 |
|
int |
completed policy years at the valuation date; 0 = new business; the frame starts at |
8, 13 |
|
float |
policies represented at |
— (1.0 everywhere) |
|
int |
attained age at Rentenbeginn; |
6 |
|
enum {level, single} |
laufender Beitrag or Einmalbeitrag |
7 |
|
EUR |
the annual-mode premium (the Jahresbeitrag), or the single premium |
5, 6, 7, 8, 12, 13 |
|
enum {annual, half_yearly, quarterly, monthly} |
payment frequency; drives |
4, 5, 6 |
|
int |
Beitragszahlungsdauer in policy years, ≤ |
6, 7, 9, 13 |
|
EUR |
Deckungskapital per policy at the valuation date; 0 for new business |
8, 13 |
|
EUR |
the Höchststandsicherung ledger already accumulated — credits made before the valuation date |
8, 13 |
|
EUR |
premiums already paid, on the annual-mode basis; the base of the Beitragsgarantie so far |
8, 13 |
|
float |
Garantieniveau: the Beitragsgarantie as a fraction of the Beitragssumme |
6, 9, 12 |
|
float |
the contract’s Rechnungszins; a cohort fact, not today’s rate REG-R15 |
8, 13 |
|
enum {cap, quote} |
Cap design or Partizipationsquote |
2, 3 |
|
str |
key into |
3, 9 |
|
str |
key into |
10, 11, 12 |
|
float |
Mindesttodesfallschutz floor on the death benefit, as a fraction of the Beitragssumme REG-R45 |
13 |
|
enum {annuity, cash} |
whether the terminal capital is reported as an annuity or as a Kapitalabfindung |
7 |
|
int {0, 1} |
whether the contractual Stornoabzug applies |
13 |
The thirteen model points. Between them they cover both premium forms, all four payment frequencies, both payoff designs, all three index paths, all four election paths, both Kapitalwahlrecht elections, both death-benefit forms, both Stornoabzug settings, two in-force points, four Rechnungszins cohorts and four Garantieniveaus.
# |
Configuration, in one line |
|---|---|
1 |
Anchor. M 40 → 67, 2 400,00 € a year annually for 27 years, Cap on |
2 |
Anchor with |
3 |
F 40 → 67, |
4 |
M 35 → 67, monthly premiums (32 years) — the Ratenzahlungszuschlag at 5 % |
5 |
F 45 → 67, quarterly premiums of 3 600,00 € a year (22 years) |
6 |
M 50 → 65, half-yearly premiums of 6 000,00 € a year, 80 % guarantee (15 years) |
7 |
F 55 → 67, Einmalbeitrag of 50 000,00 €, |
8 |
In-force. M 40 → 67 at |
9 |
Boundary — the guarantee binds. M 55 → 67, 100 % Beitragsgarantie, |
10 |
F 40 → 67, |
11 |
M 40 → 67, |
12 |
F 30 → 67 (37 years, the longest), 1 800,00 € a year, 60 % guarantee, |
13 |
In-force, boundary. M 45 → 67 at |
State variables#
Variable |
Description |
Updated |
|---|---|---|
|
the number of projected policy years; |
once per model point |
|
the first projected policy year, |
once per model point |
|
the bridge between the clocks: the 0-based policy year of month |
derived |
|
attained age in month |
annual step |
|
policies in force at time |
monthly recursion |
|
the within-month points of the same count: |
within month |
|
the survivors of all twelve months of policy year |
once a policy year |
|
Deckungskapital per policy at time |
annual recursion |
|
|
within policy year |
|
the same balances at fund level, |
within policy year |
|
cumulative annual-mode premiums paid to time |
annual, non-decreasing |
|
the Höchststandsicherung ledger: every index and safe-arm credit made, cumulated |
annual, non-decreasing |
|
the Beitragsgarantie, |
annual, non-decreasing |
|
the guaranteed capital, |
annual, non-decreasing |
|
the shadow Deckungskapital on a five-year acquisition-cost spread, with the same within-year timings — the § 169 Abs. 3 floor REG-R28 |
annual recursion |
|
|
annual |
There is no unit account, no unit price and no fund value anywhere in this model, and that is a product fact rather than a simplification: the capital is in the Sicherungsvermögen REG-R7 and the policyholder’s claim is measured in euros. There is likewise no paid-up sub-population: German lapse is a three-way decrement REG-R28 and the reference implementation models surrender only (below).
Assumption inputs#
Three classes are distinguished. (a) is contractual or statutory and is cited; (b) is the
insurer’s current discretionary scale, redetermined annually; (c) is the modeller’s view of
experience. On this product classes (b) and (c) carry almost the whole result, and every entry in
both is std — not because retrieval established nothing, but because no shipped level was
changed on the strength of what it established; model.md records each comparison.
(a) Contractual / guaranteed elements (cited)#
Input |
Value / rule |
Basis |
|---|---|---|
Guaranteed rate |
|
|
Payoff, Cap form |
|
mechanic firm [S2] [S5]; levels std |
Payoff, Quote form |
|
mechanic firm; level std |
Base of the participation |
|
confirmed by clause — [S2] Ziffer 3.3 Absatz 2 e), [S7] § 3 Ziffer 2; spec footnote 14 |
Höchststandsicherung |
a credit, once made, is permanently added to the guaranteed capital and enters |
mechanic firm |
Guarantee at Rentenbeginn |
|
|
Death benefit |
|
shape [S7] § 1 Ziffer 5 (Policenwert, min. 90 % of premiums), not [S9], whose default is no death benefit at all; floor std R14 REG-R45 |
Surrender value |
|
|
§ 169 Abs. 3 floor |
the Deckungskapital with acquisition costs spread evenly over the first five contract years |
|
Höchstzillmersatz |
acquisition charge capped at 25 ‰ — “Der Zillmersatz darf 25 Promille der Summe aller Prämien nicht überschreiten”, DeckRV § 4 Abs. 1 |
|
No credit in the year of exit |
death, surrender and (for the year’s decrements) the terminal year forfeit the running Indexjahr |
confirmed by clause — [S2] Ziffer 3.3 and 9.2, [S7] § 3 Ziffer 5; spec footnote 18 |
Ratenzahlungszuschlag |
annual 1,000; half-yearly 1,020; quarterly 1,030; monthly 1,050 |
|
Rentenfaktor |
|
chassis fact; level std |
Selbsttötung |
three-year exclusion; not modeled — the death benefit is a return of capital, so it is close to inoperative |
(b) Insurer-discretionary current elements (snapshot; redetermined annually)#
Input |
Value |
Basis |
|---|---|---|
Declared surplus rate |
2,50 % a year of |
std (1) |
Monthly Cap |
3,00 % on |
std (2) |
Partizipationsquote |
60 % on |
std (2) |
Mindest-Cap, minimum budget |
none — neither appears in either retrieved AVB, and both instead exclude the participation for a year in which the guarantee binds |
[S2] Ziffer 3.5, [S7] § 2 Ziffer 1; spec footnote 17 |
Current Rentenfaktor |
25,00 €, equal to the guaranteed factor in the base run |
std (3) |
Stornoabzug |
2 % of the base surrender value |
std, spec footnote 27 |
Cap announced before the election deadline |
yes — parameters notified at least 3 weeks before the Indexstichtag, election due 7 days before |
[S2] Ziffer 3.1; spec footnote 16 |
The declared rate is the option budget R8 REG-R18. It sits below the 2026 evidence: Assekurata’s survey puts the index-segment average at 3,07 % and classic private annuities at 2,62 % R20 REG-R53, and Stuttgarter publishes 2,16 % for its own safe arm [S8]. The value is a shipped input and is not changed in a provenance pass; the effect is a proportional understatement of every index credit. The retrieved AVB also define the budget more widely than the model does — the declared surplus plus the year’s minimum share of the Bewertungsreserven, and at Allianz net of Verwaltungskosten ([S2] Ziffer 3.3 Absatz 1, [S7] § 3 Ziffer 9) — so
opt_budget_ppis a lower bound on the contractual budget in two independent ways. Holding it level is the strongest simplification in this file. In reality the rate moves with the investment result and with the Zinszusatzreserve releases behind it REG-R17; and the feedback from the Garantieniveau through the asset mix to the declared rate — the whole design logic of Neue Klassik — is not modeled at all, so model point 9’s 100 % guarantee credits the same declared rate as the anchor’s 90 %, which a real insurer would not do.One carrier-published cap level is now on record — Allianz’s illustrative 3,2 % [S5] — but no market panel is R21. The Cap and the budget are not independent parameters, and the AVB says so: it is set annually on quotes from several financial institutions, given the surplus, the Bewertungsreserven Sockelbetrag, volatility and the index’s dividend yield [S2] Ziffer 3.3 Absatz 2 b). Concretely: the Cap is the level at which the option strip costs the budget. The research file’s own arithmetic, at monthly
μ = 0,60 %andσ = 5,00 %, gives an expected annual credit of about 2,97 % against a 2,50 % budget with a 65 % probability of a zero year; under a risk-neutral drift on a price index it prices the same strip at about 1,7 % ofG, below the 2,50 % budget — so the shipped pair (3,00 %, 2,50 %) is not mutually consistent, and at that volatility a 2,50 % budget would buy a cap somewhat above 3,00 %. The model therefore publishes a diagnostic,index_budget_ratio()= total index credits ÷ total option budget over the projection, and the worked example reports it. A value far from 1 means the pair is off, and the sensitivity section says which way.Setting the two factors equal keeps the max-of-two rule exercised by a test rather than by the base path, so a reader can see that the rule is implemented without the base run depending on it.
(c) Behavioural / experience assumptions (the modeller’s view)#
Every input in this class is std. No German insurer publishes a mortality basis, a lapse rate, an expense loading or an election distribution for this product, and no index-specific Stornoquote exists at all.
Mortality. The market-standard bases are proprietary: DAV 2008 T for death cover and DAV
2004 R, a Generationentafel in attained age and calendar year, for every annuity promise
REG-R48 REG-R49. They are the Deutsche Aktuarvereinigung’s property, are not public and are not
redistributed here. The shipped mort_table.csv is a std Gompertz-form proxy:
qx(M, x) = 0.001200 × 1.095^(x − 40), qx(F, x) = 0.65 × qx(M, x), ages 20 … 100
The anchor a substitute table must preserve is qx(M, 40) = 0.001200 exactly, so the worked example
still closes. Two properties of the real bases the proxy deliberately does not have, and a user
replacing it should know which: it is a period table, not a generational one, so it understates a
long-deferred annuitisation REG-R49; and it carries no selection effect. Neither matters much here,
because mortality in this model is a timing assumption, not an amount assumption — the death benefit
is the account value with a floor — but both matter greatly to the Rentenfaktor the terminal capital
buys, which is why the Rentenfaktor is a std input rather than a computed one.
Lapse. The GDV publishes one market-wide Stornoquote by count for all Hauptversicherungen together — 2,56 % in 2023, 2,51 % in 2022 — with no product split, no duration split and no index line at all R19, so the two irreconcilable market-wide figures this note previously weighed against each other are not the published series; the published series is a single number that cannot be disaggregated. No index-specific rate exists, and none is derivable: the tag stays because the only retrievable statistic is aggregated past the level the model needs. BaFin adds a supervisory observation but no figure — some products stand out with “sehr hohen Stornoquoten … speziell in den ersten Jahren nach Vertragsabschluss”, which is a comment on the shape delib models rather than a calibration of it R17. The research file’s own std is a level 3 % a year. delib refines that to a duration shape, because a shape flat in duration ignores the strongest single driver of German surrender behaviour — the duration-12 and age-62 double threshold of § 20 Abs. 1 Nr. 6 EStG, at which only half the Unterschiedsbetrag becomes taxable and at the personal rate rather than by final withholding R14 REG-R45:
Policy year ( |
1–2 |
3–11 |
12 |
13 … n−1 |
n |
|---|---|---|---|---|---|
Index |
0–1 |
2–10 |
11 |
12 … n−2 |
n−1 |
|
5 % |
3 % |
6 % |
2 % |
2 % |
|
5 % |
3 % |
6 % |
2 % |
0 % |
lapse_table.csv is keyed on the model’s own 0-based policy year, read through duration(t), so its
first row is k = 0 and the tax step sits at k = 11. The mean over the anchor’s 27 years is about 2,6 %, so the level stays inside the
research file’s std while the shape carries the tax threshold. In the final period the applied
rate is zero std: the end of period n − 1 is Rentenbeginn, so a lapse and a maturity are the
same event at the same instant, and the whole surviving cohort is booked as a maturity. Unlike frlib’s term
product, where the two paid the same nothing, here they pay different amounts — the surrender value
carries the Stornoabzug and forfeits the running Indexjahr, the maturity value carries neither and
takes the guarantee floor — so this convention moves real money and is not merely a bookkeeping split.
A behavioural incentive the annual grid quietly assumed away, and the monthly grid does not. With no credit in the year of exit, the product rewards surrendering just after an Indexjahr end and penalises surrendering just before one. An annual grid put every exit at a year end and so implicitly gave each the favourable date; here a surrender in month 7 of an Indexjahr is a row of the frame and the eleven unfavourable months are as real in the projection as the twelfth. What is still not modelled is a behavioural response to the incentive — the surrender rate is unconditional — and that remains a stated model risk. The premise is a clause rather than an assumption: both retrieved AVB credit the participation only at the start of the following Indexjahr and neither refunds the unspent budget on a mid-year exit ([S2] Ziffer 3.3 and 9.2, [S7] § 3 Ziffer 5). Allianz’s AVB also has an incentive of its own that delib does not model: the Stornoabzug falls away in the last year of the Aufschubdauer and in the last seven where the insured is at least 55 and the contract at least ten years old, which pushes surrender towards the end of the term rather than towards an Indexjahr boundary.
The Wahlrecht election path is the behavioural assumption unique to this product. It is a
policyholder election, so it belongs in class (c) and not in class (b). Real policyholders in this
family are widely believed to be inert — to elect once and never revisit — which if true makes the
annual right far less valuable than its description suggests; no election distribution is
established unverified, and none is published: the AVB prescribe the mechanism, not the take-up.
What the AVB do settle is the default on inertia, and it is not neutral. R+V’s contract “nimmt
grundsätzlich an der Indexpartizipation teil” [S7] § 2 Ziffer 1, so silence keeps the policyholder in
the index arm. Allianz’s default is a two-branch rule: the previous split rolls over if index
participation was at least 50 %, but a contract at 25 % or 0 % — or one for which the participation was
excluded — is moved to 50 % index participation on silence, with the split across indices taken from
what other IndexSelect policyholders with the same Indexstichtag most often chose [S2] Ziffer 3.2.
delib’s w paths are exogenous and model neither default, which for a book-level projection is a
real simplification: an inert population does not stay where it was put. Four std paths are
shipped: always_index (w = 1, the base run, because a base run in the safe
arm would reduce the product to RV_DE_S), always_safe (w = 0), half_half (w = 0,5) and
switch_at_15 (w = 1 through policy year 15, i.e. t ≤ 14, then 0).
Expenses (insurer outgo) and contractual charges (deductions from the account). These are two
different things and the model keeps them apart: a charge reduces the policyholder’s Deckungskapital,
an expense is the insurer’s cash outgo in net_cf. All levels std.
Input |
Value |
Note |
|---|---|---|
Acquisition charge |
2,5 % of the Beitragssumme, spread over |
at the Höchstzillmersatz REG-R16 |
Acquisition expense |
2,5 % of the Beitragssumme, incurred in full at inception |
the Zillmer strain: paid out at once, recovered over five years |
Premium charge |
3 % of each gross premium collected |
Verwaltungskosten |
Reserve charge |
0,25 % a year of the post-premium balance |
Verwaltungskosten |
Maintenance expense |
36,00 € per policy a year, inflating at |
Stückkosten |
Claim expense |
none — not modeled |
no source, and immaterial beside the benefit |
The charge income (β plus γ plus the amortised acquisition charge) and the expense outgo are
deliberately of the same order, so the Kostenüberschuss is small. The model does not close the
MindZV loop: it does not compute a cost result, take 50 % of it and add it to the declared rate
REG-R18. The declared rate is exogenous, and a user who changes the expense assumptions changes
net_cf without changing the surplus the policyholder receives. That is a stated limitation, not an
oversight.
Cash flow components and recursions#
Notation (defined once, used throughout)#
Symbol |
Cells |
Meaning |
|---|---|---|
|
— |
the 0-based month index, |
|
|
the 0-based policy year, |
|
|
which month of its Indexjahr |
|
|
attained age in month |
|
|
policies in force at time |
|
|
the Ratenzahlungszuschlag multiplier |
|
|
the annual-mode premium due in policy year |
|
|
Beitragssumme = |
|
|
the acquisition charge on the tariff spread and on the five-year spread |
|
|
the premium-based administration charge |
|
|
the premium credited to the account, |
|
|
Deckungskapital per policy at time |
|
|
the reserve charge rate; the amount charged, |
|
|
the guaranteed rate; the guaranteed interest, |
|
|
the participating capital of the Indexjahr of policy year |
|
|
the declared surplus rate; the fraction of it directed to the index arm |
|
|
the month’s index return; the monthly Cap; the Partizipationsquote |
|
|
the same return read on the monthly frame, |
|
|
the month’s capped return, on the frame |
|
|
|
|
|
|
|
|
the Indexrendite: |
|
|
the Indexgutschrift |
|
|
the option budget, |
|
|
the Höchststandsicherung ledger, |
|
|
cumulative annual-mode premiums paid; the guaranteed capital, |
|
|
the annual death and surrender rates of the policy year |
|
|
the monthly rates the recursion applies, each |
|
|
the death benefit, the surrender value, the benefit at Rentenbeginn (non-zero only at |
q_d and w_l are dimensionless annual probabilities and q^m_d, w^m_l their geometric twelfths;
every other quantity above is EUR per policy, and the aggregate of any per-policy amount is that
amount times the count it is struck on.
The account, and the Indexjahr inside it#
av_pp_at(k, "BEF_PREM") = A(k)
av_pp_at(k, "AFT_PREM") = A(k) + P⁺(k)
F(k) = γ · av_pp_at(k, "AFT_PREM")
av_pp_at(k, "AFT_CHARGE") = av_pp_at(k, "AFT_PREM") − F(k)
I(k) = i_g · av_pp_at(k, "AFT_CHARGE")
av_pp_at(k, "AFT_GUAR") = av_pp_at(k, "AFT_CHARGE") + I(k)
av_pp_at(k, "AFT_CREDIT") = av_pp_at(k, "AFT_GUAR") + X(k) + U(k)
A(k+1) = av_pp_at(k, "AFT_CREDIT")
All of it takes a policy year, and that is the one thing the monthly grid deliberately does not refine: this tariff defines no monthly Deckungskapital, credits the guaranteed interest per Versicherungsjahr, and settles the Indexjahr only at its end. A model that interpolated any of those would be inventing a rule no wording states.
with the Indexjahr struck on the opening balance:
G(k) = A(k) ← before this year's premium
B(k) = w(k) · b(k) · G(k) the option budget, spent
U(k) = (1 − w(k)) · b(k) · G(k) the safe arm, credited
S(k) = Σ_{m=1..12} min( r(k,m), C(k) ) capped above, not floored below, SUMMED
Y(k) = Π_{m=1..12} ( 1 + r(k,m) ) − 1
ρ(k) = max( S(k), 0 ) if payoff_form == "cap"
= max( q(k) · Y(k), 0 ) if payoff_form == "quote"
X(k) = ρ(k) · w(k) · G(k)
The twelve months of that sum are now rows of the frame. index_month(t) = t mod 12 + 1 names
which month of the Indexjahr month t is, index_return_mth(t) = r(duration(t), index_month(t)) is
its return and index_return_capped_mth(t) its capped return, so
S(k) = Σ_{t = 12k .. 12k+11} index_return_capped_mth(t)
holds by construction and is asserted in the test module. On the annual grid the same arithmetic lived inside a single cells and could not be read off the projection at all; here capped above and not floored below is visible month by month, which is the one feature of this product most often misdescribed.
The allocation identity, and it is the product’s whole economics in one line:
B(k) + U(k) = b(k) · G(k) for every policy year k
— the year’s declared surplus is either spent on options or credited as interest, never both and never
neither. check_surplus_alloc() asserts it.
The lock-in ledger and the guarantee:
K(k+1) = K(k) + X(k) + U(k), K(k₀) = guar_locked_init
Γ(k) = guar_level · Π(k) + K(k)
Γ is monotone non-decreasing by construction, because both terms are; check_lock_in() asserts that,
together with X(k) ≥ 0 and U(k) ≥ 0. A(k) is not asserted monotone, and must not be: with
γ ≥ i_g the account falls in a year that credits nothing, which is exactly model point 13’s 0,25 %
cohort. The ratchet protects the credits, not the balance.
The § 169 Abs. 3 shadow account runs the identical recursion, and carries the identical within-year
timings, with α₅ in place of α — the credits are the same, so only the acquisition profile differs:
av_min_pp_at(k, "AFT_GUAR") = ( av_min_pp(k) + P(k) − α₅(k) − β·P(k) ) · (1 − γ) · (1 + i_g)
av_min_pp(k+1) = av_min_pp_at(k, "AFT_GUAR") + X(k) + U(k)
With zill_years = 5 the two accounts coincide exactly and the floor is a no-op, which is the point:
delib’s charge profile is already at the statutory floor. Set zill_years = 1 and the floor bites.
Decrements#
q_d(t) = mort_table[sex, x(t)] the year's ANNUAL rate
q^m_d(t) = 1 − ( 1 − q_d(t) )^(1/12) the monthly rate applied
w_l(t) = 0 if duration(t) == n − 1
= lapse_table[duration(t)] otherwise the year's ANNUAL rate
w^m_l(t) = 1 − ( 1 − w_l(t) )^(1/12) the monthly rate applied
pols_if_at(t, "BEF_DECR") = l(t)
pols_death(t) = l(t) · q^m_d(t)
pols_if_at(t, "AFT_DEATH") = l(t) − pols_death(t)
pols_lapse(t) = pols_if_at(t, "AFT_DEATH") · w^m_l(t)
pols_if_at(t, "AFT_LAPSE") = pols_if_at(t, "AFT_DEATH") − pols_lapse(t)
pols_maturity(t) = pols_if_at(t, "AFT_LAPSE") if t == 12n − 1, else 0
l(t+1) = 0 if t == 12n − 1
= pols_if_at(t, "AFT_LAPSE") otherwise
pols_surv_year_end(k) = pols_if_at(12k + 11, "AFT_LAPSE")
Death and surrender are sequential, not competing: the month’s deaths are taken first and the lapse
rate is applied to the survivors of death — the annual grid’s own order, a twelfth at a time. Because
(1 − q^m_d)^12 (1 − w^m_l)^12 = (1 − q_d)(1 − w_l) exactly, l(12k) at every anniversary is the
annual grid’s l(k) to the last bit, and pols_surv_year_end(k) — the population the Indexjahr
credit is given to — is unchanged. What the finer grid changes is the split of a year’s exits
between the two decrements, not their total.
The surrender rate is zero through the whole final policy year, which is the library’s convention
and is shared with KLV_DE_S and RLV_DE_S: that year ends at Rentenbeginn and the whole surviving
cohort is booked as a maturity. A within-final-year surrender is now expressible and is deliberately
not modelled, no source establishing one.
Closure, asserted by check_pols_roll_fwd():
Σ_{t=t₀..12n−1} [ pols_death(t) + pols_lapse(t) + pols_maturity(t) ] = pols_if_init()
Benefits#
D(k) = max( av_pp_at(k, "AFT_GUAR"), death_min_rate · BS )
V(k) = max( av_pp_at(k, "AFT_GUAR"), av_min_pp_at(k, "AFT_GUAR") ) · (1 − storno_rate · surr_charge_on)
M(n−1) = max( A(n), Γ(n) )
claims(t, "DEATH") = D(duration(t)) · pols_death(t)
claims(t, "LAPSE") = V(duration(t)) · pols_lapse(t)
claims(t, "MATURITY") = M(duration(t)) · pols_maturity(t)
The counts are monthly and the amounts are annual. A claim is recognised in the month it happens and is paid the policy year’s own amount, because the account it is paid out of is defined at anniversaries.
Read the asymmetry, because it is the point. Death and surrender are struck on
av_pp_at(k, "AFT_GUAR") — the account before the year’s index and safe-arm credits — because a
mid-year exit forfeits the running Indexjahr std. The maturity is struck on A(n), the balance at
Rentenbeginn, including that year’s credits, because the contract ran the Indexjahr to its end.
Two exits at the same instant therefore take different amounts, and a model that pays them the same has
lost the product’s own rule. The monthly grid dates that forfeiture: the annual grid put every exit
at a year end and so silently gave each the favourable date, while here a surrender in month 7 of an
Indexjahr is a row of the projection. The payoff is still not pro-rated — no carrier convention for
that was established — so the forfeiture remains all-or-nothing std and the grid says when it bites.
Two reported quantities that are not cash flows:
rentenfaktor() = max( rentenfaktor_guar, rentenfaktor_curr )
ann_monthly_pp() = M(n−1) / 10 000 × rentenfaktor() if ann_option == "annuity", else 0.0
Expenses and the cash flow statement#
exp_acq_pp(t) = acq_expense_rate · BS at t = t₀ and only if dur_init == 0
exp_maint_pp(k) = exp_fixed_pp · (1 + exp_infl)^k the YEAR's amount
expenses(t) = ( exp_acq_pp(t) + exp_maint_pp(duration(t)) / 12 ) · l(t)
net_cf(t) = premiums(t) − claims(t,"DEATH") − claims(t,"LAPSE") − claims(t,"MATURITY")
− expenses(t)
liability_cf(t) = − net_cf(t)
The maintenance level is a twelfth of the year’s amount rather than a monthly amount 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. What the finer grid buys is that a policy leaving mid-year bears administration only for the months it was there.
result_cf() publishes, indexed by the month t and in this order:
pols_if, premiums, claims_death, claims_lapse, claims_maturity, expenses, liability_cf,
net_cf.
The first column is pols_if and the frame contains net_cf, as the house style requires.
result_cf_annual() sums it into policy years, indexed by the 1-based policy_year, which is the view
the worked example below is stated on.
The account movements live in result_index(), the annual state table: guar_int,
surplus_credit, index_credit and av beside the Indexjahr itself. They are state movements,
not cash flows — credits to the policyholder’s account that reach the insurer’s cash flow only later,
through a benefit — they move once a policy year, and summing them into net_cf is a numbered pitfall.
The aggregates are struck on the count each amount actually attaches to.
prem_to_av(k) = P⁺(k)·l(12k), av_charge(k) = F(k)·l(12k) and guar_int(k) = I(k)·l(12k) are on the
year’s opening in-force, because the decrementing lives paid the premium and earned the guaranteed
interest before they left; but surplus_credit(k) = U(k) · pols_surv_year_end(k) and
index_credit(k) = X(k) · pols_surv_year_end(k) — credits go to survivors, because the
decrementing lives left before the Indexjahr closed. av_released(k) is the account those exits take
out of the account, and it is a cells in its own right for exactly that reason.
The published identities#
Six check_*() cells, each taking no argument and returning a bool over the whole frame, each with a
residual companion beside it. The residual’s argument follows its cells’ clock: check_net_cf()
and check_pols_roll_fwd() carry check_*_resid(t) per month, and the four annual identities carry
check_*_resid(k), one per policy year. check_net_cf() is mandatory in this library.
Check |
Clock |
Identity |
|---|---|---|
|
month |
|
|
year |
|
|
month |
|
|
year |
|
|
year |
|
|
year |
|
A monthly residual for the account roll-forward would first have had to invent a monthly account, and one for the Indexjahr a monthly settlement; neither exists in this contract, and the two-clock split is what makes writing one impossible.
av_released(k) is the account the exits take out of the account, which is not the same as the
amounts they are paid: the death floor pays more than the account releases, the Stornoabzug pays
less, and the Beitragsgarantie pays more. Those three differences are insurer money, and keeping them
outside the account roll-forward is what makes check_av_roll_fwd() exact rather than approximate.
Processing order, on two clocks#
The annual block runs once per policy year k, and is steps A1 to A8. The monthly block runs in
every month t of that year, and is steps M1 to M4.
The annual block, for k = k₀ … n−1:
A1. Open the year. A(k), x(k) = entry_age + k; the population is l(12k). If k ≥ n, stop.
A2. Strike the participating base, G(k) = A(k) — before this year’s premium. This is what the
Indexjahr is measured on, and it is the reason a new-business point credits nothing at k = 0
however well the index does.
A3. Read the election and the declaration: w(k) from election_table.csv, b(k) from
surplus_rate_table.csv. Split the surplus: option budget B(k) = w(k)·b(k)·G(k), safe-arm credit
U(k) = (1−w(k))·b(k)·G(k).
A4. Take the premium in advance and decompose it: P(k) = P_b(k)·φ, less the acquisition charge
α(k) and the administration charge β·P(k); credit the remainder to the account.
A5. Deduct the reserve charge γ on the post-premium balance, then credit the guaranteed
interest i_g on the post-charge balance. The account now stands at av_pp_at(k, "AFT_GUAR"),
and this is the balance every exit of the year is measured on.
A6. Strike the year’s benefit amounts, D(k) and V(k), on that balance — with no index or
safe-arm credit in the year of exit.
A7. Run the Indexjahr. Cap each of the twelve monthly returns above at C(k) with no floor on
the month; sum the twelve; floor the sum at zero — or, in the Quote form, apply q(k) to
the compounded year return and floor that. The credit is X(k) = ρ(k)·w(k)·G(k).
A8. End of the year — credit and lock in. Add X(k) and U(k) to the account of the
survivors of all twelve months, pols_surv_year_end(k); roll the ledger
K(k+1) = K(k) + X(k) + U(k), the guaranteed capital Γ(k+1) = guar_level·Π(k+1) + K(k+1), the
account A(k+1), the premiums paid Π(k+1) and the shadow account av_min_pp(k+1).
The monthly block, for t = t₀ … 12n−1:
M1. Open the month. duration(t) = t // 12 selects the year’s state; the population is l(t);
index_month(t) says which month of the Indexjahr this is, and index_return_mth(t) and
index_return_capped_mth(t) publish its raw and capped returns.
M2. Collect the premium where one is due, prem_due(t) in the first month of the policy year:
premiums(t) = P(duration(t))·l(t).
M3. Incur the insurer’s expenses on the month’s opening in-force: the acquisition expense at t₀
for new business, and a twelfth of the year’s maintenance every month.
M4. End of the month — decrements and benefits. Deaths at q^m_d(t); then surrenders at w^m_l(t)
on the survivors of death. Deaths take D(duration(t)), surrenders V(duration(t)). Through the
final policy year the surrender rate is zero, and at t = 12n−1 the survivors of death are
maturities taking M(n−1) = max(A(n), Γ(n)); report the annuity that buys at
max(rentenfaktor_guar, rentenfaktor_curr), or nothing if the Kapitalwahlrecht is exercised.
Then roll l(t+1) and publish net_cf(t).
Steps A2 and A8 are the pair that defines the product: the base is struck before the premium and the credit lands after every one of the year’s decrements.
Known modeling pitfalls#
These are the specific ways an implementation of this product looks right and is wrong. Each one is
a test in tests/test_indexpolice_de.py.
Pitfalls 2, 3 and 4 now have an external check. Allianz publishes two worked Indexjahre on the
EURO STOXX 50 at an exemplary Cap of 3,2 % and Partizipationssatz of 75,00 % [S2] [S5], and the
arithmetic is delib’s. In 2020/2021 the twelve monthly movements were +18,06 %, +2,26 %, −2,52 %,
+4,45 %, +7,78 %, +1,42 %, +1,63 %, +0,61 %, +0,62 %, +2,62 %, −3,53 %, +5,00 %; four of them capped to
3,20 %, the three negative months passing through in full, the twelve summed to 15,90 %, and
75 % of that credited as an Indexpartizipation of 11,92 % — against a point-to-point index gain of
43,69 %. In 2021/2022 the same arithmetic summed to −26,96 % and the maßgebliche Jahresrendite
was 0 %. The carrier’s own footnote is pitfall 3 stated by the insurer: “Die Wertentwicklung des
EURO STOXX 50® ergibt sich aus der Differenz der Kurse zu Beginn und zum Ende des Betrachtungszeitraumes,
nicht aus der Summe der monatlichen Wertentwicklungen.” Re-running that table through this model’s
index_sum and index_credit_rate at cap = 0.032 reproduces 15,90 % and 0 % and would be a
worthwhile addition to the test file; it is not in the shipped tests, whose anchors are the
research file’s constructed Examples A and B.
One structural gap the same document exposes. Allianz applies a monthly Cap and a
Partizipationssatz to the capped sum; delib’s cap payoff form has no participation factor (w is
the election share) and its quote form has no cap, so X(t) = q · max(S(t), 0) · G(t) — the actual
Allianz payoff — is not expressible in the shipped model. Adding a quote multiplier to the cap
arm would move the worked example and its golden tests, so it is recorded here as a model change to
take deliberately and is not made in a provenance pass.
Treating the contract as unit-linked. There is no Anlagestock, no unit price and no fund value: the capital is in the Sicherungsvermögen and the surrender value is a reserve R15 REG-R7 REG-R28. Assert that no
unit_price/fund_valuecells exists, thatcv_pp(k)derives fromav_pp, and that a negative index year never reduces the account:av_pp_at(k,"AFT_CREDIT") ≥ av_pp_at(k,"AFT_GUAR")in every policy year.Flooring each month at zero.
x(m) = min(r, C)has no lower bound. On the anchor’sk = 9(research Example B, policy year 10) the correct sum is −2,60 % and the credit is 0,00 €; an implementation that floors each capped month getsS = +12,60 %and credits something. Assertindex_sum(9) < 0andindex_credit_pp(9) == 0.0.Compounding the capped returns instead of summing them. The contractual formula is a sum. On
k = 8(Example A, policy year 9) assertindex_sum(8) == 0.0890exactly, and that the twelveindex_return_capped_mth(t)of months 96 to 107 sum to it; compounding the same twelve capped returns gives 8,9599 %, an error of 0,0599 points — small enough to look like rounding and large enough to be wrong at every duration.Applying the floor to the compounded raw return. On
k = 9the raw year return isY(9) = +6,4402 %— the index rose and the credit is zero. An implementation that computesmax(Y, 0)credits 6,44 %, and one that computesmax(q·Y, 0)on the Cap model point credits 3,86 %. Assertindex_return_year(9) > 0whileindex_credit_rate(9) == 0.0on model point 1.Striking the participation on the wrong base.
G(t) = av_pp(k), before the year’s premium and before the year’s charges — no longer std but the rule in both retrieved AVB, which exclude the year’s premiums and Zuzahlungen from the Bezugsgröße ([S2] Ziffer 3.3, [S7] § 3 Ziffer 2). Assertindex_base_pp(k) == av_pp(k)in every policy year, and that on the anchorindex_credit_pp(0) == 0.0even thoughindex_credit_rate(0) > 0— the base is zero at inception. A model striking the base after the premium credits a first-year amount that does not exist.Crediting the index and the declared surplus. They are alternative applications of one budget R1 R8. Assert
check_surplus_alloc(); assertsurplus_credit_pp(k) == 0.0in every policy year on model point 1 (w = 1) andindex_credit_pp(k) == 0.0in every policy year on model point 11 (w = 0); and assert model point 12 (w = 0,5) splits the budget exactly in half.Adding the declared rate on top of the guaranteed rate. The market’s laufende Verzinsung is the guarantee plus the surplus, not a further credit above it REG-R53. In the index arm the surplus is not credited at all. Assert that on model point 9 (
zero_path,w = 1) the account grows at exactly(1 − γ)(1 + i_g)on the post-premium balance every year, and that on model point 11 (w = 0) it grows by exactlyi_gplusb·G(t)and by nothing more.Crediting the Indexjahr to the lives that left during it. Credits go to
pols_surv_year_end(k), not topols_if(12k). Assertcheck_av_roll_fwd()in every policy year, and assertindex_credit(t) == index_credit_pp(k) * pols_if_at(t, "AFT_LAPSE").Paying a pro-rata index credit on a mid-year exit. No credit in the year of exit — the rule in both retrieved AVB, which credit the participation only at the start of the following Indexjahr ([S2] Ziffer 3.3, [S7] § 3 Ziffer 5) and add nothing pro rata for the running one. Assert
db_pp(k) == max(av_pp_at(k,"AFT_GUAR"), death_min_rate * prem_sum())exactly, with no index term, and thatav_pp_at(k,"AFT_GUAR") < av_pp(k+1)in every year the index credited something — the benefit is struck on the balance before the year’s credits. Assert it on that balance and not ondb_ppitself: the Mindesttodesfallschutz floor of 32 400,00 € exceeds the anchor’s account untilk = 12(policy year 13), so the death benefit before then is larger than the account at any timing, which is what a floor is for.Testing the lock-in as “the account never falls”. It is the credits that ratchet, not the balance. On model point 13 (
guar_rate = 0,25 % = γ) the account is flat or falling once premiums stop, whileguar_cap_ppis still monotone. Assertcheck_lock_in()on every point and thatav_pp(k+1) < av_pp(k)for at least oneton model point 13 — the invariant that would fail if the ratchet had been written on the wrong quantity.Running the guarantee as an annual guaranteed rate on the reserve. Neue Klassik: the Beitragsgarantie is owed at Rentenbeginn only — the retrieved Perspektive KID puts it exactly so, “Dieser Schutz vor künftigen Marktentwicklungen gilt jedoch nicht, wenn Sie vor dem vereinbarten Rentenbeginn einlösen” [S6], and R+V’s AVB § 1 Ziffer 2 owes 90 % of premiums “zum vereinbarten Rentenbeginn” [S7]. Assert that
guar_cap_pp(k)never enters a benefit beforet = n−1, thatav_pp(k) < guar_cap_pp(k)is permitted at intermediatet, and thatclaims_maturity(n−1) == max(av_pp(n), guar_cap_pp(n)) * pols_maturity(n−1).Forgetting the Beitragsgarantie floor at Rentenbeginn. Assert that on model point 9 the floor binds:
mat_pp(n−1) == guar_cap_pp(n) > av_pp(n), and that it does not bind on the anchor. A model with no floor and a model with a floor that never binds look identical on twelve of the thirteen points.Confusing the § 169 Abs. 3 floor with the Höchstzillmersatz. Two different rules with two different functions: the DeckRV governs what may be reserved, § 169 VVG what must be paid REG-R16 REG-R28. Assert that with
zill_years = 5the shadow account equals the tariff account in every policy year (the floor is a no-op) while the 2 % Stornoabzug still bites, and thatcv_pp(k) < av_pp_at(k,"AFT_GUAR")on every point withsurr_charge_on = 1.Double-charging or mis-basing the Ratenzahlungszuschlag.
φmultiplies the premium collected and does not enter the Beitragssumme. On model point 4 (monthly) assertprem_gross_pp(k) == 2 400,00 × 1,05 == 2 520,00whileprem_sum() == 2 400,00 × 32 == 76 800,00, so the acquisition charge and the 50 % death floor are unchanged by the payment frequency.Letting the Cap and the option budget be independent parameters. They are not: the Cap is the level at which the option strip costs the budget. Assert
index_budget_ratio()— total index credits over total option budget — and report it beside the worked example. At the shipped pair it is not 1, and the notes say which way and why (assumption class (b), footnote 2).Assuming the Wahlrecht is exercised optimally, or that its path is known. It is a behavioural assumption, not a contractual one. Assert that model point 11 (
always_safe) reproduces a klassische Rentenversicherung exactly — every index cells evaluates, none of them reaches the account — and that model point 10 (switch_at_15) hasindex_credit_pp(k) == 0.0for everyt ≥ 15andsurplus_credit_pp(k) == 0.0for everyt ≤ 14.A lapse assumption flat in duration. The duration-12 tax threshold is the strongest single driver of German surrender behaviour R14 REG-R45. Assert
lapse_rate(11) > lapse_rate(10)— policy year 12 against policy year 11 — andlapse_rate(proj_len() − 1) == 0.0whilelapse_rate_base(proj_len() − 1)is still the table’s 2 %.Reporting the credits inside
net_cf.guar_int,surplus_creditandindex_creditare state movements; they reach the insurer’s cash flow only through a benefit. Assertcheck_net_cf(), and assert thatnet_cfis unchanged when the three columns are dropped from the frame.
Policyholder behaviour modelling#
All formulas are std reference constructions; there is no German calibration evidence for any of them, and none for this product specifically.
Base surrender. The duration table above, with its year-12 step at the tax threshold and its zero in the final policy year. Cumulative surrender over the anchor’s 27 years is a material fraction of the cohort, so the assumption governs how much of the ratcheted late-duration capital is ever carried to Rentenbeginn.
The Wahlrecht election. Four shipped paths, base run
w = 1. The base run is a modelling choice, not a claim about behaviour: the product exists to demonstrate the index mechanic, and a base run in the safe arm would reduce it toRV_DE_S. The alternative belief — that policyholders are inert and never revisit an election made at inception — is equally unevidenced and is the reasonswitch_at_15andhalf_halfare shipped rather than described.Dynamic surrender is not modeled, and the reason is specific to this product. On a fondsgebundene contract a falling account value drives surrender; here the account cannot fall from the index, so the natural dynamic driver is absent. The candidate driver that is present — a run of zero Indexjahre, which at the research file’s parameters is expected about two years in three — is a disappointment effect with no published calibration, and inventing one would put a large unevidenced number at the centre of the result. A reference multiplier for a user who wants one:
M(t) = 1 + λ · (number of consecutive zero credits ending at t − 1), withλ = 0in the base run.The mid-year-exit incentive is not modeled either. With no credit in the year of exit, a rational policyholder surrenders just after an Indexjahr ends. The monthly grid can now represent the alternative — an exit falls in the month it happens and the eleven unfavourable months are rows of the frame — so what is missing is no longer the grid but the behaviour: the surrender rate is unconditional and no published calibration of a timing response exists.
What the model deliberately does not do. No Beitragsfreistellung decrement (the paid-up account diverges from the premium-paying one at the moment of conversion and tracking it needs a conversion-cohort ledger REG-R28); no Dynamik take-up; no Zuzahlungen; no Kapitalwahlrecht election rate —
ann_optionis a model-point configuration, and treating it as a take-up rate would stand in for a tax comparison the model does not perform R14 REG-R45.
Worked example#
Configuration. Model point 1, the anchor cell, in full: point_id = 1;
policy_id = "DE-IDX-0001"; sex = M; entry_age = 40; dur_init = 0, so t_start() = 0 and the
table below is the entire projection; pols_if_init = 1.0; ann_start_age = 67, hence
proj_len_y() = 67 − 40 = 27 policy years and proj_len() = 324 months, t = 0 … 323;
prem_form = level; prem_gross_pp = 2 400,00 € (the annual-mode
Jahresbeitrag, i.e. the research file’s 200,00 € a month taken annually); prem_freq = annual, so
freq_load() = 1,000 and the premium collected equals the premium due; prem_term_y = 27, premiums
payable throughout, so prem_sum() = 27 × 2 400,00 = 64 800,00 €; av_pp_init = 0,00 €;
guar_locked_init = 0,00 €; prem_paid_init = 0,00 €; guar_level = 0,90, a Beitragsgarantie of
0,90 × 64 800,00 = 58 320,00 € at Rentenbeginn plus every locked-in credit; guar_rate = 0,0100,
the Höchstrechnungszins for 2025–2026 R7 R18 REG-R15; payoff_form = "cap";
index_id = "eqidx_vol17", so the Cap is 3,00 % a month and the Indexjahre at k = 8 and k = 9
are the research file’s Examples A and B; elect_id = "always_index", so w(k) = 1,00 in every one of
the 27 years and the safe arm is never used; death_min_rate = 0,50, a Mindesttodesfallschutz floor
of 0,50 × 64 800,00 = 32 400,00 €; ann_option = "annuity"; surr_charge_on = 1.
Assumptions, each tagged. Guaranteed rate i_g = 1,00 % a year, credited on the post-charge balance
— the contract’s Rechnungszins, at the Höchstrechnungszins for 2025–2026 R7 R18 REG-R15.
Declared surplus rate b(k) = 2,50 % a year of G(k), level over all 27 years std — the option
budget, consistent with the 2026 market averages R20 REG-R53. Election w(k) = 1,00 for every k
std, so opt_budget_pp(k) = 2,50 % × G(k) and surplus_credit_pp(k) = 0,00 € throughout.
Monthly Cap C(k) = 3,00 % for every k std, the midpoint of a 1,5–5,0 % band that no carrier
document could confirm. Partizipationsquote q(k) = 60 % std, carried but unused on this point.
Monthly index returns from eqidx_vol17 std: 40 years × 12 months from
numpy.random.default_rng(20260829).normal(0.0060, 0.0500, size=(40, 12)) rounded to four decimal
places — a monthly mean of 0,60 % and standard deviation of 5,00 %, an annualised 17,3 % — with row
k = 8 replaced by Example A (1,80 / −2,40 / 4,60 / 0,90 / −3,70 / 2,20 / 3,40 / −1,10 / 0,40 / 5,20 /
−0,80 / 2,60 per cent) and row k = 9 by Example B (6,50 / −2,10 / 5,80 / −1,90 / −2,40 / 4,20 /
−3,10 / 0,60 / −2,80 / 5,10 / −1,70 / −1,20 per cent). Mortality qx(M, x) = 0,001200 × 1,095^(x − 40)
std, so mort_rate(0) = 0,001200 at attained age 40 and the proxy is anchored there; DAV 2008 T
and DAV 2004 R are cited by name and never shipped REG-R48 REG-R49. Surrender
lapse_rate_base = 5 % in policy years 1–2 (k = 0, 1), 3 % in policy years 3–11, 6 % in policy year
12 (k = 11), 2 % from policy year 13 std, with lapse_rate zero through the whole final policy
year k = 26 because its end is Rentenbeginn. Acquisition charge
acq_cost_rate = 2,5 % of the Beitragssumme std — 0,025 × 64 800,00 = 1 620,00 € — spread
over zill_years = 5 premium-paying years at 324,00 € a year, inside the DeckRV § 4
Höchstzillmersatz of 25 ‰ REG-R16. Premium charge β = 3 % of each gross premium std, i.e.
72,00 € a year. Reserve charge γ = 0,25 % a year of the post-premium balance std. Acquisition
expense acq_expense_rate = 2,5 % of the Beitragssumme std, 1 620,00 € incurred in full at
inception. Maintenance expense 36,00 € a year inflating at exp_infl = 1,5 % std. Stornoabzug
storno_rate = 2 % of the base surrender value std. Rentenfaktor 25,00 € per 10 000 € per
month, guaranteed and current equal std. No claim expense, no Dynamik, no Beitragsfreistellung,
no dynamic behaviour module, no discounting.
All amounts in euros; pols_if to six decimals, cash flows and balances to the cent. The Total row
is summed at full precision and then rounded, not summed from the rounded cells.
The projection. The cash flow columns are Projection[1].result_cf_annual(), the monthly frame
summed into policy years; guar_int, index_credit and av are result_index(), the annual state.
The key is the 0-based policy year k = policy_year − 1, so row k covers months 12k … 12k + 11.
surplus_credit is 0,00 € in every year — the anchor elects the index arm in all 27 — and
liability_cf is exactly −net_cf; both are omitted here for width. av is a balance, so its
column is deliberately not totalled: adding twenty-seven opening balances is not a quantity.
k |
x(k) |
pols_if |
premiums |
claims_death |
claims_lapse |
claims_maturity |
expenses |
guar_int |
index_credit |
av |
net_cf |
|---|---|---|---|---|---|---|---|---|---|---|---|
0 |
40 |
1.000000 |
2,400.00 |
37.98 |
98.87 |
0.00 |
1,655.15 |
19.99 |
0.00 |
0.00 |
608.00 |
1 |
41 |
0.948860 |
2,277.26 |
39.46 |
188.31 |
0.00 |
33.85 |
38.08 |
81.79 |
1,915.73 |
2,015.64 |
2 |
42 |
0.900233 |
2,160.56 |
41.39 |
163.80 |
0.00 |
32.90 |
55.21 |
186.81 |
3,730.48 |
1,922.46 |
3 |
43 |
0.871969 |
2,092.73 |
43.90 |
217.08 |
0.00 |
32.35 |
73.17 |
1,204.06 |
5,587.67 |
1,799.40 |
4 |
44 |
0.844477 |
2,026.75 |
46.55 |
297.50 |
0.00 |
31.80 |
100.28 |
0.00 |
8,360.99 |
1,650.90 |
5 |
45 |
0.817730 |
1,962.55 |
49.36 |
346.53 |
0.00 |
31.25 |
116.82 |
41.78 |
9,807.67 |
1,535.41 |
6 |
46 |
0.791700 |
1,900.08 |
52.33 |
393.74 |
0.00 |
30.70 |
132.75 |
0.00 |
11,465.08 |
1,423.30 |
7 |
47 |
0.766360 |
1,839.26 |
55.46 |
436.73 |
0.00 |
30.16 |
147.26 |
0.00 |
12,978.50 |
1,316.90 |
8 |
48 |
0.741686 |
1,780.05 |
58.78 |
476.85 |
0.00 |
29.63 |
160.80 |
1,239.56 |
14,394.07 |
1,214.79 |
9 |
49 |
0.717651 |
1,722.36 |
62.28 |
550.86 |
0.00 |
29.10 |
185.79 |
0.00 |
16,954.47 |
1,080.12 |
10 |
50 |
0.694231 |
1,666.15 |
65.97 |
584.59 |
0.00 |
28.56 |
197.19 |
0.00 |
18,152.02 |
987.03 |
11 |
51 |
0.671401 |
1,611.36 |
68.87 |
1,231.45 |
0.00 |
27.64 |
207.72 |
0.00 |
19,261.02 |
283.40 |
12 |
52 |
0.629062 |
1,509.75 |
75.18 |
416.27 |
0.00 |
26.78 |
210.68 |
823.46 |
19,656.70 |
991.52 |
13 |
53 |
0.614282 |
1,474.28 |
89.77 |
453.85 |
0.00 |
26.54 |
229.75 |
0.00 |
21,602.56 |
904.11 |
14 |
54 |
0.599646 |
1,439.15 |
102.63 |
473.76 |
0.00 |
26.29 |
239.88 |
0.00 |
22,651.89 |
836.46 |
15 |
55 |
0.585141 |
1,404.34 |
116.85 |
492.49 |
0.00 |
26.04 |
249.41 |
0.00 |
23,641.56 |
768.96 |
16 |
56 |
0.570753 |
1,369.81 |
132.54 |
510.02 |
0.00 |
25.77 |
258.35 |
0.00 |
24,571.29 |
701.48 |
17 |
57 |
0.556471 |
1,335.53 |
149.81 |
526.34 |
0.00 |
25.50 |
266.69 |
0.00 |
25,440.64 |
633.88 |
18 |
58 |
0.542280 |
1,301.47 |
168.80 |
541.45 |
0.00 |
25.22 |
274.43 |
0.00 |
26,249.06 |
566.00 |
19 |
59 |
0.528168 |
1,267.60 |
189.63 |
555.33 |
0.00 |
24.92 |
281.55 |
0.00 |
26,995.84 |
497.72 |
20 |
60 |
0.514121 |
1,233.89 |
212.44 |
567.95 |
0.00 |
24.62 |
288.05 |
0.00 |
27,680.11 |
428.89 |
21 |
61 |
0.500125 |
1,200.30 |
237.36 |
579.30 |
0.00 |
24.30 |
293.91 |
0.00 |
28,300.85 |
359.35 |
22 |
62 |
0.486168 |
1,166.80 |
264.53 |
589.34 |
0.00 |
23.96 |
299.14 |
0.00 |
28,856.92 |
288.96 |
23 |
63 |
0.472234 |
1,133.36 |
294.08 |
598.06 |
0.00 |
23.62 |
303.70 |
0.00 |
29,346.98 |
217.60 |
24 |
64 |
0.458311 |
1,099.95 |
326.15 |
605.42 |
0.00 |
23.26 |
307.59 |
0.00 |
29,769.58 |
145.12 |
25 |
65 |
0.444386 |
1,066.53 |
360.85 |
611.39 |
0.00 |
22.88 |
310.80 |
0.00 |
30,123.10 |
71.41 |
26 |
66 |
0.430446 |
1,033.07 |
402.00 |
0.00 |
31,240.67 |
22.69 |
313.29 |
0.00 |
30,405.82 |
−30,632.29 |
Total |
— |
— |
42,474.94 |
3,744.94 |
12,507.30 |
31,240.67 |
2,365.47 |
5,562.28 |
3,577.46 |
— |
−7,383.44 |
The Total row is the sum at full precision, then rounded, over all 324 months. Four of the
eight totals differ from the sum of the twenty-seven already-rounded annual cells, each by a cent or
two: claims_death 3 744,94 € against 3 744,95 €, claims_lapse 12 507,30 € against 12 507,28 €,
expenses 2 365,47 € against 2 365,48 €, and net_cf −7 383,44 € against −7 383,48 €. Assert the
full-precision totals; a test written against the rounded column will fail on four of them and look
like a modelling error.
The shape is the one a Zillmer-financed savings contract has and no other: a first year that is
almost the whole story of the strain — 1 620,00 € of acquisition expense against 2 400,00 € of
premium, so the first policy year nets 608,00 € rather than the 2 000 € the premium suggests, and
within it the first month nets +765,32 € while the other eleven are each mildly negative — then
twenty-five thin positive years while the account builds, then one very large negative year when the
whole surviving cohort’s capital falls due at once. The surrender spike in policy year 12 (k = 11,
1 231,45 € against 584,59 € the year before) is the § 20 Abs. 1 Nr. 6 EStG threshold in the lapse
table and nothing else.
What the monthly grid moved, against the annual-step model this replaced. Premiums, the maturity and the whole annual layer are identical: the account, every Indexgutschrift, the Höchststandsicherung ledger, the guaranteed capital and the surrender value are bit-identical on all thirteen model points. Two columns moved:
Column |
Annual grid |
Monthly grid |
Why |
|---|---|---|---|
|
3 780,63 € / 12 476,88 € |
3 744,94 € / 12 507,30 € |
Deaths and surrenders compete month by month rather than once a year. The total exits at every anniversary are unchanged; the split is not |
|
2 376,60 € |
2 365,47 € |
A policy leaving mid-year bears administration for the months it was there |
Independent checks#
Six checks. Four rebuild a number in the table by a route the model does not take, and two are closure identities — the decrements, and the cash flow statement itself.
1. The first policy year k = 0 — contractual policy year 1 — rebuilt end to end with a calculator.
The acquisition charge is 0,025 × 64 800,00 = 1 620,00 € over five years, 324,00 € a year; the
administration charge is 0,03 × 2 400,00 = 72,00 €; so P⁺(0) = 2 400,00 − 324,00 − 72,00 = 2 004,00 €. The account opens at zero, so av_pp_at(0,"AFT_PREM") = 2 004,00, the reserve charge is
0,0025 × 2 004,00 = 5,01 €, av_pp_at(0,"AFT_CHARGE") = 1 998,99 and the guaranteed interest is
0,01 × 1 998,99 = 19,9899 € — the table’s guar_int of 19,99, on pols_if(0) = 1. That leaves
av_pp_at(0,"AFT_GUAR") = 2 018,9799 €, and the year’s two benefit amounts follow from it: the
Mindesttodesfallschutz floor 0,50 × 64 800,00 = 32 400,00 € dominates the account, so
D(0) = 32 400,00 €, and the surrender value is V(0) = 2 018,9799 × 0,98 = 1 978,6003 €. Every
line so far is annual and is unchanged by the move to a monthly grid.
The decrements are where the month enters. The year’s rates are q_d = 0,001200 at attained age 40
— the proxy’s own anchor — and w_l = 0,05; their geometric twelfths are
q^m_d = 1 − (1 − 0,0012)^(1/12) = 0,00010006 and w^m_l = 1 − (1 − 0,05)^(1/12) = 0,00426532, and
twelve months of each compound back to the year exactly, so pols_if(12) = 0,948860 — the table’s
second row to six decimals, and the annual grid’s own number. Summed over the twelve months the year
produces 0,001172 deaths and 0,049968 surrenders, against the annual grid’s 0,001200 and
0,049940: the competing-decrement shift, total exits unchanged. Hence
claims_death = 32 400,00 × 0,001172 = 37,98 € and
claims_lapse = 1 978,6003 × 0,049968 = 98,87 €. Expenses are 1 620,00 in the first month plus
twelve twelfths of 36,00 on a falling in-force, 1 655,15 € in all. The year nets
2 400,00 − 37,98 − 98,87 − 0 − 1 655,15 = 608,00 €, of which +765,32 € falls in the first month
— the whole Beitrag against the whole acquisition expense — and the other eleven are each about
−14,50 €. Every figure in the first row is reproduced without the model.
2. The Indexjahr of k = 8 — policy year 9 — rebuilt on its own terms. The twelve capped
monthly returns of research Example A sum to S(8) = +8,90 % (table below), which is positive, so
ρ(8) = 8,90 %. On the monthly grid those twelve are rows 96 to 107 of the frame and are readable one
by one as index_return_capped_mth(t); their sum is index_sum(8) by construction, which the test
module asserts. The base is the opening balance G(8) = av_pp(8) = 19 407,2450 €, giving
X(8) = 0,0890 × 19 407,2450 = 1 727,2448 € per policy; the credit goes to the survivors of all twelve
months, pols_surv_year_end(8) = 0,717651, so
index_credit(8) = 1 727,2448 × 0,717651 = 1 239,5583 € — the table’s 1 239,56, and the annual
grid’s figure to the cent. Two contrasts a reader can check on the same twelve numbers: compounding
the capped returns instead of summing them gives 8,9599 %, and the raw year return is
Y(8) = +13,4548 % against a raw sum of +13,10 %.
3. The decrement closure. Summed over all 324 months, deaths 0,073805, surrenders
0,501218 and maturities 0,424977 add to 1,000000 exactly — the whole opening cohort, with
nothing left in force at t = 324. This is check_pols_roll_fwd()’s second condition, and it is built
by direct summation over the exit cells with no reference to the recursion that produced pols_if. The
maturity count is the annual grid’s to fifteen decimals; deaths and surrenders have traded 0,000779 of
a policy between them, which is the competing-decrement shift over the whole projection.
4. The account roll-forward in policy year k = 8, at fund level.
av(8) 14,394.0730 = av_pp(8) x pols_if(96)
+ prem_to_av(8) 1,726.6439 = 2,328.00 x 0.741686
− av_charge(8) 40.3018 = 54.3381 x 0.741686
+ guar_int(8) 160.8042
+ surplus_credit(8) 0.0000 w(8) = 1, so the safe arm is empty
+ index_credit(8) 1,239.5583 to pols_surv_year_end(8), not to pols_if(96)
− av_released(8) 526.3103 = 21,897.7159 x (0.0018141 + 0.0222208)
-------------------------------
= av(9) 16,954.4673 the table's next row
Every term is struck on a different population, which is the whole difficulty of this product: the
premium, the charge and the guaranteed interest on the year’s opening in-force pols_if(12k), the
credit on the survivors of all twelve months, and av_released on the year’s two exits at the balance
they left with. This is check_av_roll_fwd(), and it is stated per policy year: the account is
defined at anniversaries and the Indexjahr settles only at the year end, so there is no monthly
residual to write. The exits are summed over the year — 0,0018141 deaths and 0,0222208 surrenders
against the annual grid’s 0,0018396 and 0,0221954 — and their total, which is what the balance
release depends on, is unchanged.
5. The cash flow statement closes on the Total row. 42 474,94 − 3 744,94 − 12 507,30 − 31 240,67 − 2 365,47 = −7 383,44 €, which is the net_cf total to the cent, and at full precision
42 474,939948 − 47 492,910425 − 2 365,474366 = −7 383,444842. Note what is not in it: the
guaranteed interest of 5 562,28 € and the index credits of 3 577,46 € are movements of the
policyholder’s account, live in result_index() rather than in the cash flow statement, and adding
them would move net_cf by 9 139,74 €. This is check_net_cf(), and it is asserted in every one of
the 324 months.
6. The guarantee at Rentenbeginn. The ledger closes at
credit_cum_pp(27) = 4 851,4383 € — its value at policy year n = 27, the sum of the per-policy index
credits over the 27 years, the safe arm being empty — and the Beitragsgarantie at
0,90 × 64 800,00 = 58 320,00 €, so the guaranteed capital is guar_cap_pp(27) = 63 171,4383 €. The
account stands at av_pp(27) = 73 511,3936 €, above it, so the floor does not bind on the anchor
and mat_pp(26) = 73 511,39 €. The maturity cash flow
is 73 511,3936 × 0,424977 = 31 240,67 €, the table’s last row. Reported beside it, and not a cash
flow of this model: ann_monthly_pp() = 73 511,3936 / 10 000 × 25,00 = 183,78 € a month.
The budget diagnostic#
index_budget_ratio() on the anchor is 0,2082 — total index credits 4 851,44 € against a total
option budget of 23 298,38 €. That is a long way from 1 and it needs reading carefully, because two
different things are in it.
On rates, the twenty-seven Indexjahre credited an average of 2,1330 % against a budget of 2,50 %, a ratio of 0,853. That is the like-for-like comparison, and it is consistent with the research file’s expectation of about 2,97 % a year at these parameters, one realised path being a sample of size 27 from a distribution with a 65 % chance of zero in any year.
On amounts the ratio collapses to 0,2082, and the reason is timing, not pricing: the path credits at a positive rate in policy years
k = 0, 1, 2, 3, 5, 8and12, and in none afterk = 12— and the rate of 12,04 % atk = 0lands on a base of zero — so the six years that actually credit all fall while the account is small and the twenty-one that do not fall while it is large.G(t)runs from 0,00 € atk = 0to 70 637,97 € atk = 26, and a rate ratio weighted byG(k)is dominated by the late years.
Both numbers are worth having and neither should be quoted alone. What the pair says about the shipped
parameters is that the Cap and the option budget are not badly mismatched on this path — 0,853 on
rates — while the amount ratio is a warning that a single deterministic index path cannot calibrate
anything. The research file’s own risk-neutral arithmetic points the other way, pricing the strip at
about 1,7 % of G against a 2,50 % budget, i.e. a 2,50 % budget would buy a cap somewhat above
3,00 %. A calibrated model would solve for the Cap; this one is given both and reports the
discrepancy, which is the honest treatment when neither number could be established for any carrier.
The two Indexjahre the mechanic turns on#
k = 8 and k = 9 of eqidx_vol17 — policy years 9 and 10 — are the research file’s constructed
Example A and Example B, wired into the shipped path so that the model reproduces them rather than
restating them. Monthly returns in per cent, Cap C = 3,00 %:
Month |
A: |
A: |
B: |
B: |
|---|---|---|---|---|
1 |
+1.80 |
+1.80 |
+6.50 |
+3.00 |
2 |
−2.40 |
−2.40 |
−2.10 |
−2.10 |
3 |
+4.60 |
+3.00 |
+5.80 |
+3.00 |
4 |
+0.90 |
+0.90 |
−1.90 |
−1.90 |
5 |
−3.70 |
−3.70 |
−2.40 |
−2.40 |
6 |
+2.20 |
+2.20 |
+4.20 |
+3.00 |
7 |
+3.40 |
+3.00 |
−3.10 |
−3.10 |
8 |
−1.10 |
−1.10 |
+0.60 |
+0.60 |
9 |
+0.40 |
+0.40 |
−2.80 |
−2.80 |
10 |
+5.20 |
+3.00 |
+5.10 |
+3.00 |
11 |
−0.80 |
−0.80 |
−1.70 |
−1.70 |
12 |
+2.60 |
+2.60 |
−1.20 |
−1.20 |
Sum |
+13.10 |
+8.90 |
+7.00 |
−2.60 |
Compounded |
+13.4548 |
+6.4402 |
Example A is the strong year. The cap bound in three months and cost 13,10 − 8,90 = 4,20 points;
S(8) = +8,90 % and ρ(8) = 8,90 %.
Example B is the case the product is criticised for: the cap bound in four months and cost
7,00 − (−2,60) = 9,60 points, S(9) = −2,60 %, and so ρ(9) = max(−2,60 %, 0) = 0. The index
rose 6,4402 % over the year and the credit was nothing. The capital was untouched, and the year’s
option budget bought options that expired worthless. An implementation that floors each month at zero
gets S = +12,60 % here; one that applies the floor to the compounded raw return credits 6,44 %; one
that applies the Partizipationsquote to it credits 3,86 %. All three are wrong, and all three look
entirely plausible in a printout.
Model point 8 reproduces both to the euro. It is the in-force cell, dur_init = 8 and
av_pp_init = 50 000,00 €, so its frame opens at month 96 — policy year k = 8, its first projected
Indexjahr — and its
base is exactly the research file’s G = 50 000,00 €:
index_credit_pp(8) = 0,0890 × 50 000,00 = 4 450,00 €, against a sichere Verzinsung arm that would have credited0,0250 × 50 000,00 = 1 250,00 €— the index arm paying 3,56 times the safe arm;index_credit_pp(9) = 0,00 €on a base of 60 631,57 €, the safe arm having offered 1 515,79 €.
The Partizipationsquote variant, on the identical index path#
Model point 2 is the anchor with payoff_form = "quote" and nothing else changed, so the two payoff
designs run against the same twelve monthly returns in every year. Selected rows; the Total row
covers all 27 years, not only the six displayed:
t |
x(t) |
pols_if |
premiums |
claims_death |
claims_lapse |
claims_maturity |
expenses |
guar_int |
index_credit |
av |
net_cf |
|---|---|---|---|---|---|---|---|---|---|---|---|
0 |
40 |
1.000000 |
2,400.00 |
37.98 |
98.87 |
0.00 |
1,655.15 |
19.99 |
0.00 |
0.00 |
608.00 |
3 |
43 |
0.871969 |
2,092.73 |
43.90 |
226.81 |
0.00 |
32.35 |
76.45 |
2,036.27 |
5,916.59 |
1,789.67 |
8 |
48 |
0.741686 |
1,780.05 |
58.78 |
511.71 |
0.00 |
29.63 |
172.56 |
1,216.40 |
15,572.35 |
1,179.93 |
9 |
49 |
0.717651 |
1,722.36 |
62.28 |
584.15 |
0.00 |
29.10 |
197.01 |
675.83 |
18,079.93 |
1,046.84 |
12 |
52 |
0.629062 |
1,509.75 |
84.12 |
465.80 |
0.00 |
26.78 |
235.75 |
832.65 |
22,169.87 |
933.05 |
26 |
66 |
0.430446 |
1,033.07 |
538.84 |
0.00 |
41,875.02 |
22.69 |
419.94 |
0.00 |
41,097.09 |
−41,403.48 |
Total |
42,474.94 |
4,593.94 |
14,760.66 |
41,875.02 |
2,365.47 |
6,712.41 |
16,521.86 |
— |
−21,120.16 |
Summed at full precision and then rounded, as above; here four of the eight totals differ from
the sum of the twenty-seven rounded annual cells, each by a cent or two: claims_death
(4 593,94 € against 4 593,95 €), claims_lapse (14 760,66 € against 14 760,64 €), expenses
(2 365,47 € against 2 365,48 €) and index_credit (16 521,86 € against 16 521,85 €). index_credit
agrees at the cent on the Cap design and does not here.
Every Indexjahr figure in this table is the annual-step model’s: the index_credit column, the
budget ratio and the two rows the designs are compared on are bit-identical, because the Indexjahr
is an annual construction and the conversion did not touch it. Only claims_death, claims_lapse and
expenses moved, for the reasons given under the anchor.
The single most instructive row is k = 9. On the Cap design that year credits nothing; on the
Quote design the same twelve returns credit max(0,60 × 6,4402 %, 0) = 3,8641 % of G(9), which is
675,83 € at fund level. At k = 8 the ranking reverses — the Cap design credits 8,90 % against the
Quote’s 0,60 × 13,4548 % = 8,0729 % — because Example A’s give-up was concentrated in three months
while the Quote gives away 40 % of the year in every state. The two designs are not
interchangeable and they fail differently, which is why a product specification may not describe one
and price the other. Over the whole projection the Quote design credits 16 521,86 € against the Cap
design’s 3 577,46 €, and its index_budget_ratio() is 0,9782 against 0,2082 — on this path, at
these levels, q = 60 % on a 17 %-volatility price index is close to a fair spend of the budget and
C = 3,00 % a month is not.
The four designs at Rentenbeginn#
All four are the same 40-year-old paying 2 400,00 € a year to 67 under a 90 % Beitragsgarantie at
i_g = 1,00 %, differing only in what the declared surplus buys. Every column below is per policy
at policy year k = n = 27, Rentenbeginn, so the credit columns are the ledger credit_cum_pp(27) and are
larger than the frame’s fund-level totals, which carry the decrements: the anchor’s 4 851,44 € of per-policy index
credits is the 3 577,46 € of the index_credit column above, before survivorship.
Model point |
Design |
Index path |
Index credits |
Safe-arm credits |
Account |
Guaranteed capital |
Benefit |
Monthly Rente |
|
|---|---|---|---|---|---|---|---|---|---|
1 (anchor) |
Cap 3,00 %/month |
eqidx_vol17 |
4,851.44 |
0.00 |
73,511.39 |
63,171.44 |
73,511.39 |
183.78 |
0.2082 |
2 |
Quote 60 % |
eqidx_vol17 |
28,216.23 |
0.00 |
98,534.74 |
86,536.23 |
98,534.74 |
246.34 |
0.9782 |
3 |
Quote 100 % |
houseidx_vol5 |
46,118.84 |
0.00 |
116,178.75 |
104,438.84 |
116,178.75 |
290.45 |
1.6482 |
11 |
Sichere Verzinsung |
eqidx_vol17 |
0.00 |
25,967.50 |
95,425.52 |
84,287.50 |
95,425.52 |
238.56 |
— |
Read the last row first: the safe arm beats the anchor’s Cap design by 21 914,13 € of terminal
capital on this path. That is not an argument against the product — it is one realisation of a payoff
whose expected value the research file puts slightly above the safe arm, with a two-in-three chance
of a zero year — but it is exactly why the Wahlrecht comparison belongs in a specification and why
always_index is a modelling choice rather than a recommendation. Model point 3 shows the other end:
a volatility-targeted house index at a 100 % participation rate credits 46 118,84 €, and its
index_budget_ratio() of 1,65 says that at 5 % volatility the shipped Cap and Quote are, if anything,
too generous for the budget — which is the same calibration failure as the anchor’s, pointing the
other way. All four columns are the same 2,50 % of surplus, spent differently.
Two corrections made to these notes at the model stage#
The model was built to the specification above and reproduces it; two numbers in the Known modeling pitfalls list did not survive contact with it, and the notes rather than the model were corrected.
Pitfall 2 said that an implementation flooring each month at zero would get
S = +9,60 %onk = 9(Example B). 9,60 points is what the cap gave away that year,7,00 − (−2,60); flooring each capped month at zero gives3,00 + 3,00 + 3,00 + 0,60 + 3,00 = +12,60 %. The pitfall’s point is unchanged and the corrected figure is now in it.Pitfall 9 asked for
db_pp(k) < av_pp(k+1)in every year the index credited something. That is false at early durations for a reason the product intends: the Mindesttodesfallschutz floor of 32 400,00 € exceeds the account untilk = 12, so the death benefit is larger than the account at any timing there. The assertion that carries the pitfall’s meaning — that the death benefit is struck on the balance before the year’s credits — isav_pp_at(k,"AFT_GUAR") < av_pp(k+1)in every year that credited, and that is what the pitfall now asks for.
Valuation and reserve pointers#
This library projects 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. § 341f HGB requires a provision at the versicherungsmathematisch berechneter Wert of the obligations, including profit shares already allocated and after deducting the present value of future premiums — the prospective method, with a retrospective fallback REG-R54. For this product the phrase “profit shares already allocated” is the operative one: every locked-in index credit is an allocated profit share and is inside the reserve from the moment it is credited, which is exactly what
credit_cum_pptracks. The discount rate is capped by the DeckRV REG-R14 and is topped up by the Zinszusatzreserve where the reference rate falls below the tariff rate REG-R17. delib computes none of this.The guarantee is an option and this model prices none of it. The Beitragsgarantie at Rentenbeginn, plus a ratchet that makes every credit permanent, is a written put whose cost rises with every good year — unlike a plain maturity guarantee, under which a bad year can be recovered by a good one. A deterministic path values it at zero except where it happens to bind (model point 9). A time-value-of-options-and-guarantees calculation re-runs this recursion, the election path and the index path per scenario, and that is what the model is shaped to feed.
Solvency II. Technical provisions are a best estimate — probability-weighted future cash flows discounted at the relevant risk-free term structure — plus a risk margin REG-R1 REG-R2 REG-R4.
BEL = Σ_t v(t) · liability_cf(t)over the recursion above. An Indexpolice sits in insurance with profit participation, not in index-linked and unit-linked insurance R15, and its future discretionary benefits — the declared rate, and therefore the option budget — are the substance of its best estimate. No Solvency II treatment of future discretionary benefits, management actions or contract boundaries in this library was read from a retrieved instrument REG-R2, so every such figure would be unverified.The declared rate is a management action, and treating it as a fixed assumption is the model’s largest valuation-side simplification. A market-consistent valuation would make
b(t)a function of the projected investment result under the MindZV floor REG-R18; here it is an input.IFRS 17. The archetypal direct-participating contract, measured under the variable fee approach; the fulfilment-cash-flow engine is this same projection, and grouping, CSM and risk adjustment are out of scope. The VFA mechanics were not read and are unverified REG-R55.
Key sensitivities and model risks#
In rough order of leverage for this product.
The Cap, and its calibration against the option budget. The single largest lever, and the one parameter that cannot be chosen freely — a point both retrieved AVB make in their own words, the Cap being set annually “auf der Grundlage von Angeboten mehrerer Finanzinstitute” on the surplus, the Bewertungsreserven Sockelbetrag and market volatility and dividend yield [S2] Ziffer 3.3 Absatz 2 b). At the shipped pair the research file’s own arithmetic gives an expected annual credit of about 2,97 % against a 2,50 % budget, with a 65 % chance of a zero year; risk-neutrally the same strip prices at about 1,7 %, below the budget. Moving the Cap from 2,5 % to 4,0 % — inside the plausible band and with no other change — moves the expected credit by more than the whole of the guaranteed interest. The shipped 3,00 % now has one external reference point: Allianz’s own worked illustration runs at 3,2 % [S5]. Any expected return quoted for this product without its volatility assumption is meaningless.
The index path’s volatility. Volatility enters twice and in opposite directions: it makes the cap bind more often, lowering the expectation, and it makes the annual floor worth more, raising it. At the 5 % annualised volatility of
houseidx_vol5the cap almost never binds and the payoff approaches the index return; at 25 % the expected credit is dominated by the floor. Model points 1 and 3 exist to be compared for exactly this reason.The base
Gof the participation — settled, and the model’s reading is the carriers’. Both retrieved AVB strike the participation on the whole Policenwert at the start of the Indexjahr, excluding that year’s premiums and Zuzahlungen: “Bezugsgröße für die →Indexpartizipation ist der →Policenwert zu Beginn des →Indexjahres” [S2] Ziffer 3.3 Absatz 2 e), and [S7] § 3 Ziffer 2 to the same effect.index_base_ppis that quantity. This was the largest unquantified uncertainty in the file; it is no longer one, and the sub-account and Überschussguthaben readings are withdrawn. What remains a modelling choice is a narrower point the AVB expose: R+V takes the value present “das gesamte Versicherungsjahr”, so a mid-year Kapitalentnahme reduces the base pro rata, and Allianz excludes the daily surplus attaching to in-year premiums. delib models neither partial withdrawals nor in-year surplus — the whole Beitrag falls in the first month of the Versicherungsjahr and the base is struck there — so neither refinement bites here.The declared surplus rate, held level and held exogenous — and, on the retrieved evidence, held low. It is the option budget, so it scales the whole index result linearly. Assekurata’s 2026 survey puts the average declared laufender Überschusszins on Indexpolicen at 3,07 % against delib’s shipped 2,50 %, and even classic private annuities at 2,62 % R20; Stuttgarter’s own published sichere Verzinsung is 2,16 % [S8]. The shipped rate is about half a point below the index-segment average, which understates every index credit by the same proportion; it is a shipped input and is not changed here. Both retrieved AVB also define the budget more widely than the model does — the year’s minimum share of the Bewertungsreserven is part of it alongside the declared surplus, and at Allianz the surplus enters net of Verwaltungskosten ([S2] Ziffer 3.3, [S7] § 3 Ziffer 9) — so
opt_budget_ppis a lower bound on the contractual budget as well. And because the feedback from the Garantieniveau to the asset mix to the declared rate is not modeled, the Garantieniveau sensitivity the model reports is only the maturity-floor effect and omits the budget effect entirely. A user comparing model point 9’s 100 % guarantee with the anchor’s 90 % is seeing half the real difference.The election path. Switching model point 1 to
always_safeturns the contract into a klassische Rentenversicherung and changes the terminal capital by the whole difference between a certain 2,50 % a year and a lottery with a two-in-three chance of nothing. The path is a std assumption with no evidence behind it in either direction.The mid-year-exit convention — settled, and the model’s convention is the carriers’. Both AVB credit the participation only at the start of the following Indexjahr and add nothing pro rata for the running one; on surrender Allianz adds only a pro-rata Schlussüberschussanteil and Bewertungsreserven Sockelbetrag, neither of which is an index credit [S2] Ziffer 9.2, [S7] § 3 Ziffer 5 R2. It remains a real cash-flow difference against the alternative convention — a few per cent of the cohort leaves each year and each forfeits a credit a pro-rating contract would pay — but delib is no longer choosing between two unevidenced readings. It still interacts with the surrender-timing incentive — which the annual grid resolved in the policyholder’s favour by construction, and which the monthly grid instead makes visible: the eleven unfavourable months of each Indexjahr now carry exits, and only a behavioural response to the incentive is missing.
Lapse. No index-specific rate exists and the GDV publishes one undifferentiated market-wide Stornoquote (2,56 % in 2023) that cannot be split by product or duration R19, so the duration shape is std. On a ratcheting contract the late years carry the largest capital, so the assumption governs how much of the accumulated guarantee is ever paid at Rentenbeginn rather than surrendered at a discount.
The Rentenfaktor, and the two-index mortality problem behind it. The reported annuity is a std 25,00 € per 10 000 € against a std period-table mortality proxy, and the two are not mutually calibrated. The real basis is DAV 2004 R, a generational table in age and calendar year REG-R49; a period-table proxy priced at a 40-year-old’s annuitisation twenty-seven years out understates the liability by a margin that dwarfs every other assumption here. The model therefore reports the annuity and does not compute one, and the specification says which of the two numbers is authoritative: neither.
Charges, and the un-modeled MindZV loop. Every charge level is std; the sector Verwaltungskostenquote runs from under 2 % to over 4 % REG-R53, and BaFin polices the level R16 REG-R35. Because the model does not return 50 % of the cost result to the policyholder REG-R18, changing an expense assumption changes
net_cfwithout changing what the policyholder receives — the one place where the model’s economics are knowingly incomplete.The three index-specific give-ups that appear nowhere. The option dealing spread is inside the Cap, the house-index level fee and volatility-target drag are inside the index, and the dividend yield of a price index is forgone entirely. The model represents all three only through the level of the Cap or the Quote it is given, so a user who raises the Cap without asking what the insurer could actually buy has silently removed them.