Skip to content

Commit

Permalink
cleanup and update for FinanceModels (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
alecloudenback committed Aug 2, 2023
1 parent e33d253 commit afcd358
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 306 deletions.
11 changes: 4 additions & 7 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
name = "LifeContingencies"
uuid = "c8f0d631-89cd-4a1f-93d0-7542c3692561"
authors = ["Alec Loudenback"]
version = "2.3.1"
version = "2.4.0"

[deps]
ActuaryUtilities = "bdd23359-8b1c-4f88-b89b-d11982a786f4"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
FinanceCore = "b9b1ffdd-6612-4b69-8227-7663be06e089"
MortalityTables = "4780e19d-04b9-53dc-86c2-9e9aa59b5a12"
Transducers = "28d57a85-8fef-5791-bfe6-a80928e7c999"
Yields = "d7e99b2f-e7f3-4d9e-9f01-2338fc023ad3"

[compat]
ActuaryUtilities = "^2,^3"
MortalityTables = "^0.12, ^1, ^2"
MortalityTables = "^1, ^2"
Transducers = "^0.4"
Yields = "^2,^3"
FinanceCore = "2"
julia = "^1.6"

[extras]
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Calculate various items for a 30-year-old male nonsmoker using 2015 VBT base tab

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

# load mortality rates from MortalityTables.jl
Expand All @@ -50,7 +50,7 @@ life = SingleLife( # The life underlying the risk
mortality = vbt2001.select[issue_age], # -- Mortality rates
)

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

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

Expand Down Expand Up @@ -157,7 +157,7 @@ tables = [
]

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

whole_life_costs = map(tables) do t
map(issue_ages) do ia
Expand Down Expand Up @@ -190,7 +190,7 @@ l2 = SingleLife(mortality = m2.ultimate, issue_age = 37)
jl = JointLife(lives=(l1, l2), contingency=LastSurvivor(), joint_assumption=Frasier())


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

Expand Down
65 changes: 32 additions & 33 deletions src/LifeContingencies.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module LifeContingencies

using ActuaryUtilities
using MortalityTables
using Transducers
using Dates
using Yields
using FinanceCore
import FinanceCore: present_value, discount

const mt = MortalityTables

Expand Down Expand Up @@ -69,11 +68,11 @@ struct SingleLife{M,D} <: Life
fractional_assump::D
end

function SingleLife(; mortality, issue_age = nothing, alive = true, fractional_assump = mt.Uniform())
function SingleLife(; mortality, issue_age=nothing, alive=true, fractional_assump=mt.Uniform())
return SingleLife(mortality; issue_age, alive, fractional_assump)
end

function SingleLife(mortality; issue_age = nothing, alive = true, fractional_assump = mt.Uniform())
function SingleLife(mortality; issue_age=nothing, alive=true, fractional_assump=mt.Uniform())
if isnothing(issue_age)
issue_age = firstindex(mortality)
end
Expand Down Expand Up @@ -231,7 +230,7 @@ end
``l_x`` is a retrospective actuarial commutation function which is the survival up to a certain point in time. By default, will have a unitary basis (ie `1.0`), but you can specify `basis` keyword argument to use something different (e.g. `1000` is common in the literature.)
"""
function l(lc::LifeContingency, to_time; basis = 1.0)
function l(lc::LifeContingency, to_time; basis=1.0)
return survival(lc.life, to_time) * basis
end

Expand Down Expand Up @@ -307,7 +306,7 @@ Issue age is based on the `issue_age` in the LifeContingency `lc`.
```
ins = Insurance(
SingleLife(mortality = UltimateMortality([0.5,0.5]),issue_age = 0),
Yields.Constant(0.05),
FinanceModels.Yield.Constant(0.05),
1 # 1 year term
)
```
Expand Down Expand Up @@ -370,12 +369,12 @@ Annuity due with the benefit period starting at `start_time` and ending after `n
```
ins = AnnuityDue(
SingleLife(mortality = UltimateMortality([0.5,0.5]),issue_age = 0),
Yields.Constant(0.05),
FinanceModels.Yield.Constant(0.05),
1, # term of policy
)
```
"""
function AnnuityDue(life, int, term; certain = nothing, start_time = 0, frequency = 1)
function AnnuityDue(life, int, term; certain=nothing, start_time=0, frequency=1)
term < 1 && return ZeroBenefit(life, int)
if isnothing(certain)
Annuity(life, int, Due(), TermAnnuity(term), start_time, frequency)
Expand All @@ -384,19 +383,19 @@ function AnnuityDue(life, int, term; certain = nothing, start_time = 0, frequenc
end
end

function AnnuityDue(life, int; certain = nothing, start_time = 0, frequency = 1)
function AnnuityDue(life, int; certain=nothing, start_time=0, frequency=1)
if isnothing(certain)
Annuity(life, int, Due(), LifeAnnuity(), start_time, frequency)
else
Annuity(life, int, Due(), LifeCertain(certain), start_time, frequency)
end
end

function AnnuityDue(lc::L, term; certain = nothing, start_time = 0, frequency = 1) where {L<:LifeContingency}
function AnnuityDue(lc::L, term; certain=nothing, start_time=0, frequency=1) where {L<:LifeContingency}
return AnnuityDue(lc.life, lc.int, term; certain, start_time, frequency)
end

function AnnuityDue(lc::L; certain = nothing, start_time = 0, frequency = 1) where {L<:LifeContingency}
function AnnuityDue(lc::L; certain=nothing, start_time=0, frequency=1) where {L<:LifeContingency}
return AnnuityDue(lc.life, lc.int; certain, start_time, frequency)
end

Expand All @@ -411,13 +410,13 @@ Annuity immediate with the benefit period starting at `start_time` and ending af
```
ins = AnnuityImmediate(
SingleLife(mortality = UltimateMortality([0.5,0.5]),issue_age = 0),
Yields.Constant(0.05),
FinanceModels.Yield.Constant(0.05),
1 # term of policy
)
```
"""
function AnnuityImmediate(life, int, term; certain = nothing, start_time = 0, frequency = 1)
function AnnuityImmediate(life, int, term; certain=nothing, start_time=0, frequency=1)
term < 1 && return ZeroBenefit(life, int)
if isnothing(certain)
Annuity(life, int, Immediate(), TermAnnuity(term), start_time, frequency)
Expand All @@ -426,19 +425,19 @@ function AnnuityImmediate(life, int, term; certain = nothing, start_time = 0, fr
end
end

function AnnuityImmediate(life, int; certain = nothing, start_time = 0, frequency = 1)
function AnnuityImmediate(life, int; certain=nothing, start_time=0, frequency=1)
if isnothing(certain)
Annuity(life, int, Immediate(), LifeAnnuity(), start_time, frequency)
else
Annuity(life, int, Immediate(), LifeCertain(certain), start_time, frequency)
end
end

function AnnuityImmediate(lc::L, term; certain = nothing, start_time = 0, frequency = 1) where {L<:LifeContingency}
function AnnuityImmediate(lc::L, term; certain=nothing, start_time=0, frequency=1) where {L<:LifeContingency}
return AnnuityImmediate(lc.life, lc.int, term; certain, start_time, frequency)
end

function AnnuityImmediate(lc::L; certain = nothing, start_time = 0, frequency = 1) where {L<:LifeContingency}
function AnnuityImmediate(lc::L; certain=nothing, start_time=0, frequency=1) where {L<:LifeContingency}
return AnnuityImmediate(lc.life, lc.int; certain, start_time, frequency)
end

Expand All @@ -449,7 +448,7 @@ The survivorship for the given insurance from time zero to `time`.
"""
function MortalityTables.survival(ins::I, t) where {I<:Insurance}
survival(ins.life,t)
survival(ins.life, t)
end

"""
Expand All @@ -475,8 +474,8 @@ The discount vector for the given insurance.
To get the fully computed and allocated vector, call `collect(discount(...))`.
"""
function Yields.discount(ins::I) where {I<:Insurance}
return Iterators.map(t -> Yields.discount.(ins.int, t), timepoints(ins))
function FinanceCore.discount(ins::I) where {I<:Insurance}
return Iterators.map(t -> FinanceCore.discount.(ins.int, t), timepoints(ins))
end


Expand Down Expand Up @@ -631,7 +630,7 @@ end
The actuarial present value of the given insurance benefits.
"""
function ActuaryUtilities.present_value(ins::T) where {T<:Insurance}
function FinanceCore.present_value(ins::T) where {T<:Insurance}
cfs = cashflows(ins)
times = timepoints(ins)
yield = ins.int
Expand All @@ -653,10 +652,10 @@ To get an undecremented present value, divide by the survivorship to that timepo
present_value(ins,10) / survival(ins,10)
```
"""
function ActuaryUtilities.present_value(ins::T,time) where {T<:Insurance}
ts =timepoints(ins)
function FinanceCore.present_value(ins::T, time) where {T<:Insurance}
ts = timepoints(ins)
times = (t - time for t in ts if t > time)
cfs = (cf for (cf,t) in zip(cashflows(ins),ts) if t > time)
cfs = (cf for (cf, t) in zip(cashflows(ins), ts) if t > time)
yield = ins.int
pv = present_value(yield, cfs, times)
return pv
Expand Down Expand Up @@ -786,9 +785,9 @@ function mt.survival(l::JointLife, from_time, to_time)
end

function mt.survival(ins::LastSurvivor, assump::JointAssumption, l::JointLife, from_time, to_time)
a = survival(ins,assump,l,from_time)
b = survival(ins,assump,l,to_time)
return b/a
a = survival(ins, assump, l, from_time)
b = survival(ins, assump, l, to_time)
return b / a
end

function mt.survival(ins::LastSurvivor, assump::JointAssumption, l::JointLife, to_time)
Expand All @@ -800,19 +799,19 @@ function mt.survival(ins::LastSurvivor, assump::JointAssumption, l::JointLife, t
return ₜpₓ + ₜpᵧ - ₜpₓ * ₜpᵧ
end

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


# unexported aliases
const V = reserve_premium_net
const v = Yields.discount
const v = FinanceCore.discount
# A(args) = present_value(Insurance(args))
# a(args, kwargs) = present_value(AnnuityImmediate(args...; kwargs...))
# ä(args) = present_value(AnnuityDue(args))
const A = present_value Insurance
const a = present_value AnnuityImmediate
const= present_value AnnuityDue
const A = FinanceCore.present_value Insurance
const a = FinanceCore.present_value AnnuityImmediate
const= FinanceCore.present_value AnnuityDue
const P = premium_net
const ω = omega

Expand Down
28 changes: 14 additions & 14 deletions test/AMLCR.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,39 @@

# §5.7 payment frequency and certain (Tables 5.1 and 5.2)
function f(age)
life = SingleLife(mortality = AMLCR_std, issue_age = age)
int = Yields.Constant(0.05)
life = SingleLife(mortality=AMLCR_std, issue_age=age)
int = 0.05
lc = [
AnnuityImmediate(life, int)
AnnuityImmediate(life, int, frequency = 4)
AnnuityImmediate(life, int, frequency=4)
AnnuityDue(life, int)
AnnuityDue(life, int, frequency = 4)
AnnuityDue(life, int, frequency=4)
AnnuityImmediate(life, int, 10)
AnnuityImmediate(life, int, 10, frequency = 4)
AnnuityDue(life, int, 10, frequency = 4)
AnnuityImmediate(life, int, 10, frequency=4)
AnnuityDue(life, int, 10, frequency=4)
]

return present_value.(lc)
end

# atol only to two decimals b/c AMLCR uses rounded rates?
@test all(isapprox.(f(20), [18.966, 19.338, 19.966, 19.588, 7.711, 7.855, 7.952], atol = 0.01))
@test all(isapprox.(f(80), [7.548, 7.917, 8.548, 8.167, 6.128, 6.373, 6.539], atol = 0.01))
@test all(isapprox.(f(20), [18.966, 19.338, 19.966, 19.588, 7.711, 7.855, 7.952], atol=0.01))
@test all(isapprox.(f(80), [7.548, 7.917, 8.548, 8.167, 6.128, 6.373, 6.539], atol=0.01))

life = SingleLife(mortality = AMLCR_std, issue_age = 20)
int = Yields.Constant(0.05)
life = SingleLife(mortality=AMLCR_std, issue_age=20)
int = 0.05

# property tests

@test present_value(AnnuityDue(life, int)) > present_value(AnnuityDue(life, int, start_time = 5))
@test present_value(AnnuityDue(life, int)) > present_value(AnnuityDue(life, int, start_time=5))

@test present_value(AnnuityDue(life, int, 5, certain = 5, start_time = 5)) == sum([1 for t = 5:9] .* [1 / 1.05^t for t = 5:9])
@test present_value(AnnuityDue(life, int, 5, certain=5, start_time=5)) == sum([1 for t = 5:9] .* [1 / 1.05^t for t = 5:9])

# relation on pg 124
@test issorted(present_value.([
AnnuityImmediate(life, int)
AnnuityImmediate(life, int, frequency = 4)
AnnuityDue(life, int, frequency = 4)
AnnuityImmediate(life, int, frequency=4)
AnnuityDue(life, int, frequency=4)
AnnuityDue(life, int)
]))

Expand Down
2 changes: 1 addition & 1 deletion test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[deps]
ActuaryUtilities = "bdd23359-8b1c-4f88-b89b-d11982a786f4"
FinanceCore = "b9b1ffdd-6612-4b69-8227-7663be06e089"
MortalityTables = "4780e19d-04b9-53dc-86c2-9e9aa59b5a12"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

Expand Down
Loading

2 comments on commit afcd358

@alecloudenback
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/88844

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v2.4.0 -m "<description of version>" afcd3585758b4093e93f7326170f352fdf57cfd5
git push origin v2.4.0

Please sign in to comment.