Beispiel #1
0
def olb_r(loan: float, q: float, period: float, gr: Union[Accumulation, float,
                                                          Rate], t) -> float:
    """
    Calculates the outstanding loan balance using the retrospective method.

    :param loan: The loan amount.
    :type loan: float
    :param q: The payment amount.
    :type q: float
    :param period: The payment period.
    :type period: float
    :param gr: A growth rate object.
    :type gr: Accumulation, float, or Rate
    :param t: The valuation time.
    :type t: float
    :return: The outstanding loan balance.
    :rtype: float
    """

    ann = Annuity(period=period, term=t, gr=gr, amount=q)

    acc = Accumulation(gr=gr)
    olb = loan * acc.val(t) - ann.sv()

    return max(olb, 0)
Beispiel #2
0
def npv(
        payments: list,
        gr: Union[Accumulation, float, Rate]
) -> float:
    """
    Calculates the net present value for a stream of payments.

    :param payments: a list of :class:`Payment` objects.
    :type payments: list
    :param gr: a growth rate object, can be interest rate as a float, Accumulation object, or Rate
    :type gr: Accumulation, float, or Rate
    :return: the net present value
    :rtype: float
    """
    if isinstance(gr, Accumulation):
        acc = Accumulation
    elif isinstance(gr, float):
        acc = Accumulation(gr)
    elif isinstance(gr, Rate):
        i = gr.convert_rate(
            pattern="Effective Interest",
            freq=1
        )
        acc = Accumulation(i)
    else:
        raise Exception("Invalid type passed to gr.")

    discount_func = acc.discount_func

    factor_none = [x.discount_factor for x in payments].count(None)

    if (factor_none != len(payments)) and discount_func:
        warnings.warn("When discount factors are supplied with a discount function, "
                      "the discount function will override the discount factors.")

    if (factor_none != 0) and discount_func is None:
        raise Exception("There is at least one missing discount factor. "
                        "Either supply the missing factors or supply a discount function instead.")

    payment_amounts = [x.amount for x in payments]

    payment_times = [x.time for x in payments]

    if discount_func:
        factors = [discount_func(t) for t in payment_times]
    else:
        factors = [x.discount_factor for x in payments]

    res = sum([a * b for a, b in zip(payment_amounts, factors)])

    return res
Beispiel #3
0
def olb_p(q: float,
          period: float,
          term: float,
          gr: Union[Accumulation, float, Rate],
          t: float,
          r: float = None,
          missed: list = None) -> float:
    """
    Calculates the outstanding loan balance using the retrospective method.

    :param q: The payment amount.
    :type q: float
    :param period: The payment period.
    :type period: float
    :param term: The loan term, in years.
    :type term: float
    :param gr: A growth rate object.
    :type gr: Accumulation, float, or Rate.
    :param t: The valuation time, in years.
    :type t: float
    :param r: The final payment amount, if different from the others, defaults to None.
    :type r: float, optional
    :param missed: A list of missed payments, for example, 4th and 5th payments would be [4, 5].
    :type missed: list
    :return: The outstanding loan balance.
    :rtype: float
    """
    acc = Accumulation(gr=gr)

    if r is not None:
        ann = Annuity(period=period, term=term - t - period, gr=gr, amount=q)

        r_pv = r * acc.discount_func(term - t)

        olb = ann.pv() + r_pv

    else:
        ann = Annuity(period=min(period, term - t),
                      term=term - t,
                      gr=gr,
                      amount=q)

        olb = ann.pv()

    if missed:

        for p in missed:
            olb += q * acc.val(t - p)

    return olb
Beispiel #4
0
    def binomial_d(self, u, gr):
        rf_fac = Accumulation(gr=gr).val(self.t)
        vu = max(u - self.k, 0)
        c0 = self.c0
        s0 = self.s0

        return (c0 * u - vu * s0) / (c0 - vu / rf_fac)
Beispiel #5
0
def risk_neutral_price(s0, t, k, n, gr, u, d, nu, nd, period: Union[float, list], option):

    if isinstance(period, (int, float)):
        n_periods = t / period
        periods = [period] * floor(n_periods)
    else:
        n_periods = len(period)
        periods = period

    if nu + nd == n_periods:
        st = binomial_st(s0=s0, n=n, u=u, d=d, nu=nu, nd=nd)
        if option == 'call':
            return max(st - k * n, 0)
        elif option == 'put':
            return max(k * n - st, 0)
        else:
            raise ValueError("Invalid option type specified")
    else:
        t = periods[nu + nd]

        rf_factor = Accumulation(gr=gr).discount_func(t=t)

        p = risk_neutral_prob(s0=s0, t=t, gr=gr, u=u, d=d, period=t)

        vu = risk_neutral_price(s0=s0, n=n, k=k, t=t, gr=gr, u=u, d=d, nu=nu + 1, nd=nd, period=periods, option=option)
        vd = risk_neutral_price(s0=s0, n=n, k=k, t=t, gr=gr, u=u, d=d, nu=nu, nd=nd + 1, period=periods, option=option)
        return p * vu * rf_factor + (1 - p) * vd * rf_factor
Beispiel #6
0
def risk_neutral_prob(t, s0, gr, u, d, nu=0, nd=0, period=None):
    if period is None:
        period = t
    s0 = s0 * (1 + u) ** nu * (1 - d) ** nd

    rf_factor = Accumulation(gr=gr).discount_func(t=period)

    return (s0 - s0 * (1-d) * rf_factor) / ((s0 * (1 + u) - s0 * (1 - d)) * rf_factor)
