Source code for cashflows.loan

"""
Loan analysis
==============================================================================

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

Computes the amorization schedule for the following types of loans:

* ``fixed_rate_loan``: In this loan, the interest rate is fixed and the total
  payments are equal during the life of the loan.

* ``buydown_loan``: the interest rate changes during the life of the loan;
  the value of the payments are calculated using the current value of the
  interest rate. When the interest rate is constant during the life of the loan,
  the results are equals to the function ``fixed_rate_loan``.

* ``fixed_ppal_loan``: the payments to the principal are constant during the life
  of loan.

* ``bullet_loan``: the principal is payed at the end of the life of the loan.


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



"""

import numpy as np
import pandas as pd

from cashflows.analysis import *
from cashflows.tvmm import *
from cashflows.timeseries import *
from cashflows.common import *

##
## base class for computations
##
[docs]class Loan(pd.DataFrame): def __init__(self, life, amount, grace, nrate, dispoints=0, orgpoints=0, data=None, index=None, columns=None, dtype=None, copy=False): super().__init__(data=data, index=index, columns=columns, dtype=dtype, copy=copy) self.life = life self.amount = amount self.grace = grace self.dispoints = dispoints self.orgpoints = orgpoints self.nrate = nrate
[docs] def tocashflow(self, tax_rate=None): cflo = self.nrate.copy() cflo[:] = 0 if tax_rate is None: tax_rate = cflo.copy() tax_rate[:] = 0 # # descuenta todos los pagos adicionales # cflo[0] += self.amount cflo[0] -= self.amount * self.orgpoints / 100 cflo[0] -= self.amount * self.dispoints / 100 cflo[0] += self.amount * self.dispoints / 100 * tax_rate[0] / 100 cflo -= self.Ppal_Payment cflo -= self.Int_Payment cflo += self.Int_Payment * tax_rate / 100 return cflo
[docs] def true_rate(self, tax_rate=None): cflo = self.tocashflow(tax_rate) return irr(cflo) * getpyr(cflo)
def __str__(self): str = [] str.append("Amount: {:.2f}".format(self.amount)) str.append("Total interest: {:.2f}".format(sum(self.Int_Payment))) str.append("Total payment: {:.2f}".format(sum(self.Tot_Payment))) str.append("Discount points: {:.2f}".format(self.dispoints)) str.append("Origination points: {:.2f}".format(self.orgpoints)) str = '\n'.join(str) + '\n\n' str = str + super().__str__() return str
[docs]def fixed_ppal_loan(amount, nrate, grace=0, dispoints=0, orgpoints=0, prepmt=None, balloonpmt=None): """Loan with fixed principal payment. Args: amount (float): Loan amount. nrate (float, pandas.Series): nominal interest rate per year. grace (int): number of grace periiods without paying principal. dispoints (float): Discount points of the loan. orgpoints (float): Origination points of the loan. prepmt (pandas.Series): generic cashflow representing prepayments. balloonpmt (pandas.Series): generic cashflow representing balloon payments. Returns: A object of the class ``Loan``. **Examples** >>> nrate = interest_rate(const_value=[10]*11, start='2018Q1', freq='Q') >>> tax_rate = interest_rate(const_value=[35]*11, start='2018Q1', freq='Q') >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=0, dispoints=0, orgpoints=0, ... prepmt=None, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE Amount: 1000.00 Total interest: 137.50 Total payment: 1137.50 Discount points: 0.00 Origination points: 0.00 <BLANKLINE> Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ 2018Q1 0.0 10.0 0.0 0.0 0.0 2018Q2 1000.0 10.0 125.0 25.0 100.0 2018Q3 900.0 10.0 122.5 22.5 100.0 2018Q4 800.0 10.0 120.0 20.0 100.0 2019Q1 700.0 10.0 117.5 17.5 100.0 2019Q2 600.0 10.0 115.0 15.0 100.0 2019Q3 500.0 10.0 112.5 12.5 100.0 2019Q4 400.0 10.0 110.0 10.0 100.0 2020Q1 300.0 10.0 107.5 7.5 100.0 2020Q2 200.0 10.0 105.0 5.0 100.0 2020Q3 100.0 10.0 102.5 2.5 100.0 <BLANKLINE> End_Ppal_Amount 2018Q1 1000.0 2018Q2 900.0 2018Q3 800.0 2018Q4 700.0 2019Q1 600.0 2019Q2 500.0 2019Q3 400.0 2019Q4 300.0 2020Q1 200.0 2020Q2 100.0 2020Q3 0.0 >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, ... prepmt=None, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE Amount: 1000.00 Total interest: 162.50 Total payment: 1162.50 Discount points: 0.00 Origination points: 0.00 <BLANKLINE> Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ 2018Q1 0.0 10.0 0.000 0.000 0.0 2018Q2 1000.0 10.0 25.000 25.000 0.0 2018Q3 1000.0 10.0 25.000 25.000 0.0 2018Q4 1000.0 10.0 150.000 25.000 125.0 2019Q1 875.0 10.0 146.875 21.875 125.0 2019Q2 750.0 10.0 143.750 18.750 125.0 2019Q3 625.0 10.0 140.625 15.625 125.0 2019Q4 500.0 10.0 137.500 12.500 125.0 2020Q1 375.0 10.0 134.375 9.375 125.0 2020Q2 250.0 10.0 131.250 6.250 125.0 2020Q3 125.0 10.0 128.125 3.125 125.0 <BLANKLINE> End_Ppal_Amount 2018Q1 1000.0 2018Q2 1000.0 2018Q3 1000.0 2018Q4 875.0 2019Q1 750.0 2019Q2 625.0 2019Q3 500.0 2019Q4 375.0 2020Q1 250.0 2020Q2 125.0 2020Q3 0.0 >>> pmt = cashflow(const_value=[0]*11, start='2018Q1', freq='Q') >>> pmt['2019Q4'] = 200 >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, ... prepmt=pmt, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE Amount: 1000.00 Total interest: 149.38 Total payment: 1149.38 Discount points: 0.00 Origination points: 0.00 <BLANKLINE> Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ 2018Q1 0.0 10.0 0.000 0.000 0.0 2018Q2 1000.0 10.0 25.000 25.000 0.0 2018Q3 1000.0 10.0 25.000 25.000 0.0 2018Q4 1000.0 10.0 150.000 25.000 125.0 2019Q1 875.0 10.0 146.875 21.875 125.0 2019Q2 750.0 10.0 143.750 18.750 125.0 2019Q3 625.0 10.0 140.625 15.625 125.0 2019Q4 500.0 10.0 337.500 12.500 325.0 2020Q1 175.0 10.0 129.375 4.375 125.0 2020Q2 50.0 10.0 51.250 1.250 50.0 2020Q3 0.0 10.0 0.000 0.000 0.0 <BLANKLINE> End_Ppal_Amount 2018Q1 1000.0 2018Q2 1000.0 2018Q3 1000.0 2018Q4 875.0 2019Q1 750.0 2019Q2 625.0 2019Q3 500.0 2019Q4 175.0 2020Q1 50.0 2020Q2 0.0 2020Q3 0.0 >>> fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, ... prepmt=None, balloonpmt=pmt) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Amount: 1000.00 Total interest: 165.00 Total payment: 1165.00 Discount points: 0.00 Origination points: 0.00 <BLANKLINE> Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ 2018Q1 0.0 10.0 0.0 0.0 0.0 2018Q2 1000.0 10.0 25.0 25.0 0.0 2018Q3 1000.0 10.0 25.0 25.0 0.0 2018Q4 1000.0 10.0 125.0 25.0 100.0 2019Q1 900.0 10.0 122.5 22.5 100.0 2019Q2 800.0 10.0 120.0 20.0 100.0 2019Q3 700.0 10.0 117.5 17.5 100.0 2019Q4 600.0 10.0 315.0 15.0 300.0 2020Q1 300.0 10.0 107.5 7.5 100.0 2020Q2 200.0 10.0 105.0 5.0 100.0 2020Q3 100.0 10.0 102.5 2.5 100.0 <BLANKLINE> End_Ppal_Amount 2018Q1 1000.0 2018Q2 1000.0 2018Q3 1000.0 2018Q4 900.0 2019Q1 800.0 2019Q2 700.0 2019Q3 600.0 2019Q4 300.0 2020Q1 200.0 2020Q2 100.0 2020Q3 0.0 >>> x = fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=0, ... prepmt=None, balloonpmt=pmt) >>> x.true_rate() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 10.00... >>> x.true_rate(tax_rate) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 6.50... >>> x.tocashflow() 2018Q1 1000.0 2018Q2 -25.0 2018Q3 -25.0 2018Q4 -125.0 2019Q1 -122.5 2019Q2 -120.0 2019Q3 -117.5 2019Q4 -315.0 2020Q1 -107.5 2020Q2 -105.0 2020Q3 -102.5 Freq: Q-DEC, dtype: float64 >>> x.tocashflow(tax_rate) 2018Q1 1000.000 2018Q2 -16.250 2018Q3 -16.250 2018Q4 -116.250 2019Q1 -114.625 2019Q2 -113.000 2019Q3 -111.375 2019Q4 -309.750 2020Q1 -104.875 2020Q2 -103.250 2020Q3 -101.625 Freq: Q-DEC, dtype: float64 >>> x = fixed_ppal_loan(amount=1000, nrate=nrate, grace=2, dispoints=0, orgpoints=10, ... prepmt=None, balloonpmt=pmt) >>> x # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Amount: 1000.00 Total interest: 165.00 Total payment: 1265.00 Discount points: 0.00 Origination points: 10.00 <BLANKLINE> Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ 2018Q1 0.0 10.0 100.0 0.0 0.0 2018Q2 1000.0 10.0 25.0 25.0 0.0 2018Q3 1000.0 10.0 25.0 25.0 0.0 2018Q4 1000.0 10.0 125.0 25.0 100.0 2019Q1 900.0 10.0 122.5 22.5 100.0 2019Q2 800.0 10.0 120.0 20.0 100.0 2019Q3 700.0 10.0 117.5 17.5 100.0 2019Q4 600.0 10.0 315.0 15.0 300.0 2020Q1 300.0 10.0 107.5 7.5 100.0 2020Q2 200.0 10.0 105.0 5.0 100.0 2020Q3 100.0 10.0 102.5 2.5 100.0 <BLANKLINE> End_Ppal_Amount 2018Q1 1000.0 2018Q2 1000.0 2018Q3 1000.0 2018Q4 900.0 2019Q1 800.0 2019Q2 700.0 2019Q3 600.0 2019Q4 300.0 2020Q1 200.0 2020Q2 100.0 2020Q3 0.0 >>> x.true_rate() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 17.1725... >>> x.tocashflow() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 2018Q1 900.0 2018Q2 -25.0 2018Q3 -25.0 2018Q4 -125.0 2019Q1 -122.5 2019Q2 -120.0 2019Q3 -117.5 2019Q4 -315.0 2020Q1 -107.5 2020Q2 -105.0 2020Q3 -102.5 Freq: Q-DEC, dtype: float64 >>> x.true_rate(tax_rate) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 13.4232... >>> x.tocashflow(tax_rate) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS 2018Q1 900.000 2018Q2 -16.250 2018Q3 -16.250 2018Q4 -116.250 2019Q1 -114.625 2019Q2 -113.000 2019Q3 -111.375 2019Q4 -309.750 2020Q1 -104.875 2020Q2 -103.250 2020Q3 -101.625 Freq: Q-DEC, dtype: float64 """ #pylint: disable-msg=too-many-arguments if not isinstance(nrate, pd.Series): TypeError('nrate must be a pandas.Series object.') if prepmt is None: prepmt = nrate.copy() prepmt[:] = 0 else: verify_period_range([nrate, prepmt]) if balloonpmt is None: balloonpmt = nrate.copy() balloonpmt[:] = 0 else: verify_period_range([nrate, balloonpmt]) # present value of the balloon payments balloonpv = sum(balloonpmt) life = len(nrate) - grace - 1 begppalbal = nrate.copy() intpmt = nrate.copy() ppalpmt = nrate.copy() totpmt = nrate.copy() endppalbal = nrate.copy() begppalbal[:] = 0 intpmt[:] = 0 ppalpmt[:] = 0 totpmt[:] = 0 endppalbal[:] = 0 pmt = (amount - balloonpv) / life # periodic ppal payment pyr = getpyr(nrate) # balance calculation for time in range(grace + life + 1): if time == 0: begppalbal[time] = 0 endppalbal[time] = amount - prepmt[time] totpmt[time] = amount * (dispoints + orgpoints) / 100 ### intpmt[time] = amount * dispoints / 100 else: begppalbal[time] = endppalbal[time - 1] intpmt[time] = begppalbal[time] * nrate[time] / pyr / float(100) if time <= grace: ppalpmt[time] = prepmt[time] + balloonpmt[time] else: ppalpmt[time] = pmt + prepmt[time] + balloonpmt[time] totpmt[time] = intpmt[time] + ppalpmt[time] endppalbal[time] = begppalbal[time] - ppalpmt[time] if endppalbal[time] < 0: totpmt[time] = begppalbal[time] + intpmt[time] ppalpmt[time] = begppalbal[time] endppalbal[time] = begppalbal[time] - ppalpmt[time] prepmt[time] = 0 pmt = 0 data = {'Beg_Ppal_Amount':begppalbal} result = Loan(life=life, amount=amount, grace=grace, nrate=nrate, dispoints=dispoints, orgpoints=orgpoints, data=data) result['Nom_Rate'] = nrate result['Tot_Payment'] = totpmt result['Int_Payment'] = intpmt result['Ppal_Payment'] = ppalpmt result['End_Ppal_Amount'] = endppalbal return result
[docs]def bullet_loan(amount, nrate, dispoints=0, orgpoints=0, prepmt=None): """ In this type of loan, the principal is payed at the end for the life of the loan. Periodic payments correspond only to interests. Args: amount (float): Loan amount. nrate (float, pandas.Series): nominal interest rate per year. dispoints (float): Discount points of the loan. orgpoints (float): Origination points of the loan. prepmt (pandas.Series): generic cashflow representing prepayments. Returns: A object of the class ``Loan``. >>> nrate = interest_rate(const_value=[10]*11, start='2018Q1', freq='Q') >>> bullet_loan(amount=1000, nrate=nrate, dispoints=0, orgpoints=0, prepmt=None) # doctest: +NORMALIZE_WHITESPACE Amount: 1000.00 Total interest: 250.00 Total payment: 1250.00 Discount points: 0.00 Origination points: 0.00 <BLANKLINE> Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ 2018Q1 0.0 10.0 0.0 0.0 0.0 2018Q2 1000.0 10.0 25.0 25.0 0.0 2018Q3 1000.0 10.0 25.0 25.0 0.0 2018Q4 1000.0 10.0 25.0 25.0 0.0 2019Q1 1000.0 10.0 25.0 25.0 0.0 2019Q2 1000.0 10.0 25.0 25.0 0.0 2019Q3 1000.0 10.0 25.0 25.0 0.0 2019Q4 1000.0 10.0 25.0 25.0 0.0 2020Q1 1000.0 10.0 25.0 25.0 0.0 2020Q2 1000.0 10.0 25.0 25.0 0.0 2020Q3 1000.0 10.0 1025.0 25.0 1000.0 <BLANKLINE> End_Ppal_Amount 2018Q1 1000.0 2018Q2 1000.0 2018Q3 1000.0 2018Q4 1000.0 2019Q1 1000.0 2019Q2 1000.0 2019Q3 1000.0 2019Q4 1000.0 2020Q1 1000.0 2020Q2 1000.0 2020Q3 0.0 """ if not isinstance(nrate, pd.Series): raise TypeError("nrate must be a pandas.Series object") balloonpmt = nrate.copy() balloonpmt[:] = 0 balloonpmt[-1] = amount return fixed_ppal_loan(amount=amount, nrate=nrate, grace=0, dispoints=dispoints, orgpoints=orgpoints, prepmt=prepmt, balloonpmt=balloonpmt)
[docs]def fixed_rate_loan(amount, nrate, life, start, freq='A', grace=0, dispoints=0, orgpoints=0, prepmt=None, balloonpmt=None): """Fixed rate loan. Args: amount (float): Loan amount. nrate (float): nominal interest rate per year. life (float): life of the loan. start (int, tuple): init period for the loan. pyr (int): number of compounding periods per year. grace (int): number of periods of grace (without payment of the principal) dispoints (float): Discount points of the loan. orgpoints (float): Origination points of the loan. prepmt (pandas.Series): generic cashflow representing prepayments. balloonpmt (pandas.Series): generic cashflow representing balloon payments. Returns: A object of the class ``Loan``. >>> pmt = cashflow(const_value=0, start='2016Q1', periods=11, freq='Q') >>> pmt['2017Q4'] = 200 >>> fixed_rate_loan(amount=1000, nrate=10, life=10, start='2016Q1', freq='Q', ... grace=0, dispoints=0, ... orgpoints=0, prepmt=pmt, balloonpmt=None) # doctest: +NORMALIZE_WHITESPACE Amount: 1000.00 Total interest: 129.68 Total payment: 1129.68 Discount points: 0.00 Origination points: 0.00 <BLANKLINE> Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ 2016Q1 1000.000000 10.0 0.000000 0.000000 0.000000 2016Q2 1000.000000 10.0 114.258763 25.000000 89.258763 2016Q3 910.741237 10.0 114.258763 22.768531 91.490232 2016Q4 819.251005 10.0 114.258763 20.481275 93.777488 2017Q1 725.473517 10.0 114.258763 18.136838 96.121925 2017Q2 629.351591 10.0 114.258763 15.733790 98.524973 2017Q3 530.826618 10.0 114.258763 13.270665 100.988098 2017Q4 429.838520 10.0 314.258763 10.745963 303.512800 2018Q1 126.325720 10.0 114.258763 3.158143 111.100620 2018Q2 15.225100 10.0 15.605727 0.380627 15.225100 2018Q3 0.000000 10.0 0.000000 0.000000 0.000000 <BLANKLINE> End_Ppal_Amount 2016Q1 1000.000000 2016Q2 910.741237 2016Q3 819.251005 2016Q4 725.473517 2017Q1 629.351591 2017Q2 530.826618 2017Q3 429.838520 2017Q4 126.325720 2018Q1 15.225100 2018Q2 0.000000 2018Q3 0.000000 """ if not isinstance(float(nrate), float): TypeError('nrate must be a float.') nrate = interest_rate(const_value=nrate, start=start, periods=life+grace+1, freq=freq) if prepmt is None: prepmt = cashflow(const_value=0, start=start, periods=len(nrate), freq=freq) else: verify_period_range([nrate, prepmt]) if balloonpmt is None: balloonpmt = nrate.copy() balloonpmt[:] = 0 else: verify_period_range([nrate, balloonpmt]) # present value of the balloon payments if balloonpmt is not None: balloonpv = timevalue(cflo=balloonpmt, prate=nrate, base_date=grace) else: balloonpv = 0 pyr = getpyr(nrate) pmt = pvpmt(pmt=None, pval=-amount+balloonpv, nrate=nrate[0], nper=len(nrate)-1, pyr=pyr) pmts = nrate.copy() pmts[:] = 0 for time in range(1, life + 1): pmts[grace + time] = pmt # balance begppalbal = nrate.copy() intpmt = nrate.copy() ppalpmt = nrate.copy() totpmt = nrate.copy() endppalbal = nrate.copy() begppalbal[:] = 0 intpmt[:] = 0 ppalpmt[:] = 0 totpmt[:] = 0 endppalbal[:] = 0 # payments per period for time, _ in enumerate(totpmt): totpmt[time] = pmts[time] + balloonpmt[time] + prepmt[time] # balance calculation for time in range(grace + life + 1): if time == 0: begppalbal[0] = amount endppalbal[0] = amount totpmt[time] = amount * (dispoints + orgpoints) / 100 ### intpmt[time] = amount * dispoints / 100 else: begppalbal[time] = endppalbal[time - 1] if time <= grace: intpmt[time] = begppalbal[time] * nrate[time] / pyr / 100 totpmt[time] = intpmt[time] endppalbal[time] = begppalbal[time] else: intpmt[time] = begppalbal[time] * nrate[time] / pyr / 100 ppalpmt[time] = totpmt[time] - intpmt[time] if ppalpmt[time] < 0: capint = - ppalpmt[time] ppalpmt[time] = 0 else: capint = 0 endppalbal[time] = begppalbal[time] - ppalpmt[time] + capint if endppalbal[time] < 0: totpmt[time] = begppalbal[time] + intpmt[time] ppalpmt[time] = begppalbal[time] endppalbal[time] = begppalbal[time] - ppalpmt[time] pmts[time] = 0 prepmt[time] = 0 data = {'Beg_Ppal_Amount':begppalbal} result = Loan(life=life, amount=amount, grace=grace, nrate=nrate, dispoints=dispoints, orgpoints=orgpoints, data=data) result['Nom_Rate'] = nrate result['Tot_Payment'] = totpmt result['Int_Payment'] = intpmt result['Ppal_Payment'] = ppalpmt result['End_Ppal_Amount'] = endppalbal return result
[docs]def buydown_loan(amount, nrate, grace=0, dispoints=0, orgpoints=0, prepmt=None): """ In this loan, the periodic payments are recalculated when there are changes in the value of the interest rate. Args: amount (float): Loan amount. nrate (float, pandas.Series): nominal interest rate per year. grace (int): numner of grace periods without paying the principal. dispoints (float): Discount points of the loan. orgpoints (float): Origination points of the loan. prepmt (pandas.Series): generic cashflow representing prepayments. Returns: A object of the class ``Loan``. >>> nrate = interest_rate(const_value=10, start='2016Q1', periods=11, freq='Q', chgpts={'2017Q2':20}) >>> buydown_loan(amount=1000, nrate=nrate, dispoints=0, orgpoints=0, prepmt=None) # doctest: +NORMALIZE_WHITESPACE Amount: 1000.00 Total interest: 200.99 Total payment: 1200.99 Discount points: 0.00 Origination points: 0.00 <BLANKLINE> Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ 2016Q1 1000.000000 10.0 0.000000 0.000000 0.000000 2016Q2 1000.000000 10.0 114.258763 25.000000 89.258763 2016Q3 910.741237 10.0 114.258763 22.768531 91.490232 2016Q4 819.251005 10.0 114.258763 20.481275 93.777488 2017Q1 725.473517 10.0 114.258763 18.136838 96.121925 2017Q2 629.351591 20.0 123.993257 31.467580 92.525677 2017Q3 536.825914 20.0 123.993257 26.841296 97.151961 2017Q4 439.673952 20.0 123.993257 21.983698 102.009559 2018Q1 337.664393 20.0 123.993257 16.883220 107.110037 2018Q2 230.554356 20.0 123.993257 11.527718 112.465539 2018Q3 118.088816 20.0 123.993257 5.904441 118.088816 <BLANKLINE> End_Ppal_Amount 2016Q1 1.000000e+03 2016Q2 9.107412e+02 2016Q3 8.192510e+02 2016Q4 7.254735e+02 2017Q1 6.293516e+02 2017Q2 5.368259e+02 2017Q3 4.396740e+02 2017Q4 3.376644e+02 2018Q1 2.305544e+02 2018Q2 1.180888e+02 2018Q3 1.136868e-13 >>> pmt = cashflow(const_value=0, start='2016Q1', periods=11, freq='Q') >>> pmt['2017Q4'] = 200 >>> buydown_loan(amount=1000, nrate=nrate, dispoints=0, orgpoints=0, prepmt=pmt) # doctest: +NORMALIZE_WHITESPACE Amount: 1000.00 Total interest: 180.67 Total payment: 1180.67 Discount points: 0.00 Origination points: 0.00 <BLANKLINE> Beg_Ppal_Amount Nom_Rate Tot_Payment Int_Payment Ppal_Payment \\ 2016Q1 1000.000000 10.0 0.000000 0.000000 0.000000 2016Q2 1000.000000 10.0 114.258763 25.000000 89.258763 2016Q3 910.741237 10.0 114.258763 22.768531 91.490232 2016Q4 819.251005 10.0 114.258763 20.481275 93.777488 2017Q1 725.473517 10.0 114.258763 18.136838 96.121925 2017Q2 629.351591 20.0 123.993257 31.467580 92.525677 2017Q3 536.825914 20.0 123.993257 26.841296 97.151961 2017Q4 439.673952 20.0 323.993257 21.983698 302.009559 2018Q1 137.664393 20.0 50.551544 6.883220 43.668324 2018Q2 93.996068 20.0 50.551544 4.699803 45.851741 2018Q3 48.144328 20.0 50.551544 2.407216 48.144328 <BLANKLINE> End_Ppal_Amount 2016Q1 1.000000e+03 2016Q2 9.107412e+02 2016Q3 8.192510e+02 2016Q4 7.254735e+02 2017Q1 6.293516e+02 2017Q2 5.368259e+02 2017Q3 4.396740e+02 2017Q4 1.376644e+02 2018Q1 9.399607e+01 2018Q2 4.814433e+01 2018Q3 4.263256e-14 """ if not isinstance(nrate, pd.Series): TypeError('nrate must be a pandas.Series object.') if prepmt is None: prepmt = nrate.copy() prepmt[:] = 0 else: verify_period_range([nrate, prepmt]) life = len(nrate) - grace - 1 begppalbal = nrate.copy() intpmt = nrate.copy() ppalpmt = nrate.copy() totpmt = nrate.copy() endppalbal = nrate.copy() begppalbal[:] = 0 intpmt[:] = 0 ppalpmt[:] = 0 totpmt[:] = 0 endppalbal[:] = 0 ## ## balance calculation ## pyr = getpyr(nrate) for time in range(grace + life + 1): if time == 0: # begppalbal[time] = amount endppalbal[time] = amount totpmt[time] = amount * (dispoints + orgpoints) / 100 ### intpmt[time] = amount * dispoints / 100 # else: # # periodic payment per period # if time <= grace: begppalbal[time] = endppalbal[time - 1] intpmt[time] = begppalbal[time] * nrate[time] / pyr / 100 totpmt[time] = intpmt[time] endppalbal[time] = begppalbal[time] else: pmt = -pvpmt(nrate=nrate[time], nper=grace+life-time+1, pval=endppalbal[time-1], pmt=None, pyr=pyr) totpmt[time] = pmt + prepmt[time] # balance begppalbal[time] = endppalbal[time - 1] intpmt[time] = begppalbal[time] * nrate[time] / pyr / 100 ppalpmt[time] = totpmt[time] - intpmt[time] endppalbal[time] = begppalbal[time] - ppalpmt[time] data = {'Beg_Ppal_Amount':begppalbal} result = Loan(life=life, amount=amount, grace=grace, nrate=nrate, dispoints=dispoints, orgpoints=orgpoints, data=data) result['Nom_Rate'] = nrate result['Tot_Payment'] = totpmt result['Int_Payment'] = intpmt result['Ppal_Payment'] = ppalpmt result['End_Ppal_Amount'] = endppalbal return result
if __name__ == "__main__": import doctest doctest.testmod()