Skip to content
All library documents

How Overnight Index Fixings Relate to Curve Forward Rates

Article Quant Q&A · Author: J D

Summary

The document investigates why a QuantLib overnight index fixing does not exactly match a manually derived one-day forward rate from a yield curve. The author sets up an overnight index with a curve handle and compares its fixing with forward rates calculated from interpolated zero rates, using a stated calendar and day-count convention. The comparison highlights that an index fixing and a curve's forward rate may involve different date conventions or calculation details, even when they appear to refer to the same accrual interval.

The post supplies code and a numerical discrepancy, but no accepted explanation or final replication method. It therefore serves as a debugging question rather than a settled derivation. Results depend on the curve construction, calendar, fixing date, accrual dates, and conventions in use; the document does not establish that continuous compounding alone explains the difference or generalize the observation beyond its example.

Key ideas

  • An overnight index fixing is being compared with a forward rate derived from a yield curve.
  • The example uses zero rates at adjacent dates to calculate a manually implied forward rate.
  • Calendar, fixing-date, accrual-period, and day-count conventions may affect comparisons.
  • The reported mismatch is posed as a question and is not resolved in the document.
  • Curve construction and compounding assumptions limit how broadly the example can be applied.

Tags

Full text
# Why aren't Overnight Rates the same as Forward Rates in Quantlib and how are they calculated?


# Why aren't Overnight Rates the same as Forward Rates in Quantlib and how are they calculated?












I'm trying to manually recalculate the index rate when using the ql.OvernightIndex function in QuantLib.

To do this, I built a simple linear spot curve and passed it to the function via the yield curve handle. I expected the index rates to be calculated as continuously compounded forward rates — from one day to the next — taking into account the specified day count convention.

However, the output doesn't match this expectation when checking my code output.

> Index rate on July 17th, 2025: 5.69077232%

> Manual calculated Forward Rate: 5.69032258%

Can anyone explain how the index rate is actually computed and how I could replicate it manually?

Thanks in advance!

Here is my Code for simulating:

```

evaluation_date = ql.Date(1, 1, 2025)
ql.Settings.instance().evaluationDate = evaluation_date

# Testdate
test_date = ql.Date(17, 7, 2025)

calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)
day_count = ql.Actual360()

#spot_curve = ql.ZeroCurve(list(dates), list(rates), day_count, calendar) #ZeroCurve Term structure based on linear interpolation of zero yields.
yieldcurve_handle = ql.YieldTermStructureHandle(spot_curve)

ONI = ql.OvernightIndex("ONI", 0, ql.USDCurrency(), calendar, day_count, yieldcurve_handle)

# Zero Rate vs. Index ---
zero_rate_direct = spot_curve.zeroRate(test_date, day_count, ql.Continuous).rate()
fixing_date = ONI.fixingDate(test_date)
index_rate = ONI.fixing(test_date)

print(f"\nTest date:                       {test_date}")
print(f"Fixing date from index:          {fixing_date}")
print(f"Direct zero rate:                {zero_rate_direct:.8%}")

print(f"Index rate on {fixing_date}:     {index_rate:.8%}")
print(f"\nZero rate on {ql.Date(16, 7, 2025)}: {spot_curve.zeroRate(ql.Date(16, 7, 2025), day_count, ql.Continuous).rate():.8%} p.a.")
print(f"Zero rate on {ql.Date(17, 7, 2025)}: {spot_curve.zeroRate(ql.Date(17, 7, 2025), day_count, ql.Continuous).rate():.8%} p.a.")
print(f"Zero rate on {ql.Date(18, 7, 2025)}: {spot_curve.zeroRate(ql.Date(18, 7, 2025), day_count, ql.Continuous).rate():.8%} p.a.")

##### Manual Calculation - Forward Rate between 17.07.2025 and 18.07.2025

d1 = ql.Date(17, 7, 2025)
d2 = ql.Date(18, 7, 2025)

#yearfractions
t1 = day_count.yearFraction(evaluation_date, d1)
t2 = day_count.yearFraction(evaluation_date, d2)
#print(197/360)
#print(198/360)

# Interpolated Zero Rates at both dates
r_t1 = spot_curve.zeroRate(d1, day_count, ql.Continuous).rate()
r_t2 = spot_curve.zeroRate(d2, day_count, ql.Continuous).rate()

# manual calculation of Forward Rate
fwd_manual = (r_t2 * t2 - r_t1 * t1) / (t2 - t1)

# Comparison with QuantLib-Method
fwd_quantlib = spot_curve.forwardRate(d1, d2, day_count, ql.Continuous).rate()

#print(f"ZeroRate at {d1}, t1 = {t1:.8f}, r(t1) = {r_t1:.8%}")
#print(f"ZeroRate at {d2}, t2 = {t2:.8f}, r(t2) = {r_t2:.8%}")
print(f"Manual calculated Forward Rate:     {fwd_manual:.8%}")
print(f"Forward Rate from QuantLib-Method:  {fwd_quantlib:.8%}")
```

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.