Sie sind auf Seite 1von 117

Financial engineering in Python

Contents
0. Introduction .............................................................................................................................................. 3
1. Interest rate curves, fixed income instruments ........................................................................................ 4
1.1. Non-risky bond pricing and associated measures ............................................................................. 4
1.1.1 Bond prices when having (treasury) zero rates ........................................................................... 4
1.2 Bootstrapping................................................................................................................................ 13
1.3 Bond yield ......................................................................................................................................... 15
1.4 Par yield............................................................................................................................................. 19
1.5 Duration and convexity ..................................................................................................................... 20
1.5.1 Python code for duration ........................................................................................................... 20
1.6. Forward rates and Forward rate agreements .................................................................................. 21
Python code for forward rates from zero rates .................................................................................. 21
1.6.2 Forward rate agreements .......................................................................................................... 24
1.7. Bond that include a certain probability of default. Models for default times and associated
measures. ................................................................................................................................................ 26
Methodology....................................................................................................................................... 27
1.8 Linear systems and bootstrapping .................................................................................................... 29
Forwards, Futures and other financial contracts ........................................................................................ 30
Forward rate agreements ....................................................................................................................... 30
Forward valuation ................................................................................................................................... 31
Forward and future contracts on currencies .......................................................................................... 33
Futures on commodities ......................................................................................................................... 33
Interest rate futures................................................................................................................................ 33
Day counts........................................................................................................................................... 33
Eurodollar futures ................................................................................................................................... 34
Treasury bond futures............................................................................................................................. 34
EXERCISES................................................................................................................................................ 35
Swaps. Interest rate, currency and credit ................................................................................................... 36
1. Fixed for floating interest rate swaps ................................................................................................. 36
2. Currency swaps ................................................................................................................................... 41
Cross-currency swaps.......................................................................................................................... 44
3. Credit Default Swaps ........................................................................................................................... 44
4. First to Default Swaps, Second to Default swap ................................................................................. 50
5. Basket Default Swaps .......................................................................................................................... 50
Options. ....................................................................................................................................................... 50
1. Plain vanilla european options. Binomial pricing method. ................................................................. 50
2. Plain vanilla european options. Black Scholes pricing. Implied volatility. .......................................... 51
Implied volatility ................................................................................................................................. 52
Digital options. General binomial price depending only on the terminal value ................................. 53
3. Spreads................................................................................................................................................ 55
4. Covered strategies .............................................................................................................................. 63
5. Combinations ...................................................................................................................................... 65
6. American options ................................................................................................................................ 66
7. Currency options and stock indices options ....................................................................................... 68
Currency options ................................................................................................................................. 68
Index options and hedging.................................................................................................................. 71
Currencies, Ito calculus and implied volatility .................................................................................... 72
Hedging payments in foreign currency ............................................................................................... 74
Greeks. ........................................................................................................................................................ 74
1. Delta and Gamma of plain vanilla options .......................................................................................... 74
2. Theta ................................................................................................................................................... 79
3. Vega and rho of a call and put. ........................................................................................................... 80
4. Examples of dynamic hedging............................................................................................................. 80
Exotic options.............................................................................................................................................. 80
Barrier option pricing .............................................................................................................................. 80
Monte Carlo method........................................................................................................................... 81
Python code for Monte Carlo method ................................................................................................ 81
Python code for binomial trees method ............................................................................................. 82
Lookback options .................................................................................................................................... 85
Monte Carlo method........................................................................................................................... 85
Python code for Monte Carlo method ................................................................................................ 86
Asset Backed Securities............................................................................................................................... 86
Stochastic models for interest rates ........................................................................................................... 86
a) Vasicek model (Ornstein-Uhlenbeck process) for short rates ........................................................ 86
b) Hull – White model ......................................................................................................................... 88
c) Cox-Ingersoll Ross model .................................................................................................................... 88
d) Bond pricing under each model.......................................................................................................... 88
i. Bond pricing under Vasicek model................................................................................................... 88
Interest rate derivatives.............................................................................................................................. 90
Bond options ........................................................................................................................................... 90
Preliminaries: Black Model ................................................................................................................. 90
European bond options ...................................................................................................................... 90
Caps and Floors ....................................................................................................................................... 94
The cap as a portfolio of interest rate options ................................................................................... 95
Valuation of Caps and Floors .............................................................................................................. 95
Swaptions ................................................................................................................................................ 96
Revision ................................................................................................................................................... 97
Solutions to proposed problems................................................................................................................. 97
APPENDIX .................................................................................................................................................... 97
Bond pricing ............................................................................................................................................ 97
Bond price and rates: .............................................................................................................................. 98
Geometric brownian motion methods ................................................................................................. 102
Market Risk measures. VaR. ................................................................................................................. 103
Historical VaR .................................................................................................................................... 103
Expected shortfall ............................................................................................................................. 105
Analytical VaR ................................................................................................................................... 106
Counterparty credit risk ........................................................................................................................ 109
Exposure measures: EE, EPE, EEE, EEPE ............................................................................................ 109

0. Introduction
The purpose of this book will be to give a free set of tools in valuation, interpretation of financial
instruments, both primary and derivatives as well as challenge the reader to improve the codes I’ve
made and make their own projects out of them.

The target of readers will be for both undergraduate / graduate students in Quantitative Finance areas
as well as junior / less junior professionals. Suggestions from both sides are welcome in order to
improve this book and to further updates of this version.
The structure of each section will be as follows:

1. Presentation of notions and methodology

2. Coding

3. Tests, results and remarks.

4. Solved and proposed exercises.

Sometimes proofs will be given for certain mathematical or financial engineering facts:
theorems/formulas.

1. Interest rate curves, fixed income instruments


1.0. Introduction

The purpose of this chapter will be to understand the zero rate curves, forward rate curves and swap
rates as well as pricing the most known fixed-income instruments such as bonds, loans, swaps, forward
rate agreements, etc.

1.1. Non-risky bond pricing and associated measures


Associated measures means: yield, par yield, duration, modified duration, convexity, accrued coupon.

We will discuss here these when there is assumed no risk in bonds.

REMARK: Throughout the book, the assumed compounding is continuous if not mentioned otherwise.

1.1.1 Bond prices when having (treasury) zero rates


General methodology of pricing bonds

Assuming a flat interest term rate with 𝑟𝑡 = 𝑟, ∀𝑡 > 0, where 𝑡 is time to maturity and suppose we have
the cash-flows 𝐶1 , 𝐶2 , … , 𝐶𝑛 at times 𝑡1 , 𝑡2 , … , 𝑡𝑛 then the price 𝐵0 = 𝑒 −𝑟𝑡1 𝐶1 + 𝑒 −𝑟𝑡2 𝐶2 + ⋯ +
𝑒 −𝑟𝑡𝑛 𝐶𝑛 is the price of the bond at time 0.

Or, 𝐵𝑡 = ∑𝑡𝑖>𝑡 𝑒 −𝑟(𝑡𝑖−𝑡) 𝐶𝑖 . If we assume that the term-structure of zero rates is not flat, then

𝐵0 = 𝑒 −𝑟𝑡1 𝑡1 𝐶1 + 𝑒 −𝑟𝑡2 𝑡2 𝐶2 + ⋯ + 𝑒 −𝑟𝑡𝑛 𝑡𝑛 𝐶𝑛 where 𝑟𝑡1 , 𝑟𝑡2 , … , 𝑟𝑡𝑛 are zero rates with terms 𝑡1 , 𝑡2 , … , 𝑡𝑛 .

I will start with an example:

Suppose we have the following treasury zero rate curve and bond data and we want the value at time 0:

Maturity (years) Zero rate (%) (continuous Bond data


compounding)
0.5 5.0 Principal/face value: FV = 100($)
1 5.8 Lifetime: T = 2 years
1.5 6.4 Coupon rate: c = 6%
2 6.8 Payments: semi-annually (twice/ year)
Value at time 0 = ∑discounted cashflows to time 0 (cash-flows = coupons (4) + notional at maturity.
We assume here we have continuous compounding and therefore continuous discounting.

That is, the theoretical price of bond is 3𝑒 −0.05⋅0.5 + 3𝑒 −0.058⋅1.0 + 3𝑒 −0.064⋅1.5 + 103 ⋅ 𝑒 −0.068⋅2.0 =
98.39.
If the discounting was annual we would have to transform the zero-rate curve with continuous
compounding into a zero-rate curve with annual compounding.

The relation between continuous rate and annual rate is given by: exp(𝑟𝑐 ) = 1 + 𝑟1𝑌 as well as for n-
𝑅𝑛 𝑛 𝑟
frequency compounding which is exp(𝑟𝑐 ) = (1 + 𝑛
) ⇒ 𝑅𝑛 = 𝑛(exp ( 𝑛𝑐 ) − 1).

n- frequency = n times per year compounding (𝑛 ∈ {1,3,4,6,12} usually).

To solve the new << example>> problem we follow 2 steps:

1. We find the new zero-curve.

2. We sum the discounted cash-flows according to these new zero-rates.

I will build first an algorithm to solve the first problem: conversion between different types of
compounding. The conversion can be: continuous to annual, continuous to yearly, continous to semi-
annualy, continuous to quarterly, continuous to monthly, or viceversa.

def transformation_zero_curve(zero_rates,conversion = ('continuous','yearly'),\


reverse = False):
"by default the conversion considered is continuous to yearly"
if reverse == False:
if conversion[1] in {'yearly','Yearly'}:
return [np.exp(zero_rates[i])-1 for i in range(len(zero_rates))]
elif conversion[1] in {'Semi-annually','semi-annually','Semi annually'\
,'semi annually'}:
return [2*(np.exp(zero_rates[i]/2)-1) for i in range(len(zero_rates))]
elif conversion[1] in {'Quarterly','quarterly'}:
return [4*(np.exp(zero_rates[i]/4)-1) for i in range(len(zero_rates))]
elif conversion[1] in {'Monthly','monthly'}:
return [12*(np.exp(zero_rates[i]/12)-1) for i in range(len(zero_rates))]
elif reverse==True:
if conversion[1] in {'yearly','Yearly'}:
return [np.log(zero_rates[i]+1) for i in range(len(zero_rates))]
elif conversion[1] in {'Semi-annually','semi-annually','Semi annually'\
,'semi annually'}:
return [2*(np.log(zero_rates[i]/2)+1) for i in range(len(zero_rates))]
elif conversion[1] in {'Quarterly','quarterly'}:
return [4*(np.log(zero_rates[i]/4)+1) for i in range(len(zero_rates))]
elif conversion[1] in {'Monthly','monthly'}:
return [12*(np.log(zero_rates[i]/12)+1) for i in range(len(zero_rates))]
Next I will test this thing:

def generate_dataframe(zero_rates,conversion = ('continuous','yearly'),\


reverse=False):
zero_rates2 = transformation_zero_curve(zero_rates,conversion,reverse)
import pandas as pd
if reverse == False:
d = {conversion[0]:zero_rates,conversion[1]:zero_rates2}
print(pd.DataFrame(data = d,index = range(1,len(zero_rates)+1)))
else:
d = {conversion[1]:zero_rates,conversion[0]:zero_rates2}
print(pd.DataFrame(data = d,index = range(1,len(zero_rates)+1)))

zero_rates = [0.05,0.058,0.064,0.068]
generate_dataframe(zero_rates)
generate_dataframe(zero_rates,reverse=True)

Results:
continuous yearly continuous yearly
1 0.050 0.051271 1 0.048790 0.050
2 0.058 0.059715 2 0.056380 0.058
3 0.064 0.066092 3 0.062035 0.064
4 0.068 0.070365 4 0.065788 0.068
Remark: In the 2 command and column above, although the names of columns
nd

are not changed the results are proper. That means to a 5% yearly rate it
corresponds a 4.87% continuous rate while to a 5% continuous rate it
corresponds a 5.1% yearly rate (left column).

Solved exercises:

1) Starting from a series of zero rates for a series of terms, compute the rest of the interest
rates by linear interpolation by the following rules:

a) For rates {𝑟1 < 𝑟2 < ⋯ < 𝑟𝑛 } for the terms {𝑡1 < 𝑡2 < ⋯ < 𝑡𝑛 }, 𝑟𝑡 = 𝛼𝑟𝑡𝑖 +
𝑡 −𝑡
(1 − 𝛼)𝑟𝑡𝑖+1 𝑤ℎ𝑒𝑟𝑒 𝑡𝑖 ≤ 𝑡 < 𝑡𝑖+1 , and 𝛼 = 𝑖+1 .
𝑡 −𝑡𝑖+1 𝑖

b) 𝑟𝑡 = 𝑟𝑛 when 𝑡 ≥ 𝑡𝑛

c) 𝑟𝑡 = 𝑟1 when 𝑡 ≤ 𝑡1 .

The function should take as parameters, a list of zero rates, a list of times, and the third
parameter could be:

a. None. Return the rate function

b. A specific time 𝑡. Return 𝑟𝑡 .

c. A figure containing a plot of the polygon line on the domain 𝐷 = [𝑡1 , 𝑡n ].


Solution:

def zero_rate_lin_interp(times,rates,*vararg,**option):
"vararg[0] should contain the time desired to compute by "
"vararg[1] should contain the number of division points "
f = lambda x:np.interp(x,times,rates)
if option.get("result")=="function":
return f
elif option.get("result")=="value":
return f(vararg[0])
elif option.get("result")=="plot":
import matplotlib.pyplot as plt
s = np.linspace(times[0],times[-1],vararg[1])
plt.plot(s,f(s))
plt.grid(True)
plt.title('Linear interpolation')
plt.show()

2) Starting from a series of zero rates for a series of terms, compute the rest of the interest
rates by quadratic interpolation by the following rules:

a) For rates {𝑟1 < 𝑟2 < ⋯ < 𝑟𝑛 }, 𝑛 ≥ 3. If 𝑛 < 3 raise a TypeError.

𝑝1 (𝑥) = 𝑎1 + 𝑏1 𝑥 + 𝑐1 𝑥 2 , 𝑥 ∈ [𝑟1 , 𝑟2 ]
b) We define 𝑆2𝑛 (𝑥) = { 𝑝2 (𝑥) = 𝑎2 + 𝑏2 𝑥 + 𝑐2 𝑥 2 , 𝑥 ∈ [𝑟2 , 𝑟3 ] where 𝑝1 (𝑟2 ) =
(𝑥) 2
𝑝𝑛−1 = 𝑎𝑛−1 + 𝑏𝑛−1 𝑥 + 𝑐𝑛−1 𝑥 , 𝑥 ∈ [𝑟𝑛−1 , 𝑟𝑛 ]
𝑝2 (𝑟2 )
Solution:

def zero_rate_quad_interp(times,rates,*vararg,**option):
from scipy import interpolate
if len(times)<3 or len(rates)<3:
raise TypeError('there is too few data')
elif len(times)!=len(rates):
raise TypeError('the lengths of times and zero rates are not compatible')
f = interpolate.interp1d(times,rates,kind = 'quadratic')
if option.get("result")=="function":
return f
elif option.get("result")=="value":
return f(vararg[0])
elif option.get("result")=="plot":
import matplotlib.pyplot as plt
s = np.linspace(times[0],times[-1],vararg[1])
plt.plot(s,f(s))
plt.grid(True)
plt.title('Quadratic interpolation')
plt.show()
Test results for these 2 interpolations:
def test2(): 0.035
times = [0.5,1,1.5]
rates = [0.03,0.04,0.05]
print(zero_rate_lin_interp(times,rates,0.75,result =
"value"))
zero_rate_lin_interp(times,rates,None,100,result
= "plot")

test2()

def test3(): 0.03375


times = [0.5,1,1.5]
rates = [0.03,0.04,0.06]

print(zero_rate_quad_interp(times,rates,0.75,result =
"value"))

zero_rate_quad_interp(times,rates,None,100,result =
"plot")

test3()

Now we get to the 2nd part, the pricing of bond.

Here are several ways to price a bond according to the input given which can be:

1. Composed of cash-flows, times of these and corresponding zero - rates.

2. Composed of: principal, annual coupon rate, lifetime, frequency of coupons and zero rates.

3. Composed of: principal, annual coupon rate, frequency of coupons, one single rate (yield). Useful in
computing the yield.

4. Principal (face value), annual coupon rate, frequency, zero rates, compounding, time of the
evaluation, lifetime of bond.

5. Any combination from the previous 4 ways.

It is a very hard mission to create a universal bond pricer, there are still jobs & projects only for that, and
it is not the objective of the book to give that pricer for free.

Its objective is that the reader is capable after studying the algorithms presented, to carry out updates
with more and more features on the functions developped and presented here or construct their own
libraries.
The problem is, if zero rates for specific maturities, are not available, these must be found by
interpolation from the existing zero rates.

Later, we will discuss how to find zero rates from market prices of bonds (& other instruments) through
bootstrapping method.

Method 1: Cash flows, times and corresponding zero rates. The simplest case.

Python code:

import numpy as np
def bond_price1(zero_rates,times,cash_flows = None):
discount_rates = np.exp(-np.array(zero_rates)*np.array(times))
return np.sum(np.dot(cash_flows,discount_rates))

Method 2: Principal, annual coupon rate, lifetime, yearly frequency of payments, zero rates.

Here are two situations: If the coupon payment dates correspond to the terms of the zero rates or not.

That is, when we have available zero rates for the corresponding remaining times or not.

In the second case, we must interpolate the zero rates.

I will illustrate this case by an example first.

Suppose the remaining time of an 8-year bond is 13 months. The coupons are paid quarterly, c = 8%
annually, the face value is FV = 100$. For simplicity we assume there are no accruals.

What is the current value of the bond if we know the zero rates for 3M,6M,9M,12M,15M?

Answer:

The next coupon is due in 1M, and the rest are in 4,7,10,13M. We have no zero rates for that but we can
imply them through linear interpolation, by letting the rates with terms lower than 3M to be equal with
3M rate and those with terms higher than 15M to be equal to 15M rate (there aren’t any in this
situation but in other cases may well be).
1 4 7
The price is 𝐶𝐹1 ⋅ exp (−𝑟1𝑀 ⋅ 12) + 𝐶𝐹2 ⋅ exp (−𝑟4𝑀 ⋅ 12) + 𝐶𝐹3 ⋅ exp (−𝑟7𝑀 ⋅ 12) + 𝐶𝐹4 ⋅
10 13 𝑐 𝑐
exp (−𝑟10𝑀 ⋅ )+ 𝐶𝐹5 ⋅ exp (−𝑟13𝑀 ⋅ ), where 𝐶𝐹1 = 𝐶𝐹2 = 𝐶𝐹3 = 𝐶𝐹4 = ⋅ 𝐹𝑉, 𝐶𝐹5 = ( + 1) ⋅
12 12 4 4
𝐹𝑉.

Python code:

def bond_price2(zero_rates,times,freq = 4,lifetime = 1,c = 0,principal = 100,t = 0):


" lifetime = the remaining lifetime of the bond (expressed in years)"
" freq = frequency/year, c = coupon, t = time when the bond is evaluated"
res_time = lifetime - t
k = int(res_time*freq)
times_payments = np.linspace(res_time - (k-1)/freq,res_time,k)
rate = lambda t:np.interp(t,times,zero_rates)
zero_rates2 = [rate(times_payments[i]) for i in range(len(times_payments))]
cash_flows = [c/freq * principal] * k
cash_flows[-1] = cash_flows[-1]+principal
return bond_price1(zero_rates2,times_payments,cash_flows)

Method 3: (Having the yield)

def bond_price3(principal,coupon,lifetime,freq,r):
"r = flat rate"
k = int(lifetime*freq)
times_payments = np.linspace(lifetime - (k-1)/freq,lifetime,k)
cash_flows = [coupon/freq * principal]*k
rates = [r]*k
cash_flows[-1] = cash_flows[-1]+principal
return bond_price1(rates,times_payments,cash_flows)

Solved exercises:

1. If we consider a coupon with a face value of 100 $, 3 years lifetime, annual coupon is 8% and a flat
interest rate of 4%. We consider that the payments are annual. Compute the price of the bond using:

a. Continuous compounding. (Discounting)

b. Yearly compounding. (Discounting)

c. Semi-annual compounding.

d. Quarterly compounding.

Solution:

a. 𝑃0 = 𝑐 ⋅ 𝐹𝑉 ⋅ exp(−𝑟𝑡1 ) + 𝑐 ⋅ 𝐹𝑉 ⋅ exp(−𝑟𝑡2 ) + (𝑐 + 1) ⋅ 𝐹𝑉 ⋅ exp(−𝑟𝑡3 )


1 1 1
𝑏. 𝑃0 = 𝑐 ⋅ 𝐹𝑉 ⋅ 1+𝑟 + 𝑐 ⋅ 𝐹𝑉 ⋅ (1+𝑟)2 + (𝑐 + 1) ⋅ 𝐹𝑉 ⋅ (1+𝑟)3

1 1 1
c. 𝑃0 = 𝑐 ⋅ 𝐹𝑉 ⋅ 𝑟 2
+ 𝑐 ⋅ 𝐹𝑉 ⋅ 𝑟 4
+ (𝑐 + 1) ⋅ 𝐹𝑉 ⋅ 𝑟 8
.
(1+ ) (1+ ) (1+ )
2 2 2

1 1 1
d. 𝑃0 = 𝑐 ⋅ 𝐹𝑉 ⋅ 𝑟 4
+ 𝑐 ⋅ 𝐹𝑉 ⋅ 𝑟 8
+ (𝑐 + 1) ⋅ 𝐹𝑉 ⋅ 𝑟 12
, where 𝑡1 , 𝑡2 , 𝑡3 = 1,2,3.
(1+ ) (1+ ) (1+ )
4 4 4

Python code:

def bond_prices(FV,c,lifetime,rate,**option):
"I assume here that payments are annualy made"
"rate = flat rate"
def bp_continuous(FV,c,lifetime,rate):
l1 = [np.exp(-rate*i) for i in range(1,lifetime+1)]
l2 = [c*FV]*lifetime
l2[-1]+=FV
return np.dot(l1,l2)
def bp_yearly(FV,c,lifetime,rate):
l1 = [1/(1+rate)**i for i in range(1,lifetime+1)]
l2 = [c*FV]*lifetime
l2[-1]+=FV
return np.dot(l1,l2)
def bp_semiannually(FV,c,lifetime,rate):
l1 = [1/(1+rate/2)**(2*i) for i in range(1,lifetime+1)]
l2 = [c*FV]*lifetime
l2[-1]+=FV
return np.dot(l1,l2)
def bp_quarterly(FV,c,lifetime,rate):
l1 = [1/(1+rate/4)**(4*i) for i in range(1,lifetime+1)]
l2 = [c*FV]*lifetime
l2[-1]+=FV
return np.dot(l1,l2)
if option.get("compounding")=="continuous":
return bp_continuous(FV,c,lifetime,rate)
elif option.get("compounding")=="yearly":
return bp_yearly(FV,c,lifetime,rate)
elif option.get("compounding")=="semiannually":
return bp_semiannually(FV,c,lifetime,rate)
elif option.get("compounding")=="quarterly":
return bp_quarterly(FV,c,lifetime,rate)

The following test give the results as stated in the right column
def test4(): Out:
FV,c,lifetime,rate = 100,0.08,3,0.04 110.85865344976467
print(bond_prices(FV,c,lifetime,rate,compounding = "continuous")) 111.10036413290851
print(bond_prices(FV,c,lifetime,rate,compounding = "yearly")) 110.98102293422474
print(bond_prices(FV,c,lifetime,rate,compounding = "semiannually")) 110.92022486435764
print(bond_prices(FV,c,lifetime,rate,compounding = "quarterly"))
test4()

One can observe that the higher the frequency of compounding, the lower the price of the bond, the
price converging to the continuous compounding frequency price.

2. One can generalize the function above by considering a certain frequency 𝑓𝑟 ∈ {1,2,4,12, … }
according to yearly, semi-annually, quarterly, monthly discounting, etc.

Solution:

def bond_prices2(FV,c,lifetime,rate,*vararg,**option):
"vararg contains the frequency of compounding"
def bp_continuous(FV,c,lifetime,rate):
l1 = [np.exp(-rate*i) for i in range(1,lifetime+1)]
l2 = [c*FV]*lifetime
l2[-1]+=FV
return np.dot(l1,l2)
def bp_frequency(FV,c,lifetime,rate,freq):
l1 = [1/(1+rate/freq)**(freq*i) for i in range(1,lifetime+1)]
l2 = [c*FV]*lifetime
l2[-1]+=FV
return np.dot(l1,l2)
if option.get("compounding")=="continuous":
return bp_continuous(FV,c,lifetime,rate)
else:
return bp_frequency(FV,c,lifetime,rate,vararg[0])

Test:

def test5(): Out:


FV,c,lifetime,rate = 100,0.08,3,0.04 110.85865344976467
print(bond_prices2(FV,c,lifetime,rate,compounding = "continuous")) 111.10036413290851
print(bond_prices2(FV,c,lifetime,rate,1)) 110.98102293422474
print(bond_prices2(FV,c,lifetime,rate,2)) 110.92022486435764
print(bond_prices2(FV,c,lifetime,rate,4))
test5()

3. Suppose this time that for a 3-year bond, there are several payments per year. Suppose the annual
coupon rate is 8%, the interest rate is flat for 4%. Compute the price with continuous compounding if:

a. There are annual payments;

b. Semi-annual payments;

c. Quarterly payments;

d. Monthly payments.

Solution:

a. 𝑃0 = 𝑐 ⋅ 𝐹𝑉 ⋅ 𝑒 −𝑟 + 𝑐 ⋅ 𝐹𝑉 ⋅ 𝑒 −2𝑟 + (𝑐 + 1) ⋅ 𝐹𝑉 ⋅ 𝑒 −3𝑟
1 3𝑟
𝑐 𝑐 𝑐 𝑐
b. 𝑃0 = 2 ⋅ 𝐹𝑉 ⋅ 𝑒 −𝑟⋅2 + 2 ⋅ 𝐹𝑉 ⋅ 𝑒 −𝑟 + 2 ⋅ 𝐹𝑉 ⋅ 𝑒 − 2 + ⋯ + (2 + 1) ⋅ 𝐹𝑉 ⋅ 𝑒 −3𝑟 (8 cash-flows)
1
𝑐 𝑐
c. 𝑃0 = 4 ⋅ 𝐹𝑉 ⋅ 𝑒 −𝑟⋅4 + ⋯ . + (4 + 1) ⋅ 𝐹𝑉 ⋅ 𝑒 −3𝑟 (12 cash-flows and therefore 12 terms).
1
𝑐 𝑐
d. 𝑃0 = ⋅ 𝐹𝑉 ⋅ 𝑒 −𝑟⋅12 + ⋯ + ( + 1) ⋅ 𝐹𝑉 ⋅ 𝑒 −3𝑟 (36 cash-flows and therefore 36 terms).
12 12

Python code:
def bond_prices3(FV,c,lifetime,rate,freq = 1):
l1 = [np.exp(-rate/freq*i) for i in range(1,int(freq*lifetime)+1)]
l2 = [c/freq*FV]*int(freq*lifetime)
l2[-1]+=FV
return np.dot(l1,l2)

Test:

def test6(): Out:


FV,c,lifetime,rate = 100,0.08,3,0.04 111.08255106044807
print(bond_prices3(FV,c,lifetime,rate,2)) 111.19506523062608
print(bond_prices3(FV,c,lifetime,rate,4)) 111.27028408117928
print(bond_prices3(FV,c,lifetime,rate,12)) 110.85865344976467
print(bond_prices3(FV,c,lifetime,rate)) Remark: by default the frequncy is yearly
test6()

Remark: While in the first two exercises, the lifetime was supposed to be an integer number of years,
while in the third example we can assume other integer numbers. The main condition is that there is an
integer number of payments during the lifetime of bond.

1.2 Bootstrapping
Definition: Means to deduce a certain curve from the market prices of several instruments from the
same class or different classes.

Examples: Zero rates curve from bond prices, yield curve from bond prices, swap term rate from interest
rate swaps, forward rate curve from forward rate agreements, spread from CDS prices, etc.

We will continue with the bond prices. We assume continuous discount rates (exponential rates).

Example 1:

Suppose we have the following table of bonds:

Face Value ($) Bond lifetime (years) Annual coupon ($) Bond price($)
FV=100 0.25 0 96
FV=100 0.5 4 93.5
FV=100 1 8 93.9
FV=100 2 12 97.5

Suppose the payments are semi-annually. Find the zero rates.

Solution:
1
100
𝐵1 = 96 ⇒ 96 = 𝑒 −𝑟3𝑀 ∗4 ∗ 100 ⇒ 𝑟3𝑀 = 4 log ( 96 ).
𝑟6𝑀 𝑐2
𝑐 +𝐹𝑉 102
𝐵2 = 93.5 = ( 22 + 𝐹𝑉) ∗ 𝑒 − 2 ⇒ 𝑟6𝑀 = 2 log ( 2 𝐵 ) = 2log(93.5)
2

𝑟 𝑟
𝑐3 − 6𝑀 𝑐 − 1𝑌 93.9
𝐵3 = 93.9 = 2
∗𝑒 2 + ( 23 + 𝐹𝑉) ∗ 𝑒 2 ⇒ 𝑟1𝑌 = 2 ∗ log( …
), etc.

𝐵4 = 97.5 = 𝑓(𝑟6𝑀 , 𝑟1𝑌 , 𝑟2𝑌 ) = 𝑔(𝑟2𝑌 ) because 𝑟6𝑀 , 𝑟1𝑌 are already known.

1. Actualy bootstrapping can be simply translated as the solving a system of non-linear equations with
unknowns, the zero rates.

2. Sometimes the system has multiple solutions, in case of sub-dimensionality (number of unknowns
lower than the number of equations), otherwise the system has exactly one solution and otherwise no
solution.

a. In the first case we might want to interpolate

b. In the latter case we can do some fitting, that is finding a curve that fits (approximates) the best, the
square error resulted.

3. The zero rates corresponding to the intermediate maturities can be found by linear/quadratic
interpolation.

4. If the payment times of the coupons do not correspond to the maturities of the bonds, or are not
multiples of these maturities we might want to write the zero rates corresponding to these payment
times as depending on the lifetimes available on the problem.

We give the following

Definition:

We define the bootstrapping problem as (𝑟𝑖 )1≤𝑖≤𝑛 = 𝐹 −1 ((𝐹𝑉𝑖 , 𝑡𝑖 , 𝑐𝑖 , 𝐵𝑖 , 𝐹𝑟)1≤𝑖≤𝑛 ) where 𝐹𝑉𝑖 =
𝑓𝑎𝑐𝑒 𝑣𝑎𝑙𝑢𝑒 𝑜𝑓 𝑡ℎ𝑒 𝑏𝑜𝑛𝑑 𝑖, 𝑡𝑖 = lifetime of 𝑏𝑜𝑛𝑑 𝑖, 𝑐𝑖 = annual coupon of the bond 𝑖, 𝐵𝑖 = the bond
price 𝑖, 𝐹𝑟𝑖 = 𝑓𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦 𝑜𝑓 𝑝𝑎𝑦𝑚𝑒𝑛𝑡𝑠 / year of bond 𝑖 and 𝑟𝑖 is the zero rate corresponding to time
𝑡𝑖 .

Example 1:

Solve the following bootstrapping problem:


1 3 7
(𝐹𝑉1 , 2 , 𝑐1 , 𝐵1 ) , (𝐹𝑉2 , 2 , 𝑐2 , 𝐵2 ) , (𝐹𝑉3 , 4 , 𝑐3 , 𝐵3 )where the frequency of payments is considered 𝐹𝑟𝑖 =
𝐹𝑟 =3 times per year.

Solution:

For bond 1, a coupon payment is made inside its lifetime, 2 months before maturity and the face value
at maturity, after 6M.
For bond 2, a coupon payment is made after 4M, 8M,12M and 16M and the face value is returned after
18M.

For bond 3, a coupon payment is made after 4M, 8M,12M,16M,20M and the face value is payed after
21M. The zero rates to be found are 𝑟6𝑀 , 𝑟18𝑀 , 𝑟21𝑀 = 𝑟1 , 𝑟3 , 𝑟7 the rest of them being interpolated w.r.t
2 2 4
1 7
these 3 rates. Also, we consider 𝑟𝑡 = 𝑟1 , 𝑖𝑓 𝑡 < , and 𝑟𝑡 = 𝑟7 if 𝑡 ≥ .
2 2 4 4

Having these in mind, from the first bond we obtain:


1 𝑟6𝑀 𝑥 𝑥
𝑐1 𝑐1
𝑒 −𝑟4𝑀 ∗3 ∗ 3
+ 𝑒−2 ∗ 𝐹𝑉1 = 𝐵1 ⇒ 𝑒 −3 ∗ 3
+ 𝑒 −2 ∗ 𝐹𝑉1 = 𝐵1 because 𝑟4𝑀 = 𝑟6𝑀 .

To find 𝑥 we apply Newton Raphson method/secant method.

Take here an example: for annual coupon of 𝑐 = 4$,Face value 100 and the price of bond 96:

def test_NR():
import numpy as np
def fct(x,c1,FV1,B1):
return np.exp(-x/3)*c1/3+np.exp(-x/2)*FV1-B1
g = lambda x: fct(x,4,100,96)
import scipy.optimize as scp
return scp.newton(g,0)
The resulted 𝑟1 = 0.1086 = 10.86%.

3
𝑐2 −𝑟3 ∗
From the second bond, ∑4𝑖=1 𝑒 −𝑟𝑖∗𝑡𝑖 ∗ 3
+𝑒 2
2
∗ 𝐹𝑉2 = 𝐵2 where 𝑟𝑖 = corresponding zero rate for
1 2 4
time 𝑡𝑖 and {𝑡1 , 𝑡2 , 𝑡3 , 𝑡4 } = {3 , 3 , 1, 3}.
3 2 2 1 3 4
𝑟1 = 𝑟4𝑀 = 𝑟6𝑀 , 𝑟2 = 𝑟8𝑀 = ( − ) ∗ 𝑟6𝑀 + ( − ) ∗ 𝑟18𝑀 , 𝑟1𝑌 = 0.5(𝑟6𝑀 + 𝑟18𝑀 ), 𝑟16𝑀 = ( − ) ∗
2 3 3 2 2 3
4 1
𝑟6𝑀 + (3 − 2) ∗ 𝑟18𝑀 .

1.3 Bond yield


Definition: A bond’s yield is the single discount rate that, when applied to all cash flows, gives a
bond price equal to its market price.

Comment: IN the next section I will make reference to a file called “bond_price_and_yield.py”
containing the library “bond_price_methods(num)”. (See Appendix 14 for the Python code).

Exercise 1:

Find the bond yield of a 2-year coupon bearing bond with semi-annual payments, 𝑐 = 4%𝑝. 𝑎., par value
𝐹 = 100$ and zero rates 𝑟6𝑀 , 𝑟1𝑌 , 𝑟18𝑀 , 𝑟2𝑌 presumably known.

Solution:

First we find the price of the bond:


𝑟6𝑀 𝑟18𝑀 ∗3 𝑦 3𝑦
𝑃0 = 2𝑒 − 2 + 2𝑒 −𝑟1𝑌 + 2𝑒 − 2 + 102𝑒 −2𝑟2𝑌 = 2𝑒 −2 + 2𝑒 −𝑦 + 2𝑒 − 2 + 102𝑒 −2𝑦 , where 𝑦 is the
bond yield.

Python code: (See Appendix: Bond pricing for the methods of bond pricing reffered to in the following
test).

def bond_yield0(FV,c,rates,times,x0):
"correspondig to exercise 1"
"x0 is the initial yield guess"
price = bond_price_methods(7)(FV,c,rates,times)
def f(x):
return c/2*FV*np.exp(-x*times[0])+c/2*FV*np.exp(-x*times[1])+\
c/2*FV*np.exp(-times[2]*x)+ (c/2+1)*FV*np.exp(-times[3]*x) - price
import scipy.optimize as scp
return scp.newton(f,x0)

def test():
FV,c,rates,times = 100,0.06,[0.05,0.058,0.064,0.068],[0.5,1,1.5,2]
print(bond_price_methods(7)(FV,c,rates,times))
print(bond_yield0(FV,c,rates,times,0.2))

A much cleaner solution is by referring to the bond price algorithm I made in section 1.1
(using bond_price2 and bond_price3 function).
In the file bond_pricers2.py we have bond_price2 and bond_price3 functions.

Solved exercise 2:
Given the zero rates below find the bond yield. Data: FV = 100, c=6%, T=2 years.
Maturity (years) Zero rates
0.5 0.05
1 0.058
1.5 0.064
2 0.068
Table 1

from bond_pricers import (bond_price2,bond_price3)


def bond_yield(zero_rates,times,freq,lifetime,coupon,principal):
price_bond = bond_price2(zero_rates,times,freq,lifetime,coupon,principal)
import scipy.optimize as scp
h = lambda r:bond_price3(principal,coupon,lifetime,freq,r) - price_bond
return scp.newton(h,0)
Test:

def test_yield(): Out:


zero_rates = [0.05,0.058,0.064,0.068] 0.06762438716028744
times = [0.5,1,1.5,2]
freq,lifetime,coupon,principal = 2,2,0.06,100 Or, 6.76%
print(bond_yield(zero_rates,times,freq,lifetime,coupon,principal))
test_yield()
Solved exercise 3: (Yield curve)
This is very important.
See how the yield changes when the entire interest rate curve changes with 1bp.

Solution:

import numpy as np
def yield_curve(zero_rates,times,freq,lifetime,coupon,principal=100,chg=0.0001\
,points = 100):
"shift on the entire curve with 1 or more bps"
return [bond_yield(np.array(zero_rates)+i*np.array([chg]*len(times)),\
times,freq,lifetime,coupon,principal) for i in \
range(1,points+1)]

Solved exercise 4: (Yield curve)


See how the yield changes when one component of the zero rate curve changes with 1bp.

Solution:

def yield_curve2(zero_rates,times,freq,lifetime,coupon,principal = 100,\


chg=0.0001,index = 0,points = 100):
"shift only on a single certain component"
return [bond_yield((np.array(zero_rates)+i*np.eye(1,len(times),index)).flatten(),\
times,freq,lifetime,coupon,principal) for i in \
range(1,points+1)]

Test for yield curve change.


We consider a zero-rate term structure as in Table 1 (Solved exercise 2).
Also the frequency, lifetime, coupon, principal are 2 (times/year), lifetime = 2 years,
coupon = 6%, principal = 100.
def test_curve():
zero_rates = [0.05,0.058,0.064,0.068]
times = [0.5,1,1.5,2]
freq,lifetime,coupon,principal = 2,2,0.06,100
yields = yield_curve(zero_rates,times,freq,lifetime,coupon,principal)
yields2 = yield_curve2(zero_rates,times,freq,lifetime,coupon,principal)
yields3 = yield_curve2(zero_rates,times,freq,lifetime,coupon,principal,0.0001,1)
yields4 = yield_curve2(zero_rates,times,freq,lifetime,coupon,principal,0.0001,2)
yields5 = yield_curve2(zero_rates,times,freq,lifetime,coupon,principal,0.0001,3)
import matplotlib.pyplot as plt
plt.figure(1)
plt.plot(yields)
plt.grid(True)
plt.title('Yield curve with parallel shift')
plt.show()
plt.figure(2)
plt.subplot(221)
plt.plot(yields2)
plt.grid(True)
plt.title('Yield curve with first component shift')
plt.subplot(222)
plt.plot(yields3)
plt.grid(True)
plt.title('Yield curve with second component shift')
plt.subplot(223)
plt.plot(yields4)
plt.grid(True)
plt.title('Yield curve with third component shift')
plt.subplot(224)
plt.plot(yields5)
plt.title('Yield curve with fourth component shift')
plt.grid(True)
plt.show()

Results:
For the parallel shift we have:

For the component-wise shift we have:


1.4 Par yield
Definition: The par yield for a certain bond maturity is the coupon rate that causes the bond price to
equal its par value. (The par value is the same as the principal value.)

Exercise 1:

If the zero rates of a 2-year bond are 5%,5.8%,6.4% and 6.8% find the par yield of the bond.
3
𝑐 𝑐 𝑐
The par yield is the coupon rate 𝑐 such that 2 ⋅ 𝑒 −0.05⋅0.5 + 2 ⋅ 𝑒 −0.058⋅1 + 2 𝑒 −0.064⋅2 +
3
𝑐 𝑐
(100 + 2) 𝑒 −0.068⋅2 = 100. That is equivalent with 2 (𝑒 −0.05⋅0.5 + 𝑒 −0.058⋅1 + 𝑒 −0.064⋅2 + 𝑒 −0.068⋅2 ) =
100(1−𝑒 −0.068⋅2 )
100 − 100𝑒 −0.068⋅2 ⇒ 𝑐 = 3 ⋅ 2.
−0.064⋅2
𝑒 −0.05⋅0.5 +𝑒 −0.058⋅1 +𝑒 +𝑒 −0.068⋅2

Python code:

def par_yield(FV,c,rates,times):
"Corresponding to exercise 2, 2year lifetime, 4 payments"
s = np.exp(-times[0]*rates[0])+np.exp(-rates[1]*times[1])+np.exp(-
rates[2]*times[2])+\
np.exp(-rates[3]*times[3])
return FV*(1-np.exp(-rates[3]*2))/s * 2

def test():
FV,c,rates,times = 100,0.06,[0.05,0.058,0.064,0.068],[0.5,1,1.5,2]
print(bond_price_methods(7)(FV,c,rates,times))
print(bond_yield(FV,c,rates,times,0.2))
print(par_yield(FV,c,rates,times))
Result: 𝒄 = 𝟔. 𝟖𝟕%.

1.5 Duration and convexity


The duration is a measure of how long on average the holder of the bond has to wait before receiving
cash payments.

If the cash-flows are 𝑐𝑖 at time 𝑡𝑖 , 1 ≤ 𝑖 ≤ 𝑛. The bond price and yield are related by 𝐵 = ∑𝑛𝑖=1 𝑐𝑖 𝑒 −𝑦𝑡𝑖 .
𝑡𝑖 𝑐𝑖 𝑒 −𝑦𝑡𝑖 𝑐𝑖 𝑒 −𝑦𝑡𝑖
The duration of the bond , 𝐷, is defined as 𝐷 = ∑𝑛𝑖=1 𝐵
= ∑𝑛𝑖=1 𝑡𝑖 [ 𝐵
].

Solved example:

Consider a 3-year 10% zero coupon bond with a face value of 100$ and suppose that the yield is 12%p.a.
Payments are made semi-annually.

What is the bond price, and what becomes the bond price if the yield changes with 10 basis points?

1.5.1 Python code for duration


Here the duration takes into account the yield.

from bond_pricers import bond_price3


def duration(principal,coupon,lifetime,freq,y):
import numpy as np
k = int(lifetime*freq)
times_payments = np.linspace(lifetime - (k-1)/freq,lifetime,k,endpoint=True)
cash_flows = [coupon/freq * principal]*k
cash_flows[-1] = cash_flows[-1]+principal
discounts = [np.exp(-y*times_payments[i]) for i in range(k)]
B = bond_price3(principal,coupon,lifetime,freq,y)
return np.dot(times_payments,np.array(cash_flows)*np.array(discounts))/B

If one wants the price and the duration one can redefine the function above by adding to the returned
values, B.

def convexity(principal,coupon,lifetime,freq,y):
import numpy as np
k = int(lifetime*freq)
times_payments = np.linspace(lifetime - (k-1)/freq,lifetime,k,endpoint=True)
cash_flows = [coupon/freq * principal]*k
cash_flows[-1] = cash_flows[-1]+principal
discounts = [np.exp(-y*times_payments[i]) for i in range(k)]
B = bond_price3(principal,coupon,lifetime,freq,y)
return np.dot(cash_flows,np.array(times_payments)**2*np.array(discounts))/B

Changes in prices are to be defined through the next two functions:

def chg_price_bond(principal,coupon,lifetime,freq,y,dy):
"only duration is considered"
price,dur = duration(principal,coupon,lifetime,freq,y)
return -price*dur*dy,price-price*dur*dy

def chg_price_bond2(principal,coupon,lifetime,freq,y,dy):
"duration + convexity considered in the change of the bond price"
price,dur = duration(principal,coupon,lifetime,freq,y)
conv = convexity(principal,coupon,lifetime,freq,y)
return -price*dur*dy+1/2*price*conv*dy**2

1.6. Forward rates and Forward rate agreements


Definition: Forward interest rates are the rates of interest implied by current zero rates for periods of
time in the future.

When interest rates are continuously compounded and rates in successive time periods are combined,
the overall equivalent is simply the average rate during the whole period.

In general, if 𝑅1 and 𝑅2 are the zero rates for maturities 𝑇1 and 𝑇2 , respectively and 𝑅𝐹 is the forward
𝑅2 𝑇2 −𝑅1 𝑇1 (𝑅2 −𝑅1 )𝑇1
interest rate for the period of time between 𝑇1 and 𝑇2 , then 𝑅𝐹 = 𝑇2 −𝑇1
= 𝑅2 + 𝑇2 −𝑇1

Taking limits as 𝑇2 approaches 𝑇1 and letting the common value 𝑇2 − 𝑇1 = 𝑇 we can write 𝑅𝐹 = 𝑅 +
𝜕𝑅 𝜕
𝑇 𝜕𝑇 or 𝑅𝐹 = − 𝜕𝑇 log 𝑃(0, 𝑇) 𝑤ℎ𝑒𝑟𝑒 𝑃(0, 𝑇) = discounted value of a bond with face value = 1 unit.

In order to buld the forward curves we will use the discrete approximation of 𝑅𝐹 . That is,
𝑡1
𝑅𝐹1,2 = 𝑅𝑡2 + (𝑅𝑡2 − 𝑅𝑡1 ) ⋅ 𝑡 ,
2 −𝑡1

𝑡2
𝑅𝐹2,3 = 𝑅𝑡3 + (𝑅𝑡3 − 𝑅𝑡2 ) ⋅ ,
𝑡3 −𝑡2

……….
𝑡𝑛−1
𝑅𝐹𝑛−1,𝑛 = 𝑅𝑡𝑛 + (𝑅𝑡𝑛 − 𝑅𝑡𝑛−1 ) ⋅ 𝑡
𝑛 −𝑡𝑛−1

So the result will be written in a condensed form as 𝐹 = [𝐹1,2 , 𝐹2,3 , … , 𝐹𝑛−1,𝑛 ] = [𝑅2 , 𝑅3 , … , 𝑅𝑛 ] +
𝑡1 𝑡 𝑡𝑛−1
[𝑅2 − 𝑅1 , 𝑅3 − 𝑅2 , … , 𝑅𝑛 − 𝑅𝑛−1 ] ⋅ [ , 2 , … , 𝑡 −𝑡
𝑡2 −𝑡1 𝑡3 −𝑡2
] where
𝑛 𝑛−1

𝑡1 𝑡2 𝑡
[𝑡 , , … , 𝑛−1 ] = [𝑡1 , 𝑡2 , … , 𝑡𝑛−1 ]/[𝑡2 − 𝑡1 , … , 𝑡𝑛 − 𝑡𝑛−1 ] .
2 −𝑡1 𝑡3 −𝑡2 𝑡𝑛 −𝑡𝑛−1

Python code for forward rates from zero rates


def forward_rates(zero_rates,times):
import numpy as np
return
np.array(zero_rates[1:len(zero_rates)])+np.diff(zero_rates)*np.array(times[0:len(times)-
1])/np.diff(times)

def test_forward_rates():
times = [1,2,3,4,5]
zero_rates = [0.03,0.04,0.046,0.05,0.053]
print(forward_rates(zero_rates,times))
test_forward_rates()

Exercise: Given the table below from the file ‘Rates.xlsx’ in the spreadsheet ‘Rates’ complete the
following tasks:

a. Read into a table the first 2 columns

b. Compute the forward rates starting from year 2.

Zero rate (n Forward rate for nth


Year years) year
1 3.0%
2 4.0%
3 4.6%
4 5.0%
5 5.3%
Solution:

a. Python code:

import xlwings as xw
import numpy as np
def exercise():
wb = xw.Book('Rates.xlsx')
sht = wb.sheets['Rates']
years = sht.range('A2:A6').value
print(years) #prints 1.0 2.0 3.0 4.0 5.0
print(sht.range(sht.range(2,1),sht.range(4,1)).value) #prints [1.0,2.0,3.0]
print(sht.range(sht.range(2,1)).value)#prints 1.0
print(sht.range(2,1).value)#prints 1.0
print(sht.range('A2','A6').value) # prints 1.0,2.0,3.0,4.0,5.0
print(sht.range('A2','B6').value) # prints a 2D list of pairs (year,rate)
print(type(sht.range('A2','B6').value)) #class list
print(np.array(sht.range('A2','B6').value)) # returns a 2D array with 2 columns
b.

def exercise1b():
wb = xw.Book('Rates.xlsx')
sht = wb.sheets['Rates']
years = sht.range('A2:A6').value
zero_rates = sht.range('B2:B6').value
forward_rates = []
for i in range(len(years)-1):
quantity = years[i+1]*zero_rates[i+1]-years[i]*zero_rates[i]
forward_rates.append(quantity/(years[i+1]-years[i]))
print(forward_rates)
Exercises:

1. Suppose that the 6-month, 12-month,18-month and 24-month zero rates are 5%, 6%, 6.5% and 7%
respectively. What is the 2-year par yield? (Payments are made semi-annually).

2. Suppose that zero interest rates with continuous compounding are as follows:

Maturity (years) Rate (% per annum)


1 2.0
2 3.0
3 3.7
4 4.2
5 4.5
Calculate forward interest rates for the second, third, fourth and fifth year.

3. Use the rates above to value an FRA where you will pay 5% (compounded annually) for the third year
on $1 million.

4. A 10-year 8% coupon bond currently sells for $90. A 10-year 4% coupon bond currently sells for 90$.

What is the 10-year zero rate?

Solutions:
1 3
𝑐 𝑐 𝑐
1. The par value is the value of annual coupon 𝑐 such that 2 ⋅ 𝑒 −0.05⋅2 + 2 ⋅ 𝑒 −0.06 + 2 ⋅ 𝑒 −0.065⋅2 +
1 3 0.07
𝑐 𝑐
(100 + ) 𝑒 −0.07⋅2 = 100 ⇒ (𝑒 −0.05⋅2 + 𝑒 −0.06 + 𝑒 −0.065⋅2 + 𝑒 − 2 ) = 100(1 − 𝑒 −0.07⋅2 ) ⇒ 𝑐 =
2 2
2⋅100(1−𝑒 −0.07⋅2 )
1 3 0.07
−0.05⋅2 −0.065⋅2 −
𝑒 +𝑒 −0.06 +𝑒 +𝑒 2

If A = value of an annuity that pays 1$ at each coupon date, and d = present value of a 1$, m= #
𝐴𝑐 (100−100𝑑)𝑚
payments/year then the par yield must satisfy 100 = 𝑚
+ 100𝑑 ⇔ 𝑐 = 𝐴
.

2. The forward rates are given by: 𝐹(𝑇1 , 𝑇2 ) = (𝑟2 𝑇2 − 𝑟1 𝑇1 )/(𝑇2 − 𝑇1 ) and the results are

2nd year 4%
3rd year 5.1%
4th year 5.7%
5th year 5.7%
Python code:

def rate_curves():
def forward_rates(times,zero_rates):
if len(times)!=len(zero_rates):
raise IndexError('the times and zero rates do not correspond in length')
fw_rates = []
for i in range(len(times)-1):
fw_rates.append((times[i+1]*zero_rates[i+1]-times[i]*zero_rates[i])/\
(times[i+1]-times[i]))
return fw_rates
def test():
times = [1,2,3,4,5]
zero_rates = [0.02,0.03,0.037,0.042,0.045]
print(forward_rates(times,zero_rates))
test()

3. The forward rate is 5.1% with continuous compounding or 𝑒 −0.051×1 − 1 = 5.232% with annual
compounding. The 3-year interest rate is 3.7% with continuous compounding.

From equation (1) above, the value of the FRA is [1,000,000 x (0.05232-0.05)x1]𝑒 −0.037×3 = 2,078.5$.

4. If we take a long position in two of the 4% coupon bonds and a short position in one of the 8% coupon
bonds leads to the following cash flows 𝑌𝑒𝑎𝑟 0: 90 - 2 × 80 = -70.

Year 10: 200 – 100 = 100, because the coupons cancel out.

$100 in 10 years time is equivalent to $70 today. The 10-year rate, R, continuously compounded is
1 100
therefore given by 100 = 70𝑒 10𝑅 ⇒ 𝑅 = 10 log ( 70 ) = 0.0357 or 3.57% per annum.

1.6.2 Forward rate agreements


Definition2: A Forward rate agreement (FRA) is an OTC contract designed to ensure that a certain
interest rate will apply to either borrowing or lending a certain principal during a specified future period
of time.

Specifications:

Consider a FRA where company X is agreeing to lend money to company Y for the period of time
between 𝑇1 and 𝑇2 . We define:

𝑅𝐾 : the rate of interest agreed to in the FRA


𝑅𝐹 : the forward LIBOR interest rate for the period between times 𝑇1 and 𝑇2 ,calculated today.

𝑅𝑀 : The actual LIBOR interest rate observed in the market at time 𝑇1 for the period between times 𝑇1
and 𝑇2 .

𝐿: the principal underlying the contract.

Valuation:

The value of a FRA where 𝑅𝐾 is received is, 𝑉𝐹𝑅𝐴 = 𝐿(𝑅𝐾 − 𝑅𝐹 )(𝑇2 − 𝑇1 )𝑒 −𝑅2 𝑇2 .(1)

Similarly, the value of an FRA where 𝑅𝐾 is paid is 𝑉𝐹𝑅𝐴 = 𝐿(𝑅𝐹 − 𝑅𝐾 )(𝑇2 − 𝑇1 )𝑒 −𝑅2 𝑇2 .(2)

Numerical example 1:

Suppose that a company enters into an FRA that is designed to ensure it will receive a fixed rate of 4%
on a principal of $100 million for a 3-month period starting in 3 years. The FRA is an exchange where
LIBOR is paid and 4% is received for the 3-month period.

If LIBOR 3M is 4.5% the cash-flow to the lender will be: 100,000,000 × (0.04 − 0.045) × 0.25 =
−125,000$ at the 3.25 year point.
125,000
This is equivalent to a cash flow of − 1+0.045×0.25 = −$123,609 at the 3-year point.

Numerical example 2

Suppose that LIBOR zero and forward rates are as follows:

Year(n) Zero rate for a n-year investment Forward rate for nth year
1 3.0
2 4.0 5.0
3 4.6 5.8
4 5.0 6.2
5 5.3 6.5
Consider an FRA where we will receive a rate of 6%, measured with annual compounding, and pay LIBOR
on a a principal of $100 million between the end of year 1 and the end of year 2.In this case, the forward
rate is 5% with continuous compounding, or 5.127% with annual compounding.

The value of the FRA is therefore 100,000,000 × (0.06 − 0.05127)𝑒 −0.04×2 = $805,800.

REMARKS:

1. A company enters into an FRA where a floating rate is payed and a fixed rate is received, if there is a
fear that the floating rate will decrease under the fixed rate.

2. An FRA is always worth zero when 𝑅𝐾 = 𝑅𝐹 .

Exercises:

1. The 6-month, 12-month, 18-month, 24-month zero rates are 4%,4.5%,4.75% and 5% with semiannual
compounding.

(a) What are the rates with continuous compounding?

(b) What is the forward rate for the 6 month period beginning in 18 months?

(c ) What is the value of an FRA that promises to pay you 6% (compouded semiannually) on a principal of
$1 million for the 6-month period starting in 18 months.

Solution:
𝑐 𝑟6𝑀
(a) The rates are given by 𝑟6𝑀 = 2 log (1 + 2
) = 2 log(1.02), 𝑟1𝑌 = 2 log(1.0225) , 𝑒𝑡𝑐.

The rates can be synthetized as follows in the next table:

Time to maturity Zero rate (semi-annual Zero rate(cont.) Forward rate Forward rate
comp) (cont) (semi-annual)
6M 4% 3.961%
12M 4.5% 4.4501% 4.93% 5%
18M 4.75% 4.6945% 5.18% 5.25%
24M 5% 4.9385% 5.67% 5.75%
(b) The forward rate (expressed with continuous compounding) is
4.9385×2−4.6945×1.5
0.5
= 5.6707%. When expressed with semi-annual compounding the forward rate is
0.0567×0.5
2(𝑒 − 1) = 0.057518 = 5.7518%.

c) The value of an FRA that where you will receive 6% for the six month period starting in 18 months is

1,000,000 × (0.06 − 0.057518) × 0.5𝑒 −𝑅2 ×2 = 1,124 = $1,124.


Python code:

import numpy as np
def transform1(times,rates):
"semi-annual comp, to continuous compounding"
rates2 = 2*np.log(1+np.array(rates)/2)
forward_rates = [(times[i]*rates2[i]-times[i-1]*rates2[i-1])/(times[i]-times[i-1]\
) for i in range(1,len(rates2))]
forward_rates2 = 2*(np.exp(np.array(forward_rates)/2)-1)
return rates2,forward_rates,forward_rates2

def test():
times,rates = [0.5,1,1.5,2],[0.04,0.045,0.0475,0.05]
print(transform1(times,rates))
test()

2. Suppose that a company enters an FRA that pays the holder 4% and receives LIBOR 3M for 9-month
period starting in 3 years. The principal is $100 million dollar.

a) What are the cash-flows if the LIBOR proves to be 4.25%, 3.75%, 3.5% in 3Y, 3Y3M and 3Y6M?

b) What is the present value of these cash-flows at the starting date of the FRA?

Answer:

a) At the 3.25 year point there is a cashflow 𝐶𝐹1 = 100,000,000 × (0.04 − 0.0425) × 0.25 = 62,500.

At the 3.5-year point there is a cashflow 𝐶𝐹2 = −62,500 and at the 3.75-year point there is a cashflow
𝐶𝐹3 = −125,000.
62,500
b) At the 3 year point the equivalent cash-flow from the 3M cash-flow is 1+0.0425×0.25 = ⋯.
62,500
The equivalent cash-flow from the 6M cash-flow is − (1+0.0425×0.25)(1+0.0375×0.25) = ⋯

125,000
The equivalent cash-flow from the 9M cash-flow is − 1 )(1+𝑟 2 )(1+𝑟 3 ) =⋯
(1+𝑟3𝑀 3𝑀 3𝑀

1.7. Bond that include a certain probability of default. Models for default times and
associated measures.
Associated measures means: yield, par yield, duration, modified duration, convexity, spread / risk
premium.

The default models discussed are mostly exponential and piecewise exponential models.
Methodology
If the issuer of a bond defaults at time 𝜏 before maturity time T, some coupons and notional are not
paid. In this case, the buyer of the bond recovers part of the notional after the default time. In terms of
cash flows, we have:

• The coupons 𝐶(𝑡𝑚 ) if the bond issuer does not default before the coupon date 𝑡𝑚 :

∑ 𝐶(𝑡𝑚 ) ⋅ 1{𝜏 > 𝑡𝑚 }


𝑡𝑚 ≥𝑡

• The notional if the bond issuer does not default before the maturity date: 𝑁 ⋅ 1𝜏>𝑇 .

• The recovery part if the bond issuer defaults before the maturity date: 𝑅 ⋅ 𝑁 ⋅ 1𝜏≤𝑇 where 𝑅 is
the corresponding recovery rate.

Therefore the stochastic discounted value of the cash-flow leg is:


𝑡 𝑇
𝑆𝑉𝑡 = ∑𝑡𝑚≥𝑡 𝐶(𝑡𝑚 ) × exp(− ∫𝑡 𝑚 𝑟𝑠 𝑑𝑠 ) × 1𝜏>𝑡𝑚 + 𝑁 × exp(− ∫𝑡 𝑟𝑠 𝑑𝑠) × 1𝜏>𝑇 + 𝑅 × 𝑁 ×
𝜏
exp(− ∫𝑡 𝑟𝑠 𝑑𝑠) × 1𝜏≤𝑇 . (1)

Therefore 𝑃𝑡 + 𝐴𝐶𝑡 = 𝐸[𝑆𝑉𝑡 |𝐹𝑡 ] where 𝐴𝐶𝑡 = accrued coupon at time t.

Solved examples:

1. Suppose we have an exponential time 𝜏 ∼ exp(𝜆), with 𝜆, 𝐹𝑉(𝑓𝑎𝑐𝑒 𝑣𝑎𝑙𝑢𝑒), 𝑇 = 𝑙𝑖𝑓𝑒𝑡𝑖𝑚𝑒, 𝑅 =


𝑟𝑒𝑐𝑜𝑣𝑒𝑟𝑦 𝑟𝑎𝑡𝑒, 𝑐 = 𝑐𝑜𝑢𝑝𝑜𝑛 𝑟𝑎𝑡𝑒 given as input. Also the interest rate term is considered flat.

Compute the price of a bond using Monte Carlo simulation. We assume no accruals.
Solution:

We use formula (1) presented above. We must simulate 𝜏 along with 1𝜏>𝑡 and 1𝜏≤𝑡 .

We make a certain no. of simulations, compute the simulated quantity ∑𝑡𝑚≥𝑡 𝐶(𝑡𝑚 ) ×
𝑡 𝑇 𝜏
exp(− ∫𝑡 𝑚 𝑟𝑠 𝑑𝑠 ) × 1𝜏>𝑡𝑚 + 𝑁 × exp(− ∫𝑡 𝑟𝑠 𝑑𝑠) × 1𝜏>𝑇 + 𝑅 × 𝑁 × exp(− ∫𝑡 𝑟𝑠 𝑑𝑠) × 1𝜏≤𝑇 for each
simulation of 𝜏.

Python code:

def bond_price_risk1(c,N,times,rate,lbd,R,size):
"rate = function of zero interest rate,size = number of simulations"
"We assume an exponential time in case of default"
import numpy as np
def stochastic_bond_price1():
"MONTE CARLO SIMULATED PRICE "
def sample_expo(lbd):
u = np.random.uniform(0,1)
return -np.log(1-u)/lbd
def ind_expo(t,lbd):
"indicator function for exponentials"
return (sample_expo(lbd)>t)*1
"R = recovery rate,N=nominal value of bond"
"a sample for the discounted bond price"
"we assume an exponential time to default"
s=0
import scipy.integrate as integ
for i in range(len(times)-1):
s = s+c*N*(times[i+1]-times[i])*ind_expo(times[i],lbd)*\
np.exp(-integ.quad(rate,0,times[i])[0])
s = s + N*np.exp(-integ.quad(rate,0,times[-1])[0])*ind_expo(times[-1],lbd)
s= s + R*N*np.exp(-integ.quad(rate,0,sample_expo(lbd))[0])*\
(1-ind_expo(times[-1],lbd))
return s
prices = list([stochastic_bond_price1() for i in range(size)])
return np.mean(prices)
Remark: times = times of payments.

def test_bp1(): Out:


c,N,times,rate,lbd,R = 0.04,100,[0.5,1],lambda s:0.05,0.8,0.4 65.13678101047623
print(bond_price_risk1(c,N,times,rate,lbd,R,10000)) 66.4600063674307
import numpy as np
print(2*np.exp(-0.425)+102*np.exp(-0.85)+32*(1-np.exp(-0.85))/0.85)
test_bp1()

2. Compute the price of the same bond using a closed formula if possible.

Solution:

We will make first the following assumptions:

(H1): the default time and interest rates are independent

(H2): The recovery rate is known and not stochastic.


𝑇
Then 𝑃𝑡 + 𝐴𝐶𝑡 = ∑𝑡𝑚≥𝑡 𝐶(𝑡𝑚 )𝐵𝑡 (𝑡𝑚 )𝑆𝑡 (𝑡𝑚 ) + 𝑁𝐵𝑡 (𝑇)𝑆𝑡 (𝑇) + 𝑅 ⋅ 𝑁 ⋅ ∫𝑡 𝐵𝑡 (𝑢)𝑓𝑡 (𝑢)𝑑𝑢 (2)

If we consider 𝜏 ∼ exp(𝜆) , 𝑅𝑡 (𝑢) = 𝑟, 𝑡ℎ𝑒𝑛 𝑃𝑡 + 𝐴𝐶𝑡 = ∑𝑡𝑚≥𝑡 𝑒 −(𝑟+𝜆)(𝑡𝑚−𝑡) 𝐶(𝑡𝑚 ) + 𝑁𝑒 −(𝑟+𝜆)(𝑇−𝑡) +


1−𝑒 −(𝜆+𝑟)(𝑇−𝑡)
𝜆𝑅𝑁 ⋅ 𝑟+𝜆

For the following test, I will assume that the coupon is 𝑐 = 4%, notional 𝑁 = 100, the times of
payments are in 6 months and 1 year, the term rate is flat, 𝑟𝑡 = 5%, the default intensity 𝜆 = 8% and
the recovery rate is 𝑅 = 40%.

Solution:
1
1 1−𝑒 −(𝑟+𝜆)(𝑇−𝑡)
𝑃0 + 𝐴𝐶0 = 𝑒 −(𝑟+𝜆)⋅2 𝐶 (2) + 𝑒 −(𝑟+𝜆)⋅1 ⋅ 𝐶(1) + 𝑁𝑒 −(𝑟+𝜆)⋅1 + 𝜆𝑅𝑁 ⋅ 𝑟+𝜆

Python code :

def bond_price_risk2(c,N,times,rate,lbd,R):
import numpy as np
s=0
for i in range(len(times)-1):
if i==0:
s=s+times[0]*c*N*np.exp(-(rate+lbd)*times[i])
else:
s = s+(times[i+1]-times[i])*c*N*np.exp(-(rate+lbd)*times[i])
s = s+N*np.exp(-(rate+lbd)*times[-1])+lbd*R*N*\
(1-np.exp(-(rate+lbd)*times[-1]))/(rate+lbd)
return s
Test :

def test_bp2(): Out :


c,N,times,rate,lbd,R = 0.04,100,[0.5,1],0.05,0.8,0.4 65.60517650353324
print(bond_price_risk2(c,N,times,rate,lbd,R))
test_bp2()

1.8 Linear systems and bootstrapping


I will start with a sample example:

Suppose we observe on the market 4 bonds A, B, C, D with the following properties:

• Bond A, lifetime 2 years, coupon rate 3%, quoted price = 91.16 euros
• Bond B, lifetime 2 years, coupon rate 6%, quoted price = 96.59 euros
• Bond C, lifetime 3 years, coupon rate 5%, quoted price = 88 euros
• Zero coupon bond D, lifetime 4 years, face value 105, quoted price = 70.5 euros.
Find the zero rates for 1Y, 2Y, 3Y, 4Y.

I will present here an elegant solution through which one can find all rates at once.

The disadvantage is that it works only when the cash-flows times correspond to the term structure
rates.

I will come back later on this issue.

Solution to the example:

We denote 𝑉(0, 𝑖) the discounting factor for maturity 𝑖.

Therefore the 4 quoted prices can be translated into the next system of 4 equations with 4
unknowns (V(0,1), V(0,2), V(0,3), V(0,4)).

3 ⋅ 𝑉(0,1) + 103 ⋅ 𝑉(0,2) + 0 ⋅ 𝑉(0,3) + 0 ⋅ 𝑉(0,4) = 91.16


6 ⋅ 𝑉(0,1) + 106 ⋅ 𝑉(0,2) + 0 ⋅ 𝑉(0,3) + 0 ⋅ 𝑉(0,4) = 96.59
5 ⋅ 𝑉(0,1) + 5 ⋅ 𝑉(0,2) + 105 ⋅ 𝑉(0,3) + 0 ⋅ 𝑉(0,4) = 88.00
0 ⋅ 𝑉(0,1) + 0 ⋅ 𝑉(0,2) + 0 ⋅ 𝑉(0,3) + 105 ⋅ 𝑉(0,4) = 70.5
Under matrix form we can write the system as follows: 𝐹 ⋅ 𝑉 = 𝑃 where

3 103 0 0 𝑉(0,1) 91.16


6 106 0 0 𝑉(0,2) 96.59
𝐹=( ),𝑉 = ( ),𝑃 = ( )
5 5 105 0 𝑉(0,3) 88.00
0 0 0 105 𝑉(0,4) 70.5

The solution to this matrix equation is 𝑉 = 𝐹 −1 ⋅ 𝑃

Counterexample: If one of the bonds, would have semi-annual payments, the system would become
under-determined (too little equations/two many unknowns, e.g. the corresponding zero – rates).

From here, depending on the compounding, we can find 𝑅(0,1), 𝑅(0,2), 𝑅(0,3), 𝑅(0,4).

Remarks:

1. The matrix F of cash-flows must be invertible. Otherwise the system hasn’t a unique solution.

2. One can solve the previous problem also by finding first V(0,1), V(0,2) through the system 𝐹 ⋅ 𝑉 =
3 103 𝑉(0,1) 91.16
𝑃, 𝐹 = ( ),𝑉 = ( ),𝑃 = ( ) then by finding using a single equation 𝑉(0,3) and
6 106 𝑉(0,2) 96.59
the same for 𝑉(0,4).

3. Remark that when 𝐹 can be decomposed into Jordan blocks, where one block is full of zeros then
the system can be partitioned into subsystems.

4. All diagonal determinants, Δ1 , Δ2 , Δ3 , Δ4 must have non-zero determinants.

Forwards, Futures and other financial contracts


Forward rate agreements
Definition and notations:

A forward rate agreement is an OTC agreement designed to ensure that a certain interest rate will be
applied to either borrowing or lending a certain amount of money, at a certain period in the future.

Valuation:

𝑹𝑭 : the forward LIBOR interest rate for the period between time 𝑻𝟏 and 𝑻𝟐 calculated today

𝑹𝑲 : The rate of interest agreed to in the FRA

𝑳: the principal underlying the contract.

The present value of a forward rate agreement between times 𝑻𝟏 and 𝑻𝟐 under the above notations,
is given by:

𝑽𝑭𝑹𝑨 = 𝑳(𝑹𝒌 − 𝑹𝑭 )(𝑻𝟐 − 𝑻𝟏 )𝒆−𝑹𝟐𝑻𝟐 .

Example 1:
Suppose the LIBOR zero rates are as in the following table, under the FRA agreement we will receive 6%
with annual compounding and pay LIBOR on a principle of $100 million between the end of year 1 and
the end of year 2.

No. of years Zero rate corresponding


1 3.0%
2 4.0%
3 4.6%
4 5.0%
5 5.3%
The forward rates are as follows:

No. of years Zero rate Forward rate


1 3.0% --
2 4.0% 5%
3 4.6% 5.8%
4 5.0% 6.2%
5 5.3% 6.5%
The forward rate is 5% with continuous compounding and 5.127% with annual compounding.

It follows that the value of the FRA is: 100,000,000 × (0.06 − 0.05127)𝑒 −0.04×2 = $805,800.

Example 2:

We change only the lifetime of the FRA: between 𝑇1 = 1.5 (years) and 𝑇2 = 2.5 (𝑦𝑒𝑎𝑟𝑠).

This time we must follow the forward rate 𝑅𝐹 (𝑇1 , 𝑇2 ).


𝑇2 𝑅2 −𝑇1 𝑅1
For that we need 𝑅(1.5), 𝑅(2.5) therefore 𝑅𝐹 (𝑇1 , 𝑇2 ) = 𝑇2 −𝑇1
= 5.5%.(with continuous
compounding) = 5.65% (with annual compounding).

The price will therefore be 310,769 $ according to a similar computation.

Forward valuation
We have 3 types of situations:

1. No income is expected.

2. A known dividend yield is provided.

3. A fixed income is expected.

Methodology:

Suppose 𝐾 = is the strike price of the forward, 𝑆0 = current asset price, 𝑇 = lifetime of the forward
contract and 𝑓: [0, +∞) → 𝑅, 𝑓(𝑥) = 𝐵(0, 𝑥) the discount function (x = term).

The value of a forward contract is: 𝑓 = (𝐹0 − 𝐾)𝐵(0, 𝑇) where 𝐹0 =forward price for the lifetime (0, 𝑇).

Depending on the situation (1,2,3) we have the following formulas for 𝐹0 .


𝑆0
1. 𝐹0 = .
𝐵(0,𝑇)

𝑆0 𝑒 −𝑞𝑇
2. 𝐹0 = 𝐵(0,𝑇)
where 𝑞 = known dividend yield. One can

3. 𝐹0 = (𝑆0 − 𝐼) where 𝐼 = sum of discounted future income at the actual market rates.

Example 1:

The current value of a stock XYZ is 100$, the interest rate term is flat at the level 𝑟 = 3%. No income is
provided. The value of a forward contract lasting 3 years with strike price 𝐾 = 100$ is 𝑆0 − 𝐾𝑒 −𝑟𝑇 =
8.60$.

Example 2:

Suppose the continuous dividend yield is 1% = 𝑞.

The value of the forward contract is 𝑉0 = 𝑆0 𝑒 −𝑞𝑇 − 𝐾𝑒 −𝑟𝑇 = 5.65$

Example 3:

Suppose that 3 cash-flows are expected to be received in 3 months, 9 months and 15 months of values
2, 2 and 2$.

In that case, the total value of the discounted cash flows is 𝐼 = 𝐶𝐹1 𝑒 −𝑟𝑡1 + 𝐶𝐹2 𝑒 −𝑟𝑡2 + 𝐶𝐹3 𝑒 −𝑟𝑡3 =
2𝑒 −0.03⋅0.25 + 2𝑒 −0.03⋅0.75 + 2𝑒 −0.03⋅1.25 = 5.86.

The value of the forward contract is then 𝑆0 − 𝐼 − 𝐾𝑒 −𝑟𝑇 = 2.7399$

Example 4:

Suppose now that the interest rate is not flat anymore and we have the following term structure rate:

𝑟1𝑌 = 2%, 𝑟1.5𝑌 = 4%, 𝑟2𝑌 = 5%.

We must take into account that the avaiable interest rates are not corresponding to the terms of
expected cash flows. Therefore we must obtain these corresponding interest rates (via linear/quadratic
interpolation).

So the interest rates applying to the cash-flows are 2%,2% and 3%.

Therefore the discounted cash-flows will sum up to 5.88$ and the value of the forward will prove 8.04

Example 5:

Suppose now that there are expected dividends in 3 months, 9 months, 15 months and 39 months.

In that case the last dividend is not taken into account because it is after the closure of the contract and
the value stays the same.
Forward and future contracts on currencies

Futures on commodities
Interest rate futures
Day counts
1. Actual/actual (for treasury bonds)

2. 30/360 (corporate and municipal bonds)

3. Actual/360 (money market instruments)

Example 1:

Assume the bond principal is 100$, coupon payment dates are March 1 and September 1, and the
coupon rate is 8% per annum. What is the interest earned between March 1 and July 3rd?

A: There are 31+30+31+30+2 = 124 actual days between March the 1st and July the 3rd and 184 days
124
between March the 1st and September 1st therefore the accrued interest is 184 ⋅ 4 = 2.6957$.

Example 2:
122
In case of a bond issued by Ford, the accrued interest is ⋅ 4 = 2.7111
180

PRICE QUOTATIONS of US treasury bills

Example 3:

If the price of a 91-day treasury bill is quoted as 8, that means the rate of interest is 8% of the face value
91
per 360 days. If the face value is 100$ then the interest earned is $2.022 = 100 × 0.08 × 360.
2.022
The true interest rate is = 2.064% for the 91-period.
100−2.022

In general the relation between the cash price and quoted price of a United states T – bill is given by:
360
𝑃= (100 − 𝑌) where 𝑃 is the quoted price, 𝑌 is the cash price, n = remaining life time of the bill.
𝑛

PRICE QUOTATIONS OF T-BONDS

Treasury bond prices in US are quoted in dollars and thirty-seconds of a dollar. The quoted price is for a
bond with a face value of $100.
5
A quote of 90-05 means a price of 90 + 32 = 90.15625.

There is a difference between the clean price, as quoted price is reffered by traders, and dirty price
which is the actual cash price paid by the purchaser of the bond.

In general 𝐶𝑎𝑠ℎ 𝑝𝑟𝑖𝑐𝑒 = Quoted price + Accrued interest since last coupon date.
Example 4:

Suppose the coupon rate is 11% with semiannual payments, we’re in March 5, 2010 and the T-bond
ends in July 10th, 2018.

We are interested in the cash price of a bond with face value = 100$ having quoted price 95-16.

Solution:
𝐴𝑐𝑡𝑢𝑎𝑙 54
Accrued interest = 𝐴𝑐𝑡𝑢𝑎𝑙 × 5.5% × 100 = 181 ⋅ 5.5 = $1.64.
16
The clean price is 95 + 32 = 95.5$ and the cash price is 95.5 + 1.64 = 97.14$.

Eurodollar futures
Convexity adjustments

One popular adjustment known as convexity adjustment to account for the total difference between the
two rates. One popular adjustment is:
1
𝐹𝑜𝑟𝑤𝑎𝑟𝑑 𝑟𝑎𝑡𝑒 = 𝐹𝑢𝑡𝑢𝑟𝑒𝑠 𝑟𝑎𝑡𝑒 − 2 𝜎 2 𝑇1 𝑇2 ,

where 𝑇1 is the time to maturity of the underlying futures contract, 𝑇2 is the time to maturity of the
underlying rate and 𝜎 is the standard deviation of the change of the interest rate throughout
one year.

This part is very important when we want to expand the LIBOR Zero Curve.

Treasury bond futures


The cash received by the party delivering the bond is Most recent settlement price × 𝑪𝑭 + Accrued
Interest.

Example 1:

Suppose the last settlement price was 90-00, the conversion factor is 1.38 and the accrued interest is 3$.

Therefore the cash received by the party delivering the bond is (90 × 1.38) + 3 = 127.2$.

Example 2a:

Suppose we have a bond paying an annual coupon of 8%, paid twice a year, the time to maturity is 20
years and 2 months, the face value is 100$. We are interested to find the conversion factor of the bond.

Solution: For the purpose of computing the CF, we will approximate the time to maturity to 20 years and
we see that the payments are matching exactly the 6 months period.

If we assume a discount rate of 6% p.a. with semi-annual compounding, that is 3% every 6M the value of
5 100
the bond is: ∑40
𝑖=1 (1.03)𝑖 + (1.03)40 = $146.23.
𝐵 146.23
The conversion factor will be 𝐹𝑉0 = 100
= 1.4623.

Example 2b:

Suppose we have now a 8% coupon paying treasury bond made every 6 months, with a remaining time
of 18 years and 4 months. Find the conversion factor of the treasury bond.

Solution:

THE CONVENTION IS TO CONSIDER THE SAME 6% rate.

The first thing is to round the 18 years and 4 months to 18 years and 3 months.

We compute the value of the bond in 3 months which will use only the 6M semi-annual rate and then
discount it at a 3M rate.
4 100
The value in 3 months is: 4 + ∑36
𝑖=1 (1.03)𝑖 + (1.03)36

The equivalent interest rate for 3M period is √1.03 − 1 = 𝑟3𝑀 .

CHEAPEST TO DELIVER BOND


The cheapest to deliver bond can be defined as the bond for which Quoted bond price – (Most recent
settlement price x Conversion Factor) = minimum.

That is because the cash received by the party delivering is: Most recent settlement price x CF + Accrued
Interest and the price paid for obtaining the bond is: Quoted Price + Accrued Interest.

The party to deliver has to choose between the following 3 bonds:

Bond # Quoted bond price Conversion factor


1 99.5 1.0382
2 143.5 1.5188
3 119.75 1.2615
If the last settlement price is 93-08 then the cost of delivering each bond is:

Bond 1: 99.50 – (93.25 x 1.0382) = $2.69

Bond 2: 143.50 – (93.25x1.5188) = $1.87

Bond 3: 119.75 – (93.25 x 1.2615) = $2.12

Therefor bond number 2 is the cheapest.

Of course, in practice, we don’t know the conversion factor (it is to be obtained from the present value
of the bond and the face value). See the previous section for that.

EXERCISES
1. A US Treasury bond pays a 7% coupon on January 7 and July 7. How much interest accrue per 100$ of
principal to the bond holder between July 7, 2009 and August 9, 2009?
How would your answer be different if it were a corporate bond? (Source: Hull, Options, Futures and
Derivatives, 8th edition).

Solution:

There are 33 calendar days between July 7, 2009 and August 9, 2009. There are 184 calendar days
between July 7, 2009 and January 7, 2010. The interest earned per $100 of principal is therefore
33
3.5 × 184 = 0.6277$. For a corporate bond we assume a day count of type 30/360, so the interest
32
earned is 3.5 × 180 = $0.622.

2. It is January 9, 2009. The price of a Treasury bond with a 12% coupon that matures on October 12,
2020, is quoted as 102-07. What is the cash price?

Solution:

There are 89 days between October 12, 2009 and January 9, 2010. There are 182 days between October
12, 2009, and April 12, 2010.

The cash price of the bond is obtained by adding the accrued interest to the quoted price. The quoted
7 89
price is 102 32. The cash price is therefore 102.21875 + 182 × 6 = $105.15.

3. What is the conversion factor of a bond lasting 10 years, having a coupon rate of 10%, paid semi-
annually, with a discount rate of 6% with semi-annual compounding.

Solution:

We must compute the price of the bond using semi-annual compounding. (In fact in all conversion
factors, it is assumed this “rule” of 6% with semi-annual compounding).
5 100
The price of the 10-year bond is ∑20
𝑖=1 (1.03)𝑖 + (1.03)20 = 129.75$ and the conversion factor is therefore
𝑃 129.75
𝐶𝐹0 = 𝐹𝑉0 = 100
.

4.

Swaps. Interest rate, currency and credit


1. Fixed for floating interest rate swaps
Objective:

The objective is to build a pricer for fixed-for-floating IRS knowing the following parameters:
Face Value of swap (FV), fixed coupon (c), floating rate (3M,6M,1Y Libor/Euribor), frequency of
payments, zero rates used.

Methodology:
A swap can be decomposed in 2 bonds: 1 floating-rate bond and 1 fixed-rate bond, (floating leg and
fixed leg).
If a buyer of a swap, has to pay a fixed coupon and receives a floating coupon (after the floating rate),

𝑉𝑠𝑤𝑎𝑝 = 𝐵𝑓𝑙 − 𝐵𝑓𝑖𝑥 . For the counterparty, the value is given by 𝑉𝑠𝑤𝑎𝑝 = 𝐵𝑓𝑖𝑥 − 𝐵𝑓𝑙 .
Example 1: (Hull)
Suppose a financial institution agrees to buy a 6M Libor and receive 8% per annum, with semi-annual
compounding on a notional of $100 million. The swap has a remaining life of 1.25 years. The LIBOR rates
with continuous compounding for 3M, 9M, 15M maturities are 10%,10.5%,11%. The 6M-Libor rate at
the last payment date was 10.2% (continuous compounding).
What is the swap value?

A: The fixed-rate bond has remaining cash-flows of 4,4 and 104 in 3M, 9M and 15M(months)
The discount values are: 3.9, 3.697,90.63.
The price of the fixed-rate bond is therefore their sum: 98.237
The floating rate bond will generate a cashflow of 10.2 % *$100 mil /2 +100 = $105.1 mil in 3M (0.25
years).
The present value of the future cash-flow is: 105.1 * 𝑒 −𝑟3𝑀 =102.505 mil $.
The value of the swap is therefor 𝐵𝑓𝑖𝑥 − 𝐵𝑓𝑙 = 98.237 − 102.505 = −4.267 𝑚𝑖𝑙 $.
Python code:
I build up generic function for bond pricing and later, for swap prices.
def discount_rates(times,rates):
import numpy as np
return np.array([np.exp(-times[i]*rates[i])for i in range(len(times))])

def discounted_cf(times,rates,cash_flows):
import numpy as np
return discount_rates(times,rates)*np.array(cash_flows)

def bond_price_bis(times,rates,cash_flows):
import numpy as np
return np.dot(discount_rates(times,rates),np.array(cash_flows))

def test_disc():
'I am testing the discounted rates functions and the bond prices'
times = [0.25,0.75,1.25]
rates = [0.1,0.105,0.11]
cash_flows = [4,4,104]
fl_rate,time = 0.102,0.25
print(discount_rates(times,rates))
print(discounted_cf(times,rates,cash_flows))
print(bond_price_bis(times,rates,cash_flows))
fix_bond = bond_price_bis(times,rates,cash_flows)
import numpy as np
fl_bond = np.exp(-time*rates[0])*(fl_rate/2+1)*100
print('the price of the swap is ', fix_bond-fl_bond)

Example 2:
Suppose we have a 9M interest rate swap, with quarterly payments, where 4% annual coupon is
exchanged for Libor 3M. Find the price of the swap if the Libor zero rates for 3M,6M and 9M are
3%,3.5% and 4%.

Python code:
def exercise2():
times = [0.25,0.5,0.75]
zero_rates = [0.03,0.035,0.04]
c,T,freq,FV = 0.04,0.75,4,100
cf = cash_flows2(c,freq,T,FV)
b_fix = lot.bond_price_bis(times,zero_rates,cf)
b_fl = lot.bond_price_bis([times[0]],[zero_rates[0]],[FV + zero_rates[0]/freq * FV])
print(b_fix - b_fl)
print(discounted_cf(times,rates,cash_flows))
print(bond_price_bis(times,rates,cash_flows))
fix_bond = bond_price_bis(times,rates,cash_flows)
import numpy as np
fl_bond = np.exp(-time*rates[0])*(fl_rate/2+1)*100
print('the price of the swap is ', fix_bond-fl_bond)
For the “lot” file and “cash_flows2 see Appendix
If the zero rates’ terms corresponding to the remaining times to the payments, one can write a more
generic function for a swap price:

def swap_price(zero_rates,c,T,freq,FV):
k = int(T*freq)
import numpy as np
times = np.linspace(T/k,T,k)
cf = cash_flows2(c,freq,T,FV)
b_fix = lot.bond_price_bis(times,zero_rates,cf)
b_fl = lot.bond_price_bis([times[0]],[zero_rates[0]],[FV+zero_rates[0]/freq*FV])
return b_fix-b_fl

Remarks:
1. “k” is the number of payments, “cf: is a vector from 𝑅 𝑘 representing the cash flows of the fixed bond.
2. For the floating rate bond, the first time of the payment coincides with the first payment of the fixed-
bond, which is times[0], the first zero rate applies to the payment of the floating rate at times[0], and
the value is FV+zero_rates[0]/freq * FV.
E.g. if a payment is due in 3M, the floating bond cash flow in 3M would be 𝐹𝑎𝑐𝑒 𝑣𝑎𝑙𝑢𝑒 +
𝑐
𝑑𝑖𝑠𝑐𝑜𝑢𝑛𝑡𝑒𝑑 𝑐𝑎𝑠ℎ 𝑓𝑙𝑜𝑤 𝑜𝑓 4 𝑎𝑡 𝑡ℎ𝑒 𝑧𝑒𝑟𝑜 𝑟𝑎𝑡𝑒0 𝑑𝑖𝑠𝑐𝑜𝑢𝑛𝑡 𝑟𝑎𝑡𝑒.
We can test the function of the swap price depending on the coupon rate.
def exercise_swap():
zero_rates = [0.03,0.035,0.04]
T,freq,FV = 0.75,4,100
h = lambda c: swap_price(zero_rates,c,T,freq,FV)
import numpy as np
Sp = np.linspace(0,0.2,21)
import matplotlib.pyplot as plt
plt.plot(list([h(x) for x in Sp]))
plt.xlabel('Fixed coupon rate'),plt.ylabel('swap value')
plt.grid(True)
plt.show()

Result:

Solved exercises:
1. Suppose Microsoft must pay 1 million $ at LIBOR 6M + 0.1% (semi-annual payments for 5 years) and
Intel must pay 1 million $ at 3% (also semi-annual payments) to outside lenders. These 2 are interested
to change the payments from floating to fix and viceversa. Suppose a bank intermediates the two
through a fixed for floating swap (2.5% against LIBOR). Also assume that the bank requires a 0.1% of the
principal as its part for exposing to default risk.

Design a table of cash flows in case LIBOR stays constant throughout the life of the swap.

Solution:

Suppose Microsoft and Intel enter this swap. After entering, the situation of cash-flows transforms as
follows:

Microsoft will pay LIBOR + 0.1% - LIBOR + 2.5% = 2.6%

Intel will pay 3%-2.5% + LIBOR = LIBOR + 0.5%.

Because the bank also wants 0.1% from the exchanged cash-flows, the two parties (Mic and Intel) will
pay 2.65% and LIBOR + 0.55%.
Suppose the LIBOR rate is 3%. For Microsoft, the inflows and outflows are presented in the following
table.

Times (years) Microsoft outflows (fixed rate) Microsoft inflows (floating rate)
0.5 13250.0 17750.0
1 13250.0 17750.0
1.5 13250.0 17750.0
2 13250.0 17750.0
2.5 13250.0 17750.0
3 13250.0 17750.0
3.5 13250.0 17750.0
4 13250.0 17750.0
4.5 13250.0 17750.0
5 13250.0 17750.0

Python code:

import numpy as np
def cash_flows(r1,r2,N,T,freq,option=1):
"""Legend:
"r1 = fixed rate, a real number"
"r2 = floating rate, a sequence of LIBOR rates whose length must be equal"
"with [T*freq] where [x] = integer part of x"
"T = time to maturity and N = notional.
"""
if len(r2)!=int(T*freq):
raise Exception('the floating rates must be consistent with the\
frequency and time to maturity')
payer = [r1/freq*N]*int(T*freq)
receiver = [r2[i]*N/freq for i in range(len(r2))]
import pandas as pd
if option==1:
times = np.linspace(0,T,int(T*freq),endpoint = False)+1/freq
d = {'inflows (fixed rate)':receiver,'outflows (floating rate)':payer,\
'times':times}
return pd.DataFrame(data = d,index = range(1,int(T*freq)+1))
else:
d = {'inflows (fixed rate)':receiver,'outflows (floating rate)':payer}
return pd.DataFrame(data = d,index = range(1,int(T*freq)+1))

Test:

def test1a(): inflows (fixed rate) outflows (floating rate) times


r1 = 0.0265 1 17750.0 13250.0 0.5
r2 = [0.0355]*10 2 17750.0 13250.0 1.0
N,T,freq = 1000000,5,2 3 17750.0 13250.0 1.5
#print(np.linspace(0,1,10,endpoint = 4 17750.0 13250.0 2.0
False)+0.1) 5 17750.0 13250.0 2.5
print(cash_flows(r1,r2,N,T,freq)) 6 17750.0 13250.0 3.0
print(cash_flows(r1,r2,N,T,freq,2)) 7 17750.0 13250.0 3.5
8 17750.0 13250.0 4.0
test1a() 9 17750.0 13250.0 4.5
10 17750.0 13250.0 5.0

2. Currency swaps
Exercise 1

Company X wishes to borrow US dollars at a fixed rate of interest. Company Y wishes to borrow
Japanese yen at a fixed rate of interest. The amounts required by the two companies are the same at
the current exchange rate. The companies are subject to the following interest rates, which have been
adjusted to reflect the impact of the taxes:

Yen Dollars
Company X: 5.0% 9.6%
Company Y 6.5% 10.0%
Design a swap that will net a bank, acting as intermediary, 50 basis points per annum.

Make the swap equally attractive to the two companies and ensure that all foreign exchange risk is
assumed by the bank.

Solution:

X has a comparative advantage in yen markets but wants to borrow dollars. Y has a comparative
advantage in dollar markets but wants to borrow yen. This provides the basis for a swap.

There is 1.5% p.a. differential between the yen rates and a 0.4% p.a. differential between the dollar
rates. The total gain to all parties from the swap is therefore 1.5-0.4 = 1.1%p.a.

The bank requires 0.5% p.a. leaving 0.3% p.a. for each X and Y. The swap should lead to X borrowing
dollars at 9.6 – 0.3% = 9.3% p.a. and to Y borrowing yen at 6.5 -0.3 = 6.2% p.a.

Exercise 2 (inspired from Hull, Options, Futures and Other derivatives)

Suppose that the term structure of LIBOR/swap interest rates is flat in both Japan and US at 4% p.a. in
Japan, and 9% in US (continuous compounding). A financial institution enters a currency swap where it
receives 5% p.a. in yen and pays 8% p.a. in dollars once a year. The principals in the two currencies are
$10 million and 1,200 million yen. The swap will last for another 3 years, and the current exchange rate
is 110 yen = $1.

a. What is the present value of the underlying bond defined by the dollar cash-flows of the swap?

b. What is the present value (in $) of the underlying bond defined by the yen cash-flows of the swap?

Present a table of cash-flows in $ and Yen along with their present values.

c. What is the value of the currency swap?


Solution:

a. The dollar cash-flows of the swap are: 0.8 in 1 year, 0.8 in 2 years, 10.8 = 0.8 + 10 (notional) (in 3
years). The present value of the underlying bond is 𝑉0 = 𝑒 −0.09 × 0.08 + 𝑒 −0.09×2 × 0.08 +
𝑒 −0.09×3 × 10.8 = 9.6439 (𝑚𝑖𝑙𝑙𝑖𝑜𝑛)$.
b. The cash-flows in yen are 60,60,1260 (in 1,2 and 3 years respectively). (60 = 5% x 1200)

The value of the underlying bond is 𝑊0 = 𝑒 −0.04 × 60 + 𝑒 −0.04×2 × 60 + 𝑒 −0.04×3 × 1260 = 1,230.55
𝑊0
c. The value of the currency swap (in US $), is (considering that I receive yen and pay $): 𝑆0
− 𝑉0 =
1,230.55
110
− 9.6439 = 1.5430 𝑚𝑖𝑙𝑙𝑖𝑜𝑛 ($)

Exercise 3 (Extend the no. of years to 𝒏, 𝒏 ≥ 𝟑).

What is the value of the above swap if the no. of years is 5? 10? Draw a graph of the value of the
currency swap depending on the no. of years.

Solution:

Here I am forced to build a function that computes this in a more general fashion. Manual computations
are out of the question. Voilà:

def currency_swap(S0,r1,r2,fix_rate1,fix_rate2,FV1,FV2,N):
""" Legend: S0 = spot exchange rate
N = no.of years of the swap, fix_rate1 = fixed rate received,
fix_rate2 = fixed rate payed,FV1, FV2 = the 2 notionals"""
CF1 = [fix_rate1 * FV1 if i<N-1 else (fix_rate1+1)*FV1 for i in range(N)]
CF2 = [fix_rate2 * FV2 if i<N-1 else (fix_rate2+1)*FV2 for i in range(N)]
discounted_values1 = [np.exp(-r1*(i+1))*CF1[i] for i in range(N)]
discounted_values2 = [np.exp(-r2*(i+1))*CF2[i] for i in range(N)]
return sum(discounted_values1)/S0 - sum(discounted_values2)
The requested graph is as follows:
The 5-year price is 1.8997 million $ and the 10-year currency swap price is 2.6120 $.

Exercise 4 (The term structures are not flat anymore).

Suppose the contract parameters are the same as in exercise 2. Now the term structures in Japan are 4%
up to 1 year included and 5% from 1 year on (time to maturity) while in US is 7% up to 2 years included
and 9% from 2 years on. What is the value of the 3-year currency swap in that case?

Answer: 1.1907331053442718 million $

This date the curve of the value looks as follows.


One can observe that the value decreases around 2 years point, changes slope around 3 year time and
then is asymptotically to 2.2 million $ as lifetime increases to 50 years.

Cross-currency swaps
Definition:

These are fixed for floating currency swaps, whereby a floating rate (usually LIBOR) in one currency is
exchanged for a fixed rate in another currency. This is a combination between a fixed-for-floating
interest rate swap and a fixed-for-fixed currency swap.

Valuation

Suppose we receive floating rate payments in currency A and pay fixed rate payments in currency B.

Then the floating payments can be valued in currency A by (i) assuming that the forward rates are
realized and (ii) discounting the resulting cash-flows at appropriate currency A discount rates.

