Skip to content

JuliaActuary/LifeContingencies.jl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LifeContingencies.jl

Stable Dev codecov

LifeContingencies is a package enabling actuarial life contingent calculations.

Features

  • Integration with other JuliaActuary packages such as MortalityTables.jl
  • Fast calculations, with some parts utilizing parallel processing power automatically
  • Use functions that look more like the math you are used to (e.g. A, ) with Unicode support
  • All of the power, speed, convenience, tooling, and ecosystem of Julia
  • Flexible and modular modeling approach

Package Overview

  • Leverages MortalityTables.jl for the mortality calculations
  • Contains common insurance calculations such as:
    • Insurance(life,yield): Whole life
    • Insurance(life,yield,n): Term life for n years
    • ä(life,yield): present_value of Life contingent annuity
    • ä(life,yield): present_value of Life contingent annuity due for n years
  • Contains various commutation functions such as D(x),M(x),C(x), etc.
  • SingleLife and JointLife capable
  • Interest rate mechanics via Yields.jl
  • More documentation available by clicking the DOCS badges at the top of this README

Examples

Basic Functions

Calculate various items for a 30-year-old male nonsmoker using 2015 VBT base table and a 5% interest rate

using LifeContingencies
using MortalityTables
using FinanceModels
import LifeContingencies: V, ä     # pull the shortform notation into scope

# load mortality rates from MortalityTables.jl
vbt2001 = MortalityTables.table("2001 VBT Residual Standard Select and Ultimate - Male Nonsmoker, ANB")

issue_age = 30
life = SingleLife(                 # The life underlying the risk
    mortality = vbt2001.select[issue_age],    # -- Mortality rates
)

yield = FinanceModels.Yield.Constant(0.05)      # Using a flat 5% interest rate

lc = LifeContingency(life, yield)  # LifeContingency joins the risk with interest


ins = Insurance(lc)                # Whole Life insurance
ins = Insurance(life, yield)       # alternate way to construct

With the above life contingent data, we can calculate vectors of relevant information:

cashflows(ins)                     # A vector of the unit cashflows
timepoints(ins)                    # The timepoints associated with the cashflows
survival(ins)                      # The survival vector
survival(ins,time)                 # The survivorship through `time`
benefit(ins)                       # The unit benefit vector
probability(ins)                   # The probability of benefit payment
present_value(ins)                 # the present value of the insurance benefits from time zero
present_value(ins,time)            # the present value of the insurance benefits from `time`

Some of the above will return lazy results. For example, cashflows(ins) will return a Generator which can be efficiently used in most places you'd use a vector of cashflows (e.g. pv(...) or sum(...)) but has the advantage of being non-allocating (less memory used, faster computations). To get a computed vector instead of the generator, simply call collect(...) on the result: collect(cashflows(ins)).

Or calculate summary scalars:

present_value(ins)                 # The actuarial present value
premium_net(lc)                    # Net whole life premium 
V(lc,5)                            # Net premium reserve for whole life insurance at time 5

Other types of life contingent benefits:

Insurance(lc,10)                 # 10 year term insurance
AnnuityImmediate(lc)               # Whole life annuity due
AnnuityDue(lc)                     # Whole life annuity due
(lc)                              # Shortform notation
(lc, 5)                           # 5 year annuity due
(lc, 5, certain=5,frequency=4)    # 5 year annuity due, with 5 year certain payable 4x per year
...                                # and more!

Constructing Lives

SingleLife(vbt2001.select[50])                 # no keywords, just a mortality vector
SingleLife(vbt2001.select[50],issue_age = 60)  # select at 50, but now 60
SingleLife(vbt2001.select,issue_age = 50)      # use issue_age to pick the right select vector
SingleLife(mortality=vbt2001.select,issue_age = 50) # mort can also be a keyword

Net Premium for Term Policy with Stochastic rates

Use a stochastic interest rate calculation to price a term policy:

using LifeContingencies, MortalityTables
using Distributions

vbt2001 = MortalityTables.table("2001 VBT Residual Standard Select and Ultimate - Male Nonsmoker, ANB")

# use an interest rate that's normally distirbuted
μ = 0.05
σ = 0.01

years = 100
int =   Yields.Forward(rand(Normal(μ,σ), years))

life = SingleLife(mortality = vbt2001.select[30], issue_age = 30)

term = 10
LifeContingencies.A(lc, term) # around 0.055

Extending example to use autocorrelated interest rates

You can use autocorrelated interest rates - substitute the following in the prior example using the ability to self reference:

σ = 0.01
initial_rate = 0.05
vec = fill(initial_rate, years)

for i in 2:length(vec)
    vec[i] = rand(Normal(vec[i-1], σ))
end

int = Yields.Forward(vec)

Premium comparison across Mortality Tables

Compare the cost of annual premium, whole life insurance between multiple tables visually:

using LifeContingencies, MortalityTables, Plots

tables = [
    MortalityTables.table("1980 CET - Male Nonsmoker, ANB"),
    MortalityTables.table("2001 VBT Residual Standard Select and Ultimate - Male Nonsmoker, ANB"),
    MortalityTables.table("2015 VBT Male Non-Smoker RR100 ANB"),
    ]

issue_ages = 30:90
int = FinanceModels.Yield.Constant(0.05)

whole_life_costs = map(tables) do t
    map(issue_ages) do ia
        lc = LifeContingency(SingleLife(mortality = t.ultimate, issue_age = ia), int)
        premium_net(lc)

    end
end

plt = plot(ylabel="Annual Premium per unit", xlabel="Issue Age",
           legend=:topleft, legendfontsize=8,size=(800,600))

for (i,t) in enumerate(tables)
    plot!(plt,issue_ages,whole_life_costs[i], label="$(t.metadata.name)")
end

display(plt)

Comparison of three different mortality tables' effect on insurance cost

Joint Life

m1 = MortalityTables.table("1986-92 CIA – Male Smoker, ANB")
m2 = MortalityTables.table("1986-92 CIA – Female Nonsmoker, ANB")
l1 = SingleLife(mortality = m1.ultimate, issue_age = 40)
l2 = SingleLife(mortality = m2.ultimate, issue_age = 37)

jl = JointLife(lives=(l1, l2), contingency=LastSurvivor(), joint_assumption=Frasier())


Insurance(jl,FinanceModels.Yield.Constant(0.05))      # whole life insurance
...                                      # similar functions as shown in the first example above

Commutation and Unexported Function shorthand

Because it's so common to use certain variables in your own code, LifeContingencies avoids exporting certain variables/functions so that it doesn't collide with your own usage. For example, you may find yourself doing something like:

a = ...
b = ...
result = b - a

If you imported using LifeContingencies and the package exported a (annuity_immediate) then you could have problems if you tried to do the above. To avoid this, we only export long-form functions like annuity_immediate. To utilize the shorthand, you can include them into your code's scope like so:

using LifeContingencies # brings all the default functions into your scope
using LifeContingencies: a, ä # also brings the short-form annuity functions into scope

Or you can do the following:

using LifeContingencies # brings all the default functions into your scope
... # later on in the code
LifeContingencies.(...) # utilize the unexported function with the module name

For more on module scoping, see the Julia Manual section.

Actuarial notation shorthand

V => reserve_premium_net
v => discount
A => present value of Insurance
ä => present value of AnnuityDue
a => present value of AnnuityImmediate
P => premium_net
ω => omega

Commutation functions

l,
D,
M,
N,
C,

References