Wednesday, August 29

Time Value of Money Calculator

Link: IPython notebook

This article shows how to use the principle of offsetting annuities to solve basic TVM problems, such as yields on bonds, mortgage payments or arbitrage-free bond pricings. The underlying implementation is done in Python and illustrates practical use of these principles on simple time value of money examples.

Theoretical background

Time Value of Money is a central concept of finance theory that gives different value to the same nominal cash flow, depending on a pay-off date. In another words, one dollar has bigger value if received now as opposed to some future point of time. Hence, the difference in these valuations is a function of time passed between some present and future date. Another parameter of this function is the rate defining interest earned during one period of time. Mathematically, this relation could be written as:

$$ FV = PV\times (1+r)^{n} $$

, where  \(PV\) is the present value of cash flow, \(FV\) is the value at some future date, \(r\) is an interest accrued during one period and \(n\) is number of periods between the today's and future date.


The relation above always holds no matter of how complex the timing of cash flow is. Thanks to this, it can be used to value virtually any cash-flow scenario.


Annuity

Annuity can be understood as a bank deposit or other cash investment generating constant periodic interest payments (coupons). Since all earned interest is paid-out at the end of each period, the outstanding balance is never changed and payments last constant forever. Initial deposit \(PV\), periodic interest rate \(r\) and periodic payments \(PMT\) are in the following relation:

$$ PV = \frac{PMT}{r} $$

If we depict all cash inflows and outflows as arrows above and below the time line, respectively; the annuity would look for example as follows:



Plain Vanilla bond model


One of the common scenarios used in finance is Plain Vanilla bond model, where initial outflow is typically followed by a series of periodic cash inflows, enclosed by a final principal inflow paid at maturity. This model can be applied to a wide-range of fixed-rate contracts, such as bonds, mortgages, non-amortizing loans, etc.


In case of a Plain Vanilla bond, the initial outflow is considered to be the price of a bond, periodic cash inflows are coupons are final cash inflow at maturity is the face value of bond.


The following picture illustrates the structure of cash aflows in a Vanilla bond model:







General Principle of Calculation

The mathematical equation for a generalized model looks as follows:

$$ FV = PV \times (1+r)^{n} + \sum_{i=1}^{n}\left ( PMT_{i} \times (1+r)^{n-i} \right ) $$

, where \(PV\) is initial inflow, \(PMT_{i}\) are periodic cash outflows, \(FV\) final inflow and \(r\) is the interest rate, as defined previously.

In case all periodic payments are the same, as in a plain-vanilla bond model, the formula above may be reduced into two mutually offsetting annuities, starting each at different point of time:




The formula


To derive the formula, assume the composition of two annuities mentioned above is priced to be arbitrage-free.


The value of Annuity A in today's money is:


$$Value_{A}=\frac{PMT}{r}-PV$$


The value of Annuity B in the future money is:


$$Value_{B}=\frac{-PMT}{r}+FV$$


, which gives us the today's valuation of 


$$Value_{B}^{now}=\left(1+r\right)^{-n}\left(\frac{-PMT}{r}+FV\right)$$


Using the substitution \(z=(1+r)^{-n}\) for discount factor, we can construct the arbitrage-free equation as follows:

$$Value_{A}+Value_{B}^{now}=0$$
$$\left(\frac{PMT}{r}-PV\right)+z\times\left(\frac{-PMT}{r}+FV\right)=0$$

After isolation of \(FV\), the final equation is:

$$FV=\frac{1}{z}\left(\left(z-1\right)\times\frac{PMT}{r}-PV\right)$$


Other variables, such as \(PV\), \(PMT\), \(n=-log(z)\) can be isolated in a similar manner. The only difficulty is the calculation of discount rate \(r\), which must be done through a root-finding methods, such as Newton-Raphson or other.


Python Implementation


Algebraic equations are implemented in the TVM class:


