Quantcast
Channel: Path
Viewing all 83 articles
Browse latest View live

Interfacing C# and VBA with Excel-DNA (no intellisense support)

$
0
0
Some readers might be aware, that there used to be a posting under this name before. However, after getting some comments and advices directly from Govert Van Drimmelen (inventor, developer and author of Excel-DNA), and further studying the issue just a bit more, I soon realized that there is lot more than meets the eye, when dealing with COM Server classes.

Even my original implementation was technically working correctly, it was potentially dangerous way to implement this scheme. Finally, I made a decision to re-implement and re-write the whole thing again. Now, I have divided everything to two separate postings.
  • This first posting presents an easy way to create interface between C# and VBA, but having no intellisense support for your COM server class used in VBA.
  • The second posting will present "not so easy" way to do the same thing and having full intellisense support for your COM server class used in VBA.

PROJECT RESULT

The end result of this small example project will be a C# COM Server class, which can be used in VBA without intellisense support. For this project, I have been using Visual Studio 2010 Express with Framework 4.0.

PREPARATORY TASKS

Download and unzip Excel-DNA Version 0.30 zip file to be ready when needed. There is also a step-by-step word documentation file available within the distribution folder. In this project, we are going to follow these instructions.

STEP ONE: C# program


Create a new class project and name it to be ExcelInterface. Also, change the class name to be InterfaceFunctions. CopyPaste the following code into your InterfaceFunctions.cs and save the project. At this point, do not mind about all those error messages. As we add reference to ExcelDnaIntegration.dll file, those errors will disappear.

using System;
using ExcelDna.Integration;
using ExcelDna.ComInterop;
using System.Runtime.InteropServices;
//
namespace ExcelInterface
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[ProgId("CSharp_functionLibrary")]
publicclassInterfaceFunctions
{
publicdouble add(double x, double y)
{
return x + y;
}
}
//
[ComVisible(false)]
classExcelAddin : IExcelAddIn
{
publicvoid AutoOpen()
{
ComServer.DllRegisterServer();
}
publicvoid AutoClose()
{
ComServer.DllUnregisterServer();
}
}
}

A couple of notes about the class implementations. Note the use attributes and enumeration in our InterfaceFunctions class. ProgIDs are automatically generated for a class by combining the namespace with the type name. However, with ProgID attribute we can set our own ProgId to be used, when calling class from VBA. With ComVisible attribute, we can control the class visibility to COM. With ClassInterfaceType enumeration we can set the type of class interface that is generated for a class. It might be worth of checking those MSDN links if this topic is completely new. Finally, by adding class ExcelAddin (implements IExcelAddin interface), we are enabling the registration and unregistration of our dll file to be done programmatically, without the use of regsvr32.exe from command prompt.

STEP TWO: Excel-DNA


Add reference to Excel-DNA library (Project - Add reference - Browse - \\ExcelDna.Integration.dll) and click OK. This dll file is inside the distribution folder what we just downloaded from Excel-DNA website. From the properties of this reference, set Copy Local to be False.

Add new file as text file to project (Project - Add new item - Text file) and name it to be ExcelInterface.dna. CopyPaste the following xml code into this file.

<DnaLibrary Name="ExcelInterface" RuntimeVersion="v4.0">
<ExternalLibrary Path="ExcelInterface.dll"ComServer="true" />
</DnaLibrary>

From the properties of this dna file, set Copy to Output Directory to be Copy if newer.

Next, from the downloaded Excel-DNA folder (Distribution), copy ExcelDna.xll file into your project folder (\\Projects\ExcelInterface\ExcelInterface) and rename it to be ExcelInterface .xll. Then, add this xll file into your current project (Project - Add existing item). At this point, it might be that you do not see anything else, except cs files on this window. From drop down box on the bottom right corner of this window, select All files and you should see ExcelInterface.xll file what we just pasted into this ExcelInterface folder. Select this file and press Add. Finally, from the properties of this xll file, set Copy to Output Directory to be Copy if newer.

Build the solution. Everything should have gone well, without any errors or warnings. At this point, my \\ExcelInterface\bin\Release folder looks like the following.










At this point, we are done with C#.

STEP THREE: VBA


Open a new Excel workbook. While this workbook is open, doubleClick ExcelInterface.xll file in your \\ExcelInterface\bin\Release folder. After this, our C# function (add) is available to be used in VBA. Next, open VBA editor, insert a new standard module and copyPaste the following program.

Option Explicit
'
Sub tester()
'
DimlibAsObject: Setlib = CreateObject("CSharp_functionLibrary")
Debug.Print lib.Add(12, 13)
Setlib = Nothing
EndSub
'

If we run this small program, it prints the value 25 into editor immediate window. This confirms, that our COM Server is working correctly. The only thing really missing is intellisense support for COM Server class methods. If you are able to live without it, then congratulations - we are done.

LATE-BINDING SCHEME


In our example VBA program above, we are creating instance of our COM Server class by creating an object of type "CSharp_functionLibrary" (set with our custom ProgID attribute) by using VBA CreateObject function. In the case we would not be using ProgID attribute, we would create an object of type "NamespaceName.ClassName".

This scheme is called late-binding. Lacking intellisense support is one downside of this scheme. Another downside is a bit slower execution time, due to some run-time checkings made by compiler. This issue might or might not be relevant for your program. Anyway, even there is no intellisense support available, we have an access to all COM-visible methods just by calling those with the correct syntax. If the syntax is not correct, compiler throws an error. Big advantage of this scheme is, that it is relatively easy to implement. Even bigger advantage comes with the issues concerning version handling safety. This advantage will be explicit after you check out my next posting (intellisense support).

If you are unable to live without intellisense support, things are getting a bit more complicated. I try to open up and present how to add such intellisense support to be used in VBA in the next posting.

FINAL NOTES

For learning more things about Excel-DNA, check out its homepage. Getting more information and examples with your problems, the main source is Excel-DNA google group. For those who would like to see useful examples using Excel-DNA in financial programs, there is an excellent book C# for Financial Markets (chapter 22) written by Daniel Duffy and Andrea Germani (published 2013). Finally, Excel-DNA is an open-source project, and we (the happy users) can invest its future development by making a donation.

Thanks for reading again and good luck!

-Mike

Interfacing C# and VBA with Excel-DNA (with intellisense support)

$
0
0
In my previous posting, I was presenting how to create C# COM Server class and use that class in VBA without intellisense support. If this topic is completely new for you, I suggest that you check out first that posting before starting to work with this one. This posting will present a bit more complex way to do the same thing, but also including full intellisense support for your COM server class to be used in VBA.

PROJECT RESULT

The end result of this small example project will be a C# COM Server class, which can be used in VBA with full intellisense support. For this project, I have been using Visual Studio 2010 Express with Framework 4.0.

PREPARATORY TASKS

Download and unzip Excel-DNA Version 0.30 zip file to be ready when needed. There is also a step-by-step word documentation file available within the distribution folder. In this project, we are going to follow these instructions.

STEP ONE: C# program


Create a new class project and name it to be XLServer. Also, change the class name to be XLServer. CopyPaste the following code into your XLServer.cs and save the project. At this point, do not mind about all those error messages. As we add reference to ExcelDnaIntegration.dll file, those errors will disappear.

using System;
using ExcelDna.Integration;
using ExcelDna.ComInterop;
using System.Runtime.InteropServices;
//
namespace XLServer
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
publicclassCOMLibrary
{
publicdouble add(double x, double y)
{
return x + y;
}
}
//
[ComVisible(false)]
classExcelAddin : IExcelAddIn
{
publicvoid AutoOpen()
{
ComServer.DllRegisterServer();
}
publicvoid AutoClose()
{
ComServer.DllUnregisterServer();
}
}
}

By adding class ExcelAddin (implements IExcelAddin interface), we are enabling the registration and unregistration of our dll file to be done programmatically, without the use of regsvr32.exe from command prompt.

Note also, that ClassInterfaceType attribute for our public COMLibrary class has been set to be AutoDual. This means, that a dual class interface is generated for this class and also exposed to COM. Class type information is produced for the class interface and published in type library file. As a part of this posting, we will create this type library file.

STEP TWO: Excel-DNA


Add reference to Excel-DNA library (Project - Add reference - Browse - \\ExcelDna.Integration.dll) and click OK. This dll file is inside the distribution folder what we just downloaded from Excel-DNA website. From the properties of this reference, set Copy Local to be True.

Add new file as text file to project (Project - Add new item - Text file) and name it to be XLServer.dna. CopyPaste the following xml code into this file.

<DnaLibrary Name="XLServer" RuntimeVersion="v4.0">
<ExternalLibrary Path="XLServer.dll"ComServer="true" />
</DnaLibrary>

From the properties of this dna file, set Copy to Output Directory to be Copy if newer.

Next, from the downloaded Excel-DNA folder (Distribution), copy ExcelDna.xll file into your project folder (\\Projects\XLServer\XLServer) and rename it to be XLServer.xll. Then, add this xll file into your current project (Project - Add existing item). At this point, it might be that you do not see anything else, except cs files on this window. From drop down box on the bottom right corner of this window, select All files and you should see XLServer.xll file what we just pasted into this XLServer folder. Select this file and press Add. Finally, from the properties of this xll file, set Copy to Output Directory to be Copy if newer.

Build the solution. Everything should have gone well, without any errors or warnings. At this point, my \\XLServer\bin\Release folder looks like the following.




At this point, we are practically done with C#.

You may also notice, that ExcelDna.Integration.dll file has been copied into our release folder. This has not been an accident. To enable intellisense to be used, we need to create type library file (tlb) for our COM Server class by using Type library exporter in Visual Studio Command Prompt. Type library exporter (tlbexp.exe) needs to have that dll file in order to find all program parts it needs when creating type library file.

STEP THREE: Creating Type Library


Next, we have to open our VS command prompt (Start - All Programs - Microsoft Visual Studio 2010 Express - Visual Studio Command Prompt 2010). Before opening, move your cursor over the name (Visual Studio Command Prompt 2010) and by right-clicking, select and open properties window. On properties, replace the existing string in Start in text box with the complete address of our current project release folder (\\VS2010\Projects\XLServer\XLServer\bin\Release).







































Press Apply and OK. By setting this start path string, we are effectively opening VS command prompt directly in our project release folder. Next, open VS command prompt and write the command "tlbexp XLServer.dll" in order to create type library into project release folder.





















Press enter and the following information will confirm that type library file (XLServer.tlb) has been created into current project release folder.





















At this point, we can close our Visual Studio command prompt. Let us take a look at our release folder. My folder has the following files.













We can see, that XLServer.tlb file has been successfully created.

STEP FOUR: VBA


Open a new Excel workbook, open VBA editor and create reference to COM class (Tools - References - Browse).




















Select XLServer.tlb (from our release folder) and press Open. Insert a new standard VBA module and copyPaste the following code.

Option Explicit
'
Sub tester()
'
DimlibAsNew COMLibrary
Debug.Print lib.Add(12, 13)
Setlib = Nothing
EndSub
'

If we run this small program, it prints the value 25 into editor immediate window. This confirms, that our COM Server is working correctly. Moreover, we have full intellisense support for COM Server class methods now available. At this point, we are done.

Early-binding scheme


In our example VBA program above, we are creating instance of our COM Server class by creating an object of type COMLibrary (C# class name) without using VBA CreateObject function. This scheme is called early-binding. The biggest advantage is intellisense support. Another advantage of this scheme is fast execution time (no compiler run-time checkings made). The first downside of this scheme is complex implementation compared to Late-binding scheme. Even bigger downside comes with the issues, concerning version handling safety.

STEP FIVE: Version handling issues - a fair warning


Let us assume, that we have done everything successfully and our COM Server is working well. Now, we would like to implement a new subtract method to our COM Server class. Let us open our project and implement this method. The content of our new XLServer.cs is given below.

using System;
using ExcelDna.Integration;
using ExcelDna.ComInterop;
using System.Runtime.InteropServices;
//
namespace XLServer
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
publicclassCOMLibrary
{
publicdouble add(double x, double y)
{
return x + y;
}
publicdouble subtract(double x, double y)
{
return x - y;
}

}
//
[ComVisible(false)]
[ClassInterface(ClassInterfaceType.None)]
classExcelAddin : IExcelAddIn
{
publicvoid AutoOpen()
{
ComServer.DllRegisterServer();
}
publicvoid AutoClose()
{
ComServer.DllUnregisterServer();
}
}
}

Build the project and save results. Now, by using Excel what we created before for testing purposes, let us open its VBA editor again and try to run the program (using add method). Remember to doubleClick XLServer.xll file in your \\XLServer\bin\Release folder after opening your Excel. Now, when running that VBA program, compiler will throw run-time error 430 (Class does not support Automation or does not support expected interface).

If you check your VBA code, you can confirm that intellisense is still alive and showing all class methods for our previous version (before changes). To be able to fix this error, we need to re-create our Type Library file again. After I created my type library file again, my intellisense was showing all methods of current version.

Just for the curious, I made exactly the same changes for late-binding scheme presented in previous posting. It turned out, that after those changes, VBA was able to use both class methods (add, subtract) without any further changes or modifications. Moreover, we did not need to create any type library files either.

Conclusions: using Early-binding scheme with COM Server class is providing intellisense support and fast execution time (without run-time compiler checkings), but whenever there will be any changes or modifications made in COM Server class source, type library file needs to be re-created. Late-binding scheme does not provide intellisense support and execution time is a bit slower (run-time compiler checkings), but whenever there will be any changes or modifications made in COM Server class source, no further modifications are needed to get VBA program working.

Now, interesting question is, that exactly how much slower (due to those run-time compiler checkings) late-bound version really is, compared to early-bound version and does it really matter on our program? If this difference is not substantial, it is hard to find any reason to implement COM Server class as early-bound object. It is nice to have intellisense support, but as we see, it comes with the price. Anyway, more information on this particular topic and also possible solution for version handling issues can be found from here and from here.

Final notes


For learning more things about Excel-DNA, check out its homepage. Getting more information and examples with your problems, the main source is Excel-DNA google group. For those who would like to see useful examples using Excel-DNA in financial programs, there is an excellent book C# for Financial Markets (chapter 22) written by Daniel Duffy and Andrea Germani (published 2013). Finally, Excel-DNA is an open-source project, and we (the happy users) can invest its future development by making a donation.

Thanks for reading again and good luck!

-Mike

Creating C# NAG random numbers to Excel

$
0
0
There has already been a couple of blog postings on generating and using random numbers in VBA. However, VBA is not efficient tool for this task and the grass might be a bit greener on the other side of the fence. Moreover, world of randomness always seems to get more interesting and lately, I have discovered low-discrepancy sequences offering some great remedy for slow convergence disease in Monte Carlo applications.

In this posting, we will be
  • getting familiar with NAG .NET library, specifically its class G05 for generators for creating pseudo-random numbers and low-discrepancy sequences.
  • creating easy-to-use C# IRandom interface and its two implementations, which are effectively wrapping specific NAG methods for generating random numbers.
  • interfacing our C# program with Excel by using Excel-DNA.

COOLED BY RANDOMNESS


Generating random numbers is a serious business. A fundamental difference in random number generation is the one between pseudorandom numbers and low-discrepancy sequence. Statistical properties of pseudo-random numbers are very close to "true" random numbers. However, low-discrepancy sequences are designed to give a more even distribution in space, because each number in sequence is designed to be maximally avoiding of the others. For this reason, they are preferred choice for Quasi Monte Carlo methods. This method may use less sample paths to converge to a true value faster than method using pseudo-random numbers.

NAG


Numerical Algorithms Group has been developing impressive collection of numerical libraries with already proven industrial strength. The company itself has over forty years of experience and staffed with experts from the fields of mathematics and statistics. The documentation of class methods is, without a doubt, the best I have seen. Also, there are a lot of useful "copy-paste-run" examples, just to get you started. Also, NAG customer support is one of the best I have experienced. Support people were studying and solving my problem, instead of just trying to shake me off.

As a teaser, NagLibrary namespace for .NET is presented in the picture below.

























Unfortunately, as with all the best things in our life, also NAG is not free. However, the company is offering a trial period, before you make any purchasing decision for the product.


NAG random  number generators


At this posting, we are concentrating on NAG .NET library class G05 for generating random numbers. Class G05 contains numerous different methods for this purpose, but we are using only two specific methods in this posting. We are going to create easy C# wrapper classes for the following methods.
  • g05sk  which generates a vector of pseudorandom numbers taken from a normal distribution. We will use Mersenne Twister algorithm for creating pseudorandom numbers.
  • g05ym which generates a uniformly distributed low-discrepancy sequence. We will use Sobol sequence for creating low-discrepancy sequence.
After we have created those C# wrapper classes for using these methods, we are going to interface everything back to Excel with Excel-DNA. The scheme what we are using here for Excel interfacing, has been thoroughly covered and explained in this posting.

STEP ONE: C# program


Create a new C# class project (RandomGenerator). First, we are going to create interface (IRandom) for random class implementations as shown in the picture below. This interface has only one public method - getRandom. For this method, we define the size of random matrix (two dimensional array) we would like to receive (number of random numbers and number of dimensions). This method is then returning required matrix, filled with random numbers.

using System;
//
namespace RandomGenerator
{
publicinterface IRandom
{
double[,] getRandom(int rows, int cols);
}
}

Next, we are going to create two different interface implementations for our IRandom interface. The first implementation is using Mersenne Twister algorithm for creating a set of pseudo-random numbers. Class implementation is shown in the picture below. Insert a new class (NAG_mersenneTwister) into your project and copyPaste the code into it. Remember also to add reference to NagLibrary32.dll file which you will find in your NAG folder.

using System;
using NagLibrary;
//
namespace RandomGenerator
{
// wrapper for NAG mersenne twister pseudorandom numbers generator
publicclassNAG_mersenneTwister : IRandom
{
privateconstint generator = 3; // hard-coded mersenne twister generator type
privateconstint subGenerator = 1;
privateconstdouble mean = 0.0;
privateconstdoublevar = 1.0;
//
publicdouble[,] getRandom(int nRows, int nCols)
{
double[,] rnd = newdouble[nCols, nRows];
int errorState = 0;
G05.G05State g05State = new G05.G05State(generator, subGenerator, out errorState);
if (errorState != 0) thrownew Exception("g05State error");
//
double[] temp = newdouble[nRows];
for (int i = 0; i < nCols; i++)
{
G05.g05sk(nRows, mean, var, g05State, temp, out errorState);
if (errorState != 0) thrownew Exception("g05sk error");
//
for (int j = 0; j < nRows; j++)
{
rnd[i, j] = temp[j];
}
}
return rnd;
}
}
}

The second implementation is using Sobol sequence for creating a set of low-discrepancy numbers. Class implementation is shown in the picture below. Insert a new class (NAG_sobolSequence) into your project and copyPaste the code into it.

using System;
using NagLibrary;
//
namespace RandomGenerator
{
// wrapper for NAG sobol sequence generator
publicclassNAG_sobolSequence : IRandom
{
privateconstint generatorType = 1; // hard-coded sobol sequence generator type
privateconstint skipFirstItems = 2500;
privateconstint returnOrder = 1;
//
// estimated coefficients for rational approximations
// used when transforming uniform variates to normal
privateconstdouble a1 = -39.6968302866538;
privateconstdouble a2 = 220.946098424521;
privateconstdouble a3 = -275.928510446969;
privateconstdouble a4 = 138.357751867269;
privateconstdouble a5 = -30.6647980661472;
privateconstdouble a6 = 2.50662827745924;
//
privateconstdouble b1 = -54.4760987982241;
privateconstdouble b2 = 161.585836858041;
privateconstdouble b3 = -155.698979859887;
privateconstdouble b4 = 66.8013118877197;
privateconstdouble b5 = -13.2806815528857;
//
privateconstdouble c1 = -0.00778489400243029;
privateconstdouble c2 = -0.322396458041136;
privateconstdouble c3 = -2.40075827716184;
privateconstdouble c4 = -2.54973253934373;
privateconstdouble c5 = 4.37466414146497;
privateconstdouble c6 = 2.93816398269878;
//
privateconstdouble d1 = 0.00778469570904146;
privateconstdouble d2 = 0.32246712907004;
privateconstdouble d3 = 2.445134137143;
privateconstdouble d4 = 3.75440866190742;
//
// break points for transformation function
constdouble p_low = 0.02425;
constdouble p_high = 1 - p_low;
//
publicdouble[,] getRandom(int nRows, int nCols)
{
int errorStatus = 0;
int n = 32 * nCols + 7;
int[] initializationInformation = newint[n];
double[,] rnd = newdouble[nCols, nRows];
//
// initialize sobol quasi-random numbers generator
G05.g05yl(generatorType, nCols, initializationInformation, skipFirstItems, out errorStatus);
if (errorStatus != 0) thrownew Exception("g05yl error");
//
// generate uniformly-distributed quasi-random numbers
G05.g05ym(nRows, returnOrder, rnd, initializationInformation, out errorStatus);
if (errorStatus != 0) thrownew Exception("g05ym error");
//
// transformation into normally-distributed quasi-random numbers
for (int i = 0; i < nCols; i++)
{
for (int j = 0; j < nRows; j++)
{
rnd[i, j] = normsinv(rnd[i, j]);
}
}
// return result matrix
return rnd;
}
//
privatedouble normsinv(double u)
{
// transform uniformly-distributed number into normal
double q, r;
double n = 0.0;
//
// throw an error if a given uniform number is out of bounds
if ((u <= 0.0) || (u >= 1.0)) thrownew Exception("given uniform number is out of bounds");
//
if (u < p_low)
{
// rational approximation for lower region
q = Math.Sqrt(-2.0 * Math.Log(u));
n = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) /
((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
goto exitPoint;
}
//
if (u <= p_high)
{
// rational approximation for mid region
q = u - 0.5;
r = q * q;
n = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q /
(((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1);
goto exitPoint;
}
//
if (u < 1.0)
{
// rational approximation for upper region
q = Math.Sqrt(-2.0 * Math.Log(1.0 - u));
n = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) /
((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
}
exitPoint:
return n;
}
}
}

The numbers received from NAG Sobol generator are mapped to uniform space. For normalizing these numbers, I have used an algorithm for computing the inverse normal cumulative distribution function, developed by Peter Acklam. At this point, we have created the parts of the program, in which are creating the actual random numbers. Next, we are going to interface our C# program with Excel by using Excel-DNA.

STEP TWO: Interfacing C# to Excel with Excel-DNA

Interfacing is done by using Excel-DNA. Insert a new class (RandomGenerator) into your project and copyPaste the code below into it. The complete example for this particular scheme has already been fully covered and explained in here.

using System;
using ExcelDna.Integration;
using System.Windows.Forms;
//
namespace RandomGenerator
{
publicstaticclassRandomGenerator
{
publicstaticvoid execute()
{
try
{
// create Excel application object
dynamic Excel = ExcelDnaUtil.Application;
//
// use mersenne twister algorithm
NAG_mersenneTwister mt = new NAG_mersenneTwister();
double[,] MT_random = mt.getRandom(1000, 2);
Excel.Range["_mersenneTwister"] = Excel.WorksheetFunction.Transpose(MT_random);
//
// use sobol sequence
NAG_sobolSequence sobol = new NAG_sobolSequence();
double[,] MT_sobol = sobol.getRandom(1000, 2);
Excel.Range["_sobolSequence"] = Excel.WorksheetFunction.Transpose(MT_sobol);
}
catch (Exception e)
{
MessageBox.Show(e.Message.ToString());
}
}
}
}

At this point, we have been creating NAG random number generator wrapper classes as implementations of IRandom interface. Moreover, we have created interfacing program, which connects C# program with Excel. In this project, we are practically using Excel only as a platform for data output for C#.

STEP THREE: Excel and VBA


Open a new Excel workbook and set the following named ranges into worksheet (Sheet1).
  • Range "_mersenneTwister" (A1:B1000)
  • Range "_sobolSequence" (D1:E1000)
Finally, we need to have a "triggering program", which will start the actual C# program. Insert ActiveX commandbutton into worksheet and create the following event handling program for this button.

Option Explicit
'
PrivateSub CommandButton1_Click()
Application.Run ("execute")
EndSub
'

Now, while this workbook is still open, doubleClick RandomGenerator.xll file in your \\RandomGenerator\bin\Release folder. After this, xll can be used by Excel and our C# function (execute) is available to be called from VBA program (Application.Run). VBA event handler program will call and start C# program execute, which will create and send the both sets of random numbers into named Excel ranges.


STEP FOUR: Scatter plots


Next, I have plotted the both sets of random numbers into a two-dimensional space. Scatter plots are shown in the picture below. On the left side, we have random numbers created with NAG pseudo-random numbers generator (Mersenne Twister). On the right side, we have random numbers created with NAG Low-discrepancy numbers generator (Sobol sequence). The upper part is showing normalized data and the lower part is showing data mapped back to uniform plane by using Excel NORMSDIST worksheet function.
























The picture clearly shows the difference between the two types of generators. The both generators are filling two-dimensional space randomly, but low-discrepancy sequence (on the right side) fills the space more uniformly, avoiding that undesired clustering effect what is appearing on the left side (pseudo-random generator).

Thanks for reading again.

-Mike

Configurable C# Monte Carlo option pricer in Excel

$
0
0
This time, I wanted to present one possible design for Monte Carlo (MC) option pricer, what I have been chewing for some time. The great wisdom what I have learned so far is the following: MC application is always inherently a tradeoff between speed and flexibility. The fastest solution is just one monolithic program, where everything is hard-coded. However, this type of solution leads to maintenance and extendability problems, when any new types of pricers needs to be created, for example. And again, a desire for more flexible solution leads to increases in design complexity and running time.

Now, with this design example, we may not create the fastest possible solution, but the one with extremely flexible design and great configurability. More specifically when pricing options, the user is able to select different types of

The presented design can actually be used, not only for pricing options, but for all applications where we would like to simulate any stochastic processes. For example, we could use "the core part" of this design (SDE, Discretization, RandomGenerator and MonteCarloEngine) when simulating short rate processes for yield curve estimation. However, this example concentrates only on pricing options. More specifically, the user is able to use this design example when pricing options without embedded decisions (American, Bermudan).


PROJECT OUTCOME

The outcome of this small project is fully configurable C# Monte Carlo pricer application. Application can be used to price wide range of different types of one-factor options (European, binary, path-dependent). The application gets all the required input parameters directly from Excel, then performs calculations in C# and finally returns calculation results back to Excel. Excel and C# are interfaced with Excel-DNA and Excel itself is used only as data input/output platform, exactly like presented in my previous blog post.


PREPARATORY TASKS

Download and unzip Excel-DNA Version 0.30 zip file to be ready when needed. There is also a step-by-step word documentation file available within the distribution folder. In this project, we are going to follow these instructions.

 

DESIGN OVERVIEW

The application design is presented in the UML class diagram below.




















In order to understand this design better, we go through the core components and general logic of this design.


STOCHASTIC PATH CREATION - THE CORE OF THE ENGINE

Whenever we need to simulate stochastic process path, we need to define stochastic differential equation to be used. In addition to this, we also need to define discretization scheme for this SDE. In order to model differential equation to be stochastic, we need standardized normal random number. The following three components provide service to create prices according to a given stochastic differential equation, discretization scheme and random number generator.



















With SDE component, we can model the following types of one-factor stochastic differential equations.






Interface ISDE defines methods for retrieving drift and diffusion terms for a given S and t. Abstract class SDE implements this interface. Finally, from abstract SDE we can implement concrete classes for different types of SDE's (GBM, Vasicek, CIR, etc). In this design example, we are using Standard Geometric Brownian Motion.

IDiscretization interface defines method for retrieving spot price for a given S, t, dt and random term. Abstract Discretization class implements this interface and also defines initial spot price (initialPrice) and time to maturity (expiration) as protected member data. It should be noted, that our concrete SDE is aggregated into Discretization. In this design example, we are using Euler discretization scheme.

Finally, IRandomGenerator defines method for getting standard normal random number. Again, RandomGenerator implements this interface and in this design example our concrete class NormalApproximation is "quick and dirty way" to generate normal random approximations as the sum of 12 independent uniformly distributed random numbers minus 6. Needless to say, we should come up with the better random number generator implementation for this class, when starting to test option pricing against benchmark prices.

 

BUILDER AND MONTE CARLO ENGINE

Creating all previously presented "core objects" in the main program can easily lead to maintenance problems and main program "explosion". The solution for this common problem is to use Builder design pattern. The interaction between Builder component and MonteCarloEngine is described in the picture below.































IBuilder interface defines method for creating and retrieving all three core objects inside Tuple. Abstract Builder class implements this interface and concrete class implements Builder class. In this design example, our concrete implementation for Builder class is ExcelBuilder class, which will build all three core objects directly from Excel workbook and finally packs these objects into Tuple.

There is an association between Builder and MonteCarloEngine. Selected Builder object to be used will be given as one argument in MonteCarloEngine constructor. In constructor code, Builder will build three core objects and packs those into Tuple. After this, constructor code will assign values for private data members directly from Tuple (SDE, Discretization, RandomGenerator).

The purpose of MonteCarloEngine class is to create stochastic price paths, by using these three core objects described above. Actually, this class is also an implementation of Mediator design pattern. We have been implementing all the needed components as loosely coupled classes. All communication between these objects are handled by MonteCarloEngine (Mediator).

When MonteCarloEngine has simulated a path, it uses event (delegate function PathSender) for distributing this simulated path (array of doubles) for pricers, one path at a time. Then, when MonteCarloEngine has been simulating desired number of paths, it uses event (delegate function ProcessStopper) for sending notification on the simulation process end for pricers. After receiving this notification from MonteCarloEngine, pricers will calculate the option prices.


PRICER

The final component of this solution is Pricer. This component is receiving simulated price path from MonteCarloEngine and calculating option value for a given one-factor payoff function for each simulated path (delegate function OneFactorPayoff). Pricer class uses a given discount factor (generic delegate function discountFactor) for calculating present value for option payoff expectation. Finally, client can retrieve calculated option price with public price method.





















IPricer interface defines methods for processing simulated price path (processPath), calculating option price (calculate) and retrieving option price (price). Pricer implements this interface. Also, it has OneFactorPayoff delegate, discountFactor generic delegate and number of simulated paths as protected member data. Technically, the variable for simulated paths is only a running counter for expectation calculation purposes. Concrete implementation of Pricer class uses OneFactorPayoff delegate function for calculating the actual option payoff for a given spot and strike.

 

C# PROGRAM

All interfaces and classes described above, are given here below. Create a new C# Class Library project (MCPricer), save the project and copyPaste the following code blocks into separate cs files.

interface ISDE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicinterface ISDE
{
// methods for calculating drift and diffusion term of stochastic differential equation
double drift(double s, double t);
double diffusion(double s, double t);
}
}

abstract class SDE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicabstractclassSDE :ISDE
{
// abstract class implementing ISDE interface
publicabstractdouble drift(double s, double t);
publicabstractdouble diffusion(double s, double t);
}
}

concrete class GBM
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
// concrete implementation for Standard Geometric Brownian Motion
publicclassGBM : SDE
{
privatedouble r; // risk-free rate
privatedouble q; // dividend yield
privatedouble v; // volatility
//
public GBM(double r, double q, double v)
{
this.r = r; this.q = q; this.v = v;
}
publicoverridedouble drift(double s, double t)
{
return (r - q) * s;
}
publicoverridedouble diffusion(double s, double t)
{
return v * s;
}
}
}

interface IDiscretization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicinterface IDiscretization
{
// method for discretizing stochastic differential equation
double next(double s, double t, double dt, double rnd);
}
}

abstract class Discretization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicabstractclassDiscretization : IDiscretization
{
// abstract class implementing IDiscretization interface
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);
}
}

concrete class EulerDiscretization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicclassEulerDiscretization : Discretization
{
// concrete implementation for Euler discretization scheme
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;
}
}
}

interface IRandomGenerator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicinterface IRandomGenerator
{
// method for generating normally distributed random variable
double getRandom();
}
}

abstract class RandomGenerator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicabstractclassRandomGenerator : IRandomGenerator
{
// abstract class implementing IRandomGenerator interface
publicabstractdouble getRandom();
}
}

concrete class NormalApproximation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicclassNormalApproximation : RandomGenerator
{
// concrete implementation for normal random variable approximation
// normRand = sum of 12 independent uniformly disctibuted random numbers, minus 6
private Random random;
public NormalApproximation()
{
random = new Random();
}
publicoverridedouble getRandom()
{
// implementation uses C# uniform random generator
double[] rnd = newdouble[12];
Func<double> generator = () => { return random.NextDouble(); };
return rnd.Select(r => generator()).Sum() - 6.0;
}
}
}

concrete class MonteCarloEngine
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicdelegatevoid PathSender(refdouble[] path);
publicdelegatevoid ProcessStopper();
//
publicclassMonteCarloEngine
{
private SDE sde;
private Discretization discretization;
private RandomGenerator randomGenerator;
privatelong paths;
privateint steps;
publicevent PathSender sendPath;
publicevent ProcessStopper stopProcess;
//
public MonteCarloEngine(Builder builder, long paths, int steps)
{
Tuple<SDE, Discretization, RandomGenerator> parts = builder.build();
sde = parts.Item1;
discretization = parts.Item2;
randomGenerator = parts.Item3;
this.paths = paths;
this.steps = steps;
}
publicvoid run()
{
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++)
{
path[0] = vOld = discretization.InitialPrice;
//
for (int j = 1; j <= steps; j++)
{
// get next value using discretization scheme
vNew = discretization.next(vOld, (dt * j), dt, randomGenerator.getRandom());
path[j] = vNew; vOld = vNew;
}
sendPath(ref path); // send one simulated path to pricer to be processed
}
stopProcess(); // simulation ends - notify pricer
}
}
}

interface IBuilder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicinterface IBuilder
{
// method for creating all the needed objects for asset price simulations
Tuple<SDE, Discretization, RandomGenerator> build();
}
}

abstract class Builder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicabstractclassBuilder : IBuilder
{
// abstract class implementing IBuilder interface
publicabstract Tuple<SDE, Discretization, RandomGenerator> build();
}
}

concrete class ExcelBuilder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExcelDna.Integration;

namespace MCPricer
{
publicclassExcelBuilder : Builder
{
privatedynamic Excel = ExcelDnaUtil.Application;
//
publicoverride Tuple<SDE, Discretization, RandomGenerator> build()
{
// build all objects needed for asset path simulations
SDE sde = build_SDE();
Discretization discretization = build_discretization(sde);
RandomGenerator randomGenerator = build_randomGenerator();
returnnew Tuple<SDE, Discretization, RandomGenerator>(sde, discretization, randomGenerator);
}
private SDE build_SDE()
{
SDE sde = null;
string sdeType = (string)Excel.Range("_stochasticModel").Value;
//
if (sdeType == "GBM")
{
double r = (double)Excel.Range("_rate").Value2;
double q = (double)Excel.Range("_yield").Value2;
double v = (double)Excel.Range("_volatility").Value2;
sde = new GBM(r, q, v);
}
// insert new stochastic model choices here
return sde;
}
private Discretization build_discretization(SDE sde)
{
Discretization discretization = null;
string discretizationType = (string)Excel.Range("_discretization").Value;
//
if (discretizationType == "EULER")
{
double initialPrice = (double)Excel.Range("_spot").Value2;
double expiration = (double)Excel.Range("_maturity").Value2;
discretization = new EulerDiscretization(sde, initialPrice, expiration);
}
// insert new discretization scheme choices here
return discretization;
}
private RandomGenerator build_randomGenerator()
{
RandomGenerator randomGenerator = null;
string randomGeneratorType = (string)Excel.Range("_randomGenerator").Value;
//
if (randomGeneratorType == "CLT")
{
randomGenerator = new NormalApproximation();
}
// insert new random generator choices here
return randomGenerator;
}
}
}

interface IPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicinterface IPricer
{
void processPath(refdouble[] path);
void calculate();
double price();
}
}

abstract class Pricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicdelegatedouble OneFactorPayoff(double spot, double strike);
//
publicabstractclassPricer : IPricer
{
protected OneFactorPayoff payoff; // delegate function for payoff calculation
protected Func<double> discountFactor; // generic delegate function for discount factor
protecteddouble v; // option price
protectedlong paths; // running counter
//
public Pricer(OneFactorPayoff payoff, Func<double> discountFactor)
{
this.payoff = payoff; this.discountFactor = discountFactor;
}
publicabstractvoid processPath(refdouble[] path);
publicvoid calculate()
{
// calculate discounted expectation
v = (v / paths) * discountFactor();
}
publicdouble price()
{
// return option value
return v;
}

}
}

