Translating QuantLib Python examples to C++
It’s easy enough to translate the Python code shown in this book into the corresponding C++ code. As an example, I’ll go through a bit of code from the notebook on instruments and pricing engines.
In [1]: import QuantLib as ql
This line imports the QuantLib module and provides a shorter alias
that can be used to qualify the classes and functions it contains.
The C++ equivalent would be:
#include <ql/quantlib.hpp>
namespace ql = QuantLib;
In C++, however, I wouldn’t include the global quantlib.hpp header,
which would inflate your compilation times; instead, you can include
the specific headers for the classes you’ll use.
Moreover, in C++ is not as discouraged as in Python to import the whole contents of a namespace in a source file, that is, to use
using namespace QuantLib;
However, the above should not be used in header files; ask the nearest C++ guru if you’re not sure why.
In [2]: today = ql.Date(7, ql.March, 2014)
ql.Settings.instance().evaluationDate = today
The code above has a couple of caveats. The first line is easy enough
to translate; you’ll have to declare the type to the variable (or use
auto if you’re compiling in C++11 mode). The second line is
trickier. To begin with, the syntax to call static methods differs in
Python and C++, so you’ll have to replace the dot before instance by
a double colon (the same goes for the namespace qualifications).
Then, evaluationDate is a property in Python but a method in C++; it
was changed in the Python module to be more idiomatic, since it’s not
that usual in Python to assign to the result of a method. Luckily, you
won’t find many such cases. The translated code is:
ql::Date today(7, ql::March, 2014);
ql::Settings::instance().evaluationDate() = today;
Next:
In [3]: option = ql.EuropeanOption(ql.PlainVanillaPayoff(ql.Option.Call, 100.0),
ql.EuropeanExercise(ql.Date(7, ql.June, 2014)))
Again, you’ll have to declare the type of the variable. Furthermore,
the constructor of EuropeanOption takes its arguments by pointer, or
more precisely, by boost::shared_ptr. This is hidden in Python,
since there’s no concept of pointer in the language; the SWIG wrappers
take care of exporting boost::shared_ptr<T> simply as T.
The corresponding C++ code (note also the double colon in Option::Call):
ql::EuropeanOption option(
boost::make_shared<ql::PlainVanillaPayoff>(ql::Option::Call, 100.0),
boost::make_shared<ql::EuropeanExercise(ql::Date(7, ql::June, 2014)));
(A note: in the remainder of the example, I’ll omit the boost:: and
ql:: namespaces for brevity.)
In [4]: u = ql.SimpleQuote(100.0)
r = ql.SimpleQuote(0.01)
sigma = ql.SimpleQuote(0.20)
Quotes, too, are stored and passed around as shared_ptr instances;
this is the case for most polymorphic classes (when in doubt, you can
look at the C++ headers and check the signatures of the functions you
want to call). The above becomes:
shared_ptr<SimpleQuote> u = make_shared<SimpleQuote>(100.0);
shared_ptr<SimpleQuote> r = make_shared<SimpleQuote>(0.01);
shared_ptr<SimpleQuote> sigma = make_shared<SimpleQuote>(0.20);
Depending on what you need to do with them, the variables might also
be declared as shared_ptr<Quote>. I used the above, since I’ll need
to call a method of SimpleQuote in a later part of the code.
In [5]: riskFreeCurve = ql.FlatForward(0, ql.TARGET(),
ql.QuoteHandle(r), ql.Actual360())
volatility = ql.BlackConstantVol(0, ql.TARGET(),
ql.QuoteHandle(sigma), ql.Actual360())
The Handle template class couldn’t be exported as such, because
Python doesn’t have templates. Thus, the SWIG wrappers have to declare
separate classes QuoteHandle, YieldTermStructureHandle and so on.
In C++, you can go back to the original syntax.
shared_ptr<YieldTermStructure> riskFreeCurve =
make_shared<FlatForward>(0, TARGET(),
Handle<Quote>(r), Actual360());
shared_ptr<BlackVolTermStructure> volatility =
make_shared<BlackConstantVol>(0, TARGET(),
Handle<Quote>(sigma), Actual360());
Next,
In [6]: process = ql.BlackScholesProcess(ql.QuoteHandle(u),
ql.YieldTermStructureHandle(riskFreeCurve),
ql.BlackVolTermStructureHandle(volatility))
turns into
shared_ptr<BlackScholesProcess> process =
make_shared<BlackConstantVol>(
Handle<Quote>(u),
Handle<YieldTermStructure>(riskFreeCurve),
Handle<BlackVolTermStructure>(volatility));
and
In [7]: engine = ql.AnalyticEuropeanEngine(process)
into
shared_ptr<PricingEngine> engine =
make_shared<AnalyticEuropeanEngine>(process);
So far, we’ve been calling constructors. Method invocation works the same in Python and C++, except that in C++ we might be calling methods through a pointer. Therefore,
In [8]: option.setPricingEngine(engine)
In [9]: print(option.NPV())
In [10]: print(option.delta())
print(option.gamma())
print(option.vega())
where option is an object in C++, becomes
option.setPricingEngine(engine);
cout << option.NPV() << endl;
cout << option.delta() << endl;
cout << option.gamma() << endl;
cout << option.vega() << endl;
whereas
In [11]: u.setValue(105.0)
since u is a (smart) pointer, turns into
u->setValue(105.0);
Of course, the direct translation I’ve been doing only applies to the
QuantLib code; I’m not able to point you to libraries that replace the
graphing functionality in matplotlib, or the data-analysis
facilities in pandas, or the parallel math functions in
numpy. However, I hope that the above can still enable you to
extract value from this cookbook, even if you’re programming in C++.