"""SULT - Loads and uses a standard ultimate life table
MIT License. Copyright 2022-2023 Terence Lim
"""
import math
import numpy as np
import pandas as pd
from typing import Dict, Callable
from actuarialmath import LifeTable
# Makeham's Law parameters from SOA’s Excel Workbook for FAM-L Tables
_A, _B, _c = 0.00022, 0.0000027, 1.124
def _faml_sult(x, t: float) -> float:
return math.exp(-_A*t - (_B*_c**x*(_c**t - 1)) / math.log(_c))
[docs]class SULT(LifeTable):
"""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)
"""
def __init__(self, i: float = 0.05, radix: int = 100000,
S: Callable[[float, float], float] = _faml_sult,
minage: int = 20, maxage: int = 130, **kwargs):
"""Construct SULT"""
super().__init__(**kwargs)
l = {t+minage: radix * S(minage, t) for t in range(1+maxage-minage)}
self.set_interest(i=i).set_table(l=l, minage=minage, maxage=maxage)
[docs] def __getitem__(self, col: str) -> Dict[int, float]:
"""Returns a column of the sult table
Args:
col : name of life table column to return
"""
funs = {'q': (self.q_x, dict()),
'p': (self.p_x, dict()),
'a': (self.whole_life_annuity, dict()),
'A': (self.whole_life_insurance, dict())}
assert col[0].lower() in funs, f"must be one of {list(funs.keys())}"
f, args = funs[col[0]]
return {x: f(x, **args) for x in range(self._MINAGE, self._MAXAGE)}
[docs] def frame(self, minage: int = 20, maxage: int = 100):
"""Derive FAM-L exam table columns of SULT as a DataFrame
Args:
minage : first age to display row
maxage : large age to display row
"""
# specify methods and arguments for computing columns of FAM-L exam table
funs = {'q_x': (self.q_x, dict()),
'a_x': (self.whole_life_annuity, dict()),
'A_x': (self.whole_life_insurance, dict()),
'2A_x': (self.whole_life_insurance, dict(moment=2)),
'a_x:10': (self.temporary_annuity, dict(t=10)),
'A_x:10': (self.endowment_insurance, dict(t=10)),
'a_x:20': (self.temporary_annuity, dict(t=20)),
'A_x:20': (self.endowment_insurance, dict(t=20)),
'5_E_x': (self.E_x, dict(t=5)),
'10_E_x': (self.E_x, dict(t=10)),
'20_E_x': (self.E_x, dict(t=20))}
t = {col: {x: f(x, **args) for x in range(self._MINAGE, self._MAXAGE)}
for col, (f, args) in funs.items()}
tab = pd.DataFrame(dict(l_x=self._table['l'])).sort_index()
tab = tab.join(pd.DataFrame.from_dict(t).set_index(tab.index[:-1]))
for digits, col in zip([1, 6, 4, 5, 5, 4, 5, 4, 5, 5, 5, 5], tab.columns):
tab[col] = tab[col].map(f"{{:.{digits}f}}".format)
return tab.loc[minage:maxage]
if __name__ == "__main__":
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)
print()
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)
print()
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)
print()
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)
print()
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)
print()
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))
print()
print("SOA Question 5.8: (C) 0.92118")
sult = SULT()
a = sult.certain_life_annuity(55, u=5)
print(sult.p_x(55, t=math.floor(a)))
print()
print("SOA Question 5.3: (C) 6.239")
sult = SULT()
t = 10.5
print(t * sult.E_r(40, t=t))
print()
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))
print()
print("SOA Question 4.14: (E) 390000 ")
sult = SULT()
p = sult.p_x(60, t=85-60)
mean = sult.bernoulli(p)
var = sult.bernoulli(p, variance=True)
F = sult.portfolio_percentile(mean=mean, variance=var, prob=.86, N=400)
print(F * 5000 * sult.interest.v_t(85-60))
print()
from actuarialmath.interest import Interest
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)
print("SOA Question 3.9: (E) 3850")
sult = SULT()
p1 = sult.p_x(20, t=25)
p2 = sult.p_x(45, t=25)
mean = sult.bernoulli(p1) * 2000 + sult.bernoulli(p2) * 2000
var = (sult.bernoulli(p1, variance=True) * 2000
+ sult.bernoulli(p2, variance=True) * 2000)
print(sult.portfolio_percentile(mean=mean, variance=var, prob=.99))
print()
print("SOA Question 3.8: (B) 1505")
sult = SULT()
p1 = sult.p_x(35, t=40)
p2 = sult.p_x(45, t=40)
mean = sult.bernoulli(p1) * 1000 + sult.bernoulli(p2) * 1000
var = (sult.bernoulli(p1, variance=True) * 1000
+ sult.bernoulli(p2, variance=True) * 1000)
print(sult.portfolio_percentile(mean=mean, variance=var, prob=.95))
print()
print("SOA Question 3.4: (B) 815")
sult = SULT()
mean = sult.p_x(25, t=95-25)
var = sult.bernoulli(mean, variance=True)
print(sult.portfolio_percentile(N=4000, mean=mean, variance=var, prob=.1))
print()
print("Standard Ultimate Life Table at i=0.05")
print(sult.frame())
print()