The LifeInsureR package provides a full-featured framework to model classical life insurance contracts (non-unit linked). Mathematically, a general life insurance contracts can be defined using death and survival (and disability) benefit vectors to define the cash flows and calculate all premiums and reserves recursively. This powerful approach is taken by the LifeInsureR package to provide the most flexible contract modelling framework in R.
An insurance contract is described by three different objects;
InsuranceContract
: The object describing the actual
contract with all contract-specific parameters (age, maturity, sum
insured, etc.).InsuranceTarif
: The general (contract-independent)
description of the insurance product (benefits, present values /
reserves, premium calculation, premium waivers, surrender value, reserve
calculation, premium decomposition). It does not store any values (only
tarif-provided default values for the insurance contract), but provides
all calculation functions. The implementation is based on a general
approach using cash flows (depending on the type of insurance). Once the
cash flows (survival, death and guaranteed) are defined for all time
steps, all further calculations are idential for all different kinds of
life insurance.ProfitParticipation
: For contracts with profit sharing
mechanisms, this object describes the profit participation (like the
InsuranceTarif
object describes the guaranteed payments)
and us used by the InsuranceContract
object.The tariff and the profit scheme are purely descriptional and their creation does not trigger any calculations. However, when one creates a contract for a given tariff, the following steps are done to calculate all values relevant for the contract:
All steps after the cash flow setup are implemented in a very generic way, as the cash flows fully determine an insurance contract and as soon as the cash flows are fixed, the valuation and reserve calculation can be expressed in terms of expected present values of the cash flows only.
While this might at first seem a bit complicated, it allows for very generic implementations of life insurance products and contracts.
To understand how the package implements life insurance contracts, let us look at a simple example:
Term Life Insurance
Costs:
Surrender Value:
library(magrittr)
library(MortalityTables)
library(LifeInsureR)
mortalityTables.load("Austria_Census")
= InsuranceTarif$new(
Tarif.L71U name = "L71-U",
type = "wholelife",
tarif = "DeathPlus - Short Term Life Insurance",
desc = "Term Life insurance (5 years) with constant sum insured and regular premiums",
policyPeriod = 5, premiumPeriod = 5, # premiumPeriod not needed, defaults to maturity
mortalityTable = mortalityTable.mixed(
table1 = mort.AT.census.2011.male, weight1 = 0.65,
table2 = mort.AT.census.2011.female, weight2 = 0.35
),i = 0.005,
tax = 0.04,
costs = initializeCosts(alpha = 0.05, gamma = 0.01, gamma.paidUp = 0.01, unitcosts = 10),
surrenderValueCalculation = function(surrenderReserve, params, values) {
* 0.9
surrenderReserve
} );
With the above product / tariff definition, it is now easy to
instantiate one particular contract for this tariff. All we need to do
is pass the tariff and the corresponding contract-specific information
(mainly age, sum insured and contract closing) to the
InsuranceContract
object:
= InsuranceContract$new(
contract.L71U
Tarif.L71U, age = 35,
contractClosing = as.Date("2020-08-18"),
sumInsured = 100000);
Just creating the contract will already calculate all cash flows,
present values, premiums, reserves, etc. for the whole contract period.
They can be accessed through the contract.L71U$Values
list.
A full list of all possible arguments can be found in the section [All possible parameters and their default values].
Once the contract is created, all values can be accessed like this:
$Values$premiums contract.L71U
x | |
---|---|
unit.net | 0.0008083 |
unit.Zillmer | 0.0008083 |
unit.gross | 0.0113840 |
net | 80.8263742 |
Zillmer | 80.8263742 |
gross | 1138.4019890 |
unitcost | 0.0000000 |
written_yearly | 1194.3380685 |
written_beforetax | 1148.4019890 |
tax | 45.9360796 |
written | 1194.3380685 |
additional_capital | 0.0000000 |
$Values$reserves contract.L71U
SumInsured | net | Zillmer | adequate | gamma | contractual | conversion | alphaRefund | reduction | |
---|---|---|---|---|---|---|---|---|---|
0 | 1e+05 | 0.00 | 0.00 | 0.00 | 0 | 0.00 | 0.00 | 0 | 0.00 |
1 | 1e+05 | 10.91 | 10.91 | -217.41 | 0 | 10.91 | 10.91 | 0 | 10.91 |
2 | 1e+05 | 17.13 | 17.13 | -154.60 | 0 | 17.13 | 17.13 | 0 | 17.13 |
3 | 1e+05 | 18.02 | 18.02 | -96.80 | 0 | 18.02 | 18.02 | 0 | 18.02 |
4 | 1e+05 | 12.67 | 12.67 | -44.90 | 0 | 12.67 | 12.67 | 0 | 12.67 |
5 | 1e+05 | 0.00 | 0.00 | 0.00 | 0 | 0.00 | 0.00 | 0 | 0.00 |
PremiumsPaid | Surrender | PremiumFreeSumInsured | |
---|---|---|---|
0 | 1138.40 | 0.00 | 0.00 |
1 | 2276.80 | 9.82 | 228.43 |
2 | 3415.21 | 15.42 | 475.74 |
3 | 4553.61 | 16.22 | 746.15 |
4 | 5692.01 | 11.40 | 1042.97 |
5 | 5692.01 | 0.00 | 0.00 |
Looking back at the definition of the tariff, the only spot where the
type of insurance actually came in was the type
argument of
the InsuranceTarif
definition. This is one of the most
important flags and is central to correct implementation of a tarif. On
the other hand, all it does is to cause different vectors of survival,
death and guaranteed cash flows. Once the cash flows are determined, the
insurance contract and tariff does not need the insurance type any
more.
In our term life example, the insurance contract’s unit cash flows
are 1 for death benefits (both when premiums are paid and when the
contract is paid-up) and for premiums in advance. All other cash flows
(guaranteed, survival or disease cash flows) are zero. Similarly, the
cost structure described above and implemented by the
LifeInsureR::initializeCosts()
function defines all cost
cash flows, which are the starting point for all further calculations
(only relevant columns of the data.frame are shown):
$Values$cashFlows contract.L71U
premiums_advance | premiums_arrears | death_SumInsured | death_GrossPremium | death_PremiumFree | |
---|---|---|---|---|---|
0 | 1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 1 | 0 | 1 |
2 | 1 | 0 | 1 | 0 | 1 |
3 | 1 | 0 | 1 | 0 | 1 |
4 | 1 | 0 | 1 | 0 | 1 |
5 | 0 | 0 | 0 | 0 | 0 |
$Values$cashFlowsCosts[,,,"survival"] contract.L71U
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0.01 | 0.01 | 0 |
1 | 0 | 0 | 0 | 0.01 | 0.01 | 0 |
2 | 0 | 0 | 0 | 0.01 | 0.01 | 0 |
3 | 0 | 0 | 0 | 0.01 | 0.01 | 0 |
4 | 0 | 0 | 0 | 0.01 | 0.01 | 0 |
5 | 0 | 0 | 0 | 0.00 | 0.00 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0.05 | 0 | 0 | 0 | 0 | 0 |
1 | 0.00 | 0 | 0 | 0 | 0 | 0 |
2 | 0.00 | 0 | 0 | 0 | 0 | 0 |
3 | 0.00 | 0 | 0 | 0 | 0 | 0 |
4 | 0.00 | 0 | 0 | 0 | 0 | 0 |
5 | 0.00 | 0 | 0 | 0 | 0 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 | 0 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 | 0 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 | 0 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 10 |
1 | 0 | 0 | 0 | 0 | 0 | 10 |
2 | 0 | 0 | 0 | 0 | 0 | 10 |
3 | 0 | 0 | 0 | 0 | 0 | 10 |
4 | 0 | 0 | 0 | 0 | 0 | 10 |
5 | 0 | 0 | 0 | 0 | 0 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 | 0 | 0 |
Once these unit cash flows are set, all insurance contracts are handled identically. First, present values of each of the cash flows are calculated, from which the premiums can be calculated by the equivalence principle.
$Values$presentValues contract.L71U
premiums | death_SumInsured | death_GrossPremium | death_Refund_future | death_PremiumFree | |
---|---|---|---|---|---|
0 | 4.94307 | 0.00400 | 0 | 0 | 0.00400 |
1 | 3.96558 | 0.00331 | 0 | 0 | 0.00331 |
2 | 2.98265 | 0.00258 | 0 | 0 | 0.00258 |
3 | 1.99416 | 0.00179 | 0 | 0 | 0.00179 |
4 | 1.00000 | 0.00093 | 0 | 0 | 0.00093 |
5 | 0.00000 | 0.00000 | 0 | 0 | 0.00000 |
$Values$presentValuesCosts contract.L71U
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0.0494 | 0.0494 | 0 |
1 | 0 | 0 | 0 | 0.0397 | 0.0397 | 0 |
2 | 0 | 0 | 0 | 0.0298 | 0.0298 | 0 |
3 | 0 | 0 | 0 | 0.0199 | 0.0199 | 0 |
4 | 0 | 0 | 0 | 0.0100 | 0.0100 | 0 |
5 | 0 | 0 | 0 | 0.0000 | 0.0000 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0.05 | 0 | 0 | 0 | 0 | 0 |
1 | 0.00 | 0 | 0 | 0 | 0 | 0 |
2 | 0.00 | 0 | 0 | 0 | 0 | 0 |
3 | 0.00 | 0 | 0 | 0 | 0 | 0 |
4 | 0.00 | 0 | 0 | 0 | 0 | 0 |
5 | 0.00 | 0 | 0 | 0 | 0 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 | 0 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 | 0 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 | 0 | 0 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 49.4307 |
1 | 0 | 0 | 0 | 0 | 0 | 39.6558 |
2 | 0 | 0 | 0 | 0 | 0 | 29.8265 |
3 | 0 | 0 | 0 | 0 | 0 | 19.9416 |
4 | 0 | 0 | 0 | 0 | 0 | 10.0000 |
5 | 0 | 0 | 0 | 0 | 0 | 0.0000 |
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 | 0 | 0 |
$Values$premiums contract.L71U
. | |
---|---|
unit.net | 0.00 |
unit.Zillmer | 0.00 |
unit.gross | 0.01 |
net | 80.83 |
Zillmer | 80.83 |
gross | 1138.40 |
unitcost | 0.00 |
written_yearly | 1194.34 |
written_beforetax | 1148.40 |
tax | 45.94 |
written | 1194.34 |
additional_capital | 0.00 |
The actual calculation of the premiums has to be in the order gross premium, Zillmer premiuem, then net premium. The reason for this particular order is that some tariffs have a gross premium refund in case of death. So to calculate the net premium, the gross premium is required.
The premiums allow the unit cash flows and present values to be
converted to monetary terms (fields
contract.L71U$Values$absCashFlows
and
contract.L71U$Values$absPresentValues
). Also, the reserves
of the contract can be calculated.
$Values$reserves contract.L71U
SumInsured | net | Zillmer | adequate | gamma | contractual | conversion | alphaRefund | reduction | |
---|---|---|---|---|---|---|---|---|---|
0 | 1e+05 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 1e+05 | 11 | 11 | -217 | 0 | 11 | 11 | 0 | 11 |
2 | 1e+05 | 17 | 17 | -155 | 0 | 17 | 17 | 0 | 17 |
3 | 1e+05 | 18 | 18 | -97 | 0 | 18 | 18 | 0 | 18 |
4 | 1e+05 | 13 | 13 | -45 | 0 | 13 | 13 | 0 | 13 |
5 | 1e+05 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
PremiumsPaid | Surrender | PremiumFreeSumInsured | |
---|---|---|---|
0 | 1138 | 0.0 | 0 |
1 | 2277 | 9.8 | 228 |
2 | 3415 | 15.4 | 476 |
3 | 4554 | 16.2 | 746 |
4 | 5692 | 11.4 | 1043 |
5 | 5692 | 0.0 | 0 |
Also, the premium composition into costs, risk premium, savings premium and other components is possible.
$Values$premiumComposition contract.L71U
charged | tax | unitcosts | gross | gamma | beta | alpha | alpha.noZillmer | alpha.Zillmer | net | risk | savings |
---|---|---|---|---|---|---|---|---|---|---|---|
1183.94 | 45.54 | 0 | 1138.4 | 1000 | 0 | 57.58 | 57.58 | 0 | 80.83 | 69.97 | 10.85 |
1183.94 | 45.54 | 0 | 1138.4 | 1000 | 0 | 57.58 | 57.58 | 0 | 80.83 | 74.69 | 6.14 |
1183.94 | 45.54 | 0 | 1138.4 | 1000 | 0 | 57.58 | 57.58 | 0 | 80.83 | 80.03 | 0.80 |
1183.94 | 45.54 | 0 | 1138.4 | 1000 | 0 | 57.58 | 57.58 | 0 | 80.83 | 86.24 | -5.41 |
1183.94 | 45.54 | 0 | 1138.4 | 1000 | 0 | 57.58 | 57.58 | 0 | 80.83 | 93.50 | -12.67 |
0.00 | 0.00 | 0 | 0.0 | 0 | 0 | 0.00 | 0.00 | 0 | 0.00 | 0.00 | 0.00 |
As we see, the whole history and future of the contract is calculated as soon as it is created. It is, however, also possible to modify a contract later on, e.g. by stopping premium payments and converting it to a paid-up contract.
= contract.L71U$premiumWaiver(t = 3)
contract.L71U.prf $Values$reserves contract.L71U.prf
SumInsured | net | Zillmer | adequate | gamma | contractual | conversion | alphaRefund | reduction | |
---|---|---|---|---|---|---|---|---|---|
0 | 100000.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0 | 0.00 |
1 | 100000.00 | 10.91 | 10.91 | -217.41 | 0.00 | 10.91 | 10.91 | 0 | 10.91 |
2 | 100000.00 | 17.13 | 17.13 | -154.60 | 0.00 | 17.13 | 17.13 | 0 | 17.13 |
3 | 746.15 | 1.34 | 1.34 | 16.22 | 14.88 | 16.22 | 16.22 | 0 | 16.22 |
4 | 746.15 | 0.70 | 0.70 | 8.16 | 7.46 | 8.16 | 8.16 | 0 | 8.16 |
5 | 746.15 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0 | 0.00 |
PremiumsPaid | Surrender | PremiumFreeSumInsured | |
---|---|---|---|
0 | 1138.40 | 0.00 | 0.00 |
1 | 2276.80 | 9.82 | 228.43 |
2 | 3415.21 | 15.42 | 475.74 |
3 | 3415.21 | 16.22 | 746.15 |
4 | 3415.21 | 8.16 | 746.15 |
5 | 3415.21 | 0.00 | 0.00 |
When prototyping a new product or creating marketing material, it is often needed to create tables of premiums, reserves, benefits or surrender values for different parameters (e.g. different ages, maturities and sums insured for the marketing department, or different guaranteed interest rates, mortality tables or costs for the product development group).
This can be easily done by the functions
contractGridPremium()
or contractGrid()
. They
take one argument axes
, which gives the parameters for the
axes of the table (more than two dimensions are possible!), while all
other parameters are passed unchanged to
InsuranceContract$new()
.
First, let us create a grid of premiums for different ages and maturities (for sum insured 100,000 Euro):
= contractGridPremium(
grd axes = list(age = seq(20, 80, 5), policyPeriod = seq(10, 40, 5)),
tarif = Tarif.L71U,
contractClosing = as.Date("2020-08-18"),
sumInsured = 100000
) grd
10 | 15 | 20 | 25 | 30 | 35 | 40 | |
---|---|---|---|---|---|---|---|
20 | 1226.97 | 1288.25 | 1369.67 | 1494.03 | 1704.47 | 2049.68 | 2595.39 |
25 | 1227.50 | 1311.22 | 1439.08 | 1655.45 | 2010.37 | 2571.46 | 3403.05 |
30 | 1256.61 | 1388.06 | 1610.50 | 1975.39 | 2552.22 | 3407.16 | 4549.65 |
35 | 1329.56 | 1558.39 | 1933.75 | 2527.14 | 3406.63 | 4581.92 | 6162.21 |
40 | 1480.91 | 1867.64 | 2478.99 | 3385.10 | 4595.97 | 6224.08 | 8581.45 |
45 | 1749.39 | 2381.32 | 3317.94 | 4569.58 | 6252.52 | 8689.26 | 12082.95 |
50 | 2179.00 | 3153.01 | 4454.64 | 6204.78 | 8738.83 | 12268.04 | 16160.61 |
55 | 2819.25 | 4186.05 | 6023.83 | 8684.78 | 12390.71 | 16478.20 | 19388.53 |
60 | 3654.46 | 5613.83 | 8450.81 | 12401.91 | 16759.81 | 19862.68 | 21019.86 |
65 | 4824.56 | 7911.21 | 12210.02 | 16951.43 | 20327.37 | 21586.39 | 21799.89 |
70 | 6942.90 | 11749.48 | 17050.94 | 20825.64 | 22233.37 | 22472.09 | 22472.09 |
75 | 10834.54 | 17077.83 | 21523.12 | 23180.94 | 23462.07 | 23462.07 | 23462.07 |
80 | 16763.64 | 22608.06 | 24787.67 | 25157.28 | 25157.28 | 25157.28 | 25157.28 |
One can also pass more than two dimensions to the axes:
= contractGridPremium(
grd axes = list(age = seq(20, 80, 10), policyPeriod = seq(10, 40, 10), sumInsured = c(10000, 50000, 100000)),
tarif = Tarif.L71U,
contractClosing = as.Date("2020-08-18")
) grd
10 | 20 | 30 | 40 | |
---|---|---|---|---|
20 | 132.06 | 146.33 | 179.81 | 268.90 |
30 | 135.02 | 170.41 | 264.58 | 464.33 |
40 | 157.45 | 257.26 | 468.96 | 867.51 |
50 | 227.26 | 454.82 | 883.24 | 1625.42 |
60 | 374.81 | 854.44 | 1685.34 | 2111.35 |
70 | 703.65 | 1714.45 | 2232.70 | 2256.57 |
80 | 1685.72 | 2488.13 | 2525.09 | 2525.09 |
10 | 20 | 30 | 40 | |
---|---|---|---|---|
20 | 618.69 | 690.04 | 857.43 | 1302.90 |
30 | 633.51 | 810.45 | 1281.31 | 2280.03 |
40 | 745.66 | 1244.70 | 2303.18 | 4295.93 |
50 | 1094.70 | 2232.52 | 4374.62 | 8085.51 |
60 | 1832.43 | 4230.60 | 8385.11 | 10515.13 |
70 | 3476.65 | 8530.67 | 11121.88 | 11241.24 |
80 | 8387.02 | 12399.04 | 12583.84 | 12583.84 |
10 | 20 | 30 | 40 | |
---|---|---|---|---|
20 | 1226.97 | 1369.67 | 1704.47 | 2595.39 |
30 | 1256.61 | 1610.50 | 2552.22 | 4549.65 |
40 | 1480.91 | 2478.99 | 4595.97 | 8581.45 |
50 | 2179.00 | 4454.64 | 8738.83 | 16160.61 |
60 | 3654.46 | 8450.81 | 16759.81 | 21019.86 |
70 | 6942.90 | 17050.94 | 22233.37 | 22472.09 |
80 | 16763.64 | 24787.67 | 25157.28 | 25157.28 |
One can use any of the parameters of the
InsuranceContract$new()
call in the axes
argument, even the tarif
or mortalityTable
parameters. This means that one can create tables comparing different
tariffs, or showing the effect of different life tables.
In the following example, we use the tarif Tarif.L71U
,
but instead of the unisex table (mixed 65:35 from male:female tables),
we use the male mortality tables of the Austrian census from 1870 to
2011 (with a contract period of 10 years fixed, and varying ages):
= contractGridPremium(
grd axes = list(mortalityTable = mort.AT.census["m", ], age = seq(20, 80, 10)),
tarif = Tarif.L71U,
sumInsured = 100000,
contractClosing = as.Date("2020-08-18")
) grd
20 | 30 | 40 | 50 | 60 | 70 | 80 | |
---|---|---|---|---|---|---|---|
ÖVSt 1868/71 M | 2196.2 | 2266.9 | 2729.5 | 3633.2 | 6045.2 | 11993.8 | 25050.0 |
ÖVSt 1879/82 M | 2125.8 | 2260.8 | 2656.7 | 3579.8 | 5660.1 | 11385.1 | 24373.1 |
ÖVSt 1889/92 M | 1979.6 | 2108.8 | 2584.7 | 3435.2 | 5619.3 | 11166.0 | 23959.4 |
ÖVSt 1899/1902 M | 1811.4 | 1985.5 | 2468.2 | 3382.7 | 5441.2 | 10630.8 | 21946.0 |
ÖVSt 1909/12 M | 1741.3 | 1931.3 | 2396.9 | 3345.0 | 5399.1 | 10374.0 | 21291.8 |
ÖVSt 1930/33 M | 1540.6 | 1646.7 | 1980.9 | 2768.4 | 4569.0 | 9010.1 | 19923.5 |
ÖVSt 1949/51 M | 1358.5 | 1391.3 | 1614.9 | 2433.8 | 4123.8 | 8124.9 | 17960.0 |
ÖVSt 1959/61 M | 1334.4 | 1353.6 | 1513.6 | 2232.3 | 4128.9 | 7980.8 | 17180.7 |
ÖVSt 1970/72 M | 1336.7 | 1338.4 | 1580.6 | 2178.1 | 3933.2 | 8459.4 | 17202.5 |
ÖVSt 1980/82 M | 1299.0 | 1293.1 | 1516.3 | 2169.5 | 3467.3 | 7198.6 | 16340.6 |
ÖVSt 1990/92 M | 1248.9 | 1257.3 | 1435.3 | 1887.9 | 3135.3 | 5839.9 | 13587.2 |
ÖVSt 2000/02 M | 1216.4 | 1209.3 | 1350.8 | 1763.0 | 2552.7 | 4845.2 | 11393.3 |
ÖVSt 2010/12 M | 1189.9 | 1186.7 | 1273.1 | 1606.2 | 2434.8 | 3968.0 | 9476.2 |
ÖVSt 2020/22 M | 1164.1 | 1181.6 | 1257.5 | 1495.1 | 2259.3 | 4058.9 | 8680.2 |
All possible parameters of an insurance contract are stored in
sub-lists of a a structure InsuranceContract.Parameters
. If
not provided by the call to InsuranceContract$new()
, the
values will be taken from either the InsuranceTariff
’s
default parameters, the ProfitParticipation
’s default
parameters or the global defaults in the
InsuranceContract.ParameterDefault
. The cascade or
parameters is (from top to bottom):
InsuranceContract$addProfitScenario()
(applies only for the
added profit scenario)InsuranceContract$new()
or InsuranceContract$clone()
ProfitParticipation
InsuranceTarif
InsuranceContract.ParameterDefaults
In addition to the parameters listed below, the
InsuranceContract$new()
constructor function takes the
following parameters
str(InsuranceContract.ParameterDefaults)
#> List of 9
#> $ ContractData :List of 28
#> ..$ id : chr "Hauptvertrag"
#> ..$ sumInsured : NULL
#> ..$ premium : NULL
#> ..$ birthDate : NULL
#> ..$ YOB : NULL
#> ..$ age : NULL
#> ..$ technicalAge : NULL
#> ..$ ageDifferences : NULL
#> ..$ sex : chr "unisex"
#> ..$ policyPeriod : num 25
#> ..$ premiumPeriod : NULL
#> ..$ deferralPeriod : num 0
#> ..$ guaranteedPeriod : num 0
#> ..$ contractClosing : NULL
#> ..$ initialCapital : num 0
#> ..$ blockStart : num 0
#> ..$ premiumPayments : chr "in advance"
#> ..$ benefitPayments : chr "in advance"
#> ..$ premiumFrequency : num 1
#> ..$ benefitFrequency : num 1
#> ..$ premiumRefund : num 0
#> ..$ premiumRefundPeriod:function (params, values)
#> .. ..- attr(*, "srcref")= 'srcref' int [1:8] 163 31 165 1 31 1 165 167
#> .. .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x000001ac6dc0f948>
#> ..$ premiumIncrease : num 1
#> ..$ annuityIncrease : num 1
#> ..$ deathBenefit : num 1
#> ..$ benefitParameter : NULL
#> ..$ costWaiver : num 0
#> ..$ attributes : list()
#> $ ContractState :List of 3
#> ..$ premiumWaiver : logi FALSE
#> ..$ surrenderPenalty: logi TRUE
#> ..$ alphaRefunded : logi FALSE
#> $ ActuarialBases :List of 11
#> ..$ mortalityTable : NULL
#> ..$ invalidityTable : NULL
#> ..$ invalidityEndsContract : logi TRUE
#> ..$ i : num 0
#> ..$ balanceSheetDate : Date[1:1], format: "1900-12-31"
#> ..$ balanceSheetMethod : chr "30/360"
#> ..$ unearnedPremiumsMethod : NULL
#> ..$ surrenderValueCalculation : NULL
#> ..$ premiumWaiverValueCalculation: NULL
#> ..$ premiumFrequencyOrder :function (params, ...)
#> .. ..- attr(*, "srcref")= 'srcref' int [1:8] 614 33 614 120 33 120 1573 1573
#> .. .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x000001ac6dbe8a18>
#> ..$ benefitFrequencyOrder :function (params, ...)
#> .. ..- attr(*, "srcref")= 'srcref' int [1:8] 615 33 615 120 33 120 1574 1574
#> .. .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x000001ac6dbe8a18>
#> $ Costs : num [1:6, 1:7, 1:7] 0 0 0 0 0 0 0 0 0 0 ...
#> ..- attr(*, "dimnames")=List of 3
#> .. ..$ type : chr [1:6] "alpha" "Zillmer" "beta" "gamma" ...
#> .. ..$ basis : chr [1:7] "SumInsured" "SumPremiums" "GrossPremium" "NetPremium" ...
#> .. ..$ frequency: chr [1:7] "once" "PremiumPeriod" "PremiumFree" "PolicyPeriod" ...
#> $ minCosts : NULL
#> $ Loadings :List of 15
#> ..$ ongoingAlphaGrossPremium: num 0
#> ..$ tax : num 0.04
#> ..$ unitcosts : num 0
#> ..$ security : num 0
#> ..$ noMedicalExam : num 0
#> ..$ noMedicalExamRelative : num 0
#> ..$ sumRebate : num 0
#> ..$ extraRebate : num 0
#> ..$ premiumRebate : num 0
#> ..$ partnerRebate : num 0
#> ..$ extraChargeGrossPremium : num 0
#> ..$ benefitFrequencyLoading : NULL
#> ..$ premiumFrequencyLoading : NULL
#> ..$ alphaRefundPeriod : num 5
#> ..$ commissionPeriod : num 5
#> $ Features :List of 8
#> ..$ zillmering : logi TRUE
#> ..$ betaGammaInZillmer : logi FALSE
#> ..$ alphaRefundLinear : logi TRUE
#> ..$ useUnearnedPremiums :function (params, values)
#> .. ..- attr(*, "srcref")= 'srcref' int [1:8] 128 28 128 93 28 93 130 130
#> .. .. ..- attr(*, "srcfile")=Classes 'srcfilealias', 'srcfile' <environment: 0x000001ac6dc0f948>
#> ..$ surrenderIncludesCostsReserves: logi TRUE
#> ..$ unitcostsInGross : logi FALSE
#> ..$ absPremiumRefund : num 0
#> ..$ alphaCostsCommission : chr "actual"
#> $ ProfitParticipation:List of 16
#> ..$ advanceProfitParticipation : num 0
#> ..$ advanceProfitParticipationInclUnitCost: num 0
#> ..$ waitingPeriod : NULL
#> ..$ guaranteedInterest : NULL
#> ..$ interestProfitRate : NULL
#> ..$ totalInterest : NULL
#> ..$ mortalityProfitRate : NULL
#> ..$ expenseProfitRate : NULL
#> ..$ sumProfitRate : NULL
#> ..$ terminalBonusRate : NULL
#> ..$ terminalBonusFundRate : NULL
#> ..$ profitParticipationScheme : NULL
#> ..$ profitComponents : NULL
#> ..$ profitClass : NULL
#> ..$ profitRates : NULL
#> ..$ scenarios : list()
#> $ Hooks :List of 10
#> ..$ adjustCashFlows : NULL
#> ..$ adjustCashFlowsCosts : NULL
#> ..$ adjustCosts : NULL
#> ..$ adjustMinCosts : NULL
#> ..$ adjustPresentValues : NULL
#> ..$ adjustPresentValuesCosts : NULL
#> ..$ adjustPremiumCoefficients: NULL
#> ..$ adjustPremiums : NULL
#> ..$ adjustPVForReserves : NULL
#> ..$ premiumRebateCalculation : NULL
# pandoc.listRK(InsuranceContract.ParameterDefaults)
An insurance contract is modelled by the abstract product
specification (InsuranceTarif
class) and the concrete
(individualized) InsuranceContract
.
InsuranceTarif
object describes the product in
abstract terms, holds default parameters and provides all calculation
functions for cash flows, present values, premiums and reserves
(provided the contract’s actual Parameters). It does not, however, hold
contract-specific data.InsuranceContract
object holds the individual
contract data (like age, contract closing date, sum insured, etc.) that
override the tariff’s defaults. It also holds a pointer to the insurance
tariff and provides the general logic to calculate all parts of an
insurance contract by calling the corresponding functions of the
tariff.The insurance contract and the underlying insurance tariff have the
same possible parameters in its constructors: The
InsuranceTarif
uses them to define defaults for contracts
that use the tariff, while the parameters passed to the contract either
provide the individually required data like age, sum insured or
maturity, or they can override the defaults provided by the tariff. In
theory, one could even create a contract with an empty underlying tariff
and provide all tariff-specific parameters also in the contract’s
new
call.
The InsuranceTarif
class provides a way to define an
abstract insurance product. The most important parameters to be passed
in the InsuranceTarif$new()
call are:
General settings for the Tariff | |
name , tarif ,
desc |
IDs and human-readable descriptions of the insurance product. They are just used as labels and array keys, but do not influence the calculations. |
type |
the most important parameter, defining the type of life insurance product (endowment, pure endowment, annuity, whole life insurance, dread-disease insurance, etc.) |
Actuarial Bases for the Tariff | |
mortalityTable |
a MortalityTable Object (package
“MortalityTables”), providing the transition probabilities (typically
death probabilities, but potentially also morbidity in dread-disease
insurances) |
i |
Guaranteed interest rate |
costs , unitcosts |
passed a data structure for all cost parameters (see below) |
premiumFrequencyLoading |
surcharge for premium payments more often than yearly (as a named list) |
premiumRefund |
how much of the (gross) premium paid is returned upon death (often provided e.g. in deferred annuities or pure endowments with no fixed death benefit) |
tax |
insurance tax |
Benefit calculation | |
surrenderValueCalculation |
can be passed a hook function that calculates the surrender value given the current reserves at each time step |
A typical call looks like the following for a pure endowment with gross premium refund upon death and a linearly decreasing surrender penalty:
= InsuranceTarif$new(
Tarif.PureEnd name = "Example Tariff - Pure Endowment",
type = "pureendowment",
tarif = "PE1-RP",
desc = "A pure endowment with regular premiums (standard tariff)",
mortalityTable = mort.AT.census.2011.unisex,
i = 0.005,
# Costs: 4% acquisition, where 2.5% are zillmered, 5\% of each premium as beta costs,
# 1%o administration costs of the sum insured over the whole contract period
costs = initializeCosts(alpha = 0.04, Zillmer = 0.025, beta = 0.05, gamma.contract = 0.001, gamma.paidUp = 0.001),
unitcosts = 10,
# Yearly premiums get no surcharge, monthly premiums add +4%
premiumFrequencyLoading = list("1" = 0, "12" = 0.04),
premiumRefund = 1, # Full gross premium refund upon death
tax = 0.04, # 4% insurance tas
surrenderValueCalculation = function(surrenderReserve, params, values) {
= params$ContractData$policyPeriod
n # Surrender Penalty is 10% at the beginning and decreases linearly to 0%
* (0.9 + 0.1 * (0:n)/n)
surrenderReserve
} )
Many parameters do not need to be given explicitly, but instead use
sensible defaults (like the premiumPeriod
, which by default
equals the whole contract period, i.e. regular premium payments over the
whole contract duration).
To create a similar tariff with some changes, one can call the
createModification
method of the
InsuranceTarif
clas, which takes as arguments all insurance
parameters that should be changed for the new tarif.
To create a single-premium version of the pure endowment shown above, one can simply use a call like:
= Tarif.PureEnd$createModification(
Tarif.PureEnd.SP name = "Example Tariff - Pure Endowment (SP)",
tarif = "PE1-SP",
desc = "A pure endowment with single premiums",
premiumPeriod = 1
)
For the examples in the remainder of this vignette, we can create some more example tariffs covering the most common types of life insurance.
General definitions for all tariffs
library(MortalityTables)
mortalityTables.load("Austria_Census")
mortalityTables.load("Austria_Annuities_AVOe2005R")
# Costs: 4% acquisition, where 2.5% are zillmered, 5\% of each premium as beta costs,
# 1%o acquisition costs of the sum insured over the whole contract period
= initializeCosts(
example.Costs alpha = 0.04, Zillmer = 0.025,
beta = 0.05,
gamma.contract = 0.001, gamma.paidUp = 0.001
)= function(surrenderReserve, params, values) {
example.Surrender = params$ContractData$policyPeriod
n # Surrender Penalty is 10% at the beginning and decreases linearly to 0%
* (0.9 + 0.1 * (0:n)/n)
surrenderReserve }
Endowment
= InsuranceTarif$new(
Tarif.Endowment name = "Example Tariff - Endowment",
type = "endowment",
tarif = "EN1",
desc = "An endowment with regular premiums",
mortalityTable = mort.AT.census.2011.unisex,
i = 0.005,
costs = example.Costs,
unitcosts = 10,
tax = 0.04, # 4% insurance tax
surrenderValueCalculation = example.Surrender
)
Whole / Term Life Insurance
= InsuranceTarif$new(
Tarif.Life name = "Example Tariff - Whole/Term Life",
type = "wholelife",
tarif = "Life1",
desc = "A whole or term life insurance with regular premiums",
mortalityTable = mort.AT.census.2011.unisex,
i = 0.005,
costs = example.Costs,
unitcosts = 10,
tax = 0.04, # 4% insurance tax
surrenderValueCalculation = example.Surrender
)
Immediate Annuity (single premium)
= InsuranceTarif$new(
Tarif.ImmAnnuity name = "Example Tariff - Immediate Annuity",
type = "annuity",
tarif = "Ann1",
desc = "An annuity with single-premium",
premiumPeriod = 1,
mortalityTable = AVOe2005R.unisex,
i = 0.005,
costs = example.Costs,
tax = 0.04 # 4% insurance tax
)
Deferred Annuity
# Premium periods and deferral periods can also be given as a function of other
# contract parameters (like the age at contract inception, etc.)
= InsuranceTarif$new(
Tarif.DefAnnuity name = "Example Tariff - Deferred Annuity",
type = "annuity",
tarif = "Life1",
desc = "A deferred annuity (life-long payments start at age 65) with reg. premiums",
policyPeriod = function(params, values) { 120 - params$ContractData$age},
deferralPeriod = function(params, values) { 65 - params$ContractData$age},
premiumPeriod = function(params, values) { 65 - params$ContractData$age},
mortalityTable = AVOe2005R.unisex,
i = 0.005,
costs = example.Costs,
tax = 0.04, # 4% insurance tax
surrenderValueCalculation = example.Surrender
)
Dread-Disease Insurance
# An example dread-disease tariff, morbidity is assumed linearly increasing with age
= mortalityTable.period(name = "Linear dread-disease table",
ddTable ages = 0:100, deathProbs = 0:100/500)
= InsuranceTarif$new(
Tarif.DreadDisease name = "Example Tariff - Dread-Disease",
type = "dread-disease",
tarif = "DD1",
desc = "A dread disease insurance with a lump-sum payment upon diagnosis",
sumInsured = 50000,
mortalityTable = mort.AT.census.2011.unisex,
invalidityTable = ddTable,
i = 0.005,
costs = example.Costs,
unitcosts = 10,
tax = 0.04, # 4% insurance tax
surrenderValueCalculation = example.Surrender
)
While the tariff describes the general product features, the contract object holds the data of a concrete contract. All insurance parameters (see section [All possible parameters and their default values]) can be given to override tarif defaults.
However, the most important and often used parameters are:
Information about insuree | |
age |
the age of the insured person at contract start |
YOB |
the year of birth of the insured person
(age , YOB and contractClosing are
redundant, at most two need to be given). YOB is only relevant for
cohort mortality tables. For period life tables (which are independent
of the birth year of the person), this parameter is not needed. |
sex |
relevant for sex-specific life tables (common in the past) |
Contract details | |
sumInsured |
the benefit when the insured event happens. Typically the lump sum for whole life insurances or endowments, or the (yearly) payment for annuities |
policyPeriod |
the duration of the whole contract |
premiumPeriod |
how long premiums are paid (1 for
single-premiumcontracts, equal to policyPeriod (default)
for regular premiums) |
premiumFrequency |
how often premiums are paid within a year (e.g.1 for yearly premiums, 4 for quarterly, 12 for monthly) |
contractClosing |
the starting date of the contract |
deathBenefit |
gives the factor of death benefit relative to the sum insured / survival benefit of endowments (1 means equal death and survival probabilities), can also be a function with non-constant values over time |
noMedicalExam ,
noMedicalExamRelative , sumRebate ,
extraRebate , premiumRebate |
various types of rebates or charges. They can either be defined in general functional form in the tariff to apply to all contracts, or given individually for each contract. For the details, when each of these rebates are applied, check the formula reference document. |
For the pure endowments defined above, a typical contract would be created like this:
= InsuranceContract$new(
contract.PureEnd
Tarif.PureEnd,age = 50, policyPeriod = 20,
premiumFrequency = 12,
sumInsured = 100000,
contractClosing = as.Date("2020-07-01")
)
$Values$premiums contract.PureEnd
x | |
---|---|
unit.net | 0.0479 |
unit.Zillmer | 0.0494 |
unit.gross | 0.0539 |
net | 4786.7874 |
Zillmer | 4935.5223 |
gross | 5394.4876 |
unitcost | 0.0000 |
written_yearly | 5845.4938 |
written_beforetax | 468.3889 |
tax | 18.7356 |
written | 487.1245 |
additional_capital | 0.0000 |
$Values$premiumComposition contract.PureEnd
t | charged | tax | loading.frequency | gross | gamma | beta | alpha | alpha.noZillmer | alpha.Zillmer |
---|---|---|---|---|---|---|---|---|---|
0 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
1 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
2 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
3 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
4 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
5 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
6 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
7 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
8 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
9 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
10 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
11 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
12 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
13 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
14 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
15 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
16 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
17 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
18 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
19 | 5834.68 | 224.41 | 215.78 | 5394.49 | 100 | 269.72 | 237.98 | 89.24 | 148.73 |
20 | 0.00 | 0.00 | 0.00 | 0.00 | 0 | 0.00 | 0.00 | 0.00 | 0.00 |
Zillmer | net | risk | savings |
---|---|---|---|
4935.52 | 4786.79 | 1.64 | 4785.14 |
4935.52 | 4786.79 | 3.57 | 4783.22 |
4935.52 | 4786.79 | 5.81 | 4780.98 |
4935.52 | 4786.79 | 8.39 | 4778.40 |
4935.52 | 4786.79 | 11.34 | 4775.45 |
4935.52 | 4786.79 | 14.72 | 4772.07 |
4935.52 | 4786.79 | 18.59 | 4768.20 |
4935.52 | 4786.79 | 22.98 | 4763.80 |
4935.52 | 4786.79 | 27.93 | 4758.85 |
4935.52 | 4786.79 | 33.46 | 4753.32 |
4935.52 | 4786.79 | 39.55 | 4747.23 |
4935.52 | 4786.79 | 46.17 | 4740.61 |
4935.52 | 4786.79 | 53.22 | 4733.57 |
4935.52 | 4786.79 | 60.74 | 4726.05 |
4935.52 | 4786.79 | 68.65 | 4718.14 |
4935.52 | 4786.79 | 76.93 | 4709.86 |
4935.52 | 4786.79 | 85.61 | 4701.18 |
4935.52 | 4786.79 | 95.05 | 4691.74 |
4935.52 | 4786.79 | 105.52 | 4681.27 |
4935.52 | 4786.79 | 117.11 | 4669.68 |
0.00 | 0.00 | 0.00 | 0.00 |
Due to the full premium refund in case of death, there is only very little biometric risk involved. If the premium refund is not included in the contract, then we have a negative biometric risk over the whole period (i.e. negative risk premium, because upon death the existing reserves is shared with the collective). The premium refund can be overridden directly in the contract call:
= InsuranceContract$new(
contract.PureEnd.NoRefund
Tarif.PureEnd,age = 50, policyPeriod = 20,
premiumFrequency = 12,
sumInsured = 100000,
contractClosing = as.Date("2020-07-01"),
premiumRefund = 0
)
cbind(`With refund` = contract.PureEnd$Values$premiums, `Without refund` = contract.PureEnd.NoRefund$Values$premiums)
With refund | Without refund | |
---|---|---|
unit.net | 0.05 | 0.04 |
unit.Zillmer | 0.05 | 0.04 |
unit.gross | 0.05 | 0.05 |
net | 4786.79 | 4267.91 |
Zillmer | 4935.52 | 4400.85 |
gross | 5394.49 | 4821.70 |
unitcost | 0.00 | 0.00 |
written_yearly | 5845.49 | 5225.97 |
written_beforetax | 468.39 | 418.75 |
tax | 18.74 | 16.75 |
written | 487.12 | 435.50 |
additional_capital | 0.00 | 0.00 |
cbind(
`Gross premium with refund` = contract.PureEnd$Values$premiumComposition[,"gross"],
`Gross premium w/o refund` = contract.PureEnd.NoRefund$Values$premiumComposition[,"gross"],
`Risk premium with refund` = contract.PureEnd$Values$premiumComposition[,"risk"],
`Risk premium w/o refund` = contract.PureEnd.NoRefund$Values$premiumComposition[,"risk"]
)
t | Gross premium with refund | Gross premium w/o refund | Risk premium with refund | Risk premium w/o refund |
---|---|---|---|---|
0 | 5394.49 | 4821.7 | 1.64 | -12.08 |
1 | 5394.49 | 4821.7 | 3.57 | -26.87 |
2 | 5394.49 | 4821.7 | 5.81 | -44.75 |
3 | 5394.49 | 4821.7 | 8.39 | -66.18 |
4 | 5394.49 | 4821.7 | 11.34 | -91.73 |
5 | 5394.49 | 4821.7 | 14.72 | -122.10 |
6 | 5394.49 | 4821.7 | 18.59 | -158.24 |
7 | 5394.49 | 4821.7 | 22.98 | -200.94 |
8 | 5394.49 | 4821.7 | 27.93 | -250.93 |
9 | 5394.49 | 4821.7 | 33.46 | -309.03 |
10 | 5394.49 | 4821.7 | 39.55 | -375.78 |
11 | 5394.49 | 4821.7 | 46.17 | -451.60 |
12 | 5394.49 | 4821.7 | 53.22 | -536.22 |
13 | 5394.49 | 4821.7 | 60.74 | -630.84 |
14 | 5394.49 | 4821.7 | 68.65 | -735.56 |
15 | 5394.49 | 4821.7 | 76.93 | -851.06 |
16 | 5394.49 | 4821.7 | 85.61 | -978.61 |
17 | 5394.49 | 4821.7 | 95.05 | -1123.58 |
18 | 5394.49 | 4821.7 | 105.52 | -1291.04 |
19 | 5394.49 | 4821.7 | 117.11 | -1484.30 |
20 | 0.00 | 0.0 | 0.00 | 0.00 |
To create a single-premium contract, one can either use the
single-premium tarif defined above, or simply pass
premiumPeriod=1
to the call:
= InsuranceContract$new(
contract.PureEnd.SP1
Tarif.PureEnd,age = 40, policyPeriod = 45, premiumPeriod = 1,
sumInsured = 100000,
contractClosing = as.Date("2020-07-01")
)= InsuranceContract$new(
contract.PureEnd.SP2
Tarif.PureEnd.SP,age = 40, policyPeriod = 45, # premiumPeriod already set by tariff!
sumInsured = 100000,
contractClosing = as.Date("2020-07-01")
)
all_equal(contract.PureEnd.SP1$Values$reserves, contract.PureEnd.SP2$Values$reserves)
#> Warning: `all_equal()` was deprecated in dplyr 1.1.0.
#> ℹ Please use `all.equal()` instead.
#> ℹ And manually order the rows/cols as needed
#> This warning is displayed once every 8 hours.
#> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
#> generated.
#> `y` must be a data frame.
Often, when a contract is changed significantly, this is modelled as a totally new contract, with the existing reserve provided as additional one-time payment for the follow-up contract. This is different from a single-premium contract, because the new contract can have regular premiums, just the existing reserve is used as the initial reserve of the new contract.
The package provides the argument initialCapital
to
provide initial capital that is also included in the calculation of the
premium of the contract.
# Contract with initial capital of 5.000 EUR
= InsuranceContract$new(
contract.Endow.initialCapital tarif = Tarif.Endowment,
sumInsured = 10000,
initialCapital = 5000,
age = 40, policyPeriod = 10,
contractClosing = as.Date("2020-09-01")
)# For comparison: Contract without initial capital of 5.000 EUR
= InsuranceContract$new(
contract.Endow tarif = Tarif.Endowment,
sumInsured = 10000,
age = 40, policyPeriod = 10,
contractClosing = as.Date("2020-09-01")
)
Comparing the reserves, one can clearly see the initial capital used as initial reserve:
data.frame(
`Premium with initialCapital`= contract.Endow.initialCapital$Values$premiumComposition[,"charged"],
`Premium without initialCapital`= contract.Endow$Values$premiumComposition[,"charged"],
`Res.with initialCapital`= contract.Endow.initialCapital$Values$reserves[,"contractual"],
`Res.without initialCapital`= contract.Endow$Values$reserves[,"contractual"]
)#> Premium.with.initialCapital Premium.without.initialCapital
#> 0 542.9533 1131.394
#> 1 542.9533 1131.394
#> 2 542.9533 1131.394
#> 3 542.9533 1131.394
#> 4 542.9533 1131.394
#> 5 542.9533 1131.394
#> 6 542.9533 1131.394
#> 7 542.9533 1131.394
#> 8 542.9533 1131.394
#> 9 542.9533 1131.394
#> 10 0.0000 0.000
#> Res.with.initialCapital Res.without.initialCapital
#> 0 4869.482 -271.9696
#> 1 5369.821 729.7741
#> 2 5872.714 1736.6324
#> 3 6378.207 2748.6945
#> 4 6886.377 3766.1190
#> 5 7397.334 4789.1206
#> 6 7911.217 5817.9828
#> 7 8428.202 6853.0547
#> 8 8948.495 7894.7497
#> 9 9472.336 8943.5471
#> 10 10000.000 10000.0000
The calculation of all contract values is controlled by the function
InsuranceContract$calculateContract()
(using methods of the
InsuranceTarif
object) and follows the following logic:
InsuranceContract$profitScenario()
or
InsuranceContract$addProfitScenario()
.An insurance contract is basically defined by the (unit) cash flows it produces: Together with the transition probabilities (mortalityTable parameter) the present values can be calculated, from which the premiums follow and finally the reserves and a potential profit sharing.
For example, a term life insurance with regular premiums would have the following cash flows:
A single-premium term life insurance would look similar, except for the premiums:
A pure endowment has no death benefits, but a survival benefit of 1 at the maturity of the contract:
An endowment has also death benefits during the contract duration:
A (deferred) annuityB has premium cash flows only during the deferral peroid and only survival cash flows during the annuity payment phase. Often, in case of death during the deferral period, all premiums paid are refunded as a death benefit.:
A terme-fix insurance has a guaranteed payment at maturity, even if the insured has already died. The premiums, however, are only paid until death (which is not reflected in the contingent cash flows, but rather in the transition probabilities):
Costs of an insurance contracts can have various forms and bases.
The InsuranceContract
class provides all common types of
costs:
The cost structure generated by initializeCosts()
is a
three-dimensional array with the above-mentioned coordinates:
initializeCosts() %>% dimnames
#> $type
#> [1] "alpha" "Zillmer" "beta" "gamma"
#> [5] "gamma_nopremiums" "unitcosts"
#>
#> $basis
#> [1] "SumInsured" "SumPremiums" "GrossPremium" "NetPremium" "Benefits"
#> [6] "Constant" "Reserve"
#>
#> $frequency
#> [1] "once" "PremiumPeriod" "PremiumFree" "PolicyPeriod"
#> [5] "AfterDeath" "FullContract" "CommissionPeriod"
The most common types of cost can be directly given in the call to
initializeCosts()
, but for some uncommon combinations, one
can directly fill any of the fields in the three-dimensional array
manually.
initializeCosts(alpha = 0.04, Zillmer = 0.025, beta = 0.05, gamma.contract = 0.001)
# the above is the short form of:
= initializeCosts()
costs.Bsp "alpha", "SumPremiums", "once"]] = 0.04
costs.Bsp[["Zillmer", "SumPremiums", "once"]] = 0.025 # German Zillmer maximum
costs.Bsp[["beta", "GrossPremium", "PremiumPeriod"]] = 0.05
costs.Bsp[["gamma", "SumInsured", "PolicyPeriod"]] = 0.001 costs.Bsp[[
These costs parameters are immediately converted to the corresponding cash flows when the contract is created. In the above example of a pure endowment (with premium waiver at time 7), the absolute cost cash flows are:
$Values$absCashFlows contract.PureEnd.NoRefund
alpha | Zillmer | beta | gamma | gamma_nopremiums | unitcosts | |
---|---|---|---|---|---|---|
0 | 3857.36 | 2410.85 | 241.09 | 100 | 100 | 0 |
1 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
2 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
3 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
4 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
5 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
6 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
7 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
8 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
9 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
10 | 0.00 | 0.00 | 241.09 | 100 | 100 | 0 |
In addition to these standardized costs, which are included in the expense-loaded premium (sometimes also called the “(actuarial) gross premium”), there are certain loadings and rebates that are applied after the expense-loaded premium \(BP_x\):
\[P_x = \left\{ (BP_x + oUZu - SuRa) \cdot VS \cdot (1-VwGew) + unitC\right\} \cdot \left(1-PrRa-VwGew_{StkK}-PartnerRa\right)\cdot \left(1+uz(k)\right) \cdot (1+tax)\]
with the following surcharges and rebates:
noMedicalExam
)sumRebate
)advanceProfitParticipation
)unitcosts
)premiumRebate
)advanceProfitParticipationInclUnitCost
)partnerRebate
)premiumFrequencyLoading
)tax
)Typically, an insurance premium is calculated with yearly premiums
paid in advance. If premiums are paid more often per year (Parameter
premiumFrequency
or benefitFrequency
set to a
value larger than 1, typically 2 for half-yearly, 4 for quarterly, or 12
for monthly payments), part of the interest during the year is lost, as
well as the premiums for the remainder of the year if the insured event
happens. So usually there is some kind of extra charge included:
premiumFrequencyLoading
and
benefitFrequencyLoading
are set to a list with keys “1”,
“2”, “4” and “12” and values that correspond to the extra charge (as a
factor on the gross premium, i.e. a percentage).premiumFrequencyOrder
and
benefitFrequencyOrder
set to a value larger than 0.costs
is implemented a function with signature \(function(params, values)\), which accesses
params$ContractData$premiumFrequency
to return different
expense rates for different premium payment frequencies.getInterestProfitRate
of the profit scheme can be
implemented as a function that modifies the rate depending on the
premium or benefit frequency (similar to the cost adjustment mentioned
in the previous item).= Tarif.Life$createModification(
Tarif.Life.FrequencyLoading name = "Term life (frequency loading)",
premiumFrequencyLoading = list("1" = 0.0, "2" = 0.01, "4" = 0.015, "12" = 0.02)
)= Tarif.Life$createModification(
Tarif.Life.FrequencyApprox1 name = "Term life (k-th yearly, approx. 1.Ord.)",
premiumFrequencyOrder = 1
)= Tarif.Life$createModification(
Tarif.Life.FrequencyApprox2 name = "Term life (k-th yearly, approx. 2.Ord.)",
premiumFrequencyOrder = 2
)= Tarif.Life$createModification(
Tarif.Life.FrequencyApprox3 name = "Term life (k-th yearly, exact)",
premiumFrequencyOrder = Inf
)
= Tarif.Life$createModification(
Tarif.Life.FrequencyExpense name = "Term life (modified gamma costs)",
costs = function(params, values) {
switch (toString(params$ContractData$premiumFrequency),
"12" = initializeCosts(alpha = 0.04, Zillmer = 0.025, beta = 0.05, gamma.contract = 0.00127, gamma.paidUp = 0.001),
"4" = initializeCosts(alpha = 0.04, Zillmer = 0.025, beta = 0.05, gamma.contract = 0.00119, gamma.paidUp = 0.001),
"2" = initializeCosts(alpha = 0.04, Zillmer = 0.025, beta = 0.05, gamma.contract = 0.0011, gamma.paidUp = 0.001),
initializeCosts(alpha = 0.04, Zillmer = 0.025, beta = 0.05, gamma.contract = 0.001, gamma.paidUp = 0.001)
)
} )
Of course, the loadings and costs mentioned above are not necessarily mathematically derived to offset the interest effect of k-th yearly payments. Rather, they are often simply decided by the management. Thus, the three approaches implemented here can have quite different results:
contractGridPremium(
axes = list(tarif = c(Tarif.Life.FrequencyLoading, Tarif.Life.FrequencyApprox1,
Tarif.Life.FrequencyApprox2, Tarif.Life.FrequencyApprox3,
Tarif.Life.FrequencyExpense),premiumFrequency = c(1, 2, 4, 12)),
age = 40, policyDuration = 20,
sumInsured = 100000,
contractClosing = as.Date("2020-09-01")
%>% kableTable )
1 | 2 | 4 | 12 | |
---|---|---|---|---|
Term life (frequency loading) | 593.6884 | 299.8126 | 150.6484 | 50.46351 |
Term life (k-th yearly, approx. 1.Ord.) | 593.6884 | 297.5766 | 148.9718 | 49.69813 |
Term life (k-th yearly, approx. 2.Ord.) | 593.6884 | 297.5761 | 148.9715 | 49.69802 |
Term life (k-th yearly, exact) | 593.6884 | 297.5761 | 148.9715 | 49.69802 |
Term life (modified gamma costs) | 593.6884 | 303.3269 | 154.4421 | 52.29163 |
Although most mortality tables and expense loadings already have a certain amount of security margins included, often a tariff (mostly protection products) adds an additional security loading directly to the net present value of the benedits.
This can be easily implemented using the parameter
security
:
contractGridPremium(
axes = list(age = seq(30, 60, 10), security = 10*(0:5)/100),
tarif = Tarif.Life,
policyDuration = 20,
sumInsured = 100000,
contractClosing = as.Date("2020-09-01")
%>% kableTable(digits = 2) )
0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | |
---|---|---|---|---|---|---|
30 | 301.75 | 319.42 | 337.09 | 354.75 | 372.42 | 390.09 |
40 | 593.69 | 640.54 | 687.39 | 734.25 | 781.10 | 827.95 |
50 | 1217.61 | 1326.83 | 1436.05 | 1545.27 | 1654.49 | 1763.71 |
60 | 2848.94 | 3121.23 | 3393.52 | 3665.81 | 3938.10 | 4210.39 |
The LifeInsureR package also provides a function to export a given contract to a spreadsheet in Excel format:
exportInsuranceContract.xlsx(contract, filename)
This function takes the contract and exports all data.frames in the
contract’s contract$Values
data list as separate tabs to a
spreadsheet file. It also adds some nice formatting and additional
markup. For contracts with multiple contract blocks (e.g. dynamic
increases / sum increases or riders), the main contract and all its
child blocks are exported as well.
The tab containing the premiums and the premium calculation coefficients even include the formulas, so one can in theory play around with the parameters to see how the premium changes.
If the contract has profit participation features and some profit scenarios have been added, they are exported as well.
Notice, however, that the Excel export is in general a static file containing the current state of the contract and all its values (cash flows, present values, premiums, reserves, profit participation, etc.) as well as the most important parameters and an overview of the history of the contract.
= contract.PureEnd.NoRefund$clone()$
contract.exportExample addDynamics(t = 3, SumInsuredDelta = 10000)$
addDynamics(t = 5, SumInsuredDelta = 15000)$
addDynamics(t = 10, SumInsuredDelta = 15000)$
addDynamics(t = 14, SumInsuredDelta = 10000)
exportInsuranceContract.xlsx(contract.exportExample, filename = "Example_PureEndowment_Dynamics.xlsx")
In real life, an insurance contract is not merely signed initially
and then left untouched until it expires or is surrendered. Rather, some
contracts already have an automatic increase of the sumInsured or the
premium (depending usually on some kind of observed consumer price
index) included in the contract. Other contracts have additional
biometric riders like an additional death or disability cover. Other
contracts are extended after their expiration, using the existing
reserve as a one-time payment (initialCapital
) for the
follow-up contract.
In all these cases, the original contract can be calculated as an
InsuranceContract
, but the additional dynamic increases,
additional riders or extensions are mathematically calculated as
separate contracts (potentiall using different tariffs / types of
insurance), although most parameters are shared from the original main
contract.
In addition to modelling one particular tariff, the
LifeInsuranceContract class can also act as a wrapper to bundle multiple
related contracts / contract slices together. The class provides several
methods for this: *
$addDynamics(t, NewSumInsured, SumInsuredDelta, id, ...)
:
Include (at time t
) a dynamic increase of premium or
sumInsured, but with the same basic parameters (age, tariff, maturity,
interest rate, etc.) as the main contract. The increase can be given
either as the new total sumInsured or as the increase in the sumInsured
caused by that one increase. Other parameters given as ...
are passed on to the InsuranceContract$new
constructor of
the layer for the dynamic increase. This also means that one can
potentially override all parameters for the increase, including the
tariff or the interest rate. *
$addExtension(t = NULL, policyPeriod, ...)
After the
original contracts maturity, append a follow-up contract (by default
paid-up, i.e. no new premiums are paid) that uses the existing reserve
as initial capital. By default, no further premiums are paid and the
sumInsured is calculated from the existing reserve and the tariff of the
extension. One can, however, also provide either a sumInsured or a
premium of the contract extension. In that case, the premium or the
sumInsured will be calculated, using the existing reserves as
initialCapital. *
$addBlock(id = NULL, block = NULL, t, ...)
Generic function
to add a child block to the contract. If a block (object of type
LifeInsuranceContract
is passed, it is inserted and flagged
as starting at time t
. If no block is passed, a new
insurance contract is created using the arguments passed as
...
, combined with the parameters of the main contract. If
t>0
, the child block starts later than the original
contract. It is also possible that the child block extends beyond the
maturity of the original contract (e.g. contract extensions are
implemented this way).
In these case, the main contract will have several child blocks (also LifeInsuranceContract objects), and the values of the main contract object will be the aggregated values of all its children, rather than the results of a calculation from an underlying tariff.
To increase the sum insured or premium by a given value ()
# Contract with initial capital of 5.000 EUR
= InsuranceContract$new(
ctr.dynInc tarif = Tarif.Endowment,
sumInsured = 10000,
age = 40, policyPeriod = 10,
contractClosing = as.Date("2020-09-01")
$
)addDynamics(t = 1, SumInsuredDelta = 1000)$
addDynamics(t = 5, NewSumInsured = 15000)$
addDynamics(t = 8, SumInsuredDelta = 4000)
$Values$basicData
ctr.dynInc#> PremiumPayment SumInsured Premiums InterestRate PolicyDuration PremiumPeriod
#> 1 10000 1087.878 0.005 10 10
#> 1 2 11000 1208.880 0.005 10 10
#> 2 2 11000 1208.880 0.005 10 10
#> 3 2 11000 1208.880 0.005 10 10
#> 4 2 11000 1208.880 0.005 10 10
#> 5 3 15000 2083.097 0.005 10 10
#> 6 3 15000 2083.097 0.005 10 10
#> 7 3 15000 2083.097 0.005 10 10
#> 8 4 19000 4271.768 0.005 10 10
#> 9 4 19000 4271.768 0.005 10 10
#> 10 0 19000 0.000 0.005 10 10
As seen in this table, the sum insured increases and the premium with
it. The PremiumPayment
column is no longer a 0/1-column
indicating whether a premium is paid or not, but rather is the number of
blocks/layers where a premium is paid.
The individual blocks can be accessed with the
contract$blocks
list:
for (b in ctr.dynInc$blocks) {
cat(paste0("Block: ", b$Parameters$ContractData$id, ", starts at t=", b$Parameters$ContractData$blockStart, ", policyPeriod=", b$Parameters$ContractData$policyPeriod, "\n"))
}#> Block: Hauptvertrag, starts at t=0, policyPeriod=10
#> Block: dyn1, starts at t=1, policyPeriod=9
#> Block: dyn2, starts at t=5, policyPeriod=5
#> Block: dyn3, starts at t=8, policyPeriod=2
Each block is formally handled like a separate contract, each
starting at its own time t=0
. The over-all contract then
takes care to correctly shift the child blocks to the time relative to
the parent block, before aggregating the data:
$blocks$Hauptvertrag$Values$basicData
ctr.dynInc#> PremiumPayment SumInsured Premiums InterestRate PolicyDuration PremiumPeriod
#> 0 1 10000 1087.878 0.005 10 10
#> 1 1 10000 1087.878 0.005 10 10
#> 2 1 10000 1087.878 0.005 10 10
#> 3 1 10000 1087.878 0.005 10 10
#> 4 1 10000 1087.878 0.005 10 10
#> 5 1 10000 1087.878 0.005 10 10
#> 6 1 10000 1087.878 0.005 10 10
#> 7 1 10000 1087.878 0.005 10 10
#> 8 1 10000 1087.878 0.005 10 10
#> 9 1 10000 1087.878 0.005 10 10
#> 10 0 10000 0.000 0.005 10 10
$blocks$dyn1$Values$basicData
ctr.dynInc#> PremiumPayment SumInsured Premiums InterestRate PolicyDuration PremiumPeriod
#> 0 1 1000 121.0011 0.005 9 9
#> 1 1 1000 121.0011 0.005 9 9
#> 2 1 1000 121.0011 0.005 9 9
#> 3 1 1000 121.0011 0.005 9 9
#> 4 1 1000 121.0011 0.005 9 9
#> 5 1 1000 121.0011 0.005 9 9
#> 6 1 1000 121.0011 0.005 9 9
#> 7 1 1000 121.0011 0.005 9 9
#> 8 1 1000 121.0011 0.005 9 9
#> 9 0 1000 0.0000 0.005 9 9
$blocks$dyn2$Values$basicData
ctr.dynInc#> PremiumPayment SumInsured Premiums InterestRate PolicyDuration PremiumPeriod
#> 0 1 4000 874.2175 0.005 5 5
#> 1 1 4000 874.2175 0.005 5 5
#> 2 1 4000 874.2175 0.005 5 5
#> 3 1 4000 874.2175 0.005 5 5
#> 4 1 4000 874.2175 0.005 5 5
#> 5 0 4000 0.0000 0.005 5 5
$blocks$dyn3$Values$basicData
ctr.dynInc#> PremiumPayment SumInsured Premiums InterestRate PolicyDuration PremiumPeriod
#> 0 1 4000 2188.671 0.005 2 2
#> 1 1 4000 2188.671 0.005 2 2
#> 2 0 4000 0.000 0.005 2 2
Instead of adding a dynamic increase, which typically uses the same tariff as the main contract, it is also possible to bundle e.g. a protection rider to a saving product. The savings product and the protection rider are calculated individually as child blocks, and the overall values of the contract are obtained by aggregating the values from the two children (savings and protection part). Of course, in this scenario, the combined sumInsured of the overall contract is not meaningful, but the sumInsured of the individual blocks is.
= InsuranceContract$new(
ctr.main tarif = Tarif.Endowment,
sumInsured = 10000,
age = 40, policyPeriod = 10,
contractClosing = as.Date("2020-09-01")
)= InsuranceContract$new(
ctr.Rider tarif = Tarif.L71U,
sumInsured = 100000,
age = 40, policyPeriod = 10,
contractClosing = as.Date("2020-09-01")
)$addBlock(block = ctr.Rider)
ctr.main
= InsuranceContract$new(
ctr.withRider tarif = Tarif.Endowment,
sumInsured = 10000,
age = 40, policyPeriod = 10,
contractClosing = as.Date("2020-09-01")
$
)addBlock(tarif = Tarif.L71U, sumInsured = 100000,
age = 40, policyPeriod = 10,
contractClosing = as.Date("2020-09-01"))
When a contract expires, many companies offer premium-free contract extensions, where the existing reserve is used as initial reserve for a follow-up contract (possibly with new terms and parameters like interest rate or mortalities).
Instead of modifying the original contract and re-calculating it, it is easier to model the extension as a new block with the existing reserve given as . The extension will be calculated like a standalone-contract and the overall contract will aggregate the values from the original contract and the extension. As the extension is a separate contract object, one can pass all contract parameters to the method.
The original premiumPeriod of the main contract is used, so by default the extension will be a premium-free extension, where the sumInsured is calculated from the existing reserve and the benefits and costs of the extensions’ tariff.
To create a premium-free extension explicitly, one can pass (which is the default anyway). To create an extension with regular (or single) premium payments, one can pass either a or a to provide the sum insured and the premium and calculate the other from the given value
# original contract, expiring after 20 years
= InsuranceContract$new(
ContractA tarif = Tarif.Endowment,
age = 40, policyPeriod = 20,
sumInsured = 10000,
contractClosing = as.Date("2000-07-01")
)
# premium-free extension
= ContractA$clone()$
ContractB addExtension(id = "Verlaengerung1", contractPeriod = 5, premiumPeriod = 0)
# sumInsured calculated from existing reserve:
$blocks$Verlaengerung1$Parameters$ContractData$sumInsured
ContractB#> [1] 10723.08
$Values$basicData
ContractB#> PremiumPayment SumInsured Premiums InterestRate PolicyDuration PremiumPeriod
#> 0 1 10000.00 544.8277 0.005 20 20
#> 1 1 10000.00 544.8277 0.005 20 20
#> 2 1 10000.00 544.8277 0.005 20 20
#> 3 1 10000.00 544.8277 0.005 20 20
#> 4 1 10000.00 544.8277 0.005 20 20
#> 5 1 10000.00 544.8277 0.005 20 20
#> 6 1 10000.00 544.8277 0.005 20 20
#> 7 1 10000.00 544.8277 0.005 20 20
#> 8 1 10000.00 544.8277 0.005 20 20
#> 9 1 10000.00 544.8277 0.005 20 20
#> 10 1 10000.00 544.8277 0.005 20 20
#> 11 1 10000.00 544.8277 0.005 20 20
#> 12 1 10000.00 544.8277 0.005 20 20
#> 13 1 10000.00 544.8277 0.005 20 20
#> 14 1 10000.00 544.8277 0.005 20 20
#> 15 1 10000.00 544.8277 0.005 20 20
#> 16 1 10000.00 544.8277 0.005 20 20
#> 17 1 10000.00 544.8277 0.005 20 20
#> 18 1 10000.00 544.8277 0.005 20 20
#> 19 1 10000.00 544.8277 0.005 20 20
#> 20 0 20723.08 0.0000 0.005 20 20
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
#> 0 10723.08 0.0000 0.005 20 0
# extension with given sumInsured resulting in 0 (gross) premiums
= ContractA$clone()$
ContractC addExtension(id = "Verlaengerung1", contractPeriod = 5, sumInsured = 10723.07973354)
$blocks$Verlaengerung1$Values$premiums[["gross"]]
ContractC#> [1] -8.449569e-06
$Values$basicData
ContractC#> PremiumPayment SumInsured Premiums InterestRate PolicyDuration
#> 0 1 10000.00 5.448277e+02 0.005 20
#> 1 1 10000.00 5.448277e+02 0.005 20
#> 2 1 10000.00 5.448277e+02 0.005 20
#> 3 1 10000.00 5.448277e+02 0.005 20
#> 4 1 10000.00 5.448277e+02 0.005 20
#> 5 1 10000.00 5.448277e+02 0.005 20
#> 6 1 10000.00 5.448277e+02 0.005 20
#> 7 1 10000.00 5.448277e+02 0.005 20
#> 8 1 10000.00 5.448277e+02 0.005 20
#> 9 1 10000.00 5.448277e+02 0.005 20
#> 10 1 10000.00 5.448277e+02 0.005 20
#> 11 1 10000.00 5.448277e+02 0.005 20
#> 12 1 10000.00 5.448277e+02 0.005 20
#> 13 1 10000.00 5.448277e+02 0.005 20
#> 14 1 10000.00 5.448277e+02 0.005 20
#> 15 1 10000.00 5.448277e+02 0.005 20
#> 16 1 10000.00 5.448277e+02 0.005 20
#> 17 1 10000.00 5.448277e+02 0.005 20
#> 18 1 10000.00 5.448277e+02 0.005 20
#> 19 1 10000.00 5.448277e+02 0.005 20
#> 20 0 20723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 -8.449569e-06 0.005 20
#> 0 10723.08 0.000000e+00 0.005 20
#> PremiumPeriod
#> 0 20
#> 1 20
#> 2 20
#> 3 20
#> 4 20
#> 5 20
#> 6 20
#> 7 20
#> 8 20
#> 9 20
#> 10 20
#> 11 20
#> 12 20
#> 13 20
#> 14 20
#> 15 20
#> 16 20
#> 17 20
#> 18 20
#> 19 20
#> 20 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
#> 20
# extension with increased sumInsured: real premiums are charged, reserves start from the existing reserve:
= ContractA$clone()$
ContractD addExtension(id = "Verlaengerung1", contractPeriod = 5, sumInsured = 20000)
$Values$basicData
ContractD#> PremiumPayment SumInsured Premiums InterestRate PolicyDuration PremiumPeriod
#> 0 1 10000 544.8277 0.005 20 20
#> 1 1 10000 544.8277 0.005 20 20
#> 2 1 10000 544.8277 0.005 20 20
#> 3 1 10000 544.8277 0.005 20 20
#> 4 1 10000 544.8277 0.005 20 20
#> 5 1 10000 544.8277 0.005 20 20
#> 6 1 10000 544.8277 0.005 20 20
#> 7 1 10000 544.8277 0.005 20 20
#> 8 1 10000 544.8277 0.005 20 20
#> 9 1 10000 544.8277 0.005 20 20
#> 10 1 10000 544.8277 0.005 20 20
#> 11 1 10000 544.8277 0.005 20 20
#> 12 1 10000 544.8277 0.005 20 20
#> 13 1 10000 544.8277 0.005 20 20
#> 14 1 10000 544.8277 0.005 20 20
#> 15 1 10000 544.8277 0.005 20 20
#> 16 1 10000 544.8277 0.005 20 20
#> 17 1 10000 544.8277 0.005 20 20
#> 18 1 10000 544.8277 0.005 20 20
#> 19 1 10000 544.8277 0.005 20 20
#> 20 1 30000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 1 20000 564.8817 0.005 20 20
#> 0 20000 0.0000 0.005 20 20
# extension with regular premiums, which are given: sumInsured is calculated from it, reserves start from the existing reserve:
= ContractA$clone()$
ContractD addExtension(id = "Verlaengerung1", contractPeriod = 5, premium = 597.8771)
$Values$basicData
ContractD#> PremiumPayment SumInsured Premiums InterestRate PolicyDuration PremiumPeriod
#> 0 1 10000 544.8277 0.005 20 20
#> 1 1 10000 544.8277 0.005 20 20
#> 2 1 10000 544.8277 0.005 20 20
#> 3 1 10000 544.8277 0.005 20 20
#> 4 1 10000 544.8277 0.005 20 20
#> 5 1 10000 544.8277 0.005 20 20
#> 6 1 10000 544.8277 0.005 20 20
#> 7 1 10000 544.8277 0.005 20 20
#> 8 1 10000 544.8277 0.005 20 20
#> 9 1 10000 544.8277 0.005 20 20
#> 10 1 10000 544.8277 0.005 20 20
#> 11 1 10000 544.8277 0.005 20 20
#> 12 1 10000 544.8277 0.005 20 20
#> 13 1 10000 544.8277 0.005 20 20
#> 14 1 10000 544.8277 0.005 20 20
#> 15 1 10000 544.8277 0.005 20 20
#> 16 1 10000 544.8277 0.005 20 20
#> 17 1 10000 544.8277 0.005 20 20
#> 18 1 10000 544.8277 0.005 20 20
#> 19 1 10000 544.8277 0.005 20 20
#> 20 1 30000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 1 20000 564.8818 0.005 20 20
#> 0 20000 0.0000 0.005 20 20
While many insurance contracts have a fixed sum insured and constant premium, many contracts include some kind of adjustment to account for inflation. There are various ways to achieve such an adjustment:
The LifeInsuranceContract package provides functionality for each of these increases. All three increases can in theory be combined in the same contract, although in practice this usually does not happen and at most one kind of increase is included in a contract
With dynamic increases, the contract initially is written with a fixed sum insured and constant premiums over the whole contract period. The future increases are not considered at all.
After the initial contract inception, either yearly or when a consumer price index changes by a value larger than a given threshold, the sum insured is increased (either by a fixed amount or by an amount determined by the index change) and the premium is adjusted accordingly. Internally, the original contract is left untouched and the increase is modelled by a separate contract with the same key parameters, only with shorter duration and a sum insured that represents only the increase. The premium for this increase is calculated like a separate contract with only the difference in the over-all sum insured as its sum insured.
Each dynamic increase then adds another separate tiny InsuranceContract object and the over-all values are the sums of all those contract blocks (sometimes also called “contract slices”).
The InsuranceContract
class provides a method to add a
dynamic increase:
InsuranceContract$addDynamics(t, NewSumInsured, SumInsuredDelta, id, ...)
Only one of NewSumInsured
(new total sum insured) and
SumInsuredDelta
(only the difference between old and new
sum insured) is needed. This method adds a new contract block to the
given InsuranceContract, starting at time \(t\) with SumInsuredDelta
as
its sum insured and its premium calculated from the shorter contract
period and the sum insured delta. These blocks for dynamic increases are
stored in the contract’s $blocks
list of children. The
values stored in the contract are then simply the sum of all its
children.
Here is an example of a 10-year endowment, which has dynamic increases at times \(t=5\), \(t=7\) and \(t=8\):
# For comparison: Contract with constant annuity
= InsuranceContract$new(
contract.Endowment.Dynamics tarif = Tarif.Endowment,
sumInsured = 10000,
age = 40,
policyPeriod = 10,
contractClosing = as.Date("2020-09-01"),
id = "Initial contract"
$
)addDynamics(t = 5, NewSumInsured = 11000, id = "Dynamic at 5")$
addDynamics(t = 7, NewSumInsured = 12000, id = "Dynamic at 7")$
addDynamics(t = 8, NewSumInsured = 13500, id = "Dynamic at 8")
# Over-all contract sum insured and premiums for all blocks combined
$Values$basicData[,c("SumInsured", "Premiums")] %>% pander contract.Endowment.Dynamics
SumInsured | Premiums | |
---|---|---|
0 | 10000 | 1087.88 |
1 | 10000 | 1087.88 |
2 | 10000 | 1087.88 |
3 | 10000 | 1087.88 |
4 | 10000 | 1087.88 |
5 | 11000 | 1306.43 |
6 | 11000 | 1306.43 |
7 | 12000 | 1671.09 |
8 | 13500 | 2491.84 |
9 | 13500 | 2491.84 |
10 | 13500 | 0.00 |
t | Over-all contract | Initial contract | Dynamic at 5 | Dynamic at 7 | Dynamic at 8 |
---|---|---|---|---|---|
0 | 10000 | 10000 | 0 | 0 | 0 |
1 | 10000 | 10000 | 0 | 0 | 0 |
2 | 10000 | 10000 | 0 | 0 | 0 |
3 | 10000 | 10000 | 0 | 0 | 0 |
4 | 10000 | 10000 | 0 | 0 | 0 |
5 | 11000 | 10000 | 1000 | 0 | 0 |
6 | 11000 | 10000 | 1000 | 0 | 0 |
7 | 12000 | 10000 | 1000 | 1000 | 0 |
8 | 13500 | 10000 | 1000 | 1000 | 1500 |
9 | 13500 | 10000 | 1000 | 1000 | 1500 |
10 | 13500 | 10000 | 1000 | 1000 | 1500 |
t | Over-all contract | Initial contract | Dynamic at 5 | Dynamic at 7 | Dynamic at 8 |
---|---|---|---|---|---|
0 | 1087.88 | 1087.88 | 0.00 | 0.00 | 0.00 |
1 | 1087.88 | 1087.88 | 0.00 | 0.00 | 0.00 |
2 | 1087.88 | 1087.88 | 0.00 | 0.00 | 0.00 |
3 | 1087.88 | 1087.88 | 0.00 | 0.00 | 0.00 |
4 | 1087.88 | 1087.88 | 0.00 | 0.00 | 0.00 |
5 | 1306.43 | 1087.88 | 218.55 | 0.00 | 0.00 |
6 | 1306.43 | 1087.88 | 218.55 | 0.00 | 0.00 |
7 | 1671.09 | 1087.88 | 218.55 | 364.66 | 0.00 |
8 | 2491.84 | 1087.88 | 218.55 | 364.66 | 820.75 |
9 | 2491.84 | 1087.88 | 218.55 | 364.66 | 820.75 |
10 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
In addition to the above guaranteed values, many contracts also
include some kind of profit sharing. The total amount of money to be
distributed is usually predetermined by law or regulation (in Austria by
the “Lebensversicherung-Gewinnbeteiligungsverordnung
– LV-GBV”, but the actual way they are distributed to individual
contracts is up the insurance undertaking. The profit participation
scheme defines profit participation allocations based on certain rates
and bases, where the formulas and the types of profit are pre-determined
in a formal document (which in this package will be implemented as an
object of class ProfitParticipation
), while the profit
rates are determined by the management of the undertaking
(“discretionary benefits”).
Typical Austrian insurance contracts have one of two kinds of profit sharing mechanisms:
The profit participation scheme of a tarif is represented by an
object of the ProfitParticipation
class. While the
InsuranceContract.Parameters
list contains elements for the
profit rates, the implementation of the calculation of the profit parts
is done by functions defined in the ProfitParticipation
constructor.
This scheme is passed to the InsuranceTarif
or
InsuranceContract
via the
profitParticipationScheme
parameter. `
There are different types of profit participation assignments, based on the type of risks they are based upon:
Each of these profit components in general depends in some way on a profit rate and a basis to which the rate is applied. So each component has the general functional form: \[Profit^{type}_t = Calc\left(Rate^{type}_t, Basis^{type}_t\right)\] The most common calculation function is a simple multiplication, i.e. \(Calc(r, b) = r\cdot b\), but other functions are possible, to
The (default) parameters that can be passed to the
ProfitParticipation
constructor are:
General profit setup | |
waitingPeriod |
During the waiting period at the beginning of a contract, no profit participation is assigned |
profitComponents |
describes the different components of profit participation (“interest”, “risk”, “expense”, “sum”, “terminal”) |
profitClass |
a profit ID used to identify the correct profit rates (rates are defined per profit class) |
profitRates |
a data frame containing company-wide profit rates. Key columns are year and profitClass |
Advance profit participation rates | |
advanceProfitParticipation |
premium rebate (percentage discount on the gross premium) |
advanceProfitParticipationInclUnitCost |
premium rebate (percentage discount on the gross premium including unit costs) |
Regular profit participation rates | |
guaranteedInterest \(i\) |
Contract-specific override of the guaranteed intereste rate (only for profit participation purposes) |
interestProfitRate \(ip_t\) |
Profit interest rate (added to the guaranteed interest rate to arrive at the total credited rate) |
totalInterest \(tcr_t\) |
The total credited interest rate (sum of guaranteed interest and profit participation interest) |
mortalityProfitRate \(rp_t\) |
Mortality / risk profit rate |
expenseProfitRate \(ep_t\) |
Expenso profit rate |
sumProfitRate \(sp_t\) |
Sum profit rate (typically a function, depending on sum insured) |
terminalBonusRate \(tb_t\) |
Terminal bonus rate |
terminalBonusFundRate \(tbf_t\) |
Terminal bonus fund rate, i.e. which percentage of the assigned profits are withheld in a separate terminal bonus fund and only paid out at maturity. |
For the calculation of the profit participation, the
ProfitParticipation
class holds a list of function pointers
to calculate each component of profit participation, as outlined above.
For each of interest, risk, expense, sum, terminal and terminal bonus
fund the following three functions can be given:
Profit rate: return the profit rate as a function from the values of the contract
Function signature: function(rates, ...)
Profit base: The quantity on which to apply the rate. Typically this function returns either the current reserve, the previous reserve (or some combination), the sum insured or the current risk premium.
Function signature:
function(rates, params, values, ...)
Calculation: A function taking the rate and the base and calculate the profit assigned for the specific profit component. Most common are a simple multiplication of base and rate, but other formulas are possible, too.
Function signature:
function(base, rate, waiting, rates, params, values, ...)
Thus, the constructor of the ProfitParticipation
class
also takes the following parameters:
Type of profit | Function for rate | Function for base | Function for calculation |
---|---|---|---|
interest on accrued profit | getInterestOnProfits |
- (existing profit) | - |
interest profit | getInterestProfitRate |
getInterestProfitBase |
calculateInterestProfit |
risk profit | getRiskProfitRate |
getRiskProfitBase |
calculateRiskProfit |
expense profit | getExpenseProfitRate |
getExpenseProfitBase |
calculateExpenseProfit |
sum profit | getSumProfitRate |
getSumProfitBase |
calculateSumProfit |
terminal bonus | getTerminalBonusRate |
getTerminalBonusBase |
calculateTerminalBonus |
terminal bonus fund | getTerminalBonusFundRate |
getTerminalBonusFundBase |
calculateTerminalBonusFund |
In addition, the following parameters define functions for reserves:
getTerminalBonusReserve
… Calculate the reserve for
the terminal bonus from the bonus assignments (old tariffs often use
some kind of discounting or conditional reserving for the terminal bonus
reserve)
Function signature:
function(profits, rates, terminalBonus, terminalBonusAccount, params, values)
To calculate the actual benefits paid out from profit participation,
the following parameters take the corresponding functions (signature:
function(profits, rates, params, values, ...)
)
calculateSurvivalBenefit |
Benefit from profit participation at maturity (in addition to the guaranteed payout) |
calculateDeathBenefitAccrued |
Benefit from profit participation upon death (in addition to the guaranteed payout) |
calculateDeathBenefitTerminal |
Benefit from terminal bonus upon death (in addition to the guaranteed payout and regular profit participation) |
calculateSurrenderBenefitAccrued |
Benefit from profit participation upon contract surrender (in addition to the surrender value) |
calculateSurrenderBenefitTerminal |
Benefit from terminal bonus upon contract surrender (in addition to the surrender value and regular profit participation) |
calculatePremiumWaiverBenefitAccrued |
Benefit from profit participation upon premium waiver (in addition to the surrender value) |
calculatePremiumWaiverBenefitTerminal |
Benefit from terminal bonus upon premium waiver surrender (in addition to the surrender value and regular profit participation) |
While the details of a profit participation scheme are very specific
and no two profit schemes are exactly alike, the basic functionality to
extract rates and bases and the calculation functions are usually not so
different. For this reason, the LifeInsureR
package
provides several little helper functions that provide the most common
functionality for the definition of rates, bases and the profit
calculation. See ?ProfitParticipationFunctions
for the full
list.
The most common functions are:
PP.base.PreviousZillmerReserve(rates, params, values, ...)
PP.base.contractualReserve(rates, params, values, ...)
PP.base.previousContractualReserve(rates, params, values, ...)
PP.base.meanContractualReserve(rates, params, values, ...)
PP.base.ZillmerRiskPremium(rates, params, values, ...)
PP.base.sumInsured(rates, params, values, ...)
PP.base.totalProfitAssignment(res, ...)
PP.rate.interestProfit(rates, ...)
PP.rate.riskProfit(rates, ...)
PP.rate.expenseProfit(rates, ...)
PP.rate.sumProfit(rates, ...)
PP.rate.terminalBonus(rates, ...)
PP.rate.terminalBonusFund(rates, ...)
PP.rate.interestProfitPlusGuarantee(rates, ...)
PP.rate.totalInterest(rates, ...)
PP.calculate.RateOnBase(base, rate, waiting, rates, params, values, ...)
PP.calculate.RateOnBaseMin0(base, rate, waiting, rates, params, values, ...)
PP.calculate.RatePlusGuaranteeOnBase(base, rate, waiting, rates, params, values, ...)
PP.benefit.ProfitPlusTerminalBonusReserve(profits, ...)
PP.benefit.Profit(profits, ...)
PP.benefit.ProfitPlusGuaranteedInterest(profits, rates, ...)
PP.benefit.ProfitPlusTotalInterest(profits, rates, params, values)
PP.benefit.ProfitPlusHalfTotalInterest(profits, ...)
PP.benefit.ProfitPlusInterestMinGuaranteeTotal(profits, rates, ...)
PP.benefit.TerminalBonus5YearsProRata(profits, params, ...)
PP.benefit.TerminalBonus5Years(profits, params, ...)
PP.benefit.TerminalBonus(profits, params, ...)
For example, imagine a tariff’s total cumulated assigned profit \(G_t\) and the benefits at time \(t\) have the formulas: \[Prof_t = \left(G_{t-1} + TBF_{t-1}\right) \cdot \left(1 + i + ip_t\right) + ip_t \cdot \frac{\left(Res_{t-1} + Res_{t}\right)}{2} + rp_t \cdot P^r_t + ep_t \cdot SumInsured + sp_t(SI) \cdot SumInsured\] \[G_t = G_{t-1} + (1 - tbf_t) \cdot Prof_t\] \[TBF_t = TBF_{t-1} + tbf_t \cdot Prof_t\] \[Death_t = G_t \cdot \left(1 + i + ip_t\right) + TBF_t\] \[Matu_n = G_n + TBF_n\] \[Surrender_t = G_t\cdot \left(1+\frac{i + ip_t}{2}\right) + 0.5 \cdot TBF_t\] \[Red_t = G_t + 0.5 \cdot TBF_t\]
These formulas can be interpreted as following:
PP.rate.interestProfitPlusGuarantee
PP.base.meanContractualReserve
PP.base.ZillmerRiskPremium
The values of \(Res_t\), \(P^r_t\), \(SumInsured\) and the guaranteed interest \(i^g_t\) are prescribed by the tariff or contract, while the profit participation rates \(ip_t\), \(rp_t\), \(ep_t\) and \(sp_t\) are decided on a year-by-year basis by the management boards.
The benefits for death, maturity, surrender and premium waivers are:
PP.benefit.ProfitPlusInterestMinGuaranteeTotal
PP.benefit.Profit
PP.benefit.ProfitPlusHalfInterestMinGuaranteeTotal
PP.benefit.Profit
This profit scheme can be easily be implemented as a
ProfitParticipation
object, where one can pass the
functions for bases and calculation and also provide default profit
rates:
= ProfitParticipation$new(
ProfitScheme.example name = "Example Profit Scheme, V 1.0",
profitComponents = c("interest", "risk", "expense", "sum", "TBF"),
getInterestOnProfits = PP.rate.interestProfitPlusGuarantee,
getInterestProfitBase = PP.base.meanContractualReserve,
getRiskProfitBase = PP.base.ZillmerRiskPremium,
getExpenseProfitBase = PP.base.sumInsured,
getSumProfitBase = PP.base.sumInsured,
getTerminalBonusFundBase = PP.base.totalProfitAssignment,
mortalityProfitRate = 0.15,
expenseProfitRate = 0.01,
sumProfitRate = function(params, ...) {if (params$ContractData$sumInsured > 1000000) 0.005 else 0;},
terminalBonusFundRate = 0.3,
calculateSurvivalBenefit = PP.benefit.ProfitPlusTerminalBonusReserve,
calculateDeathBenefitAccrued = PP.benefit.ProfitPlusInterestMinGuaranteeTotal,
calculateDeathBenefitTerminal = PP.benefit.TerminalBonus,
calculateSurrenderBenefitAccrued = PP.benefit.ProfitPlusHalfInterestMinGuaranteeTotal,
calculateSurrenderBenefitTerminal = function(profits, ...) { profits[, "TBF"] / 2 },
calculatePremiumWaiverBenefitAccrued = PP.benefit.Profit,
calculatePremiumWaiverBenefitTerminal = function(profits, ...) { profits[, "TBF"] / 2 },
profitClass = NULL
)
The calculation functions are not given, as they default to the
correct PP.calculate.RateOnBase
anyway. The interest profit
rates are not given, as they will vary over time with no default value.
Rathery, they need to be passed to the call to
InsuranceContract$addProfitScenario()
to calculate one
particular profit scenario with given rates. In contrast, the mortality,
expense and sum profit rates are initialized with some default values,
which will be used if a profit scenario does not explicitly give those
profit rates.
The profit scheme defined above can now be used with the
profitParticipationScheme
parameter in a tariff or a
contract. Usually, the profit scheme is a property of the product, so it
should be specified in the tariff, but can be overridden in a
contract.
As an example, let us create an endowment contract with 500% death benefit that uses this profit scheme:
= InsuranceContract$new(
contract.Endow.PP tarif = Tarif.Endowment,
sumInsured = 10000,
deathBenefit = 5,
age = 50, policyPeriod = 15,
profitParticipationScheme = ProfitScheme.example,
contractClosing = as.Date("2020-09-01")
)
In contrast to the guaranteed values, which can and will be
calculated as soon as the contract is created, the profit participation
needs to be explicitly called with the desired rates. A contract can
store multiple different profit scenarios, which can be added with the
addProfitScenario()
method. This method can also be chained
to add multiple scenarios (e.g. )
$
contract.Endow.PPaddProfitScenario(id = "Current total credited rate", guaranteedInterest = 0.005, interestProfitRate = 0.02, totalInterest = 0.025)$
addProfitScenario(id = "Current TCR-1%", guaranteedInterest = 0.005, interestProfitRate = 0.01, totalInterest = 0.015)$
addProfitScenario(id = "Current TCR+1%", guaranteedInterest = 0.005, interestProfitRate = 0.03, totalInterest = 0.035)
All profit scenarios are stored in the
InsuranceContract$Values$profitScenarios
list indexed with
the id given in the call.
The array containing all values of the profit scenario first holds the calculation basis and the rate for each of the profit components:
$Values$profitScenarios$`Current total credited rate` %>%
contract.Endow.PPas.data.frame() %>%
select(ends_with("Base"), ends_with("Interest"), ends_with("Rate"), -TBFRate, -TBFBase, -totalInterest) %>%
rowid_to_column("t") %>% mutate(t = t - 1) %>% kable()
t | interestBase | riskBase | expenseBase | sumBase | guaranteedInterest | interestProfitRate | riskProfitRate | expenseProfitRate | sumProfitRate | interestOnProfitRate |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0.00000 | 0.0000 | 10000 | 10000 | 0.000 | 0.00 | 0.00 | 0.00 | 0 | 0.000 |
1 | 18.56845 | 139.2892 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
2 | 803.01905 | 151.7772 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
3 | 1578.39853 | 165.1467 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
4 | 2343.76130 | 179.4261 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
5 | 3098.03332 | 194.8334 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
6 | 3839.92597 | 211.5669 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
7 | 4567.84836 | 230.0169 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
8 | 5280.07413 | 250.0469 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
9 | 5975.02268 | 271.4863 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
10 | 6651.23186 | 294.2541 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
11 | 7307.44935 | 317.9992 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
12 | 7942.76663 | 342.3761 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
13 | 8556.80171 | 366.6734 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
14 | 9149.32455 | 391.2964 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
15 | 9720.07684 | 415.8936 | 10000 | 10000 | 0.005 | 0.02 | 0.15 | 0.01 | 0 | 0.025 |
The base for interest profit is the average of the reserve:
t | SumInsured | Zillmer | AvgZillmer |
---|---|---|---|
0 | 10000 | -375.81 | -187.90 |
1 | 10000 | 412.95 | 18.57 |
2 | 10000 | 1193.09 | 803.02 |
3 | 10000 | 1963.70 | 1578.40 |
4 | 10000 | 2723.82 | 2343.76 |
5 | 10000 | 3472.25 | 3098.03 |
6 | 10000 | 4207.60 | 3839.93 |
7 | 10000 | 4928.09 | 4567.85 |
8 | 10000 | 5632.06 | 5280.07 |
9 | 10000 | 6317.99 | 5975.02 |
10 | 10000 | 6984.47 | 6651.23 |
11 | 10000 | 7630.43 | 7307.45 |
12 | 10000 | 8255.11 | 7942.77 |
13 | 10000 | 8858.50 | 8556.80 |
14 | 10000 | 9440.15 | 9149.32 |
15 | 10000 | 10000.00 | 9720.08 |
From the bases and rates, the calculation function (in our case simply the multiplication of rate and base) is applied to arrive at the yearly profit allocation of each component:
$Values$profitScenarios$`Current total credited rate` %>%
contract.Endow.PPas.data.frame() %>%
select(ends_with("Profit"), totalProfitAssignment, -totalProfit) %>%
rowid_to_column("t") %>% mutate(t = t - 1) %>%
pander
t | interestProfit | riskProfit | expenseProfit | sumProfit | componentsProfit | interestOnProfit | totalProfitAssignment |
---|---|---|---|---|---|---|---|
0 | 0.00 | 0.00 | 0 | 0 | 0.00 | 0.00 | 0.00 |
1 | 0.37 | 20.89 | 100 | 0 | 121.26 | 0.00 | 121.26 |
2 | 16.06 | 22.77 | 100 | 0 | 138.83 | 3.03 | 141.86 |
3 | 31.57 | 24.77 | 100 | 0 | 156.34 | 6.58 | 162.92 |
4 | 46.88 | 26.91 | 100 | 0 | 173.79 | 10.65 | 184.44 |
5 | 61.96 | 29.23 | 100 | 0 | 191.19 | 15.26 | 206.45 |
6 | 76.80 | 31.74 | 100 | 0 | 208.53 | 20.42 | 228.96 |
7 | 91.36 | 34.50 | 100 | 0 | 225.86 | 26.15 | 252.01 |
8 | 105.60 | 37.51 | 100 | 0 | 243.11 | 32.45 | 275.56 |
9 | 119.50 | 40.72 | 100 | 0 | 260.22 | 39.34 | 299.56 |
10 | 133.02 | 44.14 | 100 | 0 | 277.16 | 46.83 | 323.99 |
11 | 146.15 | 47.70 | 100 | 0 | 293.85 | 54.92 | 348.77 |
12 | 158.86 | 51.36 | 100 | 0 | 310.21 | 63.64 | 373.86 |
13 | 171.14 | 55.00 | 100 | 0 | 326.14 | 72.99 | 399.13 |
14 | 182.99 | 58.69 | 100 | 0 | 341.68 | 82.97 | 424.65 |
15 | 194.40 | 62.38 | 100 | 0 | 356.79 | 93.59 | 450.37 |
The totalProfitAssignment
column is the sum of all
component allocations in the given year (the \(Prof_t\) in the formulas above).
After all components are calculated, the yearly profit assignment is split into the part that is accrued in the regular bonus and the part that is placed in the terminal bonus fund, using the terminal bonus fund rate. Finally, the yearly regular and terminal bonus assignments can be summed up to the regular and the terminal bonus:
$Values$profitScenarios$`Current total credited rate` %>%
contract.Endow.PPas.data.frame() %>%
select(TBFBase, TBFRate, TBFBonusAssignment, regularBonusAssignment, TBF, regularBonus, totalProfit) %>%
rowid_to_column("t") %>% mutate(t = t - 1) %>%
pander
t | TBFBase | TBFRate | TBFBonusAssignment | regularBonusAssignment | TBF | regularBonus | totalProfit |
---|---|---|---|---|---|---|---|
0 | 0.00 | 0.0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
1 | 121.26 | 0.3 | 36.38 | 84.89 | 36.38 | 84.89 | 121.26 |
2 | 141.86 | 0.3 | 42.56 | 99.30 | 78.94 | 184.19 | 263.12 |
3 | 162.92 | 0.3 | 48.88 | 114.04 | 127.81 | 298.23 | 426.04 |
4 | 184.44 | 0.3 | 55.33 | 129.11 | 183.14 | 427.34 | 610.48 |
5 | 206.45 | 0.3 | 61.93 | 144.51 | 245.08 | 571.85 | 816.93 |
6 | 228.96 | 0.3 | 68.69 | 160.27 | 313.77 | 732.12 | 1045.89 |
7 | 252.01 | 0.3 | 75.60 | 176.40 | 389.37 | 908.52 | 1297.89 |
8 | 275.56 | 0.3 | 82.67 | 192.89 | 472.03 | 1101.41 | 1573.45 |
9 | 299.56 | 0.3 | 89.87 | 209.69 | 561.90 | 1311.11 | 1873.01 |
10 | 323.99 | 0.3 | 97.20 | 226.79 | 659.10 | 1537.90 | 2197.00 |
11 | 348.77 | 0.3 | 104.63 | 244.14 | 763.73 | 1782.04 | 2545.77 |
12 | 373.86 | 0.3 | 112.16 | 261.70 | 875.89 | 2043.74 | 2919.63 |
13 | 399.13 | 0.3 | 119.74 | 279.39 | 995.63 | 2323.13 | 3318.75 |
14 | 424.65 | 0.3 | 127.39 | 297.25 | 1123.02 | 2620.38 | 3743.40 |
15 | 450.37 | 0.3 | 135.11 | 315.26 | 1258.13 | 2935.64 | 4193.77 |
The last step in the calculation of a scenario is to calculate the benefits for each of the possible types of payout:
$Values$profitScenarios$`Current total credited rate` %>%
contract.Endow.PPas.data.frame() %>%
select(survival, deathAccrued, death, surrenderAccrued, surrender, premiumWaiverAccrued, premiumWaiver) %>%
rowid_to_column("t") %>% mutate(t = t - 1) %>%
pander
t | survival | deathAccrued | death | surrenderAccrued | surrender | premiumWaiverAccrued | premiumWaiver |
---|---|---|---|---|---|---|---|
0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
1 | 121.26 | 87.01 | 123.39 | 85.95 | 104.14 | 84.89 | 103.08 |
2 | 263.12 | 188.79 | 267.73 | 186.49 | 225.96 | 184.19 | 223.65 |
3 | 426.04 | 305.68 | 433.50 | 301.96 | 365.86 | 298.23 | 362.14 |
4 | 610.48 | 438.02 | 621.16 | 432.68 | 524.25 | 427.34 | 518.91 |
5 | 816.93 | 586.15 | 831.23 | 579.00 | 701.54 | 571.85 | 694.39 |
6 | 1045.89 | 750.42 | 1064.19 | 741.27 | 898.15 | 732.12 | 889.00 |
7 | 1297.89 | 931.24 | 1320.61 | 919.88 | 1114.57 | 908.52 | 1103.21 |
8 | 1573.45 | 1128.95 | 1600.98 | 1115.18 | 1351.20 | 1101.41 | 1337.43 |
9 | 1873.01 | 1343.88 | 1905.79 | 1327.49 | 1608.45 | 1311.11 | 1592.06 |
10 | 2197.00 | 1576.34 | 2235.44 | 1557.12 | 1886.67 | 1537.90 | 1867.45 |
11 | 2545.77 | 1826.59 | 2590.32 | 1804.31 | 2186.18 | 1782.04 | 2163.90 |
12 | 2919.63 | 2094.83 | 2970.72 | 2069.28 | 2507.23 | 2043.74 | 2481.68 |
13 | 3318.75 | 2381.21 | 3376.83 | 2352.17 | 2849.98 | 2323.13 | 2820.94 |
14 | 3743.40 | 2685.89 | 3808.91 | 2653.14 | 3214.65 | 2620.38 | 3181.89 |
15 | 4193.77 | 3009.03 | 4267.17 | 2972.34 | 3601.40 | 2935.64 | 3564.71 |
One can add as many profit scenarios as desired. Each of the rates in
the addProfitScenario
-call can also be a vector giving the
corresponding rate for each year of the contract:
$
contract.Endow.PPaddProfitScenario(id = "decreasing TCR", guaranteedInterest = 0.005,
interestProfitRate = (15:0)/15 * 0.02,
expenseProfitRate = c(rep(0.01, 5), rep(0.005, 5), rep(0, 6)))
$Values$profitScenarios$`decreasing TCR` %>%
contract.Endow.PPas.data.frame() %>%
select(interestBase, expenseBase, interestProfitRate, expenseProfitRate, interestOnProfitRate, interestProfit, expenseProfit, totalProfit) %>%
rowid_to_column("t") %>% mutate(t = t - 1) %>%
kable
t | interestBase | expenseBase | interestProfitRate | expenseProfitRate | interestOnProfitRate | interestProfit | expenseProfit | totalProfit |
---|---|---|---|---|---|---|---|---|
0 | 0.00000 | 10000 | 0.0000000 | 0.000 | 0.0000000 | 0.000000 | 0 | 0.0000 |
1 | 18.56845 | 10000 | 0.0186667 | 0.010 | 0.0236667 | 0.346611 | 100 | 121.2400 |
2 | 803.01905 | 10000 | 0.0173333 | 0.010 | 0.0223333 | 13.918997 | 100 | 260.6333 |
3 | 1578.39853 | 10000 | 0.0160000 | 0.010 | 0.0210000 | 25.254376 | 100 | 416.1329 |
4 | 2343.76130 | 10000 | 0.0146667 | 0.010 | 0.0196667 | 34.375166 | 100 | 585.6060 |
5 | 3098.03332 | 10000 | 0.0133333 | 0.005 | 0.0183333 | 41.307111 | 50 | 716.8742 |
6 | 3839.92597 | 10000 | 0.0120000 | 0.005 | 0.0170000 | 46.079112 | 50 | 856.8752 |
7 | 4567.84836 | 10000 | 0.0106667 | 0.005 | 0.0156667 | 48.723716 | 50 | 1003.5258 |
8 | 5280.07413 | 10000 | 0.0093333 | 0.005 | 0.0143333 | 49.280692 | 50 | 1154.6974 |
9 | 5975.02268 | 10000 | 0.0080000 | 0.005 | 0.0130000 | 47.800181 | 50 | 1308.2316 |
10 | 6651.23186 | 10000 | 0.0066667 | 0.000 | 0.0116667 | 44.341546 | 0 | 1411.9740 |
11 | 7307.44935 | 10000 | 0.0053333 | 0.000 | 0.0103333 | 38.973063 | 0 | 1513.2373 |
12 | 7942.76663 | 10000 | 0.0040000 | 0.000 | 0.0090000 | 31.771066 | 0 | 1609.9840 |
13 | 8556.80171 | 10000 | 0.0026667 | 0.000 | 0.0076667 | 22.818138 | 0 | 1700.1463 |
14 | 9149.32455 | 10000 | 0.0013333 | 0.000 | 0.0063333 | 12.199099 | 0 | 1781.8075 |
15 | 9720.07684 | 10000 | 0.0000000 | 0.000 | 0.0050000 | 0.000000 | 0 | 1853.1005 |
In the Excel export, a separate tab of the Excel file will hold all profit scenarios added to the contract.
While the cash-flow approach described above and based on the
type
parameter of the InsuranceTarif
works
very well for all standart types of life insurance, sometimes a tariff
does not follow the standard behaviour exactly. The valuation approach
with all-determining cash flows is still correct, but the cash flows
might need to be adjusted. For this, some hook functions are provided
that allow modification of the contract’s internals (e.g. cash flows)
before all other calculations commence.
Two hooks are provided, which allow modifications of the cash flow profiles before present values, premiums and reserves are calculated:
adjustCashFlows |
Adjust the premium and benefit cash flows of the contract |
adjustCashFlowsCosts |
Adjust the cost cash flows |
adjustPremiumCoefficients |
Adjust the coefficients of the premium calculation formulas |
The function signature is
function(x, params, values, ...)
, where x
is
the object holding the standard cash flows determined for the contract.
The return value of the hook function will be used instead of
x
.
The hook has a slightly extended function signature .
An example where the cash-flow-approach solely based on
type
does not immediately work is a waiting period of 3
years for the death benefit. In particular, if a person dies during the
first 3 years of the contract, no death benefit is paid out.
The easiest way to implement such cash flows is to let the
InsuranceTarif
first create the standard cash flows (with
benefits during the first three years) and then provide a hook function
that nullifies the benefits in the waiting period, before all present
values, premiums and reserves are calculated.
= InsuranceContract$new(
contract.Endow.Waiting tarif = Tarif.Endowment,
sumInsured = 10000,
age = 50, policyPeriod = 15,
contractClosing = as.Date("2020-09-01"),
adjustCashFlows = function(x, ...) { x[1:3, "death_SumInsured"] = 0; x }
)
$Values$cashFlows[,c("premiums_advance", "survival_advance", "death_SumInsured")] %>% pander contract.Endow.Waiting
premiums_advance | survival_advance | death_SumInsured | |
---|---|---|---|
0 | 1 | 0 | 0 |
1 | 1 | 0 | 0 |
2 | 1 | 0 | 0 |
3 | 1 | 0 | 1 |
4 | 1 | 0 | 1 |
5 | 1 | 0 | 1 |
6 | 1 | 0 | 1 |
7 | 1 | 0 | 1 |
8 | 1 | 0 | 1 |
9 | 1 | 0 | 1 |
10 | 1 | 0 | 1 |
11 | 1 | 0 | 1 |
12 | 1 | 0 | 1 |
13 | 1 | 0 | 1 |
14 | 1 | 0 | 1 |
15 | 0 | 1 | 0 |
contractGridPremium(
axes = list(age = seq(20, 80, 10), adjustCashFlows = c(function(x, ...) x, function(x, ...) { x[1:3, "death_SumInsured"] = 0; x })),
tarif = Tarif.Endowment,
sumInsured = 10000,
policyPeriod = 15,
contractClosing = as.Date("2020-09-01")
%>% `colnames<-`(c("Full benefit", "Waiting period"))
) #> adjustCashFlows
#> age Full benefit Waiting period
#> 20 757.7664 756.5401
#> 30 758.5546 757.3658
#> 40 764.1524 761.6906
#> 50 781.2534 773.6785
#> 60 818.9056 798.1579
#> 70 916.0787 867.0739
#> 80 1330.8063 1121.3076
Another example are term-fixe insurances where the Zillmer premium also includes the administration (gamma) costs over the whole contract period.
= initializeCosts(alpha = 0.04, Zillmer = 0.035, gamma = 0.0015, gamma.fullcontract = 0.001),
costs = function(coeff, type, premiums, params, values, premiumCalculationTime) {
adjustPremiumCoefficients if (type == "Zillmer") {
"SumInsured"]][["costs"]]["gamma", "SumInsured", "guaranteed"] = 1
coeff[[
}
coeff },