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.
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 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.
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.
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).
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).
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.
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.