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();
        }
    }
}

5 comments:

  1. Hi, I Need to code with R language..this One You provider is VBA?

    ReplyDelete
  2. Hi Mikael, I am trying to understand the code for the premium leg. in the line v += (df(t) * dt * p);
    As per the code t can be a fraction like 2.4, 5.5 etc. How do you get the discount factor for 2.4 years. Am i wrong in saying that otherwise.
    Thanks

    ReplyDelete
    Replies
    1. In the Main program, we have two delegate methods for interpolation and discounting : InterpolationAlgorithm and DiscountAlgorithm. First, we create DiscountCurve object (zero), which takes these two delegates and hard-coded term structure as input parameters in its constructor. At this point, DiscountCurve object has the knowledge of data and algorithms for interpolation and discounting. Internally, when a request for discount factor value for this object (DiscountCurve) is received, it then uses DiscountAlgorithm delegate (which has been set to use continuous time discount factor) and InterpolationAlgorithm (which has been set to use linear interpolation).

      As we create basket pricers (BasketCDS objects into list) in the main program, we are assigning generic type delegate method (method takes one argument as double and returns one double) to be used for a basket pricer for getting a value for discount factor for any given double (maturity). Effectively, basket pricer will use method (GetDF) from DiscountCurve class.

      As we then use that delegate in basket pricer for calculating premium leg value [as : v += (df(t) * dt * p)], we are using discounting method delegate, what we have previously assigned for that class (GetDF). Note, that our generic type delegate method in basket pricer class [Func df] takes one argument as double and returns one double.

      I admit that this would be categorized as "almost cruel and unusual" type of scheme. The observed complexity has a very specific purpose. I wanted to maintain flexibility for the user to implement and use different types of methods for algorithms, which are interpolating and calculating value for a discount factor. Currently, there are only algorithms implemented for linear interpolation and continuous time discount factor. I hope this was clarifying the issue enough.

      Delete
  3. Hi Mikael, this is my email eugenio.demaio@gmail.com
    Could we have a short chat about the implementation? Just for understanding if I am on the wrong way or not!!
    Thanks

    ReplyDelete