QuantLib 金融计算——案例之固息债的关键利率久期(KRD)
目錄QuantLib 金融計(jì)算——案例之固息債的關(guān)鍵利率久期(KRD)概述關(guān)鍵利率久期的基本概念從擾動的角度計(jì)算 KRD計(jì)算案例Quote 類和引用帶來的便利參考文獻(xiàn)擴(kuò)展閱讀
QuantLib 金融計(jì)算——案例之固息債的關(guān)鍵利率久期(KRD)
概述
作為利率風(fēng)險(xiǎn)系列的第二篇,本文將以《Interest Rate Risk Modeling》為藍(lán)本,介紹關(guān)鍵利率久期(KRD)的基本概念,并依托 QuantLib 展示相關(guān)的計(jì)算案例。
有關(guān) KRD 的高級內(nèi)容請見《《Interest Rate Risk Modeling》閱讀筆記——第九章》。
關(guān)鍵利率久期的基本概念
上一篇《案例之固息債的價(jià)格、久期、凸性和 BPS》中出現(xiàn)的久期和凸性均是基于到期利率(YTM)的風(fēng)險(xiǎn)度量指標(biāo)。使用 YTM 分析債券隱含了一個重要假設(shè):利率期限結(jié)構(gòu)上各期限的利率同步變化。這個隱含的假設(shè)與現(xiàn)實(shí)有所出入,盡管期限結(jié)構(gòu)上各期限的利率變化高度相關(guān),但并非 100% 一致。顯然,傳統(tǒng)的久期無法描述債券價(jià)格對期限結(jié)構(gòu)非平行變化的敏感性。
若要更精細(xì)地刻畫債券關(guān)于利率變化的敏感性,需要分別考慮不同期限上利率變化對債券價(jià)格的影響,這要求把期限結(jié)構(gòu)本身作為一個動態(tài)變量。
一個期限結(jié)構(gòu)其實(shí)可以看做是一個無限維的向量,任意一個期限均是一個維度。考慮一個無限維的向量是一個高深的數(shù)學(xué)問題,然而基于經(jīng)驗(yàn)觀察,期限結(jié)構(gòu)的平滑性相當(dāng)好,所以只需要選取幾個特殊期限作為“錨點(diǎn)”,實(shí)踐中就可以幾乎完全把握整個曲線的變化。
關(guān)鍵利率久期(KRD)就是債券價(jià)格關(guān)于這些錨點(diǎn)期限上利率的敏感性,一組 KRD 也就描述了債券價(jià)格對期限結(jié)構(gòu)非平行變化的敏感性。
從擾動的角度計(jì)算 KRD
假設(shè)根據(jù)當(dāng)前期限結(jié)構(gòu)算出來的債券價(jià)格是 (P),此時(shí)某個關(guān)鍵期限 (K) 上的利率出現(xiàn)了一個微小的擾動 (Delta y),擾動出現(xiàn)后重新計(jì)算出的債券價(jià)格是 (P^{prime}),那么債券價(jià)格關(guān)于 (K) 期限利率的敏感性就近似是
[frac{P^{prime} - P}{P imes Delta y}
]
也可以采用精度更高的近似方法,正負(fù)擾動對應(yīng)的價(jià)格分別是 (P^{+}) 和 (P^{-}),敏感性近似是
[frac{P^{+} - P^{-}}{2 P imes Delta y}
]
為保證期限結(jié)構(gòu)的平滑性,擾動不能只影響一個特定期限,其影響要平滑地?cái)U(kuò)散到臨近的期限。在 KRD 分析中,要求擾動以線性遞減的形式擴(kuò)展到左右相鄰的期限,而不會影響相距更遠(yuǎn)的期限。例如,選定 5、7、10 年三個相鄰期限,7 年期上 1 bp 的擾動只能影響到 5 和 10 年期。并且,7-5 年之間,擾動以每年 0.5 bp 的速度遞減,7-10 年之間,擾動以每年 1/3 bp 的速度遞減。
計(jì)算案例
繼續(xù)以上一篇《案例之固息債的價(jià)格、久期、凸性和 BPS》中出現(xiàn)的 200205 為例,計(jì)算 2020-07-28 這一天的久期和 KRD。
首先從中國貨幣網(wǎng)查詢債券的基本信息,用以配置 FixedRateBond 對象。
債券起息日:2020-03-10
到期兌付日:2030-03-10
債券期限:10 年
面值(元):100.00
計(jì)息基準(zhǔn):A/A
息票類型:附息式固定利率
付息頻率:年
票面利率(%):3.0700
結(jié)算方式:T+1
import QuantLib as ql
import prettytable as pt
import seaborn as sns
today = ql.Date(28, ql.July, 2020)
ql.Settings.instance().evaluationDate = today
settlementDays = 1
faceAmount = 100.0
effectiveDate = ql.Date(10, ql.March, 2020)
terminationDate = ql.Date(10, ql.March, 2030)
tenor = ql.Period(1, ql.Years)
calendar = ql.China(ql.China.IB)
convention = ql.Unadjusted
terminationDateConvention = convention
rule = ql.DateGeneration.Backward
endOfMonth = False
schedule = ql.Schedule(
effectiveDate,
terminationDate,
tenor,
calendar,
convention,
terminationDateConvention,
rule,
endOfMonth)
coupons = ql.DoubleVector(1)
coupons[0] = 3.07 / 100.0
accrualDayCounter = ql.ActualActual(
ql.ActualActual.Bond, schedule)
paymentConvention = ql.Unadjusted
bond = ql.FixedRateBond(
settlementDays,
faceAmount,
schedule,
coupons,
accrualDayCounter,
paymentConvention)
在上海清算所查詢估值。由于使用的是估值,也就是到期利率,所以當(dāng)前的期限結(jié)構(gòu)用 FlatForward 類表示。對于水平的期限結(jié)構(gòu)而言,遠(yuǎn)期利率、即期利率和到期利率三者相等。
bondYield = 3.4124 / 100.0
compounding = ql.Compounded
frequency = ql.Annual
flatCurve = ql.YieldTermStructureHandle(
ql.FlatForward(
settlementDays,
calendar,
bondYield,
accrualDayCounter,
compounding,
frequency))
計(jì)算 KRD 的時(shí)候需要向當(dāng)前的期限結(jié)構(gòu)添加關(guān)鍵期限上的擾動,為此可以借助 QuantLib 中的 InterpolatedPiecewiseZeroSpreadedTermStructure 類模板,它需要一個模板參數(shù) Interpolator,表示所使用的插值方法類。對于 KRD 的計(jì)算來說,選擇 Linear 作為模板參數(shù),以表示線性插值。
要配置實(shí)例化后的類 InterpolatedPiecewiseZeroSpreadedTermStructure<Linear>,需要提供三個核心參數(shù):
一個 Handle<YieldTermStructure> 對象,也就是當(dāng)前的期限結(jié)構(gòu),關(guān)鍵期限上的擾動將被施加在此期限結(jié)構(gòu)上;
一列 Handle<Quote> 對象,表示關(guān)鍵期限上的利率擾動;
一列 Date 對象,表示擾動對應(yīng)的關(guān)鍵期限。
具體到 python 環(huán)境下,實(shí)例化后的類 InterpolatedPiecewiseZeroSpreadedTermStructure<Linear> 被包裝并重命名為 SpreadedLinearZeroInterpolatedTermStructure 類。
在計(jì)算 KRD 之前,所有擾動的初始值被設(shè)置成零。關(guān)鍵期限有 11 個,分別是 6 個月和 1~10 年,均勻地覆蓋每個付息周期。
initValue = 0.0
rate6m = ql.SimpleQuote(initValue)
rate1y = ql.SimpleQuote(initValue)
rate2y = ql.SimpleQuote(initValue)
rate3y = ql.SimpleQuote(initValue)
rate4y = ql.SimpleQuote(initValue)
rate5y = ql.SimpleQuote(initValue)
rate6y = ql.SimpleQuote(initValue)
rate7y = ql.SimpleQuote(initValue)
rate8y = ql.SimpleQuote(initValue)
rate9y = ql.SimpleQuote(initValue)
rate10y = ql.SimpleQuote(initValue)
rate6mHandle = ql.QuoteHandle(rate6m)
rate1yHandle = ql.QuoteHandle(rate1y)
rate2yHandle = ql.QuoteHandle(rate2y)
rate3yHandle = ql.QuoteHandle(rate3y)
rate4yHandle = ql.QuoteHandle(rate4y)
rate5yHandle = ql.QuoteHandle(rate5y)
rate6yHandle = ql.QuoteHandle(rate6y)
rate7yHandle = ql.QuoteHandle(rate7y)
rate8yHandle = ql.QuoteHandle(rate8y)
rate9yHandle = ql.QuoteHandle(rate9y)
rate10yHandle = ql.QuoteHandle(rate10y)
spreads = ql.QuoteHandleVector()
spreads.append(rate6mHandle)
spreads.append(rate1yHandle)
spreads.append(rate2yHandle)
spreads.append(rate3yHandle)
spreads.append(rate4yHandle)
spreads.append(rate5yHandle)
spreads.append(rate6yHandle)
spreads.append(rate7yHandle)
spreads.append(rate8yHandle)
spreads.append(rate9yHandle)
spreads.append(rate10yHandle)
dates = ql.DateVector()
dates.append(flatCurve.referenceDate() + ql.Period(6, ql.Months))
dates.append(flatCurve.referenceDate() + ql.Period(1, ql.Years))
dates.append(flatCurve.referenceDate() + ql.Period(2, ql.Years))
dates.append(flatCurve.referenceDate() + ql.Period(3, ql.Years))
dates.append(flatCurve.referenceDate() + ql.Period(4, ql.Years))
dates.append(flatCurve.referenceDate() + ql.Period(5, ql.Years))
dates.append(flatCurve.referenceDate() + ql.Period(6, ql.Years))
dates.append(flatCurve.referenceDate() + ql.Period(7, ql.Years))
dates.append(flatCurve.referenceDate() + ql.Period(8, ql.Years))
dates.append(flatCurve.referenceDate() + ql.Period(9, ql.Years))
dates.append(flatCurve.referenceDate() + ql.Period(10, ql.Years))
termStructure = ql.YieldTermStructureHandle(
ql.SpreadedLinearZeroInterpolatedTermStructure(
flatCurve,
spreads,
dates,
compounding,
frequency,
accrualDayCounter))
債券定價(jià)引擎采用最常見的 DiscountingBondEngine。
engine = ql.DiscountingBondEngine(termStructure)
bond.setPricingEngine(engine)
Quote 類和引用帶來的便利
在底層 C++ 代碼中,QuantLib 類的構(gòu)造函數(shù)和成員函數(shù)大量使用了常引用參數(shù)和觀察者模式,這使得作為參數(shù)的對象具有了“穿透性”,參數(shù)對象值的改變可以靠引用和觀察者模式串聯(lián)起來的鏈條影響關(guān)聯(lián)的所有其他對象。
具體到 KRD 的計(jì)算,無需重新配置定價(jià)引擎,只要改變關(guān)鍵利率的值就可以自動觸發(fā)債券的計(jì)算。
擾動的大小定為 1 bp,調(diào)用成員方法 setValue 便可改變 SimpleQuote 對象的值。
duration = ql.BondFunctions.duration(
bond,
bondYield,
accrualDayCounter,
compounding,
frequency,
ql.Duration.Modified)
tab = pt.PrettyTable(['item', 'value'])
tab.add_row(['duration', duration])
# calculate KRDs
bp = 0.01 / 100.0
krdSum = 0.0
krds = []
times = []
# 6m KRD
rate6m.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate6m.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate6m.setValue(initValue)
krd6m = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd6m
krds.append(krd6m)
times.append(0.5)
tab.add_row(['krd6m', krd6m])
# 1y KRD
rate1y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate1y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate1y.setValue(initValue)
krd1y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd1y
krds.append(krd1y)
times.append(1.0)
tab.add_row(['krd1y', krd1y])
# 2y KRD
rate2y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate2y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate2y.setValue(initValue)
krd2y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd2y
krds.append(krd2y)
times.append(2.0)
tab.add_row(['krd2y', krd2y])
# 3y KRD
rate3y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate3y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate3y.setValue(initValue)
krd3y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd3y
krds.append(krd3y)
times.append(3.0)
tab.add_row(['krd3y', krd3y])
# 4y KRD
rate4y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate4y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate4y.setValue(initValue)
krd4y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd4y
krds.append(krd4y)
times.append(4.0)
tab.add_row(['krd4y', krd4y])
# 5y KRD
rate5y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate5y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate5y.setValue(initValue)
krd5y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd5y
krds.append(krd5y)
times.append(5.0)
tab.add_row(['krd5y', krd5y])
# 6y KRD
rate6y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate6y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate6y.setValue(initValue)
krd6y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd6y
krds.append(krd6y)
times.append(6.0)
tab.add_row(['krd6y', krd6y])
# 7y KRD
rate7y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate7y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate7y.setValue(initValue)
krd7y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd7y
krds.append(krd7y)
times.append(7.0)
tab.add_row(['krd7y', krd7y])
# 8y KRD
rate8y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate8y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate8y.setValue(initValue)
krd8y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd8y
krds.append(krd8y)
times.append(8.0)
tab.add_row(['krd8y', krd8y])
# 9y KRD
rate9y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate9y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate9y.setValue(initValue)
krd9y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd9y
krds.append(krd9y)
times.append(9.0)
tab.add_row(['krd9y', krd9y])
# 10y KRD
rate10y.setValue(bp)
dirtyPrice1 = bond.dirtyPrice()
rate10y.setValue(-bp)
dirtyPrice2 = bond.dirtyPrice()
rate10y.setValue(initValue)
krd10y = -(dirtyPrice1 - dirtyPrice2) / (2.0 * bp * dirtyPrice)
krdSum += krd10y
krds.append(krd10y)
times.append(10.0)
tab.add_row(['krd10y', krd10y])
tab.add_row(['krdSum', krdSum])
tab.float_format = '.8'
print(tab)
+----------+------------+
| item | value |
+----------+------------+
| duration | 8.07712202 |
| krd6m | 0.01412836 |
| krd1y | 0.02182248 |
| krd2y | 0.05615594 |
| krd3y | 0.08163788 |
| krd4y | 0.10535800 |
| krd5y | 0.12735475 |
| krd6y | 0.14771823 |
| krd7y | 0.16683071 |
| krd8y | 0.18443629 |
| krd9y | 0.11944279 |
| krd10y | 7.05223796 |
| krdSum | 8.07712340 |
+----------+------------+
從結(jié)果上看 KRD 有兩個局部高點(diǎn),一個是接近久期的關(guān)鍵期限(8 年),另一個是接近剩余期限(也就是現(xiàn)金流最大的時(shí)期)的關(guān)鍵期限(10 年)。
理論上,各個 KRD 之和約等于修正久期,這是因?yàn)楦鱾€關(guān)鍵期限上同時(shí)發(fā)生擾動的話就相當(dāng)于曲線發(fā)生了平行移動。數(shù)值結(jié)果正好驗(yàn)證了這一點(diǎn)。
KRD 的曲線圖是這樣的:
sns.pointplot(
x=times, y=krds, markers='o')
參考文獻(xiàn)
《Interest Rate Risk Modeling》
楊筱燕,《關(guān)鍵利率久期計(jì)算及實(shí)例分析》
擴(kuò)展閱讀
《QuantLib 金融計(jì)算》系列合集
總結(jié)
以上是生活随笔為你收集整理的QuantLib 金融计算——案例之固息债的关键利率久期(KRD)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: node+mongoose使用例子
- 下一篇: 【物理/数学】—— 概念的理解 mome