Wednesday, October 10, 2018

Wilmott : Software Interoperability in Computational Finance, Part II: Applications to Derivatives Pricing in C++11 and C#

"With an anxiety that almost amounted to agony, I collected the instruments of life around me, that I might infuse a spark of being into the lifeless thing that lay at my feet. It was already one in the morning; the rain pattered dismally against the panes, and my candle was nearly out, when, by the glimmer of the half-extinguished light, I saw the dull yellow eye of the creature open; it breathed hard, and a convulsive motion agitated its limbs."
- Mary Shelley, Frankenstein 


This technical paper, which was published in Wilmott September magazine, is the second in a series of two on the design of software systems in computational finance. We created multi-language application to value an Equity-linked product. The actual pricing of this product was performed by using QuantLib Monte Carlo framework. We used C++/CLI code as a wrapper class for native QuantLib C++ code. Then, we used C# as a front end to C++/CLI wrapper, after having constructed transaction-related parameters and market data. For flexible input data construction, a specific factory mechanism was implemented by using C# Assembly, Reflection API, and Dynamic data types. Finally, we interfaced C# client code to Excel by using Excel-DNA.

The entire source code for this application is presented in this blog post. Since the paper discussed how we implemented this application, we feel that being able to compile and run the actual code is necessary in order to understand the complete design. The original post for Equity-linked note implementation (including the original transaction term sheet) can be found in here.

The actual paper can be found in here. Thanks for reading this blog.
-Mike


C++ project


In Visual Studio, create a new C++/CLR Class Library project (QLWrapper).

At this point, pre-installed and pre-built QuantLib and Boost libraries should be available. In the case one may not have these libraries available, all required procedures for getting this part done correctly is well presented in here and here. Create references to required Boost and QuantLib header files and libraries as follows.









Next, some C++/CLI project settings needs to be modified. Disable the use of pre-compiled headers.









Update properties to suppress some specific warnings.








Optionally, update properties to suppress (almost) all the other warnings.









Next, add required C++ header and source files as presented below. Add new file for containing native C++ header file information (EquityLinkedNote.h).

// EquityLinkedNote.h
#pragma once
#include <ql/quantlib.hpp>
using namespace QuantLib;

namespace QuantLibCppNative {

 // instrument implementation for equity-linked note
 class EquityLinkedNote : public Instrument {
 public:
  // forward class declarations
  class arguments;
  class engine;

  // ctor and implementations for required base class methods
  EquityLinkedNote(Real notional, Real initialFixing, const std::vector<Date>& fixingDates,
   const std::vector<Date>& paymentDates, Real cap, Real floor);
  bool isExpired() const;

 private:
  void setupArguments(PricingEngine::arguments* args) const;
  // term sheet information
  Real notional_;
  Real initialFixing_;
  std::vector<Date> fixingDates_;
  std::vector<Date> paymentDates_;
  Real cap_;
  Real floor_;
 };

 // inner arguments class
 class EquityLinkedNote::arguments : public PricingEngine::arguments {
 public:
  void validate() const;
  Real notional;
  Real initialFixing;
  std::vector<Date> fixingDates;
  std::vector<Date> paymentDates;
  Real cap;
  Real floor;
 };

 // inner engine class
 class EquityLinkedNote::engine
  : public GenericEngine <EquityLinkedNote::arguments, EquityLinkedNote::results> {
  // base class for all further engine implementations
 };

 // path pricer implementation for equity-linked note
 class EquityLinkedNotePathPricer : public PathPricer < Path > {
 public:
  EquityLinkedNotePathPricer(Real notional, Real initialFixing, const std::vector<Date>& fixingDates,
   const std::vector<Date>& paymentDates, Real cap, Real floor, const Handle<YieldTermStructure>& curve);
  Real operator()(const Path& path) const;
 private:
  Real notional_;
  Real initialFixing_;
  std::vector<Date> fixingDates_;
  std::vector<Date> paymentDates_;
  Real cap_;
  Real floor_;
  Handle<YieldTermStructure> curve_;
 };

 // monte carlo framework engine implementation for base engine class
 template <typename RNG = PseudoRandom, typename S = Statistics>
 class EquityLinkedNoteMonteCarloPricer : public EquityLinkedNote::engine, private McSimulation <SingleVariate, RNG, S> {
 public:
  // ctor
  EquityLinkedNoteMonteCarloPricer(const boost::shared_ptr<StochasticProcess>& process,
   const Handle<YieldTermStructure>& curve, bool antitheticVariate, Size requiredSamples,
   Real requiredTolerance, Size maxSamples, BigNatural seed)
   : McSimulation<SingleVariate, RNG, S>(antitheticVariate, false), process_(process), curve_(curve),
   requiredSamples_(requiredSamples), requiredTolerance_(requiredTolerance), maxSamples_(maxSamples), seed_(seed) {
   // register observer (pricer) with observables (stochastic process, curve)
   registerWith(process_);
   registerWith(curve_);
  }

  // implementation for required base engine class method
  void calculate() const {
   // the actual simulation process will be performed within the following method
   McSimulation<SingleVariate, RNG, S>::calculate(requiredTolerance_, requiredSamples_, maxSamples_);
   this->results_.value = this->mcModel_->sampleAccumulator().mean();
   //
   if (RNG::allowsErrorEstimate) {
    this->results_.errorEstimate = this->mcModel_->sampleAccumulator().errorEstimate();
   }
   else {
    this->results_.errorEstimate = Null<Real>();
   }
  }

 private:
  // type definitions
  typedef McSimulation<SingleVariate, RNG, S> simulation;
  typedef typename simulation::path_pricer_type path_pricer_type;
  typedef typename simulation::path_generator_type path_generator_type;

  // implementation for McSimulation class virtual method
  TimeGrid timeGrid() const {
   // create time grid based on a set of given index fixing dates
   Date referenceDate = curve_->referenceDate();
   DayCounter dayCounter = curve_->dayCounter();
   std::vector<Time> fixingTimes(arguments_.fixingDates.size());
   for (Size i = 0; i != fixingTimes.size(); ++i) {
    fixingTimes[i] = dayCounter.yearFraction(referenceDate, arguments_.fixingDates[i]);
   }
   return TimeGrid(fixingTimes.begin(), fixingTimes.end());
  }

