天天看點

QuantLib 金融計算——C++ 代碼改寫成 Python 程式的一些經驗

目錄

  • QuantLib 金融計算——C++ 代碼改寫成 Python 程式的一些經驗
    • 概述
    • 将 C++ 代碼改寫成 Python 程式
      • 對比結果
    • 總結
    • 擴充閱讀

Python 在科學計算、資料分析和可視化等方面已經形成了非常強大的生态。而且,作為一門時尚的腳本語言,易學易用。是以,對于量化分析和風險管理的從業者來說,将某些 QuantLib 的曆史代碼轉換成 Python 程式是一件值得嘗試的工作。

Python 本身的面向對象機制非常完善,借助 SWIG 的包裝,由 C++ 代碼轉換而成的 Python 程式基本上可以完整地保留原本的類架構。對于使用者來說,應用層面的曆史代碼幾乎可以平行的進行移植,隻需稍加修改即可。

本文将以 QuantLib 官方網站上的 EquityOption.cpp 為例,展示如何将應用層面的 C++ 代碼轉換成 Python 程式,并總結出一般的轉換方法和注意事項。

QuantLib 金融計算——C++ 代碼改寫成 Python 程式的一些經驗

下面,我将逐句把 C++ 代碼改寫成 Python 程式。

C++ 代碼:

#include <ql/qldefines.hpp>
#ifdef BOOST_MSVC
#  include <ql/auto_link.hpp>
#endif
#include <ql/instruments/vanillaoption.hpp>
#include <ql/pricingengines/vanilla/binomialengine.hpp>
#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
#include <ql/pricingengines/vanilla/analytichestonengine.hpp>
#include <ql/pricingengines/vanilla/baroneadesiwhaleyengine.hpp>
#include <ql/pricingengines/vanilla/bjerksundstenslandengine.hpp>
#include <ql/pricingengines/vanilla/batesengine.hpp>
#include <ql/pricingengines/vanilla/integralengine.hpp>
#include <ql/pricingengines/vanilla/fdblackscholesvanillaengine.hpp>
#include <ql/pricingengines/vanilla/mceuropeanengine.hpp>
#include <ql/pricingengines/vanilla/mcamericanengine.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/utilities/dataformatters.hpp>

#include <iostream>
#include <iomanip>
 
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 的優勢顯而易見。

// set up dates
Calendar calendar = TARGET();
Date todaysDate(15, May, 1998);
Date settlementDate(17, May, 1998);
Settings::instance().evaluationDate() = todaysDate;
           
# 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++ 中對象的聲明有兩種常見的方式:

  1. BaseClass object = Class(...)

    ,其中

    Class

    可以是

    BaseClass

    本身,或者其派生類。示例中的

    TARGET

    正是

    Calendar

    的派生類;
  2. 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

形式出現,不過名稱不變。
// 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();
           
# 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

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

包來美化輸出結果。

std::vector<Date> exerciseDates;
for (Integer i = 1; i <= 4; i++)
    exerciseDates.push_back(settlementDate + 3 * i * Months);
           
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<Date>

而言就是

DateVector

因為模闆的執行個體化實際上就是一個具體的類,是以,這部分代碼的改寫方法遵循經驗 1。

和 C++ 完全不同,Python 不是一個“強類型”的語言,在改寫涉及隐式轉換的代碼時要格外注意。

Months

是 QuantLib 中的枚舉類型

TimeUnit

的元素,SWIG 在包裝枚舉類型時會将元素轉換成 Python 中的整數,丢失了

TimeUnit

的類型資訊。由于 Python 不是強類型的,被包裝的枚舉類型會丢失類型資訊,是以,

3 * i * Months

在 C++ 中可以順利地隐式轉換成一個

Period

對象——

Period(3 * i, Months)

,但是,在 Python 中

3 * i * Months

隻會被當做三個整數相乘。此時,

3 * i * Months

必須改寫成顯式聲明的格式——

ql.Period(3 * i, ql.Months)

經驗 5:隐式轉換成

Period

對象的代碼在改寫時要改成顯式聲明的格式,這類代碼通常與枚舉類型

TimeUnit

有關。
ext::shared_ptr<Exercise> europeanExercise(
    new EuropeanExercise(maturity));

ext::shared_ptr<Exercise> bermudanExercise(
    new BermudanExercise(exerciseDates));

ext::shared_ptr<Exercise> americanExercise(
    new AmericanExercise(settlementDate, maturity));
           
europeanExercise = ql.EuropeanExercise(maturity)
bermudanExercise = ql.BermudanExercise(exerciseDates)
americanExercise = ql.AmericanExercise(settlementDate, maturity)
           

C++ 中聲明智能指針的最常見方式是:

shared_ptr<BaseClass> object(new Class(...))

shared_ptr

也是最常用的智能指針類模闆),其中

Class

BaseClass

EuropeanExercise

Exercise

的派生類。這類代碼在 Python 中統一改寫成聲明對象的形式——

object = Class(...)

,因為智能指針通常被視為一個對象。

經驗 6:對于智能指針,

shared_ptr<BaseClass> object(new Class(...))

object = Class(...)

Handle<Quote> underlyingH(
    ext::shared_ptr<Quote>(new SimpleQuote(underlying)));
           
underlyingH = ql.QuoteHandle(ql.SimpleQuote(underlying))
           

Quote

類和

Handle

模闆是 QuantLib 中最常用到的兩個類(模闆),它們通常充當“觀察者模式”中被觀察的一方,一般被當做參數來配置更複雜類的執行個體。

Quote

類接受一個浮點數做參數,而

Handle

模闆接受一個智能指針。當使用者修改

Quote

執行個體的值,或

Handle

執行個體指向的指針之後,那些接受過這些執行個體的複雜類對象會接到通知,并自動觸發相關計算。這個機制非常贊!

關于

Quote

的具體使用案例,詳情可以參考《

Quote

帶來的便利》。

QuantLib 的 Python 接口已經包裝了

Handle

模闆的一些執行個體化,例如

QuoteHandle

和下面将要看到的

YieldTermStructureHandle

,這些類有一緻的命名格式——

ClassHandle

還是那句話,C++ 模闆的執行個體化實際上就是一個具體的類,是以,這部分代碼的改寫方法遵循經驗 1 和經驗 6。

// bootstrap the yield/dividend/vol curves
Handle<YieldTermStructure> flatTermStructure(
    ext::shared_ptr<YieldTermStructure>(
        new FlatForward(settlementDate, riskFreeRate, dayCounter)));
Handle<YieldTermStructure> flatDividendTS(
    ext::shared_ptr<YieldTermStructure>(
        new FlatForward(settlementDate, dividendYield, dayCounter)));
Handle<BlackVolTermStructure> flatVolTS(
    ext::shared_ptr<BlackVolTermStructure>(
        new BlackConstantVol(
            settlementDate, calendar, volatility, dayCounter)));
ext::shared_ptr<StrikedTypePayoff> payoff(
    new PlainVanillaPayoff(type, strike));
ext::shared_ptr<BlackScholesMertonProcess> bsmProcess(
    new BlackScholesMertonProcess(
        underlyingH, flatDividendTS, flatTermStructure, flatVolTS));

// options
VanillaOption europeanOption(payoff, europeanExercise);
VanillaOption bermudanOption(payoff, bermudanExercise);
VanillaOption americanOption(payoff, americanExercise);

// Analytic formulas:

// Black-Scholes for European
method = "Black-Scholes";
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new AnalyticEuropeanEngine(bsmProcess)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << "N/A"
          << std::setw(widths[3]) << std::left << "N/A"
          << std::endl;

// semi-analytic Heston for European
method = "Heston semi-analytic";
ext::shared_ptr<HestonProcess> hestonProcess(
    new HestonProcess(
        flatTermStructure, flatDividendTS, underlyingH,
        volatility * volatility, 1.0, volatility * volatility, 0.001, 0.0));
ext::shared_ptr<HestonModel> hestonModel(
    new HestonModel(hestonProcess));
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new AnalyticHestonEngine(hestonModel)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << "N/A"
          << std::setw(widths[3]) << std::left << "N/A"
          << std::endl;

// semi-analytic Bates for European
method = "Bates semi-analytic";
ext::shared_ptr<BatesProcess> batesProcess(
    new BatesProcess(
        flatTermStructure, flatDividendTS, underlyingH,
        volatility * volatility, 1.0, volatility * volatility,
        0.001, 0.0, 1e-14, 1e-14, 1e-14));
ext::shared_ptr<BatesModel> batesModel(
    new BatesModel(batesProcess));
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(new BatesEngine(batesModel)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << "N/A"
          << std::setw(widths[3]) << std::left << "N/A"
          << std::endl;
          
// Barone-Adesi and Whaley approximation for American
method = "Barone-Adesi/Whaley";
americanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BaroneAdesiWhaleyApproximationEngine(bsmProcess)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << "N/A"
          << std::setw(widths[2]) << std::left << "N/A"
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;

// Bjerksund and Stensland approximation for American
method = "Bjerksund/Stensland";
americanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BjerksundStenslandApproximationEngine(bsmProcess)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << "N/A"
          << std::setw(widths[2]) << std::left << "N/A"
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;

// Integral
method = "Integral";
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new IntegralEngine(bsmProcess)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << "N/A"
          << std::setw(widths[3]) << std::left << "N/A"
          << std::endl;

// Finite differences
Size timeSteps = 801;
method = "Finite differences";
ext::shared_ptr<PricingEngine> fdengine =
    ext::make_shared<FdBlackScholesVanillaEngine>(
        bsmProcess, timeSteps, timeSteps - 1);
europeanOption.setPricingEngine(fdengine);
bermudanOption.setPricingEngine(fdengine);
americanOption.setPricingEngine(fdengine);
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << bermudanOption.NPV()
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;
           
# bootstrap the yield/dividend/vol curves
flatTermStructure = ql.YieldTermStructureHandle(
    ql.FlatForward(settlementDate, riskFreeRate, dayCounter))
flatDividendTS = ql.YieldTermStructureHandle(
    ql.FlatForward(settlementDate, dividendYield, dayCounter))
flatVolTS = ql.BlackVolTermStructureHandle(
    ql.BlackConstantVol(
        settlementDate, calendar, volatility, dayCounter))
payoff = ql.PlainVanillaPayoff(optType, strike)
bsmProcess = ql.BlackScholesMertonProcess(
    underlyingH, flatDividendTS, flatTermStructure, flatVolTS)

# options
europeanOption = ql.VanillaOption(payoff, europeanExercise)
bermudanOption = ql.VanillaOption(payoff, bermudanExercise)
americanOption = ql.VanillaOption(payoff, americanExercise)

# Analytic formulas:

# Black-Scholes for European
method = 'Black-Scholes'
europeanOption.setPricingEngine(
    ql.AnalyticEuropeanEngine(bsmProcess))
tab.add_row([method, europeanOption.NPV(), 'N/A', 'N/A'])

# semi-analytic Heston for European
method = 'Heston semi-analytic'
hestonProcess = ql.HestonProcess(
    flatTermStructure, flatDividendTS, underlyingH,
    volatility * volatility, 1.0, volatility * volatility, 0.001, 0.0)
hestonModel = ql.HestonModel(hestonProcess)
europeanOption.setPricingEngine(
    ql.AnalyticHestonEngine(hestonModel))
tab.add_row([method, europeanOption.NPV(), 'N/A', 'N/A'])

# semi-analytic Bates for European
method = 'Bates semi-analytic'
batesProcess = ql.BatesProcess(
    flatTermStructure, flatDividendTS, underlyingH,
    volatility * volatility, 1.0, volatility * volatility,
    0.001, 0.0, 1e-14, 1e-14, 1e-14)
batesModel = ql.BatesModel(batesProcess)
europeanOption.setPricingEngine(
    ql.BatesEngine(batesModel))
tab.add_row([method, europeanOption.NPV(), 'N/A', 'N/A'])

# Barone-Adesi and Whaley approximation for American
method = 'Barone-Adesi/Whaley'
americanOption.setPricingEngine(
    ql.BaroneAdesiWhaleyEngine(bsmProcess))
tab.add_row([method, 'N/A', 'N/A', americanOption.NPV()])

# Bjerksund and Stensland approximation for American
method = 'Bjerksund/Stensland'
americanOption.setPricingEngine(
    ql.BjerksundStenslandEngine(bsmProcess))
tab.add_row([method, 'N/A', 'N/A', americanOption.NPV()])

# Integral
method = 'Integral'
europeanOption.setPricingEngine(
    ql.IntegralEngine(bsmProcess))
tab.add_row([method, europeanOption.NPV(), 'N/A', 'N/A'])

# Finite differences
timeSteps = 801
method = 'Finite differences'
fdengine = ql.FdBlackScholesVanillaEngine(bsmProcess, timeSteps, timeSteps - 1)
europeanOption.setPricingEngine(fdengine)
bermudanOption.setPricingEngine(fdengine)
americanOption.setPricingEngine(fdengine)
tab.add_row([method, europeanOption.NPV(), bermudanOption.NPV(), americanOption.NPV()])
           

這部分代碼的改寫沒什麼新意,需要注意的是,某些非模闆類在被包裝時會被重命名,例如

BaroneAdesiWhaleyApproximationEngine

被重命名為

BaroneAdesiWhaleyEngine

。如果使用者根據前面的 6 條經驗找不到 Python 接口中的對應物,那麼,要改寫的 C++ 代碼可能遇到了重命名的情況。這時,使用者需要到 QuantLib-SWIG 的接口檔案中查找 C++ 類(結構體)或函數,看看有沒有被重命名。繼續前面的例子,SWIG 代碼

%rename(BaroneAdesiWhaleyEngine) BaroneAdesiWhaleyApproximationEngine;

表明

BaroneAdesiWhaleyApproximationEngine

BaroneAdesiWhaleyEngine

經驗 7:疑似遇到重命名的情況(常見于名字特别長的類),到 QuantLib-SWIG 的接口檔案中查找重命名指令。
// Binomial method: Jarrow-Rudd
method = "Binomial Jarrow-Rudd";
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timeSteps)));
bermudanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timeSteps)));
americanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timeSteps)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << bermudanOption.NPV()
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;

// Binomial method: Cox-Ross-Rubinstein
method = "Binomial Cox-Ross-Rubinstein";
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timeSteps)));
bermudanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timeSteps)));
americanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timeSteps)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << bermudanOption.NPV()
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;

// Binomial method: Additive equiprobabilities
method = "Additive equiprobabilities";
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<AdditiveEQPBinomialTree>(
            bsmProcess, timeSteps)));
bermudanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<AdditiveEQPBinomialTree>(
            bsmProcess, timeSteps)));
americanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<AdditiveEQPBinomialTree>(
            bsmProcess, timeSteps)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << bermudanOption.NPV()
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;

// Binomial method: Binomial Trigeorgis
method = "Binomial Trigeorgis";
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timeSteps)));
bermudanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timeSteps)));
americanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timeSteps)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << bermudanOption.NPV()
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;

// Binomial method: Binomial Tian
method = "Binomial Tian";
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<Tian>(bsmProcess, timeSteps)));
bermudanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<Tian>(bsmProcess, timeSteps)));
americanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<Tian>(bsmProcess, timeSteps)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << bermudanOption.NPV()
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;

// Binomial method: Binomial Leisen-Reimer
method = "Binomial Leisen-Reimer";
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timeSteps)));
bermudanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timeSteps)));
americanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timeSteps)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << bermudanOption.NPV()
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;

// Binomial method: Binomial Joshi
method = "Binomial Joshi";
europeanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<Joshi4>(bsmProcess, timeSteps)));
bermudanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<Joshi4>(bsmProcess, timeSteps)));
americanOption.setPricingEngine(
    ext::shared_ptr<PricingEngine>(
        new BinomialVanillaEngine<Joshi4>(bsmProcess, timeSteps)));
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << bermudanOption.NPV()
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;
           
# Binomial method: Jarrow-Rudd
method = 'Binomial Jarrow-Rudd'
jrengine = ql.BinomialJRVanillaEngine(bsmProcess, timeSteps)
europeanOption.setPricingEngine(jrengine)
bermudanOption.setPricingEngine(jrengine)
americanOption.setPricingEngine(jrengine)
tab.add_row([method, europeanOption.NPV(), bermudanOption.NPV(), americanOption.NPV()])

# Binomial method: Cox-Ross-Rubinstein
method = 'Binomial Cox-Ross-Rubinstein'
crrengine = ql.BinomialCRRVanillaEngine(bsmProcess, timeSteps)
europeanOption.setPricingEngine(crrengine)
bermudanOption.setPricingEngine(crrengine)
americanOption.setPricingEngine(crrengine)
tab.add_row([method, europeanOption.NPV(), bermudanOption.NPV(), americanOption.NPV()])

# Binomial method: Additive equiprobabilities
method = 'Additive equiprobabilities'
eqpengine = ql.BinomialEQPVanillaEngine(bsmProcess, timeSteps)
europeanOption.setPricingEngine(eqpengine)
bermudanOption.setPricingEngine(eqpengine)
americanOption.setPricingEngine(eqpengine)
tab.add_row([method, europeanOption.NPV(), bermudanOption.NPV(), americanOption.NPV()])

# Binomial method: Binomial Trigeorgis
method = 'Binomial Trigeorgis'
trengine = ql.BinomialTrigeorgisVanillaEngine(bsmProcess, timeSteps)
europeanOption.setPricingEngine(trengine)
bermudanOption.setPricingEngine(trengine)
americanOption.setPricingEngine(trengine)
tab.add_row([method, europeanOption.NPV(), bermudanOption.NPV(), americanOption.NPV()])

# Binomial method: Binomial Tian
method = 'Binomial Tian'
tiengine = ql.BinomialTianVanillaEngine(bsmProcess, timeSteps)
europeanOption.setPricingEngine(tiengine)
bermudanOption.setPricingEngine(tiengine)
americanOption.setPricingEngine(tiengine)
tab.add_row([method, europeanOption.NPV(), bermudanOption.NPV(), americanOption.NPV()])

# Binomial method: Binomial Leisen-Reimer
method = 'Binomial Leisen-Reimer'
lrengine = ql.BinomialLRVanillaEngine(bsmProcess, timeSteps)
europeanOption.setPricingEngine(lrengine)
bermudanOption.setPricingEngine(lrengine)
americanOption.setPricingEngine(lrengine)
tab.add_row([method, europeanOption.NPV(), bermudanOption.NPV(), americanOption.NPV()])

# Binomial method: Binomial Joshi
method = 'Binomial Joshi'
j4engine = ql.BinomialJ4VanillaEngine(bsmProcess, timeSteps)
europeanOption.setPricingEngine(j4engine)
bermudanOption.setPricingEngine(j4engine)
americanOption.setPricingEngine(j4engine)
tab.add_row([method, europeanOption.NPV(), bermudanOption.NPV(), americanOption.NPV()])
           

對于 C++ 中的模闆,SWIG 在包裝 Python 接口時隻包裝模闆的執行個體化,并且會為模闆的執行個體化取一個新名字。這時,使用者需要到 QuantLib-SWIG 的接口檔案中查找模闆的執行個體化,看看取了什麼新名字。繼續前面的例子,SWIG 代碼

%template(BinomialJRVanillaEngine) BinomialVanillaEngine<JarrowRudd>;

表示

BinomialVanillaEngine<JarrowRudd>

在 Python 中對應的類叫做

BinomialJRVanillaEngine

經驗 8:遇到模闆執行個體化的情況,到 QuantLib-SWIG 的接口檔案中查找執行個體化後新的類名。
// Monte Carlo Method: MC (crude)
timeSteps = 1;
method = "MC (crude)";
Size mcSeed = 42;
ext::shared_ptr<PricingEngine> mcengine1;
mcengine1 = MakeMCEuropeanEngine<PseudoRandom>(
                bsmProcess)
                .withSteps(timeSteps)
                .withAbsoluteTolerance(0.02)
                .withSeed(mcSeed);
europeanOption.setPricingEngine(mcengine1);
// Real errorEstimate = europeanOption.errorEstimate();
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << "N/A"
          << std::setw(widths[3]) << std::left << "N/A"
          << std::endl;

// Monte Carlo Method: QMC (Sobol)
method = "QMC (Sobol)";
Size nSamples = 32768;    // 2^15
ext::shared_ptr<PricingEngine> mcengine2;
mcengine2 = MakeMCEuropeanEngine<LowDiscrepancy>(
                bsmProcess)
                .withSteps(timeSteps)
                .withSamples(nSamples);
europeanOption.setPricingEngine(mcengine2);
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << europeanOption.NPV()
          << std::setw(widths[2]) << std::left << "N/A"
          << std::setw(widths[3]) << std::left << "N/A"
          << std::endl;

// Monte Carlo Method: MC (Longstaff Schwartz)
method = "MC (Longstaff Schwartz)";
ext::shared_ptr<PricingEngine> mcengine3;
mcengine3 = MakeMCAmericanEngine<PseudoRandom>(
                bsmProcess)
                .withSteps(100)
                .withAntitheticVariate()
                .withCalibrationSamples(4096)
                .withAbsoluteTolerance(0.02)
                .withSeed(mcSeed);
americanOption.setPricingEngine(mcengine3);
std::cout << std::setw(widths[0]) << std::left << method
          << std::fixed
          << std::setw(widths[1]) << std::left << "N/A"
          << std::setw(widths[2]) << std::left << "N/A"
          << std::setw(widths[3]) << std::left << americanOption.NPV()
          << std::endl;
           
timeSteps = 1
# Monte Carlo Method: MC (crude)
method = 'MC (crude)'
mcSeed = 42
mcengine1 = ql.MCPREuropeanEngine(
    bsmProcess,
    timeSteps=timeSteps,
    requiredTolerance=0.02,
    seed=mcSeed)

europeanOption.setPricingEngine(mcengine1)
tab.add_row([method, europeanOption.NPV(), 'N/A', 'N/A'])

# Monte Carlo Method: QMC (Sobol)
method = 'QMC (Sobol)'
nSamples = 32768  # 2^15
mcengine2 = ql.MCLDEuropeanEngine(
    bsmProcess,
    timeSteps=timeSteps,
    requiredSamples=nSamples)
europeanOption.setPricingEngine(mcengine2)
tab.add_row([method, europeanOption.NPV(), 'N/A', 'N/A'])

# Monte Carlo Method: MC (Longstaff Schwartz)
method = 'MC (Longstaff Schwartz)'
mcengine3 = ql.MCPRAmericanEngine(
    bsmProcess,
    timeSteps=100,
    antitheticVariate=True,
    nCalibrationSamples=4096,
    requiredTolerance=0.02,
    seed=mcSeed)
americanOption.setPricingEngine(mcengine3)
tab.add_row([method, 'N/A', 'N/A', americanOption.NPV()])

tab.float_format = '.6'
tab.align = 'l'
print(tab)
           

MakeMCEuropeanEngine<PseudoRandom>

是 QuantLib 中工廠模式的一個實作,對于擁有較多預設參數的類,QuantLib 會提供一個對應的工廠類,使用者借助工廠類“制造”一個半成品對象,并通過一組成員函數以流水線的方式配置這個半成品的參數,以實作對預設參數的靈活配置。這些流水線函數有一緻的命名格式——

withArgument

Argument

通常是某個預設參數的名字。這套機制也被稱為“命名參數慣用法”。這些工廠類有一緻的命名規範——

MakeClass

Class

是一個類的名字或執行個體化的模闆,

MakeClass

将制造出一個

Class

對象。

Python 中存在“關鍵字參數”的機制,是以,上述“流水線函數”顯得非常笨拙,對于這類代碼的改寫,使用者隻要知道“

MakeClass

Class

對象”這一點,并了解流水線函數所配置的參數,然後應用前面總結的 8 條經驗就可以成功改寫。

經驗 9:名為

MakeClass

的工廠類将制造出一個

Class

對象,後續的成員函數表示配置的參數。

C++ 代碼運作結果:

Option type = Put
Maturity = May 17th, 1999
Underlying price = 36
Strike = 40
Risk-free interest rate = 6.000000 %
Dividend yield = 0.000000 %
Volatility = 20.000000 %


Method                             European      Bermudan      American      
Black-Scholes                      3.844308      N/A           N/A           
Heston semi-analytic               3.844306      N/A           N/A           
Bates semi-analytic                3.844306      N/A           N/A           
Barone-Adesi/Whaley                N/A           N/A           4.459628      
Bjerksund/Stensland                N/A           N/A           4.453064      
Integral                           3.844309      N/A           N/A           
Finite differences                 3.844330      4.360765      4.486113      
Binomial Jarrow-Rudd               3.844132      4.361174      4.486552      
Binomial Cox-Ross-Rubinstein       3.843504      4.360861      4.486415      
Additive equiprobabilities         3.836911      4.354455      4.480097      
Binomial Trigeorgis                3.843557      4.360909      4.486461      
Binomial Tian                      3.844171      4.361176      4.486413      
Binomial Leisen-Reimer             3.844308      4.360713      4.486076      
Binomial Joshi                     3.844308      4.360713      4.486076      
MC (crude)                         3.834522      N/A           N/A           
QMC (Sobol)                        3.844613      N/A           N/A           
MC (Longstaff Schwartz)            N/A           N/A           4.456935      
           

Python 程式運作結果:

Option type = -1
Maturity = May 17th, 1999
Underlying price = 36.0
Strike = 40.0
Risk-free interest rate = 6.000000%
Dividend yield = 0.000000%
Volatility = 20.000000%

+------------------------------+----------+----------+----------+
| Method                       | European | Bermudan | American |
+------------------------------+----------+----------+----------+
| Black-Scholes                | 3.844308 | N/A      | N/A      |
| Heston semi-analytic         | 3.844306 | N/A      | N/A      |
| Bates semi-analytic          | 3.844306 | N/A      | N/A      |
| Barone-Adesi/Whaley          | N/A      | N/A      | 4.459628 |
| Bjerksund/Stensland          | N/A      | N/A      | 4.453064 |
| Integral                     | 3.844309 | N/A      | N/A      |
| Finite differences           | 3.844330 | 4.360765 | 4.486113 |
| Binomial Jarrow-Rudd         | 3.844132 | 4.361174 | 4.486552 |
| Binomial Cox-Ross-Rubinstein | 3.843504 | 4.360861 | 4.486415 |
| Additive equiprobabilities   | 3.836911 | 4.354455 | 4.480097 |
| Binomial Trigeorgis          | 3.843557 | 4.360909 | 4.486461 |
| Binomial Tian                | 3.844171 | 4.361176 | 4.486413 |
| Binomial Leisen-Reimer       | 3.844308 | 4.360713 | 4.486076 |
| Binomial Joshi               | 3.844308 | 4.360713 | 4.486076 |
| MC (crude)                   | 3.834522 | N/A      | N/A      |
| QMC (Sobol)                  | 3.844613 | N/A      | N/A      |
| MC (Longstaff Schwartz)      | N/A      | N/A      | 4.456935 |
+------------------------------+----------+----------+----------+
           

完全一樣!

QuantLib 金融計算——C++ 代碼改寫成 Python 程式的一些經驗

  • BaseClass object = Class(...)

    Class object(...)

    object = Class(...)

  • Settings::instance()

    evaluationDate()

    property

  • Class::Enum object(Class::element)

    object = Class.element

  • Type object = value

    object = value

  • Period

    TimeUnit

  • shared_ptr<BaseClass> object(new Class(...))

    object = Class(...)

  • MakeClass

    Class

需要注意的是,QuantLib 中并非所有的功能都有對應的 Python 接口,如果使用者需要的功能未被包裝,使用者隻好修改 SWIG 代碼,自行生成 Python 接口,可以參考一下文章:

  • 《自己動手封裝 Python 接口(1)》
  • 《自己動手封裝 Python 接口(2)》
  • 《自己動手封裝 Python 接口(3)》

《QuantLib 金融計算》系列合集

★ 持續學習 ★ 堅持創作 ★

繼續閱讀