In this brief document we present a general function to valuate actuarial liabilities and normal costs, allowing different methods in pension funding for defined benefit schemes.
Each method is defined by the way that contributions are accrued in the active age before the specific risk apply. Following (Winklevoss 1993) and (Bowers et al. 1997), the Normal Cost and Actuarial Liability can be seen as portions of \(PVFNC^{r}_x\), the present value of future benefit at age \(x\) for the risk in \(r\), and that’s the way that we treat this in the function’s code. The methods that are implemented here are the “Benefit Prorate Method, Constant Dollar”, “Benefit Prorate Method, Constant Percent”, “Cost Prorate Method, Constant Dollar” and “Cost Prorate Method, Constant Benefit” (the last two also called “entry age methods”). For formula details the reader can go to chapters 5 and 6 on (Winklevoss 1993).
We define a general function that allows to perform the calculations in a multi-decrement environment, with flexibility to consider different kind of benefit scheme, using functions of the “lifecontingencies” package (Spedicato et. al., 2018). The arguments (following general notation in Gian Paolo Clemente’s vignette titled “Pension Funding with lifecontingencies”) are:
The general function is:
require("lifecontingencies")
<- function(x, y, r, acttableAccPeriod, decrement, i, j, delta,
PensFund
n, avg, acttablePaymPeriod, CostMet) {if (missing(n))
<- getOmega(acttableAccPeriod) - x - 1
n if (x < y)
stop("Entry age greater than actual age")
if (missing(decrement))
stop("Which is the contingency of benefit?")
if (class(acttableAccPeriod) != "mdt")
stop("Error! Needed Mdt")
if (!(decrement %in% getDecrements(acttableAccPeriod)))
stop("Error! Not recognized decrement type")
if (missing(acttableAccPeriod))
stop("Error! Need a lifetable or actuarialtable")
if (missing(x) | missing(y))
stop("Error! Need age and seniority!")
if (x > getOmega(acttableAccPeriod)) {
= 0
out return(out)
}if (missing(CostMet))
= "BPM_CD"
CostMet if (missing(avg))
stop("Error! Need avg period of computing benefit")
if (missing(j))
stop("Error! Need average salary increase rate")
if (any(x < 0, y < 0, avg < 0, delta < 0))
stop("Error! Negative parameters")
########## survive all casuses
= pxt(acttableAccPeriod, x = 0:(getOmega(acttableAccPeriod)),
acttableAccPeriodTotal t = 1)
= probs2lifetable(acttableAccPeriodTotal, type = "px")
acttableAccPeriodTotal if (missing(acttablePaymPeriod))
= acttableAccPeriodTotal
acttablePaymPeriod
########## Set benefit fix age valuation
= x
x0 = ifelse(avg == 0, 1, (1 + j)^(r - x0) * annuity(j, avg, m = 0, k = 1,
B type = "immediate")/avg)
= qxt(object = acttableAccPeriod, x = r, t = 1, decrement = decrement)
q.r = q.r * axn(acttablePaymPeriod, r, i = (1 + i)/(1 + delta) - 1,
Br n = n, k = 1) * B
########## Actuarial liability in age x, fot the age r (r>=x)
= function(x, r) {
ALyNC.x.r
## PVFB
= Exn(acttableAccPeriodTotal, x, r - x, i = i) * Br
PVFB.x
## Normal Costs and liabilities under Actuarial Cost Methods
if (CostMet == "BPM_CD") {
<- (x - y)/(r - y)
K <- 1/(r - y)
k
}
if (CostMet == "BPM_CP") {
<- (1 + j)^(x - x0) * annuity(j, x - y + 1, m = 0, k = 1,
K type = "immediate")/((1 + j)^(r - x0) * annuity(j, r -
+ 1, m = 0, k = 1, type = "immediate"))
y <- (1 + j)^(x - x0)/(annuity(j, r - y, m = 0, k = 1, type = "immediate") *
k 1 + j)^(r - x0))
(
}
if (CostMet == "CPM_CD") {
<- axn(acttableAccPeriodTotal, y, i = i, n = x - y, k = 1)/axn(acttableAccPeriodTotal,
K i = i, n = r - y, k = 1)
y, <- Exn(acttableAccPeriodTotal, y, x - y, i = i)/axn(acttableAccPeriodTotal,
k i = i, n = r - y, k = 1)
y,
}
if (CostMet == "CPM_CB") {
<- axn(acttableAccPeriodTotal, y, i = (1 + i)/(1 + j) - 1,
K n = x - y, k = 1)/axn(acttableAccPeriodTotal, y, i = (1 +
/(1 + j) - 1, n = r - y, k = 1)
i)<- Exn(acttableAccPeriodTotal, y, x - y, i = i) * (1 + j)^(x -
k /axn(acttableAccPeriodTotal, y, i = (1 + i)/(1 + j) -
y)1, n = r - y, k = 1)
}
## Results
<- PVFB.x * K
outAL.x.r <- PVFB.x * k
outNC.x.r <- PVFB.x
outPVFB.x <- c(outAL.x.r, outNC.x.r, outPVFB.x)
out.x.r
return(out.x.r)
}
######## AL y NC for the rest of ages
= x:r
ages_risk = sapply(ages_risk, function(x) ALyNC.x.r(x, r)[1])
outAL.x = sapply(ages_risk, function(x) ALyNC.x.r(x, r)[2])
outNC.x = sapply(ages_risk, function(x) ALyNC.x.r(x, r)[3])
outPVFB.x length(ages_risk)] = NA
outNC.x[<- data.frame(x = ages_risk, AL = outAL.x, NC = outNC.x, PVFB = outPVFB.x)
out.x
return(out.x)
}
A first example of application is of a person aged 30 (entered to job at age 20), with a benefit in case of disability, that is the average of the last 5 salaries before get disabled. For calculate that we’ll use the multi-decrement table ‘SoAISTdata’ for the accrued period and ‘soa08’ for the payment period, both available in “lifecontingencies” package.
data(SoAISTdata)
data(soa08)
<- new("mdt", name = "testMDT", table = SoAISTdata) MultiDecrTable
## Added fictional decrement below last x and completed x and lx until zero....
The specific name of this risk in the table is ‘inability’.
getDecrements(MultiDecrTable)
## [1] "death" "withdrawal" "inability" "retirement"
Now we calculate the liability and normal cost with the four methods, and \(i = .04\), \(j = .06\), \(delta = .03\), \(avg = 5\). In this case for the age risk 53 (\(r = 53\)).
=30
x1=20
y1=53
r1= "inability"
decrement1 = PensFund(x1, y1, r1, acttableAccPeriod = MultiDecrTable, decrement = decrement1,
BPM_CD i = .04, j = .06, delta = .03, avg = 5,
acttablePaymPeriod = soa08, CostMet = 'BPM_CD')
= PensFund(x1, y1, r1, acttableAccPeriod = MultiDecrTable, decrement = decrement1,
BPM_CP i = .04, j = .06, delta = .03, avg = 5,
acttablePaymPeriod = soa08, CostMet = 'BPM_CP')
= PensFund(x1, y1, r1, acttableAccPeriod = MultiDecrTable, decrement = decrement1,
CPM_CD i = .04, j = .06, delta = .03, avg = 5,
acttablePaymPeriod = soa08, CostMet = 'CPM_CD')
= PensFund(x1, y1, r1, acttableAccPeriod = MultiDecrTable, decrement = decrement1,
CPM_CP i = .04, j = .06, delta = .03, avg = 5,
acttablePaymPeriod = soa08, CostMet = 'CPM_CB')
The way that each method amortize the benefit cost in active years can be seen in the next graph, showing the liability accumulated relationship between at any age: “CPM_CD” \(\geqslant\) “CPM_CB”\(\geqslant\) “BPM_CD” \(\geqslant\) “BPM_CP”.
We can check the results with actuarial equivalences, specifically the formulas \(AL^{r}_x = PVFB^{r}_x-PVFNC^{r}_x\) (6.3a) and \(AL^{r}_x = AVPNC^{r}_x\) (6.4a) in (Winklevoss 1993), which means that the actuarial liability at any time can be expressed as the difference between the present value of future benefit and the present value of future normal costs, and as the actuarial value of past normal costs (retrospective view).
# AL = PVBF - PVFNC first get survivial for all causes
= pxt(MultiDecrTable, x = 0:(getOmega(MultiDecrTable)),
acttableAccPeriodTotal t = 1)
= probs2lifetable(acttableAccPeriodTotal, type = "px")
acttableAccPeriodTotal # are equal?
round(BPM_CD$AL[1], 10) == round(BPM_CD$PVFB[1] - sum(sapply(x1:(r1 - 1),
function(x) Exn(acttableAccPeriodTotal, x1, x - x1, i = 0.04) * BPM_CD$NC[x -
+ 1])), 10) x1
## [1] TRUE
# Restrospective AL First get the liability at entry age y
= PensFund(y1, y1, r1, acttableAccPeriod = MultiDecrTable, decrement = decrement1,
BPM_CD_y i = 0.04, j = 0.06, delta = 0.03, avg = 5, acttablePaymPeriod = soa08,
CostMet = "BPM_CD")
# are equal?
round(BPM_CD_y$AL[BPM_CD_y$x == 45], 10) == round(sum(sapply(y1:44, function(y) (1.04)^(45 -
/pxt(acttableAccPeriodTotal, y, 45 - y) * BPM_CD_y$NC[BPM_CD_y$x ==
y)10) y])),
## [1] TRUE
Now we’ll evaluate a retirement pension, that receives a payment grade which is a lineal function between age 60 and the maximum possible age of retirement at 70, and goes from 50% to 100%. This is not a grading function in which benefit provided at early or late retirement has the same present value as the benefit payable at normal retirement ((Winklevoss 1993), chapter 9). Instead, here the person is penalyzed for early retirement. The insured has an annual salary of 5000 at age \(x\). Also there is a seniority condition of 10 years. The other parameters are the same that the previous example.
= 50
x2 = 35
y2 = "retirement"
decrement2 = data.frame(x = x2:70, gr = c(rep(0, 10), seq(0.5, 1, 0.05)))
pay_grade = 20
snty_req = 5000
s_x = "BPM_CD" Cost_Method2
The calculation now focus on estimate the actuarial values at age \(x2\), for all the retirement scheme (all ages between \(x2\) and 70).
= data.frame(r = 0, AL = 0, NC = 0)
AL_xr for (r in x2:70) {
if ((r - y2) < snty_req)
- x2 + 1, 2:3] = c(0, 0) else {
AL_xr[r - x2 + 1, 2:3] = s_x * pay_grade[pay_grade$x == r, 2] *
AL_xr[r PensFund(x2, y2, r, acttableAccPeriod = MultiDecrTable, decrement = decrement2,
i = 0.04, j = 0.06, delta = 0.03, avg = 5, acttablePaymPeriod = soa08,
CostMet = Cost_Method2)[1, 2:3]
}- x2 + 1, 1] = r
AL_xr[r }
The total liability at age x is 1.88917^{4} and is distributed between this retirement options, influenced by the progressive function of the early retirement pay_grade) and the individual’s expected behavior (probability of retirement).
Now let’s imagine that the same insured has the benefit of receive a 50% of his/her last salary at each age before retirement if he decide to leave the Company. The benefit is in concept of recognition of past contributions to the pension plan. This benefit can be estimated setting \(n=1\) (no rent, a single payment) and \(avg=0\) (only last salary). For example, for the risk of withdrawal at age 45:
= "withdrawal"
decrement3 = 30
x3 = 20
y3 = 45
r3 = s_x * 0.5 * PensFund(x3, y3, r3, acttableAccPeriod = MultiDecrTable,
BPM_CP3 decrement = decrement3, i = 0.04, j = 0.06, delta = 0.03, avg = 0,
n = 1, acttablePaymPeriod = soa08, CostMet = "BPM_CP")
The following improvements are planned: