Beruflich Dokumente
Kultur Dokumente
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:
2. Coding
Sometimes proofs will be given for certain mathematical or financial engineering facts:
theorems/formulas.
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.
REMARK: Throughout the book, the assumed compounding is continuous if not mentioned otherwise.
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 , … , 𝑡𝑛 .
Suppose we have the following treasury zero rate curve and bond data and we want the value at time 0:
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).
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.
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:
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:
𝑝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()
print(zero_rate_quad_interp(times,rates,0.75,result =
"value"))
zero_rate_quad_interp(times,rates,None,100,result =
"plot")
test3()
Here are several ways to price a bond according to the input given which can be:
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.
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.
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_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:
c. Semi-annual compounding.
d. Quarterly compounding.
Solution:
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:
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:
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:
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:
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
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.
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.
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:
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
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𝑀 .
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:
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
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)]
Solution:
Results:
For the parallel shift we have:
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: 𝒄 = 𝟔. 𝟖𝟕%.
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?
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
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
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
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. 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:
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$.
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.
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 actual LIBOR interest rate observed in the market at time 𝑇1 for the period between times 𝑇1
and 𝑇2 .
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
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.
Exercises:
1. The 6-month, 12-month, 18-month, 24-month zero rates are 4%,4.5%,4.75% and 5% with semiannual
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) , 𝑒𝑡𝑐.
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
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 𝑡𝑚 :
• 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.
Solved examples:
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.
2. Compute the price of the same bond using a closed formula if possible.
Solution:
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 :
• 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.
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)).
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.
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 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.
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 (𝑦𝑒𝑎𝑟𝑠).
Forward valuation
We have 3 types of situations:
1. No 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, 𝑇).
𝑆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:
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.
Example 4:
Suppose now that the interest rate is not flat anymore and we have the following term structure rate:
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)
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
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.
𝑛
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.
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 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
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.
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.
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:
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:
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.
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.
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 𝑚𝑖𝑙𝑙𝑖𝑜𝑛 ($)
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 $.
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?
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 𝑉𝐴 − 𝑄𝑉𝐵 .
Elements:
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:
Solution:
That is, the total amount to be paid will be 2.5 ⋅ 8 = 20$. The payoff will be 20$.
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:
𝑇 𝜆
𝑃𝑉𝑡 (𝐷𝐿) = (1 − 𝑅) ⋅ 𝑁 ⋅ 𝜆 ⋅ ∫𝑡 𝑒 −(𝑟+𝜆)(𝑢−𝑡) 𝑑𝑢 = (1 − 𝑅) ⋅ 𝑁 ⋅ 𝑟+𝜆 ⋅ (1 − 𝑒 −(𝑟+𝜆)(𝑇−𝑡) )
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
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
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])
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)
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 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.
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 = 𝑁 ⋅ ∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑆𝑡 (𝑡𝑚 )𝐵𝑡 (𝑡𝑚 ) = 𝑁 ⋅ ∑𝑡𝑚≥𝑡 ⋅ 𝑒 −𝜆(𝑡𝑚−𝑡) ⋅ 𝑒 −𝑟(𝑡𝑚−𝑡) = ⋅ ∑𝑡𝑚≥𝑡 𝑒 −(𝑟+𝜆)(𝑡𝑚−𝑡)
𝑓𝑟𝑒𝑞 𝑓𝑟𝑒𝑞
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)
𝐷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
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.
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()
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 ).
In case of a continuous dividend rate 𝑞 the price of call and put are respectively:
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:
Implied volatility
In what follows we discuss about implied volatility for calls and put.
• 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)
Digital options. General binomial price depending only on the terminal value
Assume the Black&Scholes model for the underlying asset price.
a. Binomial pricing
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)
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 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")
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:
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)
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.
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.
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
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
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()
4. Covered strategies
We note the following strategies:
In what follows we study the P&L of the afore-mentioned strategies using BS formulas.
Python code:
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:
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.
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.
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
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)
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
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
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:
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.
3. We apply the put-call parity of the formula for the european options on index.
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 − 𝜎√𝑇.
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:
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:
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:
Computations:
We apply Ito’s lemma for 𝒅𝒇(𝑿𝒕 , 𝒀𝒕 ) = 𝒅(𝑿𝒕 𝒀𝒕 ) = 𝒇𝒕 (𝒕, 𝑿𝒕 , 𝒀𝒕 )𝒅𝒕 + 𝒇𝑿 (𝒕, 𝑿𝒕 , 𝒀𝒕 )𝒅𝑿𝒕 +
𝟏
𝒇𝒀 (𝒕, 𝑿𝒕 , 𝒀𝒕 )𝒅𝒀𝒕 + 𝟐 (𝒇𝑿𝑿 𝒅𝑿𝒅𝑿 + 𝒇𝒀𝒀 𝒅𝒀𝒅𝒀) + 𝒇𝑿𝒀 𝒅𝑿𝒅𝒀 .
Therefore:
𝒅(𝑺𝑨 𝑺𝑩 ) = 𝑆𝐴 𝑑𝑆𝐵 + 𝑆𝐵 𝑑𝑆𝐴 + 𝑑𝑆𝐴 𝑑𝑆𝐵 = 𝑆𝐴 ((𝑟𝐵 − 𝑟𝐶 )𝑆𝐵 𝑑𝑡 + 𝜎𝐵 𝑆𝐵 𝑑𝐵𝑡 ) + 𝑆𝐵 ((𝑟𝐴 − 𝑟𝐵 )𝑆𝐴 𝑑𝑡 +
𝜎𝐴 𝑆𝐴 𝑑𝐵𝑡 ) + 𝜎𝐴 𝜎𝐵 𝑆𝐴 𝑆𝐵 𝑑𝑡 = [(𝑟𝐴 − 𝑟𝐵 )𝑆𝐴 𝑆𝐵 + (𝑟𝐵 − 𝑟𝐶 )𝑆𝐴 𝑆𝐵 + 𝜎𝐴 𝜎𝐵 𝑆𝐴 𝑆𝐵 ]𝑑𝑡 + (𝜎𝐴 + 𝜎𝐵 )𝑆𝐴 𝑆𝐵 𝑑𝐵𝑡 .
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.
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.
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.
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.
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 ),
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)
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)
Python code:
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:
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:
Exotic options.
Barrier option pricing
I will touch the pricing of classical barrier options (knock-out, knock-in, double knock) through three
approaches:
• 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.
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.
𝑋𝑡𝑖 is the simulated value of the process 𝑋 at time 𝑡𝑖 , 𝐹ℎ is defined in section b) of Lookback options.
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.
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.
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:
Solution:
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:
Solution:
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.
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:
̂𝑖 )
We have 𝐿 ((𝑀 |𝑋̂) = 𝐿 ((𝑀
̂𝑖 ) ̂𝑖 ≔ 1 (𝑋
),where 𝑀 ̂𝑡 + 𝑋
̂𝑡𝑖+1 +
0≤𝑖≤𝑛−1 0≤𝑖≤𝑛−1 2 𝑖
2 2
̂𝑡 − 𝑋
√((𝑋 ̂ ̂
𝑡𝑖+1 ) − 2𝜎(𝑡𝑖 , 𝑋𝑡𝑖 ) ℎ𝑖 ln(1 − 𝑈𝑖 ))) for independent uniforms 𝑈𝑖 .
𝑖
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))
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 𝑒 −𝜃(𝑡−𝑠) 𝑑𝑊𝑠 .
Method 1 of simulating 𝑿𝒕 .
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).
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
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.
We consider the division Δ = {0 = 𝑡0 < 𝑡1 < ⋯ < 𝑡𝑛 = 𝑇} and we use the next scheme:
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)
𝜎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).
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.
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.
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
𝜎𝐹 √𝑇
.
𝒄 = 𝑷(𝟎, 𝑻)[𝑭𝑩 𝑵(𝒅𝟏 ) − 𝑲𝑵(𝒅𝟐 )] (𝟏), 𝒑 = 𝑷(𝟎, 𝑻)[𝑲𝑵(−𝒅𝟐 ) − 𝑭𝑩 𝑵(−𝒅𝟏 )](𝟐) 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
(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
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:
3M 6M 8M 11M 15M
0.02005017 0.02617048 0.03951596 0.03435346 0.04019297
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:
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 𝑑𝑡.
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.
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.
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.
A: An european put option on the bond + Forward on the bond + present value of the forward price
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).
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.
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%
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
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.
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.
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]
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()
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 − 𝛼)Π𝑘:𝑛𝑆
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 𝑉𝑎𝑅𝛼 .
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.
The formula in this case for 𝑉𝑎𝑅𝛼 = −𝜇(𝑅) + Φ−1 (𝛼) ⋅ 𝜎(𝑅).
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)
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')
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
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()
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:
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.
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.
a. We have 𝑛𝑆 simulations for the process 𝑆, at each time 𝑡𝑖 , 𝑡𝑖 ∈ {𝑡0 = 0 < 𝑡1 < ⋯ < 𝑡𝑁 = 𝑇}.
𝑗
b. We have 𝑀𝑡𝑀𝑗 (𝑡𝑖 ) = 𝑉(𝑡𝑖 , 𝑆𝑡𝑖 ) − 𝑉(0, 𝑆0 )
1 1
e. 𝐸𝑃𝐸(0, 𝑡𝑖 ) = (𝑅𝑖𝑒𝑚𝑎𝑛𝑛 𝑎𝑝𝑝𝑟𝑜𝑥𝑖𝑚𝑎𝑡𝑖𝑜𝑛) ⋅ ∑𝑖𝑘=1 𝐸𝐸(𝑡𝑘 )Δ𝑡𝑘 = ⋅ ∑𝑖𝑘=1 𝐸𝐸(𝑡𝑘 )
𝑡𝑖 𝑖
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.
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 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
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%.
2. With respect to the interest rate. Data is almost the same, but instead of varying 𝑆0 , which
determines the path, we vary 𝑟.
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:
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 (𝑠)
− 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:
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$.