Skip to content
All library documents

How QuantLib Converts Zero Rates for Curve Storage

Article Quant Q&A · Author: kismsu

Summary

This note describes why the rates returned by QuantLib’s ZeroCurve nodes can differ from the rates supplied to its constructor. When rates are entered with periodic compounding, the stored node values are converted to continuously compounded rates, so they can serve as a common internal representation without retaining the original compounding frequency. With continuous compounding selected at construction, the node values match the supplied rates.

The conversion is illustrated by equating the accumulation factors from periodic and continuous compounding. To retrieve rates under a desired convention, the answer recommends requesting zeroRate with the corresponding compounding and frequency settings. A follow-up question reports small discrepancies at the reference date and other nodes for a separately constructed curve, but the document does not resolve their cause. The explanation therefore clarifies the storage convention while leaving edge-case behavior and the reported discrepancy open.

Key ideas

  • QuantLib stores ZeroCurve node rates as continuously compounded rates with no frequency.
  • Periodic input rates are transformed to preserve their equivalent accumulation factors.
  • Node values matching input rates do not imply that the curve stores the original compounding convention.
  • Request zero rates with the desired compounding and frequency to obtain rates in that convention.
  • The reported reference-date discrepancy in a separate example is not explained.

Tags

Full text
# Quantlib ZeroCurve interpolation


# Quantlib ZeroCurve interpolation












I'd like to check how QuantLib does interpolation on rates if I use ZeroCurve constructor. As it was mentioned here, by using `curve.nodes()` you can get a list of rates, used for interpolation.

So, if I build a curve with spot rates with the following code

```
import QuantLib as ql
todays_date = ql.Date(12, 3, 2020)
spot_dates = [todays_date + ql.Period(i, ql.Years) for i in [0, 1, 2, 3, 4, 5]]
spot_rates = [0.01, 0.02, 0.03, 0.04, 0.05, 0.06]
spot_curve = ql.ZeroCurve(
    spot_dates, spot_rates, 
    ql.SimpleDayCounter(),
    ql.NullCalendar(),
    ql.Linear(),
    ql.Compounded,
    ql.Annual
)
```

I got back

```
spot_curve.nodes()
```

```
((Date(12,3,2020), 0.009950330853148023),
 (Date(12,3,2021), 0.01980262729617973),
 (Date(12,3,2022), 0.02955880224154438),
 (Date(12,3,2023), 0.03922071315328132),
 (Date(12,3,2024), 0.04879016416943205),
 (Date(12,3,2025), 0.05826890812397582))
```

I've realized that the issue is the compounding. By indicating that input rates are Continuously compounded, `nodes()` matches with the input.

```
spot_curve = ql.ZeroCurve(
    spot_dates, spot_rates, 
    ql.SimpleDayCounter(),
    ql.NullCalendar(),
    ql.Linear(), ql.Continuous, ql.Annual
)
spot_curve.nodes()
```

```
((Date(12,3,2020), 0.01),
 (Date(12,3,2021), 0.02),
 (Date(12,3,2022), 0.03),
 (Date(12,3,2023), 0.04),
 (Date(12,3,2024), 0.05),
 (Date(12,3,2025), 0.06))
```

Based on that discovery, the question is: Does QuantLib always interpolates in continues rates (with zero rates constructor)? Is this a convention?

## Answer by mmencke (score 3)

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

The data stored in the object is adjusted such that compounding is `Continuous` and frequency is `NoFrequency`. The C++ source code is available here: zerocurve.hpp. I think that the reason for this is that a `ZeroCurve` object then won't have to store `compounding` and `frequency`.

We can validate the new rates by using the equation $$e^{r_{cont}t}=(1+r_{comp})^t$$

The calculations below gives zero, which verifies the QuantLib transform of the rates from Compounded to Continuous:

```
import math
math.exp(0.009950330853148023*1.0)-pow(1.0+0.01,1.0)
math.exp(0.01980262729617973*2.0)-pow(1.0+0.02,2.0)
```

To obtain the zero rates with the needed compounding and frequency you can use something like

```
for x in spot_dates:
    print(spot_curve.zeroRate(x,
                   ql.SimpleDayCounter(),
                   ql.Compounded,
                   ql.Annual).rate())
```

## Answer by SRqt (score 0)

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

I got the same problem. Also, when I call for the ZeroRate method on the reference date of the curve I get a different rate from the one that I created the curve object with it.

For example:

```
ql.Settings.instance().evaluationDate = ql.Date(8, 12, 2021)

rates = [0.015677430225945563, 0.015677430225945563, 0.011840632376029895]
date = [Date(8,12,2021), Date(3,1,2022), Date(1,2,2022)]
curve = ql.ZeroCurve(dates, rates, ql.Actual360(), ql.NullCalendar(), ql.Linear(), ql.Simple, ql.Annual)
```

```
curve.zeroRate(ql.Date(8, 12, 2021), ql.Actual360(), ql.Simple).rate()
```

I expected the rate 0.015677430225945563, but I got 0.015677094022947813

For the other dates I get the right rate.

But also I have the same problem as the topic creator above. When I call for the curve nodes I get different rates for all dates:

```
curve.nodes()
```

```
((Date(8,12,2021), 0.015677093548145716),
 (Date(3,1,2022), 0.01566856146524625),
 (Date(1,2,2022), 0.011829935508255464))
```

Does anyone have a answer as why this is? Thanks.

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.