Sie sind auf Seite 1von 10

Bond and CDS metrics using closed formulas and Monte Carlo simulation

Contents
Intro: ......................................................................................................................................................... 1
Bond valuation .............................................................................................................................................. 2
Price of bond without default time........................................................................................................... 2
Price of bond with default time ................................................................................................................ 2
Other notions ............................................................................................................................................ 2
Piecewise exponential distribution ........................................................................................................... 2
Python code .............................................................................................................................................. 2
CDS valuation ................................................................................................................................................ 6
Terms, price and spread ........................................................................................................................... 6
Python code .............................................................................................................................................. 6
Appendix ....................................................................................................................................................... 7
Exponential random variables .................................................................................................................. 7
Python code .......................................................................................................................................... 7
Indicator function of exponential times ................................................................................................... 7
Python code .......................................................................................................................................... 7
Piecewise exponential model: Monte Carlo simulation ........................................................................... 8
Python code .......................................................................................................................................... 8
Bond price with risk using Monte Carlo simulation .................................................................................. 8
Indicator function of piecewise exponential distribution..................................................................... 8
Premium leg and default leg (CDS) ........................................................................................................... 9
Price of the CDS (Python code) ............................................................................................................... 10

Intro:
My first mini-project is the implementation of pricing methods for bonds and CDS, as well as computing
other measures as yield and spread under the auspices of time-to-default random variables.

The methods considered are:

1) Full pricing;

2) Monte Carlo simulation (APPENDIX).


Bond valuation
Price of bond without default time
If a bond has face value 𝑁, the life-time 𝑇, the starting time 0, the discount rate 𝐵𝑡 (𝑇)is considered
𝑇
continuous, namely 𝐵𝑡 (𝑇) = exp⁡(− ∫𝑡 𝑟𝑠 𝑑𝑠),then 𝑷𝒕 + 𝑨𝑪𝒕 = ∑𝒕𝒎 ≥𝒕 𝑪(𝒕𝒎 )𝑩𝒕 (𝒕𝒎 ) + 𝑵𝑩𝒕 (𝑻) (𝟏)
𝑡−𝑡𝑐
where 𝐶(𝑡𝑚 )𝑡𝑚≥𝑡 are the cash-flows at times 𝑡𝑚 , 𝐴𝐶𝑡 = 𝐶(𝑡𝑐 ) × 365 𝑐
, 𝑡 being the last coupon payment
date with 𝑐 = {𝑚: 𝑡𝑚+1 > 𝑡, 𝑡𝑚 ≥ 𝑡}.

𝑃𝑡 + 𝐴𝐶𝑡 is called the dirty price while 𝑃𝑡 is called clean price.

Price of bond with default time


Suppose the time to default is 𝜏.

The price with accrual will be 𝑷𝒕 + 𝑨𝑪𝒕 = ∑𝒕𝒎 ≥𝒕 𝑪(𝒕𝒎 )𝑩𝒕 (𝒕𝒎 )𝑺𝒕 (𝒕𝒎 ) + 𝑵𝑩𝒕 (𝑻)𝑺𝒕 (𝑻) + 𝑹𝑵 ⋅
𝑻
∫𝒕 𝑩𝒕 (𝒖)𝒇𝒕 (𝒖)𝒅𝒖⁡(𝟐) where R = recovery rate, 𝑁 = nominal (face value) of the bond, 𝐴𝐶𝑡 =accrued
coupon at time t, 𝑆𝑡 (𝑇) = survival function of bond starting at time t and ending at time T.

𝐵𝑡 (𝑢)=discount rate from time 𝑡 to time 𝑢, where 𝑡 < 𝑢.

Other notions
Yield = the value 𝑦 for which the price 𝑃𝑡 + 𝐴𝐶𝑡 = ∑𝑡𝑚≥𝑡 𝑒 −𝑦(𝑡𝑚−𝑡) 𝐶(𝑡𝑚 ) + 𝑒 −𝑦(𝑇−𝑡) 𝑁. (for non-risky
bonds).

