jrvFinance Usage

Jayanth R. Varma

2021-11-05

Contents

Introduction

The jrvFinance R package implements the basic financial analysis functions similar to (but not identical to) what is available in most spreadsheet software. This includes finding the IRR and NPV of regularly spaced cash flows and annuities. Bond pricing and YTM calculations are included. In addition, Black Scholes option pricing and Greeks are also provided.

library(jrvFinance)

NPV and IRR

Find Net Present Value (NPV) at 5% of cash flows of 100, 250 and 300 in years 1, 2 and 3 respectively:

npv(cf=c(100,250,300), rate=5e-2)
## [1] 581.1467

Find NPV at 10% of cash flows of 1, 3 and 2 in years 0.3, 1.9 and 2.5 respectively (this is known in XNPV in spreadsheet software):

npv(cf=c(1,3,2), rate=10e-2, cf.t=c(0.3,1.9,2.5))
## [1] 5.050866

Find the rate of interest (IRR) if a loan of 600 is repaid in two annual instalments of 300 and 400:

irr(c(-600,300,400))
## [1] 0.1039126

Negative interest rates are handled without difficulty

irr(c(-600,100,400))
## [1] -0.09592852

Find the rate of interest if a loan of 450 is repaid by paying 100 after 0.3 years, 300 after 1.9 years and 200 after 2.5 years (this is known in XIRR in spreadsheet software)

irr(cf=c(-450,100,300,200), cf.t=c(0, 0.3,1.9,2.5)) 
## [1] 0.1746131

Find multiple IRRs by providing interval within which to search for the IRR:

irr(cf=c(100, -230, 132), interval = c(0.05, 0.17)) 
## [1] 0.1
irr(cf=c(100, -230, 132), interval = c(0.17, 0.50)) 
## [1] 0.2

Find multiple IRRs by providing different guess values for the IRR:

irr(cf=c(100, -230, 132), r.guess = 0) 
## [1] 0.1
irr(cf=c(100, -230, 132), r.guess = 0.5) 
## [1] 0.2

Duration of general cash flows

There is a separate set of functions for computing duration for bonds. In this section, we look at the functions for computing duration of general sequence of cash flows.

Find duration of a stream of cash flows of 100, 250 and 300 in years 1, 2 and 3 when the interest rate is 5%:

duration(cf=c(100,250,300), rate=5e-2)
## [1] 2.282051

Find modified duration of same cash flows:

duration(cf=c(100,250,300), rate=5e-2, modified=TRUE)
## [1] 2.173382

Annuity functions

Find the present value at 10% of 15 annual instalments of 1 each period:

annuity.pv(rate=10e-2, n.periods=15)
## [1] 7.60608

Find present value at 10% of 15 annual instalments of 1 each period where the annuity starts immediately:

annuity.pv(rate=10e-2, n.periods=15, immediate.start = TRUE)
## [1] 8.366687

Find the present value at 7% of a perpetuity of 35 per annum :

annuity.pv(rate=7e-2, instalment = 35, n.periods=Inf)
## [1] 500
## or more simply
annuity.pv(rate=7e-2, instalment = 35)
## [1] 500

Consider an annuity of 360 monthly instalments (30 years) where each instalment is 450. Find the present value of the above annuity at 10% semi-annually compounded:

annuity.pv(rate=10e-2, instalment = 450, n.periods=360, cf.freq=12, comp.freq=2)
## [1] 52163.75

Find the semi-annually compounded rate if the above annuity has present value of 50,000:

annuity.rate(pv=50000, instalment = 450, n.periods=360, cf.freq=12, comp.freq=2)
## [1] 0.1052609

Negative interest rates are handled without difficulty:

annuity.rate(pv=250000, instalment = 450, n.periods=360, cf.freq=12, comp.freq=2)
## [1] -0.02685041

Suppose a loan of 10,000 is to be repaid in 8 equal annual instalments and the interest rate is 9% annually compounded. Find the annual instalment:

annuity.instalment(rate=9e-2, pv=10000, n.periods=8)
## [1] 1806.744

Breakup the 5th instalment of above annuity into its principal and interest components:

AIB <- annuity.instalment.breakup(rate=9e-2, pv=10000, n.periods=8,
                                  period.no=5)
# we use unlist to print the result more compactly
unlist(AIB)
## opening.principal     interest.part    principal.part closing.principal 
##         5853.3437          526.8009         1279.9428         4573.4009

Bond Price, Yield and Duration

Find price on 15 April 2012 of 8% bond maturing on 1 January 2022 at 8.8843% yield (By default, all the bond functions assume semi-annual coupons and semi-annually compounded yield):

