knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(knitr) library(kableExtra) library(LifeInsuranceContracts) library(dplyr) library(tibble) library(lubridate) options(scipen=5) library(pander) panderOptions('round', 2) panderOptions('digits', 12) panderOptions('keep.trailing.zeros', TRUE) panderOptions('table.split.table', 120) kableTable = function(grd, ...) { grd %>% kable(...) %>% add_header_above(header = c(1, dim(grd)[[2]]) %>% `names<-`(names(dimnames(grd))), align = "c") %>% kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), full_width = F) %>% column_spec(1, bold = T, border_right = T) }
The LifeInsuranceContracts 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 LifeInsuranceContracts package to provide the most flexible contract modelling framework in R.
An insurance contract is described by three different objects;
InsuranceContract
: The object describing the actual contract with all
contract-specific parameters (age, maturity, sum insured, etc.).InsuranceTarif
: The general (contract-independent) description of the
insurance product (benefits, present values / reserves, premium calculation,
premium waivers, surrender value, reserve calculation, premium decomposition).
It does not store any values (only tarif-provided default values for the
insurance contract), but provides all calculation functions. The implementation
is based on a general approach using cash flows (depending on the type of
insurance). Once the cash flows (survival, death and guaranteed) are defined
for all time steps, all further calculations are idential for all different
kinds of life insurance.ProfitParticipation
: For contracts with profit sharing mechanisms, this
object describes the profit participation (like the InsuranceTarif
object
describes the guaranteed payments) and us used by the InsuranceContract
object.The tariff and the profit scheme are purely descriptional and their creation does not trigger any calculations. However, when one creates a contract for a given tariff, the following steps are done to calculate all values relevant for the contract:
All steps after the cash flow setup are implemented in a very generic way, as the cash flows fully determine an insurance contract and as soon as the cash flows are fixed, the valuation and reserve calculation can be expressed in terms of expected present values of the cash flows only.
While this might at first seem a bit complicated, it allows for very generic implementations of life insurance products and contracts.
To understand how the package implements life insurance contracts, let us look at a simple example:
Term Life Insurance
Costs:
Surrender Value:
library(magrittr) library(MortalityTables) library(LifeInsuranceContracts) 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 } );
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
contract.L71U$Values$premiums %>% kable
contract.L71U$Values$reserves
contract.L71U$Values$reserves %>% pander()
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 LifeInsuranceContracts::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
contract.L71U$Values$cashFlows %>% select(starts_with('premiums'), starts_with('death'), -death_Refund_past ) %>% pander()
contract.L71U$Values$cashFlowsCosts[,,,"survival"]
for (base in dimnames(contract.L71U$Values$cashFlowsCosts)[[3]]) { cat("* ,,\"", base, "\"\n", sep = "") cat(contract.L71U$Values$cashFlowsCosts[,,base, "survival"] %>% pander(round = 4, style = "rmarkdown")) }
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
contract.L71U$Values$presentValues %>% as.data.frame() %>% select(starts_with('premiums'), starts_with('death'), -death_Refund_past ) %>% pander(round=5)
contract.L71U$Values$presentValuesCosts
for (base in dimnames(contract.L71U$Values$presentValuesCosts)[[3]]) { cat("* ,,\"", base, "\"\n", sep = "") cat(contract.L71U$Values$presentValuesCosts[,,base,"survival" ] %>% pander(round = 4, style = "rmarkdown")) }
contract.L71U$Values$premiums
contract.L71U$Values$premiums %>% data.frame() %>% pander()
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
contract.L71U$Values$reserves %>% pander(digits=2)
Also, the premium composition into costs, risk premium, savings premium and other components is possible.
contract.L71U$Values$premiumComposition
contract.L71U$Values$premiumComposition %>% as.data.frame() %>% select(-loading.frequency, -rebate.premium, -rebate.partner, -profit.advance, -rebate.sum, -charge.noMedicalExam, -premium.risk.actual, -premium.risk.security, -risk.disease, -premium.risk.disease.actual, -premium.risk.disease.security, -starts_with('Zillmer')) %>% pander()
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 = contract.L71U$premiumWaiver(t = 3) contract.L71U$Values$reserves
contract.L71U = contract.L71U$premiumWaiver(t = 3) contract.L71U$Values$reserves %>% pander()
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
grd %>% kableTable(digits = 2)
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
for (d in dimnames(grd)[[3]]) { cat("\n", "* , , ", names(dimnames(grd))[[3]], "=", d, "\n\n", sep = "") # cat(grd[,,d ] %>% as.data.frame() %>% rownames_to_column("age \\| policyPeriod") %>% pander(digits = 7, round = 2, style = "rmarkdown")) cat(grd[,,d ] %>% kableTable(digits = 2), "\n") }
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
grd %>% pander(round=1, digits=15, keep.trailing.zeros = T)
All possible parameters of an insurance contract are stored in sub-lists of a a structure
InsuranceContract.Parameters
. If not provided by the call to InsuranceContract$new()
,
the values will be taken from either the InsuranceTariff
's default parameters,
the ProfitParticipation
's default parameters or the global defaults in the
InsuranceContract.ParameterDefault
. The cascade or parameters is (from top to
bottom):
InsuranceContract$addProfitScenario()
(applies
only for the added profit scenario)InsuranceContract$new()
or InsuranceContract$clone()
ProfitParticipation
InsuranceTarif
InsuranceContract.ParameterDefaults
In addition to the parameters listed below, the InsuranceContract$new()
constructor function takes the following parameters
\define{
\item{tarif}{The InsuranceTarif
object describing the tarif}
\item{parent}{For contracts with multiple parts, children get passed a pointer to the parent}
\item{calculate}{How much of the contract to calculate (by default everything will be calculated)}
\item{profitid}{The profit ID for contracts with profit participation}
}
str(InsuranceContract.ParameterDefaults)
# pandoc.listRK(InsuranceContract.ParameterDefaults)
An insurance contract is modelled by the abstract product specification
(InsuranceTarif
class) and the concrete (individualized) InsuranceContract
.
InsuranceTarif
object describes the product in abstract terms, holds
default parameters and provides all calculation functions for cash flows,
present values, premiums and reserves (provided the contract's actual Parameters).
It does not, however, hold contract-specific data.InsuranceContract
object holds the individual contract data (like age, contract
closing date, sum insured, etc.) that override the tariff's defaults. It also
holds a pointer to the insurance tariff and provides the general logic to
calculate all parts of an insurance contract by calling the corresponding
functions of the tariff.The insurance contract and the underlying insurance tariff have the same possible
parameters in its constructors: The InsuranceTarif
uses them to define defaults
for contracts that use the tariff, while the parameters passed to the contract
either provide the individually required data like age, sum insured or maturity,
or they can override the defaults provided by the tariff. In theory, one could
even create a contract with an empty underlying tariff and provide all tariff-specific
parameters also in the contract's new
call.
The InsuranceTarif
class provides a way to define an abstract insurance product.
The most important parameters to be passed in the InsuranceTarif$new()
call are:
| | |
|:-----|:----------|
|General settings for the Tariff ||
| name
, tarif
, desc
| IDs and human-readable descriptions of the insurance product. They are just used as labels and array keys, but do not influence the calculations.|
| type
| the most important parameter, defining the type of life insurance product (endowment, pure endowment, annuity, whole life insurance, dread-disease insurance, etc.)|
|Actuarial Bases for the Tariff ||
| mortalityTable
| a MortalityTable
Object (package "MortalityTables"), providing the transition probabilities (typically death probabilities, but potentially also morbidity in dread-disease insurances)|
| i
| Guaranteed interest rate|
| costs
, unitcosts
| passed a data structure for all cost parameters (see below)|
| premiumFrequencyLoading
| surcharge for premium payments more often than yearly (as a named list)|
| premiumRefund
| how much of the (gross) premium paid is returned upon death (often provided e.g. in deferred annuities or pure endowments with no fixed death benefit)|
| tax
|insurance tax
|Benefit calculation ||
| surrenderValueCalculation
| can be passed a hook function that calculates the surrender value given the current reserves at each time step|
A typical call looks like the following for a pure endowment with gross premium refund upon death and a linearly decreasing surrender penalty:
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 )
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 )
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
contract.PureEnd$Values$premiums %>% kable(digits=4)
contract.PureEnd$Values$premiumComposition
contract.PureEnd$Values$premiumComposition %>% as.data.frame() %>% rowid_to_column("t") %>% mutate(t = t-1) %>% select(t, charged, tax, loading.frequency, gross, gamma, beta, alpha, alpha.noZillmer, alpha.Zillmer, Zillmer, net, risk, savings) %>% pander
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) ```r cbind(`With refund` = contract.PureEnd$Values$premiums, `Without refund` = contract.PureEnd.NoRefund$Values$premiums) %>% pander
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"] ) ```r 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"] ) %>% as_tibble() %>% rowid_to_column("t") %>% mutate(t = t-1) %>% pander
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)
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
contract.PureEnd.NoRefund.Prf$Values$reserves %>% pander
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.
The calculation of all contract values is controlled by the function
InsuranceContract$calculateContract()
(using methods of the InsuranceTarif
object) and follows the following logic:
InsuranceContract$profitScenario()
or InsuranceContract$addProfitScenario()
.An insurance contract is basically defined by the (unit) cash flows it produces: \itemize{ \item Premium payments (in advance or in arrears) at each timestep \item Survival payments at each timestep \item Guaranteed payments at each timestep \item Death benefits at each timestep \item Disease benefits at each timestep } Together with the transition probabilities (mortalityTable parameter) the present values can be calculated, from which the premiums follow and finally the reserves and a potential profit sharing.
For example, a term life insurance with regular premiums would have the following cash flows:
A single-premium term life insurance would look similar, except for the premiums:
A pure endowment has no death benefits, but a survival benefit of 1 at the maturity of the contract:
An endowment has also death benefits during the contract duration:
A (deferred) annuityB has premium cash flows only during the deferral peroid and only survival cash flows during the annuity payment phase. Often, in case of death during the deferral period, all premiums paid are refunded as a death benefit.:
A terme-fix insurance has a guaranteed payment at maturity, even if the insured has already died. The premiums, however, are only paid until death (which is not reflected in the contingent cash flows, but rather in the transition probabilities):
Costs of an insurance contracts can have various forms and bases.
The InsuranceContract
class provides all common types of costs:
The cost structure generated by initializeCosts()
is a three-dimensional array
with the above-mentioned coordinates:
initializeCosts() %>% dimnames
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
contract.PureEnd.NoRefund$Values$absCashFlows[1:11,] %>% select(alpha, Zillmer, beta, gamma, gamma_nopremiums, unitcosts) %>% pander()
In addition to these standardized costs, which are included in the expense-loaded premium (sometimes also called the "(actuarial) gross premium"), there are certain loadings and rebates that are applied after the expense-loaded premium $BP_x$:
$$P_x = \left{ (BP_x + oUZu - SuRa) \cdot VS \cdot (1-VwGew) + unitC\right} \cdot \left(1-PrRa-VwGew_{StkK}-PartnerRa\right)\cdot \left(1+uz(k)\right) \cdot (1+tax)$$
with the following surcharges and rebates:
noMedicalExam
)sumRebate
)advanceProfitParticipation
)unitcosts
)premiumRebate
)advanceProfitParticipationInclUnitCost
)partnerRebate
)premiumFrequencyLoading
)tax
)Typically, an insurance premium is calculated with yearly premiums paid in advance.
If premiums are paid more often per year (Parameter premiumFrequency
or
benefitFrequency
set to a value larger than 1, typically 2 for half-yearly,
4 for quarterly, or 12 for monthly payments), part of the interest during the year
is lost, as well as the premiums for the remainder of the year if the insured event
happens. So usually there is some kind of extra charge included:
premiumFrequencyLoading
and benefitFrequencyLoading
are set to a list with keys "1", "2", "4" and "12"
and values that correspond to the extra charge (as a factor on the gross premium,
i.e. a percentage).premiumFrequencyOrder
and benefitFrequencyOrder
set to a value larger than 0. costs
is implemented a function with signature $function(params, values)$,
which accesses params$ContractData$premiumFrequency
to return different expense
rates for different premium payment frequencies.getInterestProfitRate
of
the profit scheme can be implemented as a function that modifies the
rate depending on the premium or benefit frequency (similar to the cost adjustment
mentioned in the previous item).Tarif.Life.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
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)
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.
contractGrid()
creates a (two- or higher-dimensional) grid of InsuranceContract
objectcontractGridPremium()
creates a grid of the premiumsThe 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
grd %>% kableTable
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")
contractGridPremium(grd, premium = "net") %>% kableTable
# 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"]) })
# 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"]) }) %>% kableTable(digits = 2)
# Compare risk premiums at time t=10 (the 11th row of the premium decomposition) contractGridPremium(grd, .fun = function(c) {c$Values$premiumComposition[11, "risk"]})
# Compare risk premiums at time t=10 (the 11th row of the premium decomposition) contractGridPremium(grd, .fun = function(c) {c$Values$premiumComposition[11, "risk"]}) %>% kableTable(digits = 2)
# Compare present value of all benefits and refunds (without costs) at time t=0 contractGridPremium(grd, .fun = function(c) {c$Values$absPresentValues[1, "benefitsAndRefund"]})
# Compare present value of all benefits and refunds (without costs) at time t=0 contractGridPremium(grd, .fun = function(c) {c$Values$absPresentValues[1, "benefitsAndRefund"]}) %>% kableTable(digits = 2)
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
for (a in dimnames(grd)[[3]]) { for (d in dimnames(grd)[[4]]) { cat("\n", "* ", names(dimnames(grd))[[3]], "=", a, ", ", names(dimnames(grd))[[4]], "=", d, "\n\n", sep = "") # cat(grd[,,d ] %>% as.data.frame() %>% rownames_to_column("age \\| policyPeriod") %>% pander(digits = 7, round = 2, style = "rmarkdown")) cat(grd[,, a, d] %>% kableTable(digits = 2), "\n") } }
The LifeInsuranceContracts package also provides a function to export a given contract to a spreadsheet in Excel format:
exportInsuranceContract.xlsx(contract, filename)
This function takes the contract and exports all data.frames in the contract's
contract$Values
data list as separate tabs to a spreadsheet file. It also
adds some nice formatting and additional markup. For contracts with multiple
contract blocks (e.g. dynamic increases / sum increases or riders), the main
contract and all its child blocks are exported as well.
The tab containing the premiums and the premium calculation coefficients even include the formulas, so one can in theory play around with the parameters to see how the premium changes.
If the contract has profit participation features and some profit scenarios have been added, they are exported as well.
Notice, however, that the Excel export is in general a static file containing the current state of the contract and all its values (cash flows, present values, premiums, reserves, profit participation, etc.) as well as the most important parameters and an overview of the history of the contract.
contract.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")
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:
showVmGlgExamples(contract, prf = 10, t = 10, t_prf = 12, file = "", ...)
exportInsuranceContractExample(contract, prf = 10, outdir = ".", basename=NULL, extraname = NULL, ...)
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)
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.
While many insurance contracts have a fixed sum insured and constant premium, many contracts include some kind of adjustment to account for inflation. There are various ways to achieve such an adjustment:
The LifeInsuranceContract package provides functionality for each of these increases. All three increases can in theory be combined in the same contract, although in practice this usually does not happen and at most one kind of increase is included in a contract
With 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:
premiumIncrease
... The factor, by which the premium increases yearly. Default is 1.0 (no increase in premium).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
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:
annuityIncrease
... The factor, by which potential annuity payments increase yearly. Default is 1.0 (no increase in annuity benefits)deathBenefit
... The vector of death benefits (relative to the sum insured, which for endowments describes the survival benefit)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
premium.comparison %>% pander
For annuities, the benefit increase is not handled through deathBenefits
, but
rather through the parameter
annuityIncrease
... yearly increase factor of the annuity paymentsIn 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
With dynamic increases, the contract initially is written with a fixed sum insured and constant premiums over the whole contract period. The future increases are not considered at all.
After the initial contract inception, either yearly or when a consumer price index changes by a value larger than a given threshold, the sum insured is increased (either by a fixed amount or by an amount determined by the index change) and the premium is adjusted accordingly. Internally, the original contract is left untouched and the increase is modelled by a separate contract with the same key parameters, only with shorter duration and a sum insured that represents only the increase. The premium for this increase is calculated like a separate contract with only the difference in the over-all sum insured as its sum insured.
Each dynamic increase then adds another separate tiny InsuranceContract object and the over-all values are the sums of all those contract blocks (sometimes also called "contract slices").
The InsuranceContract
class provides a method to add a dynamic increase:
InsuranceContract$addDynamics(t, NewSumInsured, SumInsuredDelta, id, ...)
Only one of NewSumInsured
(new total sum insured) and SumInsuredDelta
(only
the difference between old and new sum insured) is needed. This method adds a
new contract block to the given InsuranceContract, starting at time $t$ with
SumInsuredDelta
as its sum insured and its premium calculated from the
shorter contract period and the sum insured delta. These blocks for dynamic increases
are stored in the contract's $blocks
list of children. The values stored in the
contract are then simply the sum of all its children.
Here is an example of a 10-year endowment, which has dynamic increases at times $t=5$, $t=7$ and $t=8$:
# For comparison: Contract with constant annuity 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
blk = c(list(`Over-all contract` = contract.Endowment.Dynamics), contract.Endowment.Dynamics$blocks) padArray = function(arr = NULL, pad = 0, len = 0) { padEnd = max(0, len - pad - NROW(arr)) # if len is too short, return an array containing at least the arr nrcols = ifelse(is.null(arr), 0, NCOL(arr)) rbind( array(0, dim = c(pad, nrcols)) %>% `colnames<-`(colnames(arr)), arr, array(0, dim = c(padEnd, nrcols)) %>% `colnames<-`(colnames(arr)) ) %>% `colnames<-`(colnames(arr)) } lapply(blk, function(b) { basic = padArray(b$Values$basicData, pad = b$Parameters$ContractData$blockStart) basic[,"SumInsured"] }) %>% bind_cols() %>% rowid_to_column("t") %>% mutate(t = t-1) %>% pander(caption = "Sum Insured for the over-all contract and each of the blocks") lapply(blk, function(b) { basic = padArray(b$Values$basicData, pad = b$Parameters$ContractData$blockStart) basic[,"Premiums"] }) %>% bind_cols() %>% rowid_to_column("t") %>% mutate(t = t-1) %>% pander(caption = "Premium time series for the over-all contract and each of the blocks")
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:
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
contract.LifePP$Values$premiumComposition[,c("charged", "tax", "unitcosts", "profit.advance", "gross", "net")] %>% as.data.frame() %>% rowid_to_column("t") %>% mutate(t = t-1) %>% pander
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:
Function signature: function(rates, ...)
* Profit base: The quantity on which to apply the rate. Typically this function
returns either the current reserve, the previous reserve (or some combination),
the sum insured or the current risk premium.
Function signature: function(rates, params, values, ...)
* Calculation: A function taking the rate and the base and calculate the
profit assigned for the specific profit component. Most common are a simple
multiplication of base and rate, but other formulas are possible, too.
Function signature: function(base, rate, waiting, rates, params, values, ...)
Thus, the constructor of the ProfitParticipation
class also takes the following
parameters:
|Type of profit |Function for rate | Function for base | Function for calculation |
|:-------------------|:--------------------------|:--------------------------|:----------------------------|
|interest on accrued profit |getInterestOnProfits
|- (existing profit) |- |
|interest profit |getInterestProfitRate
|getInterestProfitBase
|calculateInterestProfit
|
|risk profit |getRiskProfitRate
|getRiskProfitBase
|calculateRiskProfit
|
|expense profit |getExpenseProfitRate
|getExpenseProfitBase
|calculateExpenseProfit
|
|sum profit |getSumProfitRate
|getSumProfitBase
|calculateSumProfit
|
|terminal bonus |getTerminalBonusRate
|getTerminalBonusBase
|calculateTerminalBonus
|
|terminal bonus fund |getTerminalBonusFundRate
|getTerminalBonusFundBase
|calculateTerminalBonusFund
|
In addition, the following parameters define functions for reserves:
getTerminalBonusReserve
... Calculate the reserve for the terminal bonus
from the bonus assignments (old tariffs often use some kind of discounting or
conditional reserving for the terminal bonus reserve)Function signature: function(profits, rates, terminalBonus, terminalBonusAccount, params, values)
To calculate the actual benefits paid out from profit participation, the following
parameters take the corresponding functions (signature: function(profits, rates, params, values, ...)
)
| | |
|:---|:---------|
|calculateSurvivalBenefit
|Benefit from profit participation at maturity (in addition to the guaranteed payout) |
|calculateDeathBenefitAccrued
|Benefit from profit participation upon death (in addition to the guaranteed payout) |
|calculateDeathBenefitTerminal
|Benefit from terminal bonus upon death (in addition to the guaranteed payout and regular profit participation) |
|calculateSurrenderBenefitAccrued
|Benefit from profit participation upon contract surrender (in addition to the surrender value) |
|calculateSurrenderBenefitTerminal
|Benefit from terminal bonus upon contract surrender (in addition to the surrender value and regular profit participation) |
|calculatePremiumWaiverBenefitAccrued
|Benefit from profit participation upon premium waiver (in addition to the surrender value) |
|calculatePremiumWaiverBenefitTerminal
|Benefit from terminal bonus upon premium waiver surrender (in addition to the surrender value and regular profit participation) |
While the details of a profit participation scheme are very specific and no two
profit schemes are exactly alike, the basic functionality to extract rates and
bases and the calculation functions are usually not so different. For this
reason, the LifeInsuranceContracts
package provides several little helper functions
that provide the most common functionality for the definition of rates, bases and
the profit calculation. See ?ProfitParticipationFunctions
for the full list.
The most common functions are:
PP.base.PreviousZillmerReserve(rates, params, values, ...)
PP.base.contractualReserve(rates, params, values, ...)
PP.base.previousContractualReserve(rates, params, values, ...)
PP.base.meanContractualReserve(rates, params, values, ...)
PP.base.ZillmerRiskPremium(rates, params, values, ...)
PP.base.sumInsured(rates, params, values, ...)
PP.base.totalProfitAssignment(res, ...)
PP.rate.interestProfit(rates, ...)
PP.rate.riskProfit(rates, ...)
PP.rate.expenseProfit(rates, ...)
PP.rate.sumProfit(rates, ...)
PP.rate.terminalBonus(rates, ...)
PP.rate.terminalBonusFund(rates, ...)
PP.rate.interestProfitPlusGuarantee(rates, ...)
PP.rate.totalInterest(rates, ...)
PP.calculate.RateOnBase(base, rate, waiting, rates, params, values, ...)
PP.calculate.RateOnBaseMin0(base, rate, waiting, rates, params, values, ...)
PP.calculate.RatePlusGuaranteeOnBase(base, rate, waiting, rates, params, values, ...)
PP.benefit.ProfitPlusTerminalBonusReserve(profits, ...)
PP.benefit.Profit(profits, ...)
PP.benefit.ProfitPlusGuaranteedInterest(profits, rates, ...)
PP.benefit.ProfitPlusTotalInterest(profits, rates, params, values)
PP.benefit.ProfitPlusHalfTotalInterest(profits, ...)
PP.benefit.ProfitPlusInterestMinGuaranteeTotal(profits, rates, ...)
PP.benefit.TerminalBonus5YearsProRata(profits, params, ...)
PP.benefit.TerminalBonus5Years(profits, params, ...)
PP.benefit.TerminalBonus(profits, params, ...)
For example, imagine a tariff's total cumulated assigned profit $G_t$ and the benefits at time $t$ have the formulas: $$Prof_t = \left(G_{t-1} + TBF_{t-1}\right) \cdot \left(1 + i + ip_t\right) + ip_t \cdot \frac{\left(Res_{t-1} + Res_{t}\right)}{2} + rp_t \cdot P^r_t + ep_t \cdot SumInsured + sp_t(SI) \cdot SumInsured$$ $$G_t = G_{t-1} + (1 - tbf_t) \cdot Prof_t$$ $$TBF_t = TBF_{t-1} + tbf_t \cdot Prof_t$$ $$Death_t = G_t \cdot \left(1 + i + ip_t\right) + TBF_t$$ $$Matu_n = G_n + TBF_n$$ $$Surrender_t = G_t\cdot \left(1+\frac{i + ip_t}{2}\right) + 0.5 \cdot TBF_t$$ $$Red_t = G_t + 0.5 \cdot TBF_t$$
These formulas can be interpreted as following:
PP.rate.interestProfitPlusGuarantee
PP.base.meanContractualReserve
PP.base.ZillmerRiskPremium
The values of $Res_t$, $P^r_t$, $SumInsured$ and the guaranteed interest $i^g_t$ are prescribed by the tariff or contract, while the profit participation rates $ip_t$, $rp_t$, $ep_t$ and $sp_t$ are decided on a year-by-year basis by the management boards.
The benefits for death, maturity, surrender and premium waivers are:
PP.benefit.ProfitPlusInterestMinGuaranteeTotal
PP.benefit.Profit
PP.benefit.ProfitPlusHalfInterestMinGuaranteeTotal
PP.benefit.Profit
This profit scheme can be easily be implemented as a ProfitParticipation
object, where one can pass the functions for bases and calculation and also
provide default profit rates:
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.
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()
The base for interest profit is the average of the reserve:
contract.Endow.PP$Values$reserves %>% as.data.frame() %>% rownames_to_column("t") %>% select(t, SumInsured, Zillmer) %>% mutate(AvgZillmer = rollingmean(c(0,Zillmer))) %>% pander()
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
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
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
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
In the Excel export, a separate tab of the Excel file will hold all profit scenarios added to the contract.
While the cash-flow approach described above and based on the type
parameter of the
InsuranceTarif
works very well for all standart types of life insurance, sometimes
a tariff does not follow the standard behaviour exactly. The valuation approach
with all-determining cash flows is still correct, but the cash flows might need
to be adjusted. For this, some hook functions are provided that allow modification
of the contract's internals (e.g. cash flows) before all other calculations commence.
Two hooks are provided, which allow modifications of the cash flow profiles before present values, premiums and reserves are calculated:
| | |
|:---|:-----|
|adjustCashFlows
| Adjust the premium and benefit cash flows of the contract |
|adjustCashFlowsCosts
| Adjust the cost cash flows |
|adjustPremiumCoefficients
| Adjust the coefficients of the premium calculation formulas |
The function signature is function(x, params, values, ...)
, where x
is the
object holding the standard cash flows determined for the contract. The return
value of the hook function will be used instead of x
.
The \code{adjustPremiumCoefficients} hook has a slightly extended function signature \code{function(coeff, type, premiums, params, values, premiumCalculationTime)}.
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 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"))
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 },
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.