Saturday, August 15, 2015

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

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

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

CURVOLOGY

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

MARKET DATA

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

PROJECT OUTCOME

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

PROGRAM DESIGN

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

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

























THE PROGRAM

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


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

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


EXCEL INTERFACING

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


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

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



EXCEL WORKSHEET SETTINGS

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
















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

TEST RUN

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



















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


















AFTERTHOUGHTS

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

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

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

-Mike Juniperhill

Saturday, July 11, 2015

Pricing Bloomberg Swap Manager Transactions with C#

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

SWAP MANAGER AND FIELD SEARCH

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



















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







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















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

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

PROGRAM INPUT AND OUTPUT

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














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














CONFIGURATION FILES


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











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










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

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

PROGRAM DESIGN


UML class diagram has been presented below.


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

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

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

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

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

THE PROGRAM

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


AFTERTHOUGHTS

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

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

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

-Mike