Showing posts with label Bootstrapping algorithm. Show all posts
Showing posts with label Bootstrapping algorithm. Show all posts

Wednesday, October 30, 2019

QuantLib-Python: Note on ForwardCurve Construction

In this post, we will construct QuantLib ForwardCurve instance and investigate the resulting term structure of discount factors. Python program and Excel can be downloaded from my GitHub page. First, let us assume the following market data containing [A] one cash deposit and three interpolated rates from futures strip.


















After completing bootstrapping procedure, we should get [B] simple spot discount factors and [C] simple forward rates. Next, let us implement this scheme in QuantLib. Import QuantLib libraries and create relevant date objects. Then, set forward rates (from [C]), dates and create QuantLib forward curve instance.

import QuantLib as ql
today = ql.Date(30, 10, 2019)
ql.Settings.instance().evaluationDate = today  
settlementDate = ql.TARGET().advance(today, ql.Period(2, ql.Days))

rts = [0.03145, 0.03145, 0.0278373626373627, 0.0253076923076923, 0.0249373626373629]
dts = [settlementDate, ql.Date(1,2,2020), ql.Date(1,5,2020), ql.Date(1,8,2020), ql.Date(1,11,2020)]
c = ql.ForwardCurve(dts, rts, ql.Actual360(), ql.NullCalendar(), ql.BackwardFlat())
df = [c.discount(d) for d in dts]
print(df)

We get the following set of spot discount factors from the curve instance.
[1.0, 0.9919949898918935, 0.9851153255552918, 0.9787646299045335, 0.9725469122728971]
Note, that these are not what we expected to see. "The correct ones" are calculated in the column [B] in the example above. The issue here is, that when constructing ForwardCurve object, the given forward rates inputs are implicitly defined as continuously compounded rates. Assuming we would like to get the same discount factor term structure as in our example above in [B], we need to convert our forward rate inputs from simple to continuously compounded rates. Let us implement this in QuantLib. Convert simple rates to continuous rates. Then, reset forward rates and re-create QuantLib forward curve instance.

for i in range(len(rts) - 1):
    r_simple = ql.InterestRate(rts[i + 1], ql.Actual360(), ql.Simple, ql.Once)
    t = ql.Actual360().yearFraction(dts[i], dts[i + 1])
    r_continuous = r_simple.equivalentRate(ql.Continuous, ql.NoFrequency, t)
    rts[i + 1] = r_continuous.rate()
    
# set rate for the first node
rts[0] = rts[1]
c = ql.ForwardCurve(dts, rts, ql.Actual360(), ql.NullCalendar(), ql.BackwardFlat())
df = [c.discount(d) for d in dts]
print(df)

We get the following "correct" set of spot discount factors from the curve instance.
[1.0, 0.9920268596783518, 0.9851707210231435, 0.9788400520709886, 0.9726415228427708]
Inputs (dates and forward rates) for ForwardCurve object are defined in yellow box in column [E] below.
















ForwardCurve object interpolation scheme is backward flat. This means, that for the final period (from 1-Aug-20 to 1-Nov-20), forward rate of 2.48582% will be applied. Implicitly this means, that there is no practical use for the first given forward rate (at the node, where the date is 1-Nov-19). However, this rate has to be defined anyway.

Note, that QuantLib logic of constructing ForwardCurve object is completely correct. One should just be aware of the fact, that input forward rates given are handled implicitly as continuously compounded rates.

Finally, there are some other types of issues concerning forward rate interpolation, which might be worth of knowing when working with ForwardCurve object. These issues are fully explained in this video given by QuantLib lead developer Luigi Ballabio.

As always, thanks for reading this blog.
-Mike

Sunday, June 3, 2018

QuantLib : Bootstrapping OIS curve

As we all are aware, all collateralized derivative contracts must be valued by using dual-curve discounting, by using separate curves for cash flow projection and cash flow discounting. While this post does not yet explain how to implement such dual-curve disconting scheme in QuantLib, it shows how to use QuantLib for bootstrapping required OIS discount curve from available market rates. Some further reading material, which nicely explains the justification for the use of dual-curve discounting, is available in here.

The program


