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

如果未做特别说明,文中的程序都是 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 的利率互换。
posted @ 2020-05-22 23:31  xuruilong100  阅读(262)  评论(8编辑  收藏