天天看點

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

目錄

  • QuantLib 金融計算——案例之普通利率互換分析(2)
    • 概述
    • 合約條款
    • 實踐
      • 設定 RateHelper
      • Bootstrap
      • 驗證
    • 差異可能的來源
    • 下一步
    • 擴充閱讀
如果未做特别說明,文中的程式都是 C++11 代碼。

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

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

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

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

圖 16 的結果:

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

通過合約報價推算期限結構的過程稱為“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 的過程很簡單,這裡選用

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
           

與建信金科專家們的模型結果非常接近了,但有兩個日期出現了不一緻,原因不明。

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

由于工作日轉換規則是 MFL,對假期比較敏感,QuantLib 中包含中國假期的月曆類是

China

,它所記錄的假期可能和建信金科系統的假期不一緻。

  • 分析國内市場上挂鈎的 FR007 的利率互換。
  • 分析國内市場上挂鈎的 LPR 的利率互換。

《QuantLib 金融計算》系列合集

★ 持續學習 ★ 堅持創作 ★

繼續閱讀