Showing posts with label Object-oriented Programming. Show all posts
Showing posts with label Object-oriented Programming. Show all posts

Friday, December 29, 2017

QuantLib : implementing Equity-linked note using Monte Carlo framework

Within the last post, an implementation for a simple custom instrument and analytical pricing engine was presented. This post is presenting a bit more complex implementation for an equity-linked note, using custom pricing engine implementation built on the top of QuantLib Monte Carlo framework.


Term sheet


The most essential transaction details have been presented within the following screenshot below.

























In a nutshell, 3-year holding period has been divided into three annual periods. For each period, running cumulative (but capped) coupon will be calculated based on simulated index fixing values (Geometric Brownian Motion). At the end of each period, period payoff (floored) will be calculated. Pricing-wise, this period payoff will then be discounted to present valuation date. Structure total PV is the sum of all discounted period payoffs.

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

-Mike


// EquityLinkedNote.h
#pragma once
#include <ql/quantlib.hpp>
using namespace QuantLib;
//
// 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_;
};
//
//
//
//
//
// EquityLinkedNote.cpp
#include "EquityLinkedNote.h"
#include <algorithm>
//
// 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;
}
//
//
//
//
//
// main.cpp
#include "EquityLinkedNote.h"
//
int main() {
 try {
  // common data : calendar, daycounter, dates
  Calendar calendar = TARGET();
  DayCounter dayCounter = Actual360();
  Date transactionDate(30, October, 2017);
  Natural settlementDays = 2;
  Date settlementDate = calendar.advance(transactionDate, Period(settlementDays, Days));
  Settings::instance().evaluationDate() = settlementDate;
  //
  // term sheet parameters
  Real notional = 1000000.0;
  Real initialFixing = 3662.18;
  Real cap = 0.015;
  Real floor = 0.0;
  std::vector<Date> fixingDates {
   Date(30, November, 2017), Date(30, December, 2017), Date(30, January, 2018), 
   Date(28, February, 2018), Date(30, March, 2018), Date(30, April, 2018),
   Date(30, May, 2018), Date(30, June, 2018), Date(30, July, 2018), 
   Date(30, August, 2018), Date(30, September, 2018), Date(30, October, 2018),
   Date(30, November, 2018), Date(30, December, 2018), Date(30, January, 2019), 
   Date(28, February, 2019), Date(30, March, 2019), Date(30, April, 2019),
   Date(30, May, 2019), Date(30, June, 2019), Date(30, July, 2019), 
   Date(30, August, 2019), Date(30, September, 2019), Date(30, October, 2019),
   Date(30, November, 2019), Date(30, December, 2019), Date(30, January, 2020), 
   Date(29, February, 2020), Date(30, March, 2020), Date(30, April, 2020),
   Date(30, May, 2020), Date(30, June, 2020), Date(30, July, 2020), 
   Date(30, August, 2020), Date(30, September, 2020), Date(30, October, 2020)
  };
  std::vector<Date> paymentDates {
   Date(30, October, 2018), Date(30, October, 2019), Date(30, October, 2020)
  };
  //
  // create structured equity-linked note
  auto note = boost::make_shared<EquityLinkedNote>(notional, initialFixing, fixingDates, paymentDates, cap, floor);
  //
  // market data
  // create discount curve
  Real riskFreeRate = 0.01;
  auto riskFreeRateQuote = boost::make_shared<SimpleQuote>(riskFreeRate);
  Handle<Quote> riskFreeRateHandle(riskFreeRateQuote);
  auto riskFreeRateTermStructure = boost::make_shared<FlatForward>(settlementDate, riskFreeRateHandle, dayCounter);
  Handle<YieldTermStructure> riskFreeRateTermStructureHandle(riskFreeRateTermStructure);
  //
  // create stochastic process
  Handle<Quote> initialFixingHandle(boost::shared_ptr<Quote>(new SimpleQuote(initialFixing)));
  Real volatility = 0.16;
  auto volatilityQuote = boost::make_shared<SimpleQuote>(volatility);
  Handle<Quote> volatilityHandle(volatilityQuote);
  Handle<BlackVolTermStructure> volatilityTermStructureHandle(boost::shared_ptr<BlackVolTermStructure>
   (new BlackConstantVol(settlementDays, calendar, volatilityHandle, dayCounter)));
  auto process = boost::make_shared<BlackScholesProcess>(initialFixingHandle, riskFreeRateTermStructureHandle, volatilityTermStructureHandle);
  //
  // create simulation-related attributes
  bool useAntitheticVariates = false;
  Size requiredSamples = 1000;
  Real requiredTolerance = Null<Real>();
  Size maxSamples = 1000;
  BigNatural seed = 0;
  //
  auto engine = boost::make_shared<EquityLinkedNoteMonteCarloPricer<PseudoRandom, Statistics>>
   (process, riskFreeRateTermStructureHandle, useAntitheticVariates, requiredSamples, requiredTolerance, maxSamples, seed);
  note->setPricingEngine(engine);
  std::cout << note->NPV() << std::endl;
 }
 catch (std::exception& e) {
  std::cout << e.what() << std::endl;
 }
 return 0;
}


Excel


The following screenshots are presenting parameters, result, path simulations and coupon calculations in Excel for this structured note. PV is an average of all discounted path payoffs.







Wednesday, June 25, 2014

Configurable C# Monte Carlo option pricer in Excel

This time, I wanted to present one possible design for Monte Carlo (MC) option pricer, what I have been chewing for some time. The great wisdom what I have learned so far is the following: MC application is always inherently a tradeoff between speed and flexibility. The fastest solution is just one monolithic program, where everything is hard-coded. However, this type of solution leads to maintenance and extendability problems, when any new types of pricers needs to be created, for example. And again, a desire for more flexible solution leads to increases in design complexity and running time.

