# 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))