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
,它所記錄的假期可能和建信金科系統的假期不一致。