concrete class EuropeanPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicclassEuropeanPricer : Pricer
{
privatedouble x; // option strike
//
public EuropeanPricer(OneFactorPayoff payoff, double x, Func<double> discountFactor)
: base(payoff, discountFactor)
{
this.x = x;
}
publicoverridevoid processPath(refdouble[] path)
{
// calculate payoff
v += payoff(path[path.Length - 1], x);
paths++;
}
}
}

concrete class ArithmeticAsianPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicenum ENUM_ASIAN_TYPE { average_price, average_strike }
//
publicclassArithmeticAsianPricer : Pricer
{
private ENUM_ASIAN_TYPE asianType;
privatedouble x; // option strike
privatedouble averagePeriodStart; // time for starting averaging period
privatedouble t;
privateint steps;
//
public ArithmeticAsianPricer(OneFactorPayoff payoff, double x, Func<double> discountFactor,
double t, double averagePeriodStart, int steps, ENUM_ASIAN_TYPE asianType)
: base(payoff, discountFactor)
{
this.x = x;
this.t = t;
this.steps = steps;
this.averagePeriodStart = averagePeriodStart;
this.asianType = asianType;
}
publicoverridevoid processPath(refdouble[] path)
{
double dt = t / steps;
int timeCounter = -1;
//
// generic delegate for SkipWhile method to test if averaging period for an item has started
Func<double, bool> timeTest = (double p) =>
{
timeCounter++;
if ((dt * timeCounter) < averagePeriodStart) returntrue;
returnfalse;
};
//
// calculate average price for averaging period
double pathAverage = path.SkipWhile(timeTest).ToArray().Average();
//
// calculate payoff
if (asianType == ENUM_ASIAN_TYPE.average_price) v += payoff(pathAverage, x);
if (asianType == ENUM_ASIAN_TYPE.average_strike) v += payoff(path[path.Length - 1], pathAverage);
paths++;
}
}
}

concrete class BarrierPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
publicenum ENUM_BARRIER_TYPE { up_and_in, up_and_out, down_and_in, down_and_out }
//
publicclassBarrierPricer : Pricer
{
privatedouble x; // option strike
privatedouble b; // barrier level
private ENUM_BARRIER_TYPE barrierType;
//
public BarrierPricer(OneFactorPayoff payoff, double x, Func<double> discountFactor,
double b, ENUM_BARRIER_TYPE barrierType) : base(payoff, discountFactor)
{
this.x = x;
this.b = b;
this.barrierType = barrierType;
}
publicoverridevoid processPath(refdouble[] path)
{
// calculate payoff - check barrier breaches
if ((barrierType == ENUM_BARRIER_TYPE.up_and_in) && (path.Max() > b)) v += payoff(path[path.Length - 1], x);
if ((barrierType == ENUM_BARRIER_TYPE.up_and_out) && (path.Max() < b)) v += payoff(path[path.Length - 1], x);
if ((barrierType == ENUM_BARRIER_TYPE.down_and_in) && (path.Min() < b)) v += payoff(path[path.Length - 1], x);
if ((barrierType == ENUM_BARRIER_TYPE.down_and_out) && (path.Min() > b)) v += payoff(path[path.Length - 1], x);
paths++;
}
}
}

concerete class MCPricer (this is the main program, VBA program will call run method of this class).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExcelDna.Integration;
using System.Windows.Forms;

namespace MCPricer
{
publicstaticclassMCPricer
{
privatestaticdynamic Excel;
privatestatic Dictionary<string, Pricer> pricer;
privatestatic MonteCarloEngine engine;
privatestatic OneFactorPayoff callPayoff;
privatestatic OneFactorPayoff putPayoff;
privatestatic Func<double> discountFactor;
//
publicstaticvoid run()
{
try
{
// create Excel application
Excel = ExcelDnaUtil.Application;
//
// fetch pricing parameters from named Excel ranges
int steps = (int)Excel.Range("_steps").Value2;
long paths = (long)Excel.Range("_paths").Value2;
double r = (double)Excel.Range("_rate").Value2;
double t = (double)Excel.Range("_maturity").Value2;
double averagePeriodStart = (double)Excel.Range("_averagingPeriod").Value2;
double upperBarrier = (double)Excel.Range("_upperBarrier").Value2;
double lowerBarrier = (double)Excel.Range("_lowerBarrier").Value2;
double x = (double)Excel.Range("_strike").Value2;
//
// create Monte Carlo engine, payoff functions and discounting factor
engine = new MonteCarloEngine(new ExcelBuilder(), paths, steps);
callPayoff = (double spot, double strike) => Math.Max(0.0, spot - strike);
putPayoff = (double spot, double strike) => Math.Max(0.0, strike - spot);
discountFactor = () => Math.Exp(-r * t);
//
// create pricers into dictionary
pricer = new Dictionary<string, Pricer>();
pricer.Add("Vanilla call", new EuropeanPricer(callPayoff, x, discountFactor));
pricer.Add("Vanilla put", new EuropeanPricer(putPayoff, x, discountFactor));
pricer.Add("Asian average price call", new ArithmeticAsianPricer(callPayoff, x, discountFactor, t, averagePeriodStart, steps, ENUM_ASIAN_TYPE.average_price));
pricer.Add("Asian average price put", new ArithmeticAsianPricer(putPayoff, x, discountFactor, t, averagePeriodStart, steps, ENUM_ASIAN_TYPE.average_price));
pricer.Add("Asian average strike call", new ArithmeticAsianPricer(callPayoff, x, discountFactor, t, averagePeriodStart, steps, ENUM_ASIAN_TYPE.average_strike));
pricer.Add("Asian average strike put", new ArithmeticAsianPricer(putPayoff, x, discountFactor, t, averagePeriodStart, steps, ENUM_ASIAN_TYPE.average_strike));
pricer.Add("Up-and-in barrier call", new BarrierPricer(callPayoff, x, discountFactor, upperBarrier, ENUM_BARRIER_TYPE.up_and_in));
pricer.Add("Up-and-out barrier call", new BarrierPricer(callPayoff, x, discountFactor, upperBarrier, ENUM_BARRIER_TYPE.up_and_out));
pricer.Add("Down-and-in barrier call", new BarrierPricer(callPayoff, x, discountFactor, lowerBarrier, ENUM_BARRIER_TYPE.down_and_in));
pricer.Add("Down-and-out barrier call", new BarrierPricer(callPayoff, x, discountFactor, lowerBarrier, ENUM_BARRIER_TYPE.down_and_out));
pricer.Add("Up-and-in barrier put", new BarrierPricer(putPayoff, x, discountFactor, upperBarrier, ENUM_BARRIER_TYPE.up_and_in));
pricer.Add("Up-and-out barrier put", new BarrierPricer(putPayoff, x, discountFactor, upperBarrier, ENUM_BARRIER_TYPE.up_and_out));
pricer.Add("Down-and-in barrier put", new BarrierPricer(putPayoff, x, discountFactor, lowerBarrier, ENUM_BARRIER_TYPE.down_and_in));
pricer.Add("Down-and-out barrier put", new BarrierPricer(putPayoff, x, discountFactor, lowerBarrier, ENUM_BARRIER_TYPE.down_and_out));
//
// order path updates for all pricers from engine
foreach (KeyValuePair<string, Pricer> kvp in pricer) engine.sendPath += kvp.Value.processPath;
//
// order process stop notification for all pricers from engine
foreach (KeyValuePair<string, Pricer> kvp in pricer) engine.stopProcess += kvp.Value.calculate;
//
// run Monte Carlo engine
engine.run();
//
// print option types to Excel
string[] optionTypes = pricer.Keys.ToArray();
Excel.Range["_options"] = Excel.WorksheetFunction.Transpose(optionTypes);
//
// print option prices to Excel
double[] optionPrices = newdouble[pricer.Count];
for (int i = 0; i < pricer.Count; i++) optionPrices[i] = pricer.ElementAt(i).Value.price();
Excel.Range["_prices"] = Excel.WorksheetFunction.Transpose(optionPrices);
}
catch (Exception e)
{
MessageBox.Show(e.Message.ToString());
}
}
}
}

 

EXCEL-DNA INTEGRATION

After implementing all the previous cs files into C# Class Library project, we are receiving a lot of errors. However, all errors should be related to missing references to Excel-DNA integration library and Windows Forms library. Next, carefully follow the instructions described here in step two.

In a nutshell
  • add reference to Excel-DNA library (ExcelDna.Integration.dll). From the properties of this reference, set Copy Local to be False.
  • add reference to Windows Forms library (System.Windows.Forms)
  • create MCPricer.dna file (consisting XML tags). DnaLibrary Name="MCPricer" and Path="MCPricer.dll". From the properties of this dna file, set Copy to Output Directory to be Copy if newer.
  • copy ExcelDna.xll file into your project folder and rename it to be MCPricer.xll. From the properties of this xll file, set Copy to Output Directory to be Copy if newer.
Make sure, that all properties for these references and files are exactly the same as described in this post. After adding all the required references, files and building this program once again, my project folder has the following four files.

 

USER INTERFACE AND C#-VBA INTEGRATION

The essence of this part of the process has been described here in step three. Open a new Excel workbook. Create the following source data into worksheet.
















From Excel Name Manager (Formulas - Name Manager), set the following range names.

























In VB editor, create the following event handling program for CommandButton (Calculate option prices).



















TEST RUN

At this point, our application is ready for test run. While this Excel workbook is open, doubleClick MCPricer.xll file in your \\MCPricer\bin\Release folder. After this, xll file content can be used by Excel and our C# program is available to be called from VBA program (Application.Run). VBA program will call and start C# program run, which then reads all input data, performs calculations and sends result data back to Excel worksheet.

After pressing command button in Excel interface, C# MC option pricer application simulated the following prices for all requested options.
















AFTERTHOUGHTS

This small project was presenting one possible design for Monte Carlo option pricer. We came up with extremely flexible design and great configurability. The presented design can actually be used, not only for pricing options, but for all applications where we would like to simulate any stochastic processes for any purpose (short rate processes for yield curve estimation, for example).

At this point, I would like to express my appreciation for Mr. Daniel Duffy for opening up some of his "well-brewed design wisdoms" during the one of his DatasimFinancial training courses. For those who would like get familiar with useful examples and ideas using C# in financial programs, there is a great book C# for Financial Markets available written by Daniel Duffy and Andrea Germani (published 2013).

As always, I owe Thank You again for Govert Van Drimmelen (inventor, developer and author of Excel-DNA), for his amazing Excel-DNA Excel/C API wrapper. For learning more about this extremely useful tool, check out its homepage. Getting more information and examples with your problems, the main source is Excel-DNA google group. Finally, Excel-DNA is an open-source project, and we (the happy users) can invest its future development by making a donation.

And finally, Thank You for reading my blog again!
-Mike Juniperhill

Configurable C# Monte Carlo zero-coupon bond pricer in Excel

$
0
0
Monte Carlo design, what was presented in my previous blog article could actually be used, not only for pricing options, but for all applications where one would like to simulate stochastic process. One such useful application would be to simulate short rate process for pricing zero-coupon bonds, for example.

In this article, we investigate how to implement such pricing scheme with the current design for one-factor short-rate models. As a result of this small project, we will have a framework for pricing zero-coupon bonds with any given one-factor short-rate model. Specifically, we implement zero-coupon bond pricing schemes for Vasicek model and CIR model. Since both of these models are nicely tractable Gaussian models, we also compare the simulated bond prices with the exact analytical model prices. Before going further with this article, make sure that you have clear understanding of the design presented in my previous blog article.

PROJECT OUTCOME

The outcome of this small project is fully configurable C# Monte Carlo pricer application in Excel. Application can be used to price zero-coupon bond prices with any different types of one-factor short-rate models. The application gets all the required input parameters directly from Excel, then performs calculations in C# and finally returns calculation results back to Excel. Excel and C# are interfaced with Excel-DNA and Excel itself is used only as data input/output platform, exactly like presented in this blog article.


PREPARATORY TASKS

Download and unzip Excel-DNA zip file to be ready when needed. There is also a step-by-step word documentation file available within the distribution folder. In this project, we are going to follow these instructions.


EXTENSIONS FOR CURRENT DESIGN

As a base design for this application, we will use the design presented in my previous blog article. There will be no changes made to core components of this base design (SDE, Discretization, RandomGenerator). We are going to implement the following extensions for this base design.

  • Two new concrete class implementations for abstract class SDE (Vasicek, CIR)
  • One new concrete class implementation for abstract class Pricer (BondPricer)
There will be some small modifications made to Builder component.


C# PROGRAM

Create a new C# Class Library project (BondPricer), save the project and copyPaste the following code blocks into separate cs files. These classes are all we need from our current base design. For the sake of clarity, I made a decision not to include any option-related classes to this design.

interface ISDE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicinterface ISDE
{
// methods for calculating drift and diffusion term of stochastic differential equation
double drift(double s, double t);
double diffusion(double s, double t);
}
}

abstract class SDE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicabstractclassSDE :ISDE
{
// abstract class implementing ISDE interface
publicabstractdouble drift(double s, double t);
publicabstractdouble diffusion(double s, double t);
}
}

interface IDiscretization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicinterface IDiscretization
{
// method for discretizing stochastic differential equation
double next(double s, double t, double dt, double rnd);
}
}

abstract class Discretization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicabstractclassDiscretization : IDiscretization
{
// abstract class implementing IDiscretization interface
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);
}
}

concrete class EulerDiscretization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicclassEulerDiscretization : Discretization
{
// concrete implementation for Euler discretization scheme
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;
}
}
}

interface IRandomGenerator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicinterface IRandomGenerator
{
// method for generating normally distributed random variable
double getRandom();
}
}

abstract class RandomGenerator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicabstractclassRandomGenerator : IRandomGenerator
{
// abstract class implementing IRandomGenerator interface
publicabstractdouble getRandom();
}
}

concrete class NormalApproximation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicclassNormalApproximation : RandomGenerator
{
// concrete implementation for normal random variable approximation
// normRand = sum of 12 independent uniformly disctibuted random numbers, minus 6
private Random random;
public NormalApproximation()
{
random = new Random();
}
publicoverridedouble getRandom()
{
// implementation uses C# uniform random generator
double[] rnd = newdouble[12];
Func<double> generator = () => { return random.NextDouble(); };
return rnd.Select(r => generator()).Sum() - 6.0;
}
}
}

concrete class MonteCarloEngine

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicdelegatevoid PathSender(refdouble[] path);
publicdelegatevoid ProcessStopper();
//
publicclassMonteCarloEngine
{
private SDE sde;
private Discretization discretization;
private RandomGenerator randomGenerator;
privatelong paths;
privateint steps;
publicevent PathSender sendPath;
publicevent ProcessStopper stopProcess;
//
public MonteCarloEngine(Tuple<SDE, Discretization, RandomGenerator> parts, long paths, int steps)
{
sde = parts.Item1;
discretization = parts.Item2;
randomGenerator = parts.Item3;
this.paths = paths;
this.steps = steps;
}
public MonteCarloEngine(Builder builder, long paths, int steps)
{
Tuple<SDE, Discretization, RandomGenerator> parts = builder.build();
sde = parts.Item1;
discretization = parts.Item2;
randomGenerator = parts.Item3;
this.paths = paths;
this.steps = steps;
}
publicvoid run()
{
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++)
{
path[0] = vOld = discretization.InitialPrice;
//
for (int j = 1; j <= steps; j++)
{
// get next value using discretization scheme
vNew = discretization.next(vOld, (dt * j), dt, randomGenerator.getRandom());
path[j] = vNew; vOld = vNew;
}
sendPath(ref path); // send one simulated path to pricer to be processed
}
stopProcess(); // simulation ends - notify pricer
}
}
}

interface IBuilder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicinterface IBuilder
{
// method for creating all the needed objects for asset price simulations
Tuple<SDE, Discretization, RandomGenerator> build();
}
}

abstract class Builder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicabstractclassBuilder : IBuilder
{
// abstract class implementing IBuilder interface
publicabstract Tuple<SDE, Discretization, RandomGenerator> build();
}
}

interface IPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicinterface IPricer
{
void processPath(refdouble[] path);
void calculate();
double price();
}
}

abstract class Pricer

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicdelegatedouble OneFactorPayoff(double spot, double strike);
//
publicabstractclassPricer : IPricer
{
protected OneFactorPayoff payoff; // delegate function for payoff calculation
protected Func<double> discountFactor; // generic delegate function for discount factor
protecteddouble v; // option price
protectedlong paths; // running counter
//
public Pricer() { }
public Pricer(OneFactorPayoff payoff, Func<double> discountFactor)
{
this.payoff = payoff; this.discountFactor = discountFactor;
}
publicabstractvoid processPath(refdouble[] path);
publicabstractvoid calculate();
publicabstractdouble price();
}
}

After implementing all the interfaces and classes described above, add the following new implementations to this design.

concrete class ExcelBuilder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExcelDna.Integration;

namespace BondPricer
{
publicclassExcelBuilder : Builder
{
privatedouble t;
privatedynamic Excel = ExcelDnaUtil.Application;
//
public ExcelBuilder(double t) { this.t = t; }
publicoverride Tuple<SDE, Discretization, RandomGenerator> build()
{
// build all objects needed for asset path simulations
SDE sde = build_SDE();
Discretization discretization = build_discretization(sde);
RandomGenerator randomGenerator = build_randomGenerator();
returnnew Tuple<SDE, Discretization, RandomGenerator>(sde, discretization, randomGenerator);
}
private SDE build_SDE()
{
SDE sde = null;
string sdeType = (string)Excel.Range("_shortRateModel").Value;
double longTermRate = (double)Excel.Range("_longTermRate").Value2;
double reversionSpeed = (double)Excel.Range("_reversionSpeed").Value2;
double rateVolatility = (double)Excel.Range("_rateVolatility").Value2;
//
if (sdeType == "VASICEK") sde = new Vasicek(longTermRate, reversionSpeed, rateVolatility);
if (sdeType == "CIR") sde = new Vasicek(longTermRate, reversionSpeed, rateVolatility);
// insert new stochastic model choices here
return sde;
}
private Discretization build_discretization(SDE sde)
{
Discretization discretization = null;
string discretizationType = (string)Excel.Range("_discretization").Value;
//
if (discretizationType == "EULER")
{
double initialPrice = (double)Excel.Range("_spotRate").Value2;
discretization = new EulerDiscretization(sde, initialPrice, t);
}
// insert new discretization scheme choices here
return discretization;
}
private RandomGenerator build_randomGenerator()
{
RandomGenerator randomGenerator = null;
string randomGeneratorType = (string)Excel.Range("_randomGenerator").Value;
//
if (randomGeneratorType == "CLT")
{
randomGenerator = new NormalApproximation();
}
// insert new random generator choices here
return randomGenerator;
}
}
}

concrete class Vasicek
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
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;
}
}
}

concrete class CIR
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicclassCIR : SDE
{
privatedouble longTermRate;
privatedouble reversionSpeed;
privatedouble rateVolatility;
//
public CIR(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 * Math.Sqrt(s);
}
}
}

concrete class ZCBPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BondPricer
{
publicclassZCBPricer : Pricer
{
privatedouble t;
privatedouble dt;
//
public ZCBPricer(double t, double dt) : base() { this.t = t; this.dt = dt; }
//
publicoverridevoid processPath(refdouble[] path)
{
int n = (int)(t / dt);
double integral = 0.0;
for (int i = 0; i < n; i++) integral += path[i] * dt;
v += integral;
paths++;
}
publicoverridevoid calculate()
{
v /= paths; // calculate expectation
}
publicoverridedouble price()
{
return Math.Exp(-v); // return discount factor
}
}
}


concerete class BondPricer (VBA program will call run method of this class).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExcelDna.Integration;
using System.Windows.Forms;

namespace BondPricer
{
publicstaticclassBondPricer
{
privatestaticdynamic Excel;
privatestatic MonteCarloEngine engine;
privatestatic List<Pricer> bonds;
//
publicstaticvoid run()
{
try
{
// create Excel application
Excel = ExcelDnaUtil.Application;
//
// fetch bond pricing parameters from named Excel ranges
int steps = (int)Excel.Range("_steps").Value2;
long paths = (long)Excel.Range("_paths").Value2;
//
// read maturities from named range
dynamic maturityArray = Excel.Range["_maturity"].Value2;
int rows = maturityArray.GetUpperBound(0);
double maxMaturity = (double)maturityArray.GetValue(rows, 1);
double dt = (double)maxMaturity / steps;
//
// create Monte Carlo engine
engine = new MonteCarloEngine(new ExcelBuilder(maxMaturity), paths, steps);
bonds = new List<Pricer>();
//
// add pricers to list, order path updates and stop notification from engine
for (int i = 0; i < rows; i++)
{
double t = (double)maturityArray.GetValue(i + 1, 1);
bonds.Add(new ZCBPricer(t, dt));
engine.sendPath += bonds[i].processPath;
engine.stopProcess += bonds[i].calculate;
}
//
// run Monte Carlo engine
engine.run();
//
// create output array and add zero-coupon prices
double[] zeroCouponCurve = newdouble[rows];
for (int i = 0; i < rows; i++) zeroCouponCurve[i] = bonds[i].price();
//
// print curve back to Excel
Excel.Range["_zcb"] = Excel.WorksheetFunction.Transpose(zeroCouponCurve);
}
catch (Exception e)
{
MessageBox.Show(e.Message.ToString());
}
}
}
}

One essential input for this application is the array of zero-coupon bond maturities, fetched directly from Excel worksheet. Time in years for the longest bond maturity is saved into local variable maxMaturity and time step length (dt) is calculated as maxMaturity divided by the number of steps. In the Excel example below, maxMaturity is 10 years, steps per simulation is 2500 and dt is 0.004. During the simulation process, MonteCarloEngine will then always simulate a path having maturity equal to maxMaturity (10.0) and having time step length equal to dt (0.004).

Now, for each simulated short-rate path, generated by MonteCarloEngine, ZCBPricer will get a path update (array of doubles) by its processPath method. ZCBPricer will then calculate zero-coupon bond price as integral of this short rate path. However, time length of short-rate path in years, provided by MonteCarloEngine (10.0), could be longer than the maturity of zero-coupon bond defined in ZCBPricer (double t). In order to process only the rates where time is less or equal to maturity of zero-coupon bond, local variable n (t / dt) is used in loop condition of pricer's processPath method. So, the pricer will always receive the whole path, but it picks only the items it needs.

In essence, most of the time our MonteCarloEngine is "working more than it should", since most of the bond maturities are less than maxMaturity and MonteCarloEngine will always simulate a path having maturity equal to maxMaturity. However, this design has been deliberate choice for this particular application, in order to maintain a scheme with only one engine plus n pricers. Needless to say, the price we pay for this particular scheme is increased processing time.


EXCEL-DNA INTEGRATION

After implementing all the previous cs files into C# Class Library project, we are receiving a lot of errors. However, all errors should be related to missing references to Excel-DNA integration library and Windows Forms library. Next, carefully follow the instructions described here in step two.

In a nutshell

  • add reference to Excel-DNA library (ExcelDna.Integration.dll). From the properties of this reference, set Copy Local to be False.
  • add reference to Windows Forms library (System.Windows.Forms)
  • create BondPricer.dna file (consisting XML tags). DnaLibrary Name="BondPricer" and Path="BondPricer.dll". From the properties of this dna file, set Copy to Output Directory to be Copy if newer.
  • copy ExcelDna.xll file into your project folder and rename it to be BondPricer.xll. From the properties of this xll file, set Copy to Output Directory to be Copy if newer.
Make sure, that all properties for these references and files are exactly the same as described in this post. After adding all the required references, files and building this program once again, my project folder has the following four files.










USER INTERFACE AND C#-VBA INTEGRATION

The essence of this part of the process has been described here in step three. Open a new Excel workbook. Create the following source data into worksheet. In the picture below, I have also included Excel formulas for calculating bond yield from zero-coupon bond price and calculating exact analytical price for Vasicek model (coefficients A, B).

















From Excel Name Manager (Formulas - Name Manager), set the following range names.














In VB editor, create the following event handling program for CommandButton (run).














TEST RUN

At this point, our zero-coupon bond pricing application is ready for test run. While the current Excel workbook is open, doubleClick BondPricer.xll file in your \\BondPricer\bin\Release folder. After this, xll file content can be used by Excel and our C# program is available to be called from VBA program (Application.Run). VBA program will call and start C# program run, which then reads all input data, performs calculations and sends result data back to Excel worksheet.

After pressing command button in Excel interface, my C# MC zero-coupon bond pricer application simulated the following prices for all requested maturities from 1 year to 10 years (Monte Carlo simulation).

















AFTERTHOUGHTS

This small project was presenting one possible Monte Carlo design for pricing zero-coupon bonds with one-factor short-rate processes. For this application, we basically just extended our existing flexible Monte Carlo design. Comparing our simulated bond prices with the exact analytical counterparties, we can conclude that our application is pricing bonds correctly, related to short-rate model used.

Thanks for reading and have a nice summer!
-Mike Juniperhill

Bootstrapping default probabilities from CDS prices in VBA

$
0
0
Default probabilities are needed when dealing with credit market models. This time, I wanted to present one simple algorithm for bootstrapping default probabilities from CDS market prices. Final product will be just one simple Excel/VBA worksheetfunction, which can be quickly copy-pasted and used in VBE standard module.


IMPLIED SURVIVAL PROBABILITY

Calculating implied survival probabilities from CDS prices follows the same idea, as calculating implied volatility from option price. For options, we have known market price, from which we can numerically solve the corresponding option volatility by using option pricing model. For CDS, we have known market price, from which we can solve the corresponding survival probability by using CDS pricing model. This is exactly the procedure, what this algorithm is doing. However, instead of just calculating one survival probability for a given CDS price, the algorithm is calculating all survival probabilities for a given CDS term structure. The pricing model for CDS is standard model (JP Morgan approach).


FUNCTION INPUT/OUTPUT

Zero-coupon bond prices, CDS prices and recovery rate assumption are needed as market data input for calculations. VBA function survivalProbability takes market information matrix (curves) and recovery rate assumption value (recovery) as input parameters. Function then returns an array of survival probabilities. Default probabilities can then be calculated from survival probabilities.

Input market information matrix (N x 3) should contain the following data in the following order:
  • 1st row vector - maturities in years
  • 2nd row vector - zero-coupon bond prices (ex. 0.9825)
  • 3rd row vector - CDS prices as basis points (ex. 0.25 % is given as 25)

After giving required input parameters for this function and selecting correct range for function output, remember to press CTRL+SHIFT+ENTER for retrieving result array (N x 1) into worksheet.


VBA FUNCTION


Option Explicit
'
' function takes market curves matrix (Nx3) and recovery rate (1x1) as arguments, then calculates and
' returns vector of survival probabilities (Nx1) for a given market data
' matrix input data order: 1st row vector = time, 2nd row vector = zero-coupon bond prices,
' 3rd row vector = cds rates in basis points
PublicFunction survivalProbability(ByRef curves As Range, ByVal recovery AsDouble) AsVariant
'
' information for dimensioning arrays
Dim nColumns AsInteger: nColumns = curves.Columns.Count
Dim nRows AsInteger: nRows = curves.Rows.Count
'
' create arrays for data
Dim p() AsDouble: ReDim p(0 To nRows)
Dim c() AsDouble: ReDim c(0 To curves.Rows.Count, 1 To curves.Columns.Count)
'
' copy variant array data into new array, having 1 additional item for today
c(0, 1) = 0: c(0, 2) = 1: c(0, 3) = 0
Dim cInput AsVariant: cInput = curves.Value2
'
Dim i AsInteger, j AsInteger, k AsInteger
For i = 1 To nRows
c(i, 1) = cInput(i, 1)
c(i, 2) = cInput(i, 2)
c(i, 3) = cInput(i, 3)
Next i
'
' calculation of survival probabilities (SP)
Dim L AsDouble: L = (1 - recovery)
Dim term AsDouble, terms AsDouble, divider AsDouble, term1 AsDouble, term2 AsDouble
'
For i = LBound(p) To UBound(p)
'
If (i = 0) Then p(i) = 1# ' SP today is one
If (i = 1) Then p(i) = L / ((c(i, 3) / 10000) * (c(i, 1) - c(i - 1, 1)) + L) ' first SP formula
'
If (i > 1) Then' SP after first period are calculated recursively
terms = 0
For j = 1 To (i - 1)
term = c(j, 2) * (L * p(j - 1) - (L + (c(j, 1) - c(j - 1, 1)) * (c(i, 3) / 10000)) * p(j))
terms = terms + term
Next j
'
divider = c(i, 2) * (L + (c(i, 1) - c(i - 1, 1)) * (c(i, 3) / 10000))
term1 = terms / divider
term2 = (p(i - 1) * L) / (L + (c(i, 1) - c(i - 1, 1)) * (c(i, 3) / 10000))
p(i) = term1 + term2
EndIf
Next i
'
' create output array excluding the first SP (for today)
Dim result() AsDouble: ReDim result(1 To UBound(p))
For i = 1 To UBound(p)
result(i) = p(i)
Next i
'
' finally, transpose output array (Nx1)
survivalProbability = Application.WorksheetFunction.Transpose(result)
EndFunction
'


CALCULATION EXAMPLE


The following Excel screenshot presents the calculation of default probabilities for Barclays and HSBC. Market data has been retrieved in early january 2014. VBA function input matrix (curves) has been marked with yellow color. Function output range has been marked with blue color. Default probability (PD) is calculated in column G.

















Thanks for reading.

-Mike

Bootstrapping OIS-adjusted Libor curve in VBA

$
0
0
OIS discounting has been hot topic for the last few years, since most of the collateralized OTC swaps are valued by this methodology. In this blog post, I will present simple example algorithm for bootstrapping OIS-adjusted Libor curve from market data (OIS zero-coupon curve, Libor par swap curve). This bootstrapped curve can then be used to generate floating leg cash flows, when valuing collateralized interest rate swap with all cash flows and collateral in the same currency. Final product will be just one simple VBA worksheet function to be used in Excel.

VALUATION 101

In essence
  • Instead of using Libor zero-coupon curve for cash flow discounting, all swap cash flows are present valued with discount factors calculated by using OIS zero-coupon curve. 
  • The use of OIS zero-coupon curve for discounting collateralized swap cash flows is justified, because posted collateral earns overnight rate and collateral value is mark-to-market value of a swap. In order to equate these two cash flows (collateral value, mark-to-market value of a swap), discount factor for both cash flows has to be calculated by using OIS curve.
  • Cash flows for swap fixed leg are still constructed by using ordinary Libor par swap rates. 
  • Another impact of OIS valuation hits into construction of floating leg coupon rates, which are technically forward rates.
  • In the "old world", we bootstrapped Libor zero-coupon curve, from which we calculated discount factors and forward rates (for constructing floating leg coupons) at the same time. Only one curve was needed to accomplish this procedure. 
  • Because all swap cash flows are now discounted with OIS zero-coupon curve and ordinary Libor par swap rates are still used for constructing swap fixed leg cash flows, forward rates have to be "adjusted" slightly, in order to equate present value of all swap cash flows to be zero.
  • Technically, we end up with a system of linear equations, in which we equate OIS-discounted floating cash flows with OIS-discounted fixed cash flows and solve for the unknown forward rates.
Material, which has helped me to understand this subject a bit better is the following: technical notes written by Justin Clarke, teaching material by Donald J. Smith and Barclays research paper by Amrut Nashikkar. These papers have worked numerical examples, as well as theoretical issues covered thoroughly.

VBA FUNCTION

 

Option Explicit
'
PublicFunction OIS_bootstrapping(ByRef curves As Range) AsVariant
'
' import source data from Excel range into matrix
Dim source AsVariant: source = curves.Value2
'
' create all the needed matrices and define dimensions
Dim nSwaps AsInteger: nSwaps = UBound(source, 1)
Dim fixed AsVariant: ReDim fixed(1 To nSwaps, 1 To 1)
Dim float AsVariant: ReDim float(1 To nSwaps, 1 To nSwaps)
Dim forward AsVariant: ReDim forward(1 To nSwaps, 1 To 1)
'
' counters and other temp variables
Dim i AsInteger, j AsInteger, k AsInteger, nCashFlows AsInteger
Dim OIS_DF AsDouble, OIS_Rate AsDouble, t AsDouble
'
' loop for cash flows processing
nCashFlows = nSwaps: k = 0
For i = 1 To nSwaps
'
' create OIS discount factor
OIS_Rate = source(i, 2): t = source(i, 1)
If (t <= 1) Then OIS_DF = 1 / (1 + (OIS_Rate * t))
If (t > 1) Then OIS_DF = 1 / (1 + OIS_Rate) ^ t
'
' create sum of fixed leg pv's for each individual swap and create all
' cash flows (excluding coupon rate) for floating legs for each individual swap
For j = 1 To nSwaps
If (j <= nCashFlows) Then
fixed(j + k, 1) = fixed(j + k, 1) + 100 * source(j + k, 3) * OIS_DF
float(i, j + k) = 100 * OIS_DF
Else
' replace empty array value with zero value
float(i, nSwaps - j + 1) = 0#
EndIf
Next j
'
k = k + 1: nCashFlows = nCashFlows - 1
Next i
'
' solve for implied forward rates, which are going to be used to generate coupons
' for floating legs. matrix operation: [A * x = b] ---> [x = Inverse(A) * b]
' where A = float (N x N), x = forward rates (N x 1), b = sum of swap fixed leg pv's (N x 1)
forward = WorksheetFunction.MMult(WorksheetFunction.MInverse(WorksheetFunction.transpose(float)), fixed)
OIS_bootstrapping = forward
EndFunction
'


EXAMPLE CALCULATION


The following Excel screenshot presents bootstrapped OIS-adjusted forward curve (column G) and OIS valuation for collateralized 2Y interest rate swap. For the sake of simplicity, this example assumes that the payments for the both fixed and floating legs takes place quarterly. Swap fixed cash flows has been constructed by using Libor par swap rates. Floating leg cash flows has been constructed by using bootstrapped OIS-adjusted forward curve. Finally, all cash flows are discounted by using OIS discount factors (column F). The present value of all swap cash flows is zero. Worksheet function input range has been marked with yellow color and function output range has been marked with blue color.



















Presented forward curve construction scheme applies to a specific case, in which collateralized interest rate swap has the both cash flow legs and collateral in the same currency. Moreover, it is assumed that the payment frequency is the same for the both swap legs. Successful replication of the forward curve bootstrapping result was achieved, when testing VBA worksheet function with the cases presented in above-mentioned papers by Smith and Clarke.

Thanks for reading.

-Mike

Calibration of short rate models in Excel with C#, Solver Foundation and Excel-DNA

$
0
0
This time, I wanted to present one possible solution for calibrating one-factor short interest rate model to market data. As we already know, generalized form for stochastic differential equation (SDE) for any one-factor short interest rate model is the following.




Where u and w are still undefined functions of r and t. This SDE must be solved either analytically (if such solution exists) or numerically (Monte Carlo, FDM). After solving the model, it can then be used to price different interest rate products.

The problem in these models lies in the model parameters (u, w). We could estimate those parameters from the set of market data, but in this case we (most probably) end up with a set of resulting theoretical bond prices, not matching with the corresponding market bond prices. In the case of valuing or hedging interest rate products, such prices would be then more or less useless. The second approach is to turn the whole scheme upside down. Instead of estimating parameters from the set of market data directly and then feeding those into model, we would solve a set of parameters in such a way, that our resulting theoretical bond prices will match exactly with the corresponding market bond prices. In this posting, we will solve that set of parameters numerically.

Just for a reference, full derivation of Bond Pricing Equation (BPE), plus some solutions for tractable short rate models are presented in the book written by Paul Wilmott, for example.

PROJECT OUTCOME

The end product of this small project will be C# program, which will solve numerically time-dependent theta parameters for a given short rate model (Ho-Lee as our example case) for a given set of market zero-coupon bond prices, assuming that time-dependent theta parameters are piecewise constant. C# program is using Microsoft Solver Foundation for performing the required optimization tasks. Finally, we will interface C# program into Excel workbook via VBA, with Excel-DNA.

