I'm trying to build the yield curve simply using Treasury yields one would get by querying the FRED API, but as my code below will show, I'm told on the 1st iteration that there was failure at the 4th live instrument (which would be the 1-Yr Treasury, which makes me think that the issue is either in the call to FixedRateBondHelper() or the schedule we pass to it with MakeSchedule(), and not to DepositRateHelper()) because the root is not bracketed.
I checked for the usual culprits: inconsistent manner of expressing yields, failure to use DepositRateHelper() for sub-annual instruments, I've tried different dates, and I've tried multiple Yield Term Structure functions. No success. I'm using simple Treasury Yields and nothing exotic (no building curves based on swaps that call for different functions), so I'm sure that what I've done wrong can be found if someone runs the same numbers that I have included ready to run in my code below (traceback is below too). Thank you for your time.
import QuantLib as ql, pandas as pd, datetime as dt class YieldCurve(): def __init__(self, yields): """ yields(pandas Series): pandas series in which the values are floats representing the yield and the index is the name of the particular bond, and the "name" attribute is a Pandas Timestamp. Eg. what is output from a call to pandas DataReader: """ #PRELIMINARY WORK self.yields = yields self.date = self.yields.name.date() # BUILD YIELD CURVE ## Format QuantLib Arguments self.valuation_date = ql.Date(self.date.day, self.date.month, self.date.year)#self.date in ql format ql.Settings.instance().evaluationDate = self.valuation_date#Instantiate QuantLib date for the valuation date self.day_count = ql.Actual360() ## Obtain Rate Helpers self.rate_helpers =  ### Get variables either common to both deposit and fixed-rate bond helpers or used for multiple instruments within those helpers for maturity, yield_value in self.yields.items(): yield_value = yield_value / 100.0 quote = ql.SimpleQuote(yield_value) calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)#we use this for an American yield curve as opposed to ql.Target() for Euro-denominated instruments. The argument provided was come upon when providing no argument would throw a TypeError. We saw this argument used in a stack exchange post at https://quant.stackexchange.com/questions/60330/quantlib-python-ql-schedule-getting-end-of-month-dates ### Get deposit rate helpers if maturity.endswith('MO'): # Use deposit rate helpers for Treasury bills maturity_period = ql.Period(int(maturity[3:-2]), ql.Months) compounding_frequency = ql.Simple rate_helper = ql.DepositRateHelper( ql.QuoteHandle(quote),#quote arg maturity_period,#tenor arg 0,#fixingDays arg calendar,#calendar arg ql.ModifiedFollowing,#convention arg. this is the business day convention, not the daycount convention, and ModifiedFollowing is appropriate for the American treasury market False,#endOfMonth arg self.day_count#dayCounter arg. equivalent of day count convention ) self.rate_helpers.append(rate_helper) ### Get fixed-rate bond helpers else:# Use fixed-rate bond helpers for Treasury notes/bonds ### Define variables relevant to all instruments that use FixedRateBondHelper maturity_period = ql.Period(int(maturity[3:]), ql.Years) maturity_date = self.valuation_date + maturity_period print("valuation date: ", self.valuation_date) print("maturity_date: ", maturity_date) compounding_frequency = ql.Semiannual #### Get schedule for instrument schedule = ql.MakeSchedule( self.valuation_date,#effectiveDate param maturity_date,#terminationDate param frequency = compounding_frequency, # Tenor or frequency of the schedule (e.g., semiannual) calendar = calendar,#calendar param convention = ql.ModifiedFollowing,#convention parameter. Business day convention for schedule generation terminalDateConvention = ql.ModifiedFollowing,#terminalDateConvention parameter. Business day convention for adjusting the end date rule = ql.DateGeneration.Backward,#Date generation rule. used to generate the bond schedule by working backward from the maturity date to the valuation date. It determines how the intermediate cash flow dates are calculated between these two dates. This approach ensures that the cash flows are correctly scheduled in reverse order, aligning with the bond's structure. endOfMonth = False#endOfMonth param. determines whether the cash flow dates should be adjusted to the end of the month. Setting it to False means that the cash flow dates should not be adjusted to the end of the month.In the context of the American Treasury market, it is typical to set endOfMonth to False since Treasury bonds typically have cash flow dates that are not adjusted to the end of the month. ) #### Call FixedRateBondHelper() print("yield_value: ", yield_value) rate_helper = ql.FixedRateBondHelper( ql.QuoteHandle(quote), 2, #settlementDays arg. the number of business days between the trade date (when the transaction is executed) and the settlement date (when the transaction is settled and ownership of the securities is transferred). 2 was used in docs and this is appropriate for the AMerican treasury market 100,#faceAmount arg schedule,#schedule param [yield_value],#coupons param self.day_count,#dayCounter param ql.ModifiedFollowing,#paymentConv param. a common choice for adjusting payment dates. It follows the rule that if a payment date falls on a non-business day, it is adjusted to the next valid business day unless that day belongs to a different month. In the latter case, the payment date is adjusted to the previous valid business day. For the American Treasury market, the ql.ModifiedFollowing convention is often used to handle potential adjustments for weekends, holidays, or other non-business days while maintaining consistency with the general market practice. 100, #redemption param. represents the redemption value or par value of the bond. ql.Date(), ## issueDate, set to default value calendar, #paymentCalendar param maturity_period,#exCouponPeriod param calendar, #exCouponCalendar param ql.ModifiedFollowing, #exCouponConvention param False,#exCouponEndOfMonth param False#useCleanPrice param. Whether to use clean price or dirty price (set to False for dirty price) ) self.rate_helpers.append(rate_helper) ## Construct Yield Curve self.yield_curve = ql.PiecewiseLogCubicDiscount(self.valuation_date, self.rate_helpers, self.day_count) sample_yields = pd.Series([5.25, 5.46, 5.50, 5.41, 4.87, 4.49, 4.14, 3.99, 3.85, 4.11, 3.92], index = ['DGS1MO', 'DGS3MO', 'DGS6MO', 'DGS1', 'DGS2', 'DGS3', 'DGS5', 'DGS7', 'DGS10', 'DGS20', 'DGS30']) sample_yields.name = dt.datetime.strptime('2023-07-03', '%Y-%m-%d') yc_obj = YieldCurve(sample_yields)
--------------------------------------------------------------------------- RuntimeError Traceback (most recent call last) Cell In, line 1 ----> 1 yc_obj = YieldCurve(yields) Cell In, line 146, in YieldCurve.__init__(self, yields) 139 self.yield_curve = ql.PiecewiseLogCubicDiscount(self.valuation_date, 140 self.rate_helpers, 141 self.day_count) 145 # PHASE : EXTRACT YIELD CURVE ATTRIBUTES --> 146 self.curve_dates = self.yield_curve.dates() 147 self.curve_rates = [self.yield_curve.zeroRate(date, self.day_count, ql.Continuous).rate() for date in self.curve_dates] File ~\anaconda3\envs\x00\lib\site-packages\QuantLib\QuantLib.py:26649, in PiecewiseLogCubicDiscount.dates(self) 26647 def dates(self): 26648 r"""dates(PiecewiseLogCubicDiscount self) -> DateVector""" > 26649 return _QuantLib.PiecewiseLogCubicDiscount_dates(self) RuntimeError: 1st iteration: failed at 4th alive instrument, pillar July 3rd, 2024, maturity July 3rd, 2024, reference date July 3rd, 2023: root not bracketed: f[0.586591,1.61233] -> [-5.862737e+01,-1.612583e+02] ```