Suppose that the present value in currency A is 𝑉𝐴 . The fixed payments can be valued in currency B by
discounting them at the appropriate currency B discount rates. Suppose that the present value of these
cash-flows is 𝑉𝐵 . If 𝑄 is the current exchange rate (# of units of currency A per unit of currency B), the
value of the swap in currency A is 𝑉𝐴 − 𝑄𝑉𝐵 .

3. Credit Default Swaps


Definition: A credit default swap can be defined as an insurance derivative, whose goal is to transfer
credit from one party to another. In a standard contract, the protection buyer makes periodic payments,
known as the premium leg, to the protection seller. In return, the protection seller pays a compensation,
known as the default leg, to the protection buyer in case of a credit event (bankruptcy, failure to pay or
debt restructuring).

Elements:

We define first a single-name CDS. The contract is defined by:

Reference entity: the name


Notional: 𝑁
Payment frequency
Coupon rate: 𝑐
Maturity/tenor: 𝑇.
From the inception time t to maturity T, or the default time 𝜏, the protection buyer pays a fixed
payment, which is equal to 𝑐 × 𝑁 × Δ𝑡𝑚 at the fixing date 𝑡𝑚 with Δ𝑡𝑚 = 𝑡𝑚 − 𝑡𝑚−1 . The annual
premium leg is 𝑐 × 𝑁. If there is no credit event, the protection buyer will pay a total of
𝑐 × 𝑁 × (𝑇 − 𝑡).

In case of credit event before the maturity, the protection seller will compensate the protection buyer
and will pay (1 − 𝑅) × 𝑁.

Example 1:
If the notional is 𝑁 = 10 𝑚𝑖𝑙𝑙𝑖𝑜𝑛 $, the maturity is 𝑇 = 5 years and the payment frequency is quarterly.
We assume the recovery rate is set to 40% and the coupon rate is equal to 2%.

Solution:

There are 20 fixing dates, which are 3M, 6M, 9M, 12M, …., 5Y.

Each quarter, if the corporation does not default, the protection buyer pays 0.25 × 10𝑚𝑛 × 2% =
$50,000.
If there is no default during the next five years, the protection buyer will pay a total of $50,000× 20 =
1𝑚𝑛$ .

Suppose now that the corporate defaults two years and four months after the CDS inception date.

In this case, the protection buyer will pay $50,000 during the next 9 quarters and will receive the
protection seller at the default time. This protection leg is equal to (1 − 40%) × $10𝑚𝑛 = $6𝑚𝑛.

Valuation of a CDS
𝑇
The value of a CDS to the protection buyer is 𝑃𝑉𝑡 (𝐷𝐿) − 𝑃𝑉𝑡 (𝑃𝐿) = (1 − 𝑅) ⋅ 𝑁 ⋅ ∫𝑡 𝐵𝑡 (𝑢)𝑓𝑡 (𝑢)𝑑𝑢 −
𝑐 ⋅ 𝑁 ⋅ ∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑆𝑡 (𝑡𝑚 )𝐵𝑡 (𝑡𝑚 )

Example 2:

Design the cash-flows of a CDS, where the recovery rate is designed to be 40%, the lifetime 2 years, the
notional 1000$, the coupon rate is 4%, the frequency payment is quarterly when:

a. The default never occurs.

b. The default occurs after 10 M.

c. The default occurs immediately after 15M.

Solution:

a. 1% of the notional is paid each quarter, that is 𝐶𝐹 = 1% ⋅ 1000 ⋅ 0.25 = 2.5$.

That is, the total amount to be paid will be 2.5 ⋅ 8 = 20$. The payoff will be 20$.

b. The first 3 quarters, the protection buyer pays 2.5$ ⋅ 3 = 7.5$.

After the default, the protection seller pays (1 − 40%) ⋅ 1000 = 600$.

The payoff in this case, will be 7.5 − 600 = −592.5$ for the protection seller.

c. The first 5 quarters, the protection buyer pays 2.5 ⋅ 5 = 12.5 but has to pay 600$.
Example 3:

a. What is the P&L of the protection seller, if the default never occurs. We consider a flat interest rate
term of 4% at the origin and an exponential default time with intensity 0.4. The rest of data is the same
as in exercise 1.

b. What is the P&L of the protection seller, if the default occurs after 1 year and 1 month.

c. What is the P&L of the protection seller, if the default occurs immediately.

d. Draw a graph of the CDS price depending on the 𝜆 parameter, as well as one depending on spread.
The other parameters remain fixed.

Solution:

We need the price at time t = 0 of the CDS.


𝑇
𝑃𝑡 (𝑇) = 𝑃𝑉𝑡 (𝐷𝐿) − 𝑃𝑉𝑡 (𝑃𝐿) = (1 − 𝑅) ⋅ 𝑁 ⋅ ∫𝑡 𝐵𝑡 (𝑢)𝑓𝑡 (𝑢)𝑑𝑢 − 𝑐 × 𝑁 × ∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑆𝑡 (𝑡𝑚 )𝐵𝑡 (𝑡𝑚 )
𝑃(𝜏>𝑡𝑚 )
Here 𝑆𝑡 (𝑡𝑚 ) = 𝑃(𝜏 > 𝑡𝑚 |𝜏 > 𝑡) = 𝑃(𝜏>𝑡)
= 𝑒 −𝜆(𝑡𝑚−𝑡).

𝑇 𝜆
𝑃𝑉𝑡 (𝐷𝐿) = (1 − 𝑅) ⋅ 𝑁 ⋅ 𝜆 ⋅ ∫𝑡 𝑒 −(𝑟+𝜆)(𝑢−𝑡) 𝑑𝑢 = (1 − 𝑅) ⋅ 𝑁 ⋅ 𝑟+𝜆 ⋅ (1 − 𝑒 −(𝑟+𝜆)(𝑇−𝑡) )

𝑃𝑉𝑡 (𝑃𝐿) = 𝑐 ⋅ 𝑁 ⋅ ∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑒 −(𝑟+𝜆)(𝑡𝑚−𝑡)


1
In that case, Δ𝑡𝑚 = = 0.25 and the price of the CDS (what the seller of the CDS obtains) is 268.88$.
4

a. If the default never occurs, the protection seller receives 268.88 (the price) + 20 (the 8 payments) =
288.88. The P&L therefore is 288.88

b. If the default occurs after 1 year and 1M, the protection seller receives 10 +268.88 but pays in return
600 therefore P&L = 278.88 – 600 = -321.12

c. If the default occurs immediately, the P&L is -331.12$.

d. The graphs depending on the spread and flat interest rate are as follows:
Python code:

I will skip the routine initialization for the CDS price class (defining the parameters of the CDS contract
into an __init__ function) and give instead only the pricer.

import numpy as np
def CDS1(R,N,T,freq,r,lbd,c):
"R = recovery rate, N = notional, T = tenor, r = flat int.rate,lbd = "
" exponential r.v. intensity of default"
pr_leg = 0
times = np.linspace(1/freq,T,int(T*freq),endpoint = True)
for i in range(int(T*freq)):
pr_leg = pr_leg + c*N*1/freq*np.exp(-(r+lbd)*(times[i]))
disc_leg = (1-R)*N*(lbd/(r+lbd))*(1-np.exp(-(r+lbd)*T))
return disc_leg - pr_leg

Remarks:

1. The larger the spread is, the smaller gets the price of the CDS.

2. When the intensity of the default increases, the price of the CDS grows as well. That is, simply put, the
riskier gets the CDS contract the higher the cost of hedging will be, which from a heuristical point of
view is natural.

We will analyze in what follows the sensitivities of a CDS when it comes to change one or several
parameters at once.

Problem 3: What happens with the price of the CDS when time decreases? All other factors (interest
rate, default intensity remain constant).

Example 3.1: Considering the data from examples 2 and 3, (R = 40%, T = 2 years, Nominal = 1000$, c
(coupon rate = 4%)), 𝜆 = 0.4, 𝑟 = 4% remaining constant, what is the evolution of the price of the CDS
after: a) 3 months; b) 6 months; c) 12 months; d) 15 months. Deduce the price curve through
interpolation.

Solution:

In the construction of the pricer we must take care of the posterior paying times to 𝑡.

Therefore we must take care of the 𝐷𝑉01 = ∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑆𝑡 (𝑡𝑚 )𝐵𝑡 (𝑡𝑚 ).
For that we section the pricer in several steps

First we need to select the posterior prices to time 𝑡.

def search_times(t,T,freq):
"gives me both the payment times following time t and the differences from"
"time t to each posterior payment time"
import numpy as np
times = np.linspace(1/freq,T,int(T*freq),endpoint = True)
y = np.array(times[np.array(times)>=t])
return y,y - np.array(len(y)*[t])

Secondly, the cash-flows associated:

def cash_flows(t,T,freq,N,c):
" these are cash-flows designed after time t"
import numpy as np
times = np.linspace(1/freq,T,int(T*freq),endpoint = True)
rem_pay_times = times[np.array(times)>=t]
return [c*N*1/freq]*len(rem_pay_times)

Now, the main function:

def CDS_price1(c,N,t,T,freq,R,r,lbd):
import numpy as np
import scipy.integrate as integ
f = lambda u: np.exp(-r*(u-t)) #discount rate
g = lambda u: np.exp(-lbd*(u-t)) #survival function
h = lambda u: lbd*np.exp(-lbd*(u-t)) #density of survival
CF = cash_flows(t,T,freq,N,c)
times = search_times(t,T,freq)[0]
discount_rates = [f(x) for x in times]
survival_rates = [g(x) for x in times]
premium_leg = c*N*1/freq * np.dot(np.multiply(discount_rates,survival_rates),\
CF)
integr = lambda u: f(u)*h(u)
discount_leg = (1-R)*N*integ.quad(integr,t,T)[0]
return discount_leg - premium_leg

Example 3.2:

Problem 4: What happens when interest rates change?

Problem 5: What if we assume the intensity of default changes with time.

Problem 6: What if we assume a non-constant spread? A progressive one, with time.


Problem 7: Find the default intensity from the price of a CDS.

Problem 8: Find the default intensity from the spread of a CDS.

PROBLEM 7:

Here we have two main inputs: the price of the CDS, 𝑃𝑡 (𝑇) and the spread.

The remaining data to be considered as input are the contact parameters as well as the market
variables: notional (N), tenor (lifetime) (T), the interest rate(s) (curve) 𝑟𝑡 (𝑇), and the payment
frequency.

Relation between price and spread:


𝑻
From the relation 𝑷𝒕 (𝑻) = (𝟏 − 𝑹) ⋅ 𝑵 ∫𝒕 𝑩𝒕 (𝒖)𝒇𝒕 (𝒖)𝒅𝒖 − 𝒄 ⋅ 𝑵 ⋅ ∑𝒕𝒎 ≥𝒕 𝚫𝒕𝒎 𝑺𝒕 (𝒕𝒎 )𝑩𝒕 (𝒕𝒎 ) ⇒ 𝒄 =
𝑻
((𝟏 − 𝑹) ⋅ 𝑵 ⋅ ∫𝒕 𝑩𝒕 (𝒖)𝒇𝒕 (𝒖)𝒅𝒖 − 𝑷𝒕 (𝑻))/(𝑵 ⋅ ∑𝒕𝒎 ≥𝒕 𝚫𝒕𝒎 𝑺𝒕 (𝒕𝒎 )𝑩𝒕 (𝒕𝒎 )) = 𝑫𝟏 /𝑫𝟐

Situation:

Suppose a flat interest rate, and a constant intensity (hazard rate) for the default of an instrument:
𝜆
𝐵𝑡 (𝑢)𝑓𝑡 (𝑢) = 𝜆𝑒 −(𝑟+𝜆)(𝑢−𝑡) therefore 𝐷1 = (1 − 𝑅) ⋅ 𝑁 ⋅ 𝑟+𝜆 ⋅ (1 − 𝑒 −(𝑟+𝜆)(𝑇−𝑡) ) − 𝑃𝑡 (𝑇) and
1 𝑁
𝐷2 = 𝑁 ⋅ ∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑆𝑡 (𝑡𝑚 )𝐵𝑡 (𝑡𝑚 ) = 𝑁 ⋅ ∑𝑡𝑚≥𝑡 ⋅ 𝑒 −𝜆(𝑡𝑚−𝑡) ⋅ 𝑒 −𝑟(𝑡𝑚−𝑡) = ⋅ ∑𝑡𝑚≥𝑡 𝑒 −(𝑟+𝜆)(𝑡𝑚−𝑡)
𝑓𝑟𝑒𝑞 𝑓𝑟𝑒𝑞

This is a Riemann sum of 𝐵𝑡 (𝑢)𝑆𝑡 (𝑢) on the interval [𝑡, 𝑇].

The spread 𝑠𝑡 (𝑇) = 𝐷1 /𝐷2 .

If a continuous payment is assumed, (the frequency is very high):


1
𝐷2 = ⋅ (1 − 𝑒 −(𝑟+𝜆)⋅(𝑇−𝑡) ).
𝑟+𝜆
𝜆
(1−𝑅)⋅𝑁⋅ ⋅(1−𝑒 −(𝑟+𝜆)(𝑇−𝑡) )−𝑃𝑡 (𝑇)
𝜆+𝑟
In this case, 𝑠𝑡 (𝑇) = 1 .
𝑁⋅
𝑟+𝜆
(1−𝑒 −(𝑟+𝜆)(𝑇−𝑡) )

PROBLEM 8:

I will remind the reader that the spread of a CDS is the value of the coupon included in the premium leg
that makes the initial value of a CDS equal to 0.

If we assume the default time 𝜏 is having the survival function 𝑆𝑡 (𝑢) = 𝑃(𝜏 > 𝑢|𝜏 > 𝑡) and the discount
function is 𝐵𝑡 (𝑢) then the value of the spread is given by 𝑃𝑡 (𝑇) = 0 ⇔ (1 − 𝑅) ⋅ 𝑁 ⋅
𝑇 𝑇
∫𝑡 𝐵𝑡 (𝑢)𝑓𝑡 (𝑢)𝑑𝑢 − 𝑐 × 𝑁 × ∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑆𝑡 (𝑡𝑚 )𝐵𝑡 (𝑡𝑚 ) = 0 ⇔ 𝑠 = ((1 − 𝑅) ⋅ ∫𝑡 𝐵𝑡 (𝑢)𝑓𝑡 (𝑢)𝑑𝑢)/
𝑇
∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑆𝑡 (𝑡𝑚 )𝐵𝑡 (𝑡𝑚 ) 𝑜𝑟 when the premium is paid continuously 𝑠 = ((1 − 𝑅) ⋅ ∫𝑡 𝐵𝑡 (𝑢)𝑓𝑡 (𝑢)𝑑𝑢)/
𝑇
(∫𝑡 𝐵𝑡 (𝑢)𝑆𝑡 (𝑢)𝑑𝑢 )
In the latter case, we call the numerator 𝐷1 and the denominator 𝐷2.
𝜆
After some proper computations, 𝐷1 = 𝜆+𝑟 ⋅ (1 − 𝑅) ⋅ (1 − exp(−(𝑟 + 𝜆)(𝑇 − 𝑡)))(1)

If we assume 𝑟 = 0, 𝐷1 = (1 − 𝑅)(1 − exp(−𝜆(𝑇 − 𝑡))) (2)


1
On the other hand, 𝐷2 = 𝑟+𝜆 ⋅ (1 − exp(−(𝑟 + 𝜆)(𝑇 − 𝑡)))(3)
1
If we assume 𝑟 = 0, 𝐷2 = 𝜆 ⋅ (1 − exp(−𝜆(𝑇 − 𝑡))) (4)
𝐷
Therefore (2), (4)⇒ 𝐷1 = 𝜆(1 − 𝑅) = 𝑠.
2

𝐷1 𝜆2
On the other hand, from (1), (3) we have = (1 − 𝑅) = 𝑠.
𝐷2 𝑟+𝜆

𝑠 𝑠
We obtain a second degree equation in 𝜆: 𝜆2 − 𝜆 ⋅ 1−𝑅 − 1−𝑅 ⋅ 𝑟 = 0.

𝑠 𝑠 2 𝑠
±√( ) +4⋅ ⋅𝑟
1−𝑅 1−𝑅 1−𝑅
Therefore 𝜆1,2 = .
2

4. First to Default Swaps, Second to Default swap


5. Basket Default Swaps

Options.
1. Plain vanilla european options. Binomial pricing method.
Definition:

An european call option is the financial instrument that gives the right but not the obligation to buy the
underlying asset having the current price 𝑆𝑇 at an established price 𝐾 (strike price).

An european put option is the financial instrument that gives the right but not the obligation to sell the
underlying asset having the current price 𝑆𝑇 at an established price 𝐾 (strike price).

The payoff of a put option is (𝐾 − 𝑆𝑇 )+ and the payoff of a call option is (𝑆𝑇 − 𝐾)+ .

The input assumed is: 𝑆(underlying asset price), 𝐾(strike price), 𝑟 = interest rate,𝜎 =volatility, 𝑁 =
number of periods, 𝑇 = lifetime of option.

We assume a constant volatility and a flat interest rate term.

import numpy as np
import math
def option_price_bin(S0,K,sigma,T,N,r,**option):
u = np.exp(sigma*np.sqrt(T/N))
d = 1/u
p = (np.exp(r*T/N)-d)/(u-d)
def payoff_call(S,K):
return (S-K)*(S>K)
def payoff_put(S,K):
return (K-S)*(S<K)
def comb(n,k):
return math.factorial(n)/(math.factorial(k)*math.factorial(n-k))
l1 = [comb(N,i) for i in range(0,N+1)]
l2 = [(p/(1-p))**i for i in range(0,N+1)]
def price_call():
payoff = [payoff_call(S0*d**N*(u/d)**i,K) for i in range(N+1)]
return (1-p)**N*np.exp(-r*T)*np.sum(np.array(payoff)*np.array(l1)*np.array(l2))
def price_put():
payoff = [payoff_put(S0*d**N*(u/d)**i,K) for i in range(N+1)]
return (1-p)**N*np.exp(-r*T)*np.sum(np.array(payoff)*np.array(l1)*np.array(l2))
if option.get("option")=="call":
return price_call()
elif option.get("option")=="put":
return price_put()

def test_price_bin():
S0,K,sigma,T,N,r=100,100,0.3,1,12,0.03
c0 = option_price_bin(S0,K,sigma,T,N,r,option="call")
p0 = option_price_bin(S0,K,sigma,T,N,r,option="put")
print(p0+S0-c0-K*np.exp(-r*T)) #test the put-call parity
print("call price = ",c0)
print("put price = ",p0)
test_price_bin()

2. Plain vanilla european options. Black Scholes pricing. Implied volatility.


Formulas and methodologies:

For a plain vanilla call option on an underlying whose price is 𝑆𝑡 , strike price 𝐾, interest rate 𝑟, (implied)
volatility 𝜎,time to maturity 𝑇 − 𝑡, the Black Scholes price of an option is:
𝑆 𝜎2
𝑐𝑡 = 𝑆𝑡 𝑁(𝑑1 ) − 𝐾𝑒 −𝑟(𝑇−𝑡) 𝑁(𝑑2 ), 𝑑1 = (log ( 𝐾𝑡 ) + (𝑟 + 2
)(𝑇 − 𝑡))/(𝜎√𝑇 − 𝑡) and 𝑑2 = 𝑑1 −
𝜎√𝑇 − 𝑡.

The put price is (from the put-call parity formula), 𝑝𝑡 = 𝐾𝑒 −𝑟(𝑇−𝑡) 𝑁(−𝑑2 ) − 𝑆𝑡 𝑁(−𝑑1 ).

Remark: I have not taken into consideration the dividend rate.

In case of a continuous dividend rate 𝑞 the price of call and put are respectively:

𝑐𝑡 = 𝑆𝑡 𝑒 −𝑞(𝑇−𝑡) 𝑁(𝑑1 ) − 𝐾𝑒 −𝑟(𝑇−𝑡) 𝑁(𝑑2 ), 𝑝𝑡 = 𝐾𝑒 −𝑟(𝑇−𝑡) 𝑁(−𝑑2 ) − 𝑆𝑡 𝑒 −𝑞(𝑇−𝑡) 𝑁(−𝑑1 ) where


𝑆 𝜎2
𝑑1 = (log ( 𝐾𝑡 ) + (𝑟 − 𝑞 + 2
)(𝑇 − 𝑡))/(𝜎√𝑇 − 𝑡), 𝑑2 = 𝑑1 − 𝜎√𝑇 − 𝑡.

Python code
def option_price_BS(S0,T,r,sigma,K,q=0,**opt):
import numpy as np
import scipy.stats as scp
d1= (np.log(S0/K)+(r-q+sigma**2/2)*T)/(sigma*np.sqrt(T))
d2 = d1-sigma*np.sqrt(T)
def call_price(S0,T,r,sigma,K,q=0):
return S0*np.exp(-q*T)*scp.norm.cdf(d1)-K*np.exp(-r*T)*scp.norm.cdf(d2)
def put_price(S0,T,r,sigma,K,q=0):
return K*np.exp(-r*T)*scp.norm.cdf(-d2)-S0*np.exp(-q*T)*scp.norm.cdf(-d1)
if opt.get("option")=="call":
return call_price(S0,T,r,sigma,K,q)
elif opt.get("option")=="put":
return put_price(S0,T,r,sigma,K,q)

def test_price_opt():
S0,T,r,sigma,K,q = 42,0.5,0.1,0.2,40,0.05
import numpy as np
print("call price without dividend is",option_price_BS(S0,T,r,sigma,K,option="call"))
print("call price with dividend is",option_price_BS(S0,T,r,sigma,K,q,option="call"))
print("put price without dividend is",option_price_BS(S0,T,r,sigma,K,option ="put"))
print("put price with dividend is",option_price_BS(S0,T,r,sigma,K,q,option = "put"))
"We backtest the call price through put call-parity"
print(S0*np.exp(-q*T)+option_price_BS(S0,T,r,sigma,K,q,option="put")-\
option_price_BS(S0,T,r,sigma,K,q,option="call")-K*np.exp(-r*T))
test_price_opt()

Results:

call price without dividend is 4.759422392871532


call price with dividend is 3.9797550886051773
put price without dividend is 0.8085993729000922
put price with dividend is 1.0659157634437726
0.0

Implied volatility
In what follows we discuss about implied volatility for calls and put.

This is a tricky subject due to several reasons:

• There is no exact formula for implied vol, not even in case of plain vanilla options, so iterative
methods must be used. These methods are sensitive to initial guess values.

• Normally traders and quants discuss about volatility surfaces that depend on volatility terms and
smile, which are curves of implied volatility. The techniques are not always so easy.

• The implied vol can be discontinuous if the initial guess in the iterative method (Newton, e.g.) is
badly chosen.
def implied_vol(price0,S0,T,r,K,q=0,**opt):
f = lambda sigma: option_price_BS(S0,T,r,sigma,K,q,option = "call")-price0
g = lambda sigma: option_price_BS(S0,T,r,sigma,K,q,option = "put")-price0
import scipy.optimize as scp
if opt.get("option")=="call":
return scp.newton(f,0.1)
elif opt.get("option")=="put":
return scp.newton(g,0.1)

def test_implied_vol(): Out:


p0,S0,T,r,K = 5.00,100,1,0.04,100 0.06596244491089223
print(implied_vol(p0,S0,T,r,K,option = 0.1736116836201511
"call"))
print(implied_vol(p0,S0,T,r,K,option = That means 6.6% in the first case, 17.36%
"put")) implied vol in the second case.
test_implied_vol()

Digital options. General binomial price depending only on the terminal value
Assume the Black&Scholes model for the underlying asset price.

The payoff is 𝜙(𝑆𝑇 ) = 1𝑆𝑇 >𝐾 ⋅ 𝑅.

What is the price?


𝑆 𝜎2
𝐸 𝑃 [𝑒 −𝑟𝑇 𝜙(𝑆𝑇 )] = 𝑅𝑒 −𝑟𝑇 𝑃(𝑆𝑇 > 𝐾) = 𝑅𝑒 −𝑟𝑇 𝑁(𝑑2 ) where 𝑑2 = 𝑑1 − 𝜎√𝑇 = (log ( 0 ) + (𝑟 − )⋅
𝐾 2
𝑇)/(𝜎√𝑇).

There are 3 methods of pricing:

a. Binomial pricing

b. Local Volatility model

c. Pricing through replication.

a. Binomial pricing
I will build a general function for binomial pricing for options depending only on the terminal value.

import numpy as np
import math
def binomial_price(S0,K,sigma,T,N,r,payoff):
u = np.exp(sigma*np.sqrt(T/N))
d = 1/u
p = (np.exp(r*T/N)-d)/(u-d)
def comb(n,k):
return math.factorial(n)/(math.factorial(k)*math.factorial(n-k))
l1 = [comb(N,i) for i in range(0,N+1)]
l2 = [(p/(1-p))**i for i in range(0,N+1)]
payoffs = [payoff(S0*d**N*(u/d)**k) for k in range(N+1)]
return (1-p)**N*np.exp(-r*T)*np.sum(np.array(l1)*np.array(l2)*payoffs)

The next function will be dedicated to the following option types: plain call, plain put, digital
call, digital put.
def binomial_prices(S0,K,sigma,T,N,r,R=1,**options):
def payoff_call(S):
return (S-K)*(S>K)
def payoff_put(S):
return (K-S)*(S<K)
def payoff_digital_call(S):
return R*(S>K)
def payoff_digital_put(S):
return R*(S<K)
if options.get("option")=="call":
return binomial_price(S0,K,sigma,T,N,r,payoff_call)
elif options.get("option")=="put":
return binomial_price(S0,K,sigma,T,N,r,payoff_put)
elif options.get("option")=="digital call":
return binomial_price(S0,K,sigma,T,N,r,payoff_digital_call)
elif options.get("option")=="digital put":
return binomial_price(S0,K,sigma,T,N,r,payoff_digital_put)

b. Local volatility price


Python code:

def bin_price_BS(S0,K,sigma,T,N,r,R=1,**options):
d = (np.log(S0/K)+(r-sigma**2/2)*T)/(sigma*np.sqrt(T))
if options.get("option")=="digital call":
return np.exp(-r*T)*scp.norm.cdf(d)*R
elif options.get("option")=="digital put":
return np.exp(-r*T)*scp.norm.cdf(-d)*R
Test:

def test_bin2(): 0.4369230015033586


S0,K,sigma,T,N,r = 100,100,0.4,1,4,0.03 0.4369545286412395
print(binomial_prices(S0,K,sigma,T,365,r,option="digital call"))
print(bin_price_BS(S0,K,sigma,T,N,r,option="digital call"))
test_bin2()

c. Pricing through replication


We can replicate a digital call option approximately by a spread call (long call with strike price K and
short call with strike price K+h) when ℎ ≈ 0 or ℎ ≪ 𝐾.

def digital_price_replication(S0,K,sigma,T,N,r,R=1,h=0.1,**options):
if options.get("option")=="digital call":
return binomial_prices(S0,K,sigma,T,N,r,R,option="call")-binomial_prices(\
S0,K+h,sigma,T,N,r,R,option="call")
elif options.get("option")=="digital put":
return binomial_prices(S0,K+h,sigma,T,N,r,R,option="put")-binomial_prices(\
K,sigma,T,N,r,T,option="put")

We will see more on spreads on the next section

3. Spreads.
A spread trading strategy involves taking a position in two or more options of the same type (i.e. two /
more calls or two/more puts).

We will discuss:

• Bull spreads (buying a call option with strike 𝐾1 and selling a call option with a strike 𝐾2 > 𝐾1 ) or
similar reversed strategy for puts.

• Bear spreads (reversed bull call spread): selling a call with strike 𝐾1 and buying a call with strike
𝐾2 < 𝐾1 or by using puts.

• Box spread

• Calendar spread

• Diagonal spread

Solved exercises:

1. a. Define a class option: with representation, payoff, and constructor.


The data included should be the strike price and the underlying price.
b. We will define a sequence of strategies: which is a sequence of positions, L/S (long/short),
the numbers of stocks, etc. Find the payoff of the strategy.
c. Draw the graph of the payoff of the strategy. For strategy, you should define either
an option, or a function or as tuples.
Solution:
class option:
def __init__(self,K,ch):
self.strike = K
if ch in 'LlSs':
self.pos = ch
else:
raise Exception('invalid position of a plain vanilla option')
def __repr__(self):
print("Option {} with strike {}".format(self.__class__.__name__,self.strike))
def position(self,ch):
'L stands for long and S for short -- position'
if ch in 'Ll':
return 1
elif ch in 'Ss':
return -1
else:
raise Exception('invalid character')

There is a neater way to express the position function


def position(ch):
return int(ch in 'Ll') - int(ch in 'Ss')

Now we go to the call and put classes definition:


class call(option):
def __init__(self,K,ch):
self.strike = K
if ch in 'LlSs':
self.pos = ch
else:
raise Exception('invalid position of a plain vanilla {}'
.format(self.__class__.__name))
def __repr__(self):
print("{} with strike price {}".format(self.__class__.__name__,self.strike))
def payoff(self,S):
import numpy as np
return np.max([S - self.strike,0])*self.position(self.pos)

class put(option):
def __init__(self,K,ch):
self.strike = K
if ch in 'LlSs':
self.pos = ch
else:
raise Exception('invalid position of a plain vanilla {}'
.format(self.__class__.__name))
def __repr__(self):
print("{} with strike price {}".format(self.__class__.__name__,self.strike))
def payoff(self,S):
import numpy as np
return np.max([self.strike - S,0])*self.position(self.pos)

Now, we define the strategy class:

class option_strategy(call,put):
def __init__(self,strikes,positions,types,numbers):
'strikes will be a list of numbers and positions a string of characters'
'numbers represent how many instruments we use'
"e.g. strikes = [100,110,120],positions = 'SLL', types = 'CCC',numbers"
" = [1,2,1] means i have a short call with 100 strike, two long with 110"
" and 1 one long with strike price 120, all three being calls by 'CCC'"
self.strikes = strikes
self.positions = positions
self.types = types
self.numbers = numbers
"next we verify if the string of types matches in length with the "
"positions and numbers"
def match_string(string):
for x in string:
if x not in 'CcPp':
return 0
return 1
if len(strikes)==len(positions)==len(types)==len(numbers) and match_string(types)==1:
pass
else:
raise Exception('Either the number of strikes and positions is not the same or \
there is an error in the types introduced')
def payoff(self,S):
sums = 0
for i in range(len(self.strikes)):
if self.types[i] in {'C','c'}:
c1 = call(self.strikes[i],self.positions[i])
sums = sums + c1.payoff(S)*self.numbers[i]
elif self.types[i] in {'P','p'}:
c1 = put(self.strikes[i],self.positions[i])
sums = sums+c1.payoff(S)*self.numbers[i]
return sums

Example: Bear put spread: If 𝐾1 < 𝐾2 the put spread consists of buying the put with strike price
𝐾2 and short the put with strike price 𝐾1 .
def strategies(num):
def bear_put_spread(K1,K2,S):
'bear put spread'
p = option_strategy([K1,K2],'SL','PP',[1,1])
return p.payoff(S)
def bull_call_spread(K1,K2,S):
p=option_strategy([K1,K2],'LS','CC',[1,1])
return p.payoff(S)
def butterfly_call_spread(K1,K2,K3,S):
p=option_strategy([K1,K2,K3],'LSL','CCC',[1,2,1])
return p.payoff(S)
options = {1:bear_put_spread,2:bull_call_spread,3:butterfly_call_spread}
if num in options:
return options[num]
else:
raise Exception('choose another option')

The tests and the result of the third one are in the table below.
Tests:
def test1():
D = option_strategy([100,110,115],'SLS','CCC',[1,2,1])
print(D.payoff(120))

def test2():
s1 = strategies(1)

print(s1(100,120,[110]),s1(100,120,[130]),s1(100,120,[95]))

