SERVING THE QUANTITATIVE FINANCE COMMUNITY

 
User avatar
Cuchulainn
Topic Author
Posts: 59685
Joined: July 16th, 2004, 7:38 am
Location: Amsterdam
Contact:

Re: Duck Typing

May 22nd, 2017, 12:42 pm

Since it is a PDE, the variables S and t are function arguments indeed but vol does not deserve the same status. It is a special case, e.g, PDEs that do not have a sigma parameter in diffusion function.
 
User avatar
outrun
Posts: 4573
Joined: April 29th, 2016, 1:40 pm

Re: Duck Typing

May 22nd, 2017, 12:58 pm

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.
 
User avatar
Cuchulainn
Topic Author
Posts: 59685
Joined: July 16th, 2004, 7:38 am
Location: Amsterdam
Contact:

Re: Duck Typing

May 24th, 2017, 6:48 am

// 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';
 }


}
outrun:define a non-member function like:

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.. 
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.
Maybe there are more options. What do you think?

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.
Or somebody could bring what is needed to the PDE; then the PDE does not have to know nothing?
 
User avatar
outrun
Posts: 4573
Joined: April 29th, 2016, 1:40 pm

Re: Duck Typing

May 24th, 2017, 9:04 am

Yes, and you can then implement "model traits" that allow you to switch to more efficient Pde solver implementations when the model has e.g. constant volatility. E.g. the routine below could be called inside the S and t loop, but if vol was constant than it could allow you to pre-compute things *outside* the loops. A model with constant vol would also not have a member funtion "vm.vol(S,t)" but instead perhaps have "vm.vol()". 

The traits (model_trait<T>::has_const_vol) could e.g. be implemented based on tag dispatching (the mode writer has to explicitly add member types to the VolModel class which the PDE solver recognises, and can use to inspect the model type using the traits mechanism). You can also encode the information not via tags, but via member function names or member function signatures. E.g. you can demand in your design that a constant vol model needs to have a public "vol" member variable and that a time dependent vol model needs to have a vol(double t) member function (encoded in arg) or a vol_t(double t) member function (encoded in function name).  The tag dispatching scales better though. You can explicitly tag groups of models with all sort of info like "has stochastic vol" without abusing the interface.
template <typename VolModel>
 double diffusion(VolModel& vm, double S, double t)
 {
 return vm(S, t);
 }
 
User avatar
Cuchulainn
Topic Author
Posts: 59685
Joined: July 16th, 2004, 7:38 am
Location: Amsterdam
Contact:

Re: Duck Typing

May 24th, 2017, 2:07 pm

A model with constant vol would also not have a member funtion "vm.vol(S,t)" but instead perhaps have "vm.vol()". 

One solution is variadics. Would this be a solution for everything? I like variadics.

In any solution, a clear interface between server and client is needed, at the very least.
 
User avatar
outrun
Posts: 4573
Joined: April 29th, 2016, 1:40 pm

Re: Duck Typing

May 24th, 2017, 2:39 pm

Yes variadics are nice.

I meant something else, if some set of models have constant vol then it doesn't make sense to demand (in the overall interface of the design) that those models implement a vol(S,t) member function -since the vol doesn't depend on S and t-.

At the same time the PDE solver also needs to know if a specific mode has a vol that depends of S and t or not. If it doens't then it can optimize calculations, e.g. it would be pointless to call vols(S,t) many times as it traverses the discretised S,t grid. One time is enough for constant vol models.

So, I would first try to define the various interfaces that SDE models can have. Some models have constant vol, others have a surface, of stochastic vol.. each type would need to capture different data inside the object, and provide different member functions to extract that data. Once that's settled and when you have "types" defined, you can then use overloading or type traits to dispatch compile time?

Overloading is very readable and simple but traits are more flexible. E.g. maybe your PDE solver will want pick the same algorithms for a range of model types. You don't want to write the same overloader for all those types, it's more elegant to associate some common trait to all those model types.
 
User avatar
Cuchulainn
Topic Author
Posts: 59685
Joined: July 16th, 2004, 7:38 am
Location: Amsterdam
Contact:

Re: Duck Typing

June 3rd, 2017, 11:28 am

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.
1. Don't suppose you have a machine-readable 101 test case (i.e. kick the tyres example)?
2. What's the relationship with PBD (if any)?
https://en.wikipedia.org/wiki/Policy-based_design
 
