Heston 校准 quantlib 中的最大曲线时间误差 python
max curve time error in Heston calibration quantlib python
我正在 运行ning 从源 SWIG python 1.16 版本的 QuantLib 编译。
我一直在尝试根据 this example 校准赫斯顿模型。
我目前只使用 QL 校准来测试它,然后再尝试其他校准。
我需要时间相关参数,所以我使用 PiecewiseTimeDependentHestonModel
.
这是我的代码的相关部分。
辅助函数:
def tenor2date(s, base_date=None,ql=False):
# returns a date from a tenor and a base date
if base_date is None:
base_date = datetime.today()
num = float(s[:-1])
period = s[-1].upper()
if period == "Y":
return_date = base_date + relativedelta(years=num)
elif period == "M":
return_date = base_date + relativedelta(months=num)
elif period == "W":
return_date = base_date + relativedelta(weeks=num)
elif period == "D":
return_date = base_date + relativedelta(days=num)
else:
return_date = base_date
if ql:
return Date(return_date.strftime("%F"),"yyyy-mm-dd")
else:
return return_date
def setup_model(yield_ts, dividend_ts, spot, times,init_condition=(0.02, 0.2, 0.5, 0.1, 0.01)):
theta, kappa, sigma, rho, v0 = init_condition
model = ql.PiecewiseTimeDependentHestonModel(yield_ts, dividend_ts, ql.QuoteHandle(ql.SimpleQuote(spot)), v0, ql.Parameter(), ql.Parameter(),
ql.Parameter(), ql.Parameter(), ql.TimeGrid(times))
engine = ql.AnalyticPTDHestonEngine(model)
return model, engine
def setup_helpers(engine, vol_surface, ref_date, spot, yield_ts, dividend_ts):
heston_helpers = []
grid_data = []
for tenor in vol_surface:
expiry_date = tenor2date(tenor, datetime(ref_date.year(), ref_date.month(), ref_date.dayOfMonth()), True)
t = (expiry_date - ref_date)
print(f"{tenor} : {t / 365}")
p = ql.Period(t, ql.Days)
for strike, vol in zip(vol_surface[tenor]["strikes"], vol_surface[tenor]["volatilities"]):
print((strike, vol))
helper = ql.HestonModelHelper(p, calendar, spot, strike, ql.QuoteHandle(ql.SimpleQuote(vol / 100)), yield_ts, dividend_ts)
helper.setPricingEngine(engine)
heston_helpers.append(helper)
grid_data.append((expiry_date, strike))
return heston_helpers, grid_data
市场数据:
vol_surface = {'12M': {'strikes': [1.0030154025220293, 0.9840808634190958, 0.9589657270688433, 0.9408279805370683, 0.9174122318462831, 0.8963792435025802, 0.8787138822765832, 0.8538712672800733, 0.8355036501980958], 'volatilities': [6.7175, 6.5, 6.24375, 6.145, 6.195, 6.425, 6.72125, 7.21, 7.5625], 'forward': 0.919323}, '1M': {'strikes': [0.9369864196692815, 0.9324482223892986, 0.9261255003380027, 0.9213195223581382, 0.9150244003650484, 0.9088253068972495, 0.9038936313900919, 0.897245676067657, 0.8924388848562849], 'volatilities': [6.3475, 6.23375, 6.1075, 6.06, 6.09, 6.215, 6.3725, 6.63125, 6.8225], 'forward': 0.915169}, '1W': {'strikes': [0.9258809998009043, 0.9236526412979602, 0.920487656155217, 0.9180490618315417, 0.9148370595017086, 0.9116231311263782, 0.9090950947170667, 0.9057357691404444, 0.9033397443834199], 'volatilities': [6.7175, 6.63375, 6.53625, 6.5025, 6.53, 6.6425, 6.77875, 6.99625, 7.1525], 'forward': 0.914875}, '2M': {'strikes': [0.9456173410343232, 0.9392447942175677, 0.9304717860942596, 0.9238709412876663, 0.9152350197527926, 0.9068086964842931, 0.9000335970840222, 0.8908167643473346, 0.884110721680849], 'volatilities': [6.1575, 6.02625, 5.8825, 5.8325, 5.87, 6.0175, 6.1975, 6.48875, 6.7025], 'forward': 0.915506}, '3M': {'strikes': [0.9533543407827232, 0.945357456067501, 0.9343646071178692, 0.9261489737826977, 0.9154251386183144, 0.9050707394248945, 0.8966770979707913, 0.8851907303568785, 0.876803402158318], 'volatilities': [6.23, 6.09125, 5.93, 5.8725, 5.915, 6.0775, 6.28, 6.60375, 6.84], 'forward': 0.915841}, '4M': {'strikes': [0.9603950279333742, 0.9509237742916833, 0.9379657828957041, 0.928295643018581, 0.9156834006905108, 0.9036539552069216, 0.8938804229269658, 0.8804999196762403, 0.870730837142799], 'volatilities': [6.3175, 6.17125, 6.005, 5.94375, 5.985, 6.15125, 6.36, 6.69375, 6.9375], 'forward': 0.916255}, '6M': {'strikes': [0.9719887962018352, 0.9599837798239937, 0.943700651576822, 0.9316544554849711, 0.9159768970939797, 0.9013018796367052, 0.8892904835162911, 0.8727031923006017, 0.8605425787295339], 'volatilities': [6.3925, 6.22875, 6.04125, 5.9725, 6.01, 6.1875, 6.41375, 6.78625, 7.0575], 'forward': 0.916851}, '9M': {'strikes': [0.9879332225745909, 0.9724112749400833, 0.951642771321364, 0.936450663789222, 0.9167103888580063, 0.8985852649047051, 0.8835274087791912, 0.8625837214139542, 0.8472311260811375], 'volatilities': [6.54, 6.34875, 6.1325, 6.055, 6.11, 6.32, 6.5875, 7.01625, 7.32], 'forward': 0.918086}}
spotDates = [ql.Date(1,7,2019), ql.Date(8,7,2019), ql.Date(1,8,2019), ql.Date(1,9,2019), ql.Date(1,10,2019), ql.Date(1,11,2019), ql.Date(1,1,2020), ql.Date(1,4,2020), ql.Date(1,7,2020)]
spotRates = [0.9148, 0.914875, 0.915169, 0.915506, 0.915841, 0.916255, 0.916851, 0.918086, 0.919323]
udl_value = 0.9148
todaysDate = ql.Date("2019-07-01","yyyy-mm-dd")
settlementDate = ql.Date("2019-07-03","yyyy-mm-dd")
和脚本本身:
ql.Settings.instance().evaluationDate = todaysDate
dayCounter = ql.Actual365Fixed()
interpolation = ql.Linear()
compounding = ql.Compounded
compoundingFrequency = ql.Annual
times = [(x - spotDates[0]) / 365 for x in spotDates][1:]
discountFactors = [-log(x / spotRates[0]) / (times[i]) for i, x in enumerate(spotRates[1:])]
fwdCurve = ql.ZeroCurve(spotDates, [0] + discountFactors, dayCounter, calendar, interpolation, compounding, compoundingFrequency)
fwdCurveHandle = ql.YieldTermStructureHandle(fwdCurve)
dividendCurveHandle = ql.YieldTermStructureHandle(ql.FlatForward(settlementDate, 0, dayCounter))
hestonModel, hestonEngine = setup_model(fwdCurveHandle, dividendCurveHandle, udl_value, times)
heston_helpers, grid_data = setup_helpers(hestonEngine, vol_surface, todaysDate, udl_value, fwdCurveHandle, dividendCurveHandle)
lm = ql.LevenbergMarquardt(1e-8, 1e-8, 1e-8)
hestonModel.calibrate(heston_helpers, lm, ql.EndCriteria(500, 300, 1.0e-8, 1.0e-8, 1.0e-8))
当我 运行 最后一行时,我收到以下错误消息:
RuntimeError: time (1.42466) is past max curve time (1.00274)
我不明白它如何尝试为超过 1 年的东西定价,因为辅助曲线和远期曲线都是在同一组日期上定义的。
如果对某人有帮助,请在此处发布我从 quantlb 邮件中得到的答案:
以天为单位指定到期日
t = (expiry_date - ref_date)
print(f"{tenor} : {t / 365}")
p = ql.Period(t, ql.Days)
这里可能会产生违反直觉的效果,因为使用了指定的日历
来计算真正的到期日。如果日历是例如ql.UnitedStates
然后这考虑了周末和假期,
ql.UnitedStates().advance(ql.Date(1,1,2019),ql.Period(365, ql.Days))
=> Date(12,6,2020)
而
ql.NullCalendar().advance(ql.Date(1,1,2019),ql.Period(365, ql.Days))
=> Date(1,1,2020)
因此我猜利率曲线不够长,抛出了
错误信息。
所以解决方法是确保使用 ql.NullCalendar()
across。
我正在 运行ning 从源 SWIG python 1.16 版本的 QuantLib 编译。
我一直在尝试根据 this example 校准赫斯顿模型。
我目前只使用 QL 校准来测试它,然后再尝试其他校准。
我需要时间相关参数,所以我使用 PiecewiseTimeDependentHestonModel
.
这是我的代码的相关部分。
辅助函数:
def tenor2date(s, base_date=None,ql=False):
# returns a date from a tenor and a base date
if base_date is None:
base_date = datetime.today()
num = float(s[:-1])
period = s[-1].upper()
if period == "Y":
return_date = base_date + relativedelta(years=num)
elif period == "M":
return_date = base_date + relativedelta(months=num)
elif period == "W":
return_date = base_date + relativedelta(weeks=num)
elif period == "D":
return_date = base_date + relativedelta(days=num)
else:
return_date = base_date
if ql:
return Date(return_date.strftime("%F"),"yyyy-mm-dd")
else:
return return_date
def setup_model(yield_ts, dividend_ts, spot, times,init_condition=(0.02, 0.2, 0.5, 0.1, 0.01)):
theta, kappa, sigma, rho, v0 = init_condition
model = ql.PiecewiseTimeDependentHestonModel(yield_ts, dividend_ts, ql.QuoteHandle(ql.SimpleQuote(spot)), v0, ql.Parameter(), ql.Parameter(),
ql.Parameter(), ql.Parameter(), ql.TimeGrid(times))
engine = ql.AnalyticPTDHestonEngine(model)
return model, engine
def setup_helpers(engine, vol_surface, ref_date, spot, yield_ts, dividend_ts):
heston_helpers = []
grid_data = []
for tenor in vol_surface:
expiry_date = tenor2date(tenor, datetime(ref_date.year(), ref_date.month(), ref_date.dayOfMonth()), True)
t = (expiry_date - ref_date)
print(f"{tenor} : {t / 365}")
p = ql.Period(t, ql.Days)
for strike, vol in zip(vol_surface[tenor]["strikes"], vol_surface[tenor]["volatilities"]):
print((strike, vol))
helper = ql.HestonModelHelper(p, calendar, spot, strike, ql.QuoteHandle(ql.SimpleQuote(vol / 100)), yield_ts, dividend_ts)
helper.setPricingEngine(engine)
heston_helpers.append(helper)
grid_data.append((expiry_date, strike))
return heston_helpers, grid_data
市场数据:
vol_surface = {'12M': {'strikes': [1.0030154025220293, 0.9840808634190958, 0.9589657270688433, 0.9408279805370683, 0.9174122318462831, 0.8963792435025802, 0.8787138822765832, 0.8538712672800733, 0.8355036501980958], 'volatilities': [6.7175, 6.5, 6.24375, 6.145, 6.195, 6.425, 6.72125, 7.21, 7.5625], 'forward': 0.919323}, '1M': {'strikes': [0.9369864196692815, 0.9324482223892986, 0.9261255003380027, 0.9213195223581382, 0.9150244003650484, 0.9088253068972495, 0.9038936313900919, 0.897245676067657, 0.8924388848562849], 'volatilities': [6.3475, 6.23375, 6.1075, 6.06, 6.09, 6.215, 6.3725, 6.63125, 6.8225], 'forward': 0.915169}, '1W': {'strikes': [0.9258809998009043, 0.9236526412979602, 0.920487656155217, 0.9180490618315417, 0.9148370595017086, 0.9116231311263782, 0.9090950947170667, 0.9057357691404444, 0.9033397443834199], 'volatilities': [6.7175, 6.63375, 6.53625, 6.5025, 6.53, 6.6425, 6.77875, 6.99625, 7.1525], 'forward': 0.914875}, '2M': {'strikes': [0.9456173410343232, 0.9392447942175677, 0.9304717860942596, 0.9238709412876663, 0.9152350197527926, 0.9068086964842931, 0.9000335970840222, 0.8908167643473346, 0.884110721680849], 'volatilities': [6.1575, 6.02625, 5.8825, 5.8325, 5.87, 6.0175, 6.1975, 6.48875, 6.7025], 'forward': 0.915506}, '3M': {'strikes': [0.9533543407827232, 0.945357456067501, 0.9343646071178692, 0.9261489737826977, 0.9154251386183144, 0.9050707394248945, 0.8966770979707913, 0.8851907303568785, 0.876803402158318], 'volatilities': [6.23, 6.09125, 5.93, 5.8725, 5.915, 6.0775, 6.28, 6.60375, 6.84], 'forward': 0.915841}, '4M': {'strikes': [0.9603950279333742, 0.9509237742916833, 0.9379657828957041, 0.928295643018581, 0.9156834006905108, 0.9036539552069216, 0.8938804229269658, 0.8804999196762403, 0.870730837142799], 'volatilities': [6.3175, 6.17125, 6.005, 5.94375, 5.985, 6.15125, 6.36, 6.69375, 6.9375], 'forward': 0.916255}, '6M': {'strikes': [0.9719887962018352, 0.9599837798239937, 0.943700651576822, 0.9316544554849711, 0.9159768970939797, 0.9013018796367052, 0.8892904835162911, 0.8727031923006017, 0.8605425787295339], 'volatilities': [6.3925, 6.22875, 6.04125, 5.9725, 6.01, 6.1875, 6.41375, 6.78625, 7.0575], 'forward': 0.916851}, '9M': {'strikes': [0.9879332225745909, 0.9724112749400833, 0.951642771321364, 0.936450663789222, 0.9167103888580063, 0.8985852649047051, 0.8835274087791912, 0.8625837214139542, 0.8472311260811375], 'volatilities': [6.54, 6.34875, 6.1325, 6.055, 6.11, 6.32, 6.5875, 7.01625, 7.32], 'forward': 0.918086}}
spotDates = [ql.Date(1,7,2019), ql.Date(8,7,2019), ql.Date(1,8,2019), ql.Date(1,9,2019), ql.Date(1,10,2019), ql.Date(1,11,2019), ql.Date(1,1,2020), ql.Date(1,4,2020), ql.Date(1,7,2020)]
spotRates = [0.9148, 0.914875, 0.915169, 0.915506, 0.915841, 0.916255, 0.916851, 0.918086, 0.919323]
udl_value = 0.9148
todaysDate = ql.Date("2019-07-01","yyyy-mm-dd")
settlementDate = ql.Date("2019-07-03","yyyy-mm-dd")
和脚本本身:
ql.Settings.instance().evaluationDate = todaysDate
dayCounter = ql.Actual365Fixed()
interpolation = ql.Linear()
compounding = ql.Compounded
compoundingFrequency = ql.Annual
times = [(x - spotDates[0]) / 365 for x in spotDates][1:]
discountFactors = [-log(x / spotRates[0]) / (times[i]) for i, x in enumerate(spotRates[1:])]
fwdCurve = ql.ZeroCurve(spotDates, [0] + discountFactors, dayCounter, calendar, interpolation, compounding, compoundingFrequency)
fwdCurveHandle = ql.YieldTermStructureHandle(fwdCurve)
dividendCurveHandle = ql.YieldTermStructureHandle(ql.FlatForward(settlementDate, 0, dayCounter))
hestonModel, hestonEngine = setup_model(fwdCurveHandle, dividendCurveHandle, udl_value, times)
heston_helpers, grid_data = setup_helpers(hestonEngine, vol_surface, todaysDate, udl_value, fwdCurveHandle, dividendCurveHandle)
lm = ql.LevenbergMarquardt(1e-8, 1e-8, 1e-8)
hestonModel.calibrate(heston_helpers, lm, ql.EndCriteria(500, 300, 1.0e-8, 1.0e-8, 1.0e-8))
当我 运行 最后一行时,我收到以下错误消息:
RuntimeError: time (1.42466) is past max curve time (1.00274)
我不明白它如何尝试为超过 1 年的东西定价,因为辅助曲线和远期曲线都是在同一组日期上定义的。
如果对某人有帮助,请在此处发布我从 quantlb 邮件中得到的答案:
以天为单位指定到期日
t = (expiry_date - ref_date)
print(f"{tenor} : {t / 365}")
p = ql.Period(t, ql.Days)
这里可能会产生违反直觉的效果,因为使用了指定的日历 来计算真正的到期日。如果日历是例如ql.UnitedStates 然后这考虑了周末和假期,
ql.UnitedStates().advance(ql.Date(1,1,2019),ql.Period(365, ql.Days))
=> Date(12,6,2020)
而
ql.NullCalendar().advance(ql.Date(1,1,2019),ql.Period(365, ql.Days))
=> Date(1,1,2020)
因此我猜利率曲线不够长,抛出了 错误信息。
所以解决方法是确保使用 ql.NullCalendar()
across。