【手把手教你】固定收益和衍生品分析利器QuantLib入门
引言
QuantLib是一个专门用于利率、债券与衍生品等金融工具定价分析的库,可以说是固定收益和金融衍生品分析的一个利器。QuantLib本身是使用C++写的,通过SWING技术封装后可以在Python调用。直接使用pip安装可能会报错,建议下载安装包的whl文件,然后再用pip进行安装。
https://www.lfd.uci.edu/~gohlke/pythonlibs/#quantlib (建议收藏,涵盖了Python大部分第三方包),找到与自己电脑和Python相对应的版本下载,如我的电脑是64位,Python3.7,选择第一个下载,将下载的文件放在当前工作目录,然后进入cmd模式(cmd模式下的地址即为当前工作目录),输入:
“pip install QuantLib_Python‑1.11‑cp37‑cp37m‑win_amd64.whl”即可,如果import QuantLib没有报错说明安装成功。
在公众号后台回复“quantlib”可以获取quantilib的使用手册(英文版)。
如何查看系统环境、包的版本号和当前工作路径呢?
#先pip安装watermark
#查看系统环境和module的版本号
%load_ext watermark
%watermark
%watermark -p pandas,numpy,QuantLib,matplotlib
输出结果:
#查看当前工作目录
import os
os.getcwd()
#输出结果:
'C:\\Users\\zjy'
引入QuantLib包
#引入QuantLib
import QuantLib as ql
QuantLib基础模块:Dates
日期模块Dates包含了Date,Period,Calendar,DayCounter,Schedule,DateGeneration等,是QuantLib的基础模块,包含了时间、日期、日历等的定义、生成和逻辑运算等,是数学建模和量化分析的重要基础。
Date:日期
Date是日期格式,日期范围是1901-01-01至2199-12-31。有三种写法:(1)Date(n),与Excel类似,其中n的范围是367-109574之间的数字(含),其他超出该范围的数字都会报错;(2)Date(day,month,year),即日,月,年的输入格式,其中日和年必须是数字,而月可以是其他格式,如ql.June(相当于输入6)。(3)Date(日期,格式),如:Date('20-09-2020', '%d-%m-%Y')
应用实例:定义日期“2020年11月11日”
d1=ql.Date(11,11,2020)
d2=ql.Date('2020-11-11','%Y-%m-%d')
d3=ql.Date('20201111','%Y%m%d')
d4=ql.Date(44146)
#四种写法等价
print(d1==d2==d3==d4)
#输出结果:True
日期定义与运算
today=ql.Date(11,11,2020)
print('初始日期:', today)
print('ISO格式:', today.ISO())
#返回一周七天对应的数字:注意周日(Sunday)是1,周六(Saturday)是7
print('一周中第几天:', today.weekday())
print('该月的第几天:', today.dayOfMonth())
print('本年的第几天:', today.dayOfYear())
print('月份:', today.month())
print('年份:', today.year())
#相当于第一种写法的逆运算
print('日期数字:', today.serialNumber())
输出结果:
初始日期: November 11th, 2020
ISO格式: 2020-11-11
一周中第几天: 4
该月的第几天: 11
本年的第几天: 316
月份: 11
年份: 2020
日期数字: 44146
#逻辑判断
print(today == ql.Date(12, 11, 2020))
print(today > ql.Date(11, 10, 2020))
print(today < ql.Date(1, 12, 2020))
print(today != ql.Date(11, 9, 2020))
输出结果:False、True、True、True
获取Date内置日期和运算函数
print('当前日期:', ql.Date.todaysDate())
print('系统支持最小日期 :', ql.Date.minDate())
print('系统支持最大日期 :', ql.Date.maxDate())
#判断是否闰年
print('是闰年吗? :', ql.Date.isLeap(2020))
print('该月的最后一天:', ql.Date.endOfMonth(ql.Date(11, ql.November, 2020)))
print('是该月的最后一天吗? :', ql.Date.isEndOfMonth(ql.Date(30, 11, 2020)))
print('该日期的下个星期一 :', ql.Date.nextWeekday(ql.Date(11, 11, 2020), ql.Monday))
print('该月的第2个星期五 :', ql.Date.nthWeekday(2, ql.Friday, 11, 2020))
输出结果:
当前日期: November 11th, 2020
系统支持最小日期 : January 1st, 1901
系统支持最大日期 : December 31st, 2199
是闰年吗?: True
该月的最后一天: November 30th, 2020
是该月的最后一天吗?: True
该日期的下个星期一 : November 16th, 2020
该月的第2个星期五 : November 13th, 2020
Period:周期
Period可以生成日期频数,如多少日(周、月、年)等。主要有三种写法:(1)ql.Period(n, units),其中units可以是日:ql.Days,周:ql.Weeks,月:ql.Months,年:ql.Years。(2)ql.Period(periodString),其中periodString可以是日:'1D',周:'1W',月:'1M'和年:'1Y'。(3)ql.Period(frequency),如每年,ql.Annual。
#一年的等价写法
ql.Period('1Y')==ql.Period(1,ql.Years)
==ql.Period(ql.Annual)
#输出结果:True
Period主要是用于Date的运算。
today=ql.Date(11,11,2020)
print(f'{today}三天后是{today+3}')
print(f'{today}前一天是{today-1}')
print(f'{today}下一周是{today+ql.Period(1,ql.Weeks)}')
print(f'{today}下一个月是{today+ql.Period(1,ql.Months)}')
print(f'{today}下一年是{today+ql.Period(1,ql.Years)}')
输出结果:
November 11th, 2020三天后是November 14th, 2020
November 11th, 2020前一天是November 10th, 2020
November 11th, 2020下一周是November 18th, 2020
November 11th, 2020下一个月是December 11th, 2020
November 11th, 2020下一年是November 11th, 2021
Calendar:日历
Date对象没有考虑假期因素,而实际应用中,证券交易需要考虑指定交易所或者国家的假期,Calendar对主要的交易所给出了交易日历,包括:Argentina : [‘Merval’];Brazil : [‘Exchange’,‘Settlement’],Canada : [‘Settlement’, ‘TSX’]、China : [‘IB’, ‘SSE’];CzechRepublic : [‘PSE’],France : [‘Exchange’, ‘Settlement’];Germany : [‘Eurex’, ‘FrankfurtStockExchange’, ‘Settlement’, ‘Xetra’],HongKong : [‘HKEx’],Iceland : [‘ICEX’],India : [‘NSE’],Indonesia : [‘BEJ’, ‘JSX’],Israel : [‘Settlement’, ‘TASE’],Italy : [‘Exchange’, ‘Settlement’],Mexico : [‘BMV’],Russia : [‘MOEX’, ‘Settlement’],SaudiArabia : [‘Tadawul’],Singapore :[‘SGX’],Slovakia : [‘BSSE’],SouthKorea : [‘KRX’, ‘Settlement’],* : [‘TSEC’],Ukraine : [‘USE’],UnitedKingdom : [‘Exchange’, ‘Metals’, ‘Settlement’];UnitedStates :[‘FederalReserve’,‘GovernmentBond’,‘LiborImpact’, ‘NERC’, ‘NYSE’, ‘Settlement’]
calendar1 = ql.UnitedKingdom(ql.UnitedKingdom.Exchange)
calendar2 = ql.UnitedStates(ql.UnitedStates.NYSE)
calendar3 = ql.China(ql.China.SSE)
day1=ql.Date(1,1,2020)
day2=ql.Date(31,12,2020)
uk_bday=calendar1.businessDaysBetween(day1,day2)
us_bday=calendar2.businessDaysBetween(day1,day2)
ch_bday=calendar3.businessDaysBetween(day1,day2)
print(day1,'至',day2,'之间有',uk_bday,'个英国交易日')
print(day1,'至',day2,'之间有',us_bday,'个美国交易日')
print(day1,'至',day2,'之间有',ch_bday,'个中国交易日')
输出结果:
January 1st, 2020 至 December 31st, 2020 之间有 253 个英国交易日
January 1st, 2020 至 December 31st, 2020 之间有 252 个美国交易日
January 1st, 2020 至 December 31st, 2020 之间有 260 个中国交易日
判断日期类型
cal1 = ql.China()
cal2 = ql.UnitedStates()
mydate = ql.Date(1, 10, 2020)
#判断交易日
print(mydate,'在中国是交易日吗? :', cal1.isBusinessDay(mydate))
print(mydate,'在美国是交易日吗? :', cal2.isBusinessDay(mydate))
输出结果:
October 1st, 2020 在中国是交易日吗?: True
October 1st, 2020 在美国是交易日吗?: True
cal = ql.China()
day1 = ql.Date(11, 11, 2020)
day2 = ql.Date(14, 11, 2020)
print('Is Business Day : ', cal.isBusinessDay(day1))
print('Is Business Day : ', cal.isBusinessDay(day2))
#添加或者移除节假日设定
cal.addHoliday(day1)
cal.removeHoliday(day2)
print('Is Business Day : ', cal.isBusinessDay(day1))
print('Is Business Day : ', cal.isBusinessDay(day2))
输出结果:
Is Business Day : True
Is Business Day : False
Is Business Day : False
Is Business Day : True
不同国家日历日和交易日的区别
date = ql.Date(11, 11, 2020)
us_calendar = ql.UnitedStates()
ch_calendar = ql.China()
raw_date = date + ql.Period(30, ql.Days)
us_date = us_calendar.advance(date, ql.Period(30, ql.Days))
ch_date = ch_calendar.advance(date, ql.Period(30, ql.Days))
print(date,"后推30个日历日: ", raw_date)
print(date,"后推美国30个交易日: ", us_date)
print(date,"后推中国30个交易日: ", ch_date)
输出结果:
November 11th, 2020 后推30个日历日:December 11th, 2020
November 11th, 2020 后推美国30个交易日:December 24th, 2020
November 11th, 2020 后推中国30个交易日:December 22nd, 2020
DayCounter:天数计算
DayCounter可以统计某两个日期之间的天数,是固定收益类产品估值和分析的重要基础,常用的计数函数包括:
Actual360 : Actual / 360,一年按360天
Actual365Fixed : Actual / 365(Fixed),一年按365天
Standard:标准;Canadian:加拿大;NoLeap:即所有年份都是365天
ActualActual : 按每年实际天数
ISMA \ Bond\ ISDA\ Historical\ Actual365\ AFB\ Euro
Business252 : Business / 252,证券交易日
Thirty360 : 30 / 360,按月30天,年360天
SimpleDayCounter:简单的日计数
常用的函数有两个:
dayCount(d1,d2):计算 d1,d2 之间的天数
yearFraction(d1, d2):将 d1,d2 之间的天数年化
d1 = ql.Date(1,10,2020)
d2 = ql.Date(11,11,2020)
dc=ql.Business252()
dd=dc.dayCount(d1,d2)
yf=dc.yearFraction(startDate,endDate)
print(f'日历间隔天数:{d2-d1}')
print(f'Business252计算规则天数:{dd}')
print(f'Business252计算规则天数年化:{yf:.4f}')
日历间隔天数:41
Business252计算规则天数:27
Business252计算规则天数年化:0.1071
dayCounters = {
'SimpleDayCounter': ql.SimpleDayCounter(),
'Thirty360': ql.Thirty360(),
'Actual360': ql.Actual360(),
'Actual365Fixed': ql.Actual365Fixed(),
'Actual365Fixed(Canadian)': ql.Actual365Fixed(ql.Actual365Fixed.Canadian),
'Actual365NoLeap': ql.Actual365NoLeap(),
'ActualActual': ql.ActualActual(),
'Business252': ql.Business252()}
for name,dc in dayCounters.items():
dd=dc.dayCount(startDate,endDate)
print(name,'计算规则天数',dd)
输出结果:
SimpleDayCounter 计算规则天数 40
Thirty360 计算规则天数 40
Actual360 计算规则天数 41
Actual365Fixed 计算规则天数 41
Actual365Fixed(Canadian) 计算规则天数 41
Actual365NoLeap 计算规则天数 41
ActualActual 计算规则天数 41
Business252 计算规则天数 27
Schedule:时间表
Schedule(effectiveDate, terminationDate, tenor, calendar, convention, terminationDateConvention, rule, endOfMonth, firstDate=Date(), nextToLastDate=Date()),各变量分别代表:
effectiveDate, terminationDate : 日历列表的起始和终止日期, 比如债券的定价和到期日期
tenor : Period对象, 两个日期的间隔, 如债券发行频率(1年或6个月)或利率掉期利率(3个月)。
calendar : 一个日历表,用于生成要遵循的日期的特定日历。
convention : 整数型,如何调整非工作天(最后一天除外),值范围是quantlib-python的一些保留变量。
terminationDateConvention : 整数型,如果最后的日期是非工作日,如何调整它,值范围是quantlib-python的一些保留变量。
Rule : 日期生成的一个成员,用于为日期生成规则。
endOfMonth : 如果开始日期在月底,是否需要在月底安排其他日期(最后日期除外)。
firstDate : nextToLastDate(可选):Date,为生成的方法规则提供的开始和结束日期(不常用)。
Schedule对象的行为和list类似,是一种存储Date对象的序列容器。使用len(sch):返回 Schedule 对象sch内日期的个数,[i]:返回第 i 个日期,Schedule对象是可迭代的。
effectiveDate = ql.Date(1,1,2018)
terminationDate = ql.Date(15,6,2020)
frequency = ql.Period('6M')
#默认使用当前系统日期
calendar = ql.TARGET()
convention = ql.ModifiedFollowing
terminationDateConvention = ql.ModifiedFollowing
#Forward是以初始日期向后推算,Backward是以结束日期向前推算
rule = ql.DateGeneration.Forward
endOfMonth = False
mysch = ql.Schedule(effectiveDate, terminationDate, frequency, calendar,
convention, terminationDateConvention, rule, endOfMonth)
for i,d in enumerate(mysch):
print(i+1,d)
输出结果:
1 January 2nd, 2018
2 July 2nd, 2018
3 January 2nd, 2019
4 July 1st, 2019
5 January 2nd, 2020
6 June 15th, 2020
schedule常用的函数:
until(d):从日期列表中截取前半部分,并保证最后一个日期是d。
isRegular(i):判断第 i 个区间是否完整。如果一个Schedule对象有 n 个日期,该对象就有 n-1个区间,那么第 i 个区间的长度和事先规定的时间间隔一致,则判断该区间是完整的(Regular)。
mys=mysch.until(ql.Date(15, ql.June, 2019))
for i in range(len(mys)-1):
print(mys[i],'至',mys[i+1],'该区间完整吗?',mys.isRegular(i+1))
输出结果:
January 2nd, 2018 至 July 2nd, 2018 该区间完整吗?True
July 2nd, 2018 至 January 2nd, 2019 该区间完整吗?True
January 2nd, 2019 至 June 15th, 2019 该区间完整吗?False
DateGeneration
许多产品的估值依赖于对未来现金流的分析,因此准确地列出未来现金流的日期是至关重要的。在给定开始和结束日期后,可以采用“反向方法”或“正向方法”生成日期列表。
effectiveDate = ql.Date(5,1,2020)
terminationDate = ql.Date(20,4,2020)
frequency = ql.Period('1M')
#默认使用当前系统日期
calendar = ql.TARGET()
convention = ql.ModifiedFollowing
terminationDateConvention = ql.ModifiedFollowing
#Forward是以初始日期向后推算,Backward是以结束日期向前推算
endOfMonth = False
rules = {
'Backward': ql.DateGeneration.Backward,
'Forward': ql.DateGeneration.Forward,
'Zero': ql.DateGeneration.Zero,
'ThirdWednesDay': ql.DateGeneration.ThirdWednesday,
'Twentieth': ql.DateGeneration.Twentieth,
'TwentiethIMM': ql.DateGeneration.TwentiethIMM,
'CDS': ql.DateGeneration.CDS
}
for name, rule in rules.items():
schedule = ql.Schedule(effectiveDate, terminationDate, frequency, calendar,
convention, terminationDateConvention, rule, endOfMonth)
print(name, [dt for dt in schedule])
输出结果:
InterestRate:利率类
InterestRate类可用于存储具有复利类型、日计数和复利频率的利率。下面我们将展示如何使用实际日计数惯例(Actual/Actual)创建8.0%复利年利率。
annual_rate = 0.08
day_count = ql.ActualActual()
compound_type = ql.Compounded
frequency = ql.Annual
interest_rate = ql.InterestRate(annual_rate,
day_count,compound_type,frequency)
假设你以上述描述的利率投资一美元,利息对象中的复合因子法给出你的投资在任何时期后的价值。下面演示由复合因子返回的2年的值与预期的复利公式是一致的。
t = 2.0
print (interest_rate.compoundFactor(t))
print((1.0+annual_rate)**2)
输出结果:1.1664 1.1664
discountFactor方法返回复合因子方法的倒数。在计算未来现金流的现值时,折现系数是非常实用的。
print (f'{interest_rate.discountFactor(t):.4f}')
print (f'{1.0/interest_rate.compoundFactor(t):.4f}')
输出结果:0.8573 0.8573
一个给定的利率可以转换为其他的复利类型和复利频率使用相等的中心方法。
compound_type= ql.Compounded
t=2.0
new_frequency = ql.Monthly
new_interest_rate =interest_rate.equivalentRate(compound_type,
new_frequency,t)
new_annual_rate = new_interest_rate.rate()
print (f'{new_annual_rate:.4f}')
输出结果:0.0772
两个利率对象(interest_rate和new_interest_rate)的折现因子相同,如下所示。
print (f'{interest_rate.discountFactor(t):.4f}')
print (f'{new_interest_rate.discountFactor(t):.4f}')
输出结果:0.8573 0.8573
结语
QuantLib主要用于固定收益和衍生品的量化分析,内容包罗万象,涵盖的领域也比较广。本文主要介绍了QuantLib的基础模块——Dates日期类和InterestRate利率类。这两个类是后续利率、债券、金融衍生品估值和定价分析的重要基础。后续推文将以专题的形式介绍Quantlib在固定收益分析中的应用案例,敬请期待。
看完记得点赞和在看哦~
参考资料:
1. Luigi Ballabio and Goutham Balaraman,2017,《QuantLib Python Cookbook》.
2. QuantLib官方网上英文教程:https://quantlib-python-docs.readthedocs.io/en/latest/dates.html
关于Python金融量化
专注于分享Python在金融量化领域的应用。加入知识星球,可以免费获取量化投资视频资料、量化金融相关PDF资料、公众号文章Python完整源码、量化投资前沿分析框架,与博主直接交流、结识圈内朋友等。
本文地址:https://blog.csdn.net/ndhtou222/article/details/109634642