Source code for actuarialmath.interest

"""Interest Theory - Applies interest rate formulas

MIT License. Copyright (c) 2022-2023 Terence Lim
"""
import math
import numpy as np
import pandas as pd
from typing import Callable
from actuarialmath import Actuarial

[docs]class Interest(Actuarial): """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 """ def __init__(self, i: float = -1., delta: float = -1., d: float = -1., v: float = -1., i_m: float = -1., d_m: float = -1., m: int = 0, v_t: Callable[[float], float] | None = None): if i_m >= 0: # given interest rate mthly compounded i = self.mthly(m=m, i_m=i_m) if d_m >= 0: # given discount rate mthly compounded d = self.mthly(m=m, d_m=d_m) if v_t is None: if delta >= 0: # given continously-compounded rate self._i = math.exp(delta) - 1 elif d >= 0: # given annual discount rate self._i = d / (1 - d) elif v >= 0 : # given annual discount factor self._i = (1 / v) - 1 elif i >= 0: self._i = i else: # given annual interest rate raise Exception("non-negative interest rate not given") self._v_t = lambda t: self._v**t self._v = 1 / (1 + self._i) # store discount factor self._d = self._i / (1 + self._i) # store discount rate self._delta = math.log(1 + self._i) # store continuous rate else: # given discount function assert callable(v_t), "v_t must be a callable discount function" assert v_t(0) == 1, "v_t(t=0) must equal 1" self._v_t = v_t #self._i = (1 / v_t(1)) - 1 self._v = self._d = self._i = self._delta = None @property def i(self) -> float: """effective annual interest rate""" return self._i @property def d(self) -> float: """annual discount rate of interest""" return self._d @property def delta(self) -> float: """continuously compounded interest rate, or force of interest, per year""" return self._delta @property def v(self) -> float: """annual discount factor""" return self._v @property def v_t(self) -> Callable: """discount factor as a function of time""" return self._v_t
[docs] def annuity(self, t: int = -1, m: int = 1, due: bool = True) -> float: """Compute value of the annuity certain factor Args: t : number of years of payments m : m'thly frequency of payments (0 for continuous payments) due : whether annuity due (True) or immediate (False) Examples: >>> print(interest.annuity(t=10, due=False), 2.831059) """ v_t = 0 if t < 0 else self.v**t # is t finite assert m >= 0, "mthly frequency must be non-negative" if m == 0: # if continuous return (1 - v_t) / self.delta elif due: # if annuity due return (1 - v_t) / self.mthly(m=m, d=self.d) else: # if annuity immediate return (1 - v_t) / self.mthly(m=m, i=self.i)
[docs] @staticmethod def mthly(m: int = 0, i: float = -1, d: float = -1, i_m: float = -1, d_m: float = -1) -> float: """Convert to or from m'thly interest rates Args: m : m'thly frequency i : an annual-pay interest rate, to convert to m'thly d : or annual-pay discount rate, to convert to m'thly i_m : or m'thly interest rate, to convert to annual pay d_m : or m'thly discount rate, to convert to annual pay Examples: >>> i = Interest.mthly(i_m=0.05, m=12) >>> print("Convert mthly to annual-pay:", i) >>> print("Convert annual-pay to mthly:", Interest.mthly(i=i, m=12)) """ assert m >= 0, "mthly frequency must be non-negative" if i > 0: return m * ((1 + i)**(1 / m) - 1) if m else math.log(1+i) elif d > 0: return m * (1 - (1 - d)**(1 / m)) if m else -math.log(1-d) elif i_m > 0: return (1 + (i_m / m))**m - 1 elif d_m > 0: return 1 - (1 - (d_m / m))**m else: raise Exception("no interest rate given to mthly")
[docs] @staticmethod def double_force(i: float = -1., delta: float = -1., d: float = -1., v: float = -1.): """Double the force of interest Args: i : interest rate to double force of interest d : or discount rate v : or discount factor delta : or continuous rate Returns: interest rate, of same form as input rate, after doubling force of interest Examples: >>> print("Double force of interest of i =", Interest.double_force(i=0.05)) >>> print("Double force of interest of d =", Interest.double_force(d=0.05)) """ if delta >= 0: return 2 * delta elif v >= 0: return v**2 elif d >= 0: return 2 * d - (d**2) elif i >= 0: return 2 * i + (i**2) else: raise Exception("no interest rate for double_force")
if __name__ == "__main__": print("SOA Question 3.10: (C) 0.86") interest = Interest(v=0.75) L = 35 * interest.annuity(t=4, due=False) + 75 * interest.v_t(t=5) interest = Interest(v=0.5) R = 15 * interest.annuity(t=4, due=False) + 25 * interest.v_t(t=5) ans = L / (L + R) print(ans) print("Double the force of interest") i = 0.05 i2 = Interest.double_force(i=i) print(i2) print("Convert interest to discount rate") d2 = Interest(i=i2).d print(d2) print("Convert mthly to annual-pay:") i = Interest.mthly(i_m=0.05, m=12) print(i) print("Convert annual-pay to mthly:") i_m = Interest.mthly(i=i, m=12) print(i_m)