  // implementation for McSimulation class virtual method
  boost::shared_ptr<path_generator_type> pathGenerator() const {
   // create time grid and get information concerning number of simulation steps for a path
   TimeGrid grid = timeGrid();
   Size steps = (grid.size() - 1);
   // create random sequence generator and return path generator
   typename RNG::rsg_type generator = RNG::make_sequence_generator(steps, seed_);
   return boost::make_shared<path_generator_type>(process_, grid, generator, false);
  }

  // implementation for McSimulation class virtual method
  boost::shared_ptr<path_pricer_type> pathPricer() const {
   // create path pricer implementation for equity-linked note
   return boost::make_shared<EquityLinkedNotePathPricer>(arguments_.notional, arguments_.initialFixing,
    arguments_.fixingDates, arguments_.paymentDates, arguments_.cap, arguments_.floor, this->curve_);
  }

  // private data members
  boost::shared_ptr<StochasticProcess> process_;
  Handle<YieldTermStructure> curve_;
  Size requiredSamples_;
  Real requiredTolerance_;
  Size maxSamples_;
  BigNatural seed_;
 };
}

Add new file for containing native C++ source file information (EquityLinkedNote.cpp).

// EquityLinkedNote.cpp
#include "EquityLinkedNote.h"
#include <algorithm>

namespace QuantLibCppNative {
 // implementations for equity-linked note methods
 EquityLinkedNote::EquityLinkedNote(Real notional, Real initialFixing, const std::vector<Date>& fixingDates,
  const std::vector<Date>& paymentDates, Real cap, Real floor)
  : notional_(notional), initialFixing_(initialFixing), fixingDates_(fixingDates),
  paymentDates_(paymentDates), cap_(cap), floor_(floor) {
  // ctor
 }

 bool EquityLinkedNote::isExpired() const {
  Date valuationDate = Settings::instance().evaluationDate();
  // note is expired, if valuation date is greater than the last fixing date
  if (valuationDate > fixingDates_.back())
   return true;
  return false;
 }

 void EquityLinkedNote::setupArguments(PricingEngine::arguments* args) const {
  EquityLinkedNote::arguments* args_ = dynamic_cast<EquityLinkedNote::arguments*>(args);
  QL_REQUIRE(args_ != nullptr, "arguments casting error");
  args_->notional = notional_;
  args_->initialFixing = initialFixing_;
  args_->fixingDates = fixingDates_;
  args_->paymentDates = paymentDates_;
  args_->cap = cap_;
  args_->floor = floor_;
 }

 void EquityLinkedNote::arguments::validate() const {
  // checks for some general argument properties
  QL_REQUIRE(notional > 0.0, "notional must be greater than zero");
  QL_REQUIRE(initialFixing > 0.0, "initial fixing must be greater than zero");
  QL_REQUIRE(cap > floor, "cap must be greater than floor");
  // check for date consistency : all payment dates have to be included 
  // within a set of given fixing dates
  for (int i = 0; i != paymentDates.size(); ++i) {
   if (std::find(fixingDates.begin(), fixingDates.end(), paymentDates[i]) == fixingDates.end()) {
    QL_REQUIRE(false, "payment date has to be included within given fixing dates");
   }
  }
 }

 // implementations for equity-linked path pricer methods
 EquityLinkedNotePathPricer::EquityLinkedNotePathPricer(Real notional, Real initialFixing, const std::vector<Date>& fixingDates,
  const std::vector<Date>& paymentDates, Real cap, Real floor, const Handle<YieldTermStructure>& curve)
  : notional_(notional), initialFixing_(initialFixing), fixingDates_(fixingDates),
  paymentDates_(paymentDates), cap_(cap), floor_(floor), curve_(curve) {
  // ctor
 }

 // the actual pricing algorithm for a simulated path is implemented in this method
 Real EquityLinkedNotePathPricer::operator()(const Path& path) const {
  Real coupon = 0.0;
  Real cumulativeCoupon = 0.0;
  Real aggregatePathPayoff = 0.0;
  int paymentDateCounter = 0;

  // loop through fixing dates
  for (int i = 1; i != fixingDates_.size(); ++i) {
   // calculate floating coupon, based on simulated index fixing values
   coupon = std::min(path.at(i) / path.at(i - 1) - 1, cap_);
   // add floating coupon to cumulative coupon
   cumulativeCoupon += coupon;

   // calculate period payoff for each payment date
   if (fixingDates_[i] == paymentDates_[paymentDateCounter]) {
    // calculate discounted payoff for current period, add value to aggregate path payoff
    aggregatePathPayoff += std::max(cumulativeCoupon, floor_) * notional_ * curve_->discount(fixingDates_[i]);
    // re-initialize cumulative coupon to zero, look for the next payment date
    cumulativeCoupon = 0.0;
    paymentDateCounter++;
   }
  }
  return aggregatePathPayoff;
 }
}

Update the existing file to contain managed C++ header file information (QLWrapper.h).

// QLWrapper.h
#pragma once
#include "EquityLinkedNote.h"
using namespace System;
using namespace System::Collections::Generic;

namespace QLWrapper {

 // C++/CLI wrapper class
 public ref class EquityLinkedNote {
 
 public:
  EquityLinkedNote(double notional, double cap, double floor, DateTime transactionDate, 
   int settlementDays, String^ calendar, String^ dayCountConvention, int requiredSamples, 
   int seed, List<DateTime>^ fixingDates, List<DateTime>^ paymentDates, 
   double initialFixing, double volatility, Dictionary<DateTime, double>^ curve);
  !EquityLinkedNote();
  ~EquityLinkedNote();
  double PV();
 
 private:
  // note : class members as native pointers
  QuantLibCppNative::EquityLinkedNote* note;
  QuantLibCppNative::EquityLinkedNoteMonteCarloPricer<PseudoRandom, Statistics>* pricer;
 };
}

namespace QuantLibConversions {
 
