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

1 comment:

  1. Hi Mikael, had a quick doubt which I hoped you could help me understand it, as it may be just a rookie type of doubt. Does the OIS rate helper take into account the different conventions of the OIS instruments respective to their tenor. Per example, as I understand, above 1y, OIS are priced as swaps. So does the rate helper take into account this vs the <1y which can be priced as zero coupon bonds?

    I am constructing the OIS curve, where the Index is the FED Funds. Does the following appears correct to you?

    Kind regards,
    Luis Aguiar

    PS: Its in Python

    OIS_tenors = [ql.Period(1, ql.Weeks), ql.Period(2, ql.Weeks), ql.Period(1, ql.Months),
    ql.Period(2, ql.Months), ql.Period(3, ql.Months), ql.Period(4, ql.Months),
    ql.Period(5, ql.Months), ql.Period(6, ql.Months), ql.Period(7, ql.Months),
    ql.Period(8, ql.Months), ql.Period(9, ql.Months), ql.Period(10, ql.Months),
    ql.Period(11, ql.Months), ql.Period(1, ql.Years), ql.Period(18, ql.Months),
    ql.Period(2, ql.Years), ql.Period(3, ql.Years), ql.Period(4, ql.Years),
    ql.Period(5, ql.Years), ql.Period(6, ql.Years), ql.Period(7, ql.Years),
    ql.Period(8, ql.Years), ql.Period(9, ql.Years), ql.Period(10, ql.Years),
    ql.Period(12, ql.Years), ql.Period(15, ql.Years), ql.Period(20, ql.Years),
    ql.Period(25, ql.Years), ql.Period(30, ql.Years)]

    OIS_bid_offer_rates = [(0.039, 0.067), (0.043, 0.063),
    (0.045, 0.060), (0.048, 0.058),
    (0.047, 0.057),(0.044, 0.055),
    (0.042, 0.052), (0.039, 0.052),
    (0.038, 0.048), (0.036, 0.047),
    (0.033, 0.047), (0.032, 0.042),
    (0.029, 0.041), (0.030, 0.040),
    (0.017, 0.027), (0.014, 0.025),
    (0.030, 0.040), (0.052, 0.102),
    (0.109, 0.159), (0.170, 0.220),
    (0.236, 0.286), (0.316, 0.326),
    (0.344, 0.394), (0.388, 0.438),
    (0.479, 0.529), (0.556, 0.606),
    (0.643, 0.693), (0.673, 0.723),
    (0.703, 0.713)]

    OIS_rates = []
    for i in OIS_bid_offer_rates:
    OIS_rates.append((i[0]+i[1])/2)

    OIS_data = []
    for i in range(len(OIS_rates)):
    OIS_data.append([OIS_rates[i], OIS_tenors[i]])

    cal = ql.TARGET()
    today = ql.Date(11,9,2009)
    settlementDays = 2
    dc = ql.Actual360()
    settlement = cal.advance(today, settlementDays, ql.Days)
    ql.Settings.instance().evaluationDate = today

    FedFunds = ql.FedFunds()
    helper = [ql.OISRateHelper(settlementDays, tenor, ql.QuoteHandle(ql.SimpleQuote(rate/100)), FedFunds)
    for rate, tenor in OIS_data]


    OIS_curve = ql.PiecewiseLogCubicDiscount(0, cal, helper, ql.Actual365Fixed())

    ReplyDelete