Now, with this design example, we may not create the fastest possible solution, but the one with extremely flexible design and great configurability. More specifically when pricing options, the user is able to select different types of

The presented design can actually be used, not only for pricing options, but for all applications where we would like to simulate any stochastic processes. For example, we could use "the core part" of this design (SDE, Discretization, RandomGenerator and MonteCarloEngine) when simulating short rate processes for yield curve estimation. However, this example concentrates only on pricing options. More specifically, the user is able to use this design example when pricing options without embedded decisions (American, Bermudan).


PROJECT OUTCOME

The outcome of this small project is fully configurable C# Monte Carlo pricer application. Application can be used to price wide range of different types of one-factor options (European, binary, path-dependent). The application gets all the required input parameters directly from Excel, then performs calculations in C# and finally returns calculation results back to Excel. Excel and C# are interfaced with Excel-DNA and Excel itself is used only as data input/output platform, exactly like presented in my previous blog post.


PREPARATORY TASKS

Download and unzip Excel-DNA Version 0.30 zip file to be ready when needed. There is also a step-by-step word documentation file available within the distribution folder. In this project, we are going to follow these instructions.

 

DESIGN OVERVIEW

The application design is presented in the UML class diagram below.




















In order to understand this design better, we go through the core components and general logic of this design.


STOCHASTIC PATH CREATION - THE CORE OF THE ENGINE

Whenever we need to simulate stochastic process path, we need to define stochastic differential equation to be used. In addition to this, we also need to define discretization scheme for this SDE. In order to model differential equation to be stochastic, we need standardized normal random number. The following three components provide service to create prices according to a given stochastic differential equation, discretization scheme and random number generator.



















With SDE component, we can model the following types of one-factor stochastic differential equations.






Interface ISDE defines methods for retrieving drift and diffusion terms for a given S and t. Abstract class SDE implements this interface. Finally, from abstract SDE we can implement concrete classes for different types of SDE's (GBM, Vasicek, CIR, etc). In this design example, we are using Standard Geometric Brownian Motion.

IDiscretization interface defines method for retrieving spot price for a given S, t, dt and random term. Abstract Discretization class implements this interface and also defines initial spot price (initialPrice) and time to maturity (expiration) as protected member data. It should be noted, that our concrete SDE is aggregated into Discretization. In this design example, we are using Euler discretization scheme.

Finally, IRandomGenerator defines method for getting standard normal random number. Again, RandomGenerator implements this interface and in this design example our concrete class NormalApproximation is "quick and dirty way" to generate normal random approximations as the sum of 12 independent uniformly distributed random numbers minus 6. Needless to say, we should come up with the better random number generator implementation for this class, when starting to test option pricing against benchmark prices.

 

BUILDER AND MONTE CARLO ENGINE

Creating all previously presented "core objects" in the main program can easily lead to maintenance problems and main program "explosion". The solution for this common problem is to use Builder design pattern. The interaction between Builder component and MonteCarloEngine is described in the picture below.































IBuilder interface defines method for creating and retrieving all three core objects inside Tuple. Abstract Builder class implements this interface and concrete class implements Builder class. In this design example, our concrete implementation for Builder class is ExcelBuilder class, which will build all three core objects directly from Excel workbook and finally packs these objects into Tuple.

There is an association between Builder and MonteCarloEngine. Selected Builder object to be used will be given as one argument in MonteCarloEngine constructor. In constructor code, Builder will build three core objects and packs those into Tuple. After this, constructor code will assign values for private data members directly from Tuple (SDE, Discretization, RandomGenerator).

The purpose of MonteCarloEngine class is to create stochastic price paths, by using these three core objects described above. Actually, this class is also an implementation of Mediator design pattern. We have been implementing all the needed components as loosely coupled classes. All communication between these objects are handled by MonteCarloEngine (Mediator).

When MonteCarloEngine has simulated a path, it uses event (delegate function PathSender) for distributing this simulated path (array of doubles) for pricers, one path at a time. Then, when MonteCarloEngine has been simulating desired number of paths, it uses event (delegate function ProcessStopper) for sending notification on the simulation process end for pricers. After receiving this notification from MonteCarloEngine, pricers will calculate the option prices.


PRICER

The final component of this solution is Pricer. This component is receiving simulated price path from MonteCarloEngine and calculating option value for a given one-factor payoff function for each simulated path (delegate function OneFactorPayoff). Pricer class uses a given discount factor (generic delegate function discountFactor) for calculating present value for option payoff expectation. Finally, client can retrieve calculated option price with public price method.





















IPricer interface defines methods for processing simulated price path (processPath), calculating option price (calculate) and retrieving option price (price). Pricer implements this interface. Also, it has OneFactorPayoff delegate, discountFactor generic delegate and number of simulated paths as protected member data. Technically, the variable for simulated paths is only a running counter for expectation calculation purposes. Concrete implementation of Pricer class uses OneFactorPayoff delegate function for calculating the actual option payoff for a given spot and strike.

 

C# PROGRAM

All interfaces and classes described above, are given here below. Create a new C# Class Library project (MCPricer), save the project and copyPaste the following code blocks into separate cs files.

interface ISDE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public interface ISDE
    { 
        // methods for calculating drift and diffusion term of stochastic differential equation
        double drift(double s, double t);
        double diffusion(double s, double t);
    }
}

abstract class SDE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public abstract class SDE :ISDE
    { 
        // abstract class implementing ISDE interface
        public abstract double drift(double s, double t);
        public abstract double diffusion(double s, double t);
    }
}

concrete class GBM
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    // concrete implementation for Standard Geometric Brownian Motion
    public class GBM : SDE
    {
        private double r; // risk-free rate
        private double q; // dividend yield
        private double v; // volatility
        //
        public GBM(double r, double q, double v)
        {
            this.r = r; this.q = q; this.v = v;
        }
        public override double drift(double s, double t)
        {
            return (r - q) * s;
        }
        public override double diffusion(double s, double t)
        {
            return v * s;
        }
    }
}

