Skip to content
All library documents

Treasury Curve Pricing: Bill Conventions, Settlement, and Curve Choice

Article Quant Q&A · Author: first_question

Summary

The document addresses discrepancies when using on-the-run Treasury yields and coupon-strip yields to build discount curves and price a Treasury bond. The response identifies the coupon-strip curve in the example as likely erroneous because its yields sit far above the on-the-run curve. It also explains that a Treasury bill modeled as a fixed-rate bond can still be priced from discount factors, but that this representation does not make all quoted rate measures equivalent.

Bill settlement timing matters: bills and bonds settle after the trade date, so directly equating a curve discount factor with a bill price can mislead. Discount rate, simple rate, and yield to maturity use different conventions and can produce different figures for the same instrument. Finally, a bond repriced from the same calibrated curve and market price will have a zero Z-spread by construction. That result confirms internal repricing, not independent evidence that the curve is generally accurate. The discussion offers a diagnosis of one setup, not a universal curve-building prescription.

Key ideas

  • A coupon-strip curve far above the on-the-run curve may indicate faulty input or construction.
  • Bills can be priced from discount factors even when represented with a fixed-rate bond model.
  • Settlement conventions affect comparisons between bill prices and curve discount factors.
  • Bill discount rates, simple rates, and yields to maturity are distinct measures.
  • A zero Z-spread against the price used to calibrate a curve is expected and does not independently validate it.

Tags

Full text
# Pricing a bond using different curve, otr vs c-strip


# Pricing a bond using different curve, otr vs c-strip












I am trying to train building curve and price different bonds. Yet I get different results depending on the curve I use. All the data I am using is data observed on the 2024-9-17.

My goal is quite simple I want to price the bond with ISIN :US91282CLJ89 using an OTR curve and using the Coupon Strip curve.

First on the 2024-9-17 this is what my two curves look like (blue line is the YTM of the OTR and green is YTM of C-strip) [![enter image description here][1]][1]

Fo me it's a bit weird to see such a difference for the C-strip curve. The C-Strip curve yields is so much above the on-the-run curve.

Then using rateslib I define my on-the-run bonds:

```
bond = [FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2024,10,15),convention='ActActISMA',frequency='S', fixed_rate=0.0,curves='curve'), FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2024, 11, 12), convention='ActActISMA',frequency='S', fixed_rate=0.0,curves='curve'),
    FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2024, 12, 19),convention='ActActISMA',frequency='S', fixed_rate=0.0,curves='curve'), FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2025, 3, 20), convention='ActActISMA',frequency='S', fixed_rate=0.0,curves='curve'),
    FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2025, 9, 4),convention='ActActISMA',frequency='S', fixed_rate=0.0,curves='curve'), FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2026, 8, 31),convention='ActActISMA',frequency='S', fixed_rate=3.75,curves='curve'),
    FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2027, 9, 15), convention='ActActISMA',frequency='S', fixed_rate=3.375,curves='curve'), FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2029, 8, 31),convention='ActActISMA',frequency='S', fixed_rate=3.625,curves='curve'),
    FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2031, 8, 31),convention='ActActISMA',frequency='S', fixed_rate=3.75,curves='curve'), FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2034,8,15),convention='ActActISMA',frequency='S', fixed_rate=3.875,curves='curve'),
    FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2044,8,15),convention='ActActISMA',frequency='S', fixed_rate=4.125,curves='curve'), FixedRateBond(effective=datetime(2024, 9, 17),termination=datetime(2054, 8, 15),convention='ActActISMA',frequency='S', fixed_rate=4.25,curves='curve')]
clean_price = [99.638313, 99.26323, 98.795375, 97.776677, 96.264532, 100.294922, 99.789062, 100.871094, 101.371094, 101.929688, 101.390625, 105.179688]

curve_nodes = {b.kwargs['termination']: 1.0 for b in bond}
curve_nodes[datetime(2024, 9, 17)] = 1.0
curve_nodes = dict(sorted(curve_nodes.items()))

curve = Curve(
    nodes= curve_nodes,
    id="curve",
    interpolation='log_linear'
)

solver = Solver(
    curves=[curve],
    instruments=bond,
    s=clean_price,
    weights=[1.] * len(bond)
)
```

First thing I found weird here is that I get a discount factor of: 0.996249 for the 2024-10-15 ( so at the maturity of the first t-bill).

So the first T-Bill YTM should be:

```
(1-0.996249) / (0.996249 * 28/366) * 100 = 4.921553604714073
```

But doing:

```
bond[0].rate(curves=curve, metric='ytm')
```

I get a different result of: 4.862566

Why is that?

Otherwise now that I have my discount curve based on the on-the-run curve I can price the bond I want which is the following bond:

```
bond1 = FixedRateBond(effective=datetime(2024, 9, 17), termination=datetime(2031,8,31),convention='ActActISMA',frequency='S', fixed_rate=3.75)
```

Then doing

```
bond1.rate(curves=curve, metric='ytm')
```

where I get: 3.526054 with a Z-spread of:

```
bond1.oaspread(curves=curve, price=101.371094) # = 1.0115364265816287e-08
```

which I guess means this curve is not too bad for pricing since the Z-spread is quite small?

Now If I do exactly the same thing but instead I use the Coupon-Strip curve instead of the OTR curve I get the following resut:

```
bond1.rate(curves=curve_strip, metric='ytm') # =  5.475895
```

which is completely off from what I get using the OTR curve.

Any ideas of why? Is this a good way to price a bond to use the OTR curve and doing this logic, or an other method should be used?

Thanks

## Answer by Attack68 (score 1, accepted)

https://quant.stackexchange.com/a/80660

There is a lot going on here. Your c-strip curve is hundreds of basis points above the OTR curve (which looks accurate by the way). It must be erroneous.

Secondly you have modelled a `Bill` as a `FixedRateBond`. It doesn't matter considering the price for a remaining cashflow is derived from the curve in the same way (just using discount factors).

Thirdly, bills and bonds settle T+1, so a direct comparison of the discount factor of the curve and equating that with a price of a bill is inaccurate.

Fourthly 'rates' on Bills come in different forms.

```
bill = Bill(effective=dt(2024, 9, 10), termination=dt(2024, 10, 15), spec="us_gbb", curves="curve")

bill.rate(solver=solver, metric="price")
# 99.638313

bill.rate(solver=solver, metric="discount_rate")
# 4.822493

bill.rate(solver=solver, metric="simple_rate")
# 4.839999

bill.ytm(solver=solver, metric="ytm")  # this is a comparison with a single period FixedRateBond of one whole period.
# 4.972566
```

YTM calculations are not the same as discount rate or simple rate calculations.

Fifth, the price of the off the run priced from the curve is 101.371094.

```
bond1 = FixedRateBond(effective=datetime(2024, 9, 17), termination=datetime(2031,8,31),convention='ActActISMA',frequency='S', fixed_rate=3.75, curves="curve")
bond1.rate(solver=solver, metric="clean_price")
# 101.371094
```

This will obviously return a Z-spread of zero for that same price because the curve does not need to shift by any amount to re-price it correctly.

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.