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

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

概述

QuantLib 中涉及利率互换的功能大体分为两大类:c++

  • 对存续的利率互换合约估值;
  • 根据利率互换合约的成交报价推算隐含的期限结构。

这两类功能是紧密联系的,根据最新报价推算出的期限结构一般能够用来对存续合约进行估值。git

本文接下来介绍如何根据合约报价推算出隐含的利率期限结构,并以建信金科的技术文档 《利率互换贴现因子曲线的构造模型》图 16 的结果做为比较基准。github

图 16 的结果:bootstrap

合约条款

经过合约报价推算期限结构的过程称为“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 经过模板技术实现两个维度的自由搭配,具体选择哪一种组合要视业务须要而定。ui

须要注意的是,利率互换的估值对合约条款比较敏感。lua

示例中的合约均是 Shibor 3M 的利率互换,条款细则以下:spa

  • 浮动利率:Shibor 3M
  • 利差:0.0%
  • 估值日期:2020-01-15
  • 结算延迟:1 天
  • 重置延迟:1 天
  • 浮动端支付频率:季度
  • 浮动端天数计算规则:ACT/360
  • 固定端支付频率:季度
  • 固定端天数计算规则:ACT/365
  • 日历:中国银行间市场
  • 工做日转换规则:Modified Following(MFL)

实践

完整的代码请见 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

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 的利率互换。
相关文章
相关标签/搜索