I am using Quantlib's FittedBondDiscountCurve in Python 3.7 and setting MaxIterations to 0, and giving a guess_solution, which then turns the routine into an evaluator for the parametric form I choose, according to the documentation.
The problem is that while an optimized spline will allow me to produce discount factors and NPVs for bonds, the evaluated spline will only crash (in pycharm) or return no value (in command line).
Am I calling the evaluated curve wrongly? Should I be treating it differently to the optimized curve?
I include some code below. Some is clearly copied from other YC building examples, but seems to be a minimal amount of code to produce the errors. I get the same problem no matter which parametric form I use.
Thank you in advance.
import numpy as np
import QuantLib as ql
today = ql.Date(8, 2, 2018)
ql.Settings.instance().evaluationDate = today
# Create some bonds
terminationDates = [ql.Date(4, 7, 2044), ql.Date(15, 2, 2028), ql.Date(14, 4, 2023)]
tenors = np.repeat(ql.Period(ql.Semiannual), 3)
calenders = np.repeat(ql.UnitedStates(), 3)
termDateConvs = np.repeat(ql.Following, 3)
genRules = np.repeat(ql.DateGeneration.Backward, 3)
endOfMonths = np.repeat(False, 3)
firstDates = [ql.Date(27, 4, 2012), ql.Date(10, 1, 2018), ql.Date(2, 2, 2018)]
settlementDays = np.repeat(2, 3)
coupons = [0.025, 0.005, 0.0]
cleanPrices = [126.18, 98.18, 99.73]
faceValues = np.repeat(100.0, 3)
dayCounts = np.repeat(ql.ActualActual(), 3)
schedules = []
bonds = []
bondHelpers = []
for j in range(0, 3):
schedules.append(ql.Schedule(firstDates[j], terminationDates[j], tenors[j], calenders[j],
int(termDateConvs[j]), int(termDateConvs[j]), int(genRules[j]),
bool(endOfMonths[j])))
bonds.append(ql.FixedRateBond(int(settlementDays[j]), float(faceValues[j]), schedules[j],
[float(coupons[j])], dayCounts[j]))
bondHelpers.append(ql.BondHelper(ql.QuoteHandle(ql.SimpleQuote(float(cleanPrices[j]))), bonds[j]))
# Create Two yield Curves- one optimized, one with shifted parameters
curveSettlementDays = 2
curveCalendar = ql.UnitedStates()
curveDaycounter = ql.ActualActual()
curveFittingMethod = ql.ExponentialSplinesFitting()
tolerance = 1.0e-5
iterations = 10000
yieldCurveExp = ql.FittedBondDiscountCurve(curveSettlementDays, curveCalendar,\
bondHelpers, curveDaycounter, curveFittingMethod, tolerance, iterations)
res = yieldCurveExp.fitResults()
solution = list(res.solution())
print('optimal solution = ', solution)
# Altered value
list_guess = solution.copy()
list_guess[-1] = list_guess[-1] * 1.01 # small shifts?
list_guess[-2] = list_guess[-2] * 1.01
list_guess[0] = list_guess[0] * 1.01
print('altered solution = ', list_guess)
guess = ql.Array(list_guess)
iterations = 0 # evaluate, don't fit
yieldCurveExp2 = ql.FittedBondDiscountCurve(curveSettlementDays, curveCalendar, bondHelpers,
curveDaycounter, curveFittingMethod, tolerance, iterations, guess)
YieldCurveHandle = ql.YieldTermStructureHandle(yieldCurveExp)
YieldCurveHandle2 = ql.YieldTermStructureHandle(yieldCurveExp2)
res = yieldCurveExp.fitResults()
print(res.solution()) # optimization
res2 = yieldCurveExp2.fitResults()
print(res2.solution()) # = [ ]
ql_date = ql.Date(30,5,2018)
ql_date2 = ql.Date(30,6,2022)
print(yieldCurveExp.discount(ql_date2)) # 1.0040129
print(yieldCurveExp2.discount(ql_date2)) # Crash
# use it for pricing
bondEngine = ql.DiscountingBondEngine(YieldCurveHandle)
bondEngine2 = ql.DiscountingBondEngine(YieldCurveHandle2)
pricing_bond = bonds[0]
pricing_bond.setPricingEngine(bondEngine)
print('Fit model NPV =', pricing_bond.NPV()) # 137.2513
pricing_bond.setPricingEngine(bondEngine2)
print('Adjusted model NPV =', pricing_bond.NPV()) # Crash!