 // one static class for all QuantLib-related type conversions
 public ref class Convert abstract sealed {
 public:

  // convert System.String to QuantLib.DayCounter class
  static DayCounter ToDayCounter(String^ dayCounterString);

  // convert System.DateTime to QuantLib.Date class
  static Date ToDate(DateTime dateTime);

  // convert System.String to QuantLib.Calendar class
  static Calendar ToCalendar(String^ calendarString);

  // convert System.String to QuantLib.BusinessDayConvention enumerator
  static BusinessDayConvention ToBusinessDayConvention(String^ businessDayConventionString);

 };
}

Update the existing file to contain managed C++ source file information (QLWrapper.cpp).

// QLWrapper.cpp
#pragma once
#include "QLWrapper.h"

namespace QLWrapper {
 using QL = QuantLibConversions::Convert;

 EquityLinkedNote::EquityLinkedNote(double notional_, double cap_, double floor_,
  DateTime transactionDate_, int settlementDays_, String^ calendar_,
  String^ dayCountConvention_, int requiredSamples_, int seed_,
  List<DateTime>^ fixingDates_, List<DateTime>^ paymentDates_,
  double initialFixing_, double volatility_, Dictionary<DateTime, double>^ curve_) {

  // create QL calendar from a given string
  Calendar calendar = QL::ToCalendar(calendar_);

  // create QL daycounter from a given string
  DayCounter dayCountConvention = QL::ToDayCounter(dayCountConvention_);

  Date transactionDate = QL::ToDate(transactionDate_);
  Date settlementDate = calendar.advance(transactionDate, Period(settlementDays_, Days));
  Settings::instance().evaluationDate() = settlementDate;

  // import fixing dates to std::vector
  std::vector<Date> fixingDates;
  for each (System::DateTime dt in fixingDates_) {
   fixingDates.push_back(QL::ToDate(dt));
  }

  // import payment dates to std::vector
  std::vector<Date> paymentDates;
  for each (System::DateTime dt in paymentDates_) {
   paymentDates.push_back(QL::ToDate(dt));
  }

  // create structured equity-linked note
  note = new QuantLibCppNative::EquityLinkedNote(notional_, initialFixing_, fixingDates, paymentDates, cap_, floor_);

  // create std::vector containers for dates and discount factors
  std::vector<Date> maturityDates;
  std::vector<double> discountFactors;
  for each (KeyValuePair<DateTime, double> kvp in curve_) {
   maturityDates.push_back(QL::ToDate(kvp.Key));
   discountFactors.push_back(kvp.Value);
  }

  // create valuation curve from a set of given discount factors
  auto riskFreeRateTermStructure = boost::make_shared<InterpolatedDiscountCurve<LogLinear>>
   (maturityDates, discountFactors, dayCountConvention, calendar);
  Handle<YieldTermStructure> riskFreeRateTermStructureHandle(riskFreeRateTermStructure);

  // create stochastic process
  Handle<Quote> initialFixingHandle(boost::shared_ptr<Quote>(new SimpleQuote(initialFixing_)));
  auto volatilityQuote = boost::make_shared<SimpleQuote>(volatility_);
  Handle<Quote> volatilityHandle(volatilityQuote);
  Handle<BlackVolTermStructure> volatilityTermStructureHandle(boost::shared_ptr<BlackVolTermStructure>
   (new BlackConstantVol(settlementDays_, calendar, volatilityHandle, dayCountConvention)));
  auto process = boost::make_shared<BlackScholesProcess>(initialFixingHandle, riskFreeRateTermStructureHandle, volatilityTermStructureHandle);

  // create pricer instance
  pricer = new QuantLibCppNative::EquityLinkedNoteMonteCarloPricer<PseudoRandom, Statistics>
   (process, riskFreeRateTermStructureHandle, false, static_cast<Size>(requiredSamples_),
   Null<Real>(), static_cast<Size>(requiredSamples_), static_cast<BigNatural>(seed_));

  // assign pricer to instrument
  // note : cast pricer from native pointer to boost shared pointer
  note->setPricingEngine(static_cast<boost::shared_ptr<
   QuantLibCppNative::EquityLinkedNoteMonteCarloPricer<PseudoRandom, Statistics>>>(pricer));
 }
 EquityLinkedNote::!EquityLinkedNote() {
  delete note;
  delete pricer;
 }
 EquityLinkedNote::~EquityLinkedNote() {
  this->!EquityLinkedNote();
 }
 double EquityLinkedNote::PV() {
  return note->NPV();
 }
}

namespace QuantLibConversions {

 DayCounter Convert::ToDayCounter(String^ dayCounterString) {
  if (dayCounterString->ToUpper() == "ACTUAL360") return Actual360();
  if (dayCounterString->ToUpper() == "THIRTY360") return Thirty360();
  if (dayCounterString->ToUpper() == "ACTUALACTUAL") return ActualActual();
  if (dayCounterString->ToUpper() == "BUSINESS252") return Business252();
  if (dayCounterString->ToUpper() == "ACTUAL365NOLEAP") return Actual365NoLeap();
  if (dayCounterString->ToUpper() == "ACTUAL365FIXED") return Actual365Fixed();
  // requested day counter not found, throw exception
  throw gcnew System::Exception("undefined daycounter");
 }

 Date Convert::ToDate(DateTime dateTime) {
  // Date constructor using Excel dateserial
  return Date(dateTime.ToOADate());
 }

