Appendix
Quantlab types and C++
This table shows the types currently in the Quantlab language. The “Type” column presents the type name in Quantlab. The “Identifier” column shows the string used for declaring a parameter or a return value using the arg_spec function. The “Representation” column is how the type is practically represented when used as parameters and return values in your code. The “Test” column presents the testing function to see if a value is valid or not, used when elements are extracted from series, vectors and so on. Strings and handles are pointers that will be Null if they are not valid, so the only specific Quantlab testing functions are valid and valid_date, all others are tested with the common if statement.
Type |
Identifier for arg_spec |
Representation |
Test |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vector of <type> |
|
|
|
matrix of <type> |
|
|
|
series of <type> |
|
|
|
series of vectors of <type> |
|
|
|
series of matrices of <type> |
|
|
|
Interface
Converting to C++ string
const char* QL::get_string(const QL::object&) // does a checked_cast to string
const char* QL::get_string_0(const QL::object*) // returns null pointer if null
Converting argument vector to C++ vector
Get the number of elements, to create the corresponding C++ vector:
int_t get_v_size(const object&)
Extract the elements using one of the following functions, where all
functions have arguments (const object&
, int_t
); the first argument
being the vector and the second the index.
Function |
Return type |
QL type |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Example (vector of strings):
int n = QL::get_v_size(val);
vector<string> val_vec(n);
for (int i = 0; i < n; ++i)
{
QL::handle string_h = QL::get_v_s(val, i);
if (string_h)
{
// Ignore null strings, since std:string cannot be null
// Had we used char\* instead of std:string, we could have called
// QL::get_string_0 and gotten a null pointer if string was null.
val_vec[i] = QL::get_string(*string_h);
}
}
Functions and member functions
QL::arg_spec ret;
QL::arg_spec args[<size>]; // the Array must be big enough to handle all your function declarations
void my_func(<arg1>, <arg2>...)
r = QL::arg_spec("<QLang argument type>"); // e.g QL::arg_spec("void")
args[<index number>] = QL::arg_spec("<QLang argument type>", "<Argument name in QLang>");
// e.g QL::arg_spec("string", "name")
QL::add_func(ctxt,
reinterpret_cast<QL::function_t*>(my_func),
"my_ql_function",
category,
ret,
<number of arguments>,
args);
void my_member_func(myspace::my &m, <arg1>, <arg2>...)
r = QL::arg_spec("<QLang argument type>"); // e.g QL::arg_spec("void")
args[<index number>] = QL::arg_spec("<QLang argument type>",
"<Argument name in QLang>"); // e.g QL::arg_spec("string", "name")
QL::add_mem_func(ctxt,
reinterpret_cast<QL::function_t*>(my_member_func),
"my_ql_obj",
"my_ql_function",
category,
ret,
<number of arguments>,
args);
Exceptions
Very important to catch all exceptions generated in your QLL or from libraries that you have used (e.g. C++ Windows Standard Libary) or Quantlab will crash.
You must not though catch std::bad_alloc
since Quantlab will handle that
Example code
my.h
#pragma once
#include <QL/api/ql.h>
namespace myspace {
class my;
typedef QL::handle_t<my> my_p;
}
class myspace::my : public QL::object
{
private:
explicit my() {}
public:
~my() {}
private:
my(const my&);
my &operator=(const my &);
public:
static my_p create()
{
return my_p(new my());
}
void set_name(const char *s)
{
name.assign(s);
}
private:
std::string name;
};
my.cpp
#include "my.h"
static const char category[] = "My Utilities";
static myspace::my_p create_my()
{
try
{
myspace::my_p m = myspace::my::create();
return m;
}
catch (const std::bad_alloc &)
{
throw;
}
catch (const std::exception &e)
{
throw QL::error(QL::E_UNSPECIFIC, e.what());
}
}
QL::handle my_hello(myspace::my &m)
{
try
{
return QL::create_string("HELLO WORLD");
}
catch (const std::bad_alloc &)
{
throw;
}
catch (const std::exception &e)
{
throw QL::error(QL::E_UNSPECIFIC, e.what());
}
}
void my_set_name(myspace::my &m, QL::object &name)
{
if (strlen(QL::get_string(name)) > 50)
throw QL::error(QL::E_INVALID_ARG, "Too long name");
m.set_name(QL::get_string(name));
}
__declspec(dllexport) void ql_init(QL::symtab &s)
{
QL::symtab &ctxt = QL::add_module(s, "my_util");
QL::arg_spec r;
QL::arg_spec a[12];
QL::add_object_type(ctxt, "my", 0, category);
r = QL::arg_spec("my");
QL::add_func(ctxt,
reinterpret_cast<QL::function_t*>(create_my),
"my",
category,
r,
0,
a);
r = QL::arg_spec("string");
QL::add_mem_func(ctxt,
reinterpret_cast<QL::function_t*>(my_hello),
"my",
"hello",
r,
0,
a);
r = QL::arg_spec("void");
a[0] = QL::arg_spec("string");
QL::add_mem_func(ctxt,
reinterpret_cast<QL::function_t*>(my_set_name),
"my",
"set_name",
r,
1,
a);
}
API specific functions in ql_base.h
This table presents the functions in the header file ql_base, as these are mostly only available through the Quantlab API and cannot be accessed using QLang.
API function |
Header filer |
Description |
---|---|---|
|
|
Tests if a number or logical is valid. |
|
|
Returns an invalid number. |
|
|
Throws an error that is caught by Quantlab. |
|
|
Returns the size of a vector. |
|
|
Returns the number of rows and columns of a matrix. |
|
|
Returns the number of ranges on which a series is based. |
|
|
Returns the starting number, date or timestamp of one of the ranges of a series. |
|
|
Returns the step length of one of the ranges of a series. |
|
|
Returns the number of elements of one of the ranges of a series. |
|
|
Tests if a range is a number range. |
|
|
Tests if a range is a date range. |
|
|
Tests if a range is a timestamp range. |
|
|
Returns the total number of elements of a series, a multiplication of the sizes of all ranges. |
|
|
Returns the range number, date or timestamp corresponding to one element of a series. |
|
|
Returns the size of each vector in a series of vectors. |
|
|
Returns the number of rows and columns of each matrix in a series of matrices. |
|
|
Returns one element of a vector of numbers. |
|
|
Returns one element of a vector of objects. |
|
|
Returns one element of a matrix of numbers. |
|
|
Returns one element of a matrix of objects. |
|
|
Returns one element of a series of numbers. |
|
|
Returns one element of a series of objects. |
|
|
Returns one element of a series of vectors of numbers. |
|
|
Returns one element of a series of vectors of objects. |
|
|
Returns one element of a series of matrices of numbers. |
|
|
Returns one element of a series of matrices of objects. |
|
|
Creates a vector of numbers. |
|
|
Creates a vector of objects. |
|
|
Creates a matrix of numbers. |
|
|
Creates a matrix of objects. |
|
|
Creates a number range. |
|
|
Creates a date range. |
|
|
Creates a timestamp range. |
|
|
Creates a series of numbers. |
|
|
Creates a series of objects. |
|
|
Creates a series of vectors of numbers. |
|
|
Creates a series of vectors of objects. |
|
|
Creates a series of matrices of numbers. |
|
|
Creates a series of matrices of objects. |
|
|
Sets one element of a vector of numbers. |
|
|
Sets one element of a vector of objects. |
|
|
Sets one element of a matrix of numbers. |
|
|
Sets one element of a matrix of objects. |
|
|
Sets one element of a series of numbers. |
|
|
Sets one element of a series of objects. |
|
|
Sets one element of a series of vectors of numbers. |
|
|
Sets one element of a series of vectors of objects. |
|
|
Sets one element of a series of matrices of numbers. |
|
|
Sets one element of a series of matrices of objects. |
Example “pair”: Adding an object type and its member functions
This is an example showing how to add the object type “pair” to Quantlab with a creation function and member functions. The “pair” object is simply a container of two numbers, created in Quantlab using the pair function. The two components can be extracted using the member functions first and last on the pair object.
#include "ql.h"
namespace
{
class pair;
typedef QL::handle_t<const pair> pair_p;
// Declaring the pair class from the basic QL object class.
class pair : public QL::object
{
// Declaring a creation function.
public:
static pair_p create(double first, double last)
{
return pair_p(new pair(first, last));
}
// Hiding the object creation to avoid reference problems.
private:
pair(double first, double last) : first(first), last(last) {}
public:
double first;
double last;
};
}
/* Creates a pair object. */
static void create_pair(pair_p &result, double first, double last)
{
result = pair::create(first, last);
}
/* Extracts the first element of a pair. */
static double pair_first(const pair_p &p)
{
return p->first;
}
/* Extracts the last element of a pair. */
static double pair_last(const pair_p &p)
{
return p->last;
}
/*
* Adds the pair object type, a pair creation function and two member functions
* to the Quantlab context.
*/
__declspec(dllexport) void ql_init(QL::symtab &ctxt)
{
QL::arg_spec r;
QL::arg_spec a[12];
QL::add_object_type(ctxt, "pair");
r = QL::arg_spec("pair");
a[0] = QL::arg_spec("number", "first");
a[1] = QL::arg_spec("number", "last");
QL::add_func(ctxt,
reinterpret_cast<QL::function_t*>(create_pair),
"pair",
"Pairs of numbers",
r,
2,
a);
r = QL::arg_spec("number");
QL::add_mem_func(ctxt,
reinterpret_cast<QL::function_t*>(pair_first),
"pair",
"first",
r,
0,
a);
QL::add_mem_func(ctxt,
reinterpret_cast<QL::function_t*>(pair_last),
"pair",
"last",
r,
0,
a);
}
A series example
This is an example of how to access and use a series. The function my_average will calculate the average of a given series.
#include "ql.h"
static const char category[] = "Series example";
static double average(const QL::handle &s)
{
int size = QL::get_s_total_size(s);
int count = 0;
double sum = 0;
for (int i = 0 ; i < size ; i++)
{
double s_i = QL::get_s_num(s, i);
if (QL::valid(s_i))
{
sum += s_i;
++count;
}
}
return sum / count;
}
__declspec(dllexport) void ql_init(QL::symtab &ctxt)
{
QL::arg_spec r;
QL::arg_spec a[12];
r = QL::arg_spec("number");
a[0] = QL::arg_spec("series(number)", "s");
QL::add_func(ctxt,
reinterpret_cast<QL::function_t*>(average),
"my_average",
category,
r,
1,
a);
}
Blending functions
This is an example of how to blend yield curves. In a typical case you want to combine deposit rates, short-term futures or FRA:s (below, both are called FRA:s) and swap rates to form a complete yield curve. The number of deposit rates will vary depending on the time to the fixing of the first FRA. In the transition from deposit to FRA:s the standard method is to interpolate between the two nearest deposit rates in order to construct a loan that matures on the settlement date of the FRA. This is done by calling the Quantlab function curve_chop_long_interp. In this example, four different functions on this theme are created.
As an alternative, these functions can be created in the Quantlab language directly, which is shown in Blending functions: Corresponding QLang implementation.
#include "ql.h"
static const char category[] = "Curve blending";
static double min_maturity(const QL::handle &curve)
{
QL::handle v = QL::curve_instruments(curve);
int n = QL::get_v_size(v);
return QL::instr_maturity(QL::get_v_obj(v, 0));
}
static double max_maturity(const QL::handle &curve)
{
QL::handle v = QL::curve_instruments(curve);
int n = QL::get_v_size(v);
return QL::instr_maturity(QL::get_v_obj(v, n - 1));
}
static double min_settle(const QL::handle &curve)
{
QL::handle v = QL::curve_instruments(curve);
int n = QL::get_v_size(v);
double min, tmp;
min = QL::instr_settle_date(QL::get_v_obj(v, 0));
for (int i = 1; i < n ; i++)
{
tmp = QL::instr_settle_date(QL::get_v_obj(v, i));
if (min > tmp)
min = tmp;
}
return min;
}
static void blend_depo_swap(QL::handle &result,
const QL::handle &depo_curve,
const QL::handle &swap_curve,
const QL::handle &ctxt)
{
if (QL::curve_empty(swap_curve))
result = depo_curve;
else
{
double d = min_maturity(swap_curve) - 1;
QL::handle c_short = QL::curve_chop_long(depo_curve, d);
result = QL::curve_merge(c_short, swap_curve);
}
}
static void blend_depo_fra(QL::handle &result,
const QL::handle &depo_curve,
const QL::handle &fra_curve,
const QL::handle &ctxt)
{
if (QL::curve_empty(fra_curve))
result = depo_curve;
else
{
double d = min_settle(fra_curve);
QL::handle c_short = QL::curve_chop_long_interp(depo_curve, d, ctxt);
result = QL::curve_merge(c_short, fra_curve);
}
}
static void blend_fra_prio(QL::handle &result,
const QL::handle &depo_curve,
const QL::handle &fra_curve,
const QL::handle &swap_curve,
const QL::handle &ctxt)
{
if (QL::curve_empty(fra_curve))
blend_depo_swap(result, depo_curve, swap_curve, ctxt);
else
{
double d;
QL::handle c_short, c_long;
d = min_settle(fra_curve);
c_short = QL::curve_chop_long_interp(depo_curve, d, ctxt);
c_short = QL::curve_merge(c_short, fra_curve);
d = max_maturity(c_short) + 1;
c_long = QL::curve_chop_short(swap_curve, d);
result = QL::curve_merge(c_short, c_long);
}
}
static void blend_swap_prio(QL::handle &result,
const QL::handle &depo_curve,
const QL::handle &fra_curve,
const QL::handle &swap_curve,
const QL::handle &ctxt)
{
if (QL::curve_empty(swap_curve))
blend_depo_fra(result, depo_curve, swap_curve, ctxt);
else
{
double d;
QL::handle c_mid, c_short;
d = min_maturity(swap_curve) - 1;
c_mid = QL::curve_chop_long(fra_curve, d);
if (QL::curve_empty(c_mid))
blend_depo_swap(result, depo_curve, swap_curve, ctxt);
else
{
d = min_settle(c_mid);
c_short = QL::curve_chop_long_interp(depo_curve, d, ctxt);
c_short = QL::curve_merge(c_short, c_mid);
result = QL::curve_merge(c_short, swap_curve);
}
}
}
__declspec(dllexport) void ql_init(QL::symtab &ctxt)
{
QL::arg_spec r;
QL::arg_spec a[12];
r = QL::arg_spec("curve");
a[0] = QL::arg_spec("curve", "depo_curve");
a[1] = QL::arg_spec("curve", "swap_curve");
a[2] = QL::ctxt_arg();
QL::add_func(ctxt,
reinterpret_cast<QL::function_t*>(blend_depo_swap),
"blend_curves_depo_swap",
category,
r,
3,
a);
r = QL::arg_spec("curve");
a[0] = QL::arg_spec("curve", "depo_curve");
a[1] = QL::arg_spec("curve", "fra_curve");
a[2] = QL::ctxt_arg();
QL::add_func(ctxt,
reinterpret_cast<QL::function_t*>(blend_depo_fra),
"blend_curves_depo_fra",
category,
r,
3,
a);
r = QL::arg_spec("curve");
a[0] = QL::arg_spec("curve", "depo_curve");
a[1] = QL::arg_spec("curve", "fra_curve");
a[2] = QL::arg_spec("curve", "swap_curve");
a[3] = QL::ctxt_arg();
QL::add_func(ctxt,
reinterpret_cast<QL::function_t*>(blend_fra_prio),
"blend_curves_fra_prio",
category,
r,
4,
a);
QL::add_func(ctxt,
reinterpret_cast<QL::function_t*>(blend_swap_prio),
"blend_curves_swap_prio",
category,
r,
4,
a);
}
Blending functions: Corresponding QLang implementation
This is an example of how to write the blending functions in Blending functions, using the Quantlab language only. It is stated here for comparison to the code in Blending functions.
curve blend_curves_depo_swap(curve depo_c, curve swap_c)
option(category: 'Curve blending')
{
if (swap_c.empty())
return depo_c;
else
{
depo_c = depo_c.chop_long(v_min(swap_c.instruments.maturity) - 1);
return merge(depo_c, swap_c);
}
}
curve blend_curves_depo_fra(curve depo_c, curve fra_c)
option(category: 'Curve blending')
{
if (fra_c.empty())
return depo_c;
else
{
depo_c = depo_c.chop_long_interp(v_min(fra_c.instruments.settle_date));
return merge(depo_c, fra_c);
}
}
curve blend_curves_fra_prio(curve depo_c, curve fra_c, curve swap_c)
option(category: 'Curve blending')
{
if (fra_c.empty())
return blend_curves_depo_swap(depo_c, swap_c);
else
{
date chop_d;
curve short_c;
chop_d = v_min(fra_c.instruments.settle_date);
depo_c = depo_c.chop_long_interp(chop_d);
short_c = merge(depo_c, fra_c);
chop_d = v_max(short_c.instruments.maturity) + 1;
swap_c = swap_c.chop_short(chop_d);
return merge(short_c, swap_c);
}
}
curve blend_curves_swap_prio(curve depo_c, curve fra_c, curve swap_c)
option(category: 'Curve blending')
{
if (swap_c.empty())
return blend_curves_depo_fra(depo_c, fra_c);
else
{
fra_c = fra_c.chop_long(v_min(swap_c.instruments.maturity) - 1);
if (fra_c.empty())
return blend_curves_depo_swap(depo_c, swap_c);
else
{
date chop_d;
chop_d = v_min(fra_c.instruments.settle_date);
depo_c = depo_c.chop_long_interp(chop_d);
return merge(depo_c, merge(fra_c, swap_c));
}
}
}
Exponentially weighted series functions
This is an example of how to calculate exponentially weighted versions of the series functions variance, covariance and correlation. It is also an example of how to make the difference between the Quantlab interface functions and the normal C++ functions not using QLang types. The source code can be viewed by opening the exponential_roll project, and it is not printed here due to its length.
The functions added to QLang by the dll all take one or more series plus a decay factor and an optional tolerance level. The first step is to check that the series are one-dimensional and that they are based on the same range. After that, the series are translated into vectors (or matrices) defined for this project in order to move away from the QLang environment. In the translation process the “holes” (null values) in the series are taken care of, making the resulting vectors tested and clean. The weight vector is also filled with exponential weights at this step. The final step is to call the QLang-independent function covariance_weighted, with the translated and tested input data. Covariance_weighted returns the covariance between two series, and using this information the QLang return objects are filled with appropriate data for transport back to the Quantlab environment.
This implementation ignores the index (“day”) of a set of series where there is one or more “holes”, removing them in the translation process. It will also cut off the calculations when the weight of the remaining indices becomes smaller than the tolerance level. For example, with a tolerance level of 1% (0.01, the default) and a decay factor of 0.97, calculations are based on the last 151 elements of the translated series as the weights of 152 to infinity accounts for less than 1% of the total weight.
The exponential weight vector is constructed from the end. Starting at the ending element with the weight of the decay factor, the element one step towards the beginning of the vector has this weight multiplied by the decay factor. This continues for every element until the start of the weight vector. The main mathematical advantage of this weight scheme is that if one value has been calculated for a series, it is extremely easy to calculate the value of the series with one more element added. This advantage has not been used in this implementation, so there’s plenty of room for performance improvement!
User-defined yield curve models
By using the QL::model_create_custom()
it is possible to define your own
yield curve model, which can be fitted to a term structure by using the
fit()
function just as you would with any of the built in models.
In the following example a new model custom_ns()
is created to implement
the standard Nelson-Siegel model in the same way as the built in ns()
model. This is done by inheriting from the class QL::custom_model
and
implementing the four mandatory virtual functions:
#include <ql.h>
#include <math.h>
namespace
{
class custom_ns : public QL::custom_model
{
public:
/* Indices for the param vector. */
enum { B0, B1, B2, T, N_PARAMS };
static QL::custom_model_p create()
{
return QL::custom_model_p(new custom_ns);
}
virtual int n_params() const
{
return N_PARAMS;
}
virtual double default_param_guess(int p) const
{
switch (p)
{
case B0:
return 0.04;
case B1:
return 0.01;
case B2:
return -0.03;
case T:
return 3;
}
}
/* We need a lower bound on beta_0 for stability */
virtual double default_param_min(int p) const
{
switch (p)
{
case B0:
return 0.001;
default:
return custom_model::default_param_min(p);
}
}
/* Dito upper bound on T */
virtual double default_param_max(int p) const
{
switch (p)
{
case T:
return 30;
default:
return custom_model::default_param_max(p);
}
}
virtual void disc_fact(const double *p,
int n,
const double *t,
double *df)
{
int i = 0;
/*
* t[] is guaranteed to be sorted; we skip possible
* 0 maturities to avoid division by zero below.
*/
while (i < n && t[i] == 0)
df[i++] = 1;
while (i < n)
{
double t_i = t[i];
double x_t = t_i / p[T];
double e = exp(-x_t);
double r;
r = p[B0] + (p[B1] + p[B2]) * (1 - e) / x_t - p[B2] * e;
df[i++] = exp(-r * t_i);
}
}
private:
explicit custom_ns()
{}
~custom_ns()
{}
};
}
static void create_custom_ns(QL::handle &h)
{
h = QL::model_create_custom(custom_ns::create());
}
__declspec(dllexport) void ql_init(QL::symtab &ctxt)
{
QL::arg_spec r;
r = QL::arg_spec("model");
QL::add_func(ctxt, reinterpret_cast<QL::function_t*>(create_custom_ns),
"custom_ns", "Yield curve models", r, 0, 0);
}