QuantLib 金融计算——C++ 代码改写成 Python 程序的一些经验
概述
Python 在科学计算、数据分析和可视化等方面已经形成了非常强大的生态。而且,作为一门时尚的脚本语言,易学易用。因此,对于量化分析和风险管理的从业者来说,将某些 QuantLib 的历史代码转换成 Python 程序是一件值得尝试的工作。
Python 本身的面向对象机制非常完善,借助 SWIG 的包装,由 C++ 代码转换而成的 Python 程序基本上可以完整地保留原本的类架构。对于用户来说,应用层面的历史代码几乎可以平行的进行移植,只需稍加修改即可。
本文将以 QuantLib 官方网站上的 EquityOption.cpp 为例,展示如何将应用层面的 C++ 代码转换成 Python 程序,并总结出一般的转换方法和注意事项。
将 C++ 代码改写成 Python 程序
下面,我将逐句把 C++ 代码改写成 Python 程序。
C++ 代码:
#include
#ifdef BOOST_MSVC
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace QuantLib;
#if defined(QL_ENABLE_SESSIONS)
namespace QuantLib {
Integer sessionId() { return 0; }
}
#endif
Python 代码:
import QuantLib as ql
import prettytable as pt
首先,引入必要的模块,对 C++ 来说是一组头文件。Python 的优势显而易见。
C++ 代码:
// set up dates
Calendar calendar = TARGET();
Date todaysDate(15, May, 1998);
Date settlementDate(17, May, 1998);
Settings::instance().evaluationDate() = todaysDate;
Python 代码:
# set up dates
calendar = ql.TARGET()
todaysDate = ql.Date(15, ql.May, 1998)
settlementDate = ql.Date(17, ql.May, 1998)
ql.Settings.instance().evaluationDate = todaysDate
C++ 中对象的声明有两种常见的方式:
BaseClass object = Class(...),其中 Class 可以是 BaseClass 本身,或者其派生类。示例中的 TARGET 正是 Calendar 的派生类;
Class object(...)。
Python 中无需声明对象类型,而是以赋值的形式创建一个对象,所以对于上述两类格式的代码,统一改写成 object = Class(...)。
经验 1:对象声明语句 BaseClass object = Class(...) 和 Class object(...) 统一改写成 object = Class(...)。
Settings 是 QuantLib 中的一个“单体模式”的实现,通常用来为整个程序设置统一的估值日期,几乎每个应用程序中都会出现。通过调用 Settings 的静态方法 instance(),用户可以修改单体实例的某些属性,其中 evaluationDate() 方法可以把存储估值日期的成员变量地址暴露出来,让用户进行设置。
不过,Python 中的类没有 :: 运算符,类的方法也不能暴露成员变量的地址。所以,原本的静态方法一律通过 . 运算符调用,同时 evaluationDate() 方法被重定义为类的 property,这就是为什么 Python 语句中 evaluationDate 后面没有 ()。注意,instance() 后面的 () 不能丢。
经验 2:用来对 Settings::instance() 进行配置的成员函数,例如 evaluationDate(),在 Python 中以类的 property 形式出现,不过名称不变。
C++ 代码:
// our options
Option::Type type(Option::Put);
Real underlying = 36;
Real strike = 40;
Spread dividendYield = 0.00;
Rate riskFreeRate = 0.06;
Volatility volatility = 0.20;
Date maturity(17, May, 1999);
DayCounter dayCounter = Actual365Fixed();
Python 代码:
# our options
optType = ql.Option.Put
underlying = 36.0
strike = 40.0
dividendYield = 0.00
riskFreeRate = 0.06
volatility = 0.20
maturity = ql.Date(17, ql.May, 1999)
dayCounter = ql.Actual365Fixed()
C++ 中类内部枚举类型的对象声明和类对象声明相似,采用 Class::Enum object(Class::element) 的形式。枚举元素本质上是一些整数常量。
SWIG 在包装 QuantLib 的 Python 接口时会把 C++ 类内部的枚举类型转换成 Python 类中的公有属性,其值依然是一些整数值。所以,枚举类型对象的声明就直接改写成赋值语句。因此,Class::Enum object(Class::element) 语句统一改写成 object = Class.element。
示例中的 Type 是 Option 类内部的一个枚举型,而 Put 是 Type 中的一个元素,另一个是 Call。因为 type 是 Python 的关键字,改写时一定要重命名。
经验 3:对于类中的枚举类型,Class::Enum object(Class::element) 语句统一改写成 object = Class.element。
对于基本类型(整数、浮点数、字符、字符串)来说,改写非常容易。由于 Python 无需声明类型,Type object = value 语句统一改写成赋值语句——object = value。
经验 4:对于基本类型,Type object = value 语句统一改写成 object = value。
C++ 代码:
std::cout << "Option type = " << type << std::endl;
std::cout << "Maturity = " << maturity << std::endl;
std::cout << "Underlying price = " << underlying << std::endl;
std::cout << "Strike = " << strike << std::endl;
std::cout << "Risk-free interest rate = " << io::rate(riskFreeRate) << std::endl;
std::cout << "Dividend yield = " << io::rate(dividendYield) << std::endl;
std::cout << "Volatility = " << io::volatility(volatility) << std::endl;
std::cout << std::endl;
std::string method;
std::cout << std::endl ;
// write column headings
Size widths[] = { 35, 14, 14, 14 };
std::cout << std::setw(widths[0]) << std::left << "Method"
<< std::setw(widths[1]) << std::left << "European"
<< std::setw(widths[2]) << std::left << "Bermudan"
<< std::setw(widths[3]) << std::left << "American"
<< std::endl;
Python 代码:
print('Option type =', optType)
print('Maturity =', maturity)
print('Underlying price =', underlying)
print('Strike =', strike)
print('Risk-free interest rate =', '{0:%}'.format(riskFreeRate))
print('Dividend yield =', '{0:%}'.format(dividendYield))
print('Volatility =', '{0:%}'.format(volatility))
print()
# show table
tab = pt.PrettyTable(['Method', 'European', 'Bermudan', 'American'])
字符串输出部分没什么好说的,我使用了 prettytable 包来美化输出结果。
C++ 代码:
std::vector exerciseDates;
for (Integer i = 1; i <= 4; i++)
exerciseDates.push_back(settlementDate + 3 * i * Months);
Python 代码:
exerciseDates = ql.DateVector()
for i in range(1, 5):
exerciseDates.push_back(settlementDate + ql.Period(3 * i, ql.Months))
Python 本身没有“模板”的概念,因此 SWIG 只能对模板的实例化进行包装(模板的实例化就是一个具体的类),进而得到一些 Python 类。对于某些常用类型,例如 Date,QuantLib 的 Python 接口包装了对应的 std::vector 模板的实例化,包装后得到的 Python 类有一致的命名格式——ClassVector,对于 std::vector 而言就是 DateVector。
因为模板的实例化实际上就是一个具体的类,因此,这部分代码的改写方法遵循经验 1。
和 C++ 完全不同,Python 不是一个“强类型”的语言,在改写涉及隐式转换的代码时要格外注意。Months 是 QuantLib 中的枚举类型 TimeUnit 的元素,SWIG 在包装枚举类型时会将元素转换成 Python 中的整数,丢失了 TimeUnit 的类型信息。由于 Python 不是强类型的,被包装的枚举类型会丢失类型信息,因此,3 * i * Months 在 C++ 中可以顺利地隐式转换成一个 Period 对象——Period(