"""Woolhouse - 1/mthly insurance and annuities with Woolhouse's approximation
MIT License. Copyright 2022-2023 Terence Lim
"""
import math
from typing import Callable
from actuarialmath import Mthly
from actuarialmath import Annuity
[docs]class Woolhouse(Mthly):
"""1/m'thly shortcuts with Woolhouse approximation
Args:
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
"""
def __init__(self, m: int, life: Annuity, three_term: bool = False,
approximate_mu: Callable[[int, int], float] | bool = True):
super().__init__(m=m, life=life)
self.three_term = three_term # whether to include third term
self.approximate_mu = approximate_mu # how to approximate mu
[docs] def mu_x(self, x: int, s: int = 0) -> float:
"""Computes mu_x or calls approximate_mu for third term
Args:
x : age of selection
s : years after selection
"""
if self.approximate_mu is True: # approximate mu from integer ages
return -.5 * sum([math.log(self.life.p_x(x, s=s+t)) for t in [0,-1]])
elif self.approximate_mu is False:
return self.life.mu_x(x, s=s) # use exact mu from survival model
else: # apply custom function for mu
return self.approximate_mu(x, s)
[docs] def whole_life_insurance(self, x: int, s: int = 0, b: int = 1,
mu: float | None = 0.) -> float:
"""1/m'thly Woolhouse whole life insurance: A_x
Args:
x : age of selection
s : years after selection
b : amount of benefit
mu : value of mu at age x+s
"""
return b * self.insurance_twin(self.whole_life_annuity(x, s=s, mu=mu))
[docs] def term_insurance(self, x: int, s: int = 0, t: int = Annuity.WHOLE,
b: int = 1, mu: float | None = 0.,
mu1: float | None = None) -> float:
"""1/m'thly Woolhouse term insurance: A_x:t
Args:
x : year of selection
s : years after selection
t : term of insurance in years
b : amount of benefit
mu : value of mu at age x+s
mu1 : value of mu at age x+s+t
"""
A = self.whole_life_insurance(x, s=s, b=b, mu=mu)
if t < 0 or self.life.max_term(x+s, t) < t:
return A
E = self.E_x(x, s=s, t=t)
return A - E * self.whole_life_insurance(x, s=s+t, b=b, mu=mu1)
[docs] def endowment_insurance(self, x: int, s: int = 0, t: int = Annuity.WHOLE,
b: int = 1, endowment: int = -1,
mu: float | None = None) -> float:
"""1/m'thly Woolhouse term insurance: A_x:t
Args:
x : year of selection
s : years after selection
t : term of insurance in years
b : amount of benefit
endowment : amount of endowment
mu : value of mu at age x+s+t
"""
if endowment < 0:
endowment = b
E = self.E_x(x, s=s, t=t)
A = self.term_insurance(x, s=s, t=t, b=b, mu=mu)
return A + E * (b if endowment < 0 else endowment)
[docs] def deferred_insurance(self, x: int, s: int = 0, u: int = 0,
t: int = Annuity.WHOLE, b: int = 1,
mu: float | None = None,
mu1: float | None = None) -> float:
"""1/m'thly Woolhouse deferred insurance as discounted term or WL
Args:
x : year of selection
s : years after selection
u : number of years deferred
t : term of insurance in years
b : amount of benefit
mu : value of mu at age x+s+u
mu1 : value of mu at age x+s+u+t
"""
if self.life.max_term(x+s, u) < u:
return 0.
E = self.E_x(x, s=s, t=u)
return E * self.term_insurance(x, s=s+u, t=t, b=b, mu=mu, mu1=mu1)
[docs] def whole_life_annuity(self, x: int, s: int = 0, b: int = 1,
mu: float | None = None) -> float:
"""1/m'thly Woolhouse whole life annuity: a_x
Args:
x : year of selection
s : years after selection
b : amount of benefit
mu : value of mu at age x+s
Examples:
>>> life = Recursion().set_interest(i=0.05).set_a(3.4611, x=0)
>>> Woolhouse(m=4, life=life).whole_life_annuity(x=0)
"""
a = (self.life.whole_life_annuity(x, s=s, discrete=True) -
(self.m - 1)/(2 * self.m))
if self.three_term:
mu = mu or self.mu_x(x, s)
a -= (mu + self.life.interest.delta)*(self.m**2 - 1)/(12 * self.m**2)
return a * b
[docs] def temporary_annuity(self, x: int, s: int = 0, t: int = Annuity.WHOLE,
b: int = 1, mu: float | None = None,
mu1: float | None = None) -> float:
"""1/m'thly Woolhouse temporary life annuity: a_x
Args:
x : year of selection
s : years after selection
t : term of annuity in years
b : amount of benefit
mu : value of mu at age x+s
mu1 : value of mu at age x+s+t
"""
return self.deferred_annuity(x, s=s, t=t, b=b, mu=mu, mu1=mu1) # u=0
[docs] def deferred_annuity(self, x: int, s: int = 0, t: int = Annuity.WHOLE,
u: int = 0, b: int = 1, mu: float | None = None,
mu1: float | None = None) -> float:
"""1/m'thly Woolhouse deferred life annuity: a_x
Args:
x : year of selection
s : years after selection
u : years of deferral
t : term of annuity in years
mu : value of mu at age x+s+u
mu1 : value of mu at age x+s+u+t
"""
a_x = self.whole_life_annuity(x, s=s+u, mu=mu)
a_xt = self.whole_life_annuity(x, s=s+t+u, mu=mu1) if t > 0 else 0
a = self.E_x(x, s=s, t=u) * (a_x - self.E_x(x, s=s+u, t=t) * a_xt)
return a * b
if __name__ == "__main__":
from actuarialmath.sult import SULT
from actuarialmath.recursion import Recursion
from actuarialmath.udd import UDD
from actuarialmath.policyvalues import Contract
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=contract.renewals())
print(V)
contract = Contract(premium=30*12, renewal_premium=0.05)
V1 = life.gross_future_loss(a=a, contract=contract.renewals())
print(V, V1, V+V1)
print()
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(P):
return life.gross_future_loss(A=benefits + expenses, a=payments,
contract=Contract(premium=P))
P = life.solve(fun, target=-800, grid=[12110, 12550])
print(P)
print()
print("SOA Question 6.15: (B) 1.002")
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))
print()
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))
print()