使用Actual365Fixed.NoLeap构建OIS曲线时闰年2月28日指数值为0的问题
QuantLib中Estr指数闰年2月28日Fixing值为0的问题
问题现象
使用QuantLib构建Estr指数的OIS曲线时,发现每逢闰年的2月28日,调用index.fixing(ql.Date(28,2,2024))返回值为0,但使用Euribor指数或其他日计数规则时无此问题。
异常代码(Estr+OIS曲线)
import QuantLib as ql YieldTS = ql.RelinkableYieldTermStructureHandle() index = ql.Estr(YieldTS) helpers = [] for p,q in zip(['1M','50Y'], [0.02,0.02]): helperitem = ql.OISRateHelper(0, ql.Period(p), ql.QuoteHandle(ql.SimpleQuote(q)), index) helpers.append(helperitem) dayCounter = ql.Actual365Fixed(ql.Actual365Fixed.NoLeap) curve = ql.PiecewiseKrugerZero(0, index.fixingCalendar(), helpers, dayCounter) YieldTS.linkTo(curve) index.fixing(ql.Date(28,2,2024)) # 闰年2月28日返回0
正常代码(Euribor+Fra曲线)
import QuantLib as ql YieldTS = ql.RelinkableYieldTermStructureHandle() euribor = ql.Euribor(ql.Period('6M'), YieldTS) helpers = [] for p,q in zip([1,5], [0.02,0.02]): helperitem = ql.FraRateHelper( ql.QuoteHandle(ql.SimpleQuote(q)), p, euribor) helpers.append(helperitem) curve = ql.PiecewiseKrugerZero(0, euribor.fixingCalendar(), helpers, dayCounter) YieldTS.linkTo(curve) euribor.fixing(ql.Date(28,2,2024)) # 返回预期值0.02
原因分析
问题出在日计数规则不匹配+闰年处理逻辑冲突:
- Estr指数的官方默认日计数规则是
Actual/360,但你构建曲线时使用了Actual365Fixed(NoLeap)——该规则会忽略闰年,强制每年按365天计算,不承认闰年的2月29日。 - 当计算闰年2月28日的Estr隔夜fixing时,需要计算从2月28日到下一个工作日(2月29日)的天数,但
NoLeap规则不认可2月29日的存在,导致天数计算为0,最终推导的利率为0。 - Euribor例子中正常,是因为Euribor是期限型指数(你用了6M期限),计算的是一段时期的远期利率,单日天数异常的影响被稀释,且FraRateHelper的处理逻辑对这种单日问题不敏感。
解决方法
解决核心是让曲线的日计数规则与指数匹配,或修改闰年处理逻辑:
方法1:匹配Estr的默认日计数规则(Actual/360)
import QuantLib as ql YieldTS = ql.RelinkableYieldTermStructureHandle() # 显式指定Estr日计数为Actual/360(默认值,可省略,此处为明确逻辑) index = ql.Estr(YieldTS, ql.Actual360()) helpers = [] for p,q in zip(['1M','50Y'], [0.02,0.02]): helperitem = ql.OISRateHelper(0, ql.Period(p), ql.QuoteHandle(ql.SimpleQuote(q)), index) helpers.append(helperitem) # 曲线使用与指数一致的Actual/360日计数 dayCounter = ql.Actual360() curve = ql.PiecewiseKrugerZero(0, index.fixingCalendar(), helpers, dayCounter) YieldTS.linkTo(curve) print(index.fixing(ql.Date(28,2,2024))) # 返回预期值0.02
方法2:改用普通Actual365Fixed(考虑闰年)
如果坚持使用Actual365Fixed规则,去掉NoLeap参数即可:
# 改用普通Actual365Fixed(会识别闰年的366天) dayCounter = ql.Actual365Fixed() curve = ql.PiecewiseKrugerZero(0, index.fixingCalendar(), helpers, dayCounter)
内容的提问来源于stack exchange,提问作者vpvp
相关产品推荐
相关产品推荐

