Wednesday, November 7

Binomial Option Pricing Model

Link: IPython notebook

So far we have been discussing mostly pricing and valuation of asset classes with certain and predictable cash flows, such as bonds, loans, bank deposits and others. The certainty of cash payments allowed us to price such instruments using time value of money framework.

However, the situation gets somewhat more complicated when the cash flows become uncertain or optional, as in case of stock options, pre-payable mortgages, interest-rate collars or other contingent claims. In fact, many complex financial instruments have embedded optionality to some extent.

When we consider optionality of cash flows, we usually think in terms of probabilities of such cash flow occurring. In most of the cases we can simplify that whether the cash flow does or doesn't occur is determined by the underlying embedded option. The options valuation is therefore quite fundamental topic of quantitative finance.

In this article, we will discuss Cox-Ross-Rubinstein Option Pricing Model. The model is using binomial tree to value american and European-style call and put options. The aim of this article is to analyze and explain this model on a numerical example and to compare calculated results with the real market prices.

For the purposes of this post, I have also prepared a case study implementation in Python.

Input Data and Case Study

Through this article, we will consider a simple plain-vanilla American-style call option giving the right to buy the underlying instrument at predetermined time and strike price. The important factors to consider would be how the current market value of instrument is far from the strike price, how volatile the market is, how much time is there left till the option expires and what is the risk-free rate and dividend yield.

All these factors constitute inputs to the option pricing model. Let's assume the underlying instrument to be iShares S&P 500 Index ETF, as of 5th of November 2012. The price series in attached data file looks as follows (Source: Google Finance):

date, open, high, low, close, volume
2012-11-01 00:00:00,142.23,143.58,142.13,143.46,628236
2012-11-02 00:00:00,144.28,144.28,142.0,142.1,569903
2012-11-05 00:00:00,141.94,142.73,141.53,142.41,345956

To retrieve the price history from Google Finance in Python, we can use the code already present in the module:

import as google
prices = google.getquotesfromweb('IVV').getprices()prices = prices[-250:]  # We will use last 250 trading days

For the valuation, we will need additional information, such as Dividend Yield and Risk Free Rate. The latest price and dividend yield on iShares S&P 500 Index ETF can be retrieved also from Google Finance:

The risk-free rate is derived from US treasury yields with maturity date similar to the option's expiry date, as of 5th of November 2012. In this case, 1 month yield seems to be the most appropriate. We can safely assume that there is virtually no credit risk for both US Treasuries and option contracts, since their performance is guaranteed by US government and clearing houses, respectively:

As we have already mentioned, the option being valued is an American Call on iShares S&P 500 Index ETF. We will choose the option with strike price 140, expiring on 2012/12/22. Given the valuation date of 2012/11/05, the time to expiration is precisely 46 calendar days.

Instrument Volatility Calculation

Calculation of instrument's volatility may be a bit tricky because of several reasons. The most important one is question how long history of data we should consider. If the data window is too short, we may end up with insufficient number of samples with very little statistical significance. On the other hand, looking to far to the past includes a risk of underlying structural changes which make the outcome irrelevant as well. Choosing a time-frame of 1 year (250 trading days) sounds like a reasonable solution.

First what we need to do is to calculate daily returns from the above price history. Bearing in mind the fact that daily returns are continuously compounded, we need to apply a logarithm function to get the nominal rates:
$$ e^{r} = \frac{p_i}{p_{i-1}} \; ; \; r = log \frac{p_i}{p_{i-1}} $$
Daily volatility is then defined as a standard deviation of these returns. Annualized figure will be calculated as follows:
$$ stdev\left ( R \right ) \times \sqrt{250} $$
The Python code performing the whole calculation just reflects this principle:

returns = []
for i in range(0len(prices)-1):
    r = log(prices[i] / prices[i-1])

volat_d = numpy.std(returns)    # Daily volatility
volat = volat_d * 250**.5       # Annualized volatility

the output of this calculation is an annualized volatility of 18.2%, which will be used later as an input to the pricing model:

print('Volatility %0.3f' % volat)
Volatility : 0.182

Another solution would be to imply the volatility from other options' market prices using the reverse valuation process, instead of trying to calculate it directly from price data. The advantage of implied volatility approach is reduced model risk of choosing inappropriate parameters.

Generating a Binomial Tree

To generate the binomial tree, we first need to summarize all relevant inputs for the pricing model which we have mentioned previously. The inputs are as follows:

     Price : 142.410
    Strike : 140.000
 Risk-free :   0.001
 Div Yield :   0.020
  TTE Days :  46.000
Volatility :   0.182

