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 = fig.add_subplot(1,1,1)
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 = fig.add_subplot(1,1,1)
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
.