interface IDiscretization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public interface IDiscretization
    {
        // method for discretizing stochastic differential equation
        double next(double s, double t, double dt, double rnd);
    }
}

abstract class Discretization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public abstract class Discretization : IDiscretization
    {
        // abstract class implementing IDiscretization interface
        protected SDE sde;
        protected double initialPrice;
        protected double expiration;
        //
        // read-only properties for initial price and expiration
        public double InitialPrice { get { return initialPrice; } }
        public double Expiration { get { return expiration; } }
        public Discretization(SDE sde, double initialPrice, double expiration) 
        {
            this.sde = sde; 
            this.initialPrice = initialPrice; 
            this.expiration = expiration;
        }
        public abstract double next(double s, double t, double dt, double rnd);
    }
}

concrete class EulerDiscretization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public class EulerDiscretization : Discretization
    { 
        // concrete implementation for Euler discretization scheme
        public EulerDiscretization(SDE sde, double initialPrice, double expiration) 
            : base(sde, initialPrice, expiration) { }
        public override double next(double s, double t, double dt, double rnd)
        {
            return s + sde.drift(s, t) * dt + sde.diffusion(s, t) * Math.Sqrt(dt) * rnd;
        }
    }
}

interface IRandomGenerator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public interface IRandomGenerator
    {
        // method for generating normally distributed random variable
        double getRandom();
    }
}

abstract class RandomGenerator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public abstract class RandomGenerator : IRandomGenerator
    {
        // abstract class implementing IRandomGenerator interface
        public abstract double getRandom();
    }
}

concrete class NormalApproximation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public class NormalApproximation : RandomGenerator
    {
        // concrete implementation for normal random variable approximation
        // normRand = sum of 12 independent uniformly disctibuted random numbers, minus 6
        private Random random;
        public NormalApproximation()
        {
            random = new Random();
        }
        public override double getRandom()
        {
            // implementation uses C# uniform random generator
            double[] rnd = new double[12];
            Func<double> generator = () => { return random.NextDouble(); };
            return rnd.Select(r => generator()).Sum() - 6.0;
        }
    }
}

concrete class MonteCarloEngine
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public delegate void PathSender(ref double[] path);
    public delegate void ProcessStopper();
    //
    public class MonteCarloEngine
    {
        private SDE sde;
        private Discretization discretization;
        private RandomGenerator randomGenerator;
        private long paths;
        private int steps;
        public event PathSender sendPath;
        public event ProcessStopper stopProcess;
        //
        public MonteCarloEngine(Builder builder, long paths, int steps)
        {
            Tuple<SDE, Discretization, RandomGenerator> parts = builder.build();
            sde = parts.Item1;
            discretization = parts.Item2;
            randomGenerator = parts.Item3;
            this.paths = paths;
            this.steps = steps;
        }
        public void run()
        {
            double[] path = new double[steps + 1];
            double dt = discretization.Expiration / steps;
            double vOld = 0.0; double vNew = 0.0;
            //
            for (int i = 0; i < paths; i++)
            {
                path[0] = vOld = discretization.InitialPrice;
                //
                for (int j = 1; j <= steps; j++)
                {
                    // get next value using discretization scheme
                    vNew = discretization.next(vOld, (dt * j), dt, randomGenerator.getRandom());
                    path[j] = vNew; vOld = vNew;
                }
                sendPath(ref path); // send one simulated path to pricer to be processed
            }
            stopProcess(); // simulation ends - notify pricer
        }
    }
}

interface IBuilder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public interface IBuilder
    {
        // method for creating all the needed objects for asset price simulations
        Tuple<SDE, Discretization, RandomGenerator> build();
    }
}

abstract class Builder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public abstract class Builder : IBuilder
    {
        // abstract class implementing IBuilder interface
        public abstract Tuple<SDE, Discretization, RandomGenerator> build();
    }
}

concrete class ExcelBuilder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExcelDna.Integration;

namespace MCPricer
{
    public class ExcelBuilder : Builder
    {
        private dynamic Excel = ExcelDnaUtil.Application;
        //
        public override Tuple<SDE, Discretization, RandomGenerator> build()
        {
            // build all objects needed for asset path simulations
            SDE sde = build_SDE();
            Discretization discretization = build_discretization(sde);
            RandomGenerator randomGenerator = build_randomGenerator();
            return new Tuple<SDE, Discretization, RandomGenerator>(sde, discretization, randomGenerator);
        }
        private SDE build_SDE()
        {
            SDE sde = null;
            string sdeType = (string)Excel.Range("_stochasticModel").Value;
            //
            if (sdeType == "GBM")
            {
                double r = (double)Excel.Range("_rate").Value2;
                double q = (double)Excel.Range("_yield").Value2;
                double v = (double)Excel.Range("_volatility").Value2;
                sde = new GBM(r, q, v);
            }
            // insert new stochastic model choices here
            return sde;
        }
        private Discretization build_discretization(SDE sde)
        {
            Discretization discretization = null;
            string discretizationType = (string)Excel.Range("_discretization").Value;
            //
            if (discretizationType == "EULER")
            {
                double initialPrice = (double)Excel.Range("_spot").Value2;
                double expiration = (double)Excel.Range("_maturity").Value2;
                discretization = new EulerDiscretization(sde, initialPrice, expiration);
            }
            // insert new discretization scheme choices here
            return discretization;
        }
        private RandomGenerator build_randomGenerator()
        {
            RandomGenerator randomGenerator = null;
            string randomGeneratorType = (string)Excel.Range("_randomGenerator").Value;
            //
            if (randomGeneratorType == "CLT")
            {
                randomGenerator = new NormalApproximation();
            }
            // insert new random generator choices here
            return randomGenerator;
        }
    }
}

