Technical Notes#
Status: Drafted 2026-08-29 with no document retrieved; re-verified against the primary documents on 2026-08-30, when 19 of this product’s 32 source entries were opened and read.
Scope note. These notes specify a reference liability cash-flow projection model — model name
Sofort_DE_S, monthly grid — for the representative composite German sofortbeginnende
private Rentenversicherung defined in product-spec.md (same directory). This is not any single
insurer’s product; the clause evidence behind it comes from four condition sets covering the
immediate annuity — [S4], [S2], [S6] and the GDV template [S1]. [S#] and [R#] tags refer to
sources.md (numbering carried from _research/sofortrente.md; frozen); [REG-R#] tags the
cross-product reference library references/regulatory-and-actuarial-references.md (its own frozen
R1–R56 numbering). std marks standardizations introduced for the reference implementation;
unverified marks claims 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.
Retrieval conditions. These notes were drafted under a policy that blocked all egress, with
the WebSearch budget exhausted before work on this product began, so not one search was run for
the Sofortrente and the draft rested on the authoring model’s own knowledge of German practice.
The citations have since been re-verified against the primary documents: 19 of the 32 entries in
sources.md read Retrieved: yes and 12 read no, and the clause-level mechanics below are now
quoted from documents that were opened and read. The levels are not. One carrier’s guaranteed
annuity scale [S8] and one carrier group’s surplus declaration [S10] reached the corpus, neither was
used to fit anything, and every level in class (b) and class (c) below is therefore still std,
as are several in class (a). That is stated once here and tagged at every occurrence rather than
repeated in prose.
Model scope and conventions#
Purpose. Project gross best-estimate liability cash flows for one Sofortrente — the Einmalbeitrag in, the guaranteed annuity and the Überschussrente out, the guaranteed instalments continuing to beneficiaries inside a Rentengarantiezeit, the Kapitalrückgewähr on death, the Hinterbliebenenrente, and insurer expenses — on an expected, probability-weighted basis, for a single model point. Undiscounted. Discounting, the Deckungsrückstellung recursion, the Zinszusatzreserve, the Solvency II best estimate and risk margin and the IFRS 17 measurement are out of scope and are cited, not computed (see Valuation and reserve pointers).
Mortality is the model. After Rentenbeginn the contract has no premium stream, no surrender value, no lapse decrement, no paid-up state and no policyholder option of any kind R1 R2 R5 REG-R28. The only decrement is death; where a Hinterbliebenenrente is in force there are two lives and the liability runs to the second death. There is no lapse machinery anywhere in this model, and that is a cited product feature rather than an omission.
Projection frequency and origin. Monthly grid,
t = t_start() … proj_len() − 1, counted in complete months from Vertragsbeginn. Monthtis the civil month beginning at thet-th month-start after inception. The index is 0-based, which is the library-wide convention: the first month of a new-business point ist = 0,policy year = t // 12 + 1, andproj_len()is the exclusive end of the frame, so the frame isrange(t_start(), proj_len()), its last month index isproj_len() − 1and it carriesproj_len() − t_start()rows.t = 0is both the month the Einmalbeitrag is received and — under the representative vorschüssig convention — the month the first instalment is paid. An in-force model point opens att = duration_mth_init(), the number of months the contract has already run.The model carries duration, not the calendar. Every step in this product falls on a policy anniversary: the Überschussrente increase [S15], the expense inflation index, the attained-age step. Nothing happens on 31 December, so — unlike
frlib’sRente_FR_S, where revalorisation is a calendar event — no cells needs the civil month.entry_yearis carried all the same, for two reasons that are not cash flows: it is the cohort key of the generational mortality surface, and it selects the contract’s own Höchstrechnungszins vintage REG-R14 REG-R15.Timing conventions std. The Einmalbeitrag arrives at the start of month 0. Instalments are paid at the start of a payment month; under
advance(vorschüssig) the first falls att = defer_mths(), underarrears(nachschüssig) att = defer_mths() + 12/payment_freq. A payment is made if the payee is alive at the start of that month — the same instant — so the survival index of a payment is alwaystand the two timings differ only in which months carry an instalment. Deaths fall during montht, so a life dying in monththas already received the instalment due att; the Kapitalrückgewähr is settled net of it. Expenses accrue at the start of the month.Age basis and generation std.
age(t, life) = entry_age(life) + t // 12: age last birthday at inception, incrementing at each 12-month multiple of it. The birth year is a separate model point attribute and is never derived from the projection year, because the mortality surface is generational and the cohort is its key REG-R49. The shipped model points satisfyentry_year == birth_year(1) + entry_age(1), which is a std internal-consistency convention, not a contract fact; a real book will carry a fractional offset of up to a year.Unisex pricing. The tariff annuity factor is computed on a unisex blend of the sex-distinct proxy tables, at a std portfolio male share; the model point’s own
sexdrives only the best-estimate decrement path REG-R34 REG-R49. Lettingsexreach the annuity factor reproduces a tariff unlawful in Germany since 21 December 2012 and is pitfall 10.Currency, sign and rounding. EUR throughout.
net_cf(t)is income-positive (the Einmalbeitrag +, annuity payments, death benefits and expenses −), with the outgo-positive orientation of these notes published asliability_cf(t) = −net_cf(t). No intermediate rounding; displayed cash flows to the cent and probabilities to six decimals std.Out of scope and said so. The Bewertungsreserven share, which continues in the payout phase [S3] REG-R24 but is path- and balance-sheet-dependent on the HGB accounts REG-R9 REG-R18; a commuted settlement of the Restgarantiezeit, whose basis was not established (research gap 10); taxation, which falls on the annuitant rather than on the insurer’s liability REG-R41; and any management action on the declared surplus.
Model point attributes#
Attribute |
Type |
Meaning |
Exercised by |
|---|---|---|---|
|
int |
Key; |
all |
|
str |
Reference, reporting only |
all |
|
EUR |
The Einmalbeitrag, |
all |
|
int |
Annuitant’s age last birthday at Vertragsbeginn |
all |
|
int |
Calendar year of Vertragsbeginn; the Höchstrechnungszins vintage |
10, 13 |
|
int |
Annuitant’s Geburtsjahr — the generational table key |
all |
|
enum {M, F} |
Decrement only; the tariff is unisex REG-R34 |
11, 12 (F) |
|
int |
Aufschubzeit, 0 for a pure Sofortrente |
6 |
|
int |
Rentengarantiezeit, 0 = none |
1, 5, 7–10, 12–14 |
|
enum {none, full} |
Kapital-/Beitragsrückgewähr |
3, 6 |
|
float 0–1 |
Hinterbliebenenrente as a fraction of the annuitant’s annuity; 0 = rider off |
4, 5 |
|
int |
Second life’s age last birthday at Vertragsbeginn |
4, 5 |
|
int |
Second life’s Geburtsjahr |
4, 5 |
|
enum {M, F} |
Second life’s sex, decrement only |
4, 5 |
|
int {12, 4, 2, 1} |
Instalments per year, |
7 (4), 8 (1), 11 (2) |
|
enum {advance, arrears} |
vorschüssig / nachschüssig std |
9 |
|
float |
The tariff Rechnungszins |
10, 13 |
|
enum {none, konstant, teildynamisch, volldynamisch} |
Überschussverwendung |
5, 8, 11, 13 (konstant); 6, 12 (voll); 14 (none) |
|
EUR |
Guaranteed instalment carried on an in-force point; 0 = derive by equivalence |
10 |
|
int |
Months already elapsed at the valuation date; the frame’s first |
10 |
|
float |
Policies the point represents |
all |
Two configurations, not two premium forms. A Sofortrente has exactly one premium form — a
single Einmalbeitrag — so the pair of shapes this model must serve is not level/revisable but
derived against given: a new-business point (annuity_pp_init == 0) whose guaranteed annuity
the model strikes from single_prem by equivalence on the tariff basis, and an in-force point
(annuity_pp_init > 0) whose annuity was struck years ago on a basis the model does not reproduce
and is therefore carried. Model point 10 is the second kind. On it the pricing identity
check_equivalence() is not asserted, and the notes say so rather than letting the check pass
vacuously without explanation.
surv_age, surv_birth_year and surv_sex are ignored where surv_pct == 0, and the shipped table
carries zeros there. sex is carried but must not enter the annuity factor REG-R34.
The fourteen shipped model points. Between them they exercise every option, all four payment frequencies, both payment timings, all four Überschussverwendung forms, both derived and given annuities, three tariff vintages and both ends of the issue-age envelope. Model point 1 is the worked example’s anchor cell.
# |
|
age / year / cohort / sex |
options |
|
|
surplus |
what it is for |
|---|---|---|---|---|---|---|---|
1 |
100,000 |
65 / 2025 / 1960 / M |
guar 10 y |
12 / adv |
1,00 % |
teildyn. |
anchor; the representative design |
2 |
100,000 |
65 / 2025 / 1960 / M |
none |
12 / adv |
1,00 % |
teildyn. |
the plain Leibrente, against which every option’s price is read |
3 |
100,000 |
65 / 2025 / 1960 / M |
refund |
12 / adv |
1,00 % |
teildyn. |
the implicit solve |
4 |
100,000 |
65 / 2025 / 1960 / M |
surv 60 %, life 2 aged 62 / 1963 / F |
12 / adv |
1,00 % |
teildyn. |
the joint-life leg, and a longer second-life horizon |
5 |
150,000 |
68 / 2025 / 1957 / F |
guar 20 y and surv 100 %, life 2 aged 70 / 1955 / M |
12 / adv |
1,00 % |
konstant |
the combined option, and the |
6 |
100,000 |
62 / 2025 / 1963 / M |
defer 5 y, refund |
12 / adv |
1,00 % |
volldyn. |
the Aufschubzeit and its Beitragsrückgewähr |
7 |
100,000 |
65 / 2025 / 1960 / M |
guar 10 y |
4 / adv |
1,00 % |
teildyn. |
quarterly instalments |
8 |
200,000 |
72 / 2025 / 1953 / M |
guar 15 y |
1 / adv |
1,00 % |
konstant |
annual instalments |
9 |
100,000 |
65 / 2025 / 1960 / M |
guar 10 y |
12 / arr |
1,00 % |
teildyn. |
nachschüssig, the timing switch of research gap 11 |
10 |
100,000 |
65 / 2012 / 1947 / M |
guar 15 y |
12 / adv |
1,75 % |
konstant |
in force: |
11 |
25,000 |
85 / 2025 / 1940 / F |
none |
2 / adv |
1,00 % |
konstant |
boundary: oldest entry, smallest ticket, half-yearly |
12 |
500,000 |
60 / 2025 / 1965 / F |
guar 30 y |
12 / adv |
1,00 % |
volldyn. |
boundary: youngest entry, largest ticket, longest guarantee |
13 |
250,000 |
70 / 2022 / 1952 / M |
guar 10 y |
12 / adv |
0,25 % |
konstant |
the pre-2025 Höchstrechnungszins vintage REG-R15 |
14 |
100,000 |
65 / 2025 / 1960 / M |
guar 10 y |
12 / adv |
1,00 % |
none |
surplus off, so the Überschussrente’s contribution is isolatable |
Point 10’s annuity_pp_init is std: no Standmitteilung was located at any carrier [S15], so
the in-force annuity is a round figure of the right order for a 2012 tariff at 1,75 %, and its
Überschussrente is reconstructed from the form and the elapsed duration rather than carried.
State variables#
Variable |
Description |
Updated |
|---|---|---|
|
First projected month = |
once |
|
Exclusive end of the frame, so |
once |
|
|
once |
|
Attained age = |
monthly |
|
Completed policy years = |
monthly |
|
Contractual, 1-based policy year = |
monthly |
|
|
monthly |
|
Annual second-order rate at |
monthly |
|
|
monthly |
|
Annual first-order rate on the unisex blend, used only for pricing |
monthly |
|
Second-order probability alive at the start of month |
recursion |
|
|
monthly |
|
First-order survival to the start of month |
recursion |
|
|
once |
|
|
once |
|
1 while the Rentengarantiezeit runs, else 0 |
monthly |
|
Whether an instalment falls due at the start of month |
monthly |
|
The guaranteed instalment struck by equivalence from |
once |
|
The garantierte Rente in force in month |
monthly |
|
The Überschussrente instalment in month |
monthly |
|
|
monthly |
|
Cumulative guaranteed instalments paid to and including month |
recursion |
|
|
monthly |
|
|
monthly |
|
|
monthly |
|
Probability that any payment obligation remains at the start of month |
monthly |
There is no account value, no surrender-value, no paid-up and no lapse state variable, and no
av_pp_at / lapse_rate / lapse_rate_mth cells anywhere in the model. That is a statutory fact
about the product R1 R2 R5 REG-R28, not a modeling simplification, and it is asserted by
pitfall 17 rather than left to inspection.
pols_if(t) is not a policy count: it is the probability that a payment obligation of any kind
still stands — the guarantee period running, the annuitant alive, or the survivor’s annuity in
payment — and it is the weight the maintenance expense is carried on. It keeps the library’s name
for the expense weight, and its docstring says what it is so the shared conventions suite can apply
the payout-product exemption. At the frame’s first row it is pols_if_init() exactly, on a
new-business point and on an in-force one alike.
Assumption inputs#
Three classes. Class (a) is contractual or statutory and is cited where the corpus supports it; class (b) is the insurer’s current discretionary scale, revisable annually within the statutory minimum REG-R18 REG-R24; class (c) is the modeller’s view of experience.
(a) Contractual / guaranteed elements (cited)#
Input |
Value |
Basis |
|---|---|---|
Premium |
One Einmalbeitrag at |
[S2] [S4] [S6]; structural |
Nettoeinmalbeitrag |
|
[S4]; α std, class (c) |
Equivalence |
|
[S2] [S4] R10 |
Mortality basis, pricing |
A first-order annuitant table, generational, unisex tariff: DAV 2004R (Aggregattafel) at one carrier [S2] [S3], “NÜRNBERGER Tafel 2013 R” at another [S4]. DAV 2004 R is the profession’s reference, not a mandate |
|
Tariff Rechnungszins |
Model-point attribute; 1,00 % for 2025–2026 business, at or below the vintage cap. Every retrieved tariff prices at its vintage’s cap; no below-cap pricing was observed |
|
Guarantee |
The garantierte Rente is immutable for life; § 163 VVG is the only channel and it is narrow — and narrower here, Abs. 1 re-setting a premium this product does not have |
|
Payment frequency and timing |
Monthly, vorschüssig, first instalment at inception. Frequencies read at four sources; timing contradicted — two AVB pay in arrears, and the model’s convention is retained in this pass |
[S1] [S2] [S6] [S7]; timing std, contradicted by [S4] [S6] |
Rentengarantiezeit |
|
[S4]; also [S1] [S5] [S6] [S8] R23 |
Kapital-/Beitragsrückgewähr |
|
[S2] [S6] R23 |
Hinterbliebenenrente |
|
[S1] [S9]; percentages unverified — the model conditions state none |
Death-benefit exclusivity |
The refund is not combined with a guarantee period or a survivor’s annuity |
std, research gap 10 |
Surrender / paid-up / lapse |
None, once the Rentenbezug has begun — “Eine sofort beginnende Rentenversicherung können Sie nicht kündigen” [S4], “Sie können Ihren Vertrag nicht kündigen. Die Rückzahlung des Einmalbeitrages können Sie nicht verlangen” [S1]. § 168 Abs. 1 and 2 VVG never engage on a single-premium Leibrente, and § 169 falls with them |
|
Überschussbeteiligung |
A statutory entitlement continuing through the payout phase, Bewertungsreserven hälftig under § 153 Abs. 3 VVG; method not prescribed. In payment the surplus is “jeweils nur für ein Versicherungsjahr zugesagt” [S2] |
[S2] [S3] [S4] [S10] REG-R24 |
Limiting age |
|
(b) Insurer-discretionary current elements#
One carrier group’s declaration is now read and one carrier’s realised increase with it, but neither is this model’s scale. Bayern-Versicherung declares, for Einzel-Rentenversicherungen of tariff generations 2015–2025, a Zinsüberschussanteil während des Rentenbezugs of “3,35 % (2,5 %) abzüglich Rechnungszins” — 2,35 % over a 1,00 % tariff rate for 2026 — with “Ein Risiko- oder Verwaltungskostenüberschussanteil wird nicht gewährt” in the payout phase, allotted “am Ende des Versicherungsjahres”, and with no Schlussüberschussbeteiligung and no Mindestbeteiligung at the Bewertungsreserven for immediate annuities [S10]. Debeka reports the resulting increase on its own Sofortrente: 0,75 % for 2024 [S8]. Five carriers’ 2026 laufende Verzinsung run 2,4 %–3,0 % R21 R22. The figures below are not calibrated to any of these and remain std: they were chosen to make the worked example reproduce, they use a two-component shape the one retrieved declaration does not, and recalibrating them would move the golden tests. The gap between the two is recorded rather than closed. The class is labelled (b) rather than (a) precisely because the insurer may reduce it — the konstante Überschussrente included, “Falls wir in einem Jahr nicht ausreichend Überschüsse erwirtschaften, kann die Zusatzrente reduziert werden” [S6] R21.
Input |
Snapshot value |
Basis |
|---|---|---|
|
|
std (b1) |
|
|
std (b1) |
Crediting mechanic |
The Bonusrente ratchet: an increment, once bought, is not taken back, so |
[S4]; levels std |
Increase date |
The policy anniversary, not a calendar date: “erstmals zum Ende des ersten Versicherungsjahres” [S4], “am Ende des Versicherungsjahres” [S10], “jeweils für den Monat vor dem Jahrestag der Versicherung” for the Bewertungsreserven [S10] |
[S4] [S6] [S10] [S15] |
Bewertungsreserven share |
Excluded, explicitly — though at one carrier an immediate annuity receives no Mindestbeteiligung at all [S10] |
[S3] [S10] REG-R24; see Model scope |
Reduction of a declared Überschussrente |
Not modelled; the projection is a central estimate, and the sensitivity section prices the downside instead. The contracts permit reduction: [S6] on the Zusatzrente, [S2] on the Garantie-PLUS-Rente |
(b1) The market’s own description gives the shape: the constant (or flexible) form is highest at outset and flat only “so lange die Überschüsse so hoch sind, wie bei Rentenbeginn prognostiziert” R19, the volldynamic form lowest at outset and rising with each declaration and unable to fall R19 [S4], the teildynamic form intermediate on both axes and able to fall R19 R21; the rating house’s own treatment of the choice is titled “Die Qual der Wahl” R20. The opening 20 % for the constant form stays unverified and stays std: the 15 %–25 % gap between guaranteed and total annuity is not stated in any of the five retrieved insurer packs, nor in either consumer source, so there is still no observed range behind it. The growth rates are round numbers consistent with a Zinsüberschuss of one to two points over a 1,00 % Rechnungszins — a shape the one retrieved declaration supports at 2,35 % for 2026 [S10] without validating these numbers, which were not derived from it. The three forms are not calibrated to equal present value in this implementation, and a user who needs them to be must do that calibration; asserting equality is a wrong test, not a right one.
(c) Behavioural / experience assumptions (modeller’s view)#
The behavioural set is empty, and that is the product. There is no lapse rate, no paid-up rate, no dynamic surrender formula and no option take-up rate, because there is no option to take up after Rentenbeginn R1 R2 R5. What remains in class (c) is the basis — mortality and expenses — and every level in it is std.
Mortality — the shipped std proxy. DAV 2004 R and DAV 2004 R-Bestand are DAV property, are not
public and are not redistributed here REG-R47 REG-R49. mort_table.csv ships a constructed
proxy in four series — {FIRST, SECOND} × {M, F} — over attained ages 50 to 120, built as follows
std:
q_base(x) = 1 − exp( −( A + B·c^x·(c − 1)/ln c ) ), A = 0.0002, B = 1.5e-5, c = 1.10
FIRST/M = 1.250000 × q_base(x) SECOND/M = 1.20 × FIRST/M
FIRST/F = 0.795455 × q_base(x) SECOND/F = 1.20 × FIRST/F
q_base is the Gompertz–Makeham law the research file constructs and prints, with life expectancy
24,29 years at 65 and q(65) = 0.00789, q(75) = 0.02001, q(85) = 0.05078. The anchor is that
the 45 % / 55 % unisex blend of the FIRST series reproduces q_base(x) —
0.45 × 1.250000 + 0.55 × 0.795455 = 1.00000025 — so the model’s tariff basis is the research
file’s own basis and any figure printed there can be traced into the model. The reproduction is
exact to 2,5 × 10⁻⁷ relative rather than exactly exact: the female factor that would close it
is 0.4375 / 0.55 = 0.795454545… and the table carries its six-decimal rounding. The identity
holds in that sense at ages 50 to 119; age 120 is the closing row, where all four series are set to
1.0 and the survival path therefore reaches zero inside the horizon. Every series is also
capped at 1.0, which binds only on SECOND/M at attained ages 117 to 119, where 1.20 × FIRST
would otherwise exceed 1 and stop being a probability. The Data docstring states this anchor.
How far the proxy is from a real tariff basis — measured, for the first time, against a carrier
quotation. Debeka publishes a Berechnungsbeispiel for its own Sofortrente on tariff S1,
Stand 01.01.2025: 50 000 € single premium, 20-year Rentengarantiezeit, guaranteed 151 € a month
at age 65 [S8] — 302 € per 100 000 €. This model, on the same age, the same 1,00 % tariff rate and
the same guarantee period, net of both std charges, produces 349 €, about 16 % higher.
The difference is attributable to the proxy’s mortality being lighter than a real first-order German
annuitant basis, to α = 2,5 % and β = 2,0 % being too small, or to both; the corpus does not
separate them, because no carrier’s charge parameter was established [S11] [S12]. The tables are
not refitted in this pass: mort_table.csv, improvement_table.csv and the two charge loadings
are unchanged, the worked example and every golden figure with them, and the anchor identity above
still holds exactly as stated. What changes is that the reader now has a number for how much a
std decrement surface is worth on this product, which is the point of recording it. A build
that wants a market-consistent annuity should strengthen the first-order basis or the loadings
against company data, not tune the proxy to hit 302 €.
The generational surface. A period table is the wrong object for a forty-year annuity REG-R49.
improvement_table.csv ships λ(x) in two series std:
λ_SECOND(x) = 0.0150 for x ≤ 70
= 0.0150 × (105 − x) / 35 for 70 < x < 105
= 0.0000 for x ≥ 105
λ_FIRST(x) = 1.25 × λ_SECOND(x)
and the model constructs
q(x, sex, cohort, basis) = q_table(x, sex, basis) × (1 − λ(x, basis))^(cohort + x − mort_base_year)
with mort_base_year = 2025 std, so the base tables are the period tables of calendar year
2025 and the exponent is simply the calendar year in which the life attains age x, less 2025. The
exponent may be negative for a cohort attaining an age before 2025 — an in-force point issued in
2012 reads pre-2025 mortality, which is correct and is not floored.
The Sicherheitszuschlag is two-dimensional and both dimensions are shipped. For an annuity,
prudence means lighter mortality and a stronger assumed improvement trend, so a proxy
reproducing only the level is not a proxy for the table REG-R47. Here the first-order basis is
20 % lighter in level (SECOND = 1.20 × FIRST) and improves 25 % faster (λ_FIRST = 1.25 λ_SECOND),
both std with no observed range (research gap 12). The wedge between the two bases is the
systematic Risikoüberschuss this product’s surplus is largely financed from REG-R47 REG-R18;
collapsing them destroys both halves of the mechanic and is pitfall 9.
Unisex blend. mort_rate_at_age(x, "U", basis) = ρ_M · q(x, "M", basis) + (1 − ρ_M) · q(x, "F", basis)
with ρ_M = mix_male = 0.45 std. The direction is argued rather than observed: a unisex tariff
struck on sex-distinct tables is a better deal for women than for men, so the realised female share of
a voluntary annuitant portfolio sits above the population share REG-R34. No German carrier
publishes a portfolio mix (research gap 13).
Expenses and loadings. The two loadings are tariff parameters and enter the pricing identity; the two expenses are best-estimate cash flows and do not. Keeping them apart is the point of the three-class split, and confusing them is pitfall 12’s neighbour.
Input |
Value |
Basis |
|---|---|---|
|
2,5 % of |
std (c1) |
|
2,0 % |
std (c1) |
|
2,0 % of |
std (c1) |
|
60 €, accrued monthly |
std (c1) |
|
1,50 € per instalment paid |
std (c1) |
|
1,5 % p.a., stepping at the policy anniversary |
std (c1) |
|
0.45 |
|
|
2025 |
|
|
121 |
|
|
1e-8, the tolerance every |
|
|
1e-10, 200 — the bisection controls for the refund solve |
(c1) No charge or expense parameter was established at any carrier (research gap 8), so none of
these has an observed range. The loadings are argued in product-spec.md footnote 6. The incurred
expenses are sized so that the tariff over-recovers modestly, which is the right direction and
the source of the Kostenüberschuss: on the anchor cell the acquisition loading takes 2 500 € against
2 200 € incurred, and the β loading collects roughly 2 % of the Nettoeinmalbeitrag against a
maintenance-plus-payment stream of about 78 € a year. A user with real expense data should replace all
six numbers; the two loadings and the four expenses must be replaced together, because moving one
without the other silently changes the modelled profit rather than the modelled cost.
Input files#
Inputs are external CSVs in the model folder’s parent, read once per model by an unparameterized
Data Space — the annuallife/TradLife_A layout. Five files, and every one of them is read:
File |
Index columns |
Value columns |
Read by |
|---|---|---|---|
|
|
the 21 attributes above |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
basis takes FIRST or SECOND, sex takes M or F — the unisex blend is a model construction,
not a table row, so the CSV never carries a rate no real table would. Every file except
model_point_table.csv carries a final provenance column, one tag per row, per the library’s second
ruling; a model point is a configuration rather than an assumption and is the only exemption. The
scalar assumptions of class (c) are Projection References rather than a sixth CSV, and are tagged in
the tables above.
Cash flow components and recursions#
Notation (defined once, used throughout)#
Symbol |
Cells |
Meaning |
|---|---|---|
|
— |
month index from Vertragsbeginn, 0-based, |
|
|
first projected month index; exclusive end of the frame |
|
|
instalments per year; months between them, |
|
|
deferment in months; guarantee period in years |
|
|
Einmalbeitrag; |
|
|
acquisition and annuity-administration loadings |
|
|
tariff Rechnungszins; |
|
|
the garantierte Rente per instalment |
|
|
Überschussrente instalment; total instalment |
|
|
opening surplus fraction; its annual growth |
|
|
attained ages of annuitant and second life |
|
|
birth years — the generational keys |
|
|
annual mortality improvement rate at age |
|
|
annual first-order and second-order rates |
|
|
second-order survival to the start of month |
|
|
first-order survival, used only in the pricing sums |
|
|
deaths during month |
|
|
1 while the Rentengarantiezeit runs, else 0 |
|
|
survivor’s percentage; 0 when the rider is off |
|
|
expected instalments payable at |
|
|
cumulative guaranteed instalments; the refund then due |
|
|
value at |
|
|
limiting age; portfolio male share |
|
|
maintenance p.a.; per-instalment cost; inflation |
|
— |
acquisition expense incurred at |
q, λ, l, γ, F are dimensionless; SP, R, U, A, K, C and every cash flow are EUR;
ä is dimensionless (a value per unit of instalment). Note that ä is not the market’s a12:
a12 = ä / m, so the research file’s a12(65, 1,00 %) = 20.426 corresponds to ä = 245.11.
The projection frame, t and proj_len()#
horizon_mths(life) = 12 × ( omega_age − entry_age(life) )
t_start() = duration_mth_init()
proj_len() = max( horizon_mths(1),
first_pay_mth() + 12 × guar_years(),
horizon_mths(2) if surv_pct > 0 )
proj_len() is the exclusive end of the frame, counted in months from Vertragsbeginn, so
result_cf() is indexed range(t₀, n), result_cf().index[-1] == proj_len() − 1 and the frame
carries proj_len() − t₀ rows — the library’s 0-based range(proj_len()) ruling, asserted for
every model point. The three terms are the annuitant’s survival horizon, the guarantee period’s own
end, and the second life’s horizon where a Hinterbliebenenrente is in force. All three are
needed. Stopping on the annuitant’s horizon alone truncates a younger survivor’s tail
(pitfall 18); stopping on the guarantee alone truncates the life annuity. On the anchor cell
horizon_mths(1) = 12 × (121 − 65) = 672, the guarantee ends at month 120, and proj_len() = 672,
so the frame carries 672 rows, t = 0 … 671.
The frame starts at t₀, which is a product fact and is not asserted by the conventions suite:
a new-business point opens at 0 and an in-force point at the duration it has already run. What is
asserted is contiguity — a gap in t means a period was dropped, and no reading of proj_len()
would catch it.
Generational mortality construction#
q(x, s, g, b) = mort_rate_at_age(x, s, b) × ( 1 − improve_rate_at_age(x, b) )^( g + x − 2025 )
mort_rate_at_age(x, "U", b) = ρ_M · q_tab(x, "M", b) + (1 − ρ_M) · q_tab(x, "F", b)
mort_rate(t, life) = q( x(t, life), sex(life), g(life), "SECOND" ) — projection
mort_rate_tariff(t, life) = q( x(t, life), "U", g(life), "FIRST" ) — pricing
mort_rate_mth(t, life) = 1 − (1 − mort_rate(t, life))^(1/12) **[std]**
lives_if(t, life) = lives_if(t − 1, life) × (1 − mort_rate_mth(t − 1, life)), lives_if(t₀) = 1
lives_death(t, life) = lives_if(t, life) − lives_if(t + 1, life)
tariff_lives(k, life) = tariff_lives(k − 1, life) × (1 − mort_rate_tariff_mth(k − 1, life)), = 1 at k = 0
Three properties matter and each is a test. q depends on t only through the attained age and on
the model point only through the cohort, so two points with the same entry age and different birth
years read different rates at the same age (pitfall 7). The improvement is inside the surface, not
applied on top of a period rate as a separate factor keyed to the projection year — that would walk
diagonally across cohorts and is the classic error (pitfall 8). And the pricing basis and the
projection basis are different objects: mort_rate_tariff is first order and unisex,
mort_rate second order and sex-specific, and mort_rate_tariff(t, life) < mort_rate(t, life) at
every t (pitfall 9).
Because q(120) = 1 in every series and λ(120) = 0, lives_if reaches zero at or before
horizon_mths(life) — the monthly conversion of q = 1 is 1, so the path in fact goes to zero in
the first month of attained age 120, eleven months inside the horizon — and the decrements close:
Σ_t lives_death(t, life) = lives_if(t₀, life) with lives_if(n, life) = 0. That is
check_lives_roll_fwd(). The horizon is an upper bound and no cash flow depends on which of the
two it is.
Payment months, the certain floor and the payment factors#
pay_period_mths() = 12 // payment_freq() ( p )
first_pay_mth() = defer_mths() under `advance`
= defer_mths() + pay_period_mths() under `arrears`
is_payment_mth(t) = ( t ≥ first_pay_mth() ) and ( (t − first_pay_mth()) mod p == 0 )
guar_end_mth() = first_pay_mth() + 12 × guar_years()
certain_floor(t) = 1 if first_pay_mth() ≤ t < guar_end_mth() else 0
payment_factor(t) = max( γ(t), l_a(t) ) + δ · (1 − l_a(t)) · l_s(t) · (1 − γ(t))
The survival index of a payment is t under both timings. With month starts as the payment
instants, an advance instalment for month t and an arrears instalment covering [t − p, t) are both
paid at the start of month t and both require the payee to be alive then. So the two conventions
differ only in which months carry an instalment, and a G-year guarantee covers G × m
instalments under either — which is exactly what guar_end_mth() encodes, since G · m · p = 12G
regardless of m (pitfall 13).
The max is what makes the Rentengarantiezeit a certain floor rather than a second stream.
While the guarantee runs the full instalment is payable whether the annuitant is alive or not R23;
an additive γ + l_a would pay 1 + l_a for the whole guarantee, nearly doubling the outgo in the
first years (pitfall 3). The survivor leg is gated by (1 − γ(t)) for the same reason: inside the
guarantee the full instalment already goes out, so adding the survivor’s percentage on top would pay
1 + δ (pitfall 4).
The survivor gate is (1 − l_a(t)) · l_s(t) — the probability that the annuitant is dead and the
second life alive at the payment instant, assuming independence std. The Anwartschaft lapsing
on the second life’s prior death needs no separate rule: l_s(t) is already zero then.
Conversion — the guaranteed annuity struck at inception#
ä = Σ over payment months k of v^(k/12) · F̃(k)
where F̃(k) is payment_factor(k) computed on the first-order survival path l̃ rather than on
l. Then, where the refund is not elected,
net_single_prem() = SP × (1 − α)
annuity_pp_derived() = SP_net / ( ä × (1 + β) )
and annuity_guar_pp(t) = annuity_pp_init() where that is positive, else annuity_pp_derived(),
level for the whole of the annuity’s life [S6] REG-R27.
ä is a pricing quantity and must stay acyclic: it depends on the tariff basis, the elected
options and the tariff interest rate, and on nothing that depends on the path. In particular it does
not depend on surplus_form — the Überschussrente is financed out of surplus actually earned,
not priced into the guarantee (pitfall 1’s neighbour), so annuity_pp_derived() is invariant to
surplus_form and check_equivalence() closes on every derived point regardless of it.
check_tariff_int_rate() asserts tariff_int_rate() ≤ max_tariff_int_rate() + roll_fwd_tol, the cap
being read from hoechstrechnungszins_table.csv at entry_year() REG-R14 REG-R15. The cap binds
the reserving rate and, through § 138 Abs. 1 VAG, the rate a new tariff may be priced at REG-R8;
a carrier may price below it and one is observed doing so [S6], which is why the check is an
inequality.
The Kapitalrückgewähr and its implicit equation#
Where refund_form == "full", the death benefit at a death during month t is
C(t) = C(t − 1) + ( R if is_payment_mth(t) else 0 ), for t > t₀
C(t₀) = R · ( (t₀ − first_pay_mth()) // p + 1 ) if t₀ ≥ first_pay_mth() else 0
K(t) = max( SP − C(t), 0 )
The recursion opens at the frame’s first month t₀ rather than one month before it: on a
new-business point t₀ = 0, first_pay_mth() = 0 under vorschüssig and the opening value is the
single instalment paid at t = 0; on an in-force point it is the instalments the contract has
already been paid by the valuation date. Nothing is ever indexed at t = −1.
— the Einmalbeitrag less the guaranteed instalments already paid, floored at zero std
(research gap 10). C(t) includes the instalment due at t itself, because that instalment was paid
at the start of the month in which the death occurred.
The pricing equation is then implicit in R, because K depends on R and the point at which
K reaches zero depends on R too:
g(R) = R · ä · (1 + β) + Σ_t v^(t/12) · d̃_a(t) · max( SP − n(t)·R, 0 ) = SP_net
with n(t) the number of instalments paid by month t and d̃_a the first-order death density.
g is increasing in R on (0, R_max] where R_max = SP_net / (ä (1 + β)) is the no-refund
annuity, and g(0) = SP · Σ v^(t/12) d̃_a(t) < SP_net on any basis with a positive interest rate, so
a root exists and bisection on [0, R_max] converges. The reference implementation bisects to
solve_tol in at most solve_max_iter steps, evaluating the sum inline from the cached
tariff_lives path rather than through a cells parameterized by the trial R.
Computing R_max and then subtracting a refund cost is a different — and wrong — answer (pitfall
5). refund_pv() is published so the identity can be seen: check_equivalence() asserts
SP_net == annuity_pp_derived() · ä · (1 + β) + refund_pv() to roll_fwd_tol.
During an Aufschubzeit no instalment has been paid, so C(t) = 0 and K(t) = SP: the same
machinery gives the Beitragsrückgewähr on death before Rentenbeginn without a second mechanic,
and refund_form == "none" on a deferred point gives the pure deferred annuity in which the fund of
those who die is forfeited to the survivors.
The Überschussrente#
U(t) = R · u₀ · (1 + ψ)^( duration(t) − D / 12 ) for t ≥ first_pay_mth()
= 0 otherwise
A(t) = R + U(t)
with u₀ and ψ read from surplus_scale_table.csv at surplus_form(). Two properties are
asserted. It steps at the policy anniversary, not monthly: U(t) is constant across each block of
twelve months (pitfall 14). And it ratchets: A(t) ≥ A(t − 1) at every t, which is what the
Bonusrente crediting mechanic means arithmetically — an increment bought as paid-up annuity does not
come back off R23 (pitfall 15). Both are check_annuity_roll_fwd().
U(t) is an insurer-discretionary current quantity, never a guaranteed cash flow. A projection is
a central estimate of a stream the insurer may reduce R21; the sensitivity section prices the
downside rather than the model reserving for it.
Expected cash flows in month t#
Premium. One inflow, and only on a new-business point, because an in-force point’s frame does not
contain t = 0:
premiums(t) = SP × pols_if_init if t == 0, else 0
Annuity payments and guarantee claims. The instalment splits into a payment to a living payee and a payment to a beneficiary inside the guarantee:
annuity_payments(t, "ANNUITANT") = pols_if_init · A(t) · l_a(t) · 1{payment month}
annuity_payments(t, "SURVIVOR") = pols_if_init · A(t) · δ (1 − l_a(t)) l_s(t) (1 − γ(t)) · 1{payment month}
claims(t, "GUARANTEE") = pols_if_init · A(t) · γ(t) · (1 − l_a(t)) · 1{payment month}
so that, at every payment month,
annuity_payments(t) + claims(t, "GUARANTEE") = pols_if_init · A(t) · payment_factor(t)
which is check_payment_factor(), and inside the guarantee payment_factor(t) = 1 exactly, which is
check_guarantee_certain(). Splitting the stream this way is not cosmetic: it separates annuity outgo
from death outgo, which is the shape of the product, and it makes the two commonest errors — the
additive certain floor and the survivor paid on top of the guarantee — visible in a column rather than
buried in a total.
Refund claims.
claims(t, "REFUND") = pols_if_init · K(t) · lives_death(t, 1)
Expenses.
expenses(t) = 1{t = 0} · pols_if_init · ( expense_acq_rate · SP + expense_acq_fixed )
+ ( c_e / 12 ) · infl_factor(t) · pols_if(t)
+ 1{payment month} · c_p · infl_factor(t) · pols_if_init · payment_factor(t)
The acquisition term appears only at t = 0 and therefore only on a new-business point. The
per-instalment term is weighted by payment_factor(t), not by pols_if(t): a survivor’s annuity in
payment is a second payment run, and inside the guarantee the beneficiary’s instalment costs the
same to pay as the annuitant’s.
Obligation weight and the net flow.
pols_if(t) = pols_if_init · min( 1, max( γ(t), l_a(t) ) + 1{δ > 0} (1 − l_a(t)) l_s(t) )
liability_cf(t) = annuity_payments(t) + claims(t) + expenses(t) − premiums(t)
net_cf(t) = − liability_cf(t)
net_cf is income-positive, the library-wide convention, and liability_cf carries these notes’
outgo-positive orientation; both are published as columns rather than one standing for the other.
result_cf() publishes, indexed by t, in this order: pols_if, premiums, annuity_payments,
claims_guarantee, claims_refund, expenses, liability_cf, net_cf. A second frame
result_pols() publishes lives_if_1, lives_if_2, certain_floor, payment_factor,
annuity_guar_pp, annuity_surp_pp, annuity_pp, refund_pp, cum_annuity_guar_pp and pols_if.
Published check_* identities#
Nine, each returning a bool over all t and, where a per-period residual exists, publishing it at
check_*_resid(t). The library’s first ruling makes check_net_cf() mandatory; the rest are this
product’s own roll-forward and pricing identities.
Check |
Identity |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Monthly processing order#
For t = t₀ … n − 1, in this order:
Once, before the loop: read the model point; check
tariff_int_rate()against the vintage cap REG-R15; computefirst_pay_mth(),guar_end_mth()andpay_period_mths(); build the tariff survival pathl̃on the first-order unisex basis; computeä; and strikeR— directly whereannuity_pp_init() > 0, bySP_net / (ä (1 + β))where no refund is elected, and by bisection on the implicit equation where one is.Set the attained ages
x_a(t),x_s(t)fromentry_age + t // 12, and readq⁽²⁾from the generational surface at each life’s own cohort and sex.Advance the second-order survival:
l_a(t),l_s(t)froml(t − 1)andmort_rate_mth(t − 1).Set
γ(t)andis_payment_mth(t).Set the instalment:
U(t)from the surplus form and the completed policy yearduration(t), thenA(t) = R + U(t). AccumulateC(t)and setK(t) = max(SP − C(t), 0).Compute
payment_factor(t)at the payment instantt.Start of month — premium. At
t = 0only,premiums(0) = SP · pols_if_init.Start of month — instalments.
annuity_payments(t, ·)andclaims(t, "GUARANTEE")on the factors of step 6.During the month — deaths.
lives_death(t, life) = l(t) − l(t + 1); settleclaims(t, "REFUND")onK(t), which is already net of the instalment paid at step 8.Expenses. Acquisition at
t = 0; maintenance onpols_if(t); the per-instalment cost onpayment_factor(t)where step 4 found a payment month.Form
liability_cf(t)andnet_cf(t); rolll,CandUforward tot + 1.
At t = n − 1 the projection ends with no maturity payment and no tail state: l_a(n) = 0 and the
guarantee has expired, so nothing remains to be paid.
Known modeling pitfalls#
These are the specific ways an implementation of this product looks right and is wrong. Each one becomes a test.
Projecting only the guaranteed annuity. The Überschussrente is not guaranteed — in payment the RfB-funded part is “jeweils nur für ein Versicherungsjahr zugesagt” [S2] — but it is a projected cash flow, on typical market designs 15 % to 25 % of the payment unverified R21. A model publishing only
annuity_guar_ppmodels less than the payment. Assertannuity_pp(t) > annuity_guar_pp(t)at everyton any point withsurplus_form != "none", and exact equality on model point 14, which switches surplus off.Decrementing the guaranteed instalments during the Rentengarantiezeit. Inside the guarantee the payment is certain: “Stirbt die versicherte Person während der Rentengarantiezeit, so wird die monatliche Rente bis zum Ablauf der Rentengarantiezeit weiter gezahlt” [S4]. Assert
annuity_payments(t) + claims(t, "GUARANTEE") == pols_if_init() × annuity_pp(t)for every payment month witht < guar_end_mth(), exactly.Adding the certain floor instead of taking a max.
γ + l_apays1 + l_afor the whole guarantee — on the anchor cell, nearly double the outgo for ten years. Assertpayment_factor(t) == 1.0inside the guarantee wheresurv_pct == 0, and≤ 1 + surv_pcteverywhere.Paying the survivor’s annuity on top during the guarantee. The survivor leg carries a
(1 − γ(t))gate. Assertannuity_payments(t, "SURVIVOR") == 0for everyt < guar_end_mth()on model point 5, and> 0for sometafter it.Evaluating the Kapitalrückgewähr instead of solving it. The equation is implicit in
R. Assertcheck_equivalence()on model point 3, and thatannuity_pp_derived()there differs from the naiveSP_net/(ä(1 + β))less a refund cost computed at that annuity — the two answers are not the same number and the difference is not a rounding.Measuring the refund against the total annuity. It is netted against the guaranteed instalments std. Assert
refund_pp(t)is invariant tosurplus_form— compare model point 3 against a copy of it with the surplus switched off.Indexing the mortality surface by projection year instead of by birth year. A period-table implementation reads age 66 in calendar year 2026 and walks diagonally across cohorts; a generational one reads
(g = 1960, x = 66)whatever the projection year. Assert that two points with the sameentry_ageand differentbirth_yeargive differentmort_rateat the same attained age.Shipping a period-table proxy at all. A period proxy applied to a forty-year annuity understates the liability by a margin that dwarfs every other assumption REG-R49. Assert
mort_rate_gen(x, s, g, b) != mort_rate_at_age(x, s, b)whereverg + x != mort_base_yearandimprove_rate_at_age(x, b) > 0, and thatäcomputed withλ ≡ 0is strictly smaller thanä.Applying the first-order margin to the level only. For an annuity, prudence reaches the trend as well REG-R47. Assert
mort_rate_tariff(t, life) < mort_rate(t, life)at everyt, and that the ratiomort_rate_tariff / mort_ratefalls witht— a level-only margin would hold it constant.Letting
sexinto the tariff. German new business has been unisex since 21 December 2012 REG-R34. Assert that two points identical but forsexproduce the sameannuity_pp_derived()and differentlives_if.Counting the Einmalbeitrag on an in-force model point. Assert
premiums(t) == 0for everytin model point 10’s frame, andΣ premiums == single_prem() × pols_if_init()on model point 1.Opening an in-force point at
t = 0. The frame starts at the duration already run. Assertresult_cf().index[0] == duration_mth_init()andpols_if(t_start()) == pols_if_init()on model point 10, and that the acquisition expense appears nowhere in its frame.Getting the arrears offset wrong. Under
arrearsthe first instalment falls atdefer_mths() + p, and aG-year guarantee still coversG × minstalments. Assert that model point 9 has no payment att = 0, has one att = 1, and carries the same number of guaranteed instalments as model point 1.Compounding the Überschussrente monthly. It steps at the policy anniversary [S15]. Assert
annuity_surp_pp(t) == annuity_surp_pp(t − 1)whenevert % 12 != 0, and that the step att % 12 == 0is exactly(1 + surplus_growth).Letting the total annuity fall. The Bonusrente ratchets R23. Assert
annuity_pp(t) ≥ annuity_pp(t − 1)at everyt—check_annuity_roll_fwd().Discounting the published cash flows at the tariff Rechnungszins.
ireaches the projection only throughäandrefund_pv(); the model publishes undiscounted flows and a best estimate discounts at the EIOPA curve REG-R4. Assert thatnet_cffor a givenannuity_pp_initis invariant totariff_int_rate, which isolates the one legitimate channel.Inventing a lapse, a surrender value or a paid-up state. There are none R1 R2 R5 REG-R28. Assert that no
lapse_rate,lapse_rate_mth,av_pp_at,cv_ppor surrender cells exists, and that the decrements close:Σ_t lives_death(t, life) + lives_if(n, life) == lives_if(t₀, life)for each life in scope.Running the projection past the annuitant but not past the second life.
proj_len()takes the maximum of the two horizons and the guarantee’s end. Assert that on model point 4 — annuitant 65, second life 62 —proj_len() == 12 × (omega_age − surv_age), the survivor’s horizon and not the annuitant’s, and thatannuity_payments(proj_len() − 1)is finite and non-negative there.
Policyholder behaviour modelling#
There is none to model, and this is a cited product feature. Once the Rentenbezug has begun the policyholder has no right of termination, no Rückkaufswert, no Beitragsfreistellung, no capital option and no transfer R1 R2 R5 REG-R28. Every option this product has is elected once, at inception and is thereafter a parameter rather than a decision: the Rentengarantiezeit, the Kapitalrückgewähr, the Hinterbliebenenrente, the payment frequency and the Überschussverwendung form. The model therefore carries no lapse decrement, no dynamic behaviour formula and no take-up rate, and that absence is the reason its result depends more purely on the mortality basis and the surplus assumption than any other model in this library.
Behaviour enters the basis, not the projection, as selection effects std:
Annuitisation anti-selection. A Sofortrente is bought voluntarily, disproportionately by people who expect to live long, and the German market does not medically underwrite it (
product-spec.mdfootnote 4, still unverified — no retrieved condition set says so either way, though [S5] lists “keine Möglichkeit unsererseits zur Risikoprüfung” as a defining feature of an immediate-annuity tariff). DAV 2004 R is an annuitant-experience table understood to carry Selektionsfaktoren for exactly that REG-R49, so the effect belongs inside the basis rather than beside it — which is why this model applies no annuitant adjustment factor on top of the first-order table. A projection applying population mortality to an annuity book overstates deaths by a wide margin REG-R52.Sex self-selection and the direction of
ρ_M. A unisex tariff struck on sex-distinct tables is a better deal for women, so the realised female share of a voluntary annuitant portfolio sits above the population share. That is the direction, not the magnitude, ofρ_M = 0.45; no carrier publishes a portfolio mix (research gap 13).Option selection. The Rentengarantiezeit and the Kapitalrückgewähr are chosen disproportionately by buyers who expect to die early, the Hinterbliebenenrente by those with a younger spouse. Both push realised experience away from the tariff basis and no source quantifies either unverified.
Two behaviours at the boundary, handled outside the projection. Proof of life: failure to return the annual certificate suspends payment until it arrives — a timing effect on an unchanged obligation, not modelled std. And the Aufschubzeit window, in which a surrender right may survive because the termination bar has not yet bitten R1 R2: no carrier’s terms were established, and the corpus’s one candidate turned out to be a hybrid unit-linked deferred annuity rather than a short-deferment Sofortrente [S14] (research gap 17 closes as a negative). The base run switches the deferment off.
Worked example#
Configuration. Model point 1, the anchor cell, is the representative design of product-spec.md
with every attribute stated: policy_id = SOF-000001; single_prem = 100,000.00 €;
entry_age = 65; entry_year = 2025; birth_year = 1960; sex = M; defer_years = 0, so
defer_mths() = 0 and first_pay_mth() = 0; guar_years = 10, so guar_end_mth() = 120 and the
first 120 monthly instalments are certain; refund_form = none; surv_pct = 0.00, with surv_age,
surv_birth_year and surv_sex unused and carried as zeros; payment_freq = 12 and
payment_timing = advance, so an instalment falls at the start of every month from t = 0;
tariff_int_rate = 0.0100, the Höchstrechnungszins in force for 2025 business REG-R15;
surplus_form = teildynamisch; annuity_pp_init = 0.00, so the guaranteed annuity is derived by
equivalence; duration_mth_init = 0, so the frame opens at t = 0; and pols_if_init = 1.0.
Hence horizon_mths(1) = 12 × (121 − 65) = 672, proj_len() = 672, and the frame runs
t = 0 … 671 — 672 monthly rows, the whole of the projection.
Assumptions, each tagged. Tariff. Acquisition loading α = 2,5 % of the Einmalbeitrag
std and annuity administration loading β = 2,0 % of the annuity value std, so
net_single_prem() = 97,500.00 €. Tariff Rechnungszins i = 1,00 % REG-R15, discounting monthly
at v^(k/12) with v = 1/1.01. Mortality. The base tables are the research file’s Gompertz–Makeham
law q_base(x) = 1 − exp(−(A + B·c^x·(c − 1)/ln c)) with A = 0.0002, B = 1.5 × 10⁻⁵, c = 1.10
std, split by sex as FIRST/M = 1.250000 × q_base and FIRST/F = 0.795455 × q_base so that the
mix_male = 0.45 std unisex blend reproduces q_base exactly, and the second-order series
SECOND = 1.20 × FIRST std; improvement λ_SECOND(x) = 1,5 % to age 70, tapering linearly to
zero at 105, with λ_FIRST = 1.25 × λ_SECOND std; base year mort_base_year = 2025 std,
so the annuitant’s cohort exponent at age 65 is 1960 + 65 − 2025 = 0 and the anchor’s first-year
tariff rate is the base rate unmodified; limiting age omega_age = 121 std with q = 1 at
attained age 120. Monthly rates by 1 − (1 − q)^(1/12) std. The tariff factor ä is computed
on the first-order unisex path and the projection decrements on the second-order male path
REG-R34 REG-R47. Surplus. surplus_form = teildynamisch gives surplus_init_pct = 10 % and
surplus_growth = 1,0 % p.a. std, so the Überschussrente opens at a tenth of the garantierte
Rente and steps up 1 % at each policy anniversary, ratcheting under the Bonusrente mechanic R23.
Expenses. Acquisition 2,0 % of single_prem + 200 € = 2,200.00 € at t = 0 std; maintenance
60 € a year accrued monthly on pols_if(t) std; 1,50 € per instalment paid std; all
three inflating at expense_infl = 1,5 % p.a., stepping at the policy anniversary std.
Decrements. Death only — no lapse, no surrender, no paid-up, no option exercise R1 R2 R5.
Tolerances. roll_fwd_tol = 1e-8; the refund solve is not exercised on this cell.
All amounts in euros; pols_if and the survival probabilities to six decimals, cash flows to the
cent, and every total summed at full precision and then rounded rather than accumulated from
rounded cells.
The derived quantities#
Everything below follows from four numbers, and the first three of them a reader can check with a calculator.
Quantity |
Cells |
Value |
|---|---|---|
Nettoeinmalbeitrag |
|
97,500.0000 € |
Tariff annuity factor |
|
263.5711140230 |
the same as the market’s |
— |
21.9642595019 |
PV of the refund leg |
|
0.0000 € — no refund elected |
garantierte Rente |
|
362.6658241684 € per month |
Überschussrente at outset |
|
36.2665824168 € |
Total instalment at outset |
|
398.9324065852 € per month |
So the anchor configuration buys a guaranteed 362,67 € a month for life per 100 000 € of Einmalbeitrag, with a ten-year Rentengarantiezeit, and a total 398,93 € a month at outset of which a tenth is the non-guaranteed Überschussrente.
This is 4,9 % below the 381 € the research file’s own arithmetic gives, and the whole of the
difference is the annuity factor: a12 = 21.9643 here against 20.897 there, +5.1 %, and
1 / 1.05107 = 0.9514. The research file computes a12 from a period table with
a12 = a_due − 11/24; this model reads a generational surface, so the same annuitant is
priced on mortality that improves for the whole of the fifty-six-year projection. The gap is
the Trendfunktion REG-R49, it is one-sided, and it is the reason a period-table proxy is
pitfall 8 rather than a simplification.
The frame#
672 monthly rows, t = 0 … 671. The table shows the first policy year in full and the first
month of the second, where the Überschussrente steps, then the two months either side of the
guarantee’s expiry and one row every ten years to the horizon. Money
to the cent, pols_if to six decimals; the Total row is summed over all 672 rows at full
precision and then rounded.
|
|
|
|
|
|
|
|
|
|---|---|---|---|---|---|---|---|---|
0 |
1.000000 |
100,000.00 |
398.93 |
0.00 |
0.00 |
2,206.50 |
−97,394.57 |
97,394.57 |
1 |
1.000000 |
0.00 |
398.54 |
0.40 |
0.00 |
6.50 |
405.43 |
−405.43 |
2 |
1.000000 |
0.00 |
398.14 |
0.79 |
0.00 |
6.50 |
405.43 |
−405.43 |
3 |
1.000000 |
0.00 |
397.75 |
1.19 |
0.00 |
6.50 |
405.43 |
−405.43 |
4 |
1.000000 |
0.00 |
397.35 |
1.58 |
0.00 |
6.50 |
405.43 |
−405.43 |
5 |
1.000000 |
0.00 |
396.96 |
1.97 |
0.00 |
6.50 |
405.43 |
−405.43 |
6 |
1.000000 |
0.00 |
396.57 |
2.37 |
0.00 |
6.50 |
405.43 |
−405.43 |
7 |
1.000000 |
0.00 |
396.17 |
2.76 |
0.00 |
6.50 |
405.43 |
−405.43 |
8 |
1.000000 |
0.00 |
395.78 |
3.15 |
0.00 |
6.50 |
405.43 |
−405.43 |
9 |
1.000000 |
0.00 |
395.39 |
3.54 |
0.00 |
6.50 |
405.43 |
−405.43 |
10 |
1.000000 |
0.00 |
395.00 |
3.94 |
0.00 |
6.50 |
405.43 |
−405.43 |
11 |
1.000000 |
0.00 |
394.60 |
4.33 |
0.00 |
6.50 |
405.43 |
−405.43 |
12 |
1.000000 |
0.00 |
394.57 |
4.72 |
0.00 |
6.60 |
405.89 |
−405.89 |
60 |
1.000000 |
0.00 |
373.69 |
27.09 |
0.00 |
7.00 |
407.78 |
−407.78 |
119 |
1.000000 |
0.00 |
338.58 |
63.75 |
0.00 |
7.43 |
409.76 |
−409.76 |
120 |
0.839834 |
0.00 |
338.22 |
0.00 |
0.00 |
6.34 |
344.56 |
−344.56 |
121 |
0.837965 |
0.00 |
337.47 |
0.00 |
0.00 |
6.32 |
343.79 |
−343.79 |
240 |
0.555887 |
0.00 |
226.20 |
0.00 |
0.00 |
4.87 |
231.07 |
−231.07 |
360 |
0.189111 |
0.00 |
77.83 |
0.00 |
0.00 |
1.92 |
79.75 |
−79.75 |
480 |
0.007818 |
0.00 |
3.26 |
0.00 |
0.00 |
0.09 |
3.35 |
−3.35 |
600 |
0.000000 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
−0.00 |
671 |
0.000000 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
0.00 |
−0.00 |
Total |
258.921518 |
100,000.00 |
101,091.33 |
3,428.03 |
0.00 |
4,228.28 |
8,747.64 |
−8,747.64 |
The Total row is summed at full precision and then rounded, and here that visibly matters.
Adding the 672 rounded net_cf cells gives −8 747,47 € against the −8 747,64 € above —
17 cents apart, because 672 half-cent roundings do not cancel. The same happens in the other
columns: annuity_payments accumulates to 101 091,23 € from the rounded cells against
101 091,33 € at full precision, and claims_guarantee to 3 428,04 € against 3 428,03 €. Any test
of this table must sum the model’s own values, not the printed ones.
Reading the frame in four steps. Month 0 takes in the whole Einmalbeitrag, pays the first
instalment and the acquisition expense, and is the only positive net_cf in the projection.
Months 0 to 119 are inside the Rentengarantiezeit: pols_if is exactly 1.000000, and
annuity_payments + claims_guarantee is exactly the instalment, the split between them moving
from the annuitant to the beneficiaries as survival falls — 398,54 € against 0,40 € in month 1,
338,58 € against 63,75 € in month 119. Month 120 is the discontinuity: the guarantee expires,
claims_guarantee goes to zero for the rest of the projection, and the outgo drops by a sixth in
one step, from 409,76 € to 344,56 €. Months 120 to 671 are the pure Leibrente: the
instalment keeps rising with the Überschussrente while survival falls faster, so the cash flow
decays to nothing by about month 600.
The state behind those rows:
|
|
|
|
|
|
|
|
|---|---|---|---|---|---|---|---|
0 |
1.000000 |
1.000000 |
1.000000 |
362.67 |
36.27 |
398.93 |
362.67 |
11 |
0.989151 |
1.000000 |
1.000000 |
362.67 |
36.27 |
398.93 |
4,351.99 |
12 |
0.988171 |
1.000000 |
1.000000 |
362.67 |
36.63 |
399.30 |
4,714.66 |
119 |
0.841553 |
1.000000 |
1.000000 |
362.67 |
39.66 |
402.33 |
43,519.90 |
120 |
0.839834 |
0.000000 |
0.839834 |
362.67 |
40.06 |
402.73 |
43,882.56 |
240 |
0.555887 |
0.000000 |
0.555887 |
362.67 |
44.25 |
406.92 |
87,402.46 |
360 |
0.189111 |
0.000000 |
0.189111 |
362.67 |
48.88 |
411.55 |
130,922.36 |
671 |
0.000000 |
0.000000 |
0.000000 |
362.67 |
62.69 |
425.35 |
243,711.43 |
annuity_surp_pp is flat across t = 0 … 11 and steps at t = 12, which is the anniversary rule
of pitfall 14; payment_factor is exactly 1 up to t = 119 and equals lives_if_1 from t = 120,
which is the certain floor of pitfall 3.
Three independent checks#
Each rebuilds a cell of the table a different way from the way the model builds it.
1. The guaranteed instalment, from the annuity factor alone. The model builds ä by summing
672 discounted survival-weighted payment months. A reader with the printed ä does one division:
R = 97,500.00 / (263.5711140230 x 1.02) = 97,500.00 / 268.8425363035 = 362.6658241684
and in the market’s own unit, 100,000 x 0.975 / (12 x 21.9642595019 x 1.02) = 362.67 €. The
opening total instalment is then 362.6658241684 x 1.10 = 398.9324065852, the 1.10 being
1 + u₀ with the teildynamisch opening share of 10 % and the growth exponent still zero in
the first policy year (duration(0) − D / 12 = 0).
2. Month 1, rebuilt from the mortality table. The annuitant is 65 and born in 1960, so the
cohort exponent is 1960 + 65 − 2025 = 0 and the generational surface returns the shipped rate
unmodified. From mort_table.csv, FIRST/M at 65 = 0.009857796019, so
q⁽²⁾ = 1.20 x 0.009857796019 = 0.011829355222; monthly,
1 − (1 − 0.011829355222)^(1/12) = 0.000991165035; hence
lives_if(1) = 1 − 0.000991165035 = 0.999008834965 -> 0.999009
annuity_payments(1) = 398.9324065852 x 0.999008834965 = 398.5369987327 -> 398.54
claims_guarantee(1) = 398.9324065852 x 0.000991165035 = 0.3954078526 -> 0.40
and the two add back to 398.9324065852 exactly, because inside the Rentengarantiezeit
payment_factor(1) = max(1, 0.999009) = 1. The unisex tariff rate at the same age comes off
the same two rows: 0.45 x 0.009857796019 + 0.55 x 0.006273146506 = 0.007886238787, which is
q_base(65) to 2,5 × 10⁻⁷ relative — the anchor of the shipped proxy, and the reason every
annuity factor printed in _research/sofortrente.md traces into this model.
3. Month 0’s expense and cash flow, from the parameter list. No survival enters either:
expenses(0) = 0.02 x 100,000 + 200 (acquisition)
+ 60 / 12 (one month's maintenance, pols_if = 1)
+ 1.50 (one instalment run)
= 2,000.00 + 200.00 + 5.00 + 1.50 = 2,206.50
net_cf(0) = 100,000.00 − 398.93 − 0.00 − 2,206.50 = 97,394.57
and the Kostenüberschuss the tariff is designed to earn is visible in the same two lines: the
acquisition loading takes α x SP = 2,500.00 € and the acquisition expense incurred is
2 200,00 €, a 300,00 € margin at inception std.
Two closure identities#
The decrements close. Death is the only decrement in this product, so the whole cohort must be accounted for by deaths plus survivors:
sum over t = 0 … 671 of lives_death(t, 1) = 1.000000000000
lives_if(672, 1) = 0.000000000000
total = 1.000000000000 = lives_if(0, 1)
That is check_lives_roll_fwd(), and it closes to the last printed digit because q = 1 at
attained age 120 forces the survival path to zero inside the omega_age = 121 horizon.
The cash flow statement closes. Summing the columns at full precision,
100,000.0000000000 premiums
− 101,091.3334710770 annuity_payments − 3,428.0270060962 claims_guarantee − 0.0000000000 claims_refund − 4,228.2772978315 expenses = − 8,747.6377750047 net_cf
which is the printed Total to the cent. That is check_net_cf() summed rather than asserted
per month.
And a third, which is the one worth having. Inside the Rentengarantiezeit the outgo does not depend on mortality at all, so the first ten years of this projection can be computed in closed form with no table:
120 R = 120 x 362.6658241684 = 43,519.8989002072
12 R u₀ x s̈10 at 1 %, s̈10 = (1.01¹⁰ − 1)/0.01 = 10.4622125411
= 435.1989890021 x 10.4622125411
= 4,553.1443206206
total instalments, months 0 … 119 = 48,073.0432208278
The model’s own annuity_payments + claims_guarantee over t = 0 … 119 is
48,073.0432208278 €, agreeing to 7 × 10⁻¹² €. The two errors this closes off are large and
in opposite directions. A model that decremented the guaranteed instalments for survival —
pitfall 2 — would pay only the annuitant’s leg, 44 645,0162147316 €, 7,13 % below. A model
that added the certain floor instead of taking a max — pitfall 3 — would pay
92 718,0594355593 €, 92,87 % above, because it pays 1 + l_a for the whole guarantee.
The variant the notes promised: the same cell nachschüssig#
Model point 9 is model point 1 with payment_timing = arrears and nothing else changed. The
first instalment moves from t = 0 to t = 1, the guarantee window from 0 … 119 to
1 … 120, and the tariff factor falls from 263.5711140230 to 262.6685503335, by 0.9025636895 — and that
difference is itself checkable in one line. Arrears does not pay the instalment at t = 0, worth
1; against that, its guarantee window is 1 … 120 rather than 0 … 119, so the instalment at
t = 120 is certain for it and survival-contingent for advance, worth
v¹⁰ (1 − l̃(120)) = 0.9052869504 × (1 − 0.8923696956) = 0.0974363105. The net is
1 − 0.0974363105 = 0.9025636895, to the last printed digit. The guaranteed instalment rises in
exactly the inverse proportion:
363.9119916441 / 362.6658241684 = 263.5711140230 / 262.6685503335 = 1.00343586
|
|
|
|
|
|
|
|---|---|---|---|---|---|---|
0 |
1.000000 |
100,000.00 |
0.00 |
0.00 |
2,205.00 |
97,795.00 |
1 |
1.000000 |
0.00 |
399.91 |
0.40 |
6.50 |
−406.80 |
2 |
1.000000 |
0.00 |
399.51 |
0.79 |
6.50 |
−406.80 |
3 |
1.000000 |
0.00 |
399.11 |
1.19 |
6.50 |
−406.80 |
12 |
1.000000 |
0.00 |
395.93 |
4.74 |
6.60 |
−407.26 |
120 |
1.000000 |
0.00 |
339.39 |
64.72 |
7.54 |
−411.65 |
121 |
0.837965 |
0.00 |
338.63 |
0.00 |
6.32 |
−344.95 |
240 |
0.555887 |
0.00 |
226.98 |
0.00 |
4.87 |
−231.84 |
Total |
259.081684 |
100,000.00 |
101,038.39 |
3,504.53 |
4,227.99 |
−8,770.91 |
Three things are worth reading off it. t = 0 carries no instalment and no instalment-running
expense — 2 205,00 € against the anchor’s 2 206,50 € — so the nachschüssig cell’s first month is
400,43 € better, exactly one instalment plus its 1,50 € running cost, and it pays that back
over the following fifty-six years: its undiscounted net_cf total is −8 770,91 € against
−8 747,64 €. The guarantee still
covers 120 instalments, now at t = 1 … 120 (pitfall 13). And the annuity is 0,34 % higher,
not 5 %: see the correction recorded at the end of this section.
The other configuration: an in-force point with a given annuity#
A Sofortrente has one premium form, so the pair this model must serve is derived against
given. Model point 10 carries an annuity struck in 2012 on a 1,75 % tariff —
annuity_pp_init = 430.00, duration_mth_init = 156, surplus_form = konstant — and the model
uses it rather than striking one. Its frame opens at t = 156 and runs to 671: 516 rows, and no
t = 0 in them.
|
|
|
|
|
|
|
|---|---|---|---|---|---|---|
156 |
1.000000 |
0.00 |
516.00 |
0.00 |
7.89 |
−523.89 |
157 |
1.000000 |
0.00 |
514.26 |
1.74 |
7.89 |
−523.89 |
168 |
1.000000 |
0.00 |
495.50 |
20.50 |
8.01 |
−524.01 |
179 |
1.000000 |
0.00 |
475.88 |
40.12 |
8.01 |
−524.01 |
180 |
0.918857 |
0.00 |
474.13 |
0.00 |
7.47 |
−481.60 |
240 |
0.689197 |
0.00 |
355.63 |
0.00 |
6.03 |
−361.66 |
Total |
135.648915 |
0.00 |
69,516.79 |
478.05 |
1,192.84 |
−71,187.68 |
premiums is zero in every row — the Einmalbeitrag was paid in 2012, before the valuation
date, and counting it again is pitfall 11 — and the acquisition expense appears nowhere, which is
pitfall 12. pols_if(156) = 1.000000 = pols_if_init(), as it does on a new-business point, because
the projection is conditional on the annuitant being alive at the valuation date. The instalment is
430.00 + 0.20 x 430.00 = 516.00 € and stays there: the konstante Überschussrente has
surplus_growth = 0, so nothing steps at the anniversary and only the expense inflation index does.
check_equivalence() returns True without asserting anything, the annuity having been struck on a
basis this model does not reproduce.
And the cell with the Überschussrente switched off#
Model point 14 is the anchor with surplus_form = none. It derives the identical guaranteed
instalment — 362.6658241684 € — which is the arithmetic statement that ä does not depend on
surplus_form, the Überschussrente being financed out of surplus actually earned rather than
priced into the guarantee. Everything else differs:
Over the whole 672-month frame |
point 1, teildynamisch |
point 14, surplus off |
difference |
|---|---|---|---|
|
101,091.33 |
90,804.02 |
10,287.32 |
|
3,428.03 |
3,097.97 |
330.06 |
|
4,228.28 |
4,228.28 |
0.00 |
|
−8,747.64 |
+1,869.74 |
−10,617.37 |
The whole modelled Überschussrente is 10 617,37 € undiscounted, and none of it is guaranteed. It is also what turns the sign of the undiscounted total: on the guaranteed annuity alone the anchor cell collects 1 869,74 € more than it pays out over fifty-six years, and with the surplus it pays out 8 747,64 € more than it collects. Neither figure is an economic result — these are undiscounted flows and the Einmalbeitrag arrives fifty-six years before the last of them — but the difference between them is exactly the quantity the four Überschussverwendung forms distribute, and pricing it is what a sensitivity on this product is for.
Corrections made to these notes when the model was built#
Seven, all in this document, none in product-spec.md, sources.md or the frozen research
file.
Each is recorded because a reader comparing an earlier draft with this one should not have to
guess which side moved.
check_net_cf’s identity now carries the payment-month indicator. The row in Publishedcheck_*identities readnet_cf(t) == premiums(t) − pols_if_init·A(t)·payment_factor(t) − claims(t,"REFUND") − expenses(t).payment_factor(t)is defined at everyt, not only at payment months, so on a quarterly, half-yearly or annual model point the identity as written subtracted an instalment in months where none was paid. The annuity term is gated by1{payment month}, as the cash flows themselves already were.check_equivalence’s tolerance isroll_fwd_tolscaled by the Nettoeinmalbeitrag. The identity is an equality between euro amounts of the order of 10⁵ and the refund solve converges onRrather than on the residual, so an unscaled 1e-8 would have been a tolerance on the fifteenth significant figure.The shipped mortality series are capped at 1.0.
SECOND = 1.20 × FIRSTexceeds 1 on the male series at attained ages 117 to 119, where a rate is no longer a probability. The cap binds nowhere else, andmort_rate_gencarries the same cap for the case of a negative cohort exponent.lives_ifreaches zero at or beforehorizon_mths(life), not exactly at it. Withq = 1at attained age 120 the monthly rate is 1 in the first month of that age, so the path reaches zero eleven months inside the horizon. The horizon is an upper bound; nothing depends on which of the two it is.The unisex anchor is exact to 2,5 × 10⁻⁷ relative, not exactly exact. The female factor that would make
0.45 × 1.250000 + (1 − 0.45) × f = 1exactly is0.4375 / 0.55 = 0.795454545…; the shipped table carries its six-decimal rounding, 0.795455.The payment-timing convention is worth 0,34 % of the annuity on this product, not 5 %. The figure of about 5 % in Key sensitivities was carried over from
_research/sofortrente.mdsection 8, which computes it asä_due − ä_arrears = 1— the difference between an annual annuity-due and an annual annuity-immediate. On a monthly annuity the difference is one month’s instalment,1 / a12 ≈ 1 / 21.96 ≈ 0.4 %before the guarantee period damps it further. Model points 1 and 9 measure it directly at 0,34 %, and the sensitivity list now says so. The research file is frozen and is not amended; the discrepancy is recorded here instead, and it is a real error in that file’s section 8 rather than a difference of basis.And the convention itself is now known to be the minority one. The 2026-08-30 retrieval opened two AVB that state the first payment date for a Sofortrente, and both pay in arrears: NÜRNBERGER, “Die erste Rente wird einen Monat nach dem vereinbarten Versicherungsbeginn gezahlt. Die garantierte monatliche Rente wird an jedem Monatsersten gezahlt” [S4]; and CosmosDirekt, for which the first instalment on the immediate form falls “ein Jahr, ein halbes Jahr, ein viertel Jahr oder einen Monat nach dem vereinbarten Versicherungsbeginn” according to frequency [S6]. The GDV template pays “an den vereinbarten Fälligkeitstagen” and does not settle it [S1]. The model keeps
payment_timing = advanceon the anchor and every other shipped point. Changing the default would move the worked example, the printed identities and the golden tests together, which is a decision to take deliberately rather than as a side effect of a provenance pass. What changes here is the label: vorschüssig is no longer an unestablished convention filling a gap, it is a std convention that the retrieved market evidence contradicts, and model point 9 already carries the alternative at a measured 0,34 %. A later build that adoptsarrearsas the default should expect the anchor’s guaranteed annuity to fall from 363,91 € to 362,67 € and every golden figure downstream to move with it.The Überschussrente total of the surplus-off comparison is 10 617,37 €, not 10 617,38 €. The difference column of that table was formed by subtracting the two printed totals —
−8 747,64 − 1 869,74— where every other total in this document is summed at full precision and then rounded. At full precision the difference is−8 747,637775 − 1 869,737028 = −10 617,374803, which rounds to −10 617,37. Theannuity_paymentsandclaims_guaranteecells of the same row were already right; onlynet_cffell on the wrong side of a half-cent.tests/test_sofortrente_de.pyasserts the full-precision figure.
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, not reproduced.
The HGB Deckungsrückstellung. A prospective, deliberately prudent reserve on the Rechnungsgrundlagen erster Ordnung of the premium calculation — this contract’s own Rechnungszins, capped at conclusion by § 2 DeckRV REG-R14 REG-R15, and a first-order biometric table REG-R47 — formed to the extent necessary to ensure dauernde Erfüllbarkeit REG-R54. For a Sofortrente it is simply the actuarial present value of the remaining guaranteed annuity, since there are no future premiums to deduct: the same
äthis model computes, evaluated at the attained age rather than at inception. This is the balance sheet the German surplus system actually operates on — the MindZV 90/90/50 floor REG-R18, the RfB ring fence REG-R10, the RfBV ceiling REG-R19 and the § 139 VAG Bewertungsreserven test REG-R9 are all computed on the HGB accounts, not the Solvency II ones.The Zinszusatzreserve. Where the § 5 Abs. 3 DeckRV Referenzzins falls below the contract’s tariff rate, an additional HGB reserve arises REG-R17. It bites hardest on long-duration annuity business and on legacy cohorts: a model point written in 2012 at 1,75 % carries a very different ZZR from one written in 2025 at 1,00 %, which is one reason the shipped table carries both vintages. The ZZR is not computed here, and its release profile is the largest single driver of what a cohort of annuitants will actually be credited REG-R17.
Solvency II best estimate. Probability-weighted future cash flows discounted at the relevant risk-free term structure, plus a risk margin REG-R1 REG-R2 REG-R6, with EIOPA publishing the curves REG-R4.
BEL = Σ_t v(t) × liability_cf(t)over the recursions above. No risk-free rate, cost-of-capital rate, contract-boundary rule or standard-formula shock in this library was read from a retrieved instrument, so every such figure is std REG-R2.The contract boundary is not an open question on this product, and that is worth saying because it is one on most of the others. A Sofortrente has a single premium already paid and no unilateral right on either side to alter or terminate the contract R1 REG-R28, so the whole projected stream sits inside the boundary. The one qualification is the Aufschubzeit variant, where a pre-Rentenbeginn surrender right may exist R1 R2 and no carrier’s terms were established (research gap 17).
Longevity risk in the standard formula. The life underwriting module’s longevity shock is a permanent reduction in mortality rates, and it bites on this product harder than on any other in the library, there being no offsetting death benefit beyond a refund that shrinks as the annuity is paid. No shock parameter was read from a retrieved instrument REG-R2.
IFRS 17. Fulfilment cash flows plus a contractual service margin, effective for financial years beginning on or after 1 January 2023 REG-R55; a Sofortrente with an Überschussbeteiligung is a direct-participating contract and would fall under the Variable Fee Approach. The same expected-cash-flow engine feeds it; grouping, CSM, risk adjustment and coverage units are out of scope, and no delib model produces a CSM.
Key sensitivities and model risks#
In rough order of leverage for a German payout-annuity block:
The mortality basis, in both dimensions. This is a pure longevity bet and the basis is a std proxy, because DAV 2004 R is not public REG-R47 REG-R49. Two numbers with no observed range decide most of the answer: the first-order level margin (
SECOND = 1.20 × FIRST) and the trend margin (λ_FIRST = 1.25 × λ_SECOND). The trend is the more dangerous of the two over a fifty-six-year projection, because it compounds: at the anchor’sλ = 1,5 %a 25 % change in the improvement rate moves the surviving cohort at age 90 by several per cent, and the annuity factor by more than a 25 % change in the level would. A user substituting a real table must replace the surface, not the level.The Überschussrente assumption. On the anchor cell the Überschussrente opens at 10 % of the guaranteed annuity and grows 1 % a year, so by the twentieth policy year it is running at materially more than a tenth of the payment — and none of it is guaranteed R21. Two stresses matter and neither is in the base run: the constant form being reduced, which the consumer literature says happens when the insurer earns less than projected R21; and the ZZR release working the other way REG-R17. A model projecting a flat surplus rate has taken a view.
The tariff Rechnungszins and the vintage. On the std basis the 0,25 % → 1,00 % step is worth about +10 % on the guaranteed annuity at age 65 (
product-spec.md, Rentenhöhe). Model points 1 and 13 differ in almost nothing else, so the pair isolates the effect; the magnitude is constructed, not observed (research gap 5).The payment timing convention. Vorschüssig is std and unestablished (research gap 11). Model points 1 and 9 are the same cell under the two conventions and measure the difference at 0,34 % of the guaranteed annuity —
363.9119916441 / 362.6658241684. That is not the “about 5 %” of_research/sofortrente.mdsection 8, which computes the gap asä_due − ä_arrears = 1: true of an annual annuity, and twelve times too large for a monthly one, where the gap is one month’s instalment. The research file is frozen and is not amended; the correction is recorded in the worked example above. The convention still matters — it shifts every payout cash flow by a month and moves the first month’snet_cfby the whole instalment — but it is not the largest assumption in the model, and treating it as such would misdirect a sensitivity programme.The Kapitalrückgewähr solve. Model point 3’s guaranteed annuity is about 18 % below the plain life annuity on the std basis, and the reduction is sensitive to the interest rate in a way the plain annuity is not, because the refund is a death benefit discounted from an earlier date than the annuity payments it displaces. It is also the only place in the model where a numerical solve sits inside the pricing, so a change of
solve_tolis a change of answer, not of runtime.The second life and the independence assumption. The Hinterbliebenenrente is priced with both lives independent std; real joint lives are positively dependent, so the model overstates the joint-life annuity value and understates the cost of the rider. No delib source quantifies the dependence.
mix_maleand the unisex tariff. The realised portfolio mix drives the Risikoergebnis the MindZV then shares REG-R18 REG-R34, andρ_M = 0.45is a direction with no magnitude behind it. It moves the tariff annuity factor directly, so it moves every derived annuity in the table.Expense levels on small tickets. At model point 11’s 25 000 € the std acquisition expense of
2,0 % + 200 €is 700 € against a monthly annuity of the order of 175 €, and the fixed per-instalment cost is a materially larger share of the payment than at 100 000 €. That is the arithmetic behind the minimum Einmalbeitrag, and it means the per-policy expense assumption — not mortality — decides whether a small cell is viable.What is deliberately absent, and would change the answer. The Bewertungsreserven share [S3] REG-R24; a commuted Restgarantiezeit settlement (research gap 10); a reduction of a declared Überschussrente R21; and any Rentenanpassung actually observed, of which the corpus contains no specimen at any carrier for any year [S15] (research gap 16). Each is named here so that a reader knows the projection’s silence about it is a decision rather than an oversight.