Skip to content

Commit

Permalink
Merge pull request #36 from JuliaActuary/Yields
Browse files Browse the repository at this point in the history
  • Loading branch information
alecloudenback authored Oct 5, 2020
2 parents cb836ce + bd9e451 commit 8b79969
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 245 deletions.
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
MortalityTables = "4780e19d-04b9-53dc-86c2-9e9aa59b5a12"
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
Transducers = "28d57a85-8fef-5791-bfe6-a80928e7c999"
Yields = "d7e99b2f-e7f3-4d9e-9f01-2338fc023ad3"

[compat]
IterTools = "1.3"
MortalityTables = "^0.12"
QuadGK = "^2.3"
Transducers = "^0.4"
Yields = "^0.2"
julia = "^1.3"

[extras]
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ the mortality calculations
- `ä(life,n)`: Life contingent annuity due for `n` years
- Contains various commutation functions such as `D(x)`,`M(x)`,`C(x)`, etc.
- `SingleLife` and `JointLife` capable
- Various interest rate mechanics (e.g. stochastic, constant, etc.)
- Interest rate mechanics via [`Yields.jl`](https://github.com/JuliaActuary/Yields.jl)
- More documentation available by clicking the DOCS badges at the top of this README

## Examples
Expand All @@ -36,7 +36,10 @@ the mortality calculations
Calculate various items for a 30-year-old male nonsmoker using 2015 VBT base table and a 5% interest rate

```julia
using LifeContingencies, MortalityTables

using LifeContingencies
using MortalityTables
using Yields

tbls = MortalityTables.tables()
vbt2001 = tbls["2001 VBT Residual Standard Select and Ultimate - Male Nonsmoker, ANB"]
Expand All @@ -48,7 +51,7 @@ life = SingleLife(

lc = LifeContingency(
life,
InterestRate(0.05)
Yields.Constant(0.05)
)


Expand Down Expand Up @@ -76,7 +79,7 @@ vbt2001 = tbls["2001 VBT Residual Standard Select and Ultimate - Male Nonsmoker,
σ = 0.01

years = 100
int = InterestRate(
int = Yields.Constant(
rand(
Normal(μ,σ),
years)
Expand Down Expand Up @@ -108,7 +111,7 @@ for i in 2:length(vec)
vec[i] = rand(Normal(vec[i-1],σ))
end

int = InterestRate(vec)
int = Yields.Forward(vec)
```

### Premium comparison across Mortality Tables
Expand All @@ -126,7 +129,7 @@ tables = [
]

issue_ages = 30:90
int = InterestRate(0.05)
int = Yields.Constant(0.05)

whole_life_costs = map(tables) do t
map(issue_ages) do ia
Expand Down
47 changes: 20 additions & 27 deletions src/LifeContingencies.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ using Transducers
using Dates
using IterTools
using QuadGK
using Yields

const mt = MortalityTables

Expand All @@ -25,7 +26,6 @@ export LifeContingency,
premium_net,
omega

include("interest.jl")



Expand Down Expand Up @@ -153,7 +153,7 @@ end
"""
struct LifeContingency
life::Life
int::InterestRate
int
end

Base.broadcastable(lc::LifeContingency) = Ref(lc)
Expand All @@ -180,7 +180,7 @@ The last period that the interest rate is defined for. Assumed to be infinite (`
function mt.omega(lc::LifeContingency)
# if one of the omegas is infinity, that's a Float so we need
# to narrow the type with Int
return Int(min(omega(lc.life), omega(lc.int)))
return Int(omega(lc.life))
end

function mt.omega(l::SingleLife)
Expand All @@ -191,13 +191,6 @@ function mt.omega(l::JointLife)
return minimum( omega.(l.lives) )
end

function mt.omega(i::ConstantInterestRate)
return Inf
end

function mt.omega(i::VectorInterestRate)
return lastindex(i.i)
end

###################
## COMMUTATIONS ###
Expand All @@ -209,7 +202,7 @@ end
``D_x`` is a retrospective actuarial commutation function which is the product of the survival and discount factor.
"""
function D(lc::LifeContingency, to_time)
return disc(lc.int, to_time) * survival(lc,to_time)
return discount(lc.int, to_time) * survival(lc,to_time)
end


Expand All @@ -228,7 +221,7 @@ end
``C_x`` is a retrospective actuarial commutation function which is the product of the discount factor and the difference in `l` (``l_x``).
"""
function C(lc::LifeContingency, to_time)
disc(lc.int, to_time+1) * (l(lc,to_time) - l(lc, to_time+1))
discount(lc.int, to_time+1) * (l(lc,to_time) - l(lc, to_time+1))

end

Expand All @@ -239,7 +232,7 @@ end
"""
function N(lc::LifeContingency, from_time)
range = from_time:(omega(lc)-1)
return reduce(+,Map(from_time->D(lc, from_time)), range)
return foldxt(+,Map(from_time->D(lc, from_time)), range)
end

"""
Expand All @@ -250,7 +243,7 @@ Issue age is based on the issue_age in the LifeContingency `lc`.
"""
function M(lc::LifeContingency, from_time)
range = from_time:omega(lc)-1
return reduce(+,Map(from_time->C(lc, from_time)), range)
return foldxt(+,Map(from_time->C(lc, from_time)), range)
end

E(lc::LifeContingency, t, x) = D(lc,x + t) / D(lc,x)
Expand All @@ -276,7 +269,7 @@ function insurance(::SingleLife,lc::LifeContingency,to_time)
iss_age = lc.life.issue_age
end_age = to_time + iss_age -1
len = end_age - iss_age
v = disc.(lc.int,1:len+1)
v = discount.(lc.int,1:len+1)
tpx = [survival(mt,iss_age,att_age, lc.life.fractional_assump) for att_age in iss_age:end_age]
qx = mt[iss_age:end_age]

Expand All @@ -288,7 +281,7 @@ function insurance(::SingleLife,lc::LifeContingency,::Nothing)
iss_age = lc.life.issue_age
end_age = omega(lc) + iss_age - 1
len = end_age - iss_age
v = disc.(lc.int,1:len+1)
v = discount.(lc.int,1:len+1)
tpx = [survival(mt,iss_age,att_age, lc.life.fractional_assump) for att_age in iss_age:end_age]
qx = mt[iss_age:end_age]

Expand All @@ -302,7 +295,7 @@ end

function insurance(::LastSurvivor,::Frasier,lc::LifeContingency, to_time)
iszero(to_time) && return 0.0 #short circuit and return 0 if there is no time elapsed
v = disc.(lc.int,1:to_time)
v = discount.(lc.int,1:to_time)
tpx = [survival(lc,t) for t in 0:to_time-1]
qx = [ survival(lc,t) - survival(lc,t+1) for t in 0:to_time-1]

Expand All @@ -311,7 +304,7 @@ end

function insurance(::LastSurvivor,::Frasier,lc::LifeContingency, ::Nothing)
to_time = omega(lc)
v = disc.(lc.int,1:to_time)
v = discount.(lc.int,1:to_time)
tpx = [survival(lc,t) for t in 0:to_time-1]
qx = [ survival(lc,t) - survival(lc,t+1) for t in 0:to_time-1]

Expand Down Expand Up @@ -339,7 +332,7 @@ function annuity_due(::SingleLife,lc::LifeContingency, npayments; start_time=0)

end_time = npayments + start_time - 1

discount_factor = disc.(lc.int,start_time:end_time)
discount_factor = discount.(lc.int,start_time:end_time)
pmts = [survival(lc,t) for t in start_time:end_time]

return sum(discount_factor .* pmts)
Expand All @@ -348,7 +341,7 @@ end
function annuity_due(::SingleLife,lc::LifeContingency; start_time=0)
npayments = omega(lc) - start_time
end_time = (npayments+start_time)
discount_factor = disc.(lc.int,start_time:end_time)
discount_factor = discount.(lc.int,start_time:end_time)
pmts = [survival(lc,t) for t in start_time:end_time]

return sum(discount_factor .* pmts)
Expand All @@ -367,7 +360,7 @@ function annuity_due(::LastSurvivor,::Frasier, lc::LifeContingency, npayments;st
npayments -= start_time
npayments == 0 && return 0.0
end_time = npayments + start_time -1
discount_factor = disc.(lc.int,start_time:end_time)
discount_factor = discount.(lc.int,start_time:end_time)
pmts = [survival(lc,t) for t in start_time:end_time]
return sum( discount_factor .* pmts )

Expand All @@ -376,7 +369,7 @@ end
function annuity_due(::LastSurvivor,::Frasier, lc::LifeContingency;start_time=0)
npayments = omega(lc) - start_time
end_time = npayments + start_time
discount_factor = disc.(lc.int,start_time:end_time)
discount_factor = discount.(lc.int,start_time:end_time)
pmts = [survival(lc,t) for t in start_time:end_time]
return sum( discount_factor .* pmts )

Expand All @@ -397,7 +390,7 @@ annuity_immediate(lc::LifeContingency;start_time=0) = annuity_due(lc,start_time=
# eq 5.13 ALMCR 2nd ed
function annuity_immediate(lc::LifeContingency,npayments; start_time=0)
x = annuity_due(lc,npayments;start_time=start_time)
y = disc(lc,start_time,start_time+npayments)
y = discount(lc,start_time,start_time+npayments)
z = survival(lc,npayments)
return x - 1 + y * z
end
Expand Down Expand Up @@ -432,7 +425,7 @@ end
The **actuarial present value** which is the survival times the discount factor for the life contingency.
"""
function APV(lc::LifeContingency,to_time)
return survival(lc,to_time) * disc(lc.int,to_time)
return survival(lc,to_time) * discount(lc.int,to_time)
end

"""
Expand Down Expand Up @@ -470,12 +463,12 @@ function mt.survival(ins::LastSurvivor,assump::JointAssumption,l::JointLife,from
return ₜpₓ + ₜpᵧ - ₜpₓ * ₜpᵧ
end

disc(lc::LifeContingency,t) = disc(lc.int,t)
disc(lc::LifeContingency,t1,t2) = disc(lc.int,t1,t2)
Yields.discount(lc::LifeContingency,t) = discount(lc.int,t)
Yields.discount(lc::LifeContingency,t1,t2) = discount(lc.int,t1,t2)

# unexported aliases
const V = reserve_premium_net
const v = disc
const v = Yields.discount
const A = insurance
const a = annuity_immediate
const= annuity_due
Expand Down
Loading

0 comments on commit 8b79969

Please sign in to comment.