From the given risk-free rate, dividend yield and annual volatility, we will calculate the up and down movements for each node of the tree. Assuming the binomial tree with 8 levels and 46 days to expiration, it turns out that each level represents step of 5.75 days, or 0.015753 years. This is denoted as \(t_\bigtriangleup=0.015753\).
Up and down price movements per step (u, d) are then calculated by de-annualizing instrument's volatility \(\nu\) from previous section and converting it back to an (expected) continuous daily return:
$$  u = e^{\nu \cdot \sqrt{t_{\bigtriangleup}}} \; ; \; d = \frac{1}{u} $$
These price movements must be balanced in terms of the probability in such a way that expected outcome will yield into the arbitrage-free future price determined by the risk-free rate and dividend yield:
$$ u \cdot \pi_u + d \cdot \pi_d = e^{(r_f - dy) \cdot t_{\bigtriangleup}} $$
where \(\pi_u\) is the probability of up movement, \(\pi_d = 1-\pi_u\) is the probability of down movement, \(r_f\) is the risk-free rate and \(dy\) is dividend yield.
Given the inputs above, generated binomial three with 8 levels would graphically look as follows:

Backward Reduction of the Tree

As soon as the binomial tree is generated using the up and down turns, we will use it to value options. It is quite apparent that options just before the expiration have no uncertainty or time value associated, so their whole appraisal consists only from the intrinsic value given as a difference between the strike price (140) and underlying instrument's price (black figures in the terminal nodes of tree). If the option is out-of-money, it's price is naturally zero, as portrayed by the bottom half of the terminal nodes.

In fact, we are not interested in options' value at their expiration, but at the present time. Hence, we will use non-terminal nodes of the tree to bring the terminal option value back to the present. This is done by a reduction of the binomial tree as depicted by red arrows.

Value of European-style option at any node is again determined by the probabilistic outcome of up and down movements, discounted to the present using a risk-free rate. For American-style options, we need to bear in mind also an early exercise scenario, where the node is prematurely terminated, similarly as other terminal nodes in the 8th level. The option value for any such node is then calculated as follows:
$$ v = max \left( \frac{v_u \cdot \pi_u + v_d \cdot \pi_d}{e^{r_f \cdot t_{\bigtriangleup}}} \; ; \; p - s\right) $$
where \(v\) and \(p\) are the option value and instrument price at given node, \(s\) is the strike price, \(u\) and \(d\) are up and down movements, \(\pi_u\) and \(\pi_d\) are probabilities of these movements, and \(e^{r_f \cdot t_{\bigtriangleup}}\) is a risk-free discount factor.
If we speak in terms of concrete figures, option value in the starting node is calculated as an expected value in the successive nodes \((0.488 \times 6.924 + 0.512 \times 2.791=4.899\)), which is almost the same as after application of the discount factor \(e^{r_f \cdot t_{\bigtriangleup}}\) (0.07% treasury yield is negligible on a daily basis for the purpose of this example).
In case of the option being exercised prematurely, it's intrinsic value would be \(p-s = 2.41\), which is less than the expected value. The option value at starting node is therefore 4.899.

The corresponding Python implementation of tree reduction algorithm would look similarly to this:

# Generate terminal nodes of binomial tree
level = []
print('Tree level %i' % n)
for i in range(0, n+1)# Iterate through nodes
    # Instrument's price at the node
    pr = price * d*** u**(n-i)    
    # Option value at the node (depending on side)
    ov = max(0.0, pr-strike) if side==call else max(0.0, strike-pr)
    level.append((pr, ov))
    print('Node Price %.3f, Option Value %.3f' %(pr, ov))
levels = [None,None,None] # Remember levels 0,1,2 for the greeks

# reduce binomial tree
for i in range(n-1, -1, -1)# [n-1 to 0]
    levelNext = []
    print('Tree level %i' % i)
    for j in range(0, i+1)# Iterate through nodes
        node_u, node_d = level[j], level[j+1]
        # Instrument's price at the node
        pr = node_d[0] / d
        # Option value at the node (depending on side)
        ov = (node_d[1] * pd + node_u[1] * pu) / (1 + rf)   
        if style==american: # American options can be exercised anytime
            ov = max(ov, pr-strike if side==call else strike-pr)
        levelNext.append((pr, ov))
        print('Node Price %.3f, Option Value %.3f' %(pr, ov))
    level = levelNext
    if j<=2: levels[j]=level # save level 0,1,2 of the tree

The Real Market

We originally based our case study on the real market conditions as of 2012/11/05, taking into the consideration underlying instrument's market data from Google Finance and Financial Times. Google also provides market data for options traded on NYSE Arca.

As we can see from the following screenshot, the option being discussed and valued in this article is currently trading at 4.500 - 4.900, which is fairly close to the theoretical option price of 4.899.

