I have produced 2 solutions based on my understanding. Ideally, I don't want PDE to know what vol is because it make the interface less generic. I also give a 2nd option. In that case the PDE knows nothing about vol; a 'diffusion factory' does the job on behalf of PDE.outrun:define a non-member function like:Code: Select all// TestVolatilityAndPde.cpp // // Introducing various volatility into PDE // // 1. Policy (ducks) // 2. Signature-based // 3. Other approaches ?? #include <iostream> #include <cmath> #include <functional> struct Pde { // signature std::function<double (double S, double T)> diffusion; // etc. }; struct PdeII { // duck template <typename VolModel> double diffusion(VolModel& vm, double S, double t) { return vm(S, t); } // etc. }; struct MyVolModelI { double ComputeVol(double S, double t) { return 1.0 + std::sin(t); } double operator ()(double S, double t) { return ComputeVol(S,t); } }; int main() { Pde pde; MyVolModelI vm; using namespace std::placeholders; pde.diffusion = std::bind(&MyVolModelI::ComputeVol, vm, _1, _2); std::cout << pde.diffusion(100, 0.5) << '\n'; auto lambdaDiffusion = [](double S, double t) { // Call a yuge solver, answer is 42 return 42; }; pde.diffusion = lambdaDiffusion; std::cout << pde.diffusion(100, 0.5) << '\n'; { // Ducks in a row PdeII pde2; MyVolModelI vm; std::cout << pde2.diffusion<MyVolModelI>(vm,200.0, 0.5) << '\n'; } }
double get_sigma(vol_model, S, t)
this would be the interface that all things vol need to implement for PDE. The PDE code will call this function, and any model that want to support being fed to the PDE solver would need to implement this.
depending of the *type* of vol_model (or type traits) you can make it return vol_model.sigma (for those types that have a public vol sigma parameter)
or return bilinear_interpolate(vol_model, ..) for the types where vol model is some surface definition
or return 1.0 + sin(t) is that's what your vol_model is defined as..
template <typename VolModel>
double diffusion(VolModel& vm, double S, double t)
{
return vm(S, t);
}
1. Don't suppose you have a machine-readable 101 test case (i.e. kick the tyres example)?You can provide that information to the compiler via traits associated with types, e.g. "bool model_has_constant_vol(ModelType&)" or "bool model_has_time_dependent_vol(ModelType&)" or "model_has_a_public_accesible_sigma_scalar(ModelType&)" etc etc and configure your PDE solver routines accordingly (for these special cases) during compilation.
template <typename T1, typename T2>
struct C
{
T1 f1;
T2 f2;
C(T1 t1, T2 t2) : f1(t1), f2(t2) {}
void doit(int n)
{
f1.doit(n);
f2.doit(n, -n);
}
};
class C1
{
public:
void doit(int n) {}
};
class C2
{
public:
void doit(int n, int m) {}
};
int main()
{
C1 c1; C2 c2;
C<C1,C2> c(c1,c2);
c.doit(1);
}
class C1
{
public:
void doit(int n) {}
};
class C2
{
public:
void doit(int n, int m) {}
};
// ---------------------------------
// Non-member function "doit(n)"
// ---------------------------------
template <typename T>
void doit(T&& t, int n) {}
template <>
void doit(C1&& t, int n) {
t.doit(n);
}
template <>
void doit(C2&& t, int n) {
t.doit(n, -n);
}
template <typename T1, typename T2>
void doit(std::pair<T1,T2>&& t, int n) {
doit(t.first, n);
doit(t.second, n);
}
int main()
{
C1 c1;
C2 c2;
doit( std::make_pair(c1,c2), 1);
return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>
// -------------------------------------------------------------
// Derivatives
// -------------------------------------------------------------
struct VanillaOption {
double K;
double T;
};
// -------------------------------------------------------------
// Market models
// -------------------------------------------------------------
struct GBMModel {
typedef double state_type;
double sigma;
double r;
double q;
};
struct VolCurveModel {
typedef double state_type;
double r;
double q;
std::vector<double> sigma_val;
std::vector<double> sigma_t;
};
struct StochVolModel {
struct state_type {
double S0;
double V0;
};
double vol_vol;
double vol_mean_rev_rate;
double vol_mr_level;
double price_vol_vol;
double r;
double q;
};
// -------------------------------------------------------------
// simple generic curve lookup: should go into some generic algorith section
// -------------------------------------------------------------
template <typename V>
double f_lookup(const V& x, const V& f_x, double lookup_x) {
auto it = std::upper_bound(std::begin(x), std::end(x), lookup_x);
std::size_t index = it - std::begin(x);
return f_x[index];
}
// -------------------------------------------------------------
// Policies
// -------------------------------------------------------------
struct DumpPolicy {
template <typename V>
double vol_interpolation_method(const V& x, const V& y, double x_lookup) const {
std::cout << "We are doing the DumpPolicy interpolation." << std::endl;
return y[0];
}
};
struct SimplePolicy {
template <typename V>
double vol_interpolation_method(const V& x, const V& y, double x_lookup) const {
std::cout << "We are doing simple interpolation." << std::endl;
return f_lookup(x, y, x_lookup);
}
};
// I'm sure you can come up with a much better vol interpolation policy! Linear? spines?
// Feel free to inject your choice, you won't need to modify this code.
// -------------------------------------------------------------
// Model Traits
// -------------------------------------------------------------
// tags used in traits
struct constant_vol_tag {};
struct time_dependent_vol_tag {};
struct stochastic_vol_tag {};
template <typename T>
struct ModelTraits {
typedef constant_vol_tag vol_type;
static const bool constant_vol = true;
};
template <>
struct ModelTraits<GBMModel> {
typedef constant_vol_tag vol_type;
static const int factors = 1;
};
template <>
struct ModelTraits<VolCurveModel> {
typedef time_dependent_vol_tag vol_type;
static const int factors = 1;
};
template <>
struct ModelTraits<StochVolModel> {
typedef stochastic_vol_tag vol_type;
static const int factors = 2;
};
// -------------------------------------------------------------
// Tag dispatching
// -------------------------------------------------------------
namespace detail {
template <typename D, typename M, typename P>
double price(D& d, M& m, typename M::state_type& s, P& p, constant_vol_tag) {
std::cout << "todo: replace this with Black & Scholes formula" << std::endl;
return 1;
}
template <typename D, typename M, typename P>
double price(D& d, M& m, typename M::state_type& s, P& p, time_dependent_vol_tag) {
std::cout << "first lookup vol, then just forwards it to the B&S formula" << std::endl;
// here we use the policy
double vol = p.vol_interpolation_method(m.sigma_t, m.sigma_val, d.T);
GBMModel m_new{vol, m.r, m.q};
GBMModel::state_type s_new{s};
return detail::price(d, m_new, s_new, p, constant_vol_tag{});
}
template <typename D, typename M, typename P>
double price(D& d, M& m, typename M::state_type& s, P& p, stochastic_vol_tag) {
std::cout << "stoch vol is not suported yet" << std::endl;
return 4;
}
}
template <typename D, typename M, typename Policy = SimplePolicy>
double price(D& d, M& m, typename M::state_type& s, Policy p = Policy()) {
return detail::price(d, m, s, p, typename ModelTraits<M>::vol_type{});
}
// -------------------------------------------------------------
// Main
// -------------------------------------------------------------
int main() {
VanillaOption d{100.0, 3.0};
GBMModel m{0.4, 0.05, 0.0};
GBMModel::state_type mS0{100.0};
std::cout << price(d, m, mS0) << std::endl;
VolCurveModel v{0.0, 0.0};
v.sigma_val = {0.5, 0.4, 0.3, 0.25, 0.2 };
v.sigma_t = {0.0, 1.0, 2.0, 3.0, 4.0};
VolCurveModel::state_type vS0{100.0};
std::cout << price(d, v, vS0) << std::endl;
// A different interpolation Policy
std::cout << price(d, v, vS0, DumpPolicy{}) << std::endl;
return 0;
}
todo: replace this with Black & Scholes formula
1
first lookup vol, then just forwards it to the B&S formula
We are doing simple interpolation.
todo: replace this with Black & Scholes formula
1
first lookup vol, then just forwards it to the B&S formula
We are doing the DumpPolicy interpolation.
todo: replace this with Black & Scholes formula
1
// C++ 98 approach
// Data storage types
template <typename T>
struct Storage
{
// Possible ADTs and their memory allocators
//typedef std::vector<T, CustomAllocator<T>> type;
//typedef std::vector<T, std::allocator<T>> type;
typedef std::list<T, std::allocator<T>> type;
};
// An example of Composition; Client has an embedded Storage (e.g. list, vector)
// which has itself an embedded memory allocator. We see an example of a Strategy
// (ADT) with its own embedded Bridge (memory alloc).
//
template <typename T>
class Client
{ // An example of Composition
private:
typename Storage<T>::type data; // typename mandatory
public:
Client(int n, const T& val) : data(n, val) {}
void print() const
{
std::for_each(data.begin(), data.end(), [](const T& n) { std::cout << n << ","; });
std::cout << '\n';
}
};
// C++11/14 approach
// a. No need for a struct
template <typename T>
using StorageII = std::list<T, std::allocator<T>>;
// b. Client code is easier (a 'readable' Composition)
template <typename T>
class ClientII
{ // An example of Composition
private:
StorageII<T> data;
public:
ClientII(int n, const T& val) : data(n, val) {}
void print() const
{
std::for_each(data.begin(), data.end(), [](const T& n) { std::cout << n << ","; });
std::cout << '\n';
}
};