Quantlib Python收益率曲线Bootstrap报错:根未被包围问题求助
QuantLib 掉期定价曲线构建 RuntimeError 问题解决
问题现象
运行QuantLib计算掉期价格时,构建收益率曲线阶段抛出以下错误:
RuntimeError: 2nd leg: 1st iteration: failed at 1st alive instrument, pillar April 4th, 2023, maturity April 4th, 2023, reference date September 30th, 2022: root not bracketed: f[0.600742,1.66461] -> [1.241044e-02,1.241044e-02]
已排查情况:
- 确认利率已除以100,排除该常见问题
- 检查日期设置无明显错误
- 仅使用年度期限利率时代码可正常运行
用户原始代码:
from QuantLib import * import pandas as pd today = Date(30, September, 2022) Settings.instance().evaluationDate = today def create_ql_curve(): estr_short = pd.DataFrame({'Tenor': [6], 'Rate': [1.7466]}) estr_long = pd.DataFrame({'Tenor': [1,2,5,10,15,20], 'Rate': [2.2828, 2.547, 2.661, 2.804, 2.85, 2.35]}) euribor_short = pd.DataFrame({'Tenor': [6], 'Rate': [1.7466]}) euribor_long = pd.DataFrame({'Tenor': [1,2,5,10,15,20], 'Rate': [2.2828, 2.547, 2.661, 2.804, 2.85, 2.35]}) ois_helpers = [OISRateHelper(0, Period(int(tenor), Months), QuoteHandle(SimpleQuote(rate / 100)), Eonia(), YieldTermStructureHandle(), True) for rate, tenor in zip(estr_short['Rate'], estr_short['Tenor'])] ois_helpers += [OISRateHelper(0, Period(int(tenor), Years), QuoteHandle(SimpleQuote(rate / 100)), Eonia(), YieldTermStructureHandle(), True) # telescopicValueDates. When set to True bootstrapping is a lot faster. for rate, tenor in zip(estr_long['Rate'], estr_long['Tenor'])] discount_curve = PiecewiseLogCubicDiscount(0, TARGET(), ois_helpers, Actual365Fixed()) discount_curve.enableExtrapolation() helpers = [SwapRateHelper(QuoteHandle(SimpleQuote(rate / 100)), Period(int(tenor), Months), TARGET(), Annual, Unadjusted, Thirty360(), Euribor6M(), QuoteHandle(), Period(0, Days), YieldTermStructureHandle(discount_curve)) for rate, tenor in zip(euribor_short['Rate'], euribor_short['Tenor'])] helpers += [SwapRateHelper(QuoteHandle(SimpleQuote(rate / 100)), Period(int(tenor), Years), TARGET(), Annual, Unadjusted, Thirty360(), Euribor6M(), QuoteHandle(), Period(0, Days), YieldTermStructureHandle(discount_curve)) for rate, tenor in zip(euribor_long['Rate'], euribor_long['Tenor'])] euribor_curve = PiecewiseLogCubicDiscount(0, TARGET(), helpers, Actual365Fixed()) euribor_curve.enableExtrapolation() return discount_curve, euribor_curve discount_curve, euribor_curve = create_ql_curve() forecast_handle = RelinkableYieldTermStructureHandle(euribor_curve) discount_handle = RelinkableYieldTermStructureHandle(discount_curve) index_6M = Euribor6M(forecast_handle) swap_engine = DiscountingSwapEngine(discount_handle) # swaps start_date = Date(23, April, 2014) maturity_date = Date(23, April, 2023) fixed_schedule = Schedule(start_date, maturity_date, Period(1, Years), TARGET(), Unadjusted, Unadjusted, DateGeneration.Forward, False) floating_schedule = Schedule(start_date, maturity_date, Period(6, Months), TARGET(), ModifiedFollowing, ModifiedFollowing, DateGeneration.Forward, True) notional = 115000000 fixed_rate = 0.01727 fixed_leg_daycount = Actual360() float_spread = 0 float_leg_daycount = Actual360() calendar = TARGET() previous = calendar.advance(today, -27, Weeks) dates = [calendar.advance(previous, n, Days) for n in range(150)] rates = [0.005] * 150 for date, rate in zip(dates, rates): index_6M.addFixing(date, rate) swap = VanillaSwap(VanillaSwap.Receiver, notional, fixed_schedule, fixed_rate, Thirty360(), floating_schedule, index_6M, 0.0, Actual360()) swap.setPricingEngine(swap_engine) price = swap.NPV()
问题根源
错误源于构建Euribor曲线的6个月期限SwapRateHelper参数不匹配:
- 6个月掉期的固定端付息频率设置为
Annual,与浮动端6个月频率冲突,导致现金流时间点逻辑矛盾 - 使用
PiecewiseLogCubicDiscount构建预测曲线,短端插值易出现数值稳定性问题,bootstrap时无法找到有效根区间
修复方案
1. 修正6个月SwapRateHelper的付息频率
将6个月期限的SwapRateHelper固定端付息频率改为Semiannual,匹配浮动端周期:
helpers = [SwapRateHelper(QuoteHandle(SimpleQuote(rate / 100)), Period(int(tenor), Months), TARGET(), Semiannual, # 此处从Annual改为Semiannual Unadjusted, Thirty360(), Euribor6M(YieldTermStructureHandle(discount_curve)), QuoteHandle(), Period(0, Days), YieldTermStructureHandle(discount_curve)) for rate, tenor in zip(euribor_short['Rate'], euribor_short['Tenor'])]
2. 更换曲线构建方法
将Euribor曲线的构建方法改为PiecewiseLinearZero,提升短端bootstrap的稳定性:
euribor_curve = PiecewiseLinearZero(0, TARGET(), helpers, Actual365Fixed())
3. 统一日计数规则
确保SwapRateHelper中的固定端日计数与实际掉期一致,同时初始化Euribor6M时传入贴现曲线,避免默认曲线冲突。
修正后的完整代码
from QuantLib import * import pandas as pd today = Date(30, September, 2022) Settings.instance().evaluationDate = today def create_ql_curve(): estr_short = pd.DataFrame({'Tenor': [6], 'Rate': [1.7466]}) estr_long = pd.DataFrame({'Tenor': [1,2,5,10,15,20], 'Rate': [2.2828, 2.547, 2.661, 2.804, 2.85, 2.35]}) euribor_short = pd.DataFrame({'Tenor': [6], 'Rate': [1.7466]}) euribor_long = pd.DataFrame({'Tenor': [1,2,5,10,15,20], 'Rate': [2.2828, 2.547, 2.661, 2.804, 2.85, 2.35]}) ois_helpers = [OISRateHelper(0, Period(int(tenor), Months), QuoteHandle(SimpleQuote(rate / 100)), Eonia(), YieldTermStructureHandle(), True) for rate, tenor in zip(estr_short['Rate'], estr_short['Tenor'])] ois_helpers += [OISRateHelper(0, Period(int(tenor), Years), QuoteHandle(SimpleQuote(rate / 100)), Eonia(), YieldTermStructureHandle(), True) for rate, tenor in zip(estr_long['Rate'], estr_long['Tenor'])] discount_curve = PiecewiseLogCubicDiscount(0, TARGET(), ois_helpers, Actual365Fixed()) discount_curve.enableExtrapolation() # 修正6个月SwapRateHelper的付息频率为Semiannual helpers = [SwapRateHelper(QuoteHandle(SimpleQuote(rate / 100)), Period(int(tenor), Months), TARGET(), Semiannual, Unadjusted, Thirty360(), Euribor6M(YieldTermStructureHandle(discount_curve)), QuoteHandle(), Period(0, Days), YieldTermStructureHandle(discount_curve)) for rate, tenor in zip(euribor_short['Rate'], euribor_short['Tenor'])] helpers += [SwapRateHelper(QuoteHandle(SimpleQuote(rate / 100)), Period(int(tenor), Years), TARGET(), Annual, Unadjusted, Thirty360(), Euribor6M(YieldTermStructureHandle(discount_curve)), QuoteHandle(), Period(0, Days), YieldTermStructureHandle(discount_curve)) for rate, tenor in zip(euribor_long['Rate'], euribor_long['Tenor'])] # 更换为PiecewiseLinearZero构建Euribor曲线 euribor_curve = PiecewiseLinearZero(0, TARGET(), helpers, Actual365Fixed()) euribor_curve.enableExtrapolation() return discount_curve, euribor_curve discount_curve, euribor_curve = create_ql_curve() forecast_handle = RelinkableYieldTermStructureHandle(euribor_curve) discount_handle = RelinkableYieldTermStructureHandle(discount_curve) index_6M = Euribor6M(forecast_handle) swap_engine = DiscountingSwapEngine(discount_handle) # swaps start_date = Date(23, April, 2014) maturity_date = Date(23, April, 2023) fixed_schedule = Schedule(start_date, maturity_date, Period(1, Years), TARGET(), Unadjusted, Unadjusted, DateGeneration.Forward, False) floating_schedule = Schedule(start_date, maturity_date, Period(6, Months), TARGET(), ModifiedFollowing, ModifiedFollowing, DateGeneration.Forward, True) notional = 115000000 fixed_rate = 0.01727 fixed_leg_daycount = Actual360() float_spread = 0 float_leg_daycount = Actual360() calendar = TARGET() previous = calendar.advance(today, -27, Weeks) dates = [calendar.advance(previous, n, Days) for n in range(150)] rates = [0.005] * 150 for date, rate in zip(dates, rates): index_6M.addFixing(date, rate) swap = VanillaSwap(VanillaSwap.Receiver, notional, fixed_schedule, fixed_rate, Thirty360(), floating_schedule, index_6M, 0.0, Actual360()) swap.setPricingEngine(swap_engine) price = swap.NPV() print(f"Swap NPV: {price}")
内容的提问来源于stack exchange,提问作者Oamriotn
相关产品推荐
相关产品推荐

