Source code for cashflows.tvmm

"""
Time value of money models
===============================================================================

Overview
-------------------------------------------------------------------------------

The functions in this module are used for certain compound interest calculations
for a cashflow under the following restrictions:

* Payment periods coincide with the compounding periods.
* Payments occur at regular intervals.
* Payments are a constant amount.
* Interest rate is the same over all analysis period.

For convenience of the user, this module implements the following simplified
functions:

* ``pvfv``: computes the missing value in the equation
  ``fval = pval * (1 + rate) ** nper``, that represents the following
  cashflow.

.. image:: ./images/pvfv.png
    :width: 350px
    :align: center

* ``pvpmt``: computes the missing value (`pmt`, `pval`, `nper`, `nrate`) in a
  model relating a present value and
  a finite sequence of payments made at the end of the period (payments
  in arrears, or ordinary annuities), as indicated in the following cashflow
  diagram:

.. image:: ./images/pvpmt.png
    :width: 350px
    :align: center

* ``pmtfv``: computes the missing value (`pmt`, `fval`, `nper`, `nrate`) in a
  model relating a finite sequence  of payments in advance (annuities due)
  and a future value, as indicated in the following diagram:

.. image:: ./images/pmtfv.png
    :width: 350px
    :align: center

* ``tvmm``: computes the missing value (`pmt`, `fval`, `pval, `nper`, `nrate`)
  in a model relating a finite sequence  of payments made at the beginning or at
  the end of the period, a present value, a future value, and an interest rate,
  as indicated in the following diagram:

.. image:: ./images/tvmm.png
    :width: 650px
    :align: center

In addition, the function ``amortize`` computes and returns the amortization
schedule of a loan.

Functions in this module
-------------------------------------------------------------------------------

"""

import numpy
from cashflows.common import _vars2list


