Friday, December 29, 2017

QuantLib : custom instrument and pricing engine implementation

Using in-built QuantLib instruments and pricing engines is almost trivial, since the library is actually performing all required groundwork on behalf of a lucky user. Hair may start to get gray, as soon as one wants to implement a new instrument and related pricing engine. The example presented below is implementing a simple zero-coupon bond instrument and corresponding (analytical) pricing engine.

// MJZeroCouponBond.h
#pragma once
#include <ql/quantlib.hpp>
using namespace QuantLib;
//
// implementation for zero-coupon bond instrument
class MJZeroCouponBond : public Instrument {
public:
 // forward class declarations
 class arguments;
 class engine;
 //
 // ctor and implementations for required base class methods
 MJZeroCouponBond(Real faceAmount, Date maturityDate);
 bool isExpired() const;
private:
 void setupArguments(PricingEngine::arguments* args) const;
 // term sheet related information
 Real faceAmount_;
 Date maturityDate_;
};
// inner arguments class
class MJZeroCouponBond::arguments : public PricingEngine::arguments {
public:
 void validate() const;
 Real faceAmount;
 Date maturityDate;
};
// inner engine class
class MJZeroCouponBond::engine 
 : public GenericEngine<MJZeroCouponBond::arguments, MJZeroCouponBond::results> {
 // base class for all further engine implementations
};
//
// implementation for base class engine
class MJZeroCouponBondEngine : public MJZeroCouponBond::engine {
public:
 MJZeroCouponBondEngine(const Handle<YieldTermStructure>& curve);
 void calculate() const;
private:
 Handle<YieldTermStructure> curve_;
};
//
//
//
//
//
// MJZeroCouponBond.cpp
#include "MJZeroCouponBond.h"
using namespace QuantLib;
//
// implementations for MJZeroCouponBond instrument
MJZeroCouponBond::MJZeroCouponBond(Real faceAmount, Date maturityDate) 
 : faceAmount_(faceAmount), maturityDate_(maturityDate) {
 // ctor
}
bool MJZeroCouponBond::isExpired() const {
 return Settings::instance().evaluationDate() >= maturityDate_;
}
void MJZeroCouponBond::setupArguments(PricingEngine::arguments* args) const {
 MJZeroCouponBond::arguments* args_ = dynamic_cast<MJZeroCouponBond::arguments*>(args);
 QL_REQUIRE(args_ != nullptr, "arguments casting error");
 args_->faceAmount = faceAmount_;
 args_->maturityDate = maturityDate_;
}
void MJZeroCouponBond::arguments::validate() const {
 QL_REQUIRE(faceAmount > 0.0, "transaction face amount cannot be zero");
}
//
// implementations for MJZeroCouponBondEngine
MJZeroCouponBondEngine::MJZeroCouponBondEngine(const Handle<YieldTermStructure>& curve)
 : curve_(curve) {
 // register observer (MJZeroCouponBondEngine) with observable (curve)
 registerWith(curve_);
}
void MJZeroCouponBondEngine::calculate() const {
 // extract required parameters from arguments or curve object
 // implement the actual pricing algorithm and store result
 Date maturityDate = arguments_.maturityDate;
 Real P = arguments_.faceAmount;
 DiscountFactor df = curve_->discount(maturityDate);
 Real PV = P * df;
 results_.value = PV;
}
//
//
//
//
//
// Tester.cpp
#pragma once
#include "MJZeroCouponBond.h"
using namespace QuantLib;
//
int main() {
 //
 try {
  // common data : calendar, daycounter, dates
  Calendar calendar = TARGET();
  DayCounter dayCounter = Actual360();
  Date transactionDate(18, December, 2017);
  Date maturityDate(18, December, 2030);
  Natural settlementDays = 2;
  Date settlementDate = calendar.advance(transactionDate, Period(settlementDays, Days));
  Settings::instance().evaluationDate() = settlementDate;
  //
  // create flat discount curve
  auto riskFreeRate = boost::make_shared<SimpleQuote>(0.01);
  Handle<Quote> riskFreeRateHandle(riskFreeRate);
  auto riskFreeRateTermStructure = boost::make_shared<FlatForward>(settlementDate, riskFreeRateHandle, dayCounter);
  Handle<YieldTermStructure> riskFreeRateTermStructureHandle(riskFreeRateTermStructure);
  //
  // create zero coupon bonds (QL in-built and custom implementation)
  Real faceAmount = 250000.0;
  auto QLZero = boost::make_shared<ZeroCouponBond>(settlementDays, calendar, faceAmount, maturityDate);
  auto MJZero = boost::make_shared<MJZeroCouponBond>(faceAmount, maturityDate);
  //
  // create pricing engines  (QL in-built and custom implementation)
  auto QLEngine = boost::make_shared<DiscountingBondEngine>(riskFreeRateTermStructureHandle);
  auto MJEngine = boost::make_shared<MJZeroCouponBondEngine>(riskFreeRateTermStructureHandle);
  //
  // set engines to instruments and price the both transaction
  QLZero->setPricingEngine(QLEngine);
  MJZero->setPricingEngine(MJEngine);
  //
  std::cout << "pre-shift QL zero " << QLZero->NPV() << std::endl;
  std::cout << "pre-shift MJ zero " << MJZero->NPV() << std::endl;
  //
  // shift valuation curve and re-price the both transactions
  riskFreeRate->setValue(0.0101);
  std::cout << "post-shift QL zero " << QLZero->NPV() << std::endl;
  std::cout << "post-shift MJ zero " << MJZero->NPV() << std::endl;
 }
 catch (std::exception& e) {
  std::cout << e.what() << std::endl;
 }
 return 0;
}


In order to get more familiar with QuantLib Monte Carlo framework, one may take a look at Implementing QuantLib blog or get the actual book on QuantLib implementation from Leanpub. Also, MoneyScience is organizing Introduction to QuantLib Development course regularly. The most essential parts of the library are thoroughly covered during this 3-day training course, hosted by QuantLib lead developer Luigi Ballabio. Thanks for reading this blog. Merry Christmas and Happy New Year for everybody.
-Mike


No comments:

Post a Comment