interface IPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public interface IPricer
    {
        void processPath(ref double[] path);
        void calculate();
        double price();
    }
}

abstract class Pricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public delegate double OneFactorPayoff(double spot, double strike);
    //
    public abstract class Pricer : IPricer
    {
        protected OneFactorPayoff payoff; // delegate function for payoff calculation
        protected Func<double> discountFactor; // generic delegate function for discount factor
        protected double v; // option price
        protected long paths; // running counter
        //
        public Pricer(OneFactorPayoff payoff, Func<double> discountFactor)
        {
            this.payoff = payoff; this.discountFactor = discountFactor;
        }
        public abstract void processPath(ref double[] path);
        public void calculate()
        {
            // calculate discounted expectation
            v = (v / paths) * discountFactor();
        }
        public double price()
        {
            // return option value
            return v;
        }

    }
}

concrete class EuropeanPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public class EuropeanPricer : Pricer
    {
        private double x; // option strike
        //
        public EuropeanPricer(OneFactorPayoff payoff, double x, Func<double> discountFactor)
            : base(payoff, discountFactor)
        {
            this.x = x;
        }
        public override void processPath(ref double[] path)
        {
            // calculate payoff
            v += payoff(path[path.Length - 1], x);
            paths++;
        }
    }
}

concrete class ArithmeticAsianPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public enum ENUM_ASIAN_TYPE { average_price, average_strike }
    //
    public class ArithmeticAsianPricer : Pricer
    {
        private ENUM_ASIAN_TYPE asianType;
        private double x; // option strike
        private double averagePeriodStart; // time for starting averaging period
        private double t;
        private int steps;
        //
        public ArithmeticAsianPricer(OneFactorPayoff payoff, double x, Func<double> discountFactor, 
            double t, double averagePeriodStart, int steps, ENUM_ASIAN_TYPE asianType)
            : base(payoff, discountFactor)
        {
            this.x = x;
            this.t = t;
            this.steps = steps;
            this.averagePeriodStart = averagePeriodStart;
            this.asianType = asianType;
        }
        public override void processPath(ref double[] path)
        {
            double dt = t / steps;
            int timeCounter = -1;
            //
            // generic delegate for SkipWhile method to test if averaging period for an item has started
            Func<double, bool> timeTest = (double p) => 
            {
                timeCounter++;
                if ((dt * timeCounter) < averagePeriodStart) return true;
                    return false;
            };
            //
            // calculate average price for averaging period
            double pathAverage = path.SkipWhile(timeTest).ToArray().Average();
            //
            // calculate payoff
            if (asianType == ENUM_ASIAN_TYPE.average_price) v += payoff(pathAverage, x);
            if (asianType == ENUM_ASIAN_TYPE.average_strike) v += payoff(path[path.Length - 1], pathAverage);
            paths++;
        }
    }
}

concrete class BarrierPricer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MCPricer
{
    public enum ENUM_BARRIER_TYPE { up_and_in, up_and_out, down_and_in, down_and_out }
    //
    public class BarrierPricer : Pricer
    {
        private double x; // option strike
        private double b; // barrier level
        private ENUM_BARRIER_TYPE barrierType;
        //
        public BarrierPricer(OneFactorPayoff payoff, double x, Func<double> discountFactor, 
            double b, ENUM_BARRIER_TYPE barrierType) : base(payoff, discountFactor)
        {
            this.x = x;
            this.b = b;
            this.barrierType = barrierType;
        }
        public override void processPath(ref double[] path)
        {
            // calculate payoff - check barrier breaches
            if ((barrierType == ENUM_BARRIER_TYPE.up_and_in) && (path.Max() > b)) v += payoff(path[path.Length - 1], x);
            if ((barrierType == ENUM_BARRIER_TYPE.up_and_out) && (path.Max() < b)) v += payoff(path[path.Length - 1], x);
            if ((barrierType == ENUM_BARRIER_TYPE.down_and_in) && (path.Min() < b)) v += payoff(path[path.Length - 1], x);
            if ((barrierType == ENUM_BARRIER_TYPE.down_and_out) && (path.Min() > b)) v += payoff(path[path.Length - 1], x);
            paths++;
        }
    }
}

concerete class MCPricer (this is the main program, VBA program will call run method of this class).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExcelDna.Integration;
using System.Windows.Forms;