For bonds with default time, instead of the term rate 𝑟𝑡 you put 𝑦, or instead of 𝐵𝑡 (𝑢) ≔ 𝑒 −𝑦(𝑡−𝑢) and
solve the equation (2).

The spread is given by 𝑠 = 𝑦 ∗ − 𝑦 where 𝑦 ∗ is the yield of the risky bond and 𝑦⁡is the yield of the non-
risky bond.

Remark: 𝑦 ∗ > 𝑦 and 𝑃𝑡∗ < 𝑃𝑡 where 𝑃𝑡∗ is the price of the risky bond.

Piecewise exponential distribution


Suppose the hazard rate of default is 𝜆(𝑡) = ∑𝑀
𝑚=1 𝜆𝑚 × 1𝑡𝑚−1


<𝑡≤𝑡𝑚 = 𝜆𝑚 if 𝑡𝑚−1 < 𝑡 ≤ 𝑡𝑚 .

The survival function becomes 𝑆(𝑡) = exp⁡(− ∑𝑚−1 ∗ ∗ ∗


𝑘=1 (𝑡𝑘 − 𝑡𝑘−1 ) − 𝜆𝑚 (𝑡 − 𝑡𝑚−1 )) =
∗ ∗
𝑆(𝑡𝑚−1 )exp⁡(−𝜆𝑚 (𝑡 − 𝑡𝑚−1 ))⁡.

Python code
First I devise two functions for non-risky bond prices:

One will have a discrete term structure rate, namely for a division Δ = {0 < 𝑡1 < 𝑡2 < ⋯ < 𝑡𝑚 < ∞} we
have 𝑟𝑡 = 𝑟𝑖+1 , if 𝑡𝑖 < 𝑡 < 𝑡𝑖+1 , 𝑖 = 1, 𝑚 − 1 and 𝑟𝑡 = 𝑟1 , 𝑡 < 𝑡1 , 𝑟𝑡 = 𝑟𝑚+1 , 𝑡 > 𝑡𝑚 .

The second, which is mostly used will assume the term structure rate as a function of time (the first case
being a particular case of the 2nd one).