bond.price(settle="2012-04-15", mature="2022-01-01", coupon=8e-2,
           yield=8.8843e-2)
## [1] 94.30449

Same bond with annual coupons and annually compounded yield (We use the freq argument to change the coupon frequency. By default, compounding frequency of the yield is assumed to be the same as the coupon frequency):

bond.price(settle="2012-04-15", mature="2022-01-01", coupon=8e-2,
           yield=8.8843e-2, freq=1)
## [1] 94.33212

Same bond with annual coupon and semi-annually compounded yield (We set the comp-freq argument to use a compounding frequency different from the coupon frequency):

bond.price(settle="2012-04-15", mature="2022-01-01", coupon=8e-2,
           yield=8.8843e-2, freq=1, comp.freq=2)
## [1] 93.13936

A bond with 5% coupon is callable at 102 on 1-Jan-2023 and is trading at an yield to call of 6% on 1-Jan-2018. The price is:

bond.yield(settle='2018-01-01', mature='2023-01-01', coupon=5e-2, price=101,
           redemption_value = 102)
## [1] 0.05126732

Compute the yield given the price:

bond.yield(settle="2012-04-15", mature="2022-01-01", coupon=8e-2,
           price=95) 
## [1] 0.08772274

Negative yields are handled properly:

bond.yield(settle="2012-04-15", mature="2017-01-01", coupon=1e-2,
           price=120) 
## [1] -0.02927941

Yield to call:

bond.yield(settle='2018-01-01', mature='2023-01-01', coupon=5e-2, price=101,
           redemption_value = 102)
## [1] 0.05126732

Duration on 15 April 2012 of 8% bond maturing on 1 January 2022 at 8.8843% yield (semi-annual coupons and semi-annually compounded yield)

bond.duration(settle="2012-04-15", mature="2022-01-01", coupon=8e-2,
              yield=8.8843e-2)
## [1] 6.678709

Modified duration of same bond:

bond.duration(settle="2012-04-15", mature="2022-01-01", coupon=8e-2,
              yield=8.8843e-2, modified=TRUE)
## [1] 6.394649

Find prices of many bonds with one function call:

bond.prices(settle="2012-01-01", mature=c("2022-01-01", "2032-01-01"),
            coupon=c(0.09, 0.08,0.07,0.06), yield=0.10)
## [1] 93.76889 82.84091 81.30668 65.68183

Find yields of many bonds with one function call

bond.yields(settle="2012-01-01", mature=c("2022-01-01", "2032-01-01"),
            coupon=c(0.09, 0.08,0.07,0.06),
            price=c(94, 83, 81, 65))
## [1] 0.09961336 0.09978548 0.10056533 0.10110332

Find durations of many bonds with one function call:

bond.durations(settle="2012-01-01",
               mature=c("2022-01-01", "2032-01-01"),
               coupon=c(0.09, 0.08,0.07,0.06),  yield=0.10)
## [1] 6.681623 9.385458 7.023446 9.959342

Find all the coupon dates of a bond:

coupons.dates(settle="2012-04-15", mature="2022-01-01")
##  [1] "2012-07-01" "2013-01-01" "2013-07-01" "2014-01-01" "2014-07-01"
##  [6] "2015-01-01" "2015-07-01" "2016-01-01" "2016-07-01" "2017-01-01"
## [11] "2017-07-01" "2018-01-01" "2018-07-01" "2019-01-01" "2019-07-01"
## [16] "2020-01-01" "2020-07-01" "2021-01-01" "2021-07-01" "2022-01-01"
coupons.dates(settle="2012-04-15", mature="2022-01-01", freq=1)
##  [1] "2013-01-01" "2014-01-01" "2015-01-01" "2016-01-01" "2017-01-01"
##  [6] "2018-01-01" "2019-01-01" "2020-01-01" "2021-01-01" "2022-01-01"

Find a specific coupon date:

coupons.next(settle="2012-04-15", mature="2022-04-01")
## [1] "2012-10-01"
coupons.prev(settle="2012-04-15", mature="2022-04-01")
## [1] "2012-04-01"

Find the number of coupons:

coupons.n(settle="2012-04-15", mature="2017-07-01")
## [1] 11

(Generalized) Black Scholes Formulas

Find Black Scholes options values and Greeks when spot price and strike = 100, interest rate = 10%, volatility = 20%, maturity = 1 year:

