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
- (SDE) Stochastic differential equation (Geometric Brownian Motion, CIR, Vasicek, etc)
- SDE discretization scheme (Euler, Milstein, etc)
- Random number generator (Low discrepancy sequence, pseudo-random numbers, etc)
- Option pricer type (European, Digital, Asian, Barrier, etc)
- Option payoff type (Call, Put)
- The source, from which the model is going to be created (Excel, file, console, etc)
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.
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
This is a really nice and clear implementation. Many thanks to Mr. Daniel Duffy and you, Mike!
ReplyDelete