First, for the sake of brevity, required market data (Eonia rates) has been hard-coded into map container. Then, container will be iterated through for constructing all required rate helpers. After this, piecewise yield term structure will be created by using constructed rate helpers. Finally, an equally-spaced time grid (monthly) will be created and discount factors are requested from bootstrapped Eonia curve for each grid time point (screenshot below). The curve itself (eurOisCurve) could now be used as a discounting curve in a dual-curve valuation scheme in QuantLib.

















Thanks for reading this blog.
-Mike

#include <iostream>
#include <ql\quantlib.hpp>
#include <algorithm>
#include <map>
using namespace QuantLib;

int main() {

 try {
  // create common data 
  Date today(7, Jul, 2017);
  DayCounter dayCounter = Actual360();
  Calendar calendar = TARGET();
  Date settlementDate = calendar.advance(today, Period(2, Days));
  Natural settlementDays = settlementDate - today;
  Settings::instance().evaluationDate() = today;

  // create overnight index for euro currency
  auto eoniaIndex = boost::make_shared<Eonia>();

  // create container for rate helpers
  std::vector<boost::shared_ptr<RateHelper>> rateHelpers;

  // create first 1d cash instrument for eonia curve - using deposit rate helper
  auto Q1D = boost::make_shared<SimpleQuote>(-0.0036);
  rateHelpers.push_back(boost::make_shared<DepositRateHelper>
   (Handle<Quote>(Q1D), Period(1, Days), eoniaIndex->fixingDays(),
   eoniaIndex->fixingCalendar(), eoniaIndex->businessDayConvention(), 
   eoniaIndex->endOfMonth(), eoniaIndex->dayCounter()));

  // create source data for eonia curve (period, rate)
  std::map<Period, Real> eoniaCurveData;
  eoniaCurveData.insert(std::make_pair(Period(1, Weeks), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(2, Weeks), -0.00357));
  eoniaCurveData.insert(std::make_pair(Period(3, Weeks), -0.00356));
  eoniaCurveData.insert(std::make_pair(Period(1, Months), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(2, Months), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(3, Months), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(4, Months), -0.00357));
  eoniaCurveData.insert(std::make_pair(Period(5, Months), -0.00356));
  eoniaCurveData.insert(std::make_pair(Period(6, Months), -0.00353));
  eoniaCurveData.insert(std::make_pair(Period(7, Months), -0.00351));
  eoniaCurveData.insert(std::make_pair(Period(8, Months), -0.00349));
  eoniaCurveData.insert(std::make_pair(Period(9, Months), -0.00344));
  eoniaCurveData.insert(std::make_pair(Period(10, Months), -0.0034));
  eoniaCurveData.insert(std::make_pair(Period(11, Months), -0.00336));
  eoniaCurveData.insert(std::make_pair(Period(1, Years), -0.00331));
  eoniaCurveData.insert(std::make_pair(Period(15, Months), -0.00314));
  eoniaCurveData.insert(std::make_pair(Period(18, Months), -0.00295));
  eoniaCurveData.insert(std::make_pair(Period(21, Months), -0.00273));
  eoniaCurveData.insert(std::make_pair(Period(2, Years), -0.00248));
  eoniaCurveData.insert(std::make_pair(Period(3, Years), -0.00138));
  eoniaCurveData.insert(std::make_pair(Period(4, Years), -0.0001245));
  eoniaCurveData.insert(std::make_pair(Period(5, Years), 0.0011945));
  eoniaCurveData.insert(std::make_pair(Period(6, Years), 0.00254));
  eoniaCurveData.insert(std::make_pair(Period(7, Years), 0.00387));
  eoniaCurveData.insert(std::make_pair(Period(8, Years), 0.0052));
  eoniaCurveData.insert(std::make_pair(Period(9, Years), 0.006474));
  eoniaCurveData.insert(std::make_pair(Period(10, Years), 0.007634));
  eoniaCurveData.insert(std::make_pair(Period(11, Years), 0.00868));
  eoniaCurveData.insert(std::make_pair(Period(12, Years), 0.0096045));
  eoniaCurveData.insert(std::make_pair(Period(15, Years), 0.0117245));
  eoniaCurveData.insert(std::make_pair(Period(17, Years), 0.0126797));
  eoniaCurveData.insert(std::make_pair(Period(20, Years), 0.0136245));
  eoniaCurveData.insert(std::make_pair(Period(25, Years), 0.01441));
  eoniaCurveData.insert(std::make_pair(Period(30, Years), 0.01479));

  // create other instruments for eonia curve - using ois rate helper
  std::for_each(eoniaCurveData.begin(), eoniaCurveData.end(),
   [settlementDays, &rateHelpers, &eoniaIndex](std::pair<Period, Real> p) -> void 
   { rateHelpers.push_back(boost::make_shared<OISRateHelper>(settlementDays,
   p.first, Handle<Quote>(boost::make_shared<SimpleQuote>(p.second)), eoniaIndex)); });

  // create piecewise term structure
  Handle<YieldTermStructure> eurOisCurve(boost::make_shared<PiecewiseYieldCurve<Discount, LogLinear>>
   (settlementDays, eoniaIndex->fixingCalendar(), rateHelpers, eoniaIndex->dayCounter()));

  // create equally-spaced (monthly) time grid
  Time maturity = 30.0;
  Size steps = 360;
  TimeGrid grid(maturity, steps);

  // print monthly discount factors
  std::for_each(grid.begin(), grid.end(), [&eurOisCurve](Time t) -> void
   { std::cout << eurOisCurve->discount(t) << std::endl; });

 }
 catch (std::exception& e) {
  std::cout << e.what() << std::endl;
 }
 return 0;
}