Beispiel #7
0
def npv_solver(
        npval: float = None,
        payments: list = None,
        gr: Union[Accumulation, float, Rate] = None
):
    """
    An experimental net present value solver. Finds a missing component given a stream of payments and net present \
    value. For example, if the NPV is absent, but the rest of the payments are fully defined, this function \
    returns the NPV. If the NPV is provided, but one aspect of a payment (such as a payment value), this function \
    is planned to solve for that value.

    This function is still incomplete, please use with caution.

    :param npval: The net present value.
    :type npval: float
    :param payments: A list of payments.
    :type payments: list
    :param gr: A growth rate object.
    :type gr: Callable
    :return: Returns either the npv, a missing payment amount, a missing time of payment, or missing discount factor.
    :rtype: float
    """

    args = [npval, payments, gr]
    if args.count(None) > 1:
        raise Exception("Only one argument can be missing.")

    if gr:
        gr = standardize_rate(gr)
        acc = Accumulation(gr=gr)

    # exclude missing payment

    payments_excl_missing = [x for x in payments if x.time is not None]
    missing_pmt = [x for x in payments if x.time is None].pop()
    payments_excl_missing_npv = npv(payments=payments_excl_missing, gr=gr)

    missing_pmt_pv = npval - payments_excl_missing_npv
    res = np.log(missing_pmt.amount / missing_pmt_pv) / np.log(acc.discount_func(1) ** -1)

    return res
Beispiel #8
0
def binomial_f(n, s0, t, k, u, d, nu, nd, gr, period, option):

    if nu + nd > t / period - 1:
        raise ValueError("Steps exceed option length.")

    rf_factor = Accumulation(gr=gr).discount_func(t=period)

    vu = binomial_node(s0=s0, n=n, u=u, d=d, t=t, k=k, nu=nu + 1, nd=nd, gr=gr, period=period, option=option)
    vd = binomial_node(s0=s0, n=n, u=u, d=d, t=t, k=k, nu=nu, nd=nd + 1, gr=gr, period=period, option=option)

    su = binomial_st(n=n, s0=s0, u=u, d=d, nu=nu + 1, nd=nd)
    sd = binomial_st(n=n, s0=s0, u=u, d=d, nu=nu, nd=nd + 1)

    return rf_factor * (su * vd - sd * vu) / (su - sd)
Beispiel #9
0
 def binomial_price(self, u, d, gr):
     rf_fac = Accumulation(gr=gr).val(self.t)
     vd = max(d - self.k, 0)
     vu = max(u - self.k, 0)
     c0 = (u * vd - d * vu) / ((u - d) * rf_fac) + (vu - vd) / (u - d) * self.s0
     return c0
Beispiel #10
0
def get_loan_pmt(loan_amt: float,
                 period: float,
                 term: float,
                 gr: Union[float, Rate, TieredTime],
                 imd: str = 'immediate',
                 gprog: float = 0.0,
                 aprog: float = 0.0,
                 cents=False) -> dict:
    """
    Returns the loan payment schedule, given a loan amount, payment period, term, growth rate, and geometric or
    arithmetic progression of payments. If cents is set to True, uses the Daniel and Vaaler rounding algorithm
    to round each payment to cents, modifying the final payment such that there is no over/under payment of the
    loan due to rounding issues.

    :param loan_amt: The loan amount to be repaid.
    :type loan_amt: float
    :param period: The payment frequency, per fraction of a year.
    :type period: float
    :param term: The term of the loan, in years.
    :type term: float
    :param gr: Some kind of growth rate object specifying the interest rate
    :type gr: Accumulation, Callable, float, Rate
    :param imd: 'immediate' or 'due'. Whether the payments occur at the end or beginning of each period, defaults to \
    'immediate'.
    :type imd: str
    :param gprog: geometric progression, payments grow at a % of the previous payment per period, defaults to 0.
    :type gprog: float
    :param aprog: arithmetic progression, payments grow by a constant amount each period, defaults to 0.
    :type aprog: float
    :param cents: Whether you want payments rounded to cents.
    :type cents: bool
    :return: a dictionary of payment amounts along with the times of the payments
    :rtype: dict
    """
    ann = Annuity(period=period, term=term, gr=gr, gprog=gprog, imd=imd)

    if aprog >= 0:
        i = ann.gr.effective_interval(t2=ann.period)
        n = ann.n_payments
        v = ann.gr.discount_func(period)
        pmt = (loan_amt - (aprog / i) * (ann.pv() - n * v**n)) / (ann.pv())
        iann = Annuity(amount=pmt,
                       period=period,
                       term=term,
                       gr=gr,
                       aprog=aprog,
                       imd=imd)
        pmts = iann.amounts
    else:
        pmt = loan_amt / ann.pv()
        pmts = [pmt * x for x in ann.amounts]

    times = ann.times

    pmts_dict = {'times': times, 'amounts': pmts}

    if cents:

        acc = Accumulation(gr=gr)

        pmt_round = round(pmt, 2)

        pv = Annuity(amount=pmt_round,
                     period=period,
                     term=term,
                     gr=gr,
                     gprog=gprog,
                     imd=imd).pv()

        if loan_amt == round(pv, 2):

            return pmts_dict

        else:

            cent = decimal.Decimal('0.01')

            pmt_round2 = float(
                decimal.Decimal(pmt).quantize(cent, rounding=decimal.ROUND_UP))

            d_ann = Annuity(amount=pmt_round2,
                            period=period,
                            term=term,
                            gr=gr,
                            gprog=gprog,
                            aprog=aprog,
                            imd=imd)

            diff = d_ann.pv() - loan_amt

            last_pmt = round(
                d_ann.amounts[-1] - round(diff * acc.val(t=term), 2), 2)

            pmts = [round(x, 2) for x in d_ann.amounts[:-1]]
            pmts.append(last_pmt)

            pmts_dict = {'times': times, 'amounts': pmts}

    return pmts_dict