# QuantLib 金融计算——案例之固息债的关键利率久期（KRD）

2020-11-16 更新：修正 ActualActual 的误用。

# QuantLib 金融计算——案例之固息债的关键利率久期（KRD）

## 关键利率久期的基本概念

### 从扰动的角度计算 KRD

$\frac{P^{\prime} - P}{P \times \Delta y}$

$\frac{P^{+} - P^{-}}{2 P \times \Delta y}$

## 计算案例

• 债券起息日：2020-03-10
• 到期兑付日：2030-03-10
• 债券期限：10 年
• 面值(元)：100.00
• 计息基准：A/A
• 息票类型：附息式固定利率
• 付息频率：年
• 票面利率（%）：3.0700
• 结算方式：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)
terminationDateConvention = convention
rule = ql.DateGeneration.Backward
endOfMonth = False

schedule = ql.Schedule(
effectiveDate,
terminationDate,
tenor,
calendar,
convention,
terminationDateConvention,
rule,
endOfMonth)

scheduleEx = ql.Schedule(
effectiveDate,
ql.Date(10, ql.March, 2031),
tenor,
calendar,
convention,
terminationDateConvention,
rule,
endOfMonth)

coupons = ql.DoubleVector(1)
coupons[0] = 3.07 / 100.0
accrualDayCounter = ql.ActualActual(
ql.ActualActual.Bond, scheduleEx)

bond = ql.FixedRateBond(
settlementDays,
faceAmount,
schedule,
coupons,
accrualDayCounter,
paymentConvention)


bondYield = 3.4124 / 100.0

compounding = ql.Compounded
frequency = ql.Annual

flatCurve = ql.YieldTermStructureHandle(
ql.FlatForward(
settlementDays,
calendar,
bondYield,
accrualDayCounter,
compounding,
frequency))


1. 一个 Handle<YieldTermStructure> 对象，也就是当前的期限结构，关键期限上的扰动将被施加在此期限结构上；
2. 一列 Handle<Quote> 对象，表示关键期限上的利率扰动；
3. 一列 Date 对象，表示扰动对应的关键期限。

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)

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(
flatCurve,
dates,
compounding,
frequency,
accrualDayCounter))


engine = ql.DiscountingBondEngine(termStructure)
bond.setPricingEngine(engine)


## Quote 类和引用带来的便利

duration = ql.BondFunctions.duration(
bond,
bondYield,
accrualDayCounter,
compounding,
frequency,
ql.Duration.Modified)

tab = pt.PrettyTable(['item', 'value'])

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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.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   | 2.84373153 |
|  krd10y  | 4.32794826 |
|  krdSum  | 8.07712244 |
+----------+------------+


KRD 的曲线图是这样的：

sns.lineplot(
x=times, y=krds, markers='o')


## 参考文献

• 《Interest Rate Risk Modeling》
• 杨筱燕，《关键利率久期计算及实例分析》

## 扩展阅读

《QuantLib 金融计算》系列合集

posted @ 2020-08-26 21:14  xuruilong100  阅读(2391)  评论(3编辑  收藏  举报