ANALYTICAL SOLUTION FOR HO-LEE SHORT RATE MODEL


Ho-Lee model was the first no-arbitrage-type short rate model, which could be calibrated to market data. In this first section, we will go through the process of solving this SDE analytically. SDE for Ho-Lee model is the following.





Where theta is time-dependent drift coefficient (u), sigma is constant diffusion coefficient (w) and dx is the standard brownian motion. By replacing these coefficients into bond pricing equation (BPE), we get the following partial differential equation (PDE).






For this PDE, we are (very fortunately) looking for very specific solution, having the following functional form.




To solve our PDE above, we will substitute the previous solution into PDE. First, we calculate the following partial derivatives. Theta (in this case, partial derivative to time) is the following.






Delta is the following.






Gamma is the following.


After calculating partial derivatives, we substitute these into original BPE and divide all terms with V. Now we have the following PDE to be solved.






After separating all r-dependent terms and r-independent terms, we will have two ordinary differential equations (ODE) to be solved. For B coefficient, ODE is the following.





For A coefficient, ODE is the following.






After integrating ODE for B coefficient from t to T, we have the following solution.




After integrating ODE for A coefficient from t to T and substituting B coefficient into equation, we have the following solution.






We could still continue and find the exact analytical solution for time-dependent theta coefficient from this integral equation. However, for any real-life calibration purposes we have to make some assumptions concerning theta parameter. For the sake of easiness in calculations, we assume this time-dependent theta parameter to be piecewise constant. With this assumption, we get the following solution for A coefficient.






After integrating the last existing integral term, we have the following solution for A coefficient.





Finally, we have the following functional form for a zero-coupon bond price according to Ho-Lee, which can be used for calibration purposes.






From this equation, we could also solve for the unknown theta parameter analytically, without any numerical methods involved.







CALIBRATION

The pricing process with models usually starts with some already known parameters, which are then feeded into some model to calculate the results. For example, in the case of Ho-Lee model, we would know the values for spot rate, time-dependent theta, constant sigma and bond maturity. Then, by calculating coefficients A and B we would calculate zero-coupon bond price and the corresponding yield.

Concerning short rate models, these parameters are usually not known in advance and have to be estimated or calibrated. In the calibration process, we are performing the whole pricing process backwards. First, we know the market yield for a zero-coupon bond. From this yield we calculate the corresponding bond price. Assuming that we know (or have been estimating) the values for spot rate, maturity and constant sigma parameter, we can then solve for coefficient A and B. Finally, we can calculate the value for time-dependent theta parameter.

Consider the case, in which we have Libor zero curve as our input market data, consisting of N zero-coupon bond yields. From this curve, we calculate the corresponding N zero-coupon bond prices. Consider also, that we have statistically estimated the value for constant sigma (standard deviation of a short rate) and we know spot rate and N maturities. After this, we set N initial values for our piecewise constant theta parameters. By using these theta parameters, we then calculate N pairs of A and B coefficients. Finally, N theoretical bond prices and N theoretical yields can be calculated by using N pairs of A and B coefficients. In the final stage, we just adjust our initial values for our N piecewise constant theta parameters, until theoretical yields and corresponding market yields are equal for all N bonds. More specifically, we minimize the sum of absolute squared errors between theoretical yields and corresponding market yields. This part is just unconstrained minimization problem. As a result of this minimization we will solve N theta parameters, for which theoretical bond prices are matching exactly with market bond prices. In other words, we are calibrating our model to market data.

As already noted above, we are dealing with the model having analytical solution (affine or tractable models). In this case, there is actually no need for any numerical method when calibrating model parameters to market. However, for the sake of curiosity and craftmanship, we will perform this calibration process numerically.


PREPARATORY TASKS

Next, we have to get the tools for performing optimization needed in the calibration process. As a proud holder of the NAG licence, I would use these numerical libraries for the task. However, in this project we will use Microsoft Solver Foundation, since it is freely available without any costs involved. Next, download Solver Foundation solver (32-bit). If this package is completely alien for you, there are excellent hands-on tutorials available, written by Mathias Brandewinder in here and in here. Replicating and studying these examples will get you started with Solver Foundation.

Also, download and unzip Excel-DNA Version 0.30 zip file to be ready when needed. This is the version I have been using so far, but I assume you can also use newer version 0.32. Concerning the tools needed, we are now ready to move on to create the actual C# program.


PROGRAM DESIGN

Design for C# program has been presented in the following UML chart. In order to understand better what is happening between the objects, some comments are given inside the blue boxes. The main point in this design is its flexibility to handle new one-factor models. This example has been specifically made using Ho-Lee model. However, user is able to create new implementations for CIR or Vasicek model, if needed.



















C# PROGRAM

Create a new C# Class project "SolverApp_25092014". Target framework is .NET Framework 4. Remember also to create a new reference into SolverFoundation dll file (Project - Add reference - Browse - \Microsoft.Solver.Foundation.dll). My dll file is found within the following folder: C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Microsoft.Solver.Foundation.dll. The workhorse classes for this program are presented below. Create new class files and copyPaste the following codes into separate classes.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SolverFoundation.Services;

namespace SolverApp_25092014
{
publicinterface IOneFactorModel
{
Term price(Term parameter, Term t);
}
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SolverFoundation.Services;

namespace SolverApp_25092014
{
publicabstractclassOneFactorModel : IOneFactorModel
{
publicabstract Term price(Term parameter, Term t);
}
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SolverFoundation.Services;

namespace SolverApp_25092014
{
publicclassHoLee : OneFactorModel
{
private Term spot;
private Term stdev;
//
public HoLee(double spot, double stdev)
{
this.stdev = stdev; this.spot = spot;
}
publicoverride Term price(Term parameter, Term t)
{
// create term object for Ho-Lee bond price for a given eta and t
Term B = t;
Term c1 = Model.Quotient(1, 2);
Term c2 = Model.Quotient(1, 6);
Term A = -(parameter * (c1 * Model.Power(t, 2))) + c2 * Model.Power(stdev, 2) * Model.Power(t, 3);
return Model.Exp(A - spot * B);
}
}
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SolverFoundation.Services;

namespace SolverApp_25092014
{
publicclassOneFactorCalibrator
{
private OneFactorModel model;
private Dictionary<double, double> curve;
//
public OneFactorCalibrator(OneFactorModel model, Dictionary<double, double> curve)
{
this.model = model; this.curve = curve;
}
public List<double> calibrate()
{
// create a new solver model
SolverContext solver = new SolverContext();
solver.ClearModel();
Model solverModel = solver.CreateModel();
//
// create decision variables, a set of parameters to be calibrated
var decisions = curve.Select(it => new Decision(Domain.Real, "parameter_" + it.Key.ToString()));
solverModel.AddDecisions(decisions.ToArray());
//
// create objective function as sum of differences between market and theoretical yields
SumTermBuilder terms = new SumTermBuilder(curve.Count);
int i = 0;
foreach (KeyValuePair<double, double> kvp in curve)
{
// define required term objects
Term eta = solverModel.Decisions.ElementAt(i++);
Term t = kvp.Key;
//
// calculate bond prices and yields
Term theoreticalPrice = model.price(eta, t);
Term theoreticalYield = Model.Power(Model.Quotient(1, theoreticalPrice), Model.Quotient(1, t)) - 1;
Term marketPrice = kvp.Value;
Term marketYield = Model.Power(Model.Quotient(1, marketPrice), Model.Quotient(1, t)) - 1;
Term yieldDifference = Model.Power(marketYield - theoreticalYield, 2);
//
// add constructed term into sumbuilder (objective function)
terms.Add(yieldDifference);
}
//
// define optimization goal, solve the model and pack results into list
solverModel.AddGoal("solverGoal", GoalKind.Minimize, Model.Abs(terms.ToTerm()));
Solution result = solver.Solve();
return solverModel.Decisions.Select(it => it.ToDouble()).ToList();
}
}
}

At this point, we should be able to build this class project without any errors or warnings. The only thing that might create a bit of confusion here, is the logic of using Solver Foundation and especially its Term object. If you feel uncomfortable using solver objects, you may spend some quality time and go through examples by Mathias Brandewinder mentioned earlier. Also, going through the stuff presented in Microsoft.Solverfoundation.Services Namespace relieves the painful climbing on this new learning curve. There are also some documentation pdf files included in the downloaded package from Microsoft. Anyway, the basic functionality for calibration process has now been set. Next, we have to interface our C# program and Excel.


Excel-DNA

Concerning the usage and creating interface between Excel and C#, we are following instructions presented in this blog posting.

Add reference to Excel-DNA library (Project - Add reference - Browse - \\ExcelDna.Integration.dll) and click OK. This dll file is inside the distribution folder what we just downloaded from Excel-DNA  website. From the properties of this reference, set Copy Local to be False.

Add new file as text file to project (Project - Add new item - Text file) and name it to be SolverApp_25092014.dna. CopyPaste the following xml code into this file.

<DnaLibrary Name="SolverApp_25092014" RuntimeVersion="v4.0">
<ExternalLibrary Path="SolverApp_25092014.dll" />
</DnaLibrary>

From the properties of this dna file, set Copy to Output Directory to be Copy if newer.

Next, from the downloaded Excel-DNA folder (Distribution), copy ExcelDna.xll file into your project folder (\\Projects\SolverApp_25092014\SolverApp_25092014) and rename it to be SolverApp_25092014.xll. Then, add this xll file into your current project (Project - Add existing item). At this point, it might be that you do not see anything else, except cs files on this window. From drop down box on the bottom right corner of this window, select All files and you should see ExcelInterface.xll file what we just pasted into this ExcelInterface folder. Select this file and press Add. Finally, from the properties of this xll file, set Copy to Output Directory to be Copy if newer.

Build the solution. Again, everything should have gone well without any errors or warnings. At this point, my \\SolverApp_25092014\bin\Release folder looks like the following.





Next, we start to build interface between C# program and Excel workbook, via VBA


EXCEL USER INTERFACE AND C#-VBA INTEGRATION

Add new class into project and name it to be "SolverApp_25092014". Remember also, that we might need to use Message Box for error reporting coming from catch block. Hence, we need to create reference to System.Windows.Forms library (Project - Add reference - .NET - System.Windows.Forms). CopyPaste the following code into this new class file. After this, build solution again and save it. We should be able to rebuild this class project again without any errors or warnings.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ExcelDna.Integration;

namespace SolverApp_25092014
{
publicstaticclassSolverApp_25092014
{
publicstaticvoid calibrate()
{
try
{
// create Excel application object
dynamic Excel = ExcelDnaUtil.Application;
//
// read scalar parameters from named ranges
double r = (double)Excel.Range["_r"].Value2;
double stdev = (double)Excel.Range["_stdev"].Value2;
//
// read curve from named range
dynamic Matrix = Excel.Range["_curve"].Value2;
int rows = Matrix.GetUpperBound(0);
//
// export curve data into dictionary
Dictionary<double, double> curve = new Dictionary<double, double>();
for (int i = 0; i < rows; i++)
{
double key = (double)Matrix.GetValue(i + 1, 1);
doublevalue = (double)Matrix.GetValue(i + 1, 2);
curve.Add(key, value);
}
//
// create model and calibrator instances
OneFactorModel model = new HoLee(r, stdev);
OneFactorCalibrator calibrator = new OneFactorCalibrator(model, curve);
List<double> theta = calibrator.calibrate();
//
// export theta parameters into 2-dimensional array
double[,] result = newdouble[rows, 1];
for (int i = 0; i < theta.Count; i++) result[i, 0] = theta[i];
//
// print array into named range
Excel.Range["_theta"] = result;
}
catch (Exception e)
{
MessageBox.Show(e.Message.ToString());
}
}
}
}


We are almost there. Next, we will create Excel worksheet interface and VBA "trigger program" for C# program. The action starts as the user is pressing commandbutton, which then calls a simple VBA program, which then triggers the actual C# workhorse program. Create the following data view into a new Excel worksheet. Also, insert command button into worksheet.



















A few words concerning this worksheet. Zero-coupon bond curve maturities and prices are given in the columns B and C (yellow). Corresponding yields are calculated in the column D. Vector of time-dependent piecewise constant thetas will be calibrated and printed into column F (yellow). Columns from G to K are using analytical solution for Ho-Lee model to calculate theoretical zero-coupon bond prices and yields. Squared differences between market yields and calculated theoretical yields are given in the column K.

Input (output) data for (from) C# program are going to be read (written) directly from (to) Excel named ranges. Create the following named Excel ranges.






















Finally, insert a new standard VBA module and create simple program calling the actual C# program (Sub tester). Also, create event-handling program for commandbutton click, which then calls the procedure shown below.














Concerning the program and its interfacing with Excel, all those small pieces have finally been assembled together. Now, it is time to do the actual calibration test run.


TEST RUN AND MODEL VALIDATION

In this section we will perform test run and check, whether our calibration model is working properly. In another words, are we able to replicate the market with our model. After pressing "calibrate" button, my Excel shows the following calibration results for theta parameters shown in column F.





















Analytical results are already calculated in the worksheet (columns G to J). For example, time-dependent theta parameter for 4 years is 0.00771677. With this theta value embedded in A coefficient will produce zero-coupon bond price, equal to corresponding market price.

How about using SDE directly with these calibrated time-dependent thetas? Let us use Monte Carlo to price 4-year zero-coupon bond by using SDE directly. For the sake of simplicity, we perform this exercise in Excel. Simulation scheme is presented in the screenshot below.




















In this Excel worksheet presented above, 4-year period has been divided into 800 time steps, which gives us the size for one time step to be 0.005 years. Total of 16 378 paths was simulated by using Ho-Lee SDE with calibrated time-dependent piecewise constant theta parameter. All simulated zero-coupon bond prices have been calculated in the row 805. By taking the average for all these 16 378 simulated zero-coupon bond prices, gives us the value of 0.9343. Needless to say this value is  random variable, which changes every time we press that F9 button. Our corresponding market price for 4-year zero-coupon bond is 0.9344, so we are not far away from that value. We can make the conclusion, that our calibration is working as expected.

Thanks for reading this blog.
-Mike

Write/read dynamic matrix between Excel and C# with Excel-DNA

$
0
0
This time I wanted to share some quick technical information about how to write or read dynamic matrix between Excel and C# program with Excel-DNA. The scheme what I am presenting here, applies to the case in which we use Excel as input/output platform for some C# program. By saying dynamic matrix, I am referring to the case, in which the dimensions of the input or output matrix are not known at compile time.

To make the long story shorter, you can use the existing program already presented in this blog posting as a base for this new example program. Just replace the content of ExcelInterface.cs file with the information given below.


C# PROGRAM

using System;
using ExcelDna.Integration;
using System.Windows.Forms;
//
namespace ExcelInterface
{
publicstaticclassExcelInterface
{
privatestaticdynamic Excel;
//
publicstaticvoid execute()
{
try
{
// Create Excel application and random number generator
Excel = ExcelDnaUtil.Application;
Random random = new Random(Guid.NewGuid().GetHashCode());
//
//
//
// CASE 1 : WRITE DYNAMIC MATRIX TO EXCEL
int rows = (int)Excel.Range("_rows").Value2;
int cols = (int)Excel.Range("_cols").Value2;
double[,] matrix = newdouble[rows, cols];
//
// fill matrix with random numbers
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
matrix[i, j] = random.NextDouble();
}
}
// clear old data from output range by using Range.CurrentRegion property
Excel.Range["_output"].CurrentRegion = "";
//
// resize output range to match with the dimensions of newly created random matrix
Excel.Range["_output"].Resize[rows, cols] = matrix;
//
//
//
// CASE 2 : READ DYNAMIC MATRIX FROM EXCEL
object[,] input = (object[,])Excel.Range["_output"].CurrentRegion.Value2;
rows = input.GetUpperBound(0);
cols = input.GetUpperBound(1);
double sum = 0.0;
//
// sum all matrix items, calculate average and write it back to Excel
foreach (object item in input) sum += (double)item;
Excel.Range["_average"] = (sum / (rows * cols));
}
catch (Exception e)
{
MessageBox.Show(e.Message.ToString());
}
}
}
}


In a nutshell, after creating ExcelDnaUtil.Application, we are able to use some extremely useful properties for manipulating Excel.Range object, such as CurrentRegion, Offset and Resize.

EXCEL WORKSHEET SETUPS


Dimensions for dynamic matrix output are given by the user in cells F4 and F5. Output matrix is printed to cell B8 (top left cell of output matrix). Input matrix is the same as output matrix and the C#-processed output (arithmetic average of all input matrix values) is printed into cell F6.

























We have to set a couple of named Excel ranges for C# program. All the needed four named ranges are given in the screenshot below.










After implementing these range names, create a new button (Forms Controls) to this worksheet and put our C# program name into Macro box (execute). By doing this, we can get rid of the VBA code (Application.Run) completely. Any public static void method in our .NET code will be registered by Excel-DNA as a macro in Excel. Thanks for this tip to Govert Van Drimmelen (the inventor and author of Excel-DNA). After implementing all these steps, we are ready to use the program.

The purpose of this posting was to show how to handle dynamic data IO to/from Excel in C# program with Excel-DNA. For learning more things about Excel-DNA, check out its homepage. Getting more information and examples with your problems, the main source is Excel-DNA google group. Excel-DNA is an open-source project, and we (the happy users) can invest its future development by making a donation.

Thanks for reading my blog and happy waiting until Christmas.

-Mike

Multi-Threaded Copula producer in C#

$
0
0
I wanted to publish one more article for the sake of celebrating the end of the year 2014. This time I am opening one possible solution for producing multi-threaded correlated random numbers with Gaussian Copula in C#. The numerical scheme used for this procedure has already been opened well enough in this blog posting. Usually, programs presented in this blog have been having (more or less) something to do with Microsoft Excel. This time, there is only C# program. However, with all the example programs presented in this blog, the user should be able to interface this program back to Excel in many different ways, if so desired.

PRODUCER-CONSUMER


In this simple example program, Producer-Consumer Pattern has been used. CopulaQueue class is nothing more, than just a wrapper for thread-safe ConcurrentQueue data structure. More specifically, the part of the program which actually creates all correlated random numbers into CopulaQueue, is the Producer. In the program, concrete CopulaEngine implementation class takes concrete RandomGenerator implementation class and correlation matrix as its input parameters. Concrete CopulaEngine implementation class then creates correlated random numbers and uses C# Event for sending created random number matrices to CopulaQueue. Another part of the program is the Consumer. Here, I have created a simple consumer class which simply prints created random numbers to console. Consumer class also uses C# Event for Dequeueing random number matrices from CopulaQueue. Needless to say, this (Consumer) is the part of our program, where we should be utilizing all correlated random numbers created by the Producer.

On design side, this program is closely following the program presented in the blog posting referred above. Again, the beef in this design is on its flexibility to allow any new implementations for Random generator and Copula model. Moreover, our example program is using C# Framework Parallel class method Parallel.ForEach, found under System.Threading.Tasks namespace. Example program creates four producers and consumers (both into List data structure) and using multi-threading (Parallel.ForEach) when processing correlated random numbers into/from CopulaQueue.


C# PROGRAM

In order to make everything as smooth as possible for the program replication, I have been writing all required classes in the same file. Just create a new console application and copy-paste the following program into a new .cs file.

using System;
using System.Diagnostics;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Collections.Concurrent;
//
namespace MultithreadingCopula
{
// public delegate methods needed for sending and receiving matrix data
publicdelegatevoid MatrixSender(double[,] matrix);
publicdelegatedouble[,] MatrixReceiver();
// ********************************************************************
//
// the main program
classProgram
{
staticvoid Main(string[] args)
{
try
{
// start timer
Stopwatch timer = new Stopwatch();
timer.Start();
//
// PRODUCER PART
// create copula implementation, correlation matrix data and list of copula engines
CopulaQueue copulaQueue = new CopulaQueue();
double[,] correlationMatrix = createCorrelationMatrix();
List<CopulaEngine> engines = new List<CopulaEngine>();
int nEngines = 4;
int nRandomArrays = 25000;
for (int i = 0; i < nEngines; i++)
{
engines.Add(new GaussianCopula(nRandomArrays, correlationMatrix, new CSharpRandom()));
((GaussianCopula)engines[i]).SendMatrix += copulaQueue.Enqueue;
}
// process all copula engines (producers) in parallel
Parallel.ForEach(engines, engine => engine.Process());
//
// CONSUMER PART
// create list of consumers
List<Consumer> consumers = new List<Consumer>();
int nConsumers = nEngines;
for (int i = 0; i < nConsumers; i++)
{
consumers.Add(new Consumer(nRandomArrays, "consumer_" + (i + 1).ToString()));
consumers[i].ReceiveMatrix += copulaQueue.Dequeue;
}
// process all consumers in parallel
Parallel.ForEach(consumers, consumer => consumer.Process());
//
// stop timer, print elapsed time to console
timer.Stop();
Console.WriteLine("{0} {1}", "time elapsed", timer.Elapsed.TotalSeconds.ToString());
}
catch (AggregateException e)
{
Console.WriteLine(e.Message.ToString());
}
}
// hard-coded data for correlation matrix
staticdouble[,] createCorrelationMatrix()
{
double[,] correlation = newdouble[5, 5];
correlation[0, 0] = 1.0; correlation[0, 1] = 0.748534249620189;
correlation[0, 2] = 0.751; correlation[0, 3] = 0.819807151586399;
correlation[0, 4] = 0.296516443152486; correlation[1, 0] = 0.748534249620189;
correlation[1, 1] = 1.0; correlation[1, 2] = 0.630062355599386;
correlation[1, 3] = 0.715491543000545; correlation[1, 4] = 0.329358787086333;
correlation[2, 0] = 0.751; correlation[2, 1] = 0.630062355599386;
correlation[2, 2] = 1.0; correlation[2, 3] = 0.758158276137995;
correlation[2, 4] = 0.302715778081627; correlation[3, 0] = 0.819807151586399;
correlation[3, 1] = 0.715491543000545; correlation[3, 2] = 0.758158276137995;
correlation[3, 3] = 1.0; correlation[3, 4] = 0.286336231914179;
correlation[4, 0] = 0.296516443152486; correlation[4, 1] = 0.329358787086333;
correlation[4, 2] = 0.302715778081627; correlation[4, 3] = 0.286336231914179;
correlation[4, 4] = 1.0;
return correlation;
}
}
// ********************************************************************
//
// consumer class for correlated random numbers
publicclassConsumer
{
publicevent MatrixReceiver ReceiveMatrix;
publicstring ID;
privateint n;
//
public Consumer(int n, string ID) { this.n = n; this.ID = ID; }
publicvoid Process()
{
for (int i = 0; i < n; i++)
{
// get correlated 1xM random matrix from CopulaQueue and print it to console
double[,] matrix = ReceiveMatrix();
Console.WriteLine("{0} {1} {2} {3} {4} {5}", this.ID, Math.Round(matrix[0, 0], 4),
Math.Round(matrix[0, 1], 4), Math.Round(matrix[0, 2], 4),
Math.Round(matrix[0, 3], 4), Math.Round(matrix[0, 4], 4));
//
}
}
}
// ********************************************************************
//
// abstract base class for all uniform random generators
publicabstractclassRandomGenerator
{
publicabstractdouble GetUniformRandom();
}
// ********************************************************************
//
// uniform random generator implementation by using CSharp Random class
publicclassCSharpRandom : RandomGenerator
{
private Random generator;
//
public CSharpRandom()
{
// using unique GUID hashcode as a seed
generator = new Random(Guid.NewGuid().GetHashCode());
}
publicoverridedouble GetUniformRandom()
{
// Random.NextDouble method returns a double greater than or equal to 0.0
// and less than 1.0. Built-in checking procedure prevents out-of-bounds random
// number to be re-distributed further to CopulaEngine implementation.
double rand = 0.0;
while (true)
{
rand = generator.NextDouble();
if (rand > 0.0) break;
}
return rand;
}
}
// ********************************************************************
//
// abstract base class for all specific copula implementations
publicabstractclassCopulaEngine
{
// Uniform random generator and correlation matrix are hosted in this abstract base class.
// Concrete classes are providing algorithm implementation for a specific copula
// and event mechanism for sending created correlated random numbers matrix data to CopulaQueue class.
protected RandomGenerator generator;
protecteddouble[,] correlation;
//
public CopulaEngine(double[,] correlation, RandomGenerator generator)
{
this.correlation = correlation;
this.generator = generator;
}
publicabstractvoid Process();
}
// ********************************************************************
//
// concrete implementation for gaussian copula
publicclassGaussianCopula : CopulaEngine
{
publicevent MatrixSender SendMatrix;
privatedouble[,] normalRandomMatrix;
privatedouble[,] choleskyMatrix;
privatedouble[,] correlatedNormalRandomMatrix;
privatedouble[,] correlatedUniformRandomMatrix;
privateint rows;
privateint cols;
//
public GaussianCopula(int n, double[,] correlation, RandomGenerator generator)
: base(correlation, generator)
{
this.cols = base.correlation.GetUpperBound(0) + 1; // M, number of variables
this.rows = n; // N, number of 1xM matrices
}
publicoverridevoid Process()
{
// NxM matrix containing of uniformly distributed correlated random variables are calculated
// as a sequence of specific matrix operations
createNormalRandomMatrix();
createCholeskyMatrix();
correlatedNormalRandomMatrix = MatrixTools.Transpose(MatrixTools.Multiplication(choleskyMatrix, MatrixTools.Transpose(normalRandomMatrix)));
createCorrelatedUniformRandomMatrix();
}
privatevoid createNormalRandomMatrix()
{
// create NxM matrix consisting of normally distributed independent random variables
normalRandomMatrix = newdouble[rows, cols];
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
normalRandomMatrix[i, j] = StatTools.NormSInv(base.generator.GetUniformRandom());
}
}
}
privatevoid createCholeskyMatrix()
{
// create lower-triangular NxM cholesky decomposition matrix
choleskyMatrix = newdouble[cols, cols];
double s = 0.0;
//
for (int j = 1; j <= cols; j++)
{
s = 0.0;
for (int k = 1; k <= (j - 1); k++)
{
s += Math.Pow(choleskyMatrix[j - 1, k - 1], 2.0);
}
choleskyMatrix[j - 1, j - 1] = base.correlation[j - 1, j - 1] - s;
if (choleskyMatrix[j - 1, j - 1] <= 0.0) break;
choleskyMatrix[j - 1, j - 1] = Math.Sqrt(choleskyMatrix[j - 1, j - 1]);
//
for (int i = j + 1; i <= cols; i++)
{
s = 0.0;
for (int k = 1; k <= (j - 1); k++)
{
s += (choleskyMatrix[i - 1, k - 1] * choleskyMatrix[j - 1, k - 1]);
}
choleskyMatrix[i - 1, j - 1] = (base.correlation[i - 1, j - 1] - s) / choleskyMatrix[j - 1, j - 1];
}
}
}
privatevoid createCorrelatedUniformRandomMatrix()
{
// map normally distributed numbers back to uniform plane
// process NxM matrix row by row
for (int i = 0; i < rows; i++)
{
// create a new 1xM matrix for each row and perform uniform transformation
correlatedUniformRandomMatrix = newdouble[1, cols];
for (int j = 0; j < cols; j++)
{
correlatedUniformRandomMatrix[0, j] = StatTools.NormSDist(correlatedNormalRandomMatrix[i, j]);
}
// event : send transformed 1xM matrix to CopulaQueue
this.SendMatrix(correlatedUniformRandomMatrix);
}
}
}
// ********************************************************************
//
// this is technically just a wrapper class for ConcurrentQueue data structure
publicclassCopulaQueue
{
private ConcurrentQueue<double[,]> queue;
public CopulaQueue()
{
queue = new ConcurrentQueue<double[,]>();
}
publicvoid Enqueue(double[,] matrix)
{
// insert a matrix into data structure
queue.Enqueue(matrix);
}
publicdouble[,] Dequeue()
{
// remove a matrix from data structure
double[,] matrix = null;
bool hasValue = false;
while (!hasValue) hasValue = queue.TryDequeue(out matrix);
return matrix;
}
}
// ********************************************************************
//
// collection of methods for different types of statistical calculations
publicstaticclassStatTools
{
// rational approximation for the inverse of standard normal cumulative distribution.
// the distribution has a mean of zero and a standard deviation of one.
// source : Peter Acklam web page home.online.no/~pjacklam/notes/invnorm/
publicstaticdouble NormSInv(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("input parameter out of bounds");
}
return c;
}
publicstaticdouble NormSDist(double x)
{
// returns the standard normal cumulative distribution function.
// the distribution has a mean of zero and a standard deviation of one.
double sign = 1.0;
if (x < 0) sign = -1.0;
return 0.5 * (1.0 + sign * errorFunction(Math.Abs(x) / Math.Sqrt(2.0)));
}
privatestaticdouble errorFunction(double x)
{
// uses Hohner's method for improved efficiency.
constdouble a1 = 0.254829592; constdouble a2 = -0.284496736;
constdouble a3 = 1.421413741; constdouble a4 = -1.453152027;
constdouble a5 = 1.061405429; constdouble a6 = 0.3275911;
x = Math.Abs(x); double t = 1 / (1 + a6 * x);
return 1 - ((((((a5 * t + a4) * t) + a3) * t + a2) * t) + a1) * t * Math.Exp(-1 * x * x);
}
}
// ********************************************************************
//
// collection of methods for different types of matrix operations
publicstaticclassMatrixTools
{
publicstaticdouble[,] Transpose(double[,] matrix)
{
// method for transposing matrix
double[,] result = newdouble[matrix.GetUpperBound(1) + 1, matrix.GetUpperBound(0) + 1];
//
for (int i = 0; i < matrix.GetUpperBound(0) + 1; i++)
{
for (int j = 0; j < matrix.GetUpperBound(1) + 1; j++)
{
result[j, i] = matrix[i, j];
}
}
return result;
}
publicstaticdouble[,] Multiplication(double[,] matrix1, double[,] matrix2)
{
// method for matrix multiplication
double[,] result = newdouble[matrix1.GetUpperBound(0) + 1, matrix2.GetUpperBound(1) + 1];
double v = 0.0;
//
for (int i = 0; i < matrix1.GetUpperBound(0) + 1; i++)
{
for (int j = 0; j < matrix2.GetUpperBound(1) + 1; j++)
{
v = 0.0;
for (int k = 0; k < matrix1.GetUpperBound(1) + 1; k++)
{
v += matrix1[i, k] * matrix2[k, j];
}
result[i, j] = v;
}
}
return result;
}
}
}


TEST RUN

When running the program, I get the following console printout. The printout shows, how our four consumers used in the example program are Dequeueing correlated 1x5 random matrices from CopulaQueue in parallel.

























EFFICIENCY AND CORRELATION

One million rows for 5x5 correlation matrix was processed 100 times, when testing efficiency of the producer part of the program. More specifically, four producers (CopulaEngine) are creating 1x5 matrices, consisting of five correlated uniformly distributed random numbers one by one and then sending these matrices to ConcurrentQueue wrapped in CopulaQueue class in parallel. Each of the four producers are then performing this operation 250 thousand times. Finally, the whole operation was repeated 1 hundred times and time elapsed for each repetition was saved for further analysis. The results of this experiment are reported in the table below.


As we can see in the table, multi-threading is nicely reducing processing time, but in a non-linear fashion. Next, I printed out one batch of correlated random numbers (1000000x5 matrix) into text file and compared theoretical and simulated correlation in Excel. Results are reported in the table below. We can see, that simulated correlation is in line with theoretical correlation (enough, IMHO).


READING REFERENCES


For me personally, the following two books are standing in their own class. Ben and Joseph Albahari C# in a nutshell is presenting pretty much everything one has to know (and a lot more) about C# and especially on .NET framework. Very nice thing is also, that Albahari brothers have been kindly offering a free chapter on multi-threading from this book on their website. Daniel Duffy and Andrea Germani C# for financial markets is presenting how to apply C# in financial programs. There are a lot of extremely useful examples on multi-threaded programs, especially on using multi-threading in Monte Carlo applications and the general usage of Producer-Consumer pattern, when using multi-threading.

Again, Big thanks for reading my blog and Happy and Better New Year 2015 for everybody.

-Mike

Bloomberg API Wrapper for C#

$
0
0
As a starter for the year 2015, I wanted to share the current version of my C# wrapper, which can be used to execute reference and historical data requests for Bloomberg BBCOMM server.

In the past, I have been posting all updates for corresponding VBA wrapper in separate blog posts and I think some people have been finding this procedure to be at least inconvinient. To be better prepared for those program modifications and bug fixes which will be found out in the future, I will sanctify this one blog post for all postings concerning Bloomberg API Wrapper class.

Release date : 21-Mar-2015

Client program can use this wrapper to execute reference and historical data requests for BBCOMM server. Design of the wrapper class is simple. We have abstract base class BBCOMMDataRequest, which is holding all the required constants, enumerators, BBCOMM objects and data structures. Base class is implementing most of the required functionality, such as
  • Creating and starting BBCOMM session
  • Construction of Request object
  • Sending Request object to BBCOMM server
  • Closing BBCOMM session
For all of the functionalities described above, one can easily find well-written documentations to work with from Bloomberg. However, one of the values what I am trying to add with this wrapper is the one, which is always missing in these otherwise great documentations: how to pack all requested data into data structures to be easily used by the client?

After a few design iterations and brainstorming moments, I concluded that the program is actually the same for both reference and historical data requests for the most parts. Only relevant difference is algorithm implementation for iterating BBCOMM response and packing values into result data structure. After some further woodshedding, I finally decided to create the design, in which the base class is handling all the common functionalities and derived classes (ReferenceDataRequest, HistoricalDataRequest) are handling the specific algorithm implementation for iterating and packing. Client will have an access to constructors of derived classes, in which securities, fields, overrides, dates and all the other optional parameters are given in for wrapper.

Client will create dynamically-typed 3-dimensional array, into which the resulting data will be assigned by ProcessData method of wrapper. More specifically, for

  • Reference data request : result[1, 0, 1] = 2nd security, 2nd field
  • Historical data request : result[3, 1, 2] = 4th security, 2nd observation date, 3rd field. For the third dimension of the array, index value zero will always store the observation date

All relevant examples can be found in tester program presented below along with the wrapper. To be able to run tester program, one has to create a new console project and copyPaste the program given below. Moreover, project must have reference to Bloomberglp.Blpapi.dll file. For further development and testing purposes, you can use the actual Bloomberg terminal or check out Bloomberg API Emulator.

That's all for now. Thanks for reading my blog. Mike.



Wrapper : 21-Mar-2015

