The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Using the LifeInsureR Package

Reinhold Kainhofer

Open Tools
reinhold@kainhofer.com

2023-10-27

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.

1 General Overview of the Concepts

An insurance contract is described by three different objects;

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.

2 A simple example: Term life insurance

To understand how the package implements life insurance contracts, let us look at a simple example:

2.1 Product description

Term Life Insurance

Costs:

Surrender Value:

2.2 Tariff implementation (InsuranceTarif)

library(magrittr)
library(MortalityTables)
library(LifeInsureR)
mortalityTables.load("Austria_Census")

Tarif.L71U = InsuranceTarif$new(
    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) { 
      surrenderReserve * 0.9 
    }
);

2.3 Creating a contract

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:

contract.L71U  = InsuranceContract$new(
  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:

contract.L71U$Values$premiums
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
contract.L71U$Values$reserves
Table continues below
  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):

contract.L71U$Values$cashFlows
  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
contract.L71U$Values$cashFlowsCosts[,,,"survival"]
  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.

contract.L71U$Values$presentValues
  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
contract.L71U$Values$presentValuesCosts
  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
contract.L71U$Values$premiums
  .
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.

contract.L71U$Values$reserves
Table continues below
  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.

contract.L71U$Values$premiumComposition
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.prf = contract.L71U$premiumWaiver(t = 3)
contract.L71U.prf$Values$reserves
Table continues below
  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

2.4 Creating tables with various parameters

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):

grd = contractGridPremium(
  axes = list(age = seq(20, 80, 5), policyPeriod = seq(10, 40, 5)),
  tarif = Tarif.L71U, 
  contractClosing = as.Date("2020-08-18"), 
  sumInsured = 100000
)
grd
age
policyPeriod
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:

grd = contractGridPremium(
  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
age
policyPeriod
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
age
policyPeriod
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
age
policyPeriod
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):

grd = contractGridPremium(
  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

3 All possible parameters

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):

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)

4 Tarif and Contract Specification

An insurance contract is modelled by the abstract product specification (InsuranceTarif class) and the concrete (individualized) InsuranceContract.

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.

4.1 Creating the tariff

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:

Tarif.PureEnd = InsuranceTarif$new(
  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) {
    n = params$ContractData$policyPeriod
    # Surrender Penalty is 10% at the beginning and decreases linearly to 0%
    surrenderReserve * (0.9 + 0.1 * (0:n)/n)
  }
)

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.SP = Tarif.PureEnd$createModification(
  name = "Example Tariff - Pure Endowment (SP)",
  tarif = "PE1-SP",
  desc = "A pure endowment with single premiums",
  premiumPeriod = 1
)

4.1.1 Sample tariffs for the most common life insurance types

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
example.Costs = initializeCosts(
  alpha = 0.04, Zillmer = 0.025, 
  beta = 0.05, 
  gamma.contract = 0.001, gamma.paidUp = 0.001
)
example.Surrender = function(surrenderReserve, params, values) {
    n = params$ContractData$policyPeriod
    # Surrender Penalty is 10% at the beginning and decreases linearly to 0%
    surrenderReserve * (0.9 + 0.1 * (0:n)/n)
}

Endowment

