Sunday, April 26, 2015

Builder Pattern for C# Basket Default Swap Pricer

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
    public static class Main
    {
        private static Stopwatch timer;
        private static dynamic Excel;
        private static ComponentBuilder builder;
        private static Tuple<Copula, CreditMatrix, DiscountCurve> components;
        private static List<BasketCDS> engines;
        private static List<Statistics> statistics;
        //
        public static void 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
    public abstract class ComponentBuilder
    {
        public abstract Copula CreateCopula();
        public abstract CreditMatrix CreateCreditMatrix();
        public abstract DiscountCurve CreateDiscountCurve();
        public abstract Tuple<Copula, CreditMatrix, DiscountCurve> GetComponents();
    }
    //
    // implementation for builder reading parameters from Excel
    public class ExcelBuilder : ComponentBuilder
    {
        dynamic Excel = null;
        private Copula copula = null;
        private CreditMatrix credit = null;
        private DiscountCurve curve = null;
        //
        public ExcelBuilder()
        {
            Excel = ExcelDnaUtil.Application;
        }
        public override Copula CreateCopula()
        {
            // create covariance matrix
            dynamic matrix = Excel.Range["_COVARIANCE"].Value2;
            int rows = matrix.GetUpperBound(0);
            int columns = matrix.GetUpperBound(1);
            double[,] covariance = new double[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;
        }
        public override CreditMatrix CreateCreditMatrix()
        {
            // create cds matrix
            dynamic matrix = Excel.Range["_CDS"].Value2;
            int rows = matrix.GetUpperBound(0);
            int columns = matrix.GetUpperBound(1);
            double[,] cds = new double[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;
        }
        public override DiscountCurve CreateDiscountCurve()
        {
            // create zero-coupon curve
            dynamic matrix = Excel.Range["_ZERO"].Value2;
            int rows = matrix.GetUpperBound(0);
            int columns = matrix.GetUpperBound(1);
            double[,] zeroCouponCurve = new double[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:
                    throw new 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:
                    throw new Exception("discounting algorithm not defined");
            }
            DiscountCurve curve = new DiscountCurve(zeroCouponCurve, interpolation, discounting);
            return curve;
        }
        public override 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;
        }
    }
    //
    // *******************************************************************************
    public class BasketCDS
    {
        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
        private int k; // kth-to-default
        private int m; // number of reference assets
        private int n; // number of simulations
        private int 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;
        }
        public void 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 = new double[1, m];
                for (int j = 0; j < m; j++)
                {
                    set[0, j] = randomArray[i, j];
                }
                //
                // calculate default times for reference name set
                calculateDefaultTimes(set);
            }
        }
        private void calculateDefaultTimes(double[,] arr)
        {
            // convert uniform random numbers into default times
            int cols = arr.GetLength(1);
            double[,] defaultTimes = new double[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);
        }
        private void 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);
        }
        private int getNumberOfDefaults(double[,] arr)
        {
            int nDefaults = 0;
            for (int i = 0; i < arr.GetLength(1); i++)
            {
                if (arr[0, i] > 0.0) nDefaults++;
            }
            return nDefaults;
        }
        private double 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;
        }
        private double 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
    public abstract class Copula
    {
        // 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
        public abstract double[,] Create(int n, int seed);
    }
    //
    // NAG G05 COPULAS WRAPPER
    public class NAGCopula : Copula
    {
        private double[,] covariance;
        private int genID;
        private int subGenID;
        private int mode;
        private int df;
        private int m;
        private int errorNumber;
        private double[] r;
        private double[,] 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 = new double[m * (m + 1) + (df > 0 ? 2 : 1)];
        }
        public override double[,] Create(int n, int seed)
        {
            result = new double[n, m];
            G05.G05State g05State = new G05.G05State(genID, subGenID, new int[1] { seed }, out errorNumber);
            if (errorNumber != 0) throw new 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) throw new Exception("G05 method failure");
            return result;
        }
    }
    //
    // *******************************************************************************
    public class CreditMatrix
    {
        private Func<double, double> df;
        private double recovery;
        private double[,] CDSSpreads;
        private double[,] survivalMatrix;
        private double[,] hazardMatrix;
        private double[,] 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
        public double[,] HazardMatrix { get { return this.hazardMatrix; } }
        public double[,] CumulativeHazardMatrix { get { return this.cumulativeHazardMatrix; } }
        public double Recovery { get { return this.recovery; } }
        //
        private void createSurvivalMatrix()
        {
            // bootstrap matrix of survival probabilities from given CDS data
            int rows = CDSSpreads.GetUpperBound(0) + 2;
            int cols = CDSSpreads.GetUpperBound(1) + 1;
            survivalMatrix = new double[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;
                    }
                }
            }
        }
        private void createHazardMatrices()
        {
            // convert matrix of survival probabilities into two hazard rate matrices
            int rows = survivalMatrix.GetUpperBound(0);
            int cols = survivalMatrix.GetUpperBound(1) + 1;
            hazardMatrix = new double[rows, cols];
            cumulativeHazardMatrix = new double[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
    public delegate double InterpolationAlgorithm(double t, ref double[,] curve);
    public delegate double DiscountAlgorithm(double t, double r);
    //
    public class DiscountCurve
    {
        // specific algorithms for interpolation and discounting
        private InterpolationAlgorithm interpolationMethod;
        private DiscountAlgorithm discountMethod;
        private double[,] curve;
        //
        public DiscountCurve(double[,] curve,
            InterpolationAlgorithm interpolationMethod, DiscountAlgorithm discountMethod)
        {
            this.curve = curve;
            this.interpolationMethod = interpolationMethod;
            this.discountMethod = discountMethod;
        }
        public double GetDF(double t)
        {
            // get discount factor from discount curve
            return discountMethod(t, interpolation(t));
        }
        private double interpolation(double t)
        {
            // get interpolation from discount curve
            return interpolationMethod(t, ref this.curve);
        }
    }
    //
    // *******************************************************************************
    // collection of methods for different types of mathematical operations
    public static class MathTools
    {
        public static double LinearInterpolation(double t, ref double[,] 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;
        }
        public static double ContinuousDiscountFactor(double t, double r)
        {
            return Math.Exp(-r * t);
        }
    }
    //
    // *******************************************************************************
    // collection of methods for different types of matrix operations
    public static class MatrixTools
    {
        public static double[,] CorrelationToCovariance(double[,] corr, double[] stdev)
        {
            // transform correlation matrix to co-variance matrix
            double[,] cov = new double[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;
        }
        public static void RowMatrix_sort(ref double[,] 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;
                    }
                }
            }
        }
        public static void RowMatrix_removeZeroValues(ref double[,] 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 = new double[1, temp.Count];
                for (int i = 0; i < temp.Count; i++)
                {
                    arr[0, i] = temp[i];
                }
            }
            else
            {
                arr = null;
            }
        }
    }
    //
    // *******************************************************************************
    public delegate void PremiumLegUpdate(double v);
    public delegate void DefaultLegUpdate(double v);
    //
    public class Statistics
    {
        // data structures for storing leg values
        private List<double> premiumLeg;
        private List<double> defaultLeg;
        private string ID;
        //
        public Statistics(string ID)
        {
            this.ID = ID;
            premiumLeg = new List<double>();
            defaultLeg = new List<double>();
        }
        public void UpdatePremiumLeg(double v)
        {
            premiumLeg.Add(v);
        }
        public void UpdateDefaultLeg(double v)
        {
            defaultLeg.Add(v);
        }
        public void PrettyPrint()
        {
            // hard-coded 'report' output
            Console.WriteLine("{0} : {1} bps", ID, Math.Round(Spread() * 10000, 2));
        }
        public double 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.

Monday, April 20, 2015

C# Basket Default Swap Pricer

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
    public delegate void PremiumLegUpdate(double v);
    public delegate void DefaultLegUpdate(double v);
    //
    // delegate methods for interpolation and discounting
    public delegate double InterpolationAlgorithm(double t, ref double[,] curve);
    public delegate double DiscountAlgorithm(double t, double r);
    //
    //
    // *************************************************************
    // CLIENT PROGRAM
    class Program
    {
        static void 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
        static double[,] createCovarianceMatrix()
        {
            double[,] covariance = new double[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
        static double[,] createDiscountCurve()
        {
            double[,] curve = new double[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
        static double[,] createCDSCurve()
        {
            double[,] cds = new double[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;
        }
    }
    //
    //
    // *************************************************************
    public class BasketCDS
    {
        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
        private int k; // kth-to-default
        private int m; // number of reference assets
        private int n; // number of simulations
        private int 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;
        }
        public void Process()
        {
            // request correlated random numbers from copula model
            int[] seed = new int[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 = new double[1, m];
                for (int j = 0; j < m; j++)
                {
                    set[0, j] = randomArray[i, j];
                }
                //
                // calculate default times for reference name set
                calculateDefaultTimes(set);
            }
        }
        private void calculateDefaultTimes(double[,] arr)
        {
            // convert uniform random numbers into default times
            int cols = arr.GetLength(1);
            double[,] defaultTimes = new double[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);
        }
        private void 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);
        }
        private int getNumberOfDefaults(double[,] arr)
        {
            int nDefaults = 0;
            for (int i = 0; i < arr.GetLength(1); i++)
            {
                if (arr[0, i] > 0.0) nDefaults++;
            }
            return nDefaults;
        }
        private double 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;
        }
        private double 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;
        }
    }
    //
    //
    // *************************************************************
    public class CreditCurve
    {
        private Func<double, double> df;
        private double recovery;
        private double[,] CDSSpreads;
        private double[,] survivalMatrix;
        private double[,] hazardMatrix;
        private double[,] 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
        public double[,] HazardMatrix { get { return this.hazardMatrix; } }
        public double[,] CumulativeHazardMatrix { get { return this.cumulativeHazardMatrix; } }
        public double Recovery { get { return this.recovery; } }
        //
        private void createSurvivalMatrix()
        {
            // bootstrap matrix of survival probabilities from given CDS data
            int rows = CDSSpreads.GetUpperBound(0) + 2;
            int cols = CDSSpreads.GetUpperBound(1) + 1;
            survivalMatrix = new double[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;
                    }
                }
            }
        }
        private void createHazardMatrices()
        {
            // convert matrix of survival probabilities into two hazard rate matrices
            int rows = survivalMatrix.GetUpperBound(0);
            int cols = survivalMatrix.GetUpperBound(1) + 1;
            hazardMatrix = new double[rows, cols];
            cumulativeHazardMatrix = new double[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]);
                }
            }
        }
    }
    //
    //
    // *************************************************************
    public class DiscountCurve
    {
        // specific algorithms for interpolation and discounting
        private InterpolationAlgorithm interpolationMethod;
        private DiscountAlgorithm discountMethod;
        private double[,] curve;
        //
        public DiscountCurve(double[,] curve,
            InterpolationAlgorithm interpolationMethod, DiscountAlgorithm discountMethod)
        {
            this.curve = curve;
            this.interpolationMethod = interpolationMethod;
            this.discountMethod = discountMethod;
        }
        public double GetDF(double t)
        {
            // get discount factor from discount curve
            return discountMethod(t, interpolation(t));
        }
        private double interpolation(double t)
        {
            // get interpolation from discount curve
            return interpolationMethod(t, ref this.curve);
        }
    }
    //
    //
    // *************************************************************
    // collection of methods for different types of mathematical operations
    public static class MathTools
    {
        public static double LinearInterpolation(double t, ref double[,] 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;
        }
        public static double ContinuousDiscountFactor(double t, double r)
        {
            return Math.Exp(-r * t);
        }
    }
    //
    //
    // *************************************************************
    // collection of methods for different types of matrix operations
    public static class MatrixTools
    {
        public static double[,] CorrelationToCovariance(double[,] corr, double[] stdev)
        {
            // transform correlation matrix to co-variance matrix
            double[,] cov = new double[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;
        }
        public static void RowMatrix_sort(ref double[,] 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;
                    }
                }
            }
        }
        public static void RowMatrix_removeZeroValues(ref double[,] 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 = new double[1, temp.Count];
                for (int i = 0; i < temp.Count; i++)
                {
                    arr[0, i] = temp[i];
                }
            }
            else
            {
                arr = null;
            }
        }
    }
    //
    //
    // *************************************************************
    // NAG G05 COPULAS WRAPPER
    public class NAGCopula
    {
        private double[,] covariance;
        private int genID;
        private int subGenID;
        private int mode;
        private int df;
        private int m;
        private int errorNumber;
        private double[] r;
        private double[,] 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 = new double[m * (m + 1) + (df > 0 ? 2 : 1)];
        }
        public double[,] Create(int n, int[] seed)
        {
            result = new double[n, m];
            //
            G05.G05State g05State = new G05.G05State(genID, subGenID, seed, out errorNumber);
            if (errorNumber != 0) throw new 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) throw new Exception("G05 method failure");
            return result;
        }
    }
    //
    //
    // *************************************************************
    public class Statistics
    {
        // data structures for storing leg values
        private List<double> premiumLeg;
        private List<double> defaultLeg;
        private string ID;
        //
        public Statistics(string ID)
        {
            this.ID = ID;
            premiumLeg = new List<double>();
            defaultLeg = new List<double>();
        }
        public void UpdatePremiumLeg(double v)
        {
            premiumLeg.Add(v);
        }
        public void UpdateDefaultLeg(double v)
        {
            defaultLeg.Add(v);
        }
        public void PrettyPrint()
        {
            // hard-coded 'report' output
            Console.WriteLine("{0} : {1} bps", ID, Math.Round(Spread() * 10000, 2));
        }
        public double Spread()
        {
            return defaultLeg.Average() / premiumLeg.Average();
        }
    }
}