namespace MCPricer
{
    public static class MCPricer
    {
        private static dynamic Excel;
        private static Dictionary<string, Pricer> pricer;
        private static MonteCarloEngine engine;
        private static OneFactorPayoff callPayoff;
        private static OneFactorPayoff putPayoff;
        private static Func<double> discountFactor;
        //
        public static void run()
        {
            try
            {
                // create Excel application
                Excel = ExcelDnaUtil.Application;
                //
                // fetch pricing parameters from named Excel ranges
                int steps = (int)Excel.Range("_steps").Value2;
                long paths = (long)Excel.Range("_paths").Value2;
                double r = (double)Excel.Range("_rate").Value2;
                double t = (double)Excel.Range("_maturity").Value2;
                double averagePeriodStart = (double)Excel.Range("_averagingPeriod").Value2;
                double upperBarrier = (double)Excel.Range("_upperBarrier").Value2;
                double lowerBarrier = (double)Excel.Range("_lowerBarrier").Value2;
                double x = (double)Excel.Range("_strike").Value2;
                //
                // create Monte Carlo engine, payoff functions and discounting factor
                engine = new MonteCarloEngine(new ExcelBuilder(), paths, steps);
                callPayoff = (double spot, double strike) => Math.Max(0.0, spot - strike);
                putPayoff = (double spot, double strike) => Math.Max(0.0, strike - spot);
                discountFactor = () => Math.Exp(-r * t);
                //
                // create pricers into dictionary
                pricer = new Dictionary<string, Pricer>();
                pricer.Add("Vanilla call", new EuropeanPricer(callPayoff, x, discountFactor));
                pricer.Add("Vanilla put", new EuropeanPricer(putPayoff, x, discountFactor));
                pricer.Add("Asian average price call", new ArithmeticAsianPricer(callPayoff, x, discountFactor, t, averagePeriodStart, steps, ENUM_ASIAN_TYPE.average_price));
                pricer.Add("Asian average price put", new ArithmeticAsianPricer(putPayoff, x, discountFactor, t, averagePeriodStart, steps, ENUM_ASIAN_TYPE.average_price));
                pricer.Add("Asian average strike call", new ArithmeticAsianPricer(callPayoff, x, discountFactor, t, averagePeriodStart, steps, ENUM_ASIAN_TYPE.average_strike));
                pricer.Add("Asian average strike put", new ArithmeticAsianPricer(putPayoff, x, discountFactor, t, averagePeriodStart, steps, ENUM_ASIAN_TYPE.average_strike));
                pricer.Add("Up-and-in barrier call", new BarrierPricer(callPayoff, x, discountFactor, upperBarrier, ENUM_BARRIER_TYPE.up_and_in));
                pricer.Add("Up-and-out barrier call", new BarrierPricer(callPayoff, x, discountFactor, upperBarrier, ENUM_BARRIER_TYPE.up_and_out));
                pricer.Add("Down-and-in barrier call", new BarrierPricer(callPayoff, x, discountFactor, lowerBarrier, ENUM_BARRIER_TYPE.down_and_in));
                pricer.Add("Down-and-out barrier call", new BarrierPricer(callPayoff, x, discountFactor, lowerBarrier, ENUM_BARRIER_TYPE.down_and_out));
                pricer.Add("Up-and-in barrier put", new BarrierPricer(putPayoff, x, discountFactor, upperBarrier, ENUM_BARRIER_TYPE.up_and_in));
                pricer.Add("Up-and-out barrier put", new BarrierPricer(putPayoff, x, discountFactor, upperBarrier, ENUM_BARRIER_TYPE.up_and_out));
                pricer.Add("Down-and-in barrier put", new BarrierPricer(putPayoff, x, discountFactor, lowerBarrier, ENUM_BARRIER_TYPE.down_and_in));
                pricer.Add("Down-and-out barrier put", new BarrierPricer(putPayoff, x, discountFactor, lowerBarrier, ENUM_BARRIER_TYPE.down_and_out));
                //
                // order path updates for all pricers from engine
                foreach (KeyValuePair<string, Pricer> kvp in pricer) engine.sendPath += kvp.Value.processPath;
                //
                // order process stop notification for all pricers from engine
                foreach (KeyValuePair<string, Pricer> kvp in pricer) engine.stopProcess += kvp.Value.calculate;
                //
                // run Monte Carlo engine
                engine.run();
                //
                // print option types to Excel
                string[] optionTypes = pricer.Keys.ToArray();
                Excel.Range["_options"] = Excel.WorksheetFunction.Transpose(optionTypes);
                //
                // print option prices to Excel
                double[] optionPrices = new double[pricer.Count];
                for (int i = 0; i < pricer.Count; i++) optionPrices[i] = pricer.ElementAt(i).Value.price();
                Excel.Range["_prices"] = Excel.WorksheetFunction.Transpose(optionPrices);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message.ToString());
            }
        }
    }
}

 

EXCEL-DNA INTEGRATION

After implementing all the previous cs files into C# Class Library project, we are receiving a lot of errors. However, all errors should be related to missing references to Excel-DNA integration library and Windows Forms library. Next, carefully follow the instructions described here in step two.

In a nutshell
  • add reference to Excel-DNA library (ExcelDna.Integration.dll). From the properties of this reference, set Copy Local to be False.
  • add reference to Windows Forms library (System.Windows.Forms)
  • create MCPricer.dna file (consisting XML tags). DnaLibrary Name="MCPricer" and Path="MCPricer.dll". From the properties of this dna file, set Copy to Output Directory to be Copy if newer.
  • copy ExcelDna.xll file into your project folder and rename it to be MCPricer.xll. From the properties of this xll file, set Copy to Output Directory to be Copy if newer.
Make sure, that all properties for these references and files are exactly the same as described in this post. After adding all the required references, files and building this program once again, my project folder has the following four files.

 

USER INTERFACE AND C#-VBA INTEGRATION

The essence of this part of the process has been described here in step three. Open a new Excel workbook. Create the following source data into worksheet.
















From Excel Name Manager (Formulas - Name Manager), set the following range names.

























In VB editor, create the following event handling program for CommandButton (Calculate option prices).



















TEST RUN

At this point, our application is ready for test run. While this Excel workbook is open, doubleClick MCPricer.xll file in your \\MCPricer\bin\Release folder. After this, xll file content can be used by Excel and our C# program is available to be called from VBA program (Application.Run). VBA program will call and start C# program run, which then reads all input data, performs calculations and sends result data back to Excel worksheet.

After pressing command button in Excel interface, C# MC option pricer application simulated the following prices for all requested options.
















AFTERTHOUGHTS

This small project was presenting one possible design for Monte Carlo option pricer. We came up with extremely flexible design and great configurability. The presented design can actually be used, not only for pricing options, but for all applications where we would like to simulate any stochastic processes for any purpose (short rate processes for yield curve estimation, for example).

At this point, I would like to express my appreciation for Mr. Daniel Duffy for opening up some of his "well-brewed design wisdoms" during the one of his DatasimFinancial training courses. For those who would like get familiar with useful examples and ideas using C# in financial programs, there is a great book C# for Financial Markets available written by Daniel Duffy and Andrea Germani (published 2013).