 Calendar Convert::ToCalendar(String^ calendarString) {
  if (calendarString->ToUpper() == "ARGENTINA.MERVAL") return Argentina(Argentina::Market::Merval);
  if (calendarString->ToUpper() == "AUSTRALIA") return Australia();
  if (calendarString->ToUpper() == "BRAZIL.EXCHANGE") return Brazil(Brazil::Market::Exchange);
  if (calendarString->ToUpper() == "BRAZIL.SETTLEMENT") return Brazil(Brazil::Market::Settlement);
  if (calendarString->ToUpper() == "CANADA.SETTLEMENT") return Canada(Canada::Market::Settlement);
  if (calendarString->ToUpper() == "CANADA.TSX") return Canada(Canada::Market::TSX);
  if (calendarString->ToUpper() == "CHINA.IB") return China(China::Market::IB);
  if (calendarString->ToUpper() == "CHINA.SSE") return China(China::Market::SSE);
  if (calendarString->ToUpper() == "CZECHREPUBLIC.PSE") return CzechRepublic(CzechRepublic::Market::PSE);
  if (calendarString->ToUpper() == "DENMARK") return Denmark();
  if (calendarString->ToUpper() == "FINLAND") return Finland();
  if (calendarString->ToUpper() == "GERMANY.SETTLEMENT") return Germany(Germany::Market::Settlement);
  if (calendarString->ToUpper() == "GERMANY.FRANKFURTSTOCKEXCHANGE") return Germany(Germany::Market::FrankfurtStockExchange);
  if (calendarString->ToUpper() == "GERMANY.XETRA") return Germany(Germany::Market::Xetra);
  if (calendarString->ToUpper() == "GERMANY.EUREX") return Germany(Germany::Market::Eurex);
  if (calendarString->ToUpper() == "GERMANY.EUWAX") return Germany(Germany::Market::Euwax);
  if (calendarString->ToUpper() == "HONGKONG.HKEX") return HongKong(HongKong::Market::HKEx);
  if (calendarString->ToUpper() == "INDIA.NSE") return India(India::Market::NSE);
  if (calendarString->ToUpper() == "INDONESIA.BEJ") return Indonesia(Indonesia::Market::BEJ);
  if (calendarString->ToUpper() == "INDONESIA.IDX") return Indonesia(Indonesia::Market::IDX);
  if (calendarString->ToUpper() == "INDONESIA.JSX") return Indonesia(Indonesia::Market::JSX);
  if (calendarString->ToUpper() == "ISRAEL.SETTLEMENT") return Israel(Israel::Market::Settlement);
  if (calendarString->ToUpper() == "ISRAEL.TASE") return Israel(Israel::Market::TASE);
  if (calendarString->ToUpper() == "ITALY.EXCHANGE") return Italy(Italy::Market::Exchange);
  if (calendarString->ToUpper() == "ITALY.SETTLEMENT") return Italy(Italy::Market::Settlement);
  if (calendarString->ToUpper() == "JAPAN") return Japan();
  if (calendarString->ToUpper() == "MEXICO.BMV") return Mexico(Mexico::Market::BMV);
  if (calendarString->ToUpper() == "NEWZEALAND") return NewZealand();
  if (calendarString->ToUpper() == "NORWAY") return Norway();
  if (calendarString->ToUpper() == "POLAND") return Poland();
  if (calendarString->ToUpper() == "ROMANIA") return Romania();
  if (calendarString->ToUpper() == "RUSSIA.MOEX") return Russia(Russia::Market::MOEX);
  if (calendarString->ToUpper() == "RUSSIA.SETTLEMENT") return Russia(Russia::Market::Settlement);
  if (calendarString->ToUpper() == "SAUDIARABIA.TADAWUL") return SaudiArabia(SaudiArabia::Market::Tadawul);
  if (calendarString->ToUpper() == "SINGAPORE.SGX") return Singapore(Singapore::Market::SGX);
  if (calendarString->ToUpper() == "SLOVAKIA.BSSE") return Slovakia(Slovakia::Market::BSSE);
  if (calendarString->ToUpper() == "SOUTHAFRICA") return SouthAfrica();
  if (calendarString->ToUpper() == "SOUTHKOREA.KRX") return SouthKorea(SouthKorea::Market::KRX);
  if (calendarString->ToUpper() == "SOUTHKOREA.SETTLEMENT") return SouthKorea(SouthKorea::Market::Settlement);
  if (calendarString->ToUpper() == "SWEDEN") return Sweden();
  if (calendarString->ToUpper() == "SWITZERLAND") return Switzerland();
  if (calendarString->ToUpper() == "TAIWAN.TSEC") return Taiwan(Taiwan::Market::TSEC);
  if (calendarString->ToUpper() == "TARGET") return TARGET();
  if (calendarString->ToUpper() == "TURKEY") return Turkey();
  if (calendarString->ToUpper() == "UKRAINE.USE") return Ukraine(Ukraine::Market::USE);
  if (calendarString->ToUpper() == "UNITEDKINGDOM.EXCHANGE") return UnitedKingdom(UnitedKingdom::Market::Exchange);
  if (calendarString->ToUpper() == "UNITEDKINGDOM.METALS") return UnitedKingdom(UnitedKingdom::Market::Metals);
  if (calendarString->ToUpper() == "UNITEDKINGDOM.SETTLEMENT") return UnitedKingdom(UnitedKingdom::Market::Settlement);
  if (calendarString->ToUpper() == "UNITEDSTATES.GOVERNMENTBOND") return UnitedStates(UnitedStates::Market::GovernmentBond);
  if (calendarString->ToUpper() == "UNITEDSTATES.LIBORIMPACT") return UnitedStates(UnitedStates::Market::LiborImpact);
  if (calendarString->ToUpper() == "UNITEDSTATES.NERC") return UnitedStates(UnitedStates::Market::NERC);
  if (calendarString->ToUpper() == "UNITEDSTATES.NYSE") return UnitedStates(UnitedStates::Market::NYSE);
  if (calendarString->ToUpper() == "UNITEDSTATES.SETTLEMENT") return UnitedStates(UnitedStates::Market::Settlement);
  // requested calendar not found, throw exception
  throw gcnew System::Exception("undefined calendar");
 }

 BusinessDayConvention Convert::ToBusinessDayConvention(String^ businessDayConventionString) {
  if (businessDayConventionString->ToUpper() == "FOLLOWING") return BusinessDayConvention::Following;
  if (businessDayConventionString->ToUpper() == "HALFMONTHMODIFIEDFOLLOWING") return BusinessDayConvention::HalfMonthModifiedFollowing;
  if (businessDayConventionString->ToUpper() == "MODIFIEDFOLLOWING") return BusinessDayConvention::ModifiedFollowing;
  if (businessDayConventionString->ToUpper() == "MODIFIEDPRECEDING") return BusinessDayConvention::ModifiedPreceding;
  if (businessDayConventionString->ToUpper() == "NEAREST") return BusinessDayConvention::Nearest;
  if (businessDayConventionString->ToUpper() == "PRECEDING") return BusinessDayConvention::Preceding;
  if (businessDayConventionString->ToUpper() == "UNADJUSTED") return BusinessDayConvention::Unadjusted;
  // requested business day convention not found, throw exception
  throw gcnew System::Exception("undefined business day convention");
 }
}

After this, one should be able to build this C++ project successfully.

C# project


In the first stage, a simple console application is implemented, in order to quickly test the core C++ program from C# client program. Add new C# console project (CsClient). Then, add file containing C# tester program.

using System;
using System.Collections.Generic;

namespace CsClient {
    class Program {
        static void Main(string[] args) {
            try {
                double notional = 1000000.0;
                double initialFixing = 3662.18;
                double volatility = 0.16;
                double cap = 0.015;
                double floor = 0.0;
                DateTime transactionDate = new DateTime(2017, 10, 30);
                int settlementDays = 2;
                string calendar = "TARGET";
                string dayCountConvention = "ACTUAL360";
                int requiredSamples = 1000;
                int seed = 0;
                List<DateTime> fixingDates = new List<DateTime>() {
                    new DateTime(2017, 11, 30), new DateTime(2017, 12, 30), new DateTime(2018, 1, 30), 
                    new DateTime(2018, 2, 28), new DateTime(2018, 3, 30), new DateTime(2018, 4, 30), 
                    new DateTime(2018, 5, 30), new DateTime(2018, 6, 30), new DateTime(2018, 7, 30), 
                    new DateTime(2018, 8, 30), new DateTime(2018, 9, 30), new DateTime(2018, 10, 30), 
                    new DateTime(2018, 11, 30), new DateTime(2018, 12, 30), new DateTime(2019, 1, 30), 
                    new DateTime(2019, 2, 28), new DateTime(2019, 3, 30), new DateTime(2019, 4, 30), 
                    new DateTime(2019, 5, 30), new DateTime(2019, 6, 30), new DateTime(2019, 7, 30), 
                    new DateTime(2019, 8, 30), new DateTime(2019, 9, 30), new DateTime(2019, 10, 30), 
                    new DateTime(2019, 11, 30), new DateTime(2019, 12, 30), new DateTime(2020, 1, 30), 
                    new DateTime(2020, 2, 29), new DateTime(2020, 3, 30), new DateTime(2020, 4, 30), 
                    new DateTime(2020, 5, 30), new DateTime(2020, 6, 30), new DateTime(2020, 7, 30), 
                    new DateTime(2020, 8, 30), new DateTime(2020, 9, 30), new DateTime(2020, 10, 30)
                };
                List<DateTime> paymentDates = new List<DateTime>() {
                    new DateTime(2018, 10, 30), new DateTime(2019, 10, 30), new DateTime(2020, 10, 30)
                };
                Dictionary<DateTime, double> curve = new Dictionary<DateTime, double>() {
                    { new DateTime(2017, 10, 30), 1.0 }, { new DateTime(2018, 10, 30), 0.98 },
                    { new DateTime(2019, 10, 30), 0.96 }, { new DateTime(2020, 10, 30), 0.92 },
                    { new DateTime(2021, 10, 30), 0.85 }
                };
                QLWrapper.EquityLinkedNote wrapperNote = new QLWrapper.EquityLinkedNote(notional, cap, floor, transactionDate, settlementDays,
                    calendar, dayCountConvention, requiredSamples, seed, fixingDates, paymentDates, initialFixing, volatility, curve);
                double pv = wrapperNote.PV();
                Console.WriteLine(pv.ToString());
                GC.SuppressFinalize(wrapperNote);
            }
            catch (Exception e) {
                Console.WriteLine(e.Message);
            }
        }
    }
}

Set C# project as StartUp project and add reference to C++ project (QLWrapper).

At the moment, C# console client is using managed C++ code, which is wrapping native C++ code, which uses QuantLib C++ library. At this point, build should be successfully completed and previous C# program should return PV for equity-linked note.

Configurations and Factories


In order to get rid of all hard-coded market data and transaction parameters in previous C# client, configurations and data factories will be implemented. Next, implement the following XML configuration file. Remember to define the correct paths for XML and Access files into this configuration file.

<configurations>
     <XMLFilePathName>..\EquityLinkedNote.xml</XMLFilePathName>
    <connectionString>Provider=Microsoft.ACE.OLEDB.12.0;Data Source=..\Market.accdb;Jet OLEDB:Database Password=MyDbPassword;</connectionString>
</configurations>

Then, implement the following XML presentation for Equity-linked note transaction.

<EquityLinkedNote>
  <notional>1000000.0</notional>
  <cap>0.015</cap>
  <floor>0.0</floor>
  <transactionDate>2017-10-30T00:00:00</transactionDate>
  <settlementDays>2</settlementDays>
  <calendar>TARGET</calendar>
  <dayCountConvention>ACTUAL360</dayCountConvention>
  <requiredSamples>1000</requiredSamples>
  <seed>0</seed>
  <fixingDates>
    <dateTime>2017-11-30T00:00:00</dateTime>    <dateTime>2017-12-30T00:00:00</dateTime>
    <dateTime>2018-01-30T00:00:00</dateTime>    <dateTime>2018-02-28T00:00:00</dateTime>
    <dateTime>2018-03-30T00:00:00</dateTime>    <dateTime>2018-04-30T00:00:00</dateTime>
    <dateTime>2018-05-30T00:00:00</dateTime>    <dateTime>2018-06-30T00:00:00</dateTime>
    <dateTime>2018-07-30T00:00:00</dateTime>    <dateTime>2018-08-30T00:00:00</dateTime>
    <dateTime>2018-09-30T00:00:00</dateTime>    <dateTime>2018-10-30T00:00:00</dateTime>
    <dateTime>2018-11-30T00:00:00</dateTime>    <dateTime>2018-12-30T00:00:00</dateTime>
    <dateTime>2019-01-30T00:00:00</dateTime>    <dateTime>2019-02-28T00:00:00</dateTime>
    <dateTime>2019-03-30T00:00:00</dateTime>    <dateTime>2019-04-30T00:00:00</dateTime>
    <dateTime>2019-05-30T00:00:00</dateTime>    <dateTime>2019-06-30T00:00:00</dateTime>
    <dateTime>2019-07-30T00:00:00</dateTime>    <dateTime>2019-08-30T00:00:00</dateTime>
    <dateTime>2019-09-30T00:00:00</dateTime>    <dateTime>2019-10-30T00:00:00</dateTime>
    <dateTime>2019-11-30T00:00:00</dateTime>    <dateTime>2019-12-30T00:00:00</dateTime>
    <dateTime>2020-01-30T00:00:00</dateTime>    <dateTime>2020-02-29T00:00:00</dateTime>
    <dateTime>2020-03-30T00:00:00</dateTime>    <dateTime>2020-04-30T00:00:00</dateTime>
    <dateTime>2020-05-30T00:00:00</dateTime>    <dateTime>2020-06-30T00:00:00</dateTime>
    <dateTime>2020-07-30T00:00:00</dateTime>    <dateTime>2020-08-30T00:00:00</dateTime>
    <dateTime>2020-09-30T00:00:00</dateTime>    <dateTime>2020-10-30T00:00:00</dateTime>
  </fixingDates>
  <paymentDates>
    <dateTime>2018-10-30T00:00:00</dateTime>
    <dateTime>2019-10-30T00:00:00</dateTime>
    <dateTime>2020-10-30T00:00:00</dateTime>
  </paymentDates>