using System;
using System.Collections.Generic;
using System.Linq;
using BBCOMM = Bloomberglp.Blpapi;
using BloombergAPI;
//
// CLIENT PROGRAM
namespace TesterProgram
{
classProgram
{
// data structures
static List<string> bloombergSecurityNames;
static List<string> bloombergFieldNames;
static List<string> bloombergOverrideFields;
static List<string> bloombergOverrideValues;
staticdynamic[, ,] result;
//
staticint Main()
{
try
{
Console.WriteLine("CASE 1 : create reference data request without overrides >");
Console.WriteLine();
//
// create securities
bloombergSecurityNames = new List<string>();
bloombergSecurityNames.Add("GOOG US Equity");
bloombergSecurityNames.Add("CSCO US Equity");
bloombergSecurityNames.Add("CCE US Equity");
//
// create fields
bloombergFieldNames = new List<string>();
bloombergFieldNames.Add("PX_LAST");
bloombergFieldNames.Add("BEST_EPS");
bloombergFieldNames.Add("GICS_SUB_INDUSTRY_NAME");
bloombergFieldNames.Add("EQY_OPT_AVAIL");
bloombergFieldNames.Add("LATEST_ANN_DT_ANNUAL");
bloombergFieldNames.Add("PX_ROUND_LOT_SIZE");
bloombergFieldNames.Add("MIKE_JUNIPERHILL_SHOE_SIZE");
//
// create wrapper object, retrieve and print data
BBCOMMDataRequest wrapper = new ReferenceDataRequest(bloombergSecurityNames, bloombergFieldNames);
result = wrapper.ProcessData();
Print(bloombergSecurityNames, result);
//
Console.WriteLine("CASE 2 : create reference data request with override >");
Console.WriteLine();
//
// re-create fields
bloombergFieldNames = new List<string>();
bloombergFieldNames.Add("PX_LAST");
bloombergFieldNames.Add("BEST_EPS");
//
// create override for best fiscal period
bloombergOverrideFields = new List<string>();
bloombergOverrideFields.Add("BEST_FPERIOD_OVERRIDE");
bloombergOverrideValues = new List<string>();
bloombergOverrideValues.Add("2FY");
//
// retrieve and print data
wrapper = new ReferenceDataRequest(bloombergSecurityNames, bloombergFieldNames, bloombergOverrideFields, bloombergOverrideValues);
result = wrapper.ProcessData();
Print(bloombergSecurityNames, result);
//
Console.WriteLine("CASE 3 : create historical data request for one security >");
Console.WriteLine();
//
bloombergSecurityNames = new List<string>();
bloombergSecurityNames.Add("GOOG US Equity");
//
// re-create field
bloombergFieldNames = new List<string>();
bloombergFieldNames.Add("PX_LAST");
//
// create dates
DateTime startDate = DateTime.Today.AddDays((double)-21);
DateTime endDate = DateTime.Today.AddDays((double)-1);
//
// retrieve and print data
// request time-series for Google : actual daily frequency, but only for weekdays and converted to Thai baht
wrapper = new HistoricalDataRequest(bloombergSecurityNames, bloombergFieldNames, startDate, endDate,
bloombergNonTradingDayFillOption: E_NON_TRADING_DAY_FILL_OPTION.NON_TRADING_WEEKDAYS,
bloombergOverrideCurrency: "THB");
//
result = wrapper.ProcessData();
Print(bloombergSecurityNames, result);
//
Console.WriteLine("CASE 4 : create historical data request for three securities >");
Console.WriteLine();
//
// re-create securities
bloombergSecurityNames = new List<string>();
bloombergSecurityNames.Add("GOOG US Equity");
bloombergSecurityNames.Add("CSCO US Equity");
bloombergSecurityNames.Add("CCE US Equity");
//
// re-create field
bloombergFieldNames = new List<string>();
bloombergFieldNames.Add("PX_LAST");
//
// retrieve and print data
// request three time-series : use default settings to retrieve date-consistent result data
wrapper = new HistoricalDataRequest(bloombergSecurityNames, bloombergFieldNames, startDate, endDate);
result = wrapper.ProcessData();
Print(bloombergSecurityNames, result);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
//
Console.ReadLine();
return 0;
}
// result printer
publicstaticvoid Print(List<string> securities, dynamic[, ,] data)
{
for (int i = 0; i < data.GetLength(0); i++)
{
Console.WriteLine(securities[i]);
for (int j = 0; j < data.GetLength(1); j++)
{
for (int k = 0; k < data.GetLength(2); k++)
{
Console.WriteLine(data[i, j, k]);
}
}
Console.WriteLine();
}
}
}
}
//
// WRAPPER
namespace BloombergAPI
{
// enumerators for historical data request settings
publicenum E_PRICING_OPTION { PRICING_OPTION_PRICE, PRICING_OPTION_YIELD };
publicenum E_PERIODICITY_ADJUSTMENT { ACTUAL, CALENDAR, FISCAL };
publicenum E_PERIODICITY_SELECTION { DAILY, WEEKLY, MONTHLY, QUARTERLY, SEMI_ANNUALLY, YEARLY };
publicenum E_NON_TRADING_DAY_FILL_OPTION { NON_TRADING_WEEKDAYS, ALL_CALENDAR_DAYS, ACTIVE_DAYS_ONLY };
publicenum E_NON_TRADING_DAY_FILL_METHOD { PREVIOUS_VALUE, NIL_VALUE };
//
// abstract base class for data request
publicabstractclassBBCOMMDataRequest
{
// BBCOMM names
protectedreadonly BBCOMM.Name SECURITY_DATA = new BBCOMM.Name("securityData");
protectedreadonly BBCOMM.Name FIELD_DATA = new BBCOMM.Name("fieldData");
protectedreadonly BBCOMM.Name FIELD_ID = new BBCOMM.Name("fieldId");
protectedreadonly BBCOMM.Name VALUE = new BBCOMM.Name("value");
protectedreadonly BBCOMM.Name OVERRIDES = new BBCOMM.Name("overrides");
protectedreadonly BBCOMM.Name SECURITIES = new BBCOMM.Name("securities");
protectedreadonly BBCOMM.Name FIELDS = new BBCOMM.Name("fields");
protectedreadonly BBCOMM.Name SEQUENCE_NUMBER = new BBCOMM.Name("sequenceNumber");
protectedreadonly BBCOMM.Name START_DATE = new BBCOMM.Name("startDate");
protectedreadonly BBCOMM.Name END_DATE = new BBCOMM.Name("endDate");
protectedreadonly BBCOMM.Name DATE = new BBCOMM.Name("date");
protectedreadonly BBCOMM.Name PRICING_OPTION = new BBCOMM.Name("pricingOption");
protectedreadonly BBCOMM.Name PERIODICITY_ADJUSTMENT = new BBCOMM.Name("periodicityAdjustment");
protectedreadonly BBCOMM.Name PERIODICITY_SELECTION = new BBCOMM.Name("periodicitySelection");
protectedreadonly BBCOMM.Name NON_TRADING_DAY_FILL_OPTION = new BBCOMM.Name("nonTradingDayFillOption");
protectedreadonly BBCOMM.Name NON_TRADING_DAY_FILL_METHOD = new BBCOMM.Name("nonTradingDayFillMethod");
protectedreadonly BBCOMM.Name OVERRIDE_CURRENCY = new BBCOMM.Name("currency");
//
// const strings, enumerators, etc.
protectedreadonlystring NOT_AVAILABLE = "#N/A";
protectedreadonlystring SESSION_EXCEPTION = "Session not started";
protectedreadonlystring SERVICE_EXCEPTION = "Service not opened";
protectedreadonlystring REQUEST_TYPE_REFERENCE = "ReferenceDataRequest";
protectedreadonlystring REQUEST_TYPE_HISTORICAL = "HistoricalDataRequest";
protectedreadonlystring REFERENCE_DATA_SERVICE = "//blp/refdata";
protectedreadonlystring BLOOMBERG_DATE_FORMAT = "yyyyMMdd";
protected E_PRICING_OPTION pricingOption;
protected E_PERIODICITY_ADJUSTMENT periodicityAdjustment;
protected E_PERIODICITY_SELECTION periodicitySelection;
protected E_NON_TRADING_DAY_FILL_OPTION nonTradingDayFillOption;
protected E_NON_TRADING_DAY_FILL_METHOD nonTradingDayFillMethod;
protectedstring requestType;
protectedstring startDate;
protectedstring endDate;
protectedstring overrideCurrency;
//
// wrapped BBCOMM objects
protected BBCOMM.Session session;
protected BBCOMM.Service service;
protected BBCOMM.Request request;
//
// input data structures
protected List<string> securityNames = new List<string>();
protected List<string> fieldNames = new List<string>();
protected List<string> overrideFields = new List<string>();
protected List<string> overrideValues = new List<string>();
//
// output result data structure
protecteddynamic[, ,] result;
//
publicdynamic[, ,] ProcessData()
{
Open();
CreateRequest();
SendRequest();
Close();
return result;
}
privatevoid Open()
{
// create and start bloomberg BBCOMM session
BBCOMM.SessionOptions sessionOptions = new BBCOMM.SessionOptions();
session = new BBCOMM.Session(sessionOptions);
if (!session.Start()) thrownew Exception(SESSION_EXCEPTION);
//
// get service from session object and create request by service object
if (!session.OpenService(REFERENCE_DATA_SERVICE)) thrownew Exception(SERVICE_EXCEPTION);
service = session.GetService(REFERENCE_DATA_SERVICE);
request = service.CreateRequest(requestType);
}
privatevoid CreateRequest()
{
// append securities, fields
foreach (string securityName in securityNames) request.Append(SECURITIES, securityName);
foreach (string fieldName in fieldNames) request.Append(FIELDS, fieldName);
//
// conditionally, append overrides into request object
if (overrideFields.Count > 0)
{
BBCOMM.Element requestOverrides = request.GetElement(OVERRIDES);
for (int i = 0; i < overrideFields.Count; i++)
{
BBCOMM.Element requestOverride = requestOverrides.AppendElement();
requestOverride.SetElement(FIELD_ID, overrideFields[i]);
requestOverride.SetElement(VALUE, overrideValues[i]);
}
}
// set optional parameters for historical data request
if (requestType == REQUEST_TYPE_HISTORICAL)
{
request.Set(START_DATE, startDate);
request.Set(END_DATE, endDate);
request.Set(PRICING_OPTION, pricingOption.ToString());
request.Set(PERIODICITY_ADJUSTMENT, periodicityAdjustment.ToString());
request.Set(PERIODICITY_SELECTION, periodicitySelection.ToString());
request.Set(NON_TRADING_DAY_FILL_OPTION, nonTradingDayFillOption.ToString());
request.Set(NON_TRADING_DAY_FILL_METHOD, nonTradingDayFillMethod.ToString());
if (overrideCurrency != String.Empty) request.Set(OVERRIDE_CURRENCY, overrideCurrency);
}
}
privatevoid SendRequest()
{
// send constructed request to BBCOMM server
long ID = Guid.NewGuid().GetHashCode();
session.SendRequest(request, new BBCOMM.CorrelationID(ID));
bool isProcessing = true;
//
while (isProcessing)
{
// receive data response from BBCOMM server, send
// response to be processed by sub-classed algorithm
BBCOMM.Event response = session.NextEvent();
switch (response.Type)
{
case BBCOMM.Event.EventType.PARTIAL_RESPONSE:
ProcessDataResponse(ref response);
break;
case BBCOMM.Event.EventType.RESPONSE:
ProcessDataResponse(ref response);
isProcessing = false;
break;
default:
break;
}
}
}
privatevoid Close()
{
// close BBCOMM session
if (session != null) session.Stop();
}
//
// sub-classes are providing specific algorithm implementations for
// processing and packing BBCOMM server response data into resulting data structure
protectedabstractvoid ProcessDataResponse(ref BBCOMM.Event response);
}
//
// concrete class implementation for processing reference data request
publicclassReferenceDataRequest : BBCOMMDataRequest
{
public ReferenceDataRequest(List<string> bloombergSecurityNames,
List<string> bloombergFieldNames)
{
// ctor : create reference data request without field overrides
requestType = REQUEST_TYPE_REFERENCE;
securityNames = bloombergSecurityNames;
fieldNames = bloombergFieldNames;
//
// define result data structure dimensions for reference data request
result = newdynamic[securityNames.Count, 1, fieldNames.Count];
}
public ReferenceDataRequest(List<string> bloombergSecurityNames,
List<string> bloombergFieldNames, List<string> bloombergOverrideFields,
List<string> bloombergOverrideValues)
{
// ctor : create reference data request with field overrides
requestType = REQUEST_TYPE_REFERENCE;
securityNames = bloombergSecurityNames;
fieldNames = bloombergFieldNames;
overrideFields = bloombergOverrideFields;
overrideValues = bloombergOverrideValues;
//
// define result data structure dimensions for reference data request
result = newdynamic[securityNames.Count, 1, fieldNames.Count];
}
protectedoverridevoid ProcessDataResponse(ref BBCOMM.Event response)
{
// receive response, which contains N securities and M fields
// event queue can send multiple responses for large requests
foreach (BBCOMM.Message message in response.GetMessages())
{
// extract N securities
BBCOMM.Element securities = message.GetElement(SECURITY_DATA);
int nSecurities = securities.NumValues;
//
// loop through all securities
for (int i = 0; i < nSecurities; i++)
{
// extract one security and fields for this security
BBCOMM.Element security = securities.GetValueAsElement(i);
BBCOMM.Element fields = security.GetElement(FIELD_DATA);
int sequenceNumber = security.GetElementAsInt32(SEQUENCE_NUMBER);
int nFieldNames = fieldNames.Count;
//
// loop through all M fields for this security
for (int j = 0; j < nFieldNames; j++)
{
// if the requested field has been found, pack value into result data structure
if (fields.HasElement(fieldNames[j]))
{
result[sequenceNumber, 0, j] = fields.GetElement(fieldNames[j]).GetValue();
}
// otherwise, pack NOT_AVAILABLE string into data structure
else
{
result[sequenceNumber, 0, j] = NOT_AVAILABLE;
}
}
}
}
}
}
//
// concrete class implementation for processing historical data request
publicclassHistoricalDataRequest : BBCOMMDataRequest
{
privatebool hasDimensions = false;
//
// optional parameters are configured to retrieve time-series having actual daily observations, including all weekdays,
// in the case of non-trading days the previous date value will be used.
public HistoricalDataRequest(List<string> bloombergSecurityNames, List<string> bloombergFieldNames,
DateTime bloombergStartDate, DateTime BloombergEndDate,
E_PRICING_OPTION bloombergPricingOption = E_PRICING_OPTION.PRICING_OPTION_PRICE,
E_PERIODICITY_SELECTION bloombergPeriodicitySelection = E_PERIODICITY_SELECTION.DAILY,
E_PERIODICITY_ADJUSTMENT bloombergPeriodicityAdjustment = E_PERIODICITY_ADJUSTMENT.ACTUAL,
E_NON_TRADING_DAY_FILL_OPTION bloombergNonTradingDayFillOption = E_NON_TRADING_DAY_FILL_OPTION.ALL_CALENDAR_DAYS,
E_NON_TRADING_DAY_FILL_METHOD bloombergNonTradingDayFillMethod = E_NON_TRADING_DAY_FILL_METHOD.PREVIOUS_VALUE,
string bloombergOverrideCurrency = "")
{
// ctor : create historical data request without field overrides
requestType = REQUEST_TYPE_HISTORICAL;
securityNames = bloombergSecurityNames;
fieldNames = bloombergFieldNames;
startDate = bloombergStartDate.ToString(BLOOMBERG_DATE_FORMAT);
endDate = BloombergEndDate.ToString(BLOOMBERG_DATE_FORMAT);
//
pricingOption = bloombergPricingOption;
periodicitySelection = bloombergPeriodicitySelection;
periodicityAdjustment = bloombergPeriodicityAdjustment;
nonTradingDayFillOption = bloombergNonTradingDayFillOption;
nonTradingDayFillMethod = bloombergNonTradingDayFillMethod;
overrideCurrency = bloombergOverrideCurrency;
}
public HistoricalDataRequest(List<string> bloombergSecurityNames, List<string> bloombergFieldNames,
DateTime bloombergStartDate, DateTime BloombergEndDate, List<string> bloombergOverrideFields,
List<string> bloombergOverrideValues,
E_PRICING_OPTION bloombergPricingOption = E_PRICING_OPTION.PRICING_OPTION_PRICE,
E_PERIODICITY_SELECTION bloombergPeriodicitySelection = E_PERIODICITY_SELECTION.DAILY,
E_PERIODICITY_ADJUSTMENT bloombergPeriodicityAdjustment = E_PERIODICITY_ADJUSTMENT.ACTUAL,
E_NON_TRADING_DAY_FILL_OPTION bloombergNonTradingDayFillOption = E_NON_TRADING_DAY_FILL_OPTION.ALL_CALENDAR_DAYS,
E_NON_TRADING_DAY_FILL_METHOD bloombergNonTradingDayFillMethod = E_NON_TRADING_DAY_FILL_METHOD.PREVIOUS_VALUE,
string bloombergOverrideCurrency = "")
{
// ctor : create historical data request with field overrides
requestType = REQUEST_TYPE_HISTORICAL;
securityNames = bloombergSecurityNames;
fieldNames = bloombergFieldNames;
overrideFields = bloombergOverrideFields;
overrideValues = bloombergOverrideValues;
startDate = bloombergStartDate.ToString(BLOOMBERG_DATE_FORMAT);
endDate = BloombergEndDate.ToString(BLOOMBERG_DATE_FORMAT);
//
pricingOption = bloombergPricingOption;
periodicitySelection = bloombergPeriodicitySelection;
periodicityAdjustment = bloombergPeriodicityAdjustment;
nonTradingDayFillOption = bloombergNonTradingDayFillOption;
nonTradingDayFillMethod = bloombergNonTradingDayFillMethod;
overrideCurrency = bloombergOverrideCurrency;
}
protectedoverridevoid ProcessDataResponse(ref BBCOMM.Event response)
{
// unzip and pack messages received from BBCOMM server
// receive one security per message and multiple messages per event
foreach (BBCOMM.Message message in response.GetMessages())
{
// extract security and fields
BBCOMM.Element security = message.GetElement(SECURITY_DATA);
BBCOMM.Element fields = security.GetElement(FIELD_DATA);
//
int sequenceNumber = security.GetElementAsInt32(SEQUENCE_NUMBER);
int nFieldNames = fieldNames.Count;
int nObservationDates = fields.NumValues;
//
// the exact dimension will be known only, when the response has been received from BBCOMM server
if (!hasDimensions)
{
// define result data structure dimensions for historical data request
// observation date will be stored into first field for each observation date
result = newdynamic[securityNames.Count, nObservationDates, fieldNames.Count + 1];
hasDimensions = true;
}
//
// loop through all observation dates
for (int i = 0; i < nObservationDates; i++)
{
// extract all field data for a single observation date
BBCOMM.Element observationDateFields = fields.GetValueAsElement(i);
//
// pack observation date into data structure
result[sequenceNumber, i, 0] = observationDateFields.GetElementAsDatetime(DATE);
//
// then, loop through all 'user-requested' fields for a given observation date
// and pack results data into data structure
for (int j = 0; j < nFieldNames; j++)
{
// pack field value into data structure if such value has been found
if (observationDateFields.HasElement(fieldNames[j]))
{
result[sequenceNumber, i, j + 1] = observationDateFields.GetElement(fieldNames[j]).GetValue();
}
// otherwise, pack NOT_AVAILABLE string into data structure
else
{
result[sequenceNumber, i, j + 1] = NOT_AVAILABLE;
}
}
}
}
}
}
}

NAG Copula wrapper for C#

$
0
0
I've got you under my skin, Frank Sinatra is singing in computer radio. Somehow, it reminds me about my obsession with Copula, what I have been chewing in a form or another, again and again for some time already. So, this time I wanted to share the latest fruit of this obsession as NAG Copula wrapper for C#.

NAG G05 Copula wrapper

With wrapper, one can create correlated random numbers
  • from any distribution (assuming appropriate inverse transformation method is provided)
  • by using Gaussian or Student Copula
The first task, creation of correlated random numbers inside NAGCopula class, is performed by NAG G05 library methods g05rc (Student) and g05rd (Gaussian). The result provided by both of these methods, is a matrix filled with correlated uniform random numbers. The second task, mapping correlated uniform random numbers back to a desired probability distribution, is performed by InverseTransformation class implementation inside NAGCopula class.

Client can create NAGCopula object with two different constructors, with or without inverse transformation. Also, NAGCopula model selection (Gaussian, Student) will be made, based on user-given value for degrees-of-freedom parameter. This parameter, being greater than zero, will automatically trigger the use of Student Copula model.

NAGCopula class, InverseTransformation class hierarchy and Client tester program is presented below. Program will first retrieve a matrix filled with correlated random numbers from NAGCopula object and then writing the content of that matrix into a given text file. Define new addresses for file outputs, if needed. Create a new C# console project, copyPaste the whole content into a new cs file and run the program.

Copula outputs

The results of one client program run is presented in the table below. For a given bi-variate test scheme, the both Copula models (Gaussian, Student) were used to create 5000 correlated random number pairs. The created random numbers were also transformed back to Standard Normal and Exponential distributions.




















The Program


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NagLibrary;
using System.IO;

namespace NAGCopulaNamespace
{
// CLIENT
classProgram
{
staticvoid Main(string[] args)
{
try
{
double[,] covariance = CreateCovarianceMatrix();
int[] seed = newint[1];
int n = 5000;
double[,] randomMatrix;
//
// create correlated uniform random numbers using gaussian copula
NAGCopula gaussian = new NAGCopula(covariance, 1, 1, 2);
seed[0] = Math.Abs(Guid.NewGuid().GetHashCode());
randomMatrix = gaussian.Create(n, seed);
Print(@"C:\temp\GaussianCopulaNumbers.txt", randomMatrix);
//
// create correlated uniform random numbers using student copula
NAGCopula student = new NAGCopula(covariance, 1, 1, 2, 3);
seed[0] = Math.Abs(Guid.NewGuid().GetHashCode());
randomMatrix = student.Create(n, seed);
Print(@"C:\temp\StudentCopulaNumbers.txt", randomMatrix);
//
// create correlated standard normal random numbers using gaussian copula
NAGCopula gaussianTransformed = new NAGCopula(covariance, 1, 1, 2, new StandardNormal());
seed[0] = Math.Abs(Guid.NewGuid().GetHashCode());
randomMatrix = gaussianTransformed.Create(n, seed);
Print(@"C:\temp\GaussianTransformedCopulaNumbers.txt", randomMatrix);
//
// create correlated standard normal random numbers using student copula
NAGCopula studentTransformed = new NAGCopula(covariance, 1, 1, 2, new StandardNormal(), 3);
seed[0] = Math.Abs(Guid.NewGuid().GetHashCode());
randomMatrix = studentTransformed.Create(n, seed);
Print(@"C:\temp\StudentTransformedCopulaNumbers.txt", randomMatrix);
//
// create correlated exponential random numbers using gaussian copula
NAGCopula gaussianTransformed2 = new NAGCopula(covariance, 1, 1, 2, new Exponential(0.078));
seed[0] = Math.Abs(Guid.NewGuid().GetHashCode());
randomMatrix = gaussianTransformed2.Create(n, seed);
Print(@"C:\temp\GaussianTransformedCopulaNumbers2.txt", randomMatrix);
//
// create correlated exponential random numbers using student copula
NAGCopula studentTransformed2 = new NAGCopula(covariance, 1, 1, 2, new Exponential(0.078), 3);
seed[0] = Math.Abs(Guid.NewGuid().GetHashCode());
randomMatrix = studentTransformed2.Create(n, seed);
Print(@"C:\temp\StudentTransformedCopulaNumbers2.txt", randomMatrix);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
staticvoid Print(string filePathName, double[,] arr)
{
// write generated correlated random numbers into text file
StreamWriter writer = new StreamWriter(filePathName);
//
for (int i = 0; i < arr.GetLength(0); i++)
{
for (int j = 0; j < arr.GetLength(1); j++)
{
writer.Write("{0};", arr[i, j].ToString().Replace(',','.'));
}
writer.WriteLine();
}
writer.Close();
}
staticdouble[,] CreateCovarianceMatrix()
{
// hard-coded data for 2x2 co-variance matrix
// correlation is (roughly) 0.75
double[,] c = newdouble[2, 2];
c[0, 0] = 0.00137081575700264; c[0, 1] = 0.00111385579983184;
c[1, 0] = 0.00111385579983184; c[1, 1] = 0.00161530858115086;
return c;
}
}
//
//
// NAG COPULA WRAPPER
publicclassNAGCopula
{
private InverseTransformation transformer;
privatedouble[,] covariance;
privateint genID;
privateint subGenID;
privateint mode;
privateint df;
privateint m;
privateint errorNumber;
privatedouble[] r;
privatedouble[,] result;
//
public NAGCopula(double[,] covariance, int genID,
int subGenID, int mode, int df = 0)
{
// ctor : create correlated uniform random numbers
// degrees-of-freedom parameter (df), being greater than zero,
// will automatically trigger the use of student copula
this.covariance = covariance;
this.genID = genID;
this.subGenID = subGenID;
this.mode = mode;
this.df = df;
this.m = covariance.GetLength(1);
r = newdouble[m * (m + 1) + (df > 0 ? 2 : 1)];
}
public NAGCopula(double[,] covariance, int genID, int subGenID,
int mode, InverseTransformation transformer, int df = 0)
{
// ctor : create correlated random numbers from distribution X
// degrees-of-freedom parameter (df), being greater than zero,
// will automatically trigger the use of student copula
this.transformer = transformer;
this.covariance = covariance;
this.genID = genID;
this.subGenID = subGenID;
this.mode = mode;
this.df = df;
this.m = covariance.GetLength(1);
r = newdouble[m * (m + 1) + (df > 0 ? 2 : 1)];
}
publicdouble[,] Create(int n, int[] seed)
{
result = newdouble[n, m];
//
G05.G05State g05State = new G05.G05State(genID, subGenID, seed, out errorNumber);
if (errorNumber != 0) thrownew Exception("G05 state failure");
//
if (this.df != 0)
{
// student copula
G05.g05rc(mode, n, df, m, covariance, r, g05State, result, out errorNumber);
}
else
{
// gaussian copula
G05.g05rd(mode, n, m, covariance, r, g05State, result, out errorNumber);
}
//
if (errorNumber != 0) thrownew Exception("G05 method failure");
if (transformer != null) transform();
return result;
}
privatevoid transform()
{
// map uniform random numbers to distribution X
for (int i = 0; i < result.GetLength(0); i++)
{
for (int j = 0; j < result.GetLength(1); j++)
{
result[i, j] = transformer.Inverse(result[i, j]);
}
}
}
}
//
//
// INVERSE TRANSFORMATION LIBRARY
// base class for all inverse transformation classes
publicabstractclassInverseTransformation
{
publicabstractdouble Inverse(double x);
}
//
// mapping uniform random variate to exponential distribution
publicclassExponential : InverseTransformation
{
privatedouble lambda;
public Exponential(double lambda)
{
this.lambda = lambda;
}
publicoverridedouble Inverse(double x)
{
return Math.Pow(-lambda, -1.0) * Math.Log(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("input parameter out of bounds");
}
return c;
}
}
}


Afterthoughts

It is quite easy to appreciate the numerical work performed by NAG G05 library. In the case you might be interested about how to create non-correlated random numbers with NAG, check out my blog post in here. If you like the stuff, you can get yourself more familiar with NAG libraries in here. So, that was all I wanted to share with you again. Thanks for reading my blog. Good night. Mike.

C# Basket Default Swap Pricer

$
0
0
More Copula stuff coming again. This time, I wanted to open my implementation for Basket Default Swap (BDS) pricer. It is assumed, that the reader is familiar with this exotic credit product.


PROGRAM DESIGN



The core of the program is BasketCDS class, which is performing the actual Monte Carlo process. This class is using NAGCopula class for creating correlated uniform random numbers needed for all simulations. It also uses CreditCurve class for retrieving credit-related data for calculating default times. Finally, it holds a delegate method for desired discounting method. For storing the calculated default and premium leg values, delegate methods are used to send these values to Statistics class, which then stores the both leg values into its own data structures. In essence, BasketCDS class is doing the calculation and Statistics class is responsible for storing the calculated values and reporting its own set of desired (and configured) statistics when requested.


As already mentioned, CreditCurve class is holding all the credit-related information (CDS curve, hazard rates, recovery rate). DiscountCurve class holds the information on zero-coupon curve. In this class, discounting and interpolation methods are not hard-coded inside the class. Instead, the class is holding two delegate methods for this purpose. The actual implementations for the both methods are stored in static MathTools class, which is nothing more than just collection of methods for different types of mathematical operations. Some matrix operations are needed for handling matrices when calculating default times in BasketCDS class. For this purpose, static MatrixTools class is providing collection of methods for different types of matrix operations. Needless to say, new method implementations can be provided for concrete methods used by these delegates.


Source market data (co-variance matrix, CDS curve and zero-coupon curve) for this example program has been completely hard-coded. However, with the examples presented in here or in here, the user should be able to interface this program back to Excel, if so desired.


MONTE CARLO PROCESS


The client example program is calculating five fair spreads for all kth-to-default basket CDS for all five reference names in the basket. In a nutshell, Monte Carlo procedure to calculate kth-to-default BDS spread is the following.

  1. Simulate five correlated uniformly distributed random numbers by using Gaussian or Student Copula model.
  2. Transform simulated five uniform random numbers to default times by using inverse CDF of exponential distribution.
  3. By using term structure of hazard rates, define the exact default times for all five names.
  4. Calculate the value for default leg and premium leg.
  5. Store the both calculated leg values.
  6. Repeat steps from one to five N times.
  7. Finally, calculate BDS spread by dividing average of all stored default leg values with average of all stored premium leg values.

In order to run this program, one needs to create a new C# console project and copyPaste the program given below into a new cs file. It is also assumed, that the user is a registrated NAG licence holder. Other users have to provide their own implementation for Copula needed in this program or use my implementation. With the second approach, a bit more time will be spent in adjusting this program for all the changes required.

Results for one test run for the both Copula models is presented in the following console screenshot.



















Observed results are behaving as theory is suggesting. BDS prices are decreasing as more reference names are needed for triggering the actual default. Also, tail-effect created by the use of Student Copula model is captured as higher prices after 1st-to-default BDS. Computational help provided by NAG G05 library is just amazing. After you outsource the handling of Copula model for external library, there is not so much to program either.

That is all I wanted to share with you this time. Thanks for reading my blog. Mike.



THE PROGRAM


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NagLibrary;
//
namespace BasketCDSPricer
{
// delegates for updating leg values
publicdelegatevoid PremiumLegUpdate(double v);
publicdelegatevoid DefaultLegUpdate(double v);
//
// delegate methods for interpolation and discounting
publicdelegatedouble InterpolationAlgorithm(double t, refdouble[,] curve);
publicdelegatedouble DiscountAlgorithm(double t, double r);
//
//
// *************************************************************
// CLIENT PROGRAM
classProgram
{
staticvoid Main(string[] args)
{
try
{
// create covariance matrix, discount curve, credit curve and NAG gaussian copula model
double[,] covariance = createCovarianceMatrix();
DiscountCurve zero = new DiscountCurve(createDiscountCurve(),
MathTools.LinearInterpolation, MathTools.ContinuousDiscountFactor);
CreditCurve cds = new CreditCurve(createCDSCurve(), zero.GetDF, 0.4);
NAGCopula copula = new NAGCopula(covariance, 1, 1, 2);
//
// create containers for basket pricers and statistics
// set number of simulations and reference assets
List<BasketCDS> engines = new List<BasketCDS>();
List<Statistics> statistics = new List<Statistics>();
int nSimulations = 250000;
int nReferences = 5;
//
// create basket pricers and statistics gatherers into containers
// order updating for statistics gatherers from basket pricers
for (int kth = 1; kth <= nReferences; kth++)
{
statistics.Add(new Statistics(String.Concat(kth.ToString(), "-to-default")));
engines.Add(new BasketCDS(kth, nSimulations, nReferences, copula, cds, zero.GetDF));
engines[kth - 1].updateDefaultLeg += statistics[kth - 1].UpdateDefaultLeg;
engines[kth - 1].updatePremiumLeg += statistics[kth - 1].UpdatePremiumLeg;
}
// run basket pricers and print statistics
engines.ForEach(it => it.Process());
statistics.ForEach(it => it.PrettyPrint());
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
//
// hard-coded co-variance matrix data
staticdouble[,] createCovarianceMatrix()
{
double[,] covariance = newdouble[5, 5];
covariance[0, 0] = 0.001370816; covariance[1, 0] = 0.001113856; covariance[2, 0] = 0.001003616;
covariance[3, 0] = 0.000937934; covariance[4, 0] = 0.00040375;
covariance[0, 1] = 0.001113856; covariance[1, 1] = 0.001615309; covariance[2, 1] = 0.000914043;
covariance[3, 1] = 0.000888594; covariance[4, 1] = 0.000486823;
covariance[0, 2] = 0.001003616; covariance[1, 2] = 0.000914043; covariance[2, 2] = 0.001302899;
covariance[3, 2] = 0.000845642; covariance[4, 2] = 0.00040185;
covariance[0, 3] = 0.000937934; covariance[1, 3] = 0.000888594; covariance[2, 3] = 0.000845642;
covariance[3, 3] = 0.000954866; covariance[4, 3] = 0.000325403;
covariance[0, 4] = 0.00040375; covariance[1, 4] = 0.000486823; covariance[2, 4] = 0.00040185;
covariance[3, 4] = 0.000325403; covariance[4, 4] = 0.001352532;
return covariance;
}
//
// hard-coded discount curve data
staticdouble[,] createDiscountCurve()
{
double[,] curve = newdouble[5, 2];
curve[0, 0] = 1.0; curve[0, 1] = 0.00285;
curve[1, 0] = 2.0; curve[1, 1] = 0.00425;
curve[2, 0] = 3.0; curve[2, 1] = 0.00761;
curve[3, 0] = 4.0; curve[3, 1] = 0.0119;
curve[4, 0] = 5.0; curve[4, 1] = 0.01614;
return curve;
}
//
// hard-coded cds curve data
staticdouble[,] createCDSCurve()
{
double[,] cds = newdouble[5, 5];
cds[0, 0] = 17.679; cds[0, 1] = 19.784;
cds[0, 2] = 11.242; cds[0, 3] = 23.5;
cds[0, 4] = 39.09; cds[1, 0] = 44.596;
cds[1, 1] = 44.921; cds[1, 2] = 27.737;
cds[1, 3] = 50.626; cds[1, 4] = 93.228;
cds[2, 0] = 54.804; cds[2, 1] = 64.873;
cds[2, 2] = 36.909; cds[2, 3] = 67.129;
cds[2, 4] = 107.847; cds[3, 0] = 83.526;
cds[3, 1] = 96.603; cds[3, 2] = 57.053;
cds[3, 3] = 104.853; cds[3, 4] = 164.815;
cds[4, 0] = 96.15; cds[4, 1] = 115.662;
cds[4, 2] = 67.82; cds[4, 3] = 118.643;
cds[4, 4] = 159.322;
return cds;
}
}
//
//
// *************************************************************
publicclassBasketCDS
{
public PremiumLegUpdate updatePremiumLeg; // delegate for sending value
public DefaultLegUpdate updateDefaultLeg; // delegate for sending value
private NAGCopula copula; // NAG copula model
private Func<double, double> df; // discounting method delegate
private CreditCurve cds; // credit curve
privateint k; // kth-to-default
privateint m; // number of reference assets
privateint n; // number of simulations
privateint maturity; // basket cds maturity in years (integer)
//
public BasketCDS(int kth, int simulations, int maturity, NAGCopula copula,
CreditCurve cds, Func<double, double> discountFactor)
{
this.k = kth;
this.n = simulations;
this.maturity = maturity;
this.copula = copula;
this.cds = cds;
this.df = discountFactor;
}
publicvoid Process()
{
// request correlated random numbers from copula model
int[] seed = newint[1] { Math.Abs(Guid.NewGuid().GetHashCode()) };
double[,] randomArray = copula.Create(n, seed);
m = randomArray.GetLength(1);
//
// process n sets of m correlated random numbers
for (int i = 0; i < n; i++)
{
// create a set of m random numbers needed for one simulation round
double[,] set = newdouble[1, m];
for (int j = 0; j < m; j++)
{
set[0, j] = randomArray[i, j];
}
//
// calculate default times for reference name set
calculateDefaultTimes(set);
}
}
privatevoid calculateDefaultTimes(double[,] arr)
{
// convert uniform random numbers into default times
int cols = arr.GetLength(1);
double[,] defaultTimes = newdouble[1, cols];
//
for (int j = 0; j < cols; j++)
{
// iteratively, find out the correct default tenor bucket
double u = arr[0, j];
double t = Math.Abs(Math.Log(1 - u));
//
for (int k = 0; k < cds.CumulativeHazardMatrix.GetLength(1); k++)
{
int tenor = 0;
double dt = 0.0; double defaultTenor = 0.0;
if (cds.CumulativeHazardMatrix[k, j] >= t)
{
tenor = k;
if (tenor >= 1)
{
// calculate the exact default time for a given reference name
dt = -(1 / cds.HazardMatrix[k, j]) * Math.Log((1 - u)
/ (Math.Exp(-cds.CumulativeHazardMatrix[k - 1, j])));
defaultTenor = tenor + dt;
//
// default time after basket maturity
if (defaultTenor >= maturity) defaultTenor = 0.0;
}
else
{
// hard-coded assumption
defaultTenor = 0.5;
}
defaultTimes[0, j] = defaultTenor;
break;
}
}
}
// proceed to calculate leg values
updateLegValues(defaultTimes);
}
privatevoid updateLegValues(double[,] defaultTimes)
{
// check for defaulted reference names, calculate leg values
// and send statistics updates for BasketCDSStatistics
int nDefaults = getNumberOfDefaults(defaultTimes);
if (nDefaults > 0)
{
// for calculation purposes, remove zeros and sort matrix
MatrixTools.RowMatrix_removeZeroValues(ref defaultTimes);
MatrixTools.RowMatrix_sort(ref defaultTimes);
}
// calculate and send values for statistics gatherer
double dl = calculateDefaultLeg(defaultTimes, nDefaults);
double pl = calculatePremiumLeg(defaultTimes, nDefaults);
updateDefaultLeg(dl);
updatePremiumLeg(pl);
}
privateint getNumberOfDefaults(double[,] arr)
{
int nDefaults = 0;
for (int i = 0; i < arr.GetLength(1); i++)
{
if (arr[0, i] > 0.0) nDefaults++;
}
return nDefaults;
}
privatedouble calculatePremiumLeg(double[,] defaultTimes, int nDefaults)
{
double dt = 0.0; double t; double p = 0.0; double v = 0.0;
if ((nDefaults > 0) && (nDefaults >= k))
{
for (int i = 0; i < k; i++)
{
if (i == 0)
{
// premium components from 0 to t1
dt = defaultTimes[0, i] - 0.0;
t = dt;
p = 1.0;
}
else
{
// premium components from t1 to t2, etc.
dt = defaultTimes[0, i] - defaultTimes[0, i - 1];
t = defaultTimes[0, i];
p = (m - i) / (double)m;
}
v += (df(t) * dt * p);
}
}
else
{
for (int i = 0; i < maturity; i++)
{
v += df(i + 1);
}
}
return v;
}
privatedouble calculateDefaultLeg(double[,] defaultTimes, int nDefaults)
{
double v = 0.0;
if ((nDefaults > 0) && (nDefaults >= k))
{
v = (1 - cds.Recovery) * df(defaultTimes[0, k - 1]) * (1 / (double)m);
}
return v;
}
}
//
//
// *************************************************************
publicclassCreditCurve
{
private Func<double, double> df;
privatedouble recovery;
privatedouble[,] CDSSpreads;
privatedouble[,] survivalMatrix;
privatedouble[,] hazardMatrix;
privatedouble[,] cumulativeHazardMatrix;
//
public CreditCurve(double[,] CDSSpreads,
Func<double, double> discountFactor, double recovery)
{
this.df = discountFactor;
this.CDSSpreads = CDSSpreads;
this.recovery = recovery;
createSurvivalMatrix();
createHazardMatrices();
}
// public read-only accessors to class data
publicdouble[,] HazardMatrix { get { returnthis.hazardMatrix; } }
publicdouble[,] CumulativeHazardMatrix { get { returnthis.cumulativeHazardMatrix; } }
publicdouble Recovery { get { returnthis.recovery; } }
//
privatevoid createSurvivalMatrix()
{
// bootstrap matrix of survival probabilities from given CDS data
int rows = CDSSpreads.GetUpperBound(0) + 2;
int cols = CDSSpreads.GetUpperBound(1) + 1;
survivalMatrix = newdouble[rows, cols];
//
double term = 0.0; double firstTerm = 0.0; double lastTerm = 0.0;
double terms = 0.0; double quotient = 0.0;
int i = 0; int j = 0; int k = 0;
//
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
if (i == 0) survivalMatrix[i, j] = 1.0;
if (i == 1) survivalMatrix[i, j] = (1 - recovery) / ((1 - recovery) + 1 * CDSSpreads[i - 1, j] / 10000);
if (i > 1)
{
terms = 0.0;
for (k = 0; k < (i - 1); k++)
{
term = df(k + 1) * ((1 - recovery) * survivalMatrix[k, j] -
(1 - recovery + 1 * CDSSpreads[i - 1, j] / 10000) * survivalMatrix[k + 1, j]);
terms += term;
}
quotient = (df(i) * ((1 - recovery) + 1 * CDSSpreads[i - 1, j] / 10000));
firstTerm = (terms / quotient);
lastTerm = survivalMatrix[i - 1, j] * (1 - recovery) / (1 - recovery + 1 * CDSSpreads[i - 1, j] / 10000);
survivalMatrix[i, j] = firstTerm + lastTerm;
}
}
}
}
privatevoid createHazardMatrices()
{
// convert matrix of survival probabilities into two hazard rate matrices
int rows = survivalMatrix.GetUpperBound(0);
int cols = survivalMatrix.GetUpperBound(1) + 1;
hazardMatrix = newdouble[rows, cols];
cumulativeHazardMatrix = newdouble[rows, cols];
int i = 0; int j = 0;
//
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
cumulativeHazardMatrix[i, j] = -Math.Log(survivalMatrix[i + 1, j]);
if (i == 0) hazardMatrix[i, j] = cumulativeHazardMatrix[i, j];
if (i > 0) hazardMatrix[i, j] = (cumulativeHazardMatrix[i, j] - cumulativeHazardMatrix[i - 1, j]);
}
}
}
}
//
//
// *************************************************************
publicclassDiscountCurve
{
// specific algorithms for interpolation and discounting
private InterpolationAlgorithm interpolationMethod;
private DiscountAlgorithm discountMethod;
privatedouble[,] curve;
//
public DiscountCurve(double[,] curve,
InterpolationAlgorithm interpolationMethod, DiscountAlgorithm discountMethod)
{
this.curve = curve;
this.interpolationMethod = interpolationMethod;
this.discountMethod = discountMethod;
}
publicdouble GetDF(double t)
{
// get discount factor from discount curve
return discountMethod(t, interpolation(t));
}
privatedouble interpolation(double t)
{
// get interpolation from discount curve
return interpolationMethod(t, refthis.curve);
}
}
//
//
// *************************************************************
// collection of methods for different types of mathematical operations
publicstaticclassMathTools
{
publicstaticdouble LinearInterpolation(double t, refdouble[,] curve)
{
int n = curve.GetUpperBound(0) + 1;
double v = 0.0;
//
// boundary checkings
if ((t < curve[0, 0]) || (t > curve[n - 1, 0]))
{
if (t < curve[0, 0]) v = curve[0, 1];
if (t > curve[n - 1, 0]) v = curve[n - 1, 1];
}
else
{
// iteration through all given curve points
for (int i = 0; i < n; i++)
{
if ((t >= curve[i, 0]) && (t <= curve[i + 1, 0]))
{
v = curve[i, 1] + (curve[i + 1, 1] - curve[i, 1]) * (t - (i + 1));
break;
}
}
}
return v;
}
publicstaticdouble ContinuousDiscountFactor(double t, double r)
{
return Math.Exp(-r * t);
}
}
//
//
// *************************************************************
// collection of methods for different types of matrix operations
publicstaticclassMatrixTools
{
publicstaticdouble[,] CorrelationToCovariance(double[,] corr, double[] stdev)
{
// transform correlation matrix to co-variance matrix
double[,] cov = newdouble[corr.GetLength(0), corr.GetLength(1)];
//
for (int i = 0; i < cov.GetLength(0); i++)
{
for (int j = 0; j < cov.GetLength(1); j++)
{
cov[i, j] = corr[i, j] * stdev[i] * stdev[j];
}
}
//
return cov;
}
publicstaticvoid RowMatrix_sort(refdouble[,] arr)
{
// sorting a given row matrix to ascending order
// input must be 1 x M matrix
// bubblesort algorithm implementation
int cols = arr.GetUpperBound(1) + 1;
double x = 0.0;
//
for (int i = 0; i < (cols - 1); i++)
{
for (int j = (i + 1); j < cols; j++)
{
if (arr[0, i] > arr[0, j])
{
x = arr[0, i];
arr[0, i] = arr[0, j];
arr[0, j] = x;
}
}
}
}
publicstaticvoid RowMatrix_removeZeroValues(refdouble[,] arr)
{
// removes zero values from a given row matrix
// input must be 1 x M matrix
List<double> temp = new List<double>();
int cols = arr.GetLength(1);
int counter = 0;
for (int i = 0; i < cols; i++)
{
if (arr[0, i] > 0)
{
counter++;
temp.Add(arr[0, i]);
}
}
if (counter > 0)
{
arr = newdouble[1, temp.Count];
for (int i = 0; i < temp.Count; i++)
{
arr[0, i] = temp[i];
}
}
else
{
arr = null;
}
}
}
//
//
// *************************************************************
// NAG G05 COPULAS WRAPPER
publicclassNAGCopula
{
privatedouble[,] covariance;
privateint genID;
privateint subGenID;
privateint mode;
privateint df;
privateint m;
privateint errorNumber;
privatedouble[] r;
privatedouble[,] result;
//
public NAGCopula(double[,] covariance, int genID,
int subGenID, int mode, int df = 0)
{
// ctor : create correlated uniform random numbers
// degrees-of-freedom parameter (df), being greater than zero,
// will automatically trigger the use of student copula
this.covariance = covariance;
this.genID = genID;
this.subGenID = subGenID;
this.mode = mode;
this.df = df;
this.m = covariance.GetLength(1);
r = newdouble[m * (m + 1) + (df > 0 ? 2 : 1)];
}
publicdouble[,] Create(int n, int[] seed)
{
result = newdouble[n, m];
//
G05.G05State g05State = new G05.G05State(genID, subGenID, seed, out errorNumber);
if (errorNumber != 0) thrownew Exception("G05 state failure");
//
if (this.df != 0)
{
// student copula
G05.g05rc(mode, n, df, m, covariance, r, g05State, result, out errorNumber);
}
else
{
// gaussian copula
G05.g05rd(mode, n, m, covariance, r, g05State, result, out errorNumber);
}
//
if (errorNumber != 0) thrownew Exception("G05 method failure");
return result;
}
}
//
//
// *************************************************************
publicclassStatistics
{
// data structures for storing leg values
private List<double> premiumLeg;
private List<double> defaultLeg;
privatestring ID;
//
public Statistics(string ID)
{
this.ID = ID;
premiumLeg = new List<double>();
defaultLeg = new List<double>();
}
publicvoid UpdatePremiumLeg(double v)
{
premiumLeg.Add(v);
}
publicvoid UpdateDefaultLeg(double v)
{
defaultLeg.Add(v);
}
publicvoid PrettyPrint()
{
// hard-coded 'report' output
Console.WriteLine("{0} : {1} bps", ID, Math.Round(Spread() * 10000, 2));
}
publicdouble Spread()
{
return defaultLeg.Average() / premiumLeg.Average();
}
}
}

Builder Pattern for C# Basket Default Swap Pricer

$
0
0
Keep your options open is a phrase, we often hear when making decisions concerning our lives. Needless to say, this great advice is directly applicable to any programming scheme. Now, I have to confess that many times (too many times) I feel a big hurry just to finish something quickly and that kind of an approach then leads to programs having no options for changes or modifications. Good example of this is BDS pricer program presented in the previous post.


PROBLEM


So, what is then wrong with that program? There should be nothing wrong with the program, but experienced programmers should catch a lot of different issues to be improved. Program is actually not very flexible as it is at the moment. However, I will concentrate on just one specific problem: the object creation part of the program. When looking at its client (class Program), we can see how it is polluted with object creation procedures (DiscountCurve, CreditCurve, NAGCopula) and hard-coded data. What if I would like to create my objects and data from text file or from Excel workbook? The mess (main code size explosion) would be even greater. Needless to say, the program as it is now, is not leaving much of those options open.


SOLUTION


Solution for this problem is Builder design pattern. Gang of Four (GOF) definition for Builder is the following.

"The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations."

In the new program design, BasketCDS class needs three different objects: Copula, CreditMatrix and DiscountCurve. Ultimately, client will feed BasketCDS class with these three objects wrapped inside Tuple. Before this, client is using concrete ExcelBuilder (implementation of abstract ComponentBuilder) for creating all three objects. So, client is effectively outsourcing the creation part of this complex object (a tuple, consisting of three objects) for ExcelBuilder. Just for example, instead of ExcelBuilder we could have TextFileBuilder or ConsoleBuilder. So, the source from which we are creating these objects (Copula, CreditMatrix and DiscountCurve) can be changed. Moreover, our client has no part in the object creation process. As far as I see it, this scheme is satisfying that GOF definition for Builder pattern. With this small change in design, we are now having some important options open.


PROGRAM FLOW


In order to understand the program data flow a bit better, a simple class UML has been presented in the picture below. I would like to stress the fact, that in no way this presentation is formally correct. However, the point is just to open up how this program is working on a very general level.














Client is requesting for creation of several objects from ExcelBuilder object, which is an implementation of abstract ComponentBuilder class. ExcelBuilder is then creating three central objects (CreditMatrix, DiscountCurve and Copula) into generic Tuple.

Client is then feeding BasketCDS with Tuple, which is consisting all required objects. BasketCDS object is starting Monte Carlo process and sending calculated leg values to Statistics object by using delegate methods. Finally, client is requesting the result (basket CDS spread) from Statistics object.

MathTools and MatrixTools classes are static utility classes, used by several objects for different mathematical or matrix operations. 



EXCEL BUILDER


For this example program, Excel is the most convenient platform for feeding parameters for our C# program. At this posting, I am only going to present the Excel interface and the results. For this kind of scheme, interfacing C# to Excel with Excel-DNA has been thoroughly covered in this blog posting.

My current Excel worksheet interface is the following.
















The idea is simple. Client (Main) will delegate the creation part of three core objects (Copula, CreditMatrix and DiscountCurve) for ExcelBuilder class. ExcelBuilder class has been carefully configured to get specific data from specific ranges in Excel workbook. For example, it will read co-variance matrix from the named Excel range (_COVARIANCE). Personally, I am always using named Excel ranges instead of range addresses, in order to maintain flexibility when changing location of some range in the worksheet. By using this kind of a scheme, no further modifications will be needed in the program, since it communicates only with named Excel ranges.

After creating all those named ranges, a Button (Form Control) has been created into Excel worksheet, having program name Execute in its Assign Macro Box. I have been using Form Controls in order to get rid of any VBA code completely. Any public static void method in .NET code will be registered automatically by Excel-DNA as a macro in Excel. Thanks for this tip goes to Govert Van Drimmelen (inventor, developer and author of Excel-DNA).


PROGRAM RUN


After pressing Execute button, my C# program will write the following Basket CDS prices and program running time in seconds back to Excel worksheet (yellow areas).



















THE PROGRAM


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using
System.Diagnostics;
using ExcelDna.Integration;
using NagLibrary;

namespace ExcelBasketCDSPricer
{
// CLIENT
publicstaticclassMain
{
privatestatic Stopwatch timer;
privatestaticdynamic Excel;
privatestatic ComponentBuilder builder;
privatestatic Tuple<Copula, CreditMatrix, DiscountCurve> components;
privatestatic List<BasketCDS> engines;
privatestatic List<Statistics> statistics;
//
publicstaticvoid Execute()
{
try
{
// create timer and Excel objects
timer = new Stopwatch();
timer.Start();
Excel = ExcelDnaUtil.Application;
//
// create Excel builder objects and request for required components for all pricers
builder = new ExcelBuilder();
components = builder.GetComponents();
int nSimulations = (int)Excel.Range["_N"].Value2;
int nReferences = (int)Excel.Range["_REFERENCES"].Value2;
//
// create basket pricers and statistics gatherers into containers
engines = new List<BasketCDS>();
statistics = new List<Statistics>();
//
// order updating for statistics gatherers from basket pricers
for (int kth = 1; kth <= nReferences; kth++)
{
statistics.Add(new Statistics(String.Concat(kth.ToString(), "-to-default")));
engines.Add(new BasketCDS(kth, nSimulations, nReferences, components));
engines[kth - 1].updateDefaultLeg += statistics[kth - 1].UpdateDefaultLeg;
engines[kth - 1].updatePremiumLeg += statistics[kth - 1].UpdatePremiumLeg;
}
//
// run basket pricers
engines.ForEach(it => it.Process());
double[] prices =
statistics.Select(it => Math.Round(it.Spread() * 10000, 3)).ToArray<double>();
timer.Stop();
//
// print statistics back to Excel
Excel.Range["_PRICES"] = Excel.WorksheetFunction.Transpose(prices);
Excel.Range["_RUNTIME"] = Math.Round(timer.Elapsed.TotalSeconds, 3);
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
}
//
// *******************************************************************************
// abstract base class for all component builders
publicabstractclassComponentBuilder
{
publicabstract Copula CreateCopula();
publicabstract CreditMatrix CreateCreditMatrix();
publicabstract DiscountCurve CreateDiscountCurve();
publicabstract Tuple<Copula, CreditMatrix, DiscountCurve> GetComponents();
}
//
// implementation for builder reading parameters from Excel
publicclassExcelBuilder : ComponentBuilder
{
dynamic Excel = null;
private Copula copula = null;
private CreditMatrix credit = null;
private DiscountCurve curve = null;
//
public ExcelBuilder()
{
Excel = ExcelDnaUtil.Application;
}
publicoverride Copula CreateCopula()
{
// create covariance matrix
dynamic matrix = Excel.Range["_COVARIANCE"].Value2;
int rows = matrix.GetUpperBound(0);
int columns = matrix.GetUpperBound(1);
double[,] covariance = newdouble[rows, columns];
//
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
covariance[i, j] = (double)matrix.GetValue(i + 1, j + 1);
}
}
//
// create copula source information
string source = (string)Excel.Range["_COPULA"].Value2;
//
// special parameters for NAG copula
if (source == "NAG")
{
// generator id
int genid = (int)Excel.Range["_GEN"].Value2;
//
// sub generator id
int subgenid = (int)Excel.Range["_SUBGEN"].Value2;
//
// mode
int mode = (int)Excel.Range["_MODE"].Value2;
//
// degrees of freedom
int df = (int)Excel.Range["_DF"].Value2;
//
// create NAG copula
copula = new NAGCopula(covariance, genid, subgenid, mode, df);
}
return copula;
}
publicoverride CreditMatrix CreateCreditMatrix()
{
// create cds matrix
dynamic matrix = Excel.Range["_CDS"].Value2;
int rows = matrix.GetUpperBound(0);
int columns = matrix.GetUpperBound(1);
double[,] cds = newdouble[rows, columns];
//
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
cds[i, j] = (double)matrix.GetValue(i + 1, j + 1);
}
}
//
// recovery estimate and discounting method as generic delegate
double recovery = (double)Excel.Range["_RECOVERY"].Value2;
Func<double, double> df = curve.GetDF;
//
CreditMatrix credit = new CreditMatrix(cds, df, recovery);
return credit;
}
publicoverride DiscountCurve CreateDiscountCurve()
{
// create zero-coupon curve
dynamic matrix = Excel.Range["_ZERO"].Value2;
int rows = matrix.GetUpperBound(0);
int columns = matrix.GetUpperBound(1);
double[,] zeroCouponCurve = newdouble[rows, columns];
//
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
zeroCouponCurve[i, j] = (double)matrix.GetValue(i + 1, j + 1);
}
}
//
// create interpolation method
int interpolationMethodID = (int)Excel.Range["_INTERPOLATION"].Value2;
InterpolationAlgorithm interpolation = null;
switch (interpolationMethodID)
{
case 1:
interpolation = MathTools.LinearInterpolation;
break;
default:
thrownew Exception("interpolation algorithm not defined");
}
//
// create discounting method
int discountingMethodID = (int)Excel.Range["_DISCOUNTING"].Value2;
DiscountAlgorithm discounting = null;
switch (discountingMethodID)
{
case 1:
discounting = MathTools.ContinuousDiscountFactor;
break;
default:
thrownew Exception("discounting algorithm not defined");
}
DiscountCurve curve = new DiscountCurve(zeroCouponCurve, interpolation, discounting);
return curve;
}
publicoverride Tuple<Copula, CreditMatrix, DiscountCurve> GetComponents()
{
copula = CreateCopula();
curve = CreateDiscountCurve();
credit = CreateCreditMatrix();
Tuple<Copula, CreditMatrix, DiscountCurve> components =
new Tuple<Copula, CreditMatrix, DiscountCurve>(copula, credit, curve);
return components;
}
}
//
// *******************************************************************************
publicclassBasketCDS
{
public PremiumLegUpdate updatePremiumLeg; // delegate for sending value
public DefaultLegUpdate updateDefaultLeg; // delegate for sending value
//
private Copula copula; // NAG copula model
private CreditMatrix cds; // credit matrix
private DiscountCurve curve; // discount curve
privateint k; // kth-to-default
privateint m; // number of reference assets
privateint n; // number of simulations
privateint maturity; // basket cds maturity in years (integer)
//
public BasketCDS(int kth, int simulations, int maturity,
Tuple<Copula, CreditMatrix, DiscountCurve> components)
{
this.k = kth;
this.n = simulations;
this.maturity = maturity;
this.copula = components.Item1;
this.cds = components.Item2;
this.curve = components.Item3;
}
publicvoid Process()
{
// request correlated random numbers from copula model
double[,] randomArray = copula.Create(n, Math.Abs(Guid.NewGuid().GetHashCode()));
m = randomArray.GetLength(1);
//
// process n sets of m correlated random numbers
for (int i = 0; i < n; i++)
{
// create a set of m random numbers needed for one simulation round
double[,] set = newdouble[1, m];
for (int j = 0; j < m; j++)
{
set[0, j] = randomArray[i, j];
}
//
// calculate default times for reference name set
calculateDefaultTimes(set);
}
}
privatevoid calculateDefaultTimes(double[,] arr)
{
// convert uniform random numbers into default times
int cols = arr.GetLength(1);
double[,] defaultTimes = newdouble[1, cols];
//
for (int j = 0; j < cols; j++)
{
// iteratively, find out the default tenor bucket
double u = arr[0, j];
double t = Math.Abs(Math.Log(1 - u));
//
for (int k = 0; k < cds.CumulativeHazardMatrix.GetLength(1); k++)
{
int tenor = 0;
double dt = 0.0; double defaultTenor = 0.0;
if (cds.CumulativeHazardMatrix[k, j] >= t)
{
tenor = k;
if (tenor >= 1)
{
// calculate the exact default time for a given reference name
dt = -(1 / cds.HazardMatrix[k, j]) * Math.Log((1 - u)
/ (Math.Exp(-cds.CumulativeHazardMatrix[k - 1, j])));
defaultTenor = tenor + dt;
//
// default time after basket maturity
if (defaultTenor >= maturity) defaultTenor = 0.0;
}
else
{
// hard-coded assumption
defaultTenor = 0.5;
}
defaultTimes[0, j] = defaultTenor;
break;
}
}
}
// proceed to calculate leg values
updateLegValues(defaultTimes);
}
privatevoid updateLegValues(double[,] defaultTimes)
{
// check for defaulted reference names, calculate leg values
// and send statistics updates for BasketCDSStatistics
int nDefaults = getNumberOfDefaults(defaultTimes);
if (nDefaults > 0)
{
// for calculation purposes, remove zeros and sort matrix
MatrixTools.RowMatrix_removeZeroValues(ref defaultTimes);
MatrixTools.RowMatrix_sort(ref defaultTimes);
}
// calculate and send values for statistics gatherer
double dl = calculateDefaultLeg(defaultTimes, nDefaults);
double pl = calculatePremiumLeg(defaultTimes, nDefaults);
updateDefaultLeg(dl);
updatePremiumLeg(pl);
}
privateint getNumberOfDefaults(double[,] arr)
{
int nDefaults = 0;
for (int i = 0; i < arr.GetLength(1); i++)
{
if (arr[0, i] > 0.0) nDefaults++;
}
return nDefaults;
}
privatedouble calculatePremiumLeg(double[,] defaultTimes, int nDefaults)
{
double dt = 0.0; double t; double p = 0.0; double v = 0.0;
if ((nDefaults > 0) && (nDefaults >= k))
{
for (int i = 0; i < k; i++)
{
if (i == 0)
{
// premium components from 0 to t1
dt = defaultTimes[0, i] - 0.0;
t = dt;
p = 1.0;
}
else
{
// premium components from t1 to t2, etc.
dt = defaultTimes[0, i] - defaultTimes[0, i - 1];
t = defaultTimes[0, i];
p = (m - i) / (double)m;
}
v += (curve.GetDF(t) * dt * p);
}
}
else
{
for (int i = 0; i < maturity; i++)
{
v += curve.GetDF(i + 1);
}
}
return v;
}
privatedouble calculateDefaultLeg(double[,] defaultTimes, int nDefaults)
{
double v = 0.0;
if ((nDefaults > 0) && (nDefaults >= k))
{
v = (1 - cds.Recovery) * curve.GetDF(defaultTimes[0, k - 1]) * (1 / (double)m);
}
return v;
}
}
//
// *******************************************************************************
// abstract base class for all copula models
publicabstractclassCopula
{
// request matrix of correlated uniform random numbers
// number of rows are given argument n
// Number of columns are inferred from the size of covariance matrix and
publicabstractdouble[,] Create(int n, int seed);
}
//
// NAG G05 COPULAS WRAPPER
publicclassNAGCopula : Copula
{
privatedouble[,] covariance;
privateint genID;
privateint subGenID;
privateint mode;
privateint df;
privateint m;
privateint errorNumber;
privatedouble[] r;
privatedouble[,] result;
//
public NAGCopula(double[,] covariance, int genID,
int subGenID, int mode, int df = 0)
{
// ctor : create correlated uniform random numbers
// degrees-of-freedom parameter (df), being greater than zero,
// will automatically trigger the use of student copula
this.covariance = covariance;
this.genID = genID;
this.subGenID = subGenID;
this.mode = mode;
this.df = df;
this.m = covariance.GetLength(1);
r = newdouble[m * (m + 1) + (df > 0 ? 2 : 1)];
}
publicoverridedouble[,] Create(int n, int seed)
{
result = newdouble[n, m];
G05.G05State g05State = new G05.G05State(genID, subGenID, newint[1] { seed }, out errorNumber);
if (errorNumber != 0) thrownew Exception("G05 state failure");
//
if (this.df != 0)
{
// student copula
G05.g05rc(mode, n, df, m, covariance, r, g05State, result, out errorNumber);
}
else
{
// gaussian copula
G05.g05rd(mode, n, m, covariance, r, g05State, result, out errorNumber);
}
//
if (errorNumber != 0) thrownew Exception("G05 method failure");
return result;
}
}
//
// *******************************************************************************
publicclassCreditMatrix
{
private Func<double, double> df;
privatedouble recovery;
privatedouble[,] CDSSpreads;
privatedouble[,] survivalMatrix;
privatedouble[,] hazardMatrix;
privatedouble[,] cumulativeHazardMatrix;
//
public CreditMatrix(double[,] CDSSpreads,
Func<double, double> discountFactor, double recovery)
{
this.df = discountFactor;
this.CDSSpreads = CDSSpreads;
this.recovery = recovery;
createSurvivalMatrix();
createHazardMatrices();
}
// public read-only accessors to class data
publicdouble[,] HazardMatrix { get { returnthis.hazardMatrix; } }
publicdouble[,] CumulativeHazardMatrix { get { returnthis.cumulativeHazardMatrix; } }
publicdouble Recovery { get { returnthis.recovery; } }
//
privatevoid createSurvivalMatrix()
{
// bootstrap matrix of survival probabilities from given CDS data
int rows = CDSSpreads.GetUpperBound(0) + 2;
int cols = CDSSpreads.GetUpperBound(1) + 1;
survivalMatrix = newdouble[rows, cols];
//
double term = 0.0; double firstTerm = 0.0; double lastTerm = 0.0;
double terms = 0.0; double quotient = 0.0;
int i = 0; int j = 0; int k = 0;
//
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
if (i == 0) survivalMatrix[i, j] = 1.0;
if (i == 1) survivalMatrix[i, j] = (1 - recovery) / ((1 - recovery) + 1 * CDSSpreads[i - 1, j] / 10000);
if (i > 1)
{
terms = 0.0;
for (k = 0; k < (i - 1); k++)
{
term = df(k + 1) * ((1 - recovery) * survivalMatrix[k, j] -
(1 - recovery + 1 * CDSSpreads[i - 1, j] / 10000) * survivalMatrix[k + 1, j]);
terms += term;
}
quotient = (df(i) * ((1 - recovery) + 1 * CDSSpreads[i - 1, j] / 10000));
firstTerm = (terms / quotient);
lastTerm = survivalMatrix[i - 1, j] * (1 - recovery) / (1 - recovery + 1 * CDSSpreads[i - 1, j] / 10000);
survivalMatrix[i, j] = firstTerm + lastTerm;
}
}
}
}
privatevoid createHazardMatrices()
{
// convert matrix of survival probabilities into two hazard rate matrices
int rows = survivalMatrix.GetUpperBound(0);
int cols = survivalMatrix.GetUpperBound(1) + 1;
hazardMatrix = newdouble[rows, cols];
cumulativeHazardMatrix = newdouble[rows, cols];
int i = 0; int j = 0;
//
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
cumulativeHazardMatrix[i, j] = -Math.Log(survivalMatrix[i + 1, j]);
if (i == 0) hazardMatrix[i, j] = cumulativeHazardMatrix[i, j];
if (i > 0) hazardMatrix[i, j] = (cumulativeHazardMatrix[i, j] - cumulativeHazardMatrix[i - 1, j]);
}
}
}
}
//
// *******************************************************************************
// delegate methods for interpolation and discounting
publicdelegatedouble InterpolationAlgorithm(double t, refdouble[,] curve);
publicdelegatedouble DiscountAlgorithm(double t, double r);
//
publicclassDiscountCurve
{
// specific algorithms for interpolation and discounting
private InterpolationAlgorithm interpolationMethod;
private DiscountAlgorithm discountMethod;
privatedouble[,] curve;
//
public DiscountCurve(double[,] curve,
InterpolationAlgorithm interpolationMethod, DiscountAlgorithm discountMethod)
{
this.curve = curve;
this.interpolationMethod = interpolationMethod;
this.discountMethod = discountMethod;
}
publicdouble GetDF(double t)
{
// get discount factor from discount curve
return discountMethod(t, interpolation(t));
}
privatedouble interpolation(double t)
{
// get interpolation from discount curve
return interpolationMethod(t, refthis.curve);
}
}
//
// *******************************************************************************
// collection of methods for different types of mathematical operations
publicstaticclassMathTools
{
publicstaticdouble LinearInterpolation(double t, refdouble[,] curve)
{
int n = curve.GetUpperBound(0) + 1;
double v = 0.0;
//
// boundary checkings
if ((t < curve[0, 0]) || (t > curve[n - 1, 0]))
{
if (t < curve[0, 0]) v = curve[0, 1];
if (t > curve[n - 1, 0]) v = curve[n - 1, 1];
}
else
{
// iteration through all given curve points
for (int i = 0; i < n; i++)
{
if ((t >= curve[i, 0]) && (t <= curve[i + 1, 0]))
{
v = curve[i, 1] + (curve[i + 1, 1] - curve[i, 1]) * (t - (i + 1));
break;
}
}
}
return v;
}
publicstaticdouble ContinuousDiscountFactor(double t, double r)
{
return Math.Exp(-r * t);
}
}
//
// *******************************************************************************
// collection of methods for different types of matrix operations
publicstaticclassMatrixTools
{
publicstaticdouble[,] CorrelationToCovariance(double[,] corr, double[] stdev)
{
// transform correlation matrix to co-variance matrix
double[,] cov = newdouble[corr.GetLength(0), corr.GetLength(1)];
//
for (int i = 0; i < cov.GetLength(0); i++)
{
for (int j = 0; j < cov.GetLength(1); j++)
{
cov[i, j] = corr[i, j] * stdev[i] * stdev[j];
}
}
//
return cov;
}
publicstaticvoid RowMatrix_sort(refdouble[,] arr)
{
// sorting a given row matrix to ascending order
// input must be 1 x M matrix
// bubblesort algorithm implementation
int cols = arr.GetUpperBound(1) + 1;
double x = 0.0;
//
for (int i = 0; i < (cols - 1); i++)
{
for (int j = (i + 1); j < cols; j++)
{
if (arr[0, i] > arr[0, j])
{
x = arr[0, i];
arr[0, i] = arr[0, j];
arr[0, j] = x;
}
}
}
}
publicstaticvoid RowMatrix_removeZeroValues(refdouble[,] arr)
{
// removes zero values from a given row matrix
// input must be 1 x M matrix
List<double> temp = new List<double>();
int cols = arr.GetLength(1);
int counter = 0;
for (int i = 0; i < cols; i++)
{
if (arr[0, i] > 0)
{
counter++;
temp.Add(arr[0, i]);
}
}
if (counter > 0)
{
arr = newdouble[1, temp.Count];
for (int i = 0; i < temp.Count; i++)
{
arr[0, i] = temp[i];
}
}
else
{
arr = null;
}
}
}
//
// *******************************************************************************
publicdelegatevoid PremiumLegUpdate(double v);
publicdelegatevoid DefaultLegUpdate(double v);
//
publicclassStatistics
{
// data structures for storing leg values
private List<double> premiumLeg;
private List<double> defaultLeg;
privatestring ID;
//
public Statistics(string ID)
{
this.ID = ID;
premiumLeg = new List<double>();
defaultLeg = new List<double>();
}
publicvoid UpdatePremiumLeg(double v)
{
premiumLeg.Add(v);
}
publicvoid UpdateDefaultLeg(double v)
{
defaultLeg.Add(v);
}
publicvoid PrettyPrint()
{
// hard-coded 'report' output
Console.WriteLine("{0} : {1} bps", ID, Math.Round(Spread() * 10000, 2));
}
publicdouble Spread()
{
return defaultLeg.Average() / premiumLeg.Average();
}
}
}


