QuantLib 金融計算——案例之普通利率互換分析(2):Shibor3M 互換分析


如果未做特別說明,文中的程序都是 C++11 代碼。

QuantLib 金融計算——案例之普通利率互換分析(2)

概述

QuantLib 中涉及利率互換的功能大致分為兩大類:

  • 對存續的利率互換合約估值;
  • 根據利率互換合約的成交報價推算隱含的期限結構。

這兩類功能是緊密聯系的,根據最新報價推算出的期限結構通常可以用來對存續合約進行估值。

本文接下來介紹如何根據合約報價推算出隱含的利率期限結構,並以建信金科的技術文檔 《利率互換貼現因子曲線的構造模型》圖 16 的結果作為比較基准。

圖 16 的結果:

合約條款

通過合約報價推算期限結構的過程稱為“bootstrap”,其思想和實踐非常類似於理論證明中用到的“數學歸納法”,大體過程如下:

  1. 首先將要用到的已知利率和金融工具根據期限升序排列;
  2. 假設已經求得期限結構上的第 \(n\) 個值——\(TS_n\),對應於第 \(n\) 個報價;
  3. \(TS_{n+1}\) 是個待定參數 \(x\),並給定一個初值;
  4. 用已知的期限結構數據——\(TS_1,\dots,TS_n,x\),對第 \(n+1\) 個金融工具進行估值;
  5. 調整 \(x\),使得估值結果與報價達到一致,不存在套利空間;
  6. 此時,\(x\) 便是 \(TS_{n+1}\)
  7. 以此類推。

其中 \(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 的利率互換。

擴展閱讀

《QuantLib 金融計算》系列合集


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM