// TestAgentsMC.cs
//
// Monte Carlo option pricing using agents in a producer-consumer
// metaphor.
//
// Compare this solution with
//
// Multicast delegates
// Event Pattern
// OOP Observer pattern.
//
// Actors might be a good solution for batch jobs.
//
// Code ported from C++ to C#
//
// (C) Datasim Education BV 2016-2018
//
using System;
using System.Threading;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics;
using MathNet.Numerics.LinearAlgebra.Double;
using System.Globalization;
using MathNet.Numerics.Random;
using MathNet.Numerics.Distributions;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Diagnostics; // For StopWatch
public static class Utility
{
public static void ThreadSafePrint(string s)
{ // Function to avoid garbled output on the console
object _locker = new object();
Monitor.Enter(_locker);
try
{
Console.WriteLine("{0}", s);
}
finally { Monitor.Enter(_locker); }
}
}
// Encapsulate all data in one place
public class OptionData
{ // Option data + behaviour
public double K;
public double T;
public double r;
public double sig;
public int type;
// Extra data
public double D; // dividend
public OptionData()
{
K = 65.0; T = 0.25;
r = 0.08; sig = 0.3; D = 0.0;
type = -1;
}
public OptionData(OptionData opt)
{
K = opt.K; T = opt.T;
r = opt.r; sig = opt.sig; D = opt.D;
type = opt.type;
}
public OptionData(double strike, double expiration, double interestRate,
double volatility, double dividend, int PC)
{
K = strike; T = expiration; r = interestRate; sig = volatility; D = dividend; type = PC;
}
public double myPayOffFunction(double S)
{ // Payoff function
if (type == 1)
{ // Call
return Math.Max(S - K, 0.0);
}
else
{ // Put
return Math.Max(K - S, 0.0);
}
}
}
public class MyMersenneTwister
{ // Math.Net 19937
private MersenneTwister mt;
public MyMersenneTwister() { mt = new MersenneTwister(); }
public double GenerateRn()
{
return Normal.Sample(mt, 0.0, 1.0);
}
}
class Sde
{ // Defines drift + diffusion + data
private OptionData data; // The data for the option
public Sde(OptionData opt) { data = opt; }
public double drift(double t, double S)
{ // Drift term
return (data.r - data.D)*S; // r - D
}
public double diffusion(double t, double S)
{ // Diffusion term
return data.sig * S;
}
public double expiration()
{
return data.T;
}
}
// Demonstrates a basic agent that produces values.
class PathEvolver // Producer/Source
{
// The target buffer to write to.
private ITargetBlock<double> _target;
private int NT;
private int NSIM;
private Sde sde;
private double S0;
private MyMersenneTwister mt;
public PathEvolver(ITargetBlock<double> target, int NSteps, int NSimulations, Sde stochasticDE,
double S_0)
{
_target = (target); NT=(NSteps); NSIM=(NSimulations);
sde=(stochasticDE); S0=(S_0);
mt = new MyMersenneTwister();
}
public void run()
{
Utility.ThreadSafePrint("Starting producer");
double k = sde.expiration() / NT;
double sqrk = Math.Sqrt(k);
double v;
for (int n = 0; n < NSIM; ++n)
{
// if ((n/10000)*n == n)
// Console.Write("{0}, ", n);
double VOld = S0; double VNew = 0;
double x = 0.0;
for (long index = 0; index < NT; ++index)
{
v = mt.GenerateRn();
// The FDM (in this case explicit Euler), equation (9.2) from the text
VNew = VOld + (k * sde.drift(x, VOld)) + (sqrk * sde.diffusion(x, VOld) * v);
VOld = VNew;
x += k;
}
// Console.WriteLine("{0}, ", VNew);
_target.Post(VNew);
}
_target.Complete();
Utility.ThreadSafePrint("Exit producer");
}
}
// Demonstrates a basic agent that consumes values.
public class OptionPricer
{
// The source buffer to read from.
public ISourceBlock<double> _source;
// ...
private OptionData myOption;
private double price;
public OptionPricer(ISourceBlock<double> source, OptionData opt)
{
_source = (source); myOption=(opt);price=0.0;
}
public double Price()
{ // Option price
return price;
}
public async Task<double> run()
{
double payoffT;
double avgPayoffT = 0.0;
double squaredPayoff = 0.0;
double sumPriceT = 0.0;
int NSIM = 0;
double VNew;
Console.WriteLine(" Receiving... ");
while (await _source.OutputAvailableAsync())
{
// if ((NSIM / 10000) * NSIM == NSIM)
// Console.Write("{0}, ", NSIM);
// Assemble quantities (postprocessing)
VNew = _source.Receive();
payoffT = myOption.myPayOffFunction(VNew);
sumPriceT += payoffT;
avgPayoffT += payoffT / NSIM;
avgPayoffT *= avgPayoffT;
squaredPayoff += (payoffT*payoffT);
NSIM++;
}
Console.WriteLine(" Receiving");
// Finally, discounting the average price
price = Math.Exp(-myOption.r * myOption.T) * sumPriceT / NSIM;
return price;
Utility.ThreadSafePrint("Exit consumer");
}
};
class TestTPL
{
static void Main(string[] args)
{
// Create and start the stopwatch
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
// Create and start the producer and consumer agents.
OptionData myOption = new OptionData();
int NT = 500;
int NSIM = 100000;
// Create SDE
var sde1 = new Sde(myOption);
double S0 = 60.0;
OptionData myOption2 = new OptionData(myOption);
myOption2.type = 1;
var sde2 = new Sde(myOption2);
OptionData myOption3 = new OptionData(myOption);
myOption3.K = 70.0;
var sde3 = new Sde(myOption3);
// A message buffer that is shared by the agents.
var buffer = new BufferBlock<double>(); // unbounded or bounded buffer store
var buffer2 = new BufferBlock<double>();
var buffer3 = new BufferBlock<double>();
PathEvolver producer = new PathEvolver(buffer, NT, NSIM, sde1, S0);
PathEvolver producer2 = new PathEvolver(buffer2, NT, NSIM, sde2, S0);
PathEvolver producer3 = new PathEvolver(buffer3, NT, NSIM, sde2, S0);
// Define pricers
OptionPricer consumer = new OptionPricer(buffer, myOption);
OptionPricer consumer2 = new OptionPricer(buffer2, myOption2);
OptionPricer consumer3 = new OptionPricer(buffer3, myOption3);
var cs = consumer.run();
var cs2 = consumer2.run();
var cs3 = consumer3.run();
producer.run();
producer2.run();
producer3.run();
cs.Wait();
cs2.Wait();
cs3.Wait();
stopWatch.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts = stopWatch.Elapsed;
// Format and display the TimeSpan value.
string elapsedTime = String.Format("Elapsed time {0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
Console.WriteLine(elapsedTime, "RunTime");
Console.WriteLine(consumer.Price());
Console.WriteLine(consumer2.Price());
Console.WriteLine(consumer3.Price());
}
}