CONCLUSIONS


In this blog posting, Builder design pattern was applied to solve some of the problems observed in hard-coded program. By outsourcing the object creation for Builder (ExcelBuilder), client code size explosion has been avoided completely. Moreover, the source for objects creation is now open for new implementations (ConsoleBuilder, TextFileBuilder, etc). The program is still not as flexible as it could be, but that is another story.

Again, Thanks for Govert Van Drimmelen for his amazing Excel-DNA. For learning more things about Excel-DNA, check out its homepage. Getting more information and examples with your problems, the main source is Excel-DNA google group. Excel-DNA is an open-source project, and we (the happy users) can invest its future development by making a donation.

Last week, I spent three days in Datasim training course. During the course, we went systematically through most of the GOF design patterns in C#, using traditional object-oriented approach. Plus, the instructor was also presenting, how to use C# generics (delegates) for implementing some of the design patterns, using functional programming approach. The course was truly having a great balance between theory and programming. So, if anyone is looking for very practical hands-on training for this GOF stuff, this course is getting all five stars from me.

Thank You for reading my blog. I wish you have discovered some usage for Builder pattern in your programs. Mike.

Pricing Bloomberg Swap Manager Transactions with C#

$
0
0
From time to time, I need to have reliable second opinion or benchmark valuation for a given book of swap transactions. Bloomberg Swap Manager (SWPM) is just a great tool for pricing many different kinds of swaps and other types of transactions. In this small project, we are constructing a program, which can be used to request valuation fields for several portfolios (one portfolio is a list of Swap Manager transactions ID's stored in source file). Program is using Bloomberg C# API for requesting valuation fields from Bloomberg.

SWAP MANAGER AND FIELD SEARCH

Let us assume, that the following cross-currency basis swap transaction has been created and stored in Bloomberg Swap Manager. Note, that when the transaction is stored, Swap Manager is automatically creating identification code for this transaction (SL5Q3637 - on the upper right corner of the screen).



















When using Bloomberg Field Search (FLDS) for this transaction (SL5Q3637 Corp), I can request all the fields (static, calculated) what are available in any of the Swap Manager screens. Just as one example, we see that in the transaction screen above, dirty PV for this swap is 12 157 660 EUR (for historical valuation date 5.6.2015).







Field search is showing the value of  11 889 635 EUR (SW_MARKET_VAL). However, since the transaction valuation date is historical, we need to override settlement and curve date. Clicking SW_MARKET_VAL (dirty PV) field in the Field Search screen will show all the parameters, what are available for overriding for this particular field.















By overriding SETTLE_DT and SW_CURVE_DT,  PV (SW_MARKET_VAL) is 12 157 660 EUR, which is exactly the same as in our initial transaction screen above. Note, that if you are valuing any transaction per historical date, the both fields (settlement data, curve date) have to have the same date value.

Now we come to the beef of this posting : since we can use Bloomberg Field Search for finding a value for any fields what are available in any of the Swap Manager screens, we can also request values for those fields by using Bloomberg API. The easiest way is just to use Excel and Bloomberg-addin function BDP (security = SL5Q3637 Corp, field = SW_MARKET_VAL). Another way is to request those fields programmatically, by using Bloomberg programming API. In this posting, we are using the latter approach.

PROGRAM INPUT AND OUTPUT

A list of swap transactions (which have already been created and stored in Swap Manager) for a given portfolio (tradingSwaps in this example) is defined in the transaction source file (csv).














The program is producing the following output for a given portfolio of swap transactions (using current configurations).














CONFIGURATION FILES


Application configurations file (App.Config in C# project) is only defining repository folder for all individual portfolio configurations. It should be noted, that we can have several different portfolio configurations at the same time in that repository folder.











Single portfolio configuration file (as shown in the picture below) defines the following information
  • Portfolio name
  • Source data path and file name
  • Field separator for source file
  • Bloomberg fields to be requested
  • Possible Bloomberg override fields
  • Possible Bloomberg override values
  • Type of target output (file, console, excel, etc)
  • Path and file name for output file (if not console)










By handling portfolio construction via individual configuration files, we are achieving a lot of flexibility into this program. For example, different portfolios can
  • read source data (Bloomberg transaction ID's) from any given source data file.
  • have completely different set of Bloomberg request fields and set of field overrides.
  • be printed out into different target medium (console, csv, xml, web-page, Excel, etc).

In a nutshell, the program is first finding configurations repository folder based on the information given in Application configurations file (App.Config). After this, the program is constructing N portfolio configuration objects for all existing configuration files given in repository folder. Finally, program is using Bloomberg API for requesting data for each configuration and then printing out the results based on given configuration.

PROGRAM DESIGN


UML class diagram has been presented below.


GREEN
In the first stage, Configuration objects has to be created. In this example program, Client is sending creation request for static Configurations class. Static Configurations class is then using concrete XMLConfigurationBuilder (implementation of abstract ConfigurationBuilder) to create Configuration objects. Static Configuration class has one to many Configuration objects. Static Configurations class is available for all the other objects during the program execution.

THISTLE
In the second stage, PortfolioProcessor object has to be created. Client is sending creation request for one concrete FileProcessorBuilder (implementation of abstract PortfolioProcessorBuilder) to build one PortfolioProcessor object. On a general level, PortfolioProcessor is hosting several other objects: one to many Portfolio objects (aggregation), one BBCOMMWrapper object (composition) and one to many PortfolioVisitor objects (aggregation).

YELLOW
PortfolioProcessor has one BBCOMMWrapper object (composition) for requesting data from Bloomberg by using BBCOMM Server API. For any portfolio being processed, all data fields (and optional field overrides), which are going to be requested from Bloomberg API, have been defined inside static Configurations class.

TURQUOISE
There is a hierarchy of objects. PortfolioProcessor object has one to many Portfolio objects (aggregation). One Portfolio object has one to many Contract objects (aggregation). One Contract object has one to many Field objects (aggregation). Essentially, when PortfolioProcessor is executing its Process method, it will request results from BBCOMMWrapper object for all contracts and data fields, for all Portfolio objects which are hosted inside PortfolioProcessor object. After receiving results from BBCOMMWrapper object, it will then export all results into corresponding Field objects for each Contract and Portfolio.

WHEAT
When PortfolioProcessor object is being built, concrete FileProcessorBuilder is also using PortfolioVisitorFactory object for creating one or more concrete Visitor objects. References of those Visitor objects are stored inside PortfolioProcessor (aggregation). Essentially, PortfolioProcessor object method Print will print all results (values from stored in Field objects inside Contract objects for each Portfolio object) into selected target medium. For this task, PortfolioProcessor object is using one or more Visitor objects (specific Visitor type for each Portfolio).

THE PROGRAM

It should be noted, that each class (or class hierarchy) should be copy-pasted into a separate cs-file. References to Bloomberg API and System.Configuration should be made in order to compile this program.

// file : Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
using System.IO;
using System.Xml;

namespace PortfolioValuation
{
// CLIENT
classProgram
{
staticvoid Main(string[] args)
{
try
{
// build static configurations from pre-defined configuration files for each portfolio
Configurations.Build(new XMLConfigurationBuilder());
//
// build swap manager object from a given file information
PortfolioProcessorBuilder swapManagerBuilder = new FileProcessorBuilder();
PortfolioProcessor swapManager = swapManagerBuilder.Build();
//
// process and print portfolio
swapManager.Process();
swapManager.Print();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.WriteLine("Program has been executed");
Console.ReadLine();
}
}
}
//
//
// file : PortfolioProcessorBuilder.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;

namespace PortfolioValuation
{
// builder for swap manager object - class hierarchy
publicabstractclassPortfolioProcessorBuilder
{
publicabstract PortfolioProcessor Build();
}
// build data for swap manager object using source files
publicclassFileProcessorBuilder : PortfolioProcessorBuilder
{
publicoverride PortfolioProcessor Build()
{
// build portfolio and visitor objects based on configurations
List<Portfolio> portfolios = new List<Portfolio>();
List<PortfolioVisitor> visitors = new List<PortfolioVisitor>();
//
// loop through configurations
for (int i = 0; i < Configurations.N; i++)
{
// get one configuration, create portfolio object and
// read source data (isin codes) for this portfolio
Configuration table = Configurations.Get(i);
string ID = table.ID;
Portfolio portfolio = new Portfolio(ID);
List<string> sourceData = new List<string>();
TextFileHandler.Read(table.Get("sourcedatafilepathname"), sourceData);
//
// create empty contracts into portfolio
char fieldSeparator = Convert.ToChar(Configurations.Get(ID, "fieldseparator"));
sourceData.ForEach(it =>
{
if (it.Split(Convert.ToChar(fieldSeparator))[0] == ID)
portfolio.AddContract(new Contract(it.Split(fieldSeparator)[1]));
});
//
// decision : portfolio must have at least one contract
if (portfolio.Count() == 0) thrownew Exception("builder : empty portfolio error");
//
// request visitor for swap manager object, created by factory method
PortfolioOutputVisitorFactory factory = new PortfolioOutputVisitorFactory();
string visitorType = table.Get("visitortype");
PortfolioVisitor visitor = factory.CreatePortfolioOutputVisitor(visitorType);
portfolios.Add(portfolio);
visitors.Add(visitor);
}
// return constructed swap manager objects for a client
returnnew PortfolioProcessor(portfolios, visitors);
}
}
}
//
//
// file : PortfolioProcessor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PortfolioValuation
{
publicclassPortfolioProcessor
{
private BBCOMMDataRequest bloombergAPI;
private List<PortfolioVisitor> visitors;
private List<Portfolio> portfolios;
private List<string> securities;
private List<string> bloombergFields;
private List<string> bloombergOverrideFields;
private List<string> bloombergOverrideValues;
//
public PortfolioProcessor(List<Portfolio> portfolios, List<PortfolioVisitor> visitors)
{
this.portfolios = portfolios;
this.visitors = visitors;
}
publicvoid Process()
{
// process all portfolios
foreach (Portfolio portfolio in portfolios)
{
// create information for Bloomberg API
string ID = portfolio.ID;
char fieldSeparator = Convert.ToChar(Configurations.Get(ID, "fieldseparator"));
securities = portfolio.Select(it => it.Isin).ToList();
//
bloombergFields = Configurations.Get(ID, "bloombergfields").Split(fieldSeparator).ToList<string>();
bloombergOverrideFields = Configurations.Get(ID, "bloombergoverridefields").Split(fieldSeparator).ToList<string>();
bloombergOverrideValues = Configurations.Get(ID, "bloombergoverridevalues").Split(fieldSeparator).ToList<string>();
//
// decision : override collections always has to be provided
if ((bloombergOverrideFields == null) || (bloombergOverrideValues == null))
thrownew Exception("swap manager : null override collection error");
//
// create reference data request for Bloomberg API
if ((bloombergOverrideFields.Count != 0) && (bloombergOverrideValues.Count != 0))
{
// data request with overrides
bloombergAPI = new ReferenceDataRequest(securities, bloombergFields, bloombergOverrideFields, bloombergOverrideValues);
}
else
{
// data request without overrides
bloombergAPI = new ReferenceDataRequest(securities, bloombergFields);
}
// receive requested data
dynamic[, ,] result = bloombergAPI.ProcessData();
//
// process bloomberg results into portfolio contracts and fields
int nFields = bloombergFields.Count;
int nSecurities = securities.Count;
for (int i = 0; i < nSecurities; i++)
{
for (int j = 0; j < nFields; j++)
{
Field field = new Field(bloombergFields[j], result[i, 0, j]);
portfolio.UpdateContract(securities[i], field);
}
}
}
}
publicvoid Print()
{
// delegate accept requests for portfolio objects
for (int i = 0; i < portfolios.Count; i++)
{
portfolios[i].AcceptVisitor(visitors[i]);
}
}
}
}
//
//
// file : TextFileHandler.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace PortfolioValuation
{
publicstaticclassTextFileHandler
{
publicstaticvoid Read(string filePathName, List<string> output)
{
// read file content into list
StreamReader reader = new StreamReader(filePathName);
while (!reader.EndOfStream)
{
output.Add(reader.ReadLine());
}
reader.Close();
}
publicstaticvoid Write(string filePathName, List<string> input, bool append)
{
// write text stream list to file
StreamWriter writer = new StreamWriter(filePathName, append);
input.ForEach(it => writer.WriteLine(it));
writer.Close();
}
}
}
//
//
// file : PortfolioVisitor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;

namespace PortfolioValuation
{
// output visitor - class hierarchy
publicabstractclassPortfolioVisitor
{
publicabstractvoid Visit(Portfolio portfolio);
}
publicclassConsolePortfolioVisitor : PortfolioVisitor
{
publicoverridevoid Visit(Portfolio portfolio)
{
//Configuration configuration
foreach (Contract contract in portfolio)
{
string fieldSeparator = Configurations.Get(portfolio.ID, "fieldseparator");
StringBuilder sb = new StringBuilder();
foreach (Field f in contract)
{
sb.Append(f.Value);
sb.Append(fieldSeparator);
}
// print the content of string builder into console
Console.WriteLine(sb.ToString());
}
}
}
publicclassFilePortfolioVisitor : PortfolioVisitor
{
publicoverridevoid Visit(Portfolio portfolio)
{
// extract filePathName for a given portfolio from configuration table
string fieldSeparator = Configurations.Get(portfolio.ID, "fieldseparator");
List<string> resultDataFilePathNames = Configurations.Get(portfolio.ID, "resultdatafilepathname").Split(Convert.ToChar(fieldSeparator)).ToList();
List<string> portfolioIDs = Configurations.Get(portfolio.ID, "portfolioid").Split(Convert.ToChar(fieldSeparator)).ToList();
string resultDataFilePathName = null;
for (int i = 0; i < portfolioIDs.Count; i++)
{
if(portfolioIDs[i] == portfolio.ID)
{
resultDataFilePathName = resultDataFilePathNames[i];
break;
}
}
List<string> fileOutputList = new List<string>();
StringBuilder sb = new StringBuilder();
//
foreach (Contract contract in portfolio)
{
// add all fields into string builder object
foreach (Field f in contract)
{
sb.Append(f.Value);
sb.Append(fieldSeparator);
}
// add constructed string into output list and clear string builder
fileOutputList.Add(sb.ToString());
sb.Clear();
}
// print the content of output list into file
TextFileHandler.Write(resultDataFilePathName, fileOutputList, false);
}
}
}
//
//
// file : PortfolioConfiguration.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PortfolioValuation
{
publicclassPortfolioConfiguration
{
// private member data
publicreadonlystring ID;
private Dictionary<string, string> configurationTable = new Dictionary<string, string>();
//
public PortfolioConfiguration(string ID)
{
this.ID = ID;
}
// add key-value pair
publicvoid Add(string key, stringvalue)
{
// decision : configuration table cannot contain two identical keys
if (configurationTable.ContainsKey(key))
thrownew Exception("configuration table : duplicate key error");
configurationTable.Add(key, value);
}
// get value for a given key
publicstring Get(string key)
{
// decision : value for a given key cannot be returnde if it does not exist
if (!configurationTable.ContainsKey(key))
thrownew Exception("configuration table : invalid key error");
return configurationTable[key];
}
// clear all key-value pairs
publicvoid Clear()
{
// clear all configurations
configurationTable.Clear();
}
}
}
//
//
// file : Portfolio.cs
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PortfolioValuation
{
publicclassPortfolio : IEnumerable<Contract>
{
// private member data
publicreadonlystring ID;
private List<Contract> contracts;
//
// ctor
public Portfolio(string ID)
{
this.ID = ID;
contracts = new List<Contract>();
}
// add one contract into list of contracts
publicvoid AddContract(Contract contract)
{
// decision : portfolio cannot contain two identical contracts
if(contracts.Exists(it => it.Isin == contract.Isin))
thrownew Exception("portfolio : duplicate contract error");
contracts.Add(contract);
}
// update existing contract in list of contracts
publicvoid UpdateContract(string isin, Field field)
{
// contract cannot be updated if it does not exist
if (!contracts.Exists(it => it.Isin == isin))
thrownew Exception("portfolio : update contract error");
//
for (int i = 0; i < contracts.Count; i++)
{
if (contracts[i].Isin == isin)
{
contracts[i].AddField(field);
break;
}
}
}
// implementation for generic IEnumerable
public IEnumerator<Contract> GetEnumerator()
{
foreach (Contract contract in contracts)
{
yieldreturn contract;
}
}
// implementation for non-generic IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
// implementation method for visitor pattern
publicvoid AcceptVisitor(PortfolioVisitor visitor)
{
// send this-object (portfolio) for visitor implementation
visitor.Visit(this);
}
}
}
//
//
// file : PortfolioOutputVisitorFactory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PortfolioValuation
{
publicclassPortfolioOutputVisitorFactory
{
// create output visitor
public PortfolioVisitor CreatePortfolioOutputVisitor(string visitorType)
{
// HARD-CODED selection of output visitors
// add new visitor type into switch branch and write
// implementation into visitor class hierarchy
PortfolioVisitor visitor = null;
switch (visitorType)
{
case"console":
visitor = new ConsolePortfolioVisitor();
break;
case"file":
visitor = new FilePortfolioVisitor();
break;
default:
thrownew Exception("factory : undefined visitor error");
}
return visitor;
}
}
}
//
//
// file : Field.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PortfolioValuation
{
publicclassField
{
// private data and read-only accessors
private KeyValuePair<string, dynamic> kvp;
publicstring Key { get { return kvp.Key; } }
publicdynamic Value { get { return kvp.Value; } }
//
// ctor
public Field(string key, dynamicvalue)
{
kvp = new KeyValuePair<string, dynamic>(key, value);
}
}
}
//
//
// file : Contract.cs
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PortfolioValuation
{
publicclassContract : IEnumerable<Field>
{
// private data and read-only accessor
privatestring isin;
private List<Field> fields;
publicstring Isin { get { return isin; } }
//
// ctor
public Contract(string isin)
{
this.isin = isin;
fields = new List<Field>();
}
// add one field into list of fields
publicvoid AddField(Field field)
{
// decision : contract cannot contain two identical fields
if (fields.Exists(it => it.Key == field.Key))
thrownew Exception("contract : duplicate field error");
fields.Add(field);
}
// implementation for generic IEnumerable
public IEnumerator<Field> GetEnumerator()
{
foreach (Field field in fields)
{
yieldreturn field;
}
}
// implementation for non-generic IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
//
//
// file : Configurations.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PortfolioValuation
{
// static wrapper class for all configurations
publicstaticclassConfigurations
{
// static members and accessors
privatestaticint n;
publicstaticint N { get { return Configurations.n; } }
privatestatic List<Configuration> tables = null;
//
// ctor
publicstaticvoid Build(ConfigurationBuilder builder)
{
tables = builder.Build();
n = tables.Count;
}
publicstaticstring Get(string ID, string key)
{
stringvalue = null;
foreach (Configuration table in tables)
{
if (table.ID == ID)
{
value = table.Get(key);
break;
}
}
returnvalue;
}
publicstatic Configuration Get(int index)
{
return tables[index];
}
}
}
//
//
// file : ConfigurationBuilder.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.IO;
using System.Configuration;

namespace PortfolioValuation
{
// class hierarchy for configuration builder
publicabstractclassConfigurationBuilder
{
publicabstract List<Configuration> Build();
}
publicclassXMLConfigurationBuilder : ConfigurationBuilder
{
privatestring folderPath;
private List<Configuration> configurations;
public XMLConfigurationBuilder()
{
// read path to source folder containing configurations for all portfolios directly from system configuration file
this.folderPath = ConfigurationManager.AppSettings["PortfolioConfigurationsFolder"].ToString();
configurations = new List<Configuration>();
}
publicoverride List<Configuration> Build()
{
foreach (string filePathName in Directory.GetFiles(folderPath))
{
XDocument database = XDocument.Load(filePathName);
Dictionary<string, string> pairs = database.Descendants("configuration")
.Elements().ToDictionary(r => r.Attribute("key").Value.ToString(),
r => r.Attribute("value").Value.ToString());
configurations.Add(new Configuration(pairs["portfolioid"], pairs));
}
return configurations;
}
}
}
//
//
// file : Configuration.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PortfolioValuation
{
publicclassConfiguration
{
publicreadonlystring ID;
private Dictionary<string, string> pairs;
//
// ctor
public Configuration(string ID, Dictionary<string, string> pairs)
{
this.ID = ID;
this.pairs = pairs;
}
// accessor method
publicstring Get(string key)
{
// decision : value for a given key cannot be returned if it does not exist
if (!pairs.ContainsKey(key))
thrownew Exception("configuration : invalid key error");
returnthis.pairs[key];
}
}
}
//
//
// file : BBCOMMWrapper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BBCOMM = Bloomberglp.Blpapi;

namespace PortfolioValuation
{
// enumerators for historical data request settings
publicenum E_PRICING_OPTION { PRICING_OPTION_PRICE, PRICING_OPTION_YIELD };
publicenum E_PERIODICITY_ADJUSTMENT { ACTUAL, CALENDAR, FISCAL };
publicenum E_PERIODICITY_SELECTION { DAILY, WEEKLY, MONTHLY, QUARTERLY, SEMI_ANNUALLY, YEARLY };
publicenum E_NON_TRADING_DAY_FILL_OPTION { NON_TRADING_WEEKDAYS, ALL_CALENDAR_DAYS, ACTIVE_DAYS_ONLY };
publicenum E_NON_TRADING_DAY_FILL_METHOD { PREVIOUS_VALUE, NIL_VALUE };
//
// abstract base class for data request
publicabstractclassBBCOMMDataRequest
{
// BBCOMM names
protectedreadonly BBCOMM.Name SECURITY_DATA = new BBCOMM.Name("securityData");
protectedreadonly BBCOMM.Name FIELD_DATA = new BBCOMM.Name("fieldData");
protectedreadonly BBCOMM.Name FIELD_ID = new BBCOMM.Name("fieldId");
protectedreadonly BBCOMM.Name VALUE = new BBCOMM.Name("value");
protectedreadonly BBCOMM.Name OVERRIDES = new BBCOMM.Name("overrides");
protectedreadonly BBCOMM.Name SECURITIES = new BBCOMM.Name("securities");
protectedreadonly BBCOMM.Name FIELDS = new BBCOMM.Name("fields");
protectedreadonly BBCOMM.Name SEQUENCE_NUMBER = new BBCOMM.Name("sequenceNumber");
protectedreadonly BBCOMM.Name START_DATE = new BBCOMM.Name("startDate");
protectedreadonly BBCOMM.Name END_DATE = new BBCOMM.Name("endDate");
protectedreadonly BBCOMM.Name DATE = new BBCOMM.Name("date");
protectedreadonly BBCOMM.Name PRICING_OPTION = new BBCOMM.Name("pricingOption");
protectedreadonly BBCOMM.Name PERIODICITY_ADJUSTMENT = new BBCOMM.Name("periodicityAdjustment");
protectedreadonly BBCOMM.Name PERIODICITY_SELECTION = new BBCOMM.Name("periodicitySelection");
protectedreadonly BBCOMM.Name NON_TRADING_DAY_FILL_OPTION = new BBCOMM.Name("nonTradingDayFillOption");
protectedreadonly BBCOMM.Name NON_TRADING_DAY_FILL_METHOD = new BBCOMM.Name("nonTradingDayFillMethod");
protectedreadonly BBCOMM.Name OVERRIDE_CURRENCY = new BBCOMM.Name("currency");
//
// const strings, enumerators, etc.
protectedreadonlystring NOT_AVAILABLE = "#N/A";
protectedreadonlystring SESSION_EXCEPTION = "Session not started";
protectedreadonlystring SERVICE_EXCEPTION = "Service not opened";
protectedreadonlystring REQUEST_TYPE_REFERENCE = "ReferenceDataRequest";
protectedreadonlystring REQUEST_TYPE_HISTORICAL = "HistoricalDataRequest";
protectedreadonlystring REFERENCE_DATA_SERVICE = "//blp/refdata";
protectedreadonlystring BLOOMBERG_DATE_FORMAT = "yyyyMMdd";
protected E_PRICING_OPTION pricingOption;
protected E_PERIODICITY_ADJUSTMENT periodicityAdjustment;
protected E_PERIODICITY_SELECTION periodicitySelection;
protected E_NON_TRADING_DAY_FILL_OPTION nonTradingDayFillOption;
protected E_NON_TRADING_DAY_FILL_METHOD nonTradingDayFillMethod;
protectedstring requestType;
protectedstring startDate;
protectedstring endDate;
protectedstring overrideCurrency;
//
// wrapped BBCOMM objects
protected BBCOMM.Session session;
protected BBCOMM.Service service;
protected BBCOMM.Request request;
//
// input data structures
protected List<string> securityNames = new List<string>();
protected List<string> fieldNames = new List<string>();
protected List<string> overrideFields = new List<string>();
protected List<string> overrideValues = new List<string>();
//
// output result data structure
protecteddynamic[, ,] result;
//
publicdynamic[, ,] ProcessData()
{
Open();
CreateRequest();
SendRequest();
Close();
return result;
}
privatevoid Open()
{
// create and start bloomberg BBCOMM session
BBCOMM.SessionOptions sessionOptions = new BBCOMM.SessionOptions();
session = new BBCOMM.Session(sessionOptions);
if (!session.Start()) thrownew Exception(SESSION_EXCEPTION);
//
// get service from session object and create request by service object
if (!session.OpenService(REFERENCE_DATA_SERVICE)) thrownew Exception(SERVICE_EXCEPTION);
service = session.GetService(REFERENCE_DATA_SERVICE);
request = service.CreateRequest(requestType);
}
privatevoid CreateRequest()
{
// append securities, fields
foreach (string securityName in securityNames) request.Append(SECURITIES, securityName);
foreach (string fieldName in fieldNames) request.Append(FIELDS, fieldName);
//
// conditionally, append overrides into request object
if (overrideFields.Count > 0)
{
BBCOMM.Element requestOverrides = request.GetElement(OVERRIDES);
for (int i = 0; i < overrideFields.Count; i++)
{
BBCOMM.Element requestOverride = requestOverrides.AppendElement();
requestOverride.SetElement(FIELD_ID, overrideFields[i]);
requestOverride.SetElement(VALUE, overrideValues[i]);
}
}
// set optional parameters for historical data request
if (requestType == REQUEST_TYPE_HISTORICAL)
{
request.Set(START_DATE, startDate);
request.Set(END_DATE, endDate);
request.Set(PRICING_OPTION, pricingOption.ToString());
request.Set(PERIODICITY_ADJUSTMENT, periodicityAdjustment.ToString());
request.Set(PERIODICITY_SELECTION, periodicitySelection.ToString());
request.Set(NON_TRADING_DAY_FILL_OPTION, nonTradingDayFillOption.ToString());
request.Set(NON_TRADING_DAY_FILL_METHOD, nonTradingDayFillMethod.ToString());
if (overrideCurrency != String.Empty) request.Set(OVERRIDE_CURRENCY, overrideCurrency);
}
}
privatevoid SendRequest()
{
// send constructed request to BBCOMM server
long ID = Guid.NewGuid().GetHashCode();
session.SendRequest(request, new BBCOMM.CorrelationID(ID));
bool isProcessing = true;
//
while (isProcessing)
{
// receive data response from BBCOMM server, send
// response to be processed by sub-classed algorithm
BBCOMM.Event response = session.NextEvent();
switch (response.Type)
{
case BBCOMM.Event.EventType.PARTIAL_RESPONSE:
ProcessDataResponse(ref response);
break;
case BBCOMM.Event.EventType.RESPONSE:
ProcessDataResponse(ref response);
isProcessing = false;
break;
default:
break;
}
}
}
privatevoid Close()
{
// close BBCOMM session
if (session != null) session.Stop();
}
//
// sub-classes are providing specific algorithm implementations for
// processing and packing BBCOMM server response data into resulting data structure
protectedabstractvoid ProcessDataResponse(ref BBCOMM.Event response);
}
//
// concrete class implementation for processing reference data request
publicclassReferenceDataRequest : BBCOMMDataRequest
{
public ReferenceDataRequest(List<string> bloombergSecurityNames,
List<string> bloombergFieldNames)
{
// ctor : create reference data request without field overrides
requestType = REQUEST_TYPE_REFERENCE;
securityNames = bloombergSecurityNames;
fieldNames = bloombergFieldNames;
//
// define result data structure dimensions for reference data request
result = newdynamic[securityNames.Count, 1, fieldNames.Count];
}
public ReferenceDataRequest(List<string> bloombergSecurityNames,
List<string> bloombergFieldNames, List<string> bloombergOverrideFields,
List<string> bloombergOverrideValues)
{
// ctor : create reference data request with field overrides
requestType = REQUEST_TYPE_REFERENCE;
securityNames = bloombergSecurityNames;
fieldNames = bloombergFieldNames;
overrideFields = bloombergOverrideFields;
overrideValues = bloombergOverrideValues;
//
// define result data structure dimensions for reference data request
result = newdynamic[securityNames.Count, 1, fieldNames.Count];
}
protectedoverridevoid ProcessDataResponse(ref BBCOMM.Event response)
{
// receive response, which contains N securities and M fields
// event queue can send multiple responses for large requests
foreach (BBCOMM.Message message in response.GetMessages())
{
// extract N securities
BBCOMM.Element securities = message.GetElement(SECURITY_DATA);
int nSecurities = securities.NumValues;
//
// loop through all securities
for (int i = 0; i < nSecurities; i++)
{
// extract one security and fields for this security
BBCOMM.Element security = securities.GetValueAsElement(i);
BBCOMM.Element fields = security.GetElement(FIELD_DATA);
int sequenceNumber = security.GetElementAsInt32(SEQUENCE_NUMBER);
int nFieldNames = fieldNames.Count;
//
// loop through all M fields for this security
for (int j = 0; j < nFieldNames; j++)
{
// if the requested field has been found, pack value into result data structure
if (fields.HasElement(fieldNames[j]))
{
result[sequenceNumber, 0, j] = fields.GetElement(fieldNames[j]).GetValue();
}
// otherwise, pack NOT_AVAILABLE string into data structure
else
{
result[sequenceNumber, 0, j] = NOT_AVAILABLE;
}
}
}
}
}
}
//
// concrete class implementation for processing historical data request
publicclassHistoricalDataRequest : BBCOMMDataRequest
{
privatebool hasDimensions = false;
//
// optional parameters are configured to retrieve time-series having actual daily observations, including all weekdays,
// in the case of non-trading days the previous date value will be used.
public HistoricalDataRequest(List<string> bloombergSecurityNames, List<string> bloombergFieldNames,
DateTime bloombergStartDate, DateTime BloombergEndDate,
E_PRICING_OPTION bloombergPricingOption = E_PRICING_OPTION.PRICING_OPTION_PRICE,
E_PERIODICITY_SELECTION bloombergPeriodicitySelection = E_PERIODICITY_SELECTION.DAILY,
E_PERIODICITY_ADJUSTMENT bloombergPeriodicityAdjustment = E_PERIODICITY_ADJUSTMENT.ACTUAL,
E_NON_TRADING_DAY_FILL_OPTION bloombergNonTradingDayFillOption = E_NON_TRADING_DAY_FILL_OPTION.ALL_CALENDAR_DAYS,
E_NON_TRADING_DAY_FILL_METHOD bloombergNonTradingDayFillMethod = E_NON_TRADING_DAY_FILL_METHOD.PREVIOUS_VALUE,
string bloombergOverrideCurrency = "")
{
// ctor : create historical data request without field overrides
requestType = REQUEST_TYPE_HISTORICAL;
securityNames = bloombergSecurityNames;
fieldNames = bloombergFieldNames;
startDate = bloombergStartDate.ToString(BLOOMBERG_DATE_FORMAT);
endDate = BloombergEndDate.ToString(BLOOMBERG_DATE_FORMAT);
//
pricingOption = bloombergPricingOption;
periodicitySelection = bloombergPeriodicitySelection;
periodicityAdjustment = bloombergPeriodicityAdjustment;
nonTradingDayFillOption = bloombergNonTradingDayFillOption;
nonTradingDayFillMethod = bloombergNonTradingDayFillMethod;
overrideCurrency = bloombergOverrideCurrency;
}
public HistoricalDataRequest(List<string> bloombergSecurityNames, List<string> bloombergFieldNames,
DateTime bloombergStartDate, DateTime BloombergEndDate, List<string> bloombergOverrideFields,
List<string> bloombergOverrideValues,
E_PRICING_OPTION bloombergPricingOption = E_PRICING_OPTION.PRICING_OPTION_PRICE,
E_PERIODICITY_SELECTION bloombergPeriodicitySelection = E_PERIODICITY_SELECTION.DAILY,
E_PERIODICITY_ADJUSTMENT bloombergPeriodicityAdjustment = E_PERIODICITY_ADJUSTMENT.ACTUAL,
E_NON_TRADING_DAY_FILL_OPTION bloombergNonTradingDayFillOption = E_NON_TRADING_DAY_FILL_OPTION.ALL_CALENDAR_DAYS,
E_NON_TRADING_DAY_FILL_METHOD bloombergNonTradingDayFillMethod = E_NON_TRADING_DAY_FILL_METHOD.PREVIOUS_VALUE,
string bloombergOverrideCurrency = "")
{
// ctor : create historical data request with field overrides
requestType = REQUEST_TYPE_HISTORICAL;
securityNames = bloombergSecurityNames;
fieldNames = bloombergFieldNames;
overrideFields = bloombergOverrideFields;
overrideValues = bloombergOverrideValues;
startDate = bloombergStartDate.ToString(BLOOMBERG_DATE_FORMAT);
endDate = BloombergEndDate.ToString(BLOOMBERG_DATE_FORMAT);
//
pricingOption = bloombergPricingOption;
periodicitySelection = bloombergPeriodicitySelection;
periodicityAdjustment = bloombergPeriodicityAdjustment;
nonTradingDayFillOption = bloombergNonTradingDayFillOption;
nonTradingDayFillMethod = bloombergNonTradingDayFillMethod;
overrideCurrency = bloombergOverrideCurrency;
}
protectedoverridevoid ProcessDataResponse(ref BBCOMM.Event response)
{
// unzip and pack messages received from BBCOMM server
// receive one security per message and multiple messages per event
foreach (BBCOMM.Message message in response.GetMessages())
{
// extract security and fields
BBCOMM.Element security = message.GetElement(SECURITY_DATA);
BBCOMM.Element fields = security.GetElement(FIELD_DATA);
//
int sequenceNumber = security.GetElementAsInt32(SEQUENCE_NUMBER);
int nFieldNames = fieldNames.Count;
int nObservationDates = fields.NumValues;
//
// the exact dimension will be known only, when the response has been received from BBCOMM server
if (!hasDimensions)
{
// define result data structure dimensions for historical data request
// observation date will be stored into first field for each observation date
result = newdynamic[securityNames.Count, nObservationDates, fieldNames.Count + 1];
hasDimensions = true;
}
//
// loop through all observation dates
for (int i = 0; i < nObservationDates; i++)
{
// extract all field data for a single observation date
BBCOMM.Element observationDateFields = fields.GetValueAsElement(i);
//
// pack observation date into data structure
result[sequenceNumber, i, 0] = observationDateFields.GetElementAsDatetime(DATE);
//
// then, loop through all 'user-requested' fields for a given observation date
// and pack results data into data structure
for (int j = 0; j < nFieldNames; j++)
{
// pack field value into data structure if such value has been found
if (observationDateFields.HasElement(fieldNames[j]))
{
result[sequenceNumber, i, j + 1] = observationDateFields.GetElement(fieldNames[j]).GetValue();
}
// otherwise, pack NOT_AVAILABLE string into data structure
else
{
result[sequenceNumber, i, j + 1] = NOT_AVAILABLE;
}
}
}
}
}
}
}


AFTERTHOUGHTS

Stripped from all of its superficial complexities, this example program is actually nothing more, but very standard INPUT DATA - PROCESS DATA - OUTPUT RESULTS scheme. However, by implementing some useful Design Patterns (Builder, Factory method, Visitor), we are now having a lot of flexibility and configurability in this program. Getting results file with all requested fields from Bloomberg, for any new portfolio (for a list of Bloomberg Swap Manager transactions stored in a file) is just a matter of creating a new configuration file into existing repository folder.

One small disappointing issue is the manual administration tasks of hosting that list of transactions which are stored in Bloomberg Swap Manager. So far, I have not been able to find a way to request those transactions directly from Bloomberg. However, Bloomberg has recently been releasing its Swaps Toolkit for Excel, which (according to our Bloomberg account manager) has also programming interface. I might get some new ideas for handling swaps books, as soon as I am familiar enough with the product.

UML class diagram has been created with the online tool called YUML. It is completely free, easy to use, the most flexible and the coolest UML tool what I have been using so far, so check it out. And thanks again for spending your precious time here and reading my blog.

-Mike




Bootstrapping Libor Discount Factor and Forward Rate Curves using C# and Excel-DNA

$
0
0
Defining a set of correct discount factors and forward rates is the cornerstone of valuing any Libor-based products. As we know, calculation of present value for a Libor cash flow seems to be almost too easy : first define a cash flow happening in the future by using forward rates, then use discount factors to get present value for that cash flow. Then we also know, that the real deal is always getting those forward rates and discount factors from somewhere, in the first place.

For the task described above, we usually have our front office system or tools provided by some third-party vendor, which are then performing all those required complex calculations quietly behind the scenes. Figures are popping up in the screen, finding their way into some strange reports and everyone are happy. Needless to say, we should be able to perform those calculations also by hand, if so required. This is only my personal opinion on the matter, of course. During this small project, we are creating a program, which takes in market data as input parameter. From this market data, the program is then performing all those complex calculations (bootstrapping, interpolations) and returning discount factors and simply-compounded Libor forward rates.

CURVOLOGY

When constructing discount factors and forward rates from market data, one has to make a lot of different decisions. Just for an example
  • How to construct short-end of the curve? For what maturities are we going to use cash securities?
  • How to construct mid-part of the curve? Are we using futures contracts on Libor or Forward Rate Agreements? 
  • Are we going to make adjustments for convexity effect?
  • How to construct long-end of the curve? For what maturity we start to use swap rates?
  • What kind of interpolation methods are we going to use?
  • Do we interpolate discount factors or spot rates?
Due to this high degrees of freedom embedded in this task, it should not be a miracle, that two persons (given the same set of market data) most probably will end up having completely different set of discount factors and forward rates. As I have been studying this topic and getting a bit more familiar with the methods and procedures used by some well-known authors, I have personally come to the conclusion that there are some common guidelines and general conventions, but absolutely not any one universally correct way to do this task.

MARKET DATA

For this project, I have been using dataset and curve creation procedures as presented in Richard Flavell's excellent book Swaps and Other Derivatives. In this book, Flavell is giving complete treatment, how to construct Libor zero curve (discount factors and forward rates). One of the things why I like so much this book is the fact, that Flavell is not assuming anything. Instead, he is always deriving everything from the beginning. Great thing is also, that the book has all the example calculations dissected in the attached Excel workbooks. Personally, these Excels have been sort of a goldmine for me, when I have been studying different valuation issues.

PROJECT OUTCOME

The result of this project is XLL addin for Excel, which calculates discount factors and simply-compounded forward rates for USD Libor. We will use Excel-DNA for interfacing our C# program with Excel. The scheme what has been used for this project, has been fully explained in this blog post. So, carefully follow the instructions given in that post, when interfacing the program with Excel. Needless to say, one should be able to use the core classes of the program (the classes which are actually creating the curves) in any other C# project, without Excel interfacing.

PROGRAM DESIGN

Rough UML class diagram for this program is shown below. The purpose of this diagram is to get some conceptual overview, how the program is working and how the objects are related with each other. Since I am not UML expert, I am presenting my deepest apologies for its incompleteness and possible errors.

In the first stage, Client (CreateUSDLiborCurves, which will be presented in the section Excel Interfacing) is requesting ExcelBuilder (specialization of abstract MarketDataBuilder) to build market data and return it as a CurveData object. Then, Client is creating and using USDLiborZeroCurve object (implementation of ICurve interface). Inside USDLiborZeroCurve object, the curves (discount factors and forward rates) are going to be created in different types of sequential calculations (data checking, data selection, bootstrapping, interpolation). Finally, Client is requesting ExcelPrinter (specialization of abstract CurvePrinter) object to print the resulting dataset into Excel worksheet. During the calculation process, USDLiborZeroCurve is also using two static classes (DateConvention and Interpolation, not shown in UML diagram), which contains different types of date-related and interpolation-related methods.

























THE PROGRAM

The part of the program, which is creating the outcome curves (discount factors and forward rates), is shown below. Create a new Class project and just copyPaste everything into a new cs-file.


using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using ExcelDna.Integration;

namespace CurveAddin
{
//
// public enumerators and delegate methods
publicenum ENUM_MARKETDATATYPE { CASH = 1, FRA = 2, FUTURE = 3, SWAP = 4, DF = 5, SPOTRATE = 6, FORWARDRATE = 7 }
publicenum ENUM_PERIODTYPE { MONTHS = 1, YEARS = 2 }
publicdelegatedouble DayCountFactor(DateTime start, DateTime end);
publicdelegate DateTime AddPeriod(DateTime start, ENUM_PERIODTYPE periodType, int period);
publicdelegatedouble Interpolator(DayCountFactor dayCountFactor, Dictionary<DateTime, double> data, DateTime key);
publicdelegatedouble ConvexityAdjustment(double rateVolatility, double start, double end);
//
//
// class hierarchy for curve printer
publicabstractclassCurvePrinter
{
publicabstractvoid Print();
}
publicclassExcelPrinter : CurvePrinter
{
privatestaticdynamic Excel;
privatedynamic[,] data;
public ExcelPrinter(dynamic[,] data)
{
this.data = data;
}
publicoverridevoid Print()
{
// Create Excel application
Excel = ExcelDnaUtil.Application;
//
// clear old data from output range, resize output range
// and finally print data to Excel worksheet
Excel.Range["_USDLiborZeroCurve"].CurrentRegion = "";
Excel.Range["_USDLiborZeroCurve"].Resize[data.GetLength(0), data.GetLength(1)] = data;
}
}
//
//
// class hierarchy for market data builder
publicabstractclassMarketDataBuilder
{
publicabstract CurveData Build();
}
publicclassExcelBuilder : MarketDataBuilder
{
privatestaticdynamic Excel;
private DateTime settlementDate;
public DateTime SettlementDate { get { returnthis.settlementDate; } }
//
publicoverride CurveData Build()
{
// Create Excel application
Excel = ExcelDnaUtil.Application;
//
// read settlement date from Excel worksheet
settlementDate = DateTime.FromOADate((double)Excel.Range("_settlementDate").Value2);
//
// read source security data from Excel worksheet
object[,] ExcelSourceData = (object[,])Excel.Range["_marketData"].CurrentRegion.Value2;
//
// create curve data object from source security data
CurveData marketData = new CurveData(Interpolation.LinearInterpolation);
int rows = ExcelSourceData.GetUpperBound(0);
for (int i = 1; i <= rows; i++)
{
DateTime maturity = DateTime.FromOADate((double)ExcelSourceData[i, 1]);
double rate = (double)ExcelSourceData[i, 2];
string instrumentType = ((string)ExcelSourceData[i, 3]).ToUpper();
ENUM_MARKETDATATYPE marketDataType = (ENUM_MARKETDATATYPE)Enum.Parse(typeof(ENUM_MARKETDATATYPE), instrumentType);
marketData.AddCurveDataElement(maturity, rate, marketDataType);
}
return marketData;
}
}
//
//
// interface for all curve objects
publicinterface ICurve
{
void Create();
double GetDF(DateTime maturity);
double GetFWD(DateTime start);
Dictionary<DateTime, double> GetDF(DateTime start, int nYears);
Dictionary<DateTime, double> GetFWD(DateTime start, int nYears);
CurveData DiscountCurve { get; }
CurveData ForwardCurve { get; }
}
//
// implementation for USD Libor curve
publicclassUSDLiborZeroCurve : ICurve
{
publicreadonly DayCountFactor dayCountFactor;
publicreadonly AddPeriod addPeriod;
publicreadonlyint basis;
publicreadonly Interpolator interpolator;
publicreadonly DateTime settlementDate;
public CurveData DiscountCurve { get { returnthis.discountCurve; } }
public CurveData ForwardCurve { get { returnthis.forwardCurve; } }
//
private CurveData marketData;
private CurveData curveDataSelection;
private CurveData bootstrapCurve;
private CurveData spotCurve;
private CurveData discountCurve;
private CurveData forwardCurve;
privateint nCash;
privateint nFuturesOrFRAs;
privatebool adjustmentForConvexity;
private ConvexityAdjustment convexityAdjustment;
privatedouble rateVolatility;
//
public USDLiborZeroCurve(CurveData marketData, Interpolator interpolator, AddPeriod addPeriod,
DayCountFactor dayCountFactor, DateTime settlementDate, int nCash, int nFuturesOrFRAs,
bool adjustmentForConvexity = false, ConvexityAdjustment convexityAdjustment = null, double rateVolatility = 0.0)
{
this.marketData = marketData;
this.interpolator = interpolator;
this.addPeriod = addPeriod;
this.dayCountFactor = dayCountFactor;
this.settlementDate = settlementDate;
this.nCash = nCash;
this.nFuturesOrFRAs = nFuturesOrFRAs;
this.basis = 3; // HARD-CODED !! for USD Libor curve
this.adjustmentForConvexity = adjustmentForConvexity; // optional parameter
this.convexityAdjustment = convexityAdjustment; // optional parameter
this.rateVolatility = rateVolatility; // optional parameter
}
publicvoid Create()
{
// sequence of private methods for creating spot discount curve and
// simply-compounded forward rate curve for a given set of market data
checkMarketData();
selectCurveData();
bootstrapDiscountFactors();
createSpotCurve();
createDiscountCurve();
createForwardCurve();
}
// get discount factor for a given maturity date
publicdouble GetDF(DateTime maturity)
{
return discountCurve.GetMarketRate(ENUM_MARKETDATATYPE.DF, maturity, dayCountFactor);
}
// get dictionary consisting of date and discount factor for a date schedule
public Dictionary<DateTime, double> GetDF(DateTime start, int nYears)
{
List<DateTime> schedule = DateConvention.CreateDateSchedule(start, nYears, basis, ENUM_PERIODTYPE.MONTHS, addPeriod);
Dictionary<DateTime, double> curve = new Dictionary<DateTime, double>();
schedule.ForEach(it => curve.Add(it, GetDF(it)));
return curve;
}
// get simply-compounded forward rate for a given start date
publicdouble GetFWD(DateTime start)
{
return forwardCurve.GetMarketRate(ENUM_MARKETDATATYPE.FORWARDRATE, start, dayCountFactor);
}
// get dictionary consisting of date and simply-compounded forward rate for a date schedule
public Dictionary<DateTime, double> GetFWD(DateTime start, int nYears)
{
List<DateTime> schedule = DateConvention.CreateDateSchedule(start, nYears, basis, ENUM_PERIODTYPE.MONTHS, addPeriod);
Dictionary<DateTime, double> curve = new Dictionary<DateTime, double>();
schedule.ForEach(it => curve.Add(it, GetFWD(it)));
return curve;
}
// use interpolated spot discount factor curve for calculating
// simply-compounded forward rates for all required maturities (basis)
// note : maturity element of the forward curve stores the information
// on when the 3-month period starts for a given forward rate element
privatevoid createForwardCurve()
{
forwardCurve = new CurveData(interpolator);
int n = discountCurve.Count();
DateTime maturity;
double dt = 0.0;
double fdf = 0.0;
double f = 0.0;
//
for (int i = 0; i < n; i++)
{
if (i == 0)
{
// first forward rate is the first spot rate
maturity = discountCurve[i].MaturityDate;
fdf = discountCurve[i].Rate;
dt = dayCountFactor(settlementDate, maturity);
f = ((1 / fdf) - 1) / dt;
forwardCurve.AddCurveDataElement(settlementDate, f, ENUM_MARKETDATATYPE.FORWARDRATE);
}
else
{
// other forward rates are calculated recursively
// from previous spot discount factors
maturity = discountCurve[i].MaturityDate;
DateTime previousMaturity = discountCurve[i - 1].MaturityDate;
fdf = discountCurve[i].Rate / discountCurve[i - 1].Rate;
dt = dayCountFactor(previousMaturity, maturity);
f = ((1 / fdf) - 1) / dt;
forwardCurve.AddCurveDataElement(previousMaturity, f, ENUM_MARKETDATATYPE.FORWARDRATE);
}
}
}
// use continuously compounded spot rate curve for interpolating
// continuously compounded spot rates for all required maturities
// and convert these spot rates back to spot discount factors
privatevoid createDiscountCurve()
{
discountCurve = new CurveData(interpolator);
DateTime finalCurveDate = spotCurve.ElementAt(spotCurve.Count() - 1).MaturityDate;
DateTime t;
int counter = 0;
double dt = 0.0;
double r = 0.0;
double df = 0.0;
//
do
{
counter++;
t = addPeriod(settlementDate, ENUM_PERIODTYPE.MONTHS, basis * counter);
dt = dayCountFactor(settlementDate, t);
r = spotCurve.GetMarketRate(ENUM_MARKETDATATYPE.SPOTRATE, t, dayCountFactor);
df = Math.Exp(-r * dt);
discountCurve.AddCurveDataElement(t, df, ENUM_MARKETDATATYPE.DF);
} while (t < finalCurveDate);
}
// create continuously compounded spot rate curve
// from bootstrapped discount factors
privatevoid createSpotCurve()
{
spotCurve = new CurveData(interpolator);
double t = 0.0;
double r = 0.0;
int n = bootstrapCurve.Count();
for (int i = 0; i < n; i++)
{
t = dayCountFactor(settlementDate, bootstrapCurve.ElementAt(i).MaturityDate);
r = -Math.Log(bootstrapCurve.ElementAt(i).Rate) / t;
spotCurve.AddCurveDataElement(bootstrapCurve.ElementAt(i).MaturityDate, r, ENUM_MARKETDATATYPE.SPOTRATE);
}
}
// use bootstrap algorithm to create spot discount factors
// from all selected curve data elements
privatevoid bootstrapDiscountFactors()
{
bootstrapCurve = new CurveData(interpolator);
double dt = 0.0;
double r = 0.0;
double df = 0.0;
double Q = 0.0;
int n = curveDataSelection.Count();
//
for (int i = 0; i < n; i++)
{
if (curveDataSelection[i].InstrumentType == ENUM_MARKETDATATYPE.CASH)
{
dt = dayCountFactor(settlementDate, curveDataSelection[i].MaturityDate);
r = curveDataSelection[i].Rate;
df = 1 / (1 + r * dt);
bootstrapCurve.AddCurveDataElement(curveDataSelection[i].MaturityDate, df, ENUM_MARKETDATATYPE.DF);
}
if ((curveDataSelection[i].InstrumentType == ENUM_MARKETDATATYPE.FRA) |
(curveDataSelection[i].InstrumentType == ENUM_MARKETDATATYPE.FUTURE))
{
dt = dayCountFactor(curveDataSelection[i - 1].MaturityDate, curveDataSelection[i].MaturityDate);
r = curveDataSelection[i].Rate;
df = bootstrapCurve.ElementAt(i - 1).Rate / (1 + r * dt);
bootstrapCurve.AddCurveDataElement(curveDataSelection[i].MaturityDate, df, ENUM_MARKETDATATYPE.DF);
//
if ((curveDataSelection[i + 1].InstrumentType == ENUM_MARKETDATATYPE.SWAP))
Q += bootstrapCurve.ElementAt(i).Rate * dayCountFactor(settlementDate, curveDataSelection[i].MaturityDate);
}
//
if (curveDataSelection[i].InstrumentType == ENUM_MARKETDATATYPE.SWAP)
{
r = curveDataSelection[i].Rate;
dt = dayCountFactor(bootstrapCurve.ElementAt(i - 1).MaturityDate, curveDataSelection[i].MaturityDate);
df = (1 - r * Q) / (r * dt + 1);
bootstrapCurve.AddCurveDataElement(curveDataSelection[i].MaturityDate, df, ENUM_MARKETDATATYPE.DF);
Q += (df * dt);
}
}
}
// select rate instruments to be used from a given set of curve data elements
privatevoid selectCurveData()
{
curveDataSelection = new CurveData(interpolator);
int counter = 0;
double rate = 0.0;
DateTime maturityDate;
//
// select cash securities
for (int i = 1; i <= nCash; i++)
{
counter++;
maturityDate = addPeriod(settlementDate, ENUM_PERIODTYPE.MONTHS, basis * counter);
// check if cash rate for required maturity exists
if (!marketData.ElementLookup(ENUM_MARKETDATATYPE.CASH, maturityDate))
thrownew Exception("USDLiborZeroCurve error : required cash securities are missing");
rate = marketData.GetMarketRate(ENUM_MARKETDATATYPE.CASH, maturityDate, dayCountFactor);
curveDataSelection.AddCurveDataElement(maturityDate, rate, ENUM_MARKETDATATYPE.CASH);
}
// select fra or futures contracts
if (marketData.ElementLookup(ENUM_MARKETDATATYPE.FRA))
{
for (int i = 1; i <= nFuturesOrFRAs; i++)
{
if (i > 1) counter++;
maturityDate = addPeriod(settlementDate, ENUM_PERIODTYPE.MONTHS, basis * counter);
// check if fra rate for required maturity exists
if (!marketData.ElementLookup(ENUM_MARKETDATATYPE.FRA, maturityDate))
thrownew Exception("USDLiborZeroCurve error : required FRA contracts are missing");
rate = marketData.GetMarketRate(ENUM_MARKETDATATYPE.FRA, maturityDate, dayCountFactor);
curveDataSelection.AddCurveDataElement(addPeriod(maturityDate, ENUM_PERIODTYPE.MONTHS, basis), rate, ENUM_MARKETDATATYPE.FRA);
}
}
else
{
for (int i = 1; i <= nFuturesOrFRAs; i++)
{
if (i > 1) counter++;
maturityDate = addPeriod(settlementDate, ENUM_PERIODTYPE.MONTHS, basis * counter);
// check if implied futures rate for required maturity exists
if (!marketData.ElementLookup(ENUM_MARKETDATATYPE.FUTURE, maturityDate))
thrownew Exception("USDLiborZeroCurve error : required futures contracts are missing");
rate = marketData.GetMarketRate(ENUM_MARKETDATATYPE.FUTURE, maturityDate, dayCountFactor);
//
// forward rate = futures rate - convexity adjustment
if (adjustmentForConvexity)
{
double t1 = dayCountFactor(settlementDate, maturityDate);
double t2 = t1 + (basis / 12.0);
rate -= convexityAdjustment(rateVolatility, t1, t2);
}
curveDataSelection.AddCurveDataElement(addPeriod(maturityDate, ENUM_PERIODTYPE.MONTHS, basis), rate, ENUM_MARKETDATATYPE.FUTURE);
}
}
// select swap contracts
DateTime lastSwapYear = marketData[marketData.Count() - 1].MaturityDate;
DateTime lastFRAOrFutureYear = curveDataSelection[curveDataSelection.Count() - 1].MaturityDate;
int nSwaps = (lastSwapYear.Year - lastFRAOrFutureYear.Year);
for (int i = 1; i <= nSwaps; i++)
{
counter++;
maturityDate = addPeriod(settlementDate, ENUM_PERIODTYPE.YEARS, i + 1);
// check if swap rate for required maturity exists
if (!marketData.ElementLookup(ENUM_MARKETDATATYPE.SWAP, maturityDate))
thrownew Exception("USDLiborZeroCurve error : required swap contracts are missing");
rate = marketData.GetMarketRate(ENUM_MARKETDATATYPE.SWAP, maturityDate, dayCountFactor);
curveDataSelection.AddCurveDataElement(maturityDate, rate, ENUM_MARKETDATATYPE.SWAP);
}
}
// rough diagnostics : check for completely non-existing market data
// requirement : all three rate categories (cash, FRA/futures, swaps)
// must be provided by the client in order to create the curves
privatevoid checkMarketData()
{
// cash securities
if (!marketData.ElementLookup(ENUM_MARKETDATATYPE.CASH))
thrownew Exception("LiborZeroCurve error : cash securities are required to build the curve");
//
// fra/futures contracts
if ((!marketData.ElementLookup(ENUM_MARKETDATATYPE.FUTURE)) && (!marketData.ElementLookup(ENUM_MARKETDATATYPE.FRA)))
thrownew Exception("LiborZeroCurve error : FRA or futures contracts are required to build the curve");
//
// swap contracts
if (!marketData.ElementLookup(ENUM_MARKETDATATYPE.SWAP))
thrownew Exception("LiborZeroCurve error : swap contracts are required to build the curve");
}
}
//
//
// container class for holding multiple curve data elements
publicclassCurveData : IEnumerable<CurveDataElement>
{
private List<CurveDataElement> curveDataElements;
private Interpolator interpolator;
//
public CurveData(Interpolator interpolator)
{
this.interpolator = interpolator;
curveDataElements = new List<CurveDataElement>();
}
publicvoid AddCurveDataElement(DateTime maturity, double rate, ENUM_MARKETDATATYPE instrumentType)
{
curveDataElements.Add(new CurveDataElement(maturity, rate, instrumentType));
}
publicvoid AddCurveDataElement(CurveDataElement curveDataElement)
{
curveDataElements.Add(curveDataElement);
}
// implementation for generic IEnumerable
public IEnumerator<CurveDataElement> GetEnumerator()
{
foreach (CurveDataElement curveDataElement in curveDataElements)
{
yieldreturn curveDataElement;
}
}
// implementation for non-generic IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
// read-only indexer
public CurveDataElement this[int index]
{
get
{
return curveDataElements[index];
}
}
publicdouble GetMarketRate(ENUM_MARKETDATATYPE instrumentType, DateTime maturity, DayCountFactor dayCountFactor)
{
// filter required market data elements by instrument type
List<CurveDataElement> group = curveDataElements.Where(it => it.InstrumentType == instrumentType).ToList<CurveDataElement>();
//
// extract maturity and rate into dictionary object
Dictionary<DateTime, double> data = new Dictionary<DateTime, double>();
group.ForEach(it => data.Add(it.MaturityDate, it.Rate));
//
// get market rate for a given date by using given interpolation delegate method
return interpolator(dayCountFactor, data, maturity);
}
// check if elements with specific instrument type and maturity exists
publicbool ElementLookup(ENUM_MARKETDATATYPE instrumentType, DateTime maturity)
{
// first, filter required market data elements
List<CurveDataElement> group = curveDataElements.Where(it => it.InstrumentType == instrumentType).ToList<CurveDataElement>();
//
// then, check if maturity lies between min and max maturity of filtered group
bool hasElement = ((maturity >= group.Min(it => it.MaturityDate)) && (maturity <= group.Max(it => it.MaturityDate)));
return hasElement;
}
// check if elements with only specific instrument type exists
publicbool ElementLookup(ENUM_MARKETDATATYPE instrumentType)
{
int elements = curveDataElements.Count(it => it.InstrumentType == instrumentType);
bool hasElements = false;
if (elements > 0) hasElements = true;
return hasElements;
}
}
//
//
// class holding information on one curve data element
publicclassCurveDataElement
{
private DateTime maturityDate;
privatedouble rate;
private ENUM_MARKETDATATYPE rateType;
//
public DateTime MaturityDate { get { returnthis.maturityDate; } }
publicdouble Rate { get { returnthis.rate; } }
public ENUM_MARKETDATATYPE InstrumentType { get { returnthis.rateType; } }
//
public CurveDataElement(DateTime maturity, double rate, ENUM_MARKETDATATYPE rateType)
{
this.maturityDate = maturity;
this.rate = rate;
this.rateType = rateType;
}
}
//
//
// static library class for handling date-related convention calculations
publicstaticclassDateConvention
{
// calculate time difference between two dates by using ACT/360 convention
publicstaticdouble ACT360(DateTime start, DateTime end)
{
return (end - start).TotalDays / 360;
}
// create a list of scheduled dates for a given basis and date convention
publicstatic List<DateTime> CreateDateSchedule(DateTime start, int nYears, int basis,
ENUM_PERIODTYPE periodType, AddPeriod addPeriod)
{
List<DateTime> schedule = new List<DateTime>();
int nPeriods = nYears * (12 / basis);
for (int i = 1; i <= nPeriods; i++)
{
schedule.Add(addPeriod(start, periodType, (basis * i)));
}
return schedule;
}
// add period into a given date by using modified following convention
publicstatic DateTime AddPeriod_ModifiedFollowing(DateTime start, ENUM_PERIODTYPE periodType, int period)
{
DateTime dt = new DateTime();
//
switch (periodType)
{
case ENUM_PERIODTYPE.MONTHS:
dt = start.AddMonths(period);
break;
case ENUM_PERIODTYPE.YEARS:
dt = start.AddYears(period);
break;
}
//
switch (dt.DayOfWeek)
{
case DayOfWeek.Saturday:
dt = dt.AddDays(2.0);
break;
case DayOfWeek.Sunday:
dt = dt.AddDays(1.0);
break;
}
return dt;
}
// calculate value for convexity adjustment for a given time period
publicstaticdouble SimpleConvexityApproximation(double rateVolatility, double start, double end)
{
return 0.5 * (rateVolatility * rateVolatility * start * end);
}
}
//
//
// static library class for storing interpolation methods to be used by delegates
publicstaticclassInterpolation
{
publicstaticdouble LinearInterpolation(DayCountFactor dayCountFactor,
Dictionary<DateTime, double> data, DateTime 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) thrownew Exception("Interpolation error : lower bound violation");
if (key > data.ElementAt(data.Count - 1).Key) thrownew Exception("Interpolation error : upper bound violation");
}
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 = dayCountFactor(data.ElementAt(i).Key, data.ElementAt(i + 1).Key);
double w = dayCountFactor(data.ElementAt(i).Key, key) / t;
value = data.ElementAt(i).Value * (1 - w) + data.ElementAt(i + 1).Value * w;
break;
}
}
}
returnvalue;
}
}
}


EXCEL INTERFACING

For interfacing the previous C# program with Excel, carefully follow all the instructions given in this blog post. Add another Class module (a new cs-file) into this project and copyPaste the following program into this file. This is the program (CreateUSDLiborCurves), which will be called from our Excel worksheet.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ExcelDna.Integration;

namespace CurveAddin
{
publicstaticclassCurveAddin
{
publicstaticvoid CreateUSDLiborCurves()
{
try
{
// build market data from Excel worksheet
MarketDataBuilder builder = new ExcelBuilder();
CurveData marketData = builder.Build();
DateTime settlementDate = ((ExcelBuilder)builder).SettlementDate;
//
// construct USD Libor curve object
// HARD-CODED parameters :
// interpolation method, date conventions, number of contracts for cash (n=1) and futures (n=3)
ICurve curve = new USDLiborZeroCurve(marketData, Interpolation.LinearInterpolation,
DateConvention.AddPeriod_ModifiedFollowing, DateConvention.ACT360, settlementDate, 1, 3);
curve.Create();
//
// read discount factor and forward rate data into 2d-array
int rows = curve.DiscountCurve.Count();
int cols = 3;
dynamic[,] data = newdynamic[rows, cols];
for (int i = 0; i < rows; i++)
{
data[i, 0] = curve.DiscountCurve[i].MaturityDate.ToOADate();
data[i, 1] = curve.DiscountCurve[i].Rate;
data[i, 2] = curve.ForwardCurve[i].Rate;
}
//
// print curve data into Excel worksheet
(new ExcelPrinter(data)).Print();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
}
}



EXCEL WORKSHEET SETTINGS

Prepare the following market data (Flavell's book and Excel workbook on chapter three) and named ranges (marked with yellow color) into Excel worksheet. The program will take settlement date and market data range (date, rate, rate type) as input parameters. Note, that some of the parameters needed to create the curves have been hard-coded in the program (interpolation method, date conventions, number of contracts for cash and futures). However, it should be fairly straightforward to include all these parameters to be read directly from Excel worksheet. Finally, insert a form control button into worksheet and assign a macro for it (CreateUSDLiborCurves).
















Finally, a couple of notes concerning the market data setting. When creating Curve object, Client has to provide number of cash securities and number futures/FRA contracts as input parameters in constructor. Example program has been hard-coded to use one cash security and three futures contracts and it will hereby use Libor swap contracts starting on the year two. Now, for setting market data for this program, there are some general rules. First, the latest maturity for cash securities has to be later than the first maturity for futures/FRA contracts. Also, the last future/FRA maturity has to be later than the first swap contract maturity date minus one year.

TEST RUN

After I press button in my Excel, I will get the following results (columns H to J) from the program. The range consists of dates (quarterly up to 8.2.2038), discount factors and simply-compounded Libor forward rates for USD. The first result row should be interpreted as follows : discount factor (0.99220) gives factor for discounting three-month cash flow. Simply-compounded Libor forward rate (3.1450 %) gives three-month forward rate for a period, which is starting on 6.2.2008 and ending 6.5.2008. Similarly, Libor forward rate (2.7837 %) gives three-month forward rate for a period, which is starting on 6.5.2008 and ending 6.8.2008.



















After this, using generated curves (discount factors and forward rates) is straightforward. As an example, I have calculated PV for 2-year cap on 3-month USD Libor. After creating date schedule for this security, getting forward rates and discount factors can be requested by using familiar Excel worksheet functions.


















AFTERTHOUGHTS

The procedure of creating discount factors and Libor forward rates programmatically in C#, has been fully opened in this blog post. Source market data and creation procedures are following examples taken from the book written by Richard Flavell. With the tools presented in this blog, one should also be able to interface this program with Excel, if so desired.

I would like to thank Govert Van Drimmelen again for his amazing Excel-DNA, what I am always using for interfacing my C# program with Excel. For learning more things about Excel-DNA, check out its homepage. Getting more information and examples with your problems, the main source is Excel-DNA google group. Remember also, that Excel-DNA is an open-source project, and we (the happy users) can invest its future development by making a donation.

Finally, Thanks for spending your precious time in here and reading my blog.

-Mike Juniperhill

Simulating Discount Factor and Forward Rate Curves using Monte Carlo in C#

$
0
0
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.

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

Implementing statistics gatherer design by using Boost library in C++

$
0
0
As a result of Monte Carlo simulation process, we get a lot of simulated values. After this, we usually want to calculate a set of desired statistics from those simulated values. A great statistics gatherer design has been thoroughly presented in The book by Mark Joshi. However, just for the curious and for the sake of trying to create something original (while respecting traditions), I wanted to implement my own statistics gatherer design, in which several different statistical measures could be calculated independently by using the same set of processed data. Moreover, the number of such calculation objects and their implementations for algorithmic details would be fully configurable. Finally, I wanted to avoid any direct coupling between data (object, which holds the source data) and algorithms (objects, which are performing the actual statistical calculations).

Why bother?


In the following example program below, a sample of discounted payoffs from Monte Carlo process has been hard-coded and two statistical measures are then calculated and printed out.

void MonteCarloProcess()
{
// process has been producing the following discounted payoffs
// for this sample : average = 10.4881, standard error = 1.58502
double discountedPayoffs[] = { 18.5705, 3.31508, 0.0, 3.64361, 0.0, 0.0,
47.2563, 10.6534, 85.5559, 0.0, 30.2363, 0.0, 17.8391, 2.15396, 0.0,
66.587, 0.0, 9.19303, 0.0, 0.0, 24.2946, 29.6556, 0.0, 0.0, 0.0, 65.926,
0.0, 14.0329, 1.43328, 0.0, 0.0, 0.0, 1.37088, 0.0, 2.49095, 21.4755,
36.5432, 0.0, 16.8795, 0.0, 0.0, 0.0, 19.8927, 11.3132, 37.3946, 10.2666,
26.1932, 0.0, 0.551356, 29.7159, 0.0, 31.5357, 0.0, 0.0, 4.64357, 4.45376,
21.6076, 12.693, 16.0065, 0.0, 0.0, 0.0, 0.0, 25.9665, 18.7169, 2.55222,
25.6431, 8.5027, 0.0, 0.0, 29.8704, 0.0, 22.7266, 22.8463, 0.0, 0.0, 0.0,
0.0, 4.90832, 13.2787, 0.0, 0.0, 9.77076, 24.5855, 12.6094, 0.0, 0.0, 1.92343,
5.66301, 0.0, 0.0, 13.6968, 0.0, 0.0, 35.2159, 0.0, 8.44648, 7.21964, 0.0, 19.2949 };
//
// dump array data into vector
std::vector<double> data(discountedPayoffs, std::end(discountedPayoffs));
//
// calculate and print mean and standard error
double mean = AlgorithmLibrary::Mean(data);
double standardError = AlgorithmLibrary::StandardError(data);
std::cout << mean << std::endl;
std::cout << standardError << std::endl;
}

There is nothing fundamentally wrong in this example, but a great deal of it could be done in a bit different manner. The scheme presented in this example offers a chance to explore some modern tools for implementing flexible and configurable C++ programs.

Chinese walls


Personally, I would prefer to separate data (discounted payoffs) and algorithms (mean, standard error) completely. Ultimately, I would like to have a design, in which a process (monte carlo) is generating results and adding those results into a separate container. Whenever this process is finished, it will send a reference of this container for several entities, which will calculate all required statistics independently.

There are many ways to implement this kind of a scheme, but I have been heavily influenced by delegates stuff I have learned from C#. Needless to say, the equivalent mechanism for C# delegate in C++ is a function pointer. However, instead of raw function pointer I will use Boost.Function and Boost.Bind libraries.

My new design proposal will have the following components :
  • AlgorithmLibrary for calculating different statistical measures. The header file for this contains collection of methods for calculating different statistical measures.
  • ResultContainer class for storing processed results and function pointers (boost function) which are sending processed results for further calculations.
  • StatisticsElement class for storing a value of statistical measure and function pointer (boost function) for an algorithm, which can be used for calculating required statistical measure.

 

Strangers in the night


In the first stage, StatisticsElement object will be created. This object will host a single statistical measure and a pointer to an algorithm (boost function) for calculating this specific measure. By giving required algorithm as a pointer (boost function), the object is not tied with any hard-coded algorithm. In the case there would be a need for another type for algorithm implementation for calculating required statistical measure, the scheme is flexible enough to be adjusted. In the constructor of this class, we are giving this pointer to an algorithm (boost function). Moreover, this object will be indirectly connected with ResultContainer object with a function pointer (boost function). Function pointer (boost function) will be created and binded (boost bind) with the specific method (process) of StatisticsElement object.

In the second stage, ResultContainer object will be created and all previously created function pointers (boost function, boost bind) for processing calculation results will be added into ResultContainer. This object is ultimately being shared with a process. Process will generate its results (double) and these results will be added into container object. When a process is finished, container object method (sendResults) will be called. The call for this method will trigger a loop, which will iterate through a vector of function pointers (boost function) and sending a reference for a result vector to all connected StatisticsElement objects.

Finally, the client program (in this example : main) will request the calculated statistical measures directly from all StatisticsElement objects. It should be stressed, that these two objects described above, do not have any knowledge about each other at any point. ResultContainer is just storing updates from a process and finally sending results "to somewhere" when the processing is over. StatisticsElement objects are processing their own calculation procedures as soon as they will receive results "from somewhere". It should also be noted, that this design actually implements observer pattern, where ResultContainer object is Observable and StatisticalElement objects are Observers.


Proposal


// ResultContainer.h
#pragma once
#include <vector>
#include <boost\function.hpp>
//
// class for storing processed results and function pointers
// which are sending results for further processing
classResultContainer
{
private:
// container for storing processed results
std::vector<double> results;
// container for storing function pointers
std::vector<boost::function<void
(const std::vector<double>&)>> resultSenders;
public:
// method for adding one processed result into container
void addResult(double result);
// method for adding one function pointer into container
void addResultSender(boost::function<void
(const std::vector<double>&)> resultSender);
// method for sending all processed results
void sendResults() const;
};
//
//
//
// StatisticsElement.h
#pragma once
#include <vector>
#include <boost\function.hpp>
//
// class for storing a value of statistical measure and
// function pointer for an algorithm which can be used
// for calculating required statistical measure
classStatisticsElement
{
private:
// statistical measure
double statisticsValue;
// function pointer to an algorithm which calculates
// required statistical measure
boost::function<double(const std::vector<double>&)> algorithm;
public:
// parameter constructor
StatisticsElement(boost::function<double
(const std::vector<double>&)> algorithm);
// method for processing data in order to
// calculate required statistical measure
void process(const std::vector<double>& data);
// method (overloaded operator) for accessing
// calculated statistical measure
doubleoperator()() const;
};
//
//
//
// AlgorithmLibrary.h
#pragma once
#include <vector>
#include <numeric>
//
// algorithms library for calculating statistical measures
namespace AlgorithmLibrary
{
// calculate arithmetic average
double Mean(const std::vector<double>& data)
{
return std::accumulate(data.begin(), data.end(), 0.0)
/ data.size();
}
// calculate standard error estimate
double StandardError(const std::vector<double>& data)
{
double mean = AlgorithmLibrary::Mean(data);
double squaredSum = std::inner_product(data.begin(),
data.end(), data.begin(), 0.0);
return std::sqrt(squaredSum / data.size() - mean * mean)
/ std::sqrt(data.size());
}
}
//
//
//
// ResultContainer.cpp
#include "ResultContainer.h"
//
void ResultContainer::addResult(double result)
{
results.push_back(result);
}
void ResultContainer::addResultSender(boost::function<void
(const std::vector<double>&)> resultSender)
{
resultSenders.push_back(resultSender);
}
void ResultContainer::sendResults() const
{
std::vector<boost::function<void
(const std::vector<double>&)>>::const_iterator it;
for(it = resultSenders.begin(); it != resultSenders.end(); it++)
{
(*it)(results);
}
}
//
//
//
// StatisticsElement.cpp
#include "StatisticsElement.h"
//
StatisticsElement::StatisticsElement(boost::function<double
(const std::vector<double>&)> algorithm)
: statisticsValue(0.0), algorithm(algorithm)
{
//
}
void StatisticsElement::process(const std::vector<double>& data)
{
if(algorithm != NULL) statisticsValue = algorithm(data);
}
double StatisticsElement::operator()() const
{
return statisticsValue;
}
//
//
//
// MainProgram.cpp
#include <boost\bind.hpp>
#include <iostream>
#include "AlgorithmLibrary.h"
#include "ResultContainer.h"
#include "StatisticsElement.h"
//
void MonteCarloProcess(ResultContainer& resultContainer)
{
// process has been producing the following discounted payoffs
// for this sample : average = 10.4881, standard error = 1.58502
double discountedPayoffs[] = { 18.5705, 3.31508, 0.0, 3.64361, 0.0, 0.0,
47.2563, 10.6534, 85.5559, 0.0, 30.2363, 0.0, 17.8391, 2.15396, 0.0,
66.587, 0.0, 9.19303, 0.0, 0.0, 24.2946, 29.6556, 0.0, 0.0, 0.0, 65.926,
0.0, 14.0329, 1.43328, 0.0, 0.0, 0.0, 1.37088, 0.0, 2.49095, 21.4755,
36.5432, 0.0, 16.8795, 0.0, 0.0, 0.0, 19.8927, 11.3132, 37.3946, 10.2666,
26.1932, 0.0, 0.551356, 29.7159, 0.0, 31.5357, 0.0, 0.0, 4.64357, 4.45376,
21.6076, 12.693, 16.0065, 0.0, 0.0, 0.0, 0.0, 25.9665, 18.7169, 2.55222,
25.6431, 8.5027, 0.0, 0.0, 29.8704, 0.0, 22.7266, 22.8463, 0.0, 0.0, 0.0,
0.0, 4.90832, 13.2787, 0.0, 0.0, 9.77076, 24.5855, 12.6094, 0.0, 0.0, 1.92343,
5.66301, 0.0, 0.0, 13.6968, 0.0, 0.0, 35.2159, 0.0, 8.44648, 7.21964, 0.0, 19.2949 };
//
// dump array data into vector
std::vector<double> data(discountedPayoffs, std::end(discountedPayoffs));
// create vector iterator, loop through data and add items into result container object
std::vector<double>::const_iterator it;
for(it = data.begin(); it != data.end(); it++)
{
resultContainer.addResult(*it);
}
// trigger result processing for all 'connected' statistical element objects
resultContainer.sendResults();
}
//
int main()
{
// create : function pointer to mean algorithm, statistics element
// and function pointer to process method of this statistics element
boost::function<double(const std::vector<double>&)> meanAlgorithm =
AlgorithmLibrary::Mean;
StatisticsElement mean(meanAlgorithm);
boost::function<void(const std::vector<double>&)> resultSenderForMean =
boost::bind(&StatisticsElement::process, &mean, _1);
//
// create : function pointer to standard error algorithm, statistics element and
// function pointer to process method of this statistics element
boost::function<double(const std::vector<double>&)> standardErrorAlgorithm =
AlgorithmLibrary::StandardError;
StatisticsElement standardError(standardErrorAlgorithm);
boost::function<void(const std::vector<double>&)> resultSenderForStandardError =
boost::bind(&StatisticsElement::process, &standardError, _1);
//
// create : result container and add previously created function
// pointers (senders) into container
ResultContainer resultContainer;
resultContainer.addResultSender(resultSenderForMean);
resultContainer.addResultSender(resultSenderForStandardError);
//
// run (hard-coded) monte carlo process
MonteCarloProcess(resultContainer);
//
// print results from the both statistics elements
std::cout << mean() << std::endl;
std::cout << standardError() << std::endl;
return 0;
}


Help


Concerning the actual installation and configuration of Boost libraries with compiler, there is a great tutorial by eefelix available in youtube. For using Boost libraries, there is a document available, written by Dimitri Reiswich. Personally, I would like to present my appreciations for these persons for their great contribution.

Thanks for reading my blog. Have a pleasant wait for the Christmas.
-Mike

QuantLib : Builder Class for PiecewiseYieldCurve

$
0
0
Selection of high-quality benchmark securities and bootstrapping of valuation curve is the bread and butter in valuing financial transactions. In one of my blog, I opened up one possible framework for this procedure. Needless to say, that program was still far away from being really easy-to-use and state-of-the-art implementation. For this reason, I wanted to take a look at some external analytics libraries I have been getting introduced last year.

This time, I wanted to share some fruits of woodshedding QuantLib way to build up piecewise term structure. As I was initially tackling through the example files found in QL downloadable package and the other relevant examples found with Google, there was one obvious issue : with all those rates, quotes and handles involved, a lot of administrative code writing needs to be done first, in order to get any piecewise curve up and running. Since we need valuation curves anyway for everything we do with QL, there should be a manageable way to handle this issue.


One template to rule them all



For the reason mentioned above, I came up with an idea of auxiliary piecewise curve builder class. The purpose of this class would be simple : it could give a client a chance to assemble piecewise curve by adding arbitrary amount of different types of quotes (deposit, FRA or swap) and finally a client could request handle for assembled curve. The resulting curve handle could then be directly used in other QL programs. Effectively, this class would be wrapping some of the required administrative code work away from a client.


Walkthrough


As an example of the usage of this PiecewiseCurveBuilder template class, let us go through some parts of the example program. In order to keep this example as simple as possible, we will operate only with one rate for each category (deposit, FRA, swap). First, we create stand-alone quotes from market data. Memory for a SimpleQuote (inherits from Quote base class) will be reserved by using Boost shared pointer :

  boost::shared_ptr<Quote> usd_quote_deposit_3M(new SimpleQuote(0.006127));
  boost::shared_ptr<Quote> usd_quote_fra_3M6M(new SimpleQuote(0.008253));
  boost::shared_ptr<Quote> usd_quote_swap_2Y(new SimpleQuote(0.011459));

Next, we create curve builder class instance for assembling USD Libor swap curve :

  Date settlementDate = UnitedKingdom().advance(tradeDate, 2, Days);
  PiecewiseCurveBuilder<USDLibor> USDCurveBuilder(settlementDate, UnitedKingdom(), Annual, Thirty360());

After this, we add quotes into USD curve builder :

  USDCurveBuilder.AddDeposit(usd_quote_deposit_3M, 3 * Months);
  USDCurveBuilder.AddFRA(usd_quote_fra_3M6M, 3 * Months, 3 * Months);
  USDCurveBuilder.AddSwap(usd_quote_swap_2Y, 2 * Years);

Finally, we request relinkable curve handle from USD curve builder :

  RelinkableHandle<YieldTermStructure> curveHandle = USDCurveBuilder.GetCurveHandle();
  DoSomethingWithCurveHandle(curveHandle);

Requested RelinkableHandle (inherits from Handle base class) can then be directly used in other QL programs. One should be aware, that all the changes we made in stand-alone quotes (SimpleQuotes) will have an effect on the curve. For an example, we could modify the existing quotes by shocking their rate up by 100 bps :

  boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_deposit_3M)->setValue(usd_quote_deposit_3M->value() + 0.01);
  boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_fra_3M6M)->setValue(usd_quote_fra_3M6M->value() + 0.01);
  boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_2Y)->setValue(usd_quote_swap_2Y->value() + 0.01);
  DoSomethingWithCurveHandle(curveHandle);

Additional add methods for different quote types are enabling even easier way to assemble a curve. Instead of giving a rate wrapped inside Quote object, it can be given directly into curve builder. First, we create curve builder for CHF Libor swap curve :

  settlementDate = UnitedKingdom().advance(tradeDate, 2, Days);
  PiecewiseCurveBuilder<CHFLibor> CHFCurveBuilder(settlementDate, UnitedKingdom(), Annual, Thirty360());

Next, we add market rates directly into CHF curve builder :

  CHFCurveBuilder.AddDeposit(-0.006896, 6 * Months);
  CHFCurveBuilder.AddFRA(-0.007103, 6 * Months, 6 * Months);
  CHFCurveBuilder.AddSwap(-0.0068355, 2 * Years);

Finally, we request relinkable curve handle from CHF curve builder :

  curveHandle = CHFCurveBuilder.GetCurveHandle();
  DoSomethingWithCurveHandle(curveHandle);

This last option would be suitable in situations, where a client has no need for any auxiliary rate
updating.

The complete example program has been presented below. The program will first create updateable USD Libor swap curve, print all the rates, modify quotes and re-prints the rates. After this, the program will create updateable CHF Libor swap curve and prints a set of discount factors. Finally, the program will create non-updateable CHF Libor swap curve by using another set of add methods and prints a set of discount factors.

Have a great start for the year 2016. Thanks for reading my blog.
-Mike


The program


// PiecewiseCurveBuilder.h
#pragma once
#include <ql/quantlib.hpp>
usingnamespace QuantLib;
//
template<classT>
classPiecewiseCurveBuilder
{
private:
Date settlementDate;
Calendar calendar;
Frequency fixedLegFrequency;
DayCounter dayCounter;
DayCounter fixedLegDayCounter;
BusinessDayConvention businessDayConvention;
std::vector<boost::shared_ptr<RateHelper>> rateHelpers;
boost::shared_ptr<YieldTermStructure> yieldTermStructure;
public:
PiecewiseCurveBuilder(Date settlementDate, Calendar calendar,
Frequency fixedLegFrequency, DayCounter fixedLegDayCounter);
void AddDeposit(boost::shared_ptr<Quote>& quote, Period periodLength);
void AddDeposit(Rate quote, Period periodLength);
void AddFRA(boost::shared_ptr<Quote>& quote, Period periodLengthToStart, Period periodLength);
void AddFRA(Rate quote, Period periodLengthToStart, Period periodLength);
void AddSwap(boost::shared_ptr<Quote>& quote, Period periodLength);
void AddSwap(Rate quote, Period periodLength);
RelinkableHandle<YieldTermStructure> GetCurveHandle();
};
//
//
//
// PiecewiseCurveBuilder.cpp
#pragma once
#include "PiecewiseCurveBuilder.h"
//
template<classT>
PiecewiseCurveBuilder<T>::PiecewiseCurveBuilder(Date settlementDate,
Calendar calendar, Frequency fixedLegFrequency, DayCounter fixedLegDayCounter)
{
this->settlementDate = settlementDate;
this->calendar = calendar;
this->fixedLegFrequency = fixedLegFrequency;
this->fixedLegDayCounter = fixedLegDayCounter;
boost::shared_ptr<IborIndex> index(new T(3 * Months));
dayCounter = index->dayCounter();
businessDayConvention = index->businessDayConvention();
}
template<classT>
void PiecewiseCurveBuilder<T>::AddDeposit(boost::shared_ptr<Quote>& quote, Period periodLength)
{
// using third DepositRateHelper constructor
boost::shared_ptr<RateHelper> rateHelper(new DepositRateHelper(
Handle<Quote>(quote), boost::shared_ptr<IborIndex>(new T(periodLength))));
rateHelpers.push_back(rateHelper);
}
template<classT>
void PiecewiseCurveBuilder<T>::AddDeposit(Rate quote, Period periodLength)
{
// using second DepositRateHelper constructor
boost::shared_ptr<RateHelper> rateHelper(new DepositRateHelper(
quote, boost::shared_ptr<IborIndex>(new T(periodLength))));
rateHelpers.push_back(rateHelper);
}
template<classT>
void PiecewiseCurveBuilder<T>::AddFRA(boost::shared_ptr<Quote>& quote,
Period periodLengthToStart, Period periodLength)
{
// using seventh FraRateHelper constructor
boost::shared_ptr<RateHelper> rateHelper(new FraRateHelper(
Handle<Quote>(quote), periodLengthToStart,
boost::shared_ptr<IborIndex>(new T(periodLength))));
rateHelpers.push_back(rateHelper);
}
template<classT>
void PiecewiseCurveBuilder<T>::AddFRA(Rate quote,
Period periodLengthToStart, Period periodLength)
{
// using third FraRateHelper constructor
boost::shared_ptr<RateHelper> rateHelper(new FraRateHelper(
quote, periodLengthToStart,
boost::shared_ptr<IborIndex>(new T(periodLength))));
rateHelpers.push_back(rateHelper);
}
template<classT>
void PiecewiseCurveBuilder<T>::AddSwap(boost::shared_ptr<Quote>& quote,
Period periodLength)
{
// using fifth SwapRateHelper constructor
boost::shared_ptr<IborIndex> index(new T(periodLength));
boost::shared_ptr<RateHelper> rateHelper(new SwapRateHelper(
Handle<Quote>(quote), periodLength, calendar, fixedLegFrequency,
businessDayConvention, fixedLegDayCounter, index));
rateHelpers.push_back(rateHelper);
}
template<classT>
void PiecewiseCurveBuilder<T>::AddSwap(Rate quote,
Period periodLength)
{
// using fourth SwapRateHelper constructor
boost::shared_ptr<IborIndex> index(new T(periodLength));
boost::shared_ptr<RateHelper> rateHelper(new SwapRateHelper(
quote, periodLength, calendar, fixedLegFrequency,
businessDayConvention, fixedLegDayCounter, index));
rateHelpers.push_back(rateHelper);
}
template<classT>
RelinkableHandle<YieldTermStructure> PiecewiseCurveBuilder<T>::GetCurveHandle()
{
if(yieldTermStructure == NULL)
{
yieldTermStructure = boost::shared_ptr<YieldTermStructure>(
new PiecewiseYieldCurve<Discount, LogLinear>(
settlementDate, rateHelpers, dayCounter));
}
return RelinkableHandle<YieldTermStructure>(yieldTermStructure);
}
//
//
//
// Tester.cpp
#include "PiecewiseCurveBuilder.cpp"
//
// prototype : template method for calculating a fair swap rate
template<typename T> Rate GetSwapRate(Period swapMaturity, Date settlementDate, Period floatingLegTenor,
Handle<YieldTermStructure>& curveHandle, Calendar calendar, DayCounter fixedLegDayCount, Period fixedLegTenor);
// prototype : hard-coded printer for USD rates
void PrintUSDRates(Date settlementDate, Handle<YieldTermStructure>& curveHandle);
// prototype : hard-coded printer for CHF discount factors
void PrintCHFDiscountFactors(Date settlementDate, Handle<YieldTermStructure>& curveHandle);
//
int main()
{
// create trade date
Date tradeDate(5, January, 2016);
Settings::instance().evaluationDate() = tradeDate;
//
// create relinkable handle for curve
RelinkableHandle<YieldTermStructure> curveHandle;
//
// create stand-alone quotes from USD market data
boost::shared_ptr<Quote> usd_quote_deposit_1W(new SimpleQuote(0.0038975));
boost::shared_ptr<Quote> usd_quote_deposit_1M(new SimpleQuote(0.004295));
boost::shared_ptr<Quote> usd_quote_deposit_2M(new SimpleQuote(0.005149));
boost::shared_ptr<Quote> usd_quote_deposit_3M(new SimpleQuote(0.006127));
boost::shared_ptr<Quote> usd_quote_fra_3M6M(new SimpleQuote(0.008253));
boost::shared_ptr<Quote> usd_quote_fra_6M9M(new SimpleQuote(0.009065));
boost::shared_ptr<Quote> usd_quote_fra_9M12M(new SimpleQuote(0.01059));
boost::shared_ptr<Quote> usd_quote_swap_2Y(new SimpleQuote(0.011459));
boost::shared_ptr<Quote> usd_quote_swap_3Y(new SimpleQuote(0.013745));
boost::shared_ptr<Quote> usd_quote_swap_4Y(new SimpleQuote(0.015475));
boost::shared_ptr<Quote> usd_quote_swap_5Y(new SimpleQuote(0.016895));
boost::shared_ptr<Quote> usd_quote_swap_6Y(new SimpleQuote(0.01813));
boost::shared_ptr<Quote> usd_quote_swap_7Y(new SimpleQuote(0.019195));
boost::shared_ptr<Quote> usd_quote_swap_8Y(new SimpleQuote(0.020115));
boost::shared_ptr<Quote> usd_quote_swap_9Y(new SimpleQuote(0.020905));
boost::shared_ptr<Quote> usd_quote_swap_10Y(new SimpleQuote(0.021595));
boost::shared_ptr<Quote> usd_quote_swap_11Y(new SimpleQuote(0.0222));
boost::shared_ptr<Quote> usd_quote_swap_12Y(new SimpleQuote(0.022766));
boost::shared_ptr<Quote> usd_quote_swap_15Y(new SimpleQuote(0.0239675));
boost::shared_ptr<Quote> usd_quote_swap_20Y(new SimpleQuote(0.025105));
boost::shared_ptr<Quote> usd_quote_swap_25Y(new SimpleQuote(0.025675));
boost::shared_ptr<Quote> usd_quote_swap_30Y(new SimpleQuote(0.026015));
boost::shared_ptr<Quote> usd_quote_swap_40Y(new SimpleQuote(0.026205));
boost::shared_ptr<Quote> usd_quote_swap_50Y(new SimpleQuote(0.026045));
//
// create curve builder for USD Libor swap curve
Date settlementDate = UnitedKingdom().advance(tradeDate, 2, Days);
PiecewiseCurveBuilder<USDLibor> USDCurveBuilder(settlementDate,
UnitedKingdom(), Annual, Thirty360());
//
// add quotes (reference to shared pointer) into USD curve builder
USDCurveBuilder.AddDeposit(usd_quote_deposit_1W, 1 * Weeks);
USDCurveBuilder.AddDeposit(usd_quote_deposit_1M, 1 * Months);
USDCurveBuilder.AddDeposit(usd_quote_deposit_2M, 2 * Months);
USDCurveBuilder.AddDeposit(usd_quote_deposit_3M, 3 * Months);
USDCurveBuilder.AddFRA(usd_quote_fra_3M6M, 3 * Months, 3 * Months);
USDCurveBuilder.AddFRA(usd_quote_fra_6M9M, 6 * Months, 3 * Months);
USDCurveBuilder.AddFRA(usd_quote_fra_9M12M, 9 * Months, 3 * Months);
USDCurveBuilder.AddSwap(usd_quote_swap_2Y, 2 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_3Y, 3 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_4Y, 4 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_5Y, 5 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_6Y, 6 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_7Y, 7 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_8Y, 8 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_9Y, 9 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_10Y, 10 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_11Y, 11 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_12Y, 12 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_15Y, 15 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_20Y, 20 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_25Y, 25 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_30Y, 30 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_40Y, 40 * Years);
USDCurveBuilder.AddSwap(usd_quote_swap_50Y, 50 * Years);
//
// get relinkable curve handle from USD curve builder and print rates
curveHandle = USDCurveBuilder.GetCurveHandle();
PrintUSDRates(settlementDate, curveHandle);
//
// modify existing USD quotes by shocking rates up by 100 bps
Real bump = 0.01;
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_deposit_1W)->setValue(usd_quote_deposit_1W->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_deposit_1M)->setValue(usd_quote_deposit_1M->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_deposit_2M)->setValue(usd_quote_deposit_2M->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_deposit_3M)->setValue(usd_quote_deposit_3M->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_fra_3M6M)->setValue(usd_quote_fra_3M6M->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_fra_6M9M)->setValue(usd_quote_fra_6M9M->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_fra_9M12M)->setValue(usd_quote_fra_9M12M->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_2Y)->setValue(usd_quote_swap_2Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_3Y)->setValue(usd_quote_swap_3Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_4Y)->setValue(usd_quote_swap_4Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_5Y)->setValue(usd_quote_swap_5Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_6Y)->setValue(usd_quote_swap_6Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_7Y)->setValue(usd_quote_swap_7Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_8Y)->setValue(usd_quote_swap_8Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_9Y)->setValue(usd_quote_swap_9Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_10Y)->setValue(usd_quote_swap_10Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_11Y)->setValue(usd_quote_swap_11Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_12Y)->setValue(usd_quote_swap_12Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_15Y)->setValue(usd_quote_swap_15Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_20Y)->setValue(usd_quote_swap_20Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_25Y)->setValue(usd_quote_swap_25Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_30Y)->setValue(usd_quote_swap_30Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_40Y)->setValue(usd_quote_swap_40Y->value() + bump);
boost::dynamic_pointer_cast<SimpleQuote>(usd_quote_swap_50Y)->setValue(usd_quote_swap_50Y->value() + bump);
//
// re-print USD rates
PrintUSDRates(settlementDate, curveHandle);
//
//
//
// create stand-alone quotes from CHF market data
boost::shared_ptr<Quote> chf_quote_deposit_1W(new SimpleQuote(-0.00827));
boost::shared_ptr<Quote> chf_quote_deposit_1M(new SimpleQuote(-0.00798));
boost::shared_ptr<Quote> chf_quote_deposit_2M(new SimpleQuote(-0.00785));
boost::shared_ptr<Quote> chf_quote_deposit_3M(new SimpleQuote(-0.00756));
boost::shared_ptr<Quote> chf_quote_deposit_6M(new SimpleQuote(-0.006896));
boost::shared_ptr<Quote> chf_quote_fra_6M12M(new SimpleQuote(-0.007103));
boost::shared_ptr<Quote> chf_quote_swap_2Y(new SimpleQuote(-0.0068355));
boost::shared_ptr<Quote> chf_quote_swap_3Y(new SimpleQuote(-0.006125));
boost::shared_ptr<Quote> chf_quote_swap_4Y(new SimpleQuote(-0.0050195));
boost::shared_ptr<Quote> chf_quote_swap_5Y(new SimpleQuote(-0.003725));
boost::shared_ptr<Quote> chf_quote_swap_6Y(new SimpleQuote(-0.002425));
boost::shared_ptr<Quote> chf_quote_swap_7Y(new SimpleQuote(-0.0011885));
boost::shared_ptr<Quote> chf_quote_swap_8Y(new SimpleQuote(-0.000094));
boost::shared_ptr<Quote> chf_quote_swap_9Y(new SimpleQuote(0.0008755));
boost::shared_ptr<Quote> chf_quote_swap_10Y(new SimpleQuote(0.0016365));
boost::shared_ptr<Quote> chf_quote_swap_12Y(new SimpleQuote(0.003));
boost::shared_ptr<Quote> chf_quote_swap_15Y(new SimpleQuote(0.004525));
boost::shared_ptr<Quote> chf_quote_swap_20Y(new SimpleQuote(0.0063));
boost::shared_ptr<Quote> chf_quote_swap_25Y(new SimpleQuote(0.00735));
boost::shared_ptr<Quote> chf_quote_swap_30Y(new SimpleQuote(0.007825));
//
// create curve builder for CHF Libor swap curve
settlementDate = UnitedKingdom().advance(tradeDate, 2, Days);
PiecewiseCurveBuilder<CHFLibor> CHFCurveBuilder(settlementDate,
UnitedKingdom(), Annual, Thirty360());
//
// add quotes (reference to shared pointer) into CHF curve builder
CHFCurveBuilder.AddDeposit(chf_quote_deposit_1W, 1 * Weeks);
CHFCurveBuilder.AddDeposit(chf_quote_deposit_1M, 1 * Months);
CHFCurveBuilder.AddDeposit(chf_quote_deposit_2M, 2 * Months);
CHFCurveBuilder.AddDeposit(chf_quote_deposit_3M, 3 * Months);
CHFCurveBuilder.AddDeposit(chf_quote_deposit_6M, 6 * Months);
CHFCurveBuilder.AddFRA(chf_quote_fra_6M12M, 6 * Months, 6 * Months);
CHFCurveBuilder.AddSwap(chf_quote_swap_2Y, 2 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_3Y, 3 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_4Y, 4 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_5Y, 5 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_6Y, 6 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_7Y, 7 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_8Y, 8 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_9Y, 9 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_10Y, 10 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_12Y, 12 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_15Y, 15 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_20Y, 20 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_25Y, 25 * Years);
CHFCurveBuilder.AddSwap(chf_quote_swap_30Y, 30 * Years);
//
// get relinkable curve handle from CHF curve builder and print discount factors
curveHandle = CHFCurveBuilder.GetCurveHandle();
PrintCHFDiscountFactors(settlementDate, curveHandle);
//
//
//
// create another curve builder for CHF Libor swap curve
settlementDate = UnitedKingdom().advance(tradeDate, 2, Days);
PiecewiseCurveBuilder<CHFLibor> CHFCurveBuilder2(settlementDate,
UnitedKingdom(), Annual, Thirty360());
//
// add market rates directly into CHF curve builder
CHFCurveBuilder2.AddDeposit(-0.00827, 1 * Weeks);
CHFCurveBuilder2.AddDeposit(-0.00798, 1 * Months);
CHFCurveBuilder2.AddDeposit(-0.00785, 2 * Months);
CHFCurveBuilder2.AddDeposit(-0.00756, 3 * Months);
CHFCurveBuilder2.AddDeposit(-0.006896, 6 * Months);
CHFCurveBuilder2.AddFRA(-0.007103, 6 * Months, 6 * Months);
CHFCurveBuilder2.AddSwap(-0.0068355, 2 * Years);
CHFCurveBuilder2.AddSwap(-0.006125, 3 * Years);
CHFCurveBuilder2.AddSwap(-0.0050195, 4 * Years);
CHFCurveBuilder2.AddSwap(-0.003725, 5 * Years);
CHFCurveBuilder2.AddSwap(-0.002425, 6 * Years);
CHFCurveBuilder2.AddSwap(-0.0011885, 7 * Years);
CHFCurveBuilder2.AddSwap(-0.000094, 8 * Years);
CHFCurveBuilder2.AddSwap(0.0008755, 9 * Years);
CHFCurveBuilder2.AddSwap(0.0016365, 10 * Years);
CHFCurveBuilder2.AddSwap(0.003, 12 * Years);
CHFCurveBuilder2.AddSwap(0.004525, 15 * Years);
CHFCurveBuilder2.AddSwap(0.0063, 20 * Years);
CHFCurveBuilder2.AddSwap(0.00735, 25 * Years);
CHFCurveBuilder2.AddSwap(0.007825, 30 * Years);
//
// get relinkable curve handle from CHF curve builder and re-print discount factors
curveHandle = CHFCurveBuilder2.GetCurveHandle();
PrintCHFDiscountFactors(settlementDate, curveHandle);
return 0;
}
//
template<typename T> Rate GetSwapRate(Period swapMaturity, Date settlementDate, Period floatingLegTenor,
Handle<YieldTermStructure>& curveHandle, Calendar calendar, DayCounter fixedLegDayCount, Period fixedLegTenor)
{
// using quantlib factory method for building vanilla swap
boost::shared_ptr<IborIndex> index(new T(floatingLegTenor, curveHandle));
VanillaSwap swap = MakeVanillaSwap(swapMaturity, index)
.withEffectiveDate(settlementDate)
.withFixedLegCalendar(calendar)
.withFixedLegConvention(index->businessDayConvention())
.withFixedLegDayCount(fixedLegDayCount)
.withFixedLegTenor(fixedLegTenor)
.withFloatingLegCalendar(calendar);
return swap.fairRate();
}
//
void PrintUSDRates(Date settlementDate, Handle<YieldTermStructure>& curveHandle)
{
Calendar calendar = UnitedKingdom();
std::cout << std::endl;
// print USD deposit rates
std::cout << curveHandle->zeroRate(calendar.adjust(settlementDate + 1 * Weeks),
Actual360(), Simple).rate() << std::endl;
std::cout << curveHandle->zeroRate(calendar.adjust(settlementDate + 1 * Months),
Actual360(), Simple).rate() << std::endl;
std::cout << curveHandle->zeroRate(calendar.adjust(settlementDate + 2 * Months),
Actual360(), Simple).rate() << std::endl;
std::cout << curveHandle->zeroRate(calendar.adjust(settlementDate + 3 * Months),
Actual360(), Simple).rate() << std::endl;
// print USD forward rates
std::cout << curveHandle->forwardRate(calendar.adjust(settlementDate + 3 * Months),
calendar.adjust(settlementDate + 6 * Months), Actual360(), Simple).rate() << std::endl;
std::cout << curveHandle->forwardRate(calendar.adjust(settlementDate + 6 * Months),
calendar.adjust(settlementDate + 9 * Months), Actual360(), Simple).rate() << std::endl;
std::cout << curveHandle->forwardRate(calendar.adjust(settlementDate + 9 * Months),
calendar.adjust(settlementDate + 12 * Months), Actual360(), Simple).rate() << std::endl;
// print USD swap rates
std::cout << GetSwapRate<USDLibor>(2 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(3 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(4 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(5 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(6 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(7 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(8 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(9 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(10 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(11 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(12 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(15 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(20 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(25 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(30 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(40 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
std::cout << GetSwapRate<USDLibor>(50 * Years, settlementDate, 3 * Months,
curveHandle, calendar, Thirty360(), 1 * Years) << std::endl;
}
//
void PrintCHFDiscountFactors(Date settlementDate, Handle<YieldTermStructure>& curveHandle)
{
// print CHF discount factors for every 6 months up to 30 years
std::cout << std::endl;
for(unsignedint i = 1; i != 61; i++)
{
std::cout << curveHandle->discount(UnitedKingdom()
.adjust(settlementDate + (6 * i) * Months)) << std::endl;
}
}

QuantLib : Simulating HW1F paths using PathGenerator

$
0
0
Monte Carlo is bread and butter for so many purposes. Calculating payoffs for complex path-dependent products or simulating future exposures for calculating CVA are two excellent examples. The big question is always how to do this efficiently. Designing, implementing and setting up any non-trivial in-house tool to do the job is everything but not a simple afternoon exercise with a cup of coffee and Excel. Fortunately, QuantLib is offering pretty impressive tools for simulating stochastic paths. This time, I wanted to share the results of my woodshedding with QL PathGenerator class. 


Parallel lives


In order to really appreciate the tools offered by QL, let us see the results first. Some simulated paths using Hull-White One-Factor model are shown in the picture below.






























If one really want to start from the scratch, there are a lot of things to do in order to produce these paths on a flexible manner and handling all the complexities of the task at the same time. Thanks for QL, those days are finally over.


Legoland

 

Setting up desired Stochastic Process and Gaussian Sequence Generator are two main components needed in order to get this thing up and running.

Along with required process parameters (reversion speed and rate volatility), HullWhiteProcess needs Handle to YieldTermStructure object, such as PiecewiseYieldCurve, as an input.

// create Hull-White one-factor stochastic process
Real reversionSpeed =0.75;
Real rateVolatility =0.015;
boost::shared_ptr<StochasticProcess1D> HW1F(
new HullWhiteProcess(curveHandle, reversionSpeed, rateVolatility));

    For this example, I have used my own PiecewiseCurveBuilder template class in order to make curve assembling a bit more easier. It should be noted, that the menu of one-dimensional stochastic processes in QL is covering pretty much all standard processes one needs for different asset classes.

      Gaussian Sequence Generator (GSG) is assembled by using the following three classes : uniform random generator (MersenneTwisterUniformRng),  distributional transformer (CLGaussianRng) and RandomSequenceGenerator.

      // type definition for complex declaration
      typedef RandomSequenceGenerator<CLGaussianRng<MersenneTwisterUniformRng>> GSG;
      //
      // create mersenne twister uniform random generator
      unsignedlong seed =28749;
      MersenneTwisterUniformRng generator(seed);
      //
      // create gaussian generator by using central limit transformation method
      CLGaussianRng<MersenneTwisterUniformRng> gaussianGenerator(generator);
      //
      // define maturity, number of steps per path and create gaussian sequence generator
      Time maturity =5.0;
      Size nSteps =1250;
      GSG gaussianSequenceGenerator(nSteps, gaussianGenerator);
      //
      // create path generator using Hull-White process and gaussian sequence generator
      PathGenerator<GSG> pathGenerator(HW1F, maturity, nSteps, gaussianSequenceGenerator, false);

      Finally, PathGenerator object is created by feeding desired process and generator objects in constructor method, along with the other required parameters (maturity, number of steps). After this, PathGenerator object is ready for producing stochastic paths for its client.

      The program

       

      Example program will first create relinkable handle to PiecewiseYieldCurve object. Remember to include required files into your project from here. After this, the program creates HW1F process object and Gaussian Sequence Generator object, which are feeded into PathGenerator object. Finally, the program creates 20 stochastic paths, which are saved into Matrix object and ultimately being printed into text file for further analysis (Excel chart).

      #include "PiecewiseCurveBuilder.cpp"
      #include <fstream>
      #include <string>
      //
      // type definition for complex declaration
      typedef RandomSequenceGenerator<CLGaussianRng<MersenneTwisterUniformRng>> GSG;
      //
      // function prototypes
      RelinkableHandle<YieldTermStructure> CreateCurveHandle(Date settlementDate);
      voidPrintMatrix(const Matrix& matrix, std::string filePathName);
      //
      intmain()
      {
      // request handle for piecewise USD Libor curve
      Date tradeDate(22, January, 2016);
      Settings::instance().evaluationDate() = tradeDate;
      Date settlementDate = UnitedKingdom().advance(tradeDate, 2, Days);
      RelinkableHandle<YieldTermStructure> curveHandle = CreateCurveHandle(settlementDate);
      //
      // create Hull-White one-factor stochastic process
      Real reversionSpeed =0.75;
      Real rateVolatility =0.015;
      boost::shared_ptr<StochasticProcess1D> HW1F(
      new HullWhiteProcess(curveHandle, reversionSpeed, rateVolatility));
      //
      // create mersenne twister uniform random generator
      unsignedlong seed =28749;
      MersenneTwisterUniformRng generator(seed);
      //
      // create gaussian generator by using central limit transformation method
      CLGaussianRng<MersenneTwisterUniformRng> gaussianGenerator(generator);
      //
      // define maturity, number of steps per path and create gaussian sequence generator
      Time maturity =5.0;
      Size nSteps =1250;
      GSG gaussianSequenceGenerator(nSteps, gaussianGenerator);
      //
      // create path generator using Hull-White process and gaussian sequence generator
      PathGenerator<GSG> pathGenerator(HW1F, maturity, nSteps, gaussianSequenceGenerator, false);
      //
      // create matrix container for 20 generated paths
      Size nColumns =20;
      Matrix paths(nSteps +1, nColumns);
      for(unsignedint i =0; i != paths.columns(); i++)
      {
      // request a new stochastic path from path generator
      QuantLib::Sample<Path> path = pathGenerator.next();
      //
      // save generated path into container
      for(unsignedint j =0; j != path.value.length(); j++)
      {
      paths[j][i] = path.value.at(j);
      }
      }
      // finally, print matrix content into text file
      PrintMatrix(paths, "C:\\temp\\HW1F.txt");
      return0;
      }
      //
      voidPrintMatrix(const Matrix& matrix, std::string filePathName)
      {
      // open text file for input, loop through matrix rows
      std::ofstream file(filePathName);
      for(unsignedint i =0; i != matrix.rows(); i++)
      {
      // concatenate column values into string separated by semicolon
      std::string stream;
      for(unsignedint j =0; j != matrix.columns(); j++)
      {
      stream += (std::to_string(matrix[i][j]) +";");
      }
      // print string into text file
      file << stream << std::endl;
      }
      // close text file
      file.close();
      }
      //
      RelinkableHandle<YieldTermStructure> CreateCurveHandle(Date settlementDate)
      {
      // create curve builder for piecewise USD Libor swap curve
      PiecewiseCurveBuilder<USDLibor> USDCurveBuilder(settlementDate,
      UnitedKingdom(), Annual, Thirty360());
      //
      // add quotes directly into curve builder
      USDCurveBuilder.AddDeposit(0.0038975, 1* Weeks);
      USDCurveBuilder.AddDeposit(0.004295, 1* Months);
      USDCurveBuilder.AddDeposit(0.005149, 2* Months);
      USDCurveBuilder.AddDeposit(0.006127, 3* Months);
      USDCurveBuilder.AddFRA(0.008253, 3* Months, 3* Months);
      USDCurveBuilder.AddFRA(0.009065, 6* Months, 3* Months);
      USDCurveBuilder.AddFRA(0.01059, 9* Months, 3* Months);
      USDCurveBuilder.AddSwap(0.011459, 2* Years);
      USDCurveBuilder.AddSwap(0.013745, 3* Years);
      USDCurveBuilder.AddSwap(0.015475, 4* Years);
      USDCurveBuilder.AddSwap(0.016895, 5* Years);
      USDCurveBuilder.AddSwap(0.01813, 6* Years);
      USDCurveBuilder.AddSwap(0.019195, 7* Years);
      USDCurveBuilder.AddSwap(0.020115, 8* Years);
      USDCurveBuilder.AddSwap(0.020905, 9* Years);
      USDCurveBuilder.AddSwap(0.021595, 10* Years);
      USDCurveBuilder.AddSwap(0.0222, 11* Years);
      USDCurveBuilder.AddSwap(0.022766, 12* Years);
      USDCurveBuilder.AddSwap(0.0239675, 15* Years);
      USDCurveBuilder.AddSwap(0.025105, 20* Years);
      USDCurveBuilder.AddSwap(0.025675, 25* Years);
      USDCurveBuilder.AddSwap(0.026015, 30* Years);
      USDCurveBuilder.AddSwap(0.026205, 40* Years);
      USDCurveBuilder.AddSwap(0.026045, 50* Years);
      //
      // return relinkable curve handle
      return USDCurveBuilder.GetCurveHandle();
      }

      Thanks for reading my blog.

      -Mike
      Viewing all 83 articles
      Browse latest View live