BS <- GenBS(s=100, X=100, r=0.1, Sigma=20e-2, t=1, div_yield=0)
# we use unlist to print the result more compactly
unlist(BS)
##             call              put Greeks.callDelta  Greeks.putDelta 
##      13.26967658       3.75341839       0.72574688      -0.27425312 
## Greeks.callTheta  Greeks.putTheta     Greeks.Gamma      Greeks.Vega 
##      -9.26274719      -0.21437301       0.01666123      33.32246029 
##   Greeks.callRho    Greeks.putRho         extra.d1         extra.d2 
##      59.30501164     -31.17873016       0.60000000       0.40000000 
##        extra.Nd1        extra.Nd2   extra.Nminusd1   extra.Nminusd2 
##       0.72574688       0.65542174       0.27425312       0.34457826 
##   extra.callProb    extra.putProb 
##       0.65542174       0.34457826

Next we illustrate the generalized Black Scholes formulas where the asset pays a continuous dividend yield. This is useful for foreign currency options, options on futures and options on stock indices.

Spot price =100, strike = 120, interest rate = 10%, volatility = 15%, maturity = 1 year, and dividend yield = 5.8%:

GBS <- GenBS(s=100, X=120, r=0.1, Sigma=15e-2, t=1, div_yield=5.8e-2)
# we use unlist to print the result more compactly
unlist(GBS)
##             call              put Greeks.callDelta  Greeks.putDelta 
##       1.42577503      15.64127045       0.18378816      -0.75986179 
## Greeks.callTheta  Greeks.putTheta     Greeks.Gamma      Greeks.Vega 
##      -2.57918601       2.80569331       0.01733203      25.99804337 
##   Greeks.callRho    Greeks.putRho         extra.d1         extra.d2 
##      16.95304065     -91.62744951      -0.86047705      -1.01047705 
##        extra.Nd1        extra.Nd2   extra.Nminusd1   extra.Nminusd2 
##       0.19476307       0.15613340       0.80523693       0.84386660 
##   extra.callProb    extra.putProb 
##       0.15613340       0.84386660

Find implied volatility when call price is 7.97 ,spot price and strike = 100, interest rate zero, and maturity is 1 year:

GenBSImplied(s=100, X=100, r=0, price=7.97, t=1, div_yield=0)
## [1] 0.2001117

Same as above but strike = 90. Since the option price is less than intrinsic value, the implied volatility is undefined:

GenBSImplied(s=100, X=90, r=0, price=7.97, t=1, div_yield=0)
## Warning in GenBSImplied(s = 100, X = 90, r = 0, price = 7.97, t = 1, div_yield =
## 0): Implied volatility is undefined because price is outside theoretical bounds
## [1] NA

Same as above but strike = 900. Since the strike is so far out of the money, a rather high implied volatility is needed for the option to be valuable:

GenBSImplied(s=100, X=900, r=0, price=7.97, t=1, div_yield=0)
## [1] 1.370036

Utility functions

Equivalent rate with different compounding frequency

Convert 10% monthly compounded rate to equivalent semi-annually compounded rate

equiv.rate(10e-2, from.freq = 12, to.freq = 2) 
## [1] 0.1021066

Convert 15% annually compounded rate to equivalent continuously compounded rate

equiv.rate(15e-2, from.freq = 1, to.freq = Inf)
## [1] 0.1397619

edate

edate is a common spreadsheet function which is used in this package for finding coupon dates. It is made available for general use.

Find date eight months prior to 17 May 2005:

edate("2005-05-17", -8) 
## [1] "2004-09-17"

Find date four months after 28 February 2007. Note that the output is the last day of June and not the 28th June

edate("2007-02-28", 4) 
## [1] "2007-06-30"

But things change in a leap year

edate("2008-02-28", 4) 
## [1] "2008-06-28"

Newton Raphson root solver

The package implements a Newton Raphson root solver that is used internally to calculate IRR and YTM. It is available for general use.

Find root of the equation sin(x) = cos(x). (Analytically, the root is known to be π/4)

fn1 <-function(x){list(value=sin(x)-cos(x), gradient=cos(x)+sin(x))} 
newton.raphson.root(fn1) 
## [1] 0.7853982

Try to find root of equation which has no real root: sin(x) = -2

fn2 <-function(x){list(value=2 + sin(x), gradient=cos(x))}
newton.raphson.root(fn2) 
## Warning: newton.raphson.root failed: Converged to a non zero local minimum
## [1] NA

Bisection root solver

The package implements a bisection root solver that does a geometric grid search to bracket the root and then calls uniroot to find the root within this interval. The package uses the function internally to calculate IRR and YTM, but bisection.root is available for general use.

For example, the values of 1, 7, 12 and 13 do not bracket the root of sin(x) since the function is positive at all these values, but bisection.root finds the root given these numbers as the interval and the guess value.

bisection.root(sin, guess = 7, lower=1, upper=13)
## [1] 6.283185
bisection.root(sin, guess = 12, lower=1, upper=13) 
## [1] 9.424778