QuantLib Zero Rates: Decimal Inputs, Day Counts, and Compounding
Summary
The document diagnoses unexpected zero rates from a QuantLib yield curve. Its main finding is a units error: rates supplied to the curve are decimal values, so entering values such as 0.43902 represents 43.902%, not 0.43902%. Converting percentage-style inputs to decimals produces the intended rate scale and sensible discounting or forecasting behavior.
It also clarifies how to query rates. The time argument should be computed using the intended day-count convention, and continuous rates should be requested with continuous compounding. For compounded rates, a frequency such as annual or semiannual is needed; combining compounded compounding with a continuous frequency is inconsistent. At time zero, a zero rate is undefined, so QuantLib shifts the evaluation point slightly and the result may not exactly match the input. The examples illustrate the issue and correction, but the appropriate day count and compounding convention still depend on the curve’s specification.
Key ideas
- QuantLib expects rates in decimal notation, so percentage inputs must be scaled accordingly.
- Compute the time to each curve date with the intended day-count convention.
- Use continuous compounding to request continuous zero rates.
- Compounded rates require a valid compounding frequency.
- A zero rate at time zero is undefined, so QuantLib may shift the query point.
Tags
Full text
# Quantlib yield curve - zerorate output differs from expectation
# Quantlib yield curve - zerorate output differs from expectation
I was creating an yield curve using zeroRate, when I read back the value from the created yield curve - it differs from expectation.
```
tod = ql.Date(5,5,2022)
ardates = [tod, tod+ql.Period(1,ql.Weeks), tod+ql.Period(1,ql.Months), tod+ql.Period(3,ql.Months),
tod+ql.Period(6,ql.Months),tod+ql.Period(1,ql.Years),tod+ql.Period(2,ql.Years) ]
arzeros = [0.43902, 0.80713,1.0581, 1.19588,1.64246, 2.2557, 2.72901]
arc1 = ql.ZeroCurve(ardates, arzeros, ql.Actual360(), ql.UnitedStates())
arc2 = ql.CubicZeroCurve(ardates, arzeros, ql.Actual360(), ql.UnitedStates())
```
On trying to retrive the value of zero rate from these yield curves ...
```
print(arc1.zeroRate(0, ql.Compounded, ql.Continuous).rate())
```
0.4932915599104186 ; expected value was ~0.43902
```
print(arc1.zeroRate(1, ql.Compounded, ql.Continuous).rate())
```
4.125908149916458 ; expected value was ~2.2557
```
print(arc1.zeroRate(2, ql.Compounded, ql.Continuous).rate())
```
5.772093785892491; expected value was ~2.72901
Can someone help me on this matter ? I have defined the yield curve - rate for a date. I want to read out the value as is, but that is not happening.
Regards, Rohit
## Answer by Luigi Ballabio (score 2, accepted)
https://quant.stackexchange.com/a/70812
If you want continuous rates, pass `ql.Continuous` as @lampishthing suggested; and also, calculate `t` using the actual/365 day counter as in their answer.
To add more information: if you want compounded rates, you can pass `ql.Compounded`, in which case you do need to pass a third one which is the frequency; so
```
arc1.zeroRate(t, ql.Compounded, ql.Continuous)
```
doesn't make sense, but for instance
```
arc1.zeroRate(t, ql.Compounded, ql.Semiannual)
```
does, and calculates the semiannually compounded rate that corresponds to the continuous rates you passed to the term structure.
The real problem, though, shows if you remove the `.rate()` from your calls and let the library print the full information; for instance, here is the result for your last date:
```
>>> t = ql.Actual360().yearFraction(tod, ardates[-1])
>>> print(arc1.zeroRate(t, ql.Continuous))
272.901000 % Actual/360 continuous compounding
```
The library works with rates in decimal notation, so by passing
```
[0.43902, 0.80713,1.0581, 1.19588,1.64246, 2.2557, 2.72901]
```
you're really passing 43%, 80%, 105% and so on. What you wanted to write was probably
```
arzeros = [0.0043902, 0.0080713, 0.010581, 0.0119588, 0.0164246, 0.022557, 0.0272901]
```
which will give you
```
>>> print(arc1.zeroRate(t, ql.Continuous))
2.729010 % Actual/360 continuous compounding
```
and also reasonable values for the compounded cases:
```
>>> print(arc1.zeroRate(t, ql.Compounded, ql.Annual))
2.766589 % Actual/360 Annual compounding
```
Using the correct notation for input rates will also give you the correct prices when the curve is used for discunting or forecasting; rates of 43% or 272%, of course, would not work.
## Answer by lampishthing (score 2)
https://quant.stackexchange.com/a/70777
The second argument to your ZeroRate function should not be there. The mandatory arguments for the version of zeroRate function you are trying to hit are i) time, ii) compounding (which should be continuous). Therefore you should have:
```
for d, z in zip(ardates, arzeros):
t = ql.Actual360().yearFraction(tod, d)
print(arc1.zeroRate(t, ql.Continuous).rate(), f'expected value was ~{z}')
```
which yields:
```
0.4409131371427133 expected value was ~0.43902
0.8071300000000036 expected value was ~0.80713
1.0581000000000012 expected value was ~1.0581
1.19588 expected value was ~1.19588
1.6424600000000003 expected value was ~1.64246
2.2557 expected value was ~2.2557
2.72901 expected value was ~2.72901
```
Please note that defining a continuously compounded rate at a daycount of zero doesn't make sense so Quantlib moves the point a little, hence why there isn't a perfect match.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.