diff --git a/src/LifeContingencies.jl b/src/LifeContingencies.jl index fb8aa55..e47cc84 100644 --- a/src/LifeContingencies.jl +++ b/src/LifeContingencies.jl @@ -9,7 +9,7 @@ using Yields const mt = MortalityTables export LifeContingency, - Insurance, AnnuityDue, AnnuityImmediate, + Insurance, AnnuityDue, AnnuityImmediate, Endowment, APV, SingleLife, Frasier, JointLife, LastSurvivor, @@ -36,7 +36,6 @@ export LifeContingency, # 'actuarial objects' that combine multiple forms of decrements (lapse, interest, death, etc) abstract type Life end - """ struct SingleLife mortality @@ -292,13 +291,22 @@ struct Term{L,Y} <: Insurance term::Int end +struct Endowment{L,Y} <: Insurance + life::L + int::Y + term::Int + maturity::Int +end + """ Insurance(lc::LifeContingency, term) - Insurance(life,interest, term) + Insurance(life, interest, term) Insurance(lc::LifeContingency) - Insurance(life,interest) + Insurance(life, interest) + Insurance(lc::LifeContingency, term, maturity) + Insurance(life, interest, term, maturity) -Life insurance with a term period of `term`. If `term` is `nothing`, then whole life insurance. +Life insurance with a term period of `term`. If `maturity` is nothing, then term life insurance. If `term` is `nothing`, then whole life insurance. Issue age is based on the `issue_age` in the LifeContingency `lc`. @@ -312,14 +320,18 @@ ins = Insurance( ) ``` """ -Insurance(lc::LifeContingency, term) = Insurance(lc.life, lc.int, term) +Insurance(lc::LifeContingency, term::Int, maturity::Int) = Insurance(lc.life, lc.int, term, maturity) +Insurance(lc::LifeContingency, term::Int) = Insurance(lc.life, lc.int, term) Insurance(lc::LifeContingency) = Insurance(lc.life, lc.int) -function Insurance(life, int, term::Int) +function Insurance(life::Life, int, term::Int, maturity::Int) + return Endowment(life, int, term, maturity) +end +function Insurance(life::Life, int, term::Int) term < 1 && return ZeroBenefit(life, int) return Term(life, int, term) end -function Insurance(life, int) +function Insurance(life::Life, int) return WholeLife(life, int) end @@ -489,6 +501,10 @@ function benefit(ins::I) where {I<:Insurance} return 1.0 end +function benefit(ins::I) where {I<:Endowment} + return (1.0, ins.maturity) +end + function benefit(ins::ZeroBenefit) return 0.0 end @@ -511,6 +527,18 @@ function probability(ins::I) where {I<:Insurance} end end +function probability(ins::I) where {I<:Endowment} + m = ins.life.mortality + issage = ins.life.issue_age + return Iterators.map(timepoints(ins)) do t + if t == lastindex(timepoints(ins)) + (survival(m, issage + t - 1) * decrement(m, issage + t - 1, issage + t), survival(m, issage + t)) + else + (survival(m, issage + t - 1) * decrement(m, issage + t - 1, issage + t), 0) + end + end +end + function probability(ins::ZeroBenefit) return Iterators.repeated(1.0, length(timepoints(ins))) end @@ -542,6 +570,10 @@ function cashflows(ins::I) where {I<:Insurance} return Iterators.map(p -> p * b, probability(ins)) end +function cashflows(ins::I) where {I<:Endowment} + b = benefit(ins) + return Iterators.map(p -> p[1] * b[1] + p[2] * b[2], probability(ins)) +end """ timepoints(Insurance) @@ -558,6 +590,10 @@ function timepoints(ins::Term)::UnitRange{Int64} return 1:min(omega(ins.life), ins.term) end +function timepoints(ins::Endowment)::UnitRange{Int64} + return 1:min(omega(ins.life), ins.term) +end + function timepoints(ins::ZeroBenefit) return Iterators.repeated(0.0, 1) end @@ -654,7 +690,7 @@ present_value(ins,10) / survival(ins,10) ``` """ function ActuaryUtilities.present_value(ins::T,time) where {T<:Insurance} - ts =timepoints(ins) + 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) yield = ins.int @@ -676,6 +712,8 @@ end premium_net(lc::LifeContingency, to_time) = A(lc, to_time) / ä(lc, to_time) +premium_net(lc::LifeContingency, to_time, maturity) = A(lc, to_time, maturity) / ä(lc, to_time) + """ reserve_premium_net(lc::LifeContingency,time) @@ -687,6 +725,12 @@ function reserve_premium_net(lc::LifeContingency, time) return (PVFB - PVFP) / APV(lc, time) end +function reserve_premium_net(lc::LifeContingency, time, term, maturity) + PVFB = present_value(Insurance(lc, term, maturity)) - present_value(Insurance(lc, term, maturity), time) + PVFP = premium_net(lc, term ,maturity) * (ä(lc, term) - ä(lc, term - time)) + return max(0.0, (PVFB - PVFP) / APV(lc, time)) +end + """ APV(lc::LifeContingency,to_time) diff --git a/test/single_life.jl b/test/single_life.jl index 5c8e6d8..5479e40 100644 --- a/test/single_life.jl +++ b/test/single_life.jl @@ -78,9 +78,12 @@ @test present_value(Insurance(ins),0) ≈ 0.1107844934319970 @test present_value(Insurance(ins),90) / survival(Insurance(ins),90) ≈ 1 / 1.05 @test present_value(AnnuityDue(ins)) ≈ 18.6735256379281000 + @test present_value(Insurance(ins, 20, 1000)) ≈ 366.52476153552664 @test premium_net(ins) ≈ 0.0059327036350854 + @test premium_net(ins, 20, 1000) ≈ 28.20263753 @test reserve_premium_net(ins, 1) ≈ 0.0059012862412992 @test reserve_premium_net(ins, 2) ≈ 0.0119711961204193 + @test reserve_premium_net(ins, 2, 20, 1000) ≈ 0 qs = t.select[30][30:55] @test present_value(Insurance(ins, 26)) ≈ sum(qs .* [1; cumprod(1 .- qs[1:25])] .* [1.05^-t for t = 1:26])