0% found this document useful (0 votes)
9 views234 pages

Actuarial Math Solutions with Python

The document titled 'Solving Actuarial Math with Python' by Terence Lim provides a comprehensive guide on using Python for various actuarial calculations. It covers topics such as installation, interest theory, life contingent risks, survival models, expected future lifetimes, fractional ages, insurance, and annuities, along with methods and examples for each topic. The document serves as a resource for actuaries looking to integrate Python into their mathematical practices.

Uploaded by

Beena
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views234 pages

Actuarial Math Solutions with Python

The document titled 'Solving Actuarial Math with Python' by Terence Lim provides a comprehensive guide on using Python for various actuarial calculations. It covers topics such as installation, interest theory, life contingent risks, survival models, expected future lifetimes, fractional ages, insurance, and annuities, along with methods and examples for each topic. The document serves as a resource for actuaries looking to integrate Python into their mathematical practices.

Uploaded by

Beena
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Solving Actuarial Math with Python

Terence Lim

May 05, 2024


CONTENTS

1 Actuarial Python 5
1.1 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3 License . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2 Interest Theory 9
2.1 Interest rates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

3 Life Contingent Risks 15


3.1 Probability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2 Portfolio Percentile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.4 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

4 Survival Models 21
4.1 Lifetime distribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.2 Survival function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.3 Force of mortality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.4 Actuarial notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.5 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.6 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

5 Expected Future Lifetimes 27


5.1 Complete expectation of life . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.2 Curtate expectation of life . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.3 Temporary expectation of life . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.4 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

6 Fractional Ages 33
6.1 Uniform distribution of deaths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.2 Constant force of mortality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.3 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.4 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

7 Insurance 39
7.1 Present value of life insurance r.v. 𝑍 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

i
7.2 Whole life insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
7.3 Term insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.4 Deferred insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.5 Endowment insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.6 Pure endowment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.7 Variances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.8 Varying insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
7.9 Probabilities and percentiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.10 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.11 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

8 Annuities 53
8.1 Present value of life annuity r.v. 𝑌 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
8.2 Whole life annuity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
8.3 Temporary annuity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.4 Deferred whole life annuity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.5 Certain and life annuity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.6 Life insurance twin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.7 Variances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.8 Immediate life annuity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
8.9 Varying life annuities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
8.10 Probabilities and percentiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.11 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.12 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

9 Premiums 63
9.1 Present value of loss at issue r.v. 0 𝐿 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
9.2 Equivalence principle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
9.3 Net premium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
9.4 Gross premium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
9.5 Portfolio Percentile Premium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
9.6 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
9.7 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

10 Policy Values 71
10.1 Net policy value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
10.2 Gross policy value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
10.3 Variance of future loss . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
10.4 Expense reserve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
10.5 Probabilities and Percentiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
10.6 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
10.7 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

11 Reserves 81
11.1 Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
11.2 Interim reserves . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
11.3 Modified reserves . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
11.4 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
11.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

12 Recursion 87
12.1 Chain rule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
12.2 Expected future lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
12.3 Life insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
12.4 Life annuities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

ii
12.5 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
12.6 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

13 Life Table 97
13.1 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
13.2 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98

14 SULT 103
14.1 Standard ultimate life table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
14.2 Pure endowment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
14.3 Term life insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
14.4 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
14.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104

15 Select Life Table 115


15.1 Select and ultimate life model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
15.2 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
15.3 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

16 Mortality Laws 123


16.1 Uniform distribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
16.2 Beta distribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
16.3 Gompertz’s Law . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
16.4 Makeham’s Law . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
16.5 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
16.6 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

17 Constant Force of Mortality 129


17.1 Expected future lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
17.2 Pure endowment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
17.3 Life insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
17.4 Life annuities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
17.5 Net future loss . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
17.6 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
17.7 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

18 Extra Risk 135


18.1 Age rating . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
18.2 Multiple of mortality rate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
18.3 Force of mortality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
18.4 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
18.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136

19 1/M’thly 139
19.1 Life Insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
19.2 Life Annuity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
19.3 Life Insurance Twin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
19.4 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
19.5 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142

20 UDD M’thly 145


20.1 Life insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
20.2 Continuous Life Insurance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
20.3 Interest functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
20.4 Life annuities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146

iii
20.5 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
20.6 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

21 Woolhouse M’thly 151


21.1 Life Annuities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
21.2 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
21.3 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152

22 FAM-L Exam Solutions and Hints 155


22.1 1 Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
22.2 2 Survival models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
22.3 3 Life tables and selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
22.4 4 Insurance benefits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
22.5 5 Annuities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
22.6 6 Premium Calculation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
22.7 7 Policy Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214

iv
Solving Actuarial Math with Python

actuarialmath – Solve Life Contingent Risks with Python


This Python package implements fundamental methods for modeling life contingent risks, and closely follows the coverage
of traditional topics in actuarial exams and standard texts such as the “Fundamentals of Actuarial Math - Long-term”
exam syllabus by the Society of Actuaries, and “Actuarial Mathematics for Life Contingent Risks” by Dickson, Hardy
and Waters. The actuarial concepts, and corresponding Python classes, are introduced and modeled hierarchically.

Quick Start

1. pip install actuarialmath


• also requires numpy, scipy, matplotlib and pandas.
2. Start Python (version >= 3.10) or Jupyter-notebook
• Select a suitable subclass to initialize with your actuarial assumptions, such as MortalityLaws (or a special
law like ConstantForce), LifeTable, SULT, SelectLife or Recursion.
• Call appropriate methods to compute intermediate or final results, or to solve parameter values implicitly.
• Adjust answers with ExtraRisk or Mthly (or its UDD or Woolhouse) classes

CONTENTS 1
Solving Actuarial Math with Python

Examples

SOA FAM-L sample question 5.7:


(2)
Given 𝐴35 = 0.188, 𝐴65 = 0.498, 𝑆35 (30) = 0.883, calculate the EPV of a temporary annuity 𝑎35∶30|
̈ paid half-yearly
using the Woolhouse approximation.

from actuarialmath import Recursion, Woolhouse


# initialize Recursion class with actuarial inputs
life = Recursion().set_interest(i=0.04)\
.set_A(0.188, x=35)\
.set_A(0.498, x=65)\
.set_p(0.883, x=35, t=30)
# modfy the standard results with Woolhouse mthly approximation
mthly = Woolhouse(m=2, life=life, three_term=False)
# compute the desired temporary annuity value
print(1000 * mthly.temporary_annuity(35, t=30)) # solution = 17376.7

SOA FAM-L sample question 7.20:


For a fully discrete whole life insurance of 1000 on (35), you are given
• First year expenses are 30% of the gross premium plus 300
• Renewal expenses are 4% of the gross premium plus 30
• All expenses are incurred at the beginning of the policy year
• Gross premiums are calculated using the equivalence principle
• The gross premium policy value at the end of the first policy year is R
• Using the Full Preliminary Term Method, the modified reserve at the end of the first policy year is S
• Mortality follows the Standard Ultimate Life Table
• i = 0.05
Calculate R − S

from actuarialmath import SULT, Contract


life = SULT()
# compute the required FPT policy value
S = life.FPT_policy_value(35, t=1, b=1000) # is always 0 in year 1!
# input the given policy contract terms
contract = Contract(benefit=1000,
initial_premium=.3,
initial_policy=300,
renewal_premium=.04,
renewal_policy=30)
# compute gross premium using the equivalence principle
G = life.gross_premium(A=life.whole_life_insurance(35), **contract.premium_terms)
# compute the required policy value
R = life.gross_policy_value(35, t=1, contract=contract.set_contract(premium=G))
print(R-S) # solution = -277.19

Resources

2 CONTENTS
Solving Actuarial Math with Python

1. Jupyter notebook or run in Colab, to solve all sample SOA FAM-L exam questions.
2. User Guide, or download pdf
3. API reference
4. Github repo and issues

Sources

• SOA FAM-L Sample Questions: copy retrieved Aug 2022


• SOA FAM-L Sample Solutions: copy retrieved Aug 2022
• Actuarial Mathematics for Life Contingent Risks, by David Dickson, Mary Hardy and Howard Waters, published
by Cambridge University Press.

Contact

Github: [Link]
Enjoy!
Terence Lim PhD ASA CFA CPA CFP CAIA

CONTENTS 3
Solving Actuarial Math with Python

4 CONTENTS
CHAPTER

ONE

ACTUARIAL PYTHON

The actuarialmath package is written in and requires Python (currently: version 3.10). Though the comparable R
language possesses other desirable qualities, object-oriented programming is more straightforward in Python: since our
sequence of actuarial concepts logically build upon each other, they are naturally developed as a hieararchy of Python
classes with inherited methods and properties.

1.1 Installation

Install either by using pip:


• pip install actuarialmath
or cloning from github:
• git clone [Link]

1.2 Overview

Each section of this document introduces a class, along with the actuarial concepts it implements, arranged logically in
three groups. To use the package, a suitable subclass should first be selected from the last group to load the given actuarial
assumptions. Then the appropriate computational methods can be called, which may be inherited from the other general
classes or make use of any shortcut formulas that can be obtained from the specific survival model assumed.
1. Implement general actuarial methods
• Basic interest theory and probability laws
• Survival functions, expected future lifetimes and fractional ages
• Insurance, annuity, premiums, policy values, and reserves calculations
2. Adjust results for
• Extra mortality risks
• 1/mthly payment frequency using UDD or Woolhouse approaches
3. Specify survival models and assumptions, and implement associated shortcut formulas
• Recursion inputs
• Life table, select life table, or standard ultimate life table
• Mortality laws, such as constant force of maturity, beta and uniform distributions, or Makeham’s and Gom-
pertz’s laws

5
Solving Actuarial Math with Python

1.3 License

MIT License
Copyright (c) 2022-2023 Terence Lim
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PAR-
TICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFT-
WARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1.4 Methods

The Actuarial base class provides some common helpful utility functions and definitions of constants, that are needed
by other classes in the package.

from actuarialmath import Actuarial


import math
import describe
[Link](Actuarial)

class Actuarial - Define constants and common utility functions

Constants:
VARIANCE : select variance as the statistical moment to calculate

WHOLE : indicates that term of insurance or annuity is Whole Life

Methods:
--------

solve(fun, target, grid, mad):


Solve root, or parameter that minimizes absolute value, of a function

add_term(t, n):
Add two terms, either term may be Whole Life

max_term(x, t, u):
Decrease term t if adding deferral period u to (x) exceeds maxage

isclose(r, target, abs_tol):


Is close to zero or target value

6 Chapter 1. Actuarial Python


Solving Actuarial Math with Python

1.5 Examples

The solve() method is called to impute the value of a parameter such that its function output value is equal to a
specified target value, by either returning the zero root (set argument mad = False, by default) or minimizing the
absolute difference (set mad = True). As a simple example, to solve for the median of the exponential cumulative
distribution:

[Link](fun=lambda x: 1 - [Link](-x), target=0.5, grid=[0, 2])

0.6931471805599453

The add_term method adds two terms, while handling the case where either may not be a fixed term, i.e. they may be
whole life and indicated with the constant WHOLE. The max_term method trims the value of a term t, such that its sum
with age x and deferral period u is no larger than the maximum age

actuarial = Actuarial()

def as_term(t): return "WHOLE_LIFE" if t == [Link] else t

for a,b in [(3, [Link]), ([Link], -1), (3, 2), (3, -1)]:
print(f"{as_term(a)} + {as_term(b)} =", as_term(actuarial.add_term(a, b)))
print()
for t in [10, 50, [Link]]:
print(as_term(t), '->', actuarial.max_term(x=65, t=t, u=20))

3 + WHOLE_LIFE = WHOLE_LIFE
WHOLE_LIFE + -1 = WHOLE_LIFE
3 + 2 = 5
3 + -1 = 2

10 -> 10
50 -> 15
WHOLE_LIFE -> 15

1.5. Examples 7
Solving Actuarial Math with Python

8 Chapter 1. Actuarial Python


CHAPTER

TWO

INTEREST THEORY

Interest theory functions, that are in common actuarial and financial use, are reviewed. Interest rates are generally assumed
to be fixed and constant.

2.1 Interest rates

𝑖 is the amount earned on $1 after one year


• effective annual interest rate
𝑖(𝑚) is the nominal interest rate compounded m times per year
• it is numerically equal to 𝑚 times the effective interest rate over 1/m’th of a year.
𝑖
𝑑=
1+𝑖
• annual discount rate of interest
𝑑(𝑚) is the nominal discount rate compounded m times per year
• it is numerically equal to 𝑚 times the discount rate over 1/m’th of a year.
1
𝑣=
1+𝑖
• annual discount factor
𝛿 = log(1 + 𝑖)
• continuosly-compounded rate of interest, or force of interest per year
Relationships between interest rates

𝑣𝑡 = 𝑒−𝛿𝑡
= (1 − 𝑑)𝑡
= (1 + 𝑖)−𝑡
𝑑(𝑚) 𝑚𝑡
= (1 − )
𝑚
𝑖(𝑚) −𝑚𝑡
= (1 + )
𝑚

Doubling the force of interest


Note that doubling the force of interest is not the same as doubling the rate of interest.

9
Solving Actuarial Math with Python

2
𝛿 ← 2𝛿
2
𝑖 ← 2𝑖 + 𝑖2
2
𝑑 ← 2𝑑 − 𝑑2
2
𝑣 ← 𝑣2
Annuity certain
Present value of an annuity certain that pays at a rate of $1 per year for 𝑛 years
1 − 𝑣𝑛
𝑎𝑛|
̈ =
𝑑
• Annuity certain due: pays $1 at the beginning of the year
1 − 𝑣𝑛
𝑎𝑛| = = 𝑎𝑛+1|
̈ −1
𝑖
• Immediate annuity certain: pays $1 at the end of the year
1 − 𝑣𝑛
𝑎𝑛| =
𝛿
• Continuous annuity certain: pays at a rate of $1 per year continuously.

2.2 Methods

The Interest class implements methods to convert between nominal, discount, continuously-compounded and 1/m’thy
rates of interest, and compute the value of an annuity certain.

from actuarialmath import Interest


import describe
[Link](Interest)

class Interest - Store an assumed interest rate, and compute interest rate␣
↪functions

Args:
i : assumed annual interest rate
d : or annual discount rate
v : or annual discount factor
delta : or continuously compounded interest rate
v_t : or discount rate as a function of time
i_m : or m-thly interest rate
d_m : or m-thly discount rate
m : m'thly frequency, if i_m or d_m are specified

Methods:
--------

annuity(t, m, due):
Compute value of the annuity certain factor

mthly(m, i, d, i_m, d_m):


Convert to or from m'thly interest rates

double_force(i, delta, d, v):


Double the force of interest

10 Chapter 2. Interest Theory


Solving Actuarial Math with Python

2.3 Examples

The Interest class can be initialized with an assumed annual interest rate expressed in any one of the forms i, d,
v, delta, i_m, d_m (where the latter two forms also require the argument m to specify the number of times
compounded in a year). Then that rate can be retrieved in any other annual form as an attribute.

interest = Interest(i=0.05)
print(interest.d, interest.v, [Link], interest.i)

0.047619047619047616 0.9523809523809523 0.04879016416943205 0.05

The mthly static method converts between annual-pay and m-thly pay interest rates in any form.

delta = 0.05
i = Interest(delta=delta).i # convert to annual interest rate
d = Interest(delta=delta).d # convert to annual discount rate
i_m = [Link](i=i, m=12) # convert to annual interest rate monthly-pay
d_m = [Link](d=d, m=12) # convert to annual discount rate monthly-pay
print('Continuously-compounded annual rate of interest:', delta)
print(' Convert to annual interest rate: ', i, [Link](i_m=i_m,␣
↪m=12))

print(' Convert to annual discount rate: ', d, [Link](d_m=d_m,␣


↪m=12))

print(' Convert to annual interest rate (monthly-pay):', i_m)


print(' Convert to annual discount rate (monthly-pay):', d_m)

Continuously-compounded annual rate of interest: 0.05


Convert to annual interest rate: 0.05127109637602412 0.
↪051271096376023007

Convert to annual discount rate: 0.04877057549928606 0.


↪04877057549928587

Convert to annual interest rate (monthly-pay): 0.05010431149342143


Convert to annual discount rate (monthly-pay): 0.049895977858680496

The annuity class method computes the present value of an annuity that pays at the rate of $1 per year continuously
or discrete (due, immediate, or m’thly)

import [Link] as plt


t = range(100)
[Link](t, [[Link](t=s, due=True) for s in t])
[Link](f'Present Value of Annuity Certain Due (i={interest.i * 100}%)')
[Link]('Period (years)')
[Link]('Annuity Due Factor')
print("Annuity Factors By Payment Frequency (perpetual, i=5%):")
print('1. Immediate: ', [Link](t=[Link], due=False))
print('2. Immediate (quarterly pay): ', [Link](t=[Link], m=4,␣
↪due=False))

print('3. Continuous: ', [Link](t=[Link], m=0))


print('4. Due (quarterly pay): ', [Link](t=[Link], m=4,␣
↪due=True))

print('5. Due: ', [Link](t=[Link], due=True))

Annuity Factors By Payment Frequency (perpetual, i=5%):


1. Immediate: 19.999999999999982
(continues on next page)

2.3. Examples 11
Solving Actuarial Math with Python

(continued from previous page)


2. Immediate (quarterly pay): 20.371188429095998
3. Continuous: 20.49593431428785
4. Due (quarterly pay): 20.621188429096076
5. Due: 20.999999999999975

The double_force static method takes an interest rate in any form, and converts to the same form after doubling the
force of interest.

print("Example: double the force of interest i=0.05")


i = 0.05
d = Interest(i=i).d # convert interest rate to discount rate
print('i:', i, 'd:', d)
i2 = Interest.double_force(i=i) # interest rate after doubling force
d2 = Interest.double_force(d=d) # discount rate after doubling force
print('i:', round(i2, 6), round(Interest(d=d2).i, 6))
print('d:', round(d2, 6), round(Interest(i=i2).d, 6))

Example: double the force of interest i=0.05


i: 0.05 d: 0.047619047619047616
i: 0.1025 0.1025
d: 0.092971 0.092971

SOA Question 3.10:


A group of 100 people start a Scissor Usage Support Group. The rate at which members enter and leave the group is

12 Chapter 2. Interest Theory


Solving Actuarial Math with Python

dependent on whether they are right-handed or left-handed. You are given the following:
• The initial membership is made up of 75% left-handed members (L) and 25% right-handed members (R)
• After the group initially forms, 35 new (L) and 15 new (R) join the group at the start of each subsequent year
• Members leave the group only at the end of each year
• 𝑞 𝐿 = 0.25 for all years
• 𝑞 𝑅 = 0.50 for all years Calculate the proportion of the Scissor Usage Support Group’s expected membership that
is left-handed at the start of the group’s6 th year, before any new members join for that year.

print("SOA Question 3.10: (C) 0.86")


interest = Interest(v=0.75)
L = 35 * [Link](t=4, due=False) + 75 * interest.v_t(t=5)
interest = Interest(v=0.5)
R = 15 * [Link](t=4, due=False) + 25 * interest.v_t(t=5)
print(L / (L + R))

SOA Question 3.10: (C) 0.86


0.8578442833761983

2.3. Examples 13
Solving Actuarial Math with Python

14 Chapter 2. Interest Theory


CHAPTER

THREE

LIFE CONTINGENT RISKS

Life contingencies use probability models as well as interest rate functions. By modeling future lifetimes as random
variables, probabilities of survival or death can be calculated. This section reviews essential probability relationships and
the moments of common distributions.

3.1 Probability

The cumulative distribution function of a r.v. X is the probability 𝐹 (𝑥) = 𝑃 𝑟(𝑋 ≤ 𝑥). For a continuous r.v., 𝑓(𝑥) =
𝑑
𝑑𝑥 𝐹 (𝑥) is the probability density function. For a discrete r.v. 𝑝(𝑥) = 𝑃 𝑟(𝑋 = 𝑥) is the probability mass function.

𝐸(𝑥) = ∫−∞ 𝑥𝑓(𝑥)𝑑𝑥 when continuous, or ∑𝑥 𝑥𝑓(𝑥) when discrete
• is the mean, or first moment of 𝑋.
𝑉 𝑎𝑟(𝑥) = 𝐸[(𝑋 − 𝐸[𝑥])2 ]
• is the variance, or second central moment of 𝑋, often denoted 𝜎2
Linearity of expectations
𝐸(𝑎 + 𝑏𝑋 + 𝑐𝑌 ) = 𝑎 + 𝑏𝐸(𝑋) + 𝑐𝐸(𝑌 )
• the mean of the sum of random variables is the sum of the means
Variances
𝑉 𝑎𝑟(𝑎𝑋 + 𝑏𝑌 ) = 𝑎2 𝑉 𝑎𝑟(𝑋) + 𝑏2 𝑉 𝑎𝑟(𝑌 ) + 2 𝑎 𝑏 𝐶𝑜𝑣(𝑋, 𝑌 )
• is the formula to compute the variance of the weighted sum of two random variables
𝐶𝑜𝑣(𝑋, 𝑌 ) = 𝐸[𝑋𝑌 ] − 𝐸[𝑋] ⋅ 𝐸[𝑌 ]
• is the formula to compute the covariance between two random variables
Bernoulli:
The Bernoulli is a discrete distribution that takes one of two values 𝑌 ∈ {𝑎, 𝑏} with probabilities (𝑝, 1 − 𝑝)
𝐸[𝑌 ] = 𝑎 𝑝 + 𝑏 (1 − 𝑝)
• mean of Bernoulli distribution
𝑉 𝑎𝑟[𝑌 ] = (𝑎 − 𝑏)2 𝑝 (1 − 𝑝)
• Bernoulli shortcut formula for the variance
Binomial:
A binomial r.v. measures the total number of successes from 𝑁 independent 0-1 Bernoulli r.v., where each has a proba-
bility of success 𝑝.

15
Solving Actuarial Math with Python

𝐸[𝑌 ] = 𝑁 𝑝
• mean of Binomial distribution
𝑉 𝑎𝑟[𝑌 ] = 𝑁 𝑝 (1 − 𝑝)
• variance of Binomial distribution
Mixture
A mixture distribution is a random variable 𝑌 whose distribution function can be expressed as a weighted average of
the distribution functions of 𝑛 random variables 𝑌1 , ⋯ , 𝑌𝑛 . A common case is when Y is a mixture of two Binomial
distributions (𝑝′ , 𝑁 ), where 𝑝′ ∈ (𝑝1 , 𝑝2 ) with respective probabilities (𝑝, 1 − 𝑝): a value is first drawn from a
Bernoulli random variable w.p. 𝑝, then depending on its value, a final value is then drawn from the selected one of the
two component Binomial distributions.
𝐸[𝑌 ] = 𝑝 𝑁 𝑝1 + (1 − 𝑝) 𝑁 𝑝2
• mean of mixture of two Binomial distributions
𝑉 𝑎𝑟[𝑌 ] = 𝐸[𝑌 2 ] − 𝐸[𝑌 ]2 = 𝐸[𝑉 𝑎𝑟(𝑌 | 𝑝′ ) + 𝐸(𝑌 | 𝑝′ )2 ] − 𝐸[𝑌 ]2
• variance of mixture of two Binomial distributions
Conditional Variance
𝑉 𝑎𝑟[𝑌 ] = 𝑉 𝑎𝑟(𝐸[𝑌 | 𝑝′ ]) + 𝐸[𝑉 𝑎𝑟(𝑌 | 𝑝′ )]
• alternative calculation of the variance of a mixture by using conditional variance formula, where the first term can
be easily calculated using the Bernoulli shortcut, since 𝐸[𝑌 | 𝑝′ ] is a Bernoulli random variable that takes a value
of either 𝐸[𝑌 | 𝑝1 ] or 𝐸[𝑌 | 𝑝2 ] with respective probabilities (𝑝, 1 − 𝑝)

3.2 Portfolio Percentile

Normal Approximation
A portfolio, 𝑌 , which is the sum of 𝑁 iid random variables each with mean 𝜇 and variance 𝜎2 , has a normal distribution
with
• mean 𝐸[𝑌 ] = 𝑁 𝜇 and
• variance 𝑉 𝑎𝑟[𝑌 ] = 𝑁 𝜎2
Percentiles
Percentiles are essentially an inverse function of the cumulative probability distribution. If 𝐹 (𝑦) is the cdf for 𝑌 , then
the p’th quantile (or 100𝑝’th percentile) is a number 𝑦𝑝 such that 𝐹 (𝑦𝑝 ) = 𝑃 𝑟(𝑌 ≤ 𝑦𝑝 ) = 𝑝.

𝑌𝑝 = 𝐸[𝑌 ] + 𝑧𝑝 √𝑉 𝑎𝑟[𝑌 ]
• value of 𝑌 at percentile 𝑝 when Y is normally distributed, where 𝑧𝑝 is the inverse standard normal function 𝑧𝑝 =
Φ−1 (𝑝)

16 Chapter 3. Life Contingent Risks


Solving Actuarial Math with Python

3.3 Methods

The Life class implements methods for computing moments and probability distributions of random variables.

import math
from actuarialmath import Life
import describe
[Link](Life)

class Life - Compute moments and probabilities

Methods:
--------

variance(a, b, var_a, var_b, cov_ab):


Variance of weighted sum of two r.v.

covariance(a, b, ab):
Covariance of two r.v.

bernoulli(p, a, b, variance):
Mean or variance of bernoulli r.v. with values {a, b}

binomial(p, N, variance):
Mean or variance of binomial r.v.

mixture(p, p1, p2, N, variance):


Mean or variance of binomial mixture

conditional_variance(p, p1, p2, N):


Conditional variance formula

portfolio_percentile(mean, variance, prob, N):


Probability percentile of the sum of N iid r.v.'s

set_interest(interest):
Set interest rate, which can be given in any form

quantiles_frame(quantiles):
Display selected quantile values from Normal distribution table

3.4 Examples

The variance method computes the variance of 𝑎𝑋 + 𝑏𝑌 by plugging in the required inputs into the formula
𝑎2 𝑉 𝑎𝑟(𝑋) + 𝑏2 𝑉 𝑎𝑟(𝑌 ) + 2 𝑎 𝑏 𝐶𝑜𝑣(𝑋, 𝑌 ). Similarly, the covariance method computes the covariance of
𝑋 and 𝑌 by pluggin the required inputs into the formula 𝐸[𝑋𝑌 ] − 𝐸[𝑋] ⋅ 𝐸[𝑌 ]

[Link](a=2, b=3, var_a=4, var_b=5, cov_ab=6)

133

3.3. Methods 17
Solving Actuarial Math with Python

[Link](a=2, b=3, ab=8)

The bernoulli and binomial methods compute the mean or variance (by setting the argument variance=True)
of the respective probability distributions

print([Link](p=0.5, a=0, b=1), [Link](p=0.5, a=0, b=1, variance=True))

0.5 0.25

print([Link](p=0.5, N=100), [Link](p=0.5, N=100, variance=True))

50.0 25.0

For a mixture of two binomial distribution, the mixture method computes the mean or variance, while the condi-
tional_variance method computes the variance using the conditional variance formula.

print([Link](p=0.5, p1=0.3, p2=0.4, N=100),


[Link](p=0.5, p1=0.3, p2=0.4, N=100, variance=True))

35.0 47.5

The sum of 𝑁 iid r.v. with identical mean and variance can be approximated by a normal distribution that is computed by
the portfolio_cdf method, or whose 100𝑝’th percentile is computed by the portfolio_percentile method.

Life.portfolio_cdf(mean=0, variance=1, N=50, value=5)

0.7602499389065233

Life.portfolio_percentile(mean=0, variance=1, N=50, prob=0.76)

4.994313317536634

The set_interest method is called to initialize and store an Interest object with the assumed interest rate,
expressed in any annual or 𝑚-thly form, which is used in all subsequent life contingency computations.

life = Life().set_interest(i=0.05)
[Link]

0.04879016416943205

SOA Question 2.2:


Scientists are searching for a vaccine for a disease. You are given:
• 100,000 lives age x are exposed to the disease
• Future lifetimes are independent, except that the vaccine, if available, will be given to all at the end of year 1

18 Chapter 3. Life Contingent Risks


Solving Actuarial Math with Python

• The probability that the vaccine will be available is 0.2


• For each life during year 1, 𝑞𝑥 = 0.02
• For each life during year 2, 𝑞𝑥+1 = 0.01 if the vaccine has been given and 𝑞𝑥+1 = 0.02 if it has not been given
Calculate the standard deviation of the number of survivors at the end of year 2.

print("SOA Question 2.2: (D) 400")


p1 = (1. - 0.02) * (1. - 0.01) # 2_p_x if vaccine given
p2 = (1. - 0.02) * (1. - 0.02) # 2_p_x if vaccine not given
print([Link](Life.conditional_variance(p=.2, p1=p1, p2=p2, N=100000)))
print([Link]([Link](p=.2, p1=p1, p2=p2, N=100000, variance=True)))

SOA Question 2.2: (D) 400


396.5914603215815
396.5914603237804

Normal distribution table:


The quantiles_frame method displays an extract of normal distribution table for common quantile values, that is
made available during SOA FAM-L exam.

print("Values of z for selected values of Pr(Z<=z)")


print("-------------------------------------------")
print(Life.quantiles_frame().to_string(float_format=lambda x: f"{x:.3f}"))

Values of z for selected values of Pr(Z<=z)


-------------------------------------------
z 0.842 1.036 1.282 1.645 1.960 2.326 2.576
Pr(Z<=z) 0.800 0.850 0.900 0.950 0.975 0.990 0.995

3.4. Examples 19
Solving Actuarial Math with Python

20 Chapter 3. Life Contingent Risks


CHAPTER

FOUR

SURVIVAL MODELS

The future lifetime of an individual is represented as a random variable. Under this framework, probabilities of death
or survival, as well as an important quantity known as the force of mortality, can be calculated. It is shown how these
quantitites are related, along with some actuarial notation.

4.1 Lifetime distribution

Let (𝑥) denotes a life aged x, where 𝑥 ≥ 0, and 𝑇𝑥 is time-to-death, or future lifetime, of (𝑥). This means that 𝑥 + 𝑇𝑥
represents the age-at-death random variable for (𝑥).
𝑡
𝐹𝑥 (𝑡) = 𝑃 𝑟[𝑇𝑥 ≤ 𝑡] = ∫0 𝑓𝑥 (𝑡)𝑑𝑠
• probability that (𝑥) does not survive beyond age 𝑥 + 𝑡.
Lifetime density function
𝜕 𝑓0 (𝑥 + 𝑡)
𝑓𝑥 (𝑡) = 𝜕𝑡 𝐹𝑥 (𝑡) =
𝑆0 (𝑥)
• probability density function for the random variable 𝑇𝑥

4.2 Survival function

In life insurance problems we may be interested in the probability of survival rather than death
𝑆𝑥 (𝑡) ≡ 𝑡 𝑝𝑥 = 𝑃 𝑟[𝑇𝑥 > 𝑡] = 1 − 𝐹𝑥 (𝑡)
• the probability that (x) survives for at least t years
𝑆0 (𝑥 + 𝑡)
𝑆𝑥 (𝑡) =
𝑆0 (𝑥)
• by assumption that 𝑃 𝑟[𝑇𝑥 ≤ 𝑡] = 𝑃 𝑟[𝑇0 ≤ 𝑥 + 𝑡|𝑇0 > 𝑥]

𝑆𝑥 (𝑡) = ∫𝑡 𝑓𝑥 (𝑠)𝑑𝑠
𝑡 ∞
• since ∫0 𝑓𝑥 (𝑠))𝑑𝑠 + ∫𝑡 𝑓𝑥 (𝑠)𝑑𝑠 = 1
𝑙𝑥+𝑡
𝑆𝑥 (𝑡) =
𝑙𝑥
• a life table provides a concrete way to visualize the survival function: from a certain number of lives at a starting
age, we apply survival probabilities to compute the expected number of lives at any other age.
𝑆𝑥 (0) = 1, 𝑆𝑥 (∞) = 0, 𝑆𝑥′ (𝑡) ≤ 0

21
Solving Actuarial Math with Python

• conditions on 𝑆𝑥 to be a valid survival function


Percentiles:
A 100𝑝-th percentile of survival time is the time 𝑡 such that there is a probability 𝑝 that survival time ie less than 𝑡.

4.3 Force of mortality

What actuaries call the force of mortality, denoted 𝜇𝑥 , is known as the hazard rate in survival analysis and the failure rate
in reliability theory.
𝜕
𝑓𝑥 (𝑡) − 𝜕 𝑙𝑥+𝑡 − 𝜕𝑡 𝑝
𝑡 𝑥 𝜕
𝜇𝑥+𝑡 = = 𝜕𝑡 = = ln 𝑝
𝑆𝑥 (𝑡) 𝑙𝑥+𝑡 𝑡 𝑝𝑥 𝜕𝑡 𝑡 𝑥
• the force of mortality actually completely determines the survival function
𝑓𝑥 (𝑡) = 𝑡 𝑝𝑥 𝜇𝑥+𝑡
• can be related to the lifetime density function for (𝑥)
𝑥+𝑡 𝑡
𝑆𝑥 (𝑡) = 𝑒− ∫𝑥 𝜇𝑠 𝑑𝑠
= 𝑒− ∫0 𝜇𝑥+𝑠 𝑑𝑠

• the survival probability, conditional on survival to the age represented by the lower bound, can be computed by
integrating the force of mortality up to the age represented by the upper bound.
𝜇𝑥 𝑑𝑥 ≈ 𝑃 𝑟[𝑇0 ≤ 𝑥 + 𝑑𝑥|𝑇0 > 𝑥]
• intuitively, multiplying the force of mortality by a small number 𝑑𝑥 can be interpreted as the probability that (𝑥)
dies before attaining age 𝑥 + 𝑑𝑥

∫0 𝜇𝑥+𝑠 𝑑𝑠 = ∞
• since 𝑆𝑥 (∞) = 0

4.4 Actuarial notation

The actuarial profession uses shorthand notation to record mathematical formulas:


Survival probability

𝑡 𝑝𝑥 = 𝑃 𝑟[𝑇𝑥 > 𝑡] ≡ 𝑆𝑥 (𝑡)


• probability that (𝑥) survives to at least age 𝑥 + 𝑡
Expected number of survivors
𝑙𝑥 = 𝑙𝑥0 𝑥−𝑥0 𝑝𝑥0

• is the expected number of surviving lives at age 𝑥 from 𝑙𝑥0 independent individuals aged 𝑥0 .
Mortality rate

𝑡 𝑞𝑥 = 1 − 𝑡 𝑝𝑥 ≡ 𝐹𝑥 (𝑡)
• probability that (𝑥) dies before age 𝑥 + 𝑡.
Deferred mortality rate
𝑢+𝑡
𝑢|𝑡 𝑞𝑥 = 𝑃 𝑟[𝑢 < 𝑇𝑥 ≤ 𝑢 + 𝑡] = ∫𝑢 𝑠 𝑝𝑥 𝜇𝑥+𝑠 𝑑𝑠

• probability that (𝑥) survives 𝑢 years, and then dies within the subsequent 𝑡 years.

22 Chapter 4. Survival Models


Solving Actuarial Math with Python

𝑙𝑥+𝑢 − 𝑙𝑥+𝑢+𝑡
𝑢|𝑡 𝑞𝑥 =
𝑙𝑥
• can be related to the number of lives in the life table: total number of deaths of ages between 𝑥 + 𝑢 and 𝑥 + 𝑢 + 𝑡,
divided by number of lives aged 𝑥

𝑢|𝑡 𝑞𝑥 = 𝑢 𝑝𝑥 𝑡𝑞𝑥+𝑢 = 𝑢+𝑡 𝑞𝑥 − 𝑢 𝑞𝑥 = 𝑢 𝑝𝑥 − 𝑢+𝑡 𝑝𝑥

• there are three useful formulas for computing deferred mortality: (1) deferred mortality (2) limited mortality, or
(3) limited survival probabilities.

4.5 Methods

The Survival class implements methods to compute and apply relationships between the various equivalent forms
of survival and mortality functions. The force of mortality function fully describes the lifetime distribution, just as the
survival function does.

import math
from actuarialmath import Survival
import describe
[Link](Survival)

class Survival - Set and derive basic survival and mortality functions

Methods:
--------

set_survival(S, f, l, mu, minage, maxage):


Construct the basic survival and mortality functions given any one form

l_x(x, s):
Number of lives at integer age [x]+s: l_[x]+s

d_x(x, s):
Number of deaths at integer age [x]+s: d_[x]+s

p_x(x, s, t):
Probability that [x]+s lives another t years: : t_p_[x]+s

q_x(x, s, t, u):
Probability that [x]+s lives for u, but not t+u years: u|t_q_[x]+s

f_x(x, s, t):
Lifetime density function of [x]+s after t years: f_[x]+s(t)

mu_x(x, s, t):
Force of mortality of [x] at s+t years: mu_[x](s+t)

4.5. Methods 23
Solving Actuarial Math with Python

4.6 Examples

The set_survival class method is called to specify any survival model by either its survival probability (with the
argument S), force of mortality (mu), number of lives (l), or lifetime density (f) functions. Then the various survival and
mortality probabilities and function values can be retrieved using methods with names similar to their respective actuarial
notations: p_x, q_x, f_x, mu_x, d_x, l_x. In later sections, special mortality laws like the uniform and
exponential distributions, along with their available shortcut formulas, will be described and implemented.

# specify survival function by linearly declining number of lives


life = Survival().set_survival(l=lambda x,s: 100-(x+s), minage=0, maxage=100).set_
↪interest(i=0.05)

print(life.l_x(x=0), life.l_x(x=100), life.l_x(x=50), life.d_x(x=0), life.d_x(x=50))


for x in [10, 50, 90]:
print(life.p_x(x=x), life.q_x(x=x, u=10, t=10), life.f_x(x=x), life.mu_x(x=x))

100 0 50 1 1
0.9888888888888889 0.11111111111111105 1.0 0.011111111111111112
0.98 0.20000000000000007 1.0 0.02
0.9 1.0 1.0 0.1

SOA Question 2.3:


You are given that mortality follows Gompertz Law with B = 0.00027 and c = 1.1. Calculate 𝑓50 (10).

print("SOA Question 2.3: (A) 0.0483")


B, c = 0.00027, 1.1
def S(x,s,t): return ([Link](-B * c**(x+s) * (c**t - 1)/[Link](c)))
life = Survival().set_survival(S=S)
print(life.f_x(x=50, t=10))

SOA Question 2.3: (A) 0.0483


0.048327399045049846

SOA Question 2.6


You are given the survival function:
1
𝑥 3
𝑆0 (𝑥) = (1 − 60 ) , 0 ≤ 𝑥 ≤ 60
Calculate 1000𝜇35 .

print("# SOA Question 2.6: (C) 13.3")


life = Survival().set_survival(l=lambda x,s: (1 - (x+s) / 60)**(1 / 3))
print(1000*life.mu_x(35))

# SOA Question 2.6: (C) 13.3


13.337287043994648

SOA Question 2.7

24 Chapter 4. Survival Models


Solving Actuarial Math with Python

You are given the following survival function of a newborn:


𝑥
𝑆0 (𝑥) = 1 − , 0 ≤ 𝑥 < 40
250
𝑥 2
=1−( ) , 40 ≤ 𝑥 ≤ 100
100

Calculate the probability that (30) dies within the next 20 years

print("SOA Question 2.7: (B) 0.1477")


def S(x,s):
return 1 - ((x+s) / 250) if (x+s) < 40 else 1 - ((x+s) / 100)**2
print(Survival().set_survival(l=S).q_x(30, t=20))

SOA Question 2.7: (B) 0.1477


0.1477272727272727

SOA Question 2.8


In a population initially consisting of 75% females and 25% males, you are given:
• For a female, the force of mortality is constant and equals 𝜇
• For a male, the force of mortality is constant and equals 1.5𝜇
• At the end of 20 years, the population is expected to consist of 85% females and 15% males.
Calculate the probability that a female survives one year.

print("SOA Question 2.8: (C) 0.938")


def fun(mu): # Solve first for mu, given ratio of start and end proportions
male = Survival().set_survival(mu=lambda x,s: 1.5 * mu)
female = Survival().set_survival(mu=lambda x,s: mu)
return (75 * female.p_x(0, t=20)) / (25 * male.p_x(0, t=20))
mu = [Link](fun, target=85/15, grid=0.5)
p = Survival().set_survival(mu=lambda x,s: mu).p_x(0, t=1)
print(p)

SOA Question 2.8: (C) 0.938


0.9383813306903799

CAS41-F99:12
You are given the following survival function:
2
𝑆(𝑥) = 100(𝑘 − 𝑥2 ) 3
1
Find 𝑘, given that 𝜇50 = 48

print("CAS41-F99:12: k = 41")
def fun(k):
return Survival().set_survival(l=lambda x,s: 100*(k - (x+s)/2)**(2/3))\
.mu_x(50)
print([Link](fun, target=1/48, grid=50))

CAS41-F99:12: k = 41
41.00115767959991

4.6. Examples 25
Solving Actuarial Math with Python

26 Chapter 4. Survival Models


CHAPTER

FIVE

EXPECTED FUTURE LIFETIMES

In many insurance applications we are interested in the first and second moments not only of the future lifetime of an
individual, but also of the individual’s curtate future lifetime, defined as the integer part of future lifetime. For some
lifetime distributions (as described in later sections), we are able to obtain the mean and variances of future lifetimes
directly without using numerical integrationn techniques.

5.1 Complete expectation of life


∘ ∞ ∞
𝑒𝑥 = 𝐸[𝑇𝑥 ] = ∫0 𝑡 𝑡 𝑝𝑥 𝜇𝑥+𝑡 𝑑𝑡 = ∫0 𝑡 𝑝𝑥 𝑑𝑡
• is the complete expectation of life, or the mean future lifetime: it can be computed either as (1) the weighted sum
of lifetimes lived or (2) the sum over all years of the probabilities of living at least those years.
∞ ∞
𝐸[𝑇𝑥2 ] = ∫0 𝑡2 𝑡 𝑝𝑥 𝜇𝑥+𝑡 𝑑𝑠 = ∫0 2 𝑡 𝑡 𝑝𝑥 𝑑𝑡
• second moment of future lifetime

𝑉 𝑎𝑟[𝑇𝑥 ] = 𝐸[𝑇𝑥2 ] − (𝑒𝑥 )2
• variance of future lifetime

5.2 Curtate expectation of life

𝐾𝑥 = ⌊𝑇𝑥 ⌋
• is the curtate future lifetime random variable, representing the number of completed whole future years by (x) prior
to death
∞ ∞
𝑒𝑥 = 𝐸[𝐾𝑥 ] = ∑𝑘=0 𝑘 𝑘| 𝑞𝑥 = ∑𝑘=1 𝑘 𝑝𝑥 𝑑𝑡
• Is the curtate expectation of life, or the mean curtate lifetime: it can be computed two ways intuitively similar to
the approach of complete expectations.
∞ ∞
𝐸[𝐾𝑥2 ] = ∑𝑘=0 𝑘2 𝑘| 𝑞𝑥 = ∑𝑘=1 (2𝑘 − 1) 𝑘 𝑞𝑥 𝑑𝑡
• second moment of curtate future lifetime
𝑉 𝑎𝑟[𝐾𝑥 ] = 𝐸[𝐾𝑥2 ] − (𝑒𝑥 )2
• variance of curtate future lifetime

27
Solving Actuarial Math with Python

5.3 Temporary expectation of life

We are sometimes interested in the future lifetime random variable subject to a cap of n years, which is represented by
the random variable 𝑚𝑖𝑛(𝑇𝑥 , 𝑛).
∘ 𝑛 𝑛
𝑒𝑥∶𝑛| = ∫0 𝑡 𝑡 𝑝𝑥 𝜇𝑥+𝑡 𝑑𝑠 + 𝑛 𝑛 𝑝𝑥 = ∫0 𝑡 𝑝𝑥 𝑑𝑡
• term complete expectation of life
𝑛−1 𝑛
𝑒𝑥∶𝑛| = ∑𝑘=0 𝑘 𝑘| 𝑞𝑥 + 𝑛 𝑛 𝑝𝑥 = ∑𝑘=1 𝑘 𝑝𝑥
• temporary curtate expectation of life, limited at 𝑛 years

5.4 Methods

The Lifetime class implements methods to compute curtate and complete expectations and second moments of future
lifetime

from actuarialmath import Lifetime


import describe
[Link](Lifetime)

class Lifetime - Computes expected moments of future lifetime

Methods:
--------

e_x(x, s, t, curtate, moment):


Compute curtate or complete expectations and moments of life

5.5 Examples

The e_x class method computes the mean, variance or second moment (by setting the value of the argument moment to
be 1, VARIANCE, or 2 respectively) of either the complete or curtate future lifetime (by setting the value of the argument
curtate to be False or True respectively).

life = Lifetime().set_survival(mu=lambda *args: 0.1)


print(life.e_x(x=50, curtate=False), life.e_x(x=50, moment=[Link],␣
↪curtate=False))

print(life.e_x(x=50, curtate=True), life.e_x(x=50, moment=[Link],␣


↪curtate=True))

9.999999999999998 99.99999999999997
9.508331944775032 99.91670831680462

Limited future lifetimes are specified by the argument t. The example below plots the expected limited curtate lifetimes
by varying age 𝑥 and lifetime limit 𝑡.

28 Chapter 5. Expected Future Lifetimes


Solving Actuarial Math with Python

import math
import numpy as np
import [Link] as plt

def S(x, s, t): # define a survival function


return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = Lifetime().set_survival(S=S, minage=20, maxage=130)

X, Y = [Link]([Link](20, 100, 1), [Link](0, 80, 1))


Z = np.zeros_like(X)
for i in range([Link][0]):
for j in range([Link][1]):
Z[i][j] = life.e_x(x=X[i][j], t=Y[i][j])

ax = [Link](projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis', edgecolor=None)
ax.set_xlabel('Age (x)')
ax.set_ylabel('Lifetime Limit (t)')
ax.set_title('Expected Limited Curtate Lifetime ($e_{x:\overline{t}|}$)')
ax.view_init(15, -45)

array([[ 0, 0, 0, ..., 0, 0, 0],


[ 1, 1, 1, ..., 1, 1, 1],
[ 2, 2, 2, ..., 2, 2, 2],
...,
[57, 57, 57, ..., 57, 57, 57],
(continues on next page)

5.5. Examples 29
Solving Actuarial Math with Python

(continued from previous page)


[58, 58, 58, ..., 58, 58, 58],
[59, 59, 59, ..., 59, 59, 59]])

AMLCR Exericse 2.1


Let 𝐹0 (𝑡) = 1 − (1 − 𝑡/105)1/5 for 0 ≤ 𝑡 ≤ 105. Calculate
(a) the probability that a newborn life dies before age 60 [0.1559]
(b) the probability that a life aged 30 survives to at least age 70 [0/8586]
(c) the probability that a life aged 20 dies between ages 90 and 100 [0.1394]
(d) the force of mortality at age 50 [0.0036]
(e) the median future lifetime at age 50 [53.28]
(f) the complete expectation of life at age 50 [45.83]
(g) the curtate expectation of life at age 50 [45.18]

def F(t): return (1 - (1 - t/105)**(1/5) if t <= 105 else 1)


life = Lifetime().set_survival(S=lambda x,s,t: (1-F(x+s+t)) / (1-F(x+s)))
print("(a)", life.q_x(x=0, t=60))
print("(b)", life.p_x(x=30, t=70-30))
print("(c)", life.q_x(x=20, u=90-20, t=100-90))
print("(d)", life.mu_x(x=50))
print("(e)", [Link](fun=lambda t: life.p_x(x=50, t=t), target=0.5, grid=105/2))
print("(f)", life.e_x(x=50, curtate=False))
print("(g)", life.e_x(x=50))

(a) 0.1558791201558899
(b) 0.8586207034844889
(c) 0.13943444007155992
(d) 0.003636764417087519
(e) 53.28125000000001
(f) 45.833333325511866
(g) 45.17675143564247

SOA Question 2.1


You are given:
1
• 𝑆0(𝑡) = (1 − 𝜔𝑡 ) 4 , 0≤𝑡≤𝜔
1
• 𝜇65 = 180

Calculate 𝑒106 , the curtate expectation of life at age 106.

print("SOA Question 2.1: (B) 2.5")


def fun(omega): # Solve first for omega, given mu_65 = 1/180
return Lifetime().set_survival(l=lambda x,s: (1 - (x+s)/omega)**0.25,
maxage=omega)\
.mu_x(65)
omega = int([Link](fun, target=1/180, grid=100)) # solve for omega
e = Lifetime().set_survival(l=lambda x,s: (1 - (x+s)/omega)**0.25,
maxage=omega).e_x(106)
print(e)

30 Chapter 5. Expected Future Lifetimes


Solving Actuarial Math with Python

SOA Question 2.1: (B) 2.5


2.4786080555423604

SOA Question 2.4


𝑡2 ∘
You are given 𝑡 𝑞0 = 10,000 0 < 𝑡 < 100. Calculate 𝑒75∶10| .

print("SOA Question 2.4: (E) 8.2")


def q(t) : return (t**2)/10000. if t < 100 else 1.
e = Lifetime().set_survival(l=lambda x,s: 1 - q(x+s))\
.e_x(75, t=10, curtate=False)
print(e)

SOA Question 2.4: (E) 8.2


8.20952380952381

5.5. Examples 31
Solving Actuarial Math with Python

32 Chapter 5. Expected Future Lifetimes


CHAPTER

SIX

FRACTIONAL AGES

Given values of 𝑙𝑥 at integer ages only, we need to make some assumption about the probability distribution for the
future lifetime random variable between integer ages, in order to calculate survival function values for non-integer ages
or durations. Such fractional age assumptions may be specified in terms of the force of mortality function (e.g. constant)
or the survival or mortality probabilities (e.g. uniform distribution of deaths).

6.1 Uniform distribution of deaths

This is the simplest assumption, which linearly interpolates the number of lives between integer ages.
𝑇𝑥 = 𝐾𝑥 + 𝑅𝑥
• The UDD assumptions define can be modeled by a new random variable 𝑅𝑥 ∼ 𝑈 (0, 1) which is independent of
curtate lifetime 𝐾𝑥 .

𝑟 𝑞𝑥 = 𝑟 𝑞𝑥 , for integer 𝑥 and 0 ≤ 𝑟 < 1


• is an equivalent way of formulating the UDD assumption
𝑙𝑥+𝑟 = (1 − 𝑟) 𝑙𝑥 + 𝑟 𝑙𝑥+1 = 𝑙𝑥 − 𝑟 𝑑𝑥
• linear interpolation of lives between integer ages
𝑟 𝑞𝑥
𝑟 𝑞𝑥+𝑠 = , for 0 ≤ 𝑠 + 𝑟 < 0
1 − 𝑠 𝑞𝑥
• mortality rate at a fractional age over a fractional duration, under UDD
1
𝜇𝑥+𝑟 =
1 − 𝑟 𝑞𝑥
• applying the UDD approximation over successive ages implies a discontinuous function for the force of mortality,
with discontinuities occurring at integer ages.
𝑓𝑥 (𝑟) = 𝑟 𝑝𝑥 𝜇𝑥+𝑟 = 𝑞𝑥
• lifetime density is constant between integer ages, which also follows from the UDD assumption for 𝑅𝑥 .
∘ 1 1
𝑒𝑥∶1| = 1 − 𝑞𝑥 = 𝑞 𝑥 + 𝑝𝑥
2 2
• 1-year limited complete expectation
∘ 𝑟
𝑒𝑥∶𝑟| = 𝑟 𝑞𝑥 + 𝑟 𝑝𝑥 𝑟
2
• fractional year limited complete expectation
∘ 1 ∘ ∘ ∘
𝑒𝑥 = 𝑞 𝑥 + 𝑝𝑥 (1 + 𝑒𝑥+1 ) = 𝑒𝑥∶1| + 𝑝𝑥 𝑒𝑥+1
2

33
Solving Actuarial Math with Python

• recursive for complete expectation of life: UDD-specific and general formulas



𝑒𝑥 = 𝑒𝑥 + 0.5
• since 𝐸[𝑅𝑥 ] = 0.5
1
𝑉 𝑎𝑟(𝑇𝑥 ) = 𝑉 𝑎𝑟(𝐾𝑥 ) +
12
1
• since 𝑉 𝑎𝑟[𝑅𝑥 ] =
12

𝑒𝑥∶𝑛| = 𝑒𝑥∶𝑛| + 0.5 𝑛 𝑞𝑥
• These exact results under UDD are often used generally as approximations between complete and curtate expec-
tations.

6.2 Constant force of mortality

Under this assumption, the force of mortality is set to be constant between integer ages
𝜇𝑥+𝑟 = 𝜇𝑥 = − ln 𝑝𝑥 , for 0 ≤ 𝑟 < 1
• which leads to a step function for the force of mortality over successive years of age
𝑙𝑥+𝑟 = (𝑙𝑥 )1−𝑟 ⋅ (𝑙𝑥+1 )𝑟
• constant force of mortality is equivalent to exponential interpolation of lives between integer ages

𝑟 𝑝𝑥 = 𝑒−𝜇𝑥 𝑟 = (𝑝𝑥 )𝑟
1
• since 𝑝𝑥 = 𝑒− ∫0 𝜇𝑥+𝑢 𝑑𝑢
= 𝑒−𝜇𝑥
𝑟

𝑟 𝑝𝑥+𝑠 = 𝑒− ∫0 𝜇𝑥+𝑠+𝑢 𝑑𝑢
= (𝑝𝑥 )𝑟 , for 0 ≤ 𝑟 + 𝑠 < 1
• the probability of surviving for period of 𝑟 from age 𝑥 + 𝑠 is independent of 𝑠 under constant force of mortality
𝑓𝑥 (𝑟) = 𝑟 𝑝𝑥 𝜇𝑥+𝑟 = 𝑒−𝜇𝑥 𝑟 ⋅ 𝜇𝑥 , for 0 ≤ 𝑟 < 1
• lifetime density function at fractional age using constant force of mortality assumption
∘ 1 − 𝑒−𝜇𝑥 𝑟
𝑒𝑥∶𝑟| =
𝜇
• fractional year limited complete expectation

6.3 Methods

The Fractional class implements methods to compute survival and mortality functions between integer ages, assuming
either uniform distribution of deaths or constant force of mortality

from actuarialmath import Fractional


import describe
[Link](Fractional)

34 Chapter 6. Fractional Ages


Solving Actuarial Math with Python

class Fractional - Compute survival functions at fractional ages and durations

Args:
udd : select UDD (True, default) or CFM (False) between integer ages

Methods:
--------

l_r(x, s, r):
Number of lives at fractional age: l_[x]+s+r

p_r(x, s, r, t):
Probability of survival from and through fractional age: t_p_[x]+s+r

q_r(x, s, r, t, u):
Deferred mortality rate within fractional ages: u|t_q_[x]+s+r

mu_r(x, s, r):
Force of mortality at fractional age: mu_[x]+s+r

f_r(x, s, r, t):
mortality function at fractional age: f_[x]+s+r (t)

E_r(x, s, r, t):
Pure endowment at fractional age: t_E_[x]+s+r

e_r(x, s, t):
Temporary expected future lifetime at fractional age: e_[x]+s:t

e_approximate(e_complete, e_curtate, variance):


Convert between curtate and complete expectations assuming UDD shortcut

6.4 Examples

The Fractional class can be initialized to apply the uniform distribution or constant force of mortality assumptions
at fractional ages (by setting the udd argument to be either True or False respectively), while using any other survival
model for integer ages. Then survival function values at fractional ages can be computed by the methods l_r, p_r, q_r,
mu_r, f_r and E_r (for the number of lives, survival probability, mortality rate, force of mortality, lifetime density and
pure endowment functions respectively).

cfm = Fractional(udd=False).set_survival(l=lambda x,t: 50-x-t)


udd = Fractional(udd=True).set_survival(l=lambda x,t: 50-x-t)
print('Compare: Constant Force of Mortality vs UDD assumptions')
print(' --------------------------- -----')
print('mortality rate ', cfm.q_r(40, t=0.5), udd.q_r(40, t=0.5))
print('force of mortality', cfm.mu_r(40, r=0.5), udd.mu_r(40, r=0.5))
print('lifetime density ', cfm.f_r(40, r=0.5), udd.f_r(40, r=0.5))

Compare: Constant Force of Mortality vs UDD assumptions


--------------------------- -----
mortality rate 0.05131670194948623 0.04999999999999999
force of mortality 0.10536051565782628 0.10526315789473682
lifetime density 0.10536051565782628 0.09999999999999998

6.4. Examples 35
Solving Actuarial Math with Python

When using constant force of maturity as the fractional age assumption, the force of maturity may show discontinuous
jumps at integer ages

import numpy as np
import [Link] as plt
cfm = Fractional(udd=False).set_survival(l=lambda x,s: 100 - (x+s))
udd = Fractional(udd=True).set_survival(l=lambda x,s: 100 - (x+s))
x = [Link](0, 3, 0.01)
[Link](x, [cfm.mu_r(x=0, r=r) for r in x])
[Link](x, [udd.mu_r(x=0, r=r) for r in x])
[Link]([' Constant Force of Maturity', 'Uniform Distribution'])
[Link]('force of mortality $\mu$')
[Link]('Age')
[Link]('Fractional Age Assumptions and Force of Mortality')

Text(0.5, 1.0, 'Fractional Age Assumptions and Force of Mortality')

The uniform distribution assumption for fractional ages linearly interpolates the number of lives between integer ages,
whereas the constant force of mortality asssumption uses exponential interpolation.

udd = Fractional(udd=True).set_survival(mu=lambda x,s: 0.8)


cfm = Fractional(udd=False).set_survival(mu=lambda x,s: 0.8)
[Link](x, [udd.l_r(x=0, r=r) for r in x])
[Link](x, [cfm.l_r(x=0, r=r) for r in x])
[Link](['Uniform Distribution', 'Constant Force of Maturity'])
[Link]('Number of lives')
(continues on next page)

36 Chapter 6. Fractional Ages


Solving Actuarial Math with Python

(continued from previous page)


[Link]('Age')
[Link]('Fractional Age Assumptions and Interpolation of Lives')

Text(0.5, 1.0, 'Fractional Age Assumptions and Interpolation of Lives')

The complete future lifetime for fractional limited durations starting from integer ages is computed by the e_r class
method. Under the hood, when the duration is one year or less, then shortcuts are available for both the uniform distri-
bution and constant force of maturity cases; when the limited duration is more than one year, then the general one-year
recursion formula is repeatedly applied until the last period of length less than or equal to one year is reached.

import numpy as np
x = [0.5, 1., 1.5, 2.]
udd = Fractional(udd=False).set_survival(l=lambda x,s: 50 - (x+s), maxage=50)
cfm = Fractional(udd=True).set_survival(mu=lambda x,s: 0.03)
print('UDD: ', [round(udd.e_r(x=0, t=t), 4) for t in x])
print('CFM: ', [round(cfm.e_r(x=0, t=t), 4) for t in x])

UDD: [0.4975, 0.99, 1.4774, 1.9599]


CFM: [0.4963, 0.9852, 1.4669, 1.9413]

The exact results under the assumption of uniform distribution of deaths are often used to approximate between curtate
and complete expectations, and can be computed by the e_approximate static method

6.4. Examples 37
Solving Actuarial Math with Python

print(Fractional.e_approximate(e_complete=15),
Fractional.e_approximate(e_complete=200, variance=True))
print(Fractional.e_approximate(e_curtate=15),
Fractional.e_approximate(e_curtate=200, variance=True))

14.5 199.91666666666666
15.5 200.08333333333334

38 Chapter 6. Fractional Ages


CHAPTER

SEVEN

INSURANCE

For a life insurance policy, the time at which the benefit will be paid is unknown until the policyholder actually dies and
the policy becomes a claim. In this section, formulas are developed for the valuation of traditional insurance benefits
(such as whole life, term and endowment insurance) and their second moments, as well as varying insurances.

7.1 Present value of life insurance r.v. 𝑍

Valuation functions for the present value of insurance benefits, denoted by 𝑍, are based on the continuous future lifetime
random variable, 𝑇𝑥 , or the curtate future lifetime random variable, 𝐾𝑥 . The interest rate is generally assumed to be
fixed and constant. Given a survival model and an interest rate we can derive the distribution of the present value random
variable for a life contingent benefit, and can therefore compute quantities such as the mean and variance of the present
value. Valuation functions are developed per dollar of sum assured, which can be multiplied by the actual sum insured for
different benefit amounts. The expected present value of insurance benefit, denoted and solved by 𝐸𝑃 𝑉 (𝑍), is sometimes
referred to as the actuarial value, actuarial present valuen (APV), or single net premium. Later sections will review special
mortality law assumptions where EPV’s can be computed without the need for integration.

7.2 Whole life insurance

Whole life insurance pays a benefit whenver (𝑥) dies.


𝑍 = 𝑣𝑇𝑥
• continuous insurance, where the benefit of $1 is payable immediately on death

𝐴𝑥 = 𝐸[𝑣𝑇𝑥 ] = ∫𝑡=0 𝑣𝑡 𝑡 𝑝𝑥 𝜇𝑥+𝑡 𝑑𝑡
• EPV continuous whole life insurance that pays benefit of $1 immediately on death
𝑍 = 𝑣𝐾𝑥 +1
• annual insurance, where benefit of $1 is payable at end of year of death

𝐴𝑥 = 𝐸[𝑣𝐾𝑥 +1 ] = ∑𝑘=0 𝑣𝑘+1 𝑘| 𝑞𝑥
• EPV of annual whole life insurance that pays benefit of $1 at end of year on death

39
Solving Actuarial Math with Python

7.3 Term insurance

A term life insurace for 𝑡 years pays the benefit if death occurs within 𝑡 years, 0 otherwise.
𝑍 = 0 when 𝑇𝑥 > 𝑡, else 𝑣𝑇𝑥
• death benefit is payable immediately only if the policy-holder dies within 𝑡 years
1 𝑡
𝐴𝑥∶𝑡| = ∫𝑠=0 𝑣𝑠 𝑠 𝑝𝑥 𝜇𝑥+𝑠 𝑑𝑠 = 𝐴𝑥 − 𝑡 𝐸𝑥 𝐴𝑥+𝑡
• continous term insurance as the difference of continous whole life and deferred continuous whole life
𝑍 = 0 when 𝐾𝑥 ≥ 𝑡, else 𝑣𝐾𝑥 +1
• death benefit is payable at the end of the year of death provided this occurs within 𝑡 years
𝑡−1
𝐴1𝑥∶𝑡| = ∑𝑘=0 𝑣𝑘+1 𝑘| 𝑞𝑥 = 𝐴𝑥 − 𝑡 𝐸𝑥 𝐴𝑥+𝑡

• annual term insurance as the difference of annual whole life and deferred annual whole life

7.4 Deferred insurance

Does not begin to offer death benefit cover until the end of a deferred period
𝑍 = 0 when 𝑇𝑥 < 𝑢, else 𝑣𝑇𝑥
• benefit is payable immediately on the death of (x) provided that (x) dies after the age of 𝑥 + 𝑢

𝑢| 𝐴𝑥∶𝑡| = 𝑢 𝐸𝑥 𝐴𝑥+𝑢∶𝑡|
• continuous deferred insurance as the EPV of whole life insurance starting at the end of deferred period.
𝑍 = 0 when 𝐾𝑥 < 𝑢, else 𝑣𝐾𝑥 +1
• annual deferred insurance where the death benefit is payable at the end of the year of death

𝑢| 𝐴𝑥∶𝑡| = 𝑢 𝐸𝑥 𝐴𝑥+𝑢∶𝑡|
• annual deferred insurance as the EPV of whole life insurance starting at the end of deferred period.

7.5 Endowment insurance

An endowment insurance provides a combination of a term insurance and a pure endowment.


𝑍 = 𝑣𝑡 when 𝑇𝑥 ≥ 𝑡, else 𝑣𝑇𝑥
• benefit is payable on the death of (x) should (x) die within 𝑡 years, but if (x) survives for 𝑡 years, the sum insured
is payable at the end of the 𝑡-th year.
1
𝐴𝑥∶𝑡| = 𝐴𝑥∶𝑡| + 𝑡 𝐸𝑥
• continuous endowment insurance as continuous term insurance plus a pure endowment
𝑍 = 𝑣𝑡 when 𝐾𝑥 ≥ 𝑡, else 𝑣𝐾𝑥 +1
• annual endowment insurance where the death benefit is payable at the end of the year of death
1
𝐴𝑥∶𝑡| = 𝐴𝑥∶𝑡| + 𝑡 𝐸𝑥

• annual endowment insurance as annual term insurance plus a pure endowment

40 Chapter 7. Insurance
Solving Actuarial Math with Python

7.6 Pure endowment

𝑍 = 0 when 𝑇𝑥 < 𝑡, else 𝑍 = 𝑣𝑡


• benefit payable in 𝑡 years if (x) is still alive at that time, but pays nothing if (x) dies before then.

𝑛 𝐸𝑥 = 𝐴𝑥∶𝑛|1 = 𝑣𝑛 𝑛 𝑝𝑥

• because the pure endowment will be paid only at time 𝑡, assuming the life survives, there is only the discrete time
version

7.7 Variances

The variance of life insurance benefits is computed as the difference of the second moment 𝐸[𝑍 2 ] and square of the first
moment 𝐸[𝑍]2 . By the rule of momenets: 𝐸[𝑍 𝑛 ] at force of interest 𝛿 equals 𝐸[𝑍] at force of interest 𝑛𝛿 with the benefit
amount also raised to the 𝑛-th power.

𝐸[𝑍 𝑛 ] = ∫𝑡=0 𝑏𝑡𝑛 𝑣𝑡𝑛 𝑡 𝑝𝑥 𝜇𝑥+𝑡 𝑑𝑡
• when calculating with moments, the benefit and discount factor are raised to the 𝑛-th power.
2 ∞
𝐴𝑥 = ∫𝑡=0 𝑣2𝑡 𝑡 𝑝𝑥 𝜇𝑥+𝑡 𝑑𝑡

• second moment of continuous insurance equal to 𝐴𝑥 at double the force of interest.


2 ∞
𝐴𝑥 = ∑𝑘=0 𝑣2(𝑘+1) 𝑘| 𝑞𝑥
• second moment of annual insurance is equal to 𝐴𝑥 at double the force of interest.
Whole Life insurance:
𝑉 𝑎𝑟(𝐴𝑥 ) = 2 𝐴𝑥 − (𝐴𝑥 )2
• variance of continuous whole life insurance
𝑉 𝑎𝑟(𝐴𝑥 ) = 2 𝐴𝑥 − (𝐴𝑥 )2
• variance of annual whole life insurance
Term Life insurance:
1 1 1
𝑉 𝑎𝑟(𝐴𝑥∶𝑡| ) = 2 𝐴𝑥∶𝑡| − (𝐴𝑥∶𝑡| )2
• variance of continuous term life insurance
𝑉 𝑎𝑟(𝐴1𝑥∶𝑡| ) = 2 𝐴1𝑥∶𝑡| − (𝐴1𝑥∶𝑡| )2

• variance of annual term life insurance


Deferred Life insurance:
𝑉 𝑎𝑟(𝑢| 𝐴𝑥∶𝑡| ) = 2𝑢| 𝐴𝑥∶𝑡| − (𝑢| 𝐴𝑥∶𝑡| )2
• variance of continuous deferred life insurance
𝑉 𝑎𝑟(𝑢| 𝐴𝑥∶𝑡| ) = 2𝑢| 𝐴𝑥∶𝑡| − (𝑢| 𝐴𝑥∶𝑡| )2
• variance of annual deferred life insurance
Endowment insurance:
1 1
𝑉 𝑎𝑟(𝐴𝑥∶𝑡| ) = 2 𝐴𝑥∶𝑡| − (𝐴𝑥∶𝑡| )2 = 𝑉 𝑎𝑟(𝐴𝑥∶𝑡| ) + 𝑉 𝑎𝑟(𝑡 𝐸𝑥 ) − 𝐶𝑜𝑣(𝐴𝑥∶𝑡| , 𝑡 𝐸𝑥 )

7.6. Pure endowment 41


Solving Actuarial Math with Python

• variance of continous endowment insurance may also be expressed on the sum of a term insurance and pure en-
1 1
dowment, which are mutually exclusive hence 𝐶𝑜𝑣(𝐴𝑥∶𝑡| , 𝑡 𝐸𝑥 ) = 𝐸[𝐴𝑥∶𝑡| ]𝐸[𝑡 𝐸𝑥 ]
𝑉 𝑎𝑟(𝐴𝑥∶𝑡| ) = 2 𝐴𝑥∶𝑡| − (𝐴𝑥∶𝑡| )2 = 𝑉 𝑎𝑟(𝐴1𝑥∶𝑡| ) + 𝑉 𝑎𝑟(𝑡 𝐸𝑥 ) − 𝐶𝑜𝑣(𝐴1𝑥∶𝑡| , 𝑡 𝐸𝑥 )

• variance of annual endowment insurance


Pure Endowment:
2
𝑡 𝐸𝑥 = 𝑣2𝑡 𝑡 𝑝𝑥 = 𝑣𝑡 𝑡 𝐸𝑥
• second moment of pure endowment by discounting 𝑡 𝐸𝑥
𝑉 𝑎𝑟( 𝑡 𝐸𝑥 ) = 𝑣2𝑡 𝑡 𝑝𝑥 𝑡 𝑞𝑥 = 𝑣2𝑡 𝑡 𝑝𝑥 − (𝑣𝑡 𝑡 𝑝𝑥 )2
• variance of pure endowment is the variance of a Bernoulli random variable

7.8 Varying insurance

Increasing insurance:
Amount of death benefit increases arithmetically at a rate of $1 per year.

(𝐼𝐴)𝑥 = ∫𝑡=0 𝑡 𝑣𝑡 𝑡 𝑝𝑥 𝜇𝑥+𝑡 𝑑𝑡
• increasing continuous whole life insurance

(𝐼𝐴)𝑥 = ∑𝑘=0 (𝑘 + 1) 𝑣𝑘+1 𝑘| 𝑞𝑥
• increasing annual whole life insurance
𝑡
(𝐼𝐴)1𝑥∶𝑡| = ∫𝑠=0 𝑠 𝑣𝑠 𝑠 𝑝𝑥 𝜇𝑥+𝑠 𝑑𝑠

• increasing continuous term insurance


𝑡−1
(𝐼𝐴)1𝑥∶𝑡| = ∑𝑘=0 (𝑘 + 1) 𝑣𝑘+1 𝑘| 𝑞𝑥

• increasing annual term insurance


Decreasing insurance:
Amount of death benefit increasing arithmetically at a rate of $1 per year.
𝑡
(𝐷𝐴)1𝑥∶𝑡| = ∫𝑠=0 (𝑡 − 𝑠) 𝑣𝑠 𝑠 𝑝𝑥 𝜇𝑥+𝑠 𝑑𝑠

• decreasing continuous term insurance (not defined for whole life)


𝑡−1
(𝐷𝐴)1𝑥∶𝑡| = ∑𝑘=0 (𝑡 − 𝑘) 𝑣𝑘+1 𝑘| 𝑞𝑥

• decreasing annual term insurance (not defined for whole life)


Identity relationship:
1
(𝐷𝐴)1𝑥∶𝑡| + (𝐼𝐴)1𝑥∶𝑡| = 𝑡 𝐴𝑥∶𝑡|

• relates continuous increasing and decreasing insurances to level insurance


(𝐷𝐴)1𝑥∶𝑡| + (𝐼𝐴)1𝑥∶𝑡| = (𝑡 + 1) 𝐴1𝑥∶𝑡|

• relates annual increasing and decreasing insurances to level insurance

42 Chapter 7. Insurance
Solving Actuarial Math with Python

7.9 Probabilities and percentiles

The larger 𝑇 , the lower the present value of the payment and the random variable 𝑍. For a level benefit whole life
insurance, the 100𝜋-th percentile of the actuarial present value is determined by the 100(1 − 𝜋)-th percentile of the
lifetime random variable.
Percentile of 𝑍:
𝑡𝜋 = 𝑆 −1 (𝜋)
• the 100(1 − 𝑝)-th percentile of the lifetime variable for WL insurance can be obtained by looking up the inverse
− ln 𝜋
of the survival function at probability 𝑝; e.g. if 𝑡 𝑝𝑥 = 𝑒−𝜇𝑡 , then 𝑡𝜋 =
𝜇
𝑍𝜋 = 𝑏𝜋 𝑣𝑡𝜋
• the PV of the insurance r.v. that pays benefit 𝑏𝑡𝜋 at time 𝑡𝜋
Probability of 𝑍:
To determine the probability that the present value of an annuity is greater than some percentile value 𝑃 𝑟(𝑍 ≥ 𝑧𝜋 ), it
is necessary to plot Z as a function of 𝑡, then identify those time periods where 𝑍 ≥ 𝑧𝜋 and compute the cumulative
probability of 𝑇 being in those time periods.

7.10 Methods

The Insurance class implements methods to compute the expected present value of life insurance

import math
import numpy as np
import [Link] as plt
from actuarialmath import Insurance
import describe
[Link](Insurance)

class Insurance - Compute expected present values of life insurance

Methods:
--------

E_x(x, s, t, endowment, moment):


Pure endowment: t_E_x

A_x(x, s, t, u, benefit, endowment, moment, discrete):


Numerically compute EPV of insurance from basic survival functions

insurance_variance(A2, A1, b):


Compute variance of insurance given moments and benefit

whole_life_insurance(x, s, moment, b, discrete):


Whole life insurance: A_x

term_insurance(x, s, t, b, moment, discrete):


Term life insurance: A_x:t^1

deferred_insurance(x, s, u, t, b, moment, discrete):


(continues on next page)

7.9. Probabilities and percentiles 43


Solving Actuarial Math with Python

(continued from previous page)


Deferred insurance n|_A_x:t^1 = discounted term or whole life

endowment_insurance(x, s, t, b, endowment, moment, discrete):


Endowment insurance: A_x^1:t = term insurance + pure endowment

increasing_insurance(x, s, t, b, discrete):
Increasing life insurance: (IA)_x

decreasing_insurance(x, s, t, b, discrete):
Decreasing life insurance: (DA)_x

Z_x(x, s, t, discrete):
EPV of year t insurance death benefit for life aged [x]+s: b_x[s]+s(t)

Z_t(x, prob, discrete):


T_x, given the prob of the PV of life insurance, i.e. r.v. Z(t)

Z_from_t(t, discrete):
PV of annual or continuous insurance payment Z(t) at t=T_x

Z_from_prob(x, prob, discrete):


Percentile of annual or continuous WL insurance PV r.v. Z given probability

Z_to_t(Z):
T_x s.t. PV of continuous WL insurance payment is Z

Z_to_prob(x, Z):
Probability that continuous WL insurance PV r.v. is no more than Z

Z_plot(x, s, stop, benefit, T, discrete, ax, dual, title, color, alpha):


Plot of PV of insurance r.v. Z vs t

7.11 Examples

Given a survival model, the A_x class method computes the EPV for arbitrary life insurance benefits, endowments,
discount functions, terms and moments using numerical integration or summation.

life = Insurance().set_interest(delta=0.06)\
.set_survival(mu=lambda *x: 0.04)
benefit = lambda x,t: [Link](0.02 * t)
A1 = life.A_x(0, benefit=benefit, discrete=False)
A2 = life.A_x(0, moment=2, benefit=benefit, discrete=False)
var = A2 - A1**2 # compute variance of this life insurance
print(var) # 0.0833

0.08333333333333331

But typically, the common life insurance EPV’s are computed by calling the class methods
whole_life_insurance, term_insurance, deferred_insurance, endowment_insurance,
increasing_insurance, decreasing insurance, or E_x (i.e. pure endowment). It is observed in the
example below that term insurance can be equivalently computed as the difference of endowment insurance and a pure
endowment, or as the difference of whole life and deferred insurance.

44 Chapter 7. Insurance
Solving Actuarial Math with Python

def S(x, s, t): # define a survival function


return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = Insurance().set_survival(S=S, minage=20, maxage=130)\
.set_interest(i=0.05)
x, t = 50, 10
print(f"{t}-year endowment insurance for ({x}): ", life.endowment_insurance(x, t=t))
print(f"{t}-year pure endowment for ({x}): ", life.E_x(x, t=t))
print(f"{t}-year term insurance for ({x}): ", life.term_insurance(x, t=t))
print(f"{t}-year deferred insurance for ({x}): ", life.deferred_insurance(x, u=t))
print(f"whole life insurance for ({x}): ", life.whole_life_insurance(x))
print(f"{t}-year increasing insurance for ({x}):", life.increasing_insurance(x, t=t))
print(f"{t}-year decreasing insurance for ({x}):", life.decreasing_insurance(x, t=t))

10-year endowment insurance for (50): 0.6164284147269636


10-year pure endowment for (50): 0.6018174267001221
10-year term insurance for (50): 0.014610988026841604
10-year deferred insurance for (50): 0.1746968722738868
whole life insurance for (50): 0.1893078603007284
10-year increasing insurance for (50): 0.08665589675401192
10-year decreasing insurance for (50): 0.07406497154124572

The Z_t class method returns the 100𝑝-th percentile of the survival function, then Z_from_t can be called to discount
the benefit paid at that time. The Z_from_prob class method performs both steps with one call. Z_plot plots the
insurance present value r.v. 𝑍 as a function of time 𝑡.

def S(x, s, t): # define a survival function


return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = Insurance().set_survival(S=S, minage=20, maxage=130).set_interest(i=0.05)
P = 0.75 # to find the 75-th percentile of Z
T = life.Z_t(x=20, prob=P, discrete=False) # corresponding percentile of lifetime
life.Z_plot(x=20, stop=80, T=T, discrete=False) # plot Z vs t, and annotate at time␣
↪T

print('T:', T) # call methods and compare results


print('p:', P, life.p_x(x=20, t=T))
print('Z:', life.Z_from_prob(x=20, prob=P, discrete=False), life.Z_from_t(t=T,␣
↪discrete=False))

T: 60.274191128246535
p: 0.75 0.7500000000000304
Z: 0.05282410383956641 0.05282410383956641

7.11. Examples 45
Solving Actuarial Math with Python

By setting the dual argument to True, the survival probability of (x) can be simultaneously plotted to annotate, say, the
median survival lifetime.

def S(x, s, t): # define a survival function


return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = Insurance().set_survival(S=S, minage=20, maxage=130).set_interest(i=0.05)
T = life.Z_t(x=20, prob=0.5, discrete=False) # compute the median lifetime
K = life.Z_t(x=20, prob=0.5, discrete=True) # plot using discrete approximation
life.Z_plot(x=20, stop=80, T=K, discrete=True, dual=True, title="median Z(K)")
print(f"S(K={K:.4f}):", life.p_x(x=20, t=K)) # survival function at integer age
print(f"S(T={T:.4f}):", life.p_r(x=20, t=T)) # survival function at fractional age

S(K=68.0000): 0.5003864946350018
S(T=68.0097): 0.49999343308443683

46 Chapter 7. Insurance
Solving Actuarial Math with Python

SOA Question 4.18


You are given that T, the time to first failure of an industrial robot, has a density f(t) given by
𝑓(𝑡) = 0.1, 0≤𝑡<2
−2
= 0.4𝑡 , 𝑡 ≤ 𝑡 < 10
with 𝑓(𝑡) undetermined on [10, ∞).
Consider a supplemental warranty on this robot that pays 100,000 at the time T of its first failure if 2 ≤ 𝑇 ≤ 10 , with
no benefits payable otherwise. You are also given that 𝛿 = 5%. Calculate the 90th percentile of the present value of the
future benefits under this warranty.

print("SOA Question 4.18 (A) 81873 ")


def f(x,s,t): return 0.1 if t < 2 else 0.4*t**(-2)
life = Insurance().set_interest(delta=0.05)\
.set_survival(f=f, maxage=10)
def benefit(x,t):
return 0 if t < 2 else 100000
life.Z_plot(x=0, stop=10, benefit=benefit, T=2, dual=True, discrete=False,
title='benefits begin at T=2')
prob = 0.9 - life.q_x(x=0, t=2)
T = life.Z_t(x=0, prob=prob)
Z = life.Z_from_t(T, discrete=False) * benefit(0, T)
print(T, Z)

SOA Question 4.18 (A) 81873


4 81873.07530779815

7.11. Examples 47
Solving Actuarial Math with Python

SOA Question 4.10


The present value random variable for an insurance policy on (x) is expressed as:

𝑍 = 0, if 𝑇𝑥 ≤ 10
= 𝑣𝑇 , if 10 < 𝑇𝑥 ≤ 20
𝑇
= 2𝑣 , if 20 < 𝑇𝑥 ≤ 30
= 0, thereafter

Determine which of the following is a correct expression for 𝐸[𝑍].


(A) 10| 𝐴𝑥 +20| 𝐴𝑥 −30| 𝐴𝑥

(B) 𝐴𝑥 +20 𝐸𝑥 𝐴𝑥+20 − 2 30 𝐸𝑥 𝐴𝑥+30


(C) 10 𝐸𝑥 𝐴𝑥 +20 𝐸𝑥 𝐴𝑥+20 − 2 30 𝐸𝑥 𝐴𝑥+30
(D) 10 𝐸𝑥 𝐴𝑥+10 +20 𝐸𝑥 𝐴𝑥+20 − 2 30 𝐸𝑥 𝐴𝑥+30
(E) 10 𝐸𝑥 [𝐴𝑥 +10 𝐸𝑥+10 + 𝐴𝑥+20 −10 𝐸𝑥+20 + 𝐴𝑥+30 ]

print("SOA Question 4.10: (D)")


life = Insurance().set_interest(i=0)\
.set_survival(S=lambda x,s,t: 1, maxage=40)
def fun(x, t): # given target
if 10 <= t <= 20: return [Link].v_t(t)
elif 20 < t <= 30: return 2 * [Link].v_t(t)
else: return 0
def A(x, t): # Z_x+k (t-k)
(continues on next page)

48 Chapter 7. Insurance
Solving Actuarial Math with Python

(continued from previous page)


return [Link].v_t(t - x) * (t > x)
x = 0
benefits=[lambda x,t: (life.E_x(x, t=10) * A(x+10, t)
+ life.E_x(x, t=20)* A(x+20, t)
- life.E_x(x, t=30) * A(x+30, t)),
lambda x,t: (A(x, t)
+ life.E_x(x, t=20) * A(x+20, t)
- 2 * life.E_x(x, t=30) * A(x+30, t)),
lambda x,t: (life.E_x(x, t=10) * A(x, t)
+ life.E_x(x, t=20) * A(x+20, t)
- 2 * life.E_x(x, t=30) * A(x+30, t)),
lambda x,t: (life.E_x(x, t=10) * A(x+10, t)
+ life.E_x(x, t=20) * A(x+20, t)
- 2 * life.E_x(x, t=30) * A(x+30, t)),
lambda x,t: (life.E_x(x, t=10)
* (A(x+10, t)
+ life.E_x(x+10, t=10) * A(x+20, t)
- life.E_x(x+20, t=10) * A(x+30, t)))]

fig, ax = [Link](3, 2) # plot shapes of the target and the five functions
ax = [Link]()
for i, b in enumerate([fun] + benefits):
life.Z_plot(0, benefit=b, ax=ax[i], color=f"C{i+1}", title='')
ax[i].legend(["(" + "abcde"[i-1] + ")" if i else "Z"])

z = [sum(abs(b(0, t) - fun(0, t)) for t in range(40)) for b in benefits]


print("ABCDE"[[Link](z)]) # output letter of the function most similar to target

SOA Question 4.10: (D)


D

7.11. Examples 49
Solving Actuarial Math with Python

SOA Question 4.12


For three fully discrete insurance products on the same (x), you are given:
• 𝑍1 is the present value random variable for a 20-year term insurance of 50
• 𝑍2 is the present value random variable for a 20-year deferred whole life insurance of 100
• 𝑍3 is the present value random variable for a whole life insurance of 100.
• 𝐸[𝑍1 ] = 1.65 and 𝐸[𝑍2 ] = 10.75
• 𝑉 𝑎𝑟(𝑍1 ) = 46.75 and 𝑉 𝑎𝑟(𝑍2 ) = 50.78
Calculate 𝑉 𝑎𝑟(𝑍3 ).

print("SOA Question 4.12: (C) 167")


cov = [Link](a=1.65, b=10.75, ab=0) # Z1 and Z2 nonoverlapping
var = [Link](a=2, b=1, var_a=46.75, var_b=50.78, cov_ab=cov)
print(var)

SOA Question 4.12: (C) 167


166.82999999999998

SOA Question 4.11


You are given:
• 𝑍1 is the present value random variable for an n-year term insurance of 1000 issued to (x)
• 𝑍2 is the present value random variable for an n-year endowment insurance of 1000 issued to (x)
• For both 𝑍1 and 𝑍2 the death benefit is payable at the end of the year of death

50 Chapter 7. Insurance
Solving Actuarial Math with Python

• 𝐸[𝑍1 ] = 528
• 𝑉 𝑎𝑟(𝑍2 ) = 15, 000
• 𝐴𝑥∶𝑛|1 = 0.209

• 2 𝐴𝑥∶𝑛|1 = 0.136

Calculate 𝑉 𝑎𝑟(𝑍1 ).

print("SOA Question 4.11: (A) 143385")


A1 = 528/1000 # E[Z1] term insurance
C1 = 0.209 # E[pure_endowment]
C2 = 0.136 # E[pure_endowment^2]
def fun(A2):
B1 = A1 + C1 # endowment = term + pure_endowment
B2 = A2 + C2 # double force of interest
return Insurance.insurance_variance(A2=B2, A1=B1)
A2 = [Link](fun, target=15000/(1000*1000), grid=[143400, 279300])
var = Insurance.insurance_variance(A2=A2, A1=A1, b=1000)
print(var)

SOA Question 4.11: (A) 143385


143384.99999999997

SOA Question 4.15


For a special whole life insurance on (x), you are given:
• Death benefits are payable at the moment of death
• The death benefit at time 𝑡 is 𝑏𝑡 = 𝑒0.02𝑡 , for 𝑡 ≥ 0
• 𝜇𝑥+𝑡 = 0.04, for 𝑡 ≥ 0
• 𝛿 = 0.06
• Z is the present value at issue random variable for this insurance.
Calculate 𝑉 𝑎𝑟(𝑍).

print("SOA Question 4.15 (E) 0.0833 ")


life = Insurance().set_survival(mu=lambda *x: 0.04)\
.set_interest(delta=0.06)
benefit = lambda x,t: [Link](0.02*t)
A1 = life.A_x(0, benefit=benefit, discrete=False)
A2 = life.A_x(0, moment=2, benefit=benefit, discrete=False)
var = A2 - A1**2
print(var)

SOA Question 4.15 (E) 0.0833


0.08333333333333331

SOA Question 4.4


For a special increasing whole life insurance on (40), payable at the moment of death, you are given:
• The death benefit at time t is 𝑏𝑡 = 1 + 0.2𝑡, 𝑡≥0
• The interest discount factor at time t is 𝑣(𝑡) = (1 + 0.2𝑡) − 2, 𝑡≥0

7.11. Examples 51
Solving Actuarial Math with Python

• 𝑡 𝑝40 𝜇40+𝑡 = 0.025 if 0 ≤ 𝑡 < 40, otherwise 0


• Z is the present value random variable for this insurance Calculate Var(Z).

print("SOA Question 4.4 (A) 0.036")


x = 40
life = Insurance().set_survival(f=lambda *x: 0.025, maxage=x+40)\
.set_interest(v_t=lambda t: (1 + .2*t)**(-2))
benefit = lambda x,t: 1 + .2 * t
A1 = life.A_x(x, benefit=benefit, discrete=False)
A2 = life.A_x(x, moment=2, benefit=benefit, discrete=False)
var = A2 - A1**2
print(var)

SOA Question 4.4 (A) 0.036


0.03567680106032681

52 Chapter 7. Insurance
CHAPTER

EIGHT

ANNUITIES

A life annuity is a regular sequence of payments as long as the annuitant is alive on the payment date.

8.1 Present value of life annuity r.v. 𝑌

Valuation functions for the present value of annuity benefits, denoted by Y, are based on the continuous future lifetime
random variable, 𝑇𝑥 , or the curtate future lifetime random variable, 𝐾𝑥 . The expected present value of annuity benefits
is denoted and solved by EPV(Y). A useful formula from interest theory is that for a 𝑇 -year continuous annuity-certain
1 − 𝑣𝑇 1 − 𝑣𝑇
and annual annuity-due – 𝑎𝑇 | = and 𝑎𝑇̈ | = – which are used to calculate the actuarial present value of
𝛿 𝑑
life annuities.

8.2 Whole life annuity

A whole life annuity pays a level benefit until the death of the annuitant
1 − 𝑣𝑇𝑥
𝑌 = 𝑎𝑇𝑥 | =
𝛿
• present value random variable for continuous life annuity that pays at a rate of 1 per year until the moment of death
𝑇𝑥

𝑎𝑥 = 𝐸𝑃 𝑉 [𝑎𝑇𝑥 | ] = ∫𝑡=0 𝑣𝑡 𝑡 𝑝𝑥 𝑑𝑡
• expected present value of continuous whole life annuity

𝑎𝑥 = 𝑒𝑥
• special case when interest rate is 0
1 − 𝑣𝐾𝑥 +1
𝑌 = 𝑎𝐾
̈ 𝑥 +1| =
𝑑
• present value random variable for annual life annuity due that pays 1 up to the beginning of the year of death

̈ 𝑥 +1| ] = ∑𝑘=0 𝑣𝑘 𝑘 𝑝𝑥
𝑎𝑥̈ = 𝐸𝑃 𝑉 [𝑎𝐾
• expected present value of annual whole life annuity due
𝑎𝑥̈ = 1 + 𝑒𝑥
• special case when interest rate is 0

53
Solving Actuarial Math with Python

8.3 Temporary annuity

A 𝑡-year temporary life year annuity pays a level benefit until the earlier of death and year 𝑡
𝑌 = 𝑎𝑡| when or 𝑇𝑥 > 𝑡, else 𝑌 = 𝑎𝑇𝑥 |
• present value random variable for continuous temporary life annuity that pays at a rate of 1 until the moment of
death, or 𝑡 if earlier
𝑡
𝑎𝑥∶𝑡| = ∫𝑠=0 𝑣𝑠 𝑠 𝑝𝑥 𝑑𝑠 = 𝑎𝑥 − 𝑡 𝐸𝑥 𝑎𝑥+𝑡
• expected present value of continuous temporary life annuity

𝑎𝑥∶𝑡| = 𝑒𝑥∶𝑡|
• when interest rate is 0
𝑌 = 𝑎𝑡|̈ when 𝐾𝑥 ≥ 𝑡, else 𝑌 = 𝑎𝐾
̈ 𝑥 +1|
• present value random variable for annual life annuity that pays 1 at the beginning of the year until the moment of
death, or 𝑡 if earlier.
𝑡−1
̈ = ∑𝑘=0 𝑣𝑘 𝑘| 𝑝𝑥 = 𝑎𝑥̈ − 𝑡 𝐸𝑥 𝑎𝑥+𝑡
𝑎𝑥∶𝑡| ̈
• expected present value of annual temporary life annuity due
𝑎𝑥∶𝑡|
̈ = 1 + 𝑒𝑥∶𝑡| − 𝑡 𝑝𝑥
• when interest rate is 0

8.4 Deferred whole life annuity

A 𝑛-year deferred life annuity pays a level benefit starting at time 𝑛 and continuing until death.

𝑢| 𝑎𝑥 = 𝑎𝑥 − 𝑎𝑥+𝑢
• continuous deferred life annuity as the difference of whole life annuities

𝑢| 𝑎𝑥
̈ = 𝑎𝑥̈ − 𝑎𝑥+𝑢
̈
• annual deferred annuity due as the difference of annual whole life annuities due

8.5 Certain and life annuity

A common feature of pension benefits is that the pension annuity is guaranteed to be paid for some period even if the
annuitant dies before the end of the period.
𝑌 = 𝑎𝑛| when 𝑇𝑥 ≤ 𝑛, else 𝑎𝑇𝑥 |
• present value random variable for continuous certain and life annuity
𝑎𝑥∶𝑛| = 𝑎𝑛| + 𝑛| 𝑎𝑥

• can be computed as a continuous temporary certain annuity plus a deferred continuous whole life annuity
𝑌 = 𝑎𝑛|
̈ when 𝐾𝑥 < 𝑛, else 𝑎𝐾
̈ 𝑥 +1|
• present value random variable for annual certain and life annuity due
𝑎𝑥∶𝑛|
̈ = 𝑎𝑛|
̈ + 𝑛| 𝑎𝑥
̈

54 Chapter 8. Annuities
Solving Actuarial Math with Python

• can be computed as an annual temporary certain annuity due, plus a deferred annual life annuity due

8.6 Life insurance twin

Whole and Temporary Life Annuities (and Whole Life and Endowment Insurance) ONLY:
1 − 𝐴𝑥
𝑎𝑥 =
𝛿
𝐴𝑥 = 1 − 𝛿 𝑎𝑥
• continuous whole life insurance twin for continuous whole life annuity
1 − 𝐴𝑥
𝑎𝑥̈ =
𝑑
𝐴𝑥 = 1 − 𝑑 𝑎𝑥̈
• annual whole life insurance twin for annual life annuity due
1 − 𝐴𝑥∶𝑡|
𝑎𝑥∶𝑡| =
𝛿
𝐴𝑥∶𝑡| = 1 − 𝛿 𝑎𝑥∶𝑡|
• continuous endowment insurance twin for continuous temporary life annuity
1 − 𝐴𝑥∶𝑡|
𝑎𝑥∶𝑡|
̈ =
𝑑
𝐴𝑥∶𝑡| = 1 − 𝑑 𝑎𝑥∶𝑡|
̈
• annual endowment insurance twin for annual temporary life annuity due

8.7 Variances

The second moment of a life annuity is not easy to compute directly. However, for whole life and temporary life annuities,
the variance can be calculated directly using the insurance twin equations.
Whole life annuity

2 1 − 2 𝐴𝑥
𝑎𝑥 =
2𝛿
2
𝐴𝑥 = 1 − (2𝛿) 2 𝑎𝑥
2
𝐴𝑥 − (𝐴𝑥 )2
𝑉 𝑎𝑟(𝑎𝑥 ) =
𝛿2
• from doubling the force of interest of continuous whole life insurance

2 1 − 2 𝐴𝑥
𝑎𝑥̈ =
2𝑑 − 𝑑2
2
𝐴𝑥 = 1 − (2𝑑 − 𝑑2 ) 2 𝑎𝑥̈
𝐴𝑥̈ − (𝐴𝑥 )2
2
𝑉 𝑎𝑟(𝑎𝑥̈ ) =
𝑑2
• from doubling the force of interest of annual whole life insurance

8.6. Life insurance twin 55


Solving Actuarial Math with Python

Temporary life annuity


1 − 2 𝐴𝑥∶𝑡|
2
𝑎𝑥∶𝑡| =
2𝛿
2
𝐴𝑥∶𝑡| = 1 − (2𝛿) 2 𝑎𝑥∶𝑡|
2
𝐴𝑥∶𝑡| − (𝐴𝑥∶𝑡| )2
𝑉 𝑎𝑟(𝑎𝑥∶𝑡| ) =
𝛿2
• from doubling the force of interest of continuous endowment insurance
1 − 2 𝐴𝑥∶𝑡|
2
𝑎𝑥∶𝑡|
̈ =
2𝑑 − 𝑑2
2
𝐴𝑥∶𝑡| = 1 − (2𝑑 − 𝑑2 ) 2 𝑎𝑥∶𝑡|
̈
2
𝐴𝑥∶𝑡| − (𝐴𝑥∶𝑡| )2
𝑉 𝑎𝑟(𝑎𝑥∶𝑡|
̈ )=
𝑑2
• from doubling the force of interest of annual endowment insurance

8.8 Immediate life annuity

Benefit is paid at the end of the year, as long as the annuitant is alive, and can be related to the value of an annual life
annuity due:
𝑎𝑥 = 𝑎𝑥̈ − 1
• whole life annuities
𝑎𝑥∶𝑡| = 𝑎𝑥∶𝑡|
̈ − 1 + 𝑡 𝐸𝑥
• temporary life annuities

8.9 Varying life annuities

Increasing annuity:
The amount of the annuity payment increases arithmetically with time.

(𝐼𝑎)𝑥 = ∫𝑡=0 𝑡 𝑣𝑡 𝑡 𝑝𝑥 𝑑𝑡
• EPV of continuously increasing whole life annuity

(𝐼 𝑎)̈ 𝑥 = ∑𝑘=0 (𝑘 + 1) 𝑣𝑘+1 𝑘 𝑝𝑥
• EPV of annual increasing whole life annuity due
𝑡
(𝐼𝑎)𝑥∶𝑡| = ∫𝑠=0 𝑠 𝑣𝑠 𝑠 𝑝𝑥 𝑑𝑠
• EPV of continuously increasing temporary life annuity
𝑡−1
(𝐼 𝑎)̈ 𝑥∶𝑡| = ∑𝑘=0 (𝑘 + 1) 𝑣𝑘+1 𝑘 𝑝𝑥
• EPV of annual increasing temporary life annuity due
Decreasing annuity:
The amount of the annuity payment decreases arithmetically with time. There are no decreasing whole life annuities.

56 Chapter 8. Annuities
Solving Actuarial Math with Python

𝑡
(𝐷𝑎)𝑥∶𝑡| = ∫𝑠=0 (𝑡 − 𝑠) 𝑣𝑠 𝑠 𝑝𝑥 𝑑𝑠
• EPV of continuously decreasing temporary life annuity (not defined for whole life)
𝑡−1
(𝐷𝑎)̈ 𝑥∶𝑡| = ∑𝑘=0 (𝑡 − 𝑘) 𝑣𝑘+1 𝑘 𝑝𝑥
• EPV of annual decreasing temporary life annuity due (not defined for whole life)
Identity relationships:
(𝐷𝑎)𝑥∶𝑡| + (𝐼𝑎)𝑥∶𝑡| = 𝑡 𝑎𝑥∶𝑡|
• the sum of continuous 𝑡-year decreasing and increasing life annuities is 𝑡 times a 𝑡-year temporary annuity
(𝐷𝑎)̈ 𝑥∶𝑡| + (𝐼 𝑎)̈ 𝑥∶𝑡| = (𝑡 + 1) 𝑎𝑥∶𝑡|
̈
• the sum of 𝑡-year annual decreasing and increasing life annuities due is 𝑡 + 1 times a 𝑡-year temporary annuity

8.10 Probabilities and percentiles

The value of annuity random variables always increases as a function of survival time. Hence calculating a percentile of
a life annuity simply requires calculating the percentile of 𝑇 , then calculating the value of a temporary annuity.
Percentile of 𝑌 :
𝑡𝜋 = 𝑆 −1 (1 − 𝜋)
• the 100𝑝-th percentile of the lifetime variable for whole life annuity can be obtained from looking up the inverse
of the survival function at probability 𝑝.
1 − 𝑣 𝑡𝜋
𝑌𝜋 = 𝑏 𝑎𝑡𝜋 | = 𝑏
𝛿
• the PV of the annuity r.v. that pays level benefit 𝑏 up to time 𝑡𝜋
Probability of 𝑌 :
Since annuity values monotonically increases with survaval time, to calculate he probability that the present value of an
annuity is greater than some value 𝑃 𝑟(𝑌 ≥ 𝑌𝜋 ), calculate the time 𝑡𝜋 for which 𝑎𝑡𝜋 | has the required value, then calculate
the probability that time of death 𝑇𝑥 is in that range.

8.11 Methods

The Annuity class implements methods to compute the expected present value of life annuities

from actuarialmath import Annuity


import math
import describe
[Link](Annuity)

class Annuity - Compute present values and relationships of life annuities

Methods:
--------

a_x(x, s, t, u, benefit, discrete):


Compute EPV of life annuity from survival function
(continues on next page)

8.10. Probabilities and percentiles 57


Solving Actuarial Math with Python

(continued from previous page)

immediate_annuity(x, s, t, b, variance):
Compute EPV of immediate life annuity

annuity_twin(A, discrete):
Returns annuity from its WL or Endowment Insurance twin"

insurance_twin(a, moment, discrete):


Returns WL or Endowment Insurance twin from annuity

annuity_variance(A2, A1, b, discrete):


Compute variance from WL or endowment insurance twin

whole_life_annuity(x, s, b, variance, discrete):


Whole life annuity: a_x

temporary_annuity(x, s, t, b, variance, discrete):


Temporary life annuity: a_x:t

deferred_annuity(x, s, u, t, b, discrete):
Deferred life annuity n|t_a_x = n+t_a_x - n_a_x

certain_life_annuity(x, s, u, t, b, discrete):
Certain and life annuity = certain + deferred

increasing_annuity(x, s, t, b, discrete):
Increasing annuity

decreasing_annuity(x, s, t, b, discrete):
Identity (Da)_x:n + (Ia)_x:n = (n+1) a_x:n temporary annuity

Y_x(x, s, t, discrete):
EPV of year t annuity benefit for life aged [x]+s: b_x[s]+s(t)

Y_t(x, prob, discrete):


T_x given percentile of the r.v. Y = PV of WL or Temporary Annuity

Y_from_t(t, discrete):
PV of insurance payment Y(t), given T_x

Y_from_prob(x, prob, discrete):


Percentile of annual or continuous WL annuity PV r.v. Y, given probability

Y_plot(x, s, stop, benefit, T, discrete, ax, dual, title, color, alpha):


Plot PV of annuity r.v. Y vs T

58 Chapter 8. Annuities
Solving Actuarial Math with Python

8.12 Examples

Given a survival model, the a_x class method computes the EPV for arbitrary life annuity benefits, discount functions, and
terms using numerical integration or summation. Common life annuity EPV’s are computed by calling the class methods
whole_life_annuity, temporary_annuity, deferred_annuity, certain_life_annuity,
increasing_insurance, decreasing insurance, or immediate_annuity. The examples below
confirm the numerical relationships of
• whole life immediate annuity with annuity-due
• whole life with temporary and deferred annuities
• certain-and-life annuity with deferred life annuity and annuity-certain
• temporary life annuity with increasing and decreasing life annuities

def S(x, s, t): # define a survival function


return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = Annuity().set_survival(S=S, minage=20, maxage=130)\
.set_interest(i=0.05)
x, t = 50, 10
print(f"whole life annuity due for ({x}): ", life.whole_life_annuity(x))
print(f"immediate whole life annuity for ({x}): ", life.immediate_annuity(x))
print(f"{t}-year certain-and-life annuity for ({x}):", life.certain_life_annuity(x,␣
↪u=t))

print(f"{t}-year deferred life annuity for ({x}): ", life.deferred_annuity(x, u=t))


print(f"{t}-year annuity-certain for ({x}): ", [Link](t=t))
print(f"{t}-year increasing insurance for ({x}): ", life.increasing_annuity(x,␣
↪t=t))

print(f"{t}-year decreasing insurance for ({x}): ", life.decreasing_annuity(x,␣


↪t=t))

print(f"{t}-year temporary annuity for ({x}): ", life.temporary_annuity(x, t=t))


print(f"{t//2}-year temporary annuity for ({x}): ", life.temporary_annuity(x,␣
↪t=t//2))

print(f"{t//2}-year deferred {t//2}-year temporary for ({x}):",


life.a_x(x, u=t//2, t=t//2))

whole life annuity for (50): 17.024534933684702


immediate whole life annuity for (50): 16.024534933684702
10-year certain-and-life annuity for (50): 17.077353318594994
10-year deferred annuity for (50): 8.96953164295094
10-year annuity-certain for (50): 8.107821675644052
10-year increasing insurance for (50): 40.9536356665489
10-year decreasing insurance for (50): 47.65140053152248
10-year temporary annuity for (50): 8.055003290733762
5-year temporary annuity for (50): 4.534416095954249
5-year deferred 5-year temporary for (50): 3.520587194779506

The Y_t class method returns the 100𝑝-th percentile of the survival function, then Y_from_t can be called to discount
the benefits up to that time. The Y_from_prob class method performs both steps with one call. `Y_plot plots the
annuity present value r.v. as a function of time.

def S(x, s, t): # define a survival function


return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = Annuity().set_survival(S=S, minage=20, maxage=130)\
.set_interest(i=0.05)
(continues on next page)

8.12. Examples 59
Solving Actuarial Math with Python

(continued from previous page)


P = 0.75 # to find the 75-th percentile of Y
T = life.Y_t(x=20, prob=P, discrete=False) # corresponding percentile of lifetime
life.Y_plot(x=20, stop=80, T=T, discrete=False) # plot Y vs t, and annotate at time␣
↪T

print('T:', T) # call methods and compare results


print('p:', P, life.q_x(x=20, t=T))
print('Y:', life.Y_from_prob(x=20, prob=P, discrete=False), life.Y_from_t(t=T,␣
↪discrete=False))

T: 74.02351166727799
p: 0.75 0.7499999999997666
Y: 19.94237852945247 19.94237852945247

By setting the `dual`` argument to True, the survival probability of (x) can be simultaneously plotted to annotate, say, the
median survival lifetime.

def S(x, s, t): # define a survival function


return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = Annuity().set_survival(S=S, minage=20, maxage=130)\
.set_interest(i=0.05)
T = life.Y_t(x=20, prob=0.5, discrete=False) # compute the median lifetime
K = life.Y_t(x=20, prob=0.5, discrete=True) # plot using discrete approximation
life.Y_plot(x=20, stop=80, T=K, discrete=True, dual=True, title="median Z(K)")
print(f"S(K={K:.4f}):", life.p_x(x=20, t=K)) # survival function at integer age
print(f"S(T={T:.4f}):", life.p_r(x=20, t=T)) # survival function at fractional age

60 Chapter 8. Annuities
Solving Actuarial Math with Python

S(K=69.0000): 0.45995640909219604
S(T=68.0097): 0.49999343308443683

SOA Question 5.6


For a group of 100 lives age x with independent future lifetimes, you are given:
• Each life is to be paid 1 at the beginning of each year, if alive
• 𝐴𝑥 = 0.45
• 2 𝐴𝑥 = 0.22
• 𝑖 = 0.05
• 𝑌 is the present value random variable of the aggregate payments.
Using the normal approximation to 𝑌 , calculate the initial size of the fund needed to be 95% certain of being able to
make the payments for these life annuities.

print("SOA Question 5.6: (D) 1200")


life = Annuity().set_interest(i=0.05)
var = life.annuity_variance(A2=0.22, A1=0.45)
mean = life.annuity_twin(A=0.45)
print(life.portfolio_percentile(mean=mean, variance=var, prob=.95, N=100))

SOA Question 5.6: (D) 1200


1200.6946732201702

8.12. Examples 61
Solving Actuarial Math with Python

62 Chapter 8. Annuities
CHAPTER

NINE

PREMIUMS

When insurance company agrees to pay some life contingent benefits, the policyholder agrees to pay premiums to the
insurance company to secure these benefits. The premiums will also need to reimburse the insurance company for the
expenses associated with the policy.
The calculation of the premium may not explicitly allow for the insurance company’s expenses, which we refer to as a
net premium (also called a risk premium or benefit premium). If the calculation does explicitly allow for expenses, the
premium is called a gross premium (also called expense-loaded premium).
Premiums for life insurance are payable in advance, with the first premium payable when the policy is purchased.
• Fully continuous insurance: both benefits and premiums are payable continuously
• Fully discrete insurance: benefits are paid at the end of the year, premiums are paid at the beginning of the year
• Semi-continuous insurance: benefits are paid at moment of death, premiums are paid at the beginning of the year

9.1 Present value of loss at issue r.v. 0 𝐿

The loss at issue random variable is defined as the present value of the amount the insurance loses.

0𝐿 = PV of future benefits - PV of future premiums


• PV of loss at issue
Fully continuous whole life insurance:
1 − 𝑣 𝑇𝑥
0𝐿 = 𝑣𝑇𝑥 − 𝑃 𝑎𝑇𝑥 | = 𝑣𝑇𝑥 − 𝑃 ( )
𝛿
• loss at issue is the present value of the insurance payment less the present value of the level premimum annuity of
𝑃 per year
𝑃 𝜋
0𝐿 = 𝐴𝑥 − 𝑃 𝑎𝑥 = 𝐴𝑥 (1 + )−
𝛿 𝛿
• expected loss at issue by applying expected values to the loss at issue equation

63
Solving Actuarial Math with Python

9.2 Equivalence principle

Under this principle, premiums are set such that the actuarial present value of the benefit premiums equals the actuarial
present value of the benefits, hence expected loss at issue is zero:
𝐸[0 𝐿] = 𝐸𝑃 𝑉0 (future benefits) − 𝐸𝑃 𝑉0 (future premiums) = 0

9.3 Net premium

For net premiums, we take into consideration outgoing benefit payments only: expenses are not a part of the calculation.
The benefit may be a death benefit or a survival benefit or a combination. Under the equivalence principle, the net premium
is set such that the expected value of the future loss is zero at the start of the contract, 𝐸[𝐿0 ] = 0.
Whole life insurance:
𝐴
𝑃𝑥 = 𝑥
𝑎𝑥̈
• fully discrete whole life insurance net premium
𝐴𝑥
𝑃𝑥 =
𝑎𝑥
• fully continuous whole life insurance net premium
Term life insurance:
𝐴1𝑥∶𝑡|
1
𝑃𝑥∶𝑡| =
𝑎𝑥∶𝑡|
̈
• fully discrete term life net premium
1
1 𝐴𝑥∶𝑡|
𝑃 𝑥∶𝑡| =
𝑎𝑥∶𝑡|
• fully continuous term life net premium
Pure endowment:
𝐸
𝑃𝑥∶𝑡|1 = 𝑡 𝑥
𝑎𝑥∶𝑡|
̈
• fully discrete pure endowment net premium
1 𝑡 𝐸𝑥
𝑃 𝑥∶𝑡| =
𝑎𝑥∶𝑡|
• fully continuous pure endowment net premium
Endowment insurance:
𝐴𝑥∶𝑡|
𝑃𝑥∶𝑡| =
𝑎𝑥∶𝑡|
̈
• fully discrete endowment insurance net premium
𝐴𝑥∶𝑡|
𝑃 𝑥∶𝑡| =
𝑎𝑥∶𝑡|
• fully continuous endowment insurance net premium

64 Chapter 9. Premiums
Solving Actuarial Math with Python

Shortcuts for whole life and endowment insurance only:


For whole life and endowment insurance only, by plugging in the insurance or annuity twin, the following shortcuts are
available for calculating net premiums from only the life insurance or annuity factor.
1 𝑑𝐴𝑥
𝑃𝑥 = 𝑏 ( − 𝑑) = 𝑏 ( )
𝑎𝑥̈ 1 − 𝐴𝑥
• fully discrete whole life shortcut
1 𝑑𝐴𝑥
𝑃𝑥 = 𝑏 ( − 𝛿) = 𝑏 ( )
𝑎𝑥 1 − 𝐴𝑥
• fully continuous whole life shortcut
1 𝑑𝐴𝑥∶𝑛|
𝑃𝑥∶𝑡| = 𝑏 ( − 𝑑) = 𝑏 ( )
𝑎𝑥∶𝑛|
̈ 1 − 𝐴𝑥∶𝑛|
• fully discrete endowment insurance shortcut

1 𝑑𝐴𝑥∶𝑛|
𝑃 𝑥∶𝑡| = 𝑏 ( − 𝛿) = 𝑏 ( )
𝑎𝑥∶𝑛| 1 − 𝐴𝑥∶𝑛|
• fully continuous endowment insurance shortcut

9.4 Gross premium

When we calculate a gross premium for an insurance policy or an annuity, we take account of the expenses the insurer
incurs. There are three main types of expense associated with policies – initial expenses, renewal expenses and termination
or claim expenses.
Expenses:
𝑒𝑖 = initial_per_policy + initial_per_premium × gross_premium
• initial expenses at the beginning of year 1 when a policy is issued, which may be both proportional to premiums or
may be ‘per policy’, meaning that the amount is fixed for all policies, and is not related to the size of the contract.
𝑒𝑟 = renewal_per_policy + renewal_per_premium × gross_premium
• renewal expenses in the beginning of each year 2+, may be both per policy or percent of premium.
𝐸 = settlement expense
• is paid along with death benefit (𝑏); hence total claim cost upon death is:
claim cost = 𝑏 + 𝐸 = death benefit + settlement expense.
Return of premiums paid without interest upon death:
𝑡−1
𝐸𝑃 𝑉0 (return of premiums paid) = ∑𝑘=0 𝑃 (𝑘 + 1) 𝑣𝑘+1 𝑘| 𝑞𝑥 = 𝑃 ⋅ (𝐼𝐴)1𝑥∶𝑡|

• an additional benefit in some insurance policies, whose EPV can be calculated using an increasing insurance factor
Equivalence principle:
If gross premiums are set under equivalence principle, then expected gross future loss at issue equals zero:
𝐸[0 𝐿𝑔 ] = 𝐸𝑃 𝑉0 (future benefits) + 𝐸𝑃 𝑉0 (future expenses) − 𝐸𝑃 𝑉0 (future premiums) = 0

9.4. Gross premium 65


Solving Actuarial Math with Python

9.5 Portfolio Percentile Premium

The portfolio percentile premium principle is an alternative to the equivalence premium principle. Using the mean and
variance of the future loss random variable, the portfolio percentile premium principle can be used to determine a pre-
mium. We assume a large portfolio of 𝑁 identical and independent policies. The present value of the total future loss 𝐿
of the portfolio can be approximated by a normal distribution over the sum of the individual losses 𝐿𝑖

𝐿 = 𝐿 1 + 𝐿2 + ⋯ + 𝐿 𝑁
𝐸[𝐿] = 𝑁 𝐸[𝐿]
𝑉 𝑎𝑟[𝐿] = 𝑁 𝑉 𝑎𝑟[𝐿]

Note 𝐸[𝐿] and 𝑉 𝑎𝑟[𝐿] are functions of the unspecified premium 𝑃 . A probability percentile 𝑞 (say, 95% confidence) and
a threshold 𝐿∗ (say, 0) are chosen, then 𝑃 is solved for implicitly from the following equation, such that the probability
of 𝐿 not exceeding 𝐿∗ is 𝑞

⎡ 𝐿 − 𝐸[𝐿] 𝐿∗ − 𝐸[𝐿] ⎤ ∗
⎛ 𝐿 − 𝐸[𝐿] ⎞
𝑃 𝑟[𝐿 < 𝐿∗ ] = 𝑃 𝑟 ⎢ < ⎥ = Φ⎜
⎜ ⎟
⎟=𝑞

⎣ 𝑉 𝑎𝑟[𝐿] √ 𝑉 𝑎𝑟[𝐿] ⎦ √
⎝ 𝑉 𝑎𝑟[𝐿] ⎠

9.6 Methods

The Premiums class implements methods for computing net and gross premiums under the equivalence principle

import numpy as np
from actuarialmath import Premiums
import describe
[Link](Premiums)

class Premiums - Compute et and gross premiums under equivalence principle

Methods:
--------

net_premium(x, s, t, u, n, b, endowment, discrete, return_premium, annuity,␣


↪initial_cost):
Net level premium for special n-pay, u-deferred t-year term insurance

insurance_equivalence(premium, b, discrete):
Compute whole life or endowment insurance factor, given net premium

annuity_equivalence(premium, b, discrete):
Compute whole life or temporary annuity factor, given net premium

premium_equivalence(A, a, b, discrete):
Compute premium from whole life or endowment insurance and annuity factors

gross_premium(a, A, IA, discrete, benefit, E, endowment, settlement_policy,␣


↪initial_policy, initial_premium, renewal_policy, renewal_premium):
Gross premium by equivalence principle

66 Chapter 9. Premiums
Solving Actuarial Math with Python

9.7 Examples

When net premiums are set by the equivalence principle, then the three class methods insurance_equivalence,
annuity_equivalence or premium_equivalence can be called to compute the insurance, annuity or net
premium respectively given any one of the other value, for whole life and endowment insurances. For other general life
insurance or annuity benefits, net_premium computes the net premium under EPP given any term (t), deferral period
(u), endowment benefit amount (endowment), other initial expected costs (initial_cost), or refund of premium
without interest at death (return_premium).

life = Premiums().set_interest(delta=0.06)\
.set_survival(mu=lambda x,s: 0.04)
P = life.net_premium(x=0, discrete=False)
A = life.whole_life_insurance(x=0, discrete=False)
a = life.whole_life_annuity(x=0, discrete=False)
print('Insurance:', A, life.insurance_equivalence(premium=P, discrete=False))
print('Annuity:', a, life.annuity_equivalence(premium=P, discrete=False))
print('Net Premium:', P,
life.premium_equivalence(A=A, discrete=False),
life.premium_equivalence(a=a, discrete=False))

Insurance: 0.4 0.4


Annuity: 9.999999999999996 9.999999999999996
Net Premium: 0.040000000000000015 0.040000000000000015 0.040000000000000015

The gross_premium class method computes the level premium under the equivalence principles given expense and
claim amounts (initial and renewal expenses per policy or per $1 premium, settlement expense, death benefit, and endow-
ment) and corresponding actuarial present value factors (A for insurance benefits, a for premium annuity, IA for refund
of premium without interest death benefit, and E for endowment benefit).
SOA Question 6.2
For a fully discrete 10-year term life insurance policy on (x), you are given:
• Death benefits are 100,000 plus the return of all gross premiums paid without interest
• Expenses are 50% of the first year’s gross premium, 5% of renewal gross premiums and 200 per policy expenses
each year
• Expenses are payable at the beginning of the year
• 𝐴1𝑥∶10| = 0.17094

• (𝐼𝐴)1𝑥∶10| = 0.96728

• 𝑎𝑥∶10|
̈ = 6.8865
Calculate the gross premium using the equivalence principle.

print("SOA Question 6.2: (E) 3604")


life = Premiums()
A, IA, a = 0.17094, 0.96728, 6.8865
print(life.gross_premium(a=a,
A=A,
IA=IA,
benefit=100000,
initial_premium=0.5,
renewal_premium=.05,
(continues on next page)

9.7. Examples 67
Solving Actuarial Math with Python

(continued from previous page)


renewal_policy=200,
initial_policy=200))

SOA Question 6.2: (E) 3604


3604.229940320728

SOA Question 6.16


For a fully discrete 20-year endowment insurance of 100,000 on (30), you are given:
• d = 0.05
• Expenses, payable at the beginning of each year, are:

First Year First Year Renewal Years Renewal Years

Percent of Premium Per Policy Percent of Premium Per Policy

Taxes 4% 0 4% 0
Sales Commission 35% 0 2% 0
Policy Maintenance 0% 250 0% 50

• The net premium is 2143


Calculate the gross premium using the equivalence principle.

print("SOA Question 6.16: (A) 2408.6")


life = Premiums().set_interest(d=0.05)
A = life.insurance_equivalence(premium=2143, b=100000)
a = life.annuity_equivalence(premium=2143, b=100000)
p = life.gross_premium(A=A,
a=a,
benefit=100000,
settlement_policy=0,
initial_policy=250,
initial_premium=.04+.35,
renewal_policy=50,
renewal_premium=.04+.02)
print(A, a, p)

SOA Question 6.16: (A) 2408.6


0.3000139997200056 13.999720005599887 2408.575206281868

SOA Question 6.20


For a special fully discrete 3-year term insurance on (75), you are given:
• The death benefit during the first two years is the sum of the net premiums paid without interest
• The death benefit in the third year is 10,000

68 Chapter 9. Premiums
Solving Actuarial Math with Python

𝑥 𝑝𝑥
75 0.90
76 0.88
77 0.85

• 𝑖 = 0.04
Calculate the annual net premium.

print("SOA Question 6.20: (B) 459")


l = lambda x,s: dict(zip([75, 76, 77, 78],
[Link]([1, .9, .88, .85]))).get(x+s, 0)
life = Premiums().set_interest(i=0.04).set_survival(l=l)
a = life.temporary_annuity(75, t=3)
IA = life.increasing_insurance(75, t=2)
A = life.deferred_insurance(75, u=2, t=1)
print([Link](lambda P: P*IA + A*10000 - P*a, target=0, grid=100))

SOA Question 6.20: (B) 459


458.83181728297285

SOA Question 6.29


(35) purchases a fully discrete whole life insurance policy of 100,000. You are given:
• The annual gross premium, calculated using the equivalence principle, is 1770
• The expenses in policy year 1 are 50% of premium and 200 per policy
• The expenses in policy years 2 and later are 10% of premium and 50 per policy
• All expenses are incurred at the beginning of the policy year
• 𝑖 = 0.035
Calculate 𝑎35
̈ .

print("SOA Question 6.29 (B) 20.5")


life = Premiums().set_interest(i=0.035)
def fun(a):
return life.gross_premium(A=life.insurance_twin(a=a),
a=a,
benefit=100000,
initial_policy=200,
initial_premium=.5,
renewal_policy=50,
renewal_premium=.1)
print([Link](fun, target=1770, grid=[20, 22]))

SOA Question 6.29 (B) 20.5


20.480268314431726

SOA Question 6.33


An insurance company sells 15-year pure endowments of 10,000 to 500 lives, each age x, with independent future life-
times. The single premium for each pure endowment is determined by the equivalence principle.
You are given:

9.7. Examples 69
Solving Actuarial Math with Python

• 𝑖 = 0.03
• 𝜇𝑥 (𝑡) = 0.02𝑡, 𝑡≥0
• 0 𝐿 is the aggregate loss at issue random variable for these pure endowments.
Using the normal approximation without continuity correction, calculate 𝑃 𝑟(0 𝐿) > 50, 000).

print("SOA Question 6.33: (B) 0.13")


life = Premiums().set_survival(mu=lambda x,t: 0.02*t).set_interest(i=0.03)
var = life.E_x(x=0, t=15, moment=[Link], endowment=10000)
p = 1 - life.portfolio_cdf(mean=0, variance=var, value=50000, N=500)
print(p)

SOA Question 6.33: (B) 0.13


0.12828940905648634

SOA Question 5.6:


For a group of 100 lives age x with independent future lifetimes, you are given:
• Each life is to be paid 1 at the beginning of each year, if alive
• 𝐴𝑥 = 0.45
• 2 𝐴𝑥 = 0.22
• 𝑖 = 0.05
• 𝑌 is the present value random variable of the aggregate payments.
Using the normal approximation to Y, calculate the initial size of the fund needed in order to be 95% certain of being
able to make the payments for these life annuities.

print("SOA Question 5.6: (D) 1200")


life = Premiums().set_interest(i=0.05)
var = life.annuity_variance(A2=0.22, A1=0.45)
mean = life.annuity_twin(A=0.45)
fund = life.portfolio_percentile(mean, var, prob=.95, N=100)
print(fund)

SOA Question 5.6: (D) 1200


1200.6946732201702

70 Chapter 9. Premiums
CHAPTER

TEN

POLICY VALUES

Policy value at time 𝑡 is the expected present value, at time 𝑡, of the future loss random variable, where 𝑃 is the premium
for 𝑏 units of insurance, not necessarily the benefit premium determined from the equivalence principle.
Net future loss at issue
For net future loss at time 𝑡 = 0, we consider benefit payments and net premiums only.
𝑃 𝐾𝑥 +1 𝑃
0𝐿 = 𝑏 𝑣𝐾𝑥 +1 − 𝑃 𝑎𝐾
̈ 𝑥 +1| = (𝑏 + )𝑣 −
𝑑 𝑑
• net future loss at issue of fully discrete whole life insurance
𝑃 𝑇𝑥 𝑃
0𝐿 = 𝑏 𝑣𝑇𝑥 − 𝑃 𝑎𝑇𝑥 | = (𝑏 + ) 𝑣 − (continuous)
𝛿 𝛿
• net future loss at issue of fully continuous whole life insurance
Gross future loss at issue
For gross future loss at time 𝑡 = 0, expenses are included along with benefits payments and gross premiums.
𝐺 − 𝑒𝑟 𝐾𝑥 +1 𝐺 − 𝑒𝑟
0𝐿 = (𝑏 + 𝐸 + )𝑣 − + (𝑒𝑖 − 𝑒𝑟 )
𝑑 𝑑
• gross future loss at issue of fully discrete whole life insurance

10.1 Net policy value

The amount needed at time 𝑡 to cover the shortfall of expected future benefits greater than the EPV of future net premiums
after time 𝑡 is called the policy value for the policy at time 𝑡, denoted 𝑡 𝑉 ,

𝑡𝑉 = 𝐸[𝑡 𝐿] = 𝐸𝑃 𝑉𝑡 (future benefits) − 𝐸𝑃 𝑉𝑡 (future net premiums)


• net policy value at time 𝑡 is the expected net future loss of benefits less premiums after time 𝑡

0𝑉 =0
• net policy value at issue is 0 because of the equivalence principle
Term insurance:

𝑛𝑉 =0
• net policy value at year 𝑛 is 0 for a n-year term insurance
Endowment insurance:

𝑛𝑉 = endowment benefit
• net policy value at year 𝑛 is equal to the endowment benefit for a n-year endowment insurance

71
Solving Actuarial Math with Python

Shortcuts for whole life and endowment insurance:


These have a simple relationship between insurance and annuity, hence the policy value formulas can be expressed in
temrs of insurances only or annuities only.
𝑎𝑥+𝑡
̈ 𝐴 − 𝐴𝑥
𝑡𝑉 = 𝑏[1 − ] or 𝑏[ 𝑥+𝑡 ]
𝑎𝑥̈ 1 − 𝐴𝑥
• net policy value at time 𝑡 of fully-discrete whole life insurance
𝑎𝑥+𝑡 𝐴 − 𝐴𝑥
𝑡𝑉 = 𝑏[1 − ] or 𝑏[ 𝑥+𝑡 ]
𝑎𝑥 1 − 𝐴𝑥
• net policy value at time 𝑡 of fully-continuous whole life insurance
𝑎𝑥+𝑡∶𝑛−𝑡|
̈ 𝐴𝑥+𝑡∶𝑛−𝑡| − 𝐴𝑥∶𝑛|
𝑡𝑉 = 𝑏[1 − ] or 𝑏[ ]
𝑎𝑥∶𝑛|
̈ 1 − 𝐴𝑥∶𝑛|
• net policy value at time 𝑡 of fully-discrete endowment insurance
𝑎𝑥+𝑡∶𝑛−𝑡| 𝐴𝑥+𝑡∶𝑛−𝑡| − 𝐴𝑥∶𝑛|
𝑡𝑉 = 𝑏[1 − ] or 𝑏[ ]
𝑎𝑥∶𝑛| 1 − 𝐴𝑥∶𝑛|
• net policy value at time 𝑡 of fully-continuous endowment insurance

10.2 Gross policy value

Gross premium policy values explicitly allow for expenses and for the full gross premium.
𝑔
𝑡𝑉 = 𝐸[𝑡 𝐿𝑔 ] = 𝐸𝑃 𝑉𝑡 (future benefits) + 𝐸𝑃 𝑉𝑡 (future expenses) − 𝐸𝑃 𝑉𝑡 (future premiums)
• gross policy value at time 𝑡 is the expected net future loss of benefits and expenses less premiums after time 𝑡

10.3 Variance of future loss

These formulas apply to whole life and endowment insurance only. In these cases, the function of the random variable
𝑣𝑇𝑥 is the same for both the insurance and annuity of premiums which simplifies the formulas for variance of future loss.
Net future loss
𝑃 2 2
𝑉 𝑎𝑟[𝑡 𝐿] = (𝑏 + ) [ 𝐴𝑥+𝑡 − (𝐴𝑥+𝑡 )2 ]
𝑑
• fully discrete whole life insurance
𝑃 2 2
𝑉 𝑎𝑟[𝑡 𝐿] = (𝑏 + ) [ 𝐴𝑥+𝑡 − (𝐴𝑥+𝑡 )2 ]
𝛿
• fully continuous whole life insurance
𝑃 2 2
𝑉 𝑎𝑟[𝑡 𝐿] = (𝑏 + ) [ 𝐴𝑥+𝑡∶𝑛−𝑡| − (𝐴𝑥+𝑡∶𝑛−𝑡| )2 ]
𝑑
• fully discrete endowment insurance
𝑃 2 2
𝑉 𝑎𝑟[𝑡 𝐿] = (𝑏 + ) [ 𝐴𝑥+𝑡∶𝑛−𝑡| − (𝐴𝑥+𝑡∶𝑛−𝑡| )2 ]
𝛿
• fully continuous endowment insurance

72 Chapter 10. Policy Values


Solving Actuarial Math with Python

Gross future loss


𝐺 − 𝑒𝑟 2 2
𝑉 𝑎𝑟[𝑡 𝐿] = (𝑏 + 𝐸 + ) [ 𝐴𝑥+𝑡 − (𝐴𝑥+𝑡 )2 ]
𝑑
• fully discrete whole life insurance
𝐺 − 𝑒𝑟 2 2
𝑉 𝑎𝑟[𝑡 𝐿] = (𝑏 + 𝐸 + ) [ 𝐴𝑥+𝑡∶𝑛−𝑡| − (𝐴𝑥+𝑡∶𝑛−𝑡| )2 ]
𝑑
• fully discrete endowment insurance
Shortcuts for variance of net future loss under equivalence principle
When net premiums are set under equivalence principle, these shortcuts are available without explicitly specifying the
value of net premiums (which can be stated as a function of insurance only under this assumption) for whole life and
endowment insurance only:
2
𝐴𝑥+𝑡 − (𝐴𝑥+𝑡 )2
𝑉 𝑎𝑟[𝑡 𝐿] = 𝑏2 [ ]
(1 − 𝐴𝑥 )2
• variance of net future loss for fully-discrete whole life insurance under EPP
2
𝐴𝑥+𝑡 − (𝐴𝑥+𝑡 )2
𝑉 𝑎𝑟[𝑡 𝐿] = 𝑏2 [ ]
(1 − 𝐴𝑥 )2
• variance of net future loss for fully-continuous whole life insurance under EPP
2
𝐴𝑥+𝑡∶𝑛−𝑡| − (𝐴𝑥+𝑡∶𝑛−𝑡| )2
𝑉 𝑎𝑟[𝑡 𝐿] = 𝑏2 [ ]
(1 − 𝐴𝑥∶𝑛| )2
• variance of net future loss for fully-discrete endowment insurance under EPP
2
𝐴𝑥+𝑡∶𝑛−𝑡| − (𝐴𝑥+𝑡∶𝑛−𝑡| )2
𝑉 𝑎𝑟[𝑡 𝐿] = 𝑏2 [ ]
(1 − 𝐴𝑥∶𝑛| )2
• variance of net future loss for fully-continuous endowment insurance under EPP

10.4 Expense reserve

𝑃𝑒 = 𝑃𝑔 − 𝑃𝑛
• expense premium (sometimes, expense loading) is defined as the difference of gross premium and net premium: if
expenses are weighted to the start of the contract, as is normally the case, then 𝑃 𝑒 will be greater than the renewal
expense as it must fund both the renewal and initial expenses.
𝑒
𝑡𝑉 = 𝑡 𝑉 𝑔 − 𝑡 𝑉 = 𝐸𝑃 𝑉𝑡 (future expenses) − 𝐸𝑃 𝑉𝑡 (future expense loadings)
• expense reserves, defined as the difference between gross reserves and net reserves, also equals the expected present
value of future expenses less the expected present value of future expense loadings (or expense premiums)
Generally:
• 𝑡𝑉 𝑒 < 0
• 𝑡𝑉 > 𝑡𝑉 𝑔 > 0 > 𝑡𝑉 𝑒

10.4. Expense reserve 73


Solving Actuarial Math with Python

10.5 Probabilities and Percentiles

For whole life, term and endowment insurance, the present value of benefits decreases with time, while the present value
of the premium annuity increases
Percentile of 0 𝐿:
Since loss at issue is a monotonically decreasing function of lifetime, the 100𝑝-th percentile of the loss at issue r.v. is
determined from the 100𝑝-th percentile of the survival function.
Probability of 0 𝐿:
To calculate the probability that the present value of loss at issue is greater than some value 𝑃 𝑟(0 𝐿 ≥ 𝐿𝜋 ), calculate the
time 𝑡𝜋 for which 0 𝐿 = 𝑏 𝑣𝑡𝜋 − 𝑃 𝑎𝑡𝜋 | has the required value, then calculate the probability that time of death 𝑇𝑥 is in
that range.

10.6 Methods

The PolicyValues class implements methods for computing net and gross future losses, and policy values (expected
present values). The Contract class is used store and retrieve policy contract terms, such as benefit amounts and the
various types of expenses.

from [Link] import PolicyValues, Contract


import math
import describe
[Link](PolicyValues)

class PolicyValues - Compute net and gross future losses and policy values

Methods:
--------

gross_policy_value(x, s, t, n, contract):
Gross policy values for insurance: t_V = E[L_t]

gross_policy_variance(x, s, t, n, contract):
Variance of gross policy value for WL and Endowment Insurance only

net_policy_value(x, s, t, b, n, endowment, discrete):


Net policy value assuming premiums from equivalence: E[L_t]

net_policy_variance(x, s, t, b, n, endowment, discrete):


Variance of net future loss for WL or Endowment Insurance only

net_future_loss(A, A1, b):


Shortcut for net policy value with WL or Endowment Insurance factors

gross_future_loss(A, a, contract):
Shortcut for gross policy value with WL or Endowment Insurance factors

net_variance_loss(A1, A2, A, b):


Variance of net loss with WL or Endowment Insurance factors

gross_variance_loss(A1, A2, contract):


Variance of gross loss with WL or endowment insurance factors
(continues on next page)

74 Chapter 10. Policy Values


Solving Actuarial Math with Python

(continued from previous page)

L_from_t(t, contract):
PV of Loss L(t) at time of death t = T_x

L_from_prob(x, prob, contract):


Percentile of PV future loss r.v. L given probability

L_to_t(L, contract):
Time of death T_x s.t. PV future loss is no more than L

L_to_prob(x, L, contract):
Probability such that PV of future loss r.v. is no more than L"

L_plot(x, s, stop, T, contract, ax, dual, title, color, alpha):


Plot PV of future loss r.v. L vs time of death T_x

[Link](Contract)

class Contract - Set and retrieve policy contract terms

Args:
premium : level premium amount
benefit : insurance death benefit amount
settlement_policy : settlement expense per policy
endowment : endowment benefit amount
initial_policy : first year total expense per policy
initial_premium : first year total premium per $ of gross premium
renewal_policy : renewal expense per policy
renewal_premium : renewal premium per $ of gross premium
discrete : annuity due (True) or continuous (False)
T : term of insurance
discrete : annuity due (True) or continuous (False)

Methods:
--------

set_contract(terms):
Update any existing policy contract terms

premium_terms():
Dict of terms required for calculating gross premiums

renewal_profit():
Renewal dollar profit (premium less renewal expenses)

initial_cost():
Total initial cost (excludes renewal expenses)

claims_cost():
Total claims cost (death benefit + settlement expense)

renewals(t):
Returns contract object with initial terms set to renewal terms

10.6. Methods 75
Solving Actuarial Math with Python

10.7 Examples

The Contract helper class can be initialized to store expenses, benefit amounts and values of other contract features.
Its set_contract method is called to update individual values, its renewals method returns a new Contract object
with its initial expenses changed to be the same as renewal expenses, and other “getter” methods derive various summary
values.

contract = Contract(benefit=1000,
settlement_policy=20,
initial_policy=10,
initial_premium=0.75,
renewal_policy=2,
renewal_premium=0.1)
print(contract.premium_terms)
print([Link]().premium_terms)
contract.set_contract(premium=35.38618830746352)
print(contract.initial_cost)
print(contract.claims_cost)
print(contract.renewal_profit)

{'benefit': 1000, 'initial_policy': 10, 'initial_premium': 0.75, 'renewal_policy':␣


↪2, 'renewal_premium': 0.1, 'settlement_policy': 20}

{'benefit': 1000, 'initial_policy': 2, 'initial_premium': 0.1, 'renewal_policy': 2,


↪ 'renewal_premium': 0.1, 'settlement_policy': 20}

31.00102239985129
1020
29.84756947671717

The gross_policy_value method in the PolicyValues class compute policy values at issue or future year 𝑡
for general insurances and premiums. For whole life and endowment insurances only, gross_policy_variance
computes the variance of the future gross loss PV random variable. Internally, these use the gross_future_loss
and gross_variance_loss helper methods, which are also available to be called directly if the actuarial insurance
factors are already provided as inputs.
When net premiums are given by the equivalence principle, net_policy_value computes the net policy values at
issue or a future year 𝑡. For whole life and endowment insurances only, net_policy_variance compute the variance
of the future net loss PV random variable. Internally, these use the net_future_loss and net_variance_loss
helper methods, which are also available to be called directly if the actuarial insurance factors are already provided as
inputs.

def S(x, s, t): # define a survival function


return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = PolicyValues().set_survival(S=S, minage=20, maxage=130)\
.set_interest(i=0.05)
print(life.net_policy_value(x=20, b=[Link], discrete=[Link]),
life.net_policy_value(x=20, t=5, b=[Link], discrete=contract.
↪discrete))

print(life.net_policy_variance(x=20, b=[Link], discrete=[Link]),


life.net_policy_variance(x=20, t=5, b=[Link], discrete=contract.
↪discrete))

print(life.gross_policy_value(x=20, contract=contract),
life.gross_policy_variance(x=20, contract=contract))

76 Chapter 10. Policy Values


Solving Actuarial Math with Python

0.0 12.88972064810054
3734.4039865925088 4534.593306902999
-514.7435740643272 9155.101027638082

The L_from_t method computes the present value of the future loss when death occurs at time 𝑡, while L_from_prob
returns the 100𝑝-th percentile value of 𝐿. The inverse computations are performed by L_to_t and L_to_prob
methods respectively. L_plot plots the present value of future loss r.v. 𝐿 as a function of time 𝑡. The example below
determines the breakeven lifetime, when the present value of future loss L(t) = 0, and annotates in a plot with survival
probabilities.

def S(x, s, t): # define a survival function


return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = PolicyValues().set_survival(S=S, minage=20, maxage=130)\
.set_interest(i=0.05)
P = life.net_premium(x=20, discrete=False)
contract = Contract(premium=P, discrete=False)
T = life.L_to_t(L=0, contract=contract) # breakeven T ␣

life.L_plot(x=20, T=T, contract=contract, dual=True)


prob = life.L_to_prob(x=20, L=0, contract=contract)
print('L:', life.L_from_t(t=T, contract=contract),
life.L_from_prob(x=20, prob=prob, contract=contract))

L: -4.905848505210031e-10 -4.905742478911179e-10

SOA Question 6.24

10.7. Examples 77
Solving Actuarial Math with Python

For a fully continuous whole life insurance of 1 on (x), you are given:
• L is the present value of the loss at issue random variable if the premium rate is determined by the equivalence
principle
• L^* is the present value of the loss at issue random variable if the premium rate is 0.06
• 𝛿 = 0.07
• 𝐴𝑥 = 0.30
• 𝑉 𝑎𝑟(𝐿) = 0.18
Calculate 𝑉 𝑎𝑟(𝐿∗ ).

print("SOA Question 6.24: (E) 0.30")


life = PolicyValues().set_interest(delta=0.07)
x, A1 = 0, 0.30 # Policy for first insurance
P = life.premium_equivalence(A=A1, discrete=False) # Need its premium
contract = Contract(premium=P, discrete=False)
def fun(A2): # Solve for A2, given Var(Loss)
return life.gross_variance_loss(A1=A1, A2=A2, contract=contract)
A2 = [Link](fun, target=0.18, grid=0.18)
contract = Contract(premium=0.06, discrete=False) # Solve second insurance
variance = life.gross_variance_loss(A1=A1, A2=A2, contract=contract)
print(variance)

SOA Question 6.24: (E) 0.30


0.30419999999999975

SOA Question 6.30


For a fully discrete whole life insurance of 100 on (x), you are given:
• The first year expense is 10% of the gross annual premium
• Expenses in subsequent years are 5% of the gross annual premium
• The gross premium calculated using the equivalence principle is 2.338
• 𝑖 = 0.04
• 𝑎𝑥̈ = 16.50
• 2 𝐴𝑥 = 0.17
Calculate the variance of the loss at issue random variable.

print("SOA Question 6.30: (A) 900")


life = PolicyValues().set_interest(i=0.04)
contract = Contract(premium=2.338, benefit=100, initial_premium=.1,
renewal_premium=0.05)
var = life.gross_variance_loss(A1=life.insurance_twin(16.50),
A2=0.17, contract=contract)
print(var)

SOA Question 6.30: (A) 900


908.141412994607

SOA Question 7.32


For two fully continuous whole life insurance policies on (x), you are given:

78 Chapter 10. Policy Values


Solving Actuarial Math with Python

Death Benefit Annual Premium Rate Variance of the PV of Future Loss at t

Policy A 1 0.10 0.455


Policy B 2 0.16 -

• 𝛿 = 0.06
Calculate the variance of the present value of future loss at 𝑡 for Policy B.

print("SOA Question 7.32: (B) 1.4")


life = PolicyValues().set_interest(i=0.06)
contract = Contract(benefit=1, premium=0.1)
def fun(A2):
return life.gross_variance_loss(A1=0, A2=A2, contract=contract)
A2 = [Link](fun, target=0.455, grid=0.455)
contract = Contract(benefit=2, premium=0.16)
var = life.gross_variance_loss(A1=0, A2=A2, contract=contract)
print(var)

SOA Question 7.32: (B) 1.4


1.3848168384380901

SOA Question 6.12


For a fully discrete whole life insurance of 1000 on (x), you are given:
• The following expenses are incurred at the beginning of each year:

Year 1 Years 2+

Percent of premium 75% 10%


Maintenance expenses 10 2

• An additional expense of 20 is paid when the death benefit is paid


• The gross premium is determined using the equivalence principle
• 𝑖 = 0.06
• 𝑎𝑥̈ = 12.0
• 2 𝐴𝑥 = 0.14
Calculate the variance of the loss at issue random variable.

print("SOA Question 6.12: (E) 88900")


life = PolicyValues().set_interest(i=0.06)
a = 12
A = life.insurance_twin(a)
contract = Contract(benefit=1000, settlement_policy=20,
initial_policy=10, initial_premium=0.75,
renewal_policy=2, renewal_premium=0.1)
[Link] = life.gross_premium(A=A, a=a, **contract.premium_terms)
print(A, [Link])
L = life.gross_variance_loss(A1=A, A2=0.14, contract=contract)
print(L)

10.7. Examples 79
Solving Actuarial Math with Python

SOA Question 6.12: (E) 88900


0.3207547169811321 35.38618830746352
88862.59592874818

80 Chapter 10. Policy Values


CHAPTER

ELEVEN

RESERVES

The term reserves is sometimes used in place of policy values. AMLCR uses policy value to mean the expected value of
the future loss random variable, and restricts reserve to mean the actual capital held in respect of a policy, which may be
greater than or less than the policy value.

11.1 Recursion

The following recursive formulae relating 𝑡 𝑉 to 𝑡+1 𝑉 for policy values can be derived for policies with discrete cash flows.
Gross reserves
(𝑡 𝑉 𝑔 + 𝐺 − 𝑒)(1 + 𝑖) = 𝑞𝑥+𝑡 (𝑏 + 𝐸) + 𝑝𝑥+𝑡 𝑡+1 𝑉 𝑔
• recursion for gross reserves
Expense reserves
(𝑡 𝑉 𝑒 + 𝑃 𝑒 − 𝑒)(1 + 𝑖) = 𝑞𝑥+𝑡 𝐸 + 𝑝𝑥+𝑡 𝑡+1 𝑉 𝑒
• recursion for expense reserves
Net reserves
(𝑡 𝑉 + 𝑃 )(1 + 𝑖) = 𝑞𝑥+𝑡 𝑏 + 𝑝𝑥+𝑡 𝑡+1 𝑉
• recursion for net reserves
Refund of benefit reserve
(𝑡 𝑉 + 𝑃 )(1 + 𝑖) = 𝑞𝑥+𝑡 (𝑏 + 𝑡+1 𝑉 ) + 𝑝𝑥+𝑡 𝑡+1 𝑉
• if reserves are also refunded after death, then the end-of-year value reserves should be added to death benefits in
the reserves recursion formula

11.2 Interim reserves

Recursive formulae for interim reserves 𝑡+𝑟 𝑉 where 0 ≤ 𝑟 ≤ 1 can be similarly obtained. These compute the reserve
for fully discrete annual-premium insurances in the middle of the year.
(𝑡 𝑉 + 𝑃 )(1 + 𝑖)𝑟 = 𝑟 𝑞𝑥+𝑡 𝑏 𝑣1−𝑟 + 𝑟 𝑝𝑥+𝑡 𝑡+𝑟 𝑉
• forward recursion for interim net reserves

𝑡+𝑟 𝑉 (1 + 𝑖)1−𝑟 = 1−𝑟 𝑞𝑥+𝑡+𝑟 𝑏+ 1−𝑟 𝑝𝑥+𝑡+𝑟 𝑡+1 𝑉

• backward recursion for interim net reserves

81
Solving Actuarial Math with Python

11.3 Modified reserves

Because acquisition expenses are large relative to the renewal and claims expenses, accounting with level net premiums
typically results in large negative values for expense reserves (called deferred acquisition costs or DAC) particularly at
issue. Modified premium reserves are computed without expenses, and modifies the net premium method to assume a
lower initial net premium that allow implicitly for the DAC.
Full Preliminary Term
FPT is the most common method for modifying net premium policy value. It treats the insurance policy as one-year term
insurance combined with a policy as if it were issued one year later.
𝛼 = 𝐴1𝑥∶1| = 𝑣 𝑞𝑥

• initial FPT premium


𝐴𝑥+1
𝛽=
𝑎𝑥+1
̈
• renewal FPT premium
𝐹𝑃𝑇
0𝑉 = 1𝑉 𝐹 𝑃 𝑇 = 0
• since renewal premium set year 1 policy value to 0, while initial premium set to equal year 1 expected benefits.
𝐹𝑃𝑇
𝑡𝑉 for (𝑥) = 𝑡−1 𝑉 for (𝑥 + 1)
• since renewal FPT premium for (x) is net premium for (x+1) with term lengths adjusted

11.4 Methods

The Reserves class implements methods to solve reserves by recursion, and compute interim and modified reserves.

from actuarialmath import Reserves, Contract


import describe
[Link](Reserves)

class Reserves - Compute recursive, interim or modified reserves

Methods:
--------

set_reserves(T, endowment, V):


Set values of the reserves table and the endowment benefit amount

fill_reserves(x, s, reserve_benefit, contract):


Iteratively fill in missing values in reserves table

t_V_forward(x, s, t, premium, benefit, per_premium, per_policy, reserve_


↪ benefit):
Forward recursion (with optional reserve benefit)

t_V_backward(x, s, t, premium, benefit, per_premium, per_policy, reserve_


↪ benefit):
Backward recursion (with optional reserve benefit)

t_V(x, s, t, premium, benefit, reserve_benefit, per_premium, per_policy):


(continues on next page)

82 Chapter 11. Reserves


Solving Actuarial Math with Python

(continued from previous page)


Solve year-t reserves by forward or backward recursion

r_V_forward(x, s, r, premium, benefit):


Forward recursion for interim reserves

r_V_backward(x, s, r, benefit):
Backward recursion for interim reserves

FPT_premium(x, s, n, b, first):
Initial or renewal Full Preliminary Term premiums

FPT_policy_value(x, s, t, b, n, endowment, discrete):


Compute Full Preliminary Term policy value at time t

V_plot(ax, color, title):


Plot values from reserves tables

reserves_frame():
Returns reserves table as a DataFrame

11.5 Examples

The FPT_premium and FPT_policy_value methods compute the Full Preliminary Term premiums and policy
values for whole life, temporary and endowment insurances.

import math
def S(x, s, t): # define a survival function
return [Link](-.00022*t - (.0000027*1.124**(x+s)*(1.124**t-1))/[Link](1.124))
life = Reserves().set_survival(S=S, minage=20, maxage=130)\
.set_interest(i=0.05)
P = life.net_premium(x=20, b=1000)
print('Level Net Premium:', P)
print('FPT premiums: ',
life.FPT_premium(x=20, b=1000, first=True),
life.FPT_premium(x=20, b=1000, first=False))
print('FPT policy values:',
[round(life.FPT_policy_value(x=20, b=1000, t=t), 4) for t in range(5)])

Level Net Premium: 2.465109289578718


FPT premiums: 0.2377514556176763 2.582546365777722
FPT policy values: [0, 0, 2.459, 5.0374, 7.7409]

The set_reserves method is called to load given values of reserves into an internal table, and then
fill_reserves method can be called to iteratively fill in missing values. Internally, this calls the t_V method
which attempts both the t_V_forward and t_V_backward methods that implement the recursion formula forwards
and backwards respectively. Interim reserves recursion formulas for fractional durations are similarly implemented in the
r_V_forward and r_V_backward methods. Values of reserves over time can be plotted with V_plot or displayed
with the reserves_frame methods.
SOA Question 7.31
For a fully discrete 3-year endowment insurance of 1000 on (x), you are given:
• Expenses, payable at the beginning of the year, are:

11.5. Examples 83
Solving Actuarial Math with Python

Year(s) Percent of Premium Per Policy


1 20% 15
2 and 3 8% 5

• The expense reserve at the end of year 2 is –23.64


• The gross annual premium calculated using the equivalence principle is G = 368.
• 𝐺 = 1000𝑃𝑥∶3| + 𝑃 𝑒 , where 𝑃 𝑒 is the expense loading
Calculate 𝑃𝑥∶3| .

print("SOA Question 7.31: (E) 0.310")


x = 0
life = Reserves().set_reserves(T=3)
G = 368.05
def fun(P): # solve net premium from expense reserve equation
return life.t_V(x=x, t=2, premium=G-P, benefit=lambda t: 0,
per_policy=5 + .08*G)
P = [Link](fun, target=-23.64, grid=[.29, .31]) / 1000
print(P)

SOA Question 7.31: (E) 0.310


0.309966

AMLCR2 Figure 7.4:


Policy values for each year of a 20-year term insurance, sum insured 500, 000, 𝑖𝑠𝑠𝑢𝑒𝑑𝑡𝑜(50).𝑀 𝑜𝑟𝑡𝑎𝑙𝑖𝑡𝑦𝑓𝑜𝑙𝑙𝑜𝑤𝑠𝑡ℎ𝑒𝑆𝑡𝑎𝑛𝑑𝑎𝑟𝑑𝑈 𝑙𝑡𝑖𝑚𝑎𝑡𝑒𝐿𝑖𝑓

from [Link] import SULT


life = SULT()
x, T, b = 50, 20, 500000 # $500K 20-year term insurance for (50)
P = life.net_premium(x=x, t=T, b=b)
life.set_reserves(T=T)\
.fill_reserves(x=x, contract=Contract(premium=P, benefit=b))
life.V_plot(title=f"Reserves for ${b} {T}-year term insurance issued to ({x})")

84 Chapter 11. Reserves


Solving Actuarial Math with Python

AMLCR2 Figure 7.3:


Policy values for each year of a 20-year endowment insurance, sum insured
500, 000, 𝑖𝑠𝑠𝑢𝑒𝑑𝑡𝑜(50).𝑀 𝑜𝑟𝑡𝑎𝑙𝑖𝑡𝑦𝑓𝑜𝑙𝑙𝑜𝑤𝑠𝑡ℎ𝑒𝑆𝑡𝑎𝑛𝑑𝑎𝑟𝑑𝑈 𝑙𝑡𝑖𝑚𝑎𝑡𝑒𝐿𝑖𝑓𝑒𝑇 𝑎𝑏𝑙𝑒(𝑛𝑜𝑡𝑒𝐴𝑀 𝐿𝐶𝑅2𝑢𝑠𝑒𝑑𝑆𝑡𝑎𝑛𝑑𝑎𝑟𝑑𝑆𝑒𝑙𝑒𝑐𝑡𝑇 𝑎𝑏𝑙𝑒), 𝑤𝑖𝑡ℎ𝑖𝑛𝑡𝑒

from [Link] import SULT


life = SULT()
x, T, b = 50, 20, 500000 # $500K 20-year term insurance for (50)
P = life.net_premium(x=x, t=T, b=b, endowment=b)
life.set_reserves(T=T)\
.fill_reserves(x=x, contract=Contract(premium=P, benefit=b, endowment=b))
life.V_plot(title=f"Reserves for ${b} {T}-year endowment insurance issued to ({x})")

11.5. Examples 85
Solving Actuarial Math with Python

86 Chapter 11. Reserves


CHAPTER

TWELVE

RECURSION

Using annual values provided we can calculate other values at other ages and durations by applying recursion formulas
and other actuarial identities.

12.1 Chain rule

𝑡+𝑛 𝑝𝑥 = 𝑛 𝑝𝑥 ⋅ 𝑡 𝑝𝑥+𝑛
• survival probability chain rule

𝑡+𝑛 𝐸𝑥 = 𝑛 𝐸𝑥 ⋅ 𝑡 𝐸𝑥+𝑛
• pure endowment chain rule

12.2 Expected future lifetime

For both complete and curtate future lifetime, we can decompose the formula for the life expectancy of (𝑥) into a
temporary life expectancy, plus the probability of surviving till the end of that term times the remaining life expectancy.
𝑒𝑥 = 𝑒𝑥∶𝑚| + 𝑚 𝑝𝑥 𝑒𝑥+𝑚
• recursion formula for curtate expectation of lifetime
∘ ∘ ∘
𝑒𝑥 = 𝑒𝑥∶𝑚| + 𝑚 𝑝𝑥 𝑒𝑥+𝑚
• recursion formula for complete expectation of lifetime
𝑒𝑥∶𝑚+𝑛| = 𝑒𝑥∶𝑚| + 𝑚 𝑝𝑥 𝑒𝑥∶𝑛|
• recursion formula for limited curtate expectation of lifetime
∘ ∘ ∘
𝑒𝑥∶𝑚+𝑛| = 𝑒𝑥∶𝑚| + 𝑚 𝑝𝑥 𝑒𝑥+𝑚∶𝑛|
• recursion formula for limited complete expectation of lifetime
𝑒𝑥 = 𝑝𝑥 (1 + 𝑒𝑥+1 )
• special case of a one-year recursion formula for curtate expectaion of lifetime
∘ ∘ ∘
𝑒𝑥 = 𝑒𝑥∶1| + 𝑝𝑥 𝑒𝑥+1
• special case of a one-year recursion formula for complete expectation of lifetime
𝑒𝑥∶1| = 𝑝𝑥
• shortcut for one-year limited curtate expectaion of lifetime

87
Solving Actuarial Math with Python

12.3 Life insurance

𝐴𝑥 − 𝑣 𝑞 𝑥
𝐴𝑥 = 𝑣 𝑞𝑥 + 𝑣 𝑝𝑥 𝐴𝑥+1 ⇒ 𝐴𝑥+1 =
𝑣 𝑝𝑥
• whole life insurance recursion
𝐴1𝑥∶𝑡| = 𝑣 𝑞𝑥 + 𝑣 𝑝𝑥 𝐴1𝑥+1∶𝑡−1|

• term life insurance recursion


𝐴1𝑥∶1| = 𝑣 𝑞𝑥

• shortcut for one-year term life insurance


2
𝐴1𝑥∶1| = 𝑣2 𝑞𝑥

• shortcut for second moment of one-year term life insurance


𝐴𝑥∶0| = 0 𝐸𝑥 = 1
• endowment insurance at end of term is pure endowment
𝐴𝑥∶1| = 𝑞𝑥 𝑣 + 𝑝𝑥 𝑣 = 𝑣
• shortcut for one-year endowment insurance
2
𝐴𝑥∶1| = 𝑣2
• shortcut for second moment of one-year endowment insurance
𝐼𝐴1𝑥∶𝑡| = 𝑣 𝑞𝑥 + 𝑣 𝑝𝑥 (𝐴𝑥+1 + 𝐼𝐴1𝑥+1∶𝑡−1| )

• increasing insurance recursion


𝐷𝐴1𝑥∶𝑡| = 𝑡 𝑣 𝑞𝑥 + 𝑣 𝑝𝑥 (𝐷𝐴1𝑥+1∶𝑡−1| )

• decreasing insurance recursion

12.4 Life annuities

𝑎𝑥̈ − 1
𝑎𝑥̈ = 1 + 𝑣 𝑝𝑥 𝑎𝑥+1
̈ ⇒ 𝑎𝑥+1
̈ =
𝑣 𝑝𝑥
• whole life annuity recursion
𝑎𝑥∶𝑡|
̈ = 1 + 𝑣 𝑝𝑥 𝑎𝑥+1∶𝑡−1|
̈
• temporary annuity recursion
𝑎𝑥∶1|
̈ =1
• shortcut for one-year temporary annuity

88 Chapter 12. Recursion


Solving Actuarial Math with Python

12.5 Methods

The Recursion class implements methods to apply recursive, shortcut and actuarial formulas, and traces the steps taken
to find the solution.
Caveats:
1. Not all possible recursion rules and actuarial equations have (yet) been implemented in the present version of the
package.
2. You may set the recursion depth to a larger limit than the default of 3 (with the keyword argument depth when
initializing a Recursion class object).
3. But generally, the current implementation may be fragile if the solution is not available within a relatively shallow
search.
Notes:
• If a colab or jupyter notebook is auto-detected, the steps are displayed in latex format; else as raw text.
• These display options can be changed by calling the blog_options static method

from actuarialmath import Recursion, ConstantForce, Contract


import describe
[Link](Recursion)

class Recursion - Solve by appling recursive, shortcut and actuarial formulas␣


↪repeatedly

Args:
depth : maximum depth of recursions (default is 3)
verbose : whether to echo recursion steps (True, default)

Notes:
7 types of function values can be loaded for recursion computations:

- 'q' : (deferred) probability (x) dies in t years


- 'p' : probability (x) survives t years
- 'e' : (temporary) expected future lifetime, or moments
- 'A' : deferred, term, endowment or whole life insurance, or moments
- 'IA' : decreasing life insurance of t years
- 'DA' : increasing life insurance of t years
- 'a' : deferred, temporary or whole life annuity of t years, or moments

Methods:
--------

set_q(val, x, s, t, u):
Set mortality rate u|t_q_[x+s] to given value

set_p(val, x, s, t):
Set survival probability t_p_[x+s] to given value

set_e(val, x, s, t, curtate, moment):


Set expected future lifetime e_[x+s]:t to given value

set_E(val, x, s, t, endowment, moment):


Set pure endowment t_E_[x+s] to given value
(continues on next page)

12.5. Methods 89
Solving Actuarial Math with Python

(continued from previous page)

set_A(val, x, s, t, u, b, moment, endowment, discrete):


Set insurance u|_A_[x+s]:t to given value

set_IA(val, x, s, t, b, discrete):
Set increasing insurance IA_[x+s]:t to given value

set_DA(val, x, s, t, b, discrete):
Set decreasing insurance DA_[x+s]:t to given value

set_a(val, x, s, t, u, b, variance, discrete):


Set annuity u|_a_[x+s]:t to given value

blog_options(latex, notebook):
Static method to change display options for tracing the recursion steps

12.6 Examples

The given values of functions are set by calling the respective “setter” methods set_q, set_p, set_e, set_E,
set_A, set_IA, set_DA, or set_a. Then derived function values, at other ages or durations, can be computed
– the successful recursion steps taken are displayed if the verbose flag is initiated to True (which is the default value).
AMLCR2 Exercise 2.6
Given 𝑃𝑥 = 0.99 , 𝑃𝑥+1 = 0.985, 3 𝑃𝑥+1 = 0.95, 𝑞𝑥+3 = 0.02,
Calculate (a) 𝑃𝑥+3 , (b) 2 𝑃𝑥 , (c) 2 𝑃𝑥+1 , (d) 3 𝑃𝑥 , (e) 1|2 𝑞𝑥 .

from [Link] import Recursion


x = 0
life = Recursion(depth=3).set_interest(i=0.06)\
.set_p(0.99, x=x)\
.set_p(0.985, x=x+1)\
.set_p(0.95, x=x+1, t=3)\
.set_q(0.02, x=x+3)

print(life.p_x(x=x+3), 0.98)
print(life.p_x(x=x, t=2), 0.97515)
print(life.p_x(x=x+1, t=2), 0.96939)
print(life.p_x(x=x, t=3), 0.95969)
print(life.q_x(x=x, t=2, u=1), 0.03031)

Survival 𝑝𝑥+3 ∶=
𝑝𝑥+3 = 1 − 𝑞𝑥+3 complement of mortality

0.98 0.98

90 Chapter 12. Recursion


Solving Actuarial Math with Python

Survival 2 𝑝𝑥 ∶=
2 𝑝𝑥 = 3 𝑝𝑥 / 𝑝𝑥+2 survival chain rule
𝑝𝑥+2 = 2 𝑝𝑥+1 / 𝑝𝑥+1 survival chain rule
2 𝑝𝑥+1 = 3 𝑝𝑥+1 / 𝑝𝑥+3 survival chain rule
3 𝑝𝑥 = 4 𝑝𝑥 / 𝑝𝑥+3 survival chain rule
4 𝑝𝑥 = 3 𝑝𝑥+1 ∗ 𝑝𝑥 survival chain rule
𝑝𝑥+3 = 1 − 𝑞𝑥+3 complement of mortality

0.9751500000000001 0.97515

Survival 2 𝑝𝑥+1 ∶=
2 𝑝𝑥+1 = 3 𝑝𝑥 / 𝑝𝑥 survival chain rule
3 𝑝𝑥 = 4 𝑝𝑥 / 𝑝𝑥+3 survival chain rule
4 𝑝𝑥 = 3 𝑝𝑥+1 ∗ 𝑝𝑥 survival chain rule
𝑝𝑥+3 = 1 − 𝑞𝑥+3 complement of mortality

0.9693877551020409 0.96939

Survival 3 𝑝𝑥 ∶=
3 𝑝𝑥 = 4 𝑝𝑥 / 𝑝𝑥+3 survival chain rule
4 𝑝𝑥 = 3 𝑝𝑥+1 ∗ 𝑝𝑥 survival chain rule
𝑝𝑥+3 = 1 − 𝑞𝑥+3 complement of mortality

0.9596938775510204 0.95969

Mortality 1|2 𝑞𝑥 ∶=
1|2 𝑞𝑥 = 𝑝𝑥 − 3 𝑝𝑥 complement survival
3 𝑝𝑥 = 4 𝑝𝑥 / 𝑝𝑥+3 survival chain rule
4 𝑝𝑥 = 3 𝑝𝑥+1 ∗ 𝑝𝑥 survival chain rule
𝑝𝑥+3 = 1 − 𝑞𝑥+3 complement of mortality

0.030306122448979567 0.03031

SOA Question 6.40


For a special fully discrete whole life insurance, you are given:
• The death benefit is 1000(1.03)𝑘 for death in policy year k, for 𝑘 = 1, 2, 3...
• 𝑞𝑥 = 0.05
• 𝑖 = 0.06
• 𝑎𝑥+1
̈ = 7.00
• The annual net premium for this insurance at issue age x is 110
Calculate the annual net premium for this insurance at issue age 𝑥 + 1.

print("SOA Question 6.40: (C) 116 ")


x = 0
life = Recursion().set_interest(i=0.06).set_a(7, x=x+1).set_q(0.05, x=x)
a = life.whole_life_annuity(x)
A = 110 * a / 1000
print(a, A)
life = Recursion().set_interest(i=0.06).set_A(A, x=x).set_q(0.05, x=x)
(continues on next page)

12.6. Examples 91
Solving Actuarial Math with Python

(continued from previous page)


A1 = life.whole_life_insurance(x+1)
P = life.gross_premium(A=A1 / 1.03, a=7) * 1000
print(P)

SOA Question 6.40: (C) 116

Whole Life Annuity 𝑎𝑥̈ ∶=


𝑎𝑥̈ = 1 + 𝐸𝑥 ∗ 𝑎𝑥+1
̈ backward recursion
𝐸𝑥 = 𝑝𝑥 ∗ 𝑣 pure endowment
𝑝𝑥 = 1 − 𝑞 𝑥 complement of mortality

7.2735849056603765 0.8000943396226414

Whole Life Insurance 𝐴𝑥+1 ∶=


𝐴𝑥+1 = [ 𝐴𝑥 /𝑣 − 𝑞𝑥 ∗ 𝑏]/ 𝑝𝑥 forward recursion
𝑝𝑥 = 1 − 𝑞 𝑥 complement of mortality

116.51945397474269

SOA Question 6.10 : (D) 0.91


For a fully discrete 3-year term insurance of 1000 on (x), you are given:
1. 𝑝𝑥 = 0.975
2. i = 0.06
3. The actuarial present value of the death benefit is 152.85
4. The annual net premium is 56.05
Calculate 𝑝𝑥+2 .

print("SOA Question 6.10: (D) 0.91")


x = 0
life = Recursion(depth=5).set_interest(i=0.06)\
.set_p(0.975, x=x)\
.set_a(152.85/56.05, x=x, t=3)\
.set_A(152.85, x=x, t=3, b=1000)
p = life.p_x(x=x+2)
print(p)

SOA Question 6.10: (D) 0.91

Survival 𝑝𝑥+2 ∶=
𝑝𝑥+2 = 𝐸𝑥+2 /𝑣 one-year pure endowment
𝐸𝑥+2 = 𝐴𝑥+2∶1| − 𝐴1𝑥+2∶1| endowment insurance minus term
𝐴1𝑥+2∶1| = [ 𝐴1𝑥+1∶2| /𝑣 − 𝑞𝑥+1 ∗ 𝑏]/ 𝑝𝑥+1 forward recursion
𝑝𝑥+1 = [ 𝑎𝑥+1∶2|
̈ − 1]/[𝑣 ∗ 𝑎𝑥+2∶1|
̈ ] annuity recursion
𝑎𝑥+1∶2|
̈ = [ 𝑎𝑥∶3|
̈ − 1]/ 𝐸𝑥 forward recursion
𝐴1𝑥+1∶2| = [ 𝐴1𝑥∶3| /𝑣 − 𝑞𝑥 ∗ 𝑏]/ 𝑝𝑥 forward recursion
𝐸𝑥 = 𝑝𝑥 ∗ 𝑣 pure endowment

92 Chapter 12. Recursion


Solving Actuarial Math with Python

0.9097382950525702

SOA Question 6.48


For a special fully discrete 5-year deferred 3-year term insurance of 100,000 on (x) you are given:
• There are two premium payments, each equal to P . The first is paid at the beginning of the first year and the second
is paid at the end of the 5-year deferral period
• 𝑝𝑥 = 0.95
• 𝑞𝑥+5 = 0.02
• 𝑞𝑥+6 = 0.03
• 𝑞𝑥+7 = 0.04
• 𝑖 = 0.06
Calculate P using the equivalence principle.

print("SOA Question 6.48: (A) 3195")


life = Recursion(depth=3).set_interest(i=0.06)
x = 0
life.set_p(0.95, x=x, t=5)
life.set_q(0.02, x=x+5)
life.set_q(0.03, x=x+6)
life.set_q(0.04, x=x+7)
a = 1 + life.E_x(x, t=5)
A = life.deferred_insurance(x, u=5, t=3)
P = life.gross_premium(A=A, a=a, benefit=100000)
print(P)

SOA Question 6.48: (A) 3195

Pure Endowment 5 𝐸𝑥 ∶=
5
5 𝐸𝑥 = 5 𝑝𝑥 ∗ 𝑣 pure endowment

Pure Endowment 5 𝐸𝑥 ∶=
5
5 𝐸𝑥 = 5 𝑝𝑥 ∗ 𝑣 pure endowment
Term Insurance 𝐴1𝑥+5∶3| ∶=
𝐴1𝑥+5∶3| = 𝐴𝑥+5∶3| − 3 𝐸𝑥+5 endowment insurance - pure
3 𝐸𝑥+5 = 𝐸𝑥+5 ∗ 2 𝐸𝑥+6 pure endowment chain rule
2 𝐸𝑥+6 = 𝐸𝑥+6 ∗ 𝐸𝑥+7 pure endowment chain rule
𝐸𝑥+7 = 𝑝𝑥+7 ∗ 𝑣 pure endowment
𝐸𝑥+6 = 𝑝𝑥+6 ∗ 𝑣 pure endowment
𝐸𝑥+5 = 𝑝𝑥+5 ∗ 𝑣 pure endowment
𝑝𝑥+7 = 1 − 𝑞𝑥+7 complement of mortality
𝐴1𝑥+5∶3| = 𝑣 ∗ [ 𝑞𝑥+5 ∗ 𝑏 + 𝑝𝑥+5 ∗ 𝐴1𝑥+6∶2| ] backward recursion
𝐴1𝑥+6∶2| = 𝑣 ∗ [ 𝑞𝑥+6 ∗ 𝑏 + 𝑝𝑥+6 ∗ 𝐴1𝑥+7∶1| ] backward recursion
𝑝𝑥+6 = 1 − 𝑞𝑥+6 complement of mortality
𝑝𝑥+5 = 1 − 𝑞𝑥+5 complement of mortality

12.6. Examples 93
Solving Actuarial Math with Python

Term Insurance 𝐴1𝑥+5∶3| ∶=


𝐴1𝑥+5∶3| = 𝐴𝑥+5∶3| − 3 𝐸𝑥+5 endowment insurance - pure
3 𝐸𝑥+5 = 𝐸𝑥+5 ∗ 2 𝐸𝑥+6 pure endowment chain rule
2 𝐸𝑥+6 = 𝐸𝑥+6 ∗ 𝐸𝑥+7 pure endowment chain rule
𝐸𝑥+7 = 𝑝𝑥+7 ∗ 𝑣 pure endowment
𝐸𝑥+6 = 𝑝𝑥+6 ∗ 𝑣 pure endowment
𝐸𝑥+5 = 𝑝𝑥+5 ∗ 𝑣 pure endowment
𝑝𝑥+7 = 1 − 𝑞𝑥+7 complement of mortality
𝐴1𝑥+5∶3| = 𝑣 ∗ [ 𝑞𝑥+5 ∗ 𝑏 + 𝑝𝑥+5 ∗ 𝐴1𝑥+6∶2| ] backward recursion
𝐴1𝑥+6∶2| = 𝑣 ∗ [ 𝑞𝑥+6 ∗ 𝑏 + 𝑝𝑥+6 ∗ 𝐴1𝑥+7∶1| ] backward recursion
𝑝𝑥+6 = 1 − 𝑞𝑥+6 complement of mortality
𝑝𝑥+5 = 1 − 𝑞𝑥+5 complement of mortality

3195.118917658744

SOA Question 6.17


An insurance company sells special fully discrete two-year endowment insurance policies to smokers (S) and non-smokers
(NS) age x. You are given:
• The death benefit is 100,000; the maturity benefit is 30,000
• The level annual premium for non-smoker policies is determined by the equivalence principle
• The annual premium for smoker policies is twice the non-smoker annual premium
• 𝜇𝑁𝑆
𝑥+𝑡 = 0.1. 𝑡 > 0
𝑆 𝑁𝑆
• 𝑞𝑥+𝑘 = 1.5𝑞𝑥+𝑘 , for 𝑘 = 0, 1
• 𝑖 = 0.08
Calculate the expected present value of the loss at issue random variable on a smoker policy.

print("SOA Question 6.17: (A) -30000")


x = 0
life = ConstantForce(mu=0.1).set_interest(i=0.08)
A = life.endowment_insurance(x, t=2, b=100000, endowment=30000)
a = life.temporary_annuity(x, t=2)
P = life.gross_premium(a=a, A=A)
print(A, a, P)

life1 = Recursion().set_interest(i=0.08)\
.set_q(life.q_x(x, t=1) * 1.5, x=x, t=1)\
.set_q(life.q_x(x+1, t=1) * 1.5, x=x+1, t=1)
contract = Contract(premium=P * 2, benefit=100000, endowment=30000)
L = life1.gross_policy_value(x, t=0, n=2, contract=contract)
print(L)

SOA Question 6.17: (A) -30000


37251.49857703495 1.8378124241073746 20269.478042694158

94 Chapter 12. Recursion


Solving Actuarial Math with Python

Term Insurance 𝐴1𝑥∶2| ∶=


𝐴1𝑥∶2| = 𝐴𝑥∶2| − 2 𝐸𝑥 endowment insurance - pure
2
2 𝐸𝑥 = 2 𝑝𝑥 ∗ 𝑣 pure endowment
2 𝑝𝑥 = 𝑝𝑥+1 ∗ 𝑝𝑥 survival chain rule
𝐴1𝑥∶2| = 𝑣 ∗ [ 𝑞𝑥 ∗ 𝑏 + 𝑝𝑥 ∗ 𝐴1𝑥+1∶1| ] backward recursion
𝑝𝑥+1 = 1 − 𝑞𝑥+1 complement of mortality
𝐸𝑥 = 𝑝𝑥 ∗ 𝑣 pure endowment
𝑝𝑥 = 1 − 𝑞𝑥 complement of mortality

Temporary Annuity 𝑎𝑥∶2| ̈ ∶=


𝑎𝑥∶2|
̈ = 1 + 𝐸𝑥 ∗ 𝑎𝑥+1∶1|
̈ backward recursion
𝐸𝑥 = 𝑝𝑥 ∗ 𝑣 pure endowment
𝑝𝑥 = 1 − 𝑞 𝑥 complement of mortality
Pure Endowment 2 𝐸𝑥 ∶=
2
2 𝐸𝑥 = 2 𝑝𝑥 ∗ 𝑣 pure endowment
2 𝑝𝑥 = 𝑝𝑥+1 ∗ 𝑝𝑥 survival chain rule
𝑝𝑥 = 1 − 𝑞 𝑥 complement of mortality
𝑝𝑥+1 = 1 − 𝑞𝑥+1 complement of mortality

-30107.42633581115

SOA Question 2.5 : (B) 37.1


You are given the following:
1. 𝑒40∶20 = 18
2. 𝑒60 = 25
3. 20 𝑞40 = 0.2
4. 𝑞40 = 0.003
Calculate 𝑒41 .
hints:
• solve for 𝑒40 from limited lifetime formula
• compute 𝑒41 using backward recursion

print("SOA Question 2.5: (B) 37.1")


life = Recursion(verbose=True).set_e(25, x=60, curtate=True)\
.set_q(0.2, x=40, t=20)\
.set_q(0.003, x=40)\
.set_e(18, x=40, t=20, curtate=True)
e = life.e_x(41, curtate=True)
print(e)

SOA Question 2.5: (B) 37.1

12.6. Examples 95
Solving Actuarial Math with Python

Lifetime 𝑒𝑥+41 ∶=
𝑒𝑥+41 = [ 𝑒𝑥+40 − 𝑒𝑥+40∶1| ]/ 𝑝𝑥+40 forward recursion
𝑒𝑥+40 = 𝑒𝑥+40∶20| + 20 𝑝𝑥+40 ∗ 𝑒𝑥+60 backward recursion
20 𝑝𝑥+40 = 1 − 20 𝑞𝑥+40 complement of mortality
𝑒𝑥+40∶1| = 𝑝𝑥+40 1-year curtate shortcut
𝑝𝑥+40 = 1 − 𝑞𝑥+40 complement of mortality

37.11434302908726

96 Chapter 12. Recursion


CHAPTER

THIRTEEN

LIFE TABLE

A life table, from some initial age 𝑥0 to a maximum age 𝜔, represents a survival model with probabilities 𝑡 𝑝𝑥 . A life table
is typically tabulated for the number of lives 𝑙𝑥 at integer ages 𝑥 only – fractional age assumptions would be needed to
calculate survival probabilities for non-integer ages and durations.
𝑙𝑥0 +𝑡 = 𝑙𝑥0 𝑡 𝑝𝑥0 .
• 𝑙𝑥0 is an arbitrary positive number of lives at starting age 𝑥0 called the radix
𝑑𝑥 = 𝑙𝑥 − 𝑙𝑥+1
• it is usual for a life table to also show the values of 𝑑𝑥 , the expected of deaths in the year of age 𝑥 to 𝑥 + 1.
𝑑𝑥
𝑞𝑥 =
𝑙𝑥
• the mortality rate can then be derived, which is the probability that a life aged 𝑥 dies within one year.
Recursion formulas from earlier sections allow the recursive construction of a table of other life contingent values, such
as life expectancy, insurance, and annuities, at all ages.

13.1 Methods

The LifeTable class specifies a given life table to be the survival model. It inherits all the general methods for com-
puting life contingency risks, and overrides those methods where values can be looked up or calculated from life table
entries.

from actuarialmath import LifeTable


import describe
[Link](LifeTable)

class LifeTable - Calculate life table, and iteratively fill in missing values

Args:
udd : assume UDD or constant force of mortality for fractional ages
verbose : whether to echo update steps

Notes:
4 types of columns can be loaded and calculated in the life table:

- 'q' : probability (x) dies in one year


- 'l' : number of lives aged x
- 'd' : number of deaths of age x
- 'p' : probability (x) survives at least one year
(continues on next page)

97
Solving Actuarial Math with Python

(continued from previous page)

Methods:
--------

set_table(fill, minage, maxage, l, d, p, q):


Update life table

fill_table(radix):
Iteratively fill in missing table cells (does not check consistency)

frame():
Return life table columns and values in a DataFrame

__getitem__(col):
Returns a column of the life table

13.2 Examples

The set_table method is called to load a life table, by age, with given values of number of lives, number of deaths,
mortality rate, and/or survival probability: if the fill flag is set to True (by default), fill_table is automatically
called to fill in any missing values using recursion and identify formulas. All other computational methods can then be
called in the usual manner, which will use the survival model provided by the life table.
AMLCR2 Exercise 3.2
You are given the following life table extract.

Age,x 𝑙𝑥
52 89948
53 89089
54 88176
55 87208
56 86181
57 85093
58 83940
59 82719
60 81429

Calculate
• 0.2 𝑞52.4 assuming UDD (fractional age assumption),
• 0.2 𝑞52.4 assuming constant force of mortality (fractional age assumption),
• 5.7 𝑝52.4 assuming UDD,
• 5.7 𝑝52.4 assuming constant force of mortality,
• 3.2|2.5 𝑞52.4 assuming UDD, and
• 3.2|2.5 𝑞52.4 assuming constant force of mortality.

98 Chapter 13. Life Table


Solving Actuarial Math with Python

table = {x:l for x,l in zip(range(52, 61),


[89948, 89089, 88176, 87208, 86181,
85093, 83940, 82719, 81429])}
life1 = LifeTable(udd=True).set_table(l=table)
life2 = LifeTable(udd=False).set_table(l=table)
print([round(r, 6) for r in [life1.q_r(x=52, r=0.4, t=0.2), # 0.001917 ␣

life2.q_r(x=52, r=0.4, t=0.2), # 0.001917 ␣


life1.p_r(x=52, r=0.4, t=5.7), # 0.935422 ␣


life2.p_r(x=52, r=0.4, t=5.7), # 0.935423 ␣


life1.q_r(x=52, r=0.4, u=3.2, t=2.5), # 0.030957 ␣


life2.q_r(x=52, r=0.4, u=3.2, t=2.5)]]) # 0.030950

[0.001917, 0.001917, 0.935422, 0.935423, 0.030957, 0.03095]

SOA Question 6.53


A warranty pays 2000 at the end of the year of the first failure if a washing machine fails within three years of purchase.
The warranty is purchased with a single premium, G, paid at the time of purchase of the washing machine. You are given:
• 10% of the washing machines that are working at the start of each year fail by the end of that year
• i = 0.08
• The sales commission is 35% of G
• G is calculated using the equivalence principle
Calculate G.

print("SOA Question 6.53: (D) 720")


x = 0
life = LifeTable().set_interest(i=0.08)\
.set_table(q={x: 0.1, x+1: 0.1, x+2: 0.1})
A = life.term_insurance(x, t=3)
G = life.gross_premium(a=1, A=A, benefit=2000, initial_premium=0.35)
print(A, G)
print([Link]())

SOA Question 6.53: (D) 720


0.23405349794238678 720.1646090534978
l d q p
0 100000.0 10000.0 0.1 0.9
1 90000.0 9000.0 0.1 0.9
2 81000.0 8100.0 0.1 0.9
3 72900.0 NaN NaN NaN

SOA Question 6.41


For a special fully discrete 2-year term insurance on (x), you are given:
• 𝑞𝑥 = 0.01
• 𝑞𝑥+1 = 0.02

13.2. Examples 99
Solving Actuarial Math with Python

• 𝑖 = 0.05
• The death benefit in the first year is 100,000
• Both the benefits and premiums increase by 1% in the second year
Calculate the annual net premium in the first year.

print("SOA Question 6.41: (B) 1417")


x = 0
life = LifeTable().set_interest(i=0.05)\
.set_table(q={x:.01, x+1:.02})
P = 1416.93
a = 1 + life.E_x(x, t=1) * 1.01
A = (life.deferred_insurance(x, u=0, t=1)
+ 1.01 * life.deferred_insurance(x, u=1, t=1))
print(a, A)
P = 100000 * A / a
print(P)
print([Link]())

SOA Question 6.41: (B) 1417


1.9522857142857144 0.027662585034013608
1416.9332301924137
l d q p
0 100000.0 1000.0 0.01 0.99
1 99000.0 1980.0 0.02 0.98
2 97020.0 NaN NaN NaN

SOA Question 3.11


For the country of Bienna, you are given:
• Bienna publishes mortality rates in biennial form, that is, mortality rates are of the form: 2 𝑞2𝑥 , for 𝑥 = 0, 1, 2, ...
• Deaths are assumed to be uniformly distributed between ages 2𝑥 and 2𝑥 + 2, for 𝑥 = 0, 1, 2, ...
• 2 𝑞50 = 0.02
• 2 𝑞52 = 0.04 Calculate the probability that (50) dies during the next 2.5 years.

print("SOA Question 3.11: (B) 0.03")


life = LifeTable(udd=True).set_table(q={50//2: .02, 52//2: .04})
print(life.q_r(50//2, t=2.5/2))
print([Link]())

SOA Question 3.11: (B) 0.03


0.0298
l d q p
25 100000.0 2000.0 0.02 0.98
26 98000.0 3920.0 0.04 0.96
27 94080.0 NaN NaN NaN

SOA Question 3.5


You are given:

100 Chapter 13. Life Table


Solving Actuarial Math with Python

𝑥 60 61 62 63 64 65 66 67
𝑙𝑥 99,999 88,888 77,777 66,666 55,555 44,444 33,333 22,222

𝑎= 3.4|2.5 𝑞60 assuming a uniform distribution of deaths over each year of age
𝑏= 3.4|2.5 𝑞60 assuming a constant force of mortality over each year of age
Calculate 100, 000(𝑎 − 𝑏)

print("SOA Question 3.5: (E) 106")


l = {60+x: n*11111 for x,n in enumerate([9, 8, 7, 6, 5, 4, 3, 2])}
a, b = (LifeTable(udd=udd).set_table(l=l).q_r(60, u=3.4, t=2.5)
for udd in [True, False])
print(100000 * (a - b))

SOA Question 3.5: (E) 106


106.16575827938624

SOA Question 3.14


You are given the following information from a life table:

x 𝑙𝑥 𝑑𝑥 𝑝𝑥 𝑞𝑥
95 − − − 0.40
96 − − 0.20 −
97 − 72 − 1.00

You are also given:


• 𝑙90 = 1000 and 𝑙93 = 825
• Deaths are uniformly distributed over each year of age.
Calculate the probability that (90) dies between ages 93 and 95.5.

print("SOA Question 3.14: (C) 0.345")


life = LifeTable(udd=True).set_table(l={90: 1000, 93: 825},
d={97: 72},
p={96: .2},
q={95: .4, 97: 1})
print(life.q_r(90, u=93-90, t=95.5-93))
print([Link]())

SOA Question 3.14: (C) 0.345


0.345
l d q p
90 1000.0 NaN NaN NaN
93 825.0 NaN NaN NaN
95 600.0 240.0 0.4 0.6
96 360.0 288.0 0.8 0.2
97 72.0 72.0 1.0 0.0
98 0.0 NaN NaN NaN

13.2. Examples 101


Solving Actuarial Math with Python

102 Chapter 13. Life Table


CHAPTER

FOURTEEN

SULT

14.1 Standard ultimate life table

This tabulates single net premiums and basic functions (whole life and endowment insurances, whole life and temporary
annuities and pure endowments) for several time periods at integer ages between 20 and 100 years. According to the
SOA’s “Excel Workbook for FAM-L Tables”, this table was developed from the following assumptions:
• constant interest rate 𝑖 = 0.05
• radix of 100000 initial lives aged 20
• incorporates Makeham’s Law as its survival model with 𝐴 = 0.00022, 𝐵 = 0.0000027, 𝑐 = 1.124

14.2 Pure endowment

Pure endowment functions can be calculated from numbers of lives survived and compounded interest rates.
𝑙𝑥+𝑡
𝑡 𝐸𝑥 = 𝑣𝑡
𝑙𝑥
2 𝑙𝑥+𝑡
𝑡 𝐸𝑥 = 𝑣2𝑡 = 𝑣𝑡 𝑡 𝐸𝑥
𝑙𝑥

14.3 Term life insurance

Term life insurance functions can be calculated from whole life insurance and pure endowment table columns.
𝐴1𝑥∶𝑡| = 𝐴𝑥 − 𝑡 𝐸𝑥 𝐴𝑥+𝑡 = 𝐴𝑥∶𝑡| − 𝑡 𝐸𝑥
2
𝐴1𝑥∶𝑡| = 2 𝐴𝑥 − 2𝑡 𝐸𝑥 2 𝐴𝑥+𝑡 = 2 𝐴𝑥 − 𝑣𝑡 𝑡 𝐸𝑥 2 𝐴𝑥+𝑡

103
Solving Actuarial Math with Python

14.4 Methods

The SULT class implements an instance of a LifeTable, called the standard ultimate life table, which is based on Make-
ham’s Law with parameters specified in SOA’s “Excel Workbook for FAM-L Tables”

import math
from actuarialmath import SULT
import describe
[Link](SULT)

class SULT - Generates and uses a standard ultimate life table

Args:
i : interest rate
radix : initial number of lives
minage : minimum age
maxage : maximum age
S : survival function, default is Makeham with SOA FAM-L parameters

Examples:
>>> sult = SULT()
>>> a = sult.temporary_annuity(70, t=10)
>>> A = sult.deferred_annuity(70, u=10)
>>> P = sult.gross_premium(a=a, A=A, benefit=100000, initial_premium=0.75,
>>> renewal_premium=0.05)

Methods:
--------

frame(minage, maxage):
Derive FAM-L exam table columns of SULT as a DataFrame

__getitem__(col):
Returns a column of the sult table

14.5 Examples

SOA Question 6.52


For a fully discrete 10-payment whole life insurance of H on (45), you are given:
• Expenses payable at the beginning of each year are as follows:

Expense Type First Year Years 2-10 Years 11+


Per policy 100 20 10
% of Premium 105% 5% 0%

• Mortality follows the Standard Ultimate Life Table


• i = 0.05
• The gross annual premium, calculated using the equivalence principle, is of the form 𝐺 = 𝑔𝐻 + 𝑓, where 𝑔 is the
premium rate per 1 of insurance and 𝑓 is the per policy fee

104 Chapter 14. SULT


Solving Actuarial Math with Python

Calculate 𝑓.

print("SOA Question 6.52: (D) 50.80")


sult = SULT()
a = sult.temporary_annuity(45, t=10)
other_cost = 10 * sult.deferred_annuity(45, u=10)
P = sult.gross_premium(a=a,
A=0,
benefit=0,
initial_premium=1.05,
renewal_premium=0.05,
initial_policy=100 + other_cost,
renewal_policy=20)
print(a, P)

SOA Question 6.52: (D) 50.80


8.0750937741422 50.80135534704229

SOA Question 6.47


For a 10-year deferred whole life annuity-due with payments of 100,000 per year on (70), you are given:
• Annual gross premiums of 𝐺 are payable for 10 years
• First year expenses are 75% of premium
• Renewal expenses for years 2 and later are 5% of premium during the premium paying period
• Mortality follows the Standard Ultimate Life Table
• i = 0.05
Calculate 𝐺 using the equivalence principle.

print("SOA Question 6.47: (D) 66400")


sult = SULT()
a = sult.temporary_annuity(70, t=10)
A = sult.deferred_annuity(70, u=10)
P = sult.gross_premium(a=a, A=A, benefit=100000, initial_premium=0.75,
renewal_premium=0.05)
print(P)

SOA Question 6.47: (D) 66400


66384.13293704337

SOA Question 6.43


For a fully discrete, 5-payment 10-year term insurance of 200,000 on (30), you are given:
• Mortality follows the Standard Ultimate Life Table
• The following expenses are incurred at the beginning of each respective year:

14.5. Examples 105


Solving Actuarial Math with Python

Percent of Premium Per Policy Percent of Premium Per Policy

Year 1 Year 1 Years 2 - 10 Years 2 - 10

Taxes 5% 0 5% 0
Commissions 30% 0 10% 0
Maintenance 0% 8 0% 4

• i = 0.05
• 𝑎30∶5|
̈ = 4.5431
Calculate the annual gross premium using the equivalence principle.

print("SOA Question 6.43: (C) 170")


sult = SULT()
a = sult.temporary_annuity(30, t=5)
A = sult.term_insurance(30, t=10)
other_expenses = 4 * sult.deferred_annuity(30, u=5, t=5)
P = sult.gross_premium(a=a, A=A, benefit=200000, initial_premium=0.35,
initial_policy=8 + other_expenses, renewal_policy=4,
renewal_premium=0.15)
print(P)

SOA Question 6.43: (C) 170


171.22371939459944

SOA Question 6.39


XYZ Insurance writes 10,000 fully discrete whole life insurance policies of 1000 on lives age 40 and an additional 10,000
fully discrete whole life policies of 1000 on lives age 80.
XYZ used the following assumptions to determine the net premiums for these policies:
• Mortality follows the Standard Ultimate Life Table
• i = 0.05
During the first ten years, mortality did follow the Standard Ultimate Life Table.
Calculate the average net premium per policy in force received at the beginning of the eleventh year.

print("SOA Question 6.39: (A) 29")


sult = SULT()
P40 = sult.premium_equivalence(sult.whole_life_insurance(40), b=1000)
P80 = sult.premium_equivalence(sult.whole_life_insurance(80), b=1000)
p40 = sult.p_x(40, t=10)
p80 = sult.p_x(80, t=10)
P = (P40 * p40 + P80 * p80) / (p80 + p40)
print(P)

SOA Question 6.39: (A) 29


29.033866427845496

SOA Question 6.37


For a fully discrete whole life insurance policy of 50,000 on (35), with premiums payable for a maximum of 10 years,
you are given:

106 Chapter 14. SULT


Solving Actuarial Math with Python

• Expenses of 100 are payable at the end of each year including the year of death
• Mortality follows the Standard Ultimate Life Table
• i = 0.05
Calculate the annual gross premium using the equivalence principle.

print("SOA Question 6.37: (D) 820")


sult = SULT()
benefits = sult.whole_life_insurance(35, b=50000 + 100)
expenses = sult.immediate_annuity(35, b=100)
a = sult.temporary_annuity(35, t=10)
print(benefits, expenses, a)
print((benefits + expenses) / a)

SOA Question 6.37: (D) 820


4836.382819496279 1797.2773668474615 8.092602358383987
819.7190338249138

SOA Question 6.35


For a fully discrete whole life insurance policy of 100,000 on (35), you are given:
• First year commissions are 19% of the annual gross premium
• Renewal year commissions are 4% of the annual gross premium
• Mortality follows the Standard Ultimate Life Table
• i = 0.05
Calculate the annual gross premium for this policy using the equivalence principle.

print("SOA Question 6.35: (D) 530")


sult = SULT()
A = sult.whole_life_insurance(35, b=100000)
a = sult.whole_life_annuity(35)
print(sult.gross_premium(a=a, A=A, initial_premium=.19, renewal_premium=.04))

SOA Question 6.35: (D) 530


534.4072234303344

SOA Question 5.8


For an annual whole life annuity-due of 1 with a 5-year certain period on (55), you are given:
• Mortality follows the Standard Ultimate Life Table
• i = 0.05
Calculate the probability that the sum of the undiscounted payments actually made under this annuity will exceed the
expected present value, at issue, of the annuity.

print("SOA Question 5.8: (C) 0.92118")


sult = SULT()
a = sult.certain_life_annuity(55, u=5)
print(sult.p_x(55, t=[Link](a)))

14.5. Examples 107


Solving Actuarial Math with Python

SOA Question 5.8: (C) 0.92118


0.9211799771029529

SOA Question 5.3


You are given:
• Mortality follows the Standard Ultimate Life Table
• Deaths are uniformly distributed over each year of age
• i = 0.05
𝑑
Calculate 𝑑𝑡 (𝐼𝑎)40∶𝑡| at 𝑡 = 10.5.

print("SOA Question 5.3: (C) 6.239")


sult = SULT()
t = 10.5
print(t * sult.E_r(40, t=t))

SOA Question 5.3: (C) 6.239


6.23871918627528

SOA Question 4.17


For a special whole life policy on (48), you are given:
• The policy pays 5000 if the insured’s death is before the median curtate future lifetime at issue and 10,000 if death
is after the median curtate future lifetime at issue
• Mortality follows the Standard Ultimate Life Table
• Death benefits are paid at the end of the year of death
• i = 0.05
Calculate the actuarial present value of benefits for this policy.

print("SOA Question 4.17: (A) 1126.7")


sult = SULT()
median = sult.Z_t(48, prob=0.5, discrete=False)
benefit = lambda x,t: 5000 if t < median else 10000
print(sult.A_x(48, benefit=benefit))

SOA Question 4.17: (A) 1126.7


1126.774772894844

SOA Question 4.14


A fund is established for the benefit of 400 workers all age 60 with independent future lifetimes. When they reach age
85, the fund will be dissolved and distributed to the survivors.
The fund will earn interest at a rate of 5% per year.
The initial fund balance, 𝐹 , is determined so that the probability that the fund will pay at least 5000 to each survivor is
86%, using the normal approximation.
Mortality follows the Standard Ultimate Life Table.
Calculate 𝐹 .

108 Chapter 14. SULT


Solving Actuarial Math with Python

print("SOA Question 4.14: (E) 390000 ")


sult = SULT()
p = sult.p_x(60, t=85-60)
mean = [Link](p)
var = [Link](p, variance=True)
F = sult.portfolio_percentile(mean=mean, variance=var, prob=.86, N=400)
print(F * 5000 * [Link].v_t(85-60))

SOA Question 4.14: (E) 390000


389322.86778416135

SOA Question 4.5


For a 30-year term life insurance of 100,000 on (45), you are given:
• The death benefit is payable at the moment of death
• Mortality follows the Standard Ultimate Life Table
• 𝛿 = 0.05
• Deaths are uniformly distributed over each year of age
Calculate the 95th percentile of the present value of benefits random variable for this insurance

print("SOA Question 4.5: (C) 35200")


sult = SULT(udd=True).set_interest(delta=0.05)
Z = 100000 * sult.Z_from_prob(45, prob=0.95, discrete=False)
print(Z)

SOA Question 4.5: (C) 35200


35187.952037196534

SOA Question 3.9


A father-son club has 4000 members, 2000 of which are age 20 and the other 2000 are age 45. In 25 years, the members
of the club intend to hold a reunion. You are given:
• All lives have independent future lifetimes.
• Mortality follows the Standard Ultimate Life Table.
Using the normal approximation, without the continuity correction, calculate the 99th percentile of the number of sur-
viving members at the time of the reunion.

print("SOA Question 3.9: (E) 3850")


sult = SULT()
p1 = sult.p_x(20, t=25)
p2 = sult.p_x(45, t=25)
mean = [Link](p1) * 2000 + [Link](p2) * 2000
var = ([Link](p1, variance=True) * 2000
+ [Link](p2, variance=True) * 2000)
print(sult.portfolio_percentile(mean=mean, variance=var, prob=.99))

SOA Question 3.9: (E) 3850


3850.144345130047

14.5. Examples 109


Solving Actuarial Math with Python

SOA Question 3.8


A club is established with 2000 members, 1000 of exact age 35 and 1000 of exact age 45. You are given:
• Mortality follows the Standard Ultimate Life Table
• Future lifetimes are independent
• N is the random variable for the number of members still alive 40 years after the club is established
Using the normal approximation, without the continuity correction, calculate the smallest 𝑛 such that 𝑃 𝑟(𝑁 ≥ 𝑛) ≤ 0.05.

print("SOA Question 3.8: (B) 1505")


sult = SULT()
p1 = sult.p_x(35, t=40)
p2 = sult.p_x(45, t=40)
mean = [Link](p1) * 1000 + [Link](p2) * 1000
var = ([Link](p1, variance=True) * 1000
+ [Link](p2, variance=True) * 1000)
print(sult.portfolio_percentile(mean=mean, variance=var, prob=.95))

SOA Question 3.8: (B) 1505


1504.8328375406456

SOA Question 3.4


The SULT Club has 4000 members all age 25 with independent future lifetimes. The mortality for each member follows
the Standard Ultimate Life Table.
Calculate the largest integer N, using the normal approximation, such that the probability that there are at least N survivors
at age 95 is at least 90%.

print("SOA Question 3.4: (B) 815")


sult = SULT()
mean = sult.p_x(25, t=95-25)
var = [Link](mean, variance=True)
print(sult.portfolio_percentile(N=4000, mean=mean, variance=var, prob=.1))

SOA Question 3.4: (B) 815


815.0943255167722

Generate SULT Table:

import pandas as pd
print("Standard Ultimate Life Table at i=0.05")
pd.set_option('display.max_rows', None)
[Link]()

Standard Ultimate Life Table at i=0.05

l_x q_x a_x A_x 2A_x a_x:10 A_x:10 a_x:20


20 100000.0 0.000250 19.9664 0.04922 0.00580 8.0991 0.61433 13.0559 \
21 99975.0 0.000253 19.9197 0.05144 0.00614 8.0990 0.61433 13.0551
22 99949.7 0.000257 19.8707 0.05378 0.00652 8.0988 0.61434 13.0541
23 99924.0 0.000262 19.8193 0.05622 0.00694 8.0986 0.61435 13.0531
24 99897.8 0.000267 19.7655 0.05879 0.00739 8.0983 0.61437 13.0519
(continues on next page)

110 Chapter 14. SULT


Solving Actuarial Math with Python

(continued from previous page)


25 99871.1 0.000273 19.7090 0.06147 0.00788 8.0981 0.61438 13.0506
26 99843.8 0.000280 19.6499 0.06429 0.00841 8.0978 0.61439 13.0491
27 99815.9 0.000287 19.5878 0.06725 0.00900 8.0974 0.61441 13.0474
28 99787.2 0.000296 19.5228 0.07034 0.00964 8.0970 0.61443 13.0455
29 99757.7 0.000305 19.4547 0.07359 0.01033 8.0966 0.61445 13.0434
30 99727.3 0.000315 19.3834 0.07698 0.01109 8.0961 0.61447 13.0410
31 99695.8 0.000327 19.3086 0.08054 0.01192 8.0956 0.61450 13.0384
32 99663.2 0.000341 19.2303 0.08427 0.01281 8.0949 0.61453 13.0354
33 99629.3 0.000356 19.1484 0.08817 0.01379 8.0943 0.61456 13.0320
34 99593.8 0.000372 19.0626 0.09226 0.01486 8.0935 0.61460 13.0282
35 99556.7 0.000391 18.9728 0.09653 0.01601 8.0926 0.61464 13.0240
36 99517.8 0.000412 18.8788 0.10101 0.01727 8.0916 0.61468 13.0192
37 99476.7 0.000436 18.7805 0.10569 0.01863 8.0905 0.61474 13.0138
38 99433.3 0.000463 18.6777 0.11059 0.02012 8.0893 0.61480 13.0078
39 99387.3 0.000493 18.5701 0.11571 0.02173 8.0879 0.61486 13.0011
40 99338.3 0.000527 18.4578 0.12106 0.02347 8.0863 0.61494 12.9935
41 99285.9 0.000565 18.3403 0.12665 0.02536 8.0846 0.61502 12.9850
42 99229.8 0.000608 18.2176 0.13249 0.02741 8.0826 0.61511 12.9754
43 99169.4 0.000656 18.0895 0.13859 0.02963 8.0804 0.61522 12.9647
44 99104.3 0.000710 17.9558 0.14496 0.03203 8.0779 0.61534 12.9526
45 99033.9 0.000771 17.8162 0.15161 0.03463 8.0751 0.61547 12.9391
46 98957.6 0.000839 17.6706 0.15854 0.03744 8.0720 0.61562 12.9240
47 98874.5 0.000916 17.5189 0.16577 0.04047 8.0684 0.61579 12.9070
48 98783.9 0.001003 17.3607 0.17330 0.04374 8.0645 0.61598 12.8880
49 98684.9 0.001100 17.1960 0.18114 0.04727 8.0600 0.61619 12.8667
50 98576.4 0.001209 17.0245 0.18931 0.05108 8.0550 0.61643 12.8428
51 98457.2 0.001331 16.8461 0.19780 0.05517 8.0494 0.61670 12.8161
52 98326.2 0.001469 16.6606 0.20664 0.05957 8.0431 0.61700 12.7862
53 98181.8 0.001623 16.4678 0.21582 0.06430 8.0360 0.61733 12.7527
54 98022.4 0.001797 16.2676 0.22535 0.06938 8.0281 0.61771 12.7154
55 97846.2 0.001993 16.0599 0.23524 0.07483 8.0192 0.61813 12.6737
56 97651.2 0.002212 15.8444 0.24550 0.08067 8.0092 0.61861 12.6271
57 97435.2 0.002459 15.6212 0.25613 0.08692 7.9980 0.61914 12.5752
58 97195.6 0.002736 15.3901 0.26714 0.09360 7.9854 0.61974 12.5174
59 96929.6 0.003048 15.1511 0.27852 0.10073 7.9713 0.62041 12.4531
60 96634.1 0.003398 14.9041 0.29028 0.10834 7.9555 0.62116 12.3816
61 96305.8 0.003792 14.6491 0.30243 0.11644 7.9379 0.62201 12.3024
62 95940.6 0.004234 14.3861 0.31495 0.12506 7.9181 0.62295 12.2145
63 95534.4 0.004730 14.1151 0.32785 0.13421 7.8960 0.62400 12.1174
64 95082.5 0.005288 13.8363 0.34113 0.14392 7.8712 0.62518 12.0101
65 94579.7 0.005915 13.5498 0.35477 0.15420 7.8435 0.62650 11.8920
66 94020.3 0.006619 13.2557 0.36878 0.16507 7.8126 0.62797 11.7622
67 93398.1 0.007409 12.9542 0.38313 0.17654 7.7781 0.62961 11.6199
68 92706.1 0.008297 12.6456 0.39783 0.18862 7.7396 0.63145 11.4643
69 91936.9 0.009294 12.3302 0.41285 0.20133 7.6968 0.63349 11.2949
70 91082.4 0.010413 12.0083 0.42818 0.21467 7.6491 0.63576 11.1109
71 90134.0 0.011670 11.6803 0.44379 0.22864 7.5961 0.63828 10.9118
72 89082.1 0.013081 11.3468 0.45968 0.24324 7.5373 0.64108 10.6974
73 87916.8 0.014664 11.0081 0.47580 0.25847 7.4721 0.64419 10.4675
74 86627.6 0.016440 10.6649 0.49215 0.27433 7.3999 0.64762 10.2221
75 85203.5 0.018433 10.3178 0.50868 0.29079 7.3203 0.65142 9.9616
76 83632.9 0.020668 9.9674 0.52536 0.30783 7.2325 0.65560 9.6866
77 81904.3 0.023175 9.6145 0.54217 0.32544 7.1360 0.66019 9.3980
78 80006.2 0.025984 9.2598 0.55906 0.34359 7.0302 0.66523 9.0970
79 77927.4 0.029132 8.9042 0.57599 0.36224 6.9146 0.67074 8.7850
80 75657.2 0.032658 8.5484 0.59293 0.38134 6.7885 0.67674 8.4639

(continues on next page)

14.5. Examples 111


Solving Actuarial Math with Python

(continued from previous page)


81 73186.3 0.036607 8.1934 0.60984 0.40086 6.6517 0.68325 8.1354
82 70507.2 0.041025 7.8401 0.62666 0.42075 6.5037 0.69030 7.8018
83 67614.6 0.045968 7.4893 0.64336 0.44094 6.3443 0.69789 7.4651
84 64506.5 0.051493 7.1421 0.65990 0.46137 6.1735 0.70602 7.1275
85 61184.9 0.057665 6.7993 0.67622 0.48199 5.9915 0.71469 6.7910
86 57656.7 0.064554 6.4619 0.69229 0.50272 5.7986 0.72388 6.4574
87 53934.7 0.072237 6.1308 0.70806 0.52349 5.5954 0.73355 6.1285
88 50038.6 0.080798 5.8068 0.72349 0.54422 5.3828 0.74368 5.8057
89 45995.6 0.090326 5.4908 0.73853 0.56484 5.1620 0.75419 5.4903
90 41841.1 0.100917 5.1835 0.75317 0.58528 4.9346 0.76502 5.1833
91 37618.6 0.112675 4.8858 0.76735 0.60545 4.7021 0.77609 4.8857
92 33379.9 0.125708 4.5981 0.78104 0.62529 4.4665 0.78731 4.5981
93 29183.8 0.140128 4.3213 0.79423 0.64472 4.2299 0.79858 4.3213
94 25094.3 0.156052 4.0556 0.80688 0.66368 3.9945 0.80979 4.0556
95 21178.3 0.173599 3.8017 0.81897 0.68209 3.7624 0.82084 3.8017
96 17501.8 0.192887 3.5597 0.83049 0.69991 3.5356 0.83164 3.5597
97 14125.9 0.214030 3.3300 0.84143 0.71708 3.3159 0.84210 3.3300
98 11102.5 0.237134 3.1127 0.85177 0.73356 3.1050 0.85214 3.1127
99 8469.7 0.262294 2.9079 0.86153 0.74930 2.9039 0.86172 2.9079
100 6248.2 0.289584 2.7156 0.87068 0.76427 2.7137 0.87078 2.7156

A_x:20 5_E_x 10_E_x 20_E_x


20 0.37829 0.78252 0.61224 0.37440
21 0.37833 0.78250 0.61220 0.37429
22 0.37837 0.78248 0.61215 0.37417
23 0.37842 0.78245 0.61210 0.37404
24 0.37848 0.78243 0.61205 0.37390
25 0.37854 0.78240 0.61198 0.37373
26 0.37862 0.78236 0.61191 0.37354
27 0.37869 0.78233 0.61183 0.37334
28 0.37878 0.78229 0.61174 0.37310
29 0.37888 0.78224 0.61163 0.37284
30 0.37900 0.78219 0.61152 0.37254
31 0.37913 0.78213 0.61139 0.37221
32 0.37927 0.78206 0.61124 0.37183
33 0.37943 0.78199 0.61108 0.37141
34 0.37961 0.78190 0.61090 0.37094
35 0.37981 0.78181 0.61069 0.37041
36 0.38004 0.78170 0.61046 0.36982
37 0.38029 0.78158 0.61020 0.36915
38 0.38058 0.78145 0.60990 0.36841
39 0.38090 0.78130 0.60957 0.36757
40 0.38126 0.78113 0.60920 0.36663
41 0.38167 0.78094 0.60879 0.36558
42 0.38212 0.78072 0.60832 0.36440
43 0.38263 0.78048 0.60780 0.36307
44 0.38321 0.78021 0.60721 0.36159
45 0.38385 0.77991 0.60655 0.35994
46 0.38457 0.77956 0.60581 0.35809
47 0.38538 0.77918 0.60498 0.35601
48 0.38629 0.77875 0.60404 0.35370
49 0.38730 0.77827 0.60299 0.35112
50 0.38844 0.77772 0.60182 0.34824
51 0.38971 0.77711 0.60050 0.34503
52 0.39113 0.77643 0.59902 0.34146
53 0.39273 0.77566 0.59736 0.33749

(continues on next page)

112 Chapter 14. SULT


Solving Actuarial Math with Python

(continued from previous page)


54 0.39451 0.77479 0.59550 0.33308
55 0.39649 0.77382 0.59342 0.32819
56 0.39871 0.77273 0.59109 0.32279
57 0.40118 0.77151 0.58848 0.31681
58 0.40393 0.77014 0.58556 0.31024
59 0.40700 0.76860 0.58229 0.30300
60 0.41040 0.76687 0.57864 0.29508
61 0.41417 0.76493 0.57457 0.28641
62 0.41836 0.76276 0.57003 0.27698
63 0.42298 0.76033 0.56496 0.26674
64 0.42809 0.75760 0.55932 0.25569
65 0.43371 0.75455 0.55305 0.24381
66 0.43990 0.75114 0.54609 0.23112
67 0.44667 0.74732 0.53836 0.21764
68 0.45408 0.74305 0.52981 0.20343
69 0.46215 0.73828 0.52036 0.18856
70 0.47091 0.73295 0.50994 0.17313
71 0.48039 0.72701 0.49848 0.15730
72 0.49060 0.72039 0.48590 0.14122
73 0.50155 0.71303 0.47215 0.12511
74 0.51323 0.70483 0.45715 0.10918
75 0.52564 0.69574 0.44085 0.09368
76 0.53873 0.68566 0.42323 0.07887
77 0.55247 0.67450 0.40427 0.06500
78 0.56681 0.66217 0.38396 0.05230
79 0.58166 0.64859 0.36235 0.04096
80 0.59696 0.63365 0.33952 0.03113
81 0.61260 0.61727 0.31556 0.02286
82 0.62848 0.59936 0.29064 0.01616
83 0.64452 0.57985 0.26498 0.01094
84 0.66059 0.55868 0.23882 0.00706
85 0.67662 0.53581 0.21250 0.00431
86 0.69250 0.51122 0.18635 0.00248
87 0.70817 0.48492 0.16079 0.00133
88 0.72354 0.45697 0.13621 0.00066
89 0.73856 0.42748 0.11305 0.00030
90 0.75317 0.39659 0.09168 0.00012
91 0.76735 0.36453 0.07244 0.00005
92 0.78104 0.33158 0.05559 0.00002
93 0.79423 0.29808 0.04128 0.00000
94 0.80688 0.26445 0.02955 0.00000
95 0.81897 0.23116 0.02029 0.00000
96 0.83049 0.19872 0.01330 0.00000
97 0.84143 0.16765 0.00827 0.00000
98 0.85177 0.13850 0.00485 0.00000
99 0.86153 0.11173 0.00266 0.00000
100 0.87068 0.08777 0.00136 0.00000

14.5. Examples 113


Solving Actuarial Math with Python

114 Chapter 14. SULT


CHAPTER

FIFTEEN

SELECT LIFE TABLE

15.1 Select and ultimate life model

A newly selected policyholder is in the best health condition possible, compared to the general population with the same
age. The life table can be expanded to tabulate the select period when selection has an effect on mortality. Since this
selection process wears off after a few years, the ultimate part of the table can be then be used when select age is assumed
to no longer have an effect on mortality.
• Future survival probabilities depend on the individual’s current age and on the age at which the individual joined
the group (i.e. was selected). Current age is written [𝑥] + 𝑠, where 𝑥 is the selected age and 𝑠 is the number of
years after selection.
• If an individual joined the group more than 𝑑 years ago (called the select period), future survival probabilities (called
the ultimate mortality) depend only on current age. The initial selection effect is assumed to have worn off after 𝑑
years. Current age can be written as 𝑥 + 𝑠 after the select period 𝑠 ≥ 𝑑
Select life tables reflect duration as well as age during the select period. A select and ultimate mortality table is shown
in tabular form by listing agex vertically and the selection durations horizontally. If the select period is 𝑛, there are 𝑛
columns followed by a column with ultimate mortality. To find the mortality at a duration after a selection age, we read
across the row corresponding to that selection age then continue down the last column.
Notation for select survival models:

𝑡 𝑝[𝑥]+𝑠 = Pr(a life aged 𝑥 + 𝑠, selected at age 𝑥, survives to age 𝑥 + 𝑠 + 𝑡)


• defines survival probability in the select period

𝑡 𝑞[𝑥]+𝑠 = Pr(a life aged 𝑥 + 𝑠, selected at age 𝑥, dies before age 𝑥 + 𝑠 + 𝑡)


• defines mortality rate in the select period
𝑙𝑥+𝑑
𝑙[𝑥]+𝑠 = = number of lives, selected at age 𝑥, who are aged 𝑥 + 𝑠, given that 𝑙𝑥+𝑑 survived to age 𝑥 + 𝑑.
𝑑−𝑠 𝑝[𝑥]+𝑠

• defines the life table within the select period, by working backwards from the value of 𝑙𝑥+𝑑 in the ultimate part of
the table which only depends on current age.
With a select period 𝑑 and for 𝑠 ≥ 𝑑 (i.e. durations beyond the select period) the values of 𝑝[𝑥−𝑠]+𝑠 , 𝑞[𝑥−𝑠]+𝑠 , 𝑙[𝑥−𝑠]+𝑠
depend only on current age 𝑥 and not on 𝑠. So for 𝑠 ≥ 𝑑, these terms are all equal to and can be written simply as
𝑝𝑥 , 𝑞𝑥 , 𝑙𝑥 respectively.

115
Solving Actuarial Math with Python

15.2 Methods

The SelectLife class specifies a given select life table to be the survival model. It inherits all the general methods for
computing life contingency risks, and overrides those methods where values can be looked up or calculated from select
life table entries.

from actuarialmath import SelectLife


import describe
[Link](SelectLife)

class SelectLife - Calculate select life table, and iteratively fill in missing␣
↪values

Args:
periods : number of select period years
verbose : whether to echo update steps

Notes:
6 types of columns can be loaded and calculated in the select table:

- 'q' : probability [x]+s dies in one year


- 'l' : number of lives aged [x]+s
- 'd' : number of deaths of age [x]+s
- 'A' : whole life insurance
- 'a' : whole life annuity
- 'e' : expected future curtate lifetime of [x]+s

Methods:
--------

set_table(fill, l, d, q, A, a, e):
Update from table, every age has row for all select durations

set_select(s, age_selected, fill, l, d, q, A, a, e):


Update a table column, for a particular duration s in the select period

fill_table(radix):
Fills in missing table values (does not check for consistency)

__getitem__(table):
Returns values from a select and ultimate table

frame(table):
Returns select and ultimate table values as a DataFrame

l_x(x, s):
Returns number of lives aged [x]+s computed from select table

p_x(x, s, t):
t_p_[x]+s by chain rule: prod(1_p_[x]+s+y) for y in range(t)

q_x(x, s, t, u):
t|u_q_[x]+s = [x]+s survives u years, does not survive next t

e_x(x, s, t, curtate):
Returns expected life time computed from select table
(continues on next page)

116 Chapter 15. Select Life Table


Solving Actuarial Math with Python

(continued from previous page)

A_x(x, s, moment, discrete, kwargs):


Returns insurance value computed from select table

a_x(x, s, moment, discrete, kwargs):


Returns annuity value computed from select table

15.3 Examples

The set_table method is called to load a life table, by age and duration, with given values of number of lives, number
of deaths, mortality rate, insurance, annuity and/or life expectancy. The set_select method can also be called instead
to update values of the same functions but for a particular duration 𝑠 only. If the fill flag is set to True, fill_table
is automatically called to fill in any missing values using recursion and identify formulas. All other computational methods
can then be called in the usual manner, which will use the survival model provided by the select life table.
SOA Question 3.2:
You are given:
• The following extract from a mortality table with a one-year select period:

𝑥 𝑙[𝑥] 𝑑[𝑥] 𝑙𝑥+1 𝑥+1


65 1000 40 − 66
66 955 45 − 67

• Deaths are uniformly distributed over each year of age



𝑒[65] = 15.0

Calculate 𝑒[66] .

print("SOA Question 3.2: (D) 14.7")


e_curtate = SelectLife.e_approximate(e_complete=15)
life = SelectLife(udd=True).set_table(l={65: [1000, None,],
66: [955, None]},
e={65: [e_curtate, None]},
d={65: [40, None,],
66: [45, None]})
print(life.e_r(66))
print([Link]('e'))

SOA Question 3.2: (D) 14.7


14.67801047120419
e_[x]+s: 0 1
Age
65 14.50000 14.104167
66 14.17801 13.879121

SOA Question 4.16


You are given the following extract of ultimate mortality rates from a two-year select and ultimate mortality table:

15.3. Examples 117


Solving Actuarial Math with Python

𝑥 𝑞𝑥
50 0.045
51 0.050
52 0.055
53 0.060

The select mortality rates satisfy the following:


• 𝑞[𝑥] = 0.7𝑞𝑥
• 𝑞[𝑥]+1 = 0.8𝑞𝑥+1
You are also given that 𝑖 = 0.04.
Calculate 𝐴1[50]∶3| .

print("SOA Question 4.16: (D) .1116")


q = [.045, .050, .055, .060]
q_ = {50+x: [0.7 * q[x] if x < 4 else None,
0.8 * q[x+1] if x+1 < 4 else None,
q[x+2] if x+2 < 4 else None]
for x in range(4)}
life = SelectLife().set_table(q=q_).set_interest(i=.04)
print(life.term_insurance(50, t=3))

SOA Question 4.16: (D) .1116


0.1115661982248521

SOA Question 4.13


For a 2-year deferred, 2-year term insurance of 2000 on [65], you are given:
• The following select and ultimate mortality table with a 3-year select period:

𝑥 𝑞[𝑥] 𝑞[𝑥]+1 𝑞[𝑥]+2 𝑞𝑥+3 𝑥+3


65 0.08 0.10 0.12 0.14 68
66 0.09 0.11 0.13 0.15 69
67 0.10 0.12 0.14 0.16 70
68 0.11 0.13 0.15 0.17 71
69 0.12 0.14 0.16 0.18 72

• 𝑖 = 0.04
• The death benefit is payable at the end of the year of death
Calculate the actuarial present value of this insurance.

print("SOA Question 4.13: (C) 350 ")


life = SelectLife().set_interest(i=0.04)\
.set_table(q={65: [.08, .10, .12, .14],
66: [.09, .11, .13, .15],
67: [.10, .12, .14, .16],
68: [.11, .13, .15, .17],
69: [.12, .14, .16, .18]})
print(life.deferred_insurance(65, t=2, u=2, b=2000))

118 Chapter 15. Select Life Table


Solving Actuarial Math with Python

SOA Question 4.13: (C) 350


351.0578236056159

SOA Question 3.13


A life is subject to the following 3-year select and ultimate table:

[𝑥] ℓ[𝑥] ℓ[𝑥]+1 ℓ[𝑥]+2 ℓ𝑥+3 𝑥+3


55 10,000 9,493 8,533 7,664 58
56 8,547 8,028 6,889 5,630 59
57 7,011 6,443 5,395 3,904 60
58 5,853 4,846 3,548 2,210 61

You are also given:


• 𝑒60 = 1
• Deaths are uniformly distributed over each year of age

Calculate 𝑒[58]+2 .

print("SOA Question 3.13: (B) 1.6")


life = SelectLife().set_table(l={55: [10000, 9493, 8533, 7664],
56: [8547, 8028, 6889, 5630],
57: [7011, 6443, 5395, 3904],
58: [5853, 4846, 3548, 2210]},
e={57: [None, None, None, 1]})
print(life.e_r(58, s=2))

SOA Question 3.13: (B) 1.6


1.6003382187147688

SOA Question 3.12


X and Y are both age 61. X has just purchased a whole life insurance policy. Y purchased a whole life insurance policy
one year ago.
Both X and Y are subject to the following 3-year select and ultimate table:

𝑥 ℓ[𝑥] ℓ[𝑥]+1 ℓ[𝑥]+2 ℓ𝑥+3 𝑥+3


60 10,000 9,600 8,640 7,771 63
61 8,654 8,135 6,996 5,737 64
62 7,119 6,549 5,501 4,016 65
63 5,760 4,954 3,765 2,410 66

The force of mortality is constant over each year of age.


Calculate the difference in the probability of survival to age 64.5 between X and Y.

print("SOA Question 3.12: (C) 0.055 ")


life = SelectLife(udd=False).set_table(l={60: [10000, 9600, 8640, 7771],
61: [8654, 8135, 6996, 5737],
62: [7119, 6549, 5501, 4016],
(continues on next page)

15.3. Examples 119


Solving Actuarial Math with Python

(continued from previous page)


63: [5760, 4954, 3765, 2410]})
print(life.q_r(60, s=1, t=3.5) - life.q_r(61, s=0, t=3.5))

SOA Question 3.12: (C) 0.055


0.05465655938591829

SOA Question 3.7


For a mortality table with a select period of two years, you are given:

𝑥 𝑞[𝑥] 𝑞[𝑥]+1 𝑞𝑥+2 𝑥+2


50 0.0050 0.0063 0.0080 52
51 0.0060 0.0073 0.0090 53
52 0.0070 0.0083 0.0100 54
53 0.0080 0.0093 0.0110 55

The force of mortality is constant between integral ages.


Calculate 1000 2.5 𝑞[50]+0.4 .

print("SOA Question 3.7: (b) 16.4")


life = SelectLife().set_table(q={50: [.0050, .0063, .0080],
51: [.0060, .0073, .0090],
52: [.0070, .0083, .0100],
53: [.0080, .0093, .0110]})
print(1000*life.q_r(50, s=0, r=0.4, t=2.5))

SOA Question 3.7: (b) 16.4


16.420207214428586

SOA Question 3.6


You are given the following extract from a table with a 3-year select period:

𝑥 𝑞[𝑥] 𝑞[𝑥]+1 𝑞[𝑥]+2 𝑞𝑥+3 𝑥+3


60 0.09 0.11 0.13 0.15 63
61 0.10 0.12 0.14 0.16 64
62 0.11 0.13 0.15 0.17 65
63 0.12 0.14 0.16 0.18 66
64 0.13 0.15 0.17 0.19 67

𝑒64 = 5.10
Calculate 𝑒[61] .

print("SOA Question 3.6: (D) 5.85")


life = SelectLife().set_table(q={60: [.09, .11, .13, .15],
61: [.1, .12, .14, .16],
62: [.11, .13, .15, .17],
63: [.12, .14, .16, .18],
64: [.13, .15, .17, .19]},
(continues on next page)

120 Chapter 15. Select Life Table


Solving Actuarial Math with Python

(continued from previous page)


e={61: [None, None, None, 5.1]})
print(life.e_x(61))

SOA Question 3.6: (D) 5.85


5.846832

SOA Question 3.3


You are given:
• An excerpt from a select and ultimate life table with a select period of 2 years:

𝑥 ℓ[𝑥] ℓ[𝑥]+1 ℓ𝑥+2 𝑥+2


50 99,000 96,000 93,000 52
51 97,000 93,000 89,000 53
52 93,000 88,000 83,000 54
53 90,000 84,000 78,000 55

• Deaths are uniformly distributed over each year of age


Calculate 10, 000 2.2 𝑞[51]+0.5 .

print("SOA Question 3.3: (E) 1074")


life = SelectLife().set_table(l={50: [99, 96, 93],
51: [97, 93, 89],
52: [93, 88, 83],
53: [90, 84, 78]})
print(10000*life.q_r(51, s=0, r=0.5, t=2.2))

SOA Question 3.3: (E) 1074


1073.684210526316

SOA Question 3.1


You are given:
• An excerpt from a select and ultimate life table with a select period of 3 years:

𝑥 ℓ[𝑥] ℓ[𝑥]+1 ℓ[𝑥]+2 ℓ𝑥+3 𝑥+3


60 80,000 79,000 77,000 74,000 63
61 78,000 76,000 73,000 70,000 64
62 75,000 72,000 69,000 67,000 65
63 71,000 68,000 66,000 65,000 66

• Deaths follow a constant force of mortality over each year of age


Calculate 1000 23 𝑞[60]+0.75 .

print("SOA Question 3.1: (B) 117")


life = SelectLife().set_table(l={60: [80000, 79000, 77000, 74000],
61: [78000, 76000, 73000, 70000],
62: [75000, 72000, 69000, 67000],
(continues on next page)

15.3. Examples 121


Solving Actuarial Math with Python

(continued from previous page)


63: [71000, 68000, 66000, 65000]})
print(1000*life.q_r(60, s=0, r=0.75, t=3, u=2))

SOA Question 3.1: (B) 117


116.7192429022082

show verbose calculations:

table={21: [0.00120, 0.00150, 0.00170, 0.00180],


22: [0.00125, 0.00155, 0.00175, 0.00185],
23: [0.00130, 0.00160, 0.00180, 0.00195]}
life = SelectLife(verbose=True).set_table(q=table)
print(life.p_x(x=21, s=1, t=4)) # 0.9931
[Link]('l')

1 l(x=21, s=1) = 99880.0


2 l(x=21, s=2) = 99730.18000000001
3 l(x=21, s=3) = 99560.63869400001
4 l(x=22, s=3) = 99381.4295443508
5 l(x=23, s=3) = 99197.57389969376
6 d(x=21, s=0) = 120.0
7 d(x=21, s=1) = 149.81999999999243
8 d(x=21, s=2) = 169.54130599999917
9 d(x=21, s=3) = 179.20914964920667
10 d(x=22, s=3) = 183.85564465704374
11 l(x=22, s=2) = 99555.65193523747
12 l(x=23, s=2) = 99376.45151241611
13 d(x=22, s=2) = 174.22239088667266
14 d(x=23, s=2) = 178.87761272235366
15 l(x=22, s=1) = 99710.2027494992
16 l(x=23, s=1) = 99535.70864625012
17 d(x=22, s=1) = 154.55081426173274
18 d(x=23, s=1) = 159.2571338340058
19 l(x=22, s=0) = 99834.9964951181
20 l(x=23, s=0) = 99665.27350180245
21 d(x=22, s=0) = 124.79374561889563
22 d(x=23, s=0) = 129.56485555233667
0.9931675400449915

l_[x]+s: 0 1 2 3
Age
21 100000.000000 99880.000000 99730.180000 99560.638694
22 99834.996495 99710.202749 99555.651935 99381.429544
23 99665.273502 99535.708646 99376.451512 99197.573900

122 Chapter 15. Select Life Table


CHAPTER

SIXTEEN

MORTALITY LAWS

Another approach to defining a survival model is to fit a parametric function, which may have convenient properties
that simplify computations. When using these special mortality laws for lifetime distribution, shortcut formulas may be
available without the need for numerical integration.

16.1 Uniform distribution

If deaths are uniformly distributed then insurance benefits are paid out in a level manner. A convenient property of a
uniform distribution on [0, 𝜃] is that it has a midrange mean and median of 𝜃/2
𝑙𝑥 ∼ 𝜔 − 𝑥
𝜔 − (𝑥 + 𝑡)
𝑡 𝑝𝑥 =
𝜔−𝑥
• number of lives, and survival function, are linearly declining with age
1
𝜇𝑥+𝑡 = 𝑓𝑥 (𝑡) =
𝜔−𝑥−𝑡
• the force of mortality and lifetime density function are identical
∘ 𝜔−𝑥
𝑒𝑥 =
2
• expected future lifetime is the mean of a uniform distribution
∘ 𝑛
𝑒𝑥∶𝑛| = 𝑛 𝑝𝑥 𝑛 + 𝑛 𝑞𝑥
2
• expected limited lifetime is the weighted sum of the mean lifetime of deaths (for those who died) and the limit 𝑛
(for those who survived)
$Var(T_x) = \dfrac{(\omega - x)^2}{12}
• variance of complete future lifetime is the variance of a uniform distribution
𝜔 − (𝑥 + 𝑛)
𝑛 𝐸𝑥 = 𝑣𝑛
𝜔−𝑥
• pure endowment shortcut by substituting in formula for the survival function
𝑎𝜔−𝑥|
̄
𝐴𝑥̄ =
𝜔−𝑥
1
• it is not necessary to carry out integration since whole life insurance is equivalent to an annuity-certain that pays
𝜔
per year. To calculate the second moment of insurance, simply double the force of interest in the annuity-certain
in the shortcut formula.

123
Solving Actuarial Math with Python

𝑎𝑛|
̄
̄
𝐴1𝑥∶𝑛| =
𝜔−𝑥
1
• term insurance is an annuity-certain that pays per year for the term of the insurance.
𝜔

16.2 Beta distribution

This is a generalized version of the uniform distribution with two parameters 𝛼, 𝜔. The uniform is a special case of the
Beta distribution with 𝛼 = 1. However, unlike the uniform, its density function is longer constant.
𝑙𝑥 ∼ (𝜔 − 𝑥)𝛼
𝛼(𝜔 − 𝑥 − 𝑡)𝛼−1
𝑓𝑥 (𝑡) =
(𝜔 − 𝑥)𝛼
𝛼
𝜇𝑥 =
𝜔−𝑥
𝜔 − (𝑥 + 𝑡) 𝛼
𝑡 𝑝𝑥 = ( )
𝜔−𝑥
∘ 𝜔−𝑥
𝑒𝑥 =
𝛼+1
𝛼(𝜔 − 𝑥)2
𝑉 𝑎𝑟(𝑇𝑥 ) =
(1 + 𝛼)2 (2 + 𝛼)

16.3 Gompertz’s Law

Gompertz proposed this law based on observations that the logarithm of the force of mortality appeared to fit well to a
straight line for ages above 20.
𝜇𝑥 = 𝐵𝑐𝑥
𝐵𝑐𝑥
(𝑐𝑡 −1)
𝑡 𝑝𝑥 =𝑒 ln 𝑐

• the force of mortality is modeled by two parameters 𝐵 and 𝑐 > 1

16.4 Makeham’s Law

Makeham improved Gompertz’s law by adding a third parameters 𝐴 ≥ −𝐵. This includes represents a constant element
in the force of mortality that does not depend on age.
𝜇𝑥 = 𝐴 + 𝐵𝑐𝑥
𝐵𝑐𝑥
(𝑐𝑡 −1)−𝐴𝑡
𝑡 𝑝𝑥 =𝑒 ln 𝑐

• the force of mortality is modeled with three parameters 𝑐 > 1, 𝐵 > 0, 𝐴 ≥ −𝐵

124 Chapter 16. Mortality Laws


Solving Actuarial Math with Python

16.5 Methods

The MortalityLaws class, and Beta, Uniform, Makeham and Gompertz subclasses, specify continuous func-
tions for survival distributions. These classes inherit all the general methods for computing life contingency risks, and
override those methods with shortcut formulas that may be available when assuming their respective mortality laws.

from actuarialmath import MortalityLaws, Uniform, Beta, Makeham, Gompertz


import describe
[Link](MortalityLaws)
[Link](Uniform)
[Link](Beta)
[Link](Gompertz)
[Link](Makeham)

class MortalityLaws - Apply shortcut formulas for special mortality laws

Methods:
--------

l_r(x, s, r):
Fractional lives given special mortality law: l_[x]+s+r

p_r(x, s, r, t):
Fractional age survival probability given special mortality law

q_r(x, s, r, t, u):
Fractional age deferred mortality given special mortality law

mu_r(x, s, r):
Fractional age force of mortality given special mortality law

f_r(x, s, r, t):
fractional age lifetime density given special mortality law

e_r(x, s, r, t):
Fractional age future lifetime given special mortality law

class Uniform - Shortcuts with uniform distribution of deaths aka DeMoivre's Law

Args:
omega : maximum age

Examples:
>>> print(Uniform(95).e_x(30, t=40, curtate=False)) # 27.692

class Beta - Shortcuts with beta distribution of deaths (is Uniform when alpha = 1)

Args:
omega : maximum age
alpha : alpha paramter of beta distribution
radix : assumed starting number of lives for survival function

Examples:
(continues on next page)

16.5. Methods 125


Solving Actuarial Math with Python

(continued from previous page)


>>> print(Beta(omega=60, alpha=1/3).mu_x(35) * 1000)

class Gompertz - Is Makeham's Law with A = 0

Args:
B, c : parameters of Gompertz distribution

Examples:
>>> print(Gompertz(B=0.00027, c=1.1).f_x(50, t=10)) # 0.04839

class Makeham - Includes element in force of mortality that does not depend on age

Args:
A, B, c : parameters of Makeham distribution

Examples:
>>> print(Makeham(A=0.00022, B=2.7e-6, c=1.124).mu_x(60) * 0.9803) # 0.00316

16.6 Examples

The Uniform class is initialized with the omega parameter to specify the range of the uniform distribution of deaths
assumption. All the inherited general computation methods can be accessed; where shortcut formulas are available, then
those specific methods are overriden.

print('Uniform')
uniform = Uniform(80).set_interest(delta=0.04)
print(uniform.whole_life_annuity(20)) # 15.53
print(uniform.temporary_annuity(20, t=5)) # 4.35
print(Uniform(161).p_x(70, t=1)) # 0.98901
print(Uniform(95).e_x(30, t=40, curtate=False)) # 27.692
print()

uniform = Uniform(omega=80).set_interest(delta=0.04)
print(uniform.E_x(20, t=5)) # .7505
print(uniform.whole_life_insurance(20, discrete=False)) # .3789
print(uniform.term_insurance(20, t=5, discrete=False)) # .0755
print(uniform.endowment_insurance(20, t=5, discrete=False)) # .8260
print(uniform.deferred_insurance(20, u=5, discrete=False)) # .3033

Uniform
16.03290804858584
4.47503070125663
0.989010989010989
32.30769230769231

0.7505031903214833
0.378867519462745
0.07552885288417432
0.8260320432056576
0.30333866657857067

126 Chapter 16. Mortality Laws


Solving Actuarial Math with Python

The Beta class is initialized with omega and alpha parameters of the beta distribution of deaths assumption. All
the inherited general computation methods can be accessed; where shortcut formulas are available, then those specific
methods are overriden.

life = Beta(omega=100, alpha=0.5)


print(life.q_x(25, t=1, u=10)) # 0.0072
print(life.e_x(25)) # 50
print(Beta(omega=60, alpha=1/3).mu_x(35) * 1000) # 13.33

0.007188905547861446
50.0
13.333333333333332

The Gompertz or Makeham classes are initialized with the respective parameters of the Gompertz and Makeham Laws
assumptions. All the inherited general computation methods can be accessed.

life = Gompertz(B=0.000005, c=1.10)


p = life.p_x(80, t=10) # 869.4
print(life.portfolio_percentile(N=1000, mean=p, variance=p*(1-p), prob=0.99))

print(Gompertz(B=0.00027, c=1.1).f_x(50, t=10)) # 0.04839


life = Makeham(A=0.00022, B=2.7e-6, c=1.124)
print(life.mu_x(60) * 0.9803) # 0.00316

869.3908338193208
0.048389180223511644
0.0031580641631654026

SOA Question 2.3:


You are given that mortality follows Gompertz Law with B = 0.00027 and c = 1.1. Calculate 𝑓50 (10).

print("SOA Question 2.3: (A) 0.0483")


print(Gompertz(B=0.00027, c=1.1).f_x(x=50, t=10))

SOA Question 2.3: (A) 0.0483


0.048389180223511644

SOA Question 2.6


You are given the survival function:
1
𝑥 3
𝑆0 (𝑥) = (1 − 60 ) , 0 ≤ 𝑥 ≤ 60
Calculate 1000𝜇35 .

print("# SOA Question 2.6: (C) 13.3")


print(Beta(omega=60, alpha=1/3).mu_x(35) * 1000)

# SOA Question 2.6: (C) 13.3


13.333333333333332

16.6. Examples 127


Solving Actuarial Math with Python

128 Chapter 16. Mortality Laws


CHAPTER

SEVENTEEN

CONSTANT FORCE OF MORTALITY

If force of mortality is constant, then future lifetime is exponentially distributed. The exponential distribution is easy to
work with, and has the memoryless property that survival probability is independent of age (which is clearly an unrealistic
assumption for human mortality).

𝑡 𝑝𝑥 = 𝑒−𝜇𝑡

𝑡 𝑞𝑥 = 1 − 𝑒−𝜇𝑡
• survival functions do not depend on age x; given any desired probability of survival (or death), the time-of-death
threshold can also be easily derived by inverting this simple shortcut formula

17.1 Expected future lifetime

∘ 1
𝑒𝑥 =
𝜇
• expected future lifetime is the mean of an exponential distribution, and does not depend on age x
∘ 1
𝑒𝑥∶𝑛| = (1 − 𝑒−𝜇𝑛 )
𝜇
• temporary life expectancy is expected future life time, minus the probability of surviving 𝑛 years times the expected
remaining lifetime of those survivors
1
𝑉 𝑎𝑟(𝑇𝑥 ) =
𝜇2
• variance of future lifetime is the variance of an exponential distribution

17.2 Pure endowment

𝑛 𝐸𝑥 = 𝑒−(𝜇+𝛿)𝑛
• shortcut for pure endowment after substituting in the formula for survival function

129
Solving Actuarial Math with Python

17.3 Life insurance


𝜇
𝐴𝑥̄ =
𝜇+𝛿
• continuous whole life insurance, does not depend on age 𝑥
̄ 𝜇 −𝜇𝑢
𝑢| 𝐴𝑥 = 𝑒
𝜇+𝛿
• deferred continuous whole life insurance as pure endowment times whole life insurance
̄ = 𝜇 (1 − 𝑒−𝜇𝑡 )
𝐴𝑥∶𝑡|
𝜇+𝛿
• continuous term life insurance as difference of whole life and deferred whole life insurance shortcut formulas

17.4 Life annuities

1
𝑎𝑥̄ =
𝜇+𝛿
• whole life annuity, does not depend on age 𝑥
1
𝑎𝑥∶𝑡|
̄ = (1 − 𝑒−𝜇𝑡 )
𝜇+𝛿
• temporary life annuity as difference of whole life and deferred whole life annuity shortcut formulas

17.5 Net future loss


𝜇
𝑣𝑎𝑟(0 𝐿) =
2𝛿 + 𝜇
• variance of loss at issue on whole life insurance with benefit premium calculated using the equivalence principle
equals the second moment of insurance

17.6 Methods

The ConstantForce class specifies a constant force of mortality for the survival model. It inherits all the general
methods for computing life contingency risks, and overrides those methods with shortcut formulas that are available when
assuming an exponential distribution for future lifetime.

from actuarialmath import ConstantForce


import describe
[Link](ConstantForce)

class ConstantForce - Constant force of mortality - memoryless exponential␣


↪distribution of lifetime

Args:
mu : constant value of force of mortality
udd : assume UDD (True) or CFM (False, default) between integer ages
(continues on next page)

130 Chapter 17. Constant Force of Mortality


Solving Actuarial Math with Python

(continued from previous page)

Examples:
>>> life = ConstantForce(mu=0.01).set_interest(delta=0.05)
>>> life.term_insurance(35, t=35, discrete=False) + life.E_x(35, t=35)*0.
↪51791

Methods:
--------

e_x(x, s, t, curtate, moment):


Expected lifetime E[T_x] is memoryless: does not depend on (x)

E_x(x, s, t, endowment, moment):


Shortcut for pure endowment: does not depend on age x

whole_life_insurance(x, s, moment, b, discrete):


Shortcut for APV of whole life: does not depend on age x

temporary_annuity(x, s, t, b, variance, discrete):


Shortcut for temporary life annuity: does not depend on age x

term_insurance(x, s, t, b, moment, discrete):


Shortcut for APV of term life: does not depend on age x

Z_t(x, prob, discrete):


Shortcut for T_x (or K_x) given survival probability for insurance

Y_t(x, prob, discrete):


Shortcut for T_x (or K_x) given survival probability for annuity

17.7 Examples

To use the constant force of mortality assumption, the ConstantForce subclass can be initialized with the value of
mu; then shortcut formulas for computing life contingencies, where available, would be called instead of general inherited
methods.

life = ConstantForce(mu=0.01).set_interest(delta=0.05)
life.term_insurance(35, t=35, discrete=False), life.E_x(35, t=35)

(0.14625726195783623, 0.12245642825298157)

SOA Question 6.36


For a fully continuous 20-year term insurance policy of 100,000 on (50), you are given:
• Gross premiums, calculated using the equivalence principle, are payable at an annual rate of 4500
• Expenses at an annual rate of R are payable continuously throughout the life of the policy
• 𝜇50+𝑡 = 0.04, for 𝑡 > 0
• 𝛿 = 0.08
Calculate R.

17.7. Examples 131


Solving Actuarial Math with Python

print("SOA Question 6.36: (B) 500")


life = ConstantForce(mu=0.04).set_interest(delta=0.08)
a = life.temporary_annuity(50, t=20, discrete=False)
A = life.term_insurance(50, t=20, discrete=False)
def fun(R):
return life.gross_premium(a=a, A=A, initial_premium=R/4500,
renewal_premium=R/4500, benefit=100000)
R = [Link](fun, target=4500, grid=[400, 800])
print(R)

SOA Question 6.36: (B) 500


500.0

SOA Question 6.31


For a fully continuous whole life insurance policy of 100,000 on (35), you are given:
• The density function of the future lifetime of a newborn: $$
• 𝛿 = 0.05
• 𝐴70 = 0.51791
Calculate the annual net premium rate for this policy.

print("SOA Question 6.31: (D) 1330")


life = ConstantForce(mu=0.01).set_interest(delta=0.05)
A = life.term_insurance(35, t=35) + life.E_x(35, t=35) * 0.51791 # A_35
A = (life.term_insurance(35, t=35, discrete=False)
+ life.E_x(35, t=35) * 0.51791) # A_35
P = life.premium_equivalence(A=A, b=100000, discrete=False)
print(P)

SOA Question 6.31: (D) 1330


1326.5406293909457

SOA Question 6.27


For a special fully continuous whole life insurance on (x), you are given:
• Premiums and benefits:

First 20 years After 20 years

Premium Rate 3P P
Benefit 1,000,000 500,000

• 𝜇𝑥+𝑡 = 0.03, 𝑡≥0


• 𝛿 = 0.06
Calculate 𝑃 using the equivalence principle.

print("SOA Question 6.27: (D) 10310")


life = ConstantForce(mu=0.03).set_interest(delta=0.06)
x = 0
(continues on next page)

132 Chapter 17. Constant Force of Mortality


Solving Actuarial Math with Python

(continued from previous page)


payments = (3 * life.temporary_annuity(x, t=20, discrete=False)
+ life.deferred_annuity(x, u=20, discrete=False))
benefits = (1000000 * life.term_insurance(x, t=20, discrete=False)
+ 500000 * life.deferred_insurance(x, u=20, discrete=False))
P = benefits / payments
print(P)

SOA Question 6.27: (D) 10310


10309.617799001708

SOA Question 5.4


(40) wins the SOA lottery and will receive both:

• A deferred life annuity of K per year, payable continuously, starting at age 40 + 𝑒40 and

• An annuity certain of K per year, payable continuously, for 𝑒40 years
You are given:
• 𝜇 = 0.02
• 𝛿 = 0.01
• The actuarial present value of the payments is 10,000
Calculate K.

print("SOA Question 5.4: (A) 213.7")


life = ConstantForce(mu=0.02).set_interest(delta=0.01)
P = 10000 / life.certain_life_annuity(40, u=life.e_x(40, curtate=False),
discrete=False)
print(P)

SOA Question 5.4: (A) 213.7


213.74552118275955

SOA Question 5.1


You are given:
• 𝛿𝑡 = 0.06, 𝑡≥0
• 𝜇𝑥 (𝑡) = 0.01, 𝑡≥0
• 𝑌 is the present value random variable for a continuous annuity of 1 per year, payable for the lifetime of (x) with
10 years certain
Calculate 𝑃 𝑟(𝑌 > 𝐸[𝑌 ]).

print("SOA Question 5.1: (A) 0.705")


life = ConstantForce(mu=0.01).set_interest(delta=0.06)
EY = life.certain_life_annuity(0, u=10, discrete=False)
print(life.p_x(0, t=life.Y_to_t(EY))) # 0.705

SOA Question 5.1: (A) 0.705


0.7053680433746505

17.7. Examples 133


Solving Actuarial Math with Python

134 Chapter 17. Constant Force of Mortality


CHAPTER

EIGHTEEN

EXTRA RISK

If the underwriter determines that an individual should be offered insurance but at above standard rates, there are different
ways in which we can model the extra mortality risk in a premium calculation.

18.1 Age rating

(𝑥) ← (𝑥 + 𝑘)
• add years to age, referred to as age rating: the insurer may compensate for extra risk by treating the individual as
being older, for example, an impaired life aged 40 might be asked to pay the same premium paid by a non- impaired
life aged 45.

18.2 Multiple of mortality rate

𝑞𝑥 ← 𝑞𝑥 ⋅ 𝑘
• multiply mortality rate by a constant, which assumes that lives are subject to mortality rates that are higher than the
standard lives’ mortality rates.

18.3 Force of mortality

A linear transformation of 𝜇𝑥 affects survival probability in the following ways: Recall that 𝜇 is exponentiated to get 𝑝,
hence adding a constant to 𝜇 requires multiplying 𝑡 𝑝𝑥 by the constant exponentiated, while multiplying 𝜇 by a factor
requires raising 𝑡 𝑝𝑥 to that power
𝜇𝑥+𝑡 ← 𝜇𝑥+𝑡 + 𝑘 ⇒ 𝑡 𝑝𝑥 ← 𝑡 𝑝𝑥 𝑒−𝑘𝑡
• add constant to force of mortality, when the extra risk is largely independent of age
𝜇𝑥+𝑡 ← 𝜇𝑥+𝑡 ⋅ 𝑘 ⇒ 𝑡 𝑝𝑥 ← (𝑡 𝑝𝑥 )𝑘
• multiply force of mortality by constant

135
Solving Actuarial Math with Python

18.4 Methods

The ExtraRisk class implements methods to adjust the survival or mortality function by extra risks.

from actuarialmath import ExtraRisk, SelectLife, SULT


import describe
[Link](ExtraRisk)

class ExtraRisk - Adjust mortality by extra risk

Args:
life : contains original survival and mortality rates
extra : amount of extra risk to adjust
risk : adjust by {"ADD_FORCE", "MULTIPLY_FORCE", "ADD_AGE", "MULTIPLY_RATE"}

Methods:
--------

q_x(x, s):
Return q_[x]+s after adding age rating or multipliying mortality rate

p_x(x, s):
Return p_[x]+s after adding or multiplying force of mortality

__getitem__(col):
Returns survival function values adjusted by extra risk

18.5 Examples

The ExtraRisk class is initialized with its life argument set to an instance of a survival model, with amount of ex-
tra risk and type of risk (either “ADD_FORCE”, “MULTIPLY_FORCE”, “ADD_AGE” or “MULTIPLY_RATE”)
specified. The q_x or p_x methods can then be called to return the adjusted mortality rate or survival probability
respectively, or an entire column of values by age can be retrieved as the item “q” or “p”.
SOA Question 5.5
For an annuity-due that pays 100 at the beginning of each year that (45) is alive, you are given:
• Mortality for standard lives follows the Standard Ultimate Life Table
• The force of mortality for standard lives age 45 + t is represented as 𝜇𝑆𝑈𝐿𝑇
45+𝑡

• The force of mortality for substandard lives age 45 + t, 𝜇𝑆45+𝑡 , is defined as:
𝜇𝑆45+𝑡 = 𝜇𝑆𝑈𝐿𝑇
45+𝑡 + 0.05, 0≤𝑡<1
= 𝜇𝑆𝑈𝐿𝑇
45+𝑡 , 𝑡≥1
• 𝑖 = 0.05
Calculate the actuarial present value of this annuity for a substandard life age 45.

print("SOA Question 5.5: (A) 1699.6")


life = SULT()
extra = ExtraRisk(life=life, extra=0.05, risk="ADD_FORCE")
select = SelectLife(periods=1).set_interest(i=.05)\
(continues on next page)

136 Chapter 18. Extra Risk


Solving Actuarial Math with Python

(continued from previous page)


.set_select(s=0, age_selected=True, q=extra['q'])\
.set_select(s=1, age_selected=False, a=life['a'])\
.fill_table()
print(100*select['a'][45][0])

SOA Question 5.5: (A) 1699.6


1699.6076593190103

SOA Question 4.19


(80) purchases a whole life insurance policy of 100,000. You are given:
• The policy is priced with a select period of one year
• The select mortality rate equals 80% of the mortality rate from the Standard Ultimate Life Table
• Ultimate mortality follows the Standard Ultimate Life Table
• 𝑖 = 0.05
Calculate the actuarial present value of the death benefits for this insurance

print("SOA Question 4.19: (B) 59050")


life = SULT()
extra = ExtraRisk(life=life, extra=0.8, risk="MULTIPLY_RATE")
select = SelectLife(periods=1).set_interest(i=.05)\
.set_select(s=0, age_selected=True, q=extra['q'])\
.set_select(s=1, age_selected=False, q=life['q'])\
.fill_table()
print(100000*select.whole_life_insurance(80, s=0))

SOA Question 4.19: (B) 59050


59050.59973285648

Other examples

life = SULT()
extra = ExtraRisk(life=life, extra=2, risk="MULTIPLY_FORCE")
print(life.p_x(45), extra.p_x(45))

0.9992288829941123 0.9984583606096613

18.5. Examples 137


Solving Actuarial Math with Python

138 Chapter 18. Extra Risk


CHAPTER

NINETEEN

1/M’THLY

A 1/m-thly annuity pays 1/𝑚 every 𝑚-th of a year, while a 1/m-thly insurance pays $1 of benefits at the end of the
(𝑚)
1/𝑚-th year of death. A new lifetime random variable 𝐾𝑥 is introduced to value benefits which depend on the number
of complete periods of length 1/𝑚 years lived by a life (x).
(𝑚) 1
𝐾𝑥 = ⌊𝑚𝑇𝑥 ⌋
𝑚
• 1/mthly curtate future lifetime random variable, where 𝑚 > 1 is an integer, is the future lifetime of (x) in years
1
rounded to the lower 𝑚 th of a year.
(𝑚) 1
𝑃 𝑟[𝐾𝑥 = 𝑘] = 𝑃 𝑟[𝑘 ≤ 𝑇𝑥 ≤ 𝑘 + ]= 1 𝑞
𝑘| 𝑚 𝑥 = 𝑘 𝑝𝑥 − 1 𝑝
𝑘+ 𝑚 𝑥
𝑚
(𝑚)
• the probability function for 𝐾𝑥 can be derived from the associated probabilities for 𝑇𝑥 .

19.1 Life Insurance

Whole life insurance


(𝑘)
𝑍 = 𝑣 𝐾𝑥 +1/𝑚

• present value random variable of 1/mthly whole life insurance


(𝑚) ∞ 𝑘+1
𝐴𝑥 = 𝐸[𝑍] = ∑𝑘=0 𝑣 𝑚 𝑘 1 𝑞𝑥
𝑚|𝑚

• 1/m’thly whole life insurance


(𝑘) (𝑚)
𝐸[𝑍 2 ] = 𝐸[(𝑣2 )𝐾𝑥 +1/𝑚
] = 2 𝐴𝑥
(𝑚)
• second moment is also obtained from 𝐴𝑥 at double the force of interest
2 2 2 (𝑚) (𝑚)
𝑉 𝑎𝑟(𝑍) = 𝐸[𝑍 ] − 𝐸[𝑍] = 𝐴𝑥 − (𝐴𝑥 )2
• the variance of the present value of the 1/𝑚’thly insurance benefit can be derived by adjusting the interest rate in
the first time
Term life insurance
(𝑚) (𝑘)
𝑍 = 0 if 𝐾𝑥 ≥ 𝑡, else 𝑣𝐾𝑥 +1/𝑚

• death benefit is payable at the end of the 1/m-th year of death , provided this occurs within 𝑡 years.
(𝑚) 𝑚𝑡−1 𝑘+1
𝐴1𝑥∶𝑡| = ∑𝑘=0 𝑣 𝑚 𝑘 1 𝑞𝑥
𝑚|𝑚

• EPV of 1/m-thly term insurance benefits

139
Solving Actuarial Math with Python

19.2 Life Annuity

(𝑚) 1 𝑘

𝑎𝑥̈ = ∑𝑘=0 𝑣 𝑚 𝑘 𝑝𝑥
𝑚 𝑚

• 1/mthly whole life annuity


(𝑚) 1 𝑘
𝑚𝑡−1
𝑎𝑥∶𝑡|
̈ = ∑𝑘=0 𝑣 𝑚 𝑘 𝑝𝑥
𝑚 𝑚

• 1/mthly temporary life annuity


(𝑚) (𝑚) 1
𝑎𝑥 = 𝑎𝑥̈ −
𝑚
• immediate 1/m’thly whole life annuity
(𝑚) (𝑚) 1
𝑎𝑥∶𝑡| = 𝑎𝑥∶𝑡|
̈ − (1 − 𝑡 𝐸𝑥 )
𝑚
• immediate 1/m’thly temporary life annuity

19.3 Life Insurance Twin

Whole and Temporary Life Annuities (and Whole Life and Endowment Insurance) ONLY:
(𝑚)
(𝑚) (𝑚) (𝑚) 1 − 𝐴𝑥
𝐴𝑥 = 1 − 𝑑(𝑚) 𝑎𝑥̈ ⟺ 𝑎𝑥̈ =
𝑑(𝑚)
• 1/m’thly whole life annuity due
(𝑚)
1 − 𝐴𝑥∶𝑡|
(𝑚) (𝑚) (𝑚) (𝑚)
𝐴𝑥∶𝑡| =1−𝑑 𝑎𝑥∶𝑡|
̈ ⟺ 𝑎𝑥∶𝑡|
̈ =
𝑑(𝑚)
• 1/m’thly temporary annuity due and endowment insurance

19.4 Methods

Ths Mthly class implements methods to compute life insurance and annuity values with 1/mthly benefits.

from actuarialmath import Mthly, Premiums, LifeTable


import describe
[Link](Mthly)

class Mthly - Compute 1/M'thly insurance and annuities

Args:
m : number of payments per year
life : original survival and life contingent functions

Methods:
--------

v_m(k):
Compute discount rate compounded over k m'thly periods

(continues on next page)

140 Chapter 19. 1/M’thly


Solving Actuarial Math with Python

(continued from previous page)


p_m(x, s_m, t_m):
Compute survival probability over m'thly periods

q_m(x, s_m, t_m, u_m):


Compute deferred mortality over m'thly periods

Z_m(x, s, t, benefit, moment):


Return PV of insurance r.v. Z and probability of death at mthly intervals

E_x(x, s, t, moment, endowment):


Compute pure endowment factor

A_x(x, s, t, u, benefit, moment):


Compute insurance factor with m'thly benefits

whole_life_insurance(x, s, moment, b):


Whole life insurance: A_x

term_insurance(x, s, t, b, moment):
Term life insurance: A_x:t^1

deferred_insurance(x, s, n, b, t, moment):
Deferred insurance n|_A_x:t^1 = discounted whole life

endowment_insurance(x, s, t, b, endowment, moment):


Endowment insurance: A_x:t = term insurance + pure endowment

immediate_annuity(x, s, t, b):
Immediate m'thly annuity

insurance_twin(a):
Return insurance twin of m'thly annuity

annuity_twin(A):
Return value of annuity twin of m'thly insurance

annuity_variance(A2, A1, b):


Variance of m'thly annuity from m'thly insurance moments

whole_life_annuity(x, s, b, variance):
Whole life m'thly annuity: a_x

temporary_annuity(x, s, t, b, variance):
Temporary m'thly life annuity: a_x:t

deferred_annuity(x, s, u, t, b):
Deferred m'thly life annuity due n|t_a_x = n+t_a_x - n_a_x

immediate_annuity(x, s, t, b):
Immediate m'thly annuity

19.4. Methods 141


Solving Actuarial Math with Python

19.5 Examples

The Mthly class is initialized with its life argument set to an instance of a survival model with fractional age assump-
tion, and the number of periods 𝑚 in a year. The v_m, p_m and q_m methods can then be called to compute the discount
factor, survival probability and mortality rate for m-thly ages and durations. The Z_m methods returns a table of the PV
of the insurance random variable and deferred mortality rate by m-thly time of death. Actuarial present values, at integer
ages, of insurance and annuities that pay every m-th of a year, can be computed by calling their respective methods in the
usual manner.
SOA Question 6.4
For whole life annuities-due of 15 per month on each of 200 lives age 62 with independent future lifetimes, you are given:
• 𝑖 = 0.06
(12) (12)
• 𝐴62 = 0.2105 and 2 𝐴62 = 0.4075
• 𝜋 is the single premium to be paid by each of the 200 lives
• 𝑆 is the present value random variable at time 0 of total payments made to the 200 lives
Using the normal approximation, calculate 𝜋 such that 𝑃 𝑟(200𝜋 > 𝑆) = 0.90.

print("SOA Question 6.4: (E) 1893.9")


mthly = Mthly(m=12, life=Premiums().set_interest(i=0.06))
A1, A2 = 0.4075, 0.2105
mean = mthly.annuity_twin(A1)*15*12
var = mthly.annuity_variance(A1=A1, A2=A2, b=15 * 12)
S = Premiums.portfolio_percentile(mean=mean, variance=var, prob=.9, N=200)
print(S / 200)

SOA Question 6.4: (E) 1893.9


1893.912859650868

SOA Question 4.2


For a special 2-year term insurance policy on (x), you are given:
• Death benefits are payable at the end of the half-year of death
• The amount of the death benefit is 300,000 for the first half-year and increases by 30,000 per half-year thereafter
• 𝑞𝑥 = 0.16 and 𝑞𝑥+1 = 0.23
• 𝑖(2) = 0.18
• Deaths are assumed to follow a constant force of mortality between integral ages
• Z is the present value random variable for this insurance
Calculate 𝑃 𝑟(𝑍 > 277, 000).

print("SOA Question 4.2: (D) 0.18")


life = LifeTable(udd=False).set_table(q={0: 0.16, 1: 0.23})\
.set_interest(i_m=0.18, m=2)
mthly = Mthly(m=2, life=life)
Z = mthly.Z_m(0, t=2, benefit=lambda x,t: 300000 + t*30000*2)
print(Z)
print(Z[Z['Z'] >= 277000]['q'].sum())

142 Chapter 19. 1/M’thly


Solving Actuarial Math with Python

SOA Question 4.2: (D) 0.18


Z q
m
1 275229.357798 0.083485
2 277754.397778 0.076515
3 277986.052822 0.102903
4 276285.832315 0.090297
0.17941813045022975

19.5. Examples 143


Solving Actuarial Math with Python

144 Chapter 19. 1/M’thly


CHAPTER

TWENTY

UDD M’THLY

With the UDD fractional age assumption, we can work with annual insurance and annuity factors 𝐴𝑥 and 𝑎𝑥̈ , then adjust
(𝑚) (𝑚)
for a more appropriate frequency 𝐴𝑥 and 𝑎𝑥̈ using the following relationships.

20.1 Life insurance


(𝑚)
Under UDD, the values of annual 𝐴𝑥 can be used to derive exact results for 1/mthly insurance 𝐴𝑥 .
(𝑚) 𝑖
𝐴𝑥 = 𝐴𝑥
𝑖(𝑚)
• discrete whole life insurance
2 (𝑚) 𝑖2 − 2𝑖 2
𝐴𝑥 = 𝐴𝑥
(𝑖(𝑚) )2 − 2𝑖(𝑚)
• doubling the force of interest for discrete whole life insurance
1 (𝑚) 𝑖
𝐴𝑥∶𝑡| = 𝐴1𝑥∶𝑡|
𝑖(𝑚)
• discrete term insurance
(𝑚) 𝑖
𝐴𝑥∶𝑡| = (𝑚) 𝐴1𝑥∶𝑡| + 𝑡 𝐸𝑥
𝑖
• endowment insurance combines the death and survival benefits, so we need to split off the death benefit to apply
the derivations.
(𝑚) 𝑖
𝑢| 𝐴𝑥 = 𝑢 𝐸𝑥 (𝑚) 𝐴𝑥+𝑢
𝑖
• discrete deferred insurance

20.2 Continuous Life Insurance

Under UDD, continuous life insurance can also be related to annual life insurance factors
𝑖
𝐴𝑥 = 𝐴𝑥
𝛿
• whole life insurance
2 𝑖2 − 2𝑖 2
𝐴𝑥 = 𝐴𝑥
2𝛿
• doubling the force of interest for whole life insurance

145
Solving Actuarial Math with Python

1 𝑖
𝐴𝑥∶𝑡| = 𝐴1𝑥∶𝑡|
𝛿
• term life insurance
𝑖
𝐴𝑥∶𝑡| = 𝐴1𝑥∶𝑡| + 𝑡 𝐸𝑥
𝛿
• endowment insurance
𝑖
𝑢| 𝐴𝑥 = 𝑢 𝐸𝑥 𝐴𝑥+𝑢
𝛿
• deferred life insurance

20.3 Interest functions

Under UDD, values of 1/mthly life annuities can be adjusted from annual life annuity factors using interest rate functions
𝛼(𝑚) and 𝛽(𝑚). This can be shown by subtituting in annuity twins into the insurance relationships above.
𝑖𝑑
𝛼(𝑚) =
𝑖(𝑚) 𝑑(𝑚)
𝑖 − 𝑖(𝑚)
𝛽(𝑚) =
𝑖(𝑚) 𝑑(𝑚)

20.4 Life annuities


(𝑚)
Using the values of 𝑎𝑥̈ , and the interest rate functions, to obtain 𝑎𝑥̈ under UDD:
(𝑚)
𝑎𝑥̈ = 𝛼(𝑚) 𝑎𝑥̈ − 𝛽(𝑚)
• whole life annuity
(𝑚)
𝑎𝑥∶𝑛|
̈ = 𝛼(𝑚) 𝑎𝑥∶𝑛|
̈ − 𝛽(𝑚)(1 − 𝑡 𝐸𝑥 )

• temporary life annuity


(𝑚)
𝑢| 𝑎𝑥
̈ = 𝛼(𝑚) 𝑢| 𝑎𝑥̈ − 𝛽(𝑚) 𝑢 𝐸𝑥
• deferred whole life annuity

20.5 Methods

The UDD class implements an instance of Mthly, which assumes uniform distribution of deaths (UDD) between integer
ages, to compute life insurance and annuities with 1/mthly benefits with shortcut formulas.

from actuarialmath import UDD, SULT, Recursion, Contract


import describe
[Link](UDD)

class UDD - 1/mthly shortcuts with UDD assumption

Args:
m : number of payments per year
(continues on next page)

146 Chapter 20. UDD M’thly


Solving Actuarial Math with Python

(continued from previous page)


life : original fractional survival and mortality functions

Methods:
--------

alpha(m, i):
Derive 1/mthly UDD interest rate beta function value

beta(m, i):
Derive 1/mthly UDD interest rate alpha function value

interest_frame(i):
Display 1/mthly UDD interest function values

20.6 Examples

SOA Question 6.38


For an n-year endowment insurance of 1000 on (x), you are given:
• Death benefits are payable at the moment of death
• Premiums are payable annually at the beginning of each year
• Deaths are uniformly distributed over each year of age
• 𝑖 = 0.05
• 𝑛 𝐸𝑥 = 0.172
• 𝐴𝑥∶𝑛| = 0.192
Calculate the annual net premium for this insurance.

print("SOA Question 6.38: (B) 11.3")


x, n = 0, 10
life = Recursion().set_interest(i=0.05)\
.set_A(0.192, x=x, t=n, endowment=1, discrete=False)\
.set_E(0.172, x=x, t=n)
a = life.temporary_annuity(x, t=n, discrete=False)
print(a)
def fun(a): # solve for discrete annuity, given continuous
life = Recursion().set_interest(i=0.05)\
.set_a(a, x=x, t=n)\
.set_E(0.172, x=x, t=n)
return UDD(m=0, life=life).temporary_annuity(x, t=n)
a = [Link](fun, target=a, grid=a) # discrete annuity
P = life.gross_premium(a=a, A=0.192, benefit=1000)
print(a, P)

SOA Question 6.38: (B) 11.3


*Temporary Annuity a_0(t=10) <--
a_0(t=10) = [ 1 - A_0(t=10) ] / d(t=10) ~annuity twin
a_0(t=1) = 1 ~one-year discrete annuity
a_1(t=1) = 1 ~one-year discrete annuity
(continues on next page)

20.6. Examples 147


Solving Actuarial Math with Python

(continued from previous page)


16.560714925944584
16.978162620976775 11.308644185253657

SOA Question 6.32


For a whole life insurance of 100,000 on (x), you are given:
• Death benefits are payable at the moment of death
• Deaths are uniformly distributed over each year of age
• Premiums are payable monthly
• 𝑖 = 0.05
• 𝑎𝑥̈ = 9.19
Calculate the monthly net premium.

print("SOA Question 6.32: (C) 550")


x = 0
life = Recursion().set_interest(i=0.05).set_a(9.19, x=x)
benefits = UDD(m=0, life=life).whole_life_insurance(x)
payments = UDD(m=12, life=life).whole_life_annuity(x)
print(benefits, payments)
print(life.gross_premium(a=payments, A=benefits, benefit=100000)/12)

SOA Question 6.32: (C) 550


*Whole Life Insurance A_0(t=WL) <--
A_x = 1 - d * a_x ~annuity twin
0.5763261529803323 8.72530251348809
550.4356936711871

SOA Question 6.22


For a whole life insurance of 100,000 on (45) with premiums payable monthly for a period of 20 years, you are given:
• The death benefit is paid immediately upon death
• Mortality follows the Standard Ultimate Life Table
• Deaths are uniformly distributed over each year of age
• 𝑖 = 0.05
Calculate the monthly net premium.

print("SOA Question 6.22: (C) 102")


life = SULT(udd=True)
a = UDD(m=12, life=life).temporary_annuity(45, t=20)
A = UDD(m=0, life=life).whole_life_insurance(45)
print(life.gross_premium(A=A, a=a, benefit=100000)/12)

SOA Question 6.22: (C) 102


102.40668704849178

SOA Question 7.9


For a semi-continuous 20-year endowment insurance of 100,000 on (45), you are given:

148 Chapter 20. UDD M’thly


Solving Actuarial Math with Python

• Net premiums of 253 are payable monthly


• Mortality follows the Standard Ultimate Life Table
• Deaths are uniformly distributed over each year of age
• 𝑖 = 0.05
Calculate 10 𝑉 , the net premium policy value at the end of year 10 for this insurance.

print("SOA Question 7.9: (A) 38100")


sult = SULT(udd=True)
x, n, t = 45, 20, 10
a = UDD(m=12, life=sult).temporary_annuity(x+10, t=n-10)
A = UDD(m=0, life=sult).endowment_insurance(x+10, t=n-10)
print(a, A)
contract = Contract(premium=253*12, endowment=100000, benefit=100000)
print(A*100000 - a*12*253, sult.gross_future_loss(A=A, a=a, contract=contract))

SOA Question 7.9: (A) 38100


7.831075686716718 0.6187476755196442
38099.62176709247 38099.62176709246

SOA Question 6.49


For a special whole life insurance of 100,000 on (40), you are given:
• The death benefit is payable at the moment of death
• Level gross premiums are payable monthly for a maximum of 20 years
• Mortality follows the Standard Ultimate Life Table
• 𝑖 = 0.05
• Deaths are uniformly distributed over each year of age
• Initial expenses are 200
• Renewal expenses are 4% of each premium including the first
• Gross premiums are calculated using the equivalence principle
Calculate the monthly gross premium.

print("SOA Question 6.49: (C) 86")


sult = SULT(udd=True)
a = UDD(m=12, life=sult).temporary_annuity(40, t=20)
A = sult.whole_life_insurance(40, discrete=False)
P = sult.gross_premium(a=a, A=A, benefit=100000, initial_policy=200,
renewal_premium=0.04, initial_premium=0.04)
print(P/12)

SOA Question 6.49: (C) 86


85.99177833261696

Generate table of interest functions (for FAM-L exam):

print("Interest Functions at i=0.05")


print("----------------------------")
print(UDD.interest_frame())

20.6. Examples 149


Solving Actuarial Math with Python

Interest Functions at i=0.05


----------------------------
i(m) d(m) i/i(m) d/d(m) alpha(m) beta(m)
1 0.05000 0.04762 1.00000 1.00000 1.00000 0.00000
2 0.04939 0.04820 1.01235 0.98795 1.00015 0.25617
4 0.04909 0.04849 1.01856 0.98196 1.00019 0.38272
12 0.04889 0.04869 1.02271 0.97798 1.00020 0.46651
0 0.04879 0.04879 1.02480 0.97600 1.00020 0.50823

150 Chapter 20. UDD M’thly


CHAPTER

TWENTYONE

WOOLHOUSE M’THLY

Woolhouse’s formula is a method of approximating 1/mthly life annuities from annual factors that does not depend on
a fractional age assumption. It is based on the Euler-Maclaurin series expansion for the integral of a function. Life
insurances may then be computed from twin relationships.

21.1 Life Annuities

(𝑚) 𝑚 − 1 𝑚2 − 1
𝑎𝑥̈ ≈ 𝑎𝑥̈ − − (𝜇𝑥 + 𝛿)
2𝑚 12𝑚2
• 1/m’thly whole life annuity using the three-term Woolhouse approximation. The third term is often omitted in
practice, which leads to poor approximations in some cases.
(𝑚) (𝑚) (𝑚) 𝑚−1 𝑚2 − 1
𝑎𝑥∶𝑡|
̈ ≈ 𝑎𝑥̈ − 𝑡 𝐸𝑥 𝑎𝑥+𝑡
̈ ̈ −
= 𝑎𝑥∶𝑡| (1 − 𝑡 𝐸𝑥 ) − (𝜇𝑥 + 𝛿 − 𝑡 𝐸𝑥 (𝜇𝑥+𝑡 + 𝛿))
2𝑚 12𝑚2
• 1/m’thly temporary life annuity from the difference of whole life Woolhouse approximations
1 1
𝑎𝑥 ≈ 𝑎𝑥̈ − − (𝜇𝑥 + 𝛿)
2 12
• continuous life annuity with Woolhouse approximation when we let 𝑚 → ∞.
1
𝜇𝑥 ≈ − (ln 𝑝𝑥−1 + ln 𝑝𝑥 )
2
• if the force of mortality 𝜇 is not provided for the third Woolhouse term, it can be approximated from survival
probabilities at integer ages.

21.2 Methods

Ths Woolhouse class implements an instance of Mthly, which uses the Woolhouse assumption with either two or three
terms, to compute m-thly pay annuities with Woolhouse approximation formulas, from which 1/m-thly life insurance are
computed from their twin formulas.

from actuarialmath import Woolhouse, SULT, Recursion, UDD, Contract


import describe
[Link](Woolhouse)

class Woolhouse - 1/m'thly shortcuts with Woolhouse approximation

Args:
(continues on next page)

151
Solving Actuarial Math with Python

(continued from previous page)


m : number of payments per year
life : original fractional survival and mortality functions
three_term : whether to include (True) or ignore (False) third term
approximate_mu : exact (False), approximate (True) or function for third term

Methods:
--------

mu_x(x, s):
Computes mu_x or calls approximate_mu for third term

21.3 Examples

SOA Question 7.7


For a whole life insurance of 10,000 on (x), you are given:
• Death benefits are payable at the end of the year of death
• A premium of 30 is payable at the start of each month
• Commissions are 5% of each premium
• Expenses of 100 are payable at the start of each year
• 𝑖 = 0.05
• 1000𝐴𝑥+10 = 400
• 10 𝑉 is the gross premium policy value at the end of year 10 for this insurance
Calculate 10 𝑉 using the two-term Woolhouse formula for annuities.

print("SOA Question 7.7: (D) 1110")


x = 0
life = Recursion().set_interest(i=0.05).set_A(0.4, x=x+10)
a = Woolhouse(m=12, life=life).whole_life_annuity(x+10)
print(a)
contract = Contract(premium=0, benefit=10000, renewal_policy=100)
V = life.gross_future_loss(A=0.4, contract=[Link]())
contract = Contract(premium=30*12, renewal_premium=0.05)
V1 = life.gross_future_loss(a=a, contract=[Link]())
print(V, V1, V+V1)

SOA Question 7.7: (D) 1110

Whole Life Annuity 𝑎𝑥+10̈ ∶=


𝑎𝑥+10
̈ = [1 − 𝐴𝑥+10 ]/𝑑 insurance twin

12.141666666666666
5260.0 -4152.028174603174 1107.9718253968258

152 Chapter 21. Woolhouse M’thly


Solving Actuarial Math with Python

SOA Question 6.25:


For a fully discrete 10-year deferred whole life annuity-due of 1000 per month on (55), you are given:
• The premium, 𝐺, will be paid annually at the beginning of each year during the deferral period
• Expenses are expected to be 300 per year for all years, payable at the beginning of the year
• Mortality follows the Standard Ultimate Life Table
• 𝑖 = 0.05
• Using the two-term Woolhouse approximation, the expected loss at issue is -800
Calculate 𝐺.

print("SOA Question 6.25: (C) 12330")


life = SULT()
woolhouse = Woolhouse(m=12, life=life)
benefits = woolhouse.deferred_annuity(55, u=10, b=1000 * 12)
expenses = life.whole_life_annuity(55, b=300)
payments = life.temporary_annuity(55, t=10)
print(benefits + expenses, payments)
def fun(G):
return life.gross_future_loss(A=benefits + expenses, a=payments,
contract=Contract(premium=G))
G = [Link](fun, target=-800, grid=[12110, 12550])
print(G)

SOA Question 6.25: (C) 12330


98042.52569470297 8.019169307712845
12325.781125438532

SOA Question 6.15


For a fully discrete whole life insurance of 1000 on (x) with net premiums payable quarterly, you are given:
• 𝑖 = 0.05
• 𝑎𝑥̈ = 3.4611
• 𝑃 (𝑊 ) and 𝑃 (𝑈𝐷𝐷) are the annualized net premiums calculated using the 2-term Woolhouse (W) and the uniform
distribution of deaths (UDD) assumptions, respectively
𝑃 (𝑈𝐷𝐷)
Calculate .
𝑃 (𝑊 )
print("SOA Question 6.15: (B) 1.002")
x = 0
life = Recursion().set_interest(i=0.05).set_a(3.4611, x=0)
A = life.insurance_twin(3.4611)
udd = UDD(m=4, life=life)
a1 = udd.whole_life_annuity(x=x)
woolhouse = Woolhouse(m=4, life=life)
a2 = woolhouse.whole_life_annuity(x=x)
print(life.gross_premium(a=a1, A=A)/life.gross_premium(a=a2, A=A))

SOA Question 6.15: (B) 1.002


1.0022973504113772

21.3. Examples 153


Solving Actuarial Math with Python

SOA Question 5.7


You are given:
• 𝐴35 = 0.188
• 𝐴65 = 0.498
• 30 𝑝35 = 0.883
• 𝑖 = 0.04
(2)
Calculate 1000𝑎35∶30|
̈ using the two-term Woolhouse approximation.

print("SOA Question 5.7: (C) 17376.7")


life = Recursion().set_interest(i=0.04)
life.set_A(0.188, x=35)
life.set_A(0.498, x=65)
life.set_p(0.883, x=35, t=30)
mthly = Woolhouse(m=2, life=life, three_term=False)
print(mthly.temporary_annuity(35, t=30))
print(1000 * mthly.temporary_annuity(35, t=30))

SOA Question 5.7: (C) 17376.7

Whole Life Annuity 𝑎𝑥+35̈ ∶=


𝑎𝑥+35
̈ = [1 − 𝐴𝑥+35 ]/𝑑 insurance twin

Whole Life Annuity 𝑎𝑥+65̈ ∶=


𝑎𝑥+65
̈ = [1 − 𝐴𝑥+65 ]/𝑑 insurance twin
Pure Endowment 30 𝐸𝑥+35 ∶=
30
30 𝐸𝑥+35 = 30 𝑝𝑥+35 ∗ 𝑣 pure endowment

17.37671459632958

Whole Life Annuity 𝑎𝑥+35̈ ∶=


𝑎𝑥+35
̈ = [1 − 𝐴𝑥+35 ]/𝑑 insurance twin
Whole Life Annuity 𝑎𝑥+65̈ ∶=
𝑎𝑥+65
̈ = [1 − 𝐴𝑥+65 ]/𝑑 insurance twin
Pure Endowment 30 𝐸𝑥+35 ∶=
30
30 𝐸𝑥+35 = 30 𝑝𝑥+35 ∗ 𝑣 pure endowment

17376.71459632958

154 Chapter 21. Woolhouse M’thly


CHAPTER

TWENTYTWO

FAM-L EXAM SOLUTIONS AND HINTS

actuarialmath – Solving Life Contingent Risks with Python


This package implements fundamental methods for modeling life contingent risks, and closely follows traditional topics
covered in actuarial exams and standard texts such as the “Fundamentals of Actuarial Math - Long-term” exam syllabus by
the Society of Actuaries, and “Actuarial Mathematics for Life Contingent Risks” by Dickson, Hardy and Waters. These
code chunks demonstrate how to solve each of the sample FAM-L exam questions released by the SOA.
Sources:
• SOA FAM-L Exam Questions: copy retrieved Aug 2022
• SOA FAM-L Exam Solutions: copy retrieved Aug 2022
• User Guide, or download pdf
• API reference
• Github repo and issues

# Uncomment next line to install package


#! pip install actuarialmath

"""Solutions code and hints for SOA FAM-L sample questions

MIT License. Copyright 2022-2023, Terence Lim


"""
import math
import pandas as pd
import [Link] as plt
import numpy as np
from actuarialmath import Interest
from actuarialmath import Life
from actuarialmath import Survival
from actuarialmath import Lifetime
from actuarialmath import Fractional
from actuarialmath import Insurance
from actuarialmath import Annuity
from actuarialmath import Premiums
from actuarialmath import PolicyValues, Contract
from actuarialmath import Reserves
from actuarialmath import Recursion
from actuarialmath import LifeTable
from actuarialmath import SULT
from actuarialmath import SelectLife
from actuarialmath import MortalityLaws, Beta, Uniform, Makeham, Gompertz
(continues on next page)

155
Solving Actuarial Math with Python

(continued from previous page)


from actuarialmath import ConstantForce
from actuarialmath import ExtraRisk
from actuarialmath import Mthly
from actuarialmath import UDD
from actuarialmath import Woolhouse

Helper to compare computed answers to expected solutions

import time
class IsClose:
"""Helper class for testing and reporting if two values are close"""
def __init__(self, rel_tol : float = 0.01, score : bool = False,
verbose: bool = False):
[Link] = [Link] = 0
[Link] = score # whether to count INCORRECTs instead of assert
[Link] = verbose # whether to run silently
[Link] = [] # to keep list of messages for INCORRECT
[Link] = rel_tol
[Link] = [Link]()

def __call__(self, solution, answer, question="", rel_tol=None):


"""Compare solution to answer within relative tolerance

Args:
solution (str | numeric) : gold label
answer (str | numeric) : computed answer
question (str) : label to associate with this test
rel_tol (float) : relative tolerance to be considered close
"""
if isinstance(solution, str):
isclose = (solution == answer)
else:
isclose = [Link](solution, answer, rel_tol=rel_tol or [Link])
[Link] += 1
[Link] += isclose
msg = f"{question} {solution}: {answer}"
if [Link]:
print("-----", msg, "[OK]" if isclose else "[INCORRECT]", "-----")
if not [Link]:
assert isclose, msg
if not isclose:
[Link](msg)
return isclose

def __str__(self):
"""Display cumulative score and errors"""
return f"Elapsed: {[Link]()-[Link]:.1f} secs\n" \
+ f"Passed: {[Link]}/{[Link]}\n" + "\n".join([Link])
isclose = IsClose(0.01, score=False, verbose=True)

156 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

22.1 1 Tables

These tables are provided in the FAM-L exam


• Interest Functions at i=0.05
• Normal Distribution Table
• Standard Ultimate Life Table
but you actually do not need them here!

print("Interest Functions at i=0.05")


UDD.interest_frame()

Interest Functions at i=0.05

i(m) d(m) i/i(m) d/d(m) alpha(m) beta(m)


1 0.05000 0.04762 1.00000 1.00000 1.00000 0.00000
2 0.04939 0.04820 1.01235 0.98795 1.00015 0.25617
4 0.04909 0.04849 1.01856 0.98196 1.00019 0.38272
12 0.04889 0.04869 1.02271 0.97798 1.00020 0.46651
0 0.04879 0.04879 1.02480 0.97600 1.00020 0.50823

print("Values of z for selected values of Pr(Z<=z)")


print(Life.quantiles_frame().to_string(float_format=lambda x: f"{x:.3f}"))

Values of z for selected values of Pr(Z<=z)


z 0.842 1.036 1.282 1.645 1.960 2.326 2.576
Pr(Z<=z) 0.800 0.850 0.900 0.950 0.975 0.990 0.995

print("Standard Ultimate Life Table at i=0.05")


SULT().frame()

Standard Ultimate Life Table at i=0.05

l_x q_x a_x A_x 2A_x a_x:10 A_x:10 a_x:20


20 100000.0 0.000250 19.9664 0.04922 0.00580 8.0991 0.61433 13.0559 \
21 99975.0 0.000253 19.9197 0.05144 0.00614 8.0990 0.61433 13.0551
22 99949.7 0.000257 19.8707 0.05378 0.00652 8.0988 0.61434 13.0541
23 99924.0 0.000262 19.8193 0.05622 0.00694 8.0986 0.61435 13.0531
24 99897.8 0.000267 19.7655 0.05879 0.00739 8.0983 0.61437 13.0519
.. ... ... ... ... ... ... ... ...
96 17501.8 0.192887 3.5597 0.83049 0.69991 3.5356 0.83164 3.5597
97 14125.9 0.214030 3.3300 0.84143 0.71708 3.3159 0.84210 3.3300
98 11102.5 0.237134 3.1127 0.85177 0.73356 3.1050 0.85214 3.1127
99 8469.7 0.262294 2.9079 0.86153 0.74930 2.9039 0.86172 2.9079
100 6248.2 0.289584 2.7156 0.87068 0.76427 2.7137 0.87078 2.7156

A_x:20 5_E_x 10_E_x 20_E_x


20 0.37829 0.78252 0.61224 0.37440
21 0.37833 0.78250 0.61220 0.37429
22 0.37837 0.78248 0.61215 0.37417
(continues on next page)

22.1. 1 Tables 157


Solving Actuarial Math with Python

(continued from previous page)


23 0.37842 0.78245 0.61210 0.37404
24 0.37848 0.78243 0.61205 0.37390
.. ... ... ... ...
96 0.83049 0.19872 0.01330 0.00000
97 0.84143 0.16765 0.00827 0.00000
98 0.85177 0.13850 0.00485 0.00000
99 0.86153 0.11173 0.00266 0.00000
100 0.87068 0.08777 0.00136 0.00000

[81 rows x 12 columns]

22.2 2 Survival models

SOA Question 2.1 : (B) 2.5


You are given:
1
1. 𝑆0 (𝑡) = (1 − 𝜔𝑡 ) 4 , 0≤𝑡≤𝜔
1
2. 𝜇65 = 180

Calculate 𝑒106 , the curtate expectation of life at age 106.


hints:
• derive formula for 𝜇 from given survival function
• solve for 𝜔 given 𝜇65
• calculate 𝑒 by summing survival probabilities

life = Lifetime()
def mu_from_l(omega): # first solve for omega, given mu_65 = 1/180
return life.set_survival(l=lambda x,s: (1 - (x+s)/omega)**0.25).mu_x(65)
omega = int([Link](mu_from_l, target=1/180, grid=100))
e = life.set_survival(l=lambda x,s:(1 - (x + s)/omega)**.25, maxage=omega)\
.e_x(106) # then solve expected lifetime from omega
isclose(2.5, e, question="Q2.1")

----- Q2.1 2.5: 2.4786080555423604 [OK] -----

True

SOA Question 2.2 : (D) 400


Scientists are searching for a vaccine for a disease. You are given:
1. 100,000 lives age x are exposed to the disease
2. Future lifetimes are independent, except that the vaccine, if available, will be given to all at the end of year 1
3. The probability that the vaccine will be available is 0.2
4. For each life during year 1, 𝑞𝑥 = 0.02
5. For each life during year 2, 𝑞𝑥+1 = 0.01 if the vaccine has been given and 𝑞𝑥+1 = 0.02 if it has not been given

158 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

Calculate the standard deviation of the number of survivors at the end of year 2.
hints:
• calculate survival probabilities for the two scenarios
• apply conditional variance formula (or mixed distribution)

p1 = (1. - 0.02) * (1. - 0.01) # 2_p_x if vaccine given


p2 = (1. - 0.02) * (1. - 0.02) # 2_p_x if vaccine not given
std = [Link](Life.conditional_variance(p=.2, p1=p1, p2=p2, N=100000))
isclose(400, std, question="Q2.2")

----- Q2.2 400: 396.5914603215815 [OK] -----

True

SOA Question 2.3 : (A) 0.0483


You are given that mortality follows Gompertz Law with B = 0.00027 and c = 1.1.
Calculate 𝑓50 (10).
hints:
• Derive formula for 𝑓 given survival function

B, c = 0.00027, 1.1
S = lambda x,s,t: [Link](-B * c**(x+s) * (c**t - 1)/[Link](c))
life = Survival().set_survival(S=S)
f = life.f_x(x=50, t=10)
isclose(0.0483, f, question="Q2.3")

----- Q2.3 0.0483: 0.048327399045049846 [OK] -----

True

SOA Question 2.4 : (E) 8.2


𝑡2 ∘
You are given 𝑡 𝑞0 = 10,000 0 < 𝑡 < 100. Calculate 𝑒75∶10| .
hints:
• derive survival probability function 𝑡 𝑝𝑥 given 𝑡 𝑞0

• compute 𝑒 by integration

def q(t) : return (t**2)/10000. if t < 100 else 1.


e = Lifetime().set_survival(l=lambda x,s: 1 - q(x+s)).e_x(75, t=10, curtate=False)
isclose(8.2, e, question="Q2.4")

----- Q2.4 8.2: 8.20952380952381 [OK] -----

True

22.2. 2 Survival models 159


Solving Actuarial Math with Python

SOA Question 2.5 : (B) 37.1


You are given the following:
1. 𝑒40∶20 = 18
2. 𝑒60 = 25
3. 20 𝑞40 = 0.2
4. 𝑞40 = 0.003
Calculate 𝑒41 .
hints:
• solve for 𝑒40 from limited lifetime formula
• compute 𝑒41 using forward recursion

life = Recursion(verbose=True).set_e(25, x=60, curtate=True)\


.set_q(0.2, x=40, t=20)\
.set_q(0.003, x=40)\
.set_e(18, x=40, t=20, curtate=True)
e = life.e_x(41, curtate=True)
isclose(37.1, e, question="Q2.5")

Lifetime 𝑒𝑥+41 ∶=
𝑒𝑥+41 = [ 𝑒𝑥+40 − 𝑒𝑥+40∶1| ]/ 𝑝𝑥+40 forward recursion
𝑒𝑥+40 = 𝑒𝑥+40∶20| + 20 𝑝𝑥+40 ∗ 𝑒𝑥+60 backward recursion
20 𝑝𝑥+40 = 1 − 20 𝑞𝑥+40 complement of mortality
𝑒𝑥+40∶1| = 𝑝𝑥+40 1-year curtate shortcut
𝑝𝑥+40 = 1 − 𝑞𝑥+40 complement of mortality

----- Q2.5 37.1: 37.11434302908726 [OK] -----

True

SOA Question 2.6 : (C) 13.3


You are given the survival function:
1
𝑥 3
𝑆0 (𝑥) = (1 − 60 ) , 0 ≤ 𝑥 ≤ 60
Calculate 1000𝜇35 .
hints:
• derive force of mortality function 𝜇 from given survival function

life = Survival().set_survival(l=lambda x,s: (1 - (x+s)/60)**(1/3))


mu = 1000 * life.mu_x(35)
isclose(13.3, mu, question="Q2.6")

----- Q2.6 13.3: 13.337287043994648 [OK] -----

160 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

True

SOA Question 2.7 : (B) 0.1477


You are given the following survival function of a newborn:
𝑥
𝑆0 (𝑥) = 1 − , 0 ≤ 𝑥 < 40
250
𝑥 2
=1−( ) , 40 ≤ 𝑥 ≤ 100
100
Calculate the probability that (30) dies within the next 20 years.
hints:
• calculate from given survival function

l = lambda x,s: (1-((x+s)/250) if (x+s)<40 else 1-((x+s)/100)**2)


q = Survival().set_survival(l=l).q_x(30, t=20)
isclose(0.1477, q, question="Q2.7")

----- Q2.7 0.1477: 0.1477272727272727 [OK] -----

True

SOA Question 2.8 : (C) 0.94


In a population initially consisting of 75% females and 25% males, you are given:
1. For a female, the force of mortality is constant and equals 𝜇
2. For a male, the force of mortality is constant and equals 1.5 𝜇
3. At the end of 20 years, the population is expected to consist of 85% females and 15% males
Calculate the probability that a female survives one year.
hints:
• relate 𝑝𝑚𝑎𝑙𝑒 and 𝑝𝑓𝑒𝑚𝑎𝑙𝑒 through the common term 𝜇 and the given proportions

def fun(mu): # Solve first for mu, given ratio of start and end proportions
male = Survival().set_survival(mu=lambda x,s: 1.5 * mu)
female = Survival().set_survival(mu=lambda x,s: mu)
return (75 * female.p_x(0, t=20)) / (25 * male.p_x(0, t=20))
mu = [Link](fun, target=85/15, grid=0.5)
p = Survival().set_survival(mu=lambda x,s: mu).p_x(0, t=1)
isclose(0.94, p, question="Q2.8")

----- Q2.8 0.94: 0.9383813306903799 [OK] -----

True

22.2. 2 Survival models 161


Solving Actuarial Math with Python

22.3 3 Life tables and selection

SOA Question 3.1 : (B) 117


You are given:
1. An excerpt from a select and ultimate life table with a select period of 3 years:

𝑥 ℓ[𝑥] ℓ[𝑥]+1 ℓ[𝑥]+2 ℓ𝑥+3 𝑥+3


60 80,000 79,000 77,000 74,000 63
61 78,000 76,000 73,000 70,000 64
62 75,000 72,000 69,000 67,000 65
63 71,000 68,000 66,000 65,000 66

2. Deaths follow a constant force of mortality over each year of age


Calculate 1000 23 𝑞[60]+0.75 .
hints:
• interpolate with constant force of maturity

life = SelectLife().set_table(l={60: [80000, 79000, 77000, 74000],


61: [78000, 76000, 73000, 70000],
62: [75000, 72000, 69000, 67000],
63: [71000, 68000, 66000, 65000]})
q = 1000 * life.q_r(60, s=0, r=0.75, t=3, u=2)
isclose(117, q, question="Q3.1")

----- Q3.1 117: 116.7192429022082 [OK] -----

True

SOA Question 3.2 : (D) 14.7


You are given:
1. The following extract from a mortality table with a one-year select period:

𝑥 𝑙[𝑥] 𝑑[𝑥] 𝑙𝑥+1 𝑥+1


65 1000 40 − 66
66 955 45 − 67

2. Deaths are uniformly distributed over each year of age



𝑒[65] = 15.0

Calculate 𝑒[66] .
hints:

• UDD ⇒ 𝑒𝑥 = 𝑒𝑥 + 0.5
• fill select table using curtate expectations

162 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

e_curtate = Fractional.e_approximate(e_complete=15)
life = SelectLife(udd=True).set_table(l={65: [1000, None,],
66: [955, None]},
e={65: [e_curtate, None]},
d={65: [40, None,],
66: [45, None]})
e = life.e_r(x=66)
isclose(14.7, e, question="Q3.2")

----- Q3.2 14.7: 14.67801047120419 [OK] -----

True

SOA Question 3.3 : (E) 1074


You are given:
1. An excerpt from a select and ultimate life table with a select period of 2 years:

𝑥 ℓ[𝑥] ℓ[𝑥]+1 ℓ𝑥+2 𝑥+2


50 99,000 96,000 93,000 52
51 97,000 93,000 89,000 53
52 93,000 88,000 83,000 54
53 90,000 84,000 78,000 55

2. Deaths are uniformly distributed over each year of age


Calculate 10, 000 2.2 𝑞[51]+0.5 .
hints:
• interpolate lives between integer ages with UDD

life = SelectLife().set_table(l={50: [99, 96, 93],


51: [97, 93, 89],
52: [93, 88, 83],
53: [90, 84, 78]})
q = 10000 * life.q_r(51, s=0, r=0.5, t=2.2)
isclose(1074, q, question="Q3.3")

----- Q3.3 1074: 1073.684210526316 [OK] -----

True

SOA Question 3.4 : (B) 815


The SULT Club has 4000 members all age 25 with independent future lifetimes. The mortality for each member follows
the Standard Ultimate Life Table.
Calculate the largest integer N, using the normal approximation, such that the probability that there are at least N survivors
at age 95 is at least 90%.
hints:
• compute portfolio percentile with N=4000, and mean and variance from binomial distribution

22.3. 3 Life tables and selection 163


Solving Actuarial Math with Python

sult = SULT()
mean = sult.p_x(25, t=95-25)
var = [Link](mean, variance=True)
pct = sult.portfolio_percentile(N=4000, mean=mean, variance=var, prob=0.1)
isclose(815, pct, question="Q3.4")

----- Q3.4 815: 815.0943255167722 [OK] -----

True

SOA Question 3.5 : (E) 106


You are given:

𝑥 60 61 62 63 64 65 66 67
𝑙𝑥 99,999 88,888 77,777 66,666 55,555 44,444 33,333 22,222

𝑎= 3.4|2.5 𝑞60 assuming a uniform distribution of deaths over each year of age
𝑏= 3.4|2.5 𝑞60 assuming a constant force of mortality over each year of age
Calculate 100, 000(𝑎 − 𝑏)
hints:
• compute mortality rates by interpolating lives between integer ages, with UDD and constant force of mortality
assumptions

l = [99999, 88888, 77777, 66666, 55555, 44444, 33333, 22222]


a = LifeTable(udd=True).set_table(l={age:l for age,l in zip(range(60, 68), l)})\
.q_r(60, u=3.4, t=2.5)
b = LifeTable(udd=False).set_table(l={age:l for age,l in zip(range(60, 68), l)})\
.q_r(60, u=3.4, t=2.5)
isclose(106, 100000 * (a - b), question="Q3.5")

----- Q3.5 106: 106.16575827938624 [OK] -----

True

SOA Question 3.6 : (D) 15.85


You are given the following extract from a table with a 3-year select period:

𝑥 𝑞[𝑥] 𝑞[𝑥]+1 𝑞[𝑥]+2 𝑞𝑥+3 𝑥+3


60 0.09 0.11 0.13 0.15 63
61 0.10 0.12 0.14 0.16 64
62 0.11 0.13 0.15 0.17 65
63 0.12 0.14 0.16 0.18 66
64 0.13 0.15 0.17 0.19 67

𝑒64 = 5.10

164 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

Calculate 𝑒[61] .
hints:
• apply recursion formulas for curtate expectation

e = SelectLife().set_table(q={60: [.09, .11, .13, .15],


61: [.1, .12, .14, .16],
62: [.11, .13, .15, .17],
63: [.12, .14, .16, .18],
64: [.13, .15, .17, .19]},
e={61: [None, None, None, 5.1]})\
.e_x(61)
isclose(5.85, e, question="Q3.6")

----- Q3.6 5.85: 5.846832 [OK] -----

True

SOA Question 3.7 : (b) 16.4


For a mortality table with a select period of two years, you are given:

𝑥 𝑞[𝑥] 𝑞[𝑥]+1 𝑞𝑥+2 𝑥+2


50 0.0050 0.0063 0.0080 52
51 0.0060 0.0073 0.0090 53
52 0.0070 0.0083 0.0100 54
53 0.0080 0.0093 0.0110 55

The force of mortality is constant between integral ages.


Calculate 1000 2.5 𝑞[50]+0.4 .
hints:
• use deferred mortality formula
• use chain rule for survival probabilities,
• interpolate between integer ages with constant force of mortality

life = SelectLife().set_table(q={50: [.0050, .0063, .0080],


51: [.0060, .0073, .0090],
52: [.0070, .0083, .0100],
53: [.0080, .0093, .0110]})
q = 1000 * life.q_r(50, s=0, r=0.4, t=2.5)
isclose(16.4, q, question="Q3.7")

----- Q3.7 16.4: 16.420207214428586 [OK] -----

True

SOA Question 3.8 : (B) 1505


A club is established with 2000 members, 1000 of exact age 35 and 1000 of exact age 45. You are given:

22.3. 3 Life tables and selection 165


Solving Actuarial Math with Python

1. Mortality follows the Standard Ultimate Life Table


2. Future lifetimes are independent
3. N is the random variable for the number of members still alive 40 years after the club is established
Using the normal approximation, without the continuity correction, calculate the smallest 𝑛 such that 𝑃 𝑟(𝑁 ≥ 𝑛) ≤ 0.05.
hints:
• compute portfolio means and variances from sum of 2000 independent members’ means and variances of survival.

sult = SULT()
p1 = sult.p_x(35, t=40)
p2 = sult.p_x(45, t=40)
mean = [Link](p1) * 1000 + [Link](p2) * 1000
var = ([Link](p1, variance=True) * 1000
+ [Link](p2, variance=True) * 1000)
pct = sult.portfolio_percentile(mean=mean, variance=var, prob=.95)
isclose(1505, pct, question="Q3.8")

----- Q3.8 1505: 1504.8328375406456 [OK] -----

True

SOA Question 3.9 : (E) 3850


A father-son club has 4000 members, 2000 of which are age 20 and the other 2000 are age 45. In 25 years, the members
of the club intend to hold a reunion.
You are given:
1. All lives have independent future lifetimes.
2. Mortality follows the Standard Ultimate Life Table.
Using the normal approximation, without the continuity correction, calculate the 99th percentile of the number of sur-
viving members at the time of the reunion.
hints:
• compute portfolio means and variances as sum of 4000 independent members’ means and variances (of survival)
• retrieve normal percentile

sult = SULT()
p1 = sult.p_x(20, t=25)
p2 = sult.p_x(45, t=25)
mean = [Link](p1) * 2000 + [Link](p2) * 2000
var = ([Link](p1, variance=True) * 2000
+ [Link](p2, variance=True) * 2000)
pct = sult.portfolio_percentile(mean=mean, variance=var, prob=.99)
isclose(3850, pct, question="Q3.9")

----- Q3.9 3850: 3850.144345130047 [OK] -----

True

166 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

SOA Question 3.10 : (C) 0.86


A group of 100 people start a Scissor Usage Support Group. The rate at which members enter and leave the group is
dependent on whether they are right-handed or left-handed.
You are given the following:
1. The initial membership is made up of 75% left-handed members (L) and 25% right-handed members (R)
2. After the group initially forms, 35 new (L) and 15 new (R) join the group at the start of each subsequent year
3. Members leave the group only at the end of each year
4. 𝑞𝐿 = 0.25 for all years
5. 𝑞𝑅 = 0.50 for all years
Calculate the proportion of the Scissor Usage Support Group’s expected membership that is left-handed at the start of
the group’s 6th year, before any new members join for that year.
hints:
• reformulate the problem by reversing time: survival to year 6 is calculated in reverse as discounting by the same
number of years.

interest = Interest(v=0.75)
L = 35*[Link](t=4, due=False) + 75*interest.v_t(t=5)
interest = Interest(v=0.5)
R = 15*[Link](t=4, due=False) + 25*interest.v_t(t=5)
isclose(0.86, L / (L + R), question="Q3.10")

----- Q3.10 0.86: 0.8578442833761983 [OK] -----

True

SOA Question 3.11 : (B) 0.03


For the country of Bienna, you are given:
1. Bienna publishes mortality rates in biennial form, that is, mortality rates are of the form: 2 𝑞2𝑥 , for 𝑥 = 0, 1, 2, ...
2. Deaths are assumed to be uniformly distributed between ages 2𝑥 and 2𝑥 + 2, for 𝑥 = 0, 1, 2, ...
3. 2 𝑞50 = 0.02
4. 2 𝑞52 = 0.04
Calculate the probability that (50) dies during the next 2.5 years.
hints:
• calculate mortality rate by interpolating lives assuming UDD

life = LifeTable(udd=True).set_table(q={50//2: .02, 52//2: .04})


q = life.q_r(50//2, t=2.5/2)
isclose(0.03, q, question="Q3.11")

----- Q3.11 0.03: 0.0298 [OK] -----

True

22.3. 3 Life tables and selection 167


Solving Actuarial Math with Python

SOA Question 3.12 : (C) 0.055


X and Y are both age 61. X has just purchased a whole life insurance policy. Y purchased a whole life insurance policy
one year ago.
Both X and Y are subject to the following 3-year select and ultimate table:

𝑥 ℓ[𝑥] ℓ[𝑥]+1 ℓ[𝑥]+2 ℓ𝑥+3 𝑥+3


60 10,000 9,600 8,640 7,771 63
61 8,654 8,135 6,996 5,737 64
62 7,119 6,549 5,501 4,016 65
63 5,760 4,954 3,765 2,410 66

The force of mortality is constant over each year of age.


Calculate the difference in the probability of survival to age 64.5 between X and Y.
hints:
• compute survival probability by interpolating lives assuming constant force

life = SelectLife(udd=False).set_table(l={60: [10000, 9600, 8640, 7771],


61: [8654, 8135, 6996, 5737],
62: [7119, 6549, 5501, 4016],
63: [5760, 4954, 3765, 2410]})
q = life.q_r(60, s=1, t=3.5) - life.q_r(61, s=0, t=3.5)
isclose(0.055, q, question="Q3.12")

----- Q3.12 0.055: 0.05465655938591829 [OK] -----

True

SOA Question 3.13 : (B) 1.6


A life is subject to the following 3-year select and ultimate table:

[𝑥] ℓ[𝑥] ℓ[𝑥]+1 ℓ[𝑥]+2 ℓ𝑥+3 𝑥+3


55 10,000 9,493 8,533 7,664 58
56 8,547 8,028 6,889 5,630 59
57 7,011 6,443 5,395 3,904 60
58 5,853 4,846 3,548 2,210 61

You are also given:


1. 𝑒60 = 1
2. Deaths are uniformly distributed over each year of age

Calculate 𝑒[58]+2 .
hints:
• compute curtate expectations using recursion formulas
• convert to complete expectation assuming UDD

168 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

life = SelectLife().set_table(l={55: [10000, 9493, 8533, 7664],


56: [8547, 8028, 6889, 5630],
57: [7011, 6443, 5395, 3904],
58: [5853, 4846, 3548, 2210]},
e={57: [None, None, None, 1]})
e = life.e_r(x=58, s=2)
isclose(1.6, e, question="Q3.13")

----- Q3.13 1.6: 1.6003382187147688 [OK] -----

True

SOA Question 3.14 : (C) 0.345


You are given the following information from a life table:

x 𝑙𝑥 𝑑𝑥 𝑝𝑥 𝑞𝑥
95 − − − 0.40
96 − − 0.20 −
97 − 72 − 1.00

You are also given:


1. 𝑙90 = 1000 and 𝑙93 = 825
2. Deaths are uniformly distributed over each year of age.
Calculate the probability that (90) dies between ages 93 and 95.5.
hints:
• compute mortality by interpolating lives between integer ages assuming UDD

life = LifeTable(udd=True).set_table(l={90: 1000, 93: 825},


d={97: 72},
p={96: .2},
q={95: .4, 97: 1})
q = life.q_r(90, u=93-90, t=95.5 - 93)
isclose(0.345, q, question="Q3.14")

----- Q3.14 0.345: 0.345 [OK] -----

True

22.3. 3 Life tables and selection 169


Solving Actuarial Math with Python

22.4 4 Insurance benefits

SOA Question 4.1 : (A) 0.27212


For a special whole life insurance policy issued on (40), you are given:
1. Death benefits are payable at the end of the year of death
2. The amount of benefit is 2 if death occurs within the first 20 years and is 1 thereafter
3. Z is the present value random variable for the payments under this insurance
4. i = 0.03
5.

x 𝐴𝑥 20 𝐸𝑥

40 0.36987 0.51276
60 0.62567 0.17878

6. 𝐸[𝑍 2 ] = 0.24954
Calculate the standard deviation of Z.
hints:
• solve EPV as sum of term and deferred insurance
• compute variance as difference of second moment and first moment squared

life = Recursion().set_interest(i=0.03)
life.set_A(0.36987, x=40).set_A(0.62567, x=60)
life.set_E(0.51276, x=40, t=20).set_E(0.17878, x=60, t=20)
Z2 = 0.24954
A = (2 * life.term_insurance(40, t=20) + life.deferred_insurance(40, u=20))
std = [Link](life.insurance_variance(A2=Z2, A1=A))
isclose(0.27212, std, question="Q4.1")

----- Q4.1 0.27212: 0.2721117749374753 [OK] -----

True

SOA Question 4.2 : (D) 0.18


or a special 2-year term insurance policy on (x), you are given:
1. Death benefits are payable at the end of the half-year of death
2. The amount of the death benefit is 300,000 for the first half-year and increases by 30,000 per half-year thereafter
3. 𝑞𝑥 = 0.16 and 𝑞𝑥+1 = 0.23
4. 𝑖(2) = 0.18
5. Deaths are assumed to follow a constant force of mortality between integral ages
6. Z is the present value random variable for this insurance
Calculate Pr( Z > 277,000) .
hints:

170 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

• calculate Z(t) and deferred mortality for each half-yearly t


• sum the deferred mortality probabilities for periods when PV > 277000

life = LifeTable(udd=False).set_table(q={0: .16, 1: .23})\


.set_interest(i_m=.18, m=2)
mthly = Mthly(m=2, life=life)
Z = mthly.Z_m(0, t=2, benefit=lambda x,t: 300000 + t*30000*2)
p = Z[Z['Z'] >= 277000]['q'].sum()
isclose(0.18, p, question="Q4.2")

----- Q4.2 0.18: 0.17941813045022975 [OK] -----

True

SOA Question 4.3 : (D) 0.878


You are given:
1. 𝑞60 = 0.01
2. Using 𝑖 = 0.05, 𝐴60∶3| = 0.86545
3. Using 𝑖 = 0.045 calculate 𝐴60∶3|
hints:
• solve 𝑞61 from endowment insurance EPV formula
• solve 𝐴60∶3| with new 𝑖 = 0.045 as EPV of endowment insurance benefits.

life = Recursion(verbose=True).set_interest(i=0.05)\
.set_q(0.01, x=60)\
.set_A(0.86545, x=60, t=3, endowment=1)
q = life.q_x(x=61)
A = Recursion(verbose=True).set_interest(i=0.045)\
.set_q(0.01, x=60)\
.set_q(q, x=61)\
.endowment_insurance(60, t=3)
isclose(0.878, A, question="Q4.3")

Mortality 𝑞𝑥+61 ∶=
𝑞𝑥+61 = 1 − 𝑝𝑥+61 complement survival
𝑝𝑥+61 = [𝑣 − 𝐴𝑥+61∶2| ]/[𝑣 ∗ [1 − 𝐴𝑥+62∶1| ]] insurance recursion
𝐴1𝑥+61∶2| = [ 𝐴1𝑥+60∶3| /𝑣 − 𝑞𝑥+60 ∗ 𝑏]/ 𝑝𝑥+60 forward recursion
𝑝𝑥+60 = 1 − 𝑞𝑥+60 complement of mortality

Endowment Insurance 𝐴𝑥+60∶3| ∶=


𝐴1𝑥+60∶3| = 𝑣 ∗ [ 𝑞𝑥+60 ∗ 𝑏 + 𝑝𝑥+60 ∗ 𝐴1𝑥+61∶2| ] backward recursion
𝐴1𝑥+61∶2| = 𝑣 ∗ [ 𝑞𝑥+61 ∗ 𝑏 + 𝑝𝑥+61 ∗ 𝐴1𝑥+62∶1| ] backward recursion
𝑝𝑥+61 = 1 − 𝑞𝑥+61 complement of mortality
𝐸𝑥+60 = 𝑝𝑥+60 ∗ 𝑣 pure endowment
𝑝𝑥+60 = 1 − 𝑞𝑥+60 complement of mortality

22.4. 4 Insurance benefits 171


Solving Actuarial Math with Python

----- Q4.3 0.878: 0.8777667236003878 [OK] -----

True

SOA Question 4.4 : (A) 0.036


For a special increasing whole life insurance on (40), payable at the moment of death, you are given :
1. The death benefit at time t is 𝑏𝑡 = 1 + 0.2𝑡, 𝑡≥0
2. The interest discount factor at time t is 𝑣(𝑡) = (1 + 0.2𝑡) − 2, 𝑡≥0
3. 𝑡 𝑝40 𝜇40+𝑡 = 0.025 if 0 ≤ 𝑡 < 40, otherwise 0
4. Z is the present value random variable for this insurance
Calculate Var(Z).
hints:
• integrate to find EPV of 𝑍 and 𝑍 2
• variance is difference of second moment and first moment squared

x = 40
life = Insurance().set_survival(f=lambda *x: 0.025, maxage=x+40)\
.set_interest(v_t=lambda t: (1 + .2*t)**(-2))
def benefit(x,t): return 1 + .2 * t
A1 = life.A_x(x, benefit=benefit, discrete=False)
A2 = life.A_x(x, moment=2, benefit=benefit, discrete=False)
var = A2 - A1**2
isclose(0.036, var, question="Q4.4")

----- Q4.4 0.036: 0.03567680106032681 [OK] -----

True

SOA Question 4.5 : (C) 35200


For a 30-year term life insurance of 100,000 on (45), you are given:
1. The death benefit is payable at the moment of death
2. Mortality follows the Standard Ultimate Life Table
3. 𝛿 = 0.05
4. Deaths are uniformly distributed over each year of age
Calculate the 95th percentile of the present value of benefits random variable for this insurance
hints:
• interpolate between integer ages with UDD, and find lifetime that mortality rate exceeded
• compute PV of death benefit paid at that time.

sult = SULT(udd=True).set_interest(delta=0.05)
Z = 100000 * sult.Z_from_prob(45, prob=0.95, discrete=False)
isclose(35200, Z, question="Q4.5")

172 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

----- Q4.5 35200: 35187.95203719653 [OK] -----

True

SOA Question 4.6 : (B) 29.85


For a 3-year term insurance of 1000 on (70), you are given:
𝑆𝑈𝐿𝑇
1. 𝑞70+𝑘 is the mortality rate from the Standard Ultimate Life Table, for k = 0,1,2
2. 𝑞70+𝑘 is the mortality rate used to price this insurance, for k = 0,1, 2
3. 𝑞70+𝑘 = (0.95)𝑘 𝑞70+𝑘
𝑆𝑈𝐿𝑇
, for k = 0,1, 2
4. i = 0.05
Calculate the single net premium.
hints:
• calculate adjusted mortality rates
• compute term insurance as EPV of benefits

sult = SULT()
life = LifeTable().set_interest(i=0.05)\
.set_table(q={70+k: .95**k * sult.q_x(70+k) for k in range(3)})
A = life.term_insurance(70, t=3, b=1000)
isclose(29.85, A, question="Q4.6")

----- Q4.6 29.85: 29.84835110355902 [OK] -----

True

SOA Question 4.7 : (B) 0.06


For a 25-year pure endowment of 1 on (x), you are given:
1. Z is the present value random variable at issue of the benefit payment
2. Var (Z) = 0.10 E[Z]
3. 25 𝑝𝑥 = 0.57
Calculate the annual effective interest rate.
hints:
• use Bernoulli shortcut formula for variance of pure endowment Z
• solve for 𝑖, since 𝑝 is given.

def fun(i):
life = Recursion(verbose=False).set_interest(i=i)\
.set_p(0.57, x=0, t=25)
return 0.1*life.E_x(0, t=25) - life.E_x(0, t=25, moment=[Link])
i = [Link](fun, target=0, grid=[0.058, 0.066])
isclose(0.06, i, question="Q4.7")

22.4. 4 Insurance benefits 173


Solving Actuarial Math with Python

----- Q4.7 0.06: 0.06008023738770262 [OK] -----

True

SOA Question 4.8 : (C) 191


For a whole life insurance of 1000 on (50), you are given :
1. The death benefit is payable at the end of the year of death
2. Mortality follows the Standard Ultimate Life Table
3. i = 0.04 in the first year, and i = 0.05 in subsequent years
Calculate the actuarial present value of this insurance.
hints:
• use insurance recursion with special interest rate 𝑖 = 0.04 in first year.

def v_t(t): return 1.04**(-t) if t < 1 else 1.04**(-1) * 1.05**(-t+1)


A = SULT().set_interest(v_t=v_t).whole_life_insurance(50, b=1000)
isclose(191, A, question="Q4.8")

----- Q4.8 191: 191.1281281882354 [OK] -----

True

SOA Question 4.9 : (D) 0.5


You are given:
1. 𝐴35∶15| = 0.39

2. 𝐴135∶15| = 0.25

3. 𝐴35 = 0.32
Calculate 𝐴50 .
hints:
• solve 15 𝐸35 from endowment insurance minus term insurance
• solve implicitly from whole life as term plus deferred insurance

E = Recursion().set_A(0.39, x=35, t=15, endowment=1)\


.set_A(0.25, x=35, t=15)\
.E_x(35, t=15)
life = Recursion(verbose=False).set_A(0.32, x=35)\
.set_E(E, x=35, t=15)
def fun(A): return life.set_A(A, x=50).term_insurance(35, t=15)
A = [Link](fun, target=0.25, grid=[0.35, 0.55])
isclose(0.5, A, question="Q4.9")

Pure Endowment 15 𝐸𝑥+35 ∶=


1
15 𝐸𝑥+35 = 𝐴𝑥+35∶15| − 𝐴𝑥+35∶15| endowment insurance minus term

174 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

----- Q4.9 0.5: 0.5 [OK] -----

True

SOA Question 4.10 : (D)


The present value random variable for an insurance policy on (x) is expressed as: $$
Determine which of the following is a correct expression for 𝐸[𝑍].
(A) 10| 𝐴𝑥 +20| 𝐴𝑥 −30| 𝐴𝑥

(B) 𝐴𝑥 +20 𝐸𝑥 𝐴𝑥+20 − 2 30 𝐸𝑥 𝐴𝑥+30


(C) 10 𝐸𝑥 𝐴𝑥 +20 𝐸𝑥 𝐴𝑥+20 − 2 30 𝐸𝑥 𝐴𝑥+30
(D) 10 𝐸𝑥 𝐴𝑥+10 +20 𝐸𝑥 𝐴𝑥+20 − 2 30 𝐸𝑥 𝐴𝑥+30
(E) 10 𝐸𝑥 [𝐴𝑥 +10 𝐸𝑥+10 + 𝐴𝑥+20 −10 𝐸𝑥+20 + 𝐴𝑥+30 ]
hints:
• draw and compare benefit diagrams

life = Insurance().set_interest(i=0.0).set_survival(S=lambda x,s,t: 1, maxage=40)


def fun(x, t):
if 10 <= t <= 20: return [Link].v_t(t)
elif 20 < t <= 30: return 2 * [Link].v_t(t)
else: return 0
def A(x, t): # Z_x+k (t-k)
return [Link].v_t(t - x) * (t > x)
x = 0
benefits=[lambda x,t: (life.E_x(x, t=10) * A(x+10, t)
+ life.E_x(x, t=20)* A(x+20, t)
- life.E_x(x, t=30) * A(x+30, t)),
lambda x,t: (A(x, t)
+ life.E_x(x, t=20) * A(x+20, t)
- 2 * life.E_x(x, t=30) * A(x+30, t)),
lambda x,t: (life.E_x(x, t=10) * A(x, t)
+ life.E_x(x, t=20) * A(x+20, t)
- 2 * life.E_x(x, t=30) * A(x+30, t)),
lambda x,t: (life.E_x(x, t=10) * A(x+10, t)
+ life.E_x(x, t=20) * A(x+20, t)
- 2 * life.E_x(x, t=30) * A(x+30, t)),
lambda x,t: (life.E_x(x, t=10)
* (A(x+10, t)
+ life.E_x(x+10, t=10) * A(x+20, t)
- life.E_x(x+20, t=10) * A(x+30, t)))]
fig, ax = [Link](3, 2)
ax = [Link]()
for i, b in enumerate([fun] + benefits):
life.Z_plot(0, benefit=b, ax=ax[i], color=f"C{i+1}", title='')
ax[i].legend(["(" + "abcde"[i-1] + ")" if i else "Z"])
z = [sum(abs(b(0, t) - fun(0, t)) for t in range(40)) for b in benefits]
ans = "ABCDE"[[Link](z)]
isclose('D', ans, question="Q4.10")

----- Q4.10 D: D [OK] -----

22.4. 4 Insurance benefits 175


Solving Actuarial Math with Python

True

SOA Question 4.11 : (A) 143385


You are given:
1. 𝑍1 is the present value random variable for an n-year term insurance of 1000 issued to (x)
2. 𝑍2 is the present value random variable for an n-year endowment insurance of 1000 issued to (x)
3. For both 𝑍1 and 𝑍2 the death benefit is payable at the end of the year of death
4. 𝐸[𝑍1 ] = 528
5. 𝑉 𝑎𝑟(𝑍2 ) = 15, 000
6. 𝐴𝑥∶𝑛|1 = 0.209

7. 2 𝐴𝑥∶𝑛|1 = 0.136

Calculate 𝑉 𝑎𝑟(𝑍1 ).
hints:
• compute endowment insurance = term insurance + pure endowment
• apply formula of variance as the difference of second moment and first moment squared.

A1 = 528/1000 # E[Z1] term insurance


C1 = 0.209 # E[pure_endowment]
C2 = 0.136 # E[pure_endowment^2]
(continues on next page)

176 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

(continued from previous page)


B1 = A1 + C1 # endowment = term + pure_endowment
def fun(A2):
B2 = A2 + C2 # double force of interest
return Insurance.insurance_variance(A2=B2, A1=B1)
A2 = [Link](fun, target=15000/(1000*1000), grid=[143400, 279300])
var = Insurance.insurance_variance(A2=A2, A1=A1, b=1000)
isclose(143385, var, question="Q4.11")

----- Q4.11 143385: 143384.99999999997 [OK] -----

True

SOA Question 4.12 : (C) 167


For three fully discrete insurance products on the same (x), you are given:
• 𝑍1 is the present value random variable for a 20-year term insurance of 50
• 𝑍2 is the present value random variable for a 20-year deferred whole life insurance of 100
• 𝑍3 is the present value random variable for a whole life insurance of 100.
• 𝐸[𝑍1 ] = 1.65 and 𝐸[𝑍2 ] = 10.75
• 𝑉 𝑎𝑟(𝑍1 ) = 46.75 and 𝑉 𝑎𝑟(𝑍2 ) = 50.78
Calculate 𝑉 𝑎𝑟(𝑍3 ).
hints:
• since 𝑍1 , 𝑍2 are non-overlapping, 𝐸[𝑍1 𝑍2 ] = 0 for computing 𝐶𝑜𝑣(𝑍1 , 𝑍2 )
• whole life is sum of term and deferred, hence equals variance of components plus twice their covariance

cov = [Link](a=1.65, b=10.75, ab=0) # E[Z1 Z2] = 0 nonoverlapping


var = [Link](a=2, b=1, var_a=46.75, var_b=50.78, cov_ab=cov)
isclose(167, var, question="Q4.12")

----- Q4.12 167: 166.82999999999998 [OK] -----

True

SOA Question 4.13 : (C) 350


For a 2-year deferred, 2-year term insurance of 2000 on [65], you are given:
1. The following select and ultimate mortality table with a 3-year select period:

𝑥 𝑞[𝑥] 𝑞[𝑥]+1 𝑞[𝑥]+2 𝑞𝑥+3 𝑥+3


65 0.08 0.10 0.12 0.14 68
66 0.09 0.11 0.13 0.15 69
67 0.10 0.12 0.14 0.16 70
68 0.11 0.13 0.15 0.17 71
69 0.12 0.14 0.16 0.18 72

2. 𝑖 = 0.04

22.4. 4 Insurance benefits 177


Solving Actuarial Math with Python

3. The death benefit is payable at the end of the year of death


Calculate the actuarial present value of this insurance.
hints:
• compute term insurance as EPV of benefits

life = SelectLife().set_table(q={65: [.08, .10, .12, .14],


66: [.09, .11, .13, .15],
67: [.10, .12, .14, .16],
68: [.11, .13, .15, .17],
69: [.12, .14, .16, .18]})\
.set_interest(i=.04)
A = life.deferred_insurance(65, t=2, u=2, b=2000)
isclose(350, A, question="Q4.13")

----- Q4.13 350: 351.0578236056159 [OK] -----

True

SOA Question 4.14 : (E) 390000


A fund is established for the benefit of 400 workers all age 60 with independent future lifetimes. When they reach age
85, the fund will be dissolved and distributed to the survivors.
The fund will earn interest at a rate of 5% per year.
The initial fund balance, 𝐹 , is determined so that the probability that the fund will pay at least 5000 to each survivor is
86%, using the normal approximation.
Mortality follows the Standard Ultimate Life Table.
Calculate 𝐹 .
hints:
• discount (by interest rate 𝑖 = 0.05) the value at the portfolio percentile, of the sum of 400 bernoulli r.v. with
survival probability 25 𝑝60

sult = SULT()
p = sult.p_x(60, t=85-60)
mean = [Link](p)
var = [Link](p, variance=True)
F = sult.portfolio_percentile(mean=mean, variance=var, prob=.86, N=400)
F *= 5000 * [Link].v_t(85-60)
isclose(390000, F, question="Q4.14")

----- Q4.14 390000: 389322.86778416135 [OK] -----

True

SOA Question 4.15 : (E) 0.0833


For a special whole life insurance on (x), you are given :
• Death benefits are payable at the moment of death
• The death benefit at time 𝑡 is 𝑏𝑡 = 𝑒0.02𝑡 , for 𝑡 ≥ 0

178 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

• 𝜇𝑥+𝑡 = 0.04, for 𝑡 ≥ 0


• 𝛿 = 0.06
• Z is the present value at issue random variable for this insurance.
Calculate 𝑉 𝑎𝑟(𝑍).
hints:
• this special benefit function has effect of reducing actuarial discount rate to use in constant force of mortality
shortcut formulas

life = Insurance().set_survival(mu=lambda *x: 0.04).set_interest(delta=0.06)


benefit = lambda x,t: [Link](0.02*t)
A1 = life.A_x(0, benefit=benefit, discrete=False)
A2 = life.A_x(0, moment=2, benefit=benefit, discrete=False)
var = life.insurance_variance(A2=A2, A1=A1)
isclose(0.0833, var, question="Q4.15")

----- Q4.15 0.0833: 0.08333333333333331 [OK] -----

True

SOA Question 4.16 : (D) 0.11


You are given the following extract of ultimate mortality rates from a two-year select and ultimate mortality table:

𝑥 𝑞𝑥
50 0.045
51 0.050
52 0.055
53 0.060

The select mortality rates satisfy the following:


1. 𝑞[𝑥] = 0.7𝑞𝑥
2. 𝑞[𝑥]+1 = 0.8𝑞𝑥+1
You are also given that 𝑖 = 0.04.
Calculate 𝐴1[50]∶3| .

hints:
• compute EPV of future benefits with adjusted mortality rates

q = [.045, .050, .055, .060]


q = {50 + x: [q[x] * 0.7 if x < len(q) else None,
q[x+1] * 0.8 if x + 1 < len(q) else None,
q[x+2] if x + 2 < len(q) else None]
for x in range(4)}
life = SelectLife().set_table(q=q).set_interest(i=.04)
A = life.term_insurance(50, t=3)
isclose(0.1116, A, question="Q4.16")

22.4. 4 Insurance benefits 179


Solving Actuarial Math with Python

----- Q4.16 0.1116: 0.1115661982248521 [OK] -----

True

SOA Question 4.17 : (A) 1126.7


For a special whole life policy on (48), you are given:
1. The policy pays 5000 if the insured’s death is before the median curtate future lifetime at issue and 10,000 if death
is after the median curtate future lifetime at issue
2. Mortality follows the Standard Ultimate Life Table
3. Death benefits are paid at the end of the year of death
4. i = 0.05
Calculate the actuarial present value of benefits for this policy.
hints:
• find future lifetime with 50% survival probability
• compute EPV of special whole life as sum of term and deferred insurance, that have different benefit amounts
before and after median lifetime.

sult = SULT()
median = sult.Z_t(48, prob=0.5, discrete=False)
def benefit(x,t): return 5000 if t < median else 10000
A = sult.A_x(48, benefit=benefit)
isclose(1130, A, question="Q4.17")

----- Q4.17 1130: 1126.774772894844 [OK] -----

True

SOA Question 4.18 : (A) 81873


You are given that T, the time to first failure of an industrial robot, has a density f(t) given by

𝑓(𝑡) = 0.1, 0≤𝑡<2


= 0.4𝑡−2 , 𝑡 ≤ 𝑡 < 10

with 𝑓(𝑡) undetermined on [10, ∞).


Consider a supplemental warranty on this robot that pays 100,000 at the time T of its first failure if 2 ≤ 𝑇 ≤ 10 , with
no benefits payable otherwise. You are also given that 𝛿 = 5%. Calculate the 90th percentile of the present value of the
future benefits under this warranty.
hints:
• find values of limits such that integral of lifetime density function equals required survival probability

def f(x,s,t): return 0.1 if t < 2 else 0.4*t**(-2)


life = Insurance().set_interest(delta=0.05)\
.set_survival(f=f, maxage=10)
def benefit(x,t): return 0 if t < 2 else 100000
prob = 0.9 - life.q_x(0, t=2)
(continues on next page)

180 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

(continued from previous page)


T = life.Z_t(0, prob=prob)
Z = life.Z_from_t(T, discrete=False) * benefit(0, T)
isclose(81873, Z, question="Q4.18")

----- Q4.18 81873: 81873.07530779815 [OK] -----

True

SOA Question 4.19 : (B) 59050


(80) purchases a whole life insurance policy of 100,000. You are given:
1. The policy is priced with a select period of one year
2. The select mortality rate equals 80% of the mortality rate from the Standard Ultimate Life Table
3. Ultimate mortality follows the Standard Ultimate Life Table
4. 𝑖 = 0.05
Calculate the actuarial present value of the death benefits for this insurance
hints:
• calculate adjusted mortality for the one-year select period
• compute whole life insurance using backward recursion formula

life = SULT()
q = ExtraRisk(life=life, extra=0.8, risk="MULTIPLY_RATE")['q']
select = SelectLife(periods=1).set_select(s=0, age_selected=True, q=q)\
.set_select(s=1, age_selected=False, q=life['q'])\
.set_interest(i=.05)\
.fill_table()
A = 100000 * select.whole_life_insurance(80, s=0)
isclose(59050, A, question="Q4.19")

----- Q4.19 59050: 59050.59973285648 [OK] -----

True

22.5 5 Annuities

SOA Question 5.1 : (A) 0.705


You are given:
1. 𝛿𝑡 = 0.06, 𝑡≥0
2. 𝜇𝑥 (𝑡) = 0.01, 𝑡≥0
3. 𝑌 is the present value random variable for a continuous annuity of 1 per year, payable for the lifetime of (x) with
10 years certain

22.5. 5 Annuities 181


Solving Actuarial Math with Python

Calculate 𝑃 𝑟(𝑌 > 𝐸[𝑌 ]).


hints:
• sum annuity certain and deferred life annuity with constant force of mortality shortcut
• apply equation for PV annuity r.v. Y to infer lifetime
• compute survival probability from constant force of mortality function.

life = ConstantForce(mu=0.01).set_interest(delta=0.06)
EY = life.certain_life_annuity(0, u=10, discrete=False)
p = life.p_x(0, t=life.Y_to_t(EY))
isclose(0.705, p, question="Q5.1") # 0.705

----- Q5.1 0.705: 0.7053680433746505 [OK] -----

True

SOA Question 5.2 : (B) 9.64


You are given:
1. 𝐴𝑥 = 0.30
2. 𝐴𝑥+𝑛 = 0.40
3. 𝐴𝑥∶𝑛|1 = 0.35

4. i = 0.05
Calculate 𝑎𝑥∶𝑛| .
hints:
• compute term life as difference of whole life and deferred insurance
• compute twin annuity-due, and adjust to an immediate annuity.

x, n = 0, 10
a = Recursion().set_interest(i=0.05)\
.set_A(0.3, x)\
.set_A(0.4, x+n)\
.set_E(0.35, x, t=n)\
.immediate_annuity(x, t=n)
isclose(9.64, a, question="Q5.2")

Whole Life Annuity 𝑎𝑥̈ ∶=


𝑎𝑥̈ = [1 − 𝐴𝑥 ]/𝑑 insurance twin

Whole Life Annuity 𝑎𝑥+10̈ ∶=


𝑎𝑥+10
̈ = [1 − 𝐴𝑥+10 ]/𝑑 insurance twin

----- Q5.2 9.64: 9.639999999999999 [OK] -----

True

182 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

SOA Question 5.3 : (C) 6.239


You are given:
• Mortality follows the Standard Ultimate Life Table
• Deaths are uniformly distributed over each year of age
• i = 0.05
𝑑
Calculate 𝑑𝑡 (𝐼𝑎)40∶𝑡| at 𝑡 = 10.5.
hints:
• Differential reduces to be the EPV of the benefit payment at the upper time limit.

t = 10.5
E = t * SULT().E_r(40, t=t)
isclose(6.239, E, question="Q5.3")

----- Q5.3 6.239: 6.23871918627528 [OK] -----

True

SOA Question 5.4 : (A) 213.7


(40) wins the SOA lottery and will receive both:

• A deferred life annuity of K per year, payable continuously, starting at age 40 + 𝑒40 and

• An annuity certain of K per year, payable continuously, for 𝑒40 years
You are given:
1. 𝜇 = 0.02
2. 𝛿 = 0.01
3. The actuarial present value of the payments is 10,000
Calculate K.
hints:
• compute certain and life annuity factor as the sum of a certain annuity and a deferred life annuity.
• solve for amount of annual benefit that equals given EPV

life = ConstantForce(mu=0.02).set_interest(delta=0.01)
u = life.e_x(40, curtate=False)
P = 10000 / life.certain_life_annuity(40, u=u, discrete=False)
isclose(213.7, P, question="Q5.4") # 213.7

----- Q5.4 213.7: 213.74552118275955 [OK] -----

True

SOA Question 5.5 : (A) 1699.6


For an annuity-due that pays 100 at the beginning of each year that (45) is alive, you are given:
1. Mortality for standard lives follows the Standard Ultimate Life Table

22.5. 5 Annuities 183


Solving Actuarial Math with Python

2. The force of mortality for standard lives age 45 + t is represented as 𝜇𝑆𝑈𝐿𝑇


45+𝑡

3. The force of mortality for substandard lives age 45 + t, 𝜇𝑆45+𝑡 , is defined as:
𝜇𝑆45+𝑡 = 𝜇𝑆𝑈𝐿𝑇
45+𝑡 + 0.05, 0≤𝑡<1
= 𝜇𝑆𝑈𝐿𝑇
45+𝑡 , 𝑡≥1
4. 𝑖 = 0.05
Calculate the actuarial present value of this annuity for a substandard life age 45.
hints:
• adjust mortality rate for the extra risk
• compute annuity by backward recursion.

life = SULT() # start with SULT life table


q = ExtraRisk(life=life, extra=0.05, risk="ADD_FORCE")['q']
select = SelectLife(periods=1).set_select(s=0, age_selected=True, q=q)\
.set_select(s=1, age_selected=False, a=life['a'])\
.set_interest(i=0.05)\
.fill_table()
a = 100 * select['a'][45][0]
isclose(1700, a, question="Q5.5")

----- Q5.5 1700: 1699.6076593190103 [OK] -----

True

SOA Question 5.6 : (D) 1200


For a group of 100 lives age x with independent future lifetimes, you are given:
1. Each life is to be paid 1 at the beginning of each year, if alive
2. 𝐴𝑥 = 0.45
3. 2 𝐴𝑥 = 0.22
4. 𝑖 = 0.05
5. 𝑌 is the present value random variable of the aggregate payments.
Using the normal approximation to 𝑌 , calculate the initial size of the fund needed to be 95% certain of being able to
make the payments for these life annuities.
hints:
• compute mean and variance of EPV of whole life annuity from whole life insurance twin and variance identities.
• portfolio percentile of the sum of 𝑁 = 100 life annuity payments

life = Annuity().set_interest(i=0.05)
var = life.annuity_variance(A2=0.22, A1=0.45)
mean = life.annuity_twin(A=0.45)
fund = life.portfolio_percentile(mean, var, prob=.95, N=100)
isclose(1200, fund, question="Q5.6")

----- Q5.6 1200: 1200.6946732201702 [OK] -----

184 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

True

SOA Question 5.7 : (C)


You are given:
1. 𝐴35 = 0.188
2. 𝐴65 = 0.498
3. 30 𝑝35 = 0.883
4. 𝑖 = 0.04
(2)
Calculate 1000𝑎35∶30|
̈ using the two-term Woolhouse approximation.

hints:
• compute endowment insurance from relationships of whole life, temporary and deferred insurances.
• compute temporary annuity from insurance twin
• apply Woolhouse approximation

life = Recursion().set_interest(i=0.04)\
.set_A(0.188, x=35)\
.set_A(0.498, x=65)\
.set_p(0.883, x=35, t=30)
mthly = Woolhouse(m=2, life=life, three_term=False)
a = 1000 * mthly.temporary_annuity(35, t=30)
isclose(17376.7, a, question="Q5.7")

Whole Life Annuity 𝑎𝑥+35̈ ∶=


𝑎𝑥+35
̈ = [1 − 𝐴𝑥+35 ]/𝑑 insurance twin

Whole Life Annuity 𝑎𝑥+65̈ ∶=


𝑎𝑥+65
̈ = [1 − 𝐴𝑥+65 ]/𝑑 insurance twin
Pure Endowment 30 𝐸𝑥+35 ∶=
30
30 𝐸𝑥+35 = 30 𝑝𝑥+35 ∗ 𝑣 pure endowment

----- Q5.7 17376.7: 17376.71459632958 [OK] -----

True

SOA Question 5.8 : (C) 0.92118


For an annual whole life annuity-due of 1 with a 5-year certain period on (55), you are given:
1. Mortality follows the Standard Ultimate Life Table
2. i = 0.05
Calculate the probability that the sum of the undiscounted payments actually made under this annuity will exceed the
expected present value, at issue, of the annuity.
hints:
• calculate EPV of certain and life annuity.

22.5. 5 Annuities 185


Solving Actuarial Math with Python

• find survival probability of lifetime s.t. sum of annual payments exceeds EPV

sult = SULT()
a = sult.certain_life_annuity(55, u=5)
p = sult.p_x(55, t=[Link](a))
isclose(0.92118, p, question="Q5.8")

----- Q5.8 0.92118: 0.9211799771029529 [OK] -----

True

SOA Question 5.9 : (C) 0.015


hints:
• express both EPV’s expressed as forward recursions
• solve for unknown constant 𝑘.

x, p = 0, 0.9 # set arbitrary p_x = 0.9


a = Recursion().set_a(21.854, x=x)\
.set_p(p, x=x)\
.whole_life_annuity(x+1)
life = Recursion(verbose=False).set_a(22.167, x=x)
def fun(k): return a - life.set_p((1 + k) * p, x=x).whole_life_annuity(x + 1)
k = [Link](fun, target=0, grid=[0.005, 0.025])
isclose(0.015, k, question="Q5.9")

Whole Life Annuity 𝑎𝑥+1̈ ∶=


𝑎𝑥+1
̈ = [ 𝑎𝑥̈ − 1]/ 𝐸𝑥 forward recursion
𝐸𝑥 = 𝑝𝑥 ∗ 𝑣 pure endowment

----- Q5.9 0.015: 0.015009110961925157 [OK] -----

True

22.6 6 Premium Calculation

SOA Question 6.1 : (D) 35.36


6.1. You are given the following information about a special fully discrete 2-payment, 2-year term insurance on (80):
(i) Mortality follows the Standard Ultimate Life Table
(ii) i = 0.03
(iii) The death benefit is 1000 plus a return of all premiums paid without interest
(iv) Level premiums are calculated using the equivalence principle
Calculate the net premium for this special insurance.
[A modified version of Question 22 on the Fall 2012 exam]
hints:

186 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

• solve net premium such that EPV annuity = EPV insurance + IA factor for returns of premiums without interest

P = SULT().set_interest(i=0.03)\
.net_premium(80, t=2, b=1000, return_premium=True)
isclose(35.36, P, question="Q6.1")

----- Q6.1 35.36: 35.35922286190033 [OK] -----

True

SOA Question 6.2 : (E) 3604


6.2. For a fully discrete 10-year term life insurance policy on (x), you are given:
(i) Death benefits are 100,000 plus the return of all gross premiums paid without interest
(ii) Expenses are 50% of the first year’s gross premium, 5% of renewal gross premiums and 200 per policy expenses each
year
(iii) Expenses are payble at the beginnig of the year
(iv) 𝐴1𝑥∶10| = 0.17094

(v) (𝐼𝐴)1𝑥∶10| = 0.96728

(vi) 𝑎1𝑥∶10|
̈ = 6.8865

Calculate the gross premium using the equivalence principle.


[Question 25 on the Fall 2012 exam]
hints:
• EPV return of premiums without interest = Premium × IA factor
• solve for gross premiums such that EPV premiums = EPV benefits and expenses

life = Premiums()
A, IA, a = 0.17094, 0.96728, 6.8865
P = life.gross_premium(a=a, A=A, IA=IA, benefit=100000,
initial_premium=0.5, renewal_premium=.05,
renewal_policy=200, initial_policy=200)
isclose(3604, P, question="Q6.2")

----- Q6.2 3604: 3604.229940320728 [OK] -----

True

SOA Question 6.3 : (C) 0.390


S, now age 65, purchased a 20-year deferred whole life annuity-due of 1 per year at age 45. You are given:
1. Equal annual premiums, determined using the equivalence principle, were paid at the beginning of each year during
the deferral period
2. Mortality at ages 65 and older follows the Standard Ultimate Life Table
3. i = 0.05
4. Y is the present value random variable at age 65 for S’s annuity benefits

22.6. 6 Premium Calculation 187


Solving Actuarial Math with Python

Calculate the probability that Y is less than the actuarial accumulated value of S’s premiums.
hints:
• solve lifetime 𝑡 such that PV annuity certain = PV whole life annuity at age 65
• calculate mortality rate through the year before curtate lifetime

life = SULT()
t = life.Y_to_t(life.whole_life_annuity(65))
q = 1 - life.p_x(65, t=[Link](t) - 1)
isclose(0.39, q, question="Q6.3")

----- Q6.3 0.39: 0.39039071872030084 [OK] -----

True

SOA Question 6.4 : (E) 1890


For whole life annuities-due of 15 per month on each of 200 lives age 62 with independent future lifetimes, you are given:
1. i = 0.06
(12)
2. 𝐴12 2
62 = 0.4075 and 𝐴62 = 0.2105

3. 𝜋 is the single premium to be paid by each of the 200 lives


4. S is the present value random variable at time 0 of total payments made to the 200 lives
Using the normal approximation, calculate 𝜋 such at 𝑃 𝑟(200𝜋 > 𝑆) = 0.90

mthly = Mthly(m=12, life=Annuity().set_interest(i=0.06))


A1, A2 = 0.4075, 0.2105
mean = mthly.annuity_twin(A1) * 15 * 12
var = mthly.annuity_variance(A1=A1, A2=A2, b=15 * 12)
S = Annuity.portfolio_percentile(mean=mean, variance=var, prob=.9, N=200) / 200
isclose(1890, S, question="Q6.4")

----- Q6.4 1890: 1893.912859650868 [OK] -----

True

SOA Question 6.5 : (D) 33


For a fully discrete whole life insurance of 1000 on (30), you are given:
1. Mortality follows the Standard Ultimate Life Table
2. i = 0.05
3. The premium is the net premium
Calculate the first year for which the expected present value at issue of that year’s premium is less than the expected
present value at issue of that year’s benefit.

life = SULT()
P = life.net_premium(30, b=1000)
def gain(k):
return life.Y_x(30, t=k) * P - life.Z_x(30, t=k) * 1000
(continues on next page)

188 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

(continued from previous page)


k = min([k for k in range(100) if gain(k) < 0]) + 1 # add 1 because k=0 is first␣
↪policy year

isclose(33, k, question="Q6.5")

----- Q6.5 33: 33 [OK] -----

True

SOA Question 6.6 : (B) 0.79


For fully discrete whole life insurance policies of 10,000 issued on 600 lives with independent future lifetimes, each age
62, you are given:
1. Mortality follows the Standard Ultimate Life Table
2. i = 0.05
3. Expenses of 5% of the first year gross premium are incurred at issue
4. Expenses of 5 per policy are incurred at the beginning of each policy year
5. The gross premium is 103% of the net premium.
6. 0 𝐿 is the aggregate present value of future loss at issue random variable
Calculate 𝑃 𝑟(0 𝐿 < 40, 000), using the normal approximation.

life = SULT()
P = life.net_premium(62, b=10000)
contract = Contract(premium=1.03*P,
renewal_policy=5,
initial_policy=5,
initial_premium=0.05,
benefit=10000)
L = life.gross_policy_value(62, contract=contract)
var = life.gross_policy_variance(62, contract=contract)
prob = life.portfolio_cdf(mean=L, variance=var, value=40000, N=600)
isclose(.79, prob, question="Q6.6")

----- Q6.6 0.79: 0.7914321142683509 [OK] -----

True

SOA Question 6.7 : (C) 2880


For a special fully discrete 20-year endowment insurance on (40), you are given:
1. The only death benefit is the return of annual net premiums accumulated with interest at 5% to the end of the year
of death
2. The endowment benefit is 100,000
3. Mortality follows the Standard Ultimate Life Table
4. i = 0.05
Calculate the annual net premium.

22.6. 6 Premium Calculation 189


Solving Actuarial Math with Python

life = SULT()
a = life.temporary_annuity(40, t=20)
A = life.E_x(40, t=20)
IA = a - [Link](t=20) * life.p_x(40, t=20)
G = life.gross_premium(a=a, A=A, IA=IA, benefit=100000)
isclose(2880, G, question="Q6.7")

----- Q6.7 2880: 2880.2463991134578 [OK] -----

True

SOA Question 6.8 : (B) 9.5


For a fully discrete whole life insurance on (60), you are given:
1. Mortality follows the Standard Ultimate Life Table
2. i = 0.05
3. The expected company expenses, payable at the beginning of the year, are:
• 50 in the first year
• 10 in years 2 through 10
• 5 in years 11 through 20
• 0 after year 20
Calculate the level annual amount that is actuarially equivalent to the expected company expenses.
hints:
• calculate EPV of expenses as deferred life annuities
• solve for level premium

life = SULT()
initial_cost = (50 + 10 * life.deferred_annuity(60, u=1, t=9)
+ 5 * life.deferred_annuity(60, u=10, t=10))
P = life.net_premium(60, initial_cost=initial_cost)
isclose(9.5, P, question="Q6.8")

----- Q6.8 9.5: 9.526003201821927 [OK] -----

True

SOA Question 6.9 : (D) 647

life = SULT()
a = life.temporary_annuity(50, t=10)
A = life.term_insurance(50, t=20)
initial_cost = 25 * life.deferred_annuity(50, u=10, t=10)
P = life.gross_premium(a=a, A=A, benefit=100000,
initial_premium=0.42, renewal_premium=0.12,
initial_policy=75 + initial_cost, renewal_policy=25)
isclose(647, P, question="Q6.9")

190 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

----- Q6.9 647: 646.8608151974504 [OK] -----

True

SOA Question 6.10 : (D) 0.91


For a fully discrete 3-year term insurance of 1000 on (x), you are given:
1. 𝑝𝑥 = 0.975
2. i = 0.06
3. The actuarial present value of the death benefit is 152.85
4. The annual net premium is 56.05
Calculate 𝑝𝑥+2 .

x = 0
life = Recursion(depth=5).set_interest(i=0.06)\
.set_p(0.975, x=x)\
.set_a(152.85/56.05, x=x, t=3)\
.set_A(152.85, x=x, t=3, b=1000)
p = life.p_x(x=x+2)
isclose(0.91, p, question="Q6.10")

Survival 𝑝𝑥+2 ∶=
𝑝𝑥+2 = 𝐸𝑥+2 /𝑣 one-year pure endowment
𝐸𝑥+2 = 𝐴𝑥+2∶1| − 𝐴1𝑥+2∶1| endowment insurance minus term
𝐴1𝑥+2∶1| = [ 𝐴1𝑥+1∶2| /𝑣 − 𝑞𝑥+1 ∗ 𝑏]/ 𝑝𝑥+1 forward recursion
𝑝𝑥+1 = [ 𝑎𝑥+1∶2|
̈ − 1]/[𝑣 ∗ 𝑎𝑥+2∶1|
̈ ] annuity recursion
𝑎𝑥+1∶2|
̈ = [ 𝑎𝑥∶3|
̈ − 1]/ 𝐸𝑥 forward recursion
𝐴1𝑥+1∶2| = [ 𝐴1𝑥∶3| /𝑣 − 𝑞𝑥 ∗ 𝑏]/ 𝑝𝑥 forward recursion
𝐸𝑥 = 𝑝𝑥 ∗ 𝑣 pure endowment

----- Q6.10 0.91: 0.9097382950525702 [OK] -----

True

SOA Question 6.11 : (C) 0.041

life = Recursion().set_interest(i=0.04)
A = life.set_A(0.39788, 51)\
.set_q(0.0048, 50)\
.whole_life_insurance(50)
P = life.gross_premium(A=A, a=life.annuity_twin(A=A))
A = life.set_q(0.048, 50).whole_life_insurance(50)
loss = A - life.annuity_twin(A) * P
isclose(0.041, loss, question="Q6.11")

Whole Life Insurance 𝐴𝑥+50 ∶=


𝐴𝑥+50 = 𝑣 ∗ [ 𝑞𝑥+50 ∗ 𝑏 + 𝑝𝑥+50 ∗ 𝐴𝑥+51 ] backward recursion
𝑝𝑥+50 = 1 − 𝑞𝑥+50 complement of mortality

22.6. 6 Premium Calculation 191


Solving Actuarial Math with Python

Whole Life Insurance 𝐴𝑥+50 ∶=


𝐴𝑥+50 = 𝑣 ∗ [ 𝑞𝑥+50 ∗ 𝑏 + 𝑝𝑥+50 ∗ 𝐴𝑥+51 ] backward recursion
𝑝𝑥+50 = 1 − 𝑞𝑥+50 complement of mortality

----- Q6.11 0.041: 0.04069206883563675 [OK] -----

True

SOA Question 6.12 : (E) 88900


For a fully discrete whole life insurance of 1000 on (x), you are given:
1. The following expenses are incurred at the beginning of each year:

Year 1 Years 2+

Percent of premium 75% 10%


Maintenance expenses 10 2

2. An additional expense of 20 is paid when the death benefit is paid


3. The gross premium is determined using the equivalence principle
4. 𝑖 = 0.06
5. 𝑎𝑥̈ = 12.0
6. 2 𝐴𝑥 = 0.14
Calculate the variance of the loss at issue random variable.

life = PolicyValues().set_interest(i=0.06)
a = 12
A = life.insurance_twin(a)
contract = Contract(benefit=1000, settlement_policy=20,
initial_policy=10, initial_premium=0.75,
renewal_policy=2, renewal_premium=0.1)
[Link] = life.gross_premium(A=A, a=a, **contract.premium_terms)
L = life.gross_variance_loss(A1=A, A2=0.14, contract=contract)
isclose(88900, L, question="Q6.12")

----- Q6.12 88900: 88862.59592874818 [OK] -----

True

SOA Question 6.13 : (D) -400


For a fully discrete whole life insurance of 10,000 on (45), you are given:
1. Commissions are 80% of the first year premium and 10% of subsequent premiums. There are no other expenses
2. Mortality follows the Standard Ultimate Life Table
3. i = 0.05
4. 0 𝐿 denotes the loss at issue random variable
5. If 𝑇45 = 10.5, then 0 𝐿 = 4953

192 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

Calculate 𝐸[0 𝐿] .

life = SULT().set_interest(i=0.05)
A = life.whole_life_insurance(45)
contract = Contract(benefit=10000, initial_premium=.8, renewal_premium=.1)
def fun(P): # Solve for premium, given Loss(t=0) = 4953
return life.L_from_t(t=10.5, contract=contract.set_contract(premium=P))
contract.set_contract(premium=[Link](fun, target=4953, grid=100))
L = life.gross_policy_value(45, contract=contract)
life.L_plot(x=45, T=10.5, contract=contract)
isclose(-400, L, question="Q6.13")

----- Q6.13 -400: -400.94447599879277 [OK] -----

True

SOA Question 6.14 : (D) 1150


For a special fully discrete whole life insurance of 100,000 on (40), you are given:
1. The annual net premium is P for years 1 through 10, 0.5P for years 11 through 20, and 0 thereafter
2. Mortality follows the Standard Ultimate Life Table
3. i = 0.05
Calculate P.

22.6. 6 Premium Calculation 193


Solving Actuarial Math with Python

life = SULT().set_interest(i=0.05)
a = life.temporary_annuity(40, t=10) + 0.5*life.deferred_annuity(40, u=10, t=10)
A = life.whole_life_insurance(40)
P = life.gross_premium(a=a, A=A, benefit=100000)
isclose(1150, P, question="Q6.14")

----- Q6.14 1150: 1148.5800555155263 [OK] -----

True

SOA Question 6.15 : (B) 1.002


For a fully discrete whole life insurance of 1000 on (x) with net premiums payable quarterly, you are given:
1. 𝑖 = 0.05
2. 𝑎𝑥̈ = 3.4611
3. 𝑃 (𝑊 ) and 𝑃 (𝑈𝐷𝐷) are the annualized net premiums calculated using the 2-term Woolhouse (W) and the uniform
distribution of deaths (UDD) assumptions, respectively
𝑃 (𝑈𝐷𝐷)
Calculate .
𝑃 (𝑊 )
life = Recursion().set_interest(i=0.05).set_a(3.4611, x=0)
A = life.insurance_twin(3.4611)
udd = UDD(m=4, life=life)
a1 = udd.whole_life_annuity(x=x)
woolhouse = Woolhouse(m=4, life=life)
a2 = woolhouse.whole_life_annuity(x=x)
P = life.gross_premium(a=a1, A=A)/life.gross_premium(a=a2, A=A)
isclose(1.002, P, question="Q6.15")

----- Q6.15 1.002: 1.0022973504113772 [OK] -----

True

SOA Question 6.16 : (A) 2408.6


For a fully discrete 20-year endowment insurance of 100,000 on (30), you are given:
1. d = 0.05
2. Expenses, payable at the beginning of each year, are:

First Year First Year Renewal Years Renewal Years

Percent of Premium Per Policy Percent of Premium Per Policy

Taxes 4% 0 4% 0
Sales Commission 35% 0 2% 0
Policy Maintenance 0% 250 0% 50

3. The net premium is 2143


Calculate the gross premium using the equivalence principle.

194 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

life = Premiums().set_interest(d=0.05)
A = life.insurance_equivalence(premium=2143, b=100000)
a = life.annuity_equivalence(premium=2143, b=100000)
p = life.gross_premium(A=A, a=a, benefit=100000, settlement_policy=0,
initial_policy=250, initial_premium=0.04 + 0.35,
renewal_policy=50, renewal_premium=0.04 + 0.02)
isclose(2410, p, question="Q6.16")

----- Q6.16 2410: 2408.575206281868 [OK] -----

True

SOA Question 6.17 : (A) -30000


An insurance company sells special fully discrete two-year endowment insurance policies to smokers (S) and non-smokers
(NS) age x. You are given:
1. The death benefit is 100,000; the maturity benefit is 30,000
2. The level annual premium for non-smoker policies is determined by the equivalence principle
3. The annual premium for smoker policies is twice the non-smoker annual premium
4. 𝜇𝑁𝑆
𝑥+𝑡 = 0.1. 𝑡 > 0
𝑆 𝑁𝑆
5. 𝑞𝑥+𝑘 = 1.5𝑞𝑥+𝑘 , for 𝑘 = 0, 1
6. 𝑖 = 0.08
Calculate the expected present value of the loss at issue random variable on a smoker policy.

x = 0
life = ConstantForce(mu=0.1).set_interest(i=0.08)
A = life.endowment_insurance(x, t=2, b=100000, endowment=30000)
a = life.temporary_annuity(x, t=2)
P = life.gross_premium(a=a, A=A)
life1 = Recursion().set_interest(i=0.08)\
.set_q(life.q_x(x, t=1) * 1.5, x=x, t=1)\
.set_q(life.q_x(x+1, t=1) * 1.5, x=x+1, t=1)
contract = Contract(premium=P*2, benefit=100000, endowment=30000)
L = life1.gross_policy_value(x, t=0, n=2, contract=contract)
isclose(-30000, L, question="Q6.17")

Term Insurance 𝐴1𝑥∶2| ∶=


𝐴1𝑥∶2| = 𝐴𝑥∶2| − 2 𝐸𝑥 endowment insurance - pure
2
2 𝐸𝑥 = 2 𝑝𝑥 ∗ 𝑣 pure endowment
2 𝑝𝑥 = 𝑝𝑥+1 ∗ 𝑝𝑥 survival chain rule
𝐴1𝑥∶2| = 𝑣 ∗ [ 𝑞𝑥 ∗ 𝑏 + 𝑝𝑥 ∗ 𝐴1𝑥+1∶1| ] backward recursion
𝑝𝑥+1 = 1 − 𝑞𝑥+1 complement of mortality
𝐸𝑥 = 𝑝𝑥 ∗ 𝑣 pure endowment
𝑝𝑥 = 1 − 𝑞𝑥 complement of mortality

Temporary Annuity 𝑎𝑥∶2| ̈ ∶=


𝑎𝑥∶2|
̈ = 1 + 𝐸𝑥 ∗ 𝑎𝑥+1∶1|
̈ backward recursion
𝐸𝑥 = 𝑝𝑥 ∗ 𝑣 pure endowment
𝑝𝑥 = 1 − 𝑞 𝑥 complement of mortality

22.6. 6 Premium Calculation 195


Solving Actuarial Math with Python

Pure Endowment 2 𝐸𝑥 ∶=
2
2 𝐸𝑥 = 2 𝑝𝑥 ∗ 𝑣 pure endowment
2 𝑝𝑥 = 𝑝𝑥+1 ∗ 𝑝𝑥 survival chain rule
𝑝𝑥 = 1 − 𝑞 𝑥 complement of mortality
𝑝𝑥+1 = 1 − 𝑞𝑥+1 complement of mortality

----- Q6.17 -30000: -30107.42633581115 [OK] -----

True

SOA Question 6.18 : (D) 166400


For a 20-year deferred whole life annuity-due with annual payments of 30,000 on (40), you are given:
1. The single net premium is refunded without interest at the end of the year of death if death occurs during the
deferral period
2. Mortality follows the Standard Ultimate Life Table
3. i = 0.05
Calculate the single net premium for this annuity.

life = SULT().set_interest(i=0.05)
def fun(P):
A = (life.term_insurance(40, t=20, b=P)
+ life.deferred_annuity(40, u=20, b=30000))
return life.gross_premium(a=1, A=A) - P
P = [Link](fun, target=0, grid=[162000, 168800])
isclose(166400, P, question="Q6.18")

----- Q6.18 166400: 166362.83871487685 [OK] -----

True

SOA Question 6.19 : (B) 0.033

life = SULT()
contract = Contract(initial_policy=.2, renewal_policy=.01)
a = life.whole_life_annuity(50)
A = life.whole_life_insurance(50)
[Link] = life.gross_premium(A=A, a=a, **contract.premium_terms)
L = life.gross_policy_variance(50, contract=contract)
isclose(0.033, L, question="Q6.19")

----- Q6.19 0.033: 0.03283273381910885 [OK] -----

True

SOA Question 6.20 : (B) 459


For a special fully discrete 3-year term insurance on (75), you are given:
1. The death benefit during the first two years is the sum of the net premiums paid without interest
2. The death benefit in the third year is 10,000

196 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

𝑥 𝑝𝑥
75 0.90
76 0.88
77 0.85

3. 𝑖 = 0.04
Calculate the annual net premium.

life = LifeTable().set_interest(i=.04).set_table(p={75: .9, 76: .88, 77: .85})


a = life.temporary_annuity(75, t=3)
IA = life.increasing_insurance(75, t=2)
A = life.deferred_insurance(75, u=2, t=1)
def fun(P): return life.gross_premium(a=a, A=P*IA + A*10000) - P
P = [Link](fun, target=0, grid=[449, 489])
isclose(459, P, question="Q6.20")

----- Q6.20 459: 458.83181728297353 [OK] -----

True

SOA Question 6.21 : (C) 100

life = Recursion(verbose=False).set_interest(d=0.04)
life.set_A(0.7, x=75, t=15, endowment=1)
life.set_E(0.11, x=75, t=15)
def fun(P):
return (P * life.temporary_annuity(75, t=15) -
life.endowment_insurance(75, t=15, b=1000, endowment=15*float(P)))
P = [Link](fun, target=0, grid=(80, 120))
isclose(100, P, question="Q6.21")

----- Q6.21 100: 100.85470085470084 [OK] -----

True

SOA Question 6.22 : (C) 102


For a whole life insurance of 100,000 on (45) with premiums payable monthly for a period of 20 years, you are given:
1. The death benefit is paid immediately upon death
2. Mortality follows the Standard Ultimate Life Table
3. Deaths are uniformly distributed over each year of age
4. 𝑖 = 0.05
Calculate the monthly net premium.

life=SULT(udd=True)
a = UDD(m=12, life=life).temporary_annuity(45, t=20)
A = UDD(m=0, life=life).whole_life_insurance(45)
P = life.gross_premium(A=A, a=a, benefit=100000) / 12
isclose(102, P, question="Q6.22")

22.6. 6 Premium Calculation 197


Solving Actuarial Math with Python

----- Q6.22 102: 102.40668704849178 [OK] -----

True

SOA Question 6.23 : (D) 44.7

x = 0
life = Recursion().set_a(15.3926, x=x)\
.set_a(10.1329, x=x, t=15)\
.set_a(14.0145, x=x, t=30)
def fun(P):
per_policy = 30 + (30 * life.whole_life_annuity(x))
per_premium = (0.6 + 0.1*life.temporary_annuity(x, t=15)
+ 0.1*life.temporary_annuity(x, t=30))
a = life.temporary_annuity(x, t=30)
return (P * a) - (per_policy + per_premium * P)
P = [Link](fun, target=0, grid=[30.3, 49.5])
isclose(44.7, P, question="Q6.23")

----- Q6.23 44.7: 44.70806635781144 [OK] -----

True

SOA Question 6.24 : (E) 0.30


For a fully continuous whole life insurance of 1 on (x), you are given:
1. L is the present value of the loss at issue random variable if the premium rate is determined by the equivalence
principle
2. L^* is the present value of the loss at issue random variable if the premium rate is 0.06
3. 𝛿 = 0.07
4. 𝐴𝑥 = 0.30
5. 𝑉 𝑎𝑟(𝐿) = 0.18
Calculate 𝑉 𝑎𝑟(𝐿∗ ).

life = PolicyValues().set_interest(delta=0.07)
x, A1 = 0, 0.30 # Policy for first insurance
P = life.premium_equivalence(A=A1, discrete=False) # Need its premium
contract = Contract(premium=P, discrete=False)
def fun(A2): # Solve for A2, given Var(Loss)
return life.gross_variance_loss(A1=A1, A2=A2, contract=contract)
A2 = [Link](fun, target=0.18, grid=0.18)

contract = Contract(premium=0.06, discrete=False) # Solve second insurance


var = life.gross_variance_loss(A1=A1, A2=A2, contract=contract)
isclose(0.304, var, question="Q6.24")

----- Q6.24 0.304: 0.30419999999999975 [OK] -----

True

198 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

SOA Question 6.25 : (C) 12330


For a fully discrete 10-year deferred whole life annuity-due of 1000 per month on (55), you are given:
1. The premium, 𝐺, will be paid annually at the beginning of each year during the deferral period
2. Expenses are expected to be 300 per year for all years, payable at the beginning of the year
3. Mortality follows the Standard Ultimate Life Table
4. 𝑖 = 0.05
5. Using the two-term Woolhouse approximation, the expected loss at issue is -800
Calculate 𝐺.

life = SULT()
woolhouse = Woolhouse(m=12, life=life)
benefits = woolhouse.deferred_annuity(55, u=10, b=1000 * 12)
expenses = life.whole_life_annuity(55, b=300)
payments = life.temporary_annuity(55, t=10)
def fun(P):
return life.gross_future_loss(A=benefits + expenses, a=payments,
contract=Contract(premium=P))
P = [Link](fun, target=-800, grid=[12110, 12550])
isclose(12330, P, question="Q6.25")

----- Q6.25 12330: 12325.781125438532 [OK] -----

True

SOA Question 6.26 : (D) 180


For a special fully discrete whole life insurance policy of 1000 on (90), you are given:
1. The first year premium is 0
2. P is the renewal premium
3. Mortality follows the Standard Ultimate Life Table
4. i = 0.05
5. Premiums are calculated using the equivalence principle
Calculate P.

life = SULT().set_interest(i=0.05)
def fun(P):
return P - life.net_premium(90, b=1000, initial_cost=P)
P = [Link](fun, target=0, grid=[150, 190])
isclose(180, P, question="Q6.26")

----- Q6.26 180: 180.03164891315885 [OK] -----

True

SOA Question 6.27 : (D) 10310


For a special fully continuous whole life insurance on (x), you are given:

22.6. 6 Premium Calculation 199


Solving Actuarial Math with Python

1. Premiums and benefits:

First 20 years After 20 years

Premium Rate 3P P
Benefit 1,000,000 500,000

2. 𝜇𝑥+𝑡 = 0.03, 𝑡≥0


3. 𝛿 = 0.06
Calculate 𝑃 using the equivalence principle.

life = ConstantForce(mu=0.03).set_interest(delta=0.06)
x = 0
payments = (3 * life.temporary_annuity(x, t=20, discrete=False)
+ life.deferred_annuity(x, u=20, discrete=False))
benefits = (1000000 * life.term_insurance(x, t=20, discrete=False)
+ 500000 * life.deferred_insurance(x, u=20, discrete=False))
P = benefits / payments
isclose(10310, P, question="Q6.27")

----- Q6.27 10310: 10309.617799001708 [OK] -----

True

SOA Question 6.28 : (B) 36

life = SULT().set_interest(i=0.05)
a = life.temporary_annuity(40, t=5)
A = life.whole_life_insurance(40)
P = life.gross_premium(a=a, A=A, benefit=1000,
initial_policy=10, renewal_premium=.05,
renewal_policy=5, initial_premium=.2)
isclose(36, P, question="Q6.28")

----- Q6.28 36: 35.72634219391481 [OK] -----

True

SOA Question 6.29 : (B) 20.5


(35) purchases a fully discrete whole life insurance policy of 100,000. You are given:
1. The annual gross premium, calculated using the equivalence principle, is 1770
2. The expenses in policy year 1 are 50% of premium and 200 per policy
3. The expenses in policy years 2 and later are 10% of premium and 50 per policy
4. All expenses are incurred at the beginning of the policy year
5. 𝑖 = 0.035
Calculate 𝑎35
̈ .

200 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

life = Premiums().set_interest(i=0.035)
def fun(a):
return life.gross_premium(A=life.insurance_twin(a=a), a=a,
initial_policy=200, initial_premium=.5,
renewal_policy=50, renewal_premium=.1,
benefit=100000)
a = [Link](fun, target=1770, grid=[20, 22])
isclose(20.5, a, question="Q6.29")

----- Q6.29 20.5: 20.480268314431726 [OK] -----

True

SOA Question 6.30 : (A) 900


For a fully discrete whole life insurance of 100 on (x), you are given:
1. The first year expense is 10% of the gross annual premium
2. Expenses in subsequent years are 5% of the gross annual premium
3. The gross premium calculated using the equivalence principle is 2.338
4. 𝑖 = 0.04
5. 𝑎𝑥̈ = 16.50
6. 2 𝐴𝑥 = 0.17
Calculate the variance of the loss at issue random variable.

life = PolicyValues().set_interest(i=0.04)
contract = Contract(premium=2.338,
benefit=100,
initial_premium=.1,
renewal_premium=0.05)
var = life.gross_variance_loss(A1=life.insurance_twin(16.50),
A2=0.17, contract=contract)
isclose(900, var, question="Q6.30")

----- Q6.30 900: 908.141412994607 [OK] -----

True

SOA Question 6.31 : (D) 1330


For a fully continuous whole life insurance policy of 100,000 on (35), you are given:
1. The density function of the future lifetime of a newborn: $$
2. 𝛿 = 0.05
3. 𝐴70 = 0.51791
Calculate the annual net premium rate for this policy.

22.6. 6 Premium Calculation 201


Solving Actuarial Math with Python

life = ConstantForce(mu=0.01).set_interest(delta=0.05)
A = (life.term_insurance(35, t=35, discrete=False)
+ life.E_x(35, t=35)*0.51791) # A_35
P = life.premium_equivalence(A=A, b=100000, discrete=False)
isclose(1330, P, question="Q6.31")

----- Q6.31 1330: 1326.5406293909457 [OK] -----

True

SOA Question 6.32 : (C) 550


For a whole life insurance of 100,000 on (x), you are given:
1. Death benefits are payable at the moment of death
2. Deaths are uniformly distributed over each year of age
3. Premiums are payable monthly
4. 𝑖 = 0.05
5. 𝑎𝑥̈ = 9.19
Calculate the monthly net premium.

x = 0
life = Recursion().set_interest(i=0.05).set_a(9.19, x=x)
benefits = UDD(m=0, life=life).whole_life_insurance(x)
payments = UDD(m=12, life=life).whole_life_annuity(x)
P = life.gross_premium(a=payments, A=benefits, benefit=100000)/12
isclose(550, P, question="Q6.32")

Whole Life Insurance 𝐴𝑥 ∶=


𝑎𝑥̈ = [1 − 𝐴𝑥 ]/𝑑 annuity twin

----- Q6.32 550: 550.4356936711871 [OK] -----

True

SOA Question 6.33 : (B) 0.13


An insurance company sells 15-year pure endowments of 10,000 to 500 lives, each age x, with independent future life-
times. The single premium for each pure endowment is determined by the equivalence principle.
You are given:
1. 𝑖 = 0.03
2. 𝜇𝑥 (𝑡) = 0.02𝑡, 𝑡≥0
3. 0 𝐿 is the aggregate loss at issue random variable for these pure endowments.
Using the normal approximation without continuity correction, calculate 𝑃 𝑟(0 𝐿) > 50, 000).

202 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

life = Insurance().set_survival(mu=lambda x,t: 0.02*t).set_interest(i=0.03)


x = 0
var = life.E_x(x, t=15, moment=[Link], endowment=10000)
p = 1- life.portfolio_cdf(mean=0, variance=var, value=50000, N=500)
isclose(0.13, p, question="Q6.33", rel_tol=0.02)

----- Q6.33 0.13: 0.12828940905648634 [OK] -----

True

SOA Question 6.34 : (A) 23300


For a fully discrete whole life insurance policy on (61), you are given:
1. The annual gross premium using the equivalence principle is 500
2. Initial expenses, incurred at policy issue, are 15% of the premium
3. Renewal expenses, incurred at the beginning of each year after the first, are 3% of the premium
4. Mortality follows the Standard Ultimate Life Table
5. i = 0.05
Calculate the amount of the death benefit.

life = SULT()
def fun(benefit):
A = life.whole_life_insurance(61)
a = life.whole_life_annuity(61)
return life.gross_premium(A=A, a=a, benefit=benefit,
initial_premium=0.15, renewal_premium=0.03)
b = [Link](fun, target=500, grid=[23300, 23700])
isclose(23300, b, question="Q6.34")

----- Q6.34 23300: 23294.288659265632 [OK] -----

True

SOA Question 6.35 : (D) 530


For a fully discrete whole life insurance policy of 100,000 on (35), you are given:
1. First year commissions are 19% of the annual gross premium
2. Renewal year commissions are 4% of the annual gross premium
3. Mortality follows the Standard Ultimate Life Table
4. 𝑖 = 0.05
Calculate the annual gross premium for this policy using the equivalence principle.

sult = SULT()
A = sult.whole_life_insurance(35, b=100000)
a = sult.whole_life_annuity(35)
P = sult.gross_premium(a=a, A=A, initial_premium=.19, renewal_premium=.04)
isclose(530, P, question="Q6.35")

22.6. 6 Premium Calculation 203


Solving Actuarial Math with Python

----- Q6.35 530: 534.4072234303344 [OK] -----

True

SOA Question 6.36 : (B) 500

life = ConstantForce(mu=0.04).set_interest(delta=0.08)
a = life.temporary_annuity(50, t=20, discrete=False)
A = life.term_insurance(50, t=20, discrete=False)
def fun(R):
return life.gross_premium(a=a, A=A, initial_premium=R/4500,
renewal_premium=R/4500, benefit=100000)
R = [Link](fun, target=4500, grid=[400, 800])
isclose(500, R, question="Q6.36")

----- Q6.36 500: 500.0 [OK] -----

True

SOA Question 6.37 : (D) 820


For a fully discrete whole life insurance policy of 50,000 on (35), with premiums payable for a maximum of 10 years,
you are given:
1. Expenses of 100 are payable at the end of each year including the year of death
2. Mortality follows the Standard Ultimate Life Table
3. 𝑖 = 0.05
Calculate the annual gross premium using the equivalence principle.

sult = SULT()
benefits = sult.whole_life_insurance(35, b=50000 + 100)
expenses = sult.immediate_annuity(35, b=100)
a = sult.temporary_annuity(35, t=10)
P = (benefits + expenses) / a
isclose(820, P, question="Q6.37")

----- Q6.37 820: 819.7190338249138 [OK] -----

True

SOA Question 6.38 : (B) 11.3


For an n-year endowment insurance of 1000 on (x), you are given:
1. Death benefits are payable at the moment of death
2. Premiums are payable annually at the beginning of each year
3. Deaths are uniformly distributed over each year of age
4. 𝑖 = 0.05
5. 𝑛 𝐸𝑥 = 0.172

204 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

6. 𝐴𝑥∶𝑛| = 0.192
Calculate the annual net premium for this insurance.

x, n = 0, 10
life = Recursion().set_interest(i=0.05)\
.set_A(0.192, x=x, t=n, endowment=1, discrete=False)\
.set_E(0.172, x=x, t=n)
a = life.temporary_annuity(x, t=n, discrete=False)

def fun(a): # solve for discrete annuity, given continuous


life = Recursion(verbose=False).set_interest(i=0.05)\
.set_a(a, x=x, t=n)\
.set_E(0.172, x=x, t=n)
return UDD(m=0, life=life).temporary_annuity(x, t=n)
a = [Link](fun, target=a, grid=a) # discrete annuity
P = life.gross_premium(a=a, A=0.192, benefit=1000)
isclose(11.3, P, question="Q6.38")

Temporary Annuity 𝑎𝑥∶10| ∶=


𝑎𝑥∶10|
̈ = [1 − 𝐴𝑥∶10| ]/𝑑 annuity twin

----- Q6.38 11.3: 11.308644185253657 [OK] -----

True

SOA Question 6.39 : (A) 29


XYZ Insurance writes 10,000 fully discrete whole life insurance policies of 1000 on lives age 40 and an additional 10,000
fully discrete whole life policies of 1000 on lives age 80.
XYZ used the following assumptions to determine the net premiums for these policies:
1. Mortality follows the Standard Ultimate Life Table
2. i = 0.05
During the first ten years, mortality did follow the Standard Ultimate Life Table.
Calculate the average net premium per policy in force received at the beginning of the eleventh year.

sult = SULT()
P40 = sult.premium_equivalence(sult.whole_life_insurance(40), b=1000)
P80 = sult.premium_equivalence(sult.whole_life_insurance(80), b=1000)
p40 = sult.p_x(40, t=10)
p80 = sult.p_x(80, t=10)
P = (P40 * p40 + P80 * p80) / (p80 + p40)
isclose(29, P, question="Q6.39")

----- Q6.39 29: 29.033866427845496 [OK] -----

True

SOA Question 6.40 : (C) 116


For a special fully discrete whole life insurance, you are given:

22.6. 6 Premium Calculation 205


Solving Actuarial Math with Python

1. The death benefit is 1000(1.03)𝑘 for death in policy year k, for 𝑘 = 1, 2, 3...
2. 𝑞𝑥 = 0.05
3. 𝑖 = 0.06
4. 𝑎𝑥+1
̈ = 7.00
5. The annual net premium for this insurance at issue age x is 110
Calculate the annual net premium for this insurance at issue age 𝑥 + 1.

# - standard formula discounts/accumulates by too much (i should be smaller)


x = 0
life = Recursion().set_interest(i=0.06).set_a(7, x=x+1).set_q(0.05, x=x)
a = life.whole_life_annuity(x)
A = 110 * a / 1000
life = Recursion().set_interest(i=0.06).set_A(A, x=x).set_q(0.05, x=x)
A1 = life.whole_life_insurance(x+1)
P = life.gross_premium(A=A1 / 1.03, a=7) * 1000
isclose(116, P, question="Q6.40")

Whole Life Annuity 𝑎𝑥̈ ∶=


𝑎𝑥̈ = 1 + 𝐸𝑥 ∗ 𝑎𝑥+1
̈ backward recursion
𝐸𝑥 = 𝑝𝑥 ∗ 𝑣 pure endowment
𝑝𝑥 = 1 − 𝑞 𝑥 complement of mortality

Whole Life Insurance 𝐴𝑥+1 ∶=


𝐴𝑥+1 = [ 𝐴𝑥 /𝑣 − 𝑞𝑥 ∗ 𝑏]/ 𝑝𝑥 forward recursion
𝑝𝑥 = 1 − 𝑞 𝑥 complement of mortality

----- Q6.40 116: 116.51945397474269 [OK] -----

True

SOA Question 6.41 : (B) 1417


For a special fully discrete 2-year term insurance on (x), you are given:
1. 𝑞𝑥 = 0.01
2. 𝑞𝑥+1 = 0.02
3. 𝑖 = 0.05
4. The death benefit in the first year is 100,000
5. Both the benefits and premiums increase by 1% in the second year
Calculate the annual net premium in the first year.

x = 0
life = LifeTable().set_interest(i=0.05).set_table(q={x:.01, x+1:.02})
a = 1 + life.E_x(x, t=1) * 1.01
A = life.deferred_insurance(x, u=0, t=1) + 1.01*life.deferred_insurance(x, u=1, t=1)
P = 100000 * A / a
isclose(1417, P, question="Q6.41")

206 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

----- Q6.41 1417: 1416.9332301924137 [OK] -----

True

SOA Question 6.42 : (D) 0.113

x = 0
life = ConstantForce(mu=0.06).set_interest(delta=0.06)
contract = Contract(discrete=True, premium=315.8,
T=3, endowment=1000, benefit=1000)
L = [life.L_from_t(t, contract=contract) for t in range(3)] # L(t)
Q = [life.q_x(x, u=u, t=1) for u in range(3)] # prob(die in year t)
Q[-1] = 1 - sum(Q[:-1]) # follows SOA Solution: incorrectly treats endowment!
p = sum([q for (q, l) in zip (Q, L) if l > 0])
isclose(0.113, p, question="Q6.42")

----- Q6.42 0.113: 0.11307956328284252 [OK] -----

True

SOA Question 6.43 : (C) 170


For a fully discrete, 5-payment 10-year term insurance of 200,000 on (30), you are given:
1. Mortality follows the Standard Ultimate Life Table
2. The following expenses are incurred at the beginning of each respective year:

Percent of Premium Per Policy Percent of Premium Per Policy

Year 1 Year 1 Years 2 - 10 Years 2 - 10

Taxes 5% 0 5% 0
Commissions 30% 0 10% 0
Maintenance 0% 8 0% 4

3. i = 0.05
4. 𝑎30∶5|
̈ = 4.5431
Calculate the annual gross premium using the equivalence principle.
• although 10-year term, premiums only paid first first years: separately calculate the EPV of per-policy maintenance
expenses in years 6-10 and treat as additional initial expense

sult = SULT()
a = sult.temporary_annuity(30, t=5)
A = sult.term_insurance(30, t=10)
other_expenses = 4 * sult.deferred_annuity(30, u=5, t=5)
P = sult.gross_premium(a=a, A=A, benefit=200000, initial_premium=0.35,
initial_policy=8 + other_expenses, renewal_policy=4,
renewal_premium=0.15)
isclose(170, P, question="Q6.43")

22.6. 6 Premium Calculation 207


Solving Actuarial Math with Python

----- Q6.43 170: 171.22371939459944 [OK] -----

True

SOA Question 6.44 : (D) 2.18

life = Recursion().set_interest(i=0.05)\
.set_IA(0.15, x=50, t=10)\
.set_a(17, x=50)\
.set_a(15, x=60)\
.set_E(0.6, x=50, t=10)
A = life.deferred_insurance(50, u=10)
IA = life.increasing_insurance(50, t=10)
a = life.temporary_annuity(50, t=10)
P = life.gross_premium(a=a, A=A, IA=IA, benefit=100)
isclose(2.2, P, question="Q6.44")

Whole Life Insurance 𝐴𝑥+60 ∶=


𝑎𝑥+60
̈ = [1 − 𝐴𝑥+60 ]/𝑑 annuity twin

Whole Life Insurance 𝐴𝑥+60 ∶=


𝑎𝑥+60
̈ = [1 − 𝐴𝑥+60 ]/𝑑 annuity twin
Whole Life Insurance 𝐴𝑥+60 ∶=
𝑎𝑥+60
̈ = [1 − 𝐴𝑥+60 ]/𝑑 annuity twin

----- Q6.44 2.2: 2.183803457688809 [OK] -----

True

SOA Question 6.45 : (E) 690


For a fully continuous whole life insurance of 100,000 on (35), you are given:
1. The annual rate of premium is 560
2. Mortality follows the Standard Ultimate Life Table
3. Deaths are uniformly distributed over each year of age
4. i = 0.05
Calculate the 75th percentile of the loss at issue random variable for this policy.

life = SULT(udd=True)
contract = Contract(benefit=100000, premium=560, discrete=False)
L = life.L_from_prob(x=35, prob=0.75, contract=contract)
life.L_plot(x=35, contract=contract,
T=life.L_to_t(L=L, contract=contract))
isclose(690, L, question="Q6.45")

----- Q6.45 690: 689.2659416264196 [OK] -----

208 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

True

SOA Question 6.46 : (E) 208

life = Recursion().set_interest(i=0.05)\
.set_IA(0.51213, x=55, t=10)\
.set_a(12.2758, x=55)\
.set_a(7.4575, x=55, t=10)
A = life.deferred_annuity(55, u=10)
IA = life.increasing_insurance(55, t=10)
a = life.temporary_annuity(55, t=10)
P = life.gross_premium(a=a, A=A, IA=IA, benefit=300)
isclose(208, P, question="Q6.46")

----- Q6.46 208: 208.12282139036515 [OK] -----

True

SOA Question 6.47 : (D) 66400


For a 10-year deferred whole life annuity-due with payments of 100,000 per year on (70), you are given:
1. Annual gross premiums of 𝐺 are payable for 10 years
2. First year expenses are 75% of premium
3. Renewal expenses for years 2 and later are 5% of premium during the premium paying period

22.6. 6 Premium Calculation 209


Solving Actuarial Math with Python

4. Mortality follows the Standard Ultimate Life Table


5. i = 0.05
Calculate 𝐺 using the equivalence principle.

sult = SULT()
a = sult.temporary_annuity(70, t=10)
A = sult.deferred_annuity(70, u=10)
P = sult.gross_premium(a=a, A=A, benefit=100000, initial_premium=0.75,
renewal_premium=0.05)
isclose(66400, P, question="Q6.47")

----- Q6.47 66400: 66384.13293704337 [OK] -----

True

SOA Question 6.48 : (A) 3195


For a special fully discrete 5-year deferred 3-year term insurance of 100,000 on (x) you are given:
1. There are two premium payments, each equal to P . The first is paid at the beginning of the first year and the second
is paid at the end of the 5-year deferral period
2. 𝑝𝑥 = 0.95
3. 𝑞𝑥+5 = 0.02
4. 𝑞𝑥+6 = 0.03
5. 𝑞𝑥+7 = 0.04
6. 𝑖 = 0.06
Calculate P using the equivalence principle.

x = 0
life = Recursion(depth=5).set_interest(i=0.06)\
.set_p(.95, x=x, t=5)\
.set_q(.02, x=x+5)\
.set_q(.03, x=x+6)\
.set_q(.04, x=x+7)
a = 1 + life.E_x(x, t=5)
A = life.deferred_insurance(x, u=5, t=3)
P = life.gross_premium(A=A, a=a, benefit=100000)
isclose(3195, P, question="Q6.48")

Pure Endowment 5 𝐸𝑥 ∶=
5
5 𝐸𝑥 = 5 𝑝𝑥 ∗ 𝑣 pure endowment

Pure Endowment 5 𝐸𝑥 ∶=
5
5 𝐸𝑥 = 5 𝑝𝑥 ∗ 𝑣 pure endowment

210 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

Term Insurance 𝐴1𝑥+5∶3| ∶=


𝐴1𝑥+5∶1| = 𝐴𝑥+5∶1| − 𝐸𝑥+5 endowment insurance - pure
𝐴𝑥+5∶3| = 𝐴1𝑥+5∶3| + 3 𝐸𝑥+5 term plus pure endowment
3
3 𝐸𝑥+5 = 3 𝑝𝑥+5 ∗ 𝑣 pure endowment
3 𝑝𝑥+5 = 2 𝑝𝑥+6 ∗ 𝑝𝑥+5 survival chain rule
2 𝑝𝑥+6 = 𝑝𝑥+7 ∗ 𝑝𝑥+6 survival chain rule
𝐴1𝑥+5∶3| = 𝐴𝑥+5∶3| − 3 𝐸𝑥+5 endowment insurance - pure
3 𝐸𝑥+5 = 𝐸𝑥+5 ∗ 2 𝐸𝑥+6 pure endowment chain rule
2 𝐸𝑥+6 = 𝐸𝑥+6 ∗ 𝐸𝑥+7 pure endowment chain rule
𝐸𝑥+7 = 𝑝𝑥+7 ∗ 𝑣 pure endowment
𝐸𝑥+6 = 𝑝𝑥+6 ∗ 𝑣 pure endowment
𝐸𝑥+5 = 𝑝𝑥+5 ∗ 𝑣 pure endowment
𝑝𝑥+7 = 1 − 𝑞𝑥+7 complement of mortality
𝐴1𝑥+5∶3| = 𝑣 ∗ [ 𝑞𝑥+5 ∗ 𝑏 + 𝑝𝑥+5 ∗ 𝐴1𝑥+6∶2| ] backward recursion
𝐴1𝑥+6∶2| = 𝑣 ∗ [ 𝑞𝑥+6 ∗ 𝑏 + 𝑝𝑥+6 ∗ 𝐴1𝑥+7∶1| ] backward recursion
𝑝𝑥+6 = 1 − 𝑞𝑥+6 complement of mortality
𝑝𝑥+5 = 1 − 𝑞𝑥+5 complement of mortality
Term Insurance 𝐴1𝑥+5∶3| ∶=
𝐴1𝑥+5∶1| = 𝐴𝑥+5∶1| − 𝐸𝑥+5 endowment insurance - pure
𝐴𝑥+5∶3| = 𝐴1𝑥+5∶3| + 3 𝐸𝑥+5 term plus pure endowment
3
3 𝐸𝑥+5 = 3 𝑝𝑥+5 ∗ 𝑣 pure endowment
𝑝
3 𝑥+5 = 𝑝
2 𝑥+6 ∗ 𝑝𝑥+5 survival chain rule
𝑝
2 𝑥+6 = 𝑝 𝑥+7 ∗ 𝑝𝑥+6 survival chain rule
𝐴1𝑥+5∶3| = 𝐴𝑥+5∶3| − 3 𝐸𝑥+5 endowment insurance - pure
3 𝐸𝑥+5 = 𝐸𝑥+5 ∗ 2 𝐸𝑥+6 pure endowment chain rule
2 𝐸𝑥+6 = 𝐸𝑥+6 ∗ 𝐸𝑥+7 pure endowment chain rule
𝐸𝑥+7 = 𝑝𝑥+7 ∗ 𝑣 pure endowment
𝐸𝑥+6 = 𝑝𝑥+6 ∗ 𝑣 pure endowment
𝐸𝑥+5 = 𝑝𝑥+5 ∗ 𝑣 pure endowment
𝑝𝑥+7 = 1 − 𝑞𝑥+7 complement of mortality
𝐴1𝑥+5∶3| = 𝑣 ∗ [ 𝑞𝑥+5 ∗ 𝑏 + 𝑝𝑥+5 ∗ 𝐴1𝑥+6∶2| ] backward recursion
𝐴1𝑥+6∶2| = 𝑣 ∗ [ 𝑞𝑥+6 ∗ 𝑏 + 𝑝𝑥+6 ∗ 𝐴1𝑥+7∶1| ] backward recursion
𝑝𝑥+6 = 1 − 𝑞𝑥+6 complement of mortality
𝑝𝑥+5 = 1 − 𝑞𝑥+5 complement of mortality

----- Q6.48 3195: 3195.118917658744 [OK] -----

True

SOA Question 6.49 : (C) 86


For a special whole life insurance of 100,000 on (40), you are given:
1. The death benefit is payable at the moment of death
2. Level gross premiums are payable monthly for a maximum of 20 years
3. Mortality follows the Standard Ultimate Life Table
4. 𝑖 = 0.05
5. Deaths are uniformly distributed over each year of age
6. Initial expenses are 200

22.6. 6 Premium Calculation 211


Solving Actuarial Math with Python

7. Renewal expenses are 4% of each premium including the first


8. Gross premiums are calculated using the equivalence principle
Calculate the monthly gross premium.

sult = SULT(udd=True)
a = UDD(m=12, life=sult).temporary_annuity(40, t=20)
A = sult.whole_life_insurance(40, discrete=False)
P = sult.gross_premium(a=a, A=A, benefit=100000, initial_policy=200,
renewal_premium=0.04, initial_premium=0.04) / 12
isclose(86, P, question="Q6.49")

----- Q6.49 86: 85.99177833261696 [OK] -----

True

SOA Question 6.50 : (A) -47000


On July 15, 2017, XYZ Corp buys fully discrete whole life insurance policies of 1,000 on each of its 10,000 workers, all
age 35. It uses the death benefits to partially pay the premiums for the following year.
You are given:
1. Mortality follows the Standard Ultimate Life Table
2. i = 0.05
3. The insurance is priced using the equivalence principle
Calculate XYZ Corp’s expected net cash flow from these policies during July 2018.

life = SULT()
P = life.premium_equivalence(a=life.whole_life_annuity(35), b=1000)
a = life.deferred_annuity(35, u=1, t=1)
A = life.term_insurance(35, t=1, b=1000)
cash = (A - a * P) * 10000 / [Link].v
isclose(-47000, cash, question="Q6.50")

----- Q6.50 -47000: -46948.2187697819 [OK] -----

True

SOA Question 6.51 : (D) 34700

life = Recursion().set_DA(0.4891, x=62, t=10)\


.set_A(0.0910, x=62, t=10)\
.set_a(12.2758, x=62)\
.set_a(7.4574, x=62, t=10)
IA = life.increasing_insurance(62, t=10)
A = life.deferred_annuity(62, u=10)
a = life.temporary_annuity(62, t=10)
P = life.gross_premium(a=a, A=A, IA=IA, benefit=50000)
isclose(34700, P, question="Q6.51")

212 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

Increasing Insurance (𝐼𝐴)𝑥+62∶10| ∶=


(𝐼𝐴)𝑥+62∶10| = 11 𝐴1𝑥+62∶10| − (𝐷𝐴)𝑥+62∶10| varying insurance identity

----- Q6.51 34700: 34687.207544453246 [OK] -----

True

SOA Question 6.52 : (D) 50.80


For a fully discrete 10-payment whole life insurance of H on (45), you are given:
1. Expenses payable at the beginning of each year are as follows:

Expense Type First Year Years 2-10 Years 11+


Per policy 100 20 10
% of Premium 105% 5% 0%

2. Mortality follows the Standard Ultimate Life Table


3. i = 0.05
4. The gross annual premium, calculated using the equivalence principle, is of the form 𝐺 = 𝑔𝐻 + 𝑓, where 𝑔 is the
premium rate per 1 of insurance and 𝑓 is the per policy fee
Calculate 𝑓.
• set face value benefits to 0

sult = SULT()
a = sult.temporary_annuity(45, t=10)
other_cost = 10 * sult.deferred_annuity(45, u=10)
P = sult.gross_premium(a=a, A=0, benefit=0, # set face value H = 0
initial_premium=1.05, renewal_premium=0.05,
initial_policy=100 + other_cost, renewal_policy=20)
isclose(50.8, P, question="Q6.52")

----- Q6.52 50.8: 50.80135534704229 [OK] -----

True

SOA Question 6.53 : (D) 720


A warranty pays 2000 at the end of the year of the first failure if a washing machine fails within three years of purchase.
The warranty is purchased with a single premium, G, paid at the time of purchase of the washing machine. You are given:
1. 10% of the washing machines that are working at the start of each year fail by the end of that year
2. i = 0.08
3. The sales commission is 35% of G
4. G is calculated using the equivalence principle
Calculate G.

22.6. 6 Premium Calculation 213


Solving Actuarial Math with Python

x = 0
life = LifeTable().set_interest(i=0.08).set_table(q={x:.1, x+1:.1, x+2:.1})
A = life.term_insurance(x, t=3)
P = life.gross_premium(a=1, A=A, benefit=2000, initial_premium=0.35)
isclose(720, P, question="Q6.53")

----- Q6.53 720: 720.1646090534978 [OK] -----

True

SOA Question 6.54 : (A) 25440


For a fully discrete whole life insurance of 200,000 on (45), you are given:
1. Mortality follows the Standard Ultimate Life Table.
2. i = 0.05
3. The annual premium is determined using the equivalence principle.
Calculate the standard deviation of 0 𝐿 , the present value random variable for the loss at issue.
[A modified version of Question 12 on the Fall 2017 exam]

life = SULT()
std = [Link](life.net_policy_variance(45, b=200000))
isclose(25440, std, question="Q6.54")

----- Q6.54 25440: 25441.694847703857 [OK] -----

True

22.7 7 Policy Values

SOA Question 7.1 : (C) 11150


For a special fully discrete whole life insurance on (40), you are given:
1. The death benefit is 50,000 in the first 20 years and 100,000 thereafter
2. Level net premiums of 875 are payable for 20 years
3. Mortality follows the Standard Ultimate Life Table
4. i = 0.05
Calculate 10 𝑉 the net premium policy value at the end of year 10 for this insurance.

life = SULT()
x, n, t = 40, 20, 10
A = (life.whole_life_insurance(x+t, b=50000)
+ life.deferred_insurance(x+t, u=n-t, b=50000))
a = life.temporary_annuity(x+t, t=n-t, b=875)
L = life.gross_future_loss(A=A, a=a)
isclose(11150, L, question="Q7.1")

214 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

----- Q7.1 11150: 11152.108749338717 [OK] -----

True

SOA Question 7.2 : (C) 1152

x = 0
life = Recursion(verbose=False).set_interest(i=.1)\
.set_q(0.15, x=x)\
.set_q(0.165, x=x+1)\
.set_reserves(T=2, endowment=2000)

def fun(P): # solve P s.t. V is equal backwards and forwards


policy = dict(t=1, premium=P, benefit=lambda t: 2000, reserve_benefit=True)
return life.t_V_backward(x, **policy) - life.t_V_forward(x, **policy)
P = [Link](fun, target=0, grid=[1070, 1230])
isclose(1152, P, question="Q7.2")

----- Q7.2 1152: 1151.5151515151515 [OK] -----

True

SOA Question 7.3 : (E) 730

x = 0 # x=0 is (90) and interpret every 3 months as t=1 year


life = LifeTable().set_interest(i=0.08/4)\
.set_table(l={0:1000, 1:898, 2:800, 3:706})\
.set_reserves(T=8, V={3: 753.72})
V = life.t_V_backward(x=0, t=2, premium=60*0.9, benefit=lambda t: 1000)
V = life.set_reserves(V={2: V})\
.t_V_backward(x=0, t=1, premium=0, benefit=lambda t: 1000)
isclose(730, V, question="Q7.3")

----- Q7.3 730: 729.998398765594 [OK] -----

True

SOA Question 7.4 : (B) -74


For a special fully discrete whole life insurance on (40), you are given:
1. The death benefit is 1000 during the first 11 years and 5000 thereafter
2. Expenses, payable at the beginning of the year, are 100 in year 1 and 10 in years 2 and later
3. 𝜋 is the level annual premium, determined using the equivalence principle
4. 𝐺 = 1.02 × 𝜋 is the level annual gross premium
5. Mortality follows the Standard Ultimate Life Table
6. i = 0.05
7. 11 𝐸40 = 0.57949
Calculate the gross premium policy value at the end of year 1 for this insurance.

22.7. 7 Policy Values 215


Solving Actuarial Math with Python

hints:
• split benefits into two policies

life = SULT()
P = life.gross_premium(a=life.whole_life_annuity(40),
A=life.whole_life_insurance(40),
initial_policy=100, renewal_policy=10,
benefit=1000)
P += life.gross_premium(a=life.whole_life_annuity(40),
A=life.deferred_insurance(40, u=11),
benefit=4000) # for deferred portion
contract = Contract(benefit=1000, premium=1.02*P,
renewal_policy=10, initial_policy=100)
V = life.gross_policy_value(x=40, t=1, contract=contract)
contract = Contract(benefit=4000, premium=0)
A = life.deferred_insurance(41, u=10)
V += life.gross_future_loss(A=A, a=0, contract=contract) # for deferred portion
isclose(-74, V, question="Q7.4")

----- Q7.4 -74: -73.942155695248 [OK] -----

True

SOA Question 7.5 : (E) 1900


For a fully discrete whole life insurance of 10,000 on (x), you are given:
1. Deaths are uniformly distributed over each year of age
2. The net premium is 647.46
3. The net premium policy value at the end of year 4 is 1405.08
4. 𝑞𝑥+4 = 0.04561
5. i = 0.03
Calculate the net premium policy value at the end of 4.5 years.

x = 0
life = Recursion(udd=True).set_interest(i=0.03)\
.set_q(0.04561, x=x+4)\
.set_reserves(T=3, V={4: 1405.08})
V = life.r_V_forward(x, s=4, r=0.5, benefit=10000, premium=647.46)
isclose(1900, V, question="Q7.5")

----- Q7.5 1900: 1901.766021537228 [OK] -----

True

Answer 7.6: (E) -25.4

life = SULT()
P = life.net_premium(45, b=2000)
contract = Contract(benefit=2000, initial_premium=.25, renewal_premium=.05,
initial_policy=2*1.5 + 30, renewal_policy=2*.5 + 10)
(continues on next page)

216 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

(continued from previous page)


G = life.gross_premium(a=life.whole_life_annuity(45), **contract.premium_terms)
gross = life.gross_policy_value(45, t=10, contract=contract.set_contract(premium=G))
net = life.net_policy_value(45, t=10, b=2000)
V = gross - net
isclose(-25.4, V, question="Q7.6")

----- Q7.6 -25.4: -25.44920289521204 [OK] -----

True

SOA Question 7.7 : (D) 1110


For a whole life insurance of 10,000 on (x), you are given:
1. Death benefits are payable at the end of the year of death
2. A premium of 30 is payable at the start of each month
3. Commissions are 5% of each premium
4. Expenses of 100 are payable at the start of each year
5. 𝑖 = 0.05
6. 1000𝐴𝑥+10 = 400
7. 10 𝑉 is the gross premium policy value at the end of year 10 for this insurance
Calculate 10 𝑉 using the two-term Woolhouse formula for annuities.

x = 0
life = Recursion().set_interest(i=0.05).set_A(0.4, x=x+10)
a = Woolhouse(m=12, life=life).whole_life_annuity(x+10)
contract = Contract(premium=0, benefit=10000, renewal_policy=100)
V = life.gross_future_loss(A=0.4, contract=[Link]())
contract = Contract(premium=30*12, renewal_premium=0.05)
V += life.gross_future_loss(a=a, contract=[Link]())
isclose(1110, V, question="Q7.7")

Whole Life Annuity 𝑎𝑥+10̈ ∶=


𝑎𝑥+10
̈ = [1 − 𝐴𝑥+10 ]/𝑑 insurance twin

----- Q7.7 1110: 1107.9718253968258 [OK] -----

True

SOA Question 7.8 : (C) 29.85

sult = SULT()
x = 70
q = {x: [sult.q_x(x+k)*(.7 + .1*k) for k in range(3)] + [sult.q_x(x+3)]}
life = Recursion().set_interest(i=.05)\
.set_q(sult.q_x(70)*.7, x=x)\
(continues on next page)

22.7. 7 Policy Values 217


Solving Actuarial Math with Python

(continued from previous page)


.set_reserves(T=3)
V = life.t_V(x=70, t=1, premium=35.168, benefit=lambda t: 1000)
isclose(29.85, V, question="Q7.8")

Survival 𝑝𝑥+70 ∶=
𝑝𝑥+70 = 1 − 𝑞𝑥+70 complement of mortality

----- Q7.8 29.85: 29.85469179271202 [OK] -----

True

SOA Question 7.9 : (A) 38100


For a semi-continuous 20-year endowment insurance of 100,000 on (45), you are given:
1. Net premiums of 253 are payable monthly
2. Mortality follows the Standard Ultimate Life Table
3. Deaths are uniformly distributed over each year of age
4. 𝑖 = 0.05
Calculate 10 𝑉 , the net premium policy value at the end of year 10 for this insurance.

sult = SULT(udd=True)
x, n, t = 45, 20, 10
a = UDD(m=12, life=sult).temporary_annuity(x=x+10, t=n-t)
A = UDD(m=0, life=sult).endowment_insurance(x=x+10, t=n-t)
contract = Contract(premium=253*12, endowment=100000, benefit=100000)
V = sult.gross_future_loss(A=A, a=a, contract=contract)
isclose(38100, V, question="Q7.9")

----- Q7.9 38100: 38099.62176709246 [OK] -----

True

SOA Question 7.10 : (C) -970


For a fully discrete whole life insurance of 100,000 on (45), you are given:
1. Mortality follows the Standard Ultimate Life Table
2. i = 0.05
3. Commission expenses are 60% of the first year’s gross premium and 2% of renewal gross premiums
4. Administrative expenses are 500 in the first year and 50 in each renewal year
5. All expenses are payable at the start of the year
6. The gross premium, calculated using the equivalence principle, is 977.60
Calculate 5 𝑉 𝑒 , the expense reserve at the end of year 5 for this insurance.

218 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

life = SULT()
G = 977.6
P = life.net_premium(45, b=100000)
contract = Contract(benefit=0, premium=G-P, renewal_policy=.02*G + 50)
V = life.gross_policy_value(45, t=5, contract=contract)
isclose(-970, V, question="Q7.10")

----- Q7.10 -970: -971.8909301877826 [OK] -----

True

SOA Question 7.11 : (B) 1460

life = Recursion().set_interest(i=0.05).set_a(13.4205, x=55)


contract = Contract(benefit=10000)
def fun(P):
return life.L_from_t(t=10, contract=contract.set_contract(premium=P))
P = [Link](fun, target=4450, grid=400)
V = life.gross_policy_value(45, t=10, contract=contract.set_contract(premium=P))
isclose(1460, V, question="Q7.11")

Whole Life Insurance 𝐴𝑥+55 ∶=


𝑎𝑥+55
̈ = [1 − 𝐴𝑥+55 ]/𝑑 annuity twin

----- Q7.11 1460: 1459.9818035330218 [OK] -----

True

SOA Question 7.12 : (E) 4.09


For a special fully discrete 25-year endowment insurance on (44), you are given:
1. The death benefit is ( 26−k ) for death in year ,k for k = 1,2,3,…,25
2. The endowment benefit in year 25 is 1
3. Net premiums are level
4. 𝑞55 = 0.15
5. i = 0.04
6. 11 𝑉 the net premium policy value at the end of year 11, is 5.00
7. 24 𝑉 the net premium policy value at the end of year 24, is 0.60
Calculate 12 𝑉 the net premium policy value at end of year 12.

benefit = lambda k: 26 - k
x = 44
life = Recursion().set_interest(i=0.04)\
.set_q(0.15, x=55)\
.set_reserves(T=25, endowment=1, V={11: 5.})
def fun(P): # solve for net premium, from final year recursion
(continues on next page)

22.7. 7 Policy Values 219


Solving Actuarial Math with Python

(continued from previous page)


return life.t_V(x=x, t=24, premium=P, benefit=benefit)
P = [Link](fun, target=0.6, grid=0.5) # solved net premium
V = life.t_V(x, t=12, premium=P, benefit=benefit) # recursion formula
isclose(4.09, V, question="Q7.12")

Survival 𝑝𝑥+55 ∶=
𝑝𝑥+55 = 1 − 𝑞𝑥+55 complement of mortality

----- Q7.12 4.09: 4.089411764705883 [OK] -----

True

SOA Question 7.13 : (A) 180

life = SULT()
V = life.FPT_policy_value(40, t=10, n=30, endowment=1000, b=1000)
isclose(180, V, question="Q7.13")

----- Q7.13 180: 180.1071785904076 [OK] -----

True

SOA Question 7.14 : (A) 2200


For a fully discrete whole life insurance of 100,000 on (45), you are given:
1. The gross premium policy value at duration 5 is 5500 and at duration 6 is 7100
2. 𝑞50 = 0.009
3. i = 0.05
4. Renewal expenses at the start of each year are 50 plus 4% of the gross premium.
5. Claim expenses are 200.
Calculate the gross premium.

x = 45
life = Recursion(verbose=False).set_interest(i=0.05)\
.set_q(0.009, x=50)\
.set_reserves(T=10, V={5: 5500})
def fun(P): # solve for net premium,
return life.t_V(x=x, t=6, premium=P*0.96 - 50, benefit=lambda t: 100000+200)
P = [Link](fun, target=7100, grid=[2200, 2400])
isclose(2200, P, question="Q7.14")

----- Q7.14 2200: 2197.8174603174602 [OK] -----

True

220 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

SOA Question 7.15 : (E) 50.91


For a fully discrete whole life insurance of 100 on (x), you are given:
1. 𝑞𝑥+15 = 0.10
2. Deaths are uniformly distributed over each year of age
3. i = 0.05
4. 𝑡 𝑉 denotes the net premium policy value at time t
5. 16 𝑉 = 49.78
Calculate 15.6.

x = 0
V = Recursion(udd=True).set_interest(i=0.05)\
.set_q(0.1, x=x+15)\
.set_reserves(T=3, V={16: 49.78})\
.r_V_backward(x, s=15, r=0.6, benefit=100)
isclose(50.91, V, question="Q7.15")

----- Q7.15 50.91: 50.91362826922369 [OK] -----

True

SOA Question 7.16 : (D) 380

life = SelectLife().set_interest(v=.95)\
.set_table(A={86: [683/1000]},
q={80+k: [.01*(k+1)] for k in range(6)})
x, t, n = 80, 3, 5
A = life.whole_life_insurance(x+t)
a = life.temporary_annuity(x+t, t=n-t)
V = life.gross_future_loss(A=A, a=a, contract=Contract(benefit=1000, premium=130))
isclose(380, V, question="Q7.16")

----- Q7.16 380: 381.6876905200001 [OK] -----

True

SOA Question 7.17 : (D) 1.018

x = 0
life = Recursion().set_interest(v=[Link](0.90703))\
.set_q(0.02067, x=x+10)\
.set_A(0.52536, x=x+11)\
.set_A(0.30783, x=x+11, moment=2)
A1 = life.whole_life_insurance(x+10)
A2 = life.whole_life_insurance(x+10, moment=2)
ratio = (life.insurance_variance(A2=A2, A1=A1)
/ life.insurance_variance(A2=0.30783, A1=0.52536))
isclose(1.018, ratio, question="Q7.17")

22.7. 7 Policy Values 221


Solving Actuarial Math with Python

Whole Life Insurance 𝐴𝑥+10 ∶=


𝐴𝑥+10 = 𝑣 ∗ [ 𝑞𝑥+10 ∗ 𝑏 + 𝑝𝑥+10 ∗ 𝐴𝑥+11 ] backward recursion
𝑝𝑥+10 = 1 − 𝑞𝑥+10 complement of mortality

Whole Life Insurance 2 𝐴𝑥+10 ∶=


2
𝐴𝑥+10 = 𝑣2 ∗ [ 𝑞𝑥+10 ∗ 𝑏2 + 𝑝𝑥+10 ∗ 2 𝐴𝑥+11 ] backward recursion
𝑝𝑥+10 = 1 − 𝑞𝑥+10 complement of mortality

----- Q7.17 1.018: 1.0182465434445054 [OK] -----

True

SOA Question 7.18 : (A) 17.1


For a fully discrete whole life insurance of 1 on (x), you are given:
1. The net premium policy value at the end of the first year is 0.012
2. 𝑞𝑥 = 0.009
3. i = 0.04
Calculate 𝑎𝑥̈

x = 10
life = Recursion(verbose=False).set_interest(i=0.04).set_q(0.009, x=x)
def fun(a):
return life.set_a(a, x=x).net_policy_value(x, t=1)
a = [Link](fun, target=0.012, grid=[17.1, 19.1])
isclose(17.1, a, question="Q7.18")

----- Q7.18 17.1: 17.07941929974385 [OK] -----

True

SOA Question 7.19 : (D) 720


For a fully discrete whole life insurance of 100,000 on (40) you are given:
1. Expenses incurred at the beginning of the first year are 300 plus 50% of the first year premium
2. Renewal expenses, incurred at the beginning of the year, are 10% of each of the renewal premiums
3. Mortality follows the Standard Ultimate Life Table
4. i = 0.05
5. Gross premiums are calculated using the equivalence principle
Calculate the gross premium policy value for this insurance immediately after the second premium and associated renewal
expenses are paid.

life = SULT()
contract = Contract(benefit=100000,
initial_policy=300,
initial_premium=.5,
(continues on next page)

222 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

(continued from previous page)


renewal_premium=.1)
P =
life.gross_premium(A=life.whole_life_insurance(40), **contract.premium_terms)
A =
life.whole_life_insurance(41)
a =
life.immediate_annuity(41) # after premium and expenses are paid
V =
life.gross_future_loss(A=A,
a=a,
contract=contract.set_contract(premium=P).renewals())
isclose(720, V, question="Q7.19")

----- Q7.19 720: 722.7510208759086 [OK] -----

True

SOA Question 7.20 : (E) -277.23


For a fully discrete whole life insurance of 1000 on (35), you are given:
1. First year expenses are 30% of the gross premium plus 300
2. Renewal expenses are 4% of the gross premium plus 30
3. All expenses are incurred at the beginning of the policy year
4. Gross premiums are calculated using the equivalence principle
5. The gross premium policy value at the end of the first policy year is R
6. Using the Full Preliminary Term Method, the modified reserve at the end of the first policy year is S
7. Mortality follows the Standard Ultimate Life Table
8. i = 0.05
Calculate R−S .

life = SULT()
S = life.FPT_policy_value(35, t=1, b=1000) # is 0 for FPT at t=0,1
contract = Contract(benefit=1000,
initial_premium=.3,
initial_policy=300,
renewal_premium=.04,
renewal_policy=30)
G = life.gross_premium(A=life.whole_life_insurance(35), **contract.premium_terms)
R = life.gross_policy_value(35, t=1, contract=contract.set_contract(premium=G))
isclose(-277.23, R - S, question="Q7.20")

----- Q7.20 -277.23: -277.19303323929216 [OK] -----

True

SOA Question 7.21 : (D) 11866

life = SULT()
x, t, u = 55, 9, 10
P = life.gross_premium(IA=0.14743,
a=life.temporary_annuity(x, t=u),
(continues on next page)

22.7. 7 Policy Values 223


Solving Actuarial Math with Python

(continued from previous page)


A=life.deferred_annuity(x, u=u),
benefit=1000)
contract = Contract(initial_policy=life.term_insurance(x+t, t=1, b=10*P),
premium=P,
benefit=1000)
a = life.temporary_annuity(x+t, t=u-t)
A = life.deferred_annuity(x+t, u=u-t)
V = life.gross_future_loss(A=A, a=a, contract=contract)
isclose(11866, V, question="Q7.21")

----- Q7.21 11866: 11866.30158100453 [OK] -----

True

SOA Question 7.22 : (C) 46.24

life = PolicyValues().set_interest(i=0.06)
contract = Contract(benefit=8, premium=1.250)
def fun(A2):
return life.gross_variance_loss(A1=0, A2=A2, contract=contract)
A2 = [Link](fun, target=20.55, grid=20.55/8**2)
contract = Contract(benefit=12, premium=1.875)
var = life.gross_variance_loss(A1=0, A2=A2, contract=contract)
isclose(46.2, var, question="Q7.22")

----- Q7.22 46.2: 46.2375 [OK] -----

True

SOA Question 7.23 : (D) 233

life = Recursion().set_interest(i=0.04).set_p(0.995, x=25)


A = life.term_insurance(25, t=1, b=10000)
def fun(beta): # value of premiums in first 20 years must be equal
return beta * 11.087 + (A - beta)
beta = [Link](fun, target=216 * 11.087, grid=[140, 260])
isclose(233, beta, question="Q7.23")

Term Insurance 𝐴1𝑥+25∶1| ∗ 10000 ∶=


𝐴1𝑥+25∶1| = 𝐴𝑥+25∶1| − 𝐸𝑥+25 endowment insurance - pure
𝐸𝑥+25 = 𝑝𝑥+25 ∗ 𝑣 pure endowment

----- Q7.23 233: 232.64747466274176 [OK] -----

True

SOA Question 7.24 : (C) 680


For a fully discrete whole life insurance policy of 1,000,000 on (50), you are given:

224 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

1. The annual gross premium, calculated using the equivalence principle, is 11,800
2. Mortality follows the Standard Ultimate Life Table
3. i = 0.05
Calculate the expense loading, P for this policy.

life = SULT()
P = life.premium_equivalence(A=life.whole_life_insurance(50), b=1000000)
isclose(680, 11800 - P, question="Q7.24")

----- Q7.24 680: 680.291823645397 [OK] -----

True

SOA Question 7.25 : (B) 3947.37

life = SelectLife().set_interest(i=.04)\
.set_table(A={55: [.23, .24, .25],
56: [.25, .26, .27],
57: [.27, .28, .29],
58: [.20, .30, .31]})
V = life.FPT_policy_value(55, t=3, b=100000)
isclose(3950, V, question="Q7.25")

----- Q7.25 3950: 3947.3684210526353 [OK] -----

True

SOA Question 7.26 : (D) 28540


• backward = forward reserve recursion

x = 0
life = Recursion(verbose=False).set_interest(i=.05)\
.set_p(0.85, x=x)\
.set_p(0.85, x=x+1)\
.set_reserves(T=2, endowment=50000)
def benefit(k): return k * 25000
def fun(P): # solve P s.t. V is equal backwards and forwards
policy = dict(t=1, premium=P, benefit=benefit, reserve_benefit=True)
return life.t_V_backward(x, **policy) - life.t_V_forward(x, **policy)
P = [Link](fun, target=0, grid=[27650, 28730])
isclose(28540, P, question="Q7.26")

----- Q7.26 28540: 28542.392566782808 [OK] -----

True

SOA Question 7.27 : (B) 213

22.7. 7 Policy Values 225


Solving Actuarial Math with Python

x = 0
life = Recursion(verbose=False).set_interest(i=0.03)\
.set_q(0.008, x=x)\
.set_reserves(V={0: 0})
def fun(G): # Solve gross premium from expense reserves equation
return life.t_V(x=x, t=1, premium=G - 187, benefit=lambda t: 0,
per_policy=10 + 0.25*G)
G = [Link](fun, target=-38.70, grid=[200, 252])
isclose(213, G, question="Q7.27")

----- Q7.27 213: 212.970355987055 [OK] -----

True

SOA Question 7.28 : (D) 24.3

life = SULT()
PW = life.net_premium(65, b=1000) # 20_V=0 => P+W is net premium for A_65
P = life.net_premium(45, t=20, b=1000) # => P is net premium for A_45:20
isclose(24.3, PW - P, question="Q7.28")

----- Q7.28 24.3: 24.334725400123975 [OK] -----

True

SOA Question 7.29 : (E) 2270


For a fully discrete whole life insurance of B on (x), you are given:
1. Expenses, incurred at the beginning of each year, equal 30 in the first year and 5 in subsequent years
2. The net premium policy value at the end of year 10 is 2290
3. Gross premiums are calculated using the equivalence principle
4. i = 0.04
5. 𝑎𝑥̈ = 14.8
6. 𝑎𝑥+10
̈ = 11.4
Calculate 10 𝑉 𝑔 , the gross premium policy value at the end of year 10.

x = 0
life = Recursion(verbose=False).set_interest(i=0.04)\
.set_a(14.8, x=x)\
.set_a(11.4, x=x+10)
def fun(B):
return life.net_policy_value(x, t=10, b=B)
B = [Link](fun, target=2290, grid=2290*10) # Solve benefit B given net 10_V
contract = Contract(initial_policy=30, renewal_policy=5, benefit=B)
G = life.gross_premium(a=life.whole_life_annuity(x), **contract.premium_terms)
V = life.gross_policy_value(x, t=10, contract=contract.set_contract(premium=G))
isclose(2270, V, question="Q7.29")

226 Chapter 22. FAM-L Exam Solutions and Hints


Solving Actuarial Math with Python

----- Q7.29 2270: 2270.743243243244 [OK] -----

True

SOA Question 7.30 : (E) 9035


Ten years ago J, then age 25, purchased a fully discrete 10-payment whole life policy of 10,000.
All actuarial calculations for this policy were based on the following:
1. Mortality follows the Standard Ultimate Life Table
2. i = 0.05
3. The equivalence principle
In addition:
1. 𝐿10 is the present value of future losses random variable at time 10
2. At the end of policy year 10, the interest rate used to calculate 𝐿10 is changed to 0%
Calculate the increase in 𝐸[𝐿10 ] that results from this change.

b = 10000 # premiums=0 after t=10


L = SULT().set_interest(i=0.05).whole_life_insurance(x=35, b=b)
V = SULT().set_interest(i=0).whole_life_insurance(x=35, b=b)
isclose(9035, V - L, question="Q7.30")

----- Q7.30 9035: 9034.654127845053 [OK] -----

True

SOA Question 7.31 : (E) 0.310


For a fully discrete 3-year endowment insurance of 1000 on (x), you are given:
1. Expenses, payable at the beginning of the year, are:

Year(s) Percent of Premium Per Policy


1 20% 15
2 and 3 8% 5

2. The expense reserve at the end of year 2 is –23.64


3. The gross annual premium calculated using the equivalence principle is G = 368.
4. 𝐺 = 1000𝑃𝑥∶3| + 𝑃 𝑒 , where 𝑃 𝑒 is the expense loading
Calculate 𝑃𝑥∶3| .

x = 0
life = Reserves().set_reserves(T=3)
G = 368.05
def fun(P): # solve net premium expense reserve equation
return life.t_V(x, t=2, premium=G-P, benefit=lambda t:0, per_policy=5+0.08*G)
P = [Link](fun, target=-23.64, grid=[.29, .31]) / 1000
isclose(0.310, P, question="Q7.31")

22.7. 7 Policy Values 227


Solving Actuarial Math with Python

----- Q7.31 0.31: 0.309966 [OK] -----

True

SOA Question 7.32 : (B) 1.4


For two fully continuous whole life insurance policies on (x), you are given:

Death Benefit Annual Premium Rate Variance of the PV of Future Loss at t

Policy A 1 0.10 0.455


Policy B 2 0.16 -

• 𝛿 = 0.06
Calculate the variance of the present value of future loss at 𝑡 for Policy B.

life = PolicyValues().set_interest(i=0.06)
contract = Contract(benefit=1, premium=0.1)
def fun(A2):
return life.gross_variance_loss(A1=0, A2=A2, contract=contract)
A2 = [Link](fun, target=0.455, grid=0.455)
contract = Contract(benefit=2, premium=0.16)
var = life.gross_variance_loss(A1=0, A2=A2, contract=contract)
isclose(1.39, var, question="Q7.32")

----- Q7.32 1.39: 1.3848168384380901 [OK] -----

True

Final Score

from datetime import datetime


print([Link]())
print(isclose)

2023-08-08 14:57:24.992543
Elapsed: 5.9 secs
Passed: 136/136

228 Chapter 22. FAM-L Exam Solutions and Hints

You might also like