def test3():
import matplotlib.pyplot as plt
import numpy as np Test 3 result
x = np.linspace(0,200,101)
s1=strategies(1)
plt.plot([s1(100,120,y) for y in x],label = 'bear put spread
payoff')
S1 = option_strategy([100],'L','C',[1])
plt.plot([S1.payoff(i) for i in x],label='long call payoff')
S2 = option_strategy([100,110,120],'LSL','CCC',[1,2,1])
plt.plot([S2.payoff(i) for i in x],label = 'butterfly call payoff')
plt.legend()
test3()

Solved exercise 2:

Redefine the classes call and put started above by adding to them the following features.

• The graph of the payoff.

• The Black Scholes price.

• The binomial price.

• The P&L and the graph.

First I will import the needed files and functions.

from option_strategies import (call,put,option_strategy)


from black_scholes_pricing import option_price_BS
from binomial_pricing2 import binomial_prices
Remark: call, put, option_strategy are classes while option_price_BS and binomial_prices are
functions. See exercise 1.

Then we redefine the classes by making an inheritance from the base class which already has payoff
function.
class enhanced_option(call,put):
def __init__(self,K,ch):
super(enhanced_option,self).__init__(K,ch)

def graph_payoff(self,ch,**options):
import matplotlib.pyplot as plt
f1 = lambda S:call.payoff(self,S)
f2 = lambda S:put.payoff(self,S)
x,y=int((self.strike)/2),int(3*(self.strike)/2)
if options.get("option")=="call":
plt.plot(range(x,y),[f1(s) for s in range(x,y)])
plt.title('Payoff graph')
plt.grid(True)
plt.xlabel('Underlying')
plt.ylabel('Payoff')
plt.show()
elif options.get("option")=="put":
plt.plot(range(x,y),[f2(s) for s in range(x,y)])
plt.title('Payoff graph')
plt.grid(True)
plt.xlabel('Underlying')
plt.ylabel('Payoff')
plt.show()

def price_BS(self,S0,sigma,r,T,**options):
K = self.strike
if options.get("option")=="call":
return option_price_BS(S0,T,r,sigma,K,option = "call")
elif options.get("option")=="put":
return option_price_BS(S0,T,r,sigma,K,option = "put")
def price_bin(self,S0,sigma,T,N,r,**options):
K = self.strike
if options.get("option")=="call":
return binomial_prices(S0,K,sigma,T,N,r,option="call")
elif options.get("option")=="put":
return binomial_prices(S0,K,sigma,T,N,r,option="put")
def PL_graph(self,S0,sigma,r,T,**options):
h1 = lambda S:self.payoff(S)-
self.price_BS(S0,sigma,r,T,option="call")
h2 = lambda S:self.payoff(S)-
self.price_BS(S0,sigma,r,T,option="put")
import matplotlib.pyplot as plt
x,y=int((self.strike)/2),int(3*(self.strike)/2)
if options.get("option")=="call":
plt.plot(range(x,y),[h1(s) for s in range(x,y)])
plt.title('P&L graph for call option')
plt.grid(True)
plt.xlabel('Underlying')
plt.ylabel('Profit')
plt.show()
if options.get("option")=="put":
plt.plot(range(x,y),[h2(s) for s in range(x,y)])
plt.title('P&L graph for put option')
plt.grid(True)
plt.xlabel('Underlying')
plt.ylabel('Profit')
plt.show()

Remarks:

• A method (in particular a constructor) from a child class can inherit the behaviour from a
method in its parent class by using “super()” method. If a class inherits from multiple classes and
super(Child class) calls a function appearing in both parent classes, the priority is measured from
left ro right.

• We use a range for the payoff depending on the spot value of the underlying by using ratios
𝐾 3𝐾
(𝑥 = , 𝑦 = ) because it has a better significance (moneyness) than by simply adding /
2 2
substracting an arbitrary number (𝑒. 𝑔. 𝑥 = 𝐾 − 100, 𝑦 = 𝐾 + 100).

• In the payoff graph method I use “call.payoff” and “put.payoff” according to the type of the
option.

• For prices I use instead of class methods, plain functions.

The test code and result:

def test_ec():
"enhanced option"
c1 = enhanced_option(100,'L')
c2 = enhanced_option(100,'S')
c1.graph_payoff('l',option = "call")
c2.graph_payoff('s',option = "call")
print("the price of a call option with underlying {0}
volatility {1} is:"\

.format(100,0.4),c1.price_BS(100,0.4,0.04,1,option
= "call"))
c1.PL_graph(100,0.4,0.05,1,option = "call")
test_ec()

Remarks:
• C1 = enhanced_option(100,’L’) means the option
has strike price 100, and the position is long. C2 =
enhanced_option(100,’S’) means the option has
strike price 100, and the position is short. We don’t
know from this line what type of option we are
talking about. That is why enhanced option is a
generic class with methods (price_BS, price_bin,
etc.) applying for both type of classes.
• We now what type of option we want to work with
only from “c1.graph_payoff(‘l’,option = “call”)”

Solved exercise 3

Redefine the class strategy above by adding to it:

• The graph of the payoff

• The Black Scholes value of the strategy

• The binomial value of the strategy

• The P&L of the strategy along with its graph.

Python code:

class enhanced_strategy(option_strategy):
def __init__(self,strikes,positions,types,numbers):
super(enhanced_strategy,self).__init__(strikes,positions,types,numbers)
def payoff_graph(self):
import matplotlib.pyplot as plt
h = lambda S:option_strategy.payoff(self,S)
x,y=int((self.strikes[0])/2),int(3*(self.strikes[0])/2)
plt.plot(range(x,y),[h(s) for s in range(x,y)])
plt.title('Payoff graph')
plt.grid(True)
plt.xlabel('Underlying')
plt.ylabel('Payoff')
plt.show()
def price_bin(self,S0,sigma,T,N,r):
sums = 0
for i in range(len(self.strikes)):
if self.types[i] in 'Cc':
c = call(self.strikes[i],self.positions[i])
sums = sums+binomial_prices(S0,c.strike,sigma,T,N,r,option="call"\
)*c.position(self.positions[i])*self.numbers[i]
elif self.types[i] in 'Pp':
p = put(self.strikes[i],self.positions[i])
sums = sums+binomial_prices(S0,p.strike,sigma,T,N,r,option = "put"\
)*p.position(self.positions[i])*self.numbers[i]
return sums
def price_BS(self,S0,sigma,T,r):
sums = 0
for i in range(len(self.strikes)):
if self.types[i] in 'Cc':
c = call(self.strikes[i],self.positions[i])
sums = sums+option_price_BS(S0,T,r,sigma,c.strike,option="call"\
)*c.position(self.positions[i])*self.numbers[i]
elif self.types[i] in 'Pp':
p = put(self.strikes[i],self.positions[i])
sums = sums+option_price_BS(S0,T,r,sigma,p.strike,option = "put"\
)*p.position(self.positions[i])*self.numbers[i]
return sums

4. Spreads. Let’s make a scan through all types of spreads.

Design the payoff for:

a. Bear call spread. B. Bear put spread; c. Bull call spread; d. Bear call spread.

def spreads():
s1 = enhanced_strategy([100,120],'LS','CC',[1,1])#bull call spread
s2 = enhanced_strategy([100,120],'LS','PP',[1,1])#bull put spread
s3 = enhanced_strategy([100,120],'SL','PP',[1,1])#bear put spread
s4 = enhanced_strategy([100,120],'SL','CC',[1,1])#bear call spread
s1.payoff_graph()
s2.payoff_graph()
s3.payoff_graph()
s4.payoff_graph()
spreads()

Results: (I assume 𝐾1 = 100, 𝐾2 = 120).

Bull call spread Bull put spread


Bear put spread Bear call spread

4. Covered strategies
We note the following strategies:

1. Covered call – Long stock + short call

2. Reversed covered call – Short stock + long call

3. Covered put – Long put + long stock

4. Reversed covered put – Short put + short stock

In what follows we study the P&L of the afore-mentioned strategies using BS formulas.

Python code:

from black_scholes_pricing import option_price_BS


def PL(S0,T,r,sigma,K,q=0,pos = 1,**strategy):
"we construct the net profit of different protected strategies"
"the strategies can be: ------ covered call"
" ------------ covered put"
"Net profit = payoff at maturity - price at time 0 of call and put"
"pos can be 1 or -1 : long/short strategy"
p0 = option_price_BS(S0,T,r,sigma,K,q,option="put")
c0 = option_price_BS(S0,T,r,sigma,K,q,option="call")
if strategy.get("strategy")=="covered call":
return lambda S:(S*(S<K)+K*(S>=K) + c0 - S0)*pos
elif strategy.get("strategy")=="covered put":
return lambda S:(K*(S<=K)+S*(S>K)-p0-S0)*pos
Test:

def test_strategy():
S0,T,r,sigma,K = 100,1,0.04,0.3,100
h1 = PL(S0,T,r,sigma,K,strategy = "covered call")
h2 = PL(S0,T,r,sigma,K,q=0,pos=-1,strategy="covered
call")
h3 = PL(S0,T,r,sigma,K,strategy = "covered put")
h4 = PL(S0,T,r,sigma,K,q=0,pos=-1,strategy = "covered
put")
import matplotlib.pyplot as plt
plt.figure(1)
plt.subplot(121)
plt.plot(range(50,151),[h1(x) for x in range(50,151)])
plt.grid(True)
plt.title('Covered call')
plt.ylabel('Profit')
plt.xlabel('Underlying price')
plt.subplot(122)
plt.plot(range(50,151),[h2(x) for x in range(50,151)])
plt.grid(True)
plt.title('Reversed covered call')
plt.ylabel('Profit')
plt.xlabel('Underlying price')
plt.show()
plt.figure(2)
plt.subplot(121)
plt.plot(range(50,151),[h3(x) for x in range(50,151)])
plt.grid(True)
plt.title('Covered put')
plt.ylabel('Profit')
plt.xlabel('Underlying price')
plt.subplot(122)
plt.plot(range(50,151),[h4(x) for x in range(50,151)])
plt.grid(True)
plt.title('Reversed covered put')
plt.ylabel('Profit')
plt.xlabel('Underlying price')
plt.show()
test_strategy()
5. Combinations
Definition: A combination is a trading strategy that involves taking a position in both calls and puts on
the same stock. We will consider straddles, strips, straps and strangles.

STRADDLE

A popular combination is straddle which involves buying a European call and put with the same strike
price and expiration date.

STRIP

= Long position in one European call and two european puts with the same strike and expiration date.

STRAP

= Long position in two european calls and one european put with the same strike and expiration date.

STRANGLE

= Also called bottom vertical combination an investor buys a European put and European call with the
same expiration date and different strike prices. The call strike price, 𝐾2 is higher than the put strike
price, 𝐾1 .

The implementation of the payoff, price and P&L of these strategies can start from the already defined
strategy class from the previous section. (See solved exercise 3 from Section 3)

Results:

from option_strategies2 import enhanced_strategy


def combinations():
s1 = enhanced_strategy([100]*2,'LL','CP',[1,1])#straddle
s2 = enhanced_strategy([100]*2,'LL','CP',[1,2])#strip
s3 = enhanced_strategy([100]*2,'LL','CP',[2,1])#strap
s4 = enhanced_strategy([100,120],'LL','PC',[1,1])#strangle
s1.payoff_graph()
s2.payoff_graph()
s3.payoff_graph()
s4.payoff_graph()
combinations()

Results:
6. American options
Definition:

An american option is a financial instrument that allows the investor to exercise it at any time.

The pricing of an american option is more complex than the pricing of an european option.

That is because at every moment in time, the holder of the option can choose either to exercise the
option or to sell it.

The formal formula for the price of an american option is:


𝜏
Find the optimal stopping time 𝜏 and sup 𝐸[exp(− ∫0 𝑟𝑢 𝑑𝑢 ⋅ ℎ(𝑋(𝜏)))] where 𝑋(𝑡) = 𝑋𝑡 represents the
𝜏
Markov process of the underlying asset price.

Remark:
In case there are no dividends forecasted, the price of an american option is the same as the price of a
european option. That is equivalent to the fact that the early exercise is not optimal.

Let’s start first with an example.

Suppose we have an equity stock whose underlying price is 𝑆0 = 100, 𝐾 = 100, 𝑟 = 5%, implied vol
𝜎 = 40%, lifetime of the option is 1 year.

a. Find the price of an european call option if there is no dividend (using binomial model with 2 periods).

b. Find the price of an european call option if there is a dividend of 4$ in 3 months. (The same)

c. Find the price of the american call option if there is a dividend of 4$ in 3 months.

Solution:

a. 𝑐0 = exp(−𝑟 ⋅ 𝑇) [𝑝2 (𝑆0 𝑢2 − 𝐾)+ + 2𝑝(1 − 𝑝)(𝑆0 − 𝐾)+ + (1 − 𝑝)2 (𝑆0 𝑑2 − 𝐾)+ ]
1 exp(𝑟Δ𝑡)−𝑑
where 𝑢 = exp(𝜎√Δ𝑡) , 𝑑 = , 𝑝 =
𝑢 𝑢−𝑑

b. This time the real stock price applied in the binomial model is 𝑆0 − 𝐷 ⋅ exp(−𝑟 ⋅ 𝑇) = 100 − 4 ⋅
1 𝑆0
exp(−0.05 ⋅ 4). The volatility applied is 𝜎 ⋅ 1 .
𝑆0 −𝐷⋅exp(−𝑟⋅ )
4

The rest of the pricing goes along the same route.

Python code:

def binomial_price2(S0,K,sigma,T,N,r,payoff,**options):
if len(options) == 0:
return binomial_price(S0,K,sigma,T,N,r,payoff)
else:
divs = options.get("dividends")
times = options.get("times")
rates = [r]*len(times)
D = bond_price1(rates,times,divs)
return binomial_price(S0-D,K,sigma*S0/(S0-D),T,N,r,payoff)

c. For the american option we go backwards starting from maturity.

At each point in time we have to compute the european stock price with the intrinsic value, e.g. the
payoff of the call.
𝑆0
Here, as in b), we work with 𝑆0 = 𝑆0 − 𝐷 ⋅ exp(−𝑟𝑡) and 𝜎 = 𝜎 ⋅ 𝑆 −𝑟𝑡 .
0 −𝐷𝑒

1 +
In our case, 𝑐𝑢 = (price after one up-going of the stock) = 𝑒 −𝑟⋅2 ⋅ 𝑝 ⋅ (𝑆0 𝑢2 − 𝐾) and 𝑐𝑑 = 0.

(Here 𝑐𝑢 = price of the european option). If 𝐶𝑢 = price of the american option, 𝐶𝑢 = max(max(𝑆0 𝑢 −
𝐾, 0), 𝑐𝑢 ) and 𝐶𝑑 = max(0, 𝑐𝑑 ) = 0. Also, the intrinsic value at time 0 is 0.
1
So the price of the american option at time 0 will be 𝐶0 = exp (−𝑟 ⋅ ) ⋅ (𝑝𝐶𝑢 + (1 − 𝑝)𝐶𝑑 ) =
2
𝑟
exp (− 2) ⋅ 𝑝 ⋅ 𝐶𝑢 .

Python code:

def price_amer_call_2per(S0,K,sigma,T,r,payoff,**options):
if len(options)==0:
return binomial_price(S0,K,sigma,T,2,r,payoff)
else:
divs = options.get("dividends")
times = options.get("times")
rates = [r]*len(times)
D = bond_price1(rates,times,divs)
sigma = sigma*S0/(S0-D)
u = np.exp(sigma*np.sqrt(T/2))
d = 1/u
p = (np.exp(r*T/2)-d)/(u-d)
S0 = S0 - D
c_u = np.exp(-r/2)*p*np.max([S0*u**2-K,0])
C_u = np.max([c_u,np.max([S0*u-K,0])])
return np.exp(-r/2)*p*C_u

7. Currency options and stock indices options


Currency options
Suppose 𝑆0 is the spot exchange rate = the value of one unit of the foreign exchange currency in US
dollars. Suppose 𝑟𝑓 is the risk-free interest rate in foreign exchange currency and 𝑟 = interest rate in the
local currency.

Valuation of european currency options under Black Scholes pricing

The price of the european call and put are given by the following formulas:

𝑐 = 𝑆0 𝑒 −𝑟𝑓𝑇 𝑁(𝑑1 ) − 𝐾𝑒 −𝑟𝑇 𝑁(𝑑2 ),𝑝 = 𝐾𝑒 −𝑟𝑇 𝑁(−𝑑2 ) − 𝑆0 𝑒 −𝑟𝑓𝑇 𝑁(−𝑑1 )(1)
𝑆 𝜎2
log( 0 )+(𝑟−𝑟𝑓 + )𝑇
𝐾 2
Where 𝑑1 = , 𝑑2 = 𝑑1 − 𝜎√𝑇.(2)
𝜎 √𝑇

Exercises

1. Prove and check that 𝑐 ≥ max(𝑆0 exp(−𝑟𝑓 𝑇) − 𝐾𝑒𝑥𝑝(−𝑟𝑇), 0) as well as 𝑝 ≥ max(𝐾𝑒𝑥𝑝(−𝑟𝑇) −


𝑆0 𝑒𝑥𝑝(−𝑟𝑓 𝑇) , 0).

2. Compute the price of an european call option, 4month, on a currency option on British bound.
Suppose that the current exchange rate is 1.6, the exercise price is 1.6 the risk free interest rate in the
US is 8% per annum, the risk-free interest rate in Britain is 11% and volatility of 20%.
3. Consider a stock index currently standing at 250. The dividend yield on the index is 4% per annum,
and the risk free rate is 6% p.a. A 3M European call option on the index with a strike price of 245 is
currently worth 10$. What is the value of a 3M put option on the index with a strike price of 245?

Solutions:

1. Consider two portfolios:

Portfolio A: one European call option plus an amount of cash equal to 𝐾𝑒 −𝑟𝑇 .

Portfolio B: 𝑒 −𝑟𝑓𝑇 units of foreign currency being reinvested in the money market.

At maturity: 𝑉𝑇 (𝐴) = 𝑐𝑇 + 𝐾 = max(𝑆𝑇 − 𝐾, 0) + 𝐾 = max(𝑆𝑇 , 𝐾).

As well: 𝑉𝑇 (𝐵) = 𝑆𝑇 𝑒 −𝑟𝐹 𝑇 𝑒 𝑟𝐹 𝑇 = 𝑆𝑇 ≤ max(𝑆𝑇 , 𝐾) so 𝑉𝑇 (𝐵) ≤ 𝑉𝑇 (𝐴).

2. 𝑐0 = 1.6 exp(−0.11 ⋅ 0.333) 𝑁(𝑑1 ) − 1.6 exp(−0.08 ⋅ 0.333) 𝑁(𝑑2 )


𝑆 𝜎2 𝜎2 𝜎2 𝜎2
log( 0 )+(𝑟−𝑟𝑓 + )⋅𝑇 (𝑟−𝑟𝑓 + )𝑇 (𝑟−𝑟𝑓 + )√𝑇 (𝑟−𝑟𝑓 − )√𝑇
𝐾 2 2 2 2
But 𝑑1 = 𝜎 √𝑇
= 𝜎√𝑇
= 𝜎
, 𝑑2 = 𝜎

3. We apply the put-call parity of the formula for the european options on index.

𝑆0 = 250, 𝑞 = 0.04, 𝑟 = 0.06, 𝑇 = 0.25, 𝐾 = 245, 𝑐 = 10.

From the relation 𝑐 + 𝐾𝑒 −𝑟𝑇 = 𝑝 + 𝑆0 𝑒 −𝑞𝑇 or 𝑝 = 𝑐 + 𝐾𝑒 −𝑟𝑇 − 𝑆0 𝑒 −𝑞𝑇 = 10 + 245𝑒 −0.25⋅0.06 −


250𝑒 −0.25⋅0.04 = 3.84.

Python code:

import numpy as np
import scipy.stats as scp
def currency_option(S0,K,r,rf,sigma,T):
d1 = (np.log(S0/K)+(r-rf+sigma**2/2)*T)/(sigma*np.sqrt(T))
d2 = (np.log(S0/K)+(r-rf-sigma**2/2)*T)/(sigma*np.sqrt(T))
return S0*np.exp(-rf*T)*scp.norm.cdf(d1)-K*np.exp(-r*T)*scp.norm.cdf(d2)

def test3():
S0,K,r,rf,T,sigma = 1.6,1.6,0.08,0.11,0.333,0.2
print(currency_option(S0,K,r,rf,sigma,T))
test3()

Exercises:

1. A currency is currently worth 0.80 and has a volatility of 12%. The domestic and foreign interest risk-
free interest rates are 6% and 8%. Use a two-step binomial tree to value (a) a european four month call
option with a strike price of 0.79. (b) An american 4 month call option with the same strike price.

Solution:

I will put directly the Python code to price the american call.
First I am building the tree for the underlying asset prices and payoffs:

def tree(S0,u,d,n):
"binomial tree"
if n==0:
return S0
else:
bin_tree = [[S0]]
for i in range(1,n+1):
step = [S0*u**i*d**(2*k) for k in range(i+1)]
bin_tree.append(step)
return bin_tree

def payoff_tree(S0,u,d,n,h):
"h is a lambda function depending on a parameter: e.g. can be the payoff"
"of a call"
tr = tree(S0,u,d,n)
return [[h(tr[i][j]) for j in range(i+1)] for i in range(n+1) ]

On the second phase I am going recursively back, starting from the final payoff.

def price_call_tree(S0,K,sigma,r,q,T,N):
u,d = np.exp(sigma*np.sqrt(T/N)),np.exp(-sigma*np.sqrt(T/N))
dt = T/N
h = lambda S:(S-K)*(S>K)
p = (np.exp((r-q)*dt)-d)/(u-d)
payoff_tr = payoff_tree(S0,u,d,N,h)
price_tree = []
price_tree.append(payoff_tr[-1])
print(price_tree)
for i in range(1,N+1):
step = []
for j in range(0,i+1):
step.append(np.max([payoff_tr[N-i][j],np.exp(-(r-q)*dt\
)*(price_tree[i-1][j]*p+(1-p)*price_tree[i-1][j+1])]))
print(step)
price_tree.append(step)
return price_tree

def test4():
S0,K,sigma,r,q,T,N = 0.80,0.79,0.12,0.06,0.08,4/12,2
print(price_call_tree(S0,K,sigma,r,q,T,N))
test4()
The price of the european call is 0.0235 while the price of the american call is 0.025 ($).

2. Prove that the formula (2) for a put option price to sell one unit of currency A for currency B at strike
price K gives the same values as formula (1) for a call option to buy K units of currency B for currency A
1
at a strike price of 𝐾.
Solution:

A put option to sell one unit of currency A for K units of currency B is worth 𝐾𝑒 −𝑟𝐵 𝑇 𝑁(−𝑑2 ) −
𝑆 𝜎2 𝑆 𝜎2
log( 0 )+(𝑟𝐴 −𝑟𝐵 + )𝑇 log( 0 )+(𝑟𝐴 −𝑟𝐵 − )𝑇
−𝑟𝐴 𝑇 𝐾 2 𝐾 2
𝑆0 𝑒 𝑁(−𝑑1 ) where 𝑑1 = 𝜎√𝑇
, 𝑑2 = 𝜎√𝑇
and 𝑟𝐴 , 𝑟𝐵 are the risk-free
rates in currencies A and B, respectively.
1 1
The value of the option is measured in units of currency B. Defining 𝑆0∗ = , 𝐾∗ = then
𝑆0 𝐾

𝑆 ∗ 2
𝜎
− log( 0∗ )−(𝑟𝐴 −𝑟𝐵 − )𝑇
𝐾 2
𝑑1 = 𝜎 √𝑇
, 𝑑2 = 𝑑1 − 𝜎√𝑇.

The put price is therefore:


𝑆∗ 2
𝜎
log( 0∗ )+(𝑟𝐵 −𝑟𝐴 − )𝑇
𝐾 2
𝑆0 𝐾[𝑆0∗ 𝑒 −𝑟𝐵 𝑇 𝑁(𝑑1∗ ) − 𝐾 𝑒 ∗ −𝑟𝐴 𝑇
𝑁(𝑑1∗ )] where 𝑑1∗ = 𝜎 √𝑇
, 𝑑2∗ = 𝑑1∗ + 𝜎√𝑇.
1
This shows that put option is equivalent to 𝐾𝑆0 call options to buy 1 unit of currency A for 𝐾 units of
currency B. In this case the value of the option is measured in units of currency A. To obtain the call
option value in units of currency B we must divide by 𝑆0 .

Index options and hedging


Exercises

1. A portfolio is currently worth $10 million and has a beta of 1.0. An index is currently standing at 800.
Explain how a put option on the index with a strike price of 700 can be used to provide portfolio
insurance.

Solution:
700
When the index goes down to 700, the value of the portfolio can be expected to be 10 × =
800
8.75$ million. (This assumes that the dividend yield on the portfolio equals the dividend yield on the
10,000,000
index). Buying put options on 800
= 12,500 times the index with a strike of 700 therefore provides
protection against a drop in the value of the portfolio below 8.75$ million. If each contract is on 100
times the index a total of 125 contracts would be required.

2. An index is currently standing at 696 and has a volatility of 30% p.a. The risk-free rate of interest is 7%
p.a. and the index provides a dividend yield of 4%, per annuam. Calculate the value of a 3M European
put with an exercise price of 700.

Solution:

In this case 𝑆0 = 696, 𝐾 = 700, 𝑟 = 0.07, 𝜎 = 0.3, 𝑇 = 0.25, 𝑞 = 0.04. The option can be valued using
the following equation: 𝑝 = 𝐾𝑒 −𝑟𝑇 𝑁(−𝑑2 ) − 𝑆0 𝑒 −𝑞𝑇 𝑁(−𝑑1 ).
696 0.09
log( )+(0.07−0.04+ )×0.25
700 2
𝑑1 = 0.3√0.25
= 0.0868, 𝑑2 = 𝑑1 − 0.3√0.25 = −0.0632.
And 𝑁(−𝑑1 ) = 0.4654, 𝑁(−𝑑2 ) = 0.5252. The value of the put, 𝑝, is given by:

𝑝 = 700𝑒 −0.07×0.25 × 0.5252 − 696𝑒 −0.04×0.25 × 0.4654 = 40.6 = 40.6.

3. An index currently stands at 1,500. European call and put options with a strike price of 1,4 and time to
maturity of 6 months have market prices of 154.00 and 34.25. The 6M risk free rate is 5%. What is the
implied dividend yield?

Solution:

We use the put-call parity relation to deduce the dividend yield:

154 + 1400𝑒 −0.05×0.5 = 34.25 + 1500𝑒 −0.5𝑞 therefore 𝑞 = 1.99%.

Currencies, Ito calculus and implied volatility


Problem:

Suppose we have 3 currencies A, B, C where we know their domestic free interest rates and spot
exchange rates, e.g. 1 unit of currency A is worth 𝑆𝐴 units of currency of B, and 1 unit of currency B is
worth 𝑆𝐵 units of currency of C, and the price of a call that gives you the right to buy currency A in
exchange of B is 𝑐0,𝐴 and the price of a call that gives you the right to buy currency B in exchange of C is
𝑐0,𝐵 . What is the price of the call that gives you the right to buy currency A in exchange for C?

Example:

Suppose the spot exchange rates are: 1$ = 0.9 𝑒𝑢𝑟𝑜𝑠 and 1 𝑒𝑢𝑟𝑜 = 1.2 𝐶𝐻𝐹, the domestic rates for
(dollar, euro, CHF) are 2%, 3%, 4% and the price of 1 year call to exchange 1 euro to 1.1 dollars (𝐾1 =
1.1$) is 𝑐1 and the price of 1 year call to exchange 1 CHF to 0.8 euro (𝐾2 = 0.8).

a) What is the price of a call option where one buys 1$ against 1.05 CHF?

b) Keeping the price of the second option constant, how it varies the third option price by varying the
first option price.

Answer:

We work as always under Geometric Brownian Motion auspices:


$
That is if we assume 𝑆𝐴 =spot rate between 𝑒𝑢𝑟𝑜, and 𝑆𝐵 =spot rate between euro and CHF and

𝑑𝑆 = (𝑟𝐴 − 𝑟𝐵 )𝑆𝐴 𝑑𝑡 + 𝜎𝐴 𝑑𝐵𝑡