def bond_price_functions(num):
import numpy as np
import scipy.integrate as integ
def bp1(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
def bp2(FV,c,rate,times):
price = 0
for i in range(len(times)):
if i==0:
price = price+c*times[0]*np.exp(-integ.quad(rate,0,times[i])[0])*FV
else:
price = price+c*(times[i]-times[i-1])*np.exp(-integ.quad(rate,\
0,times[i])[0])*FV
return price+np.exp(-integ.quad(rate,0,times[-1])[0])*FV
dic = dict(zip([1,2],[bp1,bp2]))
return dic[num]

On the other hand, to approximate the yield we will need a pricing function given a
theoretical yield. That function will be used for numerical method (Newton/bisection/secant)
for approximation of the yield.

def bond_price_yield(FV,c,y,times):
"bond price when theoretical yield is given"
f = bond_price_functions(1)
rates = [y]*len(times);
return f(FV,c,rates,times)

def yield_bond(FV,c,rates,times):
f1 = bond_price_functions(1)
P0 = f1(FV,c,rates,times)
h = lambda y:bond_price_yield(FV,c,y,times)-P0
import scipy.optimize as scp
return scp.newton(h,0)
def yield_bond2(FV,c,rate,times):
f1 = bond_price_functions(2)
P0 = f1(FV,c,rate,times)
h = lambda y:bond_price_yield(FV,c,y,times)-P0
import scipy.optimize as scp
return scp.newton(h,0)

def test():
FV,c,times = 100,0.04,[0.5,1]
rate = lambda s:0.03 if s<0.5 else 0.04
print(yield_bond2(FV,c,rate,times))
This function devises a scale function for parameters:
Given the parameters {𝜆1 , … , 𝜆𝑘 }, times {𝑡1 , 𝑡2 , … , 𝑡𝑘−1 } and time 𝑡, we have:
𝜆1 , 𝑡 ≤ 𝑡1
𝜆2 , 𝑡1 < 𝑡 ≤ 𝑡2
𝜆= … .
𝜆𝑘−1 , 𝑡𝑘−2 < 𝑡 ≤ 𝑡𝑘−1
{ 𝜆𝑘 , 𝑡𝑘−1 < 𝑡
def scale_function(times,parameters,t):
"SUppose we have n-1 knots and n parameters, and a time"
"We localize the time t between the times provided and with the interval "
"position found we give the correct parameter"
if len(times)!=len(parameters)-1:
raise TypeError('parameters and times are not compatible')
if t<=times[0]:
return parameters[0],0
elif t>times[-1]:
return parameters[-1],len(times)
else:
for i in range(len(times)-1):
if times[i]<t and t<=times[i+1]:
return parameters[i+1],i+1

The following function, given a set of knots 𝑡1 , 𝑡2 , … , 𝑡𝑚 finds in which interval


([0, 𝑡1 ], [𝑡1 , 𝑡2 ], … , [𝑡𝑚 , +∞) is 𝑡⁡located.

def localize(times,t):
if t<=times[0]:
return len(times)
elif t>times[-1]:
return 0
else:
for i in range(len(times)-1):
if times[i]<t and t<=times[i+1]:
return len(times)-i
#%%
"Survival and density functions of piecewise exponential distribution"
def survival_function(times,parameters,t):
def summ():
import scipy.integrate as integ
f = lambda s:scale_function(times,parameters,s)[0]
return integ.quad(f,0,t)[0]
import numpy as np
return np.exp(-summ())

def density_function(times,parameters,t):
"Works in case of exponential and piecewise exponential distributions"
param = scale_function(times,parameters,t)[0]
return param*survival_function(times,parameters,t)
#%%
def bond_price_closed(c,N,times,rate,lbd,R):
import numpy as np
import scipy.integrate as integ
s=0
for i in range(len(times)-1):
s=s+c*N*(times[i+1]-times[i])*np.exp(-integ.quad(rate,0,times[i])[0]-\
lbd*times[i])
s=s+N*np.exp(-integ.quad(rate,0,times[-1])[0])*np.exp(-lbd*times[-1])
f = lambda u:np.exp(-integ.quad(rate,0,u)[0]-lbd*u)
s = s+lbd*R*N*integ.quad(f,0,times[-1])[0]
return s
#%%
def bond_price_closed2(c,N,times,rate,params,R):
import numpy as np
import scipy.integrate as integ
s= 0
h1 = lambda t:survival_function(times,params,t)
s = s+c*N*times[0]*np.exp(-integ.quad(rate,0,times[0])[0])*h1(times[0])
for i in range(len(times)-1):
s = s+c*N*(times[i+1]-times[i])*np.exp(-integ.quad(rate,0,times[i])[0])\
*h1(times[i+1])
h2 = lambda u:np.exp(-integ.quad(rate,0,u)[0])
#h2 is the discount rate function depending on u
h3 = lambda u:density_function(times,params,u)
#h3 is the density function
h = lambda u:h2(u)*h3(u)
s = s + N*np.exp(-integ.quad(rate,0,times[-1])[0])*h1(times[-1])
s = s+R*N*integ.quad(h,0,times[-1])[0]
return s
#%%
def bond_price_yield2(c,N,times,y,params,R):
"compute the price of a bond when supposedly you have a yield"
rate = lambda s:y
return bond_price_closed2(c,N,times,rate,params,R)

def risky_bond_price_yield(c,N,times,rate,params,R):
P0 = bond_price_closed2(c,N,times,rate,params,R)
h = lambda y:bond_price_yield2(c,N,times,y,params,R)-P0
import scipy.optimize as scp
return scp.newton(h,0)

def risky_bond_spread(c,N,times,rate,params,R):
y1 = risky_bond_price_yield(c,N,times,rate,params,R)#risky yield
y2 = yield_bond2(N,c,rate,times)
return y1-y2
Stochastic bond price test
def sbp2_test():
c,N,times,params,R=0.04,100,[0.5,1],[0.2,0.1,0.4],0.4
rate = lambda s:0.04 if s<0.5 else 0.06
#print(bond_price_MC2(c,N,times,rate,params,R,10000))
print("Bond price with risk",bond_price_closed2(c,N,times,rate,params,R))
h = bond_price_functions(1)
print("Bond price without risk",h(N,c,[0.06,0.06],[0.5,1]))
print("Non-risky yield",yield_bond2(N,c,rate,times))
print("Single yield price",bond_price_yield2(c,N,times,0.07,params,R))
print("Risky yield",risky_bond_price_yield(c,N,times,rate,params,R))
print("Spread",risky_bond_spread(c,N,times,rate,params,R))
sbp2_test()

CDS valuation
Terms, price and spread
Suppose we have a Credit Default Swap with coupon 𝑐, nominal value 𝑁, recovery rate of the
defaultable entity is 𝑅, lifetime 𝑇.

Then the price of a CDS can be written as the Default Leg Value – Premium Leg Value.
𝜏
𝑆𝑉𝑡 (𝐷𝐿) = (1 − 𝑅) × 𝑁 × 1(𝜏 ≤ 𝑇) × 𝑒𝑥𝑝(− ∫𝑡 𝑟𝑠 𝑑𝑠)⁡ while the present value will be 𝑃𝑉𝑡 (𝐷𝐿) =
𝜏
𝐸[(1 − 𝑅)𝑁 × 1(𝜏 ≤ 𝑇) × exp⁡(− ∫𝑡 𝑟𝑠 𝑑𝑠)⁡|𝐹𝑡 ] = (1 − 𝑅) × 𝑁 × 𝐸[1(𝜏 ≤ 𝑇) × 𝐵𝑡 (𝜏)] =
𝑇
(1 − 𝑅) × 𝑁 × ∫𝑡 𝐵𝑡 (𝑢)𝑓𝑡 (𝑢)𝑑𝑢⁡.

The premium leg has the stochastic value given by:


𝑡
𝑆𝑉𝑡 (𝑃𝐿) = ∑𝑡𝑚≥𝑡 𝑐 × 𝑁 × (𝑡𝑚 − 𝑡𝑚−1 ) × 1(𝜏 > 𝑡𝑚 ) × exp⁡(− ∫𝑡 𝑚 𝑟𝑠 𝑑𝑠)⁡.

And the discounted value is 𝑃𝑉𝑡 (𝑃𝐿) = 𝑐 × 𝑁 × ∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑆𝑡 (𝑡𝑚 )𝐵𝑡 (𝑡𝑚 ) where 𝜏 =default time,
𝑆𝑡 (𝑢) = 𝑃(𝜏 > 𝑢|𝜏 > 𝑡).

THE SPREAD of a CDS is the value of 𝑐 that makes the value of the CDS at time 𝑡 (beginning) = 0.
𝑇
𝑠 = ((1 − 𝑅) ∫𝑡 𝐵𝑡 (𝑢)𝑓𝑡 (𝑢)𝑑𝑢)/(∑𝑡𝑚≥𝑡 Δ𝑡𝑚 𝑆𝑡 (𝑡𝑚 )𝐵𝑡 (𝑡𝑚 )) .

Python code
def discounted_leg(N,times,rate,params,R):
import numpy as np
import scipy.integrate as integ
s=0
h1=lambda t:density_function(times,params,t)
h2 = lambda t:np.exp(-integ.quad(rate,0,t)[0])
h = lambda t:h1(t)*h2(t)
return (1-R)*N*integ.quad(h,0,times[-1])[0]

def premium_leg(c,N,times,rate,params,R):
import numpy as np
import scipy.integrate as integ
s= 0
h1 = lambda t:survival_function(times,params,t)
s = s+c*N*times[0]*np.exp(-integ.quad(rate,0,times[0])[0])*h1(times[0])
for i in range(len(times)-1):
s = s+c*N*(times[i+1]-times[i])*np.exp(-integ.quad(rate,0,times[i])[0])\
*h1(times[i+1])
return s

def price_CDS(c,N,times,rate,params,R):
return discounted_leg(N,times,rate,params,R)-premium_leg(c,N,times,rate,params,R)

def spread_CDS(times,rate,params,R):
import numpy as np
import scipy.integrate as integ
s= 0
h1 = lambda t:survival_function(times,params,t)
s1 = s+times[0]*np.exp(-integ.quad(rate,0,times[0])[0])*h1(times[0])
for i in range(len(times)-1):
s1=s1+(times[i+1]-times[i])*np.exp(-integ.quad(rate,0,times[i])[0])\
*h1(times[i+1])
h1 = lambda t:density_function(times,params,t)
h2 = lambda t:np.exp(-integ.quad(rate,0,t)[0])
h = lambda t:h1(t)*h2(t)
s2 = integ.quad(h,0,times[-1])[0]
return s2/s1

Appendix
Exponential random variables
The simulation of the exponential random samples is given by the Inverse Cdf method.
log(1−𝑢)
𝐹(𝑥) = 1 − 𝑒 −𝜆𝑥 = 𝑢 ⇔ 𝑥 = − . Therefore according to the inverse cdf sampling, if 𝑢 ∈ (0,1)is
𝜆
log(1−𝑈)
drawn from Uniform U(0,1) sample then 𝑋 ∼ − ∼ 𝑒𝜆 .
𝜆

Python code
def ind_expo(t,lbd):
import numyp as np
sample = np.random.uniform(0,1)
return -np.log(1-sample)/lbd

Indicator function of exponential times


Suppose I want to simulate the following quantities: For 𝑋~𝑒𝜆 , I want 1𝑋>𝑡 and 1𝑡1 <𝑋<𝑡2 .

Then I take a sample 𝑒1 , 𝑒2 , … 𝑒𝑛 of the distribution 𝑒𝜆 and then check them if are greater than t or not.

Python code
def indicator_expo(t,lbd,size):
def exponential(lbd,size):
import numpy as np
sample = np.random.uniform(0,1,size)
return -np.log(1-sample)/lbd
return (exponential(lbd,size)>t)*1

def indicator_expo2(t1,t2,lbd,size):
def exponential(lbd,size):
import numpy as np
sample = np.random.uniform(0,1,size)
return -np.log(1-sample)/lbd
return ((exponential(lbd,size)>t1)*1)*((exponential(lbd,size)<t2)*1)

Piecewise exponential model: Monte Carlo simulation


Suppose 𝜏 is the default time having the piecewise exponential distribution.


I remind that 𝑆(𝑡) = 𝑆(𝑡𝑚−1 )𝑒 −𝜆𝑚(𝑡−𝑡𝑚−1 ) if 𝑡𝑚−1
∗ ∗
< 𝑡 ≤ 𝑡𝑚 .
1 ∗
𝑆(𝑡𝑚−1 )
∗ ∗ ) ∗
We know that 𝑆(𝜏) ∼ 𝑈. It follows that: 𝑡𝑖 < −𝑡𝑚−1 + 𝜆 log( 𝑢𝑖
) if 𝑆(𝑡𝑚 < 𝑢𝑖 ≤ 𝑆(𝑡𝑚−1 ).
𝑚

Python code
def piece_expo(times,params,u):
def survivals(times,parameters):
l = [survival_function(times,parameters,times[i]) for i in range(len(times))]
return l,l[::-1]
def localize_survivals(times,parameters,u):
"takes a number u between 0 and 1 and identifies the parameter associated"
"with the interval where u is located"
res = survivals(times,parameters)[1]
return localize(res,u)
"one sample of a piecewise exponential distribution"
survivs = survivals(times,params)[0]
import numpy as np
if u>survivs[0]:
return 1/params[0]*np.log(1/u)
elif u<=survivs[-1]:
return times[-1]+1/params[-1]*np.log(survivs[-1]/u)
else:
pos = localize_survivals(times,params,u)
return times[pos-1]+1/params[pos]*np.log(survivs[pos-1]/u)

def simulate_piece_expo(times,params,size):
import numpy as np
u=np.random.uniform(0,1,size)
return list([piece_expo(times,params,u[i]) for i in range(len(u))])

Bond price with risk using Monte Carlo simulation


Indicator function of piecewise exponential distribution
I will simulate 1(𝜏 ≤ 𝑇).

def ind_piece_expo(times,params,t):
import numpy as np
u = np.random.uniform(0,1)
return (piece_expo(times,params,u)<=t)*1
def ind_piece_expo2(times,params,t,size):
import numpy as np
u = np.random.uniform(0,1,size)
return list([(piece_expo(times,params,u[i])<=t)*1 for i in range(size)])
print(ind_piece_expo2([0.5,1],[0.2,0.3,0.4],0.75,100))

Then compute the Monte Carlo price of the bond.

def bond_price_MC2(c,N,times,rate,params,R,size):
import numpy as np
def stochastic_bond_price2():
import numpy as np
import scipy.integrate as integ
s=0
s = s+c*N*times[0]*np.exp(-integ.quad(rate,0,times[0])[0])
for i in range(len(times)-1):
s = s+c*N*(times[i+1]-times[i])*np.exp(-integ.quad(rate,0,times[i])[0])\
*(1-ind_piece_expo(times,params,times[i]))
s=s+N*np.exp(-integ.quad(rate,0,times[-1])[0])\
*(1-ind_piece_expo(times,params,times[-1]))+\
R*N*np.exp(-integ.quad(rate,0,times[-1])[0])*\
ind_piece_expo(times,params,times[-1])
return s
prices = list([stochastic_bond_price2() for i in range(size)])
return np.mean(prices)
Premium leg and default leg (CDS)
𝜏
𝑆𝑉𝑡 (𝐷𝐿) = (1 − 𝑅) × 𝑁 × 1(𝜏 ≤ 𝑇) × 𝑒𝑥𝑝(− ∫𝑡 𝑟𝑠 𝑑𝑠)⁡
𝑡
𝑆𝑉𝑡 (𝑃𝐿) = ∑𝑡𝑚≥𝑡 𝑐 × 𝑁 × (𝑡𝑚 − 𝑡𝑚−1 ) × 1(𝜏 > 𝑡𝑚 ) × exp⁡(− ∫𝑡 𝑚 𝑟𝑠 𝑑𝑠)⁡

def simulate_default_leg2(R,N,times,T,rate,params,size):
import numpy as np
import scipy.integrate as integ
def default_leg():
sample = ind_piece_expo(times,params,T)
return (1-R)*N*(sample<T)*np.exp(-integ.quad(rate,0,sample)[0])
def default_leg_CDS():
sample=[]
for i in range(size):
sample.append(default_leg())
return sample
return default_leg_CDS()

def simulate_prem_leg2(c,N,times,rate,params,size):
import numpy as np
import scipy.integrate as integ
def prem_leg():
s= 0
for i in range(len(times)-1):
s = s+c*N*(times[i+1]-times[i])*ind_piece_expo(times,params,times[i])*\
np.exp(-integ.quad(rate,0,times[i])[0])
return s
sample = []
for i in range(size):
sample.append(prem_leg())
return sample

Price of the CDS (Python code)


def price_CDS(c,N,R,times,rate,params,size):
import numpy as np
return np.mean(simulate_default_leg2(R,N,times,times[-1],rate,params,size\
))-np.mean(simulate_prem_leg2(c,N,times,rate,params,size))

Das könnte Ihnen auch gefallen