Tarif.Endowment = InsuranceTarif$new(
  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

Tarif.Life = InsuranceTarif$new(
  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)

Tarif.ImmAnnuity = InsuranceTarif$new(
  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.)
Tarif.DefAnnuity = InsuranceTarif$new(
  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
ddTable = mortalityTable.period(name = "Linear dread-disease table", 
                                ages = 0:100, deathProbs = 0:100/500)
Tarif.DreadDisease = InsuranceTarif$new(
  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
)

4.2 Creating a contract

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:

contract.PureEnd = InsuranceContract$new(
    Tarif.PureEnd,
    age = 50, policyPeriod = 20, 
    premiumFrequency = 12,
    sumInsured = 100000,
    contractClosing = as.Date("2020-07-01")
  )
contract.PureEnd$Values$premiums
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
contract.PureEnd$Values$premiumComposition
Table continues below
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:

contract.PureEnd.NoRefund = InsuranceContract$new(
    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:

contract.PureEnd.SP1 = InsuranceContract$new(
    Tarif.PureEnd,
    age = 40, policyPeriod = 45, premiumPeriod = 1,
    sumInsured = 100000,
    contractClosing = as.Date("2020-07-01")
  )
contract.PureEnd.SP2 = InsuranceContract$new(
    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.

4.3 Determining Sum Insured from the premium

By default, the insurance contract is created for a given sum insured and the premiums are calculated accordingly. Sometimes, the reverse is needed: The premium (either actuarial gross premium, written premium before or after taxes) is given and the corresponding sum insured should be determined automatically.

The InsuranceContract constructor / parameter set has an additional field premium to indicate the desired premium (written premium after tax by default) from which the sum insured is then calculated:

# Premium calculated from sumInsured
contract.End = InsuranceContract$new(
  Tarif.Endowment, age = 35, policyPeriod = 10,
  contractClosing = as.Date("2020-08-18"), 
  sumInsured = 10000);

# sumInsured derived from written premium
contract.End.premium = InsuranceContract$new(
  Tarif.Endowment, age = 35, policyPeriod = 10,
  contractClosing = as.Date("2020-08-18"), 
  premium = 1139.06);

contract.End.premiumBeforeTax = InsuranceContract$new(
  Tarif.Endowment, age = 35, policyPeriod = 10,
  contractClosing = as.Date("2020-08-18"), 
  premium = c(written_beforetax = 1095.25));

contract.End.premiumGross = InsuranceContract$new(
  Tarif.Endowment, age = 35, policyPeriod = 10,
  contractClosing = as.Date("2020-08-18"), 
  premium = c(gross = 1085.25));
#> # A tibble: 4 × 7
#>   Contract              net   Zillmer gross written_beforetax written sumInsured
#>   <chr>                 <chr> <chr>   <chr> <chr>             <chr>   <chr>     
#> 1 contract.End          976.… 1004.2… 1085… 1095.24887464624  1139.0… 10000     
#> 2 contract.End.premium  976.… 1004.2… 1085… 1095.25           1139.06 10000.010…
#> 3 contract.End.premium… 976.… 1004.2… 1085… 1095.25           1139.06 10000.010…
#> 4 contract.End.premium… 976.… 1004.2… 1085… 1095.25           1139.06 10000.010…

The final written premium can be directly passed as the premium argument. Other types of premium must be passed as a named number (i.e. a one-element vector with name “gross”, “written_beforetax” or “written”).

If a premium is prescribed, a sumInsured must not be given. If both are given, the sumInsured is used and the prescribed premium is silently ignored.

The are cases, when the sumInsured cannot be derived from a prescribed premium. One relevant case is when a premium rebate depends on the sum insured, since in this case we need the sumInsured to calculate the rebate and thus the premium. All rebates, add-ons and other parameters will temporarily use a sumInsured=1 during the calculation of the actual sumInsured!

4.4 Providing additional capital at contract inception

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
contract.Endow.initialCapital = InsuranceContract$new(
  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
contract.Endow = InsuranceContract$new(
  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

4.5 Premium Waivers

After a while, many customers do not want to pay premiums for the contract any more and convert the contract to a paid-up contract. The unit benefit cash flows from that moment on stay the same, but the sum insured is adjusted, so that the existing reserve is able to cover all future benefits and costs. Furthermore, paid-up contracts typically have differen costs / loadings. Additionally, the surrender penalty is usually applied to the reserve before the conversion.

Waiving premiums and recalculating the sum insured is very easy, one just calls the method InsuranceContract$premiumWaiver(t = ..) on the existing contract.

contract.PureEnd.NoRefund.Prf = contract.PureEnd.NoRefund$clone()$premiumWaiver(t = 7)
contract.PureEnd.NoRefund.Prf$Values$reserves
Table continues below
  SumInsured net Zillmer adequate gamma contractual conversion alphaRefund reduction
0 100000.00 0.00 -2410.85 0.00 0.00 -2410.85 -2410.85 2410.85 0.00
1 100000.00 4301.39 2005.61 628.14 0.00 2005.61 2005.61 1928.68 3934.29
2 100000.00 8639.14 6458.68 5150.40 0.00 6458.68 6458.68 1446.51 7905.19
3 100000.00 13016.56 10951.67 9712.73 0.00 10951.67 10951.67 964.34 11916.01
4 100000.00 17437.40 15488.36 14318.93 0.00 15488.36 15488.36 482.17 15970.53
5 100000.00 21906.03 20073.13 18973.39 0.00 20073.13 20073.13 0.00 20073.13
6 100000.00 26427.51 24711.09 23681.23 0.00 24711.09 24711.09 0.00 24711.09
7 32905.04 27100.86 27100.86 0.00 395.92 27496.79 27496.79 0.00 27496.79
8 32905.04 27391.51 27391.51 2276.49 366.91 27758.42 27758.42 0.00 27758.42
9 32905.04 27701.50 27701.50 4580.08 337.79 28039.28 28039.28 0.00 28039.28
10 32905.04 28032.72 28032.72 6914.11 308.53 28341.25 28341.25 0.00 28341.25
11 32905.04 28387.01 28387.01 9282.30 279.11 28666.11 28666.11 0.00 28666.11
12 32905.04 28766.05 28766.05 11688.65 249.49 29015.54 29015.54 0.00 29015.54
13 32905.04 29171.17 29171.17 14137.32 219.63 29390.80 29390.80 0.00 29390.80
14 32905.04 29604.05 29604.05 16632.88 189.50 29793.55 29793.55 0.00 29793.55
15 32905.04 30066.25 30066.25 19180.06 159.04 30225.29 30225.29 0.00 30225.29
16 32905.04 30559.40 30559.40 21783.93 128.20 30687.60 30687.60 0.00 30687.60
17 32905.04 31085.51 31085.51 24450.07 96.94 31182.44 31182.44 0.00 31182.44
18 32905.04 31648.31 31648.31 27185.86 65.19 31713.50 31713.50 0.00 31713.50
19 32905.04 32252.93 32252.93 30000.59 32.91 32285.83 32285.83 0.00 32285.83
20 32905.04 32905.04 32905.04 32905.04 0.00 32905.04 32905.04 0.00 32905.04
  PremiumsPaid Surrender PremiumFreeSumInsured
0 4821.7 0.00 0.00
1 9643.4 3560.53 4465.70
2 14465.1 7193.72 8960.81
3 19286.8 10903.15 13483.97
4 24108.5 14692.88 18033.69
5 28930.2 18567.64 22608.41
6 33751.9 22981.31 27747.88
7 33751.9 27496.79 32905.04
8 33751.9 27758.42 32905.04
9 33751.9 28039.28 32905.04
10 33751.9 28341.25 32905.04
11 33751.9 28666.11 32905.04
12 33751.9 29015.54 32905.04
13 33751.9 29390.80 32905.04
14 33751.9 29793.55 32905.04
15 33751.9 30225.29 32905.04
16 33751.9 30687.60 32905.04
17 33751.9 31182.44 32905.04
18 33751.9 31713.50 32905.04
19 33751.9 32285.83 32905.04
20 33751.9 32905.04 32905.04

Notice that the contract changes are made directly to the contract (“reference semantics”). This is different from the typical behavior of R, where any change to e.g. a data.frame leaves the original data.frame intact and instead returns a modified copy.

5 Calculation Approach

5.1 Valuation

The calculation of all contract values is controlled by the function InsuranceContract$calculateContract() (using methods of the InsuranceTarif object) and follows the following logic:

  1. First the contingent (unit) cash flows and the transition probbilities are determined.
  2. The actuarial equivalence principle states that at time of inception, the (net and gross) premium must be determined in a way that the present value of the future benefits and costs minus the present value of the future premiums must be equal, i.e. in expectation the future premiums ove the whole lifetime of the contract will exactly cover the benefits and costs. Similarly, at all later time steps, the difference between these two present values needs to be reserved (i.e. has already been paid by the customer by previous premiums).
  3. This allows the premiums to be calculated by first calculating the present values for all of the benefit and costs cash flow vectors.
  4. The formulas to calculate the gross, Zillmer and net premiums involve simple linear combinations of these present values, so the coefficients of these formulas are determined next.
  5. With the coefficients of the premium formulas calculated, all premiums can be calculated (first the gross premium, because due to potential gross premium refunds in case of death, the formula for the net premium requires the gross premium, which the formula for the gross premium involves no other type of premuim).
  6. With premiums determined, all unit cash flows and unit present values can now be expressed in monetary terms / as absolute cash flows (i.e. the actual Euro-amount that flows rather than a percentage).
  7. As described above, the difference between the present values of premiums and present values of benefits and costs is defined as the required amount of reserves, so the reserves (net, gross, administration cost, balance sheet) and all values derived from them (i.e. surrender value, sum insured in case of premium waiver, etc.) are calculated.
  8. The decomposition of the premium into parts dedicated to specific purposes (tax, rebates, net premium, gross premium, Zillmer premium, cost components, risk premium, savings premium, etc.) can be done once the reserves are ready (since e.g. the savings premium is defined as the difference of discounted reserves at times \(t\) and \(t+1\)).
  9. If the contract has (discretionary or obligatory) profit sharingB mechanisms included, the corresponding [ProfitParticipation] object can calculate that profit sharing amounts, once all guaranteed values are calculated. This can also be triggered manually (with custom profit sharing rates) by calling the methods InsuranceContract$profitScenario() or InsuranceContract$addProfitScenario().

5.2 Cash Flows

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):

6 Cost structure

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:
costs.Bsp = 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

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:

contract.PureEnd.NoRefund$Values$absCashFlows
  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:

6.1 Frequency charges

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:

Tarif.Life.FrequencyLoading = Tarif.Life$createModification(
  name = "Term life (frequency loading)",
  premiumFrequencyLoading = list("1" = 0.0, "2" = 0.01, "4" = 0.015, "12" = 0.02)
)
Tarif.Life.FrequencyApprox1 = Tarif.Life$createModification(
  name = "Term life (k-th yearly, approx. 1.Ord.)",
  premiumFrequencyOrder = 1
)
Tarif.Life.FrequencyApprox2 = Tarif.Life$createModification(
  name = "Term life (k-th yearly, approx. 2.Ord.)",
  premiumFrequencyOrder = 2
)
Tarif.Life.FrequencyApprox3 = Tarif.Life$createModification(
  name = "Term life (k-th yearly, exact)",
  premiumFrequencyOrder = Inf
)

Tarif.Life.FrequencyExpense = Tarif.Life$createModification(
  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
tarif
premiumFrequency
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

6.2 Security loadings

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)
age
security
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

7 Creating premium and contract grids

When developing a new product or comparing different products, it is often required to create tables of premiums or reserves for a product/tarif where just some of the parameters (like age, sum insured or maturity) change.

For this purpose, this package provides two functions to create two- or higher-dimensional grids of contracts with each dimension representing one of the parameters varying.

The grid is defined by the axes argument to the contractGrid() call. This is a named list giving all parameters that should vary inside the grid. Any of the parameters of the InsuranceContract$new() constructor can be used in the axes.

For example, one can compare multiple tariffs or multiple varying pararameters.

Let us look at the pure endowment above, which we implemented as a single-premium variant and a variant with regular premiums, both of which have a potential (partial or full) premium refund in case of death. How do the premiums of these contracts compare and how do the premiums depend on the premium refund proportion?

grd = contractGridPremium(
  axes = list(tarif = c(Tarif.PureEnd, Tarif.Endowment, Tarif.PureEnd.SP), premiumRefund = c(0, 0.5, 1)),
  age = 50, policyPeriod = 20,
  sumInsured = 10000,
  contractClosing = as.Date("2020-09-01")
)
grd
tarif
premiumRefund
0 0.5 1
Example Tariff - Pure Endowment 511.8569 539.9719 571.4267
Example Tariff - Endowment 597.8353 630.7708 667.6189
Example Tariff - Pure Endowment (SP) 9062.9858 9792.9789 10651.0307

The default implementation of contractGridPremium returns the written premium, but one can also choose other types of premiums to display, or even other contract values (like reserves).

If one needs to investigate multiple values, it is better to first create a grid of insurance contract objects and store it, so that the call to contractGridPremium does not have to re-calculate the same contracts over and over again, extract just one premium and discard the whole contract.

grd = contractGrid(
  axes = list(tarif = c(Tarif.PureEnd, Tarif.Endowment, Tarif.PureEnd.SP), premiumRefund = c(0, 0.5, 1)),
  age = 50, policyPeriod = 20,
  sumInsured = 10000,
  contractClosing = as.Date("2020-09-01")
)
# Compare net premiums without loadings:
contractGridPremium(grd, premium = "net")
tarif
premiumRefund
0 0.5 1
Example Tariff - Pure Endowment 426.7908 451.2802 478.6787
Example Tariff - Endowment 501.6818 530.3701 562.4663
Example Tariff - Pure Endowment (SP) 7739.6669 8378.4109 9129.2062
# Compare premium sums over the whole contract period (all contracts have the same sumInsured)
contractGridPremium(grd, .fun = function(c) {with(c$Values, 
     unitPremiumSum * premiums["written"])
})
tarif
premiumRefund
0 0.5 1
Example Tariff - Pure Endowment 10237.14 10799.44 11428.53
Example Tariff - Endowment 11956.71 12615.42 13352.38
Example Tariff - Pure Endowment (SP) 9062.99 9792.98 10651.03
# Compare risk premiums at time t=10 (the 11th row of the premium decomposition)
contractGridPremium(grd, .fun = function(c) {c$Values$premiumComposition[11, "risk"]})
tarif
premiumRefund
0 0.5 1
Example Tariff - Pure Endowment -37.58 -17.98 3.96
Example Tariff - Endowment 35.11 58.07 83.76
Example Tariff - Pure Endowment (SP) -64.75 -32.81 4.74
# Compare present value of all benefits and refunds (without costs) at time t=0
contractGridPremium(grd, .fun = function(c) {c$Values$absPresentValues[1, "benefitsAndRefund"]})
tarif
premiumRefund
0 0.5 1
Example Tariff - Pure Endowment 7739.67 8183.77 8680.63
Example Tariff - Endowment 9097.78 9618.03 10200.08
Example Tariff - Pure Endowment (SP) 7739.67 8378.41 9129.21

Other useful examples of grid comparisons include e.g. the effect of the interest rate and the mortality table on the premiums:

grd = contractGridPremium(
  axes = list(mortalityTable = mort.AT.census["m", -(1:10)], i = c(0, 0.005, 0.01), age = c(30, 45, 60), policyPeriod = c(10, 20)),
  tarif = Tarif.Life,
  contractClosing = as.Date("2020-09-01"),
  sumInsured = 10000
)
grd
mortalityTable
i
0 0.005 0.01
ÖVSt 1990/92 M 40.30 40.20 40.09
ÖVSt 2000/02 M 34.96 34.88 34.80
ÖVSt 2010/12 M 31.77 31.71 31.66
ÖVSt 2020/22 M 31.25 31.19 31.15
mortalityTable
i
0 0.005 0.01
ÖVSt 1990/92 M 53.07 52.58 52.11
ÖVSt 2000/02 M 45.12 44.74 44.37
ÖVSt 2010/12 M 38.72 38.46 38.21
ÖVSt 2020/22 M 36.53 36.33 36.14
mortalityTable
i
0 0.005 0.01
ÖVSt 1990/92 M 89.76 89.27 88.79
ÖVSt 2000/02 M 76.96 76.53 76.11
ÖVSt 2010/12 M 63.17 62.83 62.50
ÖVSt 2020/22 M 54.30 54.05 53.79
mortalityTable
i
0 0.005 0.01
ÖVSt 1990/92 M 139.45 137.45 135.48
ÖVSt 2000/02 M 111.57 110.15 108.76
ÖVSt 2010/12 M 98.07 96.70 95.36
ÖVSt 2020/22 M 84.95 83.75 82.58
mortalityTable
i
0 0.005 0.01
ÖVSt 1990/92 M 288.97 287.00 285.06
ÖVSt 2000/02 M 218.10 216.59 215.10
ÖVSt 2010/12 M 191.56 190.38 189.23
ÖVSt 2020/22 M 178.94 177.73 176.53
mortalityTable
i
0 0.005 0.01
ÖVSt 1990/92 M 440.61 433.80 427.15
ÖVSt 2000/02 M 351.92 346.15 340.51
ÖVSt 2010/12 M 291.32 286.96 282.70
ÖVSt 2020/22 M 284.30 279.82 275.45

8 Exporting contract data to Excel

The LifeInsureR package also provides a function to export a given contract to a spreadsheet in Excel format:

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.exportExample = contract.PureEnd.NoRefund$clone()$
  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")

9 Creating examples for the Austrian Financial Market Authority

When introducing a new tariff, an Austrian insurance company has to submit a detailled mathematical description (so-called “Versicherungsmathematische Grundlagen”) of the tariff to the Financial Market Authority (FMA), which have to follow the regulation on actuarial bases for life insurance – LV-VMGV. The sections and contents of this document are strictly defined in the regulation, and in addition to the formulas of the tariff, an example contract has to be calculated with the following key parameters:

Given a tariff and a derived contract (with the above key parameters), this package provides two functions to calculate and print / export the required values either to the console / screen or to a file:

The function showVmGlgExamples calculates all required values for the actuarial bases document in the required order (including values after a premium waiver at the given time). If a filename is given, the output is exported to that text file, otherwise it is simply printed out in the R console:

VMGL.contract = InsuranceContract$new(
    Tarif.PureEnd,
    age = 35, policyPeriod = 30, 
    premiumFrequency = 1,
    sumInsured = 100000,
    contractClosing = as.Date("2020-07-01")
  )

showVmGlgExamples(VMGL.contract)
#> Tarif: Example Tariff - Pure Endowment
#> VS: 100000.00
#> Rechenzins: 0%, Sterbetafel: ÖVSt 2010/2012 U
#> 
#> Prämien:
#> ========
#> Nettoprämie:          3101.31
#> Zillmerprämie:        3198.88
#> Bruttoprämie:         3534.13
#> Vorgeschr.Pr.:        3685.90
#> 
#> Sparprämie (t=10):    3190.01
#> Risikoprämie (t=10):     8.87
#> 
#> Reserven:
#> =========
#> Prämienpflichtig (t=10):             30052.86
#> Pr.Frei (mangels Zahlung) (tprf=12): 24635.68
#> VwKostenreserve (t=10):                 -0.00
#> VwKostenreserve (Pr.Frei) (tprf=12):   496.91
#> 
#> Bilanzreserve (t=10.50):             31730.97
#> Prämienübertrag (BM= 7):              1772.07
#> 
#> Rückkauf und Prämienfreistellung:
#> =================================
#> Rückkaufsreserve (t=10):          30052.86
#> Rückkaufswert (t=10):             28049.33
#> Abschlusskostenrücktrag (t=10):       0.00
#> 
#> Rückkaufswert (Prf.) (t=12):      28049.33 (VS: 29726.53)
#> 
#> Prämienfreie VS (t=10):           29726.53

The exportInsuranceContractExample provides no new functionality, but combines the Excel export feature and the regulatory example together. For a given contract it will create three files:

One can give a base name and an extra name to distinguish different calculations in the file names.

10 Contracts combining multiple contract layers / slices

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.

10.1 Dynamic increases

To increase the sum insured or premium by a given value ()

# Contract with initial capital of 5.000 EUR
ctr.dynInc = InsuranceContract$new(
  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)


ctr.dynInc$Values$basicData
#>    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:

ctr.dynInc$blocks$Hauptvertrag$Values$basicData
#>    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
ctr.dynInc$blocks$dyn1$Values$basicData
#>   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
ctr.dynInc$blocks$dyn2$Values$basicData
#>   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
ctr.dynInc$blocks$dyn3$Values$basicData
#>   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

10.2 General biometric riders

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.

ctr.main = InsuranceContract$new(
  tarif = Tarif.Endowment,
  sumInsured = 10000,
  age = 40, policyPeriod = 10,
  contractClosing = as.Date("2020-09-01")
)
ctr.Rider = InsuranceContract$new(
  tarif = Tarif.L71U, 
  sumInsured = 100000, 
  age = 40, policyPeriod = 10,
  contractClosing = as.Date("2020-09-01")
)
ctr.main$addBlock(block = ctr.Rider)

ctr.withRider = InsuranceContract$new(
  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"))

10.3 Extending a contract beyond its maturity

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
ContractA = InsuranceContract$new(
  tarif = Tarif.Endowment,
  age = 40, policyPeriod = 20,
  sumInsured = 10000,
  contractClosing = as.Date("2000-07-01")
)

# premium-free extension
ContractB = ContractA$clone()$
  addExtension(id = "Verlaengerung1", contractPeriod = 5, premiumPeriod = 0)
# sumInsured calculated from existing reserve:
ContractB$blocks$Verlaengerung1$Parameters$ContractData$sumInsured
#> [1] 10723.08
ContractB$Values$basicData
#>    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
ContractC = ContractA$clone()$
  addExtension(id = "Verlaengerung1", contractPeriod = 5, sumInsured = 10723.07973354)
ContractC$blocks$Verlaengerung1$Values$premiums[["gross"]]
#> [1] -8.449569e-06
ContractC$Values$basicData
#>    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:
ContractD = ContractA$clone()$
  addExtension(id = "Verlaengerung1", contractPeriod = 5, sumInsured = 20000)
ContractD$Values$basicData
#>    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:
ContractD = ContractA$clone()$
  addExtension(id = "Verlaengerung1", contractPeriod = 5, premium = 597.8771)
ContractD$Values$basicData
#>    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

11 Handling contracts with increases

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

11.1 Fixed yearly premium increases

With this kind of increases, the initial contract valuation (i.e. the determination of the premium at contract inception) already takes into account that the premium will not stay constant over the whole period, but increases by a constant factor each year. The sum insured is calculated by the equivalence principle so that the expected present value of all future benefits and costs equals the expected present value of all future (i.e.  increasing) premium payments.

This type of yearly premium increase by a fixed factor can be implemented by the parameter:

In the following example, we create a 10-year endowment contract with constant premiums over the whole period and another one with idential parameters except that the premium increases by 4% each year:

# For comparison: Contract with constant premiums
contract.Endow.Constant = InsuranceContract$new(
  tarif = Tarif.Endowment,
  sumInsured = 10000,
  age = 50, policyPeriod = 10,
  contractClosing = as.Date("2020-09-01")
)
# Contract with 4% yearly premium increase and same sum insured
contract.Endow.PremInc = InsuranceContract$new(
  tarif = Tarif.Endowment,
  sumInsured = 10000,
  premiumIncrease = 1.04,
  age = 50, policyPeriod = 10,
  contractClosing = as.Date("2020-09-01")
)
premium.comparison = data.frame(
  `Sum Insured` = contract.Endow.Constant$Values$basicData[,"SumInsured"],
  `Constant Premium` = contract.Endow.Constant$Values$basicData[,"Premiums"],
  `4% Yearly Increase` = contract.Endow.PremInc$Values$basicData[,"Premiums"],
  check.names = F
  )
premium.comparison %>% pander
  Sum Insured Constant Premium 4% Yearly Increase
0 10000 1101 911.66
1 10000 1101 948.13
2 10000 1101 986.05
3 10000 1101 1025.50
4 10000 1101 1066.52
5 10000 1101 1109.18
6 10000 1101 1153.54
7 10000 1101 1199.69
8 10000 1101 1247.67
9 10000 1101 1297.58
10 10000 0 0.00

11.2 Fixed yearly benefit increases with constant premium

With this kind of increases, the premium will stay constant over the whole contract maturity, but the death and/or survival benefit (or the annuity payment) will increase by a fixed factor each year. This is typically to safeguard the benefit against inflation, so that the value of the annuity payment or death benefit does not diminish due to inflation. The initial contract valuation (i.e. the determination of the constant premium at contract inception) already takes into account that the benefits will not stay constant over the whole period, but increases by a constant factor each year.

This type of yearly benefit increase by a fixed factor can be implemented by the parameters:

In the following example, we create a 10-year endowment contract with constant premiums over the whole period and another one with idential parameters except that the premium increases by 4% each year:

# For comparison: Contract with constant premiums
contract.TermLife.Constant = InsuranceContract$new(
  tarif = Tarif.Life,
  sumInsured = 10000,
  age = 50, policyPeriod = 10,
  contractClosing = as.Date("2020-09-01")
)
# Contract with 4% yearly increase in sum insured (final survival benefit is 10.000)
contract.TermLife.SumInc = InsuranceContract$new(
  tarif = Tarif.Life,
  sumInsured = 10000,
  deathBenefit = (1.04)^(0:20),
  age = 50, policyPeriod = 10,
  contractClosing = as.Date("2020-09-01")
)
premium.comparison = data.frame(
  `Const S.I.` = contract.TermLife.Constant$Values$absCashFlows[,"death"],
  `Const. Premium` = contract.TermLife.Constant$Values$absCashFlows[,"premiums_advance"],
  `4% sum increase` = contract.TermLife.SumInc$Values$absCashFlows[,"death"],
  `Premium w. sum increase` = contract.TermLife.SumInc$Values$absCashFlows[,"premiums_advance"],
  check.names = F
  )
premium.comparison
Const S.I. Const. Premium 4% sum increase Premium w. sum increase
10000 61 10000.00 72.74
10000 61 10400.00 72.74
10000 61 10816.00 72.74
10000 61 11248.64 72.74
10000 61 11698.59 72.74
10000 61 12166.53 72.74
10000 61 12653.19 72.74
10000 61 13159.32 72.74
10000 61 13685.69 72.74
10000 61 14233.12 72.74
0 0 0.00 0.00

For annuities, the benefit increase is not handled through deathBenefits, but rather through the parameter

In the following example, we create a 10-year endowment contract with constant premiums over the whole period and another one with idential parameters except that the premium increases by 4% each year:

# For comparison: Contract with constant annuity
contract.Annuity.Constant = InsuranceContract$new(
  tarif = Tarif.DefAnnuity,
  sumInsured = 1200,
  age = 55, 
  policyPeriod = 10,
  deferralPeriod = 5,
  premiumPeriod = 5,
  contractClosing = as.Date("2020-09-01")
)
# Contract with 4% yearly increase in annuity benefits
contract.Annuity.Increasing = InsuranceContract$new(
  tarif = Tarif.DefAnnuity,
  sumInsured = 1200,
  annuityIncrease = 1.04,
  age = 55, 
  policyPeriod = 10,
  deferralPeriod = 5,
  premiumPeriod = 5,
  contractClosing = as.Date("2020-09-01")
)
# Contract with 4% yearly increase in premiums and in annuity payments
contract.Annuity.IncreasingBoth = InsuranceContract$new(
  tarif = Tarif.DefAnnuity,
  sumInsured = 1200,
  annuityIncrease = 1.04,
  premiumIncrease = 1.04,
  age = 55, 
  policyPeriod = 10,
  deferralPeriod = 5,
  premiumPeriod = 5,
  contractClosing = as.Date("2020-09-01")
)
premium.comparison = data.frame(
  `Const. Annuity` = contract.Annuity.Constant$Values$absCashFlows[,"survival_advance"],
  `Const. Premium` = contract.Annuity.Constant$Values$absCashFlows[,"premiums_advance"],
  `4% Annuity Increase` = contract.Annuity.Increasing$Values$absCashFlows[,"survival_advance"],
  `Premium w. Ann.Increase` = contract.Annuity.Increasing$Values$absCashFlows[,"premiums_advance"],
  `Inc.Premium w. Ann.Increase` = contract.Annuity.IncreasingBoth$Values$absCashFlows[,"premiums_advance"],
  check.names = F
  )
premium.comparison %>% pander
Const. Annuity Const. Premium 4% Annuity Increase Premium w. Ann.Increase Inc.Premium w. Ann.Increase
0 1277.47 0.00 1382.86 1271.94
0 1277.47 0.00 1382.86 1322.81
0 1277.47 0.00 1382.86 1375.73
0 1277.47 0.00 1382.86 1430.75
0 1277.47 0.00 1382.86 1487.98
1200 0.00 1200.00 0.00 0.00
1200 0.00 1248.00 0.00 0.00
1200 0.00 1297.92 0.00 0.00
1200 0.00 1349.84 0.00 0.00
1200 0.00 1403.83 0.00 0.00
0 0.00 0.00 0.00 0.00

11.3 Dynamic Increases

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:

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
contract.Endowment.Dynamics = InsuranceContract$new(
  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
contract.Endowment.Dynamics$Values$basicData[,c("SumInsured", "Premiums")] %>% pander
  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
Sum Insured for the over-all contract and each of the blocks
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
Premium time series for the over-all contract and each of the blocks
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

12 Profit participation

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:

12.1 Advance profit participation (premium rebate)

To implement advance profit participation, one still needs to create a ProfitParticipation object, which can be empty. The contract parameters advanceProfitParticipation and advanceProfitParticipationInclUnitCost then define the premium rebate. They can be set either in the profit scheme or in the tariff definition. The latter is usually the easier way, as one profit scheme might be applicable to multiple different tariffs with different advance profit participation rates.

As an example, we will use our Tarif.Life whole life tarif defined above and add a \(38\%\) advance profit participation on the premium:

profit.Advance.V1 = ProfitParticipation$new(
    name = "Profit Scheme for advance profit participation, V 1.0",
    advanceProfitParticipation = 0.38
);

Tarif.Life.withPP = Tarif.Life$createModification(
  name = "Example Tariff - Whole/Term Life with profit sharing",
  tarif = "Life1PP",
  profitParticipationScheme = profit.Advance.V1
)

contract.LifePP = InsuranceContract$new(
  tarif = Tarif.Life.withPP,
  age = 40, policyPeriod = 10,
  sumInsured = 100000,
  contractClosing = as.Date("2019-09-01")
)

The premium composition shows that the profit participation

contract.LifePP$Values$premiumComposition
t charged tax unitcosts profit.advance gross net
0 184.54 7.1 0 -108.76 286.2 160.11
1 184.54 7.1 0 -108.76 286.2 160.11
2 184.54 7.1 0 -108.76 286.2 160.11
3 184.54 7.1 0 -108.76 286.2 160.11
4 184.54 7.1 0 -108.76 286.2 160.11
5 184.54 7.1 0 -108.76 286.2 160.11
6 184.54 7.1 0 -108.76 286.2 160.11
7 184.54 7.1 0 -108.76 286.2 160.11
8 184.54 7.1 0 -108.76 286.2 160.11
9 184.54 7.1 0 -108.76 286.2 160.11
10 0.00 0.0 0 0.00 0.0 0.00

12.2 The ProfitParticiption class

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:

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:

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)

12.2.1 Existing functions to use

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, ...)

12.2.2 Example profit scheme

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:

  • There are multiple profit components: interest profit, risk profit, expense profit and sum profit.
  • The total profit assignment \(Prof_t\) in year \(t\) consists of:
    • Interest (guarantee + interest profit rate) on the accrued profits and the terminal bonus fund
    • Interest profit on the average reserve
    • Risk profit as part of the risk premium (similar to advance profit participation, but on a year-by-year basis on the actual risk premium)
    • Expense profit relative to the sum insured
    • Sum profit relative to the sum insured. The rate depends on the sum insured, too (e.g. higher SI typically have higher sum profit)
    • Only a portion \((1-tbf_t)\) is added to the accrued bonus, while the rest ist stored in the Terminal Bonus Fund.
  • The existing cumulated profit \(G_{t-1}\) and the terminal bonus fund \(TBF_{t-1}\) yields interest with the guaranteed interest rate plus potentially the interest profit rate. If the total credited rate is lower than the guarantee, the guarantee is still applied to the existing profits. => PP.rate.interestProfitPlusGuarantee
  • Additionaly, interest profit is assigned with rate \(ip_t\) (=0 if total credited rate is below guarantee) multiplied with the average of the current and the previous reserve for the guaranteed part. => PP.base.meanContractualReserve
  • The risk profit works similar to advance profit participation, only that part of the (actual) risk premium of the year is returned to the customer after having paid it. => PP.base.ZillmerRiskPremium
  • Expense profit is based on the sum insured, since most cost structures are linear in the sum insured and contain certain loadings, which are returned to the customer via this profit component.
  • A sum profit of \(sp_t\) of the sum insured is added, even if no interest profit is distributed. The sum profit rate depends on the sum insured (as a function), since the charges expenses increase linearly in the sum insured while the actual cost do increase only sub-linear. So for higher sums typically more expenses are returned. => The sum profit rate will be a function rather than a single value each year.
  • Only a part \((1-tbf_t)\) of the profit assignment in year \(t\) is added to the accrued profits \(G_t\), while the rest \(tbf_t\) is stored in the terminal bonus fund \(TBF_t\), which is partially lost on surrender or premium waiver.

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:

  • In case of death, the existing cumulated profits yield one additional year of interest => PP.benefit.ProfitPlusInterestMinGuaranteeTotal
  • At maturity of the contract, the existing cumulated profits are paid out in addition to the guaranteed benefits of the contract. => PP.benefit.Profit
  • In case of surrender (on average half a year after the contract’s anniversary), half a year of interest is added to the existing cumulated profits from the last anniversary => PP.benefit.ProfitPlusHalfInterestMinGuaranteeTotal
  • When premiums are waived, the existing accrued profits are taken into account without any additional interest. => PP.benefit.Profit
  • The terminal bonus fund is paid out fully on death and at maturity, while half of the TBF is lost on surrender or premium waiver.

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:

ProfitScheme.example = ProfitParticipation$new(
  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.

12.2.3 Using the profit scheme in a tariff or contract

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:

contract.Endow.PP = InsuranceContract$new(
  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.PP$
  addProfitScenario(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:

contract.Endow.PP$Values$profitScenarios$`Current total credited rate` %>%
  as.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:

contract.Endow.PP$Values$profitScenarios$`Current total credited rate` %>%
  as.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:

contract.Endow.PP$Values$profitScenarios$`Current total credited rate` %>%
  as.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:

contract.Endow.PP$Values$profitScenarios$`Current total credited rate` %>%
  as.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.PP$
  addProfitScenario(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)))

contract.Endow.PP$Values$profitScenarios$`decreasing TCR` %>%
  as.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.

13 Modifying the default calculation approach

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.

contract.Endow.Waiting = InsuranceContract$new(
  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 }
)

contract.Endow.Waiting$Values$cashFlows[,c("premiums_advance", "survival_advance", "death_SumInsured")] %>% pander
  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.

  costs = initializeCosts(alpha = 0.04, Zillmer = 0.035, gamma = 0.0015, gamma.fullcontract = 0.001),
  adjustPremiumCoefficients = function(coeff, type, premiums, params, values, premiumCalculationTime) {
    if (type == "Zillmer") {
      coeff[["SumInsured"]][["costs"]]["gamma", "SumInsured", "guaranteed"] = 1
    }
    coeff
  },

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.