Source code for actuarialmath.constantforce

"""Constant Force of Mortality - shortcut formulas

MIT License. Copyright 2022-2023 Terence Lim
"""
import math
from scipy.stats import norm
from actuarialmath import MortalityLaws

[docs]class ConstantForce(MortalityLaws): """Constant force of mortality - memoryless exponential distribution of lifetime Args: mu : constant value of force of mortality 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 """ def __init__(self, mu: float, **kwargs): super().__init__(**kwargs) def _mu(x: int, s: float) -> float: """Constant force of mortality""" return mu def _S(x: int, s, t: float) -> float: """Shortcut for survival function with constant force of mortality""" return math.exp(-mu * t) self.set_survival(mu=_mu, S=_S) self.mu_ = mu # store mu parameter
[docs] def e_x(self, x: int, s: int = 0, t: int = MortalityLaws.WHOLE, curtate: bool = False, moment: int = 1) -> float: """Expected lifetime E[T_x] is memoryless: does not depend on (x) Args: x : age of selection s : years after selection t : limited at year t curtate : whether curtate (True) or continuous (False) lifetime moment : first (1) or second (2) moment """ if not curtate: # Var[Tx] = 1/mu^2, E[Tx] = 1/mu if moment == MortalityLaws.VARIANCE: return 1. / self.mu_**2 # shortcut elif moment == 1: e = 1 / self.mu_ # infinite n case shortcut if t >= 0: # n-limited from recursion e_x=e_x|n + n_p_x e_x e *= (1 - math.exp(-self.mu_ * t)) return e return super().e_x(x, s=s, t=t, curtate=curtate, moment=moment)
[docs] def E_x(self, x: int, s: int = 0, t: int = MortalityLaws.WHOLE, endowment: int = 1, moment: int = 1) -> float: """Shortcut for pure endowment: does not depend on age x Args: x : age of selection s : years after selection t : term of pure endowment endowment : amount of pure endowment moment : compute first or second moment """ if t == 0: return 1. if t < 0: return 0. delta = moment * self.interest.delta # multiply force of interest return math.exp(-(self.mu_ + delta) * t) * endowment**moment
[docs] def whole_life_annuity(self, x: int, s: int = 0, b: int = 1, variance: bool = False, discrete: bool = True) -> float: """Shortcut for whole life annuity: does not depend on age x Args: x : age of selection s : years after selection b : annuity benefit amount variance : return APV (True) or variance (False) discrete : annuity due (True) or continuous (False) """ if variance: # short cut for variance of temporary life annuity A1 = self.whole_life_insurance(x, s=s, discrete=discrete) A2 = self.whole_life_insurance(x, s=s, moment=2, discrete=discrete) return (b**2 * (A2 - A1**2) / (self.interest.d if discrete else self.interest.delta)**2) if not discrete: den = self.mu_ + self.interest.delta return (1. / den) * b if den > 0 else math.inf return super().whole_life_annuity(x, s=s, b=b, discrete=discrete)
[docs] def temporary_annuity(self, x: int, s: int = 0, t: int = MortalityLaws.WHOLE, b: int = 1, variance: bool = False, discrete: bool = True) -> float: """Shortcut for temporary life annuity: does not depend on age x Args: x : age of selection s : years after selection t : term of annuity in years b : annuity benefit amount variance : return APV (True) or variance (False) discrete : annuity due (True) or continuous (False) """ interest = self.interest.d if discrete else self.interest.delta if variance: # short cut for variance of temporary life annuity A1 = self.endowment_insurance(x, s=s, t=t, discrete=discrete) A2 = self.endowment_insurance(x, s=s, t=t, moment=2, discrete=discrete) return (b**2 * (A2 - A1**2) / (self.interest.d if discrete else self.interest.delta)**2) if not discrete: a = b * 1/(self.mu_ + self.interest.delta) if t < 0: return a else: return a * (1 - math.exp(-(self.mu_ + self.interest.delta)*t)) return super().temporary_annuity(x, s=s, b=b, t=t, discrete=discrete)
[docs] def whole_life_insurance(self, x: int, s: int = 0, moment: int = 1, b: int = 1, discrete: bool = True) -> float: """Shortcut for APV of whole life: does not depend on age x Args: x : age of selection s : years after selection b : amount of benefit moment : compute first or second moment discrete : benefit paid year-end (True) or moment of death (False) """ if moment > 0 and not discrete: delta = moment * self.interest.delta # multiply force of interest return self.mu_ / (self.mu_ + delta) if self.mu_ > 0 else 0. return super().whole_life_insurance(x, s=s, moment=moment, b=b, discrete=discrete)
[docs] def term_insurance(self, x: int, s: int = 0, t: int = 1, b: int = 1, moment: int = 1, discrete: bool = True) -> float: """Shortcut for APV of term life: does not depend on age x Args: x : age of selection s : years after selection t : term of insurance b : amount of benefit moment : compute first or second moment discrete : benefit paid year-end (True) or moment of death (False) """ if moment > 0 and not discrete: delta = moment * self.interest.delta # multiply force of interest A = b**moment * self.mu_/(self.mu_ + delta) if t < 0: return A else: return A * (1 - math.exp(-(self.mu_ + delta)*t)) return super().term_insurance(x, s=s, t=t, b=b, moment=moment, discrete = discrete)
[docs] def Z_t(self, x: int, prob: float, discrete: bool = True) -> float: """Shortcut for T_x (or K_x) given survival probability for insurance Args: x : age (does not depend) prob : desired probability threshold discrete : benefit paid year-end (True) or moment of death (False) """ t = -math.log(prob) / self.mu_ return math.floor(t) if discrete else t # opposite of annuity
[docs] def Y_t(self, x: int, prob: float, discrete: bool = True) -> float: """Shortcut for T_x (or K_x) given survival probability for annuity Args: x : age (does not depend) prob: desired probability threshold discrete : continuous (False) or annuity due (True) """ t = -math.log(1 - prob) / self.mu_ return math.ceil(t) if discrete else t # opposite of insurance
if __name__ == "__main__": 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) print(a,A) def fun(R): return life.gross_premium(a=a, A=A, initial_premium=R/4500, renewal_premium=R/4500, benefit=100000) R = life.solve(fun, target=4500, grid=[400, 800]) print(R) print() 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) print() print("SOA Question 6.27: (D) 10310") 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)) print(benefits, payments) print(life.term_insurance(x, t=20), life.deferred_insurance(x, u=20)) P = benefits / payments print(P) print() 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) print() 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 print()