0
$\begingroup$

Here is my understanding of the process:

  1. Capture price of most recently sold gov security at each tenor of the curve (reference treasuryDirect)
  2. For coupon paying securities, (i.e. tenor>2yr) you must strip the interest gained from coupon from the price and recalc the security
  3. Calculate yield for each tenor in the new curve using zero coupon prices
  4. Interpolate the curve for 360 nodes (representing months up to 30 years)

What am I doing wrong?

Hello, I need help trying to construct a zero coupon curve using treasury spot rates, I am following the cookbook on quantlib but struggling to understand why I am throwing the error:

_QuantLib.YieldTermStructure_zeroRate(self, *args)  RuntimeError: 1st iteration: failed at 4th alive instrument, pillar February 3rd, 2025, maturity February 3rd, 2025, reference date February 2nd, 2024: root not bracketed: f[0.593053,1.59535] -> [-6.243439e+01,-1.681992e+02]

Code:

    import QuantLib as ql
    # Spot rates provided
    spot_rates = [5.49, 5.51, 5.43, 5.42, 5.22, 4.81, 4.36, 4.14, 3.99, 4.02, 4.03, 4.33, 4.22]  # in percent
    
    # Corresponding tenors in months and years for deposits and bonds
    deposit_tenors_months = [1, 3, 6]
    bond_tenors_years = [1, 2, 3, 5, 7, 10, 20, 30]
    
    # Convert annual spot rates to QuantLib QuoteHandle objects and adjust for percentage
    spot_rate_handles = [ql.QuoteHandle(ql.SimpleQuote(rate / 100.0)) for rate in spot_rates]
    
    # Assuming today's date is the settlement date
    settlement_date = ql.Date(2, 2, 2024)
    ql.Settings.instance().evaluationDate = settlement_date
    
    # Define day count and calendar for deposits
    calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)
    day_count_deposit = ql.Actual360()
    
    # Deposit rate helpers
    deposit_helpers = [
     ql.DepositRateHelper(spot_rate_handles[i],
     ql.Period(tenors, ql.Months),
     2,  # fixing days
     calendar,
     ql.ModifiedFollowing,
     False,
     day_count_deposit)
     for i, tenors in enumerate(deposit_tenors_months)
    ]
    
    # Day count for bonds
    day_count_bond = ql.ActualActual(ql.ActualActual.ISDA)
    
    # Bond rate helpers
    bond_helpers = []
    for i, tenors in enumerate(bond_tenors_years, start=3):  # Starting from the 4th element in spot_rates
     maturity_date = settlement_date + ql.Period(tenors, ql.Years)
     schedule = ql.Schedule(settlement_date,
     maturity_date,
     ql.Period(ql.Annual),
     calendar,
     ql.Unadjusted,
     ql.Unadjusted,
     ql.DateGeneration.Backward,
     False)
     bond_helpers.append(
     ql.FixedRateBondHelper(spot_rate_handles[i],
     3,  # settlement days
     100.0,  # face amount
     schedule,
                                   [spot_rates[i] / 100.0],  # coupon rate
     day_count_bond,
     ql.ModifiedFollowing,
     100.0)  # redemption
        )
    
    # Combine deposit and bond helpers
    rate_helpers = deposit_helpers + bond_helpers
    
    # Construct the curve
    curve = ql.PiecewiseLogCubicDiscount(settlement_date,
     rate_helpers,
     ql.ActualActual(ql.ActualActual.ISDA))
    
    # Extract zero rates for months 1-360
    zero_rates = []
    for month in range(1, 361):
     date = settlement_date + ql.Period(month, ql.Months)
     yrs = curve.dayCounter().yearFraction(settlement_date, date)
     zero_rate = curve.zeroRate(yrs, ql.Compounded, ql.Annual).rate()
     zero_rates.append(zero_rate)
    
    # Print some of the zero rates
    print("Zero Rates for the first 12 months:")
    for month, rate in enumerate(zero_rates[:12], start=1):
     print(f"Month {month}: {rate*100:.2f}%")
$\endgroup$
6
  • $\begingroup$ In your code you have 13 spot rates and 11 tenors for instruments. This didnt work for me, I omitted the last 2 spot rates. $\endgroup$
    – Attack68
    Commented Feb 5 at 13:00
  • $\begingroup$ @Attack68 ahh sorry I missed this as I wrote it at 2AM. I did correct the code to ensure that the yields and tenors are the same length but somehow am still getting the same error. Were you able to generate the zero curve after omitting the last 2 spot rates? $\endgroup$
    – dbojanin
    Commented Feb 6 at 8:35
  • $\begingroup$ Yes, in my own software, not in QuantLib, I don't understand the QuantLib error. $\endgroup$
    – Attack68
    Commented Feb 6 at 8:44
  • $\begingroup$ Thanks for editing the error message, it is much more understandable now. (Basically the iteration did not converge). $\endgroup$
    – nbbo2
    Commented Feb 7 at 12:46
  • $\begingroup$ You may wan to change your methodology to reflect better the practical behavior of U.S. treasury debt. Recall that the U.S. treasury seldom taps existing issues, unlike some other countries treasuries, but rather sells at par new issues with maturities close to already outstanding ("off-the-run") issues, but different coupons. The off-the-run treasuries form a curve,. The most recently issued ("on-the-run") treasuries usually trade a little special versus the off-the-run curve because of technical reasons that are unique to each issue and don't really create a curve or a spread with a (cont) $\endgroup$ Commented Feb 7 at 17:18

1 Answer 1

2
$\begingroup$

By executing the following code:

# Construct the curve
curve = ql.PiecewiseLogCubicDiscount(
    settlement_date, deposit_helpers, ql.ActualActual(ql.ActualActual.ISDA)
)

date, rates = zip(*curve.nodes())

I concluded that the deposits did not cause this issue. Thus moving onwards with the FixedRateBondHelpers, it is expecting prices and coupon rates as input. However, those two variables are set equally as of now, causing the error as the solver can not find a solution. If we hypothetically set the following price:

prices= [
    ql.QuoteHandle(ql.SimpleQuote(rate+90)) for rate in spot_rates
]

and in the for loop use:

bond_helpers.append(
    ql.FixedRateBondHelper(
        prices[i],
        3,  # settlement days
        100.0,  # face amount
        schedule,
        [spot_rates[i] / 100.0],  # coupon rate
        day_count_bond,
        ql.ModifiedFollowing,
        100.0,
        )  # redemption
    )

The code will execute and find a solution. Here are a few links that I found useful when investigating FixedRateBondHelper:

  1. Documentation
  2. Example usage of FixedRateBondHelper
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.