【手把手教你】使用QuantLib进行债券估值和期权定价分析

发布于: 修改于:雪球转发:0回复:0喜欢:2

01 引言

QuantLib是固定收益和金融衍生品分析的一大利器,为量化金融建模提供了完整的分析框架,但是由于本身使用C++编写,通过SWING技术封装后在Python调用,各种类(class)之间的调用非常庞杂和繁琐,又很难查看其源代码,所以学习起来相对困难。公众号通过参考官方学习文档,分享QuantLib系列学习笔记。《【手把手教你】固定收益和衍生品分析利器QuantLib入门》主要介绍了QuantLib入门的基础模块Dates日期和InterestRate利率类的基本概念和应用,本文在此基础上,简要介绍QuantLib的金融工具和定价引擎(黑盒子的调用方法),以固定利率债券和普通期权为例,演示了如何利用QuantLib对金融工具进行定价分析。

02 金融工具与定价引擎概览

QuantLib是基于欧美成熟金融市场开发的大型金融产品定价分析框架,其涵盖的金融工具(Instruments)主要分为四类:固定收益(Fixed Income)、期权(Option)、信贷(Credit)和通胀(Inflation)。每一个大类又包含了多个金融工具或衍生品,比如固定收益产品包括远期(Forward)、债券(Bonds)和互换(Swaps)等。QuantLib使用金融工具的英文全称来构建该金融工具的类(函数),比如远期利率互换,在QuantLib框架中使用ql.ForwardRateAgreement(参数)来调用,具体参数设置可以参考:QuantLib金融工具链接地址(网页链接)。

构建完金融合约(即Instruments)后,进入下一步的定价引擎(Pricing Engine),可以理解为使用哪个定价模型对该金融工具进行建模。根据金融工具的类别,定价引擎主要分为六大类,包括:Bond Pricing Engines(债券定价)、Cap Pricing Engines(价格上限定价法)、Swap Pricing Engines(互换定价法)、Swaption Pricing Engines(掉期期权定价法)、Credit Pricing Engines(信贷定价法)和Option Pricing Engines(期权定价法)。

其调用方法也是以金融工具的英文全称加参数,比如债券里最常用的贴现定价模型:ql.DiscountingBondEngine(discountCurve)。

关于各个定价引擎的具体参数和调用方式可以参照:定价引擎链接地址 

网页链接)。


03 金融工具定价应用实例

实例1:固定利率债券定价

直接借用《QuantLib Python Cookbook》上的例子:

假设有一种债券,票面价值为100,年息6%,于2015年1月15日发行,2016年1月15日到期。该债券将于2015年7月15日和2016年1月15日支付息票。100的票面金额也会在2016年1月15日支付。

为了简化问题,假设已经知道2015年1月15日美国国债的即期汇率,年化即期汇率为6个月0.5%,1年0.7%,计算该债券的公允价值。

根据贴现公式直接使用Python进行计算:

f=100  #面值
a=100*0.06/2  #票息,半年一次
#2015年7月15日一次付息,2016年1月15日到期还本付息
pv=a/(1+0.005)**(0.5)+(a+f)/(1+0.007)
print(f'债券定价为:{pv:.4f}')

#输出结果:债券定价为:105.2765

使用QuantLib的债券定价引擎计算

import QuantLib as ql
#定义当前日期(本例子是2015年1月15日)
todaysDate = ql.Date(15, 1, 2015)
#将评估日设定为当前日期
ql.Settings.instance().evaluationDate = todaysDate
#即期利率对应日期
spotDates = [ql.Date(15, 1, 2015), ql.Date(15, 7, 2015), ql.Date(15, 1, 2016)]
#即期利率,初始设定为0
spotRates = [0.0, 0.005, 0.007]
#天数计数规则
dayCount = ql.Thirty360()
#例子是美国国债,因此设定为美国日历
calendar = ql.UnitedStates()
#插值方法为线性
interpolation = ql.Linear()
#计息方式为复利
compounding = ql.Compounded
#计息频率为年
compoundingFrequency = ql.Annual
#即期利率假设满足零息债券收益率曲线
spotCurve = ql.ZeroCurve(spotDates, spotRates, dayCount, calendar, interpolation,
                             compounding, compoundingFrequency)
#利率的期限结构
spotCurveHandle = ql.YieldTermStructureHandle(spotCurve)

上述操作创建了期限结构,接下来构建固定利率债券。

#发行日期
issueDate = ql.Date(15, 1, 2015)
#到期日期
maturityDate = ql.Date(15, 1, 2016)
#付息期限
tenor = ql.Period(ql.Semiannual)
#日历
calendar = ql.UnitedStates()
#遇到假期的调整情况
bussinessConvention = ql.Unadjusted
#日期的生成规则(向后推)
dateGeneration = ql.DateGeneration.Backward
#是否月最后一日
monthEnd = False
#生成时间表
schedule = ql.Schedule (issueDate, maturityDate, tenor, calendar, bussinessConvention,
                            bussinessConvention , dateGeneration, monthEnd)
print(list(schedule))

[Date(15,1,2015), Date(15,7,2015), Date(15,1,2016)]

#息票率
dayCount = ql.Thirty360()
couponRate = .06
coupons = [couponRate]
#构建固定利率债券
settlementDays = 0
faceValue = 100
fixedRateBond = ql.FixedRateBond(settlementDays, faceValue, schedule, coupons, dayCount)

# 以期限结构作为输入值,创建债券定价引擎
# 使用贴现模型进行估值
bondEngine = ql.DiscountingBondEngine(spotCurveHandle)
fixedRateBond.setPricingEngine(bondEngine)

# 债券估值
print(f'固定利率债券估值为:{fixedRateBond.NPV():.4f}')

#输出结果:固定利率债券估值为:105.2765

得出的结果与使用Python直接套用公式计算是一致的,但过程明显复杂很多,调用了QuantLib的各种类(class),该过程可以作为其他固定收益工具定价的参考模板。

实例2:普通期权定价

以一个欧式看涨期权为例,标的资产价格为100,执行价格假定为100,无风险利率为5%,波动率为0.20,估值日为2020年11月18日,到期日为2021年11月18日。

使用Black-Scholes模型直接计算


模型公式为:

其中,St、K、T、r、σ分别为标的资产价格、执行价格、期限、无风险利率和波动率。根据上述公式和已知参数可以很容易计算普通欧式期权的价格。

# 基于Black - Scholes 公式的期权定价公式
from math import log, sqrt, exp
from scipy.stats import norm

def BSM(S0, E, T, r, sigma):
    d1 = (log(S0/E) + (r + 0.5 * sigma**2) * T) / sigma / sqrt(T)
    d2 = d1 - sigma * sqrt(T)
    Callprice = S0 * norm.cdf(d1) - E * exp(-r*T) * norm.cdf(d2)
    #Putprice=-S0*norm.cdf(-d1)+E*exp(-r*T)*norm.cdf(-d2)
    print("看涨期权价格: %.4f" % Callprice)
    #print("看跌期权价格: %.4f" % Putprice)

#参数
S0=100.0
E=100.0
T=1.0
r=0.05
sigma=0.20
BSM(S0, E, T, r, sigma)

看涨期权价格: 10.4506

使用QuantLib进行计算

#设定全局估值日
today = ql.Date(18, 11, 2020)
ql.Settings.instance().evaluationDate = today
#构建期权
#普通看涨期权
payoff=ql.PlainVanillaPayoff(ql.Option.Call, 100.0)
#到期日期
europeanExercise=ql.EuropeanExercise(ql.Date(18, 11, 2021))
option = ql.EuropeanOption(payoff, europeanExercise)

#输入参数——标的资产价格,无风险利率,标的资产波动率
u = ql.SimpleQuote(100.0)      #标的资产价值
r = ql.SimpleQuote(0.05)       #无风险利率
sigma = ql.SimpleQuote(0.20)    #波动率

#假定无风险利率和波动率曲线是平的
riskFreeCurve = ql.FlatForward(0, ql.TARGET(), ql.QuoteHandle(r), ql.Actual360())
volatility = ql.BlackConstantVol(0, ql.TARGET(), ql.QuoteHandle(sigma), ql.Actual360())

#初始化BS过程,并构造engine
process = ql.BlackScholesProcess(ql.QuoteHandle(u),
                              ql.YieldTermStructureHandle(riskFreeCurve),
                              ql.BlackVolTermStructureHandle(volatility))
engine = ql.AnalyticEuropeanEngine(process)
#对期权设定该engine
option.setPricingEngine(engine)
print(f'看涨期权的当前价值为:{option.NPV():.4f}')

看涨期权的当前价值为:10.5395得到的结果与上面直接使用BS公式计算结果基本上一致,当然QuantLib的过程也是更加复杂和繁琐,而且无法看到背后计算的逻辑(公式)。QuantLib构建完Instruments(这里是期权option)和设置定价引擎(Prcing Engines)后,除了可以获取价格(NPV),还有很多扩展功能,包括计算期权的希腊字母(即期权价格关于标的价格、时间、波动率等的变化率衡量指标)。

#计算期权的希腊字母
print("%-12s: %4.4f" %("Delta", option.delta() ))
print("%-12s: %4.4f" %("Gamma", option.gamma() ))
print("%-12s: %4.4f" %("Theta", option.vega()))

Delta       : 0.6377
Gamma       : 0.0186
Theta       : 37.7516

如果对上面一些参数(如标的资产价值,无风险利率等)做出修改,engine会自动重新计算期权的价值。

#如果当前标的资产的价值为105
#以看涨期权为例
payoff=ql.PlainVanillaPayoff(ql.Option.Call, 100.0)
option = ql.EuropeanOption(payoff, europeanExercise)
option.setPricingEngine(engine)
u.setValue(105.0)
print(f'看涨期权的当前价值为:{option.NPV():.4f}')

看涨期权的当前价值为:13.9496

#也可以同时改变多个参数
u.setValue(98.0)
r.setValue(0.04)
sigma.setValue(0.25)
print(f'看涨期权的当前价值为:{option.NPV():.4f}')

看涨期权的当前价值为:10.7357

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline   
plt.style.use('ggplot')
#正常显示画图时出现的中文和负号
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False

#不断改变当前标的资产的价值,可以查看其对期权价值的影响
f, ax = plt.subplots(figsize=(10,6))
X = np.linspace(80.0, 120.0, 400)
cv = []
for i in X:
    u.setValue(i)
    cv.append(option.NPV())
ax.set_title('期权价值——标的资产价值')
_ = ax.plot(X, cv,linewidth=2)
plt.show()

#修改估值日期
ql.Settings.instance().evaluationDate = ql.Date(18, 6, 2021)
print(f'看涨期权的当前价值为:{option.NPV():.4f}')

看涨期权的当前价值为:22.6307

#不同估值日期对比
y = []
for i in X:
    u.setValue(i)
    y.append(option.NPV())
plt.figure(figsize=(10,6))
plt.plot(X, y, '--',linewidth=2,color='b',label='估值日:2021.6.18')
plt.plot(X,cv,linewidth=2,color='r',label='估值日:2020.11.18')
plt.legend()
plt.title('不同估值日下期权价值-标的资产价值',size=15)
plt.show()

图中显示,对于同一个到期日的欧式期权(2021年11月18日到期),站在202年6月18日进行估值,相比于2020年11月18日,期权价值是整体下降的。从上述分析可知,在计算期权价格时,直接使用BS公式可以简洁的得出结果,而QuantLib的定价引擎就像个黑箱子,但是其扩展功能较多,可以通过改变定价方法、相关参数等快速对期权进行重新定价。

04 结语

本文主要介绍了QuantLib的金融工具和定价引擎的基本构成,同时以固定利率债券和普通期权定价为例,为大家展示了如何使用QuantLib对金融工具进行定价,其他不同种类的金融工具的定价过程与此相似。QuantLib的整个定价过程看起来是非常繁琐和庞杂的,就像一个黑夹子,但仔细分析又是有迹可循的,其遵循的定价框架围绕金融工程的思想,通过构建金融工具或合约,选择某个定价引擎进行分析建模。QuantLib框架涵盖的内容实在太多,而可供参考的文档又非常有限,因此要深入的学习还是有一定困难的。这里再推荐一个由QuantLib作者Luigi Ballabio写的一本书 《Implementing QuantLib》

参考资料:

1. Luigi Ballabio and Goutham Balaraman,2017,《QuantLib Python Cookbook》.

2. QuantLib官方网上英文教程:网页链接