{ 𝐴 in the risk neutral world then we seek first to know the volatility
𝑑𝑆𝐵 = (𝑟𝐵 − 𝑟𝐶 )𝑆𝐵 𝑑𝑡 + 𝜎𝐵 𝑑𝐵𝑡
between the dollar and euro.

Computations:

We apply Ito’s lemma for 𝒅𝒇(𝑿𝒕 , 𝒀𝒕 ) = 𝒅(𝑿𝒕 𝒀𝒕 ) = 𝒇𝒕 (𝒕, 𝑿𝒕 , 𝒀𝒕 )𝒅𝒕 + 𝒇𝑿 (𝒕, 𝑿𝒕 , 𝒀𝒕 )𝒅𝑿𝒕 +
𝟏
𝒇𝒀 (𝒕, 𝑿𝒕 , 𝒀𝒕 )𝒅𝒀𝒕 + 𝟐 (𝒇𝑿𝑿 𝒅𝑿𝒅𝑿 + 𝒇𝒀𝒀 𝒅𝒀𝒅𝒀) + 𝒇𝑿𝒀 𝒅𝑿𝒅𝒀 .
Therefore:

𝒅(𝑺𝑨 𝑺𝑩 ) = 𝑆𝐴 𝑑𝑆𝐵 + 𝑆𝐵 𝑑𝑆𝐴 + 𝑑𝑆𝐴 𝑑𝑆𝐵 = 𝑆𝐴 ((𝑟𝐵 − 𝑟𝐶 )𝑆𝐵 𝑑𝑡 + 𝜎𝐵 𝑆𝐵 𝑑𝐵𝑡 ) + 𝑆𝐵 ((𝑟𝐴 − 𝑟𝐵 )𝑆𝐴 𝑑𝑡 +
𝜎𝐴 𝑆𝐴 𝑑𝐵𝑡 ) + 𝜎𝐴 𝜎𝐵 𝑆𝐴 𝑆𝐵 𝑑𝑡 = [(𝑟𝐴 − 𝑟𝐵 )𝑆𝐴 𝑆𝐵 + (𝑟𝐵 − 𝑟𝐶 )𝑆𝐴 𝑆𝐵 + 𝜎𝐴 𝜎𝐵 𝑆𝐴 𝑆𝐵 ]𝑑𝑡 + (𝜎𝐴 + 𝜎𝐵 )𝑆𝐴 𝑆𝐵 𝑑𝐵𝑡 .

Hence the volatility coefficient is given by 𝜎𝐴 + 𝜎𝐵 .

Methodology of our problem:

1. We need to find the implied volatilities of the two options.

2. We apply the remark above to find the cross-vol.

3. We apply the formula (1) for the call option price for currencies.

Python code:

1. First I will define a pricer for european currency call and put.

2. Then an implied vol function

3. Then the price for a cross currency option.

Numerical example:

Suppose the spot rate between euro and US dollar is 1.1, the spot exchange rate between dollar and
CHF is 1.2. The domestic interest rates are 2%,3%,4% and the price of a call for the exchange euro/dollar
is 0.12 euros and the price of a call for the exchange dollar/CHF is 0.19 dollar. The strike prices are 0.95
and 1. What will be the price of a call on the exchange with the strike price of 1.05 ?

First we need to find the implied volatility from the EUR/USD and USD/CHF and then deduce the
EUR/CHF vol by using the rule above (under Geometric brownian motion process).

1.

import numpy as np
import scipy.stats as scp
def currency_opt(S0,K,r,rf,sigma,T,**opt):
d1 = (np.log(S0/K)+(r-rf+sigma**2/2)*T)/(sigma*np.sqrt(T))
d2 = (np.log(S0/K)+(r-rf-sigma**2/2)*T)/(sigma*np.sqrt(T))
def call_price():
return S0*np.exp(-rf*T)*scp.norm.cdf(d1)-K*np.exp(-r*T)*scp.norm.cdf(d2)
def put_price():
return K*np.exp(-r*T)*scp.norm.cdf(-d2)-S0*np.exp(-rf*T)*scp.norm.cdf(-d1)
if opt.get("option") in {"call","Call"}:
return call_price()
elif opt.get("option") in {"put","Put"}:
return put_price()
2. Implied vols for call 1 and call 2.

import scipy.optimize as opt


def implied_vol(S0,K,r,rf,price,T,**op):
h1 = lambda sigma:currency_opt(S0,K,r,rf,sigma,T,option="call")-price
h2 = lambda sigma:currency_opt(S0,K,r,rf,sigma,T,option="put")-price
if op.get("option") in {"call","Call"}:
return opt.newton(h1,0.3)
elif op.get("option") in {"put","Put"}:
return opt.newton(h2,0.3)
3.

def cross_option_price(S0,S1,r0,r1,r2,c0,c1,T,K1,K2,K3):
sig1 = implied_vol(S0,K1,r0,r1,c0,T,option="call")
sig2 = implied_vol(S1,K2,r1,r2,c1,T,option = "call")
sig = sig1+sig2
return currency_opt(S0*S1,K3,r0,r2,sig,T,option="call")
The price is 0.95 euros.

Hedging payments in foreign currency


Problem:
Suppose the date today is 1st of January 2018 and I have a 300,000 $ payment on 1st of January 2019.
The current spot rate is 1 $ = 0.9 EUR.
a. If the spot rate will become 1 $ = 1.2 EUR how many euros will I have to pay, in addition to the case
of a constant spot rate?
b. If I buy a call that will allow me to buy 300,000 $ at strike 1 euro what will be the difference?
The vol. is supposed to be 20%, the domestic rates (𝑟𝐸𝑈𝑅 = 3%, 𝑟𝑈𝑆𝐷 = 4%).
What is the payoff in that case?
a. If the spot is constant then I would pay 270,000 EUR, and if the spot becomes 1$ = 1.2 EUR then I
would pay 360,000 EUR.

b. The payoff (intrinsic value) is (1.2 − 1) ⋅ 300,000 = 0.2 ⋅ 300,000 = 60,000 𝐸𝑈𝑅.

The value of the call is 𝑐0 = 𝑆0 ⋅ 𝑒 −𝑟𝐹 𝑇 𝑁(𝑑1 ) − 𝐾𝑒 −𝑟𝑇 𝑁(𝑑2 ), or we can use the “currency opt” function
defined above. The price of a call is 0.00114 and for 300000$ we pay for 300,000 calls we pay 342.809…
EUR.

The total cost of the hedging strategy will be 300,000 + 342.809 = 300,342.809.

The payoff (the advantage) of the strategy if the spot is 1.2 EUR = 1$, (1.2 − 1) ⋅ 300,000 − 342,809 =
60,000 − 342.809 = 59,657.191 EUROS.

Greeks.
1. Delta and Gamma of plain vanilla options
Objectives:

1. Variation of delta with stock price for (a) call option and (b) put option on a non-dividend paying stock
2. Variation of delta with respect to the time to expiration when: a. The stock is in the money, b. At-the
money; c. Out of the money

3. Gamma of a portfolio. Relation between the change in a portfolio and the change in the underlying
asset.

4. Variation of gamma with stock price for an option.

5. Variation of gamma with time to maturity for a stock option.

Definition:

The delta of an option is defined as the rate of change of the option price with respect to the price of
the underlying asset.
𝜕Π 𝜕2 Π
Formally: Δ = 𝜕𝑆
and Γ = 𝜕𝑆 2

Under the Black Scholes framework: Δ(𝑐𝑎𝑙𝑙) = 𝑁(𝑑1 ) exp(−𝑞 ∗ 𝑇) , Δ(𝑝𝑢𝑡) = [N(d1 ) − 1] exp(−𝑞𝑇)
𝑁 ′(𝑑1 )𝑒 −𝑞𝑇 𝑆0 𝑁 ′ (𝑑1 )𝜎𝑒 −𝑞𝑇
Also Γ𝑐𝑎𝑙𝑙 = Γ𝑝𝑢𝑡 = 𝑆0 𝜎√𝑇
, 𝜃𝑐𝑎𝑙𝑙 =− 2√𝑇
+ 𝑞𝑆0 𝑁(𝑑1 )𝑒 −𝑞𝑇 − 𝑟𝐾 ⋅ 𝑒 −𝑟𝑇 𝑁(𝑑2 ),

𝑆0 𝑁 ′(𝑑1 )𝜎𝑒 −𝑞𝑇


𝜃𝑝𝑢𝑡 = − − 𝑞𝑆0 𝑁(−𝑑1 )𝑒 −𝑞𝑇 + 𝑟𝐾 ⋅ 𝑒 −𝑟𝑇 𝑁(−𝑑2 )
2√𝑇

Python code:

def delta(S0,T,r,sigma,K,q=0,**opt):
"T = time to maturity,K = strike price,q = dividend rate"
" r = interest rate"
import numpy as np
import scipy.stats as scp
d1= (np.log(S0/K)+(r-q+sigma**2/2)*T)/(sigma*np.sqrt(T))
if opt.get("option")=="call":
return np.exp(-q*T)*scp.norm.cdf(d1)
elif opt.get("option")=="put":
return np.exp(-q*T)*(scp.norm.cdf(d1)-1)

Results for delta of a call and put:

def plot_delta_stock(T,r,sigma,K,q=0):
h1 = lambda S:delta(S,T,r,sigma,K,q,option="call")
h2 = lambda S:delta(S,T,r,sigma,K,q,option = "put")
import matplotlib.pyplot as plt
plt.figure(1)
interval = range(int(K/2),int(K*2))
plt.plot(interval,[h1(x) for x in interval])
plt.xlabel('Stock price')
plt.ylabel('Delta of call')
plt.grid(True)
plt.show()
plt.figure(2)
plt.plot(interval,[h2(x) for x in interval])
plt.xlabel('Stock price')
plt.ylabel('Delta of put')
plt.grid(True)
plt.show()
plot_delta_stock(1,0.05,0.3,100)

Dependence of option delta w.r.t to time

Python code:

def plot_delta_time(prices,r,sigma,K,q=0,N = 10,**opt):


import numpy as np
times = np.linspace(0,1,N,endpoint = True)
import matplotlib.pyplot as plt
if opt.get("option")=="call":
h1 = lambda t:delta(prices[0],t,r,sigma,K,q,option="call")
h2 = lambda t:delta(prices[1],t,r,sigma,K,q,option= "call")
h3 = lambda t:delta(prices[2],t,r,sigma,K,q,option="call")
plt.plot(times,[h1(x) for x in times],label = 'Out of the money')
plt.plot(times,[h2(x) for x in times],label = 'At the money')
plt.plot(times,[h3(x) for x in times],label = 'In the money')
plt.xlabel('Time to maturity')
plt.ylabel('Delta of call')
plt.grid(True)
plt.legend()
plt.show()
elif opt.get("option")=="put":
h1 = lambda t:delta(prices[0],t,r,sigma,K,q,option="put")
h2 = lambda t:delta(prices[1],t,r,sigma,K,q,option= "put")
h3 = lambda t:delta(prices[2],t,r,sigma,K,q,option="put")
plt.plot(times,[h1(x) for x in times],label = 'In the money')
plt.plot(times,[h2(x) for x in times],label = 'At the money')
plt.plot(times,[h3(x) for x in times],label = 'Out of the money')
plt.xlabel('Time to maturity')
plt.ylabel('Delta of call')
plt.grid(True)
plt.legend()
plt.show()
plot_delta_time([95,100,105],0.05,0.3,100,0,100,option = "call")
plot_delta_time([75,100,125],0.05,0.3,100,option = "put")

Results:
GAMMA of an option: Evolution depending on stock and remaining lifetime.

def gamma(S0,T,r,sigma,K,q=0):
import numpy as np
import scipy.stats as scp
d1= (np.log(S0/K)+(r-q+sigma**2/2)*T)/(sigma*np.sqrt(T))
return np.exp(-q*T)*scp.norm.pdf(d1)/(S0*sigma*np.sqrt(T))

def plot_gamma_stock(T,r,sigma,K,q=0):
h = lambda S:gamma(S,T,r,sigma,K,q)
import matplotlib.pyplot as plt
plt.figure(1)
interval = range(int(K/2),int(3*K/2))
plt.plot(interval,[h(x) for x in interval],\
label = 'time = {0} years,vol = {1},strike = {2}'.format(T,sigma,K))
plt.xlabel('Stock price')
plt.ylabel('Gamma')
plt.grid(True)
plt.legend()
plt.show()
plot_gamma_stock(1,0.05,0.3,100)
Result:
Test and result:

def plot_gamma_time(prices,r,sigma,K,q=0,N=10):
import numpy as np
times = np.linspace(0,1,N,endpoint = True)
import matplotlib.pyplot as plt
h1 = lambda t:gamma(prices[0],t,r,sigma,K,q)
h2 = lambda t:gamma(prices[1],t,r,sigma,K,q)
h3 = lambda t:gamma(prices[2],t,r,sigma,K,q)
plt.plot(times,[h1(x) for x in times],\
label = 'Out of the money,S0={0}'.format(prices[0]))
plt.plot(times,[h2(x) for x in times],\
label = 'At the money,S0={0}'.format(prices[1]))
plt.plot(times,[h3(x) for x in times],\
label = 'In the money,S0={0}'.format(prices[2]))
plt.xlabel('Time to maturity')
plt.ylabel('Gamma')
plt.grid(True)
plt.legend()
plt.show()

plot_gamma_time([90,100,110],0.05,0.3,100,0,100)
2. Theta
Objectives:

1. Variation of theta of a european call option with stock price

2. Typical patterns for variation of theta of a European call option with time to maturity.

Python code:

import numpy as np
import scipy.stats as scp
def theta(S0,T,r,sigma,K,q=0,**opt):
d1 = (np.log(S0/K)+(r-q+sigma**2/2)*T)/(sigma*np.sqrt(T))
d2 = d1-sigma*np.sqrt(T)
if opt.get("option")=="call":
return -S0*scp.norm.pdf(d1)*sigma*np.exp(-q*T)/(2*np.sqrt(T))+\
q*S0*scp.norm.cdf(d1)*np.exp(-q*T)-r*K*np.exp(-r*T)*scp.norm.cdf(d2)
elif opt.get("option")=="put":
return -S0*scp.norm.pdf(d1)*sigma*np.exp(-q*T)/(2*np.sqrt(T))-\
q*S0*scp.norm.cdf(-d1)*np.exp(-q*T)+r*K*np.exp(-r*T)*scp.norm.cdf(-d2)

def plot_theta_stock(T,r,sigma,K,q=0):
import matplotlib.pyplot as plt
h1 = lambda S:theta(S,T,r,sigma,K,q,option="call")
h2 = lambda S:theta(S,T,r,sigma,K,q,option = "put")
plt.figure(1)
interval = range(0,int(K*4))
plt.figure(1)
plt.plot(interval,[h1(x) for x in interval])
plt.xlabel('Stock price')
plt.ylabel('Theta of call')
plt.grid(True)
plt.show()
plt.figure(2)
plt.plot(interval,[h2(x) for x in interval])
plt.xlabel('Stock price')
plt.ylabel('Theta of put')
plt.grid(True)
plt.show()
plot_theta_stock(1,0.05,0.4,100)

Results:

3. Vega and rho of a call and put.


4. Examples of dynamic hedging.

Exotic options.
Barrier option pricing
I will touch the pricing of classical barrier options (knock-out, knock-in, double knock) through three
approaches:

• Monte Carlo method


• Binomial trees

• Full pricing – through Black – Scholes type formulas (in case of constant volatilities).

Later on, we will study the pricing in case of local volatilities as well.

Monte Carlo method


My first objective is to price an up-and-out option, with rebate R, and trigger level 𝐻 > 𝑋0 .

The payoff in general of an up-and-out option is 𝜓(𝑋𝑇 , 𝑀𝑇 ) = 𝜙(𝑋𝑇 )1{𝑀𝑇 <𝐻} + 𝑅1{𝑀𝑇 ≥𝐻} , where
𝜙(𝑋𝑇 ) =payoff of the plain-vanilla option.

An approximation for the price is 𝑒 −𝑟𝑇 𝐸𝜓(𝑋𝑇 , 𝑀𝑇 ), 𝑋𝑇 =continuous time Euler scheme for X.

𝑛−1 𝐻−𝑋𝑡𝑖 𝑋𝑡𝑖+1 −𝑋𝑡𝑖


̂𝑇 − 𝑅)Π𝑖=0
The formula for the above expectation is: 𝑅 + 𝐸[(𝜙(𝑋 𝐹ℎ𝑖 ( | )].
𝜎(𝑡𝑖 ,𝑋𝑡𝑖 ) 𝜎(𝑡𝑖 ,𝑋𝑡𝑖 )

𝑋𝑡𝑖 is the simulated value of the process 𝑋 at time 𝑡𝑖 , 𝐹ℎ is defined in section b) of Lookback options.

Python code for Monte Carlo method


I am making reference to “gbm_methods”, which are methods for simulation of geometric brownian
motion presented in the APPENDIX.

import numpy as np
from max_gbm import gbm_methods
def price_barrier_methods():
def f(t,m,w):
return (1-np.exp(-2/t *m*(m-w)))*(m>=w)
def quantity(X0,mu,sigma,T,N,H):
"H = trigger level of the up-and-out barrier option"
path = gbm_methods(1)(X0,mu,sigma,T,N)
div = np.linspace(0,T,N+1)
prod = 1
for i in range(N):
prod = prod * f(T/N,(H-path[i])/sigma(div[i],path[i]),(path[i+1]-
path[i])/sigma(div[i],path[i]))
return prod,path[-1]
def quantity2(X0,mu,sigma,T,N,H,R,phi):
"phi must be a function of one variable"
res = quantity(X0,mu,sigma,T,N,H)
return (phi(res[1])-R)*res[0]
def price_up_and_out(X0,mu,sigma,T,N,H,R,phi,size,r):
s= 0
for i in range(size):
s = s+quantity2(X0,mu,sigma,T,N,H,R,phi)
return np.exp(-r*T)*(R + s/size)
def call_price(X0,mu,sigma,T,N,K,r):
def payoff_call(X,K):
return (X-K)*(X>K)
s=0
for i in range(10000):
s = s+payoff_call(gbm_methods(1)(X0,mu,sigma,T,N)[-1],K)
return np.exp(-r*T)*s/10000

def test():
mu = lambda t,x:0.1*x
sigma = lambda t,x:0.4*x
T,N,X0,H,R = 1,30,100,np.inf,0
K,size,r = 100,1000,0.05
phi = lambda t:(t-K)*(t>K)
print(quantity(X0,mu,sigma,T,N,H))
print(price_up_and_out(X0,mu,sigma,T,N,H,R,phi,size,r))
print(call_price(X0,mu,sigma,T,N,K,r))
test()

Remark: I have used the Geometric Brownian motion with local drift and volatility.

Python code for binomial trees method


Example1: Compute the price of a barrier up-and-out call option on an equity stock with strike price
𝐾 = 100, current underlying price 𝑆0 = 100, flat term rate of 𝑟 = 5% using binomial model with 2,3
and 4 periods, knowing the historic volatility is 𝜎 = 30% and the lifetime is 1 year. The rebate is R.

Solution:
1 𝑒 𝑟Δ𝑡 −𝑑
The binomial model gives 𝑢 = 𝑒 𝜎√Δ𝑡 , 𝑑 = 𝑢 , 𝑝 = 𝑢−𝑑
. (𝑝 is the risk neutral probability).

The main methodology is as follows: We consider two stochastic processes (𝑆𝑡 ), (𝑀𝑡 )𝑡=2,3,4 as follows:

For N=2 periods, and the general payoff 𝜓(𝑀𝑇 , 𝑆𝑇 ) = 𝜙(𝑆𝑇 )1𝑀𝑇 <𝐻 + 1𝑀𝑇 ≥𝐻 𝑅, 𝐸[𝜓(𝑀𝑇 , 𝑆𝑇 )] =
𝜙(𝑆𝑢2 ) ⋅ 𝑝2 ⋅ 1𝑆𝑢2 <𝐻 + 𝜙(𝑆) ⋅ 𝑝(1 − 𝑝) ⋅ 1𝑆𝑢<𝐻 + 𝜙(𝑆) ⋅ 𝑝(1 − 𝑝) ⋅ 1𝑆<𝐻 + 𝜙(𝑆𝑑2 ) ⋅ (1 − 𝑝)2 ⋅ 1𝑆<𝐻 .

In the next piece of code I am making reference to the Appendix (Chapter 14, Binomial model section).

def exercises():
f = binomial_methods
def up_and_out1(S,sigma,r,T,H,phi):
"We need the maximum of trajectories "
N = 2 #the number of periods'
u = f(1)(sigma,T,N);d=1/u
p = f(2)(sigma,T,N,r)
average = phi(S*u**2)*p**2*(S*u**2<H)+phi(S)*p*(1-p)*(S*u<H) + \
phi(S)*p*(1-p)*(S<H)+phi(S*d**2)*(1-p)**2*(S<H)
return np.exp(-r*T)*average
def test():
phi = lambda S:(S-100)*(S>100)
print(up_and_out1(100,0.4,0.05,1,120,phi))
print(up_and_out1(100,0.4,0.05,1,180,phi))
test()

Remark: f(1) is a function that gives the up factor, f(2) gives the risk neutral probability.

For N=3 periods there is a similar approach.

Example 2:

Starting from an initial value 𝑆, an up-factor 𝑢, a down-factor 𝑑, and a number of steps 𝑁 and knowing
that at each step 𝑖, the price 𝑆𝑖 can go to 𝑆𝑖+1 ∈ {𝑆𝑖 𝑢, 𝑆𝑖 𝑑} construct a library of methods containing:

a) A 1-step binomial tree

b) A 2-step binomial tree

c) A 3-step binomial tree

d) An n step binomial tree, recursively defined.

Solution:

def trajectories_methods(num): def tree_n_steps(S,u,d,n):


def step_one(S,u,d): if n==0:
path = [S] return [S]
traj = [] elif n==1:
path.append(S*u) return step_one(S,u,d)
traj.append(path) else:
path = [S] traj = []
path.append(S*d) path = tree_n_steps(S,u,d,n-1)
traj.append(path) for x in path:
return traj x.append(x[-1]*u)
def step_two(S,u,d): traj.append(x)
"two-steps tree" x = x[0:len(x)-1]
path1= step_one(S,u,d) x.append(x[-1]*d)
traj = [] traj.append(x)
for x in path1: return traj
x.append(x[-1]*u) keys,values =
traj.append(x) [1,2,3,4],[step_one,step_two,step_three,\
x = x[0:len(x)-1] tree_n_steps]
x.append(x[-1]*d) dic = dict(zip(keys,values))
traj.append(x) return dic[num]
return traj
def step_three(S,u,d):
"three steps tree"
path = step_two(S,u,d)
traj = []
for x in path:
x.append(x[-1]*u)
traj.append(x)
x = x[0:len(x)-1]
x.append(x[-1]*d)
traj.append(x)
return traj

Exercise 3:

a. Build a binomial tree of probabilities, starting from a probability 𝑝 ∈ (0,1) and a number of steps 𝑁 as
follows: If 𝑁 = 0, return 1, if 𝑁 = 1, return 𝑝, 1 − 𝑝, and at each step, if we go up on the tree, the
previous node is multiplied by 𝑝 and if it goes down, it is multiplied by 1 − 𝑝.

b. Return only the terminal values of the previous tree, if possible by constructing a new tree.

Solution:

a. b.
def tree_probabilities(p,n): def terminal_values(p,n):
if p<0 or p>1: trajectories = tree_probabilities(p,n)
raise ValueError('invalid probability') Fin_val = []
if n==0: for traj in trajectories:
return [1] Fin_val.append(traj[-1])
elif n==1: return Fin_val
return [[p],[1-p]]
else:
traj = []
path=tree_probabilities(p,n-1)
for x in path:
x.append(x[-1]*p)
traj.append(x)
x =x[0:len(x)-1]
x.append(x[-1]*(1-p))
traj.append(x)
return traj

Exercise 4:

Compute the maximum of all trajectories from the binomial model.

Solution:

def maximum_of_trajectories(S,u,d,n): Remark:


f = trajectories_methods(4) See exercise2 for the definition of
paths = f(S,u,d,n) “trajectories_methods”. The function “f” refers
maximums = []
for row in paths: to the general tree scheme (with n periods), a
maximums.append(np.max(row)) nested function inside “trajectories_methods”.
return maximums

Lookback options
• The payoff of the fixed strike lookback call option is: max( max 𝑆𝑡 − 𝐾, 0) where 𝐾 is the strike
0≤t<T
price.

• The payoff of the fixed strike lookback put option is max(𝐾 − max 𝑆𝑡 , 0) 𝑤ℎ𝑒𝑟𝑒 K is the fixed
strike price.

Monte Carlo method


First we consider the process 𝑀𝑡 = sup 𝑊𝑠 , 𝑀𝑡𝜆 = sup (𝑊𝑠 + 𝜆𝑠) = sup 𝑊𝑠𝜆 .
0≤𝑠≤𝑡 0≤𝑠≤𝑡 0≤𝑠≤𝑡

Proposition1:

The conditional cumulative distribution function and inverse cumulative distribution function of 𝑀𝑡𝜆
given 𝑊𝑡𝜆 = 𝑤, are given by:
2
𝐹𝑡 (𝑚|𝑤) = (1 − exp [− 𝑚(𝑚 − 𝑤)]) , 𝑚 ≥ 𝑤 + ,
𝑡
1
𝐹𝑡−1 (𝑢|𝑤) = (𝑤 + √𝑤 2 − 2𝑡𝑙𝑜𝑔(1 − 𝑢), 0 ≤ 𝑢 ≤ 1.
2

Proposition 2:

Let 𝑀𝑡 = sup 𝑋𝑠 , 𝑋̂ = (𝑋̂


𝑡𝑖 )0≤𝑖≤𝑛 denote the discrete time Euler scheme for X, and 𝑋 = continuous time
0≤𝑠≤𝑡
̃𝑖 =
Euler scheme and 𝑀 sup 𝑋𝑡 , ∀𝑖 = 0, … , 𝑛 − 1.
𝑡𝑖 ≤𝑡≤𝑡𝑖+1

̂𝑖 )
We have 𝐿 ((𝑀 |𝑋̂) = 𝐿 ((𝑀
̂𝑖 ) ̂𝑖 ≔ 1 (𝑋
),where 𝑀 ̂𝑡 + 𝑋
̂𝑡𝑖+1 +
0≤𝑖≤𝑛−1 0≤𝑖≤𝑛−1 2 𝑖

2 2
̂𝑡 − 𝑋
√((𝑋 ̂ ̂
𝑡𝑖+1 ) − 2𝜎(𝑡𝑖 , 𝑋𝑡𝑖 ) ℎ𝑖 ln(1 − 𝑈𝑖 ))) for independent uniforms 𝑈𝑖 .
𝑖

Simulation of the Maximum 𝑴𝑻 . At run 𝒋:


𝒋 𝑗 𝑗
i. 𝑺𝑻 is generated as 𝑒 𝑋𝑇 with 𝑋𝑇 = 𝑥 + 𝑏𝑇 + 𝜎√(𝑇)𝜖𝑗 for an independent Gaussian draw 𝜖𝑗 .
𝑗
𝑋𝑇 −𝑥
𝜎𝐹𝑇−1 (𝑢𝑗 | )
𝑗 𝜎
ii. 𝑀𝑇 is generated as 𝑠𝑒 for an independent uniform draw 𝑢𝑗 .
Python code for Monte Carlo method
import numpy as np

def conditional_icdf(t,u,w):
return 1/2*(w+np.sqrt(w**2-2*t*np.log(1-u)))
def final_value(S0,T,mu,sigma):
"Final value of the Geometric brownian motion dS_t=mu*S_tdt+sigma*S_tdB_T"
return S0*np.exp(mu*T+sigma*np.sqrt(T)*np.random.normal(0,1))
def max_value(S0,T,mu,sigma):
u = np.random.uniform(0,1)
S = final_value(S0,T,mu,sigma)
res = conditional_icdf(T,u, np.log(S/S0)/sigma)
return S0*np.exp(sigma*res)

def price_lookback_call(S0,K,mu,sigma,T,r,size):
s=0
def payoff_call(S,K):
return (S>K) *(S-K)
for i in range(size):
s = s+payoff_call(max_value(S0,T,mu,sigma),K)
return np.exp(-r*T)*s/size

def test_lookback_call():
S0,K,mu,sigma,T,r,size = 100,100,0.1,0.4,2,0.05,1000
print(price_lookback_call(S0,K,mu,sigma,T,r,size))

Asset Backed Securities

Stochastic models for interest rates


a) Vasicek model (Ornstein-Uhlenbeck process) for short rates
The Ornstein-Uhlenbeck process is described by the following SDE:

𝑑𝑋𝑡 = 𝜃(𝜇 − 𝑋𝑡 )𝑑𝑡 + 𝜎𝑑𝑊𝑡 .

The solution of this is found by computing 𝑑(𝑋𝑡 𝑒 𝜃𝑡 ) = ⋯ = (𝐼𝑡𝑜)𝑒 𝜃𝑡 𝜃𝜇𝑑𝑡 + 𝜎𝑒 𝜃𝑡 𝑑𝑊𝑡 and then by
𝑡 𝑡
integrating this relation to obtain 𝑋𝑡 𝑒 𝜃𝑡 − 𝑋0 = ∫0 𝑒 𝜃𝑠 𝜇𝑠𝑑𝑠 + 𝜎 ∫0 𝑒 −𝜃(𝑡−𝑠) 𝑑𝑊𝑠 ⇒ 𝑋𝑡 = 𝑒 −𝜃𝑡 𝑋0 +
𝑡 𝑡
𝑒 −𝜃𝑡 𝜇(𝑒 𝜃𝑡 − 1) + 𝜎𝑒 −𝜃𝑡 ∫0 𝑒 −𝜃(𝑡−𝑠) 𝑑𝑊𝑠 = 𝑋0 𝑒 −𝜇𝑡 + 𝜇(1 − 𝑒 −𝜃𝑡 ) + 𝜎 ∫0 𝑒 −𝜃(𝑡−𝑠) 𝑑𝑊𝑠 .

The expectation of 𝑋𝑡 is 𝐸(𝑋𝑡 ) = 𝑋0 𝑒 −𝜇𝑡 + 𝜇(1 − 𝑒 𝜃𝑡 ).

Method 1 of simulating 𝑿𝒕 .

The integral is the only obstacle for doing the job:

Suppose we divide [0,t] into the division Δ = {0 = 𝑡0 < 𝑡1 < ⋯ < 𝑡𝑛−1 < 𝑡𝑛 = 𝑇}.
𝑡
∫0 𝑒 −𝜃(𝑡−𝑠) 𝑑𝑊𝑠 = ∑𝑛−1
𝑖=0 𝑒
−𝜃(𝑡−𝑡𝑖 )
(𝑊𝑡𝑖+1 − 𝑊𝑡𝑖 ) = ∑𝑛−1
𝑖=0 𝑒
−𝜃(𝑡−𝑡𝑖 )
√𝑡𝑖+1 − 𝑡𝑖 𝜖𝑖 , 𝜖𝑖 ∼ 𝑁(0,1) i.i.d.
̂𝑗 = 𝑋 𝑒 −𝜇𝑡 + 𝜇(1 − 𝑒 −𝜃𝑡 ) + ∑𝑛−1 𝑒 −𝜃(𝑡−𝑡𝑖) √𝑡 − 𝑡 𝜖̂ 𝑗 .
So 𝑋𝑡 0 𝑖=0 𝑖+1 𝑖 𝑖

Method 2 of simulating 𝑿𝒕 :

Considering the same division Δ = {0 = 𝑡0 < 𝑡1 < ⋯ < 𝑡𝑛−1 < 𝑡𝑛 = 𝑇}, the scheme used would be the
following: 𝑋0 = 𝑥, 𝑋𝑡𝑖+1 − 𝑋𝑡𝑖 = 𝜃(𝜇 − 𝑋𝑡𝑖 )(𝑡𝑖+1 − 𝑡𝑖 ) + 𝜎(𝑊𝑡𝑖+1 − 𝑊𝑡𝑖 ) and if Δ is an equidistant
𝑇 𝑇
division, 𝑋𝑡𝑖+1 = 𝑋𝑡𝑖 + 𝜃(𝜇 − 𝑋𝑡𝑖 ) ∗ 𝑁 + 𝜎 ∗ √𝑁 ∗ 𝜖, 𝜖 ∼ 𝑁(0,1).

Python code for Method 1:

def OU(X0,T,mu,theta,sigma,N,M):
'Ornstein Uhlenbeck simulation of terminal time'
'N = number of simulations, M = no. of knots on the interval [0,T]'
import numpy as np
sims = []
for i in range(N):
s = X0 * np.exp(-mu*T) + mu*(1-np.exp(-theta *T))
for j in range(1,M):
s=s+np.exp(-theta*(T-T*j/M))*np.sqrt(T/M)*np.random.normal(0,1)
sims.append(s)
return sims

Python code for Method 2:

def OU2(X0,T,mu,theta,sigma,N,M):
trajectories = []
import numpy as np
for i in range(N):
path,s = [X0],X0
for j in range(M):
s = s+theta * (mu-s)*np.sqrt(T/M) *np.random.normal(0,1)
path.append(s)
trajectories.append(path)
return trajectories
To test the validity of the two methods one can compute the expected value of 𝑿𝟏 from the formula
presented in the introduction of this section and compare with the averages of the results of method
1 and the last column of the results of Method2.

def test_OU():
X0,mu,sigma,T,N,M,theta = 0.1,0.05,0.3,1,1000,10,2
import numpy as np
print(np.mean(OU(X0,T,mu,theta,sigma,N,M)))
print(X0*np.exp(-mu*T)+mu*(1-np.exp(-theta *T)))
y = np.array(OU2(X0,T,mu,theta,sigma,N,M))
print(np.mean(y[:,-1]))
The third result is the least precise, it becomes more accurate as N and M increases in exchange for
the time consumption.

b) Hull – White model


One-factor model:

𝑑𝑟𝑡 = [𝜃(𝑡) − 𝛼(𝑡)𝑟(𝑡)]𝑑𝑡 + 𝜎(𝑡)𝑑𝑊𝑡 .

The algorithm is simple:

We consider the division Δ = {0 = 𝑡0 < 𝑡1 < ⋯ < 𝑡𝑛 = 𝑇} and we use the next scheme:

𝑟𝑡𝑖+1 = [𝜃(𝑡𝑖 ) − 𝛼(𝑡𝑖 )𝑟𝑡𝑖 ](𝑡𝑖+1 − 𝑡𝑖 ) + 𝜎(𝑡𝑖 )√𝑡𝑖+1 − 𝑡𝑖 𝜖𝑖 , 𝑖 = 0, 𝑛 − 1.

Python code:

import numpy as np
def hull_white(theta,alpha,r0,sigma,T,N,M):
"M=number of knots, T=final time,N=no.of simulations"
"alpha,theta,sigma are functions of time"
def hull_white_path(theta,alpha,r0,sigma,T,M):
div = np.linspace(0,T,M+1)
h = 1/M
rate,r = [r0],r0
"rate is the path of interest rates generated by Hull-White process"
for i in range(0,M):
r = r+(theta(div[i])-alpha(div[i])*r)*h + sigma(div[i])*np.sqrt(h)*\
np.random.normal(0,1)
rate.append(r)
return rate
paths=[]
for i in range(N):
paths.append(hull_white_path(theta,alpha,r0,sigma,T,M))
return paths

def test_HW():
theta = lambda t:t
alpha = lambda t:0.5
r0,sigma,T,N,M = 0.03,lambda t:0.2,1,10,5
print(hull_white(theta,alpha,r0,sigma,T,N,M))
c) Cox-Ingersoll Ross model
d) Bond pricing under each model
i. Bond pricing under Vasicek model
Suppose 𝑃(𝑡, 𝑇) is the price of a T-bond having face value = 1 and no coupons. What is the price of that
bond if the instantaneous zero rate 𝑟𝑡 follows the process 𝑑𝑟𝑡 = 𝛼[𝜇 − 𝑟𝑡 ]𝑑𝑡 + 𝜎𝑑𝑊𝑡 . (EQ)

The price of the T-bond is given by:

𝑃(𝑡, 𝑇) = exp(−𝐴(𝑡, 𝑇) − 𝐵(𝑡, 𝑇)𝑟𝑡 ) where


𝜇(exp(−𝛼(𝑇−𝑡)+𝛼(𝑇−𝑡)−1))
𝐴(𝑡, 𝑇) = 𝛼
− 𝜎 2 (4 exp(−𝛼(𝑇 − 𝑡)) − exp(−2𝛼(𝑇 − 𝑡)) + 2𝛼(𝑇 − 𝑡) −
3 1−exp(−𝛼(𝑇−𝑡))
3)))/(4𝛼 ) and 𝐵(𝑡, 𝑇) = 𝛼
.

I will do this in two steps:

• Give the generic solution of the equation (EQ) from above.


𝑇
• Compute 𝐸 𝑄 [exp(− ∫𝑡 𝑟𝑢 𝑑𝑢|𝐹𝑡 )] which is the price of that T-bond where 𝑄 = 𝑟𝑖𝑠𝑘 −
𝑛𝑒𝑢𝑡𝑟𝑎𝑙 𝑚𝑒𝑎𝑠𝑢𝑟𝑒.

a. By Ito’s lemma we observe that 𝑑(exp(𝛼𝑡) 𝑟𝑡 ) = exp(𝛼𝑡) 𝑑𝑟𝑡 + 𝑟𝑡 𝑑(exp(𝛼𝑡)) = ⋯ =


