#!/usr/bin/python """A finance library for Python. All interest rates are to be expressed in decimal notation (i.e. 0.0825 instead of 8.25) This library has been placed in the public domain. Version history: 1.0 - 2001-01 - Initial program Rupert Scammell 2.0 - 2001-03 - Additions and revisions (compound interest present value, equivalent value of an annuity) Louis Luangkesorn 2.1 - 2004-01 - Readability changes Matthew Scott 2.2 - 2004-01 - Minor name changes, addition of amortizationTable function Matthew Scott 2.3 - 2004-02 - Delay to first payment can be variable in amortization. Matthew Scott 2.4 - 2004-03 - Corrected algorithm in amortization where delay != 30. Use fixedpoint if available (highly recommended since it uses bankers' rounding, which is pretty much required if you are writing a financial app - See http://fixedpoint.sf.net/ for the fixedpoint module) Matthew Scott """ import sys import math try: import fixedpoint FIXEDPOINT = True except: FIXEDPOINT = False def amortization(loan, r, c, n, delay=30): """Amortization If delay is not equal to 30, assumes that a payment period of one month means exactly 30 days. Returns: The amount of money that needs to be paid at the end of each period to get rid of the total loan. Input values: loan : Total loan amount r : annual interest rate c : number of compounding periods a year n : total number of compounding periods delay : number of days until first payment is due """ if loan == 0.0: return 0.0 ipp = r / c amt = (loan * ipp) / (1 - ((1 + ipp) ** (-n))) if FIXEDPOINT: amt = float(fixedpoint.FixedPoint(fixedpoint.FixedPoint(amt, 3), 2)) if delay != 30: # Brute-force calculate the correct payment amount. # First, determine the direction of adjustment. if delay > 30: adjustment = 0.01 elif delay < 30: adjustment = -0.01 # While the absolute value of the discrepancy on the last # payment continues to dwindle, keep calculating. amt = float('%.2f' % amt) smallestDiscrepancy = 10000.00 closestAmt = amt continueCalculating = True while continueCalculating: amt = float('%.2f' % (amt + adjustment)) # Amortize based on amount, then calculate discrepancy # of principal reduction on last payment. principal = loan interestPerPeriod = r / c firstPeriodFactor = (delay / 30.0) interestPerFirstPeriod = interestPerPeriod * firstPeriodFactor period = 1 interestAmount = principal * interestPerFirstPeriod if FIXEDPOINT: interestAmount = float(fixedpoint.FixedPoint( fixedpoint.FixedPoint(interestAmount, 3), 2)) else: interestAmount = float('%.2f' % interestAmount) principalReduction = amt - interestAmount principal -= principalReduction while principal > 0.0: period += 1 interestAmount = principal * interestPerPeriod if FIXEDPOINT: interestAmount = float(fixedpoint.FixedPoint( fixedpoint.FixedPoint(interestAmount, 3), 2)) else: interestAmount = float('%.2f' % interestAmount) principalReduction = amt - interestAmount newBalance = principal - principalReduction if period == n: discrepancy = abs(newBalance) if (delay > 30 and newBalance > 0.0) or \ (delay < 30 and newBalance < 0.0): smallestDiscrepancy = discrepancy closestAmt = amt else: # use amount with negative discrepancy closest to zero if delay > 30: smallestDiscrepancy = discrepancy closestAmt = amt continueCalculating = False principal = newBalance amt = closestAmt return amt def amortizationTable(loan, r, c, n, delay=30): """Amortization table Returns: List of (interestAmount, principalReduction, newBalance) tuples detailing the amortization of a loan. The principal reduction on the last payment is rounded off in order to cleanly bring the balance down to zero. Input values: loan : Total loan amount r : annual interest rate c : number of compounding periods a year n : total number of compounding periods delay : number of days until first payment is due """ L = [] payment = float('%.2f' % amortization(loan, r, c, n, delay)) principal = loan interestPerPeriod = r / c if delay == 30: interestPerFirstPeriod = interestPerPeriod else: firstPeriodFactor = (delay / 30.0) interestPerFirstPeriod = interestPerPeriod * firstPeriodFactor period = 0 while principal > 0.0: period += 1 if period > 1: interestAmount = principal * interestPerPeriod else: interestAmount = principal * interestPerFirstPeriod interestAmount = float('%.2f' % interestAmount) principalReduction = payment - interestAmount newBalance = principal - principalReduction # Adjust values if this is the last payment. if period == n: if newBalance > 0.0: principalReduction += newBalance elif newBalance < 0.0: principalReduction -= newBalance newBalance = 0.0 L.append((interestAmount, principalReduction, newBalance)) principal = newBalance return L def annualYield(r, c): """Annual yield Returns: Simple interest rate necessary to yield the same amount of dollars yielded by the annual rate r compounded c times for one year Input values: r : interest rate c : number of compounding periods in a year """ y = ((1 + (r / c)) ** c) - 1 return y def annuityEquivalentAnnualCost(pval, r, c, n): """Equivalent value of an annuity Returns: Coupon amount for an annuity given the present value Input values: pval : present value of annuity r : annual interest rate c : number of compounding periods in a year n : total number of payments See 'Ordinary annuity formula' above. """ ipp = r / c pymt = pval / ((1 - ((1 + ipp) ** (-n))) / ipp) return pymt def annuityOrdinary(pymt, p, r, c, n): """Ordinary annuity formula Returns: future value Input values: pymt : payment made during compounding period p : principal r : annual interest rate c : number of compounding periods in a year n : total number of payments """ block1 = ((1 + (r / c)) ** n) - 1 block2 = r / c fv = pymt * (block1 / block2) return fv def annuityPresentValue(pymt, r, c, n): """Present value of an annuity Returns: Lump sum that can be deposited at the beginning of the annuity's term, at the same interest rate and with the same compounding period, that would yield the same amount as the annuity. Input values: pymt : payment made during compounding period r : annual interest rate c : number of compounding periods in a year n : total number of payments """ ipp = r / c pval = pymt * ((1 - ((1 + ipp) ** (-n))) / ipp) return pval def compoundInterestFutureValue(p, r, c, n): """Compound interest future value Returns: future value Input values: p : principal r : interest rate c : number of compounding periods in a year n : (c * t) , total number of compounding periods """ fv = (p * (1 + (r / c))) ** n return fv def compoundInterestPresentValue(p, r, c, n): """Compound interest present value Returns: present value Input values: p : principal r : interest rate c : number of compounding periods in a year n : (c * t), total number of compounding periods """ pv = (p / ((1 + (r / c)) ** n)) return pv def compoundedInterest(fv, p): """Compounded interest Returns: Interest value Input values: fv : Future value p : Principal """ i = fv - p return i def simpleInterest(p, r, t): """Simple interest Returns: interest value Input values: p : principal r : Interest rate (decimal) t : Investment periods """ i = p * r * t return i def simpleInterestFutureValue(p, r, t): """Simple interest future value Returns: future value Input values: p : principal r : Interest rate (decimal) t : Investment periods """ fv = p * (1 + r * t) return fv