The process of creating discount factor and forward rate curves with traditional bootstrapping algorithm was presented in the last post. In this post we are going to do the same thing, but following a bit different approach.
There are two ways to use the market data when creating these curves. The first approach is to fit the curve exactly into the current market data (calibration, bootstrapping). Another approach is to select an interest rate model, estimate all required parameters from the current market data and finally build the curves by using Monte Carlo method, for example. The advantage of the first approach is, that we are able to reprice (or replicate) the market with the resulting curves, while this (almost surely) will never happen with the second approach.
Monte Carlo method itself is widely used for a wide array of complex calculation tasks. Just for an example, calculating CVA for an interest rate swap requires us to calculate swap PV for a number of chosen timepoints happening in the future, before the expiration of a swap contract. For this complex calculation task, we can select an interest rate model and use Monte Carlo to calculate swap PV for any point in time in the future.
YELLOW
First of all, Client is requesting MonteCarloEngine object to simulate the short rate paths. MonteCarloEngine is sending all simulated paths for MonteCarloCurveProcessor, using delegate method. For the simulation task, MonteCarloEngine needs (aggregation) three different objects : RandomNumber, SDE and Discretization. These objects are going to be created by HardCodedExampleBuilder (aggregation), which is a specialization for abstract MonteCarloBuilder class.
BLUE
All the needed information concerning the short rate model is embedded in EulerDiscretization (specialization for abstract Discretization) and Vasicek (specialization for abstract SDE) objects. These objects are aggregated in MonteCarloEngine.
GREEN
For performing Monte Carlo simulation task, random numbers are needed. NAG_BasicRandomNumber object (specialization for abstract RandomNumber) is aggregated in MonteCarloEngine. This object is using (aggregation) two other objects to perform the task. NAG_BasicGenerator (specialization for abstract UniformRandomGenerator) and StandardNormal (specialization for abstract InverseTransformation).
With the design used in the program, a client is able to change
So, we are having a lot of flexibility with this presented design.
It should be noted, that the basic task of this program is to simulate paths for any given (one-factor) stochastic differential equation, using a given discretization scheme. MonteCarloEngine is kind of a Mediator object, which is performing this task. MonteCarloCurveProcessor object, which has been added into this design later, is only using the results generated by MonteCarloEngine for creating a set of discount factors and calculating forward rates, when requested by the client.
When using OLS method, the task can easily be performed in Excel using Data Analysis tools. In the screenshot presented below, I have replicated the parameter estimation using Excel regression tool and example dataset from the website given above.
The parameter estimation procedure with OLS method is straightforward in Excel and should be relatively easy to perform on any new chosen set of market data. Needless to say, the real deal in the parameter estimation approach is linked with the market data sample properties. The central question is the correct period to include into a sample for estimating such parameters.
Thanks a lot for using your precious time and reading my blog.
-Mike Juniperhill
There are two ways to use the market data when creating these curves. The first approach is to fit the curve exactly into the current market data (calibration, bootstrapping). Another approach is to select an interest rate model, estimate all required parameters from the current market data and finally build the curves by using Monte Carlo method, for example. The advantage of the first approach is, that we are able to reprice (or replicate) the market with the resulting curves, while this (almost surely) will never happen with the second approach.
Monte Carlo method itself is widely used for a wide array of complex calculation tasks. Just for an example, calculating CVA for an interest rate swap requires us to calculate swap PV for a number of chosen timepoints happening in the future, before the expiration of a swap contract. For this complex calculation task, we can select an interest rate model and use Monte Carlo to calculate swap PV for any point in time in the future.
PROJECT OUTCOME
In this project, we are following this second approach and use Vasicek one-factor interest rate model in our example. The resulting C# program uses Monte Carlo for simulating discount curve (a set of zero-coupon bonds). After this, client can request discount factor DF(0, T) for any given maturity or forward rate FWD(t, T) for any given period in the future. The resulting example program is a console project. However, with all the information available in this blog, one should be able to interface the program with Excel, if so desired. Needless to say, the part of the program which actually creates the curves can be used as a part of any other C# project.PROGRAM DESIGN
For the purpose of getting some conceptual overview, about how the objects in the program are connected with each other, a rough UML class diagram is shown in the picture below.YELLOW
First of all, Client is requesting MonteCarloEngine object to simulate the short rate paths. MonteCarloEngine is sending all simulated paths for MonteCarloCurveProcessor, using delegate method. For the simulation task, MonteCarloEngine needs (aggregation) three different objects : RandomNumber, SDE and Discretization. These objects are going to be created by HardCodedExampleBuilder (aggregation), which is a specialization for abstract MonteCarloBuilder class.
BLUE
All the needed information concerning the short rate model is embedded in EulerDiscretization (specialization for abstract Discretization) and Vasicek (specialization for abstract SDE) objects. These objects are aggregated in MonteCarloEngine.
GREEN
For performing Monte Carlo simulation task, random numbers are needed. NAG_BasicRandomNumber object (specialization for abstract RandomNumber) is aggregated in MonteCarloEngine. This object is using (aggregation) two other objects to perform the task. NAG_BasicGenerator (specialization for abstract UniformRandomGenerator) and StandardNormal (specialization for abstract InverseTransformation).
With the design used in the program, a client is able to change
- uniform random number generator
- the class, which is processing generated uniform randon numbers
- inverse transformation method for mapping generated uniform random numbers into a given probability distribution
- stochastic differential equation
- discretization scheme for a given stochastic differential equation
So, we are having a lot of flexibility with this presented design.
It should be noted, that the basic task of this program is to simulate paths for any given (one-factor) stochastic differential equation, using a given discretization scheme. MonteCarloEngine is kind of a Mediator object, which is performing this task. MonteCarloCurveProcessor object, which has been added into this design later, is only using the results generated by MonteCarloEngine for creating a set of discount factors and calculating forward rates, when requested by the client.
THE PROGRAM
Create a new C# console project and copyPaste the following program into a new cs-file. Remember to add reference to NAG .NET library. If you are not registrated NAG user, create a new implementation for UniformRandomGenerator and completely remove the current uniform generator implementation.using System;
using System.Collections.Generic;
using System.Linq;
using NagLibrary;
namespace RandomDesign
{
// public delegates
publicdelegatedouble Interpolator(Dictionary<double, double> data, double key);
publicdelegatevoid PathSender(double[] path);
publicdelegatevoid ProcessStopper();
publicdelegateint Seeder();
//
// Client
classProgram
{
staticvoid Main(string[] args)
{
try
{
// create required objects, data and run monte carlo engine
double tenor = 0.25;
double maturity = 10.0;
int paths = 250000;
int steps = 1000;
MonteCarloEngine engine = new MonteCarloEngine(new HardCodedExampleBuilder(), paths, steps);
MonteCarloCurveProcessor curve = new MonteCarloCurveProcessor(maturity, tenor, Interpolation.LinearInterpolation);
engine.sendPath += curve.Process;
engine.stopProcess += curve.Calculate;
engine.run();
//
// after engine run, discount factors and simply-compounded
// forward rates can be requested by the client
Console.WriteLine("printing quarterly discount factors >");
int n = Convert.ToInt32(maturity / tenor);
for (int i = 1; i <= n; i++)
{
double T = (tenor * i);
double df = curve.GetDF(T);
Console.WriteLine(Math.Round(df, 4));
}
Console.WriteLine();
//
Console.WriteLine("printing quarterly forward rates >");
for (int i = 2; i <= n; i++)
{
double t = (tenor * (i - 1));
double T = (tenor * i);
double f = curve.GetFWD(t, T);
Console.WriteLine(Math.Round(f, 4));
}
Console.WriteLine();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
//
//
//
// class for :
// - processing stochastic paths sent by monte carlo engine
// - calculating zero-coupon bond prices using collected information on stochastic paths
// - retrieving discount factors or simply-compounded forward rates for any given period
publicclassMonteCarloCurveProcessor
{
private Dictionary<double, double> discountCurve;
private Interpolator interpolator;
privatedouble maturity;
privatedouble tenor;
privateint nBonds;
privateint nSteps;
privatedouble[] paths;
privatebool hasDimension = false;
privateint counter;
//
public MonteCarloCurveProcessor(double maturity, double tenor, Interpolator interpolator)
{
discountCurve = new Dictionary<double, double>();
this.interpolator = interpolator;
this.maturity = maturity;
this.tenor = tenor;
nBonds = Convert.ToInt32(this.maturity / this.tenor);
counter = 0;
}
publicvoid Process(double[] path)
{
if (!hasDimension)
{
nSteps = path.GetLength(0);
paths = newdouble[nSteps];
hasDimension = true;
}
// add path to all existing paths
for (int i = 0; i < nSteps; i++)
{
paths[i] += path[i];
}
counter++;
}
publicvoid Calculate()
{
// calculate the average path
for (int i = 0; i < nSteps; i++)
{
paths[i] /= counter;
}
// integrate zero-coupon bond prices
double dt = maturity / (nSteps - 1);
int takeCount = Convert.ToInt32(tenor / dt);
for (int i = 0; i < nBonds; i++)
{
double integral = paths.ToList<double>().Take(takeCount * (i + 1) + 1).Sum();
discountCurve.Add(tenor * (i + 1), Math.Exp(-integral * dt));
}
}
publicdouble GetDF(double T)
{
// interpolate discount factor from discount curve
return interpolator(discountCurve, T);
}
publicdouble GetFWD(double t, double T)
{
// using discount factors, calculate forward discount
// factor and convert it into simply-compounded forward rate
double df_T = interpolator(discountCurve, T);
double df_t = interpolator(discountCurve, t);
double fdf = (df_T / df_t);
return ((1 / fdf) - 1) / (T - t);
}
}
//
//
//
// class hierarchy for simulation object builders
publicabstractclassMonteCarloBuilder
{
publicabstract Tuple<SDE, Discretization, RandomNumber> build();
}
// implementation for hard-coded example
publicclassHardCodedExampleBuilder : MonteCarloBuilder
{
publicoverride Tuple<SDE, Discretization, RandomNumber> build()
{
// create objects for generating standard normally distributed random numbers
UniformRandomGenerator uniformRandom = new NAG_BasicGenerator(Seed.GetGUIDSeed);
InverseTransformation transformer = new StandardNormal();
RandomNumber nag = new NAG_BasicRandomNumber(uniformRandom, transformer);
//
// create stochastic differential equation and discretization objects
SDE vasicek = new Vasicek(0.1, 0.29, 0.0185);
Discretization euler = new EulerDiscretization(vasicek, 0.02, 10.0);
//
returnnew Tuple<SDE, Discretization, RandomNumber>(vasicek, euler, nag);
}
}
//
//
//
// class hierarchy for stochastic differential equations
publicabstractclassSDE
{
publicabstractdouble drift(double s, double t);
publicabstractdouble diffusion(double s, double t);
}
// implementation for Vasicek one-factor interest rate model
publicclassVasicek : SDE
{
privatedouble longTermRate;
privatedouble reversionSpeed;
privatedouble rateVolatility;
//
public Vasicek(double longTermRate, double reversionSpeed, double rateVolatility)
{
this.longTermRate = longTermRate;
this.reversionSpeed = reversionSpeed;
this.rateVolatility = rateVolatility;
}
publicoverridedouble drift(double s, double t)
{
return reversionSpeed * (longTermRate - s);
}
publicoverridedouble diffusion(double s, double t)
{
return rateVolatility;
}
}
//
//
//
// class hierarchy for discretization schemes
publicabstractclassDiscretization
{
protected SDE sde;
protecteddouble initialPrice;
protecteddouble expiration;
//
// read-only properties for initial price and expiration
publicdouble InitialPrice { get { return initialPrice; } }
publicdouble Expiration { get { return expiration; } }
public Discretization(SDE sde, double initialPrice, double expiration)
{
this.sde = sde;
this.initialPrice = initialPrice;
this.expiration = expiration;
}
publicabstractdouble next(double s, double t, double dt, double rnd);
}
// implementation for Euler discretization scheme
publicclassEulerDiscretization : Discretization
{
public EulerDiscretization(SDE sde, double initialPrice, double expiration)
: base(sde, initialPrice, expiration)
{
//
}
publicoverridedouble next(double s, double t, double dt, double rnd)
{
return s + sde.drift(s, t) * dt + sde.diffusion(s, t) * Math.Sqrt(dt) * rnd;
}
}
//
//
//
// mediator class for creating stochastic paths
publicclassMonteCarloEngine
{
private SDE sde;
private Discretization discretization;
private RandomNumber random;
privatelong paths;
privateint steps;
publicevent PathSender sendPath;
publicevent ProcessStopper stopProcess;
//
public MonteCarloEngine(MonteCarloBuilder builder, long paths, int steps)
{
Tuple<SDE, Discretization, RandomNumber> components = builder.build();
this.sde = components.Item1;
this.discretization = components.Item2;
this.random = components.Item3;
this.paths = paths;
this.steps = steps;
}
publicvoid run()
{
// create skeleton for path values
double[] path = newdouble[steps + 1];
double dt = discretization.Expiration / steps;
double vOld = 0.0; double vNew = 0.0;
//
for (int i = 0; i < paths; i++)
{
// request random numbers for a path
double[] rand = random.GetPath(steps);
path[0] = vOld = discretization.InitialPrice;
//
for (int j = 1; j <= steps; j++)
{
// get next path value using a given discretization scheme
vNew = discretization.next(vOld, (dt * j), dt, rand[j - 1]);
path[j] = vNew; vOld = vNew;
}
sendPath(path); // send one path to client (MonteCarloCurveProcessor) for processing
}
stopProcess(); // notify client (MonteCarloCurveProcessor)
}
}
//
//
//
// class hierarchy for uniform random generators
publicabstractclassUniformRandomGenerator
{
publicabstractdouble[] Get(int n);
}
// implementation for NAG basic uniform pseudorandom number generator
publicclassNAG_BasicGenerator : UniformRandomGenerator
{
private Seeder seeder;
privateint[] seed;
privateconstint baseGenerator = 1; // hard-coded NAG basic generator
privateconstint subGenerator = 1; // hard-coded sub-generator (not referenced)
//
public NAG_BasicGenerator(Seeder seeder)
{
this.seeder = seeder;
}
publicoverridedouble[] Get(int n)
{
seed = newint[] { seeder() };
double[] rnd = newdouble[n];
int errorState = 0;
G05.G05State g05State = new G05.G05State(baseGenerator, subGenerator, seed, out errorState);
if (errorState != 0) thrownew Exception("NAG : g05State error");
G05.g05sq(n, 0.0, 1.0, g05State, rnd, out errorState);
if (errorState != 0) thrownew Exception("NAG : g05sq error");
return rnd;
}
}
//
//
//
// class hierarchy for random number processors
publicabstractclassRandomNumber
{
publicreadonly UniformRandomGenerator generator;
publicreadonly InverseTransformation transformer;
//
public RandomNumber(UniformRandomGenerator generator,
InverseTransformation transformer = null)
{
this.generator = generator;
this.transformer = transformer;
}
publicabstractdouble[] GetPath(int nSteps);
}
// implementation for class processing NAG basic random numbers
publicclassNAG_BasicRandomNumber : RandomNumber
{
public NAG_BasicRandomNumber(UniformRandomGenerator generator,
InverseTransformation transformer = null)
: base(generator, transformer)
{
// initialize base class data members
}
publicoverridedouble[] GetPath(int nSteps)
{
// create and return one random number path
double[] path = base.generator.Get(nSteps);
//
// conditionally, map uniform random numbers to a given distribution
if (base.transformer != null)
{
for (int i = 0; i < nSteps; i++)
{
path[i] = base.transformer.Inverse(path[i]);
}
}
return path;
}
}
//
//
//
// class hierarchy for all inverse transformation classes
publicabstractclassInverseTransformation
{
publicabstractdouble Inverse(double x);
}
// mapping uniform random variate to standard normal distribution
publicclassStandardNormal : InverseTransformation
{
publicoverridedouble Inverse(double x)
{
constdouble a1 = -39.6968302866538; constdouble a2 = 220.946098424521;
constdouble a3 = -275.928510446969; constdouble a4 = 138.357751867269;
constdouble a5 = -30.6647980661472; constdouble a6 = 2.50662827745924;
//
constdouble b1 = -54.4760987982241; constdouble b2 = 161.585836858041;
constdouble b3 = -155.698979859887; constdouble b4 = 66.8013118877197;
constdouble b5 = -13.2806815528857;
//
constdouble c1 = -7.78489400243029E-03; constdouble c2 = -0.322396458041136;
constdouble c3 = -2.40075827716184; constdouble c4 = -2.54973253934373;
constdouble c5 = 4.37466414146497; constdouble c6 = 2.93816398269878;
//
constdouble d1 = 7.78469570904146E-03; constdouble d2 = 0.32246712907004;
constdouble d3 = 2.445134137143; constdouble d4 = 3.75440866190742;
//
constdouble p_low = 0.02425; constdouble p_high = 1.0 - p_low;
double q = 0.0; double r = 0.0; double c = 0.0;
//
if ((x > 0.0) && (x < 1.0))
{
if (x < p_low)
{
// rational approximation for lower region
q = Math.Sqrt(-2.0 * Math.Log(x));
c = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6)
/ ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
}
if ((x >= p_low) && (x <= p_high))
{
// rational approximation for middle region
q = x - 0.5; r = q * q;
c = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q
/ (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1);
}
if (x > p_high)
{
// rational approximation for upper region
q = Math.Sqrt(-2 * Math.Log(1 - x));
c = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6)
/ ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
}
}
else
{
// throw if given x value is out of bounds
// (less or equal to 0.0, greater or equal to 1.0)
thrownew Exception("StandardNormal : out of bounds error");
}
return c;
}
}
//
//
//
// static library class consisting of seeding methods for uniform random number generators
publicstaticclassSeed
{
publicstaticint GetGUIDSeed() { return Math.Abs(Guid.NewGuid().GetHashCode()); }
}
//
//
//
// static library class for interpolation methods
publicstaticclassInterpolation
{
publicstaticdouble LinearInterpolation(Dictionary<double, double> data, double key)
{
doublevalue = 0.0;
int n = data.Count;
//
// boundary checkings
if ((key < data.ElementAt(0).Key) || (key > data.ElementAt(data.Count - 1).Key))
{
if (key < data.ElementAt(0).Key) value = data.ElementAt(0).Value;
if (key > data.ElementAt(data.Count - 1).Key) value = data.ElementAt(data.Count - 1).Value;
}
else
{
// iteration through all existing elements
for (int i = 0; i < n; i++)
{
if ((key >= data.ElementAt(i).Key) && (key <= data.ElementAt(i + 1).Key))
{
double t = data.ElementAt(i + 1).Key - data.ElementAt(i).Key;
double w = (key - data.ElementAt(i).Key) / t;
value = data.ElementAt(i).Value * (1 - w) + data.ElementAt(i + 1).Value * w;
break;
}
}
}
returnvalue;
}
}
}
VASICEK PARAMETERS ESTIMATION
Parameters (long-term mean rate, reversion speed and rate volatility) for Vasicek model have to be estimated from the market data. For this task, we can use OLS method or Maximum Likelihood method. In this case, the both methods should end up with the same set of result parameters. Concrete hands-on example on parameter estimation with the both methods for Ornstein-Uhlenbeck model (Vasicek), has been presented in this great website by Thijs van den Berg.When using OLS method, the task can easily be performed in Excel using Data Analysis tools. In the screenshot presented below, I have replicated the parameter estimation using Excel regression tool and example dataset from the website given above.
The parameter estimation procedure with OLS method is straightforward in Excel and should be relatively easy to perform on any new chosen set of market data. Needless to say, the real deal in the parameter estimation approach is linked with the market data sample properties. The central question is the correct period to include into a sample for estimating such parameters.
Thanks a lot for using your precious time and reading my blog.
-Mike Juniperhill
