Source code for dcf.plans

# -*- coding: utf-8 -*-

# dcf
# ---
# A Python library for generating discounted cashflows.
#
# Author:   sonntagsgesicht
# Version:  1.0, copyright Monday, 14 October 2024
# Website:  https://github.com/sonntagsgesicht/dcf
# License:  Apache License 2.0 (see LICENSE file)


from math import log

from curves.numerics import solve, EPS

DEFAULT_AMOUNT = 1.
FIXED_RATE = 0.01


[docs] def same(num, amount=DEFAULT_AMOUNT): r""" all same payment plan :param num: number of payments $n$ :param amount: amount of each payment $N$ :return: list(float) payment plan $X_i$ for $i=1 \dots n$ Payment plan with $$X_i = N \text{ for all } i=1 \dots n$$ """ return [amount] * int(num)
[docs] def bullet(num, amount=DEFAULT_AMOUNT): r""" bullet payment plan :param num: number of payments $n$ :param amount: amount of last bullet payment $N$ :return: list(float) payment plan $X_i$ for $i=1 \dots n$ Payment plan with $$X_i = N \text{ for } i=n \text{ else } 0$$ """ return [0.] * (int(num) - 1) + [amount]
[docs] def amortize(num, amount=DEFAULT_AMOUNT): r""" linear amortize payment plan :param num: number of payments $n$ :param amount: amount of total sum of payment $N$ :return: list(float) payment plan $X_i$ for $i=1 \dots n$ Payment plan with $$X_i = N/n \text{ for } i=1 \dots n$$ """ return [amount / num] * int(num)
def _annuity(num, amount=DEFAULT_AMOUNT, fixed_rate=FIXED_RATE): r""" fixed rate annuity payment plan :param num: number of payments $n$ :param amount: amount of total sum of payment $N$ :param fixed_rate: amortization rate $r$ :return: list(float) payment plan $X_i$ for $i=1 \dots n$ Payment plan $$X_i = \frac{r}{(1 + r)^{n-i}} \cdot N \text{ for } i=1 \dots n$$ """ q = 1. + fixed_rate a = amount * (q - 1) / (q ** int(num) - 1) return list(a * q ** i for i in range(int(num)))
[docs] def consumer(num, amount=DEFAULT_AMOUNT, fixed_rate=FIXED_RATE): r""" consumer loan annuity payment plan :param num: number of payments $n$ :param amount: amount of payment total $N$ :param fixed_rate: amortization rate $r$ :return: list(float) payment plan $X_i$ for $i=1 \dots n$ Actutal payment plan total $T = N (1 + n \cdot r)$ such that $$X_i = T / n$$ """ total = amount * (1 + num * fixed_rate) return [total / num] * int(num)
[docs] def iam(num, amount=DEFAULT_AMOUNT, fixed_rate=FIXED_RATE): q = 1 + fixed_rate return [amount * fixed_rate * (q ** i) for i in range(num)]
[docs] def outstanding(plan, amount=DEFAULT_AMOUNT, sign=False): r"""sums up plans to remaining oustanding anmount :param plan: payment plan $X_i$ :param amount: inital amount $N$ :param sign: $\sigma$ sign of plan payments (optional, default: **-1**) :return: list(float) outstanding plan Adds or substracts payment plan payments $X_i$ from inital amount $N$ such that $$O_i = N + \sigma \cdot \sum_{k=1}^{i-1} X_i$$ """ sgn = 1 if sign else -1 out = [amount] for p in plan[:-1]: amount += sgn * p out.append(amount) return out
[docs] def annuity(num=None, amount=None, fixed_rate=None, *, redemption_rate=None, annuity_amount=None): f"""list of redemption payments in fixed annuity payments :param num: number of periods (optional: if not given **num** will be derived from **fixed_rate** and **redemption_rate**) :param amount: total amount (optional: if neither **amount** nor **annuity_amount** is given, **amount** will be {DEFAULT_AMOUNT} is used) :param fixed_rate: interest rate per period (optional: if not given **fixed_rate** will be derived from **num** and **redemption_rate**. if even **num** or **redemption_rate** are not given, **fixed_rate** will be {FIXED_RATE}) :param redemption_rate: initial redemption rate (optional: if not given **redemption_rate** will be derived from **num** and **fixed_rate**) :param annuity_amount: fixed annuity amount :return: list of redemption payments """ def _r(x, n=num): """redemption_rate""" return x / ((x + 1) ** int(n) - 1) if num is not None: if redemption_rate is not None: if fixed_rate is None: fixed_rate = solve(_r, a=FIXED_RATE) elif abs(_r(fixed_rate) - redemption_rate) < EPS: msg = "inconsistent arguments for " \ "'num', 'fixed_rate' and 'redemption_rate'" raise ValueError(msg) elif fixed_rate is not None: redemption_rate = _r(fixed_rate, num) else: fixed_rate = FIXED_RATE redemption_rate = _r(fixed_rate, num) elif fixed_rate is not None: if redemption_rate is not None: num = log(1 + fixed_rate / redemption_rate) / log(1 + fixed_rate) elif amount is not None and annuity_amount is not None: redemption_rate = annuity_amount / amount - fixed_rate num = log(1 + fixed_rate / redemption_rate) / log(1 + fixed_rate) elif amount is not None and annuity_amount is not None: fixed_rate = annuity_amount / amount - redemption_rate num = log(1 + fixed_rate / redemption_rate) / log(1 + fixed_rate) else: msg = "no arguments for 'num', 'fixed_rate' and 'redemption_rate'" raise ValueError(msg) if amount is None and annuity_amount is None: amount = DEFAULT_AMOUNT elif amount is None: amount = annuity_amount / (fixed_rate + redemption_rate) return _annuity(int(num), amount, fixed_rate)