As always, I owe Thank You again for Govert Van Drimmelen (inventor, developer and author of Excel-DNA), for his amazing Excel-DNA Excel/C API wrapper. For learning more about this extremely useful tool, check out its homepage. Getting more information and examples with your problems, the main source is Excel-DNA google group. Finally, Excel-DNA is an open-source project, and we (the happy users) can invest its future development by making a donation.

And finally, Thank You for reading my blog again!
-Mike Juniperhill

Saturday, January 11, 2014

Implementation for Gaussian Copula in VBA

Correlated random numbers are used a lot in Finance (pricing credit structures or basket options, to name just a couple). This post is all about creating correlated random numbers in VBA. The following Monte Carlo procedure will be used to simulate correlated and uniformly distributed  random variables with Gaussian Copula:
  • Create Cholesky Decomposition matrix A of input correlation matrix
  • Generate a vector of independent normal random variables Z
  • Compute a vector of correlated normal random variables by using Cholesky matrix X = AZ
  • Convert X back to uniform plane [0,1] to get the matrix U containing correlated uniform random variables
  • Use matrix U and Inverse Transform Sampling to generate correlated random variables for any marginal distributions.
One possible implementation design for this scheme is presented in the picture below (UML).



Main program flow
Director (main program) creates parameter wrapper (Dictionary data structure) for Copula. Director creates correlation matrix for Copula as Matrix object (custom data structure). Director creates random number generator implementation for Copula. Director sets the number of simulations and uniform transformation condition for Copula. Director creates Copula implementation as Gaussian Copula and initializes the model with required data and objects (init).

Copula model gets independent normal random numbers as Matrix object from Random implementation (aggregated in Copula). Copula uses Cholesky decomposition for creating correlated normal random numbers. Copula transforms simulated correlated normal random numbers into uniform plane (optional). Finally, director gets correlated random numbers from Copula as Matrix object.

Generator for random numbers
Readers might be aware, that random number quality produced by Excel Rand function has been reported to be insufficient. Also, the use of Excel NormsInv function is irritatingly slow. For this implementation, the more efficient tools have been employed.

The source for an algorithm implementation of Mersenne Twister was found in this page. Download a zip file from the page and look for mt19937.dll. Save this dll file into C:\temp folder. The example program presented below has been configured so, that it searches that dll file from that folder. The source code for an algorithm for computing the inverse normal cumulative distribution function was acquired from Peter Acklam's web page.

Example program
The following program is a direct implementation for the UML presented above. Director is the main program in VBA (tester). At this point, we should reference the library for Dictionary data structure (VB editor - Tools - References - Microsoft Scripting Runtime).

CopyPaste enumerators into new VBA standard module (name = Enumerators)
Option Explicit
'
Public Enum E_
    P_SIMULATIONS = 1
    P_GENERATOR_TYPE = 2
    P_CORRELATION_MATRIX = 3
    P_TRANSFORM_TO_UNIFORM = 4
End Enum
'

Next, we create the actual tester program (director). CopyPaste the following code into standard VBA module (name = MainProgram). There are some source data read from Excel worksheet Sheet1 in the program presented below. Set correlation matrix into Excel and give a name for that range ("_correlation"). Similarly, give range names for number of simulations ("_simulations") and uniform transform condition ("_transform"). Also, set up a range name for output ("_dataDump").

Option Explicit
'
Public Sub tester()
    '
    ' create correlation matrix object
    Dim correlation As New Matrix
    correlation.matrixFromRange (Sheets("Sheet1").Range("_correlation"))
    '
    ' create parameters for copula
    Dim parameters As New Scripting.Dictionary
    parameters.Add P_SIMULATIONS, CLng(Sheets("Sheet1").Range("_simulations").value)
    parameters.Add P_GENERATOR_TYPE, New MersenneTwister
    parameters.Add P_CORRELATION_MATRIX, correlation
    parameters.Add P_TRANSFORM_TO_UNIFORM, CBool(Sheets("Sheet1").Range("_transform").value)
    '
    ' create copula implementation
    Dim copula As ICopula: Set copula = New GaussianCopula
    copula.init parameters
    '
    ' get results from copula and write these into Excel
    Dim result As Matrix: Set result = copula.getMatrix
    result.matrixToRange Sheets("Sheet1").Range("_dataDump")
    '
    ' release objects
    Set result = Nothing
    Set copula = Nothing
    Set parameters = Nothing
    Set correlation = Nothing
End Sub
'

After this, we create ICopula interface. CopyPaste the following code into a new VBA class module (name = ICopula).
Option Explicit
'
' interface for copula model
'
Public Function init(ByRef parameters As Scripting.Dictionary)
    ' interface - no implementation
End Function
'
Public Function getMatrix() As Matrix
    ' interface - no implementation
End Function
'

CopyPaste the following ICopula implementation into a new VBA class module (name = GaussianCopula).
Option Explicit
'
Implements ICopula
'
Private n As Long ' number of simulations
Private transform As Boolean ' condition for uniform transformation
Private generator As IRandom ' random number generator implementation
'
Private c As Matrix ' correlation matrix
Private d As Matrix ' cholesky decomposition matrix
Private z As Matrix ' independent normal random variables
Private x As Matrix ' correlated normal random variables
'
Private Function ICopula_init(ByRef parameters As Scripting.Dictionary)
    '
    ' initialize class data and objects
    n = parameters(P_SIMULATIONS)
    transform = parameters(P_TRANSFORM_TO_UNIFORM)
    Set generator = parameters(P_GENERATOR_TYPE)
    Set c = parameters(P_CORRELATION_MATRIX)
