Skip to main content

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.md
  • reports/validation_metrics.csv
  • reports/top_residuals.csv
  • reports/inferred_vna_factors.csv
  • reports/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:

  1. notebooks/01_anbima_adherence.ipynb loads the latest curve and index datasets, computes every conventional IDkA return through pykdi, compares computed and dataset returns, plots cumulative paths and residual metrics, and explains the IPCA VNA calibration limitation.
  2. notebooks/02_pykdi_usage.ipynb walks 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pykdi_quant-0.2.0.tar.gz (213.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pykdi_quant-0.2.0-py3-none-any.whl (16.8 kB view details)

Uploaded Python 3

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

Hashes for pykdi_quant-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2473b3270e866117c95ace7d52c39ca46eddf6d4714168f2877c9d0ef9dcefc4
MD5 ced355eed60fc464a662a478eded7c1c
BLAKE2b-256 2e3a332c0dfab50e69d06c034c4cfec970edc543fc337f4ab71c687835c1add1

See more details on using hashes here.

Provenance

The following attestation bundles were made for pykdi_quant-0.2.0.tar.gz:

Publisher: publish.yml on integerQuant/pykdi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for pykdi_quant-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0a6c87ef7352eed95673053926051cd743a4ef3329613b4cbdd858f35e7a4eaa
MD5 50d23b24e197d2667226f6afc4c26620
BLAKE2b-256 7968ca1f1ee7a35df357ccfc798ff0e9dd1aa82380b32627f10085ffeb788969

See more details on using hashes here.

Provenance

The following attestation bundles were made for pykdi_quant-0.2.0-py3-none-any.whl:

Publisher: publish.yml on integerQuant/pykdi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page