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

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.

• Thanks Luigi :) May 10, 2022 at 5:52

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.

• Thank you lampishthing , the second argument was not needed. YieldTermStructure::zeroRate(Time,Compounding) const - Regards, Rohit May 8, 2022 at 4:42