[docs]def tvmm(pval=None, fval=None, pmt=None, nrate=None, nper=None, due=0, pyr=1, noprint=True): """Computes the missing argument (set to ``None``) in a model relating the present value, the future value, the periodic payment, the number of compounding periods and the nominal interest rate in a cashflow. Args: pval (float, list): Present value. fval (float, list): Future value. pmt (float, list): Periodic payment. nrate (float, list): Nominal interest rate per year. nper (int, list): Number of compounding periods. due (int): When payments are due. pyr (int, list): number of periods per year. noprint (bool): prints enhanced output Returns: Argument set to None in the function call. **Details** The ``tvmmm`` function computes and returns the missing value (``pmt``, ``fval``, ``pval``, ``nper``, ``nrate``) in a model relating a finite sequence of payments made at the beginning or at the end of each period, a present value, a future value, and a nominal interest rate. The time intervals between consecutive payments are assumed to be equal. For internal computations, the effective interest rate per period is calculated as ``nrate / pyr``. **Examples** This example shows how to find different values for a loan of 5000, with a monthly payment of 130 at the end of the month, a life of 48 periods, and a interest rate of 0.94 per month (equivalent to a nominal interest rate of 11.32%). For a loan, the future value is 0. * Monthly payments: Note that the parameter ``pmt`` is set to ``None``. >>> tvmm(pval=5000, nrate=11.32, nper=48, fval=0, pyr=12) # doctest: +ELLIPSIS -130.00... When the parameter ``noprint`` is set to ``False``, a user friendly table with the data computed by the function is print. >>> tvmm(pval=5000, nrate=11.32, nper=48, fval=0, pyr=12, noprint=False) # doctest: +ELLIPSIS Present Value: ....... 5000.00 Future Value: ........ 0.00 Payment: ............. -130.01 Due: ................. END No. of Periods: ...... 48.00 Compoundings per Year: 12 Nominal Rate: ....... 11.32 Effective Rate: ..... 11.93 Periodic Rate: ...... 0.94 * Future value: >>> tvmm(pval=5000, nrate=11.32, nper=48, pmt=pmt, fval=None, pyr=12) # doctest: +ELLIPSIS -0.0... * Present value: >>> tvmm(nrate=11.32, nper=48, pmt=pmt, fval = 0.0, pval=None, pyr=12) # doctest: +ELLIPSIS 5000... All the arguments support lists as inputs. When a argument is a list and the ``noprint`` is set to ``False``, a table with the data is print. >>> tvmm(pval=[5, 500, 5], nrate=11.32, nper=48, fval=0, pyr=12, noprint=False) # doctest: +ELLIPSIS # pval fval pmt nper nrate erate prate due ------------------------------------------------------ 0 5.00 0.00 -0.13 48.00 11.32 11.93 0.94 END 1 500.00 0.00 -0.13 48.00 11.32 11.93 0.94 END 2 5.00 0.00 -0.13 48.00 11.32 11.93 0.94 END * Interest rate: >>> tvmm(pval=5000, nper=48, pmt=pmt, fval = 0.0, pyr=12) # doctest: +ELLIPSIS 11.32... * Number of periods: >>> tvmm(pval=5000, nrate=11.32/12, pmt=pmt, fval=0.0) # doctest: +ELLIPSIS 48.0... """ #pylint: disable=too-many-arguments #pylint: disable=too-many-branches numnone = 0 numnone += 1 if pval is None else 0 numnone += 1 if fval is None else 0 numnone += 1 if nper is None else 0 numnone += 1 if pmt is None else 0 numnone += 1 if nrate is None else 0 if numnone > 1: raise ValueError('One of the params must be set to None') if numnone == 0: pmt = None if pmt == 0.0: pmt = 0.0000001 nrate = numpy.array(nrate) if pval is None: result = numpy.pv(rate=nrate/100/pyr, nper=nper, fv=fval, pmt=pmt, when=due) elif fval is None: result = numpy.fv(rate=nrate/100/pyr, nper=nper, pv=pval, pmt=pmt, when=due) elif nper is None: result = numpy.nper(rate=nrate/100/pyr, pv=pval, fv=fval, pmt=pmt, when=due) elif pmt is None: result = numpy.pmt(rate=nrate/100/pyr, nper=nper, pv=pval, fv=fval, when=due) else: result = numpy.rate(pv=pval, nper=nper, fv=fval, pmt=pmt, when=due) * 100 * pyr if noprint is True: if isinstance(result, numpy.ndarray): return result.tolist() return result nrate = nrate.tolist() if pval is None: pval = result elif fval is None: fval = result elif nper is None: nper = result elif pmt is None: pmt = result else: nrate = result params = _vars2list([pval, fval, nper, pmt, nrate]) pval = params[0] fval = params[1] nper = params[2] pmt = params[3] nrate = params[4] # raise ValueError(nrate.__repr__()) erate, prate = iconv(nrate=nrate, pyr=pyr) if len(pval) == 1: if pval is not None: print('Present Value: ....... {:8.2f}'.format(pval[0])) if fval is not None: print('Future Value: ........ {:8.2f}'.format(fval[0])) if pmt is not None: print('Payment: ............. {:8.2f}'.format(pmt[0])) if due is not None: print('Due: ................. {:s}'.format('END' if due == 0 else 'BEG')) print('No. of Periods: ...... {:8.2f}'.format(nper[0])) print('Compoundings per Year: {:>5d}'.format(pyr)) print('Nominal Rate: ....... {:8.2f}'.format(nrate[0])) print('Effective Rate: ..... {:8.2f}'.format(erate)) print('Periodic Rate: ...... {:8.2f}'.format(prate)) else: if due == 0: sdue = 'END' txtpmt = [] for item, _ in enumerate(pval): txtpmt.append(pmt[item][-1]) else: sdue = 'BEG' txtpmt = [] for item, _ in enumerate(pval): txtpmt.append(pmt[item][0]) maxlen = 5 for value1, value2, value3, value4 in zip(pval, fval, txtpmt, nper): maxlen = max(maxlen, len('{:1.2f}'.format(value1))) maxlen = max(maxlen, len('{:1.2f}'.format(value2))) maxlen = max(maxlen, len('{:1.2f}'.format(value3))) maxlen = max(maxlen, len('{:1.2f}'.format(value4))) len_aux = len('{:d}'.format(len(pval))) fmt_num = ' {:' + '{:d}'.format(maxlen) + '.2f}' fmt_num = '{:<' + '{:d}'.format(len_aux) + 'd}' + fmt_num * 7 + ' {:3s}' # fmt_shr = '{:' + '{:d}'.format(len_aux) + 's}' fmt_hdr = ' {:>' + '{:d}'.format(maxlen) + 's}' fmt_hdr = '{:' + '{:d}'.format(len_aux) + 's}' + fmt_hdr * 7 + ' due' txt = fmt_hdr.format('#', 'pval', 'fval', 'pmt', 'nper', 'nrate', 'erate', 'prate') print(txt) print('-' * len_aux + '-' * maxlen * 7 + '-' * 7 + '----') for item, _ in enumerate(pval): print(fmt_num.format(item, pval[item], fval[item], txtpmt[item], nper[item], nrate[item], erate[item], prate[item], sdue))
[docs]def pvfv(pval=None, fval=None, nrate=None, nper=None, pyr=1, noprint=True): """Computes the missing argument (set to ``None``) in a model relating the present value, the future value, the number of compoundig periods and the nominal interest rate in a cashflow. Args: pval (float, list): Present value. fval (float, list): Future value. nrate (float, list): Nominal interest rate per year. nper (int, list): Number of compounding periods. pyr (int, list): number of periods per year. noprint (bool): prints enhanced output Returns: The value of the parameter set to ``None`` in the function call. **Details** The ``pvfv`` function computes and returns the missing value (``fval``, ``pval``, ``nper``, ``nrate``) in a model relating these variables. The time intervals between consecutive payments are assumed to be equial. For internal computations, the effective interest rate per period is calculated as ``nrate / pyr``. This function is used to simplify the call to the ``tvmm`` function. See the ``tvmm`` function for details. """ return tvmm(pval=pval, fval=fval, pmt=0, nrate=nrate, nper=nper, due=0, pyr=pyr, noprint=noprint)
[docs]def pmtfv(pmt=None, fval=None, nrate=None, nper=None, pyr=1, noprint=True): """Computes the missing argument (set to ``None``) in a model relating the the future value, the periodic payment, the number of compounding periods and the nominal interest rate in a cashflow. Args: pmt (float, list): Periodic payment. fval (float, list): Future value. nrate (float, list): Nominal rate per year. nper (int, list): Number of compounding periods. pyr (int, list): number of periods per year. noprint (bool): prints enhanced output Returns: The value of the parameter set to None in the function call. **Details** The ``pmtfv`` function computes and returns the missing value (``pmt``, ``fval``, ``nper``, ``nrate``) in a model relating a finite sequence of payments made at the beginning or at the end of each period, a future value, and a nominal interest rate. The time intervals between consecutive payments are assumed to be equial. For internal computations, the effective interest rate per period is calculated as ``nrate / pyr``. This function is used to simplify the call to the ``tvmm`` function. See the ``tvmm`` function for details. """ return tvmm(pval=0, fval=fval, pmt=pmt, nrate=nrate, nper=nper, due=1, pyr=pyr, noprint=noprint)
[docs]def pvpmt(pmt=None, pval=None, nrate=None, nper=None, pyr=1, noprint=True): """Computes the missing argument (set to ``None``) in a model relating the present value, the periodic payment, the number of compounding periods and the nominal interest rate in a cashflow. Args: pmt (float, list): Periodic payment. pval (float, list): Present value. nrate (float, list): Nominal interest rate per year. nper (int, list): Number of compounding periods. pyr (int, list): number of periods per year. noprint (bool): prints enhanced output Returns: The value of the parameter set to None in the function call. **Details** The ``pvpmt`` function computes and returns the missing value (``pmt``, ``pval``, ``nper``, ``nrate``) in a model relating a finite sequence of payments made at the beginning or at the end of each period, a present value, and a nominal interest rate. The time intervals between consecutive payments are assumed to be equial. For internal computations, the effective interest rate per period is calculated as ``nrate / pyr``. This function is used to simplify the call to the ``tvmm`` function. See the ``tvmm`` function for details. """ return tvmm(pval=pval, fval=0, pmt=pmt, nrate=nrate, nper=nper, due=0, pyr=pyr, noprint=noprint)
[docs]def amortize(pval=None, fval=None, pmt=None, nrate=None, nper=None, due=0, pyr=1, noprint=True): """Amortization schedule of a loan. Args: pval (float): present value. fval (float): Future value. pmt (float): periodic payment per period. nrate (float): nominal interest rate per year. nper (int): total number of compounding periods. due (int): When payments are due. pyr (int, list): number of periods per year. noprint (bool): prints enhanced output Returns: A tuple: (principal, interest, payment, balance) **Details** The ``amortize`` function computes and returns the columns of a amortization schedule of a loan. The function returns the interest payment, the principal repayment, the periodic payment and the balance at the end of each period. **Examples** The amortization schedule for a loan of 100, at a interest rate of 10%, and a life of 5 periods, can be computed as: >>> pmt = tvmm(pval=100, nrate=10, nper=5, fval=0) # doctest: +ELLIPSIS >>> amortize(pval=100, nrate=10, nper=5, fval=0, noprint=False) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE t Beginning Periodic Interest Principal Final Principal Payment Payment Repayment Principal Amount Amount Amount -------------------------------------------------------------------- 0 100.00 0.00 0.00 0.00 100.00 1 100.00 -26.38 10.00 -16.38 83.62 2 83.62 -26.38 8.36 -18.02 65.60 3 65.60 -26.38 6.56 -19.82 45.78 4 45.78 -26.38 4.58 -21.80 23.98 5 23.98 -26.38 2.40 -23.98 0.00 >>> amortize(pval=-100, nrate=10, nper=5, fval=0, noprint=False) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE t Beginning Periodic Interest Principal Final Principal Payment Payment Repayment Principal Amount Amount Amount -------------------------------------------------------------------- 0 -100.00 0.00 0.00 0.00 -100.00 1 -100.00 26.38 -10.00 16.38 -83.62 2 -83.62 26.38 -8.36 18.02 -65.60 3 -65.60 26.38 -6.56 19.82 -45.78 4 -45.78 26.38 -4.58 21.80 -23.98 5 -23.98 26.38 -2.40 23.98 -0.00 In the next example, the argument ``due`` is used to indicate that the periodic payment occurs at the begining of period. >>> amortize(pval=100, nrate=10, nper=5, fval=0, due=1, noprint=False) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE t Beginning Periodic Interest Principal Final Principal Payment Payment Repayment Principal Amount Amount Amount -------------------------------------------------------------------- 0 100.00 -23.98 0.00 -23.98 76.02 1 76.02 -23.98 7.60 -16.38 59.64 2 59.64 -23.98 5.96 -18.02 41.62 3 41.62 -23.98 4.16 -19.82 21.80 4 21.80 -23.98 2.18 -21.80 0.00 5 0.00 0.00 0.00 0.00 0.00 >>> amortize(pval=-100, nrate=10, nper=5, fval=0, due=1, noprint=False) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE t Beginning Periodic Interest Principal Final Principal Payment Payment Repayment Principal Amount Amount Amount -------------------------------------------------------------------- 0 -100.00 23.98 0.00 23.98 -76.02 1 -76.02 23.98 -7.60 16.38 -59.64 2 -59.64 23.98 -5.96 18.02 -41.62 3 -41.62 23.98 -4.16 19.82 -21.80 4 -21.80 23.98 -2.18 21.80 -0.00 5 -0.00 0.00 -0.00 -0.00 -0.00 The function returns a tuple with the columns of the amortization schedule. >>> principal, interest, payment, balance = amortize(pval=100, ... nrate=10, nper=5, fval=0) # doctest: +ELLIPSIS >>> principal # doctest: +ELLIPSIS [0, -16.37..., -18.01..., -19.81..., -21.80..., -23.98...] >>> interest # doctest: +ELLIPSIS [0, 10.0, 8.36..., 6.56..., 4.57..., 2.39...] >>> payment # doctest: +ELLIPSIS [0, -26.37..., -26.37..., -26.37..., -26.37..., -26.37...] >>> balance # doctest: +ELLIPSIS [100, 83.62..., 65.60..., 45.78..., 23.98..., 1...] In the following examples, the ``sum`` function is used to sum of different columns of the amortization schedule. >>> principal, interest, payment, balance = amortize(pval=100, ... nrate=10, nper=5, pmt=pmt) # doctest: +ELLIPSIS >>> sum(interest) # doctest: +ELLIPSIS 31.89... >>> sum(principal) # doctest: +ELLIPSIS -99.99... >>> principal, interest, payment, balance = amortize(fval=0, ... nrate=10, nper=5, pmt=pmt) # doctest: +ELLIPSIS >>> sum(interest) # doctest: +ELLIPSIS 31.89... >>> sum(principal) # doctest: +ELLIPSIS -99.99... >>> principal, interest, payment, balance = amortize(pval=100, ... fval=0, nper=5, pmt=pmt) # doctest: +ELLIPSIS >>> sum(interest) # doctest: +ELLIPSIS 31.89... >>> sum(principal) # doctest: +ELLIPSIS -99.99... >>> amortize(pval=100, fval=0, nrate=10, pmt=pmt, noprint=False) # doctest: +ELLIPSIS t Beginning Periodic Interest Principal Final Principal Payment Payment Repayment Principal Amount Amount Amount -------------------------------------------------------------------- 0 100.00 0.00 0.00 0.00 100.00 1 100.00 -26.38 10.00 -16.38 83.62 2 83.62 -26.38 8.36 -18.02 65.60 3 65.60 -26.38 6.56 -19.82 45.78 4 45.78 -26.38 4.58 -21.80 23.98 5 23.98 -26.38 2.40 -23.98 0.00 >>> principal, interest, payment, balance = amortize(pval=100, ... fval=0, nrate=10, pmt=pmt) # doctest: +ELLIPSIS >>> sum(interest) # doctest: +ELLIPSIS 31.89... >>> sum(principal) # doctest: +ELLIPSIS -99.99... """ #pylint: disable=too-many-arguments numnone = 0 numnone += 1 if pval is None else 0 numnone += 1 if fval is None else 0 numnone += 1 if nper is None else 0 numnone += 1 if pmt is None else 0 numnone += 1 if nrate is None else 0 if numnone > 1: raise ValueError('One of the params must be set to None') if numnone == 0: pmt = None if pmt == 0.0: pmt = 0.0000001 if pval is None: pval = tvmm(fval=fval, pmt=pmt, nrate=nrate, nper=nper, due=due, pyr=pyr) elif fval is None: fval = tvmm(pval=pval, pmt=pmt, nrate=nrate, nper=nper, due=due, pyr=pyr) elif nper is None: nper = tvmm(pval=pval, fval=fval, pmt=pmt, nrate=nrate, due=due, pyr=pyr) elif pmt is None: pmt = tvmm(pval=pval, fval=fval, nrate=nrate, nper=nper, due=due, pyr=pyr) else: nrate = tvmm(pval=pval, fval=fval, pmt=pmt, nper=nper, due=due, pyr=pyr) erate = nrate / pyr / 100 if int(nper) != nper: nper = int(nper + 0.9) # variable definition begbal = [0] * (nper + 1) ipmt = [0] * (nper + 1) ppmt = [0] * (nper + 1) rembal = [0] * (nper + 1) # calcula el pmt periodico pmt = [pmt] * (nper + 1) if due == 0: # vencido pmt[0] = 0 if due == 1: # anticipado pmt[nper] = 0 begbal[0] = pval for period in range(nper + 1): if period == 0: rembal[0] = begbal[0] + pmt[0] ppmt[0] = + pmt[0] else: begbal[period] = rembal[period-1] ipmt[period] = begbal[period] * erate ppmt[period] = pmt[period] + ipmt[period] rembal[period] = begbal[period] + ppmt[period] if noprint is True: return (ppmt, ipmt, pmt, rembal) txt = ['t Beginning Periodic Interest Principal Final', ' Principal Payment Payment Repayment Principal', ' Amount Amount Amount', '--------------------------------------------------------------------'] for time in range(nper + 1): fmt = '{:<3d} {:12.2f} {:12.2f} {:12.2f} {:12.2f} {:12.2f}' txt.append(fmt.format(time, begbal[time], pmt[time], ipmt[time], ppmt[time], rembal[time])) print('\n'.join(txt)) return None
if __name__ == "__main__": import doctest doctest.testmod()