The difference between theoretical value and real market price could have several possible causes, such as the model risk or inefficiency of a market itself. As we have seen, there have been done lots of assumptions regarding model parameters and it would be also reasonable to expect that not all analysts are using the same estimates.

It is also worth noting that there may be significant fluctuations in model's output depending on the length of volatility look-back window, mostly due to volatility clustering and conditional heteroskedasticity. For instance, a time-frame of 100, 200 and 250 days would lead to valuations of 4.714, 4.339 and 4.899. As we have mentioned previously, this problem could be partially solved by implying volatility from option market prices.

The Greeks

Apart of the option value at the head node, we can extract few other figures from the tree, such as option's delta, gamma and theta. For this purpose, we need to process first three levels of the tree:

Delta is the rate of change of option's value with respect to changes in underlying instrument's price. Looking to the figure above, this rate is derived from nodes 10 and 11 as follows:
$$ \delta =\frac{v_{10} - v_{11}}{p_{10} - p_{11}} =  \frac{6.924 - 2.971}{145.701 - 139.194} = 0.607 $$
Gamma is a rate of change of option's delta with respect to changes in underlying instrument's price. Again, we first need to calculate deltas in nodes 10 and 11:
$$ \delta_{10} =\frac{v_{20} - v_{21}}{p_{20} - p_{21}} \; ; \; \delta_{11} =\frac{v_{21} - v_{22}}{p_{21} - p_{22}} $$
Gamma is then calculated as:
$$ \gamma = \frac{\delta_{10}-\delta_{11}}{p_{10} - p_{11}} = 0.022 $$
Theta is sensitivity of option's value with respect to the passage of time. Please note that nodes 00 and 21 have the same instrument's price, only the option's value differs. It is therefore possible to derive option's theta as follows:
$$ \theta = \frac{v_{21} - v_{00}}{2 \times t_{\bigtriangleup}} = -13.157 $$

Saturday, October 20

Treasury Yield Curve Bootstrapping

Link: IPython notebook

In the previous post, we have introduced readers to basic principles of time value of money and presented Python implementation of the calculator. The time value of money is an essential principle applied in almost all areas of the financial mathematics. Today, we will discuss one of them - the basics of yield curve construction and bootstrapping. Please note that full implementation of this example can be found here.

Calculation of Yield Curve from Market Prices

When calculating yield curves from  market prices, the big question is which securities we should consider in the calculation. If we generalize the problem to a set of credit risk-free government securities, our choices will be as follows:
  • All on-the-run securities
  • All on-the-run and some off-the-run securities (to fill the gaps)
  • All securities (both on and off-the run, with aggregated YTMs)
On-the-run securities are those which have been issued recently, thus the most liquid ones. Unfortunately, there are often wide maturity gaps between them. Because of this, analysts often fill these gaps with additional off-the-run issues. For example, a 50-year bond issued 2 years ago effectively serves as a security filling the maturity gap of 48 years.

It is even possible to combine all on and off-the run securities in such a way they will expire on the same date. This will naturally lead to collisions in yields and maturities, which can be then aggregated and interpolated for even better results.

Another solution is to use treasury coupon strips. The coupon strip with a single payment is priced in a way which yields directly to the spot rate for given maturity. With this approach, the bootstrapping process discussed later will not be necessary. However, the drawback using coupon strips is different tax treatment skewing the whole calculation.

With respect to the facts above and for the purpose of this simple example, we will be using on-the-run market prices of UK Gilts, as of 19th of September 2012. The quoted gilts span maturities from 6 months to 48 years, sufficient enough for the sake of this analysis. The relevant prices were obtained from and look as follows:

epic, description,          coupon, maturity,  bid,    ask
TR13, Uk Gilt Treasury Stk, 4.5,    07-Mar-13, 101.92, 102.07
T813, Uk Gilt Treasury Stk, 8,      27-Sep-13, 107.86, 107.98
TR14, Uk Gilt Treasury Stk, 2.25,   07-Mar-14, 102.90, 103.05

Calculated yields to maturity don't necessarily correspond to those quoted in data file due to accrued interest and a fact that coupon payments are bound to specific calendar date, which is not necessarily one semi-annual period from now.

The code performing the calculation in Python would look as follows. The input is a set of bonds, each with given maturity, price and coupon rate. These values are passed into the TVM calculator introduced in one of the previous articles to calculate the bond's yield to maturity:

tr = [] # list of raw (not interpolated) times to maturity
yr = [] # list of raw (not interpolated) yields
for b in bonds:
    ttm = (b.maturity - localtime).days / 360
    price = (
    ytm = TVM(n=ttm*b.freq, pv=-price, pmt=b.couponRate/b.freq, fv=1).calc_r() * b.freq

These yields, together with maturities are then aggregated in lists tr (time to maturity) and yr (yield to maturity)

0.47    0.23%
1.04    0.34%
1.48    0.24%
1.99    0.30%
...     ...    


As the time passes, the maturities of all issues are shortened by one day each day. However, if we want to compare the same yield curve across different dates, the maturities must match. Hence, we need to interpolate and normalize all yield curves into a common set of maturities, e.g. 1m, 3m, 6m, 1y, 2y, 5y and etc.

On the other hand, for the purpose of bootstrapping, it would be convenient to interpolate all maturities into a  scale with annual intervals, such as from 1 year to 40 years. Therefore, we will go this way.

The interpolation is performed in Python automatically using SciPy's interp1d command. Apart of the linear interpolation, it provides few others, such as nearest, cubic or quadratic. Unfortunately this algorithm is not able to interpolate outside the initial range - this could be problematic especially for short-term maturities, such as 1 month:

t = list(for i in range(1,41)) # interpolating in range 1..40 years
y = []
interp = scipy.interpolate.interp1d(tr, yr, bounds_error=False, fill_value=scipy.nan)
for i in t:
    value = float(interp(i))
    if not scipy.isnan(value)# Don't include out-of-range values

The output of this step would be interpolated yield curve y with maturities t=1..40

TTM     YTM (Interpolated)
1.00    0.33%
2.00    0.30%
3.00    0.40%
4.00    0.60%
...     ...

Bootstrapping of spot rates

Before going into details regarding the bootstrapping algorithm, we should explain the difference between yield curve and spot rate curve.

By definition, the yield curve shows several bond yields to maturity (ytm) across different bond contract lengths, or times to maturity (ttm). Yield to maturity is an overall discount rate which equalizes principal and coupon payments to the initial investment value, assuming reinvestability of all cash flows.

In contrast to the yield curve, a spot rate curve represents spot rates used to discount individual cash flows of the bond. Hence, a whole range of different spot rates is typically used when equalizing bond's future cash flows to its present value.

To bootstrap the yield curve, we will be building upon a fact that all bonds priced at par have coupon rate equal to the yield-to-maturity, as denoted in the following equation:

$$ \frac{C}{\left ( 1+r \right )^1} + \frac{C}{\left ( 1+r \right )^2}+...+\frac{1+C}{\left ( 1+r \right )^n} = $100 $$
Given the par value is $100, coupon rate \(C\) is equal to \($100*r\)

Starting from the annual coupon bond which matures in one year, we will gradually derive all spot rates by forward substitution of the previously calculated ones. This can be best illustrated on a numerical example. To make the example more obvious, we will use exaggerated yields as opposed to the real yield curve calculated in previous step:

TTM     YTM (Interpolated)
1.00    3.00%
2.00    5.00%
3.00    7.00%
...     ...

Starting from the bond which pays both annual coupon and principal in one year, the yield-to-maturity is apparently the same as the first and single spot rate \(y_1 = s_1 = 3\%\). Hence, the following equation holds:
$$ \frac{$100+$3}{\left ( 1+0.03 \right )^1} = $100 $$
In the next step, we will put into the equation a bond maturing in two years. In this case, yield to maturity will be y_2 = 5\%. We will also employ the spot rate \(s_1=3\%\) "calculated" in the previous step. Please bear in mind that since this bond is also priced at par, the coupon rate will be this time 5%, or $5:
$$ \frac{$5}{\left ( 1+0.03 \right )^1} + \frac{$100+$5}{\left ( 1+s_2 \right )^1} = $100 $$
Solving this equation for the second spot rate will lead to the root \(s_2 = 5.0510\%\).

Similarly, solving all other rates iteratively in the same manner will lead to the following spot rate curve:

TTM     Spot Rate 
1.00    3.0000%
2.00    5.0510%
3.00    7.1979%
...     ...

The Python code implementing the calculation described above would look as follows. The spot rates will be populated in output list s, based on the same time scale t as the original YTM rates y

s = [] # output array for spot rates
for i in range(0len(t))#calculate i-th spot rate 
    sum = 0
    for j in range(0, i)#by iterating through 0..i
        sum += y[i] / (1 + s[j])**t[j]
    value = ((1+y[i]) / (1-sum))**(1/t[i]) - 1

Final result

We are of course interested mostly in final results based on the real market data. Previously in this article, we  gathered and interpolated on-the-run market prices of UK Gilts, as of 19th of September 2012. Based on these prices, the original and interpolated yield curves and spot rates curve would look as follows:

Please note that in this particular example, there is not a big difference between yield curve and spot rate curve, especially for short term maturities. This is due to low-yield nature of government bonds. If the coupon rates were bigger, the difference between spot and YTM rates would be more evident as well.

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 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:


The value of Annuity B in the future money is:


, which gives us the today's valuation of 


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


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


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" % (
    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)
            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