𝛼𝜇 exp(𝛼𝑡) 𝑑𝑡 + 𝜎 exp(𝛼𝑡) 𝑑𝑊𝑡 . Integrating this from 0 to t we obtain that exp(𝛼𝑡) 𝑟𝑡 − 𝑟0 =
𝑡 𝑡
∫0 𝛼𝜇 exp(𝛼𝜇) 𝑑𝑢 + ∫0 𝜎 exp(𝛼𝑡) 𝑑𝑊𝑡 .
𝑡
Therefore 𝑟𝑡 = 𝑟(0) exp(−𝛼𝑡) + 𝜇(1 − exp(−𝛼𝑡) + 𝜎 exp(−𝛼𝑡) ∫0 exp(𝛼𝑢) 𝑑𝑊𝑢 .
𝑢
b. For 𝑢 ∈ [𝑡, 𝑇], 𝑟𝑢 − 𝑟𝑡 = [𝜇 − 𝑟𝑡 ](1 − e−α(t−u) ) + 𝜎𝑒 −𝛼𝑢 ∫𝑡 𝑒 𝛼𝑠 𝑑𝑊𝑠 .

Since {𝑟(𝑢) − 𝑟(𝑡): 𝑡 ≤ 𝑢 ≤ 𝑇} is independent of 𝐹𝑡 = 𝜎(𝑟𝑠 ; 0 ≤ 𝑠 ≤ 𝑡) it follows that 𝑃(𝑡, 𝑇) =


𝑇 𝑇
𝐸 𝑄 [𝑒𝑥𝑝( − ∫𝑡 𝑟𝑢 𝑑𝑢 )|𝐹𝑡 ] = 𝑒 −𝑟𝑡 (𝑇−𝑡) 𝐸𝑄 [𝑒𝑥𝑝(− ∫𝑡 (𝑟𝑢 − 𝑟𝑡 )𝑑𝑢)] (1)
𝑇 𝑇
Note that 𝐸 𝑄 [exp(− ∫𝑡 (𝑟𝑢 − 𝑟𝑡 )𝑑𝑢)] = ∫Ω exp(− ∫𝑡 (𝑟(𝑢, 𝜔) − 𝑟(𝑡, 𝜔))𝑑𝑢)𝑑𝑃. (2)
𝑇 𝑇
For a fixed 𝜔 ∈ Ω, we have ∫𝑡 (𝑟(𝑢, 𝜔) − 𝑟(𝑡, 𝜔))𝑑𝑢 = ∫𝑡 [𝜇 − 𝑟(𝑡, 𝜔)](1 − exp(𝛼(𝑡 − 𝑢)) 𝑑𝑢 +
𝑇 𝑢 [𝜇−𝑟(𝑡,𝜔)](exp(−𝛼(𝑇−𝑡))+𝛼(𝑇−𝑡)−1]
𝜎 ∫𝑡 exp(−𝛼𝑢) ∫𝑡 exp(𝛼𝑠) 𝑑𝑊𝑠 (𝜔)𝑑𝑢 = −
𝛼
𝜎 𝑢 𝑇 𝑇 [𝜇−𝑟(𝑡,𝜔)](exp(−𝛼(𝑇−𝑡))+𝛼(𝑇−𝑡)−1) 𝜎 𝑇
𝛼
[𝑒 −𝛼𝑢 ∫𝑡 𝑒 𝛼𝑠 𝑑𝑊𝑠 (𝜔)| − ∫𝑡 𝑑𝑊𝑢 ] = 𝛼
− 𝛼 ∫𝑡 (𝑒 𝛼(𝑠−𝑇) −
𝑡
1)𝑑𝑊𝑠(𝜔) . (3)
𝑇
Combining (2) and (3) it results that 𝐸𝑄 [𝑒𝑥𝑝 (− ∫𝑡 (𝑟𝑢 − 𝑟𝑡 )𝑑𝑢] = 𝐸𝑄 [exp ([𝑟𝑡 −
exp(−𝛼(𝑇−𝑡)+𝛼(𝑇−𝑡)−1)
𝜇] 𝛼
+ 𝑍)] (4)
𝜎 𝑇
Where 𝑍 = 𝛼 ∫𝑡 (𝑒 𝛼(𝑠−𝑇) − 1)𝑑𝑊𝑠 .

𝜎2 𝑇 2
𝑍 is a normal random variable with mean 0 and variance 𝑉𝑎𝑟𝑄 (𝑍) = 𝛼2 ∫𝑡 (𝑒 𝛼(𝑠−𝑇) − 1) 𝑑𝑠 =
4𝑒 −𝛼(𝑇−𝑡) −𝑒 −2𝛼(𝑇−𝑡) +2𝛼(𝑇−𝑡)−3 2
𝜎 .
2𝛼 3

𝑇 [𝑟𝑡 −𝜇](exp(−𝛼(𝑇−𝑡))+𝛼(𝑇−𝑡)−1)
So from (4) it results that 𝐸𝑄 [𝑒𝑥𝑝 (− ∫𝑡 (𝑟𝑢 − 𝑟𝑡 )𝑑𝑢) ] = exp ( 𝛼
) 𝐸 𝑄 [𝑍]

[𝑟𝑡 −𝜇](exp(−𝛼(𝑇−𝑡))+𝛼(𝑇−𝑡)−1) 1
= exp ( 𝛼
+ 2 𝑉𝑎𝑟 𝑄 [𝑍]) (5).

The result follows by combining (5) with (1).


Interest rate derivatives
Bond options
Definition:

A bond option is an option to buy or sell a particular bond by a particular date for a particular price. In
addition to trading in the OTC market, bond options are frequently embedded in bonds when they are
issued to make them more attractive to either the issuer or potential purchasers.

EMBEDDED BOND Options

Callable bond: It is a bond that contains provisions allowing the issuing firm to buy back the bond at a
predetermined price at certain times in the future. The holder of such a bond has sold a call option to
the issuer. The strike price or call price in the option is the option predetermined price that must be paid
by the issuer to the holder. Callable bonds usually cannot be called within the first few years of their life.

Puttable bond: This contains provisions that allow the holder to demand early redemption at
predetermined price at certain times in the future. The holder of such a bond has purchased a put
option on the bond as well as the bond itself.

Loan and deposit instruments also often contain embedded bond options. For example, a 5-year fixed-
rate deposit with a financial institution that can be redeemed without penalty at any time contains an
American put option on a bond.

Prepayment privileges on loans and mortgages are similarly call options on bonds.

Preliminaries: Black Model


Consider a European call option on an asset with strike price 𝐾 that lasts until time T. The option’s price
is given by 𝑐 = 𝑃(0, 𝑇)𝐸𝑇 [max(𝑆𝑇 − 𝐾, 0)] where 𝑆𝑇 is the asset price at time T and 𝐸𝑇 denotes
expectations in a world that is forward risk neutral with respect to 𝑃(𝑡, 𝑇). Define 𝐹0 and 𝐹𝑇 as the
forward price of the asset at time 0 and time T for a contract maturing at time T.

Because 𝑆𝑇 = 𝐹𝑇 , 𝑐 = 𝑃(0, 𝑇)𝐸𝑇 [max(𝐹𝑇 − 𝐾, 0)]. Assume that 𝐹𝑇 is lognormal in the world being
considered, with the standard deviation of log(𝐹𝑇 ) equal to 𝜎𝐹 √𝑇.
𝜎 𝑇 2
𝐸 (𝐹 )
log( 𝑇 𝑇 )+ 𝐹
𝐾 2
On the other hand, 𝐸𝑇 [max(𝐹𝑇 − 𝐾, 0)] = 𝐸𝑇 (𝐹𝑇 )𝑁(𝑑1 ) − 𝐾𝑁(𝑑2 ) where 𝑑1 = 𝜎𝐹 √𝑇
, 𝑑2 =
𝑑1 − 𝜎𝐹 √𝑇.
𝜎 𝑇2
𝐹
log( 0 )+ 𝐹
𝐾 2
But 𝐸𝑇 (𝐹𝑇 ) = 𝐸𝑇 (𝑆𝑇 ) = 𝐹0 . Hence 𝑐 = 𝑃(0, 𝑇)[𝐹0 𝑁(𝑑1 ) − 𝐾𝑁(𝑑2 )] where 𝑑1 = 𝜎𝐹 √𝑇
, 𝑑2 =
𝐹 𝜎2 𝑇
log( 0 )− 𝐹
𝐾 2
𝜎𝐹 √𝑇
.

Similarly 𝑝 = 𝑃(0, 𝑇)[𝐾𝑁(−𝑑2 ) − 𝐹0 𝑁(−𝑑1 )].

European bond options


Many OTC bond options and some embedded bond options are European.
Assumption: The forward bond price has a constant volatility 𝜎𝐵 . In the Black Model, 𝜎𝐹 = 𝜎𝐵 and 𝐹0 =
forward bond price 𝐹𝐵 , so that :

𝒄 = 𝑷(𝟎, 𝑻)[𝑭𝑩 𝑵(𝒅𝟏 ) − 𝑲𝑵(𝒅𝟐 )] (𝟏), 𝒑 = 𝑷(𝟎, 𝑻)[𝑲𝑵(−𝒅𝟐 ) − 𝑭𝑩 𝑵(−𝒅𝟏 )](𝟐) where
𝝈 𝑻 𝟐
𝑭
𝒍𝒐𝒈( 𝑩 )+ 𝑩
𝑲 𝟐
𝒅𝟏 = 𝝈𝑩 √𝑻
, 𝒅𝟐 = 𝒅𝟏 − 𝝈𝑩 √𝑻.

𝟎 𝑩 −𝑰
𝑭𝑩 can be calculated using the formula 𝑭𝑩 = 𝑷(𝟎,𝑻) where 𝑩𝟎 is the bond price at time 0 and I is the
present value of the coupons that will be paid during the life of the option.

Solved example (source: John Hull, Option, Futures and Other derivatives, 8th edition, chapter 28:
Interest rate Derivatives).

Consider a 10 Month European call option on a 9.75-year bond with a face value of $1,000. (When the
option matures the bond will have 8 years and 11 months remaining). Suppose that the current cash
bond price is 960 dollars, the strike price is 1000, the 10M risk-free interest rate is 10% p.a. and the
volatility of the forward bond price for a contract maturing in 10 months is 9% p.a. The bond pays a
coupon of 10% per year (with payments made semi-annually). Coupon payments of $50 are expected in
3 months and 9 months. (The accrued interest is $25 and the quoted bond price is $935).

We assume that the 3M, 9M risk-free interest rates are 9.0% and 9.5% respectively. The present value of
the coupon payments is, therefore

50𝑒 −0.25×0.09 + 50𝑒 −0.75×0.095 = 95.45.

The bond forward price is given by 𝐹𝐵 = (960 − 95.45)𝑒 0.1×0.8333 = 939.68.

(a) If the strike price is the cash price that would be paid for the bond on exercise, the parameters for
10
10
equation (1) are 𝐹𝐵 = 939.68, 𝐾 = 1000, 𝑃(0, 𝑇) = 𝑒 −0.1×12 = 0.9200, 𝜎𝐵 = 0.09, 𝑇 = 12. The price of
the call option is $9.49.

(b) If the strike price is the quoted price that would be paid for the bond on exercise, 1 month’s accrued
interest must be added to 𝐾 because the maturity of the option is 1 month after a coupon date. This
produces a value for K of 1000 + 100 × 0.08333 = 1,008.33.

The values for the other parameters in equation (1) are unchanged.

Exercises:

1. Use the Black’s model to value a 1Y European put option on a 10-year bond. Assume that the current
value of the bond is $125, the strike price is $110, the one year interest rate is 10% p.a., the bond’s
forward price volatility is 8% p.a. and the present value of the coupons to be paid during the life of the
option is $10.

Solution:
In this case, 𝐹0 = (125 − 10)𝑒 0.1×1 = 127.09, 𝐾 = 110, 𝑃(0, 𝑇) = 𝑒 −0.1×1 , 𝜎𝐵 = 0.08, 𝑇 = 1.0.
127.09 0.082
ln( )+
110 2
𝑑1 = = 1.8456, 𝑑2 = 𝑑1 − 0.08 = 1.7656.
0.08

From equation (2) the value of the put option is

110𝑒 −0.1×1 𝑁(−1.7656) − 127.09𝑒 −0.1×1 𝑁(−1.8456) = 0.12$.

import numpy as np
import scipy.stats as scp
def bond_put_option0(life_put,rate,pbond,strike,sig,pvcoupons):
F0 = (pbond-pvcoupons)*np.exp(rate*life_put)
d1 = (np.log(F0/strike)+sig**2*life_put/2)/(sig*np.sqrt(life_put))
d2 = d1-sig*np.sqrt(life_put)
P0 = np.exp(-rate*life_put)
return P0*(strike*scp.norm.cdf(-d2)-F0*scp.norm.cdf(-d1))

2. We have the quoted prices of the following zero-coupon bonds (face value = 100 for each):

3M 99.5$
6M 98.7$
8M 97.4$
11M 96.9$
15M 95.1$

Use the Black’s model to value 1Y European put option on a 10-year bond. Assume that the current
value of the bond is $125, the strike price is $110, the bonds’s forward price volatility is 8% p.a. and the
present value of the coupons to be paid during the life of the option is $10.

Solution:

We first deduce the 1 year rate from the prices.


100
𝑃 log ( 𝑃 ) log(100) − log(𝑃 )
0 0 0
𝑃0 = 𝑒 −𝑡1 𝑟1 ⋅ 100 ⇒ = 𝑒 −𝑡1 𝑟1 ⇔ 𝑟1 = = .
100 𝑡1 𝑡1
The rates are:

3M 6M 8M 11M 15M
0.02005017 0.02617048 0.03951596 0.03435346 0.04019297

The rate for 1Y year is: 0.0358 =3.58%.


def bond_put_option1(prices,times,life_put,pbond,strike,sig,pvcoupons):
"the prices are the prices of zero coupon bonds"
rates = (np.log(100)-np.log(np.array(prices)))/np.array(times)
print(rates)
h = lambda t: np.interp(t,times,rates)
rate = h(life_put)
F0 = (pbond-pvcoupons)*np.exp(rate*life_put)
d1 = (np.log(F0/strike)+sig**2/2)/(sig*np.sqrt(life_put))
d2 = d1-sig
P0=np.exp(-rate*life_put) #discount factor
return P0*(strike*scp.norm.cdf(-d2)-F0*scp.norm.cdf(-d1))

3. Consider an eight month european put on a treasury bond that currently has 14.25 years to maturity.
The current cash bond price is $910, the exercise price is $900, and the volatility for the bond price is
10% per annum. A coupon of $35 will be paid by the bond in three months. The risk-free interest rate is
8% for all maturities up to one year. Use Black’s model to determine the price of the option. Consider
both the case where the strike price corresponds to the cash price of the bond and the case where it
corresponds to the quoted price.

Solution:

The present value of the coupon payment is 35𝑒 −0.08×0.25 = 34.31.


8
So 𝐹𝐵 = (910 − 34.31)𝑒 0.08×12 = 923.66, 𝑟 = 0.08, 𝜎𝐵 = 0.10, 𝑇 = 0.6667. When the strike price is a
923.66
ln( )+0.005×0.6667
900
cash price, 𝐾 = 900 and 𝑑1 = 0.1×√0.6667
= 0.3587 and 𝑑2 = 𝑑1 − 0.1√0.6667 = 0.27779.

The option is therefore worth 900𝑒 −0.08×0.6667 𝑁(−0.2770) − 875.69𝑁(−0.3587) = 18.34

When the strike price is a quoted price 5 months of accrued interest must be added to 900 to get the
cash strike price. The cash strike price is 900 + 35 × 0.8333 = 929.17.

We go through the same procedure and we obtain that the option price is
929.17𝑒 −0.08×0.6667 𝑁(0.1136) − 875.69𝑁(0.0319) = 31.22
4. Volatility of the price of a zero coupon bond tends to 0 when yield follows a diffusion process

Suppose that the yield, R, on a zero coupon bond follows the process 𝑑𝑅 = 𝜇(𝑡, 𝑅𝑡 )𝑑𝑡 + 𝜎(𝑡, 𝑅𝑡 )𝑑𝑧.

Using Ito’s lemma show that the volatility of the zero coupon bond price declines to zero as it
approaches maturity.

Proof:
1
𝜕𝑓 𝜕𝑓 𝜕2 𝑓
−𝑅(𝑇−𝑡)
Let 𝑓(𝑡, 𝑅) = 𝑒 so 𝑑𝑓(𝑡, 𝑅) = 𝑑𝑡 + 𝑑𝑅 + 2 2 𝑑𝑅𝑑𝑅 = 𝑅𝑒 −𝑅(𝑇−𝑡) 𝑑𝑡 + (𝑡 −
𝜕𝑡 𝜕𝑅 𝜕 𝑅
1
𝑇)𝑒 −𝑅(𝑇−𝑡) 𝑑𝑅 + 2 (𝑇 − 𝑡)2 𝑒 −𝑟(𝑇−𝑡) 𝑑𝑅𝑑𝑅 = 𝑅𝑒 −𝑅(𝑇−𝑡) (𝑇
dt - − 𝑡)𝑒 −𝑅(𝑇−𝑡) (𝜇𝑑𝑡 + 𝜎𝑑𝑧) +
1
2
(𝑇 − 𝑡)2 𝑒 −𝑅(𝑇−𝑡) 𝑒 −𝑅(𝑇−𝑡) 𝜎 2 𝑑𝑡.

The volatility coefficient is −𝜎(𝑇 − 𝑡)𝑒 −𝑅(𝑇−𝑡) → 0 when 𝑡 → 𝑇.


5. Put – call parity relation between european bond options.

There are two ways of expressing the put-call parity relationship for bond options. The first is in terms of
bond prices: 𝑐 + 𝐼 + 𝐾𝑒 −𝑟𝑇 = 𝑝 + 𝐵 (𝑎) where 𝑐 = price of the call option, 𝑝 = price of the
corresponding european put option, 𝐼 = present value of the bond coupon payments during the life of
the option, 𝐾 is the strike price, 𝑇 is the time to maturity, 𝐵 is the bond price, and 𝑅 is the risk-free
interest rate for a maturity equal to the life of the options.

The second way of expressing the put-call parity is 𝑐 + 𝐾𝑒 −𝑟𝑇 = 𝑝 + 𝐹0 𝑒 −𝑟𝑇 (b) where 𝐹0 is the forward
bond price.

Sketch proof of (a):

We consider two portfolios A and B

A: European put plus the bond

B: European call + an amount of cash equivalent to the present value of the coupons during the lifetime
of the option and the present value of the strike price.

At the expiration of the option, 𝑉𝑇 (𝐴) = (𝐵𝑇 − 𝐾)+ + (𝐼 + 𝐾𝑒 −𝑟(𝑇−𝑇) )(𝑇) = (𝐵𝑇 − 𝐾)+ + 𝐾 =
max(𝐵𝑇 , 𝐾) because there are no coupons left, only the strike price price to be exchanged.

On the other hand, 𝑉𝑇 (𝐵) = max(𝐾 − 𝐵𝑇 , 0) + 𝐵𝑇 = max(𝐾, 𝐵𝑇 ) = 𝑉𝑇 (𝐴).

Therefore the two portfolios have the same value at the expiration of the options so from the Absence
of Arbitrage opportunities principle we have that 𝑉𝑡 (𝐴) = 𝑉𝑡 (𝐵), ∀𝑡 > 0.

Sketch proof of (b)

We consider two portfolios A and B as follows:

A: An european put option on the bond + Forward on the bond + present value of the forward price

B: Call option + present value of the strike price

Caps and Floors


An interest rate cap is designed to provide against the rate of interest on the floating ratre note rising
above a certain level. This level is known as the cap rate. This level is known as the cap rate.

Numerical example:

Suppose that the principal amont is $10 million, the tenor is 3 months, the life of cap is 5 years, and
the cap rate is 4%. (The cap rate is expressed with quarterly compounding).

We ignore the day count issues. Suppose that on a particular reset date the 3-month LIBOR interest rate
is 5%. The floating rate note would require 0.25 × 0.05 × $10,000,000 = $125,000 of interest to be
paid 3 months later. With a 3M LIBOR rate of 4% the interest payment would be 0.25 × 0.04 ×
$10,000,000 = $100,000. The cap therefore provides a payoff of $25,000. The payoff occurs 3M after
the 5% rate is observed. IF LIBOR < 4% the payoff = 0 3 months later.

The cap does not lead to a payoff on the first reset date.

In our example, the cap lasting for 5 years, has 19 reset dates (at times 0.25, 0.50, 0.75, …, 4.75 years)
and there are 19 potential payoffs from the caps (at times 0.50, 0.75, 1.00, …, 5.00 years).

The cap as a portfolio of interest rate options


Consider a cap with a total life of T, a principal of L, and a cap rate of 𝑅𝐾 . Define the reset dates are
𝑡1 , 𝑡2 , … , 𝑡𝑛 and define 𝑡𝑛+1 = 𝑇. Define 𝑅𝑘 as the LIBOR interest rate for the period between time 𝑡𝑘
and 𝑡𝑘+1 observed at time 𝑡𝑘 (1 ≤ 𝑘 ≤ 𝑛). The cap leads to a payoff at time 𝑡𝑘+1 of 𝐿𝛿𝑘 max(𝑅𝑘 −
𝑅𝐾 , 0) (1) where 𝛿𝑘 = 𝑡𝑘+1 − 𝑡𝑘 . Both 𝑅𝑘 and 𝑅𝐾 are expressed with a compounding frequency to the
frequency of the resets.

Expression (1) is the payoff from a call option on the LIBOR rate observed at time 𝑡𝑘 with the payoff
occuring at time 𝑡𝑘+1 . The cap is a portfolio of 𝑛 such options. The 𝑛 call options underlying the cap are
known as caplets.

Valuation of Caps and Floors


As shown in equation (1) the caplet corresponding to the rate observed at time 𝑡𝑘 provides a payoff at
time 𝑡𝑘+1 of 𝐿𝛿𝑘 max(𝑅𝑘 − 𝑅𝐾 , 0).

Under the standard market model, the value of the caplet is:
𝜎 𝑡 2
𝐹
ln( 𝑘 )+ 𝑘 𝑘
𝑅𝐾 2
𝐿𝛿𝑘 𝑃(0, 𝑡𝑘+1 )[𝐹𝑘 𝑁(𝑑1 ) − 𝑅𝐾 𝑁(𝑑2 )] where 𝑑1 = 𝜎𝑘 √𝑡𝑘
, 𝑑2 = 𝑑1 − 𝜎𝑘 √𝑡𝑘 . (2)

Here, 𝐹𝑘 is the forward interest rate at time 0 for the period between 𝑡𝑘 and 𝑡𝑘+1 , and 𝜎𝑘 is the volatility
of this forward interest rate. This is a natural extension of Black’s model.

The value of the corresponding floorlet is 𝐿𝛿𝑘 𝑃(0, 𝑡𝑘+1 )[𝑅𝐾 𝑁(−𝑑2 ) − 𝐹𝑘 𝑁(−𝑑1 )] (3).

Numerical example:

We have a contract that caps the LIBOR rate on $10 million at 8% per annum (quarterly compounding)
for 3 months starting in 1 year. LIBOR/swap zero curve is flat at 7% p.a. with quarterly comp. Volatility
of the 3M forward rate underlying the caplet is 20% p.a.

Find the price of that caplet (starting in 1year and ending in 1.25 years).

Solution:
𝑟3𝑀
The continuous compounded zero rate for all maturities is 𝑟𝑐 = 4 log (1 + 4
) = 6.9395%

𝐹𝑘 = 0.07, 𝛿𝑘 = 0.25, 𝐿 = 10, 𝑅𝐾 = 0.08, 𝑡𝑘 = 1.0, 𝑡𝑘+1 = 1.25

Also 𝑃(0, 𝑡𝑘+1 ) = 𝑃(0,1.25) = 𝑒 −0.069395×1.25 = 0.9169 and 𝜎𝑘 = 0.20.


𝜎 𝑡 2
𝐹
log( 𝑘 )+ 𝑘 𝑘 (log(
0.07 1
)+0.22 × )
𝑅𝐾 2 0.08 2
𝑑1 = 𝜎 √𝑡 𝑘
= 0.20×1
, 𝑑2 = 𝑑1 − 𝜎𝑘 √𝑡𝑘 = −0.7677 so that the caplet price is
0.25 × 10 × 0.9169[0.07𝑁(−0.5677) − 0.08𝑁(−0.7677)] = $0.005162.

The price, on a notional of 10 million $ is $0.005162 × 10,000,000 = $5162.

Swaptions
Valuation

Consider a swaption where the holder has the right to pay a rate 𝑠𝐾 and receive LIBOR on a swap that
will last 𝑛 years starting in 𝑇 years. We suppose that there are 𝑚 payments per year under the swap and
the notional principal is 𝐿.

Suppose that the swap rate for an n-year swap starting at time 𝑇 proves to be 𝑠𝑇 . The payoff from the
𝐿
swaption consists of a series of cash flows equal to 𝑚 max(𝑠𝑇 − 𝑠𝐾 , 0).

The cash flows are received 𝑚 times per year for the 𝑛 years of the life of the swap. Suppose that the
swap payment dates are 𝑇1 , 𝑇2 , … , 𝑇𝑚𝑛 measured in years from today. (We could approximately
consider that 𝑇𝑖 = 𝑇 + 𝑖/𝑚). Each casj-flow is the payoff from a call option on 𝑠𝑇 with strike price 𝑠𝐾 .

Whilst a cap is a portfolio of options on interest rates, a swaption is a single option on the swap rate
with repeated payoffs.

The standard market model gives the value of a swaption where the holder has the right to pay 𝑠𝐾 as
s 𝜎2 𝑇
𝐿 ln( 0 )+
𝑠𝐾 2
∑𝑚𝑛
𝑖=1 𝑚 𝑃(0, 𝑇𝑖 )[𝑠0 𝑁(𝑑1 ) − 𝑠𝐾 𝑁(𝑑2 )] where 𝑑1 = , 𝑑2 = 𝑑1 − √𝑇𝜎.
𝜎 √𝑇

𝑠0is the forward swap rate at time zero calculated as indicated in the following equation, and 𝜎 is the
volatility of the forward swap rate (𝜎√𝑇 is the standard deviation of ln(𝑆𝑇 )).
1
Definining A as the value of a contract that pays 𝑚 at times 𝑇𝑖 , (1 ≤ 𝑖 ≤ 𝑚𝑛), the value of the swaption
1
becomes 𝐿𝐴[𝑠0 𝑁(𝑑1 ) − 𝑠𝐾 𝑁(𝑑2 )] where 𝐴 = 𝑚 ∑𝑚𝑛
𝑖=1 𝑃(0, 𝑇𝑖 ).

Numerical example:

Suppose that the LIBOR yield curve is flat at 6% per annum with continuous compounding. Consider a
swaption that gives the holder the right to pay 6.2% in a 3-year swap starting in 5 years. The volatility of
the forward swap rate is 20%.

Payments: semi-annually

Principal = 100 million $. What is the price of this swaption?

Solution:
1
𝐴 = 2 (𝑒 −0.06⋅5.5 + 𝑒 −0.06⋅6 + 𝑒 −0.06⋅6.5 + 𝑒 −0.06⋅7 + 𝑒 −0.06⋅7.5 + 𝑒 −0.06⋅8 ) = 2.0035 .
𝑟𝑐
A rate of 6% p.a. with continuous compounding translates into 𝑟 = 2 (𝑒 2 − 1) = 6.09% with semi-
annual compounding. It follows that, 𝑠0 = 0.0609, 𝑠𝐾 = 0.062, 𝑇 = 5, 𝜎 = 0.2 so that
0.0609 5
ln( )+0.22 ×
0.062 2
𝑑1 = 0.2√5
= 0.1836 and 𝑑2 = 𝑑1 − 0.2√5 = −0.2636.

Therefore the value of the swaption (in $ millions) is 100 × 2.0035 × [0.0609 × 𝑁(0.1836) −
0.062 × 𝑁(−0.2636)] = 2.07$.

Revision
1. A company caps 3-month LIBOR at 10% p.a. The principal amount is $20 million. On a reset date, 3-
month LIBOR is 12% p.a. What payment would this lead to under a cap? When would be the payment
made?

Solution:

The amount paid is 𝐿 × (0.12 − 0.1) × 0.25 = 20,000,000 × 0.02 × 0.25 = $100,000.

2.Explain why a swap option can be regarded as a type of bond option?

Answer:

A swap option is an option to enter an interest rate swap at a certain time in the future with a certain
fixed rate being used. An interest rate swap can be viewed as the exchange of a fixed-rate bond for a
floating rate bond. A swaption is therefore the option to exchange a fixed-rate bond for a floating-rate
bond. The floating rate bond will be worth its face value at the beginning of the life of the swap. The
swaption is therefore an option on a fixed-rate bond with the strike price equal to the face value of the
bond.

3. How would you use spot volatilities and flat volatilities to value a 5-year cap?

4. Show that 𝑉1 + 𝑓 = 𝑉2 where 𝑉1 is the value of a swap option to pay a fixed rate of 𝑠𝐾 and receive
LIBOR between 𝑇1 and 𝑇2 , 𝑓 is the value of a forward swap to receive a fixed rate of 𝑠𝐾 and pay LIBOR
between times 𝑇1 and 𝑇2 , and 𝑉2 is the value of the swap option to receive the rate 𝑠𝐾 in exchange for
floating between 𝑇1 and 𝑇2 .

Remark:

The forward swap is like a FRA with the difference that instead of paying fixed rate between 𝑇1 and 𝑇2 it
pays a floating rate.

Solutions to proposed problems

APPENDIX
Bond pricing
I have combined in this library of functions, also called factory function, a few particular situations.
def bond_price_methods(num):
def bp1(FV,c,r):
"1 year bond price,annual coupon”
return FV *(c+1)*np.exp(-r)
def bp2(FV,c,r1,r2,r3):
" 2 year bond price, semi-annual coupons, 6M,1Y and 2Y zero rates"
price = c/2*np.exp(-r1/2)*FV + c/2*np.exp(-r2)*FV+c/2*np.exp(-(r1+r2)*3/2)\
*FV + (c/2+1)*FV*np.exp(-r3*2)
return price
def bp3(FV,c,r,T):
"T is an integer number of years, c=coupon rate, annual payments, only 1Y rate"
price = 0
for i in range(1,T+1):
price = price+np.exp(-r*i)*c*FV
return price + np.exp(-r*T)*FV
def bp4(FV,c,r,T):
"semi-annual coupons"
price = 0
for i in range(1,2*T+1):
price = price+np.exp(-r*i/2)*c/2*FV
return price+np.exp(-r*T)*FV
def bp5(FV,c,r,T):
"quarterly payments"
price = 0
for i in range(1,4*T+1):
price = price+np.exp(-r*i/4)*c/4*FV
return price+np.exp(-r*T)*FV
def bp6(FV,c,r,T,types):
if types==1:
return bp3(FV,c,r,T)
elif types==2:
return bp4(FV,c,r,T)
elif types==4:
return bp5(FV,c,r,T)
def bp7(FV,c,zero_rates,times):
price = 0
for i in range(len(times)):
if i==0:
price = price+c*times[0]*np.exp(-zero_rates[0]*times[0])*FV
else:
price = price+c*(times[i]-times[i-1])*np.exp(-zero_rates[i]*times[i])*FV
return price+np.exp(-zero_rates[-1]*times[-1])*FV
dic = dict(zip([1,2,3,4,5,6,7],[bp1,bp2,bp3,bp4,bp5,bp6,bp7]))
return dic[num]

Bond price and rates:


def bond_price1(FV,c,T=1,freq=4,r=0):
s = 0
import numpy as np
for i in range(4):
s=s+np.exp(-r*(i+1)/4)*c*FV/4
s = s+np.exp(-r)*FV
return s

def bond_price2(cash_flows,discount_rates):
s=0
if len(cash_flows)!=len(discount_rates):
raise IndexError('the cash_flows and discount rates are not
consistent in length')
else:
for i in range(len(cash_flows)):
s = s+cash_flows[i]*discount_rates[i]
return s

def bond_price3(FV,r,c,freq,T):
'bond_price at time 0 '
import numpy as np
k,delta = int(freq*T),1/freq
times =np.linspace(T-k*delta,T,k+1,endpoint=True)
cash_flows = np.ones((k+1))*c*FV/freq
cash_flows[-1]=cash_flows[-1]+FV
disc_rates = np.array([np.exp(-r*x) for x in times])
return bond_price2(cash_flows,disc_rates)

def bond_price4(FV,r,c,freq,T,t):
if T<t:
raise IndexError('{0} must be between {1} and {2}'.format(t,0,T))
import numpy as np
k,delta = int(freq*T),1/freq
times =np.linspace(T-k*delta,T,k+1,endpoint=True)
def locate_time(t,times_):
for i in range(len(times_)-1):
if times_[i]<=t and t<times_[i+1]:
return times_[i]
if t<times[0]:
accrued_coupon = 0
return bond_price3(FV,r,c,freq,T-t)
elif t==T:
return FV
else:
accrued_coupon = c*FV/freq * (t-locate_time(t,times))
return bond_price3(FV,r,c,freq,T-t)-accrued_coupon

def discount_rates(times,rates):
import numpy as np
return np.array([np.exp(-times[i]*rates[i])for i in range(len(times))])

def discounted_cf(times,rates,cash_flows):
import numpy as np
return discount_rates(times,rates)*np.array(cash_flows)

def bond_price_bis(times,rates,cash_flows):
import numpy as np
return np.dot(discount_rates(times,rates),np.array(cash_flows))

def cash_flows(T,freq):
'T can be a float type, freq = the number of times per year you have
payments'
import numpy as np
k,delta=int(T*freq),1/freq
times = np.linspace(T-k*delta,T,k+1)
return times

def rates1(times,zero_rates):
'The problem is when the zero_rates available are for terms not
corresponding '
'to cash-flows terms'

def test_cash_flows():
print(cash_flows(1.6,4))

def test_disc():
'I am testing the discounted rates functions and the bond prices'
times = [0.25,0.75,1.25]
rates = [0.1,0.105,0.11]
cash_flows = [4,4,104]
fl_rate,time = 0.102,0.25
print(discount_rates(times,rates))
print(discounted_cf(times,rates,cash_flows))
print(bond_price_bis(times,rates,cash_flows))
fix_bond = bond_price_bis(times,rates,cash_flows)
import numpy as np
fl_bond = np.exp(-time*rates[0])*(fl_rate/2+1)*100
print('the price of the swap is ', fix_bond-fl_bond)

def exercise1(c,FV):
t0 = [0.5,1,1.5,2] #times when the coupons are paid
r = [0.025,0.03] #the zero rates at times t[0] and t[1],see below
import numpy as np
cash_flows = np.array([c/2*FV,c/2*FV,c/2*FV,(c+2)/2*FV])
t = [0.25,1] #the terms where the rates change
def lin_int(t1,t2,y1,y2,s):
'linear interpolation'
return (s-t1)/(t2-t1)*y2+(t2-s)/(t2-t1)*y1
def disc_rate(r,t):
return np.exp(-r*t)
rates = [lin_int(t[0],t[1],r[0],r[1],t0[0]),r[1],r[1],r[1]]
disc_rates = np.array([disc_rate(rates[0],t0[0]),disc_rate(r[1],t0[1]),\
disc_rate(r[1],t0[2]),disc_rate(r[1],t0[3])])
return bond_price2(cash_flows,disc_rates)

def locate_time(t,times):
if t<=times[0]:
return 0
for i in range(len(times)-1):
if times[i]<=t and t<times[i+1]:
return i
if t>times[len(times)-1]:
return len(times)

def lin_int(t1,t2,y1,y2,t):
return (t-t1)/(t2-t1)*y2+(t2-t)/(t2-t1)*y1

def rates2(t,times,rates):
if len(times)!=len(rates):
raise IndexError('the rates and their corresponding times are not
consistent')
if t<times[0]:
return rates[0]
elif t>times[-1]:
return rates[-1]
else:
k=locate_time(t,times)
return lin_int(times[k],times[k+1],rates[k],rates[k+1],t)

def test_rates():
import numpy as np
times = np.linspace(0.25,1,4,endpoint=True)
rates_ = np.linspace(0.01,0.04,4,endpoint=True)
print(rates1(0.375,times,rates_)) #should give 0.015
print(rates1(0.1,times,rates_))#should give 0.01
print(rates1(1.5,times,rates_))#should give 0.04

def test_rates2():
import numpy as np
times = np.linspace(0.25,1,4,endpoint=True)
rates_ = [0.01,0.015,0.025,0.04]
import matplotlib.pyplot as plt
x = np.linspace(0.05,2,41,endpoint=True)
h=lambda s:rates1(s,times,rates_)
plt.plot(x,np.array([h(s) for s in x]))
plt.grid(True)
plt.show()

def test1():
'print(bond_price3(100,0.05,0.04,4,1))'
'print(bond_price3(100,0.02,0.04,4,1))'
print(bond_price4(100,0.02,0.04,4,1,0.25))

def test2():
import matplotlib.pyplot as plt
f = lambda t:bond_price4(100,0.02,0.04,4,1,t)
import numpy as np
times = np.linspace(0,1,41,endpoint=True)
print(f(times[0]))
plt.plot(times,list([f(t) for t in times]))
plt.xlabel('time passed from start (years)')
plt.ylabel('bond price')
plt.grid(True)
plt.show()

import lists_and_other_types as lot


def cash_flows2(c,freq,T,FV):
'T=time remaining to maturity'
'freq = frequency of payments'
'FV = face value of the instrument,c=coupon (in %)'
import numpy as np
k=int(T*freq)
cf = np.ones(k)*c*FV/freq
cf[-1]+=FV
return cf

Geometric brownian motion methods


def gbm_methods(num):
"Geometric brownian motion test"
def gbm_path(S,mu,sigma,T,N):
"Euler scheme"
h = T/N
div = np.linspace(0,T,N+1)
path,x=[S],S
for i in range(N):
x = x+mu(div[i],x)*h+sigma(div[i],x)*np.sqrt(h)*np.random.normal(0,1)
path.append(x)
return path
def gbm_continuous_path(S,mu,sigma,T,N):
path = gbm_path(S,mu,sigma,T,N)
div = np.linspace(0,T,N+1)
h = lambda x:np.interp(x,div,path)
return h
def test():
mu = lambda t,x:0.1
sigma = lambda t,x:0.4
print(gbm_path(100,mu,sigma,1,10))
h = gbm_continuous_path(100,mu,sigma,1,10)
import matplotlib.pyplot as plt
x = np.linspace(0,1,11)
plt.plot(x,gbm_path(100,mu,sigma,1,10),'o',x,np.array([h(s) for s in x]))
plt.plot()
def test2():
mu = lambda t,x:0.1*x
sigma = lambda t,x:0.4 *x
print(gbm_path(100,mu,sigma,1,10))
def test3():
size = 10000
mu = lambda t,x:0.1*x
sigma = lambda t,x:0.4*x
sample = []
for i in range(size):
sample.append(gbm_path(100,mu,sigma,1,10)[-1])
print(np.mean(sample))
dic = {1:gbm_path,2:gbm_continuous_path,3:test,4:test2,5:test3}
return dic[num]
Market Risk measures. VaR.
Formally, the Value at Risk for threshold alpha in case of P&L Π can be defined as follows:

𝑉𝑎𝑅Π (𝑎𝑙𝑝ℎ𝑎) = inf{𝑥 ∈ 𝑅| Pr(Π ≤ 𝑥) ≥ 𝑎𝑙𝑝ℎ𝑎}.

In practice we will define the methodology for computations of historical and analytical VaR.

Historical VaR
Methodology
1. We compute the rank statistics of the P&L’s / returns:

2. We compute the order statistics and ordered P&L’s/returns: Π1:𝑛𝑆 ≤ Π2:𝑛𝑆 ≤ ⋯ ≤ Π𝑛𝑆:𝑛𝑆 .

3. We take 𝑘 = [𝛼 ⋅ 𝑛𝑆 ] where [.] is the integer part function. We choose as VaR, either Π𝑘:𝑛𝑆 or
𝛼 ⋅ Π(𝑘+1)⋅𝑛𝑆 + (1 − 𝛼)Π𝑘:𝑛𝑆

The VaR can be concentred on two directions: 1) Returns; 2) P&L’s.


Historical VaR Python code for returns and P&L’s
def order_statistic(X,k):
'k must be greater or equal than 1'
import numpy as np
X = np.sort(np.array(X))
return X[k-1]

def VaR(X,alpha):
'quantile alpha for the worst alpha % returns'
import numpy as np
y = np.array(X)
ret = np.diff(y)/y[0::-2]
k = int(alpha*len(ret))
return order_statistic(ret,k)*(k+1-alpha*len(ret))+order_statistic(ret,k+1)* \
(alpha*len(ret)-k)

def VaR2(X,alpha):
'quantile alpha for the worst alpha % P&Ls'
import numpy as np
y = np.array(X)
PL = np.diff(y)
'PL = Profit and Loss'
k= int(alpha*len(X))
return order_statistic(PL,k)*(k+1-alpha*len(PL))+order_statistic(PL,k+1)*
\(alpha*len(PL)-k)
Results
I present as follows for Renault SA, the Value at Risk of the weekly P&L’s and returns throughout the
period January 1998 – October 2017.
Expected shortfall
Definition:

We define the expected shortfall, 𝐸𝑆𝛼 as the conditional loss, when the P&L is below 𝑉𝑎𝑅𝛼 .

𝐸𝑆𝛼 (𝑋) = 𝐸[𝑋|𝑋 < 𝑉𝑎𝑅𝛼 (𝑋)].

Numerical algorithm for computation:

We choose the P&L’s lower than 𝑉𝑎𝑅𝛼 and we compute their average.

Code in Python
def ES(X,alpha):
'Expected Shortfall on the PL'
import numpy as np
X = np.array(X)
y = np.diff(X)
y = y[y<VaR2(y,alpha)]
import numpy as np
return np.mean(y)

Results

I present in the following the case of a raw investment in a Renault SA stock and its potential loss w.r.t
the threshold 𝛼. The period is the same as before: January ’98 – October 2017.

You can see that the expected loss is around 6.5 euros for a probability of 10%.
Code

def test3():
'I am testing the Expected shortfall function for the Renault SA returns'
global names,prices
pos = names.index('Renault SA')
h = lambda alpha:ES(prices[pos],alpha)
import matplotlib.pyplot as plt
import numpy as np
knots = np.linspace(0.01,0.2,20)
y = list([h(x) for x in knots])
plt.plot(y)
plt.title('Renault SA ES graph')
plt.xlabel('threshold (%)')
plt.ylabel('ES')
plt.grid('on')

Analytical VaR
In this case the discussion is more complex. The VaR theoretically depends on the model chosen.

However, there are algorithms which will be presented later, where we will develop a numerical method
for approximating the VaR, no matter what is the pdf of the random variable X, designed for P&L/Loss.

We assume first that the returns’ distribution is Gaussian, 𝑁(𝜇, 𝜎 2 ).

The formula in this case for 𝑉𝑎𝑅𝛼 = −𝜇(𝑅) + Φ−1 (𝛼) ⋅ 𝜎(𝑅).

The steps to follow usually are:


1. We find the 𝑀𝐿𝐸(𝜇, 𝜎 2 ) = (𝜇0 , 𝜎02 ) for the historical returns.

2. We compute the VaR taking into account these new estimates.

Code for analytical VaR and ES under conditions of normal distribution

def returns(X):
import numpy as np
return np.diff(X)/X[0:len(X)-1]
def estimate_normal_parameters(X):
'X is usually a vector of prices returns'
import numpy as np
mu = np.mean(X)
sigma = np.std(X)**2
return mu,sigma
def analytic_VaR_normal(X,alpha):
mu,sigma = estimate_normal_parameters(returns(X))
import numpy as np
import scipy.stats as sct
return mu + sct.norm.isf(1-alpha,0,1)*np.sqrt(sigma)
def analytic_ES_normal(X,alpha):
mu,sigma = estimate_normal_parameters(X)
import numpy as np
import scipy.stats as sct
return mu + sct.norm.ppf(sct.norm.isf(1-alpha))/(1-alpha)*np.sqrt(sigma)

Results of comparison between historical and analytical VaR

1. We find the best coefficients that describe the returns.

2. We use the analytical formula for VaR.

Example of comparison between historical and analytical VaR for the returns of a stock
Code implementation for the above example in Python:

def test_VaR_both_methods():
'We will test the VaR for returns of the Renault SA company with historical and '
'analytical normal model'
global names,prices
import matplotlib.pyplot as plt
import numpy as np
pos = names.index('Renault SA')
mu,sigma = estimate_normal_parameters(returns(prices[pos]))
h1,h2 = lambda x: VaR(prices[pos],x),lambda x: analytic_VaR_normal(prices[pos],x)
knots = np.linspace(0.01,0.2,20)
y1,y2 = list([h1(x) for x in knots]),list([h2(x) for x in knots])
plt.subplot(211)
plt.plot(y1,'r--',label="Historical VaR")
plt.plot(y2,'b--',label = "Analytic normal VaR")
" plt.legend('historical VaR','analytic normal VaR')"
plt.title('Renault SA VaRs')
plt.xlabel('threshold (%)')
plt.ylabel('VaRs')
plt.legend()
plt.text(10.0,-0.5,r'The parameters $\mu, \sigma $ are {0} and {1}'.format(mu,sigma))
plt.grid('on')
plt.subplot(212)
plt.plot(np.array(y2)-np.array(y1),'g--',label = "Difference between VaRs")
plt.legend()
plt.grid('on')

Newton-Raphson approximation for Value at Risk

First, this is a code for inverse cumulative distribution function in the continuous distribution case:

def icdf(alpha,f):
'Inverse cumulative distribution function'
import numpy as np
import scipy.integrate as scp
F = lambda y:scp.quad(f,-np.inf,y)[0]
x=0
for i in range(10):
x = x+(alpha-F(x))/f(x)
return x

You can test this through the next code:

def test_VaR():
import numpy as np
f = lambda x:1/np.sqrt(2*np.pi)*np.exp(-x**2/2)
alpha = 0.05
print(icdf(alpha,f))

For a more conclusive test using plots, I drew the next checking test:
def test_graphic_VaRs(mu,sigma):
'test the Newton Raphson method against the classical analytical formula for icdf or '
'VaR in case of a normal random variable of parameters mu,sigma'
import numpy as np
f = lambda x: 1/(sigma*np.sqrt(2*np.pi))*np.exp(-(x-mu)**2/(2*sigma**2))
import scipy.stats as stats
g = lambda alpha:stats.norm.isf(1-alpha,loc=mu,scale = sigma)
import matplotlib.pyplot as plt
threshold = np.linspace(0.01,0.20,20)
plt.plot(threshold,np.array([icdf(alpha,f) for alpha in threshold]),label= "Newton
method")
plt.legend()
plt.plot(threshold,np.array([g(alpha) for alpha in threshold]),label = "Analytical
formulas")
plt.legend()

Its result is:

Counterparty credit risk


Exposure measures: EE, EPE, EEE, EEPE
Definition:

We define the expected exposure at time t as 𝐸𝐸(𝑡) = 𝐸[𝑒(𝑡)] = ∫0 𝑥𝑑𝐹[0,𝑡] (𝑥) where 𝐹[0,𝑡] (𝑥) =
1 𝑡
Pr({𝑒(𝑡) ≤ 𝑥}) as well as the expected positive exposure 𝐸𝑃𝐸(𝑡) = 𝑡 ∫0 𝐸𝐸(𝑢)𝑑𝑢.

Example (Forward OTC contract where underlying price 𝑺𝒕 follows the geometric brownian motion).

Suppose we have a contract with lifetime T and flat term interest rate r.

What you must know about a forward contract are the following:

Terms of the contract, and mark-to-market value:

1. The contract has a duration T, the current underlying price is 𝑆0 and the strike price is 𝐾.
2. The value of the forward at time 0 will be: 𝑓𝐿 (0,0, 𝑇) = 𝑆0 − 𝐾 ⋅ 𝑒 −𝑟⋅𝑇 , under assumption
A1) or 𝑓𝐿 (0,0, 𝑇) = 𝑆0 − 𝐾 ⋅ 𝑒 −𝑟𝜏⋅𝑇 where 𝑟𝜏 = 𝑟𝑖 where 𝑡𝑖−1 ≤ 𝑡 < 𝑡𝑖 under assumption A2.

At time t, the value of the forward contract is 𝑓𝐿 (0, 𝑡, 𝑇) = 𝑆𝑡 − 𝐾 ⋅ 𝑒 −𝑟⋅(𝑇−𝑡) under


assumption A1 and 𝑓𝐿 (0, 𝑡, 𝑇) = 𝑆𝑡 − 𝐾 ⋅ 𝑒 −𝑟𝑖(𝑇−𝑡) where 𝑟𝑖 is the associated interest rate to
the remaining time 𝑇 − 𝑡.

3. The mark-to-market value at time 𝑡 for the forward contract is 𝑀𝑡 𝑀(𝑡) = 𝑓𝐿 (0, 𝑡, 𝑇) −
𝑓𝐿 (0,0, 𝑇) = 𝑆𝑡 − 𝑆0 − 𝐾 ⋅ (𝑒 −𝑟(𝑇−𝑡) − 𝑒 −𝑟𝑇 ) = 𝑓(𝑆𝑡 ).

In the continuation of our work we wil work under the auspices of Black & Scholes diffusion model for
the underlying price 𝑆𝑡 , namely 𝑑𝑆𝑡 = 𝜇𝑆𝑡 𝑑𝑡 + 𝜎𝑆𝑡 𝑑𝐵𝑡 . We will use the Euler scheme to simulate the
trajectories of the underlying asset.

̂0 = 𝑆0 , 𝑆𝑡 = 𝑆𝑡 + 𝜇𝑆𝑡 ⋅ ℎ𝑖 + 𝜎𝑆𝑡 √ℎ𝑖 𝜖𝑖 , 𝑖 ∈ 1, 𝑛 where 𝑛 is the number of knots.


We know 𝑆 𝑖 𝑖−1 𝑖−1 𝑖−1

Numerically, the algorithm is as follows:

a. We have 𝑛𝑆 simulations for the process 𝑆, at each time 𝑡𝑖 , 𝑡𝑖 ∈ {𝑡0 = 0 < 𝑡1 < ⋯ < 𝑡𝑁 = 𝑇}.
𝑗
b. We have 𝑀𝑡𝑀𝑗 (𝑡𝑖 ) = 𝑉(𝑡𝑖 , 𝑆𝑡𝑖 ) − 𝑉(0, 𝑆0 )

c. We have potential future exposure simulates 𝑒𝑗 (𝑡𝑖 ) = max(𝑀𝑡𝑀𝑗 (𝑡𝑖 ), 0)


1 𝑛𝑆
d. 𝐸𝐸(𝑡𝑖 ) = ⋅ ∑𝑗=1 𝑒𝑗 (𝑡𝑖 )
𝑛𝑆

1 1
e. 𝐸𝑃𝐸(0, 𝑡𝑖 ) = (𝑅𝑖𝑒𝑚𝑎𝑛𝑛 𝑎𝑝𝑝𝑟𝑜𝑥𝑖𝑚𝑎𝑡𝑖𝑜𝑛) ⋅ ∑𝑖𝑘=1 𝐸𝐸(𝑡𝑘 )Δ𝑡𝑘 = ⋅ ∑𝑖𝑘=1 𝐸𝐸(𝑡𝑘 )
𝑡𝑖 𝑖

Python code for the afore-mentioned measures

def mark_to_market_value(t1,t2,S1,S2,V):
'V is the value of the instrument: it is a function of time and space in Python'
return V(t2,S2)-V(t1,S1)
def price_forward(S,K,r,T,t):
import numpy as np
return S - K*np.exp(-r*(T-t))

For all the following measure regarding counterparty exposure (risk) we will use as variables pricing
functions of two variables, (forward price, call price,etc.)
S will always be a matrix (Mx(N+1)) of simulated trajectories, from now on.
M is the number of simulations and N is the number of knots.
Algorithm for CVA
1. We create a Mx(N+1) matrix of simulated trajectories for 𝑆𝑡 where 𝑑𝑆𝑡 = 𝜇𝑆𝑡 𝑑𝑡 + 𝜎𝑆𝑡 𝑑𝐵𝑡
𝑇 2∗𝑇
where M is the number of simulations and N = the number of knots from Δ = {0 < < <
𝑁 𝑁
⋯ < 𝑇}. Each row will represent a simulated trajectory and each column a point in time.

2. We compute the matrix of mark-to-market values except for the first column.
𝑠𝑢𝑚(𝑀𝑡𝑀[,𝑖])
3. We compute the expected exposures at each knot: 𝐸𝐸(𝑡𝑖 ) = 𝑛𝑜 , namely the average
𝑠𝑖𝑚𝑢𝑙𝑎𝑡𝑖𝑜𝑛𝑠
over the column 𝑖 of the simulated mark-to-market values.
1
4. 𝐸𝑝𝐸(𝑡𝑖 ) = 𝑖 ⋅ (𝐸𝐸(𝑡1 ) + ⋯ + 𝐸𝐸(𝑡𝑖 )) = average over the columns 1,2, … , 𝑖 of the mark-to-
market values.

5. 𝐶𝑉𝐴 = (1 − 𝑅𝐵 ) ⋅ ∑𝑡𝑖≤𝑇 𝐸𝑝𝐸(𝑡𝑖 )𝐵(0, 𝑡𝑖 )(𝑆𝐵 (𝑡𝑖−1 ) − 𝑆𝐵 (𝑡𝑖 )).

We will supose we have a flat term rate of zero rates, 𝑟 = 𝑟𝑡 and we will assume for the beginning the
exp(𝜆) exponential model for survival time.

def MtM(S, T,V):


'Mark-to-Market value, depending on: S -- the matrix of simulated trajectories of '
'a certain process'
import numpy as np
S0 = S[0,0]
sz = np.shape(S)
delta = np.linspace(T/sz[1],T,sz[1])
x = np.array([[mark_to_market_value(0,delta[i-1],S0,S[j,i],V) for i in range(\
1,sz[1])] for j in range(sz[0])])
return x
def potential_future_exposure(S,T,V):
y = MtM(S,T,V)
import numpy as np
sz=np.shape(y)
def maxim(x):
return np.max([x,0])
return np.array([[maxim(z) for z in y[i]] for i in range(sz[0])])
def expected_exposure(S,T,k,V):
'k should be an integer >=1 and will represent the column in the Mark to Market value'
'k must also be <= than the number of cols (the no. of knots)'
y = potential_future_exposure(S,T,V)
import numpy as np
y = np.array(y)
sz = np.shape(y)
return np.sum(y[:,k-1])*1/sz[0]
def expected_positive_exposure(S,T,k,V):
s=0
for i in range(1,k+1):
s = s+expected_exposure(S,T,i,V)
return s/k
Example1: 𝜇 = 0.1, 𝜎 = 0.4, 𝑟 = 0.05,𝐾 = 100$ the strike price of a forward contract with maturity
2 2⋅2
T=2 years, and suppose we have a division Δ = {0, 10 , 10 , … ,2} with 11 division points and we want to
compute 𝐸𝑃𝐸(𝑇) with respect to the underlying price S.

Code for the above results

def test_graphic_EPE():
'I want to test the EPE w.r.t to S and w.r.t. K while K=100 or S = 100'
import numpy as np
x = np.linspace(50,150,11)
import matplotlib.pyplot as plt
plt.figure(1)
plt.plot(x,np.array([test_EPE1(y) for y in x]))
plt.xlabel('underlying price S'),plt.ylabel('EPE')
plt.title('Expected positive exposure w.r.t. underlying asset price')
plt.grid(True)
plt.xlim(50,150)
plt.figure(2)
plt.plot(x,np.array([test_EPE2(y) for y in x]))
plt.xlabel('strike price K'),plt.ylabel('EPE')
plt.title('Expected positive exposure w.r.t. strike price')
plt.grid(True)
plt.xlim(50,150)
plt.show()
Credit valuation adjustment and Debit Valuation Adjustment
Code in Python

def CVA(S,T,r,lbd,RR,V):
import numpy as np
s=0
sz = np.shape(S)
delta = np.linspace(0,T,sz[1]+1)
'the division of points'
def discount_rate(r,t):
return np.exp(-r*t)
for i in range(1,sz[1]):
s = s+expected_positive_exposure(S,T,i,V)*(np.exp(-delta[i-1]*lbd)-np.exp(-
delta[i]*lbd))*discount_rate(r,delta[i])
return (1-RR)*s
Code for the results above

First we define the lambda functions of CVA w.r.t. underlying price S, strike price K, and interest rate r
under the auspices of geometric brownian motion model for 𝑑𝑆𝑡 = 𝜇𝑆𝑡 𝑑𝑡 + 𝜎𝑆𝑡 𝑑𝐵𝑡 .

def test_CVA(S0):
'We are testing the CVA for a forward contract'
'the model assumed is Black Scholes with constant drift and diffusion params'
import geometric_brownian_motion_for_CVA as gbm
mu,sigma,N,T,no_sims = 0.1,0.4,10,2,1000
y = gbm.MC_sim_geometric_BM(S0,mu,sigma,N,T,no_sims)
K,r,lbd,RR = 100,0.05,1,0.4
V = lambda t,S:price_forward(S,K,r,t,T)
return CVA(y,T,r,lbd,RR,V)

def test_CVA2(r):
import geometric_brownian_motion_for_CVA as gbm
mu,sigma,N,T,no_sims = 0.1,0.4,10,2,1000
y = gbm.MC_sim_geometric_BM(100,mu,sigma,N,T,no_sims)
K,lbd,RR = 100,1,0.4
V = lambda t,S:price_forward(S,K,r,t,T)
return CVA(y,T,r,lbd,RR,V)

def test_CVA3(K):
import CVA_interface2 as CVA2
import geometric_brownian_motion_for_CVA as gbm
S0,mu,sigma,N,T,no_sims = 100,0.1,0.4,10,2,1000
y = gbm.MC_sim_geometric_BM(S0,mu,sigma,N,T,no_sims)
r,lbd,RR = 0.05,1,0.4
V = lambda t,S:CVA2.price_forward(S,K,r,t,T)
return CVA(y,T,r,lbd,RR,V)
Results

In the following I present three situations of the computations of CVA.

1. With respect to underlying price S: Data (𝜇= 0.1, 𝜎 = 0.4, no. of nodes N = 10, T = 2 years,
number of simulations = 1000, K (strike price) = 100, r=0.05(5%), lbd = 1, recovery rate = 40%.

We assume an exponential default


time: 𝑒𝜆 ⇒ 𝑆𝐵 (𝑡) = 𝑒 −𝜆∗𝑡 .S

2. With respect to the interest rate. Data is almost the same, but instead of varying 𝑆0 , which
determines the path, we vary 𝑟.

𝑆0 = 100, all data remaining the same.

3. With respect to strike price. (r=5%, 𝑆0 = 100, 𝐾 = 100, 𝑒𝑡𝑐. )


N.B. You can consult the three functions test_CVA(S0), test_CVA2 (r), test_CVA3 (K) and the graphics
test_graphic_CVA() in Python to check the results.

For the results presented in the graphics above the code is the following:
def test_graphic_CVA():
import numpy as np
x = np.linspace(50,150,11)
r = np.linspace(0.01,0.2,20)
import matplotlib.pyplot as plt
plt.figure(1)
plt.plot(x,np.array([test_CVA(y) for y in x]))
plt.xlabel('underlying price S'),plt.ylabel('CVA')
plt.title('CVA w.r.t. underlying asset price')
plt.grid(True)
plt.xlim(50,150)
plt.figure(2)
plt.plot(r,np.array([test_CVA2(y) for y in r]))
plt.xlabel('interest rate r'),plt.ylabel('CVA')
plt.title('CVA w.r.t interest rate')
plt.xlim(0.01,0.2)
plt.grid(True)
plt.figure(3)
plt.plot(x,np.array([test_CVA3(y) for y in x]))
plt.xlabel('Strike price K'),plt.ylabel('CVA')
plt.title('CVA w.r.t. strike price')
plt.xlim(50,150)
plt.grid(True)
plt.show()
Continuous time computation of CVA
Forward contract:
Methodology of CVA under Geometric Brownian Motion auspices

Hypothesis:

The term rate is flat, 𝑟𝑡 = 𝑟, 𝜏 ∼ 𝑒𝜆 or 𝐹𝐵 (𝑡) = 1 − 𝑒 −𝜆𝑡 .

As I have shown before the mark-to-market value of the forward contract at time t is 𝑀𝑡𝑀(𝑡) =
𝑓𝐿 (0, 𝑡, 𝑇) = 𝑆𝑡 − 𝑆0 − 𝐾(𝑒 −𝑟(𝑇−𝑡) − 𝑒 −𝑟𝑇 ) = 𝑓(𝑆𝑡 )
𝑇
We also know that 𝐶𝑉𝐴 = (1 − 𝑅𝐵 ) ∫0 𝐵0 (𝑡)𝐸𝑝𝐸(𝑡)𝑑𝐹𝐵 (𝑡) , 𝐹𝐵 (𝑡) = 𝑃(𝜏𝐵 < 𝑡).
1 𝑡 ∞
𝐸𝑝𝐸(𝑡) = ∫0 𝐸𝐸(𝑠)𝑑𝑠, 𝐸𝐸(𝑡) = 𝐸[𝑒(𝑡)] = ∫0 𝑠𝑑𝐺[0,𝑡] (𝑠), 𝐺[0,𝑡] (𝑠) = 𝑃(𝑒(𝑡) ≤ 𝑠).
𝑡

0, 𝑠 ≤ 0
𝑒(𝑡) = max(𝑀𝑡𝑀(𝑡), 0). Now 𝑃(𝑒(𝑡) ≤ 𝑠) = { −𝑟(𝑇−𝑡) .
𝑃(𝑆𝑡 ≤ 𝑆0 + 𝐾(𝑒 − 𝑒 −𝑟𝑇 ) + 𝑠), 𝑠 ≥ 0

The key to a faster computation of CVA stands in the computation of the probability above.
𝜎2
(𝜇− )𝑡+𝜎√𝑡𝑍
We note the last probability 𝑓(𝑠) = 𝑃 (𝑆0 𝑒 2 ≤ 𝑆0 + 𝐾(𝑒 −𝑟(𝑇−𝑡) − 𝑒 −𝑟𝑇 ) + 𝑠) =
𝐾(𝑒−𝑟(𝑇−𝑡)−𝑒−𝑟𝑇 )+𝑠 𝜎2
𝜎2 log(1+ )−(𝜇− 2 )𝑡
(𝜇− )𝑡+𝜎√𝑡𝑍 𝐾(𝑒 −𝑟(𝑇−𝑡) −𝑒 −𝑟𝑇 )+𝑠 𝑆0
𝑃 (𝑒 2 ≤1+ 𝑆0
) = 𝑃 (𝑍 ≤ 𝜎 √𝑡
) = 𝑁(𝑑1 (𝑠)),

𝐾(𝑒−𝑟(𝑇−𝑡) −𝑒−𝑟𝑇 )+𝑠 𝜎2


log(1+ )−(𝜇− 2 )𝑡
𝑆0
Where 𝑑1 (𝑠) = 𝜎 √𝑡
and 𝑍 ∼ 𝑁(0,1).

𝑑2 (𝑠)
− 1
∞ ′ ∞ ∞ 𝑠𝑒 2
So 𝐸[𝑒(𝑡)] = ∫0 𝑠(𝑁(𝑑1 (𝑠)) 𝑑𝑠 = ∫0 𝑠𝑁 ′ (𝑑1 (𝑠))𝑑1′ (𝑠)𝑑𝑠 = ∫0 √2𝜋 𝑑1′ (𝑠)𝑑𝑠 =
𝑑2 (𝑠)
− 1
∞ 𝑠𝑒 2 1 1
∫0 √2𝜋 𝜎 √𝑡 𝑆0 +𝐾(𝑒 −𝑟(𝑇−𝑡) −𝑒 −𝑟𝑇 )+𝑠
𝑑𝑠 = 𝐸𝐸(𝑡).

𝑇 1 𝑡
Now 𝐶𝑉𝐴 = 𝜆(1 − 𝑅𝐵 ) ∫0 𝑒 −(𝜆+𝑟)𝑡 𝐸𝑝𝐸(𝑡)𝑑𝑡, 𝐸𝑃𝐸(𝑡) = 𝑡 ∫0 𝐸𝐸(𝑠)𝑑𝑠.

Remark:

The execution time of the CVA function is 21.14 seconds.

Example: I will plot an example for strike price K=100, zero rate r=2%, 𝜆 = 1, 𝑇 =
1(𝑦𝑒𝑎𝑟𝑠), 𝑅(𝑟𝑒𝑐𝑜𝑣. ) = 0.4, 𝜇 = 0.1, 𝜎 = 0.4 and underlying asset price between 50 and 150$.

Das könnte Ihnen auch gefallen