from math import pow, floor, ceil, log
from quant.optimization import newton
class TVM:
    bgn, end = 01
    def __str__(self):
        return "n=%f, r=%f, pv=%f, pmt=%f, fv=%f" % (
            self.nself.rself.pvself.pmtself.fv)
    def __init__(self, n=0.0, r=0.0, pv=0.0, pmt=0.0, fv=0.0, mode=end):
        self.n = float(n)
        self.r = float(r)
        self.pv = float(pv)
        self.pmt = float(pmt)
        self.fv = float(fv)
        self.mode = mode
    def calc_pv(self):
        z = pow(1+self.r, -self.n)
        pva = self.pmt / self.r
        if (self.mode==TVM.bgn): pva += self.pmt
        return -(self.fv*z + (1-z)*pva)
    def calc_fv(self):
        z = pow(1+self.r, -self.n)
        pva = self.pmt / self.r
        if (self.mode==TVM.bgn): pva += self.pmt
        return -(self.pv + (1-z) * pva)/z
    def calc_pmt(self):
        z = pow(1+self.r, -self.n)
        if self.mode==TVM.bgn:
            return (self.pv + self.fv*z) * self.r / (z-1) / (1+self.r)
        else:
            return (self.pv + self.fv*z) * self.r / (z-1)
    def calc_n(self):
        pva = self.pmt / self.r
        if (self.mode==TVM.bgn): pva += self.pmt
        z = (-pva-self.pv) / (self.fv-pva)
        return -log(z) / log(1+self.r)
    def calc_r(self):
        def function_fv(r, self):
            z = pow(1+r, -self.n)
            pva = self.pmt / r
            if (self.mode==TVM.bgn): pva += self.pmt
            return -(self.pv + (1-z) * pva)/z
        return newton(f=function_fv, fArg=self, x0=.05, 
            y=self.fv, maxIter=1000, minError=0.0001)



The generic code for Newton-Raphson method:

from math import fabs
# f - function with 1 float returning float
# x0 - initial value
# y - desired value
# maxIter - max iterations
# minError - minimum error abs(f(x)-y)
def newton(f, fArg, x0, y, maxIter, minError):
    def func(f, fArg, x, y):
        return f(x, fArg) - y
    def slope(f, fArg, x, y):
        xp = x * 1.05
        return (func(f, fArg, xp, y)-func(f, fArg, x, y)) / (xp-x)      
    counter = 0
    while 1:
        sl = slope(f, fArg, x0, y);
        x0 = x0 - func(f, fArg, x0, y) / sl
        if (counter > maxIter)break
        if (abs(f(x0, fArg)-y) < minError)break
        counter += 1
    return x0

Example 1 - Mortgage Payments

Consider a regular mortgage for $500'000, fully amortized over 25 years, with monthly installments, bearing annual interest rate 4%. Regular monthly payments will be then calculated as follows:

from quant.tvm import TVM

pmt = TVM(n=25*12, r=.04/12, pv=500000, fv=0).calc_pmt()
print("Payment = %f" % pmt)




The output of calculation:

Payment = -2639.184201


Example 2 - Yield to Maturity


Consider a semi-annual bond with the par value of $100, coupon rate 6% and 10 years to maturity, currently selling at $80. What is the yield-to-maturity ?


r = 2*TVM(n=10*2, pmt=6/2, pv=-80, fv=100).calc_r()

print("Interest Rate = %f" % r)

The output of calculation:


Interest Rate = 0.090866


Example 3 - Arbitrage-free pricing of a bond


Consider an annual bond with par value of $100, coupon rate 5% with 8 years to maturity. Calculate an arbitrage-free price of such bond assuming that market interest rate is 6%.

pv = TVM(r=.06, n=8, pmt=5, fv=100).calc_pv()

print("Present Value = %f" % pv)

Output of the calculation:


Present Value = -93.790206


Note: For simplicity, we have discussed TVM calculations only in "END" mode. However, the implementation supports also "BGN" mode.

Next Time: Spot rates, forward rates and bootstrapping of yield curves

No comments:

Post a Comment