Wednesday, August 15, 2018

Wilmott : Software Interoperability in Computational Finance, Part I: Foundations for Applications Using C++11 and C# in the .NET Framework



I have been granted an honor to publish our technical paper on Wilmott Quantitative Finance Journal with Daniel J. Duffy. The second paper of this series should be published on September issue, with comprehensive example on calling native C++ QuantLib program from C# and ultimately use that C# program in Excel.

Abstract

This paper is the first in a series of two papers on the design of software systems in computational finance. Both papers are concerned with applications using a combination of C++, C#, and C++/CLI in the Microsoft .NET Framework. In this first paper we discuss how to write and reuse code that has been written using a mixture of native C++, C#, and C++/CLI. In particular, we can call native C++ code from C# and we can call C# code from native C++. The language that makes this level of interoperability possible is C++/CLI.

The actual paper can be found in here. Check it out. Thanks for visiting.
-Mike

Tuesday, June 5, 2018

QuantLib : Dual-Curve Bootstrapping and Swap Valuation

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

The program


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

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

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

Thanks for reading this blog.
-Mike

#include <iostream>
#include <ql\quantlib.hpp>
#include <map>
using namespace QuantLib;

int main() {

 try {

  // create common data 
  Date today(7, Jul, 2017);
  DayCounter dayCounter = Actual360();
  Calendar calendar = TARGET();
  Date settlementDate = calendar.advance(today, Period(2, Days));
  Natural settlementDays = settlementDate - today;
  Settings::instance().evaluationDate() = today;

  // create re-linkable handles for discounting and projection curves
  RelinkableHandle<YieldTermStructure> discountCurve;
  RelinkableHandle<YieldTermStructure> projectionCurve;
  // create container for all rate helpers
  std::vector<boost::shared_ptr<RateHelper>> rateHelpers;

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


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

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

  // create other instruments for eonia curve using ois rate helper
  std::for_each(eoniaSwapData.begin(), eoniaSwapData.end(),
   [settlementDays, &rateHelpers, &eoniaIndex](std::pair<Period, Real> p) -> void 
   { rateHelpers.push_back(boost::make_shared<OISRateHelper>(settlementDays,
   p.first, Handle<Quote>(boost::make_shared<SimpleQuote>(p.second)), eoniaIndex)); });
  
  // create eonia curve
  auto eoniaCurve = boost::make_shared<PiecewiseYieldCurve<Discount, LogLinear>>
   (0, eoniaIndex->fixingCalendar(), rateHelpers, eoniaIndex->dayCounter());  
  eoniaCurve->enableExtrapolation(true);
  // link discount curve to eonia curve
  discountCurve.linkTo(eoniaCurve);

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


Sunday, June 3, 2018

QuantLib : Bootstrapping OIS curve

As we all are aware, all collateralized derivative contracts must be valued by using dual-curve discounting, by using separate curves for cash flow projection and cash flow discounting. While this post does not yet explain how to implement such dual-curve disconting scheme in QuantLib, it shows how to use QuantLib for bootstrapping required OIS discount curve from available market rates. Some further reading material, which nicely explains the justification for the use of dual-curve discounting, is available in here.

The program


First, for the sake of brevity, required market data (Eonia rates) has been hard-coded into map container. Then, container will be iterated through for constructing all required rate helpers. After this, piecewise yield term structure will be created by using constructed rate helpers. Finally, an equally-spaced time grid (monthly) will be created and discount factors are requested from bootstrapped Eonia curve for each grid time point (screenshot below). The curve itself (eurOisCurve) could now be used as a discounting curve in a dual-curve valuation scheme in QuantLib.

















Thanks for reading this blog.
-Mike

#include <iostream>
#include <ql\quantlib.hpp>
#include <algorithm>
#include <map>
using namespace QuantLib;

int main() {

 try {
  // create common data 
  Date today(7, Jul, 2017);
  DayCounter dayCounter = Actual360();
  Calendar calendar = TARGET();
  Date settlementDate = calendar.advance(today, Period(2, Days));
  Natural settlementDays = settlementDate - today;
  Settings::instance().evaluationDate() = today;

  // create overnight index for euro currency
  auto eoniaIndex = boost::make_shared<Eonia>();

  // create container for rate helpers
  std::vector<boost::shared_ptr<RateHelper>> rateHelpers;

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

  // create source data for eonia curve (period, rate)
  std::map<Period, Real> eoniaCurveData;
  eoniaCurveData.insert(std::make_pair(Period(1, Weeks), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(2, Weeks), -0.00357));
  eoniaCurveData.insert(std::make_pair(Period(3, Weeks), -0.00356));
  eoniaCurveData.insert(std::make_pair(Period(1, Months), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(2, Months), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(3, Months), -0.00358));
  eoniaCurveData.insert(std::make_pair(Period(4, Months), -0.00357));
  eoniaCurveData.insert(std::make_pair(Period(5, Months), -0.00356));
  eoniaCurveData.insert(std::make_pair(Period(6, Months), -0.00353));
  eoniaCurveData.insert(std::make_pair(Period(7, Months), -0.00351));
  eoniaCurveData.insert(std::make_pair(Period(8, Months), -0.00349));
  eoniaCurveData.insert(std::make_pair(Period(9, Months), -0.00344));
  eoniaCurveData.insert(std::make_pair(Period(10, Months), -0.0034));
  eoniaCurveData.insert(std::make_pair(Period(11, Months), -0.00336));
  eoniaCurveData.insert(std::make_pair(Period(1, Years), -0.00331));
  eoniaCurveData.insert(std::make_pair(Period(15, Months), -0.00314));
  eoniaCurveData.insert(std::make_pair(Period(18, Months), -0.00295));
  eoniaCurveData.insert(std::make_pair(Period(21, Months), -0.00273));
  eoniaCurveData.insert(std::make_pair(Period(2, Years), -0.00248));
  eoniaCurveData.insert(std::make_pair(Period(3, Years), -0.00138));
  eoniaCurveData.insert(std::make_pair(Period(4, Years), -0.0001245));
  eoniaCurveData.insert(std::make_pair(Period(5, Years), 0.0011945));
  eoniaCurveData.insert(std::make_pair(Period(6, Years), 0.00254));
  eoniaCurveData.insert(std::make_pair(Period(7, Years), 0.00387));
  eoniaCurveData.insert(std::make_pair(Period(8, Years), 0.0052));
  eoniaCurveData.insert(std::make_pair(Period(9, Years), 0.006474));
  eoniaCurveData.insert(std::make_pair(Period(10, Years), 0.007634));
  eoniaCurveData.insert(std::make_pair(Period(11, Years), 0.00868));
  eoniaCurveData.insert(std::make_pair(Period(12, Years), 0.0096045));
  eoniaCurveData.insert(std::make_pair(Period(15, Years), 0.0117245));
  eoniaCurveData.insert(std::make_pair(Period(17, Years), 0.0126797));
  eoniaCurveData.insert(std::make_pair(Period(20, Years), 0.0136245));
  eoniaCurveData.insert(std::make_pair(Period(25, Years), 0.01441));
  eoniaCurveData.insert(std::make_pair(Period(30, Years), 0.01479));

  // create other instruments for eonia curve - using ois rate helper
  std::for_each(eoniaCurveData.begin(), eoniaCurveData.end(),
   [settlementDays, &rateHelpers, &eoniaIndex](std::pair<Period, Real> p) -> void 
   { rateHelpers.push_back(boost::make_shared<OISRateHelper>(settlementDays,
   p.first, Handle<Quote>(boost::make_shared<SimpleQuote>(p.second)), eoniaIndex)); });

  // create piecewise term structure
  Handle<YieldTermStructure> eurOisCurve(boost::make_shared<PiecewiseYieldCurve<Discount, LogLinear>>
   (settlementDays, eoniaIndex->fixingCalendar(), rateHelpers, eoniaIndex->dayCounter()));

  // create equally-spaced (monthly) time grid
  Time maturity = 30.0;
  Size steps = 360;
  TimeGrid grid(maturity, steps);

  // print monthly discount factors
  std::for_each(grid.begin(), grid.end(), [&eurOisCurve](Time t) -> void
   { std::cout << eurOisCurve->discount(t) << std::endl; });

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

Friday, May 11, 2018

C++/CLI Interoperability : Using QuantLib in C#, part II


This post is just a recap, which summarizes the content of two previous posts, published in here and here. It is presenting one possible solution for processing XML-based transactions by using QuantLib C++ native library, via C++/CLI wrapper in C# client. In order to implement the scheme presented here, all instructions given in those above-mentioned posts should be followed carefully.

Pipeline


Even the fanciest restaurant needs to have dedicated staff to visit fresh market and peel potatoes, in order to have everything ready for head chef to prepare delicious late night dinners for us. Similarly, having a "state-of-the-art" analytics library (such as QuantLib) available for calculations is still only "halfway home", since all required data inputs (ex. transactions) need to be constructed beforehand. It is highly preferred for this part of the process to be performed in a way, that maximum re-configurability would be maintained, while manual labour would be completely avoided.

In a nutshell, this post will present, how to go from having several XML transaction configuration files (such as the one example presented below) in a directory

<ZeroCouponBond>
  <transactionType>ZeroCouponBond</transactionType>
  <transactionID>DBS.1057</transactionID>
  <faceAmount>1000000</faceAmount>
  <tradeDate>2018-05-07T00:00:00</tradeDate>
  <settlementDate>2018-05-09T00:00:00</settlementDate>
  <maturityDate>2048-12-16T00:00:00</maturityDate>
  <calendar>SINGAPORE.SGX</calendar>
  <paymentConvention>MODIFIEDFOLLOWING</paymentConvention>
</ZeroCouponBond>

to have all these transactions constructed and processed through QuantLib. Console print below is showing 31 such constructed and processed dummy zero-coupon bond transactions.























C# project


Client

The story here goes roughly as follows. Create QuantLib::FlatForward [Wrapper.QlFlatForward] curve and QuantLib::DiscountingBondEngine [Wrapper.QlDiscountingBondEngine] instances by using C++/CLI wrapper classes. After this, all transactions will be created by Builder.TransactionsBuilder from specific directory, by using XML de-serialization. Instances of QuantLib::ZeroCouponBond [Wrapper.QlZeroCouponBond] objects will be created and paired with existing pricing engine (DiscountingBondEngine). Finally, PV for each transaction will be processed by pricing engine and resulting PV attribute will be stored back to transaction. Resulting information will also being printed back to console.

namespace Client {
    
    using Wrapper;
    using Builder;
    using Library;

    static class Program {
        static void Main(string[] args) {

            try {

                // use C++/CLI wrapper : create QuantLib::FlatForward as discounting curve
                double riskFreeRate = 0.015;
                string dayCounter = "ACTUAL360";
                DateTime settlementDate = new DateTime(2018, 5, 9);
                QlFlatForward discountingCurve = new QlFlatForward(riskFreeRate, dayCounter, settlementDate);

                // use C++/CLI wrapper : create QuantLib::DiscountingBondEngine for pricing
                QlDiscountingBondEngine pricer = new QlDiscountingBondEngine(discountingCurve);

                // use builder to create transactions from directory
                var transactions = TransactionsBuilder.Build();

                // use C++/CLI wrapper : create QuantLib::ZeroCouponBond instruments to list
                var zeroCouponBonds = transactions
                    .Where(t => t.transactionType == "ZeroCouponBond")
                    .Select(t => new QlZeroCouponBond(
                        t.faceAmount, 
                        t.tradeDate, 
                        t.settlementDate,
                        t.maturityDate, 
                        t.calendar, 
                        t.paymentConvention))
                    .ToList<QlZeroCouponBond>();

                // use C++/CLI wrapper : pair created bonds with pricing engine
                zeroCouponBonds.ForEach(z => z.SetPricingEngine(pricer));

                // use C++/CLI wrapper : assign processed pv attributes to transactions
                for(int i = 0; i < zeroCouponBonds.Count; i++) {
                    transactions[i].PV = zeroCouponBonds[i].PV();
                }

                // print information on transactions
                transactions.ForEach(t => {
                    string message = String.Format("ID:{0, -15} Maturity:{1, -15} PV:{2, -10}", 
                        t.transactionID,
                        t.maturityDate.ToShortDateString(),
                        String.Format("{0:0,0}", t.PV));
                    Console.WriteLine(message);
                });

                zeroCouponBonds.ForEach(z => GC.SuppressFinalize(z));
                GC.SuppressFinalize(discountingCurve);
                GC.SuppressFinalize(pricer);

            } 
            catch (Exception e) {
                Console.WriteLine(e.Message);
            }
        }
    }

}

Library


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
 
namespace Library {

    // abstract base class for all transaction types
    // storing only properties common for all transactions
    public abstract class Transaction {

        // no de-serialization for pv attribute
        private double pv;
        public double PV { get { return pv; } set { pv = value; } }

        public string transactionType;
        public string transactionID;

        // default ctor, required for serialization
        public Transaction() {
        }

        // parameter ctor
        public Transaction(string transactionType, string transactionID) {
            this.transactionType = transactionType;
            this.transactionID = transactionID;
        }

    }

    // class for hosting zero-coupon bond term sheet information
    public class ZeroCouponBond : Transaction {

        public double faceAmount;
        public DateTime tradeDate;
        public DateTime settlementDate;
        public DateTime maturityDate;
        public string calendar;
        public string paymentConvention;

        public ZeroCouponBond()
            : base() {
            // required ctor for serialization
        }

        public ZeroCouponBond(string transactionType, string transactionID,
            double faceAmount, DateTime tradeDate, DateTime settlementDate,
            DateTime maturityDate, string calendar, string paymentConvention)
            : base(transactionType, transactionID) {

            this.faceAmount = faceAmount;
            this.tradeDate = tradeDate;
            this.settlementDate = settlementDate;
            this.maturityDate = maturityDate;
            this.calendar = calendar;
            this.paymentConvention = paymentConvention;
        }

    }

}

Builder

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;

namespace Builder {

    public static class SerializerFactory {
        // create generic de-serializer instance from a given transaction type string
        public static dynamic Create(string transactionType) {
            // note : required type string ("Namespace.Classname") is received without namespace string
            Type t = typeof(TransactionSerializer<>).MakeGenericType(Type.GetType(String.Format("Library.{0}", transactionType)));
            return Assembly.GetAssembly(t).CreateInstance(t.FullName);
        }
    }

    // de-serialize given xml file to transaction of type T
    public class TransactionSerializer<T> {
        public T Create(string transactionFilePathName) {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            FileStream stream = File.OpenRead(transactionFilePathName);
            return (T)serializer.Deserialize(stream);
        }
    }

    public static class TransactionsBuilder {

        // return list of transaction instances, de-serialized from xml files 
        public static List<dynamic> Build() {

            // create configurations
            // NOTE : use environmental variable in path construction
            string configurationsFilePathName =
                Path.Combine(Environment.GetEnvironmentVariable("CONFIGURATIONS").ToString(), "configurations.xml");
            XmlDocument configurations = new XmlDocument();
            configurations.Load(configurationsFilePathName);
            string transactionsDirectory =
                configurations.SelectSingleNode("Configurations/TransactionsFilePathName").InnerText.ToString();

            // create transaction file names and empty list for storing transaction instances
            string[] transactionFiles = Directory.GetFiles(transactionsDirectory);
            List<dynamic> transactions = new List<dynamic>();

            // loop through transaction file names
            foreach(string transactionFile in transactionFiles) {

                // create transaction xml file
                XmlDocument transactionXMLDocument = new XmlDocument();
                transactionXMLDocument.Load(transactionFile);

                // investigate transaction type inside file
                string transactionType = (transactionXMLDocument.DocumentElement)["transactionType"].InnerText.ToString();

                // use factory class for creating correct de-serializer
                dynamic factory = SerializerFactory.Create(transactionType);

                // use de-serializer to create transaction instance
                dynamic transaction = factory.Create(transactionFile);
                transactions.Add(transaction);
            }
            return transactions;
        }

    }
}



C++/CLI project


Header file

Note, that in this C++/CLI example project it is assumed, that DiscountingBondEngine is always used for pricing ZeroCouponBond. Moreover, it is assumed, that pricing engine is always using FlatForward term structure for valuation purposes. Needless to say, these assumptions are used here purely for brevity reasons. It is possible to create class hierarchies in C++/CLI wrapper for instruments, pricing engines and term structures. Such scheme would then enable the usage of more realistic valuation scheme, in which client (C# program) would be able to select desired type for valuation curve and pricing engine for some specific instrument.

Moreover, there is currently only one constructor implementation given for each of the classes. In reality, there are always several ways to create these objects (curve, engine, instrument) in QuantLib.

Finally, there is QuantLibConversion namespace, which offers set of static functions for handling different type conversions between C# types and QuantLib types. Needless to say, there are several ways to implement such type conversion scheme, but (IMHO) implementing TypeConverter would have been a bit overkill for this purpose.

// Wrapper.h
#include "QuantLibLibrary.h"
using namespace System;

namespace Wrapper {

 // class for wrapping native QuantLib::FlatForward
 public ref class QlFlatForward {
 public:
  FlatForward* curve_;

  QlFlatForward(double riskFreeRate, String^ dayCounter, DateTime settlementDate);
  ~QlFlatForward();
  !QlFlatForward();
 };


 // class for wrapping native QuantLib::DiscountingBondEngine
 public ref class QlDiscountingBondEngine {
 public:
  DiscountingBondEngine* pricer_;

  QlDiscountingBondEngine(QlFlatForward^ flatForward);
  ~QlDiscountingBondEngine();
  !QlDiscountingBondEngine();
 };


 // class for wrapping native QuantLib::ZeroCouponBond
 public ref class QlZeroCouponBond {
 public:
  ZeroCouponBond* bond_;

  QlZeroCouponBond(double faceAmount,
   DateTime transactionDate,
   DateTime settlementDate,
   DateTime maturityDate,
   String^ calendar,
   String^ paymentConvention);
  ~QlZeroCouponBond();
  !QlZeroCouponBond();

  double PV();
  void SetPricingEngine(QlDiscountingBondEngine^ engine);

 };


}


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

 };

}

Implementation file

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

namespace Wrapper {
 using Ql = QuantLibConversions::Convert;


 // implementations for yield curve class
 QlFlatForward::QlFlatForward(double riskFreeRate, String^ dayCounter, DateTime settlementDate) {

  DayCounter dayCounter_ = Ql::ToDayCounter(dayCounter);
  Date settlementDate_ = Ql::ToDate(settlementDate);
  Handle<Quote> riskFreeRateHandle_(boost::make_shared<SimpleQuote>(riskFreeRate));
  curve_ = new FlatForward(settlementDate_, riskFreeRateHandle_, dayCounter_);
 }

 QlFlatForward::~QlFlatForward() {
  this->!QlFlatForward();
 }

 QlFlatForward::!QlFlatForward() {
  delete curve_;
 }



 // implementations for zero bond class
 QlZeroCouponBond::QlZeroCouponBond(double faceAmount,
  DateTime transactionDate,
  DateTime settlementDate,
  DateTime maturityDate,
  String^ calendar,
  String^ paymentConvention) {

  Date transactionDate_ = Ql::ToDate(transactionDate);
  Date settlementDate_ = Ql::ToDate(settlementDate);
  Date maturityDate_ = Ql::ToDate(maturityDate);

  Calendar calendar_ = Ql::ToCalendar(calendar);
  BusinessDayConvention paymentConvention_ = Ql::ToBusinessDayConvention(paymentConvention);
  Natural settlementDays_ = settlementDate_ - transactionDate_;
  bond_ = new ZeroCouponBond(settlementDays_, calendar_, faceAmount, maturityDate_, paymentConvention_);
 }

 QlZeroCouponBond::~QlZeroCouponBond() {
  this->!QlZeroCouponBond();
 }

 QlZeroCouponBond::!QlZeroCouponBond() {
  delete bond_;
 }

 double QlZeroCouponBond::PV() {
  return bond_->NPV();
 }

 void QlZeroCouponBond::SetPricingEngine(QlDiscountingBondEngine^ engine) {
  bond_->setPricingEngine(static_cast<boost::shared_ptr<DiscountingBondEngine>>(engine->pricer_));
 }



 // implementations for zero pricer class
 QlDiscountingBondEngine::QlDiscountingBondEngine(QlFlatForward^ flatForward) {
  
  Handle<YieldTermStructure> discountCurveHandle(static_cast<boost::shared_ptr<FlatForward>>(flatForward->curve_));
  pricer_ = new DiscountingBondEngine(discountCurveHandle);
  Settings::instance().evaluationDate() = flatForward->curve_->referenceDate();
 }

 QlDiscountingBondEngine::~QlDiscountingBondEngine() {
  this->!QlDiscountingBondEngine();
 }

 QlDiscountingBondEngine::!QlDiscountingBondEngine() {
  delete pricer_;
 }


}


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

}

As always, thanks for reading this blog.
-Mike

Saturday, April 28, 2018

C# : Building Transactions from XML files

In my previous post, I presented the following Main program for creating just one set of transaction parameters for a zero-coupon bond, to be delivered further for QuantLib C++/CLI wrapper class.

using System;

namespace Client {

    static class ZeroCouponBond {
        static void Main() {
            try {

                // create transaction parameters
                double riskFreeRate = 0.01;
                double faceAmount = 1000000.0;
                DateTime transactionDate = new DateTime(2018, 4, 16);
                DateTime maturityDate = new DateTime(2020, 4, 16);
                string calendar = "TARGET";
                string daycounter = "ACT360";
                int settlementDays = 2;
                
                // use C++/CLI wrapper : create bond, request pv
                QuantLibCppWrapper.MJZeroCouponBondWrapper zero = new QuantLibCppWrapper.MJZeroCouponBondWrapper(
                    riskFreeRate, faceAmount, transactionDate, maturityDate, calendar, daycounter, settlementDays);
                double PV = zero.PV();
                Console.WriteLine(PV.ToString());
                GC.SuppressFinalize(zero);
            }
            catch (Exception e) {
                Console.WriteLine(e.Message);
            }
        }
    }
}

Needless to say, in real life we would need to have a bit more flexible system for creating transaction data. In this post, I am opening one possible scheme for creating transaction data for any type of transaction directly from XML files, using de-serialization. A notable issue in this scheme is, that client program does not implement any kind of pricing model, since the valuation part has completely been outsourced for C++/CLI QuantLib wrapper class (or to some other pricing library). Client program is only creating and hosting transaction-related data and sending calls for wrapper class, when requesting PV for a specific transaction.

UML


The following class diagram is roughly presenting the scheme for building transactions.


In order to create transaction instances, Client program (Main) is using TransactionsBuilder class, which ultimately returns a list of Transaction instances. Transaction is abstract base class for all possible transaction types. This class does not provide any methods, but is merely hosting (in this scheme) properties which are common for all transactions: transaction ID, transaction type information and transaction PV. All concrete transaction implementations, such as ZeroCouponBond class, will be inherited from this base class. Now, there is a lot more than meets the eye inside Builder namespace and we will definitely get into some specifics later.

Library


This namespace is consisting of class implementations for all possible transaction types. It should be implemented entirely into a new C# console project.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;

namespace Library {

    // abstract base class for all possible transaction types
    // storing only properties common for all transactions
    public abstract class Transaction {

        // no de-serialization for pv attribute
        private double pv;
        public double PV { get { return pv; } set { pv = value; } }

        public string transactionType;
        public string transactionID;

        // default ctor, required for serialization
        public Transaction() {
        }

        // parameter ctor
        public Transaction(string transactionType, string transactionID) {
            this.transactionType = transactionType;
            this.transactionID = transactionID;
        }

    }

    // class for hosting zero-coupon bond term sheet information
    public class ZeroCouponBond : Transaction {

        public double faceAmount;
        public DateTime transactionDate;
        public DateTime maturityDate;
        public string calendar;
        public string daycounter;
        public int settlementDays;

        public ZeroCouponBond()
            : base() {
            // required ctor for serialization
        }

        public ZeroCouponBond(string transactionType, string transactionID,
            double faceAmount, DateTime transactionDate, DateTime maturityDate,
            string calendar, string daycounter, int settlementDays)
            : base(transactionType, transactionID) {

            this.faceAmount = faceAmount;
            this.transactionDate = transactionDate;
            this.maturityDate = maturityDate;
            this.calendar = calendar;
            this.daycounter = daycounter;
            this.settlementDays = settlementDays;
        }

    }

    // class for hosting equity-linked note term sheet information
    public class EquityLinkedNote : Transaction {

        public double notional;
        public double cap;
        public double floor;
        public int settlementDays;
        public bool useAntitheticVariates;
        public int requiredSamples;
        public int maxSamples;
        public int seed;
        public List<DateTime> fixingDates = null;
        public List<DateTime> paymentDates = null;

        public EquityLinkedNote()
            : base() {
            // required ctor for serialization
        }

        public EquityLinkedNote(string transactionType, string transactionID,
            double notional, double cap, double floor, int settlementDays,
            bool useAntitheticVariates, int requiredSamples,
            int maxSamples, int seed, List<DateTime> fixingDates,
            List<DateTime> paymentDates)
            : base(transactionType, transactionID) {

            this.notional = notional;
            this.cap = cap;
            this.floor = floor;
            this.settlementDays = settlementDays;
            this.useAntitheticVariates = useAntitheticVariates;
            this.requiredSamples = requiredSamples;
            this.maxSamples = maxSamples;
            this.seed = seed;
            this.fixingDates = fixingDates;
            this.paymentDates = paymentDates;
        }
    }

}

After a short review, it should be clear that these implementation classes are nothing more, but wrappers for hosting heterogeneous sets of transaction-specific data members.

Builder


TransactionBuilder (plus a couple of other helper classes) is living in Builder namespace. This content should also be implemented into existing C# console project.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;

namespace Builder {

    public static class SerializerFactory {        
        // create generic de-serializer instance from a given transaction type string
        public static dynamic Create(string transactionType) {
            // note : required type string ("Namespace.Classname") is received without namespace string
            Type t = typeof(TransactionSerializer<>).MakeGenericType(Type.GetType(String.Format("Library.{0}", transactionType)));
            return Assembly.GetAssembly(t).CreateInstance(t.FullName);
        }
    }

    // de-serialize given xml file to transaction of type T
    public class TransactionSerializer<T> {
        public T Create(string transactionFilePathName) {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            FileStream stream = File.OpenRead(transactionFilePathName);
            return (T)serializer.Deserialize(stream);
        }
    }

    public static class TransactionsBuilder {

        // return list of transaction instances, de-serialized from xml files 
        public static List<dynamic> Build() {

            // create configurations
            // NOTE : use environmental variable in path construction
            string configurationsFilePathName =
                Path.Combine(Environment.GetEnvironmentVariable("CONFIGURATIONS").ToString(), "configurations.xml");
            XmlDocument configurations = new XmlDocument();
            configurations.Load(configurationsFilePathName);
            string transactionsDirectory =
                configurations.SelectSingleNode("Configurations/TransactionsFilePathName").InnerText.ToString();

            // create transaction file names and empty list for storing transaction instances
            string[] transactionFiles = Directory.GetFiles(transactionsDirectory);
            List<dynamic> transactions = new List<dynamic>();

            // loop through transaction file names
            foreach(string transactionFile in transactionFiles) {

                // create transaction xml file
                XmlDocument transactionXMLDocument = new XmlDocument();
                transactionXMLDocument.Load(transactionFile);

                // investigate transaction type inside file
                string transactionType = (transactionXMLDocument.DocumentElement)["transactionType"].InnerText.ToString();

                // use factory class for creating correct de-serializer
                dynamic factory = SerializerFactory.Create(transactionType);

                // use de-serializer to create transaction instance
                dynamic transaction = factory.Create(transactionFile);
                transactions.Add(transaction);
            }
            return transactions;
        }

    }
}

Let us briefly go through, what is happening here. Ultimately, Build-method of TransactionsBuilder class is returning a list of Transaction instances (as dynamic) to its caller.

In the beginning of this method, all program-specific configurations are read from specific XML file. Based on created configurations, transaction XML files will then be loaded from specific directory, on a sequential manner. For each loaded transaction file, type string will be sniffed from inside file and the correct de-serializer instance (factory) will be created, based on that type string. The entity, which is creating this correct de-serializer instance, is SerializerFactory class. Finally, factory instance is used to de-serialize XML file to correct Transaction instance by using TransactionSerializer<T> class.

Required template parameter T for TransactionSerializer<T> class is constructed directly from a given transaction type string, by using MakeGenericType method. The actual de-serializer instance will be created from a given assembly by using CreateInstance method.

Client


Client namespace content is presented below. Also, this content should be implemented into existing C# console project.

namespace Client {
    using Library;
    using Builder;

    static class Program {
        static void Main(string[] args) {

            try {
                // 1. input
                // use transaction builder class for creating all transaction instances from xml files
                List<dynamic> transactions = TransactionsBuilder.Build();

                // 2. processing
                // process PV for all transactions
                // in real-life scenario, we could create calls in here for some pricing library 
                // and store processed valuation results to transaction PV attribute

                // select all zero-coupon bonds and modify PV attribute
                List<ZeroCouponBond> zeros =
                    transactions.Where(x => x.transactionType == "ZeroCouponBond")
                    .Select(y => (ZeroCouponBond)y).ToList<ZeroCouponBond>();
                zeros.ForEach(it => it.PV = 987654.32);

                // select all equity-linked notes and modify PV attribute
                List<EquityLinkedNote> notes =
                    transactions.Where(x => x.transactionType == "EquityLinkedNote")
                    .Select(y => (EquityLinkedNote)y).ToList<EquityLinkedNote>();
                notes.ForEach(it => it.PV = 32845.93);

                // 3. output
                // finally, print ID, type and PV for all transactions
                transactions.ForEach(it => Console.WriteLine
                    (String.Format("id:{0}, type:{1}, pv:{2}",
                    it.transactionID, it.transactionType, it.PV)));
            } 
            catch (Exception e) {
                Console.WriteLine(e.Message);
            }
        }
    }
}

The story in Main goes roughly as follows.
  1. Create all transactions instances from specific directory, by using XML de-serialization. 
  2. As a "service request", use created transaction instances for feeding transaction parameters for corresponding wrapper method (say, ZeroCouponBond instance is used for feeding MJZeroCouponBondWrapper). 
  3. Receive PV from wrapper method and store this result back to transaction PV attribute.
Transaction instances (constructed by TransactionsBuilder) can be investigated in Locals window as presented in the following screenshot.



















Finally, make sure to carefully implement all required configurations, which are listed below.

XML configurations


ZeroCouponBond

<ZeroCouponBond>
  <transactionType>ZeroCouponBond</transactionType>
  <transactionID>HSBC.1028</transactionID>
  <faceAmount>1000000</faceAmount>
  <transactionDate>2018-04-24T00:00:00</transactionDate>
  <maturityDate>2020-04-24T00:00:00</maturityDate>
  <calendar>TARGET</calendar>
  <daycounter>ACT360</daycounter>
  <settlementDays>2</settlementDays>
</ZeroCouponBond>

EquityLinkedNote

<EquityLinkedNote>
  <transactionType>EquityLinkedNote</transactionType>
  <transactionID>NORDEA.3866</transactionID>
  <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>ACT360</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>

Program configurations

<Configurations>
  <TransactionsFilePathName>C:\temp\transactions</TransactionsFilePathName>
</Configurations>

For setting environmental variables, a great tutorial can be found in here. Finally, configuring a new transaction in this scheme should be relatively easy. First, a new XML file for hosting specific set of transaction-related parameters (plus two parameters common for all transactions: ID and type) should be created. Secondly, a new class implementation for hosting these transaction-related parameters should be implemented into Library namespace. Finally (outside of this assembly), there should be method implementation available in wrapper for this new transaction (for calculating PV for a given set of transaction parameters). Job done.

As always, Thanks for reading this blog. Happy First of May season for everybody.
-Mike

Sunday, April 22, 2018

C++/CLI Interoperability : Using QuantLib in C#

Assume the following scenario : there is QuantLib program available, written in native C++ and you would like to use it from your C# program. What would you do? Now, even there are several extensions available, in this post we will implement specific scheme of using native C++ program in C# via C++/CLI wrapper program. The scheme presented here is widely known as Adapter design pattern.

Our native C++ program is using QuantLib libraries for creating custom implementations for instrument and pricing engine (zero-coupon bond). C++/CLI is then wrapping this native C++ program and exposing only selected methods for its client (C# program). For those completely new to C++/CLI language, there is a relatively comprehensive tutorial available here, written by Adam Sawicki. Needless to say, even this post is presenting relatively simple example of using QuantLib via wrapper, one is able to apply presented scheme to much more complex valuation scenarios. Sounds like a cliche, but "only the sky is the limit" applies well in here.

As far as I see, a programmer is facing complexities here on two fronts : on a pure program level (C++ language, QuantLib library) and on technical level (handling projects and their configurations specifically on Visual Studio). I assume, that the reader is already familiar with C++ language (including its modern features) and has experience on using QuantLib library. Now, in order to decrease the learning curve on that technical level, I decided to include all possible configurations-related steps in here. This means, that when all the steps described in this post have been implemented on a priestly manner, one should end up with succesfully compiled projects. In order to give some concrete back-up for this claim, I have specifically re-created this project succesfully from the scratch now two times.


C++/CLI wrapper project


Create a new C++ CRL Class library project. At this point, pre-installed and pre-built QuantLib and Boost libraries should be available. Create references to required Boost and QuantLib header files and libraries as follows.









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.

Next, add the following two new header files to this project.

Native C++ header file.

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

namespace QuantLibCppNative {

 // implementation for zero-coupon bond instrument
 class MJZeroCouponBond : public Instrument {
 public:
  // forward class declarations
  class arguments;
  class engine;
  //
  // ctor and implementations for required base class methods
  MJZeroCouponBond(Real faceAmount, Date maturityDate);
  bool isExpired() const;
 private:
  void setupArguments(PricingEngine::arguments* args) const;
  // term sheet related information
  Real faceAmount_;
  Date maturityDate_;
 };

 // inner arguments class
 class MJZeroCouponBond::arguments : public PricingEngine::arguments{
 public:
  void validate() const;
  Real faceAmount;
  Date maturityDate;
 };

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

 // implementation for base class engine
 class MJZeroCouponBondEngine : public MJZeroCouponBond::engine {
 public:
  MJZeroCouponBondEngine(const Handle<YieldTermStructure>& curve);
  void calculate() const;
 private:
  Handle<YieldTermStructure> curve_;
 };

}

C++/CLI header file.

// Wrapper.h
#pragma once
#include "MJZeroCouponBond.h"
using namespace System;

namespace QuantLibCppWrapper {

 // C++/CLI wrapper class for native C++ QuantLib
 public ref class MJZeroCouponBondWrapper {
 public:
  MJZeroCouponBondWrapper(double riskFreeRate_, double faceAmount_, DateTime transactionDate_,
   DateTime maturityDate_, String^ calendar_, String^ daycounter_, int settlementDays_);
  !MJZeroCouponBondWrapper();
  ~MJZeroCouponBondWrapper();
  double PV();
 private:
  // note : class members as native pointers
  QuantLibCppNative::MJZeroCouponBond* bond;
  QuantLibCppNative::MJZeroCouponBondEngine* pricer;
 };
}

Next, add the following two implementation files to this project.

Native C++ implementation file.

// MJZeroCouponBond.cpp
#include "MJZeroCouponBond.h"
using namespace QuantLib;

namespace QuantLibCppNative {

 // implementations for MJZeroCouponBond instrument
 MJZeroCouponBond::MJZeroCouponBond(Real faceAmount, Date maturityDate)
  : faceAmount_(faceAmount), maturityDate_(maturityDate) { // ctor
 }

 bool MJZeroCouponBond::isExpired() const {
  return Settings::instance().evaluationDate() >= maturityDate_;
 }

 void MJZeroCouponBond::setupArguments(PricingEngine::arguments* args) const {
  MJZeroCouponBond::arguments* args_ = dynamic_cast<MJZeroCouponBond::arguments*>(args);
  QL_REQUIRE(args_ != nullptr, "arguments casting error");
  args_->faceAmount = faceAmount_;
  args_->maturityDate = maturityDate_;
 }

 void MJZeroCouponBond::arguments::validate() const {
  QL_REQUIRE(faceAmount > 0.0, "transaction face amount cannot be zero");
 }

 // implementations for MJZeroCouponBondEngine
 MJZeroCouponBondEngine::MJZeroCouponBondEngine(const Handle<YieldTermStructure>& curve)
  : curve_(curve) {
  // register observer (MJZeroCouponBondEngine) with observable (curve)
  registerWith(curve_);
 }

 void MJZeroCouponBondEngine::calculate() const {
  // extract required parameters from arguments or curve object
  // implement the actual pricing algorithm and store result
  Date maturityDate = arguments_.maturityDate;
  Real P = arguments_.faceAmount;
  DiscountFactor df = curve_->discount(maturityDate);
  Real PV = P * df;
  results_.value = PV;
 }

}

C++/CLI implementation file.

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

namespace QuantLibCppWrapper {

 // ctor
 MJZeroCouponBondWrapper::MJZeroCouponBondWrapper(double riskFreeRate_, double faceAmount_, DateTime transactionDate_,
  DateTime maturityDate_, String^ calendar_, String^ daycounter_, int settlementDays_) {

  // conversion for calendar (string to QuantLib::Calendar)
  Calendar calendar;
  if (calendar_->ToUpper() == "TARGET") calendar = TARGET();
   else throw gcnew System::Exception("undefined calendar");
  
  // conversion for daycounter (string to QuantLib::DayCounter)
  DayCounter dayCounter;
  if (daycounter_->ToUpper() == "ACT360") dayCounter = Actual360();
   else throw gcnew System::Exception("undefined daycounter");

  // conversion : transaction and maturity dates (Datetime to QuantLib::Date)
  Date transactionDate(transactionDate_.ToOADate());
  Date maturityDate(maturityDate_.ToOADate());
  Date settlementDate = calendar.advance(transactionDate, Period(settlementDays_, Days));
  Settings::instance().evaluationDate() = settlementDate;

  // create flat discount curve
  auto riskFreeRate = boost::make_shared<SimpleQuote>(riskFreeRate_);
  Handle<Quote> riskFreeRateHandle(riskFreeRate);
  auto riskFreeRateTermStructure = boost::make_shared<FlatForward>(settlementDate, riskFreeRateHandle, dayCounter);
  Handle<YieldTermStructure> riskFreeRateTermStructureHandle(riskFreeRateTermStructure);

  // create instrument and pricer as native pointers
  bond = new QuantLibCppNative::MJZeroCouponBond(faceAmount_, maturityDate);
  pricer = new QuantLibCppNative::MJZeroCouponBondEngine(riskFreeRateTermStructureHandle);

  // pair instrument and pricer
  // note : cast pricer from native pointer to boost shared pointer
  bond->setPricingEngine(static_cast<boost::shared_ptr<QuantLibCppNative::MJZeroCouponBondEngine>>(pricer));
 }

 // finalizer
 MJZeroCouponBondWrapper::!MJZeroCouponBondWrapper() {
  delete bond;
  delete pricer;
 }

 // destructor
 MJZeroCouponBondWrapper::~MJZeroCouponBondWrapper() {
  this->!MJZeroCouponBondWrapper();
 }

 // return pv
 double MJZeroCouponBondWrapper::PV() {
  return bond->NPV();
 }

}

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.









After these steps, I have completed a succesfull built for my C++/CLI project.








C# client project


First, create a new C# console project into the existing solution.

Then, add reference to previously created C++/CLI project.








Next, implement the following program to C# project.

using System;

namespace Client {

    static class ZeroCouponBond {
        static void Main() {
            try {

                // create parameters
                double riskFreeRate = 0.01;
                double faceAmount = 1000000.0;
                DateTime transactionDate = new DateTime(2018, 4, 16);
                DateTime maturityDate = new DateTime(2020, 4, 16);
                string calendar = "TARGET";
                string daycounter = "ACT360";
                int settlementDays = 2;
                
                // use C++/CLI wrapper : create bond, request pv
                QuantLibCppWrapper.MJZeroCouponBondWrapper zero = new QuantLibCppWrapper.MJZeroCouponBondWrapper(
                    riskFreeRate, faceAmount, transactionDate, maturityDate, calendar, daycounter, settlementDays);
                double PV = zero.PV();
                Console.WriteLine(PV.ToString());
                GC.SuppressFinalize(zero);
            }
            catch (Exception e) {
                Console.WriteLine(e.Message);
            }
        }
    }
}

Finally, set C# project as start-up project (on Visual Studio, right-click selected C# project and select "Set as StartUp Project").

After completing all previous steps, I have completed a succesfull built for the both projects and got the result of 979953.65 as present value for this 2-year zero-coupon bond, evaluated by using our native C++ QuantLib program via C++/CLI wrapper class.








At this point, we are done.

Postlude : no sweeping under the carpet allowed


Clever eyes might be catching, that there is a SuppressFinalize method call made for Garbage Collector at the end of our C# program. If I will remove that call, I will get the following exception.






Now, the both destructor and finalizer in our C++/CLI project have been correctly implemented, as C++/CLI standard is suggesting. As I understand, the issue is coming from C# program side, as Garbage Collector will do its final memory release sweep before exit. The issue has been pretty completely chewed in here, here and here.

Interesting point is, that in the last given reference above, the author is actually suggesting to delete dynamically allocated member variables (bond and pricer) in destructor. Now, if I will modify that C++/CLI program accordingly as suggested (delete member variables in destructor, not in finalizer, remove trigger call from destructor to finalizer, remove method call for Garbage Collector in C# program), this program will work again as expected. By taking a look at author's past experience, at least I would come to the conclusion, that this suggested approach is also a way to go.

Finally, thanks a lot again for using your precious time for reading this blog. I hope you got what you were looking for.
-Mike