End Function
'
Private Function ICopula_getMatrix() As Matrix
    '
    ' create matrix of independent normal random numbers
    Set z = New Matrix: z.init n, c.get_c
    Set z = generator.getNormalRandomMatrix(z.get_r, z.get_c)
    '
    ' create cholesky decomposition
    Set d = New Matrix: Set d = d.cholesky(c)
    '
    ' create correlated normal random numbers
    z.transpose
    Set x = New Matrix: Set x = x.multiplication(d, z)
    x.transpose
    '
    ' transform correlated normal random numbers
    ' into correlated uniform random numbers
    If (transform) Then uniformTransformation
    Set ICopula_getMatrix = x
End Function
'
Private Function uniformTransformation()
    '
    ' map normal random number to uniform plane
    Dim nRows As Long: nRows = x.get_r
    Dim nCols As Long: nCols = x.get_c
    '
    Dim i As Long, j As Long
    For i = 1 To nRows
        For j = 1 To nCols
            x.push i, j, WorksheetFunction.NormSDist(x.at(i, j))
        Next j
    Next i
End Function
'

Then, we create interface for random number generator. CopyPaste the following code into a new VBA class module (name = IRandom).
Option Explicit
'
Public Function getNormalRandomMatrix( _
    ByVal nRows As Long, _
    ByVal nCols As Long) As Matrix
    '
    ' interface - no implementation
    ' takes in two parameters (number of rows and columns) and
    ' returns matrix object filled with normal random variates
End Function
'

Next, we create implementation for IRandom. CopyPaste the following code into a new VBA class module (name = MersenneTwister).

Option Explicit
'
Implements IRandom
'
Private Declare Function nextMT Lib "C:\temp\mt19937.dll" Alias "genrand" () As Double
'
Private Function IRandom_getNormalRandomMatrix( _
    ByVal nRows As Long, _
    ByVal nCols As Long) As Matrix
    '
    ' retrieve NxM matrix with normal random numbers
    Dim r As Matrix: Set r = New Matrix: r.init nRows, nCols
    Dim i As Long, j As Long
    For i = 1 To nRows
        For j = 1 To nCols
            r.push i, j, InverseCumulativeNormal(nextMT())
        Next j
    Next i
    '
    Set IRandom_getNormalRandomMatrix = r
End Function
'
Public Function InverseCumulativeNormal(ByVal p As Double) As Double
    '
    ' Define coefficients in rational approximations
    Const a1 = -39.6968302866538
    Const a2 = 220.946098424521
    Const a3 = -275.928510446969
    Const a4 = 138.357751867269
    Const a5 = -30.6647980661472
    Const a6 = 2.50662827745924
    '
    Const b1 = -54.4760987982241
    Const b2 = 161.585836858041
    Const b3 = -155.698979859887
    Const b4 = 66.8013118877197
    Const b5 = -13.2806815528857
    '
    Const c1 = -7.78489400243029E-03
    Const c2 = -0.322396458041136
    Const c3 = -2.40075827716184
    Const c4 = -2.54973253934373
    Const c5 = 4.37466414146497
    Const c6 = 2.93816398269878
    '
    Const d1 = 7.78469570904146E-03
    Const d2 = 0.32246712907004
    Const d3 = 2.445134137143
    Const d4 = 3.75440866190742
    '
    'Define break-points
    Const p_low = 0.02425
    Const p_high = 1 - p_low
    '
    'Define work variables
    Dim q As Double, r As Double
    '
    'If argument out of bounds, raise error
    If p <= 0 Or p >= 1 Then Err.Raise 5
    '
    If p < p_low Then
        '
        'Rational approximation for lower region
        q = Sqr(-2 * Log(p))
        InverseCumulativeNormal = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _
        ((((d1 * q + d2) * q + d3) * q + d4) * q + 1)
        '
    ElseIf p <= p_high Then
        'Rational approximation for lower region
        q = p - 0.5
        r = q * q
        InverseCumulativeNormal = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q / _
        (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1)
        '
    ElseIf p < 1 Then
        'Rational approximation for upper region
        q = Sqr(-2 * Log(1 - p))
        InverseCumulativeNormal = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _
        ((((d1 * q + d2) * q + d3) * q + d4) * q + 1)
    End If
End Function
'

Finally, we create our custom data structure class, which is used extensively in this design. CopyPaste the following code into a new VBA class module (name = Matrix).
Option Explicit
'
' general matrix data structure for double data type
' variant array of double arrays, works like Excel
Private outer() As Variant
Private r_ As Long
Private c_ As Long
'
Public Function init(ByVal r As Long, ByVal c As Long)
    '
    ' create new matrix object
    r_ = r
    c_ = c
    ReDim outer(1 To r_)
    '
    Dim i As Long
    For i = 1 To r_
        Dim inner() As Double: ReDim inner(1 To c_)
        outer(i) = inner
    Next i
End Function
'
Public Function multiplication(ByRef m1 As Matrix, ByRef m2 As Matrix) As Matrix
    '
    ' get matrix multiplication from two external matrix objects
    ' return a new matrix object
    Dim result As New Matrix: result.init m1.get_r, m2.get_c
    Dim i As Long, j As Long, k As Long
    '
    Dim r1 As Long: r1 = m1.get_r
    Dim c1 As Long: c1 = m1.get_c
    Dim r2 As Long: r2 = m2.get_r
    Dim c2 As Long: c2 = m2.get_c
    Dim v As Double
    '
    For i = 1 To r1
        For j = 1 To c2
            v = 0
            '
            For k = 1 To c1
                v = v + m1.at(i, k) * m2.at(k, j)
            Next k
            result.push i, j, v
        Next j
    Next i
    Set multiplication = result
End Function
'
Public Function clone(ByRef m As Matrix)
    '
    ' clone external matrix object into Me object - no return matrix
    Dim nRows As Long: nRows = m.get_r
    Dim nCols As Long: nCols = m.get_c
    Me.set_r nRows
    Me.set_c nCols
    Me.setOuter m.getOuter