</EquityLinkedNote>

Next, prepare MS Access database (name: Market.accdb) for containing all required market information. For the reasons of convenience we use simple MS Access database to host market data. However, extending this code for more realistic settings (for example, SQL Server) should not pose difficulties for an experienced developer.











Add C# file containing abstraction for EquityLinkedNote and required factories for XML transaction and market data.

// EquityLinkedNote.cs
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.IO;
using System.Data;
using System.Data.OleDb;

namespace CsClient
{
    // interface for all factory classes
    public interface IFactory {
        // return any type
        dynamic Create();
    }

    // XML factory : create equity-linked note from XML file
    public class XMLNoteFactory : IFactory {
        public XMLNoteFactory() {
            // default ctor 
        }
        public dynamic Create() {
            
            // de-serialize equity-linked note object from XML file
            XmlSerializer serializer = new XmlSerializer(typeof(EquityLinkedNote));
            string XMLFilePathName = @CsClient.configurations.SelectSingleNode("configurations/XMLFilePathName").InnerText;
            EquityLinkedNote note = null;
            FileStream stream = File.OpenRead(XMLFilePathName);
            note = (EquityLinkedNote)serializer.Deserialize(stream);
            return note;
        }
    }

    // SQL database factory : create all required market data from database tables
    public class SQLMarketFactory : IFactory {
        public SQLMarketFactory() {
            // default ctor 
        }
        public dynamic Create() {

            // create connection string and SQL queries
            string connectionString = @CsClient.configurations.SelectSingleNode("configurations/connectionString").InnerText;

            // create and open connection
            OleDbConnection connection = new OleDbConnection(connectionString);
            connection.Open();

            // create adapter, request curve data
            OleDbDataAdapter adapter = new OleDbDataAdapter("select * from Curve", connection);
            DataSet dataset = new DataSet();
            adapter.Fill(dataset, "curve");

            // update command, request volatility
            adapter.SelectCommand.CommandText = "select * from Volatility";
            adapter.Fill(dataset, "volatility");

            // update command, request index fixing
            adapter.SelectCommand.CommandText = "select * from Fixings";
            adapter.Fill(dataset, "initialFixing");

            // extract data to curve dictionary
            Dictionary<DateTime, double> curve = new Dictionary<DateTime, double>();
            for (int i = 0; i < dataset.Tables["Curve"].Rows.Count; i++) {
                curve.Add(
                    dataset.Tables["curve"].Rows[i].Field<DateTime>("MaturityDate"), 
                    dataset.Tables["curve"].Rows[i].Field<double>("DiscountFactor"));
            }

            // extract volatility and initial fixing
            double volatility = dataset.Tables["volatility"].Rows[0].Field<double>("Rate");
            double initialFixing = dataset.Tables["initialFixing"].Rows[0].Field<double>("Rate");

            // pack all market data into tuple
            Tuple<Dictionary<DateTime, double>, double, double> market = 
                new Tuple<Dictionary<DateTime,double>,double,double>(curve, volatility, initialFixing);

            // dispose adapter, close connection, return tuple containing market
            adapter.Dispose();
            connection.Close();
            return market;
        }
    }

    // Equity-linked note class : container class for term sheet parameters
    public class EquityLinkedNote {

        public double notional;
        public double cap;
        public double floor;
        public DateTime transactionDate;
        public int settlementDays;
        public string calendar;
        public string dayCountConvention;
        public int requiredSamples;
        public int seed;
        public List<DateTime> fixingDates = null;
        public List<DateTime> paymentDates = null;

        EquityLinkedNote() {
            // default ctor required for XML de-serialization
        }

        public EquityLinkedNote(double notional_, double cap_, double floor_, DateTime transactionDate_, 
            int settlementDays_, string calendar_, string dayCountConvention_, int requiredSamples_, 
            int seed_, List<DateTime> fixingDates_, List<DateTime> paymentDates_) {
            
            notional = notional_;
            cap = cap_;
            floor = floor_;
            transactionDate = transactionDate_;
            settlementDays = settlementDays_;
            calendar = calendar_;
            dayCountConvention = dayCountConvention_;
            requiredSamples = requiredSamples_;
            seed = seed_;
            fixingDates = fixingDates_;
            paymentDates = paymentDates_;
        }
    }
}

Finally, update C# client for creating transaction parameters and market data from configured XML files. Remember to update path to configuration XML file.

// Program.cs
using System;
using System.Collections.Generic;
using System.Xml;

namespace CsClient {
    public static class CsClient {
        public static XmlDocument configurations = null;
        private static string configurationFilePath =
            @"..\configurations.xml";
        private static double result = 0.0;

