Guides · curves
For a continuously compounded zero rate z and a maturity of t years: DF(t) = e−z·t, and inversely z = −ln(DF)/t. A discount factor is the price today of 1 unit paid at t; the zero rate is the same information restated as an annualized rate. One line of math converts either way.
Take a real pillar from a bootstrapped EUR curve (valuation date 2026-07-01): at 1Y the discount factor is 0.967570 and the zero rate 3.2968%. Both directions round-trip:
import math
z, t = 0.032968, 1.0 # 1Y zero, continuously compounded
df = math.exp(-z * t) # 0.9675695 -> DF 0.967570 (6 dp)
z_back = -math.log(df) / t # 0.0329680 -> recovers z exactly
df_market = 0.967570
print(-math.log(df_market) / t) # 0.0329675 -> 3.2968% (6 dp)
With the API's full-precision zero, exp(−0.03296776 × 1.0) = 0.9675698 — matching the reported discount factor digit for digit. DF and zero rate are the same fact in two forms: DF for pricing (multiply cashflows), zero rate for intuition (compare levels across maturities).
You rarely want one conversion; you want the whole curve, consistently. POST /v1/curves/bootstrap builds the curve from market quotes and returns discount factor and zero rate at every pillar, already consistent:
curl -s https://quantbox.dev/v1/curves/bootstrap \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"valuation_date": "2026-07-01",
"deposits": [{"tenor": "3M", "rate": 0.031},
{"tenor": "6M", "rate": 0.032}],
"swaps": [{"tenor": "2Y", "rate": 0.034},
{"tenor": "5Y", "rate": 0.036},
{"tenor": "10Y", "rate": 0.0375}],
"output_tenors": ["1Y", "2Y", "5Y", "10Y"]}'
{
"valuation_date": "2026-07-01",
"points": [
{"tenor": "1Y", "date": "2027-07-01", "years": 1.0,
"discount_factor": 0.967570, "zero_rate": 0.032968},
{"tenor": "2Y", "date": "2028-07-03", "years": 2.008219,
"discount_factor": 0.935148, "zero_rate": 0.033388},
{"tenor": "5Y", "date": "2031-07-01", "years": 5.002740,
"discount_factor": 0.837636, "zero_rate": 0.035415},
{"tenor": "10Y", "date": "2036-07-01", "years": 10.008219,
"discount_factor": 0.690776, "zero_rate": 0.036964}
],
"conventions": {
"deposits": "act/360, ModifiedFollowing, TARGET, 2 fixing days",
"swaps": "fixed leg annual 30/360 vs Euribor6M, TARGET",
"zero_rates": "continuously compounded, act/365",
"interpolation": "log-linear on discount factors"
}
}
Runs on the free tier — 500 calls/month, no credit card. Get a key. How the bootstrap itself works: bootstrap a yield curve from deposits and swaps.
import requests
r = requests.post("https://quantbox.dev/v1/curves/bootstrap",
headers={"X-API-Key": KEY},
json={"valuation_date": "2026-07-01",
"deposits": [{"tenor": "3M", "rate": 0.031},
{"tenor": "6M", "rate": 0.032}],
"swaps": [{"tenor": "2Y", "rate": 0.034},
{"tenor": "5Y", "rate": 0.036},
{"tenor": "10Y", "rate": 0.0375}],
"output_tenors": ["1Y", "2Y", "5Y", "10Y"]})
points = r.json()["points"]
Every row satisfies DF = exp(−z·t) to machine precision — t being the act/365 year fraction the API reports, not the nominal tenor:
| Tenor | t (years, act/365) | Discount factor | Zero rate | exp(−z·t) |
|---|---|---|---|---|
| 1Y | 1.000000 | 0.967570 | 3.2968% | 0.967570 ✓ |
| 2Y | 2.008219 | 0.935148 | 3.3388% | 0.935148 ✓ |
| 5Y | 5.002740 | 0.837636 | 3.5415% | 0.837636 ✓ |
| 10Y | 10.008219 | 0.690776 | 3.6964% | 0.690776 ✓ |
Why is the 2Y year fraction 2.008219 and not 2.0? The pillar date rolled from Saturday 2028-07-01 to Monday 2028-07-03 (ModifiedFollowing, TARGET), and 733/365 = 2.008219. Use the reported years, never the nominal tenor, when converting by hand.
One caveat on precision: the ✓ checks use the API's full-precision values. Recomputing from the 6-decimal figures printed here, the last digit drifts at long maturities — rounding of z is amplified by t, so at 10Y you get 0.690773 instead of 0.690776. Keep full precision in pipelines; round only for display.
Annual compounding uses a different formula. DF = (1 + r)−t. Treating the 3.2968% continuous zero at 1Y as annually compounded gives DF = 1/1.032968 = 0.968084 instead of 0.967570 — a 5.1 bp price error from a units mistake, and it grows with maturity.
Convert rates before mixing. Annual to continuous: z = ln(1 + rannual); continuous to annual: rannual = ez − 1. A 4% annual yield is a 3.9221% continuous zero (ln 1.04 = 0.039221).
Day count sets t. These zeros are act/365; the same date grid under act/360 gives different year fractions and therefore different rates for identical discount factors. The conventions block in every response states both, so the numbers stay auditable.
Discount factors do the pricing: a bond is just Σ cashflow × DF(t). Zero rates feed comparisons, charts and inputs — including the zero_curve field of POST /v1/bonds/price, which expects exactly the continuously compounded act/365 zeros this endpoint outputs. The two endpoints are convention-matched by design; see also bond duration, convexity and BPV for what comes out the other side.
Convert a whole curve in one call. Key in seconds, 500 free calls a month, full request schema in the docs.
Get a free API keyNo credit card · schemas and error codes in /docs
Related: Bootstrap a yield curve from deposits and swaps · Price a bond on a bootstrapped zero curve · Bond duration, convexity and BPV over HTTP