Git Product home page Git Product logo

lifecontingencies.jl's Introduction

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

lifecontingencies.jl's People

Contributors

alecloudenback avatar dimitarvanguelov avatar github-actions[bot] avatar staticfloat avatar tkelman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

lifecontingencies.jl's Issues

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

Interest rate

Pull interest rate functionality out of this package and utilize Yields.jl

change API for discount rates

v(int,1:10) returning the discount vector for times 1:10 seems a lot more useful than returning the 1-year discount rate at each of those time points. Makes #20 easier to refactor

survival vector on `life`?

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

yield = Yields.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

Any reason to not extend survival() to work on life? Currently only has method for ins. Similar for other functions, e.g. survival(life,30,35)

Allow Fractional Age Everywhere

Hello guys,
Nice project, here!
However, it'd be nice if we had fractional ages allowance by default.
For example, allowing fractional ages directly when instanciating SingleLife
objects knowing the fractional_assump... this implies changing
the way a couple of methods are implemented but it's relatively a light!

Thanks.

Error reproducing README example

Trying to create the plots toward the bottom of the README produces the following error:

julia> for (i, t) in enumerate(tables)
           plot!(plt, issue_ages, whole_life_costs[i], label="$(t.d.name)")
       end
ERROR: type UltimateTable has no field d
Stacktrace:
 [1] getproperty(x::MortalityTables.UltimateTable{OffsetArrays.OffsetVector{Float64, Vector{Float64}}}, f::Symbol)
   @ Base ./Base.jl:42
 [2] top-level scope
   @ ./REPL[362]:2

I tried looking for a way to extract a table name (or other field) from a table object, but it was not obvious or intuitive. fieldnames was not helpful here. Needless to say, this underscores two issues:

  1. the example in the documentation is currently not reproducible
  2. it's not clear (or easy) how to extract fields and/or metadata from table objects

API definitions - intervals vs n-payments

In trying to extend the built-in functions to include immediate annuities, I have encountered an area that highlights an ill-defined aspect of the API:

Currently, ä(ins,5) (ä(ins,to_time)) refers to the annuity-due payments that occur between times [0,5), so that includes five payments.

In extending this to an annuity certain, a(ins,5) referring to the interval [0,5) would only receive four payments at times 1,2,3,4.

This is inconsistent with how the literature portrays a(ins,5) as the 5 refers not to time but number of payments.

Question is: change the API to be consistent with the literature?

API Refactor

Refactor to make the types of insurances, well, types instead of functions. Example:

Current:

annuity_due(ins) currently calculates the present value of an annuity due.

Alternative:

a = AnnuityDue(ins) constructs an object, for which the following functions would operate:

  • cashflows(a) would produce the vector of cashflows
  • survival(a) would produce the survivorship vector
  • timepoints(a) would produce the timepoints associated with the cashflows
  • discount(a) would produce the discount vector
  • Replicating the current functionality, present_value(a) would extend ActuaryUtilities.present_value and calculate the APV via the present_value(ins.interest,cashflows(a) .* survival(a),timepoints(a))
    • Could specialize this function to the existing calculation if the duplication causes material speed slowdown

Advantages of this:

  • allows for breaking the constituent pieces apart
    • e.g. facilitates calculating the liability duration via the cashflow analysis methods in ActuaryUtilities
  • the insurances are now combine-able:
    • e.g. Insurance(ins) - AnnuityDue(ins) would create a new instance of a ContingencyCombination where it combines the insurance benefit less the annuity payments, resulting in the net product cashflow.

Disadvantages:

  • potentially duplicating calculations? E.g. the timepoints may need to be calculated three times for an APV instead of just once.

Namespace pollution

There's a lot of very short characters exported by the module, which pollutes the user's namespace. Some solutions:

  1. Don't export anything and require explicit import
  2. alias some of the basic functions e.g. insurance for A and export the aliases
  3. Keep as is

I'm leaning towards option 2 as the best use case for most people.

API design - function names

Since, for example, Ax has x as an argument to the function, should the functions really be defined as A(lc,x) instead of the current Ax(lc,x)?

For some functions like reserve which is commonly denoted tVx in the literature, it creates odd function calls:

  • Current: tVx(lc,x,t)
  • Possible shortform alternative: V(lc,x,t)
  • Possible longform alternative: net_premium_reserve(lc,x,t)

If switching to the less redundant functions, then namespace becomes an issue. For example, if there's a bunch of one letter variables (v,a,A,V, etc) then how to deal with this?

Possible solution:

  • Define longform names that do get exported, such as annual_net_premium instead of Px
  • Define the shortform (e.g. P) but don't export, and let the user pick if they want to bring the shortform versions into scope

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.