Three views of one object
Worked examples
Read the claim, then run it and check the machine agrees. One at a time — nothing here is taken on trust.
import math
import QuantLib as ql
today = ql.Date(9, 7, 2026)
dc = ql.Actual365Fixed()
pillars = [(1, 0.048), (2, 0.045), (5, 0.042)]
dates = [today] + [today + ql.Period(y, ql.Years) for y, _ in pillars]
dfs = [1.0] + [math.exp(-z * dc.yearFraction(today, d)) for (_, z), d in zip(pillars, dates[1:])]
curve = ql.DiscountCurve(dates, dfs, dc)
curve.enableExtrapolation()
for (y, z), d in zip(pillars, dates[1:]):
t = dc.yearFraction(today, d)
print(f"{y}y node: t = {t:.6f} zero = {-math.log(curve.discount(t)) / t:.6%} (quoted {z:.1%})")
z(t) = −ln df(t)/t recovers each quoted pillar to machine precision — the df↔rate conversion from Module 0, now applied to a whole curve. Note the node times: 1.000000, 2.002740, 5.002740. The 2028 leap day is in there, and pretending t = 2 exactly would already cost you in the sixth decimal.
import math
import QuantLib as ql
today = ql.Date(9, 7, 2026)
dc = ql.Actual365Fixed()
dates = [today] + [today + ql.Period(y, ql.Years) for y in (1, 2, 5)]
dfs = [1.0] + [math.exp(-z * dc.yearFraction(today, d)) for z, d in zip((0.048, 0.045, 0.042), dates[1:])]
curve = ql.DiscountCurve(dates, dfs, dc)
t1, t2 = 1.0, dc.yearFraction(today, dates[2])
f12 = (math.log(curve.discount(t1)) - math.log(curve.discount(t2))) / (t2 - t1)
print(f"1y->2y forward = {f12:.4%} (1y zero: 4.8000%)")
growth_direct = 1.0 / curve.discount(t2)
growth_rolled = (1.0 / curve.discount(t1)) * math.exp(f12 * (t2 - t1))
print(f"grow to t2 directly: {growth_direct:.10f}")
print(f"grow to t1, reinvest at the forward: {growth_rolled:.10f}")
The curve quietly quotes 4.2008% for money placed from year one to year two — well below the 4.8% you get for year one itself. The two growth routes match to every printed digit: that equality is the no-arbitrage derivation, verified numerically. An inverted zero curve is exactly this: forwards below spot rates, the market paying you less and less for each additional year of commitment.
import math
import QuantLib as ql
today = ql.Date(9, 7, 2026)
dc = ql.Actual365Fixed()
dates = [today] + [today + ql.Period(y, ql.Years) for y in (1, 2, 5)]
dfs = [1.0] + [math.exp(-z * dc.yearFraction(today, d)) for z, d in zip((0.048, 0.045, 0.042), dates[1:])]
curve = ql.DiscountCurve(dates, dfs, dc)
mine_z = -math.log(curve.discount(2.0)) / 2.0
ql_z = curve.zeroRate(2.0, ql.Continuous).rate()
mine_f = (math.log(curve.discount(1.0)) - math.log(curve.discount(2.0))) / 1.0
ql_f = curve.forwardRate(1.0, 2.0, ql.Continuous).rate()
print(f"zero(2.0): mine {mine_z:.12f} QuantLib {ql_z:.12f}")
print(f"fwd(1,2): mine {mine_f:.12f} QuantLib {ql_f:.12f}")
curve.zeroRate(t, ql.Continuous) and curve.forwardRate(t1, t2,
ql.Continuous) are the library's converters — and they agree with
your two one-liners to ~1e-15. There is nothing inside those methods
but the log formulas you now own. (The ql.Continuous flag matters:
ask for annual compounding and you'll get the same price in a
different dialect.)
Check the concept
One question at a time. Unsure? Revisit the lecture, then answer.
The challenge
Pass the quiz to unlock the challenge — your code will still be waiting here.