Fit fair constant-maturity indexes from term structures and validate them against ANBIMA IDkA
Project description
pykdi
Python Constant Duration Index is a quantitative toolkit for fitting term structures and transforming them into fair, daily constant-maturity return indexes at any requested maturity.
pykdi is not limited to the conventional vertices published by an index provider. Given a daily
zero-curve parameter history, it can construct on-demand synthetic indexes such as 6 months,
18 months, 30 months, 7 years, or an entire dense maturity grid. These indexes provide consistent
market-value histories for research, relative-value analysis, portfolio risk, historical stress,
factor construction, and liability-aware investment analysis.
The implementation is empirically checked against the official ANBIMA IDkA return methodology. The close adherence across published PRE and IPCA maturities demonstrates that the same transforms can be used confidently between the conventional vertices.
What Is A Fair Constant-Maturity Index?
A zero curve provides the fair annualized rate for a cash flow at each maturity. A constant-maturity index asks a different question:
What would the daily marked-to-market return have been if capital were continually invested at the same maturity vertex of that fair curve?
For a requested maturity of n business days, the position is initiated using yesterday's
n-day zero rate. One business day later it has n-1 days remaining and is marked using today's
n-1-day rate. Proceeds are conceptually reinvested at today's n-day vertex, keeping the index's
target maturity constant.
n / basis
(1 + y[t-1, n])
G[t] = ---------------------------------
(n - 1) / basis
(1 + y[t, n-1])
This is a fair synthetic zero-coupon return, not the return of a specific traded security. It isolates curve carry and repricing without bond selection, coupon schedules, liquidity premiums, transaction costs, or rebalancing constraints.
Why Custom Maturities Matter
Published benchmarks expose a small set of standard vertices. Real portfolios and liabilities
rarely align exactly with them. pykdi can generate any integer maturity of at least two days,
allowing users to:
- Match a portfolio, hedge, or liability horizon more precisely.
- Build dense curve-return panels for key-rate and principal-component analysis.
- Interpolate risk in return space instead of selecting the nearest benchmark.
- Construct steepener, flattener, butterfly, and roll-down factors from consistent methodology.
- Estimate volatility, correlation, VaR, expected shortfall, and drawdown by maturity.
- Backtest maturity allocation and duration-timing rules without security-selection effects.
- Compare a traded portfolio with a fair curve-implied benchmark at its actual target duration.
These indexes are analytical marks. They are useful risk and research inputs, but they are not automatically investable or guaranteed to include every source of realized P&L.
Capabilities
- Fit Nelson-Siegel-Svensson zero curves on arbitrary maturity arrays.
- Convert fitted curves into daily fair constant-maturity gross returns.
- Add a common external carry factor, such as inflation accretion or funding carry.
- Compound returns into indexes with any positive base value.
- Transform parameter histories directly into multi-maturity index panels.
- Build custom PRE and VNA-adjusted IPCA indexes directly from the latest public mirrors.
- Report drift and adherence against all 12 conventional ANBIMA IDkA maturities.
Install With UV
uv sync --all-extras
uv.lock pins the complete Python environment. Run tools and notebooks through uv without
manually activating a virtual environment:
uv run pytest
uv run ruff check .
uv build --clear
Quick Start: Custom Indexes
from pykdi import default_kdi, default_vna_proxy
# 6M, 18M, 30M, 7Y, and 12Y on a 252-business-day basis.
maturities = [126, 378, 630, 1_764, 3_024]
pre_indexes = default_kdi(maturities, segment="pre", base_value=100.0)
ipca_indexes = default_kdi(maturities, segment="ipca", base_value=100.0)
inferred_vna = default_vna_proxy()
The index outputs are pandas DataFrames indexed by observation date with integer maturity columns.
default_kdi downloads the latest public mirror data by default. Pass irts_revision and
idka_revision explicitly when a historical reproducible snapshot is required. IPCA indexes use
the common daily VNA proxy inferred from official IDkA IPCA 2Y returns. ANBIMA-backed APIs truncate
fitted decimal rates to six places before compounding, matching the four-decimal-percent buy and
sell rates used in published IDkA calculations.
General Transform API
Fit NSS Curves
from pykdi import fit_nss_curves
rates = fit_nss_curves(
parameters,
maturity_days=[21, 63, 126, 252, 378, 504, 756],
annualization_days=252,
)
Rates are decimal annual effective rates. The input parameters can come from load_irts()["pre"],
load_irts()["ipca"], or any source satisfying the input contract below.
Calculate Fair Returns
from pykdi import constant_maturity_returns
gross_returns = constant_maturity_returns(
parameters,
maturity_days=[378, 630, 1_764],
)
simple_returns = gross_returns - 1.0
The source-independent core transform uses full-precision fitted rates by default. Pass
rate_decimal_places=6 when reproducing the ANBIMA fitted-rate convention directly through the
core API.
Add External Carry
from pykdi import apply_carry_factors
# carry_factors is a date-indexed Series of daily gross factors.
total_returns = apply_carry_factors(gross_returns, carry_factors)
For ANBIMA's IPCA-linked indexes, the external factor is the daily NTN-B VNA change. The same transform can represent another inflation index, funding accrual, collateral remuneration, or a user-defined carry process when the economics support a common multiplicative factor.
Rebase Or Transform In One Step
from pykdi import compound_index, transform_constant_maturity
levels = compound_index(total_returns, base_value=1_000.0)
levels_direct = transform_constant_maturity(
parameters,
maturity_days=[378, 630, 1_764],
carry_factors=carry_factors,
base_value=1_000.0,
)
Risk Calculations
The output is a clean maturity-by-date return matrix suitable for standard risk tooling:
import numpy as np
returns = fair_indexes.pct_change().dropna()
annualized_volatility = returns.std() * np.sqrt(252)
return_correlation = returns.corr()
historical_var_99 = -returns.quantile(0.01)
expected_shortfall_99 = -returns[returns.le(-historical_var_99)].mean()
drawdown = fair_indexes / fair_indexes.cummax() - 1.0
For portfolio applications, map holdings or liabilities to chosen maturity vertices and aggregate scenario returns using exposure weights. A dense grid can also feed PCA, covariance shrinkage, historical simulation, curve-factor regressions, or custom key-rate buckets.
Weights applied to these returns must reflect the user's P&L mapping; index weights are not automatically equivalent to market-value, DV01, or duration weights.
ANBIMA Adherence Validation
The validation compares model daily gross returns with the published-maturity IDkA return mirrors. It reports correlation, mean error, median and 95th-percentile absolute error, RMSE, maximum error, annualized tracking error, cumulative drift, annualized drift, and percentages within 0.1 bp and 1 bp for every maturity.
uv run pykdi-validate
The command uses the latest Hugging Face dataset heads by default and writes:
reports/validation_report.mdreports/validation_metrics.csvreports/top_residuals.csvreports/inferred_vna_factors.csvreports/calendar_diagnostics.json
The PRE indexes are direct adherence checks. Because the public data does not include an independent NTN-B VNA series, IPCA 2Y calibrates a same-date common carry factor and the other IPCA maturities are labeled cross-maturity checks rather than independent absolute-return validation.
The generated validation_report.md contains the current empirical results. Use
--irts-revision and --idka-revision to validate an explicitly selected historical snapshot.
Educational Notebooks
uv run jupyter lab
Open the notebooks in order:
notebooks/01_anbima_adherence.ipynbloads the latest curve and index datasets, computes every conventional IDkA return throughpykdi, compares computed and dataset returns, plots cumulative paths and residual metrics, and explains the IPCA VNA calibration limitation.notebooks/02_pykdi_usage.ipynbwalks through NSS curve fitting, arbitrary maturity indexes, external carry factors, rebasing, volatility, VaR, drawdown, correlations, and curve factors.
The committed notebooks intentionally contain no outputs or execution counts. To execute a clean copy non-interactively without changing the source notebook:
uv run jupyter nbconvert --to notebook --execute notebooks/01_anbima_adherence.ipynb --output 01_anbima_adherence.executed.ipynb
uv run jupyter nbconvert --to notebook --execute notebooks/02_pykdi_usage.ipynb --output 02_pykdi_usage.executed.ipynb
The executed copies are working artifacts and should not replace the output-free notebooks in the repository.
Input Contract
NSS parameter frames require a unique, strictly increasing DatetimeIndex and these decimal
columns:
beta1, beta2, beta3, beta4, lambda1, lambda2
All values must be finite and lambdas strictly positive. The public IRTS adapter removes a row when
all six parameters exactly repeat the immediately preceding row for the same curve. Adjacent rows
must otherwise represent consecutive observations on the chosen business calendar. pykdi does
not guess holidays or synthesize a missing curve observation.
Project Layout
src/pykdi/ Core transforms, public data adapters, and validation
tests/ Formula, schema, calendar, metric, and CLI tests
notebooks/ Output-free adherence and package-usage walkthroughs
reports/ Current ANBIMA adherence artifacts
Data Provenance
The notebook datasets are author-maintained public mirrors, not authenticated ANBIMA API responses.
Defaults follow each mirror's mutable main branch so results can change as new observations or
corrections arrive. Explicit revisions provide reproducible historical reads but do not replace
authoritative source lineage. Core pykdi transforms are data-source agnostic and can be used with
internally governed or independently sourced parameter histories.
Interpretation And Limitations
A pykdi index captures fair zero-curve repricing and carry under its stated conventions. It does
not automatically include bid-ask spreads, transaction costs, market impact, security coupons,
liquidity premiums, financing constraints, or parameter-estimation uncertainty. IPCA 2Y fits by
construction because it calibrates the inferred VNA factor; the other IPCA maturities are relative
cross-maturity checks rather than independent VNA validation. Custom vertices remain model-implied
where no published benchmark exists.
References
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pykdi_quant-0.2.0.tar.gz.
File metadata
- Download URL: pykdi_quant-0.2.0.tar.gz
- Upload date:
- Size: 213.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2473b3270e866117c95ace7d52c39ca46eddf6d4714168f2877c9d0ef9dcefc4
|
|
| MD5 |
ced355eed60fc464a662a478eded7c1c
|
|
| BLAKE2b-256 |
2e3a332c0dfab50e69d06c034c4cfec970edc543fc337f4ab71c687835c1add1
|
Provenance
The following attestation bundles were made for pykdi_quant-0.2.0.tar.gz:
Publisher:
publish.yml on integerQuant/pykdi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pykdi_quant-0.2.0.tar.gz -
Subject digest:
2473b3270e866117c95ace7d52c39ca46eddf6d4714168f2877c9d0ef9dcefc4 - Sigstore transparency entry: 2213243807
- Sigstore integration time:
-
Permalink:
integerQuant/pykdi@2c563e27d754c69d587318839b2db7657c1a1924 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/integerQuant
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2c563e27d754c69d587318839b2db7657c1a1924 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pykdi_quant-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pykdi_quant-0.2.0-py3-none-any.whl
- Upload date:
- Size: 16.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a6c87ef7352eed95673053926051cd743a4ef3329613b4cbdd858f35e7a4eaa
|
|
| MD5 |
50d23b24e197d2667226f6afc4c26620
|
|
| BLAKE2b-256 |
7968ca1f1ee7a35df357ccfc798ff0e9dd1aa82380b32627f10085ffeb788969
|
Provenance
The following attestation bundles were made for pykdi_quant-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on integerQuant/pykdi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pykdi_quant-0.2.0-py3-none-any.whl -
Subject digest:
0a6c87ef7352eed95673053926051cd743a4ef3329613b4cbdd858f35e7a4eaa - Sigstore transparency entry: 2213243859
- Sigstore integration time:
-
Permalink:
integerQuant/pykdi@2c563e27d754c69d587318839b2db7657c1a1924 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/integerQuant
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2c563e27d754c69d587318839b2db7657c1a1924 -
Trigger Event:
push
-
Statement type: