QuantLib 中涉及利率互换的功能大体分为两大类:c++
这两类功能是紧密联系的,根据最新报价推算出的期限结构一般能够用来对存续合约进行估值。git
本文接下来介绍如何根据合约报价推算出隐含的利率期限结构,并以建信金科的技术文档 《利率互换贴现因子曲线的构造模型》 中图 16 的结果做为比较基准。github
图 16 的结果:bootstrap
经过合约报价推算期限结构的过程称为“bootstrap”,其思想和实践很是相似于理论证实中用到的“数学概括法”,大致过程以下:工具
其中 \(TS\) 能够是即期利率、远期利率、贴现因子三者中的任意一个,而可用的插值方法更是五花八门,例如线性插值、样条插值、对数线性插值和常数插值等等。两个维度相互搭配能够产生很是多的组合,QuantLib 经过模板技术实现两个维度的自由搭配,具体选择哪一种组合要视业务须要而定。ui
须要注意的是,利率互换的估值对合约条款比较敏感。lua
示例中的合约均是 Shibor 3M 的利率互换,条款细则以下:spa
完整的代码请见 QuantLibEx 项目的 example.cpp 文件。rest
using namespace QuantLib; using namespace std; Calendar calendar = China(China::IB); Date today(15, January, 2020); Settings::instance().evaluationDate() = today; Natural delayDays = 1; Date settlementDate = calendar.advance( today, delayDays, Days); // must be a business day settlementDate = calendar.adjust(settlementDate); cout << "Today: " << today << endl; cout << "Settlement date: " << settlementDate << endl;
Today: January 15th, 2020 Settlement date: January 16th, 2020
RateHelper
QuantLib 中 bootstrap 计算的核心是为 PiecewiseYieldCurve
模板类配置 RateHelper
对象,不一样的金融工具要使用对应的派生类。对于已知利率一般用 DepositRateHelper
类,而普通互换则用 SwapRateHelper
类。code
示例没有使用 QuantLib 提供的 Shibor
类,而是本身根据合约从新配置了一个对象。(查看源代码的话,这实际上正是 QuantLib 中 IborIndex
派生类的广泛构造方式)
另外,Actual365_25
是 QuantLib 中未提供的,要本身实现,几乎就是 Actual365Fixed
的翻版。
DayCounter termStrcDayCounter = Actual365_25(); Period mn1(1, Months), mn3(3, Months), mn6(6, Months), mn9(9, Months), yr1(1, Years), yr2(2, Years), yr3(3, Years), yr4(4, Years), yr5(5, Years), yr7(7, Years), yr10(10, Years); ext::shared_ptr<Quote> m1Rate(new SimpleQuote(2.7990 / 100.0)), m3Rate(new SimpleQuote(2.8650 / 100.0)), s6mRate(new SimpleQuote(2.8975 / 100.0)), s9mRate(new SimpleQuote(2.9125 / 100.0)), s1yRate(new SimpleQuote(2.9338 / 100.0)), s2yRate(new SimpleQuote(3.0438 / 100.0)), s3yRate(new SimpleQuote(3.1639 / 100.0)), s4yRate(new SimpleQuote(3.2805 / 100.0)), s5yRate(new SimpleQuote(3.3876 / 100.0)), s7yRate(new SimpleQuote(3.5575 / 100.0)), s10yRate(new SimpleQuote(3.7188 / 100.0)); Handle<Quote> m1RateHandle(m1Rate), m3RateHandle(m3Rate), s6mRateHandle(s6mRate), s9mRateHandle(s9mRate), s1yRateHandle(s1yRate), s2yRateHandle(s2yRate), s3yRateHandle(s3yRate), s4yRateHandle(s4yRate), s5yRateHandle(s5yRate), s7yRateHandle(s7yRate), s10yRateHandle(s10yRate); DayCounter depositDayCounter = Actual360(); ext::shared_ptr<RateHelper> m1(new DepositRateHelper( m1RateHandle, mn1, delayDays, calendar, ModifiedFollowing, false, depositDayCounter)), m3(new DepositRateHelper( m3RateHandle, mn3, delayDays, calendar, ModifiedFollowing, false, depositDayCounter)); Frequency fixedLegFreq = Quarterly; BusinessDayConvention fixedLegConv = ModifiedFollowing; DayCounter fixedLegDayCounter = Actual365Fixed(); ext::shared_ptr<IborIndex> shiborIndex( new IborIndex( "Shibor", mn3, delayDays, CNYCurrency(), calendar, Unadjusted, false, Actual360())); ext::shared_ptr<RateHelper> s6m(new SwapRateHelper( s6mRateHandle, mn6, calendar, fixedLegFreq, fixedLegConv, fixedLegDayCounter, shiborIndex)), s9m(new SwapRateHelper( s9mRateHandle, mn9, calendar, fixedLegFreq, fixedLegConv, fixedLegDayCounter, shiborIndex)), s1y(new SwapRateHelper( s1yRateHandle, yr1, calendar, fixedLegFreq, fixedLegConv, fixedLegDayCounter, shiborIndex)), s2y(new SwapRateHelper( s2yRateHandle, yr2, calendar, fixedLegFreq, fixedLegConv, fixedLegDayCounter, shiborIndex)), s3y(new SwapRateHelper( s3yRateHandle, yr3, calendar, fixedLegFreq, fixedLegConv, fixedLegDayCounter, shiborIndex)), s4y(new SwapRateHelper( s4yRateHandle, yr4, calendar, fixedLegFreq, fixedLegConv, fixedLegDayCounter, shiborIndex)), s5y(new SwapRateHelper( s5yRateHandle, yr5, calendar, fixedLegFreq, fixedLegConv, fixedLegDayCounter, shiborIndex)), s7y(new SwapRateHelper( s7yRateHandle, yr7, calendar, fixedLegFreq, fixedLegConv, fixedLegDayCounter, shiborIndex)), s10y(new SwapRateHelper( s10yRateHandle, yr10, calendar, fixedLegFreq, fixedLegConv, fixedLegDayCounter, shiborIndex)); vector<ext::shared_ptr<RateHelper>> instruments; instruments.push_back(m1); instruments.push_back(m3); instruments.push_back(s6m); instruments.push_back(s9m); instruments.push_back(s1y); instruments.push_back(s2y); instruments.push_back(s3y); instruments.push_back(s4y); instruments.push_back(s5y); instruments.push_back(s7y); instruments.push_back(s10y);
Bootstrap 的过程很简单,这里选用 PiecewiseYieldCurve<ForwardRate, BackwardFlat>
,与示例一致,将获得一个“阶梯状”的远期期限结构。
ext::shared_ptr<YieldTermStructure> termStrc( new PiecewiseYieldCurve<ForwardRate, BackwardFlat>( today, instruments, termStrcDayCounter));
Date curveNodeDate = calendar.adjust(settlementDate + mn1); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + mn3); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + mn6); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + mn9); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + yr1); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + yr2); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + yr3); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + yr4); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + yr5); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + yr7); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl; curveNodeDate = calendar.adjust(settlementDate + yr10); cout << setw(4) << curveNodeDate - today << ", " << termStrc->discount(curveNodeDate) << ", " << termStrc->zeroRate(curveNodeDate, termStrcDayCounter, Continuous).rate() * 100.0 << endl;
33, 0.997441, 2.83629 92, 0.992733, 2.89565 183, 0.985631, 2.88875 275, 0.978375, 2.90377 369, 0.970721, 2.94142 733, 0.940822, 3.03969 1097, 0.909515, 3.15787 1462, 0.877015, 3.27853 1828, 0.843917, 3.39077 2560, 0.778373, 3.57473 3654, 0.687352, 3.74755
与建信金科专家们的模型结果很是接近了,只有一个日期出现了不一致。
因为工做日转换规则是 MFL,对假期比较敏感,QuantLib 中包含中国假期的日历类是 China
,它所记录的假期可能和建信金科系统的假期不一致。