QuantLib - Python扩展
本页介绍如何通过SWIG向QuantLib的接口中添加新的功能
介绍
QuantLib是基于C++的金融计算库。但是为了方便使用,官方维护了QuantLib-SWIG项目来自动化的生成各种动态语言的接口,其中最为典型的即为python。通过修改SWIG的.i文件,用户可以方便的在python中为C++库中的功能添加新的功能。
项目
为了生成QuantLib的python接口,需要下载QuantLib和QuantLib-SWIG两套源码,自行编译。这里作者做了一个统一的项目qlengine,将这些代码整合在一起,并且提供了方便使用的编译脚本。下面的介绍都是基于qlenigne项目。
前期准备
C++编译器,在Linux上推荐g++,在windows上可以为Visual Studio 2015以上;
完整的要求,请见qlengine
修改步骤
git clone https://github.com/ChinaQuants/qlengine
我们这里会给出一个完整的导出一个期权类型以及其相关的定价模型的例子。具体的例子:
SpreadOption
KirkSpreadOptionEngine
在QuantLib根目录的ql/experimental/exoticoptions/spreadoption.hpp中,我们找到了其定义:
class SpreadOption : public MultiAssetOption {
public:
class engine;
SpreadOption(const ext::shared_ptr<PlainVanillaPayoff>& payoff,
const ext::shared_ptr<Exercise>& exercise)
: MultiAssetOption(payoff, exercise) {}
};
在QuantLib根目录的ql/experimental/exoticoptions/kirkspreadoptionengine.hpp,我们找到定价引擎的申明:
class KirkSpreadOptionEngine : public SpreadOption::engine {
public:
KirkSpreadOptionEngine(
const ext::shared_ptr<BlackProcess>& process1,
const ext::shared_ptr<BlackProcess>& process2,
const Handle<Quote>& correlation);
void calculate() const;
private:
ext::shared_ptr<BlackProcess> process1_;
ext::shared_ptr<BlackProcess> process2_;
Handle<Quote> rho_;
};
首先我们考虑导出期权的定义。所有的SWIG导出信息都在QuantLib-SWIG目录下SWIG目录:
我们在SWIG目录下新添加文件option_ext.i作为我们用来添加api的文件。SWIG并不要求.i文件与目标c++的头文件一一对应,所以文件名可以任意。用户甚至可以直接使用已经存在的.i文件,而无需新开。这里新建文件的目的是方便日后的管理。
在ql.i文件中添加一行,确保新文件会进入编译环节:
%include options_ext.i
SWIG的语法非常类似于C++,主要的要求是正确的申明接口的形式。具体的语法请参考文档。我们这边直接给出完整的接口申明代码:
#ifndef quantlib_options_ext_i
#define quantlib_options_ext_i
%include options.i
%{
using QuantLib::SpreadOption;
typedef boost::shared_ptr<Instrument> SpreadOptionPtr;
%}
%rename(SpreadOption) SpreadOptionPtr;
class SpreadOptionPtr : public boost::shared_ptr<Instrument> {
public:
%extend {
SpreadOptionPtr(
const boost::shared_ptr<Payoff>& payoff,
const boost::shared_ptr<Exercise>& exercise) {
boost::shared_ptr<PlainVanillaPayoff> stPayoff =
boost::dynamic_pointer_cast<PlainVanillaPayoff>(payoff);
QL_REQUIRE(stPayoff, "wrong payoff given");
return new SpreadOptionPtr(new SpreadOption(stPayoff, exercise));
}
}
};
#endif
我们下面逐行解释代码:
#ifndef quantlib_options_ext_i
#define quantlib_options_ext_i
......
#endif
上面的部分是C++中常见的头文件保护符。
上面这句是包含当前目录下另外一个已经存在.i文件,一般是有些公用的功能时候需要。
%{
using QuantLib::SpreadOption;
typedef boost::shared_ptr<Instrument> SpreadOptionPtr;
%}
这一部分是嵌入C++的源代码,%{...%}
中间包含的部分会原样的编译为C++代码。
%rename(SpreadOption) SpreadOptionPtr;
class SpreadOptionPtr : public boost::shared_ptr<Instrument> {
public:
%extend {
SpreadOptionPtr(
const boost::shared_ptr<Payoff>& payoff,
const boost::shared_ptr<Exercise>& exercise) {
boost::shared_ptr<PlainVanillaPayoff> stPayoff =
boost::dynamic_pointer_cast<PlainVanillaPayoff>(payoff);
QL_REQUIRE(stPayoff, "wrong payoff given");
return new SpreadOptionPtr(new SpreadOption(stPayoff, exercise));
}
}
};
这部分是核心的导出期权的代码。基本上是原始C++的原样翻版。
类似的,我们可以导出定价引擎部分:
%{
using QuantLib::KirkSpreadOptionEngine;
typedef boost::shared_ptr<PricingEngine> KirkSpreadOptionEnginePtr;
%}
%rename(KirkSpreadOptionEngine) KirkSpreadOptionEnginePtr;
class KirkSpreadOptionEnginePtr
: public boost::shared_ptr<PricingEngine> {
public:
%extend {
KirkSpreadOptionEnginePtr(
const BlackProcessPtr& process1,
const BlackProcessPtr& process2,
const Handle<Quote>& correlation) {
boost::shared_ptr<BlackProcess> bsProcess1 =
boost::dynamic_pointer_cast<BlackProcess>(process1);
boost::shared_ptr<BlackProcess> bsProcess2 =
boost::dynamic_pointer_cast<BlackProcess>(process2);
return new KirkSpreadOptionEnginePtr(
new KirkSpreadOptionEngine(bsProcess1, bsProcess2, correlation));
}
}
};
在qlengine的根目录下面,运行存在的脚本build_windows.bat
,完成编译和python模块的安装。
测试
进入QuantLib - SWIG目录下Python文件夹:
python setup.py install
from QuantLib import *
payoff = PlainVanillaPayoff(Option.Call, 0.2)
maturity = Date(1, 12, 2018)
exercise = EuropeanExercise(maturity)
option = SpreadOption(payoff, exercise)
evalDate = Date(2, 8, 2018)
Settings.instance().setEvaluationDate(evalDate)
s1 = QuoteHandle(SimpleQuote(1.0))
r1 = YieldTermStructureHandle(FlatForward(evalDate, QuoteHandle(SimpleQuote(0.05)), Actual365Fixed()))
v1 = BlackVolTermStructureHandle(BlackConstantVol(evalDate, NullCalendar(), 0.5, Actual365Fixed()))
process1 = BlackProcess(s1, r1, v1)
s2 = QuoteHandle(SimpleQuote(1.0))
r2 = YieldTermStructureHandle(FlatForward(evalDate, QuoteHandle(SimpleQuote(0.05)), Actual365Fixed()))
v2 = BlackVolTermStructureHandle(BlackConstantVol(evalDate, NullCalendar(), 0.5, Actual365Fixed()))
process2 = BlackProcess(s2, r2, v2)
corr = QuoteHandle(SimpleQuote(0.0))
engine = KirkSpreadOptionEngine(process1, process2, corr)
option.setPricingEngine(engine)
print("Option NPV with 0 corretion: {0:.4f}".format(option.NPV()))
corr = QuoteHandle(SimpleQuote(1.0))
engine = KirkSpreadOptionEngine(process1, process2, corr)
option.setPricingEngine(engine)
print("Option NPV with 1 corretion: {0:.4f}".format(option.NPV()))
输出的结果应该类似于:
Option NPV with 0 corretion: 0.0808
Option NPV with 1 corretion: 0.0000