End Function
'
Public Function transpose()
    '
    ' transpose matrix (Me) - no return matrix
    Dim nRows As Long: nRows = Me.get_r
    Dim nCols As Long: nCols = Me.get_c
    Dim m As New Matrix: m.init nCols, nRows
    '
    Dim i As Long, j As Long
    For i = 1 To nRows
        For j = 1 To nCols
            'result.push j, i, m.at(i, j)
            m.push j, i, Me.at(i, j)
        Next j
    Next i
    Me.clone m
End Function
'
Public Function cholesky(ByRef c As Matrix) As Matrix
    '
    ' create cholesky decomposition, a lower triangular matrix
    ' d = decomposition, c = correlation matrix
    ' return a new matrix object
    Dim s As Double
    Dim n As Long: n = c.get_r
    Dim m As Long: m = c.get_c
    Dim d As New Matrix: d.init n, m
    '
    Dim i As Long, j As Long, k As Long
    For j = 1 To n
        s = 0
        For k = 1 To j - 1
            s = s + d.at(j, k) ^ 2
        Next k
        d.push j, j, c.at(j, j) - s
        If d.at(j, j) <= 0 Then Exit For
        d.push j, j, Sqr(d.at(j, j))
        '
        For i = j + 1 To n
            s = 0
            For k = 1 To j - 1
                s = s + d.at(i, k) * d.at(j, k)
            Next k
            d.push i, j, (c.at(i, j) - s) / d.at(j, j)
        Next i
    Next j
    Set cholesky = d
End Function
'
Public Function matrixToRange(ByRef r As Range)
    '
    ' write matrix content (Me) into Excel range
    Dim nRows As Long: nRows = Me.get_r
    Dim nCols As Long: nCols = Me.get_c
    r.ClearContents
    '
    Dim i As Long, j As Long
    For i = 1 To nRows
        For j = 1 To nCols
            r(i, j) = Me.at(i, j)
        Next j
    Next i
End Function
'
Public Function matrixFromRange(ByRef r As Range)
    '
    ' initialize and copy matrix (Me) from Excel range
    Dim m As Variant: m = r.Value2
    r_ = UBound(m, 1)
    c_ = UBound(m, 2)
    Me.init r_, c_
    '
    Dim i As Long, j As Long
    For i = 1 To r_
        For j = 1 To c_
            Me.push i, j, m(i, j)
        Next j
    Next i
End Function
'
Public Function push(ByVal r As Long, ByVal c As Long, ByVal value As Double)
    ' set value for matrix item
    outer(r)(c) = value
End Function
'
Public Function at(ByVal r As Long, ByVal c As Long) As Double
    ' get value from matrix
    at = outer(r)(c)
End Function
'
Public Function getOuter() As Variant
    ' get variant array
    getOuter = outer
End Function
'
Public Function setOuter(ByRef v As Variant)
    ' set variant array
    outer = v
End Function
'
Public Function get_r() As Variant
    ' get number of matrix rows
    get_r = r_
End Function
'
Public Function set_r(ByVal r As Long)
    ' set number of matrix rows
    r_ = r
End Function
'
Public Function get_c() As Long
    ' get number of matrix columns
    get_c = c_
End Function
'
Public Function set_c(ByVal c As Long)
    ' set number of matrix columns
    c_ = c
End Function
'

After creating all the previous components in VBA, the program is ready for the use.

Results
Correlated random numbers has been simulated for bi-variate case. The following scatter graph shows the results for 1000 simulated correlated normal random numbers (rho = 0.76). Note, that in Copula implementation, class member transform is FALSE and hereby, uniform transformation is not performed.



Setting class member transform to be TRUE, performs uniform transformation and the results are plotted within the following scatter graph.



So, this Copula implementation is leaving an option for its user to receive correlated random numbers as normal, or receive these numbers mapped into uniform plane.

The latter scheme might be useful, if we need to simulate correlated random numbers from any other distributions. For this task, we can then use inverse transform sampling. As an example, the following scatter chart is showing the results for 1000 simulated correlated exponential random numbers (rho = 0.76, lambda = 0.085).



Correlated normal random numbers were first mapped into uniform plane (setting class member transform to be TRUE) and then transformed to exponentially distributed correlated random numbers with the inverse cumulative distribution function.

Afterthoughts
Presented design for VBA is relatively easy to implement. Also, it has a lot of flexibility. Say, we would like to create implementation for Student Copula, we would just have to create new implementation from ICopula interface. Doing this does not have any changes to be made into existing program design, since we are programming to an interface, not to an implementation. The same applies for generating random numbers. It is now easy to create your own generator for any existing Copula design, if you some day manage to create something better than MT algorithm. A small performance penalty is paid with all the function calls made using Matrix object (which is technically just wrapping arrays into a manageable class).

Creating numerical algorithms and solutions is extremely interesting and rewarding. However, there are a lot of time spent for testing and debugging and still, after all efforts, you might feel like walking on thin ice sometimes. Self-made algorithms could be sometimes unreliably, unstable or not accurate enough and for that reason, doing "anything serious" with Copulas (such as creating production pricing tools) I would recommend to check numerical libraries with "proven industrial strength", such as NAG by Numerical Algorithms Group. Along with the existing libraries for C++/C#, NAG has also Fortran library, which can be easily used also with VBA.

Needless to say, of course, that we get the same results with just a few lines of code with the tools like Matlab or Python. However, the interest of this blog is on the development side of programs. My view is that, as a developer, implementing such algorithms are excellent way to learn in order to become a better developer. However, this is only my personal view on the matter.

Anyway, that's all I wanted to share this time. Again, I hope that this post could help you to solve some of your programming problems with VBA. Happy New Year for everybody!

-Mike