        public static void Main() {
            try {
                // create configurations
                configurations = new XmlDocument();
                configurations.Load(configurationFilePath);

                // create and use factory to create equity-linked note object from XML file
                IFactory noteFactory = new XMLNoteFactory();
                EquityLinkedNote note = noteFactory.Create();

                // use factory to create market data from database
                IFactory marketDataFactory = new SQLMarketFactory();
                Tuple<Dictionary<DateTime, double>, double, double> market = marketDataFactory.Create();
                Dictionary<DateTime, double> curveData = market.Item1;
                double volatility = market.Item2;
                double initialFixing = market.Item3;

                // use C++/CLI wrapper : create equity-linked note instance
                QLWrapper.EquityLinkedNote wrapperNote =
                    new QLWrapper.EquityLinkedNote(note.notional, note.cap, note.floor,
                        note.transactionDate, note.settlementDays, note.calendar, note.dayCountConvention,
                        note.requiredSamples, note.seed, note.fixingDates, note.paymentDates, initialFixing, volatility, curveData);

                // use C++/CLI wrapper : value equity-linked note
                result = wrapperNote.PV();
                Console.WriteLine(result.ToString());
                GC.SuppressFinalize(wrapperNote);
            }
            catch (Exception e) {
                Console.WriteLine(e.Message);
            }
        }
    }
}

At this point, build should be successfully completed and our new C# client program should return PV for equity-linked note. All transaction parameters and required market data can now be modified, without touching the actual program.

Excel interfacing and Factory of Factories


We will be using Excel as input-output platform for our C# client program. For interfacing task we will use Excel-DNA. Dedicated blog post on how to implement this, can be found in here. However, for the sake of completeness, the whole procedure is also explained below on a detailed level.

First, prepare Excel GUI carefully, by following detailed instructions (range names) given in the screenshot below. For flexible transaction data construction, a specific factory mechanism is implemented by using C# Assembly, Reflection API, and Dynamic data types. Note, that the actual factory is created inside C# program, based on the string given in cell D2 (namespace.class: CsClient.XMLNoteFactory or CsClient.ExcelNoteFactory).

















Add reference to Excel-DNA library (Project - Add reference - Browse - \\ExcelDna.Integration.dll) and click OK. This dll file is inside the distribution folder what has been downloaded from Excel-DNA website. From the properties of this reference, set Copy Local to be False.

Add new file as text file to project (Project - Add new item - Text file) and name it to be CsClient.dna. CopyPaste the following xml code into this file.

<DnaLibrary Name="CsClient" RuntimeVersion="v4.0">
  <ExternalLibrary Path="CsClient.dll" />
</DnaLibrary>

From the properties of this dna file, set Copy to Output Directory to be Copy if newer.

Next, from the downloaded Excel-DNA folder (Distribution), copy ExcelDna.xll file into your project folder and rename it to be CsClient.xll. Then, add this xll file into your current project (Project - Add existing item). At this point, it might be that you do not see anything else, except cs files on this window. From drop down box on the bottom right corner of this window, select All files and you should see ExcelInterface.xll file what we just pasted into this ExcelInterface folder. Select this file and press Add. Finally, from the properties of this xll file, set Copy to Output Directory to be Copy if newer. At this point, we are done with C# and Excel-DNA.

Next, update EquityLinkedNote file for containing Excel factory for Equity-linked note.

// EquityLinkedNote.cs
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.IO;
using System.Data;
using System.Data.OleDb;
using ExcelDna.Integration;

namespace CsClient {
    // interface for all factory classes
    public interface IFactory {
        // return any type
        dynamic Create();
    }

    // Excel factory : create equity-linked note from Excel named ranges
    public class ExcelNoteFactory : IFactory {
        public ExcelNoteFactory() {
            // default ctor 
        }
        public dynamic Create() {

            // create Excel application
            dynamic Excel = ExcelDnaUtil.Application;

            // create all scalar parameters
            double notional = (double)Excel.Range["notional"].Value2;
            double cap = (double)Excel.Range["cap"].Value2;
            double floor = (double)Excel.Range["floor"].Value2;
            DateTime transactionDate = DateTime.FromOADate((double)Excel.Range["transactionDate"].Value2);
            int settlementDays = (int)Excel.Range["settlementDays"].Value2;
            string calendar = (string)Excel.Range["calendar"].Value2;
            string dayCountConvention = (string)Excel.Range["dayCountConvention"].Value2;
            int requiredSamples = (int)Excel.Range["requiredSamples"].Value2;
            int seed = (int)Excel.Range["seed"].Value2;

            // create list of fixing dates
            List<DateTime> fixingDates = new List<DateTime>();
            dynamic fixingDatesArray = Excel.Range["fixingDates"].Value2;
            for (int i = 0; i != fixingDatesArray.GetUpperBound(0); ++i) {
                fixingDates.Add(DateTime.FromOADate(Convert.ToDouble(fixingDatesArray.GetValue(i + 1, 1))));
            }

            // create list of payment dates
            List<DateTime> paymentDates = new List<DateTime>();
            dynamic paymentDatesArray = Excel.Range["paymentDates"].Value2;
            for (int i = 0; i != paymentDatesArray.GetUpperBound(0); ++i) {
                paymentDates.Add(DateTime.FromOADate(Convert.ToDouble(paymentDatesArray.GetValue(i + 1, 1))));
            }

            // create equity-linked note instance
            return new EquityLinkedNote(notional, cap, floor, transactionDate, settlementDays,
                calendar, dayCountConvention, requiredSamples, seed, fixingDates, paymentDates);
        }
    }

    // XML factory : create equity-linked note from XML file
    public class XMLNoteFactory : IFactory {
        public XMLNoteFactory() {
            // default ctor 
        }
        public dynamic Create() {

            // de-serialize equity-linked note object from XML file
            XmlSerializer serializer = new XmlSerializer(typeof(EquityLinkedNote));
            string XMLFilePathName = @CsClient.configurations.SelectSingleNode("configurations/XMLFilePathName").InnerText;
            EquityLinkedNote note = null;
            FileStream stream = File.OpenRead(XMLFilePathName);
            note = (EquityLinkedNote)serializer.Deserialize(stream);
            return note;
        }
    }

