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 public delegate double Interpolator(Dictionary<double, double> data, double key); public delegate void PathSender(double[] path); public delegate void ProcessStopper(); public delegate int Seeder(); // // Client class Program { static void 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 public class MonteCarloCurveProcessor { private Dictionary<double, double> discountCurve; private Interpolator interpolator; private double maturity; private double tenor; private int nBonds; private int nSteps; private double[] paths; private bool hasDimension = false; private int 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; } public void Process(double[] path) { if (!hasDimension) { nSteps = path.GetLength(0); paths = new double[nSteps]; hasDimension = true; } // add path to all existing paths for (int i = 0; i < nSteps; i++) { paths[i] += path[i]; } counter++; } public void 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)); } } public double GetDF(double T) { // interpolate discount factor from discount curve return interpolator(discountCurve, T); } public double 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 public abstract class MonteCarloBuilder { public abstract Tuple<SDE, Discretization, RandomNumber> build(); } // implementation for hard-coded example public class HardCodedExampleBuilder : MonteCarloBuilder { public override 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); // return new Tuple<SDE, Discretization, RandomNumber>(vasicek, euler, nag); } } // // // // class hierarchy for stochastic differential equations public abstract class SDE { public abstract double drift(double s, double t); public abstract double diffusion(double s, double t); } // implementation for Vasicek one-factor interest rate model public class Vasicek : SDE { private double longTermRate; private double reversionSpeed; private double rateVolatility; // public Vasicek(double longTermRate, double reversionSpeed, double rateVolatility) { this.longTermRate = longTermRate; this.reversionSpeed = reversionSpeed; this.rateVolatility = rateVolatility; } public override double drift(double s, double t) { return reversionSpeed * (longTermRate - s); } public override double diffusion(double s, double t) { return rateVolatility; } } // // // // class hierarchy for discretization schemes public abstract class Discretization { protected SDE sde; protected double initialPrice; protected double expiration; // // read-only properties for initial price and expiration public double InitialPrice { get { return initialPrice; } } public double Expiration { get { return expiration; } } public Discretization(SDE sde, double initialPrice, double expiration) { this.sde = sde; this.initialPrice = initialPrice; this.expiration = expiration; } public abstract double next(double s, double t, double dt, double rnd); } // implementation for Euler discretization scheme public class EulerDiscretization : Discretization { public EulerDiscretization(SDE sde, double initialPrice, double expiration) : base(sde, initialPrice, expiration) { // } public override double 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 public class MonteCarloEngine { private SDE sde; private Discretization discretization; private RandomNumber random; private long paths; private int steps; public event PathSender sendPath; public event 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; } public void run() { // create skeleton for path values double[] path = new double[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 public abstract class UniformRandomGenerator { public abstract double[] Get(int n); } // implementation for NAG basic uniform pseudorandom number generator public class NAG_BasicGenerator : UniformRandomGenerator { private Seeder seeder; private int[] seed; private const int baseGenerator = 1; // hard-coded NAG basic generator private const int subGenerator = 1; // hard-coded sub-generator (not referenced) // public NAG_BasicGenerator(Seeder seeder) { this.seeder = seeder; } public override double[] Get(int n) { seed = new int[] { seeder() }; double[] rnd = new double[n]; int errorState = 0; G05.G05State g05State = new G05.G05State(baseGenerator, subGenerator, seed, out errorState); if (errorState != 0) throw new Exception("NAG : g05State error"); G05.g05sq(n, 0.0, 1.0, g05State, rnd, out errorState); if (errorState != 0) throw new Exception("NAG : g05sq error"); return rnd; } } // // // // class hierarchy for random number processors public abstract class RandomNumber { public readonly UniformRandomGenerator generator; public readonly InverseTransformation transformer; // public RandomNumber(UniformRandomGenerator generator, InverseTransformation transformer = null) { this.generator = generator; this.transformer = transformer; } public abstract double[] GetPath(int nSteps); } // implementation for class processing NAG basic random numbers public class NAG_BasicRandomNumber : RandomNumber { public NAG_BasicRandomNumber(UniformRandomGenerator generator, InverseTransformation transformer = null) : base(generator, transformer) { // initialize base class data members } public override double[] 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 public abstract class InverseTransformation { public abstract double Inverse(double x); } // mapping uniform random variate to standard normal distribution public class StandardNormal : InverseTransformation { public override double Inverse(double x) { const double a1 = -39.6968302866538; const double a2 = 220.946098424521; const double a3 = -275.928510446969; const double a4 = 138.357751867269; const double a5 = -30.6647980661472; const double a6 = 2.50662827745924; // const double b1 = -54.4760987982241; const double b2 = 161.585836858041; const double b3 = -155.698979859887; const double b4 = 66.8013118877197; const double b5 = -13.2806815528857; // const double c1 = -7.78489400243029E-03; const double c2 = -0.322396458041136; const double c3 = -2.40075827716184; const double c4 = -2.54973253934373; const double c5 = 4.37466414146497; const double c6 = 2.93816398269878; // const double d1 = 7.78469570904146E-03; const double d2 = 0.32246712907004; const double d3 = 2.445134137143; const double d4 = 3.75440866190742; // const double p_low = 0.02425; const double 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) throw new Exception("StandardNormal : out of bounds error"); } return c; } } // // // // static library class consisting of seeding methods for uniform random number generators public static class Seed { public static int GetGUIDSeed() { return Math.Abs(Guid.NewGuid().GetHashCode()); } } // // // // static library class for interpolation methods public static class Interpolation { public static double LinearInterpolation(Dictionary<double, double> data, double key) { double value = 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; } } } return value; } } }
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