Sunday, September 3, 2017

QuantLib : another implementation for piecewise yield curve builder class


Last year I published one possible implementation using Quantlib library for constructing piecewise yield curves. Within this second implementation, I have done a couple of changes in order to increase configurability. For allowing more flexible curve construction algorithm, Traits and Interpolations are now defined as template parameters. Previously, these were hard-coded inside the class. Secondly, all types of quotes for class methods are now wrapped inside shared pointers. Previously, there was an option to give quotes as rates. After some reconsideration, I have decided to give up this option completely. Finally, I have added a class method for allowing futures prices to be used in curve construction process.


Source data and results


The following source data from the book by Richard Flavell has been used for validation.



















Resulting zero-yield curve and discount factors are presented in the graph below. For this data used, the absolute maximum difference between Flavell-constructed and Quantlib-constructed zero yield curves is around one basis point.

















The program


// PiecewiseCurveBuilder.h
#pragma once
#include <ql/quantlib.hpp>
//
namespace MJPiecewiseCurveBuilderNamespace
{
 using namespace QuantLib;
 //
 // type alias definitions
 using pQuote = boost::shared_ptr<Quote>;
 using pIndex = boost::shared_ptr<IborIndex>;
 using pHelper = boost::shared_ptr<RateHelper>;
 //
 // T = traits, I = interpolation
 template<typename T, typename I>
 class PiecewiseCurveBuilder
 {
 public:
  PiecewiseCurveBuilder();
  void AddDeposit(const pQuote& quote, const pIndex& index);
  void AddFRA(const pQuote& quote, const Period& periodLengthToStart, const pIndex& index);
  void AddSwap(const pQuote& quote, const Period& periodLength, const Calendar& fixedCalendar, Frequency fixedFrequency,
   BusinessDayConvention fixedConvention, const DayCounter& fixedDayCount, const pIndex& floatIndex);
  //
  void AddFuture(const pQuote& quote, const Date& IMMDate, int lengthInMonths, const Calendar& calendar,
   BusinessDayConvention convention, const DayCounter& dayCounter, bool endOfMonth = true);
  //
  RelinkableHandle<YieldTermStructure> GetCurveHandle(const Date& settlementDate, const DayCounter& dayCounter);
 private:
  std::vector<pHelper> rateHelpers;

 };
}
//
//
//
//
// PiecewiseCurveBuilder.cpp
#pragma once
#include "PiecewiseCurveBuilder.h"
//
namespace MJPiecewiseCurveBuilderNamespace
{
 template<typename T, typename I>
 PiecewiseCurveBuilder<T, I>::PiecewiseCurveBuilder() { }
 //
 template<typename T, typename I>
 void PiecewiseCurveBuilder<T, I>::AddDeposit(const pQuote& quote, const pIndex& index)
 {
  pHelper rateHelper(new DepositRateHelper(Handle<Quote>(quote), index));
  rateHelpers.push_back(rateHelper);
 }
 //
 template<typename T, typename I>
 void PiecewiseCurveBuilder<T, I>::AddFRA(const pQuote& quote, const Period& periodLengthToStart, const pIndex& index)
 {
  pHelper rateHelper(new FraRateHelper(Handle<Quote>(quote),
   periodLengthToStart, pIndex(index)));
  rateHelpers.push_back(rateHelper);
 }
 //
 template<typename T, typename I>
 void PiecewiseCurveBuilder<T, I>::AddSwap(const pQuote& quote, const Period& periodLength, const Calendar& fixedCalendar,
  Frequency fixedFrequency, BusinessDayConvention fixedConvention, const DayCounter& fixedDayCount,
  const pIndex& floatIndex)
 {
  pHelper rateHelper(new SwapRateHelper(Handle<Quote>(quote), periodLength, fixedCalendar, fixedFrequency,
   fixedConvention, fixedDayCount, floatIndex));
  rateHelpers.push_back(rateHelper);
 }
 //
 template<typename T, typename I>
 void PiecewiseCurveBuilder<T, I>::AddFuture(const pQuote& quote, const Date& IMMDate, int lengthInMonths, const Calendar& calendar,
  BusinessDayConvention convention, const DayCounter& dayCounter, bool endOfMonth)
 {
  pHelper rateHelper(new FuturesRateHelper(Handle<Quote>(quote), IMMDate, lengthInMonths, calendar, convention, endOfMonth, dayCounter));
  rateHelpers.push_back(rateHelper);
 }
 //
 template<typename T, typename I>
 RelinkableHandle<YieldTermStructure> PiecewiseCurveBuilder<T, I>::GetCurveHandle(const Date& settlementDate, const DayCounter& dayCounter)
 {
  // T = traits, I = interpolation
  boost::shared_ptr<YieldTermStructure> yieldTermStructure(new PiecewiseYieldCurve<T, I>(settlementDate, rateHelpers, dayCounter));
  return RelinkableHandle<YieldTermStructure>(yieldTermStructure);
 }
}
//
//
//
//
// Tester.cpp
#include "PiecewiseCurveBuilder.cpp"
#include <iostream>
using namespace MJPiecewiseCurveBuilderNamespace;
//
int main()
{
 try
 {
  Date tradeDate(4, February, 2008);
  Settings::instance().evaluationDate() = tradeDate;
  Calendar calendar = TARGET();
  Date settlementDate = calendar.advance(tradeDate, Period(2, Days), ModifiedFollowing);
  DayCounter curveDaycounter = Actual360();
  PiecewiseCurveBuilder<ZeroYield, Linear> builder;
  //
  //
  // cash part of the curve
  pQuote q_1W(new SimpleQuote(0.032175));
  pIndex i_1W(new USDLibor(Period(1, Weeks)));
  builder.AddDeposit(q_1W, i_1W);
  //
  pQuote q_1M(new SimpleQuote(0.0318125));
  pIndex i_1M(new USDLibor(Period(1, Months)));
  builder.AddDeposit(q_1M, i_1M);
  //
  pQuote q_3M(new SimpleQuote(0.03145));
  pIndex i_3M(new USDLibor(Period(3, Months)));
  builder.AddDeposit(q_3M, i_3M);
  //
  //
  // futures part of the curve
  Date IMMDate;
  pQuote q_JUN08(new SimpleQuote(97.41));
  IMMDate = IMM::nextDate(settlementDate + Period(4, Months));
  builder.AddFuture(q_JUN08, IMMDate, 3, calendar, ModifiedFollowing, Actual360());
  //
  pQuote q_SEP08(new SimpleQuote(97.52));
  IMMDate = IMM::nextDate(settlementDate + Period(7, Months));
  builder.AddFuture(q_SEP08, IMMDate, 3, calendar, ModifiedFollowing, Actual360());
  //
  pQuote q_DEC08(new SimpleQuote(97.495));
  IMMDate = IMM::nextDate(settlementDate + Period(10, Months));
  builder.AddFuture(q_DEC08, IMMDate, 3, calendar, ModifiedFollowing, Actual360());
  //
  pQuote q_MAR09(new SimpleQuote(97.395));
  IMMDate = IMM::nextDate(settlementDate + Period(13, Months));
  builder.AddFuture(q_MAR09, IMMDate, 3, calendar, ModifiedFollowing, Actual360());
  //
  //
  // swap part of the curve
  pIndex swapFloatIndex(new USDLibor(Period(3, Months)));
  pQuote q_2Y(new SimpleQuote(0.02795));
  builder.AddSwap(q_2Y, Period(2, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_3Y(new SimpleQuote(0.03035));
  builder.AddSwap(q_3Y, Period(3, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_4Y(new SimpleQuote(0.03275));
  builder.AddSwap(q_4Y, Period(4, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_5Y(new SimpleQuote(0.03505));
  builder.AddSwap(q_5Y, Period(5, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_6Y(new SimpleQuote(0.03715));
  builder.AddSwap(q_6Y, Period(6, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_7Y(new SimpleQuote(0.03885));
  builder.AddSwap(q_7Y, Period(7, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_8Y(new SimpleQuote(0.04025));
  builder.AddSwap(q_8Y, Period(8, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_9Y(new SimpleQuote(0.04155));
  builder.AddSwap(q_9Y, Period(9, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_10Y(new SimpleQuote(0.04265));
  builder.AddSwap(q_10Y, Period(10, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  pQuote q_12Y(new SimpleQuote(0.04435));
  builder.AddSwap(q_12Y, Period(12, Years), calendar, Annual,
   ModifiedFollowing, Actual360(), swapFloatIndex);
  //
  //
  // get curve handle and print out discount factors
  RelinkableHandle<YieldTermStructure> curve = builder.GetCurveHandle(settlementDate, curveDaycounter);
  std::cout << curve->discount(Date(11, February, 2008)) << std::endl;
  std::cout << curve->discount(Date(4, March, 2008)) << std::endl;
  std::cout << curve->discount(Date(4, May, 2008)) << std::endl;
  std::cout << curve->discount(Date(6, August, 2008)) << std::endl;
  std::cout << curve->discount(Date(6, November, 2008)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2009)) << std::endl;
  std::cout << curve->discount(Date(8, February, 2010)) << std::endl;
  std::cout << curve->discount(Date(7, February, 2011)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2012)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2013)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2014)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2015)) << std::endl;
  std::cout << curve->discount(Date(8, February, 2016)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2017)) << std::endl;
  std::cout << curve->discount(Date(6, February, 2018)) << std::endl;
 }
 catch (std::exception& e)
 {
  std::cout << e.what() << std::endl;
 }
 return 0;
}


Data updating


Since all rates have been wrapped inside quotes (which have been wrapped inside handles), those can be accessed only by using dynamic pointer casting. Below is an example for updating 3M cash quote.

boost::dynamic_pointer_cast<SimpleQuote>(q_3M)->setValue(0.03395);

After this quote updating shown above, a new requested value (zero rate, forward rate or discount factor) from constructed curve object will be re-calculated by using updated quote.

For those readers who are completely unfamiliar with this stuff presented, there are three extremely well-written slides available in Quantlib documentation page, written by Dimitri Reiswich. These slides are offering excellent way to get very practical hands-on overview on QuantLib library. Also, Luigi Ballabio has finally been finishing his book on QuantLib implementation, which can be purchased from Leanpub. This book is offering deep diving experience into the abyss of Quantlib architechture.

Finally, as usual, thanks for spending your precious time here and reading this blog.
-Mike

Thursday, January 7, 2016

QuantLib : Builder Class for PiecewiseYieldCurve

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

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


One template to rule them all



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


Walkthrough


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

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

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

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

After this, we add quotes into USD curve builder :

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

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

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

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

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

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

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

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

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

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

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

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

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

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


The program


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

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