User avatar
outrun
Posts: 4573
Joined: April 29th, 2016, 1:40 pm

Re: Duck Typing

June 3rd, 2017, 12:58 pm

I'll try to write a 1)

Policy based design use classed to inject behaviour into a template. E.g. boost math has error handeling policies that allow *you* to specify how you want it to do error handeling. It might be a class with a member function on_error() { do something }. You can create your own policy class -with your own on_error() member function and your own "do something" code, and boost math will then run *your* code whenever it has an error. Since it's your code it'll be running it's called "behaviour injection"

Traits are classed to extract or associate information about types. Iterators has traits that allow you to extract the value type, but also e.g. if it's an forward only iterator. It that's the case then the iterator will not support "move_back" and you can switch to a forward only type algorithm in your code. the switch is compile time

Tags are typically typedefs inside a class to store extra information about that type. It's closely related to traits, traits often look for tags in types. These tags can be added to call signatures which allow you to implement different bits of code for each tag (or combination of tags). 

Later  more about 1)!
Last edited by outrun on June 3rd, 2017, 1:14 pm, edited 5 times in total.
 
User avatar
outrun
Posts: 4573
Joined: April 29th, 2016, 1:40 pm

Re: Duck Typing

June 3rd, 2017, 1:02 pm

.. Sometimes you need to write code that processes 3rd party code which you can't modify (maybe QF vanilla call option objects). In that case you can't decorate those 3rd party types with tags (vanilla_type, call_type). Traits help you associate your tags with the 3rd party types.
 
User avatar
outrun
Posts: 4573
Joined: April 29th, 2016, 1:40 pm

Re: Duck Typing

June 3rd, 2017, 1:19 pm

How about you come up with a design case, and then we can discuss how we can apply tag dispatching, trails and policies?
 
User avatar
Cuchulainn
Topic Author
Posts: 59685
Joined: July 16th, 2004, 7:38 am
Location: Amsterdam
Contact:

Re: Duck Typing

June 3rd, 2017, 1:20 pm

Looking forward to 1)

BTW here is an example. Can it be categorised or is it a trick? It works but I am wondering how useful it is.
IMO it quacks like a duck. 

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);

}
 
User avatar
outrun
Posts: 4573
Joined: April 29th, 2016, 1:40 pm

Re: Duck Typing

June 3rd, 2017, 1:43 pm

That's a good case!

I like designs with non-member functions because it keeps the class interfaces compact and allows you extend you design (e.g. add doittwice) without having to touch your classes C1 and C2.

So what I tried here is to rid of C and replace it with a std::pair because it's function didn't seen anything other than to clue two types together in ordered fashion.
Also, I notices that for C2 the single argument doit concept translates to (n, -n)

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;
}
 
User avatar
outrun
Posts: 4573
Joined: April 29th, 2016, 1:40 pm

Re: Duck Typing

June 3rd, 2017, 8:21 pm

Here is an attempt to model a bit of typical QF stuff, making use of traits, tags, tag dispatching and policies!

Sorry for the mess, it not good quality, more an informal demo of techniques & concept to help get the discussion started :-) It *does* compile at IDEONE
#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;
}
 
User avatar
outrun
Posts: 4573
Joined: April 29th, 2016, 1:40 pm

Re: Duck Typing

June 3rd, 2017, 8:24 pm

output:
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
 
User avatar
Cuchulainn
Topic Author
Posts: 59685
Joined: July 16th, 2004, 7:38 am
Location: Amsterdam
Contact:

Re: Duck Typing

June 6th, 2017, 2:01 pm

This code also runs fine on VS2015. I am studying the design and using aliias template instead of typedef (Item 9 from reverand Meyers who has a good account). My test case is below and I would like to replace the typedef stuff by this C++11 improvement. It's cleaner and less clunky. 
Is this a good idea in this case as well?
// 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';
 }

 };

ABOUT WILMOTT

PW by JB

Wilmott.com has been "Serving the Quantitative Finance Community" since 2001. Continued...


Twitter LinkedIn Instagram

JOBS BOARD

JOBS BOARD

Looking for a quant job, risk, algo trading,...? Browse jobs here...


GZIP: On