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

No comments:

Post a Comment