[TOC] QuantLib 金融計算——案例之普通利率互換分析(2) 概述 QuantLib 中涉及利率互換的功能大致分為兩大類: 對存續的利率互換合約估值; 根據利率互換合約的成交報價推算隱含的期限結構。 這兩類功能是緊密聯繫的,根據最新報價推算出的期限結構通常可以用來對存續合約進行估值。 本文 ...
目錄
QuantLib 金融計算——案例之普通利率互換分析(2)
概述
QuantLib 中涉及利率互換的功能大致分為兩大類:
- 對存續的利率互換合約估值;
- 根據利率互換合約的成交報價推算隱含的期限結構。
這兩類功能是緊密聯繫的,根據最新報價推算出的期限結構通常可以用來對存續合約進行估值。
本文接下來介紹如何根據合約報價推算出隱含的利率期限結構,並以建信金科的技術文檔 《利率互換貼現因數曲線的構造模型》 中圖 16 的結果作為比較基準。
圖 16 的結果:
合約條款
通過合約報價推算期限結構的過程稱為“bootstrap”,其思想和實踐非常類似於理論證明中用到的“數學歸納法”,大體過程如下:
- 首先將要用到的已知利率和金融工具根據期限升序排列;
- 假設已經求得期限結構上的第 \(n\) 個值——\(TS_n\),對應於第 \(n\) 個報價;
- 令 \(TS_{n+1}\) 是個待定參數 \(x\),並給定一個初值;
- 用已知的期限結構數據——\(TS_1,\dots,TS_n,x\),對第 \(n+1\) 個金融工具進行估值;
- 調整 \(x\),使得估值結果與報價達到一致,不存在套利空間;
- 此時,\(x\) 便是 \(TS_{n+1}\);
- 以此類推。
其中 \(TS\) 可以是即期利率、遠期利率、貼現因數三者中的任意一個,而可用的插值方法更是五花八門,例如線性插值、樣條插值、對數線性插值和常數插值等等。兩個維度相互搭配可以產生非常多的組合,QuantLib 通過模板技術實現兩個維度的自由搭配,具體選擇哪種組合要視業務需要而定。
需要註意的是,利率互換的估值對合約條款比較敏感。
示例中的合約均是 Shibor 3M 的利率互換,條款細則如下:
- 浮動利率:Shibor 3M
- 利差:0.0%
- 估值日期:2020-01-15
- 結算延遲:1 天
- 重置延遲:1 天
- 浮動端支付頻率:季度
- 浮動端天數計算規則:ACT/360
- 固定端支付頻率:季度
- 固定端天數計算規則:ACT/365
- 日曆:中國銀行間市場
- 工作日轉換規則:Modified Following(MFL)
實踐
完整的代碼請見 QuantLibEx 項目的 example.cpp 文件。
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
類。
示例沒有使用 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
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
,它所記錄的假期可能和建信金科系統的假期不一致。
下一步
- 分析國內市場上掛鉤的 FR007 的利率互換。
- 分析國內市場上掛鉤的 LPR 的利率互換。