Showing posts with label Interoperability. Show all posts
Showing posts with label Interoperability. Show all posts

Monday, February 18, 2019

C#/Python: Creating Python Wrapper for C# Class by Using Python for .NET

Interoperability is just amazing concept. Sometimes, instead re-inventing the wheel again for a new language, it might be easier to recycle the old stuff. Creating Python wrapper for C++ program was presented in previous post. This post will present simple steps for creating Python wrapper for a simple C#.NET class.

Python for .NET

Python for .NET is a package, that gives Python programmers nearly seamless integration with .NET Common Language Runtime. With this package, you can script .NET applications or build entire applications in Python, using .NET services and components written in any language that targets the CLR (C#, VB.NET, F#, C++/CLI). Download package from here.

Create C# class

In Visual Studio, create a new C# class library project under the namespace CsLibrary. Create C# class Functions into this class project. The content of this class is presented below.

using System;

namespace CsLibrary
{
    // C# class
    public class Functions
    {

        public double Add(double a, double b) {
            return a + b;
        }

        public double Subtract(double a, double b) {
            return a - b;
        }

        public double Multiply(double a, double b) {
            return a * b;
        }

        public double Divide(double a, double b) {
            return a / b;
        }

    }
}

Create wrapper for C# class

In order to access this class from Python, we need to have wrapper class, which inherits from DynamicObject. Add the following new class to existing solution. Effectively, this class is just object adapter, which delegates function calls to our previous C# class.

using System;
using System.Dynamic;

namespace CsLibrary {

    public class PyWrapper : DynamicObject {

        // wrapped C# class
        private Functions functions;

        // ctor
        public PyWrapper() {
            functions = new Functions();
        }        
        
        public double Add(double a, double b) {
            return functions.Add(a, b);
        }

        public double Subtract(double a, double b) {
            return functions.Subtract(a, b);
        }

        public double Multiply(double a, double b) {
            return functions.Multiply(a, b);
        }

        public double Divide(double a, double b) {
            return functions.Divide(a, b);
        }
    }
}

Use wrapper class in Python

The following example program is using C# class wrapper (CsLibrary.dll) in Python.

import clr
clr.AddReference(r'..\CsLibrary\bin\Release\CsLibrary.dll')
from CsLibrary import PyWrapper
wrapper = PyWrapper()

x = 1.23
y = 2.34

print(wrapper.Add(x, y))
print(wrapper.Subtract(x, y))
print(wrapper.Multiply(x, y))
print(wrapper.Divide(x, y))

# 3.57
# -1.1099999999999999
# 2.8781999999999996
# 0.5256410256410257

That's it. The files can be downloaded from my GitHub page. Thanks for reading my blog.
-Mike

Sunday, February 10, 2019

C++/Python: Creating Python Wrapper for C++ Class by Using SWIG

If the need sometimes arises, existing C++ libraries can be interfaced relatively easy to be used in Python by using SWIG wrapper. SWIG stands for Simplified Wrapper and Interface Generator and it is an open-source software tool used to connect computer programs or libraries written in C or C++ with scripting languages, such as Python. As one extremely motivating example, QuantLib C++ libraries have been made available for Python by using SWIG wrappers. Complete documentation for SWIG 3.0 can be found in here. The actual SWIG package can be downloaded in here. By taking a look at the documentation, one may understand quickly, that in this post only the tip of the iceberg will get scratched.

Also, it should be noted at this point, that this post is assuming all operations taking place in Linux environment. Corresponding instructions for Windows users can be found in SWIG documentation. This blog post is presenting, how to interface custom C++ random generator class to be used in Python. The original (template) class implementation can be found in here.

Header and implementation files for simple C++ random number generator class are presented below.

Header file (RG.h)

#include <algorithm>
#include <functional>
#include <random>

class Generator {
public:
 Generator(unsigned long seed);
 void operator()(std::vector<double>& v);
private:
 std::function<double(double)> randomGenerator;
 std::normal_distribution<double> distribution;
 std::mt19937 uniformGenerator;
};

Implementation file (RG.cpp)

#include "RG.h"

Generator::Generator(unsigned long seed) {
  // construct lambda method for processing standard normal random number
  randomGenerator = [this](double x)-> double {
   x = distribution(uniformGenerator);
   return x;
  };
  uniformGenerator.seed(seed);
}

// fill client-given vector with random variates from standard normal distribtuion
void Generator::operator()(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(), randomGenerator);
}

The class functionality is simple and straightforward. First, create a class implementation by giving desired seed value in constructor. In constructor, C++ function object will be created. This function object will ultimately create our normally distributed random variates. Client will request a new set of random variates by giving a reference for STL vector by using parenthesis operator, which has been overloaded here for syntactic reasons only.

SWIG interface file (RG.i)

A simple SWIG interface for our class can be built by simply wrapping the header file as follows.

%module RG
%{
#include "RG.h"
%}

%include "std_vector.i"
%template(DoubleVector) std::vector<double>;

%include "RG.h"

Python does not know anything about std::vector. The std_vector.i library provides support for C++ STL vector class. Then, all we need to do is to instantiate different versions of vector for all the specific types we would like to use in Python. For our example class, we want to use vector consisting of double data types. Using this library involves the use of the %template directive. In Python code, C++ vector will be used by using alias DoubleVector.

SWIG build

All in all we have now three files in our folder: RG.h, RG.cpp and RG.i. With terminal opened in the folder consisting the previous three files, the following three commands will create Python wrapper for our C++ class.

$ swig -c++ -python RG.i
$ g++ -fpic -c -I/home/mikejuniperhill/anaconda3/include/python3.7m RG.cpp RG_wrap.cxx
$ g++ -shared RG.o RG_wrap.o -o _RG.so

In the second command, a correct parameter for -I argument can be found by using the following command in terminal.

$ python3-config --cflags

After these three steps, our C++ program can be accessed and used in Python program. The following simple example program is requesting a vector of standard normal random numbers from our wrapped C++ class and uses those for calculating Monte Carlo PV for one European call option.

# import RG module
import SwigTester.RG as RG
import numpy as np

# set seed, create generator object
seed = 0
generator = RG.Generator(seed)

# set vector size, initialize vector
steps = 1000
vector = RG.DoubleVector(steps)

# request generator to fill vector with standard normal variates 
generator(vector)
# copy content to numpy array
e = np.array(vector)

# use variates to value one european call option
# set parameters
s0 = 100.0
x = 100.0
t = 1.25
v = 0.25
r = 0.01

# use vectorization in numpy, pre-calculate components
drift = (r - 0.5 * v * v) * t
diffusion = v * np.sqrt(t)
df = np.exp(-r * t)

# get option value as discounted average of all terminal payoffs
c0 = np.mean(np.maximum(s0 * np.exp(drift + diffusion * e) - x, 0.0)) * df
print(c0)

Note, that I imported SwigTester.RG, because I created Python wrapper module directly into this specific folder.

As a SWIG newcomer, one may expect to face all kinds of cryptic installation and other infuriating compatibility issues. As always, Google is the best friend along this cruel and unusual phase. But, have faith - and you'll be rewarded with all wonders and joys of SWIG wrapper.

All relevant files can be found directly from my GitHub repository. Thanks for reading my blog.
-Mike



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