Stable API
The top-level textbook API is stable. Start with the user guide for worked calculations; see the experimental reference for custom models.
Lives and assumptions
See Mortality and lives and Joint lives.
LifeContingencies.SingleLife — Type
SingleLife(mortality; issue_age, alive=true, fractional_assump=UniformDeaths())
SingleLife(; mortality, issue_age, alive=true, fractional_assump=UniformDeaths())A Life object containing the necessary assumptions for contingent maths related to a single life. Use with a LifeContingency to do many actuarial present value calculations.
mortality is one of:
- a vector of rates indexed by attained age, such as
table.ultimateortable.select[30].issue_agedefaults to the vector's first age. - a select table's vector of rate vectors, such as
table.select.issue_agepicks the row, and defaults to the first issue age. - a parametric model from MortalityTables, such as
MortalityTables.Gompertz(a=0.0002, b=0.13).issue_ageis required.
Keyword arguments:
issue_ageis the assumed issue age for theSingleLifeand is the basis of many contingency calculations.alive(defaulttrue) is the life's status at issue. A life that is not alive hassurvival(life, t) == 0for everyt;survival(life, from, to)is still the mortality assumption's survival for a life alive atfrom. Useful for joint lives with different statuses.fractional_assump. Default value isUniformDeaths(). This is aDeathDistributionfrom theMortalityTables.jlpackage and is the assumption to use for non-integer ages/times.
Examples
julia> vbt = MortalityTables.table("2001 VBT Residual Standard Select and Ultimate - Male Nonsmoker, ANB");
julia> SingleLife(vbt.select[30]).issue_age # select rates for issue age 30
30
julia> SingleLife(vbt.select, issue_age=30).mortality == vbt.select[30] # the same, picking the row by issue age
true
julia> survival(SingleLife(mortality=vbt.ultimate, issue_age=30), 10)
0.9894404665434904
julia> survival(SingleLife(MortalityTables.Gompertz(a=0.0002, b=0.13), issue_age=40), 10)
0.4750123629834455LifeContingencies.JointLife — Type
JointLife(; lives, contingency, joint_assumption=Independent())A Life object containing the necessary assumptions for contingent maths related to a joint life insurance. Use with a LifeContingency to do many actuarial present value calculations.
Keyword arguments:
livesis a tuple of twoSingleLifescontingencyis required. It is the trigger for contingent benefits:LastSurvivor()orFirstToDie(). See?Contingency.joint_assumptionDefault value isIndependent(), independent lives, the only assumption implemented. See?JointAssumption.
Examples
julia> vbt = MortalityTables.table("2001 VBT Residual Standard Select and Ultimate - Male Nonsmoker, ANB");
julia> lives = (SingleLife(vbt.select[30]), SingleLife(vbt.select[35]));
julia> survival(JointLife(lives=lives, contingency=LastSurvivor()), 40) # at least one alive
0.935943451146974
julia> survival(JointLife(lives=lives, contingency=FirstToDie()), 40) # both alive
0.5468053463643783LifeContingencies.LifeContingency — Type
LifeContingency(life, interest)Pair a SingleLife or JointLife with an interest assumption. A numeric interest is an annual effective rate. A custom yield curve must supply correct values for FinanceCore's discount(curve, t) and discount(curve, from, to) methods. For a non-flat curve, implement interval discounting with the ratio of issue-date factors. Products inherit the original life selection and curve origin from this pair. Reuse it across products: Insurance(lc, 2) and AnnuityDue(lc, 2) share the assumptions but choose different payment rules. Equivalently, pass (life, interest, ...) directly to each product constructor.
julia> life = SingleLife(UltimateMortality([0.5, 1.0]; start_age=60));
julia> lc = LifeContingency(life, 0.05);
julia> present_value(Insurance(lc, 1))
0.47619047619047616LifeContingencies.Independent — Type
Independent()The assumption of independent lives in a joint life calculation. Is a subtype of JointAssumption.
LifeContingencies.Frasier — Type
Compatibility alias for Independent, the assumption of independent lives.
LifeContingencies.LastSurvivor — Type
LastSurvivor()The contingency whereupon benefits are payable upon both lives passing. Is a subtype of Contingency
LifeContingencies.FirstToDie — Type
FirstToDie()The contingency whereupon benefits are payable upon the first life passing. Is a subtype of Contingency
MortalityTables.omega — Function
omega(lc::LifeContingency)
omega(l::Life)The duration in years from issue covered by the mortality assumption. For a rate vector, survival to this duration uses its last defined rate. For a parametric law it is the time to the law's own terminal age, possibly fractional or Inf. Ending a table does not by itself establish extinction.
For a JointLife, this is the longer horizon for LastSurvivor and the shorter for FirstToDie. Each individual still needs support for the transitions being requested.
omega(rate_vector) instead gives the last attained age with a rate. With issue age 60 and rates through age 100, the vector's omega is 100 and the life's is 41.
MortalityTables.survival — Function
survival(life::SingleLife, to_time)
survival(life::SingleLife, from_time, to_time)Times are durations in years from issue. With one time, use the observed status at issue: alive=false gives zero, while alive=true uses the mortality assumption from issue.
With two times, ask the hypothetical question of surviving to to_time if alive at from_time. This uses the mortality assumption alone, regardless of the recorded alive flag. A positive result for an initially dead life does not change its status or imply that it can become alive. Use the one-time form or product valuation when the observed starting status should determine the result.
survival(life)Return a survival vector for the given life.
survival(life::JointLife, to_time)
survival(life::JointLife, from_time, to_time)With one time, the probability that life's contingency is still active at to_time, given each life's status at issue. With two, that probability conditional on the contingency being active at from_time. This conditions on the joint status alone, not on which lives are alive. Throws a DomainError when the contingency cannot be active at from_time.
survival(lc::LifeContingency, to_time)
survival(lc::LifeContingency, from_time, to_time)Delegate to lc.life's survival probabilities, with times measured in years from issue. One-time survival uses the observed status at issue. For a SingleLife, two-time survival is the mortality assumption conditional on being alive at from_time; for a JointLife, it conditions on its contingency being active then. See SingleLife and JointLife. MortalityTables' decrement gives the complementary probability.
Examples
julia> lc = LifeContingency(SingleLife([0.1, 0.2, 0.3, 0.4]), 0.05);
julia> survival(lc, 1)
0.9
julia> survival(lc, 1, 3)
0.5599999999999999
julia> decrement(lc, 1, 3)
0.44000000000000006survival(ins::Insurance, time)The probability that the product's life contingency is in force at duration time, given its original status at issue. This is independent of the product's guarantee or schedule.
survival(Insurance)The survivorship vector for the given insurance: for life insurance, the probability of being in force at the start of each year of cover; for an annuity, at each payment time.
Products and their views
See Insurance and annuities for amounts, dates and guarantees.
LifeContingencies.Insurance — Type
Insurance(lc::LifeContingency[, term])
Insurance(life, interest[, term])Life insurance paying a unit benefit at the end of the year of death, for term years, or for the whole of life when term is omitted. For a JointLife, the benefit is paid at the end of the year in which its contingency ends. A term of zero gives an insurance with no payments.
The mortality assumption has to cover the contract. Whole-life insurance needs rates that reach extinction (a rate of 1), so give a term for a table whose rates stop short of 1, or for parametric mortality.
Issue age is based on the issue_age in the LifeContingency lc. interest is a rate such as 0.05, or any yield curve that FinanceCore can discount with, such as one from FinanceModels.
Examples
julia> life = SingleLife(UltimateMortality([0.5, 1.0])); # the rates reach 1, so whole life is defined
julia> present_value(Insurance(life, 0.05, 1)) # one-year term
0.47619047619047616
julia> present_value(Insurance(life, 0.05)) # whole life
0.9297052154195011LifeContingencies.Endowment — Type
Endowment(lc, term)
Endowment(life, interest, term)A unit benefit on death during the positive whole-year term, payable at year end, or on survival to maturity. For joint lives, death means the contingency ending. Maturity is explicit: incomplete mortality can support an endowment through its last covered year without imposing a terminal death rate.
Examples
julia> lc = LifeContingency(SingleLife(UltimateMortality([0.1, 0.2]; start_age=60)), 0.0);
julia> e = Endowment(lc, 2);
julia> round(present_value(e); digits=6) # death during cover or survival to maturity
1.0
julia> present_value(e, 2; state=:alive) # maturity benefit has settled
0.0LifeContingencies.AnnuityDue — Function
AnnuityDue(lc::LifeContingency[, term]; certain=0, start_time=0, frequency=1)
AnnuityDue(life, interest[, term]; certain=0, start_time=0, frequency=1)An annuity due: frequency payments a year of 1/frequency each, at the start of each period, beginning at start_time and lasting term years, or for life when term is omitted.
The first certain * frequency installments, limited by the term, are guaranteed. They begin at commencement and remain payable if death occurs during deferral or the life starts out dead. Later installments require the life (or joint contingency) to be in force. certain=0 guarantees none. A term of zero gives an empty stream with PV zero.
term, certain and start_time are whole numbers of years; frequency is a positive whole number of payments per year. Negative or fractional values raise an ArgumentError. A monthly annuity pays 1/12 per installment; scale its value by the annual benefit amount, or by 12 for a monthly amount of one.
The mortality assumption has to cover the life-contingent payments. A life annuity needs rates that reach extinction (a rate of 1), so give a term for a table whose rates stop short of 1, or for parametric mortality.
Examples
julia> life = SingleLife(UltimateMortality([0.5, 1.0]));
julia> present_value(AnnuityDue(life, 0.05)) # 1 now, and 1 a year later if alive
1.4761904761904763
julia> present_value(AnnuityDue(life, 0.05; certain=2)) # both payments guaranteed
1.9523809523809523
julia> collect(timepoints(AnnuityDue(life, 0.05, 1; frequency=4)))
4-element Vector{Float64}:
0.0
0.25
0.5
0.75LifeContingencies.AnnuityImmediate — Function
AnnuityImmediate(lc::LifeContingency[, term]; certain=0, start_time=0, frequency=1)
AnnuityImmediate(life, interest[, term]; certain=0, start_time=0, frequency=1)An annuity immediate: like AnnuityDue, but each payment is made at the end of its period.
Examples
julia> life = SingleLife(UltimateMortality([0.5, 1.0]));
julia> present_value(AnnuityImmediate(life, 0.05)) # 1 at the end of each year while alive
0.47619047619047616LifeContingencies.timepoints — Function
timepoints(ins::Insurance)A range of scheduled payment times in years from issue, aligned with cashflows. Death insurance pays at year ends. Annuity dates depend on commencement, frequency and due/immediate timing. collect(timepoints(ins)) materializes a vector.
LifeContingencies.benefit — Function
benefit(ins::Insurance)The scheduled unit amount: 1.0 for death insurance and 1.0 / frequency for an annuity. Thus an annuity is normalized to one annual unit, regardless of payment frequency.
LifeContingencies.probability — Function
probability(Insurance)The vector of contingent benefit probabilities for the given insurance: for life insurance, the probability that the benefit is paid at the end of each year of cover; for an annuity, the probability that each installment is paid (one for guaranteed installments).
LifeContingencies.cashflows — Function
cashflows(ins::Insurance)A vector of expected, undiscounted amounts, benefit(ins) .* probability(ins), dated at timepoints. Mortality has already been applied; do not multiply by survival again. An empty product has an empty vector. Scale these amounts or the product PV to change the sum insured or annual annuity amount.
FinanceCore.discount — Function
discount(ins::Insurance)A vector of issue-date discount factors at timepoints(ins). These factors contain no mortality probabilities. For revaluation, use the curve's interval factors via present_value.
Values, premiums and reserves
See Valuation and reserves for conditioning and payment phases.
FinanceCore.present_value — Function
present_value(ins::Insurance)The actuarial present value at issue of every scheduled benefit, including any payment at time zero. Expected payments use the original life statuses, and the curve is fixed for the valuation. A zero-duration product is worth zero. The two-argument method uses the selected settlement phase at its valuation date.
present_value(ins::Insurance, time::Real; state=nothing, phase=:before)The actuarial present value of the given insurance benefits, as if you were standing at time: the expected outstanding payments as projected from issue, discounted to time with the curve's own factors discount(interest, time, t).
For example, if the given Insurance has decremented payments [1,2,3,4,5] at times [1,2,3,4,5] and you call pv(ins,3), you will get the present value of the payments [4,5] at times [4,5], discounted to time 3.
At phase=:before, end-period benefits (including immediate-annuity installments) have settled but beginning-period annuity-due installments remain. phase=:after also settles due installments. For a net stream with only an annual premium due, these correspond to the terminal reserve and initial reserve respectively; they differ from the engine phases, which apply before/after every state payment. In both phases, valuation times within 1e-9 / frequency years of a scheduled payment are snapped to its date (or within four floating-point ulps, if larger), capped below half a payment period. Death insurance and endowments use annual frequency. This also covers date drift from repeated additions in projection loops. Pass state for a known current status; otherwise the value retains the issue distribution. Dividing by the survivorship gives one only when every remaining payment depends on being in force at time, as for a life annuity outside its guarantee period, or a death benefit valued at the end of a policy year:
julia> ins = Insurance(SingleLife(UltimateMortality([0.5, 0.5, 1.0])), 0.0);
julia> cashflows(ins)
3-element Vector{Float64}:
0.5
0.25
0.25
julia> present_value(ins, 1) # the payments at times 2 and 3
0.5
julia> present_value(ins, 1) / survival(ins, 1) # for a life in force at time 1
1.0LifeContingencies.PricedPolicy — Type
PricedPolicy(benefit, premiums; premium=nothing)An immutable priced policy holding a benefit product, a premium annuity and its established annualized premium. Products must share their issue mortality and interest basis. Omit premium to price by equivalence once, or supply a finite premium for an existing policy. Revaluation via policy_value retains this premium and contractual timing. At frequency f, each premium installment is premium/f.
LifeContingencies.policy_value — Function
policy_value(policy::PricedPolicy, time_or_times; basis=nothing, state=nothing, phase=:before)Prospective benefit values less the policy's scheduled premiums at integer anniversaries. A new LifeContingency basis changes valuation assumptions while retaining the stored premium. This does not recompute a valuation net premium. A collection returns a numeric vector in the supplied order, using one occupancy pass and one backward pass.
By default, condition on the contingency remaining active; state supplies a known status. End-period benefits have settled; phase=:before includes due installments and :after settles them too. With only an annual premium due, these are the terminal and initial values respectively. Settled contracts have zero value; impossible conditioning before termination raises DomainError. Expenses are not modeled by these unit products. Remaining guarantees do not make impossible active-state conditioning valid: pass a known state (for example, :dead) to value those obligations after extinction. Joint horizons retain both lives' mortality support even if one life was dead at issue.
LifeContingencies.premium_net — Function
premium_net(lc::LifeContingency)
premium_net(lc::LifeContingency,to_time)The level annual net premium for one unit of whole-life insurance, or term insurance through to_time years. Benefits are payable at the end of the year of death; premiums are payable in advance while the contingency is in force, for the same term. Equivalence gives insurance PV divided by annuity-due PV. Expenses and profit loadings are excluded.
For a sum insured of B, multiply the result by B. A reserve uses the premium set at issue; calling this function again on a newly selected attained-age life reprices it.
premium_net(benefit, premiums)Price the benefit by equivalence against an annualized premium annuity on their common basis. A zero premium-annuity PV is undefined. Access policy.premium for an established premium, which may differ from this equivalence premium.
LifeContingencies.reserve_premium_net — Function
reserve_premium_net(lc::LifeContingency, time_or_times; state=nothing, phase=:before)
reserve_premium_net(life, interest, time_or_times; state=nothing, phase=:before)Textbook whole-life net premium reserves with annual premiums. Both the equivalence premium and the future benefits are calculated on the supplied lc basis. Pass a nonnegative integer anniversary or a vector/range of anniversaries; a collection returns values in its supplied order. No alternative valuation basis or stored-premium policy is accepted. Pass a range for a reserve column; each call establishes the equivalence premium anew. Use policy_value with a PricedPolicy for established premiums, finite contracts, limited-pay business or revaluation on another basis. These unit products do not implement statutory or GAAP reserve methods.
Death benefits through the valuation anniversary have settled. phase=:before includes the premium due then (terminal reserve); phase=:after settles it (initial reserve). Default conditioning is on the contingency remaining active; state supplies a known single or joint status. Impossible conditioning before contractual termination raises DomainError; once all obligations have settled, the reserve is zero.
LifeContingencies.APV — Function
APV(lc::LifeContingency, time)The survival-weighted issue-date discount factor survival(lc, time) * discount(lc, time). This is the unit pure endowment ${}_tE_x$ for a single life issued at age $x$, with time measured in years from issue. It is also available as the unexported commutation function D. To value a complete product, use present_value with Insurance or an annuity constructor.
Actuarial notation
These commutation functions are available with an explicit import. D is an alias of APV; A, ä, a, P, V, v and ω map to the functions in the notation guide.
LifeContingencies.l — Function
l(lc::LifeContingency, to_time)Expected survivors at policy duration to_time, equal to basis * survival(lc, to_time). The default radix is one. basis scales this call only; it does not change the life or other commutation functions. Import l explicitly from LifeContingencies.
LifeContingencies.D — Function
D(lc::LifeContingency, to_time)The unit-radix commutation function: another name for APV. Its time argument is policy duration, not attained age.
LifeContingencies.C — Function
C(lc::LifeContingency, to_time)The issue-date value of a unit death benefit for the year starting at policy duration to_time: discount(lc, to_time + 1) * (survival(lc, to_time) - survival(lc, to_time + 1)). Import C explicitly from LifeContingencies.
LifeContingencies.N — Function
N(lc::LifeContingency, from_time)Sum D(lc, t) for t in from_time:(floor(Int, omega(lc)) - 1). Durations are in years from issue and the radix is one. Infinite support raises a DomainError; this sum does not supply a continuation for incomplete mortality. Import N explicitly.
LifeContingencies.M — Function
M(lc::LifeContingency, from_time)Sum C(lc, t) for t in from_time:(floor(Int, omega(lc)) - 1). Durations are in years from issue and the radix is one. Infinite support raises a DomainError; incomplete or uncovered fractional terminal support raises a DomainError. Import M explicitly.
Index
LifeContingencies.ExperimentalLifeContingencies.EndowmentLifeContingencies.FirstToDieLifeContingencies.FrasierLifeContingencies.IndependentLifeContingencies.InsuranceLifeContingencies.JointLifeLifeContingencies.LastSurvivorLifeContingencies.LifeContingencyLifeContingencies.PricedPolicyLifeContingencies.SingleLifeLifeContingencies.APVLifeContingencies.AnnuityDueLifeContingencies.AnnuityImmediateLifeContingencies.CLifeContingencies.DLifeContingencies.MLifeContingencies.NLifeContingencies.benefitLifeContingencies.cashflowsLifeContingencies.lLifeContingencies.policy_valueLifeContingencies.premium_netLifeContingencies.probabilityLifeContingencies.reserve_premium_netLifeContingencies.timepoints