可赎回债券的 OAS Quantlib
OAS Quantlib of Callable Bond
我正在尝试确定 QuantLib 中可赎回债券的 OAS。然而,我的结果总是阴性!?
我想知道赎回时间表是否有问题,因为根据 Hull White 模型对债券定价返回的债券收益率似乎是合理的。
考虑以下债券合约:
import QuantLib as ql
import numpy as np
import pandas as pd
bf = ql.BondFunctions
qd = ql.DateParser.parseFormatted
# Conventions
accrual_convention = ql.Unadjusted
Rule = ql.DateGeneration.Backward
endofMonth = False
firstDate = None
# OAS
compounding = ql.Compounded
frequency = ql.Annual
calendar = ql.UnitedStates()
a = 0.1
sigma = 0.1
grid_points = 100
face_amount = 100
mkt_price = 78
contract = {
'IssueDate': ql.Date(30,6,2016),
'MaturityDate': ql.Date(15,6,2023),
'SettlementDays': 2,
'FirstCouponDate': ql.Date(15,12,2016),
'NextToLastCouponDate': ql.Date(15,12,2022),
'RealValue': 0.0675,
'FirstCallDate': ql.Date(15,6,2019),
'OptionalityEndDate': ql.Date(15,6,2023),
'OperatingCountry': 'US',
'StrikeDate': [ql.Date(15,6,2019), ql.Date(15,6,2020), ql.Date(15,6,2021)],
'OptionalityType': ['Call', 'Call', 'Call'],
'NoticeDays': [30, 30, 30],
'StrikePrice': [103.375, 101.688, 100.0]}
# Here is the zero curve:
times= np.array(['0 MO', '1 MO', '2 MO', '3 MO', '6 MO', '1 YR', '2 YR', '3 YR',
'5 YR', '7 YR', '10 YR', '20 YR', '30 YR'], dtype=object)
dates = [ql.Date(9,2,2017), ql.Date(9,3,2017), ql. Date(9,4,2017),
ql.Date(9,5,2017),ql.Date(9,8,2017), ql.Date(9,2,2018),
ql.Date(9,2,2019),ql.Date(9,2,2020), ql.Date(9,2,2022),
ql.Date(9,2,2024), ql.Date(9,2,2027),ql.Date(9,2,2037),
ql.Date(9,2,2047)]
rates = np.array([0.51 , 0.51 , 0.525, 0.54 , 0.64 ,
0.8 , 1.2 , 1.46 , 1.88 , 2.2 ,
2.4 , 2.74 , 3.02 ])
day_count = ql.ActualActual()
calc_date = ql.Date(9,2,2017)
ql.Settings.instance().evaluationDate = calc_date
issue_date = contract["IssueDate"]
maturity_date = contract["MaturityDate"]
tenor = ql.Period(ql.Semiannual)
coupon = contract["RealValue"]
settlement_days = contract["SettlementDays"]
# Determine Schedule
schedule = ql.Schedule(issue_date,
maturity_date,
tenor,
calendar,
accrual_convention,
accrual_convention,
Rule,
endofMonth)
# Initiate Zero Curve
curve = ql.ZeroCurve(dates,
rates,
ql.ActualActual(),
calendar,
ql.Linear())
curve.enableExtrapolation()
ts_handle = ql.YieldTermStructureHandle(curve)
def get_call_schedule(df, period=ql.Period(ql.Annual)):
dates = df["StrikeDate"]
prices = df["StrikePrice"]
callability_schedule = ql.CallabilitySchedule()
null_calendar = ql.NullCalendar()
call_date = df["StrikeDate"][0]
for time in range(len(df["StrikeDate"])):
callability_price = ql.CallabilityPrice(prices[time],
ql.CallabilityPrice.Clean)
callability_schedule.append(ql.Callability(callability_price,
ql.Callability.Call,
dates[time]))
call_date = null_calendar.advance(call_date, period)
return callability_schedule
callability_schedule = get_call_schedule(contract)
bond = ql.CallableFixedRateBond(settlement_days,
face_amount,
schedule,
[coupon],
day_count,
ql.Following,
face_amount,
calc_date,
callability_schedule)
def value_bond(a, s, ts_handle, grid_points, bond):
model = ql.HullWhite(ts_handle, a, s)
engine = ql.TreeCallableFixedRateBondEngine(model, grid_points)
bond.setPricingEngine(engine)
return bond
bondprice = value_bond(a, sigma, ts_handle, grid_points, bond)
OAS = bondprice.OAS(mkt_price,
ts_handle,
day_count,
compounding,
frequency)
bond_yield = bondprice.bondYield(mkt_price,
day_count,
compounding,
frequency)
print(OAS)
print(bond_yield)
这产生了 -6.69 的 OAS 值和 0.121 或 12.1% 的债券收益率 (YTM)。如果在行权日考虑欧式期权与在息票支付日支付行权价的美式期权,会有很大的不同吗?
我会将预付罚金(看涨期权行使价)设置得非常高,以至于看涨总是不经济,然后observe/confirm您的 OAS 为零。这至少会验证您的一些整体设置。如果它通过了该测试,那么我将逐步使其中之一变得经济,并尝试单独为欧式期权定价(您可以在仿射的 HW 过程之上使用 Jamshidian Engine 进行封闭形式)然后查看债券 dv01 的期权足够接近您的 OAS(假设后者为正)。尽管如果您的 OAS 与一组美国呼叫日期为负,但它不太可能在欧洲呼叫时间表中变为正。但这些测试可能会提供一些见解。
您的费率降低了 100 倍。2% 的费率需要写成 0.02,而不是 2.0。
我正在尝试确定 QuantLib 中可赎回债券的 OAS。然而,我的结果总是阴性!?
我想知道赎回时间表是否有问题,因为根据 Hull White 模型对债券定价返回的债券收益率似乎是合理的。
考虑以下债券合约:
import QuantLib as ql
import numpy as np
import pandas as pd
bf = ql.BondFunctions
qd = ql.DateParser.parseFormatted
# Conventions
accrual_convention = ql.Unadjusted
Rule = ql.DateGeneration.Backward
endofMonth = False
firstDate = None
# OAS
compounding = ql.Compounded
frequency = ql.Annual
calendar = ql.UnitedStates()
a = 0.1
sigma = 0.1
grid_points = 100
face_amount = 100
mkt_price = 78
contract = {
'IssueDate': ql.Date(30,6,2016),
'MaturityDate': ql.Date(15,6,2023),
'SettlementDays': 2,
'FirstCouponDate': ql.Date(15,12,2016),
'NextToLastCouponDate': ql.Date(15,12,2022),
'RealValue': 0.0675,
'FirstCallDate': ql.Date(15,6,2019),
'OptionalityEndDate': ql.Date(15,6,2023),
'OperatingCountry': 'US',
'StrikeDate': [ql.Date(15,6,2019), ql.Date(15,6,2020), ql.Date(15,6,2021)],
'OptionalityType': ['Call', 'Call', 'Call'],
'NoticeDays': [30, 30, 30],
'StrikePrice': [103.375, 101.688, 100.0]}
# Here is the zero curve:
times= np.array(['0 MO', '1 MO', '2 MO', '3 MO', '6 MO', '1 YR', '2 YR', '3 YR',
'5 YR', '7 YR', '10 YR', '20 YR', '30 YR'], dtype=object)
dates = [ql.Date(9,2,2017), ql.Date(9,3,2017), ql. Date(9,4,2017),
ql.Date(9,5,2017),ql.Date(9,8,2017), ql.Date(9,2,2018),
ql.Date(9,2,2019),ql.Date(9,2,2020), ql.Date(9,2,2022),
ql.Date(9,2,2024), ql.Date(9,2,2027),ql.Date(9,2,2037),
ql.Date(9,2,2047)]
rates = np.array([0.51 , 0.51 , 0.525, 0.54 , 0.64 ,
0.8 , 1.2 , 1.46 , 1.88 , 2.2 ,
2.4 , 2.74 , 3.02 ])
day_count = ql.ActualActual()
calc_date = ql.Date(9,2,2017)
ql.Settings.instance().evaluationDate = calc_date
issue_date = contract["IssueDate"]
maturity_date = contract["MaturityDate"]
tenor = ql.Period(ql.Semiannual)
coupon = contract["RealValue"]
settlement_days = contract["SettlementDays"]
# Determine Schedule
schedule = ql.Schedule(issue_date,
maturity_date,
tenor,
calendar,
accrual_convention,
accrual_convention,
Rule,
endofMonth)
# Initiate Zero Curve
curve = ql.ZeroCurve(dates,
rates,
ql.ActualActual(),
calendar,
ql.Linear())
curve.enableExtrapolation()
ts_handle = ql.YieldTermStructureHandle(curve)
def get_call_schedule(df, period=ql.Period(ql.Annual)):
dates = df["StrikeDate"]
prices = df["StrikePrice"]
callability_schedule = ql.CallabilitySchedule()
null_calendar = ql.NullCalendar()
call_date = df["StrikeDate"][0]
for time in range(len(df["StrikeDate"])):
callability_price = ql.CallabilityPrice(prices[time],
ql.CallabilityPrice.Clean)
callability_schedule.append(ql.Callability(callability_price,
ql.Callability.Call,
dates[time]))
call_date = null_calendar.advance(call_date, period)
return callability_schedule
callability_schedule = get_call_schedule(contract)
bond = ql.CallableFixedRateBond(settlement_days,
face_amount,
schedule,
[coupon],
day_count,
ql.Following,
face_amount,
calc_date,
callability_schedule)
def value_bond(a, s, ts_handle, grid_points, bond):
model = ql.HullWhite(ts_handle, a, s)
engine = ql.TreeCallableFixedRateBondEngine(model, grid_points)
bond.setPricingEngine(engine)
return bond
bondprice = value_bond(a, sigma, ts_handle, grid_points, bond)
OAS = bondprice.OAS(mkt_price,
ts_handle,
day_count,
compounding,
frequency)
bond_yield = bondprice.bondYield(mkt_price,
day_count,
compounding,
frequency)
print(OAS)
print(bond_yield)
这产生了 -6.69 的 OAS 值和 0.121 或 12.1% 的债券收益率 (YTM)。如果在行权日考虑欧式期权与在息票支付日支付行权价的美式期权,会有很大的不同吗?
我会将预付罚金(看涨期权行使价)设置得非常高,以至于看涨总是不经济,然后observe/confirm您的 OAS 为零。这至少会验证您的一些整体设置。如果它通过了该测试,那么我将逐步使其中之一变得经济,并尝试单独为欧式期权定价(您可以在仿射的 HW 过程之上使用 Jamshidian Engine 进行封闭形式)然后查看债券 dv01 的期权足够接近您的 OAS(假设后者为正)。尽管如果您的 OAS 与一组美国呼叫日期为负,但它不太可能在欧洲呼叫时间表中变为正。但这些测试可能会提供一些见解。
您的费率降低了 100 倍。2% 的费率需要写成 0.02,而不是 2.0。