    // SQL database factory : create all required market data from database tables
    public class SQLMarketFactory : IFactory {
        public SQLMarketFactory() {
            // default ctor 
        }
        public dynamic Create() {

            // create connection string and SQL queries
            string connectionString = @CsClient.configurations.SelectSingleNode("configurations/connectionString").InnerText;

            // create and open connection
            OleDbConnection connection = new OleDbConnection(connectionString);
            connection.Open();

            // create adapter, request curve data
            OleDbDataAdapter adapter = new OleDbDataAdapter("select * from Curve", connection);
            DataSet dataset = new DataSet();
            adapter.Fill(dataset, "curve");

            // update command, request volatility
            adapter.SelectCommand.CommandText = "select * from Volatility";
            adapter.Fill(dataset, "volatility");

            // update command, request index fixing
            adapter.SelectCommand.CommandText = "select * from Fixings";
            adapter.Fill(dataset, "initialFixing");

            // extract data to curve dictionary
            Dictionary<DateTime, double> curve = new Dictionary<DateTime, double>();
            for (int i = 0; i < dataset.Tables["Curve"].Rows.Count; i++) {
                curve.Add(
                    dataset.Tables["curve"].Rows[i].Field<DateTime>("MaturityDate"),
                    dataset.Tables["curve"].Rows[i].Field<double>("DiscountFactor"));
            }

            // extract volatility and initial fixing
            double volatility = dataset.Tables["volatility"].Rows[0].Field<double>("Rate");
            double initialFixing = dataset.Tables["initialFixing"].Rows[0].Field<double>("Rate");

            // pack all market data into tuple
            Tuple<Dictionary<DateTime, double>, double, double> market =
                new Tuple<Dictionary<DateTime, double>, double, double>(curve, volatility, initialFixing);

            // dispose adapter, close connection, return tuple containing market
            adapter.Dispose();
            connection.Close();
            return market;
        }
    }

    // Equity-linked note class : container class for term sheet parameters
    public class EquityLinkedNote {

        public double notional;
        public double cap;
        public double floor;
        public DateTime transactionDate;
        public int settlementDays;
        public string calendar;
        public string dayCountConvention;
        public int requiredSamples;
        public int seed;
        public List<DateTime> fixingDates = null;
        public List<DateTime> paymentDates = null;

        EquityLinkedNote() {
            // default ctor required for XML de-serialization
        }

        public EquityLinkedNote(double notional_, double cap_, double floor_, DateTime transactionDate_,
            int settlementDays_, string calendar_, string dayCountConvention_, int requiredSamples_,
            int seed_, List<DateTime> fixingDates_, List<DateTime> paymentDates_) {

            notional = notional_;
            cap = cap_;
            floor = floor_;
            transactionDate = transactionDate_;
            settlementDays = settlementDays_;
            calendar = calendar_;
            dayCountConvention = dayCountConvention_;
            requiredSamples = requiredSamples_;
            seed = seed_;
            fixingDates = fixingDates_;
            paymentDates = paymentDates_;
        }
    }
}

Then, update C# client program for consisting interface to Excel and factory of factories. Since we might need to use Windows Forms object in our program (MessageBox in Catch block), we need to create reference to System.Windows.Forms library (Project - Add reference - Assemblies - System.Windows.Forms).

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

namespace CsClient {
    public static class CsClient {
        public static XmlDocument configurations = null;
        private static string configurationFilePath =
            @"..\configurations.xml";
        private static double result = 0.0;

        public static void Run() {
            try {
                // create Excel
                dynamic Excel = ExcelDnaUtil.Application;

                // create configurations
                configurations = new XmlDocument();
                configurations.Load(configurationFilePath);

                // get factory selection from Excel
                string assemblyFile = @configurations.SelectSingleNode("configurations/assemblyFilePathName").InnerText;
                string factorySelection = Excel.Range["factorySelection"].Value2;

                // create and use factory to create equity-linked note object
                dynamic noteFactory = LoadFactory(assemblyFile, factorySelection);
                dynamic note = noteFactory.Create();

                // use factory to create market data from database
                IFactory marketDataFactory = new SQLMarketFactory();
                Tuple<Dictionary<DateTime, double>, double, double> market = marketDataFactory.Create();
                Dictionary<DateTime, double> curveData = market.Item1;
                double volatility = market.Item2;
                double initialFixing = market.Item3;

                // use C++/CLI wrapper : create equity-linked note instance
                QLWrapper.EquityLinkedNote wrapperNote =
                    new QLWrapper.EquityLinkedNote(note.notional, note.cap, note.floor,
                        note.transactionDate, note.settlementDays, note.calendar,
                        note.dayCountConvention, note.requiredSamples, note.seed,
                        note.fixingDates, note.paymentDates, initialFixing, volatility, curveData);

                // use C++/CLI wrapper : value equity-linked note
                result = wrapperNote.PV();
                Excel.Range["pv"] = result;
                GC.SuppressFinalize(wrapperNote);
            }
            catch (Exception e) {
                MessageBox.Show(e.Message);
            }
        }

        // Load requested factory from a given assembly
        public static dynamic LoadFactory(string assemblyFile, string factoryFullName) {
            dynamic factory = null;
            Assembly assembly = Assembly.LoadFrom(assemblyFile);
            Type[] types = assembly.GetTypes();
            foreach (Type type in types) {
                if (type.FullName == factoryFullName) {
                    factory = assembly.CreateInstance(type.FullName);
                    break;
                }
            }
            return factory;
        }

    }
}

Next, update XML configuration file with path name to dll file, which is containing C# project assembly.

<configurations>
    <assemblyFilePathName>..\CsClient.dll</assemblyFilePathName>
    <XMLFilePathName>..\QLWrapper\EquityLinkedNote.xml</XMLFilePathName>
    <connectionString>Provider=Microsoft.ACE.OLEDB.12.0;Data Source=..\Market.accdb;Jet OLEDB:Database Password=MyDbPassword;</connectionString>
</configurations>

Finally, change C# project type to Class Library (Project - CsClient Properties - Application - Output Type - Class Library). At this point we are done with the program part. Open the actual Excel interface workbook, which has been created earlier. Open created CsClient.xll file in the open Excel workbook. For existing action button found in the worksheet, assign macro (name: Run) which will trigger C# program execution.

In this final application, Excel is using external data sources (MS Access, XML) and C# class library, which is using managed C++ code, which is wrapping native C++ code, which uses QuantLib C++ library.

Thanks for reading this blog.
-Mike

No comments:

Post a Comment