Tuesday, June 5, 2018

QuantLib : Dual-Curve Bootstrapping and Swap Valuation

Implementing OIS curve bootstrapping in QuantLib was presented in my previous post. Story will continue. This post will present, how to implement dual-curve bootstrapping scheme and corresponding valuation for a simple single-currency vanilla swap transaction (collateralization is in transaction currency). Detailed information on how to implement this scheme has been acquired mainly from two sources: StackExchange post on this topic and QuantLib Python Cookbook, which is worth of checking out. The great story does not lose its value, even told in different languages.

The program


In the beginning of this program, two relinkable handles for containing yield term structures are created, one for discounting curve and another for projection curve. The real beauty of these creatures comes from the fact, that we can use these handles as curve "placeholders" within our program and later link (or re-link) these handles with any yield term structure implementation.

After this, Eonia OIS curve will be bootstrapped and discounting curve handle is linked to bootstrapped Eonia curve. Similar bootstrapping procedure will be performed for creating Euribor curve, but with a special twist. In order to implement dual-curve bootstrapping algorithm in QuantLib, discounting curve handle must be delivered as one argument for all swap rate helpers, along with dummy quote handle and dummy zero period. Then, projection curve handle is linked to bootstrapped Euribor curve. After this, our projection curve should return "OIS-adjusted" Euribor forward rates for creating floating leg cash flows.

Finally, seasoned vanilla swap transaction will be created and valued. Effectively, cash flow discounting will be performed by using discounting curve handle (in pricing engine), whereas cash flow projection will be performed by using projection curve handle (in index object, in swap transaction). Just for a final note, I carefully checked all constructors for deposit and FRA rate helpers, but did not find any possibility to deliver discount curve handle for these rate helpers.

Thanks for reading this blog.
-Mike

#include <iostream>
#include <ql\quantlib.hpp>
#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 re-linkable handles for discounting and projection curves
  RelinkableHandle<YieldTermStructure> discountCurve;
  RelinkableHandle<YieldTermStructure> projectionCurve;
  // create container for all rate helpers
  std::vector<boost::shared_ptr<RateHelper>> rateHelpers;

  // create required indices
  auto eoniaIndex = boost::make_shared<Eonia>();
  // forward euribor fixings are requested from dual-curve-bootstrapped projection curve
  auto euriborIndex = boost::make_shared<Euribor6M>(projectionCurve);


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

  // create source data for eonia swaps (period, rate)
  std::map<Period, Real> eoniaSwapData;
  eoniaSwapData.insert(std::make_pair(Period(6, Months), -0.00353));
  eoniaSwapData.insert(std::make_pair(Period(1, Years), -0.00331));
  eoniaSwapData.insert(std::make_pair(Period(2, Years), -0.00248));
  eoniaSwapData.insert(std::make_pair(Period(3, Years), -0.00138));
  eoniaSwapData.insert(std::make_pair(Period(4, Years), -0.0001245));
  eoniaSwapData.insert(std::make_pair(Period(5, Years), 0.0011945));
  eoniaSwapData.insert(std::make_pair(Period(7, Years), 0.00387));
  eoniaSwapData.insert(std::make_pair(Period(10, Years), 0.007634));

  // create other instruments for eonia curve using ois rate helper
  std::for_each(eoniaSwapData.begin(), eoniaSwapData.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 eonia curve
  auto eoniaCurve = boost::make_shared<PiecewiseYieldCurve<Discount, LogLinear>>
   (0, eoniaIndex->fixingCalendar(), rateHelpers, eoniaIndex->dayCounter());  
  eoniaCurve->enableExtrapolation(true);
  // link discount curve to eonia curve
  discountCurve.linkTo(eoniaCurve);

  // clear rate helpers container
  rateHelpers.clear();


  // euribor curve
  // cash part
  rateHelpers.push_back(boost::make_shared<DepositRateHelper>(Handle<Quote>
   (boost::make_shared<SimpleQuote>(-0.00273)), Period(6, Months),
   settlementDays, calendar, euriborIndex->businessDayConvention(), 
   euriborIndex->endOfMonth(), euriborIndex->dayCounter()));

  // fra part
  rateHelpers.push_back(boost::make_shared<FraRateHelper>(Handle<Quote>
   (boost::make_shared<SimpleQuote>(-0.00194)), Period(6, Months), euriborIndex));

  // swap part
  rateHelpers.push_back(boost::make_shared<SwapRateHelper>(Handle<Quote>
   (boost::make_shared<SimpleQuote>(-0.00119)), Period(2, Years),
   calendar, Annual, ModifiedFollowing, Actual360(), euriborIndex,
   // in order to use dual-curve bootstrapping, discount curve handle must
   // be given as one argument for swap rate helper (along with dummy handle
   // for quote and dummy zero period for technical reasons)
   Handle<Quote>(), Period(0, Days), discountCurve));

  rateHelpers.push_back(boost::make_shared<SwapRateHelper>(Handle<Quote>
   (boost::make_shared<SimpleQuote>(0.00019)), Period(3, Years),
   calendar, Annual, ModifiedFollowing, Actual360(), euriborIndex,
   Handle<Quote>(), Period(0, Days), discountCurve));

  rateHelpers.push_back(boost::make_shared<SwapRateHelper>(Handle<Quote>
   (boost::make_shared<SimpleQuote>(0.00167)), Period(4, Years),
   calendar, Annual, ModifiedFollowing, Actual360(), euriborIndex,
   Handle<Quote>(), Period(0, Days), discountCurve));

  rateHelpers.push_back(boost::make_shared<SwapRateHelper>(Handle<Quote>
   (boost::make_shared<SimpleQuote>(0.00317)), Period(5, Years),
   calendar, Annual, ModifiedFollowing, Actual360(), euriborIndex,
   Handle<Quote>(), Period(0, Days), discountCurve));

  rateHelpers.push_back(boost::make_shared<SwapRateHelper>(Handle<Quote>
   (boost::make_shared<SimpleQuote>(0.00598)), Period(7, Years),
   calendar, Annual, ModifiedFollowing, Actual360(), euriborIndex,
   Handle<Quote>(), Period(0, Days), discountCurve));

  rateHelpers.push_back(boost::make_shared<SwapRateHelper>(Handle<Quote>
   (boost::make_shared<SimpleQuote>(0.00966)), Period(10, Years),
   calendar, Annual, ModifiedFollowing, Actual360(), euriborIndex,
   Handle<Quote>(), Period(0, Days), discountCurve));
  
  // create euribor curve
  auto euriborCurve = boost::make_shared<PiecewiseYieldCurve<Discount, LogLinear>>
   (0, euriborIndex->fixingCalendar(), rateHelpers, euriborIndex->dayCounter());
  euriborCurve->enableExtrapolation();
  // link projection curve to euribor curve
  projectionCurve.linkTo(euriborCurve);


  // create seasoned vanilla swap
  Date pastSettlementDate(5, Jun, 2015);

  Schedule fixedSchedule(pastSettlementDate, pastSettlementDate + Period(5, Years),
   Period(Annual), calendar, Unadjusted, Unadjusted,
   DateGeneration::Backward, false);

  Schedule floatSchedule(pastSettlementDate, pastSettlementDate + Period(5, Years),
   Period(Semiannual), calendar, Unadjusted, Unadjusted,
   DateGeneration::Backward, false);

  VanillaSwap swap(VanillaSwap::Payer, 10000000.0, fixedSchedule, 0.0285, 
   dayCounter, floatSchedule, euriborIndex, 0.0, dayCounter);

  // add required 6M euribor index fixing for floating leg valuation
  euriborIndex->addFixing(Date(1, Jun, 2017), -0.0025);

  // create pricing engine, request swap pv
  auto pricer = boost::make_shared<DiscountingSwapEngine>(discountCurve);
  swap.setPricingEngine(pricer);
  std::cout << swap.NPV() << std::endl;

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


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