# Quantlib-Python: use zero rates to get the originally bootstrapped curve

Let's say I am trying to build a curve using deposits, future and swaps with one of the three Quantlib methods in Python as below:

crv = ql.PiecewiseLogCubicDiscount(2, ql.TARGET(), deposits + futures + swaps, ql.Actual365Fixed())


or

crv = ql.PiecewiseLinearZero(2, ql.TARGET(), deposits + futures + swaps, ql.Actual365Fixed())


or

crv = ql.PiecewiseFlatForward(2, ql.TARGET(), deposits + futures + swaps, ql.Actual365Fixed())


and that then I get the zero rates on a set of key tenors as below:

spotDate = crv.referenceDate()
dates = [ql.TARGET().advance(spotDate, t, ql.Days) for t in keytenors ]
rates = [ crv.zeroRate(t, ql.Continuous).rate() for t in keytenors ]


If I pass these zero rates to get the original crv object in the three cases above, how can I do it? I tried with:

zero_curve = ql.ZeroCurve(dates, rates,  ql.Actual365Fixed())


but the curve I get is not the same as the one I bootstrapped. In general the zero curve I obtain is much less smooth and seems linearly interpolating somehow. How can I get the PiecewiseLogCubicDiscount, PiecewiseLinearZero and PiecewiseFlatForward instead?

To retrieve the original curve, you need to use the same key tenors of the original curve and with the same interpolation. For instance, when you create the original curve as:

crv = ql.PiecewiseLinearZero(2, ql.TARGET(), deposits + futures + swaps, ql.Actual365Fixed())


the curve linearly interpolates zero rates between nodes given by the maturities of the passed deposits, futures and swaps. You can retrieve the set of underlying dates and the corresponding rates by calling crv.nodes(), which returns a sequence of (date, rate) pairs; for instance, if I call it on a curve defined as in this example, I get:

((Date(8,11,2001), 0.038716178576382605),
(Date(15,11,2001), 0.038716178576382605),
(Date(10,12,2001), 0.037654445569665344),
(Date(8,2,2002), 0.03663450512870074),
(Date(8,5,2002), 0.03704480712236303),
(Date(8,8,2002), 0.037185800177110054),
(Date(8,11,2002), 0.03725571728097072),
(Date(10,11,2003), 0.03633800161641973),
(Date(8,11,2004), 0.039086101826569714),
(Date(8,11,2006), 0.04547303923680055),
(Date(8,11,2011), 0.051542294488560084),
(Date(8,11,2016), 0.055797299887186284))


(The evaluation date used in the example is November 6th, 2001).

Since the curve is a PiecewiseLinearZero instance, the rates returned above are zero rates; and if you use them to create an instance of ZeroCurve (which also interpolates linearly)...

dates, rates = zip(*crv.nodes())
crv2 = ql.ZeroCurve(dates, rates, ql.Actual365Fixed())


...you'll get the same curve as the original:

spot = crv.referenceDate()
sample_dates = [ spot + ql.Period(i, ql.Weeks) for i in range(15*52) ]
z1 = [ crv.zeroRate(d, ql.Actual365Fixed(), ql.Continuous).rate() for d in sample_dates ]
z2 = [ crv2.zeroRate(d, ql.Actual365Fixed(), ql.Continuous).rate() for d in sample_dates ]

fig = plt.figure(figsize=(12,6))
ax.plot_date([d.to_date() for d in sample_dates], z1, '.')
ax.plot_date([d.to_date() for d in sample_dates], z2, '-')


The problem is that, if you sample the zero rates at different nodes, you'll get points on the curve; but interpolating between them, you'll get different values.

sample_nodes = [ spot + ql.Period(3*i, ql.Years) for i in range(6) ]
sample_rates = [ crv.zeroRate(d, ql.Actual365Fixed(), ql.Continuous).rate() for d in sample_nodes ]
crv3 = ql.ZeroCurve(sample_nodes, sample_rates, ql.Actual365Fixed())

z3 = [ crv3.zeroRate(d, ql.Actual365Fixed(), ql.Continuous).rate() for d in sample_dates ]

fig = plt.figure(figsize=(12,6))
ax.plot_date([d.to_date() for d in sample_dates], z1, '.')
p, = ax.plot_date([d.to_date() for d in sample_dates], z3, '-')
ax.plot_date([d.to_date() for d in sample_nodes], sample_rates, 'o', markersize=8, color=p.get_color())


In short: you need to use the same nodes and interpolation. You can retrieve the former from the original curve as curve.nodes(), and you'll have to choose a class that provides the latter. For PiecewiseLinearZero, you'll have to use ZeroCurve; for PiecewiseFlatForward, the nodes method will return pairs of dates and instantaneous forward rates, which you can use to create an instance of ForwardCurve.

For PiecewiseLogCubicDiscount, the nodes method will return pairs of dates and discount factors, and you'd have to pass them to a corresponding interpolated discount curve; however, the one currently exported to Python (DiscountCurve) uses log-linear interpolation, and not log-cubic. If you want to use the latter, you'll have to modify QuantLib-SWIG/SWIG/discountcurve.i so that it also exports the desired curve and recompile the wrappers. The same goes if you want to use a different interpolation for zero or forward rates; the corresponding files to edit are zerocurve.i and forwardcurve.i.

• Thanks Luigi. But I have multiple days I want to compare the curve on, say for example because I want to compute the bucket risk on exactly the same key tenors. So for each evaluation date I export the continuously compounded zero rates (as in the rates array in my example). Then another process will use the exported zero rates to rebuild the curve on each day and compute the key rate risk for a swap. This is why I somehow need a unique set of key tenor points to work with. How can I do that?
– opt
Mar 26 '19 at 18:18
• You can perturb key tenors independently of the nodes of the underlying curve. See <youtube.com/watch?v=v4vQNcmYegA>, or the corresponding notebook in the QuantLib Python Cookbook. Mar 26 '19 at 22:04
• If you need further discussion, you can reach out on the QuantLib mailing list. (StackExchange sites tend to frown on comments taking a turn